/* 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 . * * 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 . */ #include "config.h" #include #include #include #include #include #include /* #define ICO_DBG */ #include "ico.h" #include "ico-load.h" #include "ico-export.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 (GimpLayer *layer, gboolean *uses_alpha_levels); static void ico_image_get_reduced_buf (GimpDrawable *layer, gint bpp, GimpPalette **cmap_out, guchar **buf_out); static gboolean ico_save_init (GimpImage *image, gint32 run_mode, IcoSaveInfo *info, gint n_hot_spot_x, const gint32 *hot_spot_x, gint n_hot_spot_y, const gint32 *hot_spot_y, GError **error); static GimpPDBStatusType shared_save_image (GFile *file, FILE *fp_ani, GimpImage *image, GimpProcedure *procedure, GimpProcedureConfig *config, gint32 run_mode, gsize *n_hot_spot_x, const gint32 *hot_spot_x, gint32 **new_hot_spot_x, gsize *n_hot_spot_y, const gint32 *hot_spot_y, gint32 **new_hot_spot_y, gint32 file_offset, gint icon_index, GError **error, IcoSaveInfo *info); 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 gboolean ico_save_init (GimpImage *image, gint32 run_mode, IcoSaveInfo *info, gint n_hot_spot_x, const gint32 *hot_spot_x, gint n_hot_spot_y, const gint32 *hot_spot_y, GError **error) { GList *iter; gint num_colors; gint i; gboolean uses_alpha_values = FALSE; info->layers = gimp_image_list_layers (image); info->num_icons = g_list_length (info->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); info->hot_spot_x = g_new0 (gint, info->num_icons); info->hot_spot_y = g_new0 (gint, info->num_icons); if (run_mode == GIMP_RUN_NONINTERACTIVE && info->is_cursor && (n_hot_spot_x != info->num_icons || n_hot_spot_y != info->num_icons)) { /* While it is acceptable for interactive and last values run (in * such case, we just drop the previous values), we expect * non-interactive calls to have exactly the right numbers of * hot-spot values set. */ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Called non-interactively with %d and %d hotspot " "coordinates with an image of %d icons."), n_hot_spot_x, n_hot_spot_y, info->num_icons); return FALSE; } /* Only use existing values if we have exactly the same number of * icons. Drop previous/saved values otherwise. */ if (hot_spot_x && n_hot_spot_x == info->num_icons) { /* XXX This is the limit of our array arguments not self-aware of * their length (the separate args may be "lying" with a wrong * call). We may end up with a segfault if the arg array was not * big enough. There is not much we can really do here. I thought * about g_renew() but it won't initialize to 0 if the arg is * smaller. Also we don't want to free the original array. */ for (iter = info->layers, i = 0; iter; iter = g_list_next (iter), i++) info->hot_spot_x[i] = hot_spot_x[i]; } if (hot_spot_y && n_hot_spot_y == info->num_icons) { for (iter = info->layers, i = 0; iter; iter = g_list_next (iter), i++) info->hot_spot_y[i] = hot_spot_y[i]; } /* 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 (iter = info->layers, i = 0; iter; iter = g_list_next (iter), i++) { num_colors = ico_get_layer_num_colors (iter->data, &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_get_width (iter->data) > 255 || gimp_drawable_get_height (iter->data) > 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); return TRUE; } static gboolean ico_save_dialog (GimpImage *image, GimpProcedure *procedure, GimpProcedureConfig *config, IcoSaveInfo *info, AniFileHeader *ani_header, AniSaveInfo *ani_info) { GtkWidget *dialog; GList *iter; gint i; gboolean response; gimp_ui_init (PLUG_IN_BINARY); dialog = ico_dialog_new (image, procedure, config, info, ani_header, ani_info); for (iter = info->layers, i = 0; iter; iter = g_list_next (iter), i++) { if (info->is_cursor) { GimpParasite *parasite = NULL; /* Loading hot spots for cursors if applicable */ parasite = gimp_item_get_parasite (GIMP_ITEM (iter->data), "cur-hot-spot"); if (parasite) { gchar *parasite_data; guint32 parasite_size; gint x, y; parasite_data = (gchar *) gimp_parasite_get_data (parasite, ¶site_size); parasite_data = g_strndup (parasite_data, parasite_size); if (sscanf (parasite_data, "%i %i", &x, &y) == 2) { info->hot_spot_x[i] = x; info->hot_spot_y[i] = y; } gimp_parasite_free (parasite); g_free (parasite_data); } } /* if (gimp_layer_get_visible(layers[i])) */ ico_dialog_add_icon (dialog, iter->data, 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_set_visible (dialog, TRUE); response = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog)); gtk_widget_destroy (dialog); return response; } 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 (GimpPalette *reduced_cmap, gint num_colors, gint *black_slot) { const Babl *format; guchar *palette; gint num_colors_used = 0; gint i; if (reduced_cmap) num_colors_used = gimp_palette_get_color_count (reduced_cmap); g_return_val_if_fail (num_colors_used <= num_colors, NULL); palette = g_new0 (guchar, num_colors * 4); *black_slot = -1; format = babl_format ("R'G'B' u8"); for (i = 0; i < num_colors_used; i++) { GeglColor *color; guchar rgb[3]; color = gimp_palette_get_entry_color (reduced_cmap, i); gegl_color_get_pixel (color, format, rgb); palette[i * 4 + 2] = rgb[0]; palette[i * 4 + 1] = rgb[1]; palette[i * 4] = rgb[2]; if ((rgb[0] == 0) && (rgb[1] == 0) && (rgb[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 (GimpLayer *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 (GIMP_DRAWABLE (layer)); const Babl *format; w = gegl_buffer_get_width (buffer); h = gegl_buffer_get_height (buffer); num_pixels = w * h; switch (gimp_drawable_type (GIMP_DRAWABLE (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_palette(), 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); /* Icons with 1 bit masks are converted to * full transparency when loaded in GIMP. * If the layer is not semi-transparent, we * can subtract the "mask color" out to fix * the default depth value for 1/4/8 bpp icons. */ if (! *uses_alpha_levels) num_colors--; g_hash_table_destroy (hash); g_free (colors); g_free (buf); return num_colors; } gboolean ico_cmap_contains_black (GimpPalette *cmap) { const Babl *format; gint num_colors; gint i; gboolean success = FALSE; format = babl_format ("R'G'B' u8"); num_colors = gimp_palette_get_color_count (cmap); for (i = 0; i < num_colors; i++) { GeglColor *color = gimp_palette_get_entry_color (cmap, i); guchar rgb[3]; gegl_color_get_pixel (color, format, rgb); if (rgb[0] == 0 && rgb[1] == 0 && rgb[2] == 0) { g_object_unref (color); success = TRUE; break; } } return success; } static void ico_image_get_reduced_buf (GimpDrawable *layer, gint bpp, GimpPalette **cmap_out, guchar **buf_out) { GimpImage *tmp_image; GimpLayer *tmp_layer; gint w, h; guchar *buf; GimpPalette *palette = 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 (); } buf = g_new (guchar, w * h * 4); if (bpp <= 8 || bpp == 24 || babl_format_get_bytes_per_pixel (format) != 4) { GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer)); GeglBuffer *tmp; gint num_colors; gint entry_num; GeglColor **colors; tmp_image = gimp_image_new (w, h, gimp_image_get_base_type (image)); gimp_image_undo_disable (tmp_image); if (gimp_drawable_is_indexed (layer)) gimp_image_set_palette (tmp_image, gimp_image_get_palette (image)); 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, NULL, 0); tmp = gimp_drawable_get_buffer (GIMP_DRAWABLE (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 (GIMP_DRAWABLE (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"); palette = gimp_image_get_palette (tmp_image); if (gimp_palette_get_color_count (palette) == (1 << bpp) && ! ico_cmap_contains_black (palette)) { /* 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)) { gimp_image_set_palette (tmp_image, gimp_image_get_palette (image)); } 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 (GIMP_DRAWABLE (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< 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 (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_list_free (info->layers); g_free (info->hot_spot_x); g_free (info->hot_spot_y); memset (info, 0, sizeof (IcoSaveInfo)); } GimpPDBStatusType ico_export_image (GFile *file, GimpImage *image, GimpProcedure *procedure, GimpProcedureConfig *config, gint32 run_mode, GError **error) { IcoSaveInfo info; D(("*** Exporting Microsoft icon file %s\n", gimp_file_get_utf8_name (file))); info.is_cursor = FALSE; return shared_save_image (file, NULL, image, procedure, config, run_mode, 0, NULL, NULL, 0, NULL, NULL, 0, 0, error, &info); } GimpPDBStatusType cur_export_image (GFile *file, GimpImage *image, GimpProcedure *procedure, GimpProcedureConfig *config, gint32 run_mode, gsize *n_hot_spot_x, const gint32 *hot_spot_x, gint32 **new_hot_spot_x, gsize *n_hot_spot_y, const gint32 *hot_spot_y, gint32 **new_hot_spot_y, GError **error) { IcoSaveInfo info; D(("*** Exporting Microsoft cursor file %s\n", gimp_file_get_utf8_name (file))); info.is_cursor = TRUE; return shared_save_image (file, NULL, image, procedure, config, run_mode, n_hot_spot_x, hot_spot_x, new_hot_spot_x, n_hot_spot_y, hot_spot_y, new_hot_spot_y, 0, 0, error, &info); } /* Ported from James Huang's ani.c code, under the GPL v3 license */ GimpPDBStatusType ani_export_image (GFile *file, GimpImage *image, GimpProcedure *procedure, GimpProcedureConfig *config, gint32 run_mode, gsize *n_hot_spot_x, const gint32 *hot_spot_x, gint32 **new_hot_spot_x, gsize *n_hot_spot_y, const gint32 *hot_spot_y, gint32 **new_hot_spot_y, AniFileHeader *header, AniSaveInfo *ani_info, GError **error) { FILE *fp; gint32 i; gchar *str; GimpParasite *parasite = NULL; gchar id[5]; guint32 size; guint8 padding = 0; gint32 offset, ofs_size_riff, ofs_size_list, ofs_size_icon; gint32 ofs_size_info = 0; gint32 ofs_metadata = 0; IcoSaveInfo info; if (! ico_save_init (image, run_mode, &info, *n_hot_spot_x, hot_spot_x, *n_hot_spot_y, hot_spot_y, error)) { return GIMP_PDB_EXECUTION_ERROR; } /* Save individual frames as .cur so we can retain * the hotspot information */ info.is_cursor = TRUE; /* Default header values */ header->bSizeOf = sizeof (*header); header->frames = info.num_icons; header->steps = info.num_icons; header->x = 0; header->y = 0; if (info.depths[0] == 24) { header->bpp = 4; header->planes = 1; } else { header->bpp = 0; header->planes = 0; } header->flags = 1; /* Load metadata from parasite */ parasite = gimp_image_get_parasite (image, "ani-header"); if (parasite) { gchar *parasite_data; guint32 parasite_size; gint jif_rate; parasite_data = (gchar *) gimp_parasite_get_data (parasite, ¶site_size); parasite_data = g_strndup (parasite_data, parasite_size); if (sscanf (parasite_data, "%i", &jif_rate) == 1) { header->jif_rate = jif_rate; } gimp_parasite_free (parasite); g_free (parasite_data); } parasite = gimp_image_get_parasite (image, "ani-info-inam"); if (parasite) { guint32 parasite_size; gchar *inam = NULL; inam = (gchar *) gimp_parasite_get_data (parasite, ¶site_size); ani_info->inam = g_strndup (inam, parasite_size); gimp_parasite_free (parasite); } parasite = gimp_image_get_parasite (image, "ani-info-iart"); if (parasite) { guint32 parasite_size; gchar *iart = NULL; iart = (gchar *) gimp_parasite_get_data (parasite, ¶site_size); ani_info->iart = g_strndup (iart, parasite_size); gimp_parasite_free (parasite); } if (run_mode == GIMP_RUN_INTERACTIVE) { if (! ico_save_dialog (image, procedure, config, &info, header, ani_info)) return GIMP_PDB_CANCEL; for (i = 1; i < info.num_icons; i++) { info.depths[i] = info.depths[0]; info.default_depths[i] = info.default_depths[0]; info.compress[i] = info.compress[0]; } } gimp_progress_init_printf (_("Exporting '%s'"), gimp_file_get_utf8_name (file)); fp = g_fopen (g_file_peek_path (file), "wb"); if (! fp) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for writing: %s"), gimp_file_get_utf8_name (file), g_strerror (errno)); return GIMP_PDB_EXECUTION_ERROR; } /* Writing the .ani header data */ strcpy (id, "RIFF"); size = 0; fwrite (id, 4, 1, fp); ofs_size_riff = ftell (fp); fwrite (&size, sizeof (size), 1, fp); strcpy (id, "ACON"); fwrite (id, 4, 1, fp); if ((ani_info->inam && strlen (ani_info->inam) > 0) || (ani_info->iart && strlen (ani_info->iart) > 0)) { gint32 string_size; strcpy (id, "LIST"); fwrite (id, 4, 1, fp); ofs_size_info = ftell (fp); fwrite (&size, sizeof (size), 1, fp); strcpy (id, "INFO"); fwrite (id, 4, 1, fp); if (ani_info->inam && strlen (ani_info->inam) > 0) /* Cursor name */ { strcpy (id, "INAM"); fwrite (id, 4, 1, fp); string_size = strlen (ani_info->inam) + 1; fwrite (&string_size, 4, 1, fp); fwrite (ani_info->inam, string_size, 1, fp); ofs_metadata += 4; /* Length of metadata must be even. */ if (string_size % 2 != 0) fwrite (&padding, sizeof (padding), 1, fp); } if (ani_info->iart && strlen (ani_info->iart) > 0) /* Author name */ { strcpy (id, "IART"); fwrite (id, 4, 1, fp); string_size = strlen (ani_info->iart) + 1; fwrite (&string_size, 4, 1, fp); fwrite (ani_info->iart, string_size, 1, fp); ofs_metadata += 4; if (string_size % 2 != 0) fwrite (&padding, sizeof (padding), 1, fp); } /* Go back and update info list size */ fseek (fp, 0L, SEEK_END); size = ftell (fp) - ofs_size_info - 4; fseek (fp, ofs_size_info, SEEK_SET); fwrite (&size, sizeof (size), 1, fp); fseek (fp, 0L, SEEK_END); } strcpy (id, "anih"); size = sizeof (*header); fwrite (id, 4, 1, fp); fwrite (&size, sizeof (size), 1, fp); fwrite (header, sizeof (*header), 1, fp); strcpy (id, "LIST"); fwrite (id, 4, 1, fp); ofs_size_list = ftell (fp); fwrite (&size, sizeof (size), 1, fp); strcpy (id, "fram"); fwrite (id, 4, 1, fp); strcpy (id, "icon"); for (i = 0; i < info.num_icons; i++ ) { GimpPDBStatusType status; fwrite (id, 4, 1, fp); ofs_size_icon = ftell (fp); fwrite (&size, sizeof (size), 1, fp); offset = ftell (fp); status = shared_save_image (file, fp, image, procedure, config, run_mode, n_hot_spot_x, hot_spot_x, new_hot_spot_x, n_hot_spot_y, hot_spot_y, new_hot_spot_y, offset, i, error, &info); if (status != GIMP_PDB_SUCCESS) { ico_save_info_free (&info); g_free (ani_info->inam); g_free (ani_info->iart); fclose (fp); return GIMP_PDB_EXECUTION_ERROR; } fseek (fp, 0L, SEEK_END); size = ftell (fp) - offset; fseek (fp, ofs_size_icon, SEEK_SET); fwrite (&size, sizeof (size), 1, fp); fseek (fp, 0L, SEEK_END); gimp_progress_update ((gdouble) i / (gdouble) info.num_icons); } ico_save_info_free (&info); fseek (fp, 0L, SEEK_END); size = ftell (fp); size -= ofs_metadata; fseek (fp, ofs_size_riff, SEEK_SET); fwrite (&size, sizeof (size), 1, fp); size -= ofs_size_list; size += (ofs_metadata - 4); fseek (fp, ofs_size_list, SEEK_SET); fwrite (&size, sizeof (size), 1, fp); fclose (fp); /* Update metadata if needed */ str = g_strdup_printf ("%d", header->jif_rate); parasite = gimp_parasite_new ("ani-header", GIMP_PARASITE_PERSISTENT, strlen (str) + 1, (gpointer) str); g_free (str); gimp_image_attach_parasite (image, parasite); gimp_parasite_free (parasite); if (ani_info->inam && strlen (ani_info->inam) > 0) { str = g_strdup_printf ("%s", ani_info->inam); parasite = gimp_parasite_new ("ani-info-inam", GIMP_PARASITE_PERSISTENT, strlen (ani_info->inam) + 1, (gpointer) str); g_free (str); gimp_image_attach_parasite (image, parasite); gimp_parasite_free (parasite); } if (ani_info->iart && strlen (ani_info->iart) > 0) { str = g_strdup_printf ("%s", ani_info->iart); parasite = gimp_parasite_new ("ani-info-iart", GIMP_PARASITE_PERSISTENT, strlen (ani_info->iart) + 1, (gpointer) str); g_free (str); gimp_image_attach_parasite (image, parasite); gimp_parasite_free (parasite); } gimp_progress_update (1.0); return GIMP_PDB_SUCCESS; } GimpPDBStatusType shared_save_image (GFile *file, FILE *fp_ani, GimpImage *image, GimpProcedure *procedure, GimpProcedureConfig *config, gint32 run_mode, gsize *n_hot_spot_x, const gint32 *hot_spot_x, gint32 **new_hot_spot_x, gsize *n_hot_spot_y, const gint32 *hot_spot_y, gint32 **new_hot_spot_y, gint32 file_offset, gint icon_index, GError **error, IcoSaveInfo *info) { FILE *fp; GList *iter; gint width; gint height; IcoFileHeader header; IcoFileEntry *entries; gboolean saved; gint i; gint num_icons; GimpParasite *parasite = NULL; gchar *str; if (! fp_ani && ! ico_save_init (image, run_mode, info, n_hot_spot_x ? *n_hot_spot_x : 0, hot_spot_x, n_hot_spot_y ? *n_hot_spot_y : 0, hot_spot_y, error)) { return GIMP_PDB_EXECUTION_ERROR; } if (run_mode == GIMP_RUN_INTERACTIVE && ! fp_ani) { /* Allow user to override default values */ if (! ico_save_dialog (image, procedure, config, info, NULL, NULL)) return GIMP_PDB_CANCEL; } num_icons = (fp_ani) ? 1 : info->num_icons; if (! fp_ani) gimp_progress_init_printf (_("Exporting '%s'"), gimp_file_get_utf8_name (file)); /* If saving an .ani file, we append the next icon frame. */ if (! fp_ani) { fp = g_fopen (g_file_peek_path (file), "wb"); } else { fp = fp_ani; } if (! fp) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for writing: %s"), gimp_file_get_utf8_name (file), g_strerror (errno)); return GIMP_PDB_EXECUTION_ERROR; } header.reserved = 0; header.resource_type = 1; if (info->is_cursor) header.resource_type = 2; header.icon_count = 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, num_icons); if (fwrite (entries, sizeof (IcoFileEntry), num_icons, fp) <= 0) { ico_save_info_free (info); g_free (entries); fclose (fp); return GIMP_PDB_EXECUTION_ERROR; } for (iter = info->layers, i = 0; iter; iter = g_list_next (iter), i++) { if (! fp_ani) gimp_progress_update ((gdouble)i / (gdouble)info->num_icons); /* If saving .ani file, jump to the correct frame */ if (fp_ani) iter = g_list_nth (info->layers, icon_index); width = gimp_drawable_get_width (iter->data); height = gimp_drawable_get_height (iter->data); 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]; /* .cur file reuses these fields for cursor offsets */ if (info->is_cursor) { gint hot_spot_index = icon_index ? icon_index : i; entries[i].planes = info->hot_spot_x[hot_spot_index]; entries[i].bpp = info->hot_spot_y[hot_spot_index]; } entries[i].offset = ftell (fp) - file_offset; if (info->compress[i]) saved = ico_write_png (fp, iter->data, info->depths[i]); else saved = ico_write_icon (fp, iter->data, info->depths[i]); if (!saved) { ico_save_info_free (info); fclose (fp); return GIMP_PDB_EXECUTION_ERROR; } entries[i].size = ftell (fp) - file_offset - entries[i].offset; if (fp_ani) break; } for (i = 0; i < 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) + file_offset, SEEK_SET) < 0 || fwrite (entries, sizeof (IcoFileEntry), num_icons, fp) <= 0) { ico_save_info_free (info); fclose (fp); return GIMP_PDB_EXECUTION_ERROR; } if (! fp_ani) gimp_progress_update (1.0); /* Updating parasite hot spots if needed */ if (info->is_cursor) { for (iter = info->layers, i = 0; iter; iter = g_list_next (iter), i++) { str = g_strdup_printf ("%d %d", info->hot_spot_x[i], info->hot_spot_y[i]); parasite = gimp_parasite_new ("cur-hot-spot", GIMP_PARASITE_PERSISTENT, strlen (str) + 1, (gpointer) str); g_free (str); gimp_item_attach_parasite (GIMP_ITEM (iter->data), parasite); gimp_parasite_free (parasite); } } if (! fp_ani) { if (new_hot_spot_x) { *new_hot_spot_x = info->hot_spot_x; info->hot_spot_x = NULL; *n_hot_spot_x = num_icons; } if (new_hot_spot_y) { *new_hot_spot_y = info->hot_spot_y; info->hot_spot_y = NULL; *n_hot_spot_y = num_icons; } } /* If saving .ani file, don't clear until * all icons are saved in ani_save_image () */ if (! file_offset) { ico_save_info_free (info); fclose (fp); } g_free (entries); return GIMP_PDB_SUCCESS; }