/* * This is a plug-in for the GIMP. * * Copyright (C) 1995 Spencer Kimball and Peter Mattis * Copyright (C) 1996 Torsten Martinsen * * 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. * * $Id: blur2.c,v 1.16 1996/08/19 16:48:03 torsten Exp $ * * @(GIMP) = * @(GIMP_DEP) = * @(GIMP_OBJ) = * @(GIMP_LIB) = * @(GIMP_AUTHOR) = * @(GIMP_EMAIL) = * @(GIMP_DESC) = * @(GIMP_VERSION) = <$Revision: 1.16 $> * @(GIMP_URL) = */ /* * This filter is like the standard 'blur', except that it uses * a convolution kernel of variable size. * * I am greatly indebted to Johan Klockars * for supplying the algorithm used here, which is of complexity O(1). * Compared with the original naive algorithm, which is O(k^2) (where * k is the kernel size), it gives _massive_ speed improvements. * The code is quite simple, too. */ #include "gimp.h" #include #ifndef MIN #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif #ifndef MAX #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #endif static void blur(Image, Image); static void blur_rgb(Image input, Image output, int chans); static void blur_grey(Image input, Image output, int chans); static int n = 5; static int dialog_ID; static void scale_callback(int, void *, void *); static void ok_callback(int, void *, void *); static void cancel_callback(int, void *, void *); int main(int argc, char **argv) { Image input = 0, output = 0; void *data; char msg[25]; int group_ID, scale_ID, temp_ID; if (gimp_init(argc, argv)) { input = gimp_get_input_image(0); if (input) { switch (gimp_image_type(input)) { case RGB_IMAGE: case GRAY_IMAGE: case RGBA_IMAGE: case GRAYA_IMAGE: data = gimp_get_params(); if (data) n = ((int *) data)[0]; dialog_ID = gimp_new_dialog("Variable Blur"); group_ID = gimp_new_row_group(dialog_ID, DEFAULT, NORMAL, ""); temp_ID = gimp_new_column_group (dialog_ID, group_ID, NORMAL, ""); gimp_new_label(dialog_ID, temp_ID, "Kernel size"); /* * Unfortunately, there is currently no way to specify * a scale increment other than 1. */ scale_ID = gimp_new_scale(dialog_ID, group_ID, 3, 25, n, 0); gimp_add_callback(dialog_ID, scale_ID, scale_callback, &n); gimp_add_callback(dialog_ID, gimp_ok_item_id(dialog_ID), ok_callback, 0); gimp_add_callback(dialog_ID, gimp_cancel_item_id(dialog_ID), cancel_callback, 0); if (gimp_show_dialog(dialog_ID)) { gimp_set_params(sizeof(int), &n); output = gimp_get_output_image(0); if (output) { sprintf(msg, "Blur (kernel size %d)", n); gimp_init_progress(msg); blur(input, output); gimp_update_image(output); } } break; default: gimp_message("blur: cannot operate on indexed color images"); } } if (input) gimp_free_image(input); if (output) gimp_free_image(output); gimp_quit(); } return 0; } static void blur(Image input, Image output) { switch (gimp_image_type(input)) { case RGB_IMAGE: blur_rgb(input, output, 3); break; case RGBA_IMAGE: blur_rgb(input, output, 4); break; case GRAY_IMAGE: blur_grey(input, output, 1); break; case GRAYA_IMAGE: blur_grey(input, output, 2); break; default: break; } } static void blur_rgb(Image input, Image output, int chans) { long width, height, rowstride; unsigned char *src, *src_row, *dest, *dest_row, *s, *d; Image temp; int x, y, r, g, b, i, sx1, sy1, sx2, sy2, x1, y1, x2, y2, div, alpha; gimp_image_area(input, &sx1, &sy1, &sx2, &sy2); width = gimp_image_width(input); height = gimp_image_height(input); rowstride = width * chans; alpha = chans-3; src = gimp_image_data(input); temp = gimp_new_image("temp", width, height, gimp_image_type(input)); dest = gimp_image_data(temp); /* Include edges if possible */ x1 = MAX(sx1-n/2, 0); x2 = MIN(sx2+n/2, width); y1 = MAX(sy1-n/2, 0); y2 = MIN(sy2+n/2, height); /* Vertical blur */ src_row = src + rowstride * y1 + x1 * chans; dest_row = dest + rowstride * (y1+n/2) + x1 * chans; for (x = x1; x < x2; ++x) { s = src_row; d = dest_row; r = g = b = 0; for (i = 0; i < n; ++i) { r += s[0]; g += s[1]; b += s[2]; s += rowstride; } d[0] = r/n; d[1] = g/n; d[2] = b/n; d += rowstride; s = src_row; for (y = y1; y < y2-n; ++y) { r = r - s[0] + s[n*rowstride]; g = g - s[1] + s[n*rowstride+1]; b = b - s[2] + s[n*rowstride+2]; d[0] = r/n; d[1] = g/n; d[2] = b/n; s += rowstride; d += rowstride; } dest_row += chans; src_row += chans; if ((x % 10) == 0) gimp_do_progress(x/2, x2-x1); } /* Do remaining top pixels, if any */ if (y1-n/2 < 0) { src_row = src; dest_row = dest; for (x = x1; x < x2; ++x) { s = src_row; d = dest_row; r = g = b = 0; div = n/2+1; for (i = 0; i < div; ++i) { r += s[0]; g += s[1]; b += s[2]; s += rowstride; } d[0] = r/div; d[1] = g/div; d[2] = b/div; d += rowstride; for (y = 0; y < n/2; ++y) { r = r + s[0]; g = g + s[1]; b = b + s[2]; ++div; d[0] = r/div; d[1] = g/div; d[2] = b/div; s += rowstride; d += rowstride; } dest_row += chans; src_row += chans; } } /* Do remaining bottom pixels, if any */ if (y2+n/2 > height) { src_row = src + rowstride * (height-1); dest_row = dest + rowstride * (height-1); for (x = x1; x < x2; ++x) { s = src_row; d = dest_row; r = g = b = 0; div = n/2+1; for (i = 0; i < div; ++i) { r += s[0]; g += s[1]; b += s[2]; s -= rowstride; } d[0] = r/div; d[1] = g/div; d[2] = b/div; d -= rowstride; for (y = 0; y < n/2; ++y) { r = r + s[0]; g = g + s[1]; b = b + s[2]; ++div; d[0] = r/div; d[1] = g/div; d[2] = b/div; s -= rowstride; d -= rowstride; } dest_row += chans; src_row += chans; } } src = dest; dest = gimp_image_data(output); /* Horizontal blur */ src_row = src + rowstride * y1 + x1 * chans; dest_row = dest + rowstride * y1 + (x1+n/2) * chans; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; r = g = b = 0; for (i = 0; i < n; ++i) { r += *s++; g += *s++; b += *s++; s += alpha; } *d++ = r/n; *d++ = g/n; *d++ = b/n; d += alpha; s = src_row; for (x = x1; x < x2-n; ++x) { r = r - s[0] + s[n*chans]; ++s; g = g - s[0] + s[n*chans]; ++s; b = b - s[0] + s[n*chans]; ++s; s += alpha; *d++ = r/n; *d++ = g/n; *d++ = b/n; d += alpha; } dest_row += rowstride; src_row += rowstride; if ((y % 10) == 0) gimp_do_progress((y+y2-y1)/2, y2-y1); } /* Do remaining left pixels, if any */ if (x1-n/2 < 0) { src_row = src; dest_row = dest; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; r = g = b = 0; div = n/2+1; for (i = 0; i < div; ++i) { r += *s++; g += *s++; b += *s++; s += alpha; } *d++ = r/div; *d++ = g/div; *d++ = b/div; d += alpha; for (x = 0; x < n/2; ++x) { r += *s++; g += *s++; b += *s++; s += alpha; ++div; *d++ = r/div; *d++ = g/div; *d++ = b/div; d += alpha; } dest_row += rowstride; src_row += rowstride; } } /* Do remaining right pixels, if any */ if (x2+n/2 > width) { src_row = src + width * chans; dest_row = dest + width * chans; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; r = g = b = 0; div = n/2+1; for (i = 0; i < div; ++i) { s -= alpha; b += *--s; g += *--s; r += *--s; } d -= alpha; *--d = b/div; *--d = g/div; *--d = r/div; for (x = 0; x < n/2; ++x) { s -= alpha; b += *--s; g += *--s; r += *--s; ++div; d -= alpha; *--d = b/div; *--d = g/div; *--d = r/div; } dest_row += rowstride; src_row += rowstride; } } gimp_free_image(temp); /* Copy alpha channel */ if (chans == 3) return; src_row = gimp_image_data(input) + rowstride * y1 + x1 * chans + 3; dest_row = dest + rowstride * y1 + x1 * chans + 3; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; for (x = x1; x < x2; ++x) { *d = *s; d += 4; s += 4; } dest_row += rowstride; src_row += rowstride; } } static void blur_grey(Image input, Image output, int chans) { long width, height, rowstride; unsigned char *src, *src_row, *dest, *dest_row, *s, *d; Image temp; int x, y, r, i, sx1, sy1, sx2, sy2, x1, y1, x2, y2, div; gimp_image_area(input, &sx1, &sy1, &sx2, &sy2); width = gimp_image_width(input); height = gimp_image_height(input); rowstride = width * chans; src = gimp_image_data(input); temp = gimp_new_image("temp", width, height, gimp_image_type(input)); dest = gimp_image_data(temp); /* Include edges if possible */ x1 = MAX(sx1-n/2, 0); x2 = MIN(sx2+n/2, width); y1 = MAX(sy1-n/2, 0); y2 = MIN(sy2+n/2, height); /* Vertical blur */ src_row = src + rowstride * y1 + x1 * chans; dest_row = dest + rowstride * (y1+n/2) + x1 * chans; for (x = x1; x < x2; ++x) { s = src_row; d = dest_row; r = 0; for (i = 0; i < n; ++i) { r += s[0]; s += rowstride; } d[0] = r/n; d += rowstride; s = src_row; for (y = y1; y < y2-n; ++y) { r = r - s[0] + s[n*rowstride]; d[0] = r/n; s += rowstride; d += rowstride; } dest_row += chans; src_row += chans; if ((x % 10) == 0) gimp_do_progress(x/2, x2-x1); } /* Do remaining top pixels, if any */ if (y1-n/2 < 0) { src_row = src; dest_row = dest; for (x = x1; x < x2; ++x) { s = src_row; d = dest_row; r = 0; div = n/2+1; for (i = 0; i < div; ++i) { r += s[0]; s += rowstride; } d[0] = r/div; d += rowstride; for (y = 0; y < n/2; ++y) { r = r + s[0]; ++div; d[0] = r/div; s += rowstride; d += rowstride; } dest_row += chans;; src_row += chans;; } } /* Do remaining bottom pixels, if any */ if (y2+n/2 > height) { src_row = src + rowstride * (height-1); dest_row = dest + rowstride * (height-1); for (x = x1; x < x2; ++x) { s = src_row; d = dest_row; r = 0; div = n/2+1; for (i = 0; i < div; ++i) { r += s[0]; s -= rowstride; } d[0] = r/div; d -= rowstride; for (y = 0; y < n/2; ++y) { r = r + s[0]; ++div; d[0] = r/div; s -= rowstride; d -= rowstride; } dest_row += chans; src_row += chans; } } src = dest; dest = gimp_image_data(output); /* Horizontal blur */ src_row = src + rowstride * y1 + x1 * chans; dest_row = dest + rowstride * y1 + (x1+n/2) * chans; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; r = 0; for (i = 0; i < n; ++i) { r += *s; s += chans; } *d = r/n; d += chans; s = src_row; for (x = x1; x < x2-n; ++x) { r = r - s[0] + s[n*chans]; *d = r/n; s += chans; d += chans; } dest_row += rowstride; src_row += rowstride; if ((y % 10) == 0) gimp_do_progress((y+y2-y1)/2, y2-y1); } /* Do remaining left pixels, if any */ if (x1-n/2 < 0) { src_row = src; dest_row = dest; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; r = 0; div = n/2+1; for (i = 0; i < div; ++i) { r += *s; s += chans; } *d = r/div; d += chans; for (x = 0; x < n/2; ++x) { r += *s; ++div; *d = r/div; s += chans; d += chans; } dest_row += rowstride; src_row += rowstride; } } /* Do remaining right pixels, if any */ if (x2+n/2 > width) { src_row = src + width*chans; dest_row = dest + width*chans; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; r = 0; div = n/2+1; for (i = 0; i < div; ++i) { s -= chans; r += *s; } d -= chans; *d = r/div; for (x = 0; x < n/2; ++x) { s -= chans; d -= chans; r += *s; ++div; *d = r/div; } dest_row += rowstride; src_row += rowstride; } } gimp_free_image(temp); /* Copy alpha channel */ if (chans == 1) return; src_row = gimp_image_data(input) + rowstride * y1 + x1 * chans + 1; dest_row = dest + rowstride * y1 + x1 * chans + 1; for (y = y1; y < y2; ++y) { s = src_row; d = dest_row; for (x = x1; x < x2; ++x) { *d = *s; d += 2; s += 2; } dest_row += rowstride; src_row += rowstride; } } static void scale_callback(int item_ID, void *client_data, void *call_data) { int n; n = *((long *) call_data); *((int *) client_data) = n | 1; } static void ok_callback(int item_ID, void *client_data, void *call_data) { gimp_close_dialog(dialog_ID, 1); } static void cancel_callback(int item_ID, void *client_data, void *call_data) { gimp_close_dialog(dialog_ID, 0); }