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}