Update Catalan translation
[cheese.git] / libcheese / um-crop-area.c
blob7695dcf10a40e2530aeac47313560837847dc26b
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3 * Copyright 2009 Red Hat, Inc,
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * Written by: Matthias Clasen <mclasen@redhat.com>
22 #include "config.h"
24 #include <stdlib.h>
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
30 #include "um-crop-area.h"
33 * SECTION:um-crop-area
34 * @short_description: A cropping widget for #CheeseAvatarChooser
35 * @stability: Unstable
37 * #UmCropArea provides a simple cropping tool, used in #CheeseAvatarChooser to
38 * crop an avatar from an image taken from a webcam.
41 typedef struct {
42 GdkPixbuf *browse_pixbuf;
43 GdkPixbuf *pixbuf;
44 GdkPixbuf *color_shifted;
45 gdouble scale;
46 GdkRectangle image;
47 GdkCursorType current_cursor;
48 GdkRectangle crop;
49 gint active_region;
50 gint last_press_x;
51 gint last_press_y;
52 gint base_width;
53 gint base_height;
54 gdouble aspect;
55 } UmCropAreaPrivate;
57 G_DEFINE_TYPE_WITH_PRIVATE (UmCropArea, um_crop_area, GTK_TYPE_DRAWING_AREA)
60 * shift_color_byte:
61 * @b: the color, as a single byte
62 * @shift: the amount by which to shift the color
64 * Shift the supplied color @b by the amount @shift.
66 * Returns: the shifted color
68 static inline guchar
69 shift_color_byte (guchar b,
70 gint shift)
72 return CLAMP(b + shift, 0, 255);
76 * shift_colors:
77 * @pixbuf: a #GdkPixbuf
78 * @red: amount to shift the red channel
79 * @green: amount to shift the green channel
80 * @blue: amount to shift the blue channel
81 * @alpha: amount to shift the alpha channel
83 * Shift the color channels in the supplied @pixbuf by the amounts specified by
84 * @red, @green, @blue and @alpha.
86 static void
87 shift_colors (GdkPixbuf *pixbuf,
88 gint red,
89 gint green,
90 gint blue,
91 gint alpha)
93 gint x, y, offset, y_offset, rowstride, width, height;
94 guchar *pixels;
95 gint channels;
97 width = gdk_pixbuf_get_width (pixbuf);
98 height = gdk_pixbuf_get_height (pixbuf);
99 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
100 pixels = gdk_pixbuf_get_pixels (pixbuf);
101 channels = gdk_pixbuf_get_n_channels (pixbuf);
103 for (y = 0; y < height; y++) {
104 y_offset = y * rowstride;
105 for (x = 0; x < width; x++) {
106 offset = y_offset + x * channels;
107 if (red != 0)
108 pixels[offset] = shift_color_byte (pixels[offset], red);
109 if (green != 0)
110 pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
111 if (blue != 0)
112 pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
113 if (alpha != 0 && channels >= 4)
114 pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
120 * update_pixbufs:
121 * @area: a #UmCropArea
123 * Update the #GdkPixbuf objects inside @area, by darkening the regions outside
124 * the current crop area.
126 static void
127 update_pixbufs (UmCropArea *area)
129 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
130 gint width;
131 gint height;
132 GtkAllocation allocation;
133 gdouble scale;
134 gint dest_width, dest_height;
135 GtkWidget *widget;
137 widget = GTK_WIDGET (area);
138 gtk_widget_get_allocation (widget, &allocation);
140 width = gdk_pixbuf_get_width (priv->browse_pixbuf);
141 height = gdk_pixbuf_get_height (priv->browse_pixbuf);
143 scale = allocation.height / (gdouble)height;
144 if (scale * width > allocation.width)
145 scale = allocation.width / (gdouble)width;
147 dest_width = width * scale;
148 dest_height = height * scale;
150 if (priv->pixbuf == NULL ||
151 gdk_pixbuf_get_width (priv->pixbuf) != allocation.width ||
152 gdk_pixbuf_get_height (priv->pixbuf) != allocation.height) {
153 if (priv->pixbuf != NULL)
154 g_object_unref (priv->pixbuf);
155 priv->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
156 gdk_pixbuf_get_has_alpha (priv->browse_pixbuf),
158 dest_width, dest_height);
159 gdk_pixbuf_fill (priv->pixbuf, 0x0);
161 gdk_pixbuf_scale (priv->browse_pixbuf,
162 priv->pixbuf,
163 0, 0,
164 dest_width, dest_height,
165 0, 0,
166 scale, scale,
167 GDK_INTERP_BILINEAR);
169 if (priv->color_shifted)
170 g_object_unref (priv->color_shifted);
171 priv->color_shifted = gdk_pixbuf_copy (priv->pixbuf);
172 shift_colors (priv->color_shifted, -32, -32, -32, 0);
174 if (priv->scale == 0.0) {
175 gdouble scale_to_80, scale_to_image, crop_scale;
177 /* Scale the crop rectangle to 80% of the area, or less to fit the image */
178 scale_to_80 = MIN ((gdouble)gdk_pixbuf_get_width (priv->pixbuf) * 0.8 / priv->base_width,
179 (gdouble)gdk_pixbuf_get_height (priv->pixbuf) * 0.8 / priv->base_height);
180 scale_to_image = MIN ((gdouble)dest_width / priv->base_width,
181 (gdouble)dest_height / priv->base_height);
182 crop_scale = MIN (scale_to_80, scale_to_image);
184 priv->crop.width = crop_scale * priv->base_width / scale;
185 priv->crop.height = crop_scale * priv->base_height / scale;
186 priv->crop.x = (gdk_pixbuf_get_width (priv->browse_pixbuf) - priv->crop.width) / 2;
187 priv->crop.y = (gdk_pixbuf_get_height (priv->browse_pixbuf) - priv->crop.height) / 2;
190 priv->scale = scale;
191 priv->image.x = (allocation.width - dest_width) / 2;
192 priv->image.y = (allocation.height - dest_height) / 2;
193 priv->image.width = dest_width;
194 priv->image.height = dest_height;
199 * crop_widget:
200 * @area: a #UmCropArea
201 * @crop: (out caller-allocates): a return location for a #GdkRectangle
203 * Update the supplied @crop rectangle to the current crop area.
205 static void
206 crop_to_widget (UmCropArea *area,
207 GdkRectangle *crop)
209 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
211 crop->x = priv->image.x + priv->crop.x * priv->scale;
212 crop->y = priv->image.y + priv->crop.y * priv->scale;
213 crop->width = priv->crop.width * priv->scale;
214 crop->height = priv->crop.height * priv->scale;
218 * Location:
219 * @OUTSIDE: outside the area
220 * @INSIDE: inside the area and not on a border
221 * @TOP: on the top border
222 * @TOP_LEFT: on the top-left corner
223 * @TOP_RIGHT: on the top-right corner
224 * @BOTTOM: on the bottom border
225 * @BOTTOM_LEFT: on the bottom-left corner
226 * @BOTTOM_RIGHT: on the bottom-right corner
227 * @LEFT: on the left border
228 * @RIGHT: on the right border
230 * The location of a point, relative to a rectangular area.
232 typedef enum {
233 OUTSIDE,
234 INSIDE,
235 TOP,
236 TOP_LEFT,
237 TOP_RIGHT,
238 BOTTOM,
239 BOTTOM_LEFT,
240 BOTTOM_RIGHT,
241 LEFT,
242 RIGHT
243 } Location;
246 * um_crop_area_draw:
247 * @widget: a #UmCropArea
248 * @cr: the #cairo_t to draw to
250 * Handle the #GtkWidget::draw signal and draw the @widget.
252 * Returns: %FALSE
254 static gboolean
255 um_crop_area_draw (GtkWidget *widget,
256 cairo_t *cr)
258 UmCropArea *area = UM_CROP_AREA (widget);
259 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
260 GdkRectangle crop;
261 gint width, height, ix, iy;
263 if (priv->browse_pixbuf == NULL)
264 return FALSE;
266 update_pixbufs (area);
268 width = gdk_pixbuf_get_width (priv->pixbuf);
269 height = gdk_pixbuf_get_height (priv->pixbuf);
270 crop_to_widget (area, &crop);
272 ix = priv->image.x;
273 iy = priv->image.y;
275 gdk_cairo_set_source_pixbuf (cr, priv->color_shifted, ix, iy);
276 cairo_rectangle (cr, ix, iy, width, crop.y - iy);
277 cairo_rectangle (cr, ix, crop.y, crop.x - ix, crop.height);
278 cairo_rectangle (cr, crop.x + crop.width, crop.y, width - crop.width - (crop.x - ix), crop.height);
279 cairo_rectangle (cr, ix, crop.y + crop.height, width, height - crop.height - (crop.y - iy));
280 cairo_fill (cr);
282 gdk_cairo_set_source_pixbuf (cr, priv->pixbuf, ix, iy);
283 cairo_rectangle (cr, crop.x, crop.y, crop.width, crop.height);
284 cairo_fill (cr);
286 if (priv->active_region != OUTSIDE) {
287 gint x1, x2, y1, y2;
288 cairo_set_source_rgb (cr, 1, 1, 1);
289 cairo_set_line_width (cr, 1.0);
290 x1 = crop.x + crop.width / 3.0;
291 x2 = crop.x + 2 * crop.width / 3.0;
292 y1 = crop.y + crop.height / 3.0;
293 y2 = crop.y + 2 * crop.height / 3.0;
295 cairo_move_to (cr, x1 + 0.5, crop.y);
296 cairo_line_to (cr, x1 + 0.5, crop.y + crop.height);
298 cairo_move_to (cr, x2 + 0.5, crop.y);
299 cairo_line_to (cr, x2 + 0.5, crop.y + crop.height);
301 cairo_move_to (cr, crop.x, y1 + 0.5);
302 cairo_line_to (cr, crop.x + crop.width, y1 + 0.5);
304 cairo_move_to (cr, crop.x, y2 + 0.5);
305 cairo_line_to (cr, crop.x + crop.width, y2 + 0.5);
306 cairo_stroke (cr);
309 cairo_set_source_rgb (cr, 0, 0, 0);
310 cairo_set_line_width (cr, 1.0);
311 cairo_rectangle (cr,
312 crop.x + 0.5,
313 crop.y + 0.5,
314 crop.width - 1.0,
315 crop.height - 1.0);
316 cairo_stroke (cr);
318 cairo_set_source_rgb (cr, 1, 1, 1);
319 cairo_set_line_width (cr, 2.0);
320 cairo_rectangle (cr,
321 crop.x + 2.0,
322 crop.y + 2.0,
323 crop.width - 4.0,
324 crop.height - 4.0);
325 cairo_stroke (cr);
327 return FALSE;
331 * Range:
332 * @BELOW: less than the minimum
333 * @LOWER: less than or equal to the minimum
334 * @BETWEEN: between the minimum and the maximum
335 * @UPPER: greater than or equal to the maximum
336 * @ABOVE: greater than the maximum
338 * Indicates where a number lies with respect to a minimum and maximum value.
339 * The apparent overlap is avoided by means of a threshold value.
341 * @see_also: find_range()
343 typedef enum {
344 BELOW,
345 LOWER,
346 BETWEEN,
347 UPPER,
348 ABOVE
349 } Range;
352 * find_range:
353 * @x: number to test
354 * @min: minimum to test against
355 * @max: maximum to test against
357 * Determine where @x lies in relation to @min and @max. A threshold is also
358 * used internally, giving fives possible states.
360 * Returns: the #Range
362 * @see_also: #Range
364 static Range
365 find_range (gint x,
366 gint min,
367 gint max)
369 gint tolerance = 12;
371 if (x < min - tolerance)
372 return BELOW;
373 if (x <= min + tolerance)
374 return LOWER;
375 if (x < max - tolerance)
376 return BETWEEN;
377 if (x <= max + tolerance)
378 return UPPER;
379 return ABOVE;
383 * find_location:
384 * @rect: a #GdkRectangle
385 * @x: x coordinate
386 * @y: y coordinate
388 * Find the #Location of the specified @x and @y coordinates, relative to the
389 * crop area given by @rect.
391 * Returns: the #Location
393 static Location
394 find_location (GdkRectangle *rect,
395 gint x,
396 gint y)
398 Range x_range, y_range;
399 Location location[5][5] = {
400 { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE },
401 { OUTSIDE, TOP_LEFT, TOP, TOP_RIGHT, OUTSIDE },
402 { OUTSIDE, LEFT, INSIDE, RIGHT, OUTSIDE },
403 { OUTSIDE, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, OUTSIDE },
404 { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE }
407 x_range = find_range (x, rect->x, rect->x + rect->width);
408 y_range = find_range (y, rect->y, rect->y + rect->height);
410 return location[y_range][x_range];
414 * update_cursor:
415 * @area: a #UmCropArea
416 * @x: x coordinate
417 * @y: y coordinate
419 * Update the type of the cursor, depending on which point of the crop
420 * rectangle the pointer is over.
422 static void
423 update_cursor (UmCropArea *area,
424 gint x,
425 gint y)
427 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
428 gint cursor_type;
429 GdkRectangle crop;
430 gint region;
432 region = priv->active_region;
433 if (region == OUTSIDE) {
434 crop_to_widget (area, &crop);
435 region = find_location (&crop, x, y);
438 switch (region) {
439 case OUTSIDE:
440 cursor_type = GDK_LEFT_PTR;
441 break;
442 case TOP_LEFT:
443 cursor_type = GDK_TOP_LEFT_CORNER;
444 break;
445 case TOP:
446 cursor_type = GDK_TOP_SIDE;
447 break;
448 case TOP_RIGHT:
449 cursor_type = GDK_TOP_RIGHT_CORNER;
450 break;
451 case LEFT:
452 cursor_type = GDK_LEFT_SIDE;
453 break;
454 case INSIDE:
455 cursor_type = GDK_FLEUR;
456 break;
457 case RIGHT:
458 cursor_type = GDK_RIGHT_SIDE;
459 break;
460 case BOTTOM_LEFT:
461 cursor_type = GDK_BOTTOM_LEFT_CORNER;
462 break;
463 case BOTTOM:
464 cursor_type = GDK_BOTTOM_SIDE;
465 break;
466 case BOTTOM_RIGHT:
467 cursor_type = GDK_BOTTOM_RIGHT_CORNER;
468 break;
469 default:
470 g_assert_not_reached ();
473 if (cursor_type != priv->current_cursor) {
474 GdkCursor *cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (area)),
475 cursor_type);
476 gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor);
477 g_object_unref (cursor);
478 priv->current_cursor = cursor_type;
483 * eval_radial_line:
484 * @center_x: starting x coordinate
485 * @center_y: starting y coordinate
486 * @bounds_x: final x coordinate
487 * @bounds_y: final y coordinate
488 * @user_x: starting x coordinate for the returned y coordinate
490 * Calculate the value of y of the line from @center_x, @center_y to @bounds_x,
491 * @bounds_y, given the x value @user_x.
493 * Returns: the value of y
495 * @see_also: um_crop_area_motion_notify_event()
497 static gint
498 eval_radial_line (gdouble center_x, gdouble center_y,
499 gdouble bounds_x, gdouble bounds_y,
500 gdouble user_x)
502 gdouble decision_slope;
503 gdouble decision_intercept;
505 decision_slope = (bounds_y - center_y) / (bounds_x - center_x);
506 decision_intercept = -(decision_slope * bounds_x);
508 return (gint) (decision_slope * user_x + decision_intercept);
512 * um_crop_area_motion_notify_event:
513 * @widget: a #UmCropArea
514 * @event: the #GdkEventMotion
516 * Update the cropped region, and redraw it, based on the current cursor
517 * position.
519 * Returns: %FALSE
521 * @see_also: eval_radial_line()
523 static gboolean
524 um_crop_area_motion_notify_event (GtkWidget *widget,
525 GdkEventMotion *event)
527 UmCropArea *area = UM_CROP_AREA (widget);
528 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
529 gint x, y;
530 gint delta_x, delta_y;
531 gint width, height;
532 gint adj_width, adj_height;
533 gint pb_width, pb_height;
534 GdkRectangle damage;
535 gint left, right, top, bottom;
536 gdouble new_width, new_height;
537 gdouble center_x, center_y;
538 gint min_width, min_height;
540 if (priv->browse_pixbuf == NULL)
541 return FALSE;
543 update_cursor (area, event->x, event->y);
545 crop_to_widget (area, &damage);
546 gtk_widget_queue_draw_area (widget,
547 damage.x - 1, damage.y - 1,
548 damage.width + 2, damage.height + 2);
550 pb_width = gdk_pixbuf_get_width (priv->browse_pixbuf);
551 pb_height = gdk_pixbuf_get_height (priv->browse_pixbuf);
553 x = (event->x - priv->image.x) / priv->scale;
554 y = (event->y - priv->image.y) / priv->scale;
556 delta_x = x - priv->last_press_x;
557 delta_y = y - priv->last_press_y;
558 priv->last_press_x = x;
559 priv->last_press_y = y;
561 left = priv->crop.x;
562 right = priv->crop.x + priv->crop.width - 1;
563 top = priv->crop.y;
564 bottom = priv->crop.y + priv->crop.height - 1;
566 center_x = (left + right) / 2.0;
567 center_y = (top + bottom) / 2.0;
569 switch (priv->active_region) {
570 case INSIDE:
571 width = right - left + 1;
572 height = bottom - top + 1;
574 left += delta_x;
575 right += delta_x;
576 top += delta_y;
577 bottom += delta_y;
579 if (left < 0)
580 left = 0;
581 if (top < 0)
582 top = 0;
583 if (right > pb_width)
584 right = pb_width;
585 if (bottom > pb_height)
586 bottom = pb_height;
588 adj_width = right - left + 1;
589 adj_height = bottom - top + 1;
590 if (adj_width != width) {
591 if (delta_x < 0)
592 right = left + width - 1;
593 else
594 left = right - width + 1;
596 if (adj_height != height) {
597 if (delta_y < 0)
598 bottom = top + height - 1;
599 else
600 top = bottom - height + 1;
603 break;
605 case TOP_LEFT:
606 if (priv->aspect < 0) {
607 top = y;
608 left = x;
610 else if (y < eval_radial_line (center_x, center_y, left, top, x)) {
611 top = y;
612 new_width = (bottom - top) * priv->aspect;
613 left = right - new_width;
615 else {
616 left = x;
617 new_height = (right - left) / priv->aspect;
618 top = bottom - new_height;
620 break;
622 case TOP:
623 top = y;
624 if (priv->aspect > 0) {
625 new_width = (bottom - top) * priv->aspect;
626 right = left + new_width;
628 break;
630 case TOP_RIGHT:
631 if (priv->aspect < 0) {
632 top = y;
633 right = x;
635 else if (y < eval_radial_line (center_x, center_y, right, top, x)) {
636 top = y;
637 new_width = (bottom - top) * priv->aspect;
638 right = left + new_width;
640 else {
641 right = x;
642 new_height = (right - left) / priv->aspect;
643 top = bottom - new_height;
645 break;
647 case LEFT:
648 left = x;
649 if (priv->aspect > 0) {
650 new_height = (right - left) / priv->aspect;
651 bottom = top + new_height;
653 break;
655 case BOTTOM_LEFT:
656 if (priv->aspect < 0) {
657 bottom = y;
658 left = x;
660 else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
661 left = x;
662 new_height = (right - left) / priv->aspect;
663 bottom = top + new_height;
665 else {
666 bottom = y;
667 new_width = (bottom - top) * priv->aspect;
668 left = right - new_width;
670 break;
672 case RIGHT:
673 right = x;
674 if (priv->aspect > 0) {
675 new_height = (right - left) / priv->aspect;
676 bottom = top + new_height;
678 break;
680 case BOTTOM_RIGHT:
681 if (priv->aspect < 0) {
682 bottom = y;
683 right = x;
685 else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
686 right = x;
687 new_height = (right - left) / priv->aspect;
688 bottom = top + new_height;
690 else {
691 bottom = y;
692 new_width = (bottom - top) * priv->aspect;
693 right = left + new_width;
695 break;
697 case BOTTOM:
698 bottom = y;
699 if (priv->aspect > 0) {
700 new_width = (bottom - top) * priv->aspect;
701 right= left + new_width;
703 break;
705 default:
706 return FALSE;
709 min_width = priv->base_width / priv->scale;
710 min_height = priv->base_height / priv->scale;
712 width = right - left + 1;
713 height = bottom - top + 1;
714 if (priv->aspect < 0) {
715 if (left < 0)
716 left = 0;
717 if (top < 0)
718 top = 0;
719 if (right > pb_width)
720 right = pb_width;
721 if (bottom > pb_height)
722 bottom = pb_height;
724 width = right - left + 1;
725 height = bottom - top + 1;
727 switch (priv->active_region) {
728 case LEFT:
729 case TOP_LEFT:
730 case BOTTOM_LEFT:
731 if (width < min_width)
732 left = right - min_width;
733 break;
734 case RIGHT:
735 case TOP_RIGHT:
736 case BOTTOM_RIGHT:
737 if (width < min_width)
738 right = left + min_width;
739 break;
741 default: ;
744 switch (priv->active_region) {
745 case TOP:
746 case TOP_LEFT:
747 case TOP_RIGHT:
748 if (height < min_height)
749 top = bottom - min_height;
750 break;
751 case BOTTOM:
752 case BOTTOM_LEFT:
753 case BOTTOM_RIGHT:
754 if (height < min_height)
755 bottom = top + min_height;
756 break;
758 default: ;
761 else {
762 if (left < 0 || top < 0 ||
763 right > pb_width || bottom > pb_height ||
764 width < min_width || height < min_height) {
765 left = priv->crop.x;
766 right = priv->crop.x + priv->crop.width - 1;
767 top = priv->crop.y;
768 bottom = priv->crop.y + priv->crop.height - 1;
772 priv->crop.x = left;
773 priv->crop.y = top;
774 priv->crop.width = right - left + 1;
775 priv->crop.height = bottom - top + 1;
777 crop_to_widget (area, &damage);
778 gtk_widget_queue_draw_area (widget,
779 damage.x - 1, damage.y - 1,
780 damage.width + 2, damage.height + 2);
782 return FALSE;
786 * um_crop_area_button_press_event:
787 * @widget: a #UmCropArea
788 * @event: a #GdkEventButton
790 * Handle the mouse button being pressed on the widget, by initiating a crop
791 * region selection and redrawing the cropped area.
793 * Returns: %FALSE
795 static gboolean
796 um_crop_area_button_press_event (GtkWidget *widget,
797 GdkEventButton *event)
799 UmCropArea *area = UM_CROP_AREA (widget);
800 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
801 GdkRectangle crop;
803 if (priv->browse_pixbuf == NULL)
804 return FALSE;
806 crop_to_widget (area, &crop);
808 priv->last_press_x = (event->x - priv->image.x) / priv->scale;
809 priv->last_press_y = (event->y - priv->image.y) / priv->scale;
810 priv->active_region = find_location (&crop, event->x, event->y);
812 gtk_widget_queue_draw_area (widget,
813 crop.x - 1, crop.y - 1,
814 crop.width + 2, crop.height + 2);
816 return FALSE;
820 * um_crop_area_button_release_event:
821 * @widget: a #UmCropArea
822 * @event: a #GdkEventButton
824 * Handle the mouse button being released on the widget, by redrawing the
825 * cropped region.
827 * Returns: %FALSE
829 static gboolean
830 um_crop_area_button_release_event (GtkWidget *widget,
831 GdkEventButton *event)
833 UmCropArea *area = UM_CROP_AREA (widget);
834 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
835 GdkRectangle crop;
837 if (priv->browse_pixbuf == NULL)
838 return FALSE;
840 crop_to_widget (area, &crop);
842 priv->last_press_x = -1;
843 priv->last_press_y = -1;
844 priv->active_region = OUTSIDE;
846 gtk_widget_queue_draw_area (widget,
847 crop.x - 1, crop.y - 1,
848 crop.width + 2, crop.height + 2);
850 return FALSE;
853 static void
854 um_crop_area_set_size_request (UmCropArea *area)
856 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
857 gtk_widget_set_size_request (GTK_WIDGET (area),
858 priv->base_width,
859 priv->base_height);
862 static void
863 um_crop_area_finalize (GObject *object)
865 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (UM_CROP_AREA (object));
867 if (priv->browse_pixbuf) {
868 g_object_unref (priv->browse_pixbuf);
869 priv->browse_pixbuf = NULL;
871 if (priv->pixbuf) {
872 g_object_unref (priv->pixbuf);
873 priv->pixbuf = NULL;
875 if (priv->color_shifted) {
876 g_object_unref (priv->color_shifted);
877 priv->color_shifted = NULL;
880 G_OBJECT_CLASS (um_crop_area_parent_class)->finalize (object);
883 static void
884 um_crop_area_class_init (UmCropAreaClass *klass)
886 GObjectClass *object_class = G_OBJECT_CLASS (klass);
887 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
889 object_class->finalize = um_crop_area_finalize;
890 widget_class->draw = um_crop_area_draw;
891 widget_class->button_press_event = um_crop_area_button_press_event;
892 widget_class->button_release_event = um_crop_area_button_release_event;
893 widget_class->motion_notify_event = um_crop_area_motion_notify_event;
896 static void
897 um_crop_area_init (UmCropArea *area)
899 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
901 gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK |
902 GDK_BUTTON_PRESS_MASK |
903 GDK_BUTTON_RELEASE_MASK);
905 priv->scale = 0.0;
906 priv->image.x = 0;
907 priv->image.y = 0;
908 priv->image.width = 0;
909 priv->image.height = 0;
910 priv->active_region = OUTSIDE;
911 priv->base_width = 48;
912 priv->base_height = 48;
913 priv->aspect = 1;
915 um_crop_area_set_size_request (area);
919 * um_crop_area_new:
921 * Creates a new #UmCropArea widget.
923 * Returns: a #UmCropArea
925 GtkWidget *
926 um_crop_area_new (void)
928 return g_object_new (UM_TYPE_CROP_AREA, NULL);
932 * um_crop_area_get_picture:
933 * @area: a #UmCropArea
935 * Returns the cropped image, or the whole image if no crop region has been
936 * set, as a #GdkPixbuf.
938 * Returns: a #GdkPixbuf
940 GdkPixbuf *
941 um_crop_area_get_picture (UmCropArea *area)
943 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
944 gint width, height;
946 if (priv->browse_pixbuf == NULL)
947 return NULL;
949 width = gdk_pixbuf_get_width (priv->browse_pixbuf);
950 height = gdk_pixbuf_get_height (priv->browse_pixbuf);
951 width = MIN (priv->crop.width, width - priv->crop.x);
952 height = MIN (priv->crop.height, height - priv->crop.y);
954 return gdk_pixbuf_new_subpixbuf (priv->browse_pixbuf,
955 priv->crop.x,
956 priv->crop.y,
957 width, height);
961 * um_crop_area_set_picture:
962 * @area: a #UmCropArea
963 * @pixbuf: (allow-none): the #GdkPixbuf to set, or %NULL to clear the current
964 * picture
966 * Set the image to be used inside the @area to @pixbuf.
968 void
969 um_crop_area_set_picture (UmCropArea *area,
970 GdkPixbuf *pixbuf)
972 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
973 gint width;
974 gint height;
976 if (priv->browse_pixbuf) {
977 g_object_unref (priv->browse_pixbuf);
978 priv->browse_pixbuf = NULL;
980 if (pixbuf) {
981 priv->browse_pixbuf = g_object_ref (pixbuf);
982 width = gdk_pixbuf_get_width (pixbuf);
983 height = gdk_pixbuf_get_height (pixbuf);
984 } else {
985 width = 0;
986 height = 0;
989 priv->crop.width = 2 * priv->base_width;
990 priv->crop.height = 2 * priv->base_height;
991 priv->crop.x = (width - priv->crop.width) / 2;
992 priv->crop.y = (height - priv->crop.height) / 2;
994 priv->scale = 0.0;
995 priv->image.x = 0;
996 priv->image.y = 0;
997 priv->image.width = 0;
998 priv->image.height = 0;
1000 gtk_widget_queue_draw (GTK_WIDGET (area));
1004 * um_crop_area_set_min_size:
1005 * @area: a #UmCropArea
1006 * @width: a minimum width, in pixels
1007 * @height: a minimum height, in pixels
1009 * Sets the minimum size that the cropped image will be allowed to have.
1011 void
1012 um_crop_area_set_min_size (UmCropArea *area,
1013 gint width,
1014 gint height)
1016 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
1017 priv->base_width = width;
1018 priv->base_height = height;
1020 um_crop_area_set_size_request (area);
1022 if (priv->aspect > 0) {
1023 priv->aspect = priv->base_width / (gdouble)priv->base_height;
1028 * um_crop_area_set_constrain_aspect:
1029 * @area: a #UmCropArea
1030 * @constrain: whether to constrain the aspect ratio of the cropped image
1032 * Controls whether the aspect ratio of the cropped area of the image should be
1033 * constrained.
1035 void
1036 um_crop_area_set_constrain_aspect (UmCropArea *area,
1037 gboolean constrain)
1039 UmCropAreaPrivate *priv = um_crop_area_get_instance_private (area);
1041 if (constrain) {
1042 priv->aspect = priv->base_width / (gdouble)priv->base_height;
1044 else {
1045 priv->aspect = -1;