Skip to content

Commit

Permalink
Add JXL support (#2917)
Browse files Browse the repository at this point in the history
* WIP: preparation for JPEG XL support

* jxl: add loading support

* update jxl abstract

* add support for saving jxl (lossless not working)

* everything works except setting lossless explicitly

* remove unused header

* fix wrong quality logic

* remove debugging statements

* fix lossless encoding

* improve support for grayscale images

* use JXL instead of JPEGXL everywhere

* oops do not make libjxl a requirement

* update years

* silence some warnings

* simplify loader fast path logic

* allow python to save jxl and webp

* update error message with supported formats

* Allow setting image quality in Python

The setting is ignored where it does not make sense.

* round quality in JPEG saver

* improve error message in CMake

* add jxl support to imglab

* add Davis's suggestion

Co-authored-by: Davis E. King <davis685@gmail.com>

* Apply suggestions from code review

Co-authored-by: Davis E. King <davis685@gmail.com>

* make sure grayscale is 8 bit

* update abstract: JPEG XL can store grayscale images

* add more methods to query basic info from JXL

* documentation formatting

* Apply Davis' suggestions

---------

Co-authored-by: Davis E. King <davis685@gmail.com>
  • Loading branch information
arrufat and davisking committed Mar 4, 2024
1 parent c7ba757 commit 9a30c6d
Show file tree
Hide file tree
Showing 20 changed files with 1,156 additions and 23 deletions.
26 changes: 24 additions & 2 deletions dlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ if (NOT TARGET dlib)
"Disable this if you don't want to link against libjpeg" )
set (DLIB_WEBP_SUPPORT_STR
"Disable this if you don't want to link against libwebp" )
set (DLIB_JXL_SUPPORT_STR
"Disable this if you don't want to link against libjxl" )
set (DLIB_LINK_WITH_SQLITE3_STR
"Disable this if you don't want to link against sqlite3" )
#set (DLIB_USE_FFTW_STR "Disable this if you don't want to link against fftw" )
Expand Down Expand Up @@ -237,30 +239,33 @@ if (NOT TARGET dlib)
option(DLIB_PNG_SUPPORT ${DLIB_PNG_SUPPORT_STR} OFF)
option(DLIB_GIF_SUPPORT ${DLIB_GIF_SUPPORT_STR} OFF)
option(DLIB_WEBP_SUPPORT ${DLIB_WEBP_SUPPORT_STR} OFF)
option(DLIB_JXL_SUPPORT ${DLIB_JXL_SUPPORT_STR} OFF)
#option(DLIB_USE_FFTW ${DLIB_USE_FFTW_STR} OFF)
option(DLIB_USE_MKL_FFT ${DLIB_USE_MKL_FFT_STR} OFF)
option(DLIB_USE_FFMPEG ${DLIB_USE_FFMPEG_STR} OFF)
else()
option(DLIB_JPEG_SUPPORT ${DLIB_JPEG_SUPPORT_STR} ON)
option(DLIB_WEBP_SUPPORT ${DLIB_WEBP_SUPPORT_STR} ON)
option(DLIB_LINK_WITH_SQLITE3 ${DLIB_LINK_WITH_SQLITE3_STR} ON)
option(DLIB_USE_BLAS ${DLIB_USE_BLAS_STR} ON)
option(DLIB_USE_LAPACK ${DLIB_USE_LAPACK_STR} ON)
option(DLIB_USE_CUDA ${DLIB_USE_CUDA_STR} ON)
set(DLIB_USE_CUDA_COMPUTE_CAPABILITIES 50 CACHE STRING ${DLIB_USE_CUDA_COMPUTE_CAPABILITIES_STR})
option(DLIB_PNG_SUPPORT ${DLIB_PNG_SUPPORT_STR} ON)
option(DLIB_GIF_SUPPORT ${DLIB_GIF_SUPPORT_STR} ON)
option(DLIB_WEBP_SUPPORT ${DLIB_WEBP_SUPPORT_STR} ON)
option(DLIB_JXL_SUPPORT ${DLIB_JXL_SUPPORT_STR} ON)
#option(DLIB_USE_FFTW ${DLIB_USE_FFTW_STR} ON)
option(DLIB_USE_MKL_FFT ${DLIB_USE_MKL_FFT_STR} ON)
option(DLIB_USE_FFMPEG ${DLIB_USE_FFMPEG_STR} ON)
endif()
toggle_preprocessor_switch(DLIB_JPEG_SUPPORT)
toggle_preprocessor_switch(DLIB_WEBP_SUPPORT)
toggle_preprocessor_switch(DLIB_USE_BLAS)
toggle_preprocessor_switch(DLIB_USE_LAPACK)
toggle_preprocessor_switch(DLIB_USE_CUDA)
toggle_preprocessor_switch(DLIB_PNG_SUPPORT)
toggle_preprocessor_switch(DLIB_GIF_SUPPORT)
toggle_preprocessor_switch(DLIB_WEBP_SUPPORT)
toggle_preprocessor_switch(DLIB_JXL_SUPPORT)
#toggle_preprocessor_switch(DLIB_USE_FFTW)
toggle_preprocessor_switch(DLIB_USE_MKL_FFT)
toggle_preprocessor_switch(DLIB_USE_FFMPEG)
Expand Down Expand Up @@ -576,6 +581,23 @@ if (NOT TARGET dlib)
toggle_preprocessor_switch(DLIB_WEBP_SUPPORT)
endif()
endif()
if (DLIB_JXL_SUPPORT)
include(cmake_utils/find_libjxl.cmake)
if (JXL_FOUND)
list (APPEND dlib_needed_private_includes ${JXL_INCLUDE_DIRS})
list (APPEND dlib_needed_private_libraries ${JXL_LIBRARIES})
list (APPEND dlib_needed_public_cflags ${JXL_CFLAGS})
list (APPEND dlib_needed_public_ldflags ${JXL_LDFLAGS})
set(source_files ${source_files}
image_loader/jxl_loader.cpp
image_saver/save_jxl.cpp
)
enable_preprocessor_switch(DLIB_JXL_SUPPORT)
else()
set(DLIB_JXL_SUPPORT OFF CACHE BOOL ${DLIB_JXL_SUPPORT_STR} FORCE)
disable_preprocessor_switch(DLIB_JXL_SUPPORT)
endif()
endif()


