Skip to content
Merged
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
65 changes: 65 additions & 0 deletions 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 @@ -455,6 +458,68 @@ 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
44 changes: 33 additions & 11 deletions src/librawspeed/decompressors/JpegXlDecompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "adt/Array2DRef.h"
#include "adt/Point.h"
#include "common/RawImage.h"
#include "decoders/RawDecoderException.h"
#include "decompressors/JpegXlDecompressor.h"
#include <algorithm>
Expand All @@ -47,6 +48,21 @@ struct JxlDecoderGuard final {
JxlDecoderGuard& operator=(JxlDecoderGuard&&) = delete;
~JxlDecoderGuard() { JxlDecoderDestroy(dec); }
};

// Copy the decoded, interleaved JXL tile into the raw buffer at (offX,offY).
// Templated on the sample type so the integer (uint16_t) and float (float)
// output paths share one implementation.
template <typename T>
void copyTile(const Array2DRef<T> out, const T* pixels, uint32_t jxl_w,
uint32_t copy_w, uint32_t copy_h, uint32_t cpp, uint32_t offX,
uint32_t offY) {
for (uint32_t row = 0; row < copy_h; ++row) {
for (uint32_t col = 0; col < cpp * copy_w; ++col) {
out(static_cast<int>(row + offY), static_cast<int>(cpp * offX + col)) =
pixels[(static_cast<size_t>(row) * jxl_w * cpp) + col];
}
}
}
} // namespace

void JpegXlDecompressor::decode(uint32_t offX, uint32_t offY) {
Expand All @@ -69,15 +85,21 @@ void JpegXlDecompressor::decode(uint32_t offX, uint32_t offY) {
JxlDecoderCloseInput(dec);

const uint32_t cpp = mRaw->getCpp();
// Float DNGs (e.g. linear raw float) store an F32 buffer; integer DNGs store
// uint16. Decode JXL directly into whichever sample type mRaw expects, so the
// tile lands in the matching typed view below.
const bool isFloat = mRaw->getDataType() == RawImageType::F32;
const JxlPixelFormat fmt = {/*num_channels=*/cpp,
/*data_type=*/JXL_TYPE_UINT16,
/*data_type=*/isFloat ? JXL_TYPE_FLOAT
: JXL_TYPE_UINT16,
/*endianness=*/JXL_LITTLE_ENDIAN,
/*align=*/0};

JxlBasicInfo info = {};
uint32_t jxl_w = 0;
uint32_t jxl_h = 0;
std::vector<uint16_t> pixels;
// Type-agnostic byte buffer; libjxl reports the required size in bytes.
std::vector<uint8_t> pixels;

for (;;) {
const JxlDecoderStatus status = JxlDecoderProcessInput(dec);
Expand All @@ -99,7 +121,7 @@ void JpegXlDecompressor::decode(uint32_t offX, uint32_t offY) {
size_t buf_size = 0;
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec, &fmt, &buf_size))
ThrowRDE("JXL: JxlDecoderImageOutBufferSize failed");
pixels.resize(buf_size / sizeof(uint16_t));
pixels.resize(buf_size);
if (JXL_DEC_SUCCESS !=
JxlDecoderSetImageOutBuffer(dec, &fmt, pixels.data(), buf_size))
ThrowRDE("JXL: JxlDecoderSetImageOutBuffer failed");
Expand All @@ -115,17 +137,17 @@ void JpegXlDecompressor::decode(uint32_t offX, uint32_t offY) {
if (pixels.empty() || jxl_w == 0 || jxl_h == 0)
ThrowRDE("JXL: no pixel data decoded");

// Copy the decoded interleaved uint16 tile into the raw buffer at
// (offX,offY).
const uint32_t copy_w = min(static_cast<uint32_t>(mRaw->dim.x) - offX, jxl_w);
const uint32_t copy_h = min(static_cast<uint32_t>(mRaw->dim.y) - offY, jxl_h);

const Array2DRef<uint16_t> out(mRaw->getU16DataAsUncroppedArray2DRef());
for (uint32_t row = 0; row < copy_h; ++row) {
for (uint32_t col = 0; col < cpp * copy_w; ++col) {
out(static_cast<int>(row + offY), static_cast<int>(cpp * offX + col)) =
pixels[(static_cast<size_t>(row) * jxl_w * cpp) + col];
}
if (isFloat) {
copyTile<float>(mRaw->getF32DataAsUncroppedArray2DRef(),
reinterpret_cast<const float*>(pixels.data()), jxl_w, copy_w,
copy_h, cpp, offX, offY);
} else {
copyTile<uint16_t>(mRaw->getU16DataAsUncroppedArray2DRef(),
reinterpret_cast<const uint16_t*>(pixels.data()), jxl_w,
copy_w, copy_h, cpp, offX, offY);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/librawspeed/tiff/TiffTag.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ enum class TiffTag : uint16_t {
ORIGINALRAWFILEDIGEST = 0xC71D,
SUBTILEBLOCKSIZE = 0xC71E,
ROWINTERLEAVEFACTOR = 0xC71F,
COLUMNINTERLEAVEFACTOR = 0xCD43,
PROFILELOOKTABLEDIMS = 0xC725,
PROFILELOOKTABLEDATA = 0xC726,
OPCODELIST1 = 0xC740,
Expand Down
1 change: 1 addition & 0 deletions test/librawspeed/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ add_subdirectory(adt)
add_subdirectory(bitstreams)
add_subdirectory(codes)
add_subdirectory(common)
add_subdirectory(decoders)
add_subdirectory(io)
add_subdirectory(metadata)
add_subdirectory(test)
7 changes: 7 additions & 0 deletions test/librawspeed/decoders/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FILE(GLOB RAWSPEED_TEST_SOURCES
"DngDeinterleaveTest.cpp"
)

foreach(SRC ${RAWSPEED_TEST_SOURCES})
add_rs_test("${SRC}")
endforeach()
Loading