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