Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
clipping.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2014, 2016, 2018 johannes hanika.
4 Copyright (C) 2010-2011 Bruce Guenter.
5 Copyright (C) 2010-2012 Henrik Andersson.
6 Copyright (C) 2010 José Carlos García Sogo.
7 Copyright (C) 2010 Kaminsky Andrey.
8 Copyright (C) 2010 Milan Knížek.
9 Copyright (C) 2010, 2012 Pascal de Bruijn.
10 Copyright (C) 2010 Stuart Henderson.
11 Copyright (C) 2010-2016, 2018-2019 Tobias Ellinghaus.
12 Copyright (C) 2011 Antony Dovgal.
13 Copyright (C) 2011 Brian Teague.
14 Copyright (C) 2011 Jérémy Rosen.
15 Copyright (C) 2011 Olivier Tribout.
16 Copyright (C) 2011 Robert Bieber.
17 Copyright (C) 2011 Rostyslav Pidgornyi.
18 Copyright (C) 2012-2014, 2016, 2019-2021 Aldric Renaudin.
19 Copyright (C) 2012, 2015 Edouard Gomez.
20 Copyright (C) 2012 Gaspard Jankowiak.
21 Copyright (C) 2012 jan.
22 Copyright (C) 2012 Michal Fabik.
23 Copyright (C) 2012-2013 parafin.
24 Copyright (C) 2012 Petr Styblo.
25 Copyright (C) 2012 Richard Wonka.
26 Copyright (C) 2012 Tom Vanderpoel.
27 Copyright (C) 2012, 2014-2016 Ulrich Pegelow.
28 Copyright (C) 2013-2015, 2018-2022 Pascal Obry.
29 Copyright (C) 2013-2016 Roman Lebedev.
30 Copyright (C) 2013 Simon Spannagel.
31 Copyright (C) 2015 Guillaume Subiron.
32 Copyright (C) 2015 Mark Feit.
33 Copyright (C) 2015 Pedro Côrte-Real.
34 Copyright (C) 2016 Asma.
35 Copyright (C) 2017-2019 Heiko Bauke.
36 Copyright (C) 2018, 2020-2026 Aurélien PIERRE.
37 Copyright (C) 2018 Edgardo Hoszowski.
38 Copyright (C) 2018 Maurizio Paglia.
39 Copyright (C) 2018 rawfiner.
40 Copyright (C) 2019 Andreas Schneider.
41 Copyright (C) 2019-2022 Diederik Ter Rahe.
42 Copyright (C) 2019 Diederik ter Rahe.
43 Copyright (C) 2019, 2021-2022 Hanno Schwalm.
44 Copyright (C) 2019 Jacques Le Clerc.
45 Copyright (C) 2019 Kamal Mostafa.
46 Copyright (C) 2020-2022 Chris Elston.
47 Copyright (C) 2020 GrahamByrnes.
48 Copyright (C) 2020 matt-maguire.
49 Copyright (C) 2020-2021 Ralf Brown.
50 Copyright (C) 2020 Sakari Kapanen.
51 Copyright (C) 2021 Hubert Kowalski.
52 Copyright (C) 2022 luzpaz.
53 Copyright (C) 2022 Martin Bařinka.
54 Copyright (C) 2022 Philipp Lutz.
55 Copyright (C) 2023 Luca Zulberti.
56 Copyright (C) 2025-2026 Guillaume Stutin.
57
58 darktable is free software: you can redistribute it and/or modify
59 it under the terms of the GNU General Public License as published by
60 the Free Software Foundation, either version 3 of the License, or
61 (at your option) any later version.
62
63 darktable is distributed in the hope that it will be useful,
64 but WITHOUT ANY WARRANTY; without even the implied warranty of
65 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
66 GNU General Public License for more details.
67
68 You should have received a copy of the GNU General Public License
69 along with darktable. If not, see <http://www.gnu.org/licenses/>.
70*/
71
72#ifdef HAVE_CONFIG_H
73#include "config.h"
74#endif
75#include "widgets/bauhaus.h"
77#include "system/macros.h"
78#include "system/openmp.h"
80#include "system/mem_alloc.h"
82#include "common/image.h"
83#include "common/imagebuf.h"
84#include "pixel/interpolation.h"
85#include "math/math.h"
86#include "common/conf.h"
87#include "control/input.h"
88#include "control/control.h"
89#include "develop/develop.h"
91#include "develop/imageop.h"
92#include "develop/imageop_gui.h"
93
94#include "widgets/draw.h"
95#include "gui/guides.h"
96#include "iop/iop_api.h"
97
98
99#include <assert.h>
100#include <gdk/gdkkeysyms.h>
101#include <gtk/gtk.h>
102#include <inttypes.h>
103#include <stdlib.h>
104#include <string.h>
105#include "widgets/notebook.h"
106#include "control/signal.h"
107
109
110
116
122
123typedef struct dt_iop_clipping_params_t
124{
125 float angle; // $MIN: -180.0 $MAX: 180.0
126 float cx; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "left"
127 float cy; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "top"
128 float cw; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "right"
129 float ch; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "bottom"
130 float k_h, k_v;
131 float kxa; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.2
132 float kya; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.2
133 float kxb; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.8
134 float kyb; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.2
135 float kxc; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.8
136 float kyc; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.8
137 float kxd; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.2
138 float kyd; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.8
139 int k_type, k_sym;
140 int k_apply; // $DEFAULT: 0
141 gboolean crop_auto; // $DEFAULT: TRUE $DESCRIPTION: "automatic cropping"
142 int ratio_n; // $DEFAULT: -1
143 int ratio_d; // $DEFAULT: -1
145
162
163/* calculate the aspect ratios for current image */
164static void keystone_type_populate(struct dt_iop_module_t *self, gboolean with_applied, int select);
165
166int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
167 void *new_params, const int new_version)
168{
169 if(new_version <= old_version) return 1;
170 if(new_version != 5) return 1;
171
173 if(old_version == 2 && new_version == 5)
174 {
175 union {
176 float f;
177 uint32_t u;
178 } k;
179 // old structure def
180 typedef struct old_params_t
181 {
182 float angle, cx, cy, cw, ch, k_h, k_v;
183 } old_params_t;
184
185 const old_params_t *o = (old_params_t *)old_params;
186
187 k.f = o->k_h;
188 int is_horizontal;
189 if(k.u & 0x40000000u)
190 is_horizontal = 1;
191 else
192 is_horizontal = 0;
193 k.u &= ~0x40000000;
194 if(is_horizontal)
195 {
196 n->k_h = k.f;
197 n->k_v = 0.0f;
198 }
199 else
200 {
201 n->k_h = 0.0f;
202 n->k_v = k.f;
203 }
204
205 n->angle = o->angle, n->cx = o->cx, n->cy = o->cy, n->cw = o->cw, n->ch = o->ch;
206 n->kxa = n->kxd = 0.2f;
207 n->kxc = n->kxb = 0.8f;
208 n->kya = n->kyb = 0.2f;
209 n->kyc = n->kyd = 0.8f;
210 if(n->k_h == 0 && n->k_v == 0)
211 n->k_type = 0;
212 else
213 n->k_type = 4;
214 n->k_sym = 0;
215 n->k_apply = 0;
216 n->crop_auto = 1;
217
218 // will be computed later, -2 here is used to detect uninitialized value, -1 is already used for no
219 // clipping.
220 n->ratio_d = n->ratio_n = -2;
221 }
222 if(old_version == 3 && new_version == 5)
223 {
224 // old structure def
225 typedef struct old_params_t
226 {
227 float angle, cx, cy, cw, ch, k_h, k_v;
228 } old_params_t;
229
230 const old_params_t *o = (old_params_t *)old_params;
231
232 n->angle = o->angle, n->cx = o->cx, n->cy = o->cy, n->cw = o->cw, n->ch = o->ch;
233 n->k_h = o->k_h, n->k_v = o->k_v;
234 n->kxa = n->kxd = 0.2f;
235 n->kxc = n->kxb = 0.8f;
236 n->kya = n->kyb = 0.2f;
237 n->kyc = n->kyd = 0.8f;
238 if(n->k_h == 0 && n->k_v == 0)
239 n->k_type = 0;
240 else
241 n->k_type = 4;
242 n->k_sym = 0;
243 n->k_apply = 0;
244 n->crop_auto = 1;
245
246 // will be computed later, -2 here is used to detect uninitialized value, -1 is already used for no
247 // clipping.
248 n->ratio_d = n->ratio_n = -2;
249 }
250 if(old_version == 4 && new_version == 5)
251 {
252 typedef struct old_params_t
253 {
254 float angle, cx, cy, cw, ch, k_h, k_v;
255 float kxa, kya, kxb, kyb, kxc, kyc, kxd, kyd;
256 int k_type, k_sym;
257 int k_apply, crop_auto;
258 } old_params_t;
259
260 const old_params_t *o = (old_params_t *)old_params;
261
262 n->angle = o->angle, n->cx = o->cx, n->cy = o->cy, n->cw = o->cw, n->ch = o->ch;
263 n->k_h = o->k_h, n->k_v = o->k_v;
264 n->kxa = o->kxa, n->kxb = o->kxb, n->kxc = o->kxc, n->kxd = o->kxd;
265 n->kya = o->kya, n->kyb = o->kyb, n->kyc = o->kyc, n->kyd = o->kyd;
266 n->k_type = o->k_type;
267 n->k_sym = o->k_sym;
268 n->k_apply = o->k_apply;
269 n->crop_auto = o->crop_auto;
270
271 // will be computed later, -2 here is used to detect uninitialized value, -1 is already used for no
272 // clipping.
273 n->ratio_d = n->ratio_n = -2;
274 }
275
276 return 0;
277}
278
312
314{
315 float angle; // rotation angle
316 float aspect; // forced aspect ratio
317 float m[4]; // rot/mirror matrix
318 float inv_m[4]; // inverse of m (m^-1)
319 float ki_h, k_h; // keystone correction, ki and corrected k
320 float ki_v, k_v; // keystone correction, ki and corrected k
321 float tx, ty; // rotation center
322 float cx, cy, cw, ch; // crop window
323 float cix, ciy; // crop window on roi_out 1.0 scale
324 uint32_t all_off; // 1: v and h off, else one of them is used
325 uint32_t flags; // flipping flags
326 uint32_t flip; // flipped output buffer so more area would fit.
327
328 dt_boundingbox_t k_space; // space for the "destination" rectangle of the keystone quadrilateral
329 float kxa, kya, kxb, kyb, kxc, kyc, kxd,
330 kyd; // point of the "source" quadrilatere (modified if keystone is not "full")
331 float a, b, d, e, g, h; // value of the transformation matrix (c=f=0 && i=1)
336
338
339
340// helper to count corners in for loops:
341static inline void get_corner(const float *aabb, const int i, float *p)
342{
343 for(int k = 0; k < 2; k++) p[k] = aabb[2 * ((i >> k) & 1) + k];
344}
345
346static inline void adjust_aabb(const float *p, float *aabb)
347{
348 aabb[0] = fminf(aabb[0], p[0]);
349 aabb[1] = fminf(aabb[1], p[1]);
350 aabb[2] = fmaxf(aabb[2], p[0]);
351 aabb[3] = fmaxf(aabb[3], p[1]);
352}
353
354const char *deprecated_msg()
355{
356 return _("this module is deprecated. please use the crop, orientation and/or rotate and perspective modules instead.");
357}
358
359const char *name()
360{
361 return _("crop and rotate");
362}
363
364const char *aliases()
365{
366 return _("reframe|perspective|keystone|distortion");
367}
368
369const char **description(struct dt_iop_module_t *self)
370{
371 return dt_iop_set_description(self, _("change the framing and correct the perspective"),
372 _("corrective or creative"),
373 _("linear, RGB, scene-referred"),
374 _("geometric, RGB"),
375 _("linear, RGB, scene-referred"));
376}
377
379{
380 return IOP_GROUP_EFFECTS;
381}
382
388
390{
392}
393
395{
396 // switch off watermark, it gets confused.
398}
399
401{
402 return IOP_CS_RGB;
403}
404
405static int gui_has_focus(struct dt_iop_module_t *self)
406{
407 return (self->dev->gui_module == self);
408}
409
410static inline __attribute__((always_inline)) void keystone_get_matrix(const dt_boundingbox_t k_space, float kxa, float kxb, float kxc, float kxd, float kya,
411 float kyb, float kyc, float kyd, float *a, float *b, float *d, float *e,
412 float *g, float *h)
413{
414 *a = -((kxb * (kyd * kyd - kyc * kyd) - kxc * kyd * kyd + kyb * (kxc * kyd - kxd * kyd) + kxd * kyc * kyd)
415 * k_space[2])
416 / (kxb * (kxc * kyd * kyd - kxd * kyc * kyd) + kyb * (kxd * kxd * kyc - kxc * kxd * kyd));
417 *b = ((kxb * (kxd * kyd - kxd * kyc) - kxc * kxd * kyd + kxd * kxd * kyc + (kxc * kxd - kxd * kxd) * kyb)
418 * k_space[2])
419 / (kxb * (kxc * kyd * kyd - kxd * kyc * kyd) + kyb * (kxd * kxd * kyc - kxc * kxd * kyd));
420 *d = (kyb * (kxb * (kyd * k_space[3] - kyc * k_space[3]) - kxc * kyd * k_space[3] + kxd * kyc * k_space[3])
421 + kyb * kyb * (kxc * k_space[3] - kxd * k_space[3]))
422 / (kxb * kyb * (-kxc * kyd - kxd * kyc) + kxb * kxb * kyc * kyd + kxc * kxd * kyb * kyb);
423 *e = -(kxb * (kxd * kyc * k_space[3] - kxc * kyd * k_space[3])
424 + kxb * kxb * (kyd * k_space[3] - kyc * k_space[3])
425 + kxb * kyb * (kxc * k_space[3] - kxd * k_space[3]))
426 / (kxb * kyb * (-kxc * kyd - kxd * kyc) + kxb * kxb * kyc * kyd + kxc * kxd * kyb * kyb);
427 *g = -(kyb * (kxb * (2.0f * kxc * kyd * kyd - 2.0f * kxc * kyc * kyd) - kxc * kxc * kyd * kyd
428 + 2.0f * kxc * kxd * kyc * kyd - kxd * kxd * kyc * kyc)
429 + kxb * kxb * (kyc * kyc * kyd - kyc * kyd * kyd)
430 + kyb * kyb * (-2.0f * kxc * kxd * kyd + kxc * kxc * kyd + kxd * kxd * kyc))
431 / (kxb * kxb * (kxd * kyc * kyc * kyd - kxc * kyc * kyd * kyd)
432 + kxb * kyb * (kxc * kxc * kyd * kyd - kxd * kxd * kyc * kyc)
433 + kyb * kyb * (kxc * kxd * kxd * kyc - kxc * kxc * kxd * kyd));
434 *h = (kxb * (-kxc * kxc * kyd * kyd + 2.0f * kxc * kxd * kyc * kyd - kxd * kxd * kyc * kyc)
435 + kxb * kxb * (kxc * kyd * kyd - 2.0f * kxd * kyc * kyd + kxd * kyc * kyc)
436 + kxb * (2.0f * kxd * kxd - 2.0f * kxc * kxd) * kyb * kyc
437 + (kxc * kxc * kxd - kxc * kxd * kxd) * kyb * kyb)
438 / (kxb * kxb * (kxd * kyc * kyc * kyd - kxc * kyc * kyd * kyd)
439 + kxb * kyb * (kxc * kxc * kyd * kyd - kxd * kxd * kyc * kyc)
440 + kyb * kyb * (kxc * kxd * kxd * kyc - kxc * kxc * kxd * kyd));
441}
442
444static inline void keystone_backtransform(float *i, const dt_boundingbox_t k_space, float a, float b, float d,
445 float e, float g, float h, float kxa, float kya)
446{
447 const float xx = i[0] - k_space[0];
448 const float yy = i[1] - k_space[1];
449
450 const float div = ((d * xx - a * yy) * h + (b * yy - e * xx) * g + a * e - b * d);
451
452 i[0] = (e * xx - b * yy) / div + kxa;
453 i[1] = -(d * xx - a * yy) / div + kya;
454}
455
457static inline void keystone_transform(float *i, const dt_boundingbox_t k_space, float a, float b, float d,
458 float e, float g, float h, float kxa, float kya)
459{
460 const float xx = i[0] - kxa;
461 const float yy = i[1] - kya;
462
463 const float div = g * xx + h * yy + 1;
464 i[0] = (a * xx + b * yy) / div + k_space[0];
465 i[1] = (d * xx + e * yy) / div + k_space[1];
466}
467
469static inline void backtransform(float *x, float *o, const float *m, const float t_h, const float t_v)
470{
471 x[1] /= (1.0f + x[0] * t_h);
472 x[0] /= (1.0f + x[1] * t_v);
473 mul_mat_vec_2(m, x, o);
474}
475
477static inline void inv_matrix(float *m, float *inv_m)
478{
479 const float det = (m[0] * m[3]) - (m[1] * m[2]);
480 inv_m[0] = m[3] / det;
481 inv_m[1] = -m[1] / det;
482 inv_m[2] = -m[2] / det;
483 inv_m[3] = m[0] / det;
484}
485
487static inline void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
488{
489 mul_mat_vec_2(m, x, o);
490 o[1] *= (1.0f + o[0] * t_h);
491 o[0] *= (1.0f + o[1] * t_v);
492}
493
494/* Defined below, next to modify_roi_out() whose body it is: the distort callbacks need the
495 * size-dependent state it settles before they can use it. */
496static void _clipping_derive(dt_iop_clipping_data_t *d, const dt_iop_roi_t *const roi_in_orig,
497 dt_iop_roi_t *roi_out);
498
500 float *const restrict points, size_t points_count)
501{
502 // as dt_iop_roi_t contain int values and not floats, we can have some rounding errors
503 // as a workaround, we use a factor for preview pipes
504 float factor = 1.0f;
505 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL)) factor = 100.0f;
506 // we first need to be sure that all data values are computed
507 // this is done in modify_roi_out fct, so we create tmp roi
509 dt_iop_roi_t roi_out, roi_in;
510 roi_in.width = piece->buf_in.width * factor;
511 roi_in.height = piece->buf_in.height * factor;
512 _clipping_derive(d, &roi_in, &roi_out);
513
514 const float rx = piece->buf_in.width;
515 const float ry = piece->buf_in.height;
516
517 const dt_boundingbox_t k_space = { d->k_space[0] * rx, d->k_space[1] * ry, d->k_space[2] * rx, d->k_space[3] * ry };
518 const float kxa = d->kxa * rx, kxb = d->kxb * rx, kxc = d->kxc * rx, kxd = d->kxd * rx;
519 const float kya = d->kya * ry, kyb = d->kyb * ry, kyc = d->kyc * ry, kyd = d->kyd * ry;
520 float ma = 0, mb = 0, md = 0, me = 0, mg = 0, mh = 0;
521 if(d->k_apply == 1)
522 keystone_get_matrix(k_space, kxa, kxb, kxc, kxd, kya, kyb, kyc, kyd, &ma, &mb, &md, &me, &mg, &mh);
523 __OMP_PARALLEL_FOR__(if(points_count > 100))
524 for(size_t i = 0; i < points_count * 2; i += 2)
525 {
526 float pi[2], po[2];
527 pi[0] = points[i];
528 pi[1] = points[i + 1];
529
530 if(d->k_apply == 1) keystone_transform(pi, k_space, ma, mb, md, me, mg, mh, kxa, kya);
531
532 pi[0] -= d->tx / factor;
533 pi[1] -= d->ty / factor;
534 // transform this point using matrix m
535 transform(pi, po, d->inv_m, d->k_h, d->k_v);
536
537 if(d->flip)
538 {
539 po[1] += d->tx / factor;
540 po[0] += d->ty / factor;
541 }
542 else
543 {
544 po[0] += d->tx / factor;
545 po[1] += d->ty / factor;
546 }
547
548 points[i] = po[0] - (d->cix - d->enlarge_x) / factor;
549 points[i + 1] = po[1] - (d->ciy - d->enlarge_y) / factor;
550 }
551
552 // revert side-effects of the previous call to modify_roi_out
553 // TODO: this is just a quick hack. we need a major revamp of this module!
554 if(factor != 1.0f)
555 {
556 roi_in.width = piece->buf_in.width;
557 roi_in.height = piece->buf_in.height;
558 _clipping_derive(d, &roi_in, &roi_out);
559 }
560
561 return 1;
562}
564 float *const restrict points, size_t points_count)
565{
566 // as dt_iop_roi_t contain int values and not floats, we can have some rounding errors
567 // as a workaround, we use a factor for preview pipes
568 float factor = 1.0f;
569 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL)) factor = 100.0f;
570 // we first need to be sure that all data values are computed
571 // this is done in modify_roi_out fct, so we create tmp roi
573 dt_iop_roi_t roi_out, roi_in;
574 roi_in.width = piece->buf_in.width * factor;
575 roi_in.height = piece->buf_in.height * factor;
576 _clipping_derive(d, &roi_in, &roi_out);
577
578 const float rx = piece->buf_in.width;
579 const float ry = piece->buf_in.height;
580
581 const dt_boundingbox_t k_space = { d->k_space[0] * rx, d->k_space[1] * ry, d->k_space[2] * rx, d->k_space[3] * ry };
582 const float kxa = d->kxa * rx, kxb = d->kxb * rx, kxc = d->kxc * rx, kxd = d->kxd * rx;
583 const float kya = d->kya * ry, kyb = d->kyb * ry, kyc = d->kyc * ry, kyd = d->kyd * ry;
584 float ma = 0.0f, mb = 0.0f, md = 0.0f, me = 0.0f, mg = 0.0f, mh = 0.0f;
585 if(d->k_apply == 1)
586 keystone_get_matrix(k_space, kxa, kxb, kxc, kxd, kya, kyb, kyc, kyd, &ma, &mb, &md, &me, &mg, &mh);
587 __OMP_PARALLEL_FOR_SIMD__(if(points_count > 100) aligned(points:64) aligned(k_space:16))
588 for(size_t i = 0; i < points_count * 2; i += 2)
589 {
590 float pi[2], po[2];
591 pi[0] = -(d->enlarge_x - d->cix) / factor + points[i];
592 pi[1] = -(d->enlarge_y - d->ciy) / factor + points[i + 1];
593
594 // transform this point using matrix m
595 if(d->flip)
596 {
597 pi[1] -= d->tx / factor;
598 pi[0] -= d->ty / factor;
599 }
600 else
601 {
602 pi[0] -= d->tx / factor;
603 pi[1] -= d->ty / factor;
604 }
605
606 backtransform(pi, po, d->m, d->k_h, d->k_v);
607
608 po[0] += d->tx / factor;
609 po[1] += d->ty / factor;
610 if(d->k_apply == 1) keystone_backtransform(po, k_space, ma, mb, md, me, mg, mh, kxa, kya);
611
612 points[i] = po[0];
613 points[i + 1] = po[1];
614 }
615
616 // revert side-effects of the previous call to modify_roi_out
617 // TODO: this is just a quick hack. we need a major revamp of this module!
618 if(factor != 1.0f)
619 {
620 roi_in.width = piece->buf_in.width;
621 roi_in.height = piece->buf_in.height;
622 _clipping_derive(d, &roi_in, &roi_out);
623 }
624
625 return 1;
626}
627
628void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece,
629 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
630 const dt_iop_roi_t *const roi_out)
631{
632 (void)pipe;
634
635 // only crop, no rot fast and sharp path:
636 if(!d->flags && d->angle == 0.0 && d->all_off && roi_in->width == roi_out->width && roi_in->height == roi_out->height)
637 {
638 dt_iop_image_copy_by_size(out, in, roi_out->width, roi_out->height, 1);
639 }
640 else
641 {
643 const float rx = piece->buf_in.width * roi_in->scale;
644 const float ry = piece->buf_in.height * roi_in->scale;
645 const dt_boundingbox_t k_space = { d->k_space[0] * rx, d->k_space[1] * ry, d->k_space[2] * rx, d->k_space[3] * ry };
646 const float kxa = d->kxa * rx, kxb = d->kxb * rx, kxc = d->kxc * rx, kxd = d->kxd * rx;
647 const float kya = d->kya * ry, kyb = d->kyb * ry, kyc = d->kyc * ry, kyd = d->kyd * ry;
648 float ma = 0.0f, mb = 0.0f, md = 0.0f, me = 0.0f, mg = 0.0f, mh = 0.0f;
649 if(d->k_apply == 1)
650 keystone_get_matrix(k_space, kxa, kxb, kxc, kxd, kya, kyb, kyc, kyd, &ma, &mb, &md, &me, &mg, &mh);
652 // (slow) point-by-point transformation.
653 // TODO: optimize with scanlines and linear steps between?
654 for(int j = 0; j < roi_out->height; j++)
655 {
656 float *_out = out + (size_t)j * roi_out->width;
657 for(int i = 0; i < roi_out->width; i++)
658 {
659 float pi[2] = { 0.0f }, po[2] = { 0.0f };
660
661 pi[0] = roi_out->x - roi_out->scale * d->enlarge_x + roi_out->scale * d->cix + i + 0.5f;
662 pi[1] = roi_out->y - roi_out->scale * d->enlarge_y + roi_out->scale * d->ciy + j + 0.5f;
663
664 // transform this point using matrix m
665 if(d->flip)
666 {
667 pi[1] -= d->tx * roi_out->scale;
668 pi[0] -= d->ty * roi_out->scale;
669 }
670 else
671 {
672 pi[0] -= d->tx * roi_out->scale;
673 pi[1] -= d->ty * roi_out->scale;
674 }
675 pi[0] /= roi_out->scale;
676 pi[1] /= roi_out->scale;
677 backtransform(pi, po, d->m, d->k_h, d->k_v);
678 po[0] *= roi_in->scale;
679 po[1] *= roi_in->scale;
680 po[0] += d->tx * roi_in->scale;
681 po[1] += d->ty * roi_in->scale;
682 if(d->k_apply == 1) keystone_backtransform(po, k_space, ma, mb, md, me, mg, mh, kxa, kya);
683 po[0] -= roi_in->x + 0.5f;
684 po[1] -= roi_in->y + 0.5f;
685
686 _out[i] = dt_interpolation_compute_sample(interpolation, in, po[0], po[1], roi_in->width, roi_in->height, 1,
687 roi_in->width);
688 }
689 }
690 }
691}
692
694{
697
698 // we want to know the size of the actual buffer
699 dt_iop_roi_t mod_out;
700 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return 0;
701
702 float wp = mod_out.width, hp = mod_out.height;
703 const float cx = CLAMPF(p->cx, 0.0f, 0.9f);
704 const float cy = CLAMPF(p->cy, 0.0f, 0.9f);
705 const float cw = CLAMPF(fabsf(p->cw), 0.1f, 1.0f);
706 const float ch = CLAMPF(fabsf(p->ch), 0.1f, 1.0f);
707
708 float points[8] = { 0.0f, 0.0f, wp, hp, cx * wp, cy * hp, cw * wp, ch * hp };
710 return 0;
712
713 g->clip_max_x = fmaxf(points[0], 0.0f);
714 g->clip_max_y = fmaxf(points[1], 0.0f);
715 g->clip_max_w = fminf(points[2] - points[0], 1.0f);
716 g->clip_max_h = fminf(points[3] - points[1], 1.0f);
717
718 // if clipping values are not null, this is undistorted values...
719 g->clip_x = fmaxf(points[4], g->clip_max_x);
720 g->clip_y = fmaxf(points[5], g->clip_max_y);
721 g->clip_w = fminf(points[6] - points[4], g->clip_max_w);
722 g->clip_h = fminf(points[7] - points[5], g->clip_max_h);
723
724 return 1;
725}
726
727// 1st pass: how large would the output be, given this input roi?
728// this is always called with the full buffer before processing.
746static void _clipping_derive(dt_iop_clipping_data_t *d, const dt_iop_roi_t *const roi_in_orig,
747 dt_iop_roi_t *roi_out)
748{
749 dt_iop_roi_t roi_in_d = *roi_in_orig;
750 dt_iop_roi_t *roi_in = &roi_in_d;
751
752
753 // use whole-buffer roi information to create matrix and inverse.
754 float rt[] = { cosf(d->angle), sinf(d->angle), -sinf(d->angle), cosf(d->angle) };
755 if(d->angle == 0.0f)
756 {
757 rt[0] = rt[3] = 1.0f;
758 rt[1] = rt[2] = 0.0f;
759 }
760
761 for(int k = 0; k < 4; k++) d->m[k] = rt[k];
762 if(d->flags & FLAG_FLIP_HORIZONTAL)
763 {
764 d->m[0] = -rt[0];
765 d->m[2] = -rt[2];
766 }
767 if(d->flags & FLAG_FLIP_VERTICAL)
768 {
769 d->m[1] = -rt[1];
770 d->m[3] = -rt[3];
771 }
772
773 // now compute inverse of m
774 inv_matrix(d->m, d->inv_m);
775
776 if(d->k_apply == 0 && d->crop_auto == 1) // this is the old solution.
777 {
778 float inv_rt[4] = { 0.0f };
779 inv_matrix(rt, inv_rt);
780
781 *roi_out = *roi_in;
782 // correct keystone correction factors by resolution of this buffer
783 const float kc = 1.0f / fminf(roi_in->width, roi_in->height);
784 d->k_h = d->ki_h * kc;
785 d->k_v = d->ki_v * kc;
786
787 float cropscale = -1.0f;
788 // check portrait/landscape orientation, whichever fits more area:
789 const float oaabb[4]
790 = { -.5f * roi_in->width, -.5f * roi_in->height, .5f * roi_in->width, .5f * roi_in->height };
791 for(int flip = 0; flip < 2; flip++)
792 {
793 const float roi_in_width = flip ? roi_in->height : roi_in->width;
794 const float roi_in_height = flip ? roi_in->width : roi_in->height;
795 float newcropscale = 1.0f;
796 // fwd transform rotated points on corners and scale back inside roi_in bounds.
797 float p[2], o[2],
798 aabb[4] = { -.5f * roi_in_width, -.5f * roi_in_height, .5f * roi_in_width, .5f * roi_in_height };
799 for(int c = 0; c < 4; c++)
800 {
801 get_corner(oaabb, c, p);
802 transform(p, o, inv_rt, d->k_h, d->k_v);
803 for(int k = 0; k < 2; k++)
804 if(fabsf(o[k]) > 0.001f) newcropscale = fminf(newcropscale, aabb[(o[k] > 0 ? 2 : 0) + k] / o[k]);
805 }
806 if(newcropscale >= cropscale)
807 {
808 cropscale = newcropscale;
809 // remember rotation center in whole-buffer coordinates:
810 d->tx = roi_in->width * .5f;
811 d->ty = roi_in->height * .5f;
812 d->flip = flip;
813
814 float ach = d->ch - d->cy, acw = d->cw - d->cx;
815 // rotate and clip to max extent
816 if(flip)
817 {
818 roi_out->y = d->tx - (.5f - d->cy) * cropscale * roi_in->width;
819 roi_out->x = d->ty - (.5f - d->cx) * cropscale * roi_in->height;
820 roi_out->height = ach * cropscale * roi_in->width;
821 roi_out->width = acw * cropscale * roi_in->height;
822 }
823 else
824 {
825 roi_out->x = d->tx - (.5f - d->cx) * cropscale * roi_in->width;
826 roi_out->y = d->ty - (.5f - d->cy) * cropscale * roi_in->height;
827 roi_out->width = acw * cropscale * roi_in->width;
828 roi_out->height = ach * cropscale * roi_in->height;
829 }
830 }
831 }
832 }
833 else
834 {
835 *roi_out = *roi_in;
836 // set roi_out values with rotation and keystone
837 // initial corners pos
838 dt_boundingbox_t corn_x = { 0.0f, roi_in->width, roi_in->width, 0.0f };
839 dt_boundingbox_t corn_y = { 0.0f, 0.0f, roi_in->height, roi_in->height };
840 // destination corner points
841 dt_boundingbox_t corn_out_x = { 0.0f };
842 dt_boundingbox_t corn_out_y = { 0.0f };
843
844 // we don't test image flip as autocrop is not completely ok...
845 d->flip = 0;
846
847 // we apply rotation and keystone to all those points
848 float p[2], o[2];
849 for(int c = 0; c < 4; c++)
850 {
851 // keystone
852 o[0] = corn_x[c];
853 o[1] = corn_y[c];
854 if(d->k_apply == 1)
855 {
856 o[0] /= (float)roi_in->width;
857 o[1] /= (float)roi_in->height;
858 keystone_transform(o, d->k_space, d->a, d->b, d->d, d->e, d->g, d->h, d->kxa, d->kya);
859 o[0] *= roi_in->width;
860 o[1] *= roi_in->height;
861 }
862 // rotation
863 p[0] = o[0] - .5f * roi_in->width;
864 p[1] = o[1] - .5f * roi_in->height;
865 transform(p, o, d->inv_m, d->k_h, d->k_v);
866 o[0] += .5f * roi_in->width;
867 o[1] += .5f * roi_in->height;
868
869 // and we set the values
870 corn_out_x[c] = o[0];
871 corn_out_y[c] = o[1];
872 }
873
874 float new_x = fminf(fminf(fminf(corn_out_x[0], corn_out_x[1]), corn_out_x[2]), corn_out_x[3]);
875 if(new_x + roi_in->width < 0) new_x = -roi_in->width;
876 float new_y = fminf(fminf(fminf(corn_out_y[0], corn_out_y[1]), corn_out_y[2]), corn_out_y[3]);
877 if(new_y + roi_in->height < 0) new_y = -roi_in->height;
878
879 float new_sc_x = fmaxf(fmaxf(fmaxf(corn_out_x[0], corn_out_x[1]), corn_out_x[2]), corn_out_x[3]);
880 if(new_sc_x > 2.0f * roi_in->width) new_sc_x = 2.0f * roi_in->width;
881 float new_sc_y = fmaxf(fmaxf(fmaxf(corn_out_y[0], corn_out_y[1]), corn_out_y[2]), corn_out_y[3]);
882 if(new_sc_y > 2.0f * roi_in->height) new_sc_y = 2.0f * roi_in->height;
883
884 // be careful, we don't want too small area here !
885 if(new_sc_x - new_x < roi_in->width / 8.0f)
886 {
887 float f = (new_sc_x + new_x) / 2.0f;
888 if(f < roi_in->width / 16.0f) f = roi_in->width / 16.0f;
889 if(f >= roi_in->width * 15.0f / 16.0f) f = roi_in->width * 15.0f / 16.0f - 1.0f;
890 new_x = f - roi_in->width / 16.0f, new_sc_x = f + roi_in->width / 16.0f;
891 }
892 if(new_sc_y - new_y < roi_in->height / 8.0f)
893 {
894 float f = (new_sc_y + new_y) / 2.0f;
895 if(f < roi_in->height / 16.0f) f = roi_in->height / 16.0f;
896 if(f >= roi_in->height * 15.0f / 16.0f) f = roi_in->height * 15.0f / 16.0f - 1.0f;
897 new_y = f - roi_in->height / 16.0f, new_sc_y = f + roi_in->height / 16.0f;
898 }
899
900 new_sc_y = new_sc_y - new_y;
901 new_sc_x = new_sc_x - new_x;
902
903 // now we apply the clipping
904 new_x += d->cx * new_sc_x;
905 new_y += d->cy * new_sc_y;
906 new_sc_x *= d->cw - d->cx;
907 new_sc_y *= d->ch - d->cy;
908
909 d->enlarge_x = fmaxf(-new_x, 0.0f);
910 roi_out->x = fmaxf(new_x, 0.0f);
911 d->enlarge_y = fmaxf(-new_y, 0.0f);
912 roi_out->y = fmaxf(new_y, 0.0f);
913
914 roi_out->width = new_sc_x;
915 roi_out->height = new_sc_y;
916 d->tx = roi_in->width * .5f;
917 d->ty = roi_in->height * .5f;
918 }
919
920 // sanity check.
921 if(roi_out->x < 0) roi_out->x = 0;
922 if(roi_out->y < 0) roi_out->y = 0;
923 if(roi_out->width < 1) roi_out->width = 1;
924 if(roi_out->height < 1) roi_out->height = 1;
925
926 // save rotation crop on output buffer in world scale:
927 d->cix = roi_out->x;
928 d->ciy = roi_out->y;
929}
930
931void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
932 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
933 const dt_iop_roi_t *roi_in_orig)
934{
935 _clipping_derive((dt_iop_clipping_data_t *)piece->data, roi_in_orig, roi_out);
936}
937
938// 2nd pass: which roi would this operation need as input to fill the given output region?
939void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
940 struct dt_dev_pixelpipe_iop_t *piece,
941 const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
942{
944 *roi_in = *roi_out;
945 // modify_roi_out took care of bounds checking for us. we hopefully do not get requests outside the clipping
946 // area.
947 // transform aabb back to roi_in
948
949 // this aabb is set off by cx/cy
950 const float so = roi_out->scale;
951 const float kw = piece->buf_in.width * so, kh = piece->buf_in.height * so;
952 const float roi_out_x = roi_out->x - d->enlarge_x * so, roi_out_y = roi_out->y - d->enlarge_y * so;
953 float p[2], o[2];
954 dt_boundingbox_t aabb = { roi_out_x + d->cix * so, roi_out_y + d->ciy * so, roi_out_x + d->cix * so + roi_out->width,
955 roi_out_y + d->ciy * so + roi_out->height };
956 dt_boundingbox_t aabb_in = { INFINITY, INFINITY, -INFINITY, -INFINITY };
957 for(int c = 0; c < 4; c++)
958 {
959 // get corner points of roi_out
960 get_corner(aabb, c, p);
961
962 // backtransform aabb using m
963 if(d->flip)
964 {
965 p[1] -= d->tx * so;
966 p[0] -= d->ty * so;
967 }
968 else
969 {
970 p[0] -= d->tx * so;
971 p[1] -= d->ty * so;
972 }
973 p[0] *= 1.0 / so;
974 p[1] *= 1.0 / so;
975 backtransform(p, o, d->m, d->k_h, d->k_v);
976 o[0] *= so;
977 o[1] *= so;
978 o[0] += d->tx * so;
979 o[1] += d->ty * so;
980 o[0] /= kw;
981 o[1] /= kh;
982 if(d->k_apply == 1)
983 keystone_backtransform(o, d->k_space, d->a, d->b, d->d, d->e, d->g, d->h, d->kxa, d->kya);
984 o[0] *= kw;
985 o[1] *= kh;
986 // transform to roi_in space, get aabb.
987 adjust_aabb(o, aabb_in);
988 }
989
990 // adjust roi_in to minimally needed region
991 roi_in->x = aabb_in[0] - 1;
992 roi_in->y = aabb_in[1] - 1;
993 roi_in->width = aabb_in[2] - aabb_in[0] + 2;
994 roi_in->height = aabb_in[3] - aabb_in[1] + 2;
995
996 if(d->angle == 0.0f && d->all_off)
997 {
998 // just crop: make sure everything is precise.
999 roi_in->x = aabb_in[0];
1000 roi_in->y = aabb_in[1];
1001 roi_in->width = roi_out->width;
1002 roi_in->height = roi_out->height;
1003 }
1004
1005 // sanity check.
1006 const float scwidth = piece->buf_in.width * so, scheight = piece->buf_in.height * so;
1007 roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(scwidth));
1008 roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(scheight));
1009 roi_in->width = CLAMP(roi_in->width, 1, (int)ceilf(scwidth) - roi_in->x);
1010 roi_in->height = CLAMP(roi_in->height, 1, (int)ceilf(scheight) - roi_in->y);
1011}
1012
1013// 3rd (final) pass: you get this input region (may be different from what was requested above),
1014// do your best to fill the output region!
1016int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
1017 void *const ovoid)
1018{
1019 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1020 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1021
1023
1024 const int ch = 4;
1025 const int ch_width = ch * roi_in->width;
1026
1027 // only crop, no rot fast and sharp path:
1028 if(!d->flags && d->angle == 0.0 && d->all_off && roi_in->width == roi_out->width
1029 && roi_in->height == roi_out->height)
1030 {
1031 dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
1032 }
1033 else
1034 {
1036 const float rx = piece->buf_in.width * roi_in->scale;
1037 const float ry = piece->buf_in.height * roi_in->scale;
1038 const dt_boundingbox_t k_space = { d->k_space[0] * rx, d->k_space[1] * ry, d->k_space[2] * rx, d->k_space[3] * ry };
1039 const float kxa = d->kxa * rx, kxb = d->kxb * rx, kxc = d->kxc * rx, kxd = d->kxd * rx;
1040 const float kya = d->kya * ry, kyb = d->kyb * ry, kyc = d->kyc * ry, kyd = d->kyd * ry;
1041 float ma = 0.0f, mb = 0.0f, md = 0.0f, me = 0.0f, mg = 0.0f, mh = 0.0f;
1042 if(d->k_apply == 1)
1043 keystone_get_matrix(k_space, kxa, kxb, kxc, kxd, kya, kyb, kyc, kyd, &ma, &mb, &md, &me, &mg, &mh);
1045 // (slow) point-by-point transformation.
1046 // TODO: optimize with scanlines and linear steps between?
1047 for(int j = 0; j < roi_out->height; j++)
1048 {
1049 float *out = ((float *)ovoid) + (size_t)ch * j * roi_out->width;
1050 for(int i = 0; i < roi_out->width; i++)
1051 {
1052 float pi[2], po[2];
1053
1054 pi[0] = roi_out->x - roi_out->scale * d->enlarge_x + roi_out->scale * d->cix + i + 0.5f;
1055 pi[1] = roi_out->y - roi_out->scale * d->enlarge_y + roi_out->scale * d->ciy + j + 0.5f;
1056
1057 // transform this point using matrix m
1058 if(d->flip)
1059 {
1060 pi[1] -= d->tx * roi_out->scale;
1061 pi[0] -= d->ty * roi_out->scale;
1062 }
1063 else
1064 {
1065 pi[0] -= d->tx * roi_out->scale;
1066 pi[1] -= d->ty * roi_out->scale;
1067 }
1068 pi[0] /= roi_out->scale;
1069 pi[1] /= roi_out->scale;
1070 backtransform(pi, po, d->m, d->k_h, d->k_v);
1071 po[0] *= roi_in->scale;
1072 po[1] *= roi_in->scale;
1073 po[0] += d->tx * roi_in->scale;
1074 po[1] += d->ty * roi_in->scale;
1075 if(d->k_apply == 1) keystone_backtransform(po, k_space, ma, mb, md, me, mg, mh, kxa, kya);
1076 po[0] -= roi_in->x + 0.5f;
1077 po[1] -= roi_in->y + 0.5f;
1078
1079 dt_interpolation_compute_pixel4c(interpolation, (float *)ivoid, out + ch*i, po[0], po[1], roi_in->width,
1080 roi_in->height, ch_width);
1081 }
1082 }
1083 }
1084
1085 return 0;
1086}
1087
1088
1105static void _clipping_resolve(struct dt_iop_module_t *self, const dt_iop_clipping_params_t *const p,
1107{
1108
1109 // reset all values to be sure everything is initialized
1110 d->m[0] = d->m[3] = 1.0f;
1111 d->m[1] = d->m[2] = 0.0f;
1112 d->ki_h = d->ki_v = d->k_h = d->k_v = 0.0f;
1113 d->tx = d->ty = 0.0f;
1114 d->cix = d->ciy = 0.0f;
1115 d->kxa = d->kxd = d->kya = d->kyb = 0.0f;
1116 d->kxb = d->kxc = d->kyc = d->kyd = 0.6f;
1117 d->k_space[0] = d->k_space[1] = 0.2f;
1118 d->k_space[2] = d->k_space[3] = 0.6f;
1119 d->k_apply = 0;
1120 d->enlarge_x = d->enlarge_y = 0.0f;
1121 d->flip = 0;
1122 d->angle = M_PI / 180.0 * p->angle;
1123
1124 // image flip
1125 d->flags = (p->ch < 0 ? FLAG_FLIP_VERTICAL : 0) | (p->cw < 0 ? FLAG_FLIP_HORIZONTAL : 0);
1126 d->crop_auto = p->crop_auto;
1127
1128 // keystones values computation
1129 if(p->k_type == 4)
1130 {
1131 // this is for old keystoning
1132 d->k_apply = 0;
1133 d->all_off = 1;
1134 if(fabsf(p->k_h) >= .0001) d->all_off = 0;
1135 if(p->k_h >= -1.0 && p->k_h <= 1.0)
1136 d->ki_h = p->k_h;
1137 else
1138 d->ki_h = 0.0f;
1139 if(fabsf(p->k_v) >= .0001) d->all_off = 0;
1140 if(p->k_v >= -1.0 && p->k_v <= 1.0)
1141 d->ki_v = p->k_v;
1142 else
1143 d->ki_v = 0.0f;
1144 }
1145 else if(p->k_type >= 0 && p->k_apply == 1)
1146 {
1147 // we reset old keystoning values
1148 d->ki_h = d->ki_v = 0;
1149 d->kxa = p->kxa;
1150 d->kxb = p->kxb;
1151 d->kxc = p->kxc;
1152 d->kxd = p->kxd;
1153 d->kya = p->kya;
1154 d->kyb = p->kyb;
1155 d->kyc = p->kyc;
1156 d->kyd = p->kyd;
1157 // we adjust the points if the keystoning is not in "full" mode
1158 if(p->k_type == 1) // we want horizontal points to be aligned
1159 {
1160 // line equations parameters
1161 float a1 = (d->kxd - d->kxa) / (d->kyd - d->kya);
1162 float b1 = d->kxa - a1 * d->kya;
1163 float a2 = (d->kxc - d->kxb) / (d->kyc - d->kyb);
1164 float b2 = d->kxb - a2 * d->kyb;
1165
1166 if(d->kya > d->kyb)
1167 {
1168 // we move kya to the level of kyb
1169 d->kya = d->kyb;
1170 d->kxa = a1 * d->kya + b1;
1171 }
1172 else
1173 {
1174 // we move kyb to the level of kya
1175 d->kyb = d->kya;
1176 d->kxb = a2 * d->kyb + b2;
1177 }
1178
1179 if(d->kyc > d->kyd)
1180 {
1181 // we move kyd to the level of kyc
1182 d->kyd = d->kyc;
1183 d->kxd = a1 * d->kyd + b1;
1184 }
1185 else
1186 {
1187 // we move kyc to the level of kyd
1188 d->kyc = d->kyd;
1189 d->kxc = a2 * d->kyc + b2;
1190 }
1191 }
1192 else if(p->k_type == 2) // we want vertical points to be aligned
1193 {
1194 // line equations parameters
1195 const float a1 = (d->kyb - d->kya) / (d->kxb - d->kxa);
1196 const float b1 = d->kya - a1 * d->kxa;
1197 const float a2 = (d->kyc - d->kyd) / (d->kxc - d->kxd);
1198 const float b2 = d->kyd - a2 * d->kxd;
1199
1200 if(d->kxa > d->kxd)
1201 {
1202 // we move kxa to the level of kxd
1203 d->kxa = d->kxd;
1204 d->kya = a1 * d->kxa + b1;
1205 }
1206 else
1207 {
1208 // we move kyb to the level of kya
1209 d->kxd = d->kxa;
1210 d->kyd = a2 * d->kxd + b2;
1211 }
1212
1213 if(d->kxc > d->kxb)
1214 {
1215 // we move kyd to the level of kyc
1216 d->kxb = d->kxc;
1217 d->kyb = a1 * d->kxb + b1;
1218 }
1219 else
1220 {
1221 // we move kyc to the level of kyd
1222 d->kxc = d->kxb;
1223 d->kyc = a2 * d->kxc + b2;
1224 }
1225 }
1226 d->k_space[0] = fabsf((d->kxa + d->kxd) / 2.0f);
1227 d->k_space[1] = fabsf((d->kya + d->kyb) / 2.0f);
1228 d->k_space[2] = fabsf((d->kxb + d->kxc) / 2.0f) - d->k_space[0];
1229 d->k_space[3] = fabsf((d->kyc + d->kyd) / 2.0f) - d->k_space[1];
1230 d->kxb = d->kxb - d->kxa;
1231 d->kxc = d->kxc - d->kxa;
1232 d->kxd = d->kxd - d->kxa;
1233 d->kyb = d->kyb - d->kya;
1234 d->kyc = d->kyc - d->kya;
1235 d->kyd = d->kyd - d->kya;
1236 keystone_get_matrix(d->k_space, d->kxa, d->kxb, d->kxc, d->kxd, d->kya, d->kyb, d->kyc, d->kyd, &d->a,
1237 &d->b, &d->d, &d->e, &d->g, &d->h);
1238
1239 d->k_apply = 1;
1240 d->all_off = 0;
1241 d->crop_auto = 0;
1242 }
1243 else
1244 {
1245 d->all_off = 1;
1246 d->k_apply = 0;
1247 }
1248
1249 if(gui_has_focus(self))
1250 {
1251 d->cx = 0.0f;
1252 d->cy = 0.0f;
1253 d->cw = 1.0f;
1254 d->ch = 1.0f;
1255 }
1256 else
1257 {
1258 d->cx = CLAMPF(p->cx, 0.0f, 0.9f);
1259 d->cy = CLAMPF(p->cy, 0.0f, 0.9f);
1260 d->cw = CLAMPF(fabsf(p->cw), 0.1f, 1.0f);
1261 d->ch = CLAMPF(fabsf(p->ch), 0.1f, 1.0f);
1262 // we show a error on stderr if we have clamped something
1263 if(d->cx != p->cx || d->cy != p->cy || d->cw != fabsf(p->cw) || d->ch != fabsf(p->ch))
1264 {
1265 fprintf(stderr, "[crop&rotate] invalid crop data for %d : x=%0.04f y=%0.04f w=%0.04f h=%0.04f\n",
1266 self->dev->image_storage.id, p->cx, p->cy, p->cw, p->ch);
1267 }
1268 }
1269}
1270
1276
1277static void _event_preview_updated_callback(gpointer instance, dt_iop_module_t *self)
1278{
1280 g->preview_ready = TRUE;
1282 // force max size to be recomputed
1283 g->clip_max_pipe_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1284}
1285
1286void gui_focus(struct dt_iop_module_t *self, gboolean in)
1287{
1290
1291 dt_iop_set_cache_bypass(self, in);
1292
1293 if(self->enabled)
1294 {
1295 if(in)
1296 {
1298 G_CALLBACK(_event_preview_updated_callback), self);
1299
1300 // got focus, grab stuff to gui:
1301 // need to get gui stuff for the first time for this image,
1302 g->clip_x = CLAMPF(p->cx, 0.0f, 0.9f);
1303 g->clip_y = CLAMPF(p->cy, 0.0f, 0.9f);
1304 g->clip_w = CLAMPF(fabsf(p->cw) - p->cx, 0.1f, 1.0f - g->clip_x);
1305 g->clip_h = CLAMPF(fabsf(p->ch) - p->cy, 0.1f, 1.0f - g->clip_y);
1306 }
1307 else
1308 {
1310 G_CALLBACK(_event_preview_updated_callback), self);
1311
1312 // lost focus, commit current params:
1313 // if the keystone setting is not finished, we discard it
1314 if(p->k_apply == 0 && p->k_type < 4 && p->k_type > 0)
1315 {
1316 keystone_type_populate(self, FALSE, 0);
1317 }
1318 // hack : commit_box use distort_transform routines with gui values to get params
1319 // but this values are accurate only if clipping is the gui_module...
1320 // so we temporary put back gui_module to clipping and revert once finished
1321 dt_iop_module_t *old_gui = self->dev->gui_module;
1322 self->dev->gui_module = self;
1323 commit_box(self, g, p);
1324 self->dev->gui_module = old_gui;
1325 g->clip_max_pipe_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1326 }
1327 }
1328 else if(in)
1329 g->preview_ready = TRUE;
1330
1333}
1334
1335
1336/* --- the geometry service's view of this module (develop/geometry/geometry.h) ---------
1337 *
1338 * The record carries the PARAMETER-derived half of the state (what _clipping_resolve() settles)
1339 * and re-derives the size-dependent half per call, into a local copy, because that half is a
1340 * function of the rectangle it is handed and the chain hands a different one to the size fold
1341 * than a consumer may ask a transform about. Deriving into a local keeps the evaluators pure
1342 * and the record immutable, at the cost of running the derivation per query -- it is arithmetic
1343 * on a dozen floats, and the alternative is a record that mutates while being read.
1344 *
1345 * Note the factor: the pipe's distort callbacks multiply the input size by 100 for preview
1346 * pipes, to get around dt_iop_roi_t being integral. The chain does not, because the pipe the
1347 * GUI walks today -- the virtual one -- is not the preview pipe by that test either, so factor
1348 * 1 is what these coordinates are already computed with. Changing it would be an improvement to
1349 * make deliberately, with shadow mode watching, not a side effect of this tranche.
1350 */
1351
1352static void _clipping_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
1353{
1354 dt_iop_clipping_data_t local = *(const dt_iop_clipping_data_t *)data;
1355 _clipping_derive(&local, in, out);
1356}
1357
1359static void _clipping_keystone(const dt_iop_clipping_data_t *const d, const float rx, const float ry,
1360 dt_boundingbox_t k_space, float *kxa, float *kya, float *ma, float *mb,
1361 float *md, float *me, float *mg, float *mh)
1362{
1363 k_space[0] = d->k_space[0] * rx;
1364 k_space[1] = d->k_space[1] * ry;
1365 k_space[2] = d->k_space[2] * rx;
1366 k_space[3] = d->k_space[3] * ry;
1367
1368 *kxa = d->kxa * rx;
1369 *kya = d->kya * ry;
1370 if(d->k_apply == 1)
1371 keystone_get_matrix(k_space, d->kxa * rx, d->kxb * rx, d->kxc * rx, d->kxd * rx, d->kya * ry,
1372 d->kyb * ry, d->kyc * ry, d->kyd * ry, ma, mb, md, me, mg, mh);
1373}
1374
1375static int _clipping_geometry_transform(const void *data, const dt_geometry_record_t *const record,
1376 dt_geometry_chain_t *chain, float *points, size_t points_count)
1377{
1380 _clipping_derive(&d, &record->in, &out);
1381
1382 const float rx = record->in.width, ry = record->in.height;
1383 dt_boundingbox_t k_space;
1384 float kxa = 0.f, kya = 0.f, ma = 0.f, mb = 0.f, md = 0.f, me = 0.f, mg = 0.f, mh = 0.f;
1385 _clipping_keystone(&d, rx, ry, k_space, &kxa, &kya, &ma, &mb, &md, &me, &mg, &mh);
1386
1387 for(size_t i = 0; i < points_count * 2; i += 2)
1388 {
1389 float pi[2] = { points[i], points[i + 1] }, po[2];
1390
1391 if(d.k_apply == 1) keystone_transform(pi, k_space, ma, mb, md, me, mg, mh, kxa, kya);
1392
1393 pi[0] -= d.tx;
1394 pi[1] -= d.ty;
1395 transform(pi, po, d.inv_m, d.k_h, d.k_v);
1396
1397 if(d.flip)
1398 {
1399 po[1] += d.tx;
1400 po[0] += d.ty;
1401 }
1402 else
1403 {
1404 po[0] += d.tx;
1405 po[1] += d.ty;
1406 }
1407
1408 points[i] = po[0] - (d.cix - d.enlarge_x);
1409 points[i + 1] = po[1] - (d.ciy - d.enlarge_y);
1410 }
1411 return 1;
1412}
1413
1414static int _clipping_geometry_backtransform(const void *data, const dt_geometry_record_t *const record,
1415 dt_geometry_chain_t *chain, float *points, size_t points_count)
1416{
1419 _clipping_derive(&d, &record->in, &out);
1420
1421 const float rx = record->in.width, ry = record->in.height;
1422 dt_boundingbox_t k_space;
1423 float kxa = 0.f, kya = 0.f, ma = 0.f, mb = 0.f, md = 0.f, me = 0.f, mg = 0.f, mh = 0.f;
1424 _clipping_keystone(&d, rx, ry, k_space, &kxa, &kya, &ma, &mb, &md, &me, &mg, &mh);
1425
1426 for(size_t i = 0; i < points_count * 2; i += 2)
1427 {
1428 float pi[2], po[2];
1429 pi[0] = -(d.enlarge_x - d.cix) + points[i];
1430 pi[1] = -(d.enlarge_y - d.ciy) + points[i + 1];
1431
1432 if(d.flip)
1433 {
1434 pi[1] -= d.tx;
1435 pi[0] -= d.ty;
1436 }
1437 else
1438 {
1439 pi[0] -= d.tx;
1440 pi[1] -= d.ty;
1441 }
1442
1443 backtransform(pi, po, d.m, d.k_h, d.k_v);
1444
1445 po[0] += d.tx;
1446 po[1] += d.ty;
1447 if(d.k_apply == 1) keystone_backtransform(po, k_space, ma, mb, md, me, mg, mh, kxa, kya);
1448
1449 points[i] = po[0];
1450 points[i + 1] = po[1];
1451 }
1452 return 1;
1453}
1454
1460
1461gboolean geometry_record(struct dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
1462{
1464 if(IS_NULL_PTR(data)) return FALSE;
1465
1466 _clipping_resolve(self, (const dt_iop_clipping_params_t *)params, data);
1467
1468 record->data = data;
1469 record->free_data = dt_free_gpointer;
1471 return TRUE;
1472}
1473
1475{
1477 piece->data_size = sizeof(dt_iop_clipping_data_t);
1478}
1479
1481{
1482 dt_free_align(piece->data);
1483 piece->data = NULL;
1484}
1485
1487{
1489
1490 //retrieve full image dimensions to calculate aspect ratio if "original image" specified
1491 const char *text = dt_bauhaus_combobox_get_text(combo);
1492 if(text && !g_strcmp0(text,_("original image")))
1493 {
1494 int proc_iwd = 0, proc_iht = 0;
1495 dt_dev_get_processed_size(self->dev, &proc_iwd, &proc_iht);
1496
1497 if(!(proc_iwd > 0 && proc_iht > 0)) return 0.0f;
1498
1499 if((p->ratio_d > 0 && proc_iwd > proc_iht) || (p->ratio_d < 0 && proc_iwd < proc_iht))
1500 return (float)proc_iwd / (float)proc_iht;
1501 else
1502 return (float)proc_iht / (float)proc_iwd;
1503 }
1504
1505 // we want to know the size of the actual buffer
1506 dt_iop_roi_t mod_in;
1507 if(!dt_dev_module_geometry_gui(self->dev, self, &mod_in, NULL)) return 0.0f;
1508
1509 const int iwd = mod_in.width, iht = mod_in.height;
1510
1511 // if we do not have yet computed the aspect ratio, let's do it now
1512 if(p->ratio_d == -2 && p->ratio_n == -2)
1513 {
1514 if(fabsf(p->cw) == 1.0 && p->cx == 0.0 && fabsf(p->ch) == 1.0 && p->cy == 0.0)
1515 {
1516 p->ratio_d = -1;
1517 p->ratio_n = -1;
1518 }
1519 else
1520 {
1522 const float whratio = ((float)(iwd - 2 * interpolation->width) * (fabsf(p->cw) - p->cx))
1523 / ((float)(iht - 2 * interpolation->width) * (fabsf(p->ch) - p->cy));
1524 const float ri = (float)iwd / (float)iht;
1525
1526 const float prec = 0.0003f;
1527 if(fabsf(whratio - 3.0f / 2.0f) < prec)
1528 {
1529 p->ratio_d = 3;
1530 p->ratio_n = 2;
1531 }
1532 else if(fabsf(whratio - 2.0f / 1.0f) < prec)
1533 {
1534 p->ratio_d = 2;
1535 p->ratio_n = 1;
1536 }
1537 else if(fabsf(whratio - 7.0f / 5.0f) < prec)
1538 {
1539 p->ratio_d = 7;
1540 p->ratio_n = 5;
1541 }
1542 else if(fabsf(whratio - 4.0f / 3.0f) < prec)
1543 {
1544 p->ratio_d = 4;
1545 p->ratio_n = 3;
1546 }
1547 else if(fabsf(whratio - 5.0f / 4.0f) < prec)
1548 {
1549 p->ratio_d = 5;
1550 p->ratio_n = 4;
1551 }
1552 else if(fabsf(whratio - 1.0f / 1.0f) < prec)
1553 {
1554 p->ratio_d = 1;
1555 p->ratio_n = 1;
1556 }
1557 else if(fabsf(whratio - 16.0f / 9.0f) < prec)
1558 {
1559 p->ratio_d = 16;
1560 p->ratio_n = 9;
1561 }
1562 else if(fabsf(whratio - 16.0f / 10.0f) < prec)
1563 {
1564 p->ratio_d = 16;
1565 p->ratio_n = 10;
1566 }
1567 else if(fabsf(whratio - 244.5f / 203.2f) < prec)
1568 {
1569 p->ratio_d = 2445;
1570 p->ratio_n = 2032;
1571 }
1572 else if(fabsf(whratio - sqrtf(2.0f)) < prec)
1573 {
1574 p->ratio_d = 14142136;
1575 p->ratio_n = 10000000;
1576 }
1577 else if(fabsf(whratio - PHI) < prec)
1578 {
1579 p->ratio_d = 16180340;
1580 p->ratio_n = 10000000;
1581 }
1582 else if(fabsf(whratio - ri) < prec)
1583 {
1584 p->ratio_d = 1;
1585 p->ratio_n = 0;
1586 }
1587 else
1588 {
1589 p->ratio_d = 0;
1590 p->ratio_n = 0;
1591 }
1592 }
1593 }
1594
1595 if(p->ratio_d == 0 && p->ratio_n == 0) return -1.0f;
1596 float d = 1.0f, n = 1.0f;
1597 if(p->ratio_n == 0)
1598 {
1599 d = copysignf(iwd, p->ratio_d);
1600 n = iht;
1601 }
1602 else
1603 {
1604 d = p->ratio_d;
1605 n = p->ratio_n;
1606 }
1607
1608 // make aspect ratios like 3:2 and 2:3 to be the same thing
1609 const float dn = copysignf(MAX(fabsf(d), fabsf(n)), d);
1610 const float nn = copysignf(MIN(fabsf(d), fabsf(n)), n);
1611
1612 if(dn < 0)
1613 return -nn / dn;
1614 else
1615 return dn / nn;
1616}
1617
1619{
1621
1622 int iwd, iht;
1623 dt_dev_get_processed_size(self->dev, &iwd, &iht);
1624
1625 // enforce aspect ratio.
1626 float aspect = _ratio_get_aspect(self, g->aspect_presets);
1627
1628 // since one rarely changes between portrait and landscape by cropping,
1629 // long side of the crop box should match the long side of the image.
1630 if(iwd < iht) aspect = 1.0f / aspect;
1631
1632 if(aspect > 0)
1633 {
1634 // if only one side changed, force aspect by two adjacent in equal parts
1635 // 1 2 4 8 : x y w h
1636 double clip_x = MAX(iwd * g->clip_x / (float)iwd, 0.0f);
1637 double clip_y = MAX(iht * g->clip_y / (float)iht, 0.0f);
1638 double clip_w = MIN(iwd * g->clip_w / (float)iwd, 1.0f);
1639 double clip_h = MIN(iht * g->clip_h / (float)iht, 1.0f);
1640
1641 // if we only modified one dim, respectively, we wanted these values:
1642 const double target_h = (double)iwd * g->clip_w / ((double)iht * aspect);
1643 const double target_w = (double)iht * g->clip_h * aspect / (double)iwd;
1644 // i.e. target_w/h = w/target_h = aspect
1645 // first fix aspect ratio:
1646
1647 // corners: move two adjacent
1648 if(grab == GRAB_TOP_LEFT)
1649 {
1650 // move x y
1651 clip_x = clip_x + clip_w - (target_w + clip_w) * .5;
1652 clip_y = clip_y + clip_h - (target_h + clip_h) * .5;
1653 clip_w = (target_w + clip_w) * .5;
1654 clip_h = (target_h + clip_h) * .5;
1655 }
1656 else if(grab == GRAB_TOP_RIGHT) // move y w
1657 {
1658 clip_y = clip_y + clip_h - (target_h + clip_h) * .5;
1659 clip_w = (target_w + clip_w) * .5;
1660 clip_h = (target_h + clip_h) * .5;
1661 }
1662 else if(grab == GRAB_BOTTOM_RIGHT) // move w h
1663 {
1664 clip_w = (target_w + clip_w) * .5;
1665 clip_h = (target_h + clip_h) * .5;
1666 }
1667 else if(grab == GRAB_BOTTOM_LEFT) // move h x
1668 {
1669 clip_h = (target_h + clip_h) * .5;
1670 clip_x = clip_x + clip_w - (target_w + clip_w) * .5;
1671 clip_w = (target_w + clip_w) * .5;
1672 }
1673 else if(grab & GRAB_HORIZONTAL) // dragged either x or w (1 4)
1674 {
1675 // change h and move y, h equally
1676 const double off = target_h - clip_h;
1677 clip_h = clip_h + off;
1678 clip_y = clip_y - .5 * off;
1679 }
1680 else if(grab & GRAB_VERTICAL) // dragged either y or h (2 8)
1681 {
1682 // change w and move x, w equally
1683 const double off = target_w - clip_w;
1684 clip_w = clip_w + off;
1685 clip_x = clip_x - .5 * off;
1686 }
1687 // now fix outside boxes:
1688 if(clip_x < g->clip_max_x)
1689 {
1690 double prev_clip_h = clip_h;
1691 clip_h *= (clip_w + clip_x - g->clip_max_x) / clip_w;
1692 clip_w = clip_w + clip_x - g->clip_max_x;
1693 clip_x = g->clip_max_x;
1694 if(grab & GRAB_TOP) clip_y += prev_clip_h - clip_h;
1695 }
1696 if(clip_y < g->clip_max_y)
1697 {
1698 double prev_clip_w = clip_w;
1699 clip_w *= (clip_h + clip_y - g->clip_max_y) / clip_h;
1700 clip_h = clip_h + clip_y - g->clip_max_y;
1701 clip_y = g->clip_max_y;
1702 if(grab & GRAB_LEFT) clip_x += prev_clip_w - clip_w;
1703 }
1704 if(clip_x + clip_w > g->clip_max_x + g->clip_max_w)
1705 {
1706 double prev_clip_h = clip_h;
1707 clip_h *= (g->clip_max_x + g->clip_max_w - clip_x) / clip_w;
1708 clip_w = g->clip_max_x + g->clip_max_w - clip_x;
1709 if(grab & GRAB_TOP) clip_y += prev_clip_h - clip_h;
1710 }
1711 if(clip_y + clip_h > g->clip_max_y + g->clip_max_h)
1712 {
1713 double prev_clip_w = clip_w;
1714 clip_w *= (g->clip_max_y + g->clip_max_h - clip_y) / clip_h;
1715 clip_h = g->clip_max_y + g->clip_max_h - clip_y;
1716 if(grab & GRAB_LEFT) clip_x += prev_clip_w - clip_w;
1717 }
1718 g->clip_x = fmaxf(clip_x, 0.0f);
1719 g->clip_y = fmaxf(clip_y, 0.0f);
1720 g->clip_w = fminf(clip_w, 1.0f);
1721 g->clip_h = fminf(clip_h, 1.0f);
1722 }
1723}
1724
1726{
1727 const dt_image_t *img = &self->dev->image_storage;
1728
1730
1731 /* Same camera-framing default as the modern crop module, through the same shared helper so the
1732 * orientation mapping exists in exactly one place. This legacy module is deliberately never
1733 * auto-enabled from camera metadata -- only its reset target follows the tag. */
1734 dt_boundingbox_t box;
1736
1737 d->cx = box[1];
1738 d->cy = box[0];
1739 d->cw = box[3];
1740 d->ch = box[2];
1741}
1742
1743gboolean has_defaults(struct dt_iop_module_t *self)
1744{
1747 // p->ratio_d and p->ratio_n are inited in GUI, so they are unset in d.
1748 return d->cx == p->cx && d->cy == p->cy && d->cw == p->cw && d->ch && p->cw;
1749}
1750
1751static void _float_to_fract(const char *num, int *n, int *d)
1752{
1753 char tnum[100];
1754 gboolean sep_found = FALSE;
1755 char *p = (char *)num;
1756 int k = 0;
1757
1758 *d = 1;
1759
1760 while(*p)
1761 {
1762 if(sep_found) *d *= 10;
1763
1764 // look for decimal sep
1765 if(!sep_found && ((*p == ',') || (*p == '.')))
1766 {
1767 sep_found = TRUE;
1768 }
1769 else if (*p < '0' || *p > '9')
1770 {
1771 *n = *d = 0;
1772 return;
1773 }
1774 else
1775 {
1776 tnum[k++] = *p;
1777 }
1778
1779 p++;
1780 }
1781
1782 tnum[k] = '\0';
1783
1784 *n = atoi(tnum);
1785}
1786
1788{
1791 const int which = dt_bauhaus_combobox_get(combo);
1792 int d = abs(p->ratio_d), n = p->ratio_n;
1793 const char *text = dt_bauhaus_combobox_get_text(combo);
1794 if(which < 0)
1795 {
1796 if(!IS_NULL_PTR(text))
1797 {
1798 const char *c = text;
1799 const char *end = text + strlen(text);
1800 while(*c != ':' && *c != '/' && c < end) c++;
1801 if(c < end - 1)
1802 {
1803 // input the exact fraction
1804 c++;
1805 int dd = atoi(text);
1806 int nn = atoi(c);
1807 // some sanity check
1808 if(nn == 0 || dd == 0)
1809 {
1810 dt_control_log(_("invalid ratio format. it should be \"number:number\""));
1811 dt_bauhaus_combobox_set(combo, 0);
1812 return;
1813 }
1814 d = MAX(dd, nn);
1815 n = MIN(dd, nn);
1816 }
1817 else
1818 {
1819 // find the closest fraction from the input ratio
1820 int nn = 0, dd = 0;
1821 _float_to_fract(text, &nn, &dd);
1822
1823 // some sanity check
1824 if(dd == 0 || nn == 0)
1825 {
1826 dt_control_log(_("invalid ratio format. it should be a positive number"));
1827 dt_bauhaus_combobox_set(combo, 0);
1828 return;
1829 }
1830
1831 d = MAX(dd, nn);
1832 n = MIN(dd, nn);
1833 }
1834
1835 // simplify the fraction with binary GCD - https://en.wikipedia.org/wiki/Greatest_common_divisor
1836 // search g and d such that g is odd and gcd(nn, dd) = g x 2^d
1837 int e = 0;
1838 int nn = abs(n);
1839 int dd = abs(d);
1840 while((nn % 2 == 0) && (dd % 2 == 0))
1841 {
1842 nn /= 2;
1843 dd /= 2;
1844 e++;
1845 }
1846 while(nn != dd)
1847 {
1848 if(nn % 2 == 0) nn /= 2;
1849 else if(dd % 2 == 0) dd /= 2;
1850 else if(nn > dd) nn = (nn - dd) / 2;
1851 else dd = (dd - nn) / 2;
1852 }
1853
1854 // reduce the fraction with the GCD
1855 n /= (nn * 1 << e);
1856 d /= (nn * 1 << e);
1857 }
1858 }
1859 else
1860 {
1861 d = n = 0;
1862
1863 for(const GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
1864 {
1865 const dt_iop_clipping_aspect_t *aspect = iter->data;
1866 if(g_strcmp0(aspect->name, text) == 0)
1867 {
1868 d = aspect->d;
1869 n = aspect->n;
1870 break;
1871 }
1872 }
1873 }
1874
1875 // now we save all that if it has changed
1876 if(d != abs(p->ratio_d) || n != p->ratio_n)
1877 {
1878 if(p->ratio_d >= 0)
1879 p->ratio_d = d;
1880 else
1881 p->ratio_d = -d;
1882
1883 p->ratio_n = n;
1884 dt_conf_set_int("plugins/darkroom/clipping/ratio_d", abs(p->ratio_d));
1885 dt_conf_set_int("plugins/darkroom/clipping/ratio_n", abs(p->ratio_n));
1886 if(dt_gui_widgets_suppressed()) return;
1889 }
1890
1891 // Search if current aspect ratio matches something known
1892 int act = -1, i = 0;
1893
1894 for(const GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
1895 {
1896 const dt_iop_clipping_aspect_t *aspect = iter->data;
1897 if((aspect->d == d) && (aspect->n == n))
1898 {
1899 act = i;
1900 break;
1901 }
1902 i++;
1903 }
1904
1905 // Update combobox label
1907
1908 if(act == -1)
1909 {
1910 // we got a custom ratio
1911 char str[128];
1912 snprintf(str, sizeof(str), "%d:%d %2.2f",
1913 abs(p->ratio_d), abs(p->ratio_n), (float)abs(p->ratio_d) / (float)abs(p->ratio_n));
1914 dt_bauhaus_combobox_set_text(g->aspect_presets, str);
1915 }
1916 else if(dt_bauhaus_combobox_get(g->aspect_presets) != act)
1917 // we got a default ratio
1918 dt_bauhaus_combobox_set(g->aspect_presets, act);
1919
1921}
1922
1923void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1924{
1927
1929
1930 if(w == g->cx)
1931 {
1932 dt_bauhaus_slider_set_soft_min(g->cw, p->cx + 0.10);
1933 g->clip_w = g->clip_x + g->clip_w - p->cx;
1934 g->clip_x = p->cx;
1935 }
1936 else if(w == g->cw)
1937 {
1938 dt_bauhaus_slider_set_soft_max(g->cx, p->cw - 0.10);
1939 g->clip_w = p->cw - g->clip_x;
1940 }
1941 else if(w == g->cy)
1942 {
1943 dt_bauhaus_slider_set_soft_min(g->ch, p->cy + 0.10);
1944 g->clip_h = g->clip_y + g->clip_h - p->cy;
1945 g->clip_y = p->cy;
1946 }
1947 else if(w == g->ch)
1948 {
1949 dt_bauhaus_slider_set_soft_max(g->cy, p->ch - 0.10);
1950 g->clip_h = p->ch - g->clip_y;
1951 }
1952
1954
1955 commit_box(self, g, p);
1956
1957 if(w == g->crop_auto) dt_control_queue_redraw_center();
1958}
1959
1960void gui_reset(struct dt_iop_module_t *self)
1961{
1963 /* reset aspect preset to default */
1964 dt_conf_set_int("plugins/darkroom/clipping/ratio_d", 0);
1965 dt_conf_set_int("plugins/darkroom/clipping/ratio_n", 0);
1966 g->k_show = -1;
1967}
1968
1970{
1973 const int which = dt_bauhaus_combobox_get(combo);
1974 if((which == 5) || (which == 4 && p->k_h == 0 && p->k_v == 0))
1975 {
1976 // if the keystone is applied,autocrop must be disabled !
1977 gtk_widget_set_sensitive(g->crop_auto, FALSE);
1978 gtk_widget_set_sensitive(g->aspect_presets, TRUE);
1979 return;
1980 }
1981 // we recreate the list to be sure that the "already applied" entry is not display
1982 if(g->k_show == 2)
1983 {
1984 if(which == 0 || which == 4)
1985 g->k_show = 0;
1986 else
1987 g->k_show = 1;
1988 keystone_type_populate(self, FALSE, which);
1989 }
1990
1991 // we set the params
1992 p->k_apply = 0;
1993 p->k_type = which;
1994 if(which == 0 || which == 4)
1995 g->k_show = 0;
1996 else
1997 g->k_show = 1;
1998
1999 // we can enable autocrop
2000 gtk_widget_set_sensitive(g->crop_auto, (g->k_show == 0));
2001 gtk_widget_set_sensitive(g->aspect_presets, (g->k_show == 0));
2002
2003 commit_box(self, g, p);
2005}
2006
2007static void keystone_type_populate(struct dt_iop_module_t *self, gboolean with_applied, int select)
2008{
2011 dt_bauhaus_combobox_clear(g->keystone_type);
2012 dt_bauhaus_combobox_add(g->keystone_type, _("none"));
2013 dt_bauhaus_combobox_add(g->keystone_type, _("vertical"));
2014 dt_bauhaus_combobox_add(g->keystone_type, _("horizontal"));
2015 dt_bauhaus_combobox_add(g->keystone_type, _("full"));
2016 if(p->k_h != 0 || p->k_v != 0) dt_bauhaus_combobox_add(g->keystone_type, _("old system"));
2017 if(with_applied) dt_bauhaus_combobox_add(g->keystone_type, _("correction applied"));
2018
2019 if(select < 0) return;
2020 int sel = 0;
2021 if(select > 10 && p->k_h == 0 && p->k_v == 0)
2022 sel = 4;
2023 else if(select > 10)
2024 sel = 5;
2025 else
2026 sel = select;
2027
2028 dt_bauhaus_combobox_set(g->keystone_type, sel);
2029 // we have to be sure that the event is called...
2030 keystone_type_changed(g->keystone_type, self);
2031}
2032
2033void gui_update(struct dt_iop_module_t *self)
2034{
2037
2038 int hvflip = 0;
2039 if(p->cw < 0)
2040 {
2041 if(p->ch < 0)
2042 hvflip = 3; // BOTH
2043 else
2044 hvflip = 1; // HORIZONTAL
2045 }
2046 else
2047 {
2048 if(p->ch < 0)
2049 hvflip = 2; // VERTICAL
2050 else
2051 hvflip = 0; // NONE
2052 }
2053 dt_bauhaus_combobox_set(g->hvflip, hvflip);
2054
2055 // set aspect ratio based on the current image, if not found let's default
2056 // to free aspect.
2057
2058 if(p->ratio_d == -2 && p->ratio_n == -2) _ratio_get_aspect(self, g->aspect_presets);
2059
2060 if(p->ratio_d == -1 && p->ratio_n == -1)
2061 {
2062 p->ratio_d = dt_conf_get_int("plugins/darkroom/clipping/ratio_d");
2063 p->ratio_n = dt_conf_get_int("plugins/darkroom/clipping/ratio_n");
2064 }
2065
2066 const int d = abs(p->ratio_d), n = p->ratio_n;
2067
2068 int act = -1;
2069 int i = 0;
2070 for(const GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
2071 {
2072 const dt_iop_clipping_aspect_t *aspect = iter->data;
2073 if((aspect->d == d) && (aspect->n == n))
2074 {
2075 act = i;
2076 break;
2077 }
2078 i++;
2079 }
2080
2081 // keystone :
2082 if(p->k_apply == 1) g->k_show = 2; // needed to initialise correctly the combobox
2083 else g->k_show = -1;
2084
2085 if(g->k_show == 2)
2086 {
2087 keystone_type_populate(self, TRUE, 99);
2088 }
2089 else if(g->k_show == -1)
2090 {
2091 keystone_type_populate(self, FALSE, p->k_type);
2092 }
2093
2094 /* special handling the combobox when current act is already selected
2095 callback is not called, let do it our self then..
2096 */
2097 if(act == -1)
2098 {
2099 char str[128];
2100 snprintf(str, sizeof(str), "%d:%d %2.2f",
2101 abs(p->ratio_d), abs(p->ratio_n), (float)abs(p->ratio_d) / (float)abs(p->ratio_n));
2102 dt_bauhaus_combobox_set_text(g->aspect_presets, str);
2103 }
2104 if(dt_bauhaus_combobox_get(g->aspect_presets) == act)
2105 aspect_presets_changed(g->aspect_presets, self);
2106 else
2107 dt_bauhaus_combobox_set(g->aspect_presets, act);
2108
2109 // reset gui draw box to what we have in the parameters:
2110 g->applied = 1;
2111 g->clip_x = CLAMPF(p->cx, 0.0f, 0.9f);
2112 g->clip_y = CLAMPF(p->cy, 0.0f, 0.9f);
2113 g->clip_w = CLAMPF(fabsf(p->cw) - p->cx, 0.1f, 1.0f - g->clip_x);
2114 g->clip_h = CLAMPF(fabsf(p->ch) - p->cy, 0.1f, 1.0f - g->clip_y);
2115}
2116
2117static void hvflip_callback(GtkWidget *widget, dt_iop_module_t *self)
2118{
2119 if(dt_gui_widgets_suppressed()) return;
2122 const int flip = dt_bauhaus_combobox_get(widget);
2123 p->cw = copysignf(p->cw, (flip & 1) ? -1.0 : 1.0);
2124 p->ch = copysignf(p->ch, (flip & 2) ? -1.0 : 1.0);
2125 commit_box(self, g, p);
2126}
2127
2128static void key_swap_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2129 GdkModifierType modifier, gpointer d)
2130{
2131 (void)accel_group;
2132 (void)acceleratable;
2133 (void)keyval;
2134 (void)modifier;
2137 p->ratio_d = -p->ratio_d;
2140}
2141
2142static void aspect_flip(GtkWidget *button, dt_iop_module_t *self)
2143{
2144 key_swap_callback(NULL, NULL, 0, 0, self);
2145}
2146
2148{
2149 // want most square at the end, and the most non-square at the beginning
2150
2151 if((a->d == 0 || a->d == 1) && a->n == 0) return -1;
2152
2153 const float ad = MAX(a->d, a->n);
2154 const float an = MIN(a->d, a->n);
2155 const float bd = MAX(b->d, b->n);
2156 const float bn = MIN(b->d, b->n);
2157 const float aratio = ad / an;
2158 const float bratio = bd / bn;
2159
2160 if(aratio < bratio) return -1;
2161
2162 const float prec = 0.0003f;
2163 if(fabsf(aratio - bratio) < prec) return 0;
2164
2165 return 1;
2166}
2167
2168
2169static gchar *format_aspect(gchar *original, int adim, int bdim)
2170{
2171 // Special ratios: freehand, original image
2172 if(bdim == 0) return g_strdup(original);
2173
2174 return g_strdup_printf("%s %4.2f", original, (float)adim / (float)bdim);
2175}
2176
2177void gui_init(struct dt_iop_module_t *self)
2178{
2180
2181 g->aspect_list = NULL;
2182 g->clip_x = g->clip_y = g->handle_x = g->handle_y = 0.0;
2183 g->clip_w = g->clip_h = 1.0;
2184 g->clip_max_x = g->clip_max_y = 0.0;
2185 g->clip_max_w = g->clip_max_h = 1.0;
2186 g->clip_max_pipe_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2187 g->cropping = 0;
2188 g->straightening = 0;
2189 g->applied = 1;
2190 g->shift_hold = FALSE;
2191 g->ctrl_hold = FALSE;
2192 g->k_drag = FALSE;
2193 g->k_show = -1;
2194 g->k_selected = -1;
2195 g->preview_ready = FALSE;
2196
2197 g->notebook = dt_ui_notebook_new();
2198
2199 self->gui->widget = dt_ui_notebook_page(g->notebook, N_("main"), NULL);
2200
2202 dt_bauhaus_widget_set_label(g->hvflip, N_("flip"));
2203 dt_bauhaus_combobox_add(g->hvflip, _("none"));
2204 dt_bauhaus_combobox_add(g->hvflip, _("horizontal"));
2205 dt_bauhaus_combobox_add(g->hvflip, _("vertical"));
2206 dt_bauhaus_combobox_add(g->hvflip, _("both"));
2207 g_signal_connect(G_OBJECT(g->hvflip), "value-changed", G_CALLBACK(hvflip_callback), self);
2208 gtk_widget_set_tooltip_text(g->hvflip, _("mirror image horizontally and/or vertically"));
2209 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->hvflip, TRUE, TRUE, 0);
2210
2211 g->angle = dt_bauhaus_slider_from_params(self, N_("angle"));
2212 dt_bauhaus_slider_set_factor(g->angle, -1.0);
2213 dt_bauhaus_slider_set_format(g->angle, "\302\260");
2214 gtk_widget_set_tooltip_text(g->angle, _("right-click and drag a line on the image to drag a straight line"));
2215
2217 dt_bauhaus_widget_set_label(g->keystone_type, N_("keystone"));
2218 dt_bauhaus_combobox_add(g->keystone_type, _("none"));
2219 dt_bauhaus_combobox_add(g->keystone_type, _("vertical"));
2220 dt_bauhaus_combobox_add(g->keystone_type, _("horizontal"));
2221 dt_bauhaus_combobox_add(g->keystone_type, _("full"));
2222 gtk_widget_set_tooltip_text(g->keystone_type, _("set perspective correction for your image"));
2223 g_signal_connect(G_OBJECT(g->keystone_type), "value-changed", G_CALLBACK(keystone_type_changed), self);
2224 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->keystone_type, TRUE, TRUE, 0);
2225
2226 g->crop_auto = dt_bauhaus_combobox_from_params(self, "crop_auto");
2227 gtk_widget_set_tooltip_text(g->crop_auto, _("automatically crop to avoid black edges"));
2228
2229 dt_iop_clipping_aspect_t aspects[] = { { _("freehand"), 0, 0 },
2230 { _("original image"), 1, 0 },
2231 { _("square"), 1, 1 },
2232 { _("10:8 in print"), 2445, 2032 },
2233 { _("5:4, 4x5, 8x10"), 5, 4 },
2234 { _("11x14"), 14, 11 },
2235 { _("8.5x11, letter"), 110, 85 },
2236 { _("4:3, VGA, TV"), 4, 3 },
2237 { _("5x7"), 7, 5 },
2238 { _("ISO 216, DIN 476, A4"), 14142136, 10000000 },
2239 { _("3:2, 4x6, 35mm"), 3, 2 },
2240 { _("16:10, 8x5"), 16, 10 },
2241 { _("golden cut"), 16180340, 10000000 },
2242 { _("16:9, HDTV"), 16, 9 },
2243 { _("widescreen"), 185, 100 },
2244 { _("2:1, univisium"), 2, 1 },
2245 { _("cinemascope"), 235, 100 },
2246 { _("21:9"), 237, 100 },
2247 { _("anamorphic"), 239, 100 },
2248 { _("3:1, panorama"), 300, 100 },
2249 };
2250
2251 const int aspects_count = sizeof(aspects) / sizeof(dt_iop_clipping_aspect_t);
2252
2253 for(int i = 0; i < aspects_count; i++)
2254 {
2255 dt_iop_clipping_aspect_t *aspect = g_malloc(sizeof(dt_iop_clipping_aspect_t));
2256 aspect->name = format_aspect(aspects[i].name, aspects[i].d, aspects[i].n);
2257 aspect->d = aspects[i].d;
2258 aspect->n = aspects[i].n;
2259 g->aspect_list = g_list_append(g->aspect_list, aspect);
2260 }
2261
2262 // add custom presets from config to the list
2263 GSList *custom_aspects = dt_conf_all_string_entries("plugins/darkroom/clipping/extra_aspect_ratios");
2264 for(GSList *iter = custom_aspects; iter; iter = g_slist_next(iter))
2265 {
2267
2268 const char *c = nv->value;
2269 const char *end = nv->value + strlen(nv->value);
2270 while(*c != ':' && *c != '/' && c < end) c++;
2271 if(c < end - 1)
2272 {
2273 c++;
2274 int d = atoi(nv->value);
2275 int n = atoi(c);
2276 // some sanity check
2277 if(n == 0 || d == 0)
2278 {
2279 fprintf(stderr, "invalid ratio format for `%s'. it should be \"number:number\"\n", nv->key);
2280 dt_control_log(_("invalid ratio format for `%s'. it should be \"number:number\""), nv->key);
2281 continue;
2282 }
2283 dt_iop_clipping_aspect_t *aspect = g_malloc(sizeof(dt_iop_clipping_aspect_t));
2284 aspect->name = format_aspect(nv->key, d, n);
2285 aspect->d = d;
2286 aspect->n = n;
2287 g->aspect_list = g_list_append(g->aspect_list, aspect);
2288 }
2289 else
2290 {
2291 fprintf(stderr, "invalid ratio format for `%s'. it should be \"number:number\"\n", nv->key);
2292 dt_control_log(_("invalid ratio format for `%s'. it should be \"number:number\""), nv->key);
2293 continue;
2294 }
2295
2296 }
2297 g_slist_free_full(custom_aspects, dt_conf_string_entry_free);
2298
2299
2300 g->aspect_list = g_list_sort(g->aspect_list, (GCompareFunc)_aspect_ratio_cmp);
2301
2302 // remove duplicates from the aspect ratio list
2303 int d = ((dt_iop_clipping_aspect_t *)g->aspect_list->data)->d + 1,
2304 n = ((dt_iop_clipping_aspect_t *)g->aspect_list->data)->n + 1;
2305 for(GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
2306 {
2307 dt_iop_clipping_aspect_t *aspect = (dt_iop_clipping_aspect_t *)iter->data;
2308 int dd = MIN(aspect->d, aspect->n);
2309 int nn = MAX(aspect->d, aspect->n);
2310 if(dd == d && nn == n)
2311 {
2312 // same as the last one, remove this entry
2313 dt_free(aspect->name);
2314 GList *prev = g_list_previous(iter);
2315 g->aspect_list = g_list_delete_link(g->aspect_list, iter);
2316 // it should never be NULL as the 1st element can't be a duplicate, but better safe than sorry
2317 iter = prev ? prev : g->aspect_list;
2318 }
2319 else
2320 {
2321 d = dd;
2322 n = nn;
2323 }
2324 }
2325
2327 dt_bauhaus_combobox_set_editable(g->aspect_presets, 1);
2328 dt_bauhaus_widget_set_label(g->aspect_presets, N_("aspect"));
2329
2330 for(GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
2331 {
2332 const dt_iop_clipping_aspect_t *aspect = iter->data;
2333 dt_bauhaus_combobox_add(g->aspect_presets, aspect->name);
2334 }
2335
2336 dt_bauhaus_combobox_set(g->aspect_presets, 0);
2337
2338 g_signal_connect(G_OBJECT(g->aspect_presets), "value-changed", G_CALLBACK(aspect_presets_changed), self);
2339 gtk_widget_set_tooltip_text(g->aspect_presets, _("set the aspect ratio\n"
2340 "the list is sorted: from most square to least square\n"
2341 "to enter custom aspect ratio open the combobox and type ratio in x:y or decimal format"));
2343 g_signal_connect(G_OBJECT(g->aspect_presets), "quad-pressed", G_CALLBACK(aspect_flip), self);
2344 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->aspect_presets, TRUE, TRUE, 0);
2345
2346 self->gui->widget = dt_ui_notebook_page(g->notebook, _("margins"), NULL);
2347
2348 g->cx = dt_bauhaus_slider_from_params(self, "cx");
2351 gtk_widget_set_tooltip_text(g->cx, _("the left margin cannot overlap with the right margin"));
2352
2353 g->cw = dt_bauhaus_slider_from_params(self, "cw");
2355 dt_bauhaus_slider_set_factor(g->cw, -100.0);
2356 dt_bauhaus_slider_set_offset(g->cw, 100.0);
2358 gtk_widget_set_tooltip_text(g->cw, _("the right margin cannot overlap with the left margin"));
2359
2360 g->cy = dt_bauhaus_slider_from_params(self, "cy");
2363 gtk_widget_set_tooltip_text(g->cy, _("the top margin cannot overlap with the bottom margin"));
2364
2365 g->ch = dt_bauhaus_slider_from_params(self, "ch");
2367 dt_bauhaus_slider_set_factor(g->ch, -100.0);
2368 dt_bauhaus_slider_set_offset(g->ch, 100.0);
2370 gtk_widget_set_tooltip_text(g->ch, _("the bottom margin cannot overlap with the top margin"));
2371
2372 self->gui->widget = GTK_WIDGET(g->notebook);
2373}
2374
2375static void free_aspect(gpointer data)
2376{
2378 dt_free(aspect->name);
2379 dt_free(aspect);
2380}
2381
2383{
2385 g_list_free_full(g->aspect_list, free_aspect);
2386 g->aspect_list = NULL;
2387
2389}
2390
2391static _grab_region_t get_grab(float pzx, float pzy, dt_iop_clipping_gui_data_t *g, const float border,
2392 const float wd, const float ht)
2393{
2395 if(!(pzx < g->clip_x || pzx > g->clip_x + g->clip_w || pzy < g->clip_y || pzy > g->clip_y + g->clip_h))
2396 {
2397 // we are inside the crop box
2398 grab = GRAB_CENTER;
2399 if(pzx >= g->clip_x && pzx * wd < g->clip_x * wd + border) grab |= GRAB_LEFT; // left border
2400 if(pzy >= g->clip_y && pzy * ht < g->clip_y * ht + border) grab |= GRAB_TOP; // top border
2401 if(pzx <= g->clip_x + g->clip_w && pzx * wd > (g->clip_w + g->clip_x) * wd - border)
2402 grab |= GRAB_RIGHT; // right border
2403 if(pzy <= g->clip_y + g->clip_h && pzy * ht > (g->clip_h + g->clip_y) * ht - border)
2404 grab |= GRAB_BOTTOM; // bottom border
2405 }
2406 return grab;
2407}
2408
2409// draw symmetry signs
2410static void gui_draw_sym(cairo_t *cr, float x, float y, float scale, gboolean active)
2411{
2412 PangoLayout *layout;
2413 PangoRectangle ink;
2414 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
2415 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
2416 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(16) * PANGO_SCALE * scale);
2417 layout = pango_cairo_create_layout(cr);
2418 pango_layout_set_font_description(layout, desc);
2419 pango_layout_set_text(layout, "\352\235\217", -1);
2420 pango_layout_get_pixel_extents(layout, &ink, NULL);
2423 cr, ink.width + DT_PIXEL_APPLY_DPI(4) * scale, ink.height + DT_PIXEL_APPLY_DPI(8) * scale,
2424 x - ink.width / 2.0f - DT_PIXEL_APPLY_DPI(2) * scale, y - ink.height / 2.0f - DT_PIXEL_APPLY_DPI(4) * scale);
2425 cairo_move_to(cr, x - ink.width / 2.0f, y - 3.0 * ink.height / 4.0f - DT_PIXEL_APPLY_DPI(4) * scale);
2426 if(active)
2427 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, .9);
2428 else
2429 cairo_set_source_rgba(cr, .2, .2, .2, .9);
2430 pango_cairo_show_layout(cr, layout);
2431 pango_font_description_free(desc);
2432 g_object_unref(layout);
2433}
2434
2435// draw guides and handles over the image
2436void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
2437 int32_t pointerx, int32_t pointery)
2438{
2439 dt_develop_t *dev = self->dev;
2442 if(IS_NULL_PTR(g) || IS_NULL_PTR(p)) return;
2443
2444 // we don't do anything if the image is not ready
2445 if(!g->preview_ready) return;
2446
2447 // reapply box aspect to be sure that the ratio has not been modified by the keystone transform
2449
2450 const float wd = dt_dev_roi_request_preview_width(dev);
2451 const float ht = dt_dev_roi_request_preview_height(dev);
2452 const float zoom_scale = dt_dev_get_overlay_scale(dev);
2453
2454 cairo_translate(cr, width / 2.0, height / 2.0);
2455 cairo_scale(cr, zoom_scale, zoom_scale);
2456
2457 double dashes = DT_PIXEL_APPLY_DPI(5.0) / zoom_scale;
2458
2459 // draw cropping window
2460 float pzxpy[2] = { (float)pointerx, (float)pointery };
2462 const float pzx = pzxpy[0];
2463 const float pzy = pzxpy[1];
2465 {
2466 cairo_set_source_rgba(cr, .2, .2, .2, .8);
2467 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
2468 cairo_rectangle(cr, g->clip_max_x * wd - 1.f, g->clip_max_y * ht - 1.f,
2469 g->clip_max_w * wd + 2.0, g->clip_max_h * ht + 2.0);
2470 cairo_rectangle(cr, g->clip_x * wd, g->clip_y * ht, g->clip_w * wd, g->clip_h * ht);
2471 cairo_fill(cr);
2472 }
2473 if(g->clip_x > .0f || g->clip_y > .0f || g->clip_w < 1.0f || g->clip_h < 1.0f)
2474 {
2475 cairo_set_line_width(cr, dashes / 2.0);
2476 cairo_rectangle(cr, g->clip_x * wd, g->clip_y * ht, g->clip_w * wd, g->clip_h * ht);
2478 cairo_stroke(cr);
2479 }
2480
2481 // draw cropping window dimensions if first mouse button is pressed
2482 if(dt_control_button_down(1) && g->k_show != 1)
2483 {
2484 char dimensions[16];
2485 dimensions[0] = '\0';
2486 PangoLayout *layout;
2487 PangoRectangle ext;
2488 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
2489 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
2490 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(16) * PANGO_SCALE / zoom_scale);
2491 layout = pango_cairo_create_layout(cr);
2492 pango_layout_set_font_description(layout, desc);
2493
2494 int procw, proch;
2495 dt_dev_get_processed_size(dev, &procw, &proch);
2496 snprintf(dimensions, sizeof(dimensions), "%.0f x %.0f", (float)procw * g->clip_w, (float)proch * g->clip_h);
2497
2498 pango_layout_set_text(layout, dimensions, -1);
2499 pango_layout_get_pixel_extents(layout, NULL, &ext);
2500 const float text_w = ext.width;
2501 const float text_h = DT_PIXEL_APPLY_DPI(16+2) / zoom_scale;
2502 const float margin = DT_PIXEL_APPLY_DPI(6) / zoom_scale;
2503 float xp = (g->clip_x + g->clip_w * .5f) * wd - text_w * .5f;
2504 float yp = (g->clip_y + g->clip_h * .5f) * ht - text_h * .5f;
2505
2506 // ensure that the rendered string remains visible within the window bounds
2507 double x1, y1, x2, y2;
2508 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
2509 xp = CLAMPF(xp, x1 + 2.0 * margin, x2 - text_w - 2.0 * margin);
2510 yp = CLAMPF(yp, y1 + 2.0 * margin, y2 - text_h - 2.0 * margin);
2511
2512 cairo_set_source_rgba(cr, .5, .5, .5, .9);
2513 dt_gui_draw_rounded_rectangle(cr, text_w + 2 * margin, text_h + 2 * margin,
2514 xp - margin, yp - margin);
2515 cairo_set_source_rgb(cr, .7, .7, .7);
2516 cairo_move_to(cr, xp, yp);
2517 pango_cairo_show_layout(cr, layout);
2518 pango_font_description_free(desc);
2519 g_object_unref(layout);
2520 }
2521
2522 // draw crop area guides
2523 dt_guides_draw(cr, g->clip_x * wd, g->clip_y * ht, g->clip_w * wd, g->clip_h * ht, zoom_scale);
2524
2525 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
2527 const int border = DT_PIXEL_APPLY_DPI(30.0) / zoom_scale;
2528 if(g->straightening)
2529 {
2530 PangoRectangle ink;
2531 PangoLayout *layout;
2532 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
2533 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
2534 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(16) * PANGO_SCALE / zoom_scale);
2535 layout = pango_cairo_create_layout(cr);
2536 pango_layout_set_font_description(layout, desc);
2537 const float bzx = g->button_down_zoom_x + .5f, bzy = g->button_down_zoom_y + .5f;
2538 cairo_arc(cr, bzx * wd, bzy * ht, DT_PIXEL_APPLY_DPI(3), 0, 2.0 * M_PI);
2539 cairo_stroke(cr);
2540 cairo_arc(cr, pzx * wd, pzy * ht, DT_PIXEL_APPLY_DPI(3), 0, 2.0 * M_PI);
2541 cairo_stroke(cr);
2542 cairo_move_to(cr, bzx * wd, bzy * ht);
2543 cairo_line_to(cr, pzx * wd, pzy * ht);
2544 cairo_stroke(cr);
2545
2546 // show rotation angle
2547 float dx = pzx * wd - bzx * wd, dy = pzy * ht - bzy * ht;
2548 if(dx < 0)
2549 {
2550 dx = -dx;
2551 dy = -dy;
2552 }
2553 float angle = atan2f(dy, dx);
2554 angle = angle * 180 / M_PI;
2555 if(angle > 45.0) angle -= 90;
2556 if(angle < -45.0) angle += 90;
2557
2558 char view_angle[16];
2559 view_angle[0] = '\0';
2560 snprintf(view_angle, sizeof(view_angle), "%.2f\302\260", angle);
2561 pango_layout_set_text(layout, view_angle, -1);
2562 pango_layout_get_pixel_extents(layout, &ink, NULL);
2563 const float text_w = ink.width;
2564 const float text_h = DT_PIXEL_APPLY_DPI(16+2) / zoom_scale;
2565 const float margin = DT_PIXEL_APPLY_DPI(6) / zoom_scale;
2566 cairo_set_source_rgba(cr, .5, .5, .5, .9);
2567 const float xp = pzx * wd + DT_PIXEL_APPLY_DPI(20) / zoom_scale;
2568 const float yp = pzy * ht - ink.height;
2570 (cr, text_w + 2 * margin, text_h + 2 * margin, xp - margin, yp - margin);
2571 cairo_set_source_rgba(cr, .7, .7, .7, .7);
2572 cairo_move_to(cr, xp, yp);
2573 pango_cairo_show_layout(cr, layout);
2574 pango_font_description_free(desc);
2575 g_object_unref(layout);
2576 }
2577 else if(g->k_show != 1)
2578 {
2579 const _grab_region_t grab = g->cropping ? g->cropping : get_grab(pzx, pzy, g, border, wd, ht);
2580 if(grab == GRAB_LEFT) cairo_rectangle(cr, g->clip_x * wd, g->clip_y * ht, border, g->clip_h * ht);
2581 if(grab == GRAB_TOP) cairo_rectangle(cr, g->clip_x * wd, g->clip_y * ht, g->clip_w * wd, border);
2582 if(grab == GRAB_TOP_LEFT) cairo_rectangle(cr, g->clip_x * wd, g->clip_y * ht, border, border);
2583 if(grab == GRAB_RIGHT)
2584 cairo_rectangle(cr, (g->clip_x + g->clip_w) * wd - border, g->clip_y * ht, border, g->clip_h * ht);
2585 if(grab == GRAB_BOTTOM)
2586 cairo_rectangle(cr, g->clip_x * wd, (g->clip_y + g->clip_h) * ht - border, g->clip_w * wd, border);
2587 if(grab == GRAB_BOTTOM_RIGHT)
2588 cairo_rectangle(cr, (g->clip_x + g->clip_w) * wd - border, (g->clip_y + g->clip_h) * ht - border,
2589 border, border);
2590 if(grab == GRAB_TOP_RIGHT)
2591 cairo_rectangle(cr, (g->clip_x + g->clip_w) * wd - border, g->clip_y * ht, border, border);
2592 if(grab == GRAB_BOTTOM_LEFT)
2593 cairo_rectangle(cr, g->clip_x * wd, (g->clip_y + g->clip_h) * ht - border, border, border);
2594 cairo_stroke(cr);
2595 }
2596
2597 // draw keystone points and lines
2598 if(g->k_show == 1 && p->k_type > 0)
2599 {
2600 // points in screen space
2601 dt_iop_roi_t mod_out;
2602 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return;
2603
2604 const float wp = mod_out.width, hp = mod_out.height;
2605 float pts[8] = { p->kxa * wp, p->kya * hp, p->kxb * wp, p->kyb * hp,
2606 p->kxc * wp, p->kyc * hp, p->kxd * wp, p->kyd * hp };
2608 {
2609 if(p->k_type == 3)
2610 {
2611 // determine extremity of the lines
2612 const float v1t = pts[0] - (pts[6] - pts[0]) * pts[1] / (pts[7] - pts[1]);
2613 const float v1b = (pts[6] - pts[0]) * ht / (pts[7] - pts[1]) + v1t;
2614 const float v2t = pts[2] - (pts[4] - pts[2]) * pts[3] / (pts[5] - pts[3]);
2615 const float v2b = (pts[4] - pts[2]) * ht / (pts[5] - pts[3]) + v2t;
2616 const float h1l = pts[1] - (pts[3] - pts[1]) * pts[0] / (pts[2] - pts[0]);
2617 const float h1r = (pts[3] - pts[1]) * wd / (pts[2] - pts[0]) + h1l;
2618 const float h2l = pts[7] - (pts[5] - pts[7]) * pts[6] / (pts[4] - pts[6]);
2619 const float h2r = (pts[5] - pts[7]) * wd / (pts[4] - pts[6]) + h2l;
2620
2621 // draw the lines
2622 cairo_move_to(cr, v1t, 0);
2623 cairo_line_to(cr, v1b, ht);
2624 cairo_stroke(cr);
2625 cairo_move_to(cr, v2t, 0);
2626 cairo_line_to(cr, v2b, ht);
2627 cairo_stroke(cr);
2628 cairo_move_to(cr, 0, h1l);
2629 cairo_line_to(cr, wd, h1r);
2630 cairo_stroke(cr);
2631 cairo_move_to(cr, 0, h2l);
2632 cairo_line_to(cr, wd, h2r);
2633 cairo_stroke(cr);
2634 // redraw selected one
2635 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
2636 if(g->k_selected_segment == 0)
2637 {
2638 cairo_move_to(cr, pts[0], pts[1]);
2639 cairo_line_to(cr, pts[2], pts[3]);
2640 cairo_stroke(cr);
2641 }
2642 else if(g->k_selected_segment == 1)
2643 {
2644 cairo_move_to(cr, pts[4], pts[5]);
2645 cairo_line_to(cr, pts[2], pts[3]);
2646 cairo_stroke(cr);
2647 }
2648 else if(g->k_selected_segment == 2)
2649 {
2650 cairo_move_to(cr, pts[4], pts[5]);
2651 cairo_line_to(cr, pts[6], pts[7]);
2652 cairo_stroke(cr);
2653 }
2654 else if(g->k_selected_segment == 3)
2655 {
2656 cairo_move_to(cr, pts[0], pts[1]);
2657 cairo_line_to(cr, pts[6], pts[7]);
2658 cairo_stroke(cr);
2659 }
2660 }
2661 else if(p->k_type == 2)
2662 {
2663 // determine extremity of the lines
2664 const float h1l = pts[1] - (pts[3] - pts[1]) * pts[0] / (pts[2] - pts[0]);
2665 const float h1r = (pts[3] - pts[1]) * wd / (pts[2] - pts[0]) + h1l;
2666 const float h2l = pts[7] - (pts[5] - pts[7]) * pts[6] / (pts[4] - pts[6]);
2667 const float h2r = (pts[5] - pts[7]) * wd / (pts[4] - pts[6]) + h2l;
2668
2669 // draw the lines
2670 cairo_move_to(cr, 0, h1l);
2671 cairo_line_to(cr, wd, h1r);
2672 cairo_stroke(cr);
2673 cairo_move_to(cr, 0, h2l);
2674 cairo_line_to(cr, wd, h2r);
2675 cairo_stroke(cr);
2676 // redraw selected one
2677 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
2678 if(g->k_selected_segment == 1)
2679 {
2680 cairo_move_to(cr, pts[4], pts[5]);
2681 cairo_line_to(cr, pts[2], pts[3]);
2682 cairo_stroke(cr);
2683 }
2684 else if(g->k_selected_segment == 3)
2685 {
2686 cairo_move_to(cr, pts[0], pts[1]);
2687 cairo_line_to(cr, pts[6], pts[7]);
2688 cairo_stroke(cr);
2689 }
2690 }
2691 else if(p->k_type == 1)
2692 {
2693 // determine extremity of the lines
2694 const float v1t = pts[0] - (pts[6] - pts[0]) * pts[1] / (pts[7] - pts[1]);
2695 const float v1b = (pts[6] - pts[0]) * ht / (pts[7] - pts[1]) + v1t;
2696 const float v2t = pts[2] - (pts[4] - pts[2]) * pts[3] / (pts[5] - pts[3]);
2697 const float v2b = (pts[4] - pts[2]) * ht / (pts[5] - pts[3]) + v2t;
2698
2699 // draw the lines
2700 cairo_move_to(cr, v1t, 0);
2701 cairo_line_to(cr, v1b, ht);
2702 cairo_stroke(cr);
2703 cairo_move_to(cr, v2t, 0);
2704 cairo_line_to(cr, v2b, ht);
2705 cairo_stroke(cr);
2706 // redraw selected one
2707 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
2708 if(g->k_selected_segment == 0)
2709 {
2710 cairo_move_to(cr, pts[0], pts[1]);
2711 cairo_line_to(cr, pts[2], pts[3]);
2712 cairo_stroke(cr);
2713 }
2714 else if(g->k_selected_segment == 2)
2715 {
2716 cairo_move_to(cr, pts[4], pts[5]);
2717 cairo_line_to(cr, pts[6], pts[7]);
2718 cairo_stroke(cr);
2719 }
2720 }
2721
2722 // draw the points
2723 if(g->k_selected == 0) // point 1
2724 {
2725 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
2726 cairo_set_source_rgba(cr, 1.0, 0, 0, .8);
2727 }
2728 else
2729 {
2730 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
2731 cairo_set_source_rgba(cr, 1.0, 0, 0, .5);
2732 }
2733 cairo_arc(cr, pts[0], pts[1], DT_PIXEL_APPLY_DPI(5.0) / zoom_scale, 0, 2.0 * M_PI);
2734 cairo_stroke(cr);
2735 if(g->k_selected == 1) // point 2
2736 {
2737 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
2738 cairo_set_source_rgba(cr, 1.0, 0, 0, .8);
2739 }
2740 else
2741 {
2742 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
2743 cairo_set_source_rgba(cr, 1.0, 0, 0, .5);
2744 }
2745 cairo_arc(cr, pts[2], pts[3], DT_PIXEL_APPLY_DPI(5.0) / zoom_scale, 0, 2.0 * M_PI);
2746 cairo_stroke(cr);
2747 if(g->k_selected == 2) // point 3
2748 {
2749 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
2750 cairo_set_source_rgba(cr, 1.0, 0, 0, .8);
2751 }
2752 else
2753 {
2754 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
2755 cairo_set_source_rgba(cr, 1.0, 0, 0, .5);
2756 }
2757 cairo_arc(cr, pts[4], pts[5], DT_PIXEL_APPLY_DPI(5.0) / zoom_scale, 0, 2.0 * M_PI);
2758 cairo_stroke(cr);
2759 if(g->k_selected == 3) // point 4
2760 {
2761 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
2762 cairo_set_source_rgba(cr, 1.0, 0, 0, .8);
2763 }
2764 else
2765 {
2766 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
2767 cairo_set_source_rgba(cr, 1.0, 0, 0, .5);
2768 }
2769 cairo_arc(cr, pts[6], pts[7], DT_PIXEL_APPLY_DPI(5.0) / zoom_scale, 0, 2.0 * M_PI);
2770 cairo_stroke(cr);
2771 // draw the apply "button"
2772 PangoLayout *layout;
2773 PangoRectangle ink;
2774 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
2775 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
2776 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(16) * PANGO_SCALE);
2777 layout = pango_cairo_create_layout(cr);
2778 pango_layout_set_font_description(layout, desc);
2779 cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(16));
2780 pango_layout_set_text(layout, "ok", -1);
2781 pango_layout_get_pixel_extents(layout, &ink, NULL);
2782 int c[2] = { (MIN(pts[4], pts[2]) + MAX(pts[0], pts[6])) / 2.0f,
2783 (MIN(pts[5], pts[7]) + MAX(pts[1], pts[3])) / 2.0f };
2784 cairo_set_source_rgba(cr, .5, .5, .5, .9);
2786 ink.height + DT_PIXEL_APPLY_DPI(12),
2787 c[0] - ink.width / 2.0f - DT_PIXEL_APPLY_DPI(4),
2788 c[1] - ink.height / 2.0f - DT_PIXEL_APPLY_DPI(6));
2789 cairo_move_to(cr, c[0] - ink.width / 2.0, c[1] - 3.0 * ink.height / 4.0);
2791 pango_cairo_show_layout(cr, layout);
2792 pango_font_description_free(desc);
2793 g_object_unref(layout);
2794
2795 // draw the symmetry buttons
2796 gboolean sym = FALSE;
2797 if(p->k_type == 1 || p->k_type == 3)
2798 {
2799 if(p->k_sym == 1 || p->k_sym == 3) sym = TRUE;
2800 gui_draw_sym(cr, (pts[0] + pts[6]) / 2.0f, (pts[1] + pts[7]) / 2.0f, 1.f, sym);
2801 gui_draw_sym(cr, (pts[2] + pts[4]) / 2.0f, (pts[3] + pts[5]) / 2.0f, 1.f, sym);
2802 }
2803 if(p->k_type == 2 || p->k_type == 3)
2804 {
2805 sym = (p->k_sym >= 2);
2806 gui_draw_sym(cr, (pts[0] + pts[2]) / 2.0f, (pts[1] + pts[3]) / 2.0f, 1.f, sym);
2807 gui_draw_sym(cr, (pts[6] + pts[4]) / 2.0f, (pts[7] + pts[5]) / 2.0f, 1.f, sym);
2808 }
2809 }
2810 }
2811}
2812
2813// determine the distance between the segment [(xa,ya)(xb,yb)] and the point (xc,yc)
2814static float dist_seg(float xa, float ya, float xb, float yb, float xc, float yc)
2815{
2816 if(xa == xb && ya == yb) return (xc - xa) * (xc - xa) + (yc - ya) * (yc - ya);
2817
2818 const float sx = xb - xa;
2819 const float sy = yb - ya;
2820
2821 const float ux = xc - xa;
2822 const float uy = yc - ya;
2823
2824 const float dp = sx * ux + sy * uy;
2825 if(dp < 0) return (xc - xa) * (xc - xa) + (yc - ya) * (yc - ya);
2826
2827 const float sn2 = sx * sx + sy * sy;
2828 if(dp > sn2) return (xc - xb) * (xc - xb) + (yc - yb) * (yc - yb);
2829
2830 const float ah2 = dp * dp / sn2;
2831 const float un2 = ux * ux + uy * uy;
2832 return un2 - ah2;
2833}
2834
2835int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
2836{
2839 const dt_develop_t *dev = (const dt_develop_t *)self->dev;
2840
2841 // we don't do anything if the image is not ready
2842 if(!g->preview_ready) return 0;
2843
2844 const float wd = dt_dev_roi_request_preview_width(self->dev);
2845 const float ht = dt_dev_roi_request_preview_height(self->dev);
2846 const float zoom_scale = dt_dev_viewport_scaling(dev);
2847 float pzxpy[2] = { (float)x, (float)y };
2849 float pzx = pzxpy[0];
2850 float pzy = pzxpy[1];
2851
2853 _grab_region_t grab = get_grab(pzx, pzy, g, DT_PIXEL_APPLY_DPI(30.0) / zoom_scale, wd, ht);
2854
2855 if(dt_control_button_down(3) && g->k_show != 1)
2856 {
2857 // second mouse button, straighten activated:
2858 g->straightening = 1;
2859 dt_control_queue_cursor(GDK_CROSSHAIR);
2861 }
2862 else if(dt_control_button_down(1))
2863 {
2864 // case when we drag a point for keystone
2865 if(g->k_drag == TRUE && g->k_selected >= 0)
2866 {
2867 float pts[2] = { pzx * wd, pzy * ht };
2869 dt_iop_roi_t mod_out;
2870 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return FALSE;
2871 const float xx = pts[0] / (float)mod_out.width, yy = pts[1] / (float)mod_out.height;
2872 if(g->k_selected == 0)
2873 {
2874 if(p->k_sym == 1 || p->k_sym == 3)
2875 p->kxa = fminf(xx, (p->kxc + p->kxd - 0.01f) / 2.0f), p->kxb = p->kxc - p->kxa + p->kxd;
2876 else
2877 p->kxa = fminf(xx, p->kxb - 0.01f);
2878 if(p->k_sym > 1)
2879 p->kya = fminf(yy, (p->kyc + p->kyb - 0.01f) / 2.0f), p->kyd = p->kyc - p->kya + p->kyb;
2880 else
2881 p->kya = fminf(yy, p->kyd - 0.01f);
2882 }
2883 else if(g->k_selected == 1)
2884 {
2885 if(p->k_sym == 1 || p->k_sym == 3)
2886 p->kxb = fmaxf(xx, (p->kxc + p->kxd + 0.01f) / 2.0f), p->kxa = p->kxc - p->kxb + p->kxd;
2887 else
2888 p->kxb = fmaxf(xx, p->kxa + 0.01f);
2889 if(p->k_sym > 1)
2890 p->kyb = fminf(yy, (p->kya + p->kyd - 0.01f) / 2.0f), p->kyc = p->kya - p->kyb + p->kyd;
2891 else
2892 p->kyb = fminf(yy, p->kyc - 0.01f);
2893 }
2894 else if(g->k_selected == 2)
2895 {
2896 if(p->k_sym == 1 || p->k_sym == 3)
2897 p->kxc = fmaxf(xx, (p->kxa + p->kxb + 0.01f) / 2.0f), p->kxd = p->kxa - p->kxc + p->kxb;
2898 else
2899 p->kxc = fmaxf(xx, p->kxd + 0.01f);
2900 if(p->k_sym > 1)
2901 p->kyc = fmaxf(yy, (p->kya + p->kyd + 0.01f) / 2.0f), p->kyb = p->kya - p->kyc + p->kyd;
2902 else
2903 p->kyc = fmaxf(yy, p->kyb + 0.01f);
2904 }
2905 else if(g->k_selected == 3)
2906 {
2907 if(p->k_sym == 1 || p->k_sym == 3)
2908 p->kxd = fminf(xx, (p->kxa + p->kxb - 0.01f) / 2.0f), p->kxc = p->kxa - p->kxd + p->kxb;
2909 else
2910 p->kxd = fminf(xx, p->kxc - 0.01f);
2911 if(p->k_sym > 1)
2912 p->kyd = fmaxf(yy, (p->kyc + p->kyb + 0.01f) / 2.0f), p->kya = p->kyc - p->kyd + p->kyb;
2913 else
2914 p->kyd = fmaxf(yy, p->kya + 0.01f);
2915 }
2917 return 1;
2918 }
2919
2920 // case when we drag a segment for keystone
2921 if(g->k_drag == TRUE && g->k_selected_segment >= 0)
2922 {
2923 float decalx = pzx - g->button_down_zoom_x;
2924 float decaly = pzy - g->button_down_zoom_y;
2925 if(g->k_selected_segment == 0 && (p->k_type == 1 || p->k_type == 3))
2926 {
2927 decaly = fminf(decaly, p->kyd - p->kya);
2928 decaly = fminf(decaly, p->kyc - p->kyb);
2929 p->kxa += decalx;
2930 p->kya += decaly;
2931 p->kxb += decalx;
2932 p->kyb += decaly;
2933 }
2934 else if(g->k_selected_segment == 1 && (p->k_type == 2 || p->k_type == 3))
2935 {
2936 decalx = fmaxf(decalx, p->kxa - p->kxb);
2937 decalx = fmaxf(decalx, p->kxd - p->kxc);
2938 p->kxc += decalx;
2939 p->kyc += decaly;
2940 p->kxb += decalx;
2941 p->kyb += decaly;
2942 }
2943 else if(g->k_selected_segment == 2 && (p->k_type == 1 || p->k_type == 3))
2944 {
2945 decaly = fmaxf(decaly, p->kya - p->kyd);
2946 decaly = fmaxf(decaly, p->kyb - p->kyc);
2947 p->kxc += decalx;
2948 p->kyc += decaly;
2949 p->kxd += decalx;
2950 p->kyd += decaly;
2951 }
2952 else if(g->k_selected_segment == 3 && (p->k_type == 2 || p->k_type == 3))
2953 {
2954 decalx = fminf(decalx, p->kxb - p->kxa);
2955 decalx = fminf(decalx, p->kxc - p->kxd);
2956 p->kxa += decalx;
2957 p->kya += decaly;
2958 p->kxd += decalx;
2959 p->kyd += decaly;
2960 }
2961 g->button_down_zoom_x = pzx;
2962 g->button_down_zoom_y = pzy;
2964 return 1;
2965 }
2966 // draw a light gray frame, to show it's not stored yet:
2967 g->applied = 0;
2968 // first mouse button, adjust cropping frame, but what do we do?
2969 const float bzx = g->button_down_zoom_x + .5f, bzy = g->button_down_zoom_y + .5f;
2970 if(g->cropping == GRAB_CENTER && !g->straightening && g->k_show != 1)
2971 {
2972 g->cropping = grab;
2973 if(grab == GRAB_CENTER)
2974 {
2975 g->cropping = GRAB_ALL;
2976 g->handle_x = g->clip_x;
2977 g->handle_y = g->clip_y;
2978 }
2979 if(grab & GRAB_LEFT) g->handle_x = bzx - g->clip_x;
2980 if(grab & GRAB_TOP) g->handle_y = bzy - g->clip_y;
2981 if(grab & GRAB_RIGHT) g->handle_x = bzx - (g->clip_w + g->clip_x);
2982 if(grab & GRAB_BOTTOM) g->handle_y = bzy - (g->clip_h + g->clip_y);
2983 if(!grab && dt_control_button_down(3)) g->straightening = 1;
2984 }
2985 if(!g->straightening && dt_control_button_down(1) && g->k_show != 1)
2986 {
2987 grab = g->cropping;
2988
2989 if(grab == GRAB_ALL)
2990 {
2991 /* moving the crop window */
2992 if(!g->shift_hold)
2993 g->clip_x
2994 = fminf(g->clip_max_w + g->clip_max_x - g->clip_w, fmaxf(g->clip_max_x, g->handle_x + pzx - bzx));
2995
2996 if(!g->ctrl_hold)
2997 g->clip_y
2998 = fminf(g->clip_max_h + g->clip_max_y - g->clip_h, fmaxf(g->clip_max_y, g->handle_y + pzy - bzy));
2999 }
3000 else
3001 {
3002 /* changing the crop window */
3003 if(g->shift_hold)
3004 {
3005 /* the center is locked, scale crop radial with locked ratio */
3006 float xx = 0.0f;
3007 float yy = 0.0f;
3008 if(grab & GRAB_LEFT || grab & GRAB_RIGHT) xx = (grab & GRAB_LEFT) ? (pzx - bzx) : (bzx - pzx);
3009 if(grab & GRAB_TOP || grab & GRAB_BOTTOM) yy = (grab & GRAB_TOP) ? (pzy - bzy) : (bzy - pzy);
3010
3011 float ratio = fmaxf((g->prev_clip_w - 2.0f * xx) / g->prev_clip_w,
3012 (g->prev_clip_h - 2.0f * yy) / g->prev_clip_h);
3013
3014 // ensure we don't get too small crop size
3015 if(g->prev_clip_w * ratio < 0.1f) ratio = 0.1f / g->prev_clip_w;
3016 if(g->prev_clip_h * ratio < 0.1f) ratio = 0.1f / g->prev_clip_h;
3017
3018 // ensure we don't have too big crop size
3019 if(g->prev_clip_w * ratio > g->clip_max_w) ratio = g->clip_max_w / g->prev_clip_w;
3020 if(g->prev_clip_h * ratio > g->clip_max_h) ratio = g->clip_max_h / g->prev_clip_h;
3021
3022 // now that we are sure that the crop size is correct, we have to adjust top & left
3023 float nx = g->prev_clip_x - (g->prev_clip_w * ratio - g->prev_clip_w) / 2.0f;
3024 float ny = g->prev_clip_y - (g->prev_clip_h * ratio - g->prev_clip_h) / 2.0f;
3025 float nw = g->prev_clip_w * ratio;
3026 float nh = g->prev_clip_h * ratio;
3027
3028 // move crop area to the right if needed
3029 nx = fmaxf(nx, g->clip_max_x);
3030 // move crop area to the left if needed
3031 nx = fminf(nx, g->clip_max_w + g->clip_max_x - nw);
3032 // move crop area to the bottom if needed
3033 ny = fmaxf(ny, g->clip_max_y);
3034 // move crop area to the top if needed
3035 ny = fminf(ny, g->clip_max_h + g->clip_max_y - nh);
3036
3037 g->clip_x = nx;
3038 g->clip_y = ny;
3039 g->clip_w = nw;
3040 g->clip_h = nh;
3041 }
3042 else
3043 {
3044
3045 if(grab & GRAB_LEFT)
3046 {
3047 const float old_clip_x = g->clip_x;
3048 g->clip_x = fminf(fmaxf(g->clip_max_x, pzx - g->handle_x), g->clip_x + g->clip_w - 0.1f);
3049 g->clip_w = old_clip_x + g->clip_w - g->clip_x;
3050 }
3051 if(grab & GRAB_TOP)
3052 {
3053 const float old_clip_y = g->clip_y;
3054 g->clip_y = fminf(fmaxf(g->clip_max_y, pzy - g->handle_y), g->clip_y + g->clip_h - 0.1f);
3055 g->clip_h = old_clip_y + g->clip_h - g->clip_y;
3056 }
3057 if(grab & GRAB_RIGHT)
3058 g->clip_w = fmaxf(0.1f, fminf(g->clip_max_w + g->clip_max_x, pzx - g->clip_x - g->handle_x));
3059 if(grab & GRAB_BOTTOM)
3060 g->clip_h = fmaxf(0.1f, fminf(g->clip_max_h + g->clip_max_y, pzy - g->clip_y - g->handle_y));
3061 }
3062
3063 if(g->clip_x + g->clip_w > g->clip_max_w + g->clip_max_x)
3064 g->clip_w = g->clip_max_w + g->clip_max_x - g->clip_x;
3065 if(g->clip_y + g->clip_h > g->clip_max_h + g->clip_max_y)
3066 g->clip_h = g->clip_max_h + g->clip_max_y - g->clip_y;
3067 }
3068
3069 apply_box_aspect(self, grab);
3070 // we save crop params too
3071 float points[4]
3072 = { g->clip_x * wd, g->clip_y * ht, (g->clip_x + g->clip_w) * wd, (g->clip_y + g->clip_h) * ht };
3073
3075 {
3076 dt_iop_roi_t mod_out;
3077 if(dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out))
3078 {
3079 // only update the sliders, not the dt_iop_clipping_params_t structure, so that the call to
3080 // dt_control_queue_redraw_center below doesn't go rerun the pixelpipe because it thinks that
3081 // the image has changed when it actually hasn't, yet. The actual clipping parameters get set
3082 // from the sliders when the iop loses focus, at which time the final selected crop is applied.
3083
3085
3086 dt_bauhaus_slider_set(g->cx, g->clip_x);
3087 dt_bauhaus_slider_set_soft_min(g->cw, g->clip_x + 0.10);
3088 dt_bauhaus_slider_set(g->cy, g->clip_y);
3089 dt_bauhaus_slider_set_soft_min(g->ch, g->clip_y + 0.10);
3090 dt_bauhaus_slider_set(g->cw, g->clip_x + g->clip_w);
3091 dt_bauhaus_slider_set_soft_max(g->cx, g->clip_x + g->clip_w - 0.10);
3092 dt_bauhaus_slider_set(g->ch, g->clip_y + g->clip_h);
3093 dt_bauhaus_slider_set_soft_max(g->cy, g->clip_y + g->clip_h - 0.10);
3094
3096 }
3097 }
3098 }
3100 return 1;
3101 }
3102 else if(grab && g->k_show != 1)
3103 {
3104 // hover over active borders, no button pressed
3105 // change mouse pointer
3106 if(grab == GRAB_LEFT)
3107 dt_control_queue_cursor(GDK_LEFT_SIDE);
3108 else if(grab == GRAB_TOP)
3109 dt_control_queue_cursor(GDK_TOP_SIDE);
3110 else if(grab == GRAB_RIGHT)
3111 dt_control_queue_cursor(GDK_RIGHT_SIDE);
3112 else if(grab == GRAB_BOTTOM)
3113 dt_control_queue_cursor(GDK_BOTTOM_SIDE);
3114 else if(grab == GRAB_TOP_LEFT)
3115 dt_control_queue_cursor(GDK_TOP_LEFT_CORNER);
3116 else if(grab == GRAB_TOP_RIGHT)
3117 dt_control_queue_cursor(GDK_TOP_RIGHT_CORNER);
3118 else if(grab == GRAB_BOTTOM_RIGHT)
3119 dt_control_queue_cursor(GDK_BOTTOM_RIGHT_CORNER);
3120 else if(grab == GRAB_BOTTOM_LEFT)
3121 dt_control_queue_cursor(GDK_BOTTOM_LEFT_CORNER);
3122 else if(grab == GRAB_NONE)
3123 {
3124 dt_control_hinter_message(dt_control_get_global(), _("<b>commit</b>: double-click, <b>straighten</b>: right-drag"));
3125 dt_control_queue_cursor(GDK_LEFT_PTR);
3126 }
3127 if(grab != GRAB_NONE)
3128 dt_control_hinter_message(dt_control_get_global(), _("<b>resize</b>: drag, <b>keep aspect ratio</b>: shift+drag\n"
3129 "<b>straighten</b>: right-drag"));
3131 }
3132 else
3133 {
3134 // somewhere besides borders. maybe rotate?
3135 dt_control_queue_cursor(GDK_FLEUR);
3136 g->straightening = g->cropping = 0;
3137 // or maybe keystone
3138 // slightly adjust the size of keystone control area depending on the downsampling
3139 const float ext = DT_PIXEL_APPLY_DPI(0.005f) / zoom_scale;
3140 if(g->k_show == 1 && g->k_drag == FALSE)
3141 {
3142 float pts[2] = { pzx * wd, pzy * ht };
3144 dt_iop_roi_t mod_out;
3145 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return FALSE;
3146 float xx = pts[0] / (float)mod_out.width, yy = pts[1] / (float)mod_out.height;
3147 // are we near a keystone point ?
3148 g->k_selected = -1;
3149 g->k_selected_segment = -1;
3150 if(xx < p->kxa + ext && xx > p->kxa - ext && yy < p->kya + ext && yy > p->kya - ext) g->k_selected = 0;
3151 if(xx < p->kxb + ext && xx > p->kxb - ext && yy < p->kyb + ext && yy > p->kyb - ext) g->k_selected = 1;
3152 if(xx < p->kxc + ext && xx > p->kxc - ext && yy < p->kyc + ext && yy > p->kyc - ext) g->k_selected = 2;
3153 if(xx < p->kxd + ext && xx > p->kxd - ext && yy < p->kyd + ext && yy > p->kyd - ext) g->k_selected = 3;
3154 // or near a keystone segment
3155 if(g->k_selected < 0)
3156 {
3157 if(p->k_type == 1 || p->k_type == 3)
3158 {
3159 if(dist_seg(p->kxa, p->kya, p->kxb, p->kyb, xx, yy) < ext * ext)
3160 g->k_selected_segment = 0;
3161 else if(dist_seg(p->kxd, p->kyd, p->kxc, p->kyc, xx, yy) < ext * ext)
3162 g->k_selected_segment = 2;
3163 if(dist_seg(p->kxb, p->kyb, p->kxc, p->kyc, xx, yy) < ext * ext)
3164 g->k_selected_segment = 1;
3165 else if(dist_seg(p->kxd, p->kyd, p->kxa, p->kya, xx, yy) < ext * ext)
3166 g->k_selected_segment = 3;
3167 }
3168 }
3169 if(g->k_selected >= 0)
3170 {
3171 dt_control_hinter_message(dt_control_get_global(), _("<b>move control point</b>: drag"));
3172 dt_control_queue_cursor(GDK_CROSS);
3173 }
3174 else if(g->k_selected_segment >= 0)
3175 {
3176 dt_control_hinter_message(dt_control_get_global(), _("<b>move line</b>: drag, <b>toggle symmetry</b>: click <tt>\352\235\217</tt>"));
3177 dt_control_queue_cursor(GDK_CROSS);
3178 }
3179 else
3180 {
3181 dt_control_hinter_message(dt_control_get_global(), _("<b>apply</b>: click <tt>ok</tt>, <b>toggle symmetry</b>: click <tt>\352\235\217</tt>\n"
3182 "<b>move line/control point</b>: drag"));
3183 dt_control_queue_cursor(GDK_FLEUR);
3184 }
3185 }
3186 else
3187 {
3188 dt_control_hinter_message(dt_control_get_global(), _("<b>move</b>: drag, <b>move vertically</b>: shift+drag, <b>move horizontally</b>: ctrl+drag\n"
3189 "<b>straighten</b>: right-drag, <b>commit</b>: double-click"));
3190 }
3192 }
3193 return 0;
3194}
3195
3197{
3198 if(dt_gui_widgets_suppressed()) return;
3199 g->cropping = 0;
3200 const dt_boundingbox_t old = { p->cx, p->cy, p->cw, p->ch };
3201 const float eps = 1e-6f; // threshold to avoid rounding errors
3202
3203 if(!self->enabled)
3204 {
3205 // first time crop, if any data is stored in p, it's obsolete:
3206 p->cx = p->cy = 0.0f;
3207 p->cw = p->ch = 1.0f;
3208 }
3209 // we want value in iop space
3210 const float wd = dt_dev_roi_request_preview_width(self->dev);
3211 const float ht = dt_dev_roi_request_preview_height(self->dev);
3212 float points[4]
3213 = { g->clip_x * wd, g->clip_y * ht, (g->clip_x + g->clip_w) * wd, (g->clip_y + g->clip_h) * ht };
3215 {
3216 dt_iop_roi_t mod_out;
3217 if(dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out))
3218 {
3219 p->cx = CLAMPF(points[0] / (float)mod_out.width, 0.0f, 0.9f);
3220 p->cy = CLAMPF(points[1] / (float)mod_out.height, 0.0f, 0.9f);
3221 p->cw = copysignf(CLAMPF(points[2] / (float)mod_out.width, 0.1f, 1.0f), p->cw);
3222 p->ch = copysignf(CLAMPF(points[3] / (float)mod_out.height, 0.1f, 1.0f), p->ch);
3223 }
3224 }
3225 g->applied = 1;
3226 const gboolean changed = fabs(p->cx - old[0]) > eps || fabs(p->cy - old[1]) > eps || fabs(p->cw - old[2]) > eps || fabs(p->ch - old[3]) > eps;
3227 // fprintf(stderr, "[crop commit box] %i: %e %e %e %e\n", changed, p->cx - old[0], p->cy - old[1], p->cw - old[2], p->ch - old[3]);
3228 if(changed) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
3229}
3230
3231int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
3232{
3234 // we don't do anything if the image is not ready
3235 if(!g->preview_ready) return 0;
3236
3237 if(g->straightening)
3238 {
3239 // adjust the line with possible current angle and flip on this module
3240 dt_boundingbox_t pts = { x, y, g->button_down_x, g->button_down_y };
3242
3243 float dx = pts[0] - pts[2];
3244 float dy = pts[1] - pts[3];
3245 if(dx < 0)
3246 {
3247 dx = -dx;
3248 dy = -dy;
3249 }
3250
3251 float angle = atan2f(dy, dx);
3252 if(!(angle >= -M_PI / 2.0 && angle <= M_PI / 2.0)) angle = 0.0f;
3253 float close = angle;
3254 if(close > M_PI / 4.0)
3255 close = M_PI / 2.0 - close;
3256 else if(close < -M_PI / 4.0)
3257 close = -M_PI / 2.0 - close;
3258 else
3259 close = -close;
3260
3261 float a = 180.0 / M_PI * close;
3262 if(a < -180.0) a += 360.0;
3263 if(a > 180.0) a -= 360.0;
3264
3265 dt_bauhaus_slider_set(g->angle, a);
3266 dt_control_queue_cursor(GDK_LEFT_PTR);
3267 }
3268 if(g->k_drag) g->k_drag = FALSE;
3269
3270 /* reset internal ui states*/
3271 g->straightening = g->cropping = 0;
3272 g->shift_hold = FALSE;
3273 g->ctrl_hold = FALSE;
3274 return 1;
3275}
3276
3277int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
3278 uint32_t state)
3279{
3280
3283 dt_develop_t *dev = self->dev;
3284
3285 // we don't do anything if the image is not ready
3286 if(!g->preview_ready) return 0;
3287
3288 // avoid unexpected back to lt mode:
3289 if(type == GDK_2BUTTON_PRESS && which == 1)
3290 {
3292 commit_box(self, g, p);
3293 return 1;
3294 }
3295 if(which == 3 || which == 1)
3296 {
3297 // switch module on already, other code depends in this:
3298 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
3299
3300 if(g->k_show == 1)
3301 {
3302 if(g->k_selected >= 0)
3303 g->k_drag = TRUE; // if a keystone point is selected then we start to drag it
3304 else // if we click to the apply button
3305 {
3306 const float zoom_scale = dt_dev_viewport_scaling(dev);
3307 float pzxpy[2] = { (float)x, (float)y };
3309 float pzx = pzxpy[0];
3310 float pzy = pzxpy[1];
3311
3312 dt_iop_roi_t mod_out;
3313 if(!dt_dev_module_geometry_gui(dev, self, NULL, &mod_out)) return 0;
3314 const float wp = mod_out.width, hp = mod_out.height;
3315 float pts[8] = { p->kxa * wp, p->kya * hp, p->kxb * wp, p->kyb * hp,
3316 p->kxc * wp, p->kyc * hp, p->kxd * wp, p->kyd * hp };
3318
3319 float point[2] = { pzx, pzy };
3321 const float xx = point[0];
3322 const float yy = point[1];
3323 float c[2] = { (MIN(pts[4], pts[2]) + MAX(pts[0], pts[6])) / 2.0f,
3324 (MIN(pts[5], pts[7]) + MAX(pts[1], pts[3])) / 2.0f };
3325 const float ext = DT_PIXEL_APPLY_DPI(10.0) / (zoom_scale);
3326 // Apply button
3327 if(xx > c[0] - ext && xx < c[0] + ext && yy > c[1] - ext && yy < c[1] + ext)
3328 {
3329 // add an entry to the combo box and select it
3330 keystone_type_populate(self, TRUE, 99);
3331 // reset gui settings
3332 g->k_show = 2;
3333 g->k_selected = -1;
3334 g->k_drag = FALSE;
3335 // do the changes
3336 p->k_apply = 1;
3337 commit_box(self, g, p);
3338 }
3339 else
3340 {
3341 // Horizontal symmetry button (1)
3342 c[0] = (pts[0] + pts[6]) / 2.0f, c[1] = (pts[1] + pts[7]) / 2.0f;
3343 if(xx > c[0] - ext && xx < c[0] + ext && yy > c[1] - ext && yy < c[1] + ext
3344 && (p->k_type == 1 || p->k_type == 3))
3345 {
3346 if(p->k_sym == 0)
3347 p->k_sym = 1;
3348 else if(p->k_sym == 1)
3349 p->k_sym = 0;
3350 else if(p->k_sym == 2)
3351 p->k_sym = 3;
3352 else
3353 p->k_sym = 2;
3354 }
3355 else
3356 {
3357 // Horizontal symmetry button (2)
3358 c[0] = (pts[2] + pts[4]) / 2.0f, c[1] = (pts[3] + pts[5]) / 2.0f;
3359 if(xx > c[0] - ext && xx < c[0] + ext && yy > c[1] - ext && yy < c[1] + ext
3360 && (p->k_type == 1 || p->k_type == 3))
3361 {
3362 if(p->k_sym == 0)
3363 p->k_sym = 1;
3364 else if(p->k_sym == 1)
3365 p->k_sym = 0;
3366 else if(p->k_sym == 2)
3367 p->k_sym = 3;
3368 else
3369 p->k_sym = 2;
3370 }
3371 else
3372 {
3373 // vertical symmetry button (1)
3374 c[0] = (pts[2] + pts[0]) / 2.0f, c[1] = (pts[3] + pts[1]) / 2.0f;
3375 if(xx > c[0] - ext && xx < c[0] + ext && yy > c[1] - ext && yy < c[1] + ext
3376 && (p->k_type == 2 || p->k_type == 3))
3377 {
3378 if(p->k_sym == 0)
3379 p->k_sym = 2;
3380 else if(p->k_sym == 1)
3381 p->k_sym = 3;
3382 else if(p->k_sym == 2)
3383 p->k_sym = 0;
3384 else
3385 p->k_sym = 1;
3386 }
3387 else
3388 {
3389 // vertical symmetry button (2)
3390 c[0] = (pts[4] + pts[6]) / 2.0f, c[1] = (pts[5] + pts[7]) / 2.0f;
3391 if(xx > c[0] - ext && xx < c[0] + ext && yy > c[1] - ext && yy < c[1] + ext
3392 && (p->k_type == 2 || p->k_type == 3))
3393 {
3394 if(p->k_sym == 0)
3395 p->k_sym = 2;
3396 else if(p->k_sym == 1)
3397 p->k_sym = 3;
3398 else if(p->k_sym == 2)
3399 p->k_sym = 0;
3400 else
3401 p->k_sym = 1;
3402 }
3403 else
3404 {
3405 // dragging a border ?
3406 if(g->k_selected_segment >= 0)
3407 {
3408 float border_pzxpy[2] = { (float)x, (float)y };
3409 dt_dev_coordinates_widget_to_image_norm(dev, border_pzxpy, 1);
3410 pzx = border_pzxpy[0];
3411 pzy = border_pzxpy[1];
3412 g->button_down_zoom_x += 0.5;
3413 g->button_down_zoom_y += 0.5;
3414 g->k_drag = TRUE;
3415 }
3416 }
3417 }
3418 }
3419 }
3420 }
3421 }
3422 }
3423 else
3424 {
3425 float button_down_xy[2] = { (float)x, (float)y };
3426 dt_dev_coordinates_widget_to_image_norm(self->dev, button_down_xy, 1);
3427 g->button_down_x = button_down_xy[0];
3428 g->button_down_y = button_down_xy[1];
3429 g->button_down_angle = p->angle;
3430
3431 /* update prev clip box with current */
3432 g->prev_clip_x = g->clip_x;
3433 g->prev_clip_y = g->clip_y;
3434 g->prev_clip_w = g->clip_w;
3435 g->prev_clip_h = g->clip_h;
3436
3437 /* if shift is pressed, then lock crop on center */
3438 if(dt_modifiers_include(state, GDK_SHIFT_MASK)) g->shift_hold = TRUE;
3439 if(dt_modifiers_include(state, DT_PRIMARY_MASK)) g->ctrl_hold = TRUE;
3440 }
3441
3442 return 1;
3443 }
3444 else
3445 return 0;
3446}
3447
3448#undef PHI
3449#undef INVPHI
3450
3451// clang-format off
3452// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3453// vim: shiftwidth=2 expandtab tabstop=2 cindent
3454// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3455// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
void dt_bauhaus_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:1997
void dt_bauhaus_combobox_set_editable(GtkWidget *widget, int editable)
Definition bauhaus.c:1882
void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2017
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_slider_set_offset(GtkWidget *widget, float offset)
Definition bauhaus.c:3430
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1474
void dt_bauhaus_slider_set_soft_min(GtkWidget *widget, float val)
Definition bauhaus.c:1457
const char * dt_bauhaus_combobox_get_text(GtkWidget *widget)
Definition bauhaus.c:1970
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1554
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3423
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
int operation_tags()
Definition clipping.c:389
int operation_tags_filter()
Definition clipping.c:394
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition clipping.c:1271
static void commit_box(dt_iop_module_t *self, dt_iop_clipping_gui_data_t *g, dt_iop_clipping_params_t *p)
Definition clipping.c:3196
void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const float *const in, float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition clipping.c:628
static _grab_region_t get_grab(float pzx, float pzy, dt_iop_clipping_gui_data_t *g, const float border, const float wd, const float ht)
Definition clipping.c:2391
static int _iop_clipping_set_max_clip(struct dt_iop_module_t *self)
Definition clipping.c:693
const char ** description(struct dt_iop_module_t *self)
Definition clipping.c:369
int default_group()
Definition clipping.c:378
int distort_backtransform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count)
Definition clipping.c:563
static void free_aspect(gpointer data)
Definition clipping.c:2375
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition clipping.c:1016
static gchar * format_aspect(gchar *original, int adim, int bdim)
Definition clipping.c:2169
static void keystone_backtransform(float *i, const dt_boundingbox_t k_space, float a, float b, float d, float e, float g, float h, float kxa, float kya)
Definition clipping.c:444
static float _ratio_get_aspect(dt_iop_module_t *self, GtkWidget *combo)
Definition clipping.c:1486
static gint _aspect_ratio_cmp(const dt_iop_clipping_aspect_t *a, const dt_iop_clipping_aspect_t *b)
Definition clipping.c:2147
static void _clipping_derive(dt_iop_clipping_data_t *d, const dt_iop_roi_t *const roi_in_orig, dt_iop_roi_t *roi_out)
Derive this module's whole geometric state, and the output rect, from the committed parameters and th...
Definition clipping.c:746
static int _clipping_geometry_transform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition clipping.c:1375
static void _event_preview_updated_callback(gpointer instance, dt_iop_module_t *self)
Definition clipping.c:1277
static void hvflip_callback(GtkWidget *widget, dt_iop_module_t *self)
Definition clipping.c:2117
static const dt_geometry_vtable_t _clipping_geometry_vtable
Definition clipping.c:1455
int distort_transform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count)
Definition clipping.c:499
static void _float_to_fract(const char *num, int *n, int *d)
Definition clipping.c:1751
static void _clipping_resolve(struct dt_iop_module_t *self, const dt_iop_clipping_params_t *const p, dt_iop_clipping_data_t *d)
Parameters -> committed data. THE constructor.
Definition clipping.c:1105
static void get_corner(const float *aabb, const int i, float *p)
Definition clipping.c:341
const char * aliases()
Definition clipping.c:364
static void backtransform(float *x, float *o, const float *m, const float t_h, const float t_v)
Definition clipping.c:469
static void apply_box_aspect(dt_iop_module_t *self, _grab_region_t grab)
Definition clipping.c:1618
static void _clipping_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
Definition clipping.c:1352
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition clipping.c:1286
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition clipping.c:1474
static int _clipping_geometry_backtransform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition clipping.c:1414
static void keystone_transform(float *i, const dt_boundingbox_t k_space, float a, float b, float d, float e, float g, float h, float kxa, float kya)
Definition clipping.c:457
const char * name()
Definition clipping.c:359
static void _clipping_keystone(const dt_iop_clipping_data_t *const d, const float rx, const float ry, dt_boundingbox_t k_space, float *kxa, float *kya, float *ma, float *mb, float *md, float *me, float *mg, float *mh)
The keystone matrix and the scaled quadrilateral, which both directions need.
Definition clipping.c:1359
void gui_reset(struct dt_iop_module_t *self)
Definition clipping.c:1960
void gui_update(struct dt_iop_module_t *self)
Definition clipping.c:2033
void gui_init(struct dt_iop_module_t *self)
Definition clipping.c:2177
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition clipping.c:3277
static void aspect_presets_changed(GtkWidget *combo, dt_iop_module_t *self)
Definition clipping.c:1787
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition clipping.c:1923
int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
Definition clipping.c:3231
static int gui_has_focus(struct dt_iop_module_t *self)
Definition clipping.c:405
static void keystone_type_populate(struct dt_iop_module_t *self, gboolean with_applied, int select)
Definition clipping.c:2007
dt_iop_clipping_flags_t
Definition clipping.c:112
@ FLAG_FLIP_VERTICAL
Definition clipping.c:114
@ FLAG_FLIP_HORIZONTAL
Definition clipping.c:113
void reload_defaults(dt_iop_module_t *self)
Definition clipping.c:1725
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition clipping.c:400
int flags()
Definition clipping.c:383
void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition clipping.c:2436
void gui_cleanup(struct dt_iop_module_t *self)
Definition clipping.c:2382
static void keystone_type_changed(GtkWidget *combo, dt_iop_module_t *self)
Definition clipping.c:1969
static void adjust_aabb(const float *p, float *aabb)
Definition clipping.c:346
static float dist_seg(float xa, float ya, float xb, float yb, float xc, float yc)
Definition clipping.c:2814
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition clipping.c:1480
static void aspect_flip(GtkWidget *button, dt_iop_module_t *self)
Definition clipping.c:2142
void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *roi_in_orig)
Definition clipping.c:931
gboolean geometry_record(struct dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
Definition clipping.c:1461
static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
Definition clipping.c:487
gboolean has_defaults(struct dt_iop_module_t *self)
Definition clipping.c:1743
const char * deprecated_msg()
Definition clipping.c:354
int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
Definition clipping.c:2835
_grab_region_t
Definition clipping.c:147
@ GRAB_VERTICAL
Definition clipping.c:158
@ GRAB_ALL
Definition clipping.c:159
@ GRAB_BOTTOM
Definition clipping.c:152
@ GRAB_BOTTOM_RIGHT
Definition clipping.c:155
@ GRAB_NONE
Definition clipping.c:160
@ GRAB_HORIZONTAL
Definition clipping.c:157
@ GRAB_TOP_RIGHT
Definition clipping.c:154
@ GRAB_TOP_LEFT
Definition clipping.c:153
@ GRAB_RIGHT
Definition clipping.c:151
@ GRAB_TOP
Definition clipping.c:150
@ GRAB_BOTTOM_LEFT
Definition clipping.c:156
@ GRAB_LEFT
Definition clipping.c:149
@ GRAB_CENTER
Definition clipping.c:148
static void gui_draw_sym(cairo_t *cr, float x, float y, float scale, gboolean active)
Definition clipping.c:2410
static void inv_matrix(float *m, float *inv_m)
Definition clipping.c:477
void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
Definition clipping.c:939
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition clipping.c:166
static void key_swap_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer d)
Definition clipping.c:2128
@ IOP_CS_RGB
static const float x
const float f
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_conf_string_entry_free(gpointer data)
Free one dt_conf_string_entry_t. Shaped as a GDestroyNotify so it can be handed to g_slist_free_full(...
void dt_conf_set_int(const char *name, int val)
GSList * dt_conf_all_string_entries(const char *dir)
Every key under dir, as a list of copies.
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
gboolean dt_image_get_usercrop_oriented(const dt_image_t *img, const dt_image_orientation_t orientation, dt_boundingbox_t box)
dt_image_orientation_t dt_image_get_effective_orientation(const dt_image_t *img)
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:981
gboolean dt_control_button_down(int which)
Is mouse button which currently held down?
Definition control.c:563
#define dt_control_queue_cursor(cursor)
Definition control.h:140
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_resync_history_all(dev)
int32_t dt_dev_roi_request_preview_height(const dt_develop_t *dev)
int32_t dt_dev_roi_request_preview_width(const dt_develop_t *dev)
float dt_dev_viewport_scaling(const dt_develop_t *dev)
void dt_dev_get_processed_size(const dt_develop_t *dev, int *procw, int *proch)
Definition develop.c:1109
void dt_dev_coordinates_preview_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1306
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:353
int dt_dev_distort_backtransform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The inverse of dt_dev_distort_transform_gui(), same rules.
Definition develop.c:1699
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1959
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1291
gboolean dt_dev_module_geometry_gui(dt_develop_t *dev, dt_iop_module_t *module, dt_iop_roi_t *in, dt_iop_roi_t *out)
One module's own input and output rectangles at full resolution, from the geometry service.
Definition develop.c:1751
int dt_dev_distort_transform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The GUI's bounded transform folds, composed by the geometry service.
Definition develop.c:1691
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:402
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1144
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:108
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:147
static void dt_gui_draw_rounded_rectangle(cairo_t *cr, float width, float height, float x, float y)
Definition draw.h:554
Where things are on the image, answered without a pipeline.
#define DT_GUI_MODULE(x)
void dt_guides_draw(cairo_t *cr, const float left, const float top, const float width, const float height, const float zoom_scale)
Definition guides.c:800
float dt_boundingbox_t[4]
Definition image.h:83
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1669
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
@ IOP_FLAGS_TILING_FULL_ROI
Definition imageop.h:190
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
@ IOP_TAG_DECORATION
Definition imageop.h:171
@ IOP_TAG_CLIPPING
Definition imageop.h:172
@ IOP_TAG_DISTORT
Definition imageop.h:170
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
Which mouse buttons are held, asked of the toolkit rather than remembered.
const struct dt_interpolation * dt_interpolation_new(enum dt_interpolation_type type)
__DT_CLONE_TARGETS__ void dt_interpolation_compute_pixel4c(const struct dt_interpolation *itor, const float *in, float *out, const float x, const float y, const int width, const int height, const int linestride)
__DT_CLONE_TARGETS__ float dt_interpolation_compute_sample(const struct dt_interpolation *itor, const float *in, const float x, const float y, const int width, const int height, const int samplestride, const int linestride)
@ DT_INTERPOLATION_USERPREF_WARP
_lib_location_type_t type
Definition location.c:1
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define PHI
Definition math.h:69
static void mul_mat_vec_2(const float *m, const float *p, float *o)
Definition math.h:178
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition notebook.c:88
GtkNotebook * dt_ui_notebook_new()
Definition notebook.c:83
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
const float factor
Definition pdf.h:91
#define DT_PIXELPIPE_CACHE_HASH_INVALID
#define eps
Definition rcd.c:81
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:177
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
const float uint32_t state[4]
const float const int flip
unsigned __int64 uint64_t
Definition strptime.c:75
One key/value pair handed back by dt_conf_all_string_entries().
Definition conf.h:126
char * key
Definition conf.h:128
char * value
Definition conf.h:130
struct dt_iop_module_t *void * data
dt_image_t image_storage
Definition develop.h:225
struct dt_iop_module_t * gui_module
Definition develop.h:170
One module instance's contribution, as data.
Definition geometry.h:99
dt_iop_roi_t in
Definition geometry.h:117
const dt_geometry_vtable_t * vtable
Definition geometry.h:110
void(* free_data)(void *data)
Definition geometry.h:112
A module's geometry, evaluated. Pure functions of the record's own data.
Definition geometry.h:75
void(* map_size)(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
Full-resolution input rect -> output rect. Mirrors modify_roi_out() at scale 1.
Definition geometry.h:77
int32_t id
Definition image.h:401
dt_boundingbox_t k_space
Definition clipping.c:328
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
gboolean enabled
Definition imageop.h:313
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
static gboolean dt_modifiers_include(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_aspectflip(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)