Home | History | Annotate | Download | only in demos
      1 /*
      2  * Copyright 2012, Red Hat, Inc.
      3  * Copyright 2012, Soren Sandmann
      4  *
      5  * Permission is hereby granted, free of charge, to any person obtaining a
      6  * copy of this software and associated documentation files (the "Software"),
      7  * to deal in the Software without restriction, including without limitation
      8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      9  * and/or sell copies of the Software, and to permit persons to whom the
     10  * Software is furnished to do so, subject to the following conditions:
     11  *
     12  * The above copyright notice and this permission notice (including the next
     13  * paragraph) shall be included in all copies or substantial portions of the
     14  * Software.
     15  *
     16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
     22  * DEALINGS IN THE SOFTWARE.
     23  *
     24  * Author: Soren Sandmann <soren.sandmann (at) gmail.com>
     25  */
     26 #ifdef HAVE_CONFIG_H
     27 #include "config.h"
     28 #endif
     29 #include <math.h>
     30 #include <gtk/gtk.h>
     31 #include <pixman.h>
     32 #include <stdlib.h>
     33 #include "gtk-utils.h"
     34 
     35 typedef struct
     36 {
     37     GtkBuilder *        builder;
     38     pixman_image_t *	original;
     39     GtkAdjustment *     scale_x_adjustment;
     40     GtkAdjustment *     scale_y_adjustment;
     41     GtkAdjustment *     rotate_adjustment;
     42     GtkAdjustment *	subsample_adjustment;
     43     int                 scaled_width;
     44     int                 scaled_height;
     45 } app_t;
     46 
     47 static GtkWidget *
     48 get_widget (app_t *app, const char *name)
     49 {
     50     GtkWidget *widget = GTK_WIDGET (gtk_builder_get_object (app->builder, name));
     51 
     52     if (!widget)
     53         g_error ("Widget %s not found\n", name);
     54 
     55     return widget;
     56 }
     57 
     58 static double
     59 min4 (double a, double b, double c, double d)
     60 {
     61     double m1, m2;
     62 
     63     m1 = MIN (a, b);
     64     m2 = MIN (c, d);
     65     return MIN (m1, m2);
     66 }
     67 
     68 static double
     69 max4 (double a, double b, double c, double d)
     70 {
     71     double m1, m2;
     72 
     73     m1 = MAX (a, b);
     74     m2 = MAX (c, d);
     75     return MAX (m1, m2);
     76 }
     77 
     78 static void
     79 compute_extents (pixman_f_transform_t *trans, double *sx, double *sy)
     80 {
     81     double min_x, max_x, min_y, max_y;
     82     pixman_f_vector_t v[4] =
     83     {
     84 	{ { 1, 1, 1 } },
     85 	{ { -1, 1, 1 } },
     86 	{ { -1, -1, 1 } },
     87 	{ { 1, -1, 1 } },
     88     };
     89 
     90     pixman_f_transform_point (trans, &v[0]);
     91     pixman_f_transform_point (trans, &v[1]);
     92     pixman_f_transform_point (trans, &v[2]);
     93     pixman_f_transform_point (trans, &v[3]);
     94 
     95     min_x = min4 (v[0].v[0], v[1].v[0], v[2].v[0], v[3].v[0]);
     96     max_x = max4 (v[0].v[0], v[1].v[0], v[2].v[0], v[3].v[0]);
     97     min_y = min4 (v[0].v[1], v[1].v[1], v[2].v[1], v[3].v[1]);
     98     max_y = max4 (v[0].v[1], v[1].v[1], v[2].v[1], v[3].v[1]);
     99 
    100     *sx = (max_x - min_x) / 2.0;
    101     *sy = (max_y - min_y) / 2.0;
    102 }
    103 
    104 typedef struct
    105 {
    106     char		name [20];
    107     pixman_kernel_t	value;
    108 } named_int_t;
    109 
    110 static const named_int_t filters[] =
    111 {
    112     { "Box",			PIXMAN_KERNEL_BOX },
    113     { "Impulse",		PIXMAN_KERNEL_IMPULSE },
    114     { "Linear",			PIXMAN_KERNEL_LINEAR },
    115     { "Cubic",			PIXMAN_KERNEL_CUBIC },
    116     { "Lanczos2",		PIXMAN_KERNEL_LANCZOS2 },
    117     { "Lanczos3",		PIXMAN_KERNEL_LANCZOS3 },
    118     { "Lanczos3 Stretched",	PIXMAN_KERNEL_LANCZOS3_STRETCHED },
    119     { "Gaussian",		PIXMAN_KERNEL_GAUSSIAN },
    120 };
    121 
    122 static const named_int_t repeats[] =
    123 {
    124     { "None",                   PIXMAN_REPEAT_NONE },
    125     { "Normal",                 PIXMAN_REPEAT_NORMAL },
    126     { "Reflect",                PIXMAN_REPEAT_REFLECT },
    127     { "Pad",                    PIXMAN_REPEAT_PAD },
    128 };
    129 
    130 static pixman_kernel_t
    131 get_value (app_t *app, const named_int_t table[], const char *box_name)
    132 {
    133     GtkComboBox *box = GTK_COMBO_BOX (get_widget (app, box_name));
    134 
    135     return table[gtk_combo_box_get_active (box)].value;
    136 }
    137 
    138 static void
    139 copy_to_counterpart (app_t *app, GObject *object)
    140 {
    141     static const char *xy_map[] =
    142     {
    143 	"reconstruct_x_combo_box", "reconstruct_y_combo_box",
    144 	"sample_x_combo_box",      "sample_y_combo_box",
    145 	"scale_x_adjustment",      "scale_y_adjustment",
    146     };
    147     GObject *counterpart = NULL;
    148     int i;
    149 
    150     for (i = 0; i < G_N_ELEMENTS (xy_map); i += 2)
    151     {
    152 	GObject *x = gtk_builder_get_object (app->builder, xy_map[i]);
    153 	GObject *y = gtk_builder_get_object (app->builder, xy_map[i + 1]);
    154 
    155 	if (object == x)
    156 	    counterpart = y;
    157 	if (object == y)
    158 	    counterpart = x;
    159     }
    160 
    161     if (!counterpart)
    162 	return;
    163 
    164     if (GTK_IS_COMBO_BOX (counterpart))
    165     {
    166 	gtk_combo_box_set_active (
    167 	    GTK_COMBO_BOX (counterpart),
    168 	    gtk_combo_box_get_active (
    169 		GTK_COMBO_BOX (object)));
    170     }
    171     else if (GTK_IS_ADJUSTMENT (counterpart))
    172     {
    173 	gtk_adjustment_set_value (
    174 	    GTK_ADJUSTMENT (counterpart),
    175 	    gtk_adjustment_get_value (
    176 		GTK_ADJUSTMENT (object)));
    177     }
    178 }
    179 
    180 static double
    181 to_scale (double v)
    182 {
    183     return pow (1.15, v);
    184 }
    185 
    186 static void
    187 rescale (GtkWidget *may_be_null, app_t *app)
    188 {
    189     pixman_f_transform_t ftransform;
    190     pixman_transform_t transform;
    191     double new_width, new_height;
    192     double fscale_x, fscale_y;
    193     double rotation;
    194     pixman_fixed_t *params;
    195     int n_params;
    196     double sx, sy;
    197 
    198     pixman_f_transform_init_identity (&ftransform);
    199 
    200     if (may_be_null && gtk_toggle_button_get_active (
    201 	    GTK_TOGGLE_BUTTON (get_widget (app, "lock_checkbutton"))))
    202     {
    203 	copy_to_counterpart (app, G_OBJECT (may_be_null));
    204     }
    205 
    206     fscale_x = gtk_adjustment_get_value (app->scale_x_adjustment);
    207     fscale_y = gtk_adjustment_get_value (app->scale_y_adjustment);
    208     rotation = gtk_adjustment_get_value (app->rotate_adjustment);
    209 
    210     fscale_x = to_scale (fscale_x);
    211     fscale_y = to_scale (fscale_y);
    212 
    213     new_width = pixman_image_get_width (app->original) * fscale_x;
    214     new_height = pixman_image_get_height (app->original) * fscale_y;
    215 
    216     pixman_f_transform_scale (&ftransform, NULL, fscale_x, fscale_y);
    217 
    218     pixman_f_transform_translate (&ftransform, NULL, - new_width / 2.0, - new_height / 2.0);
    219 
    220     rotation = (rotation / 360.0) * 2 * M_PI;
    221     pixman_f_transform_rotate (&ftransform, NULL, cos (rotation), sin (rotation));
    222 
    223     pixman_f_transform_translate (&ftransform, NULL, new_width / 2.0, new_height / 2.0);
    224 
    225     pixman_f_transform_invert (&ftransform, &ftransform);
    226 
    227     compute_extents (&ftransform, &sx, &sy);
    228 
    229     pixman_transform_from_pixman_f_transform (&transform, &ftransform);
    230     pixman_image_set_transform (app->original, &transform);
    231 
    232     params = pixman_filter_create_separable_convolution (
    233         &n_params,
    234         sx * 65536.0 + 0.5,
    235 	sy * 65536.0 + 0.5,
    236 	get_value (app, filters, "reconstruct_x_combo_box"),
    237 	get_value (app, filters, "reconstruct_y_combo_box"),
    238 	get_value (app, filters, "sample_x_combo_box"),
    239 	get_value (app, filters, "sample_y_combo_box"),
    240 	gtk_adjustment_get_value (app->subsample_adjustment),
    241 	gtk_adjustment_get_value (app->subsample_adjustment));
    242 
    243     pixman_image_set_filter (app->original, PIXMAN_FILTER_SEPARABLE_CONVOLUTION, params, n_params);
    244 
    245     pixman_image_set_repeat (
    246         app->original, get_value (app, repeats, "repeat_combo_box"));
    247 
    248     free (params);
    249 
    250     app->scaled_width = ceil (new_width);
    251     app->scaled_height = ceil (new_height);
    252 
    253     gtk_widget_set_size_request (
    254         get_widget (app, "drawing_area"), new_width + 0.5, new_height + 0.5);
    255 
    256     gtk_widget_queue_draw (
    257         get_widget (app, "drawing_area"));
    258 }
    259 
    260 static gboolean
    261 on_expose (GtkWidget *da, GdkEvent *event, gpointer data)
    262 {
    263     app_t *app = data;
    264     GdkRectangle *area = &event->expose.area;
    265     cairo_surface_t *surface;
    266     pixman_image_t *tmp;
    267     cairo_t *cr;
    268     uint32_t *pixels;
    269 
    270     pixels = calloc (1, area->width * area->height * 4);
    271     tmp = pixman_image_create_bits (
    272         PIXMAN_a8r8g8b8, area->width, area->height, pixels, area->width * 4);
    273 
    274     if (area->x < app->scaled_width && area->y < app->scaled_height)
    275     {
    276         pixman_image_composite (
    277             PIXMAN_OP_SRC,
    278             app->original, NULL, tmp,
    279             area->x, area->y, 0, 0, 0, 0,
    280             app->scaled_width - area->x, app->scaled_height - area->y);
    281     }
    282 
    283     surface = cairo_image_surface_create_for_data (
    284         (uint8_t *)pixels, CAIRO_FORMAT_ARGB32,
    285         area->width, area->height, area->width * 4);
    286 
    287     cr = gdk_cairo_create (da->window);
    288 
    289     cairo_set_source_surface (cr, surface, area->x, area->y);
    290 
    291     cairo_paint (cr);
    292 
    293     cairo_destroy (cr);
    294     cairo_surface_destroy (surface);
    295     free (pixels);
    296     pixman_image_unref (tmp);
    297 
    298     return TRUE;
    299 }
    300 
    301 static void
    302 set_up_combo_box (app_t *app, const char *box_name,
    303                   int n_entries, const named_int_t table[])
    304 {
    305     GtkWidget *widget = get_widget (app, box_name);
    306     GtkListStore *model;
    307     GtkCellRenderer *cell;
    308     int i;
    309 
    310     model = gtk_list_store_new (1, G_TYPE_STRING);
    311 
    312     cell = gtk_cell_renderer_text_new ();
    313     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE);
    314     gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), cell,
    315 				    "text", 0,
    316 				    NULL);
    317 
    318     gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (model));
    319 
    320     for (i = 0; i < n_entries; ++i)
    321     {
    322 	const named_int_t *info = &(table[i]);
    323 	GtkTreeIter iter;
    324 
    325 	gtk_list_store_append (model, &iter);
    326 	gtk_list_store_set (model, &iter, 0, info->name, -1);
    327     }
    328 
    329     gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
    330 
    331     g_signal_connect (widget, "changed", G_CALLBACK (rescale), app);
    332 }
    333 
    334 static void
    335 set_up_filter_box (app_t *app, const char *box_name)
    336 {
    337     set_up_combo_box (app, box_name, G_N_ELEMENTS (filters), filters);
    338 }
    339 
    340 static char *
    341 format_value (GtkWidget *widget, double value)
    342 {
    343     return g_strdup_printf ("%.4f", to_scale (value));
    344 }
    345 
    346 static app_t *
    347 app_new (pixman_image_t *original)
    348 {
    349     GtkWidget *widget;
    350     app_t *app = g_malloc (sizeof *app);
    351     GError *err = NULL;
    352 
    353     app->builder = gtk_builder_new ();
    354     app->original = original;
    355 
    356     if (!gtk_builder_add_from_file (app->builder, "scale.ui", &err))
    357 	g_error ("Could not read file scale.ui: %s", err->message);
    358 
    359     app->scale_x_adjustment =
    360         GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_x_adjustment"));
    361     app->scale_y_adjustment =
    362         GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_y_adjustment"));
    363     app->rotate_adjustment =
    364         GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "rotate_adjustment"));
    365     app->subsample_adjustment =
    366 	GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "subsample_adjustment"));
    367 
    368     g_signal_connect (app->scale_x_adjustment, "value_changed", G_CALLBACK (rescale), app);
    369     g_signal_connect (app->scale_y_adjustment, "value_changed", G_CALLBACK (rescale), app);
    370     g_signal_connect (app->rotate_adjustment, "value_changed", G_CALLBACK (rescale), app);
    371     g_signal_connect (app->subsample_adjustment, "value_changed", G_CALLBACK (rescale), app);
    372 
    373     widget = get_widget (app, "scale_x_scale");
    374     gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
    375     g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app);
    376     widget = get_widget (app, "scale_y_scale");
    377     gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
    378     g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app);
    379     widget = get_widget (app, "rotate_scale");
    380     gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
    381 
    382     widget = get_widget (app, "drawing_area");
    383     g_signal_connect (widget, "expose_event", G_CALLBACK (on_expose), app);
    384 
    385     set_up_filter_box (app, "reconstruct_x_combo_box");
    386     set_up_filter_box (app, "reconstruct_y_combo_box");
    387     set_up_filter_box (app, "sample_x_combo_box");
    388     set_up_filter_box (app, "sample_y_combo_box");
    389 
    390     set_up_combo_box (
    391         app, "repeat_combo_box", G_N_ELEMENTS (repeats), repeats);
    392 
    393     g_signal_connect (
    394 	gtk_builder_get_object (app->builder, "lock_checkbutton"),
    395 	"toggled", G_CALLBACK (rescale), app);
    396 
    397     rescale (NULL, app);
    398 
    399     return app;
    400 }
    401 
    402 int
    403 main (int argc, char **argv)
    404 {
    405     GtkWidget *window;
    406     pixman_image_t *image;
    407     app_t *app;
    408 
    409     gtk_init (&argc, &argv);
    410 
    411     if (argc < 2)
    412     {
    413 	printf ("%s <image file>\n", argv[0]);
    414 	return -1;
    415     }
    416 
    417     if (!(image = pixman_image_from_file (argv[1], PIXMAN_a8r8g8b8)))
    418     {
    419 	printf ("Could not load image \"%s\"\n", argv[1]);
    420 	return -1;
    421     }
    422 
    423     app = app_new (image);
    424 
    425     window = get_widget (app, "main");
    426 
    427     g_signal_connect (window, "delete_event", G_CALLBACK (gtk_main_quit), NULL);
    428 
    429     gtk_window_set_default_size (GTK_WINDOW (window), 1024, 768);
    430 
    431     gtk_widget_show_all (window);
    432 
    433     gtk_main ();
    434 
    435     return 0;
    436 }
    437