diff --git a/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp b/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp index aa7b7154c..2d1b1a63a 100644 --- a/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp +++ b/fuzz/librawspeed/decompressors/LJpegDecompressor.cpp @@ -88,10 +88,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { }); const int numLJpegRowsPerRestartInterval = bs.getI32(); + const int predictorMode = bs.getByte(); + const rawspeed::LJpegDecompressor::DecodeSettings settings{ + numLJpegRowsPerRestartInterval, predictorMode}; rawspeed::LJpegDecompressor d( mRaw, rawspeed::iRectangle2D(mRaw->dim.x, mRaw->dim.y), frame, rec, - numLJpegRowsPerRestartInterval, + settings, bs.getSubStream(/*offset=*/0).peekRemainingBuffer().getAsArray1DRef()); mRaw->createData(); (void)d.decode(); diff --git a/src/librawspeed/decoders/DngDecoder.cpp b/src/librawspeed/decoders/DngDecoder.cpp index ecd119898..ae972d2a5 100644 --- a/src/librawspeed/decoders/DngDecoder.cpp +++ b/src/librawspeed/decoders/DngDecoder.cpp @@ -699,12 +699,20 @@ void DngDecoder::decodeMetaDataInternal(const CameraMetaData* meta) { TiffID id; - try { + if (mRootIFD->hasEntryRecursive(TiffTag::MAKE) && + mRootIFD->hasEntryRecursive(TiffTag::MODEL)) { id = mRootIFD->getID(); - } catch (const RawspeedException& e) { - mRaw->setError(e.what()); - // not all dngs have MAKE/MODEL entries, - // will be dealt with by using UNIQUECAMERAMODEL below + } else if (mRootIFD->hasEntryRecursive(TiffTag::UNIQUECAMERAMODEL)) { + // Not all DNGs have MAKE/MODEL entries (e.g. Blackmagic CinemaDNG). + // Fall back to UNIQUECAMERAMODEL for identification. + std::string unique = + mRootIFD->getEntryRecursive(TiffTag::UNIQUECAMERAMODEL)->getString(); + if (unique.empty()) + ThrowRDE("UNIQUECAMERAMODEL is empty"); + id.make = unique; + id.model = unique; + } else { + ThrowRDE("DNG has neither MAKE/MODEL nor UNIQUECAMERAMODEL"); } // Set the make and model diff --git a/src/librawspeed/decoders/SimpleTiffDecoder.h b/src/librawspeed/decoders/SimpleTiffDecoder.h index 1fa79fdc8..e08b5e317 100644 --- a/src/librawspeed/decoders/SimpleTiffDecoder.h +++ b/src/librawspeed/decoders/SimpleTiffDecoder.h @@ -39,7 +39,8 @@ class SimpleTiffDecoder : public AbstractTiffDecoder { public: SimpleTiffDecoder(TiffRootIFDOwner&& root, Buffer file) - : AbstractTiffDecoder(std::move(root), file) {} + : AbstractTiffDecoder(std::move(root), file), raw(nullptr), width(0), + height(0), off(0), c2(0) {} void prepareForRawDecoding(); diff --git a/src/librawspeed/decompressors/AbstractDngDecompressor.cpp b/src/librawspeed/decompressors/AbstractDngDecompressor.cpp index b828a4fe2..6ba745b67 100644 --- a/src/librawspeed/decompressors/AbstractDngDecompressor.cpp +++ b/src/librawspeed/decompressors/AbstractDngDecompressor.cpp @@ -35,6 +35,7 @@ #include "io/ByteStream.h" #include "io/Endianness.h" #include "io/IOException.h" +#include #include #include #include @@ -51,6 +52,64 @@ namespace rawspeed { +namespace { + +// Some DNG files (e.g. Blackmagic CinemaDNG) use TIFF compression=7 +// (lossless JPEG) but the actual tile data contains lossy DCT JPEG +// (SOF0/SOF1/SOF2 with DQT). Detect this by scanning the first few +// JPEG markers in the tile stream. +[[nodiscard]] bool tileContainsLossyJpeg(const ByteStream& bs) { + const auto remaining = bs.getRemainSize(); + if (remaining < 4) + return false; + + // Must start with JPEG SOI marker + if (bs.peekByte(0) != 0xFF || bs.peekByte(1) != 0xD8) + return false; + + // Scan markers after SOI. Stop after a reasonable number of bytes. + const auto limit = + std::min(remaining, static_cast(1024)); + ByteStream::size_type pos = 2; + + while (pos + 3 < limit) { + if (bs.peekByte(pos) != 0xFF) + return false; // Invalid marker - stop scanning + + const uint8_t marker = bs.peekByte(pos + 1); + + // DQT (quantization table) is definitive proof of lossy JPEG + if (marker == 0xDB) // DQT + return true; + + // SOF0/SOF1/SOF2 = lossy DCT-based JPEG + if (marker == 0xC0 || marker == 0xC1 || marker == 0xC2) + return true; + + // SOF3 = lossless - this is what compression=7 should be + if (marker == 0xC3) + return false; + + // SOS = start of scan data - stop scanning + if (marker == 0xDA) + return false; + + // Skip this marker segment + if (pos + 4 > remaining) + return false; + const auto segLen = static_cast( + (static_cast(bs.peekByte(pos + 2)) << 8) | + static_cast(bs.peekByte(pos + 3))); + if (segLen < 2) + return false; + pos += 2 + segLen; + } + + return false; +} + +} // namespace + template <> void AbstractDngDecompressor::decompressThread<1>() const noexcept { #ifdef HAVE_OPENMP #pragma omp for schedule(static) @@ -116,6 +175,15 @@ template <> void AbstractDngDecompressor::decompressThread<7>() const noexcept { for (const auto& e : Array1DRef(slices.data(), implicit_cast(slices.size()))) { try { +#ifdef HAVE_JPEG + // Some cameras (e.g. Blackmagic CinemaDNG) mislabel lossy DCT JPEG + // tiles as compression=7 (lossless JPEG). Detect and redirect. + if (tileContainsLossyJpeg(e.bs)) { + JpegDecompressor j(e.bs.peekBuffer(e.bs.getRemainSize()), mRaw); + j.decode(e.offX, e.offY); + continue; + } +#endif LJpegDecoder d(e.bs, mRaw); d.decode(e.offX, e.offY, e.width, e.height, iPoint2D(e.dsc.tileW, e.dsc.tileH), mFixLjpeg); diff --git a/src/librawspeed/decompressors/JpegDecompressor.cpp b/src/librawspeed/decompressors/JpegDecompressor.cpp index 569bb037a..a0a4da39b 100644 --- a/src/librawspeed/decompressors/JpegDecompressor.cpp +++ b/src/librawspeed/decompressors/JpegDecompressor.cpp @@ -139,6 +139,10 @@ void JpegDecompressor::decode(uint32_t offX, if (JPEG_HEADER_OK != jpeg_read_header(&dinfo, static_cast(true))) ThrowRDE("Unable to read JPEG header"); + if (dinfo.data_precision != 8) + ThrowRDE("Lossy JPEG tiles with %d-bit precision are not yet supported.", + dinfo.data_precision); + jpeg_start_decompress(&dinfo); if (dinfo.output_components != static_cast(mRaw->getCpp())) ThrowRDE("Component count doesn't match"); diff --git a/src/librawspeed/decompressors/LJpegDecoder.cpp b/src/librawspeed/decompressors/LJpegDecoder.cpp index a4efc03bf..31d3b8702 100644 --- a/src/librawspeed/decompressors/LJpegDecoder.cpp +++ b/src/librawspeed/decompressors/LJpegDecoder.cpp @@ -20,6 +20,7 @@ */ #include "decompressors/LJpegDecoder.h" +#include "adt/Array1DRef.h" #include "adt/Casts.h" #include "adt/Invariant.h" #include "adt/Point.h" @@ -30,16 +31,140 @@ #include "io/Buffer.h" #include "io/ByteStream.h" #include -#include #include +#include #include #include #include -using std::copy_n; - namespace rawspeed { +namespace { + +using PerCompRecipeVec = std::vector; + +struct ScanSettings final { + RawImage raw; + iRectangle2D imgFrame; + iPoint2D jpegFrameDim; + iPoint2D maxRes; + LJpegDecompressor::DecodeSettings decode; + Array1DRef input; +}; + +[[nodiscard]] int +getNumLJpegRowsPerRestartInterval(uint32_t numMCUsPerRestartInterval, + iPoint2D jpegFrameDim) { + if (numMCUsPerRestartInterval == 0) + return jpegFrameDim.y; + + const int numMCUsPerRow = jpegFrameDim.x; + if (numMCUsPerRestartInterval % numMCUsPerRow != 0) + ThrowRDE("Restart interval is not a multiple of frame row size"); + return implicit_cast(numMCUsPerRestartInterval) / numMCUsPerRow; +} + +[[nodiscard]] iPoint2D getMaxResolution(const RawImage& raw, iPoint2D maxDim, + int numComponents, + iPoint2D jpegFrameDim) { + if (implicit_cast(maxDim.x) * implicit_cast(raw->getCpp()) > + std::numeric_limits::max()) + ThrowRDE("Maximal output tile is too large"); + + const auto maxRes = + iPoint2D(implicit_cast(raw->getCpp()) * maxDim.x, maxDim.y); + if (maxRes.area() != numComponents * jpegFrameDim.area()) + ThrowRDE("LJpeg frame area does not match maximal tile area"); + + return maxRes; +} + +[[nodiscard]] iPoint2D +getStandardMCUSize(int numComponents, iPoint2D jpegFrameDim, iPoint2D maxRes) { + if (jpegFrameDim.x > maxRes.x) + return {}; + + if (maxRes.x % jpegFrameDim.x != 0 || maxRes.y % jpegFrameDim.y != 0) + ThrowRDE("Maximal output tile size is not a multiple of LJpeg frame size"); + + const auto mcuSize = + iPoint2D{maxRes.x / jpegFrameDim.x, maxRes.y / jpegFrameDim.y}; + if (mcuSize.area() != implicit_cast(numComponents)) + ThrowRDE("Unexpected MCU size, does not match LJpeg component count"); + + if (mcuSize.x < mcuSize.y) + return {}; + + return mcuSize; +} + +[[nodiscard]] ByteStream::size_type +decodeStandardScan(const ScanSettings& settings, iPoint2D mcuSize, + const PerCompRecipeVec& rec) { + const LJpegDecompressor::Frame jpegFrame = {mcuSize, settings.jpegFrameDim}; + LJpegDecompressor d(settings.raw, settings.imgFrame, jpegFrame, rec, + settings.decode, settings.input); + return d.decode(); +} + +void copyDeinterleavedRows(const RawImage& raw, const RawImage& tmpRaw, + uint32_t offX, uint32_t offY, uint32_t tileWidth, + int widthPack) { + const auto tmpData = tmpRaw->getU16DataAsUncroppedArray2DRef(); + const auto outData = raw->getU16DataAsUncroppedArray2DRef(); + + const auto cpp = implicit_cast(raw->getCpp()); + const int outRowPixels = cpp * implicit_cast(tileWidth); + + for (int jpegRow = 0; jpegRow < tmpRaw->dim.y; ++jpegRow) { + for (int pack = 0; pack < widthPack; ++pack) { + const int tileRow = implicit_cast(offY) + jpegRow * widthPack + pack; + if (tileRow >= raw->dim.y) + continue; + + const int srcCol = pack * outRowPixels; + const int dstCol = cpp * implicit_cast(offX); + std::memcpy(&outData(tileRow, dstCol), &tmpData(jpegRow, srcCol), + sizeof(uint16_t) * outRowPixels); + } + } +} + +[[nodiscard]] ByteStream::size_type +decodeInvertedScan(const ScanSettings& settings, int numComponents, + uint32_t offX, uint32_t offY, uint32_t tileWidth, + const PerCompRecipeVec& rec) { + const int effectiveJpegWidth = settings.jpegFrameDim.x * numComponents; + + if (effectiveJpegWidth % settings.maxRes.x != 0) + ThrowRDE("Effective JPEG width is not a multiple of tile width"); + if (settings.maxRes.y % settings.jpegFrameDim.y != 0) + ThrowRDE("Tile height is not a multiple of LJpeg frame height"); + + const int widthPack = effectiveJpegWidth / settings.maxRes.x; + if (widthPack * settings.jpegFrameDim.y != settings.maxRes.y) + ThrowRDE("Inverted reshape dimensions mismatch"); + if (widthPack < 1 || widthPack > 4) + ThrowRDE("Unexpected row packing factor: %d", widthPack); + + const auto mcuSize = iPoint2D{numComponents, 1}; + RawImage tmpRaw = + RawImage::create(iPoint2D(effectiveJpegWidth, settings.jpegFrameDim.y), + RawImageType::UINT16, 1); + const iRectangle2D tmpFrame = {{0, 0}, + {effectiveJpegWidth, settings.jpegFrameDim.y}}; + const LJpegDecompressor::Frame jpegFrame = {mcuSize, settings.jpegFrameDim}; + + LJpegDecompressor d(tmpRaw, tmpFrame, jpegFrame, rec, settings.decode, + settings.input); + const auto consumed = d.decode(); + + copyDeinterleavedRows(settings.raw, tmpRaw, offX, offY, tileWidth, widthPack); + return consumed; +} + +} // namespace + LJpegDecoder::LJpegDecoder(ByteStream bs, const RawImage& img) : AbstractLJpegDecoder(bs, img) { if (mRaw->getDataType() != RawImageType::UINT16) @@ -104,64 +229,44 @@ 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++) if (frame.compInfo[i].superH != 1 || frame.compInfo[i].superV != 1) ThrowRDE("Unsupported subsampling"); - int N_COMP = frame.cps; + const int numComponents = frame.cps; - std::vector rec; - rec.reserve(N_COMP); - std::generate_n(std::back_inserter(rec), N_COMP, - [&rec, hts = getPrefixCodeDecoders(N_COMP), - initPred = getInitialPredictors( - N_COMP)]() -> LJpegDecompressor::PerComponentRecipe { + PerCompRecipeVec rec; + rec.reserve(numComponents); + std::generate_n(std::back_inserter(rec), numComponents, + [&rec, hts = getPrefixCodeDecoders(numComponents), + initPred = getInitialPredictors(numComponents)]() + -> LJpegDecompressor::PerComponentRecipe { const auto i = implicit_cast(rec.size()); return {*hts[i], initPred[i]}; }); - const iRectangle2D imgFrame = { - {static_cast(offX), static_cast(offY)}, - {static_cast(w), static_cast(h)}}; const auto jpegFrameDim = iPoint2D(frame.w, frame.h); - - if (implicit_cast(maxDim.x) * implicit_cast(mRaw->getCpp()) > - std::numeric_limits::max()) - ThrowRDE("Maximal output tile is too large"); - - auto maxRes = - iPoint2D(implicit_cast(mRaw->getCpp()) * maxDim.x, maxDim.y); - if (maxRes.area() != N_COMP * jpegFrameDim.area()) - ThrowRDE("LJpeg frame area does not match maximal tile area"); - - 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. - numLJpegRowsPerRestartInterval = jpegFrameDim.y; - } else { - const int numMCUsPerRow = jpegFrameDim.x; - if (numMCUsPerRestartInterval % numMCUsPerRow != 0) - ThrowRDE("Restart interval is not a multiple of frame row size"); - numLJpegRowsPerRestartInterval = numMCUsPerRestartInterval / numMCUsPerRow; - } - - LJpegDecompressor d(mRaw, imgFrame, jpegFrame, rec, - numLJpegRowsPerRestartInterval, - input.peekRemainingBuffer().getAsArray1DRef()); - return d.decode(); + const auto maxRes = + getMaxResolution(mRaw, maxDim, numComponents, jpegFrameDim); + const ScanSettings settings{mRaw, + {{static_cast(offX), static_cast(offY)}, + {static_cast(w), static_cast(h)}}, + jpegFrameDim, + maxRes, + {getNumLJpegRowsPerRestartInterval( + numMCUsPerRestartInterval, jpegFrameDim), + implicit_cast(predictorMode)}, + input.peekRemainingBuffer().getAsArray1DRef()}; + + if (const auto mcuSize = + getStandardMCUSize(numComponents, jpegFrameDim, maxRes); + mcuSize.hasPositiveArea()) + return decodeStandardScan(settings, mcuSize, rec); + + return decodeInvertedScan(settings, numComponents, offX, offY, w, rec); } } // namespace rawspeed diff --git a/src/librawspeed/decompressors/LJpegDecompressor.cpp b/src/librawspeed/decompressors/LJpegDecompressor.cpp index c13618297..57b36f7cd 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.cpp +++ b/src/librawspeed/decompressors/LJpegDecompressor.cpp @@ -52,11 +52,12 @@ namespace rawspeed { LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, Frame frame_, std::vector rec_, - int numLJpegRowsPerRestartInterval_, + DecodeSettings settings, Array1DRef input_) : mRaw(std::move(img)), input(input_), imgFrame(imgFrame_), frame(std::move(frame_)), rec(std::move(rec_)), - numLJpegRowsPerRestartInterval(numLJpegRowsPerRestartInterval_) { + numLJpegRowsPerRestartInterval(settings.numLJpegRowsPerRestartInterval), + predictorMode(settings.predictorMode) { if (mRaw->getDataType() != RawImageType::UINT16) ThrowRDE("Unexpected data type (%u)", @@ -100,8 +101,8 @@ LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, ThrowRDE("Frame has zero size"); 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) + iPoint2D{1, 2} != 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.mcu.area())) @@ -115,6 +116,9 @@ LJpegDecompressor::LJpegDecompressor(RawImage img, iRectangle2D imgFrame_, if (numLJpegRowsPerRestartInterval < 1) ThrowRDE("Number of rows per restart interval must be positives"); + if (predictorMode < 1 || predictorMode > 7) + ThrowRDE("Unsupported predictor mode: %i", predictorMode); + if (static_cast(frame.mcu.x) * frame.dim.x > std::numeric_limits::max() || static_cast(frame.mcu.y) * frame.dim.y > @@ -181,9 +185,39 @@ constexpr iPoint2D MCU = {MCUWidth, MCUHeight}; } // namespace -template +namespace { + +// Compute the LJpeg prediction value given predictor mode and neighbor values. +// Ra = left, Rb = above, Rc = above-left. +// All arithmetic done in int32_t to avoid overflow in modes 4-6. +// Result is modulo 2^16 per ITU-T T.81. +inline int computePrediction(int predMode, int Ra, int Rb, int Rc) { + switch (predMode) { + case 1: + return Ra; + case 2: + return Rb; + case 3: + return Rc; + case 4: + return Ra + Rb - Rc; + case 5: + return Ra + ((Rb - Rc) >> 1); + case 6: + return Rb + ((Ra - Rc) >> 1); + case 7: + return (Ra + Rb) >> 1; + default: + __builtin_unreachable(); + } +} + +} // namespace + +template void LJpegDecompressor::decodeRowN( Array2DRef outStripe, Array2DRef pred, + Array2DRef prevStripe, std::array>, N_COMP> ht, BitStreamerJPEG& bs) const { invariant(MCUSize.area() == N_COMP); @@ -206,11 +240,21 @@ void LJpegDecompressor::decodeRowN( .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; - int prediction = pred(MCURow, MCUСol); - int diff = (static_cast&>(ht[c])) - .decodeDifference(bs); - int pix = prediction + diff; + const int c = (MCUSize.x * MCURow) + MCUСol; + int prediction; + if constexpr (!Use2DPred) { + prediction = pred(MCURow, MCUСol); + } else { + const int Ra = pred(MCURow, MCUСol); + const int stripeCol = MCUSize.x * mcuIdx + MCUСol; + const int Rb = prevStripe(MCURow, stripeCol); + const int Rc = + (mcuIdx > 0) ? prevStripe(MCURow, stripeCol - MCUSize.x) : Rb; + prediction = computePrediction(predictorMode, Ra, Rb, Rc); + } + const int diff = (static_cast&>(ht[c])) + .decodeDifference(bs); + const int pix = prediction + diff; outTile(MCURow, MCUСol) = uint16_t(pix); } } @@ -229,15 +273,27 @@ void LJpegDecompressor::decodeRowN( // 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; - int prediction = pred(MCURow, MCUСol); - int diff = (static_cast&>(ht[c])) - .decodeDifference(bs); - int pix = prediction + diff; - int stripeRow = MCURow; - int stripeCol = (MCUSize.x * mcuIdx) + MCUСol; + const int c = (MCUSize.x * MCURow) + MCUСol; + int prediction; + if constexpr (!Use2DPred) { + prediction = pred(MCURow, MCUСol); + } else { + const int Ra = pred(MCURow, MCUСol); + const int stripeCol = MCUSize.x * mcuIdx + MCUСol; + const int Rb = (stripeCol < prevStripe.width()) + ? prevStripe(MCURow, stripeCol) + : Ra; + const int Rc = (mcuIdx > 0 && stripeCol < prevStripe.width()) + ? prevStripe(MCURow, stripeCol - MCUSize.x) + : Rb; + prediction = computePrediction(predictorMode, Ra, Rb, Rc); + } + const int diff = (static_cast&>(ht[c])) + .decodeDifference(bs); + const int pix = prediction + diff; + const int stripeCol = (MCUSize.x * mcuIdx) + MCUСol; if (stripeCol < outStripe.width()) - outStripe(stripeRow, stripeCol) = uint16_t(pix); + outStripe(MCURow, stripeCol) = uint16_t(pix); } } ++mcuIdx; // We did just process one more MCU. @@ -284,6 +340,7 @@ ByteStream::size_type LJpegDecompressor::decodeN() const { restartIntervalIndex != numRestartIntervals; ++restartIntervalIndex) { auto predStorage = getInitialPreds(); auto pred = Array2DRef(predStorage.data(), MCU.x, MCU.y); + bool isFirstRow = true; if (restartIntervalIndex != 0) { auto marker = peekMarker(inputStream); @@ -321,7 +378,25 @@ ByteStream::size_type LJpegDecompressor::decodeN() const { /*croppedHeight=*/frame.mcu.y) .getAsArray2DRef(); - decodeRowN(outStripe, pred, ht, bs); + // For predictor modes 2-7, we need the previous row (stripe). + // For the first row of each restart interval, use predictor mode 1 + // (per ITU-T T.81: first row always uses horizontal prediction). + // For the first row, prevStripe points to outStripe itself (unused + // since Use2DPred will be false). + const Array2DRef prevStripe = + isFirstRow ? Array2DRef(outStripe) + : CroppedArray2DRef( + img, + /*offsetCols=*/0, + /*offsetRows=*/row - frame.mcu.y, + /*croppedWidth=*/img.width(), + /*croppedHeight=*/frame.mcu.y) + .getAsArray2DRef(); + + if (!isFirstRow && predictorMode != 1) + decodeRowN(outStripe, pred, prevStripe, ht, bs); + else + decodeRowN(outStripe, pred, prevStripe, ht, bs); // The predictor for the next line is the start of this line. pred = CroppedArray2DRef(outStripe, @@ -330,6 +405,7 @@ ByteStream::size_type LJpegDecompressor::decodeN() const { /*croppedWidth=*/MCU.x, /*croppedHeight=*/MCU.y) .getAsArray2DRef(); + isFirstRow = false; } inputStream.skipBytes(bs.getStreamPosition()); @@ -349,6 +425,9 @@ ByteStream::size_type LJpegDecompressor::decode() const { if (frame.mcu == MCU<2, 1>) { return decodeN>(); } + if (frame.mcu == MCU<1, 2>) { + return decodeN>(); + } break; case 3: if (frame.mcu == MCU<3, 1>) { diff --git a/src/librawspeed/decompressors/LJpegDecompressor.h b/src/librawspeed/decompressors/LJpegDecompressor.h index 69c77739f..11caad318 100644 --- a/src/librawspeed/decompressors/LJpegDecompressor.h +++ b/src/librawspeed/decompressors/LJpegDecompressor.h @@ -45,6 +45,10 @@ class LJpegDecompressor final { const iPoint2D mcu; const iPoint2D dim; }; + struct DecodeSettings final { + const int numLJpegRowsPerRestartInterval; + const int predictorMode; + }; struct PerComponentRecipe final { const PrefixCodeDecoder<>& ht; const uint16_t initPred; @@ -59,6 +63,7 @@ class LJpegDecompressor final { const Frame frame; const std::vector rec; const int numLJpegRowsPerRestartInterval; + const int predictorMode; int numFullMCUs = 0; int trailingPixels = 0; @@ -76,9 +81,10 @@ class LJpegDecompressor final { template [[nodiscard]] std::array getInitialPreds() const; - template + template __attribute__((always_inline)) inline void decodeRowN( Array2DRef outStripe, Array2DRef pred, + Array2DRef prevStripe, std::array>, N_COMP> ht, BitStreamerJPEG& bs) const; @@ -88,8 +94,7 @@ class LJpegDecompressor final { public: LJpegDecompressor(RawImage img, iRectangle2D imgFrame, Frame frame, std::vector rec, - int numLJpegRowsPerRestartInterval_, - Array1DRef input); + DecodeSettings settings, Array1DRef input); [[nodiscard]] ByteStream::size_type decode() const; }; diff --git a/src/librawspeed/io/FileReader.cpp b/src/librawspeed/io/FileReader.cpp index 2378ba4ca..79d0f18f3 100644 --- a/src/librawspeed/io/FileReader.cpp +++ b/src/librawspeed/io/FileReader.cpp @@ -53,9 +53,12 @@ FileReader::readFile() const { size_t fileSize = 0; #if defined(__unix__) || defined(__APPLE__) - auto fclose = [](std::FILE* fp) { std::fclose(fp); }; - using file_ptr = std::unique_ptr; - file_ptr file(fopen(fileName, "rb"), fclose); + using file_ptr = std::unique_ptr; + // The opened stream is owned by `file`, whose deleter (std::fclose) closes it + // on every exit path. The static analyzer does not model the unique_ptr + // destructor, so it wrongly reports a leak here. + // codechecker_false_positive [unix.Stream] close done by unique_ptr deleter + file_ptr file(std::fopen(fileName, "rb"), &std::fclose); if (file == nullptr) ThrowFIE("Could not open file \"%s\".", fileName);