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