]> Pileus Git - ~andy/gtk/blobdiff - gdk-pixbuf/io-ico.c
Updated Bulgarian translation
[~andy/gtk] / gdk-pixbuf / io-ico.c
index b307631e06bba8dddcaaeff333d427a0cfb50e65..4a6bad8703e1eb17e1b63fdcdf435fbc78db3244 100644 (file)
@@ -1,3 +1,4 @@
+/* -*- mode: C; c-file-style: "linux" -*- */
 /* GdkPixbuf library - Windows Icon/Cursor image loader
  *
  * Copyright (C) 1999 The Free Software Foundation
@@ -33,12 +34,16 @@ Known bugs:
 
 */
 
-#include <config.h>
+#include "config.h"
 #include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
 #include <unistd.h>
+#endif
 #include <string.h>
 #include "gdk-pixbuf-private.h"
 #include "gdk-pixbuf-io.h"
+#include <errno.h>
 
 \f
 
@@ -113,16 +118,17 @@ static void DumpBIH(unsigned char *BIH)
 
 /* Progressive loading */
 struct headerpair {
-       guint width;
-       guint height;
+       gint width;
+       gint height;
        guint depth;
        guint Negative;         /* Negative = 1 -> top down BMP,  
                                   Negative = 0 -> bottom up BMP */
 };
 
 struct ico_progressive_state {
-       ModulePreparedNotifyFunc prepared_func;
-       ModuleUpdatedNotifyFunc updated_func;
+       GdkPixbufModuleSizeFunc size_func;
+       GdkPixbufModulePreparedFunc prepared_func;
+       GdkPixbufModuleUpdatedFunc updated_func;
        gpointer user_data;
 
        gint HeaderSize;        /* The size of the header-part (incl colormap) */
@@ -136,11 +142,16 @@ struct ico_progressive_state {
        gint Lines;             /* # of finished lines */
 
        gint Type;              /*  
+                                  32 = RGBA
                                   24 = RGB
+                                  16 = 555 RGB
                                   8 = 8 bit colormapped
+                                  4 = 4 bpp colormapped
                                   1  = 1 bit bitonal 
                                 */
-
+        gboolean cursor;
+        gint x_hot;
+        gint y_hot;
 
        struct headerpair Header;       /* Decoded (BE->CPU) header */
        
@@ -151,71 +162,85 @@ struct ico_progressive_state {
        GdkPixbuf *pixbuf;      /* Our "target" */
 };
 
-gpointer
-gdk_pixbuf__ico_image_begin_load(ModulePreparedNotifyFunc prepared_func,
-                                ModuleUpdatedNotifyFunc updated_func,
-                                ModuleFrameDoneNotifyFunc frame_done_func,
-                                ModuleAnimationDoneNotifyFunc anim_done_func,
-                                gpointer user_data);
-void gdk_pixbuf__ico_image_stop_load(gpointer data);
-gboolean gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size);
-
-
-
-/* Shared library entry point --> Can go when generic_image_load
-   enters gdk-pixbuf-io */
-GdkPixbuf *
-gdk_pixbuf__ico_image_load(FILE * f)
+static gpointer
+gdk_pixbuf__ico_image_begin_load(GdkPixbufModuleSizeFunc size_func,
+                                 GdkPixbufModulePreparedFunc prepared_func,
+                                GdkPixbufModuleUpdatedFunc updated_func,
+                                gpointer user_data,
+                                 GError **error);
+static gboolean gdk_pixbuf__ico_image_stop_load(gpointer data, GError **error);
+static gboolean gdk_pixbuf__ico_image_load_increment(gpointer data,
+                                                     const guchar * buf, guint size,
+                                                     GError **error);
+
+static void 
+context_free (struct ico_progressive_state *context)
 {
-       guchar *membuf;
-       size_t length;
-       struct ico_progressive_state *State;
-
-       GdkPixbuf *pb;
-
-       State = gdk_pixbuf__ico_image_begin_load(NULL, NULL, NULL, NULL, NULL);
-       membuf = g_malloc(4096);
-
-       g_assert(membuf != NULL);
-
-       while (feof(f) == 0) {
-               length = fread(membuf, 1, 4096, f);
-               if (length > 0)
-                       gdk_pixbuf__ico_image_load_increment(State, membuf, length);
-
-       }
-       g_free(membuf);
-       if (State->pixbuf != NULL)
-               gdk_pixbuf_ref(State->pixbuf);
+       g_free (context->LineBuf);
+       context->LineBuf = NULL;
+       g_free (context->HeaderBuf);
 
-       pb = State->pixbuf;
+       if (context->pixbuf)
+               g_object_unref (context->pixbuf);
 
-       gdk_pixbuf__ico_image_stop_load(State);
-       return pb;
+       g_free (context);
 }
 
 static void DecodeHeader(guchar *Data, gint Bytes,
-                        struct ico_progressive_state *State)
+                        struct ico_progressive_state *State,
+                        GError **error)
 {
 /* For ICO's we have to be very clever. There are multiple images possible
-   in an .ICO. For now, we select (in order of priority):
-     1) The one with the highest number of colors
-     2) The largest one
+   in an .ICO. As a simple heuristic, we select the image which occupies the 
+   largest number of bytes.
  */   
  
        gint IconCount = 0; /* The number of icon-versions in the file */
        guchar *BIH; /* The DIB for the used icon */
        guchar *Ptr;
        gint I;
+       guint16 imgtype; /* 1 = icon, 2 = cursor */
  
        /* Step 1: The ICO header */
+
+       /* First word should be 0 according to specs */
+       if (((Data[1] << 8) + Data[0]) != 0) {
+               g_set_error_literal (error,
+                                    GDK_PIXBUF_ERROR,
+                                    GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                    _("Invalid header in icon"));
+               return;
+
+       }
+
+       imgtype = (Data[3] << 8) + Data[2];
+
+       State->cursor = (imgtype == 2) ? TRUE : FALSE;
+
+       /* If it is not a cursor make sure it is actually an icon */
+       if (!State->cursor && imgtype != 1) {
+               g_set_error_literal (error,
+                                    GDK_PIXBUF_ERROR,
+                                    GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                    _("Invalid header in icon"));
+               return;
+       }
+
+
        IconCount = (Data[5] << 8) + (Data[4]);
+       
        State->HeaderSize = 6 + IconCount*16;
-       
+
        if (State->HeaderSize>State->BytesInHeaderBuf) {
-               State->HeaderBuf=g_realloc(State->HeaderBuf,State->HeaderSize);
+               guchar *tmp=g_try_realloc(State->HeaderBuf,State->HeaderSize);
+               if (!tmp) {
+                       g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                             _("Not enough memory to load icon"));
+                       return;
+               }
+               State->HeaderBuf = tmp;
                State->BytesInHeaderBuf = State->HeaderSize;
        }
        if (Bytes < State->HeaderSize)
