pixman: Branch 'master' - 7 commits

Søren Sandmann Pedersen sandmann at kemper.freedesktop.org
Sat Dec 8 07:55:45 PST 2012


 demos/Makefile.am          |    8 
 demos/conical-test.c       |  134 +++++++++++++
 demos/gtk-utils.c          |   66 ++++++
 demos/gtk-utils.h          |    3 
 demos/scale.c              |  431 +++++++++++++++++++++++++++++++++++++++++++++
 demos/scale.ui             |  302 +++++++++++++++++++++++++++++++
 demos/zone_plate.png       |binary
 pixman/Makefile.sources    |    1 
 pixman/pixman-bits-image.c |  102 ++++++++++
 pixman/pixman-filter.c     |  340 +++++++++++++++++++++++++++++++++++
 pixman/pixman-image.c      |   19 +
 pixman/pixman.c            |    8 
 pixman/pixman.h            |   50 +++++
 pixman/rounding.txt        |   33 +++
 14 files changed, 1492 insertions(+), 5 deletions(-)

New commits:
commit 4f18ba30cea56331e30992242201b20954c8f7f2
Author: Søren Sandmann Pedersen <ssp at redhat.com>
Date:   Tue Dec 4 13:17:49 2012 -0500

    Add demo program for conical gradients
    
    This new test is derived from radial-test.c and displays conical
    gradients at various angles.
    
    It also demonstrates how PIXMAN_REPEAT_NORMAL is supposed to work when
    used with a gradient specification where the first stop is not a 0.0:
    In this case the gradient is supposed to have a smooth transition from
    the last stop back to the first stop with no sharp transitions. It
    also shows that the repeat mode is not ignored for conical gradients
    as one might be tempted to think.

diff --git a/demos/Makefile.am b/demos/Makefile.am
index 9e9db81..3f2a3fa 100644
--- a/demos/Makefile.am
+++ b/demos/Makefile.am
@@ -15,6 +15,7 @@ DEMOS =				\
 	composite-test		\
 	gradient-test		\
 	radial-test		\
+	conical-test		\
 	alpha-test		\
 	screen-test		\
 	convolution-test	\
@@ -37,6 +38,7 @@ trap_test_SOURCES = trap-test.c $(GTK_UTILS)
 screen_test_SOURCES = screen-test.c $(GTK_UTILS)
 convolution_test_SOURCES = convolution-test.c $(GTK_UTILS)
 radial_test_SOURCES = radial-test.c $(GTK_UTILS)
+conical_test_SOURCES = conical-test.c $(GTK_UTILS)
 tri_test_SOURCES = tri-test.c $(GTK_UTILS)
 checkerboard_SOURCES = checkerboard.c $(GTK_UTILS)
 srgb_test_SOURCES = srgb-test.c $(GTK_UTILS)
diff --git a/demos/conical-test.c b/demos/conical-test.c
new file mode 100644
index 0000000..1e08e42
--- /dev/null
+++ b/demos/conical-test.c
@@ -0,0 +1,134 @@
+#include "../test/utils.h"
+#include "gtk-utils.h"
+
+#define SIZE 128
+#define GRADIENTS_PER_ROW 7
+#define NUM_ROWS ((NUM_GRADIENTS + GRADIENTS_PER_ROW - 1) / GRADIENTS_PER_ROW)
+#define WIDTH (SIZE * GRADIENTS_PER_ROW)
+#define HEIGHT (SIZE * NUM_ROWS)
+#define NUM_GRADIENTS 35
+
+#define double_to_color(x)					\
+    (((uint32_t) ((x)*65536)) - (((uint32_t) ((x)*65536)) >> 16))
+
+#define PIXMAN_STOP(offset,r,g,b,a)		\
+    { pixman_double_to_fixed (offset),		\
+	{					\
+	    double_to_color (r),		\
+		double_to_color (g),		\
+		double_to_color (b),		\
+		double_to_color (a)		\
+	}					\
+    }
+
+
+static const pixman_gradient_stop_t stops[] = {
+    PIXMAN_STOP (0.25,       1, 0, 0, 0.7),
+    PIXMAN_STOP (0.5,        1, 1, 0, 0.7),
+    PIXMAN_STOP (0.75,       0, 1, 0, 0.7),
+    PIXMAN_STOP (1.0,        0, 0, 1, 0.7)
+};
+
+#define NUM_STOPS (sizeof (stops) / sizeof (stops[0]))
+
+static pixman_image_t *
+create_conical (int index)
+{
+    pixman_point_fixed_t c;
+    double angle;
+
+    c.x = pixman_double_to_fixed (0);
+    c.y = pixman_double_to_fixed (0);
+
+    angle = (0.5 / NUM_GRADIENTS + index / (double)NUM_GRADIENTS) * 720 - 180;
+
+    return pixman_image_create_conical_gradient (
+	&c, pixman_double_to_fixed (angle), stops, NUM_STOPS);
+}
+
+#define CHECK_SIZE 25
+
+static void
+fill_checkerboard (pixman_image_t *image, int width, int height)
+{
+#define C1 0xaaaa
+#define C2 0x8888
+
+    pixman_color_t check1 = { C1, C1, C1, 0xffff };
+    pixman_color_t check2 = { C2, C2, C2, 0xffff };
+    pixman_image_t *c1, *c2;
+    int i, j;
+
+    c1 = pixman_image_create_solid_fill (&check1);
+    c2 = pixman_image_create_solid_fill (&check2);
+
+    for (j = 0; j < height; j += CHECK_SIZE)
+    {
+	for (i = 0; i < width; i += CHECK_SIZE)
+	{
+	    pixman_image_t *src;
+
+	    if ((((i / CHECK_SIZE) ^ (j / CHECK_SIZE)) & 1) == 0)
+		src = c1;
+	    else
+		src = c2;
+	    
+	    pixman_image_composite32 (PIXMAN_OP_SRC, src, NULL, image,
+				      0, 0, 0, 0, i, j,
+				      CHECK_SIZE, CHECK_SIZE);
+	}
+    }
+}
+
+int
+main (int argc, char **argv)
+{
+    pixman_transform_t transform;
+    pixman_image_t *src_img, *dest_img;
+    int i;
+
+    enable_divbyzero_exceptions ();
+
+    dest_img = pixman_image_create_bits (PIXMAN_a8r8g8b8,
+					 WIDTH, HEIGHT,
+					 NULL, 0);
+
+    fill_checkerboard (dest_img, WIDTH, HEIGHT);
+
+    pixman_transform_init_identity (&transform);
+
+    pixman_transform_translate (NULL, &transform,
+				pixman_double_to_fixed (0.5),
+				pixman_double_to_fixed (0.5));
+
+    pixman_transform_scale (NULL, &transform,
+			    pixman_double_to_fixed (SIZE),
+			    pixman_double_to_fixed (SIZE));
+    pixman_transform_translate (NULL, &transform,
+				pixman_double_to_fixed (0.5),
+				pixman_double_to_fixed (0.5));
+
+    for (i = 0; i < NUM_GRADIENTS; i++)
+    {
+	int column = i % GRADIENTS_PER_ROW;
+	int row = i / GRADIENTS_PER_ROW;
+
+	src_img = create_conical (i); 
+	pixman_image_set_repeat (src_img, PIXMAN_REPEAT_NORMAL);
+   
+	pixman_image_set_transform (src_img, &transform);
+	
+	pixman_image_composite32 (
+	    PIXMAN_OP_OVER, src_img, NULL,dest_img,
+	    0, 0, 0, 0, column * SIZE, row * SIZE,
+	    SIZE, SIZE);
+	
+	pixman_image_unref (src_img);
+    }
+
+    show_image (dest_img);
+
+    pixman_image_unref (dest_img);
+
+    return 0;
+}
commit 3a98787bddeb007a1cd2b86235205774c15250f2
Author: Søren Sandmann Pedersen <ssp at redhat.com>
Date:   Mon Nov 12 12:27:39 2012 -0500

    Add demos/zone_plate.png
    
    The zone plate image is a useful test case for image scalers because
    it contains all representable frequencies, so any imperfection in
    resampling filters will show up as Moire patterns.
    
    This version is symmetric around the midpoint of the image, so since
    rotating it is supposed to be a noop, it can also be used to verify
    that the resampling filters don't shift the image.
    
    V2: Run the file through OptiPNG to cut the size in half, as suggested
    by Siarhei.

