Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kimageformats/src/imageformats/chunks.cpp
Line
Count
Source
1
/*
2
    This file is part of the KDE project
3
    SPDX-FileCopyrightText: 2025-2026 Mirco Miranda <mircomir@outlook.com>
4
5
    SPDX-License-Identifier: LGPL-2.0-or-later
6
*/
7
8
#include "chunks_p.h"
9
#include "packbits_p.h"
10
#include "util_p.h"
11
12
#include <QBuffer>
13
#include <QColor>
14
#include <QDataStream>
15
16
#ifdef QT_DEBUG
17
Q_LOGGING_CATEGORY(LOG_IFFPLUGIN, "kf.imageformats.plugins.iff", QtDebugMsg)
18
#else
19
Q_LOGGING_CATEGORY(LOG_IFFPLUGIN, "kf.imageformats.plugins.iff", QtWarningMsg)
20
#endif
21
22
1.69M
#define RECURSION_PROTECTION 10
23
24
76.1k
#define BITPLANES_HAM_MAX 8
25
422k
#define BITPLANES_HAM_MIN 5
26
298k
#define BITPLANES_HALFBRIDE_MAX 8
27
686k
#define BITPLANES_HALFBRIDE_MIN 1
28
29
static QString dataToString(const IFFChunk *chunk)
30
2.58k
{
31
2.58k
    if (chunk == nullptr || !chunk->isValid()) {
32
0
        return {};
33
0
    }
34
2.58k
    auto dt = chunk->data();
35
47.8k
    for (; dt.endsWith(char()); dt = dt.removeLast());
36
2.58k
    return QString::fromUtf8(dt).trimmed();
37
2.58k
}
38
39
IFFChunk::~IFFChunk()
40
1.22M
{
41
42
1.22M
}
43
44
IFFChunk::IFFChunk()
45
1.21M
    : _chunkId{0}
46
1.21M
    , _size{0}
47
1.21M
    , _align{2}
48
1.21M
    , _dataPos{0}
49
1.21M
    , _recursionCnt{0}
50
1.21M
{
51
1.21M
}
52
53
bool IFFChunk::operator ==(const IFFChunk &other) const
54
0
{
55
0
    if (chunkId() != other.chunkId()) {
56
0
        return false;
57
0
    }
58
0
    return _size == other._size && _dataPos == other._dataPos;
59
0
}
60
61
bool IFFChunk::isValid() const
62
1.21M
{
63
1.21M
    auto cid = chunkId();
64
1.21M
    if (cid.isEmpty()) {
65
0
        return false;
66
0
    }
67
    // A “type ID”, “property name”, “FORM type”, or any other IFF
68
    // identifier is a 32-bit value: the concatenation of four ASCII
69
    // characters in the range “ ” (SP, hex 20) through “~” (hex 7E).
70
    // Spaces (hex 20) should not precede printing characters;
71
    // trailing spaces are OK. Control characters are forbidden.
72
1.21M
    if (cid.at(0) == ' ') {
73
4
        return false;
74
4
    }
75
4.87M
    for (auto &&c : cid) {
76
4.87M
        if (c < ' ' || c > '~')
77
233
            return false;
78
4.87M
    }
79
1.21M
    return true;
80
1.21M
}
81
82
qint32 IFFChunk::alignBytes() const
83
2.91M
{
84
2.91M
    return _align;
85
2.91M
}
86
87
bool IFFChunk::readStructure(QIODevice *d)
88
1.21M
{
89
1.21M
    auto ok = readInfo(d);
90
1.21M
    if (recursionCounter() > RECURSION_PROTECTION - 1) {
91
416k
        ok = ok && IFFChunk::innerReadStructure(d); // force default implementation (no more recursion)
92
802k
    } else {
93
802k
        ok = ok && innerReadStructure(d);
94
802k
    }
95
1.21M
    if (ok) {
96
1.21M
        ok = d->seek(nextChunkPos());
97
1.21M
    }
98
1.21M
    return ok;
99
1.21M
}
100
101
QByteArray IFFChunk::chunkId() const
102
60.4M
{
103
60.4M
    return QByteArray(_chunkId, 4);
104
60.4M
}
105
106
quint32 IFFChunk::bytes() const
107
13.0M
{
108
13.0M
    return _size;
109
13.0M
}
110
111
const QByteArray &IFFChunk::data() const
112
13.1M
{
113
13.1M
    return _data;
114
13.1M
}
115
116
const IFFChunk::ChunkList &IFFChunk::chunks() const
117
40.7M
{
118
40.7M
    return _chunks;
119
40.7M
}
120
121
quint8 IFFChunk::chunkVersion(const QByteArray &cid)
122
0
{
123
0
    if (cid.size() != 4) {
124
0
        return 0;
125
0
    }
126
0
    if (cid.at(3) >= char('2') && cid.at(3) <= char('9')) {
127
0
        return quint8(cid.at(3) - char('0'));
128
0
    }
129
0
    return 1;
130
0
}
131
132
bool IFFChunk::isChunkType(const QByteArray &cid) const
133
6.09M
{
134
6.09M
    if (chunkId() == cid) {
135
0
        return true;
136
0
    }
137
6.09M
    if (chunkId().startsWith(cid.left(3)) && IFFChunk::chunkVersion(cid) > 1) {
138
0
        return true;
139
0
    }
140
6.09M
    return false;
141
6.09M
}
142
143
bool IFFChunk::readInfo(QIODevice *d)
144
1.21M
{
145
1.21M
    if (d == nullptr || d->read(_chunkId, 4) != 4) {
146
269
        return false;
147
269
    }
148
1.21M
    if (!IFFChunk::isValid()) {
149
237
        return false;
150
237
    }
151
1.21M
    auto sz = d->read(4);
152
1.21M
    if (sz.size() != 4) {
153
298
        return false;
154
298
    }
155
1.21M
    _size = ui32(sz.at(3), sz.at(2), sz.at(1), sz.at(0));
156
1.21M
    _dataPos = d->pos();
157
1.21M
    return true;
158
1.21M
}
159
160
QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const
161
220k
{
162
220k
    if (!seek(d, relPos)) {
163
0
        return{};
164
0
    }
165
220k
    if (size == -1) {
166
220k
        size = _size;
167
220k
    }
168
220k
    auto read = std::min(size, _size - relPos);
169
220k
    return d->read(read);
170
220k
}
171
172
bool IFFChunk::seek(QIODevice *d, qint64 relPos) const
173
342k
{
174
342k
    if (d == nullptr) {
175
0
        return false;
176
0
    }
177
342k
    return d->seek(_dataPos + relPos);
178
342k
}
179
180
bool IFFChunk::innerReadStructure(QIODevice *)
181
481k
{
182
481k
    return true;
183
481k
}
184
185
void IFFChunk::setAlignBytes(qint32 bytes)
186
1.23M
{
187
1.23M
    _align = bytes;
188
1.23M
}
189
190
qint64 IFFChunk::nextChunkPos() const
191
2.24M
{
192
2.24M
    auto pos = _dataPos + _size;
193
2.24M
    if (auto align = pos % alignBytes())
194
602k
        pos += alignBytes() - align;
195
2.24M
    return pos;
196
2.24M
}
197
198
IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const QSharedPointer<IFFChunk> &chunk)
199
0
{
200
0
    return search(cid, ChunkList() << chunk);
201
0
}
202
203
IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const ChunkList &chunks)
204
2.43M
{
205
2.43M
    IFFChunk::ChunkList list;
206
2.43M
    for (auto &&chunk : chunks) {
207
2.40M
        if (chunk->chunkId() == cid)
208
994k
            list << chunk;
209
2.40M
        list << IFFChunk::search(cid, chunk->_chunks);
210
2.40M
    }
211
2.43M
    return list;
212
2.43M
}
213
214
bool IFFChunk::cacheData(QIODevice *d)
215
220k
{
216
220k
    if (bytes() > 8 * 1024 * 1024) {
217
77
        return false;
218
77
    }
219
220k
    _data = readRawData(d);
220
220k
    return _data.size() == _size;
221
220k
}
222
223
void IFFChunk::setChunks(const ChunkList &chunks)
224
453k
{
225
453k
    _chunks = chunks;
226
453k
}
227
228
qint32 IFFChunk::recursionCounter() const
229
1.67M
{
230
1.67M
    return _recursionCnt;
231
1.67M
}
232
233
void IFFChunk::setRecursionCounter(qint32 cnt)
234
1.21M
{
235
1.21M
    _recursionCnt = cnt;
236
1.21M
}
237
238
quint32 IFFChunk::dataBytes() const
239
5.82M
{
240
5.82M
    return std::min(bytes(), quint32(data().size()));
241
5.82M
}
242
243
IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent)
244
474k
{
245
474k
    auto tmp = false;
246
474k
    if (ok == nullptr) {
247
0
        ok = &tmp;
248
0
    }
249
474k
    *ok = false;
250
251
474k
    if (d == nullptr) {
252
0
        return {};
253
0
    }
254
255
474k
    auto alignBytes = qint32(2);
256
474k
    auto recursionCnt = qint32();
257
474k
    auto nextChunkPos = qint64();
258
474k
    if (parent) {
259
453k
        alignBytes = parent->alignBytes();
260
453k
        recursionCnt = parent->recursionCounter();
261
453k
        nextChunkPos = parent->nextChunkPos();
262
453k
    }
263
264
474k
    if (recursionCnt > RECURSION_PROTECTION) {
265
0
        return {};
266
0
    }
267
268
474k
    IFFChunk::ChunkList list;
269
1.68M
    for (; !d->atEnd() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) {
270
1.21M
        auto cid = d->peek(4);
271
1.21M
        QSharedPointer<IFFChunk> chunk;
272
1.21M
        if (cid == ABIT_CHUNK) {
273
7.99k
            chunk = QSharedPointer<IFFChunk>(new ABITChunk());
274
1.21M
        } else if (cid == ANNO_CHUNK) {
275
17.5k
            chunk = QSharedPointer<IFFChunk>(new ANNOChunk());
276
1.19M
        } else if (cid == AUTH_CHUNK) {
277
15.6k
            chunk = QSharedPointer<IFFChunk>(new AUTHChunk());
278
1.17M
        } else if (cid == BEAM_CHUNK) {
279
8.97k
            chunk = QSharedPointer<IFFChunk>(new BEAMChunk());
280
1.16M
        } else if (cid == BMHD_CHUNK) {
281
75.5k
            chunk = QSharedPointer<IFFChunk>(new BMHDChunk());
282
1.09M
        } else if (cid == BODY_CHUNK) {
283
9.67k
            chunk = QSharedPointer<IFFChunk>(new BODYChunk());
284
1.08M
        } else if (cid == CAMG_CHUNK) {
285
24.0k
            chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
286
1.05M
        } else if (cid == CAT__CHUNK) {
287
2.01k
            chunk = QSharedPointer<IFFChunk>(new CATChunk());
288
1.05M
        } else if (cid == CMAP_CHUNK) {
289
7.13k
            chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
290
1.05M
        } else if (cid == CMYK_CHUNK) {
291
7.15k
            chunk = QSharedPointer<IFFChunk>(new CMYKChunk());
292
1.04M
        } else if (cid == COPY_CHUNK) {
293
11.0k
            chunk = QSharedPointer<IFFChunk>(new COPYChunk());
294
1.03M
        } else if (cid == CTBL_CHUNK) {
295
10.4k
            chunk = QSharedPointer<IFFChunk>(new CTBLChunk());
296
1.02M
        } else if (cid == DATE_CHUNK) {
297
6.37k
            chunk = QSharedPointer<IFFChunk>(new DATEChunk());
298
1.01M
        } else if (cid == DPI__CHUNK) {
299
8.90k
            chunk = QSharedPointer<IFFChunk>(new DPIChunk());
300
1.00M
        } else if (cid == EXIF_CHUNK) {
301
24.7k
            chunk = QSharedPointer<IFFChunk>(new EXIFChunk());
302
981k
        } else if (cid == FOR4_CHUNK) {
303
6.89k
            chunk = QSharedPointer<IFFChunk>(new FOR4Chunk());
304
974k
        } else if (cid == FORM_CHUNK) {
305
744k
            chunk = QSharedPointer<IFFChunk>(new FORMChunk());
306
744k
        } else if (cid == FVER_CHUNK) {
307
706
            chunk = QSharedPointer<IFFChunk>(new FVERChunk());
308
229k
        } else if (cid == HIST_CHUNK) {
309
968
            chunk = QSharedPointer<IFFChunk>(new HISTChunk());
310
228k
        } else if (cid == ICCN_CHUNK) {
311
2.25k
            chunk = QSharedPointer<IFFChunk>(new ICCNChunk());
312
226k
        } else if (cid == ICCP_CHUNK) {
313
11.3k
            chunk = QSharedPointer<IFFChunk>(new ICCPChunk());
314
214k
        } else if (cid == IDAT_CHUNK) {
315
2.17k
            chunk = QSharedPointer<IFFChunk>(new IDATChunk());
316
212k
        } else if (cid == IHDR_CHUNK) {
317
10.0k
            chunk = QSharedPointer<IFFChunk>(new IHDRChunk());
318
202k
        } else if (cid == IPAR_CHUNK) {
319
1.26k
            chunk = QSharedPointer<IFFChunk>(new IPARChunk());
320
201k
        } else if (cid == NAME_CHUNK) {
321
19.1k
            chunk = QSharedPointer<IFFChunk>(new NAMEChunk());
322
182k
        } else if (cid == PCHG_CHUNK) {
323
9.30k
            chunk = QSharedPointer<IFFChunk>(new PCHGChunk());
324
172k
        } else if (cid == PLTE_CHUNK) {
325
1.30k
            chunk = QSharedPointer<IFFChunk>(new PLTEChunk());
326
171k
        } else if (cid == RAST_CHUNK) {
327
20.4k
            chunk = QSharedPointer<IFFChunk>(new RASTChunk());
328
151k
        } else if (cid == RBOD_CHUNK) {
329
2.24k
            chunk = QSharedPointer<IFFChunk>(new RBODChunk());
330
148k
        } else if (cid == RCOL_CHUNK) {
331
617
            chunk = QSharedPointer<IFFChunk>(new RCOLChunk());
332
148k
        } else if (cid == RFLG_CHUNK) {
333
823
            chunk = QSharedPointer<IFFChunk>(new RFLGChunk());
334
147k
        } else if (cid == RGBA_CHUNK) {
335
3.20k
            chunk = QSharedPointer<IFFChunk>(new RGBAChunk());
336
144k
        } else if (cid == RGHD_CHUNK) {
337
5.76k
            chunk = QSharedPointer<IFFChunk>(new RGHDChunk());
338
138k
        } else if (cid == RSCM_CHUNK) {
339
1.55k
            chunk = QSharedPointer<IFFChunk>(new RSCMChunk());
340
136k
        } else if (cid == SHAM_CHUNK) {
341
7.08k
            chunk = QSharedPointer<IFFChunk>(new SHAMChunk());
342
129k
        } else if (cid == TBHD_CHUNK) {
343
2.31k
            chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
344
127k
        } else if (cid == VDAT_CHUNK) {
345
1.16k
            chunk = QSharedPointer<IFFChunk>(new VDATChunk());
346
126k
        } else if (cid == VERS_CHUNK) {
347
9.69k
            chunk = QSharedPointer<IFFChunk>(new VERSChunk());
348
116k
        } else if (cid == XBMI_CHUNK) {
349
7.15k
            chunk = QSharedPointer<IFFChunk>(new XBMIChunk());
350
109k
        } else if (cid == XMP0_CHUNK) {
351
12.9k
            chunk = QSharedPointer<IFFChunk>(new XMP0Chunk());
352
96.6k
        } else if (cid == YUVS_CHUNK) {
353
2.58k
            chunk = QSharedPointer<IFFChunk>(new YUVSChunk());
354
94.0k
        } else { // unknown chunk
355
94.0k
            chunk = QSharedPointer<IFFChunk>(new IFFChunk());
356
94.0k
            qCDebug(LOG_IFFPLUGIN) << "IFFChunk::innerFromDevice(): unknown chunk" << cid;
357
94.0k
        }
358
359
        // change the alignment to the one of main chunk (required for unknown Maya IFF chunks)
360
1.21M
        if (chunk->isChunkType(CAT__CHUNK)
361
1.21M
            || chunk->isChunkType(FILL_CHUNK)
362
1.21M
            || chunk->isChunkType(FORM_CHUNK)
363
1.21M
            || chunk->isChunkType(LIST_CHUNK)
364
1.21M
            || chunk->isChunkType(PROP_CHUNK)) {
365
0
            alignBytes = chunk->alignBytes();
366
1.21M
        } else {
367
1.21M
            chunk->setAlignBytes(alignBytes);
368
1.21M
        }
369
370
1.21M
        chunk->setRecursionCounter(recursionCnt + 1);
371
1.21M
        if (!chunk->readStructure(d)) {
372
3.81k
            *ok = false;
373
3.81k
            return {};
374
3.81k
        }
375
376
        // skip any non-IFF data at the end of the file.
377
        // NOTE: there should be no more chunks after the first (root)
378
1.21M
        if (nextChunkPos == 0) {
379
18.9k
            nextChunkPos = chunk->nextChunkPos();
380
18.9k
        }
381
382
1.21M
        list << chunk;
383
1.21M
    }
384
385
470k
    *ok = true;
386
470k
    return list;
387
474k
}
388
389
IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok)
390
20.7k
{
391
20.7k
    return innerFromDevice(d, ok, nullptr);
392
20.7k
}
393
394
395
/* ******************
396
 * *** BMHD Chunk ***
397
 * ****************** */
398
399
BMHDChunk::~BMHDChunk()
400
{
401
402
}
403
404
75.5k
BMHDChunk::BMHDChunk() : IFFChunk()
405
75.5k
{
406
75.5k
}
407
408
bool BMHDChunk::isValid() const
409
4.44M
{
410
4.44M
    if (dataBytes() < 20) {
411
629k
        return false;
412
629k
    }
413
3.81M
    return chunkId() == BMHDChunk::defaultChunkId();
414
4.44M
}
415
416
bool BMHDChunk::innerReadStructure(QIODevice *d)
417
55.5k
{
418
55.5k
    return cacheData(d);
419
55.5k
}
420
421
qint32 BMHDChunk::width() const
422
1.28M
{
423
1.28M
    if (!isValid()) {
424
13
        return 0;
425
13
    }
426
1.28M
    return qint32(ui16(data().at(1), data().at(0)));
427
1.28M
}
428
429
qint32 BMHDChunk::height() const
430
111k
{
431
111k
    if (!isValid()) {
432
13
        return 0;
433
13
    }
434
111k
    return qint32(ui16(data().at(3), data().at(2)));
435
111k
}
436
437
QSize BMHDChunk::size() const
438
7.74k
{
439
7.74k
    return QSize(width(), height());
440
7.74k
}
441
442
qint32 BMHDChunk::left() const
443
0
{
444
0
    if (!isValid()) {
445
0
        return 0;
446
0
    }
447
0
    return qint32(ui16(data().at(5), data().at(4)));
448
0
}
449
450
qint32 BMHDChunk::top() const
451
0
{
452
0
    if (!isValid()) {
453
0
        return 0;
454
0
    }
455
0
    return qint32(ui16(data().at(7), data().at(6)));
456
0
}
457
458
quint8 BMHDChunk::bitplanes() const
459
2.11M
{
460
2.11M
    if (!isValid()) {
461
629k
        return 0;
462
629k
    }
463
1.48M
    return quint8(data().at(8));
464
2.11M
}
465
466
BMHDChunk::Masking BMHDChunk::masking() const
467
337k
{
468
337k
    if (!isValid()) {
469
0
        return BMHDChunk::Masking::None;
470
0
    }
471
337k
    return BMHDChunk::Masking(quint8(data().at(9)));
472
337k
}
473
474
BMHDChunk::Compression BMHDChunk::compression() const
475
578k
{
476
578k
    if (!isValid()) {
477
0
        return BMHDChunk::Compression::Uncompressed;
478
0
    }
479
578k
    return BMHDChunk::Compression(data().at(10));
480
481
578k
}
482
483
qint16 BMHDChunk::transparency() const
484
0
{
485
0
    if (!isValid()) {
486
0
        return 0;
487
0
    }
488
0
    return i16(data().at(13), data().at(12));
489
0
}
490
491
quint8 BMHDChunk::xAspectRatio() const
492
5.69k
{
493
5.69k
    if (!isValid()) {
494
3
        return 0;
495
3
    }
496
5.69k
    return quint8(data().at(14));
497
5.69k
}
498
499
quint8 BMHDChunk::yAspectRatio() const
500
5.69k
{
501
5.69k
    if (!isValid()) {
502
3
        return 0;
503
3
    }
504
5.69k
    return quint8(data().at(15));
505
5.69k
}
506
507
quint16 BMHDChunk::pageWidth() const
508
0
{
509
0
    if (!isValid()) {
510
0
        return 0;
511
0
    }
512
0
    return ui16(data().at(17), data().at(16));
513
0
}
514
515
quint16 BMHDChunk::pageHeight() const
516
0
{
517
0
    if (!isValid()) {
518
0
        return 0;
519
0
    }
520
0
    return ui16(data().at(19), data().at(18));
521
0
}
522
523
quint32 BMHDChunk::rowLen() const
524
839k
{
525
839k
    return ((quint32(width()) + 15) / 16) * 2;
526
839k
}
527
528
/* ******************
529
 * *** CMAP Chunk ***
530
 * ****************** */
