Skip to content
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

ARW: Support new LJPEG compression on ILCE-7M4 and ILCE-7R5 #482

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
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);
Copy link
Contributor

@kmilos kmilos Jun 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I already mentioned, let's not use these but the dimensions in the new tag 0x7038. We then need a new mode that'll tell us to ignore any further cameras.xml crop.

You could even go a step further and use those directly as width and height on L281-L282 without needing to do anything extra here. The LJpeg decoder should take of the padding just fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work, I just pushed the changes!

You could even go a step further and use those directly as width and height on L281-L282 without needing to do anything extra here. The LJpeg decoder should take of the padding just fine.

This however does not work, as the width & height need to be multiples of the tile size to make the tiling code work, or how did you mean that?

Copy link
Contributor

@kmilos kmilos Jun 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't. The tiling code rounds up to the required multiple.

Well, at least it does in the DNG decoder, don't know if it'll just work out here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We then need a new mode that'll tell us to ignore any further cameras.xml crop.

Can we make the mandatory cropping step according to the values in cameras.xml optional by just skipping it on a decoder basis?
But I guess it would be cleaner and more flexible to introduce a new flag in cameras.xml a'la
<Crop x="0" y="0" width="0" height="0" read_from_exif="true"/>
If cropping based on exif tags fails cropping shall fall back to values provided in cameras.xml .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, let's not call this "Exif crop". This is a pure kludge on Sony's part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will have a look later, when I've got time to finish this work package for final review ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked all the rawspeed entrypoints and decodeRaw() always comes before decodeMetaData() (both calling their inherited decoder specific function), so this should work. Will give it a try now...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by the way, by resetting the history on one of my test images using the Exif.SubImage1.0x7038 crop I saw that this gives an area which still contains garbage from the tiling operation on the right and a black bar on the bottom
Screenshot from 2023-06-06 22-53-40

If I set the offset to 0,0 I only get valid pixels and no borders, so this way you can get the max. possible pixel area it seems. What's weird though is the size from Exif.SubImage1.0x7038 = 9568, 6376, but darktable crop module shows me 9567, 6376.
The DNG converted file shows 9504, 6336 in the crop module 😅

Then I did another test with way bigger crop to see where the garbage & borders begins and this proves that the values in Exif.SubImage1.0x7038 seem to be quite accurate...
Screenshot from 2023-06-06 23-51-22

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than resetting for validation, I'd remove the old images from the database (and their xmp sidecars), and import again.

The 0x7038 tag values, as mentioned, should result in the same size as the uncompressed/lossy case w/ cameras.xml crop. For 7RM5 this is, for uncompressed/lossy (9600-32)x(6376-0) which matches ljpeg compressed 0x7038 values 9568x6376.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, then the current state (of cropping) in this PR makes sense, right?

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