diff --git a/demos/zone_plate.png b/demos/zone_plate.png
new file mode 100644
index 0000000..519291d
Binary files /dev/null and b/demos/zone_plate.png differ
commit 97491ed26cfd4bad9cceffa789bfcbef77421d38
Author: Søren Sandmann Pedersen <ssp at redhat.com>
Date:   Thu Nov 22 10:18:26 2012 -0500

    demos: Add new demo program, "scale"
    
    This program allows interactively scaling and rotating images with
    using various filters and repeat modes. It uses
    pixman_filter_create_separate_convolution() to generate the filters.

diff --git a/demos/Makefile.am b/demos/Makefile.am
index 96c43f0..9e9db81 100644
--- a/demos/Makefile.am
+++ b/demos/Makefile.am
@@ -23,9 +23,10 @@ DEMOS =				\
 	quad2quad		\
 	checkerboard		\
 	srgb-trap-test		\
-	srgb-test
+	srgb-test		\
+	scale
 
-EXTRA_DIST = parrot.c parrot.jpg
+EXTRA_DIST = parrot.c parrot.jpg scale.ui
 
 gradient_test_SOURCES = gradient-test.c $(GTK_UTILS)
 alpha_test_SOURCES = alpha-test.c $(GTK_UTILS)
@@ -40,6 +41,7 @@ tri_test_SOURCES = tri-test.c $(GTK_UTILS)
 checkerboard_SOURCES = checkerboard.c $(GTK_UTILS)
 srgb_test_SOURCES = srgb-test.c $(GTK_UTILS)
 srgb_trap_test_SOURCES = srgb-trap-test.c $(GTK_UTILS)
+scale_SOURCES = scale.c $(GTK_UTILS)
 
 noinst_PROGRAMS = $(DEMOS)
 