531
532
CMAPChunk::~CMAPChunk()
533
{
534
535
}
536
537
16.2k
CMAPChunk::CMAPChunk() : IFFChunk()
538
16.2k
{
539
16.2k
}
540
541
bool CMAPChunk::isValid() const
542
17.4k
{
543
17.4k
    return chunkId() == CMAPChunk::defaultChunkId();
544
17.4k
}
545
546
qint32 CMAPChunk::count() const
547
17.4k
{
548
17.4k
    if (!isValid()) {
549
0
        return 0;
550
0
    }
551
17.4k
    return dataBytes() / 3;
552
17.4k
}
553
554
QList<QRgb> CMAPChunk::palette(bool halfbride) const
555
4.31k
{
556
4.31k
    auto p = innerPalette();
557
4.31k
    if (!halfbride) {
558
4.17k
        return p;
559
4.17k
    }
560
138
    auto tmp = p;
561
60.6k
    for (auto &&v : tmp) {
562
60.6k
        p << qRgb(qRed(v) / 2, qGreen(v) / 2, qBlue(v) / 2);
563
60.6k
    }
564
138
    return p;
565
4.31k
}
566
567
bool CMAPChunk::innerReadStructure(QIODevice *d)
568
11.5k
{
569
11.5k
    return cacheData(d);
570
11.5k
}
571
572
QList<QRgb> CMAPChunk::innerPalette() const
573
3.23k
{
574
3.23k
    QList<QRgb> l;
575
3.23k
    auto &&d = data();
576
532k
    for (qint32 i = 0, n = count(); i < n; ++i) {
577
529k
        auto i3 = i * 3;
578
529k
        l << qRgb(d.at(i3), d.at(i3 + 1), d.at(i3 + 2));
579
529k
    }
580
3.23k
    return l;
581
3.23k
}
582
583
584
/* ******************
585
 * *** CMYK Chunk ***
586
 * ****************** */
587
588
CMYKChunk::~CMYKChunk()
589
{
590
591
}
592
593
7.15k
CMYKChunk::CMYKChunk() : CMAPChunk()
594
7.15k
{
595
596
7.15k
}
597
598
bool CMYKChunk::isValid() const
599
193k
{
600
193k
    return chunkId() == CMYKChunk::defaultChunkId();
601
193k
}
602
603
qint32 CMYKChunk::count() const
604
193k
{
605
193k
    if (!isValid()) {
606
0
        return 0;
607
0
    }
608
193k
    return dataBytes() / 4;
609
193k
}
610
611
QList<QRgb> CMYKChunk::innerPalette() const
612
955
{
613
955
    QList<QRgb> l;
614
955
    auto &&d = data();
615
278k
    for (qint32 i = 0, n = count(); i < n; ++i) {
616
277k
        auto i4 = i * 4;
617
277k
        auto C = quint8(d.at(i4)) / 255.;
618
277k
        auto M = quint8(d.at(i4 + 1)) / 255.;
619
277k
        auto Y = quint8(d.at(i4 + 2)) / 255.;
620
277k
        auto K = quint8(d.at(i4 + 3)) / 255.;
621
277k
        l << QColor::fromCmykF(C, M, Y, K).toRgb().rgb();
622
277k
    }
623
955
    return l;
624
955
}
625
626
627
/* ******************
628
 * *** CAMG Chunk ***
629
 * ****************** */
630
631
CAMGChunk::~CAMGChunk()
632
{
633
634
}
635
636
24.0k
CAMGChunk::CAMGChunk() : IFFChunk()
637
24.0k
{
638
24.0k
}
639
640
bool CAMGChunk::isValid() const
641
46.7k
{
642
46.7k
    if (dataBytes() != 4) {
643
30.7k
        return false;
644
30.7k
    }
645
16.0k
    return chunkId() == CAMGChunk::defaultChunkId();
646
46.7k
}
647
648
CAMGChunk::ModeIds CAMGChunk::modeId() const
649
46.7k
{
650
46.7k
    if (!isValid()) {
651
30.7k
        return CAMGChunk::ModeIds();
652
30.7k
    }
653
16.0k
    return CAMGChunk::ModeIds(ui32(data().at(3), data().at(2), data().at(1), data().at(0)));
654
46.7k
}
655
656
bool CAMGChunk::innerReadStructure(QIODevice *d)
657
16.7k
{
658
16.7k
    return cacheData(d);
659
16.7k
}
660
661
/* ******************
662
 * *** DPI  Chunk ***
663
 * ****************** */
664
665
DPIChunk::~DPIChunk()
666
{
667
668
}
669
670
16.0k
DPIChunk::DPIChunk() : IFFChunk()
671
16.0k
{
672
16.0k
}
673
674
bool DPIChunk::isValid() const
675
88
{
676
88
    if (dpiX() == 0 || dpiY() == 0) {
677
25
        return false;
678
25
    }
679
63
    return chunkId() == DPIChunk::defaultChunkId();
680
88
}
681
682
quint16 DPIChunk::dpiX() const
683
151
{
684
151
    if (dataBytes() < 4) {
685
17
        return 0;
686
17
    }
687
134
    return ui16(data().at(1), data().at(0));
688
151
}
689
690
quint16 DPIChunk::dpiY() const
691
130
{
692
130
    if (dataBytes() < 4) {
693
0
        return 0;
694
0
    }
695
130
    return ui16(data().at(3), data().at(2));
696
130
}
697
698
qint32 DPIChunk::dotsPerMeterX() const
699
118
{
700
118
    return dpi2ppm(dpiX());
701
118
}
702
703
qint32 DPIChunk::dotsPerMeterY() const
704
118
{
705
118
    return dpi2ppm(dpiY());
706
118
}
707
708
bool DPIChunk::innerReadStructure(QIODevice *d)
709
7.18k
{
710
7.18k
    return cacheData(d);
711
7.18k
}
712
713
/* ******************
714
 * *** XBMI Chunk ***
715
 * ****************** */
716
717
XBMIChunk::~XBMIChunk()
718
{
719
720
}
721
722
7.15k
XBMIChunk::XBMIChunk() : DPIChunk()
723
7.15k
{
724
7.15k
}
725
726
bool XBMIChunk::isValid() const
727
101
{
728
101
    if (dpiX() == 0 || dpiY() == 0) {
729
46
        return false;
730
46
    }
731
55
    return chunkId() == XBMIChunk::defaultChunkId();
732
101
}
733
734
quint16 XBMIChunk::dpiX() const
735
156
{
736
156
    if (dataBytes() < 6) {
737
34
        return 0;
738
34
    }
739
122
    return ui16(data().at(3), data().at(2));
740
156
}
741
742
quint16 XBMIChunk::dpiY() const
743
120
{
744
120
    if (dataBytes() < 6) {
745
0
        return 0;
746
0
    }
747
120
    return ui16(data().at(5), data().at(4));
748
120
}
749
750
XBMIChunk::PictureType XBMIChunk::pictureType() const
751
0
{
752
0
    if (dataBytes() < 6) {
753
0
        return PictureType(-1);
754
0
    }
755
0
    return PictureType(i16(data().at(1), data().at(0)));
756
0
}
757
758
/* ******************
759
 * *** BODY Chunk ***
760
 * ****************** */
761
762
BODYChunk::~BODYChunk()
763
17.6k
{
764
765
17.6k
}
766
767
17.6k
BODYChunk::BODYChunk() : IFFChunk()
768
17.6k
{
769
17.6k
}
770
771
bool BODYChunk::isValid() const
772
233k
{
773
233k
    return chunkId() == BODYChunk::defaultChunkId();
774
233k
}
775
776
// For each RGB value, a LONG-word (32 bits) is written:
777
// with the 24 RGB bits in the MSB positions; the "genlock"
778
// bit next, and then a 7 bit repeat count.
779
//
780
// See also: https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data
781
inline qint64 rgb8Decompress(QIODevice *input, char *output, qint64 olen)
782
1.94k
{
783
1.94k
    qint64 j = 0;
784
6.28k
    for (qint64 available = olen; j < olen; available = olen - j) {
785
5.57k
        auto pos = input->pos();
786
5.57k
        auto ba4 = input->read(4);
787
5.57k
        if (ba4.size() != 4) {
788
108
            break;
789
108
        }
790
5.46k
        auto cnt = qint32(ba4.at(3) & 0x7F);
791
5.46k
        if (cnt * 3 > available) {
792
1.12k
            if (!input->seek(pos))
793
0
                return -1;
794
1.12k
            break;
795
1.12k
        }
796
315k
        for (qint32 i = 0; i < cnt; ++i) {
797
310k
            output[j++] = ba4.at(0);
798
310k
            output[j++] = ba4.at(1);
799
310k
            output[j++] = ba4.at(2);
800
310k
        }
801
4.34k
    }
802
1.94k
    return j;
803
1.94k
}
804
805
// For each RGB value, a WORD (16-bits) is written: with the
806
// 12 RGB bits in the MSB (most significant bit) positions;
807
// the "genlock" bit next; and then a 3 bit repeat count.
808
// If the repeat count is greater than 7, the 3-bit count is
809
// zero, and a BYTE repeat count follows.  If the repeat count
810
// is greater than 255, the BYTE count is zero, and a WORD
811
// repeat count follows.  Repeat counts greater than 65536 are
812
// not supported.
813
//
814
// See also: https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data
815
inline qint32 rgbnCount(QIODevice *input, quint8 &R, quint8& G, quint8 &B)
816
5.07k
{
817
5.07k
    auto ba2 = input->read(2);
818
5.07k
    if (ba2.size() != 2)
819
160
        return 0;
820
821
4.91k
    R = ba2.at(0) & 0xF0;
822
4.91k
    R = R | (R >> 4);
823
824
4.91k
    G = ba2.at(0) & 0x0F;
825
4.91k
    G = G | (G << 4);
826
827
4.91k
    B = ba2.at(1) & 0xF0;
828
4.91k
    B = B | (B >> 4);
829
830
4.91k
    auto cnt = ba2.at(1) & 7;
831
4.91k
    if (cnt == 0) {
832
1.78k
        auto ba1 = input->read(1);
833
1.78k
        if (ba1.size() != 1)
834
31
            return 0;
835
1.75k
        cnt = quint8(ba1.at(0));
836
1.75k
    }
837
4.88k
    if (cnt == 0) {
838
1.11k
        auto baw = input->read(2);
839
1.11k
        if (baw.size() != 2)
840
16
            return 0;
841
1.10k
        cnt = qint32(quint8(baw.at(0))) << 8 | quint8(baw.at(1));
842
1.10k
    }
843
844
4.86k
    return cnt;
845
4.88k
}
846
847
inline qint64 rgbNDecompress(QIODevice *input, char *output, qint64 olen)
848
790
{
849
790
    qint64 j = 0;
850
5.10k
    for (qint64 available = olen; j < olen; available = olen - j) {
851
5.07k
        quint8 R = 0, G = 0, B = 0;
852
5.07k
        auto pos = input->pos();
853
5.07k
        auto cnt = rgbnCount(input, R, G, B);
854
5.07k
        if (cnt * 3 > available || cnt == 0) {
855
766
            if (!input->seek(pos))
856
0
                return -1;
857
766
            break;
858
766
        }
859
26.0M
        for (qint32 i = 0; i < cnt; ++i) {
860
26.0M
            output[j++] = R;
861
26.0M
            output[j++] = G;
862
26.0M
            output[j++] = B;
863
26.0M
        }
864
4.31k
    }
865
790
    return j;
866
790
}
867
868
869
inline qint64 vdatDecompress(const IFFChunk *chunk, const BMHDChunk *header, qint32 y, char *output, qint64 olen)
870
23
{
871
23
    auto vdats = IFFChunk::searchT<VDATChunk>(chunk);
872
23
    auto rowLen = header->rowLen();
873
23
    if (olen < rowLen * vdats.size()) {
874
0
        return -1;
875
0
    }
876
23
    for (qint32 i = 0, n = vdats.size(); i < n; ++i) {
877
0
        auto&& uc = vdats.at(i)->uncompressedData(header);
878
0
        if (y * rowLen > uc.size() - rowLen)
879
0
            return -1;
880
0
        std::memcpy(output + (i * rowLen), uc.data() + (y * rowLen), rowLen);
881
0
    }
882
23
    return vdats.size() * rowLen;
883
23
}
884
885
QByteArray BODYChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal, const QByteArray& formType) const
886
337k
{
887
337k
    if (!isValid() || header == nullptr || d == nullptr) {
888
0
        return {};
889
0
    }
890
891
337k
    auto isRgbN = formType == RGBN_FORM_TYPE;
892
337k
    auto isRgb8 = formType == RGB8_FORM_TYPE;
893
337k
    auto isPbm = formType == PBM__FORM_TYPE;
894
337k
    auto lineCompressed = isRgbN || isRgb8 ? false : true;
895
337k
    auto readSize = strideSize(header, formType);
896
337k
    auto bufSize = readSize;
897
337k
    if (isRgbN) {
898
165k
        bufSize = std::max(quint32(65536 * 3), readSize);
899
165k
    }
900
337k
    if (isRgb8) {
901
54.5k
        bufSize = std::max(quint32(127 * 3), readSize);
902
54.5k
    }
903
459k
    for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && _readBuffer.size() < readSize;) {
904
122k
        QByteArray buf(bufSize, char());
905
122k
        qint64 rr = -1;
906
122k
        if (header->compression() == BMHDChunk::Compression::Rle) {
907
            // WARNING: The online spec says it's the same as TIFF but that's
908
            // not accurate: the RLE -128 code is not a noop.
909
3.68k
            rr = packbitsDecompress(d, buf.data(), buf.size(), true);
910
118k
        } else if (header->compression() == BMHDChunk::Compression::Vdat) {
911
23
            rr = vdatDecompress(this, header, y, buf.data(), buf.size());
912
118k
        } else if (header->compression() == BMHDChunk::Compression::RgbN8) {
913
2.74k
            if (isRgb8)
914
1.94k
                rr = rgb8Decompress(d, buf.data(), buf.size());
915
797
            else if (isRgbN)
916
790
                rr = rgbNDecompress(d, buf.data(), buf.size());
917
115k
        } else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
918
115k
            rr = d->read(buf.data(), buf.size()); // never seen
919
115k
        } else {
920
56
            qCDebug(LOG_IFFPLUGIN) << "BODYChunk::strideRead(): unknown compression" << header->compression();
921
56
        }
922
122k
        if ((rr != readSize && lineCompressed) || (rr < 1))
923
343
            return {};
924
121k
        _readBuffer.append(buf.data(), rr);
925
121k
    }
926
927
336k
    auto planes = _readBuffer.left(readSize);
928
336k
    _readBuffer.remove(0, readSize);
929
336k
    if (isPbm) {
930
582
        return pbm(planes, y, header, camg, cmap, ipal);
931
582
    }
932
336k
    if (isRgb8) {
933
54.5k
        return rgb8(planes, y, header, camg, cmap, ipal);
934
54.5k
    }
935
281k
    if (isRgbN) {
936
165k
        return rgbN(planes, y, header, camg, cmap, ipal);
937
165k
    }
938
116k
    return deinterleave(planes, y, header, camg, cmap, ipal);
939
281k
}
940
941
bool BODYChunk::resetStrideRead(QIODevice *d) const
942
1.20k
{
943
1.20k
    _readBuffer.clear();
944
1.20k
    return seek(d);
945
1.20k
}
946
947
CAMGChunk::ModeIds BODYChunk::safeModeId(const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap)
948
291k
{
949
291k
    if (camg) {
950
46.7k
        return camg->modeId();
951
46.7k
    }
952
244k
    if (header == nullptr) {
953
0
        return CAMGChunk::ModeIds();
954
0
    }
955
244k
    auto cmapCount = cmap ? cmap->count() : 0;
956
244k
    auto bitplanes = header->bitplanes();
957
244k
    if (bitplanes >= BITPLANES_HALFBRIDE_MIN && bitplanes <= BITPLANES_HALFBRIDE_MAX) {
958
144k
        if (cmapCount == (1 << (bitplanes - 1)))
959
94.2k
            return CAMGChunk::ModeIds(CAMGChunk::ModeId::HalfBrite);
960
144k
    }
961
150k
    if (bitplanes >= BITPLANES_HAM_MIN && bitplanes <= BITPLANES_HAM_MAX) {
962
6.47k
        if (cmapCount == (1 << (bitplanes - 2)))
963
847
            return CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham);
964
6.47k
    }
965
149k
    return CAMGChunk::ModeIds();