@@ -228,19 +253,14 @@ static void DecodeHeader(guchar *Data, gint Bytes,
        State->DIBoffset  = 0;
        Ptr = Data + 6;
        for (I=0;I<IconCount;I++) {
-               int ThisWidth, ThisHeight,ThisColors;
                int ThisScore;
                
-               ThisWidth = Ptr[0];
-               ThisHeight = Ptr[1];
-               ThisColors = (Ptr[2]);
-               if (ThisColors==0) 
-                       ThisColors=256; /* Yes, this is in the spec, ugh */
-               
-               ThisScore = ThisColors*1024+ThisWidth*ThisHeight; 
+               ThisScore = (Ptr[11] << 24) + (Ptr[10] << 16) + (Ptr[9] << 8) + (Ptr[8]);
 
-               if (ThisScore>State->ImageScore) {
+               if (ThisScore>=State->ImageScore) {
                        State->ImageScore = ThisScore;
+                       State->x_hot = (Ptr[5] << 8) + Ptr[4];
+                       State->y_hot = (Ptr[7] << 8) + Ptr[6];
                        State->DIBoffset = (Ptr[15]<<24)+(Ptr[14]<<16)+
                                           (Ptr[13]<<8) + (Ptr[12]);
                                                                 
@@ -249,14 +269,38 @@ static void DecodeHeader(guchar *Data, gint Bytes,
                
                Ptr += 16;      
        } 
-               
+
+       if (State->DIBoffset < 0) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Invalid header in icon"));
+               return;
+       }
+
        /* We now have a winner, pointed to in State->DIBoffset,
           so we know how many bytes are in the "header" part. */
              
        State->HeaderSize = State->DIBoffset + 40; /* 40 = sizeof(InfoHeader) */
-       
+
+       if (State->HeaderSize < 0) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Invalid header in icon"));
+               return;
+       }
+
        if (State->HeaderSize>State->BytesInHeaderBuf) {
-               State->HeaderBuf=g_realloc(State->HeaderBuf,State->HeaderSize);
+               guchar *tmp=g_try_realloc(State->HeaderBuf,State->HeaderSize);
+               if (!tmp) {
+                       g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                             _("Not enough memory to load icon"));
+                       return;
+               }
+               State->HeaderBuf = tmp;
                State->BytesInHeaderBuf = State->HeaderSize;
        }
        if (Bytes<State->HeaderSize) 
@@ -267,22 +311,33 @@ static void DecodeHeader(guchar *Data, gint Bytes,
 #ifdef DUMPBIH
        DumpBIH(BIH);
 #endif 
-       
        /* Add the palette to the headersize */
                
        State->Header.width =
            (int)(BIH[7] << 24) + (BIH[6] << 16) + (BIH[5] << 8) + (BIH[4]);
+       if (State->Header.width == 0) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Icon has zero width"));
+               return;
+       }
        State->Header.height =
-           (int)(BIH[11] << 24) + (BIH[10] << 16) + (BIH[9] << 8) + (BIH[8])/2;
+           (int)((BIH[11] << 24) + (BIH[10] << 16) + (BIH[9] << 8) + (BIH[8]))/2;
            /* /2 because the BIH height includes the transparency mask */
-       State->Header.depth = (BIH[15] << 8) + (BIH[14]);;
+       if (State->Header.height == 0) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Icon has zero height"));
+               return;
+       }
+       State->Header.depth = (BIH[15] << 8) + (BIH[14]);
 
        State->Type = State->Header.depth;      
        if (State->Lines>=State->Header.height)
                State->Type = 1; /* The transparency mask is 1 bpp */
        
