diff options
Diffstat (limited to 'plug-ins/common/decompose.c')
-rw-r--r-- | plug-ins/common/decompose.c | 973 |
1 files changed, 973 insertions, 0 deletions
diff --git a/plug-ins/common/decompose.c b/plug-ins/common/decompose.c new file mode 100644 index 0000000..b96c317 --- /dev/null +++ b/plug-ins/common/decompose.c @@ -0,0 +1,973 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Decompose plug-in (C) 1997 Peter Kirchgessner + * e-mail: peter@kirchgessner.net, WWW: http://www.kirchgessner.net + * + * Copyright 2013 Martijn van Beers <mail_dev@martijn.at> + * Copyright 2013 Téo Mazars <teo.mazars@ensimag.fr> + * + * 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/>. + */ + +/* Lab colorspace support originally written by Alexey Dyachenko, + * merged into the officical plug-in by Sven Neumann. + */ + +#include "config.h" + +#include <string.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "libgimp/stdplugins-intl.h" + +#define PLUG_IN_PROC "plug-in-decompose" +#define PLUG_IN_PROC_REG "plug-in-decompose-registered" +#define PLUG_IN_BINARY "decompose" +#define PLUG_IN_ROLE "gimp-decompose" + + +/* Description of a component */ +typedef struct +{ + const gchar *babl_name; /* channel's babl_component name */ + const gchar *channel_name; /* name of channel to extract */ + + const gdouble range_min; /* min and max */ + const gdouble range_max; + const gboolean perceptual_channel; /* "correct" the channel in Y' space */ + +} Component; + + +/* Maximum number of images/layers generated by an extraction */ +#define MAX_EXTRACT_IMAGES 4 + +/* Description of an extraction */ +typedef struct +{ + const gchar *type; /* What to extract */ + const gchar *model; /* the babl_model string to use */ + const gboolean dialog; /* Set to TRUE if you want + * this extract function in the dialog */ + const gint num_images; /* Number of images to create */ + + const gboolean clamp; /* clamping values in [0.0, 1.0] */ + + /* the babl_component names of the channels */ + const Component component[MAX_EXTRACT_IMAGES]; + +} Extract; + +typedef struct +{ + gchar extract_type[32]; + gboolean as_layers; + gboolean use_registration; +} DecomposeVals; + + +/* 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 gint32 decompose (gint32 image_id, + gint32 drawable_ID, + const gchar *extract_type, + gint32 *image_ID_dst, + gint32 *num_layers, + gint32 *layer_ID_dst); +static gint32 create_new_image (const gchar *filename, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type, + GimpPrecision precision, + gdouble xres, + gdouble yres, + gint32 *layer_ID); +static gint32 create_new_layer (gint32 image_ID, + gint position, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type); +static void transfer_registration_color (GeglBuffer *src, + GeglBuffer **dst, + gint count); +static void cpn_affine_transform_clamp (GeglBuffer *buffer, + gdouble min, + gdouble max, + gboolean clamp); +static void copy_n_components (GeglBuffer *src, + GeglBuffer **dst, + Extract ext); +static void copy_one_component (GeglBuffer *src, + GeglBuffer *dst, + const char *model, + const Component component, + gboolean clamp); +static gboolean decompose_dialog (void); +static gchar * generate_filename (guint32 image_ID, + guint colorspace, + guint channel); + + +#define CPN_RGBA_R { "R", N_("red"), 0.0, 1.0, FALSE } +#define CPN_RGBA_G { "G", N_("green"), 0.0, 1.0, FALSE } +#define CPN_RGBA_B { "B", N_("blue"), 0.0, 1.0, FALSE } +#define CPN_RGBA_A { "A", N_("alpha"), 0.0, 1.0, TRUE } + +#define CPN_HSV_H { "hue", N_("hue"), 0.0, 1.0, TRUE } +#define CPN_HSV_S { "saturation", N_("saturation"), 0.0, 1.0, TRUE } +#define CPN_HSV_V { "value", N_("value"), 0.0, 1.0, TRUE } + +#define CPN_HSL_H { "hue", N_("hue"), 0.0, 1.0, TRUE } +#define CPN_HSL_S { "saturation", N_("saturation"), 0.0, 1.0, TRUE } +#define CPN_HSL_L { "lightness", N_("lightness"), 0.0, 1.0, TRUE } + +#define CPN_CMYK_C { "Cyan", N_("cyan"), 0.0, 1.0, TRUE } +#define CPN_CMYK_M { "Magenta", N_("magenta"), 0.0, 1.0, TRUE } +#define CPN_CMYK_Y { "Yellow", N_("yellow"), 0.0, 1.0, TRUE } +#define CPN_CMYK_K { "Key", N_("black"), 0.0, 1.0, TRUE } + +#define CPN_LAB_L { "CIE L", N_("L"), 0.0, 100.0, TRUE } +#define CPN_LAB_A { "CIE a", N_("A"), -127.5, 127.5, TRUE } +#define CPN_LAB_B { "CIE b", N_("B"), -127.5, 127.5, TRUE } + +#define CPN_LCH_L { "CIE L", N_("L"), 0.0, 100.0, TRUE } +#define CPN_LCH_C { "CIE C(ab)", N_("C"), 0.0, 200.0, TRUE } +#define CPN_LCH_H { "CIE H(ab)", N_("H"), 0.0, 360.0, TRUE } + +#define CPN_YCBCR_Y { "Y'", N_("luma-y470"), 0.0, 1.0, TRUE } +#define CPN_YCBCR_CB { "Cb", N_("blueness-cb470"), -0.5, 0.5, TRUE } +#define CPN_YCBCR_CR { "Cr", N_("redness-cr470"), -0.5, 0.5, TRUE } + +#define CPN_YCBCR709_Y { "Y'", N_("luma-y709"), 0.0, 1.0, TRUE } +#define CPN_YCBCR709_CB { "Cb", N_("blueness-cb709"), -0.5, 0.5, TRUE } +#define CPN_YCBCR709_CR { "Cr", N_("redness-cr709"), -0.5, 0.5, TRUE } + + +static const Extract extract[] = +{ + { N_("RGB"), "RGB", TRUE, 3, FALSE, { CPN_RGBA_R, CPN_RGBA_G, CPN_RGBA_B } }, + { N_("RGBA"), "RGBA", TRUE, 4, FALSE, { CPN_RGBA_R, CPN_RGBA_G, CPN_RGBA_B, CPN_RGBA_A } }, + + { N_("Red"), "RGB", FALSE, 1, FALSE, { CPN_RGBA_R } }, + { N_("Green"), "RGB", FALSE, 1, FALSE, { CPN_RGBA_G } }, + { N_("Blue"), "RGB", FALSE, 1, FALSE, { CPN_RGBA_B } }, + { N_("Alpha"), "RGBA", TRUE , 1, FALSE, { CPN_RGBA_A } }, + + { N_("HSV"), "HSV", TRUE, 3, FALSE, { CPN_HSV_H, CPN_HSV_S, CPN_HSV_V } }, + { N_("Hue"), "HSV", FALSE, 1, FALSE, { CPN_HSV_H } }, + { N_("Saturation"), "HSV", FALSE, 1, FALSE, { CPN_HSV_S } }, + { N_("Value"), "HSV", FALSE, 1, FALSE, { CPN_HSV_V } }, + + { N_("HSL"), "HSL", TRUE, 3, FALSE, { CPN_HSL_H, CPN_HSL_S, CPN_HSL_L } }, + { N_("Hue (HSL)"), "HSL", FALSE, 1, FALSE, { CPN_HSL_H } }, + { N_("Saturation (HSL)"), "HSL", FALSE, 1, FALSE, { CPN_HSL_S } }, + { N_("Lightness"), "HSL", FALSE, 1, FALSE, { CPN_HSL_L } }, + + { N_("CMYK"), "CMYK", TRUE, 4, FALSE, { CPN_CMYK_C, CPN_CMYK_M, CPN_CMYK_Y, CPN_CMYK_K } }, + { N_("Cyan"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_C } }, + { N_("Magenta"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_M } }, + { N_("Yellow"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_Y } }, + { N_("Black"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_K } }, + + { N_("LAB"), "CIE Lab", TRUE, 3, FALSE, { CPN_LAB_L, CPN_LAB_A, CPN_LAB_B } }, + + { N_("LCH"), "CIE LCH(ab)", TRUE, 3, FALSE, { CPN_LCH_L, CPN_LCH_C, CPN_LCH_H } }, + + { N_("YCbCr_ITU_R470"), "Y'CbCr", TRUE, 3, FALSE, { CPN_YCBCR_Y, CPN_YCBCR_CB, CPN_YCBCR_CR} }, + { N_("YCbCr_ITU_R470_256"), "Y'CbCr", TRUE, 3, TRUE, { CPN_YCBCR_Y, CPN_YCBCR_CB, CPN_YCBCR_CR} }, + + { N_("YCbCr_ITU_R709"), "Y'CbCr709", TRUE, 3, FALSE, { CPN_YCBCR709_Y, CPN_YCBCR709_CB, CPN_YCBCR709_CR} }, + { N_("YCbCr_ITU_R709_256"), "Y'CbCr709", TRUE, 3, TRUE, { CPN_YCBCR709_Y, CPN_YCBCR709_CB, CPN_YCBCR709_CR} } +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static DecomposeVals decovals = +{ + "rgb", /* Decompose type */ + TRUE, /* Decompose to Layers */ + FALSE /* use registration color */ +}; + + +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, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_STRING, "decompose-type", NULL }, + { GIMP_PDB_INT32, "layers-mode", "Create channels as layers in a single image" } + }; + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "new-image", "Output gray image" }, + { GIMP_PDB_IMAGE, "new-image", "Output gray image (N/A for single channel extract)" }, + { GIMP_PDB_IMAGE, "new-image", "Output gray image (N/A for single channel extract)" }, + { GIMP_PDB_IMAGE, "new-image", "Output gray image (N/A for single channel extract)" } + }; + + GString *type_desc; + gint i; + + type_desc = g_string_new ("What to decompose: "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, extract[0].type); + g_string_append_c (type_desc, '"'); + + for (i = 1; i < G_N_ELEMENTS (extract); i++) + { + g_string_append (type_desc, ", "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, extract[i].type); + g_string_append_c (type_desc, '"'); + } + + args[3].description = type_desc->str; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Decompose an image into separate colorspace components"), + "This function creates new gray images with " + "different channel information in each of them", + "Peter Kirchgessner", + "Peter Kirchgessner", + "1997", + N_("_Decompose..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_install_procedure (PLUG_IN_PROC_REG, + N_("Decompose an image into separate colorspace components"), + "This function creates new gray images with " + "different channel information in each of them. " + "Pixels in the foreground color will appear black " + "in all output images. This can be used for " + "things like crop marks that have to show up on " + "all channels.", + "Peter Kirchgessner", + "Peter Kirchgessner, Clarence Risher", + "1997", + N_("_Decompose..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC_REG, "<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[MAX_EXTRACT_IMAGES + 1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gint32 num_images; + gint32 image_ID_extract[MAX_EXTRACT_IMAGES]; + gint32 layer_ID_extract[MAX_EXTRACT_IMAGES]; + gint j; + gint32 layer; + gint32 num_layers; + gint32 image_ID; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + image_ID = param[1].data.d_image; + layer = param[2].data.d_drawable; + + *nreturn_vals = MAX_EXTRACT_IMAGES + 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + for (j = 0; j < MAX_EXTRACT_IMAGES; j++) + { + values[j+1].type = GIMP_PDB_IMAGE; + values[j+1].data.d_int32 = -1; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &decovals); + + /* First acquire information with a dialog */ + if (! decompose_dialog ()) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 4 && nparams != 5 && nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + strncpy (decovals.extract_type, param[3].data.d_string, + sizeof (decovals.extract_type)); + decovals.extract_type[sizeof (decovals.extract_type) - 1] = '\0'; + + decovals.as_layers = nparams > 4 ? param[4].data.d_int32 : FALSE; + decovals.use_registration = (strcmp (name, PLUG_IN_PROC_REG) == 0); + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &decovals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_progress_init (_("Decomposing")); + + num_images = decompose (image_ID, layer, + decovals.extract_type, + image_ID_extract, + &num_layers, + layer_ID_extract); + + if (num_images <= 0) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + /* create decompose-data parasite */ + GString *data = g_string_new (""); + + g_string_printf (data, "source=%d type=%s ", + layer, decovals.extract_type); + + for (j = 0; j < num_layers; j++) + g_string_append_printf (data, "%d ", layer_ID_extract[j]); + + for (j = 0; j < num_images; j++) + { + GimpParasite *parasite; + + values[j+1].data.d_int32 = image_ID_extract[j]; + + gimp_image_undo_enable (image_ID_extract[j]); + gimp_image_clean_all (image_ID_extract[j]); + + parasite = gimp_parasite_new ("decompose-data", + 0, data->len + 1, data->str); + gimp_image_attach_parasite (image_ID_extract[j], parasite); + gimp_parasite_free (parasite); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_display_new (image_ID_extract[j]); + } + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &decovals, sizeof (DecomposeVals)); + } + + gimp_progress_end (); + } + + values[0].data.d_status = status; +} + + +/* Decompose an image. It returns the number of new (gray) images. + * The image IDs for the new images are returned in image_ID_dst. + * On failure, -1 is returned. + */ +static gint32 +decompose (gint32 image_ID, + gint32 drawable_ID, + const gchar *extract_type, + gint32 *image_ID_dst, + gint32 *nlayers, + gint32 *layer_ID_dst) +{ + const gchar *layername; + gint j, extract_idx; + gint height, width, num_layers; + GeglBuffer *src_buffer; + GeglBuffer *dst_buffer[MAX_EXTRACT_IMAGES]; + GimpPrecision precision; + gboolean requirements = FALSE; + gboolean decomp_has_alpha = FALSE; + + extract_idx = -1; /* Search extract type */ + for (j = 0; j < G_N_ELEMENTS (extract); j++) + { + if (g_ascii_strcasecmp (extract_type, extract[j].type) == 0) + { + extract_idx = j; + break; + } + } + if (extract_idx < 0) + return -1; + + num_layers = extract[extract_idx].num_images; + + /* Sanity checks */ + src_buffer = gimp_drawable_get_buffer (drawable_ID); + precision = gimp_image_get_precision (image_ID); + + for (j = 0; j < num_layers; j++) + { + /* FIXME: Not 100% reliable */ + decomp_has_alpha |= ! g_strcmp0 ("alpha", extract[extract_idx].component[j].babl_name); + decomp_has_alpha |= ! g_strcmp0 ("A", extract[extract_idx].component[j].babl_name); + } + + requirements |= (gimp_drawable_is_rgb (drawable_ID)); + requirements |= (gimp_drawable_is_indexed (drawable_ID)); + requirements |= (gimp_drawable_is_gray (drawable_ID) + && gimp_drawable_has_alpha (drawable_ID) + && (num_layers <= 2) + && decomp_has_alpha); + requirements &= (!decomp_has_alpha || gimp_drawable_has_alpha (drawable_ID)); + + if (!requirements) + { + g_message (_("Image not suitable for this decomposition")); + return -1; + } + + width = gegl_buffer_get_width (src_buffer); + height = gegl_buffer_get_height (src_buffer); + + /* Create all new gray images */ + for (j = 0; j < num_layers; j++) + { + gchar *filename; + gdouble xres, yres; + + filename = generate_filename (image_ID, extract_idx, j); + gimp_image_get_resolution (image_ID, &xres, &yres); + + if (decovals.as_layers) + { + layername = gettext (extract[extract_idx].component[j].channel_name); + + if (j == 0) + image_ID_dst[j] = create_new_image (filename, layername, + width, height, GIMP_GRAY, precision, + xres, yres, + layer_ID_dst + j); + else + layer_ID_dst[j] = create_new_layer (image_ID_dst[0], j, layername, + width, height, GIMP_GRAY); + } + else + { + image_ID_dst[j] = create_new_image (filename, NULL, + width, height, GIMP_GRAY, precision, + xres, yres, + layer_ID_dst + j); + } + + g_free (filename); + + dst_buffer[j] = gimp_drawable_get_buffer (layer_ID_dst[j]); + } + + copy_n_components (src_buffer, dst_buffer, + extract[extract_idx]); + + if (decovals.use_registration) + transfer_registration_color (src_buffer, dst_buffer, num_layers); + + gimp_progress_update (1.0); + + g_object_unref (src_buffer); + + for (j = 0; j < num_layers; j++) + { + g_object_unref (dst_buffer[j]); + } + + *nlayers = num_layers; + + return (decovals.as_layers ? 1 : num_layers); +} + + +/* Create an image. Returns layer_ID and image_ID */ +static gint32 +create_new_image (const gchar *filename, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type, + GimpPrecision precision, + gdouble xres, + gdouble yres, + gint32 *layer_ID) +{ + gint32 image_ID; + + image_ID = gimp_image_new_with_precision (width, height, type, precision); + + gimp_image_undo_disable (image_ID); + gimp_image_set_filename (image_ID, filename); + gimp_image_set_resolution (image_ID, xres, yres); + + *layer_ID = create_new_layer (image_ID, 0, + layername, width, height, type); + + return image_ID; +} + + +static gint32 +create_new_layer (gint32 image_ID, + gint position, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type) +{ + gint32 layer_ID; + GimpImageType gdtype = GIMP_RGB_IMAGE; + + switch (type) + { + case GIMP_RGB: + gdtype = GIMP_RGB_IMAGE; + break; + case GIMP_GRAY: + gdtype = GIMP_GRAY_IMAGE; + break; + case GIMP_INDEXED: + gdtype = GIMP_INDEXED_IMAGE; + break; + } + + if (! layername) + layername = _("Background"); + + layer_ID = gimp_layer_new (image_ID, layername, width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, position); + + return layer_ID; +} + +/* Registration Color function */ + +static void +transfer_registration_color (GeglBuffer *src, + GeglBuffer **dst, + gint count) +{ + GimpRGB color, test; + GeglBufferIterator *gi; + const Babl *src_format; + const Babl *dst_format; + gint src_bpp; + gint dst_bpp; + gint i; + gdouble white; + + gimp_context_get_foreground (&color); + white = 1.0; + + src_format = gegl_buffer_get_format (src); + src_bpp = babl_format_get_bytes_per_pixel (src_format); + + dst_format = gegl_buffer_get_format (dst[0]); + dst_bpp = babl_format_get_bytes_per_pixel (dst_format); + + gi = gegl_buffer_iterator_new (src, NULL, 0, NULL, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 10); + + for (i = 0; i < count; i++) + { + gegl_buffer_iterator_add (gi, dst[i], NULL, 0, NULL, + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE); + } + + while (gegl_buffer_iterator_next (gi)) + { + gpointer src_data; + gpointer dst_data[MAX_EXTRACT_IMAGES]; + gint j, k; + + src_data = gi->items[0].data; + for (j = 0; j < count; j++) + dst_data[j] = gi->items[j + 1].data; + + for (k = 0; k < gi->length; k++) + { + gulong pos = k * src_bpp; + + gimp_rgba_set_pixel (&test, src_format, ((guchar *)src_data) + pos); + + if (gimp_rgb_distance (&test, &color) < 1e-6) + { + for (j = 0; j < count; j++) + { + gpointer data = dst_data[j]; + + babl_process (babl_fish (babl_format ("Y double"), dst_format), + &white, (guchar *)data + (k * dst_bpp), 1); + } + } + } + } +} + +static void +cpn_affine_transform_clamp (GeglBuffer *buffer, + gdouble min, + gdouble max, + gboolean clamp) +{ + GeglBufferIterator *gi; + gdouble scale = 1.0 / (max - min); + 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)) + { + guint k; + double *data; + + data = (double*) gi->items[0].data; + + if (clamp) + { + for (k = 0; k < gi->length; k++) + { + data[k] = CLAMP ((data[k] + offset) * scale, 0.0, 1.0); + } + } + else + { + for (k = 0; k < gi->length; k++) + { + data[k] = (data[k] + offset) * scale; + } + } + } +} + +static void +copy_n_components (GeglBuffer *src, + GeglBuffer **dst, + Extract ext) +{ + gint i; + + for (i = 0; i < ext.num_images; i++) + { + gimp_progress_update ((gdouble) i / (gdouble) ext.num_images); + + copy_one_component (src, dst[i], ext.model, ext.component[i], ext.clamp); + } +} + +static void +copy_one_component (GeglBuffer *src, + GeglBuffer *dst, + const gchar *model, + const Component component, + gboolean clamp) +{ + const Babl *component_format; + const Babl *dst_format; + GeglBuffer *temp; + const GeglRectangle *extent; + + /* We are working in linear double precision */ + component_format = babl_format_new (babl_model (model), + babl_type ("double"), + babl_component (component.babl_name), + NULL); + + /* We need to enforce linearity here + * If the output is "Y'", the output of temp is already ok + * If the output is "Y" , it will enforce gamma-decoding. + * A bit tricky and suboptimal... + */ + if (component.perceptual_channel) + dst_format = babl_format ("Y' double"); + else + dst_format = babl_format ("Y double"); + + extent = gegl_buffer_get_extent (src); + temp = gegl_buffer_new (extent, dst_format); + + /* we want to copy the component as is */ + gegl_buffer_set_format (temp, component_format); + gegl_buffer_copy (src, NULL, GEGL_ABYSS_NONE, temp, NULL); + + if (component.range_min != 0.0 || + component.range_max != 1.0 || + clamp) + { + cpn_affine_transform_clamp (temp, + component.range_min, component.range_max, + clamp); + } + + /* This is our new "Y(') double" component buffer */ + gegl_buffer_set_format (temp, NULL); + + /* Now we let babl convert it back to the format that dst needs */ + gegl_buffer_copy (temp, NULL, GEGL_ABYSS_NONE, dst, NULL); + + g_object_unref (temp); +} + +static gboolean +decompose_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *toggle; + gint j; + gint extract_idx; + gboolean run; + + extract_idx = 0; + for (j = 0; j < G_N_ELEMENTS (extract); j++) + { + if (extract[j].dialog && + g_ascii_strcasecmp (decovals.extract_type, extract[j].type) == 0) + { + extract_idx = j; + break; + } + } + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Decompose"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_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); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + 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); + + frame = gimp_frame_new (_("Extract Channels")); + 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); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + 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); + + combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + for (j = 0; j < G_N_ELEMENTS (extract); j++) + { + if (extract[j].dialog) + { + gchar *label = g_strdup (gettext (extract[j].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); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + extract_idx, + G_CALLBACK (gimp_int_combo_box_get_active), + &extract_idx); + + toggle = gtk_check_button_new_with_mnemonic (_("_Decompose to layers")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + decovals.as_layers); + gtk_box_pack_start (GTK_BOX (main_vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &decovals.as_layers); + + toggle = + gtk_check_button_new_with_mnemonic (_("_Foreground as registration color")); + gimp_help_set_help_data (toggle, _("Pixels in the foreground color will " + "appear black in all output images. " + "This can be used for things like crop " + "marks that have to show up on all " + "channels."), PLUG_IN_PROC); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + decovals.use_registration); + gtk_box_pack_start (GTK_BOX (main_vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &decovals.use_registration); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + if (run) + strncpy (decovals.extract_type, extract[extract_idx].type, + sizeof decovals.extract_type - 1); + + return run; +} + +/* Build a filename like <imagename>-<channel>.<extension> */ +gchar * +generate_filename (guint32 image_ID, + guint colorspace, + guint channel) +{ + /* Build a filename like <imagename>-<channel>.<extension> */ + gchar *fname; + gchar *filename; + gchar *extension; + + fname = gimp_image_get_filename (image_ID); + + if (fname) + { + extension = fname + strlen (fname) - 1; + + while (extension >= fname) + { + if (*extension == '.') + break; + extension--; + } + + if (extension >= fname) + { + *(extension++) = '\0'; + + if (decovals.as_layers) + filename = g_strdup_printf ("%s-%s.%s", fname, + gettext (extract[colorspace].type), + extension); + else + filename = g_strdup_printf ("%s-%s.%s", fname, + gettext (extract[colorspace].component[channel].channel_name), + extension); + } + else + { + if (decovals.as_layers) + filename = g_strdup_printf ("%s-%s", fname, + gettext (extract[colorspace].type)); + else + filename = g_strdup_printf ("%s-%s", fname, + gettext (extract[colorspace].component[channel].channel_name)); + } + } + else + { + filename = g_strdup (gettext (extract[colorspace].component[channel].channel_name)); + } + + g_free (fname); + + return filename; +} |