/* Depth Merge -- Combine two image layers via corresponding depth maps * Copyright (C) 1997, 1998 Sean Cier (scier@PostHorizon.com) * * A plug-in for The GIMP * The GIMP is Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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 2 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 #include #include #include #include #include "libgimp/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_NAME "plug_in_depth_merge" #define PLUG_IN_VERSION "1.0.0; 14 August 1998" #define HELP_ID "plug-in-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; gint32 source1; gint32 source2; gint32 depthMap1; gint32 depthMap2; gfloat overlap; gfloat offset; gfloat scale1; gfloat scale2; } DepthMergeParams; typedef struct _DepthMerge { DepthMergeInterface *interface; DepthMergeParams params; GimpDrawable *resultDrawable; GimpDrawable *source1Drawable; GimpDrawable *source2Drawable; GimpDrawable *depthMap1Drawable; GimpDrawable *depthMap2Drawable; gint selectionX0; gint selectionY0; gint selectionX1; gint selectionY1; gint selectionWidth; gint selectionHeight; gint resultHasAlpha; } DepthMerge; void DepthMerge_initParams (DepthMerge *dm); void DepthMerge_construct (DepthMerge *dm); void DepthMerge_destroy (DepthMerge *dm); gint32 DepthMerge_execute (DepthMerge *dm); void DepthMerge_executeRegion (DepthMerge *dm, guchar *source1Row, guchar *source2Row, guchar *depthMap1Row, guchar *depthMap2Row, guchar *resultRow, gint length); static gboolean DepthMerge_dialog (DepthMerge *dm); void DepthMerge_buildPreviewSourceImage(DepthMerge *dm); void DepthMerge_updatePreview (DepthMerge *dm); static gboolean dm_constraint (gint32 imageId, gint32 drawableId, 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); void dialogValueScaleUpdateCallback (GtkAdjustment *adjustment, gpointer data); void dialogValueEntryUpdateCallback (GtkWidget *widget, gpointer data); void util_fillReducedBuffer (guchar *dest, gint destWidth, gint destHeight, gint destBPP, gint destHasAlpha, GimpDrawable *sourceDrawable, gint x0, gint y0, gint sourceWidth, gint sourceHeight); void util_convertColorspace (guchar *dest, gint destBPP, gint destHasAlpha, guchar *source, gint sourceBPP, gint sourceHasAlpha, gint length); /* ----- plug-in entry points ----- */ static void query (void); static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run /* run_proc */ }; MAIN () static void query (void) { static GimpParamDef args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, { GIMP_PDB_DRAWABLE, "result", "Result" }, { GIMP_PDB_DRAWABLE, "source1", "Source 1" }, { GIMP_PDB_DRAWABLE, "source2", "Source 2" }, { GIMP_PDB_DRAWABLE, "depthMap1", "Depth map 1" }, { GIMP_PDB_DRAWABLE, "depthMap2", "Depth map 2" }, { GIMP_PDB_FLOAT, "overlap", "Overlap" }, { GIMP_PDB_FLOAT, "offset", "Depth relative offset" }, { GIMP_PDB_FLOAT, "scale1", "Depth relative scale 1" }, { GIMP_PDB_FLOAT, "scale2", "Depth relative scale 2" } }; gimp_install_procedure (PLUG_IN_NAME, "Combine two images using corresponding " "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.", "Sean Cier", "Sean Cier", PLUG_IN_VERSION, N_("_Depth Merge..."), "RGB*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), 0, args, NULL); gimp_plugin_menu_register (PLUG_IN_NAME, "/Filters/Combine"); } static void run (const gchar *name, gint numParams, const GimpParam *param, gint *numReturnVals, GimpParam **returnVals) { static GimpParam values[1]; GimpRunMode runMode; GimpPDBStatusType status; DepthMerge dm; INIT_I18N (); runMode = (GimpRunMode) param[0].data.d_int32; status = GIMP_PDB_SUCCESS; *numReturnVals = 1; *returnVals = values; switch (runMode) { case GIMP_RUN_INTERACTIVE: DepthMerge_initParams (&dm); gimp_get_data (PLUG_IN_NAME, &(dm.params)); dm.params.result = param[2].data.d_drawable; DepthMerge_construct (&dm); if (!DepthMerge_dialog (&dm)) { values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = GIMP_PDB_SUCCESS; return; } break; case GIMP_RUN_NONINTERACTIVE: DepthMerge_initParams (&dm); if (numParams != 11) status = GIMP_PDB_CALLING_ERROR; else { dm.params.result = param[ 2].data.d_drawable; dm.params.source1 = param[ 3].data.d_drawable; dm.params.source2 = param[ 4].data.d_drawable; dm.params.depthMap1 = param[ 5].data.d_drawable; dm.params.depthMap2 = param[ 6].data.d_drawable; dm.params.overlap = param[ 7].data.d_float; dm.params.offset = param[ 8].data.d_float; dm.params.scale1 = param[ 9].data.d_float; dm.params.scale2 = param[10].data.d_float; } DepthMerge_construct (&dm); break; case GIMP_RUN_WITH_LAST_VALS: DepthMerge_initParams (&dm); gimp_get_data (PLUG_IN_NAME, &(dm.params)); DepthMerge_construct (&dm); break; default: status = GIMP_PDB_CALLING_ERROR; } if (status == GIMP_PDB_SUCCESS) { gimp_tile_cache_ntiles ((dm.resultDrawable->width + gimp_tile_width () - 1) / gimp_tile_width ()); if (!DepthMerge_execute (&dm)) status = GIMP_PDB_EXECUTION_ERROR; else { if (runMode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush (); if (runMode == GIMP_RUN_INTERACTIVE) gimp_set_data (PLUG_IN_NAME, &(dm.params), sizeof (DepthMergeParams)); } } DepthMerge_destroy (&dm); values[0].data.d_status = status; } /* ----- DepthMerge ----- */ void DepthMerge_initParams (DepthMerge *dm) { dm->params.result = -1; dm->params.source1 = -1; dm->params.source2 = -1; dm->params.depthMap1 = -1; dm->params.depthMap2 = -1; dm->params.overlap = 0; dm->params.offset = 0; dm->params.scale1 = 1; dm->params.scale2 = 1; } void DepthMerge_construct (DepthMerge *dm) { dm->interface = NULL; dm->resultDrawable = gimp_drawable_get (dm->params.result); gimp_drawable_mask_bounds (dm->resultDrawable->drawable_id, &(dm->selectionX0), &(dm->selectionY0), &(dm->selectionX1), &(dm->selectionY1)); dm->selectionWidth = dm->selectionX1 - dm->selectionX0; dm->selectionHeight = dm->selectionY1 - dm->selectionY0; dm->resultHasAlpha = gimp_drawable_has_alpha (dm->resultDrawable->drawable_id); dm->source1Drawable = (dm->params.source1 == -1) ? NULL : gimp_drawable_get (dm->params.source1); dm->source2Drawable = (dm->params.source2 == -1) ? NULL : gimp_drawable_get (dm->params.source2); dm->depthMap1Drawable = (dm->params.depthMap1 == -1) ? NULL : gimp_drawable_get (dm->params.depthMap1); dm->depthMap2Drawable = (dm->params.depthMap2 == -1) ? NULL : gimp_drawable_get (dm->params.depthMap2); 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); } 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); } if (dm->resultDrawable != NULL) gimp_drawable_detach (dm->resultDrawable); if (dm->source1Drawable != NULL) gimp_drawable_detach (dm->source1Drawable); if (dm->source2Drawable != NULL) gimp_drawable_detach (dm->source2Drawable); if (dm->depthMap1Drawable != NULL) gimp_drawable_detach (dm->depthMap1Drawable); if (dm->depthMap2Drawable != NULL) gimp_drawable_detach (dm->depthMap2Drawable); } gint32 DepthMerge_execute (DepthMerge *dm) { int x, y; GimpPixelRgn source1Rgn, source2Rgn, depthMap1Rgn, depthMap2Rgn, resultRgn; guchar *source1Row, *source2Row, *depthMap1Row, *depthMap2Row, *resultRow, *tempRow; gint source1HasAlpha, source2HasAlpha, depthMap1HasAlpha, depthMap2HasAlpha; /* initialize */ source1HasAlpha = 0; source2HasAlpha = 0; depthMap1HasAlpha = 0; depthMap2HasAlpha = 0; gimp_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 != NULL) { source1HasAlpha = gimp_drawable_has_alpha(dm->source1Drawable->drawable_id); gimp_pixel_rgn_init(&source1Rgn, dm->source1Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight, FALSE, FALSE); } 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 != NULL) { source2HasAlpha = gimp_drawable_has_alpha(dm->source2Drawable->drawable_id); gimp_pixel_rgn_init(&source2Rgn, dm->source2Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight, FALSE, FALSE); } 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 != NULL) { depthMap1HasAlpha = gimp_drawable_has_alpha(dm->depthMap1Drawable->drawable_id); gimp_pixel_rgn_init(&depthMap1Rgn, dm->depthMap1Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight, FALSE, FALSE); } else for (x = 0; x < dm->selectionWidth; x++) { depthMap1Row[x ] = 0; } if (dm->depthMap2Drawable != NULL) { depthMap2HasAlpha = gimp_drawable_has_alpha(dm->depthMap2Drawable->drawable_id); gimp_pixel_rgn_init(&depthMap2Rgn, dm->depthMap2Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight, FALSE, FALSE); } else for (x = 0; x < dm->selectionWidth; x++) { depthMap2Row[x ] = 0; } gimp_pixel_rgn_init(&resultRgn, dm->resultDrawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight, TRUE, TRUE); for (y = dm->selectionY0; y < dm->selectionY1; y++) { if (dm->source1Drawable != NULL) { gimp_pixel_rgn_get_row(&source1Rgn, tempRow, dm->selectionX0, y, dm->selectionWidth); util_convertColorspace(source1Row, 4, TRUE, tempRow, dm->source1Drawable->bpp, source1HasAlpha, dm->selectionWidth); } if (dm->source2Drawable != NULL) { gimp_pixel_rgn_get_row(&source2Rgn, tempRow, dm->selectionX0, y, dm->selectionWidth); util_convertColorspace(source2Row, 4, TRUE, tempRow, dm->source2Drawable->bpp, source2HasAlpha, dm->selectionWidth); } if (dm->depthMap1Drawable != NULL) { gimp_pixel_rgn_get_row(&depthMap1Rgn, tempRow, dm->selectionX0, y, dm->selectionWidth); util_convertColorspace(depthMap1Row, 1, FALSE, tempRow, dm->depthMap1Drawable->bpp, depthMap1HasAlpha, dm->selectionWidth); } if (dm->depthMap2Drawable != NULL) { gimp_pixel_rgn_get_row(&depthMap2Rgn, tempRow, dm->selectionX0, y, dm->selectionWidth); util_convertColorspace(depthMap2Row, 1, FALSE, tempRow, dm->depthMap2Drawable->bpp, depthMap2HasAlpha, dm->selectionWidth); } DepthMerge_executeRegion(dm, source1Row, source2Row, depthMap1Row, depthMap2Row, resultRow, dm->selectionWidth); util_convertColorspace(tempRow, dm->resultDrawable->bpp, dm->resultHasAlpha, resultRow, 4, TRUE, dm->selectionWidth); gimp_pixel_rgn_set_row(&resultRgn, tempRow, dm->selectionX0, y, dm->selectionWidth); gimp_progress_update((double)(y-dm->selectionY0) / (double)(dm->selectionHeight-1)); } g_free (resultRow); g_free (source1Row); g_free (source2Row); g_free (depthMap1Row); g_free (depthMap2Row); g_free (tempRow); gimp_drawable_flush (dm->resultDrawable); gimp_drawable_merge_shadow (dm->resultDrawable->drawable_id, TRUE); gimp_drawable_update (dm->resultDrawable->drawable_id, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); return TRUE; } void DepthMerge_executeRegion (DepthMerge *dm, guchar *source1Row, guchar *source2Row, guchar *depthMap1Row, guchar *depthMap2Row, guchar *resultRow, gint length) { float scale1, scale2, offset255, invOverlap255; float frac, depth1, depth2; unsigned short c1[4], c2[4], cR1[4], cR2[4], cR[4], temp; int 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 = (float)depthMap1Row[i]; depth2 = (float)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 *vbox; GtkWidget *frame; GtkWidget *table; GtkWidget *hbox; GtkWidget *label; GtkWidget *combo; GtkObject *adj; gboolean run; dm->interface = g_new0 (DepthMergeInterface, 1); gimp_ui_init ("depthmerge", TRUE); dm->interface->dialog = gimp_dialog_new (_("Depth Merge"), "depthmerge", NULL, 0, gimp_standard_help_func, HELP_ID, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); vbox = gtk_vbox_new (FALSE, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dm->interface->dialog)->vbox), vbox, FALSE, FALSE, 0); gtk_widget_show (vbox); /* Preview */ hbox = gtk_hbox_new (FALSE, 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 = gimp_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 */ table = gtk_table_new (8, 3, FALSE); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_row_spacing (GTK_TABLE (table), 1, 12); gtk_table_set_row_spacing (GTK_TABLE (table), 3, 12); gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); gtk_widget_show (table); label = gtk_label_new (_("Source 1:")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); combo = gimp_drawable_combo_box_new (dm_constraint, dm); gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.source1, G_CALLBACK (dialogSource1ChangedCallback), dm); gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (combo); label = gtk_label_new(_("Depth map:")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); combo = gimp_drawable_combo_box_new (dm_constraint, dm); gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.depthMap1, G_CALLBACK (dialogDepthMap1ChangedCallback), dm); gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (combo); label = gtk_label_new (_("Source 2:")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); combo = gimp_drawable_combo_box_new (dm_constraint, dm); gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.source2, G_CALLBACK (dialogSource2ChangedCallback), dm); gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 2, 3, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (combo); label = gtk_label_new (_("Depth map:")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); combo = gimp_drawable_combo_box_new (dm_constraint, dm); gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.depthMap2, G_CALLBACK (dialogDepthMap2ChangedCallback), dm); gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 3, 4, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (combo); /* Numeric parameters */ adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 4, _("O_verlap:"), 0, 6, dm->params.overlap, 0, 2, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value_changed", G_CALLBACK (dialogValueScaleUpdateCallback), &(dm->params.overlap)); g_object_set_data (G_OBJECT (adj), "dm", dm); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 5, _("O_ffset:"), 0, 6, dm->params.offset, -1, 1, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value_changed", G_CALLBACK (dialogValueScaleUpdateCallback), &(dm->params.offset)); g_object_set_data (G_OBJECT (adj), "dm", dm); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 6, _("Sc_ale 1:"), 0, 6, dm->params.scale1, -1, 1, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value_changed", G_CALLBACK (dialogValueScaleUpdateCallback), &(dm->params.scale1)); g_object_set_data (G_OBJECT (adj), "dm", dm); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 7, _("Sca_le 2:"), 0, 6, dm->params.scale2, -1, 1, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value_changed", G_CALLBACK (dialogValueScaleUpdateCallback), &(dm->params.scale2)); g_object_set_data (G_OBJECT (adj), "dm", dm); dm->interface->active = TRUE; gtk_widget_show (dm->interface->dialog); DepthMerge_updatePreview (dm); run = (gimp_dialog_run (GIMP_DIALOG (dm->interface->dialog)) == GTK_RESPONSE_OK); gtk_widget_destroy (dm->interface->dialog); dm->interface->dialog = NULL; return run; } void DepthMerge_buildPreviewSourceImage (DepthMerge *dm) { dm->interface->previewSource1 = g_new(guchar, dm->interface->previewWidth * dm->interface->previewHeight * 4); util_fillReducedBuffer(dm->interface->previewSource1, dm->interface->previewWidth, dm->interface->previewHeight, 4, TRUE, dm->source1Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); dm->interface->previewSource2 = g_new(guchar, dm->interface->previewWidth * dm->interface->previewHeight * 4); util_fillReducedBuffer(dm->interface->previewSource2, dm->interface->previewWidth, dm->interface->previewHeight, 4, TRUE, dm->source2Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); dm->interface->previewDepthMap1 = g_new(guchar, dm->interface->previewWidth * dm->interface->previewHeight * 1); util_fillReducedBuffer(dm->interface->previewDepthMap1, dm->interface->previewWidth, dm->interface->previewHeight, 1, FALSE, dm->depthMap1Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); dm->interface->previewDepthMap2 = g_new(guchar, dm->interface->previewWidth * dm->interface->previewHeight * 1); util_fillReducedBuffer(dm->interface->previewDepthMap2, dm->interface->previewWidth, dm->interface->previewHeight, 1, FALSE, dm->depthMap2Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); } void DepthMerge_updatePreview (DepthMerge *dm) { gint y; guchar *source1Row, *source2Row, *depthMap1Row, *depthMap2Row, *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); } gimp_preview_area_draw (GIMP_PREVIEW_AREA (dm->interface->preview), 0, 0, dm->interface->previewWidth, dm->interface->previewHeight, GIMP_RGBA_IMAGE, resultRGBA, dm->interface->previewWidth * 4); g_free(resultRGBA); } /* ----- Callbacks ----- */ static gboolean dm_constraint (gint32 imageId, gint32 drawableId, gpointer data) { DepthMerge *dm = (DepthMerge *)data; return ((drawableId == -1) || ((gimp_drawable_width (drawableId) == gimp_drawable_width (dm->params.result)) && (gimp_drawable_height (drawableId) == gimp_drawable_height (dm->params.result)) && ((gimp_drawable_is_rgb (drawableId) && (gimp_drawable_is_rgb (dm->params.result))) || gimp_drawable_is_gray (drawableId)))); } static void dialogSource1ChangedCallback (GtkWidget *widget, DepthMerge *dm) { if (dm->source1Drawable) gimp_drawable_detach (dm->source1Drawable); gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &dm->params.source1); dm->source1Drawable = ((dm->params.source1 == -1) ? NULL : gimp_drawable_get (dm->params.source1)); util_fillReducedBuffer (dm->interface->previewSource1, dm->interface->previewWidth, dm->interface->previewHeight, 4, TRUE, dm->source1Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); DepthMerge_updatePreview (dm); } static void dialogSource2ChangedCallback (GtkWidget *widget, DepthMerge *dm) { if (dm->source2Drawable) gimp_drawable_detach (dm->source2Drawable); gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &dm->params.source2); dm->source2Drawable = ((dm->params.source2 == -1) ? NULL : gimp_drawable_get (dm->params.source2)); util_fillReducedBuffer (dm->interface->previewSource2, dm->interface->previewWidth, dm->interface->previewHeight, 4, TRUE, dm->source2Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); DepthMerge_updatePreview (dm); } static void dialogDepthMap1ChangedCallback (GtkWidget *widget, DepthMerge *dm) { if (dm->depthMap1Drawable) gimp_drawable_detach (dm->depthMap1Drawable); gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &dm->params.depthMap1); dm->depthMap1Drawable = ((dm->params.depthMap1 == -1) ? NULL : gimp_drawable_get (dm->params.depthMap1)); util_fillReducedBuffer (dm->interface->previewDepthMap1, dm->interface->previewWidth, dm->interface->previewHeight, 1, FALSE, dm->depthMap1Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); DepthMerge_updatePreview (dm); } static void dialogDepthMap2ChangedCallback (GtkWidget *widget, DepthMerge *dm) { if (dm->depthMap2Drawable) gimp_drawable_detach (dm->depthMap2Drawable); gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &dm->params.depthMap2); dm->depthMap1Drawable = ((dm->params.depthMap2 == -1) ? NULL : gimp_drawable_get (dm->params.depthMap2)); util_fillReducedBuffer (dm->interface->previewDepthMap2, dm->interface->previewWidth, dm->interface->previewHeight, 1, FALSE, dm->depthMap2Drawable, dm->selectionX0, dm->selectionY0, dm->selectionWidth, dm->selectionHeight); DepthMerge_updatePreview (dm); } void dialogValueScaleUpdateCallback (GtkAdjustment *adjustment, gpointer data) { DepthMerge *dm = g_object_get_data (G_OBJECT (adjustment), "dm"); gimp_float_adjustment_update (adjustment, data); DepthMerge_updatePreview (dm); } /* ----- Utility routines ----- */ void util_fillReducedBuffer (guchar *dest, gint destWidth, gint destHeight, gint destBPP, gint destHasAlpha, GimpDrawable *sourceDrawable, gint x0, gint y0, gint sourceWidth, gint sourceHeight) { GimpPixelRgn rgn; guchar *sourceBuffer, *reducedRowBuffer, *sourceBufferRow, *sourceBufferPos, *reducedRowBufferPos; int x, y, i, yPrime, sourceHasAlpha, sourceBpp; int *sourceRowOffsetLookup; if ((sourceDrawable == NULL) || (sourceWidth == 0) || (sourceHeight == 0)) { for (x = 0; x < destWidth*destHeight*destBPP; x++) dest[x] = 0; return; } sourceBpp = sourceDrawable->bpp; sourceBuffer = g_new (guchar, sourceWidth * sourceHeight * sourceBpp); reducedRowBuffer = g_new (guchar, destWidth * sourceBpp); sourceRowOffsetLookup = g_new (int, destWidth); gimp_pixel_rgn_init(&rgn, sourceDrawable, x0, y0, sourceWidth, sourceHeight, FALSE, FALSE); sourceHasAlpha = gimp_drawable_has_alpha(sourceDrawable->drawable_id); for (x = 0; x < destWidth; x++) sourceRowOffsetLookup[x] = (x*(sourceWidth-1)/(destWidth-1))*sourceBpp; gimp_pixel_rgn_get_rect(&rgn, sourceBuffer, x0, y0, sourceWidth, sourceHeight); for (y = 0; y < destHeight; y++) { yPrime = y*(sourceHeight-1)/(destHeight-1); sourceBufferRow = &(sourceBuffer[yPrime * sourceWidth * sourceBpp]); sourceBufferPos = sourceBufferRow; reducedRowBufferPos = reducedRowBuffer; for (x = 0; x < destWidth; x++) { sourceBufferPos = sourceBufferRow + sourceRowOffsetLookup[x]; for (i = 0; i < sourceBpp; i++) reducedRowBufferPos[i] = sourceBufferPos[i]; reducedRowBufferPos += sourceBpp; } util_convertColorspace(&(dest[y*destWidth*destBPP]), destBPP, destHasAlpha, reducedRowBuffer, sourceDrawable->bpp, sourceHasAlpha, destWidth); } g_free (sourceBuffer); g_free (reducedRowBuffer); g_free (sourceRowOffsetLookup); } /* Utterly pathetic kludge to convert between color spaces; likes gray and rgb best, of course. Others will be creatively mutilated, and even rgb->gray is pretty bad */ void util_convertColorspace (guchar *dest, gint destBPP, gint destHasAlpha, guchar *source, gint sourceBPP, gint sourceHasAlpha, gint length) { int i, j, sourcePos, destPos, accum; int sourceColorBPP = sourceHasAlpha ? (sourceBPP-1) : sourceBPP; int destColorBPP = destHasAlpha ? (destBPP -1) : destBPP; if (((sourceColorBPP != 1) && (sourceColorBPP != 3)) || ((destColorBPP != 1) && (destColorBPP != 3))) fprintf(stderr, "Warning: I don't _like_ this color space. This is a suggestion, not a threat.\n"); if ((sourceColorBPP == destColorBPP) && (sourceBPP == destBPP )) { j = length*sourceBPP; for (i = 0; i < j; i++) dest[i] = source[i]; return; } if (sourceColorBPP == destColorBPP) { for (i = destPos = sourcePos = 0; i < length; i++, destPos += destBPP, sourcePos += sourceBPP) { for (j = 0; j < destColorBPP; j++) dest[destPos + j] = source[sourcePos + j]; } } else if (sourceColorBPP == 1) { /* Duplicate single "gray" source byte across all dest bytes */ for (i = destPos = sourcePos = 0; i < length; i++, destPos += destBPP, sourcePos += sourceBPP) { for (j = 0; j < destColorBPP; j++) dest[destPos + j] = source[sourcePos]; } } else if (destColorBPP == 1) { /* Average all source bytes into single "gray" dest byte */ for (i = destPos = sourcePos = 0; i < length; i++, destPos += destBPP, sourcePos += sourceBPP) { accum = 0; for (j = 0; j < sourceColorBPP; j++) accum += source[sourcePos + j]; dest[destPos] = accum/sourceColorBPP; } } else if (destColorBPP < sourceColorBPP) { /* Copy as many corresponding bytes from source to dest as will fit */ for (i = destPos = sourcePos = 0; i < length; i++, destPos += destBPP, sourcePos += sourceBPP) { for (j = 0; j < destColorBPP; j++) dest[destPos + j] = source[sourcePos + j]; } } else /* destColorBPP > sourceColorBPP */ { /* Fill extra dest bytes with zero */ for (i = destPos = sourcePos = 0; i < length; i++, destPos += destBPP, sourcePos += sourceBPP) { for (j = 0; j < sourceColorBPP; j++) dest[destPos + j] = source[destPos + j]; for ( ; j < destColorBPP; j++) dest[destPos + j] = 0; } } if (destHasAlpha) { if (sourceHasAlpha) { for (i = 0, destPos = destColorBPP, sourcePos = sourceColorBPP; i < length; i++, destPos += destBPP, sourcePos += sourceBPP) { for (i = 0; i < length; i++) dest[destPos] = source[sourcePos]; } } else { for (i = 0, destPos = destColorBPP; i < length; i++, destPos += destBPP) { dest[destPos] = 255; } } } }