1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2025 Mirco Miranda <mircomir@outlook.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "chunks_p.h"
9#include "iff_p.h"
10#include "util_p.h"
11
12#include <QIODevice>
13#include <QImage>
14#include <QPainter>
15
16class IFFHandlerPrivate
17{
18public:
19 IFFHandlerPrivate()
20 : m_imageNumber(0)
21 , m_imageCount(0)
22 {
23
24 }
25 ~IFFHandlerPrivate()
26 {
27
28 }
29
30 /*!
31 * \brief atariSTERast
32 * On Atari STE images, the RAST chunk can be found outside
33 * the FORM one so, I check if this is the case.
34 * \param chunks The chunk list.
35 */
36 void atariSTERast(QIODevice *d, IFFChunk::ChunkList &chunks)
37 {
38 if (chunks.size() != 1 || d->isSequential()) {
39 return;
40 }
41 auto &&c = chunks.first();
42 if (c->chunkId() != FORMChunk::defaultChunkId()) {
43 return;
44 }
45
46 // The RAST chunk is not aligned so I have to temporary change the
47 // position and the alignment to read it successfully.
48 auto pos = d->pos();
49 auto align = c->alignBytes();
50 c->setAlignBytes(1);
51 d->seek(pos: c->nextChunkPos());
52 c->setAlignBytes(align);
53 if (d->peek(maxlen: 4) == RAST_CHUNK) {
54 auto rast = QSharedPointer<IFFChunk>(new RASTChunk());
55 if (rast->readStructure(d) && rast->isValid())
56 chunks.first()->_chunks.append(t: rast);
57 }
58 d->seek(pos);
59 }
60
61 bool readStructure(QIODevice *d)
62 {
63 if (d == nullptr) {
64 return {};
65 }
66
67 if (!m_chunks.isEmpty()) {
68 return true;
69 }
70
71 auto ok = false;
72 auto chunks = IFFChunk::fromDevice(d, ok: &ok);
73 if (ok) {
74 atariSTERast(d, chunks);
75 m_chunks = chunks;
76 }
77 return ok;
78 }
79
80 template <class T>
81 static QList<const T*> searchForms(const IFFChunk::ChunkList &chunks, bool supportedOnly = true)
82 {
83 QList<const T*> list;
84 auto cid = T::defaultChunkId();
85 auto forms = IFFChunk::search(cid, chunks);
86 for (auto &&form : forms) {
87 if (auto f = dynamic_cast<const T*>(form.data()))
88 if (!supportedOnly || f->isSupported())
89 list << f;
90 }
91 return list;
92 }
93
94 template <class T>
95 QList<const T*> searchForms(bool supportedOnly = true)
96 {
97 return searchForms<T>(m_chunks, supportedOnly);
98 }
99
100 IFFChunk::ChunkList m_chunks;
101
102 /*!
103 * \brief m_imageNumber
104 * Value set by QImageReader::jumpToImage() or QImageReader::jumpToNextImage().
105 * The number of view selected in a multiview image.
106 */
107 qint32 m_imageNumber;
108
109 /*!
110 * \brief m_imageCount
111 * The total number of views (cache value)
112 */
113 mutable qint32 m_imageCount;
114};
115
116
117IFFHandler::IFFHandler()
118 : QImageIOHandler()
119 , d(new IFFHandlerPrivate)
120{
121
122}
123
124bool IFFHandler::canRead() const
125{
126 if (canRead(device: device())) {
127 setFormat("iff");
128 return true;
129 }
130 return false;
131}
132
133bool IFFHandler::canRead(QIODevice *device)
134{
135 if (!device) {
136 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::canRead(): called with no device";
137 return false;
138 }
139
140 if (device->isSequential()) {
141 return false;
142 }
143
144 // I avoid parsing obviously incorrect files
145 auto cid = device->peek(maxlen: 4);
146 if (cid != CAT__CHUNK &&
147 cid != FORM_CHUNK &&
148 cid != LIST_CHUNK &&
149 cid != CAT4_CHUNK &&
150 cid != FOR4_CHUNK &&
151 cid != LIS4_CHUNK) {
152 return false;
153 }
154
155 auto ok = false;
156 auto pos = device->pos();
157 auto chunks = IFFChunk::fromDevice(d: device, ok: &ok);
158 if (!device->seek(pos)) {
159 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::canRead(): unable to reset device position";
160 }
161 if (ok) {
162 auto forms = IFFHandlerPrivate::searchForms<FORMChunk>(chunks, supportedOnly: true);
163 auto for4s = IFFHandlerPrivate::searchForms<FOR4Chunk>(chunks, supportedOnly: true);
164 ok = !forms.isEmpty() || !for4s.isEmpty();
165 }
166 return ok;
167}
168
169static void addMetadata(QImage &img, const IFOR_Chunk *form)
170{
171 // standard IFF metadata
172 auto annos = IFFChunk::searchT<ANNOChunk>(chunk: form);
173 if (!annos.isEmpty()) {
174 auto anno = annos.first()->value();
175 if (!anno.isEmpty()) {
176 img.setText(QStringLiteral(META_KEY_DESCRIPTION), value: anno);
177 }
178 }
179 auto auths = IFFChunk::searchT<AUTHChunk>(chunk: form);
180 if (!auths.isEmpty()) {
181 auto auth = auths.first()->value();
182 if (!auth.isEmpty()) {
183 img.setText(QStringLiteral(META_KEY_AUTHOR), value: auth);
184 }
185 }
186 auto dates = IFFChunk::searchT<DATEChunk>(chunk: form);
187 if (!dates.isEmpty()) {
188 auto dt = dates.first()->value();
189 if (dt.isValid()) {
190 img.setText(QStringLiteral(META_KEY_CREATIONDATE), value: dt.toString(format: Qt::ISODate));
191 }
192 }
193 auto copys = IFFChunk::searchT<COPYChunk>(chunk: form);
194 if (!copys.isEmpty()) {
195 auto cp = copys.first()->value();
196 if (!cp.isEmpty()) {
197 img.setText(QStringLiteral(META_KEY_COPYRIGHT), value: cp);
198 }
199 }
200 auto names = IFFChunk::searchT<NAMEChunk>(chunk: form);
201 if (!names.isEmpty()) {
202 auto name = names.first()->value();
203 if (!name.isEmpty()) {
204 img.setText(QStringLiteral(META_KEY_TITLE), value: name);
205 }
206 }
207
208 // software info
209 auto vers = IFFChunk::searchT<VERSChunk>(chunk: form);
210 if (!vers.isEmpty()) {
211 auto ver = vers.first()->value();
212 if (!vers.isEmpty()) {
213 img.setText(QStringLiteral(META_KEY_SOFTWARE), value: ver);
214 }
215 }
216
217 // SView5 metadata
218 auto resChanged = false;
219 auto exifs = IFFChunk::searchT<EXIFChunk>(chunk: form);
220 if (!exifs.isEmpty()) {
221 auto exif = exifs.first()->value();
222 exif.updateImageMetadata(targetImage&: img, replaceExisting: false);
223 resChanged = exif.updateImageResolution(targetImage&: img);
224 }
225
226 auto xmp0s = IFFChunk::searchT<XMP0Chunk>(chunk: form);
227 if (!xmp0s.isEmpty()) {
228 auto xmp = xmp0s.first()->value();
229 if (!xmp.isEmpty()) {
230 img.setText(QStringLiteral(META_KEY_XMP_ADOBE), value: xmp);
231 }
232 }
233
234 auto iccps = IFFChunk::searchT<ICCPChunk>(chunk: form);
235 if (!iccps.isEmpty()) {
236 auto cs = iccps.first()->value();
237 if (cs.isValid()) {
238 auto iccns = IFFChunk::searchT<ICCNChunk>(chunk: form);
239 if (!iccns.isEmpty()) {
240 auto desc = iccns.first()->value();
241 if (!desc.isEmpty())
242 cs.setDescription(desc);
243 }
244 img.setColorSpace(cs);
245 }
246 }
247
248 // resolution -> leave after set of EXIF chunk
249 const DPIChunk *dpi = nullptr;
250 auto dpis = IFFChunk::searchT<DPIChunk>(chunk: form);
251 auto xbmis = IFFChunk::searchT<XBMIChunk>(chunk: form);
252 if (!dpis.isEmpty()) {
253 dpi = dpis.first();
254 } else if (!xbmis.isEmpty()) {
255 dpi = xbmis.first(); // never seen
256 }
257 if (dpi && dpi->isValid()) {
258 img.setDotsPerMeterX(dpi->dotsPerMeterX());
259 img.setDotsPerMeterY(dpi->dotsPerMeterY());
260 resChanged = true;
261 }
262
263 // if no explicit resolution was found, apply the aspect ratio to the default one
264 if (!resChanged) {
265 if (form->formType() == IMAG_FORM_TYPE) {
266 auto params = IFFChunk::searchT<IPARChunk>(chunk: form);
267 if (!params.isEmpty()) {
268 img.setDotsPerMeterY(img.dotsPerMeterY() * params.first()->aspectRatio());
269 }
270 } else if (form->formType() == RGFX_FORM_TYPE) {
271 auto headers = IFFChunk::searchT<RGHDChunk>(chunk: form);
272 if (!headers.isEmpty()) {
273 img.setDotsPerMeterY(img.dotsPerMeterY() * headers.first()->aspectRatio());
274 }
275 } else {
276 auto headers = IFFChunk::searchT<BMHDChunk>(chunk: form);
277 if (!headers.isEmpty()) {
278 auto xr = headers.first()->xAspectRatio();
279 auto yr = headers.first()->yAspectRatio();
280 if (xr > 0 && yr > 0 && xr > yr) {
281 img.setDotsPerMeterX(img.dotsPerMeterX() * yr / xr);
282 } else if (xr > 0 && yr > 0 && xr < yr) {
283 img.setDotsPerMeterY(img.dotsPerMeterY() * xr / yr);
284 }
285 }
286 }
287 }
288}
289
290/*!
291 * \brief convertIPAL
292 * \param img The source image.
293 * \param ipal The per line palette.
294 * \return The new image converted or \a img if no conversion is needed or possible.
295 */
296static QImage convertIPAL(const QImage& img, const IPALChunk *ipal)
297{
298 if (img.format() != QImage::Format_Indexed8) {
299 qCDebug(LOG_IFFPLUGIN) << "convertIPAL(): the image is not indexed!";
300 return img;
301 }
302
303 auto tmp = img.convertToFormat(f: ipal->hasAlpha() ? FORMAT_RGBA_8BIT : FORMAT_RGB_8BIT);
304 if (tmp.isNull()) {
305 qCritical(catFunc: LOG_IFFPLUGIN) << "convertIPAL(): error while converting the image!";
306 return img;
307 }
308
309 auto mul = tmp.hasAlphaChannel() ? 4 : 3;
310 for (auto y = 0, h = img.height(); y < h; ++y) {
311 auto src = reinterpret_cast<const quint8 *>(img.constScanLine(y));
312 auto dst = tmp.scanLine(y);
313 auto lpal = ipal->palette(y);
314 for (auto x = 0, w = img.width(); x < w; ++x) {
315 if (src[x] < lpal.size()) {
316 auto xmul = x * mul;
317 dst[xmul] = qRed(rgb: lpal.at(i: src[x]));
318 dst[xmul + 1] = qGreen(rgb: lpal.at(i: src[x]));
319 dst[xmul + 2] = qBlue(rgb: lpal.at(i: src[x]));
320 if (mul == 4) {
321 dst[xmul + 3] = qAlpha(rgb: lpal.at(i: src[x]));
322 }
323 }
324 }
325 }
326
327 return tmp;
328}
329
330bool IFFHandler::readStandardImage(QImage *image)
331{
332 auto forms = d->searchForms<FORMChunk>();
333 if (forms.isEmpty()) {
334 return false;
335 }
336 auto cin = qBound(min: 0, val: currentImageNumber(), max: int(forms.size() - 1));
337 auto &&form = forms.at(i: cin);
338
339 // show the first one (I don't have a sample with many images)
340 auto headers = IFFChunk::searchT<BMHDChunk>(chunk: form);
341 if (headers.isEmpty()) {
342 return false;
343 }
344
345 // create the image
346 auto &&header = headers.first();
347 auto img = imageAlloc(size: header->size(), format: form->format());
348 if (img.isNull()) {
349 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): error while allocating the image";
350 return false;
351 }
352
353 // set color table
354 const CAMGChunk *camg = nullptr;
355 auto camgs = IFFChunk::searchT<CAMGChunk>(chunk: form);
356 if (!camgs.isEmpty()) {
357 camg = camgs.first();
358 }
359
360 const CMAPChunk *cmap = nullptr;
361 auto cmaps = IFFChunk::searchT<CMAPChunk>(chunk: form);
362 if (cmaps.isEmpty()) {
363 auto cmyks = IFFChunk::searchT<CMYKChunk>(chunk: form);
364 for (auto &&cmyk : cmyks)
365 cmaps.append(t: cmyk);
366 }
367 if (!cmaps.isEmpty()) {
368 cmap = cmaps.first();
369 }
370 if (img.format() == QImage::Format_Indexed8) {
371 if (cmap) {
372 auto halfbride = BODYChunk::safeModeId(header, camg, cmap) & CAMGChunk::ModeId::HalfBrite ? true : false;
373 img.setColorTable(cmap->palette(halfbride));
374 }
375 }
376
377 // reading image data
378 std::unique_ptr<IPALChunk> ipal;
379 if (auto ptr = form->searchIPal()) {
380 ipal = std::unique_ptr<IPALChunk>(ptr->clone());
381 }
382 if (ipal) {
383 auto pal = img.colorTable();
384 if (pal.isEmpty() && cmap)
385 pal = cmap->palette();
386 if (!ipal->initialize(cmapPalette: pal, height: img.height())) {
387 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): unable to initialize palette changer";
388 return false;
389 }
390 }
391 auto bodies = IFFChunk::searchT<BODYChunk>(chunk: form);
392 if (bodies.isEmpty()) {
393 auto abits = IFFChunk::searchT<ABITChunk>(chunk: form);
394 for (auto &&abit : abits)
395 bodies.append(t: abit);
396 }
397 if (bodies.isEmpty()) {
398 img.fill(pixel: 0);
399 } else {
400 auto &&body = bodies.first();
401 if (!body->resetStrideRead(d: device())) {
402 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): error while reading image data";
403 return false;
404 }
405 for (auto y = 0, h = img.height(); y < h; ++y) {
406 auto line = reinterpret_cast<char*>(img.scanLine(y));
407 auto ba = body->strideRead(d: device(), y, header, camg, cmap, ipal: ipal.get(), formType: form->formType());
408 if (ba.isEmpty()) {
409 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): error while reading image scanline";
410 return false;
411 }
412 memcpy(dest: line, src: ba.constData(), n: std::min(a: img.bytesPerLine(), b: ba.size()));
413 }
414 }
415
416 // BEAM / CTBL, SHAM, RAST, PCHG conversion (if not already done)
417 if (ipal && img.format() == QImage::Format_Indexed8) {
418 img = convertIPAL(img, ipal: ipal.get());
419 }
420
421 // set metadata (including image resolution)
422 addMetadata(img, form);
423
424 *image = img;
425 return true;
426}
427
428bool IFFHandler::readMayaImage(QImage *image)
429{
430 auto forms = d->searchForms<FOR4Chunk>();
431 if (forms.isEmpty()) {
432 return false;
433 }
434 auto cin = qBound(min: 0, val: currentImageNumber(), max: int(forms.size() - 1));
435 auto &&form = forms.at(i: cin);
436
437 // show the first one (I don't have a sample with many images)
438 auto headers = IFFChunk::searchT<TBHDChunk>(chunk: form);
439 if (headers.isEmpty()) {
440 return false;
441 }
442
443 // create the image
444 auto &&header = headers.first();
445 auto img = imageAlloc(size: header->size(), format: form->format());
446 if (img.isNull()) {
447 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): error while allocating the image";
448 return false;
449 }
450
451 auto &&tiles = IFFChunk::searchT<RGBAChunk>(chunk: form);
452 if ((tiles.size() & 0xFFFF) != header->tiles()) { // Photoshop, on large images saves more than 65535 tiles
453 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): tile number mismatch: found" << tiles.size() << "while expected" << header->tiles();
454 return false;
455 }
456 for (auto &&tile : tiles) {
457 auto tp = tile->pos();
458 auto ts = tile->size();
459 if (tp.x() < 0 || tp.x() + ts.width() > img.width()) {
460 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): wrong tile position or size";
461 return false;
462 }
463 if (tp.y() < 0 || tp.y() + ts.height() > img.height()) {
464 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): wrong tile position or size";
465 return false;
466 }
467 // For future releases: it might be a good idea not to use a QPainter
468 auto ti = tile->tile(d: device(), header);
469 if (ti.isNull()) {
470 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): error while decoding the tile";
471 return false;
472 }
473 QPainter painter(&img);
474 painter.setCompositionMode(QPainter::CompositionMode_Source);
475 painter.drawImage(p: tp, image: ti);
476 }
477#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
478 img.mirror(horizontally: false, vertically: true);
479#else
480 img.flip(Qt::Orientation::Vertical);
481#endif
482 addMetadata(img, form);
483
484 *image = img;
485 return true;
486}
487
488bool IFFHandler::readCDIImage(QImage *image)
489{
490 auto forms = d->searchForms<FORMChunk>();
491 if (forms.isEmpty()) {
492 return false;
493 }
494 auto cin = qBound(min: 0, val: currentImageNumber(), max: int(forms.size() - 1));
495 auto &&form = forms.at(i: cin);
496
497 // show the first one (I don't have a sample with many images)
498 auto headers = IFFChunk::searchT<IHDRChunk>(chunk: form);
499 if (headers.isEmpty()) {
500 return false;
501 }
502
503 // create the image
504 auto &&header = headers.first();
505 auto img = imageAlloc(size: header->size(), format: form->format());
506 if (img.isNull()) {
507 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while allocating the image";
508 return false;
509 }
510
511 // set the palette
512 if (img.format() == QImage::Format_Indexed8) {
513 auto pltes = IFFChunk::searchT<PLTEChunk>(chunk: form);
514 if (!pltes.isEmpty()) {
515 img.setColorTable(pltes.first()->palette());
516 }
517 }
518
519 // decoding the image
520 auto bodies = IFFChunk::searchT<IDATChunk>(chunk: form);
521 if (bodies.isEmpty()) {
522 img.fill(pixel: 0);
523 } else {
524 auto &&body = bodies.first();
525 if (!body->resetStrideRead(d: device())) {
526 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while reading image data";
527 return false;
528 }
529 auto pars = IFFChunk::searchT<IPARChunk>(chunk: form);
530 auto yuvs = IFFChunk::searchT<YUVSChunk>(chunk: form);
531 for (auto y = 0, h = img.height(); y < h; ++y) {
532 auto line = reinterpret_cast<char*>(img.scanLine(y));
533 auto ba = body->strideRead(d: device(), y, header,
534 params: pars.isEmpty() ? nullptr : pars.first(),
535 yuvs: yuvs.isEmpty() ? nullptr : yuvs.first());
536 if (ba.isEmpty()) {
537 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while reading image scanline";
538 return false;
539 }
540 memcpy(dest: line, src: ba.constData(), n: std::min(a: img.bytesPerLine(), b: ba.size()));
541 }
542 }
543
544 // set metadata (including image resolution)
545 addMetadata(img, form);
546
547 *image = img;
548 return true;
549}
550
551bool IFFHandler::readRGFXImage(QImage *image)
552{
553 auto forms = d->searchForms<FORMChunk>();
554 if (forms.isEmpty()) {
555 return false;
556 }
557 auto cin = qBound(min: 0, val: currentImageNumber(), max: int(forms.size() - 1));
558 auto &&form = forms.at(i: cin);
559
560 // show the first one (I don't have a sample with many images)
561 auto headers = IFFChunk::searchT<RGHDChunk>(chunk: form);
562 if (headers.isEmpty()) {
563 return false;
564 }
565
566 // create the image
567 auto &&header = headers.first();
568 auto img = imageAlloc(size: header->size(), format: form->format());
569 if (img.isNull()) {
570 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while allocating the image";
571 return false;
572 }
573
574 // set the palette
575 if (img.format() == QImage::Format_Indexed8) {
576 auto pltes = IFFChunk::searchT<RCOLChunk>(chunk: form);
577 if (!pltes.isEmpty()) {
578 img.setColorTable(pltes.first()->palette());
579 }
580 }
581
582 // decoding the image
583 auto bodies = IFFChunk::searchT<RBODChunk>(chunk: form);
584 if (bodies.isEmpty()) {
585 img.fill(pixel: 0);
586 } else {
587 auto &&body = bodies.first();
588 if (!body->resetStrideRead(d: device())) {
589 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while reading image data";
590 return false;
591 }
592 auto rcsms = IFFChunk::searchT<RSCMChunk>(chunk: form);
593 auto rcols = IFFChunk::searchT<RCOLChunk>(chunk: form);
594 for (auto y = 0, h = img.height(); y < h; ++y) {
595 auto line = reinterpret_cast<char*>(img.scanLine(y));
596 auto ba = body->strideRead(d: device(), y, header,
597 rcsm: rcsms.isEmpty() ? nullptr : rcsms.first(),
598 rcol: rcols.isEmpty() ? nullptr : rcols.first());
599 if (ba.isEmpty()) {
600 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while reading image scanline";
601 return false;
602 }
603 memcpy(dest: line, src: ba.constData(), n: std::min(a: img.bytesPerLine(), b: ba.size()));
604 }
605 }
606
607 // set metadata (including image resolution)
608 addMetadata(img, form);
609
610 *image = img;
611 return true;
612}
613
614bool IFFHandler::read(QImage *image)
615{
616 if (!d->readStructure(d: device())) {
617 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): invalid IFF structure";
618 return false;
619 }
620
621 if (readStandardImage(image)) {
622 return true;
623 }
624
625 if (readMayaImage(image)) {
626 return true;
627 }
628
629 if (readCDIImage(image)) {
630 return true;
631 }
632
633 if (readRGFXImage(image)) {
634 return true;
635 }
636
637 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found";
638 return false;
639}
640
641bool IFFHandler::supportsOption(ImageOption option) const
642{
643 if (option == QImageIOHandler::Size) {
644 return true;
645 }
646 if (option == QImageIOHandler::ImageFormat) {
647 return true;
648 }
649 if (option == QImageIOHandler::ImageTransformation) {
650 return true;
651 }
652 return false;
653}
654
655QVariant IFFHandler::option(ImageOption option) const
656{
657 if (!supportsOption(option)) {
658 return {};
659 }
660
661 const IFOR_Chunk *form = nullptr;
662 if (d->readStructure(d: device())) {
663 auto forms = d->searchForms<FORMChunk>();
664 auto for4s = d->searchForms<FOR4Chunk>();
665 auto cin = currentImageNumber();
666 if (!forms.isEmpty())
667 form = cin < forms.size() ? forms.at(i: cin) : forms.first();
668 else if (!for4s.isEmpty())
669 form = cin < for4s.size() ? for4s.at(i: cin) : for4s.first();
670 }
671 if (form == nullptr) {
672 return {};
673 }
674
675 if (option == QImageIOHandler::Size) {
676 return QVariant::fromValue(value: form->size());
677 }
678
679 if (option == QImageIOHandler::ImageFormat) {
680 return QVariant::fromValue(value: form->optionformat());
681 }
682
683 if (option == QImageIOHandler::ImageTransformation) {
684 return QVariant::fromValue(value: form->transformation());
685 }
686
687 return {};
688}
689
690bool IFFHandler::jumpToNextImage()
691{
692 return jumpToImage(imageNumber: d->m_imageNumber + 1);
693}
694
695bool IFFHandler::jumpToImage(int imageNumber)
696{
697 if (imageNumber < 0 || imageNumber >= imageCount()) {
698 return false;
699 }
700 d->m_imageNumber = imageNumber;
701 return true;
702}
703
704int IFFHandler::imageCount() const
705{
706 // NOTE: image count is cached for performance reason
707 auto &&count = d->m_imageCount;
708 if (count > 0) {
709 return count;
710 }
711
712 count = QImageIOHandler::imageCount();
713 if (!d->readStructure(d: device())) {
714 qCWarning(LOG_IFFPLUGIN) << "IFFHandler::imageCount(): invalid IFF structure";
715 return count;
716 }
717
718 auto forms = d->searchForms<FORMChunk>();
719 auto for4s = d->searchForms<FOR4Chunk>();
720 if (!forms.isEmpty())
721 count = forms.size();
722 else if (!for4s.isEmpty())
723 count = for4s.size();
724
725 return count;
726}
727
728int IFFHandler::currentImageNumber() const
729{
730 return d->m_imageNumber;
731}
732
733QImageIOPlugin::Capabilities IFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
734{
735 if (format == "iff" || format == "ilbm" || format == "lbm") {
736 return Capabilities(CanRead);
737 }
738 if (!format.isEmpty()) {
739 return {};
740 }
741 if (!device->isOpen()) {
742 return {};
743 }
744
745 Capabilities cap;
746 if (device->isReadable() && IFFHandler::canRead(device)) {
747 cap |= CanRead;
748 }
749 return cap;
750}
751
752QImageIOHandler *IFFPlugin::create(QIODevice *device, const QByteArray &format) const
753{
754 QImageIOHandler *handler = new IFFHandler;
755 handler->setDevice(device);
756 handler->setFormat(format);
757 return handler;
758}
759
760#include "moc_iff_p.cpp"
761

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