/* Whirl 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) #ifndef _AIX /* Does AIX puke on this? Other plug-ins */ typedef unsigned char uchar; /* may need a fix, too... :( */ #endif /***** Local functions *****/ static void text_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 whirl(Image input, Image output, double angle); static uchar bilinear(double x, double y, uchar *v); /***** Local vars *****/ static double whirl_angle; static char *prog_name; static int dialog_ID; /***** Functions *****/ /*****/ int main(int argc, char **argv) { Image input, output; void *data; char buf[100]; int text_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) whirl_angle = *((double *) data); else whirl_angle = 0.0; dialog_ID = gimp_new_dialog("Whirl"); gimp_new_label(dialog_ID, DEFAULT, "Angle:"); sprintf(buf, "%0.2f", whirl_angle); text_ID = gimp_new_text(dialog_ID, DEFAULT, buf); gimp_add_callback(dialog_ID, text_ID, text_callback, &whirl_angle); 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(whirl_angle), &whirl_angle); gimp_init_progress("Whirl"); whirl(input, output, whirl_angle); gimp_update_image(output); } /* if */ } else gimp_message("Whirl: 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 text_callback(int item_ID, void *client_data, void *call_data) { *((double *) client_data) = atof(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 whirl(Image input, Image output, double angle) { 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 whirl */ double xhsiz, yhsiz; /* Half size of selection */ double radius, radius2; /* Radius and radius^2 */ double ang, d, factor; double sina, cosa; 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); /* Whirl the image. For each pixel in the (destination) image which is inside the selection, we compute the corresponding (whirled) location in the source image. We do a nice bilinear interpolation to avoid jaggies. The whirl algorithm is as follows. For now we will assume that we are whirling on a circular area. Every point within distance of the whirl center is rotated to the destination position. Points which are exactly at distance from the center rotate 0 degrees (i.e. they remain where they are), and the center point 'rotates' the full whirl angle. So points which are near the center rotate more than the distant ones. The formula is rot_angle = whirl_angle * (1 - distance / radius)^2 We square the (1-d/r) so that the rot_angle function has a zero derivative when distance==radius, so that we get a smooth transition from the unrotated exterior to the whirled interior. Other exponents (or completely different functions) can be used, but this one looks nice :) Also, to support elliptical whirls, we multiply the x and y distances to the whirl center by the proper scaling factors. */ /* Convert angle to radians */ angle = M_PI * angle / 180.0; /* Center of selection */ cen_x = (double) (x2 - 1 + x1) / 2.0; cen_y = (double) (y2 - 1 + y1) / 2.0; /* Compute whirl radii (semiaxes) */ xhsiz = (double) (x2 - x1) / 2.0; yhsiz = (double) (y2 - y1) / 2.0; /* These are the necessary scaling factors to turn the whirl 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; /* Whirl the image! */ destrow += y1 * rowsiz + x1 * channels; for (y = y1; y < y2; y++) { dest = destrow; for (x = x1; x < x2; x++) { /* Distance from current point to whirl 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 whirl. Else, just copy the pixel */ if (d < radius2) { /* Use the formula described above */ d = sqrt(d); factor = 1 - d / radius; ang = angle * factor * factor; /* Calculate rotated point and scale again to ellipsify */ sina = sin(ang); cosa = cos(ang); needx = (cosa * dx - sina * dy) / xscale + cen_x; needy = (sina * dx + cosa * 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 */ } /* whirl */ /*****/ 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 */