Skip to content

Initial JPEG XL decoder #755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .ci/Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ brew 'cmake'
brew 'git'
brew 'libomp'
brew 'jpeg-turbo'
brew 'jpeg-xl'
brew 'ninja'
brew 'pkgconf'
brew 'pugixml'
2 changes: 2 additions & 0 deletions .github/workflows/CI-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ jobs:
git \
googletest \
libjpeg-dev \
libjxl-dev \
libpugixml-dev \
libxml2-utils \
ninja-build \
pkgconf \
zlib1g-dev
if [ "$COMPILER_FAMILY" = "GNU" ]; then
eatmydata apt install g++-${{ inputs.compiler-version }} \
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/CI-windows-msys2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ jobs:
libxml2:p
pugixml:p
libjpeg-turbo:p
libjxl:p
zlib:p
- name: Install Additional Dependencies (Coverage)
if: inputs.flavor == 'Coverage'
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ else()
set(ALLOW_DOWNLOADING_PUGIXML OFF CACHE BOOL "If pugixml src tree is not found in location specified by PUGIXML_PATH, do fetch the archive from internet" FORCE)
endif()
option(WITH_JPEG "Enable JPEG support for DNG Lossy JPEG support" ON)
option(WITH_JXL "Enable JPEG XL support for DNG JPEG XL support" ON)
option(WITH_ZLIB "Enable ZLIB support for DNG deflate support" ON)
if(WITH_ZLIB)
option(USE_BUNDLED_ZLIB "Build and use zlib in-tree" OFF)
Expand Down
33 changes: 33 additions & 0 deletions cmake/Modules/FindJXL.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Find libjxl
# Will define:
# - JXL_FOUND
# - JXL_INCLUDE_DIRS directory to include for libjxl headers
# - JXL_LIBRARIES libraries to link to
# - JXL_VERSION

find_package(PkgConfig QUIET REQUIRED)
pkg_check_modules(JXL_PKGCONF QUIET libjxl)

if(JXL_PKGCONF_VERSION)
set(JXL_VERSION ${JXL_PKGCONF_VERSION})
endif()

find_path(JXL_INCLUDE_DIR
NAMES jxl/decode.h
HINTS ${JXL_PKGCONF_INCLUDE_DIRS})
mark_as_advanced(JXL_INCLUDE_DIR)

find_library(JXL_LIBRARY
NAMES jxl
HINTS ${JXL_PKGCONF_LIBRARY_DIRS})
mark_as_advanced(JXL_LIBRARY)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(JXL
REQUIRED_VARS JXL_LIBRARY JXL_INCLUDE_DIR
VERSION_VAR JXL_VERSION)

if(JXL_FOUND)
set(JXL_INCLUDE_DIRS ${JXL_INCLUDE_DIR})
set(JXL_LIBRARIES ${JXL_LIBRARY})
endif()
27 changes: 27 additions & 0 deletions cmake/src-dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,33 @@ else()
endif()
add_feature_info("Lossy JPEG decoding" HAVE_JPEG "used for DNG Lossy JPEG compression decoding")

unset(HAVE_JXL)
if(WITH_JXL)
message(STATUS "Looking for JPEG XL")
find_package(JXL)
if(NOT JXL_FOUND)
message(SEND_ERROR "Did not find JPEG XL! Either make it find JPEG XL, or pass -DWITH_JXL=OFF to disable JPEG XL.")
else()
message(STATUS "Looking for JPEG XL - found")
set(HAVE_JXL 1)

