Skip to content

Commit f9ad8a8

Browse files
authored
Merge pull request #859 from LebedevRI/panasonic-v8-code
Panasonic v8: not a prefix code
2 parents 0cd9d53 + a9c9266 commit f9ad8a8

File tree

4 files changed

+157
-123
lines changed

4 files changed

+157
-123
lines changed

fuzz/librawspeed/decompressors/PanasonicV8Decompressor.cpp

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
7777
uint32_t numStripLineOffsets = bs.getU32();
7878
uint32_t numStripWidths = bs.getU32();
7979
uint32_t numStripHeights = bs.getU32();
80-
uint32_t numHuffmanLUTEntries = bs.getU32();
80+
uint32_t numDefineCodesSize = bs.getU32();
8181

8282
auto stripSizes = bs.getStream(numStrips, sizeof(uint32_t));
8383
auto stripLineOffsetsInput =
8484
bs.getStream(numStripLineOffsets, sizeof(uint32_t));
8585
auto stripWidthsInput = bs.getStream(numStripWidths, sizeof(uint16_t));
8686
auto stripHeightsInput = bs.getStream(numStripHeights, sizeof(uint16_t));
87-
auto huffmanLUTEntriesInput =
88-
bs.getStream(numHuffmanLUTEntries, 2 * sizeof(uint8_t));
87+
auto defineCodes = bs.getStream(numDefineCodesSize);
8988
const auto initialPrediction = bs.getArray<uint16_t, 4>();
9089

9190
// The rest of the bs are the input strips.
@@ -118,21 +117,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
118117
PanasonicV8Decompressor::DecompressorParamsBuilder builder(
119118
imgDim, initialPrediction, getAsArray1DRef(strips),
120119
getAsArray1DRef(stripLineOffsets), getAsArray1DRef(stripWidths),
121-
getAsArray1DRef(stripHeights));
120+
getAsArray1DRef(stripHeights), defineCodes);
122121

123-
std::vector<PanasonicV8Decompressor::HuffmanLUTEntry> huffmanLUT;
124-
huffmanLUT.reserve(std::max(1U, numHuffmanLUTEntries));
125-
for (uint32_t entryIdx = 0; entryIdx < numHuffmanLUTEntries; ++entryIdx) {
126-
const auto bitcount = huffmanLUTEntriesInput.get<uint8_t>();
127-
const auto diffCat = huffmanLUTEntriesInput.get<uint8_t>();
128-
129-
huffmanLUT.emplace_back(PanasonicV8Decompressor::HuffmanLUTEntry{
130-
.bitcount = bitcount, .diffCat = diffCat});
131-
}
132-
invariant(huffmanLUTEntriesInput.getRemainSize() == 0);
133-
134-
PanasonicV8Decompressor v8(mRaw, builder.getDecompressorParams(),
135-
getAsArray1DRef(huffmanLUT));
122+
PanasonicV8Decompressor v8(mRaw, builder.getDecompressorParams());
136123
mRaw->createData();
137124
v8.decompress();
138125
MSan::CheckMemIsInitialized(mRaw->getByteDataAsUncroppedArray2DRef());