if (DLIB_USE_BLAS OR DLIB_USE_LAPACK OR DLIB_USE_MKL_FFT)
Expand Down
50 changes: 50 additions & 0 deletions dlib/cmake_utils/find_libjxl.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#=============================================================================
# Find JPEG XL library
#=============================================================================
# Find the native JPEG XL headers and libraries.
#
# JXL_INCLUDE_DIRS - where to find jxl/decode_cxx.h, etc.
# JXL_LIBRARIES - List of libraries when using jxl.
# JXL_FOUND - True if jxl is found.
#=============================================================================

# Look for the header file.

message(STATUS "Searching for JPEG XL")
find_package(PkgConfig)
if (PkgConfig_FOUND)
pkg_check_modules(JXL IMPORTED_TARGET libjxl libjxl_cms libjxl_threads)
if (JXL_FOUND)
message(STATUS "Found libjxl via pkg-config in `${JXL_LIBRARY_DIRS}`")
else()
message(" *****************************************************************************")
message(" *** No JPEG XL libraries found. ***")
message(" *** On Ubuntu 23.04 and newer you can install them by executing ***")
message(" *** sudo apt install libjxl-dev ***")
message(" *** ***")
message(" *** Otherwise, you can find precompiled packages here: ***")
message(" *** https://github.com/libjxl/libjxl/releases ***")
message(" *****************************************************************************")
endif()
else()
message(STATUS "PkgConfig could not be found, JPEG XL support won't be available")
set(JXL_FOUND 0)
endif()

