/* 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 { 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, "/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, "/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); } }