Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 Graphics & GamesPhotos & Camera

WWDC21 · 27 min · Graphics & Games / Photos & Camera

Capture and process ProRAW images

When you support ProRAW in your app, you can help photographers easily capture and edit images by combining standard RAW information with Apple’s advanced computational photography techniques. We’ll take you through an overview of the format, including the look and feel of ProRAW images, quality metrics, and compatibility with your app. From there, we’ll explore how you can incorporate ProRAW into your app at every stage of the production pipeline, including capturing imagery with AVFoundation, storage using PhotoKit, and editing with Core Image.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 29 snippets

Setting up device and session swift · at 7:52 ↗
// Use the .photo preset

private let session = AVCaptureSession()
private func configureSession() {
  session.beginConfiguration()
	session.sessionPreset = .photo
	//...
}
Setting up device and session swift · at 8:03 ↗
// Or optionally find a format that supports highest quality photos

guard let format = device.formats.first(where: { $0.isHighestPhotoQualitySupported }) else {
  // handle failure to find a format that supports highest quality stills
} 
do 
{	
  try device.lockForConfiguration()
  {
    // ...
  }
  device.unlockForConfiguration()
} 
catch 
{
  // handle the exception
}
Setting up photo output 1 swift · at 8:39 ↗
// Enable ProRAW on the photo output

private let photoOutput = AVCapturePhotoOutput()
private func configurePhotoOutput() {
  photoOutput.isHighResolutionCaptureEnabled = true
	photoOutput.isAppleProRAWEnabled = photoOutput.isAppleProRAWSupported
	//...
}
Setting up photo output 2 swift · at 8:59 ↗
// Select the desired photo quality prioritization

private let photoOutput = AVCapturePhotoOutput()
private func configurePhotoOutput() {
	photoOutput.isHighResolutionCaptureEnabled = true
	photoOutput.isAppleProRAWEnabled           = photoOutput.isAppleProRAWSupported
	photoOutput.maxPhotoQualityPrioritization  = .quality // or .speed .balanced
  //...
}
Prepare for ProRAW capture 1 swift · at 9:26 ↗
// Find a supported ProRAW pixel format

guard let proRawPixelFormat = photoOutput.availableRawPhotoPixelFormatTypes.first(
  where: {
    AVCapturePhotoOutput.isAppleProRAWPixelFormat($0) 
  }) 
else {
	// Apple ProRAW is not supported with this device / format
}

// For Bayer RAW pixel format use

AVCapturePhotoOutput.isBayerRAWPixelFormat()
Prepare for ProRAW capture 2 swift · at 10:09 ↗
// Create photo settings for ProRAW only capture

let photoSettings = AVCapturePhotoSettings(rawPixelFormatType: proRawPixelFormat)

// Create photo settings for processed photo + ProRAW capture

guard let processedPhotoCodecType = photoOutput.availablePhotoCodecTypes.first 
else 
{
	// handle failure to find a processed photo codec type
}
let photoSettings = AVCapturePhotoSettings(rawPixelFormatType: proRawPixelFormat,
	processedFormat: [AVVideoCodecKey: processedPhotoCodecType])
Prepare for ProRAW capture 3 swift · at 10:53 ↗
// Select a supported thumbnail codec type and thumbnail dimensions

guard let thumbnailPhotoCodecType = photoSettings.availableRawEmbeddedThumbnailPhotoCodecTypes.first 
else 
{
  // handle failure to find an available thumbnail photo codec type
}

let dimensions = device.activeFormat.highResolutionStillImageDimensions

photoSettings.rawEmbeddedThumbnailPhotoFormat = [
  AVVideoCodecKey: thumbnailPhotoCodecType,
  AVVideoWidthKey: dimensions.width,
  AVVideoHeightKey: dimensions.height]
Prepare for ProRAW capture 4 swift · at 11:08 ↗
// Select the desired quality prioritization for the capture

photoSettings.photoQualityPrioritization = .quality // or .speed .balanced

// Optionally, request a preview image

if let previewPixelFormat = photoSettings.availablePreviewPhotoPixelFormatTypes.first
{	
  photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPixelFormat]
}

// Capture!