if(JXL_FOUND)
set(JXL_TEST_CMAKE_FLAGS
"-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}"
"-DCMAKE_INCLUDE_PATH=${CMAKE_INCLUDE_PATH}"
"-DCMAKE_LIBRARY_PATH=${CMAKE_LIBRARY_PATH}")

try_compile(test_for_libjxl_worked
${PROJECT_BINARY_DIR}/test_for_libjxl_build
${CMAKE_CURRENT_LIST_DIR}/test_for_libjxl
test_if_libjxl_is_broken
CMAKE_FLAGS "${JXL_TEST_CMAKE_FLAGS}")

if(NOT test_for_libjxl_worked)
set(JXL_FOUND 0)
message (STATUS "System copy of libjxl is either too old or broken. Will disable JPEG XL support.")
endif()
endif()
7 changes: 7 additions & 0 deletions dlib/cmake_utils/test_for_libjxl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

cmake_minimum_required(VERSION 3.8.0)
project(test_if_libjxl_is_broken)

include_directories(${JXL_INCLUDE_DIR})
add_executable(libjxl_test libjxl_test.cpp)
target_link_libraries(libjxl_test ${JXL_LIBRARY})
20 changes: 20 additions & 0 deletions dlib/cmake_utils/test_for_libjxl/libjxl_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (C) 2023 Davis E. King (davis@dlib.net), Adrià Arrufat
// License: Boost Software License See LICENSE.txt for the full license.

#include <jxl/encode_cxx.h>
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner_cxx.h>
#include <iostream>
#include <memory>

// This code doesn't really make a lot of sense. It's just calling all the libjpeg functions to make
// sure they can be compiled and linked.

int main()
{
std::cerr << "This program is just for build system testing. Don't actually run it." << std::endl;
std::abort();
auto enc = JxlEncoderMake(nullptr);
auto dec = JxlDecoderMake(nullptr);
auto runner = JxlResizableParallelRunnerMake(nullptr);
}
3 changes: 2 additions & 1 deletion dlib/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
// You should also consider telling dlib to link against libjpeg, libpng, libgif, fftw, CUDA,
// and a BLAS and LAPACK library. To do this you need to uncomment the following #defines.
#cmakedefine DLIB_JPEG_SUPPORT
#cmakedefine DLIB_WEBP_SUPPORT
#cmakedefine DLIB_PNG_SUPPORT
#cmakedefine DLIB_GIF_SUPPORT
#cmakedefine DLIB_WEBP_SUPPORT
#cmakedefine DLIB_JXL_SUPPORT
#cmakedefine DLIB_USE_FFTW
#cmakedefine DLIB_USE_BLAS
#cmakedefine DLIB_USE_LAPACK
Expand Down
1 change: 1 addition & 0 deletions dlib/image_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "image_saver/save_png.h"
#include "image_saver/save_jpeg.h"
#include "image_saver/save_webp.h"
#include "image_saver/save_jxl.h"

#endif // DLIB_IMAGe_IO_

180 changes: 180 additions & 0 deletions dlib/image_loader/jxl_loader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// 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_LOADER_CPp_
#define DLIB_JXL_LOADER_CPp_

// only do anything with this file if DLIB_JXL_SUPPORT is defined
#ifdef DLIB_JXL_SUPPORT
#include "jxl_loader.h"
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner_cxx.h>
#include <fstream>

