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
15QT_BEGIN_NAMESPACE
16
17static const quint8 ICNSBlockHeaderSize = 8;
18
19static const QRgb ICNSColorTableMono[] = {
20 qRgb(r: 0xFF, g: 0xFF, b: 0xFF),
21 qRgb(r: 0x00, g: 0x00, b: 0x00)
22};
23Q_STATIC_ASSERT(sizeof(ICNSColorTableMono) / sizeof(ICNSColorTableMono[0]) == (1 << ICNSEntry::DepthMono));
24
25static const QRgb ICNSColorTable4bit[] = {
26 qRgb(r: 0xFF, g: 0xFF, b: 0xFF),
27 qRgb(r: 0xFC, g: 0xF3, b: 0x05),
28 qRgb(r: 0xFF, g: 0x64, b: 0x02),
29 qRgb(r: 0xDD, g: 0x08, b: 0x06),
30 qRgb(r: 0xF2, g: 0x08, b: 0x84),
31 qRgb(r: 0x46, g: 0x00, b: 0xA5),
32 qRgb(r: 0x00, g: 0x00, b: 0xD4),
33 qRgb(r: 0x02, g: 0xAB, b: 0xEA),
34 qRgb(r: 0x1F, g: 0xB7, b: 0x14),
35 qRgb(r: 0x00, g: 0x64, b: 0x11),
36 qRgb(r: 0x56, g: 0x2C, b: 0x05),
37 qRgb(r: 0x90, g: 0x71, b: 0x3A),
38 qRgb(r: 0xC0, g: 0xC0, b: 0xC0),
39 qRgb(r: 0x80, g: 0x80, b: 0x80),
40 qRgb(r: 0x40, g: 0x40, b: 0x40),
41 qRgb(r: 0x00, g: 0x00, b: 0x00)
42};
43Q_STATIC_ASSERT(sizeof(ICNSColorTable4bit) / sizeof(ICNSColorTable4bit[0]) == (1 << ICNSEntry::Depth4bit));
44
45static const QRgb ICNSColorTable8bit[] = {
46 qRgb(r: 0xFF, g: 0xFF, b: 0xFF),
47 qRgb(r: 0xFF, g: 0xFF, b: 0xCC),
48 qRgb(r: 0xFF, g: 0xFF, b: 0x99),
49 qRgb(r: 0xFF, g: 0xFF, b: 0x66),
50 qRgb(r: 0xFF, g: 0xFF, b: 0x33),
51 qRgb(r: 0xFF, g: 0xFF, b: 0x00),
52 qRgb(r: 0xFF, g: 0xCC, b: 0xFF),
53 qRgb(r: 0xFF, g: 0xCC, b: 0xCC),
54 qRgb(r: 0xFF, g: 0xCC, b: 0x99),
55 qRgb(r: 0xFF, g: 0xCC, b: 0x66),
56 qRgb(r: 0xFF, g: 0xCC, b: 0x33),
57 qRgb(r: 0xFF, g: 0xCC, b: 0x00),
58 qRgb(r: 0xFF, g: 0x99, b: 0xFF),
59 qRgb(r: 0xFF, g: 0x99, b: 0xCC),
60 qRgb(r: 0xFF, g: 0x99, b: 0x99),
61 qRgb(r: 0xFF, g: 0x99, b: 0x66),
62 qRgb(r: 0xFF, g: 0x99, b: 0x33),
63 qRgb(r: 0xFF, g: 0x99, b: 0x00),
64 qRgb(r: 0xFF, g: 0x66, b: 0xFF),
65 qRgb(r: 0xFF, g: 0x66, b: 0xCC),
66 qRgb(r: 0xFF, g: 0x66, b: 0x99),
67 qRgb(r: 0xFF, g: 0x66, b: 0x66),
68 qRgb(r: 0xFF, g: 0x66, b: 0x33),
69 qRgb(r: 0xFF, g: 0x66, b: 0x00),
70 qRgb(r: 0xFF, g: 0x33, b: 0xFF),
71 qRgb(r: 0xFF, g: 0x33, b: 0xCC),
72 qRgb(r: 0xFF, g: 0x33, b: 0x99),
73 qRgb(r: 0xFF, g: 0x33, b: 0x66),
74 qRgb(r: 0xFF, g: 0x33, b: 0x33),
75 qRgb(r: 0xFF, g: 0x33, b: 0x00),
76 qRgb(r: 0xFF, g: 0x00, b: 0xFF),
77 qRgb(r: 0xFF, g: 0x00, b: 0xCC),
78 qRgb(r: 0xFF, g: 0x00, b: 0x99),
79 qRgb(r: 0xFF, g: 0x00, b: 0x66),
80 qRgb(r: 0xFF, g: 0x00, b: 0x33),
81 qRgb(r: 0xFF, g: 0x00, b: 0x00),
82 qRgb(r: 0xCC, g: 0xFF, b: 0xFF),
83 qRgb(r: 0xCC, g: 0xFF, b: 0xCC),
84 qRgb(r: 0xCC, g: 0xFF, b: 0x99),
85 qRgb(r: 0xCC, g: 0xFF, b: 0x66),
86 qRgb(r: 0xCC, g: 0xFF, b: 0x33),
87 qRgb(r: 0xCC, g: 0xFF, b: 0x00),
88 qRgb(r: 0xCC, g: 0xCC, b: 0xFF),
89 qRgb(r: 0xCC, g: 0xCC, b: 0xCC),
90 qRgb(r: 0xCC, g: 0xCC, b: 0x99),
91 qRgb(r: 0xCC, g: 0xCC, b: 0x66),
92 qRgb(r: 0xCC, g: 0xCC, b: 0x33),
93 qRgb(r: 0xCC, g: 0xCC, b: 0x00),
94 qRgb(r: 0xCC, g: 0x99, b: 0xFF),
95 qRgb(r: 0xCC, g: 0x99, b: 0xCC),
96 qRgb(r: 0xCC, g: 0x99, b: 0x99),
97 qRgb(r: 0xCC, g: 0x99, b: 0x66),
98 qRgb(r: 0xCC, g: 0x99, b: 0x33),
99 qRgb(r: 0xCC, g: 0x99, b: 0x00),
100 qRgb(r: 0xCC, g: 0x66, b: 0xFF),
101 qRgb(r: 0xCC, g: 0x66, b: 0xCC),
102 qRgb(r: 0xCC, g: 0x66, b: 0x99),
103 qRgb(r: 0xCC, g: 0x66, b: 0x66),
104 qRgb(r: 0xCC, g: 0x66, b: 0x33),
105 qRgb(r: 0xCC, g: 0x66, b: 0x00),
106 qRgb(r: 0xCC, g: 0x33, b: 0xFF),
107 qRgb(r: 0xCC, g: 0x33, b: 0xCC),
108 qRgb(r: 0xCC, g: 0x33, b: 0x99),
109 qRgb(r: 0xCC, g: 0x33, b: 0x66),
110 qRgb(r: 0xCC, g: 0x33, b: 0x33),
111 qRgb(r: 0xCC, g: 0x33, b: 0x00),
112 qRgb(r: 0xCC, g: 0x00, b: 0xFF),
113 qRgb(r: 0xCC, g: 0x00, b: 0xCC),
114 qRgb(r: 0xCC, g: 0x00, b: 0x99),
115 qRgb(r: 0xCC, g: 0x00, b: 0x66),
116 qRgb(r: 0xCC, g: 0x00, b: 0x33),
117 qRgb(r: 0xCC, g: 0x00, b: 0x00),
118 qRgb(r: 0x99, g: 0xFF, b: 0xFF),
119 qRgb(r: 0x99, g: 0xFF, b: 0xCC),
120 qRgb(r: 0x99, g: 0xFF, b: 0x99),
121 qRgb(r: 0x99, g: 0xFF, b: 0x66),
122 qRgb(r: 0x99, g: 0xFF, b: 0x33),
123 qRgb(r: 0x99, g: 0xFF, b: 0x00),
124 qRgb(r: 0x99, g: 0xCC, b: 0xFF),
125 qRgb(r: 0x99, g: 0xCC, b: 0xCC),
126 qRgb(r: 0x99, g: 0xCC, b: 0x99),
127 qRgb(r: 0x99, g: 0xCC, b: 0x66),
128 qRgb(r: 0x99, g: 0xCC, b: 0x33),
129 qRgb(r: 0x99, g: 0xCC, b: 0x00),
130 qRgb(r: 0x99, g: 0x99, b: 0xFF),
131 qRgb(r: 0x99, g: 0x99, b: 0xCC),
132 qRgb(r: 0x99, g: 0x99, b: 0x99),
133 qRgb(r: 0x99, g: 0x99, b: 0x66),
134 qRgb(r: 0x99, g: 0x99, b: 0x33),
135 qRgb(r: 0x99, g: 0x99, b: 0x00),
136 qRgb(r: 0x99, g: 0x66, b: 0xFF),
137 qRgb(r: 0x99, g: 0x66, b: 0xCC),
138 qRgb(r: 0x99, g: 0x66, b: 0x99),
139 qRgb(r: 0x99, g: 0x66, b: 0x66),
140 qRgb(r: 0x99, g: 0x66, b: 0x33),
141 qRgb(r: 0x99, g: 0x66, b: 0x00),
142 qRgb(r: 0x99, g: 0x33, b: 0xFF),
143 qRgb(r: 0x99, g: 0x33, b: 0xCC),
144 qRgb(r: 0x99, g: 0x33, b: 0x99),
145 qRgb(r: 0x99, g: 0x33, b: 0x66),
146 qRgb(r: 0x99, g: 0x33, b: 0x33),
147 qRgb(r: 0x99, g: 0x33, b: 0x00),
148 qRgb(r: 0x99, g: 0x00, b: 0xFF),
149 qRgb(r: 0x99, g: 0x00, b: 0xCC),
150 qRgb(r: 0x99, g: 0x00, b: 0x99),
151 qRgb(r: 0x99, g: 0x00, b: 0x66),
152 qRgb(r: 0x99, g: 0x00, b: 0x33),
153 qRgb(r: 0x99, g: 0x00, b: 0x00),
154 qRgb(r: 0x66, g: 0xFF, b: 0xFF),
155 qRgb(r: 0x66, g: 0xFF, b: 0xCC),
156 qRgb(r: 0x66, g: 0xFF, b: 0x99),
157 qRgb(r: 0x66, g: 0xFF, b: 0x66),
158 qRgb(r: 0x66, g: 0xFF, b: 0x33),
159 qRgb(r: 0x66, g: 0xFF, b: 0x00),
160 qRgb(r: 0x66, g: 0xCC, b: 0xFF),
161 qRgb(r: 0x66, g: 0xCC, b: 0xCC),
162 qRgb(r: 0x66, g: 0xCC, b: 0x99),
163 qRgb(r: 0x66, g: 0xCC, b: 0x66),
164 qRgb(r: 0x66, g: 0xCC, b: 0x33),
165 qRgb(r: 0x66, g: 0xCC, b: 0x00),
166 qRgb(r: 0x66, g: 0x99, b: 0xFF),
167 qRgb(r: 0x66, g: 0x99, b: 0xCC),
168 qRgb(r: 0x66, g: 0x99, b: 0x99),
169 qRgb(r: 0x66, g: 0x99, b: 0x66),
170 qRgb(r: 0x66, g: 0x99, b: 0x33),
171 qRgb(r: 0x66, g: 0x99, b: 0x00),
172 qRgb(r: 0x66, g: 0x66, b: 0xFF),
173 qRgb(r: 0x66, g: 0x66, b: 0xCC),
174 qRgb(r: 0x66, g: 0x66, b: 0x99),
175 qRgb(r: 0x66, g: 0x66, b: 0x66),
176 qRgb(r: 0x66, g: 0x66, b: 0x33),
177 qRgb(r: 0x66, g: 0x66, b: 0x00),
178 qRgb(r: 0x66, g: 0x33, b: 0xFF),
179 qRgb(r: 0x66, g: 0x33, b: 0xCC),
180 qRgb(r: 0x66, g: 0x33, b: 0x99),
181 qRgb(r: 0x66, g: 0x33, b: 0x66),
182 qRgb(r: 0x66, g: 0x33, b: 0x33),
183 qRgb(r: 0x66, g: 0x33, b: 0x00),
184 qRgb(r: 0x66, g: 0x00, b: 0xFF),
185 qRgb(r: 0x66, g: 0x00, b: 0xCC),
186 qRgb(r: 0x66, g: 0x00, b: 0x99),
187 qRgb(r: 0x66, g: 0x00, b: 0x66),
188 qRgb(r: 0x66, g: 0x00, b: 0x33),
189 qRgb(r: 0x66, g: 0x00, b: 0x00),
190 qRgb(r: 0x33, g: 0xFF, b: 0xFF),
191 qRgb(r: 0x33, g: 0xFF, b: 0xCC),
192 qRgb(r: 0x33, g: 0xFF, b: 0x99),
193 qRgb(r: 0x33, g: 0xFF, b: 0x66),
194 qRgb(r: 0x33, g: 0xFF, b: 0x33),
195 qRgb(r: 0x33, g: 0xFF, b: 0x00),
196 qRgb(r: 0x33, g: 0xCC, b: 0xFF),
197 qRgb(r: 0x33, g: 0xCC, b: 0xCC),
198 qRgb(r: 0x33, g: 0xCC, b: 0x99),
199 qRgb(r: 0x33, g: 0xCC, b: 0x66),
200 qRgb(r: 0x33, g: 0xCC, b: 0x33),
201 qRgb(r: 0x33, g: 0xCC, b: 0x00),
202 qRgb(r: 0x33, g: 0x99, b: 0xFF),
203 qRgb(r: 0x33, g: 0x99, b: 0xCC),
204 qRgb(r: 0x33, g: 0x99, b: 0x99),
205 qRgb(r: 0x33, g: 0x99, b: 0x66),
206 qRgb(r: 0x33, g: 0x99, b: 0x33),
207 qRgb(r: 0x33, g: 0x99, b: 0x00),
208 qRgb(r: 0x33, g: 0x66, b: 0xFF),
209 qRgb(r: 0x33, g: 0x66, b: 0xCC),
210 qRgb(r: 0x33, g: 0x66, b: 0x99),
211 qRgb(r: 0x33, g: 0x66, b: 0x66),
212 qRgb(r: 0x33, g: 0x66, b: 0x33),
213 qRgb(r: 0x33, g: 0x66, b: 0x00),
214 qRgb(r: 0x33, g: 0x33, b: 0xFF),
215 qRgb(r: 0x33, g: 0x33, b: 0xCC),
216 qRgb(r: 0x33, g: 0x33, b: 0x99),
217 qRgb(r: 0x33, g: 0x33, b: 0x66),
218 qRgb(r: 0x33, g: 0x33, b: 0x33),
219 qRgb(r: 0x33, g: 0x33, b: 0x00),
220 qRgb(r: 0x33, g: 0x00, b: 0xFF),
221 qRgb(r: 0x33, g: 0x00, b: 0xCC),
222 qRgb(r: 0x33, g: 0x00, b: 0x99),
223 qRgb(r: 0x33, g: 0x00, b: 0x66),
224 qRgb(r: 0x33, g: 0x00, b: 0x33),
225 qRgb(r: 0x33, g: 0x00, b: 0x00),
226 qRgb(r: 0x00, g: 0xFF, b: 0xFF),
227 qRgb(r: 0x00, g: 0xFF, b: 0xCC),
228 qRgb(r: 0x00, g: 0xFF, b: 0x99),
229 qRgb(r: 0x00, g: 0xFF, b: 0x66),
230 qRgb(r: 0x00, g: 0xFF, b: 0x33),
231 qRgb(r: 0x00, g: 0xFF, b: 0x00),
232 qRgb(r: 0x00, g: 0xCC, b: 0xFF),
233 qRgb(r: 0x00, g: 0xCC, b: 0xCC),
234 qRgb(r: 0x00, g: 0xCC, b: 0x99),
235 qRgb(r: 0x00, g: 0xCC, b: 0x66),
236 qRgb(r: 0x00, g: 0xCC, b: 0x33),
237 qRgb(r: 0x00, g: 0xCC, b: 0x00),
238 qRgb(r: 0x00, g: 0x99, b: 0xFF),
239 qRgb(r: 0x00, g: 0x99, b: 0xCC),
240 qRgb(r: 0x00, g: 0x99, b: 0x99),
241 qRgb(r: 0x00, g: 0x99, b: 0x66),
242 qRgb(r: 0x00, g: 0x99, b: 0x33),
243 qRgb(r: 0x00, g: 0x99, b: 0x00),
244 qRgb(r: 0x00, g: 0x66, b: 0xFF),
245 qRgb(r: 0x00, g: 0x66, b: 0xCC),
246 qRgb(r: 0x00, g: 0x66, b: 0x99),
247 qRgb(r: 0x00, g: 0x66, b: 0x66),
248 qRgb(r: 0x00, g: 0x66, b: 0x33),
249 qRgb(r: 0x00, g: 0x66, b: 0x00),
250 qRgb(r: 0x00, g: 0x33, b: 0xFF),
251 qRgb(r: 0x00, g: 0x33, b: 0xCC),
252 qRgb(r: 0x00, g: 0x33, b: 0x99),
253 qRgb(r: 0x00, g: 0x33, b: 0x66),
254 qRgb(r: 0x00, g: 0x33, b: 0x33),
255 qRgb(r: 0x00, g: 0x33, b: 0x00),
256 qRgb(r: 0x00, g: 0x00, b: 0xFF),
257 qRgb(r: 0x00, g: 0x00, b: 0xCC),
258 qRgb(r: 0x00, g: 0x00, b: 0x99),
259 qRgb(r: 0x00, g: 0x00, b: 0x66),
260 qRgb(r: 0x00, g: 0x00, b: 0x33),
261 qRgb(r: 0xEE, g: 0x00, b: 0x00),
262 qRgb(r: 0xDD, g: 0x00, b: 0x00),
263 qRgb(r: 0xBB, g: 0x00, b: 0x00),
264 qRgb(r: 0xAA, g: 0x00, b: 0x00),
265 qRgb(r: 0x88, g: 0x00, b: 0x00),
266 qRgb(r: 0x77, g: 0x00, b: 0x00),
267 qRgb(r: 0x55, g: 0x00, b: 0x00),
268 qRgb(r: 0x44, g: 0x00, b: 0x00),
269 qRgb(r: 0x22, g: 0x00, b: 0x00),
270 qRgb(r: 0x11, g: 0x00, b: 0x00),
271 qRgb(r: 0x00, g: 0xEE, b: 0x00),
272 qRgb(r: 0x00, g: 0xDD, b: 0x00),
273 qRgb(r: 0x00, g: 0xBB, b: 0x00),
274 qRgb(r: 0x00, g: 0xAA, b: 0x00),
275 qRgb(r: 0x00, g: 0x88, b: 0x00),
276 qRgb(r: 0x00, g: 0x77, b: 0x00),
277 qRgb(r: 0x00, g: 0x55, b: 0x00),
278 qRgb(r: 0x00, g: 0x44, b: 0x00),
279 qRgb(r: 0x00, g: 0x22, b: 0x00),
280 qRgb(r: 0x00, g: 0x11, b: 0x00),
281 qRgb(r: 0x00, g: 0x00, b: 0xEE),
282 qRgb(r: 0x00, g: 0x00, b: 0xDD),
283 qRgb(r: 0x00, g: 0x00, b: 0xBB),
284 qRgb(r: 0x00, g: 0x00, b: 0xAA),
285 qRgb(r: 0x00, g: 0x00, b: 0x88),
286 qRgb(r: 0x00, g: 0x00, b: 0x77),
287 qRgb(r: 0x00, g: 0x00, b: 0x55),
288 qRgb(r: 0x00, g: 0x00, b: 0x44),
289 qRgb(r: 0x00, g: 0x00, b: 0x22),
290 qRgb(r: 0x00, g: 0x00, b: 0x11),
291 qRgb(r: 0xEE, g: 0xEE, b: 0xEE),
292 qRgb(r: 0xDD, g: 0xDD, b: 0xDD),
293 qRgb(r: 0xBB, g: 0xBB, b: 0xBB),
294 qRgb(r: 0xAA, g: 0xAA, b: 0xAA),
295 qRgb(r: 0x88, g: 0x88, b: 0x88),
296 qRgb(r: 0x77, g: 0x77, b: 0x77),
297 qRgb(r: 0x55, g: 0x55, b: 0x55),
298 qRgb(r: 0x44, g: 0x44, b: 0x44),
299 qRgb(r: 0x22, g: 0x22, b: 0x22),
300 qRgb(r: 0x11, g: 0x11, b: 0x11),
301 qRgb(r: 0x00, g: 0x00, b: 0x00)
302};
303Q_STATIC_ASSERT(sizeof(ICNSColorTable8bit) / sizeof(ICNSColorTable8bit[0]) == (1 << ICNSEntry::Depth8bit));
304
305static inline QDataStream &operator>>(QDataStream &in, ICNSBlockHeader &p)
306{
307 in >> p.ostype;
308 in >> p.length;
309 return in;
310}
311
312static inline QDataStream &operator<<(QDataStream &out, const ICNSBlockHeader &p)
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(min: quint64(ICNSBlockHeaderSize), val: quint64(header.length), max: 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
335static inline bool isMaskSuitable(const ICNSEntry &mask, const ICNSEntry &icon, ICNSEntry::Depth target)
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(source: 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(source: *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) {
369 case ICNSEntry::DepthMono:
370 data = ICNSColorTableMono;
371 break;
372 case ICNSEntry::Depth4bit:
373 data = ICNSColorTable4bit;
374 break;
375 case ICNSEntry::Depth8bit:
376 data = ICNSColorTable8bit;
377 break;
378 default:
379 Q_UNREACHABLE();
380 break;
381 }
382 table.resize(size: n);
383 memcpy(dest: table.data(), src: data, n: sizeof(QRgb) * n);
384 return table;
385}
386
387static bool parseIconEntryData(ICNSEntry &icon, QIODevice *device)
388{
389 const qint64 oldPos = device->pos();
390 if (oldPos != icon.dataOffset && !device->seek(pos: icon.dataOffset))
391 return false;
392
393 const QByteArray magic = device->peek(maxlen: 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(pos: oldPos))
402 return false;
403 return true;
404}
405
406static bool parseIconEntryInfo(ICNSEntry &icon)
407{
408 const QString ostype = QString::fromLatin1(ba: nameFromOSType(ostype: 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(subject: ostype);
414 if (!match.hasMatch()) {
415 qWarning(msg: "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(i: 0).toLatin1());
424
425 // That's enough for compressed ones
426 if (isIconCompressed(icon))
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)
437 icon.depth = ICNSEntry::DepthMono;
438 // Detect size:
439 const qreal bytespp = (qreal)icon.depth / 8;
440 const qreal r1 = qSqrt(v: icon.dataLength / bytespp);
441 const qreal r2 = qSqrt(v: (icon.dataLength / bytespp) / 2);
442 const quint32 r1u = qRound(d: r1);
443 const quint32 r2u = qRound(d: r2);
444 const bool singleEntry = isPowOf2OrDividesBy16(u: r1u, r: r1);
445 const bool doubleSize = isPowOf2OrDividesBy16(u: r2u, r: 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) {
452 icon.flags = ICNSEntry::IconPlusMask;
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) {
468 case ICNSEntry::GroupSmall:
469 icon.width = 16;
470 break;
471 case ICNSEntry::GroupLarge:
472 icon.width = 32;
473 break;
474 case ICNSEntry::GroupHuge:
475 icon.width = 48;
476 break;
477 case ICNSEntry::GroupThumbnail:
478 icon.width = 128;
479 break;
480 default:
481 qWarning(msg: "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
492static QImage readMask(const ICNSEntry &mask, QDataStream &stream)
493{
494 if ((mask.flags & ICNSEntry::IsMask) == 0)
495 return QImage();
496 if (mask.depth != ICNSEntry::DepthMono && mask.depth != ICNSEntry::Depth8bit) {
497 qWarning(msg: "readMask(): Failed, unusual bit depth: %u OSType: \"%s\"",
498 mask.depth, nameFromOSType(ostype: 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;
509 if (!QImageIOHandler::allocateImage(size: QSize(mask.width, mask.height), format: QImage::Format_RGB32, image: &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(r: alpha, g: alpha, b: alpha);
522 pixel++;
523 }
524 }
525 stream.device()->seek(pos: oldPos);
526 return img;
527}
528
529template <ICNSEntry::Depth depth>
530static QImage readLowDepthIcon(const ICNSEntry &icon, QDataStream &stream)
531{
532 Q_STATIC_ASSERT(depth == ICNSEntry::DepthMono || depth == ICNSEntry::Depth4bit
533 || depth == ICNSEntry::Depth8bit);
534
535 const bool isMono = depth == ICNSEntry::DepthMono;
536 const QImage::Format format = isMono ? QImage::Format_Mono : QImage::Format_Indexed8;
537 const QList<QRgb> colortable = getColorTable(depth);
538 if (colortable.isEmpty())
539 return QImage();
540 QImage img;
541 if (!QImageIOHandler::allocateImage(size: QSize(icon.width, icon.height), format, image: &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) {
552 case ICNSEntry::DepthMono:
553 cindex = (byte >> 7) & 0x01; // left 1 bit
554 byte <<= 1;
555 break;
556 case ICNSEntry::Depth4bit:
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, index_or_rgb: cindex);
565 pixel++;
566 }
567 }
568 return img;
569}
570
571static QImage read32bitIcon(const ICNSEntry &icon, QDataStream &stream)
572{
573 QImage img;
574 if (!QImageIOHandler::allocateImage(size: QSize(icon.width, icon.height), format: QImage::Format_RGB32, image: &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(maxlen: 4);
588 if (bytes.isEmpty())
589 return QImage();
590 // Zero-padding may be present:
591 if (qFromBigEndian<quint32>(source: *bytes.constData()) == 0)
592 stream.skipRawData(len: 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
626QICNSHandler::QICNSHandler() :
627 m_currentIconIndex(0), m_state(ScanNotScanned)
628{
629}
630
631bool QICNSHandler::canRead(QIODevice *device)
632{
633 if (!device || !device->isReadable()) {
634 qWarning(msg: "QICNSHandler::canRead() called without a readable device");
635 return false;
636 }
637
638 if (device->peek(maxlen: 4) == QByteArrayLiteral("icns")) {
639 if (device->isSequential()) {
640 qWarning(msg: "QICNSHandler::canRead() called on a sequential device");
641 return false;
642 }
643 return true;
644 }
645
646 return false;
647}
648
649bool QICNSHandler::canRead() const
650{
651 if (m_state == ScanNotScanned && !canRead(device: device()))
652 return false;
653
654 if (m_state != ScanError) {
655 setFormat(QByteArrayLiteral("icns"));
656 return true;
657 }
658
659 return false;
660}
661
662bool QICNSHandler::read(QImage *outImage)
663{
664 QImage img;
665 if (!ensureScanned() || m_currentIconIndex >= m_icons.size()) {
666 qWarning(msg: "QICNSHandler::read(): The device wasn't parsed properly!");
667 return false;
668 }
669
670 const ICNSEntry &icon = m_icons.at(i: m_currentIconIndex);
671 QDataStream stream(device());
672 stream.setByteOrder(QDataStream::BigEndian);
673 if (!device()->seek(pos: icon.dataOffset))
674 return false;
675
676 switch (icon.dataFormat) {
677 case ICNSEntry::RawIcon:
678 case ICNSEntry::RLE24:
679 if (qMin(a: icon.width, b: icon.height) == 0)
680 break;
681 switch (icon.depth) {
682 case ICNSEntry::DepthMono:
683 img = readLowDepthIcon<ICNSEntry::DepthMono>(icon, stream);
684 break;
685 case ICNSEntry::Depth4bit:
686 img = readLowDepthIcon<ICNSEntry::Depth4bit>(icon, stream);
687 break;
688 case ICNSEntry::Depth8bit:
689 img = readLowDepthIcon<ICNSEntry::Depth8bit>(icon, stream);
690 break;
691 case ICNSEntry::Depth32bit:
692 img = read32bitIcon(icon, stream);
693 break;
694 default:
695 qWarning(msg: "QICNSHandler::read(): Failed, unsupported icon bit depth: %u, OSType: \"%s\"",
696 icon.depth, nameFromOSType(ostype: icon.ostype).constData());
697 }
698 if (!img.isNull()) {
699 QImage alpha = readMask(mask: 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(data: device()->read(maxlen: icon.dataLength), format);
712 if (img.isNull()) {
713 if (format == 0)
714 format = "unknown";
715 qWarning(msg: "QICNSHandler::read(): Failed, compressed format \"%s\" is not supported " \
716 "by your Qt library or this file is corrupt. OSType: \"%s\"",
717 format, nameFromOSType(ostype: icon.ostype).constData());
718 }
719 }
720 *outImage = img;
721 return !img.isNull();
722}
723
724bool QICNSHandler::write(const QImage &image)
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(a: image.width(), b: image.height()) == 0)
737 return false;
738 const int minSize = qMin(a: image.width(), b: 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(a: image.width(), b: image.height()) != minSize)
750 img = img.scaled(w: newSize, h: newSize, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
751 // Construct OSType and headers:
752 const quint32 ostype = nameToOSType(ostype: nameForCompressedIcon(iconNumber: pow));
753 ICNSBlockHeader fileHeader;
754 fileHeader.ostype = ICNSBlockHeader::TypeIcns;
755 ICNSBlockHeader tocHeader;
756 tocHeader.ostype = ICNSBlockHeader::TypeToc;
757 ICNSBlockHeader iconEntry;
758 iconEntry.ostype = ostype;
759 QByteArray imageData;
760 QBuffer buffer(&imageData);
761 if (!buffer.open(openMode: QIODevice::WriteOnly) || !img.save(device: &buffer, format: "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(header: iconEntry))
768 return false;
769
770 QDataStream stream(device);
771 // iconEntry is also a TOC entry
772 stream << fileHeader << tocHeader << iconEntry << iconEntry;
773 stream.writeRawData(imageData.constData(), len: imageData.size());
774 return stream.status() == QDataStream::Ok;
775}
776
777bool QICNSHandler::supportsOption(ImageOption option) const
778{
779 return option == SubType;
780}
781
782QVariant QICNSHandler::option(ImageOption option) const
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(i: m_currentIconIndex);
790 if (icon.variant != 0)
791 return QByteArray(nameFromOSType(ostype: icon.variant) + '-' + nameFromOSType(ostype: icon.ostype));
792 return nameFromOSType(ostype: icon.ostype);
793 }
794 }
795
796 return QVariant();
797}
798
799int QICNSHandler::imageCount() const
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
816bool QICNSHandler::jumpToNextImage()
817{
818 return jumpToImage(imageNumber: 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
834 ICNSEntry entry;
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:
840 if (!parseIconEntryData(icon&: entry, device: device()))
841 return false;
842 // Parse everything else and index this entry:
843 if (parseIconEntryInfo(icon&: 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(pos: 0))
858 return false;
859
860 QDataStream stream(device());
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(header: blockHeader, bound: ICNSBlockHeaderSize + filelength - blockDataOffset)) {
873 qWarning(msg: "QICNSHandler::scanDevice(): Failed, bad header at pos %s. OSType \"%s\", length %u",
874 QByteArray::number(blockDataOffset).constData(),
875 nameFromOSType(ostype: 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) {
882 case ICNSBlockHeader::TypeIcns:
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(len: blockDataLength);
887 break;
888 }
889 filelength = blockHeader.length;
890 if (device()->size() < blockHeader.length) {
891 qWarning(msg: "QICNSHandler::scanDevice(): Failed, file is incomplete.");
892 return false;
893 }
894 break;
895 case ICNSBlockHeader::TypeIcnv:
896 case ICNSBlockHeader::TypeClut:
897 // We don't have a good use for these blocks... yet.
898 stream.skipRawData(len: blockDataLength);
899 break;
900 case ICNSBlockHeader::TypeTile:
901 case ICNSBlockHeader::TypeOver:
902 case ICNSBlockHeader::TypeOpen:
903 case ICNSBlockHeader::TypeDrop:
904 case ICNSBlockHeader::TypeOdrp:
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) {
908 ICNSBlockHeader icon;
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);
914 if (!isBlockHeaderValid(header: icon, bound: ICNSBlockHeaderSize + remaining))
915 break;
916 if (!addEntry(header: icon, imgDataOffset: device()->pos(), variant: blockHeader.ostype))
917 return false;
918 if (stream.skipRawData(len: 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(msg: "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(ostype: blockHeader.ostype).constData(),
928 QByteArray::number(device()->pos()).constData(),
929 QByteArray::number(nextBlockOffset - device()->pos()).constData());
930 if (!device()->seek(pos: nextBlockOffset))
931 return false;
932 }
933 break;
934 case ICNSBlockHeader::TypeToc: {
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(len: 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(header: tocEntry)) {
948 // TOC contains incorrect header, we should skip TOC since we can't trust it
949 qWarning(msg: "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(pos: nextBlockOffset))
953 return false;
954 break;
955 }
956 if (!addEntry(header: 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(header: blockHeader, imgDataOffset: blockDataOffset))
978 return false;
979 } else if (!addEntry(header: blockHeader, imgDataOffset: blockDataOffset)) {
980 return false;
981 }
982 stream.skipRawData(len: 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;
992 ICNSEntry::Depth targetDepth = is32bit ? ICNSEntry::Depth8bit : ICNSEntry::DepthMono;
993 for (int i = 0; i < m_masks.size(); i++) {
994 const ICNSEntry &mask = m_masks.at(i);
995 if (isMaskSuitable(mask, icon, target: targetDepth))
996 return mask;
997 }
998 return icon;
999}
1000
1001QT_END_NAMESPACE
1002
1003#endif // QT_NO_DATASTREAM
1004

source code of qtimageformats/src/plugins/imageformats/icns/qicnshandler.cpp