From 069656c9a90a8287819eb71f31f24417f96c42f9 Mon Sep 17 00:00:00 2001 From: "Andon M. Coleman" Date: Thu, 22 Aug 2024 22:22:03 -0400 Subject: [PATCH] Add JPEG-XL transcode support --- include/jxl/encode.h | 4 +- include/jxl/stats.h | 3 +- include/jxl/thread_parallel_runner.h | 3 +- src/tabs/viewer.cpp | 3 +- src/utility/image.cpp | 227 ++++++++++++++++++++++++++- 5 files changed, 232 insertions(+), 8 deletions(-) diff --git a/include/jxl/encode.h b/include/jxl/encode.h index 633a98e..0ec6764 100644 --- a/include/jxl/encode.h +++ b/include/jxl/encode.h @@ -16,12 +16,12 @@ #include #include #include -#include +//#include +#define JXL_EXPORT #include #include #include #include -#include // TODO(eustas): remove before v1.0 #include #include diff --git a/include/jxl/stats.h b/include/jxl/stats.h index 35930b4..0922613 100644 --- a/include/jxl/stats.h +++ b/include/jxl/stats.h @@ -13,7 +13,8 @@ #ifndef JXL_STATS_H_ #define JXL_STATS_H_ -#include +//#include +#define JXL_EXPORT #include #ifdef __cplusplus diff --git a/include/jxl/thread_parallel_runner.h b/include/jxl/thread_parallel_runner.h index fbfe9e2..4689c27 100644 --- a/include/jxl/thread_parallel_runner.h +++ b/include/jxl/thread_parallel_runner.h @@ -30,7 +30,8 @@ #ifndef JXL_THREAD_PARALLEL_RUNNER_H_ #define JXL_THREAD_PARALLEL_RUNNER_H_ -#include +//#include +#define JXL_THREADS_EXPORT #include #include #include diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 457d0b4..9b4d33d 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -135,6 +135,7 @@ const std::initializer_list supported_formats = FileSignature { L"image/avif", { L".avif" }, { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66 }, // ftypavif { 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } }, // ?? ?? ?? ?? 66 74 79 70 61 76 69 66 FileSignature { L"image/jxl", { L".jxl" }, { 0xFF, 0x0A } }, + FileSignature { L"image/jxl", { L".jxl" }, { 0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C} }, FileSignature { L"image/vnd-ms.dds", { L".dds" }, { 0x44, 0x44, 0x53, 0x20 } }, //FileSignature { L"image/x-targa", { L".tga" }, { 0x00, } }, // TGA has no real unique header identifier, so just use the file extension on those }; @@ -158,7 +159,7 @@ const std::initializer_list supported_hdr_encode_formats = FileSignature { L"image/vnd.ms-photo", { L".jxr" }, { 0x49, 0x49, 0xBC } }, FileSignature { L"image/vnd.radiance", { L".hdr" }, { 0x23, 0x3F, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4E, 0x43, 0x45, 0x0A } }, // Radiance High Dynamic Range image file FileSignature { L"image/x-exr", { L".exr" }, { 0x76, 0x2F, 0x31, 0x01 } }, -//FileSignature { L"image/jxl", { L".jxl" }, { 0xFF, 0x0A } }, + FileSignature { L"image/jxl", { L".jxl" }, { 0xFF, 0x0A } }, //FileSignature { L"image/avif", { L".avif" }, { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66 }, // ftypavif // { 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } }, // ?? ?? ?? ?? 66 74 79 70 61 76 69 66 //FileSignature { L"image/vnd-ms.dds", { L".dds" }, { 0x44, 0x44, 0x53, 0x20 } }, diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 92ff26c..8620021 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -17,6 +17,17 @@ #include "DirectXTex.h" #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + skiv_image_desktop_s SKIV_DesktopImage; extern std::wstring defaultHDRFileExt; @@ -2213,9 +2224,219 @@ SKIV_Image_SaveToDisk_HDR (const DirectX::Image& image, const wchar_t* wszFileNa } } - else if (StrStrIW (wszExtension, L"avif") || - StrStrIW (wszExtension, L"hdr") || - StrStrIW (wszExtension, L"jxl")) + else if (StrStrIW (wszExtension, L"jxl")) + { + extern bool isJXLDecoderAvailable (void); + if (! isJXLDecoderAvailable ()) + return E_NOTIMPL; + + static HMODULE hModJXL; + SK_RunOnce ( hModJXL = LoadLibraryW (L"jxl.dll")); + + static HMODULE hModJXLThreads; + SK_RunOnce ( hModJXLThreads = LoadLibraryW (L"jxl_threads.dll")); + + using JxlEncoderCreate_pfn = JxlEncoder* (*)(const JxlMemoryManager* memory_manager); + using JxlEncoderDestroy_pfn = void (*)(JxlEncoder* enc); + using JxlEncoderCloseInput_pfn = void (*)(JxlEncoder* enc); + using JxlEncoderProcessOutput_pfn = JxlEncoderStatus (*)(JxlEncoder* enc, uint8_t** next_out, size_t* avail_out); + using JxlEncoderFrameSettingsCreate_pfn = JxlEncoderFrameSettings* (*)(JxlEncoder* enc, const JxlEncoderFrameSettings* source); + using JxlEncoderInitBasicInfo_pfn = void (*)(JxlBasicInfo* info); + using JxlEncoderSetBasicInfo_pfn = JxlEncoderStatus (*)(JxlEncoder* enc, const JxlBasicInfo* info); + using JxlEncoderAddImageFrame_pfn = JxlEncoderStatus (*)(const JxlEncoderFrameSettings* frame_settings, const JxlPixelFormat* pixel_format, const void* buffer, size_t size); + using JxlEncoderSetColorEncoding_pfn = JxlEncoderStatus (*)(JxlEncoder* enc, const JxlColorEncoding* color); + using JxlColorEncodingSetToLinearSRGB_pfn = void (*)(JxlColorEncoding* color_encoding, JXL_BOOL is_gray); + using JxlEncoderSetParallelRunner_pfn = JxlEncoderStatus (*)(JxlEncoder* enc, JxlParallelRunner parallel_runner, void* parallel_runner_opaque); + + using JxlThreadParallelRunner_pfn = JxlParallelRetCode (*)(void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range); + using JxlThreadParallelRunnerCreate_pfn = void* (*)(const JxlMemoryManager* memory_manager, size_t num_worker_threads); + using JxlThreadParallelRunnerDestroy_pfn = void (*)(void* runner_opaque); + using JxlThreadParallelRunnerDefaultNumWorkerThreads_pfn = size_t (*)(void); + + static JxlEncoderCreate_pfn jxlEncoderCreate = (JxlEncoderCreate_pfn) GetProcAddress (hModJXL, "JxlEncoderCreate"); + static JxlEncoderDestroy_pfn jxlEncoderDestroy = (JxlEncoderDestroy_pfn) GetProcAddress (hModJXL, "JxlEncoderDestroy"); + static JxlEncoderCloseInput_pfn jxlEncoderCloseInput = (JxlEncoderCloseInput_pfn) GetProcAddress (hModJXL, "JxlEncoderCloseInput"); + static JxlEncoderProcessOutput_pfn jxlEncoderProcessOutput = (JxlEncoderProcessOutput_pfn) GetProcAddress (hModJXL, "JxlEncoderProcessOutput"); + static JxlEncoderFrameSettingsCreate_pfn jxlEncoderFrameSettingsCreate = (JxlEncoderFrameSettingsCreate_pfn) GetProcAddress (hModJXL, "JxlEncoderFrameSettingsCreate"); + static JxlEncoderInitBasicInfo_pfn jxlEncoderInitBasicInfo = (JxlEncoderInitBasicInfo_pfn) GetProcAddress (hModJXL, "JxlEncoderInitBasicInfo"); + static JxlEncoderSetBasicInfo_pfn jxlEncoderSetBasicInfo = (JxlEncoderSetBasicInfo_pfn) GetProcAddress (hModJXL, "JxlEncoderSetBasicInfo"); + static JxlEncoderAddImageFrame_pfn jxlEncoderAddImageFrame = (JxlEncoderAddImageFrame_pfn) GetProcAddress (hModJXL, "JxlEncoderAddImageFrame"); + static JxlEncoderSetColorEncoding_pfn jxlEncoderSetColorEncoding = (JxlEncoderSetColorEncoding_pfn) GetProcAddress (hModJXL, "JxlEncoderSetColorEncoding"); + static JxlColorEncodingSetToLinearSRGB_pfn jxlColorEncodingSetToLinearSRGB = (JxlColorEncodingSetToLinearSRGB_pfn)GetProcAddress (hModJXL, "JxlColorEncodingSetToLinearSRGB"); + static JxlEncoderSetParallelRunner_pfn jxlEncoderSetParallelRunner = (JxlEncoderSetParallelRunner_pfn) GetProcAddress (hModJXL, "JxlEncoderSetParallelRunner"); + + static JxlThreadParallelRunner_pfn jxlThreadParallelRunner = (JxlThreadParallelRunner_pfn) GetProcAddress (hModJXLThreads, "JxlThreadParallelRunner"); + static JxlThreadParallelRunnerCreate_pfn jxlThreadParallelRunnerCreate = (JxlThreadParallelRunnerCreate_pfn) GetProcAddress (hModJXLThreads, "JxlThreadParallelRunnerCreate"); + static JxlThreadParallelRunnerDestroy_pfn jxlThreadParallelRunnerDestroy = (JxlThreadParallelRunnerDestroy_pfn) GetProcAddress (hModJXLThreads, "JxlThreadParallelRunnerDestroy"); + static JxlThreadParallelRunnerDefaultNumWorkerThreads_pfn jxlThreadParallelRunnerDefaultNumWorkerThreads = (JxlThreadParallelRunnerDefaultNumWorkerThreads_pfn)GetProcAddress (hModJXLThreads, "JxlThreadParallelRunnerDefaultNumWorkerThreads"); + + using JxlEncoderSetFrameLossless_pfn = JxlEncoderStatus (*)(JxlEncoderFrameSettings *frame_settings, JXL_BOOL lossless); + using JxlEncoderSetFrameDistance_pfn = JxlEncoderStatus (*)(JxlEncoderFrameSettings *frame_settings, float distance); + using JxlEncoderSetFrameBitDepth_pfn = JxlEncoderStatus (*)(JxlEncoderFrameSettings *frame_settings, const JxlBitDepth *bit_depth); + using JxlEncoderDistanceFromQuality_pfn = float (*)(float quality); + + static JxlEncoderSetFrameLossless_pfn jxlEncoderSetFrameLossless = (JxlEncoderSetFrameLossless_pfn) GetProcAddress (hModJXL, "JxlEncoderSetFrameLossless"); + static JxlEncoderSetFrameDistance_pfn jxlEncoderSetFrameDistance = (JxlEncoderSetFrameDistance_pfn) GetProcAddress (hModJXL, "JxlEncoderSetFrameDistance"); + static JxlEncoderSetFrameBitDepth_pfn jxlEncoderSetFrameBitDepth = (JxlEncoderSetFrameBitDepth_pfn) GetProcAddress (hModJXL, "JxlEncoderSetFrameBitDepth"); + static JxlEncoderDistanceFromQuality_pfn jxlEncoderDistanceFromQuality = (JxlEncoderDistanceFromQuality_pfn)GetProcAddress (hModJXL, "JxlEncoderDistanceFromQuality"); + + bool succeeded = false; + + if ( jxlEncoderCreate == nullptr || + jxlThreadParallelRunnerCreate == nullptr || + jxlThreadParallelRunnerDefaultNumWorkerThreads == nullptr ) + { + PLOG_ERROR << "JPEG-XL library unavailable"; + return E_NOINTERFACE; + } + + auto jxl_encoder = jxlEncoderCreate (nullptr); + auto jxl_runner = jxlThreadParallelRunnerCreate (nullptr, + jxlThreadParallelRunnerDefaultNumWorkerThreads ()); + + for (;;) + { + if ( jxl_encoder == nullptr || + jxl_runner == nullptr ) + break; + + if ( JXL_ENC_SUCCESS != + jxlEncoderSetParallelRunner ( jxl_encoder, + jxlThreadParallelRunner, + jxl_runner ) ) + { + PLOG_ERROR << "JxlEncoderSetParallelRunner failed"; + break; + } + + std::vector fp32_pixels (image.width * image.height * 3); + + auto fp32_pixel_comp = + fp32_pixels.begin (); + + EvaluateImage ( image, + [&](const XMVECTOR* pixels, size_t width, size_t y) + { + UNREFERENCED_PARAMETER(y); + + for (size_t j = 0; j < width; ++j) + { + XMVECTOR v = + *pixels++; + + *fp32_pixel_comp++ = XMVectorGetX (v); + *fp32_pixel_comp++ = XMVectorGetY (v); + *fp32_pixel_comp++ = XMVectorGetZ (v); + } + } + ); + + JxlDataType type = JXL_TYPE_FLOAT; + JxlPixelFormat pixel_format = + { 3, type, JXL_NATIVE_ENDIAN, 0 }; + + JxlBasicInfo basic_info = { }; + jxlEncoderInitBasicInfo (&basic_info); + + basic_info.xsize = static_cast (image.width); + basic_info.ysize = static_cast (image.height); + basic_info.bits_per_sample = static_cast (DirectX::BitsPerColor (image.format)); + basic_info.exponent_bits_per_sample = DirectX::BitsPerColor (image.format) == 32 ? 8 : 5; + basic_info.uses_original_profile = JXL_FALSE; + + if ( JXL_ENC_SUCCESS != + jxlEncoderSetBasicInfo ( jxl_encoder, &basic_info) ) + { + PLOG_ERROR << "JxlEncoderSetBasicInfo failed"; + break; + } + + /////JxlColorEncoding color_encoding = { }; + ///// + /////jxlColorEncodingSetToLinearSRGB (&color_encoding, JXL_FALSE); + ///// + /////if ( JXL_ENC_SUCCESS != + ///// jxlEncoderSetColorEncoding (jxl_encoder, &color_encoding) ) + /////{ + ///// PLOG_ERROR << "JxlEncoderSetColorEncoding failed"; + ///// break; + /////} + + JxlEncoderFrameSettings* frame_settings = + jxlEncoderFrameSettingsCreate (jxl_encoder, nullptr); + + jxlEncoderSetFrameLossless (frame_settings, JXL_TRUE); + jxlEncoderSetFrameDistance (frame_settings, jxlEncoderDistanceFromQuality (100.0f)); + + if ( JXL_ENC_SUCCESS != + jxlEncoderAddImageFrame ( frame_settings, &pixel_format, + static_cast (fp32_pixels.data ()), + sizeof (float) * fp32_pixels.size () ) ) + { + PLOG_ERROR << "JxlEncoderAddImageFrame failed"; + break; + } + + jxlEncoderCloseInput (jxl_encoder); + + std::vector output (64); + + uint8_t* next_out = output.data (); + size_t avail_out = output.size () - (next_out - output.data ()); + + JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; + + while (process_result == JXL_ENC_NEED_MORE_OUTPUT) + { + process_result = + jxlEncoderProcessOutput (jxl_encoder, &next_out, &avail_out); + + if (process_result == JXL_ENC_NEED_MORE_OUTPUT) + { + size_t offset = next_out - output.data (); + + output.resize (output.size () * 2); + + next_out = output.data () + offset; + avail_out = output.size () - offset; + } + } + + output.resize (next_out - output.data ()); + + if (JXL_ENC_SUCCESS != process_result) + { + PLOG_ERROR << "JxlEncoderProcessOutput failed"; + break; + } + + FILE* fOutput = + _wfopen (wszImplicitFileName, L"wb"); + + if (fOutput != nullptr) + { + fwrite (output.data (), output.size (), 1, fOutput); + fclose (fOutput); + + PLOG_INFO << "JPEG-XL Encode Finished"; + + succeeded = true; + } + + break; + } + + if (jxl_encoder != nullptr) + jxlEncoderDestroy (jxl_encoder); + + if (jxl_runner != nullptr) + jxlThreadParallelRunnerDestroy (jxl_runner); + + return + succeeded ? S_OK : E_FAIL; + } + + else if (StrStrIW (wszExtension, L"avif")) { // Not yet, sorry... return E_NOTIMPL;