Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
crop.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2021-2022 Aldric Renaudin.
4 Copyright (C) 2021-2022 Chris Elston.
5 Copyright (C) 2021-2022 Diederik Ter Rahe.
6 Copyright (C) 2021-2022 Hanno Schwalm.
7 Copyright (C) 2021 Hubert Kowalski.
8 Copyright (C) 2021-2022 Pascal Obry.
9 Copyright (C) 2021 Ralf Brown.
10 Copyright (C) 2022 Martin Bařinka.
11 Copyright (C) 2022 Philipp Lutz.
12 Copyright (C) 2023 Alynx Zhou.
13 Copyright (C) 2023-2026 Aurélien PIERRE.
14 Copyright (C) 2023 Luca Zulberti.
15 Copyright (C) 2025-2026 Guillaume Stutin.
16
17 darktable is free software: you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 darktable is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with darktable. If not, see <http://www.gnu.org/licenses/>.
29*/
30
31#ifdef HAVE_CONFIG_H
32#include "config.h"
33#endif
34#include "widgets/bauhaus.h"
36#include "system/macros.h"
37#include "system/openmp.h"
38#include "system/mem_alloc.h"
39#include "common/logging.h"
41#include "common/image.h"
42#include "common/imagebuf.h"
43#include "pixel/interpolation.h"
44#include "math/math.h"
45#include "common/opencl.h"
46#include "common/conf.h"
47#include "control/input.h"
48#include "control/control.h"
49#include "develop/develop.h"
51#include "develop/imageop.h"
52#include "develop/imageop_gui.h"
53
54#include "widgets/draw.h"
55#include "gui/guides.h"
56#include "iop/iop_api.h"
57
58#include <assert.h>
59#include <gdk/gdkkeysyms.h>
60#include <gtk/gtk.h>
61#include <inttypes.h>
62#include <stdlib.h>
63#include <string.h>
65
67
68
74
76{
77 char *name;
78 int d, n;
80
82{
83 float cx; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "left"
84 float cy; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "top"
85 float cw; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "right"
86 float ch; // $MIN: 0.0 $MAX: 1.0 $DESCRIPTION: "bottom"
87 int ratio_n; // $DEFAULT: -1
88 int ratio_d; // $DEFAULT: -1
90
107
140
141typedef struct dt_iop_crop_data_t
142{
143 float aspect; // forced aspect ratio
144 float cx, cy, cw, ch; // crop window
146
147int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
148 const int new_version)
149{
150 return 0;
151}
152
153const char *name()
154{
155 return _("crop");
156}
157
158const char *aliases()
159{
160 return _("reframe|distortion");
161}
162
163const char **description(struct dt_iop_module_t *self)
164{
165 return dt_iop_set_description(self, _("change the framing"), _("corrective or creative"),
166 _("linear, RGB, scene-referred"), _("geometric, RGB"),
167 _("linear, RGB, scene-referred"));
168}
169
171{
172 return IOP_GROUP_EFFECTS;
173}
174
180
182{
184}
185
187{
188 // switch off watermark, it gets confused.
189 return IOP_TAG_DECORATION;
190}
191
193{
194 return IOP_CS_RGB;
195}
196
198{
199 g->clip_x = p->cx;
200 g->clip_w = p->cw - p->cx;
201 g->clip_y = p->cy;
202 g->clip_h = p->ch - p->cy;
203}
204
205/*
206* FIXME: Why do we set gui -> params bounding box going through distort_backtransform (below)
207* but we set params -> gui bounding box directly without using distort_transform (above) ???
208* Does that roundrip ? If so, distort_backtransform is actually a no-op ?
209*/
210
212{
213 dt_iop_roi_t mod_out;
214 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return;
215 // we want value in iop space
216 const float wd = (float)mod_out.width;
217 const float ht = (float)mod_out.height;
218
219 const float bbox_left = g->clip_x * wd;
220 const float bbox_top = g->clip_y * ht;
221 const float bbox_right = (g->clip_x + g->clip_w) * wd;
222 const float bbox_bottom = (g->clip_y + g->clip_h) * ht;
223
224 dt_boundingbox_t points = { bbox_left, bbox_top, bbox_right, bbox_bottom };
225
228 {
229 if(mod_out.width < 1 || mod_out.height < 1) return;
230
231 p->cx = CLAMPF(points[0] / wd, 0.0f, 0.9f);
232 p->cy = CLAMPF(points[1] / ht, 0.0f, 0.9f);
233 p->cw = CLAMPF(points[2] / wd, 0.1f, 1.0f);
234 p->ch = CLAMPF(points[3] / ht, 0.1f, 1.0f);
235 }
236
237 //fprintf(stderr, "_commit_box: cx:%.4f cy:%.4f cw:%.4f ch:%.4f\n", p->cx, p->cy, p->cw, p->ch);
238 //fprintf(stderr, "_commit_box: x:%.4f y:%.4f w:%.4f h:%.4f\n", g->clip_x, g->clip_y, g->clip_w, g->clip_h);
239}
240
250/* Takes no pipe: both callers passed the GUI's own pixel-less pipe, so this was never the
251 * dual-use fold it resembled -- it is GUI-only, and asks the dev like every other GUI geometry
252 * query. */
253static gboolean _set_max_clip(struct dt_iop_module_t *self)
254{
257
258 // we want to know the size of the actual buffer
259 dt_iop_roi_t mod_out;
260 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return FALSE;
261
262 float wp = mod_out.width;
263 float hp = mod_out.height;
264 float points[8] = { 0.0f, 0.0f, wp, hp, p->cx * wp, p->cy * hp, p->cw * wp, p->ch * hp };
267 return FALSE;
269
270 g->clip_max_x = fmaxf(points[0], 0.0f);
271 g->clip_max_y = fmaxf(points[1], 0.0f);
272 g->clip_max_w = fminf(points[2] - points[0], 1.0f);
273 g->clip_max_h = fminf(points[3] - points[1], 1.0f);
274
275 /*// if clipping values are not null, this is undistorted values...
276 g->clip_x = fmaxf(points[4], g->clip_max_x);
277 g->clip_y = fmaxf(points[5], g->clip_max_y);
278 g->clip_w = fminf(points[6] - points[4], g->clip_max_w);
279 g->clip_h = fminf(points[7] - points[5], g->clip_max_h);
280*/
281 return TRUE;
282}
283
284/* --- the shared geometry core ----------------------------------------------------------
285 *
286 * These three are the ONLY place crop's geometry is expressed. commit_params() and the
287 * pipeline's distort/roi callbacks call them, and so does geometry_record() for the pixel-less
288 * geometry service (develop/geometry/geometry.h). One constructor and one evaluator per fact:
289 * a second derivation of the same crop would drift from this one, and the drift would show up
290 * as the crop overlay sitting somewhere the cropped image is not.
291 */
292
294static void _crop_resolve(dt_iop_module_t *self, const dt_iop_crop_params_t *const p,
296{
298
299 if(!IS_NULL_PTR(g) && g->editing)
300 {
301 // In editing mode, we need to see the full uncropped image to setup the frame.
302 out->cx = 0.0f;
303 out->cy = 0.0f;
304 out->cw = 1.0f;
305 out->ch = 1.0f;
306 }
307 else
308 {
309 out->cx = CLAMPF(p->cx, 0.0f, 0.9f);
310 out->cy = CLAMPF(p->cy, 0.0f, 0.9f);
311 out->cw = CLAMPF(p->cw, 0.1f, 1.0f);
312 out->ch = CLAMPF(p->ch, 0.1f, 1.0f);
313 }
314}
315
317static void _crop_map_size(const dt_iop_crop_data_t *const d, const dt_iop_roi_t *const roi_in,
318 dt_iop_roi_t *roi_out)
319{
320 *roi_out = *roi_in;
321
322 roi_out->width = roi_in->width * (d->cw - d->cx);
323 roi_out->height = roi_in->height * (d->ch - d->cy);
324 roi_out->x = roi_in->width * d->cx;
325 roi_out->y = roi_in->height * d->cy;
326
327 // sanity check.
328 if(roi_out->x < 0) roi_out->x = 0;
329 if(roi_out->y < 0) roi_out->y = 0;
330 if(roi_out->width < 5) roi_out->width = 5;
331 if(roi_out->height < 5) roi_out->height = 5;
332}
333
335static void _crop_offset(const dt_iop_crop_data_t *const d, const dt_iop_roi_t *const roi_in, float *left,
336 float *top)
337{
338 *left = roi_in->width * d->cx;
339 *top = roi_in->height * d->cy;
340}
341
343 float *const restrict points, size_t points_count)
344{
346
347 float crop_left = 0.f, crop_top = 0.f;
348 _crop_offset(d, &piece->buf_in, &crop_left, &crop_top);
349
350 // nothing to be done if parameters are set to neutral values (no top/left border)
351 if(crop_top == 0 && crop_left == 0) return 1;
352 __OMP_PARALLEL_FOR_SIMD__(if(points_count > 100) aligned(points : 64))
353 for(size_t i = 0; i < points_count * 2; i += 2)
354 {
355 points[i] -= crop_left;
356 points[i + 1] -= crop_top;
357 }
358
359 return 1;
360}
361
363 float *const restrict points, size_t points_count)
364{
366
367 float crop_left = 0.f, crop_top = 0.f;
368 _crop_offset(d, &piece->buf_in, &crop_left, &crop_top);
369
370 // nothing to be done if parameters are set to neutral values (no top/left border)
371 if(crop_top == 0 && crop_left == 0) return 1;
372 __OMP_PARALLEL_FOR_SIMD__(if(points_count > 100) aligned(points : 64))
373 for(size_t i = 0; i < points_count * 2; i += 2)
374 {
375 points[i] += crop_left;
376 points[i + 1] += crop_top;
377 }
378
379 return 1;
380}
381
382void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
383 struct dt_dev_pixelpipe_iop_t *piece, const float *const in, float *const out,
384 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
385{
386 (void)self;
387 (void)pipe;
388 (void)piece;
389 dt_iop_copy_image_roi(out, in, 1, roi_in, roi_out, TRUE);
390}
391
392// 1st pass: how large would the output be, given this input roi?
393// this is always called with the full buffer before processing.
394void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
395 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
396 const dt_iop_roi_t *roi_in)
397{
398 _crop_map_size((dt_iop_crop_data_t *)piece->data, roi_in, roi_out);
399}
400
401// 2nd pass: which roi would this operation need as input to fill the given output region?
402void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
403 struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
404{
406 *roi_in = *roi_out;
407
408 const float iw = piece->buf_in.width * roi_out->scale;
409 const float ih = piece->buf_in.height * roi_out->scale;
410
411 roi_in->x += iw * d->cx;
412 roi_in->y += ih * d->cy;
413
414 roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(iw));
415 roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(ih));
416}
417
418int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
419 void *const ovoid)
420{
421 const dt_iop_roi_t *const roi_in = &piece->roi_in;
422 const dt_iop_roi_t *const roi_out = &piece->roi_out;
423 dt_iop_copy_image_roi(ovoid, ivoid, 4, roi_in, roi_out, TRUE);
424 return 0;
425}
426
427#ifdef HAVE_OPENCL
428int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
429{
430 const dt_iop_roi_t *const roi_out = &piece->roi_out;
431 cl_int err = -999;
432
433 size_t origin[] = { 0, 0, 0 };
434 size_t region[] = { roi_out->width, roi_out->height, 1 };
435 err = dt_opencl_enqueue_copy_image(pipe->devid, dev_in, dev_out, origin, origin, region);
436 if(err != CL_SUCCESS) goto error;
437
438 return TRUE;
439
440error:
441 dt_print(DT_DEBUG_OPENCL, "[opencl_crop] couldn't enqueue kernel! %d\n", err);
442 return FALSE;
443}
444#endif
445
451
452/* --- the geometry service's view of this module (develop/geometry/geometry.h) --------- */
453
454static void _crop_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
455{
456 _crop_map_size((const dt_iop_crop_data_t *)data, in, out);
457}
458
459static int _crop_geometry_transform(const void *data, const dt_geometry_record_t *const record,
460 dt_geometry_chain_t *chain, float *points, size_t points_count)
461{
462 float left = 0.f, top = 0.f;
463 _crop_offset((const dt_iop_crop_data_t *)data, &record->in, &left, &top);
464 if(left == 0.f && top == 0.f) return 1;
465 for(size_t i = 0; i < points_count * 2; i += 2)
466 {
467 points[i] -= left;
468 points[i + 1] -= top;
469 }
470 return 1;
471}
472
473static int _crop_geometry_backtransform(const void *data, const dt_geometry_record_t *const record,
474 dt_geometry_chain_t *chain, float *points, size_t points_count)
475{
476 float left = 0.f, top = 0.f;
477 _crop_offset((const dt_iop_crop_data_t *)data, &record->in, &left, &top);
478 if(left == 0.f && top == 0.f) return 1;
479 for(size_t i = 0; i < points_count * 2; i += 2)
480 {
481 points[i] += left;
482 points[i + 1] += top;
483 }
484 return 1;
485}
486
492
493gboolean geometry_record(dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
494{
495 /* Same constructor commit_params() uses, including the edit-mode neutralisation: while the
496 * crop GUI is in editing mode the pipe renders the full uncropped frame, and the overlays
497 * have to be told the same story or they would sit on a crop nobody is displaying. */
498 dt_iop_crop_data_t *data = (dt_iop_crop_data_t *)g_malloc0(sizeof(dt_iop_crop_data_t));
499 if(IS_NULL_PTR(data)) return FALSE;
500
501 _crop_resolve(self, (const dt_iop_crop_params_t *)params, data);
502
503 record->data = data;
504 record->free_data = dt_free_gpointer;
505 record->vtable = &_crop_geometry_vtable;
506 return TRUE;
507}
508
510 const dt_dev_pixelpipe_iop_t *piece)
511{
512 (void)self;
513 (void)pipe;
514 (void)piece;
515 return TRUE;
516}
517
519{
520 piece->data = dt_calloc_align(sizeof(dt_iop_crop_data_t));
521 piece->data_size = sizeof(dt_iop_crop_data_t);
522}
523
525{
526 dt_free_align(piece->data);
527 piece->data = NULL;
528}
529
530static float _aspect_ratio_get(dt_iop_module_t *self, GtkWidget *combo)
531{
533
534 // retrieve full image dimensions to calculate aspect ratio if "original image" specified
535 const char *text = dt_bauhaus_combobox_get_text(combo);
536 if(text && !g_strcmp0(text, _("original image")))
537 {
538 int proc_iwd = 0, proc_iht = 0;
539 dt_dev_get_processed_size(self->dev, &proc_iwd, &proc_iht);
540
541 if(!(proc_iwd > 0 && proc_iht > 0)) return 0.0f;
542
543 if((p->ratio_d > 0 && proc_iwd > proc_iht) || (p->ratio_d < 0 && proc_iwd < proc_iht))
544 return (float)proc_iwd / (float)proc_iht;
545 else
546 return (float)proc_iht / (float)proc_iwd;
547 }
548
549 // we want to know the size of the actual buffer
550 dt_iop_roi_t mod_in;
551 if(!dt_dev_module_geometry_gui(self->dev, self, &mod_in, NULL)) return 0.0f;
552
553 const int iwd = mod_in.width, iht = mod_in.height;
554
555 // if we do not have yet computed the aspect ratio, let's do it now
556 if(p->ratio_d == -2 && p->ratio_n == -2)
557 {
558 if(p->cw == 1.0 && p->cx == 0.0 && p->ch == 1.0 && p->cy == 0.0)
559 {
560 p->ratio_d = -1;
561 p->ratio_n = -1;
562 }
563 else
564 {
566 const float whratio = ((float)(iwd - 2 * interpolation->width) * (p->cw - p->cx))
567 / ((float)(iht - 2 * interpolation->width) * (p->ch - p->cy));
568 const float ri = (float)iwd / (float)iht;
569
570 const float prec = 0.0003f;
571 if(fabsf(whratio - 3.0f / 2.0f) < prec)
572 {
573 p->ratio_d = 3;
574 p->ratio_n = 2;
575 }
576 else if(fabsf(whratio - 2.0f / 1.0f) < prec)
577 {
578 p->ratio_d = 2;
579 p->ratio_n = 1;
580 }
581 else if(fabsf(whratio - 7.0f / 5.0f) < prec)
582 {
583 p->ratio_d = 7;
584 p->ratio_n = 5;
585 }
586 else if(fabsf(whratio - 4.0f / 3.0f) < prec)
587 {
588 p->ratio_d = 4;
589 p->ratio_n = 3;
590 }
591 else if(fabsf(whratio - 5.0f / 4.0f) < prec)
592 {
593 p->ratio_d = 5;
594 p->ratio_n = 4;
595 }
596 else if(fabsf(whratio - 1.0f / 1.0f) < prec)
597 {
598 p->ratio_d = 1;
599 p->ratio_n = 1;
600 }
601 else if(fabsf(whratio - 16.0f / 9.0f) < prec)
602 {
603 p->ratio_d = 16;
604 p->ratio_n = 9;
605 }
606 else if(fabsf(whratio - 16.0f / 10.0f) < prec)
607 {
608 p->ratio_d = 16;
609 p->ratio_n = 10;
610 }
611 else if(fabsf(whratio - 244.5f / 203.2f) < prec)
612 {
613 p->ratio_d = 2445;
614 p->ratio_n = 2032;
615 }
616 else if(fabsf(whratio - sqrtf(2.0f)) < prec)
617 {
618 p->ratio_d = 14142136;
619 p->ratio_n = 10000000;
620 }
621 else if(fabsf(whratio - PHI) < prec)
622 {
623 p->ratio_d = 16180340;
624 p->ratio_n = 10000000;
625 }
626 else if(fabsf(whratio - ri) < prec)
627 {
628 p->ratio_d = 1;
629 p->ratio_n = 0;
630 }
631 else
632 {
633 p->ratio_d = 0;
634 p->ratio_n = 0;
635 }
636 }
637 }
638
639 if(p->ratio_d == 0 && p->ratio_n == 0) return -1.0f;
640 float d = 1.0f, n = 1.0f;
641 if(p->ratio_n == 0)
642 {
643 d = copysignf(iwd, p->ratio_d);
644 n = iht;
645 }
646 else
647 {
648 d = p->ratio_d;
649 n = p->ratio_n;
650 }
651
652 // make aspect ratios like 3:2 and 2:3 to be the same thing
653 const float dn = copysignf(MAX(fabsf(d), fabsf(n)), d);
654 const float nn = copysignf(MIN(fabsf(d), fabsf(n)), n);
655
656 if(dn < 0)
657 return -nn / dn;
658 else
659 return dn / nn;
660}
661
663{
664 if(grab == GRAB_NONE) return;
665
667
668 // enforce aspect ratio.
669 float aspect = _aspect_ratio_get(self, g->aspect_presets);
670 if(aspect <= 0) return;
671
672 int iwd, iht;
673 dt_dev_get_processed_size(self->dev, &iwd, &iht);
674
675 // since one rarely changes between portrait and landscape by cropping,
676 // long side of the crop box should match the long side of the image.
677 if(iwd < iht) aspect = 1.0f / aspect;
678
679 // if only one side changed, force aspect by two adjacent in equal parts
680 // 1 2 4 8 : x y w h
681 double clip_x = MAX(iwd * g->clip_x / (float)iwd, 0.0f);
682 double clip_y = MAX(iht * g->clip_y / (float)iht, 0.0f);
683 double clip_w = MIN(iwd * g->clip_w / (float)iwd, 1.0f);
684 double clip_h = MIN(iht * g->clip_h / (float)iht, 1.0f);
685
686 // if we only modified one dim, respectively, we wanted these values:
687 const double target_h = (double)iwd * g->clip_w / ((double)iht * aspect);
688 const double target_w = (double)iht * g->clip_h * aspect / (double)iwd;
689 // i.e. target_w/h = w/target_h = aspect
690 // first fix aspect ratio:
691
692 // corners: move two adjacent
693 if(grab == GRAB_TOP_LEFT)
694 {
695 // move x y
696 clip_x = clip_x + clip_w - (target_w + clip_w) * .5;
697 clip_y = clip_y + clip_h - (target_h + clip_h) * .5;
698 clip_w = (target_w + clip_w) * .5;
699 clip_h = (target_h + clip_h) * .5;
700 }
701 else if(grab == GRAB_TOP_RIGHT) // move y w
702 {
703 clip_y = clip_y + clip_h - (target_h + clip_h) * .5;
704 clip_w = (target_w + clip_w) * .5;
705 clip_h = (target_h + clip_h) * .5;
706 }
707 else if(grab == GRAB_BOTTOM_RIGHT) // move w h
708 {
709 clip_w = (target_w + clip_w) * .5;
710 clip_h = (target_h + clip_h) * .5;
711 }
712 else if(grab == GRAB_BOTTOM_LEFT) // move h x
713 {
714 clip_h = (target_h + clip_h) * .5;
715 clip_x = clip_x + clip_w - (target_w + clip_w) * .5;
716 clip_w = (target_w + clip_w) * .5;
717 }
718 else if(grab & GRAB_HORIZONTAL) // dragged either x or w (1 4)
719 {
720 // change h and move y, h equally
721 const double off = target_h - clip_h;
722 clip_h = clip_h + off;
723 clip_y = clip_y - .5 * off;
724 }
725 else if(grab & GRAB_VERTICAL) // dragged either y or h (2 8)
726 {
727 // change w and move x, w equally
728 const double off = target_w - clip_w;
729 clip_w = clip_w + off;
730 clip_x = clip_x - .5 * off;
731 }
732 // now fix outside boxes:
733 if(clip_x < g->clip_max_x)
734 {
735 const double prev_clip_h = clip_h;
736 clip_h *= (clip_w + clip_x - g->clip_max_x) / clip_w;
737 clip_w = clip_w + clip_x - g->clip_max_x;
738 clip_x = g->clip_max_x;
739 if(grab & GRAB_TOP) clip_y += prev_clip_h - clip_h;
740 }
741 if(clip_y < g->clip_max_y)
742 {
743 const double prev_clip_w = clip_w;
744 clip_w *= (clip_h + clip_y - g->clip_max_y) / clip_h;
745 clip_h = clip_h + clip_y - g->clip_max_y;
746 clip_y = g->clip_max_y;
747 if(grab & GRAB_LEFT) clip_x += prev_clip_w - clip_w;
748 }
749 if(clip_x + clip_w > g->clip_max_x + g->clip_max_w)
750 {
751 const double prev_clip_h = clip_h;
752 clip_h *= (g->clip_max_x + g->clip_max_w - clip_x) / clip_w;
753 clip_w = g->clip_max_x + g->clip_max_w - clip_x;
754 if(grab & GRAB_TOP) clip_y += prev_clip_h - clip_h;
755 }
756 if(clip_y + clip_h > g->clip_max_y + g->clip_max_h)
757 {
758 const double prev_clip_w = clip_w;
759 clip_w *= (g->clip_max_y + g->clip_max_h - clip_y) / clip_h;
760 clip_h = g->clip_max_y + g->clip_max_h - clip_y;
761 if(grab & GRAB_LEFT) clip_x += prev_clip_w - clip_w;
762 }
763 g->clip_x = fmaxf(clip_x, 0.0f);
764 g->clip_y = fmaxf(clip_y, 0.0f);
765 g->clip_w = fminf(clip_w, 1.0f);
766 g->clip_h = fminf(clip_h, 1.0f);
767}
768
770{
771 const dt_image_t *img = &self->dev->image_storage;
772
774
775 /* A camera that records selected framing (Leica Q/M crop modes, and any DNG carrying the
776 * standardized DefaultUserCrop tag) declares it in the frame RawSpeed already delivers -- the
777 * DefaultCropOrigin/DefaultCropSize rectangle -- so the normalized edges need no pixel
778 * arithmetic to become crop parameters. They do need orientation: this module runs after
779 * `flip`, while the tag is expressed on the unrotated sensor.
780 *
781 * Resolve against the *current* effective orientation rather than the EXIF value alone, so a
782 * reset after the user rotated the image still lands where an equivalent manual crop would.
783 * dt_image_get_effective_orientation() reads the flip module's committed history and falls
784 * back to the EXIF orientation when there is none -- which is exactly flip's own default, and
785 * therefore the right answer when this runs while default history is still being assembled. */
787 const gboolean has_camera_framing
789
790 d->cx = box[1];
791 d->cy = box[0];
792 d->cw = box[3];
793 d->ch = box[2];
794
795 /* Apply camera framing automatically, but only while Ansel's one-time default-history
796 * initialization is still open for this image -- DT_IMAGE_AUTO_PRESETS_APPLIED is the durable
797 * snapshot of that, and complete history deletion clears it on purpose.
798 *
799 * The gate is not optional: _insert_default_modules() (develop/dev_history.c) inserts *any*
800 * default_enabled module into *any* history that lacks an entry for it, on every single load,
801 * without consulting that flag itself. Reporting the module as enabled-by-default
802 * unconditionally would therefore not just affect new images -- it would silently add a crop
803 * to every already-edited image that happens to have no crop item, on next open.
804 *
805 * An explicit crop-producing auto-preset still wins: presets are applied after this entry is
806 * inserted, and history replay keeps the last item per module. */
807 const gboolean history_initialized = (img->flags & DT_IMAGE_AUTO_PRESETS_APPLIED) != 0;
808 self->default_enabled = has_camera_framing && !history_initialized;
809}
810
811gboolean has_defaults(struct dt_iop_module_t *self)
812{
815 // p->ratio_d and p->ratio_n are inited in GUI, so they are unset in d.
816 return d->cx == p->cx && d->cy == p->cy && d->cw == p->cw && d->ch == p->ch;
817}
818
819static void _float_to_fract(const char *num, int *n, int *d)
820{
821 char tnum[100];
822 gboolean sep_found = FALSE;
823 char *p = (char *)num;
824 int k = 0;
825
826 *d = 1;
827
828 while(*p)
829 {
830 if(sep_found) *d *= 10;
831
832 // look for decimal sep
833 if(!sep_found && ((*p == ',') || (*p == '.')))
834 {
835 sep_found = TRUE;
836 }
837 else if(*p < '0' || *p > '9')
838 {
839 *n = *d = 0;
840 return;
841 }
842 else
843 {
844 tnum[k++] = *p;
845 }
846
847 p++;
848 }
849
850 tnum[k] = '\0';
851
852 *n = atoi(tnum);
853}
854
856{
859 const int which = dt_bauhaus_combobox_get(combo);
860 int d = abs(p->ratio_d), n = p->ratio_n;
861 const char *text = dt_bauhaus_combobox_get_text(combo);
862 if(which < 0)
863 {
864 if(!IS_NULL_PTR(text))
865 {
866 const char *c = text;
867 const char *end = text + strlen(text);
868 while(*c != ':' && *c != '/' && c < end) c++;
869 if(c < end - 1)
870 {
871 // input the exact fraction
872 c++;
873 const int dd = atoi(text);
874 const int nn = atoi(c);
875 // some sanity check
876 if(nn == 0 || dd == 0)
877 {
878 dt_control_log(_("invalid ratio format. it should be \"number:number\""));
879 dt_bauhaus_combobox_set(combo, 0);
880 return;
881 }
882 d = MAX(dd, nn);
883 n = MIN(dd, nn);
884 }
885 else
886 {
887 // find the closest fraction from the input ratio
888 int nn = 0, dd = 0;
889 _float_to_fract(text, &nn, &dd);
890
891 // some sanity check
892 if(dd == 0 || nn == 0)
893 {
894 dt_control_log(_("invalid ratio format. it should be a positive number"));
895 dt_bauhaus_combobox_set(combo, 0);
896 return;
897 }
898
899 d = MAX(dd, nn);
900 n = MIN(dd, nn);
901 }
902
903 // simplify the fraction with binary GCD - https://en.wikipedia.org/wiki/Greatest_common_divisor
904 // search g and d such that g is odd and gcd(nn, dd) = g x 2^d
905 int e = 0;
906 int nn = abs(n);
907 int dd = abs(d);
908 while((nn % 2 == 0) && (dd % 2 == 0))
909 {
910 nn /= 2;
911 dd /= 2;
912 e++;
913 }
914 while(nn != dd)
915 {
916 if(nn % 2 == 0)
917 nn /= 2;
918 else if(dd % 2 == 0)
919 dd /= 2;
920 else if(nn > dd)
921 nn = (nn - dd) / 2;
922 else
923 dd = (dd - nn) / 2;
924 }
925
926 // reduce the fraction with the GCD
927 n /= (nn * 1 << e);
928 d /= (nn * 1 << e);
929 }
930 }
931 else
932 {
933 d = n = 0;
934
935 for(const GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
936 {
937 const dt_iop_crop_aspect_t *aspect = iter->data;
938 if(g_strcmp0(aspect->name, text) == 0)
939 {
940 d = aspect->d;
941 n = aspect->n;
942 break;
943 }
944 }
945 }
946
947 // now we save all that if it has changed
948 if(d != abs(p->ratio_d) || n != p->ratio_n)
949 {
950 if(p->ratio_d >= 0)
951 p->ratio_d = d;
952 else
953 p->ratio_d = -d;
954
955 p->ratio_n = n;
956 dt_conf_set_int("plugins/darkroom/crop/ratio_d", abs(p->ratio_d));
957 dt_conf_set_int("plugins/darkroom/crop/ratio_n", abs(p->ratio_n));
958 if(dt_gui_widgets_suppressed()) return;
959 }
960
961 // Search if current aspect ratio matches something known
962 int act = -1;
963 int i = 0;
964
965 for(const GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
966 {
967 const dt_iop_crop_aspect_t *aspect = iter->data;
968 if((aspect->d == d) && (aspect->n == n))
969 {
970 act = i;
971 break;
972 }
973 i++;
974 }
975
976 // Update combobox label
978
979 if(act == -1)
980 {
981 // we got a custom ratio
982 char str[128];
983 snprintf(str, sizeof(str), "%d:%d %2.2f", abs(p->ratio_d), abs(p->ratio_n),
984 (float)abs(p->ratio_d) / (float)abs(p->ratio_n));
985 dt_bauhaus_combobox_set_text(g->aspect_presets, str);
986 }
987 else if(dt_bauhaus_combobox_get(g->aspect_presets) != act)
988 // we got a default ratio
989 dt_bauhaus_combobox_set(g->aspect_presets, act);
990
992
993 if(!dt_gui_widgets_suppressed()) gui_changed(self, g->aspect_presets, NULL);
994}
995
996void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
997{
1000
1002
1003 if(w == g->cx)
1004 {
1005 g->clip_w = g->clip_x + g->clip_w - p->cx;
1006 g->clip_x = p->cx;
1007 _aspect_apply(self, GRAB_LEFT);
1008 }
1009 else if(w == g->cw)
1010 {
1011 g->clip_w = p->cw - g->clip_x;
1013 }
1014 else if(w == g->cy)
1015 {
1016 g->clip_h = g->clip_y + g->clip_h - p->cy;
1017 g->clip_y = p->cy;
1018 _aspect_apply(self, GRAB_TOP);
1019 }
1020 else if(w == g->ch)
1021 {
1022 g->clip_h = p->ch - g->clip_y;
1024 }
1025 else if(w == g->aspect_presets)
1026 {
1027 _aspect_apply(self, GRAB_ALL);
1028 }
1029
1030 // update all sliders, as their values may have change to keep aspect ratio
1031 dt_bauhaus_slider_set(g->cx, g->clip_x);
1032 dt_bauhaus_slider_set_soft_min(g->cw, g->clip_x + 0.10);
1033 dt_bauhaus_slider_set(g->cy, g->clip_y);
1034 dt_bauhaus_slider_set_soft_min(g->ch, g->clip_y + 0.10);
1035 dt_bauhaus_slider_set(g->cw, g->clip_x + g->clip_w);
1036 dt_bauhaus_slider_set_soft_max(g->cx, g->clip_x + g->clip_w - 0.10);
1037 dt_bauhaus_slider_set(g->ch, g->clip_y + g->clip_h);
1038 dt_bauhaus_slider_set_soft_max(g->cy, g->clip_y + g->clip_h - 0.10);
1039
1041
1042 _commit_box(self, g, p);
1045}
1046
1047void gui_reset(struct dt_iop_module_t *self)
1048{
1049 /* reset aspect preset to default */
1050 dt_conf_set_int("plugins/darkroom/crop/ratio_d", 0);
1051 dt_conf_set_int("plugins/darkroom/crop/ratio_n", 0);
1052}
1053
1054void gui_update(struct dt_iop_module_t *self)
1055{
1058
1059 // set aspect ratio based on the current image, if not found let's default
1060 // to free aspect.
1061
1062 if(p->ratio_d == -2 && p->ratio_n == -2) _aspect_ratio_get(self, g->aspect_presets);
1063
1064 if(p->ratio_d == -1 && p->ratio_n == -1)
1065 {
1066 p->ratio_d = dt_conf_get_int("plugins/darkroom/crop/ratio_d");
1067 p->ratio_n = dt_conf_get_int("plugins/darkroom/crop/ratio_n");
1068 }
1069
1070 const int d = abs(p->ratio_d);
1071 const int n = p->ratio_n;
1072
1073 int act = -1;
1074 int i = 0;
1075 for(const GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
1076 {
1077 const dt_iop_crop_aspect_t *aspect = iter->data;
1078 if((aspect->d == d) && (aspect->n == n))
1079 {
1080 act = i;
1081 break;
1082 }
1083 i++;
1084 }
1085
1086 /* special handling the combobox when current act is already selected
1087 callback is not called, let do it our self then..
1088 */
1089 if(act == -1)
1090 {
1091 char str[128];
1092 snprintf(str, sizeof(str), "%d:%d %2.2f", abs(p->ratio_d), abs(p->ratio_n),
1093 (float)abs(p->ratio_d) / (float)abs(p->ratio_n));
1094 dt_bauhaus_combobox_set_text(g->aspect_presets, str);
1095 }
1096 if(dt_bauhaus_combobox_get(g->aspect_presets) == act)
1097 _event_aspect_presets_changed(g->aspect_presets, self);
1098 else
1099 dt_bauhaus_combobox_set(g->aspect_presets, act);
1100
1101 // reset gui draw box to what we have in the parameters:
1102 _params_to_gui(p, g);
1103
1105}
1106
1108{
1110 p->ratio_d = -p->ratio_d;
1111 _aspect_apply(self, GRAB_ALL);
1112 gui_changed(self, NULL, NULL);
1113}
1114
1115static void _enter_edit_mode(GtkToggleButton *button, struct dt_iop_module_t *self)
1116{
1119 if(!self->enabled) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1120
1121 g->editing = gtk_toggle_button_get_active(button);
1122 dt_control_change_cursor(GDK_LEFT_PTR);
1124
1125 if(g->editing)
1126 {
1128
1129 // Take a backup of current params
1130 memcpy(&g->previous_params, p, sizeof(dt_iop_crop_params_t));
1131 g->cropping = GRAB_CENTER;
1132 g->dragging = FALSE;
1133 gtk_button_set_label(GTK_BUTTON(button), _("Cancel"));
1134 gtk_widget_set_sensitive(g->commit_button, TRUE);
1137 }
1138 else
1139 {
1141
1142 // Restore the params backup
1143 memcpy(p, &g->previous_params, sizeof(dt_iop_crop_params_t));
1144 g->cropping = GRAB_NONE;
1145
1146 // Commit the params backup
1147 _params_to_gui(p, g);
1148 gui_changed(self, NULL, NULL);
1149
1150 // Update GUI
1151 gtk_button_set_label(GTK_BUTTON(button), _("Edit"));
1152 gtk_widget_set_sensitive(g->commit_button, FALSE);
1153 }
1154
1155 // It sucks that we need to invalidate the preview too but we need its final dimension.
1158}
1159
1160static void _event_commit_clicked(GtkButton *button, dt_iop_module_t *self)
1161{
1163
1164 // Close edit mode on commit
1165 g->editing = FALSE;
1166 gtk_widget_set_sensitive(g->commit_button, FALSE);
1168
1169 // Commit history and refresh view
1170 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1172
1173 // The following will de-activate the edit button and trigger the callback.
1174 // Prevent the callback to revert the param change.
1175 g_signal_handlers_block_by_func(g->edit_button, _enter_edit_mode, self);
1176 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->edit_button), FALSE);
1177 gtk_button_set_label(GTK_BUTTON(g->edit_button), _("Edit"));
1178 g_signal_handlers_unblock_by_func(g->edit_button, _enter_edit_mode, self);
1179}
1180
1182{
1183 _event_key_swap(self);
1184}
1185
1187{
1188 // want most square at the end, and the most non-square at the beginning
1189
1190 if((a->d == 0 || a->d == 1) && a->n == 0) return -1;
1191
1192 const float ad = MAX(a->d, a->n);
1193 const float an = MIN(a->d, a->n);
1194 const float bd = MAX(b->d, b->n);
1195 const float bn = MIN(b->d, b->n);
1196 const float aratio = ad / an;
1197 const float bratio = bd / bn;
1198
1199 if(aratio < bratio) return -1;
1200
1201 const float prec = 0.0003f;
1202 if(fabsf(aratio - bratio) < prec) return 0;
1203
1204 return 1;
1205}
1206
1207static gchar *_aspect_format(gchar *original, int adim, int bdim)
1208{
1209 // Special ratios: freehand, original image
1210 if(bdim == 0) return g_strdup(original);
1211
1212 return g_strdup_printf("%s %4.2f", original, (float)adim / (float)bdim);
1213}
1214
1215void gui_init(struct dt_iop_module_t *self)
1216{
1218
1219 g->aspect_list = NULL;
1220 g->clip_x = g->clip_y = g->handle_x = g->handle_y = 0.0;
1221 g->clip_w = g->clip_h = 1.0;
1222 g->clip_max_x = g->clip_max_y = 0.0;
1223 g->clip_max_w = g->clip_max_h = 1.0;
1224 //g->clip_max_pipe_hash = 0;
1225 g->cropping = GRAB_CENTER;
1226 g->shift_hold = FALSE;
1227 g->ctrl_hold = FALSE;
1228 g->editing = FALSE;
1229 g->dragging = FALSE;
1230
1231 GtkWidget *box_enabled = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1232
1233 dt_iop_crop_aspect_t aspects[] = {
1234 { _("freehand"), 0, 0 },
1235 { _("original image"), 1, 0 },
1236 { _("square"), 1, 1 },
1237 { _("7:6, 6x7"), 7, 6 },
1238 { _("10:8 in print"), 2445, 2032 },
1239 { _("6:5, 5x6"), 6, 5 },
1240 { _("5:4, 4x5, 8x10"), 5, 4 },
1241 { _("11x14"), 14, 11 },
1242 { _("8.5x11, letter"), 110, 85 },
1243 { _("4:3, VGA, TV"), 4, 3 },
1244 { _("5x7"), 7, 5 },
1245 { _("ISO 216, DIN 476, A4"), 14142136, 10000000 },
1246 { _("3:2, 4x6, 35mm"), 3, 2 },
1247 { _("16:10, 8x5"), 16, 10 },
1248 { _("golden cut"), 16180340, 10000000 },
1249 { _("5:3, 12x20"), 5, 3 },
1250 { _("16:9, HDTV"), 16, 9 },
1251 { _("widescreen"), 185, 100 },
1252 { _("2:1, Univisium"), 2, 1 },
1253 { _("Cinemascope"), 235, 100 },
1254 { _("21:9"), 7, 3 },
1255 { _("Anamorphic"), 239, 100 },
1256 { _("65:24, XPan"), 65, 24 },
1257 { _("3:1, panorama"), 3, 1 },
1258 { _("4:1, Polyvision"), 4, 1 },
1259 };
1260
1261 const int aspects_count = sizeof(aspects) / sizeof(dt_iop_crop_aspect_t);
1262
1263 for(int i = 0; i < aspects_count; i++)
1264 {
1265 dt_iop_crop_aspect_t *aspect = g_malloc(sizeof(dt_iop_crop_aspect_t));
1266 aspect->name = _aspect_format(aspects[i].name, aspects[i].d, aspects[i].n);
1267 aspect->d = aspects[i].d;
1268 aspect->n = aspects[i].n;
1269 g->aspect_list = g_list_append(g->aspect_list, aspect);
1270 }
1271
1272 // add custom presets from config to the list
1273 GSList *custom_aspects = dt_conf_all_string_entries("plugins/darkroom/clipping/extra_aspect_ratios");
1274 for(GSList *iter = custom_aspects; iter; iter = g_slist_next(iter))
1275 {
1277
1278 const char *c = nv->value;
1279 const char *end = nv->value + strlen(nv->value);
1280 while(*c != ':' && *c != '/' && c < end) c++;
1281 if(c < end - 1)
1282 {
1283 c++;
1284 int d = atoi(nv->value);
1285 int n = atoi(c);
1286 // some sanity check
1287 if(n == 0 || d == 0)
1288 {
1289 fprintf(stderr, "invalid ratio format for `%s'. it should be \"number:number\"\n", nv->key);
1290 dt_control_log(_("invalid ratio format for `%s'. it should be \"number:number\""), nv->key);
1291 continue;
1292 }
1293 dt_iop_crop_aspect_t *aspect = g_malloc(sizeof(dt_iop_crop_aspect_t));
1294 aspect->name = _aspect_format(nv->key, d, n);
1295 aspect->d = d;
1296 aspect->n = n;
1297 g->aspect_list = g_list_append(g->aspect_list, aspect);
1298 }
1299 else
1300 {
1301 fprintf(stderr, "invalid ratio format for `%s'. it should be \"number:number\"\n", nv->key);
1302 dt_control_log(_("invalid ratio format for `%s'. it should be \"number:number\""), nv->key);
1303 continue;
1304 }
1305 }
1306 g_slist_free_full(custom_aspects, dt_conf_string_entry_free);
1307
1308
1309 g->aspect_list = g_list_sort(g->aspect_list, (GCompareFunc)_aspect_ratio_cmp);
1310
1311 // remove duplicates from the aspect ratio list
1312 int d = ((dt_iop_crop_aspect_t *)g->aspect_list->data)->d + 1;
1313 int n = ((dt_iop_crop_aspect_t *)g->aspect_list->data)->n + 1;
1314 for(GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
1315 {
1316 dt_iop_crop_aspect_t *aspect = (dt_iop_crop_aspect_t *)iter->data;
1317 int dd = MIN(aspect->d, aspect->n);
1318 int nn = MAX(aspect->d, aspect->n);
1319 if(dd == d && nn == n)
1320 {
1321 // same as the last one, remove this entry
1322 dt_free(aspect->name);
1323 GList *prev = g_list_previous(iter);
1324 g->aspect_list = g_list_delete_link(g->aspect_list, iter);
1325 // it should never be NULL as the 1st element can't be a duplicate, but better safe than sorry
1326 iter = prev ? prev : g->aspect_list;
1327 }
1328 else
1329 {
1330 d = dd;
1331 n = nn;
1332 }
1333 }
1334
1336 dt_bauhaus_combobox_set_editable(g->aspect_presets, 1);
1337 dt_bauhaus_widget_set_label(g->aspect_presets, N_("aspect"));
1338
1339 for(GList *iter = g->aspect_list; iter; iter = g_list_next(iter))
1340 {
1341 const dt_iop_crop_aspect_t *aspect = iter->data;
1342 dt_bauhaus_combobox_add(g->aspect_presets, aspect->name);
1343 }
1344
1345 dt_bauhaus_combobox_set(g->aspect_presets, 0);
1346
1347 g_signal_connect(G_OBJECT(g->aspect_presets), "value-changed", G_CALLBACK(_event_aspect_presets_changed), self);
1348 gtk_widget_set_tooltip_text(g->aspect_presets, _("set the aspect ratio\n"
1349 "the list is sorted: from most square to least square\n"
1350 "to enter custom aspect ratio open the combobox and type ratio in x:y or decimal format"));
1351 dt_bauhaus_widget_set_quad_toggle(g->aspect_presets, 1);
1353 g_signal_connect(G_OBJECT(g->aspect_presets), "quad-pressed", G_CALLBACK(_event_aspect_flip), self);
1354 gtk_box_pack_start(GTK_BOX(box_enabled), g->aspect_presets, TRUE, TRUE, 0);
1355
1356 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1357
1358 g->edit_button = gtk_toggle_button_new_with_label(_("Edit"));
1359 g_signal_connect(GTK_TOGGLE_BUTTON(g->edit_button), "toggled", G_CALLBACK(_enter_edit_mode), self);
1360 gtk_box_pack_start(GTK_BOX(box), g->edit_button, TRUE, TRUE, 0);
1361
1362 g->commit_button = dt_action_button_new((dt_lib_module_t *)self, N_("Apply"), _event_commit_clicked, self, _("Apply changes"), 0, 0);
1363 gtk_box_pack_start(GTK_BOX(box), g->commit_button, TRUE, TRUE, 0);
1364 gtk_widget_set_sensitive(g->commit_button, FALSE);
1365
1366 gtk_box_pack_end(GTK_BOX(box_enabled), GTK_WIDGET(box), TRUE, TRUE, 0);
1367
1368
1369 // we put margins values under an expander
1371 (&g->cs,
1372 "plugins/darkroom/crop/expand_margins",
1373 _("margins"),
1374 GTK_BOX(box_enabled), GTK_PACK_END);
1375
1376 self->gui->widget = GTK_WIDGET(g->cs.container);
1377
1378 g->cx = dt_bauhaus_slider_from_params(self, "cx");
1381 gtk_widget_set_tooltip_text(g->cx, _("the left margin cannot overlap with the right margin"));
1382
1383 g->cw = dt_bauhaus_slider_from_params(self, "cw");
1385 dt_bauhaus_slider_set_factor(g->cw, -100.0);
1386 dt_bauhaus_slider_set_offset(g->cw, 100.0);
1388 gtk_widget_set_tooltip_text(g->cw, _("the right margin cannot overlap with the left margin"));
1389
1390 g->cy = dt_bauhaus_slider_from_params(self, "cy");
1393 gtk_widget_set_tooltip_text(g->cy, _("the top margin cannot overlap with the bottom margin"));
1394
1395 g->ch = dt_bauhaus_slider_from_params(self, "ch");
1397 dt_bauhaus_slider_set_factor(g->ch, -100.0);
1398 dt_bauhaus_slider_set_offset(g->ch, 100.0);
1400 gtk_widget_set_tooltip_text(g->ch, _("the bottom margin cannot overlap with the top margin"));
1401
1402 self->gui->widget = box_enabled;
1403}
1404
1405static void _aspect_free(gpointer data)
1406{
1408 dt_free(aspect->name);
1409 dt_free(aspect);
1410}
1411
1413{
1415 g_list_free_full(g->aspect_list, _aspect_free);
1416 g->aspect_list = NULL;
1417
1419}
1420
1421static _grab_region_t _gui_get_grab(float pzx, float pzy, dt_iop_crop_gui_data_t *g, const float border,
1422 const float wd, const float ht)
1423{
1424 if(wd == 0.f || ht == 0.f) return GRAB_NONE;
1425
1427 if(!(pzx < g->clip_x || pzx > g->clip_x + g->clip_w || pzy < g->clip_y || pzy > g->clip_y + g->clip_h))
1428 {
1429 // we are inside the crop box
1430
1431 const float x_border = (border == 0.f) ? 0.f : border / wd;
1432 const float y_border = (border == 0.f) ? 0.f : border / ht;
1433
1434 grab = GRAB_CENTER;
1435
1436 if((pzx >= g->clip_x) && pzx < g->clip_x + x_border)
1437 grab |= GRAB_LEFT; // left border
1438
1439 if((pzy >= g->clip_y) && pzy < g->clip_y + y_border)
1440 grab |= GRAB_TOP; // top border
1441
1442 if((pzx <= g->clip_x + g->clip_w) && (pzx > g->clip_w + g->clip_x - x_border))
1443 grab |= GRAB_RIGHT; // right border
1444
1445 if((pzy <= g->clip_y + g->clip_h) && (pzy > g->clip_h + g->clip_y - y_border))
1446 grab |= GRAB_BOTTOM; // bottom border
1447 }
1448 return grab;
1449}
1450
1451// draw guides and handles over the image
1452void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx,
1453 int32_t pointery)
1454{
1455 dt_develop_t *dev = self->dev;
1457 if(IS_NULL_PTR(g)) return;
1458
1460
1463 if(g->wd < 1.0 || g->ht < 1.0) return;
1464
1465 const float zoom_scale = dt_dev_get_overlay_scale(dev);
1466
1467 dt_dev_clip_roi(dev, cr, width, height);
1468 dt_dev_rescale_roi(dev, cr, width, height);
1469
1470 // draw crop area guides
1471 const float guide_x = g->editing ? g->clip_x * g->wd : 0;
1472 const float guide_y = g->editing ? g->clip_y * g->ht : 0;
1473 const float guide_w = g->editing ? g->clip_w * g->wd : g->wd;
1474 const float guide_h = g->editing ? g->clip_h * g->ht : g->ht;
1475
1476 dt_guides_draw(cr, guide_x, guide_y, guide_w, guide_h, zoom_scale);
1477
1478 if(!g->editing) return;
1479
1480 double dashes = DT_PIXEL_APPLY_DPI(5.0) / zoom_scale;
1481 double border_width = dashes / 2.;
1482
1483 // draw cropping window
1484 float pzxpy[2] = { (float)pointerx, (float)pointery };
1486 float pzx = pzxpy[0];
1487 float pzy = pzxpy[1];
1488 cairo_set_line_width(cr, border_width);
1489
1490 if(_set_max_clip(self))
1491 {
1492 cairo_set_source_rgba(cr, .1, .1, .1, .8);
1493 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1494 const float max_x = g->clip_max_x * g->wd;
1495 const float max_y = g->clip_max_y * g->ht;
1496 const float max_w = g->clip_max_w * g->wd;
1497 const float max_h = g->clip_max_h * g->ht;
1498 cairo_rectangle(cr, max_x, max_y, max_w, max_h);
1499
1500 const float clip_x = g->clip_x * g->wd - border_width / 2.;
1501 const float clip_y = g->clip_y * g->ht - border_width / 2.;
1502 const float clip_w = g->clip_w * g->wd + border_width;
1503 const float clip_h = g->clip_h * g->ht + border_width;
1504 cairo_rectangle(cr, clip_x, clip_y, clip_w, clip_h);
1505 cairo_fill(cr);
1506 }
1507 if(g->clip_x > .0f || g->clip_y > .0f || g->clip_w < 1.0f || g->clip_h < 1.0f)
1508 {
1509 const float rect_x = g->clip_x * g->wd - border_width / 2.;
1510 const float rect_y = g->clip_y * g->ht - border_width / 2.;
1511 const float rect_w = g->clip_w * g->wd + border_width;
1512 const float rect_h = g->clip_h * g->ht + border_width;
1513 cairo_rectangle(cr, rect_x, rect_y, rect_w, rect_h);
1515 cairo_stroke(cr);
1516 }
1517
1518 // draw cropping window dimensions if first mouse button is pressed
1520 {
1521 char dimensions[16];
1522 dimensions[0] = '\0';
1523 PangoLayout *layout;
1524 PangoRectangle ext;
1525 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
1526 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1527 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(16) * PANGO_SCALE / zoom_scale);
1528 layout = pango_cairo_create_layout(cr);
1529 pango_layout_set_font_description(layout, desc);
1530
1531 int procw;
1532 int proch;
1533 dt_dev_get_processed_size(dev, &procw, &proch);
1534 snprintf(dimensions, sizeof(dimensions), "%.0f x %.0f", (float)procw * g->clip_w, (float)proch * g->clip_h);
1535
1536 pango_layout_set_text(layout, dimensions, -1);
1537 pango_layout_get_pixel_extents(layout, NULL, &ext);
1538 const float text_w = ext.width;
1539 const float text_h = DT_PIXEL_APPLY_DPI(16 + 2) / zoom_scale;
1540 const float margin = DT_PIXEL_APPLY_DPI(6) / zoom_scale;
1541 float xp = (g->clip_x + g->clip_w * .5f) * g->wd - text_w * .5f;
1542 float yp = (g->clip_y + g->clip_h * .5f) * g->ht - text_h * .5f;
1543
1544 // ensure that the rendered string remains visible within the window bounds
1545 double x1;
1546 double y1;
1547 double x2;
1548 double y2;
1549 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1550 xp = CLAMPF(xp, x1 + 2.0 * margin, x2 - text_w - 2.0 * margin);
1551 yp = CLAMPF(yp, y1 + 2.0 * margin, y2 - text_h - 2.0 * margin);
1552
1553 cairo_set_source_rgba(cr, .5, .5, .5, .9);
1554 dt_gui_draw_rounded_rectangle(cr, text_w + 2 * margin, text_h + 2 * margin, xp - margin, yp - margin);
1555 cairo_set_source_rgb(cr, .7, .7, .7);
1556 cairo_move_to(cr, xp, yp);
1557 pango_cairo_show_layout(cr, layout);
1558 pango_font_description_free(desc);
1559 g_object_unref(layout);
1560 }
1561
1562 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
1564 const int border = DT_PIXEL_APPLY_DPI(30.0) / zoom_scale;
1565
1566 const _grab_region_t grab = g->cropping ? g->cropping : _gui_get_grab(pzx, pzy, g, border, g->wd, g->ht);
1567
1568 // TODO: check if the variables change from the same calculation some lines above
1569 const float clip_x_px = g->clip_x * g->wd;
1570 const float clip_y_px = g->clip_y * g->ht;
1571 const float clip_w_px = g->clip_w * g->wd;
1572 const float clip_h_px = g->clip_h * g->ht;
1573 const float clip_right_px = clip_x_px + clip_w_px;
1574 const float clip_bottom_px = clip_y_px + clip_h_px;
1575
1576 if(grab == GRAB_LEFT) cairo_rectangle(cr, clip_x_px, clip_y_px, border, clip_h_px);
1577 if(grab == GRAB_TOP) cairo_rectangle(cr, clip_x_px, clip_y_px, clip_w_px, border);
1578 if(grab == GRAB_TOP_LEFT) cairo_rectangle(cr, clip_x_px, clip_y_px, border, border);
1579 if(grab == GRAB_RIGHT) cairo_rectangle(cr, clip_right_px - border, clip_y_px, border, clip_h_px);
1580 if(grab == GRAB_BOTTOM) cairo_rectangle(cr, clip_x_px, clip_bottom_px - border, clip_w_px, border);
1581 if(grab == GRAB_BOTTOM_RIGHT) cairo_rectangle(cr, clip_right_px - border, clip_bottom_px - border, border, border);
1582 if(grab == GRAB_TOP_RIGHT) cairo_rectangle(cr, clip_right_px - border, clip_y_px, border, border);
1583 if(grab == GRAB_BOTTOM_LEFT) cairo_rectangle(cr, clip_x_px, clip_bottom_px - border, border, border);
1584 cairo_stroke(cr);
1585}
1586
1587int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
1588{
1589 dt_develop_t *dev = self->dev;
1591 if(!g->editing) return 0;
1592
1593 float pzxpy[2] = { (float)x, (float)y };
1595 float pzx = pzxpy[0];
1596 float pzy = pzxpy[1];
1597 const float zoom_scale = dt_dev_get_overlay_scale(dev);
1598 const int border = DT_PIXEL_APPLY_DPI(30.0) / zoom_scale;
1599
1600 const _grab_region_t grab = _gui_get_grab(pzx, pzy, g, border, g->wd, g->ht);
1601
1602 _set_max_clip(self);
1603
1604 if(g->dragging)
1605 {
1606 // draw a light gray frame, to show it's not stored yet:
1607 // first mouse button, adjust cropping frame, but what do we do?
1608 const float bzx = g->button_down_zoom_x;
1609 const float bzy = g->button_down_zoom_y;
1610
1611 if(g->cropping == GRAB_ALL)
1612 {
1613 /* moving the crop window */
1614 if(!g->shift_hold)
1615 g->clip_x
1616 = fminf(g->clip_max_w + g->clip_max_x - g->clip_w,
1617 fmaxf(g->clip_max_x, g->handle_x + pzx - bzx));
1618
1619 if(!g->ctrl_hold)
1620 g->clip_y
1621 = fminf(g->clip_max_h + g->clip_max_y - g->clip_h,
1622 fmaxf(g->clip_max_y, g->handle_y + pzy - bzy));
1623 }
1624 else
1625 {
1626 /* changing the crop window */
1627 if(g->shift_hold)
1628 {
1629 /* the center is locked, scale crop radial with locked ratio */
1630 float xx = 0.0f;
1631 float yy = 0.0f;
1632
1633 if(g->cropping & GRAB_LEFT || g->cropping & GRAB_RIGHT)
1634 xx = (g->cropping & GRAB_LEFT) ? (pzx - bzx) : (bzx - pzx);
1635 if(g->cropping & GRAB_TOP || g->cropping & GRAB_BOTTOM)
1636 yy = (g->cropping & GRAB_TOP) ? (pzy - bzy) : (bzy - pzy);
1637
1638 float ratio = fmaxf((g->prev_clip_w - 2.0f * xx) / g->prev_clip_w,
1639 (g->prev_clip_h - 2.0f * yy) / g->prev_clip_h);
1640
1641 // ensure we don't get too small crop size
1642 if(g->prev_clip_w * ratio < 0.1f) ratio = 0.1f / g->prev_clip_w;
1643 if(g->prev_clip_h * ratio < 0.1f) ratio = 0.1f / g->prev_clip_h;
1644
1645 // ensure we don't have too big crop size
1646 if(g->prev_clip_w * ratio > g->clip_max_w) ratio = g->clip_max_w / g->prev_clip_w;
1647 if(g->prev_clip_h * ratio > g->clip_max_h) ratio = g->clip_max_h / g->prev_clip_h;
1648
1649 // now that we are sure that the crop size is correct, we have to adjust top & left
1650 float nx = g->prev_clip_x - (g->prev_clip_w * ratio - g->prev_clip_w) / 2.0f;
1651 float ny = g->prev_clip_y - (g->prev_clip_h * ratio - g->prev_clip_h) / 2.0f;
1652 float nw = g->prev_clip_w * ratio;
1653 float nh = g->prev_clip_h * ratio;
1654
1655 // move crop area to the right if needed
1656 nx = fmaxf(nx, g->clip_max_x);
1657 // move crop area to the left if needed
1658 nx = fminf(nx, g->clip_max_w + g->clip_max_x - nw);
1659 // move crop area to the bottom if needed
1660 ny = fmaxf(ny, g->clip_max_y);
1661 // move crop area to the top if needed
1662 ny = fminf(ny, g->clip_max_h + g->clip_max_y - nh);
1663
1664 g->clip_x = nx;
1665 g->clip_y = ny;
1666 g->clip_w = nw;
1667 g->clip_h = nh;
1668 }
1669 else
1670 {
1671 if(g->cropping & GRAB_LEFT)
1672 {
1673 const float old_clip_x = g->clip_x;
1674 g->clip_x = fminf(fmaxf(g->clip_max_x, pzx - g->handle_x), g->clip_x + g->clip_w - 0.1f);
1675 g->clip_w = old_clip_x + g->clip_w - g->clip_x;
1676 }
1677 if(g->cropping & GRAB_TOP)
1678 {
1679 const float old_clip_y = g->clip_y;
1680 g->clip_y = fminf(fmaxf(g->clip_max_y, pzy - g->handle_y), g->clip_y + g->clip_h - 0.1f);
1681 g->clip_h = old_clip_y + g->clip_h - g->clip_y;
1682 }
1683 if(g->cropping & GRAB_RIGHT)
1684 g->clip_w = fmaxf(0.1f, fminf(g->clip_max_w + g->clip_max_x, pzx - g->clip_x - g->handle_x));
1685 if(g->cropping & GRAB_BOTTOM)
1686 g->clip_h = fmaxf(0.1f, fminf(g->clip_max_h + g->clip_max_y, pzy - g->clip_y - g->handle_y));
1687 }
1688
1689 if(g->clip_x + g->clip_w > g->clip_max_w + g->clip_max_x)
1690 g->clip_w = g->clip_max_w + g->clip_max_x - g->clip_x;
1691 if(g->clip_y + g->clip_h > g->clip_max_h + g->clip_max_y)
1692 g->clip_h = g->clip_max_h + g->clip_max_y - g->clip_y;
1693 }
1694
1695 _aspect_apply(self, g->cropping);
1696 gui_changed(self, NULL, NULL);
1698 return 1;
1699 }
1700 else if(grab)
1701 {
1702 // hover over active borders, no button pressed
1703 // change mouse pointer
1704 if(grab == GRAB_LEFT)
1705 dt_control_queue_cursor(GDK_LEFT_SIDE);
1706 else if(grab == GRAB_TOP)
1707 dt_control_queue_cursor(GDK_TOP_SIDE);
1708 else if(grab == GRAB_RIGHT)
1709 dt_control_queue_cursor(GDK_RIGHT_SIDE);
1710 else if(grab == GRAB_BOTTOM)
1711 dt_control_queue_cursor(GDK_BOTTOM_SIDE);
1712 else if(grab == GRAB_TOP_LEFT)
1713 dt_control_queue_cursor(GDK_TOP_LEFT_CORNER);
1714 else if(grab == GRAB_TOP_RIGHT)
1715 dt_control_queue_cursor(GDK_TOP_RIGHT_CORNER);
1716 else if(grab == GRAB_BOTTOM_RIGHT)
1717 dt_control_queue_cursor(GDK_BOTTOM_RIGHT_CORNER);
1718 else if(grab == GRAB_BOTTOM_LEFT)
1719 dt_control_queue_cursor(GDK_BOTTOM_LEFT_CORNER);
1720 else if(grab == GRAB_NONE)
1721 {
1723 dt_control_queue_cursor(GDK_LEFT_PTR);
1724 }
1725 if(grab != GRAB_NONE)
1726 dt_control_hinter_message(dt_control_get_global(), _("<b>resize</b>: drag, <b>keep aspect ratio</b>: shift+drag"));
1728 }
1729 else
1730 {
1731 dt_control_queue_cursor(GDK_FLEUR);
1732 g->cropping = GRAB_CENTER;
1733 dt_control_hinter_message(dt_control_get_global(), _("<b>move</b>: drag, <b>move vertically</b>: shift+drag, <b>move horizontally</b>: ctrl+drag"));
1735 }
1736 return 0;
1737}
1738
1739int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
1740{
1742 if(!g->editing) return 0;
1743
1744 /* reset internal ui states*/
1745 g->shift_hold = FALSE;
1746 g->ctrl_hold = FALSE;
1747 g->cropping = GRAB_CENTER;
1748 g->dragging = FALSE;
1749
1750 // FIXME: is this the right cursor? there should be a "restore_cursor" function
1751 dt_control_change_cursor(GDK_LEFT_PTR);
1752
1753 // we save the crop into the params now so params are kept in synch with gui settings
1754 gui_changed(self, NULL, NULL);
1755 return 1;
1756}
1757
1758int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
1759 uint32_t state)
1760{
1762 if(!g->editing) return 0;
1763
1764 // avoid unexpected back to lt mode:
1765 if(type == GDK_2BUTTON_PRESS && which == 1)
1766 return 1;
1767
1768 if(which == 1)
1769 {
1770 dt_develop_t *dev = self->dev;
1771 float pzxpy[2] = { (float)x, (float)y };
1773 float pzx = pzxpy[0];
1774 float pzy = pzxpy[1];
1775 const float zoom_scale = dt_dev_get_overlay_scale(dev);
1776
1777 g->button_down_x = x;
1778 g->button_down_y = y;
1779
1780 g->button_down_zoom_x = pzx;
1781 g->button_down_zoom_y = pzy;
1782
1783 /* update prev clip box with current */
1784 g->prev_clip_x = g->clip_x;
1785 g->prev_clip_y = g->clip_y;
1786 g->prev_clip_w = g->clip_w;
1787 g->prev_clip_h = g->clip_h;
1788
1789 /* if shift is pressed, then lock crop on center */
1790 if(dt_modifiers_include(state, GDK_SHIFT_MASK)) g->shift_hold = TRUE;
1791 if(dt_modifiers_include(state, DT_PRIMARY_MASK)) g->ctrl_hold = TRUE;
1792
1793 /* store grabbed area */
1794
1795 const float border = DT_PIXEL_APPLY_DPI(30.0) / zoom_scale;
1796
1797 g->cropping = _gui_get_grab(pzx, pzy, g, border, g->wd, g->ht);
1798
1799 if(g->cropping != GRAB_NONE)
1800 {
1801 g->dragging = TRUE;
1802
1803 if(g->cropping == GRAB_CENTER)
1804 {
1805 g->cropping = GRAB_ALL;
1806 g->handle_x = g->clip_x;
1807 g->handle_y = g->clip_y;
1808 }
1809 else
1810 {
1811 if(g->cropping & GRAB_LEFT) g->handle_x = pzx - g->clip_x;
1812 if(g->cropping & GRAB_TOP) g->handle_y = pzy - g->clip_y;
1813 if(g->cropping & GRAB_RIGHT) g->handle_x = pzx - (g->clip_w + g->clip_x);
1814 if(g->cropping & GRAB_BOTTOM) g->handle_y = pzy - (g->clip_h + g->clip_y);
1815 }
1816 }
1817 return 1;
1818 }
1819 else if(which == 3)
1820 {
1821 // we reset cropping
1822 g->clip_x = 0.0f;
1823 g->clip_y = 0.0f;
1824 g->clip_w = 1.0f;
1825 g->clip_h = 1.0f;
1827 return 1;
1828 }
1829 else
1830 return 0;
1831}
1832
1834{
1836 if(!g->editing) return 0;
1837
1838 g->shift_hold = FALSE;
1839 g->ctrl_hold = FALSE;
1840 g->cropping = GRAB_NONE;
1841 g->dragging = FALSE;
1842
1843 dt_control_change_cursor(GDK_LEFT_PTR);
1844
1845 return 1;
1846}
1847
1848// #undef PHI
1849// #undef INVPHI
1850
1851// clang-format off
1852// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1853// vim: shiftwidth=2 expandtab tabstop=2 cindent
1854// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1855// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
void dt_bauhaus_combobox_set_editable(GtkWidget *widget, int editable)
Definition bauhaus.c:1882
void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2017
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
Definition bauhaus.c:1572
void dt_bauhaus_slider_set_offset(GtkWidget *widget, float offset)
Definition bauhaus.c:3430
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1474
void dt_bauhaus_slider_set_soft_min(GtkWidget *widget, float val)
Definition bauhaus.c:1457
const char * dt_bauhaus_combobox_get_text(GtkWidget *widget)
Definition bauhaus.c:1970
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1554
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3423
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
_grab_region_t
Definition clipping.c:147
void dt_gui_new_collapsible_section(dt_gui_collapsible_section_t *cs, const char *confname, const char *label, GtkBox *parent, GtkPackType pack)
Create a collapsible section and pack it into the parent box.
void dt_gui_update_collapsible_section(dt_gui_collapsible_section_t *cs)
@ IOP_CS_RGB
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
const float top
void dt_conf_string_entry_free(gpointer data)
Free one dt_conf_string_entry_t. Shaped as a GDestroyNotify so it can be handed to g_slist_free_full(...
void dt_conf_set_int(const char *name, int val)
GSList * dt_conf_all_string_entries(const char *dir)
Every key under dir, as a list of copies.
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
gboolean dt_image_get_usercrop_oriented(const dt_image_t *img, const dt_image_orientation_t orientation, dt_boundingbox_t box)
dt_image_orientation_t dt_image_get_effective_orientation(const dt_image_t *img)
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:981
void dt_control_navigation_redraw()
Request a redraw of the navigation module's widget.
Definition control.c:929
gboolean dt_control_button_down(int which)
Is mouse button which currently held down?
Definition control.c:563
#define dt_control_change_cursor(cursor)
Definition control.h:121
#define dt_control_queue_cursor(cursor)
Definition control.h:140
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
int operation_tags()
Definition crop.c:181
int operation_tags_filter()
Definition crop.c:186
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition crop.c:446
static void _crop_map_size(const dt_iop_crop_data_t *const d, const dt_iop_roi_t *const roi_in, dt_iop_roi_t *roi_out)
Input rect -> output rect. THE size evaluator.
Definition crop.c:317
void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const float *const in, float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition crop.c:382
const char ** description(struct dt_iop_module_t *self)
Definition crop.c:163
int default_group()
Definition crop.c:170
int distort_backtransform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count)
Definition crop.c:362
static gchar * _aspect_format(gchar *original, int adim, int bdim)
Definition crop.c:1207
int distort_transform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count)
Definition crop.c:342
static void _float_to_fract(const char *num, int *n, int *d)
Definition crop.c:819
static int _crop_geometry_backtransform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition crop.c:473
static void _crop_offset(const dt_iop_crop_data_t *const d, const dt_iop_roi_t *const roi_in, float *left, float *top)
The translation the crop applies, in the input rect's own pixels.
Definition crop.c:335
const char * aliases()
Definition crop.c:158
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition crop.c:518
static const dt_geometry_vtable_t _crop_geometry_vtable
Definition crop.c:487
const char * name()
Definition crop.c:153
void gui_reset(struct dt_iop_module_t *self)
Definition crop.c:1047
gboolean geometry_record(dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
Definition crop.c:493
void gui_update(struct dt_iop_module_t *self)
Definition crop.c:1054
void gui_init(struct dt_iop_module_t *self)
Definition crop.c:1215
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition crop.c:1758
static void _crop_resolve(dt_iop_module_t *self, const dt_iop_crop_params_t *const p, dt_iop_crop_data_t *out)
Resolve the effective crop rectangle. THE constructor – see the note above.
Definition crop.c:294
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition crop.c:996
int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
Definition crop.c:1739
static void _aspect_apply(dt_iop_module_t *self, _grab_region_t grab)
Definition crop.c:662
static void _crop_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
Definition crop.c:454
void reload_defaults(dt_iop_module_t *self)
Definition crop.c:769
static void _enter_edit_mode(GtkToggleButton *button, struct dt_iop_module_t *self)
Definition crop.c:1115
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition crop.c:192
int flags()
Definition crop.c:175
void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition crop.c:1452
static void _commit_box(dt_iop_module_t *self, dt_iop_crop_gui_data_t *g, dt_iop_crop_params_t *p)
Definition crop.c:211
void gui_cleanup(struct dt_iop_module_t *self)
Definition crop.c:1412
int mouse_leave(struct dt_iop_module_t *self)
Definition crop.c:1833
static int _crop_geometry_transform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition crop.c:459
static void _aspect_free(gpointer data)
Definition crop.c:1405
dt_iop_crop_flip_t
Definition crop.c:70
@ FLAG_FLIP_VERTICAL
Definition crop.c:72
@ FLAG_FLIP_HORIZONTAL
Definition crop.c:71
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition crop.c:418
static void _params_to_gui(dt_iop_crop_params_t *p, dt_iop_crop_gui_data_t *g)
Definition crop.c:197
static void _event_aspect_presets_changed(GtkWidget *combo, dt_iop_module_t *self)
Definition crop.c:855
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition crop.c:524
static void _event_key_swap(dt_iop_module_t *self)
Definition crop.c:1107
gboolean has_defaults(struct dt_iop_module_t *self)
Definition crop.c:811
static gboolean _set_max_clip(struct dt_iop_module_t *self)
this function initializes the maximum clip rectangle from crop parameters and correct the clip rectan...
Definition crop.c:253
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
Definition crop.c:428
int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
Definition crop.c:1587
_grab_region_t
Definition crop.c:92
@ GRAB_VERTICAL
Definition crop.c:103
@ GRAB_ALL
Definition crop.c:104
@ GRAB_BOTTOM
Definition crop.c:97
@ GRAB_BOTTOM_RIGHT
Definition crop.c:100
@ GRAB_NONE
Definition crop.c:105
@ GRAB_HORIZONTAL
Definition crop.c:102
@ GRAB_TOP_RIGHT
Definition crop.c:99
@ GRAB_TOP_LEFT
Definition crop.c:98
@ GRAB_RIGHT
Definition crop.c:96
@ GRAB_TOP
Definition crop.c:95
@ GRAB_BOTTOM_LEFT
Definition crop.c:101
@ GRAB_LEFT
Definition crop.c:94
@ GRAB_CENTER
Definition crop.c:93
static _grab_region_t _gui_get_grab(float pzx, float pzy, dt_iop_crop_gui_data_t *g, const float border, const float wd, const float ht)
Definition crop.c:1421
static void _event_commit_clicked(GtkButton *button, dt_iop_module_t *self)
Definition crop.c:1160
static void _event_aspect_flip(GtkWidget *button, dt_iop_module_t *self)
Definition crop.c:1181
void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
Definition crop.c:402
void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *roi_in)
Definition crop.c:394
static gint _aspect_ratio_cmp(const dt_iop_crop_aspect_t *a, const dt_iop_crop_aspect_t *b)
Definition crop.c:1186
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition crop.c:147
static float _aspect_ratio_get(dt_iop_module_t *self, GtkWidget *combo)
Definition crop.c:530
gboolean runtime_data_hash(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition crop.c:509
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_resync_history_all(dev)
int32_t dt_dev_roi_request_preview_height(const dt_develop_t *dev)
int32_t dt_dev_roi_request_preview_width(const dt_develop_t *dev)
void dt_dev_get_processed_size(const dt_develop_t *dev, int *procw, int *proch)
Definition develop.c:1109
void dt_dev_coordinates_preview_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1306
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:353
int dt_dev_distort_backtransform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The inverse of dt_dev_distort_transform_gui(), same rules.
Definition develop.c:1699
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1959
gboolean dt_dev_module_geometry_gui(dt_develop_t *dev, dt_iop_module_t *module, dt_iop_roi_t *in, dt_iop_roi_t *out)
One module's own input and output rectangles at full resolution, from the geometry service.
Definition develop.c:1751
int dt_dev_distort_transform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The GUI's bounded transform folds, composed by the geometry service.
Definition develop.c:1691
gboolean dt_dev_clip_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Clip the view to the ROI. WARNING: this must be done before any translation.
Definition develop.c:2028
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:2071
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1144
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:147
static void dt_gui_draw_rounded_rectangle(cairo_t *cr, float width, float height, float x, float y)
Definition draw.h:554
Where things are on the image, answered without a pipeline.
#define DT_GUI_MODULE(x)
void dt_guides_draw(cairo_t *cr, const float left, const float top, const float width, const float height, const float zoom_scale)
Definition guides.c:800
float dt_boundingbox_t[4]
Definition image.h:83
@ DT_IMAGE_AUTO_PRESETS_APPLIED
Definition image.h:130
void dt_iop_copy_image_roi(float *const __restrict__ out, const float *const __restrict__ in, const size_t ch, const dt_iop_roi_t *const __restrict__ roi_in, const dt_iop_roi_t *const __restrict__ roi_out, const int zero_pad)
Definition imagebuf.c:163
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1669
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
@ IOP_FLAGS_GUIDES_SPECIAL_DRAW
Definition imageop.h:197
@ IOP_FLAGS_TILING_FULL_ROI
Definition imageop.h:190
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
@ IOP_TAG_DECORATION
Definition imageop.h:171
@ IOP_TAG_CLIPPING
Definition imageop.h:172
@ IOP_TAG_DISTORT
Definition imageop.h:170
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
Which mouse buttons are held, asked of the toolkit rather than remembered.
const struct dt_interpolation * dt_interpolation_new(enum dt_interpolation_type type)
@ DT_INTERPOLATION_USERPREF_WARP
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1336
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define PHI
Definition math.h:69
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2679
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
const float uint32_t state[4]
One key/value pair handed back by dt_conf_all_string_entries().
Definition conf.h:126
char * key
Definition conf.h:128
char * value
Definition conf.h:130
struct dt_iop_module_t *void * data
dt_image_t image_storage
Definition develop.h:225
One module instance's contribution, as data.
Definition geometry.h:99
dt_iop_roi_t in
Definition geometry.h:117
const dt_geometry_vtable_t * vtable
Definition geometry.h:110
void(* free_data)(void *data)
Definition geometry.h:112
A module's geometry, evaluated. Pure functions of the record's own data.
Definition geometry.h:75
void(* map_size)(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
Full-resolution input rect -> output rect. Mirrors modify_roi_out() at scale 1.
Definition geometry.h:77
int32_t flags
Definition image.h:401
GList * aspect_list
Definition crop.c:111
GtkWidget * cy
Definition crop.c:110
float button_down_zoom_y
Definition crop.c:117
gboolean dragging
Definition crop.c:134
gboolean shift_hold
Definition crop.c:132
GtkWidget * aspect_presets
Definition crop.c:112
dt_gui_collapsible_section_t cs
Definition crop.c:135
GtkWidget * edit_button
Definition crop.c:113
GtkWidget * cx
Definition crop.c:110
float button_down_zoom_x
Definition crop.c:117
gboolean ctrl_hold
Definition crop.c:133
GtkWidget * commit_button
Definition crop.c:114
GtkWidget * ch
Definition crop.c:110
GtkWidget * cw
Definition crop.c:110
dt_iop_crop_params_t previous_params
Definition crop.c:137
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
gboolean default_enabled
Definition imageop.h:318
gboolean enabled
Definition imageop.h:313
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
static gboolean dt_modifiers_include(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_aspectflip(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)