-       
-       
        /* Determine the  palette size. If the header indicates 0, it
           is actually the maximum for the bpp. You have to love the
           guys who made the spec. */
@@ -297,8 +352,24 @@ static void DecodeHeader(guchar *Data, gint Bytes,
        
        State->HeaderSize+=I;
        
+       if (State->HeaderSize < 0) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Invalid header in icon"));
+               return;
+       }
+
        if (State->HeaderSize>State->BytesInHeaderBuf) {
-               State->HeaderBuf=g_realloc(State->HeaderBuf,State->HeaderSize);
+               guchar *tmp=g_try_realloc(State->HeaderBuf,State->HeaderSize);
+               if (!tmp) {
+                       g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                             _("Not enough memory to load icon"));
+                       return;
+               }
+               State->HeaderBuf = tmp;
                State->BytesInHeaderBuf = State->HeaderSize;
        }
        if (Bytes < State->HeaderSize)
@@ -306,7 +377,12 @@ static void DecodeHeader(guchar *Data, gint Bytes,
 
        if ((BIH[16] != 0) || (BIH[17] != 0) || (BIH[18] != 0)
            || (BIH[19] != 0)) {
-               g_assert(0); /* Compressed icons aren't allowed */
+               /* FIXME: is this the correct message? */
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Compressed icons are not supported"));
+               return;
        }
 
        /* Negative heights mean top-down pixel-order */
@@ -317,18 +393,29 @@ static void DecodeHeader(guchar *Data, gint Bytes,
        if (State->Header.width < 0) {
                State->Header.width = -State->Header.width;
        }
+       g_assert (State->Header.width > 0);
+       g_assert (State->Header.height > 0);
 
-       if (State->Type == 24)
+        if (State->Type == 32)
+                State->LineWidth = State->Header.width * 4;
+        else if (State->Type == 24)
                State->LineWidth = State->Header.width * 3;
-       if (State->Type == 8)
+        else if (State->Type == 16)
+                State->LineWidth = State->Header.width * 2;
+        else if (State->Type == 8)
                State->LineWidth = State->Header.width * 1;
-       if (State->Type == 4) {
+        else if (State->Type == 4)
                State->LineWidth = (State->Header.width+1)/2;
-       }
-       if (State->Type == 1) {
+        else if (State->Type == 1) {
                State->LineWidth = State->Header.width / 8;
                if ((State->Header.width & 7) != 0)
                        State->LineWidth++;
+        } else {
+          g_set_error_literal (error,
+                               GDK_PIXBUF_ERROR,
+                               GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                               _("Unsupported icon type"));
+          return;
        }
 
        /* Pad to a 32 bit boundary */
@@ -336,21 +423,57 @@ static void DecodeHeader(guchar *Data, gint Bytes,
                State->LineWidth = (State->LineWidth / 4) * 4 + 4;
 
 
-       if (State->LineBuf == NULL)
-               State->LineBuf = g_malloc(State->LineWidth);
+       if (State->LineBuf == NULL) {
+               State->LineBuf = g_try_malloc(State->LineWidth);
+               if (!State->LineBuf) {
+                       g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                             _("Not enough memory to load icon"));
+                       return;
+               }
+       }
 
        g_assert(State->LineBuf != NULL);
 
 
        if (State->pixbuf == NULL) {
+#if 1
+               if (State->size_func) {
+                       gint width = State->Header.width;
+                       gint height = State->Header.height;
+
+                       (*State->size_func) (&width, &height, State->user_data);
+                       if (width == 0 || height == 0) {
+                               State->LineWidth = 0;
+                               return;
+                       }
+               }
+#endif
+
                State->pixbuf =
                    gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
-                                  (gint) State->Header.width,
-                                  (gint) State->Header.height);
+                                  State->Header.width,
+                                  State->Header.height);
+               if (!State->pixbuf) {
+                       g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                             _("Not enough memory to load icon"));
+                       return;
+               }
+               if (State->cursor) {
+                       gchar hot[10];
+                       g_snprintf (hot, 10, "%d", State->x_hot);
+                       gdk_pixbuf_set_option (State->pixbuf, "x_hot", hot);
+                       g_snprintf (hot, 10, "%d", State->y_hot);
+                       gdk_pixbuf_set_option (State->pixbuf, "y_hot", hot);
+               }
 
                if (State->prepared_func != NULL)
                        /* Notify the client that we are ready to go */
                        (*State->prepared_func) (State->pixbuf,
+                                                 NULL,
                                                 State->user_data);
 
        }
