Article

Capturing Photos in RAW Format

Get minimally processed data from the camera's image sensor for custom processing.

Overview

By default, iOS and a device's image signal processor perform a number of operations on the data from a camera's image sensor to create a displayable image. These manipulations include dead pixel interpolation, demosaicing, white balance, noise reduction, tone compensation, and sharpening. This processing occurs as part of capture, creating a high-quality image compressed for easy storage and display.

Sometimes it's useful to postpone such processing. Capturing in a RAW data format lets you get virtually unadulterated data from the camera's image sensor. You can't directly display a RAW-format image, and it requires much more data storage than a finished HEIF/HEVC or JPEG file. However, you can process the RAW data to modify the image with much greater fidelity than is possible by postprocessing—for example, RAW formats store much more dynamic range than processed formats, so a user can choose different exposure levels for an image after shooting.

In iOS, capturing in RAW format and saving results requires only minor changes to the basic photography workflow covered in Capturing Still and Live Photos.

Choose RAW Format Settings

Typical RAW shooting workflows capture both a RAW format image and a processed (HEIF/HEVC or JPEG) image for each shot: saving the RAW data allows the user to process it later, but keeping a processed copy makes previewing and general use more convienent. To shoot in both formats, use init(rawPixelFormatType:processedFormat:) to create an AVCapturePhotoSettings object before capture.

// Use the current device's first available RAW format.
guard let availableRawFormat = self.photoOutput.availableRawPhotoPixelFormatTypes.first else { return }
let photoSettings = AVCapturePhotoSettings(rawPixelFormatType: availableRawFormat,
                                           processedFormat: [AVVideoCodecKey : AVVideoCodecType.hevc])

// RAW capture is incompatible with digital image stabilization.
photoSettings.isAutoStillImageStabilizationEnabled = false

// Shoot the photo, using a custom class to handle capture delegate callbacks.
let captureProcessor = RAWCaptureProcessor()
self.photoOutput.capturePhoto(with: photoSettings, delegate: captureProcessor)

Handle Results

The photo output calls your delegate's photoOutput(_:didFinishProcessingPhoto:error:) method at least once for each format you've requested, and possibly additional times depending on your capture settings. For example, if you request RAW+HEIF capture in a three-exposure bracket, the photo output calls your delegate's didFinishProcessingPhoto method six times (2 formats × 3 exposures), providing six AVCapturePhoto objects.

To keep track of multiple results, compare the photoCount from each photo to the expectedPhotoCount of your resolved settings. When those numbers are equal, you've received all results from the capture. Alternatively, hold all results until the photo output calls your photoOutput(_:didFinishCaptureFor:error:) method, and save all output in that method.

To save output, use the photo object's fileDataRepresentation() method to get the RAW image data packaged in the industry-standard DNG file format (along with capture metadata and any auxiliary images you requested in capture settings).

Save RAW and Processed Image Data to the Photos Library

To save RAW images in the user's Photos library, you'll need to create a single Photos asset that associates the RAW data (saved in DNG format) with a processed version (HEIF/HEVC or JPEG) for use by apps that can't display RAW images. To do so, use the PHAssetCreationRequest class, specifying the processed image as the asset's main PHAssetResourceType.photo resource and the DNG version as an PHAssetResourceType.alternatePhoto resource. As in Saving Captured Photos, you'll need to wrap that request in a PHPhotoLibrary change block, and first make sure that your app has the user's permission to access Photos.

class RAWCaptureProcessor: NSObject, AVCapturePhotoCaptureDelegate {
    var rawImageFileURL: URL?
    var compressedFileData: Data?
    
    // Hold on to the separately delivered RAW file and compressed photo data until capture is finished.
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard error != nil else { print("Error capturing photo: \(error!)"); return }

        if photo.isRawPhoto {
            // Save the RAW (DNG) file data to a URL.
            let dngFileURL = self.makeUniqueTempFileURL(extension: "dng")
            do {
                try photo.fileDataRepresentation()!.write(to: dngFileURL)
            } catch {
                fatalError("couldn't write DNG file to URL")
            }
        } else {
            self.compressedFileData = photo.fileDataRepresentation()!
        }
    }
    
    // After both RAW and compressed versions are delivered, add them to the Photos Library.
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
        guard error != nil else { print("Error capturing photo: \(error!)"); return }
        guard let rawURL = self.rawImageFileURL, let compressedData = self.compressedFileData
            else { return }
        
        PHPhotoLibrary.requestAuthorization { status in
            guard status == .authorized else { return }
            
            PHPhotoLibrary.shared().performChanges({
                // Add the compressed (HEIF) data as the main resource for the Photos asset.
                let creationRequest = PHAssetCreationRequest.forAsset()
                creationRequest.addResource(with: .photo, data: compressedData, options: nil)
                
                // Add the RAW (DNG) file as an altenate resource.
                let options = PHAssetResourceCreationOptions()
                options.shouldMoveFile = true
                creationRequest.addResource(with: .alternatePhoto, fileURL: rawURL, options: options)
            }, completionHandler: self.handlePhotoLibraryError)
        }

    }
    
    func handlePhotoLibraryError(success: Bool, error: Error?) {}

    func makeUniqueTempFileURL(extension type: String) -> URL {
        let temporaryDirectoryURL = FileManager.default.temporaryDirectory
        let uniqueFilename = ProcessInfo.processInfo.globallyUniqueString
        let urlNoExt = temporaryDirectoryURL.appendingPathComponent(uniqueFilename)
        let url = urlNoExt.appendingPathExtension(type)
        return url
    }
}

See Also

More Capture Options

Capturing Photos with Depth

Get a depth map with a photo to create effects like the system camera's Portrait mode (on compatible devices).

Capturing a Bracketed Photo Sequence

Capture several photos at once, varying parameters like exposure duration or light sensitivity.

Capturing Uncompressed Image Data

Get processed image data without compression to use for filtering or lossless output.

Capturing Thumbnail and Preview Images

Enable delivery of reduced-size images with the main image in a photo capture.