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