Skip to content

Commit

Permalink
ARW: Support new LJPEG compression on ILCE-7M4 and ILCE-7R5
Browse files Browse the repository at this point in the history
* Support large-resolution ARW from ILCE-7RM5
* Generalize sonyArrange in LJPEG arrangement to support 1x2

Co-authored-by: Artemis Tosini <[email protected]>
  • Loading branch information
da-phil and artemist committed Jun 4, 2023
1 parent 820a907 commit b383e9e
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 16 deletions.
84 changes: 82 additions & 2 deletions src/librawspeed/decoders/ArwDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
#include "adt/NORangesSet.h" // for NORangesSet
#include "adt/Point.h" // for iPoint2D
#include "common/Common.h" // for roundDown
#include "decoders/RawDecoderException.h" // for ThrowException
#include "common/RawspeedException.h" // for RawspeedException
#include "decoders/RawDecoderException.h" // for ThrowRDE
#include "decompressors/LJpegDecoder.h" // for LJpegDecoder
#include "decompressors/SonyArw1Decompressor.h" // for SonyArw1Decompre...
#include "decompressors/SonyArw2Decompressor.h" // for SonyArw2Decompre...
#include "decompressors/UncompressedDecompressor.h" // for UncompressedDeco...
Expand Down Expand Up @@ -145,6 +147,11 @@ RawImage ArwDecoder::decodeRawInternal() {
return mRaw;
}

if (7 == compression) {
DecodeLJpeg(raw);
return mRaw;
}

if (32767 != compression)
ThrowRDE("Unsupported compression %i", compression);

Expand Down Expand Up @@ -245,7 +252,7 @@ void ArwDecoder::DecodeUncompressed(const TiffIFD* raw) const {

mRaw->dim = iPoint2D(width, height);

if (width == 0 || height == 0 || width > 9600 || height > 6376)
if (width == 0 || height == 0 || width > 9728 || height > 6656)
ThrowRDE("Unexpected image dimensions found: (%u; %u)", width, height);

if (c2 == 0)
Expand All @@ -270,6 +277,79 @@ void ArwDecoder::DecodeUncompressed(const TiffIFD* raw) const {
}
}

void ArwDecoder::DecodeLJpeg(const TiffIFD* raw) const {
uint32_t width = raw->getEntry(TiffTag::IMAGEWIDTH)->getU32();
uint32_t height = raw->getEntry(TiffTag::IMAGELENGTH)->getU32();
uint32_t bitPerPixel = raw->getEntry(TiffTag::BITSPERSAMPLE)->getU32();

switch (bitPerPixel) {
case 8:
case 12:
case 14:
break;
default:
ThrowRDE("Unexpected bits per pixel: %u", bitPerPixel);
}

if (width == 0 || height == 0 || height % 2 != 0 || width > 9728 ||
height > 6656)
ThrowRDE("Unexpected image dimensions found: (%u; %u)", width, height);

mRaw->dim = iPoint2D(width, height);

const uint32_t tilew = raw->getEntry(TiffTag::TILEWIDTH)->getU32();
const uint32_t tileh = raw->getEntry(TiffTag::TILELENGTH)->getU32();

if (!(tilew > 0 && tileh > 0))
ThrowRDE("Invalid tile size: (%u, %u)", tilew, tileh);

assert(tilew > 0);
const uint32_t tilesX = roundUpDivision(mRaw->dim.x, tilew);
if (!tilesX)
ThrowRDE("Zero tiles horizontally");

assert(tileh > 0);
const uint32_t tilesY = roundUpDivision(mRaw->dim.y, tileh);
if (!tilesY)
ThrowRDE("Zero tiles vertically");

const TiffEntry* offsets = raw->getEntry(TiffTag::TILEOFFSETS);
const TiffEntry* counts = raw->getEntry(TiffTag::TILEBYTECOUNTS);
if (offsets->count != counts->count) {
ThrowRDE("Tile count mismatch: offsets:%u count:%u", offsets->count,
counts->count);
}

// tilesX * tilesY may overflow, but division is fine, so let's do that.
if ((offsets->count / tilesX != tilesY || (offsets->count % tilesX != 0)) ||
(offsets->count / tilesY != tilesX || (offsets->count % tilesY != 0))) {
ThrowRDE("Tile X/Y count mismatch: total:%u X:%u, Y:%u", offsets->count,
tilesX, tilesY);
}

mRaw->createData();
#ifdef HAVE_OPENMP
#pragma omp for schedule(static)
#endif
for (uint32_t tile = 0U; tile < offsets->count; tile++) {
const uint32_t tileX = tile % tilesX;
const uint32_t tileY = tile / tilesX;
const uint32_t offset = offsets->getU32(tile);
const uint32_t length = counts->getU32(tile);

LJpegDecoder decoder(ByteStream(DataBuffer(mFile.getSubView(offset, length),
Endianness::little)),
mRaw, true);
decoder.decode(tileX * tilew, tileY * tileh, tilew, tileh, false);
}

const TiffEntry* origin_entry = raw->getEntry(TiffTag::DEFAULTCROPORIGIN);
const TiffEntry* size_entry = raw->getEntry(TiffTag::DEFAULTCROPSIZE);
iRectangle2D crop(origin_entry->getU32(0), origin_entry->getU32(1),
size_entry->getU32(0), size_entry->getU32(1));
mRaw->subFrame(crop);
}

