Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qicnshandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Alex Char.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
7
8#include <QtCore/qmath.h>
9#include <QtCore/qendian.h>
10#include <QtCore/qregularexpression.h>
11#include <QtCore/qbuffer.h>
12#include <QtGui/qimage.h>
13
14#ifndef QT_NO_DATASTREAM
15
17
18static const quint8 ICNSBlockHeaderSize = 8;
19
20static const QRgb ICNSColorTableMono[] = {
21 qRgb(0xFF, 0xFF, 0xFF),
22 qRgb(0x00, 0x00, 0x00)
23};
25
26static const QRgb ICNSColorTable4bit[] = {
27 qRgb(0xFF, 0xFF, 0xFF),
28 qRgb(0xFC, 0xF3, 0x05),
29 qRgb(0xFF, 0x64, 0x02),
30 qRgb(0xDD, 0x08, 0x06),
31 qRgb(0xF2, 0x08, 0x84),
32 qRgb(0x46, 0x00, 0xA5),
33 qRgb(0x00, 0x00, 0xD4),
34 qRgb(0x02, 0xAB, 0xEA),
35 qRgb(0x1F, 0xB7, 0x14),
36 qRgb(0x00, 0x64, 0x11),
37 qRgb(0x56, 0x2C, 0x05),
38 qRgb(0x90, 0x71, 0x3A),
39 qRgb(0xC0, 0xC0, 0xC0),
40 qRgb(0x80, 0x80, 0x80),
41 qRgb(0x40, 0x40, 0x40),
42 qRgb(0x00, 0x00, 0x00)
43};
45
46static const QRgb ICNSColorTable8bit[] = {
47 qRgb(0xFF, 0xFF, 0xFF),
48 qRgb(0xFF, 0xFF, 0xCC),
49 qRgb(0xFF, 0xFF, 0x99),
50 qRgb(0xFF, 0xFF, 0x66),
51 qRgb(0xFF, 0xFF, 0x33),
52 qRgb(0xFF, 0xFF, 0x00),
53 qRgb(0xFF, 0xCC, 0xFF),
54 qRgb(0xFF, 0xCC, 0xCC),
55 qRgb(0xFF, 0xCC, 0x99),
56 qRgb(0xFF, 0xCC, 0x66),
57 qRgb(0xFF, 0xCC, 0x33),
58 qRgb(0xFF, 0xCC, 0x00),
59 qRgb(0xFF, 0x99, 0xFF),
60 qRgb(0xFF, 0x99, 0xCC),
61 qRgb(0xFF, 0x99, 0x99),
62 qRgb(0xFF, 0x99, 0x66),
63 qRgb(0xFF, 0x99, 0x33),
64 qRgb(0xFF, 0x99, 0x00),
65 qRgb(0xFF, 0x66, 0xFF),
66 qRgb(0xFF, 0x66, 0xCC),
67 qRgb(0xFF, 0x66, 0x99),
68 qRgb(0xFF, 0x66, 0x66),
69 qRgb(0xFF, 0x66, 0x33),
70 qRgb(0xFF, 0x66, 0x00),
71 qRgb(0xFF, 0x33, 0xFF),
72 qRgb(0xFF, 0x33, 0xCC),
73 qRgb(0xFF, 0x33, 0x99),
74 qRgb(0xFF, 0x33, 0x66),
75 qRgb(0xFF, 0x33, 0x33),
76 qRgb(0xFF, 0x33, 0x00),
77 qRgb(0xFF, 0x00, 0xFF),
78 qRgb(0xFF, 0x00, 0xCC),
79 qRgb(0xFF, 0x00, 0x99),
80 qRgb(0xFF, 0x00, 0x66),
81 qRgb(0xFF, 0x00, 0x33),
82 qRgb(0xFF, 0x00, 0x00),
83 qRgb(0xCC, 0xFF, 0xFF),
84 qRgb(0xCC, 0xFF, 0xCC),
85 qRgb(0xCC, 0xFF, 0x99),
86 qRgb(0xCC, 0xFF, 0x66),
87 qRgb(0xCC, 0xFF, 0x33),
88 qRgb(0xCC, 0xFF, 0x00),
89 qRgb(0xCC, 0xCC, 0xFF),
90 qRgb(0xCC, 0xCC, 0xCC),
91 qRgb(0xCC, 0xCC, 0x99),
92 qRgb(0xCC, 0xCC, 0x66),
93 qRgb(0xCC, 0xCC, 0x33),
94 qRgb(0xCC, 0xCC, 0x00),
95 qRgb(0xCC, 0x99, 0xFF),
96 qRgb(0xCC, 0x99, 0xCC),
97 qRgb(0xCC, 0x99, 0x99),
98 qRgb(0xCC, 0x99, 0x66),
99 qRgb(0xCC, 0x99, 0x33),
100 qRgb(0xCC, 0x99, 0x00),
101 qRgb(0xCC, 0x66, 0xFF),
102 qRgb(0xCC, 0x66, 0xCC),
103 qRgb(0xCC, 0x66, 0x99),
104 qRgb(0xCC, 0x66, 0x66),
105 qRgb(0xCC, 0x66, 0x33),
106 qRgb(0xCC, 0x66, 0x00),
107 qRgb(0xCC, 0x33, 0xFF),
108 qRgb(0xCC, 0x33, 0xCC),
109 qRgb(0xCC, 0x33, 0x99),
110 qRgb(0xCC, 0x33, 0x66),
111 qRgb(0xCC, 0x33, 0x33),
112 qRgb(0xCC, 0x33, 0x00),
113 qRgb(0xCC, 0x00, 0xFF),
114 qRgb(0xCC, 0x00, 0xCC),
115 qRgb(0xCC, 0x00, 0x99),
116 qRgb(0xCC, 0x00, 0x66),
117 qRgb(0xCC, 0x00, 0x33),
118 qRgb(0xCC, 0x00, 0x00),
119 qRgb(0x99, 0xFF, 0xFF),
120 qRgb(0x99, 0xFF, 0xCC),
121 qRgb(0x99, 0xFF, 0x99),
122 qRgb(0x99, 0xFF, 0x66),
123 qRgb(0x99, 0xFF, 0x33),
124 qRgb(0x99, 0xFF, 0x00),
125 qRgb(0x99, 0xCC, 0xFF),
126 qRgb(0x99, 0xCC, 0xCC),
127 qRgb(0x99, 0xCC, 0x99),
128 qRgb(0x99, 0xCC, 0x66),
129 qRgb(0x99, 0xCC, 0x33),
130 qRgb(0x99, 0xCC, 0x00),
131 qRgb(0x99, 0x99, 0xFF),
132 qRgb(0x99, 0x99, 0xCC),
133 qRgb(0x99, 0x99, 0x99),
134 qRgb(0x99, 0x99, 0x66),
135 qRgb(0x99, 0x99, 0x33),
136 qRgb(0x99, 0x99, 0x00),
137 qRgb(0x99, 0x66, 0xFF),
138 qRgb(0x99, 0x66, 0xCC),
139 qRgb(0x99, 0x66, 0x99),
140 qRgb(0x99, 0x66, 0x66),
141 qRgb(0x99, 0x66, 0x33),
142 qRgb(0x99, 0x66, 0x00),
143 qRgb(0x99, 0x33, 0xFF),
144 qRgb(0x99, 0x33, 0xCC),
145 qRgb(0x99, 0x33, 0x99),
146 qRgb(0x99, 0x33, 0x66),
147 qRgb(0x99, 0x33, 0x33),
148 qRgb(0x99, 0x33, 0x00),
149 qRgb(0x99, 0x00, 0xFF),
150 qRgb(0x99, 0x00, 0xCC),
151 qRgb(0x99, 0x00, 0x99),
152 qRgb(0x99, 0x00, 0x66),
153 qRgb(0x99, 0x00, 0x33),
154 qRgb(0x99, 0x00, 0x00),
155 qRgb(0x66, 0xFF, 0xFF),
156 qRgb(0x66, 0xFF, 0xCC),
157 qRgb(0x66, 0xFF, 0x99),
158 qRgb(0x66, 0xFF, 0x66),
159 qRgb(0x66, 0xFF, 0x33),
160 qRgb(0x66, 0xFF, 0x00),
161 qRgb(0x66, 0xCC, 0xFF),
162 qRgb(0x66, 0xCC, 0xCC),
163 qRgb(0x66, 0xCC, 0x99),
164 qRgb(0x66, 0xCC, 0x66),
165 qRgb(0x66, 0xCC, 0x33),
166 qRgb(0x66, 0xCC, 0x00),
167 qRgb(0x66, 0x99, 0xFF),
168 qRgb(0x66, 0x99, 0xCC),
169 qRgb(0x66, 0x99, 0x99),
170 qRgb(0x66, 0x99, 0x66),
171 qRgb(0x66, 0x99, 0x33),
172 qRgb(0x66, 0x99, 0x00),
173 qRgb(0x66, 0x66, 0xFF),
174 qRgb(0x66, 0x66, 0xCC),
175 qRgb(0x66, 0x66, 0x99),
176 qRgb(0x66, 0x66, 0x66),
177 qRgb(0x66, 0x66, 0x33),
178 qRgb(0x66, 0x66, 0x00),
179 qRgb(0x66, 0x33, 0xFF),
180 qRgb(0x66, 0x33, 0xCC),
181 qRgb(0x66, 0x33, 0x99),
182 qRgb(0x66, 0x33, 0x66),
183 qRgb(0x66, 0x33, 0x33),
184 qRgb(0x66, 0x33, 0x00),
185 qRgb(0x66, 0x00, 0xFF),
186 qRgb(0x66, 0x00, 0xCC),
187 qRgb(0x66, 0x00, 0x99),
188 qRgb(0x66, 0x00, 0x66),
189 qRgb(0x66, 0x00, 0x33),
190 qRgb(0x66, 0x00, 0x00),
191 qRgb(0x33, 0xFF, 0xFF),
192 qRgb(0x33, 0xFF, 0xCC),
193 qRgb(0x33, 0xFF, 0x99),
194 qRgb(0x33, 0xFF, 0x66),
195 qRgb(0x33, 0xFF, 0x33),
196 qRgb(0x33, 0xFF, 0x00),
197 qRgb(0x33, 0xCC, 0xFF),
198 qRgb(0x33, 0xCC, 0xCC),
199 qRgb(0x33, 0xCC, 0x99),
200 qRgb(0x33, 0xCC, 0x66),
201 qRgb(0x33, 0xCC, 0x33),
202 qRgb(0x33, 0xCC, 0x00),
203 qRgb(0x33, 0x99, 0xFF),
204 qRgb(0x33, 0x99, 0xCC),
205 qRgb(0x33, 0x99, 0x99),
206 qRgb(0x33, 0x99, 0x66),
207 qRgb(0x33, 0x99, 0x33),
208 qRgb(0x33, 0x99, 0x00),
209 qRgb(0x33, 0x66, 0xFF),
210 qRgb(0x33, 0x66, 0xCC),
211 qRgb(0x33, 0x66, 0x99),
212 qRgb(0x33, 0x66, 0x66),
213 qRgb(0x33, 0x66, 0x33),
214 qRgb(0x33, 0x66, 0x00),
215 qRgb(0x33, 0x33, 0xFF),
216 qRgb(0x33, 0x33, 0xCC),
217 qRgb(0x33, 0x33, 0x99),
218 qRgb(0x33, 0x33, 0x66),
219 qRgb(0x33, 0x33, 0x33),
220 qRgb(0x33, 0x33, 0x00),
221 qRgb(0x33, 0x00, 0xFF),
222 qRgb(0x33, 0x00, 0xCC),
223 qRgb(0x33, 0x00, 0x99),
224 qRgb(0x33, 0x00, 0x66),
225 qRgb(0x33, 0x00, 0x33),
226 qRgb(0x33, 0x00, 0x00),
227 qRgb(0x00, 0xFF, 0xFF),
228 qRgb(0x00, 0xFF, 0xCC),
229 qRgb(0x00, 0xFF, 0x99),
230 qRgb(0x00, 0xFF, 0x66),
231 qRgb(0x00, 0xFF, 0x33),
232 qRgb(0x00, 0xFF, 0x00),
233 qRgb(0x00, 0xCC, 0xFF),
234 qRgb(0x00, 0xCC, 0xCC),
235 qRgb(0x00, 0xCC, 0x99),
236 qRgb(0x00, 0xCC, 0x66),
237 qRgb(0x00, 0xCC, 0x33),
238 qRgb(0x00, 0xCC, 0x00),
239 qRgb(0x00, 0x99, 0xFF),
240 qRgb(0x00, 0x99, 0xCC),
241 qRgb(0x00, 0x99, 0x99),
242 qRgb(0x00, 0x99, 0x66),
243 qRgb(0x00, 0x99, 0x33),
244 qRgb(0x00, 0x99, 0x00),
245 qRgb(0x00, 0x66, 0xFF),
246 qRgb(0x00, 0x66, 0xCC),
247 qRgb(0x00, 0x66, 0x99),
248 qRgb(0x00, 0x66, 0x66),
249 qRgb(0x00, 0x66, 0x33),
250 qRgb(0x00, 0x66, 0x00),
251 qRgb(0x00, 0x33, 0xFF),
252 qRgb(0x00, 0x33, 0xCC),
253 qRgb(0x00, 0x33, 0x99),
254 qRgb(0x00, 0x33, 0x66),
255 qRgb(0x00, 0x33, 0x33),
256 qRgb(0x00, 0x33, 0x00),
257 qRgb(0x00, 0x00, 0xFF),
258 qRgb(0x00, 0x00, 0xCC),
259 qRgb(0x00, 0x00, 0x99),
260 qRgb(0x00, 0x00, 0x66),
261 qRgb(0x00, 0x00, 0x33),
262 qRgb(0xEE, 0x00, 0x00),
263 qRgb(0xDD, 0x00, 0x00),
264 qRgb(0xBB, 0x00, 0x00),
265 qRgb(0xAA, 0x00, 0x00),
266 qRgb(0x88, 0x00, 0x00),
267 qRgb(0x77, 0x00, 0x00),
268 qRgb(0x55, 0x00, 0x00),
269 qRgb(0x44, 0x00, 0x00),
270 qRgb(0x22, 0x00, 0x00),
271 qRgb(0x11, 0x00, 0x00),
272 qRgb(0x00, 0xEE, 0x00),
273 qRgb(0x00, 0xDD, 0x00),
274 qRgb(0x00, 0xBB, 0x00),
275 qRgb(0x00, 0xAA, 0x00),
276 qRgb(0x00, 0x88, 0x00),
277 qRgb(0x00, 0x77, 0x00),
278 qRgb(0x00, 0x55, 0x00),
279 qRgb(0x00, 0x44, 0x00),
280 qRgb(0x00, 0x22, 0x00),
281 qRgb(0x00, 0x11, 0x00),
282 qRgb(0x00, 0x00, 0xEE),
283 qRgb(0x00, 0x00, 0xDD),
284 qRgb(0x00, 0x00, 0xBB),
285 qRgb(0x00, 0x00, 0xAA),
286 qRgb(0x00, 0x00, 0x88),
287 qRgb(0x00, 0x00, 0x77),
288 qRgb(0x00, 0x00, 0x55),
289 qRgb(0x00, 0x00, 0x44),
290 qRgb(0x00, 0x00, 0x22),
291 qRgb(0x00, 0x00, 0x11),
292 qRgb(0xEE, 0xEE, 0xEE),
293 qRgb(0xDD, 0xDD, 0xDD),
294 qRgb(0xBB, 0xBB, 0xBB),
295 qRgb(0xAA, 0xAA, 0xAA),
296 qRgb(0x88, 0x88, 0x88),
297 qRgb(0x77, 0x77, 0x77),
298 qRgb(0x55, 0x55, 0x55),
299 qRgb(0x44, 0x44, 0x44),
300 qRgb(0x22, 0x22, 0x22),
301 qRgb(0x11, 0x11, 0x11),
302 qRgb(0x00, 0x00, 0x00)
303};
305
306static inline QDataStream &operator>>(QDataStream &in, ICNSBlockHeader &p)
307{
308 in >> p.ostype;
309 in >> p.length;
310 return in;
311}
312
313static inline QDataStream &operator<<(QDataStream &out, const ICNSBlockHeader &p)
314{
315 out << p.ostype;
316 out << p.length;
317 return out;
318}
319
320static inline bool isPowOf2OrDividesBy16(quint32 u, qreal r)
321{
322 return u == r && ((u % 16 == 0) || (r >= 16 && (u & (u - 1)) == 0));
323}
324
325static inline bool isBlockHeaderValid(const ICNSBlockHeader &header, quint64 bound = 0)
326{
327 return header.ostype != 0 &&
328 (bound == 0 ||
329 // qBound can be used but requires checking the limits first
330 // this requires less operations
331 (ICNSBlockHeaderSize <= header.length && header.length <= bound));
332}
333
334static inline bool isIconCompressed(const ICNSEntry &icon)
335{
337}
338
339static inline bool isMaskSuitable(const ICNSEntry &mask, const ICNSEntry &icon, ICNSEntry::Depth target)
340{
341 return mask.variant == icon.variant && mask.depth == target
342 && mask.height == icon.height && mask.width == icon.width;
343}
344
345static inline QByteArray nameFromOSType(quint32 ostype)
346{
347 const quint32 bytes = qToBigEndian(ostype);
348 return QByteArray((const char*)&bytes, 4);
349}
350
351static inline quint32 nameToOSType(const QByteArray &ostype)
352{
353 if (ostype.size() != 4)
354 return 0;
355 return qFromBigEndian(*reinterpret_cast<const quint32*>(ostype.constData()));
356}
357
358static inline QByteArray nameForCompressedIcon(quint8 iconNumber)
359{
360 const bool portable = iconNumber < 7;
361 const QByteArray base = portable ? QByteArrayLiteral("icp") : QByteArrayLiteral("ic");
362 if (!portable && iconNumber < 10)
363 return base + "0" + QByteArray::number(iconNumber);
364 return base + QByteArray::number(iconNumber);
365}
366
367static inline QList<QRgb> getColorTable(ICNSEntry::Depth depth)
368{
369 QList<QRgb> table;
370 uint n = 1 << depth;
371 const QRgb *data;
372 switch (depth) {
374 data = ICNSColorTableMono;
375 break;
377 data = ICNSColorTable4bit;
378 break;
380 data = ICNSColorTable8bit;
381 break;
382 default:
383 Q_UNREACHABLE();
384 break;
385 }
386 table.resize(n);
387 memcpy(table.data(), data, sizeof(QRgb) * n);
388 return table;
389}
390
391static bool parseIconEntryData(ICNSEntry &icon, QIODevice *device)
392{
393 const qint64 oldPos = device->pos();
394 if (oldPos != icon.dataOffset && !device->seek(icon.dataOffset))
395 return false;
396
397 const QByteArray magic = device->peek(12);
398 const bool isPNG = magic.startsWith(QByteArrayLiteral("\211PNG\r\n\032\n\000\000\000\r"));
399 const bool isJP2 = !isPNG && magic == QByteArrayLiteral("\000\000\000\014jP \r\n\207\n");
400 if (isPNG || isJP2) {
401 // TODO: Add parsing of png/jp2 headers to enable feature reporting by plugin?
404 }
405 if (oldPos != icon.dataOffset && !device->seek(oldPos))
406 return false;
407 return true;
408}
409
411{
412 const QString ostype = QString::fromLatin1(nameFromOSType(icon.ostype));
413 // Typical OSType naming: <junk><group><depth><mask>;
414 // For icons OSType should be strictly alphanumeric + '#' character for masks/mono.
415 const QString ptrn = QStringLiteral("^(?<junk>[a-z|A-Z]{0,4})(?<group>[a-z|A-Z]{1})(?<depth>[\\d]{0,2})(?<mask>[#mk]{0,2})$");
416 QRegularExpression regexp(ptrn);
417 QRegularExpressionMatch match = regexp.match(ostype);
418 if (!match.hasMatch()) {
419 qWarning("parseIconEntryInfo(): Failed, OSType doesn't match: \"%s\"", qPrintable(ostype));
420 return false;
421 }
422 const QString group = match.captured(QStringLiteral("group"));
423 const QString depth = match.captured(QStringLiteral("depth"));
424 const QString mask = match.captured(QStringLiteral("mask"));
425 // Icon group:
426 if (!group.isEmpty())
427 icon.group = ICNSEntry::Group(group.at(0).toLatin1());
428
429 // That's enough for compressed ones
430 if (isIconCompressed(icon))
431 return true;
432 // Icon depth:
433 if (!depth.isEmpty()) {
434 const uint depthUInt = depth.toUInt();
435 if (depthUInt > 32)
436 return false;
437 icon.depth = ICNSEntry::Depth(depthUInt);
438 }
439 // Try mono if depth not found
442 // Detect size:
443 const qreal bytespp = (qreal)icon.depth / 8;
444 const qreal r1 = qSqrt(icon.dataLength / bytespp);
445 const qreal r2 = qSqrt((icon.dataLength / bytespp) / 2);
446 const quint32 r1u = qRound(r1);
447 const quint32 r2u = qRound(r2);
448 const bool singleEntry = isPowOf2OrDividesBy16(r1u, r1);
449 const bool doubleSize = isPowOf2OrDividesBy16(r2u, r2);
450 if (singleEntry) {
451 icon.flags = mask.isEmpty() ? ICNSEntry::IsIcon : ICNSEntry::IsMask;
453 icon.width = r1u;
454 icon.height = r1u;
455 } else if (doubleSize) {
458 icon.width = r2u;
459 icon.height = r2u;
460 } else if (icon.group == ICNSEntry::GroupMini) {
461 // Legacy 16x12 icons are an exception from the generic square formula
462 const bool doubleSize = icon.dataLength == 192 * bytespp * 2;
465 icon.width = 16;
466 icon.height = 12;
467 } else if (icon.depth == ICNSEntry::Depth32bit) {
468 // We have a formula mismatch in a 32bit icon there, probably RLE24
470 icon.flags = mask.isEmpty() ? ICNSEntry::IsIcon : ICNSEntry::IsMask;
471 switch (icon.group) {
473 icon.width = 16;
474 break;
476 icon.width = 32;
477 break;
479 icon.width = 48;
480 break;
482 icon.width = 128;
483 break;
484 default:
485 qWarning("parseIconEntryInfo(): Failed, 32bit icon from an unknown group. OSType: \"%s\"",
486 qPrintable(ostype));
487 }
488 icon.height = icon.width;
489 }
490 // Sanity check
491 if (icon.width == 0 || icon.width > 4096)
492 return false;
493 return true;
494}
495
496static QImage readMask(const ICNSEntry &mask, QDataStream &stream)
497{
498 if ((mask.flags & ICNSEntry::IsMask) == 0)
499 return QImage();
501 qWarning("readMask(): Failed, unusual bit depth: %u OSType: \"%s\"",
502 mask.depth, nameFromOSType(mask.ostype).constData());
503 return QImage();
504 }
505 const bool isMono = mask.depth == ICNSEntry::DepthMono;
506 const bool doubleSize = mask.flags == ICNSEntry::IconPlusMask;
507 const quint32 imageDataSize = (mask.width * mask.height * mask.depth) / 8;
508 const qint64 pos = doubleSize ? (mask.dataOffset + imageDataSize) : mask.dataOffset;
509 const qint64 oldPos = stream.device()->pos();
510 if (!stream.device()->seek(pos))
511 return QImage();
512 QImage img;
513 if (!QImageIOHandler::allocateImage(QSize(mask.width, mask.height), QImage::Format_RGB32, &img))
514 return QImage();
515 quint8 byte = 0;
516 quint32 pixel = 0;
517 for (quint32 y = 0; y < mask.height; y++) {
518 QRgb *line = reinterpret_cast<QRgb *>(img.scanLine(y));
519 for (quint32 x = 0; x < mask.width; x++) {
520 if (pixel % (8 / mask.depth) == 0)
521 stream >> byte;
522 else if (isMono)
523 byte <<= 1;
524 const quint8 alpha = isMono ? (((byte >> 7) & 0x01) * 255) : byte;
525 line[x] = qRgb(alpha, alpha, alpha);
526 pixel++;
527 }
528 }
529 stream.device()->seek(oldPos);
530 return img;
531}
532
533template <ICNSEntry::Depth depth>
534static QImage readLowDepthIcon(const ICNSEntry &icon, QDataStream &stream)
535{
537 || depth == ICNSEntry::Depth8bit);
538
539 const bool isMono = depth == ICNSEntry::DepthMono;
540 const QImage::Format format = isMono ? QImage::Format_Mono : QImage::Format_Indexed8;
541 const QList<QRgb> colortable = getColorTable(depth);
542 if (colortable.isEmpty())
543 return QImage();
544 QImage img;
545 if (!QImageIOHandler::allocateImage(QSize(icon.width, icon.height), format, &img))
546 return QImage();
547 img.setColorTable(colortable);
548 quint32 pixel = 0;
549 quint8 byte = 0;
550 for (quint32 y = 0; y < icon.height; y++) {
551 for (quint32 x = 0; x < icon.width; x++) {
552 if (pixel % (8 / depth) == 0)
553 stream >> byte;
554 quint8 cindex;
555 switch (depth) {
557 cindex = (byte >> 7) & 0x01; // left 1 bit
558 byte <<= 1;
559 break;
561 cindex = (byte >> 4) & 0x0F; // left 4 bits
562 byte <<= 4;
563 break;
564 default:
565 cindex = byte; // 8 bits
566 break;
567 }
568 img.setPixel(x, y, cindex);
569 pixel++;
570 }
571 }
572 return img;
573}
574
575static QImage read32bitIcon(const ICNSEntry &icon, QDataStream &stream)
576{
577 QImage img;
578 if (!QImageIOHandler::allocateImage(QSize(icon.width, icon.height), QImage::Format_RGB32, &img))
579 return QImage();
580 if (icon.dataFormat != ICNSEntry::RLE24) {
581 for (quint32 y = 0; y < icon.height; y++) {
582 QRgb *line = reinterpret_cast<QRgb *>(img.scanLine(y));
583 for (quint32 x = 0; x < icon.width; x++) {
584 quint8 r, g, b, a;
585 stream >> r >> g >> b >> a;
586 line[x] = qRgb(r, g, b);
587 }
588 }
589 } else {
590 img.fill(Qt::black); // rle data may not cover all, ensure initialized
591 const quint32 estPxsNum = icon.width * icon.height;
592 const QByteArray &bytes = stream.device()->peek(4);
593 if (bytes.isEmpty())
594 return QImage();
595 // Zero-padding may be present:
596 if (qFromBigEndian<quint32>(*bytes.constData()) == 0)
597 stream.skipRawData(4);
598 for (quint8 colorNRun = 0; colorNRun < 3; colorNRun++) {
599 quint32 pixel = 0;
600 QRgb *line = 0;
601 while (pixel < estPxsNum && !stream.atEnd()) {
602 quint8 byte, value;
603 stream >> byte;
604 const bool bitIsClear = (byte & 0x80) == 0;
605 // If high bit is clear: run of different values; else: same value
606 quint8 runLength = bitIsClear ? ((0xFF & byte) + 1) : ((0xFF & byte) - 125);
607 // Length of the run for for different values: 1 <= len <= 128
608 // Length of the run for same values: 3 <= len <= 130
609 if (!bitIsClear)
610 stream >> value;
611 for (quint8 i = 0; i < runLength && pixel < estPxsNum; i++) {
612 if (bitIsClear)
613 stream >> value;
614 const quint32 y = pixel / icon.height;
615 const quint32 x = pixel - (icon.width * y);
616 if (pixel % icon.height == 0)
617 line = reinterpret_cast<QRgb *>(img.scanLine(y));
618 QRgb rgb = line[x];
619 const int r = (colorNRun == 0) ? value : qRed(rgb);
620 const int g = (colorNRun == 1) ? value : qGreen(rgb);
621 const int b = (colorNRun == 2) ? value : qBlue(rgb);
622 line[x] = qRgb(r, g, b);
623 pixel++;
624 }
625 }
626 }
627 }
628 return img;
629}
630
632 m_currentIconIndex(0), m_state(ScanNotScanned)
633{
634}
635
636bool QICNSHandler::canRead(QIODevice *device)
637{
638 if (!device || !device->isReadable()) {
639 qWarning("QICNSHandler::canRead() called without a readable device");
640 return false;
641 }
642
643 if (device->peek(4) == QByteArrayLiteral("icns")) {
644 if (device->isSequential()) {
645 qWarning("QICNSHandler::canRead() called on a sequential device");
646 return false;
647 }
648 return true;
649 }
650
651 return false;
652}
653
655{
656 if (m_state == ScanNotScanned && !canRead(device()))
657 return false;
658
659 if (m_state != ScanError) {
660 setFormat(QByteArrayLiteral("icns"));
661 return true;
662 }
663
664 return false;
665}
666
667bool QICNSHandler::read(QImage *outImage)
668{
669 QImage img;
670 if (!ensureScanned() || m_currentIconIndex >= m_icons.size()) {
671 qWarning("QICNSHandler::read(): The device wasn't parsed properly!");
672 return false;
673 }
674
675 const ICNSEntry &icon = m_icons.at(m_currentIconIndex);
676 QDataStream stream(device());
677 stream.setByteOrder(QDataStream::BigEndian);
678 if (!device()->seek(icon.dataOffset))
679 return false;
680
681 switch (icon.dataFormat) {
683 case ICNSEntry::RLE24:
684 if (qMin(icon.width, icon.height) == 0)
685 break;
686 switch (icon.depth) {
688 img = readLowDepthIcon<ICNSEntry::DepthMono>(icon, stream);
689 break;
691 img = readLowDepthIcon<ICNSEntry::Depth4bit>(icon, stream);
692 break;
694 img = readLowDepthIcon<ICNSEntry::Depth8bit>(icon, stream);
695 break;
696 case ICNSEntry::Depth32bit:
697 img = read32bitIcon(icon, stream);
698 break;
699 default:
700 qWarning("QICNSHandler::read(): Failed, unsupported icon bit depth: %u, OSType: \"%s\"",
701 icon.depth, nameFromOSType(icon.ostype).constData());
702 }
703 if (!img.isNull()) {
704 QImage alpha = readMask(getIconMask(icon), stream);
705 if (!alpha.isNull())
706 img.setAlphaChannel(alpha);
707 }
708 break;
709 default:
710 const char *format = 0;
711 if (icon.dataFormat == ICNSEntry::PNG)
712 format = "png";
713 else if (icon.dataFormat == ICNSEntry::JP2)
714 format = "jp2";
715 // Even if JP2 or PNG magic is not detected, try anyway for unknown formats
716 if (qint64(icon.dataLength) <= device()->bytesAvailable())
717 img = QImage::fromData(device()->read(icon.dataLength), format);
718 if (img.isNull()) {
719 if (format == 0)
720 format = "unknown";
721 qWarning("QICNSHandler::read(): Failed, compressed format \"%s\" is not supported "
722 "by your Qt library or this file is corrupt. OSType: \"%s\"",
723 format, nameFromOSType(icon.ostype).constData());
724 }
725 }
726 *outImage = img;
727 return !img.isNull();
728}
729
730bool QICNSHandler::write(const QImage &image)
731{
732 /*
733 Notes:
734 * Experimental implementation. Just for simple converting tasks / testing purposes.
735 * Min. size is 16x16, Max. size is 1024x1024.
736 * Performs downscale to a square image if width != height.
737 * Performs upscale to 16x16, if the image is smaller.
738 * Performs downscale to a nearest power of two if size is not a power of two.
739 * Currently uses non-hardcoded OSTypes.
740 */
741 QIODevice *device = this->device();
742 if (!device->isWritable() || image.isNull() || qMin(image.width(), image.height()) == 0)
743 return false;
744 const int minSize = qMin(image.width(), image.height());
745 const int oldSize = (minSize < 16) ? 16 : minSize;
746 // Calc power of two:
747 int size = oldSize;
748 uint pow = 0;
749 // Note: Values over 10 are reserved for retina icons.
750 while (pow < 10 && (size >>= 1))
751 pow++;
752 const int newSize = 1 << pow;
753 QImage img = image;
754 // Let's enforce resizing if size differs:
755 if (newSize != oldSize || qMax(image.width(), image.height()) != minSize)
756 img = img.scaled(newSize, newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
757 // Construct OSType and headers:
758 const quint32 ostype = nameToOSType(nameForCompressedIcon(pow));
759 ICNSBlockHeader fileHeader;
760 fileHeader.ostype = ICNSBlockHeader::TypeIcns;
761 ICNSBlockHeader tocHeader;
762 tocHeader.ostype = ICNSBlockHeader::TypeToc;
763 ICNSBlockHeader iconEntry;
764 iconEntry.ostype = ostype;
765 QByteArray imageData;
766 QBuffer buffer(&imageData);
767 if (!buffer.open(QIODevice::WriteOnly) || !img.save(&buffer, "png"))
768 return false;
769 buffer.close();
770 iconEntry.length = ICNSBlockHeaderSize + imageData.size();
771 tocHeader.length = ICNSBlockHeaderSize * 2;
772 fileHeader.length = ICNSBlockHeaderSize + tocHeader.length + iconEntry.length;
773 if (!isBlockHeaderValid(iconEntry))
774 return false;
775
776 QDataStream stream(device);
777 // iconEntry is also a TOC entry
778 stream << fileHeader << tocHeader << iconEntry << iconEntry;
779 stream.writeRawData(imageData.constData(), imageData.size());
780 return stream.status() == QDataStream::Ok;
781}
782
783bool QICNSHandler::supportsOption(ImageOption option) const
784{
785 return option == SubType;
786}
787
788QVariant QICNSHandler::option(ImageOption option) const
789{
790 if (!supportsOption(option) || !ensureScanned())
791 return QVariant();
792
793 if (option == SubType) {
794 if (imageCount() > 0 && m_currentIconIndex < imageCount()) {
795 const ICNSEntry &icon = m_icons.at(m_currentIconIndex);
796 if (icon.variant != 0)
797 return QByteArray(nameFromOSType(icon.variant) + '-' + nameFromOSType(icon.ostype));
798 return nameFromOSType(icon.ostype);
799 }
800 }
801
802 return QVariant();
803}
804
806{
807 if (!ensureScanned())
808 return 0;
809
810 return m_icons.size();
811}
812
813bool QICNSHandler::jumpToImage(int imageNumber)
814{
815 if (imageNumber >= imageCount())
816 return false;
817
818 m_currentIconIndex = imageNumber;
819 return true;
820}
821
823{
824 return jumpToImage(m_currentIconIndex + 1);
825}
826
827bool QICNSHandler::ensureScanned() const
828{
829 if (m_state == ScanNotScanned) {
830 QICNSHandler *that = const_cast<QICNSHandler *>(this);
831 that->m_state = that->scanDevice() ? ScanSuccess : ScanError;
832 }
833
834 return m_state == ScanSuccess;
835}
836
837bool QICNSHandler::addEntry(const ICNSBlockHeader &header, qint64 imgDataOffset, quint32 variant)
838{
839 // Note: This function returns false only when a device positioning error occurred
840 ICNSEntry entry;
841 entry.ostype = header.ostype;
842 entry.variant = variant;
843 entry.dataOffset = imgDataOffset;
844 entry.dataLength = header.length - ICNSBlockHeaderSize;
845 // Check for known magic numbers:
846 if (!parseIconEntryData(entry, device()))
847 return false;
848 // Parse everything else and index this entry:
849 if (parseIconEntryInfo(entry)) {
850 if ((entry.flags & ICNSEntry::IsMask) != 0)
851 m_masks << entry;
852 if ((entry.flags & ICNSEntry::IsIcon) != 0)
853 m_icons << entry;
854 }
855 return true;
856}
857
858bool QICNSHandler::scanDevice()
859{
860 if (m_state == ScanSuccess)
861 return true;
862
863 if (!device()->seek(0))
864 return false;
865
866 QDataStream stream(device());
867 stream.setByteOrder(QDataStream::BigEndian);
868
869 bool scanIsIncomplete = false;
870 qint64 filelength = device()->size();
871 ICNSBlockHeader blockHeader;
872 while (!stream.atEnd() || device()->pos() < filelength) {
873 stream >> blockHeader;
874 if (stream.status() != QDataStream::Ok)
875 return false;
876
877 const qint64 blockDataOffset = device()->pos();
878 if (!isBlockHeaderValid(blockHeader, ICNSBlockHeaderSize - blockDataOffset + filelength)) {
879 qWarning("QICNSHandler::scanDevice(): Failed, bad header at pos %s. OSType \"%s\", length %u",
880 QByteArray::number(blockDataOffset).constData(),
881 nameFromOSType(blockHeader.ostype).constData(), blockHeader.length);
882 return false;
883 }
884 const quint64 blockDataLength = blockHeader.length - ICNSBlockHeaderSize;
885 const qint64 nextBlockOffset = blockDataOffset + blockDataLength;
886
887 switch (blockHeader.ostype) {
888 case ICNSBlockHeader::TypeIcns:
889 if (blockDataOffset != ICNSBlockHeaderSize) {
890 // Icns container definition should be in the beginning of the device.
891 // If we meet this block somewhere else, then just ignore it.
892 stream.skipRawData(blockDataLength);
893 break;
894 }
895 filelength = blockHeader.length;
896 if (device()->size() < blockHeader.length) {
897 qWarning("QICNSHandler::scanDevice(): Failed, file is incomplete.");
898 return false;
899 }
900 break;
901 case ICNSBlockHeader::TypeIcnv:
902 case ICNSBlockHeader::TypeClut:
903 // We don't have a good use for these blocks... yet.
904 stream.skipRawData(blockDataLength);
905 break;
906 case ICNSBlockHeader::TypeTile:
907 case ICNSBlockHeader::TypeOver:
908 case ICNSBlockHeader::TypeOpen:
909 case ICNSBlockHeader::TypeDrop:
910 case ICNSBlockHeader::TypeOdrp:
911 // Icns container seems to have an embedded icon variant container
912 // Let's start a scan for entries
913 while (!stream.atEnd() && device()->pos() < nextBlockOffset) {
914 ICNSBlockHeader icon;
915 stream >> icon;
916 if (stream.status() != QDataStream::Ok)
917 return false;
918 // Check for incorrect variant entry header and stop scan
919 quint64 remaining = blockDataLength - (device()->pos() - blockDataOffset);
920 if (!isBlockHeaderValid(icon, ICNSBlockHeaderSize + remaining))
921 break;
922 if (!addEntry(icon, device()->pos(), blockHeader.ostype))
923 return false;
924 if (stream.skipRawData(icon.length - ICNSBlockHeaderSize) < 0)
925 return false;
926 }
927 if (device()->pos() != nextBlockOffset) {
928 // Scan of this container didn't end where we expected.
929 // Let's generate some output about this incident:
930 qWarning("Scan of the icon variant container (\"%s\") failed at pos %s.\n"
931 "Reason: Scan didn't reach the end of this container's block, "
932 "delta: %s bytes. This file may be corrupted.",
933 nameFromOSType(blockHeader.ostype).constData(),
934 QByteArray::number(device()->pos()).constData(),
935 QByteArray::number(nextBlockOffset - device()->pos()).constData());
936 if (!device()->seek(nextBlockOffset))
937 return false;
938 }
939 break;
940 case ICNSBlockHeader::TypeToc: {
941 // Quick scan, table of contents
942 if (blockDataOffset != ICNSBlockHeaderSize * 2) {
943 // TOC should be the first block in the file after container definition.
944 // Ignore and go on with a deep scan.
945 stream.skipRawData(blockDataLength);
946 break;
947 }
948 // First image data offset:
949 qint64 imgDataOffset = blockDataOffset + blockHeader.length;
950 for (uint i = 0, count = blockDataLength / ICNSBlockHeaderSize; i < count; i++) {
951 ICNSBlockHeader tocEntry;
952 stream >> tocEntry;
953 if (imgDataOffset > filelength
954 || !isBlockHeaderValid(tocEntry, quint64(filelength - imgDataOffset))) {
955 // TOC contains incorrect header, we should skip TOC since we can't trust it
956 qWarning("QICNSHandler::scanDevice(): Warning! Table of contents contains a bad "
957 "entry! Stop at device pos: %s bytes. This file may be corrupted.",
958 QByteArray::number(device()->pos()).constData());
959 if (!device()->seek(nextBlockOffset))
960 return false;
961 break;
962 }
963 if (!addEntry(tocEntry, imgDataOffset))
964 return false;
965 imgDataOffset += tocEntry.length;
966 // If TOC covers all the blocks in the file, then quick scan is complete
967 if (imgDataOffset == filelength)
968 return true;
969 }
970 // Else just start a deep scan to salvage anything left after TOC's end
971 scanIsIncomplete = true;
972 break;
973 }
974 default:
975 // Deep scan, block by block
976 if (scanIsIncomplete) {
977 // Check if entry with this offset is added somewhere
978 // But only if we have incomplete TOC, otherwise just try to add
979 bool exists = false;
980 for (int i = 0; i < m_icons.size() && !exists; i++)
981 exists = m_icons.at(i).dataOffset == blockDataOffset;
982 for (int i = 0; i < m_masks.size() && !exists; i++)
983 exists = m_masks.at(i).dataOffset == blockDataOffset;
984 if (!exists && !addEntry(blockHeader, blockDataOffset))
985 return false;
986 } else if (!addEntry(blockHeader, blockDataOffset)) {
987 return false;
988 }
989 stream.skipRawData(blockDataLength);
990 break;
991 }
992 }
993 return (m_icons.size() > 0);
994}
995
996const ICNSEntry &QICNSHandler::getIconMask(const ICNSEntry &icon) const
997{
998 const bool is32bit = icon.depth == ICNSEntry::Depth32bit;
999 ICNSEntry::Depth targetDepth = is32bit ? ICNSEntry::Depth8bit : ICNSEntry::DepthMono;
1000 for (int i = 0; i < m_masks.size(); i++) {
1001 const ICNSEntry &mask = m_masks.at(i);
1002 if (isMaskSuitable(mask, icon, targetDepth))
1003 return mask;
1004 }
1005 return icon;
1006}
1007
1008QT_END_NAMESPACE
1009
1010#endif // QT_NO_DATASTREAM
int imageCount() const override
For image formats that support animation, this function returns the number of images in the animation...
bool write(const QImage &image) override
Writes the image image to the assigned device.
bool read(QImage *image) override
Read an image from the device, and stores it in image.
bool canRead() const override
Returns true if an image can be read from the device (i.e., the image format is supported,...
bool supportsOption(ImageOption option) const override
Returns true if the QImageIOHandler supports the option option; otherwise returns false.
QVariant option(ImageOption option) const override
Returns the value assigned to option as a QVariant.
bool jumpToImage(int imageNumber) override
For image formats that support animation, this function jumps to the image whose sequence number is i...
bool jumpToNextImage() override
For image formats that support animation, this function jumps to the next image.
Combined button and popup list for selecting options.
static bool isPowOf2OrDividesBy16(quint32 u, qreal r)
static bool isBlockHeaderValid(const ICNSBlockHeader &header, quint64 bound=0)
static const QRgb ICNSColorTableMono[]
static QImage readLowDepthIcon(const ICNSEntry &icon, QDataStream &stream)
static bool isMaskSuitable(const ICNSEntry &mask, const ICNSEntry &icon, ICNSEntry::Depth target)
static bool isIconCompressed(const ICNSEntry &icon)
static const QRgb ICNSColorTable4bit[]
static QList< QRgb > getColorTable(ICNSEntry::Depth depth)
static QByteArray nameForCompressedIcon(quint8 iconNumber)
static bool parseIconEntryData(ICNSEntry &icon, QIODevice *device)
static bool parseIconEntryInfo(ICNSEntry &icon)
static QImage read32bitIcon(const ICNSEntry &icon, QDataStream &stream)
static quint32 nameToOSType(const QByteArray &ostype)
static QByteArray nameFromOSType(quint32 ostype)
static const QRgb ICNSColorTable8bit[]
static QImage readMask(const ICNSEntry &mask, QDataStream &stream)
static QT_BEGIN_NAMESPACE const quint8 ICNSBlockHeaderSize
static QDataStream & operator>>(QDataStream &in, ICNSBlockHeader &p)
static QDataStream & operator<<(QDataStream &out, const ICNSBlockHeader &p)
Q_STATIC_ASSERT(sizeof(SharedImageHeader) % 4==0)
Format dataFormat