966
150k
}
967
968
quint32 BODYChunk::strideSize(const BMHDChunk *header, const QByteArray& formType) const
969
778k
{
970
    // RGB8 / RGBN
971
778k
    if (formType == RGB8_FORM_TYPE || formType == RGBN_FORM_TYPE) {
972
439k
        return header->width() * 3;
973
439k
    }
974
975
    // PBM
976
338k
    if (formType == PBM__FORM_TYPE) {
977
1.18k
        auto rs = header->width() * header->bitplanes() / 8;
978
1.18k
        if (rs & 1)
979
852
            ++rs;
980
1.18k
        return rs;
981
1.18k
    }
982
983
    // ILBM
984
337k
    auto sz = header->rowLen() * header->bitplanes();
985
337k
    if (header->masking() == BMHDChunk::Masking::HasMask)
986
281k
        sz += header->rowLen();
987
337k
    return sz;
988
338k
}
989
990
QByteArray BODYChunk::pbm(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
991
582
{
992
582
    if (planes.size() != strideSize(header, PBM__FORM_TYPE)) {
993
12
        return {};
994
12
    }
995
570
    if (header->bitplanes() == 8) {
996
        // The data are contiguous.
997
556
        return planes;
998
556
    }
999
14
    return {};
1000
570
}
1001
1002
QByteArray BODYChunk::rgb8(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
1003
54.5k
{
1004
54.5k
    if (planes.size() != strideSize(header, RGB8_FORM_TYPE)) {
1005
148
        return {};
1006
148
    }
1007
54.3k
    return planes;
1008
54.5k
}
1009
1010
QByteArray BODYChunk::rgbN(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
1011
165k
{
1012
165k
    if (planes.size() != strideSize(header, RGBN_FORM_TYPE)) {
1013
161
        return {};
1014
161
    }
1015
164k
    return planes;
1016
165k
}
1017
1018
bool BODYChunk::innerReadStructure(QIODevice *d)
1019
13.8k
{
1020
13.8k
    auto ok = true;
1021
13.8k
    if (d->peek(4) == VDAT_CHUNK) {
1022
221
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1023
221
    }
1024
13.8k
    return ok;
1025
13.8k
}
1026
1027
QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal) const
1028
116k
{
1029
116k
    if (planes.size() != strideSize(header, ILBM_FORM_TYPE)) {
1030
210
        return {};
1031
210
    }
1032
1033
116k
    auto rowLen = qint32(header->rowLen());
1034
116k
    auto bitplanes = header->bitplanes();
1035
116k
    auto modeId = BODYChunk::safeModeId(header, camg, cmap);
1036
1037
116k
    QByteArray ba;
1038
116k
    switch (bitplanes) {
1039
101k
    case 1: // gray, indexed and rgb Ham mode
1040
101k
    case 2:
1041
102k
    case 3:
1042
103k
    case 4:
1043
104k
    case 5:
1044
107k
    case 6:
1045
107k
    case 7:
1046
107k
    case 8:
1047
107k
        if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) &&
1048
8.00k
            (bitplanes >= BITPLANES_HAM_MIN && bitplanes <= BITPLANES_HAM_MAX)) {
1049
            // From A Quick Introduction to IFF.txt:
1050
            //
1051
            // Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
1052
            // In HAM mode, the bits in the two last planes describe an R G or B
1053
            // modification to the color of the previous pixel on the line to create the
1054
            // color of the current pixel.  So a 6-plane HAM picture has 4 planes for
1055
            // specifying absolute color pixels giving up to 16 absolute colors which would
1056
            // be specified in the ILBM CMAP chunk.  The bits in the last two planes are
1057
            // color modification bits which cause the Amiga, in HAM mode, to take the RGB
1058
            // value of the previous pixel (Hold and), substitute the 4 bits in planes 0-3
1059
            // for the previous color's R G or B component (Modify) and display the result
1060
            // for the current pixel.  If the first pixel of a scan line is a modification
1061
            // pixel, it modifies the RGB value of the border color (register 0).  The color
1062
            // modification bits in the last two planes (planes 4 and 5) are interpreted as
1063
            // follows:
1064
            //    00 - no modification.  Use planes 0-3 as normal color register index
1065
            //    10 - hold previous, replacing Blue component with bits from planes 0-3
1066
            //    01 - hold previous, replacing Red component with bits from planes 0-3
1067
            //    11 - hold previous. replacing Green component with bits from planes 0-3
1068
3.36k
            ba = QByteArray(rowLen * 8 * 3, char());
1069
3.36k
            auto pal = cmap->palette();
1070
3.36k
            if (ipal) {
1071
2.63k
                auto tmp = ipal->palette(y);
1072
2.63k
                if (!tmp.isEmpty())
1073
219
                    pal = tmp;
1074
2.63k
            }
1075
            // HAM 6: 2 control bits+4 bits of data, 16-color palette
1076
            //
1077
            // HAM 8: 2 control bits+6 bits of data, 64-color palette
1078
            //
1079
            // HAM 5: 1 control bit (and 1 hardwired to zero)+4 bits of data
1080
            //        (red and green modify operations are unavailable)
1081
3.36k
            auto ctlbits = bitplanes > 5 ? 2 : 1;
1082
3.36k
            auto max = (1 << (bitplanes - ctlbits)) - 1;
1083
3.36k
            quint8 prev[3] = {};
1084
25.0k
            for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
1085
195k
                for (qint32 j = 0; j < 8; ++j, ++cnt) {
1086
173k
                    quint8 idx = 0, ctl = 0;
1087
1.18M
                    for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
1088
1.01M
                        if ((planes.at(k * rowLen + i) & msk) == 0)
1089
726k
                            continue;
1090
288k
                        if (k < bitplanes - ctlbits)
1091
198k
                            idx |= 1 << k;
1092
89.8k
                        else
1093
89.8k
                            ctl |= 1 << (bitplanes - k - 1);
1094
288k
                    }
1095
173k
                    if (ctl && ctlbits == 1) {
1096
10.6k
                        ctl <<= 1; // HAM 5 has only 1 control bit and the LSB is always 0
1097
10.6k
                    }
1098
173k
                    switch (ctl) {
1099
12.7k
                    case 1: // red
1100
12.7k
                        prev[0] = idx * 255 / max;
1101
12.7k
                        break;
1102
43.7k
                    case 2: // blue
1103
43.7k
                        prev[2] = idx * 255 / max;
1104
43.7k
                        break;
1105
16.6k
                    case 3: // green
1106
16.6k
                        prev[1] = idx * 255 / max;
1107
16.6k
                        break;
1108
100k
                    default:
1109
100k
                        if (idx < pal.size()) {
1110
83.3k
                            prev[0] = qRed(pal.at(idx));
1111
83.3k
                            prev[1] = qGreen(pal.at(idx));
1112
83.3k
                            prev[2] = qBlue(pal.at(idx));
1113
83.3k
                        } else {
1114
16.8k
                            qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
1115
16.8k
                        }
1116
100k
                        break;
1117
173k
                    }
1118
173k
                    auto cnt3 = cnt * 3;
1119
173k
                    ba[cnt3] = char(prev[0]);
1120
173k
                    ba[cnt3 + 1] = char(prev[1]);
1121
173k
                    ba[cnt3 + 2] = char(prev[2]);
1122
173k
                }
1123
21.6k
            }
1124
104k
        } else if ((modeId & CAMGChunk::ModeId::HalfBrite) && (cmap) &&
1125
98.4k
                   (bitplanes >= BITPLANES_HALFBRIDE_MIN && bitplanes <= BITPLANES_HALFBRIDE_MAX)) {
1126
            // From A Quick Introduction to IFF.txt:
1127
            //
1128
            // In HALFBRITE mode, the Amiga interprets the bit in the
1129
            // last plane as HALFBRITE modification.  The bits in the other planes are
1130
            // treated as normal color register numbers (RGB values for each color register
1131
            // is specified in the CMAP chunk).  If the bit in the last plane is set (1),
1132
            // then that pixel is displayed at half brightness.  This can provide up to 64
1133
            // absolute colors.
1134
98.4k
            ba = QByteArray(rowLen * 8, char());
1135
98.4k
            auto palSize = cmap->count();
1136
346k
            for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
1137
2.23M
                for (qint32 j = 0; j < 8; ++j, ++cnt) {
1138
1.98M
                    quint8 idx = 0, ctl = 0;
1139
4.13M
                    for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
1140
2.14M
                        if ((planes.at(k * rowLen + i) & msk) == 0)
1141
1.09M
                            continue;
1142
1.05M
                        if (k < bitplanes - 1)
1143
58.5k
                            idx |= 1 << k;
1144
993k
                        else
1145
993k
                            ctl = 1;
1146
1.05M
                    }
1147
1.98M
                    if (idx < palSize) {
1148
1.98M
                        ba[cnt] = ctl ? idx + palSize : idx;
1149
1.98M
                    } else {
1150
1.19k
                        qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
1151
1.19k
                    }
1152
1.98M
                }
1153
248k
            }
1154
98.4k
        } else {
1155
            // From A Quick Introduction to IFF.txt:
1156
            //
1157
            // If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if
1158
            // necessary, you will have N planes of pixel data.  Color register used for
1159
            // each pixel is specified by looking at each pixel thru the planes.  I.e.,
1160
            // if you have 5 planes, and the bit for a particular pixel is set in planes
1161
            // 0 and 3:
1162
            //
1163
            //        PLANE     4 3 2 1 0
1164
            //        PIXEL     0 1 0 0 1
1165
            //
1166
            // then that pixel uses color register binary 01001 = 9
1167
5.87k
            ba = QByteArray(rowLen * 8, char());
1168
261k
            for (qint32 i = 0; i < rowLen; ++i) {
1169
863k
                for (qint32 k = 0, i8 = i * 8; k < bitplanes; ++k) {
1170
607k
                    auto v = planes.at(k * rowLen + i);
1171
607k
                    auto msk = 1 << k;
1172
607k
                    if (v & (1 << 7))
1173
332k
                        ba[i8] |= msk;
1174
607k
                    if (v & (1 << 6))
1175
260k
                        ba[i8 + 1] |= msk;
1176
607k
                    if (v & (1 << 5))
1177
274k
                        ba[i8 + 2] |= msk;
1178
607k
                    if (v & (1 << 4))
1179
275k
                        ba[i8 + 3] |= msk;
1180
607k
                    if (v & (1 << 3))
1181
258k
                        ba[i8 + 4] |= msk;
1182
607k
                    if (v & (1 << 2))
1183
283k
                        ba[i8 + 5] |= msk;
1184
607k
                    if (v & (1 << 1))
1185
284k
                        ba[i8 + 6] |= msk;
1186
607k
                    if (v & 1)
1187
302k
                        ba[i8 + 7] |= msk;
1188
607k
                }
1189
255k
            }
1190
5.87k
        }
1191
107k
        break;
1192
1193
107k
    case 24: // rgb
1194
8.23k
    case 32: // rgba (SView5 extension)
1195
        // From A Quick Introduction to IFF.txt:
1196
        //
1197
        // If a deep ILBM (like 12 or 24 planes), there should be no CMAP
1198
        // and instead the BODY planes are interpreted as the bits of RGB
1199
        // in the order R0...Rn G0...Gn B0...Bn
1200
        //
1201
        // NOTE: This code does not support 12-planes images
1202
8.23k
        ba = QByteArray(rowLen * bitplanes, char());
1203
328k
        for (qint32 i = 0, cnt = 0, p = bitplanes / 8; i < rowLen; ++i) {
1204
2.88M
            for (qint32 j = 0; j < 8; ++j)
1205
12.4M
                for (qint32 k = 0; k < p; ++k, ++cnt) {
1206
9.92M
                    auto k8 = k * 8;
1207
9.92M
                    auto msk = (1 << (7 - j));
1208
9.92M
                    if (planes.at(k8 * rowLen + i) & msk)
1209
5.20M
                        ba[cnt] |= 0x01;
1210
9.92M
                    if (planes.at((1 + k8) * rowLen + i) & msk)
1211
5.30M
                        ba[cnt] |= 0x02;
1212
9.92M
                    if (planes.at((2 + k8) * rowLen + i) & msk)
1213
5.24M
                        ba[cnt] |= 0x04;
1214
9.92M
                    if (planes.at((3 + k8) * rowLen + i) & msk)
1215
5.20M
                        ba[cnt] |= 0x08;
1216
9.92M
                    if (planes.at((4 + k8) * rowLen + i) & msk)
1217
5.22M
                        ba[cnt] |= 0x10;
1218
9.92M
                    if (planes.at((5 + k8) * rowLen + i) & msk)
1219
5.17M
                        ba[cnt] |= 0x20;
1220
9.92M
                    if (planes.at((6 + k8) * rowLen + i) & msk)
1221
5.16M
                        ba[cnt] |= 0x40;
1222
9.92M
                    if (planes.at((7 + k8) * rowLen + i) & msk)
1223
5.22M
                        ba[cnt] |= 0x80;
1224
9.92M
                }
1225
320k
        }
1226
8.23k
        break;
1227
1228
350
    case 48: // rgb (SView5 extension)
1229
557
    case 64: // rgba (SView5 extension)
1230
        // From https://aminet.net/package/docs/misc/ILBM64:
1231
        //
1232
        // Previously, the IFF-ILBM fileformat has been
1233
        // extended two times already, for 24 bit and 32 bit
1234
        // image data:
1235
        //
1236
        //  24 bit -> 24 planes composing RGB 8:8:8 true color
1237
        //  32 bit -> 32 planes composing RGBA 8:8:8:8 true color
1238
        //                                             plus alpha
1239
        //
1240
        // The former extension quickly became a common one,
1241
        // while the latter until recently mainly had been
1242
        // used by some NewTek software.
1243
        //
1244
        // Now the following - as a consequent logical extension
1245
        // of the previously mentioned definitions - is introduced
1246
        // by SView5-Library:
1247
        //
1248
        // 48 bit -> 48 planes composing RGB 16:16:16 true color
1249
        // 64 bit -> 64 planes composing RGBA 16:16:16:16 true color
1250
        //                                                plus alpha
1251
        //
1252
        // The resulting data is intended to allow direct transformation
1253
        // from the PNG format into the Amiga (ILBM) bitmap format.
1254
1255
557
        ba = QByteArray(rowLen * 64, char()); // the RGBX QT format is 64-bits
1256
557
        const qint32 order[] = { 1, 0, 3, 2, 5, 4, 7, 6 };
1257
43.0k
        for (qint32 i = 0, cnt = 0, p = bitplanes / 8; i < rowLen; ++i) {
1258
382k
            for (qint32 j = 0; j < 8; ++j, cnt += 8) {
1259
2.71M
                for (qint32 k = 0; k < p; ++k) {
1260
2.37M
                    auto k8 = k * 8;
1261
2.37M
                    auto msk = (1 << (7 - j));
1262
2.37M
                    auto idx = cnt + order[k];
1263
2.37M
                    if (planes.at(k8 * rowLen + i) & msk)
1264
906k
                        ba[idx] |= 0x01;
1265
2.37M
                    if (planes.at((1 + k8) * rowLen + i) & msk)
1266
900k
                        ba[idx] |= 0x02;
1267
2.37M
                    if (planes.at((2 + k8) * rowLen + i) & msk)
1268
866k
                        ba[idx] |= 0x04;
1269
2.37M
                    if (planes.at((3 + k8) * rowLen + i) & msk)
1270
841k
                        ba[idx] |= 0x08;
1271
2.37M
                    if (planes.at((4 + k8) * rowLen + i) & msk)
1272
856k
                        ba[idx] |= 0x10;
1273
2.37M
                    if (planes.at((5 + k8) * rowLen + i) & msk)
1274
865k
                        ba[idx] |= 0x20;
1275
2.37M
                    if (planes.at((6 + k8) * rowLen + i) & msk)
1276
842k
                        ba[idx] |= 0x40;
1277
2.37M
                    if (planes.at((7 + k8) * rowLen + i) & msk)
1278
876k
                        ba[idx] |= 0x80;
1279
2.37M
                }
1280
339k
                if (p == 6) { // RGBX wants unused X data set to 0xFF
1281
172k
                    ba[cnt + 6] = char(0xFF);
1282
172k
                    ba[cnt + 7] = char(0xFF);
1283
172k
                }
1284
339k
            }
1285
42.4k
        }
1286
557
        break;
1287
116k
    }
1288
116k
    return ba;
1289
116k
}
1290
1291
/* ******************
1292
 * *** ABIT Chunk ***
1293
 * ****************** */
1294
1295
ABITChunk::~ABITChunk()
1296
{
1297
1298
}
1299
1300
7.99k
ABITChunk::ABITChunk() : BODYChunk()
1301
7.99k
{
1302
1303
7.99k
}
1304
1305
bool ABITChunk::isValid() const
1306
207k
{
1307
207k
    return chunkId() == ABITChunk::defaultChunkId();
1308
207k
}
1309
1310
QByteArray ABITChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal, const QByteArray& formType) const
1311
104k
{
1312
104k
    if (!isValid() || header == nullptr || d == nullptr) {
1313
0
        return {};
1314
0
    }
1315
104k
    if (header->compression() != BMHDChunk::Compression::Uncompressed || formType != ACBM_FORM_TYPE) {
1316
74
        return {};
1317
74
    }
1318
1319
    // convert ABIT data to an ILBM line on the fly
1320
103k
    auto ilbmLine = QByteArray(strideSize(header, formType), char());
1321
103k
    auto rowSize = header->rowLen();
1322
103k
    auto height = header->height();
1323
103k
    if (y >= height) {
1324
0
        return {};
1325
0
    }
1326
224k
    for (qint32 plane = 0, planes = qint32(header->bitplanes()); plane < planes; ++plane) {
1327
120k
        if (!seek(d, qint64(plane) * rowSize * height + y * rowSize))
1328
10
            return {};
1329
120k
        auto offset = qint64(plane) * rowSize;
1330
120k
        if (offset + rowSize > ilbmLine.size())
1331
0
            return {};
1332
120k
        if (d->read(ilbmLine.data() + offset, rowSize) != rowSize)
1333
124
            return {};
1334
120k
    }
1335
1336
    // decode the ILBM line
1337
103k
    QBuffer buf;
1338
103k
    buf.setData(ilbmLine);
1339
103k
    if (!buf.open(QBuffer::ReadOnly)) {
1340
0
        return {};
1341
0
    }
1342
103k
    return BODYChunk::strideRead(&buf, y, header, camg, cmap, ipal, ILBM_FORM_TYPE);
1343
103k
}
1344
1345
bool ABITChunk::resetStrideRead(QIODevice *d) const
1346
257
{
1347
257
    return BODYChunk::resetStrideRead(d);
1348
257
}
1349
1350
1351
/* **********************
1352
 * *** FORM Interface ***
1353
 * ********************** */
1354
1355
IFOR_Chunk::~IFOR_Chunk()
1356
{
1357
1358
}
1359
1360
751k
IFOR_Chunk::IFOR_Chunk() : IFFChunk()
1361
751k
{
1362
1363
751k
}
1364
1365
QImageIOHandler::Transformation IFOR_Chunk::transformation() const
1366
0
{
1367
0
    auto exifs = IFFChunk::searchT<EXIFChunk>(chunks());
1368
0
    if (!exifs.isEmpty()) {
1369
0
        auto exif = exifs.first()->value();
1370
0
        if (!exif.isEmpty())
1371
0
            return exif.transformation();
1372
0
    }
1373
0
    return QImageIOHandler::Transformation::TransformationNone;
1374
0
}
1375
1376
QImage::Format IFOR_Chunk::optionformat() const
1377
0
{
1378
0
    auto fmt = this->format();
1379
0
    if (fmt == QImage::Format_Indexed8) {
1380
0
        if (auto ipal = searchIPal()) {
1381
0
            fmt = ipal->hasAlpha() ? FORMAT_RGBA_8BIT : FORMAT_RGB_8BIT;
1382
0
        }
1383
0
    }
1384
0
    return fmt;
1385
0
}
1386
1387
const IPALChunk *IFOR_Chunk::searchIPal() const
1388
7.52k
{
1389
7.52k
    const IPALChunk *ipal = nullptr;
1390
7.52k
    auto beam = IFFChunk::searchT<BEAMChunk>(this);
1391
7.52k
    if (!beam.isEmpty()) {
1392
141
        ipal = beam.first();
1393
141
    }
1394
7.52k
    auto ctbl = IFFChunk::searchT<CTBLChunk>(this);
1395
7.52k
    if (!ctbl.isEmpty()) {
1396
74
        ipal = ctbl.first();
1397
74
    }
1398
7.52k
    auto sham = IFFChunk::searchT<SHAMChunk>(this);
1399
7.52k
    if (!sham.isEmpty()) {
1400
108
        ipal = sham.first();
1401
108
    }
1402
7.52k
    auto rast = IFFChunk::searchT<RASTChunk>(this);
1403
7.52k
    if (!rast.isEmpty()) {
1404
259
        ipal = rast.first();
1405
259
    }
1406
7.52k
    auto pchg = IFFChunk::searchT<PCHGChunk>(this);
1407
7.52k
    if (!pchg.isEmpty()) {
1408
630
        ipal = pchg.first();
1409
630
    }
1410
7.52k
    if (ipal && ipal->isValid()) {
1411
1.07k
        return ipal;
1412
1.07k
    }
1413
6.45k
    return nullptr;
1414
7.52k
}
1415
1416
1417
/* ******************
1418
 * *** FORM Chunk ***
1419
 * ****************** */
