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
6#include "qicnshandler_p.h"
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
16QT_BEGIN_NAMESPACE
17
18static const quint8 ICNSBlockHeaderSize = 8;
19
20static const QRgb ICNSColorTableMono[] = {
21 qRgb(r: 0xFF, g: 0xFF, b: 0xFF),
22 qRgb(r: 0x00, g: 0x00, b: 0x00)
23};
24Q_STATIC_ASSERT(sizeof(ICNSColorTableMono) / sizeof(ICNSColorTableMono[0]) == (1 << ICNSEntry::DepthMono));
25
26static const QRgb ICNSColorTable4bit[] = {
27 qRgb(r: 0xFF, g: 0xFF, b: 0xFF),
28 qRgb(r: 0xFC, g: 0xF3, b: 0x05),
29 qRgb(r: 0xFF, g: 0x64, b: 0x02),
30 qRgb(r: 0xDD, g: 0x08, b: 0x06),
31 qRgb(r: 0xF2, g: 0x08, b: 0x84),
32 qRgb(r: 0x46, g: 0x00, b: 0xA5),
33 qRgb(r: 0x00, g: 0x00, b: 0xD4),
34 qRgb(r: 0x02, g: 0xAB, b: 0xEA),
35 qRgb(r: 0x1F, g: 0xB7, b: 0x14),
36 qRgb(r: 0x00, g: 0x64, b: 0x11),
37 qRgb(r: 0x56, g: 0x2C, b: 0x05),
38 qRgb(r: 0x90, g: 0x71, b: 0x3A),
39 qRgb(r: 0xC0, g: 0xC0, b: 0xC0),
40 qRgb(r: 0x80, g: 0x80, b: 0x80),
41 qRgb(r: 0x40, g: 0x40, b: 0x40),
42 qRgb(r: 0x00, g: 0x00, b: 0x00)
43};
44Q_STATIC_ASSERT(sizeof(ICNSColorTable4bit) / sizeof(ICNSColorTable4bit[0]) == (1 << ICNSEntry::Depth4bit));
45
46static const QRgb ICNSColorTable8bit[] = {
47 qRgb(r: 0xFF, g: 0xFF, b: 0xFF),
48 qRgb(r: 0xFF, g: 0xFF, b: 0xCC),
49 qRgb(r: 0xFF, g: 0xFF, b: 0x99),
50 qRgb(r: 0xFF, g: 0xFF, b: 0x66),
51 qRgb(r: 0xFF, g: 0xFF, b: 0x33),
52 qRgb(r: 0xFF, g: 0xFF, b: 0x00),
53 qRgb(r: 0xFF, g: 0xCC, b: 0xFF),
54 qRgb(r: 0xFF, g: 0xCC, b: 0xCC),
55 qRgb(r: 0xFF, g: 0xCC, b: 0x99),
56 qRgb(r: 0xFF, g: 0xCC, b: 0x66),
57 qRgb(r: 0xFF, g: 0xCC, b: 0x33),
58 qRgb(r: 0xFF, g: 0xCC, b: 0x00),
59 qRgb(r: 0xFF, g: 0x99, b: 0xFF),
60 qRgb(r: 0xFF, g: 0x99, b: 0xCC),
61 qRgb(r: 0xFF, g: 0x99, b: 0x99),
62 qRgb(r: 0xFF, g: 0x99, b: 0x66),
63 qRgb(r: 0xFF, g: 0x99, b: 0x33),
64 qRgb(r: 0xFF, g: 0x99, b: 0x00),
65 qRgb(r: 0xFF, g: 0x66, b: 0xFF),
66 qRgb(r: 0xFF, g: 0x66, b: 0xCC),
67 qRgb(r: 0xFF, g: 0x66, b: 0x99),
68 qRgb(r: 0xFF, g: 0x66, b: 0x66),
69 qRgb(r: 0xFF, g: 0x66, b: 0x33),
70 qRgb(r: 0xFF, g: 0x66, b: 0x00),
71 qRgb(r: 0xFF, g: 0x33, b: 0xFF),
72 qRgb(r: 0xFF, g: 0x33, b: 0xCC),
73 qRgb(r: 0xFF, g: 0x33, b: 0x99),
74 qRgb(r: 0xFF, g: 0x33, b: 0x66),
75 qRgb(r: 0xFF, g: 0x33, b: 0x33),
76 qRgb(r: 0xFF, g: 0x33, b: 0x00),
77 qRgb(r: 0xFF, g: 0x00, b: 0xFF),
78 qRgb(r: 0xFF, g: 0x00, b: 0xCC),
79 qRgb(r: 0xFF, g: 0x00, b: 0x99),
80 qRgb(r: 0xFF, g: 0x00, b: 0x66),
81 qRgb(r: 0xFF, g: 0x00, b: 0x33),
82 qRgb(r: 0xFF, g: 0x00, b: 0x00),
83 qRgb(r: 0xCC, g: 0xFF, b: 0xFF),
84 qRgb(r: 0xCC, g: 0xFF, b: 0xCC),
85 qRgb(r: 0xCC, g: 0xFF, b: 0x99),
86 qRgb(r: 0xCC, g: 0xFF, b: 0x66),
87 qRgb(r: 0xCC, g: 0xFF, b: 0x33),
88 qRgb(r: 0xCC, g: 0xFF, b: 0x00),
89 qRgb(r: 0xCC, g: 0xCC, b: 0xFF),
90 qRgb(r: 0xCC, g: 0xCC, b: 0xCC),
91 qRgb(r: 0xCC, g: 0xCC, b: 0x99),
92 qRgb(r: 0xCC, g: 0xCC, b: 0x66),
93 qRgb(r: 0xCC, g: 0xCC, b: 0x33),
94 qRgb(r: 0xCC, g: 0xCC, b: 0x00),
95 qRgb(r: 0xCC, g: 0x99, b: 0xFF),
96 qRgb(r: 0xCC, g: 0x99, b: 0xCC),
97 qRgb(r: 0xCC, g: 0x99, b: 0x99),
98 qRgb(r: 0xCC, g: 0x99, b: 0x66),
99 qRgb(r: 0xCC, g: 0x99, b: 0x33),
100 qRgb(r: 0xCC, g: 0x99, b: 0x00),
101 qRgb(r: 0xCC, g: 0x66, b: 0xFF),
102 qRgb(r: 0xCC, g: 0x66, b: 0xCC),
103 qRgb(r: 0xCC, g: 0x66, b: 0x99),
104 qRgb(r: 0xCC, g: 0x66, b: 0x66),
105 qRgb(r: 0xCC, g: 0x66, b: 0x33),
106 qRgb(r: 0xCC, g: 0x66, b: 0x00),
107 qRgb(r: 0xCC, g: 0x33, b: 0xFF),
108 qRgb(r: 0xCC, g: 0x33, b: 0xCC),
109 qRgb(r: 0xCC, g: 0x33, b: 0x99),
110 qRgb(r: 0xCC, g: 0x33, b: 0x66),
111 qRgb(r: 0xCC, g: 0x33, b: 0x33),
112 qRgb(r: 0xCC, g: 0x33, b: 0x00),
113 qRgb(r: 0xCC, g: 0x00, b: 0xFF),
114 qRgb(r: 0xCC, g: 0x00, b: 0xCC),
115 qRgb(r: 0xCC, g: 0x00, b: 0x99),
116 qRgb(r: 0xCC, g: 0x00, b: 0x66),
117 qRgb(r: 0xCC, g: 0x00, b: 0x33),
118 qRgb(r: 0xCC, g: 0x00, b: 0x00),
119 qRgb(r: 0x99, g: 0xFF, b: 0xFF),
120 qRgb(r: 0x99, g: 0xFF, b: 0xCC),
121 qRgb(r: 0x99, g: 0xFF, b: 0x99),
122 qRgb(r: 0x99, g: 0xFF, b: 0x66),
123 qRgb(r: 0x99, g: 0xFF, b: 0x33),
124 qRgb(r: 0x99, g: 0xFF, b: 0x00),
125 qRgb(r: 0x99, g: 0xCC, b: 0xFF),
126 qRgb(r: 0x99, g: 0xCC, b: 0xCC),
127 qRgb(r: 0x99, g: 0xCC, b: 0x99),
128 qRgb(r: 0x99, g: 0xCC, b: 0x66),
129 qRgb(r: 0x99, g: 0xCC, b: 0x33),
130 qRgb(r: 0x99, g: 0xCC, b: 0x00),
131 qRgb(r: 0x99, g: 0x99, b: 0xFF),
132 qRgb(r: 0x99, g: 0x99, b: 0xCC),
133 qRgb(r: 0x99, g: 0x99, b: 0x99),
134 qRgb(r: 0x99, g: 0x99, b: 0x66),
135 qRgb(r: 0x99, g: 0x99, b: 0x33),
136 qRgb(r: 0x99, g: 0x99, b: 0x00),
137 qRgb(r: 0x99, g: 0x66, b: 0xFF),
138 qRgb(r: 0x99, g: 0x66, b: 0xCC),
139 qRgb(r: 0x99, g: 0x66, b: 0x99),
140 qRgb(r: 0x99, g: 0x66, b: 0x66),
141 qRgb(r: 0x99, g: 0x66, b: 0x33),
142 qRgb(r: 0x99, g: 0x66, b: 0x00),
143 qRgb(r: 0x99, g: 0x33, b: 0xFF),
144 qRgb(r: 0x99, g: 0x33, b: 0xCC),
145 qRgb(r: 0x99, g: 0x33, b: 0x99),
146 qRgb(r: 0x99, g: 0x33, b: 0x66),
147 qRgb(r: 0x99, g: 0x33, b: 0x33),
148 qRgb(r: 0x99, g: 0x33, b: 0x00),
149 qRgb(r: 0x99, g: 0x00, b: 0xFF),
150 qRgb(r: 0x99, g: 0x00, b: 0xCC),
151 qRgb(r: 0x99, g: 0x00, b: 0x99),
152 qRgb(r: 0x99, g: 0x00, b: 0x66),
153 qRgb(r: 0x99, g: 0x00, b: 0x33),
154 qRgb(r: 0x99, g: 0x00, b: 0x00),
155 qRgb(r: 0x66, g: 0xFF, b: 0xFF),
156 qRgb(r: 0x66, g: 0xFF, b: 0xCC),
157 qRgb(r: 0x66, g: 0xFF, b: 0x99),
158 qRgb(r: 0x66, g: 0xFF, b: 0x66),
159 qRgb(r: 0x66, g: 0xFF, b: 0x33),
160 qRgb(r: 0x66, g: 0xFF, b: 0x00),
161 qRgb(r: 0x66, g: 0xCC, b: 0xFF),
162 qRgb(r: 0x66, g: 0xCC, b: 0xCC),
163 qRgb(r: 0x66, g: 0xCC, b: 0x99),
164 qRgb(r: 0x66, g: 0xCC, b: 0x66),
165 qRgb(r: 0x66, g: 0xCC, b: 0x33),
166 qRgb(r: 0x66, g: 0xCC, b: 0x00),
167 qRgb(r: 0x66, g: 0x99, b: 0xFF),
168 qRgb(r: 0x66, g: 0x99, b: 0xCC),
169 qRgb(r: 0x66, g: 0x99, b: 0x99),
170 qRgb(r: 0x66, g: 0x99, b: 0x66),
171 qRgb(r: 0x66, g: 0x99, b: 0x33),
172 qRgb(r: 0x66, g: 0x99, b: 0x00),
173 qRgb(r: 0x66, g: 0x66, b: 0xFF),
174 qRgb(r: 0x66, g: 0x66, b: 0xCC),
175 qRgb(r: 0x66, g: 0x66, b: 0x99),
176 qRgb(r: 0x66, g: 0x66, b: 0x66),
177 qRgb(r: 0x66, g: 0x66, b: 0x33),
178 qRgb(r: 0x66, g: 0x66, b: 0x00),
179 qRgb(r: 0x66, g: 0x33, b: 0xFF),
180 qRgb(r: 0x66, g: 0x33, b: 0xCC),
181 qRgb(r: 0x66, g: 0x33, b: 0x99),
182 qRgb(r: 0x66, g: 0x33, b: 0x66),
183 qRgb(r: 0x66, g: 0x33, b: 0x33),
184 qRgb(r: 0x66, g: 0x33, b: 0x00),
185 qRgb(r: 0x66, g: 0x00, b: 0xFF),
186 qRgb(r: 0x66, g: 0x00, b: 0xCC),
187 qRgb(r: 0x66, g: 0x00, b: 0x99),
188 qRgb(r: 0x66, g: 0x00, b: 0x66),
189 qRgb(r: 0x66, g: 0x00, b: 0x33),
190 qRgb(r: 0x66, g: 0x00, b: 0x00),
191 qRgb(r: 0x33, g: 0xFF, b: 0xFF),
192 qRgb(r: 0x33, g: 0xFF, b: 0xCC),
193 qRgb(r: 0x33, g: 0xFF, b: 0x99),
194 qRgb(r: 0x33, g: 0xFF, b: 0x66),
195 qRgb(r: 0x33, g: 0xFF, b: 0x33),
196 qRgb(r: 0x33, g: 0xFF, b: 0x00),
197 qRgb(r: 0x33, g: 0xCC, b: 0xFF),
198 qRgb(r: 0x33, g: 0xCC, b: 0xCC),
199 qRgb(r: 0x33, g: 0xCC, b: 0x99),
200 qRgb(r: 0x33, g: 0xCC, b: 0x66),
201 qRgb(r: 0x33, g: 0xCC, b: 0x33),
202 qRgb(r: 0x33, g: 0xCC, b: 0x00),
203 qRgb(r: 0x33, g: 0x99, b: 0xFF),
204 qRgb(r: 0x33, g: 0x99, b: 0xCC),
205 qRgb(r: 0x33, g: 0x99, b: 0x99),
206 qRgb(r: 0x33, g: 0x99, b: 0x66),
207 qRgb(r: 0x33, g: 0x99, b: 0x33),
208 qRgb(r: 0x33, g: 0x99, b: 0x00),
209 qRgb(r: 0x33, g: 0x66, b: 0xFF),
210 qRgb(r: 0x33, g: 0x66, b: 0xCC),
211 qRgb(r: 0x33, g: 0x66, b: 0x99),
212 qRgb(r: 0x33, g: 0x66, b: 0x66),
213 qRgb(r: 0x33, g: 0x66, b: 0x33),
214 qRgb(r: 0x33, g: 0x66, b: 0x00),
215 qRgb(r: 0x33, g: 0x33, b: 0xFF),
216 qRgb(r: 0x33, g: 0x33, b: 0xCC),
217 qRgb(r: 0x33, g: 0x33, b: 0x99),
218 qRgb(r: 0x33, g: 0x33, b: 0x66),
219 qRgb(r: 0x33, g: 0x33, b: 0x33),
220 qRgb(r: 0x33, g: 0x33, b: 0x00),
221 qRgb(r: 0x33, g: 0x00, b: 0xFF),
222 qRgb(r: 0x33, g: 0x00, b: 0xCC),
223 qRgb(r: 0x33, g: 0x00, b: 0x99),
224 qRgb(r: 0x33, g: 0x00, b: 0x66),
225 qRgb(r: 0x33, g: 0x00, b: 0x33),
226 qRgb(r: 0x33, g: 0x00, b: 0x00),
227 qRgb(r: 0x00, g: 0xFF, b: 0xFF),
228 qRgb(r: 0x00, g: 0xFF, b: 0xCC),
229 qRgb(r: 0x00, g: 0xFF, b: 0x99),
230 qRgb(r: 0x00, g: 0xFF, b: 0x66),
231 qRgb(r: 0x00, g: 0xFF, b: 0x33),
232 qRgb(r: 0x00, g: 0xFF, b: 0x00),
233 qRgb(r: 0x00, g: 0xCC, b: 0xFF),
234 qRgb(r: 0x00, g: 0xCC, b: 0xCC),
235 qRgb(r: 0x00, g: 0xCC, b: 0x99),
236 qRgb(r: 0x00, g: 0xCC, b: 0x66),
237 qRgb(r: 0x00, g: 0xCC, b: 0x33),
238 qRgb(r: 0x00, g: 0xCC, b: 0x00),
239 qRgb(r: 0x00, g: 0x99, b: 0xFF),
240 qRgb(r: 0x00, g: 0x99, b: 0xCC),
241 qRgb(r: 0x00, g: 0x99, b: 0x99),
242 qRgb(r: 0x00, g: 0x99, b: 0x66),
243 qRgb(r: 0x00, g: 0x99, b: 0x33),
244 qRgb(r: 0x00, g: 0x99, b: 0x00),
245 qRgb(r: 0x00, g: 0x66, b: 0xFF),
246 qRgb(r: 0x00, g: 0x66, b: 0xCC),
247 qRgb(r: 0x00, g: 0x66, b: 0x99),
248 qRgb(r: 0x00, g: 0x66, b: 0x66),
249 qRgb(r: 0x00, g: 0x66, b: 0x33),
250 qRgb(r: 0x00, g: 0x66, b: 0x00),
251 qRgb(r: 0x00, g: 0x33, b: 0xFF),
252 qRgb(r: 0x00, g: 0x33, b: 0xCC),
253 qRgb(r: 0x00, g: 0x33, b: 0x99),
254 qRgb(r: 0x00, g: 0x33, b: 0x66),
255 qRgb(r: 0x00, g: 0x33, b: 0x33),
256 qRgb(r: 0x00, g: 0x33, b: 0x00),
257 qRgb(r: 0x00, g: 0x00, b: 0xFF),
258 qRgb(r: 0x00, g: 0x00, b: 0xCC),
259 qRgb(r: 0x00, g: 0x00, b: 0x99),
260 qRgb(r: 0x00, g: 0x00, b: 0x66),
261 qRgb(r: 0x00, g: 0x00, b: 0x33),
262 qRgb(r: 0xEE, g: 0x00, b: 0x00),
263 qRgb(r: 0xDD, g: 0x00, b: 0x00),
264 qRgb(r: 0xBB, g: 0x00, b: 0x00),
265 qRgb(r: 0xAA, g: 0x00, b: 0x00),
266 qRgb(r: 0x88, g: 0x00, b: 0x00),
267 qRgb(r: 0x77, g: 0x00, b: 0x00),
268 qRgb(r: 0x55, g: 0x00, b: 0x00),
269 qRgb(r: 0x44, g: 0x00, b: 0x00),
270 qRgb(r: 0x22, g: 0x00, b: 0x00),
271 qRgb(r: 0x11, g: 0x00, b: 0x00),
272 qRgb(r: 0x00, g: 0xEE, b: 0x00),
273 qRgb(r: 0x00, g: 0xDD, b: 0x00),
274 qRgb(r: 0x00, g: 0xBB, b: 0x00),
275 qRgb(r: 0x00, g: 0xAA, b: 0x00),
276 qRgb(r: 0x00, g: 0x88, b: 0x00),
277 qRgb(r: 0x00, g: 0x77, b: 0x00),
278 qRgb(r: 0x00, g: 0x55, b: 0x00),
279 qRgb(r: 0x00, g: 0x44, b: 0x00),
280 qRgb(r: 0x00, g: 0x22, b: 0x00),
281 qRgb(r: 0x00, g: 0x11, b: 0x00),
282 qRgb(r: 0x00, g: 0x00, b: 0xEE),
283 qRgb(r: 0x00, g: 0x00, b: 0xDD),
284 qRgb(r: 0x00, g: 0x00, b: 0xBB),
285 qRgb(r: 0x00, g: 0x00, b: 0xAA),
286 qRgb(r: 0x00, g: 0x00, b: 0x88),
287 qRgb(r: 0x00, g: 0x00, b: 0x77),
288 qRgb(r: 0x00, g: 0x00, b: 0x55),
289 qRgb(r: 0x00, g: 0x00, b: 0x44),
290 qRgb(r: 0x00, g: 0x00, b: 0x22),
291 qRgb(r: 0x00, g: 0x00, b: 0x11),
292 qRgb(r: 0xEE, g: 0xEE, b: 0xEE),
293 qRgb(r: 0xDD, g: 0xDD, b: 0xDD),
294 qRgb(r: 0xBB, g: 0xBB, b: 0xBB),
295 qRgb(r: 0xAA, g: 0xAA, b: 0xAA),
296 qRgb(r: 0x88, g: 0x88, b: 0x88),
297 qRgb(r: 0x77, g: 0x77, b: 0x77),
298 qRgb(r: 0x55, g: 0x55, b: 0x55),
299 qRgb(r: 0x44, g: 0x44, b: 0x44),
300 qRgb(r: 0x22, g: 0x22, b: 0x22),
301 qRgb(r: 0x11, g: 0x11, b: 0x11),
302 qRgb(r: 0x00, g: 0x00, b: 0x00)
303};
304Q_STATIC_ASSERT(sizeof(ICNSColorTable8bit) / sizeof(ICNSColorTable8bit[0]) == (1 << ICNSEntry::Depth8bit));
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{
336 return icon.dataFormat == ICNSEntry::PNG || icon.dataFormat == ICNSEntry::JP2;
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(source: 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(source: *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) {
373 case ICNSEntry::DepthMono:
374 data = ICNSColorTableMono;
375 break;
376 case ICNSEntry::Depth4bit:
377 data = ICNSColorTable4bit;
378 break;
379 case ICNSEntry::Depth8bit:
380 data = ICNSColorTable8bit;
381 break;
382 default:
383 Q_UNREACHABLE();
384 break;
385 }
386 table.resize(size: n);
387 memcpy(dest: table.data(), src: data, n: 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(pos: icon.dataOffset))
395 return false;
396
397 const QByteArray magic = device->peek(maxlen: 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?
402 icon.flags = ICNSEntry::IsIcon;
403 icon.dataFormat = isPNG? ICNSEntry::PNG : ICNSEntry::JP2;
404 }
405 if (oldPos != icon.dataOffset && !device->seek(pos: oldPos))
406 return false;
407 return true;
408}
409
410static bool parseIconEntryInfo(ICNSEntry &icon)
411{
412 const QString ostype = QString::fromLatin1(ba: nameFromOSType(ostype: 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(subject: ostype);
418 if (!match.hasMatch()) {
419 qWarning(msg: "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(i: 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
440 if (icon.depth == ICNSEntry::DepthUnknown)
441 icon.depth = ICNSEntry::DepthMono;
442 // Detect size:
443 const qreal bytespp = (qreal)icon.depth / 8;
444 const qreal r1 = qSqrt(v: icon.dataLength / bytespp);
445 const qreal r2 = qSqrt(v: (icon.dataLength / bytespp) / 2);
446 const quint32 r1u = qRound(d: r1);
447 const quint32 r2u = qRound(d: r2);
448 const bool singleEntry = isPowOf2OrDividesBy16(u: r1u, r: r1);
449 const bool doubleSize = isPowOf2OrDividesBy16(u: r2u, r: r2);
450 if (singleEntry) {
451 icon.flags = mask.isEmpty() ? ICNSEntry::IsIcon : ICNSEntry::IsMask;
452 icon.dataFormat = ICNSEntry::RawIcon;
453 icon.width = r1u;
454 icon.height = r1u;
455 } else if (doubleSize) {
456 icon.flags = ICNSEntry::IconPlusMask;
457 icon.dataFormat = ICNSEntry::RawIcon;
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;
463 icon.flags = doubleSize ? ICNSEntry::IconPlusMask : ICNSEntry::IsIcon;
464 icon.dataFormat = ICNSEntry::RawIcon;
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
469 icon.dataFormat = ICNSEntry::RLE24;
470 icon.flags = mask.isEmpty() ? ICNSEntry::IsIcon : ICNSEntry::IsMask;
471 switch (icon.group) {
472 case ICNSEntry::GroupSmall:
473 icon.width = 16;
474 break;
475 case ICNSEntry::GroupLarge:
476 icon.width = 32;
477 break;
478 case ICNSEntry::GroupHuge:
479 icon.width = 48;
480 break;
481 case ICNSEntry::GroupThumbnail:
482 icon.width = 128;
483 break;
484 default:
485 qWarning(msg: "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();
500 if (mask.depth != ICNSEntry::DepthMono && mask.depth != ICNSEntry::Depth8bit) {
501 qWarning(msg: "readMask(): Failed, unusual bit depth: %u OSType: \"%s\"",
502 mask.depth, nameFromOSType(ostype: 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(size: QSize(mask.width, mask.height), format: QImage::Format_RGB32, image: &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(r: alpha, g: alpha, b: alpha);
526 pixel++;
527 }
528 }
529 stream.device()->seek(pos: oldPos);
530 return img;
531}
532
533template <ICNSEntry::Depth depth>
534static QImage readLowDepthIcon(const ICNSEntry &icon, QDataStream &stream)
535{
536 Q_STATIC_ASSERT(depth == ICNSEntry::DepthMono || depth == ICNSEntry::Depth4bit
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(size: QSize(icon.width, icon.height), format, image: &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) {
556 case ICNSEntry::DepthMono:
557 cindex = (byte >> 7) & 0x01; // left 1 bit
558 byte <<= 1;
559 break;
560 case ICNSEntry::Depth4bit:
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, index_or_rgb: 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(size: QSize(icon.width, icon.height), format: QImage::Format_RGB32, image: &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(maxlen: 4);
592 if (bytes.isEmpty())
593 return QImage();
594 // Zero-padding may be present:
595 if (qFromBigEndian<quint32>(source: *bytes.constData()) == 0)
596 stream.skipRawData(len: 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
630QICNSHandler::QICNSHandler() :
631 m_currentIconIndex(0), m_state(ScanNotScanned)
632{
633}
634
635bool QICNSHandler::canRead(QIODevice *device)
636{
637 if (!device || !device->isReadable()) {
638 qWarning(msg: "QICNSHandler::canRead() called without a readable device");
639 return false;
640 }
641
642 if (device->peek(maxlen: 4) == QByteArrayLiteral("icns")) {
643 if (device->isSequential()) {
644 qWarning(msg: "QICNSHandler::canRead() called on a sequential device");
645 return false;
646 }
647 return true;
648 }
649
650 return false;
651}
652
653bool QICNSHandler::canRead() const
654{
655 if (m_state == ScanNotScanned && !canRead(device: 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(msg: "QICNSHandler::read(): The device wasn't parsed properly!");
671 return false;
672 }
673
674 const ICNSEntry &icon = m_icons.at(i: m_currentIconIndex);
675 QDataStream stream(device());
676 stream.setByteOrder(QDataStream::BigEndian);
677 if (!device()->seek(pos: icon.dataOffset))
678 return false;
679
680 switch (icon.dataFormat) {
681 case ICNSEntry::RawIcon:
682 case ICNSEntry::RLE24:
683 if (qMin(a: icon.width, b: icon.height) == 0)
684 break;
685 switch (icon.depth) {
686 case ICNSEntry::DepthMono:
687 img = readLowDepthIcon<ICNSEntry::DepthMono>(icon, stream);
688 break;
689 case ICNSEntry::Depth4bit:
690 img = readLowDepthIcon<ICNSEntry::Depth4bit>(icon, stream);
691 break;
692 case ICNSEntry::Depth8bit:
693 img = readLowDepthIcon<ICNSEntry::Depth8bit>(icon, stream);
694 break;
695 case ICNSEntry::Depth32bit:
696 img = read32bitIcon(icon, stream);
697 break;
698 default:
699 qWarning(msg: "QICNSHandler::read(): Failed, unsupported icon bit depth: %u, OSType: \"%s\"",
700 icon.depth, nameFromOSType(ostype: icon.ostype).constData());
701 }
702 if (!img.isNull()) {
703 QImage alpha = readMask(mask: 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(data: device()->read(maxlen: icon.dataLength), format);
716 if (img.isNull()) {
717 if (format == 0)
718 format = "unknown";
719 qWarning(msg: "QICNSHandler::read(): Failed, compressed format \"%s\" is not supported " \
720 "by your Qt library or this file is corrupt. OSType: \"%s\"",
721 format, nameFromOSType(ostype: 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(a: image.width(), b: image.height()) == 0)
741 return false;
742 const int minSize = qMin(a: image.width(), b: 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(a: image.width(), b: image.height()) != minSize)
754 img = img.scaled(w: newSize, h: newSize, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
755 // Construct OSType and headers:
756 const quint32 ostype = nameToOSType(ostype: nameForCompressedIcon(iconNumber: 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(openMode: QIODevice::WriteOnly) || !img.save(device: &buffer, format: "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(header: 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(), len: 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(i: m_currentIconIndex);
794 if (icon.variant != 0)
795 return QByteArray(nameFromOSType(ostype: icon.variant) + '-' + nameFromOSType(ostype: icon.ostype));
796 return nameFromOSType(ostype: icon.ostype);
797 }
798 }
799
800 return QVariant();
801}
802
803int QICNSHandler::imageCount() const
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
820bool QICNSHandler::jumpToNextImage()
821{
822 return jumpToImage(imageNumber: 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(icon&: entry, device: device()))
845 return false;
846 // Parse everything else and index this entry:
847 if (parseIconEntryInfo(icon&: 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(pos: 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(header: blockHeader, bound: ICNSBlockHeaderSize - blockDataOffset + filelength)) {
877 qWarning(msg: "QICNSHandler::scanDevice(): Failed, bad header at pos %s. OSType \"%s\", length %u",
878 QByteArray::number(blockDataOffset).constData(),
879 nameFromOSType(ostype: 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(len: blockDataLength);
891 break;
892 }
893 filelength = blockHeader.length;
894 if (device()->size() < blockHeader.length) {
895 qWarning(msg: "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(len: 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(header: icon, bound: ICNSBlockHeaderSize + remaining))
919 break;
920 if (!addEntry(header: icon, imgDataOffset: device()->pos(), variant: blockHeader.ostype))
921 return false;
922 if (stream.skipRawData(len: 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(msg: "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(ostype: blockHeader.ostype).constData(),
932 QByteArray::number(device()->pos()).constData(),
933 QByteArray::number(nextBlockOffset - device()->pos()).constData());
934 if (!device()->seek(pos: 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(len: 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(header: tocEntry)) {
952 // TOC contains incorrect header, we should skip TOC since we can't trust it
953 qWarning(msg: "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(pos: nextBlockOffset))
957 return false;
958 break;
959 }
960 if (!addEntry(header: 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(header: blockHeader, imgDataOffset: blockDataOffset))
982 return false;
983 } else if (!addEntry(header: blockHeader, imgDataOffset: blockDataOffset)) {
984 return false;
985 }
986 stream.skipRawData(len: 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, target: targetDepth))
1000 return mask;
1001 }
1002 return icon;
1003}
1004
1005QT_END_NAMESPACE
1006
1007#endif // QT_NO_DATASTREAM
1008

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