@@ -363,22 +486,31 @@ static void DecodeHeader(guchar *Data, gint Bytes,
  * return context (opaque to user)
  */
 
-gpointer
-gdk_pixbuf__ico_image_begin_load(ModulePreparedNotifyFunc prepared_func,
-                                ModuleUpdatedNotifyFunc updated_func,
-                                ModuleFrameDoneNotifyFunc frame_done_func,
-                                ModuleAnimationDoneNotifyFunc anim_done_func,
-                                gpointer user_data)
+static gpointer
+gdk_pixbuf__ico_image_begin_load(GdkPixbufModuleSizeFunc size_func,
+                                 GdkPixbufModulePreparedFunc prepared_func,
+                                GdkPixbufModuleUpdatedFunc updated_func,
+                                gpointer user_data,
+                                 GError **error)
 {
        struct ico_progressive_state *context;
 
        context = g_new0(struct ico_progressive_state, 1);
+       context->size_func = size_func;
        context->prepared_func = prepared_func;
        context->updated_func = updated_func;
        context->user_data = user_data;
 
        context->HeaderSize = 54;
-       context->HeaderBuf = g_malloc(14 + 40 + 4*256 + 512);
+       context->HeaderBuf = g_try_malloc(14 + 40 + 4*256 + 512);
+       if (!context->HeaderBuf) {
+               g_free (context);
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                     _("Not enough memory to load ICO file"));
+               return NULL;
+       }
        /* 4*256 for the colormap */
        context->BytesInHeaderBuf = 14 + 40 + 4*256 + 512 ;
        context->HeaderDone = 0;
@@ -404,26 +536,46 @@ gdk_pixbuf__ico_image_begin_load(ModulePreparedNotifyFunc prepared_func,
  *
  * free context, unref gdk_pixbuf
  */
-void gdk_pixbuf__ico_image_stop_load(gpointer data)
+static gboolean 
+gdk_pixbuf__ico_image_stop_load(gpointer data,
+                               GError **error)
 {
        struct ico_progressive_state *context =
            (struct ico_progressive_state *) data;
 
+        /* FIXME this thing needs to report errors if
+         * we have unused image data
+         */
 
-       g_return_if_fail(context != NULL);
-
-       if (context->LineBuf != NULL)
-               g_free(context->LineBuf);
-       context->LineBuf = NULL;
-       if (context->HeaderBuf != NULL)
-               g_free(context->HeaderBuf);
-
-       if (context->pixbuf)
-               gdk_pixbuf_unref(context->pixbuf);
+       g_return_val_if_fail(context != NULL, TRUE);
 
-       g_free(context);
+       context_free (context);
+        return TRUE;
 }
 
+static void
+OneLine32 (struct ico_progressive_state *context)
+{
+        gint X;
+        guchar *Pixels;
+
+        X = 0;
+        if (context->Header.Negative == 0)
+                Pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride *
+                          (context->Header.height - context->Lines - 1));
+        else
+                Pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride *
+                          context->Lines);
+        while (X < context->Header.width) {
+                Pixels[X * 4 + 0] = context->LineBuf[X * 4 + 2];
+                Pixels[X * 4 + 1] = context->LineBuf[X * 4 + 1];
+                Pixels[X * 4 + 2] = context->LineBuf[X * 4 + 0];
+                Pixels[X * 4 + 3] = context->LineBuf[X * 4 + 3];
+                X++;
+        }
+}
 
 static void OneLine24(struct ico_progressive_state *context)
 {
@@ -448,6 +600,44 @@ static void OneLine24(struct ico_progressive_state *context)
 
 }
 
