/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2011 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 #include #include "cairo-rotate.h" #include "gth-image-rotator.h" #define MIN4(a,b,c,d) MIN(MIN((a),(b)),MIN((c),(d))) #define MAX4(a,b,c,d) MAX(MAX((a),(b)),MAX((c),(d))) #define G_2_PI (G_PI * 2) #define RAD_TO_DEG(x) ((x) * 180 / G_PI) #define DEG_TO_RAD(x) ((x) * G_PI / 180) enum { CHANGED, CENTER_CHANGED, ANGLE_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static void gth_image_rotator_gth_image_tool_interface_init (GthImageViewerToolInterface *iface); struct _GthImageRotatorPrivate { GthImageViewer *viewer; /* options */ GdkPoint center; double angle; GdkRGBA background_color; gboolean enable_crop; cairo_rectangle_int_t crop_region; GthGridType grid_type; GthTransformResize resize; /* utility variables */ int original_width; int original_height; double preview_zoom; cairo_surface_t *preview_image; cairo_rectangle_int_t preview_image_area; GdkPoint preview_center; cairo_rectangle_int_t clip_area; cairo_matrix_t matrix; gboolean dragging; double angle_before_dragging; GdkPoint drag_p1; GdkPoint drag_p2; GthFit original_fit_mode; gboolean original_zoom_enabled; }; G_DEFINE_TYPE_WITH_CODE (GthImageRotator, gth_image_rotator, G_TYPE_OBJECT, G_ADD_PRIVATE (GthImageRotator) G_IMPLEMENT_INTERFACE (GTH_TYPE_IMAGE_VIEWER_TOOL, gth_image_rotator_gth_image_tool_interface_init)) static void gth_image_rotator_set_viewer (GthImageViewerTool *base, GthImageViewer *viewer) { GthImageRotator *self = GTH_IMAGE_ROTATOR (base); GdkCursor *cursor; self->priv->viewer = viewer; self->priv->original_fit_mode = gth_image_viewer_get_fit_mode (GTH_IMAGE_VIEWER (viewer)); self->priv->original_zoom_enabled = gth_image_viewer_get_zoom_enabled (GTH_IMAGE_VIEWER (viewer)); gth_image_viewer_set_fit_mode (GTH_IMAGE_VIEWER (viewer), GTH_FIT_SIZE_IF_LARGER); gth_image_viewer_set_zoom_enabled (GTH_IMAGE_VIEWER (viewer), FALSE); cursor = _gdk_cursor_new_for_widget (GTK_WIDGET (self->priv->viewer), GDK_LEFT_PTR); gth_image_viewer_set_cursor (self->priv->viewer, cursor); g_object_unref (cursor); } static void gth_image_rotator_unset_viewer (GthImageViewerTool *base, GthImageViewer *viewer) { GthImageRotator *self = GTH_IMAGE_ROTATOR (base); gth_image_viewer_set_fit_mode (GTH_IMAGE_VIEWER (viewer), self->priv->original_fit_mode); gth_image_viewer_set_zoom_enabled (GTH_IMAGE_VIEWER (viewer), self->priv->original_zoom_enabled); self->priv->viewer = NULL; } static void gth_image_rotator_realize (GthImageViewerTool *base) { /* void */ } static void gth_image_rotator_unrealize (GthImageViewerTool *base) { /* void */ } static void _cairo_matrix_transform_point (cairo_matrix_t *matrix, double x, double y, double *tx, double *ty) { *tx = x; *ty = y; cairo_matrix_transform_point (matrix, tx, ty); } static void gth_transform_resize (cairo_matrix_t *matrix, GthTransformResize resize, cairo_rectangle_int_t *original, cairo_rectangle_int_t *boundary) { int x1, y1, x2, y2; x1 = original->x; y1 = original->y; x2 = original->x + original->width; y2 = original->y + original->height; switch (resize) { case GTH_TRANSFORM_RESIZE_CLIP: /* keep the original size */ break; case GTH_TRANSFORM_RESIZE_BOUNDING_BOX: case GTH_TRANSFORM_RESIZE_CROP: { double dx1, dx2, dx3, dx4; double dy1, dy2, dy3, dy4; _cairo_matrix_transform_point (matrix, x1, y1, &dx1, &dy1); _cairo_matrix_transform_point (matrix, x2, y1, &dx2, &dy2); _cairo_matrix_transform_point (matrix, x1, y2, &dx3, &dy3); _cairo_matrix_transform_point (matrix, x2, y2, &dx4, &dy4); x1 = (int) floor (MIN4 (dx1, dx2, dx3, dx4)); y1 = (int) floor (MIN4 (dy1, dy2, dy3, dy4)); x2 = (int) ceil (MAX4 (dx1, dx2, dx3, dx4)); y2 = (int) ceil (MAX4 (dy1, dy2, dy3, dy4)); break; } } boundary->x = x1; boundary->y = y1; boundary->width = x2 - x1; boundary->height = y2 - y1; } static void _gth_image_rotator_update_tranformation_matrix (GthImageRotator *self) { int tx, ty; self->priv->preview_center.x = self->priv->center.x * self->priv->preview_zoom; self->priv->preview_center.y = self->priv->center.y * self->priv->preview_zoom; tx = self->priv->preview_image_area.x + self->priv->preview_center.x; ty = self->priv->preview_image_area.y + self->priv->preview_center.y; cairo_matrix_init_identity (&self->priv->matrix); cairo_matrix_translate (&self->priv->matrix, tx, ty); cairo_matrix_rotate (&self->priv->matrix, self->priv->angle); cairo_matrix_translate (&self->priv->matrix, -tx, -ty); gth_transform_resize (&self->priv->matrix, self->priv->resize, &self->priv->preview_image_area, &self->priv->clip_area); } static void update_image_surface (GthImageRotator *self) { GtkAllocation allocation; cairo_surface_t *image; int max_size; int width; int height; cairo_surface_t *preview_image; if (self->priv->preview_image != NULL) { cairo_surface_destroy (self->priv->preview_image); self->priv->preview_image = NULL; } image = gth_image_viewer_get_current_image (GTH_IMAGE_VIEWER (self->priv->viewer)); if (image == NULL) return; if (! _cairo_image_surface_get_original_size (image, &self->priv->original_width, &self->priv->original_height)) { self->priv->original_width = cairo_image_surface_get_width (image); self->priv->original_height = cairo_image_surface_get_height (image); } width = cairo_image_surface_get_width (image); height = cairo_image_surface_get_height (image); gtk_widget_get_allocation (GTK_WIDGET (self->priv->viewer), &allocation); max_size = MAX (allocation.width, allocation.height) / G_SQRT2 + 2; if (scale_keeping_ratio (&width, &height, max_size, max_size, FALSE)) preview_image = _cairo_image_surface_scale_fast (image, width, height); else preview_image = cairo_surface_reference (image); self->priv->preview_zoom = (double) width / self->priv->original_width; self->priv->preview_image = preview_image; self->priv->preview_image_area.width = width; self->priv->preview_image_area.height = height; self->priv->preview_image_area.x = MAX ((allocation.width - self->priv->preview_image_area.width) / 2 - 0.5, 0); self->priv->preview_image_area.y = MAX ((allocation.height - self->priv->preview_image_area.height) / 2 - 0.5, 0); _gth_image_rotator_update_tranformation_matrix (self); } static void gth_image_rotator_size_allocate (GthImageViewerTool *base, GtkAllocation *allocation) { update_image_surface (GTH_IMAGE_ROTATOR (base)); } static void gth_image_rotator_map (GthImageViewerTool *base) { /* void */ } static void gth_image_rotator_unmap (GthImageViewerTool *base) { /* void */ } static void paint_image (GthImageRotator *self, cairo_t *cr) { cairo_matrix_t matrix; cairo_save (cr); cairo_get_matrix (cr, &matrix); cairo_matrix_multiply (&matrix, &self->priv->matrix, &matrix); cairo_set_matrix (cr, &matrix); cairo_set_source_surface (cr, self->priv->preview_image, self->priv->preview_image_area.x, self->priv->preview_image_area.y); cairo_rectangle (cr, self->priv->preview_image_area.x, self->priv->preview_image_area.y, self->priv->preview_image_area.width, self->priv->preview_image_area.height); cairo_fill (cr); cairo_restore (cr); } static void paint_darker_background (GthImageRotator *self, cairo_t *cr) { cairo_rectangle_int_t crop_region; GtkAllocation allocation; cairo_save (cr); cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5); gtk_widget_get_allocation (GTK_WIDGET (self->priv->viewer), &allocation); switch (self->priv->resize) { case GTH_TRANSFORM_RESIZE_BOUNDING_BOX: case GTH_TRANSFORM_RESIZE_CLIP: crop_region = self->priv->clip_area; break; case GTH_TRANSFORM_RESIZE_CROP: /* the crop_region is not zoomed the clip_area is already zoomed */ cairo_scale (cr, self->priv->preview_zoom, self->priv->preview_zoom); crop_region = self->priv->crop_region; crop_region.x += self->priv->clip_area.x / self->priv->preview_zoom; crop_region.y += self->priv->clip_area.y / self->priv->preview_zoom; allocation.width /= self->priv->preview_zoom; allocation.height /= self->priv->preview_zoom; break; default: g_assert_not_reached (); } /* left side */ cairo_rectangle (cr, 0, 0, crop_region.x, allocation.height); /* right side */ cairo_rectangle (cr, crop_region.x + crop_region.width, 0, allocation.width - crop_region.x - crop_region.width, allocation.height); /* top */ cairo_rectangle (cr, crop_region.x, 0, crop_region.width, crop_region.y); /* bottom */ cairo_rectangle (cr, crop_region.x, crop_region.y + crop_region.height, crop_region.width, allocation.height - crop_region.y - crop_region.height); cairo_fill (cr); cairo_restore (cr); } static void paint_grid (GthImageRotator *self, cairo_t *cr) { cairo_rectangle_int_t grid; cairo_save (cr); switch (self->priv->resize) { case GTH_TRANSFORM_RESIZE_BOUNDING_BOX: case GTH_TRANSFORM_RESIZE_CLIP: grid = self->priv->clip_area; break; case GTH_TRANSFORM_RESIZE_CROP: cairo_scale (cr, self->priv->preview_zoom, self->priv->preview_zoom); grid = self->priv->crop_region; grid.x += self->priv->clip_area.x / self->priv->preview_zoom; grid.y += self->priv->clip_area.y / self->priv->preview_zoom; break; } _cairo_paint_grid (cr, &grid, self->priv->grid_type); cairo_restore (cr); } static void paint_point (GthImageRotator *self, cairo_t *cr, GdkPoint *p) { double radius = 10.0; cairo_save (cr); #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 9, 2) cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE); #endif cairo_move_to (cr, p->x - radius, p->y - radius); cairo_line_to (cr, p->x + radius, p->y + radius); cairo_move_to (cr, p->x - radius, p->y + radius); cairo_line_to (cr, p->x + radius, p->y - radius); cairo_stroke (cr); cairo_restore (cr); } static void gth_image_rotator_draw (GthImageViewerTool *base, cairo_t *cr) { GthImageRotator *self = GTH_IMAGE_ROTATOR (base); GtkAllocation allocation; cairo_save (cr); /* background */ /* GtkStyleContext *style_context; GdkRGBA color; style_context = gtk_widget_get_style_context (GTK_WIDGET (self->priv->viewer)); gtk_style_context_get_background_color (style_context, gtk_widget_get_state (GTK_WIDGET (self->priv->viewer)), &color); gdk_cairo_set_source_rgba (cr, &color); */ gtk_widget_get_allocation (GTK_WIDGET (self->priv->viewer), &allocation); cairo_rectangle (cr, 0, 0, allocation.width, allocation.height); cairo_set_source_rgba (cr, self->priv->background_color.red, self->priv->background_color.green, self->priv->background_color.blue, self->priv->background_color.alpha); cairo_fill (cr); cairo_restore (cr); if (self->priv->preview_image == NULL) return; paint_image (self, cr); paint_darker_background (self, cr); paint_grid (self, cr); if (self->priv->dragging) { GdkPoint center; cairo_save (cr); cairo_set_antialias (cr, CAIRO_ANTIALIAS_DEFAULT); cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); cairo_restore (cr); center.x = self->priv->center.x * self->priv->preview_zoom + self->priv->preview_image_area.x; center.y = self->priv->center.y * self->priv->preview_zoom + self->priv->preview_image_area.y; paint_point (self, cr, ¢er); /* used for debugging purposes paint_point (self, cr, &self->priv->drag_p1); paint_point (self, cr, &self->priv->drag_p2); */ } } static gboolean gth_image_rotator_button_release (GthImageViewerTool *base, GdkEventButton *event) { GthImageRotator *self = GTH_IMAGE_ROTATOR (base); GdkCursor *cursor; self->priv->dragging = FALSE; self->priv->drag_p1.x = 0; self->priv->drag_p1.y = 0; self->priv->drag_p2.x = 0; self->priv->drag_p2.y = 0; cursor = _gdk_cursor_new_for_widget (GTK_WIDGET (self->priv->viewer), GDK_LEFT_PTR); gth_image_viewer_set_cursor (self->priv->viewer, cursor); g_object_unref (cursor); gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer)); return FALSE; } static gboolean gth_image_rotator_button_press (GthImageViewerTool *base, GdkEventButton *event) { GthImageRotator *self = GTH_IMAGE_ROTATOR (base); if (event->type == GDK_2BUTTON_PRESS) { double x, y; x = (event->x - self->priv->preview_image_area.x) / self->priv->preview_zoom; y = (event->y - self->priv->preview_image_area.y) / self->priv->preview_zoom; g_signal_emit (self, signals[CENTER_CHANGED], 0, (int) x, (int) y); } if (event->type == GDK_BUTTON_PRESS) { self->priv->dragging = FALSE; self->priv->drag_p1.x = event->x; self->priv->drag_p1.y = event->y; } return FALSE; } static double get_angle (GdkPoint *p1, GdkPoint *p2) { double a = 0.0; int x, y; x = p2->x - p1->x; y = p2->y - p1->y; if (x >= 0) { if (y >= 0) a = atan2 (y, x); else a = G_2_PI - atan2 (- y, x); } else { if (y >= 0) a = G_PI - atan2 (y, - x); else a = G_PI + atan2 (- y, - x); } return a; } static gboolean gth_image_rotator_motion_notify (GthImageViewerTool *base, GdkEventMotion *event) { GthImageRotator *self = GTH_IMAGE_ROTATOR (base); if (! self->priv->dragging && gtk_drag_check_threshold (GTK_WIDGET (self->priv->viewer), self->priv->drag_p1.x, self->priv->drag_p1.y, self->priv->drag_p2.x, self->priv->drag_p2.y)) { GdkCursor *cursor; self->priv->angle_before_dragging = self->priv->angle; self->priv->dragging = TRUE; cursor = gdk_cursor_new_from_name (gtk_widget_get_display (GTK_WIDGET (self->priv->viewer)), "grabbing"); gth_image_viewer_set_cursor (self->priv->viewer, cursor); if (cursor != NULL) g_object_unref (cursor); } if (self->priv->dragging) { GdkPoint center; double angle1; double angle2; double angle; self->priv->drag_p2.x = event->x; self->priv->drag_p2.y = event->y; center.x = self->priv->center.x * self->priv->preview_zoom + self->priv->preview_image_area.x; center.y = self->priv->center.y * self->priv->preview_zoom + self->priv->preview_image_area.y; angle1 = get_angle (¢er, &self->priv->drag_p1); angle2 = get_angle (¢er, &self->priv->drag_p2); angle = self->priv->angle_before_dragging + (angle2 - angle1); if (angle < - G_PI) angle = G_2_PI + angle; if (angle > + G_PI) angle = angle - G_2_PI; g_signal_emit (self, signals[ANGLE_CHANGED], 0, CLAMP (RAD_TO_DEG (angle), -180.0, 180)); } return FALSE; } static void gth_image_rotator_image_changed (GthImageViewerTool *base) { update_image_surface (GTH_IMAGE_ROTATOR (base)); } static void gth_image_rotator_zoom_changed (GthImageViewerTool *base) { update_image_surface (GTH_IMAGE_ROTATOR (base)); } static void gth_image_rotator_finalize (GObject *object) { GthImageRotator *self; g_return_if_fail (object != NULL); g_return_if_fail (GTH_IS_IMAGE_ROTATOR (object)); self = (GthImageRotator *) object; if (self->priv->preview_image != NULL) cairo_surface_destroy (self->priv->preview_image); /* Chain up */ G_OBJECT_CLASS (gth_image_rotator_parent_class)->finalize (object); } static void gth_image_rotator_class_init (GthImageRotatorClass *class) { GObjectClass *gobject_class; gobject_class = (GObjectClass*) class; gobject_class->finalize = gth_image_rotator_finalize; signals[CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GthImageRotatorClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[CENTER_CHANGED] = g_signal_new ("center-changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GthImageRotatorClass, center_changed), NULL, NULL, gth_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); signals[ANGLE_CHANGED] = g_signal_new ("angle-changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GthImageRotatorClass, angle_changed), NULL, NULL, g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE); } static void gth_image_rotator_gth_image_tool_interface_init (GthImageViewerToolInterface *iface) { iface->set_viewer = gth_image_rotator_set_viewer; iface->unset_viewer = gth_image_rotator_unset_viewer; iface->realize = gth_image_rotator_realize; iface->unrealize = gth_image_rotator_unrealize; iface->size_allocate = gth_image_rotator_size_allocate; iface->map = gth_image_rotator_map; iface->unmap = gth_image_rotator_unmap; iface->draw = gth_image_rotator_draw; iface->button_press = gth_image_rotator_button_press; iface->button_release = gth_image_rotator_button_release; iface->motion_notify = gth_image_rotator_motion_notify; iface->image_changed = gth_image_rotator_image_changed; iface->zoom_changed = gth_image_rotator_zoom_changed; } static void gth_image_rotator_init (GthImageRotator *self) { self->priv = gth_image_rotator_get_instance_private (self); self->priv->preview_image = NULL; self->priv->grid_type = GTH_GRID_NONE; self->priv->resize = GTH_TRANSFORM_RESIZE_BOUNDING_BOX; self->priv->background_color.red = 0.0; self->priv->background_color.green = 0.0; self->priv->background_color.blue = 0.0; self->priv->background_color.alpha = 1.0; self->priv->enable_crop = FALSE; self->priv->crop_region.x = 0; self->priv->crop_region.y = 0; self->priv->crop_region.width = 0; self->priv->crop_region.height = 0; self->priv->dragging = FALSE; } GthImageViewerTool * gth_image_rotator_new (void) { GthImageRotator *rotator; rotator = g_object_new (GTH_TYPE_IMAGE_ROTATOR, NULL); rotator->priv->angle = 0.0; return GTH_IMAGE_VIEWER_TOOL (rotator); } void gth_image_rotator_set_grid_type (GthImageRotator *self, GthGridType grid_type) { if (grid_type == self->priv->grid_type) return; self->priv->grid_type = grid_type; if (self->priv->viewer != NULL) gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer)); } GthGridType gth_image_rotator_get_grid_type (GthImageRotator *self) { return self->priv->grid_type; } void gth_image_rotator_set_center (GthImageRotator *self, int x, int y) { self->priv->center.x = x; self->priv->center.y = y; _gth_image_rotator_update_tranformation_matrix (self); if (self->priv->viewer != NULL) gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer)); g_signal_emit (self, signals[CHANGED], 0); } void gth_image_rotator_get_center (GthImageRotator *self, int *x, int *y) { *x = self->priv->center.x; *y = self->priv->center.y; } void gth_image_rotator_set_angle (GthImageRotator *self, double angle) { double radiants; radiants = DEG_TO_RAD (angle); if (radiants == self->priv->angle) return; self->priv->angle = radiants; _gth_image_rotator_update_tranformation_matrix (self); if (self->priv->viewer != NULL) gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer)); g_signal_emit (self, signals[CHANGED], 0); } double gth_image_rotator_get_angle (GthImageRotator *self) { return self->priv->angle; } void gth_image_rotator_set_resize (GthImageRotator *self, GthTransformResize resize) { self->priv->resize = resize; _gth_image_rotator_update_tranformation_matrix (self); if (self->priv->viewer != NULL) gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer)); g_signal_emit (self, signals[CHANGED], 0); } GthTransformResize gth_image_rotator_get_resize (GthImageRotator *self) { return self->priv->resize; } void gth_image_rotator_set_crop_region (GthImageRotator *self, cairo_rectangle_int_t *region) { self->priv->enable_crop = (region != NULL); if (region != NULL) self->priv->crop_region = *region; if (self->priv->viewer != NULL) gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer)); g_signal_emit (self, signals[CHANGED], 0); } void gth_image_rotator_set_background (GthImageRotator *self, GdkRGBA *color) { self->priv->background_color = *color; if (self->priv->viewer != NULL) gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer)); g_signal_emit (self, signals[CHANGED], 0); } void gth_image_rotator_get_background (GthImageRotator *self, GdkRGBA *color) { *color = self->priv->background_color; } G_GNUC_UNUSED static cairo_surface_t * gth_image_rotator_get_result_fast (GthImageRotator *self) { double tx, ty; cairo_matrix_t matrix; cairo_rectangle_int_t image_area; cairo_rectangle_int_t clip_area; cairo_surface_t *output; cairo_t *cr; /* compute the transformation matrix and the clip area */ tx = self->priv->center.x; ty = self->priv->center.y; cairo_matrix_init_identity (&matrix); cairo_matrix_translate (&matrix, tx, ty); cairo_matrix_rotate (&matrix, self->priv->angle); cairo_matrix_translate (&matrix, -tx, -ty); image_area.x = 0.0; image_area.y = 0.0; image_area.width = self->priv->original_width; image_area.height = self->priv->original_height; gth_transform_resize (&matrix, self->priv->resize, &image_area, &clip_area); if (! self->priv->enable_crop) { self->priv->crop_region.x = 0; self->priv->crop_region.y = 0; self->priv->crop_region.width = clip_area.width; self->priv->crop_region.height = clip_area.height; } output = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, self->priv->crop_region.width, self->priv->crop_region.height); /* set the device offset to make the clip area start from the top left * corner of the output image */ cairo_surface_set_device_offset (output, - clip_area.x - self->priv->crop_region.x, - clip_area.y - self->priv->crop_region.y); cr = cairo_create (output); /* paint the background */ cairo_rectangle (cr, clip_area.x, clip_area.y, clip_area.width, clip_area.height); cairo_clip_preserve (cr); cairo_set_source_rgba (cr, self->priv->background_color.red, self->priv->background_color.green, self->priv->background_color.blue, self->priv->background_color.alpha); cairo_fill (cr); /* paint the rotated image */ cairo_set_matrix (cr, &matrix); cairo_set_source_surface (cr, gth_image_viewer_get_current_image (GTH_IMAGE_VIEWER (self->priv->viewer)), image_area.x, image_area.y); cairo_rectangle (cr, image_area.x, image_area.y, image_area.width, image_area.height); cairo_fill (cr); cairo_surface_flush (output); cairo_surface_set_device_offset (output, 0.0, 0.0); cairo_destroy (cr); return output; } static cairo_surface_t * gth_image_rotator_get_result_high_quality (GthImageRotator *self, cairo_surface_t *image, GthAsyncTask *task) { cairo_surface_t *rotated; cairo_surface_t *result; double zoom; rotated = _cairo_image_surface_rotate (image, self->priv->angle / G_PI * 180.0, TRUE, &self->priv->background_color, task); switch (self->priv->resize) { case GTH_TRANSFORM_RESIZE_BOUNDING_BOX: self->priv->crop_region.x = 0; self->priv->crop_region.y = 0; self->priv->crop_region.width = cairo_image_surface_get_width (rotated); self->priv->crop_region.height = cairo_image_surface_get_height (rotated); break; case GTH_TRANSFORM_RESIZE_CLIP: self->priv->crop_region.x = MAX (((double) cairo_image_surface_get_width (rotated) - cairo_image_surface_get_width (image)) / 2.0, 0); self->priv->crop_region.y = MAX (((double) cairo_image_surface_get_height (rotated) - cairo_image_surface_get_height (image)) / 2.0, 0); self->priv->crop_region.width = cairo_image_surface_get_width (image); self->priv->crop_region.height = cairo_image_surface_get_height (image); break; case GTH_TRANSFORM_RESIZE_CROP: /* set by the user */ zoom = (double) cairo_image_surface_get_width (image) / self->priv->original_width; self->priv->crop_region.x *= zoom; self->priv->crop_region.width *= zoom; zoom = (double) cairo_image_surface_get_height (image) / self->priv->original_height; self->priv->crop_region.y *= zoom; self->priv->crop_region.height *= zoom; break; } result = _cairo_image_surface_copy_subsurface (rotated, self->priv->crop_region.x, self->priv->crop_region.y, MIN (self->priv->crop_region.width, cairo_image_surface_get_width (rotated) - self->priv->crop_region.x), MIN (self->priv->crop_region.height, cairo_image_surface_get_height (rotated) - self->priv->crop_region.y)); cairo_surface_destroy (rotated); return result; } cairo_surface_t * gth_image_rotator_get_result (GthImageRotator *self, cairo_surface_t *image, GthAsyncTask *task) { return gth_image_rotator_get_result_high_quality (self, image, task); }