1420
1421
FORMChunk::~FORMChunk()
1422
744k
{
1423
1424
744k
}
1425
1426
744k
FORMChunk::FORMChunk() : IFOR_Chunk()
1427
744k
{
1428
744k
}
1429
1430
bool FORMChunk::isValid() const
1431
0
{
1432
0
    return chunkId() == FORMChunk::defaultChunkId();
1433
0
}
1434
1435
bool FORMChunk::isSupported() const
1436
988k
{
1437
988k
    return format() != QImage::Format_Invalid;
1438
988k
}
1439
1440
bool FORMChunk::innerReadStructure(QIODevice *d)
1441
491k
{
1442
491k
    if (bytes() < 4) {
1443
5
        return false;
1444
5
    }
1445
491k
    _type = d->read(4);
1446
491k
    auto ok = true;
1447
1448
    // NOTE: add new supported type to CATChunk as well.
1449
491k
    if (_type == ILBM_FORM_TYPE) {
1450
413k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1451
413k
    } else if (_type == PBM__FORM_TYPE) {
1452
1.65k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1453
77.0k
    } else if (_type == ACBM_FORM_TYPE) {
1454
16.3k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1455
60.7k
    } else if (_type == RGB8_FORM_TYPE) {
1456
2.22k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1457
58.5k
    } else if (_type == RGBN_FORM_TYPE) {
1458
1.39k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1459
57.1k
    } else if (_type == IMAG_FORM_TYPE) {
1460
8.23k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1461
48.8k
    } else if (_type == RGFX_FORM_TYPE) {
1462
3.85k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1463
3.85k
    }
1464
491k
    return ok;
1465
491k
}
1466
1467
QImage::Format FORMChunk::iffFormat() const
1468
975k
{
1469
975k
    auto headers = IFFChunk::searchT<BMHDChunk>(chunks());
1470
975k
    if (headers.isEmpty()) {
1471
801k
        return QImage::Format_Invalid;
1472
801k
    }
1473
1474
174k
    if (auto &&h = headers.first()) {
1475
174k
        auto cmaps = IFFChunk::searchT<CMAPChunk>(chunks());
1476
174k
        if (cmaps.isEmpty()) {
1477
160k
            auto cmyks = IFFChunk::searchT<CMYKChunk>(chunks());
1478
160k
            for (auto &&cmyk : cmyks)
1479
47.9k
                cmaps.append(cmyk);
1480
160k
        }
1481
174k
        auto camgs = IFFChunk::searchT<CAMGChunk>(chunks());
1482
174k
        auto modeId = BODYChunk::safeModeId(h, camgs.isEmpty() ? nullptr : camgs.first(), cmaps.isEmpty() ? nullptr : cmaps.first());
1483
174k
        if (h->bitplanes() == 13) {
1484
507
            return FORMAT_RGB_8BIT; // NOTE: with a little work you could use Format_RGB444
1485
507
        }
1486
174k
        if (h->bitplanes() == 24 || h->bitplanes() == 25) {
1487
11.8k
            return FORMAT_RGB_8BIT;
1488
11.8k
        }
1489
162k
        if (h->bitplanes() == 48) {
1490
2.69k
            return QImage::Format_RGBX64;
1491
2.69k
        }
1492
159k
        if (h->bitplanes() == 32) {
1493
24.4k
            return QImage::Format_RGBA8888;
1494
24.4k
        }
1495
135k
        if (h->bitplanes() == 64) {
1496
1.22k
            return QImage::Format_RGBA64;
1497
1.22k
        }
1498
133k
        if (h->bitplanes() >= 1 && h->bitplanes() <= 8) {
1499
52.5k
            if (h->bitplanes() >= BITPLANES_HAM_MIN && h->bitplanes() <= BITPLANES_HAM_MAX) {
1500
10.6k
                if (modeId & CAMGChunk::ModeId::Ham)
1501
5.45k
                    return FORMAT_RGB_8BIT;
1502
10.6k
            }
1503
1504
47.1k
            if (!cmaps.isEmpty()) {
1505
3.93k
                return QImage::Format_Indexed8;
1506
3.93k
            }
1507
1508
43.1k
            return QImage::Format_Grayscale8;
1509
47.1k
        }
1510
133k
        qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format(): Unsupported" << h->bitplanes() << "bitplanes";
1511
81.3k
    }
1512
1513
81.3k
    return QImage::Format_Invalid;
1514
174k
}
1515
1516
QImage::Format FORMChunk::cdiFormat() const
1517
13.2k
{
1518
13.2k
    auto headers = IFFChunk::searchT<IHDRChunk>(chunks());
1519
13.2k
    if (headers.isEmpty()) {
1520
3.35k
        return QImage::Format_Invalid;
1521
3.35k
    }
1522
1523
9.90k
    if (auto &&h = headers.first()) {
1524
9.90k
        if (h->model() == IHDRChunk::Rgb555 && h->depth() == 16) { // no test case
1525
970
            return QImage::Format_RGB555;
1526
970
        }
1527
1528
8.93k
        if (h->depth() == 4) {
1529
637
            if (h->model() == IHDRChunk::CLut4 || h->model() == IHDRChunk::CLut3) { // CLut3: no test case
1530
124
                return QImage::Format_Indexed8;
1531
124
            }
1532
637
        }
1533
1534
8.81k
        if (h->depth() == 8) {
1535
6.18k
            if (h->model() == IHDRChunk::CLut8 || h->model() == IHDRChunk::CLut7 || h->model() == IHDRChunk::Rle7) {
1536
3.64k
                return QImage::Format_Indexed8;
1537
3.64k
            }
1538
2.53k
            if (h->model() == IHDRChunk::Rgb888) { // no test case
1539
278
                return FORMAT_RGB_8BIT;
1540
278
            }
1541
2.26k
            if (h->model() == IHDRChunk::DYuv) {
1542
1.81k
                return FORMAT_RGB_8BIT;
1543
1.81k
            }
1544
2.26k
        }
1545
8.81k
    }
1546
1547
3.07k
    return QImage::Format_Invalid;
1548
9.90k
}
1549
1550
QImage::Format FORMChunk::rgfxFormat() const
1551
7.67k
{
1552
7.67k
    auto headers = IFFChunk::searchT<RGHDChunk>(chunks());
1553
7.67k
    if (headers.isEmpty()) {
1554
1.79k
        return QImage::Format_Invalid;
1555
1.79k
    }
1556
1557
5.88k
    if (auto &&h = headers.first()) {
1558
5.88k
        auto rgfx_format = RGHDChunk::BitmapTypes(h->bitmapType() & 0x3FFFFFFF);
1559
5.88k
        if (rgfx_format == RGHDChunk::BitmapType::Chunky8 || rgfx_format == RGHDChunk::BitmapType::Planar8)
1560
2.07k
            return QImage::Format_Indexed8;
1561
3.81k
        if (rgfx_format == RGHDChunk::BitmapType::Rgb15 || rgfx_format == RGHDChunk::BitmapType::Rgb16)
1562
331
            return QImage::Format_RGB555; // NOTE: RGB16 ignoring alpha due to missing compatible Qt format
1563
3.48k
        if (rgfx_format == RGHDChunk::BitmapType::Rgb24)
1564
133
            return FORMAT_RGB_8BIT;
1565
3.34k
        if (rgfx_format == RGHDChunk::BitmapType::Rgb32)
1566
466
            return FORMAT_RGBA_8BIT;
1567
2.88k
        if (rgfx_format == RGHDChunk::BitmapType::Rgb48)
1568
169
            return QImage::Format_RGBX64;
1569
2.71k
        if (rgfx_format == RGHDChunk::BitmapType::Rgb64)
1570
310
            return QImage::Format_RGBA64;
1571
2.40k
        if (rgfx_format == RGHDChunk::BitmapType::Rgb96)
1572
200
            return QImage::Format_RGBX32FPx4;
1573
2.20k
        if (rgfx_format == RGHDChunk::BitmapType::Rgb128)
1574
227
            return QImage::Format_RGBA32FPx4;
1575
2.20k
    }
1576
1577
1.97k
    return QImage::Format_Invalid;
1578
5.88k
}
1579
1580
QByteArray FORMChunk::formType() const
1581
2.32M
{
1582
2.32M
    return _type;
1583
2.32M
}
1584
1585
QImage::Format FORMChunk::format() const
1586
996k
{
1587
996k
    if (formType() == IMAG_FORM_TYPE) {
1588
13.2k
        return cdiFormat();
1589
983k
    } else if (formType() == RGFX_FORM_TYPE) {
1590
7.67k
        return rgfxFormat();
1591
7.67k
    }
1592
975k
    return iffFormat();
1593
996k
}
1594
1595
QSize FORMChunk::size() const
1596
0
{
1597
0
    if (formType() == IMAG_FORM_TYPE) {
1598
0
        auto ihdrs = IFFChunk::searchT<IHDRChunk>(chunks());
1599
0
        if (!ihdrs.isEmpty()) {
1600
0
            return ihdrs.first()->size();
1601
0
        }
1602
0
    } else if (formType() == RGFX_FORM_TYPE) {
1603
0
        auto rghds = IFFChunk::searchT<RGHDChunk>(chunks());
1604
0
        if (!rghds.isEmpty()) {
1605
0
            return rghds.first()->size();
1606
0
        }
1607
0
    } else {
1608
0
        auto bmhds = IFFChunk::searchT<BMHDChunk>(chunks());
1609
0
        if (!bmhds.isEmpty()) {
1610
0
            return bmhds.first()->size();
1611
0
        }
1612
0
    }
1613
0
    return {};
1614
0
}
1615
1616
/* ******************
1617
 * *** FOR4 Chunk ***
1618
 * ****************** */
1619
1620
FOR4Chunk::~FOR4Chunk()
1621
6.89k
{
1622
1623
6.89k
}
1624
1625
6.89k
FOR4Chunk::FOR4Chunk() : IFOR_Chunk()
1626
6.89k
{
1627
1628
6.89k
}
1629
1630
bool FOR4Chunk::isValid() const
1631
0
{
1632
0
    return chunkId() == FOR4Chunk::defaultChunkId();
1633
0
}
1634
1635
qint32 FOR4Chunk::alignBytes() const
1636
19.5k
{
1637
19.5k
    return 4;
1638
19.5k
}
1639
1640
bool FOR4Chunk::isSupported() const
1641
5.91k
{
1642
5.91k
    return format() != QImage::Format_Invalid;
1643
5.91k
}
1644
1645
bool FOR4Chunk::innerReadStructure(QIODevice *d)
1646
6.23k
{
1647
6.23k
    if (bytes() < 4) {
1648
13
        return false;
1649
13
    }
1650
6.22k
    _type = d->read(4);
1651
6.22k
    auto ok = true;
1652
6.22k
    if (_type == CIMG_FOR4_TYPE) {
1653
5.19k
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1654
5.19k
    } else if (_type == TBMP_FOR4_TYPE) {
1655
437
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1656
437
    }
1657
6.22k
    return ok;
1658
6.23k
}
1659
1660
QByteArray FOR4Chunk::formType() const
1661
126
{
1662
126
    return _type;
1663
126
}
1664
1665
QImage::Format FOR4Chunk::format() const
1666
6.31k
{
1667
6.31k
    auto headers = IFFChunk::searchT<TBHDChunk>(chunks());
1668
6.31k
    if (headers.isEmpty()) {
1669
3.25k
        return QImage::Format_Invalid;
1670
3.25k
    }
1671
3.06k
    return headers.first()->format();
1672
6.31k
}
1673
1674
QSize FOR4Chunk::size() const
1675
0
{
1676
0
    auto headers = IFFChunk::searchT<TBHDChunk>(chunks());
1677
0
    if (headers.isEmpty()) {
1678
0
        return {};
1679
0
    }
1680
0
    return headers.first()->size();
1681
0
}
1682
1683
/* ******************
1684
 * *** CAT Chunk ***
1685
 * ****************** */
1686
1687
CATChunk::~CATChunk()
1688
2.01k
{
1689
1690
2.01k
}
1691
1692
2.01k
CATChunk::CATChunk() : IFFChunk()
1693
2.01k
{
1694
1695
2.01k
}
1696
1697
bool CATChunk::isValid() const
1698
0
{
1699
0
    return chunkId() == CATChunk::defaultChunkId();
1700
0
}
1701
1702
QByteArray CATChunk::catType() const
1703
0
{
1704
0
    return _type;
1705
0
}
1706
1707
bool CATChunk::innerReadStructure(QIODevice *d)
1708
1.71k
{
1709
1.71k
    if (bytes() < 4) {
1710
5
        return false;
1711
5
    }
1712
1.70k
    _type = d->read(4);
1713
1.70k
    auto ok = true;
1714
1715
    // supports the image formats of FORMChunk.
1716
1.70k
    if (_type == ILBM_FORM_TYPE) {
1717
704
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1718
1.00k
    } else if (_type == PBM__FORM_TYPE) {
1719
159
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1720
842
    } else if (_type == ACBM_FORM_TYPE) {
1721
120
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1722
722
    } else if (_type == RGB8_FORM_TYPE) {
1723
120
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1724
602
    } else if (_type == RGBN_FORM_TYPE) {
1725
49
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1726
553
    } else if (_type == IMAG_FORM_TYPE) {
1727
31
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1728
522
    } else if (_type == RGFX_FORM_TYPE) {
1729
7
        setChunks(IFFChunk::innerFromDevice(d, &ok, this));
1730
7
    }
1731
1.70k
    return ok;
1732
1.71k
}
1733
1734
/* ******************
1735
 * *** TBHD Chunk ***
1736
 * ****************** */
1737
1738
TBHDChunk::~TBHDChunk()
1739
{
1740
1741
}
1742
1743
TBHDChunk::TBHDChunk()
1744
2.31k
{
1745
1746
2.31k
}
1747
1748
bool TBHDChunk::isValid() const
1749
452k
{
1750
452k
    if (dataBytes() != 24 && dataBytes() != 32) {
1751
2.30k
        return false;
1752
2.30k
    }
1753
449k
    return chunkId() == TBHDChunk::defaultChunkId();
1754
452k
}
1755
1756
qint32 TBHDChunk::alignBytes() const
1757
2.42k
{
1758
2.42k
    return 4;
1759
2.42k
}
1760
1761
qint32 TBHDChunk::width() const
1762
401
{
1763
401
    if (!isValid()) {
1764
0
        return 0;
1765
0
    }
1766
401
    return i32(data().at(3), data().at(2), data().at(1), data().at(0));
1767
401
}
1768
1769
qint32 TBHDChunk::height() const
1770
401
{
1771
401
    if (!isValid()) {
1772
0
        return 0;
1773
0
    }
1774
401
    return i32(data().at(7), data().at(6), data().at(5), data().at(4));
1775
401
}
1776
1777
QSize TBHDChunk::size() const
1778
401
{
1779
401
    return QSize(width(), height());
1780
401
}
1781
1782
qint32 TBHDChunk::left() const
1783
0
{
1784
0
    if (dataBytes() != 32) {
1785
0
        return 0;
1786
0
    }
1787
0
    return i32(data().at(27), data().at(26), data().at(25), data().at(24));
1788
0
}
1789
1790
qint32 TBHDChunk::top() const
1791
0
{
1792
0
    if (dataBytes() != 32) {
1793
0
        return 0;
1794
0
    }
1795
0
    return i32(data().at(31), data().at(30), data().at(29), data().at(28));
1796
0
}
1797
1798
TBHDChunk::Flags TBHDChunk::flags() const
1799
252k
{
1800
252k
    if (!isValid()) {
1801
2.30k
        return TBHDChunk::Flags();
1802
2.30k
    }
1803
249k
    return TBHDChunk::Flags(ui32(data().at(15), data().at(14), data().at(13), data().at(12)));
1804
252k
}
1805
1806
qint32 TBHDChunk::bpc() const
1807
198k
{
1808
198k
    if (!isValid()) {
1809
0
        return 0;
1810
0
    }
1811
198k
    return ui16(data().at(17), data().at(16)) ? 2 : 1;
1812
198k
}
1813
1814
qint32 TBHDChunk::channels() const
1815
195k
{
1816
195k
    if ((flags() & TBHDChunk::Flag::RgbA) == TBHDChunk::Flag::RgbA) {
1817
144k
        return 4;
1818
144k
    }
1819
51.1k
    if ((flags() & TBHDChunk::Flag::Rgb) == TBHDChunk::Flag::Rgb) {
1820
51.1k
        return 3;
1821
51.1k
    }
1822
0
    return 0;
1823
51.1k
}
1824
1825
quint16 TBHDChunk::tiles() const
1826
292
{
1827
292
    if (!isValid()) {
1828
0
        return 0;
1829
0
    }
1830
292
    return ui16(data().at(19), data().at(18));
1831
292
}
1832
1833
QImage::Format TBHDChunk::format() const
1834
3.35k
{
1835
    // Support for RGBA and RGB only for now.
1836
3.35k
    if ((flags() & TBHDChunk::Flag::RgbA) == TBHDChunk::Flag::RgbA) {
1837
1.04k
        if (bpc() == 2)
1838
627
            return QImage::Format_RGBA64;
1839
414
        else if (bpc() == 1)
1840
414
            return QImage::Format_RGBA8888;
1841
2.31k
    } else if ((flags() & TBHDChunk::Flag::Rgb) == TBHDChunk::Flag::Rgb) {
1842
1.05k
        if (bpc() == 2)
1843
657
            return QImage::Format_RGBX64;
1844
402
        else if (bpc() == 1)
1845
402
            return FORMAT_RGB_8BIT;
1846
1.05k
    }
1847
1848
1.25k
    return QImage::Format_Invalid;
1849
3.35k
}
1850
1851
TBHDChunk::Compression TBHDChunk::compression() const
1852
594
{
1853
594
    if (!isValid()) {
1854
0
        return TBHDChunk::Compression::Uncompressed;
1855
0
    }
1856
594
    return TBHDChunk::Compression(ui32(data().at(23), data().at(22), data().at(21), data().at(20)));
1857
594
}
1858
1859
bool TBHDChunk::innerReadStructure(QIODevice *d)
1860
2.10k
{
1861
2.10k
    return cacheData(d);
1862
2.10k
}
1863
1864
/* ******************
1865
 * *** RGBA Chunk ***
1866
 * ****************** */
1867
1868
RGBAChunk::~RGBAChunk()
1869
3.20k
{
1870
3.20k
}
1871
1872
RGBAChunk::RGBAChunk()
1873
3.20k
{
1874
1875
3.20k
}
1876
1877
bool RGBAChunk::isValid() const
1878
192k
{
1879
192k
    if (bytes() < 8) {
1880
0
        return false;
1881
0
    }
1882
192k
    return chunkId() == RGBAChunk::defaultChunkId();
1883
192k
}
1884
1885
qint32 RGBAChunk::alignBytes() const
1886
369k
{
1887
369k
    return 4;
1888
369k
}
1889
1890
bool RGBAChunk::isTileCompressed(const TBHDChunk *header) const
1891
192k
{
1892
192k
    if (!isValid() || header == nullptr) {
1893
0
        return false;
1894
0
    }
1895
192k
    return qint64(header->channels()) * size().width() * size().height() * header->bpc() > qint64(bytes() - 8);
1896
192k
}
1897
1898
QPoint RGBAChunk::pos() const
1899
325
{
1900
325
    return _posPx;
1901
325
}
1902
1903
QSize RGBAChunk::size() const
1904
578k
{
1905
578k
    return _sizePx;
1906
578k
}
1907
1908
// Maya version of IFF uses a slightly different algorithm for RLE compression.
1909
// To understand how it works I saved images with regular patterns from Photoshop
1910
// and then checked the data. It is basically the same as packbits except for how
1911
// the length is extracted: I don't know if it's a standard variant or not, so
1912
// I'm keeping it private.
1913
inline qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
1914
585
{
1915
585
    qint64  j = 0;
1916
283k
    for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
1917
282k
        char n;
1918
1919
        // check the output buffer space for the next run
1920
282k
        if (available < 128) {
1921
7.66k
            if (input->peek(&n, 1) != 1) { // end of data (or error)
1922
1
                break;
1923
1
            }
1924
7.66k
            rr = qint64(n & 0x7F) + 1;
1925
7.66k
            if (rr > available)
1926
50
                break;
1927
7.66k
        }
1928
1929
        // decompress
1930
282k
        if (input->read(&n, 1) != 1) { // end of data (or error)
1931
2
            break;
1932
2
        }
1933
1934
282k
        rr = qint64(n & 0x7F) + 1;
1935
282k
        if ((n & 0x80) == 0) {
1936
74.9k
            auto read = input->read(output + j, rr);
1937
74.9k
            if (rr != read) {
1938
6
                return -1;
1939
6
            }
1940
207k
        } else {
1941
207k
            char b;
1942
207k
            if (input->read(&b, 1) != 1) {
1943
1
                break;
1944
1
            }
1945
207k
            std::memset(output + j, b, size_t(rr));
1946
207k
        }
1947
1948
282k
        j += rr;
1949
282k
    }
1950
579
    return j;
1951
585
}
1952
1953
QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
1954
192k
{
1955
192k
    auto readSize = size().width();
1956
192k
    if (readSize == 0) {
1957
0
        return {};
1958
0
    }
1959
1960
    // It seems that tiles are compressed independently only if there is space savings.
1961
    // The compression method specified in the header is only to indicate the type of
1962
    // compression if used.
1963
192k
    if (!isTileCompressed(header)) {
1964
        // when not compressed, the line contains all channels
1965
2.59k
        readSize *= header->bpc() * header->channels();
1966
2.59k
        QByteArray buf(readSize, char());
1967
2.59k
        auto rr = d->read(buf.data(), buf.size());
1968
2.59k
        if (rr != buf.size()) {
1969
0
            return {};
1970
0
        }
1971
2.59k
        return buf;
1972
2.59k
    }
1973
1974
    // compressed
1975
190k
    for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && _readBuffer.size() < readSize;) {
1976
594
        QByteArray buf(readSize * size().height(), char());
1977
594
        qint64 rr = -1;
1978
594
        if (header->compression() == TBHDChunk::Compression::Rle) {
1979
585
            rr = rleMayaDecompress(d, buf.data(), buf.size());
1980
585
        }
1981
594
        if (rr != buf.size()) {
1982
69
            return {};
1983
69
        }
1984
525
        _readBuffer.append(buf.data(), rr);
1985
525
    }