diff --git a/demos/scale.c b/demos/scale.c
new file mode 100644
index 0000000..9100ff7
--- /dev/null
+++ b/demos/scale.c
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2012, Red Hat, Inc.
+ * Copyright 2012, Soren Sandmann
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Author: Soren Sandmann <soren.sandmann at gmail.com>
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <math.h>
+#include <gtk/gtk.h>
+#include <pixman.h>
+#include <stdlib.h>
+#include "gtk-utils.h"
+
+typedef struct
+{
+    GtkBuilder *        builder;
+    pixman_image_t *	original;
+    GtkAdjustment *     scale_x_adjustment;
+    GtkAdjustment *     scale_y_adjustment;
+    GtkAdjustment *     rotate_adjustment;
+    int                 scaled_width;
+    int                 scaled_height;
+} app_t;
+
+static GtkWidget *
+get_widget (app_t *app, const char *name)
+{
+    GtkWidget *widget = GTK_WIDGET (gtk_builder_get_object (app->builder, name));
+
+    if (!widget)
+        g_error ("Widget %s not found\n", name);
+
+    return widget;
+}
+
+static double
+min4 (double a, double b, double c, double d)
+{
+    double m1, m2;
+
+    m1 = MIN (a, b);
+    m2 = MIN (c, d);
+    return MIN (m1, m2);
+}
+
+static double
+max4 (double a, double b, double c, double d)
+{
+    double m1, m2;
+
+    m1 = MAX (a, b);
+    m2 = MAX (c, d);
+    return MAX (m1, m2);
+}
+
+static void
+compute_extents (pixman_f_transform_t *trans, double *sx, double *sy)
+{
+    double min_x, max_x, min_y, max_y;
+    pixman_f_vector_t v[4] =
+    {
+	{ { 1, 1, 1 } },
+	{ { -1, 1, 1 } },
+	{ { -1, -1, 1 } },
+	{ { 1, -1, 1 } },
+    };
+
+    pixman_f_transform_point (trans, &v[0]);
+    pixman_f_transform_point (trans, &v[1]);
+    pixman_f_transform_point (trans, &v[2]);
+    pixman_f_transform_point (trans, &v[3]);
+
+    min_x = min4 (v[0].v[0], v[1].v[0], v[2].v[0], v[3].v[0]);
+    max_x = max4 (v[0].v[0], v[1].v[0], v[2].v[0], v[3].v[0]);
+    min_y = min4 (v[0].v[1], v[1].v[1], v[2].v[1], v[3].v[1]);
+    max_y = max4 (v[0].v[1], v[1].v[1], v[2].v[1], v[3].v[1]);
+
+    *sx = (max_x - min_x) / 2.0;
+    *sy = (max_y - min_y) / 2.0;
+}
+
+typedef struct
+{
+    char		name [20];
+    pixman_kernel_t	value;
+} named_int_t;
+
+static const named_int_t filters[] =
+{
+    { "Box",			PIXMAN_KERNEL_BOX },
+    { "Impulse",		PIXMAN_KERNEL_IMPULSE },
+    { "Linear",			PIXMAN_KERNEL_LINEAR },
+    { "Cubic",			PIXMAN_KERNEL_CUBIC },
+    { "Lanczos2",		PIXMAN_KERNEL_LANCZOS2 },
+    { "Lanczos3",		PIXMAN_KERNEL_LANCZOS3 },
+    { "Lanczos3 Stretched",	PIXMAN_KERNEL_LANCZOS3_STRETCHED },
+    { "Gaussian",		PIXMAN_KERNEL_GAUSSIAN },
+};
+
+static const named_int_t repeats[] =
+{
+    { "None",                   PIXMAN_REPEAT_NONE },
+    { "Normal",                 PIXMAN_REPEAT_NORMAL },
+    { "Reflect",                PIXMAN_REPEAT_REFLECT },
+    { "Pad",                    PIXMAN_REPEAT_PAD },
+};
+
+static pixman_kernel_t
+get_value (app_t *app, const named_int_t table[], const char *box_name)
+{
+    GtkComboBox *box = GTK_COMBO_BOX (get_widget (app, box_name));
+
+    return table[gtk_combo_box_get_active (box)].value;
+}
+
+static void
+copy_to_counterpart (app_t *app, GObject *object)
+{
+    static const char *xy_map[] =
+    {
+	"reconstruct_x_combo_box", "reconstruct_y_combo_box",
+	"sample_x_combo_box",      "sample_y_combo_box",
+	"scale_x_adjustment",      "scale_y_adjustment",
+    };
+    GObject *counterpart = NULL;
+    int i;
+
+    for (i = 0; i < G_N_ELEMENTS (xy_map); i += 2)
+    {
+	GObject *x = gtk_builder_get_object (app->builder, xy_map[i]);
+	GObject *y = gtk_builder_get_object (app->builder, xy_map[i + 1]);
+
+	if (object == x)
+	    counterpart = y;
+	if (object == y)
+	    counterpart = x;
+    }
+
+    if (!counterpart)
+	return;
+    
+    if (GTK_IS_COMBO_BOX (counterpart))
+    {
+	gtk_combo_box_set_active (
+	    GTK_COMBO_BOX (counterpart),
+	    gtk_combo_box_get_active (
+		GTK_COMBO_BOX (object)));
+    }
+    else if (GTK_IS_ADJUSTMENT (counterpart))
+    {
+	gtk_adjustment_set_value (
+	    GTK_ADJUSTMENT (counterpart),
+	    gtk_adjustment_get_value (
+		GTK_ADJUSTMENT (object)));
+    }
+}
+
+static double
+to_scale (double v)
+{
+    return pow (1.15, v);
+}
+
+static void
+rescale (GtkWidget *may_be_null, app_t *app)
+{
+    pixman_f_transform_t ftransform;
+    pixman_transform_t transform;
+    double new_width, new_height;
+    double fscale_x, fscale_y;
+    double rotation;
+    pixman_fixed_t *params;
+    int n_params;
+    double sx, sy;
+
+    pixman_f_transform_init_identity (&ftransform);
+
+    if (may_be_null && gtk_toggle_button_get_active (
+	    GTK_TOGGLE_BUTTON (get_widget (app, "lock_checkbutton"))))
+    {
+	copy_to_counterpart (app, G_OBJECT (may_be_null));
+    }
+    
+    fscale_x = gtk_adjustment_get_value (app->scale_x_adjustment);
+    fscale_y = gtk_adjustment_get_value (app->scale_y_adjustment);
+    rotation = gtk_adjustment_get_value (app->rotate_adjustment);
+
+    fscale_x = to_scale (fscale_x);
+    fscale_y = to_scale (fscale_y);
+    
+    new_width = pixman_image_get_width (app->original) * fscale_x;
+    new_height = pixman_image_get_height (app->original) * fscale_y;
+
+    pixman_f_transform_scale (&ftransform, NULL, fscale_x, fscale_y);
+
+    pixman_f_transform_translate (&ftransform, NULL, - new_width / 2.0, - new_height / 2.0);
+
+    rotation = (rotation / 360.0) * 2 * M_PI;
+    pixman_f_transform_rotate (&ftransform, NULL, cos (rotation), sin (rotation));
+
+    pixman_f_transform_translate (&ftransform, NULL, new_width / 2.0, new_height / 2.0);
+
+    pixman_f_transform_invert (&ftransform, &ftransform);
+
+    compute_extents (&ftransform, &sx, &sy);
+    
+    pixman_transform_from_pixman_f_transform (&transform, &ftransform);
+    pixman_image_set_transform (app->original, &transform);
+
+    params = pixman_filter_create_separable_convolution (
+        &n_params,
+        sx * 65536.0 + 0.5,
+	sy * 65536.0 + 0.5,
+	get_value (app, filters, "reconstruct_x_combo_box"),
+	get_value (app, filters, "reconstruct_y_combo_box"),
+	get_value (app, filters, "sample_x_combo_box"),
+	get_value (app, filters, "sample_y_combo_box"),
+        4, 4);
+
+    pixman_image_set_filter (app->original, PIXMAN_FILTER_SEPARABLE_CONVOLUTION, params, n_params);
+
+    pixman_image_set_repeat (
+        app->original, get_value (app, repeats, "repeat_combo_box"));
+    
+    free (params);
+
+    app->scaled_width = ceil (new_width);
+    app->scaled_height = ceil (new_height);
+    
+    gtk_widget_set_size_request (
+        get_widget (app, "drawing_area"), new_width + 0.5, new_height + 0.5);
+
+    gtk_widget_queue_draw (
+        get_widget (app, "drawing_area"));
+}
+
+static gboolean
+on_expose (GtkWidget *da, GdkEvent *event, gpointer data)
+{
+    app_t *app = data;
+    GdkRectangle *area = &event->expose.area;
+    cairo_surface_t *surface;
+    pixman_image_t *tmp;
+    cairo_t *cr;
+    uint32_t *pixels;
+
+    pixels = calloc (1, area->width * area->height * 4);
+    tmp = pixman_image_create_bits (
+        PIXMAN_a8r8g8b8, area->width, area->height, pixels, area->width * 4);
+
+    if (area->x < app->scaled_width && area->y < app->scaled_height)
+    {
+        pixman_image_composite (
+            PIXMAN_OP_SRC,
+            app->original, NULL, tmp,
+            area->x, area->y, 0, 0, 0, 0,
+            app->scaled_width - area->x, app->scaled_height - area->y);
+    }
+
+    surface = cairo_image_surface_create_for_data (
+        (uint8_t *)pixels, CAIRO_FORMAT_ARGB32,
+        area->width, area->height, area->width * 4);
+
+    cr = gdk_cairo_create (da->window);
+
+    cairo_set_source_surface (cr, surface, area->x, area->y);
+
+    cairo_paint (cr);
+
+    cairo_destroy (cr);
+    cairo_surface_destroy (surface);
+    free (pixels);
+    pixman_image_unref (tmp);
+
+    return TRUE;
+}
+
+static void
+set_up_combo_box (app_t *app, const char *box_name,
+                  int n_entries, const named_int_t table[])
+{
+    GtkWidget *widget = get_widget (app, box_name);
+    GtkListStore *model;
+    GtkCellRenderer *cell;
+    int i;
+
+    model = gtk_list_store_new (1, G_TYPE_STRING);
+    
+    cell = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), cell,
+				    "text", 0,
+				    NULL);
+
+    gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (model));
+    
+    for (i = 0; i < n_entries; ++i)
+    {
+	const named_int_t *info = &(table[i]);
+	GtkTreeIter iter;
+
+	gtk_list_store_append (model, &iter);
+	gtk_list_store_set (model, &iter, 0, info->name, -1);
+    }
+
+    gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+
+    g_signal_connect (widget, "changed", G_CALLBACK (rescale), app);
+}
+
+static void
+set_up_filter_box (app_t *app, const char *box_name)
+{
+    set_up_combo_box (app, box_name, G_N_ELEMENTS (filters), filters);
+}
+
+static char *
+format_value (GtkWidget *widget, double value)
+{
+    return g_strdup_printf ("%.4f", to_scale (value));
+}
+
+static app_t *
+app_new (pixman_image_t *original)
+{
+    GtkWidget *widget;
+    app_t *app = g_malloc (sizeof *app);
+    GError *err = NULL;
+
+    app->builder = gtk_builder_new ();
+    app->original = original;
+
+    if (!gtk_builder_add_from_file (app->builder, "scale.ui", &err))
+	g_error ("Could not read file scale.ui: %s", err->message);
+
+    app->scale_x_adjustment =
+        GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_x_adjustment"));
+    app->scale_y_adjustment =
+        GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "scale_y_adjustment"));
+    app->rotate_adjustment =
+        GTK_ADJUSTMENT (gtk_builder_get_object (app->builder, "rotate_adjustment"));
+
+    g_signal_connect (app->scale_x_adjustment, "value_changed", G_CALLBACK (rescale), app);
+    g_signal_connect (app->scale_y_adjustment, "value_changed", G_CALLBACK (rescale), app);
+    g_signal_connect (app->rotate_adjustment, "value_changed", G_CALLBACK (rescale), app);
+    
+    widget = get_widget (app, "scale_x_scale");
+    gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
+    g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app);
+    widget = get_widget (app, "scale_y_scale");
+    gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
+    g_signal_connect (widget, "format_value", G_CALLBACK (format_value), app);
+    widget = get_widget (app, "rotate_scale");
+    gtk_scale_add_mark (GTK_SCALE (widget), 0.0, GTK_POS_LEFT, NULL);
+
+    widget = get_widget (app, "drawing_area");
+    g_signal_connect (widget, "expose_event", G_CALLBACK (on_expose), app);
+
+    set_up_filter_box (app, "reconstruct_x_combo_box");
+    set_up_filter_box (app, "reconstruct_y_combo_box");
+    set_up_filter_box (app, "sample_x_combo_box");
+    set_up_filter_box (app, "sample_y_combo_box");
+
+    set_up_combo_box (
+        app, "repeat_combo_box", G_N_ELEMENTS (repeats), repeats);
+
+    g_signal_connect (
+	gtk_builder_get_object (app->builder, "lock_checkbutton"),
+	"toggled", G_CALLBACK (rescale), app);
+    
+    rescale (NULL, app);
+    
+    return app;
+}
+
+int
+main (int argc, char **argv)
+{
+    GtkWidget *window;
+    pixman_image_t *image;
+    app_t *app;
+    
+    gtk_init (&argc, &argv);
+
+    if (argc < 2)
+    {
+	printf ("%s <image file>\n", argv[0]);
+	return -1;
+    }
+
+    if (!(image = pixman_image_from_file (argv[1], PIXMAN_a8r8g8b8)))
+    {
+	printf ("Could not load image \"%s\"\n", argv[1]);
+	return -1;
+    }
+
+    app = app_new (image);
+    
+    window = get_widget (app, "main");
+
+    g_signal_connect (window, "delete_event", G_CALLBACK (gtk_main_quit), NULL);
+    
+    gtk_window_set_default_size (GTK_WINDOW (window), 1024, 768);
+    
+    gtk_widget_show_all (window);
+    
+    gtk_main ();
+
+    return 0;
+}
diff --git a/demos/scale.ui b/demos/scale.ui
new file mode 100644
index 0000000..f7c0c80
--- /dev/null
+++ b/demos/scale.ui
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkAdjustment" id="rotate_adjustment">
+    <property name="lower">-180</property>
+    <property name="upper">190</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+    <property name="page_size">10</property>
+  </object>
+  <object class="GtkAdjustment" id="scale_y_adjustment">
+    <property name="lower">-32</property>
+    <property name="upper">42</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+    <property name="page_size">10</property>
+  </object>
+  <object class="GtkAdjustment" id="scale_x_adjustment">
+    <property name="lower">-32</property>
+    <property name="upper">42</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+    <property name="page_size">10</property>
+  </object>
+  <object class="GtkWindow" id="main">
+    <child>
+      <object class="GtkHBox" id="u">
+        <property name="visible">True</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkViewport" id="viewport1">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkDrawingArea" id="drawing_area">
+                    <property name="visible">True</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="box1">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkHBox" id="box2">
+                <property name="visible">True</property>
+                <property name="homogeneous">True</property>
+                <child>
+                  <object class="GtkVBox" id="box3">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label1">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes"><b>Scale X</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkVScale" id="scale_x_scale">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="adjustment">scale_x_adjustment</property>
+                        <property name="fill_level">32</property>
+                        <property name="value_pos">right</property>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkVBox" id="box4">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label2">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes"><b>Scale Y</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkVScale" id="scale_y_scale">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="adjustment">scale_y_adjustment</property>
+                        <property name="fill_level">32</property>
+                        <property name="value_pos">right</property>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkVBox" id="box5">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label3">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes"><b>Rotate</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkVScale" id="rotate_scale">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="adjustment">rotate_adjustment</property>
+                        <property name="fill_level">180</property>
+                        <property name="value_pos">right</property>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="padding">6</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="box6">
+                <property name="visible">True</property>
+		<child>
+		  <object class="GtkCheckButton"
+			  id="lock_checkbutton">
+		    <property name="label" translatable="yes">Lock X and Y Dimensions</property>
+		    <property name="xalign">0.0</property>
+		  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="padding">6</property>
+                    <property name="position">1</property>
+                  </packing>
+		</child>
+                <child>
+                  <object class="GtkTable" id="grid1">
+                    <property name="visible">True</property>
+                    <property name="column_spacing">8</property>
+                    <property name="row_spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label4">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes"><b>Reconstruct X:</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label5">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes"><b>Reconstruct Y:</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label6">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes"><b>Sample X:</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label7">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes"><b>Sample Y:</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label8">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes"><b>Repeat:</b></property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">4</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="reconstruct_x_combo_box">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="reconstruct_y_combo_box">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="sample_x_combo_box">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="sample_y_combo_box">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="repeat_combo_box">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">4</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="padding">6</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
commit 7f5bb22d17f17c2032914163a318f4ec438ba280
Author: Søren Sandmann Pedersen <ssp at redhat.com>
Date:   Thu Nov 22 10:16:16 2012 -0500

    demos/gtk-utils.[ch]: Add pixman_image_from_file()
    
    This function uses GdkPixbuf to load various common formats such as
    .png and .jpg into a pixman image.

