odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

bl_test_context_baseapp.cpp (19186B)


      1 // This file is part of Blend2D project <https://blend2d.com>
      2 //
      3 // See blend2d.h or LICENSE.md for license and copyright information
      4 // SPDX-License-Identifier: Zlib
      5 
      6 #include <blend2d.h>
      7 
      8 #include "bl_test_context_baseapp.h"
      9 
     10 #include "../commons/imagediff.h"
     11 #include "../resources/abeezee_regular_ttf.h"
     12 
     13 namespace ContextTests {
     14 
     15 BaseTestApp::BaseTestApp()
     16   : default_options{make_default_options()} {}
     17 
     18 BaseTestApp::~BaseTestApp() {}
     19 
     20 TestOptions BaseTestApp::make_default_options() {
     21   TestOptions opt {};
     22   opt.width = 513;
     23   opt.height = 513;
     24   opt.format = BL_FORMAT_PRGB32;
     25   opt.count = 1000;
     26   opt.thread_count = 0;
     27   opt.seed = 1;
     28   opt.style_id = StyleId::kRandom;
     29   opt.comp_op = CompOp::kRandom;
     30   opt.opacity_op = OpacityOp::kRandom;
     31   opt.command = CommandId::kAll;
     32   opt.font = "built-in";
     33   opt.font_size = 20;
     34   opt.face_index = 0;
     35   opt.quiet = false;
     36   opt.flush_sync = false;
     37   opt.store_images = false;
     38   return opt;
     39 }
     40 
     41 bool BaseTestApp::parse_common_options(const CmdLine& cmd_line) {
     42   using namespace StringUtils;
     43 
     44   options.width = cmd_line.value_as_uint("--width", default_options.width);
     45   options.height = cmd_line.value_as_uint("--height", default_options.height);
     46   options.count = cmd_line.value_as_uint("--count", default_options.count);
     47   options.seed = cmd_line.value_as_uint("--seed", default_options.seed);
     48   options.style_id = parse_style_id(cmd_line.value_of("--style", style_id_to_string(default_options.style_id)));
     49   options.style_op = parse_style_op(cmd_line.value_of("--style-op", style_op_to_string(default_options.style_op)));
     50   options.comp_op = parse_comp_op(cmd_line.value_of("--comp-op", comp_op_to_string(default_options.comp_op)));
     51   options.opacity_op = parse_opacity_op(cmd_line.value_of("--opacity-op", opacity_op_to_string(default_options.opacity_op)));
     52   options.command = parse_command_id(cmd_line.value_of("--command", command_id_to_string(default_options.command)));
     53   options.font = cmd_line.value_of("--font", "built-in");
     54   options.font_size = cmd_line.value_as_uint("--font-size", default_options.font_size);
     55   options.face_index = cmd_line.value_as_uint("--face-index", default_options.face_index);
     56   options.quiet = cmd_line.has_arg("--quiet") || default_options.quiet;
     57   options.store_images = cmd_line.has_arg("--store") || default_options.store_images;
     58 
     59   const char* format_string = cmd_line.value_of("--format", "all");
     60   options.format = parse_format(format_string);
     61 
     62   bool format_valid = options.format != BL_FORMAT_NONE;
     63 
     64   if (!format_valid && StringUtils::strieq(format_string, "all")) {
     65     format_valid = true;
     66   }
     67 
     68   if (!format_valid ||
     69       options.command == CommandId::kUnknown ||
     70       options.style_id == StyleId::kUnknown ||
     71       options.comp_op == CompOp::kUnknown ||
     72       options.opacity_op == OpacityOp::kUnknown
     73   ) {
     74     printf("Failed to process command line arguments:\n");
     75 
     76     if (!format_valid)
     77       printf("  Unknown format '%s' - please use --help to list all available pixel formats\n", cmd_line.value_of("--format", ""));
     78 
     79     if (options.comp_op == CompOp::kUnknown)
     80       printf("  Unknown comp_op '%s' - please use --help to list all available operators\n", cmd_line.value_of("--comp-op", ""));
     81 
     82     if (options.opacity_op == OpacityOp::kUnknown)
     83       printf("  Unknown opacity_op '%s' - please use --help to list all available options\n", cmd_line.value_of("--opacity-op", ""));
     84 
     85     if (options.style_id == StyleId::kUnknown)
     86       printf("  Unknown style '%s' - please use --help to list all available styles\n", cmd_line.value_of("--style", ""));
     87 
     88     if (options.style_op == StyleOp::kUnknown)
     89       printf("  Unknown style-op '%s' - please use --help to list all available style options\n", cmd_line.value_of("--style-op", ""));
     90 
     91     if (options.command == CommandId::kUnknown)
     92       printf("  Unknown command '%s' - please use --help to list all available commands\n", cmd_line.value_of("--command", ""));
     93 
     94     return false;
     95   }
     96 
     97   if (strieq(options.font, "built-in")) {
     98     BLResult result = font_data.create_from_data(
     99       resource_abeezee_regular_ttf,
    100       sizeof(resource_abeezee_regular_ttf));
    101 
    102     if (result != BL_SUCCESS) {
    103       printf("Failed to load built-in font (result=0x%08X)\n", result);
    104       return false;
    105     }
    106   }
    107   else {
    108     BLResult result = font_data.create_from_file(options.font);
    109     if (result != BL_SUCCESS) {
    110       printf("Failed to load font %s (result=0x%08X)\n", options.font, result);
    111       return false;
    112     }
    113   }
    114 
    115   // Add all choices into the lists that are iterated during testing.
    116   if (options.format == BL_FORMAT_NONE) {
    117     test_cases.format_ids.push_back(BL_FORMAT_PRGB32);
    118     test_cases.format_ids.push_back(BL_FORMAT_A8);
    119   }
    120   else {
    121     test_cases.format_ids.push_back(options.format);
    122   }
    123 
    124   if (options.command == CommandId::kAll) {
    125     for (uint32_t i = 0; i < uint32_t(CommandId::kAll); i++) {
    126       test_cases.command_ids.push_back(CommandId(i));
    127     }
    128   }
    129   else {
    130     test_cases.command_ids.push_back(options.command);
    131   }
    132 
    133   if (options.style_id >= StyleId::kRandom) {
    134     if (options.style_id == StyleId::kRandomStable || options.style_id == StyleId::kAllStable) {
    135       test_cases.style_ids.push_back(StyleId::kSolid);
    136       test_cases.style_ids.push_back(StyleId::kSolidOpaque);
    137       test_cases.style_ids.push_back(StyleId::kGradientLinear);
    138       test_cases.style_ids.push_back(StyleId::kGradientLinearDither);
    139       test_cases.style_ids.push_back(StyleId::kPatternAligned);
    140       test_cases.style_ids.push_back(StyleId::kPatternFx);
    141       test_cases.style_ids.push_back(StyleId::kPatternFy);
    142       test_cases.style_ids.push_back(StyleId::kPatternFxFy);
    143       test_cases.style_ids.push_back(StyleId::kPatternAffineNearest);
    144       test_cases.style_ids.push_back(StyleId::kPatternAffineBilinear);
    145     }
    146     else if (options.style_id == StyleId::kRandomUnstable || options.style_id == StyleId::kAllUnstable) {
    147       test_cases.style_ids.push_back(StyleId::kGradientRadial);
    148       test_cases.style_ids.push_back(StyleId::kGradientRadialDither);
    149       test_cases.style_ids.push_back(StyleId::kGradientConic);
    150       test_cases.style_ids.push_back(StyleId::kGradientConicDither);
    151     }
    152     else {
    153       for (uint32_t i = 0; i < uint32_t(StyleId::kRandom); i++) {
    154         test_cases.style_ids.push_back(StyleId(i));
    155       }
    156     }
    157   }
    158   else {
    159     test_cases.style_ids.push_back(options.style_id);
    160   }
    161 
    162   if (options.style_op >= StyleOp::kRandom) {
    163     for (uint32_t i = 0; i < uint32_t(StyleOp::kRandom); i++) {
    164       test_cases.style_ops.push_back(StyleOp(i));
    165     }
    166   }
    167   else {
    168     test_cases.style_ops.push_back(options.style_op);
    169   }
    170 
    171   if (options.comp_op >= CompOp::kRandom) {
    172     for (uint32_t i = 0; i < uint32_t(CompOp::kRandom); i++) {
    173       test_cases.comp_ops.push_back(CompOp(i));
    174     }
    175   }
    176   else {
    177     test_cases.comp_ops.push_back(options.comp_op);
    178   }
    179 
    180   if (options.opacity_op >= OpacityOp::kRandom) {
    181     for (uint32_t i = 0; i < uint32_t(OpacityOp::kRandom); i++) {
    182       test_cases.opacity_ops.push_back(OpacityOp(i));
    183     }
    184   }
    185   else {
    186     test_cases.opacity_ops.push_back(options.opacity_op);
    187   }
    188 
    189   return true;
    190 }
    191 
    192 void BaseTestApp::print_app_info(const char* title, bool quiet) const {
    193   printf("%s [use --help for command line options]\n", title);
    194 
    195   if (!quiet) {
    196     BLRuntimeBuildInfo build_info;
    197     BLRuntime::query_build_info(&build_info);
    198     printf("  Version    : %u.%u.%u\n"
    199            "  Build Type : %s\n"
    200            "  Compiled By: %s\n\n",
    201            build_info.major_version,
    202            build_info.minor_version,
    203            build_info.patch_version,
    204            build_info.build_type == BL_RUNTIME_BUILD_TYPE_DEBUG ? "Debug" : "Release",
    205            build_info.compiler_info);
    206   }
    207 
    208   fflush(stdout);
    209 }
    210 
    211 void BaseTestApp::print_common_options(const TestOptions& test_options) const {
    212   using namespace StringUtils;
    213 
    214   printf("Common test options:\n");
    215   printf("  --width=<uint>          - Image width                       [default=%u]\n", test_options.width);
    216   printf("  --height=<uint>         - Image height                      [default=%u]\n", test_options.height);
    217   printf("  --format=<string>       - Image pixel format                [default=%s]\n", format_to_string(test_options.format));
    218   printf("  --count=<uint>          - Count of render commands          [default=%u]\n", test_options.count);
    219   printf("  --seed=<uint>           - Random number generator seed      [default=%u]\n", test_options.seed);
    220   printf("  --style=<string>        - Style to render commands with     [default=%s]\n", style_id_to_string(test_options.style_id));
    221   printf("  --style-op=<string>     - Configure how to use styles       [default=%s]\n", style_op_to_string(test_options.style_op));
    222   printf("  --comp-op=<string>      - Composition operator              [default=%s]\n", comp_op_to_string(test_options.comp_op));
    223   printf("  --opacity-op=<string>   - Opacity option                    [default=%s]\n", opacity_op_to_string(test_options.opacity_op));
    224   printf("  --command=<string>      - Specify which command to run      [default=%s]\n", command_id_to_string(test_options.command));
    225   printf("  --font=<string>         - Specify which font to use         [default=%s]\n", test_options.font);
    226   printf("  --font-size=<uint>      - Font size                         [default=%u]\n", test_options.font_size);
    227   printf("  --face-index=<uint>     - Face index of a font collection   [default=%u]\n", test_options.face_index);
    228   printf("  --store                 - Write resulting images to files   [default=%s]\n", bool_to_string(test_options.store_images));
    229   printf("  --quiet                 - Don't write log unless necessary  [default=%s]\n", bool_to_string(test_options.quiet));
    230   printf("\n");
    231 }
    232 
    233 void BaseTestApp::print_formats() const {
    234   using namespace StringUtils;
    235 
    236   printf("List of pixel formats:\n");
    237   printf("  %-23s - Premultiplied 32-bit ARGB\n", format_to_string(BL_FORMAT_PRGB32));
    238   printf("  %-23s - 32-bit RGB (1 byte unused)\n", format_to_string(BL_FORMAT_XRGB32));
    239   printf("  %-23s - 8-bit alpha-only format\n", format_to_string(BL_FORMAT_A8));
    240   printf("\n");
    241 }
    242 
    243 void BaseTestApp::print_comp_ops() const {
    244   using namespace StringUtils;
    245 
    246   printf("List of composition operators:\n");
    247   printf("  %-23s - Source over\n", comp_op_to_string(CompOp::kSrcOver));
    248   printf("  %-23s - Source copy\n", comp_op_to_string(CompOp::kSrcCopy));
    249   printf("  %-23s - Random operator for every call\n", comp_op_to_string(CompOp::kRandom));
    250   printf("  %-23s - Tests all separately\n", comp_op_to_string(CompOp::kAll));
    251   printf("\n");
    252 }
    253 
    254 void BaseTestApp::print_opacity_ops() const {
    255   using namespace StringUtils;
    256 
    257   printf("List of opacity options:\n");
    258   printf("  %-23s - Opacity is set to fully opaque (1)\n", opacity_op_to_string(OpacityOp::kOpaque));
    259   printf("  %-23s - Opacity is semi-transparent (0..1)\n", opacity_op_to_string(OpacityOp::kSemi));
    260   printf("  %-23s - Opacity is always zero (fully transparent)\n", opacity_op_to_string(OpacityOp::kTransparent));
    261   printf("  %-23s - Random opacity for every call\n", opacity_op_to_string(OpacityOp::kRandom));
    262   printf("  %-23s - Tests all opacity options separately\n", opacity_op_to_string(OpacityOp::kAll));
    263   printf("\n");
    264 }
    265 
    266 void BaseTestApp::print_style_ids() const {
    267   using namespace StringUtils;
    268 
    269   printf("List of styles:\n");
    270   printf("  %-23s - Solid color\n", style_id_to_string(StyleId::kSolid));
    271   printf("  %-23s - Linear gradient\n", style_id_to_string(StyleId::kGradientLinear));
    272   printf("  %-23s - Linear gradient (dithered)\n", style_id_to_string(StyleId::kGradientLinearDither));
    273   printf("  %-23s - Radial gradient\n", style_id_to_string(StyleId::kGradientRadial));
    274   printf("  %-23s - Radial gradient (dithered)\n", style_id_to_string(StyleId::kGradientRadialDither));
    275   printf("  %-23s - Conic gradient\n", style_id_to_string(StyleId::kGradientConic));
    276   printf("  %-23s - Conic gradient (dithered)\n", style_id_to_string(StyleId::kGradientConicDither));
    277   printf("  %-23s - Pattern with aligned translation (no scaling)\n", style_id_to_string(StyleId::kPatternAligned));
    278   printf("  %-23s - Pattern with fractional x translation\n", style_id_to_string(StyleId::kPatternFx));
    279   printf("  %-23s - Pattern with fractional y translation\n", style_id_to_string(StyleId::kPatternFy));
    280   printf("  %-23s - Pattern with fractional x and y translation\n", style_id_to_string(StyleId::kPatternFxFy));
    281   printf("  %-23s - Pattern with affine transformation (nearest)\n", style_id_to_string(StyleId::kPatternAffineNearest));
    282   printf("  %-23s - Pattern with affine transformation (bilinear)\n", style_id_to_string(StyleId::kPatternAffineBilinear));
    283   printf("  %-23s - Random style for every render call\n", style_id_to_string(StyleId::kRandom));
    284   printf("  %-23s - Like 'random', but only styles that never require --max-diff\n", style_id_to_string(StyleId::kRandomStable));
    285   printf("  %-23s - Like 'random', but only styles that could require --max-diff\n", style_id_to_string(StyleId::kRandomUnstable));
    286   printf("  %-23s - Test all styles separately\n", style_id_to_string(StyleId::kAll));
    287   printf("  %-23s - Like 'all', but only styles that never require --max-diff\n", style_id_to_string(StyleId::kAllStable));
    288   printf("  %-23s - Like 'all', but only styles that could require --max-diff\n", style_id_to_string(StyleId::kAllUnstable));
    289   printf("\n");
    290 }
    291 
    292 void BaseTestApp::print_style_ops() const {
    293   using namespace StringUtils;
    294 
    295   printf("List of style options:\n");
    296   printf("  %-23s - Pass styles directly to render calls\n", style_op_to_string(StyleOp::kExplicit));
    297   printf("  %-23s - Use set_fill_style() and set_stroke_style()\n", style_op_to_string(StyleOp::kImplicit));
    298   printf("  %-23s - Random style option for every render call\n", style_op_to_string(StyleOp::kRandom));
    299   printf("  %-23s - Test all style options separately\n", style_op_to_string(StyleOp::kAll));
    300   printf("\n");
    301 }
    302 
    303 void BaseTestApp::print_commands() const {
    304   using namespace StringUtils;
    305 
    306   printf("List of commands:\n");
    307   printf("  %-23s - Fills aligned rectangles (int coordinates)\n", command_id_to_string(CommandId::kFillRectI));
    308   printf("  %-23s - Fills unaligned rectangles (float coordinates)\n", command_id_to_string(CommandId::kFillRectD));
    309   printf("  %-23s - Fills multiple rectangles (float coordinates)\n", command_id_to_string(CommandId::kFillMultipleRects));
    310   printf("  %-23s - Fills rounded rectangles\n", command_id_to_string(CommandId::kFillRound));
    311   printf("  %-23s - Fills triangles\n", command_id_to_string(CommandId::kFillTriangle));
    312   printf("  %-23s - Fills a path having quadratic curves\n", command_id_to_string(CommandId::kFillPathQuad));
    313   printf("  %-23s - Fills a path having cubic curves\n", command_id_to_string(CommandId::kFillPathCubic));
    314   printf("  %-23s - Fills text runs\n", command_id_to_string(CommandId::kFillText));
    315   printf("  %-23s - Strokes aligned rectangles (int coordinates)\n", command_id_to_string(CommandId::kStrokeRectI));
    316   printf("  %-23s - Strokes unaligned rectangles (float coordinates)\n", command_id_to_string(CommandId::kStrokeRectD));
    317   printf("  %-23s - Strokes multiple rectangles (float coordinates)\n", command_id_to_string(CommandId::kStrokeMultipleRects));
    318   printf("  %-23s - Strokes rounded rectangles\n", command_id_to_string(CommandId::kStrokeRound));
    319   printf("  %-23s - Strokes triangles\n", command_id_to_string(CommandId::kStrokeTriangle));
    320   printf("  %-23s - Strokes a path having quadratic curves\n", command_id_to_string(CommandId::kStrokePathQuad));
    321   printf("  %-23s - Strokes a path having cubic curves\n", command_id_to_string(CommandId::kStrokePathCubic));
    322   printf("  %-23s - Strokes text runs\n", command_id_to_string(CommandId::kStrokeText));
    323   printf("  %-23s - Test all commands separately\n", command_id_to_string(CommandId::kAll));
    324   printf("\n");
    325 }
    326 
    327 bool BaseTestApp::run_multiple(CommandId command_id, const TestInfo& info, ContextTester& a_tester, ContextTester& b_tester, uint32_t max_diff) {
    328   a_tester.clear();
    329   a_tester.seed(options.seed);
    330   a_tester.render(command_id, options.count, options);
    331 
    332   b_tester.clear();
    333   b_tester.seed(options.seed);
    334   b_tester.render(command_id, options.count, options);
    335 
    336   if (!check_output(info.id.data(), a_tester, b_tester, max_diff)) {
    337     find_problem(command_id, info, a_tester, b_tester, max_diff);
    338     return false;
    339   }
    340 
    341   return true;
    342 }
    343 
    344 void BaseTestApp::find_problem(CommandId command_id, const TestInfo& info, ContextTester& a_tester, ContextTester& b_tester, uint32_t max_diff) {
    345   // Do a binary search to find exactly the failing command.
    346   size_t base = 0;
    347   size_t size = options.count;
    348 
    349   if (options.quiet) {
    350     // Print the test name so we will know which test actually failed. This is
    351     // important especially on CI where we want to use quiet mode by default.
    352     printf("Testing [%s]\n", info.name.data());
    353   }
    354 
    355   printf("  Bisecting to match the problematic command...\n");
    356 
    357   while (size_t half = size / 2u) {
    358     size_t middle = base + half;
    359     size -= half;
    360 
    361     printf("  Verifying range [%zu %zu)\n", base, base + size);
    362 
    363     a_tester.clear();
    364     b_tester.clear();
    365 
    366     a_tester.seed(options.seed);
    367     b_tester.seed(options.seed);
    368 
    369     a_tester.render(command_id, base + size, options);
    370     b_tester.render(command_id, base + size, options);
    371 
    372     check_output(info.id.data(), a_tester, b_tester, max_diff);
    373 
    374     if (ImageUtils::diff_info(a_tester.image(), b_tester.image()).max_diff <= max_diff)
    375       base = middle;
    376   }
    377 
    378   printf("  Mismatch command index: %zu\n", base);
    379 
    380   a_tester.clear();
    381   b_tester.clear();
    382 
    383   a_tester.seed(options.seed);
    384   b_tester.seed(options.seed);
    385 
    386   if (base) {
    387     a_tester.render(command_id, base - 1, options);
    388     b_tester.render(command_id, base - 1, options);
    389   }
    390 
    391   a_tester.render(command_id, 1, options);
    392   b_tester.render(command_id, 1, options);
    393 
    394   check_output(info.id.data(), a_tester, b_tester, max_diff);
    395 }
    396 
    397 bool BaseTestApp::check_output(const char* test_id, const ContextTester& a_tester, const ContextTester& b_tester, uint32_t max_diff) {
    398   const BLImage& a_image = a_tester.image();
    399   const BLImage& b_image = b_tester.image();
    400 
    401   ImageUtils::DiffInfo diff_info = ImageUtils::diff_info(a_image, b_image);
    402   if (diff_info.max_diff <= max_diff)
    403     return true;
    404 
    405   mismatch_count++;
    406 
    407   BLString image_name;
    408   image_name.assign_format("%s-bug-%05llu", test_id, (unsigned long long)mismatch_count);
    409   printf("  Mismatch: %s (max_diff=%u cumulative=%llu)\n", image_name.data(), diff_info.max_diff, (unsigned long long)diff_info.cumulative_diff);
    410 
    411   if (options.store_images) {
    412     BLImage d = ImageUtils::diff_image(a_image, b_image);
    413     store_image(d, image_name.data(), "diff");
    414     store_image(a_image, image_name.data(), a_tester.prefix());
    415     store_image(b_image, image_name.data(), b_tester.prefix());
    416   }
    417 
    418   return false;
    419 }
    420 
    421 void BaseTestApp::store_image(const BLImage& image, const char* name, const char* suffix) const {
    422   BLString s;
    423   if (suffix)
    424     s.assign_format("%s-%s.png", name, suffix);
    425   else
    426     s.assign_format("%s.png", name);
    427 
    428   if (!options.quiet)
    429     printf("  Storing %s\n", s.data());
    430 
    431   image.write_to_file(s.data());
    432 }
    433 
    434 } // {ContextTests}