1986
1987
189k
    auto buff = _readBuffer.left(readSize);
1988
189k
    _readBuffer.remove(0, readSize);
1989
1990
189k
    return buff;
1991
189k
}
1992
1993
/*!
1994
 * \brief compressedTile
1995
 *
1996
 * The compressed tile contains compressed data per channel.
1997
 *
1998
 * If 16 bit, high and low bytes are treated separately (so I have
1999
 * channels * 2 compressed data blocks). First the high ones, then the low
2000
 * ones (or vice versa): for the reconstruction I went by trial and error :)
2001
 * \param d The device
2002
 * \param header The header.
2003
 * \return The tile as Qt image.
2004
 */
2005
QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
2006
174
{
2007
174
    QImage img(size(), header->format());
2008
174
    auto bpc = header->bpc();
2009
2010
174
    if (bpc == 1) {
2011
330
        for (auto c = 0, cs = header->channels(); c < cs; ++c) {
2012
157k
            for (auto y = 0, h = img.height(); y < h; ++y) {
2013
157k
                auto ba = readStride(d, header);
2014
157k
                if (ba.isEmpty()) {
2015
50
                    return {};
2016
50
                }
2017
157k
                auto scl = reinterpret_cast<quint8*>(img.scanLine(y));
2018
938k
                for (auto x = 0, w = std::min(int(ba.size()), img.width()); x < w; ++x) {
2019
781k
                    scl[x * cs + cs - c - 1] = ba.at(x);
2020
781k
                }
2021
157k
            }
2022
278
        }
2023
102
    } else if (bpc == 2) {
2024
72
        auto cs = std::max(1, header->channels());
2025
72
        if (cs < 4) { // alpha on 64-bit images must be 0xFF
2026
35
            std::memset(img.bits(), 0xFF, img.sizeInBytes());
2027
35
        }
2028
369
        for (auto c = 0, cc = header->channels() * header->bpc(); c < cc; ++c) {
2029
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
2030
            auto c_bcp = c / cs; // Not tried
2031
#else
2032
334
            auto c_bcp = 1 - c / cs;
2033
334
#endif
2034
334
            auto c_cs = (cs - 1 - c % cs) * bpc + c_bcp;
2035
32.6k
            for (auto y = 0, h = img.height(); y < h; ++y) {
2036
32.3k
                auto ba = readStride(d, header);
2037
32.3k
                if (ba.isEmpty()) {
2038
37
                    return {};
2039
37
                }
2040
32.2k
                auto scl = reinterpret_cast<quint8*>(img.scanLine(y));
2041
1.12M
                for (auto x = 0, w = std::min(int(ba.size()), img.width()); x < w; ++x) {
2042
1.09M
                    scl[x * 4 * bpc + c_cs] = ba.at(x); // * 4 -> Qt RGB 64-bit formats are always 4 channels
2043
1.09M
                }
2044
32.2k
            }
2045
334
        }
2046
72
    }
2047
2048
87
    return img;
2049
174
}
2050
2051
/*!
2052
 * \brief RGBAChunk::uncompressedTile
2053
 *
2054
 * The uncompressed tile scanline contains the data in
2055
 * B0 G0 R0 A0 B1 G1 R1 A1... Bn Gn Rn An format.
2056
 * \param d The device
2057
 * \param header The header.
2058
 * \return The tile as Qt image.
2059
 */
2060
QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
2061
119
{
2062
119
    QImage img(size(), header->format());
2063
119
    auto bpc = header->bpc();
2064
2065
119
    if (bpc == 1) {
2066
74
        auto cs = header->channels();
2067
1.38k
        for (auto y = 0, h = img.height(); y < h; ++y) {
2068
1.31k
            auto ba = readStride(d, header);
2069
1.31k
            if (ba.isEmpty()) {
2070
0
                return {};
2071
0
            }
2072
1.31k
            auto scl = reinterpret_cast<quint8*>(img.scanLine(y));
2073
5.83k
            for (auto c = 0; c < cs; ++c) {
2074
218k
                for (auto x = 0, w = std::min(int(ba.size() / cs), img.width()); x < w; ++x) {
2075
214k
                    auto xcs = x * cs;
2076
214k
                    scl[xcs + cs - c - 1] = ba.at(xcs + c);
2077
214k
                }
2078
4.52k
            }
2079
1.31k
        }
2080
74
    } else if (bpc == 2) {
2081
45
        auto cs = header->channels();
2082
45
        if (cs < 4) { // alpha on 64-bit images must be 0xFF
2083
22
            std::memset(img.bits(), 0xFF, img.sizeInBytes());
2084
22
        }
2085
2086
1.32k
        for (auto y = 0, h = img.height(); y < h; ++y) {
2087
1.28k
            auto ba = readStride(d, header);
2088
1.28k
            if (ba.isEmpty()) {
2089
0
                return {};
2090
0
            }
2091
1.28k
            auto scl = reinterpret_cast<quint16*>(img.scanLine(y));
2092
1.28k
            auto src = reinterpret_cast<const quint16*>(ba.data());
2093
5.77k
            for (auto c = 0; c < cs; ++c) {
2094
120k
                for (auto x = 0, w = std::min(int(ba.size() / cs / bpc), img.width()); x < w; ++x) {
2095
115k
                    auto xcs = x * cs;
2096
115k
                    auto xcs4 = x * 4;
2097
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
2098
                    scl[xcs4 + cs - c - 1] = src[xcs + c]; // Not tried
2099
#else
2100
115k
                    scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8);
2101
115k
#endif
2102
115k
                }
2103
4.49k
            }
2104
1.28k
        }
2105
45
    }
2106
2107
119
    return img;
2108
119
}
2109
2110
QImage RGBAChunk::tile(QIODevice *d, const TBHDChunk *header) const
2111
293
{
2112
293
    if (!isValid() || header == nullptr) {
2113
0
        return {};
2114
0
    }
2115
293
    if (!seek(d, 8)) {
2116
0
        return {};
2117
0
    }
2118
2119
293
    if (isTileCompressed(header)) {
2120
174
        return compressedTile(d, header);
2121
174
    }
2122
2123
119
    return uncompressedTile(d, header);
2124
293
}
2125
2126
bool RGBAChunk::innerReadStructure(QIODevice *d)
2127
3.02k
{
2128
3.02k
    auto ba = d->read(8);
2129
3.02k
    if (ba.size() != 8) {
2130
12
        return false;
2131
12
    }
2132
2133
3.01k
    auto x0 = ui16(ba.at(1), ba.at(0));
2134
3.01k
    auto y0 = ui16(ba.at(3), ba.at(2));
2135
3.01k
    auto x1 = ui16(ba.at(5), ba.at(4));
2136
3.01k
    auto y1 = ui16(ba.at(7), ba.at(6));
2137
3.01k
    if (x0 > x1 || y0 > y1) {
2138
35
        return false;
2139
35
    }
2140
2141
2.97k
    _posPx = QPoint(x0, y0);
2142
2.97k
    _sizePx = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
2143
2144
2.97k
    return true;
2145
3.01k
}
2146
2147
2148
/* ******************
2149
 * *** ANNO Chunk ***
2150
 * ****************** */
2151
2152
ANNOChunk::~ANNOChunk()
2153
{
2154
2155
}
2156
2157
ANNOChunk::ANNOChunk()
2158
17.5k
{
2159
2160
17.5k
}
2161
2162
bool ANNOChunk::isValid() const
2163
415
{
2164
415
    return chunkId() == ANNOChunk::defaultChunkId();
2165
415
}
2166
2167
QString ANNOChunk::value() const
2168
415
{
2169
415
    return dataToString(this);
2170
415
}
2171
2172
bool ANNOChunk::innerReadStructure(QIODevice *d)
2173
11.6k
{
2174
11.6k
    return cacheData(d);
2175
11.6k
}
2176
2177
/* ******************
2178
 * *** AUTH Chunk ***
2179
 * ****************** */
2180
2181
AUTHChunk::~AUTHChunk()
2182
{
2183
2184
}
2185
2186
AUTHChunk::AUTHChunk()
2187
15.6k
{
2188
2189
15.6k
}
2190
2191
bool AUTHChunk::isValid() const
2192
420
{
2193
420
    return chunkId() == AUTHChunk::defaultChunkId();
2194
420
}
2195
2196
QString AUTHChunk::value() const
2197
420
{
2198
420
    return dataToString(this);
2199
420
}
2200
2201
bool AUTHChunk::innerReadStructure(QIODevice *d)
2202
7.29k
{
2203
7.29k
    return cacheData(d);
2204
7.29k
}
2205
2206
2207
/* ******************
2208
 * *** COPY Chunk ***
2209
 * ****************** */
2210
2211
COPYChunk::~COPYChunk()
2212
{
2213
2214
}
2215
2216
COPYChunk::COPYChunk()
2217
11.0k
{
2218
2219
11.0k
}
2220
2221
bool COPYChunk::isValid() const
2222
356
{
2223
356
    return chunkId() == COPYChunk::defaultChunkId();
2224
356
}
2225
2226
QString COPYChunk::value() const
2227
356
{
2228
356
    return dataToString(this);
2229
356
}
2230
2231
bool COPYChunk::innerReadStructure(QIODevice *d)
2232
5.14k
{
2233
5.14k
    return cacheData(d);
2234
5.14k
}
2235
2236
2237
/* ******************
2238
 * *** DATE Chunk ***
2239
 * ****************** */
2240
2241
DATEChunk::~DATEChunk()
2242
{
2243
2244
}
2245
2246
DATEChunk::DATEChunk()
2247
6.37k
{
2248
2249
6.37k
}
2250
2251
bool DATEChunk::isValid() const
2252
577
{
2253
577
    return chunkId() == DATEChunk::defaultChunkId();
2254
577
}
2255
2256
QDateTime DATEChunk::value() const
2257
577
{
2258
577
    if (!isValid()) {
2259
0
        return {};
2260
0
    }
2261
577
    return QDateTime::fromString(QString::fromLatin1(data()), Qt::TextDate);
2262
577
}
2263
2264
bool DATEChunk::innerReadStructure(QIODevice *d)
2265
3.69k
{
2266
3.69k
    return cacheData(d);
2267
3.69k
}
2268
2269
2270
/* ******************
2271
 * *** EXIF Chunk ***
2272
 * ****************** */
2273
2274
EXIFChunk::~EXIFChunk()
2275
{
2276
2277
}
2278
2279
EXIFChunk::EXIFChunk()
2280
24.7k
{
2281
2282
24.7k
}
2283
2284
bool EXIFChunk::isValid() const
2285
3.60k
{
2286
3.60k
    if (!data().startsWith(QByteArray("Exif\0\0"))) {
2287
161
        return false;
2288
161
    }
2289
3.44k
    return chunkId() == EXIFChunk::defaultChunkId();
2290
3.60k
}
2291
2292
MicroExif EXIFChunk::value() const
2293
3.60k
{
2294
3.60k
    if (!isValid()) {
2295
161
        return {};
2296
161
    }
2297
3.44k
    return MicroExif::fromByteArray(data().mid(6));
2298
3.60k
}
2299
2300
bool EXIFChunk::innerReadStructure(QIODevice *d)
2301
14.8k
{
2302
14.8k
    return cacheData(d);
2303
14.8k
}
2304
2305
2306
/* ******************
2307
 * *** ICCN Chunk ***
2308
 * ****************** */
2309
2310
ICCNChunk::~ICCNChunk()
2311
{
2312
2313
}
2314
2315
ICCNChunk::ICCNChunk()
2316
2.25k
{
2317
2318
2.25k
}
2319
2320
bool ICCNChunk::isValid() const
2321
111
{
2322
111
    return chunkId() == ICCNChunk::defaultChunkId();
2323
111
}
2324
2325
QString ICCNChunk::value() const
2326
111
{
2327
111
    return dataToString(this);
2328
111
}
2329
2330
bool ICCNChunk::innerReadStructure(QIODevice *d)
2331
2.00k
{
2332
2.00k
    return cacheData(d);
2333
2.00k
}
2334
2335
2336
/* ******************
2337
 * *** ICCP Chunk ***
2338
 * ****************** */
2339
2340
ICCPChunk::~ICCPChunk()
2341
{
2342
2343
}
2344
2345
ICCPChunk::ICCPChunk()
2346
11.3k
{
2347
2348
11.3k
}
2349
2350
bool ICCPChunk::isValid() const
2351
622
{
2352
622
    return chunkId() == ICCPChunk::defaultChunkId();
2353
622
}
2354
2355
QColorSpace ICCPChunk::value() const
2356
622
{
2357
622
    if (!isValid()) {
2358
0
        return {};
2359
0
    }
2360
622
    return QColorSpace::fromIccProfile(data());
2361
622
}
2362
2363
bool ICCPChunk::innerReadStructure(QIODevice *d)
2364
8.68k
{
2365
8.68k
    return cacheData(d);
2366
8.68k
}
2367
2368
/* ******************
2369
 * *** FVER Chunk ***
2370
 * ****************** */
2371
2372
FVERChunk::~FVERChunk()
2373
{
2374
2375
}
2376
2377
FVERChunk::FVERChunk()
2378
706
{
2379
2380
706
}
2381
2382
bool FVERChunk::isValid() const
2383
0
{
2384
0
    return chunkId() == FVERChunk::defaultChunkId();
2385
0
}
2386
2387
bool FVERChunk::innerReadStructure(QIODevice *d)
2388
579
{
2389
579
    return cacheData(d);
2390
579
}
2391
2392
/* ******************
2393
 * *** HIST Chunk ***
2394
 * ****************** */
2395
2396
HISTChunk::~HISTChunk()
2397
{
2398
2399
}
2400
2401
HISTChunk::HISTChunk()
2402
968
{
2403
2404
968
}
2405
2406
bool HISTChunk::isValid() const
2407
0
{
2408
0
    return chunkId() == HISTChunk::defaultChunkId();
2409
0
}
2410
2411
QString HISTChunk::value() const
2412
0
{
2413
0
    if (!isValid()) {
2414
0
        return {};
2415
0
    }
2416
0
    return QString::fromLatin1(data());
2417
0
}
2418
2419
bool HISTChunk::innerReadStructure(QIODevice *d)
2420
527
{
2421
527
    return cacheData(d);
2422
527
}
2423
2424
2425
/* ******************
2426
 * *** NAME Chunk ***
2427
 * ****************** */
2428
2429
NAMEChunk::~NAMEChunk()
2430
{
2431
2432
}
2433
2434
NAMEChunk::NAMEChunk()
2435
19.1k
{
2436
2437
19.1k
}
2438
2439
bool NAMEChunk::isValid() const
2440
603
{
2441
603
    return chunkId() == NAMEChunk::defaultChunkId();
2442
603
}
2443
2444
QString NAMEChunk::value() const
2445
603
{
2446
603
    return dataToString(this);
2447
603
}
2448
2449
bool NAMEChunk::innerReadStructure(QIODevice *d)
2450
9.74k
{
2451
9.74k
    return cacheData(d);
2452
9.74k
}
2453
2454
2455
/* ******************
2456
 * *** VDAT Chunk ***
2457
 * ****************** */
2458
2459
VDATChunk::~VDATChunk()
2460
1.16k
{
2461
2462
1.16k
}
2463
2464
VDATChunk::VDATChunk()
2465
1.16k
{
2466
2467
1.16k
}
2468
2469
bool VDATChunk::isValid() const
2470
0
{
2471
0
    return chunkId() == VDATChunk::defaultChunkId();
2472
0
}
2473
2474
static QByteArray decompressVdat(const QByteArray &comp)
2475
0
{
2476
0
    QByteArray out;
2477
0
    auto ok = true;
2478
0
    auto cpos = 0;
2479
2480
0
    auto readU16BE = [&](const QByteArray &src, int &pos, bool *ok) -> quint16 {
2481
0
        if (pos + 2 > src.size()) {
2482
0
            *ok = false;
2483
0
            return 0;
2484
0
        }
2485
0
        auto v = quint16((quint8(src[pos]) << 8) | quint8(src[pos + 1]));
2486
0
        pos += 2;
2487
0
        return v;
2488
0
    };
2489
2490
0
    auto readI8 = [&](const QByteArray &src, int &pos, bool *ok) -> qint8 {
2491
0
        if (pos >= src.size()) {
2492
0
            *ok = false;
2493
0
            return 0;
2494
0
        }
2495
0
        return qint8(src[pos++]);
2496
0
    };
2497
2498
0
    auto emitWord = [&](quint16 w) {
2499
0
        out.append(char(w & 0xFF));
2500
0
        out.append(char(w >> 8));
2501
0
    };
2502
2503
0
    auto cmdCnt = readU16BE(comp, cpos, &ok);
2504
0
    if (!ok) {
2505
0
        return{};
2506
0
    }
2507
2508
    // decode command stream
2509
0
    auto dpos = cmdCnt + (cmdCnt & 1);
2510
0
    for (auto n = cmdCnt; cpos < n && dpos < comp.size() && ok;) {
2511
0
        auto cmd = readI8(comp, cpos, &ok);
2512
0
        if (cmd == 0) {
2513
0
            auto count = readU16BE(comp, dpos, &ok);
2514
0
            for (auto i = 0; i < count; ++i)
2515
0
                emitWord(readU16BE(comp, dpos, &ok));
2516
0
        } else if (cmd == 1) {
2517
0
            auto count = readU16BE(comp, dpos, &ok);
2518
0
            auto value = readU16BE(comp, dpos, &ok);
2519
0
            for (auto i = 0; i < count; ++i)
2520
0
                emitWord(value);
2521
0
        } else if (cmd < 0) {
2522
0
            auto count = -qint32(cmd);
2523
0
            for (auto i = 0; i < count; ++i)
2524
0
                emitWord(readU16BE(comp, dpos, &ok));
2525
0
        } else {
2526
0
            auto count = quint16(cmd);
2527
0
            auto value = readU16BE(comp, dpos, &ok);
2528
0
            for (auto i = 0; i < count; ++i)
2529
0
                emitWord(value);
2530
0
        }
2531
0
        if (!ok) {
2532
0
            return{};
2533
0
        }
2534
0
    }
2535
0
    return out;
2536
0
}
2537
2538
static QByteArray vdatToIlbmPlane(const QByteArray &vdatData, const BMHDChunk *header)
2539
0
{
2540
0
    QByteArray ba(vdatData.size(), char());
2541
0
    auto rowLen = qint32(header->rowLen());
2542
0
    for (auto x = 0, n = 0; x < rowLen; x += 2) {
2543
0
        for (auto y = 0, off = x, h = header->height(); y < h; y++, off += rowLen) {
2544
0
            if ((off + 1 >= ba.size()) || n + 1 >= vdatData.size()) {
2545
0
                return{};
2546
0
            }
2547
0
            ba[off + 1] = vdatData.at(n++);
2548
0
            ba[off] = vdatData.at(n++);
2549
0
        }
2550
0
    }
2551
0
    return ba;
2552
0
}
2553
2554
const QByteArray &VDATChunk::uncompressedData(const BMHDChunk *header) const
2555
0
{
2556
0
    if (uncompressed.isEmpty()) {
2557
0
        auto tmp = decompressVdat(data());
2558
0
        if (tmp.size() == header->rowLen() * header->height()) {
2559
0
            uncompressed = vdatToIlbmPlane(tmp, header);
2560
0
        }
2561
0
    }
2562
0
    return uncompressed;
2563
0
}
2564
2565
2566
bool VDATChunk::innerReadStructure(QIODevice *d)
2567
751
{
2568
751
    return cacheData(d);
2569
751
}
2570
2571
2572
/* ******************
2573
 * *** VERS Chunk ***
2574
 * ****************** */
