diff --git a/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp b/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp index 22dd4a213..aa7b7154c 100644 --- a/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp +++ b/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp @@ -50,11 +50,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { rawspeed::RawImage mRaw(CreateRawImage(bs)); - const int N_COMP = bs.getI32(); + const int MCU_w = bs.getI32(); + const int MCU_h = bs.getI32(); const int frame_w = bs.getI32(); const int frame_h = bs.getI32(); const rawspeed::LJpegDecompressor::Frame frame{ - N_COMP, rawspeed::iPoint2D(frame_w, frame_h)}; + rawspeed::iPoint2D(MCU_w, MCU_h), rawspeed::iPoint2D(frame_w, frame_h)}; const unsigned num_recips = bs.getU32(); @@ -86,11 +87,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { return {*hts[i], initPred[i]}; }); - const int numRowsPerRestartInterval = bs.getI32(); + const int numLJpegRowsPerRestartInterval = bs.getI32(); rawspeed::LJpegDecompressor d( mRaw, rawspeed::iRectangle2D(mRaw->dim.x, mRaw->dim.y), frame, rec, - numRowsPerRestartInterval, + numLJpegRowsPerRestartInterval, bs.getSubStream(/*offset=*/0).peekRemainingBuffer().getAsArray1DRef()); mRaw->createData(); (void)d.decode(); diff --git a/src/librawspeed/adt/Point.h b/src/librawspeed/adt/Point.h index 32de65806..2621fb278 100644 --- a/src/librawspeed/adt/Point.h +++ b/src/librawspeed/adt/Point.h @@ -56,7 +56,7 @@ class iPoint2D final { return *this; } - constexpr bool operator==(const iPoint2D& rhs) const { + constexpr bool RAWSPEED_READONLY operator==(const iPoint2D& rhs) const { return x == rhs.x && y == rhs.y; } diff --git a/src/librawspeed/decoders/ArwDecoder.cpp b/src/librawspeed/decoders/ArwDecoder.cpp index ae0fe6dbb..e3bca18ac 100644 --- a/src/librawspeed/decoders/ArwDecoder.cpp +++ b/src/librawspeed/decoders/ArwDecoder.cpp @@ -20,7 +20,6 @@ */ #include "decoders/ArwDecoder.h" -#include "MemorySanitizer.h" #include "adt/Array1DRef.h" #include "adt/Array2DRef.h" #include "adt/Casts.h" @@ -318,7 +317,7 @@ void ArwDecoder::DecodeLJpeg(const TiffIFD* raw) { width > 9728 || height > 6656) ThrowRDE("Unexpected image dimensions found: (%u; %u)", width, height); - mRaw->dim = iPoint2D(2 * width, height / 2); + mRaw->dim = iPoint2D(width, height); auto tilew = uint64_t(raw->getEntry(TiffTag::TILEWIDTH)->getU32()); uint32_t tileh = raw->getEntry(TiffTag::TILELENGTH)->getU32(); @@ -326,9 +325,6 @@ void ArwDecoder::DecodeLJpeg(const TiffIFD* raw) { if (tilew <= 0 || tileh <= 0 || tileh % 2 != 0) ThrowRDE("Invalid tile size: (%" PRIu64 ", %u)", tilew, tileh); - tileh /= 2; - tilew *= 2; - assert(tilew > 0); const auto tilesX = implicit_cast(roundUpDivision(mRaw->dim.x, tilew)); @@ -409,48 +405,11 @@ void ArwDecoder::DecodeLJpeg(const TiffIFD* raw) { firstErr.c_str()); } - PostProcessLJpeg(); - const TiffEntry* size_entry = raw->getEntry(TiffTag::SONYRAWIMAGESIZE); iRectangle2D crop(0, 0, size_entry->getU32(0), size_entry->getU32(1)); mRaw->subFrame(crop); } -void ArwDecoder::PostProcessLJpeg() { - MSan::CheckMemIsInitialized(mRaw->getByteDataAsUncroppedArray2DRef()); - RawImage nonInterleavedRaw = mRaw; - - invariant(nonInterleavedRaw->dim.x % 4 == 0); - iPoint2D interleavedDims = {nonInterleavedRaw->dim.x / 2, - 2 * nonInterleavedRaw->dim.y}; - mRaw = RawImage::create(interleavedDims, RawImageType::UINT16, 1); - - const Array2DRef in = - nonInterleavedRaw->getU16DataAsUncroppedArray2DRef(); - const Array2DRef out = mRaw->getU16DataAsUncroppedArray2DRef(); - -#ifdef HAVE_OPENMP -#pragma omp parallel for schedule(static) default(none) firstprivate(in, out) -#endif - for (int inRow = 0; inRow < in.height(); ++inRow) { - static constexpr iPoint2D inMCUSize = {4, 1}; - static constexpr iPoint2D outMCUSize = {2, 2}; - - invariant(in.width() % inMCUSize.x == 0); - for (int MCUIdx = 0, numMCUsPerRow = in.width() / inMCUSize.x; - MCUIdx < numMCUsPerRow; ++MCUIdx) { - for (int outMCURow = 0; outMCURow != outMCUSize.y; ++outMCURow) { - for (int outMCUСol = 0; outMCUСol != outMCUSize.x; ++outMCUСol) { - out(outMCUSize.y * inRow + outMCURow, - outMCUSize.x * MCUIdx + outMCUСol) = - in(inRow, - MCUIdx * inMCUSize.x + outMCUSize.x * outMCURow + outMCUСol); - } - } - } - } -} - 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 68d569017..b84ed6c58 100644 --- a/src/librawspeed/decoders/ArwDecoder.h +++ b/src/librawspeed/decoders/ArwDecoder.h @@ -54,7 +54,6 @@ class ArwDecoder final : public AbstractTiffDecoder { RawImage decodeSRF(); void DecodeARW2(ByteStream input, uint32_t w, uint32_t h, uint32_t bpp); void DecodeLJpeg(const TiffIFD* raw); - void PostProcessLJpeg(); void DecodeUncompressed(const TiffIFD* raw) const; static void SonyDecrypt(Array1DRef ibuf, Array1DRef obuf, int len, uint32_t key); diff --git a/src/librawspeed/decompressors/LJpegDecoder.cpp b/src/librawspeed/decompressors/LJpegDecoder.cpp index 9dd853e87..946da1d24 100644 --- a/src/librawspeed/decompressors/LJpegDecoder.cpp +++ b/src/librawspeed/decompressors/LJpegDecoder.cpp @@ -125,26 +125,35 @@ Buffer::size_type LJpegDecoder::decodeScan() { const iRectangle2D imgFrame = { {static_cast(offX), static_cast(offY)}, {static_cast(w), static_cast(h)}}; - const LJpegDecompressor::Frame jpegFrame = {N_COMP, - iPoint2D(frame.w, frame.h)}; + const auto jpegFrameDim = iPoint2D(frame.w, frame.h); - if (iPoint2D(mRaw->getCpp() * maxDim.x, maxDim.y) != - iPoint2D(N_COMP * frame.w, frame.h)) - ThrowRDE("LJpeg frame does not match maximal tile size"); + auto maxRes = iPoint2D(mRaw->getCpp() * maxDim.x, maxDim.y); + if (maxRes.area() != N_COMP * jpegFrameDim.area()) + ThrowRDE("LJpeg frame area does not match maximal tile area"); - int numRowsPerRestartInterval; + if (maxRes.x % jpegFrameDim.x != 0 || maxRes.y % jpegFrameDim.y != 0) + ThrowRDE("Maximal output tile size is not a multiple of LJpeg frame size"); + + auto MCUSize = iPoint2D{maxRes.x / jpegFrameDim.x, maxRes.y / jpegFrameDim.y}; + if (MCUSize.area() != implicit_cast(N_COMP)) + ThrowRDE("Unexpected MCU size, does not match LJpeg component count"); + + const LJpegDecompressor::Frame jpegFrame = {MCUSize, jpegFrameDim}; + + int numLJpegRowsPerRestartInterval; if (numMCUsPerRestartInterval == 0) { // Restart interval not enabled, so all of the rows // are contained in the first (implicit) restart interval. - numRowsPerRestartInterval = jpegFrame.dim.y; + numLJpegRowsPerRestartInterval = jpegFrameDim.y; } else { - const int numMCUsPerRow = jpegFrame.dim.x; + const int numMCUsPerRow = jpegFrameDim.x; if (numMCUsPerRestartInterval % numMCUsPerRow != 0) ThrowRDE("Restart interval is not a multiple of frame row size"); - numRowsPerRestartInterval = numMCUsPerRestartInterval / numMCUsPerRow; + numLJpegRowsPerRestartInterval = numMCUsPerRestartInterval / numMCUsPerRow; } - LJpegDecompressor d(mRaw, imgFrame, jpegFrame, rec, numRowsPerRestartInterval, + LJpegDecompressor d(mRaw, imgFrame, jpegFrame, rec, + numLJpegRowsPerRestartInterval, input.peekRemainingBuffer().getAsArray1DRef()); return d.decode(); } diff --git a/src/librawspeed/decompressors/LJpegDecompressor.cpp b/src/librawspeed/decompressors/LJpegDecompressor.cpp index 9c4015ef2..76d9c02bd 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.cpp +++ b/src/librawspeed/decompressors/LJpegDecompressor.cpp @@ -21,6 +21,7 @@ #include "decompressors/LJpegDecompressor.h" #include "adt/Array1DRef.h" +#include "adt/Array2DRef.h" #include "adt/Casts.h" #include "adt/CroppedArray2DRef.h" #include "adt/Invariant.h" @@ -37,7 +38,6 @@ #include "io/Endianness.h" #include #include -#include #include #include #include @@ -52,11 +52,11 @@ namespace rawspeed { LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, Frame frame_, std::vector rec_, - int numRowsPerRestartInterval_, + int numLJpegRowsPerRestartInterval_, Array1DRef input_) : mRaw(std::move(img)), input(input_), imgFrame(imgFrame_), frame(std::move(frame_)), rec(std::move(rec_)), - numRowsPerRestartInterval(numRowsPerRestartInterval_) { + numLJpegRowsPerRestartInterval(numLJpegRowsPerRestartInterval_) { if (mRaw->getDataType() != RawImageType::UINT16) ThrowRDE("Unexpected data type (%u)", @@ -96,10 +96,12 @@ LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, if (imgFrame.pos.y + imgFrame.dim.y > mRaw->dim.y) ThrowRDE("Tile overflows image vertically"); - if (frame.cps < 1 || frame.cps > 4) - ThrowRDE("Unsupported number of components: %u", frame.cps); + if (iPoint2D{1, 1} != frame.mcu && iPoint2D{2, 1} != frame.mcu && + iPoint2D{3, 1} != frame.mcu && iPoint2D{4, 1} != frame.mcu && + iPoint2D{2, 2} != frame.mcu) + ThrowRDE("Unexpected MCU size: {%i, %i}", frame.mcu.x, frame.mcu.y); - if (rec.size() != static_cast(frame.cps)) + if (rec.size() != static_cast(frame.mcu.area())) ThrowRDE("Must have exactly one recepie per component"); for (const auto& recip : rec) { @@ -107,41 +109,45 @@ LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, ThrowRDE("Huffman table is not of a full decoding variety"); } - // We assume that the tile width requires at least one frame column. - if (imgFrame.dim.x < frame.cps) - ThrowRDE("Tile width is smaller than the frame cps"); + if (numLJpegRowsPerRestartInterval < 1) + ThrowRDE("Number of rows per restart interval must be positives"); - if (static_cast(frame.cps) * frame.dim.x > - std::numeric_limits::max()) + if (static_cast(frame.mcu.x) * frame.dim.x > + std::numeric_limits::max() || + static_cast(frame.mcu.y) * frame.dim.y > + std::numeric_limits::max()) ThrowRDE("LJpeg frame is too big"); - invariant(mRaw->dim.x > imgFrame.pos.x); - if ((static_cast(mRaw->getCpp()) * (mRaw->dim.x - imgFrame.pos.x)) < - frame.cps) - ThrowRDE("Got less pixels than the components per sample"); + if (static_cast(mRaw->getCpp()) * imgFrame.dim.x > + std::numeric_limits::max()) + ThrowRDE("Img frame is too big"); + + if (imgFrame.dim.x < frame.mcu.x || imgFrame.dim.y < frame.mcu.y) + ThrowRDE("Tile size is smaller than a single frame MCU"); + + if (imgFrame.dim.y % frame.mcu.y != 0) + ThrowRDE("Output row count is not a multiple of MCU row count"); - // How many output pixels are we expected to produce, as per DNG tiling? const int tileRequiredWidth = static_cast(mRaw->getCpp()) * imgFrame.dim.x; - // How many full pixel blocks do we need to consume for that? - if (const auto blocksToConsume = - implicit_cast(roundUpDivision(tileRequiredWidth, frame.cps)); - frame.dim.x < blocksToConsume || frame.dim.y < imgFrame.dim.y || - static_cast(frame.cps) * frame.dim.x < - static_cast(mRaw->getCpp()) * imgFrame.dim.x) { - ThrowRDE("LJpeg frame (%" PRIu64 ", %u) is smaller than expected (%u, %u)", - static_cast(frame.cps) * frame.dim.x, frame.dim.y, + // How many full pixel MCUs do we need to consume for that? + if (const auto mcusToConsume = + implicit_cast(roundUpDivision(tileRequiredWidth, frame.mcu.x)); + frame.dim.x < mcusToConsume || + frame.mcu.y * frame.dim.y < imgFrame.dim.y || + frame.mcu.x * frame.dim.x < tileRequiredWidth) { + ThrowRDE("LJpeg frame (%d, %d) is smaller than expected (%u, %u)", + frame.mcu.x * frame.dim.x, frame.mcu.y * frame.dim.y, tileRequiredWidth, imgFrame.dim.y); } - if (numRowsPerRestartInterval < 1) - ThrowRDE("Number of rows per restart interval must be positives"); + // How many full pixel MCUs will we produce? + numFullMCUs = tileRequiredWidth / frame.mcu.x; // Truncating division! + // Do we need to also produce part of a MCU? + trailingPixels = tileRequiredWidth % frame.mcu.x; - // How many full pixel blocks will we produce? - fullBlocks = tileRequiredWidth / frame.cps; // Truncating division! - // Do we need to also produce part of a block? - trailingPixels = tileRequiredWidth % frame.cps; + cps = implicit_cast(frame.mcu.area()); // FIXME; } template @@ -167,55 +173,85 @@ std::array LJpegDecompressor::getInitialPreds() const { return preds; } -template +namespace { + +template +constexpr iPoint2D MCU = {MCUWidth, MCUHeight}; + +} // namespace + +template void LJpegDecompressor::decodeRowN( - Array1DRef outRow, std::array pred, + Array2DRef outStripe, std::array pred, std::array>, N_COMP> ht, BitStreamerJPEG& bs) const { + invariant(MCUSize.area() == N_COMP); + invariant(outStripe.width() >= MCUSize.x); + invariant(outStripe.height() == MCUSize.y); + // FIXME: predictor may have value outside of the uint16_t. // https://github.com/darktable-org/rawspeed/issues/175 - int col = 0; - // 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] = - uint16_t(pred[i] + (static_cast&>(ht[i])) - .decodeDifference(bs)); - outRow(col + i) = pred[i]; + int mcuIdx = 0; + // For x, we first process all full pixel MCUs within the image buffer ... + for (; mcuIdx < numFullMCUs; ++mcuIdx) { + const auto outTile = CroppedArray2DRef(outStripe, + /*offsetCols=*/MCUSize.x * mcuIdx, + /*offsetRows=*/0, + /*croppedWidth=*/MCUSize.x, + /*croppedHeight=*/MCUSize.y) + .getAsArray2DRef(); + for (int MCURow = 0; MCURow != MCUSize.y; ++MCURow) { + for (int MCUСol = 0; MCUСol != MCUSize.x; ++MCUСol) { + int c = MCUSize.x * MCURow + MCUСol; + pred[c] = + uint16_t(pred[c] + (static_cast&>(ht[c])) + .decodeDifference(bs)); + outTile(MCURow, MCUСol) = pred[c]; + } } } - // Sometimes we also need to consume one more block, and produce part of it. + // Sometimes we also need to consume one more MCU, and produce part of it. if (trailingPixels != 0) { // Some rather esoteric DNG's have odd dimensions, e.g. width % 2 = 1. // We may end up needing just part of last N_COMP pixels. invariant(trailingPixels > 0); invariant(trailingPixels < N_COMP); - int c = 0; - for (; c < trailingPixels; ++c) { - pred[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); - for (; c < N_COMP; ++c) { - (static_cast&>(ht[c])).decodeDifference(bs); + invariant(N_COMP > 1 && "can't want part of 1-pixel-wide block"); + // Some rather esoteric DNG's have odd dimensions, e.g. width % 2 = 1. + // We may end up needing just part of last N_COMP pixels. + for (int MCURow = 0; MCURow != MCUSize.y; ++MCURow) { + for (int MCUСol = 0; MCUСol != MCUSize.x; ++MCUСol) { + int c = MCUSize.x * MCURow + MCUСol; + pred[c] = + uint16_t(pred[c] + (static_cast&>(ht[c])) + .decodeDifference(bs)); + int stripeRow = MCURow; + int stripeCol = (MCUSize.x * mcuIdx) + MCUСol; + if (stripeCol < outStripe.width()) + outStripe(stripeRow, stripeCol) = pred[c]; + } } - col += N_COMP; // We did just process one more block. + ++mcuIdx; // We did just process one more MCU. } // ... and discard the rest. - for (; col < N_COMP * frame.dim.x; col += N_COMP) { + for (; mcuIdx < frame.dim.x; ++mcuIdx) { for (int i = 0; i != N_COMP; ++i) (static_cast&>(ht[i])).decodeDifference(bs); } } // N_COMP == number of components (2, 3 or 4) -template ByteStream::size_type LJpegDecompressor::decodeN() const { +template +ByteStream::size_type LJpegDecompressor::decodeN() const { + invariant(MCU == this->frame.mcu); + + invariant(MCU.hasPositiveArea()); + // FIXME: workarounding lack of constexpr std::abs() :( + constexpr int N_COMP = MCU.x * MCU.y; + invariant(mRaw->getCpp() > 0); invariant(N_COMP > 0); @@ -234,15 +270,15 @@ template ByteStream::size_type LJpegDecompressor::decodeN() const { // The tiles at the bottom and the right may extend beyond the dimension of // the raw image buffer. The excessive content has to be ignored. - invariant(frame.dim.y >= imgFrame.dim.y); - invariant(static_cast(frame.cps) * frame.dim.x >= + invariant(static_cast(cps) * frame.dim.x >= static_cast(mRaw->getCpp()) * imgFrame.dim.x); invariant(imgFrame.pos.y + imgFrame.dim.y <= mRaw->dim.y); invariant(imgFrame.pos.x + imgFrame.dim.x <= mRaw->dim.x); - const auto numRestartIntervals = implicit_cast( - roundUpDivision(imgFrame.dim.y, numRowsPerRestartInterval)); + invariant(imgFrame.dim.y % frame.mcu.y == 0); + const auto numRestartIntervals = implicit_cast(roundUpDivision( + imgFrame.dim.y / frame.mcu.y, numLJpegRowsPerRestartInterval)); invariant(numRestartIntervals >= 0); invariant(numRestartIntervals != 0); @@ -250,7 +286,7 @@ template ByteStream::size_type LJpegDecompressor::decodeN() const { for (int restartIntervalIndex = 0; restartIntervalIndex != numRestartIntervals; ++restartIntervalIndex) { auto pred = getInitialPreds(); - auto predNext = Array1DRef(pred.data(), pred.size()); + auto predNext = Array2DRef(pred.data(), MCU.x, MCU.y); if (restartIntervalIndex != 0) { auto marker = peekMarker(inputStream); @@ -266,11 +302,12 @@ template ByteStream::size_type LJpegDecompressor::decodeN() const { BitStreamerJPEG bs(inputStream.peekRemainingBuffer().getAsArray1DRef()); - for (int rowOfRestartInterval = 0; - rowOfRestartInterval != numRowsPerRestartInterval; - ++rowOfRestartInterval) { - const int row = numRowsPerRestartInterval * restartIntervalIndex + - rowOfRestartInterval; + for (int ljpegRowOfRestartInterval = 0; + ljpegRowOfRestartInterval != numLJpegRowsPerRestartInterval; + ++ljpegRowOfRestartInterval) { + const int row = + frame.mcu.y * (numLJpegRowsPerRestartInterval * restartIntervalIndex + + ljpegRowOfRestartInterval); invariant(row >= 0); invariant(row <= imgFrame.dim.y); @@ -280,15 +317,29 @@ template ByteStream::size_type LJpegDecompressor::decodeN() const { break; } - auto outRow = 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(); + for (int MCURow = 0; MCURow != MCU.y; ++MCURow) { + for (int MCUСol = 0; MCUСol != MCU.x; ++MCUСol) { + int c = MCU.x * MCURow + MCUСol; + pred[c] = predNext(MCURow, MCUСol); + } + } + + const auto outStripe = CroppedArray2DRef(img, + /*offsetCols=*/0, + /*offsetRows=*/row, + /*croppedWidth=*/img.width(), + /*croppedHeight=*/frame.mcu.y) + .getAsArray2DRef(); - decodeRowN(outRow, pred, ht, bs); + // the predictor for the next line is the start of this line + predNext = CroppedArray2DRef(outStripe, + /*offsetCols=*/0, + /*offsetRows=*/0, + /*croppedWidth=*/MCU.x, + /*croppedHeight=*/MCU.y) + .getAsArray2DRef(); + + decodeRowN(outStripe, pred, ht, bs); } inputStream.skipBytes(bs.getStreamPosition()); @@ -298,18 +349,34 @@ template ByteStream::size_type LJpegDecompressor::decodeN() const { } ByteStream::size_type LJpegDecompressor::decode() const { - switch (frame.cps) { + switch (frame.mcu.area()) { case 1: - return decodeN<1>(); + if (frame.mcu == MCU<1, 1>) { + return decodeN>(); + } + break; case 2: - return decodeN<2>(); + if (frame.mcu == MCU<2, 1>) { + return decodeN>(); + } + break; case 3: - return decodeN<3>(); + if (frame.mcu == MCU<3, 1>) { + return decodeN>(); + } + break; case 4: - return decodeN<4>(); + if (frame.mcu == MCU<4, 1>) { + return decodeN>(); + } + if (frame.mcu == MCU<2, 2>) { + return decodeN>(); + } + break; default: __builtin_unreachable(); } + __builtin_unreachable(); } } // namespace rawspeed diff --git a/src/librawspeed/decompressors/LJpegDecompressor.h b/src/librawspeed/decompressors/LJpegDecompressor.h index 18aef77f0..e61cf53e3 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.h +++ b/src/librawspeed/decompressors/LJpegDecompressor.h @@ -41,7 +41,7 @@ namespace rawspeed { class LJpegDecompressor final { public: struct Frame final { - const int cps; + const iPoint2D mcu; const iPoint2D dim; }; struct PerComponentRecipe final { @@ -57,9 +57,10 @@ class LJpegDecompressor final { const Frame frame; const std::vector rec; - const int numRowsPerRestartInterval; + const int numLJpegRowsPerRestartInterval; - int fullBlocks = 0; + int cps = 0; + int numFullMCUs = 0; int trailingPixels = 0; template @@ -75,18 +76,19 @@ class LJpegDecompressor final { template [[nodiscard]] std::array getInitialPreds() const; - template + template __attribute__((always_inline)) inline void decodeRowN( - Array1DRef outRow, std::array pred, + Array2DRef outStripe, std::array pred, std::array>, N_COMP> ht, BitStreamerJPEG& bs) const; - template [[nodiscard]] ByteStream::size_type decodeN() const; + template + [[nodiscard]] __attribute__((noinline)) ByteStream::size_type decodeN() const; public: LJpegDecompressor(RawImage img, iRectangle2D imgFrame, Frame frame, std::vector rec, - int numRowsPerRestartInterval_, + int numLJpegRowsPerRestartInterval_, Array1DRef input); [[nodiscard]] ByteStream::size_type decode() const;