src/librawspeed/decoders/Rw2Decoder.cpp

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ struct DecompressorV8Params {
9797

9898
PanasonicV8Decompressor::Bayer2x2 initialPrediction;
9999

100-
/// Huffman decoding shift down value. Appears to be unused.
101-
std::vector<uint16_t> huffShiftDown;
100+
/// Decoding shift down value. Appears to be unused.
101+
std::vector<uint16_t> shiftDown;
102102

103103
uint16_t gammaClipVal;
104104

@@ -129,7 +129,7 @@ void DecompressorV8Params::validate() const {
129129
ThrowRDE("Strip bit length list does not have enough entries for the "
130130
"number of strips!");
131131

132-
if (std::any_of(huffShiftDown.begin(), huffShiftDown.end(),
132+
if (std::any_of(shiftDown.begin(), shiftDown.end(),
133133
[](uint16_t x) { return x != 0; })) {
134134
ThrowRDE("Non-zero shift down value encountered! Shift down decoding has "
135135
"never been tested!");
@@ -172,53 +172,14 @@ DecompressorV8Params::DecompressorV8Params(const TiffIFD& ifd) {
172172
initialPrediction[3] =
173173
ifd.getEntry(TiffTag::PANASONIC_V8_INIT_PRED_BLUE)->getU16();
174174

175-
getPanasonicTiffVector(ifd, TiffTag::PANASONIC_V8_HUF_SHIFT_DOWN,
176-
huffShiftDown);
175+
getPanasonicTiffVector(ifd, TiffTag::PANASONIC_V8_HUF_SHIFT_DOWN, shiftDown);
177176

178177
gammaClipVal = ifd.getEntry(TiffTag::PANASONIC_V8_CLIP_VAL)->getU16();
179178
// NOLINTEND(cppcoreguidelines-prefer-member-initializer)
180179

181180
validate();
182181
}
183182

184-
std::vector<PanasonicV8Decompressor::HuffmanLUTEntry>
185-
populateHuffmanLUT(const TiffIFD& ifd) {
186-
std::vector<PanasonicV8Decompressor::HuffmanLUTEntry> mHuffmanLUT;
187-
188-
ByteStream stream = ifd.getEntry(TiffTag::PANASONIC_V8_HUF_TABLE)->getData();
189-
190-
struct HuffEntry {
191-
uint16_t bitcount, symbol, mask;
192-
};
193-
std::vector<HuffEntry> huffTable(stream.getU16());
194-
195-
for (HuffEntry& entry : huffTable) {
196-
entry.bitcount = stream.getU16(); // Number of bits in symbol
197-
entry.symbol = uint16_t(stream.getU16() << (16U - entry.bitcount));
198-
entry.mask = uint16_t(
199-
0xffffU << (16U -
200-
entry.bitcount)); // mask of the bits overlapping symbol
201-
}
202-
203-
// Cache of Huffman table results for all possible 16-bit values.
204-
mHuffmanLUT.resize(1 + UINT16_MAX);
205-
206-
// Populates LUT by checking for a bitwise match between each value and the
207-
// prefix codes recorded in the table.
208-
for (unsigned li = 0; li < mHuffmanLUT.size(); ++li) {
209-
PanasonicV8Decompressor::HuffmanLUTEntry& lutVal = mHuffmanLUT[li];
210-
for (unsigned ti = 0; ti < huffTable.size(); ++ti) {
211-
if ((uint16_t(li) & huffTable[ti].mask) == huffTable[ti].symbol) {
212-
lutVal.bitcount = uint8_t(huffTable[ti].bitcount);
213-
lutVal.diffCat = uint8_t(ti);
214-
break;
215-
}
216-
}
217-
}
218-
219-
return mHuffmanLUT;
220-
}
221-
222183
/// Maybe the most complicated part of the entire file format, and seemingly,
223184
/// completely unused.
224185
void populateGammaLUT(const DecompressorV8Params& mParams, const TiffIFD& ifd) {
@@ -285,8 +246,6 @@ RawImage Rw2Decoder::decodeRawV8(const TiffIFD& raw) const {
285246
ThrowRDE("Unexpected CFA, only RGGB is supported");
286247

287248
const DecompressorV8Params mParams(raw);
288-
const std::vector<PanasonicV8Decompressor::HuffmanLUTEntry> mHuffmanLUT =
289-
populateHuffmanLUT(raw);
290249
populateGammaLUT(mParams, raw);
291250
const std::vector<Array1DRef<const uint8_t>> mStrips =
292251
getInputStrips(mParams, mFile);
@@ -297,10 +256,10 @@ RawImage Rw2Decoder::decodeRawV8(const TiffIFD& raw) const {
297256
imgDim, mParams.initialPrediction, getAsArray1DRef(mStrips),
298257
getAsArray1DRef(mParams.stripLineOffsets),
299258
getAsArray1DRef(mParams.stripWidths),
300-
getAsArray1DRef(mParams.stripHeights));
259+
getAsArray1DRef(mParams.stripHeights),
260+
raw.getEntry(TiffTag::PANASONIC_V8_HUF_TABLE)->getData());
301261

302-
PanasonicV8Decompressor v8(mRaw, b.getDecompressorParams(),
303-
getAsArray1DRef(mHuffmanLUT));
262+
PanasonicV8Decompressor v8(mRaw, b.getDecompressorParams());
304263
mRaw->createData();
305264
v8.decompress();
306265
return mRaw;

src/librawspeed/decompressors/PanasonicV8Decompressor.cpp

Lines changed: 116 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "adt/Array1DRef.h"
2626
#include "adt/Array1DRefExtras.h"
2727
#include "adt/Array2DRef.h"
28+
#include "adt/Bit.h"
29+
#include "adt/Casts.h"
2830
#include "adt/CroppedArray2DRef.h"
2931
#include "adt/Invariant.h"
3032
#include "adt/Point.h"
@@ -33,16 +35,22 @@
3335
#include "bitstreams/BitStreamer.h"
3436
#include "bitstreams/BitStreamerMSB.h" // IWYU pragma: keep
3537
#include "bitstreams/BitStreams.h"
38+
#include "codes/AbstractPrefixCode.h"
39+
#include "codes/AbstractPrefixCodeDecoder.h"
3640
#include "common/Common.h"
3741
#include "common/RawImage.h"
3842
#include "common/RawspeedException.h"
3943
#include "decoders/RawDecoderException.h"
44+
#include "io/ByteStream.h"
4045
#include "io/IOException.h"
4146
#include <algorithm>
4247
#include <array>
48+
#include <cassert>
49+
#include <climits>
4350
#include <cstddef>
4451
#include <cstdint>
4552
#include <limits>
53+
#include <numeric>
4654
#include <utility>
4755
#include <vector>
4856

@@ -128,15 +136,15 @@ class BitStreamerRevMSB final
128136
};
129137

130138
/// Utility class for Panasonic V8 entropy decoding
131-
class PanasonicV8Decompressor::InternalHuffDecoder {
139+
class PanasonicV8Decompressor::InternalDecoder {
132140
private:
133-
const Array1DRef<const HuffmanLUTEntry>
134-
mLUT; // Reference to PanasonicV8Decompressor::mHuffmanLUT
141+
// Reference to PanasonicV8Decompressor::mDecoderLUT
142+
const Array1DRef<const DecoderLUTEntry> mLUT;
135143
BitStreamerRevMSB mBitPump;
136144

137145
public:
138-
InternalHuffDecoder(const Array1DRef<const HuffmanLUTEntry>& LUT,
139-
Array1DRef<const uint8_t> bitStream)
146+
InternalDecoder(const Array1DRef<const DecoderLUTEntry>& LUT,
147+
Array1DRef<const uint8_t> bitStream)
140148
: mLUT(LUT), mBitPump(bitStream) {}
141149

142150
int32_t decodeNextDiffValue();
@@ -194,8 +202,83 @@ void isValidImageGrid(iRectangle2D imgDim,
194202
ThrowRDE("Tiles do not cover whole output image");
195203
}
196204

205+
int minBitsPerPixelNeeded(
206+
Array1DRef<const PanasonicV8Decompressor::DecoderLUTEntry> mDecoderLUT) {
207+
invariant(mDecoderLUT.size() > 0);
208+
const auto r = std::accumulate(
209+
mDecoderLUT.begin(), mDecoderLUT.end(), std::numeric_limits<int>::max(),
210+
[](int init, const PanasonicV8Decompressor::DecoderLUTEntry& e) {
211+
if (e.isSentinel())
212+
return init;
213+
invariant(e.bitcount > 0);
214+
const auto total = e.bitcount + e.diffCat;
215+
invariant(total > 0);
216+
return std::min(init, total);
217+
});
218+
invariant(r > 0);
219+
invariant(r <= (16 + 17));
220+
return r;
221+
}
222+
197223
} // namespace
198224

225+
std::vector<PanasonicV8Decompressor::DecoderLUTEntry>
226+
PanasonicV8Decompressor::DecompressorParamsBuilder::getDecoderLUT(
227+
ByteStream stream) {
228+
std::vector<PanasonicV8Decompressor::DecoderLUTEntry> mDecoderLUT;
229+
230+
const auto numSymbols = stream.getU16();
231+
if (numSymbols < 1 || numSymbols > 17)
232+
ThrowRDE("Unexpected number of symbols: %u", numSymbols);
233+
234+
struct Entry {
235+
uint8_t bitcount;
236+
uint16_t symbol, mask;
237+
uint8_t codeValue;
238+
};
239+
std::vector<Entry> table;
240+
table.reserve(numSymbols);
241+
242+
for (unsigned symbolIndex = 0; symbolIndex != numSymbols; ++symbolIndex) {
243+
const auto len = stream.getU16(); // Number of bits in symbol
244+
if (len < 1 || len > 16)
245+
ThrowRDE("Unexpected symbol length");
246+
const auto code = stream.getU16();
247+
if (!isIntN<uint32_t>(code, len))
248+
ThrowRDE("Bad symbol code");
249+
Entry entry;
250+
entry.bitcount = implicit_cast<uint8_t>(len);
251+
entry.symbol = uint16_t(code << (16U - entry.bitcount));
252+
entry.codeValue = implicit_cast<uint8_t>(symbolIndex);
253+
entry.mask = uint16_t(
254+
0xffffU << (16U -
255+
entry.bitcount)); // mask of the bits overlapping symbol
256+
if (entry.bitcount == PanasonicV8Decompressor::DecoderLUTEntry().bitcount &&
257+
entry.codeValue == PanasonicV8Decompressor::DecoderLUTEntry().diffCat)
258+
ThrowRDE("Sentinel symbol encountered");
259+
table.emplace_back(entry);
260+
}
261+
assert(table.size() == numSymbols);
262+
263+
// Cache of decoding results for all possible 16-bit values.
264+
mDecoderLUT.resize(1 + UINT16_MAX);
265+
266+
// Populates LUT by checking for a bitwise match between each value and the
267+
// codes recorded in the table.
268+
for (unsigned li = 0; li < mDecoderLUT.size(); ++li) {
269+
PanasonicV8Decompressor::DecoderLUTEntry& lutVal = mDecoderLUT[li];
270+
for (const auto& ti : table) {
271+
if ((uint16_t(li) & ti.mask) == ti.symbol) {
272+
lutVal.bitcount = ti.bitcount;
273+
lutVal.diffCat = ti.codeValue;
274+
break; // NOTE: not a prefix code!
275+
}
276+
}
277+
}
278+
279+
return mDecoderLUT;
280+
}
281+
199282
std::vector<iRectangle2D>
200283
PanasonicV8Decompressor::DecompressorParamsBuilder::getOutRects(
201284
iRectangle2D imgDim, Array1DRef<const uint32_t> stripLineOffsets,
@@ -234,18 +317,24 @@ PanasonicV8Decompressor::DecompressorParamsBuilder::getOutRects(
234317

235318
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
236319

237-
PanasonicV8Decompressor::PanasonicV8Decompressor(
238-
RawImage outputImg, DecompressorParams mParams_,
239-
Array1DRef<const HuffmanLUTEntry> mHuffmanLUT_)
240-
: mRawOutput(std::move(outputImg)), mParams(std::move(mParams_)),
241-
mHuffmanLUT(mHuffmanLUT_) {
320+
PanasonicV8Decompressor::PanasonicV8Decompressor(RawImage outputImg,
321+
DecompressorParams mParams_)
322+
: mRawOutput(std::move(outputImg)), mParams(std::move(mParams_)) {
242323
if (mRawOutput->getCpp() != 1 ||
243324
mRawOutput->getDataType() != RawImageType::UINT16 ||
244325
mRawOutput->getBpp() != sizeof(uint16_t)) {
245326
ThrowRDE("Unexpected component count / data type");
246327
}
247328
if (!mRawOutput->dim.hasPositiveArea())
248329
ThrowRDE("Unexpected image dimensions");
330+
const auto minBpp = minBitsPerPixelNeeded(mParams.mDecoderLUT);
331+
for (int stripIdx = 0; stripIdx < mParams.mStrips.size(); ++stripIdx) {
332+
const auto strip = mParams.mStrips(stripIdx);
333+
const auto maxPixelsInStrip = (uint64_t{CHAR_BIT} * strip.size()) / minBpp;
334+
const auto outRect = mParams.mOutRect(stripIdx);
335+
if (outRect.dim.area() > maxPixelsInStrip)
336+
ThrowRDE("Input strip is unsufficient to produce requested tile");
337+
}
249338
}
250339

251340
void PanasonicV8Decompressor::decompress() const {
@@ -270,7 +359,7 @@ void PanasonicV8Decompressor::decompress() const {
270359
/*croppedHeight=*/outRect.dim.y)
271360
.getAsArray2DRef();
272361

273-
InternalHuffDecoder decoder(mHuffmanLUT, strip);
362+
InternalDecoder decoder(mParams.mDecoderLUT, strip);
274363

275364
decompressStrip(out, decoder);
276365
} catch (const RawspeedException& err) {
@@ -283,8 +372,8 @@ void PanasonicV8Decompressor::decompress() const {
283372
}
284373
}
285374

286-
void PanasonicV8Decompressor::decompressStrip(
287-
const Array2DRef<uint16_t> out, InternalHuffDecoder decoder) const {
375+
void PanasonicV8Decompressor::decompressStrip(const Array2DRef<uint16_t> out,
376+
InternalDecoder decoder) const {
288377
Bayer2x2 predictedStorage = mParams.initialPrediction;
289378
const auto pred = Array2DRef(predictedStorage.data(), 2, 2);
290379

@@ -335,35 +424,23 @@ void PanasonicV8Decompressor::decompressStrip(
335424
}
336425
}
337426

338-
int32_t inline PanasonicV8Decompressor::InternalHuffDecoder::
339-
decodeNextDiffValue() {
427+
int32_t inline PanasonicV8Decompressor::InternalDecoder::decodeNextDiffValue() {
340428
// Retrieve the difference category, which indicates magnitude of the
341429
// difference between the predicted and actual value.
342430
const auto next16 = uint16_t(mBitPump.peekBits(16));
343-
const auto& [bits, diffCat] = mLUT(next16);
344-
if (diffCat == 0 && bits == 7)
345-
ThrowRDE("Huffman decoding encountered an invalid value!");
346-
mBitPump.skipBits(bits); // Skip the bits that encoded the difference category
347-
348-
if (diffCat > 0) {
349-
// Decode difference value. The scheme here encodes signed integers in a
350-
// manner similar to offset binary encoding. Here, the encoding is biased by
351-
// the difference category such that abs(diff) is in the range
352-
// [2^{diffCat-1}, 2^{diffCat}).
353-
const uint32_t rawDiffBits = mBitPump.getBits(diffCat);
354-
const uint32_t sign = rawDiffBits >> (diffCat - 1);
355-
const uint32_t val = rawDiffBits << 0;
356-
357-
// In comments below, n = diffCat
358-
if (sign == 1)
359-
// Positive value in range [2^{n-1}, 2^{n})
360-
return val;
361-
// Negative value in interval (-2^{n}, -2^{n-1}]
362-
return static_cast<int32_t>(val) + static_cast<int32_t>(~0U << diffCat) + 1;
363-
}
364-
// diffBitCount of zero indicates no difference (next pixel is same as
365-
// predicted)
366-
return 0;
431+
invariant(mLUT.size() == 1 + UINT16_MAX);
432+
const auto& [codeLen, codeValue] = mLUT(next16);
433+
if (codeValue == 0 && codeLen == 7)
434+
ThrowRDE("Decoding encountered an invalid value!");
435+
mBitPump.skipBits(
436+
codeLen); // Skip the bits that encoded the difference category
437+
int diffLen = codeValue;
438+
439+
if (diffLen == 0)
440+
return 0;
441+
442+
const uint32_t diff = mBitPump.getBits(diffLen);
443+
return AbstractPrefixCodeDecoder<BaselineCodeTag>::extend(diff, diffLen);
367444
}
368445

369446
} // namespace rawspeed

0 commit comments

Comments
 (0)