PIKApp/plug-ins/common/file-gih.c

786 lines
26 KiB
C

/* Plug-in to load and export .gih (PIKA Brush Pipe) files.
*
* Copyright (C) 1999 Tor Lillqvist
* Copyright (C) 2000 Jens Lautenbacher, Sven Neumann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* Example of how to call file_gih_save from script-fu:
(let ((ranks (cons-array 1 'byte)))
(aset ranks 0 12)
(file-gih-save 1
img
drawable
"foo.gih"
"foo.gih"
100
"test brush"
125
125
3
4
1
ranks
1
'("random")))
*/
#include "config.h"
#include <libpika/pika.h>
#include <libpika/pikaui.h>
#include <libpikabase/pikaparasiteio.h>
#include "libpika/stdplugins-intl.h"
#define SAVE_PROC "file-gih-save"
#define PLUG_IN_BINARY "file-gih"
#define PLUG_IN_ROLE "pika-file-gih"
typedef struct
{
GtkWidget *rank_entry[PIKA_PIXPIPE_MAXDIM];
GtkWidget *mode_entry[PIKA_PIXPIPE_MAXDIM];
} SizeAdjustmentData;
typedef struct _Gih Gih;
typedef struct _GihClass GihClass;
struct _Gih
{
PikaPlugIn parent_instance;
};
struct _GihClass
{
PikaPlugInClass parent_class;
};
#define GIH_TYPE (gih_get_type ())
#define GIH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIH_TYPE, Gih))
GType gih_get_type (void) G_GNUC_CONST;
static GList * gih_query_procedures (PikaPlugIn *plug_in);
static PikaProcedure * gih_create_procedure (PikaPlugIn *plug_in,
const gchar *name);
static PikaValueArray * gih_save (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
GFile *file,
PikaMetadata *metadata,
PikaProcedureConfig *config,
gpointer run_data);
static void gih_remove_guides (PikaProcedureConfig *config,
PikaImage *image);
static void gih_cell_width_notify (PikaProcedureConfig *config,
const GParamSpec *pspec,
PikaProcedureDialog *dialog);
static gboolean gih_save_dialog (PikaImage *image,
PikaProcedure *procedure,
PikaProcedureConfig *config);
G_DEFINE_TYPE (Gih, gih, PIKA_TYPE_PLUG_IN)
PIKA_MAIN (GIH_TYPE)
DEFINE_STD_SET_I18N
static const gchar * const selection_modes[] = { "incremental",
"angular",
"random",
"velocity",
"pressure",
"xtilt",
"ytilt" };
static void
gih_class_init (GihClass *klass)
{
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = gih_query_procedures;
plug_in_class->create_procedure = gih_create_procedure;
plug_in_class->set_i18n = STD_SET_I18N;
}
static void
gih_init (Gih *gih)
{
}
static GList *
gih_query_procedures (PikaPlugIn *plug_in)
{
return g_list_append (NULL, g_strdup (SAVE_PROC));
}
static PikaProcedure *
gih_create_procedure (PikaPlugIn *plug_in,
const gchar *name)
{
PikaProcedure *procedure = NULL;
if (! strcmp (name, SAVE_PROC))
{
procedure = pika_save_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
FALSE, gih_save, NULL, NULL);
pika_procedure_set_image_types (procedure, "RGB*, GRAY*");
pika_procedure_set_menu_label (procedure, _("PIKA brush (animated)"));
pika_procedure_set_icon_name (procedure, PIKA_ICON_BRUSH);
pika_procedure_set_documentation (procedure,
"exports images in PIKA brush pipe "
"format",
"This plug-in exports an image in "
"the PIKA brush pipe format. For a "
"colored brush pipe, RGBA layers are "
"used, otherwise the layers should be "
"grayscale masks. The image can be "
"multi-layered, and additionally the "
"layers can be divided into a "
"rectangular array of brushes.",
SAVE_PROC);
pika_procedure_set_attribution (procedure,
"Tor Lillqvist",
"Tor Lillqvist",
"1999");
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
"image/x-pika-gih");
pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
_("Brush Pipe"));
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
"gih");
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
TRUE);
PIKA_PROC_ARG_INT (procedure, "spacing",
_("Spacing (_percent)"),
_("Spacing of the brush"),
1, 1000, 20,
PIKA_PARAM_READWRITE);
PIKA_PROC_ARG_STRING (procedure, "description",
_("_Description"),
_("Short description of the GIH brush pipe"),
"PIKA Brush Pipe",
PIKA_PARAM_READWRITE);
PIKA_PROC_ARG_INT (procedure, "cell-width",
_("Cell _width"),
_("Width of the brush cells in pixels"),
1, 1000, 1,
PIKA_PARAM_READWRITE);
PIKA_PROC_ARG_INT (procedure, "cell-height",
_("Cell _height"),
_("Height of the brush cells in pixels"),
1, 1000, 1,
PIKA_PARAM_READWRITE);
PIKA_PROC_ARG_INT (procedure, "num-cells",
_("_Number of cells"),
_("Number of cells to cut up"),
1, 1000, 1,
PIKA_PARAM_READWRITE);
PIKA_PROC_ARG_BYTES (procedure, "ranks",
_("_Rank"),
_("Ranks of the dimensions"),
PIKA_PARAM_READWRITE);
PIKA_PROC_ARG_STRV (procedure, "selection-modes",
"Selection modes",
"Selection modes",
PIKA_PARAM_READWRITE);
/* Auxiliary arguments. Only useful for the GUI, to pass info around. */
PIKA_PROC_AUX_ARG_STRING (procedure, "info-text",
_("Display as"),
_("Describe how the layers will be split"),
"", PIKA_PARAM_READWRITE);
PIKA_PROC_AUX_ARG_INT (procedure, "dimension",
_("D_imension"),
_("How many dimensions the animated brush has"),
1, 1000, 1,
PIKA_PARAM_READWRITE);
PIKA_PROC_AUX_ARG_INT32_ARRAY (procedure, "guides",
"Guides",
"Guides to show how the layers will be split in cells",
PIKA_PARAM_READWRITE);
}
return procedure;
}
static PikaValueArray *
gih_save (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
GFile *file,
PikaMetadata *metadata,
PikaProcedureConfig *config,
gpointer run_data)
{
PikaPDBStatusType status = PIKA_PDB_SUCCESS;
PikaExportReturn export = PIKA_EXPORT_CANCEL;
PikaParasite *parasite;
PikaImage *orig_image;
GError *error = NULL;
PikaPixPipeParams gihparams = { 0, };
gchar *description = NULL;
gint spacing;
GBytes *rank_bytes;
const guint8 *rank;
gchar **selection;
orig_image = image;
if (run_mode == PIKA_RUN_INTERACTIVE)
{
GParamSpec *spec;
gint image_width;
gint image_height;
gint cell_width;
gint cell_height;
export = pika_export_image (&image, &n_drawables, &drawables, "GIH",
PIKA_EXPORT_CAN_HANDLE_RGB |
PIKA_EXPORT_CAN_HANDLE_GRAY |
PIKA_EXPORT_CAN_HANDLE_ALPHA |
PIKA_EXPORT_CAN_HANDLE_LAYERS);
if (export == PIKA_EXPORT_CANCEL)
return pika_procedure_new_return_values (procedure,
PIKA_PDB_CANCEL,
NULL);
/* Possibly retrieve data */
parasite = pika_image_get_parasite (orig_image,
"pika-brush-pipe-name");
if (parasite)
{
gchar *parasite_data;
guint32 parasite_size;
parasite_data = (gchar *) pika_parasite_get_data (parasite, &parasite_size);
g_object_set (config, "description", parasite_data , NULL);
pika_parasite_free (parasite);
}
else
{
gchar *name = g_path_get_basename (pika_file_get_utf8_name (file));
if (g_str_has_suffix (name, ".gih"))
name[strlen (name) - 4] = '\0';
if (strlen (name))
g_object_set (config, "description", name, NULL);
g_free (name);
}
g_object_get (config,
"cell-width", &cell_width,
"cell-height", &cell_height,
NULL);
image_width = pika_image_get_width (image);
image_height = pika_image_get_height (image);
spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-width");
G_PARAM_SPEC_INT (spec)->default_value = image_width;
G_PARAM_SPEC_INT (spec)->maximum = image_width;
spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-height");
G_PARAM_SPEC_INT (spec)->default_value = image_height;
G_PARAM_SPEC_INT (spec)->maximum = image_height;
if (cell_width == 1 && cell_height == 1)
{
g_object_set (config,
"cell-width", image_width,
"cell-height", image_height,
NULL);
spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "num-cells");
G_PARAM_SPEC_INT (spec)->maximum = 1;
}
pika_ui_init (PLUG_IN_BINARY);
if (! gih_save_dialog (image, procedure, config))
{
status = PIKA_PDB_CANCEL;
goto out;
}
}
pika_pixpipe_params_init (&gihparams);
g_object_get (config,
"spacing", &spacing,
"description", &description,
"cell-width", &gihparams.cellwidth,
"cell-height", &gihparams.cellheight,
"num-cells", &gihparams.ncells,
"ranks", &rank_bytes,
"selection-modes", &selection,
NULL);
gihparams.cols = pika_image_get_width (image) / gihparams.cellwidth;
gihparams.rows = pika_image_get_height (image) / gihparams.cellheight;
rank = g_bytes_get_data (rank_bytes, NULL);
gihparams.dim = g_bytes_get_size (rank_bytes);
for (gint i = 0; i < gihparams.dim; i++)
{
gihparams.rank[i] = rank[i];
gihparams.selection[i] = g_strdup (selection[i]);
}
g_strfreev (selection);
g_bytes_unref (rank_bytes);
if (status == PIKA_PDB_SUCCESS)
{
PikaProcedure *procedure;
PikaValueArray *save_retvals;
PikaObjectArray *drawables_array;
gchar *paramstring;
paramstring = pika_pixpipe_params_build (&gihparams);
drawables_array = pika_object_array_new (PIKA_TYPE_DRAWABLE, (GObject **) drawables,
n_drawables, FALSE);
procedure = pika_pdb_lookup_procedure (pika_get_pdb (),
"file-gih-save-internal");
save_retvals = pika_procedure_run (procedure,
"image", image,
"num-drawables", n_drawables,
"drawables", drawables_array,
"file", file,
"spacing", spacing,
"name", description,
"params", paramstring,
NULL);
pika_object_array_free (drawables_array);
if (PIKA_VALUES_GET_ENUM (save_retvals, 0) == PIKA_PDB_SUCCESS)
{
parasite = pika_parasite_new ("pika-brush-pipe-name",
PIKA_PARASITE_PERSISTENT,
strlen (description) + 1,
description);
pika_image_attach_parasite (orig_image, parasite);
pika_parasite_free (parasite);
}
else
{
g_set_error (&error, 0, 0,
"Running procedure 'file-gih-save-internal' "
"failed: %s",
pika_pdb_get_last_error (pika_get_pdb ()));
status = PIKA_PDB_EXECUTION_ERROR;
}
g_free (paramstring);
}
pika_pixpipe_params_free (&gihparams);
g_free (description);
out:
if (export == PIKA_EXPORT_EXPORT)
{
pika_image_delete (image);
g_free (drawables);
}
return pika_procedure_new_return_values (procedure, status, error);
}
/* save routines */
static void
gih_remove_guides (PikaProcedureConfig *config,
PikaImage *image)
{
PikaArray *array;
g_object_get (config, "guides", &array, NULL);
if (array != NULL)
{
gint32 *guides = (gint32 *) array->data;
gint n_guides = array->length / 4;
for (gint i = 0; i < n_guides; i++)
pika_image_delete_guide (image, guides[i]);
}
g_clear_pointer (&array, pika_array_free);
g_object_set (config, "guides", NULL, NULL);
}
static void
gih_cell_width_notify (PikaProcedureConfig *config,
const GParamSpec *pspec,
PikaProcedureDialog *dialog)
{
PikaImage *image;
GParamSpec *spec;
gchar *info_text;
gchar *grid_text = NULL;
const gchar *width_warning = _("Width Mismatch!");
gchar *height_warning = _("Height Mismatch!");
GtkWidget *widget;
GtkAdjustment *rank0;
gint dimension;
gint cell_width;
gint image_width;
gint n_horiz_cells;
gint n_vert_cells;
gint cell_height;
gint image_height;
gint n_vert_guides;
gint n_horiz_guides;
gint old_max_cells;
gint max_cells;
gint num_cells;
image = g_object_get_data (G_OBJECT (dialog), "image");
rank0 = g_object_get_data (G_OBJECT (dialog), "rank0");
g_object_get (config,
"cell-width", &cell_width,
"cell-height", &cell_height,
"num-cells", &num_cells,
"dimension", &dimension,
NULL);
spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-width");
image_width = G_PARAM_SPEC_INT (spec)->maximum;
n_horiz_cells = image_width / cell_width;
n_vert_guides = n_horiz_cells - 1;
if (cell_width * n_horiz_cells == image_width)
width_warning = NULL;
spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-height");
image_height = G_PARAM_SPEC_INT (spec)->maximum;
n_vert_cells = image_height / cell_height;
n_horiz_guides = n_vert_cells - 1;
if (cell_height * n_vert_cells == image_height)
height_warning = NULL;
/* TRANSLATORS: \xc3\x97 is the UTF-8 encoding for the multiplication sign. */
if (n_horiz_guides + n_vert_guides > 0)
grid_text = g_strdup_printf (_("Displays as a %d \xc3\x97 %d grid on each layer"),
n_horiz_cells, n_vert_cells);
info_text = g_strdup_printf ("%s%s%s%s%s",
grid_text != NULL ? grid_text : "",
grid_text != NULL && width_warning != NULL ? " / " : "",
width_warning != NULL ? width_warning : "",
(grid_text != NULL || width_warning != NULL) && height_warning != NULL ? " / " : "",
height_warning != NULL ? height_warning : "");
spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "num-cells");
old_max_cells = G_PARAM_SPEC_INT (spec)->maximum;
max_cells = n_horiz_cells * n_vert_cells;
if (num_cells == old_max_cells)
num_cells = max_cells;
else
num_cells = MIN (max_cells, num_cells);
G_PARAM_SPEC_INT (spec)->maximum = max_cells;
widget = pika_procedure_dialog_get_widget (dialog, "num-cells", G_TYPE_NONE);
g_object_set (widget, "upper", (gdouble) max_cells, NULL);
pika_label_spin_set_increments (PIKA_LABEL_SPIN (widget), 1.0, 1.0);
gih_remove_guides (config, image);
widget = pika_procedure_dialog_get_widget (dialog, "info-text", G_TYPE_NONE);
gtk_widget_set_visible (widget, (strlen (info_text) > 0));
if (n_horiz_guides + n_vert_guides > 0)
{
gint32 *guides = NULL;
GValue value = G_VALUE_INIT;
guides = g_new0 (gint32, n_horiz_guides + n_vert_guides);
for (gint i = 0; i < n_vert_guides; i++)
guides[i] = pika_image_add_vguide (image, cell_width * (i + 1));
for (gint i = 0; i < n_horiz_guides; i++)
guides[n_vert_guides + i] = pika_image_add_hguide (image, cell_height * (i + 1));
g_value_init (&value, PIKA_TYPE_INT32_ARRAY);
pika_value_set_int32_array (&value, guides, n_horiz_guides + n_vert_guides);
g_object_set_property (G_OBJECT (config), "guides", &value);
g_free (guides);
}
g_object_set (config,
"info-text", info_text,
"num-cells", num_cells,
NULL);
if (rank0 != NULL && dimension == 1)
gtk_adjustment_set_value (rank0, num_cells);
g_free (info_text);
g_free (grid_text);
}
static void
gih_selection_mode_changed (GtkWidget *widget,
gpointer data)
{
gint index;
index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
g_free (*((gchar **) data));
*((gchar **) data) = g_strdup (selection_modes [index]);
}
static void
gih_dimension_notify (PikaProcedureConfig *config,
const GParamSpec *pspec,
SizeAdjustmentData *data)
{
gint dimension;
gint i;
g_object_get (config, "dimension", &dimension, NULL);
for (i = 0; i < PIKA_PIXPIPE_MAXDIM; i++)
{
gtk_widget_set_sensitive (data->rank_entry[i], i < dimension);
gtk_widget_set_sensitive (data->mode_entry[i], i < dimension);
}
}
static gboolean
gih_save_dialog (PikaImage *image,
PikaProcedure *procedure,
PikaProcedureConfig *config)
{
GtkWidget *dialog;
GtkWidget *dimgrid;
GtkWidget *label;
GtkAdjustment *adjustment;
GtkWidget *spinbutton;
GtkWidget *cb;
gint i;
SizeAdjustmentData cellw_adjust;
SizeAdjustmentData cellh_adjust;
gchar **selection;
GBytes *rank_bytes = NULL;
guint8 rank[PIKA_PIXPIPE_MAXDIM];
gint num_cells;
gint dimension;
gboolean run;
dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure),
PIKA_PROCEDURE_CONFIG (config),
image);
gtk_widget_set_halign (pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
"info-text", NULL, TRUE, FALSE),
GTK_ALIGN_START);
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
"description", "spacing",
"cell-width", "cell-height", "num-cells",
"info-text", "dimension", NULL);
g_object_set_data (G_OBJECT (dialog), "image", image);
g_signal_connect (config, "notify::cell-width",
G_CALLBACK (gih_cell_width_notify),
dialog);
g_signal_connect (config, "notify::cell-height",
G_CALLBACK (gih_cell_width_notify),
dialog);
gih_cell_width_notify (config, NULL, PIKA_PROCEDURE_DIALOG (dialog));
g_signal_connect (config, "notify::dimension",
G_CALLBACK (gih_dimension_notify),
&cellw_adjust);
/*
* Ranks / Selection: ______ ______ ______ ______ ______
*/
g_object_get (config,
"selection-modes", &selection,
"ranks", &rank_bytes,
"num-cells", &num_cells,
NULL);
if (selection == NULL || g_strv_length (selection) != PIKA_PIXPIPE_MAXDIM)
{
GStrvBuilder *builder = g_strv_builder_new ();
gint old_len;
old_len = (selection == NULL ? 0 : g_strv_length (selection));
for (i = 0; i < MIN (old_len, PIKA_PIXPIPE_MAXDIM); i++)
g_strv_builder_add (builder, selection[i]);
for (i = old_len; i < PIKA_PIXPIPE_MAXDIM; i++)
g_strv_builder_add (builder, "random");
g_strfreev (selection);
selection = g_strv_builder_end (builder);
g_strv_builder_unref (builder);
}
if (rank_bytes == NULL)
{
dimension = 1;
rank[0] = 1;
for (i = 1; i < PIKA_PIXPIPE_MAXDIM; i++)
rank[i] = 0;
}
else
{
const guint8 *stored;
stored = g_bytes_get_data (rank_bytes, NULL);
dimension = g_bytes_get_size (rank_bytes);
for (i = 0; i < dimension; i++)
rank[i] = stored[i];
for (i = dimension; i < PIKA_PIXPIPE_MAXDIM; i++)
rank[i] = 1;
}
g_bytes_unref (rank_bytes);
dimension = MAX (dimension, 1);
dimgrid = gtk_grid_new ();
gtk_grid_set_column_spacing (GTK_GRID (dimgrid), 4);
label = gtk_label_new (_("Ranks:"));
gtk_grid_attach (GTK_GRID (dimgrid), label, 0, 0, 1, 1);
gtk_widget_show (label);
for (i = 0; i < PIKA_PIXPIPE_MAXDIM; i++)
{
gsize j;
adjustment = gtk_adjustment_new (rank[i], 1, 100, 1, 1, 0);
if (i == 0)
g_object_set_data (G_OBJECT (dialog), "rank0", adjustment);
spinbutton = pika_spin_button_new (adjustment, 1.0, 0);
gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
gtk_grid_attach (GTK_GRID (dimgrid), spinbutton, 1, i, 1, 1);
gtk_widget_show (spinbutton);
if (i >= dimension)
gtk_widget_set_sensitive (spinbutton, FALSE);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (pika_int_adjustment_update),
&rank[i]);
cellw_adjust.rank_entry[i] = cellh_adjust.rank_entry[i] = spinbutton;
cb = gtk_combo_box_text_new ();
for (j = 0; j < G_N_ELEMENTS (selection_modes); j++)
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cb),
selection_modes[j]);
gtk_combo_box_set_active (GTK_COMBO_BOX (cb), 2); /* random */
for (j = 0; j < G_N_ELEMENTS (selection_modes); j++)
if (!strcmp (selection[i], selection_modes[j]))
{
gtk_combo_box_set_active (GTK_COMBO_BOX (cb), j);
break;
}
gtk_grid_attach (GTK_GRID (dimgrid), cb, 2, i, 1, 1);
gtk_widget_show (cb);
if (i >= dimension)
gtk_widget_set_sensitive (cb, FALSE);
g_signal_connect (GTK_COMBO_BOX (cb), "changed",
G_CALLBACK (gih_selection_mode_changed),
&selection[i]);
cellw_adjust.mode_entry[i] = cellh_adjust.mode_entry[i] = cb;
}
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
dimgrid, TRUE, TRUE, 0);
gtk_widget_show (dimgrid);
run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));
g_object_get (config, "dimension", &dimension, NULL);
rank_bytes = g_bytes_new (rank, sizeof (guint8) * dimension);
g_object_set (config,
"selection-modes", selection,
"ranks", rank_bytes,
NULL);
gih_remove_guides (config, image);
gtk_widget_destroy (dialog);
g_strfreev (selection);
g_bytes_unref (rank_bytes);
return run;
}