diff --git a/demos/gtk-utils.c b/demos/gtk-utils.c
index 8291a1e..d7e946d 100644
--- a/demos/gtk-utils.c
+++ b/demos/gtk-utils.c
@@ -3,6 +3,72 @@
 #include "../test/utils.h"
 #include "gtk-utils.h"
 
+pixman_image_t *
+pixman_image_from_file (const char *filename, pixman_format_code_t format)
+{
+    GdkPixbuf *pixbuf;
+    pixman_image_t *image;
+    int width, height;
+    uint32_t *data, *d;
+    uint8_t *gdk_data;
+    int n_channels;
+    int j, i;
+    int stride;
+
+    if (!(pixbuf = gdk_pixbuf_new_from_file (filename, NULL)))
+	return NULL;
+
+    image = NULL;
+
+    width = gdk_pixbuf_get_width (pixbuf);
+    height = gdk_pixbuf_get_height (pixbuf);
+    n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+    gdk_data = gdk_pixbuf_get_pixels (pixbuf);
+    stride = gdk_pixbuf_get_rowstride (pixbuf);
+
+    if (!(data = malloc (width * height * sizeof (uint32_t))))
+	goto out;
+
+    d = data;
+    for (j = 0; j < height; ++j)
+    {
+	uint8_t *gdk_line = gdk_data;
+
+	for (i = 0; i < width; ++i)
+	{
+	    int r, g, b, a;
+	    uint32_t pixel;
+
+	    r = gdk_line[0];
+	    g = gdk_line[1];
+	    b = gdk_line[2];
+
+	    if (n_channels == 4)
+		a = gdk_line[3];
+	    else
+		a = 0xff;
+
+	    r = (r * a + 127) / 255;
+	    g = (g * a + 127) / 255;
+	    b = (b * a + 127) / 255;
+
+	    pixel = (a << 24) | (r << 16) | (g << 8) | b;
+
+	    *d++ = pixel;
+	    gdk_line += n_channels;
+	}
+
+	gdk_data += stride;
+    }
+
+    image = pixman_image_create_bits (
+	format, width, height, data, width * 4);
+
+out:
+    g_object_unref (pixbuf);
+    return image;
+}
+
 GdkPixbuf *
 pixbuf_from_argb32 (uint32_t *bits,
 		    int width,
diff --git a/demos/gtk-utils.h b/demos/gtk-utils.h
index 55cb701..36be4de 100644
--- a/demos/gtk-utils.h
+++ b/demos/gtk-utils.h
@@ -6,6 +6,9 @@
 
 void show_image (pixman_image_t *image);
 
+pixman_image_t *
+pixman_image_from_file (const char *filename, pixman_format_code_t format);
+
 GdkPixbuf *pixbuf_from_argb32 (uint32_t *bits,
                                int width,
                                int height,
commit 6915f3e24f4169260a8ad6ab7ff3087388dbe5db
Author: Søren Sandmann Pedersen <ssp at redhat.com>
Date:   Thu Nov 22 10:15:06 2012 -0500

    Add new pixman_filter_create_separable_convolution() API
    
    This new API is a helper function to create filter parameters suitable
    for use with PIXMAN_FILTER_SEPARABLE_CONVOLUTION.
    
    For each dimension, given a scale factor, reconstruction and sample
    filter kernels, and a subsampling resolution, this function will
    compute a convolution of the two kernels scaled appropriately, then
    sample that convolution and return the resulting vectors in a form
    suitable for being used as parameters to
    PIXMAN_FILTER_SEPARABLE_CONVOLUTION.
    
    The filter kernels offered are the following:
    
      - IMPULSE:            Dirac delta function, ie., point sampling
      - BOX:                Box filter
      - LINEAR:             Linear filter, aka. "Tent" filter
      - CUBIC:              Cubic filter, currently Mitchell-Netravali
      - GAUSSIAN:           Gaussian function, sigma=1, support=3*sigma
      - LANCZOS2:           Two-lobed Lanczos filter
      - LANCZOS3:           Three-lobed Lanczos filter
      - LANCZOS3_STRETCHED: Three-lobed Lanczos filter, stretched by 4/3.0.
                            This is the "Nice" filter from Dirty Pixels by
                            Jim Blinn.
    
    The intended way to use this function is to extract scaling factors
    from the transformation and then pass those to this function to get a
    filter suitable for compositing with that transformation. The filter
    kernels can be chosen according to quality and performance tradeoffs.
    
    To get equivalent quality to GdkPixbuf for downscalings, use BOX for
    both reconstruction and sampling. For upscalings, use LINEAR for
    reconstruction and IMPULSE for sampling (though note that for
    upscaling in both X and Y directions, simply using
    PIXMAN_FILTER_BILINEAR will likely be a better choice).

diff --git a/pixman/Makefile.sources b/pixman/Makefile.sources
index 5351fb0..c624eb9 100644
--- a/pixman/Makefile.sources
+++ b/pixman/Makefile.sources
@@ -6,6 +6,7 @@ libpixman_sources =			\
 	pixman-combine32.c		\
 	pixman-combine-float.c		\
 	pixman-conical-gradient.c	\
+	pixman-filter.c			\
 	pixman-x86.c			\
 	pixman-mips.c			\
 	pixman-arm.c			\
diff --git a/pixman/pixman-bits-image.c b/pixman/pixman-bits-image.c
index 97db108..e76b78c 100644
--- a/pixman/pixman-bits-image.c
+++ b/pixman/pixman-bits-image.c
@@ -427,7 +427,7 @@ bits_image_fetch_pixel_convolution (bits_image_t   *image,
 }
 
 static uint32_t
-bits_image_fetch_pixel_convolution_separable (bits_image_t *image,
+bits_image_fetch_pixel_separable_convolution (bits_image_t *image,
                                               pixman_fixed_t x,
                                               pixman_fixed_t y,
                                               get_pixel_t    get_pixel)
@@ -548,7 +548,7 @@ bits_image_fetch_pixel_filtered (bits_image_t *image,
 	break;
 
     case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
-        return bits_image_fetch_pixel_convolution_separable (image, x, y, get_pixel);
+        return bits_image_fetch_pixel_separable_convolution (image, x, y, get_pixel);
         break;
 
     default:
diff --git a/pixman/pixman-filter.c b/pixman/pixman-filter.c
new file mode 100644
index 0000000..c9d2dc7
--- /dev/null
+++ b/pixman/pixman-filter.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2012, Red Hat, Inc.
+ * Copyright 2012, Soren Sandmann
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Author: Soren Sandmann <soren.sandmann at gmail.com>
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+#include <config.h>
+#include "pixman-private.h"
+
+typedef double (* kernel_func_t) (double x);
+
+typedef struct
+{
+    pixman_kernel_t	kernel;
+    kernel_func_t	func;
+    double		width;
+} filter_info_t;
+
+static double
+impulse_kernel (double x)
+{
+    return (x == 0.0)? 1.0 : 0.0;
+}
+
+static double
+box_kernel (double x)
+{
+    return 1;
+}
+
+static double
+linear_kernel (double x)
+{
+    return 1 - fabs (x);
+}
+
+static double
+gaussian_kernel (double x)
+{
+#define SQRT2 (1.4142135623730950488016887242096980785696718753769480)
+#define SIGMA (SQRT2 / 2.0)
+    
+    return exp (- x * x / (2 * SIGMA * SIGMA)) / (SIGMA * sqrt (2.0 * M_PI));
+}
+
+static double
+sinc (double x)
+{
+    if (x == 0.0)
+	return 1.0;
+    else
+	return sin (M_PI * x) / (M_PI * x);
+}
+
+static double
+lanczos (double x, int n)
+{
+    return sinc (x) * sinc (x * (1.0 / n));
+}
+
+static double
+lanczos2_kernel (double x)
+{
+    return lanczos (x, 2);
+}
+
+static double
+lanczos3_kernel (double x)
+{
+    return lanczos (x, 3);
+}
+
+static double
+nice_kernel (double x)
+{
+    return lanczos3_kernel (x * 0.75);
+}
+
+static double
+general_cubic (double x, double B, double C)
+{
+    double ax = fabs(x);
+
+    if (ax < 1)
+    {
+	return ((12 - 9 * B - 6 * C) * ax * ax * ax +
+		(-18 + 12 * B + 6 * C) * ax * ax + (6 - 2 * B)) / 6;
+    }
+    else if (ax >= 1 && ax < 2)
+    {
+	return ((-B - 6 * C) * ax * ax * ax +
+		(6 * B + 30 * C) * ax * ax + (-12 * B - 48 * C) *
+		ax + (8 * B + 24 * C)) / 6;
+    }
+    else
+    {
+	return 0;
+    }
+}
+
+static double
+cubic_kernel (double x)
+{
+    /* This is the Mitchell-Netravali filter.
+     *
+     * (0.0, 0.5) would give us the Catmull-Rom spline,
+     * but that one seems to be indistinguishable from Lanczos2.
+     */
+    return general_cubic (x, 1/3.0, 1/3.0);
+}
+
+static const filter_info_t filters[] =
+{
+    { PIXMAN_KERNEL_IMPULSE,	        impulse_kernel,   0.0 },
+    { PIXMAN_KERNEL_BOX,	        box_kernel,       1.0 },
+    { PIXMAN_KERNEL_LINEAR,	        linear_kernel,    2.0 },
+    { PIXMAN_KERNEL_CUBIC,		cubic_kernel,     4.0 },
+    { PIXMAN_KERNEL_GAUSSIAN,	        gaussian_kernel,  6 * SIGMA },
+    { PIXMAN_KERNEL_LANCZOS2,	        lanczos2_kernel,  4.0 },
+    { PIXMAN_KERNEL_LANCZOS3,	        lanczos3_kernel,  6.0 },
+    { PIXMAN_KERNEL_LANCZOS3_STRETCHED, nice_kernel,      8.0 },
+};
+
+/* This function scales @kernel2 by @scale, then
+ * aligns @x1 in @kernel1 with @x2 in @kernel2 and
+ * and integrates the product of the kernels across @width.
+ *
+ * This function assumes that the intervals are within
+ * the kernels in question. E.g., the caller must not
+ * try to integrate a linear kernel ouside of [-1:1]
+ */
+static double
+integral (pixman_kernel_t kernel1, double x1,
+	  pixman_kernel_t kernel2, double scale, double x2,
+	  double width)
+{
+    /* If the integration interval crosses zero, break it into
+     * two separate integrals. This ensures that filters such
+     * as LINEAR that are not differentiable at 0 will still
+     * integrate properly.
+     */
+    if (x1 < 0 && x1 + width > 0)
+    {
+	return
+	    integral (kernel1, x1, kernel2, scale, x2, - x1) +
+	    integral (kernel1, 0, kernel2, scale, x2 - x1, width + x1);
+    }
+    else if (x2 < 0 && x2 + width > 0)
+    {
+	return
+	    integral (kernel1, x1, kernel2, scale, x2, - x2) +
+	    integral (kernel1, x1 - x2, kernel2, scale, 0, width + x2);
+    }
+    else if (kernel1 == PIXMAN_KERNEL_IMPULSE)
+    {
+	assert (width == 0.0);
+	return filters[kernel2].func (x2 * scale);
+    }
+    else if (kernel2 == PIXMAN_KERNEL_IMPULSE)
+    {
+	assert (width == 0.0);
+	return filters[kernel1].func (x1);
+    }
+    else
+    {
+	/* Integration via Simpson's rule */
+#define N_SEGMENTS 128
+#define SAMPLE(a1, a2)							\
+	(filters[kernel1].func ((a1)) * filters[kernel2].func ((a2) * scale))
+	
+	double s = 0.0;
+	double h = width / (double)N_SEGMENTS;
+	int i;
+
+	s = SAMPLE (x1, x2);
+
+	for (i = 1; i < N_SEGMENTS; i += 2)
+	{
+	    double a1 = x1 + h * i;
+	    double a2 = x2 + h * i;
+
+	    s += 2 * SAMPLE (a1, a2);
+
+	    if (i >= 2 && i < N_SEGMENTS - 1)
+		s += 4 * SAMPLE (a1, a2);
+	}
+
+	s += SAMPLE (x1 + width, x2 + width);
+	
+	return h * s * (1.0 / 3.0);
+    }
+}
+
+static pixman_fixed_t *
+create_1d_filter (int             *width,
+		  pixman_kernel_t  reconstruct,
+		  pixman_kernel_t  sample,
+		  double           scale,
+		  int              n_phases)
+{
+    pixman_fixed_t *params, *p;
+    double step;
+    double size;
+    int i;
+
+    size = scale * filters[sample].width + filters[reconstruct].width;
+    *width = ceil (size);
+
+    p = params = malloc (*width * n_phases * sizeof (pixman_fixed_t));
+
+    step = 1.0 / n_phases;
+
+    for (i = 0; i < n_phases; ++i)
+    {
+        double frac = step / 2.0 + i * step;
+	pixman_fixed_t new_total;
+        int x, x1, x2;
+	double total;
+
+	/* Sample convolution of reconstruction and sampling
+	 * filter. See rounding.txt regarding the rounding
+	 * and sample positions.
+	 */
+
+	x1 = ceil (frac - *width / 2.0 - 0.5);
+        x2 = x1 + *width;
+
+	total = 0;
+        for (x = x1; x < x2; ++x)
+        {
+	    double pos = x + 0.5 - frac;
+	    double rlow = - filters[reconstruct].width / 2.0;
+	    double rhigh = rlow + filters[reconstruct].width;
+	    double slow = pos - scale * filters[sample].width / 2.0;
+	    double shigh = slow + scale * filters[sample].width;
+	    double c = 0.0;
+	    double ilow, ihigh;
+
+	    if (rhigh >= slow && rlow <= shigh)
+	    {
+		ilow = MAX (slow, rlow);
+		ihigh = MIN (shigh, rhigh);
+
+		c = integral (reconstruct, ilow,
+			      sample, 1.0 / scale, ilow - pos,
+			      ihigh - ilow);
+	    }
+
+	    total += c;
+            *p++ = (pixman_fixed_t)(c * 65535.0 + 0.5);
+        }
+
+	/* Normalize */
+	p -= *width;
+        total = 1 / total;
+        new_total = 0;
+	for (x = x1; x < x2; ++x)
+	{
+	    pixman_fixed_t t = (*p) * total + 0.5;
+
+	    new_total += t;
+	    *p++ = t;
+	}
+
+	if (new_total != pixman_fixed_1)
+	    *(p - *width / 2) += (pixman_fixed_1 - new_total);
+    }
+
+    return params;
+}
+
+/* Create the parameter list for a SEPARABLE_CONVOLUTION filter
+ * with the given kernels and scale parameters
+ */
+PIXMAN_EXPORT pixman_fixed_t *
+pixman_filter_create_separable_convolution (int             *n_values,
+					    pixman_fixed_t   scale_x,
+					    pixman_fixed_t   scale_y,
+					    pixman_kernel_t  reconstruct_x,
+					    pixman_kernel_t  reconstruct_y,
+					    pixman_kernel_t  sample_x,
+					    pixman_kernel_t  sample_y,
+					    int              subsample_bits_x,
+					    int	             subsample_bits_y)
+{
+    double sx = fabs (pixman_fixed_to_double (scale_x));
+    double sy = fabs (pixman_fixed_to_double (scale_y));
+    pixman_fixed_t *horz, *vert, *params;
+    int subsample_x, subsample_y;
+    int width, height;
+
+    subsample_x = (1 << subsample_bits_x);
+    subsample_y = (1 << subsample_bits_y);
+
+    horz = create_1d_filter (&width, reconstruct_x, sample_x, sx, subsample_x);
+    vert = create_1d_filter (&height, reconstruct_y, sample_y, sy, subsample_y);
+
+    *n_values = 4 + width * subsample_x + height * subsample_y;
+    
+    params = malloc (*n_values * sizeof (pixman_fixed_t));
+
+    params[0] = pixman_int_to_fixed (width);
+    params[1] = pixman_int_to_fixed (height);
+    params[2] = pixman_int_to_fixed (subsample_bits_x);
+    params[3] = pixman_int_to_fixed (subsample_bits_y);
+
+    memcpy (params + 4, horz,
+	    width * subsample_x * sizeof (pixman_fixed_t));
+    memcpy (params + 4 + width * subsample_x, vert,
+	    height * subsample_y * sizeof (pixman_fixed_t));
+
+    free (horz);
+    free (vert);
+
+    return params;
+}
diff --git a/pixman/pixman.h b/pixman/pixman.h
index 20c95da..7ff9fb5 100644
--- a/pixman/pixman.h
+++ b/pixman/pixman.h
@@ -831,6 +831,33 @@ int             pixman_image_get_height              (pixman_image_t
 int		pixman_image_get_stride              (pixman_image_t               *image); /* in bytes */
 int		pixman_image_get_depth               (pixman_image_t		   *image);
 pixman_format_code_t pixman_image_get_format	     (pixman_image_t		   *image);
+
+typedef enum
+{
+    PIXMAN_KERNEL_IMPULSE,
+    PIXMAN_KERNEL_BOX,
+    PIXMAN_KERNEL_LINEAR,
+    PIXMAN_KERNEL_CUBIC,
+    PIXMAN_KERNEL_GAUSSIAN,
+    PIXMAN_KERNEL_LANCZOS2,
+    PIXMAN_KERNEL_LANCZOS3,
+    PIXMAN_KERNEL_LANCZOS3_STRETCHED       /* Jim Blinn's 'nice' filter */
+} pixman_kernel_t;
+
+/* Create the parameter list for a SEPARABLE_CONVOLUTION filter
+ * with the given kernels and scale parameters.
+ */
+pixman_fixed_t *
+pixman_filter_create_separable_convolution (int             *n_values,
+					    pixman_fixed_t   scale_x,
+					    pixman_fixed_t   scale_y,
+					    pixman_kernel_t  reconstruct_x,
+					    pixman_kernel_t  reconstruct_y,
+					    pixman_kernel_t  sample_x,
+					    pixman_kernel_t  sample_y,
+					    int              subsample_bits_x,
+					    int              subsample_bits_y);
+
 pixman_bool_t	pixman_image_fill_rectangles	     (pixman_op_t		    op,
 						      pixman_image_t		   *image,
 						      const pixman_color_t	   *color,
commit 68760d3fe1351cb745aedcada7d765edc08bbe8b
Author: Søren Sandmann Pedersen <ssp at redhat.com>
Date:   Thu Nov 22 10:17:56 2012 -0500

    rounding.txt: Describe how SEPARABLE_CONVOLUTION filter works
    
    Add some notes on how to compute the convolution matrices to be used
    with the SEPARABLE_CONVOLUTION filter.

diff --git a/pixman/rounding.txt b/pixman/rounding.txt
index 1a19f45..b52b084 100644
--- a/pixman/rounding.txt
+++ b/pixman/rounding.txt
@@ -132,3 +132,36 @@ And so the final formula for the index k of x0 in the image is:
 
 Computing the result is then simply a matter of convolving all the
 pixels starting at k with all the samples in the matrix.
+
+
+--- SEPARABLE_CONVOLUTION
+
+For this filter, x is first rounded to one of n regularly spaced
+subpixel positions. This subpixel position determines which of n
+convolution matrices is being used.
+
+Then, as in a regular convolution filter, the first pixel to be used
+is determined:
+
+    	k = floor (x - (width - 1) / 2.0 - e)
+
+and then the image pixels starting there are convolved with the chosen
+matrix. If we write x = xi + frac, where xi is an integer, we get
+
+	k = xi + floor (frac - (width - 1) / 2.0 - e)
+
+so the location of k relative to x is given by:
+
+    (k + 0.5 - x) = xi + floor (frac - (width - 1) / 2.0 - e) + 0.5 - x
+
+                  = floor (frac - (width - 1) / 2.0 - e) + 0.5 - frac
+
+which means the contents of the matrix corresponding to (frac) should
+contain width samplings of the function, with the first sample at:
+
+       floor (frac - (width - 1) / 2.0 - e) + 0.5 - frac
+
+This filter is called separable because each of the k x k convolution
+matrices is specified with two k-wide vectors, one for each dimension,
+where each entry in the matrix is computed as the product of the
+corresponding entries in the vectors.
commit 6fd480b17c8398c217e4c11e826c82dbb8288006
Author: Søren Sandmann Pedersen <ssp at redhat.com>
Date:   Thu Nov 22 10:14:06 2012 -0500

    Add new filter PIXMAN_FILTER_SEPARABLE_CONVOLUTION
    
    This filter is a new way to use a convolution matrix for filtering. In
    contrast to the existing CONVOLUTION filter, this new variant is
    different in two respects:
    
    - It is subsampled: Instead of just one convolution matrix, this
      filter chooses between a number of matrices based on the subpixel
      sample location, allowing the convolution kernel to be sampled at a
      higher resolution.
    
    - It is separable: Each matrix is specified as the tensor product of
      two vectors. This has the advantages that many fewer values have to
      be stored, and that the filtering can be done separately in the x
      and y dimensions (although the initial implementation doesn't
      actually do that).
    
    The motivation for this new filter is to improve image downsampling
    quality. Currently, the best pixman can do is the regular convolution
    filter which is limited to coarsely sampled convolution kernels.
    
    With this new feature, any separable filter can be used at any desired
    resolution.

diff --git a/pixman/pixman-bits-image.c b/pixman/pixman-bits-image.c
index 7787ef1..97db108 100644
--- a/pixman/pixman-bits-image.c
+++ b/pixman/pixman-bits-image.c
@@ -426,6 +426,104 @@ bits_image_fetch_pixel_convolution (bits_image_t   *image,
     return ((satot << 24) | (srtot << 16) | (sgtot <<  8) | (sbtot));
 }
 
+static uint32_t
+bits_image_fetch_pixel_convolution_separable (bits_image_t *image,
+                                              pixman_fixed_t x,
+                                              pixman_fixed_t y,
+                                              get_pixel_t    get_pixel)
+{
+    pixman_fixed_t *params = image->common.filter_params;
+    pixman_repeat_t repeat_mode = image->common.repeat;
+    int width = image->width;
+    int height = image->height;
+    int cwidth = pixman_fixed_to_int (params[0]);
+    int cheight = pixman_fixed_to_int (params[1]);
+    int x_phase_bits = pixman_fixed_to_int (params[2]);
+    int y_phase_bits = pixman_fixed_to_int (params[3]);
+    int x_phase_shift = 16 - x_phase_bits;
+    int y_phase_shift = 16 - y_phase_bits;
+    int x_off = ((cwidth << 16) - pixman_fixed_1) >> 1;
+    int y_off = ((cheight << 16) - pixman_fixed_1) >> 1;
+    pixman_fixed_t *y_params;
+    int srtot, sgtot, sbtot, satot;
+    int32_t x1, x2, y1, y2;
+    int32_t px, py;
+    int i, j;
+
+    /* Round x and y to the middle of the closest phase before continuing. This
+     * ensures that the convolution matrix is aligned right, since it was
+     * positioned relative to a particular phase (and not relative to whatever
+     * exact fraction we happen to get here).
+     */
+    x = ((x >> x_phase_shift) << x_phase_shift) + ((1 << x_phase_shift) >> 1);
+    y = ((y >> y_phase_shift) << y_phase_shift) + ((1 << y_phase_shift) >> 1);
+
+    px = (x & 0xffff) >> x_phase_shift;
+    py = (y & 0xffff) >> y_phase_shift;
+
+    y_params = params + 4 + (1 << x_phase_bits) * cwidth + py * cheight;
+
+    x1 = pixman_fixed_to_int (x - pixman_fixed_e - x_off);
+    y1 = pixman_fixed_to_int (y - pixman_fixed_e - y_off);
+    x2 = x1 + cwidth;
+    y2 = y1 + cheight;
+
+    srtot = sgtot = sbtot = satot = 0;
+
+    for (i = y1; i < y2; ++i)
+    {
+        pixman_fixed_48_16_t fy = *y_params++;
+        pixman_fixed_t *x_params = params + 4 + px * cwidth;
+
+        if (fy)
+        {
+            for (j = x1; j < x2; ++j)
+            {
+                pixman_fixed_t fx = *x_params++;
+		int rx = j;
+		int ry = i;
+
+                if (fx)
+                {
+                    pixman_fixed_t f;
+                    uint32_t pixel;
+
+                    if (repeat_mode != PIXMAN_REPEAT_NONE)
+                    {
+                        repeat (repeat_mode, &rx, width);
+                        repeat (repeat_mode, &ry, height);
+
+                        pixel = get_pixel (image, rx, ry, FALSE);
+                    }
+                    else
+                    {
+                        pixel = get_pixel (image, rx, ry, TRUE);
+		    }
+
+                    f = (fy * fx + 0x8000) >> 16;
+
+                    srtot += (int)RED_8 (pixel) * f;
+                    sgtot += (int)GREEN_8 (pixel) * f;
+                    sbtot += (int)BLUE_8 (pixel) * f;
+                    satot += (int)ALPHA_8 (pixel) * f;
+                }
+            }
+	}
+    }
+
+    satot = (satot + 0x8000) >> 16;
+    srtot = (srtot + 0x8000) >> 16;
+    sgtot = (sgtot + 0x8000) >> 16;
+    sbtot = (sbtot + 0x8000) >> 16;
+
+    satot = CLIP (satot, 0, 0xff);
+    srtot = CLIP (srtot, 0, 0xff);
+    sgtot = CLIP (sgtot, 0, 0xff);
+    sbtot = CLIP (sbtot, 0, 0xff);
+
+    return ((satot << 24) | (srtot << 16) | (sgtot <<  8) | (sbtot));
+}
+
 static force_inline uint32_t
 bits_image_fetch_pixel_filtered (bits_image_t *image,
 				 pixman_fixed_t x,
@@ -449,6 +547,10 @@ bits_image_fetch_pixel_filtered (bits_image_t *image,
 	return bits_image_fetch_pixel_convolution (image, x, y, get_pixel);
 	break;
 
+    case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
+        return bits_image_fetch_pixel_convolution_separable (image, x, y, get_pixel);
+        break;
+
     default:
         break;
     }
diff --git a/pixman/pixman-image.c b/pixman/pixman-image.c
index d9c3034..93ed17e 100644
--- a/pixman/pixman-image.c
+++ b/pixman/pixman-image.c
@@ -371,6 +371,7 @@ compute_image_info (pixman_image_t *image)
 	break;
 
     case PIXMAN_FILTER_CONVOLUTION:
+    case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
 	break;
 
     default:
@@ -515,8 +516,9 @@ compute_image_info (pixman_image_t *image)
      * if all channels are opaque, so we simply turn it off
      * unconditionally for those images.
      */
-    if (image->common.alpha_map					||
-	image->common.filter == PIXMAN_FILTER_CONVOLUTION	||
+    if (image->common.alpha_map						||
+	image->common.filter == PIXMAN_FILTER_CONVOLUTION		||
+        image->common.filter == PIXMAN_FILTER_SEPARABLE_CONVOLUTION     ||
 	image->common.component_alpha)
     {
 	flags &= ~(FAST_PATH_IS_OPAQUE | FAST_PATH_SAMPLES_OPAQUE);
@@ -679,6 +681,19 @@ pixman_image_set_filter (pixman_image_t *      image,
     if (params == common->filter_params && filter == common->filter)
 	return TRUE;
 
+    if (filter == PIXMAN_FILTER_SEPARABLE_CONVOLUTION)
+    {
+	int width = pixman_fixed_to_int (params[0]);
+	int height = pixman_fixed_to_int (params[1]);
+	int x_phase_bits = pixman_fixed_to_int (params[2]);
+	int y_phase_bits = pixman_fixed_to_int (params[3]);
+	int n_x_phases = (1 << x_phase_bits);
+	int n_y_phases = (1 << y_phase_bits);
+
+	return_val_if_fail (
+	    n_params == 4 + n_x_phases * width + n_y_phases * height, FALSE);
+    }
+    
     new_params = NULL;
     if (params)
     {
diff --git a/pixman/pixman.c b/pixman/pixman.c
index e0ccd87..0661f41 100644
--- a/pixman/pixman.c
+++ b/pixman/pixman.c
@@ -455,6 +455,14 @@ analyze_extent (pixman_image_t       *image,
 	    height = params[1];
 	    break;
 
+	case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
+	    params = image->common.filter_params;
+	    x_off = - pixman_fixed_e - ((params[0] - pixman_fixed_1) >> 1);
+	    y_off = - pixman_fixed_e - ((params[1] - pixman_fixed_1) >> 1);
+	    width = params[0];
+	    height = params[1];
+	    break;
+	    
 	case PIXMAN_FILTER_GOOD:
 	case PIXMAN_FILTER_BEST:
 	case PIXMAN_FILTER_BILINEAR:
diff --git a/pixman/pixman.h b/pixman/pixman.h
index 33ebf3f..20c95da 100644
--- a/pixman/pixman.h
+++ b/pixman/pixman.h
@@ -292,7 +292,28 @@ typedef enum
     PIXMAN_FILTER_BEST,
     PIXMAN_FILTER_NEAREST,
     PIXMAN_FILTER_BILINEAR,
-    PIXMAN_FILTER_CONVOLUTION
+    PIXMAN_FILTER_CONVOLUTION,
+
+    /* The SEPARABLE_CONVOLUTION filter takes the following parameters:
+     *
+     *         width:           integer given as 16.16 fixpoint number
+     *         height:          integer given as 16.16 fixpoint number
+     *         x_phase_bits:	integer given as 16.16 fixpoint
+     *         y_phase_bits:	integer given as 16.16 fixpoint
+     *         xtables:         (1 << x_phase_bits) tables of size width
+     *         ytables:         (1 << y_phase_bits) tables of size height
+     *
+     * When sampling at (x, y), the location is first rounded to one of
+     * n_x_phases * n_y_phases subpixel positions. These subpixel positions
+     * determine an xtable and a ytable to use.
+     *
+     * Conceptually a width x height matrix is then formed in which each entry
+     * is the product of the corresponding entries in the x and y tables.
+     * This matrix is then aligned with the image pixels such that its center
+     * is as close as possible to the subpixel location chosen earlier. Then
+     * the image is convolved with the matrix and the resulting pixel returned.
+     */
+    PIXMAN_FILTER_SEPARABLE_CONVOLUTION
 } pixman_filter_t;
 
 typedef enum


More information about the xorg-commit mailing list