bl_test_image_io.cpp (9058B)
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 #include <string.h> 8 9 #include "../commons/cmdline.h" 10 #include "../commons/imagediff.h" 11 #include "../commons/performance_timer.h" 12 13 namespace CodecTests { 14 15 static bool is_absolute_path(const char* s) { 16 size_t len = strlen(s); 17 return len > 0 && s[0] == '/'; 18 } 19 20 struct CodecFeatureNameEntry { 21 BLImageCodecFeatures feature; 22 char name[12]; 23 }; 24 25 static constexpr CodecFeatureNameEntry codec_features_table[] = { 26 { BL_IMAGE_CODEC_FEATURE_READ , "read" }, 27 { BL_IMAGE_CODEC_FEATURE_WRITE , "write" }, 28 { BL_IMAGE_CODEC_FEATURE_LOSSLESS , "lossless" }, 29 { BL_IMAGE_CODEC_FEATURE_LOSSY , "lossy" }, 30 { BL_IMAGE_CODEC_FEATURE_MULTI_FRAME, "multi-frame" }, 31 { BL_IMAGE_CODEC_FEATURE_IPTC , "iptc" }, 32 { BL_IMAGE_CODEC_FEATURE_EXIF , "exif" }, 33 { BL_IMAGE_CODEC_FEATURE_XMP , "xmp" } 34 }; 35 36 enum class TestKind : uint8_t { 37 kNone, 38 kSingleImage, 39 kCompareImages 40 }; 41 42 struct TestOptions { 43 TestKind test_kind = TestKind::kNone; 44 bool quiet {}; 45 const char* base_dir {}; 46 const char* file1 {}; 47 const char* file2 {}; 48 }; 49 50 struct LoadedImage { 51 BLResult result; 52 double duration; 53 BLImage image; 54 }; 55 56 static const char* bool_to_string(bool value) { 57 return value ? "true" : "false"; 58 } 59 60 static const char* format_to_string(BLFormat format) { 61 switch (format) { 62 case BL_FORMAT_PRGB32: 63 return "prgb32"; 64 case BL_FORMAT_XRGB32: 65 return "xrgb32"; 66 case BL_FORMAT_A8: 67 return "a8"; 68 default: 69 return "unknown"; 70 } 71 } 72 73 class TestApp { 74 public: 75 TestOptions default_options {}; 76 TestOptions options {}; 77 78 TestApp(); 79 ~TestApp(); 80 81 static TestOptions make_default_options(); 82 83 int help(); 84 85 void print_app_info(const char* title, bool quiet) const; 86 void print_options() const; 87 void print_built_in_codecs() const; 88 89 bool parse_options(CmdLine cmd_line); 90 91 LoadedImage load_image(const char* base_dir, const char* file_name); 92 93 bool test_single_file(const char* base_dir, const char* file_name); 94 bool compare_files(const char* base_dir, const char* fileName1, const char* fileName2); 95 96 int run(CmdLine cmd_line); 97 }; 98 99 TestApp::TestApp() 100 : default_options(make_default_options()) { 101 } 102 103 TestApp::~TestApp() {} 104 105 TestOptions TestApp::make_default_options() { 106 TestOptions options {}; 107 return options; 108 } 109 110 int TestApp::help() { 111 printf("Usage:\n"); 112 printf(" bl_test_image_io [options] --<file|compare> [--help for help]\n"); 113 printf("\n"); 114 115 printf("Purpose:\n"); 116 printf(" Verify that image codecs can decode and encode images properly.\n"); 117 printf("\n"); 118 119 print_options(); 120 print_built_in_codecs(); 121 122 return 0; 123 } 124 125 bool TestApp::parse_options(CmdLine cmd_line) { 126 options.base_dir = cmd_line.value_of("--base-dir", nullptr); 127 options.quiet = cmd_line.has_arg("--quiet") || default_options.quiet; 128 129 TestKind kind = TestKind::kNone; 130 131 if (cmd_line.value_of("--file", nullptr)) { 132 kind = TestKind::kSingleImage; 133 } 134 else if (cmd_line.has_arg("--compare")) { 135 kind = TestKind::kCompareImages; 136 } 137 138 switch (kind) { 139 case TestKind::kSingleImage: { 140 options.file1 = cmd_line.value_of("--file", nullptr); 141 break; 142 } 143 144 case TestKind::kCompareImages: { 145 int index = cmd_line.find_arg("--compare"); 146 147 if (index + 3 > cmd_line.count()) { 148 printf("Failed to process command line arguments: Invalid --compare <path1> <path2> (missing arguments)\n"); 149 return false; 150 } 151 152 options.file1 = cmd_line.args()[index + 1]; 153 options.file2 = cmd_line.args()[index + 2]; 154 break; 155 } 156 157 default: 158 break; 159 } 160 161 options.test_kind = kind; 162 return true; 163 } 164 165 void TestApp::print_app_info(const char* title, bool quiet) const { 166 printf("%s [use --help for command line options]\n", title); 167 168 if (!quiet) { 169 BLRuntimeBuildInfo build_info; 170 BLRuntime::query_build_info(&build_info); 171 printf(" Version : %u.%u.%u\n" 172 " Build Type : %s\n" 173 " Compiled By: %s\n\n", 174 build_info.major_version, 175 build_info.minor_version, 176 build_info.patch_version, 177 build_info.build_type == BL_RUNTIME_BUILD_TYPE_DEBUG ? "Debug" : "Release", 178 build_info.compiler_info); 179 } 180 181 fflush(stdout); 182 } 183 184 void TestApp::print_options() const { 185 printf("Options:\n"); 186 printf(" --base-dir=<string> - Base working directory [default=<none>]\n"); 187 printf(" --file=<string> - Path to a single file to decode [default=<none>]\n"); 188 printf(" --compare <string> <string> - Path to two files to decode & compare [default=<none>]\n"); 189 printf(" --quiet - Don't write log unless necessary [default=%s]\n", bool_to_string(options.quiet)); 190 printf("\n"); 191 } 192 193 void TestApp::print_built_in_codecs() const { 194 BLArray<BLImageCodec> codecs = BLImageCodec::built_in_codecs(); 195 196 printf("List of image codecs:\n"); 197 198 for (const BLImageCodec& codec : codecs) { 199 BLImageCodecFeatures features = codec.features(); 200 BLString f; 201 202 for (const CodecFeatureNameEntry& entry : codec_features_table) { 203 if ((features & entry.feature) != 0) { 204 if (!f.is_empty()) 205 f.append("|"); 206 f.append(entry.name); 207 } 208 } 209 210 printf(" %-4s (%-7s) - mime=%-12s files=%-22s features=%s\n", 211 codec.name().data(), 212 codec.vendor().data(), 213 codec.mime_type().data(), 214 codec.extensions().data(), 215 f.data()); 216 } 217 } 218 219 LoadedImage TestApp::load_image(const char* base_dir, const char* file_name) { 220 BLString full_path; 221 222 if (base_dir && !is_absolute_path(file_name)) { 223 full_path.append(base_dir); 224 if (full_path.size() > 0 && full_path[full_path.size() - 1] != '/') 225 full_path.append('/'); 226 full_path.append(file_name); 227 } 228 else { 229 full_path.append(file_name); 230 } 231 232 BLImage img; 233 PerformanceTimer timer; 234 235 timer.start(); 236 BLResult result = img.read_from_file(full_path.data()); 237 timer.stop(); 238 239 return LoadedImage{result, timer.duration(), img}; 240 } 241 242 bool TestApp::test_single_file(const char* base_dir, const char* file_name) { 243 LoadedImage i = load_image(base_dir, file_name); 244 245 if (i.result != BL_SUCCESS) { 246 printf("[%s] Error loading image (result=0x%80u)\n", file_name, i.result); 247 return false; 248 } 249 250 printf("[%s] loaded in %0.3f [ms] size=%ux%u format=%s\n", file_name, i.duration, i.image.size().w, i.image.size().h, format_to_string(i.image.format())); 251 return true; 252 } 253 254 bool TestApp::compare_files(const char* base_dir, const char* fileName1, const char* fileName2) { 255 LoadedImage i1 = load_image(base_dir, fileName1); 256 LoadedImage i2 = load_image(base_dir, fileName2); 257 258 BLImage& img1 = i1.image; 259 BLImage& img2 = i2.image; 260 261 if (i1.result != BL_SUCCESS) { 262 printf("[%s] Error loading first image (result=0x%80u)\n", fileName1, i1.result); 263 return false; 264 } 265 266 printf("[%s] loaded in %0.3f [ms] size=%ux%u format=%s\n", fileName1, i1.duration, img1.size().w, img1.size().h, format_to_string(img1.format())); 267 268 if (i2.result != BL_SUCCESS) { 269 printf("[%s] Error loading second image (result=0x%80u)\n", fileName2, i2.result); 270 return false; 271 } 272 273 printf("[%s] loaded in %0.3f [ms] size=%ux%u format=%s\n", fileName2, i2.duration, img2.size().w, img2.size().h, format_to_string(img2.format())); 274 275 if (img1.size() != img2.size()) { 276 printf("Image sizes don't match!\n"); 277 return false; 278 } 279 280 ImageUtils::DiffInfo diff = ImageUtils::diff_info(img1, img2); 281 if (diff.max_diff == 0xFFFFFFFFu) { 282 if (img1.format() != img2.format()) { 283 printf("Image formats don't match!\n"); 284 return false; 285 } 286 else { 287 printf("Unknown error happened during image comparison!\n"); 288 return false; 289 } 290 } 291 292 if (diff.cumulative_diff) { 293 printf("Images don't match:\n" 294 " MaximumDifference=%llu\n" 295 " CumulativeDifference=%llu\n", 296 (unsigned long long)diff.max_diff, 297 (unsigned long long)diff.cumulative_diff 298 ); 299 return false; 300 } 301 302 printf("Images match!\n"); 303 return true; 304 } 305 306 int TestApp::run(CmdLine cmd_line) { 307 print_app_info("Blend2D Image Codecs Tester", cmd_line.has_arg("--quiet")); 308 309 if (cmd_line.has_arg("--help")) { 310 return help(); 311 } 312 313 if (!parse_options(cmd_line)) { 314 return 1; 315 } 316 317 switch (options.test_kind) { 318 case TestKind::kNone: { 319 return help(); 320 } 321 322 case TestKind::kSingleImage: { 323 if (!test_single_file(options.base_dir, options.file1)) 324 return 1; 325 else 326 return 0; 327 } 328 329 case TestKind::kCompareImages: { 330 if (!compare_files(options.base_dir, options.file1, options.file2)) 331 return 1; 332 else 333 return 0; 334 } 335 336 default: 337 return 1; 338 } 339 } 340 341 } // {CodecTests} 342 343 int main(int argc, char* argv[]) { 344 BLRuntimeScope rt_scope; 345 CodecTests::TestApp app; 346 347 return app.run(CmdLine(argc, argv)); 348 }