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 const quint32 estPxsNum = icon.width * icon.height;
591 const QByteArray &bytes = stream.device()->peek(4);
592 if (bytes.isEmpty())
593 return QImage();
594 // Zero-padding may be present:
595 if (qFromBigEndian<quint32>(*bytes.constData()) == 0)
596 stream.skipRawData(4);
597 for (quint8 colorNRun = 0; colorNRun < 3; colorNRun++) {
598 quint32 pixel = 0;
599 QRgb *line = 0;
600 while (pixel < estPxsNum && !stream.atEnd()) {
601 quint8 byte, value;
602 stream >> byte;
603 const bool bitIsClear = (byte & 0x80) == 0;
604 // If high bit is clear: run of different values; else: same value
605 quint8 runLength = bitIsClear ? ((0xFF & byte) + 1) : ((0xFF & byte) - 125);
606 // Length of the run for for different values: 1 <= len <= 128
607 // Length of the run for same values: 3 <= len <= 130
608 if (!bitIsClear)
609 stream >> value;
610 for (quint8 i = 0; i < runLength && pixel < estPxsNum; i++) {
611 if (bitIsClear)
612 stream >> value;
613 const quint32 y = pixel / icon.height;
614 const quint32 x = pixel - (icon.width * y);
615 if (pixel % icon.height == 0)
616 line = reinterpret_cast<QRgb *>(img.scanLine(y));
617 QRgb rgb = line[x];
618 const int r = (colorNRun == 0) ? value : qRed(rgb);
619 const int g = (colorNRun == 1) ? value : qGreen(rgb);
620 const int b = (colorNRun == 2) ? value : qBlue(rgb);
621 line[x] = qRgb(r, g, b);
622 pixel++;
623 }
624 }
625 }
626 }
627 return img;
628}
629
631 m_currentIconIndex(0), m_state(ScanNotScanned)
632{
633}
634
635bool QICNSHandler::canRead(QIODevice *device)
636{
637 if (!device || !device->isReadable()) {
638 qWarning("QICNSHandler::canRead() called without a readable device");
639 return false;
640 }
641
642 if (device->peek(4) == QByteArrayLiteral("icns")) {
643 if (device->isSequential()) {
644 qWarning("QICNSHandler::canRead() called on a sequential device");
645 return false;
646 }
647 return true;
648 }
649
650 return false;
651}
652
654{
655 if (m_state == ScanNotScanned && !canRead(device()))
656 return false;
657
658 if (m_state != ScanError) {
659 setFormat(QByteArrayLiteral("icns"));
660 return true;
661 }
662
663 return false;
664}
665
666bool QICNSHandler::read(QImage *outImage)
667{
668 QImage img;
669 if (!ensureScanned() || m_currentIconIndex >= m_icons.size()) {
670 qWarning("QICNSHandler::read(): The device wasn't parsed properly!");
671 return false;
672 }
673
674 const ICNSEntry &icon = m_icons.at(m_currentIconIndex);
675 QDataStream stream(device());
676 stream.setByteOrder(QDataStream::BigEndian);
677 if (!device()->seek(icon.dataOffset))
678 return false;
679
680 switch (icon.dataFormat) {
682 case ICNSEntry::RLE24:
683 if (qMin(icon.width, icon.height) == 0)
684 break;
685 switch (icon.depth) {
687 img = readLowDepthIcon<ICNSEntry::DepthMono>(icon, stream);
688 break;
690 img = readLowDepthIcon<ICNSEntry::Depth4bit>(icon, stream);
691 break;
693 img = readLowDepthIcon<ICNSEntry::Depth8bit>(icon, stream);
694 break;
695 case ICNSEntry::Depth32bit:
696 img = read32bitIcon(icon, stream);
697 break;
698 default:
699 qWarning("QICNSHandler::read(): Failed, unsupported icon bit depth: %u, OSType: \"%s\"",
700 icon.depth, nameFromOSType(icon.ostype).constData());
701 }
702 if (!img.isNull()) {
703 QImage alpha = readMask(getIconMask(icon), stream);
704 if (!alpha.isNull())
705 img.setAlphaChannel(alpha);
706 }
707 break;
708 default:
709 const char *format = 0;
710 if (icon.dataFormat == ICNSEntry::PNG)
711 format = "png";
712 else if (icon.dataFormat == ICNSEntry::JP2)
713 format = "jp2";
714 // Even if JP2 or PNG magic is not detected, try anyway for unknown formats
715 img = QImage::fromData(device()->read(icon.dataLength), format);
716 if (img.isNull()) {
717 if (format == 0)
718 format = "unknown";
719 qWarning("QICNSHandler::read(): Failed, compressed format \"%s\" is not supported "
720 "by your Qt library or this file is corrupt. OSType: \"%s\"",
721 format, nameFromOSType(icon.ostype).constData());
722 }
723 }
724 *outImage = img;
725 return !img.isNull();
726}
727
728bool QICNSHandler::write(const QImage &image)
729{
730 /*
731 Notes:
732 * Experimental implementation. Just for simple converting tasks / testing purposes.
733 * Min. size is 16x16, Max. size is 1024x1024.
734 * Performs downscale to a square image if width != height.
735 * Performs upscale to 16x16, if the image is smaller.
736 * Performs downscale to a nearest power of two if size is not a power of two.
737 * Currently uses non-hardcoded OSTypes.
738 */
739 QIODevice *device = this->device();
740 if (!device->isWritable() || image.isNull() || qMin(image.width(), image.height()) == 0)
741 return false;
742 const int minSize = qMin(image.width(), image.height());
743 const int oldSize = (minSize < 16) ? 16 : minSize;
744 // Calc power of two:
745 int size = oldSize;
746 uint pow = 0;
747 // Note: Values over 10 are reserved for retina icons.
748 while (pow < 10 && (size >>= 1))
749 pow++;
750 const int newSize = 1 << pow;
751 QImage img = image;
752 // Let's enforce resizing if size differs:
753 if (newSize != oldSize || qMax(image.width(), image.height()) != minSize)
754 img = img.scaled(newSize, newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
755 // Construct OSType and headers:
756 const quint32 ostype = nameToOSType(nameForCompressedIcon(pow));
757 ICNSBlockHeader fileHeader;
758 fileHeader.ostype = ICNSBlockHeader::TypeIcns;
759 ICNSBlockHeader tocHeader;
760 tocHeader.ostype = ICNSBlockHeader::TypeToc;
761 ICNSBlockHeader iconEntry;
762 iconEntry.ostype = ostype;
763 QByteArray imageData;
764 QBuffer buffer(&imageData);
765 if (!buffer.open(QIODevice::WriteOnly) || !img.save(&buffer, "png"))
766 return false;
767 buffer.close();
768 iconEntry.length = ICNSBlockHeaderSize + imageData.size();
769 tocHeader.length = ICNSBlockHeaderSize * 2;
770 fileHeader.length = ICNSBlockHeaderSize + tocHeader.length + iconEntry.length;
771 if (!isBlockHeaderValid(iconEntry))
772 return false;
773
774 QDataStream stream(device);
775 // iconEntry is also a TOC entry
776 stream << fileHeader << tocHeader << iconEntry << iconEntry;
777 stream.writeRawData(imageData.constData(), imageData.size());
778 return stream.status() == QDataStream::Ok;
779}
780
781bool QICNSHandler::supportsOption(ImageOption option) const
782{
783 return option == SubType;
784}
785
786QVariant QICNSHandler::option(ImageOption option) const
787{
788 if (!supportsOption(option) || !ensureScanned())
789 return QVariant();
790
791 if (option == SubType) {
792 if (imageCount() > 0 && m_currentIconIndex <= imageCount()) {
793 const ICNSEntry &icon = m_icons.at(m_currentIconIndex);
794 if (icon.variant != 0)
795 return QByteArray(nameFromOSType(icon.variant) + '-' + nameFromOSType(icon.ostype));
796 return nameFromOSType(icon.ostype);
797 }
798 }
799
800 return QVariant();
801}
802
804{
805 if (!ensureScanned())
806 return 0;
807
808 return m_icons.size();
809}
810
811bool QICNSHandler::jumpToImage(int imageNumber)
812{
813 if (imageNumber >= imageCount())
814 return false;
815
816 m_currentIconIndex = imageNumber;
817 return true;
818}
819
821{
822 return jumpToImage(m_currentIconIndex + 1);
823}
824
825bool QICNSHandler::ensureScanned() const
826{
827 if (m_state == ScanNotScanned) {
828 QICNSHandler *that = const_cast<QICNSHandler *>(this);
829 that->m_state = that->scanDevice() ? ScanSuccess : ScanError;
830 }
831
832 return m_state == ScanSuccess;
833}
834
835bool QICNSHandler::addEntry(const ICNSBlockHeader &header, qint64 imgDataOffset, quint32 variant)
836{
837 // Note: This function returns false only when a device positioning error occurred
838 ICNSEntry entry;
839 entry.ostype = header.ostype;
840 entry.variant = variant;
841 entry.dataOffset = imgDataOffset;
842 entry.dataLength = header.length - ICNSBlockHeaderSize;
843 // Check for known magic numbers:
844 if (!parseIconEntryData(entry, device()))
845 return false;
846 // Parse everything else and index this entry:
847 if (parseIconEntryInfo(entry)) {
848 if ((entry.flags & ICNSEntry::IsMask) != 0)
849 m_masks << entry;
850 if ((entry.flags & ICNSEntry::IsIcon) != 0)
851 m_icons << entry;
852 }
853 return true;
854}
855
856bool QICNSHandler::scanDevice()
857{
858 if (m_state == ScanSuccess)
859 return true;
860
861 if (!device()->seek(0))
862 return false;
863
864 QDataStream stream(device());
865 stream.setByteOrder(QDataStream::BigEndian);
866
867 bool scanIsIncomplete = false;
868 qint64 filelength = device()->size();
869 ICNSBlockHeader blockHeader;
870 while (!stream.atEnd() || device()->pos() < filelength) {
871 stream >> blockHeader;
872 if (stream.status() != QDataStream::Ok)
873 return false;
874
875 const qint64 blockDataOffset = device()->pos();
876 if (!isBlockHeaderValid(blockHeader, ICNSBlockHeaderSize - blockDataOffset + filelength)) {
877 qWarning("QICNSHandler::scanDevice(): Failed, bad header at pos %s. OSType \"%s\", length %u",
878 QByteArray::number(blockDataOffset).constData(),
879 nameFromOSType(blockHeader.ostype).constData(), blockHeader.length);
880 return false;
881 }
882 const quint64 blockDataLength = blockHeader.length - ICNSBlockHeaderSize;
883 const qint64 nextBlockOffset = blockDataOffset + blockDataLength;
884
885 switch (blockHeader.ostype) {
886 case ICNSBlockHeader::TypeIcns:
887 if (blockDataOffset != ICNSBlockHeaderSize) {
888 // Icns container definition should be in the beginning of the device.
889 // If we meet this block somewhere else, then just ignore it.
890 stream.skipRawData(blockDataLength);
891 break;
892 }
893 filelength = blockHeader.length;
894 if (device()->size() < blockHeader.length) {
895 qWarning("QICNSHandler::scanDevice(): Failed, file is incomplete.");
896 return false;
897 }
898 break;
899 case ICNSBlockHeader::TypeIcnv:
900 case ICNSBlockHeader::TypeClut:
901 // We don't have a good use for these blocks... yet.
902 stream.skipRawData(blockDataLength);
903 break;
904 case ICNSBlockHeader::TypeTile:
905 case ICNSBlockHeader::TypeOver:
906 case ICNSBlockHeader::TypeOpen:
907 case ICNSBlockHeader::TypeDrop:
908 case ICNSBlockHeader::TypeOdrp:
909 // Icns container seems to have an embedded icon variant container
910 // Let's start a scan for entries
911 while (!stream.atEnd() && device()->pos() < nextBlockOffset) {
912 ICNSBlockHeader icon;
913 stream >> icon;
914 if (stream.status() != QDataStream::Ok)
915 return false;
916 // Check for incorrect variant entry header and stop scan
917 quint64 remaining = blockDataLength - (device()->pos() - blockDataOffset);
918 if (!isBlockHeaderValid(icon, ICNSBlockHeaderSize + remaining))
919 break;
920 if (!addEntry(icon, device()->pos(), blockHeader.ostype))
921 return false;
922 if (stream.skipRawData(icon.length - ICNSBlockHeaderSize) < 0)
923 return false;
924 }
925 if (device()->pos() != nextBlockOffset) {
926 // Scan of this container didn't end where we expected.
927 // Let's generate some output about this incident:
928 qWarning("Scan of the icon variant container (\"%s\") failed at pos %s.\n"
929 "Reason: Scan didn't reach the end of this container's block, "
930 "delta: %s bytes. This file may be corrupted.",
931 nameFromOSType(blockHeader.ostype).constData(),
932 QByteArray::number(device()->pos()).constData(),
933 QByteArray::number(nextBlockOffset - device()->pos()).constData());
934 if (!device()->seek(nextBlockOffset))
935 return false;
936 }
937 break;
938 case ICNSBlockHeader::TypeToc: {
939 // Quick scan, table of contents
940 if (blockDataOffset != ICNSBlockHeaderSize * 2) {
941 // TOC should be the first block in the file after container definition.
942 // Ignore and go on with a deep scan.
943 stream.skipRawData(blockDataLength);
944 break;
945 }
946 // First image data offset:
947 qint64 imgDataOffset = blockDataOffset + blockHeader.length;
948 for (uint i = 0, count = blockDataLength / ICNSBlockHeaderSize; i < count; i++) {
949 ICNSBlockHeader tocEntry;
950 stream >> tocEntry;
951 if (!isBlockHeaderValid(tocEntry)) {
952 // TOC contains incorrect header, we should skip TOC since we can't trust it
953 qWarning("QICNSHandler::scanDevice(): Warning! Table of contents contains a bad "
954 "entry! Stop at device pos: %s bytes. This file may be corrupted.",
955 QByteArray::number(device()->pos()).constData());
956 if (!device()->seek(nextBlockOffset))
957 return false;
958 break;
959 }
960 if (!addEntry(tocEntry, imgDataOffset))
961 return false;
962 imgDataOffset += tocEntry.length;
963 // If TOC covers all the blocks in the file, then quick scan is complete
964 if (imgDataOffset == filelength)
965 return true;
966 }
967 // Else just start a deep scan to salvage anything left after TOC's end
968 scanIsIncomplete = true;
969 break;
970 }
971 default:
972 // Deep scan, block by block
973 if (scanIsIncomplete) {
974 // Check if entry with this offset is added somewhere
975 // But only if we have incomplete TOC, otherwise just try to add
976 bool exists = false;
977 for (int i = 0; i < m_icons.size() && !exists; i++)
978 exists = m_icons.at(i).dataOffset == blockDataOffset;
979 for (int i = 0; i < m_masks.size() && !exists; i++)
980 exists = m_masks.at(i).dataOffset == blockDataOffset;
981 if (!exists && !addEntry(blockHeader, blockDataOffset))
982 return false;
983 } else if (!addEntry(blockHeader, blockDataOffset)) {
984 return false;
985 }
986 stream.skipRawData(blockDataLength);
987 break;
988 }
989 }
990 return (m_icons.size() > 0);
991}
992
993const ICNSEntry &QICNSHandler::getIconMask(const ICNSEntry &icon) const
994{
995 const bool is32bit = icon.depth == ICNSEntry::Depth32bit;
996 ICNSEntry::Depth targetDepth = is32bit ? ICNSEntry::Depth8bit : ICNSEntry::DepthMono;
997 for (int i = 0; i < m_masks.size(); i++) {
998 const ICNSEntry &mask = m_masks.at(i);
999 if (isMaskSuitable(mask, icon, targetDepth))
1000 return mask;
1001 }
1002 return icon;
1003}
1004
1005QT_END_NAMESPACE
1006
1007#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.
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