Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
rotatepixels.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2014 Pascal de Bruijn.
4 Copyright (C) 2014, 2016 Roman Lebedev.
5 Copyright (C) 2014, 2016, 2019 Tobias Ellinghaus.
6 Copyright (C) 2017 Heiko Bauke.
7 Copyright (C) 2017 luzpaz.
8 Copyright (C) 2018, 2020-2021, 2023, 2025-2026 Aurélien PIERRE.
9 Copyright (C) 2018 Edgardo Hoszowski.
10 Copyright (C) 2018 Maurizio Paglia.
11 Copyright (C) 2018-2022 Pascal Obry.
12 Copyright (C) 2018 rawfiner.
13 Copyright (C) 2019-2020 Aldric Renaudin.
14 Copyright (C) 2019 Andreas Schneider.
15 Copyright (C) 2020 Diederik Ter Rahe.
16 Copyright (C) 2020-2021 Ralf Brown.
17 Copyright (C) 2022 Martin Bařinka.
18 Copyright (C) 2022 Philipp Lutz.
19
20 darktable is free software: you can redistribute it and/or modify
21 it under the terms of the GNU General Public License as published by
22 the Free Software Foundation, either version 3 of the License, or
23 (at your option) any later version.
24
25 darktable is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 GNU General Public License for more details.
29
30 You should have received a copy of the GNU General Public License
31 along with darktable. If not, see <http://www.gnu.org/licenses/>.
32*/
33
34#ifdef HAVE_CONFIG_H
35#include "config.h"
36#endif
37#include "develop/imageop_gui.h"
40#include "pixel/interpolation.h"
41#include "math/math.h"
42#include "develop/develop.h"
44#include "develop/imageop.h"
45#include "iop/iop_api.h"
46
47#include <stdlib.h>
48#include "widgets/label.h"
49
51
55
61
63{
64 uint32_t rx, ry; // rotation center
65 float m[4]; // rotation matrix
67
68
69// helper to count corners in for loops:
70static void get_corner(const float *aabb, const int i, float *p)
71{
72 for(int k = 0; k < 2; k++) p[k] = aabb[2 * ((i >> k) & 1) + k];
73}
74
75static void adjust_aabb(const float *p, float *aabb)
76{
77 aabb[0] = fminf(aabb[0], p[0]);
78 aabb[1] = fminf(aabb[1], p[1]);
79 aabb[2] = fmaxf(aabb[2], p[0]);
80 aabb[3] = fmaxf(aabb[3], p[1]);
81}
82
83
84const char *name()
85{
86 return C_("modulename", "rotate pixels");
87}
88
94
96{
98}
99
101{
102 return IOP_TAG_DISTORT;
103}
104
106{
107 return IOP_CS_RGB;
108}
109
110const char **description(struct dt_iop_module_t *self)
111{
112 return dt_iop_set_description(self,
113 _("internal module to setup technical specificities of raw sensor.\n\n"
114 "you should not touch values here !"),
115 NULL, NULL, NULL, NULL);
116}
117
118
119/* --- the shared geometry core ----------------------------------------------------------
120 *
121 * These take the committed data rather than a pipeline piece, so the pixel pipe's distort
122 * callbacks and the geometry service's record (develop/geometry/geometry.h) run the same
123 * arithmetic instead of two copies of it.
124 */
125
127static void transform(const dt_iop_rotatepixels_data_t *const d, const float scale, const float *const x,
128 float *o)
129{
130 float pi[2] = { x[0] - d->rx * scale, x[1] - d->ry * scale };
131
132 mul_mat_vec_2(d->m, pi, o);
133}
134
135
137static void backtransform(const dt_iop_rotatepixels_data_t *const d, const float scale, const float *const x,
138 float *o)
139{
140 float rt[] = { d->m[0], -d->m[1], -d->m[2], d->m[3] };
141 mul_mat_vec_2(rt, x, o);
142
143 o[0] += d->rx * scale;
144 o[1] += d->ry * scale;
145}
146
156 const dt_iop_roi_t *const roi_in, dt_iop_roi_t *roi_out)
157{
158 *roi_out = *roi_in;
159
160 const float scale = roi_in->scale;
161 const float T = (float)d->ry * scale;
162
163 const float y = sqrtf(2.0f * T * T),
164 x = sqrtf(2.0f * ((float)roi_in->width - T) * ((float)roi_in->width - T));
165
167 const float IW = (float)interpolation->width * scale;
168
169 roi_out->width = y - IW;
170 roi_out->height = x - IW;
171
172 roi_out->width = MAX(0, roi_out->width & ~1);
173 roi_out->height = MAX(0, roi_out->height & ~1);
174}
175
177 float *const restrict points, size_t points_count)
178{
179 const float scale = piece->buf_in.scale;
180 __OMP_PARALLEL_FOR_SIMD__(if(points_count > 100) aligned(points:64))
181 for(size_t i = 0; i < points_count * 2; i += 2)
182 {
183 float pi[2], po[2];
184
185 pi[0] = points[i];
186 pi[1] = points[i + 1];
187
188 transform((const dt_iop_rotatepixels_data_t *)piece->data, scale, pi, po);
189
190 points[i] = po[0];
191 points[i + 1] = po[1];
192 }
193
194 return 1;
195}
196
198 float *const restrict points, size_t points_count)
199{
200 const float scale = piece->buf_in.scale;
201 __OMP_PARALLEL_FOR_SIMD__(if(points_count > 100) aligned(points:64))
202 for(size_t i = 0; i < points_count * 2; i += 2)
203 {
204 float pi[2], po[2];
205
206 pi[0] = points[i];
207 pi[1] = points[i + 1];
208
209 backtransform((const dt_iop_rotatepixels_data_t *)piece->data, scale, pi, po);
210
211 points[i] = po[0];
212 points[i + 1] = po[1];
213 }
214
215 return 1;
216}
217
218void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece,
219 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
220 const dt_iop_roi_t *const roi_out)
221{
222 (void)pipe;
223 // TODO
224 memset(out, 0, sizeof(float) * roi_out->width * roi_out->height);
225 fprintf(stderr, "TODO: implement %s() in %s\n", __FUNCTION__, __FILE__);
226}
227
228// 1st pass: how large would the output be, given this input roi?
229// this is always called with the full buffer before processing.
231 dt_iop_roi_t *roi_out,
232 const dt_iop_roi_t *const roi_in)
233{
234 _rotatepixels_map_size((const dt_iop_rotatepixels_data_t *)piece->data, roi_in, roi_out);
235}
236
237// 2nd pass: which roi would this operation need as input to fill the given output region?
239 const dt_iop_roi_t *const roi_out,
240 dt_iop_roi_t *roi_in)
241{
242 *roi_in = *roi_out;
243
244 const float scale = roi_in->scale;
245
246 dt_boundingbox_t aabb = { roi_out->x, roi_out->y, roi_out->x + roi_out->width, roi_out->y + roi_out->height };
247
248 dt_boundingbox_t aabb_in = { INFINITY, INFINITY, -INFINITY, -INFINITY };
249
250 for(int c = 0; c < 4; c++)
251 {
252 float p[2], o[2];
253
254 // get corner points of roi_out
255 get_corner(aabb, c, p);
256
257 backtransform((const dt_iop_rotatepixels_data_t *)piece->data, scale, p, o);
258
259 // transform to roi_in space, get aabb.
260 adjust_aabb(o, aabb_in);
261 }
262
264 const float IW = (float)interpolation->width * scale;
265
266 const float orig_w = roi_in->scale * piece->buf_in.width, orig_h = roi_in->scale * piece->buf_in.height;
267
268 // adjust roi_in to minimally needed region
269 roi_in->x = fmaxf(0.0f, aabb_in[0] - IW);
270 roi_in->y = fmaxf(0.0f, aabb_in[1] - IW);
271 roi_in->width = fminf(orig_w - roi_in->x, aabb_in[2] - roi_in->x + IW);
272 roi_in->height = fminf(orig_h - roi_in->y, aabb_in[3] - roi_in->y + IW);
273
274 // sanity check.
275 roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(orig_w));
276 roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(orig_h));
277 roi_in->width = CLAMP(roi_in->width, 1, (int)ceilf(orig_w) - roi_in->x);
278 roi_in->height = CLAMP(roi_in->height, 1, (int)ceilf(orig_h) - roi_in->y);
279}
280
281// 3rd (final) pass: you get this input region (may be different from what was requested above),
282// do your best to fill the output region!
285 const void *const ivoid, void *const ovoid)
286{
287 (void)pipe;
288 const dt_iop_roi_t *const roi_in = &piece->roi_in;
289 const dt_iop_roi_t *const roi_out = &piece->roi_out;
290 const int ch = piece->dsc_in.channels;
291 const int ch_width = ch * roi_in->width;
292
293 const float scale = roi_in->scale;
294
297 // (slow) point-by-point transformation.
298 // TODO: optimize with scanlines and linear steps between?
299 for(int j = 0; j < roi_out->height; j++)
300 {
301 float *out = ((float *)ovoid) + (size_t)ch * j * roi_out->width;
302 for(int i = 0; i < roi_out->width; i++, out += ch)
303 {
304 float pi[2], po[2];
305
306 pi[0] = roi_out->x + i;
307 pi[1] = roi_out->y + j;
308
309 backtransform((const dt_iop_rotatepixels_data_t *)piece->data, scale, pi, po);
310
311 po[0] -= roi_in->x;
312 po[1] -= roi_in->y;
313
314 dt_interpolation_compute_pixel4c(interpolation, (float *)ivoid, out, po[0], po[1], roi_in->width,
315 roi_in->height, ch_width);
316 }
317 }
318 return 0;
319}
320
324{
325 d->rx = p->rx;
326 d->ry = p->ry;
327
328 const float angle = p->angle * M_PI / 180.0f;
329
330 const float rt[] = { cosf(angle), sinf(angle), -sinf(angle), cosf(angle) };
331 for(int k = 0; k < 4; k++) d->m[k] = rt[k];
332}
333
336{
339
341
342 // this should not be used for normal images
343 // (i.e. for those, when this iop is off by default)
344 if((d->rx == 0u) && (d->ry == 0u)) piece->enabled = 0;
345}
346
347/* --- the geometry service's view of this module (develop/geometry/geometry.h) --------- */
348
349static void _rotatepixels_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
350{
352}
353
354static int _rotatepixels_geometry_transform(const void *data, const dt_geometry_record_t *const record,
355 dt_geometry_chain_t *chain, float *points, size_t points_count)
356{
357 const dt_iop_rotatepixels_data_t *const d = (const dt_iop_rotatepixels_data_t *)data;
358 for(size_t i = 0; i < points_count * 2; i += 2)
359 {
360 const float pi[2] = { points[i], points[i + 1] };
361 float po[2];
362 transform(d, record->in.scale, pi, po);
363 points[i] = po[0];
364 points[i + 1] = po[1];
365 }
366 return 1;
367}
368
369static int _rotatepixels_geometry_backtransform(const void *data, const dt_geometry_record_t *const record,
370 dt_geometry_chain_t *chain, float *points,
371 size_t points_count)
372{
373 const dt_iop_rotatepixels_data_t *const d = (const dt_iop_rotatepixels_data_t *)data;
374 for(size_t i = 0; i < points_count * 2; i += 2)
375 {
376 const float pi[2] = { points[i], points[i + 1] };
377 float po[2];
378 backtransform(d, record->in.scale, pi, po);
379 points[i] = po[0];
380 points[i + 1] = po[1];
381 }
382 return 1;
383}
384
390
391gboolean geometry_record(dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
392{
395 if(IS_NULL_PTR(data)) return FALSE;
396
398
399 /* A zero rotation centre is how commit_params() clears piece->enabled: the module is in the
400 * pipe but does nothing. Published with no vtable, which the chain reads as identity. */
401 if(data->rx == 0u && data->ry == 0u)
402 {
403 g_free(data);
404 return TRUE;
405 }
406
407 record->data = data;
408 record->free_data = dt_free_gpointer;
410 return TRUE;
411}
412
418
420{
421 dt_free_align(piece->data);
422 piece->data = NULL;
423}
424
426{
428
429 const dt_image_t *const image = &(self->dev->image_storage);
430
431 *d = (dt_iop_rotatepixels_params_t){ .rx = 0u, .ry = image->fuji_rotation_pos, .angle = -45.0f };
432
433 self->default_enabled = ((d->rx != 0u) || (d->ry != 0u));
434
435 // FIXME: does not work.
436 self->hide_enable_button = !self->default_enabled;
437 // Label text reflects the per-image default; applied in gui_update() (GUI thread, widget exists).
438}
439
441{
442 gtk_label_set_text(GTK_LABEL(self->gui->widget), self->default_enabled
443 ? _("automatic pixel rotation")
444 : _("automatic pixel rotation\nonly works for the sensors that need it."));
445}
447{
448 IOP_GUI_ALLOC(rotatepixels);
449
450 self->gui->widget = dt_ui_label_new("");
451 gtk_label_set_line_wrap(GTK_LABEL(self->gui->widget), TRUE);
452
453}
454
455// clang-format off
456// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
457// vim: shiftwidth=2 expandtab tabstop=2 cindent
458// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
459// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
static const float x
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_iop_params_t
Definition dev_history.h:43
Where things are on the image, answered without a pipeline.
float dt_boundingbox_t[4]
Definition image.h:83
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
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_FLAGS_UNSAFE_COPY
Definition imageop.h:196
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
@ IOP_FLAGS_TILING_FULL_ROI
Definition imageop.h:190
@ IOP_GROUP_TECHNICAL
Definition imageop.h:162
@ IOP_TAG_DISTORT
Definition imageop.h:170
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
const struct dt_interpolation * dt_interpolation_new(enum dt_interpolation_type type)
__DT_CLONE_TARGETS__ void dt_interpolation_compute_pixel4c(const struct dt_interpolation *itor, const float *in, float *out, const float x, const float y, const int width, const int height, const int linestride)
@ DT_INTERPOLATION_USERPREF
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#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
static void mul_mat_vec_2(const float *m, const float *p, float *o)
Definition math.h:178
#define M_PI
Definition math.h:47
#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_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
int operation_tags()
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)
const char ** description(struct dt_iop_module_t *self)
void modify_roi_out(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *const roi_in)
int default_group()
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)
static void _rotatepixels_geometry_map_size(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
static void _rotatepixels_map_size(const dt_iop_rotatepixels_data_t *const d, const dt_iop_roi_t *const roi_in, dt_iop_roi_t *roi_out)
Input rect -> output rect: the inner rectangle of the 45-degree rotation.
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)
static void _rotatepixels_resolve(const dt_iop_rotatepixels_params_t *const p, dt_iop_rotatepixels_data_t *d)
Params -> committed data. THE constructor, shared with geometry_record().
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
static void get_corner(const float *aabb, const int i, float *p)
__DT_CLONE_TARGETS__ int process(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)
static int _rotatepixels_geometry_backtransform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
void modify_roi_in(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *const roi_out, dt_iop_roi_t *roi_in)
const char * name()
gboolean geometry_record(dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
void gui_init(dt_iop_module_t *self)
void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void transform(const dt_iop_rotatepixels_data_t *const d, const float scale, const float *const x, float *o)
void reload_defaults(dt_iop_module_t *self)
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
static const dt_geometry_vtable_t _rotatepixels_geometry_vtable
static void adjust_aabb(const float *p, float *aabb)
void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static int _rotatepixels_geometry_transform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
static void backtransform(const dt_iop_rotatepixels_data_t *const d, const float scale, const float *const x, float *o)
dt_iop_buffer_dsc_t dsc_in
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
uint32_t fuji_rotation_pos
Definition image.h:442
unsigned int channels
Definition format.h:83
GtkWidget * widget
Definition imageop_gui.h:47
int32_t hide_enable_button
Definition imageop.h:270
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
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
#define __DT_CLONE_TARGETS__
#define MAX(a, b)
Definition thinplate.c:29