Skip to content

Commit cb0c24e

Browse files
committed
Add path for 12-bit JPG decoder
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). On systems with libjpeg-turbo 3.0+, the HAVE_JPEG_12BIT path will automatically enable full 12-bit lossy JPEG decoding.
1 parent a167a44 commit cb0c24e

5 files changed

Lines changed: 113 additions & 0 deletions

File tree

cmake/Modules/CheckJPEGSymbols.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ set(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES_SAVE};${JPEG_INCLUDE_DIRS
1313
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES_SAVE};${JPEG_LIBRARIES}")
1414

1515
CHECK_CXX_SYMBOL_EXISTS(jpeg_mem_src "cstdio;jpeglib.h" HAVE_JPEG_MEM_SRC)
16+
CHECK_CXX_SYMBOL_EXISTS(jpeg12_read_scanlines "cstdio;jpeglib.h" HAVE_JPEG_12BIT)
1617

1718
set(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES_SAVE}")
1819
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES_SAVE}")

src/config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ static_assert(RAWSPEED_LARGEPAGESIZE >= RAWSPEED_PAGESIZE,
6464

6565
#cmakedefine HAVE_JPEG
6666
#cmakedefine HAVE_JPEG_MEM_SRC
67+
#cmakedefine HAVE_JPEG_12BIT
6768

6869
#cmakedefine HAVE_CXX_THREAD_LOCAL
6970
#cmakedefine HAVE_GCC_THREAD_LOCAL

src/librawspeed/decompressors/AbstractDngDecompressor.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,63 @@
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 uint16_t segLen = (static_cast<uint16_t>(bs.peekByte(pos + 2)) << 8) |
100+
static_cast<uint16_t>(bs.peekByte(pos + 3));
101+
if (segLen < 2)
102+
return false;
103+
pos += 2 + segLen;
104+
}
105+
106+
return false;
107+
}
108+
109+
} // namespace
110+
54111
template <> void AbstractDngDecompressor::decompressThread<1>() const noexcept {
55112
#ifdef HAVE_OPENMP
56113
#pragma omp for schedule(static)
@@ -116,6 +173,15 @@ template <> void AbstractDngDecompressor::decompressThread<7>() const noexcept {
116173
for (const auto& e :
117174
Array1DRef(slices.data(), implicit_cast<int>(slices.size()))) {
118175
try {
176+
#ifdef HAVE_JPEG
177+
// Some cameras (e.g. Blackmagic CinemaDNG) mislabel lossy DCT JPEG
178+
// tiles as compression=7 (lossless JPEG). Detect and redirect.
179+
if (tileContainsLossyJpeg(e.bs)) {
180+
JpegDecompressor j(e.bs.peekBuffer(e.bs.getRemainSize()), mRaw);
181+
j.decode(e.offX, e.offY);
182+
continue;
183+
}
184+
#endif
119185
LJpegDecoder d(e.bs, mRaw);
120186
d.decode(e.offX, e.offY, e.width, e.height,
121187
iPoint2D(e.dsc.tileW, e.dsc.tileH), mFixLjpeg);

src/librawspeed/decompressors/JpegDecompressor.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ 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+
#ifdef HAVE_JPEG_12BIT
143+
if (dinfo.data_precision == 12) {
144+
decode12bit(dinfo, offX, offY);
145+
return;
146+
}
147+
#endif
148+
142149
jpeg_start_decompress(&dinfo);
143150
if (dinfo.output_components != static_cast<int>(mRaw->getCpp()))
144151
ThrowRDE("Component count doesn't match");
@@ -169,6 +176,40 @@ void JpegDecompressor::decode(uint32_t offX,
169176
}
170177
}
171178

179+
#ifdef HAVE_JPEG_12BIT
180+
void JpegDecompressor::decode12bit(JpegDecompressStruct& dinfo, uint32_t offX,
181+
uint32_t offY) {
182+
jpeg12_start_decompress(&dinfo);
183+
if (dinfo.output_components != static_cast<int>(mRaw->getCpp()))
184+
ThrowRDE("Component count doesn't match");
185+
const int row_stride = dinfo.output_width * dinfo.output_components;
186+
187+
std::vector<int16_t, AlignedAllocator<int16_t, 16>> complete_buffer;
188+
complete_buffer.resize(dinfo.output_height * row_stride);
189+
190+
const Array2DRef<int16_t> tmp(&complete_buffer[0],
191+
dinfo.output_components * dinfo.output_width,
192+
dinfo.output_height, row_stride);
193+
194+
while (dinfo.output_scanline < dinfo.output_height) {
195+
J12SAMPROW rowOut = &tmp(dinfo.output_scanline, 0);
196+
if (0 == jpeg12_read_scanlines(&dinfo, &rowOut, 1))
197+
ThrowRDE("JPEG Error while decompressing 12-bit image.");
198+
}
199+
jpeg12_finish_decompress(&dinfo);
200+
201+
const int copy_w = min(mRaw->dim.x - offX, dinfo.output_width);
202+
const int copy_h = min(mRaw->dim.y - offY, dinfo.output_height);
203+
204+
const Array2DRef<uint16_t> out(mRaw->getU16DataAsUncroppedArray2DRef());
205+
for (int row = 0; row < copy_h; row++) {
206+
for (int col = 0; col < dinfo.output_components * copy_w; col++)
207+
out(row + offY, (dinfo.output_components * offX) + col) =
208+
static_cast<uint16_t>(tmp(row, col));
209+
}
210+
}
211+
#endif
212+
172213
} // namespace rawspeed
173214

174215
#else

src/librawspeed/decompressors/JpegDecompressor.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class JpegDecompressor final : public AbstractDecompressor {
3838
Buffer input;
3939
RawImage mRaw;
4040

41+
#ifdef HAVE_JPEG_12BIT
42+
void decode12bit(JpegDecompressStruct& dinfo, uint32_t offX, uint32_t offY);
43+
#endif
44+
4145
public:
4246
JpegDecompressor(Buffer bs, RawImage img) : input(bs), mRaw(std::move(img)) {}
4347

0 commit comments

Comments
 (0)