/* Page Curl 0.5 --- image filter plug-in for The Gimp * Copyright (C) 1996 Federico Mena Quintero * * You can contact me at quartic@polloux.fciencias.unam.mx * You can contact the original The Gimp authors at gimp@xcf.berkeley.edu * * 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. * */ /* TODO: * * As of version 0.5 alpha, the only thing that is not yet implemented * is the "Warp curl" option. Everything else seems to be working * just fine. Please email me if you find any bugs. I know that the * calculation code is horrible, but you don't want to tweak it anyway ;) */ #include #include #include #include "gimp.h" /***** Macros *****/ #define WITHIN(a, b, c) ((((a) <= (b)) && ((b) <= (c))) ? 1 : 0) /***** Types *****/ #ifndef _AIX typedef unsigned char uchar; #endif typedef struct { double x, y; } vector_t; /***** Prototypes *****/ static void set_default_params(void); static void radio_callback(int item_id, void *client_data, void *call_data); static void toggle_callback(int item_id, void *client_data, void *call_data); 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 int do_dialog(void); static void v_set(vector_t *v, double x, double y); static void v_add(vector_t *v, vector_t a, vector_t b); static void v_sub(vector_t *v, vector_t a, vector_t b); static double v_mag(vector_t v); static double v_dot(vector_t a, vector_t b); static void init_calculation(void); static int left_of_diagl(double x, double y); static int right_of_diagr(double x, double y); static int below_diagb(double x, double y); static int right_of_diagm(double x, double y); static int inside_circle(double x, double y); static void put_pixel(uchar *image, int x, int y, uchar *p); static void get_pixel(uchar *image, int x, int y, uchar *p); static void do_progress(void); static void paint_background(void); static void shade_under(void); static void shade_curl(void); static void page_curl(void); /***** Variables *****/ /* Program name */ char *prog_name; /* Image parameters */ Image src_image, dest_image; uchar *src_data, *dest_data; long width, height; long channels, rowsiz; int sel_x1, sel_y1, sel_x2, sel_y2; int true_sel_width, true_sel_height; int sel_width, sel_height; int progress, max_progress; /* Center and radius of circle */ vector_t center; double radius; /* Useful points to keep around */ vector_t left_tangent; vector_t right_tangent; /* Slopes --- these are *not* in the usual geometric sense! */ double diagl_slope; double diagr_slope; double diagb_slope; double diagm_slope; /* User-configured parameters */ int dialog_id; long do_curl_shade; long do_curl_warp; long do_curl_foreground; long do_curl_background; double do_curl_opacity; long do_shade_under; long do_background_foreground; long do_background_background; long do_background_original; long do_upper_left; long do_upper_right; long do_lower_left; long do_lower_right; long do_vertical; long do_horizontal; uchar fore_color[3]; uchar back_color[3]; /***** Functions *****/ /*****/ int main(int argc, char **argv) { /* Save program name */ prog_name = argv[0]; /* Initialize filter and continue if success */ if (!gimp_init(argc, argv)) return 0; src_image = gimp_get_input_image(0); dest_image = gimp_get_output_image(0); if (src_image && dest_image) if (gimp_image_type(src_image) == RGB_IMAGE || gimp_image_type(src_image) == GRAY_IMAGE) { set_default_params(); if (do_dialog()) { page_curl(); gimp_update_image(dest_image); } /* if */ } else gimp_message("Page Curl: can only operate on 24-bit or grayscale images"); /* Free images */ if (src_image) gimp_free_image(src_image); if (dest_image) gimp_free_image(dest_image); /* Terminate */ gimp_quit(); return 0; } /* main */ /*****/ static void set_default_params(void) { do_curl_shade = 1; do_curl_warp = 0; do_curl_foreground = 0; do_curl_background = 1; do_curl_opacity = 0.75; do_shade_under = 1; do_background_foreground = 0; do_background_background = 1; do_background_original = 0; do_upper_left = 0; do_upper_right = 0; do_lower_left = 0; do_lower_right = 1; do_vertical = 1; do_horizontal = 0; } /* set_default_params */ /*****/ static void radio_callback(int item_id, void *client_data, void *call_data) { *((long *) client_data) = *((long *) call_data); } /* radio_callback */ /*****/ static void toggle_callback(int item_id, void *client_data, void *call_data) { *((long *) client_data) = *((long *) call_data); } /* toggle_callback */ /*****/ static void scale_callback(int item_id, void *client_data, void *call_data) { *((double *) client_data) = (double) *((long *) call_data) / 1000.0; } /* scale_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 int do_dialog(void) { int dialog_row_id; int frames_column_id; int frames_row_id; int frame_id; int row_id; int frame2_id; int row2_id; int col_id; int temp_id; dialog_id = gimp_new_dialog("Page Curl"); dialog_row_id = gimp_new_row_group(dialog_id, DEFAULT, NORMAL, ""); frames_column_id = gimp_new_column_group(dialog_id, dialog_row_id, NORMAL, ""); frames_row_id = gimp_new_row_group(dialog_id, frames_column_id, NORMAL, ""); frame_id = gimp_new_frame(dialog_id, frames_row_id, "Curl settings"); row_id = gimp_new_row_group(dialog_id, frame_id, NORMAL, ""); /* Curl settings */ row2_id = gimp_new_row_group(dialog_id, row_id, RADIO, ""); temp_id = gimp_new_radio_button(dialog_id, row2_id, "Shade curl (no warp)"); gimp_change_item(dialog_id, temp_id, sizeof(do_curl_shade), &do_curl_shade); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_curl_shade); temp_id = gimp_new_radio_button(dialog_id, row2_id, "Warp curl"); gimp_change_item(dialog_id, temp_id, sizeof(do_curl_warp), &do_curl_warp); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_curl_warp); /* Curl shading color */ frame2_id = gimp_new_frame(dialog_id, row_id, "Curl shading color"); row2_id = gimp_new_row_group(dialog_id, frame2_id, RADIO, ""); temp_id = gimp_new_radio_button(dialog_id, row2_id, "Use foreground color"); gimp_change_item(dialog_id, temp_id, sizeof(do_curl_foreground), &do_curl_foreground); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_curl_foreground); temp_id = gimp_new_radio_button(dialog_id, row2_id, "Use background color"); gimp_change_item(dialog_id, temp_id, sizeof(do_curl_background), &do_curl_background); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_curl_background); /* Curl opacity */ temp_id = gimp_new_scale(dialog_id, row_id, 0, 1000, do_curl_opacity * 1000, 1); gimp_new_label(dialog_id, temp_id, "Curl opacity:"); gimp_add_callback(dialog_id, temp_id, scale_callback, &do_curl_opacity); /* Shading under curl */ temp_id = gimp_new_check_button(dialog_id, row_id, "Shade under curl"); gimp_change_item(dialog_id, temp_id, sizeof(do_shade_under), &do_shade_under); gimp_add_callback(dialog_id, temp_id, toggle_callback, &do_shade_under); /* Other side of dialog */ frames_row_id = gimp_new_row_group(dialog_id, frames_column_id, NORMAL, ""); /* Curl background settings */ frame_id = gimp_new_frame(dialog_id, frames_row_id, "Curl background settings"); row_id = gimp_new_row_group(dialog_id, frame_id, RADIO, ""); temp_id = gimp_new_radio_button(dialog_id, row_id, "Use foreground color"); gimp_change_item(dialog_id, temp_id, sizeof(do_background_foreground), &do_background_foreground); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_background_foreground); temp_id = gimp_new_radio_button(dialog_id, row_id, "Use background color"); gimp_change_item(dialog_id, temp_id, sizeof(do_background_background), &do_background_background); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_background_background); temp_id = gimp_new_radio_button(dialog_id, row_id, "Copy from original image"); gimp_change_item(dialog_id, temp_id, sizeof(do_background_original), &do_background_original); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_background_original); /* Curl location */ frame_id = gimp_new_frame(dialog_id, frames_row_id, "Curl location"); row_id = gimp_new_row_group(dialog_id, frame_id, RADIO, ""); temp_id = gimp_new_radio_button(dialog_id, row_id, "Upper left corner"); gimp_change_item(dialog_id, temp_id, sizeof(do_upper_left), &do_upper_left); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_upper_left); temp_id = gimp_new_radio_button(dialog_id, row_id, "Upper right corner"); gimp_change_item(dialog_id, temp_id, sizeof(do_upper_right), &do_upper_right); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_upper_right); temp_id = gimp_new_radio_button(dialog_id, row_id, "Lower left corner"); gimp_change_item(dialog_id, temp_id, sizeof(do_lower_left), &do_lower_left); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_lower_left); temp_id = gimp_new_radio_button(dialog_id, row_id, "Lower right corner"); gimp_change_item(dialog_id, temp_id, sizeof(do_lower_right), &do_lower_right); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_lower_right); /* Curl orientation */ frame_id = gimp_new_frame(dialog_id, dialog_row_id, "Curl orientation"); col_id = gimp_new_column_group(dialog_id, frame_id, RADIO, ""); temp_id = gimp_new_radio_button(dialog_id, col_id, "Vertical curl"); gimp_change_item(dialog_id, temp_id, sizeof(do_vertical), &do_vertical); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_vertical); temp_id = gimp_new_radio_button(dialog_id, col_id, "Horizontal curl"); gimp_change_item(dialog_id, temp_id, sizeof(do_horizontal), &do_horizontal); gimp_add_callback(dialog_id, temp_id, radio_callback, &do_horizontal); /* Buttons */ 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); /* Go! */ return gimp_show_dialog(dialog_id); } /* do_dialog */ /*****/ static void do_progress(void) { progress++; if (progress % 5 == 0) gimp_do_progress(progress, max_progress); } /* do_progress */ /*****/ static void v_set(vector_t *v, double x, double y) { v->x = x; v->y = y; } /* v_set */ /*****/ static void v_add(vector_t *v, vector_t a, vector_t b) { v->x = a.x + b.x; v->y = a.y + b.y; } /* v_add */ /*****/ static void v_sub(vector_t *v, vector_t a, vector_t b) { v->x = a.x - b.x; v->y = a.y - b.y; } /* v_sub */ /*****/ static double v_mag(vector_t v) { return sqrt(v.x * v.x + v.y * v.y); } /* v_mag */ /*****/ static double v_dot(vector_t a, vector_t b) { return a.x * b.x + a.y * b.y; } /* v_dot */ /*****/ static void init_calculation(void) { double k; double alpha, beta; double angle; vector_t v1, v2; /* Image parameters */ gimp_image_area(src_image, &sel_x1, &sel_y1, &sel_x2, &sel_y2); true_sel_width = sel_x2 - sel_x1; true_sel_height = sel_y2 - sel_y1; if (do_vertical) { sel_width = true_sel_width; sel_height = true_sel_height; } else { sel_width = true_sel_height; sel_height = true_sel_width; } /* else */ src_data = gimp_image_data(src_image); dest_data = gimp_image_data(dest_image); width = gimp_image_width(src_image); height = gimp_image_height(src_image); channels = gimp_image_channels(src_image); rowsiz = width * channels; progress = 0; max_progress = sel_height * 3; /* Circle parameters */ alpha = atan((double) sel_height / sel_width); beta = alpha / 2.0; k = sel_width / ((M_PI + alpha) * sin(beta) + cos(beta)); v_set(¢er, k * cos(beta), k * sin(beta)); radius = center.y; /* left_tangent */ v_set(&left_tangent, radius * -sin(alpha), radius * cos(alpha)); v_add(&left_tangent, left_tangent, center); /* right_tangent */ v_sub(&v1, left_tangent, center); v_set(&v2, sel_width - center.x, sel_height - center.y); angle = -2.0 * acos(v_dot(v1, v2) / (v_mag(v1) * v_mag(v2))); v_set(&right_tangent, v1.x * cos(angle) + v1.y * -sin(angle), v1.x * sin(angle) + v1.y * cos(angle)); v_add(&right_tangent, right_tangent, center); /* Slopes */ diagl_slope = (double) sel_width / sel_height; diagr_slope = (sel_width - right_tangent.x) / (sel_height - right_tangent.y); diagb_slope = (right_tangent.y - left_tangent.y) / (right_tangent.x - left_tangent.x); diagm_slope = (sel_width - center.x) / sel_height; /* Colors */ gimp_foreground_color(&fore_color[0], &fore_color[1], &fore_color[2]); gimp_background_color(&back_color[0], &back_color[1], &back_color[2]); } /* init_calculation */ /*****/ static int left_of_diagl(double x, double y) { return (x < (sel_width + (y - sel_height) * diagl_slope)); } /* left_of_diagl */ /*****/ static int right_of_diagr(double x, double y) { return (x > (sel_width + (y - sel_height) * diagr_slope)); } /* right_of_diagr */ /*****/ static int below_diagb(double x, double y) { return (y < (right_tangent.y + (x - right_tangent.x) * diagb_slope)); } /* below_diagb */ /*****/ static int right_of_diagm(double x, double y) { return (x > (sel_width + (y - sel_height) * diagm_slope)); } /* right_of_diagm */ /*****/ static int inside_circle(double x, double y) { double dx, dy; dx = x - center.x; dy = y - center.y; return (sqrt(dx * dx + dy * dy) <= radius); } /* inside_circle */ /*****/ static void put_pixel(uchar *image, int x, int y, uchar *p) { int temp; int k; uchar *destp; /* Flip horizontally if necessary */ if (do_upper_left || do_lower_left) x = (sel_width - 1) - x; /* Flip vertically if necessary */ if (do_upper_left || do_upper_right) y = (sel_height - 1) - y; /* Rotate if necessary */ if (do_horizontal) { y = (sel_height - 1) - y; temp = x; x = y; y = (true_sel_height - 1) - temp; if (do_upper_right || do_lower_left) { x = (true_sel_width - 1) - x; y = (true_sel_height - 1) - y; } /* if */ } /* if */ /* Transform to image coordinates */ x = sel_x1 + x; y = (sel_y2 - 1) - y; if (WITHIN(0, x, width - 1) && WITHIN(0, y, height - 1)) { destp = image + channels * (width * y + x); for (k = channels; k; k--) *destp++ = *p++; } /* if */ } /* put_pixel */ /*****/ static void get_pixel(uchar *image, int x, int y, uchar *p) { int temp; int k; uchar *srcp; /* Flip horizontally if necessary */ if (do_upper_left || do_lower_left) x = (sel_width - 1) - x; /* Flip vertically if necessary */ if (do_upper_left || do_upper_right) y = (sel_height - 1) - y; /* Rotate if necessary */ if (do_horizontal) { y = (sel_height - 1) - y; temp = x; x = y; y = (true_sel_height - 1) - temp; if (do_upper_right || do_lower_left) { x = (true_sel_width - 1) - x; y = (true_sel_height - 1) - y; } /* if */ } /* if */ /* Transform to image coordinates */ x = sel_x1 + x; y = (sel_y2 - 1) - y; if (WITHIN(0, x, width - 1) && WITHIN(0, y, height - 1)) { srcp = image + channels * (width * y + x); for (k = channels; k; k--) *p++ = *srcp++; } else p[0] = p[1] = p[2] = 0; } /* get_pixel */ /*****/ static void paint_background(void) { int x, y; uchar p[3]; int copy; for (y = sel_height - 1; y >= 0; y--) { for (x = 0; x < sel_width; x++) { copy = do_background_original || !(right_of_diagr(x, y) || (right_of_diagm(x, y) && below_diagb(x, y) && !inside_circle(x, y))); if (copy) { get_pixel(src_data, x, y, p); put_pixel(dest_data, x, y, p); } else { if (do_background_foreground) { p[0] = fore_color[0]; p[1] = fore_color[1]; p[2] = fore_color[2]; } else if (do_background_background) { p[0] = back_color[0]; p[1] = back_color[1]; p[2] = back_color[2]; } else get_pixel(src_data, x, y, p); put_pixel(dest_data, x, y, p); } /* else */ } /* for */ do_progress(); } /* for */ } /* paint_background */ /*****/ static void shade_under(void) { int x, y; double alpha; vector_t v, dl, dr; double dl_mag, dr_mag; double angle, factor; uchar p[3]; uchar *pp; int k; if (!do_shade_under) { progress += sel_height; gimp_do_progress(progress, max_progress); return; } /* if */ v_set(&dl, -sel_width, -sel_height); dl_mag = v_mag(dl); v_set(&dr, -(sel_width - right_tangent.x), -(sel_height - right_tangent.y)); dr_mag = v_mag(dr); alpha = acos(v_dot(dl, dr) / (dl_mag * dr_mag)); for (y = sel_height - 1; y >= 0; y--) { for (x = 0; x < sel_width; x++) if (!left_of_diagl(x, y) && !right_of_diagr(x, y) && !(right_of_diagm(x, y) && below_diagb(x, y) && !inside_circle(x, y))) { v.x = -(sel_width - x); v.y = -(sel_height - y); angle = acos(v_dot(v, dl) / (v_mag(v) * dl_mag)); factor = 1.0 - angle / alpha; get_pixel(src_data, x, y, p); pp = p; for (k = channels; k; k--) *pp++ *= factor; put_pixel(dest_data, x, y, p); } /* if */ do_progress(); } /* for */ } /* shade_under */ /*****/ static void shade_curl(void) { int x, y; uchar p[3], c[3]; vector_t v, dl, dr; double dl_mag, dr_mag; double alpha; double angle; double intensity; v_set(&dl, -sel_width, -sel_height); dl_mag = v_mag(dl); v_set(&dr, -(sel_width - right_tangent.x), -(sel_height - right_tangent.y)); dr_mag = v_mag(dr); alpha = acos(v_dot(dl, dr) / (dl_mag * dr_mag)); if (do_curl_foreground) { c[0] = fore_color[0]; c[1] = fore_color[1]; c[2] = fore_color[2]; } else { c[0] = back_color[0]; c[1] = back_color[1]; c[2] = back_color[2]; } /* else */ for (y = sel_height - 1; y >= 0; y--) { for (x = 0; x < sel_width; x++) if (!left_of_diagl(x, y) && !right_of_diagr(x, y) && !inside_circle(x, y) && !below_diagb(x, y)) { v.x = -(sel_width - x); v.y = -(sel_height - y); angle = acos(v_dot(v, dl) / (v_mag(v) * dl_mag)); intensity = pow(sin(M_PI * angle / alpha), 1.5); get_pixel(dest_data, x, y, p); p[0] = do_curl_opacity * (intensity * c[0]) + (1.0 - do_curl_opacity) * p[0]; p[1] = do_curl_opacity * (intensity * c[1]) + (1.0 - do_curl_opacity) * p[1]; p[2] = do_curl_opacity * (intensity * c[2]) + (1.0 - do_curl_opacity) * p[2]; put_pixel(dest_data, x, y, p); } /* if */ do_progress(); } /* for */ } /* shade_curl */ /*****/ static void page_curl(void) { gimp_init_progress("Page Curl"); init_calculation(); paint_background(); shade_under(); shade_curl(); } /* page_curl */