/* 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 PREVIEW_SIZE 256 /* ----- DepthMerge ----- */ struct _DepthMerge; typedef struct _DepthMergeInterface { gint active; GtkWidget *dialog; GtkWidget *preview; gint previewWidth; gint previewHeight; guchar *checkRow0; guchar *checkRow1; guchar *previewSource1; guchar *previewSource2; guchar *previewDepthMap1; guchar *previewDepthMap2; gint run; } 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; GDrawable *resultDrawable; GDrawable *source1Drawable; GDrawable *source2Drawable; GDrawable *depthMap1Drawable; GDrawable *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); gint32 DepthMerge_dialog (DepthMerge *dm); void DepthMerge_buildPreviewSourceImage(DepthMerge *dm); void DepthMerge_updatePreview (DepthMerge *dm); gint constraintResultSizeAndResultColorOrGray(gint32 imageId, gint32 drawableId, gpointer data); gint constraintResultSizeAndGray(gint32 imageId, gint32 drawableId, gpointer data); void dialogOkCallback (GtkWidget *widget, gpointer data); void dialogCancelCallback (GtkWidget *widget, gpointer data); void dialogSource1ChangedCallback (gint32 id, gpointer data); void dialogSource2ChangedCallback (gint32 id, gpointer data); void dialogDepthMap1ChangedCallback (gint32 id, gpointer data); void dialogDepthMap2ChangedCallback (gint32 id, gpointer data); 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, GDrawable *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 (gchar *name, gint nparams, GParam *param, gint *nreturn_vals, GParam **return_vals); GPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run /* run_proc */ }; MAIN () static void query (void) { static GParamDef args[] = { { PARAM_INT32, "run_mode", "Interactive, non-interactive" }, { PARAM_IMAGE, "image", "Input image (unused)" }, { PARAM_DRAWABLE, "result", "Result" }, { PARAM_DRAWABLE, "source1", "Source 1" }, { PARAM_DRAWABLE, "source2", "Source 2" }, { PARAM_DRAWABLE, "depthMap1", "Depth map 1" }, { PARAM_DRAWABLE, "depthMap2", "Depth map 2" }, { PARAM_FLOAT, "overlap", "Overlap" }, { PARAM_FLOAT, "offset", "Depth relative offset" }, { PARAM_FLOAT, "scale1", "Depth relative scale 1" }, { PARAM_FLOAT, "scale2", "Depth relative scale 2" }, }; static gint numArgs = sizeof (args) / sizeof (GParamDef); INIT_I18N(); 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_("/Filters/Combine/Depth Merge..."), "RGB*, GRAY*", PROC_PLUG_IN, numArgs, 0, args, NULL); } static void run (gchar *name, gint numParams, GParam *param, gint *numReturnVals, GParam **returnVals) { static GParam values[1]; GRunModeType runMode; GStatusType status; DepthMerge dm; INIT_I18N_UI(); runMode = (GRunModeType) param[0].data.d_int32; status = STATUS_SUCCESS; *numReturnVals = 1; *returnVals = values; switch (runMode) { case 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 = PARAM_STATUS; values[0].data.d_status = STATUS_SUCCESS; return; } break; case RUN_NONINTERACTIVE: DepthMerge_initParams (&dm); if (numParams != 11) status = STATUS_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 RUN_WITH_LAST_VALS: DepthMerge_initParams (&dm); gimp_get_data (PLUG_IN_NAME, &(dm.params)); DepthMerge_construct (&dm); break; default: status = STATUS_CALLING_ERROR; } if (status == STATUS_SUCCESS) { gimp_tile_cache_ntiles ((dm.resultDrawable->width + gimp_tile_width() - 1) / gimp_tile_width()); if (!DepthMerge_execute (&dm)) status = STATUS_EXECUTION_ERROR; else { if (runMode != RUN_NONINTERACTIVE) gimp_displays_flush (); if (runMode == 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->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->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->checkRow0); g_free (dm->interface->checkRow1); 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; GPixelRgn 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 = (guchar *)g_malloc(dm->selectionWidth * 4); source1Row = (guchar *)g_malloc(dm->selectionWidth * 4); source2Row = (guchar *)g_malloc(dm->selectionWidth * 4); depthMap1Row = (guchar *)g_malloc(dm->selectionWidth ); depthMap2Row = (guchar *)g_malloc(dm->selectionWidth ); tempRow = (guchar *)g_malloc(dm->selectionWidth * 4); if (dm->source1Drawable != NULL) { source1HasAlpha = gimp_drawable_has_alpha(dm->source1Drawable->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->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->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->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->id, TRUE); gimp_drawable_update (dm->resultDrawable->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); } } gint32 DepthMerge_dialog (DepthMerge *dm) { GtkWidget *topTable; GtkWidget *previewFrame; GtkWidget *sourceTable; GtkWidget *tempLabel; GtkWidget *tempOptionMenu; GtkWidget *tempMenu; GtkWidget *numericParameterTable; GtkObject *adj; gint argc; gchar **argv; guchar *color_cube; dm->interface = g_new (DepthMergeInterface, 1); dm->interface->active = FALSE; dm->interface->run = FALSE; argc = 1; argv = g_new (gchar *, 1); argv[0] = g_strdup (PLUG_IN_NAME); gtk_init (&argc, &argv); gtk_rc_parse (gimp_gtkrc ()); gdk_set_use_xshm (gimp_use_xshm ()); gtk_preview_set_gamma (gimp_gamma ()); gtk_preview_set_install_cmap (gimp_install_cmap ()); color_cube = gimp_color_cube (); gtk_preview_set_color_cube (color_cube[0], color_cube[1], color_cube[2], color_cube[3]); gtk_widget_set_default_visual (gtk_preview_get_visual ()); gtk_widget_set_default_colormap (gtk_preview_get_cmap ()); dm->interface->dialog = gimp_dialog_new (_("Depth Merge"), "depthmerge", gimp_plugin_help_func, "filters/depthmerge.html", GTK_WIN_POS_MOUSE, FALSE, TRUE, FALSE, _("OK"), dialogOkCallback, dm, NULL, NULL, TRUE, FALSE, _("Cancel"), dialogCancelCallback, dm, NULL, NULL, FALSE, TRUE, NULL); gtk_signal_connect (GTK_OBJECT (dm->interface->dialog), "destroy", GTK_SIGNAL_FUNC (gtk_main_quit), NULL); /* topTable */ topTable = gtk_table_new (3, 3, FALSE); gtk_container_set_border_width (GTK_CONTAINER(topTable), 6); gtk_table_set_row_spacings (GTK_TABLE (topTable), 4); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dm->interface->dialog)->vbox), topTable, FALSE, FALSE, 0); gtk_widget_show(topTable); /* Preview */ previewFrame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (previewFrame), GTK_SHADOW_IN); gtk_table_attach (GTK_TABLE (topTable), previewFrame, 1, 2, 0, 1, 0, 0, 0, 0); gtk_widget_show (previewFrame); dm->interface->previewWidth = MIN (dm->selectionWidth, PREVIEW_SIZE); dm->interface->previewHeight = MIN (dm->selectionHeight, PREVIEW_SIZE); dm->interface->preview = gtk_preview_new (GTK_PREVIEW_COLOR); gtk_preview_size (GTK_PREVIEW (dm->interface->preview), dm->interface->previewWidth, dm->interface->previewHeight); gtk_container_add (GTK_CONTAINER (previewFrame), dm->interface->preview); gtk_widget_show (dm->interface->preview); DepthMerge_buildPreviewSourceImage (dm); /* Source and Depth Map selection */ sourceTable = gtk_table_new (2, 4, FALSE); gtk_table_set_col_spacings (GTK_TABLE (sourceTable), 4); gtk_table_set_col_spacing (GTK_TABLE (sourceTable), 1, 6); gtk_table_set_row_spacings (GTK_TABLE (sourceTable), 2); gtk_table_attach (GTK_TABLE (topTable), sourceTable, 0, 3, 1, 2, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); gtk_widget_show (sourceTable); tempLabel = gtk_label_new (_("Source 1:")); gtk_misc_set_alignment (GTK_MISC (tempLabel), 1.0, 0.5); gtk_table_attach (GTK_TABLE (sourceTable), tempLabel, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (tempLabel); tempOptionMenu = gtk_option_menu_new (); gtk_table_attach (GTK_TABLE (sourceTable), tempOptionMenu, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (tempOptionMenu); tempMenu = gimp_drawable_menu_new (constraintResultSizeAndResultColorOrGray, dialogSource1ChangedCallback, dm, dm->params.source1); gtk_option_menu_set_menu (GTK_OPTION_MENU (tempOptionMenu), tempMenu); gtk_widget_show (tempOptionMenu); tempLabel = gtk_label_new(_("Depth Map:")); gtk_misc_set_alignment(GTK_MISC(tempLabel), 1.0, 0.5); gtk_table_attach (GTK_TABLE (sourceTable), tempLabel, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (tempLabel); tempOptionMenu = gtk_option_menu_new (); gtk_table_attach (GTK_TABLE (sourceTable), tempOptionMenu, 3, 4, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (tempOptionMenu); tempMenu = gimp_drawable_menu_new (constraintResultSizeAndResultColorOrGray, dialogDepthMap1ChangedCallback, dm, dm->params.depthMap1); gtk_option_menu_set_menu (GTK_OPTION_MENU (tempOptionMenu), tempMenu); gtk_widget_show (tempOptionMenu); tempLabel = gtk_label_new (_("Source 2:")); gtk_misc_set_alignment (GTK_MISC (tempLabel), 1.0, 0.5); gtk_table_attach (GTK_TABLE (sourceTable), tempLabel, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (tempLabel); tempOptionMenu = gtk_option_menu_new (); gtk_table_attach (GTK_TABLE (sourceTable), tempOptionMenu, 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (tempOptionMenu); tempMenu = gimp_drawable_menu_new (constraintResultSizeAndResultColorOrGray, dialogSource2ChangedCallback, dm, dm->params.source2); gtk_option_menu_set_menu (GTK_OPTION_MENU (tempOptionMenu), tempMenu); gtk_widget_show (tempOptionMenu); tempLabel = gtk_label_new (_("Depth Map:")); gtk_misc_set_alignment(GTK_MISC(tempLabel), 1.0, 0.5); gtk_table_attach (GTK_TABLE (sourceTable), tempLabel, 2, 3, 1, 2, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (tempLabel); tempOptionMenu = gtk_option_menu_new (); gtk_table_attach (GTK_TABLE (sourceTable), tempOptionMenu, 3, 4, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (tempOptionMenu); tempMenu = gimp_drawable_menu_new (constraintResultSizeAndResultColorOrGray, dialogDepthMap2ChangedCallback, dm, dm->params.depthMap2); gtk_option_menu_set_menu (GTK_OPTION_MENU (tempOptionMenu), tempMenu); gtk_widget_show (tempOptionMenu); /* Numeric parameters */ numericParameterTable = gtk_table_new(4, 3, FALSE); gtk_table_set_col_spacings (GTK_TABLE (numericParameterTable), 4); gtk_table_set_row_spacings (GTK_TABLE (numericParameterTable), 2); gtk_table_attach (GTK_TABLE (topTable), numericParameterTable, 0, 3, 2, 3, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); gtk_widget_show (numericParameterTable); adj = gimp_scale_entry_new (GTK_TABLE (numericParameterTable), 0, 0, _("Overlap:"), 0, 0, dm->params.overlap, 0, 2, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); gtk_signal_connect (GTK_OBJECT (adj), "value_changed", GTK_SIGNAL_FUNC (dialogValueScaleUpdateCallback), &(dm->params.overlap)); gtk_object_set_user_data (GTK_OBJECT (adj), dm); adj = gimp_scale_entry_new (GTK_TABLE (numericParameterTable), 0, 1, _("Offset:"), 0, 0, dm->params.offset, -1, 1, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); gtk_signal_connect (GTK_OBJECT (adj), "value_changed", GTK_SIGNAL_FUNC (dialogValueScaleUpdateCallback), &(dm->params.offset)); gtk_object_set_user_data (GTK_OBJECT (adj), dm); adj = gimp_scale_entry_new (GTK_TABLE (numericParameterTable), 0, 2, _("Scale 1:"), 0, 0, dm->params.scale1, -1, 1, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); gtk_signal_connect (GTK_OBJECT (adj), "value_changed", GTK_SIGNAL_FUNC (dialogValueScaleUpdateCallback), &(dm->params.scale1)); gtk_object_set_user_data (GTK_OBJECT (adj), dm); adj = gimp_scale_entry_new (GTK_TABLE (numericParameterTable), 0, 3, _("Scale 2:"), 0, 0, dm->params.scale2, -1, 1, 0.001, 0.01, 3, TRUE, 0, 0, NULL, NULL); gtk_signal_connect (GTK_OBJECT (adj), "value_changed", GTK_SIGNAL_FUNC (dialogValueScaleUpdateCallback), &(dm->params.scale2)); gtk_object_set_user_data (GTK_OBJECT (adj), dm); dm->interface->active = TRUE; gtk_widget_show (dm->interface->dialog); DepthMerge_updatePreview (dm); gtk_main (); gdk_flush (); return dm->interface->run; } void DepthMerge_buildPreviewSourceImage (DepthMerge *dm) { int x; dm->interface->checkRow0 = (guchar *)g_malloc(dm->interface->previewWidth * sizeof(guchar)); dm->interface->checkRow1 = (guchar *)g_malloc(dm->interface->previewWidth * sizeof(guchar)); for (x = 0; x < dm->interface->previewWidth; x++) { if ((x / GIMP_CHECK_SIZE) & 1) { dm->interface->checkRow0[x] = GIMP_CHECK_DARK * 255; dm->interface->checkRow1[x] = GIMP_CHECK_LIGHT * 255; } else { dm->interface->checkRow0[x] = GIMP_CHECK_LIGHT * 255; dm->interface->checkRow1[x] = GIMP_CHECK_DARK * 255; } } dm->interface->previewSource1 = (guchar *)g_malloc(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 = (guchar *)g_malloc(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 = (guchar *)g_malloc(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 = (guchar *)g_malloc(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) { int x, y, i; guchar *source1Row, *source2Row, *depthMap1Row, *depthMap2Row, *resultRowRGBA, *resultRow, *checkRow; if (!dm->interface->active) return; resultRowRGBA = (guchar *)g_malloc(dm->interface->previewWidth * 4); resultRow = (guchar *)g_malloc(dm->interface->previewWidth * 3); for (y = 0; y < dm->interface->previewHeight; y++) { checkRow = ((y/GIMP_CHECK_SIZE)&1) ? dm->interface->checkRow1 : dm->interface->checkRow0; 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, resultRowRGBA, dm->interface->previewWidth); for (x = 0; x < dm->interface->previewWidth; x++) { i = ((int)( resultRowRGBA[x*4+3])*(int)resultRowRGBA[x*4 ] + (int)(255 - resultRowRGBA[x*4+3])*(int)checkRow[x] ); resultRow[x*3 ] = DIV255(i); i = ((int)( resultRowRGBA[x*4+3])*(int)resultRowRGBA[x*4+1] + (int)(255 - resultRowRGBA[x*4+3])*(int)checkRow[x] ); resultRow[x*3+1] = DIV255(i); i = ((int)( resultRowRGBA[x*4+3])*(int)resultRowRGBA[x*4+2] + (int)(255 - resultRowRGBA[x*4+3])*(int)checkRow[x] ); resultRow[x*3+2] = DIV255(i); } gtk_preview_draw_row(GTK_PREVIEW(dm->interface->preview), resultRow, 0, y, dm->interface->previewWidth); } g_free(resultRowRGBA); g_free(resultRow); gtk_widget_draw(dm->interface->preview, NULL); gdk_flush(); } /* ----- Callbacks ----- */ gint constraintResultSizeAndResultColorOrGray (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)))); } gint constraintResultSizeAndGray (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_gray (drawableId)))); } void dialogOkCallback (GtkWidget *widget, gpointer data) { DepthMerge *dm = (DepthMerge *)data; dm->interface->run = TRUE; gtk_widget_destroy (dm->interface->dialog); } void dialogCancelCallback (GtkWidget *widget, gpointer data) { DepthMerge *dm = (DepthMerge *)data; dm->interface->run = FALSE; gtk_widget_destroy (dm->interface->dialog); } void dialogSource1ChangedCallback (gint32 id, gpointer data) { DepthMerge *dm = (DepthMerge *)data; if (dm->source1Drawable != NULL) gimp_drawable_detach (dm->source1Drawable); dm->params.source1 = id; 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); } void dialogSource2ChangedCallback (gint32 id, gpointer data) { DepthMerge *dm = (DepthMerge *)data; if (dm->source2Drawable != NULL) gimp_drawable_detach (dm->source2Drawable); dm->params.source2 = id; 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); } void dialogDepthMap1ChangedCallback (gint32 id, gpointer data) { DepthMerge *dm = (DepthMerge *)data; if (dm->depthMap1Drawable != NULL) gimp_drawable_detach(dm->depthMap1Drawable); dm->params.depthMap1 = id; 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); } void dialogDepthMap2ChangedCallback (gint32 id, gpointer data) { DepthMerge *dm = (DepthMerge *)data; if (dm->depthMap2Drawable != NULL) gimp_drawable_detach(dm->depthMap2Drawable); dm->params.depthMap2 = id; dm->depthMap2Drawable = (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 = gtk_object_get_user_data (GTK_OBJECT (adjustment)); gimp_float_adjustment_update (adjustment, data); DepthMerge_updatePreview (dm); } /* ----- Utility routines ----- */ void util_fillReducedBuffer (guchar *dest, gint destWidth, gint destHeight, gint destBPP, gint destHasAlpha, GDrawable *sourceDrawable, gint x0, gint y0, gint sourceWidth, gint sourceHeight) { GPixelRgn 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 = (guchar *)g_malloc(sourceWidth * sourceHeight * sourceBpp); reducedRowBuffer = (guchar *)g_malloc(destWidth * sourceBpp); sourceRowOffsetLookup = (int *)g_malloc(destWidth * sizeof(int)); gimp_pixel_rgn_init(&rgn, sourceDrawable, x0, y0, sourceWidth, sourceHeight, FALSE, FALSE); sourceHasAlpha = gimp_drawable_has_alpha(sourceDrawable->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; } } } }