summaryrefslogtreecommitdiffstats
path: root/plug-ins/file-ico/ico-save.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plug-ins/file-ico/ico-save.c1176
1 files changed, 1176 insertions, 0 deletions
diff --git a/plug-ins/file-ico/ico-save.c b/plug-ins/file-ico/ico-save.c
new file mode 100644
index 0000000..f8ce431
--- /dev/null
+++ b/plug-ins/file-ico/ico-save.c
@@ -0,0 +1,1176 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * GIMP Plug-in for Windows Icon files.
+ * Copyright (C) 2002 Christian Kreibich <christian@whoop.org>.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include <png.h>
+
+/* #define ICO_DBG */
+
+#include "ico.h"
+#include "ico-load.h"
+#include "ico-save.h"
+#include "ico-dialog.h"
+
+#include "libgimp/stdplugins-intl.h"
+
+
+static gint ico_write_int8 (FILE *fp,
+ guint8 *data,
+ gint count);
+static gint ico_write_int16 (FILE *fp,
+ guint16 *data,
+ gint count);
+static gint ico_write_int32 (FILE *fp,
+ guint32 *data,
+ gint count);
+
+/* Helpers to set bits in a *cleared* data chunk */
+static void ico_set_bit_in_data (guint8 *data,
+ gint line_width,
+ gint bit_num,
+ gint bit_val);
+static void ico_set_nibble_in_data (guint8 *data,
+ gint line_width,
+ gint nibble_num,
+ gint nibble_val);
+static void ico_set_byte_in_data (guint8 *data,
+ gint line_width,
+ gint byte_num,
+ gint byte_val);
+
+static gint ico_get_layer_num_colors (gint32 layer,
+ gboolean *uses_alpha_levels);
+static void ico_image_get_reduced_buf (guint32 layer,
+ gint bpp,
+ gint *num_colors,
+ guchar **cmap_out,
+ guchar **buf_out);
+
+
+static gint
+ico_write_int32 (FILE *fp,
+ guint32 *data,
+ gint count)
+{
+ gint total;
+
+ total = count;
+ if (count > 0)
+ {
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ gint i;
+
+ for (i = 0; i < count; i++)
+ data[i] = GUINT32_FROM_LE (data[i]);
+#endif
+
+ ico_write_int8 (fp, (guint8 *) data, count * 4);
+
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ /* Put it back like we found it */
+ for (i = 0; i < count; i++)
+ data[i] = GUINT32_FROM_LE (data[i]);
+#endif
+ }
+
+ return total * 4;
+}
+
+
+static gint
+ico_write_int16 (FILE *fp,
+ guint16 *data,
+ gint count)
+{
+ gint total;
+
+ total = count;
+ if (count > 0)
+ {
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ gint i;
+
+ for (i = 0; i < count; i++)
+ data[i] = GUINT16_FROM_LE (data[i]);
+#endif
+
+ ico_write_int8 (fp, (guint8 *) data, count * 2);
+
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ /* Put it back like we found it */
+ for (i = 0; i < count; i++)
+ data[i] = GUINT16_FROM_LE (data[i]);
+#endif
+ }
+
+ return total * 2;
+}
+
+
+static gint
+ico_write_int8 (FILE *fp,
+ guint8 *data,
+ gint count)
+{
+ gint total;
+ gint bytes;
+
+ total = count;
+ while (count > 0)
+ {
+ bytes = fwrite ((gchar *) data, sizeof (gchar), count, fp);
+ if (bytes <= 0) /* something bad happened */
+ break;
+ count -= bytes;
+ data += bytes;
+ }
+
+ return total;
+}
+
+
+static void
+ico_save_init (gint32 image_ID,
+ IcoSaveInfo *info)
+{
+ gint *layers;
+ gint i, num_colors;
+ gboolean uses_alpha_values = FALSE;
+
+ layers = gimp_image_get_layers (image_ID, &info->num_icons);
+ info->layers = layers;
+ info->depths = g_new (gint, info->num_icons);
+ info->default_depths = g_new (gint, info->num_icons);
+ info->compress = g_new (gboolean, info->num_icons);
+
+ /* Limit the color depths to values that don't cause any color loss --
+ the user should pick these anyway, so we can save her some time.
+ If the user wants to lose some colors, the settings can always be changed
+ in the dialog: */
+ for (i = 0; i < info->num_icons; i++)
+ {
+ num_colors = ico_get_layer_num_colors (layers[i], &uses_alpha_values);
+
+ if (!uses_alpha_values)
+ {
+ if (num_colors <= 2)
+ {
+ /* Let's suggest monochrome */
+ info->default_depths [i] = 1;
+ }
+ else if (num_colors <= 16)
+ {
+ /* Let's suggest 4bpp */
+ info->default_depths [i] = 4;
+ }
+ else if (num_colors <= 256)
+ {
+ /* Let's suggest 8bpp */
+ info->default_depths [i] = 8;
+ }
+ else
+ {
+ /* Let's suggest 24bpp */
+ info->default_depths [i] = 24;
+ }
+ }
+ else
+ {
+ /* Otherwise, or if real alpha levels are used, stick with 32bpp */
+ info->default_depths [i] = 32;
+ }
+
+ /* vista icons */
+ if (gimp_drawable_width (layers[i]) > 255
+ || gimp_drawable_height (layers[i]) > 255 )
+ {
+ info->compress[i] = TRUE;
+ }
+ else
+ {
+ info->compress[i] = FALSE;
+ }
+ }
+
+ /* set with default values */
+ memcpy (info->depths, info->default_depths,
+ sizeof (gint) * info->num_icons);
+}
+
+
+
+static gboolean
+ico_save_dialog (gint32 image_ID,
+ IcoSaveInfo *info)
+{
+ GtkWidget *dialog;
+ gint i;
+ gint response;
+
+ gimp_ui_init (PLUG_IN_BINARY, TRUE);
+
+ dialog = ico_dialog_new (info);
+ for (i = 0; i < info->num_icons; i++)
+ {
+ /* if (gimp_layer_get_visible(layers[i])) */
+ ico_dialog_add_icon (dialog, info->layers[i], i);
+ }
+
+ /* Scale the thing to approximately fit its content, but not too large ... */
+ gtk_window_set_default_size (GTK_WINDOW (dialog),
+ -1,
+ 200 + (info->num_icons > 4 ?
+ 500 : info->num_icons * 120));
+
+ gtk_widget_show (dialog);
+
+ response = gimp_dialog_run (GIMP_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return (response == GTK_RESPONSE_OK);
+}
+
+static void
+ico_set_bit_in_data (guint8 *data,
+ gint line_width,
+ gint bit_num,
+ gint bit_val)
+{
+ gint line;
+ gint width32;
+ gint offset;
+
+ /* width per line in multiples of 32 bits */
+ width32 = (line_width % 32 == 0 ? line_width/32 : line_width/32 + 1);
+
+ line = bit_num / line_width;
+ offset = bit_num % line_width;
+ bit_val = bit_val & 0x00000001;
+
+ data[line * width32 * 4 + offset/8] |= (bit_val << (7 - (offset % 8)));
+}
+
+
+static void
+ico_set_nibble_in_data (guint8 *data,
+ gint line_width,
+ gint nibble_num,
+ gint nibble_val)
+{
+ gint line;
+ gint width8;
+ gint offset;
+
+ /* width per line in multiples of 32 bits */
+ width8 = (line_width % 8 == 0 ? line_width/8 : line_width/8 + 1);
+
+ line = nibble_num / line_width;
+ offset = nibble_num % line_width;
+ nibble_val = nibble_val & 0x0000000F;
+
+ data[line * width8 * 4 + offset/2] |=
+ (nibble_val << (4 * (1 - (offset % 2))));
+}
+
+
+static void
+ico_set_byte_in_data (guint8 *data,
+ gint line_width,
+ gint byte_num,
+ gint byte_val)
+{
+ gint line;
+ gint width4;
+ gint offset;
+ gint byte;
+
+ /* width per line in multiples of 32 bits */
+ width4 = (line_width % 4 == 0 ? line_width/4 : line_width/4 + 1);
+
+ line = byte_num / line_width;
+ offset = byte_num % line_width;
+ byte = byte_val & 0x000000FF;
+
+ data[line * width4 * 4 + offset] = byte;
+}
+
+
+/* Create a colormap from the given buffer data */
+static guint32 *
+ico_create_palette (const guchar *cmap,
+ gint num_colors,
+ gint num_colors_used,
+ gint *black_slot)
+{
+ guchar *palette;
+ gint i;
+
+ g_return_val_if_fail (cmap != NULL || num_colors_used == 0, NULL);
+ g_return_val_if_fail (num_colors_used <= num_colors, NULL);
+
+ palette = g_new0 (guchar, num_colors * 4);
+ *black_slot = -1;
+
+ for (i = 0; i < num_colors_used; i++)
+ {
+ palette[i * 4 + 2] = cmap[i * 3];
+ palette[i * 4 + 1] = cmap[i * 3 + 1];
+ palette[i * 4] = cmap[i * 3 + 2];
+
+ if ((cmap[i*3] == 0) &&
+ (cmap[i*3 + 1] == 0) &&
+ (cmap[i*3 + 2] == 0))
+ {
+ *black_slot = i;
+ }
+ }
+
+ if (*black_slot == -1)
+ {
+ if (num_colors_used == num_colors)
+ {
+ D(("WARNING -- no room for black, this shouldn't happen.\n"));
+ *black_slot = num_colors - 1;
+
+ palette[(num_colors-1) * 4] = 0;
+ palette[(num_colors-1) * 4 + 1] = 0;
+ palette[(num_colors-1) * 4 + 2] = 0;
+ }
+ else
+ {
+ *black_slot = num_colors_used;
+ }
+ }
+
+ return (guint32 *) palette;
+}
+
+
+static GHashTable *
+ico_create_color_to_palette_map (const guint32 *palette,
+ gint num_colors)
+{
+ GHashTable *hash;
+ gint i;
+
+ hash = g_hash_table_new_full (g_int_hash, g_int_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ for (i = 0; i < num_colors; i++)
+ {
+ const guint8 *pixel = (const guint8 *) &palette[i];
+ gint *color;
+ gint *slot;
+
+ color = g_new (gint, 1);
+ slot = g_new (gint, 1);
+
+ *color = (pixel[2] << 16 | pixel[1] << 8 | pixel[0]);
+ *slot = i;
+
+ g_hash_table_insert (hash, color, slot);
+ }
+
+ return hash;
+}
+
+static gint
+ico_get_palette_index (GHashTable *hash,
+ gint red,
+ gint green,
+ gint blue)
+{
+ gint color = 0;
+ gint *slot;
+
+ color = (red << 16 | green << 8 | blue);
+ slot = g_hash_table_lookup (hash, &color);
+
+ if (!slot)
+ {
+ return 0;
+ }
+
+ return *slot;
+}
+
+static gint
+ico_get_layer_num_colors (gint32 layer,
+ gboolean *uses_alpha_levels)
+{
+ gint w, h;
+ gint bpp;
+ gint num_colors = 0;
+ guint num_pixels;
+ guchar *buf;
+ guchar *src;
+ guint32 *colors;
+ guint32 *c;
+ GHashTable *hash;
+ GeglBuffer *buffer = gimp_drawable_get_buffer (layer);
+ const Babl *format;
+
+ w = gegl_buffer_get_width (buffer);
+ h = gegl_buffer_get_height (buffer);
+
+ num_pixels = w * h;
+
+ switch (gimp_drawable_type (layer))
+ {
+ case GIMP_RGB_IMAGE:
+ format = babl_format ("R'G'B' u8");
+ break;
+
+ case GIMP_RGBA_IMAGE:
+ format = babl_format ("R'G'B'A u8");
+ break;
+
+ case GIMP_GRAY_IMAGE:
+ format = babl_format ("Y' u8");
+ break;
+
+ case GIMP_GRAYA_IMAGE:
+ format = babl_format ("Y'A u8");
+ break;
+
+ case GIMP_INDEXED_IMAGE:
+ case GIMP_INDEXEDA_IMAGE:
+ format = gegl_buffer_get_format (buffer);
+ /* It is possible to count the colors of indexed image more easily
+ * with gimp_image_get_colormap(), but counting only the colors
+ * actually used will allow more efficient bpp if possible. */
+ break;
+
+ default:
+ g_return_val_if_reached (0);
+ }
+
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ buf = src = g_new (guchar, num_pixels * bpp);
+
+ gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0,
+ format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ g_object_unref (buffer);
+
+ hash = g_hash_table_new (g_int_hash, g_int_equal);
+ *uses_alpha_levels = FALSE;
+
+ colors = c = g_new (guint32, num_pixels);
+
+ switch (bpp)
+ {
+ case 1:
+ while (num_pixels--)
+ {
+ *c = *src;
+ g_hash_table_insert (hash, c, c);
+ src++;
+ c++;
+ }
+ break;
+
+ case 2:
+ while (num_pixels--)
+ {
+ *c = (src[1] << 8) | src[0];
+ if (src[1] != 0 && src[1] != 255)
+ *uses_alpha_levels = TRUE;
+ g_hash_table_insert (hash, c, c);
+ src += 2;
+ c++;
+ }
+ break;
+
+ case 3:
+ while (num_pixels--)
+ {
+ *c = (src[2] << 16) | (src[1] << 8) | src[0];
+ g_hash_table_insert (hash, c, c);
+ src += 3;
+ c++;
+ }
+ break;
+
+ case 4:
+ while (num_pixels--)
+ {
+ *c = (src[3] << 24) | (src[2] << 16) | (src[1] << 8) | src[0];
+ if (src[3] != 0 && src[3] != 255)
+ *uses_alpha_levels = TRUE;
+ g_hash_table_insert (hash, c, c);
+ src += 4;
+ c++;
+ }
+ break;
+ }
+
+ num_colors = g_hash_table_size (hash);
+
+ g_hash_table_destroy (hash);
+
+ g_free (colors);
+ g_free (buf);
+
+ return num_colors;
+}
+
+gboolean
+ico_cmap_contains_black (const guchar *cmap,
+ gint num_colors)
+{
+ gint i;
+
+ for (i = 0; i < num_colors; i++)
+ {
+ if ((cmap[3 * i ] == 0) &&
+ (cmap[3 * i + 1] == 0) &&
+ (cmap[3 * i + 2] == 0))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+ico_image_get_reduced_buf (guint32 layer,
+ gint bpp,
+ gint *num_colors,
+ guchar **cmap_out,
+ guchar **buf_out)
+{
+ gint32 tmp_image;
+ gint32 tmp_layer;
+ gint w, h;
+ guchar *buf;
+ guchar *cmap = NULL;
+ GeglBuffer *buffer = gimp_drawable_get_buffer (layer);
+ const Babl *format;
+
+ w = gegl_buffer_get_width (buffer);
+ h = gegl_buffer_get_height (buffer);
+
+ switch (gimp_drawable_type (layer))
+ {
+ case GIMP_RGB_IMAGE:
+ format = babl_format ("R'G'B' u8");
+ break;
+
+ case GIMP_RGBA_IMAGE:
+ format = babl_format ("R'G'B'A u8");
+ break;
+
+ case GIMP_GRAY_IMAGE:
+ format = babl_format ("Y' u8");
+ break;
+
+ case GIMP_GRAYA_IMAGE:
+ format = babl_format ("Y'A u8");
+ break;
+
+ case GIMP_INDEXED_IMAGE:
+ case GIMP_INDEXEDA_IMAGE:
+ format = gegl_buffer_get_format (buffer);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ *num_colors = 0;
+
+ buf = g_new (guchar, w * h * 4);
+
+ if (bpp <= 8 || bpp == 24 || babl_format_get_bytes_per_pixel (format) != 4)
+ {
+ gint32 image = gimp_item_get_image (layer);
+ GeglBuffer *tmp;
+
+ tmp_image = gimp_image_new (w, h, gimp_image_base_type (image));
+ gimp_image_undo_disable (tmp_image);
+
+ if (gimp_drawable_is_indexed (layer))
+ {
+ guchar *cmap;
+ gint num_colors;
+
+ cmap = gimp_image_get_colormap (image, &num_colors);
+ gimp_image_set_colormap (tmp_image, cmap, num_colors);
+ g_free (cmap);
+ }
+
+ tmp_layer = gimp_layer_new (tmp_image, "tmp", w, h,
+ gimp_drawable_type (layer),
+ 100,
+ gimp_image_get_default_new_layer_mode (tmp_image));
+ gimp_image_insert_layer (tmp_image, tmp_layer, -1, 0);
+
+ tmp = gimp_drawable_get_buffer (tmp_layer);
+
+ gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0,
+ format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, tmp, NULL);
+
+ g_object_unref (tmp);
+
+ if (! gimp_drawable_is_rgb (tmp_layer))
+ gimp_image_convert_rgb (tmp_image);
+
+ if (bpp <= 8)
+ {
+ gimp_image_convert_indexed (tmp_image,
+ GIMP_CONVERT_DITHER_FS,
+ GIMP_CONVERT_PALETTE_GENERATE,
+ 1 << bpp, TRUE, FALSE, "dummy");
+
+ cmap = gimp_image_get_colormap (tmp_image, num_colors);
+
+ if (*num_colors == (1 << bpp) &&
+ ! ico_cmap_contains_black (cmap, *num_colors))
+ {
+ /* Windows icons with color maps need the color black.
+ * We need to eliminate one more color to make room for black.
+ */
+
+ if (gimp_drawable_is_indexed (layer))
+ {
+ g_free (cmap);
+ cmap = gimp_image_get_colormap (image, num_colors);
+ gimp_image_set_colormap (tmp_image, cmap, *num_colors);
+ }
+ else if (gimp_drawable_is_gray (layer))
+ {
+ gimp_image_convert_grayscale (tmp_image);
+ }
+ else
+ {
+ gimp_image_convert_rgb (tmp_image);
+ }
+
+ tmp = gimp_drawable_get_buffer (tmp_layer);
+
+ gegl_buffer_set (tmp, GEGL_RECTANGLE (0, 0, w, h), 0,
+ format, buf, GEGL_AUTO_ROWSTRIDE);
+
+ g_object_unref (tmp);
+
+ if (! gimp_drawable_is_rgb (layer))
+ gimp_image_convert_rgb (tmp_image);
+
+ gimp_image_convert_indexed (tmp_image,
+ GIMP_CONVERT_DITHER_FS,
+ GIMP_CONVERT_PALETTE_GENERATE,
+ (1<<bpp) - 1, TRUE, FALSE, "dummy");
+ g_free (cmap);
+ cmap = gimp_image_get_colormap (tmp_image, num_colors);
+ }
+
+ gimp_image_convert_rgb (tmp_image);
+ }
+ else if (bpp == 24)
+ {
+ GimpParam *return_vals;
+ gint n_return_vals;
+
+ return_vals =
+ gimp_run_procedure ("plug-in-threshold-alpha", &n_return_vals,
+ GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE,
+ GIMP_PDB_IMAGE, tmp_image,
+ GIMP_PDB_DRAWABLE, tmp_layer,
+ GIMP_PDB_INT32, ICO_ALPHA_THRESHOLD,
+ GIMP_PDB_END);
+ gimp_destroy_params (return_vals, n_return_vals);
+ }
+
+ gimp_layer_add_alpha (tmp_layer);
+
+ tmp = gimp_drawable_get_buffer (tmp_layer);
+
+ gegl_buffer_get (tmp, GEGL_RECTANGLE (0, 0, w, h), 1.0,
+ NULL, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ g_object_unref (tmp);
+
+ gimp_image_delete (tmp_image);
+ }
+ else
+ {
+ gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0,
+ format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ }
+
+ g_object_unref (buffer);
+
+ *cmap_out = cmap;
+ *buf_out = buf;
+}
+
+static gboolean
+ico_write_png (FILE *fp,
+ gint32 layer,
+ gint32 depth)
+{
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_byte **row_pointers;
+ gint i, rowstride;
+ gint width, height;
+ gint num_colors_used;
+ guchar *palette;
+ guchar *buf;
+
+ row_pointers = NULL;
+ palette = NULL;
+ buf = NULL;
+
+ width = gimp_drawable_width (layer);
+ height = gimp_drawable_height (layer);
+
+ png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if ( !png_ptr )
+ return FALSE;
+
+ info_ptr = png_create_info_struct (png_ptr);
+ if ( !info_ptr )
+ {
+ png_destroy_write_struct (&png_ptr, NULL);
+ return FALSE;
+ }
+
+ if (setjmp (png_jmpbuf (png_ptr)))
+ {
+ png_destroy_write_struct (&png_ptr, &info_ptr);
+ if ( row_pointers )
+ g_free (row_pointers);
+ if (palette)
+ g_free (palette);
+ if (buf)
+ g_free (buf);
+ return FALSE;
+ }
+
+ ico_image_get_reduced_buf (layer, depth, &num_colors_used,
+ &palette, &buf);
+
+ png_init_io (png_ptr, fp);
+ png_set_IHDR (png_ptr, info_ptr, width, height,
+ 8,
+ PNG_COLOR_TYPE_RGBA,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+ png_write_info (png_ptr, info_ptr);
+
+ rowstride = ico_rowstride (width, 32);
+ row_pointers = g_new (png_byte*, height);
+ for (i = 0; i < height; i++)
+ {
+ row_pointers[i] = buf + rowstride * i;
+ }
+ png_write_image (png_ptr, row_pointers);
+
+ row_pointers = NULL;
+
+ png_write_end (png_ptr, info_ptr);
+ png_destroy_write_struct (&png_ptr, &info_ptr);
+
+ g_free (row_pointers);
+ g_free (palette);
+ g_free (buf);
+ return TRUE;
+}
+
+static gboolean
+ico_write_icon (FILE *fp,
+ gint32 layer,
+ gint32 depth)
+{
+ IcoFileDataHeader header;
+ gint and_len, xor_len, palette_index, x, y;
+ gint num_colors = 0, num_colors_used = 0, black_index = 0;
+ gint width, height;
+ guchar *buf = NULL, *pixel;
+ guint32 *buf32;
+ guchar *palette;
+ GHashTable *color_to_slot = NULL;
+ guchar *xor_map, *and_map;
+
+ guint32 *palette32 = NULL;
+ gint palette_len = 0;
+
+ guint8 alpha_threshold;
+
+ D(("Creating data structures for icon %i ------------------------\n",
+ num_icon));
+
+ width = gimp_drawable_width (layer);
+ height = gimp_drawable_height (layer);
+
+ header.header_size = 40;
+ header.width = width;
+ header.height = 2 * height;
+ header.planes = 1;
+ header.bpp = depth;
+ header.compression = 0;
+ header.image_size = 0;
+ header.x_res = 0;
+ header.y_res = 0;
+ header.used_clrs = 0;
+ header.important_clrs = 0;
+
+ num_colors = (1L << header.bpp);
+
+ D((" header size %i, w %i, h %i, planes %i, bpp %i\n",
+ header.header_size, header.width, header.height, header.planes,
+ header.bpp));
+
+ /* Reduce colors in copy of image */
+ ico_image_get_reduced_buf (layer, header.bpp, &num_colors_used,
+ &palette, &buf);
+ buf32 = (guint32 *) buf;
+
+ /* Set up colormap and and_map when necessary: */
+ if (header.bpp <= 8)
+ {
+ /* Create a colormap */
+ palette32 = ico_create_palette (palette,
+ num_colors, num_colors_used,
+ &black_index);
+ palette_len = num_colors * 4;
+
+ color_to_slot = ico_create_color_to_palette_map (palette32,
+ num_colors_used);
+ D((" created %i-slot colormap with %i colors, black at slot %i\n",
+ num_colors, num_colors_used, black_index));
+ }
+
+ /* Create and_map. It's padded out to 32 bits per line: */
+ and_map = ico_alloc_map (width, height, 1, &and_len);
+
+ /* 32-bit bitmaps have an alpha channel as well as a mask. Any partially or
+ * fully opaque pixel should have an opaque mask (some ICO code in Windows
+ * draws pixels as black if they have a transparent mask but a non-transparent
+ * alpha value).
+ *
+ * For bitmaps without an alpha channel, we use the normal threshold to build
+ * the mask, so that the mask is as close as possible to the original alpha
+ * channel.
+ */
+ alpha_threshold = header.bpp < 32 ? ICO_ALPHA_THRESHOLD : 0;
+
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ pixel = (guint8 *) &buf32[y * width + x];
+
+ ico_set_bit_in_data (and_map, width,
+ (height - y -1) * width + x,
+ (pixel[3] > alpha_threshold ? 0 : 1));
+ }
+
+ xor_map = ico_alloc_map (width, height, header.bpp, &xor_len);
+
+ /* Now fill in the xor map */
+ switch (header.bpp)
+ {
+ case 1:
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ pixel = (guint8 *) &buf32[y * width + x];
+ palette_index = ico_get_palette_index (color_to_slot, pixel[0],
+ pixel[1], pixel[2]);
+
+ if (ico_get_bit_from_data (and_map, width,
+ (height - y - 1) * width + x))
+ {
+ ico_set_bit_in_data (xor_map, width,
+ (height - y -1) * width + x,
+ black_index);
+ }
+ else
+ {
+ ico_set_bit_in_data (xor_map, width,
+ (height - y -1) * width + x,
+ palette_index);
+ }
+ }
+ break;
+
+ case 4:
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ pixel = (guint8 *) &buf32[y * width + x];
+ palette_index = ico_get_palette_index(color_to_slot, pixel[0],
+ pixel[1], pixel[2]);
+
+ if (ico_get_bit_from_data (and_map, width,
+ (height - y - 1) * width + x))
+ {
+ ico_set_nibble_in_data (xor_map, width,
+ (height - y -1) * width + x,
+ black_index);
+ }
+ else
+ {
+ ico_set_nibble_in_data (xor_map, width,
+ (height - y - 1) * width + x,
+ palette_index);
+ }
+ }
+ break;
+
+ case 8:
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ pixel = (guint8 *) &buf32[y * width + x];
+ palette_index = ico_get_palette_index (color_to_slot,
+ pixel[0],
+ pixel[1],
+ pixel[2]);
+
+ if (ico_get_bit_from_data (and_map, width,
+ (height - y - 1) * width + x))
+ {
+ ico_set_byte_in_data (xor_map, width,
+ (height - y - 1) * width + x,
+ black_index);
+ }
+ else
+ {
+ ico_set_byte_in_data (xor_map, width,
+ (height - y - 1) * width + x,
+ palette_index);
+ }
+
+ }
+ break;
+
+ case 24:
+ for (y = 0; y < height; y++)
+ {
+ guchar *row = xor_map + (xor_len * (height - y - 1) / height);
+
+ for (x = 0; x < width; x++)
+ {
+ pixel = (guint8 *) &buf32[y * width + x];
+
+ row[0] = pixel[2];
+ row[1] = pixel[1];
+ row[2] = pixel[0];
+
+ row += 3;
+ }
+ }
+ break;
+
+ default:
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ pixel = (guint8 *) &buf32[y * width + x];
+
+ ((guint32 *) xor_map)[(height - y -1) * width + x] =
+ GUINT32_TO_LE ((pixel[0] << 16) |
+ (pixel[1] << 8) |
+ (pixel[2]) |
+ (pixel[3] << 24));
+ }
+ }
+
+ D((" filled and_map of length %i, xor_map of length %i\n",
+ and_len, xor_len));
+
+ if (color_to_slot)
+ g_hash_table_destroy (color_to_slot);
+
+ g_free (palette);
+ g_free (buf);
+
+ ico_write_int32 (fp, (guint32*) &header, 3);
+ ico_write_int16 (fp, &header.planes, 2);
+ ico_write_int32 (fp, &header.compression, 6);
+
+ if (palette_len)
+ ico_write_int8 (fp, (guint8 *) palette32, palette_len);
+
+ ico_write_int8 (fp, xor_map, xor_len);
+ ico_write_int8 (fp, and_map, and_len);
+
+ g_free (palette32);
+ g_free (xor_map);
+ g_free (and_map);
+
+ return TRUE;
+}
+
+static void
+ico_save_info_free (IcoSaveInfo *info)
+{
+ g_free (info->depths);
+ g_free (info->default_depths);
+ g_free (info->compress);
+ g_free (info->layers);
+ memset (info, 0, sizeof (IcoSaveInfo));
+}
+
+GimpPDBStatusType
+ico_save_image (const gchar *filename,
+ gint32 image,
+ gint32 run_mode,
+ GError **error)
+{
+ FILE *fp;
+
+ gint i;
+ gint width, height;
+ IcoSaveInfo info;
+ IcoFileHeader header;
+ IcoFileEntry *entries;
+ gboolean saved;
+
+ D(("*** Exporting Microsoft icon file %s\n", filename));
+
+ ico_save_init (image, &info);
+
+ if (run_mode == GIMP_RUN_INTERACTIVE)
+ {
+ /* Allow user to override default values */
+ if ( !ico_save_dialog (image, &info))
+ return GIMP_PDB_CANCEL;
+ }
+
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ if (! (fp = g_fopen (filename, "wb")))
+ {
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Could not open '%s' for writing: %s"),
+ gimp_filename_to_utf8 (filename), g_strerror (errno));
+ return GIMP_PDB_EXECUTION_ERROR;
+ }
+
+ header.reserved = 0;
+ header.resource_type = 1;
+ header.icon_count = info.num_icons;
+ if ( !ico_write_int16 (fp, &header.reserved, 1)
+ || !ico_write_int16 (fp, &header.resource_type, 1)
+ || !ico_write_int16 (fp, &header.icon_count, 1) )
+ {
+ ico_save_info_free (&info);
+ fclose (fp);
+ return GIMP_PDB_EXECUTION_ERROR;
+ }
+
+ entries = g_new0 (IcoFileEntry, info.num_icons);
+ if (fwrite (entries, sizeof (IcoFileEntry), info.num_icons, fp) <= 0)
+ {
+ ico_save_info_free (&info);
+ g_free (entries);
+ fclose (fp);
+ return GIMP_PDB_EXECUTION_ERROR;
+ }
+
+ for (i = 0; i < info.num_icons; i++)
+ {
+ gimp_progress_update ((gdouble)i / (gdouble)info.num_icons);
+
+ width = gimp_drawable_width (info.layers[i]);
+ height = gimp_drawable_height (info.layers[i]);
+ if (width <= 255 && height <= 255)
+ {
+ entries[i].width = width;
+ entries[i].height = height;
+ }
+ else
+ {
+ entries[i].width = 0;
+ entries[i].height = 0;
+ }
+ if ( info.depths[i] <= 8 )
+ entries[i].num_colors = 1 << info.depths[i];
+ else
+ entries[i].num_colors = 0;
+ entries[i].reserved = 0;
+ entries[i].planes = 1;
+ entries[i].bpp = info.depths[i];
+ entries[i].offset = ftell (fp);
+
+ if (info.compress[i])
+ saved = ico_write_png (fp, info.layers[i], info.depths[i]);
+ else
+ saved = ico_write_icon (fp, info.layers[i], info.depths[i]);
+
+ if (!saved)
+ {
+ ico_save_info_free (&info);
+ fclose (fp);
+ return GIMP_PDB_EXECUTION_ERROR;
+ }
+
+ entries[i].size = ftell (fp) - entries[i].offset;
+ }
+
+ for (i = 0; i < info.num_icons; i++)
+ {
+ entries[i].planes = GUINT16_TO_LE (entries[i].planes);
+ entries[i].bpp = GUINT16_TO_LE (entries[i].bpp);
+ entries[i].size = GUINT32_TO_LE (entries[i].size);
+ entries[i].offset = GUINT32_TO_LE (entries[i].offset);
+ }
+
+ if (fseek (fp, sizeof(IcoFileHeader), SEEK_SET) < 0
+ || fwrite (entries, sizeof (IcoFileEntry), info.num_icons, fp) <= 0)
+ {
+ ico_save_info_free (&info);
+ fclose (fp);
+ return GIMP_PDB_EXECUTION_ERROR;
+ }
+
+ gimp_progress_update (1.0);
+
+ ico_save_info_free (&info);
+ fclose (fp);
+ g_free (entries);
+
+ return GIMP_PDB_SUCCESS;
+}