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@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
35typedef 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
47static GtkWidget *
48get_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
58static double
59min4 (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
68static double
69max4 (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
78static void
79compute_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
104typedef struct
105{
106    char		name [20];
107    pixman_kernel_t	value;
108} named_int_t;
109
110static 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
122static 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
130static pixman_kernel_t
131get_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
138static void
139copy_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
180static double
181to_scale (double v)
182{
183    return pow (1.15, v);
184}
185
186static void
187rescale (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
260static gboolean
261on_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
301static void
302set_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
334static void
335set_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
340static char *
341format_value (GtkWidget *widget, double value)
342{
343    return g_strdup_printf ("%.4f", to_scale (value));
344}
345
346static app_t *
347app_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
402int
403main (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