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