+static void
+OneLine16 (struct ico_progressive_state *context)
+{
+        int i;
+        guchar *pixels;
+        guchar *src;
+
+        if (context->Header.Negative == 0)
+                pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride * (context->Header.height - context->Lines - 1));
+        else
+                pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride * context->Lines);
+
+        src = context->LineBuf;
+
+        for (i = 0; i < context->Header.width; i++) {
+                int v, r, g, b;
+
+                v = (int) src[0] | ((int) src[1] << 8);
+                src += 2;
+
+                /* Extract 5-bit RGB values */
+
+                r = (v >> 10) & 0x1f;
+                g = (v >> 5) & 0x1f;
+                b = v & 0x1f;
+
+                /* Fill the rightmost bits to form 8-bit values */
+
+                *pixels++ = (r << 3) | (r >> 2);
+                *pixels++ = (g << 3) | (g >> 2);
+                *pixels++ = (b << 3) | (b >> 2);
+                pixels++; /* skip alpha channel */
+        }
+}
+
+
 static void OneLine8(struct ico_progressive_state *context)
 {
        gint X;
@@ -546,6 +736,10 @@ static void OneLineTransp(struct ico_progressive_state *context)
        gint X;
        guchar *Pixels;
 
+       /* Ignore the XOR mask for XP style 32-bpp icons with alpha */ 
+       if (context->Header.depth == 32)
+               return;
+
        X = 0;
        if (context->Header.Negative == 0)
                Pixels = (context->pixbuf->pixels +
@@ -562,7 +756,7 @@ static void OneLineTransp(struct ico_progressive_state *context)
                Bit = Bit & 1;
                /* The joys of having a BGR byteorder */
                Pixels[X * 4 + 3] = 255-Bit*255;
-#if 0          
+#if 0
                if (Bit){
                  Pixels[X*4+0] = 255;
                  Pixels[X*4+1] = 255;
@@ -585,19 +779,22 @@ static void OneLine(struct ico_progressive_state *context)
        }
                
        if (context->Lines <context->Header.height) {           
-
-               if (context->Type == 24)
+                if (context->Type == 32)
+                        OneLine32 (context);
+               else if (context->Type == 24)
                        OneLine24(context);
-               if (context->Type == 8)
+                else if (context->Type == 16)
+                        OneLine16 (context);
+               else if (context->Type == 8)
                        OneLine8(context);
-               if (context->Type == 4)
+               else if (context->Type == 4)
                        OneLine4(context);
-               if (context->Type == 1)
+               else if (context->Type == 1)
                        OneLine1(context);
+               else 
+                       g_assert_not_reached ();
        } else
-       {
                OneLineTransp(context);
-       }
        
        context->Lines++;
        if (context->Lines>=context->Header.height) {
@@ -615,9 +812,9 @@ static void OneLine(struct ico_progressive_state *context)
        if (context->updated_func != NULL) {
                (*context->updated_func) (context->pixbuf,
                                          0,
-                                         context->Lines,
+                                         context->Lines % context->Header.height,
                                          context->Header.width,
-                                         context->Header.height,
+                                         1,
                                          context->user_data);
 
        }
@@ -630,8 +827,11 @@ static void OneLine(struct ico_progressive_state *context)
  *
  * append image data onto inrecrementally built output image
  */
-gboolean
-gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size)
+static gboolean
+gdk_pixbuf__ico_image_load_increment(gpointer data,
+                                     const guchar * buf,
+                                     guint size,
+                                     GError **error)
 {
        struct ico_progressive_state *context =
            (struct ico_progressive_state *) data;
@@ -653,9 +853,8 @@ gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size)
                        size -= BytesToCopy;
                        buf += BytesToCopy;
                        context->HeaderDone += BytesToCopy;
-
-               }  else
-               {  
+               } 
+               else {
                        BytesToCopy =
                            context->LineWidth - context->LineDone;
                        if (BytesToCopy > size)
@@ -677,12 +876,379 @@ gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size)
 
                }
 
