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