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