Issue #526: Import/Export Windows cursor files (.CUR).

MR !565 was only a partial implementation as it was assuming all cursors
had the same hot spot coordinates in the file, which is false more often
than not (since usually it's several sizes for the same image, hence
coordinates move). I should have realized this before merging.
With this new commit, we actually loads the hot spot coordinates per
cursor, stores them as per-layer parasites, then exports with per-cursor
coordinates.

Also it makes the procedure API use int32 array (should be int16 but we
removed the support, now I think it may have been a mistake) which shows
the ugliness of our array support once again with additional size args
per array (even if it's the same size). Also I realize that our support
of arrays with config object is not good. This is also something we'll
have to look at.
This commit is contained in:
Jehan 2022-02-10 18:20:41 +01:00
parent 73c0ee8da7
commit b301dbee78
6 changed files with 274 additions and 132 deletions

View File

@ -49,9 +49,6 @@ ico_dialog_new (IcoSaveInfo *info)
GtkWidget *frame;
GtkWidget *scrolled_window;
GtkWidget *viewport;
GtkWidget *grid;
GtkAdjustment *adj;
GtkWidget *spinbutton;
GtkWidget *warning;
dialog = gimp_export_dialog_new (_("Windows Icon"),
@ -74,46 +71,6 @@ ico_dialog_new (IcoSaveInfo *info)
main_vbox, TRUE, TRUE, 0);
gtk_widget_show (main_vbox);
/* Cursor */
if (info->is_cursor)
{
frame = gimp_frame_new (_("Cursor Hot spot"));
gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 4);
gtk_widget_show (frame);
grid = gtk_grid_new ();
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_container_add (GTK_CONTAINER (frame), grid);
gtk_widget_show (grid);
adj = (GtkAdjustment *)
gtk_adjustment_new (info->hot_spot_x, 0,
G_MAXUINT16, 1, 10, 0);
spinbutton = gimp_spin_button_new (adj, 1.0, 0);
gtk_spin_button_set_range (GTK_SPIN_BUTTON (spinbutton),
0, G_MAXUINT16);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 0,
_("Hot spot _X:"), 0.0, 0.5,
spinbutton, 1);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&info->hot_spot_x);
adj = (GtkAdjustment *)
gtk_adjustment_new (info->hot_spot_y, 0,
G_MAXUINT16, 1, 10, 0);
spinbutton = gimp_spin_button_new (adj, 1.0, 0);
gtk_spin_button_set_range (GTK_SPIN_BUTTON (spinbutton),
0, G_MAXUINT16);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
_("Hot spot _Y:"), 0.0, 0.5,
spinbutton, 1);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&info->hot_spot_y);
}
/* Cursor */
frame = gimp_frame_new (_("Icon Details"));
gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 4);
@ -501,6 +458,46 @@ ico_dialog_add_icon (GtkWidget *dialog,
ico_dialog_update_icon_preview (dialog, layer, info->depths[layer_num]);
ico_dialog_check_compat (dialog, info);
/* Cursor */
if (info->is_cursor)
{
GtkWidget *grid;
GtkAdjustment *adj;
GtkWidget *spinbutton;
grid = gtk_grid_new ();
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_box_pack_start (GTK_BOX (hbox), grid, FALSE, FALSE, 0);
gtk_widget_show (grid);
adj = (GtkAdjustment *)
gtk_adjustment_new (info->hot_spot_x[layer_num], 0,
G_MAXUINT16, 1, 10, 0);
spinbutton = gimp_spin_button_new (adj, 1.0, 0);
gtk_spin_button_set_range (GTK_SPIN_BUTTON (spinbutton),
0, G_MAXUINT16);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 0,
_("Hot spot _X:"), 0.0, 0.5,
spinbutton, 1);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&info->hot_spot_x[layer_num]);
adj = (GtkAdjustment *)
gtk_adjustment_new (info->hot_spot_y[layer_num], 0,
G_MAXUINT16, 1, 10, 0);
spinbutton = gimp_spin_button_new (adj, 1.0, 0);
gtk_spin_button_set_range (GTK_SPIN_BUTTON (spinbutton),
0, G_MAXUINT16);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
_("Hot spot _Y:"), 0.0, 0.5,
spinbutton, 1);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&info->hot_spot_y[layer_num]);
}
}
static void

View File

