diff options
Diffstat (limited to 'plug-ins/common/compose.c')
-rw-r--r-- | plug-ins/common/compose.c | 1402 |
1 files changed, 1402 insertions, 0 deletions
diff --git a/plug-ins/common/compose.c b/plug-ins/common/compose.c new file mode 100644 index 0000000..2f70579 --- /dev/null +++ b/plug-ins/common/compose.c @@ -0,0 +1,1402 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Compose plug-in (C) 1997,1999 Peter Kirchgessner + * e-mail: peter@kirchgessner.net, WWW: http://www.kirchgessner.net + * + * 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/>. + */ + +/* + * This plug-in composes RGB-images from several types of channels + */ + +/* Lab colorspace support originally written by Alexey Dyachenko, + * merged into the official plug-in by Sven Neumann. + * + * Support for channels empty or filled with a single mask value + * added by Sylvain FORET. + */ + +/* + * All redundant _256 versions of YCbCr* are here only for compatibility . + * They can be dropped for GIMP 3.0 + */ + +#include "config.h" + +#include <string.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "libgimp/stdplugins-intl.h" + + +#define COMPOSE_PROC "plug-in-compose" +#define DRAWABLE_COMPOSE_PROC "plug-in-drawable-compose" +#define RECOMPOSE_PROC "plug-in-recompose" +#define PLUG_IN_BINARY "compose" +#define PLUG_IN_ROLE "gimp-compose" + +/* Maximum number of images to compose */ +#define MAX_COMPOSE_IMAGES 4 + +typedef struct +{ + union + { + gint32 ID; /* Image ID of input images or drawable */ + guchar val; /* Mask value to compose with */ + } comp; + gboolean is_ID; +} ComposeInput; + +/* Description of a component */ +typedef struct +{ + const gchar *babl_name; + const gchar *name; + const gchar *icon; + const float range_min; /* val min of the component */ + const float range_max; /* val max of the component */ + const gboolean is_perceptual; /* Take the componenent from an Y' or Y buffer */ +} COMPONENT_DSC; + +/* Description of a composition */ +typedef struct +{ + const gchar *babl_model; + const gchar *compose_type; /* Type of composition ("RGB", "RGBA",...) */ + gint num_images; /* Number of input images needed */ + /* Channel information */ + const COMPONENT_DSC components[MAX_COMPOSE_IMAGES]; + const gchar *filename; /* Name of new image */ + +} COMPOSE_DSC; + + +/* Declare local functions + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void cpn_affine_transform (GeglBuffer *buffer, + gdouble min, + gdouble max); + +static void fill_buffer_from_components (GeglBuffer *temp[MAX_COMPOSE_IMAGES], + GeglBuffer *dst, + gint num_cpn, + ComposeInput *inputs, + gdouble mask_vals[MAX_COMPOSE_IMAGES]); + +static void perform_composition (COMPOSE_DSC curr_compose_dsc, + GeglBuffer *buffer_src[MAX_COMPOSE_IMAGES], + GeglBuffer *buffer_dst, + ComposeInput *inputs, + gint num_images); + +static gint32 compose (const gchar *compose_type, + ComposeInput *inputs, + gboolean compose_by_drawable); + +static gint32 create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageType gdtype, + GimpPrecision precision, + gint32 *layer_ID, + GeglBuffer **drawable); + +static gboolean compose_dialog (const gchar *compose_type, + gint32 drawable_ID); + +static gboolean check_gray (gint32 image_id, + gint32 drawable_id, + gpointer data); + +static void combo_callback (GimpIntComboBox *cbox, + gpointer data); + +static void scale_callback (GtkAdjustment *adj, + ComposeInput *input); + +static void check_response (GtkWidget *dialog, + gint response, + gpointer data); + +static void type_combo_callback (GimpIntComboBox *combo, + gpointer data); + + + +/* Decompositions availables. + * All the following values have to be kept in sync with those of decompose.c + */ + +#define CPN_RGBA_R { "R", N_("_Red:"), GIMP_ICON_CHANNEL_RED, 0.0, 1.0, FALSE} +#define CPN_RGBA_G { "G", N_("_Green:"), GIMP_ICON_CHANNEL_GREEN, 0.0, 1.0, FALSE} +#define CPN_RGBA_B { "B", N_("_Blue:"), GIMP_ICON_CHANNEL_BLUE, 0.0, 1.0, FALSE} +#define CPN_RGBA_A { "A", N_("_Alpha:"), GIMP_ICON_CHANNEL_ALPHA, 0.0, 1.0, TRUE} + +#define CPN_HSV_H { "hue", N_("_Hue:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSV_S { "saturation", N_("_Saturation:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSV_V { "value", N_("_Value:"), NULL, 0.0, 1.0, TRUE} + +#define CPN_HSL_H { "hue", N_("_Hue:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSL_S { "saturation", N_("_Saturation:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSL_L { "lightness", N_("_Lightness:"), NULL, 0.0, 1.0, TRUE} + +#define CPN_CMYK_C { "Cyan", N_("_Cyan:"), NULL, 0.0, 1.0, TRUE} +#define CPN_CMYK_M { "Magenta", N_("_Magenta:"), NULL, 0.0, 1.0, TRUE} +#define CPN_CMYK_Y { "Yellow", N_("_Yellow:"), NULL, 0.0, 1.0, TRUE} +#define CPN_CMYK_K { "Key", N_("_Black:"), NULL, 0.0, 1.0, TRUE} + +#define CPN_LAB_L { "CIE L", N_("_L:"), NULL, 0.0, 100.0, TRUE} +#define CPN_LAB_A { "CIE a", N_("_A:"), NULL, -127.5, 127.5, TRUE} +#define CPN_LAB_B { "CIE b", N_("_B:"), NULL, -127.5, 127.5, TRUE} + +#define CPN_LCH_L { "CIE L", N_("_L"), NULL, 0.0, 100.0, TRUE} +#define CPN_LCH_C { "CIE C(ab)", N_("_C"), NULL, 0.0, 200.0, TRUE} +#define CPN_LCH_H { "CIE H(ab)", N_("_H"), NULL, 0.0, 360.0, TRUE} + +#define CPN_YCBCR_Y { "Y'", N_("_Luma y470:"), NULL, 0.0, 1.0, TRUE } +#define CPN_YCBCR_CB { "Cb", N_("_Blueness cb470:"), NULL, -0.5, 0.5, TRUE } +#define CPN_YCBCR_CR { "Cr", N_("_Redness cr470:"), NULL, -0.5, 0.5, TRUE } + +#define CPN_YCBCR709_Y { "Y'", N_("_Luma y709:"), NULL, 0.0, 1.0, TRUE } +#define CPN_YCBCR709_CB { "Cb", N_("_Blueness cb709:"), NULL, -0.5, 0.5, TRUE } +#define CPN_YCBCR709_CR { "Cr", N_("_Redness cr709:"), NULL, -0.5, 0.5, TRUE } + + +static COMPOSE_DSC compose_dsc[] = +{ + { "RGB", + N_("RGB"), 3, + { CPN_RGBA_R, + CPN_RGBA_G, + CPN_RGBA_B }, + "rgb-compose" }, + + { "RGBA", + N_("RGBA"), 4, + { CPN_RGBA_R, + CPN_RGBA_G, + CPN_RGBA_B, + CPN_RGBA_A }, + "rgba-compose" }, + + { "HSV", + N_("HSV"), 3, + { CPN_HSV_H, + CPN_HSV_S, + CPN_HSV_V }, + "hsv-compose" }, + + { "HSL", + N_("HSL"), 3, + { CPN_HSL_H, + CPN_HSL_S, + CPN_HSL_L }, + "hsl-compose" }, + + { "CMYK", + N_("CMYK"), 4, + { CPN_CMYK_C, + CPN_CMYK_M, + CPN_CMYK_Y, + CPN_CMYK_K }, + "cmyk-compose" }, + + { "CIE Lab", + N_("LAB"), 3, + { CPN_LAB_L, + CPN_LAB_A, + CPN_LAB_B }, + "lab-compose" }, + + { "CIE LCH(ab)", + N_("LCH"), 3, + { CPN_LCH_L, + CPN_LCH_C, + CPN_LCH_H }, + "lch-compose" }, + + { "Y'CbCr", + N_("YCbCr_ITU_R470"), 3, + { CPN_YCBCR_Y, + CPN_YCBCR_CB, + CPN_YCBCR_CR }, + "ycbcr470-compose" }, + + { "Y'CbCr709", + N_("YCbCr_ITU_R709"), 3, + { CPN_YCBCR709_Y, + CPN_YCBCR709_CB, + CPN_YCBCR709_CR }, + "ycbcr709-compose" }, + + { "Y'CbCr", + N_("YCbCr_ITU_R470_256"), 3, + { CPN_YCBCR_Y, + CPN_YCBCR_CB, + CPN_YCBCR_CR }, + "ycbcr470F-compose" }, + + { "Y'CbCr709", + N_("YCbCr_ITU_R709_256"), 3, + { CPN_YCBCR709_Y, + CPN_YCBCR709_CB, + CPN_YCBCR709_CR }, + "ycbcr709F-compose" } +}; + + +typedef struct +{ + ComposeInput inputs[MAX_COMPOSE_IMAGES]; /* Image IDs or mask value of input */ + gchar compose_type[32]; /* type of composition */ + gboolean do_recompose; + gint32 source_layer_ID; /* for recomposing */ +} ComposeVals; + +/* Dialog structure */ +typedef struct +{ + gint width, height; /* Size of selected image */ + + GtkWidget *channel_label[MAX_COMPOSE_IMAGES]; /* The labels to change */ + GtkWidget *channel_icon[MAX_COMPOSE_IMAGES]; /* The icons */ + GtkWidget *channel_menu[MAX_COMPOSE_IMAGES]; /* The menus */ + GtkWidget *color_scales[MAX_COMPOSE_IMAGES]; /* The values color scales */ + GtkWidget *color_spins[MAX_COMPOSE_IMAGES]; /* The values spin buttons */ + + ComposeInput selected[MAX_COMPOSE_IMAGES]; /* Image Ids or mask values from menus */ + + gint compose_idx; /* Compose type */ +} ComposeInterface; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static ComposeVals composevals = +{ + {{{ 0 }}}, /* Image IDs of images to compose or mask values */ + "rgb", /* Type of composition */ + FALSE, /* Do recompose */ + -1 /* source layer ID */ +}; + +static ComposeInterface composeint = +{ + 0, 0, /* width, height */ + { NULL }, /* Label Widgets */ + { NULL }, /* Icon Widgets */ + { NULL }, /* Menu Widgets */ + { NULL }, /* Color Scale Widgets */ + { NULL }, /* Color Spin Widgets */ + {{{ 0 }}}, /* Image Ids or mask values from menus */ + 0 /* Compose type */ +}; + + +MAIN () + + +static void +query (void) +{ + static GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image1", "First input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (not used)" }, + { GIMP_PDB_IMAGE, "image2", "Second input image" }, + { GIMP_PDB_IMAGE, "image3", "Third input image" }, + { GIMP_PDB_IMAGE, "image4", "Fourth input image" }, + { GIMP_PDB_STRING, "compose-type", NULL } + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "new_image", "Output image" } + }; + + static GimpParamDef drw_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image1", "First input image (not used)" }, + { GIMP_PDB_DRAWABLE, "drawable1", "First input drawable" }, + { GIMP_PDB_DRAWABLE, "drawable2", "Second input drawable" }, + { GIMP_PDB_DRAWABLE, "drawable3", "Third input drawable" }, + { GIMP_PDB_DRAWABLE, "drawable4", "Fourth input drawable" }, + { GIMP_PDB_STRING, "compose-type", NULL } + }; + + static const GimpParamDef drw_return_vals[] = + { + { GIMP_PDB_IMAGE, "new_image", "Output image" } + }; + + static const GimpParamDef recompose_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Image to recompose from" }, + { GIMP_PDB_DRAWABLE, "drawable", "Not used" }, + }; + + GString *type_desc; + gint i; + + type_desc = g_string_new ("What to compose: "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, compose_dsc[0].compose_type); + g_string_append_c (type_desc, '"'); + + for (i = 1; i < G_N_ELEMENTS (compose_dsc); i++) + { + g_string_append (type_desc, ", "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, compose_dsc[i].compose_type); + g_string_append_c (type_desc, '"'); + } + + args[6].description = type_desc->str; + drw_args[6].description = type_desc->str; + + gimp_install_procedure (COMPOSE_PROC, + N_("Create an image using multiple gray images as color channels"), + "This function creates a new image from " + "multiple gray images", + "Peter Kirchgessner", + "Peter Kirchgessner (peter@kirchgessner.net)", + "1997", + N_("C_ompose..."), + "GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (COMPOSE_PROC, "<Image>/Colors/Components"); + + gimp_install_procedure (DRAWABLE_COMPOSE_PROC, + "Compose an image from multiple drawables of gray images", + "This function creates a new image from " + "multiple drawables of gray images", + "Peter Kirchgessner", + "Peter Kirchgessner (peter@kirchgessner.net)", + "1998", + NULL, /* It is not available in interactive mode */ + "GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (drw_args), + G_N_ELEMENTS (drw_return_vals), + drw_args, drw_return_vals); + + gimp_install_procedure (RECOMPOSE_PROC, + N_("Recompose an image that was previously decomposed"), + "This function recombines the grayscale layers produced " + "by Decompose into a single RGB or RGBA layer, and " + "replaces the originally decomposed layer with the " + "result.", + "Bill Skaggs", + "Bill Skaggs", + "2004", + N_("R_ecompose"), + "GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (recompose_args), 0, + recompose_args, NULL); + + gimp_plugin_menu_register (RECOMPOSE_PROC, "<Image>/Colors/Components"); + + g_string_free (type_desc, TRUE); +} + + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gint32 image_ID; + gint32 drawable_ID = -1; + gint compose_by_drawable; + gint i; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + compose_by_drawable = (strcmp (name, DRAWABLE_COMPOSE_PROC) == 0); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_int32 = -1; + + if (strcmp (name, RECOMPOSE_PROC) == 0) + { + GimpParasite *parasite = gimp_image_get_parasite (param[1].data.d_image, + "decompose-data"); + + if (! parasite) + { + g_message (_("You can only run 'Recompose' if the active image " + "was originally produced by 'Decompose'.")); + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + gint nret; + + nret = sscanf (gimp_parasite_data (parasite), + "source=%d type=%31s %d %d %d %d", + &composevals.source_layer_ID, + composevals.compose_type, + &composevals.inputs[0].comp.ID, + &composevals.inputs[1].comp.ID, + &composevals.inputs[2].comp.ID, + &composevals.inputs[3].comp.ID); + + gimp_parasite_free (parasite); + + for (i = 0; i < MAX_COMPOSE_IMAGES; i++) + composevals.inputs[i].is_ID = TRUE; + + if (nret < 5) + { + g_message (_("Error scanning 'decompose-data' parasite: " + "too few layers found")); + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + composevals.do_recompose = TRUE; + compose_by_drawable = TRUE; + } + } + } + else + { + composevals.do_recompose = FALSE; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (name, &composevals); + + compose_by_drawable = TRUE; + + /* The dialog is now drawable based. Get a drawable-ID of the image */ + if (strcmp (name, COMPOSE_PROC) == 0) + { + gint32 *layer_list; + gint nlayers; + + layer_list = gimp_image_get_layers (param[1].data.d_int32, + &nlayers); + if ((layer_list == NULL) || (nlayers <= 0)) + { + g_message (_("Could not get layers for image %d"), + (gint) param[1].data.d_int32); + return; + } + + drawable_ID = layer_list[0]; + g_free (layer_list); + } + else + { + drawable_ID = param[2].data.d_int32; + } + + /* First acquire information with a dialog */ + if (! compose_dialog (composevals.compose_type, drawable_ID)) + return; + + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams < 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + composevals.inputs[0].comp.ID = (compose_by_drawable ? + param[2].data.d_int32 : + param[1].data.d_int32); + composevals.inputs[1].comp.ID = param[3].data.d_int32; + composevals.inputs[2].comp.ID = param[4].data.d_int32; + composevals.inputs[3].comp.ID = param[5].data.d_int32; + + strncpy (composevals.compose_type, param[6].data.d_string, + sizeof (composevals.compose_type)); + composevals.compose_type[sizeof (composevals.compose_type)-1] = '\0'; + + for (i = 0; i < MAX_COMPOSE_IMAGES; i++) + { + if (composevals.inputs[i].comp.ID == -1) + { + composevals.inputs[i].is_ID = FALSE; + composevals.inputs[i].comp.val = 0; + } + else + { + composevals.inputs[i].is_ID = TRUE; + } + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (name, &composevals); + + compose_by_drawable = TRUE; + break; + + default: + break; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_progress_init (_("Composing")); + + image_ID = compose (composevals.compose_type, + composevals.inputs, + compose_by_drawable); + + if (image_ID < 0) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + values[1].data.d_int32 = image_ID; + + if (composevals.do_recompose) + { + gimp_displays_flush (); + } + else + { + gimp_image_undo_enable (image_ID); + gimp_image_clean_all (image_ID); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_display_new (image_ID); + } + } + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (name, &composevals, sizeof (ComposeVals)); + } + + *nreturn_vals = composevals.do_recompose ? 1 : 2; + + values[0].data.d_status = status; +} + +static void +cpn_affine_transform (GeglBuffer *buffer, + gdouble min, + gdouble max) +{ + GeglBufferIterator *gi; + const gdouble scale = max - min; + const gdouble offset = min; + + /* We want to scale values linearly, regardless of the format of the buffer */ + gegl_buffer_set_format (buffer, babl_format ("Y double")); + + gi = gegl_buffer_iterator_new (buffer, NULL, 0, NULL, + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (gi)) + { + gdouble *data = gi->items[0].data; + guint k; + + for (k = 0; k < gi->length; k++) + { + data[k] = data[k] * scale + offset; + } + } +} + +static void +fill_buffer_from_components (GeglBuffer *temp[MAX_COMPOSE_IMAGES], + GeglBuffer *dst, + gint num_cpn, + ComposeInput *inputs, + gdouble mask_vals[MAX_COMPOSE_IMAGES]) +{ + GeglBufferIterator *gi; + gint j; + + gi = gegl_buffer_iterator_new (dst, NULL, 0, NULL, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 10); + + for (j = 0; j < num_cpn; j++) + { + if (inputs[j].is_ID) + gegl_buffer_iterator_add (gi, temp[j], NULL, 0, NULL, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE); + } + + while (gegl_buffer_iterator_next (gi)) + { + gdouble *src_data[MAX_COMPOSE_IMAGES]; + gdouble *dst_data = (gdouble*) gi->items[0].data; + gulong k, count; + + count = 1; + for (j = 0; j < num_cpn; j++) + if (inputs[j].is_ID) + src_data[j] = (gdouble*) gi->items[count++].data; + + for (k = 0; k < gi->length; k++) + { + gulong pos = k * num_cpn; + + for (j = 0; j < num_cpn; j++) + { + if (inputs[j].is_ID) + dst_data[pos+j] = src_data[j][k]; + else + dst_data[pos+j] = mask_vals[j]; + } + } + } +} + +static void +perform_composition (COMPOSE_DSC curr_compose_dsc, + GeglBuffer *buffer_src[MAX_COMPOSE_IMAGES], + GeglBuffer *buffer_dst, + ComposeInput *inputs, + gint num_images) +{ + const Babl *dst_format; + GeglBuffer *temp[MAX_COMPOSE_IMAGES]; + GeglBuffer *dst_temp; + const GeglRectangle *extent = NULL; + + const COMPONENT_DSC *components; + gdouble mask_vals[MAX_COMPOSE_IMAGES]; + gint i; + + components = curr_compose_dsc.components; + + /* Get all individual components in gray buffers */ + for (i = 0; i < num_images; i++) + { + COMPONENT_DSC cpn_dsc = components[i]; + const Babl *gray_format; + + if (cpn_dsc.is_perceptual) + gray_format = babl_format ("Y' double"); + else + gray_format = babl_format ("Y double"); + + if (! inputs[i].is_ID) + { + const Babl *fish = babl_fish (babl_format ("Y' u8"), gray_format); + + babl_process (fish, &inputs[i].comp.val, &mask_vals[i], 1); + + mask_vals[i] = mask_vals[i] * (cpn_dsc.range_max - cpn_dsc.range_min) + cpn_dsc.range_min; + } + else + { + extent = gegl_buffer_get_extent (buffer_src[i]); + + temp[i] = gegl_buffer_new (extent, gray_format); + + gegl_buffer_copy (buffer_src[i], NULL, GEGL_ABYSS_NONE, temp[i], NULL); + + if (cpn_dsc.range_min != 0.0 || cpn_dsc.range_max != 1.0) + cpn_affine_transform (temp[i], cpn_dsc.range_min, cpn_dsc.range_max); + } + + gimp_progress_update ((gdouble) i / (gdouble) (num_images + 1.0)); + } + + dst_format = babl_format_new (babl_model (curr_compose_dsc.babl_model), + babl_type ("double"), + babl_component (components[0].babl_name), + num_images > 1 ? babl_component (components[1].babl_name) : NULL, + num_images > 2 ? babl_component (components[2].babl_name) : NULL, + num_images > 3 ? babl_component (components[3].babl_name) : NULL, + NULL); + + /* extent is not NULL because there is at least one drawable */ + dst_temp = gegl_buffer_new (extent, dst_format); + + /* Gather all individual components in the dst_format buffer */ + fill_buffer_from_components (temp, dst_temp, num_images, inputs, mask_vals); + + gimp_progress_update ((gdouble) num_images / (gdouble) (num_images + 1.0)); + + /* Copy back to the format GIMP wants (and perform the conversion in itself) */ + gegl_buffer_copy (dst_temp, NULL, GEGL_ABYSS_NONE, buffer_dst, NULL); + + for (i = 0; i< num_images; i++) + if( inputs[i].is_ID) + g_object_unref (temp[i]); + + g_object_unref (dst_temp); +} + +/* Compose an image from several gray-images */ +static gint32 +compose (const gchar *compose_type, + ComposeInput *inputs, + gboolean compose_by_drawable) +{ + gint width, height; + gint num_images, compose_idx; + gint i, j; + gint num_layers; + gint32 layer_ID_dst, image_ID_dst; + gint first_ID; + GimpImageType gdtype_dst; + GeglBuffer *buffer_src[MAX_COMPOSE_IMAGES]; + GeglBuffer *buffer_dst; + GimpPrecision precision; + + /* Search type of composing */ + compose_idx = -1; + for (j = 0; j < G_N_ELEMENTS (compose_dsc); j++) + { + if (g_ascii_strcasecmp (compose_type, compose_dsc[j].compose_type) == 0) + { + compose_idx = j; + break; + } + } + + if (compose_idx < 0) + return -1; + + num_images = compose_dsc[compose_idx].num_images; + + /* Check that at least one image or one drawable is provided */ + first_ID = -1; + for (i = 0; i < num_images; i++) + { + if (inputs[i].is_ID) + { + first_ID = i; + break; + } + } + + if (-1 == first_ID) + { + g_message (_("At least one image is needed to compose")); + return -1; + } + + /* Check image sizes */ + if (compose_by_drawable) + { + gint32 first_image = gimp_item_get_image (inputs[first_ID].comp.ID); + + if (! gimp_item_is_valid (inputs[first_ID].comp.ID)) + { + g_message (_("Specified layer %d not found"), + inputs[first_ID].comp.ID); + return -1; + } + + width = gimp_drawable_width (inputs[first_ID].comp.ID); + height = gimp_drawable_height (inputs[first_ID].comp.ID); + + precision = gimp_image_get_precision (first_image); + + for (j = first_ID + 1; j < num_images; j++) + { + if (inputs[j].is_ID) + { + if (! gimp_item_is_valid (inputs[j].comp.ID)) + { + g_message (_("Specified layer %d not found"), + inputs[j].comp.ID); + return -1; + } + + if ((width != gimp_drawable_width (inputs[j].comp.ID)) || + (height != gimp_drawable_height (inputs[j].comp.ID))) + { + g_message (_("Drawables have different size")); + return -1; + } + } + } + + for (j = 0; j < num_images; j++) + { + if (inputs[j].is_ID) + buffer_src[j] = gimp_drawable_get_buffer (inputs[j].comp.ID); + else + buffer_src[j] = NULL; + } + } + else /* Compose by image ID */ + { + width = gimp_image_width (inputs[first_ID].comp.ID); + height = gimp_image_height (inputs[first_ID].comp.ID); + + precision = gimp_image_get_precision (inputs[first_ID].comp.ID); + + for (j = first_ID + 1; j < num_images; j++) + { + if (inputs[j].is_ID) + { + if ((width != gimp_image_width (inputs[j].comp.ID)) || + (height != gimp_image_height (inputs[j].comp.ID))) + { + g_message (_("Images have different size")); + return -1; + } + } + } + + /* Get first layer/drawable for all input images */ + for (j = 0; j < num_images; j++) + { + if (inputs[j].is_ID) + { + gint32 *layers; + + /* Get first layer of image */ + layers = gimp_image_get_layers (inputs[j].comp.ID, &num_layers); + + if (! layers || (num_layers <= 0)) + { + g_message (_("Error in getting layer IDs")); + return -1; + } + + /* Get drawable for layer */ + buffer_src[j] = gimp_drawable_get_buffer (layers[0]); + g_free (layers); + } + } + } + + /* Unless recomposing, create new image */ + if (composevals.do_recompose) + { + layer_ID_dst = composevals.source_layer_ID; + + if (! gimp_item_is_valid (layer_ID_dst)) + { + g_message (_("Unable to recompose, source layer not found")); + return -1; + } + + image_ID_dst = gimp_item_get_image (layer_ID_dst); + + buffer_dst = gimp_drawable_get_shadow_buffer (layer_ID_dst); + } + else + { + gdtype_dst = ((babl_model (compose_dsc[compose_idx].babl_model) == babl_model ("RGBA")) ? + GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE); + + image_ID_dst = create_new_image (compose_dsc[compose_idx].filename, + width, height, gdtype_dst, precision, + &layer_ID_dst, &buffer_dst); + } + + if (! compose_by_drawable) + { + gdouble xres, yres; + + gimp_image_get_resolution (inputs[first_ID].comp.ID, &xres, &yres); + gimp_image_set_resolution (image_ID_dst, xres, yres); + } + + perform_composition (compose_dsc[compose_idx], + buffer_src, + buffer_dst, + inputs, + num_images); + + gimp_progress_update (1.0); + + for (j = 0; j < num_images; j++) + { + if (inputs[j].is_ID) + g_object_unref (buffer_src[j]); + } + + g_object_unref (buffer_dst); + + if (composevals.do_recompose) + gimp_drawable_merge_shadow (layer_ID_dst, TRUE); + + gimp_drawable_update (layer_ID_dst, 0, 0, + gimp_drawable_width (layer_ID_dst), + gimp_drawable_height (layer_ID_dst)); + + return image_ID_dst; +} + + +/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */ +static gint32 +create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageType gdtype, + GimpPrecision precision, + gint32 *layer_ID, + GeglBuffer **buffer) +{ + gint32 image_ID; + GimpImageBaseType gitype; + + if ((gdtype == GIMP_GRAY_IMAGE) || (gdtype == GIMP_GRAYA_IMAGE)) + gitype = GIMP_GRAY; + else if ((gdtype == GIMP_INDEXED_IMAGE) || (gdtype == GIMP_INDEXEDA_IMAGE)) + gitype = GIMP_INDEXED; + else + gitype = GIMP_RGB; + + image_ID = gimp_image_new_with_precision (width, height, gitype, precision); + + gimp_image_undo_disable (image_ID); + gimp_image_set_filename (image_ID, filename); + + *layer_ID = gimp_layer_new (image_ID, _("Background"), width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, *layer_ID, -1, 0); + + *buffer = gimp_drawable_get_buffer (*layer_ID); + + return image_ID; +} + + +static gboolean +compose_dialog (const gchar *compose_type, + gint32 drawable_ID) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *table; + GtkSizeGroup *size_group; + gint32 *layer_list; + gint nlayers; + gint j; + gboolean run; + + /* Check default compose type */ + composeint.compose_idx = 0; + for (j = 0; j < G_N_ELEMENTS (compose_dsc); j++) + { + if (g_ascii_strcasecmp (compose_type, compose_dsc[j].compose_type) == 0) + { + composeint.compose_idx = j; + break; + } + } + + /* Save original image width/height */ + composeint.width = gimp_drawable_width (drawable_ID); + composeint.height = gimp_drawable_height (drawable_ID); + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + layer_list = gimp_image_get_layers (gimp_item_get_image (drawable_ID), + &nlayers); + + dialog = gimp_dialog_new (_("Compose"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, COMPOSE_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_signal_connect (dialog, "response", + G_CALLBACK (check_response), + NULL); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + /* Compose type combo */ + + frame = gimp_frame_new (_("Compose Channels")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_widget_show (hbox); + + size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + label = gtk_label_new_with_mnemonic (_("Color _model:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gtk_size_group_add_widget (size_group, label); + g_object_unref (size_group); + + combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + for (j = 0; j < G_N_ELEMENTS (compose_dsc); j++) + { + gchar *label = g_strdup (gettext (compose_dsc[j].compose_type)); + gchar *l; + + for (l = label; *l; l++) + if (*l == '-' || *l == '_') + *l = ' '; + + gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (combo), + GIMP_INT_STORE_LABEL, label, + GIMP_INT_STORE_VALUE, j, + -1); + g_free (label); + } + + gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + /* Channel representation table */ + + frame = gimp_frame_new (_("Channel Representations")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + table = gtk_table_new (MAX_COMPOSE_IMAGES, 4, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + for (j = 0; j < MAX_COMPOSE_IMAGES; j++) + { + GtkWidget *image; + GtkWidget *label; + GtkWidget *combo; + GtkObject *scale; + GtkTreeIter iter; + GtkTreeModel *model; + GdkPixbuf *ico; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_table_attach (GTK_TABLE (table), hbox, 0, 1, j, j + 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + gtk_size_group_add_widget (size_group, hbox); + + composeint.channel_icon[j] = image = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + composeint.channel_label[j] = label = gtk_label_new_with_mnemonic (""); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + if (composeint.compose_idx >= 0 && + nlayers >= compose_dsc[composeint.compose_idx].num_images && + j < nlayers) + { + composeint.selected[j].comp.ID = layer_list[j]; + } + else + { + composeint.selected[j].comp.ID = drawable_ID; + } + + composeint.selected[j].is_ID = TRUE; + + combo = gimp_drawable_combo_box_new (check_gray, NULL); + composeint.channel_menu[j] = combo; + + ico = gtk_widget_render_icon (dialog, + GIMP_ICON_CHANNEL_GRAY, + GTK_ICON_SIZE_BUTTON, NULL); + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + GIMP_INT_STORE_VALUE, -1, + GIMP_INT_STORE_LABEL, _("Mask value"), + GIMP_INT_STORE_PIXBUF, ico, + -1); + g_object_unref (ico); + gtk_table_attach (GTK_TABLE (table), combo, 1, 2, j, j + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + scale = gimp_color_scale_entry_new (GTK_TABLE (table), 2, j, NULL, + 100, 4, + 255.0, 0.0, 255.0, 1.0, 10.0, 0, + NULL, NULL); + composeint.color_scales[j] = GIMP_SCALE_ENTRY_SCALE (scale); + composeint.color_spins[j] = GIMP_SCALE_ENTRY_SPINBUTTON (scale); + + gtk_widget_set_sensitive (composeint.color_scales[j], FALSE); + gtk_widget_set_sensitive (composeint.color_spins[j], FALSE); + + g_signal_connect (scale, "value-changed", + G_CALLBACK (scale_callback), + &composeint.selected[j]); + + /* This has to be connected last otherwise it will emit before + * combo_callback has any scale and spinbutton to work with + */ + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + composeint.selected[j].comp.ID, + G_CALLBACK (combo_callback), + GINT_TO_POINTER (j)); + } + + g_free (layer_list); + + /* Calls the combo callback and sets icons, labels and sensitivity */ + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + composeint.compose_idx, + G_CALLBACK (type_combo_callback), + NULL); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + if (run) + { + gint j; + + for (j = 0; j < MAX_COMPOSE_IMAGES; j++) + { + composevals.inputs[j].is_ID = composeint.selected[j].is_ID; + + if (composevals.inputs[j].is_ID) + composevals.inputs[j].comp.ID = composeint.selected[j].comp.ID; + else + composevals.inputs[j].comp.val = composeint.selected[j].comp.val; + } + + strcpy (composevals.compose_type, + compose_dsc[composeint.compose_idx].compose_type); + } + + return run; +} + +/* Compose interface functions */ + +static gboolean +check_gray (gint32 image_id, + gint32 drawable_id, + gpointer data) + +{ + return ((gimp_image_base_type (image_id) == GIMP_GRAY) && + (gimp_image_width (image_id) == composeint.width) && + (gimp_image_height (image_id) == composeint.height)); +} + +static void +check_response (GtkWidget *dialog, + gint response, + gpointer data) +{ + switch (response) + { + case GTK_RESPONSE_OK: + { + gint i; + gint nb = 0; + gboolean has_image = FALSE; + + nb = compose_dsc[composeint.compose_idx].num_images; + + for (i = 0; i < nb; i++) + { + if (composeint.selected[i].is_ID) + { + has_image = TRUE; + break; + } + } + + if (! has_image) + { + GtkWidget *d; + + g_signal_stop_emission_by_name (dialog, "response"); + d = gtk_message_dialog_new (GTK_WINDOW (dialog), + GTK_DIALOG_DESTROY_WITH_PARENT | + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("At least one image is needed to compose")); + + gtk_dialog_run (GTK_DIALOG (d)); + gtk_widget_destroy (d); + } + } + break; + + default: + break; + } +} + +static void +combo_callback (GimpIntComboBox *widget, + gpointer data) +{ + gint id; + gint n; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &id); + n = GPOINTER_TO_INT (data); + + if (id == -1) + { + gtk_widget_set_sensitive (composeint.color_scales[n], TRUE); + gtk_widget_set_sensitive (composeint.color_spins[n], TRUE); + + composeint.selected[n].is_ID = FALSE; + composeint.selected[n].comp.val = + gtk_range_get_value (GTK_RANGE (composeint.color_scales[n])); + } + else + { + gtk_widget_set_sensitive (composeint.color_scales[n], FALSE); + gtk_widget_set_sensitive (composeint.color_spins[n], FALSE); + + composeint.selected[n].is_ID = TRUE; + composeint.selected[n].comp.ID = id; + } +} + +static void +scale_callback (GtkAdjustment *adj, + ComposeInput *input) +{ + input->comp.val = gtk_adjustment_get_value (adj); +} + +static void +type_combo_callback (GimpIntComboBox *combo, + gpointer data) +{ + if (gimp_int_combo_box_get_active (combo, &composeint.compose_idx)) + { + gboolean combo4; + gboolean scale4; + gint compose_idx; + gint j; + + compose_idx = composeint.compose_idx; + + for (j = 0; j < MAX_COMPOSE_IMAGES; j++) + { + GtkWidget *label = composeint.channel_label[j]; + GtkWidget *image = composeint.channel_icon[j]; + const gchar *text = compose_dsc[compose_idx].components[j].name; + const gchar *icon = compose_dsc[compose_idx].components[j].icon; + + gtk_label_set_text_with_mnemonic (GTK_LABEL (label), + text ? gettext (text) : ""); + + if (icon) + { + gtk_image_set_from_icon_name (GTK_IMAGE (image), + icon, GTK_ICON_SIZE_BUTTON); + gtk_widget_show (image); + } + else + { + gtk_image_clear (GTK_IMAGE (image)); + gtk_widget_hide (image); + } + } + + /* Set sensitivity of last menu */ + combo4 = (compose_dsc[compose_idx].num_images == 4); + gtk_widget_set_sensitive (composeint.channel_menu[3], combo4); + + scale4 = combo4 && !composeint.selected[3].is_ID; + gtk_widget_set_sensitive (composeint.color_scales[3], scale4); + gtk_widget_set_sensitive (composeint.color_spins[3], scale4); + } +} |