/* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * Wideangle plug-in - adjust for lens distortion * Copyright (C) 2001-2004 David Hodson hodsond@acm.org * Many thanks for Lars Clausen for the original inspiration, * useful discussion, optimisation and improvements. * Framework borrowed from many similar plugins... * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /****************************************** to do: speedup? jump to correct preset on load (look for matching parameters) -- avoided; preset menu is now input only, not displayed internationalisation use Gimp bg colour for fill (also "correct" antialiasing) ******************************************/ #include #include #include #include #include #include #include static void query (void); static void run(const gchar*, gint, const GimpParam*, gint*, GimpParam**); static void wideangleSetupContext(GimpDrawable *drawable); static void wideangleSetupCalc(); static void wideangle(); static void calcWideangleRect(int x, int y, int step, int w, int h, int d, guchar* dst); static void wideangleDialog(); static void readPresetFile(); static void writePresetFile(); #define PLUGIN_NAME "plug_in_wideangle" GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; typedef struct { gdouble centre_x; gdouble centre_y; gdouble square_a; gdouble quad_a; gdouble scale_a; gdouble brighten; } WideangleValues; static WideangleValues vals = { 0.0, 0.0, 0.0, 0.0 }; typedef struct { WideangleValues* val; char name[101]; } WideanglePreset; typedef struct { GimpDrawable* drawable; gint32 id; int width; int height; int depth; int do_it; } WideangleContextValues; static WideangleContextValues contextVals; typedef struct { double normallise_radius_sq; double centre_x; double centre_y; double mult_sq; double mult_qd; double rescale; double brighten; } WideangleCalcValues; static WideangleCalcValues calcVals; #define MAX_PIXEL_DEPTH 4 #define PREVIEW_ROUGH_AREA (200 * 150) typedef struct { /* preview consists of 25 regions, each width * height (* depth), * arranged in a 5 * 5 grid. Buffer can hold one such region. * The region at [i,j] in the grid comes from the image starting at * [xStart + i * xSpacing, [yStart + j * ySpacing], and stepping * by step in both x and y. Note that the preview is calculated * directly from the image, not from a sampled version. */ int width; int height; int depth; guchar* buffer; /* image access */ double xStart; double xSpacing; double yStart; double ySpacing; double step; double maxStep; double maxXSpacing; double maxYSpacing; /* widget */ GtkWidget* preview; /* idle updating */ int enabled; int progress; } WideanglePreview; static WideanglePreview* previewCreate(); static void previewStartUpdate(WideanglePreview*); static gint previewIdleUpdateCB(gpointer); typedef struct { GtkWidget* saveButton; GtkWidget* presetMenu; GtkWidget* selectButton; GtkWidget* previewBox; GtkAdjustment* centre_x; GtkAdjustment* centre_y; GtkAdjustment* square_a; GtkAdjustment* quad_a; GtkAdjustment* scale_a; GtkAdjustment* bright_a; WideanglePreview* preview; int do_preview; GSList* presets; } WideangleGuiValues; static WideangleGuiValues guiVals; 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, "drawable", "Input drawable" }, { GIMP_PDB_FLOAT, "offset x", "Effect centre offset in X" }, { GIMP_PDB_FLOAT, "offset y", "Effect centre offset in Y" }, { GIMP_PDB_FLOAT, "main adjust", "Amount of second-order distortion" }, { GIMP_PDB_FLOAT, "edge adjust", "Amount of fourth-order distortion" }, { GIMP_PDB_FLOAT, "rescale", "Rescale overall image size" }, { GIMP_PDB_FLOAT, "brighten", "Adjust brightness in corners" } }; static gint nargs = sizeof(args) / sizeof(args[0]); gimp_install_procedure(PLUGIN_NAME, "Corrects wideangle lens distortion", "Corrects (or creates) the distortion common to wideangle lenses.", "David Hodson ", "Copyright (C) 2001-2004 David Hodson", "Version 1.0 09 April 2004", "/Filters/Distorts/Wideangle...", "RGB*, GRAY*", GIMP_PLUGIN, nargs, 0, args, NULL); } static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[1]; GimpDrawable *drawable; GimpPDBStatusType status = GIMP_PDB_SUCCESS; GimpRunMode run_mode = (GimpRunMode)param[0].data.d_int32; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; *nreturn_vals = 1; *return_vals = values; drawable = gimp_drawable_get(param[2].data.d_drawable); wideangleSetupContext(drawable); switch (run_mode) { case GIMP_RUN_INTERACTIVE: gimp_get_data(PLUGIN_NAME, &vals); wideangleDialog(); if (!contextVals.do_it) { return; } break; case GIMP_RUN_NONINTERACTIVE: if (nparams != 9) status = GIMP_PDB_CALLING_ERROR; if (status == GIMP_PDB_SUCCESS) { vals.centre_x = param[3].data.d_float; vals.centre_y = param[4].data.d_float; vals.square_a = param[5].data.d_float; vals.quad_a = param[6].data.d_float; vals.scale_a = param[7].data.d_float; vals.brighten = param[8].data.d_float; } break; case GIMP_RUN_WITH_LAST_VALS: gimp_get_data(PLUGIN_NAME, &vals); break; default: break; } wideangleSetupCalc(); wideangle(); if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush(); if (run_mode == GIMP_RUN_INTERACTIVE) gimp_set_data(PLUGIN_NAME, &vals, sizeof (WideangleValues)); values[0].data.d_status = status; gimp_drawable_detach(drawable); } static void getSourceCoords(double i, double j, double* x, double* y, double* mag) { double radius_sq; double off_x; double off_y; double radius_mult; off_x = i - calcVals.centre_x; off_y = j - calcVals.centre_y; radius_sq = (off_x * off_x) + (off_y * off_y); radius_sq *= calcVals.normallise_radius_sq; radius_mult = radius_sq * calcVals.mult_sq + radius_sq * radius_sq * calcVals.mult_qd; *mag = radius_mult; radius_mult = calcVals.rescale * (1.0 + radius_mult); *x = calcVals.centre_x + radius_mult * off_x; *y = calcVals.centre_y + radius_mult * off_y; } static void wideangleSetupContext(GimpDrawable* drawable) { contextVals.drawable = drawable; contextVals.id = drawable->drawable_id; contextVals.width = drawable->width; contextVals.height = drawable->height; contextVals.depth = drawable->bpp; contextVals.do_it = FALSE; } static void wideangleSetupCalc() { calcVals.normallise_radius_sq = 4.0 / (contextVals.width * contextVals.width + contextVals.height * contextVals.height); calcVals.centre_x = contextVals.width * (100.0 + vals.centre_x) / 200.0; calcVals.centre_y = contextVals.height * (100.0 + vals.centre_y) / 200.0; calcVals.mult_sq = vals.square_a / 200.0; calcVals.mult_qd = vals.quad_a / 200.0; calcVals.rescale = pow(2.0, - vals.scale_a / 100.0); calcVals.brighten = - vals.brighten / 10.0; } /* * Catmull-Rom cubic interpolation * * equally spaced points p0, p1, p2, p3 * interpolate 0 <= u < 1 between p1 and p2 * * (1 u u^2 u^3) ( 0.0 1.0 0.0 0.0 ) (p0) * ( -0.5 0.0 0.5 0.0 ) (p1) * ( 1.0 -2.5 2.0 -0.5 ) (p2) * ( -0.5 1.5 -1.5 0.5 ) (p3) * */ void cubicInterpolate(guchar* src, int rowStride, int srcDepth, guchar* dst, int dstDepth, double dx, double dy, double brighten) { float um1, u, up1, up2; float vm1, v, vp1, vp2; int c; float verts[4 * MAX_PIXEL_DEPTH]; um1 = ((-0.5 * dx + 1.0) * dx - 0.5) * dx; u = (1.5 * dx - 2.5) * dx * dx + 1.0; up1 = ((-1.5 * dx + 2.0) * dx + 0.5) * dx; up2 = (0.5 * dx - 0.5) * dx * dx; vm1 = ((-0.5 * dy + 1.0) * dy - 0.5) * dy; v = (1.5 * dy - 2.5) * dy * dy + 1.0; vp1 = ((-1.5 * dy + 2.0) * dy + 0.5) * dy; vp2 = (0.5 * dy - 0.5) * dy * dy; /* Note: if dstDepth < srcDepth, we calculate unneeded pixels here */ /* later - select or create index array */ for (c = 0; c < 4 * srcDepth; ++c) { verts[c] = vm1 * src[c] + v * src[c+rowStride] + vp1 * src[c+rowStride*2] + vp2 * src[c+rowStride*3]; } for (c = 0; c < dstDepth; ++c) { float result; result = um1 * verts[c] + u * verts[c+srcDepth] + up1 * verts[c+srcDepth*2] + up2 * verts[c+srcDepth*3]; result *= brighten; if (result < 0.0) { dst[c] = 0; } else if (result > 255.0) { dst[c] = 255; } else { dst[c] = result; } } } /* Solving the eternal problem: random, cubic-interpolated, * sub-pixel coordinate access to a tiled image. * Assuming that accesses are at least slightly coherent, * PixelAccess keeps PixelAccessRegions buffers, each containing a * PixelAccessWidth x PixelAccessHeight region of pixels. * Buffer[0] is always checked first, so move the last accessed * region into that position. * When a request arrives which is outside all the regions, * get a new region (using GimpPixelRgn - good idea? bad idea?). * The new region is placed so that the requested pixel is positioned * at [PixelAccessXOffset, PixelAccessYOffset] in the region. */ #define PixelAccessRegions 20 #define PixelAccessWidth 40 #define PixelAccessHeight 20 #define PixelAccessXOffset 3 #define PixelAccessYOffset 3 #define GATHERING_STATS 0 typedef struct { GimpPixelRgn srcPR; guchar* buffer[PixelAccessRegions]; int width; int height; int depth; int imageWidth; int imageHeight; int tileMinX[PixelAccessRegions]; int tileMaxX[PixelAccessRegions]; int tileMinY[PixelAccessRegions]; int tileMaxY[PixelAccessRegions]; #if GATHERING_STATS int pixelsFound; int pixelsFoundInBuffer[PixelAccessRegions]; int pixelsLoadedFromImage; #endif } PixelAccess; static PixelAccess* pa = 0; void pixelAccessDumpRegion(PixelAccess* pa, int n) { printf("Region %d: buffer %p\n", n, pa->buffer[n]); printf(" X min %d max %d, Y min %d max %d\n", pa->tileMinX[n], pa->tileMaxX[n], pa->tileMinY[n], pa->tileMaxY[n]); } #if GATHERING_STATS void pixelAccessDumpStats(PixelAccess* pa) { printf("Pixels found %d\n", pa->pixelsFound); if (pa->pixelsFound) { int i; for (i = 0; i < PixelAccessRegions; ++i) { printf(" In buffer[%d]: %d ratio %f\n", i, pa->pixelsFoundInBuffer[i], (float)pa->pixelsFoundInBuffer[i] / (float)pa->pixelsFound ); pa->pixelsFoundInBuffer[i] = 0; } printf(" Loaded: %d ratio %f\n", pa->pixelsLoadedFromImage, (float)pa->pixelsLoadedFromImage / (float)pa->pixelsFound ); pa->pixelsLoadedFromImage = 0; } pa->pixelsFound = 0; } #endif PixelAccess* pixelAccessNew(GimpDrawable* drawable) { int i; PixelAccess* pa = (PixelAccess*)g_malloc(sizeof(PixelAccess)); pa->width = PixelAccessWidth; pa->height = PixelAccessHeight; pa->depth = drawable->bpp; pa->imageWidth = drawable->width; pa->imageHeight = drawable->height; gimp_pixel_rgn_init(&pa->srcPR, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE); for (i = 0; i < PixelAccessRegions; ++i) { pa->buffer[i] = (guchar*)g_malloc(pa->height * pa->width * pa->depth); /* better: pixelAccessReposition(), pixelAccessSelect()! */ gimp_pixel_rgn_get_rect(&pa->srcPR, pa->buffer[i], 0, 0, pa->width, pa->height); pa->tileMinX[i] = 1; pa->tileMaxX[i] = pa->width - 2; pa->tileMinY[i] = 1; pa->tileMaxY[i] = pa->height - 2; #if GATHERING_STATS pa->pixelsFoundInBuffer[i] = 0; #endif } #if GATHERING_STATS pa->pixelsFound = 0; pa->pixelsLoadedFromImage = 0; #endif return pa; } void pixelAccessDelete(PixelAccess* pa) { int i; for (i = 0; i < PixelAccessRegions; ++i) { g_free(pa->buffer[i]); } g_free(pa); } inline guchar* pixelAccessAddress(PixelAccess* pa, int i, int j) { return pa->buffer[0] + pa->depth * (pa->width * (j + 1 - pa->tileMinY[0]) + (i + 1 - pa->tileMinX[0])); } /* swap region[n] with region[0] */ void pixelAccessSelectRegion(PixelAccess* pa, int n) { guchar* temp; int a, b, c, d; int i; temp = pa->buffer[n]; a = pa->tileMinX[n]; b = pa->tileMaxX[n]; c = pa->tileMinY[n]; d = pa->tileMaxY[n]; for (i = n; i > 0; --i) { pa->buffer[i] = pa->buffer[i-1]; pa->tileMinX[i] = pa->tileMinX[i-1]; pa->tileMaxX[i] = pa->tileMaxX[i-1]; pa->tileMinY[i] = pa->tileMinY[i-1]; pa->tileMaxY[i] = pa->tileMaxY[i-1]; } pa->buffer[0] = temp; pa->tileMinX[0] = a; pa->tileMaxX[0] = b; pa->tileMinY[0] = c; pa->tileMaxY[0] = d; } /* buffer[0] is cleared, should start at [i, j], fill rows that overlap image */ void pixelAccessDoEdge(PixelAccess* pa, int i, int j) { int y; int lineStart, lineEnd; int rowStart, rowEnd; int lineWidth; guchar* line; lineStart = i; if (lineStart < 0) lineStart = 0; lineEnd = i + pa->width; if (lineEnd > pa->imageWidth) lineEnd = pa->imageWidth; lineWidth = lineEnd - lineStart; if (lineStart >= lineEnd) { return; } rowStart = j; if (rowStart < 0) rowStart = 0; rowEnd = j + pa->height; if (rowEnd > pa->imageHeight) rowEnd = pa->imageHeight; for (y = rowStart; y < rowEnd; ++y) { line = pixelAccessAddress(pa, lineStart, y); #if GATHERING_STATS pa->pixelsLoadedFromImage += lineWidth; #endif gimp_pixel_rgn_get_row(&pa->srcPR, line, lineStart, y, lineWidth); } } /* moves buffer[0] so that [x, y] is inside it */ void pixelAccessReposition(PixelAccess* pa, int xInt, int yInt) { int newStartX, newStartY; /* experiment! */ /* could look at current position of region, for example... */ /* this assumes random direction */ /* * newStartX = xInt - pa->width / 2; * newStartY = yInt - pa->height / 2; */ #if 0 /* what's wrong here? */ /* creating an array of overlapped tiles should work fine... */ newStartX = (PixelAccessWidth - 4) * (xInt / (PixelAccessWidth - 4)) - PixelAccessXOffset; newStartY = (PixelAccessHeight - 4) * (yInt / (PixelAccessHeight - 4)) - PixelAccessYOffset; #else /* this assumes mostly stepping min->max in x and y */ newStartX = xInt - PixelAccessXOffset; newStartY = yInt - PixelAccessYOffset; #endif pa->tileMinX[0] = newStartX + 1; pa->tileMaxX[0] = newStartX + pa->width - 2; pa->tileMinY[0] = newStartY + 1; pa->tileMaxY[0] = newStartY + pa->height - 2; if ( (newStartX < 0) || ((newStartX + pa->width) >= pa->imageWidth) || (newStartY < 0) || ((newStartY + pa->height) >= pa->imageHeight) ) { /* some data is off edge of image */ memset(pa->buffer[0], 0, pa->width * pa->height * pa->depth); if ( ((newStartX + pa->width) < 0) || (newStartX >= pa->imageWidth) || ((newStartY + pa->height) < 0) || (newStartY >= pa->imageHeight) ) { /* totally outside, just leave it */ } else { pixelAccessDoEdge(pa, newStartX, newStartY); } } else { #if GATHERING_STATS pa->pixelsLoadedFromImage += pa->width * pa->height; #endif gimp_pixel_rgn_get_rect(&pa->srcPR, pa->buffer[0], newStartX, newStartY, pa->width, pa->height); } } void pixelAccessGetCubic(PixelAccess* pa, double srcX, double srcY, double brighten, guchar* dst, int dstDepth) { int i; int xInt, yInt; double dx, dy; guchar* corner; xInt = floor(srcX); dx = srcX - xInt; yInt = floor(srcY); dy = srcY - yInt; #if GATHERING_STATS ++pa->pixelsFound; #endif /* we need 4x4 pixels, xInt-1 to xInt+2 horz, yInt-1 to yInt+2 vert */ /* they're probably in the last place we looked... */ if ((xInt >= pa->tileMinX[0]) && (xInt < pa->tileMaxX[0]) && (yInt >= pa->tileMinY[0]) && (yInt < pa->tileMaxY[0]) ) { #if GATHERING_STATS ++pa->pixelsFoundInBuffer[0]; #endif corner = pixelAccessAddress(pa, xInt - 1, yInt - 1); cubicInterpolate(corner, pa->depth * pa->width, pa->depth, dst, dstDepth, dx, dy, brighten); return; } /* or maybe it was a while back... */ for (i = 1; i < PixelAccessRegions; ++i) { if ((xInt >= pa->tileMinX[i]) && (xInt < pa->tileMaxX[i]) && (yInt >= pa->tileMinY[i]) && (yInt < pa->tileMaxY[i]) ) { #if GATHERING_STATS ++pa->pixelsFoundInBuffer[i]; #endif /* check here first next time */ pixelAccessSelectRegion(pa, i); corner = pixelAccessAddress(pa, xInt - 1, yInt - 1); cubicInterpolate(corner, pa->depth * pa->width, pa->depth, dst, dstDepth, dx, dy, brighten); return; } } /* nope, recycle an old region */ pixelAccessSelectRegion(pa, PixelAccessRegions - 1); pixelAccessReposition(pa, xInt, yInt); corner = pixelAccessAddress(pa, xInt - 1, yInt - 1); cubicInterpolate(corner, pa->depth * pa->width, pa->depth, dst, dstDepth, dx, dy, brighten); } /* * start at image (i, j), increment by (step, step) * output goes to dst, which is w x h x d in size * NB: d <= image.bpp */ void calcWideangleRect(int i, int j, int step, int dstWidth, int dstHeight, int dstDepth, guchar* dst) { int dstI, dstJ; int iLimit, jLimit; double srcX, srcY, mag, brighten; iLimit = i + dstWidth * step; jLimit = j + dstHeight * step; for (dstJ = j; dstJ < jLimit; dstJ += step) { for (dstI = i; dstI < iLimit; dstI += step) { getSourceCoords(dstI, dstJ, &srcX, &srcY, &mag); brighten = 1.0 + mag * calcVals.brighten; pixelAccessGetCubic(pa, srcX, srcY, brighten, dst, dstDepth); dst += dstDepth; } } } static void wideangle() { GimpPixelRgn destPR; gpointer pr; guchar* dest; gint x1, y1, x2, y2; int progress, progress_max; if (pa == 0) { pa = pixelAccessNew(contextVals.drawable); } #if GATHERING_STATS pixelAccessDumpStats(pa); #endif gimp_progress_init("Applying wideangle..."); gimp_drawable_mask_bounds(contextVals.id, &x1, &y1, &x2, &y2); progress_max = ((x2 - x1 - 1) / gimp_tile_width() + 1) * ((y2 - y1 - 1) / gimp_tile_height() + 1); dest = g_malloc(gimp_tile_width() * gimp_tile_height() * contextVals.depth); gimp_pixel_rgn_init(&destPR, contextVals.drawable, x1, y1, x2-x1, y2-y1, TRUE, TRUE); pr = gimp_pixel_rgns_register(1, &destPR); progress = 0; for ( ; pr; pr = gimp_pixel_rgns_process(pr)) { calcWideangleRect(destPR.x, destPR.y, 1, destPR.w, destPR.h, destPR.bpp, destPR.data); gimp_progress_update((float)progress / (float)progress_max); ++progress; } g_free(dest); gimp_drawable_flush(contextVals.drawable); gimp_drawable_merge_shadow(contextVals.id, TRUE); gimp_drawable_update(contextVals.id, x1, y1, (x2 - x1), (y2 - y1)); #if GATHERING_STATS pixelAccessDumpStats(pa); #endif } static void previewInit(WideanglePreview* wp, int detail) { if (detail) { wp->step = 1; wp->xSpacing = (contextVals.width - wp->width) / 4; wp->xStart = (contextVals.width - (wp->width + wp->xSpacing * 4)) / 2; wp->ySpacing = (contextVals.height - wp->height) / 4; wp->yStart = (contextVals.height - (wp->height + wp->ySpacing * 4)) / 2; } else { wp->step = wp->maxStep; wp->xStart = (contextVals.width - wp->width * wp->step * 5) / 2; wp->xSpacing = wp->width * wp->step; wp->yStart = (contextVals.height - wp->height * wp->step * 5) / 2; wp->ySpacing = wp->height * wp->step; } } static WideanglePreview* previewCreate() { int scale; WideanglePreview* wp = (WideanglePreview*) g_malloc(sizeof(WideanglePreview)); scale = floor(sqrt(contextVals.width * contextVals.height / PREVIEW_ROUGH_AREA)); if (scale < 1) scale = 1; wp->maxStep = scale; wp->width = contextVals.width / (scale * 5); wp->height = contextVals.height / (scale * 5); if (gimp_drawable_is_rgb(contextVals.id)) { wp->depth = 3; wp->preview = gtk_preview_new(GTK_PREVIEW_COLOR); } else { wp->depth = 1; wp->preview = gtk_preview_new(GTK_PREVIEW_GRAYSCALE); } wp->buffer = g_malloc0(wp->width * wp->height * wp->depth); gtk_preview_size(GTK_PREVIEW(wp->preview), wp->width * 5, wp->height * 5); wp->enabled = 0; wp->progress = 0; previewInit(wp, 0); return wp; } static void previewStartUpdate(WideanglePreview* wp) { if (!wp->enabled) { return; } #if GATHERING_STATS pixelAccessDumpStats(pa); #endif wideangleSetupCalc(); if (wp->progress == 0) { gtk_idle_add(previewIdleUpdateCB, wp); } wp->progress = 25; } static gint previewIdleUpdateCB(gpointer arg) { WideanglePreview* wp = (WideanglePreview*)arg; int x, y; int i; if (wp->enabled == 0) { wp->progress = 0; return 0; } --wp->progress; x = wp->progress / 5; y = wp->progress % 5; memset(wp->buffer, 255, wp->width * wp->depth); gtk_preview_draw_row(GTK_PREVIEW(wp->preview), wp->buffer, x * wp->width, y * wp->height, wp->width); gtk_widget_draw(wp->preview, 0); gdk_flush(); calcWideangleRect(wp->xStart + x * wp->xSpacing, wp->yStart + y * wp->ySpacing, wp->step, wp->width, wp->height, wp->depth, wp->buffer); for (i = 0; i < wp->height; ++i) { gtk_preview_draw_row(GTK_PREVIEW(wp->preview), wp->buffer + i * wp->width * wp->depth, x * wp->width, y * wp->height + i, wp->width); } gtk_widget_draw(wp->preview, 0); if (wp->progress == 0) { return 0; } return 1; } /* UI callback functions */ static void valueChangedCB(GtkWidget *widget, gpointer data) { vals.centre_x = guiVals.centre_x->value; vals.centre_y = guiVals.centre_y->value; vals.square_a = guiVals.square_a->value; vals.quad_a = guiVals.quad_a->value; vals.scale_a = guiVals.scale_a->value; vals.brighten = guiVals.bright_a->value; gtk_widget_set_sensitive(guiVals.saveButton, TRUE); previewStartUpdate(guiVals.preview); } static void previewEnableCB(GtkWidget *widget, gpointer data) { guiVals.preview->enabled = GTK_TOGGLE_BUTTON(widget)->active; if (guiVals.preview->enabled) { gtk_widget_show_all(guiVals.previewBox); previewStartUpdate(guiVals.preview); } else { gtk_widget_hide_all(guiVals.previewBox); } } static void previewDetailCB(GtkWidget *widget, gpointer data) { previewInit(guiVals.preview, GTK_TOGGLE_BUTTON(widget)->active); if (guiVals.preview->enabled) { previewStartUpdate(guiVals.preview); } } static void optionSelectCB(GtkWidget *widget, gpointer data) { if (data) { WideangleValues* preset = (WideangleValues*)data; gtk_adjustment_set_value(guiVals.centre_x, preset->centre_x); gtk_adjustment_set_value(guiVals.centre_y, preset->centre_y); gtk_adjustment_set_value(guiVals.square_a, preset->square_a); gtk_adjustment_set_value(guiVals.quad_a, preset->quad_a); gtk_adjustment_set_value(guiVals.scale_a, preset->scale_a); gtk_adjustment_set_value(guiVals.bright_a, preset->brighten); } gtk_widget_set_sensitive(guiVals.saveButton, FALSE); } /* utility function to add a preset option */ static void addPreset(const char* title, WideangleValues* values) { WideanglePreset* preset; GtkWidget* item; preset = g_new(WideanglePreset, 1); preset->val = values; strncpy(preset->name, title, 100); guiVals.presets = g_slist_append(guiVals.presets, preset); item = gtk_menu_item_new_with_label(preset->name); gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(optionSelectCB), values); gtk_widget_show(item); gtk_menu_append(GTK_MENU(guiVals.presetMenu), item); gtk_widget_set_sensitive(guiVals.selectButton, TRUE); } static void saveDoItCB(GtkWidget *query_box, const gchar *string, gpointer data) { WideangleValues* values; if (string == 0) { return; } if (strlen(string) == 0) { // g_free(string); return; } values = (WideangleValues*)g_malloc(sizeof(WideangleValues)); memcpy(values, &vals, sizeof(WideangleValues)); addPreset(string, values); writePresetFile(); gtk_widget_set_sensitive(guiVals.saveButton, FALSE); } static void resetCB(GtkWidget *widget, gpointer data) { gtk_adjustment_set_value(guiVals.centre_x, 0.0); gtk_adjustment_set_value(guiVals.centre_y, 0.0); gtk_adjustment_set_value(guiVals.square_a, 0.0); gtk_adjustment_set_value(guiVals.quad_a, 0.0); gtk_adjustment_set_value(guiVals.scale_a, 0.0); gtk_adjustment_set_value(guiVals.bright_a, 0.0); gtk_widget_set_sensitive(guiVals.saveButton, FALSE); } static void savePresetCB(GtkWidget *widget, gpointer data) { GtkWidget* dialog; dialog = gimp_query_string_box("Save Preset", 0, 0, 0, /* parent, help_func, help_id */ "Enter name for new preset", "newPreset", NULL, NULL, /* object, (deletion)signal */ &saveDoItCB, 0); gtk_widget_show_all(dialog); } static gint selectPresetCB(GtkWidget* widget, GdkEventButton* event) { gtk_menu_popup(GTK_MENU(guiVals.presetMenu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } static void readPresetFile() { guchar* fileName; FILE* file; guchar presetName[101]; float x, y, s, q, r, b; fileName = g_strdup_printf("%s" G_DIR_SEPARATOR_S "wideangle" G_DIR_SEPARATOR_S "presets.txt", gimp_directory()); file = fopen(fileName, "r"); if (file) { while (7 == fscanf(file, "%f %f %f %f %f %f %100[^\n]\n", &x, &y, &s, &q, &r, &b, presetName)) { WideangleValues* newVal = (WideangleValues*)g_malloc(sizeof(WideangleValues)); newVal->centre_x = x; newVal->centre_y = y; newVal->square_a = s; newVal->quad_a = q; newVal->scale_a = r; newVal->brighten = b; addPreset(presetName, newVal); } fclose(file); } g_free(fileName); } static void writePresetFile() { guchar* dirName; guchar* fileName; FILE* file; /* half-assed attempt to make sure the directory exists */ dirName = g_strdup_printf("%s" G_DIR_SEPARATOR_S "wideangle", gimp_directory()); mkdir(dirName, 0777); g_free(dirName); fileName = g_strdup_printf("%s" G_DIR_SEPARATOR_S "wideangle" G_DIR_SEPARATOR_S "presets.txt", gimp_directory()); file = fopen(fileName, "w"); if (file) { GSList* item = guiVals.presets; while (item) { WideanglePreset* preset = (WideanglePreset*)(item->data); fprintf(file, "%f %f %f %f %f %f %s\n", preset->val->centre_x, preset->val->centre_y, preset->val->square_a, preset->val->quad_a, preset->val->scale_a, preset->val->brighten, preset->name); item = item->next; } fclose(file); } g_free(fileName); } /* utility fn to create a control group and add it to the control panel */ static GtkAdjustment* addControl(GtkWidget* vbox, const char* name, double init) { GtkWidget* label; GtkWidget* hbox; GtkWidget* hbox2; GtkWidget* slider; GtkWidget* spinner; GtkAdjustment* adjustment; GtkStyle* style; gint zeroWidth; label = gtk_label_new(name); adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(init, -100.0, +100.0, 0.1, 1.0, 0.0)); slider = gtk_hscale_new(adjustment); gtk_scale_set_digits(GTK_SCALE(slider), 3); gtk_scale_set_draw_value(GTK_SCALE(slider), FALSE); spinner = gtk_spin_button_new(adjustment, 0.0, 1); style = gtk_widget_get_style(spinner); /* Need sensible width for value display */ /* Add a small amount to accommodate the arrows */ /* Of course, this should be done automagically */ zeroWidth = gdk_string_width(gtk_style_get_font(style), "0"); gtk_widget_set_usize(GTK_WIDGET(spinner), zeroWidth * 10, 0); hbox = gtk_hbox_new(TRUE, 2); hbox2 = gtk_hbox_new(TRUE, 2); gtk_box_pack_start(GTK_BOX(hbox2), label, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox2), spinner, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), hbox2, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), slider, TRUE, TRUE, 0); gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", GTK_SIGNAL_FUNC(valueChangedCB), 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0); return adjustment; } static void wideangleDialog() { GtkWidget* dialog; GtkWidget* toggle; GtkWidget* controls; GtkWidget* frame; GtkWidget* vbox; GtkWidget* hbox; GtkWidget* hbox2; GtkWidget* button; if (pa == 0) { pa = pixelAccessNew(contextVals.drawable); } gimp_ui_init(PLUGIN_NAME, TRUE); guiVals.do_preview = 0; dialog = gimp_dialog_new( "Wideangle Effect", PLUGIN_NAME, 0, 0, 0, 0, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, 0); hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, FALSE, 0); /* control widgets */ controls = gtk_vbox_new(FALSE, 0); /* gtk_container_set_border_width(GTK_CONTAINER(controls), 4); */ gtk_box_pack_start(GTK_BOX(hbox), controls, TRUE, TRUE, 0); frame = gtk_frame_new("Parameter Settings"); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_container_set_border_width(GTK_CONTAINER(frame), 6); gtk_box_pack_start(GTK_BOX(controls), frame, FALSE, FALSE, 0); vbox = gtk_vbox_new(FALSE, 2); gtk_container_set_border_width(GTK_CONTAINER(vbox), 4); gtk_container_add(GTK_CONTAINER(frame), vbox); guiVals.centre_x = addControl(vbox, "X Shift", vals.centre_x); guiVals.centre_y = addControl(vbox, "Y Shift", vals.centre_y); guiVals.square_a = addControl(vbox, "Main", vals.square_a); guiVals.quad_a = addControl(vbox, "Edge", vals.quad_a); guiVals.scale_a = addControl(vbox, "Zoom", vals.scale_a); guiVals.bright_a = addControl(vbox, "Brighten", vals.brighten); frame = gtk_frame_new("Presets"); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_container_set_border_width(GTK_CONTAINER(frame), 6); gtk_box_pack_start(GTK_BOX(controls), frame, FALSE, FALSE, 0); hbox2 = gtk_hbox_new(TRUE, 2); gtk_container_set_border_width(GTK_CONTAINER(hbox2), 4); gtk_container_add(GTK_CONTAINER(frame), hbox2); button = gtk_button_new_with_label("Reset"); /* Is this really the way to give the label some air? */ gtk_misc_set_padding(GTK_MISC(GTK_BIN(button)->child), 6, 3); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(resetCB), 0); gtk_box_pack_start(GTK_BOX(hbox2), button, FALSE, FALSE, 0); guiVals.presets = NULL; guiVals.presetMenu = gtk_menu_new(); gtk_widget_show_all(guiVals.presetMenu); guiVals.selectButton = gtk_button_new_with_label("Select"); gtk_widget_set_sensitive(guiVals.selectButton, FALSE); gtk_misc_set_padding(GTK_MISC(GTK_BIN(guiVals.selectButton)->child), 6, 3); gtk_signal_connect_object(GTK_OBJECT(guiVals.selectButton), "button_press_event", GTK_SIGNAL_FUNC(selectPresetCB), NULL); gtk_box_pack_start(GTK_BOX(hbox2), guiVals.selectButton, FALSE, FALSE, 0); guiVals.saveButton = gtk_button_new_with_label("Save As"); gtk_misc_set_padding(GTK_MISC(GTK_BIN(guiVals.saveButton)->child), 6, 3); gtk_widget_set_sensitive(guiVals.saveButton, FALSE); gtk_signal_connect(GTK_OBJECT(guiVals.saveButton), "clicked", GTK_SIGNAL_FUNC(savePresetCB), 0); gtk_box_pack_start(GTK_BOX(hbox2), guiVals.saveButton, FALSE, FALSE, 0); hbox2 = gtk_hbox_new(FALSE, 2); gtk_container_set_border_width(GTK_CONTAINER(hbox2), 4); gtk_box_pack_start(GTK_BOX(controls), hbox2, FALSE, FALSE,0); /* would like to put check box on other side */ toggle = gtk_check_button_new_with_label("Do Preview"); gtk_container_set_border_width(GTK_CONTAINER(toggle), 4); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), guiVals.do_preview); gtk_signal_connect(GTK_OBJECT(toggle), "toggled", GTK_SIGNAL_FUNC(previewEnableCB), 0); gtk_box_pack_end(GTK_BOX(hbox2), toggle, FALSE, FALSE, 2); /* That's all that's normally visible */ gtk_widget_show_all(dialog); /* preview widgets - hidden by default */ /* on rhs so that dialog box doesn't jump when they're made visible */ /* however, dialog OK and Cancel buttons jump instead... */ /* maybe should put preview on left and adjust (?)window gravity(?) */ guiVals.previewBox = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), guiVals.previewBox, FALSE, FALSE, 8); guiVals.preview = previewCreate(); gtk_box_pack_start(GTK_BOX(guiVals.previewBox), guiVals.preview->preview, FALSE, FALSE, 8); toggle = gtk_check_button_new_with_label("Detail"); gtk_signal_connect(GTK_OBJECT(toggle), "toggled", GTK_SIGNAL_FUNC(previewDetailCB), 0); gtk_box_pack_start(GTK_BOX(guiVals.previewBox), toggle, FALSE, FALSE, 2); gtk_widget_hide_all(guiVals.previewBox); readPresetFile(); valueChangedCB(0, 0); if (gimp_dialog_run(GIMP_DIALOG(dialog)) == GTK_RESPONSE_OK) { contextVals.do_it = TRUE; } gtk_widget_destroy(dialog); #if GATHERING_STATS pixelAccessDumpStats(pa); #endif }