2575
2576
VERSChunk::~VERSChunk()
2577
{
2578
2579
}
2580
2581
VERSChunk::VERSChunk()
2582
9.69k
{
2583
2584
9.69k
}
2585
2586
bool VERSChunk::isValid() const
2587
221
{
2588
221
    return chunkId() == VERSChunk::defaultChunkId();
2589
221
}
2590
2591
QString VERSChunk::value() const
2592
221
{
2593
221
    if (!isValid()) {
2594
0
        return {};
2595
0
    }
2596
221
    return QString::fromLatin1(data());
2597
221
}
2598
2599
bool VERSChunk::innerReadStructure(QIODevice *d)
2600
5.64k
{
2601
5.64k
    return cacheData(d);
2602
5.64k
}
2603
2604
2605
/* ******************
2606
 * *** XMP0 Chunk ***
2607
 * ****************** */
2608
2609
XMP0Chunk::~XMP0Chunk()
2610
{
2611
2612
}
2613
2614
XMP0Chunk::XMP0Chunk()
2615
12.9k
{
2616
2617
12.9k
}
2618
2619
bool XMP0Chunk::isValid() const
2620
682
{
2621
682
    return chunkId() == XMP0Chunk::defaultChunkId();
2622
682
}
2623
2624
QString XMP0Chunk::value() const
2625
682
{
2626
682
    return dataToString(this);
2627
682
}
2628
2629
bool XMP0Chunk::innerReadStructure(QIODevice *d)
2630
6.94k
{
2631
6.94k
    return cacheData(d);
2632
6.94k
}
2633
2634
2635
/* ******************
2636
 * *** IHDR Chunk ***
2637
 * ****************** */
2638
2639
IHDRChunk::~IHDRChunk()
2640
{
2641
2642
}
2643
2644
IHDRChunk::IHDRChunk()
2645
10.0k
{
2646
2647
10.0k
}
2648
2649
bool IHDRChunk::isValid() const
2650
173k
{
2651
173k
    if (dataBytes() < 14) {
2652
5.03k
        return false;
2653
5.03k
    }
2654
168k
    return chunkId() == IHDRChunk::defaultChunkId();
2655
173k
}
2656
2657
qint32 IHDRChunk::width() const
2658
34.9k
{
2659
34.9k
    if (!isValid()) {
2660
17
        return 0;
2661
17
    }
2662
34.8k
    return qint32(ui16(data().at(1), data().at(0)));
2663
34.9k
}
2664
2665
qint32 IHDRChunk::height() const
2666
508
{
2667
508
    if (!isValid()) {
2668
17
        return 0;
2669
17
    }
2670
491
    return qint32(ui16(data().at(5), data().at(4)));
2671
508
}
2672
2673
QSize IHDRChunk::size() const
2674
508
{
2675
508
    return QSize(width(), height());
2676
508
}
2677
2678
qint32 IHDRChunk::lineSize() const
2679
0
{
2680
0
    if (!isValid()) {
2681
0
        return 0;
2682
0
    }
2683
0
    return qint32(ui16(data().at(3), data().at(2)));
2684
0
}
2685
2686
quint16 IHDRChunk::depth() const
2687
32.3k
{
2688
32.3k
    if (!isValid()) {
2689
3.33k
        return 0;
2690
3.33k
    }
2691
28.9k
    return qint32(ui16(data().at(9), data().at(8)));
2692
32.3k
}
2693
2694
IHDRChunk::Model IHDRChunk::model() const
2695
97.8k
{
2696
97.8k
    if (!isValid()) {
2697
1.66k
        return IHDRChunk::Model::Invalid;
2698
1.66k
    }
2699
96.1k
    return IHDRChunk::Model(ui16(data().at(7), data().at(6)));
2700
97.8k
}
2701
2702
IHDRChunk::DYuvKind IHDRChunk::yuvKind() const
2703
4.10k
{
2704
4.10k
    if (!isValid()) {
2705
0
        return IHDRChunk::DYuvKind::One;
2706
0
    }
2707
4.10k
    return IHDRChunk::DYuvKind(data().at(10));
2708
4.10k
}
2709
2710
IHDRChunk::Yuv IHDRChunk::yuvStart() const
2711
4.10k
{
2712
4.10k
    if (!isValid()) {
2713
0
        return{};
2714
0
    }
2715
4.10k
    return Yuv(data().at(11), data().at(12), data().at(13));
2716
4.10k
}
2717
2718
bool IHDRChunk::innerReadStructure(QIODevice *d)
2719
5.60k
{
2720
5.60k
    return cacheData(d);
2721
5.60k
}
2722
2723
2724
/* ******************
2725
 * *** IPAR Chunk ***
2726
 * ****************** */
2727
2728
IPARChunk::~IPARChunk()
2729
{
2730
2731
}
2732
2733
IPARChunk::IPARChunk()
2734
1.26k
{
2735
2736
1.26k
}
2737
2738
bool IPARChunk::isValid() const
2739
40
{
2740
40
    if (dataBytes() < 22) {
2741
5
        return false;
2742
5
    }
2743
35
    return chunkId() == IPARChunk::defaultChunkId();
2744
40
}
2745
2746
qint32 IPARChunk::xOffset() const
2747
0
{
2748
0
    if (!isValid()) {
2749
0
        return 0;
2750
0
    }
2751
0
    return qint32(ui16(data().at(1), data().at(0)));
2752
0
}
2753
2754
qint32 IPARChunk::yOffset() const
2755
0
{
2756
0
    if (!isValid()) {
2757
0
        return 0;
2758
0
    }
2759
0
    return qint32(ui16(data().at(3), data().at(2)));
2760
0
}
2761
2762
double IPARChunk::aspectRatio() const
2763
40
{
2764
40
    if (!isValid()) {
2765
5
        return 1;
2766
5
    }
2767
35
    if (auto xr = ui16(data().at(5), data().at(4))) {
2768
32
        auto yr = double(ui16(data().at(7), data().at(6)));
2769
32
        return yr / xr;
2770
32
    }
2771
3
    return 1;
2772
35
}
2773
2774
qint32 IPARChunk::xPage() const
2775
0
{
2776
0
    if (!isValid()) {
2777
0
        return 0;
2778
0
    }
2779
0
    return qint32(ui16(data().at(9), data().at(8)));
2780
0
}
2781
2782
qint32 IPARChunk::yPage() const
2783
0
{
2784
0
    if (!isValid()) {
2785
0
        return 0;
2786
0
    }
2787
0
    return qint32(ui16(data().at(11), data().at(10)));
2788
0
}
2789
2790
qint32 IPARChunk::xGrub() const
2791
0
{
2792
0
    if (!isValid()) {
2793
0
        return 0;
2794
0
    }
2795
0
    return qint32(ui16(data().at(13), data().at(12)));
2796
0
}
2797
2798
qint32 IPARChunk::yGrub() const
2799
0
{
2800
0
    if (!isValid()) {
2801
0
        return 0;
2802
0
    }
2803
0
    return qint32(ui16(data().at(15), data().at(14)));
2804
0
}
2805
2806
IPARChunk::Rgb IPARChunk::mask() const
2807
0
{
2808
0
    if (!isValid()) {
2809
0
        return {};
2810
0
    }
2811
0
    return Rgb(data().at(16), data().at(17), data().at(18));
2812
0
}
2813
2814
IPARChunk::Rgb IPARChunk::transparency() const
2815
0
{
2816
0
    if (!isValid()) {
2817
0
        return {};
2818
0
    }
2819
0
    return Rgb(data().at(19), data().at(20), data().at(21));
2820
0
}
2821
2822
bool IPARChunk::innerReadStructure(QIODevice *d)
2823
958
{
2824
958
    return cacheData(d);
2825
958
}
2826
2827
2828
/* ******************
2829
 * *** PLTE Chunk ***
2830
 * ****************** */
2831
2832
PLTEChunk::~PLTEChunk()
2833
{
2834
2835
}
2836
2837
PLTEChunk::PLTEChunk()
2838
1.30k
{
2839
2840
1.30k
}
2841
2842
bool PLTEChunk::isValid() const
2843
88
{
2844
88
    if (dataBytes() < 4) {
2845
2
        return false;
2846
2
    }
2847
86
    if (dataBytes() - 4 < quint32(total()) * 3) {
2848
58
        return false;
2849
58
    }
2850
28
    return chunkId() == PLTEChunk::defaultChunkId();
2851
86
}
2852
2853
qint32 PLTEChunk::count() const
2854
0
{
2855
0
    if (!isValid()) {
2856
0
        return 0;
2857
0
    }
2858
0
    return total() - count();
2859
0
}
2860
2861
qint32 PLTEChunk::offset() const
2862
28
{
2863
28
    return qint32(ui16(data().at(1), data().at(0)));
2864
28
}
2865
2866
qint32 PLTEChunk::total() const
2867
114
{
2868
114
    return qint32(ui16(data().at(3), data().at(2)));
2869
114
}
2870
2871
QList<QRgb> PLTEChunk::innerPalette() const
2872
88
{
2873
88
    if (!isValid()) {
2874
60
        return{};
2875
60
    }
2876
28
    QList<QRgb> l;
2877
28
    auto &&d = data();
2878
1.44k
    for (qint32 i = offset(), n = total(); i < n; ++i) {
2879
1.41k
        auto i3 = 4 + i * 3;
2880
1.41k
        l << qRgb(d.at(i3), d.at(i3 + 1), d.at(i3 + 2));
2881
1.41k
    }
2882
28
    return l;
2883
88
}
2884
2885
2886
/* ******************
2887
 * *** YUVS Chunk ***
2888
 * ****************** */
2889
2890
YUVSChunk::~YUVSChunk()
2891
{
2892
2893
}
2894
2895
YUVSChunk::YUVSChunk()
2896
2.58k
{
2897
2898
2.58k
}
2899
2900
bool YUVSChunk::isValid() const
2901
2.37k
{
2902
2.37k
    return chunkId() == YUVSChunk::defaultChunkId();
2903
2.37k
}
2904
2905
qint32 YUVSChunk::count() const
2906
2.37k
{
2907
2.37k
    return dataBytes() / 3;
2908
2.37k
}
2909
2910
IHDRChunk::Yuv YUVSChunk::yuvStart(qint32 y) const
2911
2.37k
{
2912
2.37k
    if (!isValid() || y >= count()) {
2913
1.99k
        return{};
2914
1.99k
    }
2915
381
    return IHDRChunk::Yuv(data().at(y * 3), data().at(y * 3 + 1), data().at(y * 3 + 2));
2916
2.37k
}
2917
2918
bool YUVSChunk::innerReadStructure(QIODevice *d)
2919
1.76k
{
2920
1.76k
    return cacheData(d);
2921
1.76k
}
2922
2923
2924
/* ******************
2925
 * *** IDAT Chunk ***
2926
 * ****************** */
2927
2928
IDATChunk::~IDATChunk()
2929
{
2930
2931
}
2932
2933
IDATChunk::IDATChunk()
2934
2.17k
{
2935
2936
2.17k
}
2937
2938
bool IDATChunk::isValid() const
2939
13.4k
{
2940
13.4k
    return chunkId() == IDATChunk::defaultChunkId();
2941
13.4k
}
2942
2943
/*!
2944
 * Converts a YUV pixel to RGB.
2945
 */
2946
878k
inline IPARChunk::Rgb yuvToRgb(IHDRChunk::Yuv yuv) {
2947
878k
    IPARChunk::Rgb rgb;
2948
2949
    // Green Book Cap. V Par. 4.4.2.2
2950
878k
    const auto b = yuv.y + (yuv.u - 128.) * 1.733;
2951
878k
    const auto r = yuv.y + (yuv.v - 128.) * 1.371;
2952
878k
    const auto g = (yuv.y - 0.299 * r - 0.114 * b) / 0.587;
2953
2954
878k
    rgb.r = quint8(std::clamp(r + 0.5, 0., 255.));
2955
878k
    rgb.g = quint8(std::clamp(g + 0.5, 0., 255.));
2956
878k
    rgb.b = quint8(std::clamp(b + 0.5, 0., 255.));
2957
2958
878k
    return rgb;
2959
878k
}
2960
2961
8.59k
static QByteArray decompressRL7Row(QIODevice *device, int width) {
2962
8.59k
    QByteArray row;
2963
59.2k
    for (auto x = 0; x < width;) {
2964
50.6k
        char b;
2965
50.6k
        if (!device->getChar(&b)) {
2966
31
            return{};
2967
31
        }
2968
50.6k
        if (b & 0x80) {
2969
12.4k
            auto color = b & 0x7F;
2970
12.4k
            if (!device->getChar(&b)) {
2971
8
                return{};
2972
8
            }
2973
12.4k
            auto length = quint8(b);
2974
12.4k
            if (length == 0) {
2975
2.07k
                row.append(width - x, char(color));
2976
2.07k
                x = width;
2977
10.3k
            } else {
2978
10.3k
                auto count = std::min(int(length), width - x);
2979
10.3k
                row.append(count, char(color));
2980
10.3k
                x += count;
2981
10.3k
            }
2982
38.2k
        } else {
2983
38.2k
            row.append(b);
2984
38.2k
            x++;
2985
38.2k
        }
2986
50.6k
    }
2987
8.55k
    return row;
2988
8.59k
}
2989
2990
QByteArray IDATChunk::strideRead(QIODevice *d, qint32 y, const IHDRChunk *header, const IPARChunk *params, const YUVSChunk *yuvs) const
2991
13.4k
{
2992
13.4k
    Q_UNUSED(params)
2993
13.4k
    if (!isValid() || header == nullptr || d == nullptr) {
2994
0
        return {};
2995
0
    }
2996
2997
13.4k
    auto read = strideSize(header);
2998
13.4k
    for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos;) {
2999
13.3k
        QByteArray rr;
3000
13.3k
        if (header->model() == IHDRChunk::Rle7) {
3001
8.59k
            rr = decompressRL7Row(d, header->width());
3002
8.59k
        } else {
3003
4.74k
            rr = d->read(read);
3004
4.74k
        }
3005
3006
13.3k
        if (header->model() == IHDRChunk::CLut4) {
3007
0
            if (rr.size() < header->width() / 2) {
3008
0
                return {};
3009
0
            }
3010
0
            QByteArray tmp(header->width(), char());
3011
0
            for (auto x = 0, w = header->width(); x < w; ++x) {
3012
0
                auto i8 = quint8(rr.at(x / 2));
3013
0
                tmp[x] = x & 1 ? i8 & 0xF : (i8 >> 4) & 0xF;
3014
0
            }
3015
0
            rr = tmp;
3016
0
        }
3017
3018
13.3k
        if (header->model() == IHDRChunk::Rgb555) {
3019
308k
            for (qint32 x = 0, w = rr.size() - 1; x < w; x += 2) {
3020
308k
                std::swap(rr[x], rr[x + 1]);
3021
308k
            }
3022
151
        }
3023
3024
13.3k
        if (header->model() == IHDRChunk::DYuv) {
3025
4.12k
            if (rr.size() < header->width()) {
3026
23
                return {};
3027
23
            }
3028
3029
            // delta table: Green Book Cap. V Par. 3.4.1.3
3030
            // NOTE 1: using the wrong delta table creates visible artifacts on the image.
3031
            // NOTE 2: using { 0, 1, 4, 9, 16, 27, 44, 79, -128, -79, -44, -27, -16, -9, -4, -1 }
3032
            //         table gives the same result (when assigned to an uint8).
3033
4.10k
            static const qint32 deltaTable[16] = {
3034
4.10k
                0, 1, 4, 9, 16, 27, 44, 79, 128, 177, 212, 229, 240, 247, 252, 255
3035
4.10k
            };
3036
3037
4.10k
            auto yuv = header->yuvStart();
3038
4.10k
            if (header->yuvKind() == IHDRChunk::Each && yuvs) {
3039
2.37k
                yuv = yuvs->yuvStart(y);
3040
2.37k
            }
3041
3042
4.10k
            QByteArray tmp(header->width() * 3, char());
3043
443k
            for (auto x = 0, w = header->width() - 1; x < w; x += 2) {
3044
                // nibble order from Green Book Cap. V Par. 6.5.1.1
3045
                // NOTE: using the wrong nibble order creates visible artifacts on the image.
3046
439k
                const auto du = deltaTable[(rr.at(x) >> 4) & 0x0F];
3047
439k
                const auto d1 = deltaTable[rr.at(x) & 0x0F];
3048
439k
                const auto dv = deltaTable[(rr.at(x + 1) >> 4) & 0x0F];
3049
439k
                const auto d2 = deltaTable[rr.at(x + 1) & 0x0F];
3050
3051
                // pixel 1
3052
439k
                yuv.y = d1 + yuv.y;
3053
439k
                yuv.u = du + yuv.u;
3054
439k
                yuv.v = dv + yuv.v;
3055
439k
                auto rgb = yuvToRgb(yuv);
3056
439k
                tmp[x * 3] = rgb.r;
3057
439k
                tmp[x * 3 + 1] = rgb.g;
3058
439k
                tmp[x * 3 + 2] = rgb.b;
3059
3060
                // pixel 2
3061
439k
                yuv.y = d2 + yuv.y;
3062
439k
                rgb = yuvToRgb(yuv);
3063
439k
                tmp[(x + 1) * 3] = rgb.r;
3064
439k
                tmp[(x + 1) * 3 + 1] = rgb.g;
3065
439k
                tmp[(x + 1) * 3 + 2] = rgb.b;
3066
439k
            }
3067
4.10k
            rr = tmp;
3068
4.10k
        }
3069
3070
13.3k
        return rr;
3071
13.3k
    }
3072
3073
140
    return {};
3074
13.4k
}
3075
3076
bool IDATChunk::resetStrideRead(QIODevice *d) const
3077
279
{
3078
279
    return seek(d);
3079
279
}
3080
3081
quint32 IDATChunk::strideSize(const IHDRChunk *header) const
3082
13.4k
{
3083
13.4k
    if (header == nullptr) {
3084
0
        return 0;
3085
0
    }
3086
3087
13.4k
    auto rs = (header->width() * header->depth() + 7) / 8;
3088
3089
    // No padding bytes are inserted in the data.
3090
13.4k
    if (header->model() == IHDRChunk::Rgb888) {
3091
220
        return rs;
3092
220
    }
3093
3094
    // The first pixel of each scan line must begin in a longword boundary.
3095
13.2k
    if (auto mod = rs % 4)
3096
806
        rs += (4 - mod);
3097
13.2k
    return rs;
3098
13.4k
}
3099
3100
3101
/* ******************
3102
 * *** RGHD Chunk ***
3103
 * ****************** */
