Repository navigation
[0.2.0] - 2026-10-09
iOS 26 and iOS 27 capture APIs, a rebuilt logging layer, and a round of capture, session and accessibility fixes. Building now requires Xcode 27 (Swift 6.4, iOS 27 SDK); the deployment target stays iOS 18, and every new API is availability-gated. Breaking changes are marked in Changed.
Added
- Priority modes and variable aperture (iOS 27):
PRMExposureValue,PRMExposureAxes,setExposure(aperture:shutterSeconds:iso:),setShutterPriority(seconds:),setISOPriority(_:),setAperturePriority(_:),setAutoApertureRateLimit(_:),exposurePriorityAxes; device helpersprm_setExposure(aperture:shutterSeconds:iso:completion:),prm_supportsExposure(...),prm_lensApertureRange,prm_autoExposureAxes,prm_setAutoApertureRateLimit(_:). StatelensAperture,autoExposureAxes; deviceapertureRange,recommendedApertureStops. - Exposure signals (iOS 27):
PRMExposureSignal,setExposureSignals(_:),prm_setExposureSignals(_:),prm_activeExposureSignals; stateactiveExposureSignals, devicesupportedExposureSignals. - Lens lock (iOS 27):
lockLens(_:),prm_lockPrimaryConstituent(to:),prm_isPrimaryConstituentLocked; stateisPrimaryConstituentLocked, devicesupportsPrimaryConstituentLock. - Focus and exposure rects of interest (iOS 26):
setFocusAndExposure(focusMode:exposureMode:in:monitorSubjectAreaChange:),defaultFocusRect(for:),prm_setFocusAndExposure(focusMode:exposureMode:in:monitorSubjectAreaChange:),prm_defaultFocusRect(for:),prm_setExposurePointOfInterest(_:mode:); devicesupportsFocusRectOfInterest,supportsExposureRectOfInterest. - Calibrated white balance presets (iOS 26):
PRMWhiteBalancePreset.temperatureAndTint. - Nominal focal lengths (iOS 26):
PRMLens.FocalLengthSource/focalLengthSource,prm_nominalFocalLength35mm. - Subject tracking (iOS 27):
setContinuousAutoFocusTrackingEnabled(_:),setContinuousAutoFocusTrackingBias(_:),prm_setContinuousAutoFocusTracking(_:),prm_setContinuousAutoFocusTrackingBias(_:retargetingAt:)and theprm_isContinuousAutoFocusTracking…/prm_continuousAutoFocusTrackingBiasproperties; state for enabled, subject acquired and bias; devicesupportsContinuousAutoFocusTracking. - Detected objects:
PRMDetectedObject,PRMMetadataRouter(withlastFocusTrackedObject),detectedObjectsStream(),setMetadataObjectTypes(_:),PRMCameraConfiguration.includesMetadataOutputandmetadataObjectTypes. - Cinematic Video (iOS 26):
setCinematicVideoEnabled(_:targetPhotoOutputAttached:),PRMCameraConfiguration.enableCinematicVideo,PRMCinematicFocusMode,PRMCinematicFocusRequestwithsetCinematicFocus(_:)/prm_setCinematicFocus(_:),setCinematicSimulatedAperture(_:),PRMSceneMonitoringStatus/prm_cinematicSceneStatuses, formatprm_cinematicFrameRateRangeandprm_simulatedApertureRange, and on iOS 27PRMCinematicMetadataCapturewithsetCinematicMetadataCapture(_:). StateisCinematicVideoCaptureEnabled,cinematicSimulatedAperture,cinematicSceneStatuses,isCinematicVideoMetadataCaptureEnabled; devicesupportsCinematicVideo,cinematicVideoDeviceType,cinematicZoomRange,simulatedApertureRange,cinematicFrameRateRange, andPRMCameraDevice.cinematicVideoDeviceType(at:). Which cameras run Cinematic Video depends on the iPhone (Apple documents the back Dual Wide and front TrueDepth cameras; an iPhone 18 Pro Max runs it on the back wide camera), never the Triple camera Pro iPhones open by default: enabling switches to a camera that supports it and disabling switches back,switchCamera(to:)lands on the new position's Cinematic camera while it's on, andenableCinematicVideoopens that camera at configure. Enabling it is refused while the 48MP format is wanted. Apps drivingPRMCameraSessiondirectly callreapplyCinematicVideoCaptureIfNeeded()after a camera switch (PRMCameradoes it for you). - Focus safety:
PRMCamera.setFocusMode(_:)andPRMCameraSession.withVideoDevice(_:), which checks Cinematic Video and writes to the device in one step (AVFoundation raises on focus-mode changes while Cinematic Video is on).setFocusAndExposure(focusMode:exposureMode:at:monitorSubjectAreaChange:), its rect variant andprm_setFocusAndExposuretake anilexposure mode to focus without touching exposure, so a tap keeps a manual exposure. - Session health:
PRMDeferredStart/PRMCameraConfiguration.deferredStart(iOS 26),lensSmudgeDetectionInterval,setLensSmudgeDetection(interval:),PRMLensSmudgeStatusandprm_lensSmudgeStatus(iOS 26),PRMLowLightVideoNoiseReductionwithsetLowLightVideoNoiseReduction(_:)(iOS 27),enableBluetoothHighQualityRecording(iOS 26),PRMSystemPressure; statelensSmudgeStatus,isLowLightVideoNoiseReductionActive,systemPressure,interruptionReason; devicesupportsLensSmudgeDetection,supportsLowLightVideoNoiseReduction. - Dynamic aspect ratio and Smart Framing (iOS 26):
PRMAspectRatio,PRMVideoDimensions,PRMFraming,setDynamicAspectRatio(_:),supportedFramings(),setSmartFraming(enabledFramings:),framingRecommendationStream()(the current recommendation first, then changes),applyFraming(_:),prm_dynamicAspectRatio,prm_dynamicDimensions,prm_setDynamicAspectRatio(_:timeout:); statedynamicAspectRatio,dynamicDimensions; devicesupportedDynamicAspectRatios,supportsSmartFraming. - AirPods Camera Control (iOS 26):
PRMCaptureSound,PRMCaptureEventHelper.primarySound/secondarySound/usesCustomCaptureSounds. - Capture orientation:
PRMPhotoSettings.rotationAngle(_:)rotates the saved photo; passPRMRotationCoordinator.currentCaptureRotationAngleso landscape shots save upright. - Session controls:
PRMCameraSession.setZoom(_:),rampZoom(to:rate:),setFrameRate(_:allowFormatChange:),resetFrameRate(),enableDepthFormat(),setHighResolutionPhotoFormat(_:)andsetStabilization(_:), each checked and applied in one step;attachDepthDataOutput(delegate:queue:filteringEnabled:)/detachDepthDataOutput()for a live depth stream the session keeps across reconfigures;wantsRunningandrestartAfterMediaServicesReset(). - Errors:
PRMSessionError.captureFailed(_:)carries AVFoundation'sAVError(so its code, such as-11872, survives), plus.exposureCombinationUnsupportedand.unsupportedConfiguration(_:);PRMBurstInterruptedError. - Device helpers:
prm_withConfigurationLock(_:),prm_supportsCustomLensPosition,prm_supportsManualExposureCapture,prm_setLensPosition(_:completion:); the waiting variants ofprm_setLensPosition,prm_setCustomExposureandprm_lockWhiteBalancetake atimeout. - PrismUI accessibility: VoiceOver labels and states on the settings drawer and rows (the drawer is modal while open and closes with the escape gesture), mode-aware shutter labels with press-and-hold offered as custom actions, a localized level reading, Dynamic Type in the drawer and rows, and opt-in
hapticsEnabledonPRMShutterButtonandPRMLevelIndicatorView. PrismUI's strings are localizable. - Night mode:
PRMNightModeOptions,PRMNightPlan,PRMNightProgress,PRMNightPhoto,PRMNightModeCapture.plan(_:)(the plan a capture would use, for an "AUTO 3s" label), andPRMCameraSession.isExclusiveCaptureActive. - Portrait readiness:
PRMPortraitReadinessandPRMPortraitReadinessMonitor, which tell whether a Portrait capture gets its depth effect right now (subject distance from a live depth stream, face and body detections, light) for a "NATURAL LIGHT" style indicator. - Frame orientation:
PRMRotationCoordinator.portraitFrameRotation(connectionAngle:)andframeRotation(uprightAngle:connectionAngle:)give the rotation a view still has to apply to video-data frames, withPRMCameraSession.videoDataRotationAngleandisVideoDataMirrored. A connection's default rotation isn't always 0: on the Center Stage front camera of iPhone 17 and later it is 270°, so drawing frames by the coordinator's angle alone leaves that preview sideways. - Other:
PRMCameraConfiguration.enableCameraSensorOrientationCompensation(iOS 26),PRMRotationCoordinator.videoRotationAngle(relativeTo:)(iOS 27),PRMCameraSession.defaultVideoDeviceType(at:),.lowLatencystabilization (iOS 26), andPRMCameraDevice.supportedExposureModes,supportedWhiteBalanceModesandsupportedFocusModes(withAVCaptureDevice.prm_supportedExposureModesand friends, and each mode'sprm_name) so a UI offers only the modes a camera runs: iPhone cameras have no one-shot auto white balance.PRMCameraDevice.supportsLowLightBoostandsupportsVideoHDR(active format) gate those controls the same way.PRMCameraDevice.depthDeviceType(at:)names the camera that streams depth, for when the current one has no depth format (enableDepthFormat()returnsfalse): an iPhone 14 Pro's Triple camera has none on iOS 18. - Logging (
PRMLog): six levels (debugtofault,PRMLogLevel) under the subsystemdev.luminoid.prism, categoriesSession,Capture,Filter,PreviewandGeneral.PRMLog.minimumLevelsets the threshold at runtime (default.info; errors and faults are always written);PRMLog.handlerforwards every writtenPRMLogEntry. Message text is public; file paths and full error descriptions are private. - Always-on log lines: a notice summary after configure, start, stop and every device switch;
isRunningchanges; interruptions with their reason; thermal state and system pressure changes; photo and recording outcomes, where a photo smaller than the size it asked for says why when that's known (manual exposure and locked white balance capture at 12 MP; 24 MP needs auto-deferred photo delivery); permission results. Every error sent toerrorStream()is logged. - Example app: iOS 26 / 27 drawer sections in Studio with telemetry badges and detection overlays, and the iOS 26 / 27 flags in Configuration Lab. Settings that can't run together follow one rule: the newest wins, turning the others off with a toast and a log line that say what changed and why; only what the camera can't do, or a recording would have to stop for, is refused.
Changed
- Breaking: building requires Xcode 27 (
swift-tools-version: 6.4). - Breaking:
PRMLoggerandPRMLogCategoryare removed, replaced byPRMLog.PRMLogger.isVerboseTracingEnabled = truebecomesPRMLog.minimumLevel = .debug. The subsystem changes fromcom.luminoid.Prismtodev.luminoid.prism, so update Console filters andlogpredicates. - Breaking:
PRMSessionErrorhas three new cases (captureFailed,exposureCombinationUnsupported,unsupportedConfiguration), which break exhaustiveswitchstatements. AVFoundation capture and recording errors now arrive ascaptureFailedinstead ofphotoCaptureFailed/videoRecordingFailedstrings. - Breaking:
PRMPhotoCapture.outputandPRMVideoRecorder.outputare optional:nilfor a session-based wrapper before its first capture, instead of an empty placeholder output. - Breaking: device setters throw when the device lacks the mode instead of returning silently:
prm_setExposureMode,prm_setCustomExposure,prm_setWhiteBalanceMode,prm_lockWhiteBalance,prm_setTorch,prm_setFocusMode,prm_setLensPosition, andprm_setVideoHDR/prm_setLowLightBoostwhen turning the feature on. The matchingPRMCamerasetters report it onerrorStream(). - Breaking:
captureBurstthrowsPRMBurstInterruptedError(with the photos already captured) when a shot after the first fails. - Switching cameras, changing the frame rate or format, entering a depth format, removing the movie output and reconfiguring are refused while recording, with
PRMSessionError.unsupportedConfiguration. setHighResolutionPhotoFormat(true)is refused while a movie output is attached (the 48MP format would replace the video format), and turning it off then only drops the choice (allowed while recording).PRMCamera.setHighResolutionPhotoFormat(_:)returns whether it applied.- Photo settings are checked against the photo output at capture time: quality prioritization above the output's maximum is lowered to it, and
maxDimensionsthe active format doesn't offer falls back to the largest one that fits. - Breaking:
PRMNightModeCaptureis rebuilt for brighter, cleaner Night photos.init(session:context:)andcapture(_:progress:)returningPRMNightPhotoreplaceinit(capture:context:)andcapture(frameCount:perFrameDuration:iso:codec:didCaptureFrame:), andsessionreplaces thecaptureandcontextproperties. It picks the exposure from how dark the scene is, gathers frames from the live stream for 1 to 3 seconds (up to 10 when the phone is stable), aligns them, leaves out what moved, and brightens the result by up to 3 EV. It needs a physical camera: on a virtual multi-camera device it throwsvirtualDeviceManualControlUnsupported, so switch to.builtInWideAngleCamerafirst. During a capture, camera switches, format and frame-rate changes, zoom, focus, exposure and white-balance changes are refused. - The live preview no longer gets the requested stabilization mode, which on video formats could delay it by up to a second (
.autopicks cinematic stabilization there). The video data output runs unstabilized in photo modes and with iOS 26's low-latency stabilization while a movie output is attached; recordings keep the requested mode, andPRMCameraState.activeStabilizationModereports the recording connection when there is one. - The video data output's
sampleBufferDelegateis Prism's frame router, which forwards every callback to the delegate passed tosetVideoDataOutputDelegate(_:). - A
PRMPhotoSettings.manualExposureOverrideon a camera that can't take a manual exposure (virtual multi-camera devices) captures at the camera's own exposure and leaves the EXIF alone. PRMAspectRatioMaskView.cropRect(in:)orients the ratio to the bounds: 4:3 in a portrait view is a 3:4 crop.PRMCameraSession.isRunningreflects the capture session itself, and the session restarts on its own after a media-services reset if the app wanted it running.- The 48MP photo format choice and Live Photo's on/off state carry across camera switches.
stateStream()emits only when the state changed and delivers the newest state to slow subscribers.lockWhiteBalance(preset:)locks to Apple's calibrated values on iOS 26 and later (every preset except.flash);PRMWhiteBalancePreset.temperaturekeeps the nominal Kelvin.PRMLens.focalLength35mmreports nominal focal lengths on iOS 26 and later;snapping()leaves nominal values unchanged.setFrameRate,resetFrameRateandenableDepthFormatrefreshPRMCamera.device, since the active format (and with it most capability flags) can change.PRMPreviewViewstops redrawing while frames aren't arriving and flushes its texture cache on memory warnings.PRMImage.jpegData(from:compressionQuality:context:)encodes in sRGB, like the metadata-preserving encoders (an extended-range color space from a capture made it returnnil), and returnsnilfor an infinite extent.- Deprecated:
withConfigurationLock(_:)(useprm_withConfigurationLock(_:)),prm_setLensPositionAsync(useprm_setLensPosition(_:completion:)),PRMPhotoSettings.livePhoto(ignored; usecaptureLivePhoto),PRMPhotoSettings.makeAVSettings()(usemakeAVSettings(for:)),PRMDepthCapture.addDepthDataOutput(to:delegate:queue:)(usePRMCameraSession.attachDepthDataOutput).
Fixed
- After AVFoundation stopped the session (a runtime error or media-services reset),
start()did nothing and the preview stayed black. - Switching cameras in video or slow-motion mode with Live Photo configured froze the preview for a full session rebuild and ended any recording.
- Manual-exposure stills crashed on iOS 27 when the camera was a virtual multi-camera device (Triple or Dual Camera).
- Captures could crash the app: a quality prioritization above the output's maximum, a manual exposure outside the active format's ISO or shutter range, and
maxDimensionsthe output couldn't deliver. - Starting a recording while the previous one was finalizing left the new one impossible to stop (
start()now throws until the previous recording finishes), andcancel()left the recorder in.recording. - A capture could hang when a deferred photo came back empty or a Live Photo's movie never arrived.
captureBurstdiscarded every photo already captured when a later shot failed.setLensPositionnever returned while the session was stopped or interrupted.- Shutter speeds at the ends of the range could round outside it and crash; fractional frame rates such as 29.97 fps were truncated.
- Auto torch crashed on devices without it; manual exposure turned automatic video HDR back on after an explicit
setVideoHDR(_:);enableDepthFormat()left geometric distortion correction off for good. - Lens zoom labels on dual (wide + telephoto) cameras read the wide lens as 0.5×.
- Video stabilization was lost after a camera switch, and the video output's pixel format was forced back to BGRA after a format change.
- A refused manual exposure, white-balance lock, or exposure or white-balance mode change kept showing the requested values, and a tap-to-focus left manual ISO and shutter pinned.
PRMPortraitBokehFilter's depth mode crashed, the blur filters darkened the frame edges, and filters that move the image rendered offset in the live chain.- Replacing the active filter renderer could race the frame being rendered.
PRMPreviewView.texturePoint(fromViewPoint:)andviewPoint(fromTexturePoint:)mirrored 90° and 270° rotations and ignored the fill crop and fit letterbox.PRMLevelIndicatorViewhid the tilt with Reduce Motion on, and took over a shared motion manager's handler.- A late subscriber to a stream with a current value could end up with a stale one.
PRMVideoRecorder.start()could hang forever when recording failed (or was stopped) before it began.PRMVideoRecorder.stop()returned aPRMRecordingwith adurationof 0.setHighResolutionPhotoFormat(true)could pick the video-range (420v) twin of the 48MP format, which has fewer tonal steps than the full-range one.resetFrameRate()restored the photo preset without the 48MP format, so after a custom frame rate photos came back at 12MP whilesetHighResolutionPhotoFormat(true)was still in effect.- Filtered frames from a camera format that carries color primaries but no color space were rendered in device RGB, which could shift colors; the color space is now built from the format's primaries and transfer function.
- HEIC photos from the filter pass and
PRMImage.heifDataPreservingMetadatastored an alpha channel the photo didn't have, so files were larger and took twice the memory to decode.