Zum Inhalt

apple/PhysarumApp/Sources/Capture/ImageWriter.swift

import Foundation
import ImageIO
import UniformTypeIdentifiers
import CoreVideo

enum ImageWriter {
    static func writePNG(_ image: CGImage, to url: URL) -> Bool {
        guard let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil) else { return false }
        CGImageDestinationAddImage(dest, image, nil)
        return CGImageDestinationFinalize(dest)
    }

    /// Write HEIC (fallback to JPEG if the HEIC encoder is unavailable). `assetID`,
    /// when set, embeds the Apple maker-note asset identifier (Live Photo pairing).
    /// Returns the actual file URL written (extension .heic or .jpg).
    static func writeStill(_ image: CGImage, baseURL: URL, assetID: String?) -> URL? {
        let heicSupported = (CGImageDestinationCopyTypeIdentifiers() as? [String] ?? []).contains(UTType.heic.identifier)
        let type = heicSupported ? UTType.heic : UTType.jpeg
        let ext = heicSupported ? "heic" : "jpg"
        let url = baseURL.deletingPathExtension().appendingPathExtension(ext)
        guard let dest = CGImageDestinationCreateWithURL(url as CFURL, type.identifier as CFString, 1, nil) else { return nil }
        var opts: [CFString: Any] = [kCGImageDestinationLossyCompressionQuality: 0.9]
        if let id = assetID { opts[kCGImagePropertyMakerAppleDictionary] = ["17": id] }
        CGImageDestinationAddImage(dest, image, opts as CFDictionary)
        return CGImageDestinationFinalize(dest) ? url : nil
    }

    /// CVPixelBuffer (BGRA) → CGImage (Live Photo key-frame readback, used in a later task).
    static func cgImage(from pb: CVPixelBuffer) -> CGImage? {
        CVPixelBufferLockBaseAddress(pb, .readOnly)
        defer { CVPixelBufferUnlockBaseAddress(pb, .readOnly) }
        let w = CVPixelBufferGetWidth(pb), h = CVPixelBufferGetHeight(pb)
        let bpr = CVPixelBufferGetBytesPerRow(pb)
        guard let base = CVPixelBufferGetBaseAddress(pb) else { return nil }
        let cs = CGColorSpaceCreateDeviceRGB()
        let bm = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)
        guard let ctx = CGContext(data: base, width: w, height: h, bitsPerComponent: 8, bytesPerRow: bpr, space: cs, bitmapInfo: bm.rawValue) else { return nil }
        return ctx.makeImage()
    }
}