Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgexrpiz.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5// A FLOAT sample is two 16-bit words to PIZ.
6
7#include <QtQuick3DTextureHelpers/private/qssgexrcodecs_p.h>
8
9#include <QtCore/qendian.h>
10#include <QtCore/qnumeric.h>
11#include <QtCore/qvarlengtharray.h>
12
13#include <cstring>
14
15QT_BEGIN_NAMESPACE
16
17namespace QSSGExr {
18
19namespace {
20
21constexpr int HufEncSize = UShortRange + 1;
22constexpr int HufDecBits = 14;
23constexpr int HufDecSize = 1 << HufDecBits;
24constexpr int HufDecMask = HufDecSize - 1;
25
26constexpr int ShortZeroCodeRun = 59;
27constexpr int LongZeroCodeRun = 63;
28constexpr int ShortestLongRun = 2 + LongZeroCodeRun - ShortZeroCodeRun;
29constexpr int MaxCodeLength = ShortZeroCodeRun - 1;
30constexpr int MaxDecodableCodeLength = 57; // see BitReader::ensure()
31
32class BitReader
33{
34public:
35 BitReader(const char *begin, const char *end) : m_begin(begin), m_p(begin), m_end(end) { }
36
37 bool getBits(int n, quint32 *out)
38 {
39 Q_ASSERT(n > 0 && n <= 24);
40 if (!ensure(n))
41 return false;
42 *out = quint32(peek(n));
43 drop(n);
44 return true;
45 }
46
47 // Gated at 56 so that a refill cannot shift out unread bits.
48 bool ensure(int n)
49 {
50 while (m_lc < n && m_lc <= 56 && m_p < m_end) {
51 m_acc = (m_acc << 8) | quint8(*m_p++);
52 m_lc += 8;
53 }
54 return m_lc >= n;
55 }
56
57 bool refillOne()
58 {
59 if (m_p >= m_end)
60 return false;
61 m_acc = (m_acc << 8) | quint8(*m_p++);
62 m_lc += 8;
63 return true;
64 }
65
66 quint64 peek(int n) const
67 {
68 Q_ASSERT(n > 0 && n <= m_lc && m_lc <= 64);
69 return (m_acc >> (m_lc - n)) & ((quint64(1) << n) - 1);
70 }
71
72 void drop(int n) { m_lc -= n; }
73
74 void dropTrailing(int n)
75 {
76 m_acc >>= n;
77 m_lc -= n;
78 }
79
80 int bitCount() const { return m_lc; }
81 quint64 accumulator() const { return m_acc; }
82 bool atEnd() const { return m_p >= m_end; }
83 qsizetype bytesConsumed() const { return m_p - m_begin; }
84
85private:
86 const char *m_begin;
87 const char *m_p;
88 const char *m_end;
89 quint64 m_acc = 0;
90 int m_lc = 0;
91};
92
93quint16 buildReverseLut(const quint8 *bitmap, quint16 *lut)
94{
95 int k = 0;
96 // 0 is always present, the encoder clears its bit.
97 for (int i = 0; i < UShortRange; ++i) {
98 if (i == 0 || (bitmap[i >> 3] & (1 << (i & 7))))
99 lut[k++] = quint16(i);
100 }
101 const int n = k - 1;
102 while (k < UShortRange)
103 lut[k++] = 0;
104 return quint16(n);
105}
106
107void hufCanonicalCodeTable(qint64 *hcode)
108{
109 qint64 n[64] = {};
110 for (int i = 0; i < HufEncSize; ++i) {
111 Q_ASSERT(hcode[i] >= 0 && hcode[i] <= MaxCodeLength);
112 n[hcode[i]] += 1;
113 }
114
115 qint64 c = 0;
116 for (int i = MaxCodeLength; i > 0; --i) {
117 const qint64 nc = (c + n[i]) >> 1;
118 n[i] = c;
119 c = nc;
120 }
121
122 for (int i = 0; i < HufEncSize; ++i) {
123 const int l = int(hcode[i]);
124 if (l > 0)
125 hcode[i] = l | (n[l]++ << 6);
126 }
127}
128
129bool hufUnpackEncTable(BitReader &br, int im, int iM, qint64 *hcode, QString *error)
130{
131 memset(hcode, 0, sizeof(qint64) * HufEncSize);
132
133 for (int i = im; i <= iM; ++i) {
134 quint32 l = 0;
135 if (!br.getBits(6, &l)) {
136 *error = QStringLiteral("truncated PIZ code length table");
137 return false;
138 }
139
140 if (l == quint32(LongZeroCodeRun)) {
141 quint32 n = 0;
142 if (!br.getBits(8, &n)) {
143 *error = QStringLiteral("truncated PIZ code length table");
144 return false;
145 }
146 const int zerun = int(n) + ShortestLongRun;
147 if (i + zerun > iM + 1) {
148 *error = QStringLiteral("PIZ code length run overruns the symbol range");
149 return false;
150 }
151 for (int k = 0; k < zerun; ++k)
152 hcode[i + k] = 0;
153 i += zerun - 1;
154 } else if (l >= quint32(ShortZeroCodeRun)) {
155 const int zerun = int(l) - ShortZeroCodeRun + 2;
156 if (i + zerun > iM + 1) {
157 *error = QStringLiteral("PIZ code length run overruns the symbol range");
158 return false;
159 }
160 for (int k = 0; k < zerun; ++k)
161 hcode[i + k] = 0;
162 i += zerun - 1;
163 } else {
164 hcode[i] = l;
165 }
166 }
167
168 hufCanonicalCodeTable(hcode);
169 return true;
170}
171
172bool hufBuildDecTable(const qint64 *hcode, int im, int iM, PizScratch *scratch, QString *error)
173{
174 std::vector<HufDecEntry> &dec = scratch->dec;
175 std::vector<quint32> &longSymbols = scratch->longSymbols;
176 std::fill(dec.begin(), dec.end(), HufDecEntry {});
177
178 for (int i = im; i <= iM; ++i) {
179 const qint64 hc = hcode[i];
180 const int l = int(hc & 63);
181 if (l == 0)
182 continue;
183 if (l > MaxDecodableCodeLength) {
184 *error = QStringLiteral("invalid PIZ code length");
185 return false;
186 }
187 const quint64 code = quint64(hc >> 6);
188 if (code >> l) {
189 *error = QStringLiteral("invalid PIZ code table");
190 return false;
191 }
192
193 if (l > HufDecBits) {
194 const quint32 idx = quint32(code >> (l - HufDecBits));
195 Q_ASSERT(idx < quint32(HufDecSize));
196 if (dec[idx].len != 0) {
197 *error = QStringLiteral("conflicting PIZ codes");
198 return false;
199 }
200 ++dec[idx].longCount;
201 } else {
202 const quint32 base = quint32(code << (HufDecBits - l));
203 const quint32 count = 1u << (HufDecBits - l);
204 Q_ASSERT(quint64(base) + count <= quint64(HufDecSize));
205 for (quint32 j = 0; j < count; ++j) {
206 HufDecEntry &e = dec[base + j];
207 if (e.len != 0 || e.longCount != 0) {
208 *error = QStringLiteral("conflicting PIZ codes");
209 return false;
210 }
211 e.len = quint8(l);
212 e.lit = quint32(i);
213 }
214 }
215 }
216
217 quint32 total = 0;
218 for (HufDecEntry &e : dec) {
219 e.longOfs = total;
220 total += e.longCount;
221 }
222 longSymbols.assign(total, 0);
223
224 std::vector<quint32> &filled = scratch->longSymbolsFilled;
225 filled.assign(dec.size(), 0);
226 for (int i = im; i <= iM; ++i) {
227 const qint64 hc = hcode[i];
228 const int l = int(hc & 63);
229 if (l <= HufDecBits)
230 continue;
231 const quint64 code = quint64(hc >> 6);
232 const quint32 idx = quint32(code >> (l - HufDecBits));
233 longSymbols[dec[idx].longOfs + filled[idx]++] = quint32(i);
234 }
235
236 return true;
237}
238
239bool hufDecode(const qint64 *hcode,
240 const std::vector<HufDecEntry> &dec,
241 const std::vector<quint32> &longSymbols,
242 QByteArrayView bits,
243 qint64 nBits,
244 int rlc,
245 QSpan<quint16> out,
246 QString *error)
247{
248 const qsizetype byteCount = (nBits + 7) / 8;
249 Q_ASSERT(byteCount <= bits.size());
250 BitReader br(bits.data(), bits.data() + byteCount);
251
252 quint16 *o = out.data();
253 qsizetype produced = 0;
254
255 const auto emitSymbol = [&](quint32 sym) -> bool {
256 if (sym == quint32(rlc)) {
257 if (!br.ensure(8)) {
258 *error = QStringLiteral("truncated PIZ run length");
259 return false;
260 }
261 const quint32 count = quint32(br.peek(8));
262 br.drop(8);
263 if (produced == 0) {
264 *error = QStringLiteral("PIZ run with nothing to repeat");
265 return false;
266 }
267 if (count > quint32(out.size() - produced)) {
268 *error = QStringLiteral("PIZ run overruns the chunk");
269 return false;
270 }
271 const quint16 s = o[produced - 1];
272 for (quint32 k = 0; k < count; ++k)
273 o[produced++] = s;
274 return true;
275 }
276 if (produced >= out.size()) {
277 *error = QStringLiteral("PIZ stream overruns the chunk");
278 return false;
279 }
280 if (sym > 0xffff) {
281 *error = QStringLiteral("invalid PIZ symbol");
282 return false;
283 }
284 o[produced++] = quint16(sym);
285 return true;
286 };
287
288 while (!br.atEnd()) {
289 br.refillOne();
290
291 while (br.bitCount() >= HufDecBits) {
292 const HufDecEntry &e = dec[br.peek(HufDecBits) & HufDecMask];
293 if (e.len) {
294 br.drop(e.len);
295 if (!emitSymbol(e.lit))
296 return false;
297 } else {
298 if (e.longCount == 0) {
299 *error = QStringLiteral("invalid PIZ code");
300 return false;
301 }
302 bool found = false;
303 for (quint32 j = 0; j < e.longCount; ++j) {
304 const quint32 sym = longSymbols[e.longOfs + j];
305 const int l = int(hcode[sym] & 63);
306 if (!br.ensure(l))
307 continue;
308 if (quint64(hcode[sym] >> 6) == br.peek(l)) {
309 br.drop(l);
310 if (!emitSymbol(sym))
311 return false;
312 found = true;
313 break;
314 }
315 }
316 if (!found) {
317 *error = QStringLiteral("invalid PIZ code");
318 return false;
319 }
320 }
321 }
322 }
323
324 // The last code may run into the encoder's zero padding.
325 br.dropTrailing(int((8 - (nBits & 7)) & 7));
326 while (br.bitCount() > 0) {
327 const int lc = br.bitCount();
328 Q_ASSERT(lc < HufDecBits);
329 const HufDecEntry &e = dec[(br.accumulator() << (HufDecBits - lc)) & HufDecMask];
330 if (e.len == 0) {
331 *error = QStringLiteral("invalid PIZ code in the trailing bits");
332 return false;
333 }
334 br.drop(e.len);
335 if (!emitSymbol(e.lit))
336 return false;
337 }
338
339 if (produced != out.size()) {
340 *error = QStringLiteral("PIZ stream does not fill the chunk");
341 return false;
342 }
343 return true;
344}
345
346inline void wdec14(quint16 l, quint16 h, quint16 *a, quint16 *b)
347{
348 const int ls = qint16(l);
349 const int hi = qint16(h);
350 const int ai = ls + (hi & 1) + (hi >> 1);
351 *a = quint16(qint16(ai));
352 *b = quint16(qint16(ai - hi));
353}
354
355inline void wdec16(quint16 l, quint16 h, quint16 *a, quint16 *b)
356{
357 constexpr int ModMask = 0xffff;
358 constexpr int AOffset = 1 << 15;
359 const int m = l;
360 const int d = h;
361 const int bb = (m - (d >> 1)) & ModMask;
362 const int aa = (d + bb - AOffset) & ModMask;
363 *b = quint16(bb);
364 *a = quint16(aa);
365}
366
367} // namespace
368
374
375// Indices, since the reference's pointer arithmetic can point before the buffer.
376void wav2Decode(QSpan<quint16> base, qint64 offset, qint64 nx, qint64 ox, qint64 ny, qint64 oy, quint16 maxValue)
377{
378 quint16 *in = base.data();
379 const bool w14 = maxValue < (1 << 14);
380 const qint64 n = qMin(nx, ny);
381
382 qint64 p = 1;
383 while (p <= n)
384 p <<= 1;
385 p >>= 1;
386 qint64 p2 = p;
387 p >>= 1;
388
389 while (p >= 1) {
390 const qint64 oy1 = oy * p;
391 const qint64 oy2 = oy * p2;
392 const qint64 ox1 = ox * p;
393 const qint64 ox2 = ox * p2;
394
395 qint64 py = offset;
396 const qint64 ey = offset + oy * (ny - p2);
397 quint16 i00 = 0, i01 = 0, i10 = 0, i11 = 0;
398
399 for (; py <= ey; py += oy2) {
400 qint64 px = py;
401 const qint64 ex = py + ox * (nx - p2);
402 for (; px <= ex; px += ox2) {
403 const qint64 p01 = px + ox1;
404 const qint64 p10 = px + oy1;
405 const qint64 p11 = p10 + ox1;
406 Q_ASSERT(px >= 0 && p11 < base.size());
407 if (w14) {
408 wdec14(in[px], in[p10], &i00, &i10);
409 wdec14(in[p01], in[p11], &i01, &i11);
410 wdec14(i00, i01, &in[px], &in[p01]);
411 wdec14(i10, i11, &in[p10], &in[p11]);
412 } else {
413 wdec16(in[px], in[p10], &i00, &i10);
414 wdec16(in[p01], in[p11], &i01, &i11);
415 wdec16(i00, i01, &in[px], &in[p01]);
416 wdec16(i10, i11, &in[p10], &in[p11]);
417 }
418 }
419
420 if (nx & p) {
421 const qint64 p10 = px + oy1;
422 Q_ASSERT(px >= 0 && p10 < base.size());
423 if (w14)
424 wdec14(in[px], in[p10], &i00, &in[p10]);
425 else
426 wdec16(in[px], in[p10], &i00, &in[p10]);
427 in[px] = i00;
428 }
429 }
430
431 if (ny & p) {
432 qint64 px = py;
433 const qint64 ex = py + ox * (nx - p2);
434 for (; px <= ex; px += ox2) {
435 const qint64 p01 = px + ox1;
436 Q_ASSERT(px >= 0 && p01 < base.size());
437 if (w14)
438 wdec14(in[px], in[p01], &i00, &in[p01]);
439 else
440 wdec16(in[px], in[p01], &i00, &in[p01]);
441 in[px] = i00;
442 }
443 }
444
445 p2 = p;
446 p >>= 1;
447 }
448}
449
450bool hufUncompress(QByteArrayView in, QSpan<quint16> out, PizScratch *scratch, QString *error)
451{
452 if (in.size() < 20) {
453 *error = QStringLiteral("truncated PIZ Huffman header");
454 return false;
455 }
456
457 const quint32 im = qFromLittleEndian<quint32>(in.data());
458 const quint32 iM = qFromLittleEndian<quint32>(in.data() + 4);
459 // Bytes 8..11, the packed table length, are unused.
460 const quint32 nBits = qFromLittleEndian<quint32>(in.data() + 12);
461
462 if (im > quint32(HufEncSize - 1) || iM > quint32(HufEncSize - 1) || im > iM) {
463 *error = QStringLiteral("invalid PIZ symbol range");
464 return false;
465 }
466
467 BitReader tableReader(in.data() + 20, in.data() + in.size());
468 if (!hufUnpackEncTable(tableReader, int(im), int(iM), scratch->hcode.data(), error))
469 return false;
470
471 if (!hufBuildDecTable(scratch->hcode.data(), int(im), int(iM), scratch, error))
472 return false;
473
474 const qsizetype dataStart = 20 + tableReader.bytesConsumed();
475 const qsizetype avail = in.size() - dataStart;
476 if (avail < 0 || qint64(nBits) > qint64(avail) * 8) {
477 *error = QStringLiteral("PIZ bit stream extends past the chunk");
478 return false;
479 }
480
481 // The run length escape is iM, not a fixed constant.
482 return hufDecode(scratch->hcode.data(), scratch->dec, scratch->longSymbols, in.sliced(dataStart), qint64(nBits), int(iM), out, error);
483}
484
485bool pizDecode(QByteArrayView in, QSpan<char> out, const QList<PizChannel> &channels, qint64 nx, qint64 ny, qint64 bytesPerPixel, PizScratch *scratch, QString *error)
486{
487 if (out.size() % 2 != 0) {
488 *error = QStringLiteral("PIZ chunk size is not a whole number of 16-bit words");
489 return false;
490 }
491
492 qsizetype pos = 0;
493 if (in.size() < 4) {
494 *error = QStringLiteral("truncated PIZ chunk");
495 return false;
496 }
497 const quint16 minNonZero = qFromLittleEndian<quint16>(in.data());
498 const quint16 maxNonZero = qFromLittleEndian<quint16>(in.data() + 2);
499 pos = 4;
500
501 scratch->bitmap.fill(0);
502 if (minNonZero <= maxNonZero) {
503 if (maxNonZero >= BitmapSize) {
504 *error = QStringLiteral("PIZ bitmap range is out of bounds");
505 return false;
506 }
507 const qsizetype n = qsizetype(maxNonZero) - minNonZero + 1;
508 if (in.size() - pos < n) {
509 *error = QStringLiteral("truncated PIZ bitmap");
510 return false;
511 }
512 memcpy(scratch->bitmap.data() + minNonZero, in.data() + pos, size_t(n));
513 pos += n;
514 } else if (!(minNonZero == BitmapSize - 1 && maxNonZero == 0)) {
515 // Only the all-zero bitmap inverts the range.
516 *error = QStringLiteral("invalid PIZ bitmap range");
517 return false;
518 }
519
520 const quint16 maxValue = buildReverseLut(scratch->bitmap.data(), scratch->lut.data());
521
522 if (in.size() - pos < 4) {
523 *error = QStringLiteral("truncated PIZ chunk");
524 return false;
525 }
526 const qint64 huffLength = qint64(qFromLittleEndian<qint32>(in.data() + pos));
527 pos += 4;
528 if (huffLength < 0 || huffLength > in.size() - pos) {
529 *error = QStringLiteral("PIZ Huffman block extends past the chunk");
530 return false;
531 }
532
533 const qint64 totalWords = out.size() / 2;
534 QVarLengthArray<qint64, 8> wordOffset(channels.size() + 1);
535 wordOffset[0] = 0;
536 for (qsizetype c = 0; c < channels.size(); ++c) {
537 qint64 planeWords = 0;
538 if (qMulOverflow(nx, ny, &planeWords) || qMulOverflow(planeWords, channels[c].wordsPerSample, &planeWords)
539 || qAddOverflow(wordOffset[c], planeWords, &wordOffset[c + 1])) {
540 *error = QStringLiteral("PIZ channel layout is too large");
541 return false;
542 }
543 }
544 if (wordOffset[channels.size()] != totalWords) {
545 *error = QStringLiteral("PIZ channel layout does not match the chunk size");
546 return false;
547 }
548
549 scratch->planar.assign(size_t(totalWords), 0);
550 QSpan<quint16> planar(scratch->planar.data(), qsizetype(totalWords));
551
552 if (!hufUncompress(in.sliced(pos, huffLength), planar, scratch, error))
553 return false;
554
555 for (qsizetype c = 0; c < channels.size(); ++c) {
556 const qint64 words = channels[c].wordsPerSample;
557 for (qint64 j = 0; j < words; ++j)
558 wav2Decode(planar, wordOffset[c] + j, nx, words, ny, nx * words, maxValue);
559 }
560
561 // The chunk is little endian.
562 const quint16 *lut = scratch->lut.data();
563 for (qint64 i = 0; i < totalWords; ++i)
564 planar[i] = qToLittleEndian(lut[planar[i]]);
565
566 const qint64 rowStride = nx * bytesPerPixel;
567 for (qint64 r = 0; r < ny; ++r) {
568 for (qsizetype c = 0; c < channels.size(); ++c) {
569 const qint64 words = channels[c].wordsPerSample;
570 const qint64 runWords = nx * words;
571 char *dst = out.data() + r * rowStride + channels[c].byteOffsetInPixel * nx;
572 const quint16 *src = planar.data() + wordOffset[c] + r * runWords;
573 Q_ASSERT(dst + runWords * 2 <= out.data() + out.size());
574 memcpy(dst, src, size_t(runWords) * 2);
575 }
576 }
577
578 return true;
579}
580
581} // namespace QSSGExr
582
583QT_END_NAMESPACE
bool pizDecode(QByteArrayView in, QSpan< char > out, const QList< PizChannel > &channels, qint64 nx, qint64 ny, qint64 bytesPerPixel, PizScratch *scratch, QString *error)
void wav2Decode(QSpan< quint16 > base, qint64 offset, qint64 nx, qint64 ox, qint64 ny, qint64 oy, quint16 maxValue)
bool hufUncompress(QByteArrayView in, QSpan< quint16 > out, PizScratch *scratch, QString *error)