/* 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 #include #include #include "downscaling.ui" #include 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, double scale); typedef struct { const char *name; set_filter_func_t set_filter; } filter_t; static pixman_fixed_16_16_t * create_gaussian_kernel (int r, int *n_params) { pixman_fixed_16_16_t *params; double sigma = r / 2.0; int i, j; *n_params = 2 * r + 1; *n_params *= *n_params; *n_params += 2; params = g_malloc (*n_params * sizeof (pixman_fixed_16_16_t)); for (i = -r; i <= r; i++) { for (j = -r; j <= r; j++) { int idx = (i + r) * (2 * r + 1) + j + r + 2; double v = (1 / (2 * G_PI * sigma * sigma)) * exp (-(j * j + i * i) / (2 * sigma * sigma)); g_assert (idx < *n_params); params[idx] = _cairo_fixed_16_16_from_double (v); #if 0 g_print ("%f ", v); #endif } #if 0 g_print ("\n"); #endif } return params; } typedef double (* convolution_t) (int size, double x, double y); static void set_convolution (pixman_image_t *image, int w, int h, int size, convolution_t convolution) { int n_params; pixman_fixed_16_16_t *params; double *dparams; double sum; int i, j; n_params = size * size + 2; dparams = g_malloc (n_params * sizeof (double)); params = g_malloc (n_params * sizeof (pixman_fixed_16_16_t)); sum = 0.0; for (i = 0; i < size; ++i) { for (j = 0; j < size; ++j) { int idx = i * size + j + 2; dparams[idx] = convolution (size, i - size / 2, j - size / 2); sum += dparams[idx]; } } g_print ("sum %f\n", sum); for (i = 0; i < size; ++i) { for (j = 0; j < size; ++j) { int idx = i * size + j + 2; params[idx] = _cairo_fixed_16_16_from_double (dparams[idx] / sum); g_print ("%.4f ", dparams[idx] / sum); } g_print ("\n"); } params[0] = size << 16; params[1] = size << 16; pixman_image_set_filter (image, PIXMAN_FILTER_CONVOLUTION, params, n_params); } static double gaussian (int size, double x, double y) { double sigma = (size / 2.0) / 2.0; return (1 / (2 * G_PI * sigma * sigma)) * exp (-(x * x + y * y) / (2 * sigma * sigma)); } static void set_gaussian (pixman_image_t *image, int w, int h, double scale) { set_convolution (image, w, h, 5 / scale, gaussian); } static double lanczos (int size, double x, double y) { double a = size; double d = sqrt (x * x + y * y); double res; if (fabs (d) < DBL_EPSILON) { res = 1.0; } else { res = (a * sin (G_PI * d) * sin ((G_PI * d)/ a)) / (G_PI * G_PI * d * d); } return res; } static void set_lanczos (pixman_image_t *image, int w, int h, double scale) { set_convolution (image, w, h, 5 / scale, lanczos); } static void set_bilinear (pixman_image_t *image, int w, int h, double scale) { pixman_image_set_filter (image, PIXMAN_FILTER_BILINEAR, NULL, 0); } static void set_nearest (pixman_image_t *image, int w, int h, double scale) { pixman_image_set_filter (image, PIXMAN_FILTER_NEAREST, NULL, 0); } static double box (int size, double x, double y) { return 1; } static void set_box (pixman_image_t *image, int w, int h, double scale) { if (scale < 1.0) { set_convolution (image, w, h, 1.0 / scale + 0.5, box); } else { pixman_image_set_filter (image, PIXMAN_FILTER_BILINEAR, NULL, 0); } } static void set_pixbuf (pixman_image_t *image, int w, int h, double scale) { g_print ("scale: %f\n", scale); pixman_image_set_filter (image, PIXMAN_FILTER_AREA, NULL, 0); } static const filter_t filters[] = { { "nearest", set_nearest }, { "bilinear", set_bilinear }, { "gaussian", set_gaussian }, { "lanczos", set_lanczos }, { "box", set_box }, { "pixbuf", set_pixbuf }, { NULL }, }; static void set_filter (pixman_image_t *image, ImageInfo *info, double scale) { 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, scale); 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, scale); 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 \n", argv[0]); return 0; } read_image (argv[1], &info); for (i = 0; filters[i].name != NULL; ++i) { GtkWidget *w = GTK_WIDGET (gtk_builder_get_object (info.builder, filters[i].name)); g_signal_connect_swapped (w, "toggled", G_CALLBACK (gtk_widget_queue_draw), da); } gtk_widget_set_size_request (da, 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; }