diff --git a/.gitignore b/.gitignore index 2d74fdd..03bcb7a 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,6 @@ fastlane/test_output iOSInjectionProject/ ## Gcc Patch /*.gcno + +# macOS Finder Desktop Services Store files +**/.DS_Store diff --git a/JXLook.xcodeproj/project.pbxproj b/JXLook.xcodeproj/project.pbxproj index 6e06f40..f9cfd4f 100644 --- a/JXLook.xcodeproj/project.pbxproj +++ b/JXLook.xcodeproj/project.pbxproj @@ -3,10 +3,36 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 53; objects = { /* Begin PBXBuildFile section */ + 511A08D02A35475900A2C2EB /* libbrotlicommon.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 511A08CD2A35475900A2C2EB /* libbrotlicommon.a */; }; + 511A08D12A35475900A2C2EB /* libbrotlidec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 511A08CE2A35475900A2C2EB /* libbrotlidec.a */; }; + 511A08D52A354ABC00A2C2EB /* libbrotlidec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 511A08CE2A35475900A2C2EB /* libbrotlidec.a */; }; + 511A08D62A354AD300A2C2EB /* libbrotlicommon.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 511A08CD2A35475900A2C2EB /* libbrotlicommon.a */; }; + 511A08D72A354B1200A2C2EB /* libbrotlicommon.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 511A08CD2A35475900A2C2EB /* libbrotlicommon.a */; }; + 511A08D82A354B1600A2C2EB /* libbrotlidec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 511A08CE2A35475900A2C2EB /* libbrotlidec.a */; }; + 511A32A52A4A093C00F72A05 /* OSLogExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D7042D2A4A05A400A462D6 /* OSLogExtension.swift */; }; + 511A32A62A4A094D00F72A05 /* OSLogExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D7042D2A4A05A400A462D6 /* OSLogExtension.swift */; }; + 511A32A82A4A459300F72A05 /* NSScreenExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511A32A72A4A459300F72A05 /* NSScreenExtension.swift */; }; + 512A21852A533701001E63F3 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512A21842A533701001E63F3 /* Settings.swift */; }; + 512A21882A533716001E63F3 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 512A21862A533716001E63F3 /* Settings.storyboard */; }; + 51369D2D2A3811BA00A1F1F1 /* EnumExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51369D2C2A3811BA00A1F1F1 /* EnumExtensions.swift */; }; + 5147148E2A38C308000A664A /* EnumExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51369D2C2A3811BA00A1F1F1 /* EnumExtensions.swift */; }; + 5147148F2A38C345000A664A /* EnumExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51369D2C2A3811BA00A1F1F1 /* EnumExtensions.swift */; }; + 518BBC2D2A4F7B9B004B4F9D /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518BBC2C2A4F7B9B004B4F9D /* NSImageExtension.swift */; }; + 51A571892A36990C0003E685 /* MetalImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A571882A36990C0003E685 /* MetalImageView.swift */; }; + 51D7042E2A4A05A400A462D6 /* OSLogExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D7042D2A4A05A400A462D6 /* OSLogExtension.swift */; }; + 51E49F9A2A562D70003C429B /* CIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E49F992A562D70003C429B /* CIImageExtension.swift */; }; + 51EEA2992A4FCE3A001F20AA /* disableHighDynamicRange in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2912A4FCE3A001F20AA /* disableHighDynamicRange */; }; + 51EEA29A2A4FCE3A001F20AA /* enableHighDynamicRange in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2922A4FCE3A001F20AA /* enableHighDynamicRange */; }; + 51EEA29B2A4FCE3A001F20AA /* disableToneMapping in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2932A4FCE3A001F20AA /* disableToneMapping */; }; + 51EEA29C2A4FCE3A001F20AA /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2942A4FCE3A001F20AA /* README.md */; }; + 51EEA29D2A4FCE3B001F20AA /* streamLog in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2952A4FCE3A001F20AA /* streamLog */; }; + 51EEA29E2A4FCE3B001F20AA /* enableToneMapping in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2962A4FCE3A001F20AA /* enableToneMapping */; }; + 51EEA29F2A4FCE3B001F20AA /* enableExtendedDynamicRange in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2972A4FCE3A001F20AA /* enableExtendedDynamicRange */; }; + 51EEA2A02A4FCE3B001F20AA /* disableExtendedDynamicRange in Resources */ = {isa = PBXBuildFile; fileRef = 51EEA2982A4FCE3A001F20AA /* disableExtendedDynamicRange */; }; 8573556125B56054005FF7C2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8573556025B56054005FF7C2 /* AppDelegate.swift */; }; 8573556325B56054005FF7C2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8573556225B56054005FF7C2 /* ViewController.swift */; }; 8573556525B56054005FF7C2 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8573556425B56054005FF7C2 /* Document.swift */; }; @@ -19,37 +45,26 @@ 859C091425C656DE00BAFE90 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8573564725B5D4E2005FF7C2 /* libc++.tbd */; }; 859C0BA725C7E22800BAFE90 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 859C0BA525C7E20000BAFE90 /* Sparkle.framework */; }; 859C0BA825C7E22800BAFE90 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 859C0BA525C7E20000BAFE90 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 85E59CC2269C9A4F0036A75E /* libbrotlidec-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBB269C9A4F0036A75E /* libbrotlidec-static.a */; }; - 85E59CC3269C9A4F0036A75E /* libbrotlidec-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBB269C9A4F0036A75E /* libbrotlidec-static.a */; }; 85E59CC4269C9A4F0036A75E /* libjxl_threads.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBC269C9A4F0036A75E /* libjxl_threads.a */; }; 85E59CC5269C9A4F0036A75E /* libjxl_threads.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBC269C9A4F0036A75E /* libjxl_threads.a */; }; 85E59CC6269C9A4F0036A75E /* libhwy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBD269C9A4F0036A75E /* libhwy.a */; }; 85E59CC7269C9A4F0036A75E /* libhwy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBD269C9A4F0036A75E /* libhwy.a */; }; 85E59CC8269C9A4F0036A75E /* libjxl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBE269C9A4F0036A75E /* libjxl.a */; }; 85E59CC9269C9A4F0036A75E /* libjxl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBE269C9A4F0036A75E /* libjxl.a */; }; - 85E59CCA269C9A4F0036A75E /* libbrotlienc-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBF269C9A4F0036A75E /* libbrotlienc-static.a */; }; - 85E59CCB269C9A4F0036A75E /* libbrotlienc-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBF269C9A4F0036A75E /* libbrotlienc-static.a */; }; - 85E59CCC269C9A4F0036A75E /* libbrotlicommon-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CC0269C9A4F0036A75E /* libbrotlicommon-static.a */; }; - 85E59CCD269C9A4F0036A75E /* libbrotlicommon-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CC0269C9A4F0036A75E /* libbrotlicommon-static.a */; }; - 85E59CCF269C9A4F0036A75E /* libskcms.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CC1269C9A4F0036A75E /* libskcms.a */; }; 85FC6DCA25BAC56E00A1AAF4 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85FC6DC925BAC56E00A1AAF4 /* Quartz.framework */; }; 85FC6DCD25BAC56E00A1AAF4 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FC6DCC25BAC56E00A1AAF4 /* PreviewViewController.swift */; }; 85FC6DD025BAC56E00A1AAF4 /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 85FC6DCE25BAC56E00A1AAF4 /* PreviewViewController.xib */; }; - 85FC6DD525BAC56E00A1AAF4 /* JXQuickLook.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 85FC6DC825BAC56E00A1AAF4 /* JXQuickLook.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 85FC6DD525BAC56E00A1AAF4 /* JXQuickLook.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 85FC6DC825BAC56E00A1AAF4 /* JXQuickLook.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 85FC6E5025BAD03500A1AAF4 /* JXL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FC6E4F25BAD03500A1AAF4 /* JXL.swift */; }; 85FC6E5125BAD03500A1AAF4 /* JXL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FC6E4F25BAD03500A1AAF4 /* JXL.swift */; }; E99A66BF2745FA670008848F /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E99A66BE2745FA670008848F /* QuickLookThumbnailing.framework */; platformFilter = maccatalyst; }; E99A66C02745FA670008848F /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85FC6DC925BAC56E00A1AAF4 /* Quartz.framework */; }; E99A66C32745FA670008848F /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99A66C22745FA670008848F /* ThumbnailProvider.swift */; }; - E99A66C82745FA670008848F /* JXQuickLookThumbnail.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E99A66BD2745FA670008848F /* JXQuickLookThumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - E99A66CC2745FB620008848F /* libbrotlicommon-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CC0269C9A4F0036A75E /* libbrotlicommon-static.a */; }; - E99A66CD2745FB620008848F /* libbrotlidec-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBB269C9A4F0036A75E /* libbrotlidec-static.a */; }; + E99A66C82745FA670008848F /* JXQuickLookThumbnail.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E99A66BD2745FA670008848F /* JXQuickLookThumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E99A66CE2745FB620008848F /* libc++.1.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 859C090925C6566400BAFE90 /* libc++.1.tbd */; }; E99A66CF2745FB620008848F /* libjxl_threads.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBC269C9A4F0036A75E /* libjxl_threads.a */; }; E99A66D02745FB620008848F /* libhwy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBD269C9A4F0036A75E /* libhwy.a */; }; - E99A66D12745FB620008848F /* libskcms.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CC1269C9A4F0036A75E /* libskcms.a */; }; E99A66D22745FB620008848F /* libjxl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBE269C9A4F0036A75E /* libjxl.a */; }; - E99A66D32745FB620008848F /* libbrotlienc-static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85E59CBF269C9A4F0036A75E /* libbrotlienc-static.a */; }; E99A66D6274603540008848F /* JXL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FC6E4F25BAD03500A1AAF4 /* JXL.swift */; }; /* End PBXBuildFile section */ @@ -96,24 +111,44 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - 85FC6DD625BAC56E00A1AAF4 /* Embed App Extensions */ = { + 85FC6DD625BAC56E00A1AAF4 /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - E99A66C82745FA670008848F /* JXQuickLookThumbnail.appex in Embed App Extensions */, - 85FC6DD525BAC56E00A1AAF4 /* JXQuickLook.appex in Embed App Extensions */, + E99A66C82745FA670008848F /* JXQuickLookThumbnail.appex in Embed Foundation Extensions */, + 85FC6DD525BAC56E00A1AAF4 /* JXQuickLook.appex in Embed Foundation Extensions */, ); - name = "Embed App Extensions"; + name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 511A08CD2A35475900A2C2EB /* libbrotlicommon.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libbrotlicommon.a; sourceTree = ""; }; + 511A08CE2A35475900A2C2EB /* libbrotlidec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libbrotlidec.a; sourceTree = ""; }; + 511A08D32A35479300A2C2EB /* cms_interface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cms_interface.h; sourceTree = ""; }; + 511A08D42A3547C500A2C2EB /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + 511A32A72A4A459300F72A05 /* NSScreenExtension.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = NSScreenExtension.swift; sourceTree = ""; }; + 512A21842A533701001E63F3 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + 512A21872A533716001E63F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Settings.storyboard; sourceTree = ""; }; + 51369D2C2A3811BA00A1F1F1 /* EnumExtensions.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = EnumExtensions.swift; sourceTree = ""; }; + 518BBC2C2A4F7B9B004B4F9D /* NSImageExtension.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = ""; }; + 51A571882A36990C0003E685 /* MetalImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = MetalImageView.swift; sourceTree = ""; }; + 51D7042D2A4A05A400A462D6 /* OSLogExtension.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OSLogExtension.swift; sourceTree = ""; }; + 51E49F992A562D70003C429B /* CIImageExtension.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = CIImageExtension.swift; sourceTree = ""; }; + 51EEA2912A4FCE3A001F20AA /* disableHighDynamicRange */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = disableHighDynamicRange; sourceTree = ""; }; + 51EEA2922A4FCE3A001F20AA /* enableHighDynamicRange */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = enableHighDynamicRange; sourceTree = ""; }; + 51EEA2932A4FCE3A001F20AA /* disableToneMapping */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = disableToneMapping; sourceTree = ""; }; + 51EEA2942A4FCE3A001F20AA /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 51EEA2952A4FCE3A001F20AA /* streamLog */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = streamLog; sourceTree = ""; }; + 51EEA2962A4FCE3A001F20AA /* enableToneMapping */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = enableToneMapping; sourceTree = ""; }; + 51EEA2972A4FCE3A001F20AA /* enableExtendedDynamicRange */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = enableExtendedDynamicRange; sourceTree = ""; }; + 51EEA2982A4FCE3A001F20AA /* disableExtendedDynamicRange */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = disableExtendedDynamicRange; sourceTree = ""; }; 8573555D25B56054005FF7C2 /* JXLook.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JXLook.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 8573556025B56054005FF7C2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 8573556225B56054005FF7C2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 8573556025B56054005FF7C2 /* AppDelegate.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8573556225B56054005FF7C2 /* ViewController.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 8573556425B56054005FF7C2 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 8573556625B56055005FF7C2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8573556925B56055005FF7C2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -147,13 +182,9 @@ 85E59CB7269C9A4F0036A75E /* decode_cxx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = decode_cxx.h; sourceTree = ""; }; 85E59CB8269C9A4F0036A75E /* encode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = encode.h; sourceTree = ""; }; 85E59CB9269C9A4F0036A75E /* decode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = decode.h; sourceTree = ""; }; - 85E59CBB269C9A4F0036A75E /* libbrotlidec-static.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libbrotlidec-static.a"; sourceTree = ""; }; 85E59CBC269C9A4F0036A75E /* libjxl_threads.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libjxl_threads.a; sourceTree = ""; }; 85E59CBD269C9A4F0036A75E /* libhwy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libhwy.a; sourceTree = ""; }; 85E59CBE269C9A4F0036A75E /* libjxl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libjxl.a; sourceTree = ""; }; - 85E59CBF269C9A4F0036A75E /* libbrotlienc-static.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libbrotlienc-static.a"; sourceTree = ""; }; - 85E59CC0269C9A4F0036A75E /* libbrotlicommon-static.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libbrotlicommon-static.a"; sourceTree = ""; }; - 85E59CC1269C9A4F0036A75E /* libskcms.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libskcms.a; sourceTree = ""; }; 85FC6DC825BAC56E00A1AAF4 /* JXQuickLook.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = JXQuickLook.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 85FC6DC925BAC56E00A1AAF4 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; 85FC6DCC25BAC56E00A1AAF4 /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = ""; }; @@ -161,7 +192,7 @@ 85FC6DD125BAC56E00A1AAF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 85FC6DD225BAC56E00A1AAF4 /* JXQuickLook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = JXQuickLook.entitlements; sourceTree = ""; }; 85FC6E4725BAD00000A1AAF4 /* JXQuickLook-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JXQuickLook-Bridging-Header.h"; sourceTree = ""; }; - 85FC6E4F25BAD03500A1AAF4 /* JXL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXL.swift; sourceTree = ""; }; + 85FC6E4F25BAD03500A1AAF4 /* JXL.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = JXL.swift; sourceTree = ""; }; E99A66BD2745FA670008848F /* JXQuickLookThumbnail.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = JXQuickLookThumbnail.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E99A66BE2745FA670008848F /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; }; E99A66C22745FA670008848F /* ThumbnailProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailProvider.swift; sourceTree = ""; }; @@ -175,13 +206,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 85E59CCC269C9A4F0036A75E /* libbrotlicommon-static.a in Frameworks */, - 85E59CC2269C9A4F0036A75E /* libbrotlidec-static.a in Frameworks */, + 511A08D02A35475900A2C2EB /* libbrotlicommon.a in Frameworks */, + 511A08D12A35475900A2C2EB /* libbrotlidec.a in Frameworks */, 859C091425C656DE00BAFE90 /* libc++.tbd in Frameworks */, 85E59CC4269C9A4F0036A75E /* libjxl_threads.a in Frameworks */, 85E59CC6269C9A4F0036A75E /* libhwy.a in Frameworks */, 85E59CC8269C9A4F0036A75E /* libjxl.a in Frameworks */, - 85E59CCA269C9A4F0036A75E /* libbrotlienc-static.a in Frameworks */, 859C0BA725C7E22800BAFE90 /* Sparkle.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -204,14 +234,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 85E59CCD269C9A4F0036A75E /* libbrotlicommon-static.a in Frameworks */, - 85E59CC3269C9A4F0036A75E /* libbrotlidec-static.a in Frameworks */, + 511A08D62A354AD300A2C2EB /* libbrotlicommon.a in Frameworks */, + 511A08D52A354ABC00A2C2EB /* libbrotlidec.a in Frameworks */, 859C090A25C6566400BAFE90 /* libc++.1.tbd in Frameworks */, 85E59CC5269C9A4F0036A75E /* libjxl_threads.a in Frameworks */, 85E59CC7269C9A4F0036A75E /* libhwy.a in Frameworks */, - 85E59CCF269C9A4F0036A75E /* libskcms.a in Frameworks */, 85E59CC9269C9A4F0036A75E /* libjxl.a in Frameworks */, - 85E59CCB269C9A4F0036A75E /* libbrotlienc-static.a in Frameworks */, 85FC6DCA25BAC56E00A1AAF4 /* Quartz.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -220,14 +248,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E99A66CC2745FB620008848F /* libbrotlicommon-static.a in Frameworks */, - E99A66CD2745FB620008848F /* libbrotlidec-static.a in Frameworks */, + 511A08D72A354B1200A2C2EB /* libbrotlicommon.a in Frameworks */, + 511A08D82A354B1600A2C2EB /* libbrotlidec.a in Frameworks */, E99A66CE2745FB620008848F /* libc++.1.tbd in Frameworks */, E99A66CF2745FB620008848F /* libjxl_threads.a in Frameworks */, E99A66D02745FB620008848F /* libhwy.a in Frameworks */, - E99A66D12745FB620008848F /* libskcms.a in Frameworks */, E99A66D22745FB620008848F /* libjxl.a in Frameworks */, - E99A66D32745FB620008848F /* libbrotlienc-static.a in Frameworks */, E99A66BF2745FA670008848F /* QuickLookThumbnailing.framework in Frameworks */, E99A66C02745FA670008848F /* Quartz.framework in Frameworks */, ); @@ -236,10 +262,26 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 51EEA2902A4FCD74001F20AA /* scripts */ = { + isa = PBXGroup; + children = ( + 51EEA2982A4FCE3A001F20AA /* disableExtendedDynamicRange */, + 51EEA2912A4FCE3A001F20AA /* disableHighDynamicRange */, + 51EEA2932A4FCE3A001F20AA /* disableToneMapping */, + 51EEA2972A4FCE3A001F20AA /* enableExtendedDynamicRange */, + 51EEA2922A4FCE3A001F20AA /* enableHighDynamicRange */, + 51EEA2962A4FCE3A001F20AA /* enableToneMapping */, + 51EEA2942A4FCE3A001F20AA /* README.md */, + 51EEA2952A4FCE3A001F20AA /* streamLog */, + ); + path = scripts; + sourceTree = ""; + }; 8573555425B56054005FF7C2 = { isa = PBXGroup; children = ( 85E59CA6269C9A4F0036A75E /* jpeg-xl */, + 51EEA2902A4FCD74001F20AA /* scripts */, 8573555F25B56054005FF7C2 /* JXLook */, 8573557425B56055005FF7C2 /* JXLookTests */, 8573557F25B56055005FF7C2 /* JXLookUITests */, @@ -270,8 +312,16 @@ 8573556225B56054005FF7C2 /* ViewController.swift */, 8598D00E25B805AE00C08958 /* CenterClipView.swift */, 8573556425B56054005FF7C2 /* Document.swift */, + 51E49F992A562D70003C429B /* CIImageExtension.swift */, + 51369D2C2A3811BA00A1F1F1 /* EnumExtensions.swift */, + 51A571882A36990C0003E685 /* MetalImageView.swift */, + 518BBC2C2A4F7B9B004B4F9D /* NSImageExtension.swift */, + 511A32A72A4A459300F72A05 /* NSScreenExtension.swift */, + 51D7042D2A4A05A400A462D6 /* OSLogExtension.swift */, + 512A21842A533701001E63F3 /* Settings.swift */, 8573556625B56055005FF7C2 /* Assets.xcassets */, 8573556825B56055005FF7C2 /* Main.storyboard */, + 512A21862A533716001E63F3 /* Settings.storyboard */, 8573556B25B56055005FF7C2 /* Info.plist */, 8573556C25B56055005FF7C2 /* JXLook.entitlements */, 857355F125B5624E005FF7C2 /* JXLook-Bridging-Header.h */, @@ -329,6 +379,7 @@ 85E59CA8269C9A4F0036A75E /* jxl */ = { isa = PBXGroup; children = ( + 511A08D32A35479300A2C2EB /* cms_interface.h */, 85E59CA9269C9A4F0036A75E /* resizable_parallel_runner_cxx.h */, 85E59CAA269C9A4F0036A75E /* color_encoding.h */, 85E59CAB269C9A4F0036A75E /* types.h */, @@ -346,6 +397,7 @@ 85E59CB7269C9A4F0036A75E /* decode_cxx.h */, 85E59CB8269C9A4F0036A75E /* encode.h */, 85E59CB9269C9A4F0036A75E /* decode.h */, + 511A08D42A3547C500A2C2EB /* version.h */, ); path = jxl; sourceTree = ""; @@ -353,13 +405,11 @@ 85E59CBA269C9A4F0036A75E /* lib */ = { isa = PBXGroup; children = ( - 85E59CBB269C9A4F0036A75E /* libbrotlidec-static.a */, + 511A08CD2A35475900A2C2EB /* libbrotlicommon.a */, + 511A08CE2A35475900A2C2EB /* libbrotlidec.a */, 85E59CBC269C9A4F0036A75E /* libjxl_threads.a */, 85E59CBD269C9A4F0036A75E /* libhwy.a */, 85E59CBE269C9A4F0036A75E /* libjxl.a */, - 85E59CBF269C9A4F0036A75E /* libbrotlienc-static.a */, - 85E59CC0269C9A4F0036A75E /* libbrotlicommon-static.a */, - 85E59CC1269C9A4F0036A75E /* libskcms.a */, ); path = lib; sourceTree = ""; @@ -397,7 +447,7 @@ 8573555925B56054005FF7C2 /* Sources */, 8573555A25B56054005FF7C2 /* Frameworks */, 8573555B25B56054005FF7C2 /* Resources */, - 85FC6DD625BAC56E00A1AAF4 /* Embed App Extensions */, + 85FC6DD625BAC56E00A1AAF4 /* Embed Foundation Extensions */, 859C0BA925C7E22900BAFE90 /* Embed Frameworks */, ); buildRules = ( @@ -487,8 +537,9 @@ 8573555525B56054005FF7C2 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1220; - LastUpgradeCheck = 1220; + LastUpgradeCheck = 1430; TargetAttributes = { 8573555C25B56054005FF7C2 = { CreatedOnToolsVersion = 12.2; @@ -538,8 +589,17 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51EEA29F2A4FCE3B001F20AA /* enableExtendedDynamicRange in Resources */, + 51EEA29C2A4FCE3A001F20AA /* README.md in Resources */, + 512A21882A533716001E63F3 /* Settings.storyboard in Resources */, 8573556725B56055005FF7C2 /* Assets.xcassets in Resources */, + 51EEA29E2A4FCE3B001F20AA /* enableToneMapping in Resources */, + 51EEA2A02A4FCE3B001F20AA /* disableExtendedDynamicRange in Resources */, 8573556A25B56055005FF7C2 /* Main.storyboard in Resources */, + 51EEA29B2A4FCE3A001F20AA /* disableToneMapping in Resources */, + 51EEA29D2A4FCE3B001F20AA /* streamLog in Resources */, + 51EEA2992A4FCE3A001F20AA /* disableHighDynamicRange in Resources */, + 51EEA29A2A4FCE3A001F20AA /* enableHighDynamicRange in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -583,6 +643,13 @@ 8573556325B56054005FF7C2 /* ViewController.swift in Sources */, 8573556125B56054005FF7C2 /* AppDelegate.swift in Sources */, 8573556525B56054005FF7C2 /* Document.swift in Sources */, + 511A32A82A4A459300F72A05 /* NSScreenExtension.swift in Sources */, + 51E49F9A2A562D70003C429B /* CIImageExtension.swift in Sources */, + 51A571892A36990C0003E685 /* MetalImageView.swift in Sources */, + 51D7042E2A4A05A400A462D6 /* OSLogExtension.swift in Sources */, + 518BBC2D2A4F7B9B004B4F9D /* NSImageExtension.swift in Sources */, + 51369D2D2A3811BA00A1F1F1 /* EnumExtensions.swift in Sources */, + 512A21852A533701001E63F3 /* Settings.swift in Sources */, 8598D00F25B805AE00C08958 /* CenterClipView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -607,8 +674,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 85FC6DCD25BAC56E00A1AAF4 /* PreviewViewController.swift in Sources */, 85FC6E5125BAD03500A1AAF4 /* JXL.swift in Sources */, + 5147148E2A38C308000A664A /* EnumExtensions.swift in Sources */, + 511A32A52A4A093C00F72A05 /* OSLogExtension.swift in Sources */, + 85FC6DCD25BAC56E00A1AAF4 /* PreviewViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -617,6 +686,8 @@ buildActionMask = 2147483647; files = ( E99A66D6274603540008848F /* JXL.swift in Sources */, + 5147148F2A38C345000A664A /* EnumExtensions.swift in Sources */, + 511A32A62A4A094D00F72A05 /* OSLogExtension.swift in Sources */, E99A66C32745FA670008848F /* ThumbnailProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -647,6 +718,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 512A21862A533716001E63F3 /* Settings.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 512A21872A533716001E63F3 /* Base */, + ); + name = Settings.storyboard; + sourceTree = ""; + }; 8573556825B56055005FF7C2 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -701,6 +780,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -718,7 +798,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright (c) 2021-2023 Yung-Luen Lan"; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -763,6 +844,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -774,7 +856,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright (c) 2021-2023 Yung-Luen Lan"; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -795,6 +878,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; + DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -833,6 +917,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; + DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -865,6 +950,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; INFOPLIST_FILE = JXLookTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -872,7 +958,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = org.yllan.JXLookTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -887,6 +973,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; INFOPLIST_FILE = JXLookTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -894,7 +981,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = org.yllan.JXLookTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -908,6 +995,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; INFOPLIST_FILE = JXLookUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -928,6 +1016,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; INFOPLIST_FILE = JXLookUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -950,6 +1039,7 @@ CODE_SIGN_ENTITLEMENTS = JXQuickLook/JXQuickLook.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; ENABLE_HARDENED_RUNTIME = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/jpeg-xl/include/**"; @@ -981,6 +1071,7 @@ CODE_SIGN_ENTITLEMENTS = JXQuickLook/JXQuickLook.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; ENABLE_HARDENED_RUNTIME = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/jpeg-xl/include/**"; @@ -1011,9 +1102,10 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; ENABLE_HARDENED_RUNTIME = YES; - GENERATE_INFOPLIST_FILE = YES; + GENERATE_INFOPLIST_FILE = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/jpeg-xl/include/**"; INFOPLIST_FILE = JXQuickLookThumbnail/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = JXQuickLookThumbnail; @@ -1045,9 +1137,10 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z8M6R7T8R8; ENABLE_HARDENED_RUNTIME = YES; - GENERATE_INFOPLIST_FILE = YES; + GENERATE_INFOPLIST_FILE = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/jpeg-xl/include/**"; INFOPLIST_FILE = JXQuickLookThumbnail/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = JXQuickLookThumbnail; diff --git a/JXLook.xcodeproj/xcshareddata/xcschemes/JXLook.xcscheme b/JXLook.xcodeproj/xcshareddata/xcschemes/JXLook.xcscheme index bb73a2f..bbf82dd 100644 --- a/JXLook.xcodeproj/xcshareddata/xcschemes/JXLook.xcscheme +++ b/JXLook.xcodeproj/xcshareddata/xcschemes/JXLook.xcscheme @@ -1,6 +1,6 @@ - + - + + @@ -28,7 +29,11 @@ - + + + + + @@ -727,46 +732,63 @@ - + - + - - - - - + + + + + + + + + + + + + + - + - + - - - - + + + + - - - + + + + + + + + + + + diff --git a/JXLook/Base.lproj/Settings.storyboard b/JXLook/Base.lproj/Settings.storyboard new file mode 100644 index 0000000..ae594ad --- /dev/null +++ b/JXLook/Base.lproj/Settings.storyboard @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JXLook/CIImageExtension.swift b/JXLook/CIImageExtension.swift new file mode 100644 index 0000000..31cb059 --- /dev/null +++ b/JXLook/CIImageExtension.swift @@ -0,0 +1,27 @@ +// +// CIImageExtension.swift +// JXLook +// +// Created by low-batt on 7/5/23. +// + +import Cocoa + +extension CIImage { + + /// A textual representation of this instance. + /// + /// Customize the string conversion for a `CIImage` object to contain the values of the properties that are important to `JXLook`. + public override var description: String { + var result = "CIImage(" + if let name = colorSpace?.name { + result += "colorSpace: \(name), " + } + let x = Int(extent.origin.x) + let y = Int(extent.origin.y) + let width = Int(extent.width) + let height = Int(extent.height) + result += "extent [\(x) \(y) \(width) \(height)])" + return result + } +} diff --git a/JXLook/Document.swift b/JXLook/Document.swift index 8cddc94..86df17a 100644 --- a/JXLook/Document.swift +++ b/JXLook/Document.swift @@ -6,6 +6,7 @@ // import Cocoa +import os class Document: NSDocument { var image: NSImage? = nil @@ -47,6 +48,9 @@ class Document: NSDocument { guard isValid else { throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) } + if let lastPathComponent = fileURL?.lastPathComponent { + os_log("Decoding file: %{private}@", log: .decode, type: .info, lastPathComponent) + } self.image = try? JXL.parse(data: data) } diff --git a/JXLook/EnumExtensions.swift b/JXLook/EnumExtensions.swift new file mode 100644 index 0000000..b60a2e1 --- /dev/null +++ b/JXLook/EnumExtensions.swift @@ -0,0 +1,156 @@ +// +// Extensions.swift +// JXLook +// +// Created by low-batt on 6/12/23. +// + +import Cocoa + +/// Extensions of enumerations declared outside of Swift that changes the conversion from the enum value to a string to use the +/// enumeration case name instead of the raw value. This improves the readability of log messages intended for developers. + +extension JxlColorSpace: CustomStringConvertible { + public var description: String { + switch self { + case JXL_COLOR_SPACE_RGB: + // Tristimulus RGB + return "JXL_COLOR_SPACE_RGB" + case JXL_COLOR_SPACE_GRAY: + // Luminance based, the primaries in JxlColorEncoding must be ignored. This value + // implies that num_color_channels in JxlBasicInfo is 1, any other value implies + // num_color_channels is 3. + return "JXL_COLOR_SPACE_GRAY" + case JXL_COLOR_SPACE_XYB: + // XYB (opsin) color space + return "JXL_COLOR_SPACE_XYB" + case JXL_COLOR_SPACE_UNKNOWN: + // None of the other table entries describe the color space appropriately + return "JXL_COLOR_SPACE_UNKNOWN" + default: return "JxlColorSpace(rawValue: \(rawValue))" + } + } +} + +extension JxlOrientation: CustomStringConvertible { + public var description: String { + switch self { + case JXL_ORIENT_IDENTITY: return "JXL_ORIENT_IDENTITY" + case JXL_ORIENT_FLIP_HORIZONTAL: return "JXL_ORIENT_FLIP_HORIZONTAL" + case JXL_ORIENT_ROTATE_180: return "JXL_ORIENT_ROTATE_180" + case JXL_ORIENT_FLIP_VERTICAL: return "JXL_ORIENT_FLIP_VERTICAL" + case JXL_ORIENT_TRANSPOSE: return "JXL_ORIENT_TRANSPOSE" + case JXL_ORIENT_ROTATE_90_CW: return "JXL_ORIENT_ROTATE_90_CW" + case JXL_ORIENT_ANTI_TRANSPOSE: return "JXL_ORIENT_ANTI_TRANSPOSE" + case JXL_ORIENT_ROTATE_90_CCW: return "JXL_ORIENT_ROTATE_90_CCW" + default: return "JxlOrientation(rawValue: \(rawValue))" + } + } +} + +extension JxlPrimaries: CustomStringConvertible { + public var description: String { + switch self { + case JXL_PRIMARIES_SRGB: + // The CIE xy values of the red, green and blue primaries are: 0.639998686, 0.330010138; + // 0.300003784, 0.600003357; 0.150002046, 0.059997204 + return "JXL_PRIMARIES_SRGB" + case JXL_PRIMARIES_CUSTOM: + // Primaries must be read from the JxlColorEncoding primaries_red_xy, primaries_green_xy + // and primaries_blue_xy fields, or as ICC profile. This enum value is not an exact + // match of the corresponding CICP value. + return "JXL_PRIMARIES_CUSTOM" + case JXL_PRIMARIES_2100: + // As specified in Rec. ITU-R BT.2100-1 + return "JXL_PRIMARIES_2100" + case JXL_PRIMARIES_P3: + // As specified in SMPTE RP 431-2 + return "JXL_PRIMARIES_P3" + default: return "JxlPrimaries(rawValue: \(rawValue))" + } + } +} + +extension JxlRenderingIntent: CustomStringConvertible { + public var description: String { + switch self { + case JXL_RENDERING_INTENT_PERCEPTUAL: + // vendor-specific + return "JXL_RENDERING_INTENT_PERCEPTUAL" + case JXL_RENDERING_INTENT_RELATIVE: + // media-relative + return "JXL_RENDERING_INTENT_RELATIVE" + case JXL_RENDERING_INTENT_SATURATION: + // vendor-specific + return "JXL_RENDERING_INTENT_SATURATION" + case JXL_RENDERING_INTENT_ABSOLUTE: + // ICC-absolute + return "JXL_RENDERING_INTENT_ABSOLUTE" + default: return "JxlRenderingIntent(rawValue: \(rawValue))" + } + } +} + +extension JxlTransferFunction: CustomStringConvertible { + public var description: String { + switch self { + case JXL_TRANSFER_FUNCTION_709: + // As specified in SMPTE RP 431-2 + return "JXL_TRANSFER_FUNCTION_709" + case JXL_TRANSFER_FUNCTION_UNKNOWN: + // None of the other table entries describe the transfer function. + return "JXL_TRANSFER_FUNCTION_UNKNOWN" + case JXL_TRANSFER_FUNCTION_LINEAR: + // The gamma exponent is 1 + return "JXL_TRANSFER_FUNCTION_LINEAR" + case JXL_TRANSFER_FUNCTION_SRGB: + // As specified in IEC 61966-2-1 sRGB + return "JXL_TRANSFER_FUNCTION_SRGB" + case JXL_TRANSFER_FUNCTION_PQ: + // As specified in SMPTE ST 2084 + return "JXL_TRANSFER_FUNCTION_PQ" + case JXL_TRANSFER_FUNCTION_DCI: + // As specified in SMPTE ST 428-1 + return "JXL_TRANSFER_FUNCTION_DCI" + case JXL_TRANSFER_FUNCTION_HLG: + // As specified in Rec. ITU-R BT.2100-1 (HLG) + return "JXL_TRANSFER_FUNCTION_HLG" + case JXL_TRANSFER_FUNCTION_GAMMA: + // Transfer function follows power law given by the gamma value in JxlColorEncoding. + // Not a CICP value. + return "JXL_TRANSFER_FUNCTION_GAMMA" + default: return "JxlTransferFunction(rawValue: \(rawValue))" + } + } +} + +extension JxlWhitePoint: CustomStringConvertible { + public var description: String { + switch self { + case JXL_WHITE_POINT_D65: + // CIE Standard Illuminant D65: 0.3127, 0.3290 + return "JXL_WHITE_POINT_D65" + case JXL_WHITE_POINT_CUSTOM: + // White point must be read from the JxlColorEncoding white_point field, or as ICC + // profile. Th enum value is not an exact match of the corresponding CICP value. + return "JXL_WHITE_POINT_CUSTOM" + case JXL_WHITE_POINT_E: + // CIE Standard Illuminant E (equal-energy): 1/3, 1/3 + return "JXL_WHITE_POINT_E" + case JXL_WHITE_POINT_DCI: + // DCI-P3 from SMPTE RP 431-2: 0.314, 0.351 + return "JXL_WHITE_POINT_DCI" + default: return "JxlWhitePoint(rawValue: \(rawValue))" + } + } +} + +extension MTLPixelFormat: CustomStringConvertible { + public var description: String { + switch self { + case .bgra8Unorm: return "bgra8Unorm" + case .rgba16Float: return "rgba16Float" + default: return "MTLPixelFormat(rawValue: \(rawValue))" + } + } +} diff --git a/JXLook/Info.plist b/JXLook/Info.plist index 35d500d..d42fe47 100644 --- a/JXLook/Info.plist +++ b/JXLook/Info.plist @@ -38,11 +38,13 @@ CFBundleShortVersionString $(APP_VERSION) CFBundleVersion - 1036 + 2420 LSApplicationCategoryType public.app-category.photography LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + ${INFOPLIST_KEY_NSHumanReadableCopyright} NSMainStoryboardFile Main NSPrincipalClass diff --git a/JXLook/JXL.swift b/JXLook/JXL.swift index 3328f2b..214a21a 100644 --- a/JXLook/JXL.swift +++ b/JXLook/JXL.swift @@ -7,6 +7,7 @@ import Foundation import Cocoa +import os enum JXLError: Error { case cannotDecode @@ -32,7 +33,7 @@ struct JXL { let decoder = JxlDecoderCreate(nil) let runner = JxlThreadParallelRunnerCreate(nil, JxlThreadParallelRunnerDefaultNumWorkerThreads()) if JxlDecoderSetParallelRunner(decoder, JxlThreadParallelRunner, runner) != JXL_DEC_SUCCESS { - Swift.print("Cannot set runner") + os_log("Cannot set runner", log: .decode, type: .error) } JxlDecoderSubscribeEvents(decoder, Int32(JXL_DEC_BASIC_INFO.rawValue | JXL_DEC_COLOR_ENCODING.rawValue | JXL_DEC_FULL_IMAGE.rawValue)) @@ -48,13 +49,15 @@ struct JXL { JxlDecoderSetInput(decoder, nextIn, bytes.count) + var colorEncoding: JxlColorEncoding? = nil + parsingLoop: while true { let result = JxlDecoderProcessInput(decoder) switch result { case JXL_DEC_BASIC_INFO: if JxlDecoderGetBasicInfo(decoder, infoPtr) != JXL_DEC_SUCCESS { - Swift.print("Cannot get basic info") + os_log("Cannot get basic info", log: .decode, type: .error) break parsingLoop } let info: JxlBasicInfo = infoPtr.pointee @@ -65,20 +68,29 @@ struct JXL { let data_type: JxlDataType = output_num_channels < 3 ? info.bits_per_sample == 16 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8 : JXL_TYPE_FLOAT; format = JxlPixelFormat(num_channels: output_num_channels, data_type: data_type, endianness: JXL_NATIVE_ENDIAN, align: 0) - Swift.print("basic info: \(infoPtr.pointee)") + os_log("Basic info: %{public}@", log: .decode, type: .debug, "\(infoPtr.pointee)") case JXL_DEC_SUCCESS: return true case JXL_DEC_COLOR_ENCODING: - var iccSize: size_t = 0 - if JxlDecoderGetICCProfileSize(decoder, &format, JXL_COLOR_PROFILE_TARGET_DATA, &iccSize) != JXL_DEC_SUCCESS { - Swift.print("Cannot get ICC size") - } - icc?.deallocate() - icc = UnsafeMutableBufferPointer.allocate(capacity: iccSize) - if JxlDecoderGetColorAsICCProfile(decoder, &format, JXL_COLOR_PROFILE_TARGET_DATA, icc!.baseAddress, iccSize) != JXL_DEC_SUCCESS { - Swift.print("Cannot get ICC") + var encoding = JxlColorEncoding() + if JxlDecoderGetColorAsEncodedProfile(decoder, nil, + JXL_COLOR_PROFILE_TARGET_ORIGINAL, &encoding) == JXL_DEC_SUCCESS { + if JxlDecoderSetPreferredColorProfile(decoder, &encoding) != JXL_DEC_SUCCESS { + os_log("Cannot set color encoding", log: .decode, type: .error) + } + os_log("Color encoding: %{public}@", log: .decode, type: .debug, "\(encoding)") + colorEncoding = encoding + } else { + var iccSize: size_t = 0 + if JxlDecoderGetICCProfileSize(decoder, &format, JXL_COLOR_PROFILE_TARGET_DATA, &iccSize) != JXL_DEC_SUCCESS { + os_log("Cannot get ICC size", log: .decode, type: .error) + } + icc?.deallocate() + icc = UnsafeMutableBufferPointer.allocate(capacity: iccSize) + if JxlDecoderGetColorAsICCProfile(decoder, &format, JXL_COLOR_PROFILE_TARGET_DATA, icc!.baseAddress, iccSize) != JXL_DEC_SUCCESS { + os_log("Cannot get ICC", log: .decode, type: .error) + } } - case JXL_DEC_FULL_IMAGE: let info = infoPtr.pointee if (image != nil) { @@ -103,7 +115,9 @@ struct JXL { } } else { // assume it's rgb let num_channels = Int(format.num_channels) - let colorSpace = icc.flatMap({ NSColorSpace(iccProfileData: Data(buffer: $0)) }) ?? .sRGB + let colorSpace = decodeColorSpace(colorEncoding, icc) + os_log("Color space: %{public}@", log: .decode, type: .debug, "\(colorSpace)") + let colorSpaceName: NSColorSpaceName = .calibratedRGB var bitmapFormat: UInt = NSBitmapImageRep.Format.floatingPointSamples.rawValue; if (info.alpha_premultiplied == 0) { @@ -123,20 +137,20 @@ struct JXL { case JXL_DEC_NEED_IMAGE_OUT_BUFFER: var outputBufferSize: Int = 0 if JxlDecoderImageOutBufferSize(decoder, &format, &outputBufferSize) != JXL_DEC_SUCCESS { - Swift.print("cannot get size") + os_log("Cannot get size", log: .decode, type: .error) } - Swift.print("buffer size: \(outputBufferSize)") - + os_log("Buffer size: %d", log: .decode, type: .debug, outputBufferSize) + buffer.deallocate() buffer = UnsafeMutableBufferPointer.allocate(capacity: outputBufferSize) if JxlDecoderSetImageOutBuffer(decoder, &format, buffer.baseAddress, outputBufferSize) != JXL_DEC_SUCCESS { - Swift.print("cannot write buffer") + os_log("Cannot write buffer", log: .decode, type: .error) } case JXL_DEC_ERROR: return false default: - Swift.print("result \(result)") + os_log("Result: %{public}@", log: .decode, type: .debug, "\(result)") } } return false @@ -147,4 +161,68 @@ struct JXL { JxlDecoderDestroy(decoder) return image } + + private static func constructColorSpace(_ name: CFString) -> NSColorSpace { + guard let cgColorSpace = CGColorSpace(name: name) else { + os_log("Cannot get cgColorSpace for name: %{public}@", log: .decode, type: .error, + "\(name)") + return .sRGB + } + guard let nsColorSpace = NSColorSpace(cgColorSpace: cgColorSpace) else { + os_log("Cannot get nsColorSpace for cgColorSpace: %{public}@", log: .decode, + type: .error, "\(name)") + return .sRGB + } + return nsColorSpace + } + + private static func decodeColorSpace(_ colorEncoding: JxlColorEncoding?, + _ icc: UnsafeMutableBufferPointer?) -> NSColorSpace { + if let colorEncoding = colorEncoding { + switch colorEncoding.primaries { + case JXL_PRIMARIES_SRGB: + return .sRGB + case JXL_PRIMARIES_CUSTOM: + os_log("Mising implementation for color encoding primaries of type: JXL_PRIMARIES_CUSTOM", + log: .decode, type: .error) + return .sRGB + case JXL_PRIMARIES_2100: + switch colorEncoding.transfer_function { + case JXL_TRANSFER_FUNCTION_PQ: + if #available(macOS 11.0, *) { + return constructColorSpace(CGColorSpace.itur_2100_PQ) + } else if #available(macOS 10.15.4, *) { + return constructColorSpace(CGColorSpace.itur_2020_PQ) + } else { + return constructColorSpace(CGColorSpace.itur_2020_PQ_EOTF) + } + case JXL_TRANSFER_FUNCTION_HLG: + if #available(macOS 11.0, *) { + return constructColorSpace(CGColorSpace.itur_2100_HLG) + } else if #available(macOS 10.15.6, *) { + return constructColorSpace(CGColorSpace.itur_2020_HLG) + } else { + os_log("Mising implementation for transfer function of type: JXL_TRANSFER_FUNCTION_HLG", + log: .decode, type: .error) + return .sRGB + } + default: + os_log("Unexpected color transfer function: %{public}@", log: .decode, type: .error, + "\(colorEncoding.transfer_function)") + return .sRGB + } + case JXL_PRIMARIES_P3: + if #available(macOS 10.15.4, *) { + return constructColorSpace(CGColorSpace.displayP3_PQ) + } else { + return constructColorSpace(CGColorSpace.displayP3_PQ_EOTF) + } + default: + os_log("Unexpected color encoding primaries: %{public}@", log: .decode, type: .error, + "\(colorEncoding.primaries)") + return .sRGB + } + } + return icc.flatMap({ NSColorSpace(iccProfileData: Data(buffer: $0)) }) ?? .sRGB + } } diff --git a/JXLook/MetalImageView.swift b/JXLook/MetalImageView.swift new file mode 100644 index 0000000..3da39d6 --- /dev/null +++ b/JXLook/MetalImageView.swift @@ -0,0 +1,626 @@ +// +// MetalImageView.swift +// JXLook +// +// Created by low-batt on 6/1/23. +// + +import Cocoa +import MetalKit +import os + +/// View for displaying images using [Metal](https://developer.apple.com/metal/). +/// +/// At the time of this coding the latest version of macOS is Ventura. Under Ventura +/// [NSImageView](https://developer.apple.com/documentation/appkit/nsimageview) is unable to properly +/// display [High Dynamic Range ](https://developer.apple.com/documentation/metal/hdr_content/displaying_hdr_content_in_a_metal_layer#3335504) +/// images. `NSImageView` will not enable [Extended Dynamic Range](https://developer.apple.com/documentation/metal/hdr_content/displaying_hdr_content_in_a_metal_layer#3335505). +/// Highlights in HDR images beyond standard dynamic range will be clipped. +/// +/// This view extends [MTKView](https://developer.apple.com/documentation/metalkit/mtkview) to be able to +/// display a HDR image with EDR enabled, tone mapping the image to the current display headroom. +/// +/// This view contains a `NSImageView` subview. Whenever possible the `NSImageView` subview will be used to display the +/// image. The decision to use `Metal` or `NSImageView` is made by the `settingChanged` method. +/// +/// As per MVC conventions the controller is responsible for observing notifications of changes and appropriately adjusting the public +/// properties exposed by this view. +class MetalImageView: MTKView { + + @IBOutlet weak var imageView: NSImageView! + + // MARK: - Public Properties + + /// Whether to permit use of extended dynamic range for screens that support it. + var extendedDynamicRange = true { + didSet { + guard extendedDynamicRange != oldValue else { return } + settingChanged() + } + } + + /// The view’s frame rectangle, which defines its position and size in its superview’s coordinate system. + override var frame: NSRect { didSet { frameChanged() } } + + /// Whether to permit processing of high dynamic range images. + /// + /// Setting this to `false` disables special processing of HDR images. The image will be sent directly to the `NSImageView` + /// for display. This provides a workaround should there be any problems in the code that renders HDR images. + var highDynamicRange = true { + didSet { + guard highDynamicRange != oldValue else { return } + settingChanged() + } + } + + /// The image displayed by this view. + var image: NSImage? { + didSet { + forceMetalDraw = true + settingChanged() + } + } + + /// The screen the window containing this view is on. + var screen: NSScreen? { didSet { screenChanged() } } + + /// Whether to perform tone mapping on high dynamic range images. + /// + /// When extended dynamic range is unavailable this will cause HDR images to be tone mapped to SDR. When EDR is active this + /// will cause the Metal tone mapper to be used to keep the image within the display's current headroom. + var toneMapping = true { + didSet { + guard toneMapping != oldValue else { return } + settingChanged() + } + } + + // MARK: - Private Properties + + /// An evaluation context for rendering image processing performed by the `draw` method. + /// + /// This context is constructed by the `settingChanged` and saved in this property to reduce the amount of work performed + /// by the `draw` method. In [Processing an Image Using Built-in Filters](https://developer.apple.com/documentation/coreimage/processing_an_image_using_built-in_filters) + /// Apple indicates that creating a `CIContext` is expensive, therefore it is important to reuse context objects when possible. + private var ciContext: CIContext? + + /// Representation of the image for use by the `scaleAndCenterImage` method. + /// + /// This image is constructed by the `settingChanged` and saved in this property to reduce the amount of work performed + /// when the `frame` size is changed. + private var ciImage: CIImage? + + /// This flag instructs the `draw` method to erase the image displayed by this view. + /// + /// This happens when this view is displaying the image and we want the NSImageView subview to display the image. + private var clearImage = false + + /// Display headroom used to configure the `Metal` layer tone mapper. + private var configuredHeadroom: CGFloat = 1.0 + + /// The screen the window containing this view was on when the current configuration was generated. + private var configuredScreen: NSScreen? + +#if DEBUG + /// Whether to emit log messages detailing internal state. + /// + /// The log messages emitted are only of interest to developers. + private static let detailedDebugLogging = false +#endif + + /// Threshold at which a change in display headroom causes tone mapping to be reconfigured. + private static let headroomChangeThreshold = 0.02 + + /// Whether to explicitly draw the view or leave it to AppKit to decide when to display the view. + /// + /// This is needed to force an initial drawing of the view so that when a window initially starts minimized to the dock the window + /// counterpart generated by AppKit and shown as the dock icon contains a miniaturized image. + private var forceMetalDraw = true + + /// Scaled and centered image ready for use by the `draw` method. + /// + /// This image is constructed by the `scaleAndCenterImage` and saved in this property to reduce the amount of work + /// performed by the `draw` method. + private var imageToRender: CIImage? + + /// Full initialization is delayed and performed on demand as needed. + private var notInitialized = true + + /// Filter used by the `scaleAndCenterImage` method to scale the image to fit in the view. + private var scaleFilter: CIFilter? + + /// Whether this view is showing an image. + private var showingImage = false + + /// Whether this view is drawing the image (as opposed to the `NSImageView` subview). + private var useMetalRenderer = false + + // MARK: - Initialization + + required init(coder: NSCoder) { + super.init(coder: coder) + // By default a MTKView redraws its contents based on an internal timer. Reconfigure the + // view to redraw when something invalidates its contents. + enableSetNeedsDisplay = true + framebufferOnly = false + isPaused = true + } + + // MARK: - Drawing + + /// Draw or erase the image. + /// + /// This method will draw the image if the `settingChanged` method determined that + /// [Metal](https://developer.apple.com/metal/) **must** be used to display the image. Otherwise + /// preference is given to the [NSImageView](https://developer.apple.com/documentation/appkit/nsimageview) + /// subview. + /// + /// This method is also responsible for erasing the displayed image. This occurs when `Metal` is being used to display the image + /// and a configuration change causes `settingChanged` to determine `NSImageView` can now be used to display the image. + /// + /// - Note: No errors should be reported by this method. If any of the `guard` statements are violated then there is an internal + /// error in `MetalImageView` or `AppKit`. + /// + /// - Parameter dirtyRect: A rectangle defining the portion of the view that requires redrawing. The current implementation + /// ignores this parameter. + override func draw(_ dirtyRect: NSRect) { + guard useMetalRenderer || clearImage else { + // The NSImageView subview is being used to display the image instead of this view. + return + } + guard let device = device else { + os_log("Cannot get metal device", log: .render, type: .error) + return + } + guard let commandQueue = device.makeCommandQueue() else { + os_log("Cannot make a command queue", log: .render, type: .error) + return + } + guard let commandBuffer = commandQueue.makeCommandBuffer() else { + os_log("Cannot make a command buffer", log: .render, type: .error) + return + } + + // This sequence is required to erase the texture to the background color specified in the + // clearColor property. + guard let renderPassDescriptor = currentRenderPassDescriptor else { + os_log("Cannot get render pass descriptor", log: .render, type: .error) + return + } + guard let commandEncoder = commandBuffer.makeRenderCommandEncoder( + descriptor: renderPassDescriptor) else { + os_log("Cannot make a command encoder", log: .render, type: .error) + return + } + commandEncoder.endEncoding() + + // Draw or erase the image. + guard let drawable = currentDrawable else { + os_log("Cannot get current drawable", log: .render, type: .error) + return + } + if clearImage { + // Switching to displaying the image using the NSImageView subview. The clearColor has + // been set to a fully transparent color. Drawing this background color once erases the + // image being displayed by this view allowing the image drawn by the subview to be seen. + clearImage = false + showingImage = false + } else { + showingImage = true + + // Render the image into the Metal texture. + guard let ciContext = ciContext else { + os_log("Cannot get ciContext", log: .render, type: .error) + return + } + guard let colorspace = colorspace else { + os_log("Cannot get colorspace", log: .render, type: .error) + return + } + guard let imageToRender = imageToRender else { + os_log("Cannot get imageToRender", log: .render, type: .error) + return + } + let texture = drawable.texture + let width = CGFloat(texture.width) + let height = CGFloat(texture.height) + let textureBounds = CGRect(x: 0, y: 0, width: width, height: height) + ciContext.render(imageToRender, to: texture, commandBuffer: commandBuffer, + bounds: textureBounds, colorSpace: colorspace) + } + + commandBuffer.present(drawable) + commandBuffer.commit() + } + + // MARK: - Private Functions + + /// Configure Metal layer tone mapping for extended dynamic range images. + /// + /// This method constructs a [CAEDRMetadata](https://developer.apple.com/documentation/quartzcore/caedrmetadata) + /// object and stores it in the given Metal layer's + /// [edrMetadata](https://developer.apple.com/documentation/quartzcore/cametallayer/3182052-edrmetadata) + /// property. The maximum luminance is set to the current display headroom of the given screen. + /// + /// The headroom changes dynamically as discussed in [Extended Dynamic Range](https://developer.apple.com/documentation/metal/hdr_content/displaying_hdr_content_in_a_metal_layer#3335505). + /// The layer must be updated with a new `CAEDRMetadata` object if the display headroom changes significantly. The headroom + /// used is preserved in `configuredHeadroom`. This provides the ability to compare the headroom being used for tone + /// mapping to the current display headroom in order to determine if tone mapping needs to be reconfigured. A separate property + /// is needed because `CAEDRMetadata` does not provide any way to get the values used to construct it. + /// - Parameters: + /// - layer: The Metal layer on which to configure tone mapping + /// - screen: The screen the window containing this view is on + private func configureMetalToneMapping(_ layer: CAMetalLayer, _ screen: NSScreen) { + layer.edrMetadata = CAEDRMetadata.hdr10(minLuminance: 0.0, + maxLuminance: Float(screen.maximumExtendedDynamicRangeColorComponentValue * 100), + opticalOutputScale: 100) + configuredHeadroom = screen.maximumExtendedDynamicRangeColorComponentValue + } + + /// Adjust the view configuration to reflect a change in the view's frame. + private func frameChanged() { + /// Any changes to this view's frame must also be made to the `NSImageView` subview's frame. + imageView.frame = frame + + // If Metal is being used to draw the image then the scaled image used by the draw method + // must be rescaled to work with the new frame size. + guard useMetalRenderer else { return } + scaleAndCenterImage() + } + +#if DEBUG + /// Emit detailed log messages describing important state. + /// + /// This is a debugging tool for developers. + /// - Note: The property `detailedDebugLogging` must be set to `true` to enable the logging. + private func logState(function: String = #function, line: Int = #line) { + guard MetalImageView.detailedDebugLogging else { return } + os_log("State in %{public}@ at line %d:", log: .render, type: .debug, function, line) + os_log("clearImage: %{public}@", log: .render, type: .debug, String(clearImage)) + os_log("colorPixelFormat: %{public}@", log: .render, type: .debug, + String(describing: colorPixelFormat)) + if let colorspace = colorspace?.name { + os_log("colorspace: %{public}@", log: .render, type: .debug, "\(colorspace)") + } + os_log("configuredMaximumExtendedDynamicRange: %f", log: .render, type: .debug, configuredHeadroom) + if let image = image { + os_log("image.representations: %{public}@", log: .render, type: .debug, + image.representations) + } + if let image = imageView.image { + os_log("imageView.image.representations: %{public}@", log: .render, type: .debug, + image.representations) + } + if let layer = layer as? CAMetalLayer { + if let colorspace = layer.colorspace?.name { + os_log("layer.colorspace: %{public}@", log: .render, type: .debug, "\(colorspace)") + } + if let edrMetadata = layer.edrMetadata { + os_log("layer.edrMetadata: %{public}@", log: .render, type: .debug, "\(edrMetadata)") + } + os_log("layer.pixelFormat: %{public}@", log: .render, type: .debug, + "\(layer.pixelFormat)") + os_log("layer.wantsExtendedDynamicRangeContent: %{public}@", log: .render, type: .debug, + String(layer.wantsExtendedDynamicRangeContent)) + } + if let colorspace = screen?.colorSpace?.localizedName { + os_log("screen.colorSpace: %{public}@", log: .render, type: .debug, "\(colorspace)") + } + if let maximum = screen?.maximumExtendedDynamicRangeColorComponentValue { + os_log("screen.maximumExtendedDynamicRangeColorComponentValue: %f", + log: .render, type: .debug, maximum) + } + if let potential = screen?.maximumPotentialExtendedDynamicRangeColorComponentValue { + os_log("screen.maximumPotentialExtendedDynamicRangeColorComponentValue: %f", + log: .render, type: .debug, potential) + } + os_log("showingImage: %{public}@", log: .render, type: .debug, String(showingImage)) + os_log("useMetalRenderer: %{public}@", log: .render, type: .debug, String(useMetalRenderer)) + if let colorspace = window?.colorSpace?.localizedName { + os_log("window.colorspace: %{public}@", log: .render, type: .debug, "\(colorspace)") + } + } +#endif + + /// Perform delayed one time initialization. + /// + /// As SDR images are common, this method initializes on demand objects that are only needed when displaying HDR images. + private func onDemandInitialization() -> Bool { + guard notInitialized else { return true } + clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) + colorPixelFormat = .rgba16Float + device = preferredDevice ?? MTLCreateSystemDefaultDevice() + guard let device = device else { + os_log("Cannot get metal device", log: .render, type: .error) + return false + } + ciContext = CIContext(mtlDevice: device) + + // The Metal device usually corresponds to the GPU associated with the screen that’s + // displaying the view. Remember the screen that was used to detect when reconfiguration is + // required due to the window moving to a different screen. + guard let screen = screen else { + os_log("Cannot get screen", log: .render, type: .error) + return false + } + configuredScreen = screen + guard let layer = layer as? CAMetalLayer else { + os_log("Cannot get metal layer", log: .render, type: .fault) + return false + } + layer.isOpaque = false + layer.pixelFormat = .rgba16Float + guard let scaleFilter = CIFilter(name: "CILanczosScaleTransform") else { + os_log("Cannot get CILanczosScaleTransform filter", log: .render, type: .error) + return false + } + scaleFilter.setValue(1.0, forKey: kCIInputAspectRatioKey) + self.scaleFilter = scaleFilter + notInitialized = false + return true + } + + /// Scale and center the image for display in this view. + /// + /// The image contained in the `ciImage` property is scaled, centered and stored in the `imageToRender` property based on + /// the current [drawableSize](https://developer.apple.com/documentation/metalkit/mtkview/1535969-drawablesize). + /// This is done to reduce the amount of work performed by the `draw` method. + private func scaleAndCenterImage() { + + // Scale the image. + guard let ciImage = ciImage else { + os_log("Cannot get ciImage", log: .render, type: .error) + return + } + let width = CGFloat(drawableSize.width) + let height = CGFloat(drawableSize.height) + let scaleX = width / CGFloat(ciImage.extent.width) + let scaleY = height / CGFloat(ciImage.extent.height) + let factor = min(scaleX, scaleY) + guard let scaleFilter = scaleFilter else { + os_log("Cannot get scaleFilter", log: .render, type: .error) + return + } + scaleFilter.setValue(factor, forKey: kCIInputScaleKey) + let scaledImage = scaleFilter.outputImage! + + // Center the image in the view. + let originX = max(width - scaledImage.extent.width, 0) / 2 + let originY = max(height - scaledImage.extent.height, 0) / 2 + imageToRender = scaledImage.transformed(by: CGAffineTransform( + translationX: originX, y: originY)) + } + + /// Adjust the view configuration to reflect a change in the screen the window is on. + private func screenChanged() { + guard let screen = screen else { return } + + // No need to process screen changes until on demand initialization has been done. + guard !notInitialized else { return } + + // Check to see if the window moved to a different screen. + if screen != configuredScreen { + os_log("Window moved to screen: %{public}@", log: .render, type: .debug, + screen.localizedName) + configuredScreen = screen + + // The preferredDevice usually corresponds to the GPU associated with the screen that’s + // displaying the view. Ensure the optimum Metal device is being used. + device = preferredDevice ?? MTLCreateSystemDefaultDevice() + guard let device = device else { + os_log("Cannot get metal device", log: .render, type: .error) + return + } + ciContext = CIContext(mtlDevice: device) + + // Aspects such as the ability to support extended dynamic range may have changed. + // Reassess the configuration. + settingChanged() + return + } + + // Same screen. Screen attributes must have changed. We are only interested in the display + // headroom and only if the Metal renderer is being used with tone mapping. + guard useMetalRenderer, toneMapping else { return } + + // Compare the current display headroom to the headroom used to configure tone mapping. + let currentHeadroom = screen.maximumExtendedDynamicRangeColorComponentValue + let changeInHeadroom = abs(currentHeadroom - configuredHeadroom) + + // The display headroom frequently changes by small amounts. To avoid constantly adjusting + // tone mapping and redrawing ignore changes until the difference between the configured + // and current headroom passes a threshold. + guard changeInHeadroom > MetalImageView.headroomChangeThreshold else { return } + guard let layer = layer as? CAMetalLayer else { + os_log("Cannot get metal layer", log: .render, type: .error) + return + } + configureMetalToneMapping(layer, screen) + needsDisplay = true + } + + /// Adjust the view configuration to reflect a change in settings. + /// + /// To minimize the amount of work done in the `draw` method objects that are not dependent upon the view size are + /// constructed by this method and cached in private properties. When any of the inputs used by this method change it is called + /// again to reconstruct the objects. + /// + /// This method is also responsible for deciding if this view should render the image or if the `NSViewImage` subview should be + /// used. Should any problems constructing the objects be encountered the subview will be used. + private func settingChanged() { + + // Discard state. + ciImage = nil + clearImage = false + imageToRender = nil + imageView.image = nil + guard let layer = layer as? CAMetalLayer else { + os_log("Cannot get metal layer", log: .render, type: .error) + return + } + layer.colorspace = nil + layer.edrMetadata = nil + layer.wantsExtendedDynamicRangeContent = false + useMetalRenderer = false + + // Nothing more to do if we don't have an image to display. + guard let image = image else { return } + + // If this method exits without having decided to use the Metal renderer then switch to + // using the NSImageView subview by passing the image to that view. + defer { + if !useMetalRenderer { + // If the metal view is already displaying an image then it must be cleared so that + // the image being displayed by the subview is visible. + if showingImage { + clearImage = true + needsDisplay = true + os_log("Clearing image displayed by MTKView", log: .render, type: .debug) + } + // Pass the image to the NSImageView subview unless it has already been given a + // tone mapped image to display. + if imageView.image == nil { imageView.image = image } + imageView.needsDisplay = true + os_log("Using NSImageView to draw image", log: .render, type: .debug) + } +#if DEBUG + logState() +#endif + } + + if !highDynamicRange { + // The user disabled HDR in settings. This setting allows the user to fall back to using + // NSImageView for rendering should a severe problem with this Metal based renderer be + // encountered. Of course as of macOS Ventura NSImageView does not tone map HDR images + // so highlights will be clipped. + os_log("High dynamic range is disabled", log: .render, type: .info) + return + } + + // Use the NSImageView subview to display the SDR images. + let imageFilename = window?.title ?? "" + guard image.isHighDynamicRange else { + os_log("Not a high dynamic range image: %{private}@", log: .render, type: .info, + imageFilename) + return + } + os_log("Rendering high dynamic range image: %{private}@", log: .render, type: .info, + imageFilename) + + // Perform one time initialization that is not needed when displaying SDR images. + guard onDemandInitialization() else { return } + + // Determine if extended dynamic range can be used. + if extendedDynamicRange { + guard let screen = screen else { + os_log("Cannot get screen", log: .render, type: .error) + return + } + if screen.maximumPotentialExtendedDynamicRangeColorComponentValue > 1.0 { + os_log("Screen supports extended dynamic range: %{public}@ (max %.1fx)", + log: .render, type: .info, screen.localizedName, + screen.maximumPotentialExtendedDynamicRangeColorComponentValue) + layer.wantsExtendedDynamicRangeContent = true + if toneMapping { + if image.isInHybridLogGammaFormat { + // CAEDRMetadata documentation indicates the HLG inverse OETF must be + // applied to use HLG tone mapping. That has not been implemented, so EDR + // tone mapping can not be used for HLG images at this time. + os_log("Tone mapping for images using the hybrid log-gamma format is not implemented", + log: .render, type: .info) + return + } + configureMetalToneMapping(layer, screen) + os_log("Tone mapping HDR image to display headroom", log: .render, type: .info) + } else if #available(macOS 13.0, *){ + // Under macOS Ventura NSImageView is capable of displaying a HDR image once + // EDR has been enabled in the image, as long as tone mapping is not needed. + os_log("Tone mapping is disabled", log: .render, type: .info) + return + } + } else { + os_log("Screen does not support extended dynamic range: %{public}@", log: .render, + type: .info, screen.localizedName) + } + } else { + // The user disabled use of EDR. As EDR consumes more energy the user might choose to + // turn it off to conserve battery. + os_log("Extended dynamic range is disabled", log: .render, type: .info) + } + + if !layer.wantsExtendedDynamicRangeContent { + if !toneMapping { + os_log("Tone mapping is disabled", log: .render, type: .info) + return + } + if #unavailable(macOS 11.0) { + os_log("Tone mapping is not working under macOS Catalina", log: .render, type: .info) + return + } + } + + guard !image.representations.isEmpty, + let bitmapImageRep = image.representations[0] as? NSBitmapImageRep else { + // Internal error. A NSBitmapImageRep should have been attached by JXL.parse. + os_log("Cannot get bitmapImageRep", log: .render, type: .error) + return + } + guard let ciImage = CIImage(bitmapImageRep: bitmapImageRep) else { + os_log("Cannot get ciImage", log: .render, type: .error) + return + } + + // If not using EDR then tone map the image to SDR. + if !layer.wantsExtendedDynamicRangeContent { + guard let ciContext = ciContext else { + os_log("Cannot get ciContext", log: .render, type: .error) + return + } + let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) + guard let cgImage = cgImage else { + os_log("Cannot get cgImage", log: .render, type: .error) + return + } + let size = NSSize(width: cgImage.width, height: cgImage.height) + imageView.image = NSImage(cgImage: cgImage, size: size) + return + } + + // Use Metal to draw the image. + self.ciImage = ciImage + + // Use an extended colorspace for EDR. + guard let colorspace = CGColorSpace(name: CGColorSpace.extendedLinearDisplayP3) else { + os_log("Cannot get extendedLinearDisplayP3 colorspace", log: .render, type: .error) + return + } + layer.colorspace = colorspace + + // The reusable scale filter must have already been constructed and initialized. + guard let scaleFilter = scaleFilter else { + os_log("Cannot get scaleFilter", log: .render, type: .error) + return + } + scaleFilter.setValue(ciImage, forKey: kCIInputImageKey) + scaleAndCenterImage() + + useMetalRenderer = true + os_log("Using MTKView to draw image", log: .render, type: .debug) + + // Normally leave it to AppKit to decide when to redisplay the view. + guard forceMetalDraw else { + needsDisplay = true + return + } + forceMetalDraw = false + + // When the window initially starts minimized to the dock setting needsDisplay will not + // cause AppKit to draw the view. This is expected as the view is not visible. However this + // causes the AppKit code that generates the window counterpart to construct a dock icon + // that does not contain a miniaturized image as occurs with NSImageView. Forcing the view + // to be drawn works around this issue. The window property isMiniaturized is of no help as + // it initially returns false and is only updated by AppKit to true after the counterpart + // has been generated. + draw() + } +} diff --git a/JXLook/NSImageExtension.swift b/JXLook/NSImageExtension.swift new file mode 100644 index 0000000..4c50c1a --- /dev/null +++ b/JXLook/NSImageExtension.swift @@ -0,0 +1,72 @@ +// +// NSImageExtension.swift +// JXLook +// +// Created by low-batt on 6/30/23. +// + +import Cocoa +import os + +extension NSImage { + + /// Whether this is a high dynamic range image. + /// + /// This is not a general purpose extension, it relies upon the image having been created by `JKL.parse`. + var isHighDynamicRange: Bool { + guard !representations.isEmpty, + let bitmapImageRep = representations[0] as? NSBitmapImageRep else { + // Internal error. A NSBitmapImageRep should have been attached by JXL.parse. + os_log("Cannot get bitmapImageRep", log: .render, type: .error) + return false + } + guard let cgColorSpace = bitmapImageRep.colorSpace.cgColorSpace, + let colorSpaceName = cgColorSpace.name else { + os_log("Cannot get cgColorSpace name", log: .render, type: .error) + return false + } + if #available(macOS 11.0, *) { + if colorSpaceName == CGColorSpace.itur_2100_PQ { + return true + } + } else if #available(macOS 10.15.4, *) { + if colorSpaceName == CGColorSpace.itur_2020_PQ { + return true + } + } else if colorSpaceName == CGColorSpace.itur_2020_PQ_EOTF { + return true + } + if #available(macOS 11.0, *) { + if colorSpaceName == CGColorSpace.itur_2100_HLG { + return true + } + } else if #available(macOS 10.15.6, *), colorSpaceName == CGColorSpace.itur_2020_HLG { + return true + } + return false + } + + /// Whether this is a high dynamic range image that uses the hybrid log-gamma format. + /// + /// This is not a general purpose extension, it relies upon the image having been created by `JKL.parse`. + var isInHybridLogGammaFormat: Bool { + guard !representations.isEmpty, + let bitmapImageRep = representations[0] as? NSBitmapImageRep else { + // Internal error. A NSBitmapImageRep should have been attached by JXL.parse. + os_log("Cannot get bitmapImageRep", log: .render, type: .error) + return false + } + guard let colorSpaceName = bitmapImageRep.colorSpace.cgColorSpace?.name else { + // Internal error. Should be able to obtain the name of the color space. + os_log("Cannot get cgColorSpace name", log: .render, type: .error) + return false + } + if #available(macOS 11.0, *) { + return colorSpaceName == CGColorSpace.itur_2100_HLG + } + if #available(macOS 10.15.6, *) { + return colorSpaceName == CGColorSpace.itur_2020_HLG + } + return false + } +} diff --git a/JXLook/NSScreenExtension.swift b/JXLook/NSScreenExtension.swift new file mode 100644 index 0000000..4d0e947 --- /dev/null +++ b/JXLook/NSScreenExtension.swift @@ -0,0 +1,28 @@ +// +// NSScreenExtension.swift +// JXLook +// +// Created by low-batt on 6/26/23. +// + +import Cocoa + +extension NSScreen { + + /// A textual representation of this instance. + /// + /// Customize the string conversion for a `NSScreen` object to contain the values of the properties that are important to `JXLook`. + public override var description: String { + var result = "NSScreen(localizedName: \(localizedName), " + if let name = colorSpace?.localizedName { + result += "colorSpace: \(name), " + } + result += """ +maximumExtendedDynamicRangeColorComponentValue: \ +\(maximumExtendedDynamicRangeColorComponentValue), \ +maximumPotentialExtendedDynamicRangeColorComponentValue: \ +\(maximumPotentialExtendedDynamicRangeColorComponentValue)) +""" + return result + } +} diff --git a/JXLook/OSLogExtension.swift b/JXLook/OSLogExtension.swift new file mode 100644 index 0000000..79f1a23 --- /dev/null +++ b/JXLook/OSLogExtension.swift @@ -0,0 +1,38 @@ +// +// OSLogExtension.swift +// JXLook +// +// Created by low-batt on 6/26/23. +// + +import Foundation +import os + +/// Configure `OSLog` for use by JXLook. +/// +/// - Note: [Logger](https://developer.apple.com/documentation/os/logger) is not used as it is not available under +/// macOS Catalina. +extension OSLog { + + // MARK: - Log Categories + + /// Category for messages concerning decoding the JPEG XL file. + static let decode = construct("decode") + + /// Category for messages concerning rendering the image. + static let render = construct("render") + + /// Category for messages concering application settings. + static let settings = construct("settings") + + /// Category for high level messages concerning viewing the JPEG XL image. + static let view = construct("view") + + // MARK: - Private + + private static let subsystem = Bundle.main.bundleIdentifier! + + private static func construct(_ category: String) -> OSLog { + OSLog(subsystem: subsystem, category: category) + } +} diff --git a/JXLook/Settings.swift b/JXLook/Settings.swift new file mode 100644 index 0000000..2418722 --- /dev/null +++ b/JXLook/Settings.swift @@ -0,0 +1,102 @@ +// +// Settings.swift +// JXLook +// +// Created by low-batt on 6/12/23. +// + +import Foundation +import os + +/// An interface for accessing user's application settings. +/// +/// This is merely a wrapper around the macOS +/// [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) +/// system that simplifies access to settings. +struct Settings { + /// The `Settings` singleton object. + static let shared = Settings() + + enum Key: String { + case extendedDynamicRange + case highDynamicRange + case toneMapping + } + + // MARK: - Settings Properties + + /// Enable extended dynamic range for high dynamic range images on screens that support EDR. + /// + /// - Note: EDR support requires the `highDynamicRange` setting be enabled. + var extendedDynamicRange: Bool { bool(.extendedDynamicRange) } + + /// Enable use of a custom a renderer for high dynamic range images. + /// + /// Disabling this setting results in HDR images being passed directly to + /// [NSImageView](https://developer.apple.com/documentation/appkit/nsimageview). As of macOS Ventura + /// `NSImageView` does not enable EDR and does not tone map HDR images resulting in clipping of highlights. Therefore + /// disabling this setting usually produces undesirable results for HDR images. This setting is useful in order to be able to see the + /// changes in rendering provided by the `MetalImageView` class. This also provides a workaround should problems in the + /// custom rendering code be encountered. + var highDynamicRange: Bool { bool(.highDynamicRange) } + + /// Enable tone mapping for high dynamic range images. + /// + /// When EDR is active HDR images will be dynamically tone mapped to stay within the current headroom of the display. When + /// EDR is not available HDR images will be tone mapped to SDR. + /// - Note: tone mapping support requires the `highDynamicRange` setting be enabled. + var toneMapping: Bool { bool(.toneMapping) } + + // MARK: - Default Registration + + /// Register default values with the + /// [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) system for all of + /// the settings + func registerDefaults() { + let defaults: [String: Any] = [Key.extendedDynamicRange.rawValue: true, + Key.highDynamicRange.rawValue: true, + Key.toneMapping.rawValue: true] + UserDefaults.standard.register(defaults: defaults) + } + + // MARK: - Observer Registration + + /// Registers the observer object to receive notifications for the specified setting. + /// + /// This is a simple wrapper that merely allows the caller to specify the setting as a `Key` enumeration value instead of a String. + /// - Parameters: + /// - observer: The object to register for notifications of changes to the specified setting. The observer must implement + /// the key-value observing method [observeValue(forKeyPath:of:change:context:)](https://developer.apple.com/documentation/objectivec/nsobject/1416553-observevalue). + /// - key: The setting to observe. + /// - options: A combination of the `NSKeyValueObservingOptions` values that specifies what is included in + /// observation notifications. For possible values, see [NSKeyValueObservingOptions](https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions). + /// - context: Arbitrary data that is passed to observer in [observeValue(forKeyPath:of:change:context:)](https://developer.apple.com/documentation/objectivec/nsobject/1416553-observevalue). + func addObserver(_ observer: NSObject, forKey key: Key, options: NSKeyValueObservingOptions = [], + context: UnsafeMutableRawPointer?) { + UserDefaults.standard.addObserver(observer, forKeyPath: key.rawValue, options: options, + context: context) + } + + /// Removes matching entries from the notification center's dispatch table. + /// + /// This is a simple wrapper that merely allows the caller to specify the setting as a `Key` enumeration value instead of a String. + /// - Parameters: + /// - observer: The observer to remove from the dispatch table. Specify an observer to remove only entries for this observer. + /// - key: The setting being observed. + func removeObserver(_ observer: NSObject, forKey key: Key) { + UserDefaults.standard.removeObserver(observer, forKeyPath: key.rawValue) + } + + // MARK: - Private Functions + + /// Returns the value for the specified setting. + /// + /// This is a simple wrapper that merely allows the caller to specify the setting as a `Key` enumeration value instead of a String. + /// - Parameter key: The setting to return the value of. + /// - Returns: The value set for the specified setting. + private func bool(_ key: Key) -> Bool { + UserDefaults.standard.bool(forKey: key.rawValue) + } + + private init() {} +} diff --git a/JXLook/ViewController.swift b/JXLook/ViewController.swift index c75870d..234ac97 100644 --- a/JXLook/ViewController.swift +++ b/JXLook/ViewController.swift @@ -12,7 +12,7 @@ class ViewController: NSViewController { static let minSize = CGSize(width: 200, height: 200) - @IBOutlet weak var imageView: NSImageView! + @IBOutlet weak var imageView: MetalImageView! @IBOutlet weak var clipView: CenterClipView! @IBOutlet weak var scrollView: NSScrollView! @@ -22,9 +22,30 @@ class ViewController: NSViewController { } } + private var addedSettingsObservers = false + + deinit { + NotificationCenter.default.removeObserver(self, + name: NSWindow.didChangeOcclusionStateNotification, object: nil) + removeObserverForScreenChanges() + guard addedSettingsObservers else { return } + Settings.shared.removeObserver(self, forKey: .extendedDynamicRange) + Settings.shared.removeObserver(self, forKey: .highDynamicRange) + Settings.shared.removeObserver(self, forKey: .toneMapping) + } + override func viewDidLoad() { super.viewDidLoad() scrollView.allowsMagnification = true + imageView.highDynamicRange = Settings.shared.highDynamicRange + imageView.extendedDynamicRange = Settings.shared.extendedDynamicRange + imageView.toneMapping = Settings.shared.toneMapping + Settings.shared.addObserver(self, forKey: .extendedDynamicRange, options: .new, context: nil) + Settings.shared.addObserver(self, forKey: .highDynamicRange, options: .new, context: nil) + Settings.shared.addObserver(self, forKey: .toneMapping, options: .new, context: nil) + addedSettingsObservers = true + NotificationCenter.default.addObserver(self, selector: #selector(occlusionStateChanged), + name: NSWindow.didChangeOcclusionStateNotification, object: nil) } override func viewWillAppear() { @@ -32,15 +53,17 @@ class ViewController: NSViewController { } override func viewDidDisappear() { - NotificationCenter.default.removeObserver(self) + NotificationCenter.default.removeObserver(self, + name: NSScrollView.willStartLiveMagnifyNotification, object: self.scrollView) } override var representedObject: Any? { didSet { if let doc = representedObject as? Document, let img = doc.image { - self.imageView.image = img - let window = self.view.window! + imageView.screen = window.screen + self.imageView.image = img + addOrRemoveScreenObserver() let windowTitle = window.frame.height - scrollView.frame.height let maxWindowFrame = window.constrainFrameRect(CGRect(origin: CGPoint.zero, size: CGSize(width: img.size.width, height: img.size.height + windowTitle)), to: window.screen) let maxContentSize = CGSize(width: maxWindowFrame.width, height: maxWindowFrame.height - windowTitle) @@ -65,7 +88,7 @@ class ViewController: NSViewController { self.zoomToFit = true window.setContentSize(CGSize(width: maxContentSize.width, height: maxContentSize.height)) imageView.frame = CGRect(origin: CGPoint.zero, size: maxContentSize) - } + } } } } @@ -110,5 +133,106 @@ class ViewController: NSViewController { } return true } -} + // MARK: - Observers + + /// Observer for changes to application settings stored in [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults). + /// - Parameters: + /// - keyPath; The key path, relative to `object`, to the value that has changed. + /// - object: The source object of the key path `keyPath`. + /// - change: A dictionary that describes the changes that have been made to the value of the property at the key path + /// `keyPath` relative to object. Entries are described in `Change Dictionary Keys`. + /// - context: The value that was provided when the observer was registered to receive key-value observation notifications. + override func observeValue(forKeyPath keyPath: String?, of object: Any?, + change: [NSKeyValueChangeKey: Any]?, + context: UnsafeMutableRawPointer?) { + guard let keyPath = keyPath else { + os_log("Observed key path is missing", log: .settings, type: .error) + return + } + guard let key = Settings.Key(rawValue: keyPath) else { + os_log("Observed key path is unrecognized: %{public}@", log: .settings, type: .error, + keyPath) + return + } + guard let valueAsBool = change?[.newKey] as? Bool else { + os_log("Cannot get new value for key %{public}@", log: .settings, type: .error, keyPath) + return + } + let suffix = "has been " + (valueAsBool ? "enabled" : "disabled") + switch key { + case .extendedDynamicRange: + os_log("Extended dynamic range %{public}@", log: .settings, type: .info, suffix) + // Changes to this setting can affect the need to observe screen changes. + addOrRemoveScreenObserver() + imageView.extendedDynamicRange = valueAsBool + case .highDynamicRange: + os_log("High dynamic range %{public}@", log: .settings, type: .info, suffix) + // Changes to this setting can affect the need to observe screen changes. + addOrRemoveScreenObserver() + imageView.highDynamicRange = valueAsBool + case .toneMapping: + os_log("Tone mapping %{public}@", log: .settings, type: .info, suffix) + imageView.toneMapping = valueAsBool + } + } + + /// Observer for [didChangeOcclusionStateNotification](https://developer.apple.com/documentation/appkit/nswindow/1419549-didchangeocclusionstatenotificat) + /// + /// Changes in window visibility can affect the need to observe screen changes. + /// - Parameter notification: `NSWindow` object whose occlusion state changed. + @objc func occlusionStateChanged(notification: NSNotification) { + addOrRemoveScreenObserver() + } + + /// Observer for [didChangeScreenNotification](https://developer.apple.com/documentation/appkit/nswindow/1419552-didchangescreennotification) + /// - Parameter notification: `NSWindow` object that has changed screens. + @objc func screenChanged(notification: NSNotification) { + guard let screen = view.window?.screen else { return } + imageView.screen = screen + } + + // MARK: - Private Functions + + /// Add or remove the screen changes observer based on need. + /// + /// This method adds or removes an observer for screen changes based on the current need to monitor changes to the screen. + /// When extended dynamic range is active screen change notifications will be posted at a significant rate in order to report + /// changes in display headroom which can dynamically change due to ambient conditions. Adherence to energy efficiency best + /// practices requires removing observers if they are not needed to avoid needless processing. + private func addOrRemoveScreenObserver() { + guard mustObserveScreenChanges() else { + removeObserverForScreenChanges() + return + } + NotificationCenter.default.addObserver(self, selector: #selector(screenChanged), + name: NSWindow.didChangeScreenNotification, object: nil) + imageView.screen = view.window?.screen + } + + /// Determine if an observer for screen changes is needed. + /// + /// The [NSScreen](https://developer.apple.com/documentation/appkit/nsscreen) properties of interest are: + ///- [maximumPotentialExtendedDynamicRange](https://developer.apple.com/documentation/appkit/nsscreen/3180381-maximumpotentialextendeddynamicr) + /// The value of this property indicates if the screen supports extended dynamic range. + /// - [maximumExtendedDynamicRangeColor](https://developer.apple.com/documentation/appkit/nsscreen/1388362-maximumextendeddynamicrangecolor) + /// The value of this property provides the current display extended dynamic range headroom. + /// + /// Therefore changes to the screen the window is on only needs to be actively monitored if: + /// - The user has not disabled support for high dynamic range images + /// - The user has not disabled support for activating enhanced dynamic range + /// - The window is visible + /// - A high dynamic range image is being displayed + /// - Returns: `true` if screen changes must be observed, `false` otherwise. + private func mustObserveScreenChanges() -> Bool { + guard Settings.shared.highDynamicRange, Settings.shared.extendedDynamicRange, + let window = view.window, window.occlusionState.contains(.visible), + let image = imageView.image, image.isHighDynamicRange else { return false } + return true + } + + private func removeObserverForScreenChanges() { + NotificationCenter.default.removeObserver(self, + name: NSWindow.didChangeScreenNotification, object: nil) + } +} diff --git a/JXLookTests/Info.plist b/JXLookTests/Info.plist index d50b002..664a1bf 100644 --- a/JXLookTests/Info.plist +++ b/JXLookTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(APP_VERSION) CFBundleVersion - 1036 + 2420 diff --git a/JXLookUITests/Info.plist b/JXLookUITests/Info.plist index d50b002..664a1bf 100644 --- a/JXLookUITests/Info.plist +++ b/JXLookUITests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(APP_VERSION) CFBundleVersion - 1036 + 2420 diff --git a/JXQuickLook/Info.plist b/JXQuickLook/Info.plist index 140ff9f..c402b9b 100644 --- a/JXQuickLook/Info.plist +++ b/JXQuickLook/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(APP_VERSION) CFBundleVersion - 1036 + 2420 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSExtension diff --git a/JXQuickLookThumbnail/Info.plist b/JXQuickLookThumbnail/Info.plist index b41b138..d7807e3 100644 --- a/JXQuickLookThumbnail/Info.plist +++ b/JXQuickLookThumbnail/Info.plist @@ -2,8 +2,24 @@ + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + JXQuickLookThumbnail + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(APP_VERSION) CFBundleVersion - 1036 + 2420 NSExtension NSExtensionAttributes @@ -20,5 +36,7 @@ NSExtensionPrincipalClass $(PRODUCT_MODULE_NAME).ThumbnailProvider + NSHumanReadableCopyright + diff --git a/build_libjxl.sh b/build_libjxl.sh index f39ba29..a64216e 100755 --- a/build_libjxl.sh +++ b/build_libjxl.sh @@ -1,13 +1,15 @@ #!/bin/bash -git clone https://github.com/libjxl/libjxl.git --recursive +# As of July 2023 the latest libjxl release is v0.8.2. + +git clone https://github.com/libjxl/libjxl.git --recursive --branch=v0.8.2 pushd libjxl mkdir -p build pushd build -CMAKE_OSX_ARCHITECTURES='x86_64;arm64' cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET='11.0' .. +CMAKE_OSX_ARCHITECTURES='x86_64;arm64' cmake -DJPEGXL_STATIC=true -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET='10.15' .. CMAKE_OSX_ARCHITECTURES='x86_64;arm64' cmake --build . --target jxl-static -- -j CMAKE_OSX_ARCHITECTURES='x86_64;arm64' cmake --build . --target jxl_threads-static -- -j @@ -17,8 +19,11 @@ popd mkdir -p jpeg-xl/lib mkdir -p jpeg-xl/include/jxl cp -R libjxl/build/lib/libjxl*.a jpeg-xl/lib -cp -R libjxl/build/third_party/highway/libhwy.a jpeg-xl/lib -cp -R libjxl/build/third_party/brotli/libbrotli*-static.a jpeg-xl/lib +cp libjxl/build/third_party/highway/libhwy.a jpeg-xl/lib + +# Only need decoder, avoid copying the encoder library. +cp libjxl/build/third_party/brotli/libbrotlicommon-static.a jpeg-xl/lib/libbrotlicommon.a +cp libjxl/build/third_party/brotli/libbrotlidec-static.a jpeg-xl/lib/libbrotlidec.a cp -R libjxl/build/lib/include/jxl/* jpeg-xl/include/jxl/ cp -R libjxl/lib/include/jxl/* jpeg-xl/include/jxl/ diff --git a/jpeg-xl/include/jxl/cms_interface.h b/jpeg-xl/include/jxl/cms_interface.h new file mode 100644 index 0000000..fb852ee --- /dev/null +++ b/jpeg-xl/include/jxl/cms_interface.h @@ -0,0 +1,232 @@ +/* Copyright (c) the JPEG XL Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/** @addtogroup libjxl_common + * @{ + * @file cms_interface.h + * @brief Interface to allow the injection of different color management systems + * (CMSes, also called color management modules, or CMMs) in JPEG XL. + * + * A CMS is needed by the JPEG XL encoder and decoder to perform colorspace + * conversions. This defines an interface that can be implemented for different + * CMSes and then passed to the library. + */ + +#ifndef JXL_CMS_INTERFACE_H_ +#define JXL_CMS_INTERFACE_H_ + +#include "jxl/color_encoding.h" +#include "jxl/types.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +/** Represents an input or output colorspace to a color transform, as a + * serialized ICC profile. */ +typedef struct { + /** The serialized ICC profile. This is guaranteed to be present and valid. */ + struct { + const uint8_t* data; + size_t size; + } icc; + + /** Structured representation of the colorspace, if applicable. If all fields + * are different from their "unknown" value, then this is equivalent to the + * ICC representation of the colorspace. If some are "unknown", those that are + * not are still valid and can still be used on their own if they are useful. + */ + JxlColorEncoding color_encoding; + + /** Number of components per pixel. This can be deduced from the other + * representations of the colorspace but is provided for convenience and + * validation. */ + size_t num_channels; +} JxlColorProfile; + +/** Allocates and returns the data needed for @p num_threads parallel transforms + * from the @p input colorspace to @p output, with up to @p pixels_per_thread + * pixels to transform per call to JxlCmsInterface::run. @p init_data comes + * directly from the JxlCmsInterface instance. Since @c run only receives the + * data returned by @c init, a reference to @p init_data should be kept there + * if access to it is desired in @c run. Likewise for JxlCmsInterface::destroy. + * + * The ICC data in @p input and @p output is guaranteed to outlive the @c init / + * @c run / @c destroy cycle. + * + * @param init_data JxlCmsInterface::init_data passed as-is. + * @param num_threads the maximum number of threads from which + * JxlCmsInterface::run will be called. + * @param pixels_per_thread the maximum number of pixels that each call to + * JxlCmsInterface::run will have to transform. + * @param input_profile the input colorspace for the transform. + * @param output_profile the colorspace to which JxlCmsInterface::run should + * convert the input data. + * @param intensity_target for colorspaces where luminance is relative + * (essentially: not PQ), indicates the luminance at which (1, 1, 1) will + * be displayed. This is useful for conversions between PQ and a relative + * luminance colorspace, in either direction: @p intensity_target cd/m² + * in PQ should map to and from (1, 1, 1) in the relative one.\n + * It is also used for conversions to and from HLG, as it is + * scene-referred while other colorspaces are assumed to be + * display-referred. That is, conversions from HLG should apply the OOTF + * for a peak display luminance of @p intensity_target, and conversions + * to HLG should undo it. The OOTF is a gamma function applied to the + * luminance channel (https://www.itu.int/rec/R-REC-BT.2100-2-201807-I + * page 7), with the gamma value computed as + * 1.2 * 1.111^log2(intensity_target / 1000) (footnote 2 page 8 + * of the same document). + * @return The data needed for the transform, or @c NULL in case of failure. + * This will be passed to the other functions as @c user_data. + */ +typedef void* (*jpegxl_cms_init_func)(void* init_data, size_t num_threads, + size_t pixels_per_thread, + const JxlColorProfile* input_profile, + const JxlColorProfile* output_profile, + float intensity_target); + +/** Returns a buffer that can be used by callers of the interface to store the + * input of the conversion or read its result, if they pass it as the input or + * output of the @c run function. + * @param user_data the data returned by @c init. + * @param thread the index of the thread for which to return a buffer. + * @return A buffer that can be used by the caller for passing to @c run. + */ +typedef float* (*jpegxl_cms_get_buffer_func)(void* user_data, size_t thread); + +/** Executes one transform and returns true on success or false on error. It + * must be possible to call this from different threads with different values + * for @p thread, all between 0 (inclusive) and the value of @p num_threads + * passed to @c init (exclusive). It is allowed to implement this by locking + * such that the transforms are essentially performed sequentially, if such a + * performance profile is acceptable. @p user_data is the data returned by + * @c init. + * The buffers each contain @p num_pixels × @c num_channels interleaved floating + * point (0..1) samples where @c num_channels is the number of color channels of + * their respective color profiles. It is guaranteed that the only case in which + * they might overlap is if the output has fewer channels than the input, in + * which case the pointers may be identical. + * For CMYK data, 0 represents the maximum amount of ink while 1 represents no + * ink. + * @param user_data the data returned by @c init. + * @param thread the index of the thread from which the function is being + * called. + * @param input_buffer the buffer containing the pixel data to be transformed. + * @param output_buffer the buffer receiving the transformed pixel data. + * @param num_pixels the number of pixels to transform from @p input to + * @p output. + * @return JXL_TRUE on success, JXL_FALSE on failure. + */ +typedef JXL_BOOL (*jpegxl_cms_run_func)(void* user_data, size_t thread, + const float* input_buffer, + float* output_buffer, + size_t num_pixels); + +/** Performs the necessary clean-up and frees the memory allocated for user + * data. + */ +typedef void (*jpegxl_cms_destroy_func)(void*); + +/** + * Interface for performing colorspace transforms. The @c init function can be + * called several times to instantiate several transforms, including before + * other transforms have been destroyed. + * + * The call sequence for a given colorspace transform could look like the + * following: + * @dot + * digraph calls { + * newrank = true + * node [shape = box, fontname = monospace] + * init [label = "user_data <- init(\l\ + * init_data = data,\l\ + * num_threads = 3,\l\ + * pixels_per_thread = 20,\l\ + * input = (sRGB, 3 channels),\l\ + * output = (Display-P3, 3 channels),\l\ + * intensity_target = 255\l\ + * )\l"] + * subgraph cluster_0 { + * color = lightgrey + * label = "thread 1" + * labeljust = "c" + * run_1_1 [label = "run(\l\ + * user_data,\l\ + * thread = 1,\l\ + * input = in[0],\l\ + * output = out[0],\l\ + * num_pixels = 20\l\ + * )\l"] + * run_1_2 [label = "run(\l\ + * user_data,\l\ + * thread = 1,\l\ + * input = in[3],\l\ + * output = out[3],\l\ + * num_pixels = 20\l\ + * )\l"] + * } + * subgraph cluster_1 { + * color = lightgrey + * label = "thread 2" + * labeljust = "l" + * run_2_1 [label = "run(\l\ + * user_data,\l\ + * thread = 2,\l\ + * input = in[1],\l\ + * output = out[1],\l\ + * num_pixels = 20\l\ + * )\l"] + * run_2_2 [label = "run(\l\ + * user_data,\l\ + * thread = 2,\l\ + * input = in[4],\l\ + * output = out[4],\l\ + * num_pixels = 13\l\ + * )\l"] + * } + * subgraph cluster_3 { + * color = lightgrey + * label = "thread 3" + * labeljust = "c" + * run_3_1 [label = "run(\l\ + * user_data,\l\ + * thread = 3,\l\ + * input = in[2],\l\ + * output = out[2],\l\ + * num_pixels = 20\l\ + * )\l"] + * } + * init -> {run_1_1; run_2_1; run_3_1; rank = same} + * run_1_1 -> run_1_2 + * run_2_1 -> run_2_2 + * {run_1_2; run_2_2, run_3_1} -> "destroy(user_data)" + * } + * @enddot + */ +typedef struct { + /** CMS-specific data that will be passed to @ref init. */ + void* init_data; + /** Prepares a colorspace transform as described in the documentation of @ref + * jpegxl_cms_init_func. */ + jpegxl_cms_init_func init; + /** Returns a buffer that can be used as input to @c run. */ + jpegxl_cms_get_buffer_func get_src_buf; + /** Returns a buffer that can be used as output from @c run. */ + jpegxl_cms_get_buffer_func get_dst_buf; + /** Executes the transform on a batch of pixels, per @ref jpegxl_cms_run_func. + */ + jpegxl_cms_run_func run; + /** Cleans up the transform. */ + jpegxl_cms_destroy_func destroy; +} JxlCmsInterface; + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* JXL_CMS_INTERFACE_H_ */ + +/** @} */ diff --git a/jpeg-xl/include/jxl/codestream_header.h b/jpeg-xl/include/jxl/codestream_header.h index 5bc9919..cf807bc 100644 --- a/jpeg-xl/include/jxl/codestream_header.h +++ b/jpeg-xl/include/jxl/codestream_header.h @@ -18,7 +18,6 @@ #include #include -#include "jxl/color_encoding.h" #include "jxl/types.h" #if defined(__cplusplus) || defined(c_plusplus) @@ -134,6 +133,8 @@ typedef struct { * representable value. The image does not necessarily contain a pixel * actually this bright. An encoder is allowed to set 255 for SDR images * without computing a histogram. + * Leaving this set to its default of 0 lets libjxl choose a sensible default + * value based on the color encoding. */ float intensity_target; @@ -164,10 +165,14 @@ typedef struct { * (linear if outputting to floating point, nonlinear with standard sRGB * transfer function if outputting to unsigned integers) but will not convert * it to to the original color profile. The decoder also does not convert to - * the target display color profile, but instead will always indicate which - * color profile the returned pixel data is encoded in when using @see - * JXL_COLOR_PROFILE_TARGET_DATA so that a CMS can be used to convert the - * data. + * the target display color profile. To convert the pixel data produced by + * the decoder to the original color profile, one of the JxlDecoderGetColor* + * functions needs to be called with @ref JXL_COLOR_PROFILE_TARGET_DATA to get + * the color profile of the decoder output, and then an external CMS can be + * used for conversion. + * Note that for lossy compression, this should be set to false for most use + * cases, and if needed, the image should be converted to the original color + * profile after decoding, as described above. */ JXL_BOOL uses_original_profile; @@ -232,10 +237,26 @@ typedef struct { */ JxlAnimationHeader animation; + /** Intrinsic width of the image. + * The intrinsic size can be different from the actual size in pixels + * (as given by xsize and ysize) and it denotes the recommended dimensions + * for displaying the image, i.e. applications are advised to resample the + * decoded image to the intrinsic dimensions. + */ + uint32_t intrinsic_xsize; + + /** Intrinsic heigth of the image. + * The intrinsic size can be different from the actual size in pixels + * (as given by xsize and ysize) and it denotes the recommended dimensions + * for displaying the image, i.e. applications are advised to resample the + * decoded image to the intrinsic dimensions. + */ + uint32_t intrinsic_ysize; + /** Padding for forwards-compatibility, in case more fields are exposed * in a future version of the library. */ - uint8_t padding[108]; + uint8_t padding[100]; } JxlBasicInfo; /** Information for a single extra channel. @@ -289,7 +310,81 @@ typedef struct { uint64_t extensions; } JxlHeaderExtensions; -/** The header of one displayed frame. */ +/** Frame blend modes. + * When decoding, if coalescing is enabled (default), this can be ignored. + */ +typedef enum { + JXL_BLEND_REPLACE = 0, + JXL_BLEND_ADD = 1, + JXL_BLEND_BLEND = 2, + JXL_BLEND_MULADD = 3, + JXL_BLEND_MUL = 4, +} JxlBlendMode; + +/** The information about blending the color channels or a single extra channel. + * When decoding, if coalescing is enabled (default), this can be ignored and + * the blend mode is considered to be JXL_BLEND_REPLACE. + * When encoding, these settings apply to the pixel data given to the encoder. + */ +typedef struct { + /** Blend mode. + */ + JxlBlendMode blendmode; + /** Reference frame ID to use as the 'bottom' layer (0-3). + */ + uint32_t source; + /** Which extra channel to use as the 'alpha' channel for blend modes + * JXL_BLEND_BLEND and JXL_BLEND_MULADD. + */ + uint32_t alpha; + /** Clamp values to [0,1] for the purpose of blending. + */ + JXL_BOOL clamp; +} JxlBlendInfo; + +/** The information about layers. + * When decoding, if coalescing is enabled (default), this can be ignored. + * When encoding, these settings apply to the pixel data given to the encoder, + * the encoder could choose an internal representation that differs. + */ +typedef struct { + /** Whether cropping is applied for this frame. When decoding, if false, + * crop_x0 and crop_y0 are set to zero, and xsize and ysize to the main + * image dimensions. When encoding and this is false, those fields are + * ignored. When decoding, if coalescing is enabled (default), this is always + * false, regardless of the internal encoding in the JPEG XL codestream. + */ + JXL_BOOL have_crop; + + /** Horizontal offset of the frame (can be negative). + */ + int32_t crop_x0; + + /** Vertical offset of the frame (can be negative). + */ + int32_t crop_y0; + + /** Width of the frame (number of columns). + */ + uint32_t xsize; + + /** Height of the frame (number of rows). + */ + uint32_t ysize; + + /** The blending info for the color channels. Blending info for extra channels + * has to be retrieved separately using JxlDecoderGetExtraChannelBlendInfo. + */ + JxlBlendInfo blend_info; + + /** After blending, save the frame as reference frame with this ID (0-3). + * Special case: if the frame duration is nonzero, ID 0 means "will not be + * referenced in the future". This value is not used for the last frame. + */ + uint32_t save_as_reference; +} JxlLayerInfo; + +/** The header of one displayed frame or non-coalesced layer. */ typedef struct { /** How long to wait after rendering in ticks. The duration in seconds of a * tick is given by tps_numerator and tps_denominator in JxlAnimationHeader. @@ -307,13 +402,22 @@ typedef struct { uint32_t timecode; /** Length of the frame name in bytes, or 0 if no name. - * Excludes null termination character. + * Excludes null termination character. This value is set by the decoder. + * For the encoder, this value is ignored and @ref JxlEncoderSetFrameName is + * used instead to set the name and the length. */ uint32_t name_length; - /** Indicates this is the last animation frame. + /** Indicates this is the last animation frame. This value is set by the + * decoder to indicate no further frames follow. For the encoder, it is not + * required to set this value and it is ignored, @ref JxlEncoderCloseFrames is + * used to indicate the last frame to the encoder instead. */ JXL_BOOL is_last; + + /** Information about the layer in case of no coalescing. + */ + JxlLayerInfo layer_info; } JxlFrameHeader; #if defined(__cplusplus) || defined(c_plusplus) diff --git a/jpeg-xl/include/jxl/color_encoding.h b/jpeg-xl/include/jxl/color_encoding.h index 2815c53..b16f6a0 100644 --- a/jpeg-xl/include/jxl/color_encoding.h +++ b/jpeg-xl/include/jxl/color_encoding.h @@ -16,8 +16,6 @@ #include -#include "jxl/types.h" - #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif @@ -90,7 +88,7 @@ typedef enum { JXL_TRANSFER_FUNCTION_LINEAR = 8, /** As specified in IEC 61966-2-1 sRGB */ JXL_TRANSFER_FUNCTION_SRGB = 13, - /** As specified in SMPTE ST 428-1 */ + /** As specified in SMPTE ST 2084 */ JXL_TRANSFER_FUNCTION_PQ = 16, /** As specified in SMPTE ST 428-1 */ JXL_TRANSFER_FUNCTION_DCI = 17, diff --git a/jpeg-xl/include/jxl/decode.h b/jpeg-xl/include/jxl/decode.h index 675c522..7c0e555 100644 --- a/jpeg-xl/include/jxl/decode.h +++ b/jpeg-xl/include/jxl/decode.h @@ -16,12 +16,14 @@ #include #include +#include "jxl/cms_interface.h" #include "jxl/codestream_header.h" #include "jxl/color_encoding.h" #include "jxl/jxl_export.h" #include "jxl/memory_manager.h" #include "jxl/parallel_runner.h" #include "jxl/types.h" +#include "jxl/version.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { @@ -36,14 +38,14 @@ extern "C" { */ JXL_EXPORT uint32_t JxlDecoderVersion(void); -/** The result of JxlSignatureCheck. +/** The result of @ref JxlSignatureCheck. */ typedef enum { /** Not enough bytes were passed to determine if a valid signature was found. */ JXL_SIG_NOT_ENOUGH_BYTES = 0, - /** No valid JPEGXL header was found. */ + /** No valid JPEG XL header was found. */ JXL_SIG_INVALID = 1, /** A valid JPEG XL codestream signature was found, that is a JPEG XL image @@ -65,61 +67,72 @@ typedef enum { * @p size doesn't need to be a full image, only the beginning of the file. * * @return a flag indicating if a JPEG XL signature was found and what type. - * - JXL_SIG_NOT_ENOUGH_BYTES not enough bytes were passed to determine - * if a valid signature is there. - * - JXL_SIG_INVALID: no valid signature found for JPEG XL decoding. - * - JXL_SIG_CODESTREAM a valid JPEG XL codestream signature was found. - * - JXL_SIG_CONTAINER a valid JPEG XL container signature was found. + * - @ref JXL_SIG_NOT_ENOUGH_BYTES if not enough bytes were passed to + * determine if a valid signature is there. + * - @ref JXL_SIG_INVALID if no valid signature found for JPEG XL decoding. + * - @ref JXL_SIG_CODESTREAM if a valid JPEG XL codestream signature was + * found. + * - @ref JXL_SIG_CONTAINER if a valid JPEG XL container signature was found. */ JXL_EXPORT JxlSignature JxlSignatureCheck(const uint8_t* buf, size_t len); /** - * Opaque structure that holds the JPEGXL decoder. + * Opaque structure that holds the JPEG XL decoder. * - * Allocated and initialized with JxlDecoderCreate(). - * Cleaned up and deallocated with JxlDecoderDestroy(). + * Allocated and initialized with @ref JxlDecoderCreate(). + * Cleaned up and deallocated with @ref JxlDecoderDestroy(). */ typedef struct JxlDecoderStruct JxlDecoder; /** - * Creates an instance of JxlDecoder and initializes it. + * Creates an instance of @ref JxlDecoder and initializes it. * * @p memory_manager will be used for all the library dynamic allocations made * from this instance. The parameter may be NULL, in which case the default - * allocator will be used. See jpegxl/memory_manager.h for details. + * allocator will be used. See jxl/memory_manager.h for details. * * @param memory_manager custom allocator function. It may be NULL. The memory * manager will be copied internally. * @return @c NULL if the instance can not be allocated or initialized - * @return pointer to initialized JxlDecoder otherwise + * @return pointer to initialized @ref JxlDecoder otherwise */ JXL_EXPORT JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager); /** - * Re-initializes a JxlDecoder instance, so it can be re-used for decoding + * Re-initializes a @ref JxlDecoder instance, so it can be re-used for decoding * another image. All state and settings are reset as if the object was - * newly created with JxlDecoderCreate, but the memory manager is kept. + * newly created with @ref JxlDecoderCreate, but the memory manager is kept. * * @param dec instance to be re-initialized. */ JXL_EXPORT void JxlDecoderReset(JxlDecoder* dec); /** - * Deinitializes and frees JxlDecoder instance. + * Deinitializes and frees @ref JxlDecoder instance. * * @param dec instance to be cleaned up and deallocated. */ JXL_EXPORT void JxlDecoderDestroy(JxlDecoder* dec); /** - * Return value for JxlDecoderProcessInput. - * The values above 0x40 are optional informal events that can be subscribed to, - * they are never returned if they have not been registered with - * JxlDecoderSubscribeEvents. + * Return value for @ref JxlDecoderProcessInput. + * The values from @ref JXL_DEC_BASIC_INFO onwards are optional informative + * events that can be subscribed to, they are never returned if they + * have not been registered with @ref JxlDecoderSubscribeEvents. */ typedef enum { /** Function call finished successfully, or decoding is finished and there is * nothing more to be done. + * + * Note that @ref JxlDecoderProcessInput will return JXL_DEC_SUCCESS if all + * events that were registered with @ref JxlDecoderSubscribeEvents were + * processed, even before the end of the JPEG XL codestream. + * + * In this case, the return value @ref JxlDecoderReleaseInput will be the same + * as it was at the last signaled event. E.g. if JXL_DEC_FULL_IMAGE was + * subscribed to, then all bytes from the end of the JPEG XL codestream + * (including possible boxes needed for jpeg reconstruction) will be returned + * as unprocessed. */ JXL_DEC_SUCCESS = 0, @@ -128,199 +141,270 @@ typedef enum { */ JXL_DEC_ERROR = 1, - /** The decoder needs more input bytes to continue. Before the next - * JxlDecoderProcessInput call, more input data must be set, by calling - * JxlDecoderReleaseInput (if input was set previously) and then calling - * JxlDecoderSetInput. JxlDecoderReleaseInput returns how many bytes are - * not yet processed, before a next call to JxlDecoderProcessInput all - * unprocessed bytes must be provided again (the address need not match, but - * the contents must), and more bytes must be concatenated after the + /** The decoder needs more input bytes to continue. Before the next @ref + * JxlDecoderProcessInput call, more input data must be set, by calling @ref + * JxlDecoderReleaseInput (if input was set previously) and then calling @ref + * JxlDecoderSetInput. @ref JxlDecoderReleaseInput returns how many bytes + * are not yet processed, before a next call to @ref JxlDecoderProcessInput + * all unprocessed bytes must be provided again (the address need not match, + * but the contents must), and more bytes must be concatenated after the * unprocessed bytes. + * In most cases, @ref JxlDecoderReleaseInput will return no unprocessed bytes + * at this event, the only exceptions are if the previously set input ended + * within (a) the raw codestream signature, (b) the signature box, (c) a box + * header, or (d) the first 4 bytes of a brob, ftyp, or jxlp box. In any of + * these cases the number of unprocessed bytes is less than 20. */ JXL_DEC_NEED_MORE_INPUT = 2, /** The decoder is able to decode a preview image and requests setting a - * preview output buffer using JxlDecoderSetPreviewOutBuffer. This occurs if - * JXL_DEC_PREVIEW_IMAGE is requested and it is possible to decode a preview - * image from the codestream and the preview out buffer was not yet set. There - * is maximum one preview image in a codestream. + * preview output buffer using @ref JxlDecoderSetPreviewOutBuffer. This occurs + * if @ref JXL_DEC_PREVIEW_IMAGE is requested and it is possible to decode a + * preview image from the codestream and the preview out buffer was not yet + * set. There is maximum one preview image in a codestream. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the frame header (including ToC) of the preview frame as + * unprocessed. */ JXL_DEC_NEED_PREVIEW_OUT_BUFFER = 3, - /** The decoder is able to decode a DC image and requests setting a DC output - * buffer using JxlDecoderSetDCOutBuffer. This occurs if JXL_DEC_DC_IMAGE is - * requested and it is possible to decode a DC image from the codestream and - * the DC out buffer was not yet set. This event re-occurs for new frames - * if there are multiple animation frames. - * DEPRECATED: the DC feature in this form will be removed. You can use - * JxlDecoderFlushImage for progressive rendering. - */ - JXL_DEC_NEED_DC_OUT_BUFFER = 4, - /** The decoder requests an output buffer to store the full resolution image, - * which can be set with JxlDecoderSetImageOutBuffer or with - * JxlDecoderSetImageOutCallback. This event re-occurs for new frames if there - * are multiple animation frames and requires setting an output again. + * which can be set with @ref JxlDecoderSetImageOutBuffer or with @ref + * JxlDecoderSetImageOutCallback. This event re-occurs for new frames if + * there are multiple animation frames and requires setting an output again. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the frame header (including ToC) as unprocessed. */ JXL_DEC_NEED_IMAGE_OUT_BUFFER = 5, /** The JPEG reconstruction buffer is too small for reconstructed JPEG - * codestream to fit. JxlDecoderSetJPEGBuffer must be called again to make - * room for remaining bytes. This event may occur multiple times after - * JXL_DEC_JPEG_RECONSTRUCTION. + * codestream to fit. @ref JxlDecoderSetJPEGBuffer must be called again to + * make room for remaining bytes. This event may occur multiple times + * after @ref JXL_DEC_JPEG_RECONSTRUCTION. */ JXL_DEC_JPEG_NEED_MORE_OUTPUT = 6, - /** The box contents output buffer is too small. JxlDecoderSetBoxBuffer must - * be called again to make room for remaining bytes. This event may occur - * multiple times after JXL_DEC_BOX. + /** The box contents output buffer is too small. @ref JxlDecoderSetBoxBuffer + * must be called again to make room for remaining bytes. This event may occur + * multiple times after @ref JXL_DEC_BOX. */ JXL_DEC_BOX_NEED_MORE_OUTPUT = 7, - /** Informative event by JxlDecoderProcessInput: basic information such as - * image dimensions and extra channels. This event occurs max once per image. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": Basic information such as image dimensions and + * extra channels. This event occurs max once per image. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the basic info as unprocessed (including the last byte of basic info + * if it did not end on a byte boundary). */ JXL_DEC_BASIC_INFO = 0x40, - /** Informative event by JxlDecoderProcessInput: user extensions of the - * codestream header. This event occurs max once per image and always later - * than JXL_DEC_BASIC_INFO and earlier than any pixel data. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": User extensions of the codestream header. This + * event occurs max once per image and always later than @ref + * JXL_DEC_BASIC_INFO and earlier than any pixel data. + * + * @deprecated The decoder no longer returns this, the header extensions, + * if any, are available at the JXL_DEC_BASIC_INFO event. */ JXL_DEC_EXTENSIONS = 0x80, - /** Informative event by JxlDecoderProcessInput: color encoding or ICC - * profile from the codestream header. This event occurs max once per image - * and always later than JXL_DEC_BASIC_INFO and earlier than any pixel - * data. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": Color encoding or ICC profile from the + * codestream header. This event occurs max once per image and always later + * than @ref JXL_DEC_BASIC_INFO and earlier than any pixel data. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the image header (which is the start of the first frame) as + * unprocessed. */ JXL_DEC_COLOR_ENCODING = 0x100, - /** Informative event by JxlDecoderProcessInput: Preview image, a small - * frame, decoded. This event can only happen if the image has a preview - * frame encoded. This event occurs max once for the codestream and always - * later than JXL_DEC_COLOR_ENCODING and before JXL_DEC_FRAME. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": Preview image, a small frame, decoded. This + * event can only happen if the image has a preview frame encoded. This event + * occurs max once for the codestream and always later than @ref + * JXL_DEC_COLOR_ENCODING and before @ref JXL_DEC_FRAME. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the preview frame as unprocessed. */ JXL_DEC_PREVIEW_IMAGE = 0x200, - /** Informative event by JxlDecoderProcessInput: Beginning of a frame. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": Beginning of a frame. @ref * JxlDecoderGetFrameHeader can be used at this point. A note on frames: * a JPEG XL image can have internal frames that are not intended to be * displayed (e.g. used for compositing a final frame), but this only returns - * displayed frames. A displayed frame either has an animation duration or is - * the only or last frame in the image. This event occurs max once per - * displayed frame, always later than JXL_DEC_COLOR_ENCODING, and always - * earlier than any pixel data. While JPEG XL supports encoding a single frame - * as the composition of multiple internal sub-frames also called frames, this - * event is not indicated for the internal frames. + * displayed frames, unless @ref JxlDecoderSetCoalescing was set to JXL_FALSE: + * in that case, the individual layers are returned, without blending. Note + * that even when coalescing is disabled, only frames of type kRegularFrame + * are returned; frames of type kReferenceOnly and kLfFrame are always for + * internal purposes only and cannot be accessed. A displayed frame either has + * an animation duration or is the only or last frame in the image. This event + * occurs max once per displayed frame, always later than @ref + * JXL_DEC_COLOR_ENCODING, and always earlier than any pixel data. While + * JPEG XL supports encoding a single frame as the composition of multiple + * internal sub-frames also called frames, this event is not indicated for the + * internal frames. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the frame header (including ToC) as unprocessed. */ JXL_DEC_FRAME = 0x400, - /** Informative event by JxlDecoderProcessInput: DC image, 8x8 sub-sampled - * frame, decoded. It is not guaranteed that the decoder will always return DC - * separately, but when it does it will do so before outputting the full - * frame. JxlDecoderSetDCOutBuffer must be used after getting the basic - * image information to be able to get the DC pixels, if not this return - * status only indicates we're past this point in the codestream. This event - * occurs max once per frame and always later than JXL_DEC_FRAME_HEADER - * and other header events and earlier than full resolution pixel data. - * DEPRECATED: the DC feature in this form will be removed. You can use - * JxlDecoderFlushImage for progressive rendering. - */ - JXL_DEC_DC_IMAGE = 0x800, - - /** Informative event by JxlDecoderProcessInput: full frame decoded. - * JxlDecoderSetImageOutBuffer must be used after getting the basic image - * information to be able to get the image pixels, if not this return status - * only indicates we're past this point in the codestream. This event occurs - * max once per frame and always later than JXL_DEC_DC_IMAGE. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": full frame (or layer, in case coalescing is + * disabled) is decoded. @ref JxlDecoderSetImageOutBuffer must be used after + * getting the basic image information to be able to get the image pixels, if + * not this return status only indicates we're past this point in the + * codestream. This event occurs max once per frame. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the frame (or if @ref JXL_DEC_JPEG_RECONSTRUCTION is subscribed to, + * from the end of the last box that is needed for jpeg reconstruction) as + * unprocessed. */ JXL_DEC_FULL_IMAGE = 0x1000, - /** Informative event by JxlDecoderProcessInput: JPEG reconstruction data - * decoded. JxlDecoderSetJPEGBuffer may be used to set a JPEG - * reconstruction buffer after getting the JPEG reconstruction data. If a JPEG - * reconstruction buffer is set a byte stream identical to the JPEG codestream - * used to encode the image will be written to the JPEG reconstruction buffer - * instead of pixels to the image out buffer. This event occurs max once per - * image and always before JXL_DEC_FULL_IMAGE. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": JPEG reconstruction data decoded. @ref + * JxlDecoderSetJPEGBuffer may be used to set a JPEG reconstruction buffer + * after getting the JPEG reconstruction data. If a JPEG reconstruction buffer + * is set a byte stream identical to the JPEG codestream used to encode the + * image will be written to the JPEG reconstruction buffer instead of pixels + * to the image out buffer. This event occurs max once per image and always + * before @ref JXL_DEC_FULL_IMAGE. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the 'jbrd' box as unprocessed. */ JXL_DEC_JPEG_RECONSTRUCTION = 0x2000, - /** Informative event by JxlDecoderProcessInput: The header of a box of the - * container format (BMFF) is decoded. The following API functions related to - * boxes can be used after this event: - * @see JxlDecoderSetBoxBuffer and JxlDecoderReleaseBoxBuffer: set and release - * a buffer to get the box data. - * @see JxlDecoderGetBoxType get the 4-character box typename. - * @see JxlDecoderGetBoxSizeRaw get the size of the box as it appears in the - * container file, not decompressed. - * @see JxlDecoderSetDecompressBoxes to configure whether to get the box - * data decompressed, or possibly compressed. + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": The header of a box of the container format + * (BMFF) is decoded. The following API functions related to boxes can be used + * after this event: + * - @ref JxlDecoderSetBoxBuffer and @ref JxlDecoderReleaseBoxBuffer + * "JxlDecoderReleaseBoxBuffer": set and release a buffer to get the box + * data. + * - @ref JxlDecoderGetBoxType get the 4-character box typename. + * - @ref JxlDecoderGetBoxSizeRaw get the size of the box as it appears in + * the container file, not decompressed. + * - @ref JxlDecoderSetDecompressBoxes to configure whether to get the box + * data decompressed, or possibly compressed. * - * Boxes can be compressed. This is so when their box type is "brob". In that - * case, they have an underlying decompressed box type and decompressed data. - * Use JxlDecoderSetDecompressBoxes to configure which data to get, - * decompressing them requires Brotli. JxlDecoderGetBoxType has a flag to - * get the compressed box type, which can be "brob", or the decompressed box - * type. If a box is not compressed (its compressed type is not "brob"), then - * you get the same decompressed box type and data no matter what setting is - * configured. + * Boxes can be compressed. This is so when their box type is + * "brob". In that case, they have an underlying decompressed box + * type and decompressed data. @ref JxlDecoderSetDecompressBoxes allows + * configuring which data to get. Decompressing requires + * Brotli. @ref JxlDecoderGetBoxType has a flag to get the compressed box + * type, which can be "brob", or the decompressed box type. If a box + * is not compressed (its compressed type is not "brob"), then + * the output decompressed box type and data is independent of what + * setting is configured. * - * The buffer set with JxlDecoderSetBoxBuffer must be set again for each next - * box that you want to get, or can be left unset to skip outputting this box. - * The output buffer contains the full box data when the next JXL_DEC_BOX - * event or JXL_DEC_SUCCESS occurs. JXL_DEC_BOX occurs for all boxes, - * including non-metadata boxes such as the signature box or codestream boxes. - * To check whether the box is a metadata type for respectively EXIF, XMP or - * JUMBF, use JxlDecoderGetBoxType and check for types "Exif", "xml " and - * "jumb" respectively. + * The buffer set with @ref JxlDecoderSetBoxBuffer must be set again for each + * next box to be obtained, or can be left unset to skip outputting this box. + * The output buffer contains the full box data when the next @ref JXL_DEC_BOX + * event or @ref JXL_DEC_SUCCESS occurs. @ref JXL_DEC_BOX occurs for all + * boxes, including non-metadata boxes such as the signature box or codestream + * boxes. To check whether the box is a metadata type for respectively EXIF, + * XMP or JUMBF, use @ref JxlDecoderGetBoxType and check for types "Exif", + * "xml " and "jumb" respectively. + * + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * start of the box header as unprocessed. */ JXL_DEC_BOX = 0x4000, + + /** Informative event by @ref JxlDecoderProcessInput + * "JxlDecoderProcessInput": a progressive step in decoding the frame is + * reached. When calling @ref JxlDecoderFlushImage at this point, the flushed + * image will correspond exactly to this point in decoding, and not yet + * contain partial results (such as partially more fine detail) of a next + * step. By default, this event will trigger maximum once per frame, when a + * 8x8th resolution (DC) image is ready (the image data is still returned at + * full resolution, giving upscaled DC). Use @ref + * JxlDecoderSetProgressiveDetail to configure more fine-grainedness. The + * event is not guaranteed to trigger, not all images have progressive steps + * or DC encoded. + * In this case, @ref JxlDecoderReleaseInput will return all bytes from the + * end of the section that was needed to produce this progressive event as + * unprocessed. + */ + JXL_DEC_FRAME_PROGRESSION = 0x8000, } JxlDecoderStatus; /** Rewinds decoder to the beginning. The same input must be given again from * the beginning of the file and the decoder will emit events from the beginning - * again. When rewinding (as opposed to JxlDecoderReset), the decoder can keep - * state about the image, which it can use to skip to a requested frame more - * efficiently with JxlDecoderSkipFrames. Settings such as parallel runner or - * subscribed events are kept. After rewind, JxlDecoderSubscribeEvents can be - * used again, and it is feasible to leave out events that were already handled - * before, such as JXL_DEC_BASIC_INFO and JXL_DEC_COLOR_ENCODING, since they - * will provide the same information as before. + * again. When rewinding (as opposed to @ref JxlDecoderReset), the decoder can + * keep state about the image, which it can use to skip to a requested frame + * more efficiently with @ref JxlDecoderSkipFrames. Settings such as parallel + * runner or subscribed events are kept. After rewind, @ref + * JxlDecoderSubscribeEvents can be used again, and it is feasible to leave out + * events that were already handled before, such as @ref JXL_DEC_BASIC_INFO + * and @ref JXL_DEC_COLOR_ENCODING, since they will provide the same information + * as before. + * The difference to @ref JxlDecoderReset is that some state is kept, namely + * settings set by a call to + * - @ref JxlDecoderSetCoalescing, + * - @ref JxlDecoderSetDesiredIntensityTarget, + * - @ref JxlDecoderSetDecompressBoxes, + * - @ref JxlDecoderSetKeepOrientation, + * - @ref JxlDecoderSetUnpremultiplyAlpha, + * - @ref JxlDecoderSetParallelRunner, + * - @ref JxlDecoderSetRenderSpotcolors, and + * - @ref JxlDecoderSubscribeEvents. + * * @param dec decoder object */ JXL_EXPORT void JxlDecoderRewind(JxlDecoder* dec); /** Makes the decoder skip the next `amount` frames. It still needs to process * the input, but will not output the frame events. It can be more efficient - * when skipping frames, and even more so when using this after + * when skipping frames, and even more so when using this after @ref * JxlDecoderRewind. If the decoder is already processing a frame (could - * have emitted JXL_DEC_FRAME but not yet JXL_DEC_FULL_IMAGE), it starts - * skipping from the next frame. If the amount is larger than the amount of - * frames remaining in the image, all remaining frames are skipped. Calling this - * function multiple times adds the amount to skip to the already existing + * have emitted @ref JXL_DEC_FRAME but not yet @ref JXL_DEC_FULL_IMAGE), it + * starts skipping from the next frame. If the amount is larger than the amount + * of frames remaining in the image, all remaining frames are skipped. Calling + * this function multiple times adds the amount to skip to the already existing * amount. - * A frame here is defined as a frame that without skipping emits events such as - * JXL_DEC_FRAME and JXL_FULL_IMAGE, frames that are internal to the file format - * but are not rendered as part of an animation, or are not the final still - * frame of a still image, are not counted. + * + * A frame here is defined as a frame that without skipping emits events such + * as @ref JXL_DEC_FRAME and @ref JXL_DEC_FULL_IMAGE, frames that are internal + * to the file format but are not rendered as part of an animation, or are not + * the final still frame of a still image, are not counted. + * * @param dec decoder object * @param amount the amount of frames to skip */ JXL_EXPORT void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount); +/** + * Skips processing the current frame. Can be called after frame processing + * already started, signaled by a @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event, + * but before the corrsponding @ref JXL_DEC_FULL_IMAGE event. The next signaled + * event will be another @ref JXL_DEC_FRAME, or @ref JXL_DEC_SUCCESS if there + * are no more frames. If pixel data is required from the already processed part + * of the frame, @ref JxlDecoderFlushImage must be called before this. + * + * @param dec decoder object + * @return @ref JXL_DEC_SUCCESS if there is a frame to skip, and @ref + * JXL_DEC_ERROR if the function was not called during frame processing. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderSkipCurrentFrame(JxlDecoder* dec); + /** * Get the default pixel format for this decoder. * * Requires that the decoder can produce JxlBasicInfo. * - * @param dec JxlDecoder to query when creating the recommended pixel format. + * @param dec @ref JxlDecoder to query when creating the recommended pixel + * format. * @param format JxlPixelFormat to populate with the recommended settings for - * the data loaded into this decoder. - * @return JXL_DEC_SUCCESS if no error, JXL_DEC_NEED_MORE_INPUT if the - * basic info isn't yet available, and JXL_DEC_ERROR otherwise. + * the data loaded into this decoder. + * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_NEED_MORE_INPUT if the + * basic info isn't yet available, and @ref JXL_DEC_ERROR otherwise. + * + * DEPRECATED: this function will be removed in the future. */ -JXL_EXPORT JxlDecoderStatus +JXL_DEPRECATED JXL_EXPORT JxlDecoderStatus JxlDecoderDefaultPixelFormat(const JxlDecoder* dec, JxlPixelFormat* format); /** @@ -329,11 +413,11 @@ JxlDecoderDefaultPixelFormat(const JxlDecoder* dec, JxlPixelFormat* format); * * @param dec decoder object * @param parallel_runner function pointer to runner for multithreading. It may - * be NULL to use the default, single-threaded, runner. A multithreaded - * runner should be set to reach fast performance. + * be NULL to use the default, single-threaded, runner. A multithreaded + * runner should be set to reach fast performance. * @param parallel_runner_opaque opaque pointer for parallel_runner. - * @return JXL_DEC_SUCCESS if the runner was set, JXL_DEC_ERROR - * otherwise (the previous runner remains set). + * @return @ref JXL_DEC_SUCCESS if the runner was set, @ref JXL_DEC_ERROR + * otherwise (the previous runner remains set). */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner, @@ -341,14 +425,14 @@ JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner, /** * Returns a hint indicating how many more bytes the decoder is expected to - * need to make JxlDecoderGetBasicInfo available after the next + * need to make @ref JxlDecoderGetBasicInfo available after the next @ref * JxlDecoderProcessInput call. This is a suggested large enough value for - * the amount of bytes to provide in the next JxlDecoderSetInput call, but it is - * not guaranteed to be an upper bound nor a lower bound. This number does not - * include bytes that have already been released from the input. - * Can be used before the first JxlDecoderProcessInput call, and is correct - * the first time in most cases. If not, JxlDecoderSizeHintBasicInfo can be - * called again to get an updated hint. + * the amount of bytes to provide in the next @ref JxlDecoderSetInput call, but + * it is not guaranteed to be an upper bound nor a lower bound. This number does + * not include bytes that have already been released from the input. Can be used + * before the first @ref JxlDecoderProcessInput call, and is correct the first + * time in most cases. If not, @ref JxlDecoderSizeHintBasicInfo can be called + * again to get an updated hint. * * @param dec decoder object * @return the size hint in bytes if the basic info is not yet fully decoded. @@ -356,162 +440,199 @@ JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner, */ JXL_EXPORT size_t JxlDecoderSizeHintBasicInfo(const JxlDecoder* dec); -/** Select for which informative events (JXL_DEC_BASIC_INFO, etc...) the +/** Select for which informative events, i.e. @ref JXL_DEC_BASIC_INFO, etc., the * decoder should return with a status. It is not required to subscribe to any * events, data can still be requested from the decoder as soon as it available. * By default, the decoder is subscribed to no events (events_wanted == 0), and * the decoder will then only return when it cannot continue because it needs * more input data or more output buffer. This function may only be be called - * before using JxlDecoderProcessInput + * before using @ref JxlDecoderProcessInput. * * @param dec decoder object * @param events_wanted bitfield of desired events. - * @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise. + * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec, int events_wanted); -/** Enables or disables preserving of original orientation. Some images are - * encoded with an orientation tag indicating the image is rotated and/or - * mirrored (here called the original orientation). +/** Enables or disables preserving of as-in-bitstream pixeldata + * orientation. Some images are encoded with an Orientation tag + * indicating that the decoder must perform a rotation and/or + * mirroring to the encoded image data. + * + * - If skip_reorientation is JXL_FALSE (the default): the decoder + * will apply the transformation from the orientation setting, hence + * rendering the image according to its specified intent. When + * producing a JxlBasicInfo, the decoder will always set the + * orientation field to JXL_ORIENT_IDENTITY (matching the returned + * pixel data) and also align xsize and ysize so that they correspond + * to the width and the height of the returned pixel data. + * - If skip_reorientation is JXL_TRUE: the decoder will skip + * applying the transformation from the orientation setting, returning + * the image in the as-in-bitstream pixeldata orientation. + * This may be faster to decode since the decoder doesn't have to apply the + * transformation, but can cause wrong display of the image if the + * orientation tag is not correctly taken into account by the user. + * + * By default, this option is disabled, and the returned pixel data is + * re-oriented according to the image's Orientation setting. + * + * This function must be called at the beginning, before decoding is performed. * - * *) If keep_orientation is JXL_FALSE (the default): the decoder will perform - * work to undo the transformation. This ensures the decoded pixels will not - * be rotated or mirrored. The decoder will always set the orientation field - * of the JxlBasicInfo to JXL_ORIENT_IDENTITY to match the returned pixel data. - * The decoder may also swap xsize and ysize in the JxlBasicInfo compared to the - * values inside of the codestream, to correctly match the decoded pixel data, - * e.g. when a 90 degree rotation was performed. + * @see JxlBasicInfo for the orientation field, and @ref JxlOrientation for the + * possible values. * - * *) If this option is JXL_TRUE: then the image is returned as-is, which may be - * rotated or mirrored, and the user must check the orientation field in - * JxlBasicInfo after decoding to correctly interpret the decoded pixel data. - * This may be faster to decode since the decoder doesn't have to apply the - * transformation, but can cause wrong display of the image if the orientation - * tag is not correctly taken into account by the user. + * @param dec decoder object + * @param skip_reorientation JXL_TRUE to enable, JXL_FALSE to disable. + * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. + */ +JXL_EXPORT JxlDecoderStatus +JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL skip_reorientation); + +/** + * Enables or disables preserving of associated alpha channels. If + * unpremul_alpha is set to JXL_FALSE then for associated alpha channel, the + * pixel data is returned with premultiplied colors. If it is set to JXL_TRUE, + * The colors will be unpremultiplied based on the alpha channel. This function + * has no effect if the image does not have an associated alpha channel. * - * By default, this option is disabled, and the decoder automatically corrects - * the orientation. + * By default, this option is disabled, and the returned pixel data "as is". * * This function must be called at the beginning, before decoding is performed. * - * @see JxlBasicInfo for the orientation field, and @see JxlOrientation for the - * possible values. - * * @param dec decoder object - * @param keep_orientation JXL_TRUE to enable, JXL_FALSE to disable. - * @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise. + * @param unpremul_alpha JXL_TRUE to enable, JXL_FALSE to disable. + * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus -JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL keep_orientation); +JxlDecoderSetUnpremultiplyAlpha(JxlDecoder* dec, JXL_BOOL unpremul_alpha); /** Enables or disables rendering spot colors. By default, spot colors * are rendered, which is OK for viewing the decoded image. If render_spotcolors * is JXL_FALSE, then spot colors are not rendered, and have to be retrieved - * separately using JxlDecoderSetExtraChannelBuffer. This is useful for e.g. - * printing applications. + * separately using @ref JxlDecoderSetExtraChannelBuffer. This is useful for + * e.g. printing applications. * * @param dec decoder object * @param render_spotcolors JXL_TRUE to enable (default), JXL_FALSE to disable. - * @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise. + * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, JXL_BOOL render_spotcolors); +/** Enables or disables coalescing of zero-duration frames. By default, frames + * are returned with coalescing enabled, i.e. all frames have the image + * dimensions, and are blended if needed. When coalescing is disabled, frames + * can have arbitrary dimensions, a non-zero crop offset, and blending is not + * performed. For display, coalescing is recommended. For loading a multi-layer + * still image as separate layers (as opposed to the merged image), coalescing + * has to be disabled. + * + * @param dec decoder object + * @param coalescing JXL_TRUE to enable coalescing (default), JXL_FALSE to + * disable it. + * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec, + JXL_BOOL coalescing); + /** * Decodes JPEG XL file using the available bytes. Requires input has been - * set with JxlDecoderSetInput. After JxlDecoderProcessInput, input can - * optionally be released with JxlDecoderReleaseInput and then set again to - * next bytes in the stream. JxlDecoderReleaseInput returns how many bytes are - * not yet processed, before a next call to JxlDecoderProcessInput all - * unprocessed bytes must be provided again (the address need not match, but the - * contents must), and more bytes may be concatenated after the unprocessed - * bytes. + * set with @ref JxlDecoderSetInput. After @ref JxlDecoderProcessInput, input + * can optionally be released with @ref JxlDecoderReleaseInput and then set + * again to next bytes in the stream. @ref JxlDecoderReleaseInput returns how + * many bytes are not yet processed, before a next call to @ref + * JxlDecoderProcessInput all unprocessed bytes must be provided again (the + * address need not match, but the contents must), and more bytes may be + * concatenated after the unprocessed bytes. * * The returned status indicates whether the decoder needs more input bytes, or * more output buffer for a certain type of output data. No matter what the - * returned status is (other than JXL_DEC_ERROR), new information, such as - * JxlDecoderGetBasicInfo, may have become available after this call. When - * the return value is not JXL_DEC_ERROR or JXL_DEC_SUCCESS, the decoding - * requires more JxlDecoderProcessInput calls to continue. + * returned status is (other than @ref JXL_DEC_ERROR), new information, such + * as @ref JxlDecoderGetBasicInfo, may have become available after this call. + * When the return value is not @ref JXL_DEC_ERROR or @ref JXL_DEC_SUCCESS, the + * decoding requires more @ref JxlDecoderProcessInput calls to continue. * * @param dec decoder object - * @return JXL_DEC_SUCCESS when decoding finished and all events handled. If you - * still have more unprocessed input data anyway, then you can still continue - * by using JxlDecoderSetInput and calling JxlDecoderProcessInput again, similar - * to handling JXL_DEC_NEED_MORE_INPUT. JXL_DEC_SUCCESS can occur instead of - * JXL_DEC_NEED_MORE_INPUT when, for example, the input data ended right at - * the boundary of a box of the container format, all essential codestream boxes - * were already decoded, but extra metadata boxes are still present in the next - * data. JxlDecoderProcessInput cannot return success if all codestream boxes - * have not been seen yet. - * @return JXL_DEC_ERROR when decoding failed, e.g. invalid codestream. - * TODO(lode) document the input data mechanism - * @return JXL_DEC_NEED_MORE_INPUT more input data is necessary. - * @return JXL_DEC_BASIC_INFO when basic info such as image dimensions is - * available and this informative event is subscribed to. - * @return JXL_DEC_EXTENSIONS when JPEG XL codestream user extensions are - * available and this informative event is subscribed to. - * @return JXL_DEC_COLOR_ENCODING when color profile information is - * available and this informative event is subscribed to. - * @return JXL_DEC_PREVIEW_IMAGE when preview pixel information is available and - * output in the preview buffer. - * @return JXL_DEC_DC_IMAGE when DC pixel information (8x8 downscaled version - * of the image) is available and output in the DC buffer. - * @return JXL_DEC_FULL_IMAGE when all pixel information at highest detail is - * available and has been output in the pixel buffer. + * @return @ref JXL_DEC_SUCCESS when decoding finished and all events handled. + * If you still have more unprocessed input data anyway, then you can still + * continue by using @ref JxlDecoderSetInput and calling @ref + * JxlDecoderProcessInput again, similar to handling @ref + * JXL_DEC_NEED_MORE_INPUT. @ref JXL_DEC_SUCCESS can occur instead of @ref + * JXL_DEC_NEED_MORE_INPUT when, for example, the input data ended right at + * the boundary of a box of the container format, all essential codestream + * boxes were already decoded, but extra metadata boxes are still present in + * the next data. @ref JxlDecoderProcessInput cannot return success if all + * codestream boxes have not been seen yet. + * @return @ref JXL_DEC_ERROR when decoding failed, e.g. invalid codestream. + * TODO(lode): document the input data mechanism + * @return @ref JXL_DEC_NEED_MORE_INPUT when more input data is necessary. + * @return @ref JXL_DEC_BASIC_INFO when basic info such as image dimensions is + * available and this informative event is subscribed to. + * @return @ref JXL_DEC_COLOR_ENCODING when color profile information is + * available and this informative event is subscribed to. + * @return @ref JXL_DEC_PREVIEW_IMAGE when preview pixel information is + * available and output in the preview buffer. + * @return @ref JXL_DEC_FULL_IMAGE when all pixel information at highest detail + * is available and has been output in the pixel buffer. */ JXL_EXPORT JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec); /** - * Sets input data for JxlDecoderProcessInput. The data is owned by the caller - * and may be used by the decoder until JxlDecoderReleaseInput is called or - * the decoder is destroyed or reset so must be kept alive until then. - * Cannot be called if JxlDecoderSetInput was already called and - * JxlDecoderReleaseInput was not yet called, and cannot be called after + * Sets input data for @ref JxlDecoderProcessInput. The data is owned by the + * caller and may be used by the decoder until @ref JxlDecoderReleaseInput is + * called or the decoder is destroyed or reset so must be kept alive until then. + * Cannot be called if @ref JxlDecoderSetInput was already called and @ref + * JxlDecoderReleaseInput was not yet called, and cannot be called after @ref * JxlDecoderCloseInput indicating the end of input was called. + * * @param dec decoder object * @param data pointer to next bytes to read from * @param size amount of bytes available starting from data - * @return JXL_DEC_ERROR if input was already set without releasing or - * JxlDecoderCloseInput was already called, JXL_DEC_SUCCESS otherwise. + * @return @ref JXL_DEC_ERROR if input was already set without releasing or @ref + * JxlDecoderCloseInput was already called, @ref JXL_DEC_SUCCESS otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, const uint8_t* data, size_t size); /** - * Releases input which was provided with JxlDecoderSetInput. Between - * JxlDecoderProcessInput and JxlDecoderReleaseInput, the user may not alter - * the data in the buffer. Calling JxlDecoderReleaseInput is required whenever - * any input is already set and new input needs to be added with - * JxlDecoderSetInput, but is not required before JxlDecoderDestroy or - * JxlDecoderReset. Calling JxlDecoderReleaseInput when no input is set is + * Releases input which was provided with @ref JxlDecoderSetInput. Between @ref + * JxlDecoderProcessInput and @ref JxlDecoderReleaseInput, the user may not + * alter the data in the buffer. Calling @ref JxlDecoderReleaseInput is required + * whenever any input is already set and new input needs to be added with @ref + * JxlDecoderSetInput, but is not required before @ref JxlDecoderDestroy or @ref + * JxlDecoderReset. Calling @ref JxlDecoderReleaseInput when no input is set is * not an error and returns 0. + * * @param dec decoder object - * @return the amount of bytes the decoder has not yet processed that are - * still remaining in the data set by JxlDecoderSetInput, or 0 if no input is - * set or JxlDecoderReleaseInput was already called. For a next call to - * JxlDecoderProcessInput, the buffer must start with these unprocessed bytes. - * This value doesn't provide information about how many bytes the decoder - * truly processed internally or how large the original JPEG XL codestream or - * file are. + * @return The amount of bytes the decoder has not yet processed that are still + * remaining in the data set by @ref JxlDecoderSetInput, or 0 if no input is + * set or @ref JxlDecoderReleaseInput was already called. For a next call + * to @ref JxlDecoderProcessInput, the buffer must start with these + * unprocessed bytes. From this value it is possible to infer the position + * of certain JPEG XL codestream elements (e.g. end of headers, frame + * start/end). See the documentation of individual values of @ref + * JxlDecoderStatus for more information. */ JXL_EXPORT size_t JxlDecoderReleaseInput(JxlDecoder* dec); /** - * Marks the input as finished, indicates that no more JxlDecoderSetInput will - * be called. This function allows the decoder to determine correctly if it + * Marks the input as finished, indicates that no more @ref JxlDecoderSetInput + * will be called. This function allows the decoder to determine correctly if it * should return success, need more input or error in certain cases. For * backwards compatibility with a previous version of the API, using this - * function is optional when not using the JXL_DEC_BOX event (the decoder is - * able to determine the end of the image frames without marking the end), but - * using this function is required when using JXL_DEC_BOX for getting metadata - * box contents. This function does not replace JxlDecoderReleaseInput, that - * function should still be called if its return value is needed. - * JxlDecoderCloseInput should be called as soon as all known input bytes are - * set (e.g. at the beginning when not streaming but setting all input at once), - * before the final JxlDecoderProcessInput calls. + * function is optional when not using the @ref JXL_DEC_BOX event (the decoder + * is able to determine the end of the image frames without marking the end), + * but using this function is required when using @ref JXL_DEC_BOX for getting + * metadata box contents. This function does not replace @ref + * JxlDecoderReleaseInput, that function should still be called if its return + * value is needed. + * + * @ref JxlDecoderCloseInput should be called as soon as all known input bytes + * are set (e.g. at the beginning when not streaming but setting all input + * at once), before the final @ref JxlDecoderProcessInput calls. + * * @param dec decoder object */ JXL_EXPORT void JxlDecoderCloseInput(JxlDecoder* dec); @@ -522,10 +643,10 @@ JXL_EXPORT void JxlDecoderCloseInput(JxlDecoder* dec); * * @param dec decoder object * @param info struct to copy the information into, or NULL to only check - * whether the information is available through the return value. - * @return JXL_DEC_SUCCESS if the value is available, - * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case - * of other error conditions. + * whether the information is available through the return value. + * @return @ref JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR + * in case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec, JxlBasicInfo* info); @@ -537,10 +658,10 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec, * @param dec decoder object * @param index index of the extra channel to query. * @param info struct to copy the information into, or NULL to only check - * whether the information is available through the return value. - * @return JXL_DEC_SUCCESS if the value is available, - * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case - * of other error conditions. + * whether the information is available through the return value. + * @return @ref JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR + * in case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelInfo( const JxlDecoder* dec, size_t index, JxlExtraChannelInfo* info); @@ -555,9 +676,9 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelInfo( * @param index index of the extra channel to query. * @param name buffer to copy the name into * @param size size of the name buffer in bytes - * @return JXL_DEC_SUCCESS if the value is available, - * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case - * of other error conditions. + * @return @ref JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR + * in case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelName(const JxlDecoder* dec, size_t index, @@ -566,11 +687,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelName(const JxlDecoder* dec, /** Defines which color profile to get: the profile from the codestream * metadata header, which represents the color profile of the original image, - * or the color profile from the pixel data received by the decoder. Both are - * the same if the basic has uses_original_profile set. + * or the color profile from the pixel data produced by the decoder. Both are + * the same if the JxlBasicInfo has uses_original_profile set. */ typedef enum { - /** Get the color profile of the original image from the metadata.. + /** Get the color profile of the original image from the metadata. */ JXL_COLOR_PROFILE_TARGET_ORIGINAL = 0, @@ -583,174 +704,232 @@ typedef enum { * This is an alternative to an ICC Profile, which can represent a more limited * amount of color spaces, but represents them exactly through enum values. * - * It is often possible to use JxlDecoderGetColorAsICCProfile as an + * It is often possible to use @ref JxlDecoderGetColorAsICCProfile as an * alternative anyway. The following scenarios are possible: - * - The JPEG XL image has an attached ICC Profile, in that case, the encoded - * structured data is not available, this function will return an error status - * and you must use JxlDecoderGetColorAsICCProfile instead. - * - The JPEG XL image has an encoded structured color profile, and it - * represents an RGB or grayscale color space. This function will return it. - * You can still use JxlDecoderGetColorAsICCProfile as well as an - * alternative if desired, though depending on which RGB color space is - * represented, the ICC profile may be a close approximation. It is also not - * always feasible to deduce from an ICC profile which named color space it - * exactly represents, if any, as it can represent any arbitrary space. - * - The JPEG XL image has an encoded structured color profile, and it indicates - * an unknown or xyb color space. In that case, - * JxlDecoderGetColorAsICCProfile is not available. - * - * If you wish to render the image using a system that supports ICC profiles, - * use JxlDecoderGetColorAsICCProfile first. If you're looking for a specific - * color space possibly indicated in the JPEG XL image, use - * JxlDecoderGetColorAsEncodedProfile first. + * - The JPEG XL image has an attached ICC Profile, in that case, the encoded + * structured data is not available, this function will return an error + * status. @ref JxlDecoderGetColorAsICCProfile should be called instead. + * - The JPEG XL image has an encoded structured color profile, and it + * represents an RGB or grayscale color space. This function will return it. + * You can still use @ref JxlDecoderGetColorAsICCProfile as well as an + * alternative if desired, though depending on which RGB color space is + * represented, the ICC profile may be a close approximation. It is also not + * always feasible to deduce from an ICC profile which named color space it + * exactly represents, if any, as it can represent any arbitrary space. + * HDR color spaces such as those using PQ and HLG are also potentially + * problematic, in that: while ICC profiles can encode a transfer function + * that happens to approximate those of PQ and HLG (HLG for only one given + * system gamma at a time, and necessitating a 3D LUT if gamma is to be + * different from 1), they cannot (before ICCv4.4) semantically signal that + * this is the color space that they represent. Therefore, they will + * typically not actually be interpreted as representing an HDR color space. + * This is especially detrimental to PQ which will then be interpreted as if + * the maximum signal value represented SDR white instead of 10000 cd/m^2, + * meaning that the image will be displayed two orders of magnitude (5-7 EV) + * too dim. + * - The JPEG XL image has an encoded structured color profile, and it + * indicates an unknown or xyb color space. In that case, @ref + * JxlDecoderGetColorAsICCProfile is not available. + * + * When rendering an image on a system where ICC-based color management is used, + * @ref JxlDecoderGetColorAsICCProfile should generally be used first as it will + * return a ready-to-use profile (with the aforementioned caveat about HDR). + * When knowledge about the nominal color space is desired if available, @ref + * JxlDecoderGetColorAsEncodedProfile should be used first. * * @param dec decoder object - * @param format pixel format to output the data to. Only used for - * JXL_COLOR_PROFILE_TARGET_DATA, may be nullptr otherwise. + * @param unused_format deprecated, can be NULL * @param target whether to get the original color profile from the metadata * or the color profile of the decoded pixels. * @param color_encoding struct to copy the information into, or NULL to only - * check whether the information is available through the return value. - * @return JXL_DEC_SUCCESS if the data is available and returned, - * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case - * the encoded structured color profile does not exist in the codestream. + * check whether the information is available through the return value. + * @return @ref JXL_DEC_SUCCESS if the data is available and returned, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in + * case the encoded structured color profile does not exist in the + * codestream. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile( - const JxlDecoder* dec, const JxlPixelFormat* format, + const JxlDecoder* dec, const JxlPixelFormat* unused_format, JxlColorProfileTarget target, JxlColorEncoding* color_encoding); /** - * Outputs the size in bytes of the ICC profile returned by + * Outputs the size in bytes of the ICC profile returned by @ref * JxlDecoderGetColorAsICCProfile, if available, or indicates there is none * available. In most cases, the image will have an ICC profile available, but - * if it does not, JxlDecoderGetColorAsEncodedProfile must be used instead. + * if it does not, @ref JxlDecoderGetColorAsEncodedProfile must be used instead. + * * @see JxlDecoderGetColorAsEncodedProfile for more information. The ICC * profile is either the exact ICC profile attached to the codestream metadata, * or a close approximation generated from JPEG XL encoded structured data, * depending of what is encoded in the codestream. * * @param dec decoder object - * @param format pixel format to output the data to. Only used for - * JXL_COLOR_PROFILE_TARGET_DATA, may be nullptr otherwise. + * @param unused_format deprecated, can be NULL * @param target whether to get the original color profile from the metadata * or the color profile of the decoded pixels. * @param size variable to output the size into, or NULL to only check the - * return status. - * @return JXL_DEC_SUCCESS if the ICC profile is available, - * JXL_DEC_NEED_MORE_INPUT if the decoder has not yet received enough - * input data to determine whether an ICC profile is available or what its - * size is, JXL_DEC_ERROR in case the ICC profile is not available and - * cannot be generated. + * return status. + * @return @ref JXL_DEC_SUCCESS if the ICC profile is available, @ref + * JXL_DEC_NEED_MORE_INPUT if the decoder has not yet received enough + * input data to determine whether an ICC profile is available or what its + * size is, @ref JXL_DEC_ERROR in case the ICC profile is not available and + * cannot be generated. */ -JXL_EXPORT JxlDecoderStatus -JxlDecoderGetICCProfileSize(const JxlDecoder* dec, const JxlPixelFormat* format, - JxlColorProfileTarget target, size_t* size); +JXL_EXPORT JxlDecoderStatus JxlDecoderGetICCProfileSize( + const JxlDecoder* dec, const JxlPixelFormat* unused_format, + JxlColorProfileTarget target, size_t* size); /** - * Outputs ICC profile if available. The profile is only available if + * Outputs ICC profile if available. The profile is only available if @ref * JxlDecoderGetICCProfileSize returns success. The output buffer must have - * at least as many bytes as given by JxlDecoderGetICCProfileSize. + * at least as many bytes as given by @ref JxlDecoderGetICCProfileSize. * * @param dec decoder object - * @param format pixel format to output the data to. Only used for - * JXL_COLOR_PROFILE_TARGET_DATA, may be nullptr otherwise. + * @param unused_format deprecated, can be NULL * @param target whether to get the original color profile from the metadata * or the color profile of the decoded pixels. * @param icc_profile buffer to copy the ICC profile into * @param size size of the icc_profile buffer in bytes - * @return JXL_DEC_SUCCESS if the profile was successfully returned is - * available, JXL_DEC_NEED_MORE_INPUT if not yet available, - * JXL_DEC_ERROR if the profile doesn't exist or the output size is not - * large enough. + * @return @ref JXL_DEC_SUCCESS if the profile was successfully returned is + * available, @ref JXL_DEC_NEED_MORE_INPUT if not yet available, @ref + * JXL_DEC_ERROR if the profile doesn't exist or the output size is not + * large enough. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetColorAsICCProfile( - const JxlDecoder* dec, const JxlPixelFormat* format, + const JxlDecoder* dec, const JxlPixelFormat* unused_format, JxlColorProfileTarget target, uint8_t* icc_profile, size_t size); -/** Sets the color profile to use for JXL_COLOR_PROFILE_TARGET_DATA for the - * special case when the decoder has a choice. This only has effect for a JXL - * image where uses_original_profile is false, and the original color profile is - * encoded as an ICC color profile rather than a JxlColorEncoding with known - * enum values. In most other cases (uses uses_original_profile is true, or the - * color profile is already given as a JxlColorEncoding), this setting is - * ignored and the decoder uses a profile related to the image. - * No matter what, the JXL_COLOR_PROFILE_TARGET_DATA must still be queried to - * know the actual data format of the decoded pixels after decoding. - * - * The intended use case of this function is for cases where you are using - * a color management system to parse the original ICC color profile - * (JXL_COLOR_PROFILE_TARGET_ORIGINAL), from this you know that the ICC - * profile represents one of the color profiles supported by JxlColorEncoding - * (such as sRGB, PQ or HLG): in that case it is beneficial (but not necessary) - * to use JxlDecoderSetPreferredColorProfile to match the parsed profile. The - * JXL decoder has no color management system built in, but can convert XYB - * color to any of the ones supported by JxlColorEncoding. - * - * Can only be set after the JXL_DEC_COLOR_ENCODING event occurred and before - * any other event occurred, and can affect the result of - * JXL_COLOR_PROFILE_TARGET_DATA (but not of JXL_COLOR_PROFILE_TARGET_ORIGINAL), - * so should be used after getting JXL_COLOR_PROFILE_TARGET_ORIGINAL but before - * getting JXL_COLOR_PROFILE_TARGET_DATA. The color_encoding must be grayscale - * if num_color_channels from the basic info is 1, RGB if num_color_channels - * from the basic info is 3. - * - * If JxlDecoderSetPreferredColorProfile is not used, then for images for which - * uses_original_profile is false and with ICC color profile, the decoder will - * choose linear sRGB for color images, linear grayscale for grayscale images. - * This function only sets a preference, since for other images the decoder has - * no choice what color profile to use, it is determined by the image. +/** Sets the desired output color profile of the decoded image by calling + * @ref JxlDecoderSetOutputColorProfile, passing on @c color_encoding and + * setting @c icc_data to NULL. See @ref JxlDecoderSetOutputColorProfile for + * details. * * @param dec decoder object * @param color_encoding the default color encoding to set - * @return JXL_DEC_SUCCESS if the preference was set successfully, JXL_DEC_ERROR - * otherwise. + * @return @ref JXL_DEC_SUCCESS if the preference was set successfully, @ref + * JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreferredColorProfile( JxlDecoder* dec, const JxlColorEncoding* color_encoding); +/** Requests that the decoder perform tone mapping to the peak display luminance + * passed as @c desired_intensity_target, if appropriate. + * @note This is provided for convenience and the exact tone mapping that is + * performed is not meant to be considered authoritative in any way. It may + * change from version to version. + * @param dec decoder object + * @param desired_intensity_target the intended target peak luminance + * @return @ref JXL_DEC_SUCCESS if the preference was set successfully, @ref + * JXL_DEC_ERROR otherwise. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderSetDesiredIntensityTarget( + JxlDecoder* dec, float desired_intensity_target); + +/** + * Sets the desired output color profile of the decoded image either from a + * color encoding or an ICC profile. Valid calls of this function have either @c + * color_encoding or @c icc_data set to NULL and @c icc_size must be 0 if and + * only if @c icc_data is NULL. + * + * Depending on whether a color management system (CMS) has been set the + * behavior is as follows: + * + * If a color management system (CMS) has been set with @ref JxlDecoderSetCms, + * and the CMS suppports output to the desired color encoding or ICC profile, + * then it will provide the output in that color encoding or ICC profile. If the + * desired color encoding or the ICC is not supported, then an error will be + * returned. + * + * If no CMS has been set with @ref JxlDecoderSetCms, there are two cases: + * + * (1) Calling this function with a color encoding will convert XYB images to + * the desired color encoding. In this case, if the requested color encoding has + * a narrower gamut, or the white points differ, then the resulting image can + * have significant color distortion. Non-XYB images will not be converted to + * the desired color space. + * + * (2) Calling this function with an ICC profile will result in an error. + * + * If called with an ICC profile (after a call to @ref JxlDecoderSetCms), the + * ICC profile has to be a valid RGB or grayscale color profile. + * + * Can only be set after the @ref JXL_DEC_COLOR_ENCODING event occurred and + * before any other event occurred, and should be used before getting + * JXL_COLOR_PROFILE_TARGET_DATA. + * + * This function must not be called before JxlDecoderSetCms. + * + * @param dec decoder orbject + * @param color_encoding the output color encoding + * @param icc_data bytes of the icc profile + * @param icc_size size of the icc profile in bytes + * @return @ref JXL_DEC_SUCCESS if the color profile was set successfully, @ref + * JXL_DEC_ERROR otherwise. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderSetOutputColorProfile( + JxlDecoder* dec, const JxlColorEncoding* color_encoding, + const uint8_t* icc_data, size_t icc_size); + +/** + * Sets the color management system (CMS) that will be used for color + * conversion (if applicable) during decoding. May only be set before starting + * decoding and must not be called after @ref JxlDecoderSetOutputColorProfile. + * + * See @ref JxlDecoderSetOutputColorProfile for how color conversions are done + * depending on whether or not a CMS has been set with @ref JxlDecoderSetCms. + * + * @param dec decoder object. + * @param cms structure representing a CMS implementation. See @ref + * JxlCmsInterface for more details. + */ +JXL_EXPORT void JxlDecoderSetCms(JxlDecoder* dec, JxlCmsInterface cms); +// TODO(firsching): add a function JxlDecoderSetDefaultCms() for setting a +// default in case libjxl is build with an CMS. + /** * Returns the minimum size in bytes of the preview image output pixel buffer - * for the given format. This is the buffer for JxlDecoderSetPreviewOutBuffer. - * Requires the preview header information is available in the decoder. + * for the given format. This is the buffer for @ref + * JxlDecoderSetPreviewOutBuffer. Requires the preview header information is + * available in the decoder. * * @param dec decoder object * @param format format of pixels * @param size output value, buffer size in bytes - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * information not available yet. + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * information not available yet. */ JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize( const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size); /** * Sets the buffer to write the small resolution preview image - * to. The size of the buffer must be at least as large as given by - * JxlDecoderPreviewOutBufferSize. The buffer follows the format described by - * JxlPixelFormat. The preview image dimensions are given by the + * to. The size of the buffer must be at least as large as given by @ref + * JxlDecoderPreviewOutBufferSize. The buffer follows the format described + * by JxlPixelFormat. The preview image dimensions are given by the * JxlPreviewHeader. The buffer is owned by the caller. * * @param dec decoder object * @param format format of pixels. Object owned by user and its contents are - * copied internally. + * copied internally. * @param buffer buffer type to output the pixel data to * @param size size of buffer in bytes - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * size too small. + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * size too small. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreviewOutBuffer( JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size); /** * Outputs the information from the frame, such as duration when have_animation. - * This function can be called when JXL_DEC_FRAME occurred for the current + * This function can be called when @ref JXL_DEC_FRAME occurred for the current * frame, even when have_animation in the JxlBasicInfo is JXL_FALSE. * * @param dec decoder object * @param header struct to copy the information into, or NULL to only check - * whether the information is available through the return value. - * @return JXL_DEC_SUCCESS if the value is available, - * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case - * of other error conditions. + * whether the information is available through the return value. + * @return @ref JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in + * case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec, JxlFrameHeader* header); @@ -763,110 +942,135 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec, * @param name buffer to copy the name into * @param size size of the name buffer in bytes, including zero termination * character, so this must be at least JxlFrameHeader.name_length + 1. - * @return JXL_DEC_SUCCESS if the value is available, - * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case - * of other error conditions. + * @return @ref JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in + * case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameName(const JxlDecoder* dec, char* name, size_t size); /** - * Returns the minimum size in bytes of the DC image output buffer - * for the given format. This is the buffer for JxlDecoderSetDCOutBuffer. - * Requires the basic image information is available in the decoder. - * - * @param dec decoder object - * @param format format of pixels - * @param size output value, buffer size in bytes - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * information not available yet. - * - * DEPRECATED: the DC feature in this form will be removed. You can use - * JxlDecoderFlushImage for progressive rendering. - */ -JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderDCOutBufferSize( - const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size); - -/** - * Sets the buffer to write the lower resolution (8x8 sub-sampled) DC image - * to. The size of the buffer must be at least as large as given by - * JxlDecoderDCOutBufferSize. The buffer follows the format described by - * JxlPixelFormat. The DC image has dimensions ceil(xsize / 8) * ceil(ysize / - * 8). The buffer is owned by the caller. + * Outputs the blend information for the current frame for a specific extra + * channel. This function can be called when @ref JXL_DEC_FRAME occurred for the + * current frame, even when have_animation in the JxlBasicInfo is JXL_FALSE. + * This information is only useful if coalescing is disabled; otherwise the + * decoder will have performed blending already. * * @param dec decoder object - * @param format format of pixels. Object owned by user and its contents are - * copied internally. - * @param buffer buffer type to output the pixel data to - * @param size size of buffer in bytes - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * size too small. - * - * DEPRECATED: the DC feature in this form will be removed. You can use - * JxlDecoderFlushImage for progressive rendering. + * @param index the index of the extra channel + * @param blend_info struct to copy the information into + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error */ -JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderSetDCOutBuffer( - JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size); +JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelBlendInfo( + const JxlDecoder* dec, size_t index, JxlBlendInfo* blend_info); /** * Returns the minimum size in bytes of the image output pixel buffer for the - * given format. This is the buffer for JxlDecoderSetImageOutBuffer. Requires - * the basic image information is available in the decoder. + * given format. This is the buffer for @ref JxlDecoderSetImageOutBuffer. + * Requires that the basic image information is available in the decoder in the + * case of coalescing enabled (default). In case coalescing is disabled, this + * can only be called after the @ref JXL_DEC_FRAME event occurs. In that case, + * it will return the size required to store the possibly cropped frame (which + * can be larger or smaller than the image dimensions). * * @param dec decoder object * @param format format of the pixels. * @param size output value, buffer size in bytes - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * information not available yet. + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * information not available yet. */ JXL_EXPORT JxlDecoderStatus JxlDecoderImageOutBufferSize( const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size); /** * Sets the buffer to write the full resolution image to. This can be set when - * the JXL_DEC_FRAME event occurs, must be set when the - * JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and applies only for the current - * frame. The size of the buffer must be at least as large as given by - * JxlDecoderImageOutBufferSize. The buffer follows the format described by - * JxlPixelFormat. The buffer is owned by the caller. + * the @ref JXL_DEC_FRAME event occurs, must be set when the @ref + * JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and applies only for the + * current frame. The size of the buffer must be at least as large as given + * by @ref JxlDecoderImageOutBufferSize. The buffer follows the format described + * by JxlPixelFormat. The buffer is owned by the caller. * * @param dec decoder object * @param format format of the pixels. Object owned by user and its contents - * are copied internally. + * are copied internally. * @param buffer buffer type to output the pixel data to * @param size size of buffer in bytes - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * size too small. + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * size too small. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer( JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size); /** - * Callback function type for JxlDecoderSetImageOutCallback. @see - * JxlDecoderSetImageOutCallback for usage. + * Function type for @ref JxlDecoderSetImageOutCallback. * * The callback may be called simultaneously by different threads when using a * threaded parallel runner, on different pixels. * - * @param opaque optional user data, as given to JxlDecoderSetImageOutCallback. + * @param opaque optional user data, as given to @ref + * JxlDecoderSetImageOutCallback. * @param x horizontal position of leftmost pixel of the pixel data. * @param y vertical position of the pixel data. * @param num_pixels amount of pixels included in the pixel data, horizontally. - * This is not the same as xsize of the full image, it may be smaller. - * @param pixels pixel data as a horizontal stripe, in the format passed to - * JxlDecoderSetImageOutCallback. The memory is not owned by the user, and is - * only valid during the time the callback is running. + * This is not the same as xsize of the full image, it may be smaller. + * @param pixels pixel data as a horizontal stripe, in the format passed to @ref + * JxlDecoderSetImageOutCallback. The memory is not owned by the user, and + * is only valid during the time the callback is running. */ typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y, size_t num_pixels, const void* pixels); /** - * Sets pixel output callback. This is an alternative to - * JxlDecoderSetImageOutBuffer. This can be set when the JXL_DEC_FRAME event - * occurs, must be set when the JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and - * applies only for the current frame. Only one of JxlDecoderSetImageOutBuffer - * or JxlDecoderSetImageOutCallback may be used for the same frame, not both at - * the same time. + * Initialization callback for @ref JxlDecoderSetMultithreadedImageOutCallback. + * + * @param init_opaque optional user data, as given to @ref + * JxlDecoderSetMultithreadedImageOutCallback. + * @param num_threads maximum number of threads that will call the @c run + * callback concurrently. + * @param num_pixels_per_thread maximum number of pixels that will be passed in + * one call to @c run. + * @return a pointer to data that will be passed to the @c run callback, or + * @c NULL if initialization failed. + */ +typedef void* (*JxlImageOutInitCallback)(void* init_opaque, size_t num_threads, + size_t num_pixels_per_thread); + +/** + * Worker callback for @ref JxlDecoderSetMultithreadedImageOutCallback. + * + * @param run_opaque user data returned by the @c init callback. + * @param thread_id number in `[0, num_threads)` identifying the thread of the + * current invocation of the callback. + * @param x horizontal position of the first (leftmost) pixel of the pixel data. + * @param y vertical position of the pixel data. + * @param num_pixels number of pixels in the pixel data. May be less than the + * full @c xsize of the image, and will be at most equal to the @c + * num_pixels_per_thread that was passed to @c init. + * @param pixels pixel data as a horizontal stripe, in the format passed to @ref + * JxlDecoderSetMultithreadedImageOutCallback. The data pointed to + * remains owned by the caller and is only guaranteed to outlive the current + * callback invocation. + */ +typedef void (*JxlImageOutRunCallback)(void* run_opaque, size_t thread_id, + size_t x, size_t y, size_t num_pixels, + const void* pixels); + +/** + * Destruction callback for @ref JxlDecoderSetMultithreadedImageOutCallback, + * called after all invocations of the @c run callback to perform any + * appropriate clean-up of the @c run_opaque data returned by @c init. + * + * @param run_opaque user data returned by the @c init callback. + */ +typedef void (*JxlImageOutDestroyCallback)(void* run_opaque); + +/** + * Sets pixel output callback. This is an alternative to @ref + * JxlDecoderSetImageOutBuffer. This can be set when the @ref JXL_DEC_FRAME + * event occurs, must be set when the @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event + * occurs, and applies only for the current frame. Only one of @ref + * JxlDecoderSetImageOutBuffer or @ref JxlDecoderSetImageOutCallback may be used + * for the same frame, not both at the same time. * * The callback will be called multiple times, to receive the image * data in small chunks. The callback receives a horizontal stripe of pixel @@ -877,49 +1081,73 @@ typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y, * simultaneously by different threads when using a threaded parallel runner, on * different pixels. * - * If JxlDecoderFlushImage is not used, then each pixel will be visited exactly - * once by the different callback calls, during processing with one or more - * JxlDecoderProcessInput calls. These pixels are decoded to full detail, they - * are not part of a lower resolution or lower quality progressive pass, but the - * final pass. - * - * If JxlDecoderFlushImage is used, then in addition each pixel will be visited - * zero or one times during the blocking JxlDecoderFlushImage call. Pixels - * visited as a result of JxlDecoderFlushImage may represent a lower resolution - * or lower quality intermediate progressive pass of the image. Any visited - * pixel will be of a quality at least as good or better than previous visits of - * this pixel. A pixel may be visited zero times if it cannot be decoded yet - * or if it was already decoded to full precision (this behavior is not - * guaranteed). + * If @ref JxlDecoderFlushImage is not used, then each pixel will be visited + * exactly once by the different callback calls, during processing with one or + * more @ref JxlDecoderProcessInput calls. These pixels are decoded to full + * detail, they are not part of a lower resolution or lower quality progressive + * pass, but the final pass. + * + * If @ref JxlDecoderFlushImage is used, then in addition each pixel will be + * visited zero or one times during the blocking @ref JxlDecoderFlushImage call. + * Pixels visited as a result of @ref JxlDecoderFlushImage may represent a lower + * resolution or lower quality intermediate progressive pass of the image. Any + * visited pixel will be of a quality at least as good or better than previous + * visits of this pixel. A pixel may be visited zero times if it cannot be + * decoded yet or if it was already decoded to full precision (this behavior is + * not guaranteed). * * @param dec decoder object - * @param format format of the pixels. Object owned by user and its contents - * are copied internally. + * @param format format of the pixels. Object owned by user; its contents are + * copied internally. * @param callback the callback function receiving partial scanlines of pixel - * data. + * data. * @param opaque optional user data, which will be passed on to the callback, - * may be NULL. - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * JxlDecoderSetImageOutBuffer already set. + * may be NULL. + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such + * as @ref JxlDecoderSetImageOutBuffer already set. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format, JxlImageOutCallback callback, void* opaque); +/** Similar to @ref JxlDecoderSetImageOutCallback except that the callback is + * allowed an initialization phase during which it is informed of how many + * threads will call it concurrently, and those calls are further informed of + * which thread they are occurring in. + * + * @param dec decoder object + * @param format format of the pixels. Object owned by user; its contents are + * copied internally. + * @param init_callback initialization callback. + * @param run_callback the callback function receiving partial scanlines of + * pixel data. + * @param destroy_callback clean-up callback invoked after all calls to @c + * run_callback. May be NULL if no clean-up is necessary. + * @param init_opaque optional user data passed to @c init_callback, may be NULL + * (unlike the return value from @c init_callback which may only be NULL if + * initialization failed). + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such + * as @ref JxlDecoderSetImageOutBuffer having already been called. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback( + JxlDecoder* dec, const JxlPixelFormat* format, + JxlImageOutInitCallback init_callback, JxlImageOutRunCallback run_callback, + JxlImageOutDestroyCallback destroy_callback, void* init_opaque); + /** * Returns the minimum size in bytes of an extra channel pixel buffer for the - * given format. This is the buffer for JxlDecoderSetExtraChannelBuffer. + * given format. This is the buffer for @ref JxlDecoderSetExtraChannelBuffer. * Requires the basic image information is available in the decoder. * * @param dec decoder object * @param format format of the pixels. The num_channels value is ignored and is - * always treated to be 1. + * always treated to be 1. * @param size output value, buffer size in bytes - * @param index which extra channel to get, matching the index used in @see - * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in the - * associated JxlBasicInfo. - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * information not available yet or invalid index. + * @param index which extra channel to get, matching the index used in @ref + * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in + * the associated JxlBasicInfo. + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * information not available yet or invalid index. */ JXL_EXPORT JxlDecoderStatus JxlDecoderExtraChannelBufferSize( const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size, @@ -927,32 +1155,33 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderExtraChannelBufferSize( /** * Sets the buffer to write an extra channel to. This can be set when - * the JXL_DEC_FRAME or JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and applies - * only for the current frame. The size of the buffer must be at least as large - * as given by JxlDecoderExtraChannelBufferSize. The buffer follows the format - * described by JxlPixelFormat, but where num_channels is 1. The buffer is owned - * by the caller. The amount of extra channels is given by the - * num_extra_channels field in the associated JxlBasicInfo, and the information - * of individual extra channels can be queried with @see + * the @ref JXL_DEC_FRAME or @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, + * and applies only for the current frame. The size of the buffer must be at + * least as large as given by @ref JxlDecoderExtraChannelBufferSize. The buffer + * follows the format described by JxlPixelFormat, but where num_channels is 1. + * The buffer is owned by the caller. The amount of extra channels is given by + * the num_extra_channels field in the associated JxlBasicInfo, and the + * information of individual extra channels can be queried with @ref * JxlDecoderGetExtraChannelInfo. To get multiple extra channels, this function * must be called multiple times, once for each wanted index. Not all images * have extra channels. The alpha channel is an extra channel and can be gotten - * as part of the color channels when using an RGBA pixel buffer with - * JxlDecoderSetImageOutBuffer, but additionally also can be gotten separately - * as extra channel. The color channels themselves cannot be gotten this way. + * as part of the color channels when using an RGBA pixel buffer with @ref + * JxlDecoderSetImageOutBuffer, but additionally also can be gotten + * separately as extra channel. The color channels themselves cannot be gotten + * this way. * * * @param dec decoder object * @param format format of the pixels. Object owned by user and its contents - * are copied internally. The num_channels value is ignored and is always - * treated to be 1. + * are copied internally. The num_channels value is ignored and is always + * treated to be 1. * @param buffer buffer type to output the pixel data to * @param size size of buffer in bytes - * @param index which extra channel to get, matching the index used in @see - * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in the - * associated JxlBasicInfo. - * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as - * size too small or invalid index. + * @param index which extra channel to get, matching the index used in @ref + * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in + * the associated JxlBasicInfo. + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * size too small or invalid index. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetExtraChannelBuffer(JxlDecoder* dec, const JxlPixelFormat* format, @@ -961,79 +1190,82 @@ JxlDecoderSetExtraChannelBuffer(JxlDecoder* dec, const JxlPixelFormat* format, /** * Sets output buffer for reconstructed JPEG codestream. * - * The data is owned by the caller and may be used by the decoder until - * JxlDecoderReleaseJPEGBuffer is called or the decoder is destroyed or reset so - * must be kept alive until then. + * The data is owned by the caller and may be used by the decoder until @ref + * JxlDecoderReleaseJPEGBuffer is called or the decoder is destroyed or + * reset so must be kept alive until then. * - * If a JPEG buffer was set before and released with - * JxlDecoderReleaseJPEGBuffer, bytes that the decoder has already output should - * not be included, only the remaining bytes output must be set. + * If a JPEG buffer was set before and released with @ref + * JxlDecoderReleaseJPEGBuffer, bytes that the decoder has already output + * should not be included, only the remaining bytes output must be set. * * @param dec decoder object * @param data pointer to next bytes to write to * @param size amount of bytes available starting from data - * @return JXL_DEC_ERROR if output buffer was already set and - * JxlDecoderReleaseJPEGBuffer was not called on it, JXL_DEC_SUCCESS otherwise + * @return @ref JXL_DEC_ERROR if output buffer was already set and @ref + * JxlDecoderReleaseJPEGBuffer was not called on it, @ref JXL_DEC_SUCCESS + * otherwise */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec, uint8_t* data, size_t size); /** - * Releases buffer which was provided with JxlDecoderSetJPEGBuffer. + * Releases buffer which was provided with @ref JxlDecoderSetJPEGBuffer. * - * Calling JxlDecoderReleaseJPEGBuffer is required whenever - * a buffer is already set and a new buffer needs to be added with - * JxlDecoderSetJPEGBuffer, but is not required before JxlDecoderDestroy or - * JxlDecoderReset. + * Calling @ref JxlDecoderReleaseJPEGBuffer is required whenever + * a buffer is already set and a new buffer needs to be added with @ref + * JxlDecoderSetJPEGBuffer, but is not required before @ref + * JxlDecoderDestroy or @ref JxlDecoderReset. * - * Calling JxlDecoderReleaseJPEGBuffer when no buffer is set is + * Calling @ref JxlDecoderReleaseJPEGBuffer when no buffer is set is * not an error and returns 0. * * @param dec decoder object * @return the amount of bytes the decoder has not yet written to of the data - * set by JxlDecoderSetJPEGBuffer, or 0 if no buffer is set or - * JxlDecoderReleaseJPEGBuffer was already called. + * set by @ref JxlDecoderSetJPEGBuffer, or 0 if no buffer is set or @ref + * JxlDecoderReleaseJPEGBuffer was already called. */ JXL_EXPORT size_t JxlDecoderReleaseJPEGBuffer(JxlDecoder* dec); /** * Sets output buffer for box output codestream. * - * The data is owned by the caller and may be used by the decoder until - * JxlDecoderReleaseBoxBuffer is called or the decoder is destroyed or reset so - * must be kept alive until then. + * The data is owned by the caller and may be used by the decoder until @ref + * JxlDecoderReleaseBoxBuffer is called or the decoder is destroyed or + * reset so must be kept alive until then. * - * If for the current box a box buffer was set before and released with - * JxlDecoderReleaseBoxBuffer, bytes that the decoder has already output should - * not be included, only the remaining bytes output must be set. + * If for the current box a box buffer was set before and released with @ref + * JxlDecoderReleaseBoxBuffer, bytes that the decoder has already output + * should not be included, only the remaining bytes output must be set. * - * The JxlDecoderReleaseBoxBuffer must be used at the next JXL_DEC_BOX event - * or final JXL_DEC_SUCCESS event to compute the size of the output box bytes. + * The @ref JxlDecoderReleaseBoxBuffer must be used at the next @ref JXL_DEC_BOX + * event or final @ref JXL_DEC_SUCCESS event to compute the size of the output + * box bytes. * * @param dec decoder object * @param data pointer to next bytes to write to * @param size amount of bytes available starting from data - * @return JXL_DEC_ERROR if output buffer was already set and - * JxlDecoderReleaseBoxBuffer was not called on it, JXL_DEC_SUCCESS otherwise + * @return @ref JXL_DEC_ERROR if output buffer was already set and @ref + * JxlDecoderReleaseBoxBuffer was not called on it, @ref JXL_DEC_SUCCESS + * otherwise */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetBoxBuffer(JxlDecoder* dec, uint8_t* data, size_t size); /** - * Releases buffer which was provided with JxlDecoderSetBoxBuffer. + * Releases buffer which was provided with @ref JxlDecoderSetBoxBuffer. * - * Calling JxlDecoderReleaseBoxBuffer is required whenever - * a buffer is already set and a new buffer needs to be added with - * JxlDecoderSetBoxBuffer, but is not required before JxlDecoderDestroy or - * JxlDecoderReset. + * Calling @ref JxlDecoderReleaseBoxBuffer is required whenever + * a buffer is already set and a new buffer needs to be added with @ref + * JxlDecoderSetBoxBuffer, but is not required before @ref + * JxlDecoderDestroy or @ref JxlDecoderReset. * - * Calling JxlDecoderReleaseBoxBuffer when no buffer is set is + * Calling @ref JxlDecoderReleaseBoxBuffer when no buffer is set is * not an error and returns 0. * * @param dec decoder object * @return the amount of bytes the decoder has not yet written to of the data - * set by JxlDecoderSetBoxBuffer, or 0 if no buffer is set or - * JxlDecoderReleaseBoxBuffer was already called. + * set by @ref JxlDecoderSetBoxBuffer, or 0 if no buffer is set or @ref + * JxlDecoderReleaseBoxBuffer was already called. */ JXL_EXPORT size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec); @@ -1046,74 +1278,166 @@ JXL_EXPORT size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec); * finished. * * The default mode is raw. This setting can only be changed before decoding, or - * directly after a JXL_DEC_BOX event, and is remembered until the decoder is - * reset or destroyed. + * directly after a @ref JXL_DEC_BOX event, and is remembered until the decoder + * is reset or destroyed. * * Enabling decompressed mode requires Brotli support from the library. * * @param dec decoder object * @param decompress JXL_TRUE to transparently decompress, JXL_FALSE to get - * boxes in raw mode. - * @return JXL_DEC_ERROR if decompressed mode is set and Brotli is not - * available, JXL_DEC_SUCCESS otherwise. + * boxes in raw mode. + * @return @ref JXL_DEC_ERROR if decompressed mode is set and Brotli is not + * available, @ref JXL_DEC_SUCCESS otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec, JXL_BOOL decompress); /** - * Outputs the type of the current box, after a JXL_DEC_BOX event occured, as 4 - * characters without null termination character. In case of a compressed "brob" - * box, this will return "brob" if the decompressed argument is JXL_FALSE, or - * the underlying box type if the decompressed argument is JXL_TRUE. + * Outputs the type of the current box, after a @ref JXL_DEC_BOX event occured, + * as 4 characters without null termination character. In case of a compressed + * "brob" box, this will return "brob" if the decompressed argument is + * JXL_FALSE, or the underlying box type if the decompressed argument is + * JXL_TRUE. + * + * The following box types are currently described in ISO/IEC 18181-2: + * - "Exif": a box with EXIF metadata. Starts with a 4-byte tiff header offset + * (big-endian uint32) that indicates the start of the actual EXIF data + * (which starts with a tiff header). Usually the offset will be zero and the + * EXIF data starts immediately after the offset field. The Exif orientation + * should be ignored by applications; the JPEG XL codestream orientation + * takes precedence and libjxl will by default apply the correct orientation + * automatically (see @ref JxlDecoderSetKeepOrientation). + * - "xml ": a box with XML data, in particular XMP metadata. + * - "jumb": a JUMBF superbox (JPEG Universal Metadata Box Format, ISO/IEC + * 19566-5). + * - "JXL ": mandatory signature box, must come first, 12 bytes long including + * the box header + * - "ftyp": a second mandatory signature box, must come second, 20 bytes long + * including the box header + * - "jxll": a JXL level box. This indicates if the codestream is level 5 or + * level 10 compatible. If not present, it is level 5. Level 10 allows more + * features such as very high image resolution and bit-depths above 16 bits + * per channel. Added automatically by the encoder when + * JxlEncoderSetCodestreamLevel is used + * - "jxlc": a box with the image codestream, in case the codestream is not + * split across multiple boxes. The codestream contains the JPEG XL image + * itself, including the basic info such as image dimensions, ICC color + * profile, and all the pixel data of all the image frames. + * - "jxlp": a codestream box in case it is split across multiple boxes. + * The contents are the same as in case of a jxlc box, when concatenated. + * - "brob": a Brotli-compressed box, which otherwise represents an existing + * type of box such as Exif or "xml ". When @ref JxlDecoderSetDecompressBoxes + * is set to JXL_TRUE, these boxes will be transparently decompressed by the + * decoder. + * - "jxli": frame index box, can list the keyframes in case of a JPEG XL + * animation allowing the decoder to jump to individual frames more + * efficiently. + * - "jbrd": JPEG reconstruction box, contains the information required to + * byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG DCT + * coefficients (pixel content) themselves as well as the ICC profile are + * encoded in the JXL codestream (jxlc or jxlp) itself. EXIF, XMP and JUMBF + * metadata is encoded in the corresponding boxes. The jbrd box itself + * contains information such as the remaining app markers of the JPEG-1 file + * and everything else required to fit the information together into the + * exact original JPEG file. + * + * Other application-specific boxes can exist. Their typename should not begin + * with "jxl" or "JXL" or conflict with other existing typenames. + * + * The signature, jxl* and jbrd boxes are processed by the decoder and would + * typically be ignored by applications. The typical way to use this function is + * to check if an encountered box contains metadata that the application is + * interested in (e.g. EXIF or XMP metadata), in order to conditionally set a + * box buffer. * * @param dec decoder object * @param type buffer to copy the type into - * @param decompressed which box type to get: JXL_TRUE to get the raw box type, - * which can be "brob", JXL_FALSE, get the underlying box type. - * @return JXL_DEC_SUCCESS if the value is available, JXL_DEC_ERROR if not, for - * example the JXL file does not use the container format. + * @param decompressed which box type to get: JXL_FALSE to get the raw box type, + * which can be "brob", JXL_TRUE, get the underlying box type. + * @return @ref JXL_DEC_SUCCESS if the value is available, @ref JXL_DEC_ERROR if + * not, for example the JXL file does not use the container format. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxType(JxlDecoder* dec, JxlBoxType type, JXL_BOOL decompressed); /** - * Returns the size of a box as it appears in the container file, after the + * Returns the size of a box as it appears in the container file, after the @ref * JXL_DEC_BOX event. For a non-compressed box, this is the size of the * contents, excluding the 4 bytes indicating the box type. For a compressed * "brob" box, this is the size of the compressed box contents plus the * additional 4 byte indicating the underlying box type, but excluding the 4 * bytes indicating "brob". This function gives the size of the data that will * be written in the output buffer when getting boxes in the default raw - * compressed mode. When JxlDecoderSetDecompressBoxes is enabled, the return - * value of function does not change, and the decompressed size is not known - * before it has already been decompressed and output. + * compressed mode. When @ref JxlDecoderSetDecompressBoxes is enabled, the + * return value of function does not change, and the decompressed size is not + * known before it has already been decompressed and output. * * @param dec decoder object * @param size raw size of the box in bytes - * @return JXL_DEC_ERROR if no box size is available, JXL_DEC_SUCCESS otherwise. + * @return @ref JXL_DEC_ERROR if no box size is available, @ref JXL_DEC_SUCCESS + * otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxSizeRaw(const JxlDecoder* dec, uint64_t* size); +/** + * Configures at which progressive steps in frame decoding these @ref + * JXL_DEC_FRAME_PROGRESSION event occurs. The default value for the level + * of detail if this function is never called is `kDC`. + * + * @param dec decoder object + * @param detail at which level of detail to trigger @ref + * JXL_DEC_FRAME_PROGRESSION + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * an invalid value for the progressive detail. + */ +JXL_EXPORT JxlDecoderStatus +JxlDecoderSetProgressiveDetail(JxlDecoder* dec, JxlProgressiveDetail detail); + +/** + * Returns the intended downsampling ratio for the progressive frame produced + * by @ref JxlDecoderFlushImage after the latest @ref JXL_DEC_FRAME_PROGRESSION + * event. + * + * @param dec decoder object + * @return The intended downsampling ratio, can be 1, 2, 4 or 8. + */ +JXL_EXPORT size_t JxlDecoderGetIntendedDownsamplingRatio(JxlDecoder* dec); + /** * Outputs progressive step towards the decoded image so far when only partial - * input was received. If the flush was successful, the buffer set with + * input was received. If the flush was successful, the buffer set with @ref * JxlDecoderSetImageOutBuffer will contain partial image data. * - * Can be called when JxlDecoderProcessInput returns JXL_DEC_NEED_MORE_INPUT, - * after the JXL_DEC_FRAME event already occurred and before the - * JXL_DEC_FULL_IMAGE event occurred for a frame. + * Can be called when @ref JxlDecoderProcessInput returns @ref + * JXL_DEC_NEED_MORE_INPUT, after the @ref JXL_DEC_FRAME event already occurred + * and before the @ref JXL_DEC_FULL_IMAGE event occurred for a frame. * * @param dec decoder object - * @return JXL_DEC_SUCCESS if image data was flushed to the output buffer, or - * JXL_DEC_ERROR when no flush was done, e.g. if not enough image data was - * available yet even for flush, or no output buffer was set yet. An error is - * not fatal, it only indicates no flushed image is available now, regular, - * decoding can still be performed. + * @return @ref JXL_DEC_SUCCESS if image data was flushed to the output buffer, + * or @ref JXL_DEC_ERROR when no flush was done, e.g. if not enough image + * data was available yet even for flush, or no output buffer was set yet. + * This error is not fatal, it only indicates no flushed image is available + * right now. Regular decoding can still be performed. */ JXL_EXPORT JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec); +/** + * Sets the bit depth of the output buffer or callback. + * + * Can be called after @ref JxlDecoderSetImageOutBuffer or @ref + * JxlDecoderSetImageOutCallback. For float pixel data types, only the default + * @ref JXL_BIT_DEPTH_FROM_PIXEL_FORMAT setting is supported. + * + * @param dec decoder object + * @param bit_depth the bit depth setting of the pixel output + * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * incompatible custom bit depth and pixel data type. + */ +JXL_EXPORT JxlDecoderStatus +JxlDecoderSetImageOutBitDepth(JxlDecoder* dec, const JxlBitDepth* bit_depth); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/jpeg-xl/include/jxl/encode.h b/jpeg-xl/include/jxl/encode.h index 5d6a3bf..f5087c7 100644 --- a/jpeg-xl/include/jxl/encode.h +++ b/jpeg-xl/include/jxl/encode.h @@ -13,11 +13,12 @@ #ifndef JXL_ENCODE_H_ #define JXL_ENCODE_H_ +#include "jxl/cms_interface.h" #include "jxl/codestream_header.h" -#include "jxl/decode.h" #include "jxl/jxl_export.h" #include "jxl/memory_manager.h" #include "jxl/parallel_runner.h" +#include "jxl/version.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { @@ -41,14 +42,18 @@ JXL_EXPORT uint32_t JxlEncoderVersion(void); typedef struct JxlEncoderStruct JxlEncoder; /** - * Opaque structure that holds frame specific encoding options for a JPEG XL - * encoder. + * Settings and metadata for a single image frame. This includes encoder options + * for a frame such as compression quality and speed. * - * Allocated and initialized with JxlEncoderOptionsCreate(). + * Allocated and initialized with JxlEncoderFrameSettingsCreate(). * Cleaned up and deallocated when the encoder is destroyed with * JxlEncoderDestroy(). */ -typedef struct JxlEncoderOptionsStruct JxlEncoderOptions; +typedef struct JxlEncoderFrameSettingsStruct JxlEncoderFrameSettings; + +/** DEPRECATED: Use JxlEncoderFrameSettings instead. + */ +typedef JxlEncoderFrameSettings JxlEncoderOptions; /** * Return value for multiple encoder functions. @@ -67,19 +72,62 @@ typedef enum { */ JXL_ENC_NEED_MORE_OUTPUT = 2, - /** The encoder doesn't (yet) support this. + /** DEPRECATED: the encoder does not return this status and there is no need + * to handle or expect it. + * Instead, JXL_ENC_ERROR is returned with error condition + * JXL_ENC_ERR_NOT_SUPPORTED. */ JXL_ENC_NOT_SUPPORTED = 3, } JxlEncoderStatus; /** - * Id of per-frame options to set to JxlEncoderOptions with - * JxlEncoderOptionsSetInteger. - * NOTE: this enum includes most but not all encoder options. The image quality - * is a frame option that can be set with JxlEncoderOptionsSetDistance instead. - * Options that apply globally, rather than per-frame, are set with their own - * functions and do not use the per-frame JxlEncoderOptions. + * Error conditions: + * API usage errors have the 0x80 bit set to 1 + * Other errors have the 0x80 bit set to 0 + */ +typedef enum { + /** No error + */ + JXL_ENC_ERR_OK = 0, + + /** Generic encoder error due to unspecified cause + */ + JXL_ENC_ERR_GENERIC = 1, + + /** Out of memory + * TODO(jon): actually catch this and return this error + */ + JXL_ENC_ERR_OOM = 2, + + /** JPEG bitstream reconstruction data could not be + * represented (e.g. too much tail data) + */ + JXL_ENC_ERR_JBRD = 3, + + /** Input is invalid (e.g. corrupt JPEG file or ICC profile) + */ + JXL_ENC_ERR_BAD_INPUT = 4, + + /** The encoder doesn't (yet) support this. Either no version of libjxl + * supports this, and the API is used incorrectly, or the libjxl version + * should have been checked before trying to do this. + */ + JXL_ENC_ERR_NOT_SUPPORTED = 0x80, + + /** The encoder API is used in an incorrect way. + * In this case, a debug build of libjxl should output a specific error + * message. (if not, please open an issue about it) + */ + JXL_ENC_ERR_API_USAGE = 0x81, + +} JxlEncoderError; + +/** + * Id of encoder options for a frame. This includes options such as setting + * encoding effort/speed or overriding the use of certain coding tools, for this + * frame. This does not include non-frame related encoder options such as for + * boxes. */ typedef enum { /** Sets encoder effort/speed level without affecting decoding speed. Valid @@ -87,13 +135,13 @@ typedef enum { * 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise. * Default: squirrel (7). */ - JXL_ENC_OPTION_EFFORT = 0, + JXL_ENC_FRAME_SETTING_EFFORT = 0, /** Sets the decoding speed tier for the provided options. Minimum is 0 * (slowest to decode, best quality/density), and maximum is 4 (fastest to * decode, at the cost of some quality/density). Default is 0. */ - JXL_ENC_OPTION_DECODING_SPEED = 1, + JXL_ENC_FRAME_SETTING_DECODING_SPEED = 1, /** Sets resampling option. If enabled, the image is downsampled before * compression, and upsampled to original size in the decoder. Integer option, @@ -101,143 +149,209 @@ typedef enum { * 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for 4x4 * downsampling, 8 for 8x8 downsampling. */ - JXL_ENC_OPTION_RESAMPLING = 2, + JXL_ENC_FRAME_SETTING_RESAMPLING = 2, - /** Similar to JXL_ENC_OPTION_RESAMPLING, but for extra channels. Integer - * option, use -1 for the default behavior (depends on encoder + /** Similar to JXL_ENC_FRAME_SETTING_RESAMPLING, but for extra channels. + * Integer option, use -1 for the default behavior (depends on encoder * implementation), 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for * 4x4 downsampling, 8 for 8x8 downsampling. */ - JXL_ENC_OPTION_EXTRA_CHANNEL_RESAMPLING = 3, + JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING = 3, + + /** Indicates the frame added with @ref JxlEncoderAddImageFrame is already + * downsampled by the downsampling factor set with @ref + * JXL_ENC_FRAME_SETTING_RESAMPLING. The input frame must then be given in the + * downsampled resolution, not the full image resolution. The downsampled + * resolution is given by ceil(xsize / resampling), ceil(ysize / resampling) + * with xsize and ysize the dimensions given in the basic info, and resampling + * the factor set with @ref JXL_ENC_FRAME_SETTING_RESAMPLING. + * Use 0 to disable, 1 to enable. Default value is 0. + */ + JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED = 4, /** Adds noise to the image emulating photographic film noise, the higher the * given number, the grainier the image will be. As an example, a value of 100 * gives low noise whereas a value of 3200 gives a lot of noise. The default * value is 0. */ - JXL_ENC_OPTION_PHOTON_NOISE = 4, + JXL_ENC_FRAME_SETTING_PHOTON_NOISE = 5, /** Enables adaptive noise generation. This setting is not recommended for - * use, please use JXL_ENC_OPTION_PHOTON_NOISE instead. Use -1 for the default - * (encoder chooses), 0 to disable, 1 to enable. + * use, please use JXL_ENC_FRAME_SETTING_PHOTON_NOISE instead. Use -1 for the + * default (encoder chooses), 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_NOISE = 5, + JXL_ENC_FRAME_SETTING_NOISE = 6, /** Enables or disables dots generation. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_DOTS = 6, + JXL_ENC_FRAME_SETTING_DOTS = 7, /** Enables or disables patches generation. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_PATCHES = 7, + JXL_ENC_FRAME_SETTING_PATCHES = 8, /** Edge preserving filter level, -1 to 3. Use -1 for the default (encoder * chooses), 0 to 3 to set a strength. */ - JXL_ENC_OPTION_EPF = 8, + JXL_ENC_FRAME_SETTING_EPF = 9, /** Enables or disables the gaborish filter. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_GABORISH = 9, + JXL_ENC_FRAME_SETTING_GABORISH = 10, /** Enables modular encoding. Use -1 for default (encoder * chooses), 0 to enforce VarDCT mode (e.g. for photographic images), 1 to * enforce modular mode (e.g. for lossless images). */ - JXL_ENC_OPTION_MODULAR = 10, + JXL_ENC_FRAME_SETTING_MODULAR = 11, /** Enables or disables preserving color of invisible pixels. Use -1 for the * default (1 if lossless, 0 if lossy), 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_KEEP_INVISIBLE = 11, + JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE = 12, /** Determines the order in which 256x256 regions are stored in the codestream * for progressive rendering. Use -1 for the encoder * default, 0 for scanline order, 1 for center-first order. */ - JXL_ENC_OPTION_GROUP_ORDER = 12, + JXL_ENC_FRAME_SETTING_GROUP_ORDER = 13, /** Determines the horizontal position of center for the center-first group * order. Use -1 to automatically use the middle of the image, 0..xsize to * specifically set it. */ - JXL_ENC_OPTION_GROUP_ORDER_CENTER_X = 13, + JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X = 14, /** Determines the center for the center-first group order. Use -1 to * automatically use the middle of the image, 0..ysize to specifically set it. */ - JXL_ENC_OPTION_GROUP_ORDER_CENTER_Y = 14, + JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y = 15, /** Enables or disables progressive encoding for modular mode. Use -1 for the * encoder default, 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_RESPONSIVE = 15, + JXL_ENC_FRAME_SETTING_RESPONSIVE = 16, /** Set the progressive mode for the AC coefficients of VarDCT, using spectral * progression from the DCT coefficients. Use -1 for the encoder default, 0 to * disable, 1 to enable. */ - JXL_ENC_OPTION_PROGRESSIVE_AC = 16, + JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC = 17, /** Set the progressive mode for the AC coefficients of VarDCT, using * quantization of the least significant bits. Use -1 for the encoder default, * 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_QPROGRESSIVE_AC = 17, + JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC = 18, /** Set the progressive mode using lower-resolution DC images for VarDCT. Use * -1 for the encoder default, 0 to disable, 1 to have an extra 64x64 lower * resolution pass, 2 to have a 512x512 and 64x64 lower resolution pass. */ - JXL_ENC_OPTION_PROGRESSIVE_DC = 18, + JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC = 19, /** Use Global channel palette if the amount of colors is smaller than this * percentage of range. Use 0-100 to set an explicit percentage, -1 to use the * encoder default. Used for modular encoding. */ - JXL_ENC_OPTION_CHANNEL_COLORS_PRE_TRANSFORM_PERCENT = 19, + JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT = 20, - /** Use Local channel palette if the amount of colors is smaller than this - * percentage of range. Use 0-100 to set an explicit percentage, -1 to use the - * encoder default. Used for modular encoding. + /** Use Local (per-group) channel palette if the amount of colors is smaller + * than this percentage of range. Use 0-100 to set an explicit percentage, -1 + * to use the encoder default. Used for modular encoding. */ - JXL_ENC_OPTION_CHANNEL_COLORS_PERCENT = 20, + JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT = 21, /** Use color palette if amount of colors is smaller than or equal to this * amount, or -1 to use the encoder default. Used for modular encoding. */ - JXL_ENC_OPTION_PALETTE_COLORS = 21, + JXL_ENC_FRAME_SETTING_PALETTE_COLORS = 22, /** Enables or disables delta palette. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. Used in modular mode. */ - JXL_ENC_OPTION_LOSSY_PALETTE = 22, + JXL_ENC_FRAME_SETTING_LOSSY_PALETTE = 23, - /** Color space for modular encoding: 0=RGB, 1=YCoCg, 2-37=RCT, -1=default: - * try several, depending on speed. + /** Color transform for internal encoding: -1 = default, 0=XYB, 1=none (RGB), + * 2=YCbCr. The XYB setting performs the forward XYB transform. None and + * YCbCr both perform no transform, but YCbCr is used to indicate that the + * encoded data losslessly represents YCbCr values. + */ + JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM = 24, + + /** Reversible color transform for modular encoding: -1=default, 0-41=RCT + * index, e.g. index 0 = none, index 6 = YCoCg. + * If this option is set to a non-default value, the RCT will be globally + * applied to the whole frame. + * The default behavior is to try several RCTs locally per modular group, + * depending on the speed and distance setting. */ - JXL_ENC_OPTION_MODULAR_COLOR_SPACE = 23, + JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE = 25, /** Group size for modular encoding: -1=default, 0=128, 1=256, 2=512, 3=1024. */ - JXL_ENC_OPTION_MODULAR_GROUP_SIZE = 24, + JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE = 26, /** Predictor for modular encoding. -1 = default, 0=zero, 1=left, 2=top, * 3=avg0, 4=select, 5=gradient, 6=weighted, 7=topright, 8=topleft, * 9=leftleft, 10=avg1, 11=avg2, 12=avg3, 13=toptop predictive average 14=mix * 5 and 6, 15=mix everything. */ - JXL_ENC_OPTION_MODULAR_PREDICTOR = 25, + JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR = 27, + + /** Fraction of pixels used to learn MA trees as a percentage. -1 = default, + * 0 = no MA and fast decode, 50 = default value, 100 = all, values above + * 100 are also permitted. Higher values use more encoder memory. + */ + JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT = 28, + + /** Number of extra (previous-channel) MA tree properties to use. -1 = + * default, 0-11 = valid values. Recommended values are in the range 0 to 3, + * or 0 to amount of channels minus 1 (including all extra channels, and + * excluding color channels when using VarDCT mode). Higher value gives slower + * encoding and slower decoding. + */ + JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS = 29, + + /** Enable or disable CFL (chroma-from-luma) for lossless JPEG recompression. + * -1 = default, 0 = disable CFL, 1 = enable CFL. + */ + JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL = 30, + + /** Prepare the frame for indexing in the frame index box. + * 0 = ignore this frame (same as not setting a value), + * 1 = index this frame within the Frame Index Box. + * If any frames are indexed, the first frame needs to + * be indexed, too. If the first frame is not indexed, and + * a later frame is attempted to be indexed, JXL_ENC_ERROR will occur. + * If non-keyframes, i.e., frames with cropping, blending or patches are + * attempted to be indexed, JXL_ENC_ERROR will occur. + */ + JXL_ENC_FRAME_INDEX_BOX = 31, + + /** Sets brotli encode effort for use in JPEG recompression and compressed + * metadata boxes (brob). Can be -1 (default) or 0 (fastest) to 11 (slowest). + * Default is based on the general encode effort in case of JPEG + * recompression, and 4 for brob boxes. + */ + JXL_ENC_FRAME_SETTING_BROTLI_EFFORT = 32, + + /** Enables or disables brotli compression of metadata boxes derived from + * a JPEG frame when using JxlEncoderAddJPEGFrame. This has no effect on boxes + * added using JxlEncoderAddBox. + * -1 = default, 0 = disable compression, 1 = enable compression. + */ + JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES = 33, /** Enum value not to be used as an option. This value is added to force the * C compiler to have the enum to take a known size. */ - JXL_ENC_OPTION_FILL_ENUM = 65535, + JXL_ENC_FRAME_SETTING_FILL_ENUM = 65535, -} JxlEncoderOptionId; +} JxlEncoderFrameSettingId; /** * Creates an instance of JxlEncoder and initializes it. @@ -269,6 +383,17 @@ JXL_EXPORT void JxlEncoderReset(JxlEncoder* enc); */ JXL_EXPORT void JxlEncoderDestroy(JxlEncoder* enc); +/** + * Sets the color management system (CMS) that will be used for color conversion + * (if applicable) during encoding. May only be set before starting encoding. If + * left unset, the default CMS implementation will be used. + * + * @param enc encoder object. + * @param cms structure representing a CMS implementation. See JxlCmsInterface + * for more details. + */ +JXL_EXPORT void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms); + /** * Set the parallel runner for multithreading. May only be set before starting * encoding. @@ -285,6 +410,15 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner, void* parallel_runner_opaque); +/** + * Get the (last) error code in case JXL_ENC_ERROR was returned. + * + * @param enc encoder object. + * @return the JxlEncoderError that caused the (last) JXL_ENC_ERROR to be + * returned. + */ +JXL_EXPORT JxlEncoderError JxlEncoderGetError(JxlEncoder* enc); + /** * Encodes JPEG XL file using the available bytes. @p *avail_out indicates how * many output bytes are available, and @p *next_out points to the input bytes. @@ -297,6 +431,17 @@ JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner, * When the return value is not JXL_ENC_ERROR or JXL_ENC_SUCCESS, the encoding * requires more JxlEncoderProcessOutput calls to continue. * + * The caller must guarantee that *avail_out >= 32 when calling + * JxlEncoderProcessOutput; otherwise, JXL_ENC_NEED_MORE_OUTPUT will be + * returned. It is guaranteed that, if *avail_out >= 32, at least one byte of + * output will be written. + * + * This encodes the frames and/or boxes added so far. If the last frame or last + * box has been added, @ref JxlEncoderCloseInput, @ref JxlEncoderCloseFrames + * and/or @ref JxlEncoderCloseBoxes must be called before the next + * @ref JxlEncoderProcessOutput call, or the codestream won't be encoded + * correctly. + * * @param enc encoder object. * @param next_out pointer to next bytes to write to. * @param avail_out amount of bytes available starting from *next_out. @@ -308,6 +453,96 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out, size_t* avail_out); +/** + * Sets the frame information for this frame to the encoder. This includes + * animation information such as frame duration to store in the frame header. + * The frame header fields represent the frame as passed to the encoder, but not + * necessarily the exact values as they will be encoded file format: the encoder + * could change crop and blending options of a frame for more efficient encoding + * or introduce additional internal frames. Animation duration and time code + * information is not altered since those are immutable metadata of the frame. + * + * It is not required to use this function, however if have_animation is set + * to true in the basic info, then this function should be used to set the + * time duration of this individual frame. By default individual frames have a + * time duration of 0, making them form a composite still. See @ref + * JxlFrameHeader for more information. + * + * This information is stored in the JxlEncoderFrameSettings and so is used for + * any frame encoded with these JxlEncoderFrameSettings. It is ok to change + * between @ref JxlEncoderAddImageFrame calls, each added image frame will have + * the frame header that was set in the options at the time of calling + * JxlEncoderAddImageFrame. + * + * The is_last and name_length fields of the JxlFrameHeader are ignored, use + * @ref JxlEncoderCloseFrames to indicate last frame, and @ref + * JxlEncoderSetFrameName to indicate the name and its length instead. + * Calling this function will clear any name that was previously set with @ref + * JxlEncoderSetFrameName. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param frame_header frame header data to set. Object owned by the caller and + * does not need to be kept in memory, its information is copied internally. + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + */ +JXL_EXPORT JxlEncoderStatus +JxlEncoderSetFrameHeader(JxlEncoderFrameSettings* frame_settings, + const JxlFrameHeader* frame_header); + +/** + * Sets blend info of an extra channel. The blend info of extra channels is set + * separately from that of the color channels, the color channels are set with + * @ref JxlEncoderSetFrameHeader. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param index index of the extra channel to use. + * @param blend_info blend info to set for the extra channel + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo( + JxlEncoderFrameSettings* frame_settings, size_t index, + const JxlBlendInfo* blend_info); + +/** + * Sets the name of the animation frame. This function is optional, frames are + * not required to have a name. This setting is a part of the frame header, and + * the same principles as for @ref JxlEncoderSetFrameHeader apply. The + * name_length field of JxlFrameHeader is ignored by the encoder, this function + * determines the name length instead as the length in bytes of the C string. + * + * The maximum possible name length is 1071 bytes (excluding terminating null + * character). + * + * Calling @ref JxlEncoderSetFrameHeader clears any name that was + * previously set. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param frame_name name of the next frame to be encoded, as a UTF-8 encoded C + * string (zero terminated). Owned by the caller, and copied internally. + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameName( + JxlEncoderFrameSettings* frame_settings, const char* frame_name); + +/** + * Sets the bit depth of the input buffer. + * + * For float pixel formats, only the default JXL_BIT_DEPTH_FROM_PIXEL_FORMAT + * setting is allowed, while for unsigned pixel formats, + * JXL_BIT_DEPTH_FROM_CODESTREAM setting is also allowed. See the comment on + * @ref JxlEncoderAddImageFrame for the effects of the bit depth setting. + + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param bit_depth the bit depth setting of the pixel input + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameBitDepth( + JxlEncoderFrameSettings* frame_settings, const JxlBitDepth* bit_depth); + /** * Sets the buffer to read JPEG encoded bytes from for the next frame to encode. * @@ -323,25 +558,49 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, * JxlEncoderStoreJPEGMetadata and a single JPEG frame is added, it will be * possible to losslessly reconstruct the JPEG codestream. * - * @param options set of encoder options to use when encoding the frame. + * If this is the last frame, @ref JxlEncoderCloseInput or @ref + * JxlEncoderCloseFrames must be called before the next + * @ref JxlEncoderProcessOutput call. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param buffer bytes to read JPEG from. Owned by the caller and its contents * are copied internally. * @param size size of buffer in bytes. * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ -JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame( - const JxlEncoderOptions* options, const uint8_t* buffer, size_t size); +JXL_EXPORT JxlEncoderStatus +JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings, + const uint8_t* buffer, size_t size); /** * Sets the buffer to read pixels from for the next image to encode. Must call * JxlEncoderSetBasicInfo before JxlEncoderAddImageFrame. * * Currently only some data types for pixel formats are supported: - * - JXL_TYPE_UINT8 - * - JXL_TYPE_UINT16 + * - JXL_TYPE_UINT8, with range 0..255 + * - JXL_TYPE_UINT16, with range 0..65535 * - JXL_TYPE_FLOAT16, with nominal range 0..1 * - JXL_TYPE_FLOAT, with nominal range 0..1 * + * Note: the sample data type in pixel_format is allowed to be different from + * what is described in the JxlBasicInfo. The type in pixel_format, together + * with an optional @ref JxlBitDepth parameter set by @ref + * JxlEncoderSetFrameBitDepth describes the format of the uncompressed pixel + * buffer. The bits_per_sample and exponent_bits_per_sample in the JxlBasicInfo + * describes what will actually be encoded in the JPEG XL codestream. + * For example, to encode a 12-bit image, you would set bits_per_sample to 12, + * while the input frame buffer can be in the following formats: + * - if pixel format is in JXL_TYPE_UINT16 with default bit depth setting + * (i.e. JXL_BIT_DEPTH_FROM_PIXEL_FORMAT), input sample values are rescaled + * to 16-bit, i.e. multiplied by 65535/4095; + * - if pixel format is in JXL_TYPE_UINT16 with JXL_BIT_DEPTH_FROM_CODESTREAM + * bit depth setting, input sample values are provided unscaled; + * - if pixel format is in JXL_TYPE_FLOAT, input sample values are rescaled + * to 0..1, i.e. multiplied by 1.f/4095.f. + * While it is allowed, it is obviously not recommended to use a pixel_format + * with lower precision than what is specified in the JxlBasicInfo. + * * We support interleaved channels as described by the JxlPixelFormat: * - single-channel data, e.g. grayscale * - single-channel + alpha @@ -350,25 +609,37 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame( * * Extra channels not handled here need to be set by @ref * JxlEncoderSetExtraChannelBuffer. - * - * The color profile of the pixels depends on the value of uses_original_profile - * in the JxlBasicInfo. If true, the pixels are assumed to be encoded in the - * original profile that is set with JxlEncoderSetColorEncoding or - * JxlEncoderSetICCProfile. If false, the pixels are assumed to be nonlinear - * sRGB for integer data types (JXL_TYPE_UINT8, JXL_TYPE_UINT16), and linear - * sRGB for floating point data types (JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT). - * - * @param options set of encoder options to use when encoding the frame. + * If the image has alpha, and alpha is not passed here, it will implicitly be + * set to all-opaque (an alpha value of 1.0 everywhere). + * + * The pixels are assumed to be encoded in the original profile that is set with + * JxlEncoderSetColorEncoding or JxlEncoderSetICCProfile. If none of these + * functions were used, the pixels are assumed to be nonlinear sRGB for integer + * data types (JXL_TYPE_UINT8, JXL_TYPE_UINT16), and linear sRGB for floating + * point data types (JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT). + * + * Sample values in floating-point pixel formats are allowed to be outside the + * nominal range, e.g. to represent out-of-sRGB-gamut colors in the + * uses_original_profile=false case. They are however not allowed to be NaN or + * +-infinity. + * + * If this is the last frame, @ref JxlEncoderCloseInput or @ref + * JxlEncoderCloseFrames must be called before the next + * @ref JxlEncoderProcessOutput call. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param pixel_format format for pixels. Object owned by the caller and its * contents are copied internally. * @param buffer buffer type to input the pixel data from. Owned by the caller * and its contents are copied internally. - * @param size size of buffer in bytes. + * @param size size of buffer in bytes. This size should match what is implied + * by the frame dimensions and the pixel format. * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame( - const JxlEncoderOptions* options, const JxlPixelFormat* pixel_format, - const void* buffer, size_t size); + const JxlEncoderFrameSettings* frame_settings, + const JxlPixelFormat* pixel_format, const void* buffer, size_t size); /** * Sets the buffer to read pixels from for an extra channel at a given index. @@ -381,28 +652,29 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame( * It is required to call this function for every extra channel, except for the * alpha channel if that was already set through @ref JxlEncoderAddImageFrame. * - * @param options set of encoder options to use when encoding the extra channel. + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param pixel_format format for pixels. Object owned by the caller and its * contents are copied internally. The num_channels value is ignored, since the * number of channels for an extra channel is always assumed to be one. * @param buffer buffer type to input the pixel data from. Owned by the caller * and its contents are copied internally. - * @param size size of buffer in bytes. + * @param size size of buffer in bytes. This size should match what is implied + * by the frame dimensions and the pixel format. * @param index index of the extra channel to use. * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( - const JxlEncoderOptions* options, const JxlPixelFormat* pixel_format, - const void* buffer, size_t size, uint32_t index); + const JxlEncoderFrameSettings* frame_settings, + const JxlPixelFormat* pixel_format, const void* buffer, size_t size, + uint32_t index); /** Adds a metadata box to the file format. JxlEncoderProcessOutput must be used * to effectively write the box to the output. @ref JxlEncoderUseBoxes must * be enabled before using this function. * - * Background information about the container format and boxes follows here: - * - * For users of libjxl, boxes allow inserting application-specific data and - * metadata (Exif, XML, JUMBF and user defined boxes). + * Boxes allow inserting application-specific data and metadata (Exif, XML/XMP, + * JUMBF and user defined boxes). * * The box format follows ISO BMFF and shares features and box types with other * image and video formats, including the Exif, XML and JUMBF boxes. The box @@ -410,7 +682,7 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( * * Boxes in general don't contain other boxes inside, except a JUMBF superbox. * Boxes follow each other sequentially and are byte-aligned. If the container - * format is used, the JXL stream exists out of 3 or more concatenated boxes. + * format is used, the JXL stream consists of concatenated boxes. * It is also possible to use a direct codestream without boxes, but in that * case metadata cannot be added. * @@ -422,86 +694,42 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( * - N bytes: box contents. * * Only the box contents are provided to the contents argument of this function, - * the encoder encodes the size header itself. - * - * Box types are given by 4 characters. A list of known types follows: - * - "JXL ": mandatory signature box, must come first, 12 bytes long including - * the box header - * - "ftyp": a second mandatory signature box, must come second, 20 bytes long - * including the box header - * - "jxll": A JXL level box. This indicates if the codestream is level 5 or - * level 10 compatible. If not present, it is level 5. Level 10 allows more - * features such as very high image resolution and bit-depths above 16 bits - * per channel. Added automatically by the encoder when - * JxlEncoderSetCodestreamLevel is used - * - "jxlc": a box with the image codestream, in case the codestream is not - * split across multiple boxes. The codestream contains the JPEG XL image - * itself, including the basic info such as image dimensions, ICC color - * profile, and all the pixel data of all the image frames. - * - "jxlp": a codestream box in case it is split across multiple boxes. The - * encoder will automatically do this if necessary. The contents are the same - * as in case of a jxlc box, when concatenated. + * the encoder encodes the size header itself. Most boxes are written + * automatically by the encoder as needed ("JXL ", "ftyp", "jxll", "jxlc", + * "jxlp", "jxli", "jbrd"), and this function only needs to be called to add + * optional metadata when encoding from pixels (using JxlEncoderAddImageFrame). + * When recompressing JPEG files (using JxlEncoderAddJPEGFrame), if the input + * JPEG contains EXIF, XMP or JUMBF metadata, the corresponding boxes are + * already added automatically. + * + * Box types are given by 4 characters. The following boxes can be added with + * this function: * - "Exif": a box with EXIF metadata, can be added by libjxl users, or is * automatically added when needed for JPEG reconstruction. The contents of * this box must be prepended by a 4-byte tiff header offset, which may - * be 4 zero bytes. - * - "XML ": a box with XMP or IPTC metadata, can be added by libjxl users, or - * is automatically added when needed for JPEG reconstruction + * be 4 zero bytes in case the tiff header follows immediately. + * The EXIF metadata must be in sync with what is encoded in the JPEG XL + * codestream, specifically the image orientation. While this is not + * recommended in practice, in case of conflicting metadata, the JPEG XL + * codestream takes precedence. + * - "xml ": a box with XML data, in particular XMP metadata, can be added by + * libjxl users, or is automatically added when needed for JPEG reconstruction * - "jumb": a JUMBF superbox, which can contain boxes with different types of * metadata inside. This box type can be added by the encoder transparently, * and other libraries to create and handle JUMBF content exist. - * - "brob": a Brotli-compressed box, which otherwise represents an existing - * type of box such as Exif or XML. The encoder creates these when enabled and - * users of libjxl don't need to create them directly. Some box types are not - * allowed to be compressed: any of the signature, jxl* and jbrd boxes. - * - "jxli": frame index box, can list the keyframes in case of a JXL animation, - * allowing the decoder to jump to individual frames more efficiently. This - * box type is specified, but not currently supported by the encoder or - * decoder. - * - "jbrd": JPEG reconstruction box, contains the information required to - * byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG coefficients - * (pixel content) themselves are encoded in the JXL codestream (jxlc or jxlp) - * itself. Exif and XMP metadata will be encoded in Exif and XMP boxes. The - * jbrd box itself contains information such as the app markers of the JPEG-1 - * file and everything else required to fit the information together into the - * exact original JPEG file. This box is added automatically by the encoder - * when needed, and only when JPEG reconstruction is used. - * - other: other application-specific boxes can be added. Their typename should - * not begin with "jxl" or "JXL" or conflict with other existing typenames. - * - * Most boxes are automatically added by the encoder and should not be added - * with JxlEncoderAddBox. Boxes that one may wish to add with JxlEncoderAddBox - * are: Exif and XML (but not when using JPEG reconstruction since if the - * JPEG has those, these boxes are already added automatically), jumb, and - * application-specific boxes. - * - * Adding metadata boxes increases the filesize. When adding Exif metadata, the - * data must be in sync with what is encoded in the JPEG XL codestream, - * specifically the image orientation. While this is not recommended in - * practice, in case of conflicting metadata, the JPEG XL codestream takes - * precedence. - * - * It is possible to create a codestream without boxes, then what would be in - * the jxlc box is written directly to the output - * - * It is possible to split the codestream across multiple boxes, in that case - * multiple boxes of type jxlp are used. This is handled by the encoder when - * needed. - * - * For now metadata boxes can only be added before or after the codestream with - * all frames, so using JxlEncoderAddBox is only possible before the first - * JxlEncoderAddImageFrame call, and/or after the last JxlEncoderAddImageFrame - * call and JxlEncoderCloseFrames. Support for adding boxes in-between the - * codestream, and/or in-between image frames may be added later, and would - * cause the encoder to use jxlp boxes for the codestream. + * - Application-specific boxes. Their typename should not begin with "jxl" or + * "JXL" or conflict with other existing typenames, and they should be + * registered with MP4RA (mp4ra.org). + * + * These boxes can be stored uncompressed or Brotli-compressed (using a "brob" + * box), depending on the compress_box parameter. * * @param enc encoder object. - * @param type the box type, e.g. "Exif" for EXIF metadata, "XML " for XMP or + * @param type the box type, e.g. "Exif" for EXIF metadata, "xml " for XMP or * IPTC metadata, "jumb" for JUMBF metadata. * @param contents the full contents of the box, for example EXIF - * data. For an "Exif" box, the EXIF data must be prepended by a 4-byte tiff - * header offset, which may be 4 zero-bytes. The ISO BMFF box header must not - * be included, only the contents. + * data. ISO BMFF box header must not be included, only the contents. Owned by + * the caller and its contents are copied internally. * @param size size of the box contents. * @param compress_box Whether to compress this box as a "brob" box. Requires * Brotli support. @@ -509,7 +737,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( * using this function without JxlEncoderUseContainer, or adding a box type * that would result in an invalid file format. */ -JXL_EXPORT JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, JxlBoxType type, +JXL_EXPORT JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, + const JxlBoxType type, const uint8_t* contents, size_t size, JXL_BOOL compress_box); @@ -531,18 +760,25 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc); * Declares that no further boxes will be added with @ref JxlEncoderAddBox. * This function must be called after the last box is added so the encoder knows * the stream will be finished. It is not necessary to use this function if - * @ref JxlEncoderUseBoxes is not used. + * @ref JxlEncoderUseBoxes is not used. Further frames may still be added. + * + * Must be called between JxlEncoderAddBox of the last box + * and the next call to JxlEncoderProcessOutput, or @ref JxlEncoderProcessOutput + * won't output the last box correctly. * * NOTE: if you don't need to close frames and boxes at separate times, you can * use @ref JxlEncoderCloseInput instead to close both at once. * * @param enc encoder object. */ -JXL_EXPORT JxlEncoderStatus JxlEncoderCloseBoxes(JxlEncoder* enc); +JXL_EXPORT void JxlEncoderCloseBoxes(JxlEncoder* enc); /** - * Declares that this encoder will not encode any further frames. Further - * metadata boxes may still be added. + * Declares that no frames will be added and @ref JxlEncoderAddImageFrame and + * @ref JxlEncoderAddJPEGFrame won't be called anymore. Further metadata boxes + * may still be added. This function or @ref JxlEncoderCloseInput must be called + * after adding the last frame and the next call to + * @ref JxlEncoderProcessOutput, or the frame won't be properly marked as last. * * NOTE: if you don't need to close frames and boxes at separate times, you can * use @ref JxlEncoderCloseInput instead to close both at once. @@ -558,7 +794,10 @@ JXL_EXPORT void JxlEncoderCloseFrames(JxlEncoder* enc); * calls should be done to create the final output. * * The requirements of both @ref JxlEncoderCloseFrames and @ref - * JxlEncoderCloseBoxes apply to this function. + * JxlEncoderCloseBoxes apply to this function. Either this function or the + * other two must be called after the final frame and/or box, and the next + * @ref JxlEncoderProcessOutput call, or the codestream won't be encoded + * correctly. * * @param enc encoder object. */ @@ -569,6 +808,7 @@ JXL_EXPORT void JxlEncoderCloseInput(JxlEncoder* enc); * is an alternative to JxlEncoderSetICCProfile and only one of these two must * be used. This one sets the color encoding as a @ref JxlColorEncoding, while * the other sets it as ICC binary data. + * Must be called after JxlEncoderSetBasicInfo. * * @param enc encoder object. * @param color color encoding. Object owned by the caller and its contents are @@ -584,6 +824,7 @@ JxlEncoderSetColorEncoding(JxlEncoder* enc, const JxlColorEncoding* color); * ICC color profile. This is an alternative to JxlEncoderSetColorEncoding and * only one of these two must be used. This one sets the color encoding as ICC * binary data, while the other defines it as a @ref JxlColorEncoding. + * Must be called after JxlEncoderSetBasicInfo. * * @param enc encoder object. * @param icc_profile bytes of the original ICC profile @@ -606,12 +847,35 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc, */ JXL_EXPORT void JxlEncoderInitBasicInfo(JxlBasicInfo* info); +/** + * Initializes a JxlFrameHeader struct to default values. + * For forwards-compatibility, this function has to be called before values + * are assigned to the struct fields. + * The default values correspond to a frame with no animation duration and the + * 'replace' blend mode. After using this function, For animation duration must + * be set, for composite still blend settings must be set. + * + * @param frame_header frame metadata. Object owned by the caller. + */ +JXL_EXPORT void JxlEncoderInitFrameHeader(JxlFrameHeader* frame_header); + +/** + * Initializes a JxlBlendInfo struct to default values. + * For forwards-compatibility, this function has to be called before values + * are assigned to the struct fields. + * + * @param blend_info blending info. Object owned by the caller. + */ +JXL_EXPORT void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info); + /** * Sets the global metadata of the image encoded by this encoder. * * If the JxlBasicInfo contains information of extra channels beyond an alpha * channel, then @ref JxlEncoderSetExtraChannelInfo must be called between - * JxlEncoderSetBasicInfo and @ref JxlEncoderAddImageFrame. + * JxlEncoderSetBasicInfo and @ref JxlEncoderAddImageFrame. In order to indicate + * extra channels, the value of `info.num_extra_channels` should be set to the + * number of extra channels, also counting the alpha channel if present. * * @param enc encoder object. * @param info global image metadata. Object owned by the caller and its @@ -631,7 +895,6 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc, * @param type type of the extra channel. * @param info global extra channel metadata. Object owned by the caller and its * contents are copied internally. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ JXL_EXPORT void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type, JxlExtraChannelInfo* info); @@ -653,6 +916,9 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo( * Sets the name for the extra channel at the given index in UTF-8. The index * must be smaller than the num_extra_channels in the associated JxlBasicInfo. * + * TODO(lode): remove size parameter for consistency with + * JxlEncoderSetFrameName + * * @param enc encoder object * @param index index of the extra channel to set. * @param name buffer with the name of the extra channel. @@ -667,19 +933,39 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc, /** * Sets a frame-specific option of integer type to the encoder options. - * The JxlEncoderOptionId argument determines which option is set. + * The JxlEncoderFrameSettingId argument determines which option is set. * - * @param options set of encoder options to update with the new mode. + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param option ID of the option to set. * @param value Integer value to set for this option. * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR in * case of an error, such as invalid or unknown option id, or invalid integer * value for the given option. If an error is returned, the state of the - * JxlEncoderOptions object is still valid and is the same as before this + * JxlEncoderFrameSettings object is still valid and is the same as before this + * function was called. + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetOption( + JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option, + int64_t value); + +/** + * Sets a frame-specific option of float type to the encoder options. + * The JxlEncoderFrameSettingId argument determines which option is set. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param option ID of the option to set. + * @param value Float value to set for this option. + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR in + * case of an error, such as invalid or unknown option id, or invalid integer + * value for the given option. If an error is returned, the state of the + * JxlEncoderFrameSettings object is still valid and is the same as before this * function was called. */ -JXL_EXPORT JxlEncoderStatus JxlEncoderOptionsSetInteger( - JxlEncoderOptions* options, JxlEncoderOptionId option, int32_t value); +JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption( + JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option, + float value); /** Forces the encoder to use the box-based container format (BMFF) even * when not necessary. @@ -720,7 +1006,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata); /** Sets the feature level of the JPEG XL codestream. Valid values are 5 and - * 10. + * 10, or -1 (to choose automatically). Using the minimum required level, or + * level 5 in most cases, is recommended for compatibility with all decoders. * * Level 5: for end-user image delivery, this level is the most widely * supported level by image decoders and the recommended level to use unless a @@ -736,77 +1023,119 @@ JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata); * 5 limitations, allows CMYK color and up to 32 bits per color channel, but * may be less widely supported. * - * The default value is 5. To use level 10 features, the setting must be - * explicitly set to 10, the encoder will not automatically enable it. If - * incompatible parameters such as too high image resolution for the current - * level are set, the encoder will return an error. For internal coding tools, - * the encoder will only use those compatible with the level setting. + * The default value is -1. This means the encoder will automatically choose + * between level 5 and level 10 based on what information is inside the @ref + * JxlBasicInfo structure. Do note that some level 10 features, particularly + * those used by animated JPEG XL codestreams, might require level 10, even + * though the @ref JxlBasicInfo only suggests level 5. In this case, the level + * must be explicitly set to 10, otherwise the encoder will return an error. + * The encoder will restrict internal encoding choices to those compatible with + * the level setting. * * This setting can only be set at the beginning, before encoding starts. + * + * @param enc encoder object. + * @param level the level value to set, must be -1, 5, or 10. + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR + * otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, int level); +/** Returns the codestream level required to support the currently configured + * settings and basic info. This function can only be used at the beginning, + * before encoding starts, but after setting basic info. + * + * This does not support per-frame settings, only global configuration, such as + * the image dimensions, that are known at the time of writing the header of + * the JPEG XL file. + * + * If this returns 5, nothing needs to be done and the codestream can be + * compatible with any decoder. If this returns 10, JxlEncoderSetCodestreamLevel + * has to be used to set the codestream level to 10, or the encoder can be + * configured differently to allow using the more compatible level 5. + * + * @param enc encoder object. + * @return -1 if no level can support the configuration (e.g. image dimensions + * larger than even level 10 supports), 5 if level 5 is supported, 10 if setting + * the codestream level to 10 is required. + * + */ +JXL_EXPORT int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc); + /** * Enables lossless encoding. * * This is not an option like the others on itself, but rather while enabled it - * overrides a set of existing options (such as distance and modular mode) that - * enables bit-for-bit lossless encoding. + * overrides a set of existing options (such as distance, modular mode and + * color transform) that enables bit-for-bit lossless encoding. * * When disabled, those options are not overridden, but since those options * could still have been manually set to a combination that operates losslessly, * using this function with lossless set to JXL_DEC_FALSE does not guarantee * lossy encoding, though the default set of options is lossy. * - * @param options set of encoder options to update with the new mode + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param lossless whether to override options for lossless mode * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR * otherwise. */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameLossless( + JxlEncoderFrameSettings* frame_settings, JXL_BOOL lossless); + +/** DEPRECATED: use JxlEncoderSetFrameLossless instead. + */ JXL_EXPORT JxlEncoderStatus -JxlEncoderOptionsSetLossless(JxlEncoderOptions* options, JXL_BOOL lossless); +JxlEncoderOptionsSetLossless(JxlEncoderFrameSettings*, JXL_BOOL); /** - * @param options set of encoder options to update with the new mode. + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param effort the effort value to set. * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR * otherwise. * - * DEPRECATED: use JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_EFFORT, - * effort)) instead. + * DEPRECATED: use JxlEncoderFrameSettingsSetOption(frame_settings, + * JXL_ENC_FRAME_SETTING_EFFORT, effort) instead. */ -JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus -JxlEncoderOptionsSetEffort(JxlEncoderOptions* options, int effort); +JXL_DEPRECATED JXL_EXPORT JxlEncoderStatus +JxlEncoderOptionsSetEffort(JxlEncoderFrameSettings* frame_settings, int effort); /** - * @param options set of encoder options to update with the new decoding speed - * tier. + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param tier the decoding speed tier to set. * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR * otherwise. * - * DEPRECATED: use JxlEncoderOptionsSetInteger(options, - * JXL_ENC_OPTION_DECODING_SPEED, tier)) instead. + * DEPRECATED: use JxlEncoderFrameSettingsSetOption(frame_settings, + * JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier) instead. */ -JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus -JxlEncoderOptionsSetDecodingSpeed(JxlEncoderOptions* options, int tier); +JXL_DEPRECATED JXL_EXPORT JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed( + JxlEncoderFrameSettings* frame_settings, int tier); /** * Sets the distance level for lossy compression: target max butteraugli * distance, lower = higher quality. Range: 0 .. 15. - * 0.0 = mathematically lossless (however, use JxlEncoderOptionsSetLossless + * 0.0 = mathematically lossless (however, use JxlEncoderSetFrameLossless * instead to use true lossless, as setting distance to 0 alone is not the only * requirement). 1.0 = visually lossless. Recommended range: 0.5 .. 3.0. Default * value: 1.0. * - * @param options set of encoder options to update with the new mode. + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. * @param distance the distance value to set. * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR * otherwise. */ -JXL_EXPORT JxlEncoderStatus -JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, float distance); +JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameDistance( + JxlEncoderFrameSettings* frame_settings, float distance); + +/** DEPRECATED: use JxlEncoderSetFrameDistance instead. + */ +JXL_DEPRECATED JXL_EXPORT JxlEncoderStatus +JxlEncoderOptionsSetDistance(JxlEncoderFrameSettings*, float); /** * Create a new set of encoder options, with all values initially copied from @@ -814,17 +1143,22 @@ JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, float distance); * * The returned pointer is an opaque struct tied to the encoder and it will be * deallocated by the encoder when JxlEncoderDestroy() is called. For functions - * taking both a @ref JxlEncoder and a @ref JxlEncoderOptions, only - * JxlEncoderOptions created with this function for the same encoder instance - * can be used. + * taking both a @ref JxlEncoder and a @ref JxlEncoderFrameSettings, only + * JxlEncoderFrameSettings created with this function for the same encoder + * instance can be used. * * @param enc encoder object. * @param source source options to copy initial values from, or NULL to get * defaults initialized to defaults. * @return the opaque struct pointer identifying a new set of encoder options. */ -JXL_EXPORT JxlEncoderOptions* JxlEncoderOptionsCreate( - JxlEncoder* enc, const JxlEncoderOptions* source); +JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate( + JxlEncoder* enc, const JxlEncoderFrameSettings* source); + +/** DEPRECATED: use JxlEncoderFrameSettingsCreate instead. + */ +JXL_DEPRECATED JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderOptionsCreate( + JxlEncoder*, const JxlEncoderFrameSettings*); /** * Sets a color encoding to be sRGB. @@ -844,6 +1178,16 @@ JXL_EXPORT void JxlColorEncodingSetToSRGB(JxlColorEncoding* color_encoding, JXL_EXPORT void JxlColorEncodingSetToLinearSRGB( JxlColorEncoding* color_encoding, JXL_BOOL is_gray); +/** + * Enables usage of expert options. + * + * At the moment, the only expert option is setting an effort value of 10, + * which gives the best compression for pixel-lossless modes but is very slow. + * + * @param enc encoder object. + */ +JXL_EXPORT void JxlEncoderAllowExpertOptions(JxlEncoder* enc); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/jpeg-xl/include/jxl/jxl_threads_export.h b/jpeg-xl/include/jxl/jxl_threads_export.h index d385f7b..1ad567b 100644 --- a/jpeg-xl/include/jxl/jxl_threads_export.h +++ b/jpeg-xl/include/jxl/jxl_threads_export.h @@ -9,15 +9,15 @@ # ifndef JXL_THREADS_EXPORT # ifdef JXL_THREADS_INTERNAL_LIBRARY_BUILD /* We are building this library */ -# define JXL_THREADS_EXPORT __attribute__((visibility("default"))) +# define JXL_THREADS_EXPORT # else /* We are using this library */ -# define JXL_THREADS_EXPORT __attribute__((visibility("default"))) +# define JXL_THREADS_EXPORT # endif # endif # ifndef JXL_THREADS_NO_EXPORT -# define JXL_THREADS_NO_EXPORT __attribute__((visibility("hidden"))) +# define JXL_THREADS_NO_EXPORT # endif #endif diff --git a/jpeg-xl/include/jxl/types.h b/jpeg-xl/include/jxl/types.h index d0cdaca..9b5908e 100644 --- a/jpeg-xl/include/jxl/types.h +++ b/jpeg-xl/include/jxl/types.h @@ -16,6 +16,8 @@ #include #include +#include "jxl/jxl_export.h" + #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif @@ -41,28 +43,26 @@ typedef enum { * for HDR and wide gamut images when color profile conversion is required. */ JXL_TYPE_FLOAT = 0, - /** Use 1-bit packed in uint8_t, first pixel in LSB, padded to uint8_t per - * row. - * TODO(lode): support first in MSB, other padding. - */ - JXL_TYPE_BOOLEAN, - /** Use type uint8_t. May clip wide color gamut data. */ - JXL_TYPE_UINT8, + JXL_TYPE_UINT8 = 2, /** Use type uint16_t. May clip wide color gamut data. */ - JXL_TYPE_UINT16, - - /** Use type uint32_t. May clip wide color gamut data. - */ - JXL_TYPE_UINT32, + JXL_TYPE_UINT16 = 3, /** Use 16-bit IEEE 754 half-precision floating point values */ - JXL_TYPE_FLOAT16, + JXL_TYPE_FLOAT16 = 5, } JxlDataType; +/* DEPRECATED: bit-packed 1-bit data type. Use JXL_TYPE_UINT8 instead. + */ +JXL_DEPRECATED static const int JXL_TYPE_BOOLEAN = 1; + +/* DEPRECATED: uint32_t data type. Use JXL_TYPE_FLOAT instead. + */ +JXL_DEPRECATED static const int JXL_TYPE_UINT32 = 4; + /** Ordering of multi-byte data. */ typedef enum { @@ -81,7 +81,6 @@ typedef enum { * for pixels. This is not necessarily the same as the data type encoded in the * codestream. The channels are interleaved per pixel. The pixels are * organized row by row, left to right, top to bottom. - * TODO(lode): implement padding / alignment (row stride) * TODO(lode): support different channel orders if needed (RGB, BGR, ...) */ typedef struct { @@ -111,10 +110,74 @@ typedef struct { size_t align; } JxlPixelFormat; +/** Settings for the interpretation of the input and output buffers. + */ +typedef enum { + /** This is the default setting, where the encoder expects the input pixels + * to use the full range of the pixel format data type (e.g. for UINT16, the + * input range is 0 .. 65535 and the value 65535 is mapped to 1.0 when + * converting to float), and the decoder uses the full range to output + * pixels. If the bit depth in the basic info is different from this, the + * encoder expects the values to be rescaled accordingly (e.g multiplied by + * 65535/4095 for a 12-bit image using UINT16 input data type). */ + JXL_BIT_DEPTH_FROM_PIXEL_FORMAT = 0, + + /** If this setting is selected, the encoder expects the input pixels to be + * in the range defined by the bits_per_sample value of the basic info (e.g. + * for 12-bit images using UINT16 input data types, the allowed range is + * 0 .. 4095 and the value 4095 is mapped to 1.0 when converting to float), + * and the decoder outputs pixels in this range. */ + JXL_BIT_DEPTH_FROM_CODESTREAM = 1, + + /** This setting can only be used in the decoder to select a custom range for + * pixel output */ + JXL_BIT_DEPTH_CUSTOM = 2, +} JxlBitDepthType; + +/** Data type for describing the interpretation of the input and output buffers + * in terms of the range of allowed input and output pixel values. */ +typedef struct { + /** Bit depth setting, see comment on @ref JxlBitDepthType */ + JxlBitDepthType type; + + /** Custom bits per sample */ + uint32_t bits_per_sample; + + /** Custom exponent bits per sample */ + uint32_t exponent_bits_per_sample; +} JxlBitDepth; + /** Data type holding the 4-character type name of an ISOBMFF box. */ typedef char JxlBoxType[4]; +/** Types of progressive detail. + * Setting a progressive detail with value N implies all progressive details + * with smaller or equal value. Currently only the following level of + * progressive detail is implemented: + * - kDC (which implies kFrames) + * - kLastPasses (which implies kDC and kFrames) + * - kPasses (which implies kLastPasses, kDC and kFrames) + */ +typedef enum { + // after completed kRegularFrames + kFrames = 0, + // after completed DC (1:8) + kDC = 1, + // after completed AC passes that are the last pass for their resolution + // target. + kLastPasses = 2, + // after completed AC passes that are not the last pass for their resolution + // target. + kPasses = 3, + // during DC frame when lower resolution are completed (1:32, 1:16) + kDCProgressive = 4, + // after completed groups + kDCGroups = 5, + // after completed groups + kGroups = 6, +} JxlProgressiveDetail; + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/jpeg-xl/include/jxl/version.h b/jpeg-xl/include/jxl/version.h new file mode 100644 index 0000000..f231dd7 --- /dev/null +++ b/jpeg-xl/include/jxl/version.h @@ -0,0 +1,39 @@ +/* Copyright (c) the JPEG XL Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/** @addtogroup libjxl_common + * @{ + * @file version.h + * @brief libjxl version information + */ + +#ifndef JXL_VERSION_H_ +#define JXL_VERSION_H_ + +#define JPEGXL_MAJOR_VERSION 0 ///< JPEG XL Major version +#define JPEGXL_MINOR_VERSION 8 ///< JPEG XL Minor version +#define JPEGXL_PATCH_VERSION 2 ///< JPEG XL Patch version + +/** Can be used to conditionally compile code for a specific JXL version + * @param[maj] major version + * @param[min] minor version + * + * @code + * #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,8,0) + * // use old/deprecated api + * #else + * // use current api + * #endif + * @endcode + */ +#define JPEGXL_COMPUTE_NUMERIC_VERSION(major,minor,patch) ((major<<24) | (minor<<16) | (patch<<8) | 0) + +/* Numeric representation of the version */ +#define JPEGXL_NUMERIC_VERSION JPEGXL_COMPUTE_NUMERIC_VERSION(JPEGXL_MAJOR_VERSION,JPEGXL_MINOR_VERSION,JPEGXL_PATCH_VERSION) + +#endif /* JXL_VERSION_H_ */ + +/** @}*/ diff --git a/jpeg-xl/lib/libbrotlicommon-static.a b/jpeg-xl/lib/libbrotlicommon.a similarity index 97% rename from jpeg-xl/lib/libbrotlicommon-static.a rename to jpeg-xl/lib/libbrotlicommon.a index 4129e13..262ea67 100644 Binary files a/jpeg-xl/lib/libbrotlicommon-static.a and b/jpeg-xl/lib/libbrotlicommon.a differ diff --git a/jpeg-xl/lib/libbrotlidec-static.a b/jpeg-xl/lib/libbrotlidec-static.a deleted file mode 100644 index e2683de..0000000 Binary files a/jpeg-xl/lib/libbrotlidec-static.a and /dev/null differ diff --git a/jpeg-xl/lib/libbrotlidec.a b/jpeg-xl/lib/libbrotlidec.a new file mode 100644 index 0000000..e8a9c76 Binary files /dev/null and b/jpeg-xl/lib/libbrotlidec.a differ diff --git a/jpeg-xl/lib/libbrotlienc-static.a b/jpeg-xl/lib/libbrotlienc-static.a deleted file mode 100644 index f6ccac6..0000000 Binary files a/jpeg-xl/lib/libbrotlienc-static.a and /dev/null differ diff --git a/jpeg-xl/lib/libhwy.a b/jpeg-xl/lib/libhwy.a index f5a65fb..ba2787f 100644 Binary files a/jpeg-xl/lib/libhwy.a and b/jpeg-xl/lib/libhwy.a differ diff --git a/jpeg-xl/lib/libjxl.a b/jpeg-xl/lib/libjxl.a index ca6677b..26aa39d 100644 Binary files a/jpeg-xl/lib/libjxl.a and b/jpeg-xl/lib/libjxl.a differ diff --git a/jpeg-xl/lib/libjxl_threads.a b/jpeg-xl/lib/libjxl_threads.a index 7ffaeff..c1c9641 100644 Binary files a/jpeg-xl/lib/libjxl_threads.a and b/jpeg-xl/lib/libjxl_threads.a differ diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..59c7fa9 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,20 @@ +# Development Tools + +The binary files contained in this directory are tools for use by developers. All of these files are shell scripts. + +## Logging + +`JXLook` is using Apple's [unified logging](https://developer.apple.com/documentation/os/logging) system. One of the ways to [view log messages](https://developer.apple.com/documentation/os/logging/viewing_log_messages) is to use the `log` tool from [Terminal](https://support.apple.com/guide/terminal/welcome/mac). The executable `streamLog` is merely a shell script that invokes the `log` tool specifying the `stream` command with a value for the `predicate` option to only show messages being emitted by `JXLook`. + +The `log` tool requires that the `stream` command be run from an admin account. + +## Settings + +`JXLook` settings use Apple's [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) system. The following executables use the [defaults](https://support.apple.com/guide/terminal/edit-property-lists-apda49a1bb2-577e-4721-8f25-ffc0836f6997/mac) tool to change various `JXLook` settings. This provides developers a way to quickly change settings from [Terminal](https://support.apple.com/guide/terminal/welcome/mac). + +- disableExtendedDynamicRange +- disableHighDynamicRange +- disableToneMapping +- enableExtendedDynamicRange +- enableHighDynamicRange +- enableToneMapping diff --git a/scripts/disableExtendedDynamicRange b/scripts/disableExtendedDynamicRange new file mode 100755 index 0000000..6c0801b --- /dev/null +++ b/scripts/disableExtendedDynamicRange @@ -0,0 +1,3 @@ +#!/bin/bash + +defaults write org.yllan.JXLook extendedDynamicRange -bool false diff --git a/scripts/disableHighDynamicRange b/scripts/disableHighDynamicRange new file mode 100755 index 0000000..1c03710 --- /dev/null +++ b/scripts/disableHighDynamicRange @@ -0,0 +1,3 @@ +#!/bin/bash + +defaults write org.yllan.JXLook highDynamicRange -bool false diff --git a/scripts/disableToneMapping b/scripts/disableToneMapping new file mode 100755 index 0000000..146f0d9 --- /dev/null +++ b/scripts/disableToneMapping @@ -0,0 +1,3 @@ +#!/bin/bash + +defaults write org.yllan.JXLook toneMapping -bool false diff --git a/scripts/enableExtendedDynamicRange b/scripts/enableExtendedDynamicRange new file mode 100755 index 0000000..f55d1b6 --- /dev/null +++ b/scripts/enableExtendedDynamicRange @@ -0,0 +1,3 @@ +#!/bin/bash + +defaults write org.yllan.JXLook extendedDynamicRange -bool true diff --git a/scripts/enableHighDynamicRange b/scripts/enableHighDynamicRange new file mode 100755 index 0000000..d1e0a5d --- /dev/null +++ b/scripts/enableHighDynamicRange @@ -0,0 +1,3 @@ +#!/bin/bash + +defaults write org.yllan.JXLook highDynamicRange -bool true diff --git a/scripts/enableToneMapping b/scripts/enableToneMapping new file mode 100755 index 0000000..3422415 --- /dev/null +++ b/scripts/enableToneMapping @@ -0,0 +1,3 @@ +#!/bin/bash + +defaults write org.yllan.JXLook toneMapping -bool true diff --git a/scripts/streamLog b/scripts/streamLog new file mode 100755 index 0000000..5d24a19 --- /dev/null +++ b/scripts/streamLog @@ -0,0 +1,3 @@ +#!/bin/bash + +log stream --debug --predicate 'subsystem == "org.yllan.JXLook"' --style compact $@