1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4/*!
5 \class QtIcoHandler
6 \since 4.4
7 \brief The QtIcoHandler class provides support for the ICO image format.
8 \internal
9*/
10
11
12
13#include "qicohandler.h"
14#include <QtCore/qendian.h>
15#include <private/qendian_p.h>
16#include <QtGui/QImage>
17#include <QtCore/QBuffer>
18#include <QtCore/QFile>
19#include <QtCore/QLoggingCategory>
20#include <qvariant.h>
21
22QT_BEGIN_NAMESPACE
23
24Q_LOGGING_CATEGORY(lcIco, "qt.gui.imageio.ico")
25
26namespace {
27
28// These next two structs represent how the icon information is stored
29// in an ICO file.
30typedef struct
31{
32 quint8 bWidth; // Width of the image
33 quint8 bHeight; // Height of the image (actual height, not times 2)
34 quint8 bColorCount; // Number of colors in image (0 if >=8bpp) [ not ture ]
35 quint8 bReserved; // Reserved
36 quint16_le wPlanes; // Color Planes
37 quint16_le wBitCount; // Bits per pixel
38 quint32_le dwBytesInRes; // how many bytes in this resource?
39 quint32_le dwImageOffset; // where in the file is this image
40} ICONDIRENTRY, *LPICONDIRENTRY;
41#define ICONDIRENTRY_SIZE 16
42
43typedef struct
44{
45 quint16_le idReserved; // Reserved
46 quint16_le idType; // resource type (1 for icons, 2 for cursors)
47 quint16_le idCount; // how many images?
48 ICONDIRENTRY idEntries[1]; // the entries for each image
49} ICONDIR, *LPICONDIR;
50#define ICONDIR_SIZE 6 // Exclude the idEntries field
51
52typedef struct { // BMP information header
53 quint32_le biSize; // size of this struct
54 quint32_le biWidth; // pixmap width
55 quint32_le biHeight; // pixmap height (specifies the combined height of the XOR and AND masks)
56 quint16_le biPlanes; // should be 1
57 quint16_le biBitCount; // number of bits per pixel
58 quint32_le biCompression; // compression method
59 quint32_le biSizeImage; // size of image
60 quint32_le biXPelsPerMeter; // horizontal resolution
61 quint32_le biYPelsPerMeter; // vertical resolution
62 quint32_le biClrUsed; // number of colors used
63 quint32_le biClrImportant; // number of important colors
64} BMP_INFOHDR ,*LPBMP_INFOHDR;
65#define BMP_INFOHDR_SIZE 40
66
67}
68
69class ICOReader
70{
71public:
72 ICOReader(QIODevice * iodevice);
73 int count();
74 QImage iconAt(int index);
75 static bool canRead(QIODevice *iodev);
76
77 static QList<QImage> read(QIODevice *device);
78
79 static bool write(QIODevice *device, const QList<QImage> &images);
80
81 bool readIconEntry(int index, ICONDIRENTRY * iconEntry);
82
83private:
84 bool readHeader();
85
86 bool readBMPHeader(quint32 imageOffset, BMP_INFOHDR * header);
87 void findColorInfo(QImage & image);
88 void readColorTable(QImage & image);
89
90 void readBMP(QImage & image);
91 void read1BitBMP(QImage & image);
92 void read4BitBMP(QImage & image);
93 void read8BitBMP(QImage & image);
94 void read16_24_32BMP(QImage & image);
95
96 struct IcoAttrib
97 {
98 int nbits;
99 int ncolors;
100 int h;
101 int w;
102 int depth;
103 } icoAttrib;
104
105 QIODevice * iod;
106 qint64 startpos;
107 bool headerRead;
108 ICONDIR iconDir;
109
110};
111
112// Data readers and writers that takes care of alignment and endian stuff.
113static bool readIconDirEntry(QIODevice *iodev, ICONDIRENTRY *iconDirEntry)
114{
115 if (iodev)
116 return (iodev->read(data: (char*)iconDirEntry, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE);
117 return false;
118}
119
120static bool writeIconDirEntry(QIODevice *iodev, const ICONDIRENTRY &iconEntry)
121{
122 if (iodev)
123 return iodev->write(data: (char*)&iconEntry, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE;
124 return false;
125}
126
127static bool readIconDir(QIODevice *iodev, ICONDIR *iconDir)
128{
129 if (iodev)
130 return (iodev->read(data: (char*)iconDir, ICONDIR_SIZE) == ICONDIR_SIZE);
131 return false;
132}
133
134static bool writeIconDir(QIODevice *iodev, const ICONDIR &iconDir)
135{
136 if (iodev)
137 return iodev->write(data: (char*)&iconDir, len: 6) == 6;
138 return false;
139}
140
141static bool readBMPInfoHeader(QIODevice *iodev, BMP_INFOHDR *pHeader)
142{
143 if (iodev)
144 return (iodev->read(data: (char*)pHeader, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE);
145 return false;
146}
147
148static bool writeBMPInfoHeader(QIODevice *iodev, const BMP_INFOHDR &header)
149{
150 if (iodev)
151 return iodev->write(data: (char*)&header, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE;
152 return false;
153}
154
155
156ICOReader::ICOReader(QIODevice * iodevice)
157: iod(iodevice)
158, startpos(0)
159, headerRead(false)
160{
161}
162
163
164int ICOReader::count()
165{
166 if (readHeader())
167 return iconDir.idCount;
168 return 0;
169}
170
171bool ICOReader::canRead(QIODevice *iodev)
172{
173 bool isProbablyICO = false;
174 if (iodev) {
175 qint64 oldPos = iodev->pos();
176
177 ICONDIR ikonDir;
178 if (readIconDir(iodev, iconDir: &ikonDir)) {
179 if (readIconDirEntry(iodev, iconDirEntry: &ikonDir.idEntries[0])) {
180 // ICO format does not have a magic identifier, so we read 6 different values, which will hopefully be enough to identify the file.
181 if ( ikonDir.idReserved == 0
182 && (ikonDir.idType == 1 || ikonDir.idType == 2)
183 && ikonDir.idEntries[0].bReserved == 0
184 && (ikonDir.idEntries[0].wPlanes <= 1 || ikonDir.idType == 2)
185 && (ikonDir.idEntries[0].wBitCount <= 32 || ikonDir.idType == 2) // Bits per pixel
186 && ikonDir.idEntries[0].dwBytesInRes >= 40 // Must be over 40, since sizeof (infoheader) == 40
187 ) {
188 isProbablyICO = true;
189 }
190
191 if (iodev->isSequential()) {
192 // Our structs might be padded due to alignment, so we need to fetch each member before we ungetChar() !
193 quint32 tmp = ikonDir.idEntries[0].dwImageOffset;
194 iodev->ungetChar(c: (tmp >> 24) & 0xff);
195 iodev->ungetChar(c: (tmp >> 16) & 0xff);
196 iodev->ungetChar(c: (tmp >> 8) & 0xff);
197 iodev->ungetChar(c: tmp & 0xff);
198
199 tmp = ikonDir.idEntries[0].dwBytesInRes;
200 iodev->ungetChar(c: (tmp >> 24) & 0xff);
201 iodev->ungetChar(c: (tmp >> 16) & 0xff);
202 iodev->ungetChar(c: (tmp >> 8) & 0xff);
203 iodev->ungetChar(c: tmp & 0xff);
204
205 tmp = ikonDir.idEntries[0].wBitCount;
206 iodev->ungetChar(c: (tmp >> 8) & 0xff);
207 iodev->ungetChar(c: tmp & 0xff);
208
209 tmp = ikonDir.idEntries[0].wPlanes;
210 iodev->ungetChar(c: (tmp >> 8) & 0xff);
211 iodev->ungetChar(c: tmp & 0xff);
212
213 iodev->ungetChar(c: ikonDir.idEntries[0].bReserved);
214 iodev->ungetChar(c: ikonDir.idEntries[0].bColorCount);
215 iodev->ungetChar(c: ikonDir.idEntries[0].bHeight);
216 iodev->ungetChar(c: ikonDir.idEntries[0].bWidth);
217 }
218 }
219
220 if (iodev->isSequential()) {
221 // Our structs might be padded due to alignment, so we need to fetch each member before we ungetChar() !
222 quint32 tmp = ikonDir.idCount;
223 iodev->ungetChar(c: (tmp >> 8) & 0xff);
224 iodev->ungetChar(c: tmp & 0xff);
225
226 tmp = ikonDir.idType;
227 iodev->ungetChar(c: (tmp >> 8) & 0xff);
228 iodev->ungetChar(c: tmp & 0xff);
229
230 tmp = ikonDir.idReserved;
231 iodev->ungetChar(c: (tmp >> 8) & 0xff);
232 iodev->ungetChar(c: tmp & 0xff);
233 }
234 }
235 if (!iodev->isSequential()) iodev->seek(pos: oldPos);
236 }
237
238 return isProbablyICO;
239}
240
241bool ICOReader::readHeader()
242{
243 if (iod && !headerRead) {
244 startpos = iod->pos();
245 if (readIconDir(iodev: iod, iconDir: &iconDir)) {
246 if (iconDir.idReserved == 0 && (iconDir.idType == 1 || iconDir.idType == 2))
247 headerRead = true;
248 }
249 }
250
251 return headerRead;
252}
253
254bool ICOReader::readIconEntry(int index, ICONDIRENTRY *iconEntry)
255{
256 if (readHeader()) {
257 if (iod->seek(pos: startpos + ICONDIR_SIZE + (index * ICONDIRENTRY_SIZE))) {
258 return readIconDirEntry(iodev: iod, iconDirEntry: iconEntry);
259 }
260 }
261 return false;
262}
263
264
265
266bool ICOReader::readBMPHeader(quint32 imageOffset, BMP_INFOHDR * header)
267{
268 if (iod) {
269 if (iod->seek(pos: startpos + imageOffset)) {
270 if (readBMPInfoHeader(iodev: iod, pHeader: header)) {
271 return true;
272 }
273 }
274 }
275 return false;
276}
277
278void ICOReader::findColorInfo(QImage & image)
279{
280 if (icoAttrib.ncolors > 0) { // set color table
281 readColorTable(image);
282 } else if (icoAttrib.nbits == 16) { // don't support RGB values for 15/16 bpp
283 image = QImage();
284 }
285}
286
287void ICOReader::readColorTable(QImage & image)
288{
289 if (iod) {
290 image.setColorCount(icoAttrib.ncolors);
291 uchar rgb[4];
292 for (int i=0; i<icoAttrib.ncolors; i++) {
293 if (iod->read(data: (char*)rgb, maxlen: 4) != 4) {
294 image = QImage();
295 break;
296 }
297 image.setColor(i, c: qRgb(r: rgb[2],g: rgb[1],b: rgb[0]));
298 }
299 } else {
300 image = QImage();
301 }
302}
303
304void ICOReader::readBMP(QImage & image)
305{
306 if (icoAttrib.nbits == 1) { // 1 bit BMP image
307 read1BitBMP(image);
308 } else if (icoAttrib.nbits == 4) { // 4 bit BMP image
309 read4BitBMP(image);
310 } else if (icoAttrib.nbits == 8) {
311 read8BitBMP(image);
312 } else if (icoAttrib.nbits == 16 || icoAttrib.nbits == 24 || icoAttrib.nbits == 32 ) { // 16,24,32 bit BMP image
313 read16_24_32BMP(image);
314 }
315}
316
317
318/**
319 * NOTE: A 1 bit BMP is only flipped vertically, and not horizontally like all other color depths!
320 * (This is the same with the bitmask)
321 *
322 */
323void ICOReader::read1BitBMP(QImage & image)
324{
325 if (iod) {
326
327 int h = image.height();
328 qsizetype bpl = image.bytesPerLine();
329
330 while (--h >= 0) {
331 if (iod->read(data: (char*)image.scanLine(h),maxlen: bpl) != bpl) {
332 image = QImage();
333 break;
334 }
335 }
336 } else {
337 image = QImage();
338 }
339}
340
341void ICOReader::read4BitBMP(QImage & image)
342{
343 if (iod) {
344
345 int h = icoAttrib.h;
346 int buflen = ((icoAttrib.w+7)/8)*4;
347 uchar *buf = new uchar[buflen];
348 Q_CHECK_PTR(buf);
349
350 while (--h >= 0) {
351 if (iod->read(data: (char*)buf,maxlen: buflen) != buflen) {
352 image = QImage();
353 break;
354 }
355 uchar *p = image.scanLine(h);
356 uchar *b = buf;
357 for (int i=0; i<icoAttrib.w/2; i++) { // convert nibbles to bytes
358 *p++ = *b >> 4;
359 *p++ = *b++ & 0x0f;
360 }
361 if (icoAttrib.w & 1) // the last nibble
362 *p = *b >> 4;
363 }
364
365 delete [] buf;
366
367 } else {
368 image = QImage();
369 }
370}
371
372void ICOReader::read8BitBMP(QImage & image)
373{
374 if (iod) {
375
376 int h = icoAttrib.h;
377 qsizetype bpl = image.bytesPerLine();
378
379 while (--h >= 0) {
380 if (iod->read(data: (char *)image.scanLine(h), maxlen: bpl) != bpl) {
381 image = QImage();
382 break;
383 }
384 }
385 } else {
386 image = QImage();
387 }
388}
389
390void ICOReader::read16_24_32BMP(QImage & image)
391{
392 if (iod) {
393 int h = icoAttrib.h;
394 QRgb *p;
395 QRgb *end;
396 uchar *buf = new uchar[image.bytesPerLine()];
397 qsizetype bpl = ((qsizetype(icoAttrib.w)*icoAttrib.nbits+31)/32)*4;
398 uchar *b;
399
400 while (--h >= 0) {
401 p = (QRgb *)image.scanLine(h);
402 end = p + icoAttrib.w;
403 if (iod->read(data: (char *)buf, maxlen: bpl) != bpl) {
404 image = QImage();
405 break;
406 }
407 b = buf;
408 while (p < end) {
409 if (icoAttrib.nbits == 24)
410 *p++ = qRgb(r: *(b+2), g: *(b+1), b: *b);
411 else if (icoAttrib.nbits == 32)
412 *p++ = qRgba(r: *(b+2), g: *(b+1), b: *b, a: *(b+3));
413 b += icoAttrib.nbits/8;
414 }
415 }
416
417 delete[] buf;
418
419 } else {
420 image = QImage();
421 }
422}
423
424static const char icoOrigDepthKey[] = "_q_icoOrigDepth";
425
426QImage ICOReader::iconAt(int index)
427{
428 QImage img;
429
430 if (count() > index) { // forces header to be read
431
432 ICONDIRENTRY iconEntry;
433 if (readIconEntry(index, iconEntry: &iconEntry)) {
434
435 static const uchar pngMagicData[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
436
437 if (!iod->seek(pos: iconEntry.dwImageOffset)
438 || iconEntry.dwBytesInRes > iod->bytesAvailable())
439 return img;
440
441 const QByteArray pngMagic = QByteArray::fromRawData(data: (const char*)pngMagicData, size: sizeof(pngMagicData));
442 const bool isPngImage = (iod->read(maxlen: pngMagic.size()) == pngMagic);
443
444 if (isPngImage) {
445 iod->seek(pos: iconEntry.dwImageOffset);
446 QImage image = QImage::fromData(data: iod->read(maxlen: iconEntry.dwBytesInRes), format: "png");
447 image.setText(key: QLatin1String(icoOrigDepthKey), value: QString::number(iconEntry.wBitCount));
448 return image;
449 }
450
451 BMP_INFOHDR header;
452 if (readBMPHeader(imageOffset: iconEntry.dwImageOffset, header: &header)) {
453 icoAttrib.nbits = header.biBitCount ? header.biBitCount : iconEntry.wBitCount;
454
455 switch (icoAttrib.nbits) {
456 case 32:
457 case 24:
458 case 16:
459 icoAttrib.depth = 32;
460 break;
461 case 8:
462 case 4:
463 icoAttrib.depth = 8;
464 break;
465 case 1:
466 icoAttrib.depth = 1;
467 break;
468 default:
469 return img;
470 break;
471 }
472 if (icoAttrib.depth == 32) // there's no colormap
473 icoAttrib.ncolors = 0;
474 else // # colors used
475 icoAttrib.ncolors = header.biClrUsed ? uint(header.biClrUsed) : 1 << icoAttrib.nbits;
476 if (icoAttrib.ncolors > 256) //color table can't be more than 256
477 return img;
478 icoAttrib.w = iconEntry.bWidth;
479 if (icoAttrib.w == 0) // means 256 pixels
480 icoAttrib.w = header.biWidth;
481 icoAttrib.h = iconEntry.bHeight;
482 if (icoAttrib.h == 0) // means 256 pixels
483 icoAttrib.h = header.biHeight/2;
484 if (icoAttrib.w > 256 || icoAttrib.h > 256) // Max ico size
485 return img;
486
487 QImage::Format format = QImage::Format_ARGB32;
488 if (icoAttrib.nbits == 24)
489 format = QImage::Format_RGB32;
490 else if (icoAttrib.ncolors == 2 && icoAttrib.depth == 1)
491 format = QImage::Format_Mono;
492 else if (icoAttrib.ncolors > 0)
493 format = QImage::Format_Indexed8;
494
495 QImage image;
496 const QSize size(icoAttrib.w, icoAttrib.h);
497 if (QImageIOHandler::allocateImage(size, format, image: &image)) {
498 findColorInfo(image);
499 if (!image.isNull()) {
500 readBMP(image);
501 if (!image.isNull()) {
502 if (icoAttrib.nbits == 32) {
503 img = std::move(image).convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
504 } else {
505 QImage mask(image.width(), image.height(), QImage::Format_Mono);
506 if (!mask.isNull()) {
507 mask.setColorCount(2);
508 mask.setColor(i: 0, c: qRgba(r: 255,g: 255,b: 255,a: 0xff));
509 mask.setColor(i: 1, c: qRgba(r: 0 ,g: 0 ,b: 0 ,a: 0xff));
510 read1BitBMP(image&: mask);
511 if (!mask.isNull()) {
512 img = image;
513 img.setAlphaChannel(mask);
514 }
515 }
516 }
517 }
518 }
519 }
520 img.setText(key: QLatin1String(icoOrigDepthKey), value: QString::number(iconEntry.wBitCount));
521 }
522 }
523 }
524
525 return img;
526}
527
528
529/*!
530 Reads all the icons from the given \a device, and returns them as
531 a list of QImage objects.
532
533 Each image has an alpha channel that represents the mask from the
534 corresponding icon.
535
536 \sa write()
537*/
538QList<QImage> ICOReader::read(QIODevice *device)
539{
540 QList<QImage> images;
541
542 ICOReader reader(device);
543 const int N = reader.count();
544 images.reserve(asize: N);
545 for (int i = 0; i < N; i++)
546 images += reader.iconAt(index: i);
547
548 return images;
549}
550
551
552/*!
553 Writes all the QImages in the \a images list to the given \a
554 device. Returns \c true if the images are written successfully;
555 otherwise returns \c false.
556
557 The first image in the list is stored as the first icon in the
558 device, and is therefore used as the default icon by applications.
559 The alpha channel of each image is converted to a mask for each
560 corresponding icon.
561
562 \sa read()
563*/
564bool ICOReader::write(QIODevice *device, const QList<QImage> &images)
565{
566 bool retValue = false;
567
568 if (images.size()) {
569
570 qint64 origOffset = device->pos();
571
572 ICONDIR id;
573 id.idReserved = 0;
574 id.idType = 1;
575 id.idCount = images.size();
576
577 ICONDIRENTRY * entries = new ICONDIRENTRY[id.idCount];
578 BMP_INFOHDR * bmpHeaders = new BMP_INFOHDR[id.idCount];
579 QByteArray * imageData = new QByteArray[id.idCount];
580
581 for (int i=0; i<id.idCount; i++) {
582
583 QImage image = images[i];
584 // Scale down the image if it is larger than 256 pixels in either width or height
585 // because this is a maximum size of image in the ICO file.
586 if (image.width() > 256 || image.height() > 256)
587 {
588 image = image.scaled(w: 256, h: 256, aspectMode: Qt::KeepAspectRatio, mode: Qt::SmoothTransformation);
589 }
590 QImage maskImage(image.width(), image.height(), QImage::Format_Mono);
591 image = image.convertToFormat(f: QImage::Format_ARGB32);
592 maskImage.fill(color: Qt::color1);
593
594 int nbits = 32;
595 int bpl_bmp = ((image.width()*nbits+31)/32)*4;
596
597 entries[i].bColorCount = 0;
598 entries[i].bReserved = 0;
599 entries[i].wBitCount = nbits;
600 entries[i].bHeight = image.height() < 256 ? image.height() : 0; // 0 means 256
601 entries[i].bWidth = image.width() < 256 ? image.width() : 0; // 0 means 256
602 entries[i].dwBytesInRes = BMP_INFOHDR_SIZE + (bpl_bmp * image.height())
603 + (maskImage.bytesPerLine() * maskImage.height());
604 entries[i].wPlanes = 1;
605 if (i == 0)
606 entries[i].dwImageOffset = origOffset + ICONDIR_SIZE
607 + (id.idCount * ICONDIRENTRY_SIZE);
608 else
609 entries[i].dwImageOffset = entries[i-1].dwImageOffset + entries[i-1].dwBytesInRes;
610
611 bmpHeaders[i].biBitCount = entries[i].wBitCount;
612 bmpHeaders[i].biClrImportant = 0;
613 bmpHeaders[i].biClrUsed = entries[i].bColorCount;
614 bmpHeaders[i].biCompression = 0;
615 bmpHeaders[i].biHeight = entries[i].bHeight ? entries[i].bHeight * 2 : 256 * 2; // 2 is for the mask
616 bmpHeaders[i].biPlanes = entries[i].wPlanes;
617 bmpHeaders[i].biSize = BMP_INFOHDR_SIZE;
618 bmpHeaders[i].biSizeImage = entries[i].dwBytesInRes - BMP_INFOHDR_SIZE;
619 bmpHeaders[i].biWidth = entries[i].bWidth ? entries[i].bWidth : 256;
620 bmpHeaders[i].biXPelsPerMeter = 0;
621 bmpHeaders[i].biYPelsPerMeter = 0;
622
623 QBuffer buffer(&imageData[i]);
624 buffer.open(openMode: QIODevice::WriteOnly);
625
626 uchar *buf = new uchar[bpl_bmp];
627 uchar *b;
628 memset( s: buf, c: 0, n: bpl_bmp );
629 int y;
630 for (y = image.height() - 1; y >= 0; y--) { // write the image bits
631 // 32 bits
632 QRgb *p = (QRgb *)image.scanLine(y);
633 QRgb *end = p + image.width();
634 b = buf;
635 int x = 0;
636 while (p < end) {
637 *b++ = qBlue(rgb: *p);
638 *b++ = qGreen(rgb: *p);
639 *b++ = qRed(rgb: *p);
640 *b++ = qAlpha(rgb: *p);
641 if (qAlpha(rgb: *p) > 0) // Even mostly transparent pixels must not be masked away
642 maskImage.setPixel(x, y, index_or_rgb: 0);
643 p++;
644 x++;
645 }
646 buffer.write(data: (char*)buf, len: bpl_bmp);
647 }
648 delete[] buf;
649
650 // NOTE! !! The mask is only flipped vertically - not horizontally !!
651 for (y = maskImage.height() - 1; y >= 0; y--)
652 buffer.write(data: (char*)maskImage.scanLine(y), len: maskImage.bytesPerLine());
653 }
654
655 if (writeIconDir(iodev: device, iconDir: id)) {
656 int i;
657 bool bOK = true;
658 for (i = 0; i < id.idCount && bOK; i++) {
659 bOK = writeIconDirEntry(iodev: device, iconEntry: entries[i]);
660 }
661 if (bOK) {
662 for (i = 0; i < id.idCount && bOK; i++) {
663 bOK = writeBMPInfoHeader(iodev: device, header: bmpHeaders[i]);
664 bOK &= (device->write(data: imageData[i]) == (int) imageData[i].size());
665 }
666 retValue = bOK;
667 }
668 }
669
670 delete [] entries;
671 delete [] bmpHeaders;
672 delete [] imageData;
673
674 }
675 return retValue;
676}
677
678/*!
679 Constructs an instance of QtIcoHandler initialized to use \a device.
680*/
681QtIcoHandler::QtIcoHandler(QIODevice *device)
682{
683 m_currentIconIndex = 0;
684 setDevice(device);
685 m_pICOReader = new ICOReader(device);
686}
687
688/*!
689 Destructor for QtIcoHandler.
690*/
691QtIcoHandler::~QtIcoHandler()
692{
693 delete m_pICOReader;
694}
695
696QVariant QtIcoHandler::option(ImageOption option) const
697{
698 if (option == Size || option == ImageFormat) {
699 ICONDIRENTRY iconEntry;
700 if (m_pICOReader->readIconEntry(index: m_currentIconIndex, iconEntry: &iconEntry)) {
701 switch (option) {
702 case Size:
703 return QSize(iconEntry.bWidth ? iconEntry.bWidth : 256,
704 iconEntry.bHeight ? iconEntry.bHeight : 256);
705
706 case ImageFormat:
707 switch (iconEntry.wBitCount) {
708 case 2:
709 return QImage::Format_Mono;
710 case 24:
711 return QImage::Format_RGB32;
712 case 32:
713 return QImage::Format_ARGB32;
714 default:
715 return QImage::Format_Indexed8;
716 }
717 break;
718 default:
719 break;
720 }
721 }
722 }
723 return QVariant();
724}
725
726bool QtIcoHandler::supportsOption(ImageOption option) const
727{
728 return (option == Size || option == ImageFormat);
729}
730
731/*!
732 * Verifies if some values (magic bytes) are set as expected in the header of the file.
733 * If the magic bytes were found, it is assumed that the QtIcoHandler can read the file.
734 *
735 */
736bool QtIcoHandler::canRead() const
737{
738 bool bCanRead = false;
739 QIODevice *device = QImageIOHandler::device();
740 if (device) {
741 bCanRead = ICOReader::canRead(iodev: device);
742 if (bCanRead)
743 setFormat("ico");
744 } else {
745 qCWarning(lcIco, "QtIcoHandler::canRead() called with no device");
746 }
747 return bCanRead;
748}
749
750/*! This static function is used by the plugin code, and is provided for convenience only.
751 \a device must be an opened device with pointing to the start of the header data of the ICO file.
752*/
753bool QtIcoHandler::canRead(QIODevice *device)
754{
755 Q_ASSERT(device);
756 return ICOReader::canRead(iodev: device);
757}
758
759/*! \reimp
760
761*/
762bool QtIcoHandler::read(QImage *image)
763{
764 bool bSuccess = false;
765 QImage img = m_pICOReader->iconAt(index: m_currentIconIndex);
766
767 // Make sure we only write to \a image when we succeed.
768 if (!img.isNull()) {
769 bSuccess = true;
770 *image = img;
771 }
772
773 return bSuccess;
774}
775
776
777/*! \reimp
778
779*/
780bool QtIcoHandler::write(const QImage &image)
781{
782 QIODevice *device = QImageIOHandler::device();
783 QList<QImage> imgs;
784 imgs.append(t: image);
785 return ICOReader::write(device, images: imgs);
786}
787
788/*! \reimp
789
790*/
791int QtIcoHandler::imageCount() const
792{
793 return m_pICOReader->count();
794}
795
796/*! \reimp
797
798*/
799bool QtIcoHandler::jumpToImage(int imageNumber)
800{
801 if (imageNumber < imageCount()) {
802 m_currentIconIndex = imageNumber;
803 return true;
804 }
805
806 return false;
807}
808
809/*! \reimp
810
811*/
812bool QtIcoHandler::jumpToNextImage()
813{
814 return jumpToImage(imageNumber: m_currentIconIndex + 1);
815}
816
817QT_END_NAMESPACE
818

source code of qtbase/src/plugins/imageformats/ico/qicohandler.cpp