Skip to content

Commit

Permalink
Merge pull request #12733 from victoryforce/jpegxl-read-support-using…
Browse files Browse the repository at this point in the history
…-libjxl

Implementation of the JPEG XL image loader using libjxl
  • Loading branch information
TurboGit authored Nov 4, 2022
2 parents aa2691b + 94e67e4 commit 516a698
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 5 deletions.
2 changes: 1 addition & 1 deletion DefineOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ option(BINARY_PACKAGE_BUILD "Sets march optimization to generic" OFF)
option(USE_XMLLINT "Run xmllint to test if darktableconfig.xml is valid" ON)
option(USE_PORTMIDI "Enable MIDI device support using PortMidi" ON)
option(USE_OPENJPEG "Enable JPEG 2000 support" ON)
option(USE_JXL "Enable JPEG XL export support" ON)
option(USE_JXL "Enable JPEG XL support" ON)
option(USE_WEBP "Enable WebP export support" ON)
option(USE_AVIF "Enable AVIF support" ON)
option(USE_HEIF "Enable HEIF/HEIC support" ON)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Optional dependencies (minimum version):
* Imath 3.1.0 *(for 16-bit half float TIFF export and faster import)*
* libavif 0.8.2 *(for AVIF import & export)*
* libheif 1.9.0 *(for HEIF/HEIC/HIF import; also for AVIF import if no libavif)*
* libjxl 0.7.0 *(for JPEG XL export)*
* libjxl 0.7.0 *(for JPEG XL import & export)*
* WebP 0.3.0 *(for WebP import & export)*

Optional dependencies (no version requirement):
Expand Down
3 changes: 3 additions & 0 deletions data/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ if(NOT WIN32)
if("jp2" IN_LIST DT_SUPPORTED_EXTENSIONS)
list(APPEND DESKTOP_MIME_TYPES image/jp2)
endif()
if("jxl" IN_LIST DT_SUPPORTED_EXTENSIONS)
list(APPEND DESKTOP_MIME_TYPES image/jxl)
endif()
if("webp" IN_LIST DT_SUPPORTED_EXTENSIONS)
list(APPEND DESKTOP_MIME_TYPES image/webp)
endif()
Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,9 @@ if(USE_JXL)
if(JXL_FOUND)
include_directories(SYSTEM ${JXL_INCLUDE_DIRS})
list(APPEND LIBS ${JXL_LIBRARIES})
add_definitions(${JXL_DEFINITIONS})
add_definitions("-DHAVE_LIBJXL")
list(APPEND SOURCES "common/imageio_jpegxl.c")
set(DT_SUPPORTED_EXTENSIONS ${DT_SUPPORTED_EXTENSIONS} jxl CACHE INTERNAL "")
endif(JXL_FOUND)
endif(USE_JXL)

Expand Down
6 changes: 6 additions & 0 deletions src/common/darktable.c
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,12 @@ int dt_init(int argc, char *argv[], const gboolean init_gui, const gboolean load
" libheif support disabled\n"
#endif

#ifdef HAVE_LIBJXL
" libjxl support enabled\n"
#else
" libjxl support disabled\n"
#endif

#ifdef HAVE_OPENJPEG
" OpenJPEG support enabled\n"
#else
Expand Down
6 changes: 4 additions & 2 deletions src/common/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ typedef enum dt_image_loader_t
LOADER_HEIF = 13,
LOADER_LIBRAW = 14,
LOADER_WEBP = 15,
LOADER_COUNT = 16, // keep last
LOADER_JPEGXL = 16,
LOADER_COUNT = 17 // keep last
} dt_image_loader_t;

static const struct
Expand All @@ -202,7 +203,8 @@ static const struct
{ N_("ImageMagick"), 'i'},
{ N_("heif"), 'h'},
{ N_("libraw"), 'l'},
{ N_("webp"), 'w'}
{ N_("webp"), 'w'},
{ N_("jpeg xl"), 'L'}
};

