PIKApp/plug-ins/common/depth-merge.c

1158 lines
39 KiB
C

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Depth Merge -- Combine two image layers via corresponding depth maps
* Copyright (C) 1997, 1998 Sean Cier (scier@PostHorizon.com)
*
* 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.
*/
/* Version 1.0.0: (14 August 1998)
* Math optimizations, miscellaneous speedups
*
* Version 0.1: (6 July 1997)
* Initial Release
*/
#include "config.h"
#include <libpika/pika.h>
#include <libpika/pikaui.h>
#include "libpika/stdplugins-intl.h"
#define DEBUG
#ifndef LERP
#define LERP(frac,a,b) ((frac)*(b) + (1-(frac))*(a))
#endif
#define MUL255(i) ((i)*256 - (i))
#define DIV255(i) (((i) + (i)/256 + 1) / 256)
#define PLUG_IN_PROC "plug-in-depth-merge"
#define PLUG_IN_VERSION "August 1998"
#define PLUG_IN_BINARY "depth-merge"
#define PLUG_IN_ROLE "pika-depth-merge"
#define PREVIEW_SIZE 256
/* ----- DepthMerge ----- */
struct _DepthMerge;
typedef struct _DepthMergeInterface
{
gboolean active;
GtkWidget *dialog;
GtkWidget *preview;
gint previewWidth;
gint previewHeight;
guchar *previewSource1;
guchar *previewSource2;
guchar *previewDepthMap1;
guchar *previewDepthMap2;
} DepthMergeInterface;
typedef struct _DepthMergeParams
{
gint32 result_id;
gint32 source1_id;
gint32 source2_id;
gint32 depthMap1_id;
gint32 depthMap2_id;
gfloat overlap;
gfloat offset;
gfloat scale1;
gfloat scale2;
} DepthMergeParams;
typedef struct _DepthMerge
{
DepthMergeInterface *interface;
DepthMergeParams params;
PikaDrawable *resultDrawable;
PikaDrawable *source1Drawable;
PikaDrawable *source2Drawable;
PikaDrawable *depthMap1Drawable;
PikaDrawable *depthMap2Drawable;
gint selectionX;
gint selectionY;
gint selectionWidth;
gint selectionHeight;
gint resultHasAlpha;
} DepthMerge;
typedef struct _Merge Merge;
typedef struct _MergeClass MergeClass;
struct _Merge
{
PikaPlugIn parent_instance;
};
struct _MergeClass
{
PikaPlugInClass parent_class;
};
#define MERGE_TYPE (merge_get_type ())
#define MERGE (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MERGE_TYPE, Merge))
GType merge_get_type (void) G_GNUC_CONST;
static GList * merge_query_procedures (PikaPlugIn *plug_in);
static PikaProcedure * merge_create_procedure (PikaPlugIn *plug_in,
const gchar *name);
static PikaValueArray * merge_run (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
const PikaValueArray *args,
gpointer run_data);
static void DepthMerge_initParams (DepthMerge *dm);
static gboolean DepthMerge_construct (DepthMerge *dm);
static void DepthMerge_destroy (DepthMerge *dm);
static gint32 DepthMerge_execute (DepthMerge *dm);
static void DepthMerge_executeRegion (DepthMerge *dm,
guchar *source1Row,
guchar *source2Row,
guchar *depthMap1Row,
guchar *depthMap2Row,
guchar *resultRow,
gint length);
static gboolean DepthMerge_dialog (DepthMerge *dm);
static void DepthMerge_buildPreviewSourceImage (DepthMerge *dm);
static void DepthMerge_updatePreview (DepthMerge *dm);
static gboolean dm_constraint (PikaImage *image,
PikaItem *item,
gpointer data);
static void dialogSource1ChangedCallback (GtkWidget *widget,
DepthMerge *dm);
static void dialogSource2ChangedCallback (GtkWidget *widget,
DepthMerge *dm);
static void dialogDepthMap1ChangedCallback (GtkWidget *widget,
DepthMerge *dm);
static void dialogDepthMap2ChangedCallback (GtkWidget *widget,
DepthMerge *dm);
static void dialogValueScaleUpdateCallback (PikaLabelSpin *scale,
gfloat *value);
static void util_fillReducedBuffer (guchar *dest,
const Babl *dest_format,
gint destWidth,
gint destHeight,
PikaDrawable *sourceDrawable,
gint x0,
gint y0,
gint sourceWidth,
gint sourceHeight);
G_DEFINE_TYPE (Merge, merge, PIKA_TYPE_PLUG_IN)
PIKA_MAIN (MERGE_TYPE)
DEFINE_STD_SET_I18N
static void
merge_class_init (MergeClass *klass)
{
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = merge_query_procedures;
plug_in_class->create_procedure = merge_create_procedure;
plug_in_class->set_i18n = STD_SET_I18N;
}
static void
merge_init (Merge *merge)
{
}
static GList *
merge_query_procedures (PikaPlugIn *plug_in)
{
return g_list_append (NULL, g_strdup (PLUG_IN_PROC));
}
static PikaProcedure *
merge_create_procedure (PikaPlugIn *plug_in,
const gchar *name)
{
PikaProcedure *procedure = NULL;
if (! strcmp (name, PLUG_IN_PROC))
{
procedure = pika_image_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
merge_run, NULL, NULL);
pika_procedure_set_image_types (procedure, "RGB*, GRAY*");
pika_procedure_set_sensitivity_mask (procedure,
PIKA_PROCEDURE_SENSITIVE_DRAWABLE);
pika_procedure_set_menu_label (procedure, _("_Depth Merge..."));
pika_procedure_add_menu_path (procedure, "<Image>/Filters/Combine");
pika_procedure_set_documentation (procedure,
_("Combine two images using depth "
"maps (z-buffers)"),
"Taking as input two full-color, "
"full-alpha images and two "
"corresponding grayscale depth maps, "
"this plug-in combines the images based "
"on which is closer (has a lower depth "
"map value) at each point.",
name);
pika_procedure_set_attribution (procedure,
"Sean Cier",
"Sean Cier",
PLUG_IN_VERSION);
PIKA_PROC_ARG_DRAWABLE (procedure, "source-1",
"Source 1",
"Source 1",
TRUE,
G_PARAM_READWRITE);
PIKA_PROC_ARG_DRAWABLE (procedure, "source-2",
"Source 2",
"Source 2",
TRUE,
G_PARAM_READWRITE);
PIKA_PROC_ARG_DRAWABLE (procedure, "depth-map-1",
"Depth map 1",
"Depth map 1",
TRUE,
G_PARAM_READWRITE);
PIKA_PROC_ARG_DRAWABLE (procedure, "depth-map-2",
"Depth map 2",
"Depth map 2",
TRUE,
G_PARAM_READWRITE);
PIKA_PROC_ARG_DOUBLE (procedure, "overlap",
"Overlap",
"Overlap",
0, 2, 0,
G_PARAM_READWRITE);
PIKA_PROC_ARG_DOUBLE (procedure, "offset",
"Offset",
"Depth relative offset",
-1, 1, 0,
G_PARAM_READWRITE);
PIKA_PROC_ARG_DOUBLE (procedure, "scale-1",
"Scale 1",
"Depth relative scale 1",
-1, 1, 1,
G_PARAM_READWRITE);
PIKA_PROC_ARG_DOUBLE (procedure, "scale-2",
"Scale 2",
"Depth relative scale 2",
-1, 1, 1,
G_PARAM_READWRITE);
}
return procedure;
}
static PikaValueArray *
merge_run (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
const PikaValueArray *args,
gpointer run_data)
{
PikaDrawable *drawable;
DepthMerge dm;
gegl_init (NULL, NULL);
if (n_drawables != 1)
{
GError *error = NULL;
g_set_error (&error, PIKA_PLUG_IN_ERROR, 0,
_("Procedure '%s' only works with one drawable."),
PLUG_IN_PROC);
return pika_procedure_new_return_values (procedure,
PIKA_PDB_CALLING_ERROR,
error);
}
else
{
drawable = drawables[0];
}
DepthMerge_initParams (&dm);
switch (run_mode)
{
case PIKA_RUN_INTERACTIVE:
pika_get_data (PLUG_IN_PROC, &dm.params);
dm.params.result_id = pika_item_get_id (PIKA_ITEM (drawable));
if (! DepthMerge_construct (&dm))
{
return pika_procedure_new_return_values (procedure,
PIKA_PDB_EXECUTION_ERROR,
NULL);
}
if (! DepthMerge_dialog (&dm))
{
DepthMerge_destroy (&dm);
return pika_procedure_new_return_values (procedure,
PIKA_PDB_CANCEL,
NULL);
}
break;
case PIKA_RUN_NONINTERACTIVE:
dm.params.result_id = pika_item_get_id (PIKA_ITEM (drawable));
dm.params.source1_id = PIKA_VALUES_GET_DRAWABLE_ID (args, 0);
dm.params.source2_id = PIKA_VALUES_GET_DRAWABLE_ID (args, 1);
dm.params.depthMap1_id = PIKA_VALUES_GET_DRAWABLE_ID (args, 2);
dm.params.depthMap2_id = PIKA_VALUES_GET_DRAWABLE_ID (args, 3);
dm.params.overlap = PIKA_VALUES_GET_DOUBLE (args, 4);
dm.params.offset = PIKA_VALUES_GET_DOUBLE (args, 5);
dm.params.scale1 = PIKA_VALUES_GET_DOUBLE (args, 6);
dm.params.scale2 = PIKA_VALUES_GET_DOUBLE (args, 7);
if (! DepthMerge_construct (&dm))
{
return pika_procedure_new_return_values (procedure,
PIKA_PDB_EXECUTION_ERROR,
NULL);
}
break;
case PIKA_RUN_WITH_LAST_VALS:
pika_get_data (PLUG_IN_PROC, &dm.params);
if (! DepthMerge_construct (&dm))
{
return pika_procedure_new_return_values (procedure,
PIKA_PDB_EXECUTION_ERROR,
NULL);
}
break;
}
if (! DepthMerge_execute (&dm))
{
return pika_procedure_new_return_values (procedure,
PIKA_PDB_EXECUTION_ERROR,
NULL);
}
else
{
if (run_mode != PIKA_RUN_NONINTERACTIVE)
pika_displays_flush ();
if (run_mode == PIKA_RUN_INTERACTIVE)
pika_set_data (PLUG_IN_PROC, &dm.params, sizeof (DepthMergeParams));
}
DepthMerge_destroy (&dm);
return pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL);
}
/* ----- DepthMerge ----- */
static void
DepthMerge_initParams (DepthMerge *dm)
{
dm->params.result_id = -1;
dm->params.source1_id = -1;
dm->params.source2_id = -1;
dm->params.depthMap1_id = -1;
dm->params.depthMap2_id = -1;
dm->params.overlap = 0;
dm->params.offset = 0;
dm->params.scale1 = 1;
dm->params.scale2 = 1;
}
static gboolean
DepthMerge_construct (DepthMerge *dm)
{
dm->interface = NULL;
dm->resultDrawable = pika_drawable_get_by_id (dm->params.result_id);
if (! pika_drawable_mask_intersect (dm->resultDrawable,
&(dm->selectionX), &(dm->selectionY),
&(dm->selectionWidth),
&(dm->selectionHeight)))
{
return FALSE;
}
dm->resultHasAlpha = pika_drawable_has_alpha (dm->resultDrawable);
dm->source1Drawable = pika_drawable_get_by_id (dm->params.source1_id);
dm->source2Drawable = pika_drawable_get_by_id (dm->params.source2_id);
dm->depthMap1Drawable = pika_drawable_get_by_id (dm->params.depthMap1_id);
dm->depthMap2Drawable = pika_drawable_get_by_id (dm->params.depthMap2_id);
dm->params.overlap = CLAMP (dm->params.overlap, 0, 2);
dm->params.offset = CLAMP (dm->params.offset, -1, 1);
dm->params.scale1 = CLAMP (dm->params.scale1, -1, 1);
dm->params.scale2 = CLAMP (dm->params.scale2, -1, 1);
return TRUE;
}
static void
DepthMerge_destroy (DepthMerge *dm)
{
if (dm->interface != NULL)
{
g_free (dm->interface->previewSource1);
g_free (dm->interface->previewSource2);
g_free (dm->interface->previewDepthMap1);
g_free (dm->interface->previewDepthMap2);
g_free (dm->interface);
}
}
static gint32
DepthMerge_execute (DepthMerge *dm)
{
int x, y;
GeglBuffer *source1_buffer = NULL;
GeglBuffer *source2_buffer = NULL;
GeglBuffer *depthMap1_buffer = NULL;
GeglBuffer *depthMap2_buffer = NULL;
GeglBuffer *result_buffer;
guchar *source1Row, *source2Row;
guchar *depthMap1Row, *depthMap2Row;
guchar *resultRow;
guchar *tempRow;
pika_progress_init (_("Depth-merging"));
resultRow = g_new (guchar, dm->selectionWidth * 4);
source1Row = g_new (guchar, dm->selectionWidth * 4);
source2Row = g_new (guchar, dm->selectionWidth * 4);
depthMap1Row = g_new (guchar, dm->selectionWidth );
depthMap2Row = g_new (guchar, dm->selectionWidth );
tempRow = g_new (guchar, dm->selectionWidth * 4);
if (dm->source1Drawable)
{
source1_buffer = pika_drawable_get_buffer (dm->source1Drawable);
}
else
{
for (x = 0; x < dm->selectionWidth; x++)
{
source1Row[4 * x ] = 0;
source1Row[4 * x + 1] = 0;
source1Row[4 * x + 2] = 0;
source1Row[4 * x + 3] = 255;
}
}
if (dm->source2Drawable)
{
source2_buffer = pika_drawable_get_buffer (dm->source2Drawable);
}
else
{
for (x = 0; x < dm->selectionWidth; x++)
{
source2Row[4 * x ] = 0;
source2Row[4 * x + 1] = 0;
source2Row[4 * x + 2] = 0;
source2Row[4 * x + 3] = 255;
}
}
if (dm->depthMap1Drawable)
{
depthMap1_buffer = pika_drawable_get_buffer (dm->depthMap1Drawable);
}
else
{
for (x = 0; x < dm->selectionWidth; x++)
{
depthMap1Row[x] = 0;
}
}
if (dm->depthMap2Drawable)
{
depthMap2_buffer = pika_drawable_get_buffer (dm->depthMap2Drawable);
}
else
{
for (x = 0; x < dm->selectionWidth; x++)
{
depthMap2Row[x] = 0;
}
}
result_buffer = pika_drawable_get_shadow_buffer (dm->resultDrawable);
for (y = dm->selectionY; y < (dm->selectionY + dm->selectionHeight); y++)
{
if (dm->source1Drawable)
{
gegl_buffer_get (source1_buffer,
GEGL_RECTANGLE (dm->selectionX, y,
dm->selectionWidth, 1), 1.0,
babl_format ("R'G'B'A u8"), source1Row,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
}
if (dm->source2Drawable)
{
gegl_buffer_get (source2_buffer,
GEGL_RECTANGLE (dm->selectionX, y,
dm->selectionWidth, 1), 1.0,
babl_format ("R'G'B'A u8"), source2Row,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
}
if (dm->depthMap1Drawable)
{
gegl_buffer_get (depthMap1_buffer,
GEGL_RECTANGLE (dm->selectionX, y,
dm->selectionWidth, 1), 1.0,
babl_format ("Y' u8"), depthMap1Row,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
}
if (dm->depthMap2Drawable)
{
gegl_buffer_get (depthMap2_buffer,
GEGL_RECTANGLE (dm->selectionX, y,
dm->selectionWidth, 1), 1.0,
babl_format ("Y' u8"), depthMap2Row,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
}
DepthMerge_executeRegion (dm,
source1Row, source2Row, depthMap1Row, depthMap2Row,
resultRow,
dm->selectionWidth);
gegl_buffer_set (result_buffer,
GEGL_RECTANGLE (dm->selectionX, y,
dm->selectionWidth, 1), 0,
babl_format ("R'G'B'A u8"), resultRow,
GEGL_AUTO_ROWSTRIDE);
pika_progress_update ((double)(y-dm->selectionY) /
(double)(dm->selectionHeight - 1));
}
g_free (resultRow);
g_free (source1Row);
g_free (source2Row);
g_free (depthMap1Row);
g_free (depthMap2Row);
g_free (tempRow);
pika_progress_update (1.0);
if (source1_buffer)
g_object_unref (source1_buffer);
if (source2_buffer)
g_object_unref (source2_buffer);
if (depthMap1_buffer)
g_object_unref (depthMap1_buffer);
if (depthMap2_buffer)
g_object_unref (depthMap2_buffer);
g_object_unref (result_buffer);
pika_drawable_merge_shadow (dm->resultDrawable, TRUE);
pika_drawable_update (dm->resultDrawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
return TRUE;
}
static void
DepthMerge_executeRegion (DepthMerge *dm,
guchar *source1Row,
guchar *source2Row,
guchar *depthMap1Row,
guchar *depthMap2Row,
guchar *resultRow,
gint length)
{
gfloat scale1, scale2, offset255, invOverlap255;
gfloat frac, depth1, depth2;
gushort c1[4], c2[4];
gushort cR1[4] = { 0, 0, 0, 0 }, cR2[4] = { 0, 0, 0, 0 };
gushort cR[4], temp;
gint i, tempInt;
invOverlap255 = 1.0 / (MAX (dm->params.overlap, 0.001) * 255);
offset255 = dm->params.offset * 255;
scale1 = dm->params.scale1;
scale2 = dm->params.scale2;
for (i = 0; i < length; i++)
{
depth1 = (gfloat) depthMap1Row[i];
depth2 = (gfloat) depthMap2Row[i];
frac = (depth2 * scale2 - (depth1 * scale1 + offset255)) * invOverlap255;
frac = 0.5 * (frac + 1.0);
frac = CLAMP(frac, 0.0, 1.0);
/* c1 -> color corresponding to source1 */
c1[0] = source1Row[4 * i ];
c1[1] = source1Row[4 * i + 1];
c1[2] = source1Row[4 * i + 2];
c1[3] = source1Row[4 * i + 3];
/* c2 -> color corresponding to source2 */
c2[0] = source2Row[4 * i ];
c2[1] = source2Row[4 * i + 1];
c2[2] = source2Row[4 * i + 2];
c2[3] = source2Row[4 * i + 3];
if (frac != 0)
{
/* cR1 -> result if c1 is completely on top */
cR1[0] = c1[3] * c1[0] + (255 - c1[3]) * c2[0];
cR1[1] = c1[3] * c1[1] + (255 - c1[3]) * c2[1];
cR1[2] = c1[3] * c1[2] + (255 - c1[3]) * c2[2];
cR1[3] = MUL255 (c1[3]) + (255 - c1[3]) * c2[3];
}
if (frac != 1)
{
/* cR2 -> result if c2 is completely on top */
cR2[0] = c2[3] * c2[0] + (255 - c2[3]) * c1[0];
cR2[1] = c2[3] * c2[1] + (255 - c2[3]) * c1[1];
cR2[2] = c2[3] * c2[2] + (255 - c2[3]) * c1[2];
cR2[3] = MUL255 (c2[3]) + (255 - c2[3]) * c1[3];
}
if (frac == 1)
{
cR[0] = cR1[0];
cR[1] = cR1[1];
cR[2] = cR1[2];
cR[3] = cR1[3];
}
else if (frac == 0)
{
cR[0] = cR2[0];
cR[1] = cR2[1];
cR[2] = cR2[2];
cR[3] = cR2[3];
}
else
{
tempInt = LERP (frac, cR2[0], cR1[0]);
cR[0] = CLAMP (tempInt,0,255 * 255);
tempInt = LERP (frac, cR2[1], cR1[1]);
cR[1] = CLAMP (tempInt,0,255 * 255);
tempInt = LERP (frac, cR2[2], cR1[2]);
cR[2] = CLAMP (tempInt,0,255 * 255);
tempInt = LERP (frac, cR2[3], cR1[3]);
cR[3] = CLAMP (tempInt,0,255 * 255);
}
temp = DIV255 (cR[0]); resultRow[4 * i ] = MIN (temp, 255);
temp = DIV255 (cR[1]); resultRow[4 * i + 1] = MIN (temp, 255);
temp = DIV255 (cR[2]); resultRow[4 * i + 2] = MIN (temp, 255);
temp = DIV255 (cR[3]); resultRow[4 * i + 3] = MIN (temp, 255);
}
}
static gboolean
DepthMerge_dialog (DepthMerge *dm)
{
GtkWidget *dialog;
GtkWidget *vbox;
GtkWidget *frame;
GtkWidget *grid;
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *combo;
GtkWidget *scale;
gboolean run;
dm->interface = g_new0 (DepthMergeInterface, 1);
pika_ui_init (PLUG_IN_BINARY);
dm->interface->dialog =
dialog = pika_dialog_new (_("Depth Merge"), PLUG_IN_ROLE,
NULL, 0,
pika_standard_help_func, PLUG_IN_PROC,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_OK"), GTK_RESPONSE_OK,
NULL);
pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
pika_window_set_transient (GTK_WINDOW (dialog));
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
vbox, FALSE, FALSE, 0);
gtk_widget_show (vbox);
/* Preview */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
dm->interface->previewWidth = MIN (dm->selectionWidth, PREVIEW_SIZE);
dm->interface->previewHeight = MIN (dm->selectionHeight, PREVIEW_SIZE);
dm->interface->preview = pika_preview_area_new ();
gtk_widget_set_size_request (dm->interface->preview,
dm->interface->previewWidth,
dm->interface->previewHeight);
gtk_container_add (GTK_CONTAINER (frame), dm->interface->preview);
gtk_widget_show (dm->interface->preview);
DepthMerge_buildPreviewSourceImage (dm);
/* Source and Depth Map selection */
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);
label = gtk_label_new (_("Source 1:"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
gtk_widget_show (label);
combo = pika_drawable_combo_box_new (dm_constraint, dm, NULL);
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo), dm->params.source1_id,
G_CALLBACK (dialogSource1ChangedCallback),
dm, NULL);
gtk_grid_attach (GTK_GRID (grid), combo, 1, 0, 2, 1);
gtk_widget_show (combo);
label = gtk_label_new(_("Depth map:"));
gtk_widget_set_margin_bottom (label, 6);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
gtk_widget_show (label);
combo = pika_drawable_combo_box_new (dm_constraint, dm, NULL);
gtk_widget_set_margin_bottom (combo, 6);
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo), dm->params.depthMap1_id,
G_CALLBACK (dialogDepthMap1ChangedCallback),
dm, NULL);
gtk_grid_attach (GTK_GRID (grid), combo, 1, 1, 2, 1);
gtk_widget_show (combo);
label = gtk_label_new (_("Source 2:"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1);
gtk_widget_show (label);
combo = pika_drawable_combo_box_new (dm_constraint, dm, NULL);
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo), dm->params.source2_id,
G_CALLBACK (dialogSource2ChangedCallback),
dm, NULL);
gtk_grid_attach (GTK_GRID (grid), combo, 1, 2, 2, 1);
gtk_widget_show (combo);
label = gtk_label_new (_("Depth map:"));
gtk_widget_set_margin_bottom (label, 6);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, 3, 1, 1);
gtk_widget_show (label);
combo = pika_drawable_combo_box_new (dm_constraint, dm, NULL);
gtk_widget_set_margin_bottom (combo, 6);
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo), dm->params.depthMap2_id,
G_CALLBACK (dialogDepthMap2ChangedCallback),
dm, NULL);
gtk_grid_attach (GTK_GRID (grid), combo, 1, 3, 2, 1);
gtk_widget_show (combo);
/* Numeric parameters */
scale = pika_scale_entry_new (_("O_verlap:"), dm->params.overlap, 0, 2, 3);
pika_label_spin_set_increments (PIKA_LABEL_SPIN (scale), 0.001, 0.01);
g_signal_connect (scale, "value-changed",
G_CALLBACK (dialogValueScaleUpdateCallback),
&(dm->params.overlap));
g_object_set_data (G_OBJECT (scale), "dm", dm);
gtk_grid_attach (GTK_GRID (grid), scale, 0, 4, 3, 1);
gtk_widget_show (scale);
scale = pika_scale_entry_new (_("O_ffset:"), dm->params.offset, -1, 1, 3);
pika_label_spin_set_increments (PIKA_LABEL_SPIN (scale), 0.001, 0.01);
g_signal_connect (scale, "value-changed",
G_CALLBACK (dialogValueScaleUpdateCallback),
&(dm->params.offset));
g_object_set_data (G_OBJECT (scale), "dm", dm);
gtk_grid_attach (GTK_GRID (grid), scale, 0, 5, 3, 1);
gtk_widget_show (scale);
scale = pika_scale_entry_new (_("Sc_ale 1:"), dm->params.scale1, -1, 1, 3);
pika_label_spin_set_increments (PIKA_LABEL_SPIN (scale), 0.001, 0.01);
g_signal_connect (scale, "value-changed",
G_CALLBACK (dialogValueScaleUpdateCallback),
&(dm->params.scale1));
g_object_set_data (G_OBJECT (scale), "dm", dm);
gtk_grid_attach (GTK_GRID (grid), scale, 0, 6, 3, 1);
gtk_widget_show (scale);
scale = pika_scale_entry_new (_("Sca_le 2:"), dm->params.scale2, -1, 1, 3);
pika_label_spin_set_increments (PIKA_LABEL_SPIN (scale), 0.001, 0.01);
g_signal_connect (scale, "value-changed",
G_CALLBACK (dialogValueScaleUpdateCallback),
&(dm->params.scale2));
g_object_set_data (G_OBJECT (scale), "dm", dm);
gtk_grid_attach (GTK_GRID (grid), scale, 0, 7, 3, 1);
gtk_widget_show (scale);
dm->interface->active = TRUE;
gtk_widget_show (dm->interface->dialog);
DepthMerge_updatePreview (dm);
run = (pika_dialog_run (PIKA_DIALOG (dm->interface->dialog)) == GTK_RESPONSE_OK);
gtk_widget_destroy (dm->interface->dialog);
dm->interface->dialog = NULL;
return run;
}
static void
DepthMerge_buildPreviewSourceImage (DepthMerge *dm)
{
dm->interface->previewSource1 =
g_new (guchar, dm->interface->previewWidth *
dm->interface->previewHeight * 4);
util_fillReducedBuffer (dm->interface->previewSource1,
babl_format ("R'G'B'A u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->source1Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
dm->interface->previewSource2 =
g_new (guchar, dm->interface->previewWidth *
dm->interface->previewHeight * 4);
util_fillReducedBuffer (dm->interface->previewSource2,
babl_format ("R'G'B'A u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->source2Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
dm->interface->previewDepthMap1 =
g_new (guchar, dm->interface->previewWidth *
dm->interface->previewHeight * 1);
util_fillReducedBuffer (dm->interface->previewDepthMap1,
babl_format ("Y' u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->depthMap1Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
dm->interface->previewDepthMap2 =
g_new (guchar, dm->interface->previewWidth *
dm->interface->previewHeight * 1);
util_fillReducedBuffer (dm->interface->previewDepthMap2,
babl_format ("Y' u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->depthMap2Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
}
static void
DepthMerge_updatePreview (DepthMerge *dm)
{
gint y;
guchar *source1Row, *source2Row;
guchar *depthMap1Row, *depthMap2Row;
guchar *resultRGBA;
if (!dm->interface->active)
return;
resultRGBA = g_new (guchar, 4 * dm->interface->previewWidth *
dm->interface->previewHeight);
for (y = 0; y < dm->interface->previewHeight; y++)
{
source1Row =
&(dm->interface->previewSource1[ y * dm->interface->previewWidth * 4]);
source2Row =
&(dm->interface->previewSource2[ y * dm->interface->previewWidth * 4]);
depthMap1Row =
&(dm->interface->previewDepthMap1[y * dm->interface->previewWidth ]);
depthMap2Row =
&(dm->interface->previewDepthMap2[y * dm->interface->previewWidth ]);
DepthMerge_executeRegion(dm,
source1Row, source2Row, depthMap1Row, depthMap2Row,
resultRGBA + 4 * y * dm->interface->previewWidth,
dm->interface->previewWidth);
}
pika_preview_area_draw (PIKA_PREVIEW_AREA (dm->interface->preview),
0, 0,
dm->interface->previewWidth,
dm->interface->previewHeight,
PIKA_RGBA_IMAGE,
resultRGBA,
dm->interface->previewWidth * 4);
g_free(resultRGBA);
}
/* ----- Callbacks ----- */
static gboolean
dm_constraint (PikaImage *image,
PikaItem *item,
gpointer data)
{
DepthMerge *dm = data;
PikaDrawable *drawable = PIKA_DRAWABLE (item);
return ((pika_drawable_get_width (drawable) ==
pika_drawable_get_width (dm->resultDrawable)) &&
(pika_drawable_get_height (drawable) ==
pika_drawable_get_height (dm->resultDrawable)) &&
((pika_drawable_is_rgb (drawable) &&
(pika_drawable_is_rgb (dm->resultDrawable))) ||
pika_drawable_is_gray (drawable)));
}
static void
dialogSource1ChangedCallback (GtkWidget *widget,
DepthMerge *dm)
{
pika_int_combo_box_get_active (PIKA_INT_COMBO_BOX (widget),
&dm->params.source1_id);
dm->source1Drawable = pika_drawable_get_by_id (dm->params.source1_id);
util_fillReducedBuffer (dm->interface->previewSource1,
babl_format ("R'G'B'A u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->source1Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
DepthMerge_updatePreview (dm);
}
static void
dialogSource2ChangedCallback (GtkWidget *widget,
DepthMerge *dm)
{
pika_int_combo_box_get_active (PIKA_INT_COMBO_BOX (widget),
&dm->params.source2_id);
dm->source2Drawable = pika_drawable_get_by_id (dm->params.source2_id);
util_fillReducedBuffer (dm->interface->previewSource2,
babl_format ("R'G'B'A u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->source2Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
DepthMerge_updatePreview (dm);
}
static void
dialogDepthMap1ChangedCallback (GtkWidget *widget,
DepthMerge *dm)
{
pika_int_combo_box_get_active (PIKA_INT_COMBO_BOX (widget),
&dm->params.depthMap1_id);
dm->depthMap1Drawable = pika_drawable_get_by_id (dm->params.depthMap1_id);
util_fillReducedBuffer (dm->interface->previewDepthMap1,
babl_format ("Y' u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->depthMap1Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
DepthMerge_updatePreview (dm);
}
static void
dialogDepthMap2ChangedCallback (GtkWidget *widget,
DepthMerge *dm)
{
pika_int_combo_box_get_active (PIKA_INT_COMBO_BOX (widget),
&dm->params.depthMap2_id);
dm->depthMap2Drawable = pika_drawable_get_by_id (dm->params.depthMap2_id);
util_fillReducedBuffer (dm->interface->previewDepthMap2,
babl_format ("Y' u8"),
dm->interface->previewWidth,
dm->interface->previewHeight,
dm->depthMap2Drawable,
dm->selectionX, dm->selectionY,
dm->selectionWidth, dm->selectionHeight);
DepthMerge_updatePreview (dm);
}
static void
dialogValueScaleUpdateCallback (PikaLabelSpin *scale,
gfloat *value)
{
DepthMerge *dm = g_object_get_data (G_OBJECT (scale), "dm");
*value = (gfloat) pika_label_spin_get_value (scale);
DepthMerge_updatePreview (dm);
}
/* ----- Utility routines ----- */
static void
util_fillReducedBuffer (guchar *dest,
const Babl *dest_format,
gint destWidth,
gint destHeight,
PikaDrawable *sourceDrawable,
gint x0,
gint y0,
gint sourceWidth,
gint sourceHeight)
{
GeglBuffer *buffer;
guchar *sourceBuffer, *reducedRowBuffer;
guchar *sourceBufferPos, *reducedRowBufferPos;
guchar *sourceBufferRow;
gint x, y, i, yPrime;
gint destBPP;
gint *sourceRowOffsetLookup;
destBPP = babl_format_get_bytes_per_pixel (dest_format);
if (! sourceDrawable || (sourceWidth == 0) || (sourceHeight == 0))
{
for (x = 0; x < destWidth * destHeight * destBPP; x++)
dest[x] = 0;
return;
}
sourceBuffer = g_new (guchar, sourceWidth * sourceHeight * destBPP);
reducedRowBuffer = g_new (guchar, destWidth * destBPP);
sourceRowOffsetLookup = g_new (int, destWidth);
buffer = pika_drawable_get_buffer (sourceDrawable);
for (x = 0; x < destWidth; x++)
sourceRowOffsetLookup[x] = (x * (sourceWidth - 1) / (destWidth - 1)) * destBPP;
gegl_buffer_get (buffer,
GEGL_RECTANGLE (x0, y0, sourceWidth, sourceHeight), 1.0,
dest_format, sourceBuffer,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
for (y = 0; y < destHeight; y++)
{
yPrime = y * (sourceHeight - 1) / (destHeight - 1);
sourceBufferRow = &(sourceBuffer[yPrime * sourceWidth * destBPP]);
reducedRowBufferPos = reducedRowBuffer;
for (x = 0; x < destWidth; x++)
{
sourceBufferPos = sourceBufferRow + sourceRowOffsetLookup[x];
for (i = 0; i < destBPP; i++)
reducedRowBufferPos[i] = sourceBufferPos[i];
reducedRowBufferPos += destBPP;
}
memcpy (&(dest[y * destWidth * destBPP]), reducedRowBuffer,
destWidth * destBPP);
}
g_object_unref (buffer);
g_free (sourceBuffer);
g_free (reducedRowBuffer);
g_free (sourceRowOffsetLookup);
}