1 /* Mac OS X .icns icons loader
3 * Copyright (c) 2007 Lyonel Vincent <lyonel@ezix.org>
4 * Copyright (c) 2007 Bastien Nocera <hadess@hadess.net>
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.
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.
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.
29 #include "gdk-pixbuf-private.h"
30 #include "gdk-pixbuf-io.h"
32 G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
33 G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);
39 struct IcnsBlockHeader
42 guint32 size; /* caution: bigendian */
44 typedef struct IcnsBlockHeader IcnsBlockHeader;
47 * load raw icon data from 'icns' resource
49 * returns TRUE when successful
52 load_resources (unsigned size, IN gpointer data, gsize datalen,
53 OUT guchar ** picture, OUT gsize * plen,
54 OUT guchar ** mask, OUT gsize * mlen)
56 IcnsBlockHeader *header = NULL;
57 const char *bytes = NULL;
58 const char *current = NULL;
61 gboolean needs_mask = TRUE;
63 if (datalen < 2 * sizeof (guint32))
68 *picture = *mask = NULL;
72 header = (IcnsBlockHeader *) data;
73 if (memcmp (header->id, "icns", 4) != 0)
76 icnslen = GUINT32_FROM_BE (header->size);
77 if ((icnslen > datalen) || (icnslen < 2 * sizeof (guint32)))
80 current = bytes + sizeof (IcnsBlockHeader);
81 while ((current - bytes < icnslen) && (icnslen - (current - bytes) >= sizeof (IcnsBlockHeader)))
83 header = (IcnsBlockHeader *) current;
84 blocklen = GUINT32_FROM_BE (header->size);
86 /* Check that blocklen isn't garbage */
87 if (blocklen > icnslen - (current - bytes))
94 if (memcmp (header->id, "ic08", 4) == 0 /* 256x256 icon */
95 || memcmp (header->id, "ic09", 4) == 0) /* 512x512 icon */
97 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
98 *plen = blocklen - sizeof (IcnsBlockHeader);
103 if (memcmp (header->id, "it32", 4) == 0) /* 128x128 icon */
105 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
106 *plen = blocklen - sizeof (IcnsBlockHeader);
107 if (memcmp (*picture, "\0\0\0\0", 4) == 0)
113 if (memcmp (header->id, "t8mk", 4) == 0) /* 128x128 mask */
115 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
116 *mlen = blocklen - sizeof (IcnsBlockHeader);
120 if (memcmp (header->id, "ih32", 4) == 0) /* 48x48 icon */
122 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
123 *plen = blocklen - sizeof (IcnsBlockHeader);
125 if (memcmp (header->id, "h8mk", 4) == 0) /* 48x48 mask */
127 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
128 *mlen = blocklen - sizeof (IcnsBlockHeader);
132 if (memcmp (header->id, "il32", 4) == 0) /* 32x32 icon */
134 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
135 *plen = blocklen - sizeof (IcnsBlockHeader);
137 if (memcmp (header->id, "l8mk", 4) == 0) /* 32x32 mask */
139 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
140 *mlen = blocklen - sizeof (IcnsBlockHeader);
144 if (memcmp (header->id, "is32", 4) == 0) /* 16x16 icon */
146 *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
147 *plen = blocklen - sizeof (IcnsBlockHeader);
149 if (memcmp (header->id, "s8mk", 4) == 0) /* 16x16 mask */
151 *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
152 *mlen = blocklen - sizeof (IcnsBlockHeader);
164 if (needs_mask && !*mask)
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)
177 uncompress (unsigned size, INOUT guchar ** source, OUT guchar * target, INOUT gsize * _remaining)
179 guchar *data = *source;
183 /* The first time we're called, set remaining */
184 if (*_remaining == 0) {
185 remaining = size * size;
187 remaining = *_remaining;
190 while (remaining > 0)
194 if (data[0] & 0x80) /* repeating byte */
196 count = data[0] - 125;
198 if (count > remaining)
201 for (i = 0; i < count; i++)
209 else /* non-repeating bytes */
213 if (count > remaining)
216 for (i = 0; i < count; i++)
218 *target = data[i + 1];
228 *_remaining = remaining;
233 load_icon (unsigned size, IN gpointer data, gsize datalen)
237 gsize isize = 0, msize = 0, i;
238 guchar *image = NULL;
240 if (!load_resources (size, data, datalen, &icon, &isize, &mask, &msize))
243 /* 256x256 icons don't use RLE or uncompressed data,
244 * They're usually JPEG 2000 images */
247 GdkPixbufLoader *loader;
250 loader = gdk_pixbuf_loader_new ();
251 if (!gdk_pixbuf_loader_write (loader, icon, isize, NULL)
252 || !gdk_pixbuf_loader_close (loader, NULL))
254 g_object_unref (loader);
258 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
259 g_object_ref (pixbuf);
260 g_object_unref (loader);
267 if (msize != size * size) /* wrong mask size */
270 image = (guchar *) g_try_malloc0 (size * size * 4); /* 4 bytes/pixel = RGBA */
275 if (isize == size * size * 4) /* icon data is uncompressed */
276 for (i = 0; i < size * size; i++) /* 4 bytes/pixel = ARGB (A: ignored) */
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 */
288 if (!uncompress (size, &data, image, &remaining))
291 if (!uncompress (size, &data, image + 1, &remaining))
294 if (!uncompress (size, &data, image + 2, &remaining))
298 for (i = 0; i < size * size; i++) /* copy mask to alpha channel */
299 image[i * 4 + 3] = mask[i];
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 */
306 size * 4, /* no gap between rows */
307 (GdkPixbufDestroyNotify)g_free, /* free() function */
308 NULL); /* param to free() function */
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 */
325 icns_image_load (FILE *f, GError ** error)
328 GdkPixbuf *pixbuf = NULL;
331 data = g_byte_array_new ();
338 bytes = fread (buf, 1, sizeof (buf), f);
340 data = g_byte_array_append (data, buf, bytes);
346 g_file_error_from_errno (save_errno),
347 _("Error reading ICNS image: %s"),
348 g_strerror (save_errno));
350 g_byte_array_free (data, TRUE);
356 for (i = 0; i < G_N_ELEMENTS(sizes) && !pixbuf; i++)
357 pixbuf = load_icon (sizes[i], data->data, data->len);
359 g_byte_array_free (data, TRUE);
362 g_set_error_literal (error, GDK_PIXBUF_ERROR,
363 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
364 _("Could not decode ICNS file"));
370 #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
372 #define MODULE_ENTRY(function) void _gdk_pixbuf__icns_ ## function
375 MODULE_ENTRY (fill_vtable) (GdkPixbufModule * module)
377 module->load = icns_image_load;
380 MODULE_ENTRY (fill_info) (GdkPixbufFormat * info)
382 static GdkPixbufModulePattern signature[] = {
383 {"icns", NULL, 100}, /* file begins with 'icns' */
386 static gchar *mime_types[] = {
390 static gchar *extensions[] = {
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;