Zum Inhalt

macos/Sources/PerlinLiveWallpaper/RenderCLI.swift

import Foundation
import CoreGraphics
import ImageIO
import UniformTypeIdentifiers

/// Headless `--render <preset> <out.png>` support: renders one preset to a PNG.
/// Handy for visual-parity checks against the studio and for generating native
/// stills, without launching the menu-bar UI.
enum RenderCLI {
    static func renderPresetToPNG(name: String, path: String, width: Int = 1200, height: Int = 750, time: Double = 10.0) -> Bool {
        guard let gpu = GPUContext(pixelFormat: .bgra8Unorm) else {
            FileHandle.standardError.write(Data("render: no Metal device\n".utf8)); return false
        }
        let store = PresetStore()
        guard let preset = store.presets.first(where: { $0.name == name }) ?? store.presets.first else {
            FileHandle.standardError.write(Data("render: no presets\n".utf8)); return false
        }
        guard var bgra = Renderer.renderBGRA(gpu: gpu, preset: preset, width: width, height: height, time: time) else {
            FileHandle.standardError.write(Data("render: encode failed\n".utf8)); return false
        }
        // BGRA (Metal) -> RGBA (CGImage) by swapping the B and R bytes in place.
        var i = 0
        while i < bgra.count { bgra.swapAt(i, i + 2); i += 4 }
        let ok = writePNG(rgba: bgra, width: width, height: height, to: path)
        FileHandle.standardError.write(Data("render: \(preset.label) [\(preset.colorMode)] \(width)x\(height) -> \(path) ok=\(ok)\n".utf8))
        return ok
    }

    private static func writePNG(rgba: [UInt8], width: Int, height: Int, to path: String) -> Bool {
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
        guard let provider = CGDataProvider(data: Data(rgba) as CFData),
              let cg = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 32,
                               bytesPerRow: width * 4, space: colorSpace, bitmapInfo: bitmapInfo,
                               provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) else {
            return false
        }
        let url = URL(fileURLWithPath: path) as CFURL
        guard let dest = CGImageDestinationCreateWithURL(url, UTType.png.identifier as CFString, 1, nil) else { return false }
        CGImageDestinationAddImage(dest, cg, nil)
        return CGImageDestinationFinalize(dest)
    }
}