Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
coordinates.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20
21#include "iop/drawlayer/conf.h" // dt_drawlayer_conf_size()
22
23#include "develop/develop.h" // dt_dev_coordinates_*(), dt_dev_distort_*_gui(),
24 // dt_dev_module_geometry_gui(), DT_DEV_TRANSFORM_DIR_FORW_EXCL
25#include "develop/geometry/geometry.h" // dt_geometry_chain_find(), dt_geometry_record_t
26
27#include <string.h>
28
29/* --- where the raw-anchored canvas sits in this module's frame ------------------------------
30 *
31 * One derivation, three callers -- the GUI placing a dab, the GUI drawing its overlays, and the
32 * pipe compositing. They MUST agree: a dab is placed by the first and rendered by the last, and a
33 * disagreement puts the paint somewhere other than where the cursor was.
34 */
35
55{
56 dt_iop_roi_t in = { 0 };
57 dt_iop_roi_t out = { 0 };
58 gboolean found = FALSE;
59
60 if(IS_NULL_PTR(pipe))
61 {
62 const dt_geometry_record_t *const record = dt_geometry_chain_find(self->dev->geometry_chain, "flip", 0);
63 if(!IS_NULL_PTR(record))
64 {
65 in = record->in;
66 out = record->out;
67 found = TRUE;
68 }
69 }
70 else
71 {
72 for(const GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
73 {
74 const dt_dev_pixelpipe_iop_t *const piece = (const dt_dev_pixelpipe_iop_t *)node->data;
75 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module) || strcmp(piece->module->op, "flip")) continue;
76 in = piece->buf_in;
77 out = piece->buf_out;
78 found = TRUE;
79 break;
80 }
81 }
82
83 if(!found || in.width <= 0 || in.height <= 0) return FALSE;
84 return (out.width == in.height) && (out.height == in.width) && (in.width != in.height);
85}
86
88 int *height)
89{
90 if(!IS_NULL_PTR(width)) *width = 0;
91 if(!IS_NULL_PTR(height)) *height = 0;
92 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev)) return FALSE;
93
94 int32_t raw_width = 0;
95 int32_t raw_height = 0;
96 if(!dt_dev_geometry_get_raw_size(self->dev, &raw_width, &raw_height)) return FALSE;
97 if(raw_width <= 0 || raw_height <= 0) return FALSE;
98
99 /* A raw buffer is landscape; a portrait image is that buffer seen rotated a quarter turn, and
100 * the paint is rotated with it. Carried by transposing the canvas rather than by rotating
101 * pixels: the layer the user paints, and the page the sidecar holds, are both in the
102 * orientation they see. */
103 const gboolean swap = _flip_swaps_axes(self, pipe);
104
105 if(!IS_NULL_PTR(width)) *width = swap ? raw_height : raw_width;
106 if(!IS_NULL_PTR(height)) *height = swap ? raw_width : raw_height;
107 return TRUE;
108}
109
111{
113}
114
116 const dt_iop_roi_t *const frame,
118{
119 if(IS_NULL_PTR(placement)) return FALSE;
120 *placement = (dt_drawlayer_raw_placement_t){ 0 };
121 if(IS_NULL_PTR(frame) || frame->width <= 0 || frame->height <= 0) return FALSE;
122
123 int canvas_width = 0;
124 int canvas_height = 0;
125 if(!dt_drawlayer_layer_canvas_for_pipe(self, pipe, &canvas_width, &canvas_height)) return FALSE;
126
127 /* The frame's ORIGIN, not the difference of the sizes.
128 *
129 * The size fold that produces this rect (dt_dev_pixelpipe_get_roi_out, and the chain's mirror of
130 * it) runs at scale 1 from the raw frame, and a crop records itself as an OFFSET in that frame:
131 * measured on a cropped NEF, crop maps in=(0,0,4016x6016) to out=(0,2802,2464x3213). So the
132 * frame's position inside the raw image is exactly frame->x/y, and translating the canvas by it
133 * is what makes the paint stay on the content -- for a crop that moves without resizing as much
134 * as for one that resizes.
135 *
136 * Centring on the sizes alone, which this did first, gets the uncropped case right and every
137 * moved crop wrong: two crops of equal size at different positions have the same size difference
138 * and therefore the same offset, so the layer followed the frame instead of the image. The
139 * uncropped case is still centre-on-centre, because there the origin is 0 and the extents match.
140 *
141 * A module that GROWS the frame (a perspective correction) leaves the origin at 0 while the
142 * extent exceeds the canvas, so the canvas anchors to the frame's corner rather than its centre.
143 * That is a consequence of being immune to that module -- there is no offset in this frame that
144 * both ignores the correction and re-centres against it. */
145 placement->offset_x = frame->x;
146 placement->offset_y = frame->y;
147 placement->valid = TRUE;
148 return TRUE;
149}
150
152 int *frame_width, int *frame_height)
153{
154 if(!IS_NULL_PTR(frame_width)) *frame_width = 0;
155 if(!IS_NULL_PTR(frame_height)) *frame_height = 0;
156 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(placement)) return FALSE;
157
158 dt_iop_roi_t frame;
159 if(!dt_dev_module_geometry_gui(self->dev, self, &frame, NULL)) return FALSE;
160 if(frame.width <= 0 || frame.height <= 0) return FALSE;
161
162 if(!IS_NULL_PTR(frame_width)) *frame_width = frame.width;
163 if(!IS_NULL_PTR(frame_height)) *frame_height = frame.height;
164 return dt_drawlayer_raw_placement_for_frame(self, NULL, &frame, placement);
165}
166
171
172gboolean dt_drawlayer_widget_points_to_layer_coords(dt_iop_module_t *self, float *pts, const int count)
173{
174 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(pts) || count <= 0) return FALSE;
175
178
181 return FALSE;
183
184 /* ...which lands in this module's own frame, normalised. Finish through the SAME placement the
185 * renderer uses: a dab has to be written where the composite will read it from. */
187 int frame_width = 0;
188 int frame_height = 0;
189 if(!dt_drawlayer_raw_placement_gui(self, &placement, &frame_width, &frame_height)) return FALSE;
190
191 for(int i = 0; i < count; i++)
192 {
193 pts[2 * i] = (float)placement.offset_x + pts[2 * i] * (float)frame_width;
194 pts[2 * i + 1] = (float)placement.offset_y + pts[2 * i + 1] * (float)frame_height;
195 }
196 return TRUE;
197}
198
199gboolean dt_drawlayer_layer_points_to_widget_coords(dt_iop_module_t *self, float *pts, const int count)
200{
201 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(pts) || count <= 0) return FALSE;
202
203 // Canvas pixels back to this module's own frame, normalised. Inverse of the above.
205 int frame_width = 0;
206 int frame_height = 0;
207 if(!dt_drawlayer_raw_placement_gui(self, &placement, &frame_width, &frame_height)) return FALSE;
208
209 for(int i = 0; i < count; i++)
210 {
211 pts[2 * i] = (pts[2 * i] - (float)placement.offset_x) / (float)frame_width;
212 pts[2 * i + 1] = (pts[2 * i + 1] - (float)placement.offset_y) / (float)frame_height;
213 }
214
216
219 return FALSE;
220
223 return TRUE;
224}
225
226gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy,
227 float *lx, float *ly)
228{
229 if(IS_NULL_PTR(lx) || IS_NULL_PTR(ly)) return FALSE;
230
231 float pt[2] = { (float)wx, (float)wy };
232 if(!dt_drawlayer_widget_points_to_layer_coords(self, pt, 1)) return FALSE;
233
234 *lx = pt[0];
235 *ly = pt[1];
236 return TRUE;
237}
238
239gboolean dt_drawlayer_layer_to_widget_coords(dt_iop_module_t *self, const float x, const float y,
240 float *wx, float *wy)
241{
242 if(IS_NULL_PTR(wx) || IS_NULL_PTR(wy)) return FALSE;
243
244 float pt[2] = { x, y };
245 if(!dt_drawlayer_layer_points_to_widget_coords(self, pt, 1)) return FALSE;
246 *wx = pt[0];
247 *wy = pt[1];
248 return TRUE;
249}
250
251gboolean dt_drawlayer_layer_bounds_to_widget_bounds(dt_iop_module_t *self, const float x0, const float y0,
252 const float x1, const float y1,
253 float *left, float *top,
254 float *right, float *bottom)
255{
256 float pts[8] = {
257 x0, y0, x1, y0, x0, y1, x1, y1,
258 };
259
260 if(!dt_drawlayer_layer_points_to_widget_coords(self, pts, 4)) return FALSE;
261
262 float min_x = pts[0];
263 float max_x = pts[0];
264 float min_y = pts[1];
265 float max_y = pts[1];
266 for(int i = 1; i < 4; i++)
267 {
268 min_x = fminf(min_x, pts[2 * i]);
269 max_x = fmaxf(max_x, pts[2 * i]);
270 min_y = fminf(min_y, pts[2 * i + 1]);
271 max_y = fmaxf(max_y, pts[2 * i + 1]);
272 }
273
274 if(!IS_NULL_PTR(left)) *left = min_x;
275 if(!IS_NULL_PTR(top)) *top = min_y;
276 if(!IS_NULL_PTR(right)) *right = max_x;
277 if(!IS_NULL_PTR(bottom)) *bottom = max_y;
278 return TRUE;
279}
280
282 const float fallback)
283{
284 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(dab)) return fallback;
285
286 float pts[6] = {
287 dab->x, dab->y, dab->x + dab->radius, dab->y, dab->x, dab->y + dab->radius,
288 };
289
290 if(!dt_drawlayer_layer_points_to_widget_coords(self, pts, 3)) return fallback;
291
292 const float rx = hypotf(pts[2] - pts[0], pts[3] - pts[1]);
293 const float ry = hypotf(pts[4] - pts[0], pts[5] - pts[1]);
294 const float radius = 0.5f * (rx + ry);
295 return fmaxf(0.5f, isfinite(radius) ? radius : fallback);
296}
297
298/* Reached from process() through the runtime manager, and from every stroke motion --
299 * _fill_runtime_inputs() calls it once per update just to answer `padding_changed'.
300 *
301 * It used to build a whole dab to do that, reading the brush hardness and shape from conf
302 * alongside the size and then using NEITHER: the padding is a function of the radius only.
303 * Those two reads were not merely redundant arithmetic, they were two acquisitions of the
304 * application-wide conf mutex -- the one the GUI thread holds constantly -- on the realtime
305 * painting path, per motion event. Three acquisitions where one is owed.
306 *
307 * Keep it that way: this answers a question about the radius, so it reads the radius. */
309{
310 return ceilf(fmaxf(dt_drawlayer_conf_size(), 0.5f) + 1.0f);
311}
312
314{
315 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(view)) return FALSE;
316
317 int layer_width = 0;
318 int layer_height = 0;
320
321 const float widget_w = (float)dt_dev_viewport_widget_width(self->dev);
322 const float widget_h = (float)dt_dev_viewport_widget_height(self->dev);
323 const float preview_w = dt_dev_roi_request_preview_width(self->dev);
324 const float preview_h = dt_dev_roi_request_preview_height(self->dev);
325 if(widget_w <= 0.0f || widget_h <= 0.0f || preview_w <= 0.0f || preview_h <= 0.0f) return FALSE;
326
327 const float zoom_scale = dt_dev_get_overlay_scale(self->dev);
328 const float border = (float)dt_dev_viewport_border_size(self->dev);
329 const float roi_w = fminf(widget_w, preview_w * zoom_scale);
330 const float roi_h = fminf(widget_h, preview_h * zoom_scale);
331 const float rec_x = fmaxf(border, 0.5f * (widget_w - roi_w));
332 const float rec_y = fmaxf(border, 0.5f * (widget_h - roi_h));
333 const float rec_w = fminf(widget_w - 2.0f * border, roi_w);
334 const float rec_h = fminf(widget_h - 2.0f * border, roi_h);
335 if(rec_w <= 0.0f || rec_h <= 0.0f) return FALSE;
336
337 float pts[8] = {
338 rec_x, rec_y, rec_x + rec_w, rec_y, rec_x, rec_y + rec_h, rec_x + rec_w, rec_y + rec_h,
339 };
340 if(!dt_drawlayer_widget_points_to_layer_coords(self, pts, 4)) return FALSE;
341
342 float min_x = pts[0];
343 float max_x = pts[0];
344 float min_y = pts[1];
345 float max_y = pts[1];
346 for(int i = 1; i < 4; i++)
347 {
348 min_x = fminf(min_x, pts[2 * i]);
349 max_x = fmaxf(max_x, pts[2 * i]);
350 min_y = fminf(min_y, pts[2 * i + 1]);
351 max_y = fmaxf(max_y, pts[2 * i + 1]);
352 }
353
354 view->layer_x0 = min_x;
355 view->layer_y0 = min_y;
356 view->layer_x1 = max_x;
357 view->layer_y1 = max_y;
358
359 view->patch.x = MAX(0, (int)floorf(min_x - padding));
360 view->patch.y = MAX(0, (int)floorf(min_y - padding));
361 const int right = MIN(layer_width, (int)ceilf(max_x + padding));
362 const int bottom = MIN(layer_height, (int)ceilf(max_y + padding));
363 view->patch.width = MAX(0, right - view->patch.x);
364 view->patch.height = MAX(0, bottom - view->patch.y);
365 return view->patch.width > 0 && view->patch.height > 0;
366}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const float x
const dt_colormatrix_t dt_aligned_pixel_t out
const float top
gboolean dt_drawlayer_compute_view_patch(dt_iop_module_t *self, const float padding, drawlayer_view_patch_info_t *view)
gboolean dt_drawlayer_layer_canvas_for_pipe(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, int *width, int *height)
Definition coordinates.c:87
gboolean dt_drawlayer_widget_points_to_layer_coords(dt_iop_module_t *self, float *pts, const int count)
gboolean dt_drawlayer_raw_placement_gui(dt_iop_module_t *self, dt_drawlayer_raw_placement_t *placement, int *frame_width, int *frame_height)
The placement against this module's frame, as the GUI sees it.
static gboolean _virtual_piece_layer_geometry(dt_iop_module_t *self, int *layer_width, int *layer_height)
gboolean dt_drawlayer_raw_placement_for_frame(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *const frame, dt_drawlayer_raw_placement_t *placement)
float dt_drawlayer_widget_brush_radius(dt_iop_module_t *self, const dt_drawlayer_brush_dab_t *dab, const float fallback)
gboolean dt_drawlayer_layer_points_to_widget_coords(dt_iop_module_t *self, float *pts, const int count)
gboolean dt_drawlayer_layer_bounds_to_widget_bounds(dt_iop_module_t *self, const float x0, const float y0, const float x1, const float y1, float *left, float *top, float *right, float *bottom)
float dt_drawlayer_current_live_padding(dt_iop_module_t *self)
gboolean dt_drawlayer_layer_canvas(dt_iop_module_t *self, int *width, int *height)
dt_drawlayer_layer_canvas_for_pipe for a GUI caller.
gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy, float *lx, float *ly)
static gboolean _flip_swaps_axes(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe)
Does the orientation flip.c settled on swap the axes?
Definition coordinates.c:54
gboolean dt_drawlayer_layer_to_widget_coords(dt_iop_module_t *self, const float x, const float y, float *wx, float *wy)
Shared coordinate transforms and geometry computations for drawlayer.
gboolean dt_dev_geometry_get_raw_size(const dt_develop_t *dev, int32_t *width, int32_t *height)
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)
int32_t dt_dev_viewport_border_size(const dt_develop_t *dev)
int32_t dt_dev_viewport_widget_height(const dt_develop_t *dev)
int32_t dt_dev_viewport_widget_width(const dt_develop_t *dev)
void dt_dev_coordinates_preview_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1373
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:1766
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:2026
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1358
void dt_dev_coordinates_image_norm_to_widget(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1240
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:1818
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:1758
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:1211
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
GtkWidget * view
the tree view
const dt_geometry_record_t * dt_geometry_chain_find(const dt_geometry_chain_t *chain, const char *op, const int instance)
One module instance's record, or NULL. Use it for that module's own in/out dims.
Definition geometry.c:337
Where things are on the image, answered without a pipeline.
float dt_drawlayer_conf_size(void)
Read and clamp configured brush size (px).
The brush settings the drawlayer module keeps in the application configuration.
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t int int * layer_height
Definition layers.h:51
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t int * layer_width
Definition layers.h:50
static void swap(float *x, float *y)
Definition lightroom.c:1023
#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
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
struct dt_geometry_chain_t * geometry_chain
Definition develop.h:491
Fully resolved input dab descriptor.
Definition brush.h:66
Where the raw-anchored layer canvas sits in this module's own frame.
Definition coordinates.h:51
One module instance's contribution, as data.
Definition geometry.h:99
dt_iop_roi_t in
Definition geometry.h:117
dt_iop_roi_t out
Definition geometry.h:118
struct dt_develop_t * dev
Definition imageop.h:311
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29