if(NOT TARGET JXL::jxl)
add_library(JXL::jxl INTERFACE IMPORTED)
set_property(TARGET JXL::jxl PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${JXL_INCLUDE_DIRS}")
set_property(TARGET JXL::jxl PROPERTY INTERFACE_LINK_LIBRARIES "${JXL_LIBRARIES}")
endif()

target_link_libraries(rawspeed PRIVATE JXL::jxl)
set_package_properties(JXL PROPERTIES
TYPE RECOMMENDED
DESCRIPTION "library for handling the JPEG XL image data format, implements a JPEG XL codec"
PURPOSE "Used for decoding DNG JPEG XL compression")
endif()
else()
message(STATUS "JPEG XL is disabled, DNG JPEG XL support won't be available.")
endif()
add_feature_info("JPEG XL decoding" HAVE_JXL "used for DNG JPEG XL compression decoding")

unset(HAVE_ZLIB)
if (WITH_ZLIB)
message(STATUS "Looking for ZLIB")
Expand Down
2 changes: 2 additions & 0 deletions src/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ static_assert(RAWSPEED_LARGEPAGESIZE >= RAWSPEED_PAGESIZE,
#cmakedefine HAVE_JPEG
#cmakedefine HAVE_JPEG_MEM_SRC

#cmakedefine HAVE_JXL

#cmakedefine HAVE_CXX_THREAD_LOCAL
#cmakedefine HAVE_GCC_THREAD_LOCAL

Expand Down
17 changes: 15 additions & 2 deletions src/librawspeed/decoders/DngDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ void DngDecoder::dropUnsuportedChunks(std::vector<const TiffIFD*>* data) {
case 9: // VC-5 as used by GoPro
#ifdef HAVE_JPEG
case 0x884c: // lossy JPEG
#endif
#ifdef HAVE_JXL
case 0xcd42: // JPEG XL
#endif
// no change, if supported, then is still supported.
break;
Expand All @@ -140,6 +143,15 @@ void DngDecoder::dropUnsuportedChunks(std::vector<const TiffIFD*>* data) {
"chunk, but the jpeg support was "
"disabled at build!");
[[clang::fallthrough]];
#endif
#ifndef HAVE_JXL
case 0xcd42: // JPEG XL
#pragma message \
"JPEG XL is not present! JPEG XL compression will not be supported!"
writeLog(DEBUG_PRIO::WARNING, "DNG Decoder: found JPEG XL encoded "
"chunk, but the jpeg xl support was "
"disabled at build!");
[[clang::fallthrough]];
#endif
default:
supported = false;
Expand Down Expand Up @@ -362,10 +374,11 @@ void DngDecoder::decodeData(const TiffIFD* raw, uint32_t sample_format) const {
if (compression == 8 && sample_format != 3) {
ThrowRDE("Only float format is supported for "
"deflate-compressed data.");
} else if ((compression == 7 || compression == 0x884c) &&
} else if ((compression == 7 || compression == 0x884c ||
compression == 0xcd42) &&
sample_format != 1) {
ThrowRDE("Only 16 bit unsigned data supported for "
"JPEG-compressed data.");
"JPEG or JPEG XL compressed data.");
}

uint32_t predictor = ~0U;
Expand Down
35 changes: 35 additions & 0 deletions src/librawspeed/decompressors/AbstractDngDecompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
#include "decompressors/JpegDecompressor.h"
#endif

#ifdef HAVE_JXL
#include "decompressors/JpegXLDecompressor.h"
#endif

namespace rawspeed {

template <> void AbstractDngDecompressor::decompressThread<1>() const noexcept {
Expand Down Expand Up @@ -201,6 +205,29 @@ void AbstractDngDecompressor::decompressThread<0x884c>() const noexcept {
}
#endif

#ifdef HAVE_JXL
template <>
void AbstractDngDecompressor::decompressThread<0xcd42>() const noexcept {
#ifdef HAVE_OPENMP
#pragma omp for schedule(static)
#endif
for (const auto& e :
Array1DRef(slices.data(), implicit_cast<int>(slices.size()))) {
try {
JpegXLDecompressor j(e.bs.peekBuffer(e.bs.getRemainSize()), mRaw);
j.decode(e.offX, e.offY);
} catch (const RawDecoderException& err) {
mRaw->setError(err.what());
} catch (const IOException& err) {
mRaw->setError(err.what());
} catch (...) {
// We should not get any other exception type here.
__builtin_unreachable();
}
}
}
#endif

void AbstractDngDecompressor::decompressThread() const noexcept {
invariant(mRaw->dim.x > 0);
invariant(mRaw->dim.y > 0);
Expand Down Expand Up @@ -232,6 +259,14 @@ void AbstractDngDecompressor::decompressThread() const noexcept {
#else
#pragma message "JPEG is not present! Lossy JPEG DNG will not be supported!"
mRaw->setError("jpeg support is disabled.");
#endif
} else if (compression == 0xcd42) {
/* Lossy DNG */
#ifdef HAVE_JXL
decompressThread<0xcd42>();
#else
#pragma message "JPEG XL is not present! JPEG XL DNG will not be supported!"
mRaw->setError("jpeg xl support is disabled.");
#endif
} else
mRaw->setError("AbstractDngDecompressor: Unknown compression");
Expand Down
6 changes: 6 additions & 0 deletions src/librawspeed/decompressors/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ FILE(GLOB SOURCES
"JpegDecompressor.cpp"
"JpegDecompressor.h"
"JpegMarkers.h"
"JpegXLDecompressor.cpp"
"JpegXLDecompressor.h"
"KodakDecompressor.cpp"
"KodakDecompressor.h"
"LJpegDecoder.cpp"
Expand Down Expand Up @@ -80,4 +82,8 @@ if(WITH_JPEG AND TARGET JPEG::JPEG)
target_link_libraries(rawspeed_decompressors PUBLIC JPEG::JPEG)
endif()

if(WITH_JXL AND TARGET JXL::jxl)
target_link_libraries(rawspeed_decompressors PUBLIC JXL::jxl)
endif()

target_link_libraries(rawspeed PRIVATE rawspeed_decompressors)
154 changes: 154 additions & 0 deletions src/librawspeed/decompressors/JpegXLDecompressor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
RawSpeed - RAW file decoder.

Copyright (C) 2009-2014 Klaus Post
Copyright (C) 2017 Roman Lebedev

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "rawspeedconfig.h" // IWYU pragma: keep

#ifdef HAVE_JXL

#include "adt/Array2DRef.h"
#include "decoders/RawDecoderException.h"
#include "decompressors/JpegXLDecompressor.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <jxl/codestream_header.h>
#include <jxl/decode.h>
#include <jxl/types.h>
#include <vector>

using std::min;

namespace rawspeed {

void JpegXLDecompressor::decode(
uint32_t offX, uint32_t offY) { /* Each slice is a JPEG XL image */

JxlSignature signature = JxlSignatureCheck(input.begin(), input.getSize());

if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER)
ThrowRDE("Unable to verify JPEG XL signature");

JxlDecoder* decoder = JxlDecoderCreate(nullptr);

if (!decoder)
ThrowRDE("Unable to instantiate a JPEG XL decoder");

if (JxlDecoderSetInput(decoder, input.begin(), input.getSize()) !=
JXL_DEC_SUCCESS) {
JxlDecoderDestroy(decoder);
ThrowRDE("Unable to set input data for JPEG XL decoder");
}

if (JxlDecoderSubscribeEvents(decoder,
JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE) !=
JXL_DEC_SUCCESS) {
JxlDecoderDestroy(decoder);
ThrowRDE("Unable to subscribe to JPEG XL decoder events");
}

JxlDecoderStatus status;
JxlBasicInfo basicinfo;

const JxlPixelFormat pixel_format = {
mRaw->getCpp(), // number of channels
JXL_TYPE_UINT16, // data type
JXL_NATIVE_ENDIAN, // endianness
0 // alignment
};

std::vector<uint16_t> complete_buffer;

// Decoding loop
while (true) {
status = JxlDecoderProcessInput(decoder);

if (status == JXL_DEC_ERROR) {
JxlDecoderDestroy(decoder);
ThrowRDE("JPEG XL decoding error");
}

if (status == JXL_DEC_NEED_MORE_INPUT) {
JxlDecoderDestroy(decoder);
ThrowRDE("JPEG XL stream input data incomplete");
}

if (status == JXL_DEC_BASIC_INFO) {
if (JxlDecoderGetBasicInfo(decoder, &basicinfo) != JXL_DEC_SUCCESS) {
JxlDecoderDestroy(decoder);
ThrowRDE("JPEG XL stream basic info not available");
}

// Unlikely to happen, but let there be a sanity check
if (basicinfo.xsize == 0 || basicinfo.ysize == 0) {
JxlDecoderDestroy(decoder);
ThrowRDE("JPEG XL image declares zero dimensions");
}

if (basicinfo.num_color_channels != pixel_format.num_channels)
ThrowRDE("Component count doesn't match");

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

if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
size_t size =
basicinfo.xsize * basicinfo.ysize * basicinfo.num_color_channels;
complete_buffer.resize(size);
JxlDecoderSetImageOutBuffer(decoder, &pixel_format,
complete_buffer.data(), size);
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.
if (status == JXL_DEC_FULL_IMAGE)
break; // Terminate processing

} // end of processing loop

JxlDecoderDestroy(decoder);

const Array2DRef<uint16_t> tmp(complete_buffer.data(),
basicinfo.num_color_channels * basicinfo.xsize,
basicinfo.xsize);

// Now the image is decoded, and we copy the image data
unsigned int copy_w = min(mRaw->dim.x - offX, basicinfo.xsize);
unsigned int copy_h = min(mRaw->dim.y - offY, basicinfo.ysize);

const Array2DRef<uint16_t> out(mRaw->getU16DataAsUncroppedArray2DRef());
for (unsigned int row = 0; row < copy_h; row++) {
for (unsigned int col = 0; col < basicinfo.num_color_channels * copy_w;
col++)
out(offY + row, basicinfo.num_color_channels * offX + col) =
tmp(row, col);
}
}

} // namespace rawspeed

#else

#pragma message \
"JPEG XL is not present! JPEG XL compression will not be supported!"

#endif
Loading
Loading