1/* Mac OS X .icns icons loader
2 *
3 * Copyright (c) 2007 Lyonel Vincent <lyonel@ezix.org>
4 * Copyright (c) 2007 Bastien Nocera <hadess@hadess.net>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20#ifndef _WIN32
21#define _GNU_SOURCE
22#endif
23#include "config.h"
24
25#include <stdlib.h>
26#include <string.h>
27#include <errno.h>
28
29#include <glib-object.h>
30#include <glib/gi18n-lib.h>
31
32#include "gdk-pixbuf-core.h"
33#include "gdk-pixbuf-io.h"
34#include "gdk-pixbuf-loader.h"
35
36G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
37G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);
38
39#define IN /**/
40#define OUT /**/
41#define INOUT /**/
42
43struct IcnsBlockHeader
44{
45 char id[4];
46 guint32 size; /* caution: bigendian */
47};
48typedef struct IcnsBlockHeader IcnsBlockHeader;
49
50typedef struct
51{
52 GdkPixbufModuleSizeFunc size_func;
53 GdkPixbufModulePreparedFunc prepared_func;
54 GdkPixbufModuleUpdatedFunc updated_func;
55 gpointer user_data;
56
57 GByteArray *byte_array;
58 GdkPixbuf *pixbuf; /* Our "target" */
59} IcnsProgressiveState;
60
61/*
62 * load raw icon data from 'icns' resource
63 *
64 * returns TRUE when successful
65 */
66static gboolean
67load_resources (unsigned size, IN gpointer data, gsize datalen,
68 OUT guchar ** picture, OUT gsize * plen,
69 OUT guchar ** mask, OUT gsize * mlen)
70{
71 IcnsBlockHeader *header = NULL;
72 const char *bytes = NULL;
73 const char *current = NULL;
74 guint32 blocklen = 0;
75 guint32 icnslen = 0;
76 gboolean needs_mask = TRUE;
77
78 if (datalen < 2 * sizeof (guint32))
79 return FALSE;
80 if (!data)
81 return FALSE;
82
83 *picture = *mask = NULL;
84 *plen = *mlen = 0;
85
86 bytes = data;
87 header = (IcnsBlockHeader *) data;
88 if (memcmp (s1: header->id, s2: "icns", n: 4) != 0)
89 return FALSE;
90
91 icnslen = GUINT32_FROM_BE (header->size);
92 if ((icnslen > datalen) || (icnslen < 2 * sizeof (guint32)))
93 return FALSE;
94
95 current = bytes + sizeof (IcnsBlockHeader);
96 while ((current - bytes < icnslen) && (icnslen - (current - bytes) >= sizeof (IcnsBlockHeader)))
97 {
98 header = (IcnsBlockHeader *) current;
99 blocklen = GUINT32_FROM_BE (header->size);
100
101 /* Check that blocklen isn't garbage */
102 if (blocklen > icnslen - (current - bytes) ||
103 blocklen < sizeof (IcnsBlockHeader))
104 return FALSE;
105
106 switch (size)
107 {
108 case 256:
109 case 512:
110 if (memcmp (s1: header->id, s2: "ic08", n: 4) == 0 /* 256x256 icon */
111 || memcmp (s1: header->id, s2: "ic09", n: 4) == 0) /* 512x512 icon */
112 {
113 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
114 *plen = blocklen - sizeof (IcnsBlockHeader);
115 }
116 needs_mask = FALSE;
117 break;
118 case 128:
119 if (memcmp (s1: header->id, s2: "it32", n: 4) == 0) /* 128x128 icon */
120 {
121 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
122 *plen = blocklen - sizeof (IcnsBlockHeader);
123 if (memcmp (s1: *picture, s2: "\0\0\0\0", n: 4) == 0)
124 {
125 *picture += 4;
126 *plen -= 4;
127 }
128 }
129 if (memcmp (s1: header->id, s2: "t8mk", n: 4) == 0) /* 128x128 mask */
130 {
131 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
132 *mlen = blocklen - sizeof (IcnsBlockHeader);
133 }
134 break;
135 case 48:
136 if (memcmp (s1: header->id, s2: "ih32", n: 4) == 0) /* 48x48 icon */
137 {
138 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
139 *plen = blocklen - sizeof (IcnsBlockHeader);
140 }
141 if (memcmp (s1: header->id, s2: "h8mk", n: 4) == 0) /* 48x48 mask */
142 {
143 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
144 *mlen = blocklen - sizeof (IcnsBlockHeader);
145 }
146 break;
147 case 32:
148 if (memcmp (s1: header->id, s2: "il32", n: 4) == 0) /* 32x32 icon */
149 {
150 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
151 *plen = blocklen - sizeof (IcnsBlockHeader);
152 }
153 if (memcmp (s1: header->id, s2: "l8mk", n: 4) == 0) /* 32x32 mask */
154 {
155 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
156 *mlen = blocklen - sizeof (IcnsBlockHeader);
157 }
158 break;
159 case 16:
160 if (memcmp (s1: header->id, s2: "is32", n: 4) == 0) /* 16x16 icon */
161 {
162 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
163 *plen = blocklen - sizeof (IcnsBlockHeader);
164 }
165 if (memcmp (s1: header->id, s2: "s8mk", n: 4) == 0) /* 16x16 mask */
166 {
167 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
168 *mlen = blocklen - sizeof (IcnsBlockHeader);
169 }
170 break;
171 default:
172 return FALSE;
173 }
174
175 current += blocklen;
176 }
177
178 if (!*picture)
179 return FALSE;
180 if (needs_mask && !*mask)
181 return FALSE;
182 return TRUE;
183}
184
185/*
186 * uncompress RLE-encoded bytes into RGBA scratch zone:
187 * if firstbyte >= 0x80, it indicates the number of identical bytes + 125
188 * (repeated value is stored next: 1 byte)
189 * otherwise, it indicates the number of non-repeating bytes - 1
190 * (non-repeating values are stored next: n bytes)
191 */
192static gboolean
193uncompress (unsigned size, INOUT guchar ** source, OUT guchar * target, INOUT gsize * _remaining)
194{
195 guchar *data = *source;
196 gsize remaining;
197 gsize i = 0;
198
199 /* The first time we're called, set remaining */
200 if (*_remaining == 0) {
201 remaining = size * size;
202 } else {
203 remaining = *_remaining;
204 }
205
206 while (remaining > 0)
207 {
208 guint8 count = 0;
209
210 if (data[0] & 0x80) /* repeating byte */
211 {
212 count = data[0] - 125;
213
214 if (count > remaining)
215 return FALSE;
216
217 for (i = 0; i < count; i++)
218 {
219 *target = data[1];
220 target += 4;
221 }
222
223 data += 2;
224 }
225 else /* non-repeating bytes */
226 {
227 count = data[0] + 1;
228
229 if (count > remaining)
230 return FALSE;
231
232 for (i = 0; i < count; i++)
233 {
234 *target = data[i + 1];
235 target += 4;
236 }
237 data += count + 1;
238 }
239
240 remaining -= count;
241 }
242
243 *source = data;
244 *_remaining = remaining;
245 return TRUE;
246}
247
248static GdkPixbuf *
249load_icon (unsigned size, IN gpointer data, gsize datalen)
250{
251 guchar *icon = NULL;
252 guchar *mask = NULL;
253 gsize isize = 0, msize = 0, i;
254 guchar *image = NULL;
255
256 if (!load_resources (size, data, datalen, picture: &icon, plen: &isize, mask: &mask, mlen: &msize))
257 return NULL;
258
259 /* 256x256 icons don't use RLE or uncompressed data,
260 * They're usually JPEG 2000 images */
261 if (size == 256)
262 {
263 GdkPixbufLoader *loader;
264 GdkPixbuf *pixbuf;
265
266 loader = gdk_pixbuf_loader_new ();
267 if (!gdk_pixbuf_loader_write (loader, buf: icon, count: isize, NULL)
268 || !gdk_pixbuf_loader_close (loader, NULL))
269 {
270 g_object_unref (object: loader);
271 return NULL;
272 }
273
274 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
275 g_object_ref (pixbuf);
276 g_object_unref (object: loader);
277
278 return pixbuf;
279 }
280
281 g_assert (mask);
282
283 if (msize != size * size) /* wrong mask size */
284 return NULL;
285
286 image = (guchar *) g_try_malloc0 (n_bytes: size * size * 4); /* 4 bytes/pixel = RGBA */
287
288 if (!image)
289 return NULL;
290
291 if (isize == size * size * 4) /* icon data is uncompressed */
292 for (i = 0; i < size * size; i++) /* 4 bytes/pixel = ARGB (A: ignored) */
293 {
294 image[i * 4] = icon[4 * i + 1]; /* R */
295 image[i * 4 + 1] = icon[4 * i + 2]; /* G */
296 image[i * 4 + 2] = icon[4 * i + 3]; /* B */
297 }
298 else
299 {
300 guchar *data = icon;
301 gsize remaining = 0;
302
303 /* R */
304 if (!uncompress (size, source: &data, target: image, remaining: &remaining))
305 goto bail;
306 /* G */
307 if (!uncompress (size, source: &data, target: image + 1, remaining: &remaining))
308 goto bail;
309 /* B */
310 if (!uncompress (size, source: &data, target: image + 2, remaining: &remaining))
311 goto bail;
312 }
313
314 for (i = 0; i < size * size; i++) /* copy mask to alpha channel */
315 image[i * 4 + 3] = mask[i];
316
317 return gdk_pixbuf_new_from_data (data: (guchar *) image, colorspace: GDK_COLORSPACE_RGB, /* RGB image */
318 TRUE, /* with alpha channel */
319 bits_per_sample: 8, /* 8 bits per sample */
320 width: size, /* width */
321 height: size, /* height */
322 rowstride: size * 4, /* no gap between rows */
323 destroy_fn: (GdkPixbufDestroyNotify)g_free, /* free() function */
324 NULL); /* param to free() function */
325
326bail:
327 g_free (mem: image);
328 return NULL;
329}
330
331static int sizes[] = {
332 256, /* late-Tiger icons */
333 128, /* Standard OS X */
334 48, /* Not very common */
335 32, /* Standard Mac OS Classic (8 & 9) */
336 24, /* OS X toolbars */
337 16 /* used in Mac OS Classic and dialog boxes */
338};
339
340static GdkPixbuf *
341icns_image_load (FILE *f, GError ** error)
342{
343 GByteArray *data;
344 GdkPixbuf *pixbuf = NULL;
345 guint i;
346
347 data = g_byte_array_new ();
348 while (!feof (stream: f))
349 {
350 gint save_errno;
351 guchar buf[4096];
352 gsize bytes;
353
354 bytes = fread (ptr: buf, size: 1, n: sizeof (buf), stream: f);
355 save_errno = errno;
356 data = g_byte_array_append (array: data, data: buf, len: bytes);
357
358 if (ferror (stream: f))
359 {
360 g_set_error (err: error,
361 G_FILE_ERROR,
362 code: g_file_error_from_errno (err_no: save_errno),
363 _("Error reading ICNS image: %s"),
364 g_strerror (errnum: save_errno));
365
366 g_byte_array_free (array: data, TRUE);
367
368 return NULL;
369 }
370 }
371
372 for (i = 0; i < G_N_ELEMENTS(sizes) && !pixbuf; i++)
373 pixbuf = load_icon (size: sizes[i], data: data->data, datalen: data->len);
374
375 g_byte_array_free (array: data, TRUE);
376
377 if (!pixbuf)
378 g_set_error_literal (err: error, GDK_PIXBUF_ERROR,
379 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
380 _("Could not decode ICNS file"));
381
382 return pixbuf;
383}
384
385static void
386context_free (IcnsProgressiveState *context)
387{
388 g_byte_array_free (array: context->byte_array, TRUE);
389 g_clear_object (&context->pixbuf);
390 g_free (mem: context);
391}
392
393static gpointer
394gdk_pixbuf__icns_image_begin_load (GdkPixbufModuleSizeFunc size_func,
395 GdkPixbufModulePreparedFunc prepared_func,
396 GdkPixbufModuleUpdatedFunc updated_func,
397 gpointer user_data,
398 GError **error)
399{
400 IcnsProgressiveState *context;
401
402 g_assert (size_func != NULL);
403 g_assert (prepared_func != NULL);
404 g_assert (updated_func != NULL);
405
406 context = g_new0 (IcnsProgressiveState, 1);
407 context->size_func = size_func;
408 context->prepared_func = prepared_func;
409 context->updated_func = updated_func;
410 context->user_data = user_data;
411 context->byte_array = g_byte_array_new ();
412
413 return context;
414}
415
416static gboolean
417gdk_pixbuf__icns_image_stop_load (gpointer data,
418 GError **error)
419{
420 IcnsProgressiveState *context = data;
421
422 g_return_val_if_fail (context != NULL, TRUE);
423
424 context_free (context);
425 return TRUE;
426}
427
428static gboolean
429gdk_pixbuf__icns_image_load_increment (gpointer data,
430 const guchar *buf,
431 guint size,
432 GError **error)
433{
434 IcnsProgressiveState *context = data;
435 int i;
436 int filesize;
437 gint w, h;
438
439 context->byte_array = g_byte_array_append (array: context->byte_array, data: buf, len: size);
440
441 if (context->byte_array->len < 8)
442 return TRUE;
443
444 filesize = (context->byte_array->data[4] << 24) |
445 (context->byte_array->data[5] << 16) |
446 (context->byte_array->data[6] << 8) |
447 (context->byte_array->data[7]);
448
449 if (context->byte_array->len < filesize)
450 return TRUE;
451
452 for (i = 0; i < G_N_ELEMENTS(sizes) && !context->pixbuf; i++)
453 context->pixbuf = load_icon (size: sizes[i],
454 data: context->byte_array->data,
455 datalen: context->byte_array->len);
456
457 if (!context->pixbuf)
458 {
459 g_set_error_literal (err: error, GDK_PIXBUF_ERROR,
460 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
461 _("Could not decode ICNS file"));
462 return FALSE;
463 }
464
465 w = gdk_pixbuf_get_width (pixbuf: context->pixbuf);
466 h = gdk_pixbuf_get_height (pixbuf: context->pixbuf);
467
468 (*context->size_func) (&w,
469 &h,
470 context->user_data);
471
472 (*context->prepared_func) (context->pixbuf,
473 NULL,
474 context->user_data);
475
476 (*context->updated_func) (context->pixbuf,
477 0,
478 0,
479 gdk_pixbuf_get_width (pixbuf: context->pixbuf),
480 gdk_pixbuf_get_height (pixbuf: context->pixbuf),
481 context->user_data);
482
483 return TRUE;
484}
485
486#ifndef INCLUDE_icns
487#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
488#else
489#define MODULE_ENTRY(function) void _gdk_pixbuf__icns_ ## function
490#endif
491
492MODULE_ENTRY (fill_vtable) (GdkPixbufModule * module)
493{
494 module->load = icns_image_load;
495 module->begin_load = gdk_pixbuf__icns_image_begin_load;
496 module->stop_load = gdk_pixbuf__icns_image_stop_load;
497 module->load_increment = gdk_pixbuf__icns_image_load_increment;
498}
499
500MODULE_ENTRY (fill_info) (GdkPixbufFormat * info)
501{
502 static const GdkPixbufModulePattern signature[] = {
503 {"icns", NULL, 100}, /* file begins with 'icns' */
504 {NULL, NULL, 0}
505 };
506 static const gchar *mime_types[] = {
507 "image/x-icns",
508 NULL
509 };
510 static const gchar *extensions[] = {
511 "icns",
512 NULL
513 };
514
515 info->name = "icns";
516 info->signature = (GdkPixbufModulePattern *) signature;
517 info->description = NC_("image format", "MacOS X icon");
518 info->mime_types = (gchar **) mime_types;
519 info->extensions = (gchar **) extensions;
520 info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
521 info->license = "GPL";
522 info->disabled = FALSE;
523}
524
525

source code of gtk/subprojects/gdk-pixbuf/gdk-pixbuf/io-icns.c