/* 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 . */ /* * 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 #include #include #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 { gboolean is_object; union { gpointer object; /* Input images or drawable */ guchar val; /* Mask value to compose with */ } comp; } 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; typedef struct { ComposeInput inputs[MAX_COMPOSE_IMAGES]; /* Image or mask value of input */ gchar compose_type[32]; /* type of composition */ gboolean do_recompose; GimpLayer *source_layer; /* 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 *scales[MAX_COMPOSE_IMAGES]; /* The values color scales */ ComposeInput selected[MAX_COMPOSE_IMAGES]; /* Image Ids or mask values from menus */ gint compose_idx; /* Compose type */ } ComposeInterface; typedef struct _Compose Compose; typedef struct _ComposeClass ComposeClass; struct _Compose { GimpPlugIn parent_instance; }; struct _ComposeClass { GimpPlugInClass parent_class; }; #define COMPOSE_TYPE (compose_get_type ()) #define COMPOSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), COMPOSE_TYPE, Compose)) GType compose_get_type (void) G_GNUC_CONST; static GList * compose_query_procedures (GimpPlugIn *plug_in); static GimpProcedure * compose_create_procedure (GimpPlugIn *plug_in, const gchar *name); static GimpValueArray * compose_run (GimpProcedure *procedure, GimpRunMode run_mode, GimpImage *image, gint n_drawables, GimpDrawable **drawables, GimpProcedureConfig *config, gpointer run_data); 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 GimpImage * compose (const gchar *compose_type, ComposeInput *inputs, gboolean compose_by_drawable, GimpProcedureConfig *config); static GimpImage * create_new_image (GFile *file, guint width, guint height, GimpImageType gdtype, GimpPrecision precision, GimpLayer **layer, GeglBuffer **buffer); static gboolean compose_dialog (GimpProcedure *procedure, GimpProcedureConfig *config, const gchar *compose_type, GimpDrawable *drawable); static gboolean check_gray (GimpImage *image, GimpItem *drawable, gpointer data); static void combo_callback (GimpIntComboBox *cbox, gpointer data); static void scale_callback (GimpLabelSpin *scale, ComposeInput *input); static void check_response (GtkWidget *dialog, gint response, gpointer data); static void type_combo_callback (GimpProcedureConfig *config); G_DEFINE_TYPE (Compose, compose, GIMP_TYPE_PLUG_IN) GIMP_MAIN (COMPOSE_TYPE) DEFINE_STD_SET_I18N /* 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.xcf" }, { "RGBA", N_("RGBA"), 4, { CPN_RGBA_R, CPN_RGBA_G, CPN_RGBA_B, CPN_RGBA_A }, "rgba-compose.xcf" }, { "HSV", N_("HSV"), 3, { CPN_HSV_H, CPN_HSV_S, CPN_HSV_V }, "hsv-compose.xcf" }, { "HSL", N_("HSL"), 3, { CPN_HSL_H, CPN_HSL_S, CPN_HSL_L }, "hsl-compose.xcf" }, { "CMYK", N_("CMYK"), 4, { CPN_CMYK_C, CPN_CMYK_M, CPN_CMYK_Y, CPN_CMYK_K }, "cmyk-compose.xcf" }, { "CIE Lab", N_("LAB"), 3, { CPN_LAB_L, CPN_LAB_A, CPN_LAB_B }, "lab-compose.xcf" }, { "CIE LCH(ab)", N_("LCH"), 3, { CPN_LCH_L, CPN_LCH_C, CPN_LCH_H }, "lch-compose.xcf" }, { "Y'CbCr", N_("YCbCr_ITU_R470"), 3, { CPN_YCBCR_Y, CPN_YCBCR_CB, CPN_YCBCR_CR }, "ycbcr470-compose.xcf" }, { "Y'CbCr709", N_("YCbCr_ITU_R709"), 3, { CPN_YCBCR709_Y, CPN_YCBCR709_CB, CPN_YCBCR709_CR }, "ycbcr709-compose.xcf" }, { "Y'CbCr", N_("YCbCr_ITU_R470_256"), 3, { CPN_YCBCR_Y, CPN_YCBCR_CB, CPN_YCBCR_CR }, "ycbcr470F-compose.xcf" }, { "Y'CbCr709", N_("YCbCr_ITU_R709_256"), 3, { CPN_YCBCR709_Y, CPN_YCBCR709_CB, CPN_YCBCR709_CR }, "ycbcr709F-compose.xcf" } }; static ComposeVals composevals = { {{ 0, }}, /* Image IDs of images to compose or mask values */ "RGB", /* Type of composition */ FALSE, /* Do recompose */ NULL /* source layer */ }; static ComposeInterface composeint = { 0, 0, /* width, height */ { NULL }, /* Label Widgets */ { NULL }, /* Icon Widgets */ { NULL }, /* Menu Widgets */ { NULL }, /* Color Scale Widgets */ {{ 0, }}, /* Image Ids or mask values from menus */ 0 /* Compose type */ }; static void compose_class_init (ComposeClass *klass) { GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = compose_query_procedures; plug_in_class->create_procedure = compose_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void compose_init (Compose *compose) { } static GList * compose_query_procedures (GimpPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (COMPOSE_PROC)); list = g_list_append (list, g_strdup (DRAWABLE_COMPOSE_PROC)); list = g_list_append (list, g_strdup (RECOMPOSE_PROC)); return list; } static GimpProcedure * compose_create_procedure (GimpPlugIn *plug_in, const gchar *name) { GimpProcedure *procedure = NULL; 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, '"'); } if (! strcmp (name, COMPOSE_PROC)) { procedure = gimp_image_procedure_new (plug_in, name, GIMP_PDB_PROC_TYPE_PLUGIN, compose_run, NULL, NULL); gimp_procedure_set_image_types (procedure, "GRAY*"); gimp_procedure_set_sensitivity_mask (procedure, GIMP_PROCEDURE_SENSITIVE_DRAWABLE | GIMP_PROCEDURE_SENSITIVE_DRAWABLES | GIMP_PROCEDURE_SENSITIVE_NO_DRAWABLES); gimp_procedure_set_menu_label (procedure, _("C_ompose...")); gimp_procedure_add_menu_path (procedure, "/Colors/Components"); gimp_procedure_set_documentation (procedure, _("Create an image using multiple " "gray images as color channels"), _("This function creates a new image from " "multiple gray images"), name); gimp_procedure_set_attribution (procedure, "Peter Kirchgessner", "Peter Kirchgessner (peter@kirchgessner.net)", "1997"); gimp_procedure_add_image_argument (procedure, "image-2", _("Image 2"), _("Second input image"), TRUE, G_PARAM_READWRITE); gimp_procedure_add_image_argument (procedure, "image-3", _("Image 3"), _("Third input image"), TRUE, G_PARAM_READWRITE); gimp_procedure_add_image_argument (procedure, "image-4", _("Image 4"), _("Fourth input image"), TRUE, G_PARAM_READWRITE); gimp_procedure_add_choice_argument (procedure, "compose-type", _("Color _model"), type_desc->str, gimp_choice_new_with_values ("rgb", 0, _("RGB"), NULL, "rgba", 1, _("RGBA"), NULL, "hsv", 2, _("HSV"), NULL, "hsl", 3, _("HSL"), NULL, "cmyk", 4, _("CMYK"), NULL, "lab", 5, _("LAB"), NULL, "lch", 6, _("LCH"), NULL, "ycbcr470", 7, _("YCbCr ITU R470"), NULL, "ycbcr709", 8, _("YCbCr ITU R709"), NULL, "ycbcr470f", 9, _("YCbCr ITU R470 256"), NULL, "ycbcr709f", 10, _("YCbCr ITU R709 256"), NULL, NULL), "rgb", G_PARAM_READWRITE); gimp_procedure_add_image_return_value (procedure, "new-image", _("New image"), _("Output image"), FALSE, G_PARAM_READWRITE); } else if (! strcmp (name, DRAWABLE_COMPOSE_PROC)) { procedure = gimp_image_procedure_new (plug_in, name, GIMP_PDB_PROC_TYPE_PLUGIN, compose_run, NULL, NULL); gimp_procedure_set_image_types (procedure, "GRAY*"); gimp_procedure_set_sensitivity_mask (procedure, GIMP_PROCEDURE_SENSITIVE_DRAWABLE); gimp_procedure_set_documentation (procedure, _("Compose an image from multiple " "drawables of gray images"), _("This function creates a new image from " "multiple drawables of gray images"), name); gimp_procedure_set_attribution (procedure, "Peter Kirchgessner", "Peter Kirchgessner (peter@kirchgessner.net)", "1998"); gimp_procedure_add_drawable_argument (procedure, "drawable-2", _("Drawable 2"), _("Second input drawable"), TRUE, G_PARAM_READWRITE); gimp_procedure_add_drawable_argument (procedure, "drawable-3", _("Drawable 3"), _("Third input drawable"), TRUE, G_PARAM_READWRITE); gimp_procedure_add_drawable_argument (procedure, "drawable-4", _("Drawable 4"), _("Fourth input drawable"), TRUE, G_PARAM_READWRITE); gimp_procedure_add_choice_argument (procedure, "compose-type", _("Color _model"), type_desc->str, gimp_choice_new_with_values ("rgb", 0, _("RGB"), NULL, "rgba", 1, _("RGBA"), NULL, "hsv", 2, _("HSV"), NULL, "hsl", 3, _("HSL"), NULL, "cmyk", 4, _("CMYK"), NULL, "lab", 5, _("LAB"), NULL, "lch", 6, _("LCH"), NULL, "ycbcr470", 7, _("YCbCr ITU R470"), NULL, "ycbcr709", 8, _("YCbCr ITU R709"), NULL, "ycbcr470f", 9, _("YCbCr ITU R470 256"), NULL, "ycbcr709f", 10, _("YCbCr ITU R709 256"), NULL, NULL), "rgb", G_PARAM_READWRITE); gimp_procedure_add_image_return_value (procedure, "new-image", _("New image"), _("Output image"), FALSE, G_PARAM_READWRITE); } else if (! strcmp (name, RECOMPOSE_PROC)) { procedure = gimp_image_procedure_new (plug_in, name, GIMP_PDB_PROC_TYPE_PLUGIN, compose_run, NULL, NULL); gimp_procedure_set_image_types (procedure, "GRAY*"); gimp_procedure_set_sensitivity_mask (procedure, GIMP_PROCEDURE_SENSITIVE_DRAWABLE | GIMP_PROCEDURE_SENSITIVE_DRAWABLES | GIMP_PROCEDURE_SENSITIVE_NO_DRAWABLES); gimp_procedure_set_menu_label (procedure, _("R_ecompose")); gimp_procedure_add_menu_path (procedure, "/Colors/Components"); gimp_procedure_set_documentation (procedure, _("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."), name); gimp_procedure_set_attribution (procedure, "Bill Skaggs", "Bill Skaggs", "2004"); } g_string_free (type_desc, TRUE); return procedure; } static GimpValueArray * compose_run (GimpProcedure *procedure, GimpRunMode run_mode, GimpImage *image, gint n_drawables, GimpDrawable **drawables, GimpProcedureConfig *config, gpointer run_data) { GimpValueArray *return_vals; GimpDrawable *drawable = NULL; const gchar *name = gimp_procedure_get_name (procedure); gint compose_by_drawable; gint compose_idx; gint i; gegl_init (NULL, NULL); compose_by_drawable = ! strcmp (name, DRAWABLE_COMPOSE_PROC); if (compose_by_drawable) { if (n_drawables != 1) { GError *error = NULL; g_set_error (&error, GIMP_PLUG_IN_ERROR, 0, _("Procedure '%s' only works with one drawable."), name); return gimp_procedure_new_return_values (procedure, GIMP_PDB_CALLING_ERROR, error); } else { drawable = drawables[0]; } } if (! strcmp (name, RECOMPOSE_PROC)) { GimpParasite *parasite = gimp_image_get_parasite (image, "decompose-data"); if (! parasite) { g_message (_("You can only run 'Recompose' if the active image " "was originally produced by 'Decompose'.")); return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); } else { gchar *parasite_data; guint32 parasite_size; gint source; gint input[4] = { 0, }; gint nret; parasite_data = (gchar *) gimp_parasite_get_data (parasite, ¶site_size); parasite_data = g_strndup (parasite_data, parasite_size); nret = sscanf (parasite_data, "source=%d type=%31s %d %d %d %d", &source, composevals.compose_type, input, input + 1, input + 2, input + 3); gimp_parasite_free (parasite); g_free (parasite_data); if (nret < 5) { g_message (_("Error scanning 'decompose-data' parasite: " "too few layers found")); return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); } compose_by_drawable = TRUE; composevals.do_recompose = TRUE; composevals.source_layer = gimp_layer_get_by_id (source); if (! composevals.source_layer) { g_message (_("Cannot recompose: Specified source layer ID %d " "not found"), source); return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); } for (i = 0; i < MAX_COMPOSE_IMAGES; i++) { composevals.inputs[i].is_object = TRUE; composevals.inputs[i].comp.object = gimp_drawable_get_by_id (input[i]); /* fourth input is optional */ if (i == 2 && nret == 5) break; if (! composevals.inputs[i].comp.object) { g_message (_("Cannot recompose: Specified layer #%d ID %d " "not found"), i + 1, input[i]); return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); } } } } else { composevals.do_recompose = FALSE; switch (run_mode) { case GIMP_RUN_INTERACTIVE: compose_by_drawable = TRUE; /* Get a drawable-ID of the image */ if (! strcmp (name, COMPOSE_PROC)) { GimpLayer **layers; gint nlayers; layers = gimp_image_get_layers (image, &nlayers); if (! layers) { g_message (_("Could not get layers for image %d"), (gint) gimp_image_get_id (image)); return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); } drawable = GIMP_DRAWABLE (layers[0]); g_free (layers); } if (! compose_dialog (procedure, config, composevals.compose_type, drawable)) return gimp_procedure_new_return_values (procedure, GIMP_PDB_CANCEL, NULL); break; case GIMP_RUN_NONINTERACTIVE: if (compose_by_drawable) { composevals.inputs[0].comp.object = drawable; g_object_get (config, "drawable-2", &composevals.inputs[1].comp.object, "drawable-3", &composevals.inputs[2].comp.object, "drawable-4", &composevals.inputs[3].comp.object, NULL); } else { composevals.inputs[0].comp.object = image; g_object_get (config, "image-2", &composevals.inputs[1].comp.object, "image-3", &composevals.inputs[2].comp.object, "image-4", &composevals.inputs[3].comp.object, NULL); } for (i = 0; i < MAX_COMPOSE_IMAGES; i++) { if (! composevals.inputs[i].comp.object) { composevals.inputs[i].is_object = FALSE; composevals.inputs[i].comp.val = 0; } else { composevals.inputs[i].is_object = TRUE; } } break; case GIMP_RUN_WITH_LAST_VALS: compose_by_drawable = TRUE; break; default: break; } } compose_idx = gimp_procedure_config_get_choice_id (config, "compose-type"); if (compose_idx >= 0 && compose_idx < G_N_ELEMENTS (compose_dsc)) { g_strlcpy (composevals.compose_type, compose_dsc[compose_idx].compose_type, sizeof (composevals.compose_type)); } gimp_progress_init (_("Composing")); image = compose (composevals.compose_type, composevals.inputs, compose_by_drawable, config); if (! image) return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); /* Save drawables/images in GimpProcedureConfig */ if (run_mode == GIMP_RUN_NONINTERACTIVE) { if (compose_by_drawable) { g_object_set (config, "drawable-2", composevals.inputs[1].comp.object, "drawable-3", composevals.inputs[2].comp.object, "drawable-4", composevals.inputs[3].comp.object, NULL); } else { g_object_set (config, "image-2", composevals.inputs[1].comp.object, "image-3", composevals.inputs[2].comp.object, "image-4", composevals.inputs[3].comp.object, NULL); } } if (composevals.do_recompose) { gimp_displays_flush (); } else { gimp_image_undo_enable (image); gimp_image_clean_all (image); if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_display_new (image); } return_vals = gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL); if (strcmp (name, RECOMPOSE_PROC)) GIMP_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } 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_object) 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_object) 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_object) 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_object) { 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_object) g_object_unref (temp[i]); g_object_unref (dst_temp); } /* Compose an image from several gray-images */ static GimpImage * compose (const gchar *compose_type, ComposeInput *inputs, gboolean compose_by_drawable, GimpProcedureConfig *config) { gint width, height; gint num_images, compose_idx; gint i, j; gint num_layers; GimpLayer *layer_dst; GimpImage *image_dst; gint first_object; 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 NULL; num_images = compose_dsc[compose_idx].num_images; /* Check that at least one image or one drawable is provided */ first_object = -1; for (i = 0; i < num_images; i++) { if (inputs[i].is_object) { first_object = i; break; } } if (first_object == -1) { g_message (_("At least one image is needed to compose")); return NULL; } /* Check image sizes */ if (compose_by_drawable) { GimpImage *first_image = gimp_item_get_image (inputs[first_object].comp.object); width = gimp_drawable_get_width (inputs[first_object].comp.object); height = gimp_drawable_get_height (inputs[first_object].comp.object); precision = gimp_image_get_precision (first_image); for (j = first_object + 1; j < num_images; j++) { if (inputs[j].is_object) { if ((width != gimp_drawable_get_width (inputs[j].comp.object)) || (height != gimp_drawable_get_height (inputs[j].comp.object))) { g_message (_("Drawables have different size")); return NULL; } } } for (j = 0; j < num_images; j++) { if (inputs[j].is_object) buffer_src[j] = gimp_drawable_get_buffer (inputs[j].comp.object); else buffer_src[j] = NULL; } } else /* Compose by image */ { width = gimp_image_get_width (inputs[first_object].comp.object); height = gimp_image_get_height (inputs[first_object].comp.object); precision = gimp_image_get_precision (inputs[first_object].comp.object); for (j = first_object + 1; j < num_images; j++) { if (inputs[j].is_object) { if ((width != gimp_image_get_width (inputs[j].comp.object)) || (height != gimp_image_get_height (inputs[j].comp.object))) { g_message (_("Images have different size")); return NULL; } } } /* Get first layer/drawable for all input images */ for (j = 0; j < num_images; j++) { if (inputs[j].is_object) { GimpLayer **layers; /* Get first layer of image */ layers = gimp_image_get_layers (inputs[j].comp.object, &num_layers); if (! layers) { g_message (_("Error in getting layer IDs")); return NULL; } /* Get drawable for layer */ buffer_src[j] = gimp_drawable_get_buffer (GIMP_DRAWABLE (layers[0])); g_free (layers); } } } /* Unless recomposing, create new image */ if (composevals.do_recompose) { layer_dst = composevals.source_layer; image_dst = gimp_item_get_image (GIMP_ITEM (layer_dst)); buffer_dst = gimp_drawable_get_shadow_buffer (GIMP_DRAWABLE (layer_dst)); } else { gdtype_dst = ((babl_model (compose_dsc[compose_idx].babl_model) == babl_model ("RGBA")) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE); image_dst = create_new_image (g_file_new_for_path (compose_dsc[compose_idx].filename), width, height, gdtype_dst, precision, &layer_dst, &buffer_dst); } if (! compose_by_drawable) { gdouble xres, yres; gimp_image_get_resolution (inputs[first_object].comp.object, &xres, &yres); gimp_image_set_resolution (image_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_object) g_object_unref (buffer_src[j]); } g_object_unref (buffer_dst); if (composevals.do_recompose) gimp_drawable_merge_shadow (GIMP_DRAWABLE (layer_dst), TRUE); gimp_drawable_update (GIMP_DRAWABLE (layer_dst), 0, 0, gimp_drawable_get_width (GIMP_DRAWABLE (layer_dst)), gimp_drawable_get_height (GIMP_DRAWABLE (layer_dst))); return image_dst; } /* Create an image. Sets layer_ID, drawable and rgn. Returns image */ static GimpImage * create_new_image (GFile *file, guint width, guint height, GimpImageType gdtype, GimpPrecision precision, GimpLayer **layer, GeglBuffer **buffer) { GimpImage *image; 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 = gimp_image_new_with_precision (width, height, gitype, precision); gimp_image_undo_disable (image); gimp_image_set_file (image, file); *layer = gimp_layer_new (image, _("Background"), width, height, gdtype, 100, gimp_image_get_default_new_layer_mode (image)); gimp_image_insert_layer (image, *layer, NULL, 0); *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (*layer)); return image; } static gboolean compose_dialog (GimpProcedure *procedure, GimpProcedureConfig *config, const gchar *compose_type, GimpDrawable *drawable) { GtkWidget *dialog; GtkWidget *main_vbox; GtkWidget *frame; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *grid; GtkSizeGroup *size_group; GimpLayer **layer_list; gint compose_idx; gint nlayers; gint j; gboolean run; /* Check default compose type */ compose_idx = gimp_procedure_config_get_choice_id (config, "compose-type"); /* Save original image width/height */ composeint.width = gimp_drawable_get_width (drawable); composeint.height = gimp_drawable_get_height (drawable); gimp_ui_init (PLUG_IN_BINARY); layer_list = gimp_image_get_layers (gimp_item_get_image (GIMP_ITEM (drawable)), &nlayers); dialog = gimp_procedure_dialog_new (procedure, GIMP_PROCEDURE_CONFIG (config), _("Compose")); gimp_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)); /* Compose type combo */ gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog), "compose-label", _("Compose Channels"), FALSE, FALSE); gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog), "compose-frame", "compose-label", FALSE, "compose-type"); main_vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog), "compose-vbox", "compose-frame", NULL); gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog), "compose-vbox", NULL); /* Channel representation grid */ 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); grid = gtk_grid_new (); gtk_grid_set_row_spacing (GTK_GRID (grid), 6); gtk_grid_set_column_spacing (GTK_GRID (grid), 6); gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0); gtk_widget_show (grid); size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); for (j = 0; j < MAX_COMPOSE_IMAGES; j++) { GtkWidget *image; GtkWidget *label; GtkWidget *combo; GtkWidget *scale; GtkTreeIter iter; GtkTreeModel *model; hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_grid_attach (GTK_GRID (grid), hbox, 0, j, 1, 1); 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 (compose_idx >= 0 && nlayers >= compose_dsc[compose_idx].num_images && j < nlayers) { composeint.selected[j].comp.object = layer_list[j]; } else { composeint.selected[j].comp.object = drawable; } composeint.selected[j].is_object = TRUE; combo = gimp_drawable_combo_box_new (check_gray, NULL, NULL); composeint.channel_menu[j] = combo; 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_ICON_NAME, GIMP_ICON_CHANNEL_GRAY, -1); gtk_grid_attach (GTK_GRID (grid), combo, 1, j, 1, 1); gtk_widget_show (combo); gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); scale = gimp_color_scale_entry_new (NULL, 255.0, 0.0, 255.0, 0); gtk_grid_attach (GTK_GRID (grid), scale, 2, j, 3, 1); gtk_widget_show (scale); composeint.scales[j] = scale; gtk_widget_set_sensitive (scale, 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), gimp_item_get_id (composeint.selected[j].comp.object), G_CALLBACK (combo_callback), GINT_TO_POINTER (j), NULL); } g_free (layer_list); /* Calls the combo callback and sets icons, labels and sensitivity */ g_signal_connect (config, "notify::compose-type", G_CALLBACK (type_combo_callback), NULL); type_combo_callback (config); gtk_widget_show (dialog); run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog)); gtk_widget_destroy (dialog); if (run) { gint j; for (j = 0; j < MAX_COMPOSE_IMAGES; j++) { composevals.inputs[j].is_object = composeint.selected[j].is_object; if (composevals.inputs[j].is_object) composevals.inputs[j].comp.object = composeint.selected[j].comp.object; else composevals.inputs[j].comp.val = composeint.selected[j].comp.val; } strcpy (composevals.compose_type, compose_dsc[compose_idx].compose_type); } return run; } /* Compose interface functions */ static gboolean check_gray (GimpImage *image, GimpItem *drawable, gpointer data) { return ((gimp_image_get_base_type (image) == GIMP_GRAY) && (gimp_image_get_width (image) == composeint.width) && (gimp_image_get_height (image) == 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_object) { 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.scales[n], TRUE); composeint.selected[n].is_object = FALSE; composeint.selected[n].comp.val = gimp_label_spin_get_value (GIMP_LABEL_SPIN (composeint.scales[n])); } else { gtk_widget_set_sensitive (composeint.scales[n], FALSE); composeint.selected[n].is_object = TRUE; composeint.selected[n].comp.object = gimp_drawable_get_by_id (id); } } static void scale_callback (GimpLabelSpin *scale, ComposeInput *input) { input->comp.val = gimp_label_spin_get_value (scale); } static void type_combo_callback (GimpProcedureConfig *config) { gboolean combo4; gboolean scale4; gint compose_idx; gint j; if (config) compose_idx = gimp_procedure_config_get_choice_id (config, "compose-type"); else return; 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_object; gtk_widget_set_sensitive (composeint.scales[3], scale4); }