gimp/plug-ins/common/compose.c
Alx Sa d80ccb08d8 plug-ins: Include filters when composing
Since we use gimp_drawable_get_buffer () to get the
pixel data when composing or recomposing, NDE filters
were not being included in the final output.
This patch temporarily duplicates the layer, merges the
filters, and then retrieves the GeglBuffer from the merged
version of the layer rather than the raw pixels.
2025-01-01 21:26:09 -05:00

1547 lines
53 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Compose plug-in (C) 1997,1999 Peter Kirchgessner
* e-mail: peter@kirchgessner.net, WWW: http://www.kirchgessner.net
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This plug-in composes RGB-images from several types of channels
*/
/* Lab colorspace support originally written by Alexey Dyachenko,
* merged into the official plug-in by Sven Neumann.
*
* Support for channels empty or filled with a single mask value
* added by Sylvain FORET.
*/
/*
* All redundant _256 versions of YCbCr* are here only for compatibility .
* They can be dropped for GIMP 3.0
*/
#include "config.h"
#include <string.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#define COMPOSE_PROC "plug-in-compose"
#define DRAWABLE_COMPOSE_PROC "plug-in-drawable-compose"
#define RECOMPOSE_PROC "plug-in-recompose"
#define PLUG_IN_BINARY "compose"
#define PLUG_IN_ROLE "gimp-compose"
/* Maximum number of images to compose */
#define MAX_COMPOSE_IMAGES 4
typedef struct
{
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,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data);
static void cpn_affine_transform (GeglBuffer *buffer,
gdouble min,
gdouble max);
static GeglBuffer * get_buffer_with_filters
(GimpLayer *layer);
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, "<Image>/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, "<Image>/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,
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 (gimp_core_object_array_get_length ((GObject **) 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, &parasite_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;
layers = gimp_image_get_layers (image);
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;
}
}
if (strcmp (name, RECOMPOSE_PROC) != 0)
{
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 GeglBuffer *
get_buffer_with_filters (GimpLayer *layer)
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
GimpLayer *temp = gimp_layer_copy (layer);
GeglBuffer *src_buffer;
GeglBuffer *dst_buffer;
gimp_image_insert_layer (image, temp, NULL,
gimp_image_get_item_position (image,
GIMP_ITEM (layer)));
gimp_drawable_merge_filters (GIMP_DRAWABLE (temp));
src_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (temp));
dst_buffer = gegl_buffer_new (gegl_buffer_get_extent (src_buffer),
gegl_buffer_get_format (src_buffer));
gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dst_buffer, NULL);
gimp_image_remove_layer (image, temp);
g_object_unref (src_buffer);
return dst_buffer;
}
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;
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] = get_buffer_with_filters (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);
if (! layers)
{
g_message (_("Error in getting layer IDs"));
return NULL;
}
/* Get drawable for layer */
buffer_src[j] = get_buffer_with_filters (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 = gimp_core_object_array_get_length ((GObject **) layer_list);
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);
}