-               if (context->HeaderDone >= 6)
+               if (context->HeaderDone >= 6 && context->pixbuf == NULL) {
+                       GError *decode_err = NULL;
                        DecodeHeader(context->HeaderBuf,
-                                    context->HeaderDone, context);
+                                    context->HeaderDone, context, &decode_err);
+                       if (context->LineBuf != NULL && context->LineWidth == 0)
+                               return TRUE;
 
+                       if (decode_err) {
+                               g_propagate_error (error, decode_err);
+                               return FALSE;
+                       }
+               }
+       }
+
+       return TRUE;
+}
 
+/* saving ICOs */ 
+
+static gint
+write8 (FILE     *f,
+       guint8   *data,
+       gint      count)
+{
+  gint bytes;
+  gint written;
+
+  written = 0;
+  while (count > 0)
+    {
+      bytes = fwrite ((char*) data, sizeof (char), count, f);
+      if (bytes <= 0)
+        break;
+      count -= bytes;
+      data += bytes;
+      written += bytes;
+    }
+
+  return written;
+}
+
+static gint
+write16 (FILE     *f,
+        guint16  *data,
+        gint      count)
+{
+  gint i;
+
+  for (i = 0; i < count; i++)
+         data[i] = GUINT16_TO_LE (data[i]);
+
+  return write8 (f, (guint8*) data, count * 2);
+}
+
+static gint
+write32 (FILE     *f,
+        guint32  *data,
+        gint      count)
+{
+  gint i;
+
+  for (i = 0; i < count; i++)
+         data[i] = GUINT32_TO_LE (data[i]);
+  
+  return write8 (f, (guint8*) data, count * 4);
+}
+
+typedef struct _IconEntry IconEntry;
+struct _IconEntry {
+       gint width;
+       gint height;
+       gint depth;
+       gint hot_x;
+       gint hot_y;
+
+       guint8 n_colors;
+       guint32 *colors;
+       guint xor_rowstride;
+       guint8 *xor;
+       guint and_rowstride;
+       guint8 *and;
+};
+
+static gboolean
+fill_entry (IconEntry *icon, 
+           GdkPixbuf *pixbuf, 
+           gint       hot_x, 
+           gint       hot_y, 
+           GError   **error) 
+ {
+       guchar *p, *pixels, *and, *xor;
+       gint n_channels, v, x, y;
+
+       if (icon->width > 255 || icon->height > 255) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_BAD_OPTION,
+                                     _("Image too large to be saved as ICO"));
+               return FALSE;
+       } 
+       
+       if (hot_x > -1 && hot_y > -1) {
+               icon->hot_x = hot_x;
+               icon->hot_y = hot_y;
+               if (icon->hot_x >= icon->width || icon->hot_y >= icon->height) {
+                       g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_BAD_OPTION,
+                                             _("Cursor hotspot outside image"));
+                       return FALSE;
+               }
+       }
+       else {
+               icon->hot_x = -1;
+               icon->hot_y = -1;
+       }
+       
+       switch (icon->depth) {
+       case 32:
+               icon->xor_rowstride = icon->width * 4;
+               break;
+       case 24:
+               icon->xor_rowstride = icon->width * 3;
+               break;
+       case 16:
+               icon->xor_rowstride = icon->width * 2;
+               break;
+       default:
+               g_set_error (error,
+                            GDK_PIXBUF_ERROR,
+                            GDK_PIXBUF_ERROR_BAD_OPTION,
+                            _("Unsupported depth for ICO file: %d"), icon->depth);
+               return FALSE;
+       }
+
+       if ((icon->xor_rowstride % 4) != 0)             
+               icon->xor_rowstride = 4 * ((icon->xor_rowstride / 4) + 1);
+       icon->xor = g_new0 (guchar, icon->xor_rowstride * icon->height);
+
+       icon->and_rowstride = icon->width / 8;
+       if ((icon->and_rowstride % 4) != 0)             
+               icon->and_rowstride = 4 * ((icon->and_rowstride / 4) + 1);
+       icon->and = g_new0 (guchar, icon->and_rowstride * icon->height);
+
+       pixels = gdk_pixbuf_get_pixels (pixbuf);
+       n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+       for (y = 0; y < icon->height; y++) {
+               p = pixels + gdk_pixbuf_get_rowstride (pixbuf) * (icon->height - 1 - y);
+               and = icon->and + icon->and_rowstride * y;
+               xor = icon->xor + icon->xor_rowstride * y;
+               for (x = 0; x < icon->width; x++) {
+                       switch (icon->depth) {
+                       case 32:
+                               xor[0] = p[2];
+                               xor[1] = p[1];
+                               xor[2] = p[0];
+                               xor[3] = 0xff;
+                               if (n_channels == 4) {
+                                       xor[3] = p[3];
+                                       if (p[3] < 0x80)
+                                               *and |= 1 << (7 - x % 8);
+                               }
+                               xor += 4;
+                               break;
+                       case 24:
+                               xor[0] = p[2];
+                               xor[1] = p[1];
+                               xor[2] = p[0];
+                               if (n_channels == 4 && p[3] < 0x80)
+                                       *and |= 1 << (7 - x % 8);
+                               xor += 3;
+                               break;
+                       case 16:
+                               v = ((p[0] >> 3) << 10) | ((p[1] >> 3) << 5) | (p[2] >> 3);
+                               xor[0] = v & 0xff;
+                               xor[1] = v >> 8;
+                               if (n_channels == 4 && p[3] < 0x80)
+                                       *and |= 1 << (7 - x % 8);
+                               xor += 2;
+                               break;
+                       }
+                       
+                       p += n_channels;
+                       if (x % 8 == 7) 
+                               and++;
+               }
        }
 
        return TRUE;
 }
