odin-blend2d

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

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 }