iOS 27 introduces RAW 9, a major update to Core Image's RAW image processing pipeline that uses a tiled CoreML model running on the Apple Neural Engine to dramatically improve demosaicing and denoising quality. It also adds explicit tile size control and temporary buffer reuse to the CIImageProcessor API.
⢠RAW pipeline updated to version 9 (biggest update since 2006), combining demosaic and denoise via a tiled CoreML model on the Apple Neural Engine
⢠New CIRAWFilter.supportedCameraModels(for:) class method to query which camera models support a given decoder version
⢠CIImageProcessor gains explicit output tile size control via a tiles array passed to apply()
⢠CIImageProcessorOutput gains requestTemporaryPixelBuffer(identifier:) for recycled scratch buffers across tile callbacks
⢠RAW 9 delivers significantly sharper, cleaner output especially at high ISOs ā with a CoreML-powered pipeline that combines demosaic and denoise in a single pass on the Neural Engine
⢠Enables photographers and pro camera apps to reprocess years-old RAW files with state-of-the-art quality by simply opting into version9
⢠New CIImageProcessor APIs (explicit tiling and temporary CVPixelBuffer recycling) reduce memory pressure and improve render throughput for custom CoreML-based image pipelines
Loads a RAW file using CIRAWFilter, opts into the RAW 9 decoder, and renders a live-adjustable exposure + sharpness preview into a SwiftUI view backed by a Metal texture.
import SwiftUI
import CoreImage
import CoreImage.CIFilterBuiltins
import MetalKit
// MARK: - ViewModel
@MainActor
final class RAWEditorViewModel: ObservableObject {
@Published var exposure: Float = 0.0
@Published var sharpness: Float = 0.5
@Published var renderedImage: CGImage?
private var rawFilter: CIRAWFilter?
private let context: CIContext
init() {
// One context per view, caching enabled for interactive editing
let device = MTLCreateSystemDefaultDevice()!
context = CIContext(
mtlDevice: device,
options: [
.cacheIntermediates: true
]
)
}
func loadRAW(url: URL) {
guard let filter = CIRAWFilter(imageURL: url) else {
print("Failed to create CIRAWFilter for \(url.lastPathComponent)")
return
}
// Check RAW 9 support for this file's camera model
if let versions = filter.supportedDecoderVersions,
versions.contains(.version9) {
filter.decoderVersion = .version9
print("RAW 9 enabled")
} else {
print("RAW 9 not available for this camera model, using default")
}
// Scale down for screen resolution ā critical for performance
filter.scaleFactor = 0.25
rawFilter = filter
render()
}
func render() {
guard let filter = rawFilter else { return }
filter.exposure = exposure
filter.sharpnessAmount = sharpness
// colorNoiseReductionAmount has no effect in RAW 9 ā omit it
guard let output = filter.outputImage else { return }
renderedImage = context.createCGImage(
output,
from: output.extent
)
}
func queryRAW9Cameras() {
let models = CIRAWFilter.supportedCameraModels(for: .version9)
print("RAW 9 supports \(models.count) camera models")
models.prefix(5).forEach { print(" ā¢", $0) }
}
}
// MARK: - View
struct RAWEditorView: View {
@StateObject private var vm = RAWEditorViewModel()
@State private var showingPicker = false
var body: some View {
NavigationStack {
VStack(spacing: 20) {
if let cgImage = vm.renderedImage {
Image(cgImage, scale: 1.0, label: Text("RAW Preview"))
.resizable()
.scaledToFit()
.cornerRadius(12)
.padding()
} else {
ContentUnavailableView(
"No RAW File",
systemImage: "photo.badge.plus",
description: Text("Tap Open to load a RAW image")
)
}
GroupBox("RAW 9 Controls") {
VStack {
LabeledContent("Exposure") {
Slider(value: $vm.exposure, in: -3...3)
.onChange(of: vm.exposure) { _, _ in vm.render() }
}
LabeledContent("Sharpness") {
Slider(value: $vm.sharpness, in: 0...1)
.onChange(of: vm.sharpness) { _, _ in vm.render() }
}
}
}
.padding(.horizontal)
Button("Query RAW 9 Camera Support") {
vm.queryRAW9Cameras()
}
.buttonStyle(.bordered)
}
.navigationTitle("RAW 9 Editor")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Open") { showingPicker = true }
}
}
.fileImporter(
isPresented: $showingPicker,
allowedContentTypes: [.rawImage],
allowsMultipleSelection: false
) { result in
if case .success(let urls) = result, let url = urls.first {
vm.loadRAW(url: url)
}
}
}
}
}
#Preview {
RAWEditorView()
}RAW 9 is NOT enabled by default ā you must explicitly check supportedDecoderVersions for .version9 and set decoderVersion to .version9. colorNoiseReductionAmount, detailAmount, and moireReductionAmount properties have no effect in RAW 9. For interactive editing set cacheIntermediates: true on your CIContext; for batch export set it to false and raise the memoryLimit option.
RAW 9 CoreML model runs on Apple Neural Engine; older devices without ANE will fall back to CPU/GPU. Apple Pro RAW (iPhone) and DNG files are automatically supported; camera model list for RAW 9 expands via OTA updates.
More iOS 27 APIs land every week.
Get notified when new capabilities are published ā no noise, just signal.