butteraugli_main.cc (6058B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include <jxl/cms.h> 7 #include <jxl/types.h> 8 #include <stdint.h> 9 #include <stdio.h> 10 11 #include <cstdlib> 12 #include <string> 13 #include <vector> 14 15 #include "jxl/cms_interface.h" 16 #include "lib/extras/codec.h" 17 #include "lib/extras/dec/color_hints.h" 18 #include "lib/extras/metrics.h" 19 #include "lib/extras/packed_image.h" 20 #include "lib/extras/packed_image_convert.h" 21 #include "lib/jxl/base/printf_macros.h" 22 #include "lib/jxl/base/span.h" 23 #include "lib/jxl/base/status.h" 24 #include "lib/jxl/butteraugli/butteraugli.h" 25 #include "lib/jxl/codec_in_out.h" 26 #include "lib/jxl/color_encoding_internal.h" 27 #include "lib/jxl/enc_butteraugli_comparator.h" 28 #include "lib/jxl/image.h" 29 #include "tools/file_io.h" 30 #include "tools/thread_pool_internal.h" 31 32 namespace { 33 34 using jpegxl::tools::ThreadPoolInternal; 35 using jxl::ButteraugliParams; 36 using jxl::CodecInOut; 37 using jxl::Image3F; 38 using jxl::ImageF; 39 using jxl::JxlButteraugliComparator; 40 using jxl::Status; 41 42 Status WriteImage(const Image3F& image, const std::string& filename) { 43 ThreadPoolInternal pool(4); 44 JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; 45 jxl::extras::PackedPixelFile ppf = 46 jxl::extras::ConvertImage3FToPackedPixelFile( 47 image, jxl::ColorEncoding::SRGB(), format, &pool); 48 std::vector<uint8_t> encoded; 49 return jxl::Encode(ppf, filename, &encoded, &pool) && 50 jpegxl::tools::WriteFile(filename, encoded); 51 } 52 53 Status RunButteraugli(const char* pathname1, const char* pathname2, 54 const std::string& distmap_filename, 55 const std::string& raw_distmap_filename, 56 const std::string& colorspace_hint, double p, 57 float intensity_target) { 58 jxl::extras::ColorHints color_hints; 59 if (!colorspace_hint.empty()) { 60 color_hints.Add("color_space", colorspace_hint); 61 } 62 63 const char* pathname[2] = {pathname1, pathname2}; 64 CodecInOut io[2]; 65 ThreadPoolInternal pool(4); 66 for (size_t i = 0; i < 2; ++i) { 67 std::vector<uint8_t> encoded; 68 if (!jpegxl::tools::ReadFile(pathname[i], &encoded)) { 69 fprintf(stderr, "Failed to read image from %s\n", pathname[i]); 70 return false; 71 } 72 if (!jxl::SetFromBytes(jxl::Bytes(encoded), color_hints, &io[i], &pool)) { 73 fprintf(stderr, "Failed to decode image from %s\n", pathname[i]); 74 return false; 75 } 76 } 77 78 CodecInOut& io1 = io[0]; 79 CodecInOut& io2 = io[1]; 80 if (io1.xsize() != io2.xsize()) { 81 fprintf(stderr, "Width mismatch: %" PRIuS " %" PRIuS "\n", io1.xsize(), 82 io2.xsize()); 83 return false; 84 } 85 if (io1.ysize() != io2.ysize()) { 86 fprintf(stderr, "Height mismatch: %" PRIuS " %" PRIuS "\n", io1.ysize(), 87 io2.ysize()); 88 return false; 89 } 90 91 ImageF distmap; 92 ButteraugliParams ba_params; 93 ba_params.hf_asymmetry = 1.0f; 94 ba_params.xmul = 1.0f; 95 ba_params.intensity_target = intensity_target; 96 const JxlCmsInterface& cms = *JxlGetDefaultCms(); 97 JxlButteraugliComparator comparator(ba_params, cms); 98 float distance; 99 JXL_CHECK(ComputeScore(io1.Main(), io2.Main(), &comparator, cms, &distance, 100 &distmap, &pool, 101 /* ignore_alpha */ false)); 102 printf("%.10f\n", distance); 103 104 double pnorm = jxl::ComputeDistanceP(distmap, ba_params, p); 105 printf("%g-norm: %f\n", p, pnorm); 106 107 if (!distmap_filename.empty()) { 108 float good = jxl::ButteraugliFuzzyInverse(1.5); 109 float bad = jxl::ButteraugliFuzzyInverse(0.5); 110 JXL_ASSIGN_OR_DIE(Image3F heatmap, 111 jxl::CreateHeatMapImage(distmap, good, bad)); 112 JXL_CHECK(WriteImage(heatmap, distmap_filename)); 113 } 114 if (!raw_distmap_filename.empty()) { 115 FILE* out = fopen(raw_distmap_filename.c_str(), "wb"); 116 JXL_CHECK(out != nullptr); 117 fprintf(out, "Pf\n%" PRIuS " %" PRIuS "\n-1.0\n", distmap.xsize(), 118 distmap.ysize()); 119 for (size_t y = distmap.ysize(); y-- > 0;) { 120 fwrite(distmap.Row(y), 4, distmap.xsize(), out); 121 } 122 fclose(out); 123 } 124 return true; 125 } 126 127 } // namespace 128 129 int main(int argc, char** argv) { 130 if (argc < 3) { 131 fprintf(stderr, 132 "Usage: %s <reference> <distorted>\n" 133 " [--distmap <distmap>]\n" 134 " [--rawdistmap <distmap.pfm>]\n" 135 " [--intensity_target <intensity_target>]\n" 136 " [--colorspace <colorspace_hint>]\n" 137 " [--pnorm <pth norm>]\n" 138 "NOTE: images get converted to linear sRGB for butteraugli. Images" 139 " without attached profiles (such as ppm or pfm) are interpreted" 140 " as nonlinear sRGB. The hint format is RGB_D65_SRG_Rel_Lin for" 141 " linear sRGB. Intensity target is viewing conditions screen nits" 142 ", defaults to 80.\n", 143 argv[0]); 144 return 1; 145 } 146 std::string distmap; 147 std::string raw_distmap; 148 std::string colorspace; 149 double p = 3; 150 float intensity_target = 80.0; // sRGB intensity target. 151 for (int i = 3; i < argc; i++) { 152 if (std::string(argv[i]) == "--distmap" && i + 1 < argc) { 153 distmap = argv[++i]; 154 } else if (std::string(argv[i]) == "--rawdistmap" && i + 1 < argc) { 155 raw_distmap = argv[++i]; 156 } else if (std::string(argv[i]) == "--colorspace" && i + 1 < argc) { 157 colorspace = argv[++i]; 158 } else if (std::string(argv[i]) == "--intensity_target" && i + 1 < argc) { 159 intensity_target = std::stof(std::string(argv[++i])); 160 } else if (std::string(argv[i]) == "--pnorm" && i + 1 < argc) { 161 char* end; 162 p = strtod(argv[++i], &end); 163 if (end == argv[i]) { 164 fprintf(stderr, "Failed to parse pnorm \"%s\".\n", argv[i]); 165 return 1; 166 } 167 } else { 168 fprintf(stderr, "Unrecognized flag \"%s\".\n", argv[i]); 169 return 1; 170 } 171 } 172 173 Status result = RunButteraugli(argv[1], argv[2], distmap, raw_distmap, 174 colorspace, p, intensity_target); 175 return result ? 1 : 0; 176 }