Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
iop_order.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2018-2021, 2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2018-2019 Edgardo Hoszowski.
5 Copyright (C) 2019, 2021 Aldric Renaudin.
6 Copyright (C) 2019 Andreas Schneider.
7 Copyright (C) 2019, 2021 Hanno Schwalm.
8 Copyright (C) 2019 Heiko Bauke.
9 Copyright (C) 2019 Jacques Le Clerc.
10 Copyright (C) 2019 jakubfi.
11 Copyright (C) 2019 luzpaz.
12 Copyright (C) 2019-2022 Pascal Obry.
13 Copyright (C) 2019-2020 Philippe Weyland.
14 Copyright (C) 2019, 2021 Sakari Kapanen.
15 Copyright (C) 2019 Tobias Ellinghaus.
16 Copyright (C) 2020 rawfiner.
17 Copyright (C) 2021 Anna.
18 Copyright (C) 2021 Hubert Kowalski.
19 Copyright (C) 2021 Ralf Brown.
20 Copyright (C) 2022 Martin Bařinka.
21 Copyright (C) 2025 Guillaume Stutin.
22
23 darktable is free software: you can redistribute it and/or modify
24 it under the terms of the GNU General Public License as published by
25 the Free Software Foundation, either version 3 of the License, or
26 (at your option) any later version.
27
28 darktable is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
32
33 You should have received a copy of the GNU General Public License
34 along with darktable. If not, see <http://www.gnu.org/licenses/>.
35*/
36#ifdef HAVE_CONFIG_H
37#include "config.h"
38#endif
39
40#include "develop/iop_order.h"
41#include "common/styles.h"
43#include "common/deprecations.h"
44#include "common/image.h"
45#include "caches/image_cache.h"
46#include "develop/develop.h"
47#include "develop/imageop.h"
48
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include "common/utility.h"
53#include <glib.h>
54#include "common/glib_utils.h"
55
56#define DT_IOP_ORDER_VERSION 5
57
58#define DT_IOP_ORDER_INFO (dt_get_debug_flags() & DT_DEBUG_IOPORDER)
59
60static void _ioppr_reset_iop_order(GList *iop_order_list);
61static dt_iop_order_entry_t *dt_ioppr_get_iop_order_entry(GList *iop_order_list, const char *op_name,
62 const int multi_priority);
63static gboolean dt_ioppr_write_iop_order(const dt_iop_order_t kind, GList *iop_order_list, const int32_t imgid);
65static void dt_ioppr_update_for_entries(dt_develop_t *dev, GList *entry_list, gboolean append);
66static void dt_ioppr_check_duplicate_iop_order(GList **_iop_list, GList *history_list);
67static void dt_ioppr_migrate_iop_order(struct dt_develop_t *dev, const int32_t imgid);
68static GList *dt_ioppr_extract_multi_instances_list(GList *iop_order_list);
69static GList *dt_ioppr_merge_multi_instance_iop_order_list(GList *iop_order_list, GList *multi_instance_list);
70static gboolean _ioppr_sanity_check_iop_order(GList *list);
71
72const char *iop_order_string[] =
73{
74 N_("custom"),
75 N_("legacy"),
76 N_("v3.0 RAW"),
77 N_("v3.0 JPEG"),
78 N_("Ansel RAW"),
79 N_("Ansel JPEG")
80};
81
82const char *dt_iop_order_string(const dt_iop_order_t order)
83{
84 if(order >= DT_IOP_ORDER_LAST)
85 return "???";
86 else
87 return iop_order_string[order];
88}
89
90// note legacy_order & v30_order have the original iop-order double that is
91// used only for the initial database migration.
92//
93// in the new code only the iop-order as int is used to order the module on the GUI.
94
95// @@_NEW_MODULE: For new module it is required to insert the new module name in both lists below.
96
98 { { 0.5f }, "basebuffer", 0},
99 { { 1.0f }, "rawprepare", 0},
100 { { 2.0f }, "invert", 0},
101 { { 2.5f }, "rawdenoiseai", 0},
102 { { 3.0f }, "temperature", 0},
103 { { 4.0f }, "highlights", 0},
104 { { 5.0f }, "cacorrect", 0},
105 { { 6.0f }, "hotpixels", 0},
106 { { 7.0f }, "rawdenoise", 0},
107 { { 8.0f }, "demosaic", 0},
108 { { 8.5f }, "detailmask", 0},
109 { { 9.0f }, "mask_manager", 0},
110 { {10.0f }, "denoiseprofile", 0},
111 { {11.0f }, "tonemap", 0},
112 { {12.0f }, "exposure", 0},
113 { {13.0f }, "spots", 0},
114 { {14.0f }, "retouch", 0},
115 { {15.0f }, "lens", 0},
116 { {15.5f }, "cacorrectrgb", 0},
117 { {15.5f }, "initialscale", 0},
118 { {16.0f }, "ashift", 0},
119 { {17.0f }, "liquify", 0},
120 { {18.0f }, "rotatepixels", 0},
121 { {19.0f }, "scalepixels", 0},
122 { {20.0f }, "flip", 0},
123 { {21.0f }, "clipping", 0},
124 { {21.5f }, "toneequal", 0},
125 { {21.7f }, "crop", 0},
126 { {22.0f }, "graduatednd", 0},
127 { {23.0f }, "basecurve", 0},
128 { {24.0f }, "bilateral", 0},
129 { {25.0f }, "profile_gamma", 0},
130 { {26.0f }, "hazeremoval", 0},
131 { {27.0f }, "colorin", 0},
132 { {27.5f }, "channelmixerrgb", 0},
133 { {27.5f }, "diffuse", 0},
134 { {27.5f }, "censorize", 0},
135 { {27.5f }, "negadoctor", 0},
136 { {27.5f }, "blurs", 0},
137 { {27.5f }, "basicadj", 0},
138 { {28.0f }, "colorreconstruct", 0},
139 { {29.0f }, "colorchecker", 0},
140 { {30.0f }, "defringe", 0},
141 { {31.0f }, "equalizer", 0},
142 { {32.0f }, "vibrance", 0},
143 { {33.0f }, "colorbalance", 0},
144 { {33.4f }, "splittoningrgb", 0},
145 { {33.45f }, "colorprimaries", 0},
146 { {33.5f }, "colorbalancergb", 0},
147 { {33.6f }, "colorequal", 0},
148 { {33.7f }, "drawlayer", 0},
149 { {34.0f }, "colorize", 0},
150 { {36.0f }, "colormapping", 0},
151 { {37.0f }, "bloom", 0},
152 { {38.0f }, "nlmeans", 0},
153 { {39.0f }, "globaltonemap", 0},
154 { {40.0f }, "shadhi", 0},
155 { {41.0f }, "atrous", 0},
156 { {42.0f }, "bilat", 0},
157 { {43.0f }, "colorzones", 0},
158 { {44.0f }, "lowlight", 0},
159 { {45.0f }, "monochrome", 0},
160 { {46.0f }, "filmic", 0},
161 { {46.25f }, "crystgrain", 0},
162 { {46.5f }, "filmicrgb", 0},
163 { {47.0f }, "colisa", 0},
164 { {48.0f }, "zonesystem", 0},
165 { {49.0f }, "tonecurve", 0},
166 { {50.0f }, "levels", 0},
167 { {50.2f }, "rgblevels", 0},
168 { {50.5f }, "rgbcurve", 0},
169 { {51.0f }, "relight", 0},
170 { {52.0f }, "colorcorrection", 0},
171 { {53.0f }, "sharpen", 0},
172 { {54.0f }, "lowpass", 0},
173 { {55.0f }, "highpass", 0},
174 { {56.0f }, "grain", 0},
175 { {56.5f }, "lut3d", 0},
176 { {57.0f }, "colorcontrast", 0},
177 { {58.0f }, "colorout", 0},
178 { {59.0f }, "channelmixer", 0},
179 { {60.0f }, "soften", 0},
180 { {61.0f }, "vignette", 0},
181 { {62.0f }, "splittoning", 0},
182 { {63.0f }, "velvia", 0},
183 { {65.0f }, "finalscale", 0},
184 { {66.0f }, "overexposed", 0},
185 { {67.0f }, "rawoverexposed", 0},
186 { {67.5f }, "dither", 0},
187 { {68.0f }, "borders", 0},
188 { {69.0f }, "watermark", 0},
189 { {71.0f }, "gamma", 0},
190 { { 0.0f }, "", 0}
191};
192
193// default order for RAW files, assumed to be linear from start
195 { { 0.5 }, "basebuffer", 0},
196 { { 1.0 }, "rawprepare", 0},
197 { { 2.0 }, "invert", 0},
198 { { 2.5f }, "rawdenoiseai", 0},
199 { { 3.0f }, "temperature", 0},
200 { { 4.0f }, "highlights", 0},
201 { { 5.0f }, "cacorrect", 0},
202 { { 6.0f }, "hotpixels", 0},
203 { { 7.0f }, "rawdenoise", 0},
204 { { 8.0f }, "demosaic", 0},
205 { { 8.5f }, "detailmask", 0},
206 { { 9.0f }, "denoiseprofile", 0},
207 { {10.0f }, "bilateral", 0},
208 { {11.0f }, "rotatepixels", 0},
209 { {12.0f }, "scalepixels", 0},
210 { {13.0f }, "lens", 0},
211 { {13.5f }, "cacorrectrgb", 0}, // correct chromatic aberrations after lens correction so that lensfun
212 // does not reintroduce chromatic aberrations when trying to correct them
213 { {14.0f }, "hazeremoval", 0},
214 { {14.0f }, "initialscale", 0},
215 { {15.0f }, "ashift", 0},
216 { {16.0f }, "flip", 0},
217 { {17.0f }, "clipping", 0},
218 { {18.0f }, "liquify", 0},
219 { {19.0f }, "spots", 0},
220 { {20.0f }, "retouch", 0},
221 { {21.0f }, "exposure", 0},
222 { {22.0f }, "mask_manager", 0},
223 { {23.0f }, "tonemap", 0},
224 { {24.0f }, "toneequal", 0}, // last module that need enlarged roi_in
225 { {24.5f }, "crop", 0}, // should go after all modules that may need a wider roi_in
226 { {25.0f }, "graduatednd", 0},
227 { {26.0f }, "profile_gamma", 0},
228 { {28.0f }, "colorin", 0},
229 { {28.5f }, "channelmixerrgb", 0},
230 { {28.5f }, "diffuse", 0},
231 { {28.5f }, "censorize", 0},
232 { {28.5f }, "negadoctor", 0}, // Cineon film encoding comes after scanner input color profile
233 { {28.5f }, "blurs", 0}, // physically-accurate blurs (motion and lens)
234 { {29.0f }, "nlmeans", 0}, // signal processing (denoising)
235 // -> needs a signal as scene-referred as possible (even if it works in Lab)
236 { {30.0f }, "colorchecker", 0}, // calibration to "neutral" exchange colour space
237 // -> improve colour calibration of colorin and reproductibility
238 // of further edits (styles etc.)
239 { {31.0f }, "defringe", 0}, // desaturate fringes in Lab, so needs properly calibrated colours
240 // in order for chromaticity to be meaningful,
241 { {32.0f }, "atrous", 0}, // frequential operation, needs a signal as scene-referred as possible to avoid halos
242 { {33.0f }, "lowpass", 0}, // same
243 { {34.0f }, "highpass", 0}, // same
244 { {35.0f }, "sharpen", 0}, // same, worst than atrous in same use-case, less control overall
245
246 { {37.0f }, "colortransfer", 0}, // probably better if source and destination colours are neutralized in the same
247 // colour exchange space, hence after colorin and colorcheckr,
248 // but apply after frequential ops in case it does non-linear witchcraft,
249 // just to be safe
250 { {38.0f }, "colormapping", 0}, // same
251 { {39.0f }, "channelmixer", 0}, // does exactly the same thing as colorin, aka RGB to RGB matrix conversion,
252 // but coefs are user-defined instead of calibrated and read from ICC profile.
253 // Really versatile yet under-used module, doing linear ops,
254 // very good in scene-referred workflow
255 { {40.0f }, "basicadj", 0}, // module mixing view/model/control at once, usage should be discouraged
256 { {41.0f }, "colorbalance", 0}, // scene-referred color manipulation
257 { {41.4f }, "splittoningrgb", 0}, // keyed CAT16 plus RGB mixer before primary warping
258 { {41.45f }, "colorprimaries", 0}, // editable RGB/CYM primary nodes in dt UCS
259 { {41.5f }, "colorbalancergb", 0}, // scene-referred color manipulation
260 { {41.6f }, "colorequal", 0}, // dynamic hue-defined RGB stretching around the achromatic axis
261 { {41.7f }, "drawlayer", 0}, // TIFF-backed paint layers in scene-referred RGB
262 { {42.0f }, "rgbcurve", 0}, // really versatile way to edit colour in scene-referred and display-referred workflow
263 { {43.0f }, "rgblevels", 0}, // same
264 { {44.0f }, "basecurve", 0}, // conversion from scene-referred to display referred, reverse-engineered
265 // on camera JPEG default look
266 { {45.0f }, "filmic", 0}, // same, but different (parametric) approach
267 { {45.5f }, "crystgrain", 0}, // scene-referred grain, before filmic RGB
268 { {46.0f }, "filmicrgb", 0}, // same, upgraded
269 { {36.0f }, "lut3d", 0}, // apply a creative style or film emulation, possibly non-linear
270 { {47.0f }, "colisa", 0}, // edit contrast while damaging colour
271 { {48.0f }, "tonecurve", 0}, // same
272 { {49.0f }, "levels", 0}, // same
273 { {50.0f }, "shadhi", 0}, // same
274 { {51.0f }, "zonesystem", 0}, // same
275 { {52.0f }, "globaltonemap", 0}, // same
276 { {53.0f }, "relight", 0}, // flatten local contrast while pretending do add lightness
277 { {54.0f }, "bilat", 0}, // improve clarity/local contrast after all the bad things we have done
278 // to it with tonemapping
279 { {55.0f }, "colorcorrection", 0}, // now that the colours have been damaged by contrast manipulations,
280 // try to recover them - global adjustment of white balance for shadows and highlights
281 { {56.0f }, "colorcontrast", 0}, // adjust chrominance globally
282 { {57.0f }, "velvia", 0}, // same
283 { {58.0f }, "vibrance", 0}, // same, but more subtle
284 { {60.0f }, "colorzones", 0}, // same, but locally
285 { {61.0f }, "bloom", 0}, // creative module
286 { {62.0f }, "colorize", 0}, // creative module
287 { {63.0f }, "lowlight", 0}, // creative module
288 { {64.0f }, "monochrome", 0}, // creative module
289 { {65.0f }, "grain", 0}, // creative module
290 { {66.0f }, "soften", 0}, // creative module
291 { {67.0f }, "splittoning", 0}, // creative module
292 { {68.0f }, "vignette", 0}, // creative module
293 { {69.0f }, "colorreconstruct", 0},// try to salvage blown areas before ICC intents in LittleCMS2 do things with them.
294 { {70.0f }, "colorout", 0},
295 { {72.0f }, "finalscale", 0},
296 { {73.0f }, "overexposed", 0},
297 { {74.0f }, "rawoverexposed", 0},
298 { {75.0f }, "dither", 0},
299 { {76.0f }, "borders", 0},
300 { {77.0f }, "watermark", 0},
301 { {78.0f }, "gamma", 0},
302 { { 0.0f }, "", 0 }
303};
304
305// default order for JPEG/TIFF/PNG files, non-linear before colorin
307 // the following modules are not used anyway for non-RAW images :
308 { { 0.5 }, "basebuffer", 0 },
309 { { 1.0 }, "rawprepare", 0 },
310 { { 2.0 }, "invert", 0 },
311 { { 2.5f }, "rawdenoiseai", 0 },
312 { { 3.0f }, "temperature", 0 },
313 { { 4.0f }, "highlights", 0 },
314 { { 5.0f }, "cacorrect", 0 },
315 { { 6.0f }, "hotpixels", 0 },
316 { { 7.0f }, "rawdenoise", 0 },
317 { { 8.0f }, "demosaic", 0 },
318 { { 8.5f }, "detailmask", 0 },
319 // all the modules between [8; 28] expect linear RGB, so they need to be moved after colorin
320 { { 28.0f }, "colorin", 0 },
321 // moved modules : (copy-pasted in the same order)
322 { { 28.0f }, "denoiseprofile", 0},
323 { { 28.0f }, "bilateral", 0},
324 { { 28.0f }, "rotatepixels", 0},
325 { { 28.0f }, "scalepixels", 0},
326 { { 28.0f }, "lens", 0},
327 { { 28.0f }, "cacorrectrgb", 0}, // correct chromatic aberrations after lens correction so that lensfun
328 // does not reintroduce chromatic aberrations when trying to correct them
329 { { 28.0f }, "hazeremoval", 0},
330 { { 28.0f }, "initialscale", 0 },
331 { { 28.0f }, "ashift", 0},
332 { { 28.0f }, "flip", 0},
333 { { 28.0f }, "clipping", 0},
334 { { 28.0f }, "liquify", 0},
335 { { 28.0f }, "spots", 0},
336 { { 28.0f }, "retouch", 0},
337 { { 28.0f }, "exposure", 0},
338 { { 28.0f }, "mask_manager", 0},
339 { { 28.0f }, "tonemap", 0},
340 { { 28.0f }, "toneequal", 0}, // last module that need enlarged roi_in
341 { { 28.0f }, "crop", 0}, // should go after all modules that may need a wider roi_in
342 { { 28.0f }, "graduatednd", 0},
343 { { 28.0f }, "profile_gamma", 0},
344 // from there, it's the same as the raw order
345 { { 28.5f }, "channelmixerrgb", 0 },
346 { { 28.5f }, "diffuse", 0 },
347 { { 28.5f }, "censorize", 0 },
348 { { 28.5f }, "negadoctor", 0 }, // Cineon film encoding comes after scanner input color profile
349 { { 28.5f }, "blurs", 0 }, // physically-accurate blurs (motion and lens)
350 { { 29.0f }, "nlmeans", 0 }, // signal processing (denoising)
351 // -> needs a signal as scene-referred as possible (even if it works in Lab)
352 { { 30.0f }, "colorchecker", 0 }, // calibration to "neutral" exchange colour space
353 // -> improve colour calibration of colorin and reproductibility
354 // of further edits (styles etc.)
355 { { 31.0f }, "defringe", 0 }, // desaturate fringes in Lab, so needs properly calibrated colours
356 // in order for chromaticity to be meaningful,
357 { { 32.0f }, "atrous", 0 }, // frequential operation, needs a signal as scene-referred as possible to avoid halos
358 { { 33.0f }, "lowpass", 0 }, // same
359 { { 34.0f }, "highpass", 0 }, // same
360 { { 35.0f }, "sharpen", 0 }, // same, worst than atrous in same use-case, less control overall
361 { { 38.0f }, "colormapping", 0 }, // same
362 { { 39.0f }, "channelmixer", 0 }, // does exactly the same thing as colorin, aka RGB to RGB matrix conversion,
363 // but coefs are user-defined instead of calibrated and read from ICC
364 // profile. Really versatile yet under-used module, doing linear ops, very
365 // good in scene-referred workflow
366 { { 40.0f }, "basicadj", 0 }, // module mixing view/model/control at once, usage should be discouraged
367 { { 41.0f }, "colorbalance", 0 }, // scene-referred color manipulation
368 { { 41.4f }, "splittoningrgb", 0 }, // keyed CAT16 plus RGB mixer before primary warping
369 { { 41.45f }, "colorprimaries", 0 }, // editable RGB/CYM primary nodes in dt UCS
370 { { 41.5f }, "colorbalancergb", 0 }, // scene-referred color manipulation
371 { { 41.6f }, "colorequal", 0 }, // dynamic hue-defined RGB stretching around the achromatic axis
372 { { 41.7f }, "drawlayer", 0 }, // TIFF-backed paint layers in scene-referred RGB
373 { { 42.0f }, "rgbcurve", 0 }, // really versatile way to edit colour in scene-referred and display-referred
374 // workflow
375 { { 43.0f }, "rgblevels", 0 }, // same
376 { { 44.0f }, "basecurve", 0 }, // conversion from scene-referred to display referred, reverse-engineered
377 // on camera JPEG default look
378 { { 45.0f }, "filmic", 0 }, // same, but different (parametric) approach
379 { { 45.5f }, "crystgrain", 0 }, // scene-referred grain, before filmic RGB
380 { { 46.0f }, "filmicrgb", 0 }, // same, upgraded
381 { { 36.0f }, "lut3d", 0 }, // apply a creative style or film emulation, possibly non-linear
382 { { 47.0f }, "colisa", 0 }, // edit contrast while damaging colour
383 { { 48.0f }, "tonecurve", 0 }, // same
384 { { 49.0f }, "levels", 0 }, // same
385 { { 50.0f }, "shadhi", 0 }, // same
386 { { 51.0f }, "zonesystem", 0 }, // same
387 { { 52.0f }, "globaltonemap", 0 }, // same
388 { { 53.0f }, "relight", 0 }, // flatten local contrast while pretending do add lightness
389 { { 54.0f }, "bilat", 0 }, // improve clarity/local contrast after all the bad things we have done
390 // to it with tonemapping
391 { { 55.0f }, "colorcorrection", 0 }, // now that the colours have been damaged by contrast manipulations,
392 // try to recover them - global adjustment of white balance for shadows and
393 // highlights
394 { { 56.0f }, "colorcontrast", 0 }, // adjust chrominance globally
395 { { 57.0f }, "velvia", 0 }, // same
396 { { 58.0f }, "vibrance", 0 }, // same, but more subtle
397 { { 60.0f }, "colorzones", 0 }, // same, but locally
398 { { 61.0f }, "bloom", 0 }, // creative module
399 { { 62.0f }, "colorize", 0 }, // creative module
400 { { 63.0f }, "lowlight", 0 }, // creative module
401 { { 64.0f }, "monochrome", 0 }, // creative module
402 { { 65.0f }, "grain", 0 }, // creative module
403 { { 66.0f }, "soften", 0 }, // creative module
404 { { 67.0f }, "splittoning", 0 }, // creative module
405 { { 68.0f }, "vignette", 0 }, // creative module
406 { { 69.0f }, "colorreconstruct", 0 }, // try to salvage blown areas before ICC intents in LittleCMS2 do things
407 // with them.
408 { { 70.0f }, "colorout", 0 },
409 { { 72.0f }, "finalscale", 0 },
410 { { 73.0f }, "overexposed", 0 },
411 { { 74.0f }, "rawoverexposed", 0 },
412 { { 75.0f }, "dither", 0 },
413 { { 76.0f }, "borders", 0 },
414 { { 77.0f }, "watermark", 0 },
415 { { 78.0f }, "gamma", 0 },
416 { { 0.0f }, "", 0 }
417};
418
420 // RAW modules. Not used on JPG anyway
421 { { 0.5f}, "basebuffer", 0 },
422 { { 1.0f }, "rawprepare", 0 },
423 { { 2.0f }, "invert", 0 },
424 { { 2.5f }, "rawdenoiseai", 0 },
425 { { 3.0f }, "highlights", 0 },
426 { { 4.0f }, "cacorrect", 0 },
427 { { 5.0f }, "hotpixels", 0 },
428 { { 6.0f }, "rawdenoise", 0 },
429 { { 7.0f }, "demosaic", 0 },
430
431 // input color profile: undo RGB TRC/gamma/EOTF
432 { { 8.0f }, "colorin", 0 },
433
434 // so from there we are in "linear RGB", meaning there has probably been some tone curve applied
435 // on the pixels before saving to raster, but now we don't carry the uint8_t encoding
436
437 { { 9.0f }, "detailmask", 0 },
438 { { 10.0f }, "temperature", 0 },
439 { { 11.0f }, "denoiseprofile", 0},
440 { { 12.0f }, "bilateral", 0}, // RGB surface blur
441 { { 13.0f }, "rotatepixels", 0},
442 { { 14.0f }, "scalepixels", 0},
443 { { 15.0f }, "lens", 0},
444 { { 16.0f }, "cacorrectrgb", 0}, // correct chromatic aberrations after lens correction so that lensfun
445 // does not reintroduce chromatic aberrations when trying to correct them
446 { { 17.0f }, "hazeremoval", 0},
447 { { 18.0f }, "initialscale", 0 },
448 { { 19.0f }, "ashift", 0},
449 { { 20.0f }, "flip", 0},
450 { { 21.0f }, "clipping", 0},
451 { { 22.0f }, "liquify", 0},
452 { { 23.0f }, "spots", 0},
453 { { 24.0f }, "retouch", 0},
454 { { 25.0f }, "mask_manager", 0},
455
456 // Tone corrections
457 { { 26.0f }, "exposure", 0},
458 { { 27.0f }, "vignette", 0 }, // creative module but emulates lens vignetting, RGB, linear
459 { { 28.0f }, "graduatednd", 0},
460 { { 29.0f }, "toneequal", 0}, // last module that need enlarged roi_in
461 { { 30.0f }, "crop", 0}, // should go after all modules that may need a wider roi_in
462 { { 31.0f }, "profile_gamma", 0}, // shouldn't be needed for JPG
463
464 // from there, it's the same as the raw order
465
466 // Linear color handling
467 { { 32.0f }, "negadoctor", 0 }, // Cineon film encoding comes after scanner input color profile
468 { { 33.0f }, "channelmixerrgb", 0 }, // CAT & new channel mixer
469 { { 34.0f }, "channelmixer", 0 }, // Old channel mixer : used HSL...
470
471 // Linear convolutions
472 { { 35.0f }, "diffuse", 0 },
473 { { 36.0f }, "censorize", 0 },
474 { { 37.0f }, "blurs", 0 }, // physically-accurate blurs (motion and lens)
475
476 // Color work in RGB
477 { { 38.0f }, "basicadj", 0 }, // legacy shit duplicating features
478 { { 39.0f }, "splittoningrgb", 0 }, // keyed CAT16 plus RGB mixer before primary warping
479 { { 40.0f }, "colorprimaries", 0 }, // editable RGB/CYM primary nodes in dt UCS
480 { { 41.0f }, "colorbalance", 0 }, // scene-referred color manipulation
481 { { 42.0f }, "colorbalancergb", 0 }, // scene-referred color manipulation
482 { { 43.0f }, "colorequal", 0 }, // dynamic hue-defined RGB stretching around the achromatic axis
483 { { 44.0f }, "drawlayer", 0 }, // TIFF-backed paint layers in scene-referred RGB
484 { { 45.0f }, "crystgrain", 0 }, // scene-referred grain, before filmic RGB
485
486 // Interpolation for export pipelines: works better before non-linear transforms
487 { { 46.0f }, "finalscale", 0 },
488
489 { { 47.0f }, "tonemap", 0}, // shitty but at least it's unbounded RGB
490
491 // Display transforms: HDR -> SDR
492 { { 48.0f }, "filmic", 0 }, // same, but different (parametric) approach
493 { { 49.0f }, "filmicrgb", 0 }, // same, upgraded
494 { { 50.0f }, "basecurve", 0 }, // conversion from scene-referred to display referred, reverse-engineered
495 // on camera JPEG default look
496
497 // SDR modules :
498
499 // Wannabe signal-processing modules but they work in Lab so it's shit
500 { { 51.0f }, "nlmeans", 0 }, // denoise
501 { { 52.0f }, "defringe", 0 }, // desaturate fringes
502 { { 53.0f }, "bilat", 0 }, // local contrast
503 { { 54.0f }, "atrous", 0 }, // frequential operation, needs a signal as scene-referred as possible to avoid halos
504 { { 55.0f }, "lowpass", 0 }, // same
505 { { 56.0f }, "highpass", 0 }, // same
506 { { 57.0f }, "sharpen", 0 }, // same, worst than atrous in same use-case, less control overall
507
508 // RGB modules but don't support HDR white
509 { { 58.0f }, "lut3d", 0 },
510 { { 59.0f }, "rgbcurve", 0 },
511 { { 60.0f }, "rgblevels", 0 },
512 { { 61.0f }, "splittoning", 0 }, // HSL inside
513
514 // Lab color modules
515 { { 62.0f }, "colorchecker", 0 }, // calibration
516 { { 63.0f }, "colormapping", 0 }, // automagic shit. toy filter
517 { { 64.0f }, "colorcorrection", 0 }, // now that the colours have been damaged by contrast manipulations,
518 // try to recover them - global adjustment of white balance for shadows and
519 // highlights
520 { { 65.0f }, "colorcontrast", 0 }, // adjust chrominance globally
521 { { 66.0f }, "velvia", 0 }, // same
522 { { 67.0f }, "vibrance", 0 }, // same, but more subtle
523 { { 68.0f }, "colorzones", 0 }, // same, but locally
524
525 // Legacy Lab shit that should never have existed
526 { { 69.0f }, "colisa", 0 }, // contrast, lightness, saturation
527 { { 70.0f }, "tonecurve", 0 }, // same
528 { { 71.0f }, "levels", 0 }, // same
529 { { 72.0f }, "shadhi", 0 }, // same
530 { { 73.0f }, "zonesystem", 0 }, // same
531 { { 74.0f }, "globaltonemap", 0 },
532
533 // Lab toy filters
534 { { 75.0f }, "relight", 0 }, // tone EQ but worse
535 { { 76.0f }, "bloom", 0 }, // blurs but worse
536 { { 77.0f }, "colorize", 0 }, // somewhere between channel mixer and color balance
537 { { 78.0f }, "lowlight", 0 }, // simulate scotopic (night) vision
538 { { 79.0f }, "monochrome", 0 }, // channel mixer B&W mode but worse
539 { { 80.0f }, "grain", 0 }, // crystgrain but worse
540 { { 81.0f }, "soften", 0 }, // blurs but worse
541 { { 82.0f }, "colorreconstruct", 0 }, // try to salvage blown areas before ICC intents in LittleCMS2 do things
542 // with them.
543
544 // Display RGB from there
545 { { 83.0f }, "colorout", 0 },
546 { { 84.0f }, "overexposed", 0 },
547 { { 85.0f }, "rawoverexposed", 0 },
548
549 // Those 2 are shit because they internally rely on display RGB being sRGB
550 // Doesn't work for large gamut displays...
551 { { 86.0f }, "borders", 0 },
552 { { 87.0f }, "watermark", 0 },
553
554 // Hide quantization errors with noise
555 { { 88.0f }, "dither", 0 },
556
557 // Float to uint8 but only for darkroom pipelines.
558 // Also handles mask previews.
559 { { 89.0f }, "gamma", 0 },
560
561 { { 0.0f }, "", 0 }
562};
563
564// default order for RAW files, assumed to be linear from start
566 // RAW stuff
567 { { 0.0f }, "basebuffer", 0 },
568 { { 1.0f }, "rawprepare", 0},
569 { { 2.0f }, "invert", 0},
570 { { 2.5f }, "rawdenoiseai", 0},
571 { { 3.0f }, "temperature", 0},
572 { { 4.0f }, "highlights", 0},
573 { { 5.0f }, "cacorrect", 0},
574 { { 6.0f }, "hotpixels", 0},
575 { { 7.0f }, "rawdenoise", 0},
576 { { 8.0f }, "demosaic", 0},
577
578 // Sensor RGB
579 { { 9.0f }, "denoiseprofile", 0},
580 { {10.0f }, "bilateral", 0},
581 { {11.0f }, "rotatepixels", 0},
582 { {12.0f }, "scalepixels", 0},
583 { {13.0f }, "detailmask", 0},
584 { {14.0f }, "lens", 0},
585 { {15.0f }, "cacorrectrgb", 0}, // correct chromatic aberrations after lens correction so that lensfun
586 // does not reintroduce chromatic aberrations when trying to correct them
587 { {16.0f }, "hazeremoval", 0},
588
589 { {17.0f }, "initialscale", 0},
590 { {18.0f }, "ashift", 0},
591 { {19.0f }, "flip", 0},
592 { {20.0f }, "clipping", 0},
593 { {21.0f }, "liquify", 0},
594 { {22.0f }, "spots", 0},
595 { {23.0f }, "retouch", 0},
596
597 // From there we support masking in modules
598 { {24.0f }, "mask_manager", 0},
599
600 // Linear tone corrections
601 { {25.0f }, "exposure", 0},
602 { {26.0f }, "vignette", 0 }, // creative module but emulates lens vignetting, RGB, linear
603 { {27.0f }, "graduatednd", 0},
604 { {28.0f }, "toneequal", 0}, // last module that need enlarged roi_in
605 { {29.0f }, "crop", 0}, // should go after all modules that may need a wider roi_in
606
607 // Needed by some very old cameras in RAW mode
608 { {30.0f }, "profile_gamma", 0},
609
610 { {31.0f }, "colorin", 0},
611
612 // from there, it's the same as the JPEG order
613
614 // Linear color handling
615 { { 32.0f }, "negadoctor", 0 }, // Cineon film encoding comes after scanner input color profile
616 { { 33.0f }, "channelmixerrgb", 0 }, // CAT & new channel mixer
617 { { 34.0f }, "channelmixer", 0 }, // Old channel mixer : used HSL...
618
619 // Linear convolutions
620 { { 35.0f }, "diffuse", 0 },
621 { { 36.0f }, "censorize", 0 },
622 { { 37.0f }, "blurs", 0 }, // physically-accurate blurs (motion and lens)
623
624 // Color work in RGB
625 { { 38.0f }, "basicadj", 0 }, // legacy shit duplicating features
626 { { 39.0f }, "splittoningrgb", 0 }, // keyed CAT16 plus RGB mixer before primary warping
627 { { 40.0f }, "colorprimaries", 0 }, // editable RGB/CYM primary nodes in dt UCS
628 { { 41.0f }, "colorbalance", 0 }, // scene-referred color manipulation
629 { { 42.0f }, "colorbalancergb", 0 }, // scene-referred color manipulation
630 { { 43.0f }, "colorequal", 0 }, // dynamic hue-defined RGB stretching around the achromatic axis
631 { { 44.0f }, "drawlayer", 0 }, // TIFF-backed paint layers in scene-referred RGB
632 { { 45.0f }, "crystgrain", 0 }, // scene-referred grain, before filmic RGB
633
634 // Interpolation for export pipelines: works better before non-linear transforms
635 { { 46.0f }, "finalscale", 0 },
636
637 { { 47.0f }, "tonemap", 0}, // shitty but at least it's unbounded RGB
638
639 // Display transforms: HDR -> SDR
640 { { 48.0f }, "filmic", 0 }, // same, but different (parametric) approach
641 { { 49.0f }, "filmicrgb", 0 }, // same, upgraded
642 { { 50.0f }, "basecurve", 0 }, // conversion from scene-referred to display referred, reverse-engineered
643 // on camera JPEG default look
644
645 // SDR modules :
646
647 // Wannabe signal-processing modules but they work in Lab so it's shit
648 { { 51.0f }, "nlmeans", 0 }, // denoise
649 { { 52.0f }, "defringe", 0 }, // desaturate fringes
650 { { 53.0f }, "bilat", 0 }, // local contrast
651 { { 54.0f }, "atrous", 0 }, // frequential operation, needs a signal as scene-referred as possible to avoid halos
652 { { 55.0f }, "lowpass", 0 }, // same
653 { { 56.0f }, "highpass", 0 }, // same
654 { { 57.0f }, "sharpen", 0 }, // same, worst than atrous in same use-case, less control overall
655
656 // RGB modules but don't support HDR white
657 { { 58.0f }, "lut3d", 0 },
658 { { 59.0f }, "rgbcurve", 0 },
659 { { 60.0f }, "rgblevels", 0 },
660 { { 61.0f }, "splittoning", 0 }, // HSL inside
661
662 // Lab color modules
663 { { 62.0f }, "colorchecker", 0 }, // calibration
664 { { 63.0f }, "colormapping", 0 }, // automagic shit. toy filter
665 { { 64.0f }, "colorcorrection", 0 }, // now that the colours have been damaged by contrast manipulations,
666 // try to recover them - global adjustment of white balance for shadows and
667 // highlights
668 { { 65.0f }, "colorcontrast", 0 }, // adjust chrominance globally
669 { { 66.0f }, "velvia", 0 }, // same
670 { { 67.0f }, "vibrance", 0 }, // same, but more subtle
671 { { 68.0f }, "colorzones", 0 }, // same, but locally
672
673 // Legacy Lab shit that should never have existed
674 { { 69.0f }, "colisa", 0 }, // contrast, lightness, saturation
675 { { 70.0f }, "tonecurve", 0 }, // same
676 { { 71.0f }, "levels", 0 }, // same
677 { { 72.0f }, "shadhi", 0 }, // same
678 { { 73.0f }, "zonesystem", 0 }, // same
679 { { 74.0f }, "globaltonemap", 0 },
680
681 // Lab toy filters
682 { { 75.0f }, "relight", 0 }, // tone EQ but worse
683 { { 76.0f }, "bloom", 0 }, // blurs but worse
684 { { 77.0f }, "colorize", 0 }, // somewhere between channel mixer and color balance
685 { { 78.0f }, "lowlight", 0 }, // simulate scotopic (night) vision
686 { { 79.0f }, "monochrome", 0 }, // channel mixer B&W mode but worse
687 { { 80.0f }, "grain", 0 }, // crystgrain but worse
688 { { 81.0f }, "soften", 0 }, // blurs but worse
689 { { 82.0f }, "colorreconstruct", 0 }, // try to salvage blown areas before ICC intents in LittleCMS2 do things
690 // with them.
691
692 // Display RGB from there
693 { { 83.0f }, "colorout", 0 },
694 { { 84.0f }, "overexposed", 0 },
695 { { 85.0f }, "rawoverexposed", 0 },
696
697 // Those 2 are shit because they internally rely on display RGB being sRGB
698 // Doesn't work for large gamut displays...
699 { { 86.0f }, "borders", 0 },
700 { { 87.0f }, "watermark", 0 },
701
702 // Hide quantization errors with noise
703 { { 88.0f }, "dither", 0 },
704
705 // Float to uint8 but only for darkroom pipelines.
706 // Also handles mask previews.
707 { { 89.0f }, "gamma", 0 },
708
709 { { 0.0f }, "", 0 }
710};
711
712static void *_dup_iop_order_entry(const void *src, gpointer data);
713static int _count_entries_operation(GList *e_list, const char *operation);
714
715
727static GList *_insert_before(GList *iop_order_list, const char *module, const char *new_module)
728{
729 gboolean exists = FALSE;
730
731 // First check that new module is missing
732 for(const GList *l = iop_order_list; l; l = g_list_next(l))
733 {
734 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data;
735 if(!strcmp(entry->operation, new_module))
736 {
737 exists = TRUE;
738 break;
739 }
740 }
741
742 // Insert it if needed
743 if(!exists)
744 {
745 for(GList *l = iop_order_list; l; l = g_list_next(l))
746 {
747 dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)l->data;
748
749 if(!strcmp(entry->operation, module))
750 {
751 dt_iop_order_entry_t *new_entry = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t));
752
753 g_strlcpy(new_entry->operation, new_module, sizeof(new_entry->operation));
754 new_entry->instance = 0;
755 new_entry->o.iop_order = 0;
756
757 // Capture the returned pointer! g_list_insert_before may change the head.
758 iop_order_list = g_list_insert_before(iop_order_list, l, new_entry);
759 break;
760 }
761 }
762 }
763
764 return iop_order_list;
765}
766
767/* @@_NEW_MODULE: When adding a new module, insert it into a deserialized/custom
768 * iop-order list here so that pre-existing edits (whose serialized order was
769 * saved before the module existed) still place it correctly instead of
770 * sanitizing its position to INT_MAX. This runs on BOTH deserialization paths:
771 * the DB module_order table (dt_ioppr_get_iop_order_list) and XMP import
772 * (dt_exif_xmp_read_data). Anchor each new module before an existing one. */
773GList *dt_ioppr_insert_missing_modules(GList *iop_order_list)
774{
775 iop_order_list = _insert_before(iop_order_list, "nlmeans", "negadoctor");
776 iop_order_list = _insert_before(iop_order_list, "negadoctor", "channelmixerrgb");
777 iop_order_list = _insert_before(iop_order_list, "negadoctor", "censorize");
778 iop_order_list = _insert_before(iop_order_list, "rgbcurve", "colorbalancergb");
779 iop_order_list = _insert_before(iop_order_list, "colorbalancergb", "colorprimaries");
780 iop_order_list = _insert_before(iop_order_list, "colorprimaries", "splittoningrgb");
781 iop_order_list = _insert_before(iop_order_list, "rgbcurve", "drawlayer");
782 iop_order_list = _insert_before(iop_order_list, "drawlayer", "colorequal");
783 iop_order_list = _insert_before(iop_order_list, "ashift", "cacorrectrgb");
784 iop_order_list = _insert_before(iop_order_list, "graduatednd", "crop");
785 iop_order_list = _insert_before(iop_order_list, "colorbalance", "diffuse");
786 iop_order_list = _insert_before(iop_order_list, "nlmeans", "blurs");
787 iop_order_list = _insert_before(iop_order_list, "ashift", "initialscale");
788 iop_order_list = _insert_before(iop_order_list, "filmicrgb", "crystgrain");
789 iop_order_list = _insert_before(iop_order_list, "mask_manager", "detailmask");
790 iop_order_list = _insert_before(iop_order_list, "rawprepare", "basebuffer");
791 // rawdenoiseai denoises pre-WB CFA data: it must run before temperature
792 // AND demosaic, so insert it before whichever comes first in this order
793 // (temperature sits after demosaic in JPG-style orders)
794 {
795 const char *first = "demosaic";
796 for(const GList *l = iop_order_list; l; l = g_list_next(l))
797 {
798 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data;
799 if(!strcmp(entry->operation, "temperature") || !strcmp(entry->operation, "demosaic"))
800 {
801 first = entry->operation;
802 break;
803 }
804 }
805 iop_order_list = _insert_before(iop_order_list, first, "rawdenoiseai");
806 }
807 return iop_order_list;
808}
809
810
812{
813 dt_iop_order_t iop_order_version = DT_IOP_ORDER_ANSEL_RAW;
814
815 // check current iop order version
816 int stored_version = 0;
817 const gboolean has_stored_order
818 = dt_history_repository_get_module_order_version(imgid, &stored_version);
819 if(has_stored_order) iop_order_version = stored_version;
820
821 if(!has_stored_order && imgid > 0 && !!dt_image_cache_is_ready())
822 {
823 const dt_image_t *image = dt_image_cache_testget(imgid, 'r');
824 if(!IS_NULL_PTR(image))
825 {
828 }
829 }
830
831 return iop_order_version;
832}
833
834// a rule prevents operations to be switched,
835// that is a prev operation will not be allowed to be moved on top of the next operation.
837{
838 GList *rules = NULL;
839
840 const dt_iop_order_rule_t rule_entry[] = {
841 { .op_prev = "basebuffer", .op_next = "rawprepare" },
842 { .op_prev = "rawprepare", .op_next = "invert" },
843 { .op_prev = "invert", .op_next = "temperature" },
844 { .op_prev = "highlights", .op_next = "cacorrect" },
845 { .op_prev = "cacorrect", .op_next = "hotpixels" },
846 { .op_prev = "hotpixels", .op_next = "rawdenoise" },
847 { .op_prev = "rawdenoise", .op_next = "demosaic" },
848 { .op_prev = "invert", .op_next = "rawdenoiseai"},
849 { .op_prev = "rawdenoiseai",.op_next = "temperature" },
850 { .op_prev = "rawdenoiseai",.op_next = "demosaic" },
851 { .op_prev = "demosaic", .op_next = "colorin" },
852 { .op_prev = "colorin", .op_next = "colorout" },
853 { .op_prev = "colorout", .op_next = "gamma" },
854 { .op_prev = "flip", .op_next = "crop" }, // crop GUI broken if flip is done on top
855 { .op_prev = "flip", .op_next = "clipping" }, // clipping GUI broken if flip is done on top
856 { .op_prev = "ashift", .op_next = "clipping" }, // clipping GUI broken if ashift is done on top
857 { .op_prev = "colorin", .op_next = "channelmixerrgb"},
858 { "\0", "\0" } };
859
860 int i = 0;
861 while(rule_entry[i].op_prev[0])
862 {
863 dt_iop_order_rule_t *rule = calloc(1, sizeof(dt_iop_order_rule_t));
864
865 memcpy(rule->op_prev, rule_entry[i].op_prev, sizeof(rule->op_prev));
866 memcpy(rule->op_next, rule_entry[i].op_next, sizeof(rule->op_next));
867
868 rules = g_list_prepend(rules, rule);
869 i++;
870 }
871
872 return g_list_reverse(rules); // list was built in reverse order, so un-reverse it
873}
874
875GList *dt_ioppr_get_iop_order_link(GList *iop_order_list, const char *op_name, const int multi_priority)
876{
877 GList *link = NULL;
878
879 for(GList *iops_order = iop_order_list; iops_order; iops_order = g_list_next(iops_order))
880 {
881 dt_iop_order_entry_t *order_entry = (dt_iop_order_entry_t *)iops_order->data;
882
883 if(strcmp(order_entry->operation, op_name) == 0
884 && (order_entry->instance == multi_priority || multi_priority == -1))
885 {
886 link = iops_order;
887 break;
888 }
889 }
890
891 return link;
892}
893
902static dt_iop_order_entry_t *dt_ioppr_get_iop_order_entry(GList *iop_order_list, const char *op_name,
903 const int multi_priority)
904{
905 const GList * const restrict link = dt_ioppr_get_iop_order_link(iop_order_list, op_name, multi_priority);
906 if(link)
907 return (dt_iop_order_entry_t *)link->data;
908 else
909 return NULL;
910}
911
912// returns the iop_order associated with the iop order entry that matches operation == op_name
913int dt_ioppr_get_iop_order(GList *iop_order_list, const char *op_name, const int multi_priority)
914{
915 int iop_order = INT_MAX;
916 const dt_iop_order_entry_t *const restrict order_entry =
917 dt_ioppr_get_iop_order_entry(iop_order_list, op_name, multi_priority);
918
919 if(order_entry)
920 {
921 iop_order = order_entry->o.iop_order;
922 }
923 else if(!dt_deprecated(op_name))
924 fprintf(stderr, "[iop_order] %s instance %d is missing from the saved pipeline order; "
925 "its position will be sanitized to the default order\n", op_name, multi_priority);
926
927 return iop_order;
928}
929
930gint dt_sort_iop_list_by_order(gconstpointer a, gconstpointer b)
931{
932 const dt_iop_order_entry_t *const restrict am = (const dt_iop_order_entry_t *)a;
933 const dt_iop_order_entry_t *const restrict bm = (const dt_iop_order_entry_t *)b;
934 if(am->o.iop_order > bm->o.iop_order) return 1;
935 if(am->o.iop_order < bm->o.iop_order) return -1;
936 return 0;
937}
938
939gint dt_sort_iop_list_by_order_f(gconstpointer a, gconstpointer b)
940{
941 const dt_iop_order_entry_t *const restrict am = (const dt_iop_order_entry_t *)a;
942 const dt_iop_order_entry_t *const restrict bm = (const dt_iop_order_entry_t *)b;
943 if(am->o.iop_order_f > bm->o.iop_order_f) return 1;
944 if(am->o.iop_order_f < bm->o.iop_order_f) return -1;
945 return 0;
946}
947
950
952{
953 if(IS_NULL_PTR(iop_order_list)) return DT_IOP_ORDER_CUSTOM;
954
955 for(dt_iop_order_t version = DT_IOP_ORDER_LEGACY; version < DT_IOP_ORDER_LAST; version++)
956 {
957 int k = 0;
958 GList *l = iop_order_list;
959 gboolean ok = TRUE;
960 const dt_iop_order_entry_t *order = orders[version - DT_IOP_ORDER_LEGACY];
961
962 // Compare the incoming operation sequence against each built-in order. The
963 // array excludes DT_IOP_ORDER_CUSTOM, so the array index must be translated
964 // back to the enum value before returning the detected order kind.
965 while(l)
966 {
967 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data;
968 if(strcmp(order[k].operation, entry->operation))
969 {
970 ok = FALSE;
971 break;
972 }
973 else
974 {
975 // skip all the other instance of same module if any
976 while(g_list_next(l)
977 && !strcmp(order[k].operation, ((dt_iop_order_entry_t *)(g_list_next(l)->data))->operation))
978 l = g_list_next(l);
979 }
980
981 k++;
982 l = g_list_next(l);
983 }
984
985 if(ok && order[k].operation[0] == '\0') return version;
986 }
987
988 return DT_IOP_ORDER_CUSTOM;
989}
990
991gboolean dt_ioppr_has_multiple_instances(GList *iop_order_list)
992{
993 GList *l = iop_order_list;
994
995 while(l)
996 {
997 GList *next = g_list_next(l);
998 if(next
999 && (strcmp(((dt_iop_order_entry_t *)(l->data))->operation,
1000 ((dt_iop_order_entry_t *)(next->data))->operation) == 0))
1001 {
1002 return TRUE;
1003 }
1004 l = next;
1005 }
1006 return FALSE;
1007}
1008
1020static gboolean dt_ioppr_write_iop_order(const dt_iop_order_t kind, GList *iop_order_list, const int32_t imgid)
1021{
1022 // The list is stored verbatim for a custom order, and also whenever the image carries more
1023 // than one instance of some module -- a built-in version number alone cannot describe that.
1024 // Otherwise the version is the whole answer and the column stays NULL.
1025 const gboolean store_list = (kind == DT_IOP_ORDER_CUSTOM
1026 || dt_ioppr_has_multiple_instances(iop_order_list));
1027
1028 gchar *iop_list_txt = store_list ? dt_ioppr_serialize_text_iop_order_list(iop_order_list) : NULL;
1029 const gboolean ok = dt_history_repository_set_module_order(imgid, kind, iop_list_txt);
1030 dt_free(iop_list_txt);
1031
1032 return ok;
1033}
1034
1035gboolean dt_ioppr_write_iop_order_list(GList *iop_order_list, const int32_t imgid)
1036{
1038 return dt_ioppr_write_iop_order(kind, iop_order_list, imgid);
1039}
1040
1048{
1049 GList *iop_order_list = NULL;
1050 int k = 0;
1051 while(entries[k].operation[0])
1052 {
1054
1055 g_strlcpy(entry->operation, entries[k].operation, sizeof(entry->operation));
1056 entry->instance = 0;
1057 entry->o.iop_order_f = entries[k].o.iop_order_f;
1058 iop_order_list = g_list_prepend(iop_order_list, entry);
1059
1060 k++;
1061 }
1062
1063 return g_list_reverse(iop_order_list); // list was built in reverse order, so un-reverse it
1064}
1065
1067{
1068 GList *iop_order_list = NULL;
1069
1070 if(version == DT_IOP_ORDER_LEGACY)
1071 {
1072 iop_order_list = _table_to_list(legacy_order);
1073 }
1074 else if(version == DT_IOP_ORDER_V30)
1075 {
1076 iop_order_list = _table_to_list(v30_order);
1077 }
1078 else if(version == DT_IOP_ORDER_V30_JPG)
1079 {
1080 iop_order_list = _table_to_list(v30_jpg_order);
1081 }
1082 else if(version == DT_IOP_ORDER_ANSEL_RAW)
1083 {
1084 iop_order_list = _table_to_list(ansel_raw_order);
1085 }
1086 else if(version == DT_IOP_ORDER_ANSEL_JPG)
1087 {
1088 iop_order_list = _table_to_list(ansel_jpg_order);
1089 }
1090
1091
1092 return iop_order_list;
1093}
1094
1095gboolean dt_ioppr_has_iop_order_list(int32_t imgid)
1096{
1098}
1099
1100GList *dt_ioppr_get_iop_order_list(int32_t imgid, gboolean sorted)
1101{
1102 GList *iop_order_list = NULL;
1103
1104 if(imgid > 0)
1105 {
1106 // we read the iop-order-list in the preset table, the actual version is
1107 // the first int32_t serialized into the io_params. This is then a sequential
1108 // search, but there will not be many such presets and we do call this routine
1109 // only when loading an image and when changing the iop-order.
1110
1111 dt_module_order_row_t row = { 0 };
1113 {
1114 const dt_iop_order_t version = row.version;
1115 const gboolean has_iop_list = !IS_NULL_PTR(row.iop_list);
1116
1117 if(version == DT_IOP_ORDER_CUSTOM || has_iop_list)
1118 {
1119 const char *buf = row.iop_list;
1120 if(buf) iop_order_list = dt_ioppr_deserialize_text_iop_order_list(buf);
1121
1122 if(!iop_order_list)
1123 {
1124 // preset not found, fall back to last built-in version, will be loaded below
1125 fprintf(stderr, "[dt_ioppr_get_iop_order_list] error building iop_order_list imgid %d\n", imgid);
1126 }
1127 else
1128 {
1129 iop_order_list = dt_ioppr_insert_missing_modules(iop_order_list);
1130 }
1131 }
1132 else if(version == DT_IOP_ORDER_LEGACY)
1133 {
1134 iop_order_list = _table_to_list(legacy_order);
1135 }
1136 else if(version == DT_IOP_ORDER_V30)
1137 {
1138 iop_order_list = _table_to_list(v30_order);
1139 }
1140 else if(version == DT_IOP_ORDER_V30_JPG)
1141 {
1142 iop_order_list = _table_to_list(v30_jpg_order);
1143 }
1144 else if(version == DT_IOP_ORDER_ANSEL_RAW)
1145 {
1146 iop_order_list = _table_to_list(ansel_raw_order);
1147 }
1148 else if(version == DT_IOP_ORDER_ANSEL_JPG)
1149 {
1150 iop_order_list = _table_to_list(ansel_jpg_order);
1151 }
1152 else
1153 fprintf(stderr, "[dt_ioppr_get_iop_order_list] invalid iop order version %d for imgid %d\n", version, imgid);
1154
1155 if(iop_order_list)
1156 {
1157 _ioppr_reset_iop_order(iop_order_list);
1158 // Perform sanity check after migration to ensure the list is valid
1159 if(!_ioppr_sanity_check_iop_order(iop_order_list))
1160 {
1161 g_list_free_full(iop_order_list, dt_free_gpointer);
1162 iop_order_list = NULL;
1163 fprintf(stderr, "[dt_ioppr_get_iop_order_list] sanity check failed for imgid %d, falling back to default\n", imgid);
1164 }
1165 }
1166 }
1167
1169 }
1170
1171 // Fall back to the image-format default order when no module_order row exists
1172 // yet, for example after deleting history. UNKNOWN_IMAGE keeps the RAW order
1173 // because there is no concrete file format to inspect.
1174 if(!iop_order_list)
1175 {
1176 dt_iop_order_t default_order = DT_IOP_ORDER_ANSEL_RAW;
1177 if(imgid > 0 && !!dt_image_cache_is_ready())
1178 {
1179 const dt_image_t *image = dt_image_cache_testget(imgid, 'r');
1180 if(!IS_NULL_PTR(image))
1181 {
1184 }
1185 }
1186
1187 iop_order_list = dt_ioppr_get_iop_order_list_version(default_order);
1188 }
1189
1190 if(sorted) iop_order_list = g_list_sort(iop_order_list, dt_sort_iop_list_by_order);
1191
1192 return iop_order_list;
1193}
1194
1202static void _ioppr_reset_iop_order(GList *iop_order_list)
1203{
1204 // iop-order must start with a number > 0 and be incremented. There is no
1205 // other constraints.
1206 int iop_order = 1;
1207 for(const GList *l = iop_order_list; l; l = g_list_next(l))
1208 {
1210 e->o.iop_order = iop_order++;
1211 }
1212}
1213
1222{
1223 // make sure that the iop_order_list does not contains possibly removed modules
1224
1225 GList *l = dev->iop_order_list;
1226 while(l)
1227 {
1228 GList *next = g_list_next(l); // need to get next pointer now, as we may be deleting this node
1229 const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)l->data;
1230 const dt_iop_module_t *const restrict mod = dt_iop_get_module_by_op_priority(dev->iop, e->operation, e->instance);
1231 if(IS_NULL_PTR(mod))
1232 {
1233 dt_free(l->data);
1234 dev->iop_order_list = g_list_delete_link(dev->iop_order_list, l);
1235 }
1236
1237 l = next;
1238 }
1239}
1240
1241void dt_ioppr_resync_pipeline(dt_develop_t *dev, const int32_t imgid, const char *msg, gboolean check_duplicates)
1242{
1245 if(check_duplicates) dt_ioppr_check_duplicate_iop_order(&dev->iop, dev->history);
1246 if(msg) dt_ioppr_check_iop_order(dev, imgid, msg);
1247}
1248
1250{
1252
1253 // and reset all module iop_order
1254
1255 GList *modules = dev->iop;
1256 while(modules)
1257 {
1258 dt_iop_module_t *mod = (dt_iop_module_t *)(modules->data);
1259 GList *next = g_list_next(modules);
1260
1261 // Modules parked at INT_MAX are "not in pipe" (non-visible) and stay parked so
1262 // _update_iop_visibility can remove them. But an ENABLED module must always occupy a
1263 // real pipe position: leaving it at INT_MAX drops it after gamma and ties it with the
1264 // other parked modules, so an unstable sort can wedge it into a wrong slot where the
1265 // pixelpipe format pass disables it for "unexpected input buffer format" (issue #961).
1266 // This happens to enabled extra instances (multi_priority > 0) whose history entry
1267 // carried a stale INT_MAX order. Re-derive their order from the authoritative order
1268 // list; a genuinely-orphan instance still falls back to INT_MAX from the lookup.
1269 if(mod->iop_order != INT_MAX || mod->enabled)
1271
1272 modules = next;
1273 }
1274
1275 dev->iop = g_list_sort(dev->iop, dt_sort_iop_by_order);
1276}
1277
1278void dt_ioppr_rebuild_iop_order_from_modules(struct dt_develop_t *dev, GList *ordered_modules)
1279{
1280 if(IS_NULL_PTR(dev) || !ordered_modules) return;
1281
1282 GList *new_iop_order_list = NULL;
1283 int order = 1;
1284
1285 for(const GList *l = ordered_modules; l; l = g_list_next(l))
1286 {
1287 const dt_iop_module_t *mod = (const dt_iop_module_t *)l->data;
1288 if(IS_NULL_PTR(mod)) continue;
1289
1290 dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)calloc(1, sizeof(dt_iop_order_entry_t));
1291 g_strlcpy(entry->operation, mod->op, sizeof(entry->operation));
1292 entry->instance = mod->multi_priority;
1293 g_strlcpy(entry->name, mod->multi_name, sizeof(entry->name));
1294 entry->o.iop_order = order++;
1295 new_iop_order_list = g_list_append(new_iop_order_list, entry);
1296 }
1297
1298 if(new_iop_order_list)
1299 {
1300 g_list_free_full(dev->iop_order_list, dt_free_gpointer);
1301 dev->iop_order_list = new_iop_order_list;
1303 }
1304}
1305
1306// sets the iop_order on each module of *_iop_list
1307// iop_order is set only for base modules, multi-instances will be flagged as unused with INT_MAX
1308// if a module do not exists on iop_order_list it is flagged as unused with INT_MAX
1309void dt_ioppr_set_default_iop_order(dt_develop_t *dev, const int32_t imgid)
1310{
1311 // First check whether the image already owns an order in DB. If it does not,
1312 // choose the built-in order from dev->image_storage, which was just refreshed
1313 // by the darkroom/history caller and is the image state used by module reloads.
1314 const gboolean has_stored_order = dt_history_repository_has_module_order(imgid);
1315
1316 GList *iop_order_list = NULL;
1317 if(!has_stored_order && dev->image_storage.id == imgid)
1318 {
1319 const dt_iop_order_t default_order = dt_image_needs_rawprepare(&dev->image_storage)
1322 iop_order_list = dt_ioppr_get_iop_order_list_version(default_order);
1323 }
1324 else
1325 {
1326 iop_order_list = dt_ioppr_get_iop_order_list(imgid, FALSE);
1327 }
1328
1329 // we assign a single iop-order to each module
1330
1331 _ioppr_reset_iop_order(iop_order_list);
1332
1333 if(dev->iop_order_list)
1334 {
1335 g_list_free_full(dev->iop_order_list, dt_free_gpointer);
1336 dev->iop_order_list = NULL;
1337 }
1338 dev->iop_order_list = iop_order_list;
1339
1340 // we now set the module list given to this iop-order
1341
1343}
1344
1351static void dt_ioppr_migrate_iop_order(struct dt_develop_t *dev, const int32_t imgid)
1352{
1357 dt_dev_history_notify_change(dev, imgid);
1358}
1359
1360void dt_ioppr_change_iop_order(struct dt_develop_t *dev, const int32_t imgid, GList *new_iop_list)
1361{
1362 GList *iop_list = dt_ioppr_iop_order_copy_deep(new_iop_list);
1363 GList *mi = dt_ioppr_extract_multi_instances_list(dt_dev_get_global()->iop_order_list);
1364
1365 if(mi) iop_list = dt_ioppr_merge_multi_instance_iop_order_list(iop_list, mi);
1366
1369 g_list_free_full(iop_list, dt_free_gpointer);
1370 iop_list = NULL;
1371
1373}
1374
1381static GList *dt_ioppr_extract_multi_instances_list(GList *iop_order_list)
1382{
1383 GList *mi = NULL;
1384
1385 for(const GList *l = iop_order_list; l; l = g_list_next(l))
1386 {
1387 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data;
1388
1389 if(_count_entries_operation(iop_order_list, entry->operation) > 1)
1390 {
1392 mi = g_list_prepend(mi, copy);
1393 }
1394 }
1395
1396 return g_list_reverse(mi); // list was built in reverse order, so un-reverse it
1397}
1398
1410 const char *operation, GList *multi_instance_list)
1411{
1412 const int count_to = _count_entries_operation(iop_order_list, operation);
1413
1414 int item_nb = 0;
1415
1416 GList *link = iop_order_list;
1417
1418 for(const GList *l = multi_instance_list; l; l = g_list_next(l))
1419 {
1420 dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)l->data;
1421
1422 item_nb++;
1423
1424 if(item_nb <= count_to)
1425 {
1426 link = dt_ioppr_get_iop_order_link(link, operation, -1);
1427 dt_iop_order_entry_t *e = (dt_iop_order_entry_t *)link->data;
1428 e->instance = entry->instance;
1429
1430 // free this entry as not merged into the list
1431 dt_free(entry);
1432
1433 // next replace should happen to any module after this one
1434 link = g_list_next(link);
1435 }
1436 else
1437 {
1438 iop_order_list = g_list_insert_before(iop_order_list, link, entry);
1439 }
1440 }
1441
1442 // if needed removes all other instance of this operation which are superfluous
1443 if(g_list_shorter_than(multi_instance_list, count_to))
1444 {
1445 while(link)
1446 {
1447 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)link->data;
1448 GList *next = g_list_next(link);
1449 if(strcmp(operation, entry->operation) == 0)
1450 {
1451 dt_free(link->data);
1452 iop_order_list = g_list_delete_link(iop_order_list, link);
1453 }
1454
1455 link = next;
1456 }
1457 }
1458
1459 return iop_order_list;
1460}
1461
1472static GList *dt_ioppr_merge_multi_instance_iop_order_list(GList *iop_order_list, GList *multi_instance_list)
1473{
1474 GList *op = NULL;
1475
1476 GList *copy = dt_ioppr_iop_order_copy_deep(multi_instance_list);
1477 GList *l = copy;
1478
1479 while(l)
1480 {
1481 dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)l->data;
1482 GList *l_next = g_list_next(l);
1483
1484 op = g_list_append(op, entry);
1485
1486 copy = g_list_delete_link(copy, l);
1487
1488 GList *mi = l_next;
1489 while(mi)
1490 {
1491 GList *next = g_list_next(mi);
1492 dt_iop_order_entry_t *mi_entry = (dt_iop_order_entry_t *)mi->data;
1493 if(strcmp(entry->operation, mi_entry->operation) == 0)
1494 {
1495 op = g_list_append(op, mi_entry);
1496 copy = g_list_delete_link(copy, mi);
1497 }
1498
1499 mi = next;
1500 }
1501
1502 // copy operation as entry may be freed
1503 char operation[20];
1504 memcpy(operation, entry->operation, sizeof(entry->operation));
1505
1506 iop_order_list = dt_ioppr_merge_module_multi_instance_iop_order_list(iop_order_list, operation, op);
1507
1508 g_list_free(op);
1509 op = NULL;
1510
1511 l = copy;
1512 }
1513
1514 return iop_order_list;
1515}
1516
1527static void _count_iop_module(GList *iop, const char *operation, int *max_multi_priority, int *count,
1528 int *max_multi_priority_enabled, int *count_enabled)
1529{
1530 *max_multi_priority = 0;
1531 *count = 0;
1532 *max_multi_priority_enabled = 0;
1533 *count_enabled = 0;
1534
1535 for(const GList *modules = iop; modules; modules = g_list_next(modules))
1536 {
1537 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data;
1538 if(!strcmp(mod->op, operation))
1539 {
1540 (*count)++;
1541 if(*max_multi_priority < mod->multi_priority) *max_multi_priority = mod->multi_priority;
1542
1543 if(mod->enabled)
1544 {
1545 (*count_enabled)++;
1546 if(*max_multi_priority_enabled < mod->multi_priority) *max_multi_priority_enabled = mod->multi_priority;
1547 }
1548 }
1549 }
1550
1551 assert(*count >= *count_enabled);
1552}
1553
1561static int _count_entries_operation(GList *e_list, const char *operation)
1562{
1563 int count = 0;
1564
1565 for(const GList *l = e_list; l; l = g_list_next(l))
1566 {
1568 if(!strcmp(ep->operation, operation)) count++;
1569 }
1570
1571 return count;
1572}
1573
1581static gboolean _operation_already_handled(GList *e_list, const char *operation)
1582{
1583 for(const GList *l = g_list_previous(e_list); l; l = g_list_previous(l))
1584 {
1585 const dt_iop_order_entry_t *const restrict ep = (dt_iop_order_entry_t *)l->data;
1586 if(!strcmp(ep->operation, operation)) return TRUE;
1587 }
1588 return FALSE;
1589}
1590
1600int _get_multi_priority(dt_develop_t *dev, const char *operation, const int n, const gboolean only_disabled)
1601{
1602 int count = 0;
1603 for(const GList *l = dev->iop; l; l = g_list_next(l))
1604 {
1605 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)l->data;
1606 if((!only_disabled || mod->enabled == FALSE) && !strcmp(mod->op, operation))
1607 {
1608 count++;
1609 if(count == n) return mod->multi_priority;
1610 }
1611 }
1612
1613 return INT_MAX;
1614}
1615
1625static void dt_ioppr_update_for_entries(dt_develop_t *dev, GList *entry_list, gboolean append)
1626{
1627
1628 // for each priority list to be checked
1629 for(GList *e_list = entry_list; e_list; e_list = g_list_next(e_list))
1630 {
1631 const dt_iop_order_entry_t *const restrict ep = (dt_iop_order_entry_t *)e_list->data;
1632
1633 gboolean force_append = FALSE;
1634
1635 // we also need to force append (even if overwrite mode is
1636 // selected - append = FALSE) when a module has a specific name
1637 // and this name is not present into the current iop list.
1638
1639 if(*ep->name && !dt_iop_get_module_by_instance_name(dev->iop, ep->operation, ep->name))
1640 force_append = TRUE;
1641
1642 int max_multi_priority = 0, count = 0;
1643 int max_multi_priority_enabled = 0, count_enabled = 0;
1644
1645 // is it a currently active module and if so how many active instances we have
1646 _count_iop_module(dev->iop, ep->operation,
1647 &max_multi_priority, &count, &max_multi_priority_enabled, &count_enabled);
1648
1649 // look for this operation into the target iop-order list and add there as much operation as needed
1650
1651 for(GList *l = g_list_last(dev->iop_order_list); l; l = g_list_previous(l))
1652 {
1653 const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)l->data;
1654 if(!strcmp(e->operation, ep->operation) && !_operation_already_handled(e_list, ep->operation))
1655 {
1656 // how many instances of this module in the entry list, and re-number multi-priority accordingly
1657 const int new_active_instances = _count_entries_operation(entry_list, ep->operation);
1658
1659 int add_count = 0;
1660 int start_multi_priority = 0;
1661 int nb_replace = 0;
1662
1663 if(append || force_append)
1664 {
1665 nb_replace = count - count_enabled;
1666 add_count = MAX(0, new_active_instances - nb_replace);
1667 start_multi_priority = max_multi_priority + 1;
1668 }
1669 else
1670 {
1671 nb_replace = count;
1672 add_count = MAX(0, new_active_instances - count);
1673 start_multi_priority = max_multi_priority + 1;
1674 }
1675
1676 // update multi_priority to be unique in iop list
1677 int multi_priority = start_multi_priority;
1678 int nb = 0;
1679
1680 for(const GList *s = entry_list; s; s = g_list_next(s))
1681 {
1682 dt_iop_order_entry_t *item = (dt_iop_order_entry_t *)s->data;
1683 if(!strcmp(item->operation, e->operation))
1684 {
1685 nb++;
1686 if(nb <= nb_replace)
1687 {
1688 // this one replaces current module, get it's multi-priority
1689 item->instance = _get_multi_priority(dev, item->operation, nb, append);
1690 }
1691 else
1692 {
1693 // otherwise create a new multi-priority
1694 item->instance = multi_priority++;
1695 }
1696 }
1697 }
1698
1699 multi_priority = start_multi_priority;
1700
1701 l = g_list_next(l);
1702
1703 for(int k = 0; k<add_count; k++)
1704 {
1706 g_strlcpy(n->operation, ep->operation, sizeof(n->operation));
1707 n->instance = multi_priority++;
1708 n->o.iop_order = 0;
1709 dev->iop_order_list = g_list_insert_before(dev->iop_order_list, l, n);
1710 }
1711 break;
1712 }
1713 }
1714 }
1715
1717
1718// dt_ioppr_print_iop_order(dev->iop_order_list, "upd sitem");
1719}
1720
1721void dt_ioppr_update_for_style_items(dt_develop_t *dev, GList *st_items, gboolean append)
1722{
1723 GList *e_list = NULL;
1724
1725 // for each priority list to be checked
1726 for(const GList *si_list = st_items; si_list; si_list = g_list_next(si_list))
1727 {
1728 const dt_style_item_t *const restrict si = (dt_style_item_t *)si_list->data;
1729
1731 memcpy(n->operation, si->operation, sizeof(n->operation));
1732 n->instance = si->multi_priority;
1733 g_strlcpy(n->name, si->multi_name, sizeof(n->name));
1734 n->o.iop_order = 0;
1735 e_list = g_list_prepend(e_list, n);
1736 }
1737 e_list = g_list_reverse(e_list); // list was built in reverse order, so un-reverse it
1738
1739 dt_ioppr_update_for_entries(dev, e_list, append);
1740
1741 // write back the multi-priority
1742
1743 GList *el = e_list;
1744 for(const GList *si_list = st_items; si_list; si_list = g_list_next(si_list))
1745 {
1746 dt_style_item_t *si = (dt_style_item_t *)si_list->data;
1747 const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)el->data;
1748
1749 si->multi_priority = e->instance;
1751 el = g_list_next(el);
1752 }
1753
1754 g_list_free(e_list);
1755 e_list = NULL;
1756}
1757
1758void dt_ioppr_update_for_modules(dt_develop_t *dev, GList *modules, gboolean append)
1759{
1760 GList *e_list = NULL;
1761
1762 // for each priority list to be checked
1763 for(const GList *m_list = modules; m_list; m_list = g_list_next(m_list))
1764 {
1765 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)m_list->data;
1766
1768 g_strlcpy(n->operation, mod->op, sizeof(n->operation));
1769 n->instance = mod->multi_priority;
1770 g_strlcpy(n->name, mod->multi_name, sizeof(n->name));
1771 n->o.iop_order = 0;
1772 e_list = g_list_prepend(e_list, n);
1773 }
1774 e_list = g_list_reverse(e_list); // list was built in reverse order, so un-reverse it
1775
1776 dt_ioppr_update_for_entries(dev, e_list, append);
1777
1778 // write back the multi-priority
1779
1780 GList *el = e_list;
1781 for(const GList *m_list = modules; m_list; m_list = g_list_next(m_list))
1782 {
1783 dt_iop_module_t *mod = (dt_iop_module_t *)m_list->data;
1785
1786 mod->multi_priority = e->instance;
1788
1789 el = g_list_next(el);
1790 }
1791
1792 g_list_free_full(e_list, dt_free_gpointer);
1793 e_list = NULL;
1794}
1795
1796// returns the first dt_dev_history_item_t on history_list where hist->module == mod
1805{
1806 dt_dev_history_item_t *hist_entry = NULL;
1807
1808 for(const GList *history = history_list; history; history = g_list_next(history))
1809 {
1810 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
1811
1812 if(hist->module == mod)
1813 {
1814 hist_entry = hist;
1815 break;
1816 }
1817 }
1818
1819 return hist_entry;
1820}
1821
1822// check if there's duplicate iop_order entries in iop_list
1823// if so, updates the iop_order to be unique, but only if the module is disabled and not in history
1833static void dt_ioppr_check_duplicate_iop_order(GList **_iop_list, GList *history_list)
1834{
1835 GList *iop_list = *_iop_list;
1836 dt_iop_module_t *mod_prev = NULL;
1837
1838 // get the first module
1839 GList *modules = iop_list;
1840 if(modules)
1841 {
1842 mod_prev = (dt_iop_module_t *)(modules->data);
1843 modules = g_list_next(modules);
1844 }
1845 // check for each module if iop_order is the same as the previous one
1846 // if so, change it, but only if disabled and not in history
1847 while(modules)
1848 {
1849 int reset_list = 0;
1850 dt_iop_module_t *mod = (dt_iop_module_t *)(modules->data);
1851
1852 if(mod->iop_order == mod_prev->iop_order && mod->iop_order != INT_MAX)
1853 {
1854 int can_move = 0;
1855
1856 if(!mod->enabled && _ioppr_search_history_by_module(history_list, mod) == NULL)
1857 {
1858 can_move = 1;
1859
1860 GList *modules1 = g_list_next(modules);
1861 if(modules1)
1862 {
1863 dt_iop_module_t *mod_next = (dt_iop_module_t *)(modules1->data);
1864 if(mod->iop_order != mod_next->iop_order)
1865 {
1866 mod->iop_order += (mod_next->iop_order - mod->iop_order) / 2.0;
1867 }
1868 else
1869 {
1870 dt_ioppr_check_duplicate_iop_order(&modules, history_list);
1871 reset_list = 1;
1872 }
1873 }
1874 else
1875 {
1876 mod->iop_order += 1.0;
1877 }
1878 }
1879 else if(!mod_prev->enabled && _ioppr_search_history_by_module(history_list, mod_prev) == NULL)
1880 {
1881 can_move = 1;
1882
1883 GList *modules1 = g_list_previous(modules);
1884 if(modules1) modules1 = g_list_previous(modules1);
1885 if(modules1)
1886 {
1887 dt_iop_module_t *mod_next = (dt_iop_module_t *)(modules1->data);
1888 if(mod_prev->iop_order != mod_next->iop_order)
1889 {
1890 mod_prev->iop_order -= (mod_prev->iop_order - mod_next->iop_order) / 2.0;
1891 }
1892 else
1893 {
1894 can_move = 0;
1895 fprintf(stderr,
1896 "[dt_ioppr_check_duplicate_iop_order 1] modules %s %s(%d) and %s %s(%d) have the same iop_order\n",
1897 mod_prev->op, mod_prev->multi_name, mod_prev->iop_order, mod->op, mod->multi_name, mod->iop_order);
1898 }
1899 }
1900 else
1901 {
1902 mod_prev->iop_order -= 0.5;
1903 }
1904 }
1905
1906 if(!can_move)
1907 {
1908 fprintf(stderr,
1909 "[dt_ioppr_check_duplicate_iop_order] modules %s %s(%d) and %s %s(%d) have the same iop_order\n",
1910 mod_prev->op, mod_prev->multi_name, mod_prev->iop_order, mod->op, mod->multi_name, mod->iop_order);
1911 }
1912 }
1913
1914 if(reset_list)
1915 {
1916 modules = iop_list;
1917 if(modules)
1918 {
1919 mod_prev = (dt_iop_module_t *)(modules->data);
1920 modules = g_list_next(modules);
1921 }
1922 }
1923 else
1924 {
1925 mod_prev = mod;
1926 modules = g_list_next(modules);
1927 }
1928 }
1929
1930 *_iop_list = iop_list;
1931}
1932
1933// check if all so modules on iop_list have a iop_order defined in iop_order_list
1934int dt_ioppr_check_so_iop_order(GList *iop_list, GList *iop_order_list)
1935{
1936 int iop_order_missing = 0;
1937
1938 // check if all the modules have their iop_order assigned
1939 for(const GList *modules = iop_list; modules; modules = g_list_next(modules))
1940 {
1941 const dt_iop_module_so_t *const restrict mod = (dt_iop_module_so_t *)(modules->data);
1942 const dt_iop_order_entry_t *const restrict entry =
1943 dt_ioppr_get_iop_order_entry(iop_order_list, mod->op, 0); // mod->multi_priority);
1944 if(IS_NULL_PTR(entry) && !dt_deprecated(mod->op))
1945 {
1946 iop_order_missing = 1;
1947 fprintf(stderr, "[dt_ioppr_check_so_iop_order] missing iop_order for module %s\n", mod->op);
1948 }
1949 }
1950
1951 return iop_order_missing;
1952}
1953
1961static void *_dup_iop_order_entry(const void *src, gpointer data)
1962{
1963 const dt_iop_order_entry_t *const restrict scr_entry = (dt_iop_order_entry_t *)src;
1964 dt_iop_order_entry_t *new_entry = malloc(sizeof(dt_iop_order_entry_t));
1965 memcpy(new_entry, scr_entry, sizeof(dt_iop_order_entry_t));
1966 return (void *)new_entry;
1967}
1968
1969// returns a duplicate of iop_order_list
1970GList *dt_ioppr_iop_order_copy_deep(GList *iop_order_list)
1971{
1972 return (GList *)g_list_copy_deep(iop_order_list, _dup_iop_order_entry, NULL);
1973}
1974
1975// helper to sort a GList of dt_iop_module_t by iop_order
1976gint dt_sort_iop_by_order(gconstpointer a, gconstpointer b)
1977{
1978 const dt_iop_module_t *const restrict am = (const dt_iop_module_t *)a;
1979 const dt_iop_module_t *const restrict bm = (const dt_iop_module_t *)b;
1980 if(am->iop_order > bm->iop_order) return 1;
1981 if(am->iop_order < bm->iop_order) return -1;
1982 return 0;
1983}
1984
1985// if module can be placed before than module_next on the pipe
1986// it returns the new iop_order
1987// if it cannot be placed it returns -1.0
1988// this assumes that the order is always positive
1989gboolean dt_ioppr_check_can_move_before_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_next)
1990{
1991 // we should't be here if the next module is using a raster mask and and our module is that raster mask source
1992 if(module_next->raster_mask.sink.source == module)
1993 return FALSE;
1994
1995 gboolean can_move = FALSE;
1996
1997 // module is before on the pipe
1998 // move it up
1999 if(module->iop_order < module_next->iop_order)
2000 {
2001 // let's first search for module
2002 GList *modules = iop_list;
2003 for(; modules; modules = g_list_next(modules))
2004 {
2005 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data;
2006 if(mod == module) break;
2007 }
2008
2009 // we found the module
2010 if(modules)
2011 {
2012 dt_iop_module_t *mod1 = NULL;
2013 dt_iop_module_t *mod2 = NULL;
2014
2015 // now search for module_next and the one previous to that, so iop_order can be calculated
2016 // also check the rules
2017 for(modules = g_list_next(modules); modules; modules = g_list_next(modules))
2018 {
2019 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
2020
2021 // if we reach module_next everything is OK
2022 if(mod == module_next)
2023 {
2024 mod2 = mod;
2025 break;
2026 }
2027
2028 // moving a module that is the source for a raster mask ABOVE the module using it is forbidden
2029 if(mod->raster_mask.sink.source == module)
2030 break;
2031
2032 // is there a rule about swapping this two?
2033 int rule_found = 0;
2034 for(const GList *rules = dt_ioppr_get_iop_order_rules_global(); rules; rules = g_list_next(rules))
2035 {
2036 const dt_iop_order_rule_t *const restrict rule = (dt_iop_order_rule_t *)rules->data;
2037
2038 if(strcmp(module->op, rule->op_prev) == 0 && strcmp(mod->op, rule->op_next) == 0)
2039 {
2040 rule_found = 1;
2041 break;
2042 }
2043 }
2044 if(rule_found) break;
2045
2046 mod1 = mod;
2047 }
2048
2049 // we reach the module_next module
2050 if(mod2)
2051 {
2052 // this is already the previous module!
2053 if(module == mod1)
2054 {
2055 ;
2056 }
2057 else if(mod1->iop_order == mod2->iop_order)
2058 {
2059 fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] %s %s(%d) and %s %s(%d) have the same iop_order\n",
2060 mod1->op, mod1->multi_name, mod1->iop_order, mod2->op, mod2->multi_name, mod2->iop_order);
2061 }
2062 else
2063 {
2064 can_move = TRUE;
2065 }
2066 }
2067 }
2068 else
2069 fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] can't find module %s %s\n", module->op, module->multi_name);
2070 }
2071 // module is next on the pipe
2072 // move it down
2073 else if(module->iop_order > module_next->iop_order)
2074 {
2075 // let's first search for module
2076 GList *modules = g_list_last(iop_list);
2077 for(; modules; modules = g_list_previous(modules))
2078 {
2079 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data;
2080 if(mod == module) break;
2081 }
2082
2083 // we found the module
2084 if(modules)
2085 {
2086 dt_iop_module_t *mod1 = NULL;
2087 dt_iop_module_t *mod2 = NULL;
2088
2089 // now search for module_next and the one next to that, so iop_order can be calculated
2090 // also check the rules
2091 for(modules = g_list_previous(modules); modules; modules = g_list_previous(modules))
2092 {
2093 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
2094
2095 // we reach the module next to module_next, everything is OK
2096 if(!IS_NULL_PTR(mod2))
2097 {
2098 mod1 = mod;
2099 break;
2100 }
2101
2102 // moving a module using a raster mask BELOW its raster source module is forbidden
2103 if(module->raster_mask.sink.source == mod)
2104 break;
2105
2106 // is there a rule about swapping this two?
2107 int rule_found = 0;
2108 for(const GList *rules = dt_ioppr_get_iop_order_rules_global(); rules; rules = g_list_next(rules))
2109 {
2110 const dt_iop_order_rule_t *const restrict rule = (dt_iop_order_rule_t *)rules->data;
2111
2112 if(strcmp(mod->op, rule->op_prev) == 0 && strcmp(module->op, rule->op_next) == 0)
2113 {
2114 rule_found = 1;
2115 break;
2116 }
2117 }
2118 if(rule_found) break;
2119
2120 if(mod == module_next) mod2 = mod;
2121 }
2122
2123 // we reach the module_next module
2124 if(mod1)
2125 {
2126 // this is already the previous module!
2127 if(module == mod2)
2128 {
2129 ;
2130 }
2131 else if(mod1->iop_order == mod2->iop_order)
2132 {
2133 fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] %s %s(%d) and %s %s(%d) have the same iop_order\n",
2134 mod1->op, mod1->multi_name, mod1->iop_order, mod2->op, mod2->multi_name, mod2->iop_order);
2135 }
2136 else
2137 {
2138 can_move = TRUE;
2139 }
2140 }
2141 }
2142 else
2143 fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] can't find module %s %s\n", module->op, module->multi_name);
2144 }
2145 else
2146 {
2147 fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] modules %s %s(%d) and %s %s(%d) have the same iop_order\n",
2148 module->op, module->multi_name, module->iop_order, module_next->op, module_next->multi_name, module_next->iop_order);
2149 }
2150
2151 return can_move;
2152}
2153
2154// if module can be placed after than module_prev on the pipe
2155// it returns the new iop_order
2156// if it cannot be placed it returns -1.0
2157// this assumes that the order is always positive
2158gboolean dt_ioppr_check_can_move_after_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_prev)
2159{
2160 // we shouldn't be here if the previous module is the a raster mask's source and our module is using it
2161 if(module->raster_mask.sink.source == module_prev)
2162 return FALSE;
2163
2164 gboolean can_move = FALSE;
2165
2166 // moving after module_prev is the same as moving before the very next one after module_prev
2167 dt_iop_module_t *module_next = NULL;
2168
2169 for(const GList *modules = g_list_last(iop_list); modules; modules = g_list_previous(modules))
2170 {
2171 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
2172 if(mod == module_prev) break;
2173
2174 module_next = mod;
2175 }
2176 if(IS_NULL_PTR(module_next))
2177 {
2178 fprintf(
2179 stderr,
2180 "[dt_ioppr_get_iop_order_after_iop] can't find module previous to %s %s(%d) while moving %s %s(%d) after it\n",
2181 module_prev->op, module_prev->multi_name, module_prev->iop_order, module->op, module->multi_name,
2182 module->iop_order);
2183 }
2184 else
2185 can_move = dt_ioppr_check_can_move_before_iop(iop_list, module, module_next);
2186
2187 return can_move;
2188}
2189
2190// changes the module->iop_order so it comes before in the pipe than module_next
2191// sort dev->iop to reflect the changes
2192// return 1 if iop_order is changed, 0 otherwise
2193gboolean dt_ioppr_move_iop_before(struct dt_develop_t *dev, dt_iop_module_t *module, dt_iop_module_t *module_next)
2194{
2195 GList *next = dt_ioppr_get_iop_order_link(dev->iop_order_list, module_next->op, module_next->multi_priority);
2196 GList *current = dt_ioppr_get_iop_order_link(dev->iop_order_list, module->op, module->multi_priority);
2197
2198 if(IS_NULL_PTR(next)) return FALSE;
2199
2200 if(IS_NULL_PTR(current))
2201 {
2202 // Module not in iop_order_list, create entry
2204 g_strlcpy(entry->operation, module->op, sizeof(entry->operation));
2205 entry->instance = module->multi_priority;
2206 entry->o.iop_order = 0; // will be reset later
2207 current = g_list_alloc();
2208 current->data = entry;
2209 }
2210
2211 dev->iop_order_list = g_list_remove_link(dev->iop_order_list, current);
2212 dev->iop_order_list = g_list_insert_before(dev->iop_order_list, next, current->data);
2213
2214 g_list_free(current);
2215 current = NULL;
2216
2218
2219 return TRUE;
2220}
2221
2222// changes the module->iop_order so it comes after in the pipe than module_prev
2223// sort dev->iop to reflect the changes
2224// return 1 if iop_order is changed, 0 otherwise
2225gboolean dt_ioppr_move_iop_after(struct dt_develop_t *dev, dt_iop_module_t *module, dt_iop_module_t *module_prev)
2226{
2227 GList *prev = dt_ioppr_get_iop_order_link(dev->iop_order_list, module_prev->op, module_prev->multi_priority);
2228 GList *current = dt_ioppr_get_iop_order_link(dev->iop_order_list, module->op, module->multi_priority);
2229
2230 if(IS_NULL_PTR(prev)) return FALSE;
2231
2232 if(IS_NULL_PTR(current))
2233 {
2234 // Module not in iop_order_list, create entry
2236 g_strlcpy(entry->operation, module->op, sizeof(entry->operation));
2237 entry->instance = module->multi_priority;
2238 entry->o.iop_order = 0; // will be reset later
2239 current = g_list_alloc();
2240 current->data = entry;
2241 }
2242
2243 dev->iop_order_list = g_list_remove_link(dev->iop_order_list, current);
2244
2245 // we want insert after => so insert before the next item
2246 GList *next = g_list_next(prev);
2247 if(prev)
2248 dev->iop_order_list = g_list_insert_before(dev->iop_order_list, next, current->data);
2249 else
2250 dev->iop_order_list = g_list_append(dev->iop_order_list, current->data);
2251
2252 g_list_free(current);
2253 current = NULL;
2254
2256
2257 return TRUE;
2258}
2259
2269static void _ioppr_check_rules(GList *iop_list, const int32_t imgid, const char *msg)
2270{
2271 // for each module check if it doesn't break a rule
2272 for(const GList *modules = iop_list; modules; modules = g_list_next(modules))
2273 {
2274 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data;
2275 if(mod->iop_order == INT_MAX)
2276 {
2277 continue;
2278 }
2279
2280 // we have a module, now check each rule
2281 for(const GList *rules = dt_ioppr_get_iop_order_rules_global(); rules; rules = g_list_next(rules))
2282 {
2283 const dt_iop_order_rule_t *const restrict rule = (dt_iop_order_rule_t *)rules->data;
2284
2285 // mod must be before rule->op_next
2286 if(strcmp(mod->op, rule->op_prev) == 0)
2287 {
2288 // check if there's a rule->op_next module before mod
2289 for(const GList *modules_prev = g_list_previous(modules);
2290 modules_prev;
2291 modules_prev = g_list_previous(modules_prev))
2292 {
2293 const dt_iop_module_t *const restrict mod_prev = (dt_iop_module_t *)modules_prev->data;
2294
2295 if(strcmp(mod_prev->op, rule->op_next) == 0)
2296 {
2297 fprintf(stderr, "[_ioppr_check_rules] found rule %s %s module %s %s(%d) is after %s %s(%d) image %i (%s)\n",
2298 rule->op_prev, rule->op_next, mod->op, mod->multi_name, mod->iop_order, mod_prev->op,
2299 mod_prev->multi_name, mod_prev->iop_order, imgid, msg);
2300 }
2301 }
2302 }
2303 // mod must be after rule->op_prev
2304 else if(strcmp(mod->op, rule->op_next) == 0)
2305 {
2306 // check if there's a rule->op_prev module after mod
2307 for(const GList *modules_next = g_list_next(modules); modules_next; modules_next = g_list_next(modules_next))
2308 {
2309 const dt_iop_module_t *const restrict mod_next = (dt_iop_module_t *)modules_next->data;
2310
2311 if(strcmp(mod_next->op, rule->op_prev) == 0)
2312 {
2313 fprintf(stderr, "[_ioppr_check_rules] found rule %s %s module %s %s(%d) is before %s %s(%d) image %i (%s)\n",
2314 rule->op_prev, rule->op_next, mod->op, mod->multi_name, mod->iop_order, mod_next->op,
2315 mod_next->multi_name, mod_next->iop_order, imgid, msg);
2316 }
2317 }
2318 }
2319 }
2320 }
2321}
2322
2324{
2325 const char *operation = module->op;
2326 const int32_t instance = module->multi_priority;
2327
2329
2330 g_strlcpy(entry->operation, operation, sizeof(entry->operation));
2331 entry->instance = instance;
2332 entry->o.iop_order = 0;
2333
2334 GList *place = NULL;
2335
2336 int max_instance = -1;
2337
2338 for(GList *l = dev->iop_order_list; l; l = g_list_next(l))
2339 {
2340 const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)l->data;
2341 if(!strcmp(e->operation, operation) && e->instance > max_instance)
2342 {
2343 place = l;
2344 max_instance = e->instance;
2345 }
2346 }
2347
2348 dev->iop_order_list = g_list_insert_before(dev->iop_order_list, place, entry);
2349}
2350
2351int dt_ioppr_check_iop_order(dt_develop_t *dev, const int32_t imgid, const char *msg)
2352{
2353 int iop_order_ok = 1;
2354
2355 // check if gamma is the last iop
2356 {
2357 GList *modules;
2358 for(modules = g_list_last(dev->iop); modules; modules = g_list_previous(dev->iop))
2359 {
2360 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data;
2361 if(mod->iop_order != INT_MAX)
2362 break;
2363 }
2364 if(modules)
2365 {
2366 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data;
2367
2368 if(strcmp(mod->op, "gamma") != 0)
2369 {
2370 iop_order_ok = 0;
2371 fprintf(stderr, "[dt_ioppr_check_iop_order] gamma is not the last iop, last is %s %s(%d) image %i (%s)\n",
2372 mod->op, mod->multi_name, mod->iop_order,imgid, msg);
2373 }
2374 }
2375 else
2376 {
2377 // fprintf(stderr, "[dt_ioppr_check_iop_order] dev->iop is empty image %i (%s)\n",imgid, msg);
2378 }
2379 }
2380
2381 // some other checks
2382 {
2383 for(const GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(dev->iop))
2384 {
2385 const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data;
2386 if(!mod->default_enabled && mod->iop_order != INT_MAX)
2387 {
2388 if(mod->enabled)
2389 {
2390 iop_order_ok = 0;
2391 fprintf(stderr, "[dt_ioppr_check_iop_order] module not used but enabled!! %s %s(%d) image %i (%s)\n",
2392 mod->op, mod->multi_name, mod->iop_order,imgid, msg);
2393 }
2394 if(mod->multi_priority == 0)
2395 {
2396 iop_order_ok = 0;
2397 fprintf(stderr, "[dt_ioppr_check_iop_order] base module set as not used %s %s(%d) image %i (%s)\n",
2398 mod->op, mod->multi_name, mod->iop_order,imgid, msg);
2399 }
2400 }
2401 }
2402 }
2403
2404 // check if there's duplicate or out-of-order iop_order
2405 {
2406 dt_iop_module_t *mod_prev = NULL;
2407 for(const GList *modules = g_list_first(dev->iop); modules; modules = g_list_next(modules))
2408 {
2409 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
2410 if(mod->iop_order != INT_MAX)
2411 {
2412 if(mod_prev)
2413 {
2414 if(mod->iop_order < mod_prev->iop_order)
2415 {
2416 iop_order_ok = 0;
2417 fprintf(stderr,
2418 "[dt_ioppr_check_iop_order] module %s %s(%d) should be after %s %s(%d) image %i (%s)\n",
2419 mod->op, mod->multi_name, mod->iop_order, mod_prev->op, mod_prev->multi_name,
2420 mod_prev->iop_order, imgid, msg);
2421 }
2422 else if(mod->iop_order == mod_prev->iop_order)
2423 {
2424 iop_order_ok = 0;
2425 fprintf(
2426 stderr,
2427 "[dt_ioppr_check_iop_order] module %s %s(%i)(%d) and %s %s(%i)(%d) have the same order image %i (%s)\n",
2428 mod->op, mod->multi_name, mod->multi_priority, mod->iop_order, mod_prev->op,
2429 mod_prev->multi_name, mod_prev->multi_priority, mod_prev->iop_order, imgid, msg);
2430 }
2431 }
2432 }
2433 mod_prev = mod;
2434 }
2435 }
2436
2437 _ioppr_check_rules(dev->iop, imgid, msg);
2438
2439 for(const GList *history = dev->history; history; history = g_list_next(history))
2440 {
2441 const dt_dev_history_item_t *const restrict hist = (dt_dev_history_item_t *)(history->data);
2442
2443 if(hist->iop_order == INT_MAX)
2444 {
2445 if(hist->enabled)
2446 {
2447 iop_order_ok = 0;
2448 fprintf(stderr, "[dt_ioppr_check_iop_order] history module not used but enabled!! %s %s(%d) image %i (%s)\n",
2449 hist->op_name, hist->multi_name, hist->iop_order, imgid, msg);
2450 }
2451 if(hist->multi_priority == 0)
2452 {
2453 iop_order_ok = 0;
2454 fprintf(stderr, "[dt_ioppr_check_iop_order] history base module set as not used %s %s(%d) image %i (%s)\n",
2455 hist->op_name, hist->multi_name, hist->iop_order, imgid, msg);
2456 }
2457 }
2458 }
2459
2460 dt_print(DT_DEBUG_PARAMS, "[dt_ioppr_check_iop_order] IOP order passed (called from %s)\n", msg);
2461
2462 return iop_order_ok;
2463}
2464
2465void *dt_ioppr_serialize_iop_order_list(GList *iop_order_list, size_t *size)
2466{
2467 g_return_val_if_fail(!IS_NULL_PTR(iop_order_list), NULL);
2468 g_return_val_if_fail(!IS_NULL_PTR(size), NULL);
2469 // compute size of all modules
2470 *size = 0;
2471
2472 for(const GList *l = iop_order_list; l; l = g_list_next(l))
2473 {
2474 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data;
2475 *size += strlen(entry->operation) + sizeof(int32_t) * 2;
2476 }
2477
2478 if(*size == 0)
2479 return NULL;
2480
2481 // allocate the parameter buffer
2482 char *params = (char *)malloc(*size);
2483
2484 // set set preset iop-order version
2485 int pos = 0;
2486
2487 for(const GList *l = iop_order_list; l; l = g_list_next(l))
2488 {
2489 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data;
2490 // write the len of the module name
2491 const int32_t len = strlen(entry->operation);
2492 memcpy(params+pos, &len, sizeof(int32_t));
2493 pos += sizeof(int32_t);
2494
2495 // write the module name
2496 memcpy(params+pos, entry->operation, len);
2497 pos += len;
2498
2499 // write the instance number
2500 memcpy(params+pos, &(entry->instance), sizeof(int32_t));
2501 pos += sizeof(int32_t);
2502 }
2503
2504 return params;
2505}
2506
2507char *dt_ioppr_serialize_text_iop_order_list(GList *iop_order_list)
2508{
2509 GString *text = g_string_new("");
2510
2511 const GList *const last = g_list_last(iop_order_list);
2512 for(const GList *l = iop_order_list; l; l = g_list_next(l))
2513 {
2514 const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data;
2515 gchar buf[64];
2516 snprintf(buf, sizeof(buf), "%s,%d%s", entry->operation, entry->instance, (l == last) ? "" : ",");
2517 g_string_append(text, buf);
2518 }
2519
2520 return g_string_free(text, FALSE);
2521}
2522
2523/* this sanity check routine is used to correct wrong iop-list that
2524 * could have been stored while some bugs were present in
2525 * dartkable. There was a window around Sep 2019 where such issue
2526 * existed and some xmp may have been corrupt at this time making dt
2527 * crash while reimporting using the xmp.
2528 *
2529 * One common case seems that the list does not end with gamma.
2530*/
2531
2540static gboolean _ioppr_sanity_check_iop_order(GList *list)
2541{
2542 gboolean ok = TRUE;
2543
2544 // First check that first module is basebuffer (even for a jpeg, we
2545 // are speaking of the module ordering not the activated modules.
2546
2547 GList *first = g_list_first(list);
2548 dt_iop_order_entry_t *entry_first = (dt_iop_order_entry_t *)first->data;
2549
2550 ok = ok && (g_strcmp0(entry_first->operation, "basebuffer") == 0);
2551
2552 // Then check that last module is gamma
2553
2554 GList *last = g_list_last(list);
2555 dt_iop_order_entry_t *entry_last = (dt_iop_order_entry_t *)last->data;
2556
2557 ok = ok && (g_strcmp0(entry_last->operation, "gamma") == 0);
2558
2559 return ok;
2560}
2561
2563{
2564 GList *iop_order_list = NULL;
2565
2566 GList *list = dt_util_str_to_glist(",", buf);
2567 for(GList *l = list; l; l = g_list_next(l))
2568 {
2570 entry->o.iop_order = 0;
2571
2572 // first operation name
2573 g_strlcpy(entry->operation, (char *)l->data, sizeof(entry->operation));
2574
2575 // then operation instance
2576 l = g_list_next(l);
2577 if(IS_NULL_PTR(l)) goto error;
2578
2579 const char *data = (char *)l->data;
2580 int inst = 0;
2581 sscanf(data, "%d", &inst);
2582 entry->instance = inst;
2583
2584 // append to the list
2585 iop_order_list = g_list_prepend(iop_order_list, entry);
2586 }
2587 iop_order_list = g_list_reverse(iop_order_list); // list was built in reverse order, so un-reverse it
2588
2589 g_list_free_full(list, dt_free_gpointer);
2590 list = NULL;
2591
2592 _ioppr_reset_iop_order(iop_order_list);
2593
2594 // Remove sanity check from here; it's now handled after migration in dt_ioppr_get_iop_order_list()
2595
2596 return iop_order_list;
2597
2598 error:
2599 g_list_free_full(list, dt_free_gpointer);
2600 list = NULL;
2601 g_list_free_full(iop_order_list, dt_free_gpointer);
2602 iop_order_list = NULL;
2603 return NULL;
2604}
2605
2606GList *dt_ioppr_deserialize_iop_order_list(const char *buf, size_t size)
2607{
2608 GList *iop_order_list = NULL;
2609
2610 // parse all modules
2611 while(size)
2612 {
2614
2615 entry->o.iop_order = 0;
2616
2617 // get length of module name
2618 const int32_t len = *(int32_t *)buf;
2619 buf += sizeof(int32_t);
2620
2621 if(len < 0 || len > 20) { dt_free(entry); goto error; }
2622
2623 // set module name
2624 memcpy(entry->operation, buf, len);
2625 *(entry->operation + len) = '\0';
2626 buf += len;
2627
2628 // get the instance number
2629 entry->instance = *(int32_t *)buf;
2630 buf += sizeof(int32_t);
2631
2632 if(entry->instance < 0 || entry->instance > 1000) { dt_free(entry); goto error; }
2633
2634 // append to the list
2635 iop_order_list = g_list_prepend(iop_order_list, entry);
2636
2637 size -= (2 * sizeof(int32_t) + len);
2638 }
2639 iop_order_list = g_list_reverse(iop_order_list); // list was built in reverse order, so un-reverse it
2640
2641 _ioppr_reset_iop_order(iop_order_list);
2642
2643 return iop_order_list;
2644
2645 error:
2646 g_list_free_full(iop_order_list, dt_free_gpointer);
2647 iop_order_list = NULL;
2648 return NULL;
2649}
2650
2651#undef DT_IOP_ORDER_INFO
2652// clang-format off
2653// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2654// vim: shiftwidth=2 expandtab tabstop=2 cindent
2655// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2656// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const dt_adaptation_t kind
static const int row
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
GList * dt_ioppr_get_iop_order_rules_global(void)
Definition darktable.c:538
int dt_deprecated(const char *op)
Modules without a proper IOP order should throw errors and log, except if they are deprecated definit...
void dt_dev_write_history(dt_develop_t *dev, gboolean async)
Thread-safe wrapper around dt_dev_write_history_ext() for dev->image_storage.id.
void dt_dev_history_pixelpipe_update(dt_develop_t *dev, gboolean rebuild)
Rebuild or resync pixelpipes after backend history changes.
void dt_dev_history_notify_change(dt_develop_t *dev, const int32_t imgid)
Notify the rest of the app that history changes were written.
gboolean dt_dev_reload_history_items(dt_develop_t *dev, const int32_t imgid)
Reload history from DB and rebuild pipelines/GUI state.
void dt_dev_history_gui_update(struct dt_develop_t *dev)
Apply history-loaded params to module GUIs.
static gboolean g_list_shorter_than(const GList *list, unsigned len)
Definition glib_utils.h:34
gboolean dt_history_repository_get_module_order(const int32_t imgid, dt_module_order_row_t *row)
Read imgid's row. FALSE when there is none, and row is then left zeroed.
void dt_module_order_row_cleanup(dt_module_order_row_t *row)
Release what dt_history_repository_get_module_order() allocated.
gboolean dt_history_repository_set_module_order(const int32_t imgid, const int version, const char *iop_list)
Store imgid's order: version, and iop_list serialized or NULL.
gboolean dt_history_repository_has_custom_module_order(const int32_t imgid)
Whether imgid's row stores a serialized list, i.e. iop_list IS NOT NULL.
gboolean dt_history_repository_get_module_order_version(const int32_t imgid, int *version)
imgid's stored order version.
gboolean dt_history_repository_has_module_order(const int32_t imgid)
Whether imgid has a main.module_order row at all, whatever it says.
dt_image_t * dt_image_cache_testget(const int32_t imgid, char mode)
gboolean dt_image_cache_is_ready(void)
Has the image cache been initialised? Callers that run before dt_image_cache_init() or after its clea...
Definition image_cache.c:83
void dt_image_cache_read_release(const dt_image_t *img)
dt_iop_module_t * dt_iop_get_module_by_instance_name(GList *modules, const char *operation, const char *multi_name)
Definition imageop.c:1857
dt_iop_module_t * dt_iop_get_module_by_op_priority(GList *modules, const char *operation, const int multi_priority)
Definition imageop.c:1839
const dt_iop_order_entry_t ansel_raw_order[]
Definition iop_order.c:565
static void dt_ioppr_check_duplicate_iop_order(GList **_iop_list, GList *history_list)
Detect and resolve duplicate iop_order values.
Definition iop_order.c:1833
static GList * dt_ioppr_extract_multi_instances_list(GList *iop_order_list)
Extract all order entries that have multiple instances.
Definition iop_order.c:1381
const dt_iop_order_entry_t v30_order[]
Definition iop_order.c:194
static void _ioppr_check_rules(GList *iop_list, const int32_t imgid, const char *msg)
Validate pipeline order against fence and rule constraints.
Definition iop_order.c:2269
GList * dt_ioppr_get_iop_order_list(int32_t imgid, gboolean sorted)
Load the order list for an image from the DB.
Definition iop_order.c:1100
gboolean dt_ioppr_has_multiple_instances(GList *iop_order_list)
Detect whether multiple instances are grouped for a non-custom order.
Definition iop_order.c:991
gboolean dt_ioppr_has_iop_order_list(int32_t imgid)
Check whether the image has an explicit order list stored in DB.
Definition iop_order.c:1095
static gboolean _ioppr_sanity_check_iop_order(GList *list)
Basic sanity check for an order list.
Definition iop_order.c:2540
GList * dt_ioppr_merge_module_multi_instance_iop_order_list(GList *iop_order_list, const char *operation, GList *multi_instance_list)
Merge an operation's multiple instances into the order list.
Definition iop_order.c:1409
void * dt_ioppr_serialize_iop_order_list(GList *iop_order_list, size_t *size)
Serialize an order list into a binary blob (used for presets).
Definition iop_order.c:2465
GList * dt_ioppr_get_iop_order_list_version(dt_iop_order_t version)
Return the built-in order list for a given version.
Definition iop_order.c:1066
gint dt_sort_iop_list_by_order_f(gconstpointer a, gconstpointer b)
Compare two list nodes holding modules by iop_order.
Definition iop_order.c:939
static void * _dup_iop_order_entry(const void *src, gpointer data)
Deep-copy callback for dt_iop_order_entry_t.
Definition iop_order.c:1961
gint dt_sort_iop_by_order(gconstpointer a, gconstpointer b)
Compare two module instances by iop_order for sorting.
Definition iop_order.c:1976
static void _ioppr_reset_iop_order(GList *iop_order_list)
Reset iop_order values to a sequential order for a list.
Definition iop_order.c:1202
GList * dt_ioppr_get_iop_order_rules()
Return the list of ordering rules (prev/next constraints).
Definition iop_order.c:836
static void dt_ioppr_migrate_iop_order(struct dt_develop_t *dev, const int32_t imgid)
Apply a new order list by reloading history and rebuilding UI/pipelines.
Definition iop_order.c:1351
static dt_dev_history_item_t * _ioppr_search_history_by_module(GList *history_list, dt_iop_module_t *mod)
Find a history item referencing a given module instance.
Definition iop_order.c:1804
static int _count_entries_operation(GList *e_list, const char *operation)
Count order-list entries matching an operation name.
Definition iop_order.c:1561
static GList * _insert_before(GList *iop_order_list, const char *module, const char *new_module)
Insert a missing module entry before another module in an order list.
Definition iop_order.c:727
void dt_ioppr_rebuild_iop_order_from_modules(struct dt_develop_t *dev, GList *ordered_modules)
Rebuild dev->iop_order_list from a list of ordered modules.
Definition iop_order.c:1278
gint dt_sort_iop_list_by_order(gconstpointer a, gconstpointer b)
Definition iop_order.c:930
GList * _table_to_list(const dt_iop_order_entry_t entries[])
Build an order list from a static entry table.
Definition iop_order.c:1047
gboolean dt_ioppr_check_can_move_before_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_next)
Validate whether module can be moved before module_next.
Definition iop_order.c:1989
gboolean dt_ioppr_check_can_move_after_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_prev)
Validate whether module can be moved after module_prev.
Definition iop_order.c:2158
gboolean dt_ioppr_move_iop_after(struct dt_develop_t *dev, dt_iop_module_t *module, dt_iop_module_t *module_prev)
Move a module instance after another module in the pipe.
Definition iop_order.c:2225
static GList * dt_ioppr_merge_multi_instance_iop_order_list(GList *iop_order_list, GList *multi_instance_list)
Merge multiple-instance entries into an order list.
Definition iop_order.c:1472
const dt_iop_order_entry_t ansel_jpg_order[]
Definition iop_order.c:419
static gboolean dt_ioppr_write_iop_order(const dt_iop_order_t kind, GList *iop_order_list, const int32_t imgid)
Persist an order list for a given image with a specific kind.
Definition iop_order.c:1020
GList * dt_ioppr_iop_order_copy_deep(GList *iop_order_list)
Deep-copy an order list.
Definition iop_order.c:1970
gboolean dt_ioppr_write_iop_order_list(GList *iop_order_list, const int32_t imgid)
Persist an order list to the DB for a given image.
Definition iop_order.c:1035
void dt_ioppr_resync_modules_order(dt_develop_t *dev)
Update dev->iop module order values from dev->iop_order_list.
Definition iop_order.c:1249
int dt_ioppr_get_iop_order(GList *iop_order_list, const char *op_name, const int multi_priority)
Return the iop_order for a given operation/instance pair.
Definition iop_order.c:913
int _get_multi_priority(dt_develop_t *dev, const char *operation, const int n, const gboolean only_disabled)
Return the multi_priority of the n-th instance of an operation.
Definition iop_order.c:1600
int dt_ioppr_check_so_iop_order(GList *iop_list, GList *iop_order_list)
Check whether any module .so is missing an iop_order entry.
Definition iop_order.c:1934
const dt_iop_order_entry_t legacy_order[]
Definition iop_order.c:97
static const dt_iop_order_entry_t * orders[5]
Definition iop_order.c:949
const char * iop_order_string[]
Definition iop_order.c:72
gboolean dt_ioppr_move_iop_before(struct dt_develop_t *dev, dt_iop_module_t *module, dt_iop_module_t *module_next)
Move a module instance before another module in the pipe.
Definition iop_order.c:2193
dt_iop_order_t dt_ioppr_get_iop_order_list_kind(GList *iop_order_list)
Determine the kind of an order list by inspecting its content.
Definition iop_order.c:951
void dt_ioppr_update_for_style_items(dt_develop_t *dev, GList *st_items, gboolean append)
Update dev->iop_order_list with modules referenced by style items.
Definition iop_order.c:1721
void dt_ioppr_set_default_iop_order(dt_develop_t *dev, const int32_t imgid)
Set dev->iop_order_list to the default order for a given image.
Definition iop_order.c:1309
void dt_ioppr_resync_pipeline(dt_develop_t *dev, const int32_t imgid, const char *msg, gboolean check_duplicates)
Resynchronize pipeline order and related structures.
Definition iop_order.c:1241
char * dt_ioppr_serialize_text_iop_order_list(GList *iop_order_list)
Serialize an order list to a text representation.
Definition iop_order.c:2507
const dt_iop_order_entry_t v30_jpg_order[]
Definition iop_order.c:306
GList * dt_ioppr_deserialize_text_iop_order_list(const char *buf)
Deserialize an order list from a text representation.
Definition iop_order.c:2562
void dt_ioppr_insert_module_instance(struct dt_develop_t *dev, dt_iop_module_t *module)
Ensure a module instance has an entry in dev->iop_order_list.
Definition iop_order.c:2323
GList * dt_ioppr_insert_missing_modules(GList *iop_order_list)
Insert modules that postdate a deserialized/custom order list.
Definition iop_order.c:773
const char * dt_iop_order_string(const dt_iop_order_t order)
Return the human-readable name for an IOP order enum value.
Definition iop_order.c:82
void dt_ioppr_update_for_modules(dt_develop_t *dev, GList *modules, gboolean append)
Update dev->iop_order_list with modules from a module list.
Definition iop_order.c:1758
static dt_iop_order_entry_t * dt_ioppr_get_iop_order_entry(GList *iop_order_list, const char *op_name, const int multi_priority)
Return the first order entry matching operation/instance.
Definition iop_order.c:902
static gboolean _operation_already_handled(GList *e_list, const char *operation)
Check if an operation was already handled earlier in the list.
Definition iop_order.c:1581
dt_iop_order_t dt_ioppr_get_iop_order_version(const int32_t imgid)
Fetch the IOP order version stored for an image.
Definition iop_order.c:811
static void dt_ioppr_resync_iop_list(dt_develop_t *dev)
Resynchronize dev->iop list order against dev->iop_order_list.
Definition iop_order.c:1221
GList * dt_ioppr_deserialize_iop_order_list(const char *buf, size_t size)
Deserialize an order list from a binary blob.
Definition iop_order.c:2606
int dt_ioppr_check_iop_order(dt_develop_t *dev, const int32_t imgid, const char *msg)
Debug helper to validate the current order for a develop context.
Definition iop_order.c:2351
static void _count_iop_module(GList *iop, const char *operation, int *max_multi_priority, int *count, int *max_multi_priority_enabled, int *count_enabled)
Count module instances and track their highest priorities.
Definition iop_order.c:1527
static void dt_ioppr_update_for_entries(dt_develop_t *dev, GList *entry_list, gboolean append)
Update dev->iop_order_list to include entries from entry_list.
Definition iop_order.c:1625
void dt_ioppr_change_iop_order(struct dt_develop_t *dev, const int32_t imgid, GList *new_iop_list)
Replace the current order list with a new one and persist it.
Definition iop_order.c:1360
GList * dt_ioppr_get_iop_order_link(GList *iop_order_list, const char *op_name, const int multi_priority)
Find a list link matching an operation and instance.
Definition iop_order.c:875
dt_iop_order_t
Definition iop_order.h:149
@ DT_IOP_ORDER_ANSEL_RAW
Definition iop_order.h:154
@ DT_IOP_ORDER_LEGACY
Definition iop_order.h:151
@ DT_IOP_ORDER_LAST
Definition iop_order.h:156
@ DT_IOP_ORDER_V30_JPG
Definition iop_order.h:153
@ DT_IOP_ORDER_V30
Definition iop_order.h:152
@ DT_IOP_ORDER_CUSTOM
Definition iop_order.h:150
@ DT_IOP_ORDER_ANSEL_JPG
Definition iop_order.h:155
@ DT_DEBUG_PARAMS
Definition logging.h:71
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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 dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
size_t size
Definition mipmap_cache.c:3
void copy(TYPE *dest, TYPE *source, size_t num_el)
Copy a flat buffer used to initialize test matrices.
GList * iop_order_list
Definition develop.h:275
dt_image_t image_storage
Definition develop.h:225
GList * iop
Definition develop.h:269
GList * history
Definition develop.h:259
int32_t id
Definition image.h:401
char multi_name[128]
Definition imageop.h:387
gboolean enabled
Definition imageop.h:313
struct dt_iop_module_t::@23::@24 source
struct dt_iop_module_t::@23::@25 sink
struct dt_iop_module_t::@23 raster_mask
dt_dev_operation_t op
Definition imageop.h:259
Definition iop_order.h:160
char name[25]
Definition iop_order.h:168
int iop_order
Definition iop_order.h:163
char operation[20]
Definition iop_order.h:166
double iop_order_f
Definition iop_order.h:162
union dt_iop_order_entry_t::@26 o
int32_t instance
Definition iop_order.h:167
One main.module_order row.
GHashTable * entries
Definition supervisor.c:120
#define MAX(a, b)
Definition thinplate.c:29
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:833