scale.c revision 14b11b2b
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 "pixman-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 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 */ 117static void 118compute_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 124typedef struct 125{ 126 char name [20]; 127 int value; 128} named_int_t; 129 130static 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 142static 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 150static int 151get_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 158static void 159copy_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 200static double 201to_scale (double v) 202{ 203 return pow (1.15, v); 204} 205 206static void 207rescale (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 280static gboolean 281on_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) 282{ 283 app_t *app = user_data; 284 GdkRectangle area; 285 cairo_surface_t *surface; 286 pixman_image_t *tmp; 287 uint32_t *pixels; 288 289 gdk_cairo_get_clip_rectangle(cr, &area); 290 291 pixels = calloc (1, area.width * area.height * 4); 292 tmp = pixman_image_create_bits ( 293 PIXMAN_a8r8g8b8, area.width, area.height, pixels, area.width * 4); 294 295 if (area.x < app->scaled_width && area.y < app->scaled_height) 296 { 297 pixman_image_composite ( 298 PIXMAN_OP_SRC, 299 app->original, NULL, tmp, 300 area.x, area.y, 0, 0, 0, 0, 301 app->scaled_width - area.x, app->scaled_height - area.y); 302 } 303 304 surface = cairo_image_surface_create_for_data ( 305 (uint8_t *)pixels, CAIRO_FORMAT_ARGB32, 306 area.width, area.height, area.width * 4); 307 308 cairo_set_source_surface (cr, surface, area.x, area.y); 309 310 cairo_paint (cr); 311 312 cairo_surface_destroy (surface); 313 free (pixels); 314 pixman_image_unref (tmp); 315 316 return TRUE; 317} 318 319static void 320set_up_combo_box (app_t *app, const char *box_name, 321 int n_entries, const named_int_t table[]) 322{ 323 GtkWidget *widget = get_widget (app, box_name); 324 GtkListStore *model; 325 GtkCellRenderer *cell; 326 int i; 327 328 model = gtk_list_store_new (1, G_TYPE_STRING); 329 330 cell = gtk_cell_renderer_text_new (); 331 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE); 332 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), cell, 333 "text", 0, 334 NULL); 335 336 gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (model)); 337 338 for (i = 0; i < n_entries; ++i) 339 { 340 const named_int_t *info = &(table[i]); 341 GtkTreeIter iter; 342 343 gtk_list_store_append (model, &iter); 344 gtk_list_store_set (model, &iter, 0, info->name, -1); 345 } 346 347 gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); 348 349 g_signal_connect (widget, "changed", G_CALLBACK (rescale), app); 350} 351 352static void 353set_up_filter_box (app_t *app, const char *box_name) 354{ 355 set_up_combo_box (app, box_name, G_N_ELEMENTS (filters), filters); 356} 357 358static char * 359format_value (GtkWidget *widget, double value) 360{ 361 return g_strdup_printf ("%.4f", to_scale (value)); 362} 363 364static app_t * 365app_new (pixman_image_t *original) 366{ 367 GtkWidget *widget; 368 app_t *app = g_malloc (sizeof *app); 369 GError *err = NULL; 370 371 app->builder = gtk_builder_new (); 372 app->original = original; 373 374 if (!gtk_builder_add_from_file (app->builder, "scale.ui", &err)) 375 g_error ("Could not read file scale.ui: %s", err->message); 376 377 app->scale_x_adjustment = 378 GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_x_adjustment")); 379 app->scale_y_adjustment = 380 GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_y_adjustment")); 381 app->rotate_adjustment = 382 GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "rotate_adjustment")); 383 app->subsample_adjustment = 384 GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "subsample_adjustment")); 385 386 g_signal_connect (app->scale_x_adjustment, "value_changed", G_CALLBACK (rescale), app); 387 g_signal_connect (app->scale_y_adjustment, "value_changed", G_CALLBACK (rescale), app); 388 g_signal_connect (app->rotate_adjustment, "value_changed", G_CALLBACK (rescale), app); 389 g_signal_connect (app->subsample_adjustment, "value_changed", G_CALLBACK (rescale), app); 390 391 widget = get_widget (app, "scale_x_scale"); 392 gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL); 393 g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app); 394 widget = get_widget (app, "scale_y_scale"); 395 gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL); 396 g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app); 397 widget = get_widget (app, "rotate_scale"); 398 gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL); 399 400 widget = get_widget (app, "drawing_area"); 401 g_signal_connect (widget, "draw", G_CALLBACK (on_draw), app); 402 403 set_up_filter_box (app, "reconstruct_x_combo_box"); 404 set_up_filter_box (app, "reconstruct_y_combo_box"); 405 set_up_filter_box (app, "sample_x_combo_box"); 406 set_up_filter_box (app, "sample_y_combo_box"); 407 408 set_up_combo_box ( 409 app, "repeat_combo_box", G_N_ELEMENTS (repeats), repeats); 410 411 g_signal_connect ( 412 gtk_builder_get_object (app->builder, "lock_checkbutton"), 413 "toggled", G_CALLBACK (rescale), app); 414 415 rescale (NULL, app); 416 417 return app; 418} 419 420int 421main (int argc, char **argv) 422{ 423 GtkWidget *window; 424 pixman_image_t *image; 425 app_t *app; 426 427 gtk_init (&argc, &argv); 428 429 if (argc < 2) 430 { 431 printf ("%s <image file>\n", argv[0]); 432 return -1; 433 } 434 435 if (!(image = pixman_image_from_file (argv[1], PIXMAN_a8r8g8b8))) 436 { 437 printf ("Could not load image \"%s\"\n", argv[1]); 438 return -1; 439 } 440 441 app = app_new (image); 442 443 window = get_widget (app, "main"); 444 445 g_signal_connect (window, "delete_event", G_CALLBACK (gtk_main_quit), NULL); 446 447 gtk_window_set_default_size (GTK_WINDOW (window), 1024, 768); 448 449 gtk_widget_show_all (window); 450 451 gtk_main (); 452 453 return 0; 454} 455