diff --git a/plug-ins/file-ico/ico-dialog.c b/plug-ins/file-ico/ico-dialog.c index ba65f9d11f..6172149aa7 100644 --- a/plug-ins/file-ico/ico-dialog.c +++ b/plug-ins/file-ico/ico-dialog.c @@ -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); diff --git a/plug-ins/file-ico/ico-load.c b/plug-ins/file-ico/ico-load.c index 62d8fdce20..abe4f2a915 100644 --- a/plug-ins/file-ico/ico-load.c +++ b/plug-ins/file-ico/ico-load.c @@ -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); diff --git a/plug-ins/file-ico/ico-save.c b/plug-ins/file-ico/ico-save.c index 4f8bf1c927..3f366e0145 100644 --- a/plug-ins/file-ico/ico-save.c +++ b/plug-ins/file-ico/ico-save.c @@ -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, ¶site_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); diff --git a/plug-ins/file-ico/ico-save.h b/plug-ins/file-ico/ico-save.h index 8e78ea969f..920feb05d5 100644 --- a/plug-ins/file-ico/ico-save.h +++ b/plug-ins/file-ico/ico-save.h @@ -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); diff --git a/plug-ins/file-ico/ico.c b/plug-ins/file-ico/ico.c index cf1926c7f2..25f0f63999 100644 --- a/plug-ins/file-ico/ico.c +++ b/plug-ins/file-ico/ico.c @@ -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 , " + "Nikc M.", + "Christian Kreibich , " + "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 , " + "Nikc M.", + "Christian Kreibich , " + "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) diff --git a/plug-ins/file-ico/ico.h b/plug-ins/file-ico/ico.h index 39374a059c..972dd19ceb 100644 --- a/plug-ins/file-ico/ico.h +++ b/plug-ins/file-ico/ico.h @@ -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;