Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
ashift.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2016, 2018 johannes hanika.
4 Copyright (C) 2016 Roman Lebedev.
5 Copyright (C) 2016-2019 Tobias Ellinghaus.
6 Copyright (C) 2016-2018 Ulrich Pegelow.
7 Copyright (C) 2017, 2019 Heiko Bauke.
8 Copyright (C) 2017, 2019, 2022 luzpaz.
9 Copyright (C) 2018-2021, 2023-2026 Aurélien PIERRE.
10 Copyright (C) 2018-2019 Edgardo Hoszowski.
11 Copyright (C) 2018 Maurizio Paglia.
12 Copyright (C) 2018-2022 Pascal Obry.
13 Copyright (C) 2018 rawfiner.
14 Copyright (C) 2019-2022 Aldric Renaudin.
15 Copyright (C) 2019 Andreas Schneider.
16 Copyright (C) 2019-2022 Diederik Ter Rahe.
17 Copyright (C) 2019 Diederik ter Rahe.
18 Copyright (C) 2019 Jacques Le Clerc.
19 Copyright (C) 2019 mepi0011.
20 Copyright (C) 2020-2022 Chris Elston.
21 Copyright (C) 2020 GrahamByrnes.
22 Copyright (C) 2020 Hubert Kowalski.
23 Copyright (C) 2020 Marco.
24 Copyright (C) 2020-2021 Ralf Brown.
25 Copyright (C) 2021-2022 Hanno Schwalm.
26 Copyright (C) 2022 Martin Bařinka.
27 Copyright (C) 2022 Philipp Lutz.
28 Copyright (C) 2022 Victor Forsiuk.
29 Copyright (C) 2023 Alynx Zhou.
30 Copyright (C) 2023 Luca Zulberti.
31 Copyright (C) 2025-2026 Guillaume Stutin.
32 Copyright (C) 2025 Miguel Moquillon.
33
34 darktable is free software: you can redistribute it and/or modify
35 it under the terms of the GNU General Public License as published by
36 the Free Software Foundation, either version 3 of the License, or
37 (at your option) any later version.
38
39 darktable is distributed in the hope that it will be useful,
40 but WITHOUT ANY WARRANTY; without even the implied warranty of
41 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42 GNU General Public License for more details.
43
44 You should have received a copy of the GNU General Public License
45 along with darktable. If not, see <http://www.gnu.org/licenses/>.
46*/
47
48#ifdef HAVE_CONFIG_H
49#include "config.h"
51#include "common/conf.h"
52#endif
53#include "widgets/bauhaus.h"
55#include "system/macros.h"
56#include "system/openmp.h"
58#include "system/mem_alloc.h"
59#include "system/simd.h"
60#include "common/hash.h"
61#include "common/logging.h"
63#include "pixel/bilateral.h"
64#include "common/image.h"
65#include "common/imagebuf.h"
66#include "pixel/interpolation.h"
67#include "math/math.h"
68#include "common/opencl.h"
69#include "control/control.h"
70#include "develop/develop.h"
72#include "develop/imageop.h"
73#include "develop/imageop_gui.h"
74#include "widgets/button.h"
75
76#include "widgets/draw.h"
77#include "iop/iop_api.h"
78
79#include "gui/guides.h"
80
81#include <assert.h>
82#include <gtk/gtk.h>
83#include <inttypes.h>
84#include <math.h>
85#include <stdlib.h>
86#include <string.h>
87
88// Inspiration to this module comes from the program ShiftN (http://www.shiftn.de) by
89// Marcus Hebel.
90
91// Thanks to Marcus for his support when implementing part of the ShiftN functionality
92// to darktable.
93
94#define ROTATION_RANGE 10 // allowed min/max default range for rotation parameter
95#define ROTATION_RANGE_SOFT 180 // allowed min/max range for rotation parameter with manual adjustment
96#define LENSSHIFT_RANGE 1.0 // allowed min/max default range for lensshift parameters
97#define LENSSHIFT_RANGE_SOFT 2.0 // allowed min/max range for lensshift parameters with manual adjustment
98#define SHEAR_RANGE 0.2 // allowed min/max range for shear parameter
99#define SHEAR_RANGE_SOFT 0.5 // allowed min/max range for shear parameter with manual adjustment
100#define MIN_LINE_LENGTH 5 // the minimum length of a line in pixels to be regarded as relevant
101#define MAX_TANGENTIAL_DEVIATION 30 // by how many degrees a line may deviate from the +/-180 and +/-90 to be regarded as relevant
102#define LSD_SCALE 0.99 // LSD: scaling factor for line detection
103#define LSD_SIGMA_SCALE 0.6 // LSD: sigma for Gaussian filter is computed as sigma = sigma_scale/scale
104#define LSD_QUANT 2.0 // LSD: bound to the quantization error on the gradient norm
105#define LSD_ANG_TH 22.5 // LSD: gradient angle tolerance in degrees
106#define LSD_LOG_EPS 0.0 // LSD: detection threshold: -log10(NFA) > log_eps
107#define LSD_DENSITY_TH 0.7 // LSD: minimal density of region points in rectangle
108#define LSD_N_BINS 1024 // LSD: number of bins in pseudo-ordering of gradient modulus
109#define LSD_GAMMA 0.45 // gamma correction to apply on raw images prior to line detection
110#define RANSAC_RUNS 400 // how many iterations to run in ransac
111#define RANSAC_EPSILON 2 // starting value for ransac epsilon (in -log10 units)
112#define RANSAC_EPSILON_STEP 1 // step size of epsilon optimization (log10 units)
113#define RANSAC_ELIMINATION_RATIO 60 // percentage of lines we try to eliminate as outliers
114#define RANSAC_OPTIMIZATION_STEPS 5 // home many steps to optimize epsilon
115#define RANSAC_OPTIMIZATION_DRY_RUNS 50 // how man runs per optimization steps
116#define RANSAC_HURDLE 5 // hurdle rate: the number of lines below which we do a complete permutation instead of random sampling
117#define MINIMUM_FITLINES 2 // minimum number of lines needed for automatic parameter fit
118#define NMS_EPSILON 1e-3 // break criterion for Nelder-Mead simplex
119#define NMS_SCALE 1.0 // scaling factor for Nelder-Mead simplex
120#define NMS_ITERATIONS 400 // number of iterations for Nelder-Mead simplex
121#define NMS_CROP_EPSILON 100.0 // break criterion for Nelder-Mead simplex on crop fitting
122#define NMS_CROP_SCALE 0.5 // scaling factor for Nelder-Mead simplex on crop fitting
123#define NMS_CROP_ITERATIONS 100 // number of iterations for Nelder-Mead simplex on crop fitting
124#define NMS_ALPHA 1.0 // reflection coefficient for Nelder-Mead simplex
125#define NMS_BETA 0.5 // contraction coefficient for Nelder-Mead simplex
126#define NMS_GAMMA 2.0 // expansion coefficient for Nelder-Mead simplex
127#define DEFAULT_F_LENGTH 28.0 // focal length we assume if no exif data are available
128
129// define to get debugging output
130#undef ASHIFT_DEBUG
131
132#define SQR(a) ((a) * (a))
133
134// maximum number of drawn lines that can be saved in parameters
135// any change in this value needs to upgrade parameters version !
136#define MAX_SAVED_LINES 50
137
138// For line detection we use the LSD algorithm as published by Rafael Grompone:
139//
140// "LSD: a Line Segment Detector" by Rafael Grompone von Gioi,
141// Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall,
142// Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd
143// http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd
144#include "ashift_lsd.c"
145
146// For parameter optimization we are using the Nelder-Mead simplex method
147// implemented by Michael F. Hutt.
150#include "widgets/label.h"
151#include "widgets/togglebutton.h"
152#include "control/signal.h"
153
154
156
157
158const char *name()
159{
160 return _("Horizon and _perspective");
161}
162
163const char *aliases()
164{
165 return _("rotation|keystone|distortion|crop|reframe|horizon");
166}
167
168const char **description(struct dt_iop_module_t *self)
169{
170 return dt_iop_set_description(self, _("rotate or distort perspective"),
171 _("corrective or creative"),
172 _("linear, RGB, scene-referred"),
173 _("geometric, RGB"),
174 _("linear, RGB, scene-referred"));
175}
176
182
184{
185 return IOP_GROUP_REPAIR;
186}
187
189{
190 return IOP_TAG_DISTORT;
191}
192
194{
195 // switch off clipping and decoration, we want to see the full image.
197}
198
200{
201 return IOP_CS_RGB;
202}
203
211
217
231
233{
234 ASHIFT_FITTING_ALL = 0, // $DESCRIPTION: rotation, lens shift, shear
235 ASHIFT_FITTING_LENS_ROTATION = 1, // $DESCRIPTION: rotation, lens shift
236 ASHIFT_FITTING_ROTATION = 2, // $DESCRIPTION: rotation only
237 ASHIFT_FITTING_LENS = 3, // $DESCRIPTION: lens shift only
239
248
250{
251 ASHIFT_FIT_NONE = 0, // none
252 ASHIFT_FIT_ROTATION = 1 << 0, // flag indicates to fit rotation angle
253 ASHIFT_FIT_LENS_VERT = 1 << 1, // flag indicates to fit vertical lens shift
254 ASHIFT_FIT_LENS_HOR = 1 << 2, // flag indicates to fit horizontal lens shift
255 ASHIFT_FIT_SHEAR = 1 << 3, // flag indicates to fit shear parameter
256 ASHIFT_FIT_LINES_VERT = 1 << 4, // use vertical lines for fitting
257 ASHIFT_FIT_LINES_HOR = 1 << 5, // use horizontal lines for fitting
275
283
292
294{
295 ASHIFT_MODE_GENERIC = 0, // $DESCRIPTION: "generic"
296 ASHIFT_MODE_SPECIFIC = 1 // $DESCRIPTION: "specific"
298
300{
301 ASHIFT_CROP_OFF = 0, // $DESCRIPTION: "off"
302 ASHIFT_CROP_LARGEST = 1,// $DESCRIPTION: "largest area"
303 ASHIFT_CROP_ASPECT = 2 // $DESCRIPTION: "original format"
305
312
321
329
342
360
379
381{
382 float rotation; // $MIN: -ROTATION_RANGE_SOFT $MAX: ROTATION_RANGE_SOFT $DEFAULT: 0.0
383 float lensshift_v; // $MIN: -LENSSHIFT_RANGE_SOFT $MAX: LENSSHIFT_RANGE_SOFT $DEFAULT: 0.0 $DESCRIPTION: "lens shift (vertical)"
384 float lensshift_h; // $MIN: -LENSSHIFT_RANGE_SOFT $MAX: LENSSHIFT_RANGE_SOFT $DEFAULT: 0.0 $DESCRIPTION: "lens shift (horizontal)"
385 float shear; // $MIN: -SHEAR_RANGE_SOFT $MAX: SHEAR_RANGE_SOFT $DEFAULT: 0.0 $DESCRIPTION: "shear"
386 float f_length; // $MIN: 1.0 $MAX: 2000.0 $DEFAULT: DEFAULT_F_LENGTH $DESCRIPTION: "focal length"
387 float crop_factor; // $MIN: 0.5 $MAX: 10.0 $DEFAULT: 1.0 $DESCRIPTION: "crop factor"
388 float orthocorr; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "lens dependence"
389 float aspect; // $MIN: 0.5 $MAX: 2.0 $DEFAULT: 1.0 $DESCRIPTION: "aspect adjust"
390 dt_iop_ashift_mode_t mode; // $DEFAULT: ASHIFT_MODE_SPECIFIC $DESCRIPTION: "lens model"
391 dt_iop_ashift_crop_t cropmode; // $DEFAULT: ASHIFT_CROP_LARGEST $DESCRIPTION: "automatic cropping"
392 float cl; // $DEFAULT: 0.0
393 float cr; // $DEFAULT: 1.0
394 float ct; // $DEFAULT: 0.0
395 float cb; // $DEFAULT: 1.0
400
402{
403 float p1[3];
404 float p2[3];
405 float length;
406 float width;
407 float weight;
409 // homogeneous coordinates:
410 float L[3];
412
424
447
449{
450 int width;
452 float x;
453 float y;
454 float alpha;
455 float homograph[3][3];
456 float edges[4][3];
458
460{
506 float *points;
510 float *buf;
520 float lastx;
521 float lasty;
522 float crop_cx;
523 float crop_cy;
526
535 gboolean editing;
538
541
543{
544 float rotation;
547 float shear;
550 float aspect;
551 float cl;
552 float cr;
553 float ct;
554 float cb;
556
563
564int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
565 void *new_params, const int new_version)
566{
567 if(old_version == 1 && new_version == 5)
568 {
569 const dt_iop_ashift_params1_t *old = old_params;
570 dt_iop_ashift_params_t *new = new_params;
571 new->rotation = old->rotation;
572 new->lensshift_v = old->lensshift_v;
573 new->lensshift_h = old->lensshift_h;
574 new->shear = 0.0f;
575 new->f_length = DEFAULT_F_LENGTH;
576 new->crop_factor = 1.0f;
577 new->orthocorr = 100.0f;
578 new->aspect = 1.0f;
579 new->mode = ASHIFT_MODE_GENERIC;
580 new->cropmode = ASHIFT_CROP_OFF;
581 new->cl = 0.0f;
582 new->cr = 1.0f;
583 new->ct = 0.0f;
584 new->cb = 1.0f;
585 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) new->last_drawn_lines[i] = 0.0f;
586 for(int i = 0; i < 8; i++) new->last_quad_lines[i] = 0.0f;
587 new->last_drawn_lines_count = 0;
588 return 0;
589 }
590 if(old_version == 2 && new_version == 5)
591 {
592 const dt_iop_ashift_params2_t *old = old_params;
593 dt_iop_ashift_params_t *new = new_params;
594 new->rotation = old->rotation;
595 new->lensshift_v = old->lensshift_v;
596 new->lensshift_h = old->lensshift_h;
597 new->shear = 0.0f;
598 new->f_length = old->f_length;
599 new->crop_factor = old->crop_factor;
600 new->orthocorr = old->orthocorr;
601 new->aspect = old->aspect;
602 new->mode = old->mode;
603 new->cropmode = ASHIFT_CROP_OFF;
604 new->cl = 0.0f;
605 new->cr = 1.0f;
606 new->ct = 0.0f;
607 new->cb = 1.0f;
608 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) new->last_drawn_lines[i] = 0.0f;
609 for(int i = 0; i < 8; i++) new->last_quad_lines[i] = 0.0f;
610 new->last_drawn_lines_count = 0;
611 return 0;
612 }
613 if(old_version == 3 && new_version == 5)
614 {
615 const dt_iop_ashift_params3_t *old = old_params;
616 dt_iop_ashift_params_t *new = new_params;
617 new->rotation = old->rotation;
618 new->lensshift_v = old->lensshift_v;
619 new->lensshift_h = old->lensshift_h;
620 new->shear = 0.0f;
621 new->f_length = old->f_length;
622 new->crop_factor = old->crop_factor;
623 new->orthocorr = old->orthocorr;
624 new->aspect = old->aspect;
625 new->mode = old->mode;
626 new->cropmode = old->cropmode;
627 new->cl = old->cl;
628 new->cr = old->cr;
629 new->ct = old->ct;
630 new->cb = old->cb;
631 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) new->last_drawn_lines[i] = 0.0f;
632 for(int i = 0; i < 8; i++) new->last_quad_lines[i] = 0.0f;
633 new->last_drawn_lines_count = 0;
634 return 0;
635 }
636 if(old_version == 4 && new_version == 5)
637 {
638 const dt_iop_ashift_params4_t *old = old_params;
639 dt_iop_ashift_params_t *new = new_params;
640 new->rotation = old->rotation;
641 new->lensshift_v = old->lensshift_v;
642 new->lensshift_h = old->lensshift_h;
643 new->shear = old->shear;
644 new->f_length = old->f_length;
645 new->crop_factor = old->crop_factor;
646 new->orthocorr = old->orthocorr;
647 new->aspect = old->aspect;
648 new->mode = old->mode;
649 new->cropmode = old->cropmode;
650 new->cl = old->cl;
651 new->cr = old->cr;
652 new->ct = old->ct;
653 new->cb = old->cb;
654 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) new->last_drawn_lines[i] = 0.0f;
655 for(int i = 0; i < 8; i++) new->last_quad_lines[i] = 0.0f;
656 new->last_drawn_lines_count = 0;
657 return 0;
658 }
659
660 return 1;
661}
662
663
664// Return the module user params pointer in normal mode,
665// or the GUI private copy if editing.
667{
669 if(!IS_NULL_PTR(g) && g->editing)
670 return &g->new_params;
671 else
672 return (dt_iop_ashift_params_t *)self->params;
673}
674
675
676// normalized product of two 3x1 vectors
677// dst needs to be different from v1 and v2
678static inline void vec3prodn(float *dst, const float *const v1, const float *const v2)
679{
680 const float l1 = v1[1] * v2[2] - v1[2] * v2[1];
681 const float l2 = v1[2] * v2[0] - v1[0] * v2[2];
682 const float l3 = v1[0] * v2[1] - v1[1] * v2[0];
683
684 // normalize so that l1^2 + l2^2 + l3^3 = 1
685 const float sq = sqrtf(l1 * l1 + l2 * l2 + l3 * l3);
686
687 const float f = sq > 0.0f ? 1.0f / sq : 1.0f;
688
689 dst[0] = l1 * f;
690 dst[1] = l2 * f;
691 dst[2] = l3 * f;
692}
693
694// normalize a 3x1 vector so that x^2 + y^2 + z^2 = 1
695// dst and v may be the same
696static inline void vec3norm(float *dst, const float *const v)
697{
698 const float sq = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
699
700 // special handling for an all-zero vector
701 const float f = sq > 0.0f ? 1.0f / sq : 1.0f;
702
703 dst[0] = v[0] * f;
704 dst[1] = v[1] * f;
705 dst[2] = v[2] * f;
706}
707
708// normalize a 3x1 vector so that x^2 + y^2 = 1; a useful normalization for
709// lines in homogeneous coordinates
710// dst and v may be the same
711static inline void vec3lnorm(float *dst, const float *const v)
712{
713 const float sq = sqrtf(v[0] * v[0] + v[1] * v[1]);
714
715 // special handling for a point vector of the image center
716 const float f = sq > 0.0f ? 1.0f / sq : 1.0f;
717
718 dst[0] = v[0] * f;
719 dst[1] = v[1] * f;
720 dst[2] = v[2] * f;
721}
722
723
724// scalar product of two 3x1 vectors
725static inline float vec3scalar(const float *const v1, const float *const v2)
726{
727 return (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]);
728}
729
730// check if 3x1 vector is (very close to) null
731static inline int vec3isnull(const float *const v)
732{
733 const float eps = 1e-10f;
734 return (fabsf(v[0]) < eps && fabsf(v[1]) < eps && fabsf(v[2]) < eps);
735}
736
737#ifdef ASHIFT_DEBUG
738static void print_roi(const dt_iop_roi_t *roi, const char *label)
739{
740 printf("{ %5d %5d %5d %5d %.6f } %s\n", roi->x, roi->y, roi->width, roi->height, roi->scale, label);
741}
742#endif
743
753{
754 p->cl = 0.0f;
755 p->cr = 1.0f;
756 p->ct = 0.0f;
757 p->cb = 1.0f;
758}
759
760#define MAT3SWAP(a, b) { float (*tmp)[3] = (a); (a) = (b); (b) = tmp; }
761
763static void homography(float *homograph, const float angle, const float shift_v, const float shift_h,
764 const float shear, const float f_length_kb, const float orthocorr, const float aspect,
765 const int width, const int height, dt_iop_ashift_homodir_t dir)
766{
767 // calculate homograph that combines all translations, rotations
768 // and warping into one single matrix operation.
769 // this is heavily leaning on ShiftN where the homographic matrix expects
770 // input in (y : x : 1) format. in the darktable world we want to keep the
771 // (x : y : 1) convention. therefore we need to flip coordinates first and
772 // make sure that output is in correct format after corrections are applied.
773
774 const float u = width;
775 const float v = height;
776
777 const float phi = M_PI * angle / 180.0f;
778 const float cosi = cosf(phi);
779 const float sini = sinf(phi);
780 const float ascale = sqrtf(aspect);
781
782 // most of this comes from ShiftN
783 const float f_global = f_length_kb;
784 const float horifac = 1.0f - orthocorr / 100.0f;
785 const float exppa_v = expf(shift_v);
786 const float fdb_v = f_global / (14.4f + (v / u - 1) * 7.2f);
787 const float rad_v = fdb_v * (exppa_v - 1.0f) / (exppa_v + 1.0f);
788 const float alpha_v = CLAMP(atanf(rad_v), -1.5f, 1.5f);
789 const float rt_v = sinf(0.5f * alpha_v);
790 const float r_v = fmaxf(0.1f, 2.0f * (horifac - 1.0f) * rt_v * rt_v + 1.0f);
791
792 const float vertifac = 1.0f - orthocorr / 100.0f;
793 const float exppa_h = expf(shift_h);
794 const float fdb_h = f_global / (14.4f + (u / v - 1) * 7.2f);
795 const float rad_h = fdb_h * (exppa_h - 1.0f) / (exppa_h + 1.0f);
796 const float alpha_h = CLAMP(atanf(rad_h), -1.5f, 1.5f);
797 const float rt_h = sinf(0.5f * alpha_h);
798 const float r_h = fmaxf(0.1f, 2.0f * (vertifac - 1.0f) * rt_h * rt_h + 1.0f);
799
800
801 // three intermediate buffers for matrix calculation ...
802 float m1[3][3], m2[3][3], m3[3][3];
803
804 // ... and some pointers to handle them more intuitively
805 float (*mwork)[3] = m1;
806 float (*minput)[3] = m2;
807 float (*moutput)[3] = m3;
808
809 // Step 1: flip x and y coordinates (see above)
810 memset(minput, 0, sizeof(float) * 9);
811 minput[0][1] = 1.0f;
812 minput[1][0] = 1.0f;
813 minput[2][2] = 1.0f;
814
815
816 // Step 2: rotation of image around its center
817 memset(mwork, 0, sizeof(float) * 9);
818 mwork[0][0] = cosi;
819 mwork[0][1] = -sini;
820 mwork[1][0] = sini;
821 mwork[1][1] = cosi;
822 mwork[0][2] = -0.5f * v * cosi + 0.5f * u * sini + 0.5f * v;
823 mwork[1][2] = -0.5f * v * sini - 0.5f * u * cosi + 0.5f * u;
824 mwork[2][2] = 1.0f;
825
826 // multiply mwork * minput -> moutput
827 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
828
829
830 // Step 3: apply shearing
831 memset(mwork, 0, sizeof(float) * 9);
832 mwork[0][0] = 1.0f;
833 mwork[0][1] = shear;
834 mwork[1][1] = 1.0f;
835 mwork[1][0] = shear;
836 mwork[2][2] = 1.0f;
837
838 // moutput (of last calculation) -> minput
839 MAT3SWAP(minput, moutput);
840 // multiply mwork * minput -> moutput
841 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
842
843
844 // Step 4: apply vertical lens shift effect
845 memset(mwork, 0, sizeof(float) * 9);
846 mwork[0][0] = exppa_v;
847 mwork[1][0] = 0.5f * ((exppa_v - 1.0f) * u) / v;
848 mwork[1][1] = 2.0f * exppa_v / (exppa_v + 1.0f);
849 mwork[1][2] = -0.5f * ((exppa_v - 1.0f) * u) / (exppa_v + 1.0f);
850 mwork[2][0] = (exppa_v - 1.0f) / v;
851 mwork[2][2] = 1.0f;
852
853 // moutput (of last calculation) -> minput
854 MAT3SWAP(minput, moutput);
855 // multiply mwork * minput -> moutput
856 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
857
858
859 // Step 5: horizontal compression
860 memset(mwork, 0, sizeof(float) * 9);
861 mwork[0][0] = 1.0f;
862 mwork[1][1] = r_v;
863 mwork[1][2] = 0.5f * u * (1.0f - r_v);
864 mwork[2][2] = 1.0f;
865
866 // moutput (of last calculation) -> minput
867 MAT3SWAP(minput, moutput);
868 // multiply mwork * minput -> moutput
869 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
870
871
872 // Step 6: flip x and y back again
873 memset(mwork, 0, sizeof(float) * 9);
874 mwork[0][1] = 1.0f;
875 mwork[1][0] = 1.0f;
876 mwork[2][2] = 1.0f;
877
878 // moutput (of last calculation) -> minput
879 MAT3SWAP(minput, moutput);
880 // multiply mwork * minput -> moutput
881 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
882
883
884 // from here output vectors would be in (x : y : 1) format
885
886 // Step 7: now we can apply horizontal lens shift with the same matrix format as above
887 memset(mwork, 0, sizeof(float) * 9);
888 mwork[0][0] = exppa_h;
889 mwork[1][0] = 0.5f * ((exppa_h - 1.0f) * v) / u;
890 mwork[1][1] = 2.0f * exppa_h / (exppa_h + 1.0f);
891 mwork[1][2] = -0.5f * ((exppa_h - 1.0f) * v) / (exppa_h + 1.0f);
892 mwork[2][0] = (exppa_h - 1.0f) / u;
893 mwork[2][2] = 1.0f;
894
895 // moutput (of last calculation) -> minput
896 MAT3SWAP(minput, moutput);
897 // multiply mwork * minput -> moutput
898 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
899
900
901 // Step 8: vertical compression
902 memset(mwork, 0, sizeof(float) * 9);
903 mwork[0][0] = 1.0f;
904 mwork[1][1] = r_h;
905 mwork[1][2] = 0.5f * v * (1.0f - r_h);
906 mwork[2][2] = 1.0f;
907
908 // moutput (of last calculation) -> minput
909 MAT3SWAP(minput, moutput);
910 // multiply mwork * minput -> moutput
911 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
912
913
914 // Step 9: apply aspect ratio scaling
915 memset(mwork, 0, sizeof(float) * 9);
916 mwork[0][0] = 1.0f * ascale;
917 mwork[1][1] = 1.0f / ascale;
918 mwork[2][2] = 1.0f;
919
920 // moutput (of last calculation) -> minput
921 MAT3SWAP(minput, moutput);
922 // multiply mwork * minput -> moutput
923 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
924
925
926 // Step 10: find x/y offsets and apply according correction so that
927 // no negative coordinates occur in output vector
928 float umin = FLT_MAX, vmin = FLT_MAX;
929 // visit all four corners
930 for(int y = 0; y < height; y += height - 1)
931 for(int x = 0; x < width; x += width - 1)
932 {
933 float pi[3], po[3];
934 pi[0] = x;
935 pi[1] = y;
936 pi[2] = 1.0f;
937 // moutput expects input in (x:y:1) format and gives output as (x:y:1)
938 mat3mulv(po, (float *)moutput, pi);
939 umin = fmin(umin, po[0] / po[2]);
940 vmin = fmin(vmin, po[1] / po[2]);
941 }
942
943 memset(mwork, 0, sizeof(float) * 9);
944 mwork[0][0] = 1.0f;
945 mwork[1][1] = 1.0f;
946 mwork[2][2] = 1.0f;
947 mwork[0][2] = -umin;
948 mwork[1][2] = -vmin;
949
950 // moutput (of last calculation) -> minput
951 MAT3SWAP(minput, moutput);
952 // multiply mwork * minput -> moutput
953 mat3mul((float *)moutput, (float *)mwork, (float *)minput);
954
955
956 // on request we either keep the final matrix for forward conversions
957 // or produce an inverted matrix for backward conversions
958 if(dir == ASHIFT_HOMOGRAPH_FORWARD)
959 {
960 // we have what we need -> copy it to the right place
961 memcpy(homograph, moutput, sizeof(float) * 9);
962 }
963 else
964 {
965 // generate inverted homograph (mat3inv function defined in colorspaces.c)
966 if(mat3inv((float *)homograph, (float *)moutput))
967 {
968 // in case of error we set to unity matrix
969 memset(mwork, 0, sizeof(float) * 9);
970 mwork[0][0] = 1.0f;
971 mwork[1][1] = 1.0f;
972 mwork[2][2] = 1.0f;
973 memcpy(homograph, mwork, sizeof(float) * 9);
974 }
975 }
976}
977#undef MAT3SWAP
978
979
980// check if module parameters are set to all neutral values in which case the module's
981// output is identical to its input
982static inline int isneutral(const dt_iop_ashift_data_t *data)
983{
984 // values lower than this have no visible effect
985 const float eps = 1.0e-4f;
986
987 return(fabs(data->rotation) < eps &&
988 fabs(data->lensshift_v) < eps &&
989 fabs(data->lensshift_h) < eps &&
990 fabs(data->shear) < eps &&
991 fabs(data->aspect - 1.0f) < eps &&
992 data->cl < eps &&
993 1.0f - data->cr < eps &&
994 data->ct < eps &&
995 1.0f - data->cb < eps);
996}
997
998
1000 float *const restrict points, size_t points_count)
1001{
1002 const dt_iop_ashift_data_t *const data = (dt_iop_ashift_data_t *)piece->data;
1003
1004 // nothing to be done if parameters are set to neutral values
1005 if(isneutral(data)) return 1;
1006
1007 float DT_ALIGNED_ARRAY homograph[3][3];
1008 homography((float *)homograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb,
1009 data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_FORWARD);
1010
1011 // clipping offset
1012 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);
1013 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);
1014 const float cx = fullwidth * data->cl;
1015 const float cy = fullheight * data->ct;
1016 __OMP_PARALLEL_FOR_SIMD__(if(points_count > 100) aligned(points, homograph:64))
1017 for(size_t i = 0; i < points_count * 2; i += 2)
1018 {
1019 float DT_ALIGNED_PIXEL pi[3] = { points[i], points[i + 1], 1.0f };
1020 float DT_ALIGNED_PIXEL po[3];
1021 mat3mulv(po, (float *)homograph, pi);
1022 points[i] = po[0] / po[2] - cx;
1023 points[i + 1] = po[1] / po[2] - cy;
1024 }
1025
1026 return 1;
1027}
1028
1029
1031 float *points, size_t points_count)
1032{
1033 const dt_iop_ashift_data_t *const data = (dt_iop_ashift_data_t *)piece->data;
1034
1035 // nothing to be done if parameters are set to neutral values
1036 if(isneutral(data)) return 1;
1037
1038 float DT_ALIGNED_ARRAY ihomograph[3][3];
1039 homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb,
1040 data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);
1041
1042 // clipping offset
1043 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);
1044 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);
1045 const float cx = fullwidth * data->cl;
1046 const float cy = fullheight * data->ct;
1047 __OMP_PARALLEL_FOR_SIMD__(if(points_count > 100) aligned(ihomograph, points:64))
1048 for(size_t i = 0; i < points_count * 2; i += 2)
1049 {
1050 float DT_ALIGNED_PIXEL pi[3] = { points[i] + cx, points[i + 1] + cy, 1.0f };
1051 float DT_ALIGNED_PIXEL po[3];
1052 mat3mulv(po, (float *)ihomograph, pi);
1053 points[i] = po[0] / po[2];
1054 points[i + 1] = po[1] / po[2];
1055 }
1056
1057 return 1;
1058}
1059
1060void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece,
1061 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
1062 const dt_iop_roi_t *const roi_out)
1063{
1064 (void)pipe;
1065 const dt_iop_ashift_data_t *const data = (dt_iop_ashift_data_t *)piece->data;
1066
1067 // if module is set to neutral parameters we just copy input->output and are done
1068 if(isneutral(data))
1069 {
1070 dt_iop_image_copy_by_size(out, in, roi_out->width, roi_out->height, 1);
1071 return;
1072 }
1073
1075
1076 float ihomograph[3][3];
1077 homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb,
1078 data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);
1079
1080 // clipping offset
1081 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);
1082 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);
1083 const float cx = roi_out->scale * fullwidth * data->cl;
1084 const float cy = roi_out->scale * fullheight * data->ct;
1086 // go over all pixels of output image
1087 for(int j = 0; j < roi_out->height; j++)
1088 {
1089 float *const restrict _out = out + (size_t)j * roi_out->width;
1090 for(int i = 0; i < roi_out->width; i++)
1091 {
1092 float pin[4], pout[4];
1093
1094 // convert output pixel coordinates to original image coordinates
1095 pout[0] = roi_out->x + i + cx;
1096 pout[1] = roi_out->y + j + cy;
1097 pout[0] /= roi_out->scale;
1098 pout[1] /= roi_out->scale;
1099 pout[2] = 1.0f;
1100
1101 // apply homograph
1102 mat3mulv(pin, (float *)ihomograph, pout);
1103
1104 // convert to input pixel coordinates
1105 pin[0] /= pin[2];
1106 pin[1] /= pin[2];
1107 pin[0] *= roi_in->scale;
1108 pin[1] *= roi_in->scale;
1109 pin[0] -= roi_in->x;
1110 pin[1] -= roi_in->y;
1111
1112 // get output values by interpolation from input image
1113 dt_interpolation_compute_pixel4c(interpolation, in, _out + i, pin[0], pin[1], roi_in->width,
1114 roi_in->height, roi_in->width);
1115 }
1116 }
1117}
1118
1119void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
1120 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
1121 const dt_iop_roi_t *roi_in)
1122{
1124 *roi_out = *roi_in;
1125
1126 // nothing more to be done if parameters are set to neutral values
1127 if(isneutral(data)) return;
1128
1129 float homograph[3][3];
1130 homography((float *)homograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb,
1131 data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_FORWARD);
1132
1133 float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX;
1134
1135 // go through all four vertices of input roi and convert coordinates to output
1136 for(int y = 0; y < roi_in->height; y += roi_in->height - 1)
1137 {
1138 for(int x = 0; x < roi_in->width; x += roi_in->width - 1)
1139 {
1140 float pin[3], pout[3];
1141
1142 // convert from input coordinates to original image coordinates
1143 pin[0] = roi_in->x + x;
1144 pin[1] = roi_in->y + y;
1145 pin[0] /= roi_in->scale;
1146 pin[1] /= roi_in->scale;
1147 pin[2] = 1.0f;
1148
1149 // apply homograph
1150 mat3mulv(pout, (float *)homograph, pin);
1151
1152 // convert to output image coordinates
1153 pout[0] /= pout[2];
1154 pout[1] /= pout[2];
1155 pout[0] *= roi_out->scale;
1156 pout[1] *= roi_out->scale;
1157 xm = MIN(xm, pout[0]);
1158 xM = MAX(xM, pout[0]);
1159 ym = MIN(ym, pout[1]);
1160 yM = MAX(yM, pout[1]);
1161 }
1162 }
1163
1164 float width = xM - xm + 1;
1165 float height = yM - ym + 1;
1166
1167 // clipping adjustments
1168 width *= data->cr - data->cl;
1169 height *= data->cb - data->ct;
1170
1171 roi_out->width = floorf(width);
1172 roi_out->height = floorf(height);
1173
1174#ifdef ASHIFT_DEBUG
1175 print_roi(roi_in, "roi_in (going into modify_roi_out)");
1176 print_roi(roi_out, "roi_out (after modify_roi_out)");
1177#endif
1178}
1179
1180void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
1181 struct dt_dev_pixelpipe_iop_t *piece,
1182 const dt_iop_roi_t *const roi_out, dt_iop_roi_t *roi_in)
1183{
1185 *roi_in = *roi_out;
1186
1187 // nothing more to be done if parameters are set to neutral values
1188 if(isneutral(data)) return;
1189
1190 // NB: while editing, commit_params() neutralizes the crop (cl/cr/ct/cb), so the back-transform
1191 // below maps the full uncropped output back to the full input (cx=cy=0). process() then captures
1192 // the whole image into g->buf — exactly what structure detection and the crop frame need — with no
1193 // edit-specific special-casing in this function.
1194 (void)pipe;
1195 float ihomograph[3][3];
1196 homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb,
1197 data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);
1198
1199 const float orig_w = roi_in->scale * piece->buf_in.width;
1200 const float orig_h = roi_in->scale * piece->buf_in.height;
1201
1202 // clipping offset
1203 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);
1204 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);
1205 const float cx = roi_out->scale * fullwidth * data->cl;
1206 const float cy = roi_out->scale * fullheight * data->ct;
1207
1208 float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX;
1209
1210 // go through all four vertices of output roi and convert coordinates to input
1211 for(int y = 0; y < roi_out->height; y += roi_out->height - 1)
1212 {
1213 for(int x = 0; x < roi_out->width; x += roi_out->width - 1)
1214 {
1215 float pin[3], pout[3];
1216
1217 // convert from output image coordinates to original image coordinates
1218 pout[0] = roi_out->x + x + cx;
1219 pout[1] = roi_out->y + y + cy;
1220 pout[0] /= roi_out->scale;
1221 pout[1] /= roi_out->scale;
1222 pout[2] = 1.0f;
1223
1224 // apply homograph
1225 mat3mulv(pin, (float *)ihomograph, pout);
1226
1227 // convert to input image coordinates
1228 pin[0] /= pin[2];
1229 pin[1] /= pin[2];
1230 pin[0] *= roi_in->scale;
1231 pin[1] *= roi_in->scale;
1232 xm = MIN(xm, pin[0]);
1233 xM = MAX(xM, pin[0]);
1234 ym = MIN(ym, pin[1]);
1235 yM = MAX(yM, pin[1]);
1236 }
1237 }
1238
1240 roi_in->x = fmaxf(0.0f, xm - interpolation->width);
1241 roi_in->y = fmaxf(0.0f, ym - interpolation->width);
1242 roi_in->width = fminf(ceilf(orig_w) - roi_in->x, xM - roi_in->x + 1 + interpolation->width);
1243 roi_in->height = fminf(ceilf(orig_h) - roi_in->y, yM - roi_in->y + 1 + interpolation->width);
1244
1245 // sanity check.
1246 roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(orig_w));
1247 roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(orig_h));
1248 roi_in->width = CLAMP(roi_in->width, 1, (int)floorf(orig_w) - roi_in->x);
1249 roi_in->height = CLAMP(roi_in->height, 1, (int)floorf(orig_h) - roi_in->y);
1250#ifdef ASHIFT_DEBUG
1251 print_roi(roi_out, "roi_out (going into modify_roi_in)");
1252 print_roi(roi_in, "roi_in (after modify_roi_in)");
1253#endif
1254}
1255
1256// simple conversion of rgb image into greyscale variant suitable for line segment detection
1257// the lsd routines expect input as *double, roughly in the range [0.0; 256.0]
1258static void rgb2grey256(const float *const in, double *const out, const int width, const int height)
1259{
1260 const size_t npixels = (size_t)width * height;
1262 for(int index = 0; index < npixels; index++)
1263 {
1264 out[index] = (0.3f * in[4*index+0] + 0.59f * in[4*index+1] + 0.11f * in[4*index+2]) * 256.0;
1265 }
1266}
1267
1268// sobel edge enhancement in one direction
1269static void edge_enhance_1d(const double *in, double *out, const int width, const int height,
1271{
1272 // Sobel kernels for both directions
1273 const double hkernel[3][3] = { { 1.0, 0.0, -1.0 }, { 2.0, 0.0, -2.0 }, { 1.0, 0.0, -1.0 } };
1274 const double vkernel[3][3] = { { 1.0, 2.0, 1.0 }, { 0.0, 0.0, 0.0 }, { -1.0, -2.0, -1.0 } };
1275 const int kwidth = 3;
1276 const int khwidth = kwidth / 2;
1277
1278 // select kernel
1279 const double *kernel = (dir == ASHIFT_ENHANCE_HORIZONTAL) ? (const double *)hkernel : (const double *)vkernel;
1281 // loop over image pixels and perform sobel convolution
1282 for(int j = khwidth; j < height - khwidth; j++)
1283 {
1284 const double *inp = in + (size_t)j * width + khwidth;
1285 double *outp = out + (size_t)j * width + khwidth;
1286 for(int i = khwidth; i < width - khwidth; i++, inp++, outp++)
1287 {
1288 double sum = 0.0f;
1289 for(int jj = 0; jj < kwidth; jj++)
1290 {
1291 const int k = jj * kwidth;
1292 const int l = (jj - khwidth) * width;
1293 for(int ii = 0; ii < kwidth; ii++)
1294 {
1295 sum += inp[l + ii - khwidth] * kernel[k + ii];
1296 }
1297 }
1298 *outp = sum;
1299 }
1300 }
1302 // border fill in output buffer, so we don't get pseudo lines at image frame
1303 for(int j = 0; j < height; j++)
1304 for(int i = 0; i < width; i++)
1305 {
1306 double val = out[j * width + i];
1307
1308 if(j < khwidth)
1309 val = out[(khwidth - j) * width + i];
1310 else if(j >= height - khwidth)
1311 val = out[(j - khwidth) * width + i];
1312 else if(i < khwidth)
1313 val = out[j * width + (khwidth - i)];
1314 else if(i >= width - khwidth)
1315 val = out[j * width + (i - khwidth)];
1316
1317 out[j * width + i] = val;
1318
1319 // jump over center of image
1320 if(i == khwidth && j >= khwidth && j < height - khwidth) i = width - khwidth;
1321 }
1322}
1323
1324// edge enhancement in both directions
1325static int edge_enhance(const double *in, double *out, const int width, const int height)
1326{
1327 double *Gx = NULL;
1328 double *Gy = NULL;
1329
1330 Gx = malloc(sizeof(double) * width * height);
1331 if(IS_NULL_PTR(Gx)) goto error;
1332
1333 Gy = malloc(sizeof(double) * width * height);
1334 if(IS_NULL_PTR(Gy)) goto error;
1335
1336 // perform edge enhancement in both directions
1339
1340// calculate absolute values
1342 for(size_t k = 0; k < (size_t)width * height; k++)
1343 {
1344 out[k] = sqrt(Gx[k] * Gx[k] + Gy[k] * Gy[k]);
1345 }
1346
1347 dt_free(Gx);
1348 dt_free(Gy);
1349 return TRUE;
1350
1351error:
1352 dt_free(Gx);
1353 dt_free(Gy);
1354 return FALSE;
1355}
1356
1357// XYZ -> sRGB matrix
1359{
1360 sRGB[0] = 3.1338561f * XYZ[0] - 1.6168667f * XYZ[1] - 0.4906146f * XYZ[2];
1361 sRGB[1] = -0.9787684f * XYZ[0] + 1.9161415f * XYZ[1] + 0.0334540f * XYZ[2];
1362 sRGB[2] = 0.0719453f * XYZ[0] - 0.2289914f * XYZ[1] + 1.4052427f * XYZ[2];
1363}
1364
1365// sRGB -> XYZ matrix
1367{
1368 XYZ[0] = 0.4360747f * sRGB[0] + 0.3850649f * sRGB[1] + 0.1430804f * sRGB[2];
1369 XYZ[1] = 0.2225045f * sRGB[0] + 0.7168786f * sRGB[1] + 0.0606169f * sRGB[2];
1370 XYZ[2] = 0.0139322f * sRGB[0] + 0.0971045f * sRGB[1] + 0.7141733f * sRGB[2];
1371}
1372
1373// detail enhancement via bilateral grid (function arguments in and out may represent identical buffers)
1374static int detail_enhance(const float *const in, float *const out, const int width, const int height)
1375{
1376 const float sigma_r = 5.0f;
1377 const float sigma_s = fminf(width, height) * 0.02f;
1378 const float detail = 10.0f;
1379 const size_t npixels = (size_t)width * height;
1380 int success = TRUE;
1381
1382 // we need to convert from RGB to Lab first;
1383 // as colors don't matter we are safe to assume data to be sRGB
1384
1385 // convert RGB input to Lab, use output buffer for intermediate storage
1387 for(size_t index = 0; index < 4*npixels; index += 4)
1388 {
1390 sRGB_to_XYZ(in + index, XYZ);
1391 dt_XYZ_to_Lab(XYZ, out + index);
1392 }
1393
1394 // bilateral grid detail enhancement
1396
1397 if(!IS_NULL_PTR(b))
1398 {
1403 }
1404 else
1405 success = FALSE;
1406
1407 // convert resulting Lab to RGB output
1409 for(size_t index = 0; index < 4*npixels; index += 4)
1410 {
1412 dt_Lab_to_XYZ(out + index, XYZ);
1413 XYZ_to_sRGB(XYZ, out + index);
1414 }
1415
1416 return success;
1417}
1418
1419// apply gamma correction to RGB buffer (function arguments in and out may represent identical buffers)
1420static void gamma_correct(const float *const in, float *const out, const int width, const int height)
1421{
1422 const size_t npixels = (size_t)width * height;
1424 for(int index = 0; index < 4*npixels; index += 4)
1425 {
1426 for(int c = 0; c < 3; c++)
1427 out[index+c] = powf(in[index+c], LSD_GAMMA);
1428 }
1429}
1430
1431// do actual line_detection based on LSD algorithm and return results according
1432// to this module's conventions
1433static int line_detect(float *in, const int width, const int height, const int x_off, const int y_off,
1434 const float scale, dt_iop_ashift_line_t **alines, int *lcount, int *vcount, int *hcount,
1435 float *vweight, float *hweight, dt_iop_ashift_enhance_t enhance, const int is_raw)
1436{
1437 double *greyscale = NULL;
1438 double *lsd_lines = NULL;
1439 dt_iop_ashift_line_t *ashift_lines = NULL;
1440
1441 int vertical_count = 0;
1442 int horizontal_count = 0;
1443 float vertical_weight = 0.0f;
1444 float horizontal_weight = 0.0f;
1445
1446 // apply gamma correction if image is raw
1447 if(is_raw)
1448 {
1449 gamma_correct(in, in, width, height);
1450 }
1451
1452 // if requested perform an additional detail enhancement step
1453 if(enhance & ASHIFT_ENHANCE_DETAIL)
1454 {
1455 (void)detail_enhance(in, in, width, height);
1456 }
1457
1458 // allocate intermediate buffers
1459 greyscale = malloc(sizeof(double) * width * height);
1460 if(IS_NULL_PTR(greyscale)) goto error;
1461
1462 // convert to greyscale image
1463 rgb2grey256(in, greyscale, width, height);
1464
1465 // if requested perform an additional edge enhancement step
1466 if(enhance & ASHIFT_ENHANCE_EDGES)
1467 {
1468 (void)edge_enhance(greyscale, greyscale, width, height);
1469 }
1470
1471 // call the line segment detector LSD;
1472 // LSD stores the number of found lines in lines_count.
1473 // it returns structural details as vector 'double lines[7 * lines_count]'
1474 int lines_count;
1475
1476 lsd_lines = LineSegmentDetection(&lines_count, greyscale, width, height,
1479 LSD_N_BINS, NULL, NULL, NULL);
1480
1481 // we count the lines that we really want to use
1482 int lct = 0;
1483 if(lines_count > 0)
1484 {
1485 // aggregate lines data into our own structures
1486 ashift_lines = (dt_iop_ashift_line_t *)malloc(sizeof(dt_iop_ashift_line_t) * lines_count);
1487 if(IS_NULL_PTR(ashift_lines)) goto error;
1488
1489 for(int n = 0; n < lines_count; n++)
1490 {
1491 const float x1 = lsd_lines[n * 7 + 0];
1492 const float y1 = lsd_lines[n * 7 + 1];
1493 const float x2 = lsd_lines[n * 7 + 2];
1494 const float y2 = lsd_lines[n * 7 + 3];
1495
1496 // check for lines running along image borders and skip them.
1497 // these would likely be false-positives which could result
1498 // from any kind of processing artifacts
1499 if((fabsf(x1 - x2) < 1 && fmaxf(x1, x2) < 2)
1500 || (fabsf(x1 - x2) < 1 && fminf(x1, x2) > width - 3)
1501 || (fabsf(y1 - y2) < 1 && fmaxf(y1, y2) < 2)
1502 || (fabsf(y1 - y2) < 1 && fminf(y1, y2) > height - 3))
1503 continue;
1504
1505 // line position in absolute coordinates
1506 float px1 = x_off + x1;
1507 float py1 = y_off + y1;
1508 float px2 = x_off + x2;
1509 float py2 = y_off + y2;
1510
1511 // scale back to input buffer
1512 px1 /= scale;
1513 py1 /= scale;
1514 px2 /= scale;
1515 py2 /= scale;
1516
1517 // store as homogeneous coordinates
1518 ashift_lines[lct].p1[0] = px1;
1519 ashift_lines[lct].p1[1] = py1;
1520 ashift_lines[lct].p1[2] = 1.0f;
1521 ashift_lines[lct].p2[0] = px2;
1522 ashift_lines[lct].p2[1] = py2;
1523 ashift_lines[lct].p2[2] = 1.0f;
1524
1525 // calculate homogeneous coordinates of connecting line (defined by the two points)
1526 vec3prodn(ashift_lines[lct].L, ashift_lines[lct].p1, ashift_lines[lct].p2);
1527
1528 // normalaze line coordinates so that x^2 + y^2 = 1
1529 // (this will always succeed as L is a real line connecting two real points)
1530 vec3lnorm(ashift_lines[lct].L, ashift_lines[lct].L);
1531
1532 // length and width of rectangle (see LSD)
1533 ashift_lines[lct].length = sqrt((px2 - px1) * (px2 - px1) + (py2 - py1) * (py2 - py1));
1534 ashift_lines[lct].width = lsd_lines[n * 7 + 4] / scale;
1535
1536 // ... and weight (= length * width * angle precision)
1537 const float weight = ashift_lines[lct].length * ashift_lines[lct].width * lsd_lines[n * 7 + 5];
1538 ashift_lines[lct].weight = weight;
1539
1540
1541 const float angle = atan2f(py2 - py1, px2 - px1) / M_PI * 180.0f;
1542 const int vertical = fabsf(fabsf(angle) - 90.0f) < MAX_TANGENTIAL_DEVIATION ? 1 : 0;
1543 const int horizontal = fabsf(fabsf(fabsf(angle) - 90.0f) - 90.0f) < MAX_TANGENTIAL_DEVIATION ? 1 : 0;
1544
1545 const int relevant = ashift_lines[lct].length > MIN_LINE_LENGTH ? 1 : 0;
1546
1547 // register type of line
1549 if(vertical && relevant)
1550 {
1552 vertical_count++;
1553 vertical_weight += weight;
1554 }
1555 else if(horizontal && relevant)
1556 {
1558 horizontal_count++;
1559 horizontal_weight += weight;
1560 }
1561 ashift_lines[lct].type = type;
1562
1563 // the next valid line
1564 lct++;
1565 }
1566 }
1567#ifdef ASHIFT_DEBUG
1568 printf("%d lines (vertical %d, horizontal %d, not relevant %d)\n", lines_count, vertical_count,
1569 horizontal_count, lct - vertical_count - horizontal_count);
1570 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;
1571 for(int n = 0; n < lct; n++)
1572 {
1573 xmin = fmin(xmin, fmin(ashift_lines[n].p1[0], ashift_lines[n].p2[0]));
1574 xmax = fmax(xmax, fmax(ashift_lines[n].p1[0], ashift_lines[n].p2[0]));
1575 ymin = fmin(ymin, fmin(ashift_lines[n].p1[1], ashift_lines[n].p2[1]));
1576 ymax = fmax(ymax, fmax(ashift_lines[n].p1[1], ashift_lines[n].p2[1]));
1577 printf("x1 %.0f, y1 %.0f, x2 %.0f, y2 %.0f, length %.0f, width %f, X %f, Y %f, Z %f, type %d, scalars %f %f\n",
1578 ashift_lines[n].p1[0], ashift_lines[n].p1[1], ashift_lines[n].p2[0], ashift_lines[n].p2[1],
1579 ashift_lines[n].length, ashift_lines[n].width,
1580 ashift_lines[n].L[0], ashift_lines[n].L[1], ashift_lines[n].L[2], ashift_lines[n].type,
1581 vec3scalar(ashift_lines[n].p1, ashift_lines[n].L),
1582 vec3scalar(ashift_lines[n].p2, ashift_lines[n].L));
1583 }
1584 printf("xmin %.0f, xmax %.0f, ymin %.0f, ymax %.0f\n", xmin, xmax, ymin, ymax);
1585#endif
1586
1587 // store results in provided locations
1588 *lcount = lct;
1589 *vcount = vertical_count;
1590 *vweight = vertical_weight;
1591 *hcount = horizontal_count;
1592 *hweight = horizontal_weight;
1593 *alines = ashift_lines;
1594
1595 // free intermediate buffers
1596 dt_free(lsd_lines);
1597 dt_free(greyscale);
1598 return lct > 0 ? TRUE : FALSE;
1599
1600error:
1601 dt_free(lsd_lines);
1602 dt_free(greyscale);
1603 return FALSE;
1604}
1605
1606// get image from buffer, analyze for structure and save results
1608{
1610
1611 float *buffer = NULL;
1612 int width = 0;
1613 int height = 0;
1614 int x_off = 0;
1615 int y_off = 0;
1616 float scale = 0.0f;
1617
1619 // read buffer data if they are available
1620 if(!IS_NULL_PTR(g->buf))
1621 {
1622 width = g->buf_width;
1623 height = g->buf_height;
1624 x_off = g->buf_x_off;
1625 y_off = g->buf_y_off;
1626 scale = g->buf_scale;
1627
1628 // create a temporary buffer to hold image data
1629 buffer = malloc(sizeof(float) * 4 * (size_t)width * height);
1630 if(!IS_NULL_PTR(buffer))
1631 dt_iop_image_copy_by_size(buffer, g->buf, width, height, 4);
1632 }
1634
1635 // declared before the first `goto error` and NULL-initialised, so the error path may
1636 // free it unconditionally -- line_detect() can fail after allocating
1637 dt_iop_ashift_line_t *lines = NULL;
1638
1639 if(IS_NULL_PTR(buffer)) goto error;
1640
1641 // get rid of old structural data
1642 g->lines_count = 0;
1643 g->vertical_count = 0;
1644 g->horizontal_count = 0;
1645 dt_free(g->lines);
1646 g->lines = NULL; // freed without NULLing: an error return below left this dangling,
1647 // and the next call's dt_free(g->lines) was a double free
1648
1649 int lines_count;
1650 int vertical_count;
1651 int horizontal_count;
1652 float vertical_weight;
1653 float horizontal_weight;
1654
1655 // get new structural data. The trailing flag tells line_detect() the image originates from a
1656 // raw sensor (different edge/contrast expectations than a display-referred JPEG); this holds
1657 // for any raw colorimetry, mosaiced or already-demosaiced sraw/linear DNG, hence needs_rawprepare.
1658 const gboolean raw_origin = dt_image_needs_rawprepare(&module->dev->image_storage);
1659 dt_iop_fmt_log(module, "structure: class=%s raw_origin=%d enhance=%d",
1661 raw_origin, enhance);
1662 if(!line_detect(buffer, width, height, x_off, y_off, scale, &lines, &lines_count,
1663 &vertical_count, &horizontal_count, &vertical_weight, &horizontal_weight,
1664 enhance, raw_origin))
1665 goto error;
1666
1667 // save new structural data
1668 // line_detect() rescales coordinates back to input (full-res) space, so
1669 // we must apply the same scaling to metadata.
1670 const float inv_scale = (scale > 0.f) ? (1.f / scale) : 1.f;
1671 g->lines_in_width = (int)roundf(width * inv_scale);
1672 g->lines_in_height = (int)roundf(height * inv_scale);
1673 g->lines_x_off = (int)roundf(x_off * inv_scale);
1674 g->lines_y_off = (int)roundf(y_off * inv_scale);
1675 g->lines_count = lines_count;
1676 g->vertical_count = vertical_count;
1677 g->horizontal_count = horizontal_count;
1678 g->vertical_weight = vertical_weight;
1679 g->horizontal_weight = horizontal_weight;
1680 g->lines_version++;
1681 g->lines = lines;
1682
1683 dt_free(buffer);
1684 return TRUE;
1685
1686error:
1687 dt_free(lines);
1688 dt_free(buffer);
1689 return FALSE;
1690}
1691
1692
1693// swap two integer values
1694static inline void swap(int *a, int *b)
1695{
1696 int tmp = *a;
1697 *a = *b;
1698 *b = tmp;
1699}
1700
1701// do complete permutations
1702static int quickperm(int *a, int *p, const int N, int *i)
1703{
1704 if(*i >= N) return FALSE;
1705
1706 p[*i]--;
1707 int j = (*i % 2 == 1) ? p[*i] : 0;
1708 swap(&a[j], &a[*i]);
1709 *i = 1;
1710 while(p[*i] == 0)
1711 {
1712 p[*i] = *i;
1713 (*i)++;
1714 }
1715 return TRUE;
1716}
1717
1718// Fisher-Yates shuffle
1719static void shuffle(int *a, const int N)
1720{
1721 for(int i = 0; i < N; i++)
1722 {
1723 int j = i + rand() % (N - i);
1724 swap(&a[j], &a[i]);
1725 }
1726}
1727
1728// factorial function
1729static int fact(const int n)
1730{
1731 return (n == 1 ? 1 : n * fact(n - 1));
1732}
1733
1734// We use a pseudo-RANSAC algorithm to elminiate ouliers from our set of lines. The
1735// original RANSAC works on linear optimization problems. Our model is nonlinear. We
1736// take advantage of the fact that lines interesting for our model are vantage lines
1737// that meet in one vantage point for each subset of lines (vertical/horizontal).
1738// Strategy: we construct a model by (random) sampling within the subset of lines and
1739// calculate the vantage point. Then we check the "distance" of all other lines to the
1740// vantage point. The model that gives highest number of lines combined with the highest
1741// total weight and lowest overall "distance" wins.
1742// Disadvantage: compared to the original RANSAC we don't get any model parameters that
1743// we could use for the following NMS fit.
1744// Self-tuning: we optimize "epsilon", the hurdle rate to reject a line as an outlier,
1745// by a number of dry runs first. The target average percentage value of lines to eliminate as
1746// outliers (without judging on the quality of the model) is given by RANSAC_ELIMINATION_RATIO,
1747// note: the actual percentage of outliers removed in the final run will be lower because we
1748// will finally look for the best quality model with the optimized epsilon and that quality value also
1749// encloses the number of good lines
1750static void ransac(const dt_iop_ashift_line_t *lines, int *index_set, int *inout_set,
1751 const int set_count, const float total_weight, const int xmin, const int xmax,
1752 const int ymin, const int ymax)
1753{
1754 if(set_count < 3) return;
1755
1756 const size_t set_size = set_count * sizeof(int);
1757 int *best_set = malloc(set_size);
1758 memcpy(best_set, index_set, set_size);
1759 int *best_inout = calloc(1, set_size);
1760
1761 float best_quality = 0.0f;
1762
1763 // hurdle value epsilon for rejecting a line as an outlier will be self-tuning
1764 // in a number of dry runs
1765 float epsilon = powf(10.0f, -RANSAC_EPSILON);
1766 float epsilon_step = RANSAC_EPSILON_STEP;
1767 // some accounting variables for self-tuning
1768 int lines_eliminated = 0;
1769 int valid_runs = 0;
1770
1771 // number of runs to optimize epsilon
1773 // go for complete permutations on small set sizes, else for random sample consensus
1774 const int riter = (set_count > RANSAC_HURDLE) ? RANSAC_RUNS : fact(set_count);
1775
1776 // some data needed for quickperm
1777 int *perm = malloc(sizeof(int) * (set_count + 1));
1778 for(int n = 0; n < set_count + 1; n++) perm[n] = n;
1779 int piter = 1;
1780
1781 // inout holds good/bad qualification for each line
1782 int *inout = malloc(set_size);
1783
1784 for(int r = 0; r < optiruns + riter; r++)
1785 {
1786 // get random or systematic variation of index set
1787 if(set_count > RANSAC_HURDLE || r < optiruns)
1788 shuffle(index_set, set_count);
1789 else
1790 (void)quickperm(index_set, perm, set_count, &piter);
1791
1792 // summed quality evaluation of this run
1793 float quality = 0.0f;
1794
1795 // we build a model ouf of the first two lines
1796 const float *L1 = lines[index_set[0]].L;
1797 const float *L2 = lines[index_set[1]].L;
1798
1799 // get intersection point (ideally a vantage point)
1800 float V[3];
1801 vec3prodn(V, L1, L2);
1802
1803 // catch special cases:
1804 // a) L1 and L2 are identical -> V is NULL -> no valid vantage point
1805 // b) vantage point lies inside image frame (no chance to correct for this case)
1806 if(vec3isnull(V) ||
1807 (fabsf(V[2]) > 0.0f &&
1808 V[0]/V[2] >= xmin &&
1809 V[1]/V[2] >= ymin &&
1810 V[0]/V[2] <= xmax &&
1811 V[1]/V[2] <= ymax))
1812 {
1813 // no valid model
1814 quality = 0.0f;
1815 }
1816 else
1817 {
1818 // valid model
1819
1820 // normalize V so that x^2 + y^2 + z^2 = 1
1821 vec3norm(V, V);
1822
1823 // the two lines constituting the model are part of the set
1824 inout[0] = 1;
1825 inout[1] = 1;
1826
1827 // go through all remaining lines, check if they are within the model, and
1828 // mark that fact in inout[].
1829 // summarize a quality parameter for all lines within the model
1830 for(int n = 2; n < set_count; n++)
1831 {
1832 // L is normalized so that x^2 + y^2 = 1
1833 const float *L3 = lines[index_set[n]].L;
1834
1835 // we take the absolute value of the dot product of V and L as a measure
1836 // of the "distance" between point and line. Note that this is not the real euclidean
1837 // distance but - with the given normalization - just a pragmatically selected number
1838 // that goes to zero if V lies on L and increases the more V and L are apart
1839 const float d = fabsf(vec3scalar(V, L3));
1840
1841 // depending on d we either include or exclude the point from the set
1842 inout[n] = (d < epsilon) ? 1 : 0;
1843
1844 float q;
1845
1846 if(inout[n] == 1)
1847 {
1848 // a quality parameter that depends 1/3 on the number of lines within the model,
1849 // 1/3 on their weight, and 1/3 on their weighted distance d to the vantage point
1850 q = 0.33f / (float)set_count
1851 + 0.33f * lines[index_set[n]].weight / total_weight
1852 + 0.33f * (1.0f - d / epsilon) * (float)set_count * lines[index_set[n]].weight / total_weight;
1853 }
1854 else
1855 {
1856 q = 0.0f;
1857 lines_eliminated++;
1858 }
1859
1860 quality += q;
1861 }
1862 valid_runs++;
1863 }
1864
1865 if(r < optiruns)
1866 {
1867 // on last run of each self-tuning step
1868 if((r % RANSAC_OPTIMIZATION_DRY_RUNS) == (RANSAC_OPTIMIZATION_DRY_RUNS - 1) && (valid_runs > 0))
1869 {
1870#ifdef ASHIFT_DEBUG
1871 printf("ransac self-tuning (run %d): epsilon %f", r, epsilon);
1872#endif
1873 // average ratio of lines that we eliminated with the given epsilon
1874 float ratio = 100.0f * (float)lines_eliminated / ((float)set_count * valid_runs);
1875 // adjust epsilon accordingly
1876 if(ratio < RANSAC_ELIMINATION_RATIO)
1877 epsilon = powf(10.0f, log10(epsilon) - epsilon_step);
1878 else if(ratio > RANSAC_ELIMINATION_RATIO)
1879 epsilon = powf(10.0f, log10(epsilon) + epsilon_step);
1880#ifdef ASHIFT_DEBUG
1881 printf(" (elimination ratio %f) -> %f\n", ratio, epsilon);
1882#endif
1883 // reduce step-size for next optimization round
1884 epsilon_step /= 2.0f;
1885 lines_eliminated = 0;
1886 valid_runs = 0;
1887 }
1888 }
1889 else
1890 {
1891 // in the "real" runs check against the best model found so far
1892 if(quality > best_quality)
1893 {
1894 memcpy(best_set, index_set, set_size);
1895 memcpy(best_inout, inout, set_size);
1896 best_quality = quality;
1897 }
1898 }
1899
1900#ifdef ASHIFT_DEBUG
1901 // report some statistics
1902 int count = 0, lastcount = 0;
1903 for(int n = 0; n < set_count; n++) count += best_inout[n];
1904 for(int n = 0; n < set_count; n++) lastcount += inout[n];
1905 printf("ransac run %d: best qual %.6f, eps %.6f, line count %d of %d (this run: qual %.5f, count %d (%2f%%))\n", r,
1906 best_quality, epsilon, count, set_count, quality, lastcount, 100.0f * lastcount / (float)set_count);
1907#endif
1908 }
1909
1910 // store back best set
1911 memcpy(index_set, best_set, set_size);
1912 memcpy(inout_set, best_inout, set_size);
1913
1914 dt_free(inout);
1915 dt_free(perm);
1916 dt_free(best_inout);
1917 dt_free(best_set);
1918}
1919
1920
1921// try to clean up structural data by eliminating outliers and thereby increasing
1922// the chance of a convergent fitting
1924{
1926
1927 const int width = g->lines_in_width;
1928 const int height = g->lines_in_height;
1929 const int xmin = g->lines_x_off;
1930 const int ymin = g->lines_y_off;
1931 const int xmax = xmin + width;
1932 const int ymax = ymin + height;
1933
1934 // holds the index set of lines we want to work on
1935 int *lines_set = malloc(sizeof(int) * g->lines_count);
1936 // holds the result of ransac
1937 int *inout_set = malloc(sizeof(int) * g->lines_count);
1938
1939 // some accounting variables
1940 int vnb = 0, vcount = 0;
1941 int hnb = 0, hcount = 0;
1942
1943 // just to be on the safe side
1944 if(IS_NULL_PTR(g->lines)) goto error;
1945
1946 // generate index list for the vertical lines
1947 for(int n = 0; n < g->lines_count; n++)
1948 {
1949 // is this a selected vertical line?
1950 if((g->lines[n].type & ASHIFT_LINE_MASK) != ASHIFT_LINE_VERTICAL_SELECTED)
1951 continue;
1952
1953 lines_set[vnb] = n;
1954 inout_set[vnb] = 0;
1955 vnb++;
1956 }
1957
1958 // it only makes sense to call ransac if we have more than two lines
1959 if(vnb > 2)
1960 ransac(g->lines, lines_set, inout_set, vnb, g->vertical_weight,
1961 xmin, xmax, ymin, ymax);
1962
1963 // adjust line selected flag according to the ransac results
1964 for(int n = 0; n < vnb; n++)
1965 {
1966 const int m = lines_set[n];
1967 if(inout_set[n] == 1)
1968 {
1969 g->lines[m].type |= ASHIFT_LINE_SELECTED;
1970 vcount++;
1971 }
1972 else
1973 g->lines[m].type &= ~ASHIFT_LINE_SELECTED;
1974 }
1975 // update number of vertical lines
1976 g->vertical_count = vcount;
1977 g->lines_version++;
1978
1979 // now generate index list for the horizontal lines
1980 for(int n = 0; n < g->lines_count; n++)
1981 {
1982 // is this a selected horizontal line?
1983 if((g->lines[n].type & ASHIFT_LINE_MASK) != ASHIFT_LINE_HORIZONTAL_SELECTED)
1984 continue;
1985
1986 lines_set[hnb] = n;
1987 inout_set[hnb] = 0;
1988 hnb++;
1989 }
1990
1991 // it only makes sense to call ransac if we have more than two lines
1992 if(hnb > 2)
1993 ransac(g->lines, lines_set, inout_set, hnb, g->horizontal_weight,
1994 xmin, xmax, ymin, ymax);
1995
1996 // adjust line selected flag according to the ransac results
1997 for(int n = 0; n < hnb; n++)
1998 {
1999 const int m = lines_set[n];
2000 if(inout_set[n] == 1)
2001 {
2002 g->lines[m].type |= ASHIFT_LINE_SELECTED;
2003 hcount++;
2004 }
2005 else
2006 g->lines[m].type &= ~ASHIFT_LINE_SELECTED;
2007 }
2008 // update number of horizontal lines
2009 g->horizontal_count = hcount;
2010 g->lines_version++;
2011
2012 dt_free(inout_set);
2013 dt_free(lines_set);
2014
2015 return TRUE;
2016
2017error:
2018 dt_free(inout_set);
2019 dt_free(lines_set);
2020 return FALSE;
2021}
2022
2023// utility function to map a variable in [min; max] to [-INF; + INF]
2024static inline double logit(double x, double min, double max)
2025{
2026 const double eps = 1.0e-6;
2027 // make sure p does not touch the borders of its definition area,
2028 // not critical for data accuracy as logit() is only used on initial fit parameters
2029 double p = CLAMP((x - min) / (max - min), eps, 1.0 - eps);
2030
2031 return (2.0 * atanh(2.0 * p - 1.0));
2032}
2033
2034// inverted function to logit()
2035static inline double ilogit(double L, double min, double max)
2036{
2037 double p = 0.5 * (1.0 + tanh(0.5 * L));
2038
2039 return (p * (max - min) + min);
2040}
2041
2042// helper function for simplex() return quality parameter for the given model
2043// strategy:
2044// * generate homography matrix out of fixed parameters and fitting parameters
2045// * apply homography to all end points of affected lines
2046// * generate new line out of transformed end points
2047// * calculate scalar product s of line with perpendicular axis
2048// * sum over weighted s^2 values
2049static double model_fitness(double *params, void *data)
2050{
2052
2053 // just for convenience: get shorter names
2054 dt_iop_ashift_line_t *lines = fit->lines;
2055 const int lines_count = fit->lines_count;
2056 const int width = fit->width;
2057 const int height = fit->height;
2058 const float f_length_kb = fit->f_length_kb;
2059 const float orthocorr = fit->orthocorr;
2060 const float aspect = fit->aspect;
2061
2062 float rotation = fit->rotation;
2063 float lensshift_v = fit->lensshift_v;
2064 float lensshift_h = fit->lensshift_h;
2065 float shear = fit->shear;
2066 float rotation_range = fit->rotation_range;
2067 float lensshift_v_range = fit->lensshift_v_range;
2068 float lensshift_h_range = fit->lensshift_h_range;
2069 float shear_range = fit->shear_range;
2070
2071 int pcount = 0;
2072
2073 // fill in fit parameters from params[]. Attention: order matters!!!
2074 if(isnan(rotation))
2075 {
2076 rotation = ilogit(params[pcount], -rotation_range, rotation_range);
2077 pcount++;
2078 }
2079
2080 if(isnan(lensshift_v))
2081 {
2082 lensshift_v = ilogit(params[pcount], -lensshift_v_range, lensshift_v_range);
2083 pcount++;
2084 }
2085
2086 if(isnan(lensshift_h))
2087 {
2088 lensshift_h = ilogit(params[pcount], -lensshift_h_range, lensshift_h_range);
2089 pcount++;
2090 }
2091
2092 if(isnan(shear))
2093 {
2094 shear = ilogit(params[pcount], -shear_range, shear_range);
2095 pcount++;
2096 }
2097
2098 assert(pcount == fit->params_count);
2099
2100 // the possible reference axes
2101 const float Av[3] = { 1.0f, 0.0f, 0.0f };
2102 const float Ah[3] = { 0.0f, 1.0f, 0.0f };
2103
2104 // generate homograph out of the parameters
2105 float homograph[3][3];
2106 homography((float *)homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb,
2107 orthocorr, aspect, width, height, ASHIFT_HOMOGRAPH_FORWARD);
2108
2109 // accounting variables
2110 double sumsq_v = 0.0;
2111 double sumsq_h = 0.0;
2112 double weight_v = 0.0;
2113 double weight_h = 0.0;
2114 int count_v = 0;
2115 int count_h = 0;
2116 int count = 0;
2117
2118 // iterate over all lines
2119 for(int n = 0; n < lines_count; n++)
2120 {
2121 // check if this is a line which we must skip
2122 if((lines[n].type & fit->linemask) != fit->linetype)
2123 continue;
2124
2125 // the direction of this line (vertical?)
2126 const int isvertical = lines[n].type & ASHIFT_LINE_DIRVERT;
2127
2128 // select the perpendicular reference axis
2129 const float *A = isvertical ? Ah : Av;
2130
2131 // apply homographic transformation to the end points
2132 float P1[3], P2[3];
2133 mat3mulv(P1, (float *)homograph, lines[n].p1);
2134 mat3mulv(P2, (float *)homograph, lines[n].p2);
2135
2136 // get line connecting the two points
2137 float L[3];
2138 vec3prodn(L, P1, P2);
2139
2140 // normalize L so that x^2 + y^2 = 1; makes sure that
2141 // y^2 = 1 / (1 + m^2) and x^2 = m^2 / (1 + m^2) with m defining the slope of the line
2142 vec3lnorm(L, L);
2143
2144 // get scalar product of line L with orthogonal axis A -> gives 0 if line is perpendicular
2145 float s = vec3scalar(L, A);
2146
2147 // sum up weighted s^2 for both directions individually
2148 sumsq_v += isvertical ? s * s * lines[n].weight : 0.0;
2149 weight_v += isvertical ? lines[n].weight : 0.0;
2150 count_v += isvertical ? 1 : 0;
2151 sumsq_h += !isvertical ? s * s * lines[n].weight : 0.0;
2152 weight_h += !isvertical ? lines[n].weight : 0.0;
2153 count_h += !isvertical ? 1 : 0;
2154 count++;
2155 }
2156
2157 const double v = weight_v > 0.0f && count > 0 ? sumsq_v / weight_v * (float)count_v / count : 0.0;
2158 const double h = weight_h > 0.0f && count > 0 ? sumsq_h / weight_h * (float)count_h / count : 0.0;
2159
2160 double sum = sqrt(1.0 - (1.0 - v) * (1.0 - h)) * 1.0e6;
2161 //double sum = sqrt(v + h) * 1.0e6;
2162
2163#ifdef ASHIFT_DEBUG
2164 printf("fitness with rotation %f, lensshift_v %f, lensshift_h %f, shear %f -> lines %d, quality %10f\n",
2165 rotation, lensshift_v, lensshift_h, shear, count, sum);
2166#endif
2167
2168 return sum;
2169}
2170
2171// setup all data structures for fitting and call NM simplex
2173{
2175
2176 if(IS_NULL_PTR(g->lines)) return NMS_NOT_ENOUGH_LINES;
2177 if(dir == ASHIFT_FIT_NONE) return NMS_SUCCESS;
2178
2179 double params[4];
2180 int pcount = 0;
2181 int enough_lines = TRUE;
2182
2183 // initialize fit parameters
2185 fit.lines = g->lines;
2186 fit.lines_count = g->lines_count;
2187 fit.width = g->lines_in_width;
2188 fit.height = g->lines_in_height;
2189 fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor;
2190 fit.orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;
2191 fit.aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;
2192 fit.rotation = p->rotation;
2193 fit.lensshift_v = p->lensshift_v;
2194 fit.lensshift_h = p->lensshift_h;
2195 fit.shear = p->shear;
2196 fit.rotation_range = g->rotation_range;
2197 fit.lensshift_v_range = g->lensshift_v_range;
2198 fit.lensshift_h_range = g->lensshift_h_range;
2199 fit.shear_range = g->shear_range;
2202 fit.params_count = 0;
2203 fit.weight = 0.0f;
2204
2205 // if the image is flipped and if we do not want to fit both lens shift
2206 // directions or none at all, then we need to change direction
2207 dt_iop_ashift_fitaxis_t mdir = dir;
2209 (mdir & ASHIFT_FIT_LENS_BOTH) != 0)
2210 {
2211 // flip all directions
2212 mdir ^= g->isflipped ? ASHIFT_FIT_FLIP : 0;
2213 // special case that needs to be corrected
2214 mdir |= (mdir & ASHIFT_FIT_LINES_BOTH) == 0 ? ASHIFT_FIT_LINES_BOTH : 0;
2215 }
2216
2217
2218 // prepare fit structure and starting parameters for simplex fit.
2219 // note: the sequence of parameters in params[] needs to match the
2220 // respective order in dt_iop_ashift_fit_params_t. Parameters which are
2221 // to be fittet are marked with NAN in the fit structure. Non-NAN
2222 // parameters are assumed to be constant.
2223 if(mdir & ASHIFT_FIT_ROTATION)
2224 {
2225 // we fit rotation
2226 fit.params_count++;
2227 params[pcount] = logit(fit.rotation, -fit.rotation_range, fit.rotation_range);
2228 pcount++;
2229 fit.rotation = NAN;
2230 }
2231
2232 if(mdir & ASHIFT_FIT_LENS_VERT)
2233 {
2234 // we fit vertical lens shift
2235 fit.params_count++;
2236 params[pcount] = logit(fit.lensshift_v, -fit.lensshift_v_range, fit.lensshift_v_range);
2237 pcount++;
2238 fit.lensshift_v = NAN;
2239 }
2240
2241 if(mdir & ASHIFT_FIT_LENS_HOR)
2242 {
2243 // we fit horizontal lens shift
2244 fit.params_count++;
2245 params[pcount] = logit(fit.lensshift_h, -fit.lensshift_h_range, fit.lensshift_h_range);
2246 pcount++;
2247 fit.lensshift_h = NAN;
2248 }
2249
2250 if(mdir & ASHIFT_FIT_SHEAR)
2251 {
2252 // we fit the shear parameter
2253 fit.params_count++;
2254 params[pcount] = logit(fit.shear, -fit.shear_range, fit.shear_range);
2255 pcount++;
2256 fit.shear = NAN;
2257 }
2258
2259 if(mdir & ASHIFT_FIT_LINES_VERT)
2260 {
2261 // we use vertical lines for fitting
2263 fit.weight += g->vertical_weight;
2264 enough_lines = enough_lines && (g->vertical_count >= MINIMUM_FITLINES);
2265 }
2266
2267 if(mdir & ASHIFT_FIT_LINES_HOR)
2268 {
2269 // we use horizontal lines for fitting
2270 fit.linetype |= 0;
2271 fit.weight += g->horizontal_weight;
2272 enough_lines = enough_lines && (g->horizontal_count >= MINIMUM_FITLINES);
2273 }
2274
2275 // this needs to come after ASHIFT_FIT_LINES_VERT and ASHIFT_FIT_LINES_HOR
2277 {
2278 // if we use fitting in both directions we need to
2279 // adjust fit.linetype and fit.linemask to match all selected lines
2282 }
2283
2284 // error case: we do not run simplex if there are not enough lines
2285 if(!enough_lines)
2286 {
2287#ifdef ASHIFT_DEBUG
2288 printf("optimization not possible: insufficient number of lines\n");
2289#endif
2290 return NMS_NOT_ENOUGH_LINES;
2291 }
2292
2293 // start the simplex fit
2294 int iter = simplex(model_fitness, params, fit.params_count, NMS_EPSILON, NMS_SCALE, NMS_ITERATIONS, NULL, (void*)&fit);
2295
2296 // error case: the fit did not converge
2297 if(iter >= NMS_ITERATIONS)
2298 {
2299#ifdef ASHIFT_DEBUG
2300 printf("optimization not successful: maximum number of iterations reached (%d)\n", iter);
2301#endif
2302 return NMS_DID_NOT_CONVERGE;
2303 }
2304
2305 // fit was successful: now consolidate the results (order matters!!!)
2306 pcount = 0;
2307 fit.rotation = isnan(fit.rotation) ? ilogit(params[pcount++], -fit.rotation_range, fit.rotation_range) : fit.rotation;
2308 fit.lensshift_v = isnan(fit.lensshift_v) ? ilogit(params[pcount++], -fit.lensshift_v_range, fit.lensshift_v_range) : fit.lensshift_v;
2309 fit.lensshift_h = isnan(fit.lensshift_h) ? ilogit(params[pcount++], -fit.lensshift_h_range, fit.lensshift_h_range) : fit.lensshift_h;
2310 fit.shear = isnan(fit.shear) ? ilogit(params[pcount++], -fit.shear_range, fit.shear_range) : fit.shear;
2311#ifdef ASHIFT_DEBUG
2312 printf("params after optimization (%d iterations): rotation %f, lensshift_v %f, lensshift_h %f, shear %f\n",
2313 iter, fit.rotation, fit.lensshift_v, fit.lensshift_h, fit.shear);
2314#endif
2315
2316 // sanity check: in case of extreme values the image gets distorted so strongly that it spans an insanely huge area. we check that
2317 // case and assume values that increase the image area by more than a factor of 4 as being insane.
2318 float homograph[3][3];
2319 homography((float *)homograph, fit.rotation, fit.lensshift_v, fit.lensshift_h, fit.shear, fit.f_length_kb,
2321
2322 // visit all four corners and find maximum span
2323 float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX;
2324 for(int y = 0; y < fit.height; y += fit.height - 1)
2325 for(int x = 0; x < fit.width; x += fit.width - 1)
2326 {
2327 float pi[3], po[3];
2328 pi[0] = x;
2329 pi[1] = y;
2330 pi[2] = 1.0f;
2331 mat3mulv(po, (float *)homograph, pi);
2332 po[0] /= po[2];
2333 po[1] /= po[2];
2334 xm = fmin(xm, po[0]);
2335 ym = fmin(ym, po[1]);
2336 xM = fmax(xM, po[0]);
2337 yM = fmax(yM, po[1]);
2338 }
2339
2340 if((xM - xm) * (yM - ym) > 4.0f * fit.width * fit.height)
2341 {
2342#ifdef ASHIFT_DEBUG
2343 printf("optimization not successful: degenerate case with area growth factor (%f) exceeding limits\n",
2344 (xM - xm) * (yM - ym) / (fit.width * fit.height));
2345#endif
2346 return NMS_INSANE;
2347 }
2348
2349 // now write the results into structure p
2350 p->rotation = fit.rotation;
2351 p->lensshift_v = fit.lensshift_v;
2352 p->lensshift_h = fit.lensshift_h;
2353 p->shear = fit.shear;
2354 return NMS_SUCCESS;
2355}
2356
2357#ifdef ASHIFT_DEBUG
2358// only used in development phase. call model_fitness() with current parameters and
2359// print some useful information
2360static void model_probe(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_iop_ashift_fitaxis_t dir)
2361{
2363
2364 if(IS_NULL_PTR(g->lines)) return;
2365 if(dir == ASHIFT_FIT_NONE) return;
2366
2367 double params[4];
2368 int enough_lines = TRUE;
2369
2370 // initialize fit parameters
2372 fit.lines = g->lines;
2373 fit.lines_count = g->lines_count;
2374 fit.width = g->lines_in_width;
2375 fit.height = g->lines_in_height;
2376 fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor;
2377 fit.orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;
2378 fit.aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;
2379 fit.rotation = p->rotation;
2380 fit.lensshift_v = p->lensshift_v;
2381 fit.lensshift_h = p->lensshift_h;
2382 fit.shear = p->shear;
2385 fit.params_count = 0;
2386 fit.weight = 0.0f;
2387
2388 // if the image is flipped and if we do not want to fit both lens shift
2389 // directions or none at all, then we need to change direction
2390 dt_iop_ashift_fitaxis_t mdir = dir;
2392 (mdir & ASHIFT_FIT_LENS_BOTH) != 0)
2393 {
2394 // flip all directions
2395 mdir ^= g->isflipped ? ASHIFT_FIT_FLIP : 0;
2396 // special case that needs to be corrected
2397 mdir |= (mdir & ASHIFT_FIT_LINES_BOTH) == 0 ? ASHIFT_FIT_LINES_BOTH : 0;
2398 }
2399
2400 if(mdir & ASHIFT_FIT_LINES_VERT)
2401 {
2402 // we use vertical lines for fitting
2404 fit.weight += g->vertical_weight;
2405 enough_lines = enough_lines && (g->vertical_count >= MINIMUM_FITLINES);
2406 }
2407
2408 if(mdir & ASHIFT_FIT_LINES_HOR)
2409 {
2410 // we use horizontal lines for fitting
2411 fit.linetype |= 0;
2412 fit.weight += g->horizontal_weight;
2413 enough_lines = enough_lines && (g->horizontal_count >= MINIMUM_FITLINES);
2414 }
2415
2416 // this needs to come after ASHIFT_FIT_LINES_VERT and ASHIFT_FIT_LINES_HOR
2418 {
2419 // if we use fitting in both directions we need to
2420 // adjust fit.linetype and fit.linemask to match all selected lines
2423 }
2424
2425 double quality = model_fitness(params, (void *)&fit);
2426
2427 printf("model fitness: %.8f (rotation %f, lensshift_v %f, lensshift_h %f, shear %f)\n",
2428 quality, p->rotation, p->lensshift_v, p->lensshift_h, p->shear);
2429}
2430#endif
2431
2432// function to keep crop fitting parameters within constraints
2433static void crop_constraint(double *params, int pcount)
2434{
2435 if(pcount > 0) params[0] = fabs(params[0]);
2436 if(pcount > 1) params[1] = fabs(params[1]);
2437 if(pcount > 2) params[2] = fabs(params[2]);
2438
2439 if(pcount > 0 && params[0] > 1.0) params[0] = 1.0 - params[0];
2440 if(pcount > 1 && params[1] > 1.0) params[1] = 1.0 - params[1];
2441 if(pcount > 2 && params[2] > 0.5*M_PI) params[2] = 0.5*M_PI - params[2];
2442}
2443
2444// helper function for getting the best fitting crop area;
2445// returns the negative area of the largest rectangle that fits within the
2446// defined image with a given rectangle's center and its aspect angle;
2447// the trick: the rectangle center coordinates are given in the input
2448// image coordinates so we know for sure that it also lies within the image after
2449// conversion to the output coordinates
2450static double crop_fitness(double *params, void *data)
2451{
2453
2454 const float wd = cropfit->width;
2455 const float ht = cropfit->height;
2456
2457 // get variable and constant parameters, respectively
2458 const float x = isnan(cropfit->x) ? params[0] : cropfit->x;
2459 const float y = isnan(cropfit->y) ? params[1] : cropfit->y;
2460 const float alpha = isnan(cropfit->alpha) ? params[2] : cropfit->alpha;
2461
2462 // the center of the rectangle in input image coordinates
2463 const float Pc[3] = { x * wd, y * ht, 1.0f };
2464
2465 // convert to the output image coordinates and normalize
2466 float P[3];
2467 mat3mulv(P, (float *)cropfit->homograph, Pc);
2468 P[0] /= P[2];
2469 P[1] /= P[2];
2470 P[2] = 1.0f;
2471
2472 // two auxiliary points (some arbitrary distance away from P) to construct the diagonals
2473 const float Pa[2][3] = { { P[0] + 10.0f * cosf(alpha), P[1] + 10.0f * sinf(alpha), 1.0f },
2474 { P[0] + 10.0f * cosf(alpha), P[1] - 10.0f * sinf(alpha), 1.0f } };
2475
2476 // the two diagonals: D = P x Pa
2477 float D[2][3];
2478 vec3prodn(D[0], P, Pa[0]);
2479 vec3prodn(D[1], P, Pa[1]);
2480
2481 // find all intersection points of all four edges with both diagonals (I = E x D);
2482 // the shortest distance d2min of the intersection point I to the crop area center P determines
2483 // the size of the crop area that still fits into the image (for the given center and aspect angle)
2484 float d2min = FLT_MAX;
2485 for(int k = 0; k < 4; k++)
2486 for(int l = 0; l < 2; l++)
2487 {
2488 // the intersection point
2489 float I[3];
2490 vec3prodn(I, cropfit->edges[k], D[l]);
2491
2492 // special case: I is all null -> E and D are identical -> P lies on E -> d2min = 0
2493 if(vec3isnull(I))
2494 {
2495 d2min = 0.0f;
2496 break;
2497 }
2498
2499 // special case: I[2] is 0.0f -> E and D are parallel and intersect at infinity -> no relevant point
2500 if(I[2] == 0.0f)
2501 continue;
2502
2503 // the default case -> normalize I
2504 I[0] /= I[2];
2505 I[1] /= I[2];
2506
2507 // calculate distance from I to P
2508 const float d2 = SQR(P[0] - I[0]) + SQR(P[1] - I[1]);
2509
2510 // the minimum distance over all intersection points
2511 d2min = MIN(d2min, d2);
2512 }
2513
2514 // calculate the area of the rectangle
2515 const float A = 2.0f * d2min * sinf(2.0f * alpha);
2516
2517#ifdef ASHIFT_DEBUG
2518 printf("crop fitness with x %f, y %f, angle %f -> distance %f, area %f\n",
2519 x, y, alpha, d2min, A);
2520#endif
2521 // and return -A to allow Nelder-Mead simplex to search for the minimum
2522 return -A;
2523}
2524
2525// strategy: for a given center of the crop area and a specific aspect angle
2526// we calculate the largest crop area that still lies within the output image;
2527// now we allow a Nelder-Mead simplex to search for the center coordinates
2528// (and optionally the aspect angle) that delivers the largest overall crop area.
2530{
2532
2533 // Resetting the crop does not depend on pipeline geometry. Do it before looking up buf_in so
2534 // "off" also works during initialization or after a cache-only preview pass.
2535 if(p->cropmode == ASHIFT_CROP_OFF)
2536 {
2539 if(g->editing)
2540 {
2544 }
2545 return;
2546 }
2547
2548 // Auto-crop is a purely geometric fit: it only needs ashift's *full* (uncropped) input size, not
2549 // pixel data. Read it from the geometry record, which is GUI-thread state and remains available
2550 // when the preview worker exact-hits downstream cache entries without running ashift's process()
2551 // to populate g->buf. ashift's roi_in is crop-dependent, while buf_in is the
2552 // stable full input geometry required by the fit.
2553 int crop_width = 0, crop_height = 0;
2554 dt_iop_roi_t crop_in;
2555 if(dt_dev_module_geometry_gui(self->dev, self, &crop_in, NULL) && crop_in.width > 0 && crop_in.height > 0)
2556 {
2557 crop_width = crop_in.width;
2558 crop_height = crop_in.height;
2559 }
2560 else
2561 {
2562 // Fall back to the captured buffer size if the preview geometry is not ready yet.
2563 crop_width = g->buf_width;
2564 crop_height = g->buf_height;
2565 }
2566
2567 // if sizes are not ready (module disabled / preview not computed yet), just ignore this
2568 if(crop_width == 0 || crop_height == 0) return;
2569
2570 // skip if fitting is still running
2571 if(g->fitting) return;
2572
2573 // Changing the crop changes the output geometry and thus where the control-line overlay lands.
2574 // Drop the overlay's cached screen coordinates so gui_post_expose() recomputes them against the
2575 // virtual-pipe geometry that the resync below makes current (#710 overlay lag).
2577
2578 g->fitting = 1;
2579
2580 double params[3];
2581 int pcount;
2582
2583 // get parameters for the homograph
2584 const float f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor;
2585 const float orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;
2586 const float aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;
2587 const float rotation = p->rotation;
2588 const float lensshift_v = p->lensshift_v;
2589 const float lensshift_h = p->lensshift_h;
2590 const float shear = p->shear;
2591
2592 // prepare structure of constant parameters
2594 cropfit.width = crop_width;
2595 cropfit.height = crop_height;
2596 homography((float *)cropfit.homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb,
2597 orthocorr, aspect, cropfit.width, cropfit.height, ASHIFT_HOMOGRAPH_FORWARD);
2598
2599 const float wd = cropfit.width;
2600 const float ht = cropfit.height;
2601 // The crop rectangle is solved in ashift coordinates. A later orientation swap rotates both the
2602 // rectangle and the image, preserving their relative aspect; swapping width/height here too
2603 // would therefore invert "original format" on portrait images.
2604 const float crop_alpha = atan2f(ht, wd);
2605
2606 // the four vertices of the image in input image coordinates
2607 const float Vc[4][3] = { { 0.0f, 0.0f, 1.0f },
2608 { 0.0f, ht, 1.0f },
2609 { wd, ht, 1.0f },
2610 { wd, 0.0f, 1.0f } };
2611
2612 // convert the vertices to output image coordinates
2613 float V[4][3];
2614 for(int n = 0; n < 4; n++)
2615 mat3mulv(V[n], (float *)cropfit.homograph, Vc[n]);
2616
2617 // get width and height of output image for later use
2618 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;
2619 for(int n = 0; n < 4; n++)
2620 {
2621 // normalize V
2622 V[n][0] /= V[n][2];
2623 V[n][1] /= V[n][2];
2624 V[n][2] = 1.0f;
2625 xmin = MIN(xmin, V[n][0]);
2626 xmax = MAX(xmax, V[n][0]);
2627 ymin = MIN(ymin, V[n][1]);
2628 ymax = MAX(ymax, V[n][1]);
2629 }
2630 const float owd = xmax - xmin;
2631 const float oht = ymax - ymin;
2632
2633 // calculate the lines defining the four edges of the image area: E = V[n] x V[n+1]
2634 for(int n = 0; n < 4; n++)
2635 vec3prodn(cropfit.edges[n], V[n], V[(n + 1) % 4]);
2636
2637 // initial fit parameters: crop area is centered and aspect angle is that of the original image
2638 // number of parameters: fit only crop center coordinates with a fixed aspect ratio, or fit all three variables
2639 if(p->cropmode == ASHIFT_CROP_LARGEST)
2640 {
2641 params[0] = 0.5;
2642 params[1] = 0.5;
2643 params[2] = crop_alpha;
2644 cropfit.x = NAN;
2645 cropfit.y = NAN;
2646 cropfit.alpha = NAN;
2647 pcount = 3;
2648 }
2649 else //(p->cropmode == ASHIFT_CROP_ASPECT)
2650 {
2651 params[0] = 0.5;
2652 params[1] = 0.5;
2653 cropfit.x = NAN;
2654 cropfit.y = NAN;
2655 cropfit.alpha = crop_alpha;
2656 pcount = 2;
2657 }
2658
2659 // NMS_CROP_EPSILON (100 px^2) is a fixed absolute tolerance, but crop_fitness's magnitude scales
2660 // with the image's area (tens of millions of px^2) — is_near identity params, the optimum is already
2661 // ~the initial guess, the landscape goes near-flat, and floating-point noise alone keeps the
2662 // simplex spread above this tiny absolute value forever, burning all NMS_CROP_ITERATIONS. Scale
2663 // the tolerance with image area (floored at the original constant) instead.
2664 const double crop_epsilon = fmax(NMS_CROP_EPSILON, 1e-4 * (double)crop_width * (double)crop_height);
2665
2666 // start the simplex fit
2667 const int iter = simplex(crop_fitness, params, pcount, crop_epsilon, NMS_CROP_SCALE, NMS_CROP_ITERATIONS,
2668 crop_constraint, (void*)&cropfit);
2669 // in case the fit did not converge -> failed
2670 if(iter >= NMS_CROP_ITERATIONS) goto failed;
2671
2672 // the fit did converge -> get clipping margins out of params:
2673 cropfit.x = isnan(cropfit.x) ? params[0] : cropfit.x;
2674 cropfit.y = isnan(cropfit.y) ? params[1] : cropfit.y;
2675 cropfit.alpha = isnan(cropfit.alpha) ? params[2] : cropfit.alpha;
2676
2677 // the area of the best fitting rectangle
2678 const float A = fabs(crop_fitness(params, (void*)&cropfit));
2679
2680 // unlikely to happen but we need to catch this case
2681 if(A == 0.0f) goto failed;
2682
2683 // we need the half diagonal of that rectangle (this is in output image dimensions);
2684 // no need to check for division by zero here as this case implies A == 0.0f, caught above
2685 const float d = sqrtf(A / (2.0f * sinf(2.0f * cropfit.alpha)));
2686
2687 // the rectangle's center in input image (homogeneous) coordinates
2688 const float Pc[3] = { cropfit.x * wd, cropfit.y * ht, 1.0f };
2689
2690 // convert rectangle center to output image coordinates and normalize
2691 float P[3];
2692 mat3mulv(P, (float *)cropfit.homograph, Pc);
2693 P[0] /= P[2];
2694 P[1] /= P[2];
2695
2696 // calculate clipping margins relative to output image dimensions
2697 p->cl = CLAMP((P[0] - d * cosf(cropfit.alpha)) / owd, 0.0f, 1.0f);
2698 p->cr = CLAMP((P[0] + d * cosf(cropfit.alpha)) / owd, 0.0f, 1.0f);
2699 p->ct = CLAMP((P[1] - d * sinf(cropfit.alpha)) / oht, 0.0f, 1.0f);
2700 p->cb = CLAMP((P[1] + d * sinf(cropfit.alpha)) / oht, 0.0f, 1.0f);
2701
2702 // final sanity check
2703 if(p->cr - p->cl <= 0.0f || p->cb - p->ct <= 0.0f) goto failed;
2704
2705 g->fitting = 0;
2706
2707#ifdef ASHIFT_DEBUG
2708 printf("margins after crop fitting: iter %d, x %f, y %f, angle %f, crop area (%f %f %f %f), width %f, height %f\n",
2709 iter, cropfit.x, cropfit.y, cropfit.alpha, p->cl, p->cr, p->ct, p->cb, wd, ht);
2710#endif
2711
2712 if(g->editing)
2713 {
2717 }
2718 return;
2719
2720failed:
2721 g->fitting = 0;
2722
2723 // At rotation/lensshift (0,0,0) the target crop is trivially the full image regardless of
2724 // whether the simplex formally reports convergence, so a non-convergence here is not worth
2725 // reporting as a failure.
2726 const gboolean identity_transform = (p->rotation == 0.0f && p->lensshift_v == 0.0f && p->lensshift_h == 0.0f);
2727 if(!identity_transform)
2728 dt_control_log(_("Automatic cropping failed. Keeping previous margins."));
2729
2730 return;
2731}
2732
2733// determine if the line is vertical or horizontal
2735{
2737 if(fabsf(line->p1[0] - line->p2[0]) > fabsf(line->p1[1] - line->p2[1]))
2739 line->type = linetype;
2740}
2741
2742// add a basic line. used for drawing perspective method
2743static void _draw_basic_line(dt_iop_ashift_line_t *line, float x1, float y1, float x2, float y2,
2745{
2746 // store as homogeneous coordinates
2747 line->p1[0] = x1;
2748 line->p1[1] = y1;
2749 line->p1[2] = 1.0f;
2750 line->p2[0] = x2;
2751 line->p2[1] = y2;
2752 line->p2[2] = 1.0f;
2753
2754 // calculate homogeneous coordinates of connecting line (defined by the two points)
2755 vec3prodn(line->L, line->p1, line->p2);
2756
2757 // normalaze line coordinates so that x^2 + y^2 = 1
2758 // (this will always succeed as L is a real line connecting two real points)
2759 vec3lnorm(line->L, line->L);
2760
2761 // length and width of rectangle (see LSD)
2762 line->length = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
2763 line->width = 1.0f;
2764 line->weight = 1.0f;
2765
2766 // register type of line
2767 line->type = type;
2768}
2769
2771{
2773 gtk_widget_set_sensitive(g->fit_v, enable);
2774 gtk_widget_set_sensitive(g->fit_h, enable);
2775 gtk_widget_set_sensitive(g->fit_both, enable);
2776}
2777
2779{
2780 // to save drawn lines in parameters, we only need extremas positions
2781 // this positions needs to be saved in "original image" reference
2782
2785 if(IS_NULL_PTR(g) || IS_NULL_PTR(p)) return;
2786
2787 // save quad lines (we only handle the 2 vertical lines)
2788 if(g->current_structure_method == ASHIFT_METHOD_QUAD && g->lines && g->lines_count >= 4)
2789 {
2790 float pts[8] = { g->lines[0].p1[0], g->lines[0].p1[1], g->lines[0].p2[0],
2791 g->lines[0].p2[1], g->lines[1].p1[0], g->lines[1].p1[1],
2792 g->lines[1].p2[0], g->lines[1].p2[1] };
2795 {
2796 for(int i = 0; i < 8; i++) p->last_quad_lines[i] = pts[i];
2797 }
2798 }
2799 // save drawn lines (we drop the unselected ones)
2800 if(g->current_structure_method == ASHIFT_METHOD_LINES && g->lines)
2801 {
2802 p->last_drawn_lines_count = 0;
2803
2804 for(int i = 0; i < g->lines_count; i++)
2805 {
2806 // we only save selected lines, not removed ones
2807 if(g->lines[i].type == ASHIFT_LINE_HORIZONTAL_SELECTED
2808 || g->lines[i].type == ASHIFT_LINE_VERTICAL_SELECTED)
2809 {
2810 p->last_drawn_lines[p->last_drawn_lines_count * 4 ] = g->lines[i].p1[0];
2811 p->last_drawn_lines[p->last_drawn_lines_count * 4 + 1] = g->lines[i].p1[1];
2812 p->last_drawn_lines[p->last_drawn_lines_count * 4 + 2] = g->lines[i].p2[0];
2813 p->last_drawn_lines[p->last_drawn_lines_count * 4 + 3] = g->lines[i].p2[1];
2814 p->last_drawn_lines_count++;
2815 if(p->last_drawn_lines_count >= MAX_SAVED_LINES) break;
2816 }
2817 }
2819 DT_DEV_TRANSFORM_DIR_BACK_EXCL, p->last_drawn_lines,
2820 p->last_drawn_lines_count * 2);
2821 }
2822}
2823
2825{
2826 // parameters contains lines extremas positions in "original image" reference
2827 // so we need to translate them in module input reference
2828 // and to compute length and ... values
2829
2832 if(IS_NULL_PTR(g) || IS_NULL_PTR(p)) return FALSE;
2833
2834 /* The saved lines are in the raw frame, and so is the size they are measured against -- what
2835 * used to be read off a pipe piece's iwidth/iheight, which is the pipe's own input size. Ask
2836 * the dev for it, the same way _do_get_structure_lines() below does. */
2837 int32_t raw_width = 0;
2838 int32_t raw_height = 0;
2839 if(!dt_dev_geometry_get_raw_size(self->dev, &raw_width, &raw_height)) return FALSE;
2840
2841 if(method == ASHIFT_METHOD_QUAD
2842 && p->last_quad_lines[0] > 0.0f && p->last_quad_lines[1] > 0.0f
2843 && p->last_quad_lines[2] > 0.0f && p->last_quad_lines[3] > 0.0f)
2844 {
2845 float pts[8] = { p->last_quad_lines[0], p->last_quad_lines[1],
2846 p->last_quad_lines[2], p->last_quad_lines[3],
2847 p->last_quad_lines[4], p->last_quad_lines[5],
2848 p->last_quad_lines[6], p->last_quad_lines[7] };
2851 {
2852 if(g->lines)
2853 {
2854 dt_free(g->lines);
2855 }
2856 g->lines = (dt_iop_ashift_line_t *)g_malloc0(sizeof(dt_iop_ashift_line_t) * 4);
2857 // vertical lines
2858 _draw_basic_line(&g->lines[0], pts[0], pts[1], pts[2], pts[3],
2860 _draw_basic_line(&g->lines[1], pts[4], pts[5], pts[6], pts[7],
2862
2863 // horizontal lines
2864 _draw_basic_line(&g->lines[2], pts[0], pts[1], pts[4], pts[5],
2866 _draw_basic_line(&g->lines[3], pts[2], pts[3], pts[6], pts[7],
2868
2869 g->lines_count = 4;
2870 g->vertical_count = 2;
2871 g->horizontal_count = 2;
2872 g->vertical_weight = 2.0;
2873 g->horizontal_weight = 2.0;
2874 g->lines_in_width = raw_width;
2875 g->lines_in_height = raw_height;
2876 g->current_structure_method = method;
2877 return TRUE;
2878 }
2879 }
2880
2881 if(method == ASHIFT_METHOD_LINES && p->last_drawn_lines_count > 0)
2882 {
2883 float pts[MAX_SAVED_LINES * 4] = { 0.0f };
2884
2885 for(int i = 0; i < p->last_drawn_lines_count * 4; i++)
2886 pts[i] = p->last_drawn_lines[i];
2887
2889 DT_DEV_TRANSFORM_DIR_BACK_EXCL, pts, p->last_drawn_lines_count * 2))
2890 {
2891 if(g->lines)
2892 {
2893 dt_free(g->lines);
2894 }
2895 g->lines = (dt_iop_ashift_line_t *)g_malloc0(sizeof(dt_iop_ashift_line_t) * p->last_drawn_lines_count);
2896
2897 int vnb = 0; // number of vertical lines
2898 int hnb = 0; // number of horizontal lines
2899 for(int i = 0; i < p->last_drawn_lines_count; i++)
2900 {
2901 // determine if the line is vertical or horizontal
2903 if(fabsf(pts[i * 4] - pts[i * 4 + 2]) > fabsf(pts[i * 4 + 1] - pts[i * 4 + 3]))
2905
2906 _draw_basic_line(&g->lines[i], pts[i * 4], pts[i * 4 + 1], pts[i * 4 + 2], pts[i * 4 + 3], linetype);
2907 if(linetype == ASHIFT_LINE_VERTICAL_SELECTED)
2908 vnb++;
2909 else
2910 hnb++;
2911 }
2912
2913 g->lines_count = p->last_drawn_lines_count;
2914 g->vertical_count = vnb;
2915 g->horizontal_count = hnb;
2916 g->vertical_weight = (float)vnb;
2917 g->horizontal_weight = (float)hnb;
2918 g->lines_in_width = raw_width;
2919 g->lines_in_height = raw_height;
2920 g->current_structure_method = method;
2921 return TRUE;
2922 }
2923 }
2924 return FALSE;
2925}
2926
2927// helper function to clean structural data
2928static int _do_clean_structure(dt_iop_module_t *module, dt_iop_ashift_params_t *p, gboolean save_drawn)
2929{
2931
2932 if(g->fitting) return FALSE;
2933
2934 // if needed, we save the actual drawn line
2935 if(save_drawn) _draw_save_lines_to_params(module);
2936
2937 g->fitting = 1;
2938 g->lines_count = 0;
2939 g->vertical_count = 0;
2940 g->horizontal_count = 0;
2941 if(g->lines)
2942 {
2943 dt_free(g->lines);
2944 }
2945 g->lines = NULL;
2946 g->lines_version++;
2947 g->current_structure_method = ASHIFT_METHOD_NONE;
2948 g->fitting = 0;
2949 return TRUE;
2950}
2951
2952// helper function to start analysis for structural data and report about errors
2955{
2957
2958 if(g->fitting) return FALSE;
2959
2960 g->fitting = 1;
2961
2963 float *b = g->buf;
2965
2966 if(IS_NULL_PTR(b))
2967 {
2968 // The preview input buffer is captured by process() on every preview render (process() always
2969 // runs while editing, because the module is in cache-bypass mode). It is simply not ready yet,
2970 // e.g. the click landed before the first edit-mode render completed. Queue the job and let the
2971 // in-flight render publish g->buf; _event_process_after_preview_callback() resumes us. We do not
2972 // poke the cache from here: a module cache-request only renders the pipe up to the previous
2973 // module and never runs ashift's process(), so it could never capture g->buf and would spin (#710).
2975 g->jobparams = enhance;
2976 goto error;
2977 }
2978
2979 if(!_get_structure(self, enhance))
2980 {
2981 dt_control_log(_("could not detect structural data in image"));
2982#ifdef ASHIFT_DEBUG
2983 // find out more
2984 printf("do_get_structure: buf %p, buf_hash %" PRIu64 ", buf_width %d, buf_height %d, lines %p, lines_count %d\n",
2985 g->buf, g->buf_hash, g->buf_width, g->buf_height, g->lines, g->lines_count);
2986#endif
2987 goto error;
2988 }
2989
2990 if(!_remove_outliers(self))
2991 {
2992 dt_control_log(_("could not run outlier removal"));
2993#ifdef ASHIFT_DEBUG
2994 // find out more
2995 printf("_remove_outliers: buf %p, buf_hash %" PRIu64 ", buf_width %d, buf_height %d, lines %p, lines_count %d\n",
2996 g->buf, g->buf_hash, g->buf_width, g->buf_height, g->lines, g->lines_count);
2997#endif
2998 goto error;
2999 }
3000
3002
3003 g->fitting = 0;
3004 return TRUE;
3005
3006error:
3007 g->fitting = 0;
3008 return FALSE;
3009}
3010
3011// initialise the lines structure method
3013{
3016
3017 // Manual line drawing only needs the module input geometry, never the pixel buffer (unlike
3018 // auto-detection). Tying it to g->buf used to block all manual input whenever the preview buffer
3019 // was momentarily unavailable, e.g. when the module already had parameters set (#710).
3020 /* piece->iwidth/iheight was the PIPE's input size, which is the raw geometry -- not anything
3021 * this module derives -- so it is read from where that fact lives. */
3022 int32_t raw_width = 0, raw_height = 0;
3023 if(!dt_dev_geometry_get_raw_size(self->dev, &raw_width, &raw_height)) return;
3024 if(raw_width <= 0 || raw_height <= 0) return;
3025
3026 _do_clean_structure(self, p, TRUE);
3027
3028 g->current_structure_method = ASHIFT_METHOD_LINES;
3029 g->lines_in_width = raw_width;
3030 g->lines_in_height = raw_height;
3031 g->lines_x_off = 0;
3032 g->lines_y_off = 0;
3033
3034 // we try to recover eventual saved lines
3037
3039}
3040
3041// initialise the quad structure method
3043{
3046
3047 // The manual perspective rectangle only needs the module input geometry, never the pixel buffer
3048 // (unlike auto-detection), so it must not wait for the preview input buffer (#710).
3049 /* piece->iwidth/iheight was the PIPE's input size, which is the raw geometry -- not anything
3050 * this module derives -- so it is read from where that fact lives. */
3051 int32_t raw_width = 0, raw_height = 0;
3052 if(!dt_dev_geometry_get_raw_size(self->dev, &raw_width, &raw_height)) return;
3053 if(raw_width <= 0 || raw_height <= 0) return;
3054
3055 _do_clean_structure(self, p, TRUE);
3056
3058 {
3059 // Recover previous lines
3061 }
3062 else
3063 {
3064 // Create new lines
3066 const float wd = geometry.processed_width;
3067 const float ht = geometry.processed_height;
3068 float pts[8] = { wd * 0.2, ht * 0.2, wd * 0.2, ht * 0.8, wd * 0.8, ht * 0.2, wd * 0.8, ht * 0.8 };
3071 {
3072 g->current_structure_method = ASHIFT_METHOD_QUAD;
3073 g->lines = (dt_iop_ashift_line_t *)malloc(sizeof(dt_iop_ashift_line_t) * 4);
3074 g->lines_count = 4;
3075
3076 _draw_basic_line(&g->lines[0], pts[0], pts[1], pts[2], pts[3],
3078 _draw_basic_line(&g->lines[1], pts[4], pts[5], pts[6], pts[7],
3080 _draw_basic_line(&g->lines[2], pts[0], pts[1], pts[4], pts[5],
3082 _draw_basic_line(&g->lines[3], pts[2], pts[3], pts[6], pts[7],
3084
3085 // get real line type (they may be wrong due to image rotation)
3086 for(int i = 0; i < 4; i++) _draw_retrieve_line_type(&g->lines[i]);
3087
3088 g->lines_in_width = raw_width;
3089 g->lines_in_height = raw_height;
3090 g->lines_x_off = 0;
3091 g->lines_y_off = 0;
3092 g->vertical_count = 2;
3093 g->horizontal_count = 2;
3094 g->vertical_weight = 2.0;
3095 g->horizontal_weight = 2.0;
3096 g->lines_version++;
3097
3099 }
3100 }
3102}
3103
3104// helper function to start parameter fit and report about errors
3106{
3108
3109 if(g->fitting) return;
3110
3111 // if no structure available get it
3112 if(IS_NULL_PTR(g->lines))
3113 if(!_do_get_structure_auto(module, p, ASHIFT_ENHANCE_NONE)) return;
3114
3115 if(g->lines_in_width > 0 && g->lines_in_height > 0)
3116 {
3117 const float f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor;
3118 const float orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;
3119 const float aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;
3120
3121 float homograph[3][3];
3122 homography((float *)homograph, p->rotation, p->lensshift_v, p->lensshift_h, p->shear, f_length_kb,
3123 orthocorr, aspect, g->lines_in_width, g->lines_in_height, ASHIFT_HOMOGRAPH_FORWARD);
3124
3125 const float ivec[2] = { (float)g->lines_in_width, (float)g->lines_in_height };
3126 const float ivecl = sqrtf(ivec[0] * ivec[0] + ivec[1] * ivec[1]);
3127
3128 const float pin0[3] = { 0.0f, 0.0f, 1.0f };
3129 const float pin1[3] = { (float)g->lines_in_width, (float)g->lines_in_height, 1.0f };
3130 float pout0[3];
3131 float pout1[3];
3132 mat3mulv(pout0, (float *)homograph, pin0);
3133 mat3mulv(pout1, (float *)homograph, pin1);
3134 pout0[0] /= pout0[2];
3135 pout0[1] /= pout0[2];
3136 pout1[0] /= pout1[2];
3137 pout1[1] /= pout1[2];
3138
3139 const float ovec[2] = { pout1[0] - pout0[0], pout1[1] - pout0[1] };
3140 const float ovecl = sqrtf(ovec[0] * ovec[0] + ovec[1] * ovec[1]);
3141 const float alpha = acos(CLAMP((ivec[0] * ovec[0] + ivec[1] * ovec[1]) / (ivecl * ovecl), -1.0f, 1.0f));
3142 g->isflipped = fabs(fmod(alpha + M_PI, M_PI) - M_PI / 2.0f) < M_PI / 4.0f ? 1 : 0;
3143 }
3144
3145 g->fitting = 1;
3146
3147 dt_iop_ashift_nmsresult_t res = nmsfit(module, p, dir);
3148
3149 g->fitting = 0;
3150
3151 switch(res)
3152 {
3155 _("not enough structure for automatic correction\nminimum %d lines in each relevant direction"),
3157 return;
3159 case NMS_INSANE:
3160 dt_control_log(_("automatic correction failed, please correct manually"));
3161 return;
3162 case NMS_SUCCESS:
3163 default:
3164 break;
3165 }
3166
3167 // finally apply cropping
3168 do_crop(module, p);
3169
3171 dt_bauhaus_slider_set(g->rotation, p->rotation);
3172 dt_bauhaus_slider_set(g->lensshift_v, p->lensshift_v);
3173 dt_bauhaus_slider_set(g->lensshift_h, p->lensshift_h);
3174 dt_bauhaus_slider_set(g->shear, p->shear);
3176}
3177
3179int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3180 void *const ovoid)
3181{
3182 const dt_iop_roi_t *const roi_in = &piece->roi_in;
3183 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3186
3187 const int ch = piece->dsc_in.channels;
3188 const int ch_width = ch * roi_in->width;
3189
3190 // only for preview pipe: collect input buffer data and do some other evaluations
3191 if(!IS_NULL_PTR(g) && self->dev->gui_attached
3192 && pipe == self->dev->preview_pipe
3193 && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
3194 {
3195 // we want to find out if the final output image is flipped in relation to this iop
3196 // so we can adjust the gui labels accordingly
3197 const int width = roi_in->width;
3198 const int height = roi_in->height;
3199 const int x_off = roi_in->x;
3200 const int y_off = roi_in->y;
3201 const float scale_x = (piece->buf_in.width > 0) ? (float)width / (float)piece->buf_in.width : 1.0f;
3202 const float scale_y = (piece->buf_in.height > 0) ? (float)height / (float)piece->buf_in.height : 1.0f;
3203 // Keep structure detection anchored to the actual preview buffer resolution,
3204 // not to external ROI scale semantics.
3205 const float scale = 0.5f * (scale_x + scale_y);
3206
3207 // origin of image and opposite corner as reference points
3208 dt_boundingbox_t points = { 0.0f, 0.0f, (float)piece->buf_in.width, (float)piece->buf_in.height };
3209 float ivec[2] = { points[2] - points[0], points[3] - points[1] };
3210 float ivecl = sqrtf(ivec[0] * ivec[0] + ivec[1] * ivec[1]);
3211
3212 /* Where do they go? Asked of THIS pipe, the one whose frame we are producing.
3213 *
3214 * This used to walk the GUI thread's pixel-less clone pipe: a worker thread reading
3215 * another thread's node list with no lock, while the GUI can be
3216 * resyncing it (dt_dev_pixelpipe_change() rebuilds pipe->nodes from dev->iop). The answer
3217 * was also whatever history the virtual pipe happened to hold, not the history this frame
3218 * is being rendered from.
3219 *
3220 * The running pipe answers the same question by construction: every distorting module
3221 * downstream of ashift -- liquify, rotatepixels, scalepixels, flip, clipping, crop,
3222 * borders -- commits identical params on every darkroom pipe (none of them is pipe-type
3223 * dependent), and piece->buf_in/buf_out are full-resolution scale-1.0 dims on every pipe,
3224 * because dt_dev_pixelpipe_get_roi_out() seeds its fold from pipe->iwidth/iheight at
3225 * scale 1. So the geometry is the same, the ownership is correct, and the answer now
3226 * describes the frame in hand. */
3229
3230 float ovec[2] = { points[2] - points[0], points[3] - points[1] };
3231 float ovecl = sqrtf(ovec[0] * ovec[0] + ovec[1] * ovec[1]);
3232
3233 // angle between input vector and output vector
3234 float alpha = acos(CLAMP((ivec[0] * ovec[0] + ivec[1] * ovec[1]) / (ivecl * ovecl), -1.0f, 1.0f));
3235
3236 // we are interested if |alpha| is in the range of 90° +/- 45° -> we assume the image is flipped
3237 const int isflipped = fabs(fmod(alpha + M_PI, M_PI) - M_PI / 2.0f) < M_PI / 4.0f ? 1 : 0;
3238
3239 // did modules prior to this one in pixelpipe have changed? -> check via hash value
3240 uint64_t hash = piece->global_hash;
3241
3243 g->isflipped = isflipped;
3244
3245 // save a copy of preview input buffer for parameter fitting
3246 if(IS_NULL_PTR(g->buf) || (size_t)g->buf_width * g->buf_height < (size_t)width * height)
3247 {
3248 // if needed allocate buffer
3249 dt_free(g->buf); // a no-op if g->buf is NULL
3250 // only get new buffer if no old buffer available or old buffer does not fit in terms of size
3251 g->buf = malloc(sizeof(float) * 4 * width * height);
3252 if(IS_NULL_PTR(g->buf))
3253 {
3255 return 1;
3256 }
3257 }
3258
3259 if(g->buf /* && hash != g->buf_hash */)
3260 {
3261 // copy data
3262 dt_iop_image_copy_by_size(g->buf, ivoid, width, height, ch);
3263
3264 g->buf_width = width;
3265 g->buf_height = height;
3266 g->buf_x_off = x_off;
3267 g->buf_y_off = y_off;
3268 g->buf_scale = scale;
3269 g->buf_hash = hash;
3270 }
3271
3273 }
3274
3275 // if module is set to neutral parameters we just copy input->output and are done
3276 if(isneutral(data))
3277 {
3278 dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
3279 return 0;
3280 }
3281
3283
3284 float ihomograph[3][3];
3285 homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb,
3286 data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);
3287
3288 // clipping offset
3289 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);
3290 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);
3291 const float cx = roi_out->scale * fullwidth * data->cl;
3292 const float cy = roi_out->scale * fullheight * data->ct;
3294 // go over all pixels of output image
3295 for(int j = 0; j < roi_out->height; j++)
3296 {
3297 float *const restrict out = ((float *)ovoid) + (size_t)ch * j * roi_out->width;
3298 for(int i = 0; i < roi_out->width; i++)
3299 {
3300 float pin[3], pout[3];
3301
3302 // convert output pixel coordinates to original image coordinates
3303 pout[0] = roi_out->x + i + cx;
3304 pout[1] = roi_out->y + j + cy;
3305 pout[0] /= roi_out->scale;
3306 pout[1] /= roi_out->scale;
3307 pout[2] = 1.0f;
3308
3309 // apply homograph
3310 mat3mulv(pin, (float *)ihomograph, pout);
3311
3312 // convert to input pixel coordinates
3313 pin[0] /= pin[2];
3314 pin[1] /= pin[2];
3315 pin[0] *= roi_in->scale;
3316 pin[1] *= roi_in->scale;
3317 pin[0] -= roi_in->x;
3318 pin[1] -= roi_in->y;
3319
3320 // get output values by interpolation from input image
3321 dt_interpolation_compute_pixel4c(interpolation, (float *)ivoid, out + ch*i, pin[0], pin[1], roi_in->width,
3322 roi_in->height, ch_width);
3323 }
3324 }
3325 return 0;
3326}
3327
3328#ifdef HAVE_OPENCL
3329int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
3330{
3331 const dt_iop_roi_t *const roi_in = &piece->roi_in;
3332 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3336
3337 const int devid = pipe->devid;
3338 const int iwidth = roi_in->width;
3339 const int iheight = roi_in->height;
3340 const int width = roi_out->width;
3341 const int height = roi_out->height;
3342
3343 cl_int err = -999;
3344 cl_mem dev_homo = NULL;
3345
3346 // only for preview pipe: collect input buffer data and do some other evaluations
3347 if(self->dev->gui_attached && g
3348 && pipe == self->dev->preview_pipe
3349 && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
3350 {
3351 // we want to find out if the final output image is flipped in relation to this iop
3352 // so we can adjust the gui labels accordingly
3353 const int x_off = roi_in->x;
3354 const int y_off = roi_in->y;
3355 const float scale_x = (piece->buf_in.width > 0) ? (float)iwidth / (float)piece->buf_in.width : 1.0f;
3356 const float scale_y = (piece->buf_in.height > 0) ? (float)iheight / (float)piece->buf_in.height : 1.0f;
3357 // Keep structure detection anchored to the actual preview buffer resolution,
3358 // not to external ROI scale semantics.
3359 const float scale = 0.5f * (scale_x + scale_y);
3360
3361 // origin of image and opposite corner as reference points
3362 dt_boundingbox_t points = { 0.0f, 0.0f, (float)piece->buf_in.width, (float)piece->buf_in.height };
3363 const float ivec[2] = { points[2] - points[0], points[3] - points[1] };
3364 const float ivecl = sqrtf(ivec[0] * ivec[0] + ivec[1] * ivec[1]);
3365
3366 /* Where do they go? Asked of THIS pipe, the one whose frame we are producing.
3367 *
3368 * This used to walk the GUI thread's pixel-less clone pipe: a worker thread reading
3369 * another thread's node list with no lock, while the GUI can be
3370 * resyncing it (dt_dev_pixelpipe_change() rebuilds pipe->nodes from dev->iop). The answer
3371 * was also whatever history the virtual pipe happened to hold, not the history this frame
3372 * is being rendered from.
3373 *
3374 * The running pipe answers the same question by construction: every distorting module
3375 * downstream of ashift -- liquify, rotatepixels, scalepixels, flip, clipping, crop,
3376 * borders -- commits identical params on every darkroom pipe (none of them is pipe-type
3377 * dependent), and piece->buf_in/buf_out are full-resolution scale-1.0 dims on every pipe,
3378 * because dt_dev_pixelpipe_get_roi_out() seeds its fold from pipe->iwidth/iheight at
3379 * scale 1. So the geometry is the same, the ownership is correct, and the answer now
3380 * describes the frame in hand. */
3383
3384 const float ovec[2] = { points[2] - points[0], points[3] - points[1] };
3385 const float ovecl = sqrtf(ovec[0] * ovec[0] + ovec[1] * ovec[1]);
3386
3387 // angle between input vector and output vector
3388 const float alpha = acos(CLAMP((ivec[0] * ovec[0] + ivec[1] * ovec[1]) / (ivecl * ovecl), -1.0f, 1.0f));
3389
3390 // we are interested if |alpha| is in the range of 90° +/- 45° -> we assume the image is flipped
3391 const int isflipped = fabs(fmod(alpha + M_PI, M_PI) - M_PI / 2.0f) < M_PI / 4.0f ? 1 : 0;
3392
3393 // do modules coming before this one in pixelpipe have changed? -> check via hash value
3394 uint64_t hash = piece->global_hash;
3395
3397 g->isflipped = isflipped;
3398
3399 // save a copy of preview input buffer for parameter fitting
3400 if(IS_NULL_PTR(g->buf) || (size_t)g->buf_width * g->buf_height < (size_t)iwidth * iheight)
3401 {
3402 // if needed allocate buffer
3403 dt_free(g->buf); // a no-op if g->buf is NULL
3404 // only get new buffer if no old buffer or old buffer does not fit in terms of size
3405 g->buf = malloc(sizeof(float) * 4 * iwidth * iheight);
3406 }
3407
3408 if(g->buf /* && hash != g->buf_hash */)
3409 {
3410 // copy data
3411 err = dt_opencl_copy_device_to_host(devid, g->buf, dev_in, iwidth, iheight, sizeof(float) * 4);
3412
3413 g->buf_width = iwidth;
3414 g->buf_height = iheight;
3415 g->buf_x_off = x_off;
3416 g->buf_y_off = y_off;
3417 g->buf_scale = scale;
3418 g->buf_hash = hash;
3419 }
3421 if(err != CL_SUCCESS) goto error;
3422 }
3423
3424 // if module is set to neutral parameters we just copy input->output and are done
3425 if(isneutral(d))
3426 {
3427 size_t origin[] = { 0, 0, 0 };
3428 size_t region[] = { width, height, 1 };
3429 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, origin, origin, region);
3430 if(err != CL_SUCCESS) goto error;
3431 return TRUE;
3432 }
3433
3434 float ihomograph[3][3];
3435 homography((float *)ihomograph, d->rotation, d->lensshift_v, d->lensshift_h, d->shear, d->f_length_kb,
3436 d->orthocorr, d->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);
3437
3438 // clipping offset
3439 const float fullwidth = (float)piece->buf_out.width / (d->cr - d->cl);
3440 const float fullheight = (float)piece->buf_out.height / (d->cb - d->ct);
3441 const float cx = roi_out->scale * fullwidth * d->cl;
3442 const float cy = roi_out->scale * fullheight * d->ct;
3443
3444 dev_homo = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 9, ihomograph);
3445 if(IS_NULL_PTR(dev_homo)) goto error;
3446
3447 const int iroi[2] = { roi_in->x, roi_in->y };
3448 const int oroi[2] = { roi_out->x, roi_out->y };
3449 const float in_scale = roi_in->scale;
3450 const float out_scale = roi_out->scale;
3451 const float clip[2] = { cx, cy };
3452
3453 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
3454
3456
3457 int ldkernel = -1;
3458
3459 switch(interpolation->id)
3460 {
3462 ldkernel = gd->kernel_ashift_bilinear;
3463 break;
3465 ldkernel = gd->kernel_ashift_bicubic;
3466 break;
3468 ldkernel = gd->kernel_ashift_mitchell;
3469 break;
3470 default:
3471 goto error;
3472 }
3473
3474 dt_opencl_set_kernel_arg(devid, ldkernel, 0, sizeof(cl_mem), (void *)&dev_in);
3475 dt_opencl_set_kernel_arg(devid, ldkernel, 1, sizeof(cl_mem), (void *)&dev_out);
3476 dt_opencl_set_kernel_arg(devid, ldkernel, 2, sizeof(int), (void *)&width);
3477 dt_opencl_set_kernel_arg(devid, ldkernel, 3, sizeof(int), (void *)&height);
3478 dt_opencl_set_kernel_arg(devid, ldkernel, 4, sizeof(int), (void *)&iwidth);
3479 dt_opencl_set_kernel_arg(devid, ldkernel, 5, sizeof(int), (void *)&iheight);
3480 dt_opencl_set_kernel_arg(devid, ldkernel, 6, 2 * sizeof(int), (void *)iroi);
3481 dt_opencl_set_kernel_arg(devid, ldkernel, 7, 2 * sizeof(int), (void *)oroi);
3482 dt_opencl_set_kernel_arg(devid, ldkernel, 8, sizeof(float), (void *)&in_scale);
3483 dt_opencl_set_kernel_arg(devid, ldkernel, 9, sizeof(float), (void *)&out_scale);
3484 dt_opencl_set_kernel_arg(devid, ldkernel, 10, 2 * sizeof(float), (void *)clip);
3485 dt_opencl_set_kernel_arg(devid, ldkernel, 11, sizeof(cl_mem), (void *)&dev_homo);
3486 err = dt_opencl_enqueue_kernel_2d(devid, ldkernel, sizes);
3487 if(err != CL_SUCCESS) goto error;
3488
3490 return TRUE;
3491
3492error:
3494 dt_print(DT_DEBUG_OPENCL, "[opencl_ashift] couldn't enqueue kernel! %d\n", err);
3495 return FALSE;
3496}
3497#endif
3498
3499// gather information about "is_near"-ness in g->points_idx
3500static void _get_near(const float *points, dt_iop_ashift_points_idx_t *points_idx, const int lines_count, float pzx,
3501 float pzy, float delta, gboolean multiple)
3502{
3503 const float delta2 = delta * delta;
3504
3505 for(int n = 0; n < lines_count; n++)
3506 {
3507 points_idx[n].is_near = 0;
3508
3509 // skip irrelevant lines
3510 if(points_idx[n].type == ASHIFT_LINE_IRRELEVANT)
3511 continue;
3512
3513 // first check if the mouse pointer is outside the bounding box of the line -> skip this line
3514 if(pzx < points_idx[n].bbx - delta &&
3515 pzx > points_idx[n].bbX + delta &&
3516 pzy < points_idx[n].bby - delta &&
3517 pzy > points_idx[n].bbY + delta)
3518 continue;
3519
3520 // pointer is inside bounding box
3521 size_t offset = points_idx[n].offset;
3522 const int length = points_idx[n].length;
3523
3524 // sanity check (this should not happen)
3525 if(length < 2) continue;
3526
3527 // check line point by point
3528 for(int l = 0; l < length; l++, offset++)
3529 {
3530 float dx = pzx - points[offset * 2];
3531 float dy = pzy - points[offset * 2 + 1];
3532
3533 if(dx * dx + dy * dy < delta2)
3534 {
3535 points_idx[n].is_near = 1;
3536 break;
3537 }
3538 }
3539 // if we don't want multiple selection, stop here
3540 if(!multiple && points_idx[n].is_near) break;
3541 }
3542}
3543
3544// mark lines which are inside a rectangular area in isbounding mode
3545static void _get_bounded_inside(const float *points, dt_iop_ashift_points_idx_t *points_idx,
3546 const int points_lines_count, float pzx, float pzy,
3547 float pzx2, float pzy2, dt_iop_ashift_bounding_t mode)
3548{
3549 // get bounding box coordinates
3550 float ax = pzx;
3551 float ay = pzy;
3552 float bx = pzx2;
3553 float by = pzy2;
3554 if(pzx > pzx2)
3555 {
3556 ax = pzx2;
3557 bx = pzx;
3558 }
3559 if(pzy > pzy2)
3560 {
3561 ay = pzy2;
3562 by = pzy;
3563 }
3564
3565 // we either look for the selected or the deselected lines
3568
3569 for(int n = 0; n < points_lines_count; n++)
3570 {
3571 // mark line as "not is_near" and "not bounded"
3572 points_idx[n].is_near = 0;
3573 points_idx[n].bounded = 0;
3574
3575 // skip irrelevant lines
3576 if(points_idx[n].type == ASHIFT_LINE_IRRELEVANT)
3577 continue;
3578
3579 // is the line inside the box ?
3580 if(points_idx[n].bbx >= ax && points_idx[n].bbx <= bx && points_idx[n].bbX >= ax
3581 && points_idx[n].bbX <= bx && points_idx[n].bby >= ay && points_idx[n].bby <= by
3582 && points_idx[n].bbY >= ay && points_idx[n].bbY <= by)
3583 {
3584 points_idx[n].bounded = 1;
3585 // only mark "is_near"-ness of those lines we are interested in
3586 points_idx[n].is_near = ((points_idx[n].type & mask) != state) ? 0 : 1;
3587 }
3588 }
3589}
3590
3591// generate hash value for lines taking into account only the end point coordinates
3592static uint64_t _get_lines_hash(const dt_iop_ashift_line_t *lines, const int lines_count)
3593{
3594 uint64_t hash = 5381;
3595 for(int n = 0; n < lines_count; n++)
3596 {
3597 const dt_boundingbox_t v = { lines[n].p1[0], lines[n].p1[1], lines[n].p2[0], lines[n].p2[1] };
3598 hash = dt_hash(hash, (const char *)&v, sizeof(dt_boundingbox_t));
3599 }
3600 return hash;
3601}
3602
3603// update color information in points_idx if lines have changed in terms of type (but not in terms
3604// of number or position)
3605static int update_colors(struct dt_iop_module_t *self, dt_iop_ashift_points_idx_t *points_idx,
3606 int points_lines_count)
3607{
3609
3610 // is the display flipped relative to the original image?
3611 const int isflipped = g->isflipped;
3612
3613 // go through all lines
3614 for(int n = 0; n < points_lines_count; n++)
3615 {
3616 const dt_iop_ashift_linetype_t type = points_idx[n].type;
3617
3618 // set line color according to line type/orientation
3619 // note: if the screen display is flipped versus the original image we need
3620 // to respect that fact in the color selection
3622 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_BLUE : ASHIFT_LINECOLOR_GREEN;
3624 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_YELLOW : ASHIFT_LINECOLOR_RED;
3626 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_GREEN : ASHIFT_LINECOLOR_BLUE;
3628 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_RED : ASHIFT_LINECOLOR_YELLOW;
3629 else
3630 points_idx[n].color = ASHIFT_LINECOLOR_GREY;
3631 }
3632
3633 return TRUE;
3634}
3635
3636// get all the points to display lines in the gui
3637static int get_points(struct dt_iop_module_t *self, const dt_iop_ashift_line_t *lines, const int lines_count,
3638 const int lines_version, float **points, float **extremas,
3639 dt_iop_ashift_points_idx_t **points_idx, int *points_lines_count, float scale)
3640{
3641 dt_develop_t *dev = self->dev;
3643
3644 dt_iop_ashift_points_idx_t *my_points_idx = NULL;
3645 float *my_points = NULL;
3646 float *my_extremas = NULL;
3647
3648 // is the display flipped relative to the original image?
3649 const int isflipped = g->isflipped;
3650
3651 // allocate new index array
3652 my_points_idx = (dt_iop_ashift_points_idx_t *)malloc(sizeof(dt_iop_ashift_points_idx_t) * lines_count);
3653 if(IS_NULL_PTR(my_points_idx)) goto error;
3654
3655 // account for total number of points
3656 size_t total_points = 0;
3657
3658 // first step: basic initialization of my_points_idx and counting of total_points
3659 for(int n = 0; n < lines_count; n++)
3660 {
3661 const int length = MAX(lines[n].length, 2);
3662
3663 total_points += length;
3664
3665 my_points_idx[n].length = length;
3666 my_points_idx[n].is_near = 0;
3667 my_points_idx[n].bounded = 0;
3668
3669 const dt_iop_ashift_linetype_t type = lines[n].type;
3670 my_points_idx[n].type = type;
3671
3672 // set line color according to line type/orientation
3673 // note: if the screen display is flipped versus the original image we need
3674 // to respect that fact in the color selection
3676 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_BLUE : ASHIFT_LINECOLOR_GREEN;
3678 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_YELLOW : ASHIFT_LINECOLOR_RED;
3680 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_GREEN : ASHIFT_LINECOLOR_BLUE;
3682 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_RED : ASHIFT_LINECOLOR_YELLOW;
3683 else
3684 my_points_idx[n].color = ASHIFT_LINECOLOR_GREY;
3685 }
3686
3687 // now allocate new points buffer
3688 my_points = (float *)malloc(sizeof(float) * 2 * total_points);
3689 my_extremas = (float *)malloc(sizeof(float) * 2 * 2 * lines_count);
3690 if(IS_NULL_PTR(my_points)) goto error;
3691
3692 // second step: generate points for each line
3693 for(int n = 0, offset = 0; n < lines_count; n++)
3694 {
3695 my_extremas[4 * n] = lines[n].p1[0];
3696 my_extremas[4 * n + 1] = lines[n].p1[1];
3697 my_extremas[4 * n + 2] = lines[n].p2[0];
3698 my_extremas[4 * n + 3] = lines[n].p2[1];
3699
3700 my_points_idx[n].offset = offset;
3701
3702 float x = lines[n].p1[0];
3703 float y = lines[n].p1[1];
3704 const int length = lines[n].length;
3705
3706 const float dx = (lines[n].p2[0] - x) / (float)(length - 1);
3707 const float dy = (lines[n].p2[1] - y) / (float)(length - 1);
3708
3709 // for very small length, we set the second extrema at last point
3710 if(length < 2)
3711 {
3712 my_points[2 * offset] = x;
3713 my_points[2 * offset + 1] = y;
3714 offset++;
3715 my_points[2 * offset] = lines[n].p2[0];
3716 my_points[2 * offset + 1] = lines[n].p2[1];
3717 offset++;
3718 }
3719 else
3720 {
3721 for(int l = 0; l < length && offset < total_points; l++, offset++)
3722 {
3723 my_points[2 * offset] = x;
3724 my_points[2 * offset + 1] = y;
3725
3726 x += dx;
3727 y += dy;
3728 }
3729 }
3730 }
3731
3732 // third step: transform all points
3733 if(!dt_dev_distort_transform_gui(dev, self->iop_order, DT_DEV_TRANSFORM_DIR_FORW_INCL, my_points, total_points))
3734 goto error;
3736 my_extremas, 2 * lines_count))
3737 goto error;
3738
3739 // Apply preview scaling after distortion to keep GUI coordinates in preview space.
3740 const float gui_scale = (scale > 0.f) ? scale : 1.f;
3741 if(gui_scale != 1.f)
3742 {
3743 for(size_t i = 0; i < total_points * 2; i++)
3744 my_points[i] *= gui_scale;
3745 for(int i = 0; i < 4 * lines_count; i++)
3746 my_extremas[i] *= gui_scale;
3747 }
3748
3749 // fourth step: get bounding box in final coordinates (used later for checking "is_near"-ness to mouse pointer)
3750 for(int n = 0; n < lines_count; n++)
3751 {
3752 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;
3753
3754 const size_t offset = my_points_idx[n].offset;
3755 const int length = my_points_idx[n].length;
3756
3757 // Walk the full rasterized polyline so the hit-test bounding box matches
3758 // the displayed line, not only its first sample.
3759 for(int l = 0; l < length; l++)
3760 {
3761 const size_t point = offset + l;
3762 xmin = fmin(xmin, my_points[2 * point]);
3763 xmax = fmax(xmax, my_points[2 * point]);
3764 ymin = fmin(ymin, my_points[2 * point + 1]);
3765 ymax = fmax(ymax, my_points[2 * point + 1]);
3766 }
3767
3768 my_points_idx[n].bbx = xmin;
3769 my_points_idx[n].bbX = xmax;
3770 my_points_idx[n].bby = ymin;
3771 my_points_idx[n].bbY = ymax;
3772 }
3773
3774 // check if lines_version has changed in-between -> too bad: we can forget about all we did :(
3775 if(g->lines_version > lines_version)
3776 goto error;
3777
3778 *points = my_points;
3779 *points_idx = my_points_idx;
3780 *points_lines_count = lines_count;
3781 *extremas = my_extremas;
3782
3783 return TRUE;
3784
3785error:
3786 dt_free(my_points_idx);
3787 dt_free(my_points);
3788 dt_free(my_extremas);
3789 return FALSE;
3790}
3791
3792/* This module's own transform and nothing else -- no direction bound expresses that, since
3793 * FORW_INCL at this module's own iop_order includes everything after it too.
3794 *
3795 * It used to be done by resolving this module's piece on the GUI's pixel-less pipe and invoking
3796 * its distort_transform() by hand, with the focused-module exception applied around it. The
3797 * geometry service expresses the whole thing directly and applies the same exception, so what is
3798 * left is the call.
3799 *
3800 * The old hand-rolled version deliberately did NOT test piece->enabled: it is FALSE for exactly
3801 * the first mouse_moved following a button_pressed while ASHIFT_CROP_ASPECT is active, which drew
3802 * one frame of the centre image without the crop overlay. Records carry their enabled state from
3803 * history rather than from a piece mid-commit, so that flicker has no equivalent here. */
3804static int call_distort_transform(struct dt_iop_module_t *self, float *points, size_t points_count)
3805{
3806 return dt_geometry_module_transform(self->dev, self, points, points_count);
3807}
3808
3809void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
3810 int32_t pointerx, int32_t pointery)
3811{
3812 dt_develop_t *dev = self->dev;
3815 if(IS_NULL_PTR(g) || IS_NULL_PTR(p)) return;
3816
3817 // the usual rescaling stuff
3818 const float wd = dt_dev_roi_request_preview_width(dev);
3819 const float ht = dt_dev_roi_request_preview_height(dev);
3820 if(wd < 1.0 || ht < 1.0) return;
3821
3822 const float zoom_scale = dt_dev_get_overlay_scale(dev);
3823
3824 cairo_save(cr);
3825 {
3826 dt_dev_rescale_roi(dev, cr, width, height);
3827
3828 // draw crop area guides
3829 if(!g->editing) dt_guides_draw(cr, 0, 0, wd, ht, zoom_scale);
3830
3831 cairo_restore(cr);
3832 }
3833
3834 // Fast path: rotation setting by inputting horizon line.
3835 // Conflicts with editing mode where painting with button pressed is understood as validating lines.
3836 if(g->straightening && !g->editing)
3837 {
3838 cairo_save(cr);
3839
3840 dt_dev_rescale_roi(dev, cr, width, height);
3841 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
3843
3844 float pzxpy[2] = { (float)pointerx, (float)pointery };
3846 const float pzx = pzxpy[0];
3847 const float pzy = pzxpy[1];
3848
3849 PangoRectangle ink;
3850 PangoLayout *layout;
3851 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
3852 const float fontsize = DT_PIXEL_APPLY_DPI(16);
3853 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
3854 pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE / zoom_scale);
3855 layout = pango_cairo_create_layout(cr);
3856 pango_layout_set_font_description(layout, desc);
3857 const float bzx = g->straighten_x;
3858 const float bzy = g->straighten_y;
3859 cairo_arc(cr, bzx * wd, bzy * ht, DT_PIXEL_APPLY_DPI(3) / zoom_scale, 0, 2.0 * M_PI);
3860 cairo_stroke(cr);
3861 cairo_arc(cr, pzx * wd, pzy * ht, DT_PIXEL_APPLY_DPI(3) / zoom_scale, 0, 2.0 * M_PI);
3862 cairo_stroke(cr);
3863 cairo_move_to(cr, bzx * wd, bzy * ht);
3864 cairo_line_to(cr, pzx * wd, pzy * ht);
3865 cairo_stroke(cr);
3866
3867 // show rotation angle
3868 float dx = pzx * wd - bzx * wd;
3869 float dy = pzy * ht - bzy * ht;
3870 if(dx < 0)
3871 {
3872 dx = -dx;
3873 dy = -dy;
3874 }
3875 float angle = atan2f(dy, dx);
3876 angle = angle * 180 / M_PI;
3877 if(angle > 45.0) angle -= 90;
3878 if(angle < -45.0) angle += 90;
3879
3880 gchar *view_angle = NULL;
3881 view_angle = g_strdup_printf("%.2f\302\260", angle);
3882 pango_layout_set_text(layout, view_angle ? view_angle : "-1\302\260", -1);
3883 dt_free(view_angle);
3884
3885 PangoRectangle logic;
3886 pango_layout_get_pixel_extents(layout, &ink, &logic);
3887 const float text_w = logic.width;
3888 const float text_h = logic.height;
3889 const float margin = DT_PIXEL_APPLY_DPI(6) / zoom_scale;
3890 cairo_set_source_rgba(cr, .5, .5, .5, .9);
3891 const float xp = pzx * wd + DT_PIXEL_APPLY_DPI(20) / zoom_scale;
3892 const float yp = pzy * ht - logic.height;
3893
3894 const double rectangle_x = xp - margin;
3895 const double rectangle_y = yp - margin;
3896 const double rectangle_w = text_w + 2 * margin;
3897 const double rectangle_h = text_h + 2 * margin;
3898 dt_gui_draw_rounded_rectangle(cr, rectangle_w, rectangle_h, rectangle_x, rectangle_y);
3899
3900 cairo_set_source_rgba(cr, .7, .7, .7, .7);
3901 cairo_move_to(cr, xp, yp);
3902 pango_cairo_show_layout(cr, layout);
3903 pango_font_description_free(desc);
3904 g_object_unref(layout);
3905 cairo_restore(cr);
3906 return;
3907 }
3908
3909 if(!g->editing) return; // nothing else to draw
3910
3911 cairo_save(cr);
3912 dt_dev_clip_roi(dev, cr, width, height);
3913 dt_dev_rescale_roi(dev, cr, width, height);
3914
3915 // we draw the cropping area; use this module's own input rectangle
3916 dt_iop_roi_t mod_in;
3917 if(!dt_dev_module_geometry_gui(self->dev, self, &mod_in, NULL))
3918 {
3919 cairo_restore(cr);
3920 return;
3921 }
3922 const float iwd = mod_in.width;
3923 const float iht = mod_in.height;
3924 const float ixo = mod_in.x;
3925 const float iyo = mod_in.y;
3926
3927 // The four corners of the inner image polygon
3928 float V[4][2] = { { ixo, iyo },
3929 { ixo, iyo + iht },
3930 { ixo + iwd, iyo + iht },
3931 { ixo + iwd, iyo } };
3932
3933 // convert coordinates of corners to coordinates of this module's output
3934 if(!call_distort_transform(self, (float *)V, 4))
3935 return;
3936
3937 // get x/y-offset as well as width and height of output buffer
3938 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;
3939 for(int n = 0; n < 4; n++)
3940 {
3941 xmin = MIN(xmin, V[n][0]);
3942 xmax = MAX(xmax, V[n][0]);
3943 ymin = MIN(ymin, V[n][1]);
3944 ymax = MAX(ymax, V[n][1]);
3945 }
3946
3947 /*
3948 // Paint black outside area when not in editing mode then returns.
3949 if(!g->editing)
3950 {
3951 if(!dt_dev_distort_transform_gui(self->dev, self->iop_order,
3952 DT_DEV_TRANSFORM_DIR_FORW_EXCL, (float *)V, 4))
3953 return;
3954 const float scale_factor = dt_dev_get_natural_scale(dev, dev->preview_pipe);
3955 for(size_t i = 0; i < 4; i++)
3956 {
3957 V[i][0] *= scale_factor;
3958 V[i][1] *= scale_factor;
3959 }
3960 // force cover the outside area in black.
3961 // This avoids to get an other color rendered outside the image because of nexts modules processing.
3962 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
3963 cairo_rectangle(cr, 0.0, 0.0, wd, ht); // outer (full) area
3964 cairo_move_to(cr, V[0][0], V[0][1]); // inner image polygon
3965 cairo_line_to(cr, V[1][0], V[1][1]);
3966 cairo_line_to(cr, V[2][0], V[2][1]);
3967 cairo_line_to(cr, V[3][0], V[3][1]);
3968 cairo_close_path(cr);
3969 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
3970 cairo_fill(cr);
3971 cairo_restore(cr);
3972 return;
3973 }
3974*/
3975 const float owd = xmax - xmin;
3976 const float oht = ymax - ymin;
3977
3978 // The four inner clipping polygon
3979 float C[4][2] = { { xmin + p->cl * owd, ymin + p->ct * oht },
3980 { xmin + p->cl * owd, ymin + p->cb * oht },
3981 { xmin + p->cr * owd, ymin + p->cb * oht },
3982 { xmin + p->cr * owd, ymin + p->ct * oht } };
3983
3984 // convert clipping corners to final output image
3986 DT_DEV_TRANSFORM_DIR_FORW_EXCL, (float *)C, 4))
3987 return;
3989 DT_DEV_TRANSFORM_DIR_FORW_EXCL, (float *)V, 4))
3990 return;
3991
3992 double dashes = DT_PIXEL_APPLY_DPI(5.0) / zoom_scale;
3993 cairo_set_dash(cr, &dashes, 0, 0);
3994
3995 // Resize the coordinates of the rectangles V and C according to the current zoom.
3996 const float scale_factor = dt_dev_get_natural_scale(self->dev);
3997 for(size_t i = 0; i < 4; i++)
3998 {
3999 V[i][0] *= scale_factor;
4000 V[i][1] *= scale_factor;
4001 C[i][0] *= scale_factor;
4002 C[i][1] *= scale_factor;
4003 }
4004
4005 // Paint image outside of clipping area in transparent dark grey
4006 cairo_set_source_rgba(cr, .2, .2, .2, .8);
4007 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
4008 cairo_rectangle(cr, 0.0, 0.0, wd, ht); // outer (full) area
4009 cairo_move_to(cr, C[0][0], C[0][1]); // inner clipping polygon
4010 cairo_line_to(cr, C[1][0], C[1][1]);
4011 cairo_line_to(cr, C[2][0], C[2][1]);
4012 cairo_line_to(cr, C[3][0], C[3][1]);
4013 cairo_close_path(cr);
4014 cairo_fill(cr);
4015 // Paint outside area in black.
4016 // This avoids to get an other color rendered outside the image because of nexts modules preview.
4017 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
4018 cairo_rectangle(cr, 0.0, 0.0, wd, ht); // outer (full) area
4019 cairo_move_to(cr, V[0][0], V[0][1]); // inner image polygon
4020 cairo_line_to(cr, V[1][0], V[1][1]);
4021 cairo_line_to(cr, V[2][0], V[2][1]);
4022 cairo_line_to(cr, V[3][0], V[3][1]);
4023 cairo_close_path(cr);
4024 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
4025 cairo_fill(cr);
4026 // draw white outline around clipping area
4027 cairo_move_to(cr, C[0][0], C[0][1]); // inner clipping polygon
4028 cairo_line_to(cr, C[1][0], C[1][1]);
4029 cairo_line_to(cr, C[2][0], C[2][1]);
4030 cairo_line_to(cr, C[3][0], C[3][1]);
4031 cairo_close_path(cr);
4033 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2) / zoom_scale);
4034 cairo_stroke(cr);
4035
4036 // we draw the guides correctly scaled here instead of using the darkroom expose callback
4037 const float cx = fminf(C[0][0], fminf(C[1][0], fminf(C[2][0], C[3][0])));
4038 const float cy = fminf(C[0][1], fminf(C[1][1], fminf(C[2][1], C[3][1])));
4039 const float cw = fmaxf(C[0][0], fmaxf(C[1][0], fmaxf(C[2][0], C[3][0]))) - cx;
4040 const float ch = fmaxf(C[0][1], fmaxf(C[1][1], fmaxf(C[2][1], C[3][1]))) - cy;
4041 dt_guides_draw(cr, cx, cy, cw, ch, zoom_scale);
4042
4043 cairo_restore(cr);
4044
4045 // structural data are currently being collected or fit procedure is running? -> skip
4046 // no structural data or visibility switched off? -> stop here
4047 if(g->fitting || IS_NULL_PTR(g->lines) || IS_NULL_PTR(g->buf) || !self->enabled) return;
4048
4049 // get hash value that changes if distortions from here to the end of the pixelpipe changed.
4050 // The composed geometry has no hash of its own -- it is rebuilt wherever a pipe flag is raised,
4051 // so it cannot be stale here -- and the preview pipe's is what actually tracks the distortions
4052 // these points are drawn against.
4054 // get hash value that changes if coordinates of lines have changed
4055 const uint64_t lines_hash = _get_lines_hash(g->lines, g->lines_count);
4056
4057 // points data are missing or outdated, or distortion has changed?
4058 if(IS_NULL_PTR(g->points) || IS_NULL_PTR(g->points_idx) || hash != g->grid_hash
4059 || g->points_lines_count != g->lines_count
4060 || (g->lines_version > g->points_version
4061 && g->lines_hash != lines_hash))
4062 {
4063 // we need to reprocess points
4064 dt_free(g->points);
4065 dt_free(g->points_idx);
4066 dt_free(g->draw_points);
4067 g->points_lines_count = 0;
4068
4069 const float scale = dt_dev_get_natural_scale(dev);
4070
4071 if(!get_points(self, g->lines, g->lines_count, g->lines_version, &g->points, &g->draw_points, &g->points_idx,
4072 &g->points_lines_count, scale))
4073 return;
4074
4075 g->points_version = g->lines_version;
4076 g->grid_hash = hash;
4077 g->lines_hash = lines_hash;
4078 }
4079 else if(g->lines_hash == lines_hash)
4080 {
4081 // update line type information in points_idx
4082 for(int n = 0; n < g->points_lines_count; n++)
4083 g->points_idx[n].type = g->lines[n].type;
4084
4085 // coordinates of lines are unchanged -> we only need to update colors
4086 if(!update_colors(self, g->points_idx, g->points_lines_count))
4087 return;
4088
4089 g->points_version = g->lines_version;
4090 }
4091
4092 // a final check
4093 if(IS_NULL_PTR(g->points) || IS_NULL_PTR(g->points_idx)) return;
4094
4095 cairo_save(cr);
4096 dt_dev_rescale_roi(dev, cr, width, height);
4097
4098 // this must match the sequence of enum dt_iop_ashift_linecolor_t!
4099 const float line_colors[5][4] =
4100 { { 0.3f, 0.3f, 0.3f, 0.8f }, // grey (misc. lines)
4101 { 0.0f, 1.0f, 0.0f, 0.8f }, // green (selected vertical lines)
4102 { 0.8f, 0.0f, 0.0f, 0.8f }, // red (de-selected vertical lines)
4103 { 0.0f, 0.0f, 1.0f, 0.8f }, // blue (selected horizontal lines)
4104 { 0.8f, 0.8f, 0.0f, 0.8f } }; // yellow (de-selected horizontal lines)
4105
4106 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
4107
4108 // now draw all lines
4109 for(int n = 0; n < g->points_lines_count; n++)
4110 {
4111 // hide removed lines in drawn mode
4112 if((g->current_structure_method == ASHIFT_METHOD_QUAD || g->current_structure_method == ASHIFT_METHOD_LINES)
4113 && g->points_idx[n].type != ASHIFT_LINE_HORIZONTAL_SELECTED
4114 && g->points_idx[n].type != ASHIFT_LINE_VERTICAL_SELECTED)
4115 continue;
4116 // is the is_near flag set? -> draw line a bit thicker
4117 if(g->points_idx[n].is_near)
4118 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.0) / zoom_scale);
4119 else
4120 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5) / zoom_scale);
4121
4122 // the color of this line
4123 const float *color = line_colors[g->points_idx[n].color];
4124 cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]);
4125
4126 size_t offset = g->points_idx[n].offset;
4127 const int length = g->points_idx[n].length;
4128
4129 // sanity check (this should not happen)
4130 if(length < 2) continue;
4131
4132 // set starting point of multi-segment line
4133 cairo_move_to(cr, g->points[offset * 2], g->points[offset * 2 + 1]);
4134
4135 offset++;
4136 // draw individual line segments
4137 for(int l = 1; l < length; l++, offset++)
4138 {
4139 cairo_line_to(cr, g->points[offset * 2], g->points[offset * 2 + 1]);
4140 }
4141
4142 // finally stroke the line
4143 cairo_stroke(cr);
4144 }
4145
4146 // we also draw the corner in case of drawn perspective
4147 if((g->current_structure_method == ASHIFT_METHOD_QUAD || g->current_structure_method == ASHIFT_METHOD_LINES)
4148 && g->draw_points)
4149 {
4151 const int nb = (g->current_structure_method == ASHIFT_METHOD_LINES) ? g->lines_count * 2 : 4;
4152 for(int i = 0; i < nb; i++)
4153 {
4154 // hide removed lines
4155 if(g->lines[i / 2].type != ASHIFT_LINE_HORIZONTAL_SELECTED
4156 && g->lines[i / 2].type != ASHIFT_LINE_VERTICAL_SELECTED)
4157 continue;
4158 if(g->draw_near_point == i)
4159 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) / zoom_scale);
4160 else
4161 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
4162 cairo_arc(cr, g->draw_points[i * 2], g->draw_points[i * 2 + 1], DT_PIXEL_APPLY_DPI(5.0) / zoom_scale, 0,
4163 2.0 * M_PI);
4164 cairo_stroke(cr);
4165 }
4166 }
4167
4168 // and we draw the selection box if any
4169 if(g->isbounding != ASHIFT_BOUNDING_OFF)
4170 {
4171 float pzxpy[2] = { (float)pointerx, (float)pointery };
4173 const float pzx = pzxpy[0];
4174 const float pzy = pzxpy[1];
4175
4176 double dashed[] = { DT_PIXEL_APPLY_DPI(4.0), DT_PIXEL_APPLY_DPI(4.0) };
4177 dashed[0] /= zoom_scale;
4178 dashed[1] /= zoom_scale;
4179 const int len = sizeof(dashed) / sizeof(dashed[0]);
4180
4181 cairo_rectangle(cr, g->lastx * wd, g->lasty * ht, (pzx - g->lastx) * wd,
4182 (pzy - g->lasty) * ht);
4183 cairo_set_source_rgba(cr, .3, .3, .3, .8);
4184 cairo_set_line_width(cr, 1.0 / zoom_scale);
4185 cairo_set_dash(cr, dashed, len, 0);
4186 cairo_stroke_preserve(cr);
4187 cairo_set_source_rgba(cr, .8, .8, .8, .8);
4188 cairo_set_dash(cr, dashed, len, 4);
4189 cairo_stroke(cr);
4190 }
4191
4192 // indicate which area is used for "is_near"-ness detection when selecting/deselecting lines
4193 if(g->near_delta > 0)
4194 {
4195 float pzxpy[2] = { (float)pointerx, (float)pointery };
4197 const float pzx = pzxpy[0];
4198 const float pzy = pzxpy[1];
4199
4200 double dashed[] = { DT_PIXEL_APPLY_DPI(4.0), DT_PIXEL_APPLY_DPI(4.0) };
4201 dashed[0] /= zoom_scale;
4202 dashed[1] /= zoom_scale;
4203 const int len = sizeof(dashed) / sizeof(dashed[0]);
4204
4205 cairo_arc(cr, pzx * wd, pzy * ht, g->near_delta, 0, 2.0 * M_PI);
4206
4207 cairo_set_source_rgba(cr, .3, .3, .3, .8);
4208 cairo_set_line_width(cr, 1.0 / zoom_scale);
4209 cairo_set_dash(cr, dashed, len, 0);
4210 cairo_stroke_preserve(cr);
4211 cairo_set_source_rgba(cr, .8, .8, .8, .8);
4212 cairo_set_dash(cr, dashed, len, 4);
4213 cairo_stroke(cr);
4214 }
4215
4216 cairo_restore(cr);
4217}
4218
4219// update the number of selected vertical and horizontal lines
4220static void _update_lines_count(const dt_iop_ashift_line_t *lines, const int lines_count,
4221 int *vertical_count, int *horizontal_count)
4222{
4223 int vlines = 0;
4224 int hlines = 0;
4225
4226 for(int n = 0; n < lines_count; n++)
4227 {
4229 vlines++;
4231 hlines++;
4232 }
4233
4234 *vertical_count = vlines;
4235 *horizontal_count = hlines;
4236}
4237
4238// determine if we are is_near a drawn line extrema
4239static int _draw_near_point(dt_develop_t *dev, const float x, const float y, const float *points, const int limit)
4240{
4241 const float zoom_scale = dt_dev_get_overlay_scale(dev);
4242 const float delta = DT_PIXEL_APPLY_DPI(6) / (zoom_scale > 0.f ? zoom_scale : 1.f);
4243
4244 for(int i = 0; i < limit; i++)
4245 {
4246 if(x - points[i * 2] < delta && x - points[i * 2] > -delta && y - points[i * 2 + 1] < delta
4247 && y - points[i * 2 + 1] > -delta)
4248 return i;
4249 }
4250 return -1;
4251}
4252
4254{
4255 line->length = sqrt((line->p2[0] - line->p1[0]) * (line->p2[0] - line->p1[0])
4256 + (line->p2[1] - line->p1[1]) * (line->p2[1] - line->p1[1]));
4257}
4258
4259int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
4260{
4262 if(IS_NULL_PTR(g)) return FALSE;
4263
4264 if(g->straightening)
4265 {
4267 return TRUE;
4268 }
4269
4270 gboolean handled = FALSE;
4271
4272 const float wd = dt_dev_roi_request_preview_width(self->dev);
4273 const float ht = dt_dev_roi_request_preview_height(self->dev);
4274 if(wd < 1.0 || ht < 1.0) return 1;
4275
4276 float pzxpy[2] = { (float)x, (float)y };
4278 float pzx = pzxpy[0];
4279 float pzy = pzxpy[1];
4280
4281 // if visibility of lines is switched off or no lines available, we have nothing to do
4282 if(IS_NULL_PTR(g->lines)) return FALSE;
4283
4284 /* Selection uses transformed display point descriptors, but writes the state
4285 * back into g->lines[n]. Display points can be stale until the next expose
4286 * after editing or deleting lines, so every loop below is capped to live
4287 * line storage. */
4288 const int selectable_lines_count = (!IS_NULL_PTR(g->points) && !IS_NULL_PTR(g->points_idx))
4289 ? MIN(g->points_lines_count, g->lines_count)
4290 : 0;
4291
4292 // if we are moving a drawn line extrema, we do the change here
4293 if(g->draw_point_move)
4294 {
4295 const int line = g->draw_near_point / 2;
4296 if(g->draw_near_point < 0 || line >= g->lines_count)
4297 {
4298 g->draw_point_move = FALSE;
4299 g->draw_near_point = -1;
4300 return FALSE;
4301 }
4302
4304 const float pd_w = geometry.processed_width;
4305 const float pd_h = geometry.processed_height;
4306 float pts[2] = { pzx * pd_w, pzy * pd_h };
4309 {
4310 // first we move the point
4311 if(g->draw_near_point >= 0)
4312 {
4313 if(g->draw_near_point % 2 == 0)
4314 {
4315 g->lines[line].p1[0] = pts[0];
4316 g->lines[line].p1[1] = pts[1];
4317 }
4318 else
4319 {
4320 g->lines[line].p2[0] = pts[0];
4321 g->lines[line].p2[1] = pts[1];
4322 }
4323 _draw_recompute_line_length(&g->lines[line]);
4324 }
4325
4326 // for the rectangle method, we need to move the horizontal line too
4327 if(g->current_structure_method == ASHIFT_METHOD_QUAD && g->lines_count >= 4)
4328 {
4329 if(g->draw_near_point == 0)
4330 {
4331 g->lines[2].p1[0] = pts[0];
4332 g->lines[2].p1[1] = pts[1];
4333 _draw_recompute_line_length(&g->lines[2]);
4334 }
4335 else if(g->draw_near_point == 1)
4336 {
4337 g->lines[3].p1[0] = pts[0];
4338 g->lines[3].p1[1] = pts[1];
4339 _draw_recompute_line_length(&g->lines[3]);
4340 }
4341 else if(g->draw_near_point == 2)
4342 {
4343 g->lines[2].p2[0] = pts[0];
4344 g->lines[2].p2[1] = pts[1];
4345 _draw_recompute_line_length(&g->lines[2]);
4346 }
4347 else if(g->draw_near_point == 3)
4348 {
4349 g->lines[3].p2[0] = pts[0];
4350 g->lines[3].p2[1] = pts[1];
4351 _draw_recompute_line_length(&g->lines[3]);
4352 }
4353 }
4354 g->lines_hash++;
4355 g->lines_version++;
4357 }
4358 return TRUE;
4359 }
4360
4361 // case where we move a drawn line
4362 if(g->draw_line_move >= 0)
4363 {
4364 if(g->draw_line_move >= g->lines_count)
4365 {
4366 g->draw_line_move = -1;
4367 return FALSE;
4368 }
4369
4371 const float pd_w = geometry.processed_width;
4372 const float pd_h = geometry.processed_height;
4373 float pts[2] = { pzx * pd_w, pzy * pd_h };
4376 {
4377 const float dx = (pts[0] - g->draw_pointmove_x);
4378 const float dy = (pts[1] - g->draw_pointmove_y);
4379 const int n = g->draw_line_move;
4380 g->draw_pointmove_x = pts[0];
4381 g->draw_pointmove_y = pts[1];
4382
4383 // we move the line extremas
4384 g->lines[n].p1[0] += dx;
4385 g->lines[n].p1[1] += dy;
4386 g->lines[n].p2[0] += dx;
4387 g->lines[n].p2[1] += dy;
4388 // sanity check to be sure the extremas don't go outside the image area
4389 g->lines[n].p1[0] = CLAMPF(g->lines[n].p1[0], 0.0f, g->lines_in_width);
4390 g->lines[n].p1[1] = CLAMPF(g->lines[n].p1[1], 0.0f, g->lines_in_height);
4391 g->lines[n].p2[0] = CLAMPF(g->lines[n].p2[0], 0.0f, g->lines_in_width);
4392 g->lines[n].p2[1] = CLAMPF(g->lines[n].p2[1], 0.0f, g->lines_in_height);
4393
4395
4396 // for the rectangle method, we need to move the adjacent lines too
4397 if(g->current_structure_method == ASHIFT_METHOD_QUAD && g->lines_count >= 4)
4398 {
4399 if(n == 0)
4400 {
4401 g->lines[2].p1[0] = g->lines[n].p1[0];
4402 g->lines[2].p1[1] = g->lines[n].p1[1];
4403 g->lines[3].p1[0] = g->lines[n].p2[0];
4404 g->lines[3].p1[1] = g->lines[n].p2[1];
4405 _draw_recompute_line_length(&g->lines[2]);
4406 _draw_recompute_line_length(&g->lines[3]);
4407 }
4408 else if(n == 1)
4409 {
4410 g->lines[2].p2[0] = g->lines[n].p1[0];
4411 g->lines[2].p2[1] = g->lines[n].p1[1];
4412 g->lines[3].p2[0] = g->lines[n].p2[0];
4413 g->lines[3].p2[1] = g->lines[n].p2[1];
4414 _draw_recompute_line_length(&g->lines[2]);
4415 _draw_recompute_line_length(&g->lines[3]);
4416 }
4417 else if(n == 2)
4418 {
4419 g->lines[0].p1[0] = g->lines[n].p1[0];
4420 g->lines[0].p1[1] = g->lines[n].p1[1];
4421 g->lines[1].p1[0] = g->lines[n].p2[0];
4422 g->lines[1].p1[1] = g->lines[n].p2[1];
4423 _draw_recompute_line_length(&g->lines[0]);
4424 _draw_recompute_line_length(&g->lines[1]);
4425 }
4426 else if(n == 3)
4427 {
4428 g->lines[0].p2[0] = g->lines[n].p1[0];
4429 g->lines[0].p2[1] = g->lines[n].p1[1];
4430 g->lines[1].p2[0] = g->lines[n].p2[0];
4431 g->lines[1].p2[1] = g->lines[n].p2[1];
4432 _draw_recompute_line_length(&g->lines[0]);
4433 _draw_recompute_line_length(&g->lines[1]);
4434 }
4435 }
4436
4437 g->lines_hash++;
4438 g->lines_version++;
4440 }
4441 return TRUE;
4442 }
4443 // if we are in draw mode, we check if we are is_near a corner
4444 if(g->draw_points
4445 && ((g->current_structure_method == ASHIFT_METHOD_QUAD && g->lines_count >= 4)
4446 || g->current_structure_method == ASHIFT_METHOD_LINES))
4447 {
4448 const int limit = (g->current_structure_method == ASHIFT_METHOD_LINES) ? g->lines_count * 2 : 4;
4449 g->draw_near_point = _draw_near_point(self->dev, pzx * wd, pzy * ht, g->draw_points, limit);
4450 }
4451
4452 // if in rectangle selecting mode adjust "is_near"-ness of lines according to
4453 // the rectangular selection
4454 if(g->isbounding != ASHIFT_BOUNDING_OFF)
4455 {
4456 if(wd >= 1.0 && ht >= 1.0 && selectable_lines_count > 0)
4457 {
4458 // mark lines inside the rectangle
4459 _get_bounded_inside(g->points, g->points_idx, selectable_lines_count, pzx * wd, pzy * ht, g->lastx * wd,
4460 g->lasty * ht, g->isbounding);
4461 }
4462
4464 return FALSE;
4465 }
4466
4467 // gather information about "is_near"-ness in g->points_idx
4468 if(selectable_lines_count > 0)
4469 _get_near(
4470 g->points, g->points_idx, selectable_lines_count, pzx * wd, pzy * ht, g->near_delta,
4471 !(g->current_structure_method == ASHIFT_METHOD_LINES || g->current_structure_method == ASHIFT_METHOD_QUAD));
4472
4473 // if we are in sweeping mode iterate over lines as we move the pointer and change "selected" state.
4474 if(g->isdeselecting || g->isselecting)
4475 {
4476 // Loop over displayed lines close to the pointer and update the matching live line flags.
4477 for(int n = 0; g->selecting_lines_version == g->lines_version && n < selectable_lines_count; n++)
4478 {
4479 if(g->points_idx[n].is_near == 0)
4480 continue;
4481
4482 if(g->isdeselecting)
4483 {
4484 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;
4485 handled = TRUE;
4486 }
4487 else if(g->isselecting && g->current_structure_method != ASHIFT_METHOD_LINES)
4488 {
4489 g->lines[n].type |= ASHIFT_LINE_SELECTED;
4490 handled = TRUE;
4491 }
4492 }
4493 }
4494
4495 if(handled)
4496 {
4497 _update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count);
4498 g->lines_version++;
4499 g->selecting_lines_version++;
4500 }
4501
4503
4504 // if not in sweeping mode we need to pass the event
4505 return (g->isdeselecting || g->isselecting);
4506}
4507
4508int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
4509 uint32_t state)
4510{
4512 if(IS_NULL_PTR(g)) return FALSE;
4513 gboolean handled = FALSE;
4514
4515 // avoid unexpected back to lt mode:
4516 if(type == GDK_2BUTTON_PRESS && which == 1)
4517 return TRUE;
4518
4519 float pzxpy[2] = { (float)x, (float)y };
4521 float pzx = pzxpy[0];
4522 float pzy = pzxpy[1];
4523
4524 const float wd = dt_dev_roi_request_preview_width(self->dev);
4525 const float ht = dt_dev_roi_request_preview_height(self->dev);
4526 if(wd < 1.0 || ht < 1.0) return 1;
4527
4528 // if we start to draw a straightening line
4529 if(IS_NULL_PTR(g->lines) && which == 3 && !g->editing)
4530 {
4531 dt_control_change_cursor(GDK_CROSSHAIR);
4532 g->straightening = TRUE;
4533 g->lastx = pzx;
4534 g->lasty = pzy;
4535 g->straighten_x = pzx;
4536 g->straighten_y = pzy;
4537 return TRUE;
4538 }
4539
4540 // grab a draw corner
4541 if((g->current_structure_method == ASHIFT_METHOD_QUAD
4542 || g->current_structure_method == ASHIFT_METHOD_LINES)
4543 && g->draw_near_point >= 0)
4544 {
4546 const int line = g->draw_near_point / 2;
4547 if(IS_NULL_PTR(g->lines) || line >= g->lines_count) return FALSE;
4548
4549 g->draw_point_move = TRUE;
4550 g->lastx = x;
4551 g->lasty = y;
4552 return TRUE;
4553 }
4554
4555 // remember lines version at this stage so we can continuously monitor if the
4556 // lines have changed in-between
4557 g->selecting_lines_version = g->lines_version;
4558
4559 // if shift button is pressed go into bounding mode (selecting or deselecting
4560 // in a rectangle area)
4561 if(dt_modifier_is(state, GDK_SHIFT_MASK))
4562 {
4563 if(IS_NULL_PTR(g->lines) || IS_NULL_PTR(g->points) || IS_NULL_PTR(g->points_idx)) return FALSE;
4565 g->lastx = pzx;
4566 g->lasty = pzy;
4567
4568 g->isbounding = (which == 3) ? ASHIFT_BOUNDING_DESELECT : ASHIFT_BOUNDING_SELECT;
4569 dt_control_change_cursor(GDK_CROSS);
4570
4571 return TRUE;
4572 }
4573
4574 const float min_scale = dt_dev_get_zoom_scale(self->dev, 0);
4575 const float cur_scale = dt_dev_get_zoom_scale(self->dev, 0);
4576
4577 /* Selection uses transformed display point descriptors, but writes the state
4578 * back into g->lines[n]. Display points can be stale until the next expose
4579 * after editing or deleting lines, so every loop below is capped to live
4580 * line storage. */
4581 const int selectable_lines_count = (!IS_NULL_PTR(g->lines)
4582 && !IS_NULL_PTR(g->points)
4583 && !IS_NULL_PTR(g->points_idx))
4584 ? MIN(g->points_lines_count, g->lines_count)
4585 : 0;
4586
4587 // if we are zoomed out (no panning possible) and we have lines to display we take control
4588 const int take_control = (cur_scale == min_scale) && (selectable_lines_count > 0);
4589
4590 if(g->current_structure_method == ASHIFT_METHOD_QUAD || g->current_structure_method == ASHIFT_METHOD_LINES)
4591 g->near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta_draw");
4592 else
4593 g->near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta");
4594
4595 // gather information about "is_near"-ness in g->points_idx
4596 if(selectable_lines_count > 0)
4597 _get_near(g->points, g->points_idx, selectable_lines_count,
4598 pzx * wd, pzy * ht, g->near_delta,
4599 !(g->current_structure_method == ASHIFT_METHOD_QUAD
4600 || g->current_structure_method == ASHIFT_METHOD_LINES));
4601
4602 if((g->current_structure_method == ASHIFT_METHOD_LINES && which == 1)
4603 || g->current_structure_method == ASHIFT_METHOD_QUAD)
4604 {
4605 // we search the selected line and mark it as the moved line
4606 for(int n = 0; n < selectable_lines_count; n++)
4607 {
4608 if(g->points_idx[n].is_near)
4609 {
4611 const float pd_w = geometry.processed_width;
4612 const float pd_h = geometry.processed_height;
4613 float pts[2] = { pzx * pd_w, pzy * pd_h };
4616 g->draw_line_move = n;
4617 g->draw_pointmove_x = pts[0];
4618 g->draw_pointmove_y = pts[1];
4620 return TRUE;
4621 }
4622 }
4623 // for the rectangle draw fitting, we don't go further
4624 if(g->current_structure_method == ASHIFT_METHOD_QUAD) return FALSE;
4625 }
4626 else
4627 {
4628 // iterate over all lines close to the pointer and change "selected" state.
4629 // left-click selects and right-click deselects the line
4630 for(int n = 0; g->selecting_lines_version == g->lines_version && n < selectable_lines_count; n++)
4631 {
4632 if(g->points_idx[n].is_near == 0) continue;
4633
4634 if(which == 3)
4635 {
4636 if(g->current_structure_method != ASHIFT_METHOD_LINES)
4637 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;
4638 else
4639 {
4640 // we completely remove the line from the list
4641 if(g->lines[n].type == ASHIFT_LINE_HORIZONTAL_SELECTED)
4642 {
4643 g->horizontal_count--;
4644 g->horizontal_weight -= 1.0f;
4645 }
4646 else
4647 {
4648 g->vertical_count--;
4649 g->vertical_weight -= 1.0f;
4650 }
4651
4652 const int count = g->lines_count - 1;
4653 dt_iop_ashift_line_t *lines = NULL;
4654 if(count > 0)
4655 {
4656 lines = (dt_iop_ashift_line_t *)malloc(sizeof(dt_iop_ashift_line_t) * count);
4657 if(IS_NULL_PTR(lines))
4658 {
4659 if(g->lines[n].type == ASHIFT_LINE_HORIZONTAL_SELECTED)
4660 {
4661 g->horizontal_count++;
4662 g->horizontal_weight += 1.0f;
4663 }
4664 else
4665 {
4666 g->vertical_count++;
4667 g->vertical_weight += 1.0f;
4668 }
4669 break;
4670 }
4671 }
4672 int pos = 0;
4673 for(int i = 0; i < g->lines_count; i++)
4674 {
4675 if(i != n)
4676 {
4677 lines[pos] = g->lines[i];
4678 pos++;
4679 }
4680 }
4681 if(g->lines)
4682 {
4683 dt_free(g->lines);
4684 }
4685 g->lines = lines;
4686 g->lines_count = count;
4687 handled = TRUE;
4688 break;
4689 }
4690
4691 handled = TRUE;
4692 }
4693 else if(g->current_structure_method != ASHIFT_METHOD_LINES)
4694 {
4695 g->lines[n].type |= ASHIFT_LINE_SELECTED;
4696 handled = TRUE;
4697 }
4698 }
4699 }
4700
4701 if(!handled && g->current_structure_method == ASHIFT_METHOD_LINES && which == 1)
4702 {
4704 // start to draw a manual line
4705 g->draw_point_move = TRUE;
4706 g->lastx = x;
4707 g->lasty = y;
4708
4709 // we instantiate a new line with both extrema at the current position
4710 // and enable the "move point" mode with the second extrema
4712 const float pd_w = geometry.processed_width;
4713 const float pd_h = geometry.processed_height;
4714 float pts[2] = { pzx * pd_w, pzy * pd_h };
4717 const int count = g->lines_count + 1;
4718 // if count > MAX_SAVED_LINES we alert that the next lines won't be saved in params
4719 // but they still may be used for the current section (that's why we still allow them)
4720 if(count > MAX_SAVED_LINES) dt_control_log(_("only %d lines can be saved in parameters"), MAX_SAVED_LINES);
4721
4722 dt_iop_ashift_line_t *lines = (dt_iop_ashift_line_t *)malloc(sizeof(dt_iop_ashift_line_t) * count);
4723 for(int i = 0; i < g->lines_count; i++)
4724 {
4725 lines[i] = g->lines[i];
4726 }
4727 if(g->lines)
4728 {
4729 dt_free(g->lines);
4730 }
4731 g->lines = lines;
4732 g->lines_count = count;
4733 _draw_basic_line(&g->lines[count - 1], pts[0], pts[1], pts[0], pts[1], ASHIFT_LINE_VERTICAL_SELECTED);
4734
4735 g->vertical_count++;
4736 g->vertical_weight += 1.0f;
4737 g->draw_near_point = g->lines_count * 2 - 1;
4738 return TRUE;
4739 }
4740
4741 // we switch into sweeping mode either if we anyhow take control
4742 // or if cursor was close to a line when button was pressed. in other
4743 // cases we hand over the event (for image panning)
4744 if((take_control || handled) && which == 3)
4745 {
4746 dt_control_change_cursor(GDK_PIRATE);
4747 g->isdeselecting = 1;
4748 }
4749 else if(take_control || handled)
4750 {
4751 dt_control_change_cursor(GDK_PLUS);
4752 g->isselecting = 1;
4753 }
4754
4755 if(handled)
4756 {
4757 _update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count);
4758 g->lines_version++;
4759 g->selecting_lines_version++;
4760 }
4761
4762 return (take_control || handled);
4763}
4764
4765int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
4766{
4769 if(IS_NULL_PTR(g)) return FALSE;
4770 const float wd = dt_dev_roi_request_preview_width(self->dev);
4771 const float ht = dt_dev_roi_request_preview_height(self->dev);
4772
4773 dt_control_change_cursor(GDK_LEFT_PTR);
4774
4775 if(g->straightening && !g->editing)
4776 {
4777 g->straightening = FALSE;
4778 // adjust the line with possible current angle and flip on this module
4779 float pzxpy[2] = { (float)x, (float)y };
4781 const float pzx = pzxpy[0];
4782 const float pzy = pzxpy[1];
4784 const float pd_w = geometry.processed_width;
4785 const float pd_h = geometry.processed_height;
4786 float pts[4] = { pzx * pd_w, pzy * pd_h, g->lastx * pd_w, g->lasty * pd_h };
4788 self->iop_order,
4790
4791 float dx = pts[0] - pts[2];
4792 float dy = pts[1] - pts[3];
4793 if(dx < 0)
4794 {
4795 dx = -dx;
4796 dy = -dy;
4797 }
4798
4799 float angle = atan2f(dy, dx);
4800 if(!(angle >= -M_PI / 2.0 && angle <= M_PI / 2.0)) angle = 0.0f;
4801 float close = angle;
4802 if(close > M_PI / 4.0)
4803 close = M_PI / 2.0 - close;
4804 else if(close < -M_PI / 4.0)
4805 close = -M_PI / 2.0 - close;
4806 else
4807 close = -close;
4808
4809 float a = 180.0 / M_PI * close;
4810 if(a < -180.0) a += 360.0;
4811 if(a > 180.0) a -= 360.0;
4812
4814 a -= dt_bauhaus_slider_get(g->rotation);
4815 p->rotation = -a;
4816 dt_bauhaus_slider_set(g->rotation, p->rotation);
4818
4819 do_crop(self, p);
4820
4821 dt_dev_add_history_item(self->dev, self, FALSE, TRUE);
4822 return TRUE;
4823 }
4824
4825 // ends eventual line move
4826 if(g->draw_line_move >= 0)
4827 {
4828 g->draw_line_move = -1;
4829 // we save the lines in params
4831 return TRUE;
4832 }
4833
4834 // release a drawn corner
4835 if(g->draw_point_move)
4836 {
4837 if(IS_NULL_PTR(g->lines))
4838 {
4839 g->draw_point_move = FALSE;
4840 g->draw_near_point = -1;
4841 return FALSE;
4842 }
4843
4844 // we determine the vertical/horizontal line type (that may have changed)
4845 // we also save the lines in params
4846 // points move are done directly in mouse_move routine
4847 for(int l = 0; l < g->lines_count; l++)
4848 {
4849 const dt_iop_ashift_linetype_t old_linetype = g->lines[l].type;
4850 _draw_retrieve_line_type(&g->lines[l]);
4851
4852 if(g->lines[l].type != old_linetype && g->lines[l].type == ASHIFT_LINE_VERTICAL_SELECTED)
4853 {
4854 g->vertical_count++;
4855 g->vertical_weight += 1.0f;
4856 g->horizontal_count--;
4857 g->horizontal_weight -= 1.0f;
4858 }
4859 else if(g->lines[l].type != old_linetype)
4860 {
4861 g->horizontal_count++;
4862 g->horizontal_weight += 1.0f;
4863 g->vertical_count--;
4864 g->vertical_weight -= 1.0f;
4865 }
4866
4867 g->lines_version++;
4868 }
4869 g->draw_point_move = FALSE;
4870 g->draw_near_point = -1;
4871
4872 // we save the lines in params
4874
4876 return TRUE;
4877 }
4878
4879 // finalize the isbounding mode
4880 // if user has released the shift button in-between -> do nothing
4881 if(g->isbounding != ASHIFT_BOUNDING_OFF && dt_modifier_is(state, GDK_SHIFT_MASK))
4882 {
4883 gboolean handled = FALSE;
4884
4885 // we compute the rectangle selection
4886 float pzxpy[2] = { (float)x, (float)y };
4888 const float pzx = pzxpy[0];
4889 const float pzy = pzxpy[1];
4890
4891 if(wd >= 1.0 && ht >= 1.0)
4892 {
4893 const int selectable_lines_count = (!IS_NULL_PTR(g->lines)
4894 && !IS_NULL_PTR(g->points)
4895 && !IS_NULL_PTR(g->points_idx))
4896 ? MIN(g->points_lines_count, g->lines_count)
4897 : 0;
4898
4899 // mark lines inside the rectangle
4900 if(selectable_lines_count > 0)
4901 _get_bounded_inside(g->points, g->points_idx, selectable_lines_count, pzx * wd, pzy * ht, g->lastx * wd,
4902 g->lasty * ht, g->isbounding);
4903
4904 // select or deselect lines within the rectangle according to isbounding state
4905 for(int n = 0; g->selecting_lines_version == g->lines_version && n < selectable_lines_count; n++)
4906 {
4907 if(g->points_idx[n].bounded == 0) continue;
4908
4909 if(g->isbounding == ASHIFT_BOUNDING_DESELECT)
4910 {
4911 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;
4912 handled = TRUE;
4913 }
4914 else if(g->current_structure_method != ASHIFT_METHOD_LINES)
4915 {
4916 g->lines[n].type |= ASHIFT_LINE_SELECTED;
4917 handled = TRUE;
4918 }
4919 }
4920
4921 if(handled)
4922 {
4923 _update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count);
4924 g->lines_version++;
4925 g->selecting_lines_version++;
4926 }
4927
4929 }
4930 }
4931
4932 // end of sweeping/isbounding mode
4933 g->isselecting = g->isdeselecting = 0;
4934 g->isbounding = ASHIFT_BOUNDING_OFF;
4935 g->near_delta = 0;
4936 g->lastx = g->lasty = -1.0f;
4937 g->crop_cx = g->crop_cy = -1.0f;
4938
4939 // if we have deselected drawn lines, we need to update params
4940 if(g->current_structure_method == ASHIFT_METHOD_LINES && which == 3)
4941 {
4942 // we save the lines in params
4944 }
4945
4946 return 0;
4947}
4948
4949int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
4950{
4952 if(IS_NULL_PTR(g)) return FALSE;
4953
4954 // do nothing if visibility of lines is switched off or no lines available
4955 if(IS_NULL_PTR(g->lines)) return FALSE;
4956
4957 if(g->near_delta > 0 && (g->isdeselecting || g->isselecting))
4958 {
4959 gboolean handled = FALSE;
4960
4961 float pzxpy[2] = { (float)x, (float)y };
4963 const float pzx = pzxpy[0];
4964 const float pzy = pzxpy[1];
4965 const float wd = dt_dev_roi_request_preview_width(self->dev);
4966 const float ht = dt_dev_roi_request_preview_height(self->dev);
4967
4968 float near_delta = 5.0f;
4969 if(g->current_structure_method == ASHIFT_METHOD_QUAD || g->current_structure_method == ASHIFT_METHOD_LINES)
4970 near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta_draw");
4971 else
4972 near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta");
4973 const float amount = up ? 0.8f : 1.25f;
4974 near_delta = MAX(4.0f, MIN(near_delta * amount, 100.0f));
4975 if(g->current_structure_method == ASHIFT_METHOD_QUAD || g->current_structure_method == ASHIFT_METHOD_LINES)
4976 dt_conf_set_float("plugins/darkroom/ashift/near_delta_draw", near_delta);
4977 else
4978 dt_conf_set_float("plugins/darkroom/ashift/near_delta", near_delta);
4979 g->near_delta = near_delta;
4980
4981 // for drawn structure, we stop here
4982 if(g->current_structure_method == ASHIFT_METHOD_QUAD || g->current_structure_method == ASHIFT_METHOD_LINES)
4983 return TRUE;
4984
4985 // gather information about "is_near"-ness in g->points_idx
4986 const int selectable_lines_count = (!IS_NULL_PTR(g->points) && !IS_NULL_PTR(g->points_idx))
4987 ? MIN(g->points_lines_count, g->lines_count)
4988 : 0;
4989 if(selectable_lines_count > 0)
4990 _get_near(g->points, g->points_idx, selectable_lines_count, pzx * wd, pzy * ht, g->near_delta, TRUE);
4991
4992 // iterate over all lines close to the pointer and change "selected" state.
4993 for(int n = 0; g->selecting_lines_version == g->lines_version && n < selectable_lines_count; n++)
4994 {
4995 if(g->points_idx[n].is_near == 0)
4996 continue;
4997
4998 if(g->isdeselecting)
4999 {
5000 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;
5001 handled = TRUE;
5002 }
5003 else if(g->isselecting && g->current_structure_method != ASHIFT_METHOD_LINES)
5004 {
5005 g->lines[n].type |= ASHIFT_LINE_SELECTED;
5006 handled = TRUE;
5007 }
5008
5009 handled = TRUE;
5010 }
5011
5012 if(handled)
5013 {
5014 _update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count);
5015 g->lines_version++;
5016 g->selecting_lines_version++;
5017 }
5018
5020 return TRUE;
5021 }
5022
5023 return FALSE;
5024}
5025
5026
5027void _make_controls_sensitive(dt_iop_module_t *self, const gboolean sensitive)
5028{
5030 gtk_widget_set_sensitive(g->structure_auto, sensitive);
5031 gtk_widget_set_sensitive(g->structure_lines, sensitive);
5032 gtk_widget_set_sensitive(g->structure_quad, sensitive);
5033 _gui_update_structure_states(self, sensitive && g->lines && g->lines_count > 0);
5034}
5035
5036
5037void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
5038{
5041
5042#ifdef ASHIFT_DEBUG
5043 model_probe(self, p, g->lastfit);
5044#endif
5045
5046 if(w == g->rotation)
5047 p->rotation = dt_bauhaus_slider_get(g->rotation);
5048 else if(w == g->lensshift_h)
5049 p->lensshift_h = dt_bauhaus_slider_get(g->lensshift_h);
5050 else if(w == g->lensshift_v)
5051 p->lensshift_v = dt_bauhaus_slider_get(g->lensshift_v);
5052 else if(w == g->shear)
5053 p->shear = dt_bauhaus_slider_get(g->shear);
5054 else if(w == g->mode)
5055 {
5056 p->mode = dt_bauhaus_combobox_get(g->mode);
5057 gtk_widget_set_visible(g->specifics, p->mode == ASHIFT_MODE_SPECIFIC);
5058 }
5059 else if(w == g->f_length)
5060 p->f_length = dt_bauhaus_slider_get(g->f_length);
5061 else if(w == g->crop_factor)
5062 p->crop_factor = dt_bauhaus_slider_get(g->crop_factor);
5063 else if(w == g->orthocorr)
5064 p->orthocorr = dt_bauhaus_slider_get(g->orthocorr);
5065 else if(w == g->aspect)
5066 p->aspect = dt_bauhaus_slider_get(g->aspect);
5067
5068 _make_controls_sensitive(self, g->editing);
5069
5070 do_crop(self, p);
5071}
5072
5073void gui_reset(struct dt_iop_module_t *self)
5074{
5076 g->editing = FALSE;
5077 g->jobcode = ASHIFT_JOBCODE_NONE;
5078 g->jobparams = 0;
5080
5082 memcpy(p, self->default_params, sizeof(dt_iop_ashift_params_t));
5083 memcpy(&g->previous_params, p, sizeof(dt_iop_ashift_params_t));
5084 memcpy(&g->new_params, p, sizeof(dt_iop_ashift_params_t));
5085 dt_bauhaus_combobox_set(g->cropmode, p->cropmode);
5086 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->edit_button), FALSE);
5087 gtk_button_set_label(GTK_BUTTON(g->edit_button), _("Edit"));
5088 gtk_widget_set_sensitive(g->commit_button, FALSE);
5089 dt_control_change_cursor(GDK_LEFT_PTR);
5091
5092 /* reset eventual remaining structures */
5093 _do_clean_structure(self, p, FALSE);
5095}
5096
5097static void fitting_option_changed(GtkWidget *widget, gpointer user_data)
5098{
5099 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5101 g->fitting_mode = dt_bauhaus_combobox_get(widget);
5102}
5103
5104static void cropmode_callback(GtkWidget *widget, gpointer user_data)
5105{
5106 if(dt_gui_widgets_suppressed()) return;
5107
5108 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5111 const int crop_mode = dt_bauhaus_combobox_get(g->cropmode);
5112 dt_conf_set_int("plugins/darkroom/ashift/autocrop_value", crop_mode);
5113
5114 p->cropmode = crop_mode;
5115 do_crop(self, p);
5116
5117 if(g->editing)
5118 {
5119 // do_crop() already resealed the temporary GUI-only contract and refreshed the ROI.
5120 }
5121 else
5122 {
5123 // Commit the crop mode and the margins computed above together. Scheduling the pipe from
5124 // do_crop() before this history item existed made the non-edit path synchronize stale margins.
5125 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
5126 }
5127}
5128
5129/* The fit_v/fit_h buttons are labeled from the user's point of view, i.e. the final,
5130 * post-flip image the user sees on screen. ashift itself runs upstream of the flip module in
5131 * iop_order (see doc/reorganisation.md and the ashift notes in CLAUDE.md) and always fits lines
5132 * in its own, pre-flip buffer space, so when the flip module rotates the image 90 degrees, the
5133 * on-screen "horizontal"/"vertical" directions are swapped relative to that buffer space.
5134 * dt_image_get_effective_orientation() reads the flip module's committed history, falling back
5135 * to EXIF, so it reflects the real final orientation regardless of how it was set (camera EXIF,
5136 * lighttable rotate, or the flip module edited directly). */
5138{
5139 if(!self->dev) return FALSE;
5141 return (orientation & ORIENTATION_SWAP_XY) != 0;
5142}
5143
5144static int _event_fit_v_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
5145{
5146 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5147 if(dt_gui_widgets_suppressed()) return FALSE;
5148
5149 if(event->button == 1)
5150 {
5153 const gboolean swap_axes = _ashift_orientation_swaps_axes(self);
5154
5156 switch(g->fitting_mode)
5157 {
5158 case(ASHIFT_FITTING_ALL):
5160 g->lastfit = fitaxis = swap_axes ? ASHIFT_FIT_HORIZONTALLY : ASHIFT_FIT_VERTICALLY;
5161 break;
5164 break;
5165 case(ASHIFT_FITTING_LENS):
5167 break;
5168 default:
5169 fitaxis = ASHIFT_FIT_NONE;
5170 }
5171
5173
5174 if(self->enabled)
5175 {
5176 // module is enable -> we process directly
5177 do_fit(self, p, fitaxis);
5178 }
5179 else
5180 {
5181 // module is not enabled -> invoke it and queue the job to be processed once
5182 // the preview image is ready
5183 g->jobcode = ASHIFT_JOBCODE_FIT;
5184 g->jobparams = g->lastfit = fitaxis;
5186 }
5187 return TRUE;
5188 }
5189 return FALSE;
5190}
5191
5192static int _event_fit_h_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
5193{
5194 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5195 if(dt_gui_widgets_suppressed()) return FALSE;
5196
5197 if(event->button == 1)
5198 {
5201 const gboolean swap_axes = _ashift_orientation_swaps_axes(self);
5202
5204 switch(g->fitting_mode)
5205 {
5206 case(ASHIFT_FITTING_ALL):
5208 g->lastfit = fitaxis = swap_axes ? ASHIFT_FIT_VERTICALLY : ASHIFT_FIT_HORIZONTALLY;
5209 break;
5212 break;
5213 case(ASHIFT_FITTING_LENS):
5215 break;
5216 default:
5217 fitaxis = ASHIFT_FIT_NONE;
5218 }
5219
5221
5222 if(self->enabled)
5223 {
5224 // module is enable -> we process directly
5225 do_fit(self, p, fitaxis);
5226 }
5227 else
5228 {
5229 // module is not enabled -> invoke it and queue the job to be processed once
5230 // the preview image is ready
5231 g->jobcode = ASHIFT_JOBCODE_FIT;
5232 g->jobparams = g->lastfit = fitaxis;
5234 }
5235 return TRUE;
5236 }
5237 return FALSE;
5238}
5239
5240static int _event_fit_both_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
5241{
5242 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5243 if(dt_gui_widgets_suppressed()) return FALSE;
5244
5245 if(event->button == 1)
5246 {
5249
5251 switch(g->fitting_mode)
5252 {
5253 case(ASHIFT_FITTING_ALL):
5254 g->lastfit = fitaxis = ASHIFT_FIT_BOTH_SHEAR;
5255 break;
5257 g->lastfit = fitaxis = ASHIFT_FIT_ROTATION_BOTH_LINES;
5258 break;
5259 case(ASHIFT_FITTING_LENS):
5260 g->lastfit = fitaxis = ASHIFT_FIT_BOTH_NO_ROTATION;
5261 break;
5263 g->lastfit = fitaxis = ASHIFT_FIT_BOTH;
5264 break;
5265 default:
5266 g->lastfit = fitaxis = ASHIFT_FIT_NONE;
5267 }
5268
5270
5271 if(self->enabled)
5272 {
5273 // module is enable -> we process directly
5274 do_fit(self, p, fitaxis);
5275 }
5276 else
5277 {
5278 // module is not enabled -> invoke it and queue the job to be processed once
5279 // the preview image is ready
5280 g->jobcode = ASHIFT_JOBCODE_FIT;
5281 g->jobparams = g->lastfit = fitaxis;
5283 }
5284 return TRUE;
5285 }
5286 return FALSE;
5287}
5288
5289static int _event_structure_auto_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
5290{
5291 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5292 if(dt_gui_widgets_suppressed()) return FALSE;
5293
5294 if(event->button == 1)
5295 {
5298
5299 _do_clean_structure(self, p, TRUE);
5300
5301 const int control = dt_modifiers_include(event->state, DT_PRIMARY_MASK);
5302 const int shift = dt_modifiers_include(event->state, GDK_SHIFT_MASK);
5303
5304 dt_iop_ashift_enhance_t enhance =
5305 (control ? ASHIFT_ENHANCE_EDGES : 0) | (shift ? ASHIFT_ENHANCE_DETAIL : 0);
5306
5307 if(!enhance) enhance = ASHIFT_ENHANCE_NONE;
5308
5309 // if the button is unselected, we don't go further
5310 if(enhance == ASHIFT_ENHANCE_NONE && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
5311 {
5313 return TRUE;
5314 }
5315 else
5316 {
5317 // force the button to be untoggled, so the update routine can enable it
5318 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
5319 }
5320
5321 g->current_structure_method = ASHIFT_METHOD_AUTO;
5322
5324
5325 if(self->enabled)
5326 {
5327 // module is enabled -> process directly
5328 (void)_do_get_structure_auto(self, p, enhance);
5329 }
5330 else
5331 {
5332 // module is not enabled -> invoke it and queue the job to be processed once
5333 // the preview image is ready
5335 g->jobparams = enhance;
5336 }
5337
5339 return TRUE;
5340 }
5341 return FALSE;
5342}
5343
5344static int _event_structure_quad_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
5345{
5346 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5348 if(dt_gui_widgets_suppressed()) return FALSE;
5349 if(!g->editing) return FALSE;
5350
5352
5353 if(self->enabled)
5354 {
5355 // module is enabled -> process directly
5357 }
5358 else
5359 {
5360 // module is not enabled -> invoke it and queue the job to be processed once
5361 // the preview image is ready
5364 }
5365
5366 return TRUE;
5367}
5368
5369static int _event_structure_lines_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
5370{
5371 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5373 if(dt_gui_widgets_suppressed()) return FALSE;
5374 if(!g->editing) return FALSE;
5375
5377
5378 if(self->enabled)
5379 {
5380 // module is enabled -> process directly
5382 }
5383 else
5384 {
5385 // module is not enabled -> invoke it and queue the job to be processed once
5386 // the preview image is ready
5388 }
5389
5391 return TRUE;
5392}
5393
5394static void _enter_edit_mode(GtkToggleButton* button, struct dt_iop_module_t *self)
5395{
5398 if(!self->enabled) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
5399
5400 g->editing = gtk_toggle_button_get_active(button);
5401 dt_control_change_cursor(GDK_LEFT_PTR);
5403 _make_controls_sensitive(self, g->editing);
5404
5405 if(g->editing)
5406 {
5408
5409 // Take a backup of current params
5410 memcpy(&g->previous_params, p, sizeof(dt_iop_ashift_params_t));
5411
5412 // Init the working copy of params for user interactions.
5413 memcpy(&g->new_params, p, sizeof(dt_iop_ashift_params_t));
5414
5415 gtk_button_set_label(GTK_BUTTON(button), _("Cancel"));
5416 gtk_widget_set_sensitive(g->commit_button, TRUE);
5418 }
5419 else
5420 {
5422
5423 // Restore the params backup
5424 memcpy(p, &g->previous_params, sizeof(dt_iop_ashift_params_t));
5425
5426 // Commit the params backup
5427 gui_changed(self, NULL, NULL);
5428
5430 dt_bauhaus_combobox_set(g->cropmode, p->cropmode);
5432
5433 // Update GUI
5434 gtk_button_set_label(GTK_BUTTON(button), _("Edit"));
5435 gtk_widget_set_sensitive(g->commit_button, FALSE);
5436 }
5437
5438 // Entering or leaving edit mode changes ashift's runtime crop contract. Settle the composed
5439 // geometry first, then publish the resulting full-image dimensions before waking either pixel
5440 // worker. Resyncing first and queuing separate ZOOMED updates afterwards let a worker start
5441 // with the previous ROI, get killed by the next update, and repeatedly feed slightly different
5442 // preview dimensions back into the GUI geometry.
5446}
5447
5448static void _event_commit_clicked(GtkButton *button, dt_iop_module_t *self)
5449{
5452
5453 // Close edit mode on commit
5454 g->editing = FALSE;
5455 gtk_widget_set_sensitive(g->commit_button, FALSE);
5456 _make_controls_sensitive(self, g->editing);
5457
5459
5460 memcpy(p, &g->new_params, sizeof(dt_iop_ashift_params_t));
5461
5462 // Commit history and refresh view
5463 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
5467
5468 // The following will de-activate the edit button and trigger the callback.
5469 // Prevent the callback to revert the param change.
5470 g_signal_handlers_block_by_func(g->edit_button, _enter_edit_mode, self);
5471 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->edit_button), FALSE);
5472 gtk_button_set_label(GTK_BUTTON(g->edit_button), _("Edit"));
5473 g_signal_handlers_unblock_by_func(g->edit_button, _enter_edit_mode, self);
5474}
5475
5477{
5480
5481 dt_iop_ashift_jobcode_t jobcode = g->jobcode;
5482 int jobparams = g->jobparams;
5483
5484 // purge
5485 g->jobcode = ASHIFT_JOBCODE_NONE;
5486 g->jobparams = 0;
5487
5488 if(dt_gui_widgets_suppressed()) return;
5489
5490 switch(jobcode)
5491 {
5494 break;
5495
5498 break;
5499
5502 break;
5503
5504 case ASHIFT_JOBCODE_FIT:
5505 do_fit(self, p, (dt_iop_ashift_fitaxis_t)jobparams);
5506 break;
5507
5509 default:
5510 break;
5511 }
5512}
5513
5514// Run any pending GUI job once the preview pipe has finished a render. By this point process() has
5515// captured g->buf (it always runs while editing because the module is in cache-bypass mode), so the
5516// pixel-reading jobs (auto-detection, fit) find their buffer; the geometry-only jobs (manual
5517// line/quad, auto-crop) never needed it. Driving jobs from PREVIEW_PIPE_FINISHED — rather than from
5518// a cache request that only renders up to the previous module — is what avoids the buffer never
5519// being captured and the pipe spinning forever (#710).
5520static void _event_process_after_preview_callback(gpointer instance, gpointer user_data)
5521{
5522 (void)instance;
5523 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5525 if(IS_NULL_PTR(g) || g->jobcode == ASHIFT_JOBCODE_NONE || dt_gui_widgets_suppressed()) return;
5526
5529}
5530
5539static void _event_process_after_ui_callback(gpointer instance, gpointer user_data)
5540{
5541 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5544
5545 (void)instance;
5546
5547 if(dt_gui_widgets_suppressed()) return;
5548 if(!g->editing && p->cropmode == ASHIFT_CROP_OFF) return;
5549
5551
5552 // Force the control-line overlay to recompute its screen coordinates on the next expose. Its
5553 // cache is keyed on the preview-pipe hash, which is published asynchronously by the worker, so on
5554 // crop-mode/rotation changes the cached points would otherwise lag a frame behind the freshly
5555 // rendered geometry (the overlay "not adjusting" to the new crop). The thumbnail/virtual-pipe
5556 // geometry refreshed just above is authoritative here, so invalidating the cache now makes the
5557 // overlay follow it reliably.
5559
5561}
5562
5563/* --- the shared geometry core ----------------------------------------------------------
5564 *
5565 * commit_params() below and the geometry service's record (develop/geometry/geometry.h) both go
5566 * through _ashift_resolve(), and both evaluate through homography(), which is already a pure
5567 * function of the resolved parameters and the input dimensions. So the correction the pipe
5568 * applies and the one the GUI overlays are drawn against are the same one, expressed once.
5569 *
5570 * The edit-mode branch is why the record cannot be built from history alone: while the ashift
5571 * GUI is editing, the effective parameters live in g->new_params -- which never reach history --
5572 * and the crop is neutralised so the full transformed frame is visible. A record built from
5573 * history would describe a crop nobody is rendering, exactly when the overlays matter most.
5574 */
5575static void _ashift_resolve(struct dt_iop_module_t *self, const dt_iop_ashift_params_t *const p1,
5577{
5579
5580 const gboolean editing = !IS_NULL_PTR(g) && g->editing;
5581 const dt_iop_ashift_params_t *p = editing ? &g->new_params : p1;
5582
5583 d->rotation = p->rotation;
5584 d->lensshift_v = p->lensshift_v;
5585 d->lensshift_h = p->lensshift_h;
5586 d->shear = p->shear;
5587 d->f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor;
5588 d->orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;
5589 d->aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;
5590
5591 if(editing)
5592 {
5593 // In editing mode we need to see the full uncropped image to set up the crop frame and run
5594 // structure detection on the whole image. Same approach as the crop module's commit_params():
5595 // neutralize the crop here so the pipe renders the full transformed image (the darkroom view
5596 // already expects the uncropped output while a cache-bypass module is focused). modify_roi_in()
5597 // then naturally requests the full input, so process() captures the whole image into g->buf. The
5598 // real crop is reapplied from params as soon as edit mode is committed/cancelled. (#710)
5599 d->cl = 0.0f;
5600 d->cr = 1.0f;
5601 d->ct = 0.0f;
5602 d->cb = 1.0f;
5603 }
5604 else
5605 {
5606 d->cl = p->cl;
5607 d->cr = p->cr;
5608 d->ct = p->ct;
5609 d->cb = p->cb;
5610 }
5611}
5612
5615{
5617}
5618
5619/* --- the geometry service's view of this module (develop/geometry/geometry.h) --------- */
5620
5622static int _ashift_geometry_apply(const void *data, const dt_geometry_record_t *const record, float *points,
5623 size_t points_count, const int direction)
5624{
5625 const dt_iop_ashift_data_t *const d = (const dt_iop_ashift_data_t *)data;
5626 if(isneutral(d)) return 1;
5627
5628 float DT_ALIGNED_ARRAY h[3][3];
5629 homography((float *)h, d->rotation, d->lensshift_v, d->lensshift_h, d->shear, d->f_length_kb, d->orthocorr,
5630 d->aspect, record->in.width, record->in.height, direction);
5631
5632 // clipping offset
5633 const float fullwidth = (float)record->out.width / (d->cr - d->cl);
5634 const float fullheight = (float)record->out.height / (d->cb - d->ct);
5635 const float cx = fullwidth * d->cl;
5636 const float cy = fullheight * d->ct;
5637
5638 const gboolean forward = (direction == ASHIFT_HOMOGRAPH_FORWARD);
5639 for(size_t i = 0; i < points_count * 2; i += 2)
5640 {
5641 float DT_ALIGNED_PIXEL pi[3] = { points[i] + (forward ? 0.f : cx), points[i + 1] + (forward ? 0.f : cy),
5642 1.0f };
5643 float DT_ALIGNED_PIXEL po[3];
5644 mat3mulv(po, (float *)h, pi);
5645 points[i] = po[0] / po[2] - (forward ? cx : 0.f);
5646 points[i + 1] = po[1] / po[2] - (forward ? cy : 0.f);
5647 }
5648 return 1;
5649}
5650
5651static void _ashift_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
5652{
5653 const dt_iop_ashift_data_t *const d = (const dt_iop_ashift_data_t *)data;
5654 *out = *in;
5655 if(isneutral(d)) return;
5656
5657 float h[3][3];
5658 homography((float *)h, d->rotation, d->lensshift_v, d->lensshift_h, d->shear, d->f_length_kb, d->orthocorr,
5659 d->aspect, in->width, in->height, ASHIFT_HOMOGRAPH_FORWARD);
5660
5661 float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX;
5662
5663 // the four vertices of the input rect, through the homograph, give the output bounding box
5664 for(int y = 0; y < in->height; y += in->height - 1)
5665 for(int x = 0; x < in->width; x += in->width - 1)
5666 {
5667 float pin[3] = { (in->x + x) / in->scale, (in->y + y) / in->scale, 1.0f };
5668 float pout[3];
5669 mat3mulv(pout, (float *)h, pin);
5670 pout[0] = pout[0] / pout[2] * out->scale;
5671 pout[1] = pout[1] / pout[2] * out->scale;
5672 xm = MIN(xm, pout[0]);
5673 xM = MAX(xM, pout[0]);
5674 ym = MIN(ym, pout[1]);
5675 yM = MAX(yM, pout[1]);
5676 }
5677
5678 // clipping adjustments
5679 out->width = floorf((xM - xm + 1) * (d->cr - d->cl));
5680 out->height = floorf((yM - ym + 1) * (d->cb - d->ct));
5681}
5682
5683static int _ashift_geometry_transform(const void *data, const dt_geometry_record_t *const record,
5684 dt_geometry_chain_t *chain, float *points, size_t points_count)
5685{
5686 return _ashift_geometry_apply(data, record, points, points_count, ASHIFT_HOMOGRAPH_FORWARD);
5687}
5688
5689static int _ashift_geometry_backtransform(const void *data, const dt_geometry_record_t *const record,
5690 dt_geometry_chain_t *chain, float *points, size_t points_count)
5691{
5692 return _ashift_geometry_apply(data, record, points, points_count, ASHIFT_HOMOGRAPH_INVERTED);
5693}
5694
5700
5701gboolean geometry_record(struct dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
5702{
5704 if(IS_NULL_PTR(data)) return FALSE;
5705
5706 _ashift_resolve(self, (const dt_iop_ashift_params_t *)params, data);
5707
5708 record->data = data;
5709 record->free_data = dt_free_gpointer;
5711 return TRUE;
5712}
5713
5715 const dt_dev_pixelpipe_iop_t *piece)
5716{
5717 (void)self;
5718 (void)pipe;
5719 (void)piece;
5720 return TRUE;
5721}
5722
5724{
5726 piece->data = (void *)d;
5727 piece->data_size = sizeof(dt_iop_ashift_data_t);
5728}
5729
5731{
5732 dt_free_align(piece->data);
5733 piece->data = NULL;
5734}
5735
5736void gui_update(struct dt_iop_module_t *self)
5737{
5740
5741 // Slider labels (image-orientation dependent), widget defaults and the per-image edit-mode state
5742 // reset, formerly in reload_defaults(). They need live widgets and the gui_data edit caches, so
5743 // they belong here on the GUI thread. Read the already-computed default_params; never recompute.
5744 const dt_iop_ashift_params_t *const d = (const dt_iop_ashift_params_t *)self->default_params;
5745
5746 // orientation only needed as a-priori info to label the lens-shift sliders before the pixelpipe
5747 // is set up; later the pipeline assessment gives the definite result.
5748 int isflipped = 0;
5749 if(self->dev)
5750 {
5751 const dt_image_t *img = &self->dev->image_storage;
5752 isflipped = (img->orientation == ORIENTATION_ROTATE_CCW_90_DEG
5753 || img->orientation == ORIENTATION_ROTATE_CW_90_DEG) ? 1 : 0;
5754 }
5755
5756 char string_v[256];
5757 char string_h[256];
5758 snprintf(string_v, sizeof(string_v), _("lens shift (%s)"), isflipped ? _("horizontal") : _("vertical"));
5759 snprintf(string_h, sizeof(string_h), _("lens shift (%s)"), isflipped ? _("vertical") : _("horizontal"));
5760 dt_bauhaus_widget_set_label(g->lensshift_v, string_v);
5761 dt_bauhaus_widget_set_label(g->lensshift_h, string_h);
5762
5763 dt_bauhaus_slider_set_default(g->f_length, d->f_length);
5764 dt_bauhaus_slider_set_default(g->crop_factor, d->crop_factor);
5765 dt_bauhaus_combobox_set_default(g->cropmode, d->cropmode);
5766
5768 dt_free(g->buf);
5769 g->buf = NULL;
5770 g->buf_width = 0;
5771 g->buf_height = 0;
5772 g->buf_x_off = 0;
5773 g->buf_y_off = 0;
5774 g->buf_scale = 1.0f;
5776 g->isflipped = -1;
5777 g->lastfit = ASHIFT_FIT_NONE;
5779
5780 g->fitting = 0;
5781 g->editing = FALSE;
5782 dt_free(g->lines);
5783 g->lines = NULL;
5784 g->lines_count = 0;
5785 g->horizontal_count = 0;
5786 g->vertical_count = 0;
5788 g->lines_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
5789 g->rotation_range = ROTATION_RANGE_SOFT;
5790 g->lensshift_v_range = LENSSHIFT_RANGE_SOFT;
5791 g->lensshift_h_range = LENSSHIFT_RANGE_SOFT;
5792 g->shear_range = SHEAR_RANGE_SOFT;
5793 g->lines_version = 0;
5794 g->isselecting = 0;
5795 g->isdeselecting = 0;
5796 g->isbounding = ASHIFT_BOUNDING_OFF;
5797 g->near_delta = 0;
5798 g->selecting_lines_version = 0;
5799
5800 dt_free(g->points);
5801 g->points = NULL;
5802 dt_free(g->points_idx);
5803 g->points_idx = NULL;
5804 g->points_lines_count = 0;
5805 g->points_version = 0;
5806
5807 g->jobcode = ASHIFT_JOBCODE_NONE;
5808 g->jobparams = 0;
5809 g->lastx = g->lasty = -1.0f;
5810 g->crop_cx = g->crop_cy = 1.0f;
5811
5812 g->current_structure_method = ASHIFT_METHOD_NONE;
5813 g->draw_line_move = -1;
5814 g->draw_near_point = -1;
5815 g->draw_point_move = FALSE;
5816 memcpy(&g->previous_params, self->default_params, sizeof(dt_iop_ashift_params_t));
5817 memcpy(&g->new_params, self->default_params, sizeof(dt_iop_ashift_params_t));
5818
5820
5821 gtk_widget_set_visible(g->specifics, p->mode == ASHIFT_MODE_SPECIFIC);
5822 dt_bauhaus_combobox_set(g->cropmode, p->cropmode);
5824
5826}
5827
5829{
5830 // our module is disabled by default
5831 module->default_enabled = 0;
5832
5833 float f_length = DEFAULT_F_LENGTH;
5834 float crop_factor = 1.0f;
5835
5836 // try to get focal length and crop factor from image data
5837 if(module->dev)
5838 {
5839 const dt_image_t *img = &module->dev->image_storage;
5840 // focal length should be available in exif data if lens is electronically coupled to the camera
5841 f_length = isfinite(img->exif_focal_length) && img->exif_focal_length > 0.0f ? img->exif_focal_length : f_length;
5842 // crop factor of the camera is often not available and user will need to set it manually in the gui
5843 crop_factor = isfinite(img->exif_crop) && img->exif_crop > 0.0f ? img->exif_crop : crop_factor;
5844 }
5845
5846 // init defaults:
5847 ((dt_iop_ashift_params_t *)module->default_params)->f_length = f_length;
5848 ((dt_iop_ashift_params_t *)module->default_params)->crop_factor = crop_factor;
5849 ((dt_iop_ashift_params_t *)module->default_params)->cropmode
5850 = dt_conf_get_int("plugins/darkroom/ashift/autocrop_value");
5851
5852 // The slider labels (image-orientation dependent), widget defaults and the edit-mode state reset
5853 // (preview buffer, detected lines, fit/structure state) are done in gui_update() on the GUI thread:
5854 // reload_defaults() also runs on export/thumbnail devs with no gui_data and off the GUI thread, so
5855 // it must touch params only.
5856}
5857
5858
5860{
5863 module->data = gd;
5864
5865 const int program = 2; // basic.cl, from programs.conf
5866 gd->kernel_ashift_bilinear = dt_opencl_create_kernel(program, "ashift_bilinear");
5867 gd->kernel_ashift_bicubic = dt_opencl_create_kernel(program, "ashift_bicubic");
5868 gd->kernel_ashift_mitchell = dt_opencl_create_kernel(program, "ashift_mitchell");
5869}
5870
5879
5880// adjust labels of lens shift parameters according to flip status of image
5881static gboolean _event_draw(GtkWidget *widget, cairo_t *cr, dt_iop_module_t *self)
5882{
5884 if(dt_gui_widgets_suppressed()) return FALSE;
5885
5887 const int isflipped = g->isflipped;
5889
5890 if(isflipped == -1) return FALSE;
5891
5892 char string_v[256];
5893 char string_h[256];
5894
5895 snprintf(string_v, sizeof(string_v), _("lens shift (%s)"), isflipped ? _("horizontal") : _("vertical"));
5896 snprintf(string_h, sizeof(string_h), _("lens shift (%s)"), isflipped ? _("vertical") : _("horizontal"));
5897
5899 dt_bauhaus_widget_set_label(g->lensshift_v, string_v);
5900 dt_bauhaus_widget_set_label(g->lensshift_h, string_h);
5902
5903 return FALSE;
5904}
5905
5906void gui_init(struct dt_iop_module_t *self)
5907{
5909
5910 dt_iop_gui_enter_critical_section(self); //not actually needed, we're the only one with a pointer to this instance
5911 g->buf = NULL;
5912 g->buf_width = 0;
5913 g->buf_height = 0;
5914 g->buf_x_off = 0;
5915 g->buf_y_off = 0;
5916 g->buf_scale = 1.0f;
5918 g->isflipped = -1;
5919 g->lastfit = ASHIFT_FIT_NONE;
5920 g->editing = FALSE;
5922
5923 g->fitting = 0;
5924 g->fitting_mode = ASHIFT_FITTING_ALL;
5925 g->lines = NULL;
5926 g->lines_count = 0;
5927 g->vertical_count = 0;
5928 g->horizontal_count = 0;
5929 g->lines_version = 0;
5930 g->points = NULL;
5931 g->points_idx = NULL;
5932 g->points_lines_count = 0;
5933 g->points_version = 0;
5935 g->lines_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
5936 g->rotation_range = ROTATION_RANGE_SOFT;
5937 g->lensshift_v_range = LENSSHIFT_RANGE_SOFT;
5938 g->lensshift_h_range = LENSSHIFT_RANGE_SOFT;
5939 g->shear_range = SHEAR_RANGE_SOFT;
5940 g->isselecting = 0;
5941 g->isdeselecting = 0;
5942 g->isbounding = ASHIFT_BOUNDING_OFF;
5943 g->near_delta = 0;
5944 g->selecting_lines_version = 0;
5945
5946 g->jobcode = ASHIFT_JOBCODE_NONE;
5947 g->jobparams = 0;
5948 g->lastx = g->lasty = -1.0f;
5949 g->crop_cx = g->crop_cy = 1.0f;
5950 memcpy(&g->previous_params, self->params, sizeof(dt_iop_ashift_params_t));
5951 memcpy(&g->new_params, self->params, sizeof(dt_iop_ashift_params_t));
5952
5953 g->draw_near_point = -1;
5954 g->draw_line_move = -1;
5955
5956 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5957
5958 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
5959
5960 g->edit_button = gtk_toggle_button_new_with_label(_("Edit"));
5961 g_signal_connect(GTK_TOGGLE_BUTTON(g->edit_button), "toggled", G_CALLBACK(_enter_edit_mode), self);
5962 gtk_box_pack_start(GTK_BOX(box), g->edit_button, TRUE, TRUE, 0);
5963
5964 g->commit_button = dt_action_button_new((dt_lib_module_t *)self, N_("Apply"), _event_commit_clicked, self, _("Apply changes"), 0, 0);
5965 gtk_box_pack_start(GTK_BOX(box), g->commit_button, TRUE, TRUE, 0);
5966 gtk_widget_set_sensitive(g->commit_button, FALSE);
5967
5968 gtk_box_pack_start(GTK_BOX(self->gui->widget), GTK_WIDGET(box), TRUE, TRUE, 0);
5969
5970 GtkWidget *main_box = self->gui->widget;
5971
5973 (&g->cs,
5974 "plugins/darkroom/ashift/expand_values",
5975 _("Manual settings"),
5976 GTK_BOX(main_box), GTK_PACK_END);
5977
5978 self->gui->widget = GTK_WIDGET(g->cs.container);
5979
5980 g->rotation = dt_bauhaus_slider_from_params(self, N_("rotation"));
5981 dt_bauhaus_slider_set_format(g->rotation, "\302\260");
5983
5984 g->lensshift_v = dt_bauhaus_slider_from_params(self, "lensshift_v");
5986 dt_bauhaus_slider_set_digits(g->lensshift_v, 3);
5987
5988 g->lensshift_h = dt_bauhaus_slider_from_params(self, "lensshift_h");
5990 dt_bauhaus_slider_set_digits(g->lensshift_h, 3);
5991
5992 g->shear = dt_bauhaus_slider_from_params(self, "shear");
5994
5995 g->mode = dt_bauhaus_combobox_from_params(self, "mode");
5996 self->gui->widget = g->specifics = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5997
5998 g->f_length = dt_bauhaus_slider_from_params(self, "f_length");
5999 dt_bauhaus_slider_set_soft_range(g->f_length, 10.0f, 1000.0f);
6000 dt_bauhaus_slider_set_digits(g->f_length, 0);
6001 dt_bauhaus_slider_set_format(g->f_length, " mm");
6002
6003 g->crop_factor = dt_bauhaus_slider_from_params(self, "crop_factor");
6004 dt_bauhaus_slider_set_soft_range(g->crop_factor, 1.0f, 2.0f);
6005
6006 g->orthocorr = dt_bauhaus_slider_from_params(self, "orthocorr");
6007 dt_bauhaus_slider_set_format(g->orthocorr, "%");
6008 // this parameter could serve to finetune between generic model (0%) and specific model (100%).
6009 // however, users can more easily get the same effect with the aspect adjust parameter so we keep
6010 // this one hidden.
6011 gtk_widget_set_no_show_all(g->orthocorr, TRUE);
6012 gtk_widget_set_visible(g->orthocorr, FALSE);
6013
6014 g->aspect = dt_bauhaus_slider_from_params(self, "aspect");
6015
6016 gtk_box_pack_start(GTK_BOX(g->cs.container), g->specifics, TRUE, TRUE, 0);
6017
6018 self->gui->widget = main_box;
6019
6020 GtkGrid *auto_grid = GTK_GRID(gtk_grid_new());
6021 gtk_grid_set_row_spacing(auto_grid, DT_GUI_BOX_SPACING);
6022 gtk_grid_set_column_spacing(auto_grid, DT_GUI_BOX_SPACING);
6023
6024 gtk_grid_attach(auto_grid, dt_ui_label_new(_("Mark reference lines")), 0, 0, 1, 1);
6025
6026 g->structure_lines = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_drawn, 0, NULL);
6027 gtk_widget_set_hexpand(GTK_WIDGET(g->structure_lines), TRUE);
6028 gtk_grid_attach(auto_grid, g->structure_lines, 1, 0, 1, 1);
6029
6030 g->structure_quad = dtgtk_togglebutton_new(dtgtk_cairo_paint_draw_structure, 0, NULL);
6031 gtk_widget_set_hexpand(GTK_WIDGET(g->structure_quad), TRUE);
6032 gtk_grid_attach(auto_grid, g->structure_quad, 2, 0, 1, 1);
6033
6034 g->structure_auto = dtgtk_togglebutton_new(dtgtk_cairo_paint_structure, 0, NULL);
6035 gtk_widget_set_hexpand(GTK_WIDGET(g->structure_auto), TRUE);
6036 gtk_grid_attach(auto_grid, g->structure_auto, 3, 0, 1, 1);
6037
6038 gtk_grid_attach(auto_grid, dt_ui_label_new(_("Fit perspective transform")), 0, 1, 1, 1);
6039
6041 gtk_widget_set_hexpand(GTK_WIDGET(g->fit_v), TRUE);
6042 gtk_grid_attach(auto_grid, g->fit_v, 1, 1, 1, 1);
6043
6045 gtk_widget_set_hexpand(GTK_WIDGET(g->fit_h), TRUE);
6046 gtk_grid_attach(auto_grid, g->fit_h, 2, 1, 1, 1);
6047
6048 g->fit_both = dtgtk_button_new(dtgtk_cairo_paint_perspective, 3, NULL);
6049 gtk_widget_set_hexpand(GTK_WIDGET(g->fit_both), TRUE);
6050 gtk_grid_attach(auto_grid, g->fit_both, 3, 1, 1, 1);
6051
6052 gtk_widget_show_all(GTK_WIDGET(auto_grid));
6053 gtk_box_pack_start(GTK_BOX(self->gui->widget), GTK_WIDGET(auto_grid), TRUE, TRUE, 0);
6054
6055 GtkWidget *helpers = dt_ui_section_label_new(_("Fitting options"));
6056 gtk_box_pack_start(GTK_BOX(self->gui->widget), helpers, TRUE, TRUE, 0);
6057
6058 const gchar *option_labels[] = { _("rotation, lens shift, shear"),
6059 _("rotation, lens shift"),
6060 _("rotation only"),
6061 _("lens shift only"), NULL };
6062 g->fitting_option
6064 (GtkCallback)fitting_option_changed, self, option_labels);
6065 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->fitting_option, TRUE, TRUE, 0);
6066
6067 const gchar *crop_labels[] = { _("off"), _("largest area"), _("original format"), NULL };
6068 g->cropmode
6069 = dt_bauhaus_combobox_new_full(dt_bauhaus_get_global(), DT_GUI_MODULE(self), _("automatic cropping"), NULL,
6070 ((dt_iop_ashift_params_t *)self->params)->cropmode,
6071 (GtkCallback)cropmode_callback, self, crop_labels);
6073 ((dt_iop_ashift_params_t *)self->default_params)->cropmode);
6074 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->cropmode, FALSE, FALSE, 0);
6075
6076 self->gui->widget = main_box;
6077
6078 gtk_widget_set_tooltip_text(g->rotation, _("rotate image\nright-click and drag to define a horizontal or vertical line by drawing on the image"));
6079 gtk_widget_set_tooltip_text(g->lensshift_v, _("apply lens shift correction in one direction"));
6080 gtk_widget_set_tooltip_text(g->lensshift_h, _("apply lens shift correction in one direction"));
6081 gtk_widget_set_tooltip_text(g->shear, _("shear the image along one diagonal"));
6082 gtk_widget_set_tooltip_text(g->cropmode, _("automatically crop to avoid black edges"));
6083 gtk_widget_set_tooltip_text(g->mode, _("lens model of the perspective correction: "
6084 "generic or according to the focal length"));
6085 gtk_widget_set_tooltip_text(g->f_length, _("focal length of the lens, "
6086 "default value set from EXIF data if available"));
6087 gtk_widget_set_tooltip_text(g->crop_factor, _("crop factor of the camera sensor, "
6088 "default value set from EXIF data if available, "
6089 "manual setting is often required"));
6090 gtk_widget_set_tooltip_text(g->orthocorr, _("the level of lens dependent correction, set to maximum for full lens dependency, "
6091 "set to zero for the generic case"));
6092 gtk_widget_set_tooltip_text(g->aspect, _("adjust aspect ratio of image by horizontal and vertical scaling"));
6093 gtk_widget_set_tooltip_text(g->fit_v, _("automatically correct for vertical perspective distortion\n"
6094 "ctrl+click to only fit rotation\n"
6095 "shift+click to only fit lens shift"));
6096 gtk_widget_set_tooltip_text(g->fit_h, _("automatically correct for horizontal perspective distortion\n"
6097 "ctrl+click to only fit rotation\n"
6098 "shift+click to only fit lens shift"));
6099 gtk_widget_set_tooltip_text(g->fit_both, _("automatically correct for vertical and "
6100 "horizontal perspective distortions; fitting rotation,"
6101 "lens shift in both directions, and shear\n"
6102 "ctrl+click to only fit rotation\n"
6103 "shift+click to only fit lens shift\n"
6104 "ctrl+shift+click to only fit rotation and lens shift"));
6105 gtk_widget_set_tooltip_text(g->structure_auto, _("automatically analyse line structure in image\n"
6106 "ctrl+click for an additional edge enhancement\n"
6107 "shift+click for an additional detail enhancement\n"
6108 "ctrl+shift+click for a combination of both methods"));
6109 gtk_widget_set_tooltip_text(g->structure_quad, _("manually define perspective rectangle"));
6110 gtk_widget_set_tooltip_text(g->structure_lines, _("manually draw structure lines"));
6111
6112 g_signal_connect(G_OBJECT(g->fit_v), "button-press-event", G_CALLBACK(_event_fit_v_button_clicked),
6113 (gpointer)self);
6114 g_signal_connect(G_OBJECT(g->fit_h), "button-press-event", G_CALLBACK(_event_fit_h_button_clicked),
6115 (gpointer)self);
6116 g_signal_connect(G_OBJECT(g->fit_both), "button-press-event", G_CALLBACK(_event_fit_both_button_clicked),
6117 (gpointer)self);
6118 g_signal_connect(G_OBJECT(g->structure_quad), "button-press-event", G_CALLBACK(_event_structure_quad_clicked),
6119 (gpointer)self);
6120 g_signal_connect(G_OBJECT(g->structure_lines), "button-press-event", G_CALLBACK(_event_structure_lines_clicked),
6121 (gpointer)self);
6122 g_signal_connect(G_OBJECT(g->structure_auto), "button-press-event", G_CALLBACK(_event_structure_auto_clicked),
6123 (gpointer)self);
6124 g_signal_connect(G_OBJECT(self->gui->widget), "draw", G_CALLBACK(_event_draw), self);
6125
6126 /* Pending GUI jobs run once the preview pipe published a fresh render: by then process() has
6127 captured g->buf. The UI-pipe-finished hook only refreshes the overlay geometry. */
6129 G_CALLBACK(_event_process_after_preview_callback), self);
6131 G_CALLBACK(_event_process_after_ui_callback), self);
6132}
6133
6135{
6139
6141 if(g->lines)
6142 {
6143 dt_free(g->lines);
6144 }
6145 if(g->buf)
6146 {
6147 dt_free(g->buf);
6148 }
6149 if(g->points)
6150 {
6151 dt_free(g->points);
6152 }
6153 if(g->points_idx)
6154 {
6155 dt_free(g->points_idx);
6156 }
6157
6159}
6160
6161// clang-format off
6162// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
6163// vim: shiftwidth=2 expandtab tabstop=2 cindent
6164// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
6165// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
int operation_tags()
Definition ashift.c:188
static int _draw_near_point(dt_develop_t *dev, const float x, const float y, const float *points, const int limit)
Definition ashift.c:4239
int operation_tags_filter()
Definition ashift.c:193
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 ashift.c:5613
static double logit(double x, double min, double max)
Definition ashift.c:2024
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 ashift.c:1060
static void vec3norm(float *dst, const float *const v)
Definition ashift.c:696
const char ** description(struct dt_iop_module_t *self)
Definition ashift.c:168
static int _remove_outliers(dt_iop_module_t *module)
Definition ashift.c:1923
int default_group()
Definition ashift.c:183
static void sRGB_to_XYZ(const dt_aligned_pixel_t sRGB, dt_aligned_pixel_t XYZ)
Definition ashift.c:1366
static float vec3scalar(const float *const v1, const float *const v2)
Definition ashift.c:725
int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
Definition ashift.c:4949
__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 ashift.c:3179
#define RANSAC_OPTIMIZATION_STEPS
Definition ashift.c:114
static void _gui_update_structure_states(dt_iop_module_t *self, gboolean enable)
Definition ashift.c:2770
static int update_colors(struct dt_iop_module_t *self, dt_iop_ashift_points_idx_t *points_idx, int points_lines_count)
Definition ashift.c:3605
#define SHEAR_RANGE_SOFT
Definition ashift.c:99
#define RANSAC_RUNS
Definition ashift.c:110
static double ilogit(double L, double min, double max)
Definition ashift.c:2035
dt_iop_ashift_bounding_t
Definition ashift.c:307
@ ASHIFT_BOUNDING_SELECT
Definition ashift.c:309
@ ASHIFT_BOUNDING_OFF
Definition ashift.c:308
@ ASHIFT_BOUNDING_DESELECT
Definition ashift.c:310
static void _update_lines_count(const dt_iop_ashift_line_t *lines, const int lines_count, int *vertical_count, int *horizontal_count)
Definition ashift.c:4220
#define MAX_SAVED_LINES
Definition ashift.c:136
void reload_defaults(dt_iop_module_t *module)
Definition ashift.c:5828
static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_iop_ashift_fitaxis_t dir)
Definition ashift.c:2172
static void vec3prodn(float *dst, const float *const v1, const float *const v2)
Definition ashift.c:678
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 ashift.c:999
static void _event_process_after_preview_callback(gpointer instance, gpointer user_data)
Definition ashift.c:5520
#define LENSSHIFT_RANGE
Definition ashift.c:96
#define LSD_DENSITY_TH
Definition ashift.c:107
#define NMS_CROP_SCALE
Definition ashift.c:122
#define RANSAC_HURDLE
Definition ashift.c:116
#define LSD_QUANT
Definition ashift.c:104
static void vec3lnorm(float *dst, const float *const v)
Definition ashift.c:711
#define SHEAR_RANGE
Definition ashift.c:98
static int fact(const int n)
Definition ashift.c:1729
const char * aliases()
Definition ashift.c:163
static gboolean _draw_retrieve_lines_from_params(dt_iop_module_t *self, dt_iop_ashift_method_t method)
Definition ashift.c:2824
static void _get_near(const float *points, dt_iop_ashift_points_idx_t *points_idx, const int lines_count, float pzx, float pzy, float delta, gboolean multiple)
Definition ashift.c:3500
static int _event_structure_auto_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition ashift.c:5289
#define LSD_SIGMA_SCALE
Definition ashift.c:103
static int _get_structure(dt_iop_module_t *module, dt_iop_ashift_enhance_t enhance)
Definition ashift.c:1607
dt_iop_ashift_params_t * _get_ashift_params(dt_iop_module_t *self)
Definition ashift.c:666
dt_iop_ashift_linecolor_t
Definition ashift.c:241
@ ASHIFT_LINECOLOR_GREY
Definition ashift.c:242
@ ASHIFT_LINECOLOR_GREEN
Definition ashift.c:243
@ ASHIFT_LINECOLOR_YELLOW
Definition ashift.c:246
@ ASHIFT_LINECOLOR_RED
Definition ashift.c:244
@ ASHIFT_LINECOLOR_BLUE
Definition ashift.c:245
static int _event_structure_lines_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition ashift.c:5369
static void XYZ_to_sRGB(const dt_aligned_pixel_t XYZ, dt_aligned_pixel_t sRGB)
Definition ashift.c:1358
dt_iop_ashift_fitaxis_t
Definition ashift.c:250
@ ASHIFT_FIT_BOTH_NO_ROTATION
Definition ashift.c:266
@ ASHIFT_FIT_HORIZONTALLY_NO_ROTATION
Definition ashift.c:265
@ ASHIFT_FIT_ROTATION_HORIZONTAL_LINES
Definition ashift.c:271
@ ASHIFT_FIT_VERTICALLY
Definition ashift.c:260
@ ASHIFT_FIT_ROTATION_VERTICAL_LINES
Definition ashift.c:270
@ ASHIFT_FIT_FLIP
Definition ashift.c:273
@ ASHIFT_FIT_LENS_VERT
Definition ashift.c:253
@ ASHIFT_FIT_LINES_VERT
Definition ashift.c:256
@ ASHIFT_FIT_ROTATION
Definition ashift.c:252
@ ASHIFT_FIT_LENS_BOTH
Definition ashift.c:258
@ ASHIFT_FIT_SHEAR
Definition ashift.c:255
@ ASHIFT_FIT_LENS_HOR
Definition ashift.c:254
@ ASHIFT_FIT_LINES_HOR
Definition ashift.c:257
@ ASHIFT_FIT_NONE
Definition ashift.c:251
@ ASHIFT_FIT_BOTH_SHEAR
Definition ashift.c:268
@ ASHIFT_FIT_HORIZONTALLY
Definition ashift.c:261
@ ASHIFT_FIT_ROTATION_BOTH_LINES
Definition ashift.c:272
@ ASHIFT_FIT_BOTH
Definition ashift.c:262
@ ASHIFT_FIT_VERTICALLY_NO_ROTATION
Definition ashift.c:264
@ ASHIFT_FIT_LINES_BOTH
Definition ashift.c:259
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition ashift.c:5723
static void _clear_crop_box(dt_iop_ashift_params_t *p)
Reset the active crop rectangle to the complete transformed image.
Definition ashift.c:752
static int call_distort_transform(struct dt_iop_module_t *self, float *points, size_t points_count)
Definition ashift.c:3804
static void shuffle(int *a, const int N)
Definition ashift.c:1719
static double model_fitness(double *params, void *data)
Definition ashift.c:2049
static int _event_fit_h_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition ashift.c:5192
dt_iop_ashift_nmsresult_t
Definition ashift.c:277
@ NMS_SUCCESS
Definition ashift.c:278
@ NMS_NOT_ENOUGH_LINES
Definition ashift.c:279
@ NMS_INSANE
Definition ashift.c:281
@ NMS_DID_NOT_CONVERGE
Definition ashift.c:280
const char * name()
Definition ashift.c:158
static void _event_process_after_ui_callback(gpointer instance, gpointer user_data)
Refresh ashift overlay geometry once the displayed pipe published its new output.
Definition ashift.c:5539
void gui_reset(struct dt_iop_module_t *self)
Definition ashift.c:5073
static void _ashift_resolve(struct dt_iop_module_t *self, const dt_iop_ashift_params_t *const p1, dt_iop_ashift_data_t *d)
Definition ashift.c:5575
void gui_update(struct dt_iop_module_t *self)
Definition ashift.c:5736
static int quickperm(int *a, int *p, const int N, int *i)
Definition ashift.c:1702
int distort_backtransform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *points, size_t points_count)
Definition ashift.c:1030
#define RANSAC_EPSILON
Definition ashift.c:111
void gui_init(struct dt_iop_module_t *self)
Definition ashift.c:5906
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition ashift.c:4508
static int _do_clean_structure(dt_iop_module_t *module, dt_iop_ashift_params_t *p, gboolean save_drawn)
Definition ashift.c:2928
static int _do_get_structure_auto(dt_iop_module_t *self, dt_iop_ashift_params_t *p, dt_iop_ashift_enhance_t enhance)
Definition ashift.c:2953
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition ashift.c:5037
static double crop_fitness(double *params, void *data)
Definition ashift.c:2450
int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
Definition ashift.c:4765
static void _run_pending_preview_job(dt_iop_module_t *self)
Definition ashift.c:5476
#define DEFAULT_F_LENGTH
Definition ashift.c:127
static void gamma_correct(const float *const in, float *const out, const int width, const int height)
Definition ashift.c:1420
static int _event_fit_v_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition ashift.c:5144
static int isneutral(const dt_iop_ashift_data_t *data)
Definition ashift.c:982
#define NMS_SCALE
Definition ashift.c:119
static int edge_enhance(const double *in, double *out, const int width, const int height)
Definition ashift.c:1325
void _make_controls_sensitive(dt_iop_module_t *self, const gboolean sensitive)
Definition ashift.c:5027
dt_iop_ashift_crop_t
Definition ashift.c:300
@ ASHIFT_CROP_LARGEST
Definition ashift.c:302
@ ASHIFT_CROP_ASPECT
Definition ashift.c:303
@ ASHIFT_CROP_OFF
Definition ashift.c:301
#define RANSAC_EPSILON_STEP
Definition ashift.c:112
void cleanup_global(dt_iop_module_so_t *module)
Definition ashift.c:5871
dt_iop_ashift_jobcode_t
Definition ashift.c:314
@ ASHIFT_JOBCODE_GET_STRUCTURE
Definition ashift.c:316
@ ASHIFT_JOBCODE_GET_STRUCTURE_LINES
Definition ashift.c:318
@ ASHIFT_JOBCODE_GET_STRUCTURE_QUAD
Definition ashift.c:319
@ ASHIFT_JOBCODE_FIT
Definition ashift.c:317
@ ASHIFT_JOBCODE_NONE
Definition ashift.c:315
static uint64_t _get_lines_hash(const dt_iop_ashift_line_t *lines, const int lines_count)
Definition ashift.c:3592
static __DT_CLONE_TARGETS__ void homography(float *homograph, const float angle, const float shift_v, const float shift_h, const float shear, const float f_length_kb, const float orthocorr, const float aspect, const int width, const int height, dt_iop_ashift_homodir_t dir)
Definition ashift.c:763
static void _get_bounded_inside(const float *points, dt_iop_ashift_points_idx_t *points_idx, const int points_lines_count, float pzx, float pzy, float pzx2, float pzy2, dt_iop_ashift_bounding_t mode)
Definition ashift.c:3545
static void _enter_edit_mode(GtkToggleButton *button, struct dt_iop_module_t *self)
Definition ashift.c:5394
static void crop_constraint(double *params, int pcount)
Definition ashift.c:2433
static gboolean _event_draw(GtkWidget *widget, cairo_t *cr, dt_iop_module_t *self)
Definition ashift.c:5881
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition ashift.c:199
#define LENSSHIFT_RANGE_SOFT
Definition ashift.c:97
static const dt_geometry_vtable_t _ashift_geometry_vtable
Definition ashift.c:5695
#define ROTATION_RANGE
Definition ashift.c:94
int flags()
Definition ashift.c:177
static int _ashift_geometry_backtransform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition ashift.c:5689
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 ashift.c:3809
dt_iop_ashift_fitting_t
Definition ashift.c:233
@ ASHIFT_FITTING_LENS_ROTATION
Definition ashift.c:235
@ ASHIFT_FITTING_ROTATION
Definition ashift.c:236
@ ASHIFT_FITTING_LENS
Definition ashift.c:237
@ ASHIFT_FITTING_ALL
Definition ashift.c:234
#define MAX_TANGENTIAL_DEVIATION
Definition ashift.c:101
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 *const roi_out, dt_iop_roi_t *roi_in)
Definition ashift.c:1180
void gui_cleanup(struct dt_iop_module_t *self)
Definition ashift.c:6134
#define NMS_CROP_EPSILON
Definition ashift.c:121
#define MAT3SWAP(a, b)
Definition ashift.c:760
static void _draw_recompute_line_length(dt_iop_ashift_line_t *line)
Definition ashift.c:4253
static gboolean _ashift_orientation_swaps_axes(dt_iop_module_t *self)
Definition ashift.c:5137
#define MINIMUM_FITLINES
Definition ashift.c:117
#define LSD_ANG_TH
Definition ashift.c:105
static void _draw_retrieve_line_type(dt_iop_ashift_line_t *line)
Definition ashift.c:2734
#define ROTATION_RANGE_SOFT
Definition ashift.c:95
#define RANSAC_ELIMINATION_RATIO
Definition ashift.c:113
static void edge_enhance_1d(const double *in, double *out, const int width, const int height, dt_iop_ashift_enhance_t dir)
Definition ashift.c:1269
static int detail_enhance(const float *const in, float *const out, const int width, const int height)
Definition ashift.c:1374
#define MIN_LINE_LENGTH
Definition ashift.c:100
static void ransac(const dt_iop_ashift_line_t *lines, int *index_set, int *inout_set, const int set_count, const float total_weight, const int xmin, const int xmax, const int ymin, const int ymax)
Definition ashift.c:1750
#define RANSAC_OPTIMIZATION_DRY_RUNS
Definition ashift.c:115
static int vec3isnull(const float *const v)
Definition ashift.c:731
#define LSD_GAMMA
Definition ashift.c:109
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition ashift.c:5730
static void _ashift_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
Definition ashift.c:5651
static int _event_structure_quad_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition ashift.c:5344
dt_iop_ashift_enhance_t
Definition ashift.c:285
@ ASHIFT_ENHANCE_DETAIL
Definition ashift.c:288
@ ASHIFT_ENHANCE_NONE
Definition ashift.c:286
@ ASHIFT_ENHANCE_EDGES
Definition ashift.c:287
@ ASHIFT_ENHANCE_HORIZONTAL
Definition ashift.c:289
@ ASHIFT_ENHANCE_VERTICAL
Definition ashift.c:290
dt_iop_ashift_linetype_t
Definition ashift.c:219
@ ASHIFT_LINE_IRRELEVANT
Definition ashift.c:220
@ ASHIFT_LINE_VERTICAL_NOT_SELECTED
Definition ashift.c:225
@ ASHIFT_LINE_MASK
Definition ashift.c:229
@ ASHIFT_LINE_DIRVERT
Definition ashift.c:223
@ ASHIFT_LINE_HORIZONTAL_NOT_SELECTED
Definition ashift.c:226
@ ASHIFT_LINE_HORIZONTAL_SELECTED
Definition ashift.c:228
@ ASHIFT_LINE_RELEVANT
Definition ashift.c:222
@ ASHIFT_LINE_VERTICAL_SELECTED
Definition ashift.c:227
@ ASHIFT_LINE_SELECTED
Definition ashift.c:224
#define LSD_LOG_EPS
Definition ashift.c:106
static void _do_get_structure_lines(dt_iop_module_t *self)
Definition ashift.c:3012
#define SQR(a)
Definition ashift.c:132
static void _draw_save_lines_to_params(dt_iop_module_t *self)
Definition ashift.c:2778
#define NMS_EPSILON
Definition ashift.c:118
static void do_fit(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_iop_ashift_fitaxis_t dir)
Definition ashift.c:3105
void init_global(dt_iop_module_so_t *module)
Definition ashift.c:5859
gboolean geometry_record(struct dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
Definition ashift.c:5701
#define LSD_SCALE
Definition ashift.c:102
dt_iop_ashift_method_t
Definition ashift.c:205
@ ASHIFT_METHOD_NONE
Definition ashift.c:206
@ ASHIFT_METHOD_AUTO
Definition ashift.c:207
@ ASHIFT_METHOD_QUAD
Definition ashift.c:208
@ ASHIFT_METHOD_LINES
Definition ashift.c:209
static int line_detect(float *in, const int width, const int height, const int x_off, const int y_off, const float scale, dt_iop_ashift_line_t **alines, int *lcount, int *vcount, int *hcount, float *vweight, float *hweight, dt_iop_ashift_enhance_t enhance, const int is_raw)
Definition ashift.c:1433
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
Definition ashift.c:3329
static void rgb2grey256(const float *const in, double *const out, const int width, const int height)
Definition ashift.c:1258
int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
Definition ashift.c:4259
#define LSD_N_BINS
Definition ashift.c:108
static int _ashift_geometry_apply(const void *data, const dt_geometry_record_t *const record, float *points, size_t points_count, const int direction)
Apply the homography plus the clipping offset, in whichever direction.
Definition ashift.c:5622
static void _do_get_structure_quad(dt_iop_module_t *self)
Definition ashift.c:3042
#define NMS_ITERATIONS
Definition ashift.c:120
dt_iop_ashift_mode_t
Definition ashift.c:294
@ ASHIFT_MODE_GENERIC
Definition ashift.c:295
@ ASHIFT_MODE_SPECIFIC
Definition ashift.c:296
dt_iop_ashift_homodir_t
Definition ashift.c:213
@ ASHIFT_HOMOGRAPH_FORWARD
Definition ashift.c:214
@ ASHIFT_HOMOGRAPH_INVERTED
Definition ashift.c:215
static void fitting_option_changed(GtkWidget *widget, gpointer user_data)
Definition ashift.c:5097
static void _event_commit_clicked(GtkButton *button, dt_iop_module_t *self)
Definition ashift.c:5448
static int _event_fit_both_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition ashift.c:5240
static int get_points(struct dt_iop_module_t *self, const dt_iop_ashift_line_t *lines, const int lines_count, const int lines_version, float **points, float **extremas, dt_iop_ashift_points_idx_t **points_idx, int *points_lines_count, float scale)
Definition ashift.c:3637
static void cropmode_callback(GtkWidget *widget, gpointer user_data)
Definition ashift.c:5104
static void do_crop(dt_iop_module_t *self, dt_iop_ashift_params_t *p)
Definition ashift.c:2529
static void swap(int *a, int *b)
Definition ashift.c:1694
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)
Definition ashift.c:1119
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 ashift.c:564
static int _ashift_geometry_transform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition ashift.c:5683
gboolean runtime_data_hash(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition ashift.c:5714
static void _draw_basic_line(dt_iop_ashift_line_t *line, float x1, float y1, float x2, float y2, dt_iop_ashift_linetype_t type)
Definition ashift.c:2743
#define NMS_CROP_ITERATIONS
Definition ashift.c:123
static double * LineSegmentDetection(int *n_out, double *img, int X, int Y, double scale, double sigma_scale, double quant, double ang_th, double log_eps, double density_th, int n_bins, int **reg_img, int *reg_x, int *reg_y)
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
GtkWidget * dt_bauhaus_combobox_new_full(dt_bauhaus_t *bh, dt_gui_module_t *self, const char *label, const char *tip, int pos, GtkCallback callback, gpointer data, const char **texts)
Definition bauhaus.c:1705
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1491
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
Definition bauhaus.c:1400
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:432
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:183
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:157
__DT_CLONE_TARGETS__ void dt_bilateral_slice_to_output(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
Definition bilateral.c:396
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:341
float sigma_s
Definition bilateral.h:3
float sigma_r
Definition bilateral.h:3
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:135
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_gui_new_collapsible_section(dt_gui_collapsible_section_t *cs, const char *confname, const char *label, GtkBox *parent, GtkPackType pack)
Create a collapsible section and pack it into the parent box.
void dt_gui_update_collapsible_section(dt_gui_collapsible_section_t *cs)
@ IOP_CS_RGB
static const float x
const float l2
const float f
const float l1
const float v
#define A(y, x)
int mat3inv(float *const dst, const float *const src)
Thin alias of mat3inv_float(), same contract.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
dt_Lab_to_XYZ(Lab, XYZ)
static const float const float const float min
static dt_aligned_pixel_t XYZ
static dt_aligned_pixel_t sRGB
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static const float const float C
dt_XYZ_to_Lab(XYZ, Lab)
const float delta
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
#define P(V, params)
dt_image_pipe_class_t dt_image_pipe_class(const dt_image_t *img)
const char * dt_image_pipe_class_name(const dt_image_pipe_class_t klass)
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
dt_image_orientation_t dt_image_get_effective_orientation(const dt_image_t *img)
void dt_control_mouse_is_dragging(gboolean state)
Definition control.c:432
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_queue_redraw()
Request a redraw of the whole workspace.
Definition control.c:919
#define dt_control_change_cursor(cursor)
Definition control.h:121
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
gboolean dt_dev_geometry_get_raw_size(const dt_develop_t *dev, int32_t *width, int32_t *height)
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_resync_history_all(dev)
#define dt_dev_pixelpipe_update_zoom_preview(dev)
#define dt_dev_pixelpipe_update_zoom_main(dev)
#define dt_dev_pixelpipe_update_history_preview(dev)
int32_t dt_dev_roi_request_preview_height(const dt_develop_t *dev)
int32_t dt_dev_roi_request_preview_width(const dt_develop_t *dev)
float dt_dev_get_natural_scale(dt_develop_t *dev)
Definition develop.c:1944
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:353
int dt_dev_distort_backtransform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The inverse of dt_dev_distort_transform_gui(), same rules.
Definition develop.c:1699
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1959
float dt_dev_get_zoom_scale(const dt_develop_t *dev, const gboolean preview)
Definition develop.c:989
gboolean dt_dev_module_geometry_gui(dt_develop_t *dev, dt_iop_module_t *module, dt_iop_roi_t *in, dt_iop_roi_t *out)
One module's own input and output rectangles at full resolution, from the geometry service.
Definition develop.c:1751
int dt_dev_distort_transform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The GUI's bounded transform folds, composed by the geometry service.
Definition develop.c:1691
gboolean dt_dev_clip_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Clip the view to the ROI. WARNING: this must be done before any translation.
Definition develop.c:2028
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:2071
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:402
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1144
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:1826
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:111
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:108
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
GtkWidget * geometry
its size, under the preview
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:147
static void dt_gui_draw_rounded_rectangle(cairo_t *cr, float width, float height, float x, float y)
Definition draw.h:554
const int res
Definition dtpthread.h:351
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
int dt_geometry_module_transform(dt_develop_t *dev, const dt_iop_module_t *module, float *points, const size_t points_count)
Definition geometry.c:386
void dt_geometry_chain_rebuild(dt_develop_t *dev)
Rebuild the chain from the dev's current modules and history. GUI thread only.
Definition geometry.c:247
Where things are on the image, answered without a pipeline.
GdkRGBA color[]
Definition geotagging.c:541
static int perm[512]
Definition grain.c:174
#define DT_GUI_MODULE(x)
void dt_guides_draw(cairo_t *cr, const float left, const float top, const float width, const float height, const float zoom_scale)
Definition guides.c:800
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
static gboolean enable(const dt_image_t *image)
Definition highlights.c:879
float dt_boundingbox_t[4]
Definition image.h:83
dt_image_orientation_t
Definition image.h:216
@ ORIENTATION_SWAP_XY
Definition image.h:221
@ ORIENTATION_ROTATE_CCW_90_DEG
Definition image.h:228
@ ORIENTATION_ROTATE_CW_90_DEG
Definition image.h:229
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1669
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
#define dt_iop_fmt_log(module, fmt,...)
Debug helper to trace a module's input-format-driven decisions on the -d pipe channel (DT_DEBUG_PIPE)...
Definition imageop.h:535
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
@ IOP_FLAGS_GUIDES_SPECIAL_DRAW
Definition imageop.h:197
@ IOP_FLAGS_TILING_FULL_ROI
Definition imageop.h:190
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_REPAIR
Definition imageop.h:159
@ IOP_TAG_DECORATION
Definition imageop.h:171
@ IOP_TAG_CLIPPING
Definition imageop.h:172
@ IOP_TAG_DISTORT
Definition imageop.h:170
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
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_INTERPOLATION_BICUBIC
@ DT_INTERPOLATION_BILINEAR
@ DT_INTERPOLATION_MITCHELL
@ DT_INTERPOLATION_USERPREF_WARP
static float kernel(const float *x, const float *y)
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1336
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static void mat3mul(float *const __restrict__ dest, const float *const __restrict__ m1, const float *const __restrict__ m2)
Definition math.h:163
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
static void mat3mulv(float *const __restrict__ dest, const float *const mat, const float *const __restrict__ v)
Definition math.h:148
#define DT_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
static int simplex(double(*objfunc)(double[], void *params), double start[], int n, double EPSILON, double scale, int maxiter, void(*constrain)(double[], int n), void *params)
#define N
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
int dt_opencl_copy_device_to_host(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2581
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2679
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
#define DT_PIXELPIPE_CACHE_HASH_INVALID
static uint64_t dt_dev_pixelpipe_get_hash(const dt_dev_pixelpipe_t *pipe)
#define eps
Definition rcd.c:81
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:177
@ DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED
This signal is raised when pipe is finished and the gui is attached no param, no returned value.
Definition signal.h:182
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
const float r
unsigned __int64 uint64_t
Definition strptime.c:75
Objective facts about the image a dev is working on.
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:167
dt_image_t image_storage
Definition develop.h:225
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
One module instance's contribution, as data.
Definition geometry.h:99
dt_iop_roi_t in
Definition geometry.h:117
const dt_geometry_vtable_t * vtable
Definition geometry.h:110
void(* free_data)(void *data)
Definition geometry.h:112
dt_iop_roi_t out
Definition geometry.h:118
A module's geometry, evaluated. Pure functions of the record's own data.
Definition geometry.h:75
void(* map_size)(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
Full-resolution input rect -> output rect. Mirrors modify_roi_out() at scale 1.
Definition geometry.h:77
dt_image_orientation_t orientation
Definition image.h:366
float exif_focal_length
Definition image.h:371
float exif_crop
Definition image.h:373
enum dt_interpolation_type id
dt_iop_ashift_linetype_t linemask
Definition ashift.c:429
dt_iop_ashift_line_t * lines
Definition ashift.c:430
dt_iop_ashift_linetype_t linetype
Definition ashift.c:428
GtkWidget * commit_button
Definition ashift.c:480
GtkWidget * edit_button
Definition ashift.c:479
dt_iop_ashift_jobcode_t jobcode
Definition ashift.c:524
GtkWidget * lensshift_v
Definition ashift.c:462
GtkWidget * structure_auto
Definition ashift.c:475
GtkWidget * f_length
Definition ashift.c:468
dt_iop_ashift_fitting_t fitting_mode
Definition ashift.c:539
GtkWidget * cropmode
Definition ashift.c:465
GtkWidget * structure_quad
Definition ashift.c:476
dt_iop_ashift_points_idx_t * points_idx
Definition ashift.c:507
GtkWidget * lensshift_h
Definition ashift.c:463
GtkWidget * structure_lines
Definition ashift.c:477
dt_iop_ashift_line_t * lines
Definition ashift.c:495
dt_gui_collapsible_section_t cs
Definition ashift.c:534
dt_iop_ashift_bounding_t isbounding
Definition ashift.c:488
dt_iop_ashift_fitaxis_t lastfit
Definition ashift.c:519
dt_iop_ashift_params_t previous_params
Definition ashift.c:536
GtkWidget * crop_factor
Definition ashift.c:469
GtkWidget * fitting_option
Definition ashift.c:478
GtkWidget * specifics
Definition ashift.c:467
GtkWidget * rotation
Definition ashift.c:461
dt_iop_ashift_params_t new_params
Definition ashift.c:537
GtkWidget * fit_both
Definition ashift.c:474
GtkWidget * orthocorr
Definition ashift.c:470
dt_iop_ashift_method_t current_structure_method
Definition ashift.c:527
dt_iop_ashift_linetype_t type
Definition ashift.c:408
dt_iop_ashift_mode_t mode
Definition ashift.c:339
dt_iop_ashift_mode_t mode
Definition ashift.c:352
dt_iop_ashift_crop_t cropmode
Definition ashift.c:354
dt_iop_ashift_mode_t mode
Definition ashift.c:371
dt_iop_ashift_crop_t cropmode
Definition ashift.c:373
float last_drawn_lines[50 *4]
Definition ashift.c:396
dt_iop_ashift_crop_t cropmode
Definition ashift.c:391
dt_iop_ashift_mode_t mode
Definition ashift.c:390
float last_quad_lines[8]
Definition ashift.c:398
dt_iop_ashift_linecolor_t color
Definition ashift.c:420
dt_iop_ashift_linetype_t type
Definition ashift.c:419
unsigned int channels
Definition format.h:83
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
gboolean enabled
Definition imageop.h:313
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
static gboolean dt_modifiers_include(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_structure(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_draw_structure(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_drawn(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_perspective(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)