3104
3105
RGHDChunk::~RGHDChunk()
3106
{
3107
3108
}
3109
3110
RGHDChunk::RGHDChunk()
3111
5.76k
{
3112
3113
5.76k
}
3114
3115
bool RGHDChunk::isValid() const
3116
49.4k
{
3117
49.4k
    return dataBytes() >= 13 * sizeof(quint32) && chunkId() == RGHDChunk::defaultChunkId();
3118
49.4k
}
3119
3120
QSize RGHDChunk::size() const
3121
455
{
3122
455
    return QSize(width(), height());
3123
455
}
3124
3125
qint32 RGHDChunk::leftEdge() const
3126
0
{
3127
0
    if (!isValid()) {
3128
0
        return 0;
3129
0
    }
3130
0
    return i32(data(), 0);
3131
0
}
3132
3133
qint32 RGHDChunk::topEdge() const
3134
0
{
3135
0
    if (!isValid()) {
3136
0
        return 0;
3137
0
    }
3138
0
    return i32(data(), 4);
3139
0
}
3140
3141
qint32 RGHDChunk::width() const
3142
18.0k
{
3143
18.0k
    if (!isValid()) {
3144
53
        return 0;
3145
53
    }
3146
17.9k
    return i32(data(), 8);
3147
18.0k
}
3148
3149
qint32 RGHDChunk::height() const
3150
455
{
3151
455
    if (!isValid()) {
3152
53
        return 0;
3153
53
    }
3154
402
    return i32(data(), 12);
3155
455
}
3156
3157
qint32 RGHDChunk::pageWidth() const
3158
0
{
3159
0
    if (!isValid()) {
3160
0
        return 0;
3161
0
    }
3162
0
    return i32(data(), 16);
3163
0
}
3164
3165
qint32 RGHDChunk::pageHeight() const
3166
0
{
3167
0
    if (!isValid()) {
3168
0
        return 0;
3169
0
    }
3170
0
    return i32(data(), 20);
3171
0
}
3172
3173
quint32 RGHDChunk::depth() const
3174
0
{
3175
0
    if (!isValid()) {
3176
0
        return 0;
3177
0
    }
3178
0
    return ui32(data(), 24);
3179
0
}
3180
3181
quint32 RGHDChunk::pixelBits() const
3182
0
{
3183
0
    if (!isValid()) {
3184
0
        return 0;
3185
0
    }
3186
0
    return ui32(data(), 28);
3187
0
}
3188
3189
quint32 RGHDChunk::bytesPerLine() const
3190
0
{
3191
0
    if (!isValid()) {
3192
0
        return 0;
3193
0
    }
3194
0
    return ui32(data(), 32);
3195
0
}
3196
3197
RGHDChunk::Compression RGHDChunk::compression() const
3198
5.87k
{
3199
5.87k
    if (!isValid()) {
3200
0
        return Compression::Uncompressed;
3201
0
    }
3202
5.87k
    return Compression(ui32(data(), 36));
3203
5.87k
}
3204
3205
quint32 RGHDChunk::xAspect() const
3206
146
{
3207
146
    if (!isValid()) {
3208
7
        return 0;
3209
7
    }
3210
139
    return ui32(data(), 40);
3211
146
}
3212
3213
quint32 RGHDChunk::yAspect() const
3214
136
{
3215
136
    if (!isValid()) {
3216
0
        return 0;
3217
0
    }
3218
136
    return ui32(data(), 44);
3219
136
}
3220
3221
double RGHDChunk::aspectRatio() const
3222
146
{
3223
146
    if (auto xr = xAspect()) {
3224
136
        auto yr = yAspect();
3225
136
        return double(yr) / double(xr);
3226
136
    }
3227
10
    return 1;
3228
146
}
3229
3230
RGHDChunk::BitmapTypes RGHDChunk::bitmapType() const
3231
24.7k
{
3232
24.7k
    if (!isValid()) {
3233
891
        return BitmapType::Planar8;
3234
891
    }
3235
23.8k
    return BitmapTypes(ui32(data(), 48));
3236
24.7k
}
3237
3238
bool RGHDChunk::innerReadStructure(QIODevice *d)
3239
5.39k
{
3240
5.39k
    return cacheData(d);
3241
5.39k
}
3242
3243
3244
/* ******************
3245
 * *** RCOL Chunk ***
3246
 * ****************** */
3247
3248
RCOLChunk::~RCOLChunk()
3249
{
3250
3251
}
3252
3253
RCOLChunk::RCOLChunk()
3254
617
{
3255
3256
617
}
3257
3258
bool RCOLChunk::isValid() const
3259
63
{
3260
63
    return dataBytes() >= 776 && chunkId() == RCOLChunk::defaultChunkId();
3261
63
}
3262
3263
qint32 RCOLChunk::count() const
3264
30
{
3265
30
    return isValid() ? 256 : 0;
3266
30
}
3267
3268
QList<QRgb> RCOLChunk::innerPalette() const
3269
33
{
3270
33
    if (!isValid()) {
3271
3
        return {};
3272
3
    }
3273
3274
30
    QList<QRgb> l;
3275
30
    auto &&d = data();
3276
7.71k
    for (qint32 i = 0, n = count(); i < n; ++i) {
3277
7.68k
        auto i3 = i * 3 + 8;
3278
7.68k
        l << qRgb(d.at(i3), d.at(i3 + 1), d.at(i3 + 2));
3279
7.68k
    }
3280
3281
30
    if (ui32(data(), 0)) {
3282
22
        auto tr = ui32(data(), 4);
3283
22
        if (tr < l.size()) {
3284
9
            l[tr] &= 0x00FFFFFF;
3285
9
        }
3286
22
    }
3287
3288
30
    return l;
3289
33
}
3290
3291
3292
/* ******************
3293
 * *** RFLG Chunk ***
3294
 * ****************** */
3295
3296
RFLGChunk::~RFLGChunk()
3297
{
3298
3299
}
3300
3301
RFLGChunk::RFLGChunk()
3302
823
{
3303
3304
823
}
3305
3306
bool RFLGChunk::isValid() const
3307
0
{
3308
0
    return dataBytes() >= 4 && chunkId() == RFLGChunk::defaultChunkId();
3309
0
}
3310
3311
RFLGChunk::Flags RFLGChunk::flags() const
3312
0
{
3313
0
    if (!isValid()) {
3314
0
        return {};
3315
0
    }
3316
0
    return Flags(ui32(data(), 0));
3317
0
}
3318
3319
bool RFLGChunk::innerReadStructure(QIODevice *d)
3320
790
{
3321
790
    return cacheData(d);
3322
790
}
3323
3324
3325
/* ******************
3326
 * *** RSCM Chunk ***
3327
 * ****************** */
3328
3329
RSCMChunk::~RSCMChunk()
3330
{
3331
3332
}
3333
3334
RSCMChunk::RSCMChunk()
3335
1.55k
{
3336
3337
1.55k
}
3338
3339
bool RSCMChunk::isValid() const
3340
0
{
3341
0
    return dataBytes() >= 12 && chunkId() == RSCMChunk::defaultChunkId();
3342
0
}
3343
3344
quint32 RSCMChunk::viewMode() const
3345
0
{
3346
0
    if (!isValid()) {
3347
0
        return 0;
3348
0
    }
3349
0
    return ui32(data(), 0);
3350
0
}
3351
3352
quint32 RSCMChunk::localVM0() const
3353
0
{
3354
0
    if (!isValid()) {
3355
0
        return 0;
3356
0
    }
3357
0
    return ui32(data(), 4);
3358
0
}
3359
3360
quint32 RSCMChunk::localVM1() const
3361
0
{
3362
0
    if (!isValid()) {
3363
0
        return 0;
3364
0
    }
3365
0
    return ui32(data(), 8);
3366
0
}
3367
3368
bool RSCMChunk::innerReadStructure(QIODevice *d)
3369
1.21k
{
3370
1.21k
    return cacheData(d);
3371
1.21k
}
3372
3373
3374
/* ******************
3375
 * *** RBOD Chunk ***
3376
 * ****************** */
3377
3378
RBODChunk::~RBODChunk()
3379
{
3380
3381
}
3382
3383
RBODChunk::RBODChunk()
3384
2.24k
{
3385
3386
2.24k
}
3387
3388
bool RBODChunk::isValid() const
3389
5.97k
{
3390
5.97k
    return chunkId() == RBODChunk::defaultChunkId();
3391
5.97k
}
3392
3393
QByteArray RBODChunk::strideRead(QIODevice *d, qint32 y, const RGHDChunk *header, const RSCMChunk *rcsm, const RCOLChunk *rcol) const
3394
5.97k
{
3395
5.97k
    if (!isValid() || header == nullptr || d == nullptr) {
3396
0
        return {};
3397
0
    }
3398
3399
5.97k
    QByteArray planes;
3400
5.97k
    auto readSize = strideSize(header);
3401
11.7k
    for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && planes.size() < readSize;) {
3402
5.85k
        if (header->compression() == RGHDChunk::Compression::Uncompressed) {
3403
5.84k
            planes = d->read(readSize);
3404
5.84k
        } else {
3405
15
            qCDebug(LOG_IFFPLUGIN) << "RBODChunk::strideRead(): unknown compression" << header->compression();
3406
15
        }
3407
5.85k
        if (planes.size() != readSize) {
3408
84
            return {};
3409
84
        }
3410
5.85k
    }
3411
3412
5.88k
    return deinterleave(planes, y, header, rcsm, rcol);
3413
5.97k
}
3414
3415
bool RBODChunk::resetStrideRead(QIODevice *d) const
3416
283
{
3417
283
    return seek(d);
3418
283
}
3419
3420
QByteArray RBODChunk::deinterleave(const QByteArray &planes, qint32 y, const RGHDChunk *header, const RSCMChunk *rcsm, const RCOLChunk *rcol) const
3421
5.88k
{
3422
5.88k
    Q_UNUSED(y)
3423
5.88k
    Q_UNUSED(rcsm)
3424
5.88k
    Q_UNUSED(rcol)
3425
5.88k
    if (planes.size() != strideSize(header)) {
3426
57
        return {};
3427
57
    }
3428
3429
5.82k
    QByteArray ba;
3430
3431
5.82k
    auto width = header->width();
3432
5.82k
    auto rgfx_format = RGHDChunk::BitmapTypes(header->bitmapType() & 0x3FFFFFFF);
3433
3434
5.82k
    if (rgfx_format == RGHDChunk::BitmapType::Chunky8) {
3435
2.70k
        ba = planes;
3436
3.12k
    } else if (rgfx_format == RGHDChunk::BitmapType::Planar8) {
3437
        // No test case: ignoring...
3438
3.12k
    } else if (rgfx_format == RGHDChunk::BitmapType::Rgb15 || rgfx_format == RGHDChunk::BitmapType::Rgb16) {
3439
811
        ba = planes;
3440
811
        if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
3441
13.3k
            for (qint32 x = 0; x < width; ++x) {
3442
12.5k
                auto x2 = x * 2;
3443
12.5k
                ba[x2] = planes[x2 + 1];
3444
12.5k
                ba[x2 + 1] = planes[x2];
3445
12.5k
            }
3446
811
        }
3447
2.31k
    } else if (rgfx_format == RGHDChunk::BitmapType::Rgb24) {
3448
316
        ba = planes;
3449
1.99k
    } else if (rgfx_format == RGHDChunk::BitmapType::Rgb32) {
3450
304
        ba.resize(planes.size());
3451
304
        auto invAlpha = header->bitmapType() & RGHDChunk::BitmapType::HasInvAlpha;
3452
34.2k
        for (qint32 x = 0; x < width; ++x) {
3453
33.9k
            auto x4 = x * 4;
3454
33.9k
            ba[x4] = planes[x4 + 1];
3455
33.9k
            ba[x4 + 1] = planes[x4 + 2];
3456
33.9k
            ba[x4 + 2] = planes[x4 + 3];
3457
33.9k
            ba[x4 + 3] = invAlpha ? 255 - planes[x4] : planes[x4];
3458
33.9k
        }
3459
1.69k
    } else if (rgfx_format == RGHDChunk::BitmapType::Rgb48) {
3460
335
        QDataStream in(planes);
3461
335
        in.setByteOrder(QDataStream::BigEndian);
3462
335
        QDataStream ou(&ba, QDataStream::WriteOnly);
3463
335
        ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder));
3464
3465
335
        quint16 r, g, b;
3466
4.51k
        for (qint32 x = 0; x < width; ++x) {
3467
4.17k
            in >> r >> g >> b;
3468
4.17k
            ou << r << g << b << quint16(0xFFFF);
3469
4.17k
        }
3470
3471
335
        if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) {
3472
0
            return {};
3473
0
        }
3474
1.35k
    } else if (rgfx_format == RGHDChunk::BitmapType::Rgb64) {
3475
488
        QDataStream in(planes);
3476
488
        in.setByteOrder(QDataStream::BigEndian);
3477
488
        QDataStream ou(&ba, QDataStream::WriteOnly);
3478
488
        ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder));
3479
3480
488
        auto invAlpha = header->bitmapType() & RGHDChunk::BitmapType::HasInvAlpha;
3481
488
        quint16 r, g, b, a;
3482
32.5k
        for (qint32 x = 0; x < width; ++x) {
3483
32.0k
            in >> a >> r >> g >> b;
3484
32.0k
            if (invAlpha)
3485
10.3k
                a = 0xFFFF - a;
3486
32.0k
            ou << r << g << b << a;
3487
32.0k
        }
3488
3489
488
        if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) {
3490
0
            return {};
3491
0
        }
3492
867
    } else if (rgfx_format == RGHDChunk::BitmapType::Rgb96) {
3493
396
        QDataStream in(planes);
3494
396
        in.setByteOrder(QDataStream::BigEndian);
3495
396
        in.setFloatingPointPrecision(QDataStream::SinglePrecision);
3496
396
        QDataStream ou(&ba, QDataStream::WriteOnly);
3497
396
        ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder));
3498
396
        ou.setFloatingPointPrecision(QDataStream::SinglePrecision);
3499
3500
396
        float r, g, b;
3501
5.00k
        for (qint32 x = 0; x < width; ++x) {
3502
4.60k
            in >> r >> g >> b;
3503
4.60k
            ou << r << g << b << float(1);
3504
4.60k
        }
3505
3506
396
        if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) {
3507
0
            return {};
3508
0
        }
3509
471
    } else if (rgfx_format == RGHDChunk::BitmapType::Rgb128) {
3510
415
        QDataStream in(planes);
3511
415
        in.setByteOrder(QDataStream::BigEndian);
3512
415
        in.setFloatingPointPrecision(QDataStream::SinglePrecision);
3513
415
        QDataStream ou(&ba, QDataStream::WriteOnly);
3514
415
        ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder));
3515
415
        ou.setFloatingPointPrecision(QDataStream::SinglePrecision);
3516
3517
415
        auto invAlpha = header->bitmapType() & RGHDChunk::BitmapType::HasInvAlpha;
3518
415
        float r, g, b, a;
3519
1.81k
        for (qint32 x = 0; x < width; ++x) {
3520
1.40k
            in >> a >> r >> g >> b;
3521
1.40k
            if (invAlpha)
3522
644
                a = 1 - a;
3523
1.40k
            ou << r << g << b << a;
3524
1.40k
        }
3525
3526
415
        if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) {
3527
0
            return {};
3528
0
        }
3529
415
    }
3530
3531
5.82k
    return ba;
3532
5.82k
}
3533
3534
quint32 RBODChunk::strideSize(const RGHDChunk *header) const
3535
11.8k
{
3536
11.8k
    auto rgfx_format = RGHDChunk::BitmapTypes(header->bitmapType() & 0x3FFFFFFF);
3537
11.8k
    if (rgfx_format == RGHDChunk::BitmapType::Planar8) {
3538
6
        return (header->width() + 7) / 8;
3539
6
    }
3540
11.8k
    if (rgfx_format == RGHDChunk::BitmapType::Chunky8) {
3541
5.43k
        return header->width();
3542
5.43k
    }
3543
6.41k
    if (rgfx_format == RGHDChunk::BitmapType::Rgb15 || rgfx_format == RGHDChunk::BitmapType::Rgb16) {
3544
1.66k
        return header->width() * 2;
3545
1.66k
    }
3546
4.74k
    if (rgfx_format == RGHDChunk::BitmapType::Rgb24) {
3547
642
        return header->width() * 3;
3548
642
    }
3549
4.10k
    if (rgfx_format == RGHDChunk::BitmapType::Rgb32) {
3550
662
        return header->width() * 4;
3551
662
    }
3552
3.44k
    if (rgfx_format == RGHDChunk::BitmapType::Rgb48) {
3553
691
        return header->width() * 6;
3554
691
    }
3555
2.75k
    if (rgfx_format == RGHDChunk::BitmapType::Rgb64) {
3556
990
        return header->width() * 8;
3557
990
    }
3558
1.76k
    if (rgfx_format == RGHDChunk::BitmapType::Rgb96) {
3559
814
        return header->width() * 12;
3560
814
    }
3561
949
    if (rgfx_format == RGHDChunk::BitmapType::Rgb128) {
3562
837
        return header->width() * 16;
3563
837
    }
3564
112
    return 0;
3565
949
}
3566
3567
3568
/* ******************
3569
 * *** BEAM Chunk ***
3570
 * ****************** */
3571
3572
BEAMChunk::~BEAMChunk()
3573
19.6k
{
3574
3575
19.6k
}
3576
3577
BEAMChunk::BEAMChunk()
3578
19.4k
    : IPALChunk()
3579
    , _height()
3580
19.4k
{
3581
3582
19.4k
}
3583
3584
bool BEAMChunk::isValid() const
3585
126
{
3586
126
    return chunkId() == BEAMChunk::defaultChunkId();
3587
126
}
3588
3589
IPALChunk *BEAMChunk::clone() const
3590
194
{
3591
194
    return new BEAMChunk(*this);
3592
194
}
3593
3594
bool BEAMChunk::initialize(const QList<QRgb> &, qint32 height)
3595
194
{
3596
194
    _height = height;
3597
194
    return true;
3598
194
}
3599
3600
QList<QRgb> BEAMChunk::palette(qint32 y) const
3601
1.25M
{
3602
1.25M
    auto &&height = _height;
3603
1.25M
    if (height < 1) {
3604
0
        return {};
3605
0
    }
3606
1.25M
    auto bpp = bytes() / height;
3607
1.25M
    if (bytes() != height * bpp) {
3608
1.09M
        return {};
3609
1.09M
    }
3610
164k
    auto col = qint32(bpp / 2);
3611
164k
    auto &&dt = data();
3612
164k
    QList<QRgb> pal;
3613
174k
    for (auto c = 0; c < col; ++c) {
3614
        // 2 bytes per color (0x0R 0xGB)
3615
9.91k
        auto idx = bpp * y + c * 2;
3616
9.91k
        if (idx + 1 < dt.size()) {
3617
9.63k
            auto r = quint8(dt[idx] & 0x0F);
3618
9.63k
            auto g = quint8(dt[idx + 1] & 0xF0);
3619
9.63k
            auto b = quint8(dt[idx + 1] & 0x0F);
3620
9.63k
            pal << qRgb(r | (r << 4), (g >> 4) | g, b | (b << 4));
3621
9.63k
        }
3622
9.91k
    }
3623
164k
    return pal;
3624
1.25M
}
3625
3626
bool BEAMChunk::innerReadStructure(QIODevice *d)
3627
11.4k
{
3628
11.4k
    return cacheData(d);
3629
11.4k
}
3630
3631
3632
/* ******************
3633
 * *** CTBL Chunk ***
3634
 * ****************** */
3635
3636
CTBLChunk::~CTBLChunk()
3637
{
3638
3639
}
3640
3641
10.4k
CTBLChunk::CTBLChunk() : BEAMChunk()
3642
10.4k
{
3643
3644
10.4k
}
3645
3646
bool CTBLChunk::isValid() const
3647
68
{
3648
68
    return chunkId() == CTBLChunk::defaultChunkId();
3649
68
}
3650
3651
3652
/* ******************
3653
 * *** SHAM Chunk ***
3654
 * ****************** */
3655
3656
SHAMChunk::~SHAMChunk()
3657
7.12k
{
3658
3659
7.12k
}
3660
3661
SHAMChunk::SHAMChunk()
3662
7.08k
    : IPALChunk()
3663
    , _height()
3664
7.08k
{
3665
3666
7.08k
}
3667
3668
bool SHAMChunk::isValid() const
3669
108
{
3670
108
    if (dataBytes() < 2) {
3671
23
        return false;
3672
23
    }
3673
85
    auto &&dt = data();
3674
85
    if (dt[0] != 0 && dt[1] != 0) {
3675
        // In all the sham test cases I have them at zero...
3676
        // if they are different from zero I suppose they should
3677
        // be interpreted differently from what was done.
3678
41
        return false;
3679
41
    }
3680
44
    return chunkId() == SHAMChunk::defaultChunkId();
3681
85
}
3682
3683
IPALChunk *SHAMChunk::clone() const
3684
44
{
3685
44
    return new SHAMChunk(*this);
3686
44
}
3687
3688
QList<QRgb> SHAMChunk::palette(qint32 y) const
3689
775k
{
3690
775k
    auto && height = _height;
3691
775k
    if (height < 1) {
3692
0
        return {};
3693
0
    }
3694
775k
    auto bpp = 32; // always 32 bytes per palette (16 colors)
3695
775k
    auto div = 0;
3696
775k
    if (bytes() == quint32(height * bpp + 2)) {
3697
6
        div = 1;
3698
775k
    } else if (bytes() == quint32(height / 2 * bpp + 2)) {
3699
15
        div = 2;
3700
15
    }
3701
775k
    if (div == 0) {
3702
775k
        return {};
3703
775k
    }
3704
21
    auto &&dt = data();
3705
21
    QList<QRgb> pal;
3706
357
    for (auto c = 0, col = bpp / 2, idx0 = y / div * bpp + 2; c < col; ++c) {
3707
        // 2 bytes per color (0x0R 0xGB)
3708
336
        auto idx = idx0 + c * 2;
3709
336
        if (idx + 1 < dt.size()) {
3710
288
            auto r = quint8(dt[idx] & 0x0F);
3711
288
            auto g = quint8(dt[idx + 1] & 0xF0);
3712
288
            auto b = quint8(dt[idx + 1] & 0x0F);
3713
288
            pal << qRgb(r | (r << 4), (g >> 4) | g, b | (b << 4));
3714
288
        }
3715
336
    }
3716
21
    return pal;
3717
775k
}
3718
3719
bool SHAMChunk::initialize(const QList<QRgb> &, qint32 height)
3720
44
{
3721
44
    _height = height;
3722
44
    return true;
3723
44
}
3724
3725
bool SHAMChunk::innerReadStructure(QIODevice *d)
3726
3.85k
{
3727
3.85k
    return cacheData(d);
3728
3.85k
}
3729
3730
/* ******************
3731
 * *** RAST Chunk ***
3732
 * ****************** */