void ArwDecoder::DecodeARW2(ByteStream input, uint32_t w, uint32_t h,
uint32_t bpp) {

Expand Down
1 change: 1 addition & 0 deletions src/librawspeed/decoders/ArwDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ArwDecoder final : public AbstractTiffDecoder {
[[nodiscard]] int getDecoderVersion() const override { return 1; }
RawImage decodeSRF(const TiffIFD* raw);
void DecodeARW2(ByteStream input, uint32_t w, uint32_t h, uint32_t bpp);
void DecodeLJpeg(const TiffIFD* raw) const;
void DecodeUncompressed(const TiffIFD* raw) const;
static void SonyDecrypt(const uint32_t* ibuf, uint32_t* obuf, uint32_t len,
uint32_t key);
Expand Down
8 changes: 5 additions & 3 deletions src/librawspeed/decompressors/LJpegDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ using std::copy_n;

namespace rawspeed {

LJpegDecoder::LJpegDecoder(ByteStream bs, const RawImage& img)
: AbstractLJpegDecoder(bs, img) {
LJpegDecoder::LJpegDecoder(ByteStream bs, const RawImage& img,
bool interleaveRows_)
: AbstractLJpegDecoder(bs, img), interleaveRows{interleaveRows_} {
if (mRaw->getDataType() != RawImageType::UINT16)
ThrowRDE("Unexpected data type (%u)",
static_cast<unsigned>(mRaw->getDataType()));
Expand Down Expand Up @@ -113,7 +114,8 @@ void LJpegDecoder::decodeScan() {

LJpegDecompressor d(
mRaw, iRectangle2D({(int)offX, (int)offY}, {(int)w, (int)h}),
LJpegDecompressor::Frame{N_COMP, iPoint2D(frame.w, frame.h)}, rec, input);
LJpegDecompressor::Frame{N_COMP, iPoint2D(frame.w, frame.h)}, rec, input,
interleaveRows);
d.decode();
}

Expand Down
3 changes: 2 additions & 1 deletion src/librawspeed/decompressors/LJpegDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ class LJpegDecoder final : public AbstractLJpegDecoder {
uint32_t offY = 0;
uint32_t w = 0;
uint32_t h = 0;
bool interleaveRows = false;

public:
LJpegDecoder(ByteStream bs, const RawImage& img);
LJpegDecoder(ByteStream bs, const RawImage& img, bool interleaveRows = false);

void decode(uint32_t offsetX, uint32_t offsetY, uint32_t width,
uint32_t height, bool fixDng16Bug_);
Expand Down
43 changes: 34 additions & 9 deletions src/librawspeed/decompressors/LJpegDecompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ namespace rawspeed {
LJpegDecompressor::LJpegDecompressor(const RawImage& img,
iRectangle2D imgFrame_, Frame frame_,
std::vector<PerComponentRecipe> rec_,
ByteStream bs)
ByteStream bs, bool interleaveRows_)
: mRaw(img), input(bs), imgFrame(imgFrame_), frame(std::move(frame_)),
rec(std::move(rec_)) {
rec(std::move(rec_)), interleaveRows{interleaveRows_} {
if (mRaw->getDataType() != RawImageType::UINT16)
ThrowRDE("Unexpected data type (%u)",
static_cast<unsigned>(mRaw->getDataType()));
Expand Down Expand Up @@ -100,16 +100,19 @@ LJpegDecompressor::LJpegDecompressor(const RawImage& img,
ThrowRDE("Got less pixels than the components per sample");

// How many output pixels are we expected to produce, as per DNG tiling?
const int tileRequiredWidth = (int)mRaw->getCpp() * imgFrame.dim.x;
const auto interleaveFactor = interleaveRows ? 2 : 1;
const int tileRequiredWidth =
(int)mRaw->getCpp() * imgFrame.dim.x * interleaveFactor;
// How many of these rows do we need?
const auto numRows = imgFrame.dim.y / interleaveFactor;

// How many full pixel blocks do we need to consume for that?
if (const int blocksToConsume = roundUpDivision(tileRequiredWidth, frame.cps);
frame.dim.x < blocksToConsume || frame.dim.y < imgFrame.dim.y ||
frame.dim.x < blocksToConsume || frame.dim.y < numRows ||
(int64_t)frame.cps * frame.dim.x <
(int64_t)mRaw->getCpp() * imgFrame.dim.x) {
ThrowRDE("LJpeg frame (%u, %u) is smaller than expected (%u, %u)",
frame.cps * frame.dim.x, frame.dim.y, tileRequiredWidth,
imgFrame.dim.y);
frame.cps * frame.dim.x, frame.dim.y, tileRequiredWidth, numRows);
}

// How many full pixel blocks will we produce?
Expand Down Expand Up @@ -148,17 +151,20 @@ template <int N_COMP, bool WeirdWidth> void LJpegDecompressor::decodeN() {
invariant(N_COMP > 0);
invariant(N_COMP >= mRaw->getCpp());
invariant((N_COMP / mRaw->getCpp()) > 0);
invariant(((N_COMP & 1) == 0) | !interleaveRows);

invariant(mRaw->dim.x >= N_COMP);
invariant((mRaw->getCpp() * (mRaw->dim.x - imgFrame.pos.x)) >= N_COMP);

auto interleaveHeight = (interleaveRows ? 2 : 1);
auto interleaveWidth = N_COMP / interleaveHeight;

const CroppedArray2DRef img(mRaw->getU16DataAsUncroppedArray2DRef(),
mRaw->getCpp() * imgFrame.pos.x, imgFrame.pos.y,
mRaw->getCpp() * imgFrame.dim.x, imgFrame.dim.y);

const auto ht = getPrefixCodeDecoders<N_COMP>();
auto pred = getInitialPreds<N_COMP>();
uint16_t* predNext = pred.data();

BitPumpJPEG bitStream(input);

Expand All @@ -173,13 +179,17 @@ template <int N_COMP, bool WeirdWidth> void LJpegDecompressor::decodeN() {
invariant(imgFrame.pos.y + imgFrame.dim.y <= mRaw->dim.y);
invariant(imgFrame.pos.x + imgFrame.dim.x <= mRaw->dim.x);

const auto numRows = imgFrame.dim.y / interleaveHeight;

// For y, we can simply stop decoding when we reached the border.
for (int row = 0; row < imgFrame.dim.y; ++row) {
for (int row = 0; row < numRows; ++row) {
int col = 0;

/*
copy_n(predNext, N_COMP, pred.data());
// the predictor for the next line is the start of this line
predNext = &img(row, col);
*/

// FIXME: predictor may have value outside of the uint16_t.
// https://github.com/darktable-org/rawspeed/issues/175
Expand All @@ -190,7 +200,12 @@ template <int N_COMP, bool WeirdWidth> void LJpegDecompressor::decodeN() {
pred[i] = uint16_t(
pred[i] +
((const PrefixCodeDecoder<>&)(ht[i])).decodeDifference(bitStream));
img(row, col + i) = pred[i];
if (interleaveRows) {
img((row * interleaveHeight) + (i / interleaveHeight),
(col / interleaveWidth) + (i % interleaveWidth)) = pred[i];
} else {
img(row, col + i) = pred[i];
}
}
}

Expand Down Expand Up @@ -223,6 +238,16 @@ template <int N_COMP, bool WeirdWidth> void LJpegDecompressor::decodeN() {
for (int i = 0; i != N_COMP; ++i)
((const PrefixCodeDecoder<>&)(ht[i])).decodeDifference(bitStream);
}

// The first sample of the next row is calculated based on the first sample
// of this row, so copy it for the next iteration
if (interleaveRows) {
copy_n(&img(row * interleaveHeight, 0), interleaveWidth, pred.data());
copy_n(&img(row * interleaveHeight + 1, 0), interleaveWidth,
pred.data() + interleaveWidth);
} else {
copy_n(&img(row, 0), N_COMP, pred.data());
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/librawspeed/decompressors/LJpegDecompressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class LJpegDecompressor final {

int fullBlocks = 0;
int trailingPixels = 0;
bool interleaveRows = false;

template <int N_COMP, size_t... I>
[[nodiscard]] std::array<std::reference_wrapper<const PrefixCodeDecoder<>>,
Expand All @@ -76,7 +77,7 @@ class LJpegDecompressor final {

public:
LJpegDecompressor(const RawImage& img, iRectangle2D imgFrame, Frame frame,
std::vector<PerComponentRecipe> rec, ByteStream bs);
std::vector<PerComponentRecipe> rec, ByteStream bs, bool interleaveRows = false);

void decode();
};
Expand Down

0 comments on commit b383e9e

Please sign in to comment.