403 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Mac OS X .icns icons loader
 | |
|  *
 | |
|  * Copyright (c) 2007 Lyonel Vincent <lyonel@ezix.org>
 | |
|  * Copyright (c) 2007 Bastien Nocera <hadess@hadess.net>
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library; if not, write to the
 | |
|  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 | |
|  * Boston, MA 02111-1307, USA.
 | |
|  */
 | |
| 
 | |
| #define _GNU_SOURCE
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <errno.h>
 | |
| 
 | |
| #include "gdk-pixbuf-private.h"
 | |
| #include "gdk-pixbuf-io.h"
 | |
| 
 | |
| G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
 | |
| G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);
 | |
| 
 | |
| #define IN /**/
 | |
| #define OUT /**/
 | |
| #define INOUT /**/
 | |
| 
 | |
| struct IcnsBlockHeader
 | |
| {
 | |
|   char id[4];
 | |
|   guint32 size;			/* caution: bigendian */
 | |
| };
 | |
| typedef struct IcnsBlockHeader IcnsBlockHeader;
 | |
| 
 | |
| /*
 | |
|  * load raw icon data from 'icns' resource
 | |
|  *
 | |
|  * returns TRUE when successful
 | |
|  */
 | |
| static gboolean
 | |
| load_resources (unsigned size, IN gpointer data, gsize datalen,
 | |
| 		OUT guchar ** picture, OUT gsize * plen,
 | |
| 		OUT guchar ** mask, OUT gsize * mlen)
 | |