photoOutput.capturePhoto(with: photoSettings, delegate: delegate)
Consuming captured ProRAW 1 swift · at 11:44 ↗
func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishProcessingPhoto photo: AVCapturePhoto,
                 error: Error?) 
{
  guard error == nil 
  else 
  {
    // handle failure from the photo capture
  }
	if let preview = photo.previewPixelBuffer 
  { 
    // photo.previewCGImageRepresentation()
    // display the preview
  }
	if photo.isRawPhoto 
  {
		guard let proRAWFileDataRepresentation = photo.fileDataRepresentation() 
    else 
    {
      // handle failure to get ProRAW DNG file data representation
    }
		guard let proRAWPixelBuffer = photo.pixelBuffer 
    else 
    {
      // handle failure to get ProRAW pixel data
    }
		// use the file or pixel data
	}
Consuming captured ProRAW 2 swift · at 12:52 ↗
// Provide settings for lossless compression with less bits

class AppleProRAWCustomizer: NSObject, AVCapturePhotoFileDataRepresentationCustomizer 
{
	func replacementAppleProRAWCompressionSettings(for photo: AVCapturePhoto,
                                                 defaultSettings: [String : Any],
                                                 maximumBitDepth: Int) -> [String : Any] 
  {
    return [AVVideoAppleProRAWBitDepthKey: min(10, maximumBitDepth),
            AVVideoQualityKey: 1.00]
  }
}
Consuming captured ProRAW 3 swift · at 13:35 ↗
// Provide settings for lossy compression

class AppleProRAWCustomizer: NSObject, AVCapturePhotoFileDataRepresentationCustomizer 
{
	func replacementAppleProRAWCompressionSettings(
    for photo: AVCapturePhoto,
    defaultSettings: [String : Any],
    maximumBitDepth: Int) -> [String : Any] 
  {
    return [AVVideoAppleProRAWBitDepthKey: min(8, maximumBitDepth),
            AVVideoQualityKey: 0.90]
  }
}
Consuming captured ProRAW 4 swift · at 13:51 ↗
// Customizing the compression settings for the captured ProRAW photo

func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishProcessingPhoto photo: AVCapturePhoto,
                 error: Error?) 
{
  guard error == nil 
  else 
  {
    // handle failure from the photo capture
  }
    if photo.isRawPhoto 
  {
		let customizer = AppleProRAWCustomizer()
		guard let customizedFileData = photo.fileDataRepresentation(with: customizer) 
    else 
    {
      // handle failure to get customized ProRAW DNG file data representation
    }
		// use the file data
	}
Saving a ProRAW asset with PhotoKit swift · at 15:19 ↗
PHPhotoLibrary.shared().performChanges 
{
    let creationRequest = PHAssetCreationRequest.forAsset()
  
    creationRequest.addResource(with:.photo, 
                                fileURL:proRawFileURL, 
                                options:nil)
} 
completionHandler: 
{ 
  success, error in
  // handle the success and possible error
}
Fetching RAW assets from the photo library swift · at 15:45 ↗
// New enum PHAssetCollectionSubtype.smartAlbumRAW

PHAssetCollection.fetchAssetCollections(with: .smartAlbum, 
                                        subtype: .smartAlbumRAW, 
                                        options: nil)
Retrieving RAW resources from a PHAsset swift · at 17:16 ↗
let resources = PHAssetResource.assetResources(for: asset)
for resource in resources 
{
  if (resource.type == .photo || resource.type == .alternatePhoto) 
  {
    if let resourceUTType = UTType(resource.uniformTypeIdentifier) 
    {
      if resourceUTType.conforms(to: UTType.rawImage) 
      {
        let resourceManager = PHAssetResourceManager.default()
        resourceManager.requestData(for: resource, options: nil) 
        { 
          data in
          // use the data
        } 
        completionHandler: 
        { 
          error in
          // handle any error 
        }
      }
    }
  }
}
Getting CIImages from a ProRAW swift · at 18:28 ↗
// Getting the preview image

let isrc  = CGImageSourceCreateWithURL(url as CFURL, nil)
let cgimg = CGImageSourceCreateThumbnailAtIndex(isrc!, 0, nil)

return CIImage(cgImage: cgimg)
Getting CIImages from a ProRAW 2 (New in iOS 15 and macOS 12) swift · at 18:36 ↗
// Getting the preview image

let rawFilter = CIRAWFilter(imageURL: url)

return rawFilter.previewImage
Getting CIImages from a ProRAW 3 swift · at 18:44 ↗
// Getting the preview image

let rawFilter = CIRAWFilter(imageURL: url)

return rawFilter.previewImage

// Getting segmentation mattes images

return CIImage(contentsOf: url,
               options: [.auxiliarySemanticSegmentationSkinMatte : true])
Getting CIImages from a ProRAW 4 swift · at 18:56 ↗
// Getting the preview image

let rawFilter = CIRAWFilter(imageURL: url)

return rawFilter.previewImage

// Getting segmentation mattes images

let rawFilter = CIRAWFilter(imageURL: url)

return rawFilter.semanticSegmentationSkinMatte
Getting CIImages from a ProRAW 5 swift · at 19:09 ↗
// Getting the primary image

return CIImage(contentsOf: url, options:nil)

let rawFilter = CIFilter(imageURL: url, options:nil)

return rawFilter.outputImage
Applying common user adjustments swift · at 19:31 ↗
func get_adjusted_raw_image (url: URL) -> CIImage?
{
    // Load the image
    let rawFilter = CIFilter(imageURL: url, options:nil)
    
    // Change one or more filter inputs
    rawFilter.setValue(value, forKey: CIRAWFilterOption.keyName.rawValue)
   
    // Get the adjusted image
    return rawFilter.outputImage
}
Applying common user adjustments 2 swift · at 19:54 ↗
func get_adjusted_raw_image (url: URL) -> CIImage?
{
    // Load the image
    let rawFilter = CIRAWFilter(imageURL: url)
    
    // Change one or more filter inputs
    rawFilter.property = value
   
    // Get the adjusted image
    return rawFilter.outputImage
}
Applying common user adjustments 3 swift · at 20:17 ↗
// Exposure

rawFilter.exposure = -1.0

// Temperature and tint

rawFilter.neutralTemperature = 6500  // in °K
rawFilter.neutralTint        = 0.0

// Sharpness

rawFilter.sharpnessAmount = 0.5

// Local tone map strength

rawFilter.localToneMapAmount = 0.5
Getting linear scene-referred output 1 swift · at 21:40 ↗
// Turn off the filter inputs that apply the default look to the RAW

rawFilter.baselineExposure      = 0.0
rawFilter.shadowBias            = 0.0
rawFilter.boostAmount           = 0.0
rawFilter.localToneMapAmount    = 0.0
rawFilter.isGamutMappingEnabled = false

let linearRawImage = rawFilter.outputImage
Getting linear scene-referred output 2 swift · at 22:00 ↗
// Use the linear image with other filters

let histogram = CIFilter.areaHistogram()

histogram.inputImage = linearRawImage
histogram.extent     = linearRawImage.extent

// Or render it to a RGBAh buffer

let rd = CIRenderDestination(bitmapData: data.mutableBytes,
                             width: 􀠪imageWidth, 
                             height: 􀠪imageHeight, 
                             bytesPerRow: 􀠪rowBytes,
                             format: .RGBAh)

rd.colorSpace = CGColorSpace(name: CGColorSpace.extendedLinearITUR_2020)

let task = context.startTask(toRender: rawFilter.outputImage, 
                             from: rect, 
                             to: rd, 
                             at: point)

task.waitUntilCompleted()
Saving edits to other file formats 1 (8-bit HEIC) swift · at 23:54 ↗
// Saving to 8-bit HEIC

try ciContext.writeHEIFRepresentation(of: rawFilter!.outputImage!,
                                      to: theURL,
                                      format: .RGBA8,
                                      colorSpace: CGColorSpace(name: CGColorSpace.displayP3)!,
                                      options: [:])
Saving edits to other file formats 2 (10-bit HEIC) swift · at 24:12 ↗
// Saving to 10-bit HEIC

try ciContext.writeHEIF10Representation(of: rawFilter!.outputImage!,
                                        to: theURL,
                                        format: .RGBA8,
                                        colorSpace: CGColorSpace(name: CGColorSpace.displayP3)!, 
                                        options: [:])
Displaying to a Metal Kit View in EDR on Mac swift · at 24:51 ↗
class MyView : MTKView {
  var context: CIContext
  var commandQueue: MTLCommandQueue
  //...
}
Displaying to a Metal Kit View in EDR on Mac swift · at 25:13 ↗
// Create a Metal Kit View subclass

class MyView : MTKView {
  var context: CIContext
  var commandQueue: MTLCommandQueue
  //...
}

// Init your Metal Kit View for EDR

colorPixelFormat = MTLPixelFormat.rgba16Float

if let caml = layer as? CAMetalLayer {
  caml.wantsExtendedDynamicRangeContent = true
  //...
}

// Ask the filter for an image designed for EDR and render it

rawFilter.extendedDynamicRangeAmount = 1.0

context.startTask(toRender: rawFilter.outputImage, 
                  from: rect, 
                  to: rd, 
                  at: point)

Resources