2026 Photos & Camera
WWDC26 · 16 min · Photos & Camera
Enhance RAW image processing with Core Image
Harness the power of version 9 of the Core Image RAW processing APIs to dramatically improve image quality in your apps, with improved sharpness and more defined color, while using the Apple Neural Engine for optimal performance. Take advantage of the CIRAWFilter API to let your users edit RAW photos by changing exposure, noise reduction, sharpness, contrast and more. And explore new CIImageProcessor APIs that optimize performance by giving you precise control over tile sizing and buffer management.
Watch at developer.apple.com ↗Chapters
- 0:00 — Introduction
- 0:52 — How Core Image supports RAW
- 2:48 — The evolution of RAW support
- 3:33 — RAW 9 overview
- 3:56 — RAW 9 quality improvements
- 5:50 — Enable and edit RAW 9 with CIRAWFilter API
- 8:33 — RAW 9 performance overview
- 9:19 — Interactive editing
- 10:52 — Exporting to other formats
- 11:50 — New CIImageProcessor features
Code shown on screen · 3 snippets
Contact for exports
let exportCtx = CIContext(options : [
.cacheIntermediate : false,
.memoryLimit : 512 ]) CIImageProcessor with explicit output tile sizes
import CoreImage
class MyProcessor: CIImageProcessorKernel {
override class func roi(forInput input: Int32,
arguments: [String : Any]?,
outputRect: CGRect) -> CGRect { return outputRect }
override class func process(with inputs: [CIImageProcessorInput]?,
arguments: [String : Any]?,
output: CIImageProcessorOutput) throws {
guard let input = inputs?.first,
let iBuffer = input.pixelBuffer,
let oBuffer = output.pixelBuffer else { return }
let iRegion = input.region
let oRegion = output.region // controlled by Core Image
// MyCopyBuffer(iBuffer,iRegion, oBuffer,oRegion)
}
}
let extent = inImg.extent
let tileSize = 512.0 // whatever tile size you want
var tiles: [CIVector] = []
for y in stride(from: extent.minY, to: extent.maxY, by: tileSize) {
for x in stride(from: extent.minX, to: extent.maxX, by: tileSize) {
let tile = CGRect(x: x, y: y,
width: min(tileSize, extent.maxX - x),
height: min(tileSize, extent.maxY - y))
tiles.append(CIVector(cgRect: tile))
}
}
let result = try MyProcessor.apply(withTiledExtent: tiles, inputs: [inImg], arguments: [:]) CIImageProcessor using temporary PixelBuffer
import CoreImage
class MyProcessor: CIImageProcessorKernel {
override class func process(with inputs: [CIImageProcessorInput]?,
arguments: [String: Any]?,
output: CIImageProcessorOutput) throws {
guard let input = inputs?.first,
let srcPixelBuffer = input.pixelBuffer,
let dstPixelBuffer = output.pixelBuffer else { return }
// Get a scratch buffer from Core Image's cache
guard let scratch = output.temporaryPixelBuffer(identifier : "myScratch",
format: kCVPixelFormatType_64RGBAHalf,
width: Int(output.region.width),
height: Int(output.region.height),
pixelBufferAttributes: nil) else { return }
// Step 1: copy input CVPixelBuffer → scratch
// Step 2: process pixels in scratch
// Step 3: copy scratch → output CVPixelBuffer
}
} Resources
Related sessions
-
27 min -
18 min