1/*2 This file is part of darktable,3 Copyright (C) 2016-2026 darktable developers.45 darktable is free software: you can redistribute it and/or modify6 it under the terms of the GNU General Public License as published by7 the Free Software Foundation, either version 3 of the License, or8 (at your option) any later version.910 darktable is distributed in the hope that it will be useful,11 but WITHOUT ANY WARRANTY; without even the implied warranty of12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13 GNU General Public License for more details.1415 You should have received a copy of the GNU General Public License16 along with darktable. If not, see <http://www.gnu.org/licenses/>.17*/18192021222324252627282930313233343536373839404142434445464748// Inspiration to this module comes from the program ShiftN (http://www.shiftn.de) by49// Marcus Hebel.5051// Thanks to Marcus for his support when implementing part of the ShiftN functionality52// to darktable.53545556575859606162636465666768697071727374757677787980818283848586878889// define to get debugging output909192// maximum number of drawn lines that can be saved in parameters93// any change in this value needs to upgrade parameters version !949596// For line detection we use the LSD algorithm as published by Rafael Grompone:97//98// "LSD: a Line Segment Detector" by Rafael Grompone von Gioi,99// Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall,100// Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd101// http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd102103104// For parameter optimization we are using the Nelder-Mead simplex method105// implemented by Michael F. Hutt.106107108109DT_MODULE_INTROSPECTION(5, dt_iop_ashift_params_t)110111112const char *name()113{114 return _("rotate and perspective");115}116117const char *aliases()118{119 return _("rotation|keystone|distortion|crop|reframe");120}121122const char **description(dt_iop_module_t *self)123{124 return dt_iop_set_description(self, _("rotate or distort perspective"),125 _("corrective or creative"),126 _("linear, RGB, scene-referred"),127 _("geometric, RGB"),128 _("linear, RGB, scene-referred"));129}130131int flags()132{133 return IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_TILING_FULL_ROI | IOP_FLAGS_ONE_INSTANCE134 | IOP_FLAGS_ALLOW_FAST_PIPE135 | IOP_FLAGS_GUIDES_SPECIAL_DRAW | IOP_FLAGS_GUIDES_WIDGET;136}137138int default_group()139{140 return IOP_GROUP_CORRECT | IOP_GROUP_TECHNICAL;141}142143int operation_tags()144{145 return IOP_TAG_DISTORT;146}147148int operation_tags_filter()149{150 // switch off cropping and decoration, we want to see the full image.151 return IOP_TAG_DECORATION | IOP_TAG_CROPPING;152}153154dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self,155 dt_dev_pixelpipe_t *pipe,156 dt_dev_pixelpipe_iop_t *piece)157{158 return IOP_CS_RGB;159}160161typedef enum dt_iop_ashift_method_t162{163 ASHIFT_METHOD_NONE = 0,164 ASHIFT_METHOD_AUTO,165 ASHIFT_METHOD_QUAD,166 ASHIFT_METHOD_LINES167} dt_iop_ashift_method_t;168169typedef enum dt_iop_ashift_homodir_t170{171 ASHIFT_HOMOGRAPH_FORWARD,172 ASHIFT_HOMOGRAPH_INVERTED173} dt_iop_ashift_homodir_t;174175typedef enum dt_iop_ashift_linetype_t176{177 ASHIFT_LINE_IRRELEVANT = 0, // the line is found to be not interesting178 // eg. too short, or not horizontal or vertical179 ASHIFT_LINE_RELEVANT = 1 << 0, // the line is relevant for us180 ASHIFT_LINE_DIRVERT = 1 << 1, // the line is (mostly) vertical, else (mostly) horizontal181 ASHIFT_LINE_SELECTED = 1 << 2, // the line is selected for fitting182 ASHIFT_LINE_VERTICAL_NOT_SELECTED = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_DIRVERT,183 ASHIFT_LINE_HORIZONTAL_NOT_SELECTED = ASHIFT_LINE_RELEVANT,184 ASHIFT_LINE_VERTICAL_SELECTED = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_DIRVERT | ASHIFT_LINE_SELECTED,185 ASHIFT_LINE_HORIZONTAL_SELECTED = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED,186 ASHIFT_LINE_MASK = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_DIRVERT | ASHIFT_LINE_SELECTED187} dt_iop_ashift_linetype_t;188189typedef enum dt_iop_ashift_linecolor_t190{191 ASHIFT_LINECOLOR_GREY = 0,192 ASHIFT_LINECOLOR_GREEN = 1,193 ASHIFT_LINECOLOR_RED = 2,194 ASHIFT_LINECOLOR_BLUE = 3,195 ASHIFT_LINECOLOR_YELLOW = 4196} dt_iop_ashift_linecolor_t;197198typedef enum dt_iop_ashift_fitaxis_t199{200 ASHIFT_FIT_NONE = 0, // none201 ASHIFT_FIT_ROTATION = 1 << 0, // flag indicates to fit rotation angle202 ASHIFT_FIT_LENS_VERT = 1 << 1, // flag indicates to fit vertical lens shift203 ASHIFT_FIT_LENS_HOR = 1 << 2, // flag indicates to fit horizontal lens shift204 ASHIFT_FIT_SHEAR = 1 << 3, // flag indicates to fit shear parameter205 ASHIFT_FIT_LINES_VERT = 1 << 4, // use vertical lines for fitting206 ASHIFT_FIT_LINES_HOR = 1 << 5, // use horizontal lines for fitting207 ASHIFT_FIT_LENS_BOTH = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR,208 ASHIFT_FIT_LINES_BOTH = ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR,209 ASHIFT_FIT_VERTICALLY = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LINES_VERT,210 ASHIFT_FIT_HORIZONTALLY = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_HOR | ASHIFT_FIT_LINES_HOR,211 ASHIFT_FIT_BOTH = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR |212 ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR,213 ASHIFT_FIT_VERTICALLY_NO_ROTATION = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LINES_VERT,214 ASHIFT_FIT_HORIZONTALLY_NO_ROTATION = ASHIFT_FIT_LENS_HOR | ASHIFT_FIT_LINES_HOR,215 ASHIFT_FIT_BOTH_NO_ROTATION = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR |216 ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR,217 ASHIFT_FIT_BOTH_SHEAR = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR |218 ASHIFT_FIT_SHEAR | ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR,219 ASHIFT_FIT_ROTATION_VERTICAL_LINES = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LINES_VERT,220 ASHIFT_FIT_ROTATION_HORIZONTAL_LINES = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LINES_HOR,221 ASHIFT_FIT_ROTATION_BOTH_LINES = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR,222 ASHIFT_FIT_FLIP = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR | ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR223} dt_iop_ashift_fitaxis_t;224225typedef enum dt_iop_ashift_nmsresult_t226{227 NMS_SUCCESS = 0,228 NMS_NOT_ENOUGH_LINES = 1,229 NMS_DID_NOT_CONVERGE = 2,230 NMS_INSANE = 3231} dt_iop_ashift_nmsresult_t;232233typedef enum dt_iop_ashift_enhance_t234{235 ASHIFT_ENHANCE_NONE = 0,236 ASHIFT_ENHANCE_EDGES = 1 << 0,237 ASHIFT_ENHANCE_DETAIL = 1 << 1,238 ASHIFT_ENHANCE_HORIZONTAL = 0x100,239 ASHIFT_ENHANCE_VERTICAL = 0x200240} dt_iop_ashift_enhance_t;241242typedef enum dt_iop_ashift_mode_t243{244 ASHIFT_MODE_GENERIC = 0, // $DESCRIPTION: "generic"245 ASHIFT_MODE_SPECIFIC = 1 // $DESCRIPTION: "specific"246} dt_iop_ashift_mode_t;247248typedef enum dt_iop_ashift_crop_t249{250 ASHIFT_CROP_OFF = 0, // $DESCRIPTION: "off"251 ASHIFT_CROP_LARGEST = 1,// $DESCRIPTION: "largest area"252 ASHIFT_CROP_ASPECT = 2 // $DESCRIPTION: "original format"253} dt_iop_ashift_crop_t;254255typedef enum dt_iop_ashift_bounding_t256{257 ASHIFT_BOUNDING_OFF = 0,258 ASHIFT_BOUNDING_SELECT = 1,259 ASHIFT_BOUNDING_DESELECT = 2260} dt_iop_ashift_bounding_t;261262typedef enum dt_iop_ashift_jobcode_t263{264 ASHIFT_JOBCODE_NONE = 0,265 ASHIFT_JOBCODE_GET_STRUCTURE = 1,266 ASHIFT_JOBCODE_FIT = 2,267 ASHIFT_JOBCODE_GET_STRUCTURE_LINES = 3,268 ASHIFT_JOBCODE_GET_STRUCTURE_QUAD = 4,269 ASHIFT_JOBCODE_DO_CROP = 5270} dt_iop_ashift_jobcode_t;271272typedef struct dt_iop_ashift_params_t273{274 float rotation; // $MIN: -ROTATION_RANGE_SOFT $MAX: ROTATION_RANGE_SOFT $DEFAULT: 0.0275 float lensshift_v; // $MIN: -LENSSHIFT_RANGE_SOFT $MAX: LENSSHIFT_RANGE_SOFT $DEFAULT: 0.0 $DESCRIPTION: "lens shift (vertical)"276 float lensshift_h; // $MIN: -LENSSHIFT_RANGE_SOFT $MAX: LENSSHIFT_RANGE_SOFT $DEFAULT: 0.0 $DESCRIPTION: "lens shift (horizontal)"277 float shear; // $MIN: -SHEAR_RANGE_SOFT $MAX: SHEAR_RANGE_SOFT $DEFAULT: 0.0 $DESCRIPTION: "shear"278 float f_length; // $MIN: 1.0 $MAX: 2000.0 $DEFAULT: DEFAULT_F_LENGTH $DESCRIPTION: "focal length"279 float crop_factor; // $MIN: 0.5 $MAX: 10.0 $DEFAULT: 1.0 $DESCRIPTION: "crop factor"280 float orthocorr; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "lens dependence"281 float aspect; // $MIN: 0.5 $MAX: 2.0 $DEFAULT: 1.0 $DESCRIPTION: "aspect adjust"282 dt_iop_ashift_mode_t mode; // $DEFAULT: ASHIFT_MODE_GENERIC $DESCRIPTION: "lens model"283 dt_iop_ashift_crop_t cropmode; // $DEFAULT: ASHIFT_CROP_LARGEST $DESCRIPTION: "automatic cropping"284 float cl; // $DEFAULT: 0.0 $MIN: 0.0 $MAX: 1.0285 float cr; // $DEFAULT: 1.0 $MIN: 0.0 $MAX: 1.0286 float ct; // $DEFAULT: 0.0 $MIN: 0.0 $MAX: 1.0287 float cb; // $DEFAULT: 1.0 $MIN: 0.0 $MAX: 1.0288 float last_drawn_lines[MAX_SAVED_LINES * 4];289 int last_drawn_lines_count;290 float last_quad_lines[8];291} dt_iop_ashift_params_t;292293typedef struct dt_iop_ashift_line_t294{295 float p1[3];296 float p2[3];297 float length;298 float width;299 float weight;300 dt_iop_ashift_linetype_t type;301 // homogeneous coordinates:302 float DT_ALIGNED_PIXEL L[3];303} dt_iop_ashift_line_t;304305typedef struct dt_iop_ashift_points_idx_t306{307 size_t offset;308 int length;309 int near;310 int bounded;311 dt_iop_ashift_linetype_t type;312 dt_iop_ashift_linecolor_t color;313 // bounding box:314 float bbx, bby, bbX, bbY;315} dt_iop_ashift_points_idx_t;316317typedef struct dt_iop_ashift_fit_params_t318{319 int params_count;320 dt_iop_ashift_linetype_t linetype;321 dt_iop_ashift_linetype_t linemask;322 dt_iop_ashift_line_t *lines;323 int lines_count;324 int width;325 int height;326 float weight;327 float f_length_kb;328 float orthocorr;329 float aspect;330 float rotation;331 float lensshift_v;332 float lensshift_h;333 float shear;334 float rotation_range;335 float lensshift_v_range;336 float lensshift_h_range;337 float shear_range;338} dt_iop_ashift_fit_params_t;339340typedef struct dt_iop_ashift_cropfit_params_t341{342 int width;343 int height;344 float x;345 float y;346 float alpha;347 float DT_ALIGNED_ARRAY homograph[3][3];348 float DT_ALIGNED_ARRAY edges[4][3];349} dt_iop_ashift_cropfit_params_t;350351typedef struct dt_iop_ashift_gui_data_t352{353 GtkWidget *rotation;354 GtkWidget *lensshift_v;355 GtkWidget *lensshift_h;356 GtkWidget *shear;357 GtkWidget *cropmode;358 GtkWidget *mode;359 GtkWidget *specifics;360 GtkWidget *f_length;361 GtkWidget *crop_factor;362 GtkWidget *orthocorr;363 GtkWidget *aspect;364 GtkWidget *fit_v;365 GtkWidget *fit_h;366 GtkWidget *fit_both;367 GtkWidget *structure_auto;368 GtkWidget *structure_quad;369 GtkWidget *structure_lines;370 gboolean straightening;371 float straighten_x;372 float straighten_y;373 int fitting;374 int isflipped;375 int isselecting;376 int isdeselecting;377 dt_iop_ashift_bounding_t isbounding;378 float near_delta;379 int selecting_lines_version;380 float rotation_range;381 float lensshift_v_range;382 float lensshift_h_range;383 float shear_range;384 dt_iop_ashift_line_t *lines;385 int lines_in_width;386 int lines_in_height;387 int lines_x_off;388 int lines_y_off;389 int lines_count;390 int vertical_count;391 int horizontal_count;392 int lines_version;393 float vertical_weight;394 float horizontal_weight;395 float *points;396 dt_iop_ashift_points_idx_t *points_idx;397 int points_lines_count;398 int points_version;399 float *buf;400 int buf_width;401 int buf_height;402 int buf_x_off;403 int buf_y_off;404 float buf_scale;405 dt_hash_t lines_hash;406 dt_hash_t grid_hash;407 dt_hash_t buf_hash;408 dt_iop_ashift_fitaxis_t lastfit;409 float lastx;410 float lasty;411 float crop_cx;412 float crop_cy;413 dt_iop_ashift_jobcode_t jobcode;414 int jobparams;415 gboolean adjust_crop;416 float cl; // shadow copy of dt_iop_ashift_data_t.cl417 float cr; // shadow copy of dt_iop_ashift_data_t.cr418 float ct; // shadow copy of dt_iop_ashift_data_t.ct419 float cb; // shadow copy of dt_iop_ashift_data_t.cb420421 dt_iop_ashift_method_t current_structure_method;422 int draw_near_point;423 gboolean draw_point_move;424 int draw_line_move;425 float draw_pointmove_x;426 float draw_pointmove_y;427 float *draw_points;428 dt_gui_collapsible_section_t cs;429} dt_iop_ashift_gui_data_t;430431typedef struct dt_iop_ashift_data_t432{433 float rotation;434 float lensshift_v;435 float lensshift_h;436 float shear;437 float f_length_kb;438 float orthocorr;439 float aspect;440 float cl;441 float cr;442 float ct;443 float cb;444} dt_iop_ashift_data_t;445446typedef struct dt_iop_ashift_global_data_t447{448 int kernel_ashift_bilinear;449 int kernel_ashift_bicubic;450 int kernel_ashift_lanczos2;451 int kernel_ashift_lanczos3;452} dt_iop_ashift_global_data_t;453454int legacy_params(dt_iop_module_t *self,455 const void *const old_params,456 const int old_version,457 void **new_params,458 int32_t *new_params_size,459 int *new_version)460{461 typedef struct dt_iop_ashift_params_v5_t462 {463 float rotation;464 float lensshift_v;465 float lensshift_h;466 float shear;467 float f_length;468 float crop_factor;469 float orthocorr;470 float aspect;471 dt_iop_ashift_mode_t mode;472 dt_iop_ashift_crop_t cropmode;473 float cl;474 float cr;475 float ct;476 float cb;477 float last_drawn_lines[MAX_SAVED_LINES * 4];478 int last_drawn_lines_count;479 float last_quad_lines[8];480 } dt_iop_ashift_params_v5_t;481482 if(old_version == 1)483 {484 typedef struct dt_iop_ashift_params_v1_t485 {486 float rotation;487 float lensshift_v;488 float lensshift_h;489 int toggle;490 } dt_iop_ashift_params_v1_t;491492 const dt_iop_ashift_params_v1_t *o = old_params;493 dt_iop_ashift_params_v5_t *n = malloc(sizeof(dt_iop_ashift_params_v5_t));494 n->rotation = o->rotation;495 n->lensshift_v = o->lensshift_v;496 n->lensshift_h = o->lensshift_h;497 n->shear = 0.0f;498 n->f_length = DEFAULT_F_LENGTH;499 n->crop_factor = 1.0f;500 n->orthocorr = 100.0f;501 n->aspect = 1.0f;502 n->mode = ASHIFT_MODE_GENERIC;503 n->cropmode = ASHIFT_CROP_OFF;504 n->cl = 0.0f;505 n->cr = 1.0f;506 n->ct = 0.0f;507 n->cb = 1.0f;508 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) n->last_drawn_lines[i] = 0.0f;509 for(int i = 0; i < 8; i++) n->last_quad_lines[i] = 0.0f;510 n->last_drawn_lines_count = 0;511512 *new_params = n;513 *new_params_size = sizeof(dt_iop_ashift_params_v5_t);514 *new_version = 5;515 return 0;516 }517 if(old_version == 2)518 {519 typedef struct dt_iop_ashift_params_v2_t520 {521 float rotation;522 float lensshift_v;523 float lensshift_h;524 float f_length;525 float crop_factor;526 float orthocorr;527 float aspect;528 dt_iop_ashift_mode_t mode;529 int toggle;530 } dt_iop_ashift_params_v2_t;531532 const dt_iop_ashift_params_v2_t *o = old_params;533 dt_iop_ashift_params_v5_t *n = malloc(sizeof(dt_iop_ashift_params_v5_t));534 n->rotation = o->rotation;535 n->lensshift_v = o->lensshift_v;536 n->lensshift_h = o->lensshift_h;537 n->shear = 0.0f;538 n->f_length = o->f_length;539 n->crop_factor = o->crop_factor;540 n->orthocorr = o->orthocorr;541 n->aspect = o->aspect;542 n->mode = o->mode;543 n->cropmode = ASHIFT_CROP_OFF;544 n->cl = 0.0f;545 n->cr = 1.0f;546 n->ct = 0.0f;547 n->cb = 1.0f;548 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) n->last_drawn_lines[i] = 0.0f;549 for(int i = 0; i < 8; i++) n->last_quad_lines[i] = 0.0f;550 n->last_drawn_lines_count = 0;551552 *new_params = n;553 *new_params_size = sizeof(dt_iop_ashift_params_v5_t);554 *new_version = 5;555 return 0;556 }557 if(old_version == 3)558 {559 typedef struct dt_iop_ashift_params_v3_t560 {561 float rotation;562 float lensshift_v;563 float lensshift_h;564 float f_length;565 float crop_factor;566 float orthocorr;567 float aspect;568 dt_iop_ashift_mode_t mode;569 int toggle;570 dt_iop_ashift_crop_t cropmode;571 float cl;572 float cr;573 float ct;574 float cb;575 } dt_iop_ashift_params_v3_t;576577 const dt_iop_ashift_params_v3_t *o = old_params;578 dt_iop_ashift_params_v5_t *n = malloc(sizeof(dt_iop_ashift_params_v5_t));579 n->rotation = o->rotation;580 n->lensshift_v = o->lensshift_v;581 n->lensshift_h = o->lensshift_h;582 n->shear = 0.0f;583 n->f_length = o->f_length;584 n->crop_factor = o->crop_factor;585 n->orthocorr = o->orthocorr;586 n->aspect = o->aspect;587 n->mode = o->mode;588 n->cropmode = o->cropmode;589 n->cl = o->cl;590 n->cr = o->cr;591 n->ct = o->ct;592 n->cb = o->cb;593 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) n->last_drawn_lines[i] = 0.0f;594 for(int i = 0; i < 8; i++) n->last_quad_lines[i] = 0.0f;595 n->last_drawn_lines_count = 0;596597 *new_params = n;598 *new_params_size = sizeof(dt_iop_ashift_params_v5_t);599 *new_version = 5;600 return 0;601 }602 if(old_version == 4)603 {604 typedef struct dt_iop_ashift_params_v4_t605 {606 float rotation;607 float lensshift_v;608 float lensshift_h;609 float shear;610 float f_length;611 float crop_factor;612 float orthocorr;613 float aspect;614 dt_iop_ashift_mode_t mode;615 int toggle;616 dt_iop_ashift_crop_t cropmode;617 float cl;618 float cr;619 float ct;620 float cb;621 } dt_iop_ashift_params_v4_t;622623 const dt_iop_ashift_params_v4_t *o = old_params;624 dt_iop_ashift_params_v5_t *n = malloc(sizeof(dt_iop_ashift_params_v5_t));625 n->rotation = o->rotation;626 n->lensshift_v = o->lensshift_v;627 n->lensshift_h = o->lensshift_h;628 n->shear = o->shear;629 n->f_length = o->f_length;630 n->crop_factor = o->crop_factor;631 n->orthocorr = o->orthocorr;632 n->aspect = o->aspect;633 n->mode = o->mode;634 n->cropmode = o->cropmode;635 n->cl = o->cl;636 n->cr = o->cr;637 n->ct = o->ct;638 n->cb = o->cb;639 for(int i = 0; i < MAX_SAVED_LINES * 4; i++) n->last_drawn_lines[i] = 0.0f;640 for(int i = 0; i < 8; i++) n->last_quad_lines[i] = 0.0f;641 n->last_drawn_lines_count = 0;642643 *new_params = n;644 *new_params_size = sizeof(dt_iop_ashift_params_v5_t);645 *new_version = 5;646 return 0;647 }648649 return 1;650}651652// normalized product of two 3x1 vectors653// dst needs to be different from v1 and v2654static inline void vec3prodn(float *dst, const float *const v1, const float *const v2)655{656 const float l1 = v1[1] * v2[2] - v1[2] * v2[1];657 const float l2 = v1[2] * v2[0] - v1[0] * v2[2];658 const float l3 = v1[0] * v2[1] - v1[1] * v2[0];659660 // normalize so that l1^2 + l2^2 + l3^3 = 1661 const float sq = sqrtf(l1 * l1 + l2 * l2 + l3 * l3);662663 const float f = sq > 0.0f ? 1.0f / sq : 1.0f;664665 dst[0] = l1 * f;666 dst[1] = l2 * f;667 dst[2] = l3 * f;668}669670// normalize a 3x1 vector so that x^2 + y^2 + z^2 = 1671// dst and v may be the same672static inline void vec3norm(float *dst, const float *const v)673{674 const float sq = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);675676 // special handling for an all-zero vector677 const float f = sq > 0.0f ? 1.0f / sq : 1.0f;678679 dst[0] = v[0] * f;680 dst[1] = v[1] * f;681 dst[2] = v[2] * f;682}683684// normalize a 3x1 vector so that x^2 + y^2 = 1; a useful normalization for685// lines in homogeneous coordinates686// dst and v may be the same687static inline void vec3lnorm(float *dst, const float *const v)688{689 const float sq = dt_fast_hypotf(v[0], v[1]);690691 // special handling for a point vector of the image center692 const float f = sq > 0.0f ? 1.0f / sq : 1.0f;693694 dst[0] = v[0] * f;695 dst[1] = v[1] * f;696 dst[2] = v[2] * f;697}698699700// scalar product of two 3x1 vectors701static inline float vec3scalar(const float *const v1, const float *const v2)702{703 return (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]);704}705706// check if 3x1 vector is (very close to) null707static inline int vec3isnull(const float *const v)708{709 const float eps = 1e-10f;710 return (fabsf(v[0]) < eps && fabsf(v[1]) < eps && fabsf(v[2]) < eps);711}712713static inline void _shadow_crop_box(const dt_iop_ashift_params_t *p,714 dt_iop_ashift_gui_data_t *g)715{716 // copy actual crop box values into shadow variables717 g->cl = p->cl;718 g->cr = p->cr;719 g->ct = p->ct;720 g->cb = p->cb;721}722723static void _clear_shadow_crop_box(dt_iop_ashift_gui_data_t *g)724{725 // reset the crop to the full image726 g->cl = 0.0f;727 g->cr = 1.0f;728 g->ct = 0.0f;729 g->cb = 1.0f;730}731732static inline void _commit_crop_box(dt_iop_ashift_params_t *p,733 const dt_iop_ashift_gui_data_t *g)734{735 // copy shadow values for crop box into actual parameters736 p->cl = g->cl;737 p->cr = g->cr;738 p->ct = g->ct;739 p->cb = g->cb;740}741742static inline void _swap_shadow_crop_box(dt_iop_ashift_params_t *p,743 dt_iop_ashift_gui_data_t *g)744{745 // exchange shadow values and actual crop values this is needed for746 // a temporary commit to be able to properly update the undo history747 float tmp;748 tmp = p->cl; p->cl = g->cl; g->cl = tmp;749 tmp = p->cr; p->cr = g->cr; g->cr = tmp;750 tmp = p->ct; p->ct = g->ct; g->ct = tmp;751 tmp = p->cb; p->cb = g->cb; g->cb = tmp;752}753754755756static void _homography(float *homograph,757 const float angle,758 const float shift_v,759 const float shift_h,760 const float shear,761 const float f_length_kb,762 const float orthocorr,763 const float aspect,764 const int width,765 const int height,766 const dt_iop_ashift_homodir_t dir)767{768 // calculate homograph that combines all translations, rotations769 // and warping into one single matrix operation.770 // this is heavily leaning on ShiftN where the homographic matrix expects771 // input in (y : x : 1) format. in the darktable world we want to keep the772 // (x : y : 1) convention. therefore we need to flip coordinates first and773 // make sure that output is in correct format after corrections are applied.774775 const float u = width;776 const float v = height;777778 const float phi = deg2radf(angle);779 const float cosi = cosf(phi);780 const float sini = sinf(phi);781 const float ascale = sqrtf(aspect);782783 // most of this comes from ShiftN784 const float f_global = f_length_kb;785 const float horifac = 1.0f - orthocorr / 100.0f;786 const float exppa_v = expf(shift_v);787 const float fdb_v = f_global / (14.4f + (v / u - 1) * 7.2f);788 const float rad_v = fdb_v * (exppa_v - 1.0f) / (exppa_v + 1.0f);789 const float alpha_v = CLAMP(atanf(rad_v), -1.5f, 1.5f);790 const float rt_v = sinf(0.5f * alpha_v);791 const float r_v = fmaxf(0.1f, 2.0f * (horifac - 1.0f) * rt_v * rt_v + 1.0f);792793 const float vertifac = 1.0f - orthocorr / 100.0f;794 const float exppa_h = expf(shift_h);795 const float fdb_h = f_global / (14.4f + (u / v - 1) * 7.2f);796 const float rad_h = fdb_h * (exppa_h - 1.0f) / (exppa_h + 1.0f);797 const float alpha_h = CLAMP(atanf(rad_h), -1.5f, 1.5f);798 const float rt_h = sinf(0.5f * alpha_h);799 const float r_h = fmaxf(0.1f, 2.0f * (vertifac - 1.0f) * rt_h * rt_h + 1.0f);800801802 // three intermediate buffers for matrix calculation ...803 float DT_ALIGNED_ARRAY m1[3][3];804 float DT_ALIGNED_ARRAY m2[3][3];805 float DT_ALIGNED_ARRAY m3[3][3];806807 // ... and some pointers to handle them more intuitively808 float (*mwork)[3] = m1;809 float (*minput)[3] = m2;810 float (*moutput)[3] = m3;811812 // Step 1: flip x and y coordinates (see above)813 memset(minput, 0, sizeof(float) * 9);814 minput[0][1] = 1.0f;815 minput[1][0] = 1.0f;816 minput[2][2] = 1.0f;817818819 // Step 2: rotation of image around its center820 memset(mwork, 0, sizeof(float) * 9);821 mwork[0][0] = cosi;822 mwork[0][1] = -sini;823 mwork[1][0] = sini;824 mwork[1][1] = cosi;825 mwork[0][2] = -0.5f * v * cosi + 0.5f * u * sini + 0.5f * v;826 mwork[1][2] = -0.5f * v * sini - 0.5f * u * cosi + 0.5f * u;827 mwork[2][2] = 1.0f;828829 // multiply mwork * minput -> moutput830 mat3mul((float *)moutput, (float *)mwork, (float *)minput);831832833 // Step 3: apply shearing834 memset(mwork, 0, sizeof(float) * 9);835 mwork[0][0] = 1.0f;836 mwork[0][1] = shear;837 mwork[1][1] = 1.0f;838 mwork[1][0] = shear;839 mwork[2][2] = 1.0f;840841 // moutput (of last calculation) -> minput842 MAT3SWAP(minput, moutput);843 // multiply mwork * minput -> moutput844 mat3mul((float *)moutput, (float *)mwork, (float *)minput);845846847 // Step 4: apply vertical lens shift effect848 memset(mwork, 0, sizeof(float) * 9);849 mwork[0][0] = exppa_v;850 mwork[1][0] = 0.5f * ((exppa_v - 1.0f) * u) / v;851 mwork[1][1] = 2.0f * exppa_v / (exppa_v + 1.0f);852 mwork[1][2] = -0.5f * ((exppa_v - 1.0f) * u) / (exppa_v + 1.0f);853 mwork[2][0] = (exppa_v - 1.0f) / v;854 mwork[2][2] = 1.0f;855856 // moutput (of last calculation) -> minput857 MAT3SWAP(minput, moutput);858 // multiply mwork * minput -> moutput859 mat3mul((float *)moutput, (float *)mwork, (float *)minput);860861862 // Step 5: horizontal compression863 memset(mwork, 0, sizeof(float) * 9);864 mwork[0][0] = 1.0f;865 mwork[1][1] = r_v;866 mwork[1][2] = 0.5f * u * (1.0f - r_v);867 mwork[2][2] = 1.0f;868869 // moutput (of last calculation) -> minput870 MAT3SWAP(minput, moutput);871 // multiply mwork * minput -> moutput872 mat3mul((float *)moutput, (float *)mwork, (float *)minput);873874875 // Step 6: flip x and y back again876 memset(mwork, 0, sizeof(float) * 9);877 mwork[0][1] = 1.0f;878 mwork[1][0] = 1.0f;879 mwork[2][2] = 1.0f;880881 // moutput (of last calculation) -> minput882 MAT3SWAP(minput, moutput);883 // multiply mwork * minput -> moutput884 mat3mul((float *)moutput, (float *)mwork, (float *)minput);885886887 // from here output vectors would be in (x : y : 1) format888889 // Step 7: now we can apply horizontal lens shift with the same matrix format as above890 memset(mwork, 0, sizeof(float) * 9);891 mwork[0][0] = exppa_h;892 mwork[1][0] = 0.5f * ((exppa_h - 1.0f) * v) / u;893 mwork[1][1] = 2.0f * exppa_h / (exppa_h + 1.0f);894 mwork[1][2] = -0.5f * ((exppa_h - 1.0f) * v) / (exppa_h + 1.0f);895 mwork[2][0] = (exppa_h - 1.0f) / u;896 mwork[2][2] = 1.0f;897898 // moutput (of last calculation) -> minput899 MAT3SWAP(minput, moutput);900 // multiply mwork * minput -> moutput901 mat3mul((float *)moutput, (float *)mwork, (float *)minput);902903904 // Step 8: vertical compression905 memset(mwork, 0, sizeof(float) * 9);906 mwork[0][0] = 1.0f;907 mwork[1][1] = r_h;908 mwork[1][2] = 0.5f * v * (1.0f - r_h);909 mwork[2][2] = 1.0f;910911 // moutput (of last calculation) -> minput912 MAT3SWAP(minput, moutput);913 // multiply mwork * minput -> moutput914 mat3mul((float *)moutput, (float *)mwork, (float *)minput);915916917 // Step 9: apply aspect ratio scaling918 memset(mwork, 0, sizeof(float) * 9);919 mwork[0][0] = 1.0f * ascale;920 mwork[1][1] = 1.0f / ascale;921 mwork[2][2] = 1.0f;922923 // moutput (of last calculation) -> minput924 MAT3SWAP(minput, moutput);925 // multiply mwork * minput -> moutput926 mat3mul((float *)moutput, (float *)mwork, (float *)minput);927928929 // Step 10: find x/y offsets and apply according correction so that930 // no negative coordinates occur in output vector931 float umin = FLT_MAX, vmin = FLT_MAX;932 // visit all four corners933 for(int y = 0; y < height; y += height - 1)934 for(int x = 0; x < width; x += width - 1)935 {936 float pi[3], po[3];937 pi[0] = x;938 pi[1] = y;939 pi[2] = 1.0f;940 // moutput expects input in (x:y:1) format and gives output as (x:y:1)941 mat3mulv(po, (float *)moutput, pi);942 umin = MIN(umin, po[0] / po[2]);943 vmin = MIN(vmin, po[1] / po[2]);944 }945946 memset(mwork, 0, sizeof(float) * 9);947 mwork[0][0] = 1.0f;948 mwork[1][1] = 1.0f;949 mwork[2][2] = 1.0f;950 mwork[0][2] = -umin;951 mwork[1][2] = -vmin;952953 // moutput (of last calculation) -> minput954 MAT3SWAP(minput, moutput);955 // multiply mwork * minput -> moutput956 mat3mul((float *)moutput, (float *)mwork, (float *)minput);957958959 // on request we either keep the final matrix for forward conversions960 // or produce an inverted matrix for backward conversions961 if(dir == ASHIFT_HOMOGRAPH_FORWARD)962 {963 // we have what we need -> copy it to the right place964 memcpy(homograph, moutput, sizeof(float) * 9);965 }966 else967 {968 // generate inverted homograph (mat3inv function defined in colorspaces.c)969 if(mat3inv((float *)homograph, (float *)moutput))970 {971 // in case of error we set to unity matrix972 memset(mwork, 0, sizeof(float) * 9);973 mwork[0][0] = 1.0f;974 mwork[1][1] = 1.0f;975 mwork[2][2] = 1.0f;976 memcpy(homograph, mwork, sizeof(float) * 9);977 }978 }979}980981982983// check if module parameters are set to all neutral values in which case the module's984// output is identical to its input985static inline gboolean _isneutral(const dt_iop_ashift_data_t *data)986{987 // values lower than this have no visible effect988 const float eps = 1.0e-4f;989990 return(feqf(data->rotation, 0.0f, eps) &&991 feqf(data->lensshift_v, 0.0f, eps) &&992 feqf(data->lensshift_h, 0.0f, eps) &&993 feqf(data->shear, 0, eps) &&994 feqf(data->aspect, 1.0f, eps) &&995 feqf(data->cl, 0.0f, eps) &&996 feqf(data->cr, 1.0f, eps) &&997 feqf(data->ct, 0.0f, eps) &&998 feqf(data->cb, 1.0f, eps));999}100010011002gboolean distort_transform(dt_iop_module_t *self,1003 dt_dev_pixelpipe_iop_t *piece,1004 float *const restrict points,1005 const size_t points_count)1006{1007 const dt_iop_ashift_data_t *const data = piece->data;10081009 // nothing to be done if parameters are set to neutral values1010 if(_isneutral(data)) return TRUE;10111012 float DT_ALIGNED_ARRAY homograph[3][3];1013 _homography((float *)homograph, data->rotation, data->lensshift_v, data->lensshift_h,1014 data->shear, data->f_length_kb,1015 data->orthocorr, data->aspect,1016 piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_FORWARD);10171018 // clipping offset1019 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);1020 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);1021 const float cx = fullwidth * data->cl;1022 const float cy = fullheight * data->ct;10231024 float *const pts = DT_IS_ALIGNED(points);10251026 DT_OMP_FOR(if(points_count > 100))1027 for(size_t i = 0; i < points_count * 2; i += 2)1028 {1029 const float DT_ALIGNED_PIXEL pi[3] = { pts[i], pts[i + 1], 1.0f };1030 float DT_ALIGNED_PIXEL po[3];1031 mat3mulv(po, (float *)homograph, pi);1032 pts[i] = po[0] / po[2] - cx;1033 pts[i + 1] = po[1] / po[2] - cy;1034 }10351036 return TRUE;1037}103810391040gboolean distort_backtransform(dt_iop_module_t *self,1041 dt_dev_pixelpipe_iop_t *piece,1042 float *points,1043 const size_t points_count)1044{1045 const dt_iop_ashift_data_t *const data = piece->data;10461047 // nothing to be done if parameters are set to neutral values1048 if(_isneutral(data)) return TRUE;10491050 float DT_ALIGNED_ARRAY ihomograph[3][3];1051 _homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h,1052 data->shear, data->f_length_kb,1053 data->orthocorr, data->aspect,1054 piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);10551056 // clipping offset1057 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);1058 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);1059 const float cx = fullwidth * data->cl;1060 const float cy = fullheight * data->ct;10611062 float *const pts = DT_IS_ALIGNED(points);10631064 DT_OMP_FOR(if(points_count > 100))1065 for(size_t i = 0; i < points_count * 2; i += 2)1066 {1067 const float DT_ALIGNED_PIXEL pi[3] = { pts[i] + cx, pts[i + 1] + cy, 1.0f };1068 float DT_ALIGNED_PIXEL po[3];1069 mat3mulv(po, (float *)ihomograph, pi);1070 pts[i] = po[0] / po[2];1071 pts[i + 1] = po[1] / po[2];1072 }10731074 return TRUE;1075}10761077void distort_mask(dt_iop_module_t *self,1078 dt_dev_pixelpipe_iop_t *piece,1079 const float *const in,1080 float *const out,1081 const dt_iop_roi_t *const roi_in,1082 const dt_iop_roi_t *const roi_out)1083{1084 const dt_iop_ashift_data_t *const data = piece->data;10851086 // if module is set to neutral parameters we just copy input->output and are done1087 if(_isneutral(data))1088 {1089 dt_iop_image_copy_by_size(out, in, roi_out->width, roi_out->height, 1);1090 return;1091 }10921093 const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP);10941095 float DT_ALIGNED_ARRAY ihomograph[3][3];1096 _homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h,1097 data->shear, data->f_length_kb,1098 data->orthocorr, data->aspect,1099 piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);11001101 // clipping offset1102 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);1103 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);1104 const float cx = roi_out->scale * fullwidth * data->cl;1105 const float cy = roi_out->scale * fullheight * data->ct;11061107 DT_OMP_FOR(shared(ihomograph))1108 // go over all pixels of output image1109 for(int j = 0; j < roi_out->height; j++)1110 {1111 float *const restrict _out = out + (size_t)j * roi_out->width;1112 for(int i = 0; i < roi_out->width; i++)1113 {1114 float DT_ALIGNED_PIXEL pin[3], DT_ALIGNED_PIXEL pout[3];11151116 // convert output pixel coordinates to original image coordinates1117 pout[0] = roi_out->x + i + cx;1118 pout[1] = roi_out->y + j + cy;1119 pout[0] /= roi_out->scale;1120 pout[1] /= roi_out->scale;1121 pout[2] = 1.0f;11221123 // apply homograph1124 mat3mulv(pin, (float *)ihomograph, pout);11251126 // convert to input pixel coordinates1127 pin[0] /= pin[2];1128 pin[1] /= pin[2];1129 pin[0] *= roi_in->scale;1130 pin[1] *= roi_in->scale;1131 pin[0] -= roi_in->x;1132 pin[1] -= roi_in->y;11331134 // get output values by interpolation from input image1135 _out[i] = CLIP(dt_interpolation_compute_sample(interpolation, in,1136 pin[0], pin[1],1137 roi_in->width, roi_in->height, 1, roi_in->width));1138 }1139 }1140}11411142void modify_roi_out(dt_iop_module_t *self,1143 dt_dev_pixelpipe_iop_t *piece,1144 dt_iop_roi_t *roi_out,1145 const dt_iop_roi_t *roi_in)1146{1147 const dt_iop_ashift_data_t *data = piece->data;1148 *roi_out = *roi_in;11491150 // nothing more to be done if parameters are set to neutral values1151 if(_isneutral(data)) return;11521153 float DT_ALIGNED_ARRAY homograph[3][3];1154 _homography((float *)homograph, data->rotation, data->lensshift_v, data->lensshift_h,1155 data->shear, data->f_length_kb,1156 data->orthocorr, data->aspect,1157 piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_FORWARD);11581159 float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX;11601161 // go through all four vertices of input roi and convert coordinates to output1162 for(int y = 0; y < roi_in->height; y += roi_in->height - 1)1163 {1164 for(int x = 0; x < roi_in->width; x += roi_in->width - 1)1165 {1166 float DT_ALIGNED_PIXEL pin[3], DT_ALIGNED_PIXEL pout[3];11671168 // convert from input coordinates to original image coordinates1169 pin[0] = roi_in->x + x;1170 pin[1] = roi_in->y + y;1171 pin[0] /= roi_in->scale;1172 pin[1] /= roi_in->scale;1173 pin[2] = 1.0f;11741175 // apply homograph1176 mat3mulv(pout, (float *)homograph, pin);11771178 // convert to output image coordinates1179 pout[0] /= pout[2];1180 pout[1] /= pout[2];1181 pout[0] *= roi_out->scale;1182 pout[1] *= roi_out->scale;1183 xm = MIN(xm, pout[0]);1184 xM = MAX(xM, pout[0]);1185 ym = MIN(ym, pout[1]);1186 yM = MAX(yM, pout[1]);1187 }1188 }11891190 const float width = (xM - xm + 1.0f) * (data->cr - data->cl);1191 const float height = (yM - ym + 1.0f) * (data->cb - data->ct);11921193 roi_out->width = floorf(width);1194 roi_out->height = floorf(height);11951196 if(roi_out->width < 4 || roi_out->height < 4)1197 {1198 dt_print_pipe(DT_DEBUG_PIPE,1199 "insane data", piece->pipe, self, DT_DEVICE_NONE, roi_in, roi_out);1200 if(dt_pipe_is_full(piece->pipe))1201 {1202 dt_control_log1203 (_("module '%s' has insane data so it is bypassed for now."1204 " you should disable it or change parameters\n"),1205 self->name());1206 }1207 roi_out->width = roi_in->width;1208 roi_out->height = roi_in->height;1209 piece->enabled = FALSE;1210 }1211}12121213void modify_roi_in(dt_iop_module_t *self,1214 dt_dev_pixelpipe_iop_t *piece,1215 const dt_iop_roi_t *const roi_out,1216 dt_iop_roi_t *roi_in)1217{1218 const dt_iop_ashift_data_t *data = piece->data;1219 *roi_in = *roi_out;12201221 // nothing more to be done if parameters are set to neutral values1222 if(_isneutral(data)) return;12231224 float DT_ALIGNED_ARRAY ihomograph[3][3];1225 _homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h,1226 data->shear, data->f_length_kb,1227 data->orthocorr, data->aspect,1228 piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);12291230 const float orig_w = roi_in->scale * piece->buf_in.width;1231 const float orig_h = roi_in->scale * piece->buf_in.height;12321233 // clipping offset1234 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);1235 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);1236 const float cx = roi_out->scale * fullwidth * data->cl;1237 const float cy = roi_out->scale * fullheight * data->ct;12381239 float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX;12401241 // go through all four vertices of output roi and convert coordinates to input1242 for(int y = 0; y < roi_out->height; y += roi_out->height - 1)1243 {1244 for(int x = 0; x < roi_out->width; x += roi_out->width - 1)1245 {1246 float DT_ALIGNED_PIXEL pin[3], DT_ALIGNED_PIXEL pout[3];12471248 // convert from output image coordinates to original image coordinates1249 pout[0] = roi_out->x + x + cx;1250 pout[1] = roi_out->y + y + cy;1251 pout[0] /= roi_out->scale;1252 pout[1] /= roi_out->scale;1253 pout[2] = 1.0f;12541255 // apply homograph1256 mat3mulv(pin, (float *)ihomograph, pout);12571258 // convert to input image coordinates1259 pin[0] /= pin[2];1260 pin[1] /= pin[2];1261 pin[0] *= roi_in->scale;1262 pin[1] *= roi_in->scale;1263 xm = MIN(xm, pin[0]);1264 xM = MAX(xM, pin[0]);1265 ym = MIN(ym, pin[1]);1266 yM = MAX(yM, pin[1]);1267 }1268 }12691270 const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP);12711272 const float iw1 = interpolation->width;1273 const float iw2 = 2.0f * iw1;1274 roi_in->x = xm - iw1;1275 roi_in->y = ym - iw1;1276 roi_in->width = xM + iw2 - xm + 1.0f;1277 roi_in->height = yM + iw2 - ym + 1.0f;127812791280 // sanity check.1281 roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(orig_w));1282 roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(orig_h));1283 roi_in->width = CLAMP(roi_in->width, 4, (int)floorf(orig_w) - roi_in->x);1284 roi_in->height = CLAMP(roi_in->height, 4, (int)floorf(orig_h) - roi_in->y);1285}12861287// simple conversion of rgb image into greyscale variant suitable for1288// line segment detection the lsd routines expect input as *double,1289// roughly in the range [0.0; 256.0]1290static void rgb2grey256(const float *const in,1291 double *const out,1292 const int width,1293 const int height)1294{1295 const size_t npixels = (size_t)width * height;12961297 DT_OMP_FOR()1298 for(size_t index = 0; index < npixels; index++)1299 {1300 out[index] = (0.3f * in[4*index+0]1301 + 0.59f * in[4*index+1]1302 + 0.11f * in[4*index+2]) * 256.0;1303 }1304}13051306// sobel edge enhancement in one direction1307static void edge_enhance_1d(const double *in, double *out,1308 const int width,1309 const int height,1310 const dt_iop_ashift_enhance_t dir)1311{1312 // Sobel kernels for both directions1313 const double hkernel[3][3] = { { 1.0, 0.0, -1.0 },1314 { 2.0, 0.0, -2.0 },1315 { 1.0, 0.0, -1.0 } };13161317 const double vkernel[3][3] = { { 1.0, 2.0, 1.0 },1318 { 0.0, 0.0, 0.0 },1319 { -1.0, -2.0, -1.0 } };1320 const int kwidth = 3;1321 const int khwidth = kwidth / 2;13221323 // select kernel1324 const double *kernel = (dir == ASHIFT_ENHANCE_HORIZONTAL)1325 ? (const double *)hkernel1326 : (const double *)vkernel;13271328 DT_OMP_FOR()1329 // loop over image pixels and perform sobel convolution1330 for(int j = khwidth; j < height - khwidth; j++)1331 {1332 const double *inp = in + (size_t)j * width + khwidth;1333 double *outp = out + (size_t)j * width + khwidth;1334 for(int i = khwidth; i < width - khwidth; i++, inp++, outp++)1335 {1336 double sum = 0.0f;1337 for(int jj = 0; jj < kwidth; jj++)1338 {1339 const int k = jj * kwidth;1340 const int l = (jj - khwidth) * width;1341 for(int ii = 0; ii < kwidth; ii++)1342 {1343 sum += inp[l + ii - khwidth] * kernel[k + ii];1344 }1345 }1346 *outp = sum;1347 }1348 }13491350 DT_OMP_FOR()1351 // border fill in output buffer, so we don't get pseudo lines at image frame1352 for(int j = 0; j < height; j++)1353 for(int i = 0; i < width; i++)1354 {1355 double val = out[(size_t)j * width + i];13561357 if(j < khwidth)1358 val = out[(size_t)(khwidth - j) * width + i];1359 else if(j >= height - khwidth)1360 val = out[(size_t)(j - khwidth) * width + i];1361 else if(i < khwidth)1362 val = out[(size_t)j * width + (khwidth - i)];1363 else if(i >= width - khwidth)1364 val = out[(size_t)j * width + (i - khwidth)];13651366 out[(size_t)j * width + i] = val;13671368 // jump over center of image1369 if(i == khwidth && j >= khwidth && j < height - khwidth) i = width - khwidth;1370 }1371}13721373// edge enhancement in both directions1374static gboolean edge_enhance(const double *in,1375 double *out,1376 const int width,1377 const int height)1378{1379 double *Gx = NULL;1380 double *Gy = NULL;13811382 Gx = malloc(sizeof(double) * width * height);1383 if(Gx == NULL) goto error;13841385 Gy = malloc(sizeof(double) * width * height);1386 if(Gy == NULL) goto error;13871388 // perform edge enhancement in both directions1389 edge_enhance_1d(in, Gx, width, height, ASHIFT_ENHANCE_HORIZONTAL);1390 edge_enhance_1d(in, Gy, width, height, ASHIFT_ENHANCE_VERTICAL);13911392// calculate absolute values1393 DT_OMP_FOR()1394 for(size_t k = 0; k < (size_t)width * height; k++)1395 {1396 out[k] = sqrt(Gx[k] * Gx[k] + Gy[k] * Gy[k]);1397 }13981399 free(Gx);1400 free(Gy);1401 return TRUE;14021403error:1404 if(Gx) free(Gx);1405 if(Gy) free(Gy);1406 return FALSE;1407}14081409// detail enhancement via bilateral grid (function arguments in and1410// out may represent identical buffers)1411static gboolean detail_enhance(const float *const in,1412 float *const out,1413 const int width,1414 const int height)1415{1416 const float sigma_r = 5.0f;1417 const float sigma_s = fminf(width, height) * 0.02f;1418 const float detail = 10.0f;1419 const size_t npixels = (size_t)width * height;1420 gboolean success = TRUE;14211422 // we need to convert from RGB to Lab first;1423 // as colors don't matter we are safe to assume data to be sRGB14241425 // convert RGB input to Lab, use output buffer for intermediate storage1426 DT_OMP_FOR()1427 for(size_t index = 0; index < 4*npixels; index += 4)1428 {1429 dt_aligned_pixel_t XYZ;1430 dt_Rec709_to_XYZ_D50(in + index, XYZ); // convert linear sRBG to XYZ1431 dt_XYZ_to_Lab(XYZ, out + index);1432 }14331434 // bilateral grid detail enhancement1435 dt_bilateral_t *b = dt_bilateral_init(width, height, sigma_s, sigma_r);14361437 if(b != NULL)1438 {1439 dt_bilateral_splat(b, out);1440 dt_bilateral_blur(b);1441 dt_bilateral_slice_to_output(b, out, out, detail);1442 dt_bilateral_free(b);1443 }1444 else1445 success = FALSE;14461447 // convert resulting Lab to RGB output1448 DT_OMP_FOR()1449 for(size_t index = 0; index < 4*npixels; index += 4)1450 {1451 dt_aligned_pixel_t XYZ;1452 dt_Lab_to_XYZ(out + index, XYZ);1453 dt_XYZ_to_linearRGB(XYZ, out + index);1454 }14551456 return success;1457}14581459// apply gamma correction to RGB buffer (function arguments in and out1460// may represent identical buffers)1461static void gamma_correct(const float *const in,1462 float *const out,1463 const int width,1464 const int height)1465{1466 const size_t npixels = (size_t)width * height;1467 DT_OMP_FOR()1468 for(size_t index = 0; index < 4 * npixels; index += 4)1469 {1470 for_three_channels(c)1471 out[index+c] = powf(in[index+c], LSD_GAMMA);1472 }1473}14741475// do actual line_detection based on LSD algorithm and return results according1476// to this module's conventions1477static gboolean line_detect(float *in,1478 const int width,1479 const int height,1480 const int x_off,1481 const int y_off,1482 const float scale,1483 dt_iop_ashift_line_t **alines,1484 int *lcount,1485 int *vcount,1486 int *hcount,1487 float *vweight,1488 float *hweight,1489 const dt_iop_ashift_enhance_t enhance,1490 const gboolean is_raw)1491{1492 double *greyscale = NULL;1493 double *lsd_lines = NULL;1494 dt_iop_ashift_line_t *ashift_lines = NULL;14951496 int vertical_count = 0;1497 int horizontal_count = 0;1498 float vertical_weight = 0.0f;1499 float horizontal_weight = 0.0f;15001501 // apply gamma correction if image is raw1502 if(is_raw)1503 {1504 gamma_correct(in, in, width, height);1505 }15061507 // if requested perform an additional detail enhancement step1508 if(enhance & ASHIFT_ENHANCE_DETAIL)1509 {1510 (void)detail_enhance(in, in, width, height);1511 }15121513 // allocate intermediate buffers1514 greyscale = malloc(sizeof(double) * width * height);1515 if(greyscale == NULL) goto error;15161517 // convert to greyscale image1518 rgb2grey256(in, greyscale, width, height);15191520 // if requested perform an additional edge enhancement step1521 if(enhance & ASHIFT_ENHANCE_EDGES)1522 {1523 (void)edge_enhance(greyscale, greyscale, width, height);1524 }15251526 // call the line segment detector LSD;1527 // LSD stores the number of found lines in lines_count.1528 // it returns structural details as vector 'double lines[7 * lines_count]'1529 int lines_count;15301531 lsd_lines = LineSegmentDetection(&lines_count, greyscale, width, height,1532 LSD_SCALE, LSD_SIGMA_SCALE, LSD_QUANT,1533 LSD_ANG_TH, LSD_LOG_EPS, LSD_DENSITY_TH,1534 LSD_N_BINS, NULL, NULL, NULL);15351536 // we count the lines that we really want to use1537 int lct = 0;1538 if(lines_count > 0)1539 {1540 // aggregate lines data into our own structures1541 ashift_lines = malloc(sizeof(dt_iop_ashift_line_t) * lines_count);15421543 if(ashift_lines == NULL) goto error;15441545 for(int n = 0; n < lines_count; n++)1546 {1547 const float x1 = lsd_lines[n * 7 + 0];1548 const float y1 = lsd_lines[n * 7 + 1];1549 const float x2 = lsd_lines[n * 7 + 2];1550 const float y2 = lsd_lines[n * 7 + 3];15511552 // check for lines running along image borders and skip them.1553 // these would likely be false-positives which could result1554 // from any kind of processing artifacts1555 if((fabsf(x1 - x2) < 1 && fmaxf(x1, x2) < 2)1556 || (fabsf(x1 - x2) < 1 && fminf(x1, x2) > width - 3)1557 || (fabsf(y1 - y2) < 1 && fmaxf(y1, y2) < 2)1558 || (fabsf(y1 - y2) < 1 && fminf(y1, y2) > height - 3))1559 continue;15601561 // line position in absolute coordinates1562 float px1 = x_off + x1;1563 float py1 = y_off + y1;1564 float px2 = x_off + x2;1565 float py2 = y_off + y2;15661567 // scale back to input buffer1568 px1 /= scale;1569 py1 /= scale;1570 px2 /= scale;1571 py2 /= scale;15721573 // store as homogeneous coordinates1574 ashift_lines[lct].p1[0] = px1;1575 ashift_lines[lct].p1[1] = py1;1576 ashift_lines[lct].p1[2] = 1.0f;1577 ashift_lines[lct].p2[0] = px2;1578 ashift_lines[lct].p2[1] = py2;1579 ashift_lines[lct].p2[2] = 1.0f;15801581 // calculate homogeneous coordinates of connecting line (defined by the two points)1582 vec3prodn(ashift_lines[lct].L, ashift_lines[lct].p1, ashift_lines[lct].p2);15831584 // normalaze line coordinates so that x^2 + y^2 = 11585 // (this will always succeed as L is a real line connecting two real points)1586 vec3lnorm(ashift_lines[lct].L, ashift_lines[lct].L);15871588 // length and width of rectangle (see LSD)1589 ashift_lines[lct].length = sqrt((px2 - px1) * (px2 - px1)1590 + (py2 - py1) * (py2 - py1));1591 ashift_lines[lct].width = lsd_lines[n * 7 + 4] / scale;15921593 // ... and weight (= length * width * angle precision)1594 const float weight = ashift_lines[lct].length1595 * ashift_lines[lct].width1596 * lsd_lines[n * 7 + 5];15971598 ashift_lines[lct].weight = weight;159916001601 const float angle = rad2degf(atan2f(py2 - py1, px2 - px1));1602 const gboolean vertical = fabsf(fabsf(angle) - 90.0f) < MAX_TANGENTIAL_DEVIATION;1603 const gboolean horizontal = fabsf(fabsf(fabsf(angle) - 90.0f) - 90.0f) < MAX_TANGENTIAL_DEVIATION;1604 const gboolean relevant = ashift_lines[lct].length > MIN_LINE_LENGTH;16051606 // register type of line1607 dt_iop_ashift_linetype_t type = ASHIFT_LINE_IRRELEVANT;16081609 if(vertical && relevant)1610 {1611 type = ASHIFT_LINE_VERTICAL_SELECTED;1612 vertical_count++;1613 vertical_weight += weight;1614 }1615 else if(horizontal && relevant)1616 {1617 type = ASHIFT_LINE_HORIZONTAL_SELECTED;1618 horizontal_count++;1619 horizontal_weight += weight;1620 }1621 ashift_lines[lct].type = type;16221623 // the next valid line1624 lct++;1625 }1626 }16271628 printf("%d lines (vertical %d, horizontal %d, not relevant %d)\n",1629 lines_count, vertical_count,1630 horizontal_count, lct - vertical_count - horizontal_count);1631 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;1632 for(int n = 0; n < lct; n++)1633 {1634 xmin = fmin(xmin, fmin(ashift_lines[n].p1[0], ashift_lines[n].p2[0]));1635 xmax = fmax(xmax, fmax(ashift_lines[n].p1[0], ashift_lines[n].p2[0]));1636 ymin = fmin(ymin, fmin(ashift_lines[n].p1[1], ashift_lines[n].p2[1]));1637 ymax = fmax(ymax, fmax(ashift_lines[n].p1[1], ashift_lines[n].p2[1]));1638 printf("x1 %.0f, y1 %.0f, x2 %.0f, y2 %.0f,"1639 " length %.0f, width %f, X %f, Y %f, Z %f, type %d, scalars %f %f\n",1640 ashift_lines[n].p1[0], ashift_lines[n].p1[1], ashift_lines[n].p2[0],1641 ashift_lines[n].p2[1],1642 ashift_lines[n].length, ashift_lines[n].width,1643 ashift_lines[n].L[0], ashift_lines[n].L[1], ashift_lines[n].L[2],1644 ashift_lines[n].type,1645 vec3scalar(ashift_lines[n].p1, ashift_lines[n].L),1646 vec3scalar(ashift_lines[n].p2, ashift_lines[n].L));1647 }1648 printf("xmin %.0f, xmax %.0f, ymin %.0f, ymax %.0f\n", xmin, xmax, ymin, ymax);164916501651 // store results in provided locations1652 *lcount = lct;1653 *vcount = vertical_count;1654 *vweight = vertical_weight;1655 *hcount = horizontal_count;1656 *hweight = horizontal_weight;1657 *alines = ashift_lines;16581659 // free intermediate buffers1660 free(lsd_lines);1661 free(greyscale);1662 return lct > 0;16631664error:1665 free(lsd_lines);1666 free(greyscale);1667 return FALSE;1668}16691670// get image from buffer, analyze for structure and save results1671static int _get_structure(dt_iop_module_t *self,1672 const dt_iop_ashift_enhance_t enhance)1673{1674 dt_iop_ashift_gui_data_t *g = self->gui_data;16751676 float *buffer = NULL;1677 int width = 0;1678 int height = 0;1679 int x_off = 0;1680 int y_off = 0;1681 float scale = 0.0f;16821683 dt_iop_gui_enter_critical_section(self);1684 // read buffer data if they are available1685 if(g->buf != NULL)1686 {1687 width = g->buf_width;1688 height = g->buf_height;1689 x_off = g->buf_x_off;1690 y_off = g->buf_y_off;1691 scale = g->buf_scale;16921693 // create a temporary buffer to hold image data1694 buffer = dt_alloc_align_float((size_t)4 * width * height);1695 if(buffer != NULL)1696 dt_iop_image_copy_by_size(buffer, g->buf, width, height, 4);1697 }1698 dt_iop_gui_leave_critical_section(self);16991700 if(buffer == NULL) goto error;17011702 // get rid of old structural data1703 g->lines_count = 0;1704 g->vertical_count = 0;1705 g->horizontal_count = 0;1706 free(g->lines);1707 g->lines = NULL;17081709 dt_iop_ashift_line_t *lines;1710 int lines_count;1711 int vertical_count;1712 int horizontal_count;1713 float vertical_weight;1714 float horizontal_weight;17151716 // get new structural data1717 if(!line_detect(buffer, width, height, x_off, y_off, scale, &lines, &lines_count,1718 &vertical_count, &horizontal_count, &vertical_weight, &horizontal_weight,1719 enhance, dt_image_is_raw(&self->dev->image_storage)))1720 goto error;17211722 // save new structural data1723 g->lines_in_width = width;1724 g->lines_in_height = height;1725 g->lines_x_off = x_off;1726 g->lines_y_off = y_off;1727 g->lines_count = lines_count;1728 g->vertical_count = vertical_count;1729 g->horizontal_count = horizontal_count;1730 g->vertical_weight = vertical_weight;1731 g->horizontal_weight = horizontal_weight;1732 g->lines_version++;1733 g->lines = lines;17341735 dt_free_align(buffer);1736 return TRUE;17371738error:1739 dt_free_align(buffer);1740 return FALSE;1741}174217431744// swap two integer values1745static inline void swap(int *a, int *b)1746{1747 const int tmp = *a;1748 *a = *b;1749 *b = tmp;1750}17511752// do complete permutations1753static gboolean quickperm(int *a, int *p, const int N, int *i)1754{1755 if(*i >= N) return FALSE;17561757 p[*i]--;1758 const int j = (*i % 2 == 1) ? p[*i] : 0;1759 swap(&a[j], &a[*i]);1760 *i = 1;1761 while(p[*i] == 0)1762 {1763 p[*i] = *i;1764 (*i)++;1765 }1766 return TRUE;1767}17681769// Fisher-Yates shuffle1770static void shuffle(int *a, const int N)1771{1772 for(int i = 0; i < N; i++)1773 {1774 const int j = i + rand() % (N - i);1775 swap(&a[j], &a[i]);1776 }1777}17781779// factorial function1780static int fact(const int n)1781{1782 return (n == 1 ? 1 : n * fact(n - 1));1783}17841785// We use a pseudo-RANSAC algorithm to elminiate ouliers from our set of lines. The1786// original RANSAC works on linear optimization problems. Our model is nonlinear. We1787// take advantage of the fact that lines interesting for our model are vantage lines1788// that meet in one vantage point for each subset of lines (vertical/horizontal).1789// Strategy: we construct a model by (random) sampling within the subset of lines and1790// calculate the vantage point. Then we check the "distance" of all other lines to the1791// vantage point. The model that gives highest number of lines combined with the highest1792// total weight and lowest overall "distance" wins.1793// Disadvantage: compared to the original RANSAC we don't get any model parameters that1794// we could use for the following NMS fit.1795//1796// Self-tuning: we optimize "epsilon", the hurdle rate to reject a1797// line as an outlier, by a number of dry runs first. The target1798// average percentage value of lines to eliminate as outliers (without1799// judging on the quality of the model) is given by1800// RANSAC_ELIMINATION_RATIO,1801//1802// note: the actual percentage of outliers removed in the final run1803// will be lower because we will finally look for the best quality1804// model with the optimized epsilon and that quality value also1805// encloses the number of good lines.18061807static void ransac(const dt_iop_ashift_line_t *lines,1808 int *index_set,1809 int *inout_set,1810 const int set_count,1811 const float total_weight,1812 const int xmin,1813 const int xmax,1814 const int ymin,1815 const int ymax)1816{1817 if(set_count < 3) return;18181819 const size_t set_size = set_count * sizeof(int);1820 int *best_set = malloc(set_size);1821 memcpy(best_set, index_set, set_size);1822 int *best_inout = calloc(1, set_size);18231824 float best_quality = 0.0f;18251826 // hurdle value epsilon for rejecting a line as an outlier will be self-tuning1827 // in a number of dry runs1828 float epsilon = powf(10.0f, -RANSAC_EPSILON);1829 float epsilon_step = RANSAC_EPSILON_STEP;1830 // some accounting variables for self-tuning1831 int lines_eliminated = 0;1832 int valid_runs = 0;18331834 // number of runs to optimize epsilon1835 const int optiruns = RANSAC_OPTIMIZATION_STEPS * RANSAC_OPTIMIZATION_DRY_RUNS;1836 // go for complete permutations on small set sizes, else for random sample consensus1837 const int riter = (set_count > RANSAC_HURDLE) ? RANSAC_RUNS : fact(set_count);18381839 // some data needed for quickperm1840 int *perm = malloc(sizeof(int) * (set_count + 1));1841 for(int n = 0; n < set_count + 1; n++) perm[n] = n;1842 int piter = 1;18431844 // inout holds good/bad qualification for each line1845 int *inout = malloc(set_size);18461847 for(int r = 0; r < optiruns + riter; r++)1848 {1849 // get random or systematic variation of index set1850 if(set_count > RANSAC_HURDLE || r < optiruns)1851 shuffle(index_set, set_count);1852 else1853 (void)quickperm(index_set, perm, set_count, &piter);18541855 // summed quality evaluation of this run1856 float quality = 0.0f;18571858 // we build a model ouf of the first two lines1859 const float *L1 = lines[index_set[0]].L;1860 const float *L2 = lines[index_set[1]].L;18611862 // get intersection point (ideally a vantage point)1863 float DT_ALIGNED_PIXEL V[3];1864 vec3prodn(V, L1, L2);18651866 // catch special cases:1867 // a) L1 and L2 are identical -> V is NULL -> no valid vantage point1868 // b) vantage point lies inside image frame (no chance to correct for this case)1869 if(vec3isnull(V) ||1870 (fabsf(V[2]) > 0.0f &&1871 V[0]/V[2] >= xmin &&1872 V[1]/V[2] >= ymin &&1873 V[0]/V[2] <= xmax &&1874 V[1]/V[2] <= ymax))1875 {1876 // no valid model1877 quality = 0.0f;1878 }1879 else1880 {1881 // valid model18821883 // normalize V so that x^2 + y^2 + z^2 = 11884 vec3norm(V, V);18851886 // the two lines constituting the model are part of the set1887 inout[0] = 1;1888 inout[1] = 1;18891890 // go through all remaining lines, check if they are within the model, and1891 // mark that fact in inout[].1892 // summarize a quality parameter for all lines within the model1893 for(int n = 2; n < set_count; n++)1894 {1895 // L is normalized so that x^2 + y^2 = 11896 const float *L3 = lines[index_set[n]].L;18971898 // we take the absolute value of the dot product of V and L as1899 // a measure of the "distance" between point and line. Note1900 // that this is not the real euclidean distance but - with the1901 // given normalization - just a pragmatically selected number1902 // that goes to zero if V lies on L and increases the more V1903 // and L are apart1904 const float d = fabsf(vec3scalar(V, L3));19051906 // depending on d we either include or exclude the point from the set1907 inout[n] = (d < epsilon) ? 1 : 0;19081909 float q;19101911 if(inout[n] == 1)1912 {1913 // a quality parameter that depends 1/3 on the number of1914 // lines within the model, 1/3 on their weight, and 1/3 on1915 // their weighted distance d to the vantage point1916 q = 0.33f / (float)set_count1917 + 0.33f * lines[index_set[n]].weight / total_weight1918 + 0.33f * (1.0f - d / epsilon)1919 * (float)set_count * lines[index_set[n]].weight / total_weight;1920 }1921 else1922 {1923 q = 0.0f;1924 lines_eliminated++;1925 }19261927 quality += q;1928 }1929 valid_runs++;1930 }19311932 if(r < optiruns)1933 {1934 // on last run of each self-tuning step1935 if((r % RANSAC_OPTIMIZATION_DRY_RUNS) == (RANSAC_OPTIMIZATION_DRY_RUNS - 1)1936 && (valid_runs > 0))1937 {19381939 printf("ransac self-tuning (run %d): epsilon %f", r, epsilon);19401941 // average ratio of lines that we eliminated with the given epsilon1942 float ratio = 100.0f * (float)lines_eliminated / ((float)set_count * valid_runs);1943 // adjust epsilon accordingly1944 if(ratio < RANSAC_ELIMINATION_RATIO)1945 epsilon = powf(10.0f, log10(epsilon) - epsilon_step);1946 else if(ratio > RANSAC_ELIMINATION_RATIO)1947 epsilon = powf(10.0f, log10(epsilon) + epsilon_step);19481949 printf(" (elimination ratio %f) -> %f\n", ratio, epsilon);19501951 // reduce step-size for next optimization round1952 epsilon_step /= 2.0f;1953 lines_eliminated = 0;1954 valid_runs = 0;1955 }1956 }1957 else1958 {1959 // in the "real" runs check against the best model found so far1960 if(quality > best_quality)1961 {1962 memcpy(best_set, index_set, set_size);1963 memcpy(best_inout, inout, set_size);1964 best_quality = quality;1965 }1966 }196719681969 // report some statistics1970 int count = 0, lastcount = 0;1971 for(int n = 0; n < set_count; n++) count += best_inout[n];1972 for(int n = 0; n < set_count; n++) lastcount += inout[n];1973 printf("ransac run %d: best qual %.6f, eps %.6f,"1974 " line count %d of %d (this run: qual %.5f, count %d (%2f%%))\n", r,1975 best_quality, epsilon, count, set_count, quality, lastcount,1976 100.0f * lastcount / (float)set_count);19771978 }19791980 // store back best set1981 memcpy(index_set, best_set, set_size);1982 memcpy(inout_set, best_inout, set_size);19831984 free(inout);1985 free(perm);1986 free(best_inout);1987 free(best_set);1988}198919901991// try to clean up structural data by eliminating outliers and thereby increasing1992// the chance of a convergent fitting1993static gboolean _remove_outliers(const dt_iop_module_t *self)1994{1995 dt_iop_ashift_gui_data_t *g = self->gui_data;19961997 const int width = g->lines_in_width;1998 const int height = g->lines_in_height;1999 const int xmin = g->lines_x_off;2000 const int ymin = g->lines_y_off;2001 const int xmax = xmin + width;2002 const int ymax = ymin + height;20032004 if(g->lines_count < 1)2005 return TRUE;2006 // holds the index set of lines we want to work on2007 int *lines_set = malloc(sizeof(int) * g->lines_count);2008 // holds the result of ransac2009 int *inout_set = malloc(sizeof(int) * g->lines_count);20102011 if(!lines_set || !inout_set)2012 goto error;2013 // some accounting variables2014 int vnb = 0, vcount = 0;2015 int hnb = 0, hcount = 0;20162017 // just to be on the safe side2018 if(g->lines == NULL) goto error;20192020 // generate index list for the vertical lines2021 for(int n = 0; n < g->lines_count; n++)2022 {2023 // is this a selected vertical line?2024 if((g->lines[n].type & ASHIFT_LINE_MASK) != ASHIFT_LINE_VERTICAL_SELECTED)2025 continue;20262027 lines_set[vnb] = n;2028 inout_set[vnb] = 0;2029 vnb++;2030 }20312032 // it only makes sense to call ransac if we have more than two lines2033 if(vnb > 2)2034 ransac(g->lines, lines_set, inout_set, vnb, g->vertical_weight,2035 xmin, xmax, ymin, ymax);20362037 // adjust line selected flag according to the ransac results2038 for(int n = 0; n < vnb; n++)2039 {2040 const int m = lines_set[n];2041 if(inout_set[n] == 1)2042 {2043 g->lines[m].type |= ASHIFT_LINE_SELECTED;2044 vcount++;2045 }2046 else2047 g->lines[m].type &= ~ASHIFT_LINE_SELECTED;2048 }2049 // update number of vertical lines2050 g->vertical_count = vcount;2051 g->lines_version++;20522053 // now generate index list for the horizontal lines2054 for(int n = 0; n < g->lines_count; n++)2055 {2056 // is this a selected horizontal line?2057 if((g->lines[n].type & ASHIFT_LINE_MASK) != ASHIFT_LINE_HORIZONTAL_SELECTED)2058 continue;20592060 lines_set[hnb] = n;2061 inout_set[hnb] = 0;2062 hnb++;2063 }20642065 // it only makes sense to call ransac if we have more than two lines2066 if(hnb > 2)2067 ransac(g->lines, lines_set, inout_set, hnb, g->horizontal_weight,2068 xmin, xmax, ymin, ymax);20692070 // adjust line selected flag according to the ransac results2071 for(int n = 0; n < hnb; n++)2072 {2073 const int m = lines_set[n];2074 if(inout_set[n] == 1)2075 {2076 g->lines[m].type |= ASHIFT_LINE_SELECTED;2077 hcount++;2078 }2079 else2080 g->lines[m].type &= ~ASHIFT_LINE_SELECTED;2081 }2082 // update number of horizontal lines2083 g->horizontal_count = hcount;2084 g->lines_version++;20852086 free(inout_set);2087 free(lines_set);20882089 return TRUE;20902091error:2092 free(inout_set);2093 free(lines_set);2094 return FALSE;2095}20962097// utility function to map a variable in [min; max] to [-INF; + INF]2098static inline double logit(const double x, const double min, const double max)2099{2100 const double eps = 1.0e-6;2101 // make sure p does not touch the borders of its definition area,2102 // not critical for data accuracy as logit() is only used on initial fit parameters2103 const double p = CLAMP((x - min) / (max - min), eps, 1.0 - eps);21042105 return (2.0 * atanh(2.0 * p - 1.0));2106}21072108// inverted function to logit()2109static inline double ilogit(const double L, const double min, const double max)2110{2111 const double p = 0.5 * (1.0 + tanh(0.5 * L));21122113 return (p * (max - min) + min);2114}21152116// helper function for simplex() return quality parameter for the given model2117// strategy:2118// * generate homography matrix out of fixed parameters and fitting parameters2119// * apply homography to all end points of affected lines2120// * generate new line out of transformed end points2121// * calculate scalar product s of line with perpendicular axis2122// * sum over weighted s^2 values21232124static double model_fitness(double *params, void *data)2125{2126 const dt_iop_ashift_fit_params_t *fit = (dt_iop_ashift_fit_params_t *)data;21272128 // just for convenience: get shorter names2129 const dt_iop_ashift_line_t *lines = fit->lines;2130 const int lines_count = fit->lines_count;2131 const int width = fit->width;2132 const int height = fit->height;2133 const float f_length_kb = fit->f_length_kb;2134 const float orthocorr = fit->orthocorr;2135 const float aspect = fit->aspect;21362137 float rotation = fit->rotation;2138 float lensshift_v = fit->lensshift_v;2139 float lensshift_h = fit->lensshift_h;2140 float shear = fit->shear;2141 const float rotation_range = fit->rotation_range;2142 const float lensshift_v_range = fit->lensshift_v_range;2143 const float lensshift_h_range = fit->lensshift_h_range;2144 const float shear_range = fit->shear_range;21452146 int pcount = 0;21472148 // fill in fit parameters from params[]. Attention: order matters!!!2149 if(dt_isnan(rotation))2150 {2151 rotation = ilogit(params[pcount], -rotation_range, rotation_range);2152 pcount++;2153 }21542155 if(dt_isnan(lensshift_v))2156 {2157 lensshift_v = ilogit(params[pcount], -lensshift_v_range, lensshift_v_range);2158 pcount++;2159 }21602161 if(dt_isnan(lensshift_h))2162 {2163 lensshift_h = ilogit(params[pcount], -lensshift_h_range, lensshift_h_range);2164 pcount++;2165 }21662167 if(dt_isnan(shear))2168 {2169 shear = ilogit(params[pcount], -shear_range, shear_range);2170 pcount++;2171 }21722173 assert(pcount == fit->params_count);21742175 // the possible reference axes2176 const float DT_ALIGNED_PIXEL Av[3] = { 1.0f, 0.0f, 0.0f };2177 const float DT_ALIGNED_PIXEL Ah[3] = { 0.0f, 1.0f, 0.0f };21782179 // generate homograph out of the parameters2180 float DT_ALIGNED_ARRAY homograph[3][3];2181 _homography((float *)homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb,2182 orthocorr, aspect, width, height, ASHIFT_HOMOGRAPH_FORWARD);21832184 // accounting variables2185 double sumsq_v = 0.0;2186 double sumsq_h = 0.0;2187 double weight_v = 0.0;2188 double weight_h = 0.0;2189 int count_v = 0;2190 int count_h = 0;2191 int count = 0;21922193 // iterate over all lines2194 for(int n = 0; n < lines_count; n++)2195 {2196 // check if this is a line which we must skip2197 if((lines[n].type & fit->linemask) != fit->linetype)2198 continue;21992200 // the direction of this line (vertical?)2201 const int isvertical = lines[n].type & ASHIFT_LINE_DIRVERT;22022203 // select the perpendicular reference axis2204 const float *A = isvertical ? Ah : Av;22052206 // apply homographic transformation to the end points2207 float DT_ALIGNED_PIXEL P1[3];2208 float DT_ALIGNED_PIXEL P2[3];2209 mat3mulv(P1, (float *)homograph, lines[n].p1);2210 mat3mulv(P2, (float *)homograph, lines[n].p2);22112212 // get line connecting the two points2213 float DT_ALIGNED_PIXEL L[3];2214 vec3prodn(L, P1, P2);22152216 // normalize L so that x^2 + y^2 = 1; makes sure that2217 // y^2 = 1 / (1 + m^2) and x^2 = m^2 / (1 + m^2) with m defining the slope of the line2218 vec3lnorm(L, L);22192220 // get scalar product of line L with orthogonal axis A -> gives 02221 // if line is perpendicular2222 const float s = vec3scalar(L, A);22232224 // sum up weighted s^2 for both directions individually2225 sumsq_v += isvertical ? s * s * lines[n].weight : 0.0;2226 weight_v += isvertical ? lines[n].weight : 0.0;2227 count_v += isvertical ? 1 : 0;2228 sumsq_h += !isvertical ? s * s * lines[n].weight : 0.0;2229 weight_h += !isvertical ? lines[n].weight : 0.0;2230 count_h += !isvertical ? 1 : 0;2231 count++;2232 }22332234 const double v = weight_v > 0.0f && count > 02235 ? sumsq_v / weight_v * (float)count_v / count2236 : 0.0;22372238 const double h = weight_h > 0.0f && count > 02239 ? sumsq_h / weight_h * (float)count_h / count2240 : 0.0;22412242 double sum = sqrt(1.0 - (1.0 - v) * (1.0 - h)) * 1.0e6;2243 //double sum = sqrt(v + h) * 1.0e6;224422452246 printf("fitness with rotation %f, lensshift_v %f,"2247 " lensshift_h %f, shear %f -> lines %d, quality %10f\n",2248 rotation, lensshift_v, lensshift_h, shear, count, sum);224922502251 return sum;2252}22532254// setup all data structures for fitting and call NM simplex2255static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *self,2256 dt_iop_ashift_params_t *p,2257 const dt_iop_ashift_fitaxis_t dir)2258{2259 const dt_iop_ashift_gui_data_t *g = self->gui_data;22602261 if(!g->lines) return NMS_NOT_ENOUGH_LINES;2262 if(dir == ASHIFT_FIT_NONE) return NMS_SUCCESS;22632264 double params[4];2265 int pcount = 0;2266 int enough_lines = TRUE;22672268 // initialize fit parameters2269 dt_iop_ashift_fit_params_t fit;2270 fit.lines = g->lines;2271 fit.lines_count = g->lines_count;2272 fit.width = g->lines_in_width;2273 fit.height = g->lines_in_height;2274 fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC)2275 ? DEFAULT_F_LENGTH2276 : p->f_length * p->crop_factor;2277 fit.orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;2278 fit.aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;2279 fit.rotation = p->rotation;2280 fit.lensshift_v = p->lensshift_v;2281 fit.lensshift_h = p->lensshift_h;2282 fit.shear = p->shear;2283 fit.rotation_range = g->rotation_range;2284 fit.lensshift_v_range = g->lensshift_v_range;2285 fit.lensshift_h_range = g->lensshift_h_range;2286 fit.shear_range = g->shear_range;2287 fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED;2288 fit.linemask = ASHIFT_LINE_MASK;2289 fit.params_count = 0;2290 fit.weight = 0.0f;22912292 // if the image is flipped and if we do not want to fit both lens shift2293 // directions or none at all, then we need to change direction2294 dt_iop_ashift_fitaxis_t mdir = dir;2295 if((mdir & ASHIFT_FIT_LENS_BOTH) != ASHIFT_FIT_LENS_BOTH &&2296 (mdir & ASHIFT_FIT_LENS_BOTH) != 0)2297 {2298 // flip all directions2299 mdir ^= g->isflipped ? ASHIFT_FIT_FLIP : 0;2300 // special case that needs to be corrected2301 mdir |= (mdir & ASHIFT_FIT_LINES_BOTH) == 0 ? ASHIFT_FIT_LINES_BOTH : 0;2302 }230323042305 // prepare fit structure and starting parameters for simplex fit.2306 // note: the sequence of parameters in params[] needs to match the2307 // respective order in dt_iop_ashift_fit_params_t. Parameters which are2308 // to be fittet are marked with NAN in the fit structure. Non-NAN2309 // parameters are assumed to be constant.2310 if(mdir & ASHIFT_FIT_ROTATION)2311 {2312 // we fit rotation2313 fit.params_count++;2314 params[pcount] = logit(fit.rotation, -fit.rotation_range, fit.rotation_range);2315 pcount++;2316 fit.rotation = NAN;2317 }23182319 if(mdir & ASHIFT_FIT_LENS_VERT)2320 {2321 // we fit vertical lens shift2322 fit.params_count++;2323 params[pcount] = logit(fit.lensshift_v, -fit.lensshift_v_range, fit.lensshift_v_range);2324 pcount++;2325 fit.lensshift_v = NAN;2326 }23272328 if(mdir & ASHIFT_FIT_LENS_HOR)2329 {2330 // we fit horizontal lens shift2331 fit.params_count++;2332 params[pcount] = logit(fit.lensshift_h, -fit.lensshift_h_range, fit.lensshift_h_range);2333 pcount++;2334 fit.lensshift_h = NAN;2335 }23362337 if(mdir & ASHIFT_FIT_SHEAR)2338 {2339 // we fit the shear parameter2340 fit.params_count++;2341 params[pcount] = logit(fit.shear, -fit.shear_range, fit.shear_range);2342 pcount++;2343 fit.shear = NAN;2344 }23452346 if(mdir & ASHIFT_FIT_LINES_VERT)2347 {2348 // we use vertical lines for fitting2349 fit.linetype |= ASHIFT_LINE_DIRVERT;2350 fit.weight += g->vertical_weight;2351 enough_lines = enough_lines && (g->vertical_count >= MINIMUM_FITLINES);2352 }23532354 if(mdir & ASHIFT_FIT_LINES_HOR)2355 {2356 // we use horizontal lines for fitting2357 fit.linetype |= 0;2358 fit.weight += g->horizontal_weight;2359 enough_lines = enough_lines && (g->horizontal_count >= MINIMUM_FITLINES);2360 }23612362 // this needs to come after ASHIFT_FIT_LINES_VERT and ASHIFT_FIT_LINES_HOR2363 if((mdir & ASHIFT_FIT_LINES_BOTH) == ASHIFT_FIT_LINES_BOTH)2364 {2365 // if we use fitting in both directions we need to2366 // adjust fit.linetype and fit.linemask to match all selected lines2367 fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED;2368 fit.linemask = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED;2369 }23702371 // error case: we do not run simplex if there are not enough lines2372 if(!enough_lines)2373 {23742375 printf("optimization not possible: insufficient number of lines\n");23762377 return NMS_NOT_ENOUGH_LINES;2378 }23792380 // start the simplex fit2381 const int iter = simplex(model_fitness, params, fit.params_count,2382 NMS_EPSILON, NMS_SCALE, NMS_ITERATIONS, NULL, (void*)&fit);23832384 // error case: the fit did not converge2385 if(iter >= NMS_ITERATIONS)2386 {23872388 printf("optimization not successful: maximum number of iterations reached (%d)\n", iter);23892390 return NMS_DID_NOT_CONVERGE;2391 }23922393 // fit was successful: now consolidate the results (order matters!!!)2394 pcount = 0;2395 fit.rotation = dt_isnan(fit.rotation)2396 ? ilogit(params[pcount++], -fit.rotation_range, fit.rotation_range)2397 : fit.rotation;23982399 fit.lensshift_v = dt_isnan(fit.lensshift_v)2400 ? ilogit(params[pcount++], -fit.lensshift_v_range, fit.lensshift_v_range)2401 : fit.lensshift_v;24022403 fit.lensshift_h = dt_isnan(fit.lensshift_h)2404 ? ilogit(params[pcount++], -fit.lensshift_h_range, fit.lensshift_h_range)2405 : fit.lensshift_h;24062407 fit.shear = dt_isnan(fit.shear)2408 ? ilogit(params[pcount++], -fit.shear_range, fit.shear_range)2409 : fit.shear;241024112412 printf("params after optimization (%d iterations): rotation %f,"2413 " lensshift_v %f, lensshift_h %f, shear %f\n",2414 iter, fit.rotation, fit.lensshift_v, fit.lensshift_h, fit.shear);241524162417 // sanity check: in case of extreme values the image gets distorted2418 // so strongly that it spans an insanely huge area. we check that2419 // case and assume values that increase the image area by more than2420 // a factor of 4 as being insane.2421 float DT_ALIGNED_ARRAY homograph[3][3];2422 _homography((float *)homograph, fit.rotation, fit.lensshift_v, fit.lensshift_h,2423 fit.shear, fit.f_length_kb,2424 fit.orthocorr, fit.aspect, fit.width, fit.height, ASHIFT_HOMOGRAPH_FORWARD);24252426 // visit all four corners and find maximum span2427 float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX;2428 for(int y = 0; y < fit.height; y += fit.height - 1)2429 for(int x = 0; x < fit.width; x += fit.width - 1)2430 {2431 float DT_ALIGNED_PIXEL pi[3], DT_ALIGNED_PIXEL po[3];2432 pi[0] = x;2433 pi[1] = y;2434 pi[2] = 1.0f;2435 mat3mulv(po, (float *)homograph, pi);2436 po[0] /= po[2];2437 po[1] /= po[2];2438 xm = MIN(xm, po[0]);2439 ym = MIN(ym, po[1]);2440 xM = MAX(xM, po[0]);2441 yM = MAX(yM, po[1]);2442 }24432444 if((xM - xm) * (yM - ym) > 4.0f * fit.width * fit.height)2445 {24462447 printf("optimization not successful: degenerate case with"2448 " area growth factor (%f) exceeding limits\n",2449 (xM - xm) * (yM - ym) / (fit.width * fit.height));24502451 return NMS_INSANE;2452 }24532454 // now write the results into structure p2455 p->rotation = fit.rotation;2456 p->lensshift_v = fit.lensshift_v;2457 p->lensshift_h = fit.lensshift_h;2458 p->shear = fit.shear;2459 return NMS_SUCCESS;2460}246124622463// only used in development phase. call model_fitness() with current parameters and2464// print some useful information2465static void model_probe(dt_iop_module_t *self,2466 dt_iop_ashift_params_t *p,2467 const dt_iop_ashift_fitaxis_t dir)2468{2469 dt_iop_ashift_gui_data_t *g = self->gui_data;24702471 if(!g->lines) return;2472 if(dir == ASHIFT_FIT_NONE) return;24732474 double params[4];2475 int enough_lines = TRUE;24762477 // initialize fit parameters2478 dt_iop_ashift_fit_params_t fit;2479 fit.lines = g->lines;2480 fit.lines_count = g->lines_count;2481 fit.width = g->lines_in_width;2482 fit.height = g->lines_in_height;2483 fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC)2484 ? DEFAULT_F_LENGTH2485 : p->f_length * p->crop_factor;2486 fit.orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;2487 fit.aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;2488 fit.rotation = p->rotation;2489 fit.lensshift_v = p->lensshift_v;2490 fit.lensshift_h = p->lensshift_h;2491 fit.shear = p->shear;2492 fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED;2493 fit.linemask = ASHIFT_LINE_MASK;2494 fit.params_count = 0;2495 fit.weight = 0.0f;24962497 // if the image is flipped and if we do not want to fit both lens shift2498 // directions or none at all, then we need to change direction2499 dt_iop_ashift_fitaxis_t mdir = dir;2500 if((mdir & ASHIFT_FIT_LENS_BOTH) != ASHIFT_FIT_LENS_BOTH &&2501 (mdir & ASHIFT_FIT_LENS_BOTH) != 0)2502 {2503 // flip all directions2504 mdir ^= g->isflipped ? ASHIFT_FIT_FLIP : 0;2505 // special case that needs to be corrected2506 mdir |= (mdir & ASHIFT_FIT_LINES_BOTH) == 0 ? ASHIFT_FIT_LINES_BOTH : 0;2507 }25082509 if(mdir & ASHIFT_FIT_LINES_VERT)2510 {2511 // we use vertical lines for fitting2512 fit.linetype |= ASHIFT_LINE_DIRVERT;2513 fit.weight += g->vertical_weight;2514 enough_lines = enough_lines && (g->vertical_count >= MINIMUM_FITLINES);2515 }25162517 if(mdir & ASHIFT_FIT_LINES_HOR)2518 {2519 // we use horizontal lines for fitting2520 fit.linetype |= 0;2521 fit.weight += g->horizontal_weight;2522 enough_lines = enough_lines && (g->horizontal_count >= MINIMUM_FITLINES);2523 }25242525 // this needs to come after ASHIFT_FIT_LINES_VERT and ASHIFT_FIT_LINES_HOR2526 if((mdir & ASHIFT_FIT_LINES_BOTH) == ASHIFT_FIT_LINES_BOTH)2527 {2528 // if we use fitting in both directions we need to2529 // adjust fit.linetype and fit.linemask to match all selected lines2530 fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED;2531 fit.linemask = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED;2532 }25332534 double quality = model_fitness(params, (void *)&fit);25352536 printf("model fitness: %.8f (rotation %f, lensshift_v %f, lensshift_h %f, shear %f)\n",2537 quality, p->rotation, p->lensshift_v, p->lensshift_h, p->shear);2538}253925402541// function to keep crop fitting parameters within constraints2542static void crop_constraint(double *params, const int pcount)2543{2544 if(pcount > 0) params[0] = fabs(params[0]);2545 if(pcount > 1) params[1] = fabs(params[1]);2546 if(pcount > 2) params[2] = fabs(params[2]);25472548 if(pcount > 0 && params[0] > 1.0) params[0] = 1.0 - params[0];2549 if(pcount > 1 && params[1] > 1.0) params[1] = 1.0 - params[1];2550 if(pcount > 2 && params[2] > 0.5*M_PI) params[2] = 0.5*M_PI - params[2];2551}25522553// helper function for getting the best fitting crop area;2554// returns the negative area of the largest rectangle that fits within the2555// defined image with a given rectangle's center and its aspect angle;2556// the trick: the rectangle center coordinates are given in the input2557// image coordinates so we know for sure that it also lies within the image after2558// conversion to the output coordinates2559static double crop_fitness(double *params, void *data)2560{2561 const dt_iop_ashift_cropfit_params_t *cropfit = (dt_iop_ashift_cropfit_params_t *)data;25622563 const float wd = cropfit->width;2564 const float ht = cropfit->height;25652566 // get variable and constant parameters, respectively2567 const float x = dt_isnan(cropfit->x) ? params[0] : cropfit->x;2568 const float y = dt_isnan(cropfit->y) ? params[1] : cropfit->y;2569 const float alpha = dt_isnan(cropfit->alpha) ? params[2] : cropfit->alpha;25702571 // the center of the rectangle in input image coordinates2572 const float DT_ALIGNED_PIXEL Pc[3] = { x * wd, y * ht, 1.0f };25732574 // convert to the output image coordinates and normalize2575 float DT_ALIGNED_PIXEL P[3];2576 mat3mulv(P, (float *)cropfit->homograph, Pc);2577 P[0] /= P[2];2578 P[1] /= P[2];2579 P[2] = 1.0f;25802581 // two auxiliary points (some arbitrary distance away from P) to construct the diagonals2582 const float DT_ALIGNED_ARRAY Pa[2][3] =2583 { { P[0] + 10.0f * cosf(alpha), P[1] + 10.0f * sinf(alpha), 1.0f },2584 { P[0] + 10.0f * cosf(alpha), P[1] - 10.0f * sinf(alpha), 1.0f } };25852586 // the two diagonals: D = P x Pa2587 float D[2][3];2588 vec3prodn(D[0], P, Pa[0]);2589 vec3prodn(D[1], P, Pa[1]);25902591 // find all intersection points of all four edges with both2592 // diagonals (I = E x D); the shortest distance d2min of the2593 // intersection point I to the crop area center P determines the2594 // size of the crop area that still fits into the image (for the2595 // given center and aspect angle)2596 float d2min = FLT_MAX;2597 for(int k = 0; k < 4; k++)2598 for(int l = 0; l < 2; l++)2599 {2600 // the intersection point2601 float I[3];2602 vec3prodn(I, cropfit->edges[k], D[l]);26032604 // special case: I is all null -> E and D are identical -> P2605 // lies on E -> d2min = 02606 if(vec3isnull(I))2607 {2608 d2min = 0.0f;2609 break;2610 }26112612 // special case: I[2] is 0.0f -> E and D are parallel and2613 // intersect at infinity -> no relevant point2614 if(I[2] == 0.0f)2615 continue;26162617 // the default case -> normalize I2618 I[0] /= I[2];2619 I[1] /= I[2];26202621 // calculate distance from I to P2622 const float d2 = sqrf(P[0] - I[0]) + sqrf(P[1] - I[1]);26232624 // the minimum distance over all intersection points2625 d2min = MIN(d2min, d2);2626 }26272628 // calculate the area of the rectangle2629 const float A = 2.0f * d2min * sinf(2.0f * alpha);263026312632 printf("crop fitness with x %f, y %f, angle %f -> distance %f, area %f\n",2633 x, y, alpha, d2min, A);26342635 // and return -A to allow Nelder-Mead simplex to search for the minimum2636 return -A;2637}26382639// strategy: for a given center of the crop area and a specific aspect2640// angle we calculate the largest crop area that still lies within the2641// output image; now we allow a Nelder-Mead simplex to search for the2642// center coordinates (and optionally the aspect angle) that delivers2643// the largest overall crop area.26442645static void do_crop(const dt_iop_module_t *self, dt_iop_ashift_params_t *p)2646{2647 dt_iop_ashift_gui_data_t *g = self->gui_data;26482649 // if sizes are not ready (module disabled), just ignore this2650 if(g->buf_width == 0 || g->buf_height == 0) return;26512652 // skip if fitting is still running2653 if(g->fitting) return;26542655 // reset fit margins if auto-cropping is off2656 if(p->cropmode == ASHIFT_CROP_OFF)2657 {2658 _clear_shadow_crop_box(g);2659 _commit_crop_box(p, g);2660 return;2661 }26622663 g->fitting = 1;26642665 double params[3];2666 int pcount;26672668 // get parameters for the homograph2669 const float f_length_kb = (p->mode == ASHIFT_MODE_GENERIC)2670 ? DEFAULT_F_LENGTH2671 : p->f_length * p->crop_factor;26722673 const float orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;2674 const float aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;2675 const float rotation = p->rotation;2676 const float lensshift_v = p->lensshift_v;2677 const float lensshift_h = p->lensshift_h;2678 const float shear = p->shear;26792680 // prepare structure of constant parameters2681 dt_iop_ashift_cropfit_params_t DT_ALIGNED_ARRAY cropfit;2682 cropfit.width = g->buf_width;2683 cropfit.height = g->buf_height;2684 _homography((float *)cropfit.homograph, rotation, lensshift_v, lensshift_h,2685 shear, f_length_kb,2686 orthocorr, aspect, cropfit.width, cropfit.height, ASHIFT_HOMOGRAPH_FORWARD);26872688 const float wd = cropfit.width;2689 const float ht = cropfit.height;26902691 // the four vertices of the image in input image coordinates2692 const float Vc[4][3] = { { 0.0f, 0.0f, 1.0f },2693 { 0.0f, ht, 1.0f },2694 { wd, ht, 1.0f },2695 { wd, 0.0f, 1.0f } };26962697 // convert the vertices to output image coordinates2698 float V[4][3];2699 for(int n = 0; n < 4; n++)2700 mat3mulv(V[n], (float *)cropfit.homograph, Vc[n]);27012702 // get width and height of output image for later use2703 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;2704 for(int n = 0; n < 4; n++)2705 {2706 // normalize V2707 V[n][0] /= V[n][2];2708 V[n][1] /= V[n][2];2709 V[n][2] = 1.0f;2710 xmin = MIN(xmin, V[n][0]);2711 xmax = MAX(xmax, V[n][0]);2712 ymin = MIN(ymin, V[n][1]);2713 ymax = MAX(ymax, V[n][1]);2714 }2715 const float owd = xmax - xmin;2716 const float oht = ymax - ymin;27172718 // calculate the lines defining the four edges of the image area: E = V[n] x V[n+1]2719 for(int n = 0; n < 4; n++)2720 vec3prodn(cropfit.edges[n], V[n], V[(n + 1) % 4]);27212722 // initial fit parameters: crop area is centered and aspect angle is2723 // that of the original image number of parameters: fit only crop2724 // center coordinates with a fixed aspect ratio, or fit all three2725 // variables2726 if(p->cropmode == ASHIFT_CROP_LARGEST)2727 {2728 params[0] = 0.5;2729 params[1] = 0.5;2730 params[2] = atan2f((float)cropfit.height, (float)cropfit.width);2731 cropfit.x = NAN;2732 cropfit.y = NAN;2733 cropfit.alpha = NAN;2734 pcount = 3;2735 }2736 else //(p->cropmode == ASHIFT_CROP_ASPECT)2737 {2738 params[0] = 0.5;2739 params[1] = 0.5;2740 cropfit.x = NAN;2741 cropfit.y = NAN;2742 cropfit.alpha = atan2f((float)cropfit.height, (float)cropfit.width);2743 pcount = 2;2744 }27452746 // start the simplex fit2747 const int iter = simplex(crop_fitness, params, pcount,2748 NMS_CROP_EPSILON, NMS_CROP_SCALE, NMS_CROP_ITERATIONS,2749 crop_constraint, (void*)&cropfit);2750 // in case the fit did not converge -> failed2751 if(iter >= NMS_CROP_ITERATIONS) goto failed;27522753 // the fit did converge -> get clipping margins out of params:2754 cropfit.x = dt_isnan(cropfit.x) ? params[0] : cropfit.x;2755 cropfit.y = dt_isnan(cropfit.y) ? params[1] : cropfit.y;2756 cropfit.alpha = dt_isnan(cropfit.alpha) ? params[2] : cropfit.alpha;27572758 // the area of the best fitting rectangle2759 const float A = fabs(crop_fitness(params, (void*)&cropfit));27602761 // unlikely to happen but we need to catch this case2762 if(A == 0.0f) goto failed;27632764 // we need the half diagonal of that rectangle (this is in output2765 // image dimensions); no need to check for division by zero here as2766 // this case implies A == 0.0f, caught above2767 const float d = sqrtf(A / (2.0f * sinf(2.0f * cropfit.alpha)));27682769 // the rectangle's center in input image (homogeneous) coordinates2770 const float Pc[3] = { cropfit.x * wd, cropfit.y * ht, 1.0f };27712772 // convert rectangle center to output image coordinates and normalize2773 float DT_ALIGNED_PIXEL P[3];2774 mat3mulv(P, (float *)cropfit.homograph, Pc);2775 P[0] /= P[2];2776 P[1] /= P[2];27772778 // calculate clipping margins relative to output image dimensions2779 g->cl = CLAMP((P[0] - d * cosf(cropfit.alpha)) / owd, 0.0f, 1.0f);2780 g->cr = CLAMP((P[0] + d * cosf(cropfit.alpha)) / owd, 0.0f, 1.0f);2781 g->ct = CLAMP((P[1] - d * sinf(cropfit.alpha)) / oht, 0.0f, 1.0f);2782 g->cb = CLAMP((P[1] + d * sinf(cropfit.alpha)) / oht, 0.0f, 1.0f);27832784 // final sanity check2785 if(g->cr - g->cl <= 0.0f || g->cb - g->ct <= 0.0f) goto failed;27862787 g->fitting = 0;27882789 dt_print(DT_DEBUG_PIPE | DT_DEBUG_VERBOSE,2790 "margins after crop fitting: iter=%d x=%.4f y=%.4f angle=%.4f"2791 " crop area (%.4f %.4f %.4f %.4f) wd=%i ht=%i",2792 iter, cropfit.x, cropfit.y, cropfit.alpha,2793 g->cl, g->cr, g->ct, g->cb, cropfit.width, cropfit.height);2794 dt_control_queue_redraw_center();2795 return;27962797failed:2798 // in case of failure: reset clipping margins, set "automatic cropping" parameter2799 // to "off" state, and display warning message2800 _clear_shadow_crop_box(g);2801 _commit_crop_box(p, g);2802 p->cropmode = ASHIFT_CROP_OFF;2803 dt_bauhaus_combobox_set(g->cropmode, p->cropmode);2804 g->fitting = 0;2805 dt_control_log(_("automatic cropping failed"));2806 return;2807}28082809// manually adjust crop area by shifting its center2810static void crop_adjust(const dt_iop_module_t *self,2811 const dt_iop_ashift_params_t *const p,2812 const float newx,2813 const float newy)2814{2815 dt_iop_ashift_gui_data_t *g = self->gui_data;28162817 // skip if fitting is still running2818 if(g->fitting) return;28192820 // get parameters for the homograph2821 const float f_length_kb = (p->mode == ASHIFT_MODE_GENERIC)2822 ? DEFAULT_F_LENGTH2823 : p->f_length * p->crop_factor;28242825 const float orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;2826 const float aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;2827 const float rotation = p->rotation;2828 const float lensshift_v = p->lensshift_v;2829 const float lensshift_h = p->lensshift_h;2830 const float shear = p->shear;2831 const float wd = g->buf_width;2832 const float ht = g->buf_height;28332834 const float alpha = atan2f(ht, wd);28352836 float DT_ALIGNED_ARRAY homograph[3][3];2837 _homography((float *)homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb,2838 orthocorr, aspect, wd, ht, ASHIFT_HOMOGRAPH_FORWARD);28392840 // the four vertices of the image in input image coordinates2841 const float Vc[4][3] = { { 0.0f, 0.0f, 1.0f },2842 { 0.0f, ht, 1.0f },2843 { wd, ht, 1.0f },2844 { wd, 0.0f, 1.0f } };28452846 // convert the vertices to output image coordinates2847 float V[4][3];2848 for(int n = 0; n < 4; n++)2849 mat3mulv(V[n], (float *)homograph, Vc[n]);28502851 // get width and height of output image2852 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;2853 for(int n = 0; n < 4; n++)2854 {2855 // normalize V2856 V[n][0] /= V[n][2];2857 V[n][1] /= V[n][2];2858 V[n][2] = 1.0f;2859 xmin = MIN(xmin, V[n][0]);2860 xmax = MAX(xmax, V[n][0]);2861 ymin = MIN(ymin, V[n][1]);2862 ymax = MAX(ymax, V[n][1]);2863 }2864 const float owd = xmax - xmin;2865 const float oht = ymax - ymin;28662867 // calculate the lines defining the four edges of the image area: E = V[n] x V[n+1]2868 float E[4][3];2869 for(int n = 0; n < 4; n++)2870 vec3prodn(E[n], V[n], V[(n + 1) % 4]);28712872 // the center of the rectangle in output image coordinates2873 const float DT_ALIGNED_PIXEL P[3] = { newx * owd, newy * oht, 1.0f };28742875 // two auxiliary points (some arbitrary distance away from P) to2876 // construct the diagonals2877 const float Pa[2][3] =2878 { { P[0] + 10.0f * cosf(alpha), P[1] + 10.0f * sinf(alpha), 1.0f },2879 { P[0] + 10.0f * cosf(alpha), P[1] - 10.0f * sinf(alpha), 1.0f } };28802881 // the two diagonals: D = P x Pa2882 float D[2][3];2883 vec3prodn(D[0], P, Pa[0]);2884 vec3prodn(D[1], P, Pa[1]);28852886 // find all intersection points of all four edges with both2887 // diagonals (I = E x D); the shortest distance d2min of the2888 // intersection point I to the crop area center P determines the2889 // size of the crop area that still fits into the image (for the2890 // given center and aspect angle)2891 float d2min = FLT_MAX;2892 for(int k = 0; k < 4; k++)2893 for(int l = 0; l < 2; l++)2894 {2895 // the intersection point2896 float I[3];2897 vec3prodn(I, E[k], D[l]);28982899 // special case: I is all null -> E and D are identical -> P lies on E -> d2min = 02900 if(vec3isnull(I))2901 {2902 d2min = 0.0f;2903 break;2904 }29052906 // special case: I[2] is 0.0f -> E and D are parallel and2907 // intersect at infinity -> no relevant point2908 if(I[2] == 0.0f)2909 continue;29102911 // the default case -> normalize I2912 I[0] /= I[2];2913 I[1] /= I[2];29142915 // calculate distance from I to P2916 const float d2 = sqrf(P[0] - I[0]) + sqrf(P[1] - I[1]);29172918 // the minimum distance over all intersection points2919 d2min = MIN(d2min, d2);2920 }29212922 const float d = sqrtf(d2min);29232924 // do not allow crop area to drop below 1% of input image area2925 const float A = 2.0f * d * d * sinf(2.0f * alpha);2926 if(A < 0.01f * wd * ht) return;29272928 // calculate clipping margins relative to output image dimensions2929 g->cl = CLAMP((P[0] - d * cosf(alpha)) / owd, 0.0f, 1.0f);2930 g->cr = CLAMP((P[0] + d * cosf(alpha)) / owd, 0.0f, 1.0f);2931 g->ct = CLAMP((P[1] - d * sinf(alpha)) / oht, 0.0f, 1.0f);2932 g->cb = CLAMP((P[1] + d * sinf(alpha)) / oht, 0.0f, 1.0f);29332934 dt_print(DT_DEBUG_PIPE | DT_DEBUG_VERBOSE,2935 "margins after crop adjustment: x=%.3f y=%.3f angle=%.3f"2936 " crop area (%.3f %.3f %.3f %.3f) width=%i height=%i",2937 0.5f * (g->cl + g->cr), 0.5f * (g->ct + g->cb), alpha,2938 g->cl, g->cr, g->ct, g->cb, (int)wd, (int)ht);2939 return;2940}29412942// determine if the line is vertical or horizontal2943static void _draw_retrieve_line_type(dt_iop_ashift_line_t *line)2944{2945 dt_iop_ashift_linetype_t linetype = ASHIFT_LINE_VERTICAL_SELECTED;2946 if(fabsf(line->p1[0] - line->p2[0]) > fabsf(line->p1[1] - line->p2[1]))2947 linetype = ASHIFT_LINE_HORIZONTAL_SELECTED;2948 line->type = linetype;2949}29502951// add a basic line. used for drawing perspective method2952static void _draw_basic_line(dt_iop_ashift_line_t *line,2953 const float x1,2954 const float y1,2955 const float x2,2956 const float y2,2957 const dt_iop_ashift_linetype_t type)2958{2959 // store as homogeneous coordinates2960 line->p1[0] = x1;2961 line->p1[1] = y1;2962 line->p1[2] = 1.0f;2963 line->p2[0] = x2;2964 line->p2[1] = y2;2965 line->p2[2] = 1.0f;29662967 // calculate homogeneous coordinates of connecting line (defined by the two points)2968 vec3prodn(line->L, line->p1, line->p2);29692970 // normalaze line coordinates so that x^2 + y^2 = 12971 // (this will always succeed as L is a real line connecting two real points)2972 vec3lnorm(line->L, line->L);29732974 // length and width of rectangle (see LSD)2975 line->length = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));2976 line->width = 1.0f;2977 line->weight = 1.0f;29782979 // register type of line2980 line->type = type;2981}29822983static void _gui_update_structure_states(const dt_iop_module_t *self, GtkWidget *widget)2984{2985 const dt_iop_ashift_gui_data_t *g = self->gui_data;2986 if(widget && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))2987 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);2988 else2989 {2990 if(widget != g->structure_lines)2991 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->structure_lines), FALSE);2992 if(widget != g->structure_quad)2993 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->structure_quad), FALSE);2994 if(widget != g->structure_auto)2995 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->structure_auto), FALSE);2996 if(widget) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);2997 }29982999 // update fit buttons state3000 const gboolean enable =3001 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->structure_auto))3002 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->structure_quad))3003 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->structure_lines)));3004 gtk_widget_set_sensitive(g->fit_v, enable);3005 gtk_widget_set_sensitive(g->fit_h, enable);3006 gtk_widget_set_sensitive(g->fit_both, enable);3007}30083009static void _draw_save_lines_to_params(dt_iop_module_t *self)3010{3011 // to save drawn lines in parameters, we only need extremas positions3012 // this positions needs to be saved in "original image" reference30133014 dt_iop_ashift_gui_data_t *g = self->gui_data;3015 dt_iop_ashift_params_t *p = self->params;3016 if(!g || !p) return;30173018 // save quad lines (we only handle the 2 vertical lines)3019 if(g->current_structure_method == ASHIFT_METHOD_QUAD && g->lines && g->lines_count >= 4)3020 {3021 float pts[8] =3022 { g->lines[0].p1[0], g->lines[0].p1[1],3023 g->lines[0].p2[0], g->lines[0].p2[1],3024 g->lines[1].p1[0], g->lines[1].p1[1],3025 g->lines[1].p2[0], g->lines[1].p2[1] };30263027 if(dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe,3028 self->iop_order,3029 DT_DEV_TRANSFORM_DIR_BACK_EXCL, pts, 4))3030 {3031 for(int i = 0; i < 8; i++)3032 p->last_quad_lines[i] = pts[i];30333034 dt_dev_add_history_item(darktable.develop, self, TRUE);3035 }3036 }3037 // save drawn lines (we drop the unselected ones)3038 if(g->current_structure_method == ASHIFT_METHOD_LINES && g->lines)3039 {3040 p->last_drawn_lines_count = 0;30413042 for(int i = 0; i < g->lines_count; i++)3043 {3044 // we only save selected lines, not removed ones3045 if(g->lines[i].type == ASHIFT_LINE_HORIZONTAL_SELECTED3046 || g->lines[i].type == ASHIFT_LINE_VERTICAL_SELECTED)3047 {3048 p->last_drawn_lines[p->last_drawn_lines_count * 4 ] = g->lines[i].p1[0];3049 p->last_drawn_lines[p->last_drawn_lines_count * 4 + 1] = g->lines[i].p1[1];3050 p->last_drawn_lines[p->last_drawn_lines_count * 4 + 2] = g->lines[i].p2[0];3051 p->last_drawn_lines[p->last_drawn_lines_count * 4 + 3] = g->lines[i].p2[1];3052 p->last_drawn_lines_count++;3053 if(p->last_drawn_lines_count >= MAX_SAVED_LINES) break;3054 }3055 }3056 if(dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe,3057 self->iop_order,3058 DT_DEV_TRANSFORM_DIR_BACK_EXCL,3059 p->last_drawn_lines,3060 p->last_drawn_lines_count * 2))3061 {3062 dt_dev_add_history_item(darktable.develop, self, TRUE);3063 }3064 }3065}3066static gboolean _draw_retrieve_lines_from_params(dt_iop_module_t *self,3067 const dt_iop_ashift_method_t method)3068{3069 // parameters contains lines extremas positions in "original image" reference3070 // so we need to translate them in module input reference3071 // and to compute length and ... values30723073 dt_iop_ashift_gui_data_t *g = self->gui_data;3074 dt_iop_ashift_params_t *p = self->params;3075 if(!g || !p) return FALSE;30763077 const dt_dev_pixelpipe_iop_t *piece =3078 dt_dev_distort_get_iop_pipe(self->dev, self->dev->preview_pipe, self);3079 if(method == ASHIFT_METHOD_QUAD3080 && p->last_quad_lines[0] > 0.0f && p->last_quad_lines[1] > 0.0f3081 && p->last_quad_lines[2] > 0.0f && p->last_quad_lines[3] > 0.0f)3082 {3083 float pts[8] = { p->last_quad_lines[0], p->last_quad_lines[1],3084 p->last_quad_lines[2], p->last_quad_lines[3],3085 p->last_quad_lines[4], p->last_quad_lines[5],3086 p->last_quad_lines[6], p->last_quad_lines[7] };3087 if(dt_dev_distort_transform_plus(self->dev, self->dev->preview_pipe, self->iop_order,3088 DT_DEV_TRANSFORM_DIR_BACK_EXCL, pts, 4))3089 {3090 if(g->lines) free(g->lines);3091 g->lines = calloc(4, sizeof(dt_iop_ashift_line_t));3092 // vertical lines3093 _draw_basic_line3094 (&g->lines[0],3095 pts[0], pts[1], pts[2], pts[3],3096 ASHIFT_LINE_VERTICAL_SELECTED);3097 _draw_basic_line3098 (&g->lines[1],3099 pts[4], pts[5], pts[6], pts[7],3100 ASHIFT_LINE_VERTICAL_SELECTED);31013102 // horizontal lines3103 _draw_basic_line3104 (&g->lines[2],3105 pts[0],3106 pts[1], pts[4], pts[5],3107 ASHIFT_LINE_HORIZONTAL_SELECTED);3108 _draw_basic_line3109 (&g->lines[3],3110 pts[2],3111 pts[3], pts[6], pts[7],3112 ASHIFT_LINE_HORIZONTAL_SELECTED);31133114 g->lines_count = 4;3115 g->vertical_count = 2;3116 g->horizontal_count = 2;3117 g->vertical_weight = 2.0;3118 g->horizontal_weight = 2.0;3119 g->lines_in_width = piece->iwidth;3120 g->lines_in_height = piece->iheight;3121 g->current_structure_method = method;3122 return TRUE;3123 }3124 }31253126 // the stored count indexes the fixed saved-lines array3127 if(method == ASHIFT_METHOD_LINES && p->last_drawn_lines_count > 03128 && p->last_drawn_lines_count <= MAX_SAVED_LINES)3129 {3130 float pts[MAX_SAVED_LINES * 4] = { 0.0f };31313132 for(int i = 0; i < p->last_drawn_lines_count * 4; i++)3133 pts[i] = p->last_drawn_lines[i];31343135 if(dt_dev_distort_transform_plus(self->dev, self->dev->preview_pipe, self->iop_order,3136 DT_DEV_TRANSFORM_DIR_BACK_EXCL, pts,3137 p->last_drawn_lines_count * 2))3138 {3139 if(g->lines) free(g->lines);3140 g->lines = calloc(p->last_drawn_lines_count, sizeof(dt_iop_ashift_line_t));31413142 int vnb = 0; // number of vertical lines3143 int hnb = 0; // number of horizontal lines3144 for(int i = 0; i < p->last_drawn_lines_count; i++)3145 {3146 // determine if the line is vertical or horizontal3147 dt_iop_ashift_linetype_t linetype = ASHIFT_LINE_VERTICAL_SELECTED;3148 if(fabsf(pts[i * 4] - pts[i * 4 + 2]) > fabsf(pts[i * 4 + 1] - pts[i * 4 + 3]))3149 linetype = ASHIFT_LINE_HORIZONTAL_SELECTED;31503151 _draw_basic_line(&g->lines[i],3152 pts[i * 4], pts[i * 4 + 1],3153 pts[i * 4 + 2], pts[i * 4 + 3],3154 linetype);3155 if(linetype == ASHIFT_LINE_VERTICAL_SELECTED)3156 vnb++;3157 else3158 hnb++;3159 }31603161 g->lines_count = p->last_drawn_lines_count;3162 g->vertical_count = vnb;3163 g->horizontal_count = hnb;3164 g->vertical_weight = (float)vnb;3165 g->horizontal_weight = (float)hnb;3166 g->lines_in_width = piece->iwidth;3167 g->lines_in_height = piece->iheight;3168 g->current_structure_method = method;3169 return TRUE;3170 }3171 }3172 return FALSE;3173}31743175// helper function to clean structural data3176static gboolean _do_clean_structure(dt_iop_module_t *self,3177 dt_iop_ashift_params_t *p,3178 const gboolean save_drawn)3179{3180 dt_iop_ashift_gui_data_t *g = self->gui_data;31813182 if(g->fitting) return FALSE;31833184 // if needed, we save the actual drawn line3185 if(save_drawn) _draw_save_lines_to_params(self);31863187 g->fitting = 1;3188 g->lines_count = 0;3189 g->vertical_count = 0;3190 g->horizontal_count = 0;3191 if(g->lines) free(g->lines);3192 g->lines = NULL;3193 g->lines_version++;3194 g->current_structure_method = ASHIFT_METHOD_NONE;3195 g->fitting = 0;3196 return TRUE;3197}31983199// helper function to start analysis for structural data and report about errors3200static gboolean _do_get_structure_auto(dt_iop_module_t *self,3201 dt_iop_ashift_params_t *p,3202 const dt_iop_ashift_enhance_t enhance)3203{3204 dt_iop_ashift_gui_data_t *g = self->gui_data;32053206 if(g->fitting) return FALSE;32073208 g->fitting = 1;32093210 dt_iop_gui_enter_critical_section(self);3211 const float *b = g->buf;3212 dt_iop_gui_leave_critical_section(self);32133214 if(b == NULL)3215 {3216 dt_control_log(_("data pending - please repeat"));3217 // force to reprocess the preview, otherwise the buffer is ko3218 dt_dev_reprocess_preview(self->dev, self->iop_order);3219 goto error;3220 }32213222 if(!_get_structure(self, enhance))3223 {3224 dt_control_log(_("could not detect structural data in image"));32253226 // find out more3227 printf("do_get_structure: buf %p, buf_hash %lu, buf_width %d, buf_height %d,"3228 " lines %p, lines_count %d\n",3229 g->buf, g->buf_hash, g->buf_width, g->buf_height, g->lines, g->lines_count);32303231 goto error;3232 }32333234 if(!_remove_outliers(self))3235 {3236 dt_control_log(_("could not run outlier removal"));32373238 // find out more3239 printf("_remove_outliers: buf %p, buf_hash %lu, buf_width %d, buf_height %d,"3240 " lines %p, lines_count %d\n",3241 g->buf, g->buf_hash, g->buf_width, g->buf_height, g->lines, g->lines_count);32423243 goto error;3244 }32453246 g->fitting = 0;3247 return TRUE;32483249error:3250 g->fitting = 0;3251 return FALSE;3252}32533254// initialise the lines structure method3255static void _do_get_structure_lines(dt_iop_module_t *self)3256{3257 dt_iop_ashift_params_t *p = self->params;3258 dt_iop_ashift_gui_data_t *g = self->gui_data;32593260 // we verify that we have a valid buffer3261 dt_iop_gui_enter_critical_section(self);3262 const float *b = g->buf;3263 dt_iop_gui_leave_critical_section(self);32643265 if(b == NULL)3266 {3267 dt_control_log(_("data pending - please repeat"));3268 // force to reprocess the preview, otherwise the buffer is ko3269 dt_dev_reprocess_preview(self->dev, self->iop_order);3270 return;3271 }32723273 _gui_update_structure_states(self, g->structure_lines);32743275 const dt_dev_pixelpipe_iop_t *piece =3276 dt_dev_distort_get_iop_pipe(self->dev, self->dev->preview_pipe, self);32773278 _do_clean_structure(self, p, TRUE);32793280 // if the button is unselected, we don't go further3281 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->structure_lines)))3282 {3283 dt_control_queue_redraw_center();3284 return;3285 }32863287 g->current_structure_method = ASHIFT_METHOD_LINES;32883289 g->lines_in_width = piece->iwidth;3290 g->lines_in_height = piece->iheight;3291 g->lines_x_off = 0;3292 g->lines_y_off = 0;32933294 // we try to recover eventual saved lines3295 _draw_retrieve_lines_from_params(self, ASHIFT_METHOD_LINES);32963297 dt_control_queue_redraw_center();3298}32993300// initialise the quad structure method3301static void _do_get_structure_quad(dt_iop_module_t *self)3302{3303 dt_iop_ashift_params_t *p = self->params;3304 dt_iop_ashift_gui_data_t *g = self->gui_data;33053306 // we verify that we have a valid buffer3307 dt_iop_gui_enter_critical_section(self);3308 const float *b = g->buf;3309 dt_iop_gui_leave_critical_section(self);33103311 if(b == NULL)3312 {3313 dt_control_log(_("data pending - please repeat"));3314 // force to reprocess the preview, otherwise the buffer is ko3315 dt_dev_reprocess_preview(self->dev, self->iop_order);3316 return;3317 }33183319 _gui_update_structure_states(self, g->structure_quad);33203321 const dt_dev_pixelpipe_iop_t *piece =3322 dt_dev_distort_get_iop_pipe(self->dev, self->dev->preview_pipe, self);33233324 _do_clean_structure(self, p, TRUE);33253326 // if the button is unselected, we don't go further3327 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->structure_quad)))3328 {3329 dt_control_queue_redraw_center();3330 return;3331 }3332 // we try to recover eventual saved lines3333 if(_draw_retrieve_lines_from_params(self, ASHIFT_METHOD_QUAD))3334 {3335 dt_control_queue_redraw_center();3336 }3337 else3338 {3339 float wd, ht;3340 dt_dev_get_preview_size(self->dev, &wd, &ht);3341 float pts[8] =3342 { wd * 0.2, ht * 0.2, wd * 0.2, ht * 0.8,3343 wd * 0.8, ht * 0.2, wd * 0.8, ht * 0.8 };33443345 if(dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe,3346 self->iop_order,3347 DT_DEV_TRANSFORM_DIR_FORW_INCL, pts, 4))3348 {3349 g->current_structure_method = ASHIFT_METHOD_QUAD;3350 g->lines = calloc(4, sizeof(dt_iop_ashift_line_t));3351 g->lines_count = 4;33523353 _draw_basic_line(&g->lines[0],3354 pts[0], pts[1],3355 pts[2], pts[3],3356 ASHIFT_LINE_VERTICAL_SELECTED);3357 _draw_basic_line(&g->lines[1],3358 pts[4],3359 pts[5], pts[6], pts[7],3360 ASHIFT_LINE_VERTICAL_SELECTED);3361 _draw_basic_line(&g->lines[2],3362 pts[0], pts[1], pts[4],3363 pts[5],3364 ASHIFT_LINE_HORIZONTAL_SELECTED);3365 _draw_basic_line(&g->lines[3],3366 pts[2],3367 pts[3], pts[6], pts[7],3368 ASHIFT_LINE_HORIZONTAL_SELECTED);33693370 // get real line type (they may be wrong due to image rotation)3371 for(int i = 0; i < 4; i++) _draw_retrieve_line_type(&g->lines[i]);33723373 g->lines_in_width = piece->iwidth;3374 g->lines_in_height = piece->iheight;3375 g->lines_x_off = 0;3376 g->lines_y_off = 0;3377 g->vertical_count = 2;3378 g->horizontal_count = 2;3379 g->vertical_weight = 2.0;3380 g->horizontal_weight = 2.0;3381 g->lines_version++;33823383 dt_control_queue_redraw_center();3384 }3385 }3386}33873388// helper function to start parameter fit and report about errors3389static void do_fit(dt_iop_module_t *self,3390 dt_iop_ashift_params_t *p,3391 const dt_iop_ashift_fitaxis_t dir)3392{3393 dt_iop_ashift_gui_data_t *g = self->gui_data;33943395 if(g->fitting) return;33963397 // if no structure available get it3398 if(g->lines == NULL)3399 if(!_do_get_structure_auto(self, p, ASHIFT_ENHANCE_NONE)) return;34003401 g->fitting = 1;34023403 const dt_iop_ashift_nmsresult_t res = nmsfit(self, p, dir);34043405 g->fitting = 0;34063407 switch(res)3408 {3409 case NMS_NOT_ENOUGH_LINES:3410 dt_control_log(3411 _("not enough structure for automatic correction\n"3412 "minimum %d lines in each relevant direction"),3413 MINIMUM_FITLINES);3414 return;3415 case NMS_DID_NOT_CONVERGE:3416 case NMS_INSANE:3417 dt_control_log(_("automatic correction failed, please correct manually"));3418 return;3419 case NMS_SUCCESS:3420 default:3421 break;3422 }34233424 // finally apply cropping3425 do_crop(self, p);3426 dt_dev_invalidate_all(darktable.develop);34273428 DT_ENTER_GUI_UPDATE();3429 dt_bauhaus_slider_set(g->rotation, p->rotation);3430 dt_bauhaus_slider_set(g->lensshift_v, p->lensshift_v);3431 dt_bauhaus_slider_set(g->lensshift_h, p->lensshift_h);3432 dt_bauhaus_slider_set(g->shear, p->shear);3433 DT_LEAVE_GUI_UPDATE();3434}34353436void process(dt_iop_module_t *self,3437 dt_dev_pixelpipe_iop_t *piece,3438 const void *const ivoid,3439 void *const ovoid,3440 const dt_iop_roi_t *const roi_in,3441 const dt_iop_roi_t *const roi_out)3442{3443 const dt_iop_ashift_data_t *data = piece->data;3444 dt_iop_ashift_gui_data_t *g = self->gui_data;34453446 const int ch = piece->colors;3447 const int ch_width = ch * roi_in->width;34483449 // only for preview pipe: collect input buffer data and do some other evaluations3450 if(g && self->dev->gui_attached && dt_pipe_is_preview(piece->pipe))3451 {3452 // we want to find out if the final output image is flipped in relation to this iop3453 // so we can adjust the gui labels accordingly3454 // origin of image and opposite corner as reference points3455 dt_boundingbox_t points = { 0.0f,3456 0.0f,3457 (float)piece->buf_in.width,3458 (float)piece->buf_in.height };34593460 const float ivec[2] = { points[2] - points[0], points[3] - points[1] };3461 const float ivecl = dt_fast_hypotf(ivec[0], ivec[1]);34623463 // where do they go?3464 dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe, self->iop_order,3465 DT_DEV_TRANSFORM_DIR_FORW_EXCL, points, 2);34663467 const float ovec[2] = { points[2] - points[0], points[3] - points[1] };3468 const float ovecl = dt_fast_hypotf(ovec[0], ovec[1]);34693470 // angle between input vector and output vector3471 const float alpha = acosf(CLAMP((ivec[0] * ovec[0] + ivec[1] * ovec[1]) / (ivecl * ovecl),3472 -1.0f, 1.0f));34733474 // we are interested if |alpha| is in the range of 90° +/- 45° ->3475 // we assume the image is flipped3476 const int isflipped = fabsf(fmodf(alpha + M_PI_F, M_PI_F) - M_PI_2f) < M_PI_4f;34773478 // did modules prior to this one in pixelpipe have changed? -> check via hash value3479 const dt_hash_t hash = dt_dev_hash_plus(self->dev, self->dev->preview_pipe,3480 self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);34813482 dt_iop_gui_enter_critical_section(self);3483 g->isflipped = isflipped;34843485 const size_t requested_size = (size_t)roi_in->width * roi_in->height;34863487 // save a copy of preview input buffer for parameter fitting3488 if(g->buf == NULL3489 || (size_t)g->buf_width * g->buf_height < requested_size)3490 {3491 // if needed allocate buffer3492 dt_free_align(g->buf);3493 // only get new buffer if no old buffer available or old buffer3494 // does not fit in terms of size3495 g->buf = dt_alloc_align_float(4 * requested_size);3496 }34973498 if(g->buf /* && hash != g->buf_hash */)3499 {3500 // copy data using actual roi_in dimensions (can exceed buf_in when scale > 1.0)3501 dt_iop_image_copy_by_size(g->buf, ivoid, roi_in->width, roi_in->height, ch);35023503 g->buf_width = roi_in->width;3504 g->buf_height = roi_in->height;3505 g->buf_x_off = roi_in->x;3506 g->buf_y_off = roi_in->y;3507 g->buf_scale = roi_in->scale;3508 g->buf_hash = hash;3509 }35103511 dt_iop_gui_leave_critical_section(self);3512 }35133514 // if module is set to neutral parameters we just copy input->output and are done3515 if(_isneutral(data))3516 {3517 dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);3518 return;3519 }35203521 const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP);35223523 float DT_ALIGNED_ARRAY ihomograph[3][3];3524 _homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h,3525 data->shear, data->f_length_kb,3526 data->orthocorr, data->aspect,3527 piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);35283529 // clipping offset3530 const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl);3531 const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct);3532 const float cx = roi_out->scale * fullwidth * data->cl;3533 const float cy = roi_out->scale * fullheight * data->ct;35343535 DT_OMP_FOR(shared(ihomograph))3536 // go over all pixels of output image3537 for(int j = 0; j < roi_out->height; j++)3538 {3539 float *const restrict out = ((float *)ovoid) + (size_t)ch * j * roi_out->width;3540 for(int i = 0; i < roi_out->width; i++)3541 {3542 float pin[3], pout[3];35433544 // convert output pixel coordinates to original image coordinates3545 pout[0] = roi_out->x + i + cx;3546 pout[1] = roi_out->y + j + cy;3547 pout[0] /= roi_out->scale;3548 pout[1] /= roi_out->scale;3549 pout[2] = 1.0f;35503551 // apply homograph3552 mat3mulv(pin, (float *)ihomograph, pout);35533554 // convert to input pixel coordinates3555 pin[0] /= pin[2];3556 pin[1] /= pin[2];3557 pin[0] *= roi_in->scale;3558 pin[1] *= roi_in->scale;3559 pin[0] -= roi_in->x;3560 pin[1] -= roi_in->y;35613562 // get output values by interpolation from input image3563 dt_interpolation_compute_pixel4c(interpolation, (float *)ivoid, out + ch*i,3564 pin[0], pin[1], roi_in->width,3565 roi_in->height, ch_width);3566 }3567 }3568}356935703571int process_cl(dt_iop_module_t *self,3572 dt_dev_pixelpipe_iop_t *piece,3573 cl_mem dev_in,3574 cl_mem dev_out,3575 const dt_iop_roi_t *const roi_in,3576 const dt_iop_roi_t *const roi_out)3577{3578 const dt_iop_ashift_data_t *d = piece->data;3579 const dt_iop_ashift_global_data_t *gd = self->global_data;3580 dt_iop_ashift_gui_data_t *g = self->gui_data;35813582 const int devid = piece->pipe->devid;35833584 cl_int err = DT_OPENCL_DEFAULT_ERROR;3585 cl_mem dev_homo = NULL;35863587 // only for preview pipe: collect input buffer data and do some other evaluations3588 if(self->dev->gui_attached && g && dt_pipe_is_preview(piece->pipe))3589 {3590 // we want to find out if the final output image is flipped in relation to this iop3591 // so we can adjust the gui labels accordingly3592 // origin of image and opposite corner as reference points3593 dt_boundingbox_t points = { 0.0f,3594 0.0f,3595 (float)piece->buf_in.width,3596 (float)piece->buf_in.height };35973598 const float ivec[2] = { points[2] - points[0], points[3] - points[1] };3599 const float ivecl = dt_fast_hypotf(ivec[0], ivec[1]);36003601 // where do they go?3602 dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe, self->iop_order,3603 DT_DEV_TRANSFORM_DIR_FORW_EXCL, points, 2);36043605 const float ovec[2] = { points[2] - points[0], points[3] - points[1] };3606 const float ovecl = dt_fast_hypotf(ovec[0], ovec[1]);36073608 // angle between input vector and output vector3609 const float alpha =3610 acosf(CLAMP((ivec[0] * ovec[0] + ivec[1] * ovec[1]) / (ivecl * ovecl), -1.0f, 1.0f));36113612 // we are interested if |alpha| is in the range of 90° +/- 45° ->3613 // we assume the image is flipped3614 const int isflipped =3615 fabsf(fmodf(alpha + M_PI_F, M_PI_F) - M_PI_2f) < M_PI_4f;36163617 // do modules coming before this one in pixelpipe have changed? -> check via hash value3618 const dt_hash_t hash = dt_dev_hash_plus(self->dev, self->dev->preview_pipe,3619 self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);36203621 dt_iop_gui_enter_critical_section(self);3622 g->isflipped = isflipped;36233624 const size_t requested_size = (size_t)roi_in->width * roi_in->height;36253626 // save a copy of preview input buffer for parameter fitting3627 if(g->buf == NULL || (size_t)g->buf_width * g->buf_height < requested_size)3628 {3629 // if needed allocate buffer3630 dt_free_align(g->buf);3631 // only get new buffer if no old buffer or old buffer does not fit in terms of size3632 g->buf = dt_alloc_align_float(4 * requested_size);3633 }36343635 if(g->buf /* && hash != g->buf_hash */)3636 {3637 // copy data using actual roi_in dimensions (can exceed buf_in when scale > 1.0)3638 err = dt_opencl_copy_image_to_host(devid, g->buf, dev_in,3639 roi_in->width, roi_in->height, sizeof(float) * 4);36403641 g->buf_width = roi_in->width;3642 g->buf_height = roi_in->height;3643 g->buf_x_off = roi_in->x;3644 g->buf_y_off = roi_in->y;3645 g->buf_scale = roi_in->scale;3646 g->buf_hash = hash;3647 }3648 dt_iop_gui_leave_critical_section(self);3649 if(err != CL_SUCCESS) goto error;3650 }36513652 // if module is set to neutral parameters we just copy input->output and are done3653 if(_isneutral(d))3654 {3655 const size_t region[2] = { roi_out->width, roi_out->height };3656 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, CLIMG_ORIGIN, CLIMG_ORIGIN, region);3657 if(err != CL_SUCCESS) goto error;3658 return CL_SUCCESS;3659 }36603661 float DT_ALIGNED_ARRAY ihomograph[3][3];3662 _homography((float *)ihomograph, d->rotation, d->lensshift_v, d->lensshift_h,3663 d->shear, d->f_length_kb,3664 d->orthocorr, d->aspect,3665 piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED);36663667 // clipping offset3668 const float fullwidth = (float)piece->buf_out.width / (d->cr - d->cl);3669 const float fullheight = (float)piece->buf_out.height / (d->cb - d->ct);3670 const float cx = roi_out->scale * fullwidth * d->cl;3671 const float cy = roi_out->scale * fullheight * d->ct;36723673 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;3674 dev_homo = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 9, ihomograph);3675 if(dev_homo == NULL) goto error;36763677 const int iroi[2] = { roi_in->x, roi_in->y };3678 const int oroi[2] = { roi_out->x, roi_out->y };3679 const float clip[2] = { cx, cy };368036813682 const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP);36833684 int ldkernel = -1;36853686 switch(interpolation->id)3687 {3688 case DT_INTERPOLATION_BILINEAR:3689 ldkernel = gd->kernel_ashift_bilinear;3690 break;3691 case DT_INTERPOLATION_BICUBIC:3692 ldkernel = gd->kernel_ashift_bicubic;3693 break;3694 case DT_INTERPOLATION_LANCZOS2:3695 ldkernel = gd->kernel_ashift_lanczos2;3696 break;3697 case DT_INTERPOLATION_LANCZOS3:3698 ldkernel = gd->kernel_ashift_lanczos3;3699 break;3700 default:3701 err = DT_OPENCL_DEFAULT_ERROR;3702 goto error;3703 }37043705 err = dt_opencl_enqueue_kernel_2d_args3706 (devid, ldkernel, roi_out->width, roi_out->height,3707 CLARG(dev_in), CLARG(dev_out), CLARG(roi_out->width), CLARG(roi_out->height),3708 CLARG(roi_in->width), CLARG(roi_in->height), CLARRAY(2, iroi),3709 CLARRAY(2, oroi),3710 CLARG(roi_in->scale), CLARG(roi_out->scale), CLARRAY(2, clip), CLARG(dev_homo));37113712error:3713 dt_opencl_release_mem_object(dev_homo);3714 return err;3715}371637173718// gather information about "near"-ness in g->points_idx3719static void _get_near(const float *points,3720 dt_iop_ashift_points_idx_t *points_idx,3721 const int lines_count,3722 const float pzx,3723 const float pzy,3724 const float delta,3725 const gboolean multiple)3726{3727 const float delta2 = delta * delta;37283729 if(lines_count < 1 || points_idx == NULL)3730 return;37313732 for(int n = 0; n < lines_count; n++)3733 points_idx[n].near = 0;3734 for(int n = 0; n < lines_count; n++)3735 {3736 // skip irrelevant lines3737 if(points_idx[n].type == ASHIFT_LINE_IRRELEVANT)3738 continue;37393740 // first check if the mouse pointer is outside the bounding box of3741 // the line -> skip this line3742 if(pzx < points_idx[n].bbx - delta &&3743 pzx > points_idx[n].bbX + delta &&3744 pzy < points_idx[n].bby - delta &&3745 pzy > points_idx[n].bbY + delta)3746 continue;37473748 // pointer is inside bounding box3749 size_t offset = points_idx[n].offset;3750 const int length = points_idx[n].length;37513752 // sanity check (this should not happen)3753 if(length < 2) continue;37543755 // check line point by point3756 for(int l = 0; l < length; l++, offset++)3757 {3758 const float dx = pzx - points[offset * 2];3759 const float dy = pzy - points[offset * 2 + 1];37603761 if(dx * dx + dy * dy < delta2)3762 {3763 points_idx[n].near = 1;3764 break;3765 }3766 }3767 // if we don't want multiple selection, stop here3768 if(!multiple && points_idx[n].near) break;3769 }3770}37713772// mark lines which are inside a rectangular area in isbounding mode3773static void _get_bounded_inside(const float *points,3774 dt_iop_ashift_points_idx_t *points_idx,3775 const int points_lines_count,3776 const float pzx,3777 const float pzy,3778 const float pzx2,3779 const float pzy2,3780 const dt_iop_ashift_bounding_t mode)3781{3782 // get bounding box coordinates3783 float ax = pzx;3784 float ay = pzy;3785 float bx = pzx2;3786 float by = pzy2;3787 if(pzx > pzx2)3788 {3789 ax = pzx2;3790 bx = pzx;3791 }3792 if(pzy > pzy2)3793 {3794 ay = pzy2;3795 by = pzy;3796 }37973798 // we either look for the selected or the deselected lines3799 const dt_iop_ashift_linetype_t mask = ASHIFT_LINE_SELECTED;3800 const dt_iop_ashift_linetype_t state =3801 (mode == ASHIFT_BOUNDING_DESELECT) ? ASHIFT_LINE_SELECTED : 0;38023803 for(int n = 0; n < points_lines_count; n++)3804 {3805 // mark line as "not near" and "not bounded"3806 points_idx[n].near = 0;3807 points_idx[n].bounded = 0;38083809 // skip irrelevant lines3810 if(points_idx[n].type == ASHIFT_LINE_IRRELEVANT)3811 continue;38123813 // is the line inside the box ?3814 if(points_idx[n].bbx >= ax3815 && points_idx[n].bbx <= bx3816 && points_idx[n].bbX >= ax3817 && points_idx[n].bbX <= bx3818 && points_idx[n].bby >= ay3819 && points_idx[n].bby <= by3820 && points_idx[n].bbY >= ay3821 && points_idx[n].bbY <= by)3822 {3823 points_idx[n].bounded = 1;3824 // only mark "near"-ness of those lines we are interested in3825 points_idx[n].near = ((points_idx[n].type & mask) != state) ? 0 : 1;3826 }3827 }3828}38293830// generate hash value for lines taking into account only the end point coordinates3831static dt_hash_t _get_lines_hash(const dt_iop_ashift_line_t *lines,3832 const int lines_count)3833{3834 dt_hash_t hash = DT_INITHASH;3835 for(int n = 0; n < lines_count; n++)3836 {3837 const uint32_t v[4] = { (uint32_t)lines[n].p1[0],3838 (uint32_t)lines[n].p1[1],3839 (uint32_t)lines[n].p2[0],3840 (uint32_t)lines[n].p2[1] };3841 hash = dt_hash(hash, &v, sizeof(v));3842 }3843 return hash;3844}38453846// update color information in points_idx if lines have changed in3847// terms of type (but not in terms of number or position)38483849static gboolean _update_colors(const dt_iop_module_t *self,3850 dt_iop_ashift_points_idx_t *points_idx,3851 const int points_lines_count)3852{3853 const dt_iop_ashift_gui_data_t *g = self->gui_data;38543855 // is the display flipped relative to the original image?3856 const gboolean isflipped = g->isflipped;38573858 // go through all lines3859 for(int n = 0; n < points_lines_count; n++)3860 {3861 const dt_iop_ashift_linetype_t type = points_idx[n].type;38623863 // set line color according to line type/orientation3864 // note: if the screen display is flipped versus the original image we need3865 // to respect that fact in the color selection3866 if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_SELECTED)3867 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_BLUE : ASHIFT_LINECOLOR_GREEN;3868 else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_NOT_SELECTED)3869 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_YELLOW : ASHIFT_LINECOLOR_RED;3870 else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_SELECTED)3871 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_GREEN : ASHIFT_LINECOLOR_BLUE;3872 else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_NOT_SELECTED)3873 points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_RED : ASHIFT_LINECOLOR_YELLOW;3874 else3875 points_idx[n].color = ASHIFT_LINECOLOR_GREY;3876 }38773878 return TRUE;3879}38803881// get all the points to display lines in the gui3882static gboolean _get_points(const dt_iop_module_t *self,3883 const dt_iop_ashift_line_t *lines,3884 const int lines_count,3885 const int lines_version,3886 float **points,3887 float **extremas,3888 dt_iop_ashift_points_idx_t **points_idx,3889 int *points_lines_count,3890 const float scale)3891{3892 dt_develop_t *dev = self->dev;3893 const dt_iop_ashift_gui_data_t *g = self->gui_data;38943895 if(lines_count < 1)3896 return FALSE;3897 dt_iop_ashift_points_idx_t *my_points_idx = NULL;3898 float *my_points = NULL;3899 float *my_extremas = NULL;39003901 // is the display flipped relative to the original image?3902 const int isflipped = g->isflipped;39033904 // allocate new index array3905 my_points_idx = malloc(sizeof(dt_iop_ashift_points_idx_t) * lines_count);3906 if(my_points_idx == NULL) goto error;39073908 // account for total number of points3909 size_t total_points = 0;39103911 // first step: basic initialization of my_points_idx and counting of total_points3912 for(int n = 0; n < lines_count; n++)3913 {3914 const int length = MAX(lines[n].length, 2);39153916 total_points += length;39173918 my_points_idx[n].length = length;3919 my_points_idx[n].near = 0;3920 my_points_idx[n].bounded = 0;39213922 const dt_iop_ashift_linetype_t type = lines[n].type;3923 my_points_idx[n].type = type;39243925 // set line color according to line type/orientation3926 // note: if the screen display is flipped versus the original image we need3927 // to respect that fact in the color selection3928 if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_SELECTED)3929 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_BLUE : ASHIFT_LINECOLOR_GREEN;3930 else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_NOT_SELECTED)3931 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_YELLOW : ASHIFT_LINECOLOR_RED;3932 else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_SELECTED)3933 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_GREEN : ASHIFT_LINECOLOR_BLUE;3934 else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_NOT_SELECTED)3935 my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_RED : ASHIFT_LINECOLOR_YELLOW;3936 else3937 my_points_idx[n].color = ASHIFT_LINECOLOR_GREY;3938 }39393940 // now allocate new points buffer3941 my_points = (float *)malloc(sizeof(float) * 2 * total_points);3942 my_extremas = (float *)malloc(sizeof(float) * 2 * 2 * lines_count);3943 if(my_points == NULL || my_extremas == NULL) goto error;39443945 // second step: generate points for each line3946 for(int n = 0, offset = 0; n < lines_count; n++)3947 {3948 my_extremas[4 * n] = lines[n].p1[0] / scale;3949 my_extremas[4 * n + 1] = lines[n].p1[1] / scale;3950 my_extremas[4 * n + 2] = lines[n].p2[0] / scale;3951 my_extremas[4 * n + 3] = lines[n].p2[1] / scale;39523953 my_points_idx[n].offset = offset;39543955 float x = lines[n].p1[0] / scale;3956 float y = lines[n].p1[1] / scale;3957 const int length = lines[n].length;39583959 const float dx = (lines[n].p2[0] / scale - x) / (float)(length - 1);3960 const float dy = (lines[n].p2[1] / scale - y) / (float)(length - 1);39613962 // for very small length, we set the second extrema at last point3963 if(length < 2)3964 {3965 my_points[2 * offset] = x;3966 my_points[2 * offset + 1] = y;3967 offset++;3968 my_points[2 * offset] = lines[n].p2[0] / scale;3969 my_points[2 * offset + 1] = lines[n].p2[1] / scale;3970 offset++;3971 }3972 else3973 {3974 for(int l = 0; l < length && offset < total_points; l++, offset++)3975 {3976 my_points[2 * offset] = x;3977 my_points[2 * offset + 1] = y;39783979 x += dx;3980 y += dy;3981 }3982 }3983 }39843985 // third step: transform all points3986 if(!dt_dev_distort_transform_plus(dev, dev->preview_pipe, self->iop_order,3987 DT_DEV_TRANSFORM_DIR_FORW_INCL,3988 my_points, total_points))3989 goto error;3990 if(!dt_dev_distort_transform_plus(dev, dev->preview_pipe, self->iop_order,3991 DT_DEV_TRANSFORM_DIR_FORW_INCL,3992 my_extremas, 2 * lines_count))3993 goto error;39943995 // fourth step: get bounding box in final coordinates (used later3996 // for checking "near"-ness to mouse pointer)3997 for(int n = 0; n < lines_count; n++)3998 {3999 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;40004001 const size_t offset = my_points_idx[n].offset;4002 const int length = my_points_idx[n].length;40034004 for(int l = 0; l < length; l++)4005 {4006 xmin = MIN(xmin, my_points[2 * offset]);4007 xmax = MAX(xmax, my_points[2 * offset]);4008 ymin = MIN(ymin, my_points[2 * offset + 1]);4009 ymax = MAX(ymax, my_points[2 * offset + 1]);4010 }40114012 my_points_idx[n].bbx = xmin;4013 my_points_idx[n].bbX = xmax;4014 my_points_idx[n].bby = ymin;4015 my_points_idx[n].bbY = ymax;4016 }40174018 // check if lines_version has changed in-between -> too bad: we can4019 // forget about all we did :(4020 if(g->lines_version > lines_version)4021 goto error;40224023 *points = my_points;4024 *points_idx = my_points_idx;4025 *points_lines_count = lines_count;4026 *extremas = my_extremas;40274028 return TRUE;40294030error:4031 if(my_points_idx != NULL) free(my_points_idx);4032 if(my_points != NULL) free(my_points);4033 if(my_extremas) free(my_extremas);4034 return FALSE;4035}40364037/* this function replaces this sentence, it calls distort_transform()4038 for this module on the pipe4039 if(!dt_dev_distort_transform_plus(self->dev, self->dev->preview_pipe,4040 self->priority, self->priority + 1,4041 (float *)V, 4))4042*/4043static int call_distort_transform(const dt_develop_t *dev,4044 const dt_dev_pixelpipe_t *pipe,4045 dt_iop_module_t *self,4046 float *points,4047 const size_t points_count)4048{4049 int ret = 0;4050 dt_dev_pixelpipe_iop_t *piece =4051 dt_dev_distort_get_iop_pipe(self->dev, self->dev->preview_pipe, self);4052 if(!piece) return ret;4053 if(piece->module == self4054 // && *piece->enabled see note below4055 && !(dt_iop_module_is_skipped(dev, piece->module)4056 && dt_pipe_is_basic(pipe)))4057 {4058 ret = piece->module->distort_transform(piece->module, piece, points, points_count);4059 }4060 return ret;4061 //NOTE: piece->enabled is FALSE for exactly the first mouse_moved4062 // event following a button_pressed event when ASHIFT_CROP_ASPECT4063 // is active, which causes the first gui_post_expose call on4064 // starting to resize the crop box to draw the center image without4065 // the crop overlay, resulting in an annoying visual glitch.4066 // Removing the check appears to have no adverse effects and4067 // eliminates the glitch.4068}40694070static float _calculate_straightening(const dt_iop_module_t *self,4071 const float pzx, const float pzy,4072 const float bzx, const float bzy,4073 const float wd, const float ht,4074 const float zoom_scale)4075{4076 float dx = (pzx - bzx) * wd, dy = (pzy - bzy) * ht;4077 if(sqrt(dx * dx + dy * dy) * zoom_scale < DT_PIXEL_APPLY_DPI(25))4078 return 0.0f;40794080 // adjust the line with possible current angle and flip on this module4081 float pts[4] = { 0, 0, dx, dy };4082 dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe,4083 self->iop_order,4084 DT_DEV_TRANSFORM_DIR_FORW_EXCL, pts, 2);4085 dx = pts[0] - pts[2];4086 dy = pts[1] - pts[3];40874088 if(dx < 0)4089 {4090 dx = -dx;4091 dy = -dy;4092 }40934094 const float angle = atan2f(dy, dx);4095 if(!(angle >= -M_PI_2f && angle <= M_PI_2f))4096 return 0.0f;4097 float close = angle;4098 if(close > M_PI_4f)4099 close = M_PI_2f - close;4100 else if(close < -M_PI_4f)4101 close = -M_PI_2f - close;4102 else4103 close = -close;41044105 float a = rad2degf(close);4106 if(a < -180.f) a += 360.f;4107 if(a > 180.f) a -= 360.f;41084109 return a;4110}41114112void gui_post_expose(dt_iop_module_t *self,4113 cairo_t *cr,4114 const float wd,4115 const float ht,4116 const float pzx,4117 const float pzy,4118 const float zoom_scale)4119{4120 dt_develop_t *dev = self->dev;4121 dt_iop_ashift_gui_data_t *g = self->gui_data;4122 const dt_iop_ashift_params_t *p = self->params;41234124 const gboolean dimmed = dt_iop_canvas_not_sensitive(dev);4125 const double lwidth = (dimmed ? 0.5 : 1.0) / zoom_scale;4126 const double fillc = dimmed ? 0.9 : 0.2;41274128 // we draw the cropping area; we need x_off/y_off/width/height which is only available4129 // after g->buf has been processed4130 if(g->buf && self->enabled && dt_iop_has_focus(self))4131 {4132 // roi data of the preview pipe input buffer41334134 const float iwd = g->buf_width;4135 const float iht = g->buf_height;4136 const float ixo = g->buf_x_off;4137 const float iyo = g->buf_y_off;41384139 // the four corners of the input buffer of this module4140 const float V[4][2] = { { ixo, iyo },4141 { ixo, iyo + iht },4142 { ixo + iwd, iyo + iht },4143 { ixo + iwd, iyo } };41444145 // convert coordinates of corners to coordinates of this module's output4146 if(!call_distort_transform(self->dev, self->dev->preview_pipe, self, (float *)V, 4))4147 return;41484149 // get x/y-offset as well as width and height of output buffer4150 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;4151 for(int n = 0; n < 4; n++)4152 {4153 xmin = MIN(xmin, V[n][0]);4154 xmax = MAX(xmax, V[n][0]);4155 ymin = MIN(ymin, V[n][1]);4156 ymax = MAX(ymax, V[n][1]);4157 }4158 const float owd = xmax - xmin;4159 const float oht = ymax - ymin;41604161 // the four clipping corners4162 const float C[4][2] = { { xmin + g->cl * owd, ymin + g->ct * oht },4163 { xmin + g->cl * owd, ymin + g->cb * oht },4164 { xmin + g->cr * owd, ymin + g->cb * oht },4165 { xmin + g->cr * owd, ymin + g->ct * oht } };41664167 // convert clipping corners to final output image4168 if(!dt_dev_distort_transform_plus(self->dev, self->dev->preview_pipe, self->iop_order,4169 DT_DEV_TRANSFORM_DIR_FORW_EXCL, (float *)C, 4))4170 return;41714172 cairo_save(cr);41734174 const double dashes = DT_PIXEL_APPLY_DPI(5.0) / zoom_scale;4175 cairo_set_dash(cr, &dashes, 0, 0);41764177 // mask parts of image outside of clipping area in dark grey4178 cairo_set_source_rgba(cr, fillc, fillc, fillc, 1.0 - fillc);4179 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);41804181 cairo_rectangle(cr, 0, 0, wd, ht);41824183 cairo_move_to(cr, C[0][0], C[0][1]);4184 cairo_line_to(cr, C[1][0], C[1][1]);4185 cairo_line_to(cr, C[2][0], C[2][1]);4186 cairo_line_to(cr, C[3][0], C[3][1]);4187 cairo_close_path(cr);4188 cairo_fill(cr);41894190 // draw white outline around clipping area4191 dt_draw_set_color_overlay(cr, TRUE, 1.0);4192 cairo_set_line_width(cr, 2.0 *lwidth);4193 cairo_move_to(cr, C[0][0], C[0][1]);4194 cairo_line_to(cr, C[1][0], C[1][1]);4195 cairo_line_to(cr, C[2][0], C[2][1]);4196 cairo_line_to(cr, C[3][0], C[3][1]);4197 cairo_close_path(cr);4198 cairo_stroke(cr);41994200 // we draw the guides correctly scaled here instead of using the4201 // darkroom expose callback42024203 const float cx = fminf(C[0][0], fminf(C[1][0], fminf(C[2][0], C[3][0])));4204 const float cy = fminf(C[0][1], fminf(C[1][1], fminf(C[2][1], C[3][1])));4205 const float cw = fmaxf(C[0][0], fmaxf(C[1][0], fmaxf(C[2][0], C[3][0]))) - cx;4206 const float ch = fmaxf(C[0][1], fmaxf(C[1][1], fmaxf(C[2][1], C[3][1]))) - cy;4207 dt_guides_draw(cr, cx, cy, cw, ch, zoom_scale);42084209 // if adjusting crop, draw indicator4210 if(g->adjust_crop && p->cropmode == ASHIFT_CROP_ASPECT)4211 {4212 const double x1 = C[0][0];4213 const double x2 = fabs(x1 - C[1][0]) < 0.001f ? C[2][0] : C[1][0];4214 const double y1 = C[0][1];4215 const double y2 = fabs(y1 - C[1][1]) < 0.001f ? C[2][1] : C[1][1];42164217 const double xpos = (x1 + x2) / 2.0f;4218 const double ypos = (y1 + y2) / 2.0f;4219 const double base_size = fabs(x1 - x2);4220 const double size_circle = base_size / 30.0f;4221 const double size_line = base_size / 5.0f;4222 const double size_arrow = base_size / 25.0f;42234224 cairo_set_line_width(cr, 2.0 * lwidth);4225 dt_draw_set_color_overlay(cr, TRUE, 1.0);4226 cairo_arc (cr, xpos, ypos, size_circle, 0, 2.0 * M_PI);4227 cairo_stroke(cr);4228 cairo_fill(cr);42294230 cairo_set_line_width(cr, 2.0 * lwidth);4231 dt_draw_set_color_overlay(cr, TRUE, 1.0);42324233 // horizontal line4234 cairo_move_to(cr, xpos - size_line, ypos);4235 cairo_line_to(cr, xpos + size_line, ypos);42364237 cairo_move_to(cr, xpos - size_line, ypos);4238 cairo_rel_line_to(cr, size_arrow, size_arrow);4239 cairo_move_to(cr, xpos - size_line, ypos);4240 cairo_rel_line_to(cr, size_arrow, -size_arrow);42414242 cairo_move_to(cr, xpos + size_line, ypos);4243 cairo_rel_line_to(cr, -size_arrow, size_arrow);4244 cairo_move_to(cr, xpos + size_line, ypos);4245 cairo_rel_line_to(cr, -size_arrow, -size_arrow);42464247 // vertical line4248 cairo_move_to(cr, xpos, ypos - size_line);4249 cairo_line_to(cr, xpos, ypos + size_line);42504251 cairo_move_to(cr, xpos, ypos - size_line);4252 cairo_rel_line_to(cr, -size_arrow, size_arrow);4253 cairo_move_to(cr, xpos, ypos - size_line);4254 cairo_rel_line_to(cr, size_arrow, size_arrow);42554256 cairo_move_to(cr, xpos, ypos + size_line);4257 cairo_rel_line_to(cr, -size_arrow, -size_arrow);4258 cairo_move_to(cr, xpos, ypos + size_line);4259 cairo_rel_line_to(cr, size_arrow, -size_arrow);42604261 cairo_stroke(cr);4262 }42634264 cairo_restore(cr);4265 }4266 else4267 dt_guides_draw(cr, 0.0f, 0.0f, wd, ht, zoom_scale);42684269 // we draw the straightening line4270 if(g->straightening)4271 {4272 cairo_save(cr);4273 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) * lwidth);4274 dt_draw_set_color_overlay(cr, TRUE, 1.0);42754276 const float bzx = g->straighten_x, bzy = g->straighten_y;4277 cairo_arc(cr, bzx * wd, bzy * ht,4278 DT_PIXEL_APPLY_DPI(3) / zoom_scale, 0, 2.0 * M_PI);4279 cairo_stroke(cr);4280 cairo_arc(cr, pzx * wd, pzy * ht,4281 DT_PIXEL_APPLY_DPI(3) / zoom_scale, 0, 2.0 * M_PI);4282 cairo_stroke(cr);4283 cairo_move_to(cr, bzx * wd, bzy * ht);4284 cairo_line_to(cr, pzx * wd, pzy * ht);4285 cairo_stroke(cr);42864287 const float angle =4288 _calculate_straightening(self, pzx, pzy, bzx, bzy, wd, ht, zoom_scale);42894290 if(angle != 0.0f)4291 {4292 PangoRectangle ink;4293 PangoLayout *layout;4294 PangoFontDescription *desc = dt_gui_get_font();4295 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);4296 pango_font_description_set_absolute_size4297 (desc,4298 DT_PIXEL_APPLY_DPI(16) * PANGO_SCALE / zoom_scale);4299 layout = pango_cairo_create_layout(cr);4300 pango_layout_set_font_description(layout, desc);4301 char view_angle[16];4302 view_angle[0] = '\0';4303 snprintf(view_angle, sizeof(view_angle), "%.2f°", angle);4304 pango_layout_set_text(layout, view_angle, -1);4305 pango_layout_get_pixel_extents(layout, &ink, NULL);4306 const float text_w = ink.width;4307 const float text_h = DT_PIXEL_APPLY_DPI(16 + 2) / zoom_scale;4308 const float margin = DT_PIXEL_APPLY_DPI(6) / zoom_scale;4309 cairo_set_source_rgba(cr, .5, .5, .5, .9);4310 const float xp = pzx * wd + DT_PIXEL_APPLY_DPI(20) / zoom_scale;4311 const float yp = pzy * ht - ink.height;4312 dt_gui_draw_rounded_rectangle(cr,4313 text_w + 2 * margin,4314 text_h + 2 * margin,4315 xp - margin, yp - margin);4316 cairo_set_source_rgba(cr, .7, .7, .7, .7);4317 cairo_move_to(cr, xp, yp);4318 pango_cairo_show_layout(cr, layout);4319 pango_font_description_free(desc);4320 g_object_unref(layout);4321 }4322 cairo_restore(cr);4323 }43244325 // structural data are currently being collected or fit procedure is running? -> skip4326 if(g->fitting) return;43274328 // no structural data or visibility switched off? -> stop here4329 if(g->lines == NULL || !dt_iop_has_focus(self)) return;43304331 // get hash value that reflects distortions from here to the end of the pixelpipe4332 const dt_hash_t hash = dt_dev_hash_distort_plus(dev,4333 dev->preview_pipe,4334 (double)self->iop_order,4335 DT_DEV_TRANSFORM_DIR_FORW_INCL);4336 // get hash value that changes if coordinates of lines have changed4337 const dt_hash_t lines_hash = _get_lines_hash(g->lines, g->lines_count);43384339 // points data are missing or outdated, or distortion has changed?4340 if(g->points == NULL || g->points_idx == NULL || hash != g->grid_hash4341 || (g->lines_version > g->points_version4342 && g->lines_hash != lines_hash))4343 {4344 // we need to reprocess points4345 free(g->points);4346 g->points = NULL;4347 free(g->points_idx);4348 g->points_idx = NULL;4349 free(g->draw_points);4350 g->draw_points = NULL;4351 g->points_lines_count = 0;43524353 if(!_get_points(self, g->lines, g->lines_count, g->lines_version,4354 &g->points, &g->draw_points, &g->points_idx,4355 &g->points_lines_count, 1.0f))4356 return;43574358 g->points_version = g->lines_version;4359 g->grid_hash = hash;4360 g->lines_hash = lines_hash;4361 }4362 else if(g->lines_hash == lines_hash)4363 {4364 // update line type information in points_idx4365 for(int n = 0; n < g->points_lines_count; n++)4366 g->points_idx[n].type = g->lines[n].type;43674368 // coordinates of lines are unchanged -> we only need to update colors4369 if(!_update_colors(self, g->points_idx, g->points_lines_count))4370 return;43714372 g->points_version = g->lines_version;4373 }43744375 // a final check4376 if(g->points == NULL || g->points_idx == NULL) return;43774378 cairo_save(cr);43794380 // this must match the sequence of enum dt_iop_ashift_linecolor_t!4381 const float line_colors[5][4] =4382 { { 0.3f, 0.3f, 0.3f, 0.8f }, // grey (misc. lines)4383 { 0.0f, 1.0f, 0.0f, 0.8f }, // green (selected vertical lines)4384 { 0.8f, 0.0f, 0.0f, 0.8f }, // red (de-selected vertical lines)4385 { 0.0f, 0.0f, 1.0f, 0.8f }, // blue (selected horizontal lines)4386 { 0.8f, 0.8f, 0.0f, 0.8f } }; // yellow (de-selected horizontal lines)43874388 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);43894390 // now draw all lines4391 for(int n = 0; n < g->points_lines_count; n++)4392 {4393 // hide removed lines in drawn mode4394 if((g->current_structure_method == ASHIFT_METHOD_QUAD4395 || g->current_structure_method == ASHIFT_METHOD_LINES)4396 && g->points_idx[n].type != ASHIFT_LINE_HORIZONTAL_SELECTED4397 && g->points_idx[n].type != ASHIFT_LINE_VERTICAL_SELECTED)4398 continue;4399 // is the near flag set? -> draw line a bit thicker4400 if(g->points_idx[n].near)4401 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.0) * lwidth);4402 else4403 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5) * lwidth);44044405 // the color of this line4406 const float *color = line_colors[g->points_idx[n].color];4407 cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]);44084409 size_t offset = g->points_idx[n].offset;4410 const int length = g->points_idx[n].length;44114412 // sanity check (this should not happen)4413 if(length < 2) continue;44144415 // set starting point of multi-segment line4416 cairo_move_to(cr, g->points[offset * 2], g->points[offset * 2 + 1]);44174418 offset++;4419 // draw individual line segments4420 for(int l = 1; l < length; l++, offset++)4421 {4422 cairo_line_to(cr, g->points[offset * 2], g->points[offset * 2 + 1]);4423 }44244425 // finally stroke the line4426 cairo_stroke(cr);4427 }44284429 // we also draw the corner in case of drawn perspective4430 if((g->current_structure_method == ASHIFT_METHOD_QUAD4431 || g->current_structure_method == ASHIFT_METHOD_LINES)4432 && g->draw_points)4433 {4434 dt_draw_set_color_overlay(cr, FALSE, 1.0);4435 const int nb = (g->current_structure_method == ASHIFT_METHOD_LINES)4436 ? g->lines_count * 24437 : 4;44384439 for(int i = 0; i < nb; i++)4440 {4441 // hide removed lines4442 if(g->lines[i / 2].type != ASHIFT_LINE_HORIZONTAL_SELECTED4443 && g->lines[i / 2].type != ASHIFT_LINE_VERTICAL_SELECTED)4444 continue;4445 if(g->draw_near_point == i)4446 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(4.0) * lwidth);4447 else4448 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) * lwidth);4449 cairo_arc(cr,4450 g->draw_points[i * 2],4451 g->draw_points[i * 2 + 1],4452 DT_PIXEL_APPLY_DPI(5.0) / zoom_scale, 0,4453 2.0 * M_PI);4454 cairo_stroke(cr);4455 }4456 }44574458 // and we draw the selection box if any4459 if(g->isbounding != ASHIFT_BOUNDING_OFF)4460 {4461 double dashed[] = { 4.0, 4.0 };4462 dashed[0] /= zoom_scale;4463 dashed[1] /= zoom_scale;4464 const int len = sizeof(dashed) / sizeof(dashed[0]);44654466 cairo_rectangle(cr, g->lastx * wd, g->lasty * ht, (pzx - g->lastx) * wd,4467 (pzy - g->lasty) * ht);4468 cairo_set_source_rgba(cr, .3, .3, .3, .8);4469 cairo_set_line_width(cr, 1.0 / zoom_scale);4470 cairo_set_dash(cr, dashed, len, 0);4471 cairo_stroke_preserve(cr);4472 cairo_set_source_rgba(cr, .8, .8, .8, .8);4473 cairo_set_dash(cr, dashed, len, 4);4474 cairo_stroke(cr);4475 }44764477 // indicate which area is used for "near"-ness detection when selecting/deselecting lines4478 if(g->near_delta > 0)4479 {4480 double dashed[] = { 4.0, 4.0 };4481 dashed[0] /= zoom_scale;4482 dashed[1] /= zoom_scale;4483 const int len = sizeof(dashed) / sizeof(dashed[0]);44844485 cairo_arc(cr, pzx * wd, pzy * ht, g->near_delta, 0, 2.0 * M_PI);44864487 cairo_set_source_rgba(cr, .3, .3, .3, .8);4488 cairo_set_line_width(cr, 1.0 * lwidth);4489 cairo_set_dash(cr, dashed, len, 0);4490 cairo_stroke_preserve(cr);4491 cairo_set_source_rgba(cr, .8, .8, .8, .8);4492 cairo_set_dash(cr, dashed, len, 4);4493 cairo_stroke(cr);4494 }44954496 cairo_restore(cr);4497}44984499// update the number of selected vertical and horizontal lines4500static void _update_lines_count(const dt_iop_ashift_line_t *lines,4501 const int lines_count,4502 int *vertical_count,4503 int *horizontal_count)4504{4505 int vlines = 0;4506 int hlines = 0;45074508 for(int n = 0; n < lines_count && lines; n++)4509 {4510 if((lines[n].type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_SELECTED)4511 vlines++;4512 else if((lines[n].type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_SELECTED)4513 hlines++;4514 }45154516 *vertical_count = vlines;4517 *horizontal_count = hlines;4518}45194520// determine if we are near a drawn line extrema4521static int _draw_near_point(const float x,4522 const float y,4523 const float zoom_scale,4524 const float *points,4525 const int limit)4526{4527 const float delta = DT_PIXEL_APPLY_DPI(6) / zoom_scale;45284529 for(int i = 0; i < limit; i++)4530 {4531 if(x - points[i * 2] < delta4532 && x - points[i * 2] > -delta4533 && y - points[i * 2 + 1] < delta4534 && y - points[i * 2 + 1] > -delta)4535 return i;4536 }4537 return -1;4538}45394540static void _draw_recompute_line_length(dt_iop_ashift_line_t *line)4541{4542 line->length = sqrt((line->p2[0] - line->p1[0]) * (line->p2[0] - line->p1[0])4543 + (line->p2[1] - line->p1[1]) * (line->p2[1] - line->p1[1]));4544}45454546int mouse_moved(dt_iop_module_t *self,4547 const float pzx,4548 const float pzy,4549 const double pressure,4550 const int which,4551 const float zoom_scale)4552{4553 dt_iop_ashift_gui_data_t *g = self->gui_data;45544555 if(g->straightening)4556 {4557 dt_control_queue_redraw_center();4558 return TRUE;4559 }45604561 if(!self->enabled)4562 return FALSE;45634564 gboolean handled = FALSE;45654566 float wd, ht;4567 dt_dev_get_preview_size(self->dev, &wd, &ht);45684569 if(g->adjust_crop)4570 {4571 const dt_iop_ashift_params_t *p = self->params;45724573 dt_boundingbox_t pts = { pzx, pzy, 1.0f, 1.0f };4574 dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe, self->iop_order,4575 DT_DEV_TRANSFORM_DIR_FORW_INCL, pts, 2);45764577 const float newx = g->crop_cx + (pts[0] - pts[2]) - g->lastx;4578 const float newy = g->crop_cy + (pts[1] - pts[3]) - g->lasty;45794580 crop_adjust(self, p, newx, newy);4581 dt_control_queue_redraw_center();4582 return TRUE;4583 }45844585 // if visibility of lines is switched off or no lines available, we4586 // would normally adjust the crop box but since g->adjust_crop was4587 // FALSE, we have nothing to do4588 if(!g->lines) return FALSE;45894590 // if we are moving a drawn line extrema, we do the change here4591 if(g->draw_point_move)4592 {4593 float pts[2] = { pzx * wd, pzy * ht };4594 if(dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe,4595 self->iop_order,4596 DT_DEV_TRANSFORM_DIR_FORW_INCL, pts, 1))4597 {4598 // first we move the point4599 if(g->draw_near_point >= 0)4600 {4601 const int l = g->draw_near_point / 2;4602 if(g->draw_near_point % 2 == 0)4603 {4604 g->lines[l].p1[0] = pts[0];4605 g->lines[l].p1[1] = pts[1];4606 }4607 else4608 {4609 g->lines[l].p2[0] = pts[0];4610 g->lines[l].p2[1] = pts[1];4611 }4612 _draw_recompute_line_length(&g->lines[l]);4613 }46144615 // for the rectangle method, we need to move the horizontal line too4616 if(g->current_structure_method == ASHIFT_METHOD_QUAD)4617 {4618 if(g->draw_near_point == 0)4619 {4620 g->lines[2].p1[0] = pts[0];4621 g->lines[2].p1[1] = pts[1];4622 _draw_recompute_line_length(&g->lines[2]);4623 }4624 else if(g->draw_near_point == 1)4625 {4626 g->lines[3].p1[0] = pts[0];4627 g->lines[3].p1[1] = pts[1];4628 _draw_recompute_line_length(&g->lines[3]);4629 }4630 else if(g->draw_near_point == 2)4631 {4632 g->lines[2].p2[0] = pts[0];4633 g->lines[2].p2[1] = pts[1];4634 _draw_recompute_line_length(&g->lines[2]);4635 }4636 else if(g->draw_near_point == 3)4637 {4638 g->lines[3].p2[0] = pts[0];4639 g->lines[3].p2[1] = pts[1];4640 _draw_recompute_line_length(&g->lines[3]);4641 }4642 }4643 g->lines_hash++;4644 g->lines_version++;4645 dt_control_queue_redraw_center();4646 }4647 return TRUE;4648 }46494650 // case where we move a drawn line4651 if(g->draw_line_move >= 0)4652 {4653 float pts[2] = { pzx * wd, pzy * ht };4654 if(dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe,4655 self->iop_order,4656 DT_DEV_TRANSFORM_DIR_FORW_INCL, pts, 1))4657 {4658 const float dx = (pts[0] - g->draw_pointmove_x);4659 const float dy = (pts[1] - g->draw_pointmove_y);4660 const int n = g->draw_line_move;4661 g->draw_pointmove_x = pts[0];4662 g->draw_pointmove_y = pts[1];46634664 // we move the line extremas4665 g->lines[n].p1[0] += dx;4666 g->lines[n].p1[1] += dy;4667 g->lines[n].p2[0] += dx;4668 g->lines[n].p2[1] += dy;4669 // sanity check to be sure the extremas don't go outside the image area4670 g->lines[n].p1[0] = CLAMPF(g->lines[n].p1[0], 0.0f, g->lines_in_width);4671 g->lines[n].p1[1] = CLAMPF(g->lines[n].p1[1], 0.0f, g->lines_in_height);4672 g->lines[n].p2[0] = CLAMPF(g->lines[n].p2[0], 0.0f, g->lines_in_width);4673 g->lines[n].p2[1] = CLAMPF(g->lines[n].p2[1], 0.0f, g->lines_in_height);46744675 _draw_recompute_line_length(&g->lines[n]);46764677 // for the rectangle method, we need to move the adjacent lines too4678 if(g->current_structure_method == ASHIFT_METHOD_QUAD)4679 {4680 if(n == 0)4681 {4682 g->lines[2].p1[0] = g->lines[n].p1[0];4683 g->lines[2].p1[1] = g->lines[n].p1[1];4684 g->lines[3].p1[0] = g->lines[n].p2[0];4685 g->lines[3].p1[1] = g->lines[n].p2[1];4686 _draw_recompute_line_length(&g->lines[2]);4687 _draw_recompute_line_length(&g->lines[3]);4688 }4689 else if(n == 1)4690 {4691 g->lines[2].p2[0] = g->lines[n].p1[0];4692 g->lines[2].p2[1] = g->lines[n].p1[1];4693 g->lines[3].p2[0] = g->lines[n].p2[0];4694 g->lines[3].p2[1] = g->lines[n].p2[1];4695 _draw_recompute_line_length(&g->lines[2]);4696 _draw_recompute_line_length(&g->lines[3]);4697 }4698 else if(n == 2)4699 {4700 g->lines[0].p1[0] = g->lines[n].p1[0];4701 g->lines[0].p1[1] = g->lines[n].p1[1];4702 g->lines[1].p1[0] = g->lines[n].p2[0];4703 g->lines[1].p1[1] = g->lines[n].p2[1];4704 _draw_recompute_line_length(&g->lines[0]);4705 _draw_recompute_line_length(&g->lines[1]);4706 }4707 else if(n == 3)4708 {4709 g->lines[0].p2[0] = g->lines[n].p1[0];4710 g->lines[0].p2[1] = g->lines[n].p1[1];4711 g->lines[1].p2[0] = g->lines[n].p2[0];4712 g->lines[1].p2[1] = g->lines[n].p2[1];4713 _draw_recompute_line_length(&g->lines[0]);4714 _draw_recompute_line_length(&g->lines[1]);4715 }4716 }47174718 g->lines_hash++;4719 g->lines_version++;4720 dt_control_queue_redraw_center();4721 }4722 return TRUE;4723 }4724 // if we are in draw mode, we check if we are near a corner4725 if(g->draw_points4726 && ((g->current_structure_method == ASHIFT_METHOD_QUAD4727 && g->lines_count >= 4)4728 || g->current_structure_method == ASHIFT_METHOD_LINES))4729 {4730 const int limit = (g->current_structure_method == ASHIFT_METHOD_LINES)4731 ? g->lines_count * 24732 : 4;47334734 g->draw_near_point = _draw_near_point(pzx * wd, pzy * ht, zoom_scale, g->draw_points, limit);4735 }47364737 // if in rectangle selecting mode adjust "near"-ness of lines according to4738 // the rectangular selection4739 if(g->isbounding != ASHIFT_BOUNDING_OFF)4740 {4741 if(wd >= 1.0 && ht >= 1.0)4742 {4743 // mark lines inside the rectangle4744 _get_bounded_inside(g->points, g->points_idx, g->points_lines_count,4745 pzx * wd, pzy * ht, g->lastx * wd,4746 g->lasty * ht, g->isbounding);4747 }47484749 dt_control_queue_redraw_center();4750 return FALSE;4751 }47524753 // gather information about "near"-ness in g->points_idx4754 _get_near(g->points,4755 g->points_idx,4756 g->points_lines_count,4757 pzx * wd, pzy * ht,4758 g->near_delta,4759 !(g->current_structure_method == ASHIFT_METHOD_LINES4760 || g->current_structure_method == ASHIFT_METHOD_QUAD));47614762 // if we are in sweeping mode iterate over lines as we move the4763 // pointer and change "selected" state.4764 if(g->isdeselecting || g->isselecting)4765 {4766 for(int n = 0;4767 g->selecting_lines_version == g->lines_version && n < g->points_lines_count;4768 n++)4769 {4770 if(g->points_idx[n].near == 0)4771 continue;47724773 if(g->isdeselecting)4774 {4775 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;4776 handled = TRUE;4777 }4778 else if(g->isselecting && g->current_structure_method != ASHIFT_METHOD_LINES)4779 {4780 g->lines[n].type |= ASHIFT_LINE_SELECTED;4781 handled = TRUE;4782 }4783 }4784 }47854786 if(handled)4787 {4788 _update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count);4789 g->lines_version++;4790 g->selecting_lines_version++;4791 }47924793 dt_control_queue_redraw_center();47944795 // if not in sweeping mode we need to pass the event4796 return (g->isdeselecting || g->isselecting);4797}47984799int button_pressed(dt_iop_module_t *self,4800 const float pzx,4801 const float pzy,4802 const double pressure,4803 const int which,4804 const int type,4805 const uint32_t state,4806 const float zoom_scale)4807{4808 dt_iop_ashift_gui_data_t *g = self->gui_data;4809 gboolean handled = FALSE;48104811 // avoid unexpected back to lt mode:4812 if(type == GDK_2BUTTON_PRESS && which == GDK_BUTTON_PRIMARY)4813 return TRUE;48144815 float wd, ht;4816 if(!dt_dev_get_preview_size(self->dev, &wd, &ht)) return 1;48174818 // if we start to draw a straightening line4819 if(!g->lines && which == GDK_BUTTON_SECONDARY)4820 {4821 dt_control_change_cursor("crosshair");4822 g->straightening = TRUE;4823 g->straighten_x = pzx;4824 g->straighten_y = pzy;4825 return TRUE;4826 }48274828 // if no lines available -> potentially adjust crop area4829 if(g->current_structure_method != ASHIFT_METHOD_LINES && !g->lines)4830 {4831 const dt_iop_ashift_params_t *p = self->params;4832 if(p->cropmode == ASHIFT_CROP_ASPECT)4833 {4834 dt_control_change_cursor("pointer");4835 g->adjust_crop = TRUE;48364837 dt_boundingbox_t pts = { pzx, pzy, 1.0f, 1.0f };4838 dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe, self->iop_order,4839 DT_DEV_TRANSFORM_DIR_FORW_INCL, pts, 2);48404841 g->lastx = pts[0] - pts[2];4842 g->lasty = pts[1] - pts[3];4843 g->crop_cx = 0.5f * (g->cl + g->cr);4844 g->crop_cy = 0.5f * (g->ct + g->cb);4845 return TRUE;4846 }4847 else4848 return FALSE;4849 }48504851 // grab a draw corner4852 if((g->current_structure_method == ASHIFT_METHOD_QUAD4853 || g->current_structure_method == ASHIFT_METHOD_LINES)4854 && g->draw_near_point >= 0)4855 {4856 g->draw_point_move = TRUE;4857 g->lastx = pzx;4858 g->lasty = pzy;4859 return TRUE;4860 }48614862 // remember lines version at this stage so we can continuously monitor if the4863 // lines have changed in-between4864 g->selecting_lines_version = g->lines_version;48654866 // if shift button is pressed go into bounding mode (selecting or deselecting4867 // in a rectangle area)4868 if(dt_modifier_is(state, GDK_SHIFT_MASK))4869 {4870 g->lastx = pzx;4871 g->lasty = pzy;48724873 g->isbounding = (which == GDK_BUTTON_SECONDARY) ? ASHIFT_BOUNDING_DESELECT : ASHIFT_BOUNDING_SELECT;4874 dt_control_change_cursor("crosshair");48754876 return TRUE;4877 }48784879 // if we are zoomed out (no panning possible) and we have lines to display we take control4880 const int take_control = (dt_dev_get_zoomed_in() == 1.0f) && (g->points_lines_count > 0);48814882 if(g->current_structure_method == ASHIFT_METHOD_QUAD4883 || g->current_structure_method == ASHIFT_METHOD_LINES)4884 g->near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta_draw");4885 else4886 g->near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta");48874888 // gather information about "near"-ness in g->points_idx4889 _get_near(g->points, g->points_idx, g->points_lines_count,4890 pzx * wd, pzy * ht, g->near_delta,4891 !(g->current_structure_method == ASHIFT_METHOD_QUAD4892 || g->current_structure_method == ASHIFT_METHOD_LINES));48934894 if((g->current_structure_method == ASHIFT_METHOD_LINES && which == GDK_BUTTON_PRIMARY)4895 || g->current_structure_method == ASHIFT_METHOD_QUAD)4896 {4897 // we search the selected line and mark it as the moved line4898 for(int n = 0; n < g->points_lines_count; n++)4899 {4900 if(g->points_idx[n].near)4901 {4902 float pts[2] = { pzx * wd, pzy * ht };4903 dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe,4904 self->iop_order,4905 DT_DEV_TRANSFORM_DIR_FORW_INCL, pts, 1);4906 g->draw_line_move = n;4907 g->draw_pointmove_x = pts[0];4908 g->draw_pointmove_y = pts[1];4909 return TRUE;4910 }4911 }4912 // for the rectangle draw fitting, we don't go further4913 if(g->current_structure_method == ASHIFT_METHOD_QUAD) return FALSE;4914 }4915 else4916 {4917 // iterate over all lines close to the pointer and change "selected" state.4918 // left-click selects and right-click deselects the line4919 for(int n = 0;4920 g->selecting_lines_version == g->lines_version && n < g->points_lines_count;4921 n++)4922 {4923 if(g->points_idx[n].near == 0) continue;49244925 if(which == GDK_BUTTON_SECONDARY)4926 {4927 if(g->current_structure_method != ASHIFT_METHOD_LINES)4928 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;4929 else4930 {4931 // we completely remove the line from the list4932 if(g->lines[n].type == ASHIFT_LINE_HORIZONTAL_SELECTED)4933 {4934 g->horizontal_count--;4935 g->horizontal_weight -= 1.0f;4936 }4937 else4938 {4939 g->vertical_count--;4940 g->vertical_weight -= 1.0f;4941 }49424943 const int count = g->lines_count - 1;4944 dt_iop_ashift_line_t *lines = calloc(count, sizeof(dt_iop_ashift_line_t));4945 if(lines)4946 {4947 int pos = 0;4948 for(int i = 0; i < count; i++)4949 {4950 if(i != n)4951 {4952 lines[pos] = g->lines[i];4953 pos++;4954 }4955 }4956 if(g->lines) free(g->lines);4957 g->lines = lines;4958 g->lines_count = count;4959 }4960 }49614962 handled = TRUE;4963 }4964 else if(g->current_structure_method != ASHIFT_METHOD_LINES)4965 {4966 g->lines[n].type |= ASHIFT_LINE_SELECTED;4967 handled = TRUE;4968 }4969 }4970 }49714972 if(!handled && g->current_structure_method == ASHIFT_METHOD_LINES && which == GDK_BUTTON_PRIMARY)4973 {4974 // start to draw a manual line4975 g->draw_point_move = TRUE;4976 g->lastx = pzx;4977 g->lasty = pzy;49784979 // we instantiate a new line with both extrema at the current position4980 // and enable the "move point" mode with the second extrema4981 float pts[2] = { pzx * wd, pzy * ht };4982 dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe, self->iop_order,4983 DT_DEV_TRANSFORM_DIR_FORW_INCL, pts, 1);49844985 const int count = g->lines_count + 1;4986 // if count > MAX_SAVED_LINES we alert that the next lines won't4987 // be saved in params but they still may be used for the current4988 // section (that's why we still allow them)4989 if(count > MAX_SAVED_LINES)4990 dt_control_log(_("only %d lines can be saved in parameters"), MAX_SAVED_LINES);49914992 dt_iop_ashift_line_t *lines = malloc(sizeof(dt_iop_ashift_line_t) * count);4993 for(int i = 0; i < g->lines_count; i++)4994 {4995 lines[i] = g->lines[i];4996 }4997 if(g->lines) free(g->lines);4998 g->lines = lines;4999 g->lines_count = count;5000 _draw_basic_line(&g->lines[count - 1],5001 pts[0],5002 pts[1],5003 pts[0],5004 pts[1],5005 ASHIFT_LINE_VERTICAL_SELECTED);50065007 g->vertical_count++;5008 g->vertical_weight += 1.0f;5009 g->draw_near_point = g->lines_count * 2 - 1;5010 return TRUE;5011 }50125013 // we switch into sweeping mode either if we anyhow take control5014 // or if cursor was close to a line when button was pressed. in other5015 // cases we hand over the event (for image panning)5016 if((take_control || handled) && which == GDK_BUTTON_SECONDARY)5017 {5018 dt_control_change_cursor("not-allowed");5019 g->isdeselecting = 1;5020 }5021 else if(take_control || handled)5022 {5023 dt_control_change_cursor("cell");5024 g->isselecting = 1;5025 }50265027 if(handled)5028 {5029 _update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count);5030 g->lines_version++;5031 g->selecting_lines_version++;5032 }50335034 return (take_control || handled);5035}50365037int button_released(dt_iop_module_t *self,5038 const float pzx,5039 const float pzy,5040 const int which,5041 const uint32_t state,5042 const float zoom_scale)5043{5044 dt_iop_ashift_gui_data_t *g = self->gui_data;5045 float wd, ht;5046 dt_dev_get_preview_size(self->dev, &wd, &ht);50475048 dt_control_change_cursor("default");50495050 // ends eventual line move5051 if(g->draw_line_move >= 0)5052 {5053 g->draw_line_move = -1;5054 // we save the lines in params5055 _draw_save_lines_to_params(self);5056 return TRUE;5057 }50585059 if(g->straightening)5060 {5061 g->straightening = FALSE;50625063 const float bzx = g->straighten_x, bzy = g->straighten_y;5064 const float angle = _calculate_straightening(self, pzx, pzy, bzx, bzy, wd, ht, zoom_scale);5065 if(angle == 0.0f) return TRUE;50665067 const float n = dt_bauhaus_slider_get(g->rotation) - angle;5068 dt_bauhaus_slider_set(g->rotation, n);5069 dt_toast_log(_("rotation adjusted by %3.2f° to %3.2f°"), - angle, n);5070 return TRUE;5071 }50725073 // release a drawn corner5074 if(g->draw_point_move)5075 {5076 // we determine the vertical/horizontal line type (that may have changed)5077 // we also save the lines in params5078 // points move are done directly in mouse_move routine5079 for(int l = 0; l < g->lines_count; l++)5080 {5081 const dt_iop_ashift_linetype_t old_linetype = g->lines[l].type;5082 _draw_retrieve_line_type(&g->lines[l]);50835084 if(g->lines[l].type != old_linetype5085 && g->lines[l].type == ASHIFT_LINE_VERTICAL_SELECTED)5086 {5087 g->vertical_count++;5088 g->vertical_weight += 1.0f;5089 g->horizontal_count--;5090 g->horizontal_weight -= 1.0f;5091 }5092 else if(g->lines[l].type != old_linetype)5093 {5094 g->horizontal_count++;5095 g->horizontal_weight += 1.0f;5096 g->vertical_count--;5097 g->vertical_weight -= 1.0f;5098 }50995100 g->lines_version++;5101 }5102 g->draw_point_move = FALSE;5103 g->draw_near_point = -1;51045105 // we save the lines in params5106 _draw_save_lines_to_params(self);51075108 dt_control_queue_redraw_center();5109 return TRUE;5110 }51115112 if(g->adjust_crop)5113 {5114 // stop adjust crop5115 g->adjust_crop = FALSE;5116 dt_iop_ashift_params_t *p = self->params;5117 _swap_shadow_crop_box(p,g); // temporarily update the crop box in p5118 dt_dev_add_history_item(darktable.develop, self, TRUE);5119 _swap_shadow_crop_box(p,g); // restore p5120 }51215122 // finalize the isbounding mode5123 // if user has released the shift button in-between -> do nothing5124 if(g->isbounding != ASHIFT_BOUNDING_OFF && dt_modifier_is(state, GDK_SHIFT_MASK))5125 {5126 gboolean handled = FALSE;51275128 // we compute the rectangle selection5129 if(wd >= 1.0 && ht >= 1.0)5130 {5131 // mark lines inside the rectangle5132 _get_bounded_inside(g->points, g->points_idx, g->points_lines_count,5133 pzx * wd, pzy * ht, g->lastx * wd,5134 g->lasty * ht, g->isbounding);51355136 // select or deselect lines within the rectangle according to isbounding state5137 for(int n = 0;5138 g->selecting_lines_version == g->lines_version && n < g->points_lines_count;5139 n++)5140 {5141 if(g->points_idx[n].bounded == 0) continue;51425143 if(g->isbounding == ASHIFT_BOUNDING_DESELECT)5144 {5145 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;5146 handled = TRUE;5147 }5148 else if(g->current_structure_method != ASHIFT_METHOD_LINES)5149 {5150 g->lines[n].type |= ASHIFT_LINE_SELECTED;5151 handled = TRUE;5152 }5153 }51545155 if(handled)5156 {5157 _update_lines_count(g->lines,5158 g->lines_count,5159 &g->vertical_count,5160 &g->horizontal_count);5161 g->lines_version++;5162 g->selecting_lines_version++;5163 }51645165 dt_control_queue_redraw_center();5166 }5167 }51685169 // end of sweeping/isbounding mode5170 g->isselecting = g->isdeselecting = 0;5171 g->isbounding = ASHIFT_BOUNDING_OFF;5172 g->near_delta = 0;5173 g->lastx = g->lasty = -1.0f;5174 g->crop_cx = g->crop_cy = -1.0f;51755176 // if we have deselected drawn lines, we need to update params5177 if(g->current_structure_method == ASHIFT_METHOD_LINES && which == GDK_BUTTON_SECONDARY)5178 {5179 // we save the lines in params5180 _draw_save_lines_to_params(self);5181 }51825183 return 0;5184}51855186int scrolled(dt_iop_module_t *self,5187 const float pzx,5188 const float pzy,5189 const int up,5190 const uint32_t state)5191{5192 dt_iop_ashift_gui_data_t *g = self->gui_data;51935194 // do nothing if visibility of lines is switched off or no lines available5195 if(!g->lines) return FALSE;51965197 if(g->near_delta > 0 && (g->isdeselecting || g->isselecting))5198 {5199 gboolean handled = FALSE;52005201 float wd, ht;5202 dt_dev_get_preview_size(self->dev, &wd, &ht);52035204 float near_delta = 5.0f;5205 if(g->current_structure_method == ASHIFT_METHOD_QUAD5206 || g->current_structure_method == ASHIFT_METHOD_LINES)5207 near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta_draw");5208 else5209 near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta");5210 const float amount = up ? 1.25f : 0.8f;5211 near_delta = MAX(4.0f, MIN(near_delta * amount, 100.0f));5212 if(g->current_structure_method == ASHIFT_METHOD_QUAD5213 || g->current_structure_method == ASHIFT_METHOD_LINES)5214 dt_conf_set_float("plugins/darkroom/ashift/near_delta_draw", near_delta);5215 else5216 dt_conf_set_float("plugins/darkroom/ashift/near_delta", near_delta);5217 g->near_delta = near_delta;52185219 // for drawn structure, we stop here5220 if(g->current_structure_method == ASHIFT_METHOD_QUAD5221 || g->current_structure_method == ASHIFT_METHOD_LINES)5222 return TRUE;52235224 // gather information about "near"-ness in g->points_idx5225 _get_near(g->points,5226 g->points_idx,5227 g->points_lines_count,5228 pzx * wd,5229 pzy * ht,5230 g->near_delta,5231 TRUE);52325233 // iterate over all lines close to the pointer and change "selected" state.5234 for(int n = 0;5235 g->selecting_lines_version == g->lines_version && n < g->points_lines_count;5236 n++)5237 {5238 if(g->points_idx[n].near == 0)5239 continue;52405241 if(g->isdeselecting)5242 {5243 g->lines[n].type &= ~ASHIFT_LINE_SELECTED;5244 handled = TRUE;5245 }5246 else if(g->isselecting && g->current_structure_method != ASHIFT_METHOD_LINES)5247 {5248 g->lines[n].type |= ASHIFT_LINE_SELECTED;5249 handled = TRUE;5250 }52515252 handled = TRUE;5253 }52545255 if(handled)5256 {5257 _update_lines_count(g->lines,5258 g->lines_count,5259 &g->vertical_count,5260 &g->horizontal_count);5261 g->lines_version++;5262 g->selecting_lines_version++;5263 }52645265 dt_control_queue_redraw_center();5266 return TRUE;5267 }52685269 return FALSE;5270}52715272void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)5273{5274 dt_iop_ashift_params_t *p = self->params;5275 dt_iop_ashift_gui_data_t *g = self->gui_data;527652775278 model_probe(self, p, g->lastfit);527952805281 if(w != g->cropmode) dt_dev_invalidate_all(self->dev);5282 if(g->buf_height > 0 && g->buf_width > 0)5283 {5284 do_crop(self, p);5285 _commit_crop_box(p, g);5286 }5287 else5288 {5289 g->jobcode = ASHIFT_JOBCODE_DO_CROP;5290 }52915292 if(w == g->mode)5293 {5294 gtk_widget_set_visible(g->specifics, p->mode == ASHIFT_MODE_SPECIFIC);5295 }5296}52975298void gui_reset(dt_iop_module_t *self)5299{5300 dt_iop_ashift_params_t *p = self->params;5301 /* reset eventual remaining structures */5302 _do_clean_structure(self, p, FALSE);5303 _gui_update_structure_states(self, NULL);5304 // force to reprocess the preview, otherwise the buffer is ko5305 dt_dev_invalidate_all(self->dev);5306 dt_dev_pixelpipe_cache_flush(self->dev->preview_pipe);5307}53085309static void cropmode_callback(GtkWidget *widget, dt_iop_module_t *self)5310{5311 DT_GUARD_GUI_UPDATE();5312 dt_iop_ashift_params_t *p = self->params;5313 dt_iop_ashift_gui_data_t *g = self->gui_data;53145315 dt_conf_set_int("plugins/darkroom/ashift/autocrop_value",5316 dt_bauhaus_combobox_get(g->cropmode));5317 _swap_shadow_crop_box(p,g); //temporarily update real crop box5318 dt_dev_add_history_item(darktable.develop, self, TRUE);5319 _swap_shadow_crop_box(p,g);5320}53215322static void _event_fit_v_button_clicked(GtkGestureSingle *gesture,5323 gint n_press,5324 gdouble x,5325 gdouble y,5326 dt_iop_module_t *self)5327{5328 DT_GUARD_GUI_UPDATE();53295330 if(dt_gui_current_button(gesture) == GDK_BUTTON_PRIMARY)5331 {5332 dt_iop_ashift_params_t *p = self->params;5333 dt_iop_ashift_gui_data_t *g = self->gui_data;53345335 const GdkModifierType state = dt_gui_current_state(gesture);5336 const int control = dt_modifiers_include(state, GDK_CONTROL_MASK);5337 const int shift = dt_modifiers_include(state, GDK_SHIFT_MASK);53385339 dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE;53405341 if(control)5342 g->lastfit = fitaxis = ASHIFT_FIT_ROTATION_VERTICAL_LINES;5343 else if(shift)5344 g->lastfit = fitaxis = ASHIFT_FIT_VERTICALLY_NO_ROTATION;5345 else5346 g->lastfit = fitaxis = ASHIFT_FIT_VERTICALLY;53475348 dt_iop_request_focus(self);53495350 if(self->enabled)5351 {5352 // module is enable -> we process directly5353 do_fit(self, p, fitaxis);5354 }5355 else5356 {5357 // module is not enabled -> invoke it and queue the job to be processed once5358 // the preview image is ready5359 g->jobcode = ASHIFT_JOBCODE_FIT;5360 g->jobparams = g->lastfit = fitaxis;5361 }53625363 // temporarily update real crop box5364 _swap_shadow_crop_box(p, g);5365 //also calls dt_control_queue_redraw_center5366 dt_dev_add_history_item(darktable.develop, self, TRUE);5367 _swap_shadow_crop_box(p, g);5368 5369 }5370}53715372static void _event_fit_h_button_clicked(GtkGestureSingle *gesture,5373 gint n_press,5374 gdouble x,5375 gdouble y,5376 dt_iop_module_t *self)5377{5378 DT_GUARD_GUI_UPDATE();53795380 if(dt_gui_current_button(gesture) == GDK_BUTTON_PRIMARY)5381 {5382 dt_iop_ashift_params_t *p = self->params;5383 dt_iop_ashift_gui_data_t *g = self->gui_data;53845385 const GdkModifierType state = dt_gui_current_state(gesture);5386 const int control = dt_modifiers_include(state, GDK_CONTROL_MASK);5387 const int shift = dt_modifiers_include(state, GDK_SHIFT_MASK);53885389 dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE;53905391 if(control)5392 g->lastfit = fitaxis = ASHIFT_FIT_ROTATION_HORIZONTAL_LINES;5393 else if(shift)5394 g->lastfit = fitaxis = ASHIFT_FIT_HORIZONTALLY_NO_ROTATION;5395 else5396 g->lastfit = fitaxis = ASHIFT_FIT_HORIZONTALLY;53975398 dt_iop_request_focus(self);53995400 if(self->enabled)5401 {5402 // module is enable -> we process directly5403 do_fit(self, p, fitaxis);5404 }5405 else5406 {5407 // module is not enabled -> invoke it and queue the job to be processed once5408 // the preview image is ready5409 g->jobcode = ASHIFT_JOBCODE_FIT;5410 g->jobparams = g->lastfit = fitaxis;5411 }54125413 // temporarily update real crop box5414 _swap_shadow_crop_box(p, g);5415 //also calls dt_control_queue_redraw_center5416 dt_dev_add_history_item(darktable.develop, self, TRUE);5417 _swap_shadow_crop_box(p, g);5418 5419 }5420}54215422static void _event_fit_both_button_clicked(GtkGestureSingle *gesture,5423 gint n_press,5424 gdouble x,5425 gdouble y,5426 dt_iop_module_t *self)5427{5428 DT_GUARD_GUI_UPDATE();54295430 if(dt_gui_current_button(gesture) == GDK_BUTTON_PRIMARY)5431 {5432 dt_iop_ashift_params_t *p = self->params;5433 dt_iop_ashift_gui_data_t *g = self->gui_data;54345435 const GdkModifierType state = dt_gui_current_state(gesture);5436 const int control = dt_modifiers_include(state, GDK_CONTROL_MASK);5437 const int shift = dt_modifiers_include(state, GDK_SHIFT_MASK);54385439 dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE;54405441 if(control && shift)5442 fitaxis = ASHIFT_FIT_BOTH;5443 else if(control)5444 fitaxis = ASHIFT_FIT_ROTATION_BOTH_LINES;5445 else if(shift)5446 fitaxis = ASHIFT_FIT_BOTH_NO_ROTATION;5447 else5448 fitaxis = ASHIFT_FIT_BOTH_SHEAR;54495450 dt_iop_request_focus(self);54515452 if(self->enabled)5453 {5454 // module is enable -> we process directly5455 do_fit(self, p, fitaxis);5456 }5457 else5458 {5459 // module is not enabled -> invoke it and queue the job to be processed once5460 // the preview image is ready5461 g->jobcode = ASHIFT_JOBCODE_FIT;5462 g->jobparams = g->lastfit = fitaxis;5463 }54645465 // temporarily update real crop box5466 _swap_shadow_crop_box(p, g);5467 //also calls dt_control_queue_redraw_center5468 dt_dev_add_history_item(darktable.develop, self, TRUE);5469 _swap_shadow_crop_box(p, g);5470 5471 }5472}54735474static void _event_structure_auto_clicked(GtkGestureSingle *gesture,5475 gint n_press,5476 gdouble x,5477 gdouble y,5478 dt_iop_module_t *self)5479{5480 GtkWidget *widget = dt_gui_get_widget(gesture);5481 DT_GUARD_GUI_UPDATE();54825483 if(dt_gui_current_button(gesture) == GDK_BUTTON_PRIMARY)5484 {5485 dt_iop_ashift_params_t *p = self->params;5486 dt_iop_ashift_gui_data_t *g = self->gui_data;54875488 _do_clean_structure(self, p, TRUE);54895490 const GdkModifierType state = dt_gui_current_state(gesture);5491 const int control = dt_modifiers_include(state, GDK_CONTROL_MASK);5492 const int shift = dt_modifiers_include(state, GDK_SHIFT_MASK);54935494 dt_iop_ashift_enhance_t enhance;54955496 if(control && shift)5497 enhance = ASHIFT_ENHANCE_EDGES | ASHIFT_ENHANCE_DETAIL;5498 else if(shift)5499 enhance = ASHIFT_ENHANCE_DETAIL;5500 else if(control)5501 enhance = ASHIFT_ENHANCE_EDGES;5502 else5503 enhance = ASHIFT_ENHANCE_NONE;55045505 // if the button is unselected, we don't go further5506 if(enhance == ASHIFT_ENHANCE_NONE5507 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))5508 {5509 _gui_update_structure_states(self, widget);5510 dt_control_queue_redraw_center();5511 return;5512 }5513 else5514 {5515 // force the button to be untoggled, so the update routine can enable it5516 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);5517 _gui_update_structure_states(self, widget);5518 }55195520 g->current_structure_method = ASHIFT_METHOD_AUTO;55215522 dt_iop_request_focus(self);55235524 if(self->enabled)5525 {5526 // module is enabled -> process directly5527 (void)_do_get_structure_auto(self, p, enhance);5528 }5529 else5530 {5531 // module is not enabled -> invoke it and queue the job to be processed once5532 // the preview image is ready5533 dt_dev_invalidate_all(self->dev);5534 g->jobcode = ASHIFT_JOBCODE_GET_STRUCTURE;5535 g->jobparams = enhance;5536 }55375538 // also calls dt_control_queue_redraw_center5539 dt_dev_add_history_item(darktable.develop, self, TRUE);5540 5541 }5542}55435544// routine that is called after preview image has been processed. we5545// use it to perform structure collection or fitting in case those5546// have been triggered while the module had not yet been enabled5547static void _event_process_after_preview_callback(gpointer instance, dt_iop_module_t *self)5548{5549 dt_iop_ashift_params_t *p = self->params;5550 dt_iop_ashift_gui_data_t *g = self->gui_data;55515552 const dt_iop_ashift_jobcode_t jobcode = g->jobcode;5553 const int jobparams = g->jobparams;55545555 // purge5556 g->jobcode = ASHIFT_JOBCODE_NONE;5557 g->jobparams = 0;55585559 DT_GUARD_GUI_UPDATE();55605561 switch(jobcode)5562 {5563 case ASHIFT_JOBCODE_DO_CROP:5564 // the signal can come from a preview run that started before the5565 // module was enabled and so never filled g->buf; do_crop() would5566 // return without cropping, so keep the request for the next run5567 dt_iop_gui_enter_critical_section(self);5568 const gboolean buf_empty = g->buf_width == 0 || g->buf_height == 0;5569 dt_iop_gui_leave_critical_section(self);5570 if(buf_empty)5571 {5572 g->jobcode = ASHIFT_JOBCODE_DO_CROP;5573 break;5574 }5575 do_crop(self, p);5576 _commit_crop_box(p, g);5577 // save all that5578 _swap_shadow_crop_box(p, g); // temporarily update real crop box5579 dt_dev_add_history_item(darktable.develop, self, TRUE);5580 _swap_shadow_crop_box(p, g);5581 dt_dev_invalidate_all(darktable.develop);5582 break;55835584 case ASHIFT_JOBCODE_GET_STRUCTURE_QUAD:5585 _do_get_structure_quad(self);5586 break;55875588 case ASHIFT_JOBCODE_GET_STRUCTURE_LINES:5589 _do_get_structure_lines(self);5590 break;55915592 case ASHIFT_JOBCODE_GET_STRUCTURE:5593 (void)_do_get_structure_auto(self, p, (dt_iop_ashift_enhance_t)jobparams);5594 break;55955596 case ASHIFT_JOBCODE_FIT:5597 do_fit(self, p, (dt_iop_ashift_fitaxis_t)jobparams);5598 dt_dev_add_history_item(darktable.develop, self, TRUE);5599 break;56005601 case ASHIFT_JOBCODE_NONE:5602 default:5603 break;5604 }56055606 dt_control_queue_redraw_center();5607}56085609void commit_params(dt_iop_module_t *self,5610 dt_iop_params_t *p1,5611 dt_dev_pixelpipe_t *pipe,5612 dt_dev_pixelpipe_iop_t *piece)5613{5614 const dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)p1;5615 dt_iop_ashift_data_t *d = piece->data;56165617 d->rotation = p->rotation;5618 d->lensshift_v = p->lensshift_v;5619 d->lensshift_h = p->lensshift_h;5620 d->shear = p->shear;5621 d->f_length_kb = (p->mode == ASHIFT_MODE_GENERIC)5622 ? DEFAULT_F_LENGTH5623 : p->f_length * p->crop_factor;56245625 d->orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr;5626 d->aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect;56275628 if(dt_iop_has_focus(self)5629 || dt_isnan(p->cl)5630 || dt_isnan(p->cr)5631 || dt_isnan(p->ct)5632 || dt_isnan(p->cb))5633 {5634 // if gui has focus we want to see the full uncropped image5635 d->cl = 0.0f;5636 d->cr = 1.0f;5637 d->ct = 0.0f;5638 d->cb = 1.0f;5639 }5640 else5641 {5642 d->cl = p->cl;5643 d->cr = p->cr;5644 d->ct = p->ct;5645 d->cb = p->cb;5646 }5647}56485649void init_pipe(dt_iop_module_t *self,5650 dt_dev_pixelpipe_t *pipe,5651 dt_dev_pixelpipe_iop_t *piece)5652{5653 dt_iop_ashift_data_t *d = calloc(1, sizeof(dt_iop_ashift_data_t));5654 piece->data = (void *)d;5655}56565657void cleanup_pipe(dt_iop_module_t *self,5658 dt_dev_pixelpipe_t *pipe,5659 dt_dev_pixelpipe_iop_t *piece)5660{5661 free(piece->data);5662 piece->data = NULL;5663}56645665void gui_update(dt_iop_module_t *self)5666{5667 dt_iop_ashift_gui_data_t *g = self->gui_data;5668 const dt_iop_ashift_params_t *p = self->params;56695670 gtk_widget_set_visible(g->specifics, p->mode == ASHIFT_MODE_SPECIFIC);56715672 // copy crop box into shadow variables5673 _shadow_crop_box(p,g);56745675 dt_gui_update_collapsible_section(&g->cs);5676}56775678void reload_defaults(dt_iop_module_t *self)5679{5680 // our module is disabled by default5681 self->default_enabled = FALSE;56825683 int isflipped = 0;5684 float f_length = DEFAULT_F_LENGTH;5685 float crop_factor = 1.0f;56865687 // try to get information on orientation, focal length and crop5688 // factor from image data5689 if(self->dev)5690 {5691 const dt_image_t *img = &self->dev->image_storage;5692 // orientation only needed as a-priori information to correctly5693 // label some sliders before pixelpipe has been set up. later we5694 // will get a definite result by assessing the pixelpipe5695 isflipped = (img->orientation == ORIENTATION_ROTATE_CCW_90_DEG5696 || img->orientation == ORIENTATION_ROTATE_CW_90_DEG);56975698 // focal length should be available in exif data if lens is5699 // electronically coupled to the camera5700 f_length = (img->exif_focal_length > 0.0f && img->exif_focal_length <= 1000000.0f)5701 ? img->exif_focal_length : f_length;5702 // crop factor of the camera is often not available and user will5703 // need to set it manually in the gui5704 crop_factor = (img->exif_crop > 0.0f && img->exif_crop <= 1000.0f) ? img->exif_crop : crop_factor;5705 }57065707 // init defaults:5708 dt_iop_ashift_params_t *d = self->default_params;5709 d->f_length = f_length;5710 d->crop_factor = crop_factor;5711 d->cropmode = dt_conf_get_int("plugins/darkroom/ashift/autocrop_value");57125713 // reset gui elements5714 dt_iop_ashift_gui_data_t *g = self->gui_data;5715 if(g)5716 {57175718 char string_v[256];5719 char string_h[256];57205721 snprintf(string_v, sizeof(string_v),5722 _("lens shift (%s)"), isflipped ? _("horizontal") : _("vertical"));5723 snprintf(string_h, sizeof(string_h),5724 _("lens shift (%s)"), isflipped ? _("vertical") : _("horizontal"));57255726 dt_bauhaus_widget_set_label(g->lensshift_v, NULL, string_v);5727 dt_bauhaus_widget_set_label(g->lensshift_h, NULL, string_h);57285729 dt_bauhaus_slider_set_default(g->f_length, f_length);5730 dt_bauhaus_slider_set_default(g->crop_factor, crop_factor);57315732 dt_iop_gui_enter_critical_section(self);5733 dt_free_align(g->buf);5734 g->buf = NULL;5735 g->buf_width = 0;5736 g->buf_height = 0;5737 g->buf_x_off = 0;5738 g->buf_y_off = 0;5739 g->buf_scale = 1.0f;5740 g->buf_hash = DT_INVALID_HASH;5741 g->isflipped = -1;5742 g->lastfit = ASHIFT_FIT_NONE;5743 dt_iop_gui_leave_critical_section(self);57445745 g->fitting = 0;5746 free(g->lines);5747 g->lines = NULL;5748 g->lines_count =0;5749 g->horizontal_count = 0;5750 g->vertical_count = 0;5751 g->grid_hash = DT_INVALID_HASH;5752 g->lines_hash = DT_INVALID_HASH;5753 g->rotation_range = ROTATION_RANGE_SOFT;5754 g->lensshift_v_range = LENSSHIFT_RANGE_SOFT;5755 g->lensshift_h_range = LENSSHIFT_RANGE_SOFT;5756 g->shear_range = SHEAR_RANGE_SOFT;5757 g->lines_version = 0;5758 g->isselecting = 0;5759 g->isdeselecting = 0;5760 g->isbounding = ASHIFT_BOUNDING_OFF;5761 g->near_delta = 0;5762 g->selecting_lines_version = 0;57635764 free(g->points);5765 g->points = NULL;5766 free(g->points_idx);5767 g->points_idx = NULL;5768 g->points_lines_count = 0;5769 g->points_version = 0;57705771 g->jobcode = ASHIFT_JOBCODE_NONE;5772 g->jobparams = 0;5773 g->adjust_crop = FALSE;5774 g->lastx = g->lasty = -1.0f;5775 g->crop_cx = g->crop_cy = 1.0f;57765777 g->current_structure_method = ASHIFT_METHOD_NONE;5778 g->draw_line_move = -1;5779 g->draw_near_point = -1;5780 g->draw_point_move = FALSE;57815782 _gui_update_structure_states(self, NULL);5783 }5784}578557865787void init_global(dt_iop_module_so_t *self)5788{5789 dt_iop_ashift_global_data_t *gd = malloc(sizeof(dt_iop_ashift_global_data_t));5790 self->data = gd;57915792 const int program = 2; // basic.cl, from programs.conf5793 gd->kernel_ashift_bilinear = dt_opencl_create_kernel(program, "ashift_bilinear");5794 gd->kernel_ashift_bicubic = dt_opencl_create_kernel(program, "ashift_bicubic");5795 gd->kernel_ashift_lanczos2 = dt_opencl_create_kernel(program, "ashift_lanczos2");5796 gd->kernel_ashift_lanczos3 = dt_opencl_create_kernel(program, "ashift_lanczos3");5797}57985799void cleanup_global(dt_iop_module_so_t *self)5800{5801 const dt_iop_ashift_global_data_t *gd = self->data;5802 dt_opencl_free_kernel(gd->kernel_ashift_bilinear);5803 dt_opencl_free_kernel(gd->kernel_ashift_bicubic);5804 dt_opencl_free_kernel(gd->kernel_ashift_lanczos2);5805 dt_opencl_free_kernel(gd->kernel_ashift_lanczos3);5806 free(self->data);5807 self->data = NULL;5808}58095810// adjust labels of lens shift parameters according to flip status of image5811static gboolean _event_draw(GtkWidget *widget,5812 cairo_t *cr,5813 dt_iop_module_t *self)5814{5815 const dt_iop_ashift_gui_data_t *g = self->gui_data;5816 DT_GUARD_GUI_UPDATE(FALSE);58175818 dt_iop_gui_enter_critical_section(self);5819 const int isflipped = g->isflipped;5820 dt_iop_gui_leave_critical_section(self);58215822 if(isflipped == -1) return FALSE;58235824 char string_v[256];5825 char string_h[256];58265827 snprintf(string_v, sizeof(string_v),5828 _("lens shift (%s)"), isflipped ? _("horizontal") : _("vertical"));5829 snprintf(string_h, sizeof(string_h),5830 _("lens shift (%s)"), isflipped ? _("vertical") : _("horizontal"));58315832 DT_ENTER_GUI_UPDATE();5833 dt_bauhaus_widget_set_label(g->lensshift_v, NULL, string_v);5834 dt_bauhaus_widget_set_label(g->lensshift_h, NULL, string_h);5835 DT_LEAVE_GUI_UPDATE();58365837 return FALSE;5838}58395840void gui_focus(dt_iop_module_t *self, const gboolean in)5841{5842 darktable.develop->history_postpone_invalidate = in5843 && dt_dev_modulegroups_test_activated(darktable.develop);58445845 if(self->enabled)5846 {5847 dt_iop_ashift_params_t *p = self->params;5848 dt_iop_ashift_gui_data_t *g = self->gui_data;5849 if(in)5850 {5851 _shadow_crop_box(p,g);5852 dt_control_queue_redraw_center();5853 }5854 else5855 {5856 _commit_crop_box(p, g);5857 _gui_update_structure_states(self, NULL);5858 _do_clean_structure(self, p, TRUE);5859 }5860 }5861}58625863static float log2_curve(const float inval, const dt_bauhaus_curve_t dir)5864{5865 float outval;5866 if(dir == DT_BAUHAUS_SET)5867 {5868 outval = log2f(inval * 1.5f + 0.5f) / 2.0f + 0.5f;5869 }5870 else5871 {5872 outval = (exp2f(inval * 2.0 - 1.0) - 0.5f) / 1.5f;5873 }5874 return outval;5875}58765877static void _event_structure_quad_clicked(GtkGestureSingle *gesture,5878 gint n_press,5879 gdouble x,5880 gdouble y,5881 dt_iop_module_t *self)5882{5883 dt_iop_ashift_gui_data_t *g = self->gui_data;5884 DT_GUARD_GUI_UPDATE();58855886 dt_iop_request_focus(self);58875888 if(self->enabled)5889 {5890 // module is enabled -> process directly5891 _do_get_structure_quad(self);5892 }5893 else5894 {5895 // module is not enabled -> invoke it and queue the job to be5896 // processed once the preview image is ready5897 dt_dev_invalidate_all(self->dev);5898 g->jobcode = ASHIFT_JOBCODE_GET_STRUCTURE_QUAD;5899 }59005901 // also calls dt_control_queue_redraw_center5902 dt_dev_add_history_item(darktable.develop, self, TRUE);590359045905}59065907static void _event_structure_lines_clicked(GtkGestureSingle *gesture,5908 gint n_press,5909 gdouble x,5910 gdouble y,5911 dt_iop_module_t *self)5912{5913 dt_iop_ashift_gui_data_t *g = self->gui_data;5914 DT_GUARD_GUI_UPDATE();59155916 dt_iop_request_focus(self);59175918 if(self->enabled)5919 {5920 // module is enabled -> process directly5921 _do_get_structure_lines(self);5922 }5923 else5924 {5925 // module is not enabled -> invoke it and queue the job to be processed once5926 // the preview image is ready5927 dt_dev_invalidate_all(self->dev);5928 g->jobcode = ASHIFT_JOBCODE_GET_STRUCTURE_LINES;5929 }59305931 // also calls dt_control_queue_redraw_center5932 dt_dev_add_history_item(darktable.develop, self, TRUE);593359345935}59365937// the structure buttons implement radio-button behavior by managing all5938// toggle states themselves, so keep GTK from flipping the button on release:5939// claim the sequence in CAPTURE phase (same pattern as dt_iop_togglebutton_new)5940// and the button's own bubble-phase gesture never emits "clicked"5941static GtkGestureSingle *_connect_structure_button(GtkWidget *widget,5942 GCallback pressed,5943 dt_iop_module_t *self)5944{5945 GtkGestureSingle *gesture = dt_gui_connect_click(widget, pressed, NULL, self);5946 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(gesture),5947 GTK_PHASE_CAPTURE);5948 g_signal_connect(G_OBJECT(gesture), "begin", G_CALLBACK(dt_gui_gesture_claim), NULL);5949 return gesture;5950}59515952void gui_init(dt_iop_module_t *self)5953{5954 dt_iop_ashift_gui_data_t *g = IOP_GUI_ALLOC(ashift);59555956 //not actually needed, we're the only one with a pointer to this instance5957 dt_iop_gui_enter_critical_section(self);5958 g->buf = NULL;5959 g->buf_width = 0;5960 g->buf_height = 0;5961 g->buf_x_off = 0;5962 g->buf_y_off = 0;5963 g->buf_scale = 1.0f;5964 g->buf_hash = DT_INVALID_HASH;5965 g->isflipped = -1;5966 g->lastfit = ASHIFT_FIT_NONE;5967 dt_iop_gui_leave_critical_section(self);59685969 g->fitting = 0;5970 g->lines = NULL;5971 g->lines_count = 0;5972 g->vertical_count = 0;5973 g->horizontal_count = 0;5974 g->lines_version = 0;5975 g->points = NULL;5976 g->points_idx = NULL;5977 g->points_lines_count = 0;5978 g->points_version = 0;5979 g->grid_hash = DT_INVALID_HASH;5980 g->lines_hash = DT_INVALID_HASH;5981 g->rotation_range = ROTATION_RANGE_SOFT;5982 g->lensshift_v_range = LENSSHIFT_RANGE_SOFT;5983 g->lensshift_h_range = LENSSHIFT_RANGE_SOFT;5984 g->shear_range = SHEAR_RANGE_SOFT;5985 g->isselecting = 0;5986 g->isdeselecting = 0;5987 g->isbounding = ASHIFT_BOUNDING_OFF;5988 g->near_delta = 0;5989 g->selecting_lines_version = 0;59905991 g->jobcode = ASHIFT_JOBCODE_NONE;5992 g->jobparams = 0;5993 g->adjust_crop = FALSE;5994 g->lastx = g->lasty = -1.0f;5995 g->crop_cx = g->crop_cy = 1.0f;59965997 g->draw_near_point = -1;5998 g->draw_line_move = -1;59996000 g->rotation = dt_bauhaus_slider_from_params(self, N_("rotation"));6001 dt_bauhaus_slider_set_format(g->rotation, "°");6002 dt_bauhaus_slider_set_factor(g->rotation, -1.f);6003 dt_bauhaus_slider_set_soft_range(g->rotation, -ROTATION_RANGE, ROTATION_RANGE);6004 dt_action_t *ac = dt_action_widget(g->rotation);6005 dt_shortcut_register(ac, 0, DT_ACTION_EFFECT_UP, GDK_KEY_bracketleft, GDK_MOD1_MASK);6006 dt_shortcut_register(ac, 0, DT_ACTION_EFFECT_DOWN, GDK_KEY_bracketright, GDK_MOD1_MASK);6007 dt_shortcut_register(ac, 0, 0, GDK_KEY_r, GDK_MOD1_MASK);60086009 g->cropmode = dt_bauhaus_combobox_from_params(self, "cropmode");6010 g_signal_connect(G_OBJECT(g->cropmode), "value-changed",6011 G_CALLBACK(cropmode_callback), self);60126013 GtkWidget *main_box = self->widget;60146015 dt_gui_new_collapsible_section6016 (&g->cs,6017 "plugins/darkroom/ashift/expand_values",6018 _("manual perspective"),6019 GTK_BOX(main_box),6020 DT_ACTION(self));60216022 self->widget = GTK_WIDGET(g->cs.container);60236024 g->lensshift_v = dt_bauhaus_slider_from_params(self, "lensshift_v");6025 dt_bauhaus_slider_set_soft_range(g->lensshift_v, -LENSSHIFT_RANGE, LENSSHIFT_RANGE);6026 dt_bauhaus_slider_set_digits(g->lensshift_v, 3);60276028 g->lensshift_h = dt_bauhaus_slider_from_params(self, "lensshift_h");6029 dt_bauhaus_slider_set_soft_range(g->lensshift_h, -LENSSHIFT_RANGE, LENSSHIFT_RANGE);6030 dt_bauhaus_slider_set_digits(g->lensshift_h, 3);60316032 g->shear = dt_bauhaus_slider_from_params(self, "shear");6033 dt_bauhaus_slider_set_soft_range(g->shear, -SHEAR_RANGE, SHEAR_RANGE);60346035 g->mode = dt_bauhaus_combobox_from_params(self, "mode");60366037 self->widget = g->specifics = dt_gui_vbox();60386039 g->f_length = dt_bauhaus_slider_from_params(self, "f_length");6040 dt_bauhaus_slider_set_soft_range(g->f_length, 10.0f, 1000.0f);6041 dt_bauhaus_slider_set_log_curve(g->f_length);6042 dt_bauhaus_slider_set_digits(g->f_length, 0);6043 dt_bauhaus_slider_set_format(g->f_length, " mm");60446045 g->crop_factor = dt_bauhaus_slider_from_params(self, "crop_factor");6046 dt_bauhaus_slider_set_soft_range(g->crop_factor, 1.0f, 2.0f);60476048 g->orthocorr = dt_bauhaus_slider_from_params(self, "orthocorr");6049 dt_bauhaus_slider_set_format(g->orthocorr, "%");6050 // this parameter could serve to finetune between generic model (0%)6051 // and specific model (100%). however, users can more easily get6052 // the same effect with the aspect adjust parameter so we keep this6053 // one hidden.6054 gtk_widget_set_no_show_all(g->orthocorr, TRUE);6055 gtk_widget_set_visible(g->orthocorr, FALSE);60566057 g->aspect = dt_bauhaus_slider_from_params(self, "aspect");6058 dt_bauhaus_slider_set_curve(g->aspect, log2_curve);60596060 dt_gui_box_add(g->cs.container, g->specifics);60616062 self->widget = main_box;60636064 dt_gui_box_add(self->widget, dt_ui_section_label_new(C_("section", "perspective")));60656066 GtkGrid *auto_grid = GTK_GRID(gtk_grid_new());6067 gtk_grid_set_row_spacing(auto_grid, 2 * DT_BAUHAUS_SPACE);6068 gtk_grid_set_column_spacing(auto_grid, DT_PIXEL_APPLY_DPI(10));60696070 gtk_grid_attach(auto_grid, dt_ui_label_new(_("structure")), 0, 0, 1, 1);60716072 g->structure_lines = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_drawn, 0, NULL);6073 gtk_widget_set_hexpand(GTK_WIDGET(g->structure_lines), TRUE);6074 gtk_grid_attach(auto_grid, g->structure_lines, 1, 0, 1, 1);60756076 g->structure_quad = dtgtk_togglebutton_new(dtgtk_cairo_paint_draw_structure, 0, NULL);6077 gtk_widget_set_hexpand(GTK_WIDGET(g->structure_quad), TRUE);6078 gtk_grid_attach(auto_grid, g->structure_quad, 2, 0, 1, 1);60796080 g->structure_auto = dtgtk_togglebutton_new(dtgtk_cairo_paint_structure, 0, NULL);6081 gtk_widget_set_hexpand(GTK_WIDGET(g->structure_auto), TRUE);6082 gtk_grid_attach(auto_grid, g->structure_auto, 3, 0, 1, 1);60836084 gtk_grid_attach(auto_grid, dt_ui_label_new(_("fit")), 0, 1, 1, 1);60856086 g->fit_v = dtgtk_button_new(dtgtk_cairo_paint_perspective, 1, NULL);6087 gtk_widget_set_hexpand(GTK_WIDGET(g->fit_v), TRUE);6088 gtk_grid_attach(auto_grid, g->fit_v, 1, 1, 1, 1);60896090 g->fit_h = dtgtk_button_new(dtgtk_cairo_paint_perspective, 2, NULL);6091 gtk_widget_set_hexpand(GTK_WIDGET(g->fit_h), TRUE);6092 gtk_grid_attach(auto_grid, g->fit_h, 2, 1, 1, 1);60936094 g->fit_both = dtgtk_button_new(dtgtk_cairo_paint_perspective, 3, NULL);6095 gtk_widget_set_hexpand(GTK_WIDGET(g->fit_both), TRUE);6096 gtk_grid_attach(auto_grid, g->fit_both, 3, 1, 1, 1);60976098 gtk_widget_show_all(GTK_WIDGET(auto_grid));6099 dt_gui_box_add(self->widget, auto_grid);61006101 gtk_widget_set_tooltip_text6102 (g->rotation,6103 _("rotate image\nright-click and drag to define a horizontal or vertical"6104 " line by drawing on the image"));6105 gtk_widget_set_tooltip_text6106 (g->lensshift_v, _("apply lens shift correction in one direction"));6107 gtk_widget_set_tooltip_text6108 (g->lensshift_h, _("apply lens shift correction in one direction"));6109 gtk_widget_set_tooltip_text6110 (g->shear, _("shear the image along one diagonal"));6111 gtk_widget_set_tooltip_text(g->cropmode, _("automatically crop to avoid black edges"));6112 gtk_widget_set_tooltip_text(g->mode, _("lens model of the perspective correction: "6113 "generic or according to the focal length"));6114 gtk_widget_set_tooltip_text6115 (g->f_length, _("focal length of the lens, "6116 "default value set from EXIF data if available"));6117 gtk_widget_set_tooltip_text6118 (g->crop_factor, _("crop factor of the camera sensor, "6119 "default value set from EXIF data if available, "6120 "manual setting is often required"));6121 gtk_widget_set_tooltip_text6122 (g->orthocorr, _("the level of lens dependent correction, set to maximum"6123 " for full lens dependency, "6124 "set to zero for the generic case"));6125 gtk_widget_set_tooltip_text6126 (g->aspect, _("adjust aspect ratio of image by horizontal and vertical scaling"));6127 gtk_widget_set_tooltip_text6128 (g->fit_v, _("automatically correct for vertical perspective distortion\n"6129 "ctrl+click to only fit rotation\n"6130 "shift+click to only fit lens shift"));6131 gtk_widget_set_tooltip_text6132 (g->fit_h, _("automatically correct for horizontal perspective distortion\n"6133 "ctrl+click to only fit rotation\n"6134 "shift+click to only fit lens shift"));6135 gtk_widget_set_tooltip_text6136 (g->fit_both, _("automatically correct for vertical and "6137 "horizontal perspective distortions, fitting rotation, "6138 "lens shift in both directions, and shear\n"6139 "ctrl+click to only fit rotation\n"6140 "shift+click to only fit lens shift\n"6141 "ctrl+shift+click to only fit rotation and lens shift"));6142 gtk_widget_set_tooltip_text6143 (g->structure_auto, _("automatically analyse line structure in image\n"6144 "ctrl+click for an additional edge enhancement\n"6145 "shift+click for an additional detail enhancement\n"6146 "ctrl+shift+click for a combination of both methods"));6147 gtk_widget_set_tooltip_text6148 (g->structure_quad, _("manually define perspective rectangle"));6149 gtk_widget_set_tooltip_text(g->structure_lines, _("manually draw structure lines"));61506151 g_object_set_data(G_OBJECT(g->fit_v), DT_ACTION_GESTURE_KEY,6152 dt_gui_connect_click(g->fit_v, _event_fit_v_button_clicked, NULL, self));6153 g_object_set_data(G_OBJECT(g->fit_h), DT_ACTION_GESTURE_KEY,6154 dt_gui_connect_click(g->fit_h, _event_fit_h_button_clicked, NULL, self));6155 g_object_set_data(G_OBJECT(g->fit_both), DT_ACTION_GESTURE_KEY,6156 dt_gui_connect_click(g->fit_both, _event_fit_both_button_clicked, NULL, self));6157 g_object_set_data(G_OBJECT(g->structure_quad), DT_ACTION_GESTURE_KEY,6158 _connect_structure_button(g->structure_quad, G_CALLBACK(_event_structure_quad_clicked), self));6159 g_object_set_data(G_OBJECT(g->structure_lines), DT_ACTION_GESTURE_KEY,6160 _connect_structure_button(g->structure_lines, G_CALLBACK(_event_structure_lines_clicked), self));6161 g_object_set_data(G_OBJECT(g->structure_auto), DT_ACTION_GESTURE_KEY,6162 _connect_structure_button(g->structure_auto, G_CALLBACK(_event_structure_auto_clicked), self));6163 g_signal_connect(G_OBJECT(self->widget), "draw", G_CALLBACK(_event_draw), self);61646165 dt_action_define_iop(self, N_("fit"),6166 N_("vertical"), g->fit_v, &dt_action_def_button);6167 dt_action_define_iop(self, N_("fit"),6168 N_("horizontal"), g->fit_h, &dt_action_def_button);6169 dt_action_define_iop(self, N_("fit"),6170 N_("both"), g->fit_both, &dt_action_def_button);6171 dt_action_define_iop(self, N_("structure"),6172 N_("rectangle"), g->structure_quad, &dt_action_def_toggle);6173 dt_action_define_iop(self, N_("structure"),6174 N_("lines"), g->structure_lines, &dt_action_def_toggle);6175 dt_action_define_iop(self, N_("structure"),6176 N_("auto"), g->structure_auto, &dt_action_def_toggle);61776178 /* add signal handler for preview pipe finish to redraw the overlay */6179 DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED, _event_process_after_preview_callback);61806181 darktable.develop->proxy.rotate = self;6182}61836184void gui_cleanup(dt_iop_module_t *self)6185{6186 if(darktable.develop->proxy.rotate == self)6187 darktable.develop->proxy.rotate = NULL;61886189 const dt_iop_ashift_gui_data_t *g = self->gui_data;6190 if(g->lines) free(g->lines);6191 dt_free_align(g->buf);6192 if(g->points) free(g->points);6193 if(g->points_idx) free(g->points_idx);6194}61956196GSList *mouse_actions(dt_iop_module_t *self)6197{6198 GSList *lm = NULL;6199 lm = dt_mouse_action_create_format6200 (lm, DT_MOUSE_ACTION_RIGHT_DRAG, 0, _("[%s] define/rotate horizon"), self->name());62016202 lm = dt_mouse_action_create_format6203 (lm, DT_MOUSE_ACTION_LEFT, 0, _("[%s on segment] select segment"), self->name());62046205 lm = dt_mouse_action_create_format6206 (lm, DT_MOUSE_ACTION_RIGHT, 0,6207 _("[%s on segment] unselect segment"), self->name());62086209 lm = dt_mouse_action_create_format6210 (lm, DT_MOUSE_ACTION_LEFT_DRAG, GDK_SHIFT_MASK,6211 _("[%s] select all segments from zone"), self->name());62126213 lm = dt_mouse_action_create_format6214 (lm, DT_MOUSE_ACTION_RIGHT_DRAG, GDK_SHIFT_MASK,6215 _("[%s] unselect all segments from zone"), self->name());62166217 return lm;6218}6219// clang-format off6220// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py6221// vim: shiftwidth=2 expandtab tabstop=2 cindent6222// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;6223// clang-format on