Skip to content
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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_JPEGXL "Enable JPEG XL support for DNG 1.7 JPEG XL compression" 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
23 changes: 23 additions & 0 deletions cmake/src-dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,29 @@ else()
endif()
add_feature_info("Lossy JPEG decoding" HAVE_JPEG "used for DNG Lossy JPEG compression decoding")

unset(HAVE_JPEGXL)
if(WITH_JPEGXL)
message(STATUS "Looking for JPEG XL (libjxl)")
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(libjxl IMPORTED_TARGET libjxl)
endif()
if(NOT libjxl_FOUND)
message(SEND_ERROR "Did not find libjxl! Either install jpeg-xl, or pass -DWITH_JPEGXL=OFF to disable JPEG XL.")
else()
message(STATUS "Looking for JPEG XL - found (${libjxl_VERSION})")
set(HAVE_JPEGXL 1)
target_link_libraries(rawspeed PRIVATE PkgConfig::libjxl)
set_package_properties(libjxl PROPERTIES
TYPE RECOMMENDED
DESCRIPTION "JPEG XL reference codec library"
PURPOSE "Used for decoding DNG JPEG XL (DNG 1.7) compression")
endif()
else()
message(STATUS "JPEG XL is disabled, DNG JPEG XL (DNG 1.7) support won't be available.")
endif()
add_feature_info("JPEG XL decoding" HAVE_JPEGXL "used for DNG JPEG XL (DNG 1.7) 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_JPEGXL

#cmakedefine HAVE_CXX_THREAD_LOCAL
#cmakedefine HAVE_GCC_THREAD_LOCAL

Expand Down
78 changes: 77 additions & 1 deletion src/librawspeed/decoders/DngDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "common/DngOpcodes.h"
#include "common/RawImage.h"
#include "decoders/AbstractTiffDecoder.h"
#include "decoders/DngDeinterleave.h"
#include "decoders/RawDecoderException.h"
#include "decompressors/AbstractDngDecompressor.h"
#include "io/Buffer.h"
Expand All @@ -45,7 +46,9 @@
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <map>
#include <memory>
Expand Down Expand Up @@ -120,7 +123,10 @@ void DngDecoder::dropUnsuportedChunks(std::vector<const TiffIFD*>* data) {
#ifdef HAVE_JPEG
case 0x884c: // lossy JPEG
#endif
// no change, if supported, then is still supported.
#ifdef HAVE_JPEGXL
case 52546: // JPEG XL (DNG 1.7)
#endif
// no change, if supported, then is still supported.
break;

#ifndef HAVE_ZLIB
Expand All @@ -140,6 +146,15 @@ void DngDecoder::dropUnsuportedChunks(std::vector<const TiffIFD*>* data) {
"chunk, but the jpeg support was "
"disabled at build!");
[[clang::fallthrough]];
#endif
#ifndef HAVE_JPEGXL
case 52546: // JPEG XL (DNG 1.7)
#pragma message \
"JPEG XL is not present! DNG JPEG XL compression will not be supported!"
writeLog(DEBUG_PRIO::WARNING, "DNG Decoder: found JPEG XL-encoded "
"chunk, but JPEG XL support was "
"disabled at build!");
[[clang::fallthrough]];
#endif
default:
supported = false;
Expand Down Expand Up @@ -443,6 +458,67 @@ void DngDecoder::decodeData(const TiffIFD* raw, uint32_t sample_format) const {
mRaw->createData();

slices.decompress();

// DNG 1.7 may store the (already assembled) frame with its color-plane
// fields stacked, signalled by Row/ColumnInterleaveFactor. This is a
// whole-frame post-pass over the fully assembled buffer, applied BEFORE
// ActiveArea/DefaultCropOrigin (handleMetadata) crop the image. A single
// (e.g. JXL) tile can straddle a field boundary, so this can NOT be done
// per-tile.
deinterleaveFields(raw);
}

void DngDecoder::deinterleaveFields(const TiffIFD* raw) const {
uint32_t rowFactor = 1;
if (raw->hasEntry(TiffTag::ROWINTERLEAVEFACTOR))
rowFactor = raw->getEntry(TiffTag::ROWINTERLEAVEFACTOR)->getU32();

uint32_t colFactor = 1;
if (raw->hasEntry(TiffTag::COLUMNINTERLEAVEFACTOR))
colFactor = raw->getEntry(TiffTag::COLUMNINTERLEAVEFACTOR)->getU32();

if (rowFactor == 0 || colFactor == 0)
ThrowRDE("Invalid interleave factor (%u, %u)", rowFactor, colFactor);

// Fast path: nothing to do.
if (rowFactor == 1 && colFactor == 1)
return;

const int storedH = mRaw->dim.y;
const int storedW = mRaw->dim.x;

if (rowFactor > static_cast<uint32_t>(storedH) ||
colFactor > static_cast<uint32_t>(storedW))
ThrowRDE("Interleave factor (%u, %u) larger than image dimensions (%i, %i)",
rowFactor, colFactor, storedW, storedH);

// stored-row -> final-row and stored-col -> final-col lookup tables.
const std::vector<int> rowMap =
dngDeinterleaveFieldMap(storedH, implicit_cast<int>(rowFactor));
const std::vector<int> colMap =
dngDeinterleaveFieldMap(storedW, implicit_cast<int>(colFactor));

// Operate on raw bytes so the same scatter works for both UINT16 and F32
// buffers. `bpp` is the size of one whole pixel (all channels) in bytes; the
// byte Array2DRef indexes columns in bytes.
const int bpp = implicit_cast<int>(mRaw->getBpp());
const Array2DRef<std::byte> img = mRaw->getByteDataAsUncroppedArray2DRef();

// A temporary copy of the assembled (stored-order) frame to scatter from.
std::vector<std::byte> tmp(static_cast<size_t>(storedH) * storedW * bpp);
const Array2DRef<std::byte> src(tmp.data(), storedW * bpp, storedH);
for (int sy = 0; sy < storedH; ++sy)
std::memcpy(&src(sy, 0), &img(sy, 0), static_cast<size_t>(storedW) * bpp);

// Scatter src(sy,sx) -> img(fy,fx), one whole pixel (bpp bytes) at a time.
for (int sy = 0; sy < storedH; ++sy) {
const int fy = rowMap[static_cast<size_t>(sy)];
for (int sx = 0; sx < storedW; ++sx) {
const int fx = colMap[static_cast<size_t>(sx)];
std::memcpy(&img(fy, bpp * fx), &src(sy, bpp * sx),
static_cast<size_t>(bpp));
}
}
}

RawImage DngDecoder::decodeRawInternal() {
Expand Down
1 change: 1 addition & 0 deletions src/librawspeed/decoders/DngDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class DngDecoder final : public AbstractTiffDecoder {
void parseWhiteBalance() const;
DngTilingDescription getTilingDescription(const TiffIFD* raw) const;
void decodeData(const TiffIFD* raw, uint32_t sample_format) const;
void deinterleaveFields(const TiffIFD* raw) const;
void handleMetadata(const TiffIFD* raw);
bool decodeMaskedAreas(const TiffIFD* raw) const;
bool decodeBlackLevels(const TiffIFD* raw) const;
Expand Down
77 changes: 77 additions & 0 deletions src/librawspeed/decoders/DngDeinterleave.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
RawSpeed - RAW file decoder.

Copyright (C) 2026 Mayk Thewessen

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
*/

#pragma once

#include <cassert>
#include <cstdint>
#include <vector>

namespace rawspeed {

// Pixel de-interleave for DNG 1.7 RowInterleaveFactor (0xC71F) /
// ColumnInterleaveFactor (0xCD43).
//
// A CFA DNG may store its mosaic as `R` x `C` stacked "fields" (color-plane
// subimages), each of which compresses far better under lossy JPEG XL than the
// raw mosaic. De-interleaving REORDERS pixels only; it never resizes, so the
// final dimensions equal the stored (decoded) dimensions.
//
// Field f's rows scatter to final rows f, f+R, f+2R, ... i.e. the forward map
// stored->final is `fy = within_field_row * R + field_row_index`, and
// symmetrically for columns with C. Non-divisible sizes are handled exactly
// like the dng_sdk reference (dng_read_image.cpp): earlier fields absorb the
// remainder.
//
// This matches the Adobe DNG 1.7.1.0 specification and the dng_sdk reference
// implementation.

// Build the stored-row -> final-row (or stored-col -> final-col) lookup table.
//
// `total` is the stored extent (height for rows, width for columns) and
// `factor` is the corresponding interleave factor (R or C, >= 1).
//
// Returns a vector `map` of size `total` such that the pixel stored at index
// `s` belongs at final index `map[s]`.
[[nodiscard]] inline std::vector<int> dngDeinterleaveFieldMap(int total,
int factor) {
assert(total >= 0);
assert(factor >= 1);

std::vector<int> map(static_cast<size_t>(total));

int acc = 0;
for (int f = 0; f < factor; ++f) {
// Number of rows/cols in this field. Earlier fields absorb the remainder,
// matching dng_sdk: rows[f] = (total - f + factor - 1) / factor.
const int fieldExtent = (total - f + factor - 1) / factor;
for (int within = 0; within < fieldExtent; ++within) {
const int storedIdx = acc + within;
assert(storedIdx < total);
map[static_cast<size_t>(storedIdx)] = within * factor + f;
}
acc += fieldExtent;
}
assert(acc == total);

return map;
}

} // namespace rawspeed
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_JPEGXL
#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_JPEGXL
template <>
void AbstractDngDecompressor::decompressThread<52546>() 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 == 52546) {
/* JPEG XL (DNG 1.7) */
#ifdef HAVE_JPEGXL
decompressThread<52546>();
#else
#pragma message "JPEG XL is not present! DNG JPEG XL 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 @@ -82,4 +84,8 @@ if(WITH_JPEG AND TARGET JPEG::JPEG)
target_link_libraries(rawspeed_decompressors PUBLIC JPEG::JPEG)
endif()

if(WITH_JPEGXL AND TARGET PkgConfig::libjxl)
target_link_libraries(rawspeed_decompressors PUBLIC PkgConfig::libjxl)
endif()

target_link_libraries(rawspeed PRIVATE rawspeed_decompressors)
Loading