From fb143a70f66aae32eb3993e28b9f79ada6a949ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= Date: Tue, 27 Feb 2024 15:44:20 +0100 Subject: [PATCH] Add LJpeg predictors 2-7 --- .../decompressors/LJpegDecompressor.cpp | 3 +- .../decompressors/AbstractLJpegDecoder.cpp | 2 +- .../decompressors/LJpegDecoder.cpp | 3 +- .../decompressors/LJpegDecompressor.cpp | 86 ++++++++++++++++--- .../decompressors/LJpegDecompressor.h | 6 +- 5 files changed, 84 insertions(+), 16 deletions(-) diff --git a/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp b/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp index 22dd4a213..c5851ad96 100644 --- a/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp +++ b/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp @@ -87,10 +87,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { }); const int numRowsPerRestartInterval = bs.getI32(); + const int predictorMode = bs.getByte(); rawspeed::LJpegDecompressor d( mRaw, rawspeed::iRectangle2D(mRaw->dim.x, mRaw->dim.y), frame, rec, - numRowsPerRestartInterval, + numRowsPerRestartInterval, predictorMode, bs.getSubStream(/*offset=*/0).peekRemainingBuffer().getAsArray1DRef()); mRaw->createData(); (void)d.decode(); diff --git a/src/librawspeed/decompressors/AbstractLJpegDecoder.cpp b/src/librawspeed/decompressors/AbstractLJpegDecoder.cpp index d365b0696..24d272b58 100644 --- a/src/librawspeed/decompressors/AbstractLJpegDecoder.cpp +++ b/src/librawspeed/decompressors/AbstractLJpegDecoder.cpp @@ -208,7 +208,7 @@ void AbstractLJpegDecoder::parseSOS(ByteStream sos) { // Get predictor, see table H.1 from the JPEG spec predictorMode = sos.getByte(); - // The spec says predictoreMode is in [0..7], but Hasselblad uses '8'. + // The spec says predictorMode is in [0..7], but Hasselblad uses '8'. if (predictorMode > 8) ThrowRDE("Invalid predictor mode."); diff --git a/src/librawspeed/decompressors/LJpegDecoder.cpp b/src/librawspeed/decompressors/LJpegDecoder.cpp index 0ad0468e3..f38ea9f76 100644 --- a/src/librawspeed/decompressors/LJpegDecoder.cpp +++ b/src/librawspeed/decompressors/LJpegDecoder.cpp @@ -95,7 +95,7 @@ void LJpegDecoder::decode(uint32_t offsetX, uint32_t offsetY, uint32_t width, Buffer::size_type LJpegDecoder::decodeScan() { invariant(frame.cps > 0); - if (predictorMode != 1) + if ((predictorMode < 1) || (predictorMode > 7)) ThrowRDE("Unsupported predictor mode: %u", predictorMode); for (uint32_t i = 0; i < frame.cps; i++) @@ -133,6 +133,7 @@ Buffer::size_type LJpegDecoder::decodeScan() { } LJpegDecompressor d(mRaw, imgFrame, jpegFrame, rec, numRowsPerRestartInterval, + predictorMode, input.peekRemainingBuffer().getAsArray1DRef()); return d.decode(); } diff --git a/src/librawspeed/decompressors/LJpegDecompressor.cpp b/src/librawspeed/decompressors/LJpegDecompressor.cpp index 9083c4eb9..f8ab8b1e3 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.cpp +++ b/src/librawspeed/decompressors/LJpegDecompressor.cpp @@ -53,10 +53,12 @@ LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, Frame frame_, std::vector rec_, int numRowsPerRestartInterval_, + int predictorMode_, Array1DRef input_) : mRaw(std::move(img)), input(input_), imgFrame(imgFrame_), frame(std::move(frame_)), rec(std::move(rec_)), - numRowsPerRestartInterval(numRowsPerRestartInterval_) { + numRowsPerRestartInterval(numRowsPerRestartInterval_), + predictorMode(predictorMode_) { if (mRaw->getDataType() != RawImageType::UINT16) ThrowRDE("Unexpected data type (%u)", @@ -100,7 +102,7 @@ LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, ThrowRDE("Unsupported number of components: %u", frame.cps); if (rec.size() != static_cast(frame.cps)) - ThrowRDE("Must have exactly one recepie per component"); + ThrowRDE("Must have exactly one recipe per component"); for (const auto& recip : rec) { if (!recip.ht.isFullDecode()) @@ -136,7 +138,7 @@ LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, } if (numRowsPerRestartInterval < 1) - ThrowRDE("Number of rows per restart interval must be positives"); + ThrowRDE("Number of rows per restart interval must be positive"); // How many full pixel blocks will we produce? fullBlocks = tileRequiredWidth / frame.cps; // Truncating division! @@ -169,7 +171,8 @@ std::array LJpegDecompressor::getInitialPreds() const { template void LJpegDecompressor::decodeRowN( - CroppedArray1DRef outRow, std::array pred, + CroppedArray1DRef outRow, CroppedArray1DRef prevRow, + std::array pred, int predMode, std::array>, N_COMP> ht, BitStreamerJPEG& bs) const { // FIXME: predictor may have value outside of the uint16_t. @@ -179,10 +182,39 @@ void LJpegDecompressor::decodeRowN( // For x, we first process all full pixel blocks within the image buffer ... for (; col < N_COMP * fullBlocks; col += N_COMP) { for (int i = 0; i != N_COMP; ++i) { - pred[i] = + outRow(col + i) = uint16_t(pred[i] + (static_cast&>(ht[i])) .decodeDifference(bs)); - outRow(col + i) = pred[i]; + if (col < N_COMP * (fullBlocks - 1)) { + int32_t predA = outRow(col + i); + int32_t predB = predMode > 1 ? prevRow(col + N_COMP + i) : 0; + int32_t predC = predMode > 1 ? prevRow(col + i) : 0; + switch (predMode) { + case 1: + pred[i] = predA; + break; + case 2: + pred[i] = predB; + break; + case 3: + pred[i] = predC; + break; + case 4: + pred[i] = predA + predB - predC; + break; + case 5: + pred[i] = predA + ((predB - predC) >> 1); + break; + case 6: + pred[i] = predB + ((predA - predC) >> 1); + break; + case 7: + pred[i] = (predA + predB) >> 1; + break; + default: + __builtin_unreachable(); + } + } } } @@ -197,10 +229,38 @@ void LJpegDecompressor::decodeRowN( invariant(trailingPixels < N_COMP); int c = 0; for (; c < trailingPixels; ++c) { - pred[c] = + // Continue predictor update skipped at last full block + int32_t predA = outRow(col - N_COMP + c); + int32_t predB = predMode > 1 ? prevRow(col + c) : 0; + int32_t predC = predMode > 1 ? prevRow(col - N_COMP + c) : 0; + switch (predMode) { + case 1: + pred[c] = predA; + break; + case 2: + pred[c] = predB; + break; + case 3: + pred[c] = predC; + break; + case 4: + pred[c] = predA + predB - predC; + break; + case 5: + pred[c] = predA + ((predB - predC) >> 1); + break; + case 6: + pred[c] = predB + ((predA - predC) >> 1); + break; + case 7: + pred[c] = (predA + predB) >> 1; + break; + default: + __builtin_unreachable(); + } + outRow(col + c) = uint16_t(pred[c] + (static_cast&>(ht[c])) .decodeDifference(bs)); - outRow(col + c) = pred[c]; } // Discard the rest of the block. invariant(c < N_COMP); @@ -251,8 +311,9 @@ ByteStream::size_type LJpegDecompressor::decodeN() const { ByteStream inputStream(DataBuffer(input, Endianness::little)); for (int restartIntervalIndex = 0; restartIntervalIndex != numRestartIntervals; ++restartIntervalIndex) { - auto pred = getInitialPreds(); - auto predNext = Array1DRef(pred.data(), pred.size()); + auto predInit = getInitialPreds(); + auto predNext = Array1DRef(predInit.data(), predInit.size()); + std::array pred; if (restartIntervalIndex != 0) { auto marker = peekMarker(inputStream); @@ -283,14 +344,17 @@ ByteStream::size_type LJpegDecompressor::decodeN() const { } auto outRow = img[row]; + auto prevRow = row > 0 ? img[row - 1] : img[row]; copy_n(predNext.begin(), N_COMP, pred.data()); // the predictor for the next line is the start of this line predNext = outRow .getBlock(/*size=*/N_COMP, /*index=*/0) .getAsArray1DRef(); + // the predictor mode is always horizontal on the first line + int predMode = row == 0 ? 1 : predictorMode; - decodeRowN(outRow, pred, ht, bs); + decodeRowN(outRow, prevRow, pred, predMode, ht, bs); } inputStream.skipBytes(bs.getStreamPosition()); diff --git a/src/librawspeed/decompressors/LJpegDecompressor.h b/src/librawspeed/decompressors/LJpegDecompressor.h index 754627bb1..e971612e6 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.h +++ b/src/librawspeed/decompressors/LJpegDecompressor.h @@ -58,6 +58,7 @@ class LJpegDecompressor final { const Frame frame; const std::vector rec; const int numRowsPerRestartInterval; + const int predictorMode; int fullBlocks = 0; int trailingPixels = 0; @@ -77,7 +78,8 @@ class LJpegDecompressor final { template __attribute__((always_inline)) inline void decodeRowN( - CroppedArray1DRef outRow, std::array pred, + CroppedArray1DRef outRow, CroppedArray1DRef prevRow, + std::array pred, int predMode, std::array>, N_COMP> ht, BitStreamerJPEG& bs) const; @@ -87,7 +89,7 @@ class LJpegDecompressor final { public: LJpegDecompressor(RawImage img, iRectangle2D imgFrame, Frame frame, std::vector rec, - int numRowsPerRestartInterval_, + int numRowsPerRestartInterval, int predictorMode, Array1DRef input); [[nodiscard]] ByteStream::size_type decode() const;