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

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