/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2009 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "gth-file-tool-adjust-colors.h" #include "gth-preview-tool.h" #define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x))) #define APPLY_DELAY 150 #define PREVIEW_SIZE 0.9 struct _GthFileToolAdjustColorsPrivate { cairo_surface_t *destination; cairo_surface_t *preview; GtkBuilder *builder; GtkAdjustment *gamma_adj; GtkAdjustment *brightness_adj; GtkAdjustment *contrast_adj; GtkAdjustment *saturation_adj; GtkAdjustment *cyan_red_adj; GtkAdjustment *magenta_green_adj; GtkAdjustment *yellow_blue_adj; GtkWidget *histogram_view; GthHistogram *histogram; GthTask *image_task; guint apply_event; GthImageViewerTool *preview_tool; gboolean apply_to_original; gboolean closing; }; G_DEFINE_TYPE_WITH_CODE (GthFileToolAdjustColors, gth_file_tool_adjust_colors, GTH_TYPE_IMAGE_VIEWER_PAGE_TOOL, G_ADD_PRIVATE (GthFileToolAdjustColors)) typedef struct { GthFileToolAdjustColors *self; GthViewerPage *viewer_page; double gamma; double brightness; double contrast; double saturation; double color_level[3]; PixbufCache *cache; double midtone_distance[256]; } AdjustData; static void adjust_colors_before (GthAsyncTask *task, gpointer user_data) { AdjustData *adjust_data = user_data; int i; adjust_data->cache = pixbuf_cache_new (); for (i = 0; i < 256; i++) adjust_data->midtone_distance[i] = 0.667 * (1 - SQR (((double) i - 127.0) / 127.0)); } static guchar gamma_correction (int original, double gamma) { double inten; inten = (double) original / 255.0; if (gamma != 0.0) { if (inten >= 0.0) inten = pow ( inten, (1.0 / gamma)); else inten = -pow (-inten, (1.0 / gamma)); } return CLAMP (inten * 255.0, 0, 255); } static gpointer adjust_colors_exec (GthAsyncTask *task, gpointer user_data) { AdjustData *adjust_data = user_data; cairo_surface_t *source; cairo_format_t format; int width; int height; int source_stride; int destination_stride; unsigned char *p_source_line; unsigned char *p_destination_line; unsigned char *p_source; unsigned char *p_destination; gboolean cancelled; double progress; int x, y, temp; int values[4]; int channel; int value; double saturation; cairo_surface_t *destination; if (adjust_data->saturation < 0) saturation = tan (adjust_data->saturation * G_PI_2); else saturation = adjust_data->saturation; source = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task)); format = cairo_image_surface_get_format (source); width = cairo_image_surface_get_width (source); height = cairo_image_surface_get_height (source); source_stride = cairo_image_surface_get_stride (source); destination = cairo_image_surface_create (format, width, height); destination_stride = cairo_image_surface_get_stride (destination); p_source_line = _cairo_image_surface_flush_and_get_data (source); p_destination_line = _cairo_image_surface_flush_and_get_data (destination); for (y = 0; y < height; y++) { gth_async_task_get_data (task, NULL, &cancelled, NULL); if (cancelled) return NULL; progress = (double) y / height; gth_async_task_set_data (task, NULL, NULL, &progress); p_source = p_source_line; p_destination = p_destination_line; for (x = 0; x < width; x++) { CAIRO_GET_RGBA (p_source, values[0], values[1], values[2], values[3]); /* gamma correction / brightness / contrast */ for (channel = 0; channel < 3; channel++) { value = values[channel]; if (! pixbuf_cache_get (adjust_data->cache, channel + 1, &value)) { int tmp; if (adjust_data->gamma != 0.0) value = gamma_correction (value, adjust_data->gamma); if (adjust_data->brightness > 0) tmp = interpolate_value (value, 0, adjust_data->brightness); else tmp = interpolate_value (value, 255, - adjust_data->brightness); value = CLAMP (tmp, 0, 255); if (adjust_data->contrast < 0) tmp = interpolate_value (value, 127, tan (adjust_data->contrast * G_PI_2)); else tmp = interpolate_value (value, 127, adjust_data->contrast); value = CLAMP (tmp, 0, 255); tmp = value + adjust_data->color_level[channel] * adjust_data->midtone_distance[value]; value = CLAMP (tmp, 0, 255); pixbuf_cache_set (adjust_data->cache, channel + 1, values[channel], value); } values[channel] = value; } /* saturation */ if (adjust_data->saturation != 0.0) { guchar min, max, lightness; int tmp; max = MAX (MAX (values[0], values[1]), values[2]); min = MIN (MIN (values[0], values[1]), values[2]); lightness = (max + min) / 2; tmp = interpolate_value (values[0], lightness, saturation); values[0] = CLAMP (tmp, 0, 255); tmp = interpolate_value (values[1], lightness, saturation); values[1] = CLAMP (tmp, 0, 255); tmp = interpolate_value (values[2], lightness, saturation); values[2] = CLAMP (tmp, 0, 255); } CAIRO_SET_RGBA (p_destination, values[0], values[1], values[2], values[3]); p_source += 4; p_destination += 4; } p_source_line += source_stride; p_destination_line += destination_stride; } cairo_surface_mark_dirty (destination); gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), destination); cairo_surface_destroy (destination); cairo_surface_destroy (source); return NULL; } static void adjust_data_free (gpointer user_data) { AdjustData *adjust_data = user_data; pixbuf_cache_free (adjust_data->cache); g_object_unref (adjust_data->viewer_page); g_free (adjust_data); } static void reset_button_clicked_cb (GtkButton *button, gpointer user_data) { GthFileToolAdjustColors *self = user_data; gtk_adjustment_set_value (self->priv->gamma_adj, 0.0); gtk_adjustment_set_value (self->priv->brightness_adj, 0.0); gtk_adjustment_set_value (self->priv->contrast_adj, 0.0); gtk_adjustment_set_value (self->priv->saturation_adj, 0.0); gtk_adjustment_set_value (self->priv->cyan_red_adj, 0.0); gtk_adjustment_set_value (self->priv->magenta_green_adj, 0.0); gtk_adjustment_set_value (self->priv->yellow_blue_adj, 0.0); } static void apply_changes (GthFileToolAdjustColors *self); static void image_task_completed_cb (GthTask *task, GError *error, gpointer user_data) { GthFileToolAdjustColors *self = user_data; GthImage *destination_image; self->priv->image_task = NULL; if (self->priv->closing) { g_object_unref (task); gth_image_viewer_page_tool_reset_image (GTH_IMAGE_VIEWER_PAGE_TOOL (self)); return; } if (error != NULL) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) apply_changes (self); g_object_unref (task); return; } destination_image = gth_image_task_get_destination (GTH_IMAGE_TASK (task)); if (destination_image == NULL) { g_object_unref (task); return; } cairo_surface_destroy (self->priv->destination); self->priv->destination = gth_image_get_cairo_surface (destination_image); if (self->priv->apply_to_original) { if (self->priv->destination != NULL) { GthViewerPage *viewer_page; viewer_page = gth_image_viewer_page_tool_get_page (GTH_IMAGE_VIEWER_PAGE_TOOL (self)); gth_image_viewer_page_set_image (GTH_IMAGE_VIEWER_PAGE (viewer_page), self->priv->destination, TRUE); } gth_file_tool_hide_options (GTH_FILE_TOOL (self)); } else { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("preview_checkbutton")))) gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->destination); gth_histogram_calculate_for_image (self->priv->histogram, self->priv->destination); } g_object_unref (task); } static gboolean apply_cb (gpointer user_data) { GthFileToolAdjustColors *self = user_data; GtkWidget *window; AdjustData *adjust_data; if (self->priv->apply_event != 0) { g_source_remove (self->priv->apply_event); self->priv->apply_event = 0; } if (self->priv->image_task != NULL) { gth_task_cancel (self->priv->image_task); return FALSE; } window = gth_file_tool_get_window (GTH_FILE_TOOL (self)); adjust_data = g_new0 (AdjustData, 1); adjust_data->self = self; adjust_data->viewer_page = g_object_ref (gth_browser_get_viewer_page (GTH_BROWSER (window))); adjust_data->gamma = pow (10, - (gtk_adjustment_get_value (self->priv->gamma_adj) / 100.0)); adjust_data->brightness = gtk_adjustment_get_value (self->priv->brightness_adj) / 100.0 * -1.0; adjust_data->contrast = gtk_adjustment_get_value (self->priv->contrast_adj) / 100.0 * -1.0; adjust_data->saturation = gtk_adjustment_get_value (self->priv->saturation_adj) / 100.0 * -1.0; adjust_data->color_level[0] = gtk_adjustment_get_value (self->priv->cyan_red_adj); adjust_data->color_level[1] = gtk_adjustment_get_value (self->priv->magenta_green_adj); adjust_data->color_level[2] = gtk_adjustment_get_value (self->priv->yellow_blue_adj); self->priv->image_task = gth_image_task_new (_("Applying changes"), adjust_colors_before, adjust_colors_exec, NULL, adjust_data, adjust_data_free); if (self->priv->apply_to_original) gth_image_task_set_source_surface (GTH_IMAGE_TASK (self->priv->image_task), gth_image_viewer_page_tool_get_source (GTH_IMAGE_VIEWER_PAGE_TOOL (self))); else gth_image_task_set_source_surface (GTH_IMAGE_TASK (self->priv->image_task), self->priv->preview); g_signal_connect (self->priv->image_task, "completed", G_CALLBACK (image_task_completed_cb), self); gth_browser_exec_task (GTH_BROWSER (window), self->priv->image_task, GTH_TASK_FLAGS_DEFAULT); return FALSE; } static void apply_changes (GthFileToolAdjustColors *self) { if (self->priv->apply_event != 0) { g_source_remove (self->priv->apply_event); self->priv->apply_event = 0; } self->priv->apply_event = g_timeout_add (APPLY_DELAY, apply_cb, self); } static void value_changed_cb (GtkAdjustment *adj, gpointer user_data) { apply_changes (GTH_FILE_TOOL_ADJUST_COLORS (user_data)); } static void preview_checkbutton_toggled_cb (GtkToggleButton *togglebutton, gpointer user_data) { GthFileToolAdjustColors *self = user_data; if (gtk_toggle_button_get_active (togglebutton)) gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->destination); else gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview); } static GtkWidget * gth_file_tool_adjust_colors_get_options (GthFileTool *base) { GthFileToolAdjustColors *self; GthViewerPage *viewer_page; GtkWidget *viewer; cairo_surface_t *source; GtkWidget *options; int width, height; GtkAllocation allocation; self = (GthFileToolAdjustColors *) base; viewer_page = gth_image_viewer_page_tool_get_page (GTH_IMAGE_VIEWER_PAGE_TOOL (self)); if (viewer_page == NULL) return NULL; _cairo_clear_surface (&self->priv->destination); _cairo_clear_surface (&self->priv->preview); source = gth_image_viewer_page_tool_get_source (GTH_IMAGE_VIEWER_PAGE_TOOL (self)); if (source == NULL) return NULL; width = cairo_image_surface_get_width (source); height = cairo_image_surface_get_height (source); viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page)); gtk_widget_get_allocation (GTK_WIDGET (viewer), &allocation); if (scale_keeping_ratio (&width, &height, PREVIEW_SIZE * allocation.width, PREVIEW_SIZE * allocation.height, FALSE)) self->priv->preview = _cairo_image_surface_scale_fast (source, width, height); else self->priv->preview = cairo_surface_reference (source); self->priv->destination = cairo_surface_reference (self->priv->preview); self->priv->apply_to_original = FALSE; self->priv->closing = FALSE; self->priv->builder = _gtk_builder_new_from_file ("adjust-colors-options.ui", "file_tools"); options = _gtk_builder_get_widget (self->priv->builder, "options"); gtk_widget_show (options); self->priv->histogram_view = gth_histogram_view_new (self->priv->histogram); gtk_widget_show (self->priv->histogram_view); gtk_box_pack_start (GTK_BOX (GET_WIDGET ("histogram_hbox")), self->priv->histogram_view, TRUE, TRUE, 0); self->priv->brightness_adj = gth_color_scale_label_new (GET_WIDGET ("brightness_hbox"), GTK_LABEL (GET_WIDGET ("brightness_label")), GTH_COLOR_SCALE_BLACK_WHITE, 0.0, -99.0, 99.0, 1.0, 1.0, "%+.0f"); self->priv->contrast_adj = gth_color_scale_label_new (GET_WIDGET ("contrast_hbox"), GTK_LABEL (GET_WIDGET ("contrast_label")), GTH_COLOR_SCALE_GRAY_BLACK, 0.0, -99.0, 99.0, 1.0, 1.0, "%+.0f"); self->priv->gamma_adj = gth_color_scale_label_new (GET_WIDGET ("gamma_hbox"), GTK_LABEL (GET_WIDGET ("gamma_label")), GTH_COLOR_SCALE_WHITE_BLACK, 0.0, -99.0, 99.0, 1.0, 1.0, "%+.0f"); self->priv->saturation_adj = gth_color_scale_label_new (GET_WIDGET ("saturation_hbox"), GTK_LABEL (GET_WIDGET ("saturation_label")), GTH_COLOR_SCALE_GRAY_WHITE, 0.0, -99.0, 99.0, 1.0, 1.0, "%+.0f"); self->priv->cyan_red_adj = gth_color_scale_label_new (GET_WIDGET ("cyan_red_hbox"), GTK_LABEL (GET_WIDGET ("cyan_red_label")), GTH_COLOR_SCALE_CYAN_RED, 0.0, -99.0, 99.0, 1.0, 1.0, "%+.0f"); self->priv->magenta_green_adj = gth_color_scale_label_new (GET_WIDGET ("magenta_green_hbox"), GTK_LABEL (GET_WIDGET ("magenta_green_label")), GTH_COLOR_SCALE_MAGENTA_GREEN, 0.0, -99.0, 99.0, 1.0, 1.0, "%+.0f"); self->priv->yellow_blue_adj = gth_color_scale_label_new (GET_WIDGET ("yellow_blue_hbox"), GTK_LABEL (GET_WIDGET ("yellow_blue_label")), GTH_COLOR_SCALE_YELLOW_BLUE, 0.0, -99.0, 99.0, 1.0, 1.0, "%+.0f"); g_signal_connect (G_OBJECT (self->priv->brightness_adj), "value-changed", G_CALLBACK (value_changed_cb), self); g_signal_connect (G_OBJECT (self->priv->contrast_adj), "value-changed", G_CALLBACK (value_changed_cb), self); g_signal_connect (G_OBJECT (self->priv->gamma_adj), "value-changed", G_CALLBACK (value_changed_cb), self); g_signal_connect (G_OBJECT (self->priv->saturation_adj), "value-changed", G_CALLBACK (value_changed_cb), self); g_signal_connect (G_OBJECT (self->priv->cyan_red_adj), "value-changed", G_CALLBACK (value_changed_cb), self); g_signal_connect (G_OBJECT (self->priv->magenta_green_adj), "value-changed", G_CALLBACK (value_changed_cb), self); g_signal_connect (G_OBJECT (self->priv->yellow_blue_adj), "value-changed", G_CALLBACK (value_changed_cb), self); g_signal_connect (GET_WIDGET ("preview_checkbutton"), "toggled", G_CALLBACK (preview_checkbutton_toggled_cb), self); self->priv->preview_tool = gth_preview_tool_new (); gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview); gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), self->priv->preview_tool); gth_histogram_calculate_for_image (self->priv->histogram, self->priv->preview); return options; } static void gth_file_tool_adjust_colors_destroy_options (GthFileTool *base) { GthFileToolAdjustColors *self; GthViewerPage *viewer_page; self = (GthFileToolAdjustColors *) base; if (self->priv->apply_event != 0) { g_source_remove (self->priv->apply_event); self->priv->apply_event = 0; } viewer_page = gth_image_viewer_page_tool_get_page (GTH_IMAGE_VIEWER_PAGE_TOOL (self)); gth_image_viewer_page_reset_viewer_tool (GTH_IMAGE_VIEWER_PAGE (viewer_page)); gth_viewer_page_update_sensitivity (viewer_page); _cairo_clear_surface (&self->priv->preview); _cairo_clear_surface (&self->priv->destination); _g_clear_object (&self->priv->builder); } static void gth_file_tool_adjust_colors_apply_options (GthFileTool *base) { GthFileToolAdjustColors *self; self = (GthFileToolAdjustColors *) base; self->priv->apply_to_original = TRUE; apply_changes (self); } static void gth_file_tool_adjust_colors_populate_headerbar (GthFileTool *base, GthBrowser *browser) { GthFileToolAdjustColors *self; GtkWidget *button; self = (GthFileToolAdjustColors *) base; /* reset button */ button = gth_browser_add_header_bar_button (browser, GTH_BROWSER_HEADER_SECTION_EDITOR_COMMANDS, "edit-undo-symbolic", _("Reset"), NULL, NULL); g_signal_connect (button, "clicked", G_CALLBACK (reset_button_clicked_cb), self); } static void gth_file_tool_sharpen_reset_image (GthImageViewerPageTool *base) { GthFileToolAdjustColors *self = (GthFileToolAdjustColors *) base; if (self->priv->image_task != NULL) { self->priv->closing = TRUE; gth_task_cancel (self->priv->image_task); return; } if (self->priv->apply_event != 0) { g_source_remove (self->priv->apply_event); self->priv->apply_event = 0; } gth_image_viewer_page_reset (GTH_IMAGE_VIEWER_PAGE (gth_image_viewer_page_tool_get_page (GTH_IMAGE_VIEWER_PAGE_TOOL (self)))); gth_file_tool_hide_options (GTH_FILE_TOOL (self)); } static void gth_file_tool_adjust_colors_init (GthFileToolAdjustColors *self) { self->priv = gth_file_tool_adjust_colors_get_instance_private (self); self->priv->histogram = gth_histogram_new (); self->priv->preview = NULL; self->priv->destination = NULL; self->priv->builder = NULL; self->priv->image_task = NULL; gth_file_tool_construct (GTH_FILE_TOOL (self), "image-adjust-colors-symbolic", _("Adjust Colors"), GTH_TOOLBOX_SECTION_COLORS); gtk_widget_set_tooltip_text (GTK_WIDGET (self), _("Change brightness, contrast, saturation and gamma level of the image")); } static void gth_file_tool_adjust_colors_finalize (GObject *object) { GthFileToolAdjustColors *self; g_return_if_fail (object != NULL); g_return_if_fail (GTH_IS_FILE_TOOL_ADJUST_COLORS (object)); self = (GthFileToolAdjustColors *) object; cairo_surface_destroy (self->priv->preview); cairo_surface_destroy (self->priv->destination); _g_object_unref (self->priv->builder); _g_object_unref (self->priv->histogram); G_OBJECT_CLASS (gth_file_tool_adjust_colors_parent_class)->finalize (object); } static void gth_file_tool_adjust_colors_class_init (GthFileToolAdjustColorsClass *klass) { GObjectClass *gobject_class; GthFileToolClass *file_tool_class; GthImageViewerPageToolClass *image_viewer_page_tool_class; gobject_class = (GObjectClass*) klass; gobject_class->finalize = gth_file_tool_adjust_colors_finalize; file_tool_class = (GthFileToolClass *) klass; file_tool_class->get_options = gth_file_tool_adjust_colors_get_options; file_tool_class->destroy_options = gth_file_tool_adjust_colors_destroy_options; file_tool_class->apply_options = gth_file_tool_adjust_colors_apply_options; file_tool_class->populate_headerbar = gth_file_tool_adjust_colors_populate_headerbar; image_viewer_page_tool_class = (GthImageViewerPageToolClass *) klass; image_viewer_page_tool_class->reset_image = gth_file_tool_sharpen_reset_image; }