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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
* Written by: Matthias Clasen <mclasen@redhat.com>
25
#include <glib/gi18n.h>
28
#include "um-crop-area.h"
30
struct _UmCropAreaPrivate {
31
GdkPixbuf *browse_pixbuf;
33
GdkPixbuf *color_shifted;
36
GdkCursorType current_cursor;
43
G_DEFINE_TYPE (UmCropArea, um_crop_area, GTK_TYPE_DRAWING_AREA);
46
shift_color_byte (guchar b,
49
return CLAMP(b + shift, 0, 255);
53
shift_colors (GdkPixbuf *pixbuf,
59
gint x, y, offset, y_offset, rowstride, width, height;
63
width = gdk_pixbuf_get_width (pixbuf);
64
height = gdk_pixbuf_get_height (pixbuf);
65
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
66
pixels = gdk_pixbuf_get_pixels (pixbuf);
67
channels = gdk_pixbuf_get_n_channels (pixbuf);
69
for (y = 0; y < height; y++) {
70
y_offset = y * rowstride;
71
for (x = 0; x < width; x++) {
72
offset = y_offset + x * channels;
74
pixels[offset] = shift_color_byte (pixels[offset], red);
76
pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
78
pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
79
if (alpha != 0 && channels >= 4)
80
pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
86
update_pixbufs (UmCropArea *area)
88
GtkAllocation allocation;
95
gint dest_x, dest_y, dest_width, dest_height;
98
widget = GTK_WIDGET (area);
99
gtk_widget_get_allocation (widget, &allocation);
101
if (area->priv->pixbuf == NULL ||
102
gdk_pixbuf_get_width (area->priv->pixbuf) != allocation.width ||
103
gdk_pixbuf_get_height (area->priv->pixbuf) != allocation.height) {
104
if (area->priv->pixbuf != NULL)
105
g_object_unref (area->priv->pixbuf);
106
area->priv->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
107
allocation.width, allocation.height);
109
style = gtk_widget_get_style (widget);
110
color = &style->bg[gtk_widget_get_state (widget)];
111
pixel = ((color->red & 0xff00) << 16) |
112
((color->green & 0xff00) << 8) |
113
(color->blue & 0xff00);
114
gdk_pixbuf_fill (area->priv->pixbuf, pixel);
116
width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
117
height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
119
scale = allocation.height / (gdouble)height;
120
if (scale * width > allocation.width)
121
scale = allocation.width / (gdouble)width;
123
dest_width = width * scale;
124
dest_height = height * scale;
125
dest_x = (allocation.width - dest_width) / 2;
126
dest_y = (allocation.height - dest_height) / 2,
128
gdk_pixbuf_scale (area->priv->browse_pixbuf,
131
dest_width, dest_height,
134
GDK_INTERP_BILINEAR);
136
if (area->priv->color_shifted)
137
g_object_unref (area->priv->color_shifted);
138
area->priv->color_shifted = gdk_pixbuf_copy (area->priv->pixbuf);
139
shift_colors (area->priv->color_shifted, -32, -32, -32, 0);
141
if (area->priv->scale == 0.0) {
142
area->priv->crop.width = 96.0 / scale;
143
area->priv->crop.height = 96.0 / scale;
144
area->priv->crop.x = (gdk_pixbuf_get_width (area->priv->browse_pixbuf) - area->priv->crop.width) / 2;
145
area->priv->crop.y = (gdk_pixbuf_get_height (area->priv->browse_pixbuf) - area->priv->crop.height) / 2;
148
area->priv->scale = scale;
149
area->priv->image.x = dest_x;
150
area->priv->image.y = dest_y;
151
area->priv->image.width = dest_width;
152
area->priv->image.height = dest_height;
157
crop_to_widget (UmCropArea *area,
160
crop->x = area->priv->image.x + area->priv->crop.x * area->priv->scale;
161
crop->y = area->priv->image.y + area->priv->crop.y * area->priv->scale;
162
crop->width = area->priv->crop.width * area->priv->scale;
163
crop->height = area->priv->crop.height * area->priv->scale;
180
um_crop_area_expose (GtkWidget *widget,
181
GdkEventExpose *event)
190
UmCropArea *uarea = UM_CROP_AREA (widget);
192
if (uarea->priv->browse_pixbuf == NULL)
195
update_pixbufs (uarea);
197
window = gtk_widget_get_window (widget);
198
style = gtk_widget_get_style (widget);
199
state = gtk_widget_get_state (widget);
201
width = gdk_pixbuf_get_width (uarea->priv->pixbuf);
202
height = gdk_pixbuf_get_height (uarea->priv->pixbuf);
203
crop_to_widget (uarea, &crop);
208
area.height = crop.y;
209
gdk_rectangle_intersect (&area, &event->area, &area);
210
gdk_draw_pixbuf (window,
212
uarea->priv->color_shifted,
215
area.width, area.height,
216
GDK_RGB_DITHER_NONE, 0, 0);
221
area.height = crop.height;
222
gdk_rectangle_intersect (&area, &event->area, &area);
223
gdk_draw_pixbuf (window,
225
uarea->priv->color_shifted,
228
area.width, area.height,
229
GDK_RGB_DITHER_NONE, 0, 0);
233
area.width = crop.width;
234
area.height = crop.height;
235
gdk_rectangle_intersect (&area, &event->area, &area);
236
gdk_draw_pixbuf (window,
241
area.width, area.height,
242
GDK_RGB_DITHER_NONE, 0, 0);
244
area.x = crop.x + crop.width;
246
area.width = width - area.x;
247
area.height = crop.height;
248
gdk_rectangle_intersect (&area, &event->area, &area);
249
gdk_draw_pixbuf (window,
251
uarea->priv->color_shifted,
254
area.width, area.height,
255
GDK_RGB_DITHER_NONE, 0, 0);
258
area.y = crop.y + crop.width;
260
area.height = height - area.y;
261
gdk_rectangle_intersect (&area, &event->area, &area);
262
gdk_draw_pixbuf (window,
264
uarea->priv->color_shifted,
267
area.width, area.height,
268
GDK_RGB_DITHER_NONE, 0, 0);
270
cr = gdk_cairo_create (window);
271
gdk_cairo_rectangle (cr, &event->area);
274
if (uarea->priv->active_region != OUTSIDE) {
276
gdk_cairo_set_source_color (cr, &style->white);
277
cairo_set_line_width (cr, 1.0);
278
x1 = crop.x + crop.width / 3.0;
279
x2 = crop.x + 2 * crop.width / 3.0;
280
y1 = crop.y + crop.height / 3.0;
281
y2 = crop.y + 2 * crop.height / 3.0;
283
cairo_move_to (cr, x1 + 0.5, crop.y);
284
cairo_line_to (cr, x1 + 0.5, crop.y + crop.height);
286
cairo_move_to (cr, x2 + 0.5, crop.y);
287
cairo_line_to (cr, x2 + 0.5, crop.y + crop.height);
289
cairo_move_to (cr, crop.x, y1 + 0.5);
290
cairo_line_to (cr, crop.x + crop.width, y1 + 0.5);
292
cairo_move_to (cr, crop.x, y2 + 0.5);
293
cairo_line_to (cr, crop.x + crop.width, y2 + 0.5);
297
gdk_cairo_set_source_color (cr, &style->black);
298
cairo_set_line_width (cr, 1.0);
306
gdk_cairo_set_source_color (cr, &style->white);
307
cairo_set_line_width (cr, 2.0);
335
if (x < min - tolerance)
337
if (x <= min + tolerance)
339
if (x < max - tolerance)
341
if (x <= max + tolerance)
347
find_location (GdkRectangle *rect,
351
Range x_range, y_range;
352
Location location[5][5] = {
353
{ OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE },
354
{ OUTSIDE, TOP_LEFT, TOP, TOP_RIGHT, OUTSIDE },
355
{ OUTSIDE, LEFT, INSIDE, RIGHT, OUTSIDE },
356
{ OUTSIDE, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, OUTSIDE },
357
{ OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE }
360
x_range = find_range (x, rect->x, rect->x + rect->width);
361
y_range = find_range (y, rect->y, rect->y + rect->height);
363
return location[y_range][x_range];
367
update_cursor (UmCropArea *area,
374
crop_to_widget (area, &crop);
376
switch (find_location (&crop, x, y)) {
378
cursor_type = GDK_LEFT_PTR;
381
cursor_type = GDK_TOP_LEFT_CORNER;
384
cursor_type = GDK_TOP_SIDE;
387
cursor_type = GDK_TOP_RIGHT_CORNER;
390
cursor_type = GDK_LEFT_SIDE;
393
cursor_type = GDK_FLEUR;
396
cursor_type = GDK_RIGHT_SIDE;
399
cursor_type = GDK_BOTTOM_LEFT_CORNER;
402
cursor_type = GDK_BOTTOM_SIDE;
405
cursor_type = GDK_BOTTOM_RIGHT_CORNER;
409
if (cursor_type != area->priv->current_cursor) {
410
GdkCursor *cursor = gdk_cursor_new (cursor_type);
411
gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor);
412
gdk_cursor_unref (cursor);
413
area->priv->current_cursor = cursor_type;
418
um_crop_area_motion_notify_event (GtkWidget *widget,
419
GdkEventMotion *event)
423
gint delta_x, delta_y;
424
gint width, height, d;
425
UmCropArea *area = UM_CROP_AREA (widget);
427
width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
428
height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
430
x = (event->x - area->priv->image.x) / area->priv->scale;
431
y = (event->y - area->priv->image.y) / area->priv->scale;
432
x = CLAMP (x, 0, width);
433
y = CLAMP (y, 0, height);
435
delta_x = x - area->priv->last_press_x;
436
delta_y = y - area->priv->last_press_y;
437
area->priv->last_press_x = x;
438
area->priv->last_press_y = y;
440
x2 = area->priv->crop.x + area->priv->crop.width;
441
y2 = area->priv->crop.y + area->priv->crop.height;
443
switch (area->priv->active_region) {
445
area->priv->crop.x = CLAMP (area->priv->crop.x + delta_x, 0, width - area->priv->crop.width);
446
area->priv->crop.y = CLAMP (area->priv->crop.y + delta_y, 0, height - area->priv->crop.height);
450
d = MAX (x2 - x, y2 - y);
451
if (d < 48 / area->priv->scale)
452
d = 48 / area->priv->scale;
457
area->priv->crop.x = x2 - d;
458
area->priv->crop.y = y2 - d;
459
area->priv->crop.width = area->priv->crop.height = d;
464
d = MAX (y2 - y, x - area->priv->crop.x);
465
if (d < 48 / area->priv->scale)
466
d = 48 / area->priv->scale;
467
if (area->priv->crop.x + d > width)
468
d = width - area->priv->crop.x;
471
area->priv->crop.y = y2 - d;
472
area->priv->crop.width = area->priv->crop.height = d;
477
d = MAX (x2 - x, y - area->priv->crop.y);
478
if (d < 48 / area->priv->scale)
479
d = 48 / area->priv->scale;
480
if (area->priv->crop.y + d > height)
481
d = height - area->priv->crop.y;
484
area->priv->crop.x = x2 - d;
485
area->priv->crop.width = area->priv->crop.height = d;
491
area->priv->crop.width = MAX (x - area->priv->crop.x, y - area->priv->crop.y);
492
if (area->priv->crop.width < 48 / area->priv->scale)
493
area->priv->crop.width = 48 / area->priv->scale;
494
if (area->priv->crop.x + area->priv->crop.width > width)
495
area->priv->crop.width = width - area->priv->crop.x;
496
area->priv->crop.height = area->priv->crop.width;
497
if (area->priv->crop.y + area->priv->crop.height > height)
498
area->priv->crop.height = height - area->priv->crop.y;
499
area->priv->crop.width = area->priv->crop.height;
507
update_cursor (area, event->x, event->y);
508
gtk_widget_queue_draw (widget);
514
um_crop_area_button_press_event (GtkWidget *widget,
515
GdkEventButton *event)
518
UmCropArea *area = UM_CROP_AREA (widget);
520
crop_to_widget (area, &crop);
522
area->priv->last_press_x = (event->x - area->priv->image.x) / area->priv->scale;
523
area->priv->last_press_y = (event->y - area->priv->image.y) / area->priv->scale;
524
area->priv->active_region = find_location (&crop, event->x, event->y);
526
gtk_widget_queue_draw (widget);
532
um_crop_area_button_release_event (GtkWidget *widget,
533
GdkEventButton *event)
535
UmCropArea *area = UM_CROP_AREA (widget);
537
area->priv->last_press_x = -1;
538
area->priv->last_press_y = -1;
539
area->priv->active_region = OUTSIDE;
541
gtk_widget_queue_draw (widget);
547
um_crop_area_finalize (GObject *object)
549
UmCropArea *area = UM_CROP_AREA (object);
551
if (area->priv->browse_pixbuf) {
552
g_object_unref (area->priv->browse_pixbuf);
553
area->priv->browse_pixbuf = NULL;
555
if (area->priv->pixbuf) {
556
g_object_unref (area->priv->pixbuf);
557
area->priv->pixbuf = NULL;
559
if (area->priv->color_shifted) {
560
g_object_unref (area->priv->color_shifted);
561
area->priv->color_shifted = NULL;
566
um_crop_area_class_init (UmCropAreaClass *klass)
568
GObjectClass *object_class = G_OBJECT_CLASS (klass);
569
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
571
object_class->finalize = um_crop_area_finalize;
572
widget_class->expose_event = um_crop_area_expose;
573
widget_class->button_press_event = um_crop_area_button_press_event;
574
widget_class->button_release_event = um_crop_area_button_release_event;
575
widget_class->motion_notify_event = um_crop_area_motion_notify_event;
577
g_type_class_add_private (klass, sizeof (UmCropAreaPrivate));
581
um_crop_area_init (UmCropArea *area)
583
area->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((area), UM_TYPE_CROP_AREA,
586
gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK |
587
GDK_BUTTON_PRESS_MASK |
588
GDK_BUTTON_RELEASE_MASK);
590
area->priv->scale = 0.0;
591
area->priv->image.x = 0;
592
area->priv->image.y = 0;
593
area->priv->image.width = 0;
594
area->priv->image.height = 0;
595
area->priv->active_region = OUTSIDE;
599
um_crop_area_new (void)
601
return g_object_new (UM_TYPE_CROP_AREA, NULL);
605
um_crop_area_get_picture (UmCropArea *area)
607
return gdk_pixbuf_new_subpixbuf (area->priv->browse_pixbuf,
608
area->priv->crop.x, area->priv->crop.y,
609
area->priv->crop.width, area->priv->crop.height);
613
um_crop_area_set_picture (UmCropArea *area,
619
if (area->priv->browse_pixbuf) {
620
g_object_unref (area->priv->browse_pixbuf);
621
area->priv->browse_pixbuf = NULL;
624
area->priv->browse_pixbuf = g_object_ref (pixbuf);
625
width = gdk_pixbuf_get_width (pixbuf);
626
height = gdk_pixbuf_get_height (pixbuf);
633
gtk_widget_get_allocation (um->browse_drawing_area, &allocation);
634
um->priv->crop.width = 96;
635
um->priv->crop.height = 96;
636
um->priv->crop.x = (allocation.width - um->priv->crop.width) / 2;
637
um->priv->crop.y = (allocation.height - um->priv->crop.height) / 2;
639
area->priv->crop.width = 96;
640
area->priv->crop.height = 96;
641
area->priv->crop.x = (width - area->priv->crop.width) / 2;
642
area->priv->crop.y = (height - area->priv->crop.height) / 2;
644
area->priv->scale = 0.0;
645
area->priv->image.x = 0;
646
area->priv->image.y = 0;
647
area->priv->image.width = 0;
648
area->priv->image.height = 0;
650
gtk_widget_queue_draw (GTK_WIDGET (area));