[gthumb] cairo-scale: added a custom implementation of the scale function



commit 30d41839b16ec9e8c982ad331b2bc7eb3891446f
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Thu Aug 16 19:05:02 2012 +0200

    cairo-scale: added a custom implementation of the scale function

 extensions/file_tools/cairo-scale.c |  461 +++++++++++++++++++++++++++++++++++
 1 files changed, 461 insertions(+), 0 deletions(-)
---
diff --git a/extensions/file_tools/cairo-scale.c b/extensions/file_tools/cairo-scale.c
index db58004..d3c4bc7 100644
--- a/extensions/file_tools/cairo-scale.c
+++ b/extensions/file_tools/cairo-scale.c
@@ -24,12 +24,464 @@
 #include "cairo-scale.h"
 
 
+/* -- _cairo_image_surface_scale_nearest -- */
+
+
+typedef long gfixed;
+#define GINT_TO_FIXED(x)         ((gfixed) ((x) << 16))
+#define GDOUBLE_TO_FIXED(x)      ((gfixed) ((x) * (1 << 16) + 0.5))
+#define GFIXED_TO_INT(x)         ((x) >> 16)
+#define GFIXED_TO_DOUBLE(x)      (((double) (x)) / (1 << 16))
+#define GFIXED_ROUND_TO_INT(x)   (((x) + (1 << (16-1))) >> 16)
+#define GFIXED_1                 65536L
+#define GFIXED_2                 131072L
+#define gfixed_mul(x, y)         ((((x) * (y)) + (1 << (16-1))) >> 16)
+#define gfixed_div(x, y)         (((x) << 16) / (y))
+
+
+cairo_surface_t *
+_cairo_image_surface_scale_nearest (cairo_surface_t *image,
+				    int              new_width,
+				    int              new_height)
+{
+	cairo_surface_t *scaled;
+	int              src_width;
+	int              src_height;
+	guchar          *p_src;
+	guchar          *p_dest;
+	int              src_rowstride;
+	int              dest_rowstride;
+	gfixed           step_x, step_y;
+	guchar          *p_src_row;
+	guchar          *p_src_col;
+	guchar          *p_dest_row;
+	guchar          *p_dest_col;
+	gfixed           max_row, max_col;
+	gfixed           x_src, y_src;
+	int              x, y;
+
+	g_return_val_if_fail (cairo_image_surface_get_format (image) == CAIRO_FORMAT_ARGB32, NULL);
+
+	scaled = cairo_surface_create_similar_image (image,
+						     CAIRO_FORMAT_ARGB32,
+						     new_width,
+						     new_height);
+
+	src_width = cairo_image_surface_get_width  (image);
+	src_height = cairo_image_surface_get_height (image);
+	p_src = cairo_image_surface_get_data (image);
+	p_dest = cairo_image_surface_get_data (scaled);
+	src_rowstride = cairo_image_surface_get_stride (image);
+	dest_rowstride = cairo_image_surface_get_stride (scaled);
+
+	cairo_surface_flush (scaled);
+
+	step_x = GDOUBLE_TO_FIXED ((double) src_width / new_width);
+	step_y = GDOUBLE_TO_FIXED ((double) src_height / new_height);
+
+	p_dest_row = p_dest;
+	p_src_row = p_src;
+	max_row = GINT_TO_FIXED (src_height - 1);
+	max_col= GINT_TO_FIXED (src_width - 1);
+	/* pick the pixel in the middle to avoid the shift effect. */
+	y_src = gfixed_div (step_y, GFIXED_2);
+	for (y = 0; y < new_height; y++) {
+		p_dest_col = p_dest_row;
+		p_src_col = p_src_row;
+
+		x_src = gfixed_div (step_x, GFIXED_2);
+		for (x = 0; x < new_width; x++) {
+			p_src_col = p_src_row + (GFIXED_TO_INT (MIN (x_src, max_col)) << 2); /* p_src_col = p_src_row + x_src * 4 */
+			memcpy (p_dest_col, p_src_col, 4);
+
+			p_dest_col += 4;
+			x_src += step_x;
+		}
+
+		p_dest_row += dest_rowstride;
+		y_src += step_y;
+		p_src_row = p_src + (GFIXED_TO_INT (MIN (y_src, max_row)) * src_rowstride);
+	}
+
+	cairo_surface_mark_dirty (scaled);
+
+	return scaled;
+}
+
+
+/* -- _cairo_image_surface_scale_filter -- */
+
+
+#define WORKLOAD_FACTOR 0.265
+#define EPSILON ((double) 1.0e-16)
+
+
+typedef double (*weight_func_t) (double distance);
+
+
+typedef enum {
+	FILTER_POINT = 0,
+	FILTER_BOX,
+	FILTER_TRIANGLE,
+	FILTER_QUADRATIC,
+	N_FILTERS
+} filter_type_t;
+
+
+static double
+box (double x)
+{
+	return 1.0;
+}
+
+
+static double
+triangle (double x)
+{
+	return (x < 1.0) ? 1.0 - x : 0.0;
+}
+
+
+static double
+quadratic (double x)
+{
+	/*
+	 * 2rd order (quadratic) B-Spline approximation of Gaussian.
+	 */
+	if (x < 0.5)
+		return 0.75 - x * x;
+	if (x < 1.5)
+		return 0.5 * (x - 1.5) * (x - 1.5);
+	return 0.0;
+}
+
+
+static struct {
+	weight_func_t weight_func;
+	double        support;
+}
+const filters[N_FILTERS] = {
+	{ box,		 .0 },
+	{ box,		 .5 },
+	{ triangle,	1.0 },
+	{ quadratic,	1.5 }
+};
+
+
+/* -- resize_filter_t --  */
+
+
+typedef struct {
+	weight_func_t weight_func;
+	double        support;
+} resize_filter_t;
+
+
+static resize_filter_t *
+resize_filter_create (filter_type_t filter)
+{
+	resize_filter_t *resize_filter;
+
+	resize_filter = g_slice_new (resize_filter_t);
+	resize_filter->weight_func = filters[filter].weight_func;
+	resize_filter->support = filters[filter].support;
+
+	return resize_filter;
+}
+
+
+static inline double
+resize_filter_get_support (resize_filter_t *resize_filter)
+{
+	return resize_filter->support;
+}
+
+
+static inline double
+resize_filter_get_weight (resize_filter_t *resize_filter,
+			  double           distance)
+{
+	return resize_filter->weight_func (fabs (distance));
+}
+
+
+static void
+resize_filter_destroy (resize_filter_t *resize_filter)
+{
+	g_slice_free (resize_filter_t, resize_filter);
+}
+
+
+static inline double
+reciprocal (double x)
+{
+	double sign = x < 0.0 ? -1.0 : 1.0;
+	return (sign * x) >= EPSILON ? 1.0 / x : sign * (1.0 / EPSILON);
+}
+
+
+static inline int
+clamp_pixel (double v)
+{
+	if (v <= 0.0)
+		return 0.0;
+	if (v >= 255.0)
+		return 255.0;
+	return (int) (v + 0.5);
+}
+
+
+static void
+horizontal_scale (resize_filter_t *resize_filter,
+		  cairo_surface_t *image,
+		  cairo_surface_t *scaled,
+		  double           x_factor)
+{
+	double  scale;
+	double  support;
+	int     x;
+	guchar *p_src;
+        guchar *p_dest;
+        int     src_rowstride;
+        int     dest_rowstride;
+        double *weights;
+        guchar *p_src_row;
+        guchar *p_dest_pixel;
+
+        cairo_surface_flush (scaled);
+
+	scale = MAX (1.0 / x_factor + EPSILON, 1.0);
+	support = scale * resize_filter_get_support (resize_filter);
+	if (support < 0.5) {
+		support = 0.5;
+		scale = 1.0;
+	}
+
+	p_src = cairo_image_surface_get_data (image);
+	p_dest = cairo_image_surface_get_data (scaled);
+	src_rowstride = cairo_image_surface_get_stride (image);
+	dest_rowstride = cairo_image_surface_get_stride (scaled);
+	weights = g_new (double, 2.0 * support + 3.0);
+
+	scale = reciprocal (scale);
+	for (x = 0; x < cairo_image_surface_get_width (scaled); x++) {
+		double bisect;
+		int    start;
+		int    stop;
+		double density;
+		int    n;
+		int    y;
+
+		bisect = (x + 0.5) / x_factor + EPSILON;
+		start = MAX (bisect - support + 0.5, 0.0);
+		stop = MIN (bisect + support + 0.5, cairo_image_surface_get_width (image));
+		density = 0.0;
+
+		for (n = 0; n < stop - start; n++) {
+			weights[n] = resize_filter_get_weight (resize_filter, scale * ((double) (start + n) - bisect + 0.5));
+			density += weights[n];
+		}
+
+		if ((density != 0.0) && (density != 1.0)) {
+			register int i;
+
+			density = reciprocal (density);
+			for (i = 0; i < n; i++)
+				weights[i] *= density;
+		}
+
+		p_src_row = p_src;
+		p_dest_pixel = p_dest + (x * 4);
+		for (y = 0; y < cairo_image_surface_get_height (scaled); y++) {
+			guchar       *p_src_pixel;
+			double        r, g, b, a;
+			register int  i;
+
+			p_src_pixel = p_src_row + (start * 4);
+			r = g = b = a = 0.0;
+			for (i = 0; i < n; i++) {
+				double w = weights[i];
+				r += w * p_src_pixel[CAIRO_RED];
+				g += w * p_src_pixel[CAIRO_GREEN];
+				b += w * p_src_pixel[CAIRO_BLUE];
+				a += w * p_src_pixel[CAIRO_ALPHA];
+
+				p_src_pixel += 4;
+			}
+
+			p_dest_pixel[CAIRO_RED] = clamp_pixel (r);
+			p_dest_pixel[CAIRO_GREEN] = clamp_pixel (g);
+			p_dest_pixel[CAIRO_BLUE] = clamp_pixel (b);
+			p_dest_pixel[CAIRO_ALPHA] = clamp_pixel (a);
+
+			p_dest_pixel += dest_rowstride;
+			p_src_row += src_rowstride;
+		}
+	}
+
+	cairo_surface_mark_dirty (scaled);
+
+	g_free (weights);
+}
+
+
+static void
+vertical_scale (resize_filter_t *resize_filter,
+		cairo_surface_t *image,
+		cairo_surface_t *scaled,
+		double           y_factor)
+{
+	double  scale;
+	double  support;
+	int     y;
+	guchar *p_src;
+        guchar *p_dest;
+        int     src_rowstride;
+        int     dest_rowstride;
+        double *weights;
+        guchar *p_src_col;
+        guchar *p_dest_pixel;
+
+        cairo_surface_flush (scaled);
+
+	scale = MAX (1.0 / y_factor + EPSILON, 1.0);
+	support = scale * resize_filter_get_support (resize_filter);
+	if (support < 0.5) {
+		support = 0.5;
+		scale = 1.0;
+	}
+
+	p_src = cairo_image_surface_get_data (image);
+	p_dest = cairo_image_surface_get_data (scaled);
+	src_rowstride = cairo_image_surface_get_stride (image);
+	dest_rowstride = cairo_image_surface_get_stride (scaled);
+	weights = g_new (double, 2.0 * support + 3.0);
+
+	scale = reciprocal (scale);
+	for (y = 0; y < cairo_image_surface_get_height (scaled); y++) {
+		double bisect;
+		int    start;
+		int    stop;
+		double density;
+		int    n;
+		int    x;
+
+		bisect = (y + 0.5) / y_factor + EPSILON;
+		start = MAX (bisect - support + 0.5, 0.0);
+		stop = MIN (bisect + support + 0.5, cairo_image_surface_get_height (image));
+		density = 0.0;
+
+		for (n = 0; n < stop - start; n++) {
+			weights[n] = resize_filter_get_weight (resize_filter, scale * ((double) (start + n) - bisect + 0.5));
+			density += weights[n];
+		}
+
+		if ((density != 0.0) && (density != 1.0)) {
+			register int i;
+
+			density = reciprocal (density);
+			for (i = 0; i < n; i++)
+				weights[i] *= density;
+		}
+
+		p_src_col = p_src + (start * src_rowstride);
+		p_dest_pixel = p_dest + (y * dest_rowstride);
+		for (x = 0; x < cairo_image_surface_get_width (scaled); x++) {
+			guchar       *p_src_pixel;
+			double        r, g, b, a;
+			register int  i;
+
+			p_src_pixel = p_src_col;
+			r = g = b = a = 0.0;
+			for (i = 0; i < n; i++) {
+				double w = weights[i];
+				r += w * p_src_pixel[CAIRO_RED];
+				g += w * p_src_pixel[CAIRO_GREEN];
+				b += w * p_src_pixel[CAIRO_BLUE];
+				a += w * p_src_pixel[CAIRO_ALPHA];
+
+				p_src_pixel += src_rowstride;
+			}
+
+			p_dest_pixel[CAIRO_RED] = clamp_pixel (r);
+			p_dest_pixel[CAIRO_GREEN] = clamp_pixel (g);
+			p_dest_pixel[CAIRO_BLUE] = clamp_pixel (b);
+			p_dest_pixel[CAIRO_ALPHA] = clamp_pixel (a);
+
+			p_dest_pixel += 4;
+			p_src_col += 4;
+		}
+	}
+
+	cairo_surface_mark_dirty (scaled);
+
+	g_free (weights);
+}
+
+
+cairo_surface_t *
+_cairo_image_surface_scale_filter (cairo_surface_t *image,
+				   int              new_width,
+				   int              new_height,
+				   filter_type_t    filter)
+{
+	int              src_width;
+	int              src_height;
+	cairo_surface_t *scaled;
+	resize_filter_t *resize_filter;
+	double           x_factor;
+	double           y_factor;
+	cairo_surface_t *tmp;
+
+	src_width = cairo_image_surface_get_width  (image);
+	src_height = cairo_image_surface_get_height (image);
+
+	if ((src_width == new_width) && (src_height == new_height))
+		return _cairo_image_surface_copy (image);
+
+	scaled = cairo_surface_create_similar_image (image,
+						     CAIRO_FORMAT_ARGB32,
+						     new_width,
+						     new_height);
+	if (scaled == NULL)
+		return NULL;
+
+	resize_filter = resize_filter_create (filter);
+	x_factor = (double) new_width / src_width;
+	y_factor = (double) new_height / src_height;
+	if (x_factor * y_factor > WORKLOAD_FACTOR) {
+		tmp = cairo_surface_create_similar_image (image,
+							  CAIRO_FORMAT_ARGB32,
+							  new_width,
+							  src_height);
+
+		horizontal_scale (resize_filter, image, tmp, x_factor);
+		vertical_scale (resize_filter, tmp, scaled, y_factor);
+	}
+	else {
+		tmp = cairo_surface_create_similar_image (image,
+							  CAIRO_FORMAT_ARGB32,
+							  src_width,
+							  new_height);
+		vertical_scale (resize_filter, image, tmp, y_factor);
+		horizontal_scale (resize_filter, tmp, scaled, x_factor);
+	}
+
+	resize_filter_destroy (resize_filter);
+	cairo_surface_destroy (tmp);
+
+	return scaled;
+}
+
+
 cairo_surface_t *
 _cairo_image_surface_scale (cairo_surface_t *image,
 			    int              scaled_width,
 			    int              scaled_height,
 			    gboolean         high_quality)
 {
+#if 0
+
 	GdkPixbuf       *p;
 	GdkPixbuf       *scaled_p;
 	cairo_surface_t *scaled;
@@ -42,4 +494,13 @@ _cairo_image_surface_scale (cairo_surface_t *image,
 	g_object_unref (p);
 
 	return scaled;
+
+#else
+
+	if (high_quality)
+		return _cairo_image_surface_scale_filter (image, scaled_width, scaled_height, FILTER_TRIANGLE);
+	else
+		return _cairo_image_surface_scale_nearest (image, scaled_width, scaled_height);
+
+#endif
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]