plug-ins: Add .ani file import/export

This commit is contained in:
Alx Sa 2022-08-08 18:31:45 +00:00
parent e7faae1dc3
commit aa51b9e19e
8 changed files with 1001 additions and 83 deletions

View File

@ -38,10 +38,16 @@ static void ico_dialog_toggle_compress (GtkWidget *checkbox,
GObject *hbox);
static void ico_dialog_check_compat (GtkWidget *dialog,
IcoSaveInfo *info);
static void ico_dialog_ani_update_inam (GtkEntry *entry,
gpointer data);
static void ico_dialog_ani_update_iart (GtkEntry *entry,
gpointer data);
GtkWidget *
ico_dialog_new (IcoSaveInfo *info)
ico_dialog_new (IcoSaveInfo *info,
AniFileHeader *ani_header,
AniSaveInfo *ani_info)
{
GtkWidget *dialog;
GtkWidget *main_vbox;
@ -51,7 +57,8 @@ ico_dialog_new (IcoSaveInfo *info)
GtkWidget *viewport;
GtkWidget *warning;
dialog = gimp_export_dialog_new (info->is_cursor ?
dialog = gimp_export_dialog_new (ani_header ?
_("Windows Animated Cursor") : info->is_cursor ?
_("Windows Cursor") : _("Windows Icon"),
PLUG_IN_BINARY,
"plug-in-winicon");
@ -65,6 +72,11 @@ ico_dialog_new (IcoSaveInfo *info)
*/
g_object_set_data (G_OBJECT (dialog), "save_info", info);
if (ani_header)
{
g_object_set_data (G_OBJECT (dialog), "save_ani_header", ani_header);
g_object_set_data (G_OBJECT (dialog), "save_ani_info", ani_info);
}
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
@ -72,6 +84,82 @@ ico_dialog_new (IcoSaveInfo *info)
main_vbox, TRUE, TRUE, 0);
gtk_widget_show (main_vbox);
/*Animated Cursor */
if (ani_header)
{
GtkWidget *grid;
GtkAdjustment *adjustment;
GtkWidget *spin;
GtkWidget *label;
GtkWidget *hbox;
GtkWidget *entry;
frame = gimp_frame_new (_("Animated Cursor Settings"));
gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
grid = gtk_grid_new ();
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_container_add (GTK_CONTAINER (frame), grid);
gtk_widget_show (grid);
/* Cursor Name */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
_("_Cursor Name (Optional)"),
0.0, 0.5,
hbox, 1);
entry = gtk_entry_new ();
gtk_entry_set_text (GTK_ENTRY (entry),
ani_info->inam ? ani_info->inam : "");
gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
gtk_widget_show (entry);
g_signal_connect (GTK_ENTRY (entry), "focus-out-event",
G_CALLBACK (ico_dialog_ani_update_inam),
NULL);
/* Author Name */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 3,
_("_Author Name (Optional)"),
0.0, 0.5,
hbox, 1);
entry = gtk_entry_new ();
gtk_entry_set_text (GTK_ENTRY (entry),
ani_info->iart ? ani_info->iart : "");
gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
gtk_widget_show (entry);
g_signal_connect (GTK_ENTRY (entry), "focus-out-event",
G_CALLBACK (ico_dialog_ani_update_iart),
NULL);
/* Default delay spin */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 5,
_("_Delay between frames:"),
0.0, 0.5,
hbox, 1);
adjustment = gtk_adjustment_new (ani_header->jif_rate, 1, G_MAXINT,
1, 10, 0);
spin = gimp_spin_button_new (adjustment, 1, 0);
gtk_box_pack_start (GTK_BOX (hbox), spin, FALSE, FALSE, 0);
gtk_widget_show (spin);
label = gtk_label_new (_(" jiffies (16.66 ms)"));
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&ani_header->jif_rate);
}
/* Cursor */
frame = gimp_frame_new (_("Icon Details"));
gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 4);
@ -572,3 +660,29 @@ ico_dialog_check_compat (GtkWidget *dialog,
gtk_widget_set_visible (warning, warn);
}
static void
ico_dialog_ani_update_inam (GtkEntry *entry,
gpointer data)
{
AniSaveInfo *ani_info;
GtkWidget *dialog;
dialog = gtk_widget_get_toplevel (GTK_WIDGET (entry));
ani_info = g_object_get_data (G_OBJECT (dialog), "save_ani_info");
ani_info->inam = g_strdup_printf ("%s", gtk_entry_get_text (entry));
}
static void
ico_dialog_ani_update_iart (GtkEntry *entry,
gpointer data)
{
AniSaveInfo *ani_info;
GtkWidget *dialog;
dialog = gtk_widget_get_toplevel (GTK_WIDGET (entry));
ani_info = g_object_get_data (G_OBJECT (dialog), "save_ani_info");
ani_info->iart = g_strdup_printf ("%s", gtk_entry_get_text (entry));
}

