/* This file is part of darktable, Copyright (C) 2018-2021 darktable developers. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common/darktable.h" #include "common/iop_order.h" #include "common/styles.h" #include "common/debug.h" #include "develop/imageop.h" #include "develop/pixelpipe.h" #include #include #include #define DT_IOP_ORDER_VERSION 5 #define DT_IOP_ORDER_INFO (darktable.unmuted & DT_DEBUG_IOPORDER) static void _ioppr_reset_iop_order(GList *iop_order_list); /** Note : * we do not use finite-math-only and fast-math because divisions by zero are not manually avoided in the code * fp-contract=fast enables hardware-accelerated Fused Multiply-Add * the rest is loop reorganization and vectorization optimization **/ #if defined(__GNUC__) #pragma GCC optimize ("unroll-loops", "tree-loop-if-convert", \ "tree-loop-distribution", "no-strict-aliasing", \ "loop-interchange", "tree-loop-im", \ "unswitch-loops", "tree-loop-ivcanon", "ira-loop-pressure", \ "split-ivs-in-unroller", "variable-expansion-in-unroller", \ "split-loops", "ivopts", "predictive-commoning",\ \ "fp-contract=fast", \ "tree-vectorize") #endif const char *iop_order_string[] = { N_("custom"), N_("legacy"), N_("v3.0 RAW"), N_("v3.0 JPEG") }; const char *dt_iop_order_string(const dt_iop_order_t order) { if(order >= DT_IOP_ORDER_LAST) return "???"; else return iop_order_string[order]; } // note legacy_order & v30_order have the original iop-order double that is // used only for the initial database migration. // // in the new code only the iop-order as int is used to order the module on the GUI. // @@_NEW_MODULE: For new module it is required to insert the new module name in both lists below. const dt_iop_order_entry_t legacy_order[] = { { { 1.0f }, "rawprepare", 0}, { { 2.0f }, "invert", 0}, { { 3.0f }, "temperature", 0}, { { 4.0f }, "highlights", 0}, { { 5.0f }, "cacorrect", 0}, { { 6.0f }, "hotpixels", 0}, { { 7.0f }, "rawdenoise", 0}, { { 8.0f }, "demosaic", 0}, { { 9.0f }, "mask_manager", 0}, { {10.0f }, "denoiseprofile", 0}, { {11.0f }, "tonemap", 0}, { {12.0f }, "exposure", 0}, { {13.0f }, "spots", 0}, { {14.0f }, "retouch", 0}, { {15.0f }, "lens", 0}, { {15.5f }, "cacorrectrgb", 0}, { {16.0f }, "ashift", 0}, { {17.0f }, "liquify", 0}, { {18.0f }, "rotatepixels", 0}, { {19.0f }, "scalepixels", 0}, { {20.0f }, "flip", 0}, { {21.0f }, "clipping", 0}, { {21.5f }, "toneequal", 0}, { {21.7f }, "crop", 0}, { {22.0f }, "graduatednd", 0}, { {23.0f }, "basecurve", 0}, { {24.0f }, "bilateral", 0}, { {25.0f }, "profile_gamma", 0}, { {26.0f }, "hazeremoval", 0}, { {27.0f }, "colorin", 0}, { {27.5f }, "channelmixerrgb", 0}, { {27.5f }, "diffuse", 0}, { {27.5f }, "censorize", 0}, { {27.5f }, "negadoctor", 0}, { {27.5f }, "blurs", 0}, { {27.5f }, "basicadj", 0}, { {28.0f }, "colorreconstruct", 0}, { {29.0f }, "colorchecker", 0}, { {30.0f }, "defringe", 0}, { {31.0f }, "equalizer", 0}, { {32.0f }, "vibrance", 0}, { {33.0f }, "colorbalance", 0}, { {33.5f }, "colorbalancergb", 0}, { {34.0f }, "colorize", 0}, { {35.0f }, "colortransfer", 0}, { {36.0f }, "colormapping", 0}, { {37.0f }, "bloom", 0}, { {38.0f }, "nlmeans", 0}, { {39.0f }, "globaltonemap", 0}, { {40.0f }, "shadhi", 0}, { {41.0f }, "atrous", 0}, { {42.0f }, "bilat", 0}, { {43.0f }, "colorzones", 0}, { {44.0f }, "lowlight", 0}, { {45.0f }, "monochrome", 0}, { {46.0f }, "filmic", 0}, { {46.5f }, "filmicrgb", 0}, { {47.0f }, "colisa", 0}, { {48.0f }, "zonesystem", 0}, { {49.0f }, "tonecurve", 0}, { {50.0f }, "levels", 0}, { {50.2f }, "rgblevels", 0}, { {50.5f }, "rgbcurve", 0}, { {51.0f }, "relight", 0}, { {52.0f }, "colorcorrection", 0}, { {53.0f }, "sharpen", 0}, { {54.0f }, "lowpass", 0}, { {55.0f }, "highpass", 0}, { {56.0f }, "grain", 0}, { {56.5f }, "lut3d", 0}, { {57.0f }, "colorcontrast", 0}, { {58.0f }, "colorout", 0}, { {59.0f }, "channelmixer", 0}, { {60.0f }, "soften", 0}, { {61.0f }, "vignette", 0}, { {62.0f }, "splittoning", 0}, { {63.0f }, "velvia", 0}, { {64.0f }, "clahe", 0}, { {65.0f }, "finalscale", 0}, { {66.0f }, "overexposed", 0}, { {67.0f }, "rawoverexposed", 0}, { {67.5f }, "dither", 0}, { {68.0f }, "borders", 0}, { {69.0f }, "watermark", 0}, { {71.0f }, "gamma", 0}, { { 0.0f }, "", 0} }; // default order for RAW files, assumed to be linear from start const dt_iop_order_entry_t v30_order[] = { { { 1.0 }, "rawprepare", 0}, { { 2.0 }, "invert", 0}, { { 3.0f }, "temperature", 0}, { { 4.0f }, "highlights", 0}, { { 5.0f }, "cacorrect", 0}, { { 6.0f }, "hotpixels", 0}, { { 7.0f }, "rawdenoise", 0}, { { 8.0f }, "demosaic", 0}, { { 9.0f }, "denoiseprofile", 0}, { {10.0f }, "bilateral", 0}, { {11.0f }, "rotatepixels", 0}, { {12.0f }, "scalepixels", 0}, { {13.0f }, "lens", 0}, { {13.5f }, "cacorrectrgb", 0}, // correct chromatic aberrations after lens correction so that lensfun // does not reintroduce chromatic aberrations when trying to correct them { {14.0f }, "hazeremoval", 0}, { {15.0f }, "ashift", 0}, { {16.0f }, "flip", 0}, { {17.0f }, "clipping", 0}, { {18.0f }, "liquify", 0}, { {19.0f }, "spots", 0}, { {20.0f }, "retouch", 0}, { {21.0f }, "exposure", 0}, { {22.0f }, "mask_manager", 0}, { {23.0f }, "tonemap", 0}, { {24.0f }, "toneequal", 0}, // last module that need enlarged roi_in { {24.5f }, "crop", 0}, // should go after all modules that may need a wider roi_in { {25.0f }, "graduatednd", 0}, { {26.0f }, "profile_gamma", 0}, { {27.0f }, "equalizer", 0}, { {28.0f }, "colorin", 0}, { {28.5f }, "channelmixerrgb", 0}, { {28.5f }, "diffuse", 0}, { {28.5f }, "censorize", 0}, { {28.5f }, "negadoctor", 0}, // Cineon film encoding comes after scanner input color profile { {28.5f }, "blurs", 0}, // physically-accurate blurs (motion and lens) { {29.0f }, "nlmeans", 0}, // signal processing (denoising) // -> needs a signal as scene-referred as possible (even if it works in Lab) { {30.0f }, "colorchecker", 0}, // calibration to "neutral" exchange colour space // -> improve colour calibration of colorin and reproductibility // of further edits (styles etc.) { {31.0f }, "defringe", 0}, // desaturate fringes in Lab, so needs properly calibrated colours // in order for chromaticity to be meaningful, { {32.0f }, "atrous", 0}, // frequential operation, needs a signal as scene-referred as possible to avoid halos { {33.0f }, "lowpass", 0}, // same { {34.0f }, "highpass", 0}, // same { {35.0f }, "sharpen", 0}, // same, worst than atrous in same use-case, less control overall { {37.0f }, "colortransfer", 0}, // probably better if source and destination colours are neutralized in the same // colour exchange space, hence after colorin and colorcheckr, // but apply after frequential ops in case it does non-linear witchcraft, // just to be safe { {38.0f }, "colormapping", 0}, // same { {39.0f }, "channelmixer", 0}, // does exactly the same thing as colorin, aka RGB to RGB matrix conversion, // but coefs are user-defined instead of calibrated and read from ICC profile. // Really versatile yet under-used module, doing linear ops, // very good in scene-referred workflow { {40.0f }, "basicadj", 0}, // module mixing view/model/control at once, usage should be discouraged { {41.0f }, "colorbalance", 0}, // scene-referred color manipulation { {41.5f }, "colorbalancergb", 0}, // scene-referred color manipulation { {42.0f }, "rgbcurve", 0}, // really versatile way to edit colour in scene-referred and display-referred workflow { {43.0f }, "rgblevels", 0}, // same { {44.0f }, "basecurve", 0}, // conversion from scene-referred to display referred, reverse-engineered // on camera JPEG default look { {45.0f }, "filmic", 0}, // same, but different (parametric) approach { {46.0f }, "filmicrgb", 0}, // same, upgraded { {36.0f }, "lut3d", 0}, // apply a creative style or film emulation, possibly non-linear { {47.0f }, "colisa", 0}, // edit contrast while damaging colour { {48.0f }, "tonecurve", 0}, // same { {49.0f }, "levels", 0}, // same { {50.0f }, "shadhi", 0}, // same { {51.0f }, "zonesystem", 0}, // same { {52.0f }, "globaltonemap", 0}, // same { {53.0f }, "relight", 0}, // flatten local contrast while pretending do add lightness { {54.0f }, "bilat", 0}, // improve clarity/local contrast after all the bad things we have done // to it with tonemapping { {55.0f }, "colorcorrection", 0}, // now that the colours have been damaged by contrast manipulations, // try to recover them - global adjustment of white balance for shadows and highlights { {56.0f }, "colorcontrast", 0}, // adjust chrominance globally { {57.0f }, "velvia", 0}, // same { {58.0f }, "vibrance", 0}, // same, but more subtle { {60.0f }, "colorzones", 0}, // same, but locally { {61.0f }, "bloom", 0}, // creative module { {62.0f }, "colorize", 0}, // creative module { {63.0f }, "lowlight", 0}, // creative module { {64.0f }, "monochrome", 0}, // creative module { {65.0f }, "grain", 0}, // creative module { {66.0f }, "soften", 0}, // creative module { {67.0f }, "splittoning", 0}, // creative module { {68.0f }, "vignette", 0}, // creative module { {69.0f }, "colorreconstruct", 0},// try to salvage blown areas before ICC intents in LittleCMS2 do things with them. { {70.0f }, "colorout", 0}, { {71.0f }, "clahe", 0}, { {72.0f }, "finalscale", 0}, { {73.0f }, "overexposed", 0}, { {74.0f }, "rawoverexposed", 0}, { {75.0f }, "dither", 0}, { {76.0f }, "borders", 0}, { {77.0f }, "watermark", 0}, { {78.0f }, "gamma", 0}, { { 0.0f }, "", 0 } }; // default order for JPEG/TIFF/PNG files, non-linear before colorin const dt_iop_order_entry_t v30_jpg_order[] = { // the following modules are not used anyway for non-RAW images : { { 1.0 }, "rawprepare", 0 }, { { 2.0 }, "invert", 0 }, { { 3.0f }, "temperature", 0 }, { { 4.0f }, "highlights", 0 }, { { 5.0f }, "cacorrect", 0 }, { { 6.0f }, "hotpixels", 0 }, { { 7.0f }, "rawdenoise", 0 }, { { 8.0f }, "demosaic", 0 }, // all the modules between [8; 28] expect linear RGB, so they need to be moved after colorin { { 28.0f }, "colorin", 0 }, // moved modules : (copy-pasted in the same order) { { 28.0f }, "denoiseprofile", 0}, { { 28.0f }, "bilateral", 0}, { { 28.0f }, "rotatepixels", 0}, { { 28.0f }, "scalepixels", 0}, { { 28.0f }, "lens", 0}, { { 28.0f }, "cacorrectrgb", 0}, // correct chromatic aberrations after lens correction so that lensfun // does not reintroduce chromatic aberrations when trying to correct them { { 28.0f }, "hazeremoval", 0}, { { 28.0f }, "ashift", 0}, { { 28.0f }, "flip", 0}, { { 28.0f }, "clipping", 0}, { { 28.0f }, "liquify", 0}, { { 28.0f }, "spots", 0}, { { 28.0f }, "retouch", 0}, { { 28.0f }, "exposure", 0}, { { 28.0f }, "mask_manager", 0}, { { 28.0f }, "tonemap", 0}, { { 28.0f }, "toneequal", 0}, // last module that need enlarged roi_in { { 28.0f }, "crop", 0}, // should go after all modules that may need a wider roi_in { { 28.0f }, "graduatednd", 0}, { { 28.0f }, "profile_gamma", 0}, { { 28.0f }, "equalizer", 0}, // from there, it's the same as the raw order { { 28.5f }, "channelmixerrgb", 0 }, { { 28.5f }, "diffuse", 0 }, { { 28.5f }, "censorize", 0 }, { { 28.5f }, "negadoctor", 0 }, // Cineon film encoding comes after scanner input color profile { { 28.5f }, "blurs", 0 }, // physically-accurate blurs (motion and lens) { { 29.0f }, "nlmeans", 0 }, // signal processing (denoising) // -> needs a signal as scene-referred as possible (even if it works in Lab) { { 30.0f }, "colorchecker", 0 }, // calibration to "neutral" exchange colour space // -> improve colour calibration of colorin and reproductibility // of further edits (styles etc.) { { 31.0f }, "defringe", 0 }, // desaturate fringes in Lab, so needs properly calibrated colours // in order for chromaticity to be meaningful, { { 32.0f }, "atrous", 0 }, // frequential operation, needs a signal as scene-referred as possible to avoid halos { { 33.0f }, "lowpass", 0 }, // same { { 34.0f }, "highpass", 0 }, // same { { 35.0f }, "sharpen", 0 }, // same, worst than atrous in same use-case, less control overall { { 37.0f }, "colortransfer", 0 }, // probably better if source and destination colours are neutralized in the // same // colour exchange space, hence after colorin and colorcheckr, // but apply after frequential ops in case it does non-linear witchcraft, // just to be safe { { 38.0f }, "colormapping", 0 }, // same { { 39.0f }, "channelmixer", 0 }, // does exactly the same thing as colorin, aka RGB to RGB matrix conversion, // but coefs are user-defined instead of calibrated and read from ICC // profile. Really versatile yet under-used module, doing linear ops, very // good in scene-referred workflow { { 40.0f }, "basicadj", 0 }, // module mixing view/model/control at once, usage should be discouraged { { 41.0f }, "colorbalance", 0 }, // scene-referred color manipulation { { 41.5f }, "colorbalancergb", 0 }, // scene-referred color manipulation { { 42.0f }, "rgbcurve", 0 }, // really versatile way to edit colour in scene-referred and display-referred // workflow { { 43.0f }, "rgblevels", 0 }, // same { { 44.0f }, "basecurve", 0 }, // conversion from scene-referred to display referred, reverse-engineered // on camera JPEG default look { { 45.0f }, "filmic", 0 }, // same, but different (parametric) approach { { 46.0f }, "filmicrgb", 0 }, // same, upgraded { { 36.0f }, "lut3d", 0 }, // apply a creative style or film emulation, possibly non-linear { { 47.0f }, "colisa", 0 }, // edit contrast while damaging colour { { 48.0f }, "tonecurve", 0 }, // same { { 49.0f }, "levels", 0 }, // same { { 50.0f }, "shadhi", 0 }, // same { { 51.0f }, "zonesystem", 0 }, // same { { 52.0f }, "globaltonemap", 0 }, // same { { 53.0f }, "relight", 0 }, // flatten local contrast while pretending do add lightness { { 54.0f }, "bilat", 0 }, // improve clarity/local contrast after all the bad things we have done // to it with tonemapping { { 55.0f }, "colorcorrection", 0 }, // now that the colours have been damaged by contrast manipulations, // try to recover them - global adjustment of white balance for shadows and // highlights { { 56.0f }, "colorcontrast", 0 }, // adjust chrominance globally { { 57.0f }, "velvia", 0 }, // same { { 58.0f }, "vibrance", 0 }, // same, but more subtle { { 60.0f }, "colorzones", 0 }, // same, but locally { { 61.0f }, "bloom", 0 }, // creative module { { 62.0f }, "colorize", 0 }, // creative module { { 63.0f }, "lowlight", 0 }, // creative module { { 64.0f }, "monochrome", 0 }, // creative module { { 65.0f }, "grain", 0 }, // creative module { { 66.0f }, "soften", 0 }, // creative module { { 67.0f }, "splittoning", 0 }, // creative module { { 68.0f }, "vignette", 0 }, // creative module { { 69.0f }, "colorreconstruct", 0 }, // try to salvage blown areas before ICC intents in LittleCMS2 do things // with them. { { 70.0f }, "colorout", 0 }, { { 71.0f }, "clahe", 0 }, { { 72.0f }, "finalscale", 0 }, { { 73.0f }, "overexposed", 0 }, { { 74.0f }, "rawoverexposed", 0 }, { { 75.0f }, "dither", 0 }, { { 76.0f }, "borders", 0 }, { { 77.0f }, "watermark", 0 }, { { 78.0f }, "gamma", 0 }, { { 0.0f }, "", 0 } }; static void *_dup_iop_order_entry(const void *src, gpointer data); static int _count_entries_operation(GList *e_list, const char *operation); static GList *_insert_before(GList *iop_order_list, const char *module, const char *new_module) { gboolean exists = FALSE; // first check that new module is missing for(const GList *l = iop_order_list; l; l = g_list_next(l)) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; if(!strcmp(entry->operation, new_module)) { exists = TRUE; break; } } // the insert it if needed if(!exists) { for(GList *l = iop_order_list; l; l = g_list_next(l)) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; if(!strcmp(entry->operation, module)) { dt_iop_order_entry_t *new_entry = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t)); g_strlcpy(new_entry->operation, new_module, sizeof(new_entry->operation)); new_entry->instance = 0; new_entry->o.iop_order = 0; iop_order_list = g_list_insert_before(iop_order_list, l, new_entry); break; } } } return iop_order_list; } dt_iop_order_t dt_ioppr_get_iop_order_version(const int32_t imgid) { const gboolean is_display_referred = dt_conf_is_equal("plugins/darkroom/workflow", "display-referred"); dt_iop_order_t iop_order_version = is_display_referred ? DT_IOP_ORDER_LEGACY : DT_IOP_ORDER_V30; // check current iop order version sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT version FROM main.module_order WHERE imgid = ?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); if(sqlite3_step(stmt) == SQLITE_ROW) { iop_order_version = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); return iop_order_version; } // a rule prevents operations to be switched, // that is a prev operation will not be allowed to be moved on top of the next operation. GList *dt_ioppr_get_iop_order_rules() { GList *rules = NULL; const dt_iop_order_rule_t rule_entry[] = { { .op_prev = "rawprepare", .op_next = "invert" }, { .op_prev = "invert", .op_next = "temperature" }, { .op_prev = "temperature", .op_next = "highlights" }, { .op_prev = "highlights", .op_next = "cacorrect" }, { .op_prev = "cacorrect", .op_next = "hotpixels" }, { .op_prev = "hotpixels", .op_next = "rawdenoise" }, { .op_prev = "rawdenoise", .op_next = "demosaic" }, { .op_prev = "demosaic", .op_next = "colorin" }, { .op_prev = "colorin", .op_next = "colorout" }, { .op_prev = "colorout", .op_next = "gamma" }, { .op_prev = "flip", .op_next = "clipping" }, // clipping GUI broken if flip is done on top { .op_prev = "ashift", .op_next = "clipping" }, // clipping GUI broken if ashift is done on top { .op_prev = "colorin", .op_next = "channelmixerrgb"}, { "\0", "\0" } }; int i = 0; while(rule_entry[i].op_prev[0]) { dt_iop_order_rule_t *rule = calloc(1, sizeof(dt_iop_order_rule_t)); memcpy(rule->op_prev, rule_entry[i].op_prev, sizeof(rule->op_prev)); memcpy(rule->op_next, rule_entry[i].op_next, sizeof(rule->op_next)); rules = g_list_prepend(rules, rule); i++; } return g_list_reverse(rules); // list was built in reverse order, so un-reverse it } GList *dt_ioppr_get_iop_order_link(GList *iop_order_list, const char *op_name, const int multi_priority) { GList *link = NULL; for(GList *iops_order = iop_order_list; iops_order; iops_order = g_list_next(iops_order)) { dt_iop_order_entry_t *order_entry = (dt_iop_order_entry_t *)iops_order->data; if(strcmp(order_entry->operation, op_name) == 0 && (order_entry->instance == multi_priority || multi_priority == -1)) { link = iops_order; break; } } return link; } // returns the first iop order entry that matches operation == op_name dt_iop_order_entry_t *dt_ioppr_get_iop_order_entry(GList *iop_order_list, const char *op_name, const int multi_priority) { const GList * const restrict link = dt_ioppr_get_iop_order_link(iop_order_list, op_name, multi_priority); if(link) return (dt_iop_order_entry_t *)link->data; else return NULL; } // returns the iop_order associated with the iop order entry that matches operation == op_name int dt_ioppr_get_iop_order(GList *iop_order_list, const char *op_name, const int multi_priority) { int iop_order = INT_MAX; const dt_iop_order_entry_t *const restrict order_entry = dt_ioppr_get_iop_order_entry(iop_order_list, op_name, multi_priority); if(order_entry) { iop_order = order_entry->o.iop_order; } else fprintf(stderr, "cannot get iop-order for %s instance %d\n", op_name, multi_priority); return iop_order; } gboolean dt_ioppr_is_iop_before(GList *iop_order_list, const char *base_operation, const char *operation, const int multi_priority) { const int base_order = dt_ioppr_get_iop_order(iop_order_list, base_operation, -1); const int op_order = dt_ioppr_get_iop_order(iop_order_list, operation, multi_priority); return op_order < base_order; } gint dt_sort_iop_list_by_order(gconstpointer a, gconstpointer b) { const dt_iop_order_entry_t *const restrict am = (const dt_iop_order_entry_t *)a; const dt_iop_order_entry_t *const restrict bm = (const dt_iop_order_entry_t *)b; if(am->o.iop_order > bm->o.iop_order) return 1; if(am->o.iop_order < bm->o.iop_order) return -1; return 0; } gint dt_sort_iop_list_by_order_f(gconstpointer a, gconstpointer b) { const dt_iop_order_entry_t *const restrict am = (const dt_iop_order_entry_t *)a; const dt_iop_order_entry_t *const restrict bm = (const dt_iop_order_entry_t *)b; if(am->o.iop_order_f > bm->o.iop_order_f) return 1; if(am->o.iop_order_f < bm->o.iop_order_f) return -1; return 0; } dt_iop_order_t dt_ioppr_get_iop_order_list_kind(GList *iop_order_list) { // first check if this is the v30 order RAW int k = 0; GList *l = iop_order_list; gboolean ok = TRUE; while(l) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; if(strcmp(v30_order[k].operation, entry->operation)) { ok = FALSE; break; } else { // skip all the other instance of same module if any while(g_list_next(l) && !strcmp(v30_order[k].operation, ((dt_iop_order_entry_t *)(g_list_next(l)->data))->operation)) l = g_list_next(l); } k++; l = g_list_next(l); } if(ok) return DT_IOP_ORDER_V30; // then check if this is the v30 order JPG k = 0; l = iop_order_list; ok = TRUE; while(l) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; if(strcmp(v30_jpg_order[k].operation, entry->operation)) { ok = FALSE; break; } else { // skip all the other instance of same module if any while(g_list_next(l) && !strcmp(v30_jpg_order[k].operation, ((dt_iop_order_entry_t *)(g_list_next(l)->data))->operation)) l = g_list_next(l); } k++; l = g_list_next(l); } if(ok) return DT_IOP_ORDER_V30_JPG; // then check if this is the legacy order k = 0; l = iop_order_list; ok = TRUE; while(l) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; if(strcmp(legacy_order[k].operation, entry->operation)) { ok = FALSE; break; } else { // skip all the other instance of same module if any while(g_list_next(l) && !strcmp(legacy_order[k].operation, ((dt_iop_order_entry_t *)(g_list_next(l)->data))->operation)) l = g_list_next(l); } k++; l = g_list_next(l); } if(ok) return DT_IOP_ORDER_LEGACY; return DT_IOP_ORDER_CUSTOM; } gboolean dt_ioppr_has_multiple_instances(GList *iop_order_list) { GList *l = iop_order_list; while(l) { GList *next = g_list_next(l); if(next && (strcmp(((dt_iop_order_entry_t *)(l->data))->operation, ((dt_iop_order_entry_t *)(next->data))->operation) == 0)) { return TRUE; } l = next; } return FALSE; } gboolean dt_ioppr_write_iop_order(const dt_iop_order_t kind, GList *iop_order_list, const int32_t imgid) { sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "INSERT OR REPLACE INTO main.module_order VALUES (?1, 0, NULL)", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); if(sqlite3_step(stmt) != SQLITE_DONE) return FALSE; sqlite3_finalize(stmt); if(kind == DT_IOP_ORDER_CUSTOM || dt_ioppr_has_multiple_instances(iop_order_list)) { gchar *iop_list_txt = dt_ioppr_serialize_text_iop_order_list(iop_order_list); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "UPDATE main.module_order SET version = ?2, iop_list = ?3 WHERE imgid = ?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, kind); DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 3, iop_list_txt, -1, SQLITE_TRANSIENT); if(sqlite3_step(stmt) != SQLITE_DONE) return FALSE; sqlite3_finalize(stmt); g_free(iop_list_txt); } else { DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "UPDATE main.module_order SET version = ?2, iop_list = NULL WHERE imgid = ?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, kind); if(sqlite3_step(stmt) != SQLITE_DONE) return FALSE; sqlite3_finalize(stmt); } return TRUE; } gboolean dt_ioppr_write_iop_order_list(GList *iop_order_list, const int32_t imgid) { const dt_iop_order_t kind = dt_ioppr_get_iop_order_list_kind(iop_order_list); return dt_ioppr_write_iop_order(kind, iop_order_list, imgid); } GList *_table_to_list(const dt_iop_order_entry_t entries[]) { GList *iop_order_list = NULL; int k = 0; while(entries[k].operation[0]) { dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t)); g_strlcpy(entry->operation, entries[k].operation, sizeof(entry->operation)); entry->instance = 0; entry->o.iop_order_f = entries[k].o.iop_order_f; iop_order_list = g_list_prepend(iop_order_list, entry); k++; } return g_list_reverse(iop_order_list); // list was built in reverse order, so un-reverse it } GList *dt_ioppr_get_iop_order_list_version(dt_iop_order_t version) { GList *iop_order_list = NULL; if(version == DT_IOP_ORDER_LEGACY) { iop_order_list = _table_to_list(legacy_order); } else if(version == DT_IOP_ORDER_V30) { iop_order_list = _table_to_list(v30_order); } else if(version == DT_IOP_ORDER_V30_JPG) { iop_order_list = _table_to_list(v30_jpg_order); } return iop_order_list; } gboolean dt_ioppr_has_iop_order_list(int32_t imgid) { gboolean result = FALSE; sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT version, iop_list" " FROM main.module_order" " WHERE imgid=?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); if(sqlite3_step(stmt) == SQLITE_ROW) { result = (sqlite3_column_type(stmt, 1) != SQLITE_NULL); } sqlite3_finalize(stmt); return result; } GList *dt_ioppr_get_iop_order_list(int32_t imgid, gboolean sorted) { GList *iop_order_list = NULL; if(imgid > 0) { sqlite3_stmt *stmt; // we read the iop-order-list in the preset table, the actual version is // the first int32_t serialized into the io_params. This is then a sequential // search, but there will not be many such presets and we do call this routine // only when loading an image and when changing the iop-order. DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT version, iop_list" " FROM main.module_order" " WHERE imgid=?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); if(sqlite3_step(stmt) == SQLITE_ROW) { const dt_iop_order_t version = sqlite3_column_int(stmt, 0); const gboolean has_iop_list = (sqlite3_column_type(stmt, 1) != SQLITE_NULL); if(version == DT_IOP_ORDER_CUSTOM || has_iop_list) { const char *buf = (char *)sqlite3_column_text(stmt, 1); if(buf) iop_order_list = dt_ioppr_deserialize_text_iop_order_list(buf); if(!iop_order_list) { // preset not found, fall back to last built-in version, will be loaded below fprintf(stderr, "[dt_ioppr_get_iop_order_list] error building iop_order_list imgid %d\n", imgid); } else { // @@_NEW_MODULE: For new module it is required to insert the new module name in the iop-order list here. // The insertion can be done depending on the current iop-order list kind. _insert_before(iop_order_list, "nlmeans", "negadoctor"); _insert_before(iop_order_list, "negadoctor", "channelmixerrgb"); _insert_before(iop_order_list, "negadoctor", "censorize"); _insert_before(iop_order_list, "rgbcurve", "colorbalancergb"); _insert_before(iop_order_list, "ashift", "cacorrectrgb"); _insert_before(iop_order_list, "graduatednd", "crop"); _insert_before(iop_order_list, "colorbalance", "diffuse"); _insert_before(iop_order_list, "nlmeans", "blurs"); } } else if(version == DT_IOP_ORDER_LEGACY) { iop_order_list = _table_to_list(legacy_order); } else if(version == DT_IOP_ORDER_V30) { iop_order_list = _table_to_list(v30_order); } else if(version == DT_IOP_ORDER_V30_JPG) { iop_order_list = _table_to_list(v30_jpg_order); } else fprintf(stderr, "[dt_ioppr_get_iop_order_list] invalid iop order version %d for imgid %d\n", version, imgid); if(iop_order_list) { _ioppr_reset_iop_order(iop_order_list); } } sqlite3_finalize(stmt); } // fallback to last iop order list (also used to initialize the pipe when imgid = 0) // and new image not yet loaded or whose history has been reset. if(!iop_order_list) { const char *workflow = dt_conf_get_string_const("plugins/darkroom/workflow"); dt_iop_order_t iop_order_version = strcmp(workflow, "display-referred") == 0 ? DT_IOP_ORDER_LEGACY : DT_IOP_ORDER_V30; if(iop_order_version == DT_IOP_ORDER_LEGACY) iop_order_list = _table_to_list(legacy_order); else iop_order_list = _table_to_list(v30_order); } if(sorted) iop_order_list = g_list_sort(iop_order_list, dt_sort_iop_list_by_order); return iop_order_list; } static void _ioppr_reset_iop_order(GList *iop_order_list) { // iop-order must start with a number > 0 and be incremented. There is no // other constraints. int iop_order = 1; for(const GList *l = iop_order_list; l; l = g_list_next(l)) { dt_iop_order_entry_t *e = (dt_iop_order_entry_t *)l->data; e->o.iop_order = iop_order++; } } void dt_ioppr_resync_iop_list(dt_develop_t *dev) { // make sure that the iop_order_list does not contains possibly removed modules GList *l = dev->iop_order_list; while(l) { GList *next = g_list_next(l); // need to get next pointer now, as we may be deleting this node const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)l->data; const dt_iop_module_t *const restrict mod = dt_iop_get_module_by_op_priority(dev->iop, e->operation, e->instance); if(mod == NULL) { dev->iop_order_list = g_list_remove_link(dev->iop_order_list, l); } l = next; } } void dt_ioppr_resync_modules_order(dt_develop_t *dev) { _ioppr_reset_iop_order(dev->iop_order_list); // and reset all module iop_order GList *modules = dev->iop; while(modules) { dt_iop_module_t *mod = (dt_iop_module_t *)(modules->data); GList *next = g_list_next(modules); // modules with iop_order set to INT_MAX we keep them as they will be removed (non visible) // _lib_modulegroups_update_iop_visibility. if(mod->iop_order != INT_MAX) mod->iop_order = dt_ioppr_get_iop_order(dev->iop_order_list, mod->op, mod->multi_priority); modules = next; } dev->iop = g_list_sort(dev->iop, dt_sort_iop_by_order); } // sets the iop_order on each module of *_iop_list // iop_order is set only for base modules, multi-instances will be flagged as unused with INT_MAX // if a module do not exists on iop_order_list it is flagged as unused with INT_MAX void dt_ioppr_set_default_iop_order(dt_develop_t *dev, const int32_t imgid) { // get the iop-order for this image GList *iop_order_list = dt_ioppr_get_iop_order_list(imgid, FALSE); // we assign a single iop-order to each module _ioppr_reset_iop_order(iop_order_list); if(dev->iop_order_list) g_list_free_full(dev->iop_order_list, free); dev->iop_order_list = iop_order_list; // we now set the module list given to this iop-order dt_ioppr_resync_modules_order(dev); } void dt_ioppr_migrate_iop_order(struct dt_develop_t *dev, const int32_t imgid) { dt_ioppr_set_default_iop_order(dev, imgid); dt_dev_reload_history_items(dev); } void dt_ioppr_change_iop_order(struct dt_develop_t *dev, const int32_t imgid, GList *new_iop_list) { GList *iop_list = dt_ioppr_iop_order_copy_deep(new_iop_list); GList *mi = dt_ioppr_extract_multi_instances_list(darktable.develop->iop_order_list); if(mi) iop_list = dt_ioppr_merge_multi_instance_iop_order_list(iop_list, mi); dt_dev_write_history(darktable.develop); dt_ioppr_write_iop_order(DT_IOP_ORDER_CUSTOM, iop_list, imgid); g_list_free_full(iop_list, free); dt_ioppr_migrate_iop_order(darktable.develop, imgid); } GList *dt_ioppr_extract_multi_instances_list(GList *iop_order_list) { GList *mi = NULL; for(const GList *l = iop_order_list; l; l = g_list_next(l)) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; if(_count_entries_operation(iop_order_list, entry->operation) > 1) { dt_iop_order_entry_t *copy = (dt_iop_order_entry_t *)_dup_iop_order_entry((void *)entry, NULL); mi = g_list_prepend(mi, copy); } } return g_list_reverse(mi); // list was built in reverse order, so un-reverse it } GList *dt_ioppr_merge_module_multi_instance_iop_order_list(GList *iop_order_list, const char *operation, GList *multi_instance_list) { const int count_to = _count_entries_operation(iop_order_list, operation); int item_nb = 0; GList *link = iop_order_list; for(const GList *l = multi_instance_list; l; l = g_list_next(l)) { dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)l->data; item_nb++; if(item_nb <= count_to) { link = dt_ioppr_get_iop_order_link(link, operation, -1); dt_iop_order_entry_t *e = (dt_iop_order_entry_t *)link->data; e->instance = entry->instance; // free this entry as not merged into the list free(entry); // next replace should happen to any module after this one link = g_list_next(link); } else { iop_order_list = g_list_insert_before(iop_order_list, link, entry); } } // if needed removes all other instance of this operation which are superfluous if(g_list_shorter_than(multi_instance_list, count_to)) { while(link) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)link->data; GList *next = g_list_next(link); if(strcmp(operation, entry->operation) == 0) { iop_order_list = g_list_remove_link(iop_order_list, link); } link = next; } } return iop_order_list; } GList *dt_ioppr_merge_multi_instance_iop_order_list(GList *iop_order_list, GList *multi_instance_list) { GList *op = NULL; GList *copy = dt_ioppr_iop_order_copy_deep(multi_instance_list); GList *l = copy; while(l) { dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)l->data; GList *l_next = g_list_next(l); op = g_list_append(op, entry); copy = g_list_remove_link(copy, l); GList *mi = l_next; while(mi) { GList *next = g_list_next(mi); dt_iop_order_entry_t *mi_entry = (dt_iop_order_entry_t *)mi->data; if(strcmp(entry->operation, mi_entry->operation) == 0) { op = g_list_append(op, mi_entry); copy = g_list_remove_link(copy, mi); } mi = next; } // copy operation as entry may be freed char operation[20]; memcpy(operation, entry->operation, sizeof(entry->operation)); iop_order_list = dt_ioppr_merge_module_multi_instance_iop_order_list(iop_order_list, operation, op); g_list_free(op); op = NULL; l = copy; } return iop_order_list; } static void _count_iop_module(GList *iop, const char *operation, int *max_multi_priority, int *count, int *max_multi_priority_enabled, int *count_enabled) { *max_multi_priority = 0; *count = 0; *max_multi_priority_enabled = 0; *count_enabled = 0; for(const GList *modules = iop; modules; modules = g_list_next(modules)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(!strcmp(mod->op, operation)) { (*count)++; if(*max_multi_priority < mod->multi_priority) *max_multi_priority = mod->multi_priority; if(mod->enabled) { (*count_enabled)++; if(*max_multi_priority_enabled < mod->multi_priority) *max_multi_priority_enabled = mod->multi_priority; } } } assert(*count >= *count_enabled); } static int _count_entries_operation(GList *e_list, const char *operation) { int count = 0; for(const GList *l = e_list; l; l = g_list_next(l)) { dt_iop_order_entry_t *ep = (dt_iop_order_entry_t *)l->data; if(!strcmp(ep->operation, operation)) count++; } return count; } static gboolean _operation_already_handled(GList *e_list, const char *operation) { for(const GList *l = g_list_previous(e_list); l; l = g_list_previous(l)) { const dt_iop_order_entry_t *const restrict ep = (dt_iop_order_entry_t *)l->data; if(!strcmp(ep->operation, operation)) return TRUE; } return FALSE; } // returns the nth module's priority being active or not int _get_multi_priority(dt_develop_t *dev, const char *operation, const int n, const gboolean only_disabled) { int count = 0; for(const GList *l = dev->iop; l; l = g_list_next(l)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)l->data; if((!only_disabled || mod->enabled == FALSE) && !strcmp(mod->op, operation)) { count++; if(count == n) return mod->multi_priority; } } return INT_MAX; } void dt_ioppr_update_for_entries(dt_develop_t *dev, GList *entry_list, gboolean append) { // for each priority list to be checked for(GList *e_list = entry_list; e_list; e_list = g_list_next(e_list)) { const dt_iop_order_entry_t *const restrict ep = (dt_iop_order_entry_t *)e_list->data; gboolean force_append = FALSE; // we also need to force append (even if overwrite mode is // selected - append = FALSE) when a module has a specific name // and this name is not present into the current iop list. if(*ep->name && !dt_iop_get_module_by_instance_name(dev->iop, ep->operation, ep->name)) force_append = TRUE; int max_multi_priority = 0, count = 0; int max_multi_priority_enabled = 0, count_enabled = 0; // is it a currently active module and if so how many active instances we have _count_iop_module(dev->iop, ep->operation, &max_multi_priority, &count, &max_multi_priority_enabled, &count_enabled); // look for this operation into the target iop-order list and add there as much operation as needed for(GList *l = g_list_last(dev->iop_order_list); l; l = g_list_previous(l)) { const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)l->data; if(!strcmp(e->operation, ep->operation) && !_operation_already_handled(e_list, ep->operation)) { // how many instances of this module in the entry list, and re-number multi-priority accordingly const int new_active_instances = _count_entries_operation(entry_list, ep->operation); int add_count = 0; int start_multi_priority = 0; int nb_replace = 0; if(append || force_append) { nb_replace = count - count_enabled; add_count = MAX(0, new_active_instances - nb_replace); start_multi_priority = max_multi_priority + 1; } else { nb_replace = count; add_count = MAX(0, new_active_instances - count); start_multi_priority = max_multi_priority + 1; } // update multi_priority to be unique in iop list int multi_priority = start_multi_priority; int nb = 0; for(const GList *s = entry_list; s; s = g_list_next(s)) { dt_iop_order_entry_t *item = (dt_iop_order_entry_t *)s->data; if(!strcmp(item->operation, e->operation)) { nb++; if(nb <= nb_replace) { // this one replaces current module, get it's multi-priority item->instance = _get_multi_priority(dev, item->operation, nb, append); } else { // otherwise create a new multi-priority item->instance = multi_priority++; } } } multi_priority = start_multi_priority; l = g_list_next(l); for(int k = 0; koperation, ep->operation, sizeof(n->operation)); n->instance = multi_priority++; n->o.iop_order = 0; dev->iop_order_list = g_list_insert_before(dev->iop_order_list, l, n); } break; } } } _ioppr_reset_iop_order(dev->iop_order_list); // dt_ioppr_print_iop_order(dev->iop_order_list, "upd sitem"); } void dt_ioppr_update_for_style_items(dt_develop_t *dev, GList *st_items, gboolean append) { GList *e_list = NULL; // for each priority list to be checked for(const GList *si_list = st_items; si_list; si_list = g_list_next(si_list)) { const dt_style_item_t *const restrict si = (dt_style_item_t *)si_list->data; dt_iop_order_entry_t *n = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t)); memcpy(n->operation, si->operation, sizeof(n->operation)); n->instance = si->multi_priority; g_strlcpy(n->name, si->multi_name, sizeof(n->name)); n->o.iop_order = 0; e_list = g_list_prepend(e_list, n); } e_list = g_list_reverse(e_list); // list was built in reverse order, so un-reverse it dt_ioppr_update_for_entries(dev, e_list, append); // write back the multi-priority GList *el = e_list; for(const GList *si_list = st_items; si_list; si_list = g_list_next(si_list)) { dt_style_item_t *si = (dt_style_item_t *)si_list->data; const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)el->data; si->multi_priority = e->instance; si->iop_order = dt_ioppr_get_iop_order(dev->iop_order_list, si->operation, si->multi_priority); el = g_list_next(el); } g_list_free(e_list); } void dt_ioppr_update_for_modules(dt_develop_t *dev, GList *modules, gboolean append) { GList *e_list = NULL; // for each priority list to be checked for(const GList *m_list = modules; m_list; m_list = g_list_next(m_list)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)m_list->data; dt_iop_order_entry_t *n = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t)); g_strlcpy(n->operation, mod->op, sizeof(n->operation)); n->instance = mod->multi_priority; g_strlcpy(n->name, mod->multi_name, sizeof(n->name)); n->o.iop_order = 0; e_list = g_list_prepend(e_list, n); } e_list = g_list_reverse(e_list); // list was built in reverse order, so un-reverse it dt_ioppr_update_for_entries(dev, e_list, append); // write back the multi-priority GList *el = e_list; for(const GList *m_list = modules; m_list; m_list = g_list_next(m_list)) { dt_iop_module_t *mod = (dt_iop_module_t *)m_list->data; dt_iop_order_entry_t *e = (dt_iop_order_entry_t *)el->data; mod->multi_priority = e->instance; mod->iop_order = dt_ioppr_get_iop_order(dev->iop_order_list, mod->op, mod->multi_priority); el = g_list_next(el); } g_list_free_full(e_list, free); } // returns the first dt_dev_history_item_t on history_list where hist->module == mod static dt_dev_history_item_t *_ioppr_search_history_by_module(GList *history_list, dt_iop_module_t *mod) { dt_dev_history_item_t *hist_entry = NULL; for(const GList *history = history_list; history; history = g_list_next(history)) { dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data); if(hist->module == mod) { hist_entry = hist; break; } } return hist_entry; } // check if there's duplicate iop_order entries in iop_list // if so, updates the iop_order to be unique, but only if the module is disabled and not in history void dt_ioppr_check_duplicate_iop_order(GList **_iop_list, GList *history_list) { GList *iop_list = *_iop_list; dt_iop_module_t *mod_prev = NULL; // get the first module GList *modules = iop_list; if(modules) { mod_prev = (dt_iop_module_t *)(modules->data); modules = g_list_next(modules); } // check for each module if iop_order is the same as the previous one // if so, change it, but only if disabled and not in history while(modules) { int reset_list = 0; dt_iop_module_t *mod = (dt_iop_module_t *)(modules->data); if(mod->iop_order == mod_prev->iop_order && mod->iop_order != INT_MAX) { int can_move = 0; if(!mod->enabled && _ioppr_search_history_by_module(history_list, mod) == NULL) { can_move = 1; GList *modules1 = g_list_next(modules); if(modules1) { dt_iop_module_t *mod_next = (dt_iop_module_t *)(modules1->data); if(mod->iop_order != mod_next->iop_order) { mod->iop_order += (mod_next->iop_order - mod->iop_order) / 2.0; } else { dt_ioppr_check_duplicate_iop_order(&modules, history_list); reset_list = 1; } } else { mod->iop_order += 1.0; } } else if(!mod_prev->enabled && _ioppr_search_history_by_module(history_list, mod_prev) == NULL) { can_move = 1; GList *modules1 = g_list_previous(modules); if(modules1) modules1 = g_list_previous(modules1); if(modules1) { dt_iop_module_t *mod_next = (dt_iop_module_t *)(modules1->data); if(mod_prev->iop_order != mod_next->iop_order) { mod_prev->iop_order -= (mod_prev->iop_order - mod_next->iop_order) / 2.0; } else { can_move = 0; fprintf(stderr, "[dt_ioppr_check_duplicate_iop_order 1] modules %s %s(%d) and %s %s(%d) have the same iop_order\n", mod_prev->op, mod_prev->multi_name, mod_prev->iop_order, mod->op, mod->multi_name, mod->iop_order); } } else { mod_prev->iop_order -= 0.5; } } if(!can_move) { fprintf(stderr, "[dt_ioppr_check_duplicate_iop_order] modules %s %s(%d) and %s %s(%d) have the same iop_order\n", mod_prev->op, mod_prev->multi_name, mod_prev->iop_order, mod->op, mod->multi_name, mod->iop_order); } } if(reset_list) { modules = iop_list; if(modules) { mod_prev = (dt_iop_module_t *)(modules->data); modules = g_list_next(modules); } } else { mod_prev = mod; modules = g_list_next(modules); } } *_iop_list = iop_list; } // check if all so modules on iop_list have a iop_order defined in iop_order_list int dt_ioppr_check_so_iop_order(GList *iop_list, GList *iop_order_list) { int iop_order_missing = 0; // check if all the modules have their iop_order assigned for(const GList *modules = iop_list; modules; modules = g_list_next(modules)) { const dt_iop_module_so_t *const restrict mod = (dt_iop_module_so_t *)(modules->data); const dt_iop_order_entry_t *const restrict entry = dt_ioppr_get_iop_order_entry(iop_order_list, mod->op, 0); // mod->multi_priority); if(entry == NULL) { iop_order_missing = 1; fprintf(stderr, "[dt_ioppr_check_so_iop_order] missing iop_order for module %s\n", mod->op); } } return iop_order_missing; } static void *_dup_iop_order_entry(const void *src, gpointer data) { const dt_iop_order_entry_t *const restrict scr_entry = (dt_iop_order_entry_t *)src; dt_iop_order_entry_t *new_entry = malloc(sizeof(dt_iop_order_entry_t)); memcpy(new_entry, scr_entry, sizeof(dt_iop_order_entry_t)); return (void *)new_entry; } // returns a duplicate of iop_order_list GList *dt_ioppr_iop_order_copy_deep(GList *iop_order_list) { return (GList *)g_list_copy_deep(iop_order_list, _dup_iop_order_entry, NULL); } // helper to sort a GList of dt_iop_module_t by iop_order gint dt_sort_iop_by_order(gconstpointer a, gconstpointer b) { const dt_iop_module_t *const restrict am = (const dt_iop_module_t *)a; const dt_iop_module_t *const restrict bm = (const dt_iop_module_t *)b; if(am->iop_order > bm->iop_order) return 1; if(am->iop_order < bm->iop_order) return -1; return 0; } // if module can be placed before than module_next on the pipe // it returns the new iop_order // if it cannot be placed it returns -1.0 // this assumes that the order is always positive gboolean dt_ioppr_check_can_move_before_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_next) { if(module->flags() & IOP_FLAGS_FENCE) { return FALSE; } gboolean can_move = FALSE; // module is before on the pipe // move it up if(module->iop_order < module_next->iop_order) { // let's first search for module GList *modules = iop_list; for(; modules; modules = g_list_next(modules)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(mod == module) break; } // we found the module if(modules) { dt_iop_module_t *mod1 = NULL; dt_iop_module_t *mod2 = NULL; // now search for module_next and the one previous to that, so iop_order can be calculated // also check the rules for(modules = g_list_next(modules); modules; modules = g_list_next(modules)) { dt_iop_module_t *mod = (dt_iop_module_t *)modules->data; // if we reach module_next everything is OK if(mod == module_next) { mod2 = mod; break; } // check if module can be moved around this one if(mod->flags() & IOP_FLAGS_FENCE) { break; } // is there a rule about swapping this two? int rule_found = 0; for(const GList *rules = darktable.iop_order_rules; rules; rules = g_list_next(rules)) { const dt_iop_order_rule_t *const restrict rule = (dt_iop_order_rule_t *)rules->data; if(strcmp(module->op, rule->op_prev) == 0 && strcmp(mod->op, rule->op_next) == 0) { rule_found = 1; break; } } if(rule_found) break; mod1 = mod; } // we reach the module_next module if(mod2) { // this is already the previous module! if(module == mod1) { ; } else if(mod1->iop_order == mod2->iop_order) { fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] %s %s(%d) and %s %s(%d) have the same iop_order\n", mod1->op, mod1->multi_name, mod1->iop_order, mod2->op, mod2->multi_name, mod2->iop_order); } else { can_move = TRUE; } } } else fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] can't find module %s %s\n", module->op, module->multi_name); } // module is next on the pipe // move it down else if(module->iop_order > module_next->iop_order) { // let's first search for module GList *modules = g_list_last(iop_list); for(; modules; modules = g_list_previous(modules)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(mod == module) break; } // we found the module if(modules) { dt_iop_module_t *mod1 = NULL; dt_iop_module_t *mod2 = NULL; // now search for module_next and the one next to that, so iop_order can be calculated // also check the rules for(modules = g_list_previous(modules); modules; modules = g_list_previous(modules)) { dt_iop_module_t *mod = (dt_iop_module_t *)modules->data; // we reach the module next to module_next, everything is OK if(mod2 != NULL) { mod1 = mod; break; } // check for rules // check if module can be moved around this one if(mod->flags() & IOP_FLAGS_FENCE) { break; } // is there a rule about swapping this two? int rule_found = 0; for(const GList *rules = darktable.iop_order_rules; rules; rules = g_list_next(rules)) { const dt_iop_order_rule_t *const restrict rule = (dt_iop_order_rule_t *)rules->data; if(strcmp(mod->op, rule->op_prev) == 0 && strcmp(module->op, rule->op_next) == 0) { rule_found = 1; break; } } if(rule_found) break; if(mod == module_next) mod2 = mod; } // we reach the module_next module if(mod1) { // this is already the previous module! if(module == mod2) { ; } else if(mod1->iop_order == mod2->iop_order) { fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] %s %s(%d) and %s %s(%d) have the same iop_order\n", mod1->op, mod1->multi_name, mod1->iop_order, mod2->op, mod2->multi_name, mod2->iop_order); } else { can_move = TRUE; } } } else fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] can't find module %s %s\n", module->op, module->multi_name); } else { fprintf(stderr, "[dt_ioppr_get_iop_order_before_iop] modules %s %s(%d) and %s %s(%d) have the same iop_order\n", module->op, module->multi_name, module->iop_order, module_next->op, module_next->multi_name, module_next->iop_order); } return can_move; } // if module can be placed after than module_prev on the pipe // it returns the new iop_order // if it cannot be placed it returns -1.0 // this assumes that the order is always positive gboolean dt_ioppr_check_can_move_after_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_prev) { gboolean can_move = FALSE; // moving after module_prev is the same as moving before the very next one after module_prev dt_iop_module_t *module_next = NULL; for(const GList *modules = g_list_last(iop_list); modules; modules = g_list_previous(modules)) { dt_iop_module_t *mod = (dt_iop_module_t *)modules->data; if(mod == module_prev) break; module_next = mod; } if(module_next == NULL) { fprintf( stderr, "[dt_ioppr_get_iop_order_after_iop] can't find module previous to %s %s(%d) while moving %s %s(%d) after it\n", module_prev->op, module_prev->multi_name, module_prev->iop_order, module->op, module->multi_name, module->iop_order); } else can_move = dt_ioppr_check_can_move_before_iop(iop_list, module, module_next); return can_move; } // changes the module->iop_order so it comes before in the pipe than module_next // sort dev->iop to reflect the changes // return 1 if iop_order is changed, 0 otherwise gboolean dt_ioppr_move_iop_before(struct dt_develop_t *dev, dt_iop_module_t *module, dt_iop_module_t *module_next) { GList *next = dt_ioppr_get_iop_order_link(dev->iop_order_list, module_next->op, module_next->multi_priority); GList *current = dt_ioppr_get_iop_order_link(dev->iop_order_list, module->op, module->multi_priority); if(!next || !current) return FALSE; dev->iop_order_list = g_list_remove_link(dev->iop_order_list, current); dev->iop_order_list = g_list_insert_before(dev->iop_order_list, next, current->data); g_list_free(current); dt_ioppr_resync_modules_order(dev); return TRUE; } // changes the module->iop_order so it comes after in the pipe than module_prev // sort dev->iop to reflect the changes // return 1 if iop_order is changed, 0 otherwise gboolean dt_ioppr_move_iop_after(struct dt_develop_t *dev, dt_iop_module_t *module, dt_iop_module_t *module_prev) { GList *prev = dt_ioppr_get_iop_order_link(dev->iop_order_list, module_prev->op, module_prev->multi_priority); GList *current = dt_ioppr_get_iop_order_link(dev->iop_order_list, module->op, module->multi_priority); if(!prev || !current) return FALSE; dev->iop_order_list = g_list_remove_link(dev->iop_order_list, current); // we want insert after => so insert before the next item GList *next = g_list_next(prev); if(prev) dev->iop_order_list = g_list_insert_before(dev->iop_order_list, next, current->data); else dev->iop_order_list = g_list_append(dev->iop_order_list, current->data); g_list_free(current); dt_ioppr_resync_modules_order(dev); return TRUE; } //-------------------------------------------------------------------- // from here just for debug //-------------------------------------------------------------------- void dt_ioppr_print_module_iop_order(GList *iop_list, const char *msg) { for(const GList *modules = iop_list; modules; modules = g_list_next(modules)) { dt_iop_module_t *mod = (dt_iop_module_t *)(modules->data); fprintf(stderr, "[%s] module %s %s multi_priority=%i, iop_order=%d\n", msg, mod->op, mod->multi_name, mod->multi_priority, mod->iop_order); } } void dt_ioppr_print_history_iop_order(GList *history_list, const char *msg) { for(const GList *history = history_list; history; history = g_list_next(history)) { dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data); fprintf(stderr, "[%s] module %s %s multi_priority=%i, iop_order=%d\n", msg, hist->op_name, hist->multi_name, hist->multi_priority, hist->iop_order); } } void dt_ioppr_print_iop_order(GList *iop_order_list, const char *msg) { for(const GList *iops_order = iop_order_list; iops_order; iops_order = g_list_next(iops_order)) { dt_iop_order_entry_t *order_entry = (dt_iop_order_entry_t *)(iops_order->data); fprintf(stderr, "[%s] op %20s (inst %d) iop_order=%d\n", msg, order_entry->operation, order_entry->instance, order_entry->o.iop_order); } } static GList *_get_fence_modules_list(GList *iop_list) { GList *fences = NULL; for(const GList *modules = iop_list; modules; modules = g_list_next(modules)) { dt_iop_module_t *mod = (dt_iop_module_t *)modules->data; if(mod->flags() & IOP_FLAGS_FENCE) { fences = g_list_prepend(fences, mod); } } return g_list_reverse(fences); // list was built in reverse order, so un-reverse it } static void _ioppr_check_rules(GList *iop_list, const int imgid, const char *msg) { // check for IOP_FLAGS_FENCE on each module // create a list of fences modules GList *fences = _get_fence_modules_list(iop_list); // check if each module is between the fences for(const GList *modules = iop_list; modules; modules = g_list_next(modules)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(mod->iop_order == INT_MAX) { continue; } dt_iop_module_t *fence_prev = NULL; dt_iop_module_t *fence_next = NULL; for(const GList *mod_fences = fences; mod_fences; mod_fences = g_list_next(mod_fences)) { dt_iop_module_t *mod_fence = (dt_iop_module_t *)mod_fences->data; // mod should be before this fence if(mod->iop_order < mod_fence->iop_order) { if(fence_next == NULL) fence_next = mod_fence; else if(mod_fence->iop_order < fence_next->iop_order) fence_next = mod_fence; } // mod should be after this fence else if(mod->iop_order > mod_fence->iop_order) { if(fence_prev == NULL) fence_prev = mod_fence; else if(mod_fence->iop_order > fence_prev->iop_order) fence_prev = mod_fence; } } // now check if mod is between the fences if(fence_next && mod->iop_order > fence_next->iop_order) { fprintf(stderr, "[_ioppr_check_rules] found fence %s %s module %s %s(%d) is after %s %s(%d) image %i (%s)\n", fence_next->op, fence_next->multi_name, mod->op, mod->multi_name, mod->iop_order, fence_next->op, fence_next->multi_name, fence_next->iop_order, imgid, msg); } if(fence_prev && mod->iop_order < fence_prev->iop_order) { fprintf(stderr, "[_ioppr_check_rules] found fence %s %s module %s %s(%d) is before %s %s(%d) image %i (%s)\n", fence_prev->op, fence_prev->multi_name, mod->op, mod->multi_name, mod->iop_order, fence_prev->op, fence_prev->multi_name, fence_prev->iop_order, imgid, msg); } } // for each module check if it doesn't break a rule for(const GList *modules = iop_list; modules; modules = g_list_next(modules)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(mod->iop_order == INT_MAX) { continue; } // we have a module, now check each rule for(const GList *rules = darktable.iop_order_rules; rules; rules = g_list_next(rules)) { const dt_iop_order_rule_t *const restrict rule = (dt_iop_order_rule_t *)rules->data; // mod must be before rule->op_next if(strcmp(mod->op, rule->op_prev) == 0) { // check if there's a rule->op_next module before mod for(const GList *modules_prev = g_list_previous(modules); modules_prev; modules_prev = g_list_previous(modules_prev)) { const dt_iop_module_t *const restrict mod_prev = (dt_iop_module_t *)modules_prev->data; if(strcmp(mod_prev->op, rule->op_next) == 0) { fprintf(stderr, "[_ioppr_check_rules] found rule %s %s module %s %s(%d) is after %s %s(%d) image %i (%s)\n", rule->op_prev, rule->op_next, mod->op, mod->multi_name, mod->iop_order, mod_prev->op, mod_prev->multi_name, mod_prev->iop_order, imgid, msg); } } } // mod must be after rule->op_prev else if(strcmp(mod->op, rule->op_next) == 0) { // check if there's a rule->op_prev module after mod for(const GList *modules_next = g_list_next(modules); modules_next; modules_next = g_list_next(modules_next)) { const dt_iop_module_t *const restrict mod_next = (dt_iop_module_t *)modules_next->data; if(strcmp(mod_next->op, rule->op_prev) == 0) { fprintf(stderr, "[_ioppr_check_rules] found rule %s %s module %s %s(%d) is before %s %s(%d) image %i (%s)\n", rule->op_prev, rule->op_next, mod->op, mod->multi_name, mod->iop_order, mod_next->op, mod_next->multi_name, mod_next->iop_order, imgid, msg); } } } } } if(fences) g_list_free(fences); } void dt_ioppr_insert_module_instance(struct dt_develop_t *dev, dt_iop_module_t *module) { const char *operation = module->op; const int32_t instance = module->multi_priority; dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t)); g_strlcpy(entry->operation, operation, sizeof(entry->operation)); entry->instance = instance; entry->o.iop_order = 0; GList *place = NULL; int max_instance = -1; for(GList *l = dev->iop_order_list; l; l = g_list_next(l)) { const dt_iop_order_entry_t *const restrict e = (dt_iop_order_entry_t *)l->data; if(!strcmp(e->operation, operation) && e->instance > max_instance) { place = l; max_instance = e->instance; } } dev->iop_order_list = g_list_insert_before(dev->iop_order_list, place, entry); } int dt_ioppr_check_iop_order(dt_develop_t *dev, const int imgid, const char *msg) { int iop_order_ok = 1; // check if gamma is the last iop { GList *modules; for(modules = g_list_last(dev->iop); modules; modules = g_list_previous(dev->iop)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(mod->iop_order != INT_MAX) break; } if(modules) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(strcmp(mod->op, "gamma") != 0) { iop_order_ok = 0; fprintf(stderr, "[dt_ioppr_check_iop_order] gamma is not the last iop, last is %s %s(%d) image %i (%s)\n", mod->op, mod->multi_name, mod->iop_order,imgid, msg); } } else { // fprintf(stderr, "[dt_ioppr_check_iop_order] dev->iop is empty image %i (%s)\n",imgid, msg); } } // some other checks { for(const GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(dev->iop)) { const dt_iop_module_t *const restrict mod = (dt_iop_module_t *)modules->data; if(!mod->default_enabled && mod->iop_order != INT_MAX) { if(mod->enabled) { iop_order_ok = 0; fprintf(stderr, "[dt_ioppr_check_iop_order] module not used but enabled!! %s %s(%d) image %i (%s)\n", mod->op, mod->multi_name, mod->iop_order,imgid, msg); } if(mod->multi_priority == 0) { iop_order_ok = 0; fprintf(stderr, "[dt_ioppr_check_iop_order] base module set as not used %s %s(%d) image %i (%s)\n", mod->op, mod->multi_name, mod->iop_order,imgid, msg); } } } } // check if there's duplicate or out-of-order iop_order { dt_iop_module_t *mod_prev = NULL; for(const GList *modules = dev->iop; modules; modules = g_list_next(modules)) { dt_iop_module_t *mod = (dt_iop_module_t *)modules->data; if(mod->iop_order != INT_MAX) { if(mod_prev) { if(mod->iop_order < mod_prev->iop_order) { iop_order_ok = 0; fprintf(stderr, "[dt_ioppr_check_iop_order] module %s %s(%d) should be after %s %s(%d) image %i (%s)\n", mod->op, mod->multi_name, mod->iop_order, mod_prev->op, mod_prev->multi_name, mod_prev->iop_order, imgid, msg); } else if(mod->iop_order == mod_prev->iop_order) { iop_order_ok = 0; fprintf( stderr, "[dt_ioppr_check_iop_order] module %s %s(%i)(%d) and %s %s(%i)(%d) have the same order image %i (%s)\n", mod->op, mod->multi_name, mod->multi_priority, mod->iop_order, mod_prev->op, mod_prev->multi_name, mod_prev->multi_priority, mod_prev->iop_order, imgid, msg); } } } mod_prev = mod; } } _ioppr_check_rules(dev->iop, imgid, msg); for(const GList *history = dev->history; history; history = g_list_next(history)) { const dt_dev_history_item_t *const restrict hist = (dt_dev_history_item_t *)(history->data); if(hist->iop_order == INT_MAX) { if(hist->enabled) { iop_order_ok = 0; fprintf(stderr, "[dt_ioppr_check_iop_order] history module not used but enabled!! %s %s(%d) image %i (%s)\n", hist->op_name, hist->multi_name, hist->iop_order, imgid, msg); } if(hist->multi_priority == 0) { iop_order_ok = 0; fprintf(stderr, "[dt_ioppr_check_iop_order] history base module set as not used %s %s(%d) image %i (%s)\n", hist->op_name, hist->multi_name, hist->iop_order, imgid, msg); } } } return iop_order_ok; } void *dt_ioppr_serialize_iop_order_list(GList *iop_order_list, size_t *size) { g_return_val_if_fail(iop_order_list != NULL, NULL); g_return_val_if_fail(size != NULL, NULL); // compute size of all modules *size = 0; for(const GList *l = iop_order_list; l; l = g_list_next(l)) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; *size += strlen(entry->operation) + sizeof(int32_t) * 2; } if(*size == 0) return NULL; // allocate the parameter buffer char *params = (char *)malloc(*size); // set set preset iop-order version int pos = 0; for(const GList *l = iop_order_list; l; l = g_list_next(l)) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; // write the len of the module name const int32_t len = strlen(entry->operation); memcpy(params+pos, &len, sizeof(int32_t)); pos += sizeof(int32_t); // write the module name memcpy(params+pos, entry->operation, len); pos += len; // write the instance number memcpy(params+pos, &(entry->instance), sizeof(int32_t)); pos += sizeof(int32_t); } return params; } char *dt_ioppr_serialize_text_iop_order_list(GList *iop_order_list) { gchar *text = g_strdup(""); const GList *const last = g_list_last(iop_order_list); for(const GList *l = iop_order_list; l; l = g_list_next(l)) { const dt_iop_order_entry_t *const restrict entry = (dt_iop_order_entry_t *)l->data; gchar buf[64]; snprintf(buf, sizeof(buf), "%s,%d%s", entry->operation, entry->instance, (l == last) ? "" : ","); text = g_strconcat(text, buf, NULL); } return text; } /* this sanity check routine is used to correct wrong iop-list that * could have been stored while some bugs were present in * dartkable. There was a window around Sep 2019 where such issue * existed and some xmp may have been corrupt at this time making dt * crash while reimporting using the xmp. * * One common case seems that the list does not end with gamma. */ static gboolean _ioppr_sanity_check_iop_order(GList *list) { gboolean ok = TRUE; // First check that first module is rawprepare (even for a jpeg, we // are speaking of the module ordering not the activated modules. GList *first = g_list_first(list); dt_iop_order_entry_t *entry_first = (dt_iop_order_entry_t *)first->data; ok = ok && (g_strcmp0(entry_first->operation, "rawprepare") == 0); // Then check that last module is gamma GList *last = g_list_last(list); dt_iop_order_entry_t *entry_last = (dt_iop_order_entry_t *)last->data; ok = ok && (g_strcmp0(entry_last->operation, "gamma") == 0); return ok; } GList *dt_ioppr_deserialize_text_iop_order_list(const char *buf) { GList *iop_order_list = NULL; GList *list = dt_util_str_to_glist(",", buf); for(GList *l = list; l; l = g_list_next(l)) { dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t)); entry->o.iop_order = 0; // first operation name g_strlcpy(entry->operation, (char *)l->data, sizeof(entry->operation)); // then operation instance l = g_list_next(l); if(!l) goto error; const char *data = (char *)l->data; int inst = 0; sscanf(data, "%d", &inst); entry->instance = inst; // append to the list iop_order_list = g_list_prepend(iop_order_list, entry); } iop_order_list = g_list_reverse(iop_order_list); // list was built in reverse order, so un-reverse it g_list_free_full(list, g_free); _ioppr_reset_iop_order(iop_order_list); if(!_ioppr_sanity_check_iop_order(iop_order_list)) goto error; return iop_order_list; error: g_list_free_full(iop_order_list, free); return NULL; } GList *dt_ioppr_deserialize_iop_order_list(const char *buf, size_t size) { GList *iop_order_list = NULL; // parse all modules while(size) { dt_iop_order_entry_t *entry = (dt_iop_order_entry_t *)malloc(sizeof(dt_iop_order_entry_t)); entry->o.iop_order = 0; // get length of module name const int32_t len = *(int32_t *)buf; buf += sizeof(int32_t); if(len < 0 || len > 20) { free(entry); goto error; } // set module name memcpy(entry->operation, buf, len); *(entry->operation + len) = '\0'; buf += len; // get the instance number entry->instance = *(int32_t *)buf; buf += sizeof(int32_t); if(entry->instance < 0 || entry->instance > 1000) { free(entry); goto error; } // append to the list iop_order_list = g_list_prepend(iop_order_list, entry); size -= (2 * sizeof(int32_t) + len); } iop_order_list = g_list_reverse(iop_order_list); // list was built in reverse order, so un-reverse it _ioppr_reset_iop_order(iop_order_list); return iop_order_list; error: g_list_free_full(iop_order_list, free); return NULL; } #undef DT_IOP_ORDER_INFO