grapeot / AppleJPEGGainMap

Reference implementation to embed a gain map to an SDR photo so it renders as HDR on iPhones and Macs.
34 stars 0 forks source link

Perhaps a simpler approach to make an iOS-compatible HDR image #4

Open gwy15 opened 4 months ago

gwy15 commented 4 months ago

Hello, I was trying to generate an iOS-compatible image from my Lightroom and bump into this repository.

Correct me if I'm wrong, but I think this repository mainly focus on how to generate iOS-compatible image from ISO hdr images generated from softwares like adobe? Well if that's the case here, another repo https://github.com/HCYANG2000/Generate_Apple_HDR_Photos might be a somehow simpler approach to do so.

I tested it on my mac and iPhone and it works great on converting Adobe Lightroom generated avifs with HDR effect to iOS-compatible heic.

here's my modified code that compiles and works as swift main.swift ./path-to-avif

import Cocoa
import CoreImage

// Constants for command line arguments
struct CommandLineArguments {
    let path: String
    let compressionRatio: CGFloat
}

// Parses the command line arguments and returns a struct
func parseCommandLineArguments() -> CommandLineArguments {
    let arguments = CommandLine.arguments
    let path = arguments.count > 1 ? arguments[1] : "."
    let compressionRatio = arguments.count > 2 ? CGFloat(Double(arguments[2]) ?? 0.7) : 0.7

    return CommandLineArguments(
        path: path,
        compressionRatio: compressionRatio
    )
}

// Function to check if the path is a directory and process accordingly
func processPath(_ path: String, compressionRatio: CGFloat) {
    let fileManager = FileManager.default
    var isDirectory: ObjCBool = false

    let imageExtensions = [".avif"]

    if fileManager.fileExists(atPath: path, isDirectory: &isDirectory) {
        if isDirectory.boolValue {
            // It's a directory, process all non-heic files
            do {
                let files = try fileManager.contentsOfDirectory(atPath: path)
                for file in files {
                    let filePath = (path as NSString).appendingPathComponent(file)
                    if imageExtensions.contains(where: file.hasSuffix) {
                        processFile(filePath, compressionRatio: compressionRatio)
                    }
                }
            } catch {
                print("Error reading contents of directory: \(error)")
            }
        } else {
            // It's a file, process the single file
            processFile(path, compressionRatio: compressionRatio)
        }
    } else {
        print("The provided path does not exist.")
    }
}

// Function to process a single file
func processFile(_ filePath: String, compressionRatio: CGFloat) {
    guard let colorSpace = CGColorSpace(name: CGColorSpace.itur_2100_HLG) else {
        print("Couldn't create a color space.")
        exit(1)
    }
    let context = CIContext(options: [.workingColorSpace: colorSpace])

    let inputURL = URL(fileURLWithPath: filePath)
    let outputURL = inputURL.deletingPathExtension().appendingPathExtension("heic")

    print(inputURL.path, " -> ", outputURL.path)

    guard let image = CIImage(contentsOf: inputURL, options: [.expandToHDR: true]) else {
        print("Couldn't create an image from \(inputURL.path).")
        exit(1)
    }

    let options = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: compressionRatio]
    do {
        try context.writeHEIF10Representation(of: image, to: outputURL, colorSpace: image.colorSpace!, options: options)
    } catch {
        print("Failed to write the image with error: \(error)")
        exit(1)
    }
}

// Main function to execute the conversion process
func main() {
    let args = parseCommandLineArguments()

    guard !args.path.isEmpty else {
        print("Usage: <path> [compressionRatio]")
        return
    }

    processPath(args.path, compressionRatio: args.compressionRatio)
}

// Run the main function
main()
grapeot commented 20 hours ago

Thanks for the suggestions and example code!

Your understanding of the goals of the repo is roughly correct, but there are two subtle differences.

  1. Potentially the most important difference is the popular API to solve this problem, which uses the writeHEIFRepresentation function, is a private and undocumented function _for this specific purpose). It's very hard to do fine-grained control on the options on the specific gain maps, among other options. And it's also not guaranteed that this function will work for this purpose in the future. This repo aims to solve this problem by completely using a set of lower-level documented functions (from WWDC '23) to provide better controllability and reliability to the overall functionality.
  2. The provided code is not to turn an Adobe-compatible HDR image into an iOS-compatible image. It is one level lower than that. For example, you can use any base image and any gain map image calculated by you, even when these two are unrelated, to get an iOS-compatible image. This can lead to some very interesting applications, such as something like Dolby Vision but for HDR photos, so that you can completely control how the HDR photo looks like in SDR environments.

If you have interest in having more research on that, the readme file may be a good starting point. And thanks again for the comment!