// Copyright (C) 2024 Davis E. King (davis@dlib.net), AdriĆ Arrufat // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_JXL_SAVER_CPp_ #define DLIB_JXL_SAVER_CPp_ // only do anything with this file if DLIB_JXL_SUPPORT is defined #ifdef DLIB_JXL_SUPPORT #include "save_jxl.h" #include "image_saver.h" #include <sstream> #include <jxl/encode_cxx.h> #include <jxl/resizable_parallel_runner_cxx.h> namespace dlib { // ---------------------------------------------------------------------------------------- namespace impl { void impl_save_jxl ( const std::string& filename, const uint8_t* pixels, const uint32_t width, const uint32_t height, const uint32_t num_channels, const float quality ) { std::ofstream fout(filename, std::ios::binary); if (!fout.good()) { throw image_save_error("Unable to open " + filename + " for writing."); } auto enc = JxlEncoderMake(nullptr); auto runner = JxlResizableParallelRunnerMake(nullptr); JxlResizableParallelRunnerSetThreads(runner.get(), JxlResizableParallelRunnerSuggestThreads(width, height)); if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(), JxlResizableParallelRunner, runner.get())) { throw image_save_error("jxl_saver: JxlResizableParallelRunner failed"); } JxlPixelFormat pixel_format{ .num_channels = num_channels, .data_type = JXL_TYPE_UINT8, .endianness = JXL_NATIVE_ENDIAN, .align = 0 }; JxlBasicInfo basic_info; JxlEncoderInitBasicInfo(&basic_info); basic_info.xsize = width; basic_info.ysize = height; basic_info.bits_per_sample = 8; basic_info.uses_original_profile = quality == 100; switch (num_channels) { case 1: basic_info.num_color_channels = 1; basic_info.num_extra_channels = 0; basic_info.alpha_bits = 0; basic_info.alpha_exponent_bits = 0; break; case 3: basic_info.num_color_channels = 3; basic_info.num_extra_channels = 0; basic_info.alpha_bits = 0; basic_info.alpha_exponent_bits = 0; break; case 4: basic_info.num_color_channels = 3; basic_info.num_extra_channels = 1; basic_info.alpha_bits = basic_info.bits_per_sample; basic_info.alpha_exponent_bits = 0; break; default: throw ("jxl_saver: unsupported number of channels"); } if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), &basic_info)) { throw image_save_error("jxl_saver: JxlEncoderSetBasicInfo failed"); } JxlColorEncoding color_encoding = {}; JxlColorEncodingSetToSRGB(&color_encoding, /* is_gray = */ num_channels < 3); if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(enc.get(), &color_encoding)) { throw image_save_error("jxl_saver: JxlEncoderSetColorEncoding failed"); } JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), nullptr); JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, 0); const float distance = JxlEncoderDistanceFromQuality(quality); if (JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(frame_settings, distance)) { throw image_save_error("jxl_saver: JxlEncoderSetFrameDistance failed"); } if (basic_info.alpha_bits > 0) { if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance(frame_settings, 0, distance)) { throw image_save_error("jxl_saver: JxlEncoderSetExtraChannelDistance failed"); } } // explictly enable lossless mode if (distance == 0) { if (JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE)) { throw image_save_error("jxl_saver: JxlEncoderSetFrameLossless failed"); } } void* pixels_data = reinterpret_cast<void*>(const_cast<uint8_t*>(pixels)); const size_t pixels_size = width * height * num_channels; if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(frame_settings, &pixel_format, pixels_data, pixels_size)) { throw image_save_error("jxl_saver: JxlEncoderAddImageFrame failed"); } JxlEncoderCloseInput(enc.get()); std::vector<uint8_t> compressed; compressed.resize(64); uint8_t* next_out = compressed.data(); size_t avail_out = compressed.size() - (next_out - compressed.data()); JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { size_t offset = next_out - compressed.data(); compressed.resize(compressed.size() * 2); next_out = compressed.data() + offset; avail_out = compressed.size() - offset; } } compressed.resize(next_out - compressed.data()); if (JXL_ENC_SUCCESS != process_result) { throw image_save_error("jxl_saver: JxlEncoderProcessOutput failed"); } fout.write(reinterpret_cast<char*>(compressed.data()), compressed.size()); if (!fout.good()) { throw image_save_error("Error while writing JPEG XL image to " + filename + "."); } } } // ---------------------------------------------------------------------------------------- } #endif // DLIB_JXL_SUPPORT #endif // DLIB_JXL_SAVER_CPp_