namespace dlib
{

static std::vector<unsigned char> load_contents(const std::string& filename)
{
std::ifstream stream(filename, std::ios::binary);
stream.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
std::vector<unsigned char> buffer;
vectorstream temp(buffer);
temp << stream.rdbuf();
return buffer;
}

// ----------------------------------------------------------------------------------------

jxl_loader::
jxl_loader(const char* filename) : height(0), width(0)
{
data = load_contents(filename);
get_info();
}

// ----------------------------------------------------------------------------------------

jxl_loader::
jxl_loader(const std::string& filename) : height(0), width(0)
{
data = load_contents(filename);
get_info();
}

// ----------------------------------------------------------------------------------------

jxl_loader::
jxl_loader(const dlib::file& f) : height(0), width(0)
{
data = load_contents(f.full_name());
get_info();
}

// ----------------------------------------------------------------------------------------

jxl_loader::
jxl_loader(const unsigned char* imgbuffer, size_t imgbuffersize) : height(0), width(0)
{
data.resize(imgbuffersize);
memcpy(data.data(), imgbuffer, imgbuffersize);
get_info();
}

// ----------------------------------------------------------------------------------------

bool jxl_loader::is_gray() const { return depth == 1; }
bool jxl_loader::is_graya() const { return depth == 2; };
bool jxl_loader::is_rgb() const { return depth == 3; }
bool jxl_loader::is_rgba() const { return depth == 4; }
unsigned int jxl_loader::bit_depth() const { return bits_per_sample; };
long jxl_loader::nr() const { return static_cast<long>(height); };
long jxl_loader::nc() const { return static_cast<long>(width); };

// ----------------------------------------------------------------------------------------

void jxl_loader::get_info()
{
JxlSignature signature = JxlSignatureCheck(data.data(), data.size());
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER)
{
throw image_load_error("jxl_loader: JxlSignatureCheck failed");
}

auto dec = JxlDecoderMake(nullptr);
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO))
{
throw image_load_error("jxl_loader: JxlDecoderSubscribeEvents failed");
}

JxlDecoderSetInput(dec.get(), data.data(), data.size());
JxlDecoderCloseInput(dec.get());
if (JXL_DEC_BASIC_INFO != JxlDecoderProcessInput(dec.get())) {
throw image_load_error("jxl_loader: JxlDecoderProcessInput failed");
}

JxlBasicInfo basic_info;
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &basic_info))
{
throw image_load_error("jxl_loader: JxlDecoderGetBasicInfo failed");
}
width = basic_info.xsize;
height = basic_info.ysize;
depth = basic_info.num_color_channels + basic_info.num_extra_channels;
bits_per_sample = basic_info.bits_per_sample;
}
// ----------------------------------------------------------------------------------------

void jxl_loader::decode(unsigned char* out, const size_t out_size) const
{
auto runner = JxlResizableParallelRunnerMake(nullptr);
auto dec = JxlDecoderMake(nullptr);
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FULL_IMAGE))
{
throw image_load_error("jxl_loader: JxlDecoderSubscribeEvents failed");
}

if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get()))
{
throw image_load_error("jxl_loader: JxlDecoderSetParallelRunner failed");
}

if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec.get(), data.data(), data.size()))
{
throw image_load_error("jxl_loader: JxlDecoderSetInput failed");
}
JxlDecoderCloseInput(dec.get());

JxlPixelFormat format = {
.num_channels = depth,
.data_type = JXL_TYPE_UINT8,
.endianness = JXL_NATIVE_ENDIAN,
.align=0
};
for (;;)
{
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR)
{
throw image_load_error("jxl_loader: JxlDecoderProcessInput failed");
}
else if (status == JXL_DEC_NEED_MORE_INPUT)
{
throw image_load_error("jxl_loader: Error, expected more input");
}
else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER)
{
JxlResizableParallelRunnerSetThreads(runner.get(), JxlResizableParallelRunnerSuggestThreads(width, height));
size_t buffer_size;
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size))
{
throw image_load_error("jxl_loader: JxlDecoderImageOutBufferSize failed");
}
if (buffer_size != width * height * depth)
{
throw image_load_error("jxl_loader: invalid output buffer size");
}
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, out, out_size))
{
throw image_load_error("jxl_loader: JxlDecoderSetImageOutBuffer failed");
}
}
else if (status == JXL_DEC_FULL_IMAGE)
{
// If the image is an animation, more full frames may be decoded.
// This loader only decodes the first one.
return;
}
else if (status == JXL_DEC_SUCCESS)
{
return;
}
else
{
throw image_load_error("jxl_loder: Unknown decoder status");
}
}
}
}

#endif // DLIB_JXL_SUPPORT
#endif // DLIB_JXL_LOADER_CPp_
Loading

0 comments on commit 9a30c6d

Please sign in to comment.