1 /*
2     This file is part of darktable,
3     Copyright (C) 2011-2021 darktable developers.
4 
5     darktable 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     darktable 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 darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 #include <assert.h>
22 #include <gdk/gdkkeysyms.h>
23 #include <gtk/gtk.h>
24 #include <inttypes.h>
25 #include <math.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "common/debug.h"
30 #include "common/imageio.h"
31 #include "common/opencl.h"
32 #include "control/conf.h"
33 #include "control/control.h"
34 #include "develop/develop.h"
35 #include "develop/imageop.h"
36 #include "develop/imageop_gui.h"
37 #include "dtgtk/resetlabel.h"
38 #include "gui/accelerators.h"
39 #include "gui/draw.h"
40 #include "gui/gtk.h"
41 #include "gui/presets.h"
42 #include "iop/iop_api.h"
43 
44 DT_MODULE_INTROSPECTION(2, dt_iop_flip_params_t)
45 
46 typedef struct dt_iop_flip_params_t
47 {
48   dt_image_orientation_t orientation;
49 } dt_iop_flip_params_t;
50 
51 typedef struct dt_iop_flip_params_t dt_iop_flip_data_t;
52 
53 typedef struct dt_iop_flip_global_data_t
54 {
55   int kernel_flip;
56 } dt_iop_flip_global_data_t;
57 
58 // helper to count corners in for loops:
get_corner(const int32_t * aabb,const int i,int32_t * p)59 static void get_corner(const int32_t *aabb, const int i, int32_t *p)
60 {
61   for(int k = 0; k < 2; k++) p[k] = aabb[2 * ((i >> k) & 1) + k];
62 }
63 
adjust_aabb(const int32_t * p,int32_t * aabb)64 static void adjust_aabb(const int32_t *p, int32_t *aabb)
65 {
66   aabb[0] = MIN(aabb[0], p[0]);
67   aabb[1] = MIN(aabb[1], p[1]);
68   aabb[2] = MAX(aabb[2], p[0]);
69   aabb[3] = MAX(aabb[3], p[1]);
70 }
71 
name()72 const char *name()
73 {
74   return _("orientation");
75 }
76 
aliases()77 const char *aliases()
78 {
79   return _("rotation|flip");
80 }
81 
default_group()82 int default_group()
83 {
84   return IOP_GROUP_BASIC | IOP_GROUP_TECHNICAL;
85 }
86 
operation_tags()87 int operation_tags()
88 {
89   return IOP_TAG_DISTORT;
90 }
91 
flags()92 int flags()
93 {
94   return IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_TILING_FULL_ROI | IOP_FLAGS_ONE_INSTANCE | IOP_FLAGS_UNSAFE_COPY
95          | IOP_FLAGS_GUIDES_WIDGET;
96 }
97 
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)98 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
99 {
100   return iop_cs_rgb;
101 }
102 
description(struct dt_iop_module_t * self)103 const char *description(struct dt_iop_module_t *self)
104 {
105   return dt_iop_set_description(self, _("flip or rotate image by step of 90 degrees"), _("corrective"),
106                                 _("linear, RGB, scene-referred"), _("geometric, RGB"),
107                                 _("linear, RGB, scene-referred"));
108 }
109 
merge_two_orientations(dt_image_orientation_t raw_orientation,dt_image_orientation_t user_orientation)110 static dt_image_orientation_t merge_two_orientations(dt_image_orientation_t raw_orientation,
111                                                      dt_image_orientation_t user_orientation)
112 {
113   dt_image_orientation_t raw_orientation_corrected = raw_orientation;
114   /*
115    * if user-specified orientation has ORIENTATION_SWAP_XY set, then we need
116    * to swap ORIENTATION_FLIP_Y and ORIENTATION_FLIP_X bits
117    * in raw orientation
118    */
119   if((user_orientation & ORIENTATION_SWAP_XY) == ORIENTATION_SWAP_XY)
120   {
121     if((raw_orientation & ORIENTATION_FLIP_Y) == ORIENTATION_FLIP_Y)
122       raw_orientation_corrected |= ORIENTATION_FLIP_X;
123     else
124       raw_orientation_corrected &= ~ORIENTATION_FLIP_X;
125 
126     if((raw_orientation & ORIENTATION_FLIP_X) == ORIENTATION_FLIP_X)
127       raw_orientation_corrected |= ORIENTATION_FLIP_Y;
128     else
129       raw_orientation_corrected &= ~ORIENTATION_FLIP_Y;
130 
131     if((raw_orientation & ORIENTATION_SWAP_XY) == ORIENTATION_SWAP_XY)
132       raw_orientation_corrected |= ORIENTATION_SWAP_XY;
133   }
134 
135   // and now we can automagically compute new flip
136   return raw_orientation_corrected ^ user_orientation;
137 }
138 
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)139 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
140                   void *new_params, const int new_version)
141 {
142   if(old_version == 1 && new_version == 2)
143   {
144     typedef struct dt_iop_flip_params_v1_t
145     {
146       int32_t orientation;
147     } dt_iop_flip_params_v1_t;
148 
149     const dt_iop_flip_params_v1_t *old = (dt_iop_flip_params_v1_t *)old_params;
150     dt_iop_flip_params_t *n = (dt_iop_flip_params_t *)new_params;
151     const dt_iop_flip_params_t *d = (dt_iop_flip_params_t *)self->default_params;
152 
153     *n = *d; // start with a fresh copy of default parameters
154 
155     // we might be called from presets update infrastructure => there is no image
156     dt_image_orientation_t image_orientation = ORIENTATION_NONE;
157 
158     if(self->dev)
159       image_orientation = dt_image_orientation(&self->dev->image_storage);
160 
161     n->orientation = merge_two_orientations(image_orientation,
162                                             (dt_image_orientation_t)(old->orientation));
163 
164     return 0;
165   }
166   return 1;
167 }
168 
backtransform(const int32_t * x,int32_t * o,const dt_image_orientation_t orientation,int32_t iw,int32_t ih)169 static void backtransform(const int32_t *x, int32_t *o, const dt_image_orientation_t orientation, int32_t iw,
170                           int32_t ih)
171 {
172   if(orientation & ORIENTATION_SWAP_XY)
173   {
174     o[1] = x[0];
175     o[0] = x[1];
176     const int32_t tmp = iw;
177     iw = ih;
178     ih = tmp;
179   }
180   else
181   {
182     o[0] = x[0];
183     o[1] = x[1];
184   }
185   if(orientation & ORIENTATION_FLIP_X)
186   {
187     o[0] = iw - o[0] - 1;
188   }
189   if(orientation & ORIENTATION_FLIP_Y)
190   {
191     o[1] = ih - o[1] - 1;
192   }
193 }
194 
distort_transform(dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,float * const restrict points,size_t points_count)195 int distort_transform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count)
196 {
197   // if (!self->enabled) return 2;
198   const dt_iop_flip_data_t *d = (dt_iop_flip_data_t *)piece->data;
199 
200   // nothing to be done if parameters are set to neutral values (no flip or swap)
201   if (d->orientation == 0) return 1;
202 
203 #ifdef _OPENMP
204 #pragma omp parallel for default(none) \
205     dt_omp_firstprivate(points_count, points, d, piece) \
206     schedule(static) if(points_count > 500)
207 #endif
208   for(size_t i = 0; i < points_count * 2; i += 2)
209   {
210     float x = points[i];
211     float y = points[i + 1];
212     if(d->orientation & ORIENTATION_FLIP_X) x = piece->buf_in.width - points[i];
213     if(d->orientation & ORIENTATION_FLIP_Y) y = piece->buf_in.height - points[i + 1];
214     if(d->orientation & ORIENTATION_SWAP_XY)
215     {
216       const float yy = y;
217       y = x;
218       x = yy;
219     }
220     points[i] = x;
221     points[i + 1] = y;
222   }
223 
224   return 1;
225 }
distort_backtransform(dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,float * const restrict points,size_t points_count)226 int distort_backtransform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *const restrict points,
227                           size_t points_count)
228 {
229   // if (!self->enabled) return 2;
230   const dt_iop_flip_data_t *d = (dt_iop_flip_data_t *)piece->data;
231 
232   // nothing to be done if parameters are set to neutral values (no flip or swap)
233   if (d->orientation == 0) return 1;
234 
235 #ifdef _OPENMP
236 #pragma omp parallel for default(none) \
237     dt_omp_firstprivate(points_count, points, d, piece) \
238     schedule(static) if(points_count > 500)
239 #endif
240   for(size_t i = 0; i < points_count * 2; i += 2)
241   {
242     float x, y;
243     if(d->orientation & ORIENTATION_SWAP_XY)
244     {
245       y = points[i];
246       x = points[i + 1];
247     }
248     else
249     {
250       x = points[i];
251       y = points[i + 1];
252     }
253     if(d->orientation & ORIENTATION_FLIP_X) x = piece->buf_in.width - x;
254     if(d->orientation & ORIENTATION_FLIP_Y) y = piece->buf_in.height - y;
255 
256     points[i] = x;
257     points[i + 1] = y;
258   }
259 
260   return 1;
261 }
262 
distort_mask(struct dt_iop_module_t * self,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)263 void distort_mask(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, const float *const in,
264                   float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
265 {
266   const dt_iop_flip_data_t *d = (dt_iop_flip_data_t *)piece->data;
267 
268   const int bpp = sizeof(float);
269   const int stride = bpp * roi_in->width;
270 
271   dt_imageio_flip_buffers((char *)out, (const char *)in, bpp, roi_in->width, roi_in->height,
272                           roi_in->width, roi_in->height, stride, d->orientation);
273 }
274 
275 // 1st pass: how large would the output be, given this input roi?
276 // this is always called with the full buffer before processing.
modify_roi_out(struct dt_iop_module_t * self,struct dt_dev_pixelpipe_iop_t * piece,dt_iop_roi_t * roi_out,const dt_iop_roi_t * roi_in)277 void modify_roi_out(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
278                     const dt_iop_roi_t *roi_in)
279 {
280   const dt_iop_flip_data_t *d = (dt_iop_flip_data_t *)piece->data;
281   *roi_out = *roi_in;
282 
283   // transform whole buffer roi
284   if(d->orientation & ORIENTATION_SWAP_XY)
285   {
286     roi_out->width = roi_in->height;
287     roi_out->height = roi_in->width;
288   }
289 }
290 
291 // 2nd pass: which roi would this operation need as input to fill the given output region?
modify_roi_in(struct dt_iop_module_t * self,struct dt_dev_pixelpipe_iop_t * piece,const dt_iop_roi_t * roi_out,dt_iop_roi_t * roi_in)292 void modify_roi_in(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
293                    const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
294 {
295   const dt_iop_flip_data_t *d = (dt_iop_flip_data_t *)piece->data;
296   *roi_in = *roi_out;
297   // transform aabb back to roi_in
298 
299   // this aabb contains all valid points (thus the -1)
300   int32_t p[2], o[2],
301       aabb[4] = { roi_out->x, roi_out->y, roi_out->x + roi_out->width - 1, roi_out->y + roi_out->height - 1 };
302   int32_t aabb_in[4] = { INT_MAX, INT_MAX, INT_MIN, INT_MIN };
303   for(int c = 0; c < 4; c++)
304   {
305     // get corner points of roi_out
306     get_corner(aabb, c, p);
307     // backtransform aabb
308     backtransform(p, o, d->orientation, piece->buf_out.width * roi_out->scale,
309                   piece->buf_out.height * roi_out->scale);
310     // transform to roi_in space, get aabb.
311     adjust_aabb(o, aabb_in);
312   }
313 
314   // adjust roi_in to minimally needed region
315   roi_in->x = aabb_in[0];
316   roi_in->y = aabb_in[1];
317   // to convert valid points to widths, we need to add one
318   roi_in->width = aabb_in[2] - aabb_in[0] + 1;
319   roi_in->height = aabb_in[3] - aabb_in[1] + 1;
320 
321   // sanity check.
322   float w = piece->buf_in.width * roi_out->scale, h = piece->buf_in.height * roi_out->scale;
323   roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(w));
324   roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(h));
325   roi_in->width = CLAMP(roi_in->width, 1, (int)ceilf(w) - roi_in->x);
326   roi_in->height = CLAMP(roi_in->height, 1, (int)ceilf(h) - roi_in->y);
327 }
328 
329 // 3rd (final) pass: you get this input region (may be different from what was requested above),
330 // do your best to fill the output region!
process(struct 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)331 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
332              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
333 {
334   const dt_iop_flip_data_t *d = (dt_iop_flip_data_t *)piece->data;
335 
336   const int bpp = sizeof(float) * piece->colors;
337   const int stride = bpp * roi_in->width;
338 
339   dt_imageio_flip_buffers((char *)ovoid, (const char *)ivoid, bpp, roi_in->width, roi_in->height,
340                           roi_in->width, roi_in->height, stride, d->orientation);
341 }
342 
343 #ifdef HAVE_OPENCL
process_cl(struct 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)344 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
345                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
346 {
347   const dt_iop_flip_data_t *data = (dt_iop_flip_data_t *)piece->data;
348   const dt_iop_flip_global_data_t *gd = (dt_iop_flip_global_data_t *)self->global_data;
349   cl_int err = -999;
350 
351   const int devid = piece->pipe->devid;
352   const int width = roi_in->width;
353   const int height = roi_in->height;
354   const int orientation = data->orientation;
355 
356   size_t sizes[] = { ROUNDUPWD(width), ROUNDUPWD(height), 1 };
357 
358   dt_opencl_set_kernel_arg(devid, gd->kernel_flip, 0, sizeof(cl_mem), (void *)&dev_in);
359   dt_opencl_set_kernel_arg(devid, gd->kernel_flip, 1, sizeof(cl_mem), (void *)&dev_out);
360   dt_opencl_set_kernel_arg(devid, gd->kernel_flip, 2, sizeof(int), (void *)&width);
361   dt_opencl_set_kernel_arg(devid, gd->kernel_flip, 3, sizeof(int), (void *)&height);
362   dt_opencl_set_kernel_arg(devid, gd->kernel_flip, 4, sizeof(int), (void *)&orientation);
363   err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_flip, sizes);
364 
365   if(err != CL_SUCCESS) goto error;
366   return TRUE;
367 
368 error:
369   dt_print(DT_DEBUG_OPENCL, "[opencl_flip] couldn't enqueue kernel! %d\n", err);
370   return FALSE;
371 }
372 #endif
373 
init_global(dt_iop_module_so_t * self)374 void init_global(dt_iop_module_so_t *self)
375 {
376   const int program = 2; // basic.cl, from programs.conf
377   dt_iop_flip_global_data_t *gd = (dt_iop_flip_global_data_t *)malloc(sizeof(dt_iop_flip_global_data_t));
378   self->data = gd;
379   gd->kernel_flip = dt_opencl_create_kernel(program, "flip");
380 }
381 
cleanup_global(dt_iop_module_so_t * self)382 void cleanup_global(dt_iop_module_so_t *self)
383 {
384   const dt_iop_flip_global_data_t *gd = (dt_iop_flip_global_data_t *)self->data;
385   dt_opencl_free_kernel(gd->kernel_flip);
386   free(self->data);
387   self->data = NULL;
388 }
389 
commit_params(struct dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)390 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
391                    dt_dev_pixelpipe_iop_t *piece)
392 {
393   const dt_iop_flip_params_t *p = (dt_iop_flip_params_t *)p1;
394   dt_iop_flip_data_t *d = (dt_iop_flip_data_t *)piece->data;
395 
396   if(p->orientation == ORIENTATION_NULL)
397     d->orientation = dt_image_orientation(&self->dev->image_storage);
398   else
399     d->orientation = p->orientation;
400 
401   if(d->orientation == ORIENTATION_NONE) piece->enabled = 0;
402 }
403 
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)404 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
405 {
406   piece->data = malloc(sizeof(dt_iop_flip_data_t));
407 }
408 
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)409 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
410 {
411   free(piece->data);
412   piece->data = NULL;
413 }
414 
init_presets(dt_iop_module_so_t * self)415 void init_presets(dt_iop_module_so_t *self)
416 {
417   dt_iop_flip_params_t p = (dt_iop_flip_params_t){ ORIENTATION_NONE };
418   DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "BEGIN", NULL, NULL, NULL);
419 
420   p.orientation = ORIENTATION_NULL;
421   dt_gui_presets_add_generic(_("autodetect"), self->op,
422                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_NONE);
423   dt_gui_presets_update_autoapply(_("autodetect"), self->op, self->version(), 1);
424 
425   p.orientation = ORIENTATION_NONE;
426   dt_gui_presets_add_generic(_("no rotation"), self->op,
427                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_NONE);
428 
429   p.orientation = ORIENTATION_FLIP_HORIZONTALLY;
430   dt_gui_presets_add_generic(_("flip horizontally"), self->op,
431                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_NONE);
432 
433   p.orientation = ORIENTATION_FLIP_VERTICALLY;
434   dt_gui_presets_add_generic(_("flip vertically"), self->op,
435                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_NONE);
436 
437   p.orientation = ORIENTATION_ROTATE_CW_90_DEG;
438   dt_gui_presets_add_generic(_("rotate by -90 degrees"), self->op,
439                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_NONE);
440 
441   p.orientation = ORIENTATION_ROTATE_CCW_90_DEG;
442   dt_gui_presets_add_generic(_("rotate by  90 degrees"), self->op,
443                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_NONE);
444 
445   p.orientation = ORIENTATION_ROTATE_180_DEG;
446   dt_gui_presets_add_generic(_("rotate by 180 degrees"), self->op,
447                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_NONE);
448 
449   DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "COMMIT", NULL, NULL, NULL);
450 }
451 
reload_defaults(dt_iop_module_t * self)452 void reload_defaults(dt_iop_module_t *self)
453 {
454   dt_iop_flip_params_t *d = self->default_params;
455 
456   d->orientation = ORIENTATION_NULL;
457 
458   self->default_enabled = 1;
459 
460   if(self->dev->image_storage.legacy_flip.user_flip != 0
461      && self->dev->image_storage.legacy_flip.user_flip != 0xff)
462   {
463     sqlite3_stmt *stmt;
464     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
465                                 "SELECT * FROM main.history WHERE imgid = ?1 AND operation = 'flip'", -1, &stmt,
466                                 NULL);
467     DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, self->dev->image_storage.id);
468     if(sqlite3_step(stmt) != SQLITE_ROW)
469     {
470       // convert the old legacy flip bits to a proper parameter set:
471       d->orientation
472           = merge_two_orientations(dt_image_orientation(&self->dev->image_storage),
473                                    (dt_image_orientation_t)(self->dev->image_storage.legacy_flip.user_flip));
474     }
475     sqlite3_finalize(stmt);
476   }
477 }
478 
do_rotate(dt_iop_module_t * self,uint32_t cw)479 static void do_rotate(dt_iop_module_t *self, uint32_t cw)
480 {
481   dt_iop_flip_params_t *p = (dt_iop_flip_params_t *)self->params;
482   dt_image_orientation_t orientation = p->orientation;
483 
484   if(orientation == ORIENTATION_NULL) orientation = dt_image_orientation(&self->dev->image_storage);
485 
486   if(cw == 0)
487   {
488     if(orientation & ORIENTATION_SWAP_XY)
489       orientation ^= ORIENTATION_FLIP_Y;
490     else
491       orientation ^= ORIENTATION_FLIP_X;
492   }
493   else
494   {
495     if(orientation & ORIENTATION_SWAP_XY)
496       orientation ^= ORIENTATION_FLIP_X;
497     else
498       orientation ^= ORIENTATION_FLIP_Y;
499   }
500   orientation ^= ORIENTATION_SWAP_XY;
501 
502   p->orientation = orientation;
503   dt_dev_add_history_item(darktable.develop, self, TRUE);
504 }
rotate_cw(GtkWidget * widget,dt_iop_module_t * self)505 static void rotate_cw(GtkWidget *widget, dt_iop_module_t *self)
506 {
507   do_rotate(self, 1);
508 }
rotate_ccw(GtkWidget * widget,dt_iop_module_t * self)509 static void rotate_ccw(GtkWidget *widget, dt_iop_module_t *self)
510 {
511   do_rotate(self, 0);
512 }
_flip_h(GtkWidget * widget,dt_iop_module_t * self)513 static void _flip_h(GtkWidget *widget, dt_iop_module_t *self)
514 {
515   dt_iop_flip_params_t *p = (dt_iop_flip_params_t *)self->params;
516   dt_image_orientation_t orientation = p->orientation;
517 
518   if(orientation == ORIENTATION_NULL) orientation = dt_image_orientation(&self->dev->image_storage);
519 
520   if(orientation & ORIENTATION_SWAP_XY)
521     p->orientation = orientation ^ ORIENTATION_FLIP_VERTICALLY;
522   else
523     p->orientation = orientation ^ ORIENTATION_FLIP_HORIZONTALLY;
524 
525   dt_dev_add_history_item(darktable.develop, self, TRUE);
526 }
_flip_v(GtkWidget * widget,dt_iop_module_t * self)527 static void _flip_v(GtkWidget *widget, dt_iop_module_t *self)
528 {
529   dt_iop_flip_params_t *p = (dt_iop_flip_params_t *)self->params;
530   dt_image_orientation_t orientation = p->orientation;
531 
532   if(orientation == ORIENTATION_NULL) orientation = dt_image_orientation(&self->dev->image_storage);
533 
534   if(orientation & ORIENTATION_SWAP_XY)
535     p->orientation = orientation ^ ORIENTATION_FLIP_HORIZONTALLY;
536   else
537     p->orientation = orientation ^ ORIENTATION_FLIP_VERTICALLY;
538 
539   dt_dev_add_history_item(darktable.develop, self, TRUE);
540 }
541 
gui_init(struct dt_iop_module_t * self)542 void gui_init(struct dt_iop_module_t *self)
543 {
544   self->gui_data = NULL;
545   dt_iop_flip_params_t *p = (dt_iop_flip_params_t *)self->params;
546 
547   self->widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
548 
549   GtkWidget *label = dtgtk_reset_label_new(_("transform"), self, &p->orientation, sizeof(int32_t));
550   gtk_box_pack_start(GTK_BOX(self->widget), label, TRUE, TRUE, 0);
551 
552   dt_iop_button_new(self, N_("rotate 90 degrees CCW"),
553                     G_CALLBACK(rotate_ccw), FALSE, GDK_KEY_bracketleft, 0,
554                     dtgtk_cairo_paint_refresh, 0, self->widget);
555 
556   dt_iop_button_new(self, N_("rotate 90 degrees CW"),
557                     G_CALLBACK(rotate_cw), FALSE, GDK_KEY_bracketright, 0,
558                     dtgtk_cairo_paint_refresh, 1, self->widget);
559 
560   dt_iop_button_new(self, N_("flip horizontally"), G_CALLBACK(_flip_h), FALSE, 0, 0, dtgtk_cairo_paint_flip, 1,
561                     self->widget);
562 
563   dt_iop_button_new(self, N_("flip vertically"), G_CALLBACK(_flip_v), FALSE, 0, 0, dtgtk_cairo_paint_flip, 0,
564                     self->widget);
565 }
566 
gui_cleanup(struct dt_iop_module_t * self)567 void gui_cleanup(struct dt_iop_module_t *self)
568 {
569   self->gui_data = NULL;
570 }
571 
572 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
573 // vim: shiftwidth=2 expandtab tabstop=2 cindent
574 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
575