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
36 G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
37 G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);
38
39 #define IN /**/
40 #define OUT /**/
41 #define INOUT /**/
42
43 struct IcnsBlockHeader
44 {
45 char id[4];
46 guint32 size; /* caution: bigendian */
47 };
48 typedef struct IcnsBlockHeader IcnsBlockHeader;
49
50 typedef 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 */
66 static gboolean
load_resources(unsigned size,IN gpointer data,gsize datalen,OUT guchar ** picture,OUT gsize * plen,OUT guchar ** mask,OUT gsize * mlen)67 load_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 (header->id, "icns", 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 (header->id, "ic08", 4) == 0 /* 256x256 icon */
111 || memcmp (header->id, "ic09", 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 (header->id, "it32", 4) == 0) /* 128x128 icon */
120 {
121 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
122 *plen = blocklen - sizeof (IcnsBlockHeader);
123 if (memcmp (*picture, "\0\0\0\0", 4) == 0)
124 {
125 *picture += 4;
126 *plen -= 4;
127 }
128 }
129 if (memcmp (header->id, "t8mk", 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 (header->id, "ih32", 4) == 0) /* 48x48 icon */
137 {
138 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
139 *plen = blocklen - sizeof (IcnsBlockHeader);
140 }
141 if (memcmp (header->id, "h8mk", 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 (header->id, "il32", 4) == 0) /* 32x32 icon */
149 {
150 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
151 *plen = blocklen - sizeof (IcnsBlockHeader);
152 }
153 if (memcmp (header->id, "l8mk", 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 (header->id, "is32", 4) == 0) /* 16x16 icon */
161 {
162 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
163 *plen = blocklen - sizeof (IcnsBlockHeader);
164 }
165 if (memcmp (header->id, "s8mk", 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 */
192 static gboolean
uncompress(unsigned size,INOUT guchar ** source,OUT guchar * target,INOUT gsize * _remaining)193 uncompress (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
248 static GdkPixbuf *
load_icon(unsigned size,IN gpointer data,gsize datalen)249 load_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, &icon, &isize, &mask, &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, icon, isize, NULL)
268 || !gdk_pixbuf_loader_close (loader, NULL))
269 {
270 g_object_unref (loader);
271 return NULL;
272 }
273
274 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
275 g_object_ref (pixbuf);
276 g_object_unref (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 (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, &data, image, &remaining))
305 goto bail;
306 /* G */
307 if (!uncompress (size, &data, image + 1, &remaining))
308 goto bail;
309 /* B */
310 if (!uncompress (size, &data, image + 2, &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 ((guchar *) image, GDK_COLORSPACE_RGB, /* RGB image */
318 TRUE, /* with alpha channel */
319 8, /* 8 bits per sample */
320 size, /* width */
321 size, /* height */
322 size * 4, /* no gap between rows */
323 (GdkPixbufDestroyNotify)g_free, /* free() function */
324 NULL); /* param to free() function */
325
326 bail:
327 g_free (image);
328 return NULL;
329 }
330
331 static 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
340 static GdkPixbuf *
icns_image_load(FILE * f,GError ** error)341 icns_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 (f))
349 {
350 gint save_errno;
351 guchar buf[4096];
352 gsize bytes;
353
354 bytes = fread (buf, 1, sizeof (buf), f);
355 save_errno = errno;
356 data = g_byte_array_append (data, buf, bytes);
357
358 if (ferror (f))
359 {
360 g_set_error (error,
361 G_FILE_ERROR,
362 g_file_error_from_errno (save_errno),
363 _("Error reading ICNS image: %s"),
364 g_strerror (save_errno));
365
366 g_byte_array_free (data, TRUE);
367
368 return NULL;
369 }
370 }
371
372 for (i = 0; i < G_N_ELEMENTS(sizes) && !pixbuf; i++)
373 pixbuf = load_icon (sizes[i], data->data, data->len);
374
375 g_byte_array_free (data, TRUE);
376
377 if (!pixbuf)
378 g_set_error_literal (error, GDK_PIXBUF_ERROR,
379 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
380 _("Could not decode ICNS file"));
381
382 return pixbuf;
383 }
384
385 static void
context_free(IcnsProgressiveState * context)386 context_free (IcnsProgressiveState *context)
387 {
388 g_byte_array_free (context->byte_array, TRUE);
389 g_clear_object (&context->pixbuf);
390 g_free (context);
391 }
392
393 static gpointer
gdk_pixbuf__icns_image_begin_load(GdkPixbufModuleSizeFunc size_func,GdkPixbufModulePreparedFunc prepared_func,GdkPixbufModuleUpdatedFunc updated_func,gpointer user_data,GError ** error)394 gdk_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 context = g_new0 (IcnsProgressiveState, 1);
403 context->size_func = size_func;
404 context->prepared_func = prepared_func;
405 context->updated_func = updated_func;
406 context->user_data = user_data;
407 context->byte_array = g_byte_array_new ();
408
409 return context;
410 }
411
412 static gboolean
gdk_pixbuf__icns_image_stop_load(gpointer data,GError ** error)413 gdk_pixbuf__icns_image_stop_load (gpointer data,
414 GError **error)
415 {
416 IcnsProgressiveState *context = data;
417
418 g_return_val_if_fail (context != NULL, TRUE);
419
420 context_free (context);
421 return TRUE;
422 }
423
424 static gboolean
gdk_pixbuf__icns_image_load_increment(gpointer data,const guchar * buf,guint size,GError ** error)425 gdk_pixbuf__icns_image_load_increment (gpointer data,
426 const guchar *buf,
427 guint size,
428 GError **error)
429 {
430 IcnsProgressiveState *context = data;
431 int i;
432 int filesize;
433 gint w, h;
434
435 context->byte_array = g_byte_array_append (context->byte_array, buf, size);
436
437 if (context->byte_array->len < 8)
438 return TRUE;
439
440 filesize = (context->byte_array->data[4] << 24) |
441 (context->byte_array->data[5] << 16) |
442 (context->byte_array->data[6] << 8) |
443 (context->byte_array->data[7]);
444
445 if (context->byte_array->len < filesize)
446 return TRUE;
447
448 for (i = 0; i < G_N_ELEMENTS(sizes) && !context->pixbuf; i++)
449 context->pixbuf = load_icon (sizes[i],
450 context->byte_array->data,
451 context->byte_array->len);
452
453 if (!context->pixbuf)
454 {
455 g_set_error_literal (error, GDK_PIXBUF_ERROR,
456 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
457 _("Could not decode ICNS file"));
458 return FALSE;
459 }
460
461 w = gdk_pixbuf_get_width (context->pixbuf);
462 h = gdk_pixbuf_get_height (context->pixbuf);
463
464 if (context->size_func != NULL)
465 (*context->size_func) (&w,
466 &h,
467 context->user_data);
468
469 if (context->prepared_func != NULL)
470 (*context->prepared_func) (context->pixbuf,
471 NULL,
472 context->user_data);
473
474 if (context->updated_func != NULL)
475 (*context->updated_func) (context->pixbuf,
476 0,
477 0,
478 gdk_pixbuf_get_width (context->pixbuf),
479 gdk_pixbuf_get_height (context->pixbuf),
480 context->user_data);
481
482 return TRUE;
483 }
484
485 #ifndef INCLUDE_icns
486 #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
487 #else
488 #define MODULE_ENTRY(function) void _gdk_pixbuf__icns_ ## function
489 #endif
490
MODULE_ENTRY(fill_vtable)491 MODULE_ENTRY (fill_vtable) (GdkPixbufModule * module)
492 {
493 module->load = icns_image_load;
494 module->begin_load = gdk_pixbuf__icns_image_begin_load;
495 module->stop_load = gdk_pixbuf__icns_image_stop_load;
496 module->load_increment = gdk_pixbuf__icns_image_load_increment;
497 }
498
MODULE_ENTRY(fill_info)499 MODULE_ENTRY (fill_info) (GdkPixbufFormat * info)
500 {
501 static const GdkPixbufModulePattern signature[] = {
502 {"icns", NULL, 100}, /* file begins with 'icns' */
503 {NULL, NULL, 0}
504 };
505 static const gchar *mime_types[] = {
506 "image/x-icns",
507 NULL
508 };
509 static const gchar *extensions[] = {
510 "icns",
511 NULL
512 };
513
514 info->name = "icns";
515 info->signature = (GdkPixbufModulePattern *) signature;
516 info->description = NC_("image format", "MacOS X icon");
517 info->mime_types = (gchar **) mime_types;
518 info->extensions = (gchar **) extensions;
519 info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
520 info->license = "GPL";
521 info->disabled = FALSE;
522 }
523
524