/* Pinch 1.03 --- image filter plug-in for The Gimp image manipulation program * Copyright (C) 1996 Federico Mena Quintero * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * You can contact me at quartic@polloux.fciencias.unam.mx * You can contact the original The Gimp authors at gimp@xcf.berkeley.edu */ #include #include #include #include "gimp.h" /* Some useful macros */ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define WITHIN(a, b, c) ((((a) <= (b)) && ((b) <= (c))) ? 1 : 0) /* Precision for scale */ #define PRECISION 1000 #define PRECISION_DIGITS 3 #ifndef _AIX typedef unsigned char uchar; #endif /***** Local functions *****/ static void scale_callback(int item_ID, void *client_data, void *call_data); static void ok_callback(int item_ID, void *client_data, void *call_data); static void cancel_callback(int item_ID, void *client_data, void *call_data); static void pinch(Image input, Image output, double amount); static uchar bilinear(double x, double y, uchar *v); /***** Local vars *****/ static long pinch_amount; static char *prog_name; static int dialog_id; /***** Functions *****/ /*****/ int main(int argc, char **argv) { Image input, output; void *data; int scale_id; /* Save program name */ prog_name = argv[0]; /* Initialize filter and continue if success */ if (!gimp_init(argc, argv)) return 0; /* Get default input and output images */ input = gimp_get_input_image(0); output = gimp_get_output_image(0); /* If both images were available, continue */ if (input && output) { if ((gimp_image_type(input) == RGB_IMAGE) || (gimp_image_type(input) == GRAY_IMAGE)) { data = gimp_get_params(); if (data) pinch_amount = *((long *) data); else pinch_amount = 0; dialog_id = gimp_new_dialog("Pinch"); gimp_new_label(dialog_id, DEFAULT, "Amount:"); scale_id = gimp_new_scale(dialog_id, DEFAULT, -PRECISION, PRECISION, pinch_amount, PRECISION_DIGITS); gimp_add_callback(dialog_id, scale_id, scale_callback, &pinch_amount); gimp_add_callback(dialog_id, gimp_ok_item_id(dialog_id), ok_callback, NULL); gimp_add_callback(dialog_id, gimp_cancel_item_id(dialog_id), cancel_callback, NULL); if (gimp_show_dialog(dialog_id)) { gimp_set_params(sizeof(pinch_amount), &pinch_amount); gimp_init_progress("Pinch"); pinch(input, output, (double) pinch_amount / PRECISION); gimp_update_image(output); } /* if */ } else gimp_message("Pinch: can only operate on 24-bit or grayscale images"); gimp_free_image(input); gimp_free_image(output); gimp_quit(); } /* if */ return 0; } /* main */ /*****/ static void scale_callback(int item_id, void *client_data, void *call_data) { *((long *) client_data) = *((long *) call_data); } /* text_callback */ /*****/ static void ok_callback(int item_id, void *client_data, void *call_data) { gimp_close_dialog(dialog_id, 1); } /* ok_callback */ /*****/ static void cancel_callback(int item_id, void *client_data, void *call_data) { gimp_close_dialog(dialog_id, 0); } /* cancel_callback */ /*****/ static void pinch(Image input, Image output, double amount) { long width, height; long channels, rowsiz; uchar *src, *p; uchar *destrow; uchar *dest; int x1, y1, x2, y2; int x, y; int progress, max_progress; double cen_x, cen_y; /* Center of pinch */ double xhsiz, yhsiz; /* Half size of selection */ double radius, radius2; /* Radius and radius^2 */ double amnt, d; double needx, needy; double dx, dy; double xscale, yscale; int xi, yi; uchar values[4]; uchar val; int k; /* Get selection area */ gimp_image_area(input, &x1, &y1, &x2, &y2); width = gimp_image_width(input); height = gimp_image_height(input); channels = gimp_image_channels(input); rowsiz = width * channels; progress = 0; max_progress = y2 - y1; src = gimp_image_data(input); destrow = gimp_image_data(output); /* Pinch the image. For each pixel in the destination image which is inside the selection, we compute the corresponding pinched location in the source image. We use bilinear interpolation to avoid ugly jaggies. Let's assume that we are operating on a circular area. Every point within distance of the pinch center is pinched to its destination position. To keep this explanation simple, for positive pinch amounts, all points within distance of the pinch center are brought nearer the center. If point P in the destination image lies at distance d from the center (normalized to 1, of course), we take the point from the source image which lies at the same direction from the center as P, but that is at a distance of sin(pi/2 * d). This value is raised to the user-specified amount power, and the pixel is fetched. If the user specifies a positive value, the image is pinched 'inwards'... if the value is negative, the image is pinched 'outwards'. To support elliptical pinches, we multiply the x and y distances to the pinch center by the proper scaling factors to internally turn it into a circle. */ /* Center of selection */ cen_x = (double) (x2 - 1 + x1) / 2.0; cen_y = (double) (y2 - 1 + y1) / 2.0; /* Compute pinch radii (semiaxes) */ xhsiz = (double) (x2 - x1) / 2.0; yhsiz = (double) (y2 - y1) / 2.0; /* These are the necessary scaling factors to turn the pinch ellipse into a large circle */ if (xhsiz < yhsiz) { xscale = yhsiz / xhsiz; yscale = 1.0; } else if (xhsiz > yhsiz) { xscale = 1.0; yscale = xhsiz / yhsiz; } else { xscale = 1.0; yscale = 1.0; } /* else */ radius = MAX(xhsiz, yhsiz); radius2 = radius * radius; /* Pinch the image! */ destrow += y1 * rowsiz + x1 * channels; /* We negate the user-specified pinch amount so that user-positive values pinch 'into' the image and user-negative ones pinch 'outside' the image. */ amount = -amount; for (y = y1; y < y2; y++) { dest = destrow; for (x = x1; x < x2; x++) { /* Distance from current point to pinch center, scaled */ dx = (x - cen_x) * xscale; dy = (y - cen_y) * yscale; /* Distance^2 to center of *circle* (our scaled ellipse) */ d = dx * dx + dy * dy; /* If we are inside circle, then pinch. Else, just copy the pixel */ if (d < radius2) { /* Use the formula described above */ amnt = pow(sin(M_PI_2 * sqrt(d) / radius), amount); /* Calculate pinched point and scale again to ellipsify */ needx = amnt * dx / xscale + cen_x; needy = amnt * dy / yscale + cen_y; /* Calculations complete; now copy the proper pixel */ xi = needx; yi = needy; p = src + rowsiz * yi + xi * channels; for (k = 0; k < channels; k++) { if (WITHIN(0, xi, width - 1) && WITHIN(0, yi, height - 1)) values[0] = *(p + k); else values[0] = 0; if (WITHIN(0, xi + 1, width - 1) && WITHIN(0, yi, height - 1)) values[1] = *(p + channels + k); else values[1] = 0; if (WITHIN(0, xi, width - 1) && WITHIN(0, yi + 1, height - 1)) values[2] = *(p + rowsiz + k); else values[2] = 0; if (WITHIN(0, xi + 1, width - 1) && WITHIN(0, yi + 1, height - 1)) values[3] = *(p + rowsiz + channels + k); else values[3] = 0; val = bilinear(needx, needy, values); *dest++ = val; } /* for */ } else { p = src + rowsiz * y + x * channels; for (k = 0; k < channels; k++) *dest++ = *p++; } /* else */ } /* for */ destrow += rowsiz; progress++; if (progress % 5 == 0) gimp_do_progress(progress, max_progress); } /* for */ } /* pinch */ /*****/ static uchar bilinear(double x, double y, uchar *v) { double m0, m1; x = fmod(x, 1.0); y = fmod(y, 1.0); m0 = (1.0 - x) * v[0] + x * v[1]; m1 = (1.0 - x) * v[2] + x * v[3]; return (uchar) ((1.0 - y) * m0 + y * m1); } /* bilinear */