| {
 | |
|   IcnsBlockHeader *header = NULL;
 | |
|   const char *bytes = NULL;
 | |
|   const char *current = NULL;
 | |
|   guint32 blocklen = 0;
 | |
|   guint32 icnslen = 0;
 | |
|   gboolean needs_mask = TRUE;
 | |
| 
 | |
|   if (datalen < 2 * sizeof (guint32))
 | |
|     return FALSE;
 | |
|   if (!data)
 | |
|     return FALSE;
 | |
| 
 | |
|   *picture = *mask = NULL;
 | |
|   *plen = *mlen = 0;
 | |
| 
 | |
|   bytes = data;
 | |
|   header = (IcnsBlockHeader *) data;
 | |
|   if (memcmp (header->id, "icns", 4) != 0)
 | |
|     return FALSE;
 | |
| 
 | |
|   icnslen = GUINT32_FROM_BE (header->size);
 | |
|   if ((icnslen > datalen) || (icnslen < 2 * sizeof (guint32)))
 | |
|     return FALSE;
 | |
| 
 | |
|   current = bytes + sizeof (IcnsBlockHeader);
 | |
|   while ((current - bytes < icnslen) && (icnslen - (current - bytes) >= sizeof (IcnsBlockHeader)))
 | |
|     {
 | |
|       header = (IcnsBlockHeader *) current;
 | |
|       blocklen = GUINT32_FROM_BE (header->size);
 | |
| 
 | |
|       /* Check that blocklen isn't garbage */
 | |
|       if (blocklen > icnslen - (current - bytes))
 | |
|         return FALSE;
 | |
| 
 | |
|       switch (size)
 | |
| 	{
 | |
| 	case 256:
 | |
| 	case 512:
 | |
|           if (memcmp (header->id, "ic08", 4) == 0	/* 256x256 icon */
 | |
|               || memcmp (header->id, "ic09", 4) == 0)	/* 512x512 icon */
 | |
|             {
 | |
| 	      *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *plen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	    needs_mask = FALSE;
 | |
| 	  break;
 | |
| 	case 128:
 | |
| 	  if (memcmp (header->id, "it32", 4) == 0)	/* 128x128 icon */
 | |
| 	    {
 | |
| 	      *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *plen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	      if (memcmp (*picture, "\0\0\0\0", 4) == 0)
 | |
| 		{
 | |
| 		  *picture += 4;
 | |
| 		  *plen -= 4;
 | |
| 		}
 | |
| 	    }
 | |
| 	  if (memcmp (header->id, "t8mk", 4) == 0)	/* 128x128 mask */
 | |
| 	    {
 | |
| 	      *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *mlen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	  break;
 | |
| 	case 48:
 | |
| 	  if (memcmp (header->id, "ih32", 4) == 0)	/* 48x48 icon */
 | |
| 	    {
 | |
| 	      *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *plen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	  if (memcmp (header->id, "h8mk", 4) == 0)	/* 48x48 mask */
 | |
| 	    {
 | |
| 	      *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *mlen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	  break;
 | |
| 	case 32:
 | |
| 	  if (memcmp (header->id, "il32", 4) == 0)	/* 32x32 icon */
 | |
| 	    {
 | |
| 	      *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *plen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	  if (memcmp (header->id, "l8mk", 4) == 0)	/* 32x32 mask */
 | |
| 	    {
 | |
| 	      *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *mlen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	  break;
 | |
| 	case 16:
 | |
| 	  if (memcmp (header->id, "is32", 4) == 0)	/* 16x16 icon */
 | |
| 	    {
 | |
| 	      *picture = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *plen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	  if (memcmp (header->id, "s8mk", 4) == 0)	/* 16x16 mask */
 | |
| 	    {
 | |
| 	      *mask = (gpointer) (current + sizeof (IcnsBlockHeader));
 | |
| 	      *mlen = blocklen - sizeof (IcnsBlockHeader);
 | |
| 	    }
 | |
| 	  break;
 | |
| 	default:
 | |
| 	  return FALSE;
 | |
| 	}
 | |
| 
 | |
|       current += blocklen;
 | |
|     }
 | |
| 
 | |
|   if (!*picture)
 | |
|     return FALSE;
 | |
|   if (needs_mask && !*mask)
 | |
|     return FALSE;
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * uncompress RLE-encoded bytes into RGBA scratch zone:
 | |
|  * if firstbyte >= 0x80, it indicates the number of identical bytes + 125
 | |
|  * 	(repeated value is stored next: 1 byte)
 | |
|  * otherwise, it indicates the number of non-repeating bytes - 1
 | |
|  *	(non-repeating values are stored next: n bytes)
 | |
|  */
 | |
| static gboolean
 | |
| uncompress (unsigned size, INOUT guchar ** source, OUT guchar * target, INOUT gsize * _remaining)
 | |
| {
 | |
|   guchar *data = *source;
 | |
|   gsize remaining;
 | |
|   gsize i = 0;
 | |
| 
 | |
|   /* The first time we're called, set remaining */
 | |
|   if (*_remaining == 0) {
 | |
|     remaining = size * size;
 | |
|   } else {
 | |
|     remaining = *_remaining;
 | |
|   }
 | |
| 
 | |
|   while (remaining > 0)
 | |
|     {
 | |
|       guint8 count = 0;
 | |
| 
 | |
|       if (data[0] & 0x80)	/* repeating byte */
 | |
| 	{
 | |
| 	  count = data[0] - 125;
 | |
| 
 | |
| 	  if (count > remaining)
 | |
| 	    return FALSE;
 | |
| 
 | |
| 	  for (i = 0; i < count; i++)
 | |
| 	    {
 | |
| 	      *target = data[1];
 | |
| 	      target += 4;
 | |
| 	    }
 | |
| 
 | |
| 	  data += 2;
 | |
| 	}
 | |
|       else			/* non-repeating bytes */
 | |
| 	{
 | |
| 	  count = data[0] + 1;
 | |
| 
 | |
| 	  if (count > remaining)
 | |
| 	    return FALSE;
 | |
| 
 | |
| 	  for (i = 0; i < count; i++)
 | |
| 	    {
 | |
| 	      *target = data[i + 1];
 | |
| 	      target += 4;
 | |
| 	    }
 | |
| 	  data += count + 1;
 | |
| 	}
 | |
| 
 | |
|       remaining -= count;
 | |
|     }
 | |
| 
 | |
|   *source = data;
 | |
|   *_remaining = remaining;
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static GdkPixbuf *
 | |
| load_icon (unsigned size, IN gpointer data, gsize datalen)
 | |
| {
 | |
|   guchar *icon = NULL;
 | |
|   guchar *mask = NULL;
 | |
|   gsize isize = 0, msize = 0, i;
 | |
|   guchar *image = NULL;
 | |
| 
 | |
|   if (!load_resources (size, data, datalen, &icon, &isize, &mask, &msize))
 | |
|     return NULL;
 | |
| 
 | |
|   /* 256x256 icons don't use RLE or uncompressed data,
 | |
|    * They're usually JPEG 2000 images */
 | |
|   if (size == 256)
 | |
|     {
 | |
|       GdkPixbufLoader *loader;
 | |
|       GdkPixbuf *pixbuf;
 | |
| 
 | |
|       loader = gdk_pixbuf_loader_new ();
 | |
|       if (!gdk_pixbuf_loader_write (loader, icon, isize, NULL)
 | |
| 	  || !gdk_pixbuf_loader_close (loader, NULL))
 | |
|         {
 | |
|           g_object_unref (loader);
 | |
|           return NULL;
 | |
| 	}
 | |
| 
 | |
|       pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
 | |
|       g_object_ref (pixbuf);
 | |
|       g_object_unref (loader);
 | |
| 
 | |
|       return pixbuf;
 | |
|     }
 | |
| 
 | |
|   g_assert (mask);
 | |
| 
 | |
|   if (msize != size * size)	/* wrong mask size */
 | |
|     return NULL;
 | |
| 
 | |
|   image = (guchar *) g_try_malloc0 (size * size * 4);	/* 4 bytes/pixel = RGBA */
 | |
| 
 | |
|   if (!image)
 | |
|     return NULL;
 | |
| 
 | |
|   if (isize == size * size * 4)	/* icon data is uncompressed */
 | |
|     for (i = 0; i < size * size; i++)	/* 4 bytes/pixel = ARGB (A: ignored) */
 | |
|       {
 | |
| 	image[i * 4] = icon[4 * i + 1];	/* R */
 | |
| 	image[i * 4 + 1] = icon[4 * i + 2];	/* G */
 | |
| 	image[i * 4 + 2] = icon[4 * i + 3];	/* B */
 | |
|       }
 | |
|   else
 | |
|     {
 | |
|       guchar *data = icon;
 | |
|       gsize remaining = 0;
 | |
| 
 | |
|       /* R */
 | |
|       if (!uncompress (size, &data, image, &remaining))
 | |
|         goto bail;
 | |
|       /* G */
 | |
|       if (!uncompress (size, &data, image + 1, &remaining))
 | |
|         goto bail;
 | |
|       /* B */
 | |
|       if (!uncompress (size, &data, image + 2, &remaining))
 | |
|         goto bail;
 | |
|     }
 | |
| 
 | |
|   for (i = 0; i < size * size; i++)	/* copy mask to alpha channel */
 | |
|     image[i * 4 + 3] = mask[i];
 | |
| 
 | |
|   return gdk_pixbuf_new_from_data ((guchar *) image, GDK_COLORSPACE_RGB,	/* RGB image */
 | |
| 				   TRUE,	/* with alpha channel */
 | |
| 				   8,	/* 8 bits per sample */
 | |
| 				   size,	/* width */
 | |
| 				   size,	/* height */
 | |
| 				   size * 4,	/* no gap between rows */
 | |
| 				   (GdkPixbufDestroyNotify)g_free,	/* free() function */
 | |
| 				   NULL);	/* param to free() function */
 | |
| 
 | |
| bail:
 | |
|   g_free (image);
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| static int sizes[] = {
 | |
|   256, /* late-Tiger icons */
 | |
|   128, /* Standard OS X */
 | |
|   48,  /* Not very common */
 | |
|   32,  /* Standard Mac OS Classic (8 & 9) */
 | |
|   24,  /* OS X toolbars */
 | |
|   16   /* used in Mac OS Classic and dialog boxes */
 | |
| };
 | |
| 
 | |
| static GdkPixbuf *
 | |
| icns_image_load (FILE *f, GError ** error)
 | |
| {
 | |
|   GByteArray *data;
 | |
|   GdkPixbuf *pixbuf = NULL;
 | |
|   guint i;
 | |
| 
 | |
|   data = g_byte_array_new ();
 | |
|   while (!feof (f))
 | |
|     {
 | |
|       gint save_errno;
 | |
|       guchar buf[4096];
 | |
|       gsize bytes;
 | |
| 
 | |
|       bytes = fread (buf, 1, sizeof (buf), f);
 | |
|       save_errno = errno;
 | |
|       data = g_byte_array_append (data, buf, bytes);
 | |
| 
 | |
|       if (ferror (f))
 | |
|         {
 | |
| 	  g_set_error (error,
 | |
| 		       G_FILE_ERROR,
 | |
| 		       g_file_error_from_errno (save_errno),
 | |
| 		       _("Error reading ICNS image: %s"),
 | |
| 		       g_strerror (save_errno));
 | |
| 
 | |
| 	  g_byte_array_free (data, TRUE);
 | |
| 
 | |
| 	  return NULL;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   for (i = 0; i < G_N_ELEMENTS(sizes) && !pixbuf; i++)
 | |
|     pixbuf = load_icon (sizes[i], data->data, data->len);
 | |
| 
 | |
|   g_byte_array_free (data, TRUE);
 | |
| 
 | |
|   if (!pixbuf)
 | |
|     g_set_error_literal (error, GDK_PIXBUF_ERROR,
 | |
|                          GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
 | |
|                          _("Could not decode ICNS file"));
 | |
| 
 | |
|   return pixbuf;
 | |
| }
 | |
| 
 | |
| #ifndef INCLUDE_icns
 | |
| #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
 | |
| #else
 | |
| #define MODULE_ENTRY(function) void _gdk_pixbuf__icns_ ## function
 | |
| #endif
 | |
| 
 | |
| MODULE_ENTRY (fill_vtable) (GdkPixbufModule * module)
 | |
| {
 | |
|   module->load = icns_image_load;
 | |
| }
 | |
| 
 | |
| MODULE_ENTRY (fill_info) (GdkPixbufFormat * info)
 | |
| {
 | |
|   static GdkPixbufModulePattern signature[] = {
 | |
|     {"icns", NULL, 100},	/* file begins with 'icns' */
 | |
|     {NULL, NULL, 0}
 | |
|   };
 | |
|   static gchar *mime_types[] = {
 | |
|     "image/x-icns",
 | |
|     NULL
 | |
|   };
 | |
|   static gchar *extensions[] = {
 | |
|     "icns",
 | |
|     NULL
 | |
|   };
 | |
| 
 | |
|   info->name = "icns";
 | |
|   info->signature = signature;
 | |
|   info->description = N_("The ICNS image format");
 | |
|   info->mime_types = mime_types;
 | |
|   info->extensions = extensions;
 | |
|   info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
 | |
|   info->license = "GPL";
 | |
|   info->disabled = FALSE;
 | |
| }
 | |
| 
 | 