typedef struct dt_image_geoloc_t
Expand Down
8 changes: 8 additions & 0 deletions src/common/imageio.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
#ifdef HAVE_OPENJPEG
#include "common/imageio_j2k.h"
#endif
#ifdef HAVE_LIBJXL
#include "common/imageio_jpegxl.h"
#endif
#include "common/image_compression.h"
#include "common/imageio_gm.h"
#include "common/imageio_im.h"
Expand Down Expand Up @@ -1286,6 +1289,11 @@ dt_imageio_retval_t dt_imageio_open(dt_image_t *img, // non-const
/* check if file is ldr using magic's */
if(dt_imageio_is_ldr(filename)) ret = dt_imageio_open_ldr(img, filename, buf);

#ifdef HAVE_LIBJXL
if(ret != DT_IMAGEIO_OK && ret != DT_IMAGEIO_CACHE_FULL)
ret = dt_imageio_open_jpegxl(img, filename, buf);
#endif

/* silly check using file extensions: */
if(ret != DT_IMAGEIO_OK && ret != DT_IMAGEIO_CACHE_FULL && dt_imageio_is_hdr(filename))
ret = dt_imageio_open_hdr(img, filename, buf);
Expand Down
251 changes: 251 additions & 0 deletions src/common/imageio_jpegxl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
This file is part of darktable,
Copyright (C) 2022 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/

#include <inttypes.h>

#include <jxl/decode.h>
#include <jxl/resizable_parallel_runner.h>

#include "image.h"
#include "imageio.h"

dt_imageio_retval_t dt_imageio_open_jpegxl(dt_image_t *img, const char *filename, dt_mipmap_buffer_t *mbuf)
{

JxlDecoderStatus status;
JxlBasicInfo basicinfo;
size_t icc_size = 0;
uint32_t num_threads;

FILE* inputfile = g_fopen(filename, "rb");

if(!inputfile)
{
fprintf(stderr, "[jpegxl_open] Cannot open file for read: %s\n", filename);
return DT_IMAGEIO_FILE_CORRUPTED;
}

fseek(inputfile, 0, SEEK_END);
size_t inputFileSize = ftell(inputfile);
fseek(inputfile, 0, SEEK_SET);

void* read_buffer = malloc(inputFileSize);

if(fread(read_buffer, 1, inputFileSize, inputfile) != inputFileSize)
{
fprintf(stderr, "[jpegxl_open] Failed to read %zu bytes: %s\n", inputFileSize, filename);
free(read_buffer);
fclose(inputfile);
return DT_IMAGEIO_FILE_CORRUPTED;
}
fclose(inputfile);

JxlSignature signature = JxlSignatureCheck(read_buffer, inputFileSize);

if(signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER)
{
// It's normal if this function is called for a non-jxl file, so we should fail silently.
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

const JxlPixelFormat pixel_format =
{
4, // number of channels
JXL_TYPE_FLOAT, // channel depth
JXL_NATIVE_ENDIAN, // endianness
0 // align
};

JxlDecoder *decoder = JxlDecoderCreate(NULL);

if(!decoder)
{
fprintf(stderr, "[jpegxl_open] ERROR: JxlDecoderCreate failed\n");
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

JxlParallelRunner *runner = JxlResizableParallelRunnerCreate(NULL);
if(!runner)
{
fprintf(stderr, "[jpegxl_open] ERROR: JxlResizableParallelRunnerCreate failed\n");
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

if(JxlDecoderSetInput(decoder, read_buffer, inputFileSize) != JXL_DEC_SUCCESS)
{
fprintf(stderr, "[jpegxl_open] ERROR: JxlDecoderSetInput failed\n");
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

if(JxlDecoderSubscribeEvents(decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS)
{
fprintf(stderr, "[jpegxl_open] ERROR: JxlDecoderSubscribeEvents failed\n");
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

if(JxlDecoderSetParallelRunner(decoder, JxlResizableParallelRunner, runner) != JXL_DEC_SUCCESS)
{
fprintf(stderr, "[jpegxl_open] ERROR: JxlDecoderSetParallelRunner failed\n");
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}


// Grand Decoding Loop
while(1)
{
status = JxlDecoderProcessInput(decoder);

if(status == JXL_DEC_ERROR)
{
fprintf(stderr, "[jpegxl_open] ERROR: JXL decoding failed\n");
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

if(status == JXL_DEC_NEED_MORE_INPUT)
{
fprintf(stderr, "[jpegxl_open] ERROR: JXL data incomplete\n");
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

if(status == JXL_DEC_BASIC_INFO)
{
if(JxlDecoderGetBasicInfo(decoder, &basicinfo) != JXL_DEC_SUCCESS)
{
fprintf(stderr, "[jpegxl_open] ERROR: JXL basic info not available\n");
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}

// Unlikely to happen, but let there be a sanity check
if(basicinfo.xsize == 0 || basicinfo.ysize == 0)
{
fprintf(stderr,"[jpegxl_open] ERROR: JXL image declares zero dimensions\n");
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}


num_threads = JxlResizableParallelRunnerSuggestThreads(basicinfo.xsize, basicinfo.ysize);
JxlResizableParallelRunnerSetThreads(runner, num_threads);

continue; // go to next loop iteration to process rest of the input
}

if(status == JXL_DEC_COLOR_ENCODING)
{
if(JxlDecoderGetICCProfileSize(decoder, &pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS)
{
if(icc_size)
{
img->profile_size = icc_size;
img->profile = (uint8_t *)g_malloc0(icc_size);
JxlDecoderGetColorAsICCProfile(decoder, &pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, img->profile, icc_size);
}
} else
{
// According to libjxl docs, the only situation where an ICC profile is not available is when the image has an
// unknown or xyb color space. But dt does not support such images, so in this case we should refuse to import
// the image. If in the future dt will support the xyb color space, we can add code here to handle that case.
fprintf(stderr, "[jpegxl_open] WARNING: the imaga '%s' has an unknown or xyb color space. We do not import such images\n", filename);
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
free(read_buffer);
return DT_IMAGEIO_FILE_CORRUPTED;
}
continue; // go to next iteration to process rest of the input
}

if(status == JXL_DEC_NEED_IMAGE_OUT_BUFFER)
{
img->width = basicinfo.xsize;
img->height = basicinfo.ysize;
img->buf_dsc.channels = 4;
img->buf_dsc.datatype = TYPE_FLOAT;
float *mipbuf = (float *)dt_mipmap_cache_alloc(mbuf, img);
if(!mipbuf)
{
JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);
g_free(read_buffer);
fprintf(stderr, "[jpegxl_open] could not alloc full buffer for image: %s\n", img->filename);
return DT_IMAGEIO_CACHE_FULL;
}
JxlDecoderSetImageOutBuffer(decoder, &pixel_format, mipbuf, basicinfo.xsize * basicinfo.ysize * 4 * 4);
continue; // go to next iteration to process rest of the input
}

// If the image is an animation, more full frames may be decoded. We do not check and reject the image
// if it is an animation, but only read the first frame. It hardly makes sense to process such an image,
// but perhaps the user intends to use dt as a DAM for such images.
if (status == JXL_DEC_FULL_IMAGE)
break; // Terminate processing

} // end of processing loop


JxlResizableParallelRunnerDestroy(runner);
JxlDecoderDestroy(decoder);

// Set all needed type flags and make a record about the loader
img->buf_dsc.filters = 0u;
img->flags &= ~DT_IMAGE_RAW;
img->flags &= ~DT_IMAGE_S_RAW;
img->loader = LOADER_JPEGXL;

// JXL can be LDR or HDR. But if channel width does not exceed 8 bit it must be LDR.
if(basicinfo.bits_per_sample <= 8)
{
img->flags &= ~DT_IMAGE_HDR;
img->flags |= DT_IMAGE_LDR;
} else
{
img->flags &= ~DT_IMAGE_LDR;
img->flags |= DT_IMAGE_HDR;
}

return DT_IMAGEIO_OK;
}

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on
30 changes: 30 additions & 0 deletions src/common/imageio_jpegxl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
This file is part of darktable,
Copyright (C) 2022 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "common/image.h"
#include "common/mipmap_cache.h"

dt_imageio_retval_t dt_imageio_open_jpegxl(dt_image_t *img, const char *filename, dt_mipmap_buffer_t *buf);

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on

0 comments on commit 516a698

Please sign in to comment.