/* This file is part of darktable, Copyright (C) 2014-2025 darktable developers. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ #include "bauhaus/bauhaus.h" #include "common/interpolation.h" #include "common/math.h" #include "common/opencl.h" #include "develop/develop.h" #include "develop/imageop.h" #include "develop/tiling.h" #include "gui/gtk.h" #include "iop/iop_api.h" #include DT_MODULE_INTROSPECTION(1, dt_iop_rotatepixels_params_t) typedef struct dt_iop_rotatepixels_gui_data_t { } dt_iop_rotatepixels_gui_data_t; typedef struct dt_iop_rotatepixels_params_t { uint32_t rx, ry; float angle; } dt_iop_rotatepixels_params_t; typedef struct dt_iop_rotatepixels_data_t { uint32_t rx, ry; // rotation center float m[4]; // rotation matrix } dt_iop_rotatepixels_data_t; typedef struct dt_iop_rotatepixels_global_data_t { int kernel_rotatepixels; } dt_iop_rotatepixels_global_data_t; dt_iop_rotatepixels_gui_data_t dummy; // helper to count corners in for loops: static void get_corner(const float *aabb, const int i, float *p) { for(int k = 0; k < 2; k++) p[k] = aabb[2 * ((i >> k) & 1) + k]; } static void adjust_aabb(const float *p, float *aabb) { aabb[0] = fminf(aabb[0], p[0]); aabb[1] = fminf(aabb[1], p[1]); aabb[2] = fmaxf(aabb[2], p[0]); aabb[3] = fmaxf(aabb[3], p[1]); } const char *name() { return C_("modulename", "rotate pixels"); } int flags() { return IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_TILING_FULL_ROI | IOP_FLAGS_ONE_INSTANCE | IOP_FLAGS_UNSAFE_COPY | IOP_FLAGS_HIDDEN; } int default_group() { return IOP_GROUP_CORRECT | IOP_GROUP_TECHNICAL; } int operation_tags() { return IOP_TAG_DISTORT; } dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { return IOP_CS_RGB; } const char **description(dt_iop_module_t *self) { return dt_iop_set_description(self, _("internal module to setup technical specificities of raw sensor\n\n" "you should not touch values here!"), NULL, NULL, NULL, NULL); } DT_OMP_DECLARE_SIMD() static void transform(const dt_dev_pixelpipe_iop_t *const piece, const float scale, const float *const x, float *o) { const dt_iop_rotatepixels_data_t *d = piece->data; const float pi[2] = { x[0] - d->rx * scale, x[1] - d->ry * scale }; mul_mat_vec_2(d->m, pi, o); } DT_OMP_DECLARE_SIMD() static void backtransform(const dt_dev_pixelpipe_iop_t *const piece, const float scale, const float *const x, float *o) { const dt_iop_rotatepixels_data_t *d = piece->data; const float rt[] = { d->m[0], -d->m[1], -d->m[2], d->m[3] }; mul_mat_vec_2(rt, x, o); o[0] += d->rx * scale; o[1] += d->ry * scale; } gboolean distort_transform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count) { const float scale = piece->buf_in.scale / piece->iscale; DT_OMP_FOR_SIMD(if(points_count > 100) aligned(points:64)) for(size_t i = 0; i < points_count * 2; i += 2) { float pi[2], po[2]; pi[0] = points[i]; pi[1] = points[i + 1]; transform(piece, scale, pi, po); points[i] = po[0]; points[i + 1] = po[1]; } return TRUE; } gboolean distort_backtransform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count) { const float scale = piece->buf_in.scale / piece->iscale; DT_OMP_FOR_SIMD(if(points_count > 100) aligned(points:64)) for(size_t i = 0; i < points_count * 2; i += 2) { float pi[2], po[2]; pi[0] = points[i]; pi[1] = points[i + 1]; backtransform(piece, scale, pi, po); points[i] = po[0]; points[i + 1] = po[1]; } return TRUE; } void distort_mask(dt_iop_module_t *self, 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) { // TODO memset(out, 0, sizeof(float) * roi_out->width * roi_out->height); dt_print(DT_DEBUG_ALWAYS, "TODO: implement %s() in %s", __FUNCTION__, __FILE__); } void modify_roi_out(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *const roi_in) { const dt_iop_rotatepixels_data_t *d = piece->data; *roi_out = *roi_in; /* * Think of input image as: * 1. Square, containing: * 3. 4x Right triangles (two pairs), located in the Edges of square * 2. Rectangle (rotated 45 degrees), located in between triangles. * * Therefore, output image dimensions, that are sizes of inner Rectangle * can be found using Pythagorean theorem. * * Not precise pseudographics: (width == height + 1) * height * _ ------- * | |1 /\2| * ty | | y \| * _ |/ /| width * |\x / | * |2\/ 1| * ------- */ const float scale = roi_in->scale / piece->iscale, T = (float)d->ry * scale; const float y = sqrtf(2.0f * T * T), x = sqrtf(2.0f * ((float)roi_in->width - T) * ((float)roi_in->width - T)); const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP); const float IW = (float)interpolation->width * scale; roi_out->width = y - IW; roi_out->height = x - IW; roi_out->width = MAX(0, roi_out->width & ~1); roi_out->height = MAX(0, roi_out->height & ~1); } void modify_roi_in(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *const roi_out, dt_iop_roi_t *roi_in) { *roi_in = *roi_out; const float scale = roi_in->scale / piece->iscale; const dt_boundingbox_t aabb = { roi_out->x, roi_out->y, roi_out->x + roi_out->width, roi_out->y + roi_out->height }; dt_boundingbox_t aabb_in = { FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX }; for(int c = 0; c < 4; c++) { float p[2], o[2]; // get corner points of roi_out get_corner(aabb, c, p); backtransform(piece, scale, p, o); // transform to roi_in space, get aabb. adjust_aabb(o, aabb_in); } const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP); const float IW = (float)interpolation->width * scale; const float orig_w = roi_in->scale * piece->buf_in.width, orig_h = roi_in->scale * piece->buf_in.height; // adjust roi_in to minimally needed region roi_in->x = fmaxf(0.0f, aabb_in[0] - IW); roi_in->y = fmaxf(0.0f, aabb_in[1] - IW); roi_in->width = fminf(orig_w - roi_in->x, aabb_in[2] - roi_in->x + IW); roi_in->height = fminf(orig_h - roi_in->y, aabb_in[3] - roi_in->y + IW); // sanity check. roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(orig_w)); roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(orig_h)); roi_in->width = CLAMP(roi_in->width, 1, (int)ceilf(orig_w) - roi_in->x); roi_in->height = CLAMP(roi_in->height, 1, (int)ceilf(orig_h) - roi_in->y); } // 3rd (final) pass: you get this input region (may be different from what was requested above), // do your best to fill the output region! void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) { const int ch = piece->colors; const int ch_width = ch * roi_in->width; const float scale = roi_in->scale / piece->iscale; assert(ch == 4); const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP); DT_OMP_FOR() // (slow) point-by-point transformation. // TODO: optimize with scanlines and linear steps between? for(int j = 0; j < roi_out->height; j++) { float *out = ((float *)ovoid) + (size_t)ch * j * roi_out->width; for(int i = 0; i < roi_out->width; i++, out += ch) { float pi[2], po[2]; pi[0] = roi_out->x + i; pi[1] = roi_out->y + j; backtransform(piece, scale, pi, po); po[0] -= roi_in->x; po[1] -= roi_in->y; dt_interpolation_compute_pixel4c(interpolation, (float *)ivoid, out, po[0], po[1], roi_in->width, roi_in->height, ch_width); } } } #ifdef HAVE_OPENCL int process_cl(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) { const dt_iop_rotatepixels_data_t *d = piece->data; const dt_iop_rotatepixels_global_data_t *gd = self->global_data; const int devid = piece->pipe->devid; const float scale = roi_in->scale / piece->iscale; const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP); // backtransform() as one matrix and offset const float m[4] = { d->m[0], -d->m[1], -d->m[2], d->m[3] }; const float center[2] = { d->rx * scale, d->ry * scale }; const int roo[2] = { roi_out->x, roi_out->y }; const int roi[2] = { roi_in->x, roi_in->y }; const int itype = interpolation->id; const int iwidth = interpolation->width; return dt_opencl_enqueue_kernel_2d_args(devid, gd->kernel_rotatepixels, roi_out->width, roi_out->height, CLARG(dev_in), CLARG(dev_out), CLARG(roi_out->width), CLARG(roi_out->height), CLARG(roi_in->width), CLARG(roi_in->height), CLARG(roo), CLARG(roi), CLARG(m), CLARG(center), CLARG(itype), CLARG(iwidth)); } #endif void init_global(dt_iop_module_so_t *self) { const int program = 2; // basic.cl, from programs.conf dt_iop_rotatepixels_global_data_t *gd = malloc(sizeof(dt_iop_rotatepixels_global_data_t)); self->data = gd; gd->kernel_rotatepixels = dt_opencl_create_kernel(program, "rotatepixels"); } void cleanup_global(dt_iop_module_so_t *self) { dt_iop_rotatepixels_global_data_t *gd = self->data; dt_opencl_free_kernel(gd->kernel_rotatepixels); free(self->data); self->data = NULL; } void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { const dt_iop_rotatepixels_params_t *p = (dt_iop_rotatepixels_params_t *)p1; dt_iop_rotatepixels_data_t *d = piece->data; d->rx = p->rx; d->ry = p->ry; const float angle = deg2radf(p->angle); const float rt[] = { cosf(angle), sinf(angle), -sinf(angle), cosf(angle) }; for(int k = 0; k < 4; k++) d->m[k] = rt[k]; // this should not be used for normal images // (i.e. for those, when this iop is off by default) if((d->rx == 0u) && (d->ry == 0u)) piece->enabled = FALSE; } void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { piece->data = calloc(1, sizeof(dt_iop_rotatepixels_data_t)); } void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { free(piece->data); piece->data = NULL; } void reload_defaults(dt_iop_module_t *self) { dt_iop_rotatepixels_params_t *d = self->default_params; const dt_image_t *const image = &(self->dev->image_storage); *d = (dt_iop_rotatepixels_params_t){ .rx = 0u, .ry = image->fuji_rotation_pos, .angle = -45.0f }; self->default_enabled = ((d->rx != 0u) || (d->ry != 0u)); } // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; // clang-format on