Skip to content

Commit

Permalink
Add JPEG-XL transcode support
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaldaien committed Aug 23, 2024
1 parent 1b9c3fa commit 069656c
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 8 deletions.
4 changes: 2 additions & 2 deletions include/jxl/encode.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
#include <jxl/cms_interface.h>
#include <jxl/codestream_header.h>
#include <jxl/color_encoding.h>
#include <jxl/jxl_export.h>
//#include <jxl/jxl_export.h>
#define JXL_EXPORT
#include <jxl/memory_manager.h>
#include <jxl/parallel_runner.h>
#include <jxl/stats.h>
#include <jxl/types.h>
#include <jxl/version.h> // TODO(eustas): remove before v1.0
#include <stddef.h>
#include <stdint.h>

Expand Down
3 changes: 2 additions & 1 deletion include/jxl/stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
#ifndef JXL_STATS_H_
#define JXL_STATS_H_

#include <jxl/jxl_export.h>
//#include <jxl/jxl_export.h>
#define JXL_EXPORT
#include <stddef.h>

#ifdef __cplusplus
Expand Down
3 changes: 2 additions & 1 deletion include/jxl/thread_parallel_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
#ifndef JXL_THREAD_PARALLEL_RUNNER_H_
#define JXL_THREAD_PARALLEL_RUNNER_H_

#include <jxl/jxl_threads_export.h>
//#include <jxl/jxl_threads_export.h>
#define JXL_THREADS_EXPORT
#include <jxl/memory_manager.h>
#include <jxl/parallel_runner.h>
#include <stddef.h>
Expand Down
3 changes: 2 additions & 1 deletion src/tabs/viewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ const std::initializer_list<FileSignature> 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
};
Expand All @@ -158,7 +159,7 @@ const std::initializer_list<FileSignature> 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 } },
Expand Down
227 changes: 224 additions & 3 deletions src/utility/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@
#include "DirectXTex.h"
#include <utility/DirectXTexEXR.h>

#include <jxl/codestream_header.h>
#include <jxl/encode.h>
#include <jxl/encode_cxx.h>
#include <jxl/resizable_parallel_runner.h>
#include <jxl/resizable_parallel_runner_cxx.h>
#include <jxl/thread_parallel_runner.h>
#include <jxl/thread_parallel_runner_cxx.h>
#include <jxl/types.h>

#include <DirectXPackedVector.h>

skiv_image_desktop_s SKIV_DesktopImage;

extern std::wstring defaultHDRFileExt;
Expand Down Expand Up @@ -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 <float> 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 <uint32_t> (image.width);
basic_info.ysize = static_cast <uint32_t> (image.height);
basic_info.bits_per_sample = static_cast <uint32_t> (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 <const void *> (fp32_pixels.data ()),
sizeof (float) * fp32_pixels.size () ) )
{
PLOG_ERROR << "JxlEncoderAddImageFrame failed";
break;
}

jxlEncoderCloseInput (jxl_encoder);

std::vector <uint8_t> 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;
Expand Down

0 comments on commit 069656c

Please sign in to comment.