/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 | } |