1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2025 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
11#include <QBuffer>
12#include <QColor>
13
14#ifdef QT_DEBUG
15Q_LOGGING_CATEGORY(LOG_IFFPLUGIN, "kf.imageformats.plugins.iff", QtDebugMsg)
16#else
17Q_LOGGING_CATEGORY(LOG_IFFPLUGIN, "kf.imageformats.plugins.iff", QtWarningMsg)
18#endif
19
20#define RECURSION_PROTECTION 10
21
22#define BITPLANES_HAM_MAX 8
23#define BITPLANES_HAM_MIN 5
24#define BITPLANES_HALFBRIDE_MAX 8
25#define BITPLANES_HALFBRIDE_MIN 1
26
27static QString dataToString(const IFFChunk *chunk)
28{
29 if (chunk == nullptr || !chunk->isValid()) {
30 return {};
31 }
32 auto dt = chunk->data();
33 for (; dt.endsWith(c: char()); dt = dt.removeLast());
34 return QString::fromUtf8(ba: dt).trimmed();
35}
36
37IFFChunk::~IFFChunk()
38{
39
40}
41
42IFFChunk::IFFChunk()
43 : _chunkId{0}
44 , _size{0}
45 , _align{2}
46 , _dataPos{0}
47 , _recursionCnt{0}
48{
49}
50
51bool IFFChunk::operator ==(const IFFChunk &other) const
52{
53 if (chunkId() != other.chunkId()) {
54 return false;
55 }
56 return _size == other._size && _dataPos == other._dataPos;
57}
58
59bool IFFChunk::isValid() const
60{
61 auto cid = chunkId();
62 if (cid.isEmpty()) {
63 return false;
64 }
65 // A “type ID”, “property name”, “FORM type”, or any other IFF
66 // identifier is a 32-bit value: the concatenation of four ASCII
67 // characters in the range “ ” (SP, hex 20) through “~” (hex 7E).
68 // Spaces (hex 20) should not precede printing characters;
69 // trailing spaces are OK. Control characters are forbidden.
70 if (cid.at(i: 0) == ' ') {
71 return false;
72 }
73 for (auto &&c : cid) {
74 if (c < ' ' || c > '~')
75 return false;
76 }
77 return true;
78}
79
80qint32 IFFChunk::alignBytes() const
81{
82 return _align;
83}
84
85bool IFFChunk::readStructure(QIODevice *d)
86{
87 auto ok = readInfo(d);
88 if (recursionCounter() > RECURSION_PROTECTION - 1) {
89 ok = ok && IFFChunk::innerReadStructure(d); // force default implementation (no more recursion)
90 } else {
91 ok = ok && innerReadStructure(d);
92 }
93 if (ok) {
94 ok = d->seek(pos: nextChunkPos());
95 }
96 return ok;
97}
98
99QByteArray IFFChunk::chunkId() const
100{
101 return QByteArray(_chunkId, 4);
102}
103
104quint32 IFFChunk::bytes() const
105{
106 return _size;
107}
108
109const QByteArray &IFFChunk::data() const
110{
111 return _data;
112}
113
114const IFFChunk::ChunkList &IFFChunk::chunks() const
115{
116 return _chunks;
117}
118
119quint8 IFFChunk::chunkVersion(const QByteArray &cid)
120{
121 if (cid.size() != 4) {
122 return 0;
123 }
124 if (cid.at(i: 3) >= char('2') && cid.at(i: 3) <= char('9')) {
125 return quint8(cid.at(i: 3) - char('0'));
126 }
127 return 1;
128}
129
130bool IFFChunk::isChunkType(const QByteArray &cid) const
131{
132 if (chunkId() == cid) {
133 return true;
134 }
135 if (chunkId().startsWith(bv: cid.left(n: 3)) && IFFChunk::chunkVersion(cid) > 1) {
136 return true;
137 }
138 return false;
139}
140
141bool IFFChunk::readInfo(QIODevice *d)
142{
143 if (d == nullptr || d->read(data: _chunkId, maxlen: 4) != 4) {
144 return false;
145 }
146 if (!IFFChunk::isValid()) {
147 return false;
148 }
149 auto sz = d->read(maxlen: 4);
150 if (sz.size() != 4) {
151 return false;
152 }
153 _size = ui32(c1: sz.at(i: 3), c2: sz.at(i: 2), c3: sz.at(i: 1), c4: sz.at(i: 0));
154 _dataPos = d->pos();
155 return true;
156}
157
158QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const
159{
160 if (!seek(d, relPos)) {
161 return{};
162 }
163 if (size == -1) {
164 size = _size;
165 }
166 auto read = std::min(a: size, b: _size - relPos);
167 return d->read(maxlen: read);
168}
169
170bool IFFChunk::seek(QIODevice *d, qint64 relPos) const
171{
172 if (d == nullptr) {
173 return false;
174 }
175 return d->seek(pos: _dataPos + relPos);
176}
177
178bool IFFChunk::innerReadStructure(QIODevice *)
179{
180 return true;
181}
182
183void IFFChunk::setAlignBytes(qint32 bytes)
184{
185 _align = bytes;
186}
187
188qint64 IFFChunk::nextChunkPos() const
189{
190 auto pos = _dataPos + _size;
191 if (auto align = pos % alignBytes())
192 pos += alignBytes() - align;
193 return pos;
194}
195
196IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const QSharedPointer<IFFChunk> &chunk)
197{
198 return search(cid, chunks: ChunkList() << chunk);
199}
200
201IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const ChunkList &chunks)
202{
203 IFFChunk::ChunkList list;
204 for (auto &&chunk : chunks) {
205 if (chunk->chunkId() == cid)
206 list << chunk;
207 list << IFFChunk::search(cid, chunks: chunk->_chunks);
208 }
209 return list;
210}
211
212bool IFFChunk::cacheData(QIODevice *d)
213{
214 if (bytes() > 8 * 1024 * 1024) {
215 return false;
216 }
217 _data = readRawData(d);
218 return _data.size() == _size;
219}
220
221void IFFChunk::setChunks(const ChunkList &chunks)
222{
223 _chunks = chunks;
224}
225
226qint32 IFFChunk::recursionCounter() const
227{
228 return _recursionCnt;
229}
230
231void IFFChunk::setRecursionCounter(qint32 cnt)
232{
233 _recursionCnt = cnt;
234}
235
236IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent)
237{
238 auto tmp = false;
239 if (ok == nullptr) {
240 ok = &tmp;
241 }
242 *ok = false;
243
244 if (d == nullptr) {
245 return {};
246 }
247
248 auto alignBytes = qint32(2);
249 auto recursionCnt = qint32();
250 auto nextChunkPos = qint64();
251 if (parent) {
252 alignBytes = parent->alignBytes();
253 recursionCnt = parent->recursionCounter();
254 nextChunkPos = parent->nextChunkPos();
255 }
256
257 if (recursionCnt > RECURSION_PROTECTION) {
258 return {};
259 }
260
261 IFFChunk::ChunkList list;
262 for (; !d->atEnd() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) {
263 auto cid = d->peek(maxlen: 4);
264 QSharedPointer<IFFChunk> chunk;
265 if (cid == ABIT_CHUNK) {
266 chunk = QSharedPointer<IFFChunk>(new ABITChunk());
267 } else if (cid == ANNO_CHUNK) {
268 chunk = QSharedPointer<IFFChunk>(new ANNOChunk());
269 } else if (cid == AUTH_CHUNK) {
270 chunk = QSharedPointer<IFFChunk>(new AUTHChunk());
271 } else if (cid == BEAM_CHUNK) {
272 chunk = QSharedPointer<IFFChunk>(new BEAMChunk());
273 } else if (cid == BMHD_CHUNK) {
274 chunk = QSharedPointer<IFFChunk>(new BMHDChunk());
275 } else if (cid == BODY_CHUNK) {
276 chunk = QSharedPointer<IFFChunk>(new BODYChunk());
277 } else if (cid == CAMG_CHUNK) {
278 chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
279 } else if (cid == CAT__CHUNK) {
280 chunk = QSharedPointer<IFFChunk>(new CATChunk());
281 } else if (cid == CMAP_CHUNK) {
282 chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
283 } else if (cid == CMYK_CHUNK) {
284 chunk = QSharedPointer<IFFChunk>(new CMYKChunk());
285 } else if (cid == COPY_CHUNK) {
286 chunk = QSharedPointer<IFFChunk>(new COPYChunk());
287 } else if (cid == CTBL_CHUNK) {
288 chunk = QSharedPointer<IFFChunk>(new CTBLChunk());
289 } else if (cid == DATE_CHUNK) {
290 chunk = QSharedPointer<IFFChunk>(new DATEChunk());
291 } else if (cid == DPI__CHUNK) {
292 chunk = QSharedPointer<IFFChunk>(new DPIChunk());
293 } else if (cid == EXIF_CHUNK) {
294 chunk = QSharedPointer<IFFChunk>(new EXIFChunk());
295 } else if (cid == FOR4_CHUNK) {
296 chunk = QSharedPointer<IFFChunk>(new FOR4Chunk());
297 } else if (cid == FORM_CHUNK) {
298 chunk = QSharedPointer<IFFChunk>(new FORMChunk());
299 } else if (cid == FVER_CHUNK) {
300 chunk = QSharedPointer<IFFChunk>(new FVERChunk());
301 } else if (cid == HIST_CHUNK) {
302 chunk = QSharedPointer<IFFChunk>(new HISTChunk());
303 } else if (cid == ICCN_CHUNK) {
304 chunk = QSharedPointer<IFFChunk>(new ICCNChunk());
305 } else if (cid == ICCP_CHUNK) {
306 chunk = QSharedPointer<IFFChunk>(new ICCPChunk());
307 } else if (cid == NAME_CHUNK) {
308 chunk = QSharedPointer<IFFChunk>(new NAMEChunk());
309 } else if (cid == RAST_CHUNK) {
310 chunk = QSharedPointer<IFFChunk>(new RASTChunk());
311 } else if (cid == RGBA_CHUNK) {
312 chunk = QSharedPointer<IFFChunk>(new RGBAChunk());
313 } else if (cid == SHAM_CHUNK) {
314 chunk = QSharedPointer<IFFChunk>(new SHAMChunk());
315 } else if (cid == TBHD_CHUNK) {
316 chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
317 } else if (cid == VERS_CHUNK) {
318 chunk = QSharedPointer<IFFChunk>(new VERSChunk());
319 } else if (cid == XMP0_CHUNK) {
320 chunk = QSharedPointer<IFFChunk>(new XMP0Chunk());
321 } else { // unknown chunk
322 chunk = QSharedPointer<IFFChunk>(new IFFChunk());
323 qCDebug(LOG_IFFPLUGIN) << "IFFChunk::innerFromDevice(): unknown chunk" << cid;
324 }
325
326 // change the alignment to the one of main chunk (required for unknown Maya IFF chunks)
327 if (chunk->isChunkType(CAT__CHUNK)
328 || chunk->isChunkType(FILL_CHUNK)
329 || chunk->isChunkType(FORM_CHUNK)
330 || chunk->isChunkType(LIST_CHUNK)
331 || chunk->isChunkType(PROP_CHUNK)) {
332 alignBytes = chunk->alignBytes();
333 } else {
334 chunk->setAlignBytes(alignBytes);
335 }
336
337 chunk->setRecursionCounter(recursionCnt + 1);
338 if (!chunk->readStructure(d)) {
339 *ok = false;
340 return {};
341 }
342
343 // skip any non-IFF data at the end of the file.
344 // NOTE: there should be no more chunks after the first (root)
345 if (nextChunkPos == 0) {
346 nextChunkPos = chunk->nextChunkPos();
347 }
348
349 list << chunk;
350 }
351
352 *ok = true;
353 return list;
354}
355
356IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok)
357{
358 return innerFromDevice(d, ok, parent: nullptr);
359}
360
361
362/* ******************
363 * *** BMHD Chunk ***
364 * ****************** */
365
366BMHDChunk::~BMHDChunk()
367{
368
369}
370
371BMHDChunk::BMHDChunk() : IFFChunk()
372{
373}
374
375bool BMHDChunk::isValid() const
376{
377 if (bytes() < 20) {
378 return false;
379 }
380 return chunkId() == BMHDChunk::defaultChunkId();
381}
382
383bool BMHDChunk::innerReadStructure(QIODevice *d)
384{
385 return cacheData(d);
386}
387
388qint32 BMHDChunk::width() const
389{
390 if (!isValid()) {
391 return 0;
392 }
393 return qint32(ui16(c1: data().at(i: 1), c2: data().at(i: 0)));
394}
395
396qint32 BMHDChunk::height() const
397{
398 if (!isValid()) {
399 return 0;
400 }
401 return qint32(ui16(c1: data().at(i: 3), c2: data().at(i: 2)));
402}
403
404QSize BMHDChunk::size() const
405{
406 return QSize(width(), height());
407}
408
409qint32 BMHDChunk::left() const
410{
411 if (!isValid()) {
412 return 0;
413 }
414 return qint32(ui16(c1: data().at(i: 5), c2: data().at(i: 4)));
415}
416
417qint32 BMHDChunk::top() const
418{
419 if (!isValid()) {
420 return 0;
421 }
422 return qint32(ui16(c1: data().at(i: 7), c2: data().at(i: 6)));
423}
424
425quint8 BMHDChunk::bitplanes() const
426{
427 if (!isValid()) {
428 return 0;
429 }
430 return quint8(data().at(i: 8));
431}
432
433BMHDChunk::Masking BMHDChunk::masking() const
434{
435 if (!isValid()) {
436 return BMHDChunk::Masking::None;
437 }
438 return BMHDChunk::Masking(quint8(data().at(i: 9)));
439}
440
441BMHDChunk::Compression BMHDChunk::compression() const
442{
443 if (!isValid()) {
444 return BMHDChunk::Compression::Uncompressed;
445 }
446 return BMHDChunk::Compression(data().at(i: 10));
447
448}
449
450qint16 BMHDChunk::transparency() const
451{
452 if (!isValid()) {
453 return 0;
454 }
455 return i16(c1: data().at(i: 13), c2: data().at(i: 12));
456}
457
458quint8 BMHDChunk::xAspectRatio() const
459{
460 if (!isValid()) {
461 return 0;
462 }
463 return quint8(data().at(i: 14));
464}
465
466quint8 BMHDChunk::yAspectRatio() const
467{
468 if (!isValid()) {
469 return 0;
470 }
471 return quint8(data().at(i: 15));
472}
473
474quint16 BMHDChunk::pageWidth() const
475{
476 if (!isValid()) {
477 return 0;
478 }
479 return ui16(c1: data().at(i: 17), c2: data().at(i: 16));
480}
481
482quint16 BMHDChunk::pageHeight() const
483{
484 if (!isValid()) {
485 return 0;
486 }
487 return ui16(c1: data().at(i: 19), c2: data().at(i: 18));
488}
489
490quint32 BMHDChunk::rowLen() const
491{
492 return ((quint32(width()) + 15) / 16) * 2;
493}
494
495/* ******************
496 * *** CMAP Chunk ***
497 * ****************** */
498
499CMAPChunk::~CMAPChunk()
500{
501
502}
503
504CMAPChunk::CMAPChunk() : IFFChunk()
505{
506}
507
508bool CMAPChunk::isValid() const
509{
510 return chunkId() == CMAPChunk::defaultChunkId();
511}
512
513qint32 CMAPChunk::count() const
514{
515 if (!isValid()) {
516 return 0;
517 }
518 return bytes() / 3;
519}
520
521QList<QRgb> CMAPChunk::palette(bool halfbride) const
522{
523 auto p = innerPalette();
524 if (!halfbride) {
525 return p;
526 }
527 auto tmp = p;
528 for (auto &&v : tmp) {
529 p << qRgb(r: qRed(rgb: v) / 2, g: qGreen(rgb: v) / 2, b: qBlue(rgb: v) / 2);
530 }
531 return p;
532}
533
534bool CMAPChunk::innerReadStructure(QIODevice *d)
535{
536 return cacheData(d);
537}
538
539QList<QRgb> CMAPChunk::innerPalette() const
540{
541 QList<QRgb> l;
542 auto &&d = data();
543 for (qint32 i = 0, n = count(); i < n; ++i) {
544 auto i3 = i * 3;
545 l << qRgb(r: d.at(i: i3), g: d.at(i: i3 + 1), b: d.at(i: i3 + 2));
546 }
547 return l;
548}
549
550
551/* ******************
552 * *** CMYK Chunk ***
553 * ****************** */
554
555CMYKChunk::~CMYKChunk()
556{
557
558}
559
560CMYKChunk::CMYKChunk() : CMAPChunk()
561{
562
563}
564
565bool CMYKChunk::isValid() const
566{
567 return chunkId() == CMYKChunk::defaultChunkId();
568}
569
570qint32 CMYKChunk::count() const
571{
572 if (!isValid()) {
573 return 0;
574 }
575 return bytes() / 4;
576}
577
578QList<QRgb> CMYKChunk::innerPalette() const
579{
580 QList<QRgb> l;
581 auto &&d = data();
582 for (qint32 i = 0, n = count(); i < n; ++i) {
583 auto i4 = i * 4;
584 auto C = quint8(d.at(i: i4)) / 255.;
585 auto M = quint8(d.at(i: i4 + 1)) / 255.;
586 auto Y = quint8(d.at(i: i4 + 2)) / 255.;
587 auto K = quint8(d.at(i: i4 + 3)) / 255.;
588 l << QColor::fromCmykF(c: C, m: M, y: Y, k: K).toRgb().rgb();
589 }
590 return l;
591}
592
593
594/* ******************
595 * *** CAMG Chunk ***
596 * ****************** */
597
598CAMGChunk::~CAMGChunk()
599{
600
601}
602
603CAMGChunk::CAMGChunk() : IFFChunk()
604{
605}
606
607bool CAMGChunk::isValid() const
608{
609 if (bytes() != 4) {
610 return false;
611 }
612 return chunkId() == CAMGChunk::defaultChunkId();
613}
614
615CAMGChunk::ModeIds CAMGChunk::modeId() const
616{
617 if (!isValid()) {
618 return CAMGChunk::ModeIds();
619 }
620 return CAMGChunk::ModeIds(ui32(c1: data().at(i: 3), c2: data().at(i: 2), c3: data().at(i: 1), c4: data().at(i: 0)));
621}
622
623bool CAMGChunk::innerReadStructure(QIODevice *d)
624{
625 return cacheData(d);
626}
627
628/* ******************
629 * *** DPI Chunk ***
630 * ****************** */
631
632DPIChunk::~DPIChunk()
633{
634
635}
636
637DPIChunk::DPIChunk() : IFFChunk()
638{
639}
640
641bool DPIChunk::isValid() const
642{
643 if (dpiX() == 0 || dpiY() == 0) {
644 return false;
645 }
646 return chunkId() == DPIChunk::defaultChunkId();
647}
648
649quint16 DPIChunk::dpiX() const
650{
651 if (bytes() < 4) {
652 return 0;
653 }
654 return i16(c1: data().at(i: 1), c2: data().at(i: 0));
655}
656
657quint16 DPIChunk::dpiY() const
658{
659 if (bytes() < 4) {
660 return 0;
661 }
662 return i16(c1: data().at(i: 3), c2: data().at(i: 2));
663}
664
665qint32 DPIChunk::dotsPerMeterX() const
666{
667 return qRound(d: dpiX() / 25.4 * 1000);
668}
669
670qint32 DPIChunk::dotsPerMeterY() const
671{
672 return qRound(d: dpiY() / 25.4 * 1000);
673}
674
675bool DPIChunk::innerReadStructure(QIODevice *d)
676{
677 return cacheData(d);
678}
679
680/* ******************
681 * *** BODY Chunk ***
682 * ****************** */
683
684BODYChunk::~BODYChunk()
685{
686
687}
688
689BODYChunk::BODYChunk() : IFFChunk()
690{
691}
692
693bool BODYChunk::isValid() const
694{
695 return chunkId() == BODYChunk::defaultChunkId();
696}
697
698// For each RGB value, a LONG-word (32 bits) is written:
699// with the 24 RGB bits in the MSB positions; the "genlock"
700// bit next, and then a 7 bit repeat count.
701//
702// See also: https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data
703inline qint64 rgb8Decompress(QIODevice *input, char *output, qint64 olen)
704{
705 qint64 j = 0;
706 for (qint64 available = olen; j < olen; available = olen - j) {
707 auto pos = input->pos();
708 auto ba4 = input->read(maxlen: 4);
709 if (ba4.size() != 4) {
710 break;
711 }
712 auto cnt = qint32(ba4.at(i: 3) & 0x7F);
713 if (cnt * 3 > available) {
714 if (!input->seek(pos))
715 return -1;
716 break;
717 }
718 for (qint32 i = 0; i < cnt; ++i) {
719 output[j++] = ba4.at(i: 0);
720 output[j++] = ba4.at(i: 1);
721 output[j++] = ba4.at(i: 2);
722 }
723 }
724 return j;
725}
726
727// For each RGB value, a WORD (16-bits) is written: with the
728// 12 RGB bits in the MSB (most significant bit) positions;
729// the "genlock" bit next; and then a 3 bit repeat count.
730// If the repeat count is greater than 7, the 3-bit count is
731// zero, and a BYTE repeat count follows. If the repeat count
732// is greater than 255, the BYTE count is zero, and a WORD
733// repeat count follows. Repeat counts greater than 65536 are
734// not supported.
735//
736// See also: https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data
737inline qint32 rgbnCount(QIODevice *input, quint8 &R, quint8& G, quint8 &B)
738{
739 auto ba2 = input->read(maxlen: 2);
740 if (ba2.size() != 2)
741 return 0;
742
743 R = ba2.at(i: 0) & 0xF0;
744 R = R | (R >> 4);
745
746 G = ba2.at(i: 0) & 0x0F;
747 G = G | (G << 4);
748
749 B = ba2.at(i: 1) & 0xF0;
750 B = B | (B >> 4);
751
752 auto cnt = ba2.at(i: 1) & 7;
753 if (cnt == 0) {
754 auto ba1 = input->read(maxlen: 1);
755 if (ba1.size() != 1)
756 return 0;
757 cnt = quint8(ba1.at(i: 0));
758 }
759 if (cnt == 0) {
760 auto baw = input->read(maxlen: 2);
761 if (baw.size() != 2)
762 return 0;
763 cnt = qint32(quint8(baw.at(i: 0))) << 8 | quint8(baw.at(i: 1));
764 }
765
766 return cnt;
767}
768
769inline qint64 rgbNDecompress(QIODevice *input, char *output, qint64 olen)
770{
771 qint64 j = 0;
772 for (qint64 available = olen; j < olen; available = olen - j) {
773 quint8 R = 0, G = 0, B = 0;
774 auto pos = input->pos();
775 auto cnt = rgbnCount(input, R, G, B);
776 if (cnt * 3 > available || cnt == 0) {
777 if (!input->seek(pos))
778 return -1;
779 break;
780 }
781 for (qint32 i = 0; i < cnt; ++i) {
782 output[j++] = R;
783 output[j++] = G;
784 output[j++] = B;
785 }
786 }
787 return j;
788}
789
790QByteArray BODYChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal, const QByteArray& formType) const
791{
792 if (!isValid() || header == nullptr || d == nullptr) {
793 return {};
794 }
795
796 auto isRgbN = formType == RGBN_FORM_TYPE;
797 auto isRgb8 = formType == RGB8_FORM_TYPE;
798 auto isPbm = formType == PBM__FORM_TYPE;
799 auto lineCompressed = isRgbN || isRgb8 ? false : true;
800 auto readSize = strideSize(header, formType);
801 auto bufSize = readSize;
802 if (isRgbN) {
803 bufSize = std::max(a: quint32(65536 * 3), b: readSize);
804 }
805 if (isRgb8) {
806 bufSize = std::max(a: quint32(127 * 3), b: readSize);
807 }
808 for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && _readBuffer.size() < readSize;) {
809 QByteArray buf(bufSize, char());
810 qint64 rr = -1;
811 if (header->compression() == BMHDChunk::Compression::Rle) {
812 // WARNING: The online spec says it's the same as TIFF but that's
813 // not accurate: the RLE -128 code is not a noop.
814 rr = packbitsDecompress(input: d, output: buf.data(), olen: buf.size(), allowN128: true);
815 } else if (header->compression() == BMHDChunk::Compression::RgbN8) {
816 if (isRgb8)
817 rr = rgb8Decompress(input: d, output: buf.data(), olen: buf.size());
818 else if (isRgbN)
819 rr = rgbNDecompress(input: d, output: buf.data(), olen: buf.size());
820 } else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
821 rr = d->read(data: buf.data(), maxlen: buf.size()); // never seen
822 } else {
823 qCDebug(LOG_IFFPLUGIN) << "BODYChunk::strideRead(): unknown compression" << header->compression();
824 }
825 if ((rr != readSize && lineCompressed) || (rr < 1))
826 return {};
827 _readBuffer.append(s: buf.data(), len: rr);
828 }
829
830 auto planes = _readBuffer.left(n: readSize);
831 _readBuffer.remove(index: 0, len: readSize);
832 if (isPbm) {
833 return pbm(planes, y, header, camg, cmap, ipal);
834 }
835 if (isRgb8) {
836 return rgb8(planes, y, header, camg, cmap, ipal);
837 }
838 if (isRgbN) {
839 return rgbN(planes, y, header, camg, cmap, ipal);
840 }
841 return deinterleave(planes, y, header, camg, cmap, ipal);
842}
843
844bool BODYChunk::resetStrideRead(QIODevice *d) const
845{
846 _readBuffer.clear();
847 return seek(d);
848}
849
850CAMGChunk::ModeIds BODYChunk::safeModeId(const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap)
851{
852 if (camg) {
853 return camg->modeId();
854 }
855 if (header == nullptr) {
856 return CAMGChunk::ModeIds();
857 }
858 auto cmapCount = cmap ? cmap->count() : 0;
859 auto bitplanes = header->bitplanes();
860 if (bitplanes >= BITPLANES_HALFBRIDE_MIN && bitplanes <= BITPLANES_HALFBRIDE_MAX) {
861 if (cmapCount == (1 << (header->bitplanes() - 1)))
862 return CAMGChunk::ModeIds(CAMGChunk::ModeId::HalfBrite);
863 }
864 if (bitplanes >= BITPLANES_HAM_MIN && bitplanes <= BITPLANES_HAM_MAX) {
865 if (cmapCount == (1 << (header->bitplanes() - 2)))
866 return CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham);
867 }
868 return CAMGChunk::ModeIds();
869}
870
871quint32 BODYChunk::strideSize(const BMHDChunk *header, const QByteArray& formType) const
872{
873 // RGB8 / RGBN
874 if (formType == RGB8_FORM_TYPE || formType == RGBN_FORM_TYPE) {
875 return header->width() * 3;
876 }
877
878 // PBM
879 if (formType == PBM__FORM_TYPE) {
880 auto rs = header->width() * header->bitplanes() / 8;
881 if (rs & 1)
882 ++rs;
883 return rs;
884 }
885
886 // ILBM
887 return header->rowLen() * header->bitplanes();
888}
889
890QByteArray BODYChunk::pbm(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
891{
892 if (planes.size() != strideSize(header, PBM__FORM_TYPE)) {
893 return {};
894 }
895 if (header->bitplanes() == 8) {
896 // The data are contiguous.
897 return planes;
898 }
899 return {};
900}
901
902QByteArray BODYChunk::rgb8(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
903{
904 if (planes.size() != strideSize(header, RGB8_FORM_TYPE)) {
905 return {};
906 }
907 return planes;
908}
909
910QByteArray BODYChunk::rgbN(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
911{
912 if (planes.size() != strideSize(header, RGBN_FORM_TYPE)) {
913 return {};
914 }
915 return planes;
916}
917
918QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal) const
919{
920 if (planes.size() != strideSize(header, ILBM_FORM_TYPE)) {
921 return {};
922 }
923
924 auto rowLen = qint32(header->rowLen());
925 auto bitplanes = header->bitplanes();
926 auto modeId = BODYChunk::safeModeId(header, camg, cmap);
927
928 QByteArray ba;
929 switch (bitplanes) {
930 case 1: // gray, indexed and rgb Ham mode
931 case 2:
932 case 3:
933 case 4:
934 case 5:
935 case 6:
936 case 7:
937 case 8:
938 if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) &&
939 (bitplanes >= BITPLANES_HAM_MIN && bitplanes <= BITPLANES_HAM_MAX)) {
940 // From A Quick Introduction to IFF.txt:
941 //
942 // Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
943 // In HAM mode, the bits in the two last planes describe an R G or B
944 // modification to the color of the previous pixel on the line to create the
945 // color of the current pixel. So a 6-plane HAM picture has 4 planes for
946 // specifying absolute color pixels giving up to 16 absolute colors which would
947 // be specified in the ILBM CMAP chunk. The bits in the last two planes are
948 // color modification bits which cause the Amiga, in HAM mode, to take the RGB
949 // value of the previous pixel (Hold and), substitute the 4 bits in planes 0-3
950 // for the previous color's R G or B component (Modify) and display the result
951 // for the current pixel. If the first pixel of a scan line is a modification
952 // pixel, it modifies the RGB value of the border color (register 0). The color
953 // modification bits in the last two planes (planes 4 and 5) are interpreted as
954 // follows:
955 // 00 - no modification. Use planes 0-3 as normal color register index
956 // 10 - hold previous, replacing Blue component with bits from planes 0-3
957 // 01 - hold previous, replacing Red component with bits from planes 0-3
958 // 11 - hold previous. replacing Green component with bits from planes 0-3
959 ba = QByteArray(rowLen * 8 * 3, char());
960 auto pal = cmap->palette();
961 if (ipal) {
962 auto tmp = ipal->palette(y, height: header->height());
963 if (tmp.size() == pal.size())
964 pal = tmp;
965 }
966 auto max = (1 << (bitplanes - 2)) - 1;
967 quint8 prev[3] = {};
968 for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
969 for (qint32 j = 0; j < 8; ++j, ++cnt) {
970 quint8 idx = 0, ctl = 0;
971 for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
972 if ((planes.at(i: k * rowLen + i) & msk) == 0)
973 continue;
974 if (k < bitplanes - 2)
975 idx |= 1 << k;
976 else
977 ctl |= 1 << (bitplanes - k - 1);
978 }
979 switch (ctl) {
980 case 1: // red
981 prev[0] = idx * 255 / max;
982 break;
983 case 2: // blue
984 prev[2] = idx * 255 / max;
985 break;
986 case 3: // green
987 prev[1] = idx * 255 / max;
988 break;
989 default:
990 if (idx < pal.size()) {
991 prev[0] = qRed(rgb: pal.at(i: idx));
992 prev[1] = qGreen(rgb: pal.at(i: idx));
993 prev[2] = qBlue(rgb: pal.at(i: idx));
994 } else {
995 qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
996 }
997 break;
998 }
999 auto cnt3 = cnt * 3;
1000 ba[cnt3] = char(prev[0]);
1001 ba[cnt3 + 1] = char(prev[1]);
1002 ba[cnt3 + 2] = char(prev[2]);
1003 }
1004 }
1005 } else if ((modeId & CAMGChunk::ModeId::HalfBrite) && (cmap) &&
1006 (bitplanes >= BITPLANES_HALFBRIDE_MIN && bitplanes <= BITPLANES_HALFBRIDE_MAX)) {
1007 // From A Quick Introduction to IFF.txt:
1008 //
1009 // In HALFBRITE mode, the Amiga interprets the bit in the
1010 // last plane as HALFBRITE modification. The bits in the other planes are
1011 // treated as normal color register numbers (RGB values for each color register
1012 // is specified in the CMAP chunk). If the bit in the last plane is set (1),
1013 // then that pixel is displayed at half brightness. This can provide up to 64
1014 // absolute colors.
1015 ba = QByteArray(rowLen * 8, char());
1016 auto palSize = cmap->count();
1017 for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
1018 for (qint32 j = 0; j < 8; ++j, ++cnt) {
1019 quint8 idx = 0, ctl = 0;
1020 for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
1021 if ((planes.at(i: k * rowLen + i) & msk) == 0)
1022 continue;
1023 if (k < bitplanes - 1)
1024 idx |= 1 << k;
1025 else
1026 ctl = 1;
1027 }
1028 if (idx < palSize) {
1029 ba[cnt] = ctl ? idx + palSize : idx;
1030 } else {
1031 qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
1032 }
1033 }
1034 }
1035 } else {
1036 // From A Quick Introduction to IFF.txt:
1037 //
1038 // If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if
1039 // necessary, you will have N planes of pixel data. Color register used for
1040 // each pixel is specified by looking at each pixel thru the planes. I.e.,
1041 // if you have 5 planes, and the bit for a particular pixel is set in planes
1042 // 0 and 3:
1043 //
1044 // PLANE 4 3 2 1 0
1045 // PIXEL 0 1 0 0 1
1046 //
1047 // then that pixel uses color register binary 01001 = 9
1048 ba = QByteArray(rowLen * 8, char());
1049 for (qint32 i = 0; i < rowLen; ++i) {
1050 for (qint32 k = 0, i8 = i * 8; k < bitplanes; ++k) {
1051 auto v = planes.at(i: k * rowLen + i);
1052 if (v & (1 << 7))
1053 ba[i8] |= 1 << k;
1054 if (v & (1 << 6))
1055 ba[i8 + 1] |= 1 << k;
1056 if (v & (1 << 5))
1057 ba[i8 + 2] |= 1 << k;
1058 if (v & (1 << 4))
1059 ba[i8 + 3] |= 1 << k;
1060 if (v & (1 << 3))
1061 ba[i8 + 4] |= 1 << k;
1062 if (v & (1 << 2))
1063 ba[i8 + 5] |= 1 << k;
1064 if (v & (1 << 1))
1065 ba[i8 + 6] |= 1 << k;
1066 if (v & 1)
1067 ba[i8 + 7] |= 1 << k;
1068 }
1069 }
1070 }
1071 break;
1072
1073 case 24: // rgb
1074 case 32: // rgba (SView5 extension)
1075 // From A Quick Introduction to IFF.txt:
1076 //
1077 // If a deep ILBM (like 12 or 24 planes), there should be no CMAP
1078 // and instead the BODY planes are interpreted as the bits of RGB
1079 // in the order R0...Rn G0...Gn B0...Bn
1080 //
1081 // NOTE: This code does not support 12-planes images
1082 ba = QByteArray(rowLen * bitplanes, char());
1083 for (qint32 i = 0, cnt = 0, p = bitplanes / 8; i < rowLen; ++i) {
1084 for (qint32 j = 0; j < 8; ++j)
1085 for (qint32 k = 0; k < p; ++k, ++cnt) {
1086 auto k8 = k * 8;
1087 auto msk = (1 << (7 - j));
1088 if (planes.at(i: k8 * rowLen + i) & msk)
1089 ba[cnt] |= 0x01;
1090 if (planes.at(i: (1 + k8) * rowLen + i) & msk)
1091 ba[cnt] |= 0x02;
1092 if (planes.at(i: (2 + k8) * rowLen + i) & msk)
1093 ba[cnt] |= 0x04;
1094 if (planes.at(i: (3 + k8) * rowLen + i) & msk)
1095 ba[cnt] |= 0x08;
1096 if (planes.at(i: (4 + k8) * rowLen + i) & msk)
1097 ba[cnt] |= 0x10;
1098 if (planes.at(i: (5 + k8) * rowLen + i) & msk)
1099 ba[cnt] |= 0x20;
1100 if (planes.at(i: (6 + k8) * rowLen + i) & msk)
1101 ba[cnt] |= 0x40;
1102 if (planes.at(i: (7 + k8) * rowLen + i) & msk)
1103 ba[cnt] |= 0x80;
1104 }
1105 }
1106 break;
1107
1108 case 48: // rgb (SView5 extension)
1109 case 64: // rgba (SView5 extension)
1110 // From https://aminet.net/package/docs/misc/ILBM64:
1111 //
1112 // Previously, the IFF-ILBM fileformat has been
1113 // extended two times already, for 24 bit and 32 bit
1114 // image data:
1115 //
1116 // 24 bit -> 24 planes composing RGB 8:8:8 true color
1117 // 32 bit -> 32 planes composing RGBA 8:8:8:8 true color
1118 // plus alpha
1119 //
1120 // The former extension quickly became a common one,
1121 // while the latter until recently mainly had been
1122 // used by some NewTek software.
1123 //
1124 // Now the following - as a consequent logical extension
1125 // of the previously mentioned definitions - is introduced
1126 // by SView5-Library:
1127 //
1128 // 48 bit -> 48 planes composing RGB 16:16:16 true color
1129 // 64 bit -> 64 planes composing RGBA 16:16:16:16 true color
1130 // plus alpha
1131 //
1132 // The resulting data is intended to allow direct transformation
1133 // from the PNG format into the Amiga (ILBM) bitmap format.
1134
1135 ba = QByteArray(rowLen * 64, char()); // the RGBX QT format is 64-bits
1136 const qint32 order[] = { 1, 0, 3, 2, 5, 4, 7, 6 };
1137 for (qint32 i = 0, cnt = 0, p = bitplanes / 8; i < rowLen; ++i) {
1138 for (qint32 j = 0; j < 8; ++j, cnt += 8) {
1139 for (qint32 k = 0; k < p; ++k) {
1140 auto k8 = k * 8;
1141 auto msk = (1 << (7 - j));
1142 auto idx = cnt + order[k];
1143 if (planes.at(i: k8 * rowLen + i) & msk)
1144 ba[idx] |= 0x01;
1145 if (planes.at(i: (1 + k8) * rowLen + i) & msk)
1146 ba[idx] |= 0x02;
1147 if (planes.at(i: (2 + k8) * rowLen + i) & msk)
1148 ba[idx] |= 0x04;
1149 if (planes.at(i: (3 + k8) * rowLen + i) & msk)
1150 ba[idx] |= 0x08;
1151 if (planes.at(i: (4 + k8) * rowLen + i) & msk)
1152 ba[idx] |= 0x10;
1153 if (planes.at(i: (5 + k8) * rowLen + i) & msk)
1154 ba[idx] |= 0x20;
1155 if (planes.at(i: (6 + k8) * rowLen + i) & msk)
1156 ba[idx] |= 0x40;
1157 if (planes.at(i: (7 + k8) * rowLen + i) & msk)
1158 ba[idx] |= 0x80;
1159 }
1160 if (p == 6) { // RGBX wants unused X data set to 0xFF
1161 ba[cnt + 6] = char(0xFF);
1162 ba[cnt + 7] = char(0xFF);
1163 }
1164 }
1165 }
1166 break;
1167 }
1168 return ba;
1169}
1170
1171/* ******************
1172 * *** ABIT Chunk ***
1173 * ****************** */
1174
1175ABITChunk::~ABITChunk()
1176{
1177
1178}
1179
1180ABITChunk::ABITChunk() : BODYChunk()
1181{
1182
1183}
1184
1185bool ABITChunk::isValid() const
1186{
1187 return chunkId() == ABITChunk::defaultChunkId();
1188}
1189
1190QByteArray ABITChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal, const QByteArray& formType) const
1191{
1192 if (!isValid() || header == nullptr || d == nullptr) {
1193 return {};
1194 }
1195 if (header->compression() != BMHDChunk::Compression::Uncompressed || formType != ACBM_FORM_TYPE) {
1196 return {};
1197 }
1198
1199 // convert ABIT data to an ILBM line on the fly
1200 auto ilbmLine = QByteArray(strideSize(header, formType), char());
1201 auto rowSize = header->rowLen();
1202 auto height = header->height();
1203 if (y >= height) {
1204 return {};
1205 }
1206 for (qint32 plane = 0, planes = qint32(header->bitplanes()); plane < planes; ++plane) {
1207 if (!seek(d, relPos: qint64(plane) * rowSize * height + y * rowSize))
1208 return {};
1209 auto offset = qint64(plane) * rowSize;
1210 if (offset + rowSize > ilbmLine.size())
1211 return {};
1212 if (d->read(data: ilbmLine.data() + offset, maxlen: rowSize) != rowSize)
1213 return {};
1214 }
1215
1216 // decode the ILBM line
1217 QBuffer buf;
1218 buf.setData(ilbmLine);
1219 if (!buf.open(openMode: QBuffer::ReadOnly)) {
1220 return {};
1221 }
1222 return BODYChunk::strideRead(d: &buf, y, header, camg, cmap, ipal, ILBM_FORM_TYPE);
1223}
1224
1225bool ABITChunk::resetStrideRead(QIODevice *d) const
1226{
1227 return BODYChunk::resetStrideRead(d);
1228}
1229
1230
1231/* **********************
1232 * *** FORM Interface ***
1233 * ********************** */
1234
1235IFOR_Chunk::~IFOR_Chunk()
1236{
1237
1238}
1239
1240IFOR_Chunk::IFOR_Chunk() : IFFChunk()
1241{
1242
1243}
1244
1245QImageIOHandler::Transformation IFOR_Chunk::transformation() const
1246{
1247 auto exifs = IFFChunk::searchT<EXIFChunk>(chunks: chunks());
1248 if (!exifs.isEmpty()) {
1249 auto exif = exifs.first()->value();
1250 if (!exif.isEmpty())
1251 return exif.transformation();
1252 }
1253 return QImageIOHandler::Transformation::TransformationNone;
1254}
1255
1256QImage::Format IFOR_Chunk::optionformat() const
1257{
1258 auto fmt = this->format();
1259 if (fmt == QImage::Format_Indexed8) {
1260 if (searchIPal())
1261 fmt = FORMAT_RGB_8BIT;
1262 }
1263 return fmt;
1264}
1265
1266const IPALChunk *IFOR_Chunk::searchIPal() const
1267{
1268 const IPALChunk *ipal = nullptr;
1269 auto beam = IFFChunk::searchT<BEAMChunk>(chunk: this);
1270 if (!beam.isEmpty()) {
1271 ipal = beam.first();
1272 }
1273 auto ctbl = IFFChunk::searchT<CTBLChunk>(chunk: this);
1274 if (!ctbl.isEmpty()) {
1275 ipal = ctbl.first();
1276 }
1277 auto sham = IFFChunk::searchT<SHAMChunk>(chunk: this);
1278 if (!sham.isEmpty()) {
1279 ipal = sham.first();
1280 }
1281 auto rast = IFFChunk::searchT<RASTChunk>(chunk: this);
1282 if (!rast.isEmpty()) {
1283 ipal = rast.first();
1284 }
1285 if (ipal && ipal->isValid()) {
1286 return ipal;
1287 }
1288 return nullptr;
1289}
1290
1291
1292/* ******************
1293 * *** FORM Chunk ***
1294 * ****************** */
1295
1296FORMChunk::~FORMChunk()
1297{
1298
1299}
1300
1301FORMChunk::FORMChunk() : IFOR_Chunk()
1302{
1303}
1304
1305bool FORMChunk::isValid() const
1306{
1307 return chunkId() == FORMChunk::defaultChunkId();
1308}
1309
1310bool FORMChunk::isSupported() const
1311{
1312 return format() != QImage::Format_Invalid;
1313}
1314
1315bool FORMChunk::innerReadStructure(QIODevice *d)
1316{
1317 if (bytes() < 4) {
1318 return false;
1319 }
1320 _type = d->read(maxlen: 4);
1321 auto ok = true;
1322
1323 // NOTE: add new supported type to CATChunk as well.
1324 if (_type == ILBM_FORM_TYPE) {
1325 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1326 } else if (_type == PBM__FORM_TYPE) {
1327 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1328 } else if (_type == ACBM_FORM_TYPE) {
1329 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1330 } else if (_type == RGB8_FORM_TYPE) {
1331 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1332 } else if (_type == RGBN_FORM_TYPE) {
1333 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1334 }
1335 return ok;
1336}
1337
1338QByteArray FORMChunk::formType() const
1339{
1340 return _type;
1341}
1342
1343QImage::Format FORMChunk::format() const
1344{
1345 auto headers = IFFChunk::searchT<BMHDChunk>(chunks: chunks());
1346 if (headers.isEmpty()) {
1347 return QImage::Format_Invalid;
1348 }
1349
1350 if (auto &&h = headers.first()) {
1351 auto cmaps = IFFChunk::searchT<CMAPChunk>(chunks: chunks());
1352 if (cmaps.isEmpty()) {
1353 auto cmyks = IFFChunk::searchT<CMYKChunk>(chunks: chunks());
1354 for (auto &&cmyk : cmyks)
1355 cmaps.append(t: cmyk);
1356 }
1357 auto camgs = IFFChunk::searchT<CAMGChunk>(chunks: chunks());
1358 auto modeId = BODYChunk::safeModeId(header: h, camg: camgs.isEmpty() ? nullptr : camgs.first(), cmap: cmaps.isEmpty() ? nullptr : cmaps.first());
1359 if (h->bitplanes() == 13) {
1360 return FORMAT_RGB_8BIT; // NOTE: with a little work you could use Format_RGB444
1361 }
1362 if (h->bitplanes() == 24 || h->bitplanes() == 25) {
1363 return FORMAT_RGB_8BIT;
1364 }
1365 if (h->bitplanes() == 48) {
1366 return QImage::Format_RGBX64;
1367 }
1368 if (h->bitplanes() == 32) {
1369 return QImage::Format_RGBA8888;
1370 }
1371 if (h->bitplanes() == 64) {
1372 return QImage::Format_RGBA64;
1373 }
1374 if (h->bitplanes() >= 1 && h->bitplanes() <= 8) {
1375 if (!IFFChunk::search(PCHG_CHUNK, chunks: chunks()).isEmpty()) {
1376 qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format(): PCHG chunk is not supported";
1377 return QImage::Format_Invalid;
1378 }
1379
1380 if (h->bitplanes() >= BITPLANES_HAM_MIN && h->bitplanes() <= BITPLANES_HAM_MAX) {
1381 if (modeId & CAMGChunk::ModeId::Ham)
1382 return FORMAT_RGB_8BIT;
1383 }
1384
1385 if (!cmaps.isEmpty()) {
1386 return QImage::Format_Indexed8;
1387 }
1388
1389 return QImage::Format_Grayscale8;
1390 }
1391 qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format(): Unsupported" << h->bitplanes() << "bitplanes";
1392 }
1393
1394 return QImage::Format_Invalid;
1395}
1396
1397QSize FORMChunk::size() const
1398{
1399 auto headers = IFFChunk::searchT<BMHDChunk>(chunks: chunks());
1400 if (headers.isEmpty()) {
1401 return {};
1402 }
1403 return headers.first()->size();
1404}
1405
1406/* ******************
1407 * *** FOR4 Chunk ***
1408 * ****************** */
1409
1410FOR4Chunk::~FOR4Chunk()
1411{
1412
1413}
1414
1415FOR4Chunk::FOR4Chunk() : IFOR_Chunk()
1416{
1417
1418}
1419
1420bool FOR4Chunk::isValid() const
1421{
1422 return chunkId() == FOR4Chunk::defaultChunkId();
1423}
1424
1425qint32 FOR4Chunk::alignBytes() const
1426{
1427 return 4;
1428}
1429
1430bool FOR4Chunk::isSupported() const
1431{
1432 return format() != QImage::Format_Invalid;
1433}
1434
1435bool FOR4Chunk::innerReadStructure(QIODevice *d)
1436{
1437 if (bytes() < 4) {
1438 return false;
1439 }
1440 _type = d->read(maxlen: 4);
1441 auto ok = true;
1442 if (_type == CIMG_FOR4_TYPE) {
1443 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1444 } else if (_type == TBMP_FOR4_TYPE) {
1445 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1446 }
1447 return ok;
1448}
1449
1450QByteArray FOR4Chunk::formType() const
1451{
1452 return _type;
1453}
1454
1455QImage::Format FOR4Chunk::format() const
1456{
1457 auto headers = IFFChunk::searchT<TBHDChunk>(chunks: chunks());
1458 if (headers.isEmpty()) {
1459 return QImage::Format_Invalid;
1460 }
1461 return headers.first()->format();
1462}
1463
1464QSize FOR4Chunk::size() const
1465{
1466 auto headers = IFFChunk::searchT<TBHDChunk>(chunks: chunks());
1467 if (headers.isEmpty()) {
1468 return {};
1469 }
1470 return headers.first()->size();
1471}
1472
1473/* ******************
1474 * *** CAT Chunk ***
1475 * ****************** */
1476
1477CATChunk::~CATChunk()
1478{
1479
1480}
1481
1482CATChunk::CATChunk() : IFFChunk()
1483{
1484
1485}
1486
1487bool CATChunk::isValid() const
1488{
1489 return chunkId() == CATChunk::defaultChunkId();
1490}
1491
1492QByteArray CATChunk::catType() const
1493{
1494 return _type;
1495}
1496
1497bool CATChunk::innerReadStructure(QIODevice *d)
1498{
1499 if (bytes() < 4) {
1500 return false;
1501 }
1502 _type = d->read(maxlen: 4);
1503 auto ok = true;
1504
1505 // supports the image formats of FORMChunk.
1506 if (_type == ILBM_FORM_TYPE) {
1507 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1508 } else if (_type == PBM__FORM_TYPE) {
1509 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1510 } else if (_type == ACBM_FORM_TYPE) {
1511 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1512 } else if (_type == RGB8_FORM_TYPE) {
1513 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1514 } else if (_type == RGBN_FORM_TYPE) {
1515 setChunks(IFFChunk::innerFromDevice(d, ok: &ok, parent: this));
1516 }
1517 return ok;
1518}
1519
1520/* ******************
1521 * *** TBHD Chunk ***
1522 * ****************** */
1523
1524TBHDChunk::~TBHDChunk()
1525{
1526
1527}
1528
1529TBHDChunk::TBHDChunk()
1530{
1531
1532}
1533
1534bool TBHDChunk::isValid() const
1535{
1536 if (bytes() != 24 && bytes() != 32) {
1537 return false;
1538 }
1539 return chunkId() == TBHDChunk::defaultChunkId();
1540}
1541
1542qint32 TBHDChunk::alignBytes() const
1543{
1544 return 4;
1545}
1546
1547qint32 TBHDChunk::width() const
1548{
1549 if (!isValid()) {
1550 return 0;
1551 }
1552 return i32(c1: data().at(i: 3), c2: data().at(i: 2), c3: data().at(i: 1), c4: data().at(i: 0));
1553}
1554
1555qint32 TBHDChunk::height() const
1556{
1557 if (!isValid()) {
1558 return 0;
1559 }
1560 return i32(c1: data().at(i: 7), c2: data().at(i: 6), c3: data().at(i: 5), c4: data().at(i: 4));
1561}
1562
1563QSize TBHDChunk::size() const
1564{
1565 return QSize(width(), height());
1566}
1567
1568qint32 TBHDChunk::left() const
1569{
1570 if (bytes() != 32) {
1571 return 0;
1572 }
1573 return i32(c1: data().at(i: 27), c2: data().at(i: 26), c3: data().at(i: 25), c4: data().at(i: 24));
1574}
1575
1576qint32 TBHDChunk::top() const
1577{
1578 if (bytes() != 32) {
1579 return 0;
1580 }
1581 return i32(c1: data().at(i: 31), c2: data().at(i: 30), c3: data().at(i: 29), c4: data().at(i: 28));
1582}
1583
1584TBHDChunk::Flags TBHDChunk::flags() const
1585{
1586 if (!isValid()) {
1587 return TBHDChunk::Flags();
1588 }
1589 return TBHDChunk::Flags(ui32(c1: data().at(i: 15), c2: data().at(i: 14), c3: data().at(i: 13), c4: data().at(i: 12)));
1590}
1591
1592qint32 TBHDChunk::bpc() const
1593{
1594 if (!isValid()) {
1595 return 0;
1596 }
1597 return ui16(c1: data().at(i: 17), c2: data().at(i: 16)) ? 2 : 1;
1598}
1599
1600qint32 TBHDChunk::channels() const
1601{
1602 if ((flags() & TBHDChunk::Flag::RgbA) == TBHDChunk::Flag::RgbA) {
1603 return 4;
1604 }
1605 if ((flags() & TBHDChunk::Flag::Rgb) == TBHDChunk::Flag::Rgb) {
1606 return 3;
1607 }
1608 return 0;
1609}
1610
1611quint16 TBHDChunk::tiles() const
1612{
1613 if (!isValid()) {
1614 return 0;
1615 }
1616 return ui16(c1: data().at(i: 19), c2: data().at(i: 18));
1617}
1618
1619QImage::Format TBHDChunk::format() const
1620{
1621 // Support for RGBA and RGB only for now.
1622 if ((flags() & TBHDChunk::Flag::RgbA) == TBHDChunk::Flag::RgbA) {
1623 if (bpc() == 2)
1624 return QImage::Format_RGBA64;
1625 else if (bpc() == 1)
1626 return QImage::Format_RGBA8888;
1627 } else if ((flags() & TBHDChunk::Flag::Rgb) == TBHDChunk::Flag::Rgb) {
1628 if (bpc() == 2)
1629 return QImage::Format_RGBX64;
1630 else if (bpc() == 1)
1631 return FORMAT_RGB_8BIT;
1632 }
1633
1634 return QImage::Format_Invalid;
1635}
1636
1637TBHDChunk::Compression TBHDChunk::compression() const
1638{
1639 if (!isValid()) {
1640 return TBHDChunk::Compression::Uncompressed;
1641 }
1642 return TBHDChunk::Compression(ui32(c1: data().at(i: 23), c2: data().at(i: 22), c3: data().at(i: 21), c4: data().at(i: 20)));
1643}
1644
1645bool TBHDChunk::innerReadStructure(QIODevice *d)
1646{
1647 return cacheData(d);
1648}
1649
1650/* ******************
1651 * *** RGBA Chunk ***
1652 * ****************** */
1653
1654RGBAChunk::~RGBAChunk()
1655{
1656}
1657
1658RGBAChunk::RGBAChunk()
1659{
1660
1661}
1662
1663bool RGBAChunk::isValid() const
1664{
1665 if (bytes() < 8) {
1666 return false;
1667 }
1668 return chunkId() == RGBAChunk::defaultChunkId();
1669}
1670
1671qint32 RGBAChunk::alignBytes() const
1672{
1673 return 4;
1674}
1675
1676bool RGBAChunk::isTileCompressed(const TBHDChunk *header) const
1677{
1678 if (!isValid() || header == nullptr) {
1679 return false;
1680 }
1681 return qint64(header->channels()) * size().width() * size().height() * header->bpc() > qint64(bytes() - 8);
1682}
1683
1684QPoint RGBAChunk::pos() const
1685{
1686 return _posPx;
1687}
1688
1689QSize RGBAChunk::size() const
1690{
1691 return _sizePx;
1692}
1693
1694// Maya version of IFF uses a slightly different algorithm for RLE compression.
1695// To understand how it works I saved images with regular patterns from Photoshop
1696// and then checked the data. It is basically the same as packbits except for how
1697// the length is extracted: I don't know if it's a standard variant or not, so
1698// I'm keeping it private.
1699inline qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
1700{
1701 qint64 j = 0;
1702 for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
1703 char n;
1704
1705 // check the output buffer space for the next run
1706 if (available < 128) {
1707 if (input->peek(data: &n, maxlen: 1) != 1) { // end of data (or error)
1708 break;
1709 }
1710 rr = qint64(n & 0x7F) + 1;
1711 if (rr > available)
1712 break;
1713 }
1714
1715 // decompress
1716 if (input->read(data: &n, maxlen: 1) != 1) { // end of data (or error)
1717 break;
1718 }
1719
1720 rr = qint64(n & 0x7F) + 1;
1721 if ((n & 0x80) == 0) {
1722 auto read = input->read(data: output + j, maxlen: rr);
1723 if (rr != read) {
1724 return -1;
1725 }
1726 } else {
1727 char b;
1728 if (input->read(data: &b, maxlen: 1) != 1) {
1729 break;
1730 }
1731 std::memset(s: output + j, c: b, n: size_t(rr));
1732 }
1733
1734 j += rr;
1735 }
1736 return j;
1737}
1738
1739QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
1740{
1741 auto readSize = size().width();
1742 if (readSize == 0) {
1743 return {};
1744 }
1745
1746 // It seems that tiles are compressed independently only if there is space savings.
1747 // The compression method specified in the header is only to indicate the type of
1748 // compression if used.
1749 if (!isTileCompressed(header)) {
1750 // when not compressed, the line contains all channels
1751 readSize *= header->bpc() * header->channels();
1752 QByteArray buf(readSize, char());
1753 auto rr = d->read(data: buf.data(), maxlen: buf.size());
1754 if (rr != buf.size()) {
1755 return {};
1756 }
1757 return buf;
1758 }
1759
1760 // compressed
1761 for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && _readBuffer.size() < readSize;) {
1762 QByteArray buf(readSize * size().height(), char());
1763 qint64 rr = -1;
1764 if (header->compression() == TBHDChunk::Compression::Rle) {
1765 rr = rleMayaDecompress(input: d, output: buf.data(), olen: buf.size());
1766 }
1767 if (rr != buf.size()) {
1768 return {};
1769 }
1770 _readBuffer.append(s: buf.data(), len: rr);
1771 }
1772
1773 auto buff = _readBuffer.left(n: readSize);
1774 _readBuffer.remove(index: 0, len: readSize);
1775
1776 return buff;
1777}
1778
1779/*!
1780 * \brief compressedTile
1781 *
1782 * The compressed tile contains compressed data per channel.
1783 *
1784 * If 16 bit, high and low bytes are treated separately (so I have
1785 * channels * 2 compressed data blocks). First the high ones, then the low
1786 * ones (or vice versa): for the reconstruction I went by trial and error :)
1787 * \param d The device
1788 * \param header The header.
1789 * \return The tile as Qt image.
1790 */
1791QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
1792{
1793 QImage img(size(), header->format());
1794 auto bpc = header->bpc();
1795
1796 if (bpc == 1) {
1797 for (auto c = 0, cs = header->channels(); c < cs; ++c) {
1798 for (auto y = 0, h = img.height(); y < h; ++y) {
1799 auto ba = readStride(d, header);
1800 if (ba.isEmpty()) {
1801 return {};
1802 }
1803 auto scl = reinterpret_cast<quint8*>(img.scanLine(y));
1804 for (auto x = 0, w = std::min(a: int(ba.size()), b: img.width()); x < w; ++x) {
1805 scl[x * cs + cs - c - 1] = ba.at(i: x);
1806 }
1807 }
1808 }
1809 } else if (bpc == 2) {
1810 auto cs = header->channels();
1811 if (cs < 4) { // alpha on 64-bit images must be 0xFF
1812 std::memset(s: img.bits(), c: 0xFF, n: img.sizeInBytes());
1813 }
1814 for (auto c = 0, cc = header->channels() * header->bpc(); c < cc; ++c) {
1815#if Q_BYTE_ORDER == Q_BIG_ENDIAN
1816 auto c_bcp = c / cs; // Not tried
1817#else
1818 auto c_bcp = 1 - c / cs;
1819#endif
1820 auto c_cs = (cs - 1 - c % cs) * bpc + c_bcp;
1821 for (auto y = 0, h = img.height(); y < h; ++y) {
1822 auto ba = readStride(d, header);
1823 if (ba.isEmpty()) {
1824 return {};
1825 }
1826 auto scl = reinterpret_cast<quint8*>(img.scanLine(y));
1827 for (auto x = 0, w = std::min(a: int(ba.size()), b: img.width()); x < w; ++x) {
1828 scl[x * 4 * bpc + c_cs] = ba.at(i: x); // * 4 -> Qt RGB 64-bit formats are always 4 channels
1829 }
1830 }
1831 }
1832 }
1833
1834 return img;
1835}
1836
1837/*!
1838 * \brief RGBAChunk::uncompressedTile
1839 *
1840 * The uncompressed tile scanline contains the data in
1841 * B0 G0 R0 A0 B1 G1 R1 A1... Bn Gn Rn An format.
1842 * \param d The device
1843 * \param header The header.
1844 * \return The tile as Qt image.
1845 */
1846QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
1847{
1848 QImage img(size(), header->format());
1849 auto bpc = header->bpc();
1850
1851 if (bpc == 1) {
1852 auto cs = header->channels();
1853 for (auto y = 0, h = img.height(); y < h; ++y) {
1854 auto ba = readStride(d, header);
1855 if (ba.isEmpty()) {
1856 return {};
1857 }
1858 auto scl = reinterpret_cast<quint8*>(img.scanLine(y));
1859 for (auto c = 0; c < cs; ++c) {
1860 for (auto x = 0, w = std::min(a: int(ba.size() / cs), b: img.width()); x < w; ++x) {
1861 auto xcs = x * cs;
1862 scl[xcs + cs - c - 1] = ba.at(i: xcs + c);
1863 }
1864 }
1865 }
1866 } else if (bpc == 2) {
1867 auto cs = header->channels();
1868 if (cs < 4) { // alpha on 64-bit images must be 0xFF
1869 std::memset(s: img.bits(), c: 0xFF, n: img.sizeInBytes());
1870 }
1871
1872 for (auto y = 0, h = img.height(); y < h; ++y) {
1873 auto ba = readStride(d, header);
1874 if (ba.isEmpty()) {
1875 return {};
1876 }
1877 auto scl = reinterpret_cast<quint16*>(img.scanLine(y));
1878 auto src = reinterpret_cast<const quint16*>(ba.data());
1879 for (auto c = 0; c < cs; ++c) {
1880 for (auto x = 0, w = std::min(a: int(ba.size() / cs / bpc), b: img.width()); x < w; ++x) {
1881 auto xcs = x * cs;
1882 auto xcs4 = x * 4;
1883#if Q_BYTE_ORDER == Q_BIG_ENDIAN
1884 scl[xcs4 + cs - c - 1] = src[xcs + c]; // Not tried
1885#else
1886 scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8);
1887#endif
1888 }
1889 }
1890 }
1891 }
1892
1893 return img;
1894}
1895
1896QImage RGBAChunk::tile(QIODevice *d, const TBHDChunk *header) const
1897{
1898 if (!isValid() || header == nullptr) {
1899 return {};
1900 }
1901 if (!seek(d, relPos: 8)) {
1902 return {};
1903 }
1904
1905 if (isTileCompressed(header)) {
1906 return compressedTile(d, header);
1907 }
1908
1909 return uncompressedTile(d, header);
1910}
1911
1912bool RGBAChunk::innerReadStructure(QIODevice *d)
1913{
1914 auto ba = d->read(maxlen: 8);
1915 if (ba.size() != 8) {
1916 return false;
1917 }
1918
1919 auto x0 = ui16(c1: ba.at(i: 1), c2: ba.at(i: 0));
1920 auto y0 = ui16(c1: ba.at(i: 3), c2: ba.at(i: 2));
1921 auto x1 = ui16(c1: ba.at(i: 5), c2: ba.at(i: 4));
1922 auto y1 = ui16(c1: ba.at(i: 7), c2: ba.at(i: 6));
1923 if (x0 > x1 || y0 > y1) {
1924 return false;
1925 }
1926
1927 _posPx = QPoint(x0, y0);
1928 _sizePx = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
1929
1930 return true;
1931}
1932
1933
1934/* ******************
1935 * *** ANNO Chunk ***
1936 * ****************** */
1937
1938ANNOChunk::~ANNOChunk()
1939{
1940
1941}
1942
1943ANNOChunk::ANNOChunk()
1944{
1945
1946}
1947
1948bool ANNOChunk::isValid() const
1949{
1950 return chunkId() == ANNOChunk::defaultChunkId();
1951}
1952
1953QString ANNOChunk::value() const
1954{
1955 return dataToString(chunk: this);
1956}
1957
1958bool ANNOChunk::innerReadStructure(QIODevice *d)
1959{
1960 return cacheData(d);
1961}
1962
1963/* ******************
1964 * *** AUTH Chunk ***
1965 * ****************** */
1966
1967AUTHChunk::~AUTHChunk()
1968{
1969
1970}
1971
1972AUTHChunk::AUTHChunk()
1973{
1974
1975}
1976
1977bool AUTHChunk::isValid() const
1978{
1979 return chunkId() == AUTHChunk::defaultChunkId();
1980}
1981
1982QString AUTHChunk::value() const
1983{
1984 return dataToString(chunk: this);
1985}
1986
1987bool AUTHChunk::innerReadStructure(QIODevice *d)
1988{
1989 return cacheData(d);
1990}
1991
1992
1993/* ******************
1994 * *** COPY Chunk ***
1995 * ****************** */
1996
1997COPYChunk::~COPYChunk()
1998{
1999
2000}
2001
2002COPYChunk::COPYChunk()
2003{
2004
2005}
2006
2007bool COPYChunk::isValid() const
2008{
2009 return chunkId() == COPYChunk::defaultChunkId();
2010}
2011
2012QString COPYChunk::value() const
2013{
2014 return dataToString(chunk: this);
2015}
2016
2017bool COPYChunk::innerReadStructure(QIODevice *d)
2018{
2019 return cacheData(d);
2020}
2021
2022
2023/* ******************
2024 * *** DATE Chunk ***
2025 * ****************** */
2026
2027DATEChunk::~DATEChunk()
2028{
2029
2030}
2031
2032DATEChunk::DATEChunk()
2033{
2034
2035}
2036
2037bool DATEChunk::isValid() const
2038{
2039 return chunkId() == DATEChunk::defaultChunkId();
2040}
2041
2042QDateTime DATEChunk::value() const
2043{
2044 if (!isValid()) {
2045 return {};
2046 }
2047 return QDateTime::fromString(string: QString::fromLatin1(ba: data()), format: Qt::TextDate);
2048}
2049
2050bool DATEChunk::innerReadStructure(QIODevice *d)
2051{
2052 return cacheData(d);
2053}
2054
2055
2056/* ******************
2057 * *** EXIF Chunk ***
2058 * ****************** */
2059
2060EXIFChunk::~EXIFChunk()
2061{
2062
2063}
2064
2065EXIFChunk::EXIFChunk()
2066{
2067
2068}
2069
2070bool EXIFChunk::isValid() const
2071{
2072 if (!data().startsWith(bv: QByteArray("Exif\0\0"))) {
2073 return false;
2074 }
2075 return chunkId() == EXIFChunk::defaultChunkId();
2076}
2077
2078MicroExif EXIFChunk::value() const
2079{
2080 if (!isValid()) {
2081 return {};
2082 }
2083 return MicroExif::fromByteArray(ba: data().mid(index: 6));
2084}
2085
2086bool EXIFChunk::innerReadStructure(QIODevice *d)
2087{
2088 return cacheData(d);
2089}
2090
2091
2092/* ******************
2093 * *** ICCN Chunk ***
2094 * ****************** */
2095
2096ICCNChunk::~ICCNChunk()
2097{
2098
2099}
2100
2101ICCNChunk::ICCNChunk()
2102{
2103
2104}
2105
2106bool ICCNChunk::isValid() const
2107{
2108 return chunkId() == ICCNChunk::defaultChunkId();
2109}
2110
2111QString ICCNChunk::value() const
2112{
2113 return dataToString(chunk: this);
2114}
2115
2116bool ICCNChunk::innerReadStructure(QIODevice *d)
2117{
2118 return cacheData(d);
2119}
2120
2121
2122/* ******************
2123 * *** ICCP Chunk ***
2124 * ****************** */
2125
2126ICCPChunk::~ICCPChunk()
2127{
2128
2129}
2130
2131ICCPChunk::ICCPChunk()
2132{
2133
2134}
2135
2136bool ICCPChunk::isValid() const
2137{
2138 return chunkId() == ICCPChunk::defaultChunkId();
2139}
2140
2141QColorSpace ICCPChunk::value() const
2142{
2143 if (!isValid()) {
2144 return {};
2145 }
2146 return QColorSpace::fromIccProfile(iccProfile: data());
2147}
2148
2149bool ICCPChunk::innerReadStructure(QIODevice *d)
2150{
2151 return cacheData(d);
2152}
2153
2154/* ******************
2155 * *** FVER Chunk ***
2156 * ****************** */
2157
2158FVERChunk::~FVERChunk()
2159{
2160
2161}
2162
2163FVERChunk::FVERChunk()
2164{
2165
2166}
2167
2168bool FVERChunk::isValid() const
2169{
2170 return chunkId() == FVERChunk::defaultChunkId();
2171}
2172
2173bool FVERChunk::innerReadStructure(QIODevice *d)
2174{
2175 return cacheData(d);
2176}
2177
2178/* ******************
2179 * *** HIST Chunk ***
2180 * ****************** */
2181
2182HISTChunk::~HISTChunk()
2183{
2184
2185}
2186
2187HISTChunk::HISTChunk()
2188{
2189
2190}
2191
2192bool HISTChunk::isValid() const
2193{
2194 return chunkId() == HISTChunk::defaultChunkId();
2195}
2196
2197QString HISTChunk::value() const
2198{
2199 if (!isValid()) {
2200 return {};
2201 }
2202 return QString::fromLatin1(ba: data());
2203}
2204
2205bool HISTChunk::innerReadStructure(QIODevice *d)
2206{
2207 return cacheData(d);
2208}
2209
2210
2211/* ******************
2212 * *** NAME Chunk ***
2213 * ****************** */
2214
2215NAMEChunk::~NAMEChunk()
2216{
2217
2218}
2219
2220NAMEChunk::NAMEChunk()
2221{
2222
2223}
2224
2225bool NAMEChunk::isValid() const
2226{
2227 return chunkId() == NAMEChunk::defaultChunkId();
2228}
2229
2230QString NAMEChunk::value() const
2231{
2232 return dataToString(chunk: this);
2233}
2234
2235bool NAMEChunk::innerReadStructure(QIODevice *d)
2236{
2237 return cacheData(d);
2238}
2239
2240
2241/* ******************
2242 * *** VERS Chunk ***
2243 * ****************** */
2244
2245VERSChunk::~VERSChunk()
2246{
2247
2248}
2249
2250VERSChunk::VERSChunk()
2251{
2252
2253}
2254
2255bool VERSChunk::isValid() const
2256{
2257 return chunkId() == VERSChunk::defaultChunkId();
2258}
2259
2260QString VERSChunk::value() const
2261{
2262 if (!isValid()) {
2263 return {};
2264 }
2265 return QString::fromLatin1(ba: data());
2266}
2267
2268bool VERSChunk::innerReadStructure(QIODevice *d)
2269{
2270 return cacheData(d);
2271}
2272
2273
2274/* ******************
2275 * *** XMP0 Chunk ***
2276 * ****************** */
2277
2278XMP0Chunk::~XMP0Chunk()
2279{
2280
2281}
2282
2283XMP0Chunk::XMP0Chunk()
2284{
2285
2286}
2287
2288bool XMP0Chunk::isValid() const
2289{
2290 return chunkId() == XMP0Chunk::defaultChunkId();
2291}
2292
2293QString XMP0Chunk::value() const
2294{
2295 return dataToString(chunk: this);
2296}
2297
2298bool XMP0Chunk::innerReadStructure(QIODevice *d)
2299{
2300 return cacheData(d);
2301}
2302
2303
2304/* ******************
2305 * *** BEAM Chunk ***
2306 * ****************** */
2307
2308BEAMChunk::~BEAMChunk()
2309{
2310
2311}
2312
2313BEAMChunk::BEAMChunk() : IPALChunk()
2314{
2315
2316}
2317
2318bool BEAMChunk::isValid() const
2319{
2320 return chunkId() == BEAMChunk::defaultChunkId();
2321}
2322
2323QList<QRgb> BEAMChunk::palette(qint32 y, qint32 height) const
2324{
2325 if (height < 1) {
2326 return {};
2327 }
2328 auto bpp = bytes() / height;
2329 if (bytes() != height * bpp) {
2330 return {};
2331 }
2332 auto col = qint32(bpp / 2);
2333 auto &&dt = data();
2334 QList<QRgb> pal;
2335 for (auto c = 0; c < col; ++c) {
2336 // 2 bytes per color (0x0R 0xGB)
2337 auto idx = bpp * y + c * 2;
2338 auto r = quint8(dt[idx] & 0x0F);
2339 auto g = quint8(dt[idx + 1] & 0xF0);
2340 auto b = quint8(dt[idx + 1] & 0x0F);
2341 pal << qRgb(r: r | (r << 4), g: (g >> 4) | g, b: b | (b << 4));
2342 }
2343 return pal;
2344}
2345
2346bool BEAMChunk::innerReadStructure(QIODevice *d)
2347{
2348 return cacheData(d);
2349}
2350
2351
2352/* ******************
2353 * *** CTBL Chunk ***
2354 * ****************** */
2355
2356CTBLChunk::~CTBLChunk()
2357{
2358
2359}
2360
2361CTBLChunk::CTBLChunk() : BEAMChunk()
2362{
2363
2364}
2365
2366bool CTBLChunk::isValid() const
2367{
2368 return chunkId() == CTBLChunk::defaultChunkId();
2369}
2370
2371
2372/* ******************
2373 * *** SHAM Chunk ***
2374 * ****************** */
2375
2376SHAMChunk::~SHAMChunk()
2377{
2378
2379}
2380
2381SHAMChunk::SHAMChunk() : IPALChunk()
2382{
2383
2384}
2385
2386bool SHAMChunk::isValid() const
2387{
2388 if (bytes() < 2) {
2389 return false;
2390 }
2391 auto &&dt = data();
2392 if (dt[0] != 0 && dt[1] != 0) {
2393 // In all the sham test cases I have them at zero...
2394 // if they are different from zero I suppose they should
2395 // be interpreted differently from what was done.
2396 return false;
2397 }
2398 return chunkId() == SHAMChunk::defaultChunkId();
2399}
2400
2401QList<QRgb> SHAMChunk::palette(qint32 y, qint32 height) const
2402{
2403 if (height < 1) {
2404 return {};
2405 }
2406 auto bpp = 32; // always 32 bytes per palette (16 colors)
2407 auto div = 0;
2408 if (bytes() == quint32(height * bpp + 2)) {
2409 div = 1;
2410 } else if (bytes() == quint32(height / 2 * bpp + 2)) {
2411 div = 2;
2412 }
2413 if (div == 0) {
2414 return {};
2415 }
2416 auto &&dt = data();
2417 QList<QRgb> pal;
2418 for (auto c = 0, col = bpp / 2, idx0 = y / div * bpp + 2; c < col; ++c) {
2419 // 2 bytes per color (0x0R 0xGB)
2420 auto idx = idx0 + c * 2;
2421 auto r = quint8(dt[idx] & 0x0F);
2422 auto g = quint8(dt[idx + 1] & 0xF0);
2423 auto b = quint8(dt[idx + 1] & 0x0F);
2424 pal << qRgb(r: r | (r << 4), g: (g >> 4) | g, b: b | (b << 4));
2425 }
2426 return pal;
2427}
2428
2429bool SHAMChunk::innerReadStructure(QIODevice *d)
2430{
2431 return cacheData(d);
2432}
2433
2434/* ******************
2435 * *** RAST Chunk ***
2436 * ****************** */
2437
2438RASTChunk::~RASTChunk()
2439{
2440
2441}
2442
2443RASTChunk::RASTChunk() : IPALChunk()
2444{
2445
2446}
2447
2448bool RASTChunk::isValid() const
2449{
2450 return chunkId() == RASTChunk::defaultChunkId();
2451}
2452
2453QList<QRgb> RASTChunk::palette(qint32 y, qint32 height) const
2454{
2455 if (height < 1) {
2456 return {};
2457 }
2458 auto bpp = bytes() / height;
2459 if (bytes() != height * bpp) {
2460 return {};
2461 }
2462 auto col = qint32(bpp / 2 - 1);
2463 auto &&dt = data();
2464 QList<QRgb> pal;
2465 for (auto c = 0; c < col; ++c) {
2466 auto idx = bpp * y + 2 + c * 2;
2467 // The Atari ST uses 3 bits per color (512 colors) while the Atari STE
2468 // uses 4 bits per color (4096 colors). This strange encoding with the
2469 // least significant bit set as MSB is, I believe, to ensure hardware
2470 // compatibility between the two machines.
2471 #define H1L(a) ((quint8(a) & 0x7) << 1) | ((quint8(a) >> 3) & 1)
2472 auto r = H1L(dt[idx]);
2473 auto g = H1L(dt[idx + 1] >> 4);
2474 auto b = H1L(dt[idx + 1]);
2475 #undef H1L
2476 pal << qRgb(r: r | (r << 4), g: (g << 4) | g, b: b | (b << 4));
2477 }
2478 return pal;
2479}
2480
2481bool RASTChunk::innerReadStructure(QIODevice *d)
2482{
2483 return cacheData(d);
2484}
2485

source code of kimageformats/src/imageformats/chunks.cpp