Line |
Branch |
Exec |
Source |
1 |
|
|
/* |
2 |
|
|
* Copyright 2021 Red Hat, Inc, |
3 |
|
|
* |
4 |
|
|
* Authors: |
5 |
|
|
* - Matthias Clasen <mclasen@redhat.com> |
6 |
|
|
* - Niels De Graef <nielsdg@redhat.com> |
7 |
|
|
* |
8 |
|
|
* This program is free software; you can redistribute it and/or modify |
9 |
|
|
* it under the terms of the GNU General Public License as published by |
10 |
|
|
* the Free Software Foundation; either version 2 of the License, or |
11 |
|
|
* (at your option) any later version. |
12 |
|
|
* |
13 |
|
|
* This program is distributed in the hope that it will be useful, |
14 |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 |
|
|
* GNU General Public License for more details. |
17 |
|
|
* |
18 |
|
|
* You should have received a copy of the GNU General Public License |
19 |
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>. |
20 |
|
|
*/ |
21 |
|
|
|
22 |
|
|
#include "config.h" |
23 |
|
|
|
24 |
|
|
#include <glib.h> |
25 |
|
|
#include <glib/gi18n.h> |
26 |
|
|
#include <gtk/gtk.h> |
27 |
|
|
#include <gsk/gl/gskglrenderer.h> |
28 |
|
|
|
29 |
|
|
#include "cc-crop-area.h" |
30 |
|
|
|
31 |
|
|
/** |
32 |
|
|
* CcCropArea: |
33 |
|
|
* |
34 |
|
|
* A widget that shows a [iface@Gdk.Paintable] and allows the user specify a |
35 |
|
|
* cropping rectangle to effectively crop to that given area. |
36 |
|
|
*/ |
37 |
|
|
|
38 |
|
|
/* Location of the cursor relative to the cropping rectangle/circle */ |
39 |
|
|
typedef enum { |
40 |
|
|
OUTSIDE, |
41 |
|
|
INSIDE, |
42 |
|
|
TOP, |
43 |
|
|
TOP_LEFT, |
44 |
|
|
TOP_RIGHT, |
45 |
|
|
BOTTOM, |
46 |
|
|
BOTTOM_LEFT, |
47 |
|
|
BOTTOM_RIGHT, |
48 |
|
|
LEFT, |
49 |
|
|
RIGHT |
50 |
|
|
} Location; |
51 |
|
|
|
52 |
|
|
struct _CcCropArea { |
53 |
|
|
GtkWidget parent_instance; |
54 |
|
|
|
55 |
|
|
GdkPaintable *paintable; |
56 |
|
|
|
57 |
|
|
double scale; /* scale factor to go from paintable size to widget size */ |
58 |
|
|
|
59 |
|
|
const char *current_cursor; |
60 |
|
|
Location active_region; |
61 |
|
|
double drag_offx; |
62 |
|
|
double drag_offy; |
63 |
|
|
|
64 |
|
|
/* In source coordinates. See get_scaled_crop() for widget coordinates */ |
65 |
|
|
GdkRectangle crop; |
66 |
|
|
|
67 |
|
|
/* In widget coordinates */ |
68 |
|
|
GdkRectangle image; |
69 |
|
|
int min_crop_width; |
70 |
|
|
int min_crop_height; |
71 |
|
|
}; |
72 |
|
|
|
73 |
|
✗ |
G_DEFINE_TYPE (CcCropArea, cc_crop_area, GTK_TYPE_WIDGET); |
74 |
|
|
|
75 |
|
|
static void |
76 |
|
✗ |
update_image_and_crop (CcCropArea *area) |
77 |
|
|
{ |
78 |
|
|
GtkAllocation allocation; |
79 |
|
|
int width, height; |
80 |
|
|
int dest_width, dest_height; |
81 |
|
|
double scale; |
82 |
|
|
|
83 |
|
✗ |
if (area->paintable == NULL) |
84 |
|
✗ |
return; |
85 |
|
|
|
86 |
|
✗ |
gtk_widget_get_allocation (GTK_WIDGET (area), &allocation); |
87 |
|
|
|
88 |
|
|
/* Get the size of the paintable */ |
89 |
|
✗ |
width = gdk_paintable_get_intrinsic_width (area->paintable); |
90 |
|
✗ |
height = gdk_paintable_get_intrinsic_height (area->paintable); |
91 |
|
|
|
92 |
|
|
/* Find out the scale to convert to widget width/height */ |
93 |
|
✗ |
scale = allocation.height / (double) height; |
94 |
|
✗ |
if (scale * width > allocation.width) |
95 |
|
✗ |
scale = allocation.width / (double) width; |
96 |
|
|
|
97 |
|
✗ |
dest_width = width * scale; |
98 |
|
✗ |
dest_height = height * scale; |
99 |
|
|
|
100 |
|
✗ |
if (area->scale == 0.0) { |
101 |
|
|
double scale_to_80, scale_to_image, crop_scale; |
102 |
|
|
|
103 |
|
|
/* Start with a crop area of 80% of the area, unless it's larger than min_size */ |
104 |
|
✗ |
scale_to_80 = MIN ((double) dest_width * 0.8, (double) dest_height * 0.8); |
105 |
|
✗ |
scale_to_image = MIN ((double) area->min_crop_width, (double) area->min_crop_height); |
106 |
|
✗ |
crop_scale = MAX (scale_to_80, scale_to_image); |
107 |
|
|
|
108 |
|
|
/* Divide by `scale` to get back to paintable coordinates */ |
109 |
|
✗ |
area->crop.width = crop_scale / scale; |
110 |
|
✗ |
area->crop.height = crop_scale / scale; |
111 |
|
✗ |
area->crop.x = (width - area->crop.width) / 2; |
112 |
|
✗ |
area->crop.y = (height - area->crop.height) / 2; |
113 |
|
|
} |
114 |
|
|
|
115 |
|
✗ |
area->scale = scale; |
116 |
|
✗ |
area->image.x = (allocation.width - dest_width) / 2; |
117 |
|
✗ |
area->image.y = (allocation.height - dest_height) / 2; |
118 |
|
✗ |
area->image.width = dest_width; |
119 |
|
✗ |
area->image.height = dest_height; |
120 |
|
|
} |
121 |
|
|
|
122 |
|
|
/* Returns area->crop in widget coordinates (vs paintable coordsinates) */ |
123 |
|
|
static void |
124 |
|
✗ |
get_scaled_crop (CcCropArea *area, |
125 |
|
|
GdkRectangle *crop) |
126 |
|
|
{ |
127 |
|
✗ |
crop->x = area->image.x + area->crop.x * area->scale; |
128 |
|
✗ |
crop->y = area->image.y + area->crop.y * area->scale; |
129 |
|
✗ |
crop->width = area->image.x + (area->crop.x + area->crop.width) * area->scale - crop->x; |
130 |
|
✗ |
crop->height = area->image.y + (area->crop.y + area->crop.height) * area->scale - crop->y; |
131 |
|
✗ |
} |
132 |
|
|
|
133 |
|
|
typedef enum { |
134 |
|
|
BELOW, |
135 |
|
|
LOWER, |
136 |
|
|
BETWEEN, |
137 |
|
|
UPPER, |
138 |
|
|
ABOVE |
139 |
|
|
} Range; |
140 |
|
|
|
141 |
|
|
static Range |
142 |
|
✗ |
find_range (int x, |
143 |
|
|
int min, |
144 |
|
|
int max) |
145 |
|
|
{ |
146 |
|
✗ |
int tolerance = 12; |
147 |
|
|
|
148 |
|
✗ |
if (x < min - tolerance) |
149 |
|
✗ |
return BELOW; |
150 |
|
✗ |
if (x <= min + tolerance) |
151 |
|
✗ |
return LOWER; |
152 |
|
✗ |
if (x < max - tolerance) |
153 |
|
✗ |
return BETWEEN; |
154 |
|
✗ |
if (x <= max + tolerance) |
155 |
|
✗ |
return UPPER; |
156 |
|
✗ |
return ABOVE; |
157 |
|
|
} |
158 |
|
|
|
159 |
|
|
/* Finds the location of (@x, @y) relative to the crop @rect */ |
160 |
|
|
static Location |
161 |
|
✗ |
find_location (GdkRectangle *rect, |
162 |
|
|
int x, |
163 |
|
|
int y) |
164 |
|
|
{ |
165 |
|
|
Range x_range, y_range; |
166 |
|
✗ |
Location location[5][5] = { |
167 |
|
|
{ OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE }, |
168 |
|
|
{ OUTSIDE, TOP_LEFT, TOP, TOP_RIGHT, OUTSIDE }, |
169 |
|
|
{ OUTSIDE, LEFT, INSIDE, RIGHT, OUTSIDE }, |
170 |
|
|
{ OUTSIDE, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, OUTSIDE }, |
171 |
|
|
{ OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE } |
172 |
|
|
}; |
173 |
|
|
|
174 |
|
✗ |
x_range = find_range (x, rect->x, rect->x + rect->width); |
175 |
|
✗ |
y_range = find_range (y, rect->y, rect->y + rect->height); |
176 |
|
|
|
177 |
|
✗ |
return location[y_range][x_range]; |
178 |
|
|
} |
179 |
|
|
|
180 |
|
|
static void |
181 |
|
✗ |
update_cursor (CcCropArea *area, |
182 |
|
|
int x, |
183 |
|
|
int y) |
184 |
|
|
{ |
185 |
|
|
const char *cursor_type; |
186 |
|
|
GdkRectangle crop; |
187 |
|
|
int region; |
188 |
|
|
|
189 |
|
✗ |
region = area->active_region; |
190 |
|
✗ |
if (region == OUTSIDE) { |
191 |
|
✗ |
get_scaled_crop (area, &crop); |
192 |
|
✗ |
region = find_location (&crop, x, y); |
193 |
|
|
} |
194 |
|
|
|
195 |
|
✗ |
switch (region) { |
196 |
|
✗ |
case OUTSIDE: |
197 |
|
✗ |
cursor_type = "default"; |
198 |
|
✗ |
break; |
199 |
|
✗ |
case TOP_LEFT: |
200 |
|
✗ |
cursor_type = "nw-resize"; |
201 |
|
✗ |
break; |
202 |
|
✗ |
case TOP: |
203 |
|
✗ |
cursor_type = "n-resize"; |
204 |
|
✗ |
break; |
205 |
|
✗ |
case TOP_RIGHT: |
206 |
|
✗ |
cursor_type = "ne-resize"; |
207 |
|
✗ |
break; |
208 |
|
✗ |
case LEFT: |
209 |
|
✗ |
cursor_type = "w-resize"; |
210 |
|
✗ |
break; |
211 |
|
✗ |
case INSIDE: |
212 |
|
✗ |
cursor_type = "move"; |
213 |
|
✗ |
break; |
214 |
|
✗ |
case RIGHT: |
215 |
|
✗ |
cursor_type = "e-resize"; |
216 |
|
✗ |
break; |
217 |
|
✗ |
case BOTTOM_LEFT: |
218 |
|
✗ |
cursor_type = "sw-resize"; |
219 |
|
✗ |
break; |
220 |
|
✗ |
case BOTTOM: |
221 |
|
✗ |
cursor_type = "s-resize"; |
222 |
|
✗ |
break; |
223 |
|
✗ |
case BOTTOM_RIGHT: |
224 |
|
✗ |
cursor_type = "se-resize"; |
225 |
|
✗ |
break; |
226 |
|
✗ |
default: |
227 |
|
✗ |
g_assert_not_reached (); |
228 |
|
|
} |
229 |
|
|
|
230 |
|
✗ |
if (cursor_type != area->current_cursor) { |
231 |
|
|
GtkNative *native; |
232 |
|
✗ |
g_autoptr (GdkCursor) cursor = NULL; |
233 |
|
|
|
234 |
|
✗ |
native = gtk_widget_get_native (GTK_WIDGET (area)); |
235 |
|
✗ |
if (!native) { |
236 |
|
✗ |
g_warning ("Can't adjust cursor: no GtkNative found"); |
237 |
|
✗ |
return; |
238 |
|
|
} |
239 |
|
✗ |
cursor = gdk_cursor_new_from_name (cursor_type, NULL); |
240 |
|
✗ |
gdk_surface_set_cursor (gtk_native_get_surface (native), cursor); |
241 |
|
✗ |
area->current_cursor = cursor_type; |
242 |
|
|
} |
243 |
|
|
} |
244 |
|
|
|
245 |
|
|
static gboolean |
246 |
|
✗ |
on_motion (CcCropArea *area, |
247 |
|
|
double event_x, |
248 |
|
|
double event_y) |
249 |
|
|
{ |
250 |
|
✗ |
if (area->paintable == NULL) |
251 |
|
✗ |
return FALSE; |
252 |
|
|
|
253 |
|
✗ |
update_cursor (area, event_x, event_y); |
254 |
|
|
|
255 |
|
✗ |
return FALSE; |
256 |
|
|
} |
257 |
|
|
|
258 |
|
|
static void |
259 |
|
✗ |
on_leave (CcCropArea *area) |
260 |
|
|
{ |
261 |
|
✗ |
if (area->paintable == NULL) |
262 |
|
✗ |
return; |
263 |
|
|
|
264 |
|
|
/* Restore 'default' cursor */ |
265 |
|
✗ |
update_cursor (area, 0, 0); |
266 |
|
|
} |
267 |
|
|
|
268 |
|
|
static void |
269 |
|
✗ |
on_drag_begin (CcCropArea *area, |
270 |
|
|
double start_x, |
271 |
|
|
double start_y) |
272 |
|
|
{ |
273 |
|
|
GdkRectangle crop; |
274 |
|
|
|
275 |
|
✗ |
if (area->paintable == NULL) |
276 |
|
✗ |
return; |
277 |
|
|
|
278 |
|
✗ |
update_cursor (area, start_x, start_y); |
279 |
|
|
|
280 |
|
✗ |
get_scaled_crop (area, &crop); |
281 |
|
|
|
282 |
|
✗ |
area->active_region = find_location (&crop, start_x, start_y); |
283 |
|
|
|
284 |
|
✗ |
area->drag_offx = 0.0; |
285 |
|
✗ |
area->drag_offy = 0.0; |
286 |
|
|
} |
287 |
|
|
|
288 |
|
|
static void |
289 |
|
✗ |
on_drag_update (CcCropArea *area, |
290 |
|
|
double offset_x, |
291 |
|
|
double offset_y, |
292 |
|
|
GtkGestureDrag *gesture) |
293 |
|
|
{ |
294 |
|
|
double start_x, start_y; |
295 |
|
|
int x, y, delta_x, delta_y; |
296 |
|
|
int clamped_delta_x, clamped_delta_y; |
297 |
|
|
int left, right, top, bottom; |
298 |
|
|
int center_x, center_y; |
299 |
|
|
int distance_left, distance_right, distance_top, distance_bottom; |
300 |
|
|
int closest_distance_x, closest_distance_y; |
301 |
|
|
int size_x, size_y; |
302 |
|
|
int min_size, max_size, wanted_size, new_size; |
303 |
|
|
|
304 |
|
✗ |
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y); |
305 |
|
|
|
306 |
|
|
/* Get the x, y, dx, dy in paintable coords */ |
307 |
|
✗ |
x = (start_x + offset_x - area->image.x) / area->scale; |
308 |
|
✗ |
y = (start_y + offset_y - area->image.y) / area->scale; |
309 |
|
✗ |
delta_x = (offset_x - area->drag_offx) / area->scale; |
310 |
|
✗ |
delta_y = (offset_y - area->drag_offy) / area->scale; |
311 |
|
|
|
312 |
|
|
/* Helper variables */ |
313 |
|
✗ |
left = area->crop.x; |
314 |
|
✗ |
right = area->crop.x + area->crop.width - 1; |
315 |
|
✗ |
top = area->crop.y; |
316 |
|
✗ |
bottom = area->crop.y + area->crop.height - 1; |
317 |
|
|
|
318 |
|
✗ |
center_x = (left + right) / 2; |
319 |
|
✗ |
center_y = (top + bottom) / 2; |
320 |
|
|
|
321 |
|
✗ |
distance_left = left; |
322 |
|
✗ |
distance_right = gdk_paintable_get_intrinsic_width (area->paintable) - (right + 1); |
323 |
|
✗ |
distance_top = top; |
324 |
|
✗ |
distance_bottom = gdk_paintable_get_intrinsic_height (area->paintable) - (bottom + 1); |
325 |
|
|
|
326 |
|
✗ |
closest_distance_x = MIN (distance_left, distance_right); |
327 |
|
✗ |
closest_distance_y = MIN (distance_top, distance_bottom); |
328 |
|
|
|
329 |
|
|
/* All size variables are center-to-center, not edge-to-edge, hence the missing '+ 1' everywhere */ |
330 |
|
✗ |
size_x = right - left; |
331 |
|
✗ |
size_y = bottom - top; |
332 |
|
|
|
333 |
|
✗ |
min_size = MAX (area->min_crop_width / area->scale, area->min_crop_height / area->scale); |
334 |
|
|
|
335 |
|
|
/* What we have to do depends on where the user started dragging */ |
336 |
|
✗ |
switch (area->active_region) { |
337 |
|
✗ |
case INSIDE: |
338 |
|
✗ |
if (delta_x < 0) |
339 |
|
✗ |
clamped_delta_x = MAX (delta_x, -distance_left); |
340 |
|
|
else |
341 |
|
✗ |
clamped_delta_x = MIN (delta_x, distance_right); |
342 |
|
|
|
343 |
|
✗ |
if (delta_y < 0) |
344 |
|
✗ |
clamped_delta_y = MAX (delta_y, -distance_top); |
345 |
|
|
else |
346 |
|
✗ |
clamped_delta_y = MIN (delta_y, distance_bottom); |
347 |
|
|
|
348 |
|
✗ |
left += clamped_delta_x; |
349 |
|
✗ |
right += clamped_delta_x; |
350 |
|
✗ |
top += clamped_delta_y; |
351 |
|
✗ |
bottom += clamped_delta_y; |
352 |
|
|
|
353 |
|
✗ |
break; |
354 |
|
|
|
355 |
|
|
/* The wanted size assumes one side remains glued to the cursor */ |
356 |
|
✗ |
case TOP_LEFT: |
357 |
|
✗ |
max_size = MIN (size_y + distance_top, size_x + distance_left); |
358 |
|
✗ |
wanted_size = MAX (bottom - y, right - x); |
359 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
360 |
|
✗ |
top = bottom - new_size; |
361 |
|
✗ |
left = right - new_size; |
362 |
|
✗ |
break; |
363 |
|
|
|
364 |
|
✗ |
case TOP: |
365 |
|
✗ |
max_size = MIN (size_y + distance_top, size_x + 2 * closest_distance_x); |
366 |
|
✗ |
wanted_size = bottom - y; |
367 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
368 |
|
✗ |
top = bottom - new_size; |
369 |
|
✗ |
left = center_x - new_size / 2; |
370 |
|
✗ |
right = left + new_size; |
371 |
|
✗ |
break; |
372 |
|
|
|
373 |
|
✗ |
case TOP_RIGHT: |
374 |
|
✗ |
max_size = MIN (size_y + distance_top, size_x + distance_right); |
375 |
|
✗ |
wanted_size = MAX (bottom - y, x - left); |
376 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
377 |
|
✗ |
top = bottom - new_size; |
378 |
|
✗ |
right = left + new_size; |
379 |
|
✗ |
break; |
380 |
|
|
|
381 |
|
✗ |
case LEFT: |
382 |
|
✗ |
max_size = MIN (size_x + distance_left, size_y + 2 * closest_distance_y); |
383 |
|
✗ |
wanted_size = right - x; |
384 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
385 |
|
✗ |
left = right - new_size; |
386 |
|
✗ |
top = center_y - new_size / 2; |
387 |
|
✗ |
bottom = top + new_size; |
388 |
|
✗ |
break; |
389 |
|
|
|
390 |
|
✗ |
case BOTTOM_LEFT: |
391 |
|
✗ |
max_size = MIN (size_y + distance_bottom, size_x + distance_left); |
392 |
|
✗ |
wanted_size = MAX (y - top, right - x); |
393 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
394 |
|
✗ |
bottom = top + new_size; |
395 |
|
✗ |
left = right - new_size; |
396 |
|
✗ |
break; |
397 |
|
|
|
398 |
|
✗ |
case RIGHT: |
399 |
|
✗ |
max_size = MIN (size_x + distance_right, size_y + 2 * closest_distance_y); |
400 |
|
✗ |
wanted_size = x - left; |
401 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
402 |
|
✗ |
right = left + new_size; |
403 |
|
✗ |
top = center_y - new_size / 2; |
404 |
|
✗ |
bottom = top + new_size; |
405 |
|
✗ |
break; |
406 |
|
|
|
407 |
|
✗ |
case BOTTOM_RIGHT: |
408 |
|
✗ |
max_size = MIN (size_y + distance_bottom, size_x + distance_right); |
409 |
|
✗ |
wanted_size = MAX (y - top, x - left); |
410 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
411 |
|
✗ |
bottom = top + new_size; |
412 |
|
✗ |
right = left + new_size; |
413 |
|
✗ |
break; |
414 |
|
|
|
415 |
|
✗ |
case BOTTOM: |
416 |
|
✗ |
max_size = MIN (size_y + distance_bottom, size_x + 2 * closest_distance_x); |
417 |
|
✗ |
wanted_size = y - top; |
418 |
|
✗ |
new_size = CLAMP (wanted_size, MIN (min_size, max_size), max_size); |
419 |
|
✗ |
bottom = top + new_size; |
420 |
|
✗ |
left = center_x - new_size / 2; |
421 |
|
✗ |
right = left + new_size; |
422 |
|
✗ |
break; |
423 |
|
|
|
424 |
|
✗ |
default: |
425 |
|
✗ |
return; |
426 |
|
|
} |
427 |
|
|
|
428 |
|
✗ |
area->crop.x = left; |
429 |
|
✗ |
area->crop.y = top; |
430 |
|
✗ |
area->crop.width = right - left + 1; |
431 |
|
✗ |
area->crop.height = bottom - top + 1; |
432 |
|
|
|
433 |
|
|
/* Only update drag_off based on the rounded deltas, otherwise rounding accumulates */ |
434 |
|
✗ |
area->drag_offx += area->scale * delta_x; |
435 |
|
✗ |
area->drag_offy += area->scale * delta_y; |
436 |
|
|
|
437 |
|
✗ |
gtk_widget_queue_draw (GTK_WIDGET (area)); |
438 |
|
|
} |
439 |
|
|
|
440 |
|
|
static void |
441 |
|
✗ |
on_drag_end (CcCropArea *area, |
442 |
|
|
double offset_x, |
443 |
|
|
double offset_y) |
444 |
|
|
{ |
445 |
|
✗ |
area->active_region = OUTSIDE; |
446 |
|
✗ |
area->drag_offx = 0.0; |
447 |
|
✗ |
area->drag_offy = 0.0; |
448 |
|
✗ |
} |
449 |
|
|
|
450 |
|
|
static void |
451 |
|
✗ |
on_drag_cancel (CcCropArea *area, |
452 |
|
|
GdkEventSequence *sequence) |
453 |
|
|
{ |
454 |
|
✗ |
area->active_region = OUTSIDE; |
455 |
|
✗ |
area->drag_offx = 0; |
456 |
|
✗ |
area->drag_offy = 0; |
457 |
|
✗ |
} |
458 |
|
|
|
459 |
|
|
#define CORNER_LINE_WIDTH 4.0 |
460 |
|
|
#define CORNER_LINE_LENGTH 15.0 |
461 |
|
|
#define CORNER_SIZE (CORNER_LINE_LENGTH + CORNER_LINE_WIDTH / 2) |
462 |
|
|
|
463 |
|
|
static void |
464 |
|
✗ |
cc_crop_area_snapshot (GtkWidget *widget, |
465 |
|
|
GtkSnapshot *snapshot) |
466 |
|
|
{ |
467 |
|
✗ |
CcCropArea *area = CC_CROP_AREA (widget); |
468 |
|
|
cairo_t *cr; |
469 |
|
|
GdkRectangle crop; |
470 |
|
|
|
471 |
|
✗ |
if (area->paintable == NULL) |
472 |
|
✗ |
return; |
473 |
|
|
|
474 |
|
✗ |
update_image_and_crop (area); |
475 |
|
|
|
476 |
|
|
|
477 |
|
✗ |
gtk_snapshot_save (snapshot); |
478 |
|
|
|
479 |
|
|
/* First draw the picture */ |
480 |
|
✗ |
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (area->image.x, area->image.y)); |
481 |
|
|
|
482 |
|
✗ |
gdk_paintable_snapshot (area->paintable, snapshot, area->image.width, area->image.height); |
483 |
|
|
|
484 |
|
|
/* Draw the cropping UI on top with cairo */ |
485 |
|
✗ |
cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, area->image.width, area->image.height)); |
486 |
|
|
|
487 |
|
✗ |
get_scaled_crop (area, &crop); |
488 |
|
✗ |
crop.x -= area->image.x; |
489 |
|
✗ |
crop.y -= area->image.y; |
490 |
|
|
|
491 |
|
|
/* Draw the circle as an ellipse, to prevent rounding from jitter of the edges */ |
492 |
|
✗ |
cairo_save (cr); |
493 |
|
✗ |
cairo_translate (cr, crop.x + crop.width / 2.0, crop.y + crop.height / 2.0); |
494 |
|
✗ |
cairo_scale (cr, crop.width / 2.0, crop.height / 2.0); |
495 |
|
✗ |
cairo_arc (cr, 0, 0, 1, 0, 2 * G_PI); |
496 |
|
✗ |
cairo_restore (cr); |
497 |
|
✗ |
cairo_save (cr); |
498 |
|
✗ |
cairo_rectangle (cr, 0, 0, area->image.width, area->image.height); |
499 |
|
✗ |
cairo_set_source_rgba (cr, 0, 0, 0, 0.4); |
500 |
|
✗ |
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); |
501 |
|
✗ |
cairo_fill (cr); |
502 |
|
✗ |
cairo_restore (cr); |
503 |
|
|
|
504 |
|
|
/* draw the four corners */ |
505 |
|
✗ |
cairo_set_source_rgb (cr, 1, 1, 1); |
506 |
|
✗ |
cairo_set_line_width (cr, CORNER_LINE_WIDTH); |
507 |
|
|
|
508 |
|
|
/* top left corner */ |
509 |
|
✗ |
cairo_move_to (cr, crop.x + CORNER_LINE_WIDTH / 2, crop.y + CORNER_SIZE); |
510 |
|
✗ |
cairo_rel_line_to (cr, 0, -CORNER_LINE_LENGTH); |
511 |
|
✗ |
cairo_rel_line_to (cr, CORNER_LINE_LENGTH, 0); |
512 |
|
|
/* top right corner */ |
513 |
|
✗ |
cairo_rel_move_to (cr, crop.width - 2 * CORNER_SIZE, 0); |
514 |
|
✗ |
cairo_rel_line_to (cr, CORNER_LINE_LENGTH, 0); |
515 |
|
✗ |
cairo_rel_line_to (cr, 0, CORNER_LINE_LENGTH); |
516 |
|
|
/* bottom right corner */ |
517 |
|
✗ |
cairo_rel_move_to (cr, 0, crop.height - 2 * CORNER_SIZE); |
518 |
|
✗ |
cairo_rel_line_to (cr, 0, CORNER_LINE_LENGTH); |
519 |
|
✗ |
cairo_rel_line_to (cr, -CORNER_LINE_LENGTH, 0); |
520 |
|
|
/* bottom left corner */ |
521 |
|
✗ |
cairo_rel_move_to (cr, -(crop.width - 2 * CORNER_SIZE), 0); |
522 |
|
✗ |
cairo_rel_line_to (cr, -CORNER_LINE_LENGTH, 0); |
523 |
|
✗ |
cairo_rel_line_to (cr, 0, -CORNER_LINE_LENGTH); |
524 |
|
|
|
525 |
|
✗ |
cairo_stroke (cr); |
526 |
|
|
|
527 |
|
✗ |
gtk_snapshot_restore (snapshot); |
528 |
|
|
} |
529 |
|
|
|
530 |
|
|
static void |
531 |
|
✗ |
cc_crop_area_finalize (GObject *object) |
532 |
|
|
{ |
533 |
|
✗ |
CcCropArea *area = CC_CROP_AREA (object); |
534 |
|
|
|
535 |
|
✗ |
g_clear_object (&area->paintable); |
536 |
|
✗ |
} |
537 |
|
|
|
538 |
|
|
static void |
539 |
|
✗ |
cc_crop_area_class_init (CcCropAreaClass *klass) |
540 |
|
|
{ |
541 |
|
✗ |
GObjectClass *object_class = G_OBJECT_CLASS (klass); |
542 |
|
✗ |
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
543 |
|
|
|
544 |
|
✗ |
object_class->finalize = cc_crop_area_finalize; |
545 |
|
|
|
546 |
|
✗ |
widget_class->snapshot = cc_crop_area_snapshot; |
547 |
|
✗ |
} |
548 |
|
|
|
549 |
|
|
static void |
550 |
|
✗ |
cc_crop_area_init (CcCropArea *area) |
551 |
|
|
{ |
552 |
|
|
GtkGesture *gesture; |
553 |
|
|
GtkEventController *controller; |
554 |
|
|
|
555 |
|
|
/* Add handlers for dragging */ |
556 |
|
✗ |
gesture = gtk_gesture_drag_new (); |
557 |
|
✗ |
g_signal_connect_swapped (gesture, "drag-begin", G_CALLBACK (on_drag_begin), area); |
558 |
|
✗ |
g_signal_connect_swapped (gesture, "drag-update", G_CALLBACK (on_drag_update), area); |
559 |
|
✗ |
g_signal_connect_swapped (gesture, "drag-end", G_CALLBACK (on_drag_end), area); |
560 |
|
✗ |
g_signal_connect_swapped (gesture, "cancel", G_CALLBACK (on_drag_cancel), area); |
561 |
|
✗ |
gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (gesture)); |
562 |
|
|
|
563 |
|
|
/* Add handlers for motion events */ |
564 |
|
✗ |
controller = gtk_event_controller_motion_new (); |
565 |
|
✗ |
g_signal_connect_swapped (controller, "motion", G_CALLBACK (on_motion), area); |
566 |
|
✗ |
g_signal_connect_swapped (controller, "leave", G_CALLBACK (on_leave), area); |
567 |
|
✗ |
gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (controller)); |
568 |
|
|
|
569 |
|
✗ |
area->scale = 0.0; |
570 |
|
✗ |
area->image.x = 0; |
571 |
|
✗ |
area->image.y = 0; |
572 |
|
✗ |
area->image.width = 0; |
573 |
|
✗ |
area->image.height = 0; |
574 |
|
✗ |
area->active_region = OUTSIDE; |
575 |
|
✗ |
area->min_crop_width = 48; |
576 |
|
✗ |
area->min_crop_height = 48; |
577 |
|
|
|
578 |
|
✗ |
gtk_widget_set_size_request (GTK_WIDGET (area), 48, 48); |
579 |
|
✗ |
} |
580 |
|
|
|
581 |
|
|
GtkWidget * |
582 |
|
✗ |
cc_crop_area_new (void) |
583 |
|
|
{ |
584 |
|
✗ |
return g_object_new (CC_TYPE_CROP_AREA, NULL); |
585 |
|
|
} |
586 |
|
|
|
587 |
|
|
/** |
588 |
|
|
* cc_crop_area_create_pixbuf: |
589 |
|
|
* @area: A crop area |
590 |
|
|
* |
591 |
|
|
* Renders the area's paintable, with the cropping applied by the user, into a |
592 |
|
|
* GdkPixbuf. |
593 |
|
|
* |
594 |
|
|
* Returns: (transfer full): The cropped picture |
595 |
|
|
*/ |
596 |
|
|
GdkPixbuf * |
597 |
|
✗ |
cc_crop_area_create_pixbuf (CcCropArea *area) |
598 |
|
|
{ |
599 |
|
✗ |
g_autoptr (GtkSnapshot) snapshot = NULL; |
600 |
|
✗ |
g_autoptr (GskRenderNode) node = NULL; |
601 |
|
✗ |
g_autoptr (GskRenderer) renderer = NULL; |
602 |
|
✗ |
g_autoptr (GdkTexture) texture = NULL; |
603 |
|
✗ |
g_autoptr (GError) error = NULL; |
604 |
|
|
graphene_rect_t viewport; |
605 |
|
|
|
606 |
|
✗ |
g_return_val_if_fail (CC_IS_CROP_AREA (area), NULL); |
607 |
|
|
|
608 |
|
✗ |
snapshot = gtk_snapshot_new (); |
609 |
|
✗ |
gdk_paintable_snapshot (area->paintable, snapshot, |
610 |
|
✗ |
gdk_paintable_get_intrinsic_width (area->paintable), |
611 |
|
✗ |
gdk_paintable_get_intrinsic_height (area->paintable)); |
612 |
|
✗ |
node = gtk_snapshot_free_to_node (g_steal_pointer (&snapshot)); |
613 |
|
|
|
614 |
|
✗ |
renderer = gsk_gl_renderer_new (); |
615 |
|
✗ |
if (!gsk_renderer_realize (renderer, NULL, &error)) { |
616 |
|
✗ |
g_warning ("Couldn't realize GL renderer: %s", error->message); |
617 |
|
✗ |
return NULL; |
618 |
|
|
} |
619 |
|
✗ |
viewport = GRAPHENE_RECT_INIT (area->crop.x, area->crop.y, |
620 |
|
|
area->crop.width, area->crop.height); |
621 |
|
✗ |
texture = gsk_renderer_render_texture (renderer, node, &viewport); |
622 |
|
✗ |
gsk_renderer_unrealize (renderer); |
623 |
|
|
|
624 |
|
✗ |
return gdk_pixbuf_get_from_texture (texture); |
625 |
|
|
} |
626 |
|
|
|
627 |
|
|
/** |
628 |
|
|
* cc_crop_area_get_paintable: |
629 |
|
|
* @area: A crop area |
630 |
|
|
* |
631 |
|
|
* Returns the area's paintable, unmodified. |
632 |
|
|
* |
633 |
|
|
* Returns: (transfer none) (nullable): The paintable which the user can crop |
634 |
|
|
*/ |
635 |
|
|
GdkPaintable * |
636 |
|
✗ |
cc_crop_area_get_paintable (CcCropArea *area) |
637 |
|
|
{ |
638 |
|
✗ |
g_return_val_if_fail (CC_IS_CROP_AREA (area), NULL); |
639 |
|
|
|
640 |
|
✗ |
return area->paintable; |
641 |
|
|
} |
642 |
|
|
|
643 |
|
|
void |
644 |
|
✗ |
cc_crop_area_set_paintable (CcCropArea *area, |
645 |
|
|
GdkPaintable *paintable) |
646 |
|
|
{ |
647 |
|
✗ |
g_return_if_fail (CC_IS_CROP_AREA (area)); |
648 |
|
✗ |
g_return_if_fail (GDK_IS_PAINTABLE (paintable)); |
649 |
|
|
|
650 |
|
✗ |
g_set_object (&area->paintable, paintable); |
651 |
|
|
|
652 |
|
✗ |
area->scale = 0.0; |
653 |
|
✗ |
area->image.x = 0; |
654 |
|
✗ |
area->image.y = 0; |
655 |
|
✗ |
area->image.width = 0; |
656 |
|
✗ |
area->image.height = 0; |
657 |
|
|
|
658 |
|
✗ |
gtk_widget_queue_draw (GTK_WIDGET (area)); |
659 |
|
|
} |
660 |
|
|
|
661 |
|
|
/** |
662 |
|
|
* cc_crop_area_set_min_size: |
663 |
|
|
* @area: A crop widget |
664 |
|
|
* @width: The minimal width |
665 |
|
|
* @height: The minimal height |
666 |
|
|
* |
667 |
|
|
* Sets the minimal size of the crop rectangle (in paintable coordinates) |
668 |
|
|
*/ |
669 |
|
|
void |
670 |
|
✗ |
cc_crop_area_set_min_size (CcCropArea *area, |
671 |
|
|
int width, |
672 |
|
|
int height) |
673 |
|
|
{ |
674 |
|
✗ |
g_return_if_fail (CC_IS_CROP_AREA (area)); |
675 |
|
|
|
676 |
|
✗ |
area->min_crop_width = width; |
677 |
|
✗ |
area->min_crop_height = height; |
678 |
|
|
|
679 |
|
✗ |
gtk_widget_set_size_request (GTK_WIDGET (area), |
680 |
|
|
area->min_crop_width, |
681 |
|
|
area->min_crop_height); |
682 |
|
|
} |
683 |
|
|
|