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 <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 
16 #include <atomic>
17 #include <cmath>
18 #include <memory>
19 #include <vector>
20 
21 #include "lib/jxl/base/compiler_specific.h"
22 #include "lib/jxl/common.h"
23 #include "lib/jxl/image.h"
24 #include "lib/jxl/image_ops.h"
25 
26 #define BUTTERAUGLI_ENABLE_CHECKS 0
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   bool approximate_border = false;
46 };
47 
48 // ButteraugliInterface defines the public interface for butteraugli.
49 //
50 // It calculates the difference between rgb0 and rgb1.
51 //
52 // rgb0 and rgb1 contain the images. rgb0[c][px] and rgb1[c][px] contains
53 // the red image for c == 0, green for c == 1, blue for c == 2. Location index
54 // px is calculated as y * xsize + x.
55 //
56 // Value of pixels of images rgb0 and rgb1 need to be represented as raw
57 // intensity. Most image formats store gamma corrected intensity in pixel
58 // values. This gamma correction has to be removed, by applying the following
59 // function to values in the 0-1 range:
60 // butteraugli_val = pow(input_val, gamma);
61 // A typical value of gamma is 2.2. It is usually stored in the image header.
62 // Take care not to confuse that value with its inverse. The gamma value should
63 // be always greater than one.
64 // Butteraugli does not work as intended if the caller does not perform
65 // gamma correction.
66 //
67 // hf_asymmetry is a multiplier for penalizing new HF artifacts more than
68 // blurring away features (1.0 -> neutral).
69 //
70 // diffmap will contain an image of the size xsize * ysize, containing
71 // localized differences for values px (indexed with the px the same as rgb0
72 // and rgb1). diffvalue will give a global score of similarity.
73 //
74 // A diffvalue smaller than kButteraugliGood indicates that images can be
75 // observed as the same image.
76 // diffvalue larger than kButteraugliBad indicates that a difference between
77 // the images can be observed.
78 // A diffvalue between kButteraugliGood and kButteraugliBad indicates that
79 // a subtle difference can be observed between the images.
80 //
81 // Returns true on success.
82 bool ButteraugliInterface(const Image3F &rgb0, const Image3F &rgb1,
83                           const ButteraugliParams &params, ImageF &diffmap,
84                           double &diffvalue);
85 
86 // Deprecated (calls the previous function)
87 bool ButteraugliInterface(const Image3F &rgb0, const Image3F &rgb1,
88                           float hf_asymmetry, float xmul, ImageF &diffmap,
89                           double &diffvalue);
90 
91 // Converts the butteraugli score into fuzzy class values that are continuous
92 // at the class boundary. The class boundary location is based on human
93 // raters, but the slope is arbitrary. Particularly, it does not reflect
94 // the expectation value of probabilities of the human raters. It is just
95 // expected that a smoother class boundary will allow for higher-level
96 // optimization algorithms to work faster.
97 //
98 // Returns 2.0 for a perfect match, and 1.0 for 'ok', 0.0 for bad. Because the
99 // scoring is fuzzy, a butteraugli score of 0.96 would return a class of
100 // around 1.9.
101 double ButteraugliFuzzyClass(double score);
102 
103 // Input values should be in range 0 (bad) to 2 (good). Use
104 // kButteraugliNormalization as normalization.
105 double ButteraugliFuzzyInverse(double seek);
106 
107 // Implementation details, don't use anything below or your code will
108 // break in the future.
109 
110 #ifdef _MSC_VER
111 #define BUTTERAUGLI_INLINE __forceinline
112 #else
113 #define BUTTERAUGLI_INLINE inline
114 #endif
115 
116 #ifdef __clang__
117 // Early versions of Clang did not support __builtin_assume_aligned.
118 #define BUTTERAUGLI_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned)
119 #elif defined(__GNUC__)
120 #define BUTTERAUGLI_HAS_ASSUME_ALIGNED 1
121 #else
122 #define BUTTERAUGLI_HAS_ASSUME_ALIGNED 0
123 #endif
124 
125 // Returns a void* pointer which the compiler then assumes is N-byte aligned.
126 // Example: float* JXL_RESTRICT aligned = (float*)JXL_ASSUME_ALIGNED(in, 32);
127 //
128 // The assignment semantics are required by GCC/Clang. ICC provides an in-place
129 // __assume_aligned, whereas MSVC's __assume appears unsuitable.
130 #if BUTTERAUGLI_HAS_ASSUME_ALIGNED
131 #define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) \
132   __builtin_assume_aligned((ptr), (align))
133 #else
134 #define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) (ptr)
135 #endif  // BUTTERAUGLI_HAS_ASSUME_ALIGNED
136 
137 struct PsychoImage {
138   ImageF uhf[2];  // XY
139   ImageF hf[2];   // XY
140   Image3F mf;     // XYB
141   Image3F lf;     // XYB
142 };
143 
144 // Depending on implementation, Blur either needs a normal or transposed image.
145 // Hold one or both of them here and only allocate on demand to reduce memory
146 // usage.
147 struct BlurTemp {
GetBlurTemp148   ImageF *Get(const ImageF &in) {
149     if (temp.xsize() == 0) {
150       temp = ImageF(in.xsize(), in.ysize());
151     }
152     return &temp;
153   }
154 
GetTransposedBlurTemp155   ImageF *GetTransposed(const ImageF &in) {
156     if (transposed_temp.xsize() == 0) {
157       transposed_temp = ImageF(in.ysize(), in.xsize());
158     }
159     return &transposed_temp;
160   }
161 
162   ImageF temp;
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   ButteraugliComparator(const Image3F &rgb0, const ButteraugliParams &params);
172   virtual ~ButteraugliComparator() = default;
173 
174   // Computes the butteraugli map between the original image given in the
175   // constructor and the distorted image give here.
176   void Diffmap(const Image3F &rgb1, ImageF &result) const;
177 
178   // Same as above, but OpsinDynamicsImage() was already applied.
179   void DiffmapOpsinDynamicsImage(const Image3F &xyb1, ImageF &result) const;
180 
181   // Same as above, but the frequency decomposition was already applied.
182   void DiffmapPsychoImage(const PsychoImage &pi1, ImageF &diffmap) const;
183 
184   void Mask(ImageF *BUTTERAUGLI_RESTRICT mask) const;
185 
186  private:
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 bool ButteraugliDiffmap(const Image3F &rgb0, const Image3F &rgb1,
206                         double hf_asymmetry, double xmul, ImageF &diffmap);
207 
208 bool ButteraugliDiffmap(const Image3F &rgb0, const Image3F &rgb1,
209                         const ButteraugliParams &params, 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 Image3F CreateHeatMapImage(const ImageF &distmap, double good_threshold,
216                            double bad_threshold);
217 
218 }  // namespace jxl
219 
220 #endif  // LIB_JXL_BUTTERAUGLI_BUTTERAUGLI_H_
221