/* Page Curl 0.9 --- image filter plug-in for GIMP * Copyright (C) 1996 Federico Mena Quintero * Ported to Gimp 1.0 1998 by Simon Budig * * You can contact me at quartic@polloux.fciencias.unam.mx * You can contact the original 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 3 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, see . * */ /* * Ported to the 0.99.x architecture by Simon Budig, Simon.Budig@unix-ag.org */ /* * Version History * 0.5: (1996) Version for Gimp 0.54 by Federico Mena Quintero * 0.6: (Feb '98) First Version for Gimp 0.99.x, very buggy. * 0.8: (Mar '98) First "stable" version * 0.9: (May '98) * - Added support for Gradients. It is now possible to map * a gradient to the back of the curl. * - This implies a changed PDB-Interface: New "mode" parameter. * - Pagecurl now returns the ID of the new layer. * - Exchanged the meaning of FG/BG Color, because mostly the FG * color is darker. * 1.0: (July '04) * - Code cleanup, added reverse gradient option. */ #include "config.h" #include #include #include "libgimp/stdplugins-intl.h" #include "pagecurl-icons.c" #define PLUG_IN_PROC "plug-in-pagecurl" #define PLUG_IN_BINARY "pagecurl" #define PLUG_IN_ROLE "gimp-pagecurl" #define PLUG_IN_VERSION "July 2004, 1.0" #define NGRADSAMPLES 256 typedef enum { CURL_COLORS_FG_BG, CURL_COLORS_GRADIENT, CURL_COLORS_GRADIENT_REVERSE, CURL_COLORS_LAST = CURL_COLORS_GRADIENT_REVERSE } CurlColors; typedef enum { CURL_ORIENTATION_VERTICAL, CURL_ORIENTATION_HORIZONTAL, CURL_ORIENTATION_LAST = CURL_ORIENTATION_HORIZONTAL } CurlOrientation; typedef enum { CURL_EDGE_LOWER_RIGHT = 1, CURL_EDGE_LOWER_LEFT = 2, CURL_EDGE_UPPER_LEFT = 3, CURL_EDGE_UPPER_RIGHT = 4, CURL_EDGE_FIRST = CURL_EDGE_LOWER_RIGHT, CURL_EDGE_LAST = CURL_EDGE_UPPER_RIGHT } CurlEdge; #define CURL_EDGE_LEFT(e) ((e) == CURL_EDGE_LOWER_LEFT || \ (e) == CURL_EDGE_UPPER_LEFT) #define CURL_EDGE_RIGHT(e) ((e) == CURL_EDGE_LOWER_RIGHT || \ (e) == CURL_EDGE_UPPER_RIGHT) #define CURL_EDGE_LOWER(e) ((e) == CURL_EDGE_LOWER_RIGHT || \ (e) == CURL_EDGE_LOWER_LEFT) #define CURL_EDGE_UPPER(e) ((e) == CURL_EDGE_UPPER_LEFT || \ (e) == CURL_EDGE_UPPER_RIGHT) /***** Types *****/ typedef struct { CurlColors colors; gdouble opacity; gboolean shade; CurlEdge edge; CurlOrientation orientation; } CurlParams; /***** Prototypes *****/ static void query (void); static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); static void set_default_params (void); static void dialog_scale_update (GtkAdjustment *adjustment, gdouble *value); static gboolean dialog (void); static void init_calculation (gint32 drawable_id); static gint32 do_curl_effect (gint32 drawable_id); static void clear_curled_region (gint32 drawable_id); static gint32 page_curl (gint32 drawable_id); static GimpRGB * get_gradient_samples (gint32 drawable_id, gboolean reverse); /***** Variables *****/ const GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run /* run_proc */ }; static CurlParams curl; /* Image parameters */ static gint32 image_id; static GtkWidget *curl_image = NULL; static gint sel_x, sel_y; static gint true_sel_width, true_sel_height; static gint sel_width, sel_height; static gint drawable_position; /* Center and radius of circle */ static GimpVector2 center; static double radius; /* Useful points to keep around */ static GimpVector2 left_tangent; static GimpVector2 right_tangent; /* Slopes --- these are *not* in the usual geometric sense! */ static gdouble diagl_slope; static gdouble diagr_slope; static gdouble diagb_slope; static gdouble diagm_slope; /* User-configured parameters */ static GimpRGB fg_color; static GimpRGB bg_color; /***** Functions *****/ MAIN () static void query (void) { static const GimpParamDef args[] = { { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, { GIMP_PDB_IMAGE, "image", "Input image" }, { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, { GIMP_PDB_INT32, "colors", "FG- and BG-Color (0), Current gradient (1), Current gradient reversed (2)" }, { GIMP_PDB_INT32, "edge", "Edge to curl (1-4, clockwise, starting in the lower right edge)" }, { GIMP_PDB_INT32, "orientation", "Vertical (0), Horizontal (1)" }, { GIMP_PDB_INT32, "shade", "Shade the region under the curl (1) or not (0)" }, }; static const GimpParamDef return_vals[] = { { GIMP_PDB_LAYER, "curl-layer", "The new layer with the curl." } }; gimp_install_procedure (PLUG_IN_PROC, N_("Curl up one of the image corners"), "This plug-in creates a pagecurl-effect.", "Federico Mena Quintero and Simon Budig", "Federico Mena Quintero and Simon Budig", PLUG_IN_VERSION, N_("_Pagecurl..."), "RGB*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals), args, return_vals); gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Distorts"); } static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[2]; GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; gint32 drawable_id; run_mode = param[0].data.d_int32; INIT_I18N (); gegl_init (NULL, NULL); set_default_params (); /* Possibly retrieve data */ gimp_get_data (PLUG_IN_PROC, &curl); *nreturn_vals = 2; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; values[1].type = GIMP_PDB_LAYER; values[1].data.d_layer = -1; /* Get the specified drawable */ drawable_id = param[2].data.d_drawable; image_id = param[1].data.d_image; if ((gimp_drawable_is_rgb (drawable_id) || gimp_drawable_is_gray (drawable_id)) && gimp_drawable_mask_intersect (drawable_id, &sel_x, &sel_y, &true_sel_width, &true_sel_height)) { switch (run_mode) { case GIMP_RUN_INTERACTIVE: /* First acquire information with a dialog */ if (! dialog ()) return; break; case GIMP_RUN_NONINTERACTIVE: /* Make sure all the arguments are there! */ if (nparams != 7) status = GIMP_PDB_CALLING_ERROR; if (status == GIMP_PDB_SUCCESS) { curl.colors = CLAMP (param[3].data.d_int32, 0, CURL_COLORS_LAST); curl.edge = CLAMP (param[4].data.d_int32, CURL_EDGE_FIRST, CURL_EDGE_LAST); curl.orientation = CLAMP (param[5].data.d_int32, 0, CURL_ORIENTATION_LAST); curl.shade = param[6].data.d_int32 ? TRUE : FALSE; } break; case GIMP_RUN_WITH_LAST_VALS: break; default: break; } if (status == GIMP_PDB_SUCCESS) { values[1].data.d_layer = page_curl (drawable_id); if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush (); if (run_mode == GIMP_RUN_INTERACTIVE) gimp_set_data (PLUG_IN_PROC, &curl, sizeof (CurlParams)); } } else /* Sorry - no indexed/noalpha images */ status = GIMP_PDB_EXECUTION_ERROR; values[0].data.d_status = status; } /***************************************************** * Functions to locate the current point in the curl. * The following functions assume an curl in the * lower right corner. * diagb crosses the two tangential points from the * circle with diagl and diagr. * * +------------------------------------------------+ * | __-- /| * | __-- _/ | * | __-- / | * | __-- _/ | * | __-- / | * | __--____ _/ | * | __----~~ \ _/ | * | __-- | _/ | * | diagl __-- _/| _/diagr | * | __-- diagm_/ |/ | * | __-- /___/ | * +-------------------------+----------------------+ */ static inline gboolean left_of_diagl (gdouble x, gdouble y) { return (x < (sel_width + (y - sel_height) * diagl_slope)); } static inline gboolean right_of_diagr (gdouble x, gdouble y) { return (x > (sel_width + (y - sel_height) * diagr_slope)); } static inline gboolean below_diagb (gdouble x, gdouble y) { return (y < (right_tangent.y + (x - right_tangent.x) * diagb_slope)); } static inline gboolean right_of_diagm (gdouble x, gdouble y) { return (x > (sel_width + (y - sel_height) * diagm_slope)); } static inline gboolean inside_circle (gdouble x, gdouble y) { x -= center.x; y -= center.y; return x * x + y * y <= radius * radius; } static void set_default_params (void) { curl.colors = CURL_COLORS_FG_BG; curl.opacity = 1.0; curl.shade = TRUE; curl.edge = CURL_EDGE_LOWER_RIGHT; curl.orientation = CURL_ORIENTATION_VERTICAL; } /********************************************************************/ /* dialog callbacks */ /********************************************************************/ static void dialog_scale_update (GtkAdjustment *adjustment, gdouble *value) { *value = ((gdouble) gtk_adjustment_get_value (adjustment)) / 100.0; } static void curl_pixbuf_update (void) { GdkPixbuf *pixbuf; gint index; gchar *resource; switch (curl.edge) { case CURL_EDGE_LOWER_RIGHT: index = 0; break; case CURL_EDGE_LOWER_LEFT: index = 1; break; case CURL_EDGE_UPPER_RIGHT: index = 2; break; case CURL_EDGE_UPPER_LEFT: index = 3; break; default: return; } index += curl.orientation * 4; resource = g_strdup_printf ("/org/gimp/pagecurl-icons/curl%c.png", '0' + index); pixbuf = gdk_pixbuf_new_from_resource (resource, NULL); g_free (resource); gtk_image_set_from_pixbuf (GTK_IMAGE (curl_image), pixbuf); g_object_unref (pixbuf); } static gboolean dialog (void) { /* Missing options: Color-dialogs? / own curl layer ? / transparency to original drawable / Warp-curl (unsupported yet) */ GtkWidget *dialog; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *table; GtkWidget *frame; GtkWidget *button; GtkWidget *combo; GtkObject *adjustment; gboolean run; gimp_ui_init (PLUG_IN_BINARY, FALSE); dialog = gimp_dialog_new (_("Pagecurl Effect"), PLUG_IN_ROLE, NULL, 0, gimp_standard_help_func, PLUG_IN_PROC, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); gimp_window_set_transient (GTK_WINDOW (dialog)); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), vbox, TRUE, TRUE, 0); gtk_widget_show (vbox); frame = gimp_frame_new (_("Curl Location")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); table = gtk_table_new (3, 2, TRUE); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); curl_image = gtk_image_new (); gtk_table_attach (GTK_TABLE (table), curl_image, 0, 2, 1, 2, GTK_SHRINK, GTK_SHRINK, 0, 0); gtk_widget_show (curl_image); curl_pixbuf_update (); { static const gchar *name[] = { N_("Lower right"), N_("Lower left"), N_("Upper left"), N_("Upper right") }; gint i; button = NULL; for (i = CURL_EDGE_FIRST; i <= CURL_EDGE_LAST; i++) { button = gtk_radio_button_new_with_label (button == NULL ? NULL : gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)), gettext (name[i - CURL_EDGE_FIRST])); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), curl.edge == i); g_object_set_data (G_OBJECT (button), "gimp-item-data", GINT_TO_POINTER (i)); gtk_table_attach (GTK_TABLE (table), button, CURL_EDGE_LEFT (i) ? 0 : 1, CURL_EDGE_LEFT (i) ? 1 : 2, CURL_EDGE_UPPER (i) ? 0 : 2, CURL_EDGE_UPPER (i) ? 1 : 3, GTK_FILL | GTK_EXPAND, GTK_SHRINK, 0, 0); gtk_widget_show (button); g_signal_connect (button, "toggled", G_CALLBACK (gimp_radio_button_update), &curl.edge); g_signal_connect (button, "toggled", G_CALLBACK (curl_pixbuf_update), NULL); } } gtk_widget_show (table); gtk_widget_show (frame); frame = gimp_frame_new (_("Curl Orientation")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); gtk_container_add (GTK_CONTAINER (frame), hbox); { static const gchar *name[] = { N_("_Vertical"), N_("_Horizontal") }; gint i; button = NULL; for (i = 0; i <= CURL_ORIENTATION_LAST; i++) { button = gtk_radio_button_new_with_mnemonic (button == NULL ? NULL : gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)), gettext (name[i])); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), curl.orientation == i); g_object_set_data (G_OBJECT (button), "gimp-item-data", GINT_TO_POINTER (i)); gtk_box_pack_end (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_show (button); g_signal_connect (button, "toggled", G_CALLBACK (gimp_radio_button_update), &curl.orientation); g_signal_connect (button, "toggled", G_CALLBACK (curl_pixbuf_update), NULL); } } gtk_widget_show (hbox); gtk_widget_show (frame); button = gtk_check_button_new_with_mnemonic (_("_Shade under curl")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), curl.shade); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); gtk_widget_show (button); g_signal_connect (button, "toggled", G_CALLBACK (gimp_toggle_button_update), &curl.shade); combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo), GIMP_INT_STORE_VALUE, CURL_COLORS_GRADIENT_REVERSE, GIMP_INT_STORE_LABEL, _("Current gradient (reversed)"), GIMP_INT_STORE_ICON_NAME, GIMP_ICON_GRADIENT, -1); gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo), GIMP_INT_STORE_VALUE, CURL_COLORS_GRADIENT, GIMP_INT_STORE_LABEL, _("Current gradient"), GIMP_INT_STORE_ICON_NAME, GIMP_ICON_GRADIENT, -1); gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo), GIMP_INT_STORE_VALUE, CURL_COLORS_FG_BG, GIMP_INT_STORE_LABEL, _("Foreground / background colors"), GIMP_INT_STORE_ICON_NAME, GIMP_ICON_COLORS_DEFAULT, -1); gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0); gtk_widget_show (combo); gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), curl.colors, G_CALLBACK (gimp_int_combo_box_get_active), &curl.colors); gtk_widget_show (dialog); table = gtk_table_new (1, 3, FALSE); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); gtk_widget_show (table); adjustment = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("_Opacity:"), 100, 0, curl.opacity * 100.0, 0.0, 100.0, 1.0, 1.0, 0.0, TRUE, 0, 0, NULL, NULL); g_signal_connect (adjustment, "value-changed", G_CALLBACK (dialog_scale_update), &curl.opacity); run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); gtk_widget_destroy (dialog); return run; } static void init_calculation (gint32 drawable_id) { gdouble k; gdouble alpha, beta; gdouble angle; GimpVector2 v1, v2; gint32 *image_layers; gint32 nlayers; gimp_layer_add_alpha (drawable_id); /* Image parameters */ /* Determine Position of original Layer in the Layer stack. */ image_layers = gimp_image_get_layers (image_id, &nlayers); drawable_position = 0; while (drawable_position < nlayers && image_layers[drawable_position] != drawable_id) drawable_position++; switch (curl.orientation) { case CURL_ORIENTATION_VERTICAL: sel_width = true_sel_width; sel_height = true_sel_height; break; case CURL_ORIENTATION_HORIZONTAL: sel_width = true_sel_height; sel_height = true_sel_width; break; } /* Circle parameters */ alpha = atan ((double) sel_height / sel_width); beta = alpha / 2.0; k = sel_width / ((G_PI + alpha) * sin (beta) + cos (beta)); gimp_vector2_set (¢er, k * cos (beta), k * sin (beta)); radius = center.y; /* left_tangent */ gimp_vector2_set (&left_tangent, radius * -sin (alpha), radius * cos (alpha)); gimp_vector2_add (&left_tangent, &left_tangent, ¢er); /* right_tangent */ gimp_vector2_sub (&v1, &left_tangent, ¢er); gimp_vector2_set (&v2, sel_width - center.x, sel_height - center.y); angle = -2.0 * acos (gimp_vector2_inner_product (&v1, &v2) / (gimp_vector2_length (&v1) * gimp_vector2_length (&v2))); gimp_vector2_set (&right_tangent, v1.x * cos (angle) + v1.y * -sin (angle), v1.x * sin (angle) + v1.y * cos (angle)); gimp_vector2_add (&right_tangent, &right_tangent, ¢er); /* 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_context_get_foreground (&fg_color); gimp_context_get_background (&bg_color); } static gint32 do_curl_effect (gint32 drawable_id) { gint x = 0; gint y = 0; gboolean color_image; gint x1, y1; guint progress, max_progress; gdouble intensity, alpha; GimpVector2 v, dl, dr; gdouble dl_mag, dr_mag, angle, factor; GeglBuffer *curl_buffer; gint32 curl_layer_id; GimpRGB *grad_samples = NULL; gint width, height, n_ch; GeglRectangle *roi; GeglBufferIterator *iter; const Babl *format; color_image = gimp_drawable_is_rgb (drawable_id); curl_layer_id = gimp_layer_new (image_id, _("Curl Layer"), true_sel_width, true_sel_height, color_image ? GIMP_RGBA_IMAGE : GIMP_GRAYA_IMAGE, 100, gimp_image_get_default_new_layer_mode (image_id)); gimp_image_insert_layer (image_id, curl_layer_id, gimp_item_get_parent (drawable_id), drawable_position); gimp_drawable_fill (curl_layer_id, GIMP_FILL_TRANSPARENT); gimp_drawable_offsets (drawable_id, &x1, &y1); gimp_layer_set_offsets (curl_layer_id, sel_x + x1, sel_y + y1); curl_buffer = gimp_drawable_get_shadow_buffer (curl_layer_id); width = gegl_buffer_get_width (curl_buffer); height = gegl_buffer_get_height (curl_buffer); format = babl_format ("R'G'B'A float"); n_ch = babl_format_get_n_components (format); iter = gegl_buffer_iterator_new (curl_buffer, GEGL_RECTANGLE (0, 0, width, height), 0, format, GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1); /* Init shade_under */ gimp_vector2_set (&dl, -sel_width, -sel_height); dl_mag = gimp_vector2_length (&dl); gimp_vector2_set (&dr, -(sel_width - right_tangent.x), -(sel_height - right_tangent.y)); dr_mag = gimp_vector2_length (&dr); alpha = acos (gimp_vector2_inner_product (&dl, &dr) / (dl_mag * dr_mag)); /* Gradient Samples */ switch (curl.colors) { case CURL_COLORS_FG_BG: break; case CURL_COLORS_GRADIENT: grad_samples = get_gradient_samples (curl_layer_id, FALSE); break; case CURL_COLORS_GRADIENT_REVERSE: grad_samples = get_gradient_samples (curl_layer_id, TRUE); break; } max_progress = 2 * sel_width * sel_height; progress = 0; /* Main loop */ while (gegl_buffer_iterator_next (iter)) { gfloat *dest; roi = &iter->items[0].roi; dest = (gfloat *) iter->items[0].data; for (y1 = roi->y; y1 < roi->y + roi->height; y1++) { GimpRGB color; for (x1 = roi->x; x1 < roi->x + roi->width; x1++) { /* Map coordinates to get the curl correct... */ switch (curl.orientation) { case CURL_ORIENTATION_VERTICAL: x = CURL_EDGE_RIGHT (curl.edge) ? x1 : sel_width - 1 - x1; y = CURL_EDGE_UPPER (curl.edge) ? y1 : sel_height - 1 - y1; break; case CURL_ORIENTATION_HORIZONTAL: x = CURL_EDGE_LOWER (curl.edge) ? y1 : sel_width - 1 - y1; y = CURL_EDGE_LEFT (curl.edge) ? x1 : sel_height - 1 - x1; break; } if (left_of_diagl (x, y)) { /* uncurled region: transparent black */ gimp_rgba_set (&color, 0, 0, 0, 0); } else if (right_of_diagr (x, y) || (right_of_diagm (x, y) && below_diagb (x, y) && !inside_circle (x, y))) { /* curled region: transparent black */ gimp_rgba_set (&color, 0, 0, 0, 0); } else { v.x = -(sel_width - x); v.y = -(sel_height - y); angle = acos (gimp_vector2_inner_product (&v, &dl) / (gimp_vector2_length (&v) * dl_mag)); if (inside_circle (x, y) || below_diagb (x, y)) { /* Below the curl. */ factor = angle / alpha; gimp_rgba_set (&color, 0, 0, 0, curl.shade ? factor : 0); } else { GimpRGB *gradrgb; /* On the curl */ switch (curl.colors) { case CURL_COLORS_FG_BG: intensity = pow (sin (G_PI * angle / alpha), 1.5); gimp_rgba_set (&color, intensity * bg_color.r + (1.0 - intensity) * fg_color.r, intensity * bg_color.g + (1.0 - intensity) * fg_color.g, intensity * bg_color.b + (1.0 - intensity) * fg_color.b, (1.0 - intensity * (1.0 - curl.opacity))); break; case CURL_COLORS_GRADIENT: case CURL_COLORS_GRADIENT_REVERSE: /* Calculate position in Gradient */ intensity = (angle/alpha) + sin (G_PI*2 * angle/alpha) * 0.075; gradrgb = grad_samples + ((guint) (intensity * NGRADSAMPLES)); /* Check boundaries */ intensity = CLAMP (intensity, 0.0, 1.0); color = *gradrgb; color.a = gradrgb->a * (1.0 - intensity * (1.0 - curl.opacity)); break; } } } dest[0] = color.r; dest[1] = color.g; dest[2] = color.b; dest[3] = color.a; dest += n_ch; } } progress += roi->width * roi->height; gimp_progress_update ((double) progress / (double) max_progress); } gimp_progress_update (0.5); gegl_buffer_flush (curl_buffer); gimp_drawable_merge_shadow (curl_layer_id, FALSE); gimp_drawable_update (curl_layer_id, 0, 0, width, height); g_free (grad_samples); return curl_layer_id; } /************************************************/ static void clear_curled_region (gint32 drawable_id) { gint x = 0; gint y = 0; guint x1, y1; gfloat *dest, *src; guint progress, max_progress; GeglBuffer *buf; GeglBuffer *shadow_buf; GeglRectangle *roi; GeglBufferIterator *iter; const Babl *format; gint width, height, bpp, n_ch; gint buf_index; max_progress = 2 * sel_width * sel_height; progress = max_progress / 2; buf = gimp_drawable_get_buffer (drawable_id); shadow_buf = gimp_drawable_get_shadow_buffer (drawable_id); width = gegl_buffer_get_width (buf); height = gegl_buffer_get_height (buf); format = babl_format ("R'G'B'A float"); bpp = babl_format_get_bytes_per_pixel (format); n_ch = babl_format_get_n_components (format); iter = gegl_buffer_iterator_new (shadow_buf, GEGL_RECTANGLE (0, 0, width, height), 0, format, GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 2); buf_index = gegl_buffer_iterator_add (iter, buf, NULL, 0, format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE); while (gegl_buffer_iterator_next (iter)) { roi = &iter->items[0].roi; dest = iter->items[0].data; src = iter->items[buf_index].data; memcpy (dest, src, roi->width * roi->height * bpp); for (y1 = roi->y; y1 < roi->y + roi->height; y1++) { for (x1 = roi->x; x1 < roi->x + roi->width; x1++) { /* Map coordinates to get the curl correct... */ switch (curl.orientation) { case CURL_ORIENTATION_VERTICAL: x = (CURL_EDGE_RIGHT (curl.edge) ? x1 - sel_x : sel_width - 1 - (x1 - sel_x)); y = (CURL_EDGE_UPPER (curl.edge) ? y1 - sel_y : sel_height - 1 - (y1 - sel_y)); break; case CURL_ORIENTATION_HORIZONTAL: x = (CURL_EDGE_LOWER (curl.edge) ? y1 - sel_y : sel_width - 1 - (y1 - sel_y)); y = (CURL_EDGE_LEFT (curl.edge) ? x1 - sel_x : sel_height - 1 - (x1 - sel_x)); break; } if (right_of_diagr (x, y) || (right_of_diagm (x, y) && below_diagb (x, y) && !inside_circle (x, y))) { /* Right of the curl: Alpha = 0 */ dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2]; dest[3] = 0.0; } dest += n_ch; src += n_ch; } } progress += roi->width * roi->height; gimp_progress_update ((double) progress / (double) max_progress); } gimp_progress_update (1.0); gegl_buffer_flush (shadow_buf); gimp_drawable_merge_shadow (drawable_id, TRUE); gimp_drawable_update (drawable_id, sel_x, sel_y, true_sel_width, true_sel_height); } static gint32 page_curl (gint32 drawable_id) { gint curl_layer_id; gimp_image_undo_group_start (image_id); gimp_progress_init (_("Page Curl")); init_calculation (drawable_id); curl_layer_id = do_curl_effect (drawable_id); clear_curled_region (drawable_id); gimp_image_undo_group_end (image_id); return curl_layer_id; } /* Returns NGRADSAMPLES samples of active gradient. Each sample has (gimp_drawable_bpp (drawable_id)) bytes. "ripped" from gradmap.c. */ static GimpRGB * get_gradient_samples (gint32 drawable_id, gboolean reverse) { gchar *gradient_name; gint n_d_samples; gdouble *d_samples = NULL; GimpRGB *rgba; gint i; gradient_name = gimp_context_get_gradient (); gimp_gradient_get_uniform_samples (gradient_name, NGRADSAMPLES, reverse, &n_d_samples, &d_samples); rgba = g_new0 (GimpRGB, NGRADSAMPLES); for (i = 0; i < NGRADSAMPLES; i++) { gimp_rgba_set (rgba + i, d_samples[i*4 + 0], d_samples[i*4 + 1], d_samples[i*4 + 2], d_samples[i*4 + 3]); } g_free (gradient_name); return rgba; }