From b383e9ead00c0ec3578d5e0b94cbcdcbda322766 Mon Sep 17 00:00:00 2001 From: Philipp Lutz Date: Sat, 3 Jun 2023 21:50:24 +0200 Subject: [PATCH 1/4] ARW: Support new LJPEG compression on ILCE-7M4 and ILCE-7R5 * Support large-resolution ARW from ILCE-7RM5 * Generalize sonyArrange in LJPEG arrangement to support 1x2 Co-authored-by: Artemis Tosini --- src/librawspeed/decoders/ArwDecoder.cpp | 84 ++++++++++++++++++- src/librawspeed/decoders/ArwDecoder.h | 1 + .../decompressors/LJpegDecoder.cpp | 8 +- src/librawspeed/decompressors/LJpegDecoder.h | 3 +- .../decompressors/LJpegDecompressor.cpp | 43 ++++++++-- .../decompressors/LJpegDecompressor.h | 3 +- 6 files changed, 126 insertions(+), 16 deletions(-) diff --git a/src/librawspeed/decoders/ArwDecoder.cpp b/src/librawspeed/decoders/ArwDecoder.cpp index f20b03908..7365eb1a1 100644 --- a/src/librawspeed/decoders/ArwDecoder.cpp +++ b/src/librawspeed/decoders/ArwDecoder.cpp @@ -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... @@ -145,6 +147,11 @@ RawImage ArwDecoder::decodeRawInternal() { return mRaw; } + if (7 == compression) { + DecodeLJpeg(raw); + return mRaw; + } + if (32767 != compression) ThrowRDE("Unsupported compression %i", compression); @@ -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) @@ -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) { diff --git a/src/librawspeed/decoders/ArwDecoder.h b/src/librawspeed/decoders/ArwDecoder.h index a916e15cb..7e6adf6b4 100644 --- a/src/librawspeed/decoders/ArwDecoder.h +++ b/src/librawspeed/decoders/ArwDecoder.h @@ -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); diff --git a/src/librawspeed/decompressors/LJpegDecoder.cpp b/src/librawspeed/decompressors/LJpegDecoder.cpp index 8bf8b222d..6cb8ccf51 100644 --- a/src/librawspeed/decompressors/LJpegDecoder.cpp +++ b/src/librawspeed/decompressors/LJpegDecoder.cpp @@ -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(mRaw->getDataType())); @@ -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(); } diff --git a/src/librawspeed/decompressors/LJpegDecoder.h b/src/librawspeed/decompressors/LJpegDecoder.h index 9160e35e0..4374e7c5c 100644 --- a/src/librawspeed/decompressors/LJpegDecoder.h +++ b/src/librawspeed/decompressors/LJpegDecoder.h @@ -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_); diff --git a/src/librawspeed/decompressors/LJpegDecompressor.cpp b/src/librawspeed/decompressors/LJpegDecompressor.cpp index 090898f63..bcc0e7454 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.cpp +++ b/src/librawspeed/decompressors/LJpegDecompressor.cpp @@ -40,9 +40,9 @@ namespace rawspeed { LJpegDecompressor::LJpegDecompressor(const RawImage& img, iRectangle2D imgFrame_, Frame frame_, std::vector 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(mRaw->getDataType())); @@ -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? @@ -148,17 +151,20 @@ template 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(); auto pred = getInitialPreds(); - uint16_t* predNext = pred.data(); BitPumpJPEG bitStream(input); @@ -173,13 +179,17 @@ template 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 @@ -190,7 +200,12 @@ template 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]; + } } } @@ -223,6 +238,16 @@ template 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()); + } } } diff --git a/src/librawspeed/decompressors/LJpegDecompressor.h b/src/librawspeed/decompressors/LJpegDecompressor.h index 44151a2d6..436f61583 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.h +++ b/src/librawspeed/decompressors/LJpegDecompressor.h @@ -58,6 +58,7 @@ class LJpegDecompressor final { int fullBlocks = 0; int trailingPixels = 0; + bool interleaveRows = false; template [[nodiscard]] std::array>, @@ -76,7 +77,7 @@ class LJpegDecompressor final { public: LJpegDecompressor(const RawImage& img, iRectangle2D imgFrame, Frame frame, - std::vector rec, ByteStream bs); + std::vector rec, ByteStream bs, bool interleaveRows = false); void decode(); }; From 25585e239e9fbed34ee56d39ea2a97e7595b3fe9 Mon Sep 17 00:00:00 2001 From: Philipp Lutz Date: Mon, 5 Jun 2023 19:48:34 +0200 Subject: [PATCH 2/4] Use Exif.SubImage1.0x7038 field for Sony lossless compression crop Recap: due to crop size roundup to multiples of tile size (512) Sony lossless files exhibit black areas if not further cropped down to actual pixel area, which is covered by the sensor. Exif.SubImage1.0x7038 seems to reliably provide the true crop size, whereas Exif.SubImage1.DefaultCropSize only crops to OOC jpeg size losing some pixel area --- src/librawspeed/decoders/ArwDecoder.cpp | 4 +++- src/librawspeed/tiff/TiffTag.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/librawspeed/decoders/ArwDecoder.cpp b/src/librawspeed/decoders/ArwDecoder.cpp index 7365eb1a1..181060481 100644 --- a/src/librawspeed/decoders/ArwDecoder.cpp +++ b/src/librawspeed/decoders/ArwDecoder.cpp @@ -344,7 +344,9 @@ void ArwDecoder::DecodeLJpeg(const TiffIFD* raw) const { } const TiffEntry* origin_entry = raw->getEntry(TiffTag::DEFAULTCROPORIGIN); - const TiffEntry* size_entry = raw->getEntry(TiffTag::DEFAULTCROPSIZE); + const TiffEntry* size_entry = raw->hasEntry(TiffTag::SONYRAWIMAGESIZE) + ? raw->getEntry(TiffTag::SONYRAWIMAGESIZE) + : 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); diff --git a/src/librawspeed/tiff/TiffTag.h b/src/librawspeed/tiff/TiffTag.h index 583b621f0..d1e79e338 100644 --- a/src/librawspeed/tiff/TiffTag.h +++ b/src/librawspeed/tiff/TiffTag.h @@ -128,6 +128,7 @@ enum class TiffTag { CANONCOLORDATA = 0x4001, + SONYRAWIMAGESIZE = 0x7038, SONYGRBGLEVELS = 0x7303, SONYRGGBLEVELS = 0x7313, From 1972397b4a6091e3f13fd17a00860560bf15c827 Mon Sep 17 00:00:00 2001 From: Philipp Lutz Date: Mon, 5 Jun 2023 19:49:20 +0200 Subject: [PATCH 3/4] Boyscout: make sony exif tag naming consistent --- src/librawspeed/decoders/ArwDecoder.cpp | 8 ++++---- src/librawspeed/tiff/TiffTag.h | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/librawspeed/decoders/ArwDecoder.cpp b/src/librawspeed/decoders/ArwDecoder.cpp index 181060481..b5ea12365 100644 --- a/src/librawspeed/decoders/ArwDecoder.cpp +++ b/src/librawspeed/decoders/ArwDecoder.cpp @@ -204,7 +204,7 @@ RawImage ArwDecoder::decodeRawInternal() { mRaw->dim = iPoint2D(width, height); std::vector curve(0x4001); - const TiffEntry* c = raw->getEntry(TiffTag::SONY_CURVE); + const TiffEntry* c = raw->getEntry(TiffTag::SONYCURVE); std::array sony_curve = {{0, 0, 0, 0, 0, 4095}}; for (uint32_t i = 0; i < 4; i++) @@ -509,11 +509,11 @@ void ArwDecoder::GetWB() const { priv->getU32()); const TiffEntry* sony_offset = - makerNoteIFD.getEntryRecursive(TiffTag::SONY_OFFSET); + makerNoteIFD.getEntryRecursive(TiffTag::SONYOFFSET); const TiffEntry* sony_length = - makerNoteIFD.getEntryRecursive(TiffTag::SONY_LENGTH); + makerNoteIFD.getEntryRecursive(TiffTag::SONYLENGTH); const TiffEntry* sony_key = - makerNoteIFD.getEntryRecursive(TiffTag::SONY_KEY); + makerNoteIFD.getEntryRecursive(TiffTag::SONYKEY); if (!sony_offset || !sony_length || !sony_key || sony_key->count != 4) ThrowRDE("couldn't find the correct metadata for WB decoding"); diff --git a/src/librawspeed/tiff/TiffTag.h b/src/librawspeed/tiff/TiffTag.h index d1e79e338..020c20856 100644 --- a/src/librawspeed/tiff/TiffTag.h +++ b/src/librawspeed/tiff/TiffTag.h @@ -127,8 +127,11 @@ enum class TiffTag { FUJIOLDWB = 0x2ff0, CANONCOLORDATA = 0x4001, - + SONYCURVE = 0x7010, SONYRAWIMAGESIZE = 0x7038, + SONYOFFSET = 0x7200, + SONYLENGTH = 0x7201, + SONYKEY = 0x7221, SONYGRBGLEVELS = 0x7303, SONYRGGBLEVELS = 0x7313, @@ -337,10 +340,6 @@ enum class TiffTag { CALIBRATIONILLUMINANT1 = 0xC65A, // IFD0 CALIBRATIONILLUMINANT2 = 0xC65B, // IFD0 - SONY_CURVE = 28688, - SONY_OFFSET = 0x7200, - SONY_LENGTH = 0x7201, - SONY_KEY = 0x7221, // PRINT IMAGE MATCHING DATA PIMIFDPOINTER = 0xC4A5, From 5f3fe8f377701e644989c52e262302a9ad5d28e8 Mon Sep 17 00:00:00 2001 From: Philipp Lutz Date: Tue, 6 Jun 2023 23:42:18 +0200 Subject: [PATCH 4/4] Do not apply crop from cameras.xml and set crop origin to 0,0 --- src/librawspeed/decoders/ArwDecoder.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/librawspeed/decoders/ArwDecoder.cpp b/src/librawspeed/decoders/ArwDecoder.cpp index b5ea12365..9489c9d64 100644 --- a/src/librawspeed/decoders/ArwDecoder.cpp +++ b/src/librawspeed/decoders/ArwDecoder.cpp @@ -149,6 +149,8 @@ RawImage ArwDecoder::decodeRawInternal() { if (7 == compression) { DecodeLJpeg(raw); + // cropping of lossless compressed L files already done in Ljpeg decoder + applyCrop = false; return mRaw; } @@ -343,11 +345,10 @@ void ArwDecoder::DecodeLJpeg(const TiffIFD* raw) const { decoder.decode(tileX * tilew, tileY * tileh, tilew, tileh, false); } - const TiffEntry* origin_entry = raw->getEntry(TiffTag::DEFAULTCROPORIGIN); const TiffEntry* size_entry = raw->hasEntry(TiffTag::SONYRAWIMAGESIZE) ? raw->getEntry(TiffTag::SONYRAWIMAGESIZE) : raw->getEntry(TiffTag::DEFAULTCROPSIZE); - iRectangle2D crop(origin_entry->getU32(0), origin_entry->getU32(1), + iRectangle2D crop(0, 0, size_entry->getU32(0), size_entry->getU32(1)); mRaw->subFrame(crop); }