Skip to content

Commit 8e10cee

Browse files
committed
Add more meaningful error message for 12-bit JPG files
Some Blackmagic CinemaDNG files (Pocket Cinema Camera 4K files (3)/(4), Micro Cinema Camera (1)) use SOF1 (Extended Sequential DCT) at 12-bit precision but label tiles as TIFF compression=7 (lossless JPEG). The lossless JPEG decoder hit a DQT marker and threw "Not a valid RAW file." On libjpeg-turbo 2.1.5, the error is now "Unsupported JPEG data precision 12" (much clearer). Future work: on systems with libjpeg-turbo 3.0+, we can enable full 12-bit lossy JPEG decoding.
1 parent a167a44 commit 8e10cee

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

src/librawspeed/decompressors/AbstractDngDecompressor.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,64 @@
5151

5252
namespace rawspeed {
5353

54+
namespace {
55+
56+
// Some DNG files (e.g. Blackmagic CinemaDNG) use TIFF compression=7
57+
// (lossless JPEG) but the actual tile data contains lossy DCT JPEG
58+
// (SOF0/SOF1/SOF2 with DQT). Detect this by scanning the first few
59+
// JPEG markers in the tile stream.
60+
[[nodiscard]] bool tileContainsLossyJpeg(const ByteStream& bs) {
61+
const auto remaining = bs.getRemainSize();
62+
if (remaining < 4)
63+
return false;
64+
65+
// Must start with JPEG SOI marker
66+
if (bs.peekByte(0) != 0xFF || bs.peekByte(1) != 0xD8)
67+
return false;
68+
69+
// Scan markers after SOI. Stop after a reasonable number of bytes.
70+
const auto limit =
71+
std::min(remaining, static_cast<ByteStream::size_type>(1024));
72+
ByteStream::size_type pos = 2;
73+
74+
while (pos + 3 < limit) {
75+
if (bs.peekByte(pos) != 0xFF)
76+
return false; // Invalid marker - stop scanning
77+
78+
const uint8_t marker = bs.peekByte(pos + 1);
79+
80+
// DQT (quantization table) is definitive proof of lossy JPEG
81+
if (marker == 0xDB) // DQT
82+
return true;
83+
84+
// SOF0/SOF1/SOF2 = lossy DCT-based JPEG
85+
if (marker == 0xC0 || marker == 0xC1 || marker == 0xC2)
86+
return true;
87+
88+
// SOF3 = lossless - this is what compression=7 should be
89+
if (marker == 0xC3)
90+
return false;
91+
92+
// SOS = start of scan data - stop scanning
93+
if (marker == 0xDA)
94+
return false;
95+
96+
// Skip this marker segment
97+
if (pos + 4 > remaining)
98+
return false;
99+
const auto segLen = static_cast<uint16_t>(
100+
(static_cast<uint16_t>(bs.peekByte(pos + 2)) << 8) |
101+
static_cast<uint16_t>(bs.peekByte(pos + 3)));
102+
if (segLen < 2)
103+
return false;
104+
pos += 2 + segLen;
105+
}
106+
107+
return false;
108+
}
109+
110+
} // namespace
111+
54112
template <> void AbstractDngDecompressor::decompressThread<1>() const noexcept {
55113
#ifdef HAVE_OPENMP
56114
#pragma omp for schedule(static)
@@ -116,6 +174,15 @@ template <> void AbstractDngDecompressor::decompressThread<7>() const noexcept {
116174
for (const auto& e :
117175
Array1DRef(slices.data(), implicit_cast<int>(slices.size()))) {
118176
try {
177+
#ifdef HAVE_JPEG
178+
// Some cameras (e.g. Blackmagic CinemaDNG) mislabel lossy DCT JPEG
179+
// tiles as compression=7 (lossless JPEG). Detect and redirect.
180+
if (tileContainsLossyJpeg(e.bs)) {
181+
JpegDecompressor j(e.bs.peekBuffer(e.bs.getRemainSize()), mRaw);
182+
j.decode(e.offX, e.offY);
183+
continue;
184+
}
185+
#endif
119186
LJpegDecoder d(e.bs, mRaw);
120187
d.decode(e.offX, e.offY, e.width, e.height,
121188
iPoint2D(e.dsc.tileW, e.dsc.tileH), mFixLjpeg);

src/librawspeed/decompressors/JpegDecompressor.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ void JpegDecompressor::decode(uint32_t offX,
139139
if (JPEG_HEADER_OK != jpeg_read_header(&dinfo, static_cast<boolean>(true)))
140140
ThrowRDE("Unable to read JPEG header");
141141

142+
if (dinfo.data_precision != 8)
143+
ThrowRDE("Lossy JPEG tiles with %d-bit precision are not yet supported.",
144+
dinfo.data_precision);
145+
142146
jpeg_start_decompress(&dinfo);
143147
if (dinfo.output_components != static_cast<int>(mRaw->getCpp()))
144148
ThrowRDE("Component count doesn't match");

0 commit comments

Comments
 (0)