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