Issue #526 - Add Import/Export for Windows .CUR files

This commit is contained in:
Nikc 2022-02-10 12:09:40 +00:00 committed by Jehan
parent d25b7301d6
commit a22fd4f43a
6 changed files with 342 additions and 67 deletions

View File

@ -43,13 +43,16 @@ static void ico_dialog_check_compat (GtkWidget *dialog,
GtkWidget *
ico_dialog_new (IcoSaveInfo *info)
{
GtkWidget *dialog;
GtkWidget *main_vbox;
GtkWidget *vbox;
GtkWidget *frame;
GtkWidget *scrolled_window;
GtkWidget *viewport;
GtkWidget *warning;
GtkWidget *dialog;
GtkWidget *main_vbox;
GtkWidget *vbox;
GtkWidget *frame;
GtkWidget *scrolled_window;
GtkWidget *viewport;
GtkWidget *grid;
GtkAdjustment *adj;
GtkWidget *spinbutton;
GtkWidget *warning;
dialog = gimp_export_dialog_new (_("Windows Icon"),
PLUG_IN_BINARY,
@ -65,14 +68,55 @@ ico_dialog_new (IcoSaveInfo *info)
g_object_set_data (G_OBJECT (dialog), "save_info", info);
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
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, 0);
gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 4);
gtk_widget_show (frame);
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
@ -98,7 +142,7 @@ ico_dialog_new (IcoSaveInfo *info)
"by all programs. Older applications may not "
"open this file correctly."),
NULL);
gtk_box_pack_end (GTK_BOX (main_vbox), warning, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (main_vbox), warning, FALSE, FALSE, 12);
/* don't show the warning here */
g_object_set_data (G_OBJECT (dialog), "warning", warning);

View File

