]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-icns.c
Bug 545875 – evo crashed when trying to print pages 6-7 of a 1 page
[~andy/gtk] / gdk-pixbuf / io-icns.c
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, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #define _GNU_SOURCE
23 #include <stdlib.h>
24 #include <string.h>
25 #include <errno.h>
26
27 #include "gdk-pixbuf-private.h"
28 #include "gdk-pixbuf-io.h"
29
30 G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
31 G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);
32
33 #define IN /**/
34 #define OUT /**/
35 #define INOUT /**/
36
37 struct IcnsBlockHeader
38 {
39   char id[4];
40   guint32 size;                 /* caution: bigendian */
41 };
42 typedef struct IcnsBlockHeader IcnsBlockHeader;
43
44 /*
45  * load raw icon data from 'icns' resource
46  *
47  * returns TRUE when successful
48  */
49 static gboolean
50 load_resources (unsigned size, IN gpointer data, gsize datalen,
51                 OUT guchar ** picture, OUT gsize * plen,
52                 OUT guchar ** mask, OUT gsize * mlen)
53 {
54   IcnsBlockHeader *header = NULL;
55   const char *bytes = NULL;
56   const char *current = NULL;
57   guint32 blocklen = 0;
58   guint32 icnslen = 0;
59   gboolean needs_mask = TRUE;
60
61   if (datalen < 2 * sizeof (guint32))
62     return FALSE;
63   if (!data)
64     return FALSE;
65
66   *picture = *mask = NULL;
67   *plen = *mlen = 0;
68
69   bytes = data;
70   header = (IcnsBlockHeader *) data;
71   if (memcmp (header->id, "icns", 4) != 0)
72     return FALSE;
73
74   icnslen = GUINT32_FROM_BE (header->size);
75   if ((icnslen > datalen) || (icnslen < 2 * sizeof (guint32)))
76     return FALSE;
77
78   current = bytes + sizeof (IcnsBlockHeader);
79   while ((current - bytes < icnslen) && (icnslen - (current - bytes) >= sizeof (IcnsBlockHeader)))
80     {
81       header = (IcnsBlockHeader *) current;
82       blocklen = GUINT32_FROM_BE (header->size);
83
84       /* Check that blocklen isn't garbage */
85       if (blocklen > icnslen - (current - bytes))
86         return FALSE;
87
88       switch (size)
89         {
90         case 256:
91         case 512:
92           if (memcmp (header->id, "ic08", 4) == 0       /* 256x256 icon */
93               || memcmp (header->id, "ic09", 4) == 0)   /* 512x512 icon */
94             {
95               *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
96               *plen = blocklen - sizeof (IcnsBlockHeader);
97             }
98             needs_mask = FALSE;
99           break;
100         case 128:
101           if (memcmp (header->id, "it32", 4) == 0)      /* 128x128 icon */
102             {
103               *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
104               *plen = blocklen - sizeof (IcnsBlockHeader);
105               if (memcmp (*picture, "\0\0\0\0", 4) == 0)
106                 {
107                   *picture += 4;
108                   *plen -= 4;
109                 }
110             }
111           if (memcmp (header->id, "t8mk", 4) == 0)      /* 128x128 mask */
112             {
113               *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
114               *mlen = blocklen - sizeof (IcnsBlockHeader);
115             }
116           break;
117         case 48:
118           if (memcmp (header->id, "ih32", 4) == 0)      /* 48x48 icon */
119             {
120               *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
121               *plen = blocklen - sizeof (IcnsBlockHeader);
122             }
123           if (memcmp (header->id, "h8mk", 4) == 0)      /* 48x48 mask */
124             {
125               *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
126               *mlen = blocklen - sizeof (IcnsBlockHeader);
127             }
128           break;
129         case 32:
130           if (memcmp (header->id, "il32", 4) == 0)      /* 32x32 icon */
131             {
132               *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
133               *plen = blocklen - sizeof (IcnsBlockHeader);
134             }
135           if (memcmp (header->id, "l8mk", 4) == 0)      /* 32x32 mask */
136             {
137               *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
138               *mlen = blocklen - sizeof (IcnsBlockHeader);
139             }
140           break;
141         case 16:
142           if (memcmp (header->id, "is32", 4) == 0)      /* 16x16 icon */
143             {
144               *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
145               *plen = blocklen - sizeof (IcnsBlockHeader);
146             }
147           if (memcmp (header->id, "s8mk", 4) == 0)      /* 16x16 mask */
148             {
149               *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
150               *mlen = blocklen - sizeof (IcnsBlockHeader);
151             }
152           break;
153         default:
154           return FALSE;
155         }
156
157       current += blocklen;
158     }
159
160   if (!*picture)
161     return FALSE;
162   if (needs_mask && !*mask)
163     return FALSE;
164   return TRUE;
165 }
166
167 /*
168  * uncompress RLE-encoded bytes into RGBA scratch zone:
169  * if firstbyte >= 0x80, it indicates the number of identical bytes + 125
170  *      (repeated value is stored next: 1 byte)
171  * otherwise, it indicates the number of non-repeating bytes - 1
172  *      (non-repeating values are stored next: n bytes)
173  */
174 static gboolean
175 uncompress (unsigned size, INOUT guchar ** source, OUT guchar * target, INOUT gsize * _remaining)
176 {
177   guchar *data = *source;
178   gsize remaining;
179   gsize i = 0;
180
181   /* The first time we're called, set remaining */
182   if (*_remaining == 0) {
183     remaining = size * size;
184   } else {
185     remaining = *_remaining;
186   }
187
188   while (remaining > 0)
189     {
190       guint8 count = 0;
191
192       if (data[0] & 0x80)       /* repeating byte */
193         {
194           count = data[0] - 125;
195
196           if (count > remaining)
197             return FALSE;
198
199           for (i = 0; i < count; i++)
200             {
201               *target = data[1];
202               target += 4;
203             }
204
205           data += 2;
206         }
207       else                      /* non-repeating bytes */
208         {
209           count = data[0] + 1;
210
211           if (count > remaining)
212             return FALSE;
213
214           for (i = 0; i < count; i++)
215             {
216               *target = data[i + 1];
217               target += 4;
218             }
219           data += count + 1;
220         }
221
222       remaining -= count;
223     }
224
225   *source = data;
226   *_remaining = remaining;
227   return TRUE;
228 }
229
230 static GdkPixbuf *
231 load_icon (unsigned size, IN gpointer data, gsize datalen)
232 {
233   guchar *icon = NULL;
234   guchar *mask = NULL;
235   gsize isize = 0, msize = 0, i;
236   guchar *image = NULL;
237
238   if (!load_resources (size, data, datalen, &icon, &isize, &mask, &msize))
239     return NULL;
240
241   /* 256x256 icons don't use RLE or uncompressed data,
242    * They're usually JPEG 2000 images */
243   if (size == 256)
244     {
245       GdkPixbufLoader *loader;
246       GdkPixbuf *pixbuf;
247
248       loader = gdk_pixbuf_loader_new ();
249       if (!gdk_pixbuf_loader_write (loader, icon, isize, NULL)
250           || !gdk_pixbuf_loader_close (loader, NULL))
251         {
252           g_object_unref (loader);
253           return NULL;
254         }
255
256       pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
257       g_object_ref (pixbuf);
258       g_object_unref (loader);
259
260       return pixbuf;
261     }
262
263   g_assert (mask);
264
265   if (msize != size * size)     /* wrong mask size */
266     return NULL;
267
268   image = (guchar *) g_try_malloc0 (size * size * 4);   /* 4 bytes/pixel = RGBA */
269
270   if (!image)
271     return NULL;
272
273   if (isize == size * size * 4) /* icon data is uncompressed */
274     for (i = 0; i < size * size; i++)   /* 4 bytes/pixel = ARGB (A: ignored) */
275       {
276         image[i * 4] = icon[4 * i + 1]; /* R */
277         image[i * 4 + 1] = icon[4 * i + 2];     /* G */
278         image[i * 4 + 2] = icon[4 * i + 3];     /* B */
279       }
280   else
281     {
282       guchar *data = icon;
283       gsize remaining = 0;
284
285       /* R */
286       if (!uncompress (size, &data, image, &remaining))
287         goto bail;
288       /* G */
289       if (!uncompress (size, &data, image + 1, &remaining))
290         goto bail;
291       /* B */
292       if (!uncompress (size, &data, image + 2, &remaining))
293         goto bail;
294     }
295
296   for (i = 0; i < size * size; i++)     /* copy mask to alpha channel */
297     image[i * 4 + 3] = mask[i];
298
299   return gdk_pixbuf_new_from_data ((guchar *) image, GDK_COLORSPACE_RGB,        /* RGB image */
300                                    TRUE,        /* with alpha channel */
301                                    8,   /* 8 bits per sample */
302                                    size,        /* width */
303                                    size,        /* height */
304                                    size * 4,    /* no gap between rows */
305                                    (GdkPixbufDestroyNotify)g_free,      /* free() function */
306                                    NULL);       /* param to free() function */
307
308 bail:
309   g_free (image);
310   return NULL;
311 }
312
313 static int sizes[] = {
314   256, /* late-Tiger icons */
315   128, /* Standard OS X */
316   48,  /* Not very common */
317   32,  /* Standard Mac OS Classic (8 & 9) */
318   24,  /* OS X toolbars */
319   16   /* used in Mac OS Classic and dialog boxes */
320 };
321
322 static GdkPixbuf *
323 icns_image_load (FILE *f, GError ** error)
324 {
325   GByteArray *data;
326   GdkPixbuf *pixbuf = NULL;
327   guint i;
328
329   data = g_byte_array_new ();
330   while (!feof (f))
331     {
332       gint save_errno;
333       guchar buf[4096];
334       gsize bytes;
335
336       bytes = fread (buf, 1, sizeof (buf), f);
337       save_errno = errno;
338       data = g_byte_array_append (data, buf, bytes);
339
340       if (ferror (f))
341         {
342           g_set_error (error,
343                        G_FILE_ERROR,
344                        g_file_error_from_errno (save_errno),
345                        _("Error reading ICNS image: %s"),
346                        g_strerror (save_errno));
347
348           g_byte_array_free (data, TRUE);
349
350           return NULL;
351         }
352     }
353
354   for (i = 0; i < G_N_ELEMENTS(sizes) && !pixbuf; i++)
355     pixbuf = load_icon (sizes[i], data->data, data->len);
356
357   g_byte_array_free (data, TRUE);
358
359   if (!pixbuf)
360     g_set_error_literal (error, GDK_PIXBUF_ERROR,
361                          GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
362                          _("Could not decode ICNS file"));
363
364   return pixbuf;
365 }
366
367 #ifndef INCLUDE_icns
368 #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
369 #else
370 #define MODULE_ENTRY(function) void _gdk_pixbuf__icns_ ## function
371 #endif
372
373 MODULE_ENTRY (fill_vtable) (GdkPixbufModule * module)
374 {
375   module->load = icns_image_load;
376 }
377
378 MODULE_ENTRY (fill_info) (GdkPixbufFormat * info)
379 {
380   static GdkPixbufModulePattern signature[] = {
381     {"icns", NULL, 100},        /* file begins with 'icns' */
382     {NULL, NULL, 0}
383   };
384   static gchar *mime_types[] = {
385     "image/x-icns",
386     NULL
387   };
388   static gchar *extensions[] = {
389     "icns",
390     NULL
391   };
392
393   info->name = "icns";
394   info->signature = signature;
395   info->description = N_("The ICNS image format");
396   info->mime_types = mime_types;
397   info->extensions = extensions;
398   info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
399   info->license = "GPL";
400   info->disabled = FALSE;
401 }
402