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 |
44 | { |
45 | char [4]; |
46 | guint32 ; /* caution: bigendian */ |
47 | }; |
48 | typedef struct 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 |
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 * = 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 | */ |
192 | static gboolean |
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 * |
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, 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 | |
326 | bail: |
327 | g_free (mem: 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 * |
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 (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 | |
385 | static void |
386 | context_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 | |
393 | static gpointer |
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 | 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 | |
416 | static gboolean |
417 | gdk_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 | |
428 | static gboolean |
429 | gdk_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 | |
492 | MODULE_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 | |
500 | MODULE_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 | |