Commit aa51b9e1 authored by Alx Sa's avatar Alx Sa
Browse files

plug-ins: Add .ani file import/export

parent e7faae1d
Pipeline #429489 passed with stages
in 28 minutes and 2 seconds
......@@ -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));
}
......@@ -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__ */
......@@ -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;
......
......@@ -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,
......
......@@ -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,20 +1201,285 @@ 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
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;
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, *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");