/* sample_colorize.c * A PIKA Plug-In by Wolfgang Hofer */ /* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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 . */ #include "config.h" #include #include #include #include #include #include "libpika/stdplugins-intl.h" #define PLUG_IN_PROC "plug-in-sample-colorize" #define PLUG_IN_BINARY "sample-colorize" #define PLUG_IN_ROLE "pika-sample-colorize" #define NUMBER_IN_ARGS 13 #define LUMINOSITY_0(X) ((X[0] * 30 + X[1] * 59 + X[2] * 11)) #define LUMINOSITY_1(X) ((X[0] * 30 + X[1] * 59 + X[2] * 11) / 100) #define MIX_CHANNEL(a, b, m) (((a * m) + (b * (255 - m))) / 255) #define SMP_GRADIENT -444 #define SMP_INV_GRADIENT -445 #define PREVIEW_BPP 3 #define PREVIEW_SIZE_X 256 #define PREVIEW_SIZE_Y 256 #define DA_WIDTH 256 #define DA_HEIGHT 25 #define GRADIENT_HEIGHT 15 #define CONTROL_HEIGHT DA_HEIGHT - GRADIENT_HEIGHT #define LEVELS_DA_MASK (GDK_EXPOSURE_MASK | \ GDK_ENTER_NOTIFY_MASK | \ GDK_BUTTON_PRESS_MASK | \ GDK_BUTTON_RELEASE_MASK | \ GDK_BUTTON1_MOTION_MASK | \ GDK_POINTER_MOTION_HINT_MASK) #define LOW_INPUT 0x1 #define GAMMA 0x2 #define HIGH_INPUT 0x4 #define LOW_OUTPUT 0x8 #define HIGH_OUTPUT 0x10 #define INPUT_LEVELS 0x20 #define OUTPUT_LEVELS 0x40 #define INPUT_SLIDERS 0x80 #define OUTPUT_SLIDERS 0x100 #define DRAW 0x200 #define REFRESH_DST 0x400 #define ALL 0xFFF #define MC_GET_SAMPLE_COLORS 1 #define MC_DST_REMAP 2 #define MC_ALL (MC_GET_SAMPLE_COLORS | MC_DST_REMAP) typedef struct { PikaDrawable *dst; gint32 sample_id; PikaDrawable *sample; gint32 guess_missing; /* TRUE or FALSE */ gint32 lvl_in_min; /* 0 up to 254 */ gint32 lvl_in_max; /* 1 up to 255 */ float lvl_in_gamma; /* 0.1 up to 10.0 (1.0 == linear) */ gint32 lvl_out_min; /* 0 up to 254 */ gint32 lvl_out_max; /* 1 up to 255 */ float tol_col_err; /* 0.0% up to 100.0% * this is used to find out colors of the same * colortone, while analyzing sample colors, * It does not make much sense for the user to adjust this * value. (I used a param file to find out a suitable value) */ } t_values; typedef struct { PikaProcedureConfig *config; GtkWidget *dialog; GtkWidget *sample_preview; GtkWidget *dst_preview; GtkWidget *sample_colortab_preview; GtkWidget *sample_drawarea; GtkWidget *in_lvl_gray_preview; GtkWidget *in_lvl_drawarea; GtkWidget *sample_button; gint active_slider; gint slider_pos[5]; /* positions for the five sliders */ gboolean enable_preview_update; gboolean sample_show_selection; gboolean dst_show_selection; gboolean sample_show_color; gboolean dst_show_color; } t_samp_interface; typedef struct { guchar color[4]; /* R,G,B,A */ gint32 sum_color; /* nr. of sourcepixels with (nearly the same) color */ void *next; } t_samp_color_elem; typedef struct { gint32 all_samples; /* number of all source pixels with this luminosity */ gint from_sample; /* TRUE: color found in sample, FALSE: interpolated color added */ t_samp_color_elem *col_ptr; /* List of sample colors at same luminosity */ } t_samp_table_elem; typedef struct { PikaDrawable *drawable; GeglBuffer *buffer; const Babl *format; gint width; gint height; void *sel_gdrw; gint x1; gint y1; gint x2; gint y2; gint index_alpha; /* 0 == no alpha, 1 == GREYA, 3 == RGBA */ gint bpp; gint tile_width; gint tile_height; gint shadow; gint32 seldeltax; gint32 seldeltay; } t_GDRW; typedef struct _Colorize Colorize; typedef struct _ColorizeClass ColorizeClass; struct _Colorize { PikaPlugIn parent_instance; }; struct _ColorizeClass { PikaPlugInClass parent_class; }; #define COLORIZE_TYPE (colorize_get_type ()) #define COLORIZE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), COLORIZE_TYPE, Colorize)) GType colorize_get_type (void) G_GNUC_CONST; static GList * colorize_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * colorize_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * colorize_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, PikaProcedureConfig *config, gpointer run_data); static gint main_colorize (gint mc_flags, PikaProcedureConfig *config); static void get_filevalues (void); static gboolean smp_dialog (PikaProcedure *procedure, PikaProcedureConfig *config); static void refresh_dst_preview (GtkWidget *preview, guchar *src_buffer); static void update_preview (PikaDrawable *drawable); static void clear_tables (void); static void free_colors (void); static void levels_update (gint update); static gint level_in_events (GtkWidget *widget, GdkEvent *event); static gint level_in_draw (GtkWidget *widget, cairo_t *cr); static gint level_out_events (GtkWidget *widget, GdkEvent *event); static gint level_out_draw (GtkWidget *widget, cairo_t *cr); static void calculate_level_transfers (void); static void get_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *pixel); static void init_gdrw (t_GDRW *gdrw, PikaDrawable *drawable, gboolean shadow); static void end_gdrw (t_GDRW *gdrw); static void remap_pixel (guchar *pixel, const guchar *original, gint bpp2, PikaProcedureConfig *config); static void guess_missing_colors (void); static void fill_missing_colors (void); static void smp_get_colors (GtkWidget *dialog); static void get_gradient (gint mode); static void clear_preview (GtkWidget *preview); G_DEFINE_TYPE (Colorize, colorize, PIKA_TYPE_PLUG_IN) PIKA_MAIN (COLORIZE_TYPE) DEFINE_STD_SET_I18N static t_samp_interface g_di; /* global dialog interface variables */ static t_values g_values = { NULL, -1, NULL, 1, 0, 255, 1.0, 0, 255, 5.5 }; static t_samp_table_elem g_lum_tab[256]; static guchar g_lvl_trans_tab[256]; static guchar g_out_trans_tab[256]; static guchar g_sample_color_tab[256 * 3]; static guchar g_dst_preview_buffer[PREVIEW_SIZE_X * PREVIEW_SIZE_Y * 4 ]; /* color copy with mask of dst in previewsize */ static gint32 g_tol_col_err; static gint32 g_max_col_err; static gint g_Sdebug = FALSE; static gint g_show_progress = FALSE; static void colorize_class_init (ColorizeClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = colorize_query_procedures; plug_in_class->create_procedure = colorize_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void colorize_init (Colorize *colorize) { } static GList * colorize_query_procedures (PikaPlugIn *plug_in) { return g_list_append (NULL, g_strdup (PLUG_IN_PROC)); } static PikaProcedure * colorize_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, PLUG_IN_PROC)) { static gchar *help_string = "This plug-in colorizes the contents of the specified (gray) layer" " with the help of a sample (color) layer." " It analyzes all colors in the sample layer." " The sample colors are sorted by brightness (== intentisty) and amount" " and stored in a sample colortable (where brightness is the index)" " The pixels of the destination layer are remapped with the help of the" " sample colortable. If use_subcolors is TRUE, the remapping process uses" " all sample colors of the corresponding brightness-intensity and" " distributes the subcolors according to their amount in the sample" " (If the sample has 5 green, 3 yellow, and 1 red pixel of the " " intensity value 105, the destination pixels at intensity value 105" " are randomly painted in green, yellow and red in a relation of 5:3:1" " If use_subcolors is FALSE only one sample color per intensity is used." " (green will be used in this example)" " The brightness intensity value is transformed at the remapping process" " according to the levels: out_lo, out_hi, in_lo, in_high and gamma" " The in_low / in_high levels specify an initial mapping of the intensity." " The gamma value determines how intensities are interpolated between" " the in_lo and in_high levels. A gamma value of 1.0 results in linear" " interpolation. Higher gamma values results in more high-level intensities" " Lower gamma values results in more low-level intensities" " The out_low/out_high levels constrain the resulting intensity index" " The intensity index is used to pick the corresponding color" " in the sample colortable. If hold_inten is FALSE the picked color" " is used 1:1 as resulting remap_color." " If hold_inten is TRUE The brightness of the picked color is adjusted" " back to the original intensity value (only hue and saturation are" " taken from the picked sample color)" " (or to the input level, if orig_inten is set FALSE)" " Works on both Grayscale and RGB image with/without alpha channel." " (the image with the dst_drawable is converted to RGB if necessary)" " The sample_drawable should be of type RGB or RGBA"; procedure = pika_image_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, colorize_run, NULL, NULL); pika_procedure_set_image_types (procedure, "RGB*, GRAY*"); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE); pika_procedure_set_menu_label (procedure, _("_Sample Colorize...")); pika_procedure_add_menu_path (procedure, "/Colors/Map"); pika_procedure_set_documentation (procedure, _("Colorize image using a sample " "image as a guide"), _(help_string), name); pika_procedure_set_attribution (procedure, "Wolfgang Hofer", "hof@hotbot.com", "02/2000"); PIKA_PROC_ARG_DRAWABLE (procedure, "sample-drawable", _("Sample drawable"), _("Sample drawable (should be of Type RGB or RGBA)"), TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "hold-inten", _("Hold _intensity"), _("Hold brightness intensity levels"), TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "orig-inten", _("Original i_ntensity"), _("TRUE: hold brightness of original intensity " "levels, " "FALSE: Hold Intensity of input levels"), TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "rnd-subcolors", _("Us_e subcolors"), _("TRUE: Use all subcolors of same intensity, " "FALSE: Use only one color per intensity"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "guess-missing", _("Smooth sam_ples"), _("TRUE: guess samplecolors for the missing " "intensity values, " "FALSE: use only colors found in the sample"), TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "in-low", _("_Low"), _("Intensity of lowest input"), 0, 254, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "in-high", _("_High"), _("Intensity of highest input"), 1, 255, 255, G_PARAM_READWRITE); PIKA_PROC_ARG_DOUBLE (procedure, "gamma", _("Ga_mma"), _("Gamma adjustment factor, 1.0 is linear"), 0.1, 10.0, 1.0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "out-low", _("Lo_w"), _("Lowest sample color intensity"), 0, 254, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "out-high", _("Hi_gh"), _("Highest sample color intensity"), 1, 255, 255, G_PARAM_READWRITE); } return procedure; } static PikaValueArray * colorize_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, PikaProcedureConfig *config, gpointer run_data) { const gchar *env; PikaDrawable *drawable; gegl_init (NULL, NULL); if (n_drawables != 1) { GError *error = NULL; g_set_error (&error, PIKA_PLUG_IN_ERROR, 0, _("Procedure '%s' only works with one drawable."), PLUG_IN_PROC); return pika_procedure_new_return_values (procedure, PIKA_PDB_CALLING_ERROR, error); } else { drawable = drawables[0]; } env = g_getenv ("SAMPLE_COLORIZE_DEBUG"); if (env != NULL && (*env != 'n') && (*env != 'N')) g_Sdebug = TRUE; if (g_Sdebug) g_printf ("sample colorize run\n"); g_show_progress = FALSE; g_di.config = config; g_object_get (config, "sample-drawable", &g_values.sample, "guess_missing", &g_values.guess_missing, "in-low", &g_values.lvl_in_min, "in-high", &g_values.lvl_in_max, "gamma", &g_values.lvl_in_gamma, "out-low", &g_values.lvl_out_min, "out-high", &g_values.lvl_out_max, NULL); g_values.sample_id = -1; /* fix value */ g_values.tol_col_err = 5.5; /* Get the specified dst_drawable */ g_values.dst = drawable; clear_tables (); /* Make sure that the drawable is gray or RGB color */ if (pika_drawable_is_rgb (drawable) || pika_drawable_is_gray (drawable)) { switch (run_mode) { case PIKA_RUN_INTERACTIVE: if (! smp_dialog (procedure, config)) { return pika_procedure_new_return_values (procedure, PIKA_PDB_CANCEL, NULL); } g_show_progress = TRUE; if (main_colorize (MC_DST_REMAP, config) == 0) { pika_displays_flush (); g_show_progress = FALSE; } free_colors (); pika_displays_flush (); break; case PIKA_RUN_NONINTERACTIVE: if (g_values.sample) g_values.sample_id = pika_item_get_id (PIKA_ITEM (g_values.sample)); if (main_colorize (MC_GET_SAMPLE_COLORS, config) >= 0) { main_colorize (MC_DST_REMAP, config); } else { return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, NULL); } break; case PIKA_RUN_WITH_LAST_VALS: break; } } else { return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, NULL); } return pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); } /* ============================================================================ * callback and constraint procedures for the dialog * ============================================================================ */ static void smp_toggle_callback (GtkWidget *widget, gpointer data) { gboolean hold_inten; gboolean orig_inten; gboolean rnd_subcolors; gboolean guess_missing; g_object_get (g_di.config, "hold-inten", &hold_inten, "orig-inten", &orig_inten, "rnd_subcolors", &rnd_subcolors, "guess_missing", &guess_missing, NULL); if ((data == &g_di.sample_show_selection) || (data == &g_di.sample_show_color)) { update_preview (pika_drawable_get_by_id (g_values.sample_id)); return; } if ((data == &g_di.dst_show_selection) || (data == &g_di.dst_show_color)) { update_preview (g_values.dst); return; } if (hold_inten || orig_inten || rnd_subcolors) { pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (g_di.dialog), "orig-inten", hold_inten, NULL, NULL, FALSE); refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); } if (guess_missing) guess_missing_colors (); else fill_missing_colors (); smp_get_colors (NULL); } static void smp_sample_combo_callback (GtkWidget *widget) { gint value; pika_int_combo_box_get_active (PIKA_INT_COMBO_BOX (widget), &value); g_values.sample_id = value; if (value == SMP_GRADIENT || value == SMP_INV_GRADIENT) { get_gradient (value); smp_get_colors (NULL); if (g_di.sample_preview) clear_preview (g_di.sample_preview); if (g_di.sample_button) gtk_widget_set_sensitive (g_di.sample_button, FALSE); } else { update_preview (pika_drawable_get_by_id (g_values.sample_id)); if (g_di.sample_button) gtk_widget_set_sensitive (g_di.sample_button, TRUE); } } static void smp_dest_combo_callback (GtkWidget *widget) { gint id; pika_int_combo_box_get_active (PIKA_INT_COMBO_BOX (widget), &id); g_values.dst = pika_drawable_get_by_id (id); update_preview (g_values.dst); if (g_di.sample_button) gtk_widget_set_sensitive (g_di.sample_button, TRUE); } static gint smp_constrain (PikaImage *image, PikaItem *item, gpointer data) { /* don't accept layers from indexed images */ if (pika_drawable_is_indexed (PIKA_DRAWABLE (item))) return FALSE; return TRUE; } static void smp_adj_lvl_in_max_upd_callback (GObject *config, const GParamSpec *pspec, gpointer *data) { gint32 value; gint upd_flags; g_object_get (config, "in-high", &value, NULL); value = CLAMP (value, 1, 255); if (value != g_values.lvl_in_max) { g_values.lvl_in_max = value; upd_flags = INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST; if (g_values.lvl_in_max < g_values.lvl_in_min) { g_values.lvl_in_min = g_values.lvl_in_max; upd_flags |= LOW_INPUT; } levels_update (upd_flags); } } static void smp_adj_lvl_in_min_upd_callback (GObject *config, const GParamSpec *pspec, gpointer *data) { gint32 value; gint upd_flags; g_object_get (config, "in-low", &value, NULL); value = CLAMP (value, 0, 254); if (value != g_values.lvl_in_min) { g_values.lvl_in_min = value; upd_flags = INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST; if (g_values.lvl_in_min > g_values.lvl_in_max) { g_values.lvl_in_max = g_values.lvl_in_min; upd_flags |= HIGH_INPUT; } levels_update (upd_flags); } } static void smp_text_gamma_upd_callback (GObject *config, const GParamSpec *pspec, gpointer *data) { double value; g_object_get (config, "gamma", &value, NULL); value = CLAMP (value, 0.1, 10.0); if (value != g_values.lvl_in_gamma) { g_values.lvl_in_gamma = value; levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST); } } static void smp_adj_lvl_out_max_upd_callback (GObject *config, const GParamSpec *pspec, gpointer *data) { gint32 value; gint upd_flags; g_object_get (config, "out-high", &value, NULL); value = CLAMP (value, 1, 255); if (value != g_values.lvl_out_max) { g_values.lvl_out_max = value; upd_flags = OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW | REFRESH_DST; if (g_values.lvl_out_max < g_values.lvl_out_min) { g_values.lvl_out_min = g_values.lvl_out_max; upd_flags |= LOW_OUTPUT; } levels_update (upd_flags); } } static void smp_adj_lvl_out_min_upd_callback (GObject *config, const GParamSpec *pspec, gpointer *data) { gint32 value; gint upd_flags; g_object_get (config, "out-low", &value, NULL); value = CLAMP (value, 0, 254); if (value != g_values.lvl_out_min) { g_values.lvl_out_min = value; upd_flags = OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW | REFRESH_DST; if (g_values.lvl_out_min > g_values.lvl_out_max) { g_values.lvl_out_max = g_values.lvl_out_min; upd_flags |= HIGH_OUTPUT; } levels_update (upd_flags); } } /* ============================================================================ * DIALOG helper procedures * (workers for the updates on the preview widgets) * ============================================================================ */ static void refresh_dst_preview (GtkWidget *preview, guchar *src_buffer) { guchar allrowsbuf[3 * PREVIEW_SIZE_X * PREVIEW_SIZE_Y]; guchar *ptr; guchar *src_ptr; guchar lum; guchar maskbyte; gint x, y; gint preview_bpp; gint src_bpp; preview_bpp = PREVIEW_BPP; src_bpp = PREVIEW_BPP +1; /* 3 colors + 1 maskbyte */ src_ptr = src_buffer; ptr = allrowsbuf; for (y = 0; y < PREVIEW_SIZE_Y; y++) { for (x = 0; x < PREVIEW_SIZE_X; x++) { if ((maskbyte = src_ptr[3]) == 0) { ptr[0] = src_ptr[0]; ptr[1] = src_ptr[1]; ptr[2] = src_ptr[2]; } else { if (g_di.dst_show_color) { remap_pixel (ptr, src_ptr, 3, g_di.config); } else { /* lum = g_out_trans_tab[g_lvl_trans_tab[LUMINOSITY_1(src_ptr)]]; */ /* get brightness from (uncolorized) original */ lum = g_lvl_trans_tab[LUMINOSITY_1 (src_ptr)]; /* get brightness from (uncolorized) original */ ptr[0] = lum; ptr[1] = lum; ptr[2] = lum; } if (maskbyte < 255) { ptr[0] = MIX_CHANNEL (ptr[0], src_ptr[0], maskbyte); ptr[1] = MIX_CHANNEL (ptr[1], src_ptr[1], maskbyte); ptr[2] = MIX_CHANNEL (ptr[2], src_ptr[2], maskbyte); } } ptr += preview_bpp; src_ptr += src_bpp; } } pika_preview_area_draw (PIKA_PREVIEW_AREA (preview), 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, PIKA_RGB_IMAGE, allrowsbuf, PREVIEW_SIZE_X * 3); } static void clear_preview (GtkWidget *preview) { pika_preview_area_fill (PIKA_PREVIEW_AREA (preview), 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, 170, 170, 170); } static void update_pv (GtkWidget *preview, gboolean show_selection, t_GDRW *gdrw, guchar *dst_buffer, gboolean is_color) { guchar allrowsbuf[4 * PREVIEW_SIZE_X * PREVIEW_SIZE_Y]; guchar pixel[4]; guchar *ptr; gint x, y; gint x2, y2; gint ofx, ofy; gint sel_width, sel_height; double scale_x, scale_y; guchar *buf_ptr; guchar dummy[4]; guchar maskbytes[4]; gint dstep; guchar alpha; if (! preview) return; /* init gray pixel (if we are called without a sourceimage (gdwr == NULL) */ pixel[0] = pixel[1] =pixel[2] = pixel[3] = 127; /* calculate scale factors and offsets */ if (show_selection) { sel_width = gdrw->x2 - gdrw->x1; sel_height = gdrw->y2 - gdrw->y1; if (sel_height > sel_width) { scale_y = (gfloat) sel_height / PREVIEW_SIZE_Y; scale_x = scale_y; ofx = (gdrw->x1 + ((sel_width - (PREVIEW_SIZE_X * scale_x)) / 2)); ofy = gdrw->y1; } else { scale_x = (gfloat) sel_width / PREVIEW_SIZE_X; scale_y = scale_x; ofx = gdrw->x1; ofy = (gdrw->y1 + ((sel_height - (PREVIEW_SIZE_Y * scale_y)) / 2)); } } else { if (gdrw->height > gdrw->width) { scale_y = (gfloat) gdrw->height / PREVIEW_SIZE_Y; scale_x = scale_y; ofx = (gdrw->width - (PREVIEW_SIZE_X * scale_x)) / 2; ofy = 0; } else { scale_x = (gfloat) gdrw->width / PREVIEW_SIZE_X; scale_y = scale_x; ofx = 0; ofy = (gdrw->height - (PREVIEW_SIZE_Y * scale_y)) / 2; } } /* check if output goes to previw widget or to dst_buffer */ if (dst_buffer) { buf_ptr = dst_buffer; dstep = PREVIEW_BPP +1; } else { buf_ptr = &dummy[0]; dstep = 0; } /* render preview */ ptr = allrowsbuf; for (y = 0; y < PREVIEW_SIZE_Y; y++) { for (x = 0; x < PREVIEW_SIZE_X; x++) { if (gdrw->drawable) { x2 = ofx + (x * scale_x); y2 = ofy + (y * scale_y); get_pixel (gdrw, x2, y2, &pixel[0]); if (gdrw->sel_gdrw) { get_pixel (gdrw->sel_gdrw, x2 + gdrw->seldeltax, y2 + gdrw->seldeltay, &maskbytes[0]); } else { maskbytes[0] = 255; } } alpha = pixel[gdrw->index_alpha]; if (is_color && (gdrw->bpp > 2)) { buf_ptr[0] = ptr[0] = pixel[0]; buf_ptr[1] = ptr[1] = pixel[1]; buf_ptr[2] = ptr[2] = pixel[2]; } else { if (gdrw->bpp > 2) *ptr = LUMINOSITY_1 (pixel); else *ptr = pixel[0]; *buf_ptr = *ptr; buf_ptr[1] = ptr[1] = *ptr; buf_ptr[2] = ptr[2] = *ptr; } if (gdrw->index_alpha == 0) /* has no alpha channel */ buf_ptr[3] = ptr[3] = 255; else buf_ptr[3] = ptr[3] = MIN (maskbytes[0], alpha); buf_ptr += dstep; /* advance (or stay at dummy byte) */ ptr += 4; } } if (dst_buffer == NULL) { pika_preview_area_draw (PIKA_PREVIEW_AREA (preview), 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, PIKA_RGBA_IMAGE, allrowsbuf, PREVIEW_SIZE_X * 4); gtk_widget_queue_draw (preview); } } static void update_preview (PikaDrawable *drawable) { t_GDRW gdrw; gboolean is_drawable = FALSE; if (! drawable || !g_di.enable_preview_update) return; if (pika_item_get_id (PIKA_ITEM (drawable)) == g_values.sample_id) { is_drawable = TRUE; init_gdrw (&gdrw, drawable, FALSE); update_pv (g_di.sample_preview, g_di.sample_show_selection, &gdrw, NULL, g_di.sample_show_color); } else if (pika_item_get_id (PIKA_ITEM (drawable)) == pika_item_get_id (PIKA_ITEM (g_values.dst))) { is_drawable = TRUE; init_gdrw (&gdrw, drawable, FALSE); update_pv (g_di.dst_preview, g_di.dst_show_selection, &gdrw, &g_dst_preview_buffer[0], g_di.dst_show_color); refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); } if (is_drawable) end_gdrw (&gdrw); } static void levels_draw_slider (cairo_t *cr, GdkRGBA *border_color, GdkRGBA *fill_color, gint xpos) { cairo_move_to (cr, xpos, 0); cairo_line_to (cr, xpos - (CONTROL_HEIGHT - 1) / 2, CONTROL_HEIGHT - 1); cairo_line_to (cr, xpos + (CONTROL_HEIGHT - 1) / 2, CONTROL_HEIGHT - 1); cairo_line_to (cr, xpos, 0); gdk_cairo_set_source_rgba (cr, fill_color); cairo_fill_preserve (cr); gdk_cairo_set_source_rgba (cr, border_color); cairo_stroke (cr); } static void smp_get_colors (GtkWidget *dialog) { gint i; guchar buffer[3 * DA_WIDTH * GRADIENT_HEIGHT]; update_preview (pika_drawable_get_by_id (g_values.sample_id)); /* do not colorize, just analyze sample colors */ if (dialog) main_colorize (MC_GET_SAMPLE_COLORS, g_di.config); for (i = 0; i < GRADIENT_HEIGHT; i++) memcpy (buffer + i * 3 * DA_WIDTH, g_sample_color_tab, 3 * DA_WIDTH); update_preview (g_values.dst); pika_preview_area_draw (PIKA_PREVIEW_AREA (g_di.sample_colortab_preview), 0, 0, DA_WIDTH, GRADIENT_HEIGHT, PIKA_RGB_IMAGE, buffer, DA_WIDTH * 3); } static void levels_update (gint update) { gint i; if (g_Sdebug) g_printf ("levels_update: update request %x\n", update); /* Recalculate the transfer array */ calculate_level_transfers (); if (update & REFRESH_DST) refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); /* update the spinbutton entry widgets */ if (update & LOW_INPUT) g_object_set (g_di.config, "in-low", g_values.lvl_in_min, NULL); if (update & GAMMA) g_object_set (g_di.config, "gamma", g_values.lvl_in_gamma, NULL); if (update & HIGH_INPUT) g_object_set (g_di.config, "in-high", g_values.lvl_in_max, NULL); if (update & LOW_OUTPUT) g_object_set (g_di.config, "out-low", g_values.lvl_out_min, NULL); if (update & HIGH_OUTPUT) g_object_set (g_di.config, "out-high", g_values.lvl_out_max, NULL); if (update & INPUT_LEVELS) { guchar buffer[DA_WIDTH * GRADIENT_HEIGHT]; for (i = 0; i < GRADIENT_HEIGHT; i++) memcpy (buffer + DA_WIDTH * i, g_lvl_trans_tab, DA_WIDTH); pika_preview_area_draw (PIKA_PREVIEW_AREA (g_di.in_lvl_gray_preview), 0, 0, DA_WIDTH, GRADIENT_HEIGHT, PIKA_GRAY_IMAGE, buffer, DA_WIDTH); } if (update & INPUT_SLIDERS) gtk_widget_queue_draw (g_di.in_lvl_drawarea); if (update & OUTPUT_SLIDERS) gtk_widget_queue_draw (g_di.sample_drawarea); } static gboolean level_in_events (GtkWidget *widget, GdkEvent *event) { GdkEventButton *bevent; GdkEventMotion *mevent; gdouble width, mid, tmp; gint x, distance; gint i; gint update = FALSE; switch (event->type) { case GDK_BUTTON_PRESS: if (g_Sdebug) g_printf ("EVENT: GDK_BUTTON_PRESS\n"); gtk_grab_add (widget); bevent = (GdkEventButton *) event; distance = G_MAXINT; for (i = 0; i < 3; i++) { if (fabs (bevent->x - g_di.slider_pos[i]) < distance) { g_di.active_slider = i; distance = fabs (bevent->x - g_di.slider_pos[i]); } } x = bevent->x; update = TRUE; break; case GDK_BUTTON_RELEASE: if (g_Sdebug) g_printf ("EVENT: GDK_BUTTON_RELEASE\n"); gtk_grab_remove (widget); switch (g_di.active_slider) { case 0: /* low input */ levels_update (LOW_INPUT | GAMMA | DRAW); break; case 1: /* gamma */ levels_update (GAMMA); break; case 2: /* high input */ levels_update (HIGH_INPUT | GAMMA | DRAW); break; } refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); break; case GDK_MOTION_NOTIFY: if (g_Sdebug) g_printf ("EVENT: GDK_MOTION_NOTIFY\n"); mevent = (GdkEventMotion *) event; x = mevent->x; gdk_event_request_motions (mevent); update = TRUE; break; default: if (g_Sdebug) g_printf ("EVENT: default\n"); break; } if (update) { if (g_Sdebug) g_printf ("EVENT: ** update **\n"); switch (g_di.active_slider) { case 0: /* low input */ g_values.lvl_in_min = ((double) x / (double) DA_WIDTH) * 255.0; g_values.lvl_in_min = CLAMP (g_values.lvl_in_min, 0, g_values.lvl_in_max); break; case 1: /* gamma */ width = (double) (g_di.slider_pos[2] - g_di.slider_pos[0]) / 2.0; mid = g_di.slider_pos[0] + width; x = CLAMP (x, g_di.slider_pos[0], g_di.slider_pos[2]); tmp = (double) (x - mid) / width; g_values.lvl_in_gamma = 1.0 / pow (10, tmp); /* round the gamma value to the nearest 1/100th */ g_values.lvl_in_gamma = floor (g_values.lvl_in_gamma * 100 + 0.5) / 100.0; break; case 2: /* high input */ g_values.lvl_in_max = ((double) x / (double) DA_WIDTH) * 255.0; g_values.lvl_in_max = CLAMP (g_values.lvl_in_max, g_values.lvl_in_min, 255); break; } levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW); } return FALSE; } static gboolean level_in_draw (GtkWidget *widget, cairo_t *cr) { GtkStyleContext *style = gtk_widget_get_style_context (widget); gdouble width, mid, tmp; GdkRGBA black = { 0.0, 0.0, 0.0, 1.0 }; GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 }; GdkRGBA gray = { 0.5, 0.5, 0.5, 1.0 }; gtk_render_background (style, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); cairo_translate (cr, 0.5, 0.5); cairo_set_line_width (cr, 1.0); g_di.slider_pos[0] = DA_WIDTH * ((double) g_values.lvl_in_min / 255.0); g_di.slider_pos[2] = DA_WIDTH * ((double) g_values.lvl_in_max / 255.0); width = (double) (g_di.slider_pos[2] - g_di.slider_pos[0]) / 2.0; mid = g_di.slider_pos[0] + width; tmp = log10 (1.0 / g_values.lvl_in_gamma); g_di.slider_pos[1] = (int) (mid + width * tmp + 0.5); levels_draw_slider (cr, &black, &gray, g_di.slider_pos[1]); levels_draw_slider (cr, &black, &black, g_di.slider_pos[0]); levels_draw_slider (cr, &black, &white, g_di.slider_pos[2]); return FALSE; } static gboolean level_out_events (GtkWidget *widget, GdkEvent *event) { GdkEventButton *bevent; GdkEventMotion *mevent; gint x, distance; gint i; gint update = FALSE; switch (event->type) { case GDK_BUTTON_PRESS: if (g_Sdebug) g_printf ("OUT_EVENT: GDK_BUTTON_PRESS\n"); bevent = (GdkEventButton *) event; distance = G_MAXINT; for (i = 3; i < 5; i++) { if (fabs (bevent->x - g_di.slider_pos[i]) < distance) { g_di.active_slider = i; distance = fabs (bevent->x - g_di.slider_pos[i]); } } x = bevent->x; update = TRUE; break; case GDK_BUTTON_RELEASE: if (g_Sdebug) g_printf ("OUT_EVENT: GDK_BUTTON_RELEASE\n"); switch (g_di.active_slider) { case 3: /* low output */ levels_update (LOW_OUTPUT | DRAW); break; case 4: /* high output */ levels_update (HIGH_OUTPUT | DRAW); break; } refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); break; case GDK_MOTION_NOTIFY: if (g_Sdebug) g_printf ("OUT_EVENT: GDK_MOTION_NOTIFY\n"); mevent = (GdkEventMotion *) event; x = mevent->x; gdk_event_request_motions (mevent); update = TRUE; break; default: if (g_Sdebug) g_printf ("OUT_EVENT: default\n"); break; } if (update) { if (g_Sdebug) g_printf ("OUT_EVENT: ** update **\n"); switch (g_di.active_slider) { case 3: /* low output */ g_values.lvl_out_min = ((double) x / (double) DA_WIDTH) * 255.0; g_values.lvl_out_min = CLAMP (g_values.lvl_out_min, 0, g_values.lvl_out_max); break; case 4: /* high output */ g_values.lvl_out_max = ((double) x / (double) DA_WIDTH) * 255.0; g_values.lvl_out_max = CLAMP (g_values.lvl_out_max, g_values.lvl_out_min, 255); break; } levels_update (OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW); } return FALSE; } static gboolean level_out_draw (GtkWidget *widget, cairo_t *cr) { GtkStyleContext *style = gtk_widget_get_style_context (widget); GdkRGBA black = { 0.0, 0.0, 0.0, 1.0 }; gtk_render_background (style, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); cairo_translate (cr, 0.5, 0.5); cairo_set_line_width (cr, 1.0); g_di.slider_pos[3] = DA_WIDTH * ((double) g_values.lvl_out_min / 255.0); g_di.slider_pos[4] = DA_WIDTH * ((double) g_values.lvl_out_max / 255.0); levels_draw_slider (cr, &black, &black, g_di.slider_pos[3]); levels_draw_slider (cr, &black, &black, g_di.slider_pos[4]); return FALSE; } /* ============================================================================ * smp_dialog * The Interactive Dialog * ============================================================================ */ static gboolean smp_dialog (PikaProcedure *procedure, PikaProcedureConfig *config) { GtkWidget *dialog; GtkWidget *hbox; GtkWidget *vbox2; GtkWidget *frame; GtkWidget *grid; GtkWidget *check_button; GtkWidget *label; GtkWidget *combo; gint ty; gboolean run; /* set flags for check buttons from mode value bits */ if (g_Sdebug) g_print ("smp_dialog START\n"); /* init some dialog variables */ g_di.enable_preview_update = FALSE; g_di.sample_show_selection = TRUE; g_di.dst_show_selection = TRUE; g_di.dst_show_color = TRUE; g_di.sample_show_color = TRUE; /* Init GTK */ pika_ui_init (PLUG_IN_BINARY); /* Main Dialog */ g_di.dialog = dialog = pika_procedure_dialog_new (procedure, PIKA_PROCEDURE_CONFIG (config), _("Sample Colorize")); /* grid for values */ grid = gtk_grid_new (); gtk_grid_set_row_spacing (GTK_GRID (grid), 12); gtk_grid_set_column_spacing (GTK_GRID (grid), 6); gtk_container_set_border_width (GTK_CONTAINER (grid), 12); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), grid, TRUE, TRUE, 0); ty = 0; /* layer combo_box (Dst) */ label = gtk_label_new (_("Destination:")); gtk_label_set_xalign (GTK_LABEL (label), 1.0); gtk_grid_attach (GTK_GRID (grid), label, 0, ty, 1, 1); gtk_widget_show (label); combo = pika_layer_combo_box_new (smp_constrain, NULL, NULL); pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo), pika_item_get_id (PIKA_ITEM (g_values.dst)), G_CALLBACK (smp_dest_combo_callback), NULL, NULL); gtk_grid_attach (GTK_GRID (grid), combo, 1, ty, 1, 1); gtk_widget_show (combo); /* layer combo_box (Sample) */ label = gtk_label_new (_("Sample:")); gtk_label_set_xalign (GTK_LABEL (label), 1.0); gtk_grid_attach (GTK_GRID (grid), label, 3, ty, 1, 1); gtk_widget_show (label); combo = pika_layer_combo_box_new (smp_constrain, NULL, NULL); pika_int_combo_box_prepend (PIKA_INT_COMBO_BOX (combo), PIKA_INT_STORE_VALUE, SMP_INV_GRADIENT, PIKA_INT_STORE_LABEL, _("From reverse gradient"), PIKA_INT_STORE_ICON_NAME, PIKA_ICON_GRADIENT, -1); pika_int_combo_box_prepend (PIKA_INT_COMBO_BOX (combo), PIKA_INT_STORE_VALUE, SMP_GRADIENT, PIKA_INT_STORE_LABEL, _("From gradient"), PIKA_INT_STORE_ICON_NAME, PIKA_ICON_GRADIENT, -1); pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo), g_values.sample_id, G_CALLBACK (smp_sample_combo_callback), NULL, NULL); gtk_grid_attach (GTK_GRID (grid), combo, 4, ty, 1, 1); gtk_widget_show (combo); ty++; hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_grid_attach (GTK_GRID (grid), hbox, 0, ty, 2, 1); gtk_widget_show (hbox); /* check button */ check_button = gtk_check_button_new_with_mnemonic (_("Sho_w selection")); gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); gtk_widget_show (check_button); g_signal_connect (check_button, "toggled", G_CALLBACK (smp_toggle_callback), &g_di.dst_show_selection); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), g_di.dst_show_selection); /* check button */ check_button = gtk_check_button_new_with_mnemonic (_("Show co_lor")); gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); gtk_widget_show (check_button); g_signal_connect (check_button, "toggled", G_CALLBACK (smp_toggle_callback), &g_di.dst_show_color); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), g_di.dst_show_color); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_grid_attach (GTK_GRID (grid), hbox, 3, ty, 2, 1); gtk_widget_show (hbox); /* check button */ check_button = gtk_check_button_new_with_mnemonic (_("Show selec_tion")); gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); gtk_widget_show (check_button); g_signal_connect (check_button, "toggled", G_CALLBACK (smp_toggle_callback), &g_di.sample_show_selection); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), g_di.sample_show_selection); /* check button */ check_button = gtk_check_button_new_with_mnemonic (_("Show c_olor")); gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); gtk_widget_show (check_button); g_signal_connect (check_button, "toggled", G_CALLBACK (smp_toggle_callback), &g_di.sample_show_color); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), g_di.sample_show_color); ty++; /* Preview (Dst) */ frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_grid_attach (GTK_GRID (grid), frame, 0, ty, 2, 1); gtk_widget_show (frame); g_di.dst_preview = pika_preview_area_new (); gtk_widget_set_halign (g_di.dst_preview, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (g_di.dst_preview, TRUE); gtk_widget_set_size_request (g_di.dst_preview, PREVIEW_SIZE_X, PREVIEW_SIZE_Y); gtk_container_add (GTK_CONTAINER (frame), g_di.dst_preview); gtk_widget_show (g_di.dst_preview); /* Preview (Sample)*/ frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_grid_attach (GTK_GRID (grid), frame, 3, ty, 2, 1); gtk_widget_show (frame); g_di.sample_preview = pika_preview_area_new (); gtk_widget_set_halign (g_di.sample_preview, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (g_di.sample_preview, TRUE); gtk_widget_set_size_request (g_di.sample_preview, PREVIEW_SIZE_X, PREVIEW_SIZE_Y); gtk_container_add (GTK_CONTAINER (frame), g_di.sample_preview); gtk_widget_show (g_di.sample_preview); ty++; /* The levels graylevel prevev */ frame = gtk_frame_new (NULL); vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); gtk_container_add (GTK_CONTAINER (frame), vbox2); gtk_grid_attach (GTK_GRID (grid), frame, 0, ty, 2, 1); g_di.in_lvl_gray_preview = pika_preview_area_new (); gtk_widget_set_halign (g_di.in_lvl_gray_preview, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (g_di.in_lvl_gray_preview, TRUE); gtk_widget_set_size_request (g_di.in_lvl_gray_preview, DA_WIDTH, GRADIENT_HEIGHT); gtk_widget_set_events (g_di.in_lvl_gray_preview, LEVELS_DA_MASK); gtk_box_pack_start (GTK_BOX (vbox2), g_di.in_lvl_gray_preview, FALSE, TRUE, 0); gtk_widget_show (g_di.in_lvl_gray_preview); g_signal_connect (g_di.in_lvl_gray_preview, "event", G_CALLBACK (level_in_events), NULL); /* The levels drawing area */ g_di.in_lvl_drawarea = gtk_drawing_area_new (); gtk_widget_set_halign (g_di.in_lvl_drawarea, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (g_di.in_lvl_drawarea, TRUE); gtk_widget_set_size_request (g_di.in_lvl_drawarea, DA_WIDTH, CONTROL_HEIGHT); gtk_widget_set_events (g_di.in_lvl_drawarea, LEVELS_DA_MASK); gtk_box_pack_start (GTK_BOX (vbox2), g_di.in_lvl_drawarea, FALSE, TRUE, 0); gtk_widget_show (g_di.in_lvl_drawarea); g_signal_connect (g_di.in_lvl_drawarea, "event", G_CALLBACK (level_in_events), NULL); /* TODO: Fix crash when editing inputs with this enabled */ /* g_signal_connect (g_di.in_lvl_drawarea, "draw", G_CALLBACK (level_in_draw), NULL); */ gtk_widget_show (vbox2); gtk_widget_show (frame); /* The sample_colortable prevev */ frame = gtk_frame_new (NULL); vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); gtk_container_add (GTK_CONTAINER (frame), vbox2); gtk_grid_attach (GTK_GRID (grid), frame, 3, ty, 2, 1); g_di.sample_colortab_preview = pika_preview_area_new (); gtk_widget_set_halign (g_di.sample_colortab_preview, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (g_di.sample_colortab_preview, TRUE); gtk_widget_set_size_request (g_di.sample_colortab_preview, DA_WIDTH, GRADIENT_HEIGHT); gtk_box_pack_start (GTK_BOX (vbox2), g_di.sample_colortab_preview, FALSE, TRUE, 0); gtk_widget_show (g_di.sample_colortab_preview); /* The levels drawing area */ g_di.sample_drawarea = gtk_drawing_area_new (); gtk_widget_set_halign (g_di.sample_drawarea, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (g_di.sample_drawarea, TRUE); gtk_widget_set_size_request (g_di.sample_drawarea, DA_WIDTH, CONTROL_HEIGHT); gtk_widget_set_events (g_di.sample_drawarea, LEVELS_DA_MASK); gtk_box_pack_start (GTK_BOX (vbox2), g_di.sample_drawarea, FALSE, TRUE, 0); gtk_widget_show (g_di.sample_drawarea); g_signal_connect (g_di.sample_drawarea, "event", G_CALLBACK (level_out_events), NULL); g_signal_connect (g_di.sample_drawarea, "draw", G_CALLBACK (level_out_draw), NULL); gtk_widget_show (vbox2); gtk_widget_show (frame); ty++; /* PikaProcedureDialog */ pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog), "input-label", _("Input levels:"), FALSE, FALSE); pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "in-low", PIKA_TYPE_SPIN_BUTTON); pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "gamma", PIKA_TYPE_SPIN_BUTTON); pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "in-high", PIKA_TYPE_SPIN_BUTTON); g_signal_connect (config, "notify::in-low", G_CALLBACK (smp_adj_lvl_in_min_upd_callback), NULL); g_signal_connect (config, "notify::gamma", G_CALLBACK (smp_text_gamma_upd_callback), NULL); g_signal_connect (config, "notify::in-high", G_CALLBACK (smp_adj_lvl_in_max_upd_callback), NULL); hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "low-hbox","input-label", "in-low", "gamma", "in-high", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER); pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog), "output-label", _("Output levels:"), FALSE, FALSE); pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "out-low", PIKA_TYPE_SPIN_BUTTON); pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "out-high", PIKA_TYPE_SPIN_BUTTON); g_signal_connect (config, "notify::out-low", G_CALLBACK (smp_adj_lvl_out_min_upd_callback), NULL); g_signal_connect (config, "notify::out-high", G_CALLBACK (smp_adj_lvl_out_max_upd_callback), NULL); hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "high-hbox","output-label", "out-low", "out-high", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER); /* Checkboxes */ hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "intensity-hbox", "hold-inten", "orig-inten", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER); g_signal_connect (config, "notify::hold-inten", G_CALLBACK (smp_toggle_callback), NULL); g_signal_connect (config, "notify::orig-inten", G_CALLBACK (smp_toggle_callback), NULL); hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "sample-hbox", "rnd-subcolors", "guess-missing", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER); g_signal_connect (config, "notify::rnd-subcolors", G_CALLBACK (smp_toggle_callback), NULL); g_signal_connect (config, "notify::guess-missing", G_CALLBACK (smp_toggle_callback), NULL); g_di.sample_button = gtk_button_new_with_mnemonic (_("Get _Sample Colors")); gtk_box_pack_start (GTK_BOX (hbox), g_di.sample_button, FALSE, FALSE, 12); g_signal_connect (g_di.sample_button, "clicked", G_CALLBACK (smp_get_colors), dialog); gtk_widget_set_visible (g_di.sample_button, TRUE); gtk_widget_show (grid); hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "slider-row", "low-hbox", "high-hbox", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "check-row", "intensity-hbox", "sample-hbox", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); vbox2 = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "option-box", "slider-row", "check-row", NULL); gtk_box_set_spacing (GTK_BOX (vbox2), 12); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "option-box", NULL); gtk_widget_show (dialog); /* set old_id's different (to force updates of the previews) */ g_di.enable_preview_update = TRUE; smp_get_colors (NULL); update_preview (g_values.dst); levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW); run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog)); gtk_widget_destroy (dialog); return run; } /* ----------------------------- * DEBUG print procedures START * ----------------------------- */ static void print_ppm (const gchar *ppm_name) { FILE *fp; gint idx; gint cnt; gint r; gint g; gint b; t_samp_color_elem *col_ptr; if (ppm_name == NULL) return; fp = g_fopen (ppm_name, "w"); if (fp) { fprintf (fp, "P3\n# CREATOR: Pika sample coloros\n256 256\n255\n"); for (idx = 0; idx < 256; idx++) { col_ptr = g_lum_tab[idx].col_ptr; for (cnt = 0; cnt < 256; cnt++) { r = g = b = 0; if (col_ptr) { if ((col_ptr->sum_color > 0) && (cnt != 20)) { r = (gint) col_ptr->color[0]; g = (gint) col_ptr->color[1]; b = (gint) col_ptr->color[2]; } if (cnt > 20) col_ptr = col_ptr->next; } fprintf (fp, "%d %d %d\n", r, g, b); } } fclose (fp); } } static void print_color_list (FILE *fp, t_samp_color_elem *col_ptr) { if (fp == NULL) return; while (col_ptr) { fprintf (fp, " RGBA: %03d %03d %03d %03d sum: [%d]\n", (gint)col_ptr->color[0], (gint)col_ptr->color[1], (gint)col_ptr->color[2], (gint)col_ptr->color[3], (gint)col_ptr->sum_color); col_ptr = col_ptr->next; } } static void print_table (FILE *fp) { gint idx; if (fp == NULL) return; fprintf (fp, "---------------------------\n"); fprintf (fp, "print_table\n"); fprintf (fp, "---------------------------\n"); for (idx = 0; idx < 256; idx++) { fprintf (fp, "LUM [%03d] pixcount:%d\n", idx, (int)g_lum_tab[idx].all_samples); print_color_list (fp, g_lum_tab[idx].col_ptr); } } static void print_transtable (FILE *fp) { gint idx; if (fp == NULL) return; fprintf (fp, "---------------------------\n"); fprintf (fp, "print_transtable\n"); fprintf (fp, "---------------------------\n"); for (idx = 0; idx < 256; idx++) { fprintf (fp, "LVL_TRANS [%03d] in_lvl: %3d out_lvl: %3d\n", idx, (int)g_lvl_trans_tab[idx], (int)g_out_trans_tab[idx]); } } /* ----------------------------- * DEBUG print procedures END * ----------------------------- */ /* DEBUG: read values from file */ static void get_filevalues (void) { FILE *fp; gchar buf[1000]; /* g_values.lvl_out_min = 0; g_values.lvl_out_max = 255; g_values.lvl_in_min = 0; g_values.lvl_in_max = 255; g_values.lvl_in_gamma = 1.0; */ g_values.tol_col_err = 5.5; fp = g_fopen ("sample_colorize.values", "r"); if (fp != NULL) { fgets (buf, 999, fp); sscanf (buf, "%f", &g_values.tol_col_err); fclose (fp); } g_printf ("g_values.tol_col_err :%f\n", g_values.tol_col_err); } static gint32 color_error (guchar ref_red, guchar ref_green, guchar ref_blue, guchar cmp_red, guchar cmp_green, guchar cmp_blue) { glong ff; glong fs; glong cmp_h, ref_h; /* 1. Brightness differences */ cmp_h = (3 * cmp_red + 6 * cmp_green + cmp_blue) / 10; ref_h = (3 * ref_red + 6 * ref_green + ref_blue) / 10; fs = labs (ref_h - cmp_h); ff = fs * fs; /* 2. add Red Color differences */ fs = abs (ref_red - cmp_red); ff += (fs * fs); /* 3. add Green Color differences */ fs = abs (ref_green - cmp_green); ff += (fs * fs); /* 4. add Blue Color differences */ fs = abs (ref_blue - cmp_blue); ff += (fs * fs); return ((gint32)(ff)); } /* get pixel value * return light gray transparent pixel if out of bounds * (should occur in the previews only) */ static void get_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *pixel) { gegl_buffer_sample (gdrw->buffer, x, y, NULL, pixel, gdrw->format, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); } /* clear table */ static void clear_tables (void) { guint i; for (i = 0; i < 256; i++) { g_lum_tab[i].col_ptr = NULL; g_lum_tab[i].all_samples = 0; g_lvl_trans_tab[i] = i; g_out_trans_tab[i] = i; g_sample_color_tab[3 * i + 0] = i; g_sample_color_tab[3 * i + 1] = i; g_sample_color_tab[3 * i + 2] = i; } } /* free all allocated sample colors in table g_lum_tab */ static void free_colors (void) { gint lum; t_samp_color_elem *col_ptr; t_samp_color_elem *next_ptr; for (lum = 0; lum < 256; lum++) { for (col_ptr = g_lum_tab[lum].col_ptr; col_ptr != NULL; col_ptr = next_ptr) { next_ptr = (t_samp_color_elem *)col_ptr->next; g_free (col_ptr); } g_lum_tab[lum].col_ptr = NULL; g_lum_tab[lum].all_samples = 0; } } /* setup lum transformer table according to input_levels, gamma and output levels * (uses sam algorithm as PIKA Level Tool) */ static void calculate_level_transfers (void) { double inten; gint i; gint in_min, in_max; gint out_min, out_max; if (g_values.lvl_in_max >= g_values.lvl_in_min) { in_max = g_values.lvl_in_max; in_min = g_values.lvl_in_min; } else { in_max = g_values.lvl_in_min; in_min = g_values.lvl_in_max; } if (g_values.lvl_out_max >= g_values.lvl_out_min) { out_max = g_values.lvl_out_max; out_min = g_values.lvl_out_min; } else { out_max = g_values.lvl_out_min; out_min = g_values.lvl_out_max; } /* Recalculate the levels arrays */ for (i = 0; i < 256; i++) { /* determine input intensity */ inten = (double) i / 255.0; if (g_values.lvl_in_gamma != 0.0) { inten = pow (inten, (1.0 / g_values.lvl_in_gamma)); } inten = (double) (inten * (in_max - in_min) + in_min); inten = CLAMP (inten, 0.0, 255.0); g_lvl_trans_tab[i] = (guchar) (inten + 0.5); /* determine the output intensity */ inten = (double) i / 255.0; inten = (double) (inten * (out_max - out_min) + out_min); inten = CLAMP (inten, 0.0, 255.0); g_out_trans_tab[i] = (guchar) (inten + 0.5); } } /* alloc and init new col Element */ static t_samp_color_elem * new_samp_color (const guchar *color) { t_samp_color_elem *col_ptr; col_ptr = g_new0 (t_samp_color_elem, 1); memcpy (&col_ptr->color[0], color, 4); col_ptr->sum_color = 1; col_ptr->next = NULL; return col_ptr; } /* store color in g_lum_tab */ static void add_color (const guchar *color) { gint32 lum; t_samp_color_elem *col_ptr; lum = LUMINOSITY_1(color); g_lum_tab[lum].all_samples++; g_lum_tab[lum].from_sample = TRUE; /* check if exactly the same color is already in the list */ for (col_ptr = g_lum_tab[lum].col_ptr; col_ptr != NULL; col_ptr = (t_samp_color_elem *)col_ptr->next) { if ((color[0] == col_ptr->color[0]) && (color[1] == col_ptr->color[1]) && (color[2] == col_ptr->color[2])) { col_ptr->sum_color++; return; } } /* alloc and init element for the new color */ col_ptr = new_samp_color (color); if (col_ptr != NULL) { /* add new color element as 1.st of the list */ col_ptr->next = g_lum_tab[lum].col_ptr; g_lum_tab[lum].col_ptr = col_ptr; } } /* sort Sublists (color) by descending sum_color in g_lum_tab */ static void sort_color (gint32 lum) { t_samp_color_elem *col_ptr; t_samp_color_elem *next_ptr; t_samp_color_elem *prev_ptr; t_samp_color_elem *sorted_col_ptr; gint32 min; gint32 min_next; sorted_col_ptr = NULL; min_next = 0; while (g_lum_tab[lum].col_ptr != NULL) { min = min_next; next_ptr = NULL; prev_ptr = NULL; for (col_ptr = g_lum_tab[lum].col_ptr; col_ptr != NULL; col_ptr = next_ptr) { next_ptr = col_ptr->next; if (col_ptr->sum_color > min) { /* check min value for next loop */ if ((col_ptr->sum_color < min_next) || (min == min_next)) { min_next = col_ptr->sum_color; } prev_ptr = col_ptr; } else { /* add element at head of sorted list */ col_ptr->next = sorted_col_ptr; sorted_col_ptr = col_ptr; /* remove element from list */ if (prev_ptr == NULL) { g_lum_tab[lum].col_ptr = next_ptr; /* remove 1.st element */ } else { prev_ptr->next = next_ptr; } } } } g_lum_tab[lum].col_ptr = sorted_col_ptr; } static void cnt_same_sample_colortones (t_samp_color_elem *ref_ptr, guchar *prev_color, guchar *color_tone, gint *csum) { gint32 col_error, ref_error; t_samp_color_elem *col_ptr; ref_error = 0; if (prev_color != NULL) { ref_error = color_error (ref_ptr->color[0], ref_ptr->color[1], ref_ptr->color[2], prev_color[0], prev_color[1], prev_color[2]); } /* collect colors that are (nearly) the same */ for (col_ptr = ref_ptr->next; col_ptr != NULL; col_ptr = (t_samp_color_elem *)col_ptr->next) { if (col_ptr->sum_color < 1) continue; col_error = color_error (ref_ptr->color[0], ref_ptr->color[1], ref_ptr->color[2], col_ptr->color[0], col_ptr->color[1], col_ptr->color[2]); if (col_error <= g_tol_col_err) { /* cout color of the same colortone */ *csum += col_ptr->sum_color; /* mark the already checked color with negative sum_color value */ col_ptr->sum_color = 0 - col_ptr->sum_color; if (prev_color != NULL) { col_error = color_error (col_ptr->color[0], col_ptr->color[1], col_ptr->color[2], prev_color[0], prev_color[1], prev_color[2]); if (col_error < ref_error) { /* use the color that is closest to prev_color */ memcpy (color_tone, &col_ptr->color[0], 3); ref_error = col_error; } } } } } /* find the dominant colortones (out of all sample colors) * for each available brightness intensity value. * and store them in g_sample_color_tab */ static void ideal_samples (void) { gint32 lum; t_samp_color_elem *col_ptr; guchar *color; guchar color_tone[4]; guchar color_ideal[4]; gint csum, maxsum; color = NULL; for (lum = 0; lum < 256; lum++) { if (g_lum_tab[lum].col_ptr == NULL) continue; sort_color (lum); col_ptr = g_lum_tab[lum].col_ptr; memcpy (&color_ideal[0], &col_ptr->color[0], 3); maxsum = 0; /* collect colors that are (nearly) the same */ for (; col_ptr != NULL; col_ptr = (t_samp_color_elem *)col_ptr->next) { csum = 0; if (col_ptr->sum_color > 0) { memcpy (&color_tone[0], &col_ptr->color[0], 3); cnt_same_sample_colortones (col_ptr, color, &color_tone[0], &csum); if (csum > maxsum) { maxsum = csum; memcpy (&color_ideal[0], &color_tone[0], 3); } } else col_ptr->sum_color = abs (col_ptr->sum_color); } /* store ideal color and keep track of the color */ color = &g_sample_color_tab[3 * lum]; memcpy (color, &color_ideal[0], 3); } } static void guess_missing_colors (void) { gint32 lum; gint32 idx; gfloat div; guchar lo_color[4]; guchar hi_color[4]; guchar new_color[4]; lo_color[0] = 0; lo_color[1] = 0; lo_color[2] = 0; lo_color[3] = 255; hi_color[0] = 255; hi_color[1] = 255; hi_color[2] = 255; hi_color[3] = 255; new_color[0] = 0; new_color[1] = 0; new_color[2] = 0; new_color[3] = 255; for (lum = 0; lum < 256; lum++) { if ((g_lum_tab[lum].col_ptr == NULL) || (g_lum_tab[lum].from_sample == FALSE)) { if (lum > 0) { for (idx = lum; idx < 256; idx++) { if ((g_lum_tab[idx].col_ptr != NULL) && g_lum_tab[idx].from_sample) { memcpy (&hi_color[0], &g_sample_color_tab[3 * idx], 3); break; } if (idx == 255) { hi_color[0] = 255; hi_color[1] = 255; hi_color[2] = 255; break; } } div = idx - (lum -1); new_color[0] = lo_color[0] + ((float)(hi_color[0] - lo_color[0]) / div); new_color[1] = lo_color[1] + ((float)(hi_color[1] - lo_color[1]) / div); new_color[2] = lo_color[2] + ((float)(hi_color[2] - lo_color[2]) / div); /* * printf ("LO: %03d %03d %03d HI: %03d %03d %03d NEW: %03d %03d %03d\n", * (int)lo_color[0], (int)lo_color[1], (int)lo_color[2], * (int)hi_color[0], (int)hi_color[1], (int)hi_color[2], * (int)new_color[0], (int)new_color[1], (int)new_color[2]); */ } g_lum_tab[lum].col_ptr = new_samp_color (&new_color[0]); g_lum_tab[lum].from_sample = FALSE; memcpy (&g_sample_color_tab [3 * lum], &new_color[0], 3); } memcpy (&lo_color[0], &g_sample_color_tab [3 * lum], 3); } } static void fill_missing_colors (void) { gint32 lum; gint32 idx; gint32 lo_idx; guchar lo_color[4]; guchar hi_color[4]; guchar new_color[4]; lo_color[0] = 0; lo_color[1] = 0; lo_color[2] = 0; lo_color[3] = 255; hi_color[0] = 255; hi_color[1] = 255; hi_color[2] = 255; hi_color[3] = 255; new_color[0] = 0; new_color[1] = 0; new_color[2] = 0; new_color[3] = 255; lo_idx = 0; for (lum = 0; lum < 256; lum++) { if ((g_lum_tab[lum].col_ptr == NULL) || (g_lum_tab[lum].from_sample == FALSE)) { if (lum > 0) { for (idx = lum; idx < 256; idx++) { if ((g_lum_tab[idx].col_ptr != NULL) && (g_lum_tab[idx].from_sample)) { memcpy (&hi_color[0], &g_sample_color_tab[3 * idx], 3); break; } if (idx == 255) { /* * hi_color[0] = 255; * hi_color[1] = 255; * hi_color[2] = 255; */ memcpy (&hi_color[0], &lo_color[0], 3); break; } } if ((lum > (lo_idx + ((idx - lo_idx ) / 2))) || (lo_idx == 0)) { new_color[0] = hi_color[0]; new_color[1] = hi_color[1]; new_color[2] = hi_color[2]; } else { new_color[0] = lo_color[0]; new_color[1] = lo_color[1]; new_color[2] = lo_color[2]; } } g_lum_tab[lum].col_ptr = new_samp_color (&new_color[0]); g_lum_tab[lum].from_sample = FALSE; memcpy (&g_sample_color_tab[3 * lum], &new_color[0], 3); } else { lo_idx = lum; memcpy (&lo_color[0], &g_sample_color_tab[3 * lum], 3); } } } /* get 256 samples of active gradient (optional in inverse order) */ static void get_gradient (gint mode) { PikaGradient *gradient; gint n_f_samples; gdouble *f_samples; gdouble *f_samp; /* float samples */ gint lum; free_colors (); gradient = pika_context_get_gradient (); pika_gradient_get_uniform_samples (gradient, 256 /* n_samples */, mode == SMP_INV_GRADIENT, &n_f_samples, &f_samples); for (lum = 0; lum < 256; lum++) { f_samp = &f_samples[lum * 4]; g_sample_color_tab[3 * lum + 0] = f_samp[0] * 255; g_sample_color_tab[3 * lum + 1] = f_samp[1] * 255; g_sample_color_tab[3 * lum + 2] = f_samp[2] * 255; g_lum_tab[lum].col_ptr = new_samp_color (&g_sample_color_tab[3 * lum]); g_lum_tab[lum].from_sample = TRUE; g_lum_tab[lum].all_samples = 1; } g_free (f_samples); } static void end_gdrw (t_GDRW *gdrw) { t_GDRW *sel_gdrw = (t_GDRW *) gdrw->sel_gdrw; if (sel_gdrw && sel_gdrw->buffer) { g_object_unref (sel_gdrw->buffer); sel_gdrw->buffer = NULL; } g_object_unref (gdrw->buffer); gdrw->buffer = NULL; } static void init_gdrw (t_GDRW *gdrw, PikaDrawable *drawable, gboolean shadow) { PikaImage *image; PikaDrawable *sel_channel; gint32 x1, x2, y1, y2; gint offsetx, offsety; gint w, h; gint sel_offsetx, sel_offsety; t_GDRW *sel_gdrw; gint32 non_empty; gdrw->drawable = drawable; if (shadow) gdrw->buffer = pika_drawable_get_shadow_buffer (drawable); else gdrw->buffer = pika_drawable_get_buffer (drawable); gdrw->width = pika_drawable_get_width (drawable); gdrw->height = pika_drawable_get_height (drawable); gdrw->tile_width = pika_tile_width (); gdrw->tile_height = pika_tile_height (); gdrw->shadow = shadow; gdrw->seldeltax = 0; gdrw->seldeltay = 0; /* get offsets within the image */ pika_drawable_get_offsets (gdrw->drawable, &offsetx, &offsety); if (! pika_drawable_mask_intersect (gdrw->drawable, &gdrw->x1, &gdrw->y1, &w, &h)) return; gdrw->x2 = gdrw->x1 + w; gdrw->y2 = gdrw->y1 + h; if (pika_drawable_has_alpha (drawable)) gdrw->format = babl_format ("R'G'B'A u8"); else gdrw->format = babl_format ("R'G'B' u8"); gdrw->bpp = babl_format_get_bytes_per_pixel (gdrw->format); if (pika_drawable_has_alpha (drawable)) { /* index of the alpha channelbyte {1|3} */ gdrw->index_alpha = gdrw->bpp -1; } else { gdrw->index_alpha = 0; /* there is no alpha channel */ } image = pika_item_get_image (PIKA_ITEM (gdrw->drawable)); /* check and see if we have a selection mask */ sel_channel = PIKA_DRAWABLE (pika_image_get_selection (image)); pika_selection_bounds (image, &non_empty, &x1, &y1, &x2, &y2); if (non_empty && sel_channel) { /* selection is TRUE */ sel_gdrw = g_new0 (t_GDRW, 1); sel_gdrw->drawable = sel_channel; sel_gdrw->buffer = pika_drawable_get_buffer (sel_channel); sel_gdrw->format = babl_format ("Y u8"); sel_gdrw->width = pika_drawable_get_width (sel_channel); sel_gdrw->height = pika_drawable_get_height (sel_channel); sel_gdrw->tile_width = pika_tile_width (); sel_gdrw->tile_height = pika_tile_height (); sel_gdrw->shadow = shadow; sel_gdrw->x1 = x1; sel_gdrw->y1 = y1; sel_gdrw->x2 = x2; sel_gdrw->y2 = y2; sel_gdrw->seldeltax = 0; sel_gdrw->seldeltay = 0; sel_gdrw->bpp = babl_format_get_bytes_per_pixel (sel_gdrw->format); sel_gdrw->index_alpha = 0; /* there is no alpha channel */ sel_gdrw->sel_gdrw = NULL; /* offset delta between drawable and selection * (selection always has image size and should always have offsets of 0 ) */ pika_drawable_get_offsets (sel_channel, &sel_offsetx, &sel_offsety); gdrw->seldeltax = offsetx - sel_offsetx; gdrw->seldeltay = offsety - sel_offsety; gdrw->sel_gdrw = (t_GDRW *) sel_gdrw; if (g_Sdebug) { g_printf ("init_gdrw: SEL_BOUNDS x1: %d y1: %d x2:%d y2: %d\n", (int)sel_gdrw->x1, (int)sel_gdrw->y1, (int)sel_gdrw->x2, (int)sel_gdrw->y2); g_printf ("init_gdrw: SEL_OFFS x: %d y: %d\n", (int)sel_offsetx, (int)sel_offsety ); g_printf ("init_gdrw: SEL_DELTA x: %d y: %d\n", (int)gdrw->seldeltax, (int)gdrw->seldeltay ); } } else gdrw->sel_gdrw = NULL; /* selection is FALSE */ } /* analyze the colors in the sample_drawable */ static int sample_analyze (t_GDRW *sample_gdrw) { gint32 sample_pixels; gint32 row, col; gint32 first_row, first_col, last_row, last_col; gint32 x, y; gint32 x2, y2; float progress_step; float progress_max; float progress; guchar color[4]; FILE *prot_fp; sample_pixels = 0; /* init progress */ progress_max = (sample_gdrw->x2 - sample_gdrw->x1); progress_step = 1.0 / progress_max; progress = 0.0; if (g_show_progress) pika_progress_init (_("Sample analyze")); prot_fp = NULL; if (g_Sdebug) prot_fp = g_fopen ("sample_colors.dump", "w"); /* ------------------------------------------------ * foreach pixel in the SAMPLE_drawable: * calculate brightness intensity LUM * ------------------------------------------------ * the inner loops (x/y) are designed to process * all pixels of one tile in the sample drawable, the outer loops (row/col) do step * to the next tiles. (this was done to reduce tile swapping) */ first_row = sample_gdrw->y1 / sample_gdrw->tile_height; last_row = (sample_gdrw->y2 / sample_gdrw->tile_height); first_col = sample_gdrw->x1 / sample_gdrw->tile_width; last_col = (sample_gdrw->x2 / sample_gdrw->tile_width); for (row = first_row; row <= last_row; row++) { for (col = first_col; col <= last_col; col++) { if (col == first_col) x = sample_gdrw->x1; else x = col * sample_gdrw->tile_width; if (col == last_col) x2 = sample_gdrw->x2; else x2 = (col +1) * sample_gdrw->tile_width; for ( ; x < x2; x++) { if (row == first_row) y = sample_gdrw->y1; else y = row * sample_gdrw->tile_height; if (row == last_row) y2 = sample_gdrw->y2; else y2 = (row +1) * sample_gdrw->tile_height ; /* printf ("X: %4d Y:%4d Y2:%4d\n", (int)x, (int)y, (int)y2); */ for ( ; y < y2; y++) { /* check if the pixel is in the selection */ if (sample_gdrw->sel_gdrw) { get_pixel (sample_gdrw->sel_gdrw, (x + sample_gdrw->seldeltax), (y + sample_gdrw->seldeltay), &color[0]); if (color[0] == 0) continue; } get_pixel (sample_gdrw, x, y, &color[0]); /* if this is a visible (non-transparent) pixel */ if ((sample_gdrw->index_alpha < 1) || (color[sample_gdrw->index_alpha] != 0)) { /* store color in the sublists of g_lum_tab */ add_color (&color[0]); sample_pixels++; } } if (g_show_progress) pika_progress_update (progress += progress_step); } } } if (g_show_progress) pika_progress_update (1.0); if (g_Sdebug) g_printf ("ROWS: %d - %d COLS: %d - %d\n", (int)first_row, (int)last_row, (int)first_col, (int)last_col); print_table (prot_fp); if (g_Sdebug) print_ppm ("sample_color_all.ppm"); /* find out ideal sample colors for each brightness intensity (lum) * and set g_sample_color_tab to the ideal colors. */ ideal_samples (); calculate_level_transfers (); if (g_values.guess_missing) guess_missing_colors (); else fill_missing_colors (); print_table (prot_fp); if (g_Sdebug) print_ppm ("sample_color_2.ppm"); print_transtable (prot_fp); if (prot_fp) fclose (prot_fp); /* check if there was at least one visible pixel */ if (sample_pixels == 0) { g_printf ("Error: Source sample has no visible Pixel\n"); return -1; } return 0; } static void rnd_remap (gint32 lum, guchar *mapped_color) { t_samp_color_elem *col_ptr; gint rnd; gint ct; gint idx; if (g_lum_tab[lum].all_samples > 1) { rnd = g_random_int_range (0, g_lum_tab[lum].all_samples); ct = 0; idx = 0; for (col_ptr = g_lum_tab[lum].col_ptr; col_ptr != NULL; col_ptr = (t_samp_color_elem *)col_ptr->next) { ct += col_ptr->sum_color; if (rnd < ct) { /* printf ("RND_remap: rnd: %d all:%d ct:%d idx:%d\n", * rnd, (int)g_lum_tab[lum].all_samples, ct, idx); */ memcpy (mapped_color, &col_ptr->color[0], 3); return; } idx++; } } memcpy (mapped_color, &g_sample_color_tab[lum + lum + lum], 3); } static void remap_pixel (guchar *pixel, const guchar *original, gint bpp2, PikaProcedureConfig *config) { guchar mapped_color[4]; gint lum; gdouble orig_lum, mapped_lum; gdouble grn, red, blu; gdouble mg, mr, mb; gdouble dg, dr, db; gdouble dlum; gboolean hold_inten = FALSE; gboolean orig_inten = FALSE; gboolean rnd_subcolors = FALSE; g_object_get (config, "hold-inten", &hold_inten, "orig-inten", &orig_inten, "rnd-subcolors", &rnd_subcolors, NULL); /* get brightness from (uncolorized) original */ lum = g_out_trans_tab[g_lvl_trans_tab[LUMINOSITY_1 (original)]]; if (rnd_subcolors) rnd_remap (lum, mapped_color); else memcpy (mapped_color, &g_sample_color_tab[3 * lum], 3); if (hold_inten) { if (orig_inten) orig_lum = LUMINOSITY_0(original); else orig_lum = 100.0 * g_lvl_trans_tab[LUMINOSITY_1 (original)]; mapped_lum = LUMINOSITY_0 (mapped_color); if (mapped_lum == 0) { /* convert black to greylevel with desired brightness value */ mapped_color[0] = orig_lum / 100.0; mapped_color[1] = mapped_color[0]; mapped_color[2] = mapped_color[0]; } else { /* Calculate theoretical RGB to reach given intensity LUM * value (orig_lum) */ mr = mapped_color[0]; mg = mapped_color[1]; mb = mapped_color[2]; if (mr > 0.0) { red = orig_lum / (30 + (59 * mg / mr) + (11 * mb / mr)); grn = mg * red / mr; blu = mb * red / mr; } else if (mg > 0.0) { grn = orig_lum / ((30 * mr / mg) + 59 + (11 * mb / mg)); red = mr * grn / mg; blu = mb * grn / mg; } else { blu = orig_lum / ((30 * mr / mb) + (59 * mg / mb) + 11); grn = mg * blu / mb; red = mr * blu / mb; } /* on overflow: Calculate real RGB values * (this may change the hue and saturation, * more and more into white) */ if (red > 255) { if ((blu < 255) && (grn < 255)) { /* overflow in the red channel (compensate with green and blue) */ dlum = (red - 255.0) * 30.0; if (mg > 0) { dg = dlum / (59.0 + (11.0 * mb / mg)); db = dg * mb / mg; } else if (mb > 0) { db = dlum / (11.0 + (59.0 * mg / mb)); dg = db * mg / mb; } else { db = dlum / (11.0 + 59.0); dg = dlum / (59.0 + 11.0); } grn += dg; blu += db; } red = 255.0; if (grn > 255) { grn = 255.0; blu = (orig_lum - 22695) / 11; /* 22695 = (255 * 30) + (255 * 59) */ } if (blu > 255) { blu = 255.0; grn = (orig_lum - 10455) / 59; /* 10455 = (255 * 30) + (255 * 11) */ } } else if (grn > 255) { if ((blu < 255) && (red < 255)) { /* overflow in the green channel (compensate with red and blue) */ dlum = (grn - 255.0) * 59.0; if (mr > 0) { dr = dlum / (30.0 + (11.0 * mb / mr)); db = dr * mb / mr; } else if (mb > 0) { db = dlum / (11.0 + (30.0 * mr / mb)); dr = db * mr / mb; } else { db = dlum / (11.0 + 30.0); dr = dlum / (30.0 + 11.0); } red += dr; blu += db; } grn = 255.0; if (red > 255) { red = 255.0; blu = (orig_lum - 22695) / 11; /* 22695 = (255*59) + (255*30) */ } if (blu > 255) { blu = 255.0; red = (orig_lum - 17850) / 30; /* 17850 = (255*59) + (255*11) */ } } else if (blu > 255) { if ((red < 255) && (grn < 255)) { /* overflow in the blue channel (compensate with green and red) */ dlum = (blu - 255.0) * 11.0; if (mg > 0) { dg = dlum / (59.0 + (30.0 * mr / mg)); dr = dg * mr / mg; } else if (mr > 0) { dr = dlum / (30.0 + (59.0 * mg / mr)); dg = dr * mg / mr; } else { dr = dlum / (30.0 + 59.0); dg = dlum / (59.0 + 30.0); } grn += dg; red += dr; } blu = 255.0; if (grn > 255) { grn = 255.0; red = (orig_lum - 17850) / 30; /* 17850 = (255*11) + (255*59) */ } if (red > 255) { red = 255.0; grn = (orig_lum - 10455) / 59; /* 10455 = (255*11) + (255*30) */ } } mapped_color[0] = CLAMP0255 (red + 0.5); mapped_color[1] = CLAMP0255 (grn + 0.5); mapped_color[2] = CLAMP0255 (blu + 0.5); } } /* set colorized pixel in shadow pr */ memcpy (pixel, &mapped_color[0], bpp2); } static void colorize_func (const guchar *src, guchar *dest, gint bpp, gboolean has_alpha, PikaProcedureConfig *config) { if (has_alpha) { bpp--; dest[bpp] = src[bpp]; } remap_pixel (dest, src, bpp, config); } static void colorize_drawable (PikaDrawable *drawable, PikaProcedureConfig *config) { GeglBuffer *src_buffer; GeglBuffer *dest_buffer; GeglBufferIterator *iter; const Babl *format; gboolean has_alpha; gint bpp; gint x, y, w, h; gint total_area; gint area_so_far; if (! pika_drawable_mask_intersect (drawable, &x, &y, &w, &h)) return; src_buffer = pika_drawable_get_buffer (drawable); dest_buffer = pika_drawable_get_shadow_buffer (drawable); has_alpha = pika_drawable_has_alpha (drawable); if (has_alpha) format = babl_format ("R'G'B'A u8"); else format = babl_format ("R'G'B' u8"); bpp = babl_format_get_bytes_per_pixel (format); if (g_show_progress) pika_progress_init (_("Remap colorized")); total_area = w * h; area_so_far = 0; if (total_area <= 0) goto out; iter = gegl_buffer_iterator_new (src_buffer, GEGL_RECTANGLE (x, y, w, h), 0, format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2); gegl_buffer_iterator_add (iter, dest_buffer, GEGL_RECTANGLE (x, y, w, h), 0 , format, GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE); while (gegl_buffer_iterator_next (iter)) { const guchar *src = iter->items[0].data; guchar *dest = iter->items[1].data; gint row; for (row = 0; row < iter->items[0].roi.height; row++) { const guchar *s = src; guchar *d = dest; gint pixels = iter->items[0].roi.width; while (pixels--) { colorize_func (s, d, bpp, has_alpha, config); s += bpp; d += bpp; } src += iter->items[0].roi.width * bpp; dest += iter->items[1].roi.width * bpp; } area_so_far += iter->items[0].roi.width * iter->items[0].roi.height; pika_progress_update ((gdouble) area_so_far / (gdouble) total_area); } g_object_unref (src_buffer); g_object_unref (dest_buffer); pika_drawable_merge_shadow (drawable, TRUE); pika_drawable_update (drawable, x, y, w, h); out: if (g_show_progress) pika_progress_update (0.0); } /* colorize dst_drawable like sample_drawable */ static int main_colorize (gint mc_flags, PikaProcedureConfig *config) { t_GDRW sample_gdrw; gboolean sample_drawable = FALSE; gint32 max; gint32 id; gint rc; if (g_Sdebug) get_filevalues (); /* for debugging: read values from file */ /* calculate value of tolerable color error */ max = color_error (0,0, 0, 255, 255, 255); /* 260100 */ g_tol_col_err = (((float)max * (g_values.tol_col_err * g_values.tol_col_err)) / (100.0 *100.0)); g_max_col_err = max; rc = 0; if (mc_flags & MC_GET_SAMPLE_COLORS) { id = g_values.sample_id; if ((id == SMP_GRADIENT) || (id == SMP_INV_GRADIENT)) { get_gradient (id); } else { sample_drawable = TRUE; init_gdrw (&sample_gdrw, pika_drawable_get_by_id (id), FALSE); free_colors (); rc = sample_analyze (&sample_gdrw); } } if ((mc_flags & MC_DST_REMAP) && (rc == 0)) { if (pika_drawable_is_gray (g_values.dst) && (mc_flags & MC_DST_REMAP)) { pika_image_convert_rgb (pika_item_get_image (PIKA_ITEM (g_values.dst))); } colorize_drawable (g_values.dst, config); } if (sample_drawable) end_gdrw (&sample_gdrw); return rc; }