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