View File

@ -22,9 +22,11 @@
#define __ICO_DIALOG_H__
GtkWidget * ico_dialog_new (IcoSaveInfo *info);
void ico_dialog_add_icon (GtkWidget *dialog,
GimpDrawable *layer,
gint layer_num);
GtkWidget * ico_dialog_new (IcoSaveInfo *info,
AniFileHeader *ani_header,
AniSaveInfo *ani_info);
void ico_dialog_add_icon (GtkWidget *dialog,
GimpDrawable *layer,
gint layer_num);
#endif /* __ICO_DIALOG_H__ */

View File

@ -142,6 +142,7 @@ ico_read_init (FILE *fp)
static gboolean
ico_read_size (FILE *fp,
gint32 file_offset,
IcoLoadInfo *info)
{
png_structp png_ptr;
@ -151,7 +152,7 @@ ico_read_size (FILE *fp,
gint32 color_type;
guint32 magic;
if (fseek (fp, info->offset, SEEK_SET) < 0)
if (fseek (fp, info->offset + file_offset, SEEK_SET) < 0)
return FALSE;
ico_read_int32 (fp, &magic, 1);
@ -208,6 +209,7 @@ ico_read_size (FILE *fp,
static IcoLoadInfo*
ico_read_info (FILE *fp,
gint icon_count,
gint32 file_offset,
GError **error)
{
gint i;
@ -237,7 +239,7 @@ ico_read_info (FILE *fp,
if (info[i].width == 0 || info[i].height == 0)
{
ico_read_size (fp, info + i);
ico_read_size (fp, file_offset, info + i);
}
D(("ico_read_info: %ix%i (%i bits, size: %i, offset: %i)\n",
@ -605,6 +607,7 @@ ico_load_layer (FILE *fp,
gint32 icon_num,
guchar *buf,
gint maxsize,
gint32 file_offset,
IcoLoadInfo *info)
{
gint width, height;
@ -613,7 +616,7 @@ ico_load_layer (FILE *fp,
GeglBuffer *buffer;
gchar name[ICO_MAXBUF];
if (fseek (fp, info->offset, SEEK_SET) < 0 ||
if (fseek (fp, info->offset + file_offset, SEEK_SET) < 0 ||
! ico_read_int32 (fp, &first_bytes, 1))
return NULL;
@ -653,6 +656,7 @@ ico_load_layer (FILE *fp,
GimpImage *
ico_load_image (GFile *file,
gint32 *file_offset,
GError **error)
{
FILE *fp;
@ -666,8 +670,9 @@ ico_load_image (GFile *file,
gint maxsize;
gchar *str;
gimp_progress_init_printf (_("Opening '%s'"),
gimp_file_get_utf8_name (file));
if (! file_offset)
gimp_progress_init_printf (_("Opening '%s'"),
gimp_file_get_utf8_name (file));
fp = g_fopen (g_file_peek_path (file), "rb");
@ -679,6 +684,9 @@ ico_load_image (GFile *file,
return NULL;
}
if (file_offset)
fseek (fp, *file_offset, SEEK_SET);
header = ico_read_init (fp);
icon_count = header.icon_count;
if (!icon_count)
@ -687,7 +695,7 @@ ico_load_image (GFile *file,
return NULL;
}
info = ico_read_info (fp, icon_count, error);
info = ico_read_info (fp, icon_count, file_offset ? *file_offset : 0, error);
if (! info)
{
fclose (fp);
@ -713,7 +721,8 @@ ico_load_image (GFile *file,
D(("image size: %ix%i\n", max_width, max_height));
image = gimp_image_new (max_width, max_height, GIMP_RGB);
gimp_image_set_file (image, file);
if (! file_offset)
gimp_image_set_file (image, file);
maxsize = max_width * max_height * 4;
buf = g_new (guchar, max_width * max_height * 4);
@ -721,7 +730,7 @@ ico_load_image (GFile *file,
{
GimpLayer *layer;
layer = ico_load_layer (fp, image, i, buf, maxsize, info+i);
layer = ico_load_layer (fp, image, i, buf, maxsize, file_offset ? *file_offset : 0, info+i);
/* Save CUR hot spot information */
if (header.resource_type == 2)
@ -737,10 +746,172 @@ ico_load_image (GFile *file,
gimp_parasite_free (parasite);
}
}
if (file_offset)
*file_offset = ftell (fp);
g_free (buf);
g_free (info);
fclose (fp);
/* Don't update progress here if .ani file */
if (! file_offset)
gimp_progress_update (1.0);
return image;
}
/* Ported from James Huang's ani.c code, under the GPL license, version 3
* or any later version of the license */
GimpImage *
ani_load_image (GFile *file,
gboolean load_thumb,
gint *width,
gint *height,
GError **error)
{
FILE *fp;
GimpImage *image = NULL;
GimpParasite *parasite;
gchar id[4];
guint32 size;
gint32 file_offset;
gint frame = 1;
AniFileHeader header;
gchar inam[G_MAXSHORT] = {0};
gchar iart[G_MAXSHORT] = {0};
gchar *str;
gimp_progress_init_printf (_("Opening '%s'"),
gimp_file_get_utf8_name (file));
fp = g_fopen (g_file_peek_path (file), "rb");
if (! fp)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not open '%s' for reading: %s"),
gimp_file_get_utf8_name (file), g_strerror (errno));
return NULL;
}
while (fread (id, 1, 4, fp) == 4)
{
if (memcmp (id, "RIFF", 4) == 0 )
{
fread (&size, sizeof (size), 1, fp);
}
else if (memcmp (id, "anih", 4) == 0)
{
fread (&size, sizeof (size), 1, fp);
fread (&header, sizeof (header), 1, fp);
}
else if (memcmp (id, "rate", 4) == 0)
{
fread (&size, sizeof (size), 1, fp);
fseek (fp, size, SEEK_CUR);
}
else if (memcmp (id, "seq ", 4) == 0)
{
fread (&size, sizeof (size), 1, fp);
fseek (fp, size, SEEK_CUR);
}
else if (memcmp (id, "LIST", 4) == 0)
{
fread (&size, sizeof (size), 1, fp);
}
else if (memcmp (id, "INAM", 4) == 0)
{
fread (&size, sizeof (size), 1, fp);
fread (&inam, sizeof (char), size, fp);
inam[size] = '\0';
}
else if (memcmp (id, "IART", 4) == 0)
{
fread (&size, sizeof (size), 1, fp);
fread (&iart, sizeof (char), size, fp);
iart[size] = '\0';
}
else if (memcmp (id, "icon", 4) == 0)
{
fread (&size, sizeof (size), 1, fp);
file_offset = ftell (fp);
if (load_thumb)
{
image = ico_load_thumbnail_image (file, width, height, file_offset, error);
break;
}
else
{
if (! image)
{
image = ico_load_image (file, &file_offset, error);
}
else
{
GimpImage *temp_image = NULL;
GimpLayer **layers;
GimpLayer *new_layer;
gint nlayers;
temp_image = ico_load_image (file, &file_offset, error);
layers = gimp_image_get_layers (temp_image, &nlayers);
if (layers)
{
for (gint i = 0; i < nlayers; i++)
{
new_layer = gimp_layer_new_from_drawable (GIMP_DRAWABLE (layers[i]),
image);
gimp_image_insert_layer (image, new_layer, NULL, frame);
frame++;
}
}
gimp_image_delete (temp_image);
}
/* Update position after reading icon data */
fseek (fp, file_offset, SEEK_SET);
if (header.frames > 0)
gimp_progress_update ((gdouble) frame /
(gdouble) header.frames);
}
}
}
fclose (fp);
/* Saving header metadata */
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);
/* Saving INFO block */
if (strlen (inam) > 0)
{
str = g_strdup_printf ("%s", inam);
parasite = gimp_parasite_new ("ani-info-inam",
GIMP_PARASITE_PERSISTENT,
strlen (str) + 1, (gpointer) str);
g_free (str);
gimp_image_attach_parasite (image, parasite);
gimp_parasite_free (parasite);
}
if (strlen (iart) > 0)
{
str = g_strdup_printf ("%s", iart);
parasite = gimp_parasite_new ("ani-info-iart",
GIMP_PARASITE_PERSISTENT,
strlen (str) + 1, (gpointer) str);
g_free (str);
gimp_image_attach_parasite (image, parasite);
gimp_parasite_free (parasite);
}
gimp_image_set_file (image, file);
gimp_progress_update (1.0);
return image;
@ -750,6 +921,7 @@ GimpImage *
ico_load_thumbnail_image (GFile *file,
gint *width,
gint *height,
gint32 file_offset,
GError **error)
{
FILE *fp;
@ -776,6 +948,9 @@ ico_load_thumbnail_image (GFile *file,
return NULL;
}
if (file_offset > 0)
fseek (fp, file_offset, SEEK_SET);
header = ico_read_init (fp);
icon_count = header.icon_count;
if (! icon_count)
@ -787,7 +962,7 @@ ico_load_thumbnail_image (GFile *file,
D(("*** %s: Microsoft icon file, containing %i icon(s)\n",
gimp_file_get_utf8_name (file), icon_count));
info = ico_read_info (fp, icon_count, error);
info = ico_read_info (fp, icon_count, file_offset, error);
if (! info)
{
fclose (fp);
@ -821,7 +996,7 @@ ico_load_thumbnail_image (GFile *file,
image = gimp_image_new (w, h, GIMP_RGB);
buf = g_new (guchar, w*h*4);
ico_load_layer (fp, image, match, buf, w*h*4, info+match);
ico_load_layer (fp, image, match, buf, w*h*4, file_offset, info+match);
g_free (buf);
*width = w;

View File

@ -23,10 +23,17 @@
GimpImage * ico_load_image (GFile *file,
gint32 *file_offset,
GError **error);
GimpImage * ani_load_image (GFile *file,
gboolean load_thumb,
gint *width,
gint *height,
GError **error);
GimpImage * ico_load_thumbnail_image (GFile *file,
gint *width,
gint *height,
gint32 file_offset,
GError **error);
gint ico_get_bit_from_data (const guint8 *data,

View File

@ -82,12 +82,15 @@ static gboolean ico_save_init (GimpImage *image,
GError **error);
static GimpPDBStatusType
shared_save_image (GFile *file,
FILE *fp_ani,
GimpImage *image,
gint32 run_mode,
gint *n_hot_spot_x,
gint32 **hot_spot_x,
gint *n_hot_spot_y,
gint32 **hot_spot_y,
gint32 file_offset,
gint icon_index,
GError **error,
IcoSaveInfo *info);
@ -302,8 +305,10 @@ ico_save_init (GimpImage *image,
static gboolean
ico_save_dialog (GimpImage *image,
IcoSaveInfo *info)
ico_save_dialog (GimpImage *image,
IcoSaveInfo *info,
AniFileHeader *ani_header,
AniSaveInfo *ani_info)
{
GtkWidget *dialog;
GList *iter;
@ -312,7 +317,7 @@ ico_save_dialog (GimpImage *image,
gimp_ui_init (PLUG_IN_BINARY);
dialog = ico_dialog_new (info);
dialog = ico_dialog_new (info, ani_header, ani_info);
for (iter = info->layers, i = 0;
iter;
iter = g_list_next (iter), i++)
@ -1174,9 +1179,9 @@ ico_save_image (GFile *file,
info.is_cursor = FALSE;
return shared_save_image (file, image, run_mode,
return shared_save_image (file, NULL, image, run_mode,
0, NULL, 0, NULL,
error, &info);
0, 0, error, &info);
}
GimpPDBStatusType
@ -1196,49 +1201,122 @@ cur_save_image (GFile *file,
info.is_cursor = TRUE;
return shared_save_image (file, image, run_mode,
return shared_save_image (file, NULL, image, run_mode,
n_hot_spot_x, hot_spot_x,
n_hot_spot_y, hot_spot_y,
error, &info);
0, 0, error, &info);
}
/* Ported from James Huang's ani.c code, under the GPL v3 license */
GimpPDBStatusType
shared_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
gint *n_hot_spot_x,
gint32 **hot_spot_x,
gint *n_hot_spot_y,
gint32 **hot_spot_y,
GError **error,
IcoSaveInfo *info)
ani_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
gint *n_hot_spot_x,
gint32 **hot_spot_x,
gint *n_hot_spot_y,
gint32 **hot_spot_y,
AniFileHeader *header,
AniSaveInfo *ani_info,
GError **error)
{
FILE *fp;
GList *iter;
gint width;
gint height;
IcoFileHeader header;
IcoFileEntry *entries;
gboolean saved;
gint i;
GimpParasite *parasite = NULL;
gchar *str;
FILE *fp;
gint32 i;
gchar *str;
GimpParasite *parasite = NULL;
gchar id[5];
guint32 size;
gint32 offset, ofs_size_riff, ofs_size_list, ofs_size_icon;
gint32 ofs_size_info = 0;
IcoSaveInfo info;
if (! ico_save_init (image, run_mode, info,
n_hot_spot_x ? *n_hot_spot_x : 0,
hot_spot_x ? *hot_spot_x : NULL,
n_hot_spot_y ? *n_hot_spot_y : 0,
hot_spot_y ? *hot_spot_y : NULL,
error))
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, &parasite_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, &parasite_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, &parasite_size);
ani_info->iart = g_strndup (iart, parasite_size);
gimp_parasite_free (parasite);
}
if (run_mode == GIMP_RUN_INTERACTIVE)
{
/* Allow user to override default values */
if ( !ico_save_dialog (image, info))
if (! ico_save_dialog (image, &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'"),
@ -1246,6 +1324,212 @@ shared_save_image (GFile *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);
}
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);
}
/* 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, run_mode,
n_hot_spot_x, hot_spot_x,
n_hot_spot_y, 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);
fseek (fp, ofs_size_riff, SEEK_SET);
fwrite (&size, sizeof (size), 1, fp);
size -= ofs_size_list;
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,
gint32 run_mode,
gint *n_hot_spot_x,
gint32 **hot_spot_x,
gint *n_hot_spot_y,
gint32 **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 ? *hot_spot_x : NULL,
n_hot_spot_y ? *n_hot_spot_y : 0,
hot_spot_y ? *hot_spot_y : NULL,
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, 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),
@ -1258,7 +1542,7 @@ shared_save_image (GFile *file,
header.resource_type = 1;
if (info->is_cursor)
header.resource_type = 2;
header.icon_count = info->num_icons;
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))
@ -1268,8 +1552,8 @@ shared_save_image (GFile *file,
return GIMP_PDB_EXECUTION_ERROR;
}
entries = g_new0 (IcoFileEntry, info->num_icons);
if (fwrite (entries, sizeof (IcoFileEntry), info->num_icons, fp) <= 0)
entries = g_new0 (IcoFileEntry, num_icons);
if (fwrite (entries, sizeof (IcoFileEntry), num_icons, fp) <= 0)
{
ico_save_info_free (info);
g_free (entries);
@ -1281,7 +1565,12 @@ shared_save_image (GFile *file,
iter;
iter = g_list_next (iter), i++)
{
gimp_progress_update ((gdouble)i / (gdouble)info->num_icons);
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);
@ -1305,10 +1594,12 @@ shared_save_image (GFile *file,
/* .cur file reuses these fields for cursor offsets */
if (info->is_cursor)
{
entries[i].planes = info->hot_spot_x[i];
entries[i].bpp = info->hot_spot_y[i];
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);
entries[i].offset = ftell (fp) - file_offset;
if (info->compress[i])
saved = ico_write_png (fp, iter->data, info->depths[i]);
@ -1322,10 +1613,13 @@ shared_save_image (GFile *file,
return GIMP_PDB_EXECUTION_ERROR;
}
entries[i].size = ftell (fp) - entries[i].offset;
entries[i].size = ftell (fp) - file_offset - entries[i].offset;
if (fp_ani)
break;
}
for (i = 0; i < info->num_icons; i++)
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);
@ -1333,15 +1627,16 @@ shared_save_image (GFile *file,
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)
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;
}
gimp_progress_update (1.0);
if (! fp_ani)
gimp_progress_update (1.0);
/* Updating parasite hot spots if needed */
if (info->is_cursor)
@ -1360,23 +1655,32 @@ shared_save_image (GFile *file,
}
}
if (hot_spot_x)
if (! fp_ani)
{
*hot_spot_x = info->hot_spot_x;
info->hot_spot_x = NULL;
if (hot_spot_x)
{
*hot_spot_x = info->hot_spot_x;
info->hot_spot_x = NULL;
}
if (hot_spot_y)
{
*hot_spot_y = info->hot_spot_y;
info->hot_spot_y = NULL;
}
if (n_hot_spot_x)
*n_hot_spot_x = num_icons;
if (n_hot_spot_y)
*n_hot_spot_y = num_icons;
}
if (hot_spot_y)
{
*hot_spot_y = info->hot_spot_y;
info->hot_spot_y = NULL;
}
if (n_hot_spot_x)
*n_hot_spot_x = info->num_icons;
if (n_hot_spot_y)
*n_hot_spot_y = info->num_icons;
ico_save_info_free (info);
fclose (fp);
/* 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;

View File

@ -36,6 +36,17 @@ GimpPDBStatusType cur_save_image (GFile *file,
gint32 **hot_spot_y,
GError **error);
GimpPDBStatusType ani_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
gint *n_hot_spot_x,
gint32 **hot_spot_x,
gint *n_hot_spot_y,
gint32 **hot_spot_y,
AniFileHeader *header,
AniSaveInfo *ani_info,
GError **error);
gboolean ico_cmap_contains_black (const guchar *cmap,
gint num_colors);

View File

@ -20,8 +20,11 @@
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <glib/gstdio.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
@ -33,11 +36,14 @@
#include "libgimp/stdplugins-intl.h"
#define LOAD_PROC "file-ico-load"
#define LOAD_CUR_PROC "file-cur-load"
#define LOAD_THUMB_PROC "file-ico-load-thumb"
#define SAVE_PROC "file-ico-save"
#define SAVE_CUR_PROC "file-cur-save"
#define LOAD_PROC "file-ico-load"
#define LOAD_CUR_PROC "file-cur-load"
#define LOAD_ANI_PROC "file-ani-load"
#define LOAD_THUMB_PROC "file-ico-load-thumb"
#define LOAD_ANI_THUMB_PROC "file-ani-load-thumb"
#define SAVE_PROC "file-ico-save"
#define SAVE_CUR_PROC "file-cur-save"
#define SAVE_ANI_PROC "file-ani-save"
typedef struct _Ico Ico;
@ -68,11 +74,21 @@ static GimpValueArray * ico_load (GimpProcedure *procedure,
GFile *file,
const GimpValueArray *args,
gpointer run_data);
static GimpValueArray * ani_load (GimpProcedure *procedure,
GimpRunMode run_mode,
GFile *file,
const GimpValueArray *args,
gpointer run_data);
static GimpValueArray * ico_load_thumb (GimpProcedure *procedure,
GFile *file,
gint size,
const GimpValueArray *args,
gpointer run_data);
static GimpValueArray * ani_load_thumb (GimpProcedure *procedure,
GFile *file,
gint size,
const GimpValueArray *args,
gpointer run_data);
static GimpValueArray * ico_save (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
@ -89,6 +105,14 @@ static GimpValueArray * cur_save (GimpProcedure *procedure,
GFile *file,
const GimpValueArray *args,
gpointer run_data);
static GimpValueArray * ani_save (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
gint n_drawables,
GimpDrawable **drawables,
GFile *file,
const GimpValueArray *args,
gpointer run_data);
G_DEFINE_TYPE (Ico, ico, GIMP_TYPE_PLUG_IN)
@ -118,10 +142,13 @@ ico_query_procedures (GimpPlugIn *plug_in)
GList *list = NULL;
list = g_list_append (list, g_strdup (LOAD_THUMB_PROC));
list = g_list_append (list, g_strdup (LOAD_ANI_THUMB_PROC));
list = g_list_append (list, g_strdup (LOAD_PROC));
list = g_list_append (list, g_strdup (LOAD_CUR_PROC));
list = g_list_append (list, g_strdup (LOAD_ANI_PROC));
list = g_list_append (list, g_strdup (SAVE_PROC));
list = g_list_append (list, g_strdup (SAVE_CUR_PROC));
list = g_list_append (list, g_strdup (SAVE_ANI_PROC));
return list;
}
@ -190,6 +217,36 @@ ico_create_procedure (GimpPlugIn *plug_in,
gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
LOAD_THUMB_PROC);
}
else if (! strcmp (name, LOAD_ANI_PROC))
{
procedure = gimp_load_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
ani_load, NULL, NULL);
gimp_procedure_set_menu_label (procedure, _("Microsoft Windows animated cursor"));
gimp_procedure_set_icon_name (procedure, GIMP_ICON_BRUSH);
gimp_procedure_set_documentation (procedure,
_("Loads files of Windows ANI file format"),
"Loads files of Windows ANI file format",
name);
gimp_procedure_set_attribution (procedure,
"Christian Kreibich <christian@whoop.org>, "
"James Huang, Alex S.",
"Christian Kreibich <christian@whoop.org>, "
"James Huang, Alex S.",
"2007-2022");
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"application/x-navi-animation");
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"ani");
gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
"0,string,RIFF");
gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
LOAD_ANI_THUMB_PROC);
}
else if (! strcmp (name, LOAD_THUMB_PROC))
{
procedure = gimp_thumbnail_procedure_new (plug_in, name,
@ -205,6 +262,24 @@ ico_create_procedure (GimpPlugIn *plug_in,
"Sven Neumann <sven@gimp.org>",
"2005");
}
else if (! strcmp (name, LOAD_ANI_THUMB_PROC))
{
procedure = gimp_thumbnail_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
ani_load_thumb, NULL, NULL);
gimp_procedure_set_documentation (procedure,
_("Loads a preview from a Windows ANI files"),
"",
name);
gimp_procedure_set_attribution (procedure,
"Dom Lachowicz, Sven Neumann, James Huang, "
"Alex S.",
"Dom Lachowicz, "
"Sven Neumann <sven@gimp.org>, "
"James Huang, Alex S.",
"2007-2022");
}
else if (! strcmp (name, SAVE_PROC))
{
procedure = gimp_save_procedure_new (plug_in, name,
@ -277,6 +352,72 @@ ico_create_procedure (GimpPlugIn *plug_in,
"Y coordinates of hot spot (one per layer)",
G_PARAM_READWRITE);
}
else if (! strcmp (name, SAVE_ANI_PROC))
{
procedure = gimp_save_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
ani_save, NULL, NULL);
gimp_procedure_set_image_types (procedure, "*");
gimp_procedure_set_menu_label (procedure, _("Microsoft Windows animated cursor"));
gimp_procedure_set_icon_name (procedure, GIMP_ICON_BRUSH);
gimp_procedure_set_documentation (procedure,
_("Saves files in Windows ANI file format"),
_("Saves files in Windows ANI file format"),
name);
gimp_procedure_set_attribution (procedure,
"Christian Kreibich <christian@whoop.org>, "
"James Huang, Alex S.",
"Christian Kreibich <christian@whoop.org>, "
"James Huang, Alex S.",
"2007-2022");
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"application/x-navi-animation");
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"ani");
GIMP_PROC_ARG_STRING (procedure, "cursor-name",
"Cursor Name",
_("Cursor Name (Optional)"),
NULL,
G_PARAM_READWRITE);
GIMP_PROC_ARG_STRING (procedure, "author-name",
"Cursor Author",
_("Cursor Author (Optional)"),
NULL,
G_PARAM_READWRITE);
GIMP_PROC_ARG_INT (procedure, "default-delay",
"Default delay",
"Default delay between frames "
"in jiffies (1/60 of a second)",
0, G_MAXINT, 8,
G_PARAM_READWRITE);
GIMP_PROC_ARG_INT (procedure, "n-hot-spot-x",
"Number of hot spot's X coordinates",
"Number of hot spot's X coordinates",
0, G_MAXINT, 0,
G_PARAM_READWRITE);
GIMP_PROC_ARG_INT32_ARRAY (procedure, "hot-spot-x",
"Hot spot X",
"X coordinates of hot spot (one per layer)",
G_PARAM_READWRITE);
GIMP_PROC_ARG_INT (procedure, "n-hot-spot-y",
"Number of hot spot's Y coordinates",
"Number of hot spot's Y coordinates",
0, G_MAXINT, 0,
G_PARAM_READWRITE);
GIMP_PROC_ARG_INT32_ARRAY (procedure, "hot-spot-y",
"Hot spot Y",
"Y coordinates of hot spot (one per layer)",
G_PARAM_READWRITE);
}
return procedure;
}
@ -294,7 +435,37 @@ ico_load (GimpProcedure *procedure,
gegl_init (NULL, NULL);
image = ico_load_image (file, &error);
image = ico_load_image (file, NULL, &error);
if (! image)
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_EXECUTION_ERROR,
error);
return_vals = gimp_procedure_new_return_values (procedure,
GIMP_PDB_SUCCESS,
NULL);
GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
return return_vals;
}
static GimpValueArray *
ani_load (GimpProcedure *procedure,
GimpRunMode run_mode,
GFile *file,
const GimpValueArray *args,
gpointer run_data)
{
GimpValueArray *return_vals;
GimpImage *image;
GError *error = NULL;
gegl_init (NULL, NULL);
image = ani_load_image (file, FALSE,
NULL, NULL, &error);
if (! image)
return gimp_procedure_new_return_values (procedure,
@ -329,7 +500,46 @@ ico_load_thumb (GimpProcedure *procedure,
height = size;
image = ico_load_thumbnail_image (file,
&width, &height, &error);
&width, &height, 0, &error);
if (image)
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_EXECUTION_ERROR,
error);
return_vals = gimp_procedure_new_return_values (procedure,
GIMP_PDB_SUCCESS,
NULL);
GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
GIMP_VALUES_SET_INT (return_vals, 2, width);
GIMP_VALUES_SET_INT (return_vals, 3, height);
gimp_value_array_truncate (return_vals, 4);
return return_vals;
}
static GimpValueArray *
ani_load_thumb (GimpProcedure *procedure,
GFile *file,
gint size,
const GimpValueArray *args,
gpointer run_data)
{
GimpValueArray *return_vals;
gint width;
gint height;
GimpImage *image;
GError *error = NULL;
gegl_init (NULL, NULL);
width = size;
height = size;
image = ani_load_image (file, TRUE,
&width, &height, &error);
if (image)
return gimp_procedure_new_return_values (procedure,
@ -426,6 +636,85 @@ cur_save (GimpProcedure *procedure,
return gimp_procedure_new_return_values (procedure, status, error);
}
static GimpValueArray *
ani_save (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
gint n_drawables,
GimpDrawable **drawables,
GFile *file,
const GimpValueArray *args,
gpointer run_data)
{
GimpProcedureConfig *config;
GimpPDBStatusType status;
GError *error = NULL;
gchar *inam = NULL;
gchar *iart = NULL;
gint jif_rate = 0;
gint32 *hot_spot_x = NULL;
gint32 *hot_spot_y = NULL;
gint n_hot_spot_x = 0;
gint n_hot_spot_y = 0;
AniFileHeader header;
AniSaveInfo ani_info;
gegl_init (NULL, NULL);
config = gimp_procedure_create_config (procedure);
gimp_procedure_config_begin_run (config, image, run_mode, args);
g_object_get (config,
"cursor-name", &inam,
"author-name", &iart,
"default-delay", &jif_rate,
"n-hot-spot-x", &n_hot_spot_x,
"n-hot-spot-y", &n_hot_spot_y,
"hot-spot-x", &hot_spot_x,
"hot-spot-y", &hot_spot_y,
NULL);
/* Jiffies (1/60th of a second) used if rate chunk not present. */
header.jif_rate = jif_rate;
ani_info.inam = inam;
ani_info.iart = iart;
status = ani_save_image (file, image, run_mode,
&n_hot_spot_x, &hot_spot_x,
&n_hot_spot_y, &hot_spot_y,
&header, &ani_info, &error);
if (status == GIMP_PDB_SUCCESS)
{
/* XXX: seems libgimpconfig is not able to serialize
* GimpInt32Array args yet anyway. Still leave this here for now,
* as reminder of missing feature when we see the warnings.
*/
g_object_set (config,
"cursor-name", NULL,
"author-name", NULL,
"default-delay", header.jif_rate,
"n-hot-spot-x", n_hot_spot_x,
"n-hot-spot-y", n_hot_spot_y,
/*"hot-spot-x", hot_spot_x,*/
/*"hot-spot-y", hot_spot_y,*/
NULL);
g_free (hot_spot_x);
g_free (hot_spot_y);
g_free (inam);
g_free (iart);
g_free (ani_info.inam);
g_free (ani_info.iart);
memset (&ani_info, 0, sizeof (AniSaveInfo));
}
gimp_procedure_config_end_run (config, status);
g_object_unref (config);
return gimp_procedure_new_return_values (procedure, status, error);
}
gint
ico_rowstride (gint width,
gint bpp)

View File

@ -97,6 +97,22 @@ typedef struct _IcoSaveInfo
gint *hot_spot_y;
} IcoSaveInfo;
typedef struct _AniFileHeader
{
guint32 bSizeOf; /* Number of bytes in AniFileHeader (36 bytes) */
guint32 frames; /* Number of unique icons in this cursor */
guint32 steps; /* Number of Blits before the animation cycles */
guint32 x, y; /* Reserved, must be zero. */
guint32 bpp, planes; /* Reserved, must be zero. */
guint32 jif_rate; /* Default Jiffies (1/60th of a second) if rate chunk's not present. */
guint32 flags; /* Animation Flag */
} AniFileHeader;
typedef struct _AniSaveInfo
{
gchar *inam; /* Cursor name metadata */
gchar *iart; /* Author name metadata */
} AniSaveInfo;
/* Miscellaneous helper functions below: */