summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSøren Sandmann Pedersen <sandmann@redhat.com>2008-10-06 18:27:47 -0400
committerSøren Sandmann Pedersen <sandmann@redhat.com>2008-10-06 18:27:47 -0400
commita2d5704d6b53d05ea659cd5da3ed763fa53e9ba2 (patch)
tree16b584cf5b1f7b46e4aba3267bb0b17349eb9fe9
Initial checkin
-rw-r--r--downscaling.c414
-rw-r--r--downscaling.ui106
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>";
+