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 "develop/geometry/geometry.h" // dt_geometry_chain_find(), dt_geometry_record_t
22
23#include <string.h>
24
25/* --- where the raw-anchored canvas sits in this module's frame ------------------------------
26 *
27 * One derivation, three callers -- the GUI placing a dab, the GUI drawing its overlays, and the
28 * pipe compositing. They MUST agree: a dab is placed by the first and rendered by the last, and a
29 * disagreement puts the paint somewhere other than where the cursor was.
30 */
31
50static gboolean _flip_swaps_axes(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe)
51{
52 dt_iop_roi_t in = { 0 };
53 dt_iop_roi_t out = { 0 };
54 gboolean found = FALSE;
55
56 if(IS_NULL_PTR(pipe))
57 {
58 const dt_geometry_record_t *const record = dt_geometry_chain_find(self->dev->geometry_chain, "flip", 0);
59 if(!IS_NULL_PTR(record))
60 {
61 in = record->in;
62 out = record->out;
63 found = TRUE;
64 }
65 }
66 else
67 {
68 for(const GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
69 {
70 const dt_dev_pixelpipe_iop_t *const piece = (const dt_dev_pixelpipe_iop_t *)node->data;
71 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module) || strcmp(piece->module->op, "flip")) continue;
72 in = piece->buf_in;
73 out = piece->buf_out;
74 found = TRUE;
75 break;
76 }
77 }
78
79 if(!found || in.width <= 0 || in.height <= 0) return FALSE;
80 return (out.width == in.height) && (out.height == in.width) && (in.width != in.height);
81}
82
84 int *height)
85{
86 if(!IS_NULL_PTR(width)) *width = 0;
87 if(!IS_NULL_PTR(height)) *height = 0;
88 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev)) return FALSE;
89
90 int32_t raw_width = 0;
91 int32_t raw_height = 0;
92 if(!dt_dev_geometry_get_raw_size(self->dev, &raw_width, &raw_height)) return FALSE;
93 if(raw_width <= 0 || raw_height <= 0) return FALSE;
94
95 /* A raw buffer is landscape; a portrait image is that buffer seen rotated a quarter turn, and
96 * the paint is rotated with it. Carried by transposing the canvas rather than by rotating
97 * pixels: the layer the user paints, and the page the sidecar holds, are both in the
98 * orientation they see. */
99 const gboolean swap = _flip_swaps_axes(self, pipe);
100
101 if(!IS_NULL_PTR(width)) *width = swap ? raw_height : raw_width;
102 if(!IS_NULL_PTR(height)) *height = swap ? raw_width : raw_height;
103 return TRUE;
104}
105
107{
109}
110
112 const dt_iop_roi_t *const frame,
114{
115 if(IS_NULL_PTR(placement)) return FALSE;
116 *placement = (dt_drawlayer_raw_placement_t){ 0 };
117 if(IS_NULL_PTR(frame) || frame->width <= 0 || frame->height <= 0) return FALSE;
118
119 int canvas_width = 0;
120 int canvas_height = 0;
121 if(!dt_drawlayer_layer_canvas_for_pipe(self, pipe, &canvas_width, &canvas_height)) return FALSE;
122
123 /* The frame's ORIGIN, not the difference of the sizes.
124 *
125 * The size fold that produces this rect (dt_dev_pixelpipe_get_roi_out, and the chain's mirror of
126 * it) runs at scale 1 from the raw frame, and a crop records itself as an OFFSET in that frame:
127 * measured on a cropped NEF, crop maps in=(0,0,4016x6016) to out=(0,2802,2464x3213). So the
128 * frame's position inside the raw image is exactly frame->x/y, and translating the canvas by it
129 * is what makes the paint stay on the content -- for a crop that moves without resizing as much
130 * as for one that resizes.
131 *
132 * Centring on the sizes alone, which this did first, gets the uncropped case right and every
133 * moved crop wrong: two crops of equal size at different positions have the same size difference
134 * and therefore the same offset, so the layer followed the frame instead of the image. The
135 * uncropped case is still centre-on-centre, because there the origin is 0 and the extents match.
136 *
137 * A module that GROWS the frame (a perspective correction) leaves the origin at 0 while the
138 * extent exceeds the canvas, so the canvas anchors to the frame's corner rather than its centre.
139 * That is a consequence of being immune to that module -- there is no offset in this frame that
140 * both ignores the correction and re-centres against it. */
141 placement->offset_x = frame->x;
142 placement->offset_y = frame->y;
143 placement->valid = TRUE;
144 return TRUE;
145}
146
148 int *frame_width, int *frame_height)
149{
150 if(!IS_NULL_PTR(frame_width)) *frame_width = 0;
151 if(!IS_NULL_PTR(frame_height)) *frame_height = 0;
152 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(placement)) return FALSE;
153
154 dt_iop_roi_t frame;
155 if(!dt_dev_module_geometry_gui(self->dev, self, &frame, NULL)) return FALSE;
156 if(frame.width <= 0 || frame.height <= 0) return FALSE;
157
158 if(!IS_NULL_PTR(frame_width)) *frame_width = frame.width;
159 if(!IS_NULL_PTR(frame_height)) *frame_height = frame.height;
160 return dt_drawlayer_raw_placement_for_frame(self, NULL, &frame, placement);
161}
162
163static gboolean _virtual_piece_layer_geometry(dt_iop_module_t *self, int *layer_width, int *layer_height)
164{
165 return dt_drawlayer_layer_canvas(self, layer_width, layer_height);
166}
167
168gboolean dt_drawlayer_widget_points_to_layer_coords(dt_iop_module_t *self, float *pts, const int count)
169{
170 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(pts) || count <= 0) return FALSE;
171
174
177 return FALSE;
179
180 /* ...which lands in this module's own frame, normalised. Finish through the SAME placement the
181 * renderer uses: a dab has to be written where the composite will read it from. */
183 int frame_width = 0;
184 int frame_height = 0;
185 if(!dt_drawlayer_raw_placement_gui(self, &placement, &frame_width, &frame_height)) return FALSE;
186
187 for(int i = 0; i < count; i++)
188 {
189 pts[2 * i] = (float)placement.offset_x + pts[2 * i] * (float)frame_width;
190 pts[2 * i + 1] = (float)placement.offset_y + pts[2 * i + 1] * (float)frame_height;
191 }
192 return TRUE;
193}
194
195gboolean dt_drawlayer_layer_points_to_widget_coords(dt_iop_module_t *self, float *pts, const int count)
196{
197 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(pts) || count <= 0) return FALSE;
198
199 // Canvas pixels back to this module's own frame, normalised. Inverse of the above.
201 int frame_width = 0;
202 int frame_height = 0;
203 if(!dt_drawlayer_raw_placement_gui(self, &placement, &frame_width, &frame_height)) return FALSE;
204
205 for(int i = 0; i < count; i++)
206 {
207 pts[2 * i] = (pts[2 * i] - (float)placement.offset_x) / (float)frame_width;
208 pts[2 * i + 1] = (pts[2 * i + 1] - (float)placement.offset_y) / (float)frame_height;
209 }
210
212
215 return FALSE;
216
219 return TRUE;
220}
221
222gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy,
223 float *lx, float *ly)
224{
225 if(IS_NULL_PTR(lx) || IS_NULL_PTR(ly)) return FALSE;
226
227 float pt[2] = { (float)wx, (float)wy };
228 if(!dt_drawlayer_widget_points_to_layer_coords(self, pt, 1)) return FALSE;
229
230 *lx = pt[0];
231 *ly = pt[1];
232 return TRUE;
233}
234
235gboolean dt_drawlayer_layer_to_widget_coords(dt_iop_module_t *self, const float x, const float y,
236 float *wx, float *wy)
237{
238 if(IS_NULL_PTR(wx) || IS_NULL_PTR(wy)) return FALSE;
239
240 float pt[2] = { x, y };
241 if(!dt_drawlayer_layer_points_to_widget_coords(self, pt, 1)) return FALSE;
242 *wx = pt[0];
243 *wy = pt[1];
244 return TRUE;
245}
246
247gboolean dt_drawlayer_layer_bounds_to_widget_bounds(dt_iop_module_t *self, const float x0, const float y0,
248 const float x1, const float y1,
249 float *left, float *top,
250 float *right, float *bottom)
251{
252 float pts[8] = {
253 x0, y0, x1, y0, x0, y1, x1, y1,
254 };
255
256 if(!dt_drawlayer_layer_points_to_widget_coords(self, pts, 4)) return FALSE;
257
258 float min_x = pts[0];
259 float max_x = pts[0];
260 float min_y = pts[1];
261 float max_y = pts[1];
262 for(int i = 1; i < 4; i++)
263 {
264 min_x = fminf(min_x, pts[2 * i]);
265 max_x = fmaxf(max_x, pts[2 * i]);
266 min_y = fminf(min_y, pts[2 * i + 1]);
267 max_y = fmaxf(max_y, pts[2 * i + 1]);
268 }
269
270 if(!IS_NULL_PTR(left)) *left = min_x;
271 if(!IS_NULL_PTR(top)) *top = min_y;
272 if(!IS_NULL_PTR(right)) *right = max_x;
273 if(!IS_NULL_PTR(bottom)) *bottom = max_y;
274 return TRUE;
275}
276
278 const float fallback)
279{
280 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(dab)) return fallback;
281
282 float pts[6] = {
283 dab->x, dab->y, dab->x + dab->radius, dab->y, dab->x, dab->y + dab->radius,
284 };
285
286 if(!dt_drawlayer_layer_points_to_widget_coords(self, pts, 3)) return fallback;
287
288 const float rx = hypotf(pts[2] - pts[0], pts[3] - pts[1]);
289 const float ry = hypotf(pts[4] - pts[0], pts[5] - pts[1]);
290 const float radius = 0.5f * (rx + ry);
291 return fmaxf(0.5f, isfinite(radius) ? radius : fallback);
292}
293
295{
297 .radius = fmaxf(_conf_size(), 0.5f),
298 .hardness = _conf_hardness(),
299 .shape = _conf_brush_shape(),
300 };
301 return ceilf(dab.radius + 1.0f);
302}
303
305{
306 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(view)) return FALSE;
307
308 int layer_width = 0;
309 int layer_height = 0;
310 if(!_virtual_piece_layer_geometry(self, &layer_width, &layer_height)) return FALSE;
311
312 const float widget_w = (float)dt_dev_viewport_widget_width(self->dev);
313 const float widget_h = (float)dt_dev_viewport_widget_height(self->dev);
314 const float preview_w = dt_dev_roi_request_preview_width(self->dev);
315 const float preview_h = dt_dev_roi_request_preview_height(self->dev);
316 if(widget_w <= 0.0f || widget_h <= 0.0f || preview_w <= 0.0f || preview_h <= 0.0f) return FALSE;
317
318 const float zoom_scale = dt_dev_get_overlay_scale(self->dev);
319 const float border = (float)dt_dev_viewport_border_size(self->dev);
320 const float roi_w = fminf(widget_w, preview_w * zoom_scale);
321 const float roi_h = fminf(widget_h, preview_h * zoom_scale);
322 const float rec_x = fmaxf(border, 0.5f * (widget_w - roi_w));
323 const float rec_y = fmaxf(border, 0.5f * (widget_h - roi_h));
324 const float rec_w = fminf(widget_w - 2.0f * border, roi_w);
325 const float rec_h = fminf(widget_h - 2.0f * border, roi_h);
326 if(rec_w <= 0.0f || rec_h <= 0.0f) return FALSE;
327
328 float pts[8] = {
329 rec_x, rec_y, rec_x + rec_w, rec_y, rec_x, rec_y + rec_h, rec_x + rec_w, rec_y + rec_h,
330 };
331 if(!dt_drawlayer_widget_points_to_layer_coords(self, pts, 4)) return FALSE;
332
333 float min_x = pts[0];
334 float max_x = pts[0];
335 float min_y = pts[1];
336 float max_y = pts[1];
337 for(int i = 1; i < 4; i++)
338 {
339 min_x = fminf(min_x, pts[2 * i]);
340 max_x = fmaxf(max_x, pts[2 * i]);
341 min_y = fminf(min_y, pts[2 * i + 1]);
342 max_y = fmaxf(max_y, pts[2 * i + 1]);
343 }
344
345 view->layer_x0 = min_x;
346 view->layer_y0 = min_y;
347 view->layer_x1 = max_x;
348 view->layer_y1 = max_y;
349
350 view->patch.x = MAX(0, (int)floorf(min_x - padding));
351 view->patch.y = MAX(0, (int)floorf(min_y - padding));
352 const int right = MIN(layer_width, (int)ceilf(max_x + padding));
353 const int bottom = MIN(layer_height, (int)ceilf(max_y + padding));
354 view->patch.width = MAX(0, right - view->patch.x);
355 view->patch.height = MAX(0, bottom - view->patch.y);
356 return view->patch.width > 0 && view->patch.height > 0;
357}
#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:83
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:50
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:1306
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
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1291
void dt_dev_coordinates_image_norm_to_widget(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1173
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
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
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.
static float _conf_hardness(void)
Derive hardness as complementary value of softness.
static float _conf_size(void)
Read and clamp configured brush size (px).
static dt_iop_drawlayer_brush_shape_t _conf_brush_shape(void)
Read and clamp configured brush shape.
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_iop_module_t *void * data
struct dt_geometry_chain_t * geometry_chain
Definition develop.h:491
Fully resolved input dab descriptor.
Definition brush.h:65
Where the raw-anchored layer canvas sits in this module's own frame.
Definition coordinates.h:48
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