/src/libjxl/lib/jxl/butteraugli/butteraugli.h
Line | Count | Source (jump to first uncovered line) |
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 | 0 | Status GetTransposed(const ImageF &in, ImageF **out) { |
153 | 0 | JxlMemoryManager *memory_manager = in.memory_manager(); |
154 | 0 | if (transposed_temp.xsize() == 0) { |
155 | 0 | JXL_ASSIGN_OR_RETURN( |
156 | 0 | transposed_temp, |
157 | 0 | ImageF::Create(memory_manager, in.ysize(), in.xsize())); |
158 | 0 | } |
159 | 0 | *out = &transposed_temp; |
160 | 0 | return true; |
161 | 0 | } |
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 | 0 | 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_ |