@ -664,7 +664,6 @@ ico_load_image (GFile *file,
guchar *buf;
guint icon_count;
gint maxsize;
GimpParasite *parasite;
gchar *str;
gimp_progress_init_printf (_("Opening '%s'"),
@ -716,23 +715,27 @@ ico_load_image (GFile *file,
image = gimp_image_new (max_width, max_height, GIMP_RGB);
gimp_image_set_file (image, file);
/* Save CUR hot spot information */
if (header.resource_type == 2)
{
str = g_strdup_printf ("%d %d", info[0].planes, info[0].bpp);
parasite = gimp_parasite_new ("cur-hot-spot",
GIMP_PARASITE_PERSISTENT,
strlen (str) + 1, (gpointer) str);
g_free (str);
gimp_image_attach_parasite (image, parasite);
gimp_parasite_free (parasite);
}
maxsize = max_width * max_height * 4;
buf = g_new (guchar, max_width * max_height * 4);
for (i = 0; i < icon_count; i++)
{
ico_load_layer (fp, image, i, buf, maxsize, info+i);
GimpLayer *layer;
layer = ico_load_layer (fp, image, i, buf, maxsize, info+i);
/* Save CUR hot spot information */
if (header.resource_type == 2)
{
GimpParasite *parasite;
str = g_strdup_printf ("%d %d", info[i].planes, info[i].bpp);
parasite = gimp_parasite_new ("cur-hot-spot",
GIMP_PARASITE_PERSISTENT,
strlen (str) + 1, (gpointer) str);
g_free (str);
gimp_item_attach_parasite (GIMP_ITEM (layer), parasite);
gimp_parasite_free (parasite);
}
}
g_free (buf);
g_free (info);

View File

@ -72,6 +72,25 @@ static void ico_image_get_reduced_buf (GimpDrawable *layer,
guchar **cmap_out,
guchar **buf_out);
static gboolean ico_save_init (GimpImage *image,
gint32 run_mode,
IcoSaveInfo *info,
gint n_hot_spot_x,
gint32 *hot_spot_x,
gint n_hot_spot_y,
gint32 *hot_spot_y,
GError **error);
static 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);
static gint
ico_write_int32 (FILE *fp,
@ -155,9 +174,15 @@ ico_write_int8 (FILE *fp,
}
static void
static gboolean
ico_save_init (GimpImage *image,
IcoSaveInfo *info)
gint32 run_mode,
IcoSaveInfo *info,
gint n_hot_spot_x,
gint32 *hot_spot_x,
gint n_hot_spot_y,
gint32 *hot_spot_y,
GError **error)
{
GList *iter;
gint num_colors;
@ -169,6 +194,51 @@ ico_save_init (GimpImage *image,
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 &&
(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
@ -225,6 +295,8 @@ ico_save_init (GimpImage *image,
/* set with default values */
memcpy (info->depths, info->default_depths,
sizeof (gint) * info->num_icons);
return TRUE;
}
@ -237,37 +309,41 @@ ico_save_dialog (GimpImage *image,
GList *iter;
gint i;
gint response;
GimpParasite *parasite = NULL;
gimp_ui_init (PLUG_IN_BINARY);
/* Loading hot spots for cursors if applicable */
parasite = gimp_image_get_parasite (image, "cur-hot-spot");
if (parasite)
{
gchar *parasite_data;
guint32 parasite_size;
gint x, y;
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
parasite_data = g_strndup (parasite_data, parasite_size);
if (sscanf (parasite_data, "%i %i", &x, &y) == 2)
{
info->hot_spot_x = x;
info->hot_spot_y = y;
}
gimp_parasite_free (parasite);
g_free (parasite_data);
}
dialog = ico_dialog_new (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, &parasite_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);
}
@ -1080,6 +1156,8 @@ ico_save_info_free (IcoSaveInfo *info)
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));
}
@ -1089,42 +1167,51 @@ ico_save_image (GFile *file,
gint32 run_mode,
GError **error)
{
IcoSaveInfo info;
IcoSaveInfo info;
D(("*** Exporting Microsoft icon file %s\n",
gimp_file_get_utf8_name (file)));
info.is_cursor = FALSE;
return shared_save_image (file, image, run_mode, error, &info);
return shared_save_image (file, image, run_mode,
0, NULL, 0, NULL,
error, &info);
}
GimpPDBStatusType
cur_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
gint32 hot_spot_x,
gint32 hot_spot_y,
gint *n_hot_spot_x,
gint32 **hot_spot_x,
gint *n_hot_spot_y,
gint32 **hot_spot_y,
GError **error)
{
IcoSaveInfo info;
IcoSaveInfo info;
D(("*** Exporting Microsoft cursor file %s\n",
gimp_file_get_utf8_name (file)));
info.is_cursor = TRUE;
info.hot_spot_x = hot_spot_x;
info.hot_spot_y = hot_spot_y;
return shared_save_image (file, image, run_mode, error, &info);
return shared_save_image (file, image, run_mode,
n_hot_spot_x, hot_spot_x,
n_hot_spot_y, hot_spot_y,
error, &info);
}
GimpPDBStatusType
shared_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
GError **error,
IcoSaveInfo *info)
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)
{
FILE *fp;
GList *iter;
@ -1137,7 +1224,15 @@ shared_save_image (GFile *file,
GimpParasite *parasite = NULL;
gchar *str;
ico_save_init (image, 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))
{
return GIMP_PDB_EXECUTION_ERROR;
}
if (run_mode == GIMP_RUN_INTERACTIVE)
{
@ -1210,8 +1305,8 @@ shared_save_image (GFile *file,
/* .cur file reuses these fields for cursor offsets */
if (info->is_cursor)
{
entries[i].planes = info->hot_spot_x;
entries[i].bpp = info->hot_spot_y;
entries[i].planes = info->hot_spot_x[i];
entries[i].bpp = info->hot_spot_y[i];
}
entries[i].offset = ftell (fp);
@ -1251,15 +1346,35 @@ shared_save_image (GFile *file,
/* Updating parasite hot spots if needed */
if (info->is_cursor)
{
str = g_strdup_printf ("%d %d", info->hot_spot_x, info->hot_spot_y);
parasite = gimp_parasite_new ("cur-hot-spot",
GIMP_PARASITE_PERSISTENT,
strlen (str) + 1, (gpointer) str);
g_free (str);
gimp_image_attach_parasite (image, parasite);
gimp_parasite_free (parasite);
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 (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 = info->num_icons;
if (n_hot_spot_y)
*n_hot_spot_y = info->num_icons;
ico_save_info_free (info);
fclose (fp);
g_free (entries);

View File

@ -30,16 +30,12 @@ GimpPDBStatusType ico_save_image (GFile *file,
GimpPDBStatusType cur_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
gint32 hot_spot_x,
gint32 hot_spot_y,
gint *n_hot_spot_x,
gint32 **hot_spot_x,
gint *n_hot_spot_y,
gint32 **hot_spot_y,
GError **error);
GimpPDBStatusType shared_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
GError **error,
IcoSaveInfo *info);
gboolean ico_cmap_contains_black (const guchar *cmap,
gint num_colors);

View File

@ -255,17 +255,25 @@ ico_create_procedure (GimpPlugIn *plug_in,
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"cur");
GIMP_PROC_ARG_INT (procedure, "hot-spot-x",
"Hot spot X",
"X coordinate of hot spot",
0, G_MAXUINT16, 0,
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, "hot-spot-y",
"Hot spot Y",
"Y coordinate of hot spot",
0, G_MAXUINT16, 0,
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;
@ -374,9 +382,11 @@ cur_save (GimpProcedure *procedure,
{
GimpProcedureConfig *config;
GimpPDBStatusType status;
GError *error = NULL;
gint hot_spot_x = 0;
gint hot_spot_y = 0;
GError *error = NULL;
gint32 *hot_spot_x = NULL;
gint32 *hot_spot_y = NULL;
gint n_hot_spot_x = 0;
gint n_hot_spot_y = 0;
INIT_I18N ();
gegl_init (NULL, NULL);
@ -385,11 +395,32 @@ cur_save (GimpProcedure *procedure,
gimp_procedure_config_begin_run (config, image, run_mode, args);
g_object_get (config,
"hot-spot-x", &hot_spot_x,
"hot-spot-y", &hot_spot_y,
"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);
status = cur_save_image (file, image, run_mode, hot_spot_x, hot_spot_y, &error);
status = cur_save_image (file, image, run_mode,
&n_hot_spot_x, &hot_spot_x,
&n_hot_spot_y, &hot_spot_y,
&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,
"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);
}
gimp_procedure_config_end_run (config, status);
g_object_unref (config);

View File

@ -50,11 +50,11 @@ typedef struct _IcoFileHeader
typedef struct _IcoFileEntry
{
guint8 width; /* Width of icon in pixels */
guint8 height; /* Height of icon in pixels */
guint8 height; /* Height of icon in pixels */
guint8 num_colors; /* Number of colors of paletted image */
guint8 reserved; /* Must be 0 */
guint16 planes; /* Must be 1 */
guint16 bpp; /* 1, 4, 8, 24 or 32 bits per pixel */
guint16 planes; /* Must be 1 for ICO, x position of hot spot for CUR */
guint16 bpp; /* 1, 4, 8, 24 or 32 bits per pixel for ICO, y position of hot spot for CUR */
guint32 size; /* Size of icon (including data header) */
guint32 offset; /* Absolute offset of data in a file */
} IcoFileEntry;
@ -93,8 +93,8 @@ typedef struct _IcoSaveInfo
GList *layers;
gint num_icons;
gboolean is_cursor;
gint hot_spot_x;
gint hot_spot_y;
gint *hot_spot_x;
gint *hot_spot_y;
} IcoSaveInfo;