| /* |
| * Copyright 2012, Red Hat, Inc. |
| * Copyright 2012, Soren Sandmann |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| * |
| * Author: Soren Sandmann <soren.sandmann@gmail.com> |
| */ |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| #include <math.h> |
| #include <gtk/gtk.h> |
| #include <pixman.h> |
| #include <stdlib.h> |
| #include "gtk-utils.h" |
| |
| typedef struct |
| { |
| GtkBuilder * builder; |
| pixman_image_t * original; |
| GtkAdjustment * scale_x_adjustment; |
| GtkAdjustment * scale_y_adjustment; |
| GtkAdjustment * rotate_adjustment; |
| GtkAdjustment * subsample_adjustment; |
| int scaled_width; |
| int scaled_height; |
| } app_t; |
| |
| static GtkWidget * |
| get_widget (app_t *app, const char *name) |
| { |
| GtkWidget *widget = GTK_WIDGET (gtk_builder_get_object (app->builder, name)); |
| |
| if (!widget) |
| g_error ("Widget %s not found\n", name); |
| |
| return widget; |
| } |
| |
| static double |
| min4 (double a, double b, double c, double d) |
| { |
| double m1, m2; |
| |
| m1 = MIN (a, b); |
| m2 = MIN (c, d); |
| return MIN (m1, m2); |
| } |
| |
| static double |
| max4 (double a, double b, double c, double d) |
| { |
| double m1, m2; |
| |
| m1 = MAX (a, b); |
| m2 = MAX (c, d); |
| return MAX (m1, m2); |
| } |
| |
| static void |
| compute_extents (pixman_f_transform_t *trans, double *sx, double *sy) |
| { |
| double min_x, max_x, min_y, max_y; |
| pixman_f_vector_t v[4] = |
| { |
| { { 1, 1, 1 } }, |
| { { -1, 1, 1 } }, |
| { { -1, -1, 1 } }, |
| { { 1, -1, 1 } }, |
| }; |
| |
| pixman_f_transform_point (trans, &v[0]); |
| pixman_f_transform_point (trans, &v[1]); |
| pixman_f_transform_point (trans, &v[2]); |
| pixman_f_transform_point (trans, &v[3]); |
| |
| min_x = min4 (v[0].v[0], v[1].v[0], v[2].v[0], v[3].v[0]); |
| max_x = max4 (v[0].v[0], v[1].v[0], v[2].v[0], v[3].v[0]); |
| min_y = min4 (v[0].v[1], v[1].v[1], v[2].v[1], v[3].v[1]); |
| max_y = max4 (v[0].v[1], v[1].v[1], v[2].v[1], v[3].v[1]); |
| |
| *sx = (max_x - min_x) / 2.0; |
| *sy = (max_y - min_y) / 2.0; |
| } |
| |
| typedef struct |
| { |
| char name [20]; |
| pixman_kernel_t value; |
| } named_int_t; |
| |
| static const named_int_t filters[] = |
| { |
| { "Box", PIXMAN_KERNEL_BOX }, |
| { "Impulse", PIXMAN_KERNEL_IMPULSE }, |
| { "Linear", PIXMAN_KERNEL_LINEAR }, |
| { "Cubic", PIXMAN_KERNEL_CUBIC }, |
| { "Lanczos2", PIXMAN_KERNEL_LANCZOS2 }, |
| { "Lanczos3", PIXMAN_KERNEL_LANCZOS3 }, |
| { "Lanczos3 Stretched", PIXMAN_KERNEL_LANCZOS3_STRETCHED }, |
| { "Gaussian", PIXMAN_KERNEL_GAUSSIAN }, |
| }; |
| |
| static const named_int_t repeats[] = |
| { |
| { "None", PIXMAN_REPEAT_NONE }, |
| { "Normal", PIXMAN_REPEAT_NORMAL }, |
| { "Reflect", PIXMAN_REPEAT_REFLECT }, |
| { "Pad", PIXMAN_REPEAT_PAD }, |
| }; |
| |
| static pixman_kernel_t |
| get_value (app_t *app, const named_int_t table[], const char *box_name) |
| { |
| GtkComboBox *box = GTK_COMBO_BOX (get_widget (app, box_name)); |
| |
| return table[gtk_combo_box_get_active (box)].value; |
| } |
| |
| static void |
| copy_to_counterpart (app_t *app, GObject *object) |
| { |
| static const char *xy_map[] = |
| { |
| "reconstruct_x_combo_box", "reconstruct_y_combo_box", |
| "sample_x_combo_box", "sample_y_combo_box", |
| "scale_x_adjustment", "scale_y_adjustment", |
| }; |
| GObject *counterpart = NULL; |
| int i; |
| |
| for (i = 0; i < G_N_ELEMENTS (xy_map); i += 2) |
| { |
| GObject *x = gtk_builder_get_object (app->builder, xy_map[i]); |
| GObject *y = gtk_builder_get_object (app->builder, xy_map[i + 1]); |
| |
| if (object == x) |
| counterpart = y; |
| if (object == y) |
| counterpart = x; |
| } |
| |
| if (!counterpart) |
| return; |
| |
| if (GTK_IS_COMBO_BOX (counterpart)) |
| { |
| gtk_combo_box_set_active ( |
| GTK_COMBO_BOX (counterpart), |
| gtk_combo_box_get_active ( |
| GTK_COMBO_BOX (object))); |
| } |
| else if (GTK_IS_ADJUSTMENT (counterpart)) |
| { |
| gtk_adjustment_set_value ( |
| GTK_ADJUSTMENT (counterpart), |
| gtk_adjustment_get_value ( |
| GTK_ADJUSTMENT (object))); |
| } |
| } |
| |
| static double |
| to_scale (double v) |
| { |
| return pow (1.15, v); |
| } |
| |
| static void |
| rescale (GtkWidget *may_be_null, app_t *app) |
| { |
| pixman_f_transform_t ftransform; |
| pixman_transform_t transform; |
| double new_width, new_height; |
| double fscale_x, fscale_y; |
| double rotation; |
| pixman_fixed_t *params; |
| int n_params; |
| double sx, sy; |
| |
| pixman_f_transform_init_identity (&ftransform); |
| |
| if (may_be_null && gtk_toggle_button_get_active ( |
| GTK_TOGGLE_BUTTON (get_widget (app, "lock_checkbutton")))) |
| { |
| copy_to_counterpart (app, G_OBJECT (may_be_null)); |
| } |
| |
| fscale_x = gtk_adjustment_get_value (app->scale_x_adjustment); |
| fscale_y = gtk_adjustment_get_value (app->scale_y_adjustment); |
| rotation = gtk_adjustment_get_value (app->rotate_adjustment); |
| |
| fscale_x = to_scale (fscale_x); |
| fscale_y = to_scale (fscale_y); |
| |
| new_width = pixman_image_get_width (app->original) * fscale_x; |
| new_height = pixman_image_get_height (app->original) * fscale_y; |
| |
| pixman_f_transform_scale (&ftransform, NULL, fscale_x, fscale_y); |
| |
| pixman_f_transform_translate (&ftransform, NULL, - new_width / 2.0, - new_height / 2.0); |
| |
| rotation = (rotation / 360.0) * 2 * M_PI; |
| pixman_f_transform_rotate (&ftransform, NULL, cos (rotation), sin (rotation)); |
| |
| pixman_f_transform_translate (&ftransform, NULL, new_width / 2.0, new_height / 2.0); |
| |
| pixman_f_transform_invert (&ftransform, &ftransform); |
| |
| compute_extents (&ftransform, &sx, &sy); |
| |
| pixman_transform_from_pixman_f_transform (&transform, &ftransform); |
| pixman_image_set_transform (app->original, &transform); |
| |
| params = pixman_filter_create_separable_convolution ( |
| &n_params, |
| sx * 65536.0 + 0.5, |
| sy * 65536.0 + 0.5, |
| get_value (app, filters, "reconstruct_x_combo_box"), |
| get_value (app, filters, "reconstruct_y_combo_box"), |
| get_value (app, filters, "sample_x_combo_box"), |
| get_value (app, filters, "sample_y_combo_box"), |
| gtk_adjustment_get_value (app->subsample_adjustment), |
| gtk_adjustment_get_value (app->subsample_adjustment)); |
| |
| pixman_image_set_filter (app->original, PIXMAN_FILTER_SEPARABLE_CONVOLUTION, params, n_params); |
| |
| pixman_image_set_repeat ( |
| app->original, get_value (app, repeats, "repeat_combo_box")); |
| |
| free (params); |
| |
| app->scaled_width = ceil (new_width); |
| app->scaled_height = ceil (new_height); |
| |
| gtk_widget_set_size_request ( |
| get_widget (app, "drawing_area"), new_width + 0.5, new_height + 0.5); |
| |
| gtk_widget_queue_draw ( |
| get_widget (app, "drawing_area")); |
| } |
| |
| static gboolean |
| on_expose (GtkWidget *da, GdkEvent *event, gpointer data) |
| { |
| app_t *app = data; |
| GdkRectangle *area = &event->expose.area; |
| cairo_surface_t *surface; |
| pixman_image_t *tmp; |
| cairo_t *cr; |
| uint32_t *pixels; |
| |
| pixels = calloc (1, area->width * area->height * 4); |
| tmp = pixman_image_create_bits ( |
| PIXMAN_a8r8g8b8, area->width, area->height, pixels, area->width * 4); |
| |
| if (area->x < app->scaled_width && area->y < app->scaled_height) |
| { |
| pixman_image_composite ( |
| PIXMAN_OP_SRC, |
| app->original, NULL, tmp, |
| area->x, area->y, 0, 0, 0, 0, |
| app->scaled_width - area->x, app->scaled_height - area->y); |
| } |
| |
| surface = cairo_image_surface_create_for_data ( |
| (uint8_t *)pixels, CAIRO_FORMAT_ARGB32, |
| area->width, area->height, area->width * 4); |
| |
| cr = gdk_cairo_create (da->window); |
| |
| cairo_set_source_surface (cr, surface, area->x, area->y); |
| |
| cairo_paint (cr); |
| |
| cairo_destroy (cr); |
| cairo_surface_destroy (surface); |
| free (pixels); |
| pixman_image_unref (tmp); |
| |
| return TRUE; |
| } |
| |
| static void |
| set_up_combo_box (app_t *app, const char *box_name, |
| int n_entries, const named_int_t table[]) |
| { |
| GtkWidget *widget = get_widget (app, box_name); |
| GtkListStore *model; |
| GtkCellRenderer *cell; |
| int i; |
| |
| model = gtk_list_store_new (1, G_TYPE_STRING); |
| |
| cell = gtk_cell_renderer_text_new (); |
| gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE); |
| gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), cell, |
| "text", 0, |
| NULL); |
| |
| gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (model)); |
| |
| for (i = 0; i < n_entries; ++i) |
| { |
| const named_int_t *info = &(table[i]); |
| GtkTreeIter iter; |
| |
| gtk_list_store_append (model, &iter); |
| gtk_list_store_set (model, &iter, 0, info->name, -1); |
| } |
| |
| gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); |
| |
| g_signal_connect (widget, "changed", G_CALLBACK (rescale), app); |
| } |
| |
| static void |
| set_up_filter_box (app_t *app, const char *box_name) |
| { |
| set_up_combo_box (app, box_name, G_N_ELEMENTS (filters), filters); |
| } |
| |
| static char * |
| format_value (GtkWidget *widget, double value) |
| { |
| return g_strdup_printf ("%.4f", to_scale (value)); |
| } |
| |
| static app_t * |
| app_new (pixman_image_t *original) |
| { |
| GtkWidget *widget; |
| app_t *app = g_malloc (sizeof *app); |
| GError *err = NULL; |
| |
| app->builder = gtk_builder_new (); |
| app->original = original; |
| |
| if (!gtk_builder_add_from_file (app->builder, "scale.ui", &err)) |
| g_error ("Could not read file scale.ui: %s", err->message); |
| |
| app->scale_x_adjustment = |
| GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_x_adjustment")); |
| app->scale_y_adjustment = |
| GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_y_adjustment")); |
| app->rotate_adjustment = |
| GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "rotate_adjustment")); |
| app->subsample_adjustment = |
| GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "subsample_adjustment")); |
| |
| g_signal_connect (app->scale_x_adjustment, "value_changed", G_CALLBACK (rescale), app); |
| g_signal_connect (app->scale_y_adjustment, "value_changed", G_CALLBACK (rescale), app); |
| g_signal_connect (app->rotate_adjustment, "value_changed", G_CALLBACK (rescale), app); |
| g_signal_connect (app->subsample_adjustment, "value_changed", G_CALLBACK (rescale), app); |
| |
| widget = get_widget (app, "scale_x_scale"); |
| gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL); |
| g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app); |
| widget = get_widget (app, "scale_y_scale"); |
| gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL); |
| g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app); |
| widget = get_widget (app, "rotate_scale"); |
| gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL); |
| |
| widget = get_widget (app, "drawing_area"); |
| g_signal_connect (widget, "expose_event", G_CALLBACK (on_expose), app); |
| |
| set_up_filter_box (app, "reconstruct_x_combo_box"); |
| set_up_filter_box (app, "reconstruct_y_combo_box"); |
| set_up_filter_box (app, "sample_x_combo_box"); |
| set_up_filter_box (app, "sample_y_combo_box"); |
| |
| set_up_combo_box ( |
| app, "repeat_combo_box", G_N_ELEMENTS (repeats), repeats); |
| |
| g_signal_connect ( |
| gtk_builder_get_object (app->builder, "lock_checkbutton"), |
| "toggled", G_CALLBACK (rescale), app); |
| |
| rescale (NULL, app); |
| |
| return app; |
| } |
| |
| int |
| main (int argc, char **argv) |
| { |
| GtkWidget *window; |
| pixman_image_t *image; |
| app_t *app; |
| |
| gtk_init (&argc, &argv); |
| |
| if (argc < 2) |
| { |
| printf ("%s <image file>\n", argv[0]); |
| return -1; |
| } |
| |
| if (!(image = pixman_image_from_file (argv[1], PIXMAN_a8r8g8b8))) |
| { |
| printf ("Could not load image \"%s\"\n", argv[1]); |
| return -1; |
| } |
| |
| app = app_new (image); |
| |
| window = get_widget (app, "main"); |
| |
| g_signal_connect (window, "delete_event", G_CALLBACK (gtk_main_quit), NULL); |
| |
| gtk_window_set_default_size (GTK_WINDOW (window), 1024, 768); |
| |
| gtk_widget_show_all (window); |
| |
| gtk_main (); |
| |
| return 0; |
| } |