• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 *
get_widget(app_t * app,const char * name)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 /* Figure out the boundary of a diameter=1 circle transformed into an ellipse
59  * by trans. Proof that this is the correct calculation:
60  *
61  * Transform x,y to u,v by this matrix calculation:
62  *
63  *  |u|   |a c| |x|
64  *  |v| = |b d|*|y|
65  *
66  * Horizontal component:
67  *
68  *  u = ax+cy (1)
69  *
70  * For each x,y on a radius-1 circle (p is angle to the point):
71  *
72  *  x^2+y^2 = 1
73  *  x = cos(p)
74  *  y = sin(p)
75  *  dx/dp = -sin(p) = -y
76  *  dy/dp = cos(p) = x
77  *
78  * Figure out derivative of (1) relative to p:
79  *
80  *  du/dp = a(dx/dp) + c(dy/dp)
81  *        = -ay + cx
82  *
83  * The min and max u are when du/dp is zero:
84  *
85  *  -ay + cx = 0
86  *  cx = ay
87  *  c = ay/x  (2)
88  *  y = cx/a  (3)
89  *
90  * Substitute (2) into (1) and simplify:
91  *
92  *  u = ax + ay^2/x
93  *    = a(x^2+y^2)/x
94  *    = a/x (because x^2+y^2 = 1)
95  *  x = a/u (4)
96  *
97  * Substitute (4) into (3) and simplify:
98  *
99  *  y = c(a/u)/a
100  *  y = c/u (5)
101  *
102  * Square (4) and (5) and add:
103  *
104  *  x^2+y^2 = (a^2+c^2)/u^2
105  *
106  * But x^2+y^2 is 1:
107  *
108  *  1 = (a^2+c^2)/u^2
109  *  u^2 = a^2+c^2
110  *  u = hypot(a,c)
111  *
112  * Similarily the max/min of v is at:
113  *
114  *  v = hypot(b,d)
115  *
116  */
117 static void
compute_extents(pixman_f_transform_t * trans,double * sx,double * sy)118 compute_extents (pixman_f_transform_t *trans, double *sx, double *sy)
119 {
120     *sx = hypot (trans->m[0][0], trans->m[0][1]) / trans->m[2][2];
121     *sy = hypot (trans->m[1][0], trans->m[1][1]) / trans->m[2][2];
122 }
123 
124 typedef struct
125 {
126     char	name [20];
127     int		value;
128 } named_int_t;
129 
130 static const named_int_t filters[] =
131 {
132     { "Box",			PIXMAN_KERNEL_BOX },
133     { "Impulse",		PIXMAN_KERNEL_IMPULSE },
134     { "Linear",			PIXMAN_KERNEL_LINEAR },
135     { "Cubic",			PIXMAN_KERNEL_CUBIC },
136     { "Lanczos2",		PIXMAN_KERNEL_LANCZOS2 },
137     { "Lanczos3",		PIXMAN_KERNEL_LANCZOS3 },
138     { "Lanczos3 Stretched",	PIXMAN_KERNEL_LANCZOS3_STRETCHED },
139     { "Gaussian",		PIXMAN_KERNEL_GAUSSIAN },
140 };
141 
142 static const named_int_t repeats[] =
143 {
144     { "None",                   PIXMAN_REPEAT_NONE },
145     { "Normal",                 PIXMAN_REPEAT_NORMAL },
146     { "Reflect",                PIXMAN_REPEAT_REFLECT },
147     { "Pad",                    PIXMAN_REPEAT_PAD },
148 };
149 
150 static int
get_value(app_t * app,const named_int_t table[],const char * box_name)151 get_value (app_t *app, const named_int_t table[], const char *box_name)
152 {
153     GtkComboBox *box = GTK_COMBO_BOX (get_widget (app, box_name));
154 
155     return table[gtk_combo_box_get_active (box)].value;
156 }
157 
158 static void
copy_to_counterpart(app_t * app,GObject * object)159 copy_to_counterpart (app_t *app, GObject *object)
160 {
161     static const char *xy_map[] =
162     {
163 	"reconstruct_x_combo_box", "reconstruct_y_combo_box",
164 	"sample_x_combo_box",      "sample_y_combo_box",
165 	"scale_x_adjustment",      "scale_y_adjustment",
166     };
167     GObject *counterpart = NULL;
168     int i;
169 
170     for (i = 0; i < G_N_ELEMENTS (xy_map); i += 2)
171     {
172 	GObject *x = gtk_builder_get_object (app->builder, xy_map[i]);
173 	GObject *y = gtk_builder_get_object (app->builder, xy_map[i + 1]);
174 
175 	if (object == x)
176 	    counterpart = y;
177 	if (object == y)
178 	    counterpart = x;
179     }
180 
181     if (!counterpart)
182 	return;
183 
184     if (GTK_IS_COMBO_BOX (counterpart))
185     {
186 	gtk_combo_box_set_active (
187 	    GTK_COMBO_BOX (counterpart),
188 	    gtk_combo_box_get_active (
189 		GTK_COMBO_BOX (object)));
190     }
191     else if (GTK_IS_ADJUSTMENT (counterpart))
192     {
193 	gtk_adjustment_set_value (
194 	    GTK_ADJUSTMENT (counterpart),
195 	    gtk_adjustment_get_value (
196 		GTK_ADJUSTMENT (object)));
197     }
198 }
199 
200 static double
to_scale(double v)201 to_scale (double v)
202 {
203     return pow (1.15, v);
204 }
205 
206 static void
rescale(GtkWidget * may_be_null,app_t * app)207 rescale (GtkWidget *may_be_null, app_t *app)
208 {
209     pixman_f_transform_t ftransform;
210     pixman_transform_t transform;
211     double new_width, new_height;
212     double fscale_x, fscale_y;
213     double rotation;
214     pixman_fixed_t *params;
215     int n_params;
216     double sx, sy;
217 
218     pixman_f_transform_init_identity (&ftransform);
219 
220     if (may_be_null && gtk_toggle_button_get_active (
221 	    GTK_TOGGLE_BUTTON (get_widget (app, "lock_checkbutton"))))
222     {
223 	copy_to_counterpart (app, G_OBJECT (may_be_null));
224     }
225 
226     fscale_x = gtk_adjustment_get_value (app->scale_x_adjustment);
227     fscale_y = gtk_adjustment_get_value (app->scale_y_adjustment);
228     rotation = gtk_adjustment_get_value (app->rotate_adjustment);
229 
230     fscale_x = to_scale (fscale_x);
231     fscale_y = to_scale (fscale_y);
232 
233     new_width = pixman_image_get_width (app->original) * fscale_x;
234     new_height = pixman_image_get_height (app->original) * fscale_y;
235 
236     pixman_f_transform_scale (&ftransform, NULL, fscale_x, fscale_y);
237 
238     pixman_f_transform_translate (&ftransform, NULL, - new_width / 2.0, - new_height / 2.0);
239 
240     rotation = (rotation / 360.0) * 2 * M_PI;
241     pixman_f_transform_rotate (&ftransform, NULL, cos (rotation), sin (rotation));
242 
243     pixman_f_transform_translate (&ftransform, NULL, new_width / 2.0, new_height / 2.0);
244 
245     pixman_f_transform_invert (&ftransform, &ftransform);
246 
247     compute_extents (&ftransform, &sx, &sy);
248 
249     pixman_transform_from_pixman_f_transform (&transform, &ftransform);
250     pixman_image_set_transform (app->original, &transform);
251 
252     params = pixman_filter_create_separable_convolution (
253         &n_params,
254         sx * 65536.0 + 0.5,
255 	sy * 65536.0 + 0.5,
256 	get_value (app, filters, "reconstruct_x_combo_box"),
257 	get_value (app, filters, "reconstruct_y_combo_box"),
258 	get_value (app, filters, "sample_x_combo_box"),
259 	get_value (app, filters, "sample_y_combo_box"),
260 	gtk_adjustment_get_value (app->subsample_adjustment),
261 	gtk_adjustment_get_value (app->subsample_adjustment));
262 
263     pixman_image_set_filter (app->original, PIXMAN_FILTER_SEPARABLE_CONVOLUTION, params, n_params);
264 
265     pixman_image_set_repeat (
266         app->original, get_value (app, repeats, "repeat_combo_box"));
267 
268     free (params);
269 
270     app->scaled_width = ceil (new_width);
271     app->scaled_height = ceil (new_height);
272 
273     gtk_widget_set_size_request (
274         get_widget (app, "drawing_area"), new_width + 0.5, new_height + 0.5);
275 
276     gtk_widget_queue_draw (
277         get_widget (app, "drawing_area"));
278 }
279 
280 static gboolean
on_expose(GtkWidget * da,GdkEvent * event,gpointer data)281 on_expose (GtkWidget *da, GdkEvent *event, gpointer data)
282 {
283     app_t *app = data;
284     GdkRectangle *area = &event->expose.area;
285     cairo_surface_t *surface;
286     pixman_image_t *tmp;
287     cairo_t *cr;
288     uint32_t *pixels;
289 
290     pixels = calloc (1, area->width * area->height * 4);
291     tmp = pixman_image_create_bits (
292         PIXMAN_a8r8g8b8, area->width, area->height, pixels, area->width * 4);
293 
294     if (area->x < app->scaled_width && area->y < app->scaled_height)
295     {
296         pixman_image_composite (
297             PIXMAN_OP_SRC,
298             app->original, NULL, tmp,
299             area->x, area->y, 0, 0, 0, 0,
300             app->scaled_width - area->x, app->scaled_height - area->y);
301     }
302 
303     surface = cairo_image_surface_create_for_data (
304         (uint8_t *)pixels, CAIRO_FORMAT_ARGB32,
305         area->width, area->height, area->width * 4);
306 
307     cr = gdk_cairo_create (da->window);
308 
309     cairo_set_source_surface (cr, surface, area->x, area->y);
310 
311     cairo_paint (cr);
312 
313     cairo_destroy (cr);
314     cairo_surface_destroy (surface);
315     free (pixels);
316     pixman_image_unref (tmp);
317 
318     return TRUE;
319 }
320 
321 static void
set_up_combo_box(app_t * app,const char * box_name,int n_entries,const named_int_t table[])322 set_up_combo_box (app_t *app, const char *box_name,
323                   int n_entries, const named_int_t table[])
324 {
325     GtkWidget *widget = get_widget (app, box_name);
326     GtkListStore *model;
327     GtkCellRenderer *cell;
328     int i;
329 
330     model = gtk_list_store_new (1, G_TYPE_STRING);
331 
332     cell = gtk_cell_renderer_text_new ();
333     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE);
334     gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), cell,
335 				    "text", 0,
336 				    NULL);
337 
338     gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (model));
339 
340     for (i = 0; i < n_entries; ++i)
341     {
342 	const named_int_t *info = &(table[i]);
343 	GtkTreeIter iter;
344 
345 	gtk_list_store_append (model, &iter);
346 	gtk_list_store_set (model, &iter, 0, info->name, -1);
347     }
348 
349     gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
350 
351     g_signal_connect (widget, "changed", G_CALLBACK (rescale), app);
352 }
353 
354 static void
set_up_filter_box(app_t * app,const char * box_name)355 set_up_filter_box (app_t *app, const char *box_name)
356 {
357     set_up_combo_box (app, box_name, G_N_ELEMENTS (filters), filters);
358 }
359 
360 static char *
format_value(GtkWidget * widget,double value)361 format_value (GtkWidget *widget, double value)
362 {
363     return g_strdup_printf ("%.4f", to_scale (value));
364 }
365 
366 static app_t *
app_new(pixman_image_t * original)367 app_new (pixman_image_t *original)
368 {
369     GtkWidget *widget;
370     app_t *app = g_malloc (sizeof *app);
371     GError *err = NULL;
372 
373     app->builder = gtk_builder_new ();
374     app->original = original;
375 
376     if (!gtk_builder_add_from_file (app->builder, "scale.ui", &err))
377 	g_error ("Could not read file scale.ui: %s", err->message);
378 
379     app->scale_x_adjustment =
380         GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_x_adjustment"));
381     app->scale_y_adjustment =
382         GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_y_adjustment"));
383     app->rotate_adjustment =
384         GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "rotate_adjustment"));
385     app->subsample_adjustment =
386 	GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "subsample_adjustment"));
387 
388     g_signal_connect (app->scale_x_adjustment, "value_changed", G_CALLBACK (rescale), app);
389     g_signal_connect (app->scale_y_adjustment, "value_changed", G_CALLBACK (rescale), app);
390     g_signal_connect (app->rotate_adjustment, "value_changed", G_CALLBACK (rescale), app);
391     g_signal_connect (app->subsample_adjustment, "value_changed", G_CALLBACK (rescale), app);
392 
393     widget = get_widget (app, "scale_x_scale");
394     gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
395     g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app);
396     widget = get_widget (app, "scale_y_scale");
397     gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
398     g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app);
399     widget = get_widget (app, "rotate_scale");
400     gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
401 
402     widget = get_widget (app, "drawing_area");
403     g_signal_connect (widget, "expose_event", G_CALLBACK (on_expose), app);
404 
405     set_up_filter_box (app, "reconstruct_x_combo_box");
406     set_up_filter_box (app, "reconstruct_y_combo_box");
407     set_up_filter_box (app, "sample_x_combo_box");
408     set_up_filter_box (app, "sample_y_combo_box");
409 
410     set_up_combo_box (
411         app, "repeat_combo_box", G_N_ELEMENTS (repeats), repeats);
412 
413     g_signal_connect (
414 	gtk_builder_get_object (app->builder, "lock_checkbutton"),
415 	"toggled", G_CALLBACK (rescale), app);
416 
417     rescale (NULL, app);
418 
419     return app;
420 }
421 
422 int
main(int argc,char ** argv)423 main (int argc, char **argv)
424 {
425     GtkWidget *window;
426     pixman_image_t *image;
427     app_t *app;
428 
429     gtk_init (&argc, &argv);
430 
431     if (argc < 2)
432     {
433 	printf ("%s <image file>\n", argv[0]);
434 	return -1;
435     }
436 
437     if (!(image = pixman_image_from_file (argv[1], PIXMAN_a8r8g8b8)))
438     {
439 	printf ("Could not load image \"%s\"\n", argv[1]);
440 	return -1;
441     }
442 
443     app = app_new (image);
444 
445     window = get_widget (app, "main");
446 
447     g_signal_connect (window, "delete_event", G_CALLBACK (gtk_main_quit), NULL);
448 
449     gtk_window_set_default_size (GTK_WINDOW (window), 1024, 768);
450 
451     gtk_widget_show_all (window);
452 
453     gtk_main ();
454 
455     return 0;
456 }
457