/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2011 The Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "cairo-rotate.h" void _cairo_image_surface_rotate_get_cropping_parameters (cairo_surface_t *image, double angle, double *p1_plus_p2, double *p_min) { double angle_rad; double cos_angle, sin_angle; double src_width, src_height; double t1, t2; if (angle < -90) angle += 180; else if (angle > 90) angle -= 180; angle_rad = fabs (angle) / 180.0 * G_PI; cos_angle = cos (angle_rad); sin_angle = sin (angle_rad); src_width = cairo_image_surface_get_width (image) - 1.0; src_height = cairo_image_surface_get_height (image) - 1.0; if (src_width > src_height) { t1 = cos_angle * src_width - sin_angle * src_height; t2 = sin_angle * src_width + cos_angle * src_height; *p1_plus_p2 = 1.0 + (t1 * src_height) / (t2 * src_width); *p_min = src_height / src_width * sin_angle * cos_angle + (*p1_plus_p2 - 1) * cos_angle * cos_angle; } else { t1 = cos_angle * src_height - sin_angle * src_width; t2 = sin_angle * src_height + cos_angle * src_width; *p1_plus_p2 = 1.0 + (t1 * src_width) / (t2 * src_height); *p_min = src_width / src_height * sin_angle * cos_angle + (*p1_plus_p2 - 1) * cos_angle * cos_angle; } } void _cairo_image_surface_rotate_get_cropping_region (cairo_surface_t *image, double angle, double p1, double p2, cairo_rectangle_int_t *region) { double angle_rad; double cos_angle, sin_angle; double src_width, src_height; double new_width; double xx1, yy1, xx2, yy2; if (angle < -90) angle += 180; else if (angle > 90) angle -= 180; p1 = CLAMP (p1, 0.0, 1.0); p2 = CLAMP (p2, 0.0, 1.0); angle_rad = fabs (angle) / 180.0 * G_PI; cos_angle = cos (angle_rad); sin_angle = sin (angle_rad); src_width = cairo_image_surface_get_width (image) - 1.0; src_height = cairo_image_surface_get_height (image) - 1.0; if (angle < 0) { /* This is to make the p1/p2 sliders behavour more user friendly */ double t; t = p1; p1 = p2; p2 = t; } if (src_width > src_height) { xx1 = p1 * src_width * cos_angle + src_height * sin_angle; yy1 = p1 * src_width * sin_angle; xx2 = (1 - p2) * src_width * cos_angle; yy2 = (1 - p2) * src_width * sin_angle + src_height * cos_angle; } else { xx1 = p1 * src_height * sin_angle; yy1 = (1 - p1) * src_height * cos_angle; xx2 = (1 - p2) * src_height * sin_angle + src_width * cos_angle; yy2 = p2 * src_height * cos_angle + src_width * sin_angle; } if (angle < 0) { new_width = (cos_angle * src_width) + (sin_angle * src_height); xx1 = new_width - xx1; xx2 = new_width - xx2; } region->x = GDOUBLE_ROUND_TO_INT (MIN (xx1, xx2)); region->y = GDOUBLE_ROUND_TO_INT (MIN (yy1, yy2)); region->width = GDOUBLE_ROUND_TO_INT (MAX (xx1, xx2)) - region->x + 1; region->height = GDOUBLE_ROUND_TO_INT (MAX (yy1, yy2)) - region->y + 1; } double _cairo_image_surface_rotate_get_align_angle (gboolean vertical, GdkPoint *p1, GdkPoint *p2) { double angle; if (! vertical) { if (p1->y == p2->y) return 0.0; if (p2->x > p1->x) angle = -atan2 (p2->y - p1->y, p2->x - p1->x); else angle = -atan2 (p1->y - p2->y, p1->x - p2->x); } else { if (p1->x == p2->x) return 0.0; if (p2->y > p1->y) angle = atan2 (p2->x - p1->x, p2->y - p1->y); else angle = atan2 (p1->x - p2->x, p1->y - p2->y); } angle = angle * 180.0 / G_PI; angle = GDOUBLE_ROUND_TO_INT (angle * 10.0) / 10.0; return angle; } static cairo_surface_t* rotate (cairo_surface_t *image, double angle, gboolean high_quality, guchar r0, guchar g0, guchar b0, guchar a0, GthAsyncTask *task) { cairo_surface_t *image_with_background; cairo_surface_t *rotated; double angle_rad; double cos_angle, sin_angle; double src_width, src_height; int new_width, new_height; int src_rowstride, new_rowstride; int xi, yi; double x, y; double x2, y2; int x2min, y2min; int x2max, y2max; double fx, fy; guchar *p_src, *p_new; guchar *p_src2, *p_new2; guchar r00, r01, r10, r11; guchar g00, g01, g10, g11; guchar b00, b01, b10, b11; guchar a00, a01, a10, a11; double half_new_width; double half_new_height; double half_src_width; double half_src_height; int tmp; guchar r, g, b, a; guint32 pixel; angle = CLAMP (angle, -90.0, 90.0); angle_rad = angle / 180.0 * G_PI; cos_angle = cos (angle_rad); sin_angle = sin (angle_rad); src_width = cairo_image_surface_get_width (image); src_height = cairo_image_surface_get_height (image); new_width = GDOUBLE_ROUND_TO_INT ( cos_angle * src_width + fabs(sin_angle) * src_height); new_height = GDOUBLE_ROUND_TO_INT (fabs (sin_angle) * src_width + cos_angle * src_height); if (a0 == 0xff) { /* pre-multiply the background color */ image_with_background = _cairo_image_surface_copy (image); p_src = _cairo_image_surface_flush_and_get_data (image); p_new = _cairo_image_surface_flush_and_get_data (image_with_background); src_rowstride = cairo_image_surface_get_stride (image); new_rowstride = cairo_image_surface_get_stride (image_with_background); cairo_surface_flush (image_with_background); for (yi = 0; yi < src_height; yi++) { p_src2 = p_src; p_new2 = p_new; for (xi = 0; xi < src_width; xi++) { a = p_src2[CAIRO_ALPHA]; r = p_src2[CAIRO_RED] + _cairo_multiply_alpha (r0, 0xff - a); g = p_src2[CAIRO_GREEN] + _cairo_multiply_alpha (g0, 0xff - a); b = p_src2[CAIRO_BLUE] + _cairo_multiply_alpha (b0, 0xff - a); pixel = CAIRO_RGBA_TO_UINT32 (r, g, b, 0xff); memcpy (p_new2, &pixel, sizeof (guint32)); p_new2 += 4; p_src2 += 4; } p_src += src_rowstride; p_new += new_rowstride; } cairo_surface_mark_dirty (image_with_background); } else image_with_background = cairo_surface_reference (image); /* create the rotated image */ rotated = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, new_width, new_height); p_src = _cairo_image_surface_flush_and_get_data (image_with_background); p_new = _cairo_image_surface_flush_and_get_data (rotated); src_rowstride = cairo_image_surface_get_stride (image_with_background); new_rowstride = cairo_image_surface_get_stride (rotated); /* * bilinear interpolation * fx * v00------------v01 * | | | * fy |--------v | * | | * | | * | | * v10------------v11 */ #define INTERPOLATE(v, v00, v01, v10, v11, fx, fy) \ tmp = (1.0 - (fy)) * \ ((1.0 - (fx)) * (v00) + (fx) * (v01)) \ + \ (fy) * \ ((1.0 - (fx)) * (v10) + (fx) * (v11)); \ v = CLAMP (tmp, 0, 255); #define GET_VALUES(r, g, b, a, x, y) \ if (x >= 0 && x < src_width && y >= 0 && y < src_height) { \ p_src2 = p_src + src_rowstride * y + 4 * x; \ r = p_src2[CAIRO_RED]; \ g = p_src2[CAIRO_GREEN]; \ b = p_src2[CAIRO_BLUE]; \ a = p_src2[CAIRO_ALPHA]; \ } \ else { \ r = r0; \ g = g0; \ b = b0; \ a = a0; \ } half_new_width = new_width / 2.0; half_new_height = new_height / 2.0; half_src_width = src_width / 2.0; half_src_height = src_height / 2.0; cairo_surface_flush (rotated); y = - half_new_height; for (yi = 0; yi < new_height; yi++) { if (task != NULL) { gboolean cancelled; double progress; gth_async_task_get_data (task, NULL, &cancelled, NULL); if (cancelled) goto out; progress = (double) yi / new_height; gth_async_task_set_data (task, NULL, NULL, &progress); } p_new2 = p_new; x = - half_new_width; for (xi = 0; xi < new_width; xi++) { x2 = cos_angle * x - sin_angle * y + half_src_width; y2 = sin_angle * x + cos_angle * y + half_src_height; if (high_quality) { /* Bilinear interpolation. */ x2min = (int) x2; y2min = (int) y2; x2max = x2min + 1; y2max = y2min + 1; GET_VALUES (r00, g00, b00, a00, x2min, y2min); GET_VALUES (r01, g01, b01, a01, x2max, y2min); GET_VALUES (r10, g10, b10, a10, x2min, y2max); GET_VALUES (r11, g11, b11, a11, x2max, y2max); fx = x2 - x2min; fy = y2 - y2min; INTERPOLATE (r, r00, r01, r10, r11, fx, fy); INTERPOLATE (g, g00, g01, g10, g11, fx, fy); INTERPOLATE (b, b00, b01, b10, b11, fx, fy); INTERPOLATE (a, a00, a01, a10, a11, fx, fy); pixel = CAIRO_RGBA_TO_UINT32 (r, g, b, a); memcpy (p_new2, &pixel, sizeof (guint32)); } else { /* Nearest neighbor */ x2min = GDOUBLE_ROUND_TO_INT (x2); y2min = GDOUBLE_ROUND_TO_INT (y2); GET_VALUES (p_new2[CAIRO_RED], p_new2[CAIRO_GREEN], p_new2[CAIRO_BLUE], p_new2[CAIRO_ALPHA], x2min, y2min); } p_new2 += 4; x += 1.0; } p_new += new_rowstride; y += 1.0; } out: cairo_surface_mark_dirty (rotated); cairo_surface_destroy (image_with_background); #undef INTERPOLATE #undef GET_VALUES return rotated; } cairo_surface_t * _cairo_image_surface_rotate (cairo_surface_t *image, double angle, gboolean high_quality, GdkRGBA *background_color, GthAsyncTask *task) { cairo_surface_t *rotated; cairo_surface_t *tmp = NULL; if (angle >= 90.0) { image = tmp = _cairo_image_surface_transform (image, GTH_TRANSFORM_ROTATE_90); angle -= 90.0; } else if (angle <= -90.0) { image = tmp = _cairo_image_surface_transform (image, GTH_TRANSFORM_ROTATE_270); angle += 90.0; } if (angle != 0.0) rotated = rotate (image, -angle, high_quality, background_color->red * 255.0, background_color->green * 255.0, background_color->blue * 255.0, background_color->alpha * 255.0, task); else rotated = cairo_surface_reference (image); if (tmp != NULL) cairo_surface_destroy (tmp); return rotated; }