Skip to content

Commit 5efdb0e

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 5efdb0e

5 files changed

Lines changed: 115 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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "io/ByteStream.h"
3636
#include "io/Endianness.h"
3737
#include "io/IOException.h"
38+
#include <algorithm>
3839
#include <cstdint>
3940
#include <limits>
4041
#include <string>
@@ -51,6 +52,64 @@
5152

5253
namespace rawspeed {
5354

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