3733
3734
RASTChunk::~RASTChunk()
3735
20.7k
{
3736
3737
20.7k
}
3738
3739
RASTChunk::RASTChunk()
3740
20.4k
    : IPALChunk()
3741
    , _height()
3742
20.4k
{
3743
3744
20.4k
}
3745
3746
bool RASTChunk::isValid() const
3747
273
{
3748
273
    return chunkId() == RASTChunk::defaultChunkId();
3749
273
}
3750
3751
IPALChunk *RASTChunk::clone() const
3752
258
{
3753
258
    return new RASTChunk(*this);
3754
258
}
3755
3756
QList<QRgb> RASTChunk::palette(qint32 y) const
3757
1.00M
{
3758
1.00M
    auto &&height = _height;
3759
1.00M
    if (height < 1) {
3760
0
        return {};
3761
0
    }
3762
1.00M
    auto bpp = bytes() / height;
3763
1.00M
    if (bytes() != height * bpp) {
3764
878k
        return {};
3765
878k
    }
3766
127k
    auto col = qint32(bpp / 2 - 1);
3767
127k
    auto &&dt = data();
3768
127k
    QList<QRgb> pal;
3769
214k
    for (auto c = 0; c < col; ++c) {
3770
87.1k
        auto idx = bpp * y + 2 + c * 2;
3771
87.1k
        if (idx + 1 < dt.size()) {
3772
            // The Atari ST uses 3 bits per color (512 colors) while the Atari STE
3773
            // uses 4 bits per color (4096 colors). This strange encoding with the
3774
            // least significant bit set as MSB is, I believe, to ensure hardware
3775
            // compatibility between the two machines.
3776
260k
            #define H1L(a) ((quint8(a) & 0x7) << 1) | ((quint8(a) >> 3) & 1)
3777
86.9k
            auto r = H1L(dt[idx]);
3778
86.9k
            auto g = H1L(dt[idx + 1] >> 4);
3779
86.9k
            auto b = H1L(dt[idx + 1]);
3780
86.9k
            #undef H1L
3781
86.9k
            pal << qRgb(r | (r << 4), (g << 4) | g, b | (b << 4));
3782
86.9k
        }
3783
87.1k
    }
3784
127k
    return pal;
3785
1.00M
}
3786
3787
bool RASTChunk::initialize(const QList<QRgb> &, qint32 height)
3788
258
{
3789
258
    _height = height;
3790
258
    return true;
3791
258
}
3792
3793
bool RASTChunk::innerReadStructure(QIODevice *d)
3794
13.7k
{
3795
13.7k
    return cacheData(d);
3796
13.7k
}
3797
3798
/* ******************
3799
 * *** PCHG Chunk ***
3800
 * ****************** */
3801
3802
PCHGChunk::~PCHGChunk()
3803
9.87k
{
3804
9.87k
}
3805
3806
9.30k
PCHGChunk::PCHGChunk() : IPALChunk()
3807
9.30k
{
3808
3809
9.30k
}
3810
3811
PCHGChunk::Compression PCHGChunk::compression() const
3812
574
{
3813
574
    if (!isValid()) {
3814
0
        return Compression::Uncompressed;
3815
0
    }
3816
574
    return Compression(ui16(data(), 0));
3817
574
}
3818
3819
PCHGChunk::Flags PCHGChunk::flags() const
3820
453
{
3821
453
    if (!isValid()) {
3822
0
        return Flags(Flag::None);
3823
0
    }
3824
453
    return Flags(ui16(data(), 2));
3825
453
}
3826
3827
qint16 PCHGChunk::startLine() const
3828
347
{
3829
347
    if (!isValid()) {
3830
0
        return 0;
3831
0
    }
3832
347
    return i16(data(), 4);
3833
347
}
3834
3835
quint16 PCHGChunk::lineCount() const
3836
390
{
3837
390
    if (!isValid()) {
3838
0
        return 0;
3839
0
    }
3840
390
    return ui16(data(), 6);
3841
390
}
3842
3843
quint16 PCHGChunk::changedLines() const
3844
0
{
3845
0
    if (!isValid()) {
3846
0
        return 0;
3847
0
    }
3848
0
    return ui16(data(), 8);
3849
0
}
3850
3851
quint16 PCHGChunk::minReg() const
3852
0
{
3853
0
    if (!isValid()) {
3854
0
        return 0;
3855
0
    }
3856
0
    return ui16(data(), 10);
3857
0
}
3858
3859
quint16 PCHGChunk::maxReg() const
3860
0
{
3861
0
    if (!isValid()) {
3862
0
        return 0;
3863
0
    }
3864
0
    return ui16(data(), 12);
3865
0
}
3866
3867
quint16 PCHGChunk::maxChanges() const
3868
0
{
3869
0
    if (!isValid()) {
3870
0
        return 0;
3871
0
    }
3872
0
    return ui16(data(), 14);
3873
0
}
3874
3875
quint32 PCHGChunk::totalChanges() const
3876
192
{
3877
192
    if (!isValid()) {
3878
0
        return 0;
3879
0
    }
3880
192
    return ui32(data(), 16);
3881
192
}
3882
3883
bool PCHGChunk::hasAlpha() const
3884
106
{
3885
106
    return (flags() & PCHGChunk::Flag::UseAlpha) ? true : false;
3886
106
}
3887
3888
bool PCHGChunk::isValid() const
3889
2.58k
{
3890
2.58k
    if (dataBytes() < 20) {
3891
56
        return false;
3892
56
    }
3893
2.53k
    return chunkId() == PCHGChunk::defaultChunkId();
3894
2.58k
}
3895
3896
IPALChunk *PCHGChunk::clone() const
3897
574
{
3898
574
    return new PCHGChunk(*this);
3899
574
}
3900
3901
QList<QRgb> PCHGChunk::palette(qint32 y) const
3902
984k
{
3903
984k
    return _palettes.value(y);
3904
984k
}
3905
3906
// ----------------------------------------------------------------------------
3907
// PCHG_FastDecomp reimplementation (Amiga 68k -> portable C++/Qt)
3908
// ----------------------------------------------------------------------------
3909
// This mirrors the original 68k routine semantics:
3910
//   - The Huffman tree is stored as a sequence of signed 16-bit words (big-endian)
3911
//     and TreeCode points to the *last word* of that sequence.
3912
//   - Bits are consumed MSB-first from 32-bit big-endian longwords of the source.
3913
//   - Navigation rules (matching the assembly):
3914
//       bit=1:  read w = *(a3). If w < 0 then a3 += w (byte-wise) and continue;
3915
//               else emit (w & 0xFF) and reset a3 to TreeCode (last word).
3916
//       bit=0:  predecrement a3 by 2; read w = *a3. If w < 0: continue;
3917
//               else if (w & 0x0100) emit (w & 0xFF) and reset a3; else continue.
3918
//   - Stop after writing exactly OriginalSize bytes.
3919
//
3920
// This function expects a single QByteArray laid out as:
3921
//   [ tree (treeSize bytes, even) | compressed bitstream (... bytes) ]
3922
//
3923
// On any error, logs with qCCritical(LOG_IFFPLUGIN) and returns {}.
3924
// ----------------------------------------------------------------------------
3925
//
3926
// NOTE: Sebastiano Vigna, the author of the PCHG specification and the ASM
3927
//       decompression code for the Motorola 68K, gave us permission to use his
3928
//       code and recommended that we convert it with AI.
3929
3930
// Core decompressor (tree + compressed stream in one QByteArray)
3931
static QByteArray pchgFastDecomp(const QByteArray& input, int treeSize, int originalSize)
3932
185
{
3933
    // Read a big-endian 16-bit signed word from a byte buffer
3934
1.26M
    auto read_be16 = [&](const char* base, int byteIndex, int size) -> qint16 {
3935
1.26M
        if (byteIndex + 1 >= size)
3936
0
            return 0; // caller must bounds-check; we keep silent here
3937
1.26M
        const quint8 b0 = static_cast<quint8>(base[byteIndex]);
3938
1.26M
        const quint8 b1 = static_cast<quint8>(base[byteIndex + 1]);
3939
1.26M
        return static_cast<qint16>((b0 << 8) | b1);
3940
1.26M
    };
3941
3942
    // Read a big-endian 32-bit unsigned long from a byte buffer
3943
39.6k
    auto read_be32 = [&](const char* base, int byteIndex, int size) -> quint32 {
3944
39.6k
        if (byteIndex + 3 >= size)
3945
0
            return 0; // caller must bounds-check
3946
39.6k
        const quint8 b0 = static_cast<quint8>(base[byteIndex]);
3947
39.6k
        const quint8 b1 = static_cast<quint8>(base[byteIndex + 1]);
3948
39.6k
        const quint8 b2 = static_cast<quint8>(base[byteIndex + 2]);
3949
39.6k
        const quint8 b3 = static_cast<quint8>(base[byteIndex + 3]);
3950
39.6k
        return (static_cast<quint32>(b0) << 24) |
3951
39.6k
               (static_cast<quint32>(b1) << 16) |
3952
39.6k
               (static_cast<quint32>(b2) << 8)  |
3953
39.6k
                static_cast<quint32>(b3);
3954
39.6k
    };
3955
3956
    // Basic validation
3957
185
    if (treeSize <= 0 || (treeSize & 1)) {
3958
34
        qCCritical(LOG_IFFPLUGIN) << "Invalid treeSize (must be positive and even)" << treeSize;
3959
34
        return {};
3960
34
    }
3961
151
    if (input.size() < treeSize) {
3962
38
        qCCritical(LOG_IFFPLUGIN) << "Input too small for treeSize" << input.size() << treeSize;
3963
38
        return {};
3964
38
    }
3965
113
    if (originalSize < 0) {
3966
25
        qCCritical(LOG_IFFPLUGIN) << "Invalid originalSize" << originalSize;
3967
25
        return {};
3968
25
    }
3969
3970
88
    const char* data = input.constData();
3971
88
    const int totalSize = input.size();
3972
3973
    // Tree view (big-endian words)
3974
88
    const int treeBytes = treeSize;
3975
88
    const int treeWords = treeBytes / 2;
3976
88
    if (treeWords <= 0) {
3977
0
        qCCritical(LOG_IFFPLUGIN) << "Tree has zero words";
3978
0
        return {};
3979
0
    }
3980
3981
    // Compressed stream
3982
88
    const int srcBase = treeBytes; // offset where bitstream starts
3983
88
    const int srcSize = totalSize - srcBase;
3984
88
    if (srcSize <= 0 && originalSize > 0) {
3985
15
        qCCritical(LOG_IFFPLUGIN) << "No compressed payload present";
3986
15
        return {};
3987
15
    }
3988
3989
    // Emulate a3 pointer to words:
3990
    // a2 points to the *last word* => word index (0..treeWords-1)
3991
860k
    auto resetA3 = [&]() {
3992
860k
        return treeWords - 1; // last word index
3993
860k
    };
3994
73
    int a3_word = resetA3();
3995
3996
    // Bit reader: loads 32b big-endian and shifts MSB-first
3997
73
    quint32 bitbuf = 0;
3998
73
    int     bits   = 0;     // remaining bits in bitbuf
3999
73
    int     srcPos = 0;     // byte offset relative to srcBase
4000
4001
39.6k
    auto refill = [&]() -> bool {
4002
39.6k
        if (srcPos + 4 > srcSize) {
4003
34
            qCCritical(LOG_IFFPLUGIN) << "Compressed stream underflow while refilling bit buffer"
4004
34
                                      << "srcPos=" << srcPos << "srcSize=" << srcSize;
4005
34
            return false;
4006
34
        }
4007
39.6k
        bitbuf = read_be32(data + srcBase, srcPos, srcSize);
4008
39.6k
        bits   = 32;
4009
39.6k
        srcPos += 4;
4010
39.6k
        return true;
4011
39.6k
    };
4012
4013
    // Main decode loop: produce exactly originalSize bytes
4014
73
    QByteArray out;
4015
1.26M
    while (out.size() < qsizetype(originalSize)) {
4016
1.26M
        if (bits == 0) {
4017
39.6k
            if (!refill()) {
4018
                // Not enough bits to complete output
4019
34
                return {};
4020
34
            }
4021
39.6k
        }
4022
4023
1.26M
        const bool bit1 = (bitbuf & 0x80000000u) != 0u; // MSB before shift
4024
1.26M
        bitbuf <<= 1;
4025
1.26M
        --bits;
4026
4027
1.26M
        if (bit1) {
4028
            // Case bit == 1  --> w = *(a3)
4029
505k
            if (a3_word < 0 || a3_word >= treeWords) {
4030
0
                qCCritical(LOG_IFFPLUGIN) << "a3 out of bounds (bit=1)" << a3_word;
4031
0
                return {};
4032
0
            }
4033
505k
            const int byteIndex = a3_word * 2;
4034
505k
            const qint16 w = read_be16(data, byteIndex, treeBytes);
4035
4036
505k
            if (w < 0) {
4037
                // a3 += w  (w is a signed byte offset, must be even)
4038
340
                if (w & 1) {
4039
12
                    qCCritical(LOG_IFFPLUGIN) << "Misaligned tree offset (odd)" << w;
4040
12
                    return {};
4041
12
                }
4042
328
                const int deltaWords = w / 2; // arithmetic division, w is even in valid streams
4043
328
                const int next = a3_word + deltaWords;
4044
328
                if (next < 0 || next >= treeWords) {
4045
14
                    qCCritical(LOG_IFFPLUGIN) << "a3 out of bounds after offset" << next;
4046
14
                    return {};
4047
14
                }
4048
314
                a3_word = next;
4049
504k
            } else {
4050
                // Leaf: emit low 8 bits, reset a3
4051
504k
                out.append(static_cast<char>(w & 0xFF));
4052
504k
                a3_word = resetA3();
4053
504k
            }
4054
761k
        } else {
4055
            // Case bit == 0  --> w = *--a3 (predecrement)
4056
761k
            --a3_word;
4057
761k
            if (a3_word < 0) {
4058
9
                qCCritical(LOG_IFFPLUGIN) << "a3 underflow on predecrement";
4059
9
                return {};
4060
9
            }
4061
761k
            const int byteIndex = a3_word * 2;
4062
761k
            const qint16 w = read_be16(data, byteIndex, treeBytes);
4063
4064
761k
            if (w < 0) {
4065
                // Internal node: continue with current a3
4066
307
                continue;
4067
307
            }
4068
4069
            // Non-negative: check bit #8; if set -> leaf
4070
761k
            if ((w & 0x0100) != 0) {
4071
355k
                out.append(static_cast<char>(w & 0xFF));
4072
355k
                a3_word = resetA3();
4073
405k
            } else {
4074
                // Not a leaf: continue scanning
4075
405k
                continue;
4076
405k
            }
4077
761k
        }
4078
1.26M
    }
4079
4080
4
    return out;
4081
73
}
4082
// !Huffman decompression
4083
4084
bool PCHGChunk::initialize(const QList<QRgb> &cmapPalette, qint32 height)
4085
574
{
4086
574
    Q_UNUSED(height)
4087
574
    auto dt = data().mid(20);
4088
574
    if (compression() == PCHGChunk::Compression::Huffman) {
4089
185
        QDataStream ds(dt);
4090
185
        ds.setByteOrder(QDataStream::BigEndian);
4091
4092
185
        quint32 infoSize;
4093
185
        ds >> infoSize;
4094
185
        quint32 origSize;
4095
185
        ds >> origSize;
4096
4097
185
        dt = pchgFastDecomp(dt.mid(8), infoSize, origSize);
4098
185
    }
4099
574
    if (dt.isEmpty()) {
4100
184
        return false;
4101
184
    }
4102
4103
390
    QDataStream ds(dt);
4104
390
    ds.setByteOrder(QDataStream::BigEndian);
4105
4106
    // read the masks
4107
390
    auto lcnt = lineCount();
4108
390
    auto nlw = (lcnt + 31) / 32; // number of LWORD containing the bit mask
4109
390
    QList<quint32> masks;
4110
75.7k
    for (auto i = 0; i < nlw; ++i) {
4111
75.3k
        quint32 mask;
4112
75.3k
        ds >> mask;
4113
75.3k
        masks << mask;
4114
75.3k
    }
4115
390
    if (ds.status() != QDataStream::Ok) {
4116
43
        return false;
4117
43
    }
4118
4119
    // read the palettes
4120
347
    auto changesLoaded = qint64();
4121
347
    auto startY = startLine();
4122
347
    auto last = cmapPalette;
4123
347
    auto flgs = flags();
4124
605k
    for (auto i = 0; i < lcnt; ++i) {
4125
605k
        auto mask = masks.at(i / 32);
4126
605k
        if (((mask >> (31 - i % 32)) & 1) == 0) {
4127
372k
            _palettes.insert(i + startY, last);
4128
372k
            continue; // no palette change for this line
4129
372k
        }
4130
4131
233k
        QHash<quint16, QRgb> hash;
4132
233k
        if (flgs & PCHGChunk::Flag::F12Bit) {
4133
5.65k
            quint8 c16;
4134
5.65k
            ds >> c16;
4135
5.65k
            quint8 c32;
4136
5.65k
            ds >> c32;
4137
91.8k
            for (auto j = 0; j < int(c16); ++j) {
4138
86.2k
                quint16 tmp;
4139
86.2k
                ds >> tmp;
4140
86.2k
                hash.insert(((tmp >> 12) & 0xF), qRgb(((tmp >> 8) & 0xF) * 17, ((tmp >> 4) & 0xF) * 17, ((tmp & 0xF) * 17)));
4141
86.2k
            }
4142
86.2k
            for (auto j = 0; j < int(c32); ++j) {
4143
80.6k
                quint16 tmp;
4144
80.6k
                ds >> tmp;
4145
80.6k
                hash.insert((((tmp >> 12) & 0xF) + 16), qRgb(((tmp >> 8) & 0xF) * 17, ((tmp >> 4) & 0xF) * 17, ((tmp & 0xF) * 17)));
4146
80.6k
            }
4147
227k
        } else if (flgs & PCHGChunk::Flag::F32Bit) { // NOTE: missing test case (not tested)
4148
674
            quint16 cnt;
4149
674
            ds >> cnt;
4150
2.27M
            for (auto j = 0; j < int(cnt); ++j) {
4151
2.27M
                quint16 reg;
4152
2.27M
                ds >> reg;
4153
2.27M
                quint8 alpha;
4154
2.27M
                ds >> alpha;
4155
2.27M
                quint8 red;
4156
2.27M
                ds >> red;
4157
2.27M
                quint8 blue;
4158
2.27M
                ds >> blue;
4159
2.27M
                quint8 green;
4160
2.27M
                ds >> green;
4161
2.27M
                hash.insert(reg, qRgba(red, green, blue, flgs & PCHGChunk::Flag::UseAlpha ? alpha : 0xFF));
4162
2.27M
            }
4163
674
        }
4164
4165
233k
        if (ds.status() != QDataStream::Ok) {
4166
155
            return false;
4167
155
        }
4168
4169
109M
        for (auto i = qsizetype(), n = last.size(); i < n; ++i) {
4170
108M
            if (hash.contains(i))
4171
6.21k
                last[i] = hash.value(i);
4172
108M
        }
4173
4174
232k
        _palettes.insert(i + startY, last);
4175
232k
        changesLoaded += hash.size();
4176
232k
    }
4177
4178
192
    if (changesLoaded != qint64(totalChanges())) {
4179
181
        qCDebug(LOG_IFFPLUGIN) << "PCHGChunk::innerReadStructure(): palette changes count mismatch!";
4180
181
    }
4181
4182
192
    return true;
4183
347
}
4184
4185
bool PCHGChunk::innerReadStructure(QIODevice *d)
4186
5.00k
{
4187
5.00k
    return cacheData(d);
4188
5.00k
}