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