butteraugli.h (8267B)
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 // Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) 7 8 #ifndef LIB_JXL_BUTTERAUGLI_BUTTERAUGLI_H_ 9 #define LIB_JXL_BUTTERAUGLI_BUTTERAUGLI_H_ 10 11 #include <jxl/memory_manager.h> 12 13 #include <atomic> 14 #include <cstddef> 15 #include <cstdlib> 16 #include <cstring> 17 #include <memory> 18 19 #include "lib/jxl/base/compiler_specific.h" 20 #include "lib/jxl/base/status.h" 21 #include "lib/jxl/image.h" 22 23 #if !defined(BUTTERAUGLI_ENABLE_CHECKS) 24 #define BUTTERAUGLI_ENABLE_CHECKS 0 25 #endif 26 27 #define BUTTERAUGLI_RESTRICT JXL_RESTRICT 28 29 // This is the main interface to butteraugli image similarity 30 // analysis function. 31 32 namespace jxl { 33 34 struct ButteraugliParams { 35 // Multiplier for penalizing new HF artifacts more than blurring away 36 // features. 1.0=neutral. 37 float hf_asymmetry = 1.0f; 38 39 // Multiplier for the psychovisual difference in the X channel. 40 float xmul = 1.0f; 41 42 // Number of nits that correspond to 1.0f input values. 43 float intensity_target = 80.0f; 44 }; 45 46 // ButteraugliInterface defines the public interface for butteraugli. 47 // 48 // It calculates the difference between rgb0 and rgb1. 49 // 50 // rgb0 and rgb1 contain the images. rgb0[c][px] and rgb1[c][px] contains 51 // the red image for c == 0, green for c == 1, blue for c == 2. Location index 52 // px is calculated as y * xsize + x. 53 // 54 // Value of pixels of images rgb0 and rgb1 need to be represented as raw 55 // intensity. Most image formats store gamma corrected intensity in pixel 56 // values. This gamma correction has to be removed, by applying the following 57 // function to values in the 0-1 range: 58 // butteraugli_val = pow(input_val, gamma); 59 // A typical value of gamma is 2.2. It is usually stored in the image header. 60 // Take care not to confuse that value with its inverse. The gamma value should 61 // be always greater than one. 62 // Butteraugli does not work as intended if the caller does not perform 63 // gamma correction. 64 // 65 // hf_asymmetry is a multiplier for penalizing new HF artifacts more than 66 // blurring away features (1.0 -> neutral). 67 // 68 // diffmap will contain an image of the size xsize * ysize, containing 69 // localized differences for values px (indexed with the px the same as rgb0 70 // and rgb1). diffvalue will give a global score of similarity. 71 // 72 // A diffvalue smaller than kButteraugliGood indicates that images can be 73 // observed as the same image. 74 // diffvalue larger than kButteraugliBad indicates that a difference between 75 // the images can be observed. 76 // A diffvalue between kButteraugliGood and kButteraugliBad indicates that 77 // a subtle difference can be observed between the images. 78 // 79 // Returns true on success. 80 bool ButteraugliInterface(const Image3F &rgb0, const Image3F &rgb1, 81 const ButteraugliParams ¶ms, ImageF &diffmap, 82 double &diffvalue); 83 84 // Deprecated (calls the previous function) 85 bool ButteraugliInterface(const Image3F &rgb0, const Image3F &rgb1, 86 float hf_asymmetry, float xmul, ImageF &diffmap, 87 double &diffvalue); 88 89 // Same as ButteraugliInterface, but reuses rgb0 and rgb1 for other purposes 90 // inside the function after they are not needed any more, and it ignores 91 // params.xmul. 92 Status ButteraugliInterfaceInPlace(Image3F &&rgb0, Image3F &&rgb1, 93 const ButteraugliParams ¶ms, 94 ImageF &diffmap, double &diffvalue); 95 96 // Converts the butteraugli score into fuzzy class values that are continuous 97 // at the class boundary. The class boundary location is based on human 98 // raters, but the slope is arbitrary. Particularly, it does not reflect 99 // the expectation value of probabilities of the human raters. It is just 100 // expected that a smoother class boundary will allow for higher-level 101 // optimization algorithms to work faster. 102 // 103 // Returns 2.0 for a perfect match, and 1.0 for 'ok', 0.0 for bad. Because the 104 // scoring is fuzzy, a butteraugli score of 0.96 would return a class of 105 // around 1.9. 106 double ButteraugliFuzzyClass(double score); 107 108 // Input values should be in range 0 (bad) to 2 (good). Use 109 // kButteraugliNormalization as normalization. 110 double ButteraugliFuzzyInverse(double seek); 111 112 // Implementation details, don't use anything below or your code will 113 // break in the future. 114 115 #ifdef _MSC_VER 116 #define BUTTERAUGLI_INLINE __forceinline 117 #else 118 #define BUTTERAUGLI_INLINE inline 119 #endif 120 121 #ifdef __clang__ 122 // Early versions of Clang did not support __builtin_assume_aligned. 123 #define BUTTERAUGLI_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned) 124 #elif defined(__GNUC__) 125 #define BUTTERAUGLI_HAS_ASSUME_ALIGNED 1 126 #else 127 #define BUTTERAUGLI_HAS_ASSUME_ALIGNED 0 128 #endif 129 130 // Returns a void* pointer which the compiler then assumes is N-byte aligned. 131 // Example: float* JXL_RESTRICT aligned = (float*)JXL_ASSUME_ALIGNED(in, 32); 132 // 133 // The assignment semantics are required by GCC/Clang. ICC provides an in-place 134 // __assume_aligned, whereas MSVC's __assume appears unsuitable. 135 #if BUTTERAUGLI_HAS_ASSUME_ALIGNED 136 #define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) \ 137 __builtin_assume_aligned((ptr), (align)) 138 #else 139 #define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) (ptr) 140 #endif // BUTTERAUGLI_HAS_ASSUME_ALIGNED 141 142 struct PsychoImage { 143 ImageF uhf[2]; // XY 144 ImageF hf[2]; // XY 145 Image3F mf; // XYB 146 Image3F lf; // XYB 147 }; 148 149 // Blur needs a transposed image. 150 // Hold it here and only allocate on demand to reduce memory usage. 151 struct BlurTemp { 152 Status GetTransposed(const ImageF &in, ImageF **out) { 153 JxlMemoryManager *memory_manager = in.memory_manager(); 154 if (transposed_temp.xsize() == 0) { 155 JXL_ASSIGN_OR_RETURN( 156 transposed_temp, 157 ImageF::Create(memory_manager, in.ysize(), in.xsize())); 158 } 159 *out = &transposed_temp; 160 return true; 161 } 162 163 ImageF transposed_temp; 164 }; 165 166 class ButteraugliComparator { 167 public: 168 // Butteraugli is calibrated at xmul = 1.0. We add a multiplier here so that 169 // we can test the hypothesis that a higher weighing of the X channel would 170 // improve results at higher Butteraugli values. 171 virtual ~ButteraugliComparator() = default; 172 173 static StatusOr<std::unique_ptr<ButteraugliComparator>> Make( 174 const Image3F &rgb0, const ButteraugliParams ¶ms); 175 176 // Computes the butteraugli map between the original image given in the 177 // constructor and the distorted image give here. 178 Status Diffmap(const Image3F &rgb1, ImageF &result) const; 179 180 // Same as above, but OpsinDynamicsImage() was already applied. 181 Status DiffmapOpsinDynamicsImage(const Image3F &xyb1, ImageF &result) const; 182 183 // Same as above, but the frequency decomposition was already applied. 184 Status DiffmapPsychoImage(const PsychoImage &pi1, ImageF &diffmap) const; 185 186 Status Mask(ImageF *BUTTERAUGLI_RESTRICT mask) const; 187 188 private: 189 ButteraugliComparator(size_t xsize, size_t ysize, 190 const ButteraugliParams ¶ms); 191 Image3F *Temp() const; 192 void ReleaseTemp() const; 193 194 const size_t xsize_; 195 const size_t ysize_; 196 ButteraugliParams params_; 197 PsychoImage pi0_; 198 199 // Shared temporary image storage to reduce the number of allocations; 200 // obtained via Temp(), must call ReleaseTemp when no longer needed. 201 mutable Image3F temp_; 202 mutable std::atomic_flag temp_in_use_ = ATOMIC_FLAG_INIT; 203 204 mutable BlurTemp blur_temp_; 205 std::unique_ptr<ButteraugliComparator> sub_; 206 }; 207 208 // Deprecated. 209 Status ButteraugliDiffmap(const Image3F &rgb0, const Image3F &rgb1, 210 double hf_asymmetry, double xmul, ImageF &diffmap); 211 212 Status ButteraugliDiffmap(const Image3F &rgb0, const Image3F &rgb1, 213 const ButteraugliParams ¶ms, ImageF &diffmap); 214 215 double ButteraugliScoreFromDiffmap(const ImageF &diffmap, 216 const ButteraugliParams *params = nullptr); 217 218 // Generate rgb-representation of the distance between two images. 219 StatusOr<Image3F> CreateHeatMapImage(const ImageF &distmap, 220 double good_threshold, 221 double bad_threshold); 222 223 } // namespace jxl 224 225 #endif // LIB_JXL_BUTTERAUGLI_BUTTERAUGLI_H_