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 * ); |
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 ; |
137 | ICONDIR iconDir; |
138 | |
139 | }; |
140 | |
141 | // Data readers and writers that takes care of alignment and endian stuff. |
142 | static bool readIconDirEntry(QIODevice *iodev, ICONDIRENTRY *iconDirEntry) |
143 | { |
144 | if (iodev) |
145 | return (iodev->read(data: (char*)iconDirEntry, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE); |
146 | return false; |
147 | } |
148 | |
149 | static bool writeIconDirEntry(QIODevice *iodev, const ICONDIRENTRY &iconEntry) |
150 | { |
151 | if (iodev) |
152 | return iodev->write(data: (char*)&iconEntry, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE; |
153 | return false; |
154 | } |
155 | |
156 | static bool readIconDir(QIODevice *iodev, ICONDIR *iconDir) |
157 | { |
158 | if (iodev) |
159 | return (iodev->read(data: (char*)iconDir, ICONDIR_SIZE) == ICONDIR_SIZE); |
160 | return false; |
161 | } |
162 | |
163 | static bool writeIconDir(QIODevice *iodev, const ICONDIR &iconDir) |
164 | { |
165 | if (iodev) |
166 | return iodev->write(data: (char*)&iconDir, len: 6) == 6; |
167 | return false; |
168 | } |
169 | |
170 | static bool (QIODevice *iodev, BMP_INFOHDR *) |
171 | { |
172 | if (iodev) |
173 | return (iodev->read(data: (char*)pHeader, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE); |
174 | return false; |
175 | } |
176 | |
177 | static bool (QIODevice *iodev, const BMP_INFOHDR &) |
178 | { |
179 | if (iodev) |
180 | return iodev->write(data: (char*)&header, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE; |
181 | return false; |
182 | } |
183 | |
184 | |
185 | ICOReader::ICOReader(QIODevice * iodevice) |
186 | : iod(iodevice) |
187 | , startpos(0) |
188 | , headerRead(false) |
189 | { |
190 | } |
191 | |
192 | |
193 | int ICOReader::count() |
194 | { |
195 | if (readHeader()) |
196 | return iconDir.idCount; |
197 | return 0; |
198 | } |
199 | |
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, iconDir: &ikonDir)) { |
208 | qint64 readBytes = ICONDIR_SIZE; |
209 | if (readIconDirEntry(iodev, iconDirEntry: &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(c: (tmp >> 24) & 0xff); |
226 | iodev->ungetChar(c: (tmp >> 16) & 0xff); |
227 | iodev->ungetChar(c: (tmp >> 8) & 0xff); |
228 | iodev->ungetChar(c: tmp & 0xff); |
229 | |
230 | tmp = ikonDir.idEntries[0].dwBytesInRes; |
231 | iodev->ungetChar(c: (tmp >> 24) & 0xff); |
232 | iodev->ungetChar(c: (tmp >> 16) & 0xff); |
233 | iodev->ungetChar(c: (tmp >> 8) & 0xff); |
234 | iodev->ungetChar(c: tmp & 0xff); |
235 | |
236 | tmp = ikonDir.idEntries[0].wBitCount; |
237 | iodev->ungetChar(c: (tmp >> 8) & 0xff); |
238 | iodev->ungetChar(c: tmp & 0xff); |
239 | |
240 | tmp = ikonDir.idEntries[0].wPlanes; |
241 | iodev->ungetChar(c: (tmp >> 8) & 0xff); |
242 | iodev->ungetChar(c: tmp & 0xff); |
243 | |
244 | iodev->ungetChar(c: ikonDir.idEntries[0].bReserved); |
245 | iodev->ungetChar(c: ikonDir.idEntries[0].bColorCount); |
246 | iodev->ungetChar(c: ikonDir.idEntries[0].bHeight); |
247 | iodev->ungetChar(c: 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(c: (tmp >> 8) & 0xff); |
255 | iodev->ungetChar(c: tmp & 0xff); |
256 | |
257 | tmp = ikonDir.idType; |
258 | iodev->ungetChar(c: (tmp >> 8) & 0xff); |
259 | iodev->ungetChar(c: tmp & 0xff); |
260 | |
261 | tmp = ikonDir.idReserved; |
262 | iodev->ungetChar(c: (tmp >> 8) & 0xff); |
263 | iodev->ungetChar(c: tmp & 0xff); |
264 | } |
265 | } |
266 | if (!iodev->isSequential()) iodev->seek(pos: oldPos); |
267 | } |
268 | |
269 | return isProbablyICO; |
270 | } |
271 | |
272 | bool ICOReader::() |
273 | { |
274 | if (iod && !headerRead) { |
275 | startpos = iod->pos(); |
276 | if (readIconDir(iodev: iod, iconDir: &iconDir)) { |
277 | if (iconDir.idReserved == 0 && (iconDir.idType == 1 || iconDir.idType == 2)) |
278 | headerRead = true; |
279 | } |
280 | } |
281 | |
282 | return headerRead; |
283 | } |
284 | |
285 | bool ICOReader::readIconEntry(int index, ICONDIRENTRY *iconEntry) |
286 | { |
287 | if (readHeader()) { |
288 | if (iod->seek(pos: startpos + ICONDIR_SIZE + (index * ICONDIRENTRY_SIZE))) { |
289 | return readIconDirEntry(iodev: iod, iconDirEntry: iconEntry); |
290 | } |
291 | } |
292 | return false; |
293 | } |
294 | |
295 | |
296 | |
297 | bool ICOReader::(quint32 imageOffset, BMP_INFOHDR * ) |
298 | { |
299 | if (iod) { |
300 | if (iod->seek(pos: startpos + imageOffset)) { |
301 | if (readBMPInfoHeader(iodev: iod, pHeader: header)) { |
302 | return true; |
303 | } |
304 | } |
305 | } |
306 | return false; |
307 | } |
308 | |
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 | |
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(data: (char*)rgb, maxlen: 4) != 4) { |
325 | image = QImage(); |
326 | break; |
327 | } |
328 | image.setColor(i, c: qRgb(r: rgb[2],g: rgb[1],b: rgb[0])); |
329 | } |
330 | } else { |
331 | image = QImage(); |
332 | } |
333 | } |
334 | |
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 | */ |
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(data: (char*)image.scanLine(h),maxlen: bpl) != bpl) { |
363 | image = QImage(); |
364 | break; |
365 | } |
366 | } |
367 | } else { |
368 | image = QImage(); |
369 | } |
370 | } |
371 | |
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(data: (char*)buf,maxlen: 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 | |
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(data: (char *)image.scanLine(h), maxlen: bpl) != bpl) { |
412 | image = QImage(); |
413 | break; |
414 | } |
415 | } |
416 | } else { |
417 | image = QImage(); |
418 | } |
419 | } |
420 | |
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(data: (char *)buf, maxlen: bpl) != bpl) { |
435 | image = QImage(); |
436 | break; |
437 | } |
438 | b = buf; |
439 | while (p < end) { |
440 | if (icoAttrib.nbits == 24) |
441 | *p++ = qRgb(r: *(b+2), g: *(b+1), b: *b); |
442 | else if (icoAttrib.nbits == 32) |
443 | *p++ = qRgba(r: *(b+2), g: *(b+1), b: *b, a: *(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 | |
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: &iconEntry)) { |
465 | |
466 | static const uchar pngMagicData[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; |
467 | |
468 | iod->seek(pos: iconEntry.dwImageOffset); |
469 | |
470 | const QByteArray pngMagic = QByteArray::fromRawData((const char*)pngMagicData, size: sizeof(pngMagicData)); |
471 | const bool isPngImage = (iod->read(maxlen: pngMagic.size()) == pngMagic); |
472 | |
473 | if (isPngImage) { |
474 | iod->seek(pos: iconEntry.dwImageOffset); |
475 | QImage image = QImage::fromData(data: iod->read(maxlen: iconEntry.dwBytesInRes), format: "png" ); |
476 | image.setText(key: QLatin1String(icoOrigDepthKey), value: QString::number(iconEntry.wBitCount)); |
477 | return image; |
478 | } |
479 | |
480 | BMP_INFOHDR ; |
481 | if (readBMPHeader(imageOffset: iconEntry.dwImageOffset, header: &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(f: 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(i: 0, c: qRgba(r: 255,g: 255,b: 255,a: 0xff)); |
537 | mask.setColor(i: 1, c: qRgba(r: 0 ,g: 0 ,b: 0 ,a: 0xff)); |
538 | read1BitBMP(image&: mask); |
539 | if (!mask.isNull()) { |
540 | img = image; |
541 | img.setAlphaChannel(mask); |
542 | } |
543 | } |
544 | } |
545 | } |
546 | } |
547 | } |
548 | img.setText(key: QLatin1String(icoOrigDepthKey), value: 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 | */ |
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(asize: N); |
573 | for (int i = 0; i < N; i++) |
574 | images += reader.iconAt(index: 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 | */ |
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 * = 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(w: 256, h: 256, aspectMode: Qt::KeepAspectRatio, mode: Qt::SmoothTransformation); |
617 | } |
618 | QImage maskImage(image.width(), image.height(), QImage::Format_Mono); |
619 | image = image.convertToFormat(f: QImage::Format_ARGB32); |
620 | maskImage.fill(color: 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(openMode: QIODevice::WriteOnly); |
653 | |
654 | uchar *buf = new uchar[bpl_bmp]; |
655 | uchar *b; |
656 | memset( s: buf, c: 0, n: 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(rgb: *p); |
666 | *b++ = qGreen(rgb: *p); |
667 | *b++ = qRed(rgb: *p); |
668 | *b++ = qAlpha(rgb: *p); |
669 | if (qAlpha(rgb: *p) > 0) // Even mostly transparent pixels must not be masked away |
670 | maskImage.setPixel(x, y, index_or_rgb: 0); |
671 | p++; |
672 | x++; |
673 | } |
674 | buffer.write(data: (char*)buf, len: 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(data: (char*)maskImage.scanLine(y), len: maskImage.bytesPerLine()); |
681 | } |
682 | |
683 | if (writeIconDir(iodev: device, iconDir: id)) { |
684 | int i; |
685 | bool bOK = true; |
686 | for (i = 0; i < id.idCount && bOK; i++) { |
687 | bOK = writeIconDirEntry(iodev: device, iconEntry: entries[i]); |
688 | } |
689 | if (bOK) { |
690 | for (i = 0; i < id.idCount && bOK; i++) { |
691 | bOK = writeBMPInfoHeader(iodev: device, header: bmpHeaders[i]); |
692 | bOK &= (device->write(data: 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 | */ |
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 | */ |
719 | QtIcoHandler::~QtIcoHandler() |
720 | { |
721 | delete m_pICOReader; |
722 | } |
723 | |
724 | QVariant QtIcoHandler::option(ImageOption option) const |
725 | { |
726 | if (option == Size || option == ImageFormat) { |
727 | ICONDIRENTRY iconEntry; |
728 | if (m_pICOReader->readIconEntry(index: m_currentIconIndex, iconEntry: &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 | |
754 | 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 | */ |
764 | bool QtIcoHandler::canRead() const |
765 | { |
766 | bool bCanRead = false; |
767 | QIODevice *device = QImageIOHandler::device(); |
768 | if (device) { |
769 | bCanRead = ICOReader::canRead(iodev: device); |
770 | if (bCanRead) |
771 | setFormat("ico" ); |
772 | } else { |
773 | qWarning(msg: "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 | */ |
781 | bool QtIcoHandler::canRead(QIODevice *device) |
782 | { |
783 | Q_ASSERT(device); |
784 | return ICOReader::canRead(iodev: device); |
785 | } |
786 | |
787 | /*! \reimp |
788 | |
789 | */ |
790 | bool QtIcoHandler::read(QImage *image) |
791 | { |
792 | bool bSuccess = false; |
793 | QImage img = m_pICOReader->iconAt(index: 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 | */ |
808 | bool QtIcoHandler::write(const QImage &image) |
809 | { |
810 | QIODevice *device = QImageIOHandler::device(); |
811 | QVector<QImage> imgs; |
812 | imgs.append(t: image); |
813 | return ICOReader::write(device, images: imgs); |
814 | } |
815 | |
816 | /*! \reimp |
817 | |
818 | */ |
819 | int QtIcoHandler::imageCount() const |
820 | { |
821 | return m_pICOReader->count(); |
822 | } |
823 | |
824 | /*! \reimp |
825 | |
826 | */ |
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 | */ |
840 | bool QtIcoHandler::jumpToNextImage() |
841 | { |
842 | return jumpToImage(imageNumber: m_currentIconIndex + 1); |
843 | } |
844 | |
845 | QT_END_NAMESPACE |
846 | |