]> Pileus Git - ~andy/gtk/blobdiff - gdk-pixbuf/io-ico.c
Correct the math to calculate bilinear weights. (#112412, Brian Cameron)
[~andy/gtk] / gdk-pixbuf / io-ico.c
index 4fdb9bae735ec6a5ea37b5d0902c190d53520066..e19f86f8f7096214704da07fa791de5c8edb4d2f 100644 (file)
@@ -36,6 +36,7 @@ Known bugs:
 
 #include <config.h>
 #include <stdio.h>
+#include <stdlib.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
@@ -125,6 +126,7 @@ struct headerpair {
 };
 
 struct ico_progressive_state {
+       GdkPixbufModuleSizeFunc size_func;
        GdkPixbufModulePreparedFunc prepared_func;
        GdkPixbufModuleUpdatedFunc updated_func;
        gpointer user_data;
@@ -191,9 +193,8 @@ static void DecodeHeader(guchar *Data, gint Bytes,
                         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 */
@@ -231,18 +232,11 @@ 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];
@@ -407,6 +401,19 @@ static void DecodeHeader(guchar *Data, gint Bytes,
 
 
        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,
                                   State->Header.width,
@@ -452,6 +459,7 @@ gdk_pixbuf__ico_image_begin_load(GdkPixbufModuleSizeFunc size_func,
        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;
@@ -691,6 +699,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 +
@@ -827,10 +839,13 @@ gdk_pixbuf__ico_image_load_increment(gpointer data,
 
                }
 
-               if (context->HeaderDone >= 6) {
+               if (context->HeaderDone >= 6 && context->pixbuf == NULL) {
                        GError *decode_err = NULL;
                        DecodeHeader(context->HeaderBuf,
                                     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;
@@ -841,12 +856,327 @@ gdk_pixbuf__ico_image_load_increment(gpointer data,
        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 (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_x = hot_y;
+               if (icon->hot_x >= icon->width || icon->hot_y >= icon->height) {
+                       g_set_error (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;
+}
+
 void
 MODULE_ENTRY (ico, 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;
 }
 
 void
@@ -872,7 +1202,7 @@ MODULE_ENTRY (ico, fill_info) (GdkPixbufFormat *info)
        info->description = N_("The ICO image format");
        info->mime_types = mime_types;
        info->extensions = extensions;
-       info->flags = 0;
+       info->flags = GDK_PIXBUF_FORMAT_WRITABLE;
 }