diff --git a/dlib/CMakeLists.txt b/dlib/CMakeLists.txt index 83230606f4..36dab9fb3b 100644 --- a/dlib/CMakeLists.txt +++ b/dlib/CMakeLists.txt @@ -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" ) @@ -237,12 +239,12 @@ 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) @@ -250,17 +252,20 @@ if (NOT TARGET dlib) 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) @@ -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) diff --git a/dlib/cmake_utils/find_libjxl.cmake b/dlib/cmake_utils/find_libjxl.cmake new file mode 100644 index 0000000000..fabcb48766 --- /dev/null +++ b/dlib/cmake_utils/find_libjxl.cmake @@ -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() diff --git a/dlib/cmake_utils/test_for_libjxl/CMakeLists.txt b/dlib/cmake_utils/test_for_libjxl/CMakeLists.txt new file mode 100644 index 0000000000..c91b5d8ff6 --- /dev/null +++ b/dlib/cmake_utils/test_for_libjxl/CMakeLists.txt @@ -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}) diff --git a/dlib/cmake_utils/test_for_libjxl/libjxl_test.cpp b/dlib/cmake_utils/test_for_libjxl/libjxl_test.cpp new file mode 100644 index 0000000000..9ec6240d04 --- /dev/null +++ b/dlib/cmake_utils/test_for_libjxl/libjxl_test.cpp @@ -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 +#include +#include +#include +#include + +// 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); +} diff --git a/dlib/config.h.in b/dlib/config.h.in index abaa655c1f..1e21ee72af 100644 --- a/dlib/config.h.in +++ b/dlib/config.h.in @@ -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 diff --git a/dlib/image_io.h b/dlib/image_io.h index 9edf41db02..88597fffdd 100644 --- a/dlib/image_io.h +++ b/dlib/image_io.h @@ -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_ diff --git a/dlib/image_loader/jxl_loader.cpp b/dlib/image_loader/jxl_loader.cpp new file mode 100644 index 0000000000..97fa705dbc --- /dev/null +++ b/dlib/image_loader/jxl_loader.cpp @@ -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 +#include +#include + +namespace dlib +{ + + static std::vector 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 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(height); }; + long jxl_loader::nc() const { return static_cast(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_ diff --git a/dlib/image_loader/jxl_loader.h b/dlib/image_loader/jxl_loader.h new file mode 100644 index 0000000000..6eea595f71 --- /dev/null +++ b/dlib/image_loader/jxl_loader.h @@ -0,0 +1,138 @@ +// 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_IMPORT +#define DLIB_JXL_IMPORT + +#include + +#include "jxl_loader_abstract.h" +#include "image_loader.h" +#include "../pixel.h" +#include "../dir_nav.h" +#include "../test_for_odr_violations.h" + +namespace dlib +{ + + class jxl_loader : noncopyable + { + public: + + jxl_loader(const char* filename); + jxl_loader(const std::string& filename); + jxl_loader(const dlib::file& f); + jxl_loader(const unsigned char* imgbuffer, size_t buffersize); + + bool is_gray() const; + bool is_graya() const; + bool is_rgb() const; + bool is_rgba() const; + unsigned int bit_depth() const; + long nr() const; + long nc() const; + + template + void get_image(image_type& image) const + { +#ifndef DLIB_JXL_SUPPORT + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + You are getting this error because you are trying to use the jxl_loader + object but you haven't defined DLIB_JXL_SUPPORT. You must do so to use + this object. You must also make sure you set your build environment + to link against the libjxl library. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + static_assert(sizeof(image_type) == 0, "JPEG XL support not enabled."); +#endif + image_view vimg(image); + vimg.set_size(height, width); + using pixel_type = typename image_traits::pixel_type; + + // Fast path: rgb, rgb_alpha, grayscale with matching input depth + if (pixel_traits::num == depth && ( + pixel_traits::rgb || + pixel_traits::rgb_alpha || + (pixel_traits::grayscale && + std::is_same::basic_pixel_type, unsigned char>())) + ) + { + const size_t output_size = width * height * depth; + unsigned char* output = reinterpret_cast(image_data(vimg)); + decode(output, output_size); + return; + } + + // Manual decoding: we still need to handle the case wether the input data has alpha. + if (depth == 4) + { + array2d decoded; + decoded.set_size(height, width); + unsigned char* output = reinterpret_cast(image_data(decoded)); + decode(output, width * height * depth); + assign_image(vimg, decoded); + } + else + { + array2d decoded; + decoded.set_size(height, width); + unsigned char* output = reinterpret_cast(image_data(decoded)); + decode(output, width * height * depth); + assign_image(vimg, decoded); + } + } + + private: + void get_info(); + void decode(unsigned char *out, const size_t out_size) const; + uint32_t height; + uint32_t width; + uint32_t depth; + uint32_t bits_per_sample; + std::vector data; + }; + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + void load_jxl ( + image_type& image, + const std::string& file_name + ) + { + jxl_loader(file_name).get_image(image); + } + + template < + typename image_type + > + void load_jxl ( + image_type& image, + const unsigned char* imgbuff, + size_t imgbuffsize + ) + { + jxl_loader(imgbuff, imgbuffsize).get_image(image); + } + + template < + typename image_type + > + void load_jxl ( + image_type& image, + const char* imgbuff, + size_t imgbuffsize + ) + { + jxl_loader(reinterpret_cast(imgbuff), imgbuffsize).get_image(image); + } + +// ---------------------------------------------------------------------------------------- + +} + +#ifdef NO_MAKEFILE +#include "jxl_loader.cpp" +#endif + +#endif // DLIB_JXL_IMPORT diff --git a/dlib/image_loader/jxl_loader_abstract.h b/dlib/image_loader/jxl_loader_abstract.h new file mode 100644 index 0000000000..b50104f0fb --- /dev/null +++ b/dlib/image_loader/jxl_loader_abstract.h @@ -0,0 +1,223 @@ +// Copyright (C) 2024 Davis E. King (davis@dlib.net), Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#undef DLIB_JXL_IMPORT_ABSTRACT +#ifdef DLIB_JXL_IMPORT_ABSTRACT + +#include "image_loader_abstract.h" +#include "../algs.h" +#include "../pixel.h" +#include "../dir_nav.h" +#include "../image_processing/generic_image.h" + +namespace dlib +{ + + class jxl_loader : noncopyable + { + /*! + WHAT THIS OBJECT REPRESENTS + This object represents a class capable of loading JPEG XL image files. + Once an instance of it is created to contain a JPEG XL file from + disk you can obtain the image stored in it via get_image(). + !*/ + + public: + + jxl_loader( + const char* filename + ); + /*! + ensures + - loads the JPEG XL file with the given file name into this object + throws + - std::bad_alloc + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given JPEG XL file. + !*/ + + jxl_loader( + const std::string& filename + ); + /*! + ensures + - loads the JPEG XL file with the given file name into this object + throws + - std::bad_alloc + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given JPEG XL file. + !*/ + + jxl_loader( + const dlib::file& f + ); + /*! + ensures + - loads the JPEG XL file with the given file name into this object + throws + - std::bad_alloc + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given JPEG XL file. + !*/ + + jxl_loader( + const unsigned char* imgbuffer, + size_t buffersize + ); + /*! + ensures + - loads the JPEG XL from memory imgbuffer of size buffersize into + this object + throws + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given JPEG XL buffer. + !*/ + + ~jxl_loader( + ); + /*! + ensures + - all resources associated with *this has been released + !*/ + + template< + typename image_type + > + void get_image( + image_type& img + ) const; + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - loads the JPEG XL image stored in this object into img + !*/ + + }; + +// ---------------------------------------------------------------------------------------- + + bool is_gray( + ) const; + /*! + ensures + - if (this object contains a grayscale image without an alpha channel) + then + - returns true + - else + - returns false + !*/ + + bool is_graya( + ) const; + /*! + ensures + - if (this object contains a grayscale image with an alpha channel) then + - returns true + - else + - returns false + !*/ + + bool is_rgb( + ) const; + /*! + ensures + - if (this object contains a 3 channel RGB image) then + - returns true + - else + - returns false + !*/ + + bool is_rgba( + ) const; + /*! + ensures + - if (this object contains a 4 channel RGB alpha image) then + - returns true + - else + - returns false + !*/ + + unsigned int bit_depth ( + ) const; + /*! + ensures + - returns the number of bits per channel in the image contained by this + object. + !*/ + + long nr ( + ) const; + /*! + ensures + - returns the number of rows (height) of the image contained by this + object. + !*/ + + long nc ( + ) const; + /*! + ensures + - returns the number of colums (width) of the image contained by this + object. + !*/ + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + void load_jxl ( + image_type& image, + const std::string& file_name + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - performs: jxl_loader(file_name).get_image(image); + !*/ + + template < + typename image_type + > + void load_jxl ( + image_type& image, + const unsigned char* imgbuff, + size_t imgbuffsize + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - performs: jxl_loader(imgbuff, imgbuffsize).get_image(image); + !*/ + + template < + typename image_type + > + void load_jxl ( + image_type& image, + const char* imgbuff, + size_t imgbuffsize + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - performs: jxl_loader((unsigned char*)imgbuff, imgbuffsize).get_image(image); + !*/ + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_JXL_IMPORT_ABSTRACT + diff --git a/dlib/image_loader/load_image.h b/dlib/image_loader/load_image.h index 9e2c3b0960..d6130b01fd 100644 --- a/dlib/image_loader/load_image.h +++ b/dlib/image_loader/load_image.h @@ -8,6 +8,7 @@ #include "png_loader.h" #include "jpeg_loader.h" #include "webp_loader.h" +#include "jxl_loader.h" #include "image_loader.h" #include #include @@ -27,6 +28,7 @@ namespace dlib DNG, GIF, WEBP, + JXL, UNKNOWN }; @@ -43,20 +45,23 @@ namespace dlib // Determine the true image type using link: // http://en.wikipedia.org/wiki/List_of_file_signatures static const char *pngHeader = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; + static const char *jxlHeader = "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A"; - if (memcmp(buffer, pngHeader, strlen(pngHeader)) == 0) - return PNG; - else if(buffer[0]=='\xff' && buffer[1]=='\xd8' && buffer[2]=='\xff') + if (buffer[0]=='\xff' && buffer[1]=='\xd8' && buffer[2]=='\xff') return JPG; - else if(buffer[0]=='B' && buffer[1]=='M') + else if (memcmp(buffer, pngHeader, strlen(pngHeader)) == 0) + return PNG; + else if (buffer[0]=='B' && buffer[1]=='M') return BMP; - else if(buffer[0]=='D' && buffer[1]=='N' && buffer[2] == 'G') + else if (buffer[0]=='D' && buffer[1]=='N' && buffer[2] == 'G') return DNG; - else if(buffer[0]=='G' && buffer[1]=='I' && buffer[2] == 'F') + else if (buffer[0]=='G' && buffer[1]=='I' && buffer[2] == 'F') return GIF; - else if(buffer[0]=='R' && buffer[1]=='I' && buffer[2] == 'F' && buffer[3] == 'F' && + else if (buffer[0]=='R' && buffer[1]=='I' && buffer[2] == 'F' && buffer[3] == 'F' && buffer[8]=='W' && buffer[9]=='E' && buffer[10] == 'B' && buffer[11] == 'P') return WEBP; + else if (memcmp(buffer, jxlHeader, 12) == 0) // we can't use strlen because the header starts with \x00. + return JXL; return UNKNOWN; } @@ -91,6 +96,9 @@ namespace dlib #ifdef DLIB_WEBP_SUPPORT case image_file_type::WEBP: load_webp(image, file_name); return; #endif +#ifdef DLIB_JXL_SUPPORT + case image_file_type::JXL: load_jxl(image, file_name); return; +#endif #ifdef DLIB_GIF_SUPPORT case image_file_type::GIF: { @@ -218,6 +226,40 @@ namespace dlib sout << "Note that you must cause DLIB_GIF_SUPPORT to be defined for your entire project.\n"; sout << "So don't #define it in one file. Instead, use a compiler switch like -DDLIB_GIF_SUPPORT\n"; sout << "so it takes effect for your entire application."; +#endif + throw image_load_error(sout.str()); + } + else if (im_type == image_file_type::WEBP) + { + std::ostringstream sout; + sout << "Unable to load image in file " + file_name + ".\n" + + "You must #define DLIB_WEBP_SUPPORT and link to libwebp to read WebP files.\n" + + "Do this by following the instructions at http://dlib.net/compile.html.\n\n"; +#ifdef _MSC_VER + sout << "Note that you must cause DLIB_WEBP_SUPPORT to be defined for your entire project.\n"; + sout << "So don't #define it in one file. Instead, add it to the C/C++->Preprocessor->Preprocessor Definitions\n"; + sout << "field in Visual Studio's Property Pages window so it takes effect for your entire application."; +#else + sout << "Note that you must cause DLIB_WEBP_SUPPORT to be defined for your entire project.\n"; + sout << "So don't #define it in one file. Instead, use a compiler switch like -DDLIB_WEBP_SUPPORT\n"; + sout << "so it takes effect for your entire application."; +#endif + throw image_load_error(sout.str()); + } + else if (im_type == image_file_type::JXL) + { + std::ostringstream sout; + sout << "Unable to load image in file " + file_name + ".\n" + + "You must #define DLIB_JXL_SUPPORT and link to libjxl to read JPEG XL files.\n" + + "Do this by following the instructions at http://dlib.net/compile.html.\n\n"; +#ifdef _MSC_VER + sout << "Note that you must cause DLIB_JXL_SUPPORT to be defined for your entire project.\n"; + sout << "So don't #define it in one file. Instead, add it to the C/C++->Preprocessor->Preprocessor Definitions\n"; + sout << "field in Visual Studio's Property Pages window so it takes effect for your entire application."; +#else + sout << "Note that you must cause DLIB_JXL_SUPPORT to be defined for your entire project.\n"; + sout << "So don't #define it in one file. Instead, use a compiler switch like -DDLIB_JXL_SUPPORT\n"; + sout << "so it takes effect for your entire application."; #endif throw image_load_error(sout.str()); } diff --git a/dlib/image_saver/save_jxl.cpp b/dlib/image_saver/save_jxl.cpp new file mode 100644 index 0000000000..567b286938 --- /dev/null +++ b/dlib/image_saver/save_jxl.cpp @@ -0,0 +1,167 @@ +// 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 +#include +#include + +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); + if (JXL_ENC_SUCCESS != JxlEncoderUseContainer(enc.get(), JXL_TRUE)) + { + throw image_save_error("jxl_saver: JxlEncoderUseContainer failed"); + } + + 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(const_cast(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 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(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_ + diff --git a/dlib/image_saver/save_jxl.h b/dlib/image_saver/save_jxl.h new file mode 100644 index 0000000000..15ee59e66c --- /dev/null +++ b/dlib/image_saver/save_jxl.h @@ -0,0 +1,119 @@ +// Copyright (C) 2024 Davis E. King (davis@dlib.net), Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_SAVE_JXL_Hh_ +#define DLIB_SAVE_JXL_Hh_ + +#include "save_jxl_abstract.h" + +#include "../enable_if.h" +#include "image_saver.h" +#include "../matrix.h" +#include "../array2d.h" +#include "../pixel.h" +#include "../image_processing/generic_image.h" +#include + +namespace dlib +{ + +// ---------------------------------------------------------------------------------------- + + namespace impl + { + void impl_save_jxl ( + const std::string& filename, + const uint8_t* data, + const uint32_t width, + const uint32_t height, + const uint32_t num_channels, + const float quality + ); + } + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + typename disable_if>::type save_jxl ( + const image_type& img_, + const std::string& filename, + const float quality = 90 + ) + { +#ifndef DLIB_JXL_SUPPORT + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + You are getting this error because you are trying to use the save_jxl + function but you haven't defined DLIB_JXL_SUPPORT. You must do so to use + this object. You must also make sure you set your build environment + to link against the libjxl library. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + static_assert(sizeof(image_type) == 0, "JPEG XL support not enabled."); +#endif + const_image_view img(img_); + using pixel_type = typename image_traits::pixel_type; + + // make sure requires clause is not broken + DLIB_CASSERT(img.size() != 0, + "\t save_jxl()" + << "\n\t You can't save an empty image as a JPEG XL." + ); + DLIB_CASSERT(0 <= quality && quality <= 100, + "\t save_jxl()" + << "\n\t Invalid quality value." + << "\n\t quality: " << quality + ); + + auto data = reinterpret_cast(image_data(img)); + const int width = img.nc(); + const int height = img.nr(); + const int depth = pixel_traits::num; + // Fast path: rgb, rgb_alpha, 8-bit grayscale + if (pixel_traits::rgb || + pixel_traits::rgb_alpha || + (pixel_traits::grayscale && + std::is_same::basic_pixel_type, unsigned char>())) + { + impl::impl_save_jxl(filename, data, width, height, depth, quality); + } + else + { + // This is some other kind of color image so just save it as an RGB image. + if (pixel_traits::has_alpha) + { + array2d temp; + assign_image(temp, img); + auto data = reinterpret_cast(image_data(temp)); + impl::impl_save_jxl(filename, data, width, height, depth, quality); + } + else + { + array2d temp; + assign_image(temp, img); + auto data = reinterpret_cast(image_data(temp)); + impl::impl_save_jxl(filename, data, width, height, depth, quality); + } + } + } + +// ---------------------------------------------------------------------------------------- + + template < + typename EXP + > + void save_jxl( + const matrix_exp& img, + const std::string& filename, + uint32_t quality = 90 + ) + { + array2d temp; + assign_image(temp, img); + save_jxl(temp, filename, quality); + } + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_SAVE_JXL_Hh_ diff --git a/dlib/image_saver/save_jxl_abstract.h b/dlib/image_saver/save_jxl_abstract.h new file mode 100644 index 0000000000..c38aae1272 --- /dev/null +++ b/dlib/image_saver/save_jxl_abstract.h @@ -0,0 +1,55 @@ +// Copyright (C) 2024 Davis E. King (davis@dlib.net), Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#undef DLIB_SAVE_JXL_ABSTRACT_Hh_ +#ifdef DLIB_SAVE_JXL_ABSTRACT_Hh_ + +#include "../image_processing/generic_image.h" +#include "../pixel.h" +#include + +namespace dlib +{ + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + void save_jxl ( + const image_type& img, + const std::string& filename, + float quality = 90 + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h or a matrix expression + - image.size() != 0 + - quality >= 0 + ensures + - writes the image to the file indicated by filename in the JPEG XL format. + - image[0][0] will be in the upper left corner of the image. + - image[image.nr()-1][image.nc()-1] will be in the lower right corner of the + image. + - This routine can save images containing any type of pixel. However, + save_jxl() can only natively store rgb_pixel, rgb_alpha_pixel and unsigned + char pixel types. All other pixel types will be converted into one of + these types as appropriate before being saved to disk. + - The quality value determines how lossy the compression is. Larger quality + values result in larger output images but the images will look better. + Although it can range from 0 to 100, the recommended range is between + 68 and 96. A value of 90 means visually lossless, while a value of 100 + means mathematically lossless. + throws + - image_save_error + This exception is thrown if there is an error that prevents us from saving + the image. + - std::bad_alloc + !*/ + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_SAVE_JXL_ABSTRACT_Hh_ + diff --git a/dlib/image_saver/save_webp.h b/dlib/image_saver/save_webp.h index e5c00a7168..5a76ed0ee6 100644 --- a/dlib/image_saver/save_webp.h +++ b/dlib/image_saver/save_webp.h @@ -123,4 +123,4 @@ namespace dlib } -#endif // DLIB_WEBP_SUPPORT +#endif // DLIB_SAVE_WEBP_Hh_ diff --git a/tools/imglab/src/cluster.cpp b/tools/imglab/src/cluster.cpp index 4aaeead9b1..8fcec3418d 100644 --- a/tools/imglab/src/cluster.cpp +++ b/tools/imglab/src/cluster.cpp @@ -301,7 +301,16 @@ int cluster_dataset( if (assignments[i].c == c) temp.push_back(images[assignments[i].idx]); } - +#ifdef DLIB_JXL_SUPPORT + if (parser.option("jxl")) + { + string outfile = "cluster_"+pad_int_with_zeros(c+1, 3) + ".jxl"; + cout << "Saving " << outfile << endl; + const float jxl_quality = std::stof(parser.option("jxl").argument()); + save_jxl(tile_images(temp), outfile, jxl_quality); + } + else +#endif #ifdef DLIB_WEBP_SUPPORT if (parser.option("webp")) { diff --git a/tools/imglab/src/common.cpp b/tools/imglab/src/common.cpp index d23947be92..be57eb6e26 100644 --- a/tools/imglab/src/common.cpp +++ b/tools/imglab/src/common.cpp @@ -58,6 +58,18 @@ std::string to_jpg_name (const std::string& filename) // ---------------------------------------------------------------------------------------- +#ifdef DLIB_JXL_SUPPORT +std::string to_jxl_name (const std::string& filename) +{ + std::string::size_type pos = filename.find_last_of("."); + if (pos == std::string::npos) + throw dlib::error("invalid filename: " + filename); + return filename.substr(0,pos) + ".jxl"; +} +#endif + +// ---------------------------------------------------------------------------------------- + #ifdef DLIB_WEBP_SUPPORT std::string to_webp_name (const std::string& filename) { diff --git a/tools/imglab/src/common.h b/tools/imglab/src/common.h index 3806b2b75c..37ff671e64 100644 --- a/tools/imglab/src/common.h +++ b/tools/imglab/src/common.h @@ -34,6 +34,9 @@ void make_empty_file ( std::string to_png_name (const std::string& filename); std::string to_jpg_name (const std::string& filename); +#ifdef DLIB_JXL_SUPPORT +std::string to_jxl_name (const std::string& filename); +#endif #ifdef DLIB_WEBP_SUPPORT std::string to_webp_name (const std::string& filename); #endif diff --git a/tools/imglab/src/flip_dataset.cpp b/tools/imglab/src/flip_dataset.cpp index 9431531679..19ef4e0123 100644 --- a/tools/imglab/src/flip_dataset.cpp +++ b/tools/imglab/src/flip_dataset.cpp @@ -219,6 +219,14 @@ void flip_dataset(const command_line_parser& parser) filename = to_jpg_name(filename); save_jpeg(temp, filename,JPEG_QUALITY); } +#ifdef DLIB_JXL_SUPPORT + else if (parser.option("jxl")) + { + filename = to_jxl_name(filename); + const float jxl_quality = std::stof(parser.option("jxl").argument()); + save_webp(temp, filename, jxl_quality); + } +#endif #ifdef DLIB_WEBP_SUPPORT else if (parser.option("webp")) { diff --git a/tools/imglab/src/main.cpp b/tools/imglab/src/main.cpp index 57704599a2..ae93e6a4b3 100644 --- a/tools/imglab/src/main.cpp +++ b/tools/imglab/src/main.cpp @@ -21,7 +21,7 @@ #include -const char* VERSION = "1.20"; +const char* VERSION = "1.21"; @@ -61,8 +61,8 @@ void create_new_dataset ( // then parser[i] should be a directory std::vector files = get_files_in_directory_tree(parser[i], - match_endings(".png .PNG .jpeg .JPEG .jpg .JPG .bmp .BMP .dng .DNG .gif .GIF .webp .WEBP"), - depth); + match_endings(".png .PNG .jpeg .JPEG .jpg .JPG .bmp .BMP .dng .DNG .gif .GIF .jxl .JXL .webp .WEBP"), + depth); sort(files.begin(), files.end()); for (unsigned long j = 0; j < files.size(); ++j) @@ -339,6 +339,14 @@ void rotate_dataset(const command_line_parser& parser) filename = to_jpg_name(filename); save_jpeg(temp, filename,JPEG_QUALITY); } +#ifdef DLIB_JXL_SUPPORT + else if (parser.option("jxl")) + { + filename = to_jxl_name(filename); + const float jxl_quality = std::stof(parser.option("jxl").argument()); + save_jxl(temp, filename, jxl_quality); + } +#endif #ifdef DLIB_WEBP_SUPPORT else if (parser.option("webp")) { @@ -491,6 +499,14 @@ int resample_dataset(const command_line_parser& parser) dimg.filename = to_jpg_name(dimg.filename); save_jpeg(chip,dimg.filename, JPEG_QUALITY); } +#ifdef DLIB_JXL_SUPPORT + else if (parser.option("jxl")) + { + dimg.filename = to_jxl_name(dimg.filename); + const float jxl_quality = std::stof(parser.option("jxl").argument()); + save_jxl(chip, dimg.filename, jxl_quality); + } +#endif #ifdef DLIB_WEBP_SUPPORT else if (parser.option("webp")) { @@ -524,9 +540,23 @@ int tile_dataset(const command_line_parser& parser) string out_image = parser.option("tile").argument(); string ext = right_substr(out_image,"."); - if (ext != "png" && ext != "jpg" && ext != "webp") + if (ext != "png" && ext != "jpg" +#if DLIB_JXL_SUPPORT + && ext != "jxl" +#endif +#if DLIB_WEBP_SUPPORT + && ext != "webp" +#endif + ) { - cerr << "The output image file must have either .png, .jpg or .webp extension." << endl; + cerr << "The output image file must have one of these extensions: .png, .jpg" << endl; +#if DLIB_JXL_SUPPORT + cerr << ", .jxl"; +#endif +#if DLIB_WEBP_SUPPORT + cerr << ", .webp"; +#endif + cerr << "."; return EXIT_FAILURE; } @@ -571,6 +601,14 @@ int tile_dataset(const command_line_parser& parser) { save_png(tile_images(images), out_image); } +#ifdef DLIB_JXL_SUPPORT + else if (ext == "jxl") + { + // Lossless by default + const float jxl_quality = get_option(parser, "jxl", 100.f); + save_jxl(tile_images(images), out_image, jxl_quality); + } +#endif #ifdef DLIB_WEBP_SUPPORT else if (ext == "webp") { @@ -657,8 +695,11 @@ int main(int argc, char** argv) parser.add_option("rmignore","Remove all boxes marked ignore and save the results to a new XML file."); parser.add_option("rm-if-overlaps","Remove all boxes labeled if they overlap any box not labeled and save the results to a new XML file.",1); parser.add_option("jpg", "When saving images to disk, write them as jpg files instead of png."); +#ifdef DLIB_JXL_SUPPORT + parser.add_option("jxl", "When saving images to disk, write them as jxl files instead of png, using as the quality factor.", 1); +#endif #ifdef DLIB_WEBP_SUPPORT - parser.add_option("webp", "When saving images to disk, write them as webp files instead of png or jpg, using as the quality factor.", 1); + parser.add_option("webp", "When saving images to disk, write them as webp files instead of png, using as the quality factor.", 1); #endif parser.set_group_name("Cropping sub images"); @@ -775,8 +816,14 @@ int main(int argc, char** argv) parser.check_incompatible_options("rmtrunc", "ignore"); parser.check_incompatible_options("box-images", "rename"); parser.check_incompatible_options("box-images", "ignore"); +#ifdef DLIB_JXL_SUPPORT + parser.check_incompatible_options("jpg", "jxl"); +#endif #ifdef DLIB_WEBP_SUPPORT parser.check_incompatible_options("jpg", "webp"); +#endif +#if DLIB_JXL_SUPPORT && DLIB_WEBP_SUPPORT + parser.check_incompatible_options("jxl", "webp"); #endif const char* convert_args[] = {"pascal-xml","pascal-v1","idl"}; parser.check_option_arg_range("convert", convert_args); @@ -787,6 +834,9 @@ int main(int argc, char** argv) parser.check_option_arg_range("cropped-object-size", 4, 10000*10000); parser.check_option_arg_range("crop-size", 1.0, 100.0); parser.check_option_arg_range("split-train-test", 0.0, 1.0); +#ifdef DLIB_JXL_SUPPORT + parser.check_option_arg_range("jxl", 0.f, 100.f); +#endif #ifdef DLIB_WEBP_SUPPORT parser.check_option_arg_range("webp", 0.f, std::numeric_limits::max()); #endif diff --git a/tools/python/src/numpy_returns.cpp b/tools/python/src/numpy_returns.cpp index 1954ba3825..e32480ca5d 100644 --- a/tools/python/src/numpy_returns.cpp +++ b/tools/python/src/numpy_returns.cpp @@ -38,21 +38,47 @@ bool has_ending (std::string const full_string, std::string const &ending) { // ---------------------------------------------------------------------------------------- template -void save_image(numpy_image img, const std::string &path) +void save_image(numpy_image img, const std::string &path, const float quality) { std::string lowered_path = path; std::transform(lowered_path.begin(), lowered_path.end(), lowered_path.begin(), ::tolower); + std::string error_message = "Unsupported image type, image path must end with one of [.bmp, .dng"; +#if DLIB_PNG_SUPPORT + error_message += ", .png"; +#endif +#if DLIB_JPEG_SUPPORT + error_message += ", .jpg, jpeg"; +#endif +#if DLIB_WEBP_SUPPORT + error_message += ", .webp"; +#endif +#if DLIB_JXL_SUPPORT + error_message += ", .jxl"; +#endif + error_message += "]"; if(has_ending(lowered_path, ".bmp")) { save_bmp(img, path); } else if(has_ending(lowered_path, ".dng")) { save_dng(img, path); +#if DLIB_PNG_SUPPORT } else if(has_ending(lowered_path, ".png")) { save_png(img, path); +#endif +#if DLIB_JPEG_SUPPORT } else if(has_ending(lowered_path, ".jpg") || has_ending(lowered_path, ".jpeg")) { - save_jpeg(img, path); + save_jpeg(img, path, put_in_range(0, 100, std::lround(quality))); +#endif +#if DLIB_WEBP_SUPPORT + } else if(has_ending(lowered_path, ".webp")) { + save_webp(img, path, std::max(0.f, quality)); +#endif +#if DLIB_JXL_SUPPORT + } else if(has_ending(lowered_path, ".jxl")) { + save_jxl(img, path, put_in_range(0, 100, quality)); +#endif } else { - throw dlib::error("Unsupported image type, image path must end with one of [.bmp, .png, .dng, .jpg, .jpeg]"); + throw dlib::error(error_message); } return; } @@ -140,11 +166,11 @@ void bind_numpy_returns(py::module &m) m.def("save_image", &save_image, "Saves the given image to the specified path. Determines the file type from the file extension specified in the path", - py::arg("img"), py::arg("filename") + py::arg("img"), py::arg("filename"), py::arg("quality") = 75 ); m.def("save_image", &save_image, "Saves the given image to the specified path. Determines the file type from the file extension specified in the path", - py::arg("img"), py::arg("filename") + py::arg("img"), py::arg("filename"), py::arg("quality") = 75 ); m.def("jitter_image", &get_jitter_images,