+
+static void
+free_entry (IconEntry *icon)
+{
+       g_free (icon->colors);
+       g_free (icon->and);
+       g_free (icon->xor);
+       g_free (icon);
+}
+
+static void
+write_icon (FILE *f, GSList *entries)
+{
+       IconEntry *icon;
+       GSList *entry;
+       guint8 bytes[4];
+       guint16 words[4];
+       guint32 dwords[6];
+       gint type;
+       gint n_entries;
+       gint offset;
+       gint size;
+
+       if (((IconEntry *)entries->data)->hot_x > -1)
+               type = 2;
+       else 
+               type = 1;
+       n_entries = g_slist_length (entries);
+
+       /* header */
+       words[0] = 0;
+       words[1] = type;
+       words[2] = n_entries;
+       write16 (f, words, 3);
+       
+       offset = 6 + 16 * n_entries;
+
+       for (entry = entries; entry; entry = entry->next) {
+               icon = (IconEntry *)entry->data;
+               size = 40 + icon->height * (icon->and_rowstride + icon->xor_rowstride);
+               
+               /* directory entry */
+               bytes[0] = icon->width;
+               bytes[1] = icon->height;
+               bytes[2] = icon->n_colors;
+               bytes[3] = 0;
+               write8 (f, bytes, 4);
+               if (type == 1) {
+                       words[0] = 1;
+                       words[1] = icon->depth;
+               }
+               else {
+                       words[0] = icon->hot_x;
+                       words[1] = icon->hot_y;
+               }
+               write16 (f, words, 2);
+               dwords[0] = size;
+               dwords[1] = offset;
+               write32 (f, dwords, 2);
+
+               offset += size;
+       }
+
+       for (entry = entries; entry; entry = entry->next) {
+               icon = (IconEntry *)entry->data;
+
+               /* bitmap header */
+               dwords[0] = 40;
+               dwords[1] = icon->width;
+               dwords[2] = icon->height * 2;
+               write32 (f, dwords, 3);
+               words[0] = 1;
+               words[1] = icon->depth;
+               write16 (f, words, 2);
+               dwords[0] = 0;
+               dwords[1] = 0;
+               dwords[2] = 0;
+               dwords[3] = 0;
+               dwords[4] = 0;
+               dwords[5] = 0;
+               write32 (f, dwords, 6);
+
+               /* image data */
+               write8 (f, icon->xor, icon->xor_rowstride * icon->height);
+               write8 (f, icon->and, icon->and_rowstride * icon->height);
+       }
+}
+
+static gboolean
+gdk_pixbuf__ico_image_save (FILE          *f, 
+                            GdkPixbuf     *pixbuf, 
+                            gchar        **keys,
+                            gchar        **values,
+                            GError       **error)
+{
+       gint hot_x, hot_y;
+       IconEntry *icon;
+       GSList *entries = NULL;
+
+       /* support only single-image ICOs for now */
+       icon = g_new0 (IconEntry, 1);
+       icon->width = gdk_pixbuf_get_width (pixbuf);
+       icon->height = gdk_pixbuf_get_height (pixbuf);
+       icon->depth = gdk_pixbuf_get_has_alpha (pixbuf) ? 32 : 24;
+       hot_x = -1;
+       hot_y = -1;
+       
+       /* parse options */
+       if (keys && *keys) {
+               gchar **kiter;
+               gchar **viter;
+               
+               for (kiter = keys, viter = values; *kiter && *viter; kiter++, viter++) {
+                       char *endptr;
+                       if (strcmp (*kiter, "depth") == 0) {
+                               sscanf (*viter, "%d", &icon->depth);
+                       }
+                       else if (strcmp (*kiter, "x_hot") == 0) {
+                               hot_x = strtol (*viter, &endptr, 10);
+                       }
+                       else if (strcmp (*kiter, "y_hot") == 0) {
+                               hot_y = strtol (*viter, &endptr, 10);
+                       }
+
+               }
+       }
+
+       if (!fill_entry (icon, pixbuf, hot_x, hot_y, error)) {
+               free_entry (icon);
+               return FALSE;
+       }
+
+       entries = g_slist_append (entries, icon); 
+       write_icon (f, entries);
+
+       g_slist_foreach (entries, (GFunc)free_entry, NULL);
+       g_slist_free (entries);
+
+       return TRUE;
+}
+
+#ifndef INCLUDE_ico
+#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
+#else
+#define MODULE_ENTRY(function) void _gdk_pixbuf__ico_ ## function
+#endif
+
+MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
+{
+       module->begin_load = gdk_pixbuf__ico_image_begin_load;
+       module->stop_load = gdk_pixbuf__ico_image_stop_load;
+       module->load_increment = gdk_pixbuf__ico_image_load_increment;
+        module->save = gdk_pixbuf__ico_image_save;
+}
+
+MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
+{
+       static GdkPixbufModulePattern signature[] = {
+               { "  \x1   ", "zz znz", 100 }, 
+               { "  \x2   ", "zz znz", 100 },
+               { NULL, NULL, 0 }
+       };
+       static gchar * mime_types[] = {
+               "image/x-icon",
+               "image/x-ico",
+               "image/x-win-bitmap",
+               NULL
+       };
+       static gchar * extensions[] = {
+               "ico",
+               "cur",
+               NULL
+       };
+
+       info->name = "ico";
+       info->signature = signature;
+       info->description = N_("The ICO image format");
+       info->mime_types = mime_types;
+       info->extensions = extensions;
+       info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
+       info->license = "LGPL";
+}
+
+
+
+