Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
tests/styles.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2026 Guillaume Stutin.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include "common/darktable.h"
20#include "common/database.h"
21#include "common/film.h"
24#include "common/image.h"
25#include "common/styles.h"
26
27#include <assert.h>
28#include <gio/gio.h>
29#include <glib/gstdio.h>
30#include <sqlite3.h>
31#include <stdio.h>
32#include <stdlib.h>
33
34#ifdef _WIN32
35#include "win/main_wrapper.h"
36#endif
37
38#ifndef ANSEL_TEST_SOURCE_DIR
39#define ANSEL_TEST_SOURCE_DIR "."
40#endif
41
42#ifndef ANSEL_TEST_BINARY_DIR
43#define ANSEL_TEST_BINARY_DIR "."
44#endif
45
60static char *test_image_dir = NULL;
61
75
76static int test_fail(const char *scenario, const char *message, char **failure_reason)
77{
78 fprintf(stderr, "[FAIL] %s: %s\n", scenario, message);
79 if(!IS_NULL_PTR(failure_reason) && IS_NULL_PTR(*failure_reason))
80 *failure_reason = g_strdup(message);
81 return 1;
82}
83
84static gboolean is_style_file(const char *filename)
85{
86 const char *extension = strrchr(filename, '.');
87 return !IS_NULL_PTR(extension) && !g_ascii_strcasecmp(extension, ".dtstyle");
88}
89
90static char *scenario_name_from_style_file(const char *filename)
91{
92 char *scenario = g_strdup(filename);
93 char *extension = strrchr(scenario, '.');
94 if(!IS_NULL_PTR(extension)) *extension = '\0';
95 return scenario;
96}
97
98static int sql_int_for_bound_images(const char *query, const int32_t imgid_a, const int32_t imgid_b)
99{
100 sqlite3_stmt *stmt = NULL;
101 sqlite3_prepare_v2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
102 sqlite3_bind_int(stmt, 1, imgid_a);
103 sqlite3_bind_int(stmt, 2, imgid_b);
104 const int value = (sqlite3_step(stmt) == SQLITE_ROW) ? sqlite3_column_int(stmt, 0) : -1;
105 sqlite3_finalize(stmt);
106 return value;
107}
108
109static int max_style_id(void)
110{
111 sqlite3_stmt *stmt = NULL;
112 sqlite3_prepare_v2(dt_database_get(darktable.db), "SELECT COALESCE(MAX(id), 0) FROM data.styles", -1,
113 &stmt, NULL);
114 const int value = (sqlite3_step(stmt) == SQLITE_ROW) ? sqlite3_column_int(stmt, 0) : 0;
115 sqlite3_finalize(stmt);
116 return value;
117}
118
119static char *imported_style_name(const int before_import_style_id)
120{
121 sqlite3_stmt *stmt = NULL;
122 sqlite3_prepare_v2(dt_database_get(darktable.db),
123 "SELECT name FROM data.styles WHERE id > ?1 ORDER BY id DESC LIMIT 1", -1, &stmt,
124 NULL);
125 sqlite3_bind_int(stmt, 1, before_import_style_id);
126
127 char *name = NULL;
128 if(sqlite3_step(stmt) == SQLITE_ROW)
129 name = g_strdup((const char *)sqlite3_column_text(stmt, 0));
130
131 sqlite3_finalize(stmt);
132 return name;
133}
134
135static int32_t create_test_image(const char *source_image_path)
136{
137 static int image_index = 0;
138 const char *extension = strrchr(source_image_path, '.');
139 char *filename = g_strdup_printf("style-test-%d%s", image_index++, extension ? extension : ".raw");
140 char *image_path = g_build_filename(test_image_dir, filename, NULL);
141
142 GFile *source_file = g_file_new_for_path(source_image_path);
143 GFile *image_file = g_file_new_for_path(image_path);
144 GError *error = NULL;
145 const gboolean copied = g_file_copy(source_file, image_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
146 g_object_unref(source_file);
147 g_object_unref(image_file);
148
149 if(!copied)
150 {
151 fprintf(stderr, "[FAIL] copy test image: %s\n", error ? error->message : "unknown error");
152 g_clear_error(&error);
153 dt_free(filename);
154 dt_free(image_path);
155 return 0;
156 }
157
158 dt_film_t film;
159 dt_film_init(&film);
160 if(dt_film_new(&film, test_image_dir) <= 0)
161 {
162 dt_film_cleanup(&film);
163 dt_free(filename);
164 dt_free(image_path);
165 return 0;
166 }
167
168 const int32_t imgid = dt_image_import(film.id, image_path, FALSE);
169
170 dt_film_cleanup(&film);
171 dt_free(filename);
172 dt_free(image_path);
173 return imgid;
174}
175
176static int load_xmp_on_image(const char *scenario, const int32_t imgid, const char *xmp_path,
177 char **failure_reason)
178{
179 if(!g_file_test(xmp_path, G_FILE_TEST_IS_REGULAR))
180 return test_fail(scenario, "missing XMP fixture", failure_reason);
181
182 return dt_history_load_and_apply(imgid, (gchar *)xmp_path, TRUE)
183 ? test_fail(scenario, "could not load XMP fixture", failure_reason)
184 : 0;
185}
186
187static int load_start_history(const char *scenario, const int32_t imgid, const char *start_xmp_path,
188 const char *start_xmp_name, char **failure_reason)
189{
190 if(g_file_test(start_xmp_path, G_FILE_TEST_IS_REGULAR))
191 return load_xmp_on_image(scenario, imgid, start_xmp_path, failure_reason);
192
193 printf("[NOTE] %s: no %s fixture, using imported base history\n", scenario, start_xmp_name);
194 return 0;
195}
196
197static void print_module_order_summary(const char *label, const int32_t imgid)
198{
199 sqlite3_stmt *stmt = NULL;
200 sqlite3_prepare_v2(dt_database_get(darktable.db),
201 "SELECT version, iop_list FROM main.module_order WHERE imgid=?1", -1, &stmt, NULL);
202 sqlite3_bind_int(stmt, 1, imgid);
203
204 if(sqlite3_step(stmt) == SQLITE_ROW)
205 {
206 const unsigned char *iop_list = sqlite3_column_text(stmt, 1);
207 fprintf(stderr, " %s pipe order for image %d:\n", label, imgid);
208 fprintf(stderr, " version=%d\n", sqlite3_column_int(stmt, 0));
209 fprintf(stderr, " iop_list=%s\n", iop_list ? (const char *)iop_list : "(null)");
210 }
211 else
212 {
213 fprintf(stderr, " %s pipe order for image %d: no module_order row\n", label, imgid);
214 }
215
216 sqlite3_finalize(stmt);
217}
218
219static int compare_pipe_order(const char *scenario, const int32_t actual_imgid, const int32_t expected_imgid,
220 char **failure_reason)
221{
222 const int diff = sql_int_for_bound_images(
223 "SELECT"
224 " (SELECT COUNT(*) FROM"
225 " (SELECT version, iop_list FROM main.module_order WHERE imgid=?1"
226 " EXCEPT"
227 " SELECT version, iop_list FROM main.module_order WHERE imgid=?2))"
228 " +"
229 " (SELECT COUNT(*) FROM"
230 " (SELECT version, iop_list FROM main.module_order WHERE imgid=?2"
231 " EXCEPT"
232 " SELECT version, iop_list FROM main.module_order WHERE imgid=?1))",
233 actual_imgid, expected_imgid);
234
235 if(!diff) return 0;
236
237 test_fail(scenario, "module order differs from expected XMP", failure_reason);
238 print_module_order_summary("actual", actual_imgid);
239 print_module_order_summary("expected", expected_imgid);
240 return 1;
241}
242
243static void print_enabled_state_summary(const char *label, const int32_t imgid)
244{
245 fprintf(stderr, " %s enabled state for image %d:\n", label, imgid);
246
247 sqlite3_stmt *stmt = NULL;
248 sqlite3_prepare_v2(
250 "SELECT h.operation, h.multi_priority, IFNULL(h.multi_name, ''), h.enabled"
251 " FROM main.history h"
252 " WHERE h.imgid=?1"
253 " AND h.num < (SELECT history_end FROM main.images WHERE id=?1)"
254 " AND h.num = (SELECT MAX(h2.num)"
255 " FROM main.history h2"
256 " WHERE h2.imgid=?1"
257 " AND h2.operation=h.operation"
258 " AND h2.multi_priority=h.multi_priority"
259 " AND h2.num < (SELECT history_end FROM main.images WHERE id=?1))"
260 " ORDER BY h.operation, h.multi_priority",
261 -1, &stmt, NULL);
262 sqlite3_bind_int(stmt, 1, imgid);
263
264 while(sqlite3_step(stmt) == SQLITE_ROW)
265 {
266 const unsigned char *operation = sqlite3_column_text(stmt, 0);
267 const unsigned char *multi_name = sqlite3_column_text(stmt, 2);
268 fprintf(stderr, " %-24s priority=%d name='%s' enabled=%d\n",
269 operation ? (const char *)operation : "", sqlite3_column_int(stmt, 1),
270 multi_name ? (const char *)multi_name : "", sqlite3_column_int(stmt, 3));
271 }
272
273 sqlite3_finalize(stmt);
274}
275
276static int compare_enabled_state(const char *scenario, const int32_t actual_imgid,
277 const int32_t expected_imgid, char **failure_reason)
278{
279 const int diff = sql_int_for_bound_images(
280 "WITH actual AS ("
281 " SELECT h.operation, h.multi_priority, IFNULL(h.multi_name, '') AS multi_name, h.enabled"
282 " FROM main.history h"
283 " WHERE h.imgid=?1"
284 " AND h.num < (SELECT history_end FROM main.images WHERE id=?1)"
285 " AND h.num = (SELECT MAX(h2.num)"
286 " FROM main.history h2"
287 " WHERE h2.imgid=?1"
288 " AND h2.operation=h.operation"
289 " AND h2.multi_priority=h.multi_priority"
290 " AND h2.num < (SELECT history_end FROM main.images WHERE id=?1))"
291 "), expected AS ("
292 " SELECT h.operation, h.multi_priority, IFNULL(h.multi_name, '') AS multi_name, h.enabled"
293 " FROM main.history h"
294 " WHERE h.imgid=?2"
295 " AND h.num < (SELECT history_end FROM main.images WHERE id=?2)"
296 " AND h.num = (SELECT MAX(h2.num)"
297 " FROM main.history h2"
298 " WHERE h2.imgid=?2"
299 " AND h2.operation=h.operation"
300 " AND h2.multi_priority=h.multi_priority"
301 " AND h2.num < (SELECT history_end FROM main.images WHERE id=?2))"
302 ")"
303 "SELECT"
304 " (SELECT COUNT(*) FROM (SELECT * FROM actual EXCEPT SELECT * FROM expected))"
305 " +"
306 " (SELECT COUNT(*) FROM (SELECT * FROM expected EXCEPT SELECT * FROM actual))",
307 actual_imgid, expected_imgid);
308
309 if(!diff) return 0;
310
311 test_fail(scenario, "final enabled states differ from expected XMP", failure_reason);
312 print_enabled_state_summary("actual", actual_imgid);
313 print_enabled_state_summary("expected", expected_imgid);
314 return 1;
315}
316
317static int apply_style_to_image(const char *scenario, const char *style_name, const int32_t imgid,
318 char **failure_reason)
319{
320 const int style_id = dt_styles_get_id_by_name(style_name);
321 if(style_id == 0)
322 return test_fail(scenario, "imported style is missing from the database", failure_reason);
323
324 dt_conf_set_bool("history/copy_iop_order", FALSE);
325 dt_conf_set_bool("history/paste_instances", TRUE);
326
327 return dt_styles_apply_to_image_merge(style_name, style_id, imgid, DT_HISTORY_MERGE_APPEND)
328 ? test_fail(scenario, "style application failed", failure_reason)
329 : 0;
330}
331
332static int run_style_scenario(const char *scenario_dir, const char *source_image_path, const char *style_file,
333 char **failure_reason)
334{
335 char *scenario = scenario_name_from_style_file(style_file);
336 char *style_path = g_build_filename(scenario_dir, style_file, NULL);
337 char *start_xmp_name = g_strdup_printf("start_%s.xmp", scenario);
338 char *end_xmp_name = g_strdup_printf("end_%s.xmp", scenario);
339 char *start_xmp_path = g_build_filename(scenario_dir, start_xmp_name, NULL);
340 char *end_xmp_path = g_build_filename(scenario_dir, end_xmp_name, NULL);
341
342 printf("\n[STEP] %s\n", scenario);
343
344 int result = 1;
345 const int before_import_style_id = max_style_id();
346 dt_styles_import_from_file(style_path);
347 char *style_name = imported_style_name(before_import_style_id);
348 if(IS_NULL_PTR(style_name)) style_name = g_strdup(scenario);
349
350 const int32_t actual_imgid = create_test_image(source_image_path);
351 const int32_t expected_imgid = create_test_image(source_image_path);
352
353 if(actual_imgid <= 0 || expected_imgid <= 0)
354 {
355 test_fail(scenario, "could not import test image", failure_reason);
356 goto end;
357 }
358
359 if(load_start_history(scenario, actual_imgid, start_xmp_path, start_xmp_name, failure_reason)) goto end;
360 if(apply_style_to_image(scenario, style_name, actual_imgid, failure_reason)) goto end;
361 if(load_xmp_on_image(scenario, expected_imgid, end_xmp_path, failure_reason)) goto end;
362 if(compare_pipe_order(scenario, actual_imgid, expected_imgid, failure_reason)) goto end;
363 if(compare_enabled_state(scenario, actual_imgid, expected_imgid, failure_reason)) goto end;
364
365 printf("[OK] %s\n", scenario);
366 result = 0;
367
368end:
369 dt_free(style_name);
370 dt_free(scenario);
371 dt_free(style_path);
372 dt_free(start_xmp_name);
373 dt_free(end_xmp_name);
374 dt_free(start_xmp_path);
375 dt_free(end_xmp_path);
376 return result;
377}
378
379static int run_style_scenarios(const char *scenario_dir, const char *source_image_path)
380{
381 GError *error = NULL;
382 GDir *dir = g_dir_open(scenario_dir, 0, &error);
383 if(IS_NULL_PTR(dir))
384 {
385 fprintf(stderr, "[FAIL] cannot open style scenario folder `%s`: %s\n", scenario_dir,
386 error ? error->message : "unknown error");
387 g_clear_error(&error);
388 return 1;
389 }
390
391 GList *style_files = NULL;
392 const char *entry = NULL;
393 while((entry = g_dir_read_name(dir)) != NULL)
394 {
395 if(is_style_file(entry)) style_files = g_list_prepend(style_files, g_strdup(entry));
396 }
397 g_dir_close(dir);
398
399 style_files = g_list_sort(style_files, (GCompareFunc)g_strcmp0);
400 if(IS_NULL_PTR(style_files))
401 {
402 printf("[SKIP] no .dtstyle file found in %s\n", scenario_dir);
403 return 0;
404 }
405
406 int result = 0;
407 GList *summaries = NULL;
408 for(GList *l = style_files; l; l = g_list_next(l))
409 {
410 const char *style_file = (const char *)l->data;
411 dt_style_scenario_result_t *summary = g_malloc0(sizeof(*summary));
412 summary->style_file = g_strdup(style_file);
413 summary->result = run_style_scenario(scenario_dir, source_image_path, style_file, &summary->reason);
414 if(summary->result && IS_NULL_PTR(summary->reason))
415 summary->reason = g_strdup("unknown failure");
416
417 summaries = g_list_prepend(summaries, summary);
418 result |= summary->result;
419 }
420
421 summaries = g_list_reverse(summaries);
422 printf("\n[SUMMARY]\n");
423 for(GList *l = summaries; l; l = g_list_next(l))
424 {
426 printf("%s: %s", summary->style_file, summary->result ? "FAILED" : "PASSED");
427 if(summary->result)
428 printf(" - %s", summary->reason);
429 printf("\n");
430 }
431
432 for(GList *l = summaries; l; l = g_list_next(l))
433 {
435 dt_free(summary->style_file);
436 dt_free(summary->reason);
437 dt_free(summary);
438 }
439 g_list_free(summaries);
440 g_list_free_full(style_files, dt_free_gpointer);
441 return result;
442}
443
444static char *prepare_test_datadir(const char *tmp_dir)
445{
446 char *datadir = g_build_filename(tmp_dir, "data", NULL);
447 char *rawspeed_dir = g_build_filename(datadir, "rawspeed", NULL);
448 char *rawspeed_xml = g_build_filename(rawspeed_dir, "cameras.xml", NULL);
449 char *rawspeed_source_xml = g_build_filename(ANSEL_TEST_SOURCE_DIR, "src", "external", "rawspeed", "data",
450 "cameras.xml", NULL);
451
452 g_mkdir(datadir, 0700);
453 g_mkdir(rawspeed_dir, 0700);
454
455 GError *error = NULL;
456 GFile *source_file = g_file_new_for_path(rawspeed_source_xml);
457 GFile *dest_file = g_file_new_for_path(rawspeed_xml);
458 const gboolean copied = g_file_copy(source_file, dest_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
459 g_object_unref(source_file);
460 g_object_unref(dest_file);
461
462 if(!copied)
463 {
464 fprintf(stderr, "[FAIL] copy rawspeed cameras.xml: %s\n", error ? error->message : "unknown error");
465 g_clear_error(&error);
466 dt_free(datadir);
467 datadir = NULL;
468 }
469
470 dt_free(rawspeed_dir);
471 dt_free(rawspeed_xml);
472 dt_free(rawspeed_source_xml);
473 return datadir;
474}
475
476int main(int argc, char *argv[])
477{
478 const gboolean explicit_scenario_dir = argc > 1;
479 char *default_scenario_dir = g_build_filename(ANSEL_TEST_SOURCE_DIR, "tests", "styles", NULL);
480 char *default_source_image = g_build_filename(ANSEL_TEST_SOURCE_DIR, "tests", "integration", "images",
481 "mire1.cr2", NULL);
482 const char *scenario_dir = explicit_scenario_dir ? argv[1] : default_scenario_dir;
483 const char *source_image_path = (argc > 2) ? argv[2] : default_source_image;
484
485 if(!g_file_test(scenario_dir, G_FILE_TEST_IS_DIR))
486 {
487 fprintf(stderr, explicit_scenario_dir ? "[FAIL] style scenario folder does not exist: %s\n"
488 : "[SKIP] style scenario folder does not exist: %s\n",
489 scenario_dir);
490 dt_free(default_scenario_dir);
491 dt_free(default_source_image);
492 return explicit_scenario_dir ? 1 : 0;
493 }
494
495 if(!g_file_test(source_image_path, G_FILE_TEST_IS_REGULAR))
496 {
497 fprintf(stderr, "[FAIL] test image does not exist: %s\n", source_image_path);
498 dt_free(default_scenario_dir);
499 dt_free(default_source_image);
500 return 1;
501 }
502
503 char *config_dir = g_strdup_printf("%s/ansel-test-styles-config-XXXXXX", g_get_tmp_dir());
504 char *cache_dir = g_strdup_printf("%s/ansel-test-styles-cache-XXXXXX", g_get_tmp_dir());
505 char *tmp_dir = g_strdup_printf("%s/ansel-test-styles-tmp-XXXXXX", g_get_tmp_dir());
506
507 assert(!IS_NULL_PTR(g_mkdtemp(config_dir)));
508 assert(!IS_NULL_PTR(g_mkdtemp(cache_dir)));
509 assert(!IS_NULL_PTR(g_mkdtemp(tmp_dir)));
510 test_image_dir = tmp_dir;
511
512 char *test_datadir = prepare_test_datadir(tmp_dir);
513 if(IS_NULL_PTR(test_datadir))
514 {
515 dt_free(default_scenario_dir);
516 dt_free(default_source_image);
517 dt_free(config_dir);
518 dt_free(cache_dir);
519 dt_free(tmp_dir);
520 return 1;
521 }
522
523 char *noiseprofiles = g_build_filename(ANSEL_TEST_SOURCE_DIR, "data", "noiseprofiles.json", NULL);
524 char *argv_override[] = {
525 "ansel-test-styles",
526 "--library", ":memory:",
527 "--datadir", test_datadir,
528 "--noiseprofiles", noiseprofiles,
529 "--moduledir", ANSEL_TEST_BINARY_DIR "/lib/ansel",
530 "--configdir", config_dir,
531 "--cachedir", cache_dir,
532 "--tmpdir", tmp_dir,
533 "--disable-opencl",
534 "--conf", "write_sidecar_files=FALSE",
535 "--conf", "plugins/lighttable/export/force_lcms2=FALSE",
536 "-t", "1",
537 NULL
538 };
539 int argc_override = sizeof(argv_override) / sizeof(*argv_override) - 1;
540
541 if(dt_init(argc_override, argv_override, FALSE, FALSE)) exit(1);
542
543 const int result = run_style_scenarios(scenario_dir, source_image_path);
544
545 dt_cleanup();
546 dt_free(noiseprofiles);
547 dt_free(test_datadir);
548 dt_free(default_scenario_dir);
549 dt_free(default_source_image);
550 dt_free(config_dir);
551 dt_free(cache_dir);
552 dt_free(tmp_dir);
553
554 return result;
555}
556
557// clang-format off
558// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
559// vim: shiftwidth=2 expandtab tabstop=2 cindent
560// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
561// 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
const char * extension(dt_imageio_module_data_t *data)
Definition avif.c:645
int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
char * name
void dt_conf_set_bool(const char *name, int val)
void dt_cleanup()
Definition darktable.c:1311
darktable_t darktable
Definition darktable.c:181
int dt_init(int argc, char *argv[], const gboolean init_gui, const gboolean load_data)
Definition darktable.c:451
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#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 darktable.h:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
void dt_film_init(dt_film_t *film)
Definition film.c:70
int dt_film_new(dt_film_t *film, const char *directory)
Definition film.c:161
void dt_film_cleanup(dt_film_t *film)
Definition film.c:80
int dt_history_load_and_apply(const int32_t imgid, gchar *filename, int history_only)
@ DT_HISTORY_MERGE_APPEND
int main()
Definition prova.c:47
int dt_styles_apply_to_image_merge(const char *name, const int style_id, const int32_t newimgid, const dt_history_merge_strategy_t mode, dt_hm_batch_state_t *batch)
int32_t dt_styles_get_id_by_name(const char *name)
void dt_styles_import_from_file(const char *style_path)
const struct dt_database_t * db
Definition darktable.h:779
int32_t id
Definition film.h:45
One-line scenario result printed after every style has run.
static int test_fail(const char *scenario, const char *message, char **failure_reason)
static int sql_int_for_bound_images(const char *query, const int32_t imgid_a, const int32_t imgid_b)
static char * prepare_test_datadir(const char *tmp_dir)
static int load_start_history(const char *scenario, const int32_t imgid, const char *start_xmp_path, const char *start_xmp_name, char **failure_reason)
static char * imported_style_name(const int before_import_style_id)
static char * test_image_dir
Run style-application scenarios from tests/styles.
static int compare_enabled_state(const char *scenario, const int32_t actual_imgid, const int32_t expected_imgid, char **failure_reason)
static int run_style_scenarios(const char *scenario_dir, const char *source_image_path)
#define ANSEL_TEST_SOURCE_DIR
static int32_t create_test_image(const char *source_image_path)
static int max_style_id(void)
static char * scenario_name_from_style_file(const char *filename)
static int compare_pipe_order(const char *scenario, const int32_t actual_imgid, const int32_t expected_imgid, char **failure_reason)
static int apply_style_to_image(const char *scenario, const char *style_name, const int32_t imgid, char **failure_reason)
static gboolean is_style_file(const char *filename)
static int run_style_scenario(const char *scenario_dir, const char *source_image_path, const char *style_file, char **failure_reason)
static int load_xmp_on_image(const char *scenario, const int32_t imgid, const char *xmp_path, char **failure_reason)
static void print_module_order_summary(const char *label, const int32_t imgid)
static void print_enabled_state_summary(const char *label, const int32_t imgid)
#define ANSEL_TEST_BINARY_DIR