diff options
author | Søren Sandmann Pedersen <sandmann@redhat.com> | 2008-10-06 18:27:47 -0400 |
---|---|---|
committer | Søren Sandmann Pedersen <sandmann@redhat.com> | 2008-10-06 18:27:47 -0400 |
commit | a2d5704d6b53d05ea659cd5da3ed763fa53e9ba2 (patch) | |
tree | 16b584cf5b1f7b46e4aba3267bb0b17349eb9fe9 |
Initial checkin
-rw-r--r-- | downscaling.c | 414 | ||||
-rw-r--r-- | downscaling.ui | 106 |
2 files changed, 520 insertions, 0 deletions
diff --git a/downscaling.c b/downscaling.c new file mode 100644 index 0000000..ddc8f47 --- /dev/null +++ b/downscaling.c @@ -0,0 +1,414 @@ +/* Copyright (C) 2005, 2008 Red Hat, Inc. + * Copyright © 2002 University of Southern California + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <math.h> +#include <gtk/gtk.h> +#include <pixman.h> +#include "downscaling.ui" +#include <stdlib.h> + +typedef struct +{ + GtkBuilder *builder; + int width; + int height; + guchar *pixels; + int stride; + cairo_format_t format; +} ImageInfo; + +/* Cutted and pasted from GDK */ +static void +convert (GdkPixbuf *pixbuf, ImageInfo *info) +{ + gint width = gdk_pixbuf_get_width (pixbuf); + gint height = gdk_pixbuf_get_height (pixbuf); + guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf); + int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf); + int n_channels = gdk_pixbuf_get_n_channels (pixbuf); + int cairo_stride; + guchar *cairo_pixels; + cairo_format_t format; + int j; + + if (n_channels == 3) + format = CAIRO_FORMAT_RGB24; + else + format = CAIRO_FORMAT_ARGB32; + + cairo_stride = cairo_format_stride_for_width (format, width); + cairo_pixels = g_malloc (height * cairo_stride); + + info->pixels = cairo_pixels; + + for (j = height; j; j--) + { + guchar *p = gdk_pixels; + guchar *q = cairo_pixels; + + if (n_channels == 3) + { + guchar *end = p + 3 * width; + + while (p < end) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + q[0] = p[2]; + q[1] = p[1]; + q[2] = p[0]; +#else + q[1] = p[0]; + q[2] = p[1]; + q[3] = p[2]; +#endif + p += 3; + q += 4; + } + } + + else + { + guchar *end = p + 4 * width; + guint t1,t2,t3; + +#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END + + while (p < end) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + MULT(q[0], p[2], p[3], t1); + MULT(q[1], p[1], p[3], t2); + MULT(q[2], p[0], p[3], t3); + q[3] = p[3]; +#else + q[0] = p[3]; + MULT(q[1], p[0], p[3], t1); + MULT(q[2], p[1], p[3], t2); + MULT(q[3], p[2], p[3], t3); +#endif + + p += 4; + q += 4; + } +#undef MULT + } + + gdk_pixels += gdk_rowstride; + cairo_pixels += cairo_stride; + } + + info->width = width; + info->height = height; + info->stride = cairo_stride; + info->format = format; +} + +static void +read_image (const char *filename, ImageInfo *info) +{ + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (filename, &err); + + if (!pixbuf) + { + g_print ("%s\n", err->message); + exit (1); + } + + convert (pixbuf, info); +} + +/* Cutted and pasted from various files in cairo */ +static cairo_bool_t +_cairo_matrix_is_identity (const cairo_matrix_t *matrix) +{ + return (matrix->xx == 1.0 && matrix->yx == 0.0 && + matrix->xy == 0.0 && matrix->yy == 1.0 && + matrix->x0 == 0.0 && matrix->y0 == 0.0); +} + +/* The 16.16 number must always be available */ +#define CAIRO_MAGIC_NUMBER_FIXED_16_16 (103079215104.0) + +static inline int32_t +_cairo_fixed_16_16_from_double (double d) +{ + union { + double d; + int32_t i[2]; + } u; + + u.d = d + CAIRO_MAGIC_NUMBER_FIXED_16_16; +#ifdef FLOAT_WORDS_BIGENDIAN + return u.i[1]; +#else + return u.i[0]; +#endif +} + +static void +_cairo_matrix_to_pixman_matrix (const cairo_matrix_t *matrix, + pixman_transform_t *pixman_transform) +{ + static const pixman_transform_t pixman_identity_transform = {{ + {1 << 16, 0, 0}, + { 0, 1 << 16, 0}, + { 0, 0, 1 << 16} + }}; + + if (_cairo_matrix_is_identity (matrix)) { + *pixman_transform = pixman_identity_transform; + } + else { + cairo_matrix_t inv = *matrix; + double x = 0, y = 0; + pixman_vector_t vector; + + pixman_transform->matrix[0][0] = _cairo_fixed_16_16_from_double (matrix->xx); + pixman_transform->matrix[0][1] = _cairo_fixed_16_16_from_double (matrix->xy); + pixman_transform->matrix[0][2] = _cairo_fixed_16_16_from_double (matrix->x0); + + pixman_transform->matrix[1][0] = _cairo_fixed_16_16_from_double (matrix->yx); + pixman_transform->matrix[1][1] = _cairo_fixed_16_16_from_double (matrix->yy); + pixman_transform->matrix[1][2] = _cairo_fixed_16_16_from_double (matrix->y0); + + pixman_transform->matrix[2][0] = 0; + pixman_transform->matrix[2][1] = 0; + pixman_transform->matrix[2][2] = 1 << 16; + + /* The conversion above breaks cairo's translation invariance: + * a translation of (a, b) in device space translates to + * a translation of (xx * a + xy * b, yx * a + yy * b) + * for cairo, while pixman uses rounded versions of xx ... yy. + * This error increases as a and b get larger. + * + * To compensate for this, we fix the point (0, 0) in pattern + * space and adjust pixman's transform to agree with cairo's at + * that point. */ + + /* Note: If we can't invert the transformation, skip the adjustment. */ + if (cairo_matrix_invert (&inv) != CAIRO_STATUS_SUCCESS) + return; + + /* find the device space coordinate that maps to (0, 0) */ + cairo_matrix_transform_point (&inv, &x, &y); + + /* transform the resulting device space coordinate back + * to the pattern space, using pixman's transform */ + vector.vector[0] = _cairo_fixed_16_16_from_double (x); + vector.vector[1] = _cairo_fixed_16_16_from_double (y); + vector.vector[2] = 1 << 16; + + if (!pixman_transform_point_3d (pixman_transform, &vector)) + return; + + /* Ideally, the vector should now be (0, 0). We can now compensate + * for the resulting error */ + pixman_transform->matrix[0][2] -= vector.vector[0]; + pixman_transform->matrix[1][2] -= vector.vector[1]; + } +} + +typedef void (* set_filter_func_t) (pixman_image_t *image, + int w, + int h); + +typedef struct +{ + const char *name; + set_filter_func_t set_filter; +} filter_t; + +static void +set_lanczos (pixman_image_t *image, int w, int h) +{ + +} + +static void +set_bilinear (pixman_image_t *image, int w, int h) +{ + pixman_image_set_filter (image, PIXMAN_FILTER_BILINEAR, NULL, 0); +} + +static void +set_nearest (pixman_image_t *image, int w, int h) +{ + pixman_image_set_filter (image, PIXMAN_FILTER_NEAREST, NULL, 0); +} + +static const filter_t filters[] = +{ + { "nearest", set_nearest }, + { "bilinear", set_bilinear }, + { "lanczos", set_bilinear /* FIXME */ }, + { NULL }, +}; + +static void +set_filter (pixman_image_t *image, ImageInfo *info) +{ + int i; + + for (i = 0; filters[i].name != NULL; ++i) + { + GtkWidget *widget = + GTK_WIDGET (gtk_builder_get_object (info->builder, + filters[i].name)); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + filters[i].set_filter (image, info->width, info->height); + return; + } + } + + g_assert_not_reached(); +} + +static pixman_image_t * +transform_data (ImageInfo *info) +{ + pixman_image_t *source = pixman_image_create_bits ( + info->format == CAIRO_FORMAT_RGB24? PIXMAN_x8r8g8b8 : PIXMAN_a8r8g8b8, + info->width, info->height, (guint32 *)info->pixels, info->stride); + pixman_image_t *dest = pixman_image_create_bits ( + PIXMAN_a8r8g8b8, info->width, info->height, + NULL, -1); + GtkWidget *scaler = (GtkWidget *)gtk_builder_get_object (info->builder, "scaler"); + pixman_transform_t trans; + cairo_matrix_t cmatrix; + double scale; + pixman_color_t color = { 0xffff, 0xffff, 0xffff, 0xffff }; + pixman_rectangle16_t rect = { 0, 0, info->width, info->height }; + + pixman_image_fill_rectangles (PIXMAN_OP_SRC, + dest, + &color, 1, &rect); + + scale = gtk_range_get_value (GTK_RANGE (scaler)); + + scale = pow (1.8, scale); + + cairo_matrix_init_identity (&cmatrix); + cairo_matrix_translate (&cmatrix, + info->width / 2.0, info->height / 2.0); + + cairo_matrix_scale (&cmatrix, scale, scale); + + cairo_matrix_translate (&cmatrix, + - info->width / 2.0, - info->height / 2.0); + + cairo_matrix_invert (&cmatrix); + + _cairo_matrix_to_pixman_matrix (&cmatrix, &trans); + + pixman_image_set_transform (source, &trans); + + set_filter (source, info); + + pixman_image_composite (PIXMAN_OP_OVER, + source, NULL, dest, + 0, 0, 0, 0, 0, 0, info->width, info->height); + + pixman_image_unref (source); + + return dest; +} + +gboolean +on_expose (GtkWidget *widget, + GdkEvent *event, + gpointer data) +{ + ImageInfo *info = data; + + cairo_t *cr = gdk_cairo_create (widget->window); + pixman_image_t *transformed = transform_data (info); + cairo_surface_t *surface = cairo_image_surface_create_for_data ( + (guchar *)pixman_image_get_data (transformed), + CAIRO_FORMAT_ARGB32, + info->width, info->height, + pixman_image_get_stride (transformed)); + + cairo_set_source_surface (cr, surface, 0, 0); + + cairo_paint (cr); + + cairo_surface_destroy (surface); + cairo_destroy (cr); + + pixman_image_unref (transformed); + + return TRUE; +} + +int +main (int argc, char **argv) +{ + GtkWidget *window, *da; + ImageInfo info; + GtkWidget *scaler; + int i; + + gtk_init (&argc, &argv); + + info.builder = gtk_builder_new (); + + gtk_builder_add_from_string (info.builder, uidef, -1, NULL); + + window = GTK_WIDGET (gtk_builder_get_object (info.builder, "window")); + da = GTK_WIDGET (gtk_builder_get_object (info.builder, "drawing_area")); + scaler = GTK_WIDGET (gtk_builder_get_object (info.builder, "scaler")); + + if (argc < 2) + { + g_print ("%s <image file>\n", argv[0]); + return 0; + } + + read_image (argv[1], &info); + + for (i = 0; filters[i].name != NULL; ++i) + { + GtkWidget *w = gtk_builder_get_object (info.builder, filters[i].name); + + g_signal_connect_swapped (w, "toggled", + G_CALLBACK (gtk_widget_queue_draw), da); + } + + gtk_window_set_default_size (GTK_WINDOW (window), info.width, info.height); + +#if 0 + g_print ("width %d, height %d, pixels: %p, stride: %d, format: %d\n", + info.width, info.height, info.pixels, info.stride, info.format); +#endif + + g_signal_connect_swapped (scaler, "value_changed", G_CALLBACK (gtk_widget_queue_draw), da); + + g_signal_connect (da, "expose_event", G_CALLBACK (on_expose), &info); + + gtk_widget_show_all (window); + + g_signal_connect (window, "delete_event", gtk_main_quit, NULL); + + gtk_main (); + + return 0; +} diff --git a/downscaling.ui b/downscaling.ui new file mode 100644 index 0000000..f0a3e2b --- /dev/null +++ b/downscaling.ui @@ -0,0 +1,106 @@ +const char *uidef = +"<?xml version=\"1.0\"?>" \ +"<!--Generated with glade3 3.4.4 on Mon Oct 6 17:08:20 2008 -->" \ +"<interface>" \ +" <object class=\"GtkAdjustment\" id=\"adjustment1\">" \ +" <property name=\"upper\">5</property>" \ +" <property name=\"lower\">-5</property>" \ +" <property name=\"page_increment\">0</property>" \ +" <property name=\"step_increment\">0.29999999999999999</property>" \ +" <property name=\"page_size\">0</property>" \ +" <property name=\"value\">0</property>" \ +" </object>" \ +" <object class=\"GtkWindow\" id=\"window\">" \ +" <child>" \ +" <object class=\"GtkHBox\" id=\"hbox1\">" \ +" <property name=\"visible\">True</property>" \ +" <child>" \ +" <object class=\"GtkDrawingArea\" id=\"drawing_area\">" \ +" <property name=\"visible\">True</property>" \ +" </object>" \ +" </child>" \ +" <child>" \ +" <object class=\"GtkVSeparator\" id=\"vseparator1\">" \ +" <property name=\"visible\">True</property>" \ +" </object>" \ +" <packing>" \ +" <property name=\"expand\">False</property>" \ +" <property name=\"position\">1</property>" \ +" </packing>" \ +" </child>" \ +" <child>" \ +" <object class=\"GtkVBox\" id=\"vbox1\">" \ +" <property name=\"visible\">True</property>" \ +" <property name=\"spacing\">6</property>" \ +" <child>" \ +" <object class=\"GtkHScale\" id=\"scaler\">" \ +" <property name=\"visible\">True</property>" \ +" <property name=\"can_focus\">True</property>" \ +" <property name=\"adjustment\">adjustment1</property>" \ +" </object>" \ +" <packing>" \ +" <property name=\"expand\">False</property>" \ +" </packing>" \ +" </child>" \ +" <child>" \ +" <object class=\"GtkHSeparator\" id=\"hseparator1\">" \ +" <property name=\"visible\">True</property>" \ +" </object>" \ +" <packing>" \ +" <property name=\"expand\">False</property>" \ +" <property name=\"position\">1</property>" \ +" </packing>" \ +" </child>" \ +" <child>" \ +" <object class=\"GtkRadioButton\" id=\"nearest\">" \ +" <property name=\"visible\">True</property>" \ +" <property name=\"can_focus\">True</property>" \ +" <property name=\"label\" translatable=\"yes\">Nearest</property>" \ +" <property name=\"active\">True</property>" \ +" <property name=\"draw_indicator\">True</property>" \ +" </object>" \ +" <packing>" \ +" <property name=\"expand\">False</property>" \ +" <property name=\"position\">2</property>" \ +" </packing>" \ +" </child>" \ +" <child>" \ +" <object class=\"GtkRadioButton\" id=\"bilinear\">" \ +" <property name=\"visible\">True</property>" \ +" <property name=\"can_focus\">True</property>" \ +" <property name=\"label\" translatable=\"yes\">Bilinear</property>" \ +" <property name=\"active\">True</property>" \ +" <property name=\"draw_indicator\">True</property>" \ +" <property name=\"group\">nearest</property>" \ +" </object>" \ +" <packing>" \ +" <property name=\"expand\">False</property>" \ +" <property name=\"position\">3</property>" \ +" </packing>" \ +" </child>" \ +" <child>" \ +" <object class=\"GtkRadioButton\" id=\"lanczos\">" \ +" <property name=\"visible\">True</property>" \ +" <property name=\"can_focus\">True</property>" \ +" <property name=\"label\" translatable=\"yes\">Lanczos</property>" \ +" <property name=\"active\">True</property>" \ +" <property name=\"draw_indicator\">True</property>" \ +" <property name=\"group\">nearest</property>" \ +" </object>" \ +" <packing>" \ +" <property name=\"expand\">False</property>" \ +" <property name=\"position\">4</property>" \ +" </packing>" \ +" </child>" \ +" </object>" \ +" <packing>" \ +" <property name=\"expand\">False</property>" \ +" <property name=\"padding\">6</property>" \ +" <property name=\"position\">2</property>" \ +" </packing>" \ +" </child>" \ +" </object>" \ +" </child>" \ +" </object>" \ +"</interface>"; + |