/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * GFlare plug-in -- lense flare effect by using custom gradients * Copyright (C) 1997 Eiichi Takamori * * 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 . * * * A fair proportion of this code was taken from GIMP & Script-fu * copyrighted by Spencer Kimball and Peter Mattis, and from Gradient * Editor copyrighted by Federico Mena Quintero. (See copyright notice * below) Thanks for senior GIMP hackers!! * * GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * Gradient editor module copyight (C) 1996-1997 Federico Mena Quintero * federico@nuclecu.unam.mx */ #include "config.h" #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include "libgimp/stdplugins-intl.h" /* #define DEBUG */ #ifdef DEBUG #define DEBUG_PRINT(X) g_print X #else #define DEBUG_PRINT(X) #endif #define LUMINOSITY(PIX) (GIMP_RGB_LUMINANCE (PIX[0], PIX[1], PIX[2]) + 0.5) #define RESPONSE_RESCAN 1 #define PLUG_IN_PROC "plug-in-gflare" #define PLUG_IN_BINARY "gradient-flare" #define PLUG_IN_ROLE "gimp-gradient-flare" #define GRADIENT_NAME_MAX 256 #define GRADIENT_RESOLUTION 360 #define GFLARE_NAME_MAX 256 #define GFLARE_FILE_HEADER "GIMP GFlare 0.25\n" #define SFLARE_NUM 30 #define DLG_PREVIEW_WIDTH 256 #define DLG_PREVIEW_HEIGHT 256 #define DLG_PREVIEW_MASK GDK_EXPOSURE_MASK | \ GDK_BUTTON_PRESS_MASK #define DLG_LISTBOX_WIDTH 80 #define DLG_LISTBOX_HEIGHT 40 #define ED_PREVIEW_WIDTH 256 #define ED_PREVIEW_HEIGHT 256 #define GM_PREVIEW_WIDTH 80 #define GM_PREVIEW_HEIGHT 16 #define SCALE_WIDTH 80 #ifndef OPAQUE #define OPAQUE 255 #endif #define GRAY50 128 #define GRADIENT_CACHE_SIZE 32 #define CALC_GLOW 0x01 #define CALC_RAYS 0x02 #define CALC_SFLARE 0x04 typedef struct _Preview Preview; typedef gchar GradientName[GRADIENT_NAME_MAX]; typedef enum { GF_NORMAL = 0, GF_ADDITION, GF_OVERLAY, GF_SCREEN, GF_NUM_MODES } GFlareMode; typedef enum { GF_CIRCLE = 0, GF_POLYGON, GF_NUM_SHAPES } GFlareShape; typedef struct { gchar *name; gchar *filename; gdouble glow_opacity; GFlareMode glow_mode; gdouble rays_opacity; GFlareMode rays_mode; gdouble sflare_opacity; GFlareMode sflare_mode; GradientName glow_radial; GradientName glow_angular; GradientName glow_angular_size; gdouble glow_size; gdouble glow_rotation; gdouble glow_hue; GradientName rays_radial; GradientName rays_angular; GradientName rays_angular_size; gdouble rays_size; gdouble rays_rotation; gdouble rays_hue; gint rays_nspikes; gdouble rays_thickness; GradientName sflare_radial; GradientName sflare_sizefac; GradientName sflare_probability; gdouble sflare_size; gdouble sflare_rotation; gdouble sflare_hue; GFlareShape sflare_shape; gint sflare_nverts; guint32 sflare_seed; gboolean random_seed; } GFlare; typedef struct { FILE *fp; gint error; } GFlareFile; typedef enum { PAGE_SETTINGS, PAGE_SELECTOR, PAGE_GENERAL, PAGE_GLOW, PAGE_RAYS, PAGE_SFLARE } PageNum; typedef struct { gint init; GFlare *gflare; GtkWidget *shell; Preview *preview; struct { gdouble x0, y0, x1, y1; } pwin; gboolean update_preview; GtkWidget *notebook; GtkWidget *sizeentry; GtkWidget *asupsample_frame; GtkListStore *selector_list; GtkTreeSelection *selection; gint init_params_done; } GFlareDialog; typedef void (* GFlareEditorCallback) (gint updated, gpointer data); typedef struct { gint init; gint run; GFlareEditorCallback callback; gpointer calldata; GFlare *target_gflare; GFlare *gflare; GtkWidget *shell; Preview *preview; GtkWidget *notebook; PageNum cur_page; GtkWidget *polygon_entry; GtkWidget *polygon_toggle; gint init_params_done; } GFlareEditor; typedef struct { gdouble x0; gdouble y0; gdouble x1; gdouble y1; } CalcBounds; typedef struct { gint init; gint type; GFlare *gflare; gdouble xcenter; gdouble ycenter; gdouble radius; gdouble rotation; gdouble hue; gdouble vangle; gdouble vlength; gint glow_opacity; CalcBounds glow_bounds; guchar *glow_radial; guchar *glow_angular; guchar *glow_angular_size; gdouble glow_radius; gdouble glow_rotation; gint rays_opacity; CalcBounds rays_bounds; guchar *rays_radial; guchar *rays_angular; guchar *rays_angular_size; gdouble rays_radius; gdouble rays_rotation; gdouble rays_spike_mod; gdouble rays_thinness; gint sflare_opacity; GList *sflare_list; guchar *sflare_radial; guchar *sflare_sizefac; guchar *sflare_probability; gdouble sflare_radius; gdouble sflare_rotation; GFlareShape sflare_shape; gdouble sflare_angle; gdouble sflare_factor; } CalcParams; /* * What's the difference between (structure) CalcParams and GFlare ? * well, radius and lengths are actual length for CalcParams where * they are typically 0 to 100 for GFlares, and angles are G_PI based * (radian) for CalcParams where they are degree for GFlares. et cetra. * This is because convenience for dialog processing and for calculating. * these conversion is taken place in calc init routines. see below. */ typedef struct { gdouble xcenter; gdouble ycenter; gdouble radius; CalcBounds bounds; } CalcSFlare; typedef struct { const Babl *format; gint bpp; gint is_color; gint has_alpha; gint x, y, w, h; /* mask bounds */ } DrawableInfo; typedef struct _GradientMenu GradientMenu; typedef void (* GradientMenuCallback) (const gchar *gradient_name, gpointer data); struct _GradientMenu { GtkWidget *preview; GtkWidget *combo; GradientMenuCallback callback; gpointer callback_data; GradientName gradient_name; }; typedef gint (* PreviewInitFunc) (Preview *preview, gpointer data); typedef void (* PreviewRenderFunc) (Preview *preview, guchar *buffer, gint y, gpointer data); typedef void (* PreviewDeinitFunc) (Preview *preview, gpointer data); struct _Preview { GtkWidget *widget; gint width; gint height; PreviewInitFunc init_func; gpointer init_data; PreviewRenderFunc render_func; gpointer render_data; PreviewDeinitFunc deinit_func; gpointer deinit_data; guint timeout_tag; guint idle_tag; gint init_done; gint current_y; gint drawn_y; guchar *buffer; guchar *full_image_buffer; }; typedef struct { gint tag; gint got_gradients; gint current_y; gint drawn_y; PreviewRenderFunc render_func; guchar *buffer; } PreviewIdle; typedef struct _GradientCacheItem GradientCacheItem; struct _GradientCacheItem { GradientCacheItem *next; GradientCacheItem *prev; GradientName name; guchar values[4 * GRADIENT_RESOLUTION]; }; typedef struct { gint xcenter; gint ycenter; gdouble radius; gdouble rotation; gdouble hue; gdouble vangle; gdouble vlength; gint use_asupsample; gint asupsample_max_depth; gdouble asupsample_threshold; gchar gflare_name[GFLARE_NAME_MAX]; } PluginValues; typedef void (* QueryFunc) (GtkWidget *, gpointer, gpointer); /*** *** Global Functions Prototypes **/ static void plugin_query (void); static void plugin_run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); static GFlare * gflare_new_with_default (const gchar *new_name); static GFlare * gflare_dup (const GFlare *src, const gchar *new_name); static void gflare_copy (GFlare *dest, const GFlare *src); static GFlare * gflare_load (const gchar *filename, const gchar *name); static void gflare_save (GFlare *gflare); static void gflare_name_copy (gchar *dest, const gchar *src); static gint gflares_list_insert (GFlare *gflare); static GFlare * gflares_list_lookup (const gchar *name); static gint gflares_list_index (GFlare *gflare); static gint gflares_list_remove (GFlare *gflare); static void gflares_list_load_all (void); static void gflares_list_free_all (void); static void calc_init_params (GFlare *gflare, gint calc_type, gdouble xcenter, gdouble ycenter, gdouble radius, gdouble rotation, gdouble hue, gdouble vangle, gdouble vlength); static gint calc_init_progress (void); static void calc_deinit (void); static void calc_glow_pix (guchar *dest_pix, gdouble x, gdouble y); static void calc_rays_pix (guchar *dest_pix, gdouble x, gdouble y); static void calc_sflare_pix (guchar *dest_pix, gdouble x, gdouble y, guchar *src_pix); static void calc_gflare_pix (guchar *dest_pix, gdouble x, gdouble y, guchar *src_pix); static gboolean dlg_run (void); static void dlg_preview_calc_window (void); static void ed_preview_calc_window (void); static GtkWidget * ed_mode_menu_new (GFlareMode *mode_var); static Preview * preview_new (gint width, gint height, PreviewInitFunc init_func, gpointer init_data, PreviewRenderFunc render_func, gpointer render_data, PreviewDeinitFunc deinit_func, gpointer deinit_data); static void preview_free (Preview *preview); static void preview_render_start (Preview *preview); static void preview_render_end (Preview *preview); static void preview_rgba_to_rgb (guchar *dest, gint x, gint y, guchar *src); static void gradient_menu_init (void); static void gradient_menu_rescan (void); static GradientMenu * gradient_menu_new (GradientMenuCallback callback, gpointer callback_data, const gchar *default_gradient_name); static void gradient_name_copy (gchar *dest, const gchar *src); static void gradient_name_encode (gchar *dest, const gchar *src); static void gradient_name_decode (gchar *dest, const gchar *src); static void gradient_init (void); static void gradient_free (void); static gchar ** gradient_get_list (gint *num_gradients); static void gradient_get_values (const gchar *gradient_name, guchar *values, gint nvalues); static void gradient_cache_flush (void); /* *** INSERT-FILE-END *** */ /** *** Variables **/ const GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ plugin_query, /* query_proc */ plugin_run, /* run_proc */ }; PluginValues pvals = { 128, /* xcenter */ 128, /* ycenter */ 100.0, /* radius */ 0.0, /* rotation */ 0.0, /* hue */ 60.0, /* vangle */ 400.0, /* vlength */ FALSE, /* use_asupsample */ 3, /* asupsample_max_depth */ 0.2, /* asupsample_threshold */ "Default" /* gflare_name */ }; GFlare default_gflare = { NULL, /* name */ NULL, /* filename */ 100, /* glow_opacity */ GF_NORMAL, /* glow_mode */ 100, /* rays_opacity */ GF_NORMAL, /* rays_mode */ 100, /* sflare_opacity */ GF_NORMAL, /* sflare_mode */ "%red_grad", /* glow_radial */ "%white", /* glow_angular */ "%white", /* glow_angular_size */ 100.0, /* glow_size */ 0.0, /* glow_rotation */ 0.0, /* glow_hue */ "%white_grad",/* rays_radial */ "%random", /* rays_angular */ "%random", /* rays_angular_size */ 100.0, /* rays_size */ 0.0, /* rays_rotation */ 0.0, /* rays_hue */ 40, /* rays_nspikes */ 20.0, /* rays_thickness */ "%white_grad",/* sflare_radial */ "%random", /* sflare_sizefac */ "%random", /* sflare_probability */ 40.0, /* sflare_size */ 0.0, /* sflare_rotation */ 0.0, /* sflare_hue */ GF_CIRCLE, /* sflare_shape */ 6, /* sflare_nverts */ 0, /* sflare_seed */ TRUE, /* random_seed */ }; /* These are keywords to be written to disk files specifying flares. */ /* They are not translated since we want gflare files to be compatible across languages. */ static const gchar *gflare_modes[] = { "NORMAL", "ADDITION", "OVERLAY", "SCREEN" }; static const gchar *gflare_shapes[] = { "CIRCLE", "POLYGON" }; /* These are for menu entries, so they are translated. */ static const gchar *gflare_menu_modes[] = { N_("Normal"), N_("Addition"), N_("Overlay"), N_("Screen") }; static gint32 image_ID; static gint32 drawable_ID; static DrawableInfo dinfo; static GFlareDialog *dlg = NULL; static GFlareEditor *ed = NULL; static GList *gflares_list = NULL; static gint num_gflares = 0; static gchar *gflare_path = NULL; static CalcParams calc; static GList *gradient_menus; static gchar **gradient_names = NULL; static gint num_gradient_names = 0; static GradientCacheItem *gradient_cache_head = NULL; static gint gradient_cache_count = 0; static const gchar *internal_gradients[] = { "%white", "%white_grad", "%red_grad", "%blue_grad", "%yellow_grad", "%random" }; #ifdef DEBUG static gint get_values_external_count = 0; static clock_t get_values_external_clock = 0; #endif /** *** +++ Static Functions Prototypes **/ static void plugin_do (void); static void plugin_do_non_asupsample (GeglBuffer *src_buffer, GeglBuffer *dest_buffer); static void plugin_do_asupsample (GeglBuffer *src_buffer, GeglBuffer *dest_buffer); static void plugin_render_func (gdouble x, gdouble y, GimpRGB *color, gpointer data); static void plugin_put_pixel_func (gint ix, gint iy, GimpRGB *color, gpointer data); static void plugin_progress_func (gint y1, gint y2, gint curr_y, gpointer data); static GFlare * gflare_new (void); static void gflare_free (GFlare *gflare); static void gflare_read_int (gint *intvar, GFlareFile *gf); static void gflare_read_double (gdouble *dblvar, GFlareFile *gf); static void gflare_read_gradient_name (gchar *name, GFlareFile *gf); static void gflare_read_shape (GFlareShape *shape, GFlareFile *gf); static void gflare_read_mode (GFlareMode *mode, GFlareFile *gf); static void gflare_write_gradient_name (gchar *name, FILE *fp); static gint calc_sample_one_gradient (void); static void calc_place_sflare (void); static void calc_get_gradient (guchar *pix, guchar *gradient, gdouble pos); static gdouble fmod_positive (gdouble x, gdouble m); static void calc_paint_func (guchar *dest, guchar *src1, guchar *src2, gint opacity, GFlareMode mode); static void calc_combine (guchar *dest, guchar *src1, guchar *src2, gint opacity); static void calc_addition (guchar *dest, guchar *src1, guchar *src2); static void calc_screen (guchar *dest, guchar *src1, guchar *src2); static void calc_overlay (guchar *dest, guchar *src1, guchar *src2); static void dlg_setup_gflare (void); static void dlg_preview_realize (GtkWidget *widget); static gboolean dlg_preview_handle_event (GtkWidget *widget, GdkEvent *event); static void dlg_preview_update (void); static gint dlg_preview_init_func (Preview *preview, gpointer data); static void dlg_preview_render_func (Preview *preview, guchar *dest, gint y, gpointer data); static void dlg_preview_deinit_func (Preview *preview, gpointer data); static void dlg_make_page_settings (GFlareDialog *dlg, GtkWidget *notebook); static void dlg_position_entry_callback (GtkWidget *widget, gpointer data); static void dlg_update_preview_callback (GtkWidget *widget, gpointer data); static void dlg_make_page_selector (GFlareDialog *dlg, GtkWidget *notebook); static void dlg_selector_setup_listbox (void); static void dlg_selector_list_item_callback (GtkTreeSelection *selection); static void dlg_selector_new_callback (GtkWidget *widget, gpointer data); static void dlg_selector_new_ok_callback (GtkWidget *widget, const gchar *new_name, gpointer data); static void dlg_selector_edit_callback (GtkWidget *widget, gpointer data); static void dlg_selector_edit_done_callback (gint updated, gpointer data); static void dlg_selector_copy_callback (GtkWidget *widget, gpointer data); static void dlg_selector_copy_ok_callback (GtkWidget *widget, const gchar *copy_name, gpointer data); static void dlg_selector_delete_callback (GtkWidget *widget, gpointer data); static void dlg_selector_do_delete_callback (GtkWidget *widget, gboolean delete, gpointer data); static void ed_run (GtkWindow *parent, GFlare *target_gflare, GFlareEditorCallback callback, gpointer calldata); static void ed_destroy_callback (GtkWidget *widget, GFlareEditor *ed); static void ed_response (GtkWidget *widget, gint response_id, GFlareEditor *ed); static void ed_make_page_general (GFlareEditor *ed, GtkWidget *notebook); static void ed_make_page_glow (GFlareEditor *ed, GtkWidget *notebook); static void ed_make_page_rays (GFlareEditor *ed, GtkWidget *notebook); static void ed_make_page_sflare (GFlareEditor *ed, GtkWidget *notebook); static void ed_put_gradient_menu (GtkWidget *table, gint x, gint y, const gchar *caption, GradientMenu *gm); static void ed_mode_menu_callback (GtkWidget *widget, gpointer data); static void ed_gradient_menu_callback (const gchar *gradient_name, gpointer data); static void ed_shape_radio_callback (GtkWidget *widget, gpointer data); static void ed_ientry_callback (GtkWidget *widget, gpointer data); static void ed_page_map_callback (GtkWidget *widget, gpointer data); static void ed_preview_update (void); static gint ed_preview_init_func (Preview *preview, gpointer data); static void ed_preview_deinit_func (Preview *preview, gpointer data); static void ed_preview_render_func (Preview *preview, guchar *buffer, gint y, gpointer data); static void ed_preview_render_general (guchar *buffer, gint y); static void ed_preview_render_glow (guchar *buffer, gint y); static void ed_preview_render_rays (guchar *buffer, gint y); static void ed_preview_render_sflare (guchar *buffer, gint y); static gint preview_render_start_2 (Preview *preview); static gint preview_handle_idle (Preview *preview); static void gm_gradient_get_list (void); static void gm_gradient_combo_fill (GradientMenu *gm, const gchar *default_gradient); static void gm_gradient_combo_callback (GtkWidget *widget, gpointer data); static void gm_preview_draw (GtkWidget *preview, const gchar *gradient_name); static void gm_combo_destroy_callback (GtkWidget *widget, gpointer data); static void gradient_get_values_internal (const gchar *gradient_name, guchar *values, gint nvalues); static void gradient_get_blend (const guchar *fg, const guchar *bg, guchar *values, gint nvalues); static void gradient_get_random (guchar *values, gint nvalues); static void gradient_get_default (const gchar *name, guchar *values, gint nvalues); static void gradient_get_values_external (const gchar *gradient_name, guchar *values, gint nvalues); static void gradient_get_values_real_external (const gchar *gradient_name, guchar *values, gint nvalues, gboolean reverse); static GradientCacheItem *gradient_cache_lookup (const gchar *name, gboolean *found); static void gradient_cache_zorch (void); /* *** INSERT-FILE-END *** */ /*************************************************************************/ /** **/ /** +++ Plug-in Interfaces **/ /** **/ /*************************************************************************/ MAIN () void plugin_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 (unused)" }, { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, { GIMP_PDB_STRING, "gflare-name", "The name of GFlare" }, { GIMP_PDB_INT32, "xcenter", "X coordinate of center of GFlare" }, { GIMP_PDB_INT32, "ycenter", "Y coordinate of center of GFlare" }, { GIMP_PDB_FLOAT, "radius", "Radius of GFlare (pixel)" }, { GIMP_PDB_FLOAT, "rotation", "Rotation of GFlare (degree)" }, { GIMP_PDB_FLOAT, "hue", "Hue rotation of GFlare (degree)" }, { GIMP_PDB_FLOAT, "vangle", "Vector angle for second flares (degree)" }, { GIMP_PDB_FLOAT, "vlength", "Vector length for second flares (percentage to Radius)" }, { GIMP_PDB_INT32, "use-asupsample", "Whether it uses or not adaptive supersampling while rendering (boolean)" }, { GIMP_PDB_INT32, "asupsample-max-depth", "Max depth for adaptive supersampling"}, { GIMP_PDB_FLOAT, "asupsample-threshold", "Threshold for adaptive supersampling"} }; const gchar *help_string = "This plug-in produces a lense flare effect using custom gradients. " "In interactive call, the user can edit his/her own favorite lense flare " "(GFlare) and render it. Edited gflare is saved automatically to " "the folder in gflare-path, if it is defined in gimprc. " "In non-interactive call, the user can only render one of GFlare " "which has been stored in gflare-path already."; gimp_install_procedure (PLUG_IN_PROC, N_("Produce a lense flare effect using gradients"), help_string, "Eiichi Takamori", "Eiichi Takamori, and a lot of GIMP people", "1997", N_("_Gradient Flare..."), "RGB*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), 0, args, NULL); gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Light and Shadow/Light"); } void plugin_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; gchar *path; INIT_I18N (); gegl_init (NULL, NULL); *nreturn_vals = 1; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; run_mode = param[0].data.d_int32; image_ID = param[1].data.d_image; drawable_ID = param[2].data.d_drawable; dinfo.is_color = gimp_drawable_is_rgb (drawable_ID); dinfo.has_alpha = gimp_drawable_has_alpha (drawable_ID); if (dinfo.is_color) { if (dinfo.has_alpha) dinfo.format = babl_format ("R'G'B'A u8"); else dinfo.format = babl_format ("R'G'B' u8"); } else { if (dinfo.has_alpha) dinfo.format = babl_format ("Y'A u8"); else dinfo.format = babl_format ("Y u8"); } dinfo.bpp = babl_format_get_bytes_per_pixel (dinfo.format); if (! gimp_drawable_mask_intersect (drawable_ID, &dinfo.x, &dinfo.y, &dinfo.w, &dinfo.h)) return; /* * Start gradient caching */ gradient_init (); /* * Parse gflare path from gimprc and load gflares */ path = gimp_gimprc_query ("gflare-path"); if (path) { gflare_path = g_filename_from_utf8 (path, -1, NULL, NULL, NULL); g_free (path); } else { gchar *gimprc = gimp_personal_rc_file ("gimprc"); gchar *full_path = gimp_config_build_data_path ("gflare"); gchar *esc_path = g_strescape (full_path, NULL); g_free (full_path); g_message (_("No %s in gimprc:\n" "You need to add an entry like\n" "(%s \"%s\")\n" "to your %s file."), "gflare-path", "gflare-path", esc_path, gimp_filename_to_utf8 (gimprc)); g_free (gimprc); g_free (esc_path); } gflares_list_load_all (); switch (run_mode) { case GIMP_RUN_INTERACTIVE: /* Possibly retrieve data */ gimp_get_data (PLUG_IN_PROC, &pvals); /* First acquire information with a dialog */ if (! dlg_run ()) return; break; case GIMP_RUN_NONINTERACTIVE: if (nparams != 14) { status = GIMP_PDB_CALLING_ERROR; } else { gflare_name_copy (pvals.gflare_name, param[3].data.d_string); pvals.xcenter = param[4].data.d_int32; pvals.ycenter = param[5].data.d_int32; pvals.radius = param[6].data.d_float; pvals.rotation = param[7].data.d_float; pvals.hue = param[8].data.d_float; pvals.vangle = param[9].data.d_float; pvals.vlength = param[10].data.d_float; pvals.use_asupsample = param[11].data.d_int32; pvals.asupsample_max_depth = param[12].data.d_int32; pvals.asupsample_threshold = param[13].data.d_float; if (pvals.radius <= 0) status = GIMP_PDB_CALLING_ERROR; } break; case GIMP_RUN_WITH_LAST_VALS: /* Possibly retrieve data */ gimp_get_data (PLUG_IN_PROC, &pvals); break; default: break; } if (status == GIMP_PDB_SUCCESS) { /* Make sure that the drawable is gray or RGB color */ if (gimp_drawable_is_rgb (drawable_ID) || gimp_drawable_is_gray (drawable_ID)) { gimp_progress_init (_("Gradient Flare")); plugin_do (); if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush (); /* Store data */ if (run_mode == GIMP_RUN_INTERACTIVE) gimp_set_data (PLUG_IN_PROC, &pvals, sizeof (PluginValues)); } else { status = GIMP_PDB_EXECUTION_ERROR; *nreturn_vals = 2; values[1].type = GIMP_PDB_STRING; values[1].data.d_string = _("Cannot operate on indexed color images."); } } values[0].data.d_status = status; /* * Deinitialization */ gradient_free (); } static void plugin_do (void) { GeglBuffer *src_buffer; GeglBuffer *dest_buffer; GFlare *gflare; gflare = gflares_list_lookup (pvals.gflare_name); if (gflare == NULL) { /* FIXME */ g_warning ("Not found %s\n", pvals.gflare_name); return; } /* Initialize calc params and gradients */ calc_init_params (gflare, CALC_GLOW | CALC_RAYS | CALC_SFLARE, pvals.xcenter, pvals.ycenter, pvals.radius, pvals.rotation, pvals.hue, pvals.vangle, pvals.vlength); while (calc_init_progress ()) ; src_buffer = gimp_drawable_get_buffer (drawable_ID); dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); /* Render it ! */ if (pvals.use_asupsample) plugin_do_asupsample (src_buffer, dest_buffer); else plugin_do_non_asupsample (src_buffer, dest_buffer); g_object_unref (src_buffer); g_object_unref (dest_buffer); gimp_progress_update (1.0); /* Clean up */ calc_deinit (); gimp_drawable_merge_shadow (drawable_ID, TRUE); gimp_drawable_update (drawable_ID, dinfo.x, dinfo.y, dinfo.w, dinfo.h); } /* these routines should be almost rewritten anyway */ static void plugin_do_non_asupsample (GeglBuffer *src_buffer, GeglBuffer *dest_buffer) { GeglBufferIterator *iter; gint progress; gint max_progress; progress = 0; max_progress = dinfo.w * dinfo.h; iter = gegl_buffer_iterator_new (src_buffer, GEGL_RECTANGLE (dinfo.x, dinfo.y, dinfo.w, dinfo.h), 0, dinfo.format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2); gegl_buffer_iterator_add (iter, dest_buffer, GEGL_RECTANGLE (dinfo.x, dinfo.y, dinfo.w, dinfo.h), 0, dinfo.format, GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE); while (gegl_buffer_iterator_next (iter)) { const guchar *src_row = iter->items[0].data; guchar *dest_row = iter->items[1].data; gint row, y; for (row = 0, y = iter->items[0].roi.y; row < iter->items[0].roi.height; row++, y++) { const guchar *src = src_row; guchar *dest = dest_row; gint col, x; for (col = 0, x = iter->items[0].roi.x; col < iter->items[0].roi.width; col++, x++) { guchar src_pix[4]; guchar dest_pix[4]; gint b; for (b = 0; b < 3; b++) src_pix[b] = dinfo.is_color ? src[b] : src[0]; src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE; calc_gflare_pix (dest_pix, x, y, src_pix); if (dinfo.is_color) { for (b = 0; b < 3; b++) dest[b] = dest_pix[b]; } else { dest[0] = LUMINOSITY (dest_pix); } if (dinfo.has_alpha) dest[dinfo.bpp - 1] = dest_pix[3]; src += dinfo.bpp; dest += dinfo.bpp; } src_row += dinfo.bpp * iter->items[0].roi.width; dest_row += dinfo.bpp * iter->items[1].roi.width; } /* Update progress */ progress += iter->items[0].roi.width * iter->items[0].roi.height; gimp_progress_update ((double) progress / (double) max_progress); } } static void plugin_do_asupsample (GeglBuffer *src_buffer, GeglBuffer *dest_buffer) { gimp_adaptive_supersample_area (dinfo.x, dinfo.y, dinfo.x + dinfo.w - 1, dinfo.y + dinfo.h - 1, pvals.asupsample_max_depth, pvals.asupsample_threshold, plugin_render_func, src_buffer, plugin_put_pixel_func, dest_buffer, plugin_progress_func, NULL); } /* Adaptive supersampling callback functions These routines may look messy, since adaptive supersampling needs pixel values in `double' (from 0.0 to 1.0) but calc_*_pix () returns guchar values. */ static void plugin_render_func (gdouble x, gdouble y, GimpRGB *color, gpointer data) { GeglBuffer *src_buffer = data; guchar src_pix[4]; guchar flare_pix[4]; guchar src[4]; gint b; gint ix, iy; /* translate (0.5, 0.5) before convert to `int' so that it can surely point the center of pixel */ ix = floor (x + 0.5); iy = floor (y + 0.5); gegl_buffer_sample (src_buffer, ix, iy, NULL, src, dinfo.format, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); for (b = 0; b < 3; b++) src_pix[b] = dinfo.is_color ? src[b] : src[0]; src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE; calc_gflare_pix (flare_pix, x, y, src_pix); color->r = flare_pix[0] / 255.0; color->g = flare_pix[1] / 255.0; color->b = flare_pix[2] / 255.0; color->a = flare_pix[3] / 255.0; } static void plugin_put_pixel_func (gint ix, gint iy, GimpRGB *color, gpointer data) { GeglBuffer *dest_buffer = data; guchar dest[4]; if (dinfo.is_color) { dest[0] = color->r * 255; dest[1] = color->g * 255; dest[2] = color->b * 255; } else { dest[0] = gimp_rgb_luminance_uchar (color); } if (dinfo.has_alpha) dest[dinfo.bpp - 1] = color->a * 255; gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (ix, iy, 1, 1), 0, dinfo.format, dest, GEGL_AUTO_ROWSTRIDE); } static void plugin_progress_func (gint y1, gint y2, gint curr_y, gpointer data) { gimp_progress_update ((double) curr_y / (double) (y2 - y1)); } /*************************************************************************/ /** **/ /** +++ GFlare Routines **/ /** **/ /*************************************************************************/ /* * These code are more or less based on Quartic's gradient.c, * other gimp sources, and script-fu. */ static GFlare * gflare_new (void) { GFlare *gflare = g_new0 (GFlare, 1); gflare->name = NULL; gflare->filename = NULL; return gflare; } static GFlare * gflare_new_with_default (const gchar *new_name) { return gflare_dup (&default_gflare, new_name); } static GFlare * gflare_dup (const GFlare *src, const gchar *new_name) { GFlare *dest = g_new0 (GFlare, 1); *dest = *src; dest->name = g_strdup (new_name); dest->filename = NULL; return dest; } static void gflare_copy (GFlare *dest, const GFlare *src) { gchar *name, *filename; name = dest->name; filename = dest->filename; *dest = *src; dest->name = name; dest->filename =filename; } static void gflare_free (GFlare *gflare) { g_return_if_fail (gflare != NULL); g_free (gflare->name); g_free (gflare->filename); g_free (gflare); } GFlare * gflare_load (const gchar *filename, const gchar *name) { FILE *fp; GFlareFile *gf; GFlare *gflare; gchar header[256]; g_return_val_if_fail (filename != NULL, NULL); fp = g_fopen (filename, "rb"); if (!fp) { g_message (_("Failed to open GFlare file '%s': %s"), gimp_filename_to_utf8 (filename), g_strerror (errno)); return NULL; } if (fgets (header, sizeof(header), fp) == NULL || strcmp (header, GFLARE_FILE_HEADER) != 0) { g_warning (_("'%s' is not a valid GFlare file."), gimp_filename_to_utf8 (filename)); fclose (fp); return NULL; } gf = g_new (GFlareFile, 1); gf->fp = fp; gf->error = FALSE; gflare = gflare_new (); gflare->name = g_strdup (name); gflare->filename = g_strdup (filename); gflare_read_double (&gflare->glow_opacity, gf); gflare_read_mode (&gflare->glow_mode, gf); gflare_read_double (&gflare->rays_opacity, gf); gflare_read_mode (&gflare->rays_mode, gf); gflare_read_double (&gflare->sflare_opacity, gf); gflare_read_mode (&gflare->sflare_mode, gf); gflare_read_gradient_name (gflare->glow_radial, gf); gflare_read_gradient_name (gflare->glow_angular, gf); gflare_read_gradient_name (gflare->glow_angular_size, gf); gflare_read_double (&gflare->glow_size, gf); gflare_read_double (&gflare->glow_rotation, gf); gflare_read_double (&gflare->glow_hue, gf); gflare_read_gradient_name (gflare->rays_radial, gf); gflare_read_gradient_name (gflare->rays_angular, gf); gflare_read_gradient_name (gflare->rays_angular_size, gf); gflare_read_double (&gflare->rays_size, gf); gflare_read_double (&gflare->rays_rotation, gf); gflare_read_double (&gflare->rays_hue, gf); gflare_read_int (&gflare->rays_nspikes, gf); gflare_read_double (&gflare->rays_thickness, gf); gflare_read_gradient_name (gflare->sflare_radial, gf); gflare_read_gradient_name (gflare->sflare_sizefac, gf); gflare_read_gradient_name (gflare->sflare_probability, gf); gflare_read_double (&gflare->sflare_size, gf); gflare_read_double (&gflare->sflare_hue, gf); gflare_read_double (&gflare->sflare_rotation, gf); gflare_read_shape (&gflare->sflare_shape, gf); gflare_read_int (&gflare->sflare_nverts, gf); gflare_read_int ((gint *) &gflare->sflare_seed, gf); if (gflare->sflare_seed == 0) gflare->sflare_seed = g_random_int(); fclose (gf->fp); if (gf->error) { g_warning (_("invalid formatted GFlare file: %s\n"), filename); g_free (gflare); g_free (gf); return NULL; } g_free (gf); return gflare; } static void gflare_read_int (gint *intvar, GFlareFile *gf) { if (gf->error) return; if (fscanf (gf->fp, "%d", intvar) != 1) gf->error = TRUE; } static void gflare_read_double (gdouble *dblvar, GFlareFile *gf) { gchar buf[31]; if (gf->error) return; if (fscanf (gf->fp, "%30s", buf) == 1) *dblvar = g_ascii_strtod (buf, NULL); else gf->error = TRUE; } static void gflare_read_gradient_name (GradientName name, GFlareFile *gf) { gchar tmp[1024], dec[1024]; if (gf->error) return; /* FIXME: this is buggy */ if (fscanf (gf->fp, "%1023s", tmp) == 1) { /* @GRADIENT_NAME */ gradient_name_decode (dec, tmp); gradient_name_copy (name, dec); } else gf->error = TRUE; } static void gflare_read_shape (GFlareShape *shape, GFlareFile *gf) { gchar tmp[1024]; gint i; if (gf->error) return; if (fscanf (gf->fp, "%1023s", tmp) == 1) { for (i = 0; i < GF_NUM_SHAPES; i++) if (strcmp (tmp, gflare_shapes[i]) == 0) { *shape = i; return; } } gf->error = TRUE; } static void gflare_read_mode (GFlareMode *mode, GFlareFile *gf) { gchar tmp[1024]; gint i; if (gf->error) return; if (fscanf (gf->fp, "%1023s", tmp) == 1) { for (i = 0; i < GF_NUM_MODES; i++) if (strcmp (tmp, gflare_modes[i]) == 0) { *mode = i; return; } } gf->error = TRUE; } static void gflare_save (GFlare *gflare) { FILE *fp; gchar *path; gchar buf[3][G_ASCII_DTOSTR_BUF_SIZE]; static gboolean message_ok = FALSE; if (gflare->filename == NULL) { GList *list; if (gflare_path == NULL) { if (! message_ok) { gchar *gimprc = gimp_personal_rc_file ("gimprc"); gchar *dir = gimp_personal_rc_file ("gflare"); gchar *gflare_dir; gflare_dir = g_strescape ("${gimp_dir}" G_DIR_SEPARATOR_S "gflare", NULL); g_message (_("GFlare '%s' is not saved. If you add a new entry " "in '%s', like:\n" "(gflare-path \"%s\")\n" "and make a folder '%s', then you can save " "your own GFlares into that folder."), gflare->name, gimprc, gflare_dir, gimp_filename_to_utf8 (dir)); g_free (gimprc); g_free (gflare_dir); g_free (dir); message_ok = TRUE; } return; } list = gimp_path_parse (gflare_path, 256, FALSE, NULL); path = gimp_path_get_user_writable_dir (list); gimp_path_free (list); if (! path) path = g_strdup (gimp_directory ()); gflare->filename = g_build_filename (path, gflare->name, NULL); g_free (path); } fp = g_fopen (gflare->filename, "wb"); if (!fp) { g_message (_("Failed to write GFlare file '%s': %s"), gimp_filename_to_utf8 (gflare->filename), g_strerror (errno)); return; } fprintf (fp, "%s", GFLARE_FILE_HEADER); g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_opacity); fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->glow_mode]); g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_opacity); fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->rays_mode]); g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_opacity); fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->sflare_mode]); gflare_write_gradient_name (gflare->glow_radial, fp); gflare_write_gradient_name (gflare->glow_angular, fp); gflare_write_gradient_name (gflare->glow_angular_size, fp); g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_size); g_ascii_dtostr (buf[1], G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_rotation); g_ascii_dtostr (buf[2], G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_hue); fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]); gflare_write_gradient_name (gflare->rays_radial, fp); gflare_write_gradient_name (gflare->rays_angular, fp); gflare_write_gradient_name (gflare->rays_angular_size, fp); g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_size); g_ascii_dtostr (buf[1], G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_rotation); g_ascii_dtostr (buf[2], G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_hue); fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]); g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_thickness); fprintf (fp, "%d %s\n", gflare->rays_nspikes, buf[0]); gflare_write_gradient_name (gflare->sflare_radial, fp); gflare_write_gradient_name (gflare->sflare_sizefac, fp); gflare_write_gradient_name (gflare->sflare_probability, fp); g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_size); g_ascii_dtostr (buf[1], G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_rotation); g_ascii_dtostr (buf[2], G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_hue); fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]); fprintf (fp, "%s %d %d\n", gflare_shapes[gflare->sflare_shape], gflare->sflare_nverts, gflare->sflare_seed); fclose (fp); } static void gflare_write_gradient_name (GradientName name, FILE *fp) { gchar enc[1024]; /* @GRADIENT_NAME */ /* encode white spaces and control characters (if any) */ gradient_name_encode (enc, name); fprintf (fp, "%s\n", enc); } static void gflare_name_copy (gchar *dest, const gchar *src) { strncpy (dest, src, GFLARE_NAME_MAX - 1); dest[GFLARE_NAME_MAX - 1] = '\0'; } /*************************************************************************/ /** **/ /** +++ GFlares List **/ /** **/ /*************************************************************************/ static gint gflare_compare (const GFlare *flare1, const GFlare *flare2) { return strcmp (flare1->name, flare2->name); } static gint gflares_list_insert (GFlare *gflare) { num_gflares++; gflares_list = g_list_insert_sorted (gflares_list, gflare, (GCompareFunc) gflare_compare); return gflares_list_index (gflare); } static gint gflare_compare_name (const GFlare *flare, const gchar *name) { return strcmp (flare->name, name); } static GFlare * gflares_list_lookup (const gchar *name) { GList *llink; llink = g_list_find_custom (gflares_list, name, (GCompareFunc) gflare_compare_name); return (llink) ? llink->data : NULL; } static gint gflares_list_index (GFlare *gflare) { return g_list_index (gflares_list, gflare); } static gint gflares_list_remove (GFlare *gflare) { GList *tmp; gint n; n = 0; tmp = gflares_list; while (tmp) { if (tmp->data == gflare) { /* Found! */ if (tmp->next == NULL) num_gflares--; gflares_list = g_list_remove (gflares_list, gflare); return n; } tmp = tmp->next; n++; } return -1; } /* * Load all gflares, which are founded in gflare-path-list, into gflares_list. */ static void gflares_list_load_all (void) { GList *path; GList *list; /* Make sure to clear any existing gflares */ gflares_list_free_all (); path = gimp_config_path_expand_to_files (gflare_path, NULL); for (list = path; list; list = g_list_next (list)) { GFileEnumerator *enumerator; enumerator = g_file_enumerate_children (list->data, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (enumerator) { GFileInfo *info; while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { GFileType file_type = g_file_info_get_file_type (info); if (file_type == G_FILE_TYPE_REGULAR && ! g_file_info_get_is_hidden (info)) { GFlare *gflare; GFile *child; gchar *filename; gchar *basename; child = g_file_enumerator_get_child (enumerator, info); filename = g_file_get_path (child); basename = g_file_get_basename (child); gflare = gflare_load (filename, basename); g_free (filename); g_free (basename); if (gflare) gflares_list_insert (gflare); g_object_unref (child); } g_object_unref (info); } g_object_unref (enumerator); } } g_list_free_full (path, (GDestroyNotify) g_object_unref); } static void gflares_list_free_all (void) { g_list_free_full (gflares_list, (GDestroyNotify) gflare_free); gflares_list = NULL; } /*************************************************************************/ /** **/ /** +++ Calculator **/ /** **/ /*************************************************************************/ /* * These routines calculates pixel values of particular gflare, at * specified point. The client which wants to get benefit from these * calculation routines must call calc_init_params() first, and * iterate calling calc_init_progress() until it returns FALSE. and * must call calc_deinit() when job is done. */ static void calc_init_params (GFlare *gflare, gint calc_type, gdouble xcenter, gdouble ycenter, gdouble radius, gdouble rotation, gdouble hue, gdouble vangle, gdouble vlength) { calc.type = calc_type; calc.gflare = gflare; calc.xcenter = xcenter; calc.ycenter = ycenter; calc.radius = radius; calc.rotation = rotation * G_PI / 180.0; calc.hue = hue; calc.vangle = vangle * G_PI / 180.0; calc.vlength = radius * vlength / 100.0; calc.glow_radius = radius * gflare->glow_size / 100.0; calc.rays_radius = radius * gflare->rays_size / 100.0; calc.sflare_radius = radius * gflare->sflare_size / 100.0; calc.glow_rotation = (rotation + gflare->glow_rotation) * G_PI / 180.0; calc.rays_rotation = (rotation + gflare->rays_rotation) * G_PI / 180.0; calc.sflare_rotation = (rotation + gflare->sflare_rotation) * G_PI / 180.0; calc.glow_opacity = gflare->glow_opacity * 255 / 100.0; calc.rays_opacity = gflare->rays_opacity * 255 / 100.0; calc.sflare_opacity = gflare->sflare_opacity * 255 / 100.0; calc.glow_bounds.x0 = calc.xcenter - calc.glow_radius - 0.1; calc.glow_bounds.x1 = calc.xcenter + calc.glow_radius + 0.1; calc.glow_bounds.y0 = calc.ycenter - calc.glow_radius - 0.1; calc.glow_bounds.y1 = calc.ycenter + calc.glow_radius + 0.1; calc.rays_bounds.x0 = calc.xcenter - calc.rays_radius - 0.1; calc.rays_bounds.x1 = calc.xcenter + calc.rays_radius + 0.1; calc.rays_bounds.y0 = calc.ycenter - calc.rays_radius - 0.1; calc.rays_bounds.y1 = calc.ycenter + calc.rays_radius + 0.1; /* Thanks to Marcelo Malheiros for this algorithm */ calc.rays_thinness = log (gflare->rays_thickness / 100.0) / log(0.8); calc.rays_spike_mod = 1.0 / (2 * gflare->rays_nspikes); /* Initialize part of sflare The rest will be initialized in calc_sflare() */ calc.sflare_list = NULL; calc.sflare_shape = gflare->sflare_shape; if (calc.sflare_shape == GF_POLYGON) { calc.sflare_angle = 2 * G_PI / (2 * gflare->sflare_nverts); calc.sflare_factor = 1.0 / cos (calc.sflare_angle); } calc.glow_radial = NULL; calc.glow_angular = NULL; calc.glow_angular_size = NULL; calc.rays_radial = NULL; calc.rays_angular = NULL; calc.rays_angular_size = NULL; calc.sflare_radial = NULL; calc.sflare_sizefac = NULL; calc.sflare_probability = NULL; calc.init = TRUE; } static int calc_init_progress (void) { if (calc_sample_one_gradient ()) return TRUE; calc_place_sflare (); return FALSE; } /* Store samples of gradient into an array this routine is called during Calc initialization this code is very messy... :( */ static int calc_sample_one_gradient (void) { static struct { guchar **values; gint name_offset; gint hue_offset; gint gray; } table[] = { { &calc.glow_radial, G_STRUCT_OFFSET (GFlare, glow_radial), G_STRUCT_OFFSET (GFlare, glow_hue), FALSE }, { &calc.glow_angular, G_STRUCT_OFFSET (GFlare, glow_angular), 0, FALSE }, { &calc.glow_angular_size, G_STRUCT_OFFSET (GFlare, glow_angular_size), 0, TRUE }, { &calc.rays_radial, G_STRUCT_OFFSET (GFlare, rays_radial), G_STRUCT_OFFSET (GFlare, rays_hue), FALSE }, { &calc.rays_angular, G_STRUCT_OFFSET (GFlare, rays_angular), 0, FALSE }, { &calc.rays_angular_size, G_STRUCT_OFFSET (GFlare, rays_angular_size), 0, TRUE }, { &calc.sflare_radial, G_STRUCT_OFFSET (GFlare, sflare_radial), G_STRUCT_OFFSET (GFlare, sflare_hue), FALSE }, { &calc.sflare_sizefac, G_STRUCT_OFFSET (GFlare, sflare_sizefac), 0, TRUE }, { &calc.sflare_probability, G_STRUCT_OFFSET (GFlare, sflare_probability), 0, TRUE } }; GFlare *gflare = calc.gflare; GradientName *grad_name; guchar *gradient; gdouble hue_deg; gint i, j, hue; for (i = 0; i < G_N_ELEMENTS (table); i++) { if (*(table[i].values) == NULL) { /* @GRADIENT_NAME */ grad_name = (GradientName *) ((char*) gflare + table[i].name_offset); gradient = *(table[i].values) = g_new (guchar, 4 * GRADIENT_RESOLUTION); gradient_get_values (*grad_name, gradient, GRADIENT_RESOLUTION); /* * Do hue rotation, if needed */ if (table[i].hue_offset != 0) { hue_deg = calc.hue + *(gdouble *) ((char*) gflare + table[i].hue_offset); hue = (gint) (hue_deg / 360.0 * 256.0) % 256; if (hue < 0) hue += 256; g_assert (0 <= hue && hue < 256); if (hue > 0) { for (j = 0; j < GRADIENT_RESOLUTION; j++) { GimpRGB rgb; GimpHSV hsv; rgb.r = (gdouble) gradient[j*4] / 255.0; rgb.g = (gdouble) gradient[j*4+1] / 255.0; rgb.b = (gdouble) gradient[j*4+2] / 255.0; gimp_rgb_to_hsv (&rgb, &hsv); hsv.h = (hsv.h + ((gdouble) hue / 255.0)); if (hsv.h > 1.0) hsv.h -= 1.0; gimp_hsv_to_rgb (&hsv, &rgb); gradient[j*4] = ROUND (rgb.r * 255.0); gradient[j*4+1] = ROUND (rgb.g * 255.0); gradient[j*4+2] = ROUND (rgb.b * 255.0); } } } /* * Grayfy gradient, if needed */ if (table[i].gray) { for (j = 0; j < GRADIENT_RESOLUTION; j++) /* the first byte is enough */ gradient[j*4] = LUMINOSITY ((gradient + j*4)); } /* sampling of one gradient is done */ return TRUE; } } return FALSE; } static void calc_place_sflare (void) { GFlare *gflare; CalcSFlare *sflare; gdouble prob[GRADIENT_RESOLUTION]; gdouble sum, sum2; gdouble pos; gdouble rnd, sizefac; int n; int i; GRand *gr; gr = g_rand_new (); if ((calc.type & CALC_SFLARE) == 0) return; gflare = calc.gflare; /* Calc cumulative probability */ sum = 0.0; for (i = 0; i < GRADIENT_RESOLUTION; i++) { /* probability gradient was grayfied already */ prob[i] = calc.sflare_probability[i*4]; sum += prob[i]; } if (sum == 0.0) sum = 1.0; sum2 = 0; for (i = 0; i < GRADIENT_RESOLUTION; i++) { sum2 += prob[i]; /* cumulation */ prob[i] = sum2 / sum; } g_rand_set_seed (gr, gflare->sflare_seed); for (n = 0; n < SFLARE_NUM; n++) { sflare = g_new (CalcSFlare, 1); rnd = g_rand_double (gr); for (i = 0; i < GRADIENT_RESOLUTION; i++) if (prob[i] >= rnd) break; if (i >= GRADIENT_RESOLUTION) i = GRADIENT_RESOLUTION - 1; /* sizefac gradient was grayfied already */ sizefac = calc.sflare_sizefac[i*4] / 255.0; sizefac = pow (sizefac, 5.0); pos = (double) (i - GRADIENT_RESOLUTION / 2) / GRADIENT_RESOLUTION; sflare->xcenter = calc.xcenter + cos (calc.vangle) * calc.vlength * pos; sflare->ycenter = calc.ycenter - sin (calc.vangle) * calc.vlength * pos; sflare->radius = sizefac * calc.sflare_radius; /* FIXME */ sflare->bounds.x0 = sflare->xcenter - sflare->radius - 1; sflare->bounds.x1 = sflare->xcenter + sflare->radius + 1; sflare->bounds.y0 = sflare->ycenter - sflare->radius - 1; sflare->bounds.y1 = sflare->ycenter + sflare->radius + 1; calc.sflare_list = g_list_append (calc.sflare_list, sflare); } g_rand_free (gr); } static void calc_deinit (void) { if (!calc.init) { g_warning("calc_deinit: not initialized"); return; } g_list_free_full (calc.sflare_list, (GDestroyNotify) g_free); g_free (calc.glow_radial); g_free (calc.glow_angular); g_free (calc.glow_angular_size); g_free (calc.rays_radial); g_free (calc.rays_angular); g_free (calc.rays_angular_size); g_free (calc.sflare_radial); g_free (calc.sflare_sizefac); g_free (calc.sflare_probability); calc.init = FALSE; } /* * Get sample value at specified position of a gradient * * gradient samples are stored into array at the time of * calc_sample_one_gradients (), and it is now linear interpolated. * * INPUT: * guchar gradient[4*GRADIENT_RESOLUTION] gradient array(RGBA) * gdouble pos position (0<=pos<=1) * OUTPUT: * guchar pix[4] */ static void calc_get_gradient (guchar *pix, guchar *gradient, gdouble pos) { gint ipos; gdouble frac; gint i; if (pos < 0 || pos > 1) { pix[0] = pix[1] = pix[2] = pix[3] = 0; return; } pos *= GRADIENT_RESOLUTION - 1.0001; ipos = (gint) pos; frac = pos - ipos; gradient += ipos * 4; for (i = 0; i < 4; i++) { pix[i] = gradient[i] * (1 - frac) + gradient[i+4] * frac; } } /* I need fmod to return always positive value */ static gdouble fmod_positive (gdouble x, gdouble m) { return x - floor (x/m) * m; } /* * Calc glow's pixel (RGBA) value * INPUT: * gdouble x, y image coordinates * OUTPUT: * guchar pix[4] */ static void calc_glow_pix (guchar *dest_pix, gdouble x, gdouble y) { gdouble radius, angle; gdouble angular_size; guchar radial_pix[4], angular_pix[4], size_pix[4]; gint i; if ((calc.type & CALC_GLOW) == 0 || x < calc.glow_bounds.x0 || x > calc.glow_bounds.x1 || y < calc.glow_bounds.y0 || y > calc.glow_bounds.y1) { memset (dest_pix, 0, 4); return; } x -= calc.xcenter; y -= calc.ycenter; radius = sqrt (x*x + y*y) / calc.glow_radius; angle = (atan2 (-y, x) + calc.glow_rotation ) / (2 * G_PI); angle = fmod_positive (angle, 1.0); calc_get_gradient (size_pix, calc.glow_angular_size, angle); /* angular_size gradient was grayfied already */ angular_size = size_pix[0] / 255.0; radius /= (angular_size+0.0001); /* in case angular_size == 0.0 */ if (radius < 0 || radius > 1) { memset (dest_pix, 0, 4); return; } calc_get_gradient (radial_pix, calc.glow_radial, radius); calc_get_gradient (angular_pix, calc.glow_angular, angle); for (i = 0; i < 4; i++) dest_pix[i] = radial_pix[i] * angular_pix[i] / 255; } /* * Calc rays's pixel (RGBA) value * */ static void calc_rays_pix (guchar *dest_pix, gdouble x, gdouble y) { gdouble radius, angle; gdouble angular_size; gdouble spike_frac, spike_inten, spike_angle; guchar radial_pix[4], angular_pix[4], size_pix[4]; gint i; if ((calc.type & CALC_RAYS) == 0 || x < calc.rays_bounds.x0 || x > calc.rays_bounds.x1 || y < calc.rays_bounds.y0 || y > calc.rays_bounds.y1) { memset (dest_pix, 0, 4); return; } x -= calc.xcenter; y -= calc.ycenter; radius = sqrt (x*x + y*y) / calc.rays_radius; angle = (atan2 (-y, x) + calc.rays_rotation ) / (2 * G_PI); angle = fmod_positive (angle, 1.0); /* make sure 0 <= angle < 1.0 */ spike_frac = fmod (angle, calc.rays_spike_mod * 2); spike_angle = angle - spike_frac + calc.rays_spike_mod; spike_frac = (angle - spike_angle) / calc.rays_spike_mod; /* spike_frac is between -1.0 and 1.0 here (except round error...) */ spike_inten = pow (1.0 - fabs (spike_frac), calc.rays_thinness); calc_get_gradient (size_pix, calc.rays_angular_size, spike_angle); /* angular_size gradient was grayfied already */ angular_size = size_pix[0] / 255.0; radius /= (angular_size+0.0001); /* in case angular_size == 0.0 */ if(radius < 0 || radius > 1) { memset (dest_pix, 0, 4); return; } calc_get_gradient (radial_pix, calc.rays_radial, radius); calc_get_gradient (angular_pix, calc.rays_angular, spike_angle); for (i = 0; i < 3; i++) dest_pix[i] = radial_pix[i] * angular_pix[i] / 255; dest_pix[3] = spike_inten * radial_pix[3] * angular_pix[3] / 255; } /* * Calc sflare's pixel (RGBA) value * * the sflare (second flares) are needed to be rendered one each * sequentially, onto the source image, such as like usual layer * operations. So the function takes src_pix as argument. glow, rays * routines don't have src_pix as argument, because of convenience. * * @JAPANESE * sflare $B$OJ#?t$N%U%l%"$r=g$K(B($B%l%$%dE*$K(B)$B$+$V$;$J$,$iIA2h$9$kI,MW$,(B * $B$"$k$N$G!"$3$l$@$1(B src_pix $B$r0z?t$K$H$C$F(B paint_func $B$rE,MQ$9$k!#(B * glow, rays $B$O4J0W2=$N$?$a$K$J$7!#(B */ void calc_sflare_pix (guchar *dest_pix, gdouble x, gdouble y, guchar *src_pix) { GList *list; CalcSFlare *sflare; gdouble sx, sy, th; gdouble radius, angle; guchar radial_pix[4], tmp_pix[4]; memcpy (dest_pix, src_pix, 4); if ((calc.type & CALC_SFLARE) == 0) return; list = calc.sflare_list; while (list) { sflare = list->data; list = list->next; if (x < sflare->bounds.x0 || x > sflare->bounds.x1 || y < sflare->bounds.y0 || y > sflare->bounds.y1) continue; sx = x - sflare->xcenter; sy = y - sflare->ycenter; radius = sqrt (sx * sx + sy * sy) / sflare->radius; if (calc.sflare_shape == GF_POLYGON) { angle = atan2 (-sy, sx) - calc.vangle + calc.sflare_rotation; th = fmod_positive (angle, calc.sflare_angle * 2) - calc.sflare_angle; radius *= cos (th) * calc.sflare_factor; } if (radius < 0 || radius > 1) continue; calc_get_gradient (radial_pix, calc.sflare_radial, radius); memcpy (tmp_pix, dest_pix, 4); calc_paint_func (dest_pix, tmp_pix, radial_pix, calc.sflare_opacity, calc.gflare->sflare_mode); } } static void calc_gflare_pix (guchar *dest_pix, gdouble x, gdouble y, guchar *src_pix) { GFlare *gflare = calc.gflare; guchar glow_pix[4], rays_pix[4]; guchar tmp_pix[4]; memcpy (dest_pix, src_pix, 4); if (calc.type & CALC_GLOW) { memcpy (tmp_pix, dest_pix, 4); calc_glow_pix (glow_pix, x, y); calc_paint_func (dest_pix, tmp_pix, glow_pix, calc.glow_opacity, gflare->glow_mode); } if (calc.type & CALC_RAYS) { memcpy (tmp_pix, dest_pix, 4); calc_rays_pix (rays_pix, x, y); calc_paint_func (dest_pix, tmp_pix, rays_pix, calc.rays_opacity, gflare->rays_mode); } if (calc.type & CALC_SFLARE) { memcpy (tmp_pix, dest_pix, 4); calc_sflare_pix (dest_pix, x, y, tmp_pix); } } /* Paint func routines, such as Normal, Addition, ... */ static void calc_paint_func (guchar *dest, guchar *src1, guchar *src2, gint opacity, GFlareMode mode) { guchar buf[4], *s=buf; if (src2[3] == 0 || opacity <= 0) { memcpy (dest, src1, 4); return; } switch (mode) { case GF_NORMAL: s = src2; break; case GF_ADDITION: calc_addition (s, src1, src2); break; case GF_OVERLAY: calc_overlay (s, src1, src2); break; case GF_SCREEN: calc_screen (s, src1, src2); break; default: s = src2; break; } calc_combine (dest, src1, s, opacity); } static void calc_combine (guchar *dest, guchar *src1, guchar *src2, gint opacity) { gdouble s1_a, s2_a, new_a; gdouble ratio, compl_ratio; gint i; s1_a = src1[3] / 255.0; s2_a = src2[3] * opacity / 65025.0; new_a = s1_a + (1.0 - s1_a) * s2_a; if (new_a != 0.0) ratio = s2_a / new_a; else ratio = 0.0; compl_ratio = 1.0 - ratio; for (i = 0; i < 3; i++) dest[i] = src1[i] * compl_ratio + src2[i] * ratio; dest[3] = new_a * 255.0; } static void calc_addition (guchar *dest, guchar *src1, guchar *src2) { gint tmp, i; for (i = 0; i < 3; i++) { tmp = src1[i] + src2[i]; dest[i] = tmp <= 255 ? tmp: 255; } dest[3] = MIN (src1[3], src2[3]); } static void calc_screen (guchar *dest, guchar *src1, guchar *src2) { gint i; for (i = 0; i < 3; i++) { dest[i] = 255 - ((255 - src1[i]) * (255 - src2[i])) / 255; } dest[3] = MIN (src1[3], src2[3]); } static void calc_overlay (guchar *dest, guchar *src1, guchar *src2) { gint screen, mult, i; for (i = 0; i < 3; i++) { screen = 255 - ((255 - src1[i]) * (255 - src2[i])) / 255; mult = (src1[i] * src2[i]) / 255; dest[i] = (screen * src1[i] + mult * (255 - src1[i])) / 255; } dest[3] = MIN (src1[3], src2[3]); } /*************************************************************************/ /** **/ /** Main Dialog **/ /** +++ dlg **/ /** **/ /*************************************************************************/ /* This is gflare main dialog, one which opens in first. */ static gboolean dlg_run (void) { GeglBuffer *src_buffer; GtkWidget *shell; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *frame; GtkWidget *abox; GtkWidget *button; GtkWidget *notebook; gboolean run = FALSE; gimp_ui_init (PLUG_IN_BINARY, TRUE); /* * Init Main Dialog */ dlg = g_new0 (GFlareDialog, 1); dlg->init = TRUE; dlg->update_preview = TRUE; gradient_menu_init (); /* FIXME: this should go elsewhere */ dlg_setup_gflare (); g_assert (gflares_list != NULL); g_assert (dlg->gflare != NULL); g_assert (dlg->gflare->name != NULL); /* * Dialog Shell */ shell = dlg->shell = gimp_dialog_new (_("Gradient Flare"), 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 (shell), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); /* * main hbox */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell))), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); /* * Preview */ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); gtk_widget_show (vbox); abox = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); gtk_box_pack_start (GTK_BOX (vbox), abox, TRUE, TRUE, 0); gtk_widget_show (abox); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (abox), frame); gtk_widget_show (frame); src_buffer = gimp_drawable_get_buffer (drawable_ID); dlg->preview = preview_new (DLG_PREVIEW_WIDTH, DLG_PREVIEW_HEIGHT, dlg_preview_init_func, NULL, dlg_preview_render_func, src_buffer, dlg_preview_deinit_func, NULL); gtk_widget_set_events (GTK_WIDGET (dlg->preview->widget), DLG_PREVIEW_MASK); gtk_container_add (GTK_CONTAINER (frame), dlg->preview->widget); g_signal_connect (dlg->preview->widget, "realize", G_CALLBACK (dlg_preview_realize), NULL); g_signal_connect (dlg->preview->widget, "event", G_CALLBACK (dlg_preview_handle_event), NULL); dlg_preview_calc_window (); button = gtk_check_button_new_with_mnemonic (_("A_uto update preview")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), dlg->update_preview); gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0); gtk_widget_show (button); g_signal_connect (button, "toggled", G_CALLBACK (dlg_update_preview_callback), &dlg->update_preview); /* * Notebook */ notebook = dlg->notebook = gtk_notebook_new (); gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0); gtk_widget_show (notebook); dlg_make_page_settings (dlg, notebook); dlg_make_page_selector (dlg, notebook); gtk_widget_show (shell); /* * Initialization done */ dlg->init = FALSE; dlg_preview_update (); if (gimp_dialog_run (GIMP_DIALOG (shell)) == GTK_RESPONSE_OK) { gflare_name_copy (pvals.gflare_name, dlg->gflare->name); run = TRUE; } g_object_unref (src_buffer); gtk_widget_destroy (shell); return run; } static void dlg_setup_gflare (void) { dlg->gflare = gflares_list_lookup (pvals.gflare_name); if (!dlg->gflare) { dlg->gflare = gflares_list_lookup ("Default"); if (!dlg->gflare) { g_warning (_("'Default' is created.")); dlg->gflare = gflare_new_with_default (_("Default")); gflares_list_insert (dlg->gflare); } } } /***********************************/ /** Main Dialog / Preview **/ /***********************************/ /* * Calculate preview's window, ie. translation of preview widget and * drawable. * * x0, x1, y0, y1 are drawable coord, corresponding with top left * corner of preview widget, etc. */ void dlg_preview_calc_window (void) { gint width = gimp_drawable_width (drawable_ID); gint height = gimp_drawable_height (drawable_ID); gint is_wide; gdouble offx, offy; is_wide = ((double) DLG_PREVIEW_HEIGHT * width >= (double) DLG_PREVIEW_WIDTH * height); if (is_wide) { offy = ((double) width * DLG_PREVIEW_HEIGHT / DLG_PREVIEW_WIDTH) / 2.0; dlg->pwin.x0 = 0; dlg->pwin.x1 = width; dlg->pwin.y0 = height / 2.0 - offy; dlg->pwin.y1 = height / 2.0 + offy; } else { offx = ((double) height * DLG_PREVIEW_WIDTH / DLG_PREVIEW_HEIGHT) / 2.0; dlg->pwin.x0 = width / 2.0 - offx; dlg->pwin.x1 = width / 2.0 + offx; dlg->pwin.y0 = 0; dlg->pwin.y1 = height; } } void ed_preview_calc_window (void) { gint width = gimp_drawable_width (drawable_ID); gint height = gimp_drawable_height (drawable_ID); gint is_wide; gdouble offx, offy; is_wide = ((double) DLG_PREVIEW_HEIGHT * width >= (double) DLG_PREVIEW_WIDTH * height); if (is_wide) { offy = ((double) width * DLG_PREVIEW_HEIGHT / DLG_PREVIEW_WIDTH) / 2.0; dlg->pwin.x0 = 0; dlg->pwin.x1 = width; dlg->pwin.y0 = height / 2.0 - offy; dlg->pwin.y1 = height / 2.0 + offy; } else { offx = ((double) height * DLG_PREVIEW_WIDTH / DLG_PREVIEW_HEIGHT) / 2.0; dlg->pwin.x0 = width / 2.0 - offx; dlg->pwin.x1 = width / 2.0 + offx; dlg->pwin.y0 = 0; dlg->pwin.y1 = height; } } static void dlg_preview_realize (GtkWidget *widget) { GdkDisplay *display = gtk_widget_get_display (widget); GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_CROSSHAIR); gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); gdk_cursor_unref (cursor); } static gboolean dlg_preview_handle_event (GtkWidget *widget, GdkEvent *event) { GdkEventButton *bevent; gint bx, by, x, y; switch (event->type) { case GDK_BUTTON_PRESS: bevent = (GdkEventButton *) event; bx = bevent->x; by = bevent->y; /* convert widget coord to drawable coord */ x = dlg->pwin.x0 + (double) (dlg->pwin.x1 - dlg->pwin.x0) * bx / DLG_PREVIEW_WIDTH; y = dlg->pwin.y0 + (double) (dlg->pwin.y1 - dlg->pwin.y0) * by / DLG_PREVIEW_HEIGHT; if ((x != pvals.xcenter || y != pvals.ycenter)) { if (x != pvals.xcenter) { pvals.xcenter = x; gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (dlg->sizeentry), 0, x); } if (y != pvals.ycenter) { pvals.ycenter = y; gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (dlg->sizeentry), 1, y); } dlg_preview_update (); } return TRUE; default: break; } return FALSE; } static void dlg_preview_update (void) { if (!dlg->init && dlg->update_preview) { dlg->init_params_done = FALSE; preview_render_start (dlg->preview); } } /* preview callbacks */ static gint dlg_preview_init_func (Preview *preview, gpointer data) { /* call init_params first, and iterate init_progress while it returns true */ if (dlg->init_params_done == FALSE) { calc_init_params (dlg->gflare, CALC_GLOW | CALC_RAYS | CALC_SFLARE, pvals.xcenter, pvals.ycenter, pvals.radius, pvals.rotation, pvals.hue, pvals.vangle, pvals.vlength); dlg->init_params_done = TRUE; return TRUE; } return calc_init_progress (); } /* render preview do what "preview" means, ie. render lense flare effect onto drawable */ static void dlg_preview_render_func (Preview *preview, guchar *dest, gint y, gpointer data) { GeglBuffer *src_buffer = data; gint width = gimp_drawable_width (drawable_ID); gint height = gimp_drawable_height (drawable_ID); gint x; gint dx, dy; /* drawable x, y */ guchar *src_row, *src; guchar src_pix[4], dest_pix[4]; gint b; dy = (dlg->pwin.y0 + (gdouble) (dlg->pwin.y1 - dlg->pwin.y0) * y / DLG_PREVIEW_HEIGHT); if (dy < 0 || dy >= height) { memset (dest, GRAY50, 3 * DLG_PREVIEW_WIDTH); return; } src_row = g_new (guchar, dinfo.bpp * width); gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, dy, width, 1), 1.0, dinfo.format, src_row, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); for (x = 0; x < DLG_PREVIEW_HEIGHT; x++) { dx = (dlg->pwin.x0 + (double) (dlg->pwin.x1 - dlg->pwin.x0) * x / DLG_PREVIEW_WIDTH); if (dx < 0 || dx >= width) { for (b = 0; b < 3; b++) *dest++ = GRAY50; continue; } /* Get drawable pix value */ src = &src_row[dx * dinfo.bpp]; for (b = 0; b < 3; b++) src_pix[b] = dinfo.is_color ? src[b] : src[0]; src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE; /* Get GFlare pix value */ calc_gflare_pix (dest_pix, dx, dy, src_pix); /* Draw gray check if needed */ preview_rgba_to_rgb (dest, x, y, dest_pix); dest += 3; } g_free (src_row); } static void dlg_preview_deinit_func (Preview *preview, gpointer data) { if (dlg->init_params_done) { calc_deinit (); dlg->init_params_done = TRUE; } } /*****************************************/ /** Main Dialog / Settings Page **/ /*****************************************/ static void dlg_make_page_settings (GFlareDialog *dlg, GtkWidget *notebook) { GtkWidget *main_vbox; GtkWidget *frame; GtkWidget *center; GtkWidget *chain; GtkWidget *table; GtkWidget *button; GtkWidget *asup_table; GtkObject *adj; gdouble xres, yres; gint row; main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); frame = gimp_frame_new (_("Center")); gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); gimp_image_get_resolution (image_ID, &xres, &yres); center = dlg->sizeentry = gimp_coordinates_new (gimp_image_get_unit (image_ID), "%a", TRUE, TRUE, 75, GIMP_SIZE_ENTRY_UPDATE_SIZE, FALSE, FALSE, _("_X:"), pvals.xcenter, xres, -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 0, gimp_drawable_width (drawable_ID), _("_Y:"), pvals.ycenter, yres, -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 0, gimp_drawable_height (drawable_ID)); chain = GTK_WIDGET (GIMP_COORDINATES_CHAINBUTTON (center)); gtk_container_add (GTK_CONTAINER (frame), center); g_signal_connect (center, "value-changed", G_CALLBACK (dlg_position_entry_callback), NULL); g_signal_connect (center, "refval-changed", G_CALLBACK (dlg_position_entry_callback), NULL); gtk_widget_hide (chain); gtk_widget_show (center); frame = gimp_frame_new (_("Parameters")); gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (5, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); gtk_widget_show (table); row = 0; adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("_Radius:"), SCALE_WIDTH, 6, pvals.radius, 0.0, gimp_drawable_width (drawable_ID) / 2, 1.0, 10.0, 1, FALSE, 0.0, GIMP_MAX_IMAGE_SIZE, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &pvals.radius); g_signal_connect (adj, "value-changed", G_CALLBACK (dlg_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Ro_tation:"), SCALE_WIDTH, 6, pvals.rotation, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &pvals.rotation); g_signal_connect (adj, "value-changed", G_CALLBACK (dlg_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("_Hue rotation:"), SCALE_WIDTH, 6, pvals.hue, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &pvals.hue); g_signal_connect (adj, "value-changed", G_CALLBACK (dlg_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Vector _angle:"), SCALE_WIDTH, 6, pvals.vangle, 0.0, 359.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &pvals.vangle); g_signal_connect (adj, "value-changed", G_CALLBACK (dlg_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Vector _length:"), SCALE_WIDTH, 6, pvals.vlength, 1, 1000, 1.0, 10.0, 1, FALSE, 1, GIMP_MAX_IMAGE_SIZE, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &pvals.vlength); g_signal_connect (adj, "value-changed", G_CALLBACK (dlg_preview_update), NULL); /** *** Asupsample settings *** This code is stolen from gimp-0.99.x/app/blend.c **/ /* asupsample frame */ frame = dlg->asupsample_frame = gimp_frame_new (NULL); gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); button = gtk_check_button_new_with_mnemonic (_("A_daptive supersampling")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), pvals.use_asupsample); gtk_frame_set_label_widget (GTK_FRAME (frame), button); gtk_widget_show (button); asup_table = gtk_table_new (2, 3, FALSE); gtk_table_set_col_spacings (GTK_TABLE (asup_table), 6); gtk_table_set_row_spacings (GTK_TABLE (asup_table), 6); gtk_container_add (GTK_CONTAINER (frame), asup_table); gtk_widget_show (asup_table); g_signal_connect (button, "toggled", G_CALLBACK (gimp_toggle_button_update), &pvals.use_asupsample); g_object_bind_property (button, "active", asup_table, "sensitive", G_BINDING_SYNC_CREATE); adj = gimp_scale_entry_new (GTK_TABLE (asup_table), 0, 0, _("_Max depth:"), -1, 4, pvals.asupsample_max_depth, 1.0, 10.0, 1.0, 1.0, 0, TRUE, 0.0, 0.0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_int_adjustment_update), &pvals.asupsample_max_depth); adj = gimp_scale_entry_new (GTK_TABLE (asup_table), 0, 1, _("_Threshold"), -1, 4, pvals.asupsample_threshold, 0.0, 4.0, 0.01, 0.01, 2, TRUE, 0.0, 0.0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &pvals.asupsample_threshold); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), main_vbox, gtk_label_new_with_mnemonic (_("_Settings"))); gtk_widget_show (main_vbox); } static void dlg_position_entry_callback (GtkWidget *widget, gpointer data) { gint x, y; x = RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0)); y = RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1)); DEBUG_PRINT (("dlg_position_entry_callback\n")); if (pvals.xcenter != x || pvals.ycenter != y) { pvals.xcenter = x; pvals.ycenter = y; dlg_preview_update (); } } static void dlg_update_preview_callback (GtkWidget *widget, gpointer data) { gimp_toggle_button_update (widget, data); dlg_preview_update (); } /*****************************************/ /** Main Dialog / Selector Page **/ /*****************************************/ static void dlg_make_page_selector (GFlareDialog *dlg, GtkWidget *notebook) { GtkWidget *vbox; GtkWidget *hbox; GtkWidget *listbox; GtkListStore *list; GtkWidget *listview; GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkWidget *button; gint i; static struct { const gchar *label; GCallback callback; } buttons[] = { { N_("_New"), G_CALLBACK (dlg_selector_new_callback) }, { N_("_Edit"), G_CALLBACK (dlg_selector_edit_callback) }, { N_("_Copy"), G_CALLBACK (dlg_selector_copy_callback) }, { N_("_Delete"), G_CALLBACK (dlg_selector_delete_callback) } }; DEBUG_PRINT (("dlg_make_page_selector\n")); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); /* * List Box */ listbox = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listbox), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_size_request (listbox, DLG_LISTBOX_WIDTH, DLG_LISTBOX_HEIGHT); gtk_box_pack_start (GTK_BOX (vbox), listbox, TRUE, TRUE, 0); list = dlg->selector_list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER); listview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list)); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (listview), FALSE); gtk_container_add (GTK_CONTAINER (listbox), listview); gtk_widget_show (listbox); dlg->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview)); gtk_tree_selection_set_mode (dlg->selection, GTK_SELECTION_BROWSE); gtk_widget_show (listview); g_signal_connect (dlg->selection, "changed", G_CALLBACK (dlg_selector_list_item_callback), NULL); renderer = gtk_cell_renderer_text_new (); /* Note: the title isn't shown, so it doesn't need to be translated. */ column = gtk_tree_view_column_new_with_attributes ("GFlare", renderer, "text", 0, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (listview), column); dlg_selector_setup_listbox (); /* * The buttons for the possible listbox operations */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); for (i = 0; i < G_N_ELEMENTS (buttons); i++) { button = gtk_button_new_with_mnemonic (gettext (buttons[i].label)); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_show (button); g_signal_connect (button, "clicked", buttons[i].callback, button); } gtk_widget_show (vbox); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, gtk_label_new_with_mnemonic (_("S_elector"))); gtk_widget_show (vbox); } /* * Set up selector's listbox, according to gflares_list */ static void dlg_selector_setup_listbox (void) { GList *list; GFlare *gflare; gint n; list = gflares_list; n = 0; while (list) { GtkTreeIter iter; gflare = list->data; /* dlg->gflare should be valid (ie. not NULL) here. */ gtk_list_store_append (dlg->selector_list, &iter); gtk_list_store_set (dlg->selector_list, &iter, 0, gflare->name, 1, gflare, -1); if (gflare == dlg->gflare) gtk_tree_selection_select_iter (dlg->selection, &iter); list = list->next; n++; } } static void dlg_selector_list_item_callback (GtkTreeSelection *selection) { GtkTreeIter iter; if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { gtk_tree_model_get (GTK_TREE_MODEL (dlg->selector_list), &iter, 1, &dlg->gflare, -1); } dlg_preview_update (); } /* * "New" button in Selector page */ static void dlg_selector_new_callback (GtkWidget *widget, gpointer data) { GtkWidget *query_box; query_box = gimp_query_string_box (_("New Gradient Flare"), gtk_widget_get_toplevel (widget), gimp_standard_help_func, PLUG_IN_PROC, _("Enter a name for the new GFlare"), _("Unnamed"), NULL, NULL, dlg_selector_new_ok_callback, dlg); gtk_widget_show (query_box); } static void dlg_selector_new_ok_callback (GtkWidget *widget, const gchar *new_name, gpointer data) { GFlare *gflare; GtkTreeIter iter; gint pos; g_return_if_fail (new_name != NULL); if (gflares_list_lookup (new_name)) { g_message (_("The name '%s' is used already!"), new_name); return; } gflare = gflare_new_with_default (new_name); pos = gflares_list_insert (gflare); gtk_list_store_insert (dlg->selector_list, &iter, pos); gtk_list_store_set (dlg->selector_list, &iter, 0, gflare->name, 1, gflare, -1); gtk_tree_selection_select_iter (dlg->selection, &iter); dlg->gflare = gflare; dlg_preview_update (); } /* * "Edit" button in Selector page */ static void dlg_selector_edit_callback (GtkWidget *widget, gpointer data) { preview_render_end (dlg->preview); gtk_widget_set_sensitive (dlg->shell, FALSE); ed_run (GTK_WINDOW (dlg->shell), dlg->gflare, dlg_selector_edit_done_callback, NULL); } static void dlg_selector_edit_done_callback (gint updated, gpointer data) { gtk_widget_set_sensitive (dlg->shell, TRUE); if (updated) { gflare_save (dlg->gflare); } dlg_preview_update (); } /* * "Copy" button in Selector page */ static void dlg_selector_copy_callback (GtkWidget *widget, gpointer data) { GtkWidget *query_box; gchar *name; name = g_strdup_printf ("%s copy", dlg->gflare->name); query_box = gimp_query_string_box (_("Copy Gradient Flare"), gtk_widget_get_toplevel (widget), gimp_standard_help_func, PLUG_IN_PROC, _("Enter a name for the copied GFlare"), name, NULL, NULL, dlg_selector_copy_ok_callback, dlg); g_free (name); gtk_widget_show (query_box); } static void dlg_selector_copy_ok_callback (GtkWidget *widget, const gchar *copy_name, gpointer data) { GFlare *gflare; GtkTreeIter iter; gint pos; g_return_if_fail (copy_name != NULL); if (gflares_list_lookup (copy_name)) { g_warning (_("The name '%s' is used already!"), copy_name); return; } gflare = gflare_dup (dlg->gflare, copy_name); pos = gflares_list_insert (gflare); gtk_list_store_insert (dlg->selector_list, &iter, pos); gtk_list_store_set (dlg->selector_list, &iter, 0, gflare->name, 1, gflare, -1); gtk_tree_selection_select_iter (dlg->selection, &iter); dlg->gflare = gflare; gflare_save (dlg->gflare); dlg_preview_update (); } /* * "Delete" button in Selector page */ static void dlg_selector_delete_callback (GtkWidget *widget, gpointer data) { GtkWidget *dialog; gchar *str; if (num_gflares <= 1) { g_message (_("Cannot delete!! There must be at least one GFlare.")); return; } gtk_widget_set_sensitive (dlg->shell, FALSE); str = g_strdup_printf (_("Are you sure you want to delete " "\"%s\" from the list and from disk?"), dlg->gflare->name); dialog = gimp_query_boolean_box (_("Delete Gradient Flare"), dlg->shell, gimp_standard_help_func, PLUG_IN_PROC, GIMP_ICON_DIALOG_QUESTION, str, _("_Delete"), _("_Cancel"), NULL, NULL, dlg_selector_do_delete_callback, NULL); g_free (str); gtk_widget_show (dialog); } static void dlg_selector_do_delete_callback (GtkWidget *widget, gboolean delete, gpointer data) { GFlare *old_gflare; GList *tmp; gint i, new_i; GtkTreeIter iter; gtk_widget_set_sensitive (dlg->shell, TRUE); if (!delete) return; i = gflares_list_index (dlg->gflare); if (i >= 0) { /* Remove current gflare from gflares_list and free it */ old_gflare = dlg->gflare; gflares_list_remove (dlg->gflare); dlg->gflare = NULL; /* Remove from listbox */ if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (dlg->selector_list), &iter, NULL, i)) { g_warning ("Unsynchronized lists. Bad things will happen!"); return; } gtk_list_store_remove (dlg->selector_list, &iter); /* Calculate new position of gflare and select it */ new_i = (i < num_gflares) ? i : num_gflares - 1; if ((tmp = g_list_nth (gflares_list, new_i))) dlg->gflare = tmp->data; if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (dlg->selector_list), &iter, NULL, i)) { g_warning ("Unsynchronized lists. Bad things will happen!"); return; } gtk_tree_selection_select_iter (dlg->selection, &iter); /* Delete old one from disk and memory */ if (old_gflare->filename) g_unlink (old_gflare->filename); gflare_free (old_gflare); /* Update */ dlg_preview_update (); } else { g_warning (_("not found %s in gflares_list"), dlg->gflare->name); } } /*************************************************************************/ /** **/ /** GFlare Editor **/ /** +++ ed **/ /** **/ /*************************************************************************/ /* This is gflare editor dialog, one which opens by clicking "Edit" button on the selector page in the main dialog. */ static void ed_run (GtkWindow *parent, GFlare *target_gflare, GFlareEditorCallback callback, gpointer calldata) { GtkWidget *shell; GtkWidget *hbox; GtkWidget *frame; GtkWidget *abox; GtkWidget *notebook; if (!ed) ed = g_new0 (GFlareEditor, 1); ed->init = TRUE; ed->run = FALSE; ed->target_gflare = target_gflare; ed->gflare = gflare_dup (target_gflare, target_gflare->name); ed->callback = callback; ed->calldata = calldata; /* * Dialog Shell */ ed->shell = shell = gimp_dialog_new (_("Gradient Flare Editor"), PLUG_IN_ROLE, GTK_WIDGET (parent), 0, gimp_standard_help_func, PLUG_IN_PROC, _("_Rescan Gradients"), RESPONSE_RESCAN, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_alternative_button_order (GTK_DIALOG (shell), RESPONSE_RESCAN, GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); g_signal_connect (shell, "response", G_CALLBACK (ed_response), ed); g_signal_connect (shell, "destroy", G_CALLBACK (ed_destroy_callback), ed); /* * main hbox */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell))), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); /* * Preview */ abox = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); gtk_box_pack_start (GTK_BOX (hbox), abox, FALSE, FALSE, 0); gtk_widget_show (abox); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (abox), frame); gtk_widget_show (frame); ed->preview = preview_new (ED_PREVIEW_WIDTH, ED_PREVIEW_HEIGHT, ed_preview_init_func, NULL, ed_preview_render_func, NULL, ed_preview_deinit_func, NULL); gtk_widget_set_events (GTK_WIDGET (ed->preview->widget), DLG_PREVIEW_MASK); gtk_container_add (GTK_CONTAINER (frame), ed->preview->widget); g_signal_connect (ed->preview->widget, "event", G_CALLBACK (dlg_preview_handle_event), NULL); ed_preview_calc_window (); /* * Notebook */ notebook = ed->notebook = gtk_notebook_new (); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0); gtk_widget_show (notebook); ed_make_page_general (ed, notebook); ed_make_page_glow (ed, notebook); ed_make_page_rays (ed, notebook); ed_make_page_sflare (ed, notebook); gtk_widget_show (shell); ed->init = FALSE; ed_preview_update (); } static void ed_destroy_callback (GtkWidget *widget, GFlareEditor *ed) { preview_free (ed->preview); gflare_free (ed->gflare); if (ed->callback) (*ed->callback) (ed->run, ed->calldata); } static void ed_response (GtkWidget *widget, gint response_id, GFlareEditor *ed) { switch (response_id) { case RESPONSE_RESCAN: ed->init = TRUE; gradient_menu_rescan (); ed->init = FALSE; ed_preview_update (); gtk_widget_queue_draw (ed->notebook); break; case GTK_RESPONSE_OK: ed->run = TRUE; gflare_copy (ed->target_gflare, ed->gflare); gtk_widget_destroy (ed->shell); break; default: gtk_widget_destroy (ed->shell); break; } } static void ed_make_page_general (GFlareEditor *ed, GtkWidget *notebook) { GFlare *gflare = ed->gflare; GtkWidget *vbox; GtkWidget *frame; GtkWidget *table; GtkWidget *combo; GtkObject *adj; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); /* Glow */ frame = gimp_frame_new (_("Glow Paint Options")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (2, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); gtk_widget_show (table); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("Opacity:"), SCALE_WIDTH, 6, gflare->glow_opacity, 0.0, 100.0, 1.0, 10.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->glow_opacity); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); combo = ed_mode_menu_new (&gflare->glow_mode); gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, _("Paint mode:"), 0.0, 0.5, combo, 1, FALSE); /* Rays */ frame = gimp_frame_new (_("Rays Paint Options")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (2, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); gtk_widget_show (table); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("Opacity:"), SCALE_WIDTH, 6, gflare->rays_opacity, 0.0, 100.0, 1.0, 10.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->rays_opacity); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); combo = ed_mode_menu_new (&gflare->rays_mode); gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, _("Paint mode:"), 0.0, 0.5, combo, 1, FALSE); /* Rays */ frame = gimp_frame_new (_("Second Flares Paint Options")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (2, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); gtk_widget_show (table); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("Opacity:"), SCALE_WIDTH, 6, gflare->sflare_opacity, 0.0, 100.0, 1.0, 10.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->sflare_opacity); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); combo = ed_mode_menu_new (&gflare->sflare_mode); gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, _("Paint mode:"), 0.0, 0.5, combo, 1, FALSE); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, gtk_label_new_with_mnemonic (_("_General"))); g_signal_connect (vbox, "map", G_CALLBACK (ed_page_map_callback), (gpointer) PAGE_GENERAL); gtk_widget_show (vbox); } static void ed_make_page_glow (GFlareEditor *ed, GtkWidget *notebook) { GFlare *gflare = ed->gflare; GradientMenu *gm; GtkWidget *vbox; GtkWidget *frame; GtkWidget *table; GtkObject *adj; gint row; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); /* * Gradient Menus */ frame = gimp_frame_new (_("Gradients")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (3, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->glow_radial, gflare->glow_radial); ed_put_gradient_menu (table, 0, 0, _("Radial gradient:"), gm); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->glow_angular, gflare->glow_angular); ed_put_gradient_menu (table, 0, 1, _("Angular gradient:"), gm); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->glow_angular_size, gflare->glow_angular_size); ed_put_gradient_menu (table, 0, 2, _("Angular size gradient:"), gm); gtk_widget_show (table); /* * Scales */ frame = gimp_frame_new (_("Parameters")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (3, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); row = 0; adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Size (%):"), SCALE_WIDTH, 6, gflare->glow_size, 0.0, 200.0, 1.0, 10.0, 1, FALSE, 0, G_MAXINT, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->glow_size); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Rotation:"), SCALE_WIDTH, 6, gflare->glow_rotation, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->glow_rotation); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Hue rotation:"), SCALE_WIDTH, 6, gflare->glow_hue, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->glow_hue); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); gtk_widget_show (table); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, gtk_label_new_with_mnemonic (_("G_low"))); g_signal_connect (vbox, "map", G_CALLBACK (ed_page_map_callback), (gpointer) PAGE_GLOW); gtk_widget_show (vbox); } static void ed_make_page_rays (GFlareEditor *ed, GtkWidget *notebook) { GFlare *gflare = ed->gflare; GradientMenu *gm; GtkWidget *vbox; GtkWidget *frame; GtkWidget *table; GtkObject *adj; gint row; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); /* * Gradient Menus */ frame = gimp_frame_new (_("Gradients")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (3, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); row = 0; gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->rays_radial, gflare->rays_radial); ed_put_gradient_menu (table, 0, row++, _("Radial gradient:"), gm); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->rays_angular, gflare->rays_angular); ed_put_gradient_menu (table, 0, row++, _("Angular gradient:"), gm); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->rays_angular_size, gflare->rays_angular_size); ed_put_gradient_menu (table, 0, row++, _("Angular size gradient:"), gm); gtk_widget_show (table); /* * Scales */ frame = gimp_frame_new (_("Parameters")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (5, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); row = 0; adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Size (%):"), SCALE_WIDTH, 6, gflare->rays_size, 0.0, 200.0, 1.0, 10.0, 1, FALSE, 0, G_MAXINT, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->rays_size); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Rotation:"), SCALE_WIDTH, 6, gflare->rays_rotation, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->rays_rotation); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Hue rotation:"), SCALE_WIDTH, 6, gflare->rays_hue, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->rays_hue); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("# of Spikes:"), SCALE_WIDTH, 6, gflare->rays_nspikes, 1, 300, 1.0, 10.0, 0, FALSE, 0, G_MAXINT, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_int_adjustment_update), &gflare->rays_nspikes); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Spike thickness:"), SCALE_WIDTH, 6, gflare->rays_thickness, 1.0, 100.0, 1.0, 10.0, 1, FALSE, 0, GIMP_MAX_IMAGE_SIZE, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->rays_thickness); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); gtk_widget_show (table); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, gtk_label_new_with_mnemonic (_("_Rays"))); g_signal_connect (vbox, "map", G_CALLBACK (ed_page_map_callback), (gpointer) PAGE_RAYS); gtk_widget_show (vbox); } static void ed_make_page_sflare (GFlareEditor *ed, GtkWidget *notebook) { GFlare *gflare = ed->gflare; GradientMenu *gm; GtkWidget *vbox; GtkWidget *table; GtkWidget *frame; GtkWidget *shape_vbox; GSList *shape_group = NULL; GtkWidget *polygon_hbox; GtkWidget *seed_hbox; GtkWidget *toggle; GtkWidget *label; GtkWidget *seed; GtkWidget *entry; GtkObject *adj; gchar buf[256]; gint row; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); /* * Gradient Menus */ frame = gimp_frame_new (_("Gradients")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (3, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->sflare_radial, gflare->sflare_radial); ed_put_gradient_menu (table, 0, 0, _("Radial gradient:"), gm); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->sflare_sizefac, gflare->sflare_sizefac); ed_put_gradient_menu (table, 0, 1, _("Size factor gradient:"), gm); gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback, gflare->sflare_probability, gflare->sflare_probability); ed_put_gradient_menu (table, 0, 2, _("Probability gradient:"), gm); gtk_widget_show (table); /* * Scales */ frame = gimp_frame_new (_("Parameters")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); table = gtk_table_new (3, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); row = 0; adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Size (%):"), SCALE_WIDTH, 6, gflare->sflare_size, 0.0, 200.0, 1.0, 10.0, 1, FALSE, 0, G_MAXINT, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->sflare_size); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Rotation:"), SCALE_WIDTH, 6, gflare->sflare_rotation, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->sflare_rotation); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, _("Hue rotation:"), SCALE_WIDTH, 6, gflare->sflare_hue, -180.0, 180.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (adj, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &gflare->sflare_hue); g_signal_connect (adj, "value-changed", G_CALLBACK (ed_preview_update), NULL); gtk_widget_show (table); /* * Shape Radio Button Frame */ frame = gimp_frame_new (_("Shape of Second Flares")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); shape_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); gtk_container_add (GTK_CONTAINER (frame), shape_vbox); gtk_widget_show (shape_vbox); toggle = gtk_radio_button_new_with_label (shape_group, _("Circle")); shape_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); g_object_set_data (G_OBJECT (toggle), "gimp-item-data", GINT_TO_POINTER (GF_CIRCLE)); g_signal_connect (toggle, "toggled", G_CALLBACK (ed_shape_radio_callback), &gflare->sflare_shape); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), gflare->sflare_shape == GF_CIRCLE); gtk_box_pack_start (GTK_BOX (shape_vbox), toggle, FALSE, FALSE, 0); gtk_widget_show (toggle); polygon_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start (GTK_BOX (shape_vbox), polygon_hbox, FALSE, FALSE, 0); gtk_widget_show (polygon_hbox); toggle = ed->polygon_toggle = gtk_radio_button_new_with_label (shape_group, _("Polygon")); shape_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); g_object_set_data (G_OBJECT (toggle), "gimp-item-data", GINT_TO_POINTER (GF_POLYGON)); g_signal_connect (toggle, "toggled", G_CALLBACK (ed_shape_radio_callback), &gflare->sflare_shape); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), gflare->sflare_shape == GF_POLYGON); gtk_box_pack_start (GTK_BOX (polygon_hbox), toggle, FALSE, FALSE, 0); gtk_widget_show (toggle); entry = ed->polygon_entry = gtk_entry_new (); gtk_entry_set_width_chars (GTK_ENTRY (entry), 4); g_snprintf (buf, sizeof (buf), "%d", gflare->sflare_nverts); gtk_entry_set_text (GTK_ENTRY (entry), buf); g_signal_connect (entry, "changed", G_CALLBACK (ed_ientry_callback), &gflare->sflare_nverts); gtk_box_pack_start (GTK_BOX (polygon_hbox), entry, FALSE, FALSE, 0); gtk_widget_show (entry); g_object_bind_property (toggle, "active", entry, "sensitive", G_BINDING_SYNC_CREATE); /* * Random Seed Entry */ seed_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start (GTK_BOX (vbox), seed_hbox, FALSE, FALSE, 0); gtk_widget_show (seed_hbox); label = gtk_label_new (_("Random seed:")); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_box_pack_start (GTK_BOX (seed_hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); seed = gimp_random_seed_new (&gflare->sflare_seed, &gflare->random_seed); gtk_box_pack_start (GTK_BOX (seed_hbox), seed, FALSE, TRUE, 0); gtk_widget_show (seed); g_signal_connect (GIMP_RANDOM_SEED_SPINBUTTON_ADJ (seed), "value-changed", G_CALLBACK (ed_preview_update), NULL); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, gtk_label_new_with_mnemonic (_("_Second Flares"))); g_signal_connect (vbox, "map", G_CALLBACK (ed_page_map_callback), (gpointer) PAGE_SFLARE); gtk_widget_show (vbox); } GtkWidget * ed_mode_menu_new (GFlareMode *mode_var) { GtkWidget *combo = gimp_int_combo_box_new_array (GF_NUM_MODES, gflare_menu_modes); gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), *mode_var, G_CALLBACK (ed_mode_menu_callback), mode_var); return combo; } /* puts gradient menu with caption into table occupies 1 row and 3 cols in table */ static void ed_put_gradient_menu (GtkWidget *table, gint x, gint y, const gchar *caption, GradientMenu *gm) { GtkWidget *label; label = gtk_label_new (caption); gtk_label_set_xalign (GTK_LABEL (label), 1.0); gtk_widget_show (label); gtk_table_attach (GTK_TABLE (table), label, x , x + 1, y, y + 1, GTK_FILL, 0, 0, 0); gtk_table_attach (GTK_TABLE (table), gm->preview, x + 1, x + 2, y, y + 1, 0, 0, 0, 0); gtk_table_attach (GTK_TABLE (table), gm->combo, x + 2, x + 3, y, y + 1, 0, 0, 0, 0); } static void ed_mode_menu_callback (GtkWidget *widget, gpointer data) { gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), (gint *) data); ed_preview_update (); } static void ed_gradient_menu_callback (const gchar *gradient_name, gpointer data) { gchar *dest_string = data; /* @GRADIENT_NAME */ gradient_name_copy (dest_string, gradient_name); ed_preview_update (); } static void ed_shape_radio_callback (GtkWidget *widget, gpointer data) { gimp_radio_button_update (widget, data); ed_preview_update (); } static void ed_ientry_callback (GtkWidget *widget, gpointer data) { gint new_val; new_val = atoi (gtk_entry_get_text (GTK_ENTRY (widget))); *(gint *)data = new_val; ed_preview_update (); } /* NOTE: This is hack, because this code depends on internal "map" signal of changing pages of gtknotebook. */ static void ed_page_map_callback (GtkWidget *widget, gpointer data) { gint page_num = GPOINTER_TO_INT (data); DEBUG_PRINT(("ed_page_map_callback\n")); ed->cur_page = page_num; ed_preview_update (); } static void ed_preview_update (void) { if (ed->init) return; ed->init_params_done = FALSE; preview_render_start (ed->preview); } static gint ed_preview_init_func (Preview *preview, gpointer data) { int type = 0; if (ed->init_params_done == FALSE) { switch (ed->cur_page) { case PAGE_GENERAL: type = (CALC_GLOW | CALC_RAYS | CALC_SFLARE); break; case PAGE_GLOW: type = CALC_GLOW; break; case PAGE_RAYS: type = CALC_RAYS; break; case PAGE_SFLARE: type = CALC_SFLARE; break; default: g_warning ("ed_preview_edit_func: bad page"); break; } calc_init_params (ed->gflare, type, ED_PREVIEW_WIDTH/2, ED_PREVIEW_HEIGHT/2, ED_PREVIEW_WIDTH/2, 0.0, 0.0, pvals.vangle, pvals.vlength); ed->init_params_done = TRUE; return TRUE; } return calc_init_progress (); } static void ed_preview_deinit_func (Preview *preview, gpointer data) { if (ed->init_params_done) { calc_deinit (); ed->init_params_done = FALSE; } } static void ed_preview_render_func (Preview *preview, guchar *buffer, gint y, gpointer data) { switch (ed->cur_page) { case PAGE_GENERAL: ed_preview_render_general (buffer, y); break; case PAGE_GLOW: ed_preview_render_glow (buffer, y); break; case PAGE_RAYS: ed_preview_render_rays (buffer, y); break; case PAGE_SFLARE: ed_preview_render_sflare (buffer, y); break; default: g_warning ("hmm, bad page in ed_preview_render_func ()"); break; } } static void ed_preview_render_general (guchar *buffer, gint y) { int x, i; guchar gflare_pix[4]; static guchar src_pix[4] = {0, 0, 0, OPAQUE}; int gflare_a; for (x = 0; x < ED_PREVIEW_WIDTH; x++) { calc_gflare_pix (gflare_pix, x, y, src_pix); gflare_a = gflare_pix[3]; for (i = 0; i < 3; i++) { *buffer++ = gflare_pix[i] * gflare_a / 255; } } } static void ed_preview_render_glow (guchar *buffer, gint y) { int x, i; guchar pix[4]; for (x = 0; x < ED_PREVIEW_WIDTH; x++) { calc_glow_pix (pix, x, y); for (i = 0; i < 3; i++) *buffer++ = pix[i] * pix[3] / 255; } } static void ed_preview_render_rays (guchar *buffer, gint y) { int x, i; guchar pix[4]; for (x = 0; x < ED_PREVIEW_WIDTH; x++) { calc_rays_pix (pix, x, y); for (i = 0; i < 3; i++) *buffer++ = pix[i] * pix[3] / 255; } } static void ed_preview_render_sflare (guchar *buffer, gint y) { int x, i; guchar pix[4]; static guchar src_pix[4] = {0, 0, 0, OPAQUE}; for (x = 0; x < ED_PREVIEW_WIDTH; x++) { calc_sflare_pix (pix, x, y, src_pix); for (i = 0; i < 3; i++) *buffer++ = pix[i] * pix[3] / 255; } } /*************************************************************************/ /** **/ /** +++ Preview **/ /** **/ /*************************************************************************/ /* this is generic preview routines. */ /* Routines to render the preview in background */ static Preview * preview_new (gint width, gint height, PreviewInitFunc init_func, gpointer init_data, PreviewRenderFunc render_func, gpointer render_data, PreviewDeinitFunc deinit_func, gpointer deinit_data) { Preview *preview; preview = g_new0 (Preview, 1); preview->widget = gimp_preview_area_new (); gtk_widget_set_size_request (preview->widget, width, height); gtk_widget_show (preview->widget); preview->width = width; preview->height = height; preview->init_func = init_func; preview->init_data = init_data; preview->render_func = render_func; preview->render_data = render_data; preview->deinit_func = deinit_func; preview->deinit_data = deinit_data; preview->idle_tag = 0; preview->buffer = g_new (guchar, width * 3); preview->full_image_buffer = g_new (guchar, width * height * 3); return preview; } static void preview_free (Preview *preview) { preview_render_end (preview); /* not destroy preview->widget */ g_free (preview->buffer); g_free (preview->full_image_buffer); g_free (preview); } /* Start rendering of the preview in background using an idle event. If already started and not yet finished, stop it first. */ static void preview_render_start (Preview *preview) { preview_render_end (preview); preview->init_done = FALSE; preview->current_y = 0; preview->drawn_y = 0; preview->timeout_tag = g_timeout_add (100, (GSourceFunc) preview_render_start_2, preview); } static gint preview_render_start_2 (Preview *preview) { preview->timeout_tag = 0; preview->idle_tag = g_idle_add ((GSourceFunc) preview_handle_idle, preview); return FALSE; } static void preview_render_end (Preview *preview) { if (preview->timeout_tag > 0) { g_source_remove (preview->timeout_tag); preview->timeout_tag = 0; } if (preview->idle_tag > 0) { if (preview->deinit_func) (*preview->deinit_func) (preview, preview->deinit_data); g_source_remove (preview->idle_tag); preview->idle_tag = 0; } } /* Handle an idle event. Return FALSE if done, TRUE otherwise. */ static gboolean preview_handle_idle (Preview *preview) { gboolean done = FALSE; if (preview->init_done == FALSE) { if (preview->init_func && (*preview->init_func) (preview, preview->init_data)) return TRUE; preview->init_done = TRUE; } if (preview->render_func) (*preview->render_func) (preview, preview->buffer, preview->current_y, preview->render_data); else memset (preview->buffer, 0, preview->width * 3); memcpy (preview->full_image_buffer + preview->width * 3 * preview->current_y, preview->buffer, preview->width * 3); if (++preview->current_y >= preview->height) done = TRUE; if (done) { gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview->widget), 0, 0, preview->width, preview->height, GIMP_RGB_IMAGE, preview->full_image_buffer, preview->width * 3); preview->drawn_y = preview->current_y; preview_render_end (preview); return FALSE; } return TRUE; } /* Convert RGBA to RGB with rendering gray check if needed. (from nova.c) input: guchar src[4] RGBA pixel output: guchar dest[3] RGB pixel */ static void preview_rgba_to_rgb (guchar *dest, gint x, gint y, guchar *src) { gint src_a; gint check; gint b; src_a = src[3]; if (src_a == OPAQUE) /* full opaque */ { for (b = 0; b < 3; b++) dest[b] = src[b]; } else { if ((x % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM) ^ (y % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM)) check = GIMP_CHECK_LIGHT * 255; else check = GIMP_CHECK_DARK * 255; if (src_a == 0) /* full transparent */ { for (b = 0; b < 3; b++) dest[b] = check; } else { for (b = 0; b < 3; b++) dest[b] = (src[b] * src_a + check * (OPAQUE-src_a)) / OPAQUE; } } } /*************************************************************************/ /** **/ /** +++ Gradient Menu **/ /** +++ gm **/ /** **/ /*************************************************************************/ static void gradient_menu_init (void) { gm_gradient_get_list (); gradient_menus = NULL; } static void gradient_menu_rescan (void) { GList *tmp; GradientMenu *gm; GtkTreeModel *model; /* Detach and destroy menus first */ tmp = gradient_menus; while (tmp) { gm = tmp->data; tmp = tmp->next; model = gtk_combo_box_get_model (GTK_COMBO_BOX (gm->combo)); gtk_list_store_clear (GTK_LIST_STORE (model)); } gm_gradient_get_list (); tmp = gradient_menus; while (tmp) { gm = tmp->data; tmp = tmp->next; gm_gradient_combo_fill (gm, gm->gradient_name); } } static GradientMenu * gradient_menu_new (GradientMenuCallback callback, gpointer callback_data, const gchar *default_gradient_name) { GradientMenu *gm = g_new (GradientMenu, 1); gm->callback = callback; gm->callback_data = callback_data; gm->preview = gimp_preview_area_new (); gtk_widget_set_size_request (gm->preview, GM_PREVIEW_WIDTH, GM_PREVIEW_HEIGHT); gm->combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); g_signal_connect (gm->combo, "changed", G_CALLBACK (gm_gradient_combo_callback), gm); gm_gradient_combo_fill (gm, default_gradient_name); gtk_widget_show (gm->preview); gtk_widget_show (gm->combo); g_signal_connect (gm->combo, "destroy", G_CALLBACK (gm_combo_destroy_callback), gm); gradient_menus = g_list_append (gradient_menus, gm); return gm; } /* Local Functions */ static void gm_gradient_get_list (void) { int i; if (gradient_names) { for (i = 0; i < num_gradient_names; i++) g_free (gradient_names[i]); g_free (gradient_names); } gradient_cache_flush (); /* to make sure */ gradient_names = gradient_get_list (&num_gradient_names); } static void gm_gradient_combo_fill (GradientMenu *gm, const gchar *default_gradient) { gint active = 0; gint i; for (i = 0; i < num_gradient_names; i++) { const gchar *name = gradient_names[i]; if (strcmp (name, default_gradient) == 0) active = i; gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (gm->combo), GIMP_INT_STORE_VALUE, i, GIMP_INT_STORE_LABEL, name, -1); } gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (gm->combo), active); } static void gm_gradient_combo_callback (GtkWidget *widget, gpointer data) { GradientMenu *gm = data; const gchar *gradient_name; gint index; if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &index) || index < 0 || index >= num_gradient_names) return; gradient_name = gradient_names[index]; DEBUG_PRINT(("gm_combo_callback\n")); gradient_name_copy (gm->gradient_name, gradient_name); gm_preview_draw (gm->preview, gradient_name); if (gm->callback) (* gm->callback) (gradient_name, gm->callback_data); } static void gm_preview_draw (GtkWidget *preview, const gchar *gradient_name) { guchar values[GM_PREVIEW_WIDTH][4]; gint nvalues = GM_PREVIEW_WIDTH; gint row, irow, col; guchar dest_row[GM_PREVIEW_WIDTH][3]; guchar *dest; guchar *src; gint check, b; guchar *dest_total_preview_buffer; const gint alpha = 3; gradient_get_values (gradient_name, (guchar *)values, nvalues); dest_total_preview_buffer = g_new (guchar, GM_PREVIEW_HEIGHT * GM_PREVIEW_WIDTH * 3); for (row = 0; row < GM_PREVIEW_HEIGHT; row += GIMP_CHECK_SIZE_SM) { for (col = 0; col < GM_PREVIEW_WIDTH; col++) { dest = dest_row[col]; src = values[col]; if (src[alpha] == OPAQUE) { /* no alpha channel or opaque -- simple way */ for (b = 0; b < alpha; b++) dest[b] = src[b]; } else { /* more or less transparent */ if((col % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM) ^ (row % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM)) { check = GIMP_CHECK_LIGHT * 255; } else { check = GIMP_CHECK_DARK * 255; } if (src[alpha] == 0) { /* full transparent -- check */ for (b = 0; b < alpha; b++) dest[b] = check; } else { /* middlemost transparent -- mix check and src */ for (b = 0; b < alpha; b++) dest[b] = (src[b] * src[alpha] + check * (OPAQUE - src[alpha])) / OPAQUE; } } } for (irow = 0; irow < GIMP_CHECK_SIZE_SM && row + irow < GM_PREVIEW_HEIGHT; irow++) { memcpy (dest_total_preview_buffer + (row+irow) * 3 * GM_PREVIEW_WIDTH, dest_row, GM_PREVIEW_WIDTH * 3); } } gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), 0, 0, GM_PREVIEW_WIDTH, GM_PREVIEW_HEIGHT, GIMP_RGB_IMAGE, (const guchar *) dest_total_preview_buffer, GM_PREVIEW_WIDTH * 3); g_free (dest_total_preview_buffer); } static void gm_combo_destroy_callback (GtkWidget *w, gpointer data) { GradientMenu *gm = data; gradient_menus = g_list_remove (gradient_menus, gm); } /*************************************************************************/ /** **/ /** +++ Gradients **/ /** **/ /*************************************************************************/ /* Manage both internal and external gradients: list up, cache, sampling, etc. External gradients are cached. */ static void gradient_name_copy (gchar *dest, const gchar *src) { strncpy (dest, src, GRADIENT_NAME_MAX - 1); dest[GRADIENT_NAME_MAX - 1] = '\0'; } /* Translate SPACE to "\\040", etc. */ static void gradient_name_encode (gchar *dest, const gchar *src) { gint cnt = GRADIENT_NAME_MAX - 1; while (*src && cnt--) { if (g_ascii_iscntrl (*src) || g_ascii_isspace (*src) || *src == '\\') { sprintf (dest, "\\%03o", *src++); dest += 4; } else *dest++ = *src++; } *dest = '\0'; } /* Translate "\\040" to SPACE, etc. */ static void gradient_name_decode (gchar *dest, const gchar *src) { gint cnt = GRADIENT_NAME_MAX - 1; guint tmp; while (*src && cnt--) { if (*src == '\\' && *(src+1) && *(src+2) && *(src+3)) { sscanf (src+1, "%3o", &tmp); *dest++ = tmp; src += 4; } else *dest++ = *src++; } *dest = '\0'; } static void gradient_init (void) { gradient_cache_head = NULL; gradient_cache_count = 0; } static void gradient_free (void) { gradient_cache_flush (); } static gchar ** gradient_get_list (gint *num_gradients) { gchar **gradients; gchar **external_gradients = NULL; gint external_ngradients = 0; gint i, n; gradient_cache_flush (); external_gradients = gimp_gradients_get_list (NULL, &external_ngradients); *num_gradients = G_N_ELEMENTS (internal_gradients) + external_ngradients; gradients = g_new (gchar *, *num_gradients); n = 0; for (i = 0; i < G_N_ELEMENTS (internal_gradients); i++) { gradients[n++] = g_strdup (internal_gradients[i]); } for (i = 0; i < external_ngradients; i++) { gradients[n++] = g_strdup (external_gradients[i]); } g_strfreev (external_gradients); return gradients; } static void gradient_get_values (const gchar *gradient_name, guchar *values, gint nvalues) { /* Criteria to distinguish internal and external is rather simple here. It should be fixed later. */ if (gradient_name[0] == '%') gradient_get_values_internal (gradient_name, values, nvalues); else gradient_get_values_external (gradient_name, values, nvalues); } static void gradient_get_values_internal (const gchar *gradient_name, guchar *values, gint nvalues) { const guchar white[4] = { 255, 255, 255, 255 }; const guchar white_trans[4] = { 255, 255, 255, 0 }; const guchar red_trans[4] = { 255, 0, 0, 0 }; const guchar blue_trans[4] = { 0, 0, 255, 0 }; const guchar yellow_trans[4] = { 255, 255, 0, 0 }; /* The internal gradients here are example -- What kind of internals would be useful ? */ if(!strcmp(gradient_name, "%white")) { gradient_get_blend (white, white, values, nvalues); } else if(!strcmp(gradient_name, "%white_grad")) { gradient_get_blend (white, white_trans, values, nvalues); } else if (!strcmp (gradient_name, "%red_grad")) { gradient_get_blend (white, red_trans, values, nvalues); } else if (!strcmp (gradient_name, "%blue_grad")) { gradient_get_blend (white, blue_trans, values, nvalues); } else if (!strcmp (gradient_name, "%yellow_grad")) { gradient_get_blend (white, yellow_trans, values, nvalues); } else if (!strcmp (gradient_name, "%random")) { gradient_get_random (values, nvalues); } else { gradient_get_default (gradient_name, values, nvalues); } } static void gradient_get_blend (const guchar *fg, const guchar *bg, guchar *values, gint nvalues) { gdouble x; gint i; gint j; guchar *v = values; for (i = 0; i < nvalues; i++) { x = (double) i / nvalues; for (j = 0; j < 4; j++) *v++ = fg[j] * (1 - x) + bg[j] * x; } } static void gradient_get_random (guchar *values, gint nvalues) { gint i; gint j; gint inten; guchar *v = values; /* This is really simple -- gaussian noise might be better */ for (i = 0; i < nvalues; i++) { inten = g_random_int_range (0, 256); for (j = 0; j < 3; j++) *v++ = inten; *v++ = 255; } } static void gradient_get_default (const gchar *name, guchar *values, gint nvalues) { gdouble e[3]; gdouble x; gint i; gint j; guchar *v = values; /* Create gradient by name */ name++; for (j = 0; j < 3; j++) e[j] = name[j] / 255.0; for (i = 0; i < nvalues; i++) { x = (double) i / nvalues; for (j = 0; j < 3; j++) *v++ = 255 * pow (x, e[j]); *v++ = 255; } } /* Caching gradients is really needed. It really takes 0.2 seconds each time to resample an external gradient. (And this plug-in has currently 6 gradient menus.) However, this caching routine is not too good. It picks up just GRADIENT_RESOLUTION samples every time, and rescales it later. And cached values are stored in guchar array. No accuracy. */ static void gradient_get_values_external (const gchar *gradient_name, guchar *values, gint nvalues) { GradientCacheItem *ci; gboolean found; #ifdef DEBUG clock_t clk = clock (); #endif g_return_if_fail (nvalues >= 2); ci = gradient_cache_lookup (gradient_name, &found); if (!found) { /* FIXME: "reverse" hardcoded to FALSE. */ gradient_get_values_real_external (gradient_name, ci->values, GRADIENT_RESOLUTION, FALSE); } if (nvalues == GRADIENT_RESOLUTION) { memcpy (values, ci->values, 4 * GRADIENT_RESOLUTION); } else { double pos, frac; int ipos; int i, j; for (i = 0; i < nvalues; i++) { pos = ((double) i / (nvalues - 1)) * (GRADIENT_RESOLUTION - 1); g_assert (0 <= pos && pos <= GRADIENT_RESOLUTION - 1); ipos = (int) pos; frac = pos - ipos; if (frac == 0.0) { memcpy (&values[4 * i], &ci->values[4 * ipos], 4); } else for (j = 0; j < 4; j++) values[4 * i + j] = ci->values[4 * ipos + j] * (1 - frac) + ci->values[4 * (ipos + 1) + j] * frac; } } #ifdef DEBUG get_values_external_clock += clock () - clk; get_values_external_count ++; #endif } static void gradient_get_values_real_external (const gchar *gradient_name, guchar *values, gint nvalues, gboolean reverse) { gint n_tmp_values; gdouble *tmp_values; gint i; gint j; gimp_gradient_get_uniform_samples (gradient_name, nvalues, reverse, &n_tmp_values, &tmp_values); for (i = 0; i < nvalues; i++) for (j = 0; j < 4; j++) values[4 * i + j] = (guchar) (tmp_values[4 * i + j] * 255); g_free (tmp_values); } void gradient_cache_flush (void) { GradientCacheItem *ci; GradientCacheItem *tmp; ci = gradient_cache_head; while (ci) { tmp = ci->next; g_free (ci); ci = tmp; } gradient_cache_head = NULL; gradient_cache_count = 0; } static GradientCacheItem * gradient_cache_lookup (const gchar *name, gboolean *found) { GradientCacheItem *ci; ci = gradient_cache_head; while (ci) { if (!strcmp (ci->name, name)) break; ci = ci->next; } if (ci) { *found = TRUE; if (!ci->prev) { g_assert (ci == gradient_cache_head); return ci; } ci->prev->next = ci->next; if (ci->next) ci->next->prev = ci->prev; ci->next = gradient_cache_head; gradient_cache_head->prev = ci; gradient_cache_head = ci; ci->prev = NULL; return ci; } else { *found = FALSE; while (gradient_cache_count >= GRADIENT_CACHE_SIZE) gradient_cache_zorch(); ci = g_new (GradientCacheItem, 1); strncpy (ci->name, name, GRADIENT_NAME_MAX - 1); ci->name[GRADIENT_NAME_MAX - 1] = '\0'; ci->next = gradient_cache_head; ci->prev = NULL; if (gradient_cache_head) gradient_cache_head->prev = ci; gradient_cache_head = ci; ++gradient_cache_count; return ci; } } static void gradient_cache_zorch (void) { GradientCacheItem *ci = gradient_cache_head; while (ci && ci->next) { ci = ci->next; } if (ci) { g_assert (ci->next == NULL); if (ci->prev) ci->prev->next = NULL; else gradient_cache_head = NULL; g_free (ci); --gradient_cache_count; } } #ifdef DEBUG void gradient_report (void) { double total = (double) get_values_external_clock / CLOCKS_PER_SEC; g_printerr ("gradient_get_values_external " "%.2f sec. / %d times (ave %.2f sec.)\n", total, get_values_external_count, total / get_values_external_count); } #endif