Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion fuzz/librawspeed/decompressors/LJpegDecompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
18 changes: 13 additions & 5 deletions src/librawspeed/decoders/DngDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions src/librawspeed/decompressors/AbstractDngDecompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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<ByteStream::size_type>(1024));

Check warning

Code scanning / CodeChecker

no header providing "std::min" is directly included Warning

no header providing "std::min" is directly included
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<uint16_t>(
(static_cast<uint16_t>(bs.peekByte(pos + 2)) << 8) |
static_cast<uint16_t>(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)
Expand Down Expand Up @@ -116,6 +174,15 @@
for (const auto& e :
Array1DRef(slices.data(), implicit_cast<int>(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);
Expand Down
4 changes: 4 additions & 0 deletions src/librawspeed/decompressors/JpegDecompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ void JpegDecompressor::decode(uint32_t offX,
if (JPEG_HEADER_OK != jpeg_read_header(&dinfo, static_cast<boolean>(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<int>(mRaw->getCpp()))
ThrowRDE("Component count doesn't match");
Expand Down
203 changes: 154 additions & 49 deletions src/librawspeed/decompressors/LJpegDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/

#include "decompressors/LJpegDecoder.h"
#include "adt/Array1DRef.h"
#include "adt/Casts.h"
#include "adt/Invariant.h"
#include "adt/Point.h"
Expand All @@ -30,16 +31,140 @@
#include "io/Buffer.h"
#include "io/ByteStream.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <limits>
#include <vector>

using std::copy_n;

namespace rawspeed {

namespace {

using PerCompRecipeVec = std::vector<LJpegDecompressor::PerComponentRecipe>;

struct ScanSettings final {
RawImage raw;
iRectangle2D imgFrame;
iPoint2D jpegFrameDim;
iPoint2D maxRes;
LJpegDecompressor::DecodeSettings decode;
Array1DRef<const uint8_t> input;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
};

[[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<int>(numMCUsPerRestartInterval) / numMCUsPerRow;
}

[[nodiscard]] iPoint2D getMaxResolution(const RawImage& raw, iPoint2D maxDim,
int numComponents,
iPoint2D jpegFrameDim) {
if (implicit_cast<int64_t>(maxDim.x) * implicit_cast<int>(raw->getCpp()) >
std::numeric_limits<int>::max())
ThrowRDE("Maximal output tile is too large");

const auto maxRes =
iPoint2D(implicit_cast<int>(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<uint64_t>(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<int>(raw->getCpp());
const int outRowPixels = cpp * implicit_cast<int>(tileWidth);

for (int jpegRow = 0; jpegRow < tmpRaw->dim.y; ++jpegRow) {
for (int pack = 0; pack < widthPack; ++pack) {
const int tileRow = implicit_cast<int>(offY) + jpegRow * widthPack + pack;
if (tileRow >= raw->dim.y)
continue;

const int srcCol = pack * outRowPixels;
const int dstCol = cpp * implicit_cast<int>(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)
Expand Down Expand Up @@ -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<LJpegDecompressor::PerComponentRecipe> 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<int>(rec.size());
return {*hts[i], initPred[i]};
});

const iRectangle2D imgFrame = {
{static_cast<int>(offX), static_cast<int>(offY)},
{static_cast<int>(w), static_cast<int>(h)}};
const auto jpegFrameDim = iPoint2D(frame.w, frame.h);

if (implicit_cast<int64_t>(maxDim.x) * implicit_cast<int>(mRaw->getCpp()) >
std::numeric_limits<int>::max())
ThrowRDE("Maximal output tile is too large");

auto maxRes =
iPoint2D(implicit_cast<int>(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<uint64_t>(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<int>(offX), static_cast<int>(offY)},
{static_cast<int>(w), static_cast<int>(h)}},
jpegFrameDim,
maxRes,
{getNumLJpegRowsPerRestartInterval(
numMCUsPerRestartInterval, jpegFrameDim),
implicit_cast<int>(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
Loading
Loading