@ -120,7 +120,7 @@ ico_read_int8 (FILE *fp,
}
static guint32
static IcoFileHeader
ico_read_init (FILE *fp)
{
IcoFileHeader header;
@ -130,12 +130,13 @@ ico_read_init (FILE *fp)
! ico_read_int16 (fp, &header.resource_type, 1) ||
! ico_read_int16 (fp, &header.icon_count, 1) ||
header.reserved != 0 ||
header.resource_type != 1)
(header.resource_type != 1 && header.resource_type != 2))
{
return 0;
header.icon_count = 0;
return header;
}
return header.icon_count;
return header;
}
@ -229,6 +230,7 @@ ico_read_info (FILE *fp,
{
info[i].width = entries[i].width;
info[i].height = entries[i].height;
info[i].planes = entries[i].planes;
info[i].bpp = GUINT16_FROM_LE (entries[i].bpp);
info[i].size = GUINT32_FROM_LE (entries[i].size);
info[i].offset = GUINT32_FROM_LE (entries[i].offset);
@ -653,14 +655,17 @@ GimpImage *
ico_load_image (GFile *file,
GError **error)
{
FILE *fp;
IcoLoadInfo *info;
gint max_width, max_height;
gint i;
GimpImage *image;
guchar *buf;
guint icon_count;
gint maxsize;
FILE *fp;
IcoFileHeader header;
IcoLoadInfo *info;
gint max_width, max_height;
gint i;
GimpImage *image;
guchar *buf;
guint icon_count;
gint maxsize;
GimpParasite *parasite;
gchar *str;
gimp_progress_init_printf (_("Opening '%s'"),
gimp_file_get_utf8_name (file));
@ -675,7 +680,8 @@ ico_load_image (GFile *file,
return NULL;
}
icon_count = ico_read_init (fp);
header = ico_read_init (fp);
icon_count = header.icon_count;
if (!icon_count)
{
fclose (fp);
@ -710,6 +716,18 @@ 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++)
@ -731,15 +749,16 @@ ico_load_thumbnail_image (GFile *file,
gint *height,
GError **error)
{
FILE *fp;
IcoLoadInfo *info;
GimpImage *image;
gint w = 0;
gint h = 0;
gint bpp = 0;
gint match = 0;
gint i, icon_count;
guchar *buf;
FILE *fp;
IcoLoadInfo *info;
IcoFileHeader header;
GimpImage *image;
gint w = 0;
gint h = 0;
gint bpp = 0;
gint match = 0;
gint i, icon_count;
guchar *buf;
gimp_progress_init_printf (_("Opening thumbnail for '%s'"),
gimp_file_get_utf8_name (file));
@ -754,7 +773,8 @@ ico_load_thumbnail_image (GFile *file,
return NULL;
}
icon_count = ico_read_init (fp);
header = ico_read_init (fp);
icon_count = header.icon_count;
if (! icon_count)
{
fclose (fp);

View File

@ -233,13 +233,36 @@ static gboolean
ico_save_dialog (GimpImage *image,
IcoSaveInfo *info)
{
GtkWidget *dialog;
GList *iter;
gint i;
gint response;
GtkWidget *dialog;
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;
@ -1066,25 +1089,60 @@ ico_save_image (GFile *file,
gint32 run_mode,
GError **error)
{
FILE *fp;
GList *iter;
gint width;
gint height;
IcoSaveInfo info;
IcoFileHeader header;
IcoFileEntry *entries;
gboolean saved;
gint i;
D(("*** Exporting Microsoft icon file %s\n",
gimp_file_get_utf8_name (file)));
ico_save_init (image, &info);
info.is_cursor = FALSE;
return shared_save_image (file, image, run_mode, error, &info);
}
GimpPDBStatusType
cur_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
gint32 hot_spot_x,
gint32 hot_spot_y,
GError **error)
{
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);
}
GimpPDBStatusType
shared_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
GError **error,
IcoSaveInfo *info)
{
FILE *fp;
GList *iter;
gint width;
gint height;
IcoFileHeader header;
IcoFileEntry *entries;
gboolean saved;
gint i;
GimpParasite *parasite = NULL;
gchar *str;
ico_save_init (image, info);
if (run_mode == GIMP_RUN_INTERACTIVE)
{
/* Allow user to override default values */
if ( !ico_save_dialog (image, &info))
if ( !ico_save_dialog (image, info))
return GIMP_PDB_CANCEL;
}
@ -1103,30 +1161,32 @@ ico_save_image (GFile *file,
header.reserved = 0;
header.resource_type = 1;
header.icon_count = info.num_icons;
if (info->is_cursor)
header.resource_type = 2;
header.icon_count = info->num_icons;
if (! ico_write_int16 (fp, &header.reserved, 1) ||
! ico_write_int16 (fp, &header.resource_type, 1) ||
! ico_write_int16 (fp, &header.icon_count, 1))
{
ico_save_info_free (&info);
ico_save_info_free (info);
fclose (fp);
return GIMP_PDB_EXECUTION_ERROR;
}
entries = g_new0 (IcoFileEntry, info.num_icons);
if (fwrite (entries, sizeof (IcoFileEntry), info.num_icons, fp) <= 0)
entries = g_new0 (IcoFileEntry, info->num_icons);
if (fwrite (entries, sizeof (IcoFileEntry), info->num_icons, fp) <= 0)
{
ico_save_info_free (&info);
ico_save_info_free (info);
g_free (entries);
fclose (fp);
return GIMP_PDB_EXECUTION_ERROR;
}
for (iter = info.layers, i = 0;
for (iter = info->layers, i = 0;
iter;
iter = g_list_next (iter), i++)
{
gimp_progress_update ((gdouble)i / (gdouble)info.num_icons);
gimp_progress_update ((gdouble)i / (gdouble)info->num_icons);
width = gimp_drawable_get_width (iter->data);
height = gimp_drawable_get_height (iter->data);
@ -1140,23 +1200,29 @@ ico_save_image (GFile *file,
entries[i].width = 0;
entries[i].height = 0;
}
if ( info.depths[i] <= 8 )
entries[i].num_colors = 1 << info.depths[i];
if (info->depths[i] <= 8 )
entries[i].num_colors = 1 << info->depths[i];
else
entries[i].num_colors = 0;
entries[i].reserved = 0;
entries[i].planes = 1;
entries[i].bpp = info.depths[i];
entries[i].bpp = info->depths[i];
/* .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].offset = ftell (fp);
if (info.compress[i])
saved = ico_write_png (fp, iter->data, info.depths[i]);
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]);
saved = ico_write_icon (fp, iter->data, info->depths[i]);
if (!saved)
{
ico_save_info_free (&info);
ico_save_info_free (info);
fclose (fp);
return GIMP_PDB_EXECUTION_ERROR;
}
@ -1164,7 +1230,7 @@ ico_save_image (GFile *file,
entries[i].size = ftell (fp) - entries[i].offset;
}
for (i = 0; i < info.num_icons; i++)
for (i = 0; i < info->num_icons; i++)
{
entries[i].planes = GUINT16_TO_LE (entries[i].planes);
entries[i].bpp = GUINT16_TO_LE (entries[i].bpp);
@ -1173,16 +1239,28 @@ ico_save_image (GFile *file,
}
if (fseek (fp, sizeof(IcoFileHeader), SEEK_SET) < 0
|| fwrite (entries, sizeof (IcoFileEntry), info.num_icons, fp) <= 0)
|| fwrite (entries, sizeof (IcoFileEntry), info->num_icons, fp) <= 0)
{
ico_save_info_free (&info);
ico_save_info_free (info);
fclose (fp);
return GIMP_PDB_EXECUTION_ERROR;
}
gimp_progress_update (1.0);
ico_save_info_free (&info);
/* 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);
}
ico_save_info_free (info);
fclose (fp);
g_free (entries);

View File

@ -27,6 +27,19 @@ GimpPDBStatusType ico_save_image (GFile *file,
gint32 run_mode,
GError **error);
GimpPDBStatusType cur_save_image (GFile *file,
GimpImage *image,
gint32 run_mode,
gint32 hot_spot_x,
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

@ -34,8 +34,10 @@
#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"
typedef struct _Ico Ico;
@ -79,6 +81,14 @@ static GimpValueArray * ico_save (GimpProcedure *procedure,
GFile *file,
const GimpValueArray *args,
gpointer run_data);
static GimpValueArray * cur_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)
@ -107,7 +117,9 @@ ico_query_procedures (GimpPlugIn *plug_in)
list = g_list_append (list, g_strdup (LOAD_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 (SAVE_PROC));
list = g_list_append (list, g_strdup (SAVE_CUR_PROC));
return list;
}
@ -143,6 +155,36 @@ ico_create_procedure (GimpPlugIn *plug_in,
gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
"0,string,\\000\\001\\000\\000,0,string,\\000\\002\\000\\000");
gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
LOAD_THUMB_PROC);
}
else if (! strcmp (name, LOAD_CUR_PROC))
{
procedure = gimp_load_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
ico_load, NULL, NULL);
gimp_procedure_set_menu_label (procedure, N_("Microsoft Windows cursor"));
gimp_procedure_set_icon_name (procedure, GIMP_ICON_BRUSH);
gimp_procedure_set_documentation (procedure,
"Loads files of Windows CUR file format",
"Loads files of Windows CUR file format",
name);
gimp_procedure_set_attribution (procedure,
"Christian Kreibich <christian@whoop.org>, "
"Nikc M.",
"Christian Kreibich <christian@whoop.org>, "
"Nikc M.",
"2002-2022");
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"image/vnd.microsoft.icon");
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"cur");
gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
"0,string,\\000\\001\\000\\000,0,string,\\000\\002\\000\\000");
gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
LOAD_THUMB_PROC);
}
@ -153,7 +195,7 @@ ico_create_procedure (GimpPlugIn *plug_in,
ico_load_thumb, NULL, NULL);
gimp_procedure_set_documentation (procedure,
"Loads a preview from an Windows ICO file",
"Loads a preview from a Windows ICO or CUR files",
"",
name);
gimp_procedure_set_attribution (procedure,
@ -186,6 +228,45 @@ ico_create_procedure (GimpPlugIn *plug_in,
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"ico");
}
else if (! strcmp (name, SAVE_CUR_PROC))
{
procedure = gimp_save_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
cur_save, NULL, NULL);
gimp_procedure_set_image_types (procedure, "*");
gimp_procedure_set_menu_label (procedure, N_("Microsoft Windows cursor"));
gimp_procedure_set_icon_name (procedure, GIMP_ICON_BRUSH);
gimp_procedure_set_documentation (procedure,
"Saves files in Windows CUR file format",
"Saves files in Windows CUR file format",
name);
gimp_procedure_set_attribution (procedure,
"Christian Kreibich <christian@whoop.org>, "
"Nikc M.",
"Christian Kreibich <christian@whoop.org>, "
"Nikc M.",
"2002-2022");
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"image/vnd.microsoft.icon");
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,
G_PARAM_READWRITE);
GIMP_PROC_ARG_INT (procedure, "hot-spot-y",
"Hot spot Y",
"Y coordinate of hot spot",
0, G_MAXUINT16, 0,
G_PARAM_READWRITE);
}
return procedure;
}
@ -281,6 +362,41 @@ ico_save (GimpProcedure *procedure,
return gimp_procedure_new_return_values (procedure, status, error);
}
static GimpValueArray *
cur_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;
gint hot_spot_x = 0;
gint hot_spot_y = 0;
INIT_I18N ();
gegl_init (NULL, NULL);
config = gimp_procedure_create_config (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,
NULL);
status = cur_save_image (file, image, run_mode, hot_spot_x, hot_spot_y, &error);
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

@ -80,6 +80,7 @@ typedef struct _IcoLoadInfo
guint width;
guint height;
gint bpp;
gint planes;
gint offset;
gint size;
} IcoLoadInfo;
@ -91,6 +92,9 @@ typedef struct _IcoSaveInfo
gboolean *compress;
GList *layers;
gint num_icons;
gboolean is_cursor;
gint hot_spot_x;
gint hot_spot_y;
} IcoSaveInfo;