/src/guetzli/third_party/butteraugli/butteraugli/butteraugli.h
Line | Count | Source |
1 | | // Copyright 2016 Google Inc. All Rights Reserved. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | // Disclaimer: This is not an official Google product. |
16 | | // |
17 | | // Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) |
18 | | |
19 | | #ifndef BUTTERAUGLI_BUTTERAUGLI_H_ |
20 | | #define BUTTERAUGLI_BUTTERAUGLI_H_ |
21 | | |
22 | | #include <cassert> |
23 | | #include <cmath> |
24 | | #include <cstdint> |
25 | | #include <cstdio> |
26 | | #include <cstdlib> |
27 | | #include <cstring> |
28 | | #include <memory> |
29 | | #include <vector> |
30 | | |
31 | | #define BUTTERAUGLI_ENABLE_CHECKS 0 |
32 | | |
33 | | // This is the main interface to butteraugli image similarity |
34 | | // analysis function. |
35 | | |
36 | | namespace butteraugli { |
37 | | |
38 | | template<typename T> |
39 | | class Image; |
40 | | |
41 | | using Image8 = Image<uint8_t>; |
42 | | using ImageF = Image<float>; |
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: |
56 | | // butteraugli_val = 255.0 * pow(png_val / 255.0, 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 | | // diffmap will contain an image of the size xsize * ysize, containing |
64 | | // localized differences for values px (indexed with the px the same as rgb0 |
65 | | // and rgb1). diffvalue will give a global score of similarity. |
66 | | // |
67 | | // A diffvalue smaller than kButteraugliGood indicates that images can be |
68 | | // observed as the same image. |
69 | | // diffvalue larger than kButteraugliBad indicates that a difference between |
70 | | // the images can be observed. |
71 | | // A diffvalue between kButteraugliGood and kButteraugliBad indicates that |
72 | | // a subtle difference can be observed between the images. |
73 | | // |
74 | | // Returns true on success. |
75 | | |
76 | | bool ButteraugliInterface(const std::vector<ImageF> &rgb0, |
77 | | const std::vector<ImageF> &rgb1, |
78 | | ImageF &diffmap, |
79 | | double &diffvalue); |
80 | | |
81 | | const double kButteraugliQuantLow = 0.26; |
82 | | const double kButteraugliQuantHigh = 1.454; |
83 | | |
84 | | // Converts the butteraugli score into fuzzy class values that are continuous |
85 | | // at the class boundary. The class boundary location is based on human |
86 | | // raters, but the slope is arbitrary. Particularly, it does not reflect |
87 | | // the expectation value of probabilities of the human raters. It is just |
88 | | // expected that a smoother class boundary will allow for higher-level |
89 | | // optimization algorithms to work faster. |
90 | | // |
91 | | // Returns 2.0 for a perfect match, and 1.0 for 'ok', 0.0 for bad. Because the |
92 | | // scoring is fuzzy, a butteraugli score of 0.96 would return a class of |
93 | | // around 1.9. |
94 | | double ButteraugliFuzzyClass(double score); |
95 | | |
96 | | // Input values should be in range 0 (bad) to 2 (good). Use |
97 | | // kButteraugliNormalization as normalization. |
98 | | double ButteraugliFuzzyInverse(double seek); |
99 | | |
100 | | // Returns a map which can be used for adaptive quantization. Values can |
101 | | // typically range from kButteraugliQuantLow to kButteraugliQuantHigh. Low |
102 | | // values require coarse quantization (e.g. near random noise), high values |
103 | | // require fine quantization (e.g. in smooth bright areas). |
104 | | bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize, |
105 | | const std::vector<std::vector<float> > &rgb, std::vector<float> &quant); |
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_RESTRICT __restrict |
112 | | #else |
113 | | #define BUTTERAUGLI_RESTRICT __restrict__ |
114 | | #endif |
115 | | |
116 | | #ifdef _MSC_VER |
117 | | #define BUTTERAUGLI_INLINE __forceinline |
118 | | #else |
119 | | #define BUTTERAUGLI_INLINE inline |
120 | | #endif |
121 | | |
122 | | #ifdef __clang__ |
123 | | // Early versions of Clang did not support __builtin_assume_aligned. |
124 | | #define BUTTERAUGLI_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned) |
125 | | #elif defined(__GNUC__) |
126 | | #define BUTTERAUGLI_HAS_ASSUME_ALIGNED 1 |
127 | | #else |
128 | | #define BUTTERAUGLI_HAS_ASSUME_ALIGNED 0 |
129 | | #endif |
130 | | |
131 | | // Returns a void* pointer which the compiler then assumes is N-byte aligned. |
132 | | // Example: float* PIK_RESTRICT aligned = (float*)PIK_ASSUME_ALIGNED(in, 32); |
133 | | // |
134 | | // The assignment semantics are required by GCC/Clang. ICC provides an in-place |
135 | | // __assume_aligned, whereas MSVC's __assume appears unsuitable. |
136 | | #if BUTTERAUGLI_HAS_ASSUME_ALIGNED |
137 | 25.6G | #define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) __builtin_assume_aligned((ptr), (align)) |
138 | | #else |
139 | | #define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) (ptr) |
140 | | #endif // BUTTERAUGLI_HAS_ASSUME_ALIGNED |
141 | | |
142 | | // Functions that depend on the cache line size. |
143 | | class CacheAligned { |
144 | | public: |
145 | | static constexpr size_t kPointerSize = sizeof(void *); |
146 | | static constexpr size_t kCacheLineSize = 64; |
147 | | |
148 | | // The aligned-return annotation is only allowed on function declarations. |
149 | | static void *Allocate(const size_t bytes); |
150 | | static void Free(void *aligned_pointer); |
151 | | }; |
152 | | |
153 | | template <typename T> |
154 | | using CacheAlignedUniquePtrT = std::unique_ptr<T[], void (*)(void *)>; |
155 | | |
156 | | using CacheAlignedUniquePtr = CacheAlignedUniquePtrT<uint8_t>; |
157 | | |
158 | | template <typename T = uint8_t> |
159 | 66.5M | static inline CacheAlignedUniquePtrT<T> Allocate(const size_t entries) { |
160 | 66.5M | return CacheAlignedUniquePtrT<T>( |
161 | 66.5M | static_cast<T * const BUTTERAUGLI_RESTRICT>( |
162 | 66.5M | CacheAligned::Allocate(entries * sizeof(T))), |
163 | 66.5M | CacheAligned::Free); |
164 | 66.5M | } butteraugli_comparator.cc:std::__1::unique_ptr<unsigned char [], void (*)(void*)> butteraugli::Allocate<unsigned char>(unsigned long) Line | Count | Source | 159 | 14.6M | static inline CacheAlignedUniquePtrT<T> Allocate(const size_t entries) { | 160 | 14.6M | return CacheAlignedUniquePtrT<T>( | 161 | 14.6M | static_cast<T * const BUTTERAUGLI_RESTRICT>( | 162 | 14.6M | CacheAligned::Allocate(entries * sizeof(T))), | 163 | 14.6M | CacheAligned::Free); | 164 | 14.6M | } |
butteraugli.cc:std::__1::unique_ptr<unsigned char [], void (*)(void*)> butteraugli::Allocate<unsigned char>(unsigned long) Line | Count | Source | 159 | 51.9M | static inline CacheAlignedUniquePtrT<T> Allocate(const size_t entries) { | 160 | 51.9M | return CacheAlignedUniquePtrT<T>( | 161 | 51.9M | static_cast<T * const BUTTERAUGLI_RESTRICT>( | 162 | 51.9M | CacheAligned::Allocate(entries * sizeof(T))), | 163 | 51.9M | CacheAligned::Free); | 164 | 51.9M | } |
|
165 | | |
166 | | // Returns the smallest integer not less than "amount" that is divisible by |
167 | | // "multiple", which must be a power of two. |
168 | | template <size_t multiple> |
169 | | static inline size_t Align(const size_t amount) { |
170 | | static_assert(multiple != 0 && ((multiple & (multiple - 1)) == 0), |
171 | | "Align<> argument must be a power of two"); |
172 | | return (amount + multiple - 1) & ~(multiple - 1); |
173 | | } |
174 | | |
175 | | // Single channel, contiguous (cache-aligned) rows separated by padding. |
176 | | // T must be POD. |
177 | | // |
178 | | // Rationale: vectorization benefits from aligned operands - unaligned loads and |
179 | | // especially stores are expensive when the address crosses cache line |
180 | | // boundaries. Introducing padding after each row ensures the start of a row is |
181 | | // aligned, and that row loops can process entire vectors (writes to the padding |
182 | | // are allowed and ignored). |
183 | | // |
184 | | // We prefer a planar representation, where channels are stored as separate |
185 | | // 2D arrays, because that simplifies vectorization (repeating the same |
186 | | // operation on multiple adjacent components) without the complexity of a |
187 | | // hybrid layout (8 R, 8 G, 8 B, ...). In particular, clients can easily iterate |
188 | | // over all components in a row and Image requires no knowledge of the pixel |
189 | | // format beyond the component type "T". The downside is that we duplicate the |
190 | | // xsize/ysize members for each channel. |
191 | | // |
192 | | // This image layout could also be achieved with a vector and a row accessor |
193 | | // function, but a class wrapper with support for "deleter" allows wrapping |
194 | | // existing memory allocated by clients without copying the pixels. It also |
195 | | // provides convenient accessors for xsize/ysize, which shortens function |
196 | | // argument lists. Supports move-construction so it can be stored in containers. |
197 | | template <typename ComponentType> |
198 | | class Image { |
199 | | // Returns cache-aligned row stride, being careful to avoid 2K aliasing. |
200 | 66.5M | static size_t BytesPerRow(const size_t xsize) { |
201 | | // Allow reading one extra AVX-2 vector on the right margin. |
202 | 66.5M | const size_t row_size = xsize * sizeof(T) + 32; |
203 | 66.5M | const size_t align = CacheAligned::kCacheLineSize; |
204 | 66.5M | size_t bytes_per_row = (row_size + align - 1) & ~(align - 1); |
205 | | // During the lengthy window before writes are committed to memory, CPUs |
206 | | // guard against read after write hazards by checking the address, but |
207 | | // only the lower 11 bits. We avoid a false dependency between writes to |
208 | | // consecutive rows by ensuring their sizes are not multiples of 2 KiB. |
209 | 66.5M | if (bytes_per_row % 2048 == 0) { |
210 | 0 | bytes_per_row += align; |
211 | 0 | } |
212 | 66.5M | return bytes_per_row; |
213 | 66.5M | } |
214 | | |
215 | | public: |
216 | | using T = ComponentType; |
217 | | |
218 | 32.0M | Image() : xsize_(0), ysize_(0), bytes_per_row_(0), |
219 | 32.0M | bytes_(static_cast<uint8_t*>(nullptr), Ignore) {} |
220 | | |
221 | | Image(const size_t xsize, const size_t ysize) |
222 | 65.7M | : xsize_(xsize), |
223 | 65.7M | ysize_(ysize), |
224 | 65.7M | bytes_per_row_(BytesPerRow(xsize)), |
225 | 65.7M | bytes_(Allocate(bytes_per_row_ * ysize)) {} |
226 | | |
227 | | Image(const size_t xsize, const size_t ysize, T val) |
228 | 807k | : xsize_(xsize), |
229 | 807k | ysize_(ysize), |
230 | 807k | bytes_per_row_(BytesPerRow(xsize)), |
231 | 807k | bytes_(Allocate(bytes_per_row_ * ysize)) { |
232 | 36.1M | for (size_t y = 0; y < ysize_; ++y) { |
233 | 35.3M | T* const BUTTERAUGLI_RESTRICT row = Row(y); |
234 | 1.81G | for (int x = 0; x < xsize_; ++x) { |
235 | 1.77G | row[x] = val; |
236 | 1.77G | } |
237 | 35.3M | } |
238 | 807k | } |
239 | | |
240 | | Image(const size_t xsize, const size_t ysize, |
241 | | uint8_t * const BUTTERAUGLI_RESTRICT bytes, |
242 | | const size_t bytes_per_row) |
243 | | : xsize_(xsize), |
244 | | ysize_(ysize), |
245 | | bytes_per_row_(bytes_per_row), |
246 | | bytes_(bytes, Ignore) {} |
247 | | |
248 | | // Move constructor (required for returning Image from function) |
249 | | Image(Image &&other) |
250 | 14.2M | : xsize_(other.xsize_), |
251 | 14.2M | ysize_(other.ysize_), |
252 | 14.2M | bytes_per_row_(other.bytes_per_row_), |
253 | 14.2M | bytes_(std::move(other.bytes_)) {} |
254 | | |
255 | | // Move assignment (required for std::vector) |
256 | 33.1M | Image &operator=(Image &&other) { |
257 | 33.1M | xsize_ = other.xsize_; |
258 | 33.1M | ysize_ = other.ysize_; |
259 | 33.1M | bytes_per_row_ = other.bytes_per_row_; |
260 | 33.1M | bytes_ = std::move(other.bytes_); |
261 | 33.1M | return *this; |
262 | 33.1M | } |
263 | | |
264 | | void Swap(Image &other) { |
265 | | std::swap(xsize_, other.xsize_); |
266 | | std::swap(ysize_, other.ysize_); |
267 | | std::swap(bytes_per_row_, other.bytes_per_row_); |
268 | | std::swap(bytes_, other.bytes_); |
269 | | } |
270 | | |
271 | | // How many pixels. |
272 | 3.84G | size_t xsize() const { return xsize_; } |
273 | 5.89G | size_t ysize() const { return ysize_; } |
274 | | |
275 | 15.1G | T *const BUTTERAUGLI_RESTRICT Row(const size_t y) { |
276 | 15.1G | #ifdef BUTTERAUGLI_ENABLE_CHECKS |
277 | 15.1G | if (y >= ysize_) { |
278 | 0 | printf("Row %zu out of bounds (ysize=%zu)\n", y, ysize_); |
279 | 0 | abort(); |
280 | 0 | } |
281 | 15.1G | #endif |
282 | 15.1G | void *row = bytes_.get() + y * bytes_per_row_; |
283 | 15.1G | return reinterpret_cast<T *>(BUTTERAUGLI_ASSUME_ALIGNED(row, 64)); |
284 | 15.1G | } |
285 | | |
286 | 10.4G | const T *const BUTTERAUGLI_RESTRICT Row(const size_t y) const { |
287 | 10.4G | #ifdef BUTTERAUGLI_ENABLE_CHECKS |
288 | 10.4G | if (y >= ysize_) { |
289 | 0 | printf("Const row %zu out of bounds (ysize=%zu)\n", y, ysize_); |
290 | 0 | abort(); |
291 | 0 | } |
292 | 10.4G | #endif |
293 | 10.4G | void *row = bytes_.get() + y * bytes_per_row_; |
294 | 10.4G | return reinterpret_cast<const T *>(BUTTERAUGLI_ASSUME_ALIGNED(row, 64)); |
295 | 10.4G | } |
296 | | |
297 | | // Raw access to byte contents, for interfacing with other libraries. |
298 | | // Unsigned char instead of char to avoid surprises (sign extension). |
299 | | uint8_t * const BUTTERAUGLI_RESTRICT bytes() { return bytes_.get(); } |
300 | | const uint8_t * const BUTTERAUGLI_RESTRICT bytes() const { |
301 | | return bytes_.get(); |
302 | | } |
303 | | size_t bytes_per_row() const { return bytes_per_row_; } |
304 | | |
305 | | // Returns number of pixels (some of which are padding) per row. Useful for |
306 | | // computing other rows via pointer arithmetic. |
307 | | intptr_t PixelsPerRow() const { |
308 | | static_assert(CacheAligned::kCacheLineSize % sizeof(T) == 0, |
309 | | "Padding must be divisible by the pixel size."); |
310 | | return static_cast<intptr_t>(bytes_per_row_ / sizeof(T)); |
311 | | } |
312 | | |
313 | | private: |
314 | | // Deleter used when bytes are not owned. |
315 | 0 | static void Ignore(void *ptr) {} |
316 | | |
317 | | // (Members are non-const to enable assignment during move-assignment.) |
318 | | size_t xsize_; // original intended pixels, not including any padding. |
319 | | size_t ysize_; |
320 | | size_t bytes_per_row_; // [bytes] including padding. |
321 | | CacheAlignedUniquePtr bytes_; |
322 | | }; |
323 | | |
324 | | // Returns newly allocated planes of the given dimensions. |
325 | | template <typename T> |
326 | | static inline std::vector<Image<T>> CreatePlanes(const size_t xsize, |
327 | | const size_t ysize, |
328 | 547k | const size_t num_planes) { |
329 | 547k | std::vector<Image<T>> planes; |
330 | 547k | planes.reserve(num_planes); |
331 | 2.19M | for (size_t i = 0; i < num_planes; ++i) { |
332 | 1.64M | planes.emplace_back(xsize, ysize); |
333 | 1.64M | } |
334 | 547k | return planes; |
335 | 547k | } butteraugli_comparator.cc:std::__1::vector<butteraugli::Image<float>, std::__1::allocator<butteraugli::Image<float> > > butteraugli::CreatePlanes<float>(unsigned long, unsigned long, unsigned long) Line | Count | Source | 328 | 140k | const size_t num_planes) { | 329 | 140k | std::vector<Image<T>> planes; | 330 | 140k | planes.reserve(num_planes); | 331 | 562k | for (size_t i = 0; i < num_planes; ++i) { | 332 | 421k | planes.emplace_back(xsize, ysize); | 333 | 421k | } | 334 | 140k | return planes; | 335 | 140k | } |
butteraugli.cc:std::__1::vector<butteraugli::Image<float>, std::__1::allocator<butteraugli::Image<float> > > butteraugli::CreatePlanes<float>(unsigned long, unsigned long, unsigned long) Line | Count | Source | 328 | 407k | const size_t num_planes) { | 329 | 407k | std::vector<Image<T>> planes; | 330 | 407k | planes.reserve(num_planes); | 331 | 1.62M | for (size_t i = 0; i < num_planes; ++i) { | 332 | 1.22M | planes.emplace_back(xsize, ysize); | 333 | 1.22M | } | 334 | 407k | return planes; | 335 | 407k | } |
|
336 | | |
337 | | // Returns a new image with the same dimensions and pixel values. |
338 | | template <typename T> |
339 | | static inline Image<T> CopyPixels(const Image<T> &other) { |
340 | | Image<T> copy(other.xsize(), other.ysize()); |
341 | | const void *BUTTERAUGLI_RESTRICT from = other.bytes(); |
342 | | void *BUTTERAUGLI_RESTRICT to = copy.bytes(); |
343 | | memcpy(to, from, other.ysize() * other.bytes_per_row()); |
344 | | return copy; |
345 | | } |
346 | | |
347 | | // Returns new planes with the same dimensions and pixel values. |
348 | | template <typename T> |
349 | | static inline std::vector<Image<T>> CopyPlanes( |
350 | | const std::vector<Image<T>> &planes) { |
351 | | std::vector<Image<T>> copy; |
352 | | copy.reserve(planes.size()); |
353 | | for (const Image<T> &plane : planes) { |
354 | | copy.push_back(CopyPixels(plane)); |
355 | | } |
356 | | return copy; |
357 | | } |
358 | | |
359 | | // Compacts a padded image into a preallocated packed vector. |
360 | | template <typename T> |
361 | 13.9M | static inline void CopyToPacked(const Image<T> &from, std::vector<T> *to) { |
362 | 13.9M | const size_t xsize = from.xsize(); |
363 | 13.9M | const size_t ysize = from.ysize(); |
364 | | #if BUTTERAUGLI_ENABLE_CHECKS |
365 | | if (to->size() < xsize * ysize) { |
366 | | printf("%zu x %zu exceeds %zu capacity\n", xsize, ysize, to->size()); |
367 | | abort(); |
368 | | } |
369 | | #endif |
370 | 130M | for (size_t y = 0; y < ysize; ++y) { |
371 | 116M | const float* const BUTTERAUGLI_RESTRICT row_from = from.Row(y); |
372 | 116M | float* const BUTTERAUGLI_RESTRICT row_to = to->data() + y * xsize; |
373 | 116M | memcpy(row_to, row_from, xsize * sizeof(T)); |
374 | 116M | } |
375 | 13.9M | } |
376 | | |
377 | | // Expands a packed vector into a preallocated padded image. |
378 | | template <typename T> |
379 | 14.2M | static inline void CopyFromPacked(const std::vector<T> &from, Image<T> *to) { |
380 | 14.2M | const size_t xsize = to->xsize(); |
381 | 14.2M | const size_t ysize = to->ysize(); |
382 | 14.2M | assert(from.size() == xsize * ysize); |
383 | 142M | for (size_t y = 0; y < ysize; ++y) { |
384 | 128M | const float* const BUTTERAUGLI_RESTRICT row_from = |
385 | 128M | from.data() + y * xsize; |
386 | 128M | float* const BUTTERAUGLI_RESTRICT row_to = to->Row(y); |
387 | 128M | memcpy(row_to, row_from, xsize * sizeof(T)); |
388 | 128M | } |
389 | 14.2M | } butteraugli_comparator.cc:void butteraugli::CopyFromPacked<float>(std::__1::vector<float, std::__1::allocator<float> > const&, butteraugli::Image<float>*) Line | Count | Source | 379 | 14.2M | static inline void CopyFromPacked(const std::vector<T> &from, Image<T> *to) { | 380 | 14.2M | const size_t xsize = to->xsize(); | 381 | 14.2M | const size_t ysize = to->ysize(); | 382 | 14.2M | assert(from.size() == xsize * ysize); | 383 | 142M | for (size_t y = 0; y < ysize; ++y) { | 384 | 128M | const float* const BUTTERAUGLI_RESTRICT row_from = | 385 | 128M | from.data() + y * xsize; | 386 | 128M | float* const BUTTERAUGLI_RESTRICT row_to = to->Row(y); | 387 | 128M | memcpy(row_to, row_from, xsize * sizeof(T)); | 388 | 128M | } | 389 | 14.2M | } |
Unexecuted instantiation: butteraugli.cc:void butteraugli::CopyFromPacked<float>(std::__1::vector<float, std::__1::allocator<float> > const&, butteraugli::Image<float>*) |
390 | | |
391 | | template <typename T> |
392 | | static inline std::vector<Image<T>> PlanesFromPacked( |
393 | | const size_t xsize, const size_t ysize, |
394 | 4.74M | const std::vector<std::vector<T>> &packed) { |
395 | 4.74M | std::vector<Image<T>> planes; |
396 | 4.74M | planes.reserve(packed.size()); |
397 | 14.2M | for (const std::vector<T> &p : packed) { |
398 | 14.2M | planes.push_back(Image<T>(xsize, ysize)); |
399 | 14.2M | CopyFromPacked(p, &planes.back()); |
400 | 14.2M | } |
401 | 4.74M | return planes; |
402 | 4.74M | } butteraugli_comparator.cc:std::__1::vector<butteraugli::Image<float>, std::__1::allocator<butteraugli::Image<float> > > butteraugli::PlanesFromPacked<float>(unsigned long, unsigned long, std::__1::vector<std::__1::vector<float, std::__1::allocator<float> >, std::__1::allocator<std::__1::vector<float, std::__1::allocator<float> > > > const&) Line | Count | Source | 394 | 4.74M | const std::vector<std::vector<T>> &packed) { | 395 | 4.74M | std::vector<Image<T>> planes; | 396 | 4.74M | planes.reserve(packed.size()); | 397 | 14.2M | for (const std::vector<T> &p : packed) { | 398 | 14.2M | planes.push_back(Image<T>(xsize, ysize)); | 399 | 14.2M | CopyFromPacked(p, &planes.back()); | 400 | 14.2M | } | 401 | 4.74M | return planes; | 402 | 4.74M | } |
Unexecuted instantiation: butteraugli.cc:std::__1::vector<butteraugli::Image<float>, std::__1::allocator<butteraugli::Image<float> > > butteraugli::PlanesFromPacked<float>(unsigned long, unsigned long, std::__1::vector<std::__1::vector<float, std::__1::allocator<float> >, std::__1::allocator<std::__1::vector<float, std::__1::allocator<float> > > > const&) |
403 | | |
404 | | template <typename T> |
405 | | static inline std::vector<std::vector<T>> PackedFromPlanes( |
406 | 4.61M | const std::vector<Image<T>> &planes) { |
407 | 4.61M | assert(!planes.empty()); |
408 | 4.61M | const size_t num_pixels = planes[0].xsize() * planes[0].ysize(); |
409 | 4.61M | std::vector<std::vector<T>> packed; |
410 | 4.61M | packed.reserve(planes.size()); |
411 | 13.8M | for (const Image<T> &image : planes) { |
412 | 13.8M | packed.push_back(std::vector<T>(num_pixels)); |
413 | 13.8M | CopyToPacked(image, &packed.back()); |
414 | 13.8M | } |
415 | 4.61M | return packed; |
416 | 4.61M | } |
417 | | |
418 | | struct PsychoImage { |
419 | | std::vector<ImageF> uhf; |
420 | | std::vector<ImageF> hf; |
421 | | std::vector<ImageF> mf; |
422 | | std::vector<ImageF> lf; |
423 | | }; |
424 | | |
425 | | class ButteraugliComparator { |
426 | | public: |
427 | | ButteraugliComparator(const std::vector<ImageF>& rgb0); |
428 | | |
429 | | // Computes the butteraugli map between the original image given in the |
430 | | // constructor and the distorted image give here. |
431 | | void Diffmap(const std::vector<ImageF>& rgb1, ImageF& result) const; |
432 | | |
433 | | // Same as above, but OpsinDynamicsImage() was already applied. |
434 | | void DiffmapOpsinDynamicsImage(const std::vector<ImageF>& xyb1, |
435 | | ImageF& result) const; |
436 | | |
437 | | // Same as above, but the frequency decomposition was already applied. |
438 | | void DiffmapPsychoImage(const PsychoImage& ps1, ImageF &result) const; |
439 | | |
440 | | void Mask(std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask, |
441 | | std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask_dc) const; |
442 | | |
443 | | private: |
444 | | void MaltaDiffMapLF(const ImageF& y0, |
445 | | const ImageF& y1, |
446 | | double w_0gt1, |
447 | | double w_0lt1, |
448 | | double normalization, |
449 | | ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const; |
450 | | |
451 | | void MaltaDiffMap(const ImageF& y0, |
452 | | const ImageF& y1, |
453 | | double w_0gt1, |
454 | | double w_0lt1, |
455 | | double normalization, |
456 | | ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const; |
457 | | |
458 | | ImageF CombineChannels(const std::vector<ImageF>& scale_xyb, |
459 | | const std::vector<ImageF>& scale_xyb_dc, |
460 | | const std::vector<ImageF>& block_diff_dc, |
461 | | const std::vector<ImageF>& block_diff_ac) const; |
462 | | |
463 | | const size_t xsize_; |
464 | | const size_t ysize_; |
465 | | const size_t num_pixels_; |
466 | | PsychoImage pi0_; |
467 | | }; |
468 | | |
469 | | void ButteraugliDiffmap(const std::vector<ImageF> &rgb0, |
470 | | const std::vector<ImageF> &rgb1, |
471 | | ImageF &diffmap); |
472 | | |
473 | | double ButteraugliScoreFromDiffmap(const ImageF& distmap); |
474 | | |
475 | | // Generate rgb-representation of the distance between two images. |
476 | | void CreateHeatMapImage(const std::vector<float> &distmap, |
477 | | double good_threshold, double bad_threshold, |
478 | | size_t xsize, size_t ysize, |
479 | | std::vector<uint8_t> *heatmap); |
480 | | |
481 | | // Compute values of local frequency and dc masking based on the activity |
482 | | // in the two images. |
483 | | void Mask(const std::vector<ImageF>& xyb0, |
484 | | const std::vector<ImageF>& xyb1, |
485 | | std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask, |
486 | | std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask_dc); |
487 | | |
488 | | template <class V> |
489 | | BUTTERAUGLI_INLINE void RgbToXyb(const V &r, const V &g, const V &b, |
490 | | V *BUTTERAUGLI_RESTRICT valx, |
491 | | V *BUTTERAUGLI_RESTRICT valy, |
492 | 902M | V *BUTTERAUGLI_RESTRICT valb) { |
493 | 902M | *valx = r - g; |
494 | 902M | *valy = r + g; |
495 | 902M | *valb = b; |
496 | 902M | } |
497 | | |
498 | | template <class V> |
499 | | BUTTERAUGLI_INLINE void OpsinAbsorbance(const V &in0, const V &in1, |
500 | | const V &in2, |
501 | | V *BUTTERAUGLI_RESTRICT out0, |
502 | | V *BUTTERAUGLI_RESTRICT out1, |
503 | 1.80G | V *BUTTERAUGLI_RESTRICT out2) { |
504 | | // https://en.wikipedia.org/wiki/Photopsin absorbance modeling. |
505 | 1.80G | static const double mixi0 = 0.254462330846; |
506 | 1.80G | static const double mixi1 = 0.488238255095; |
507 | 1.80G | static const double mixi2 = 0.0635278003854; |
508 | 1.80G | static const double mixi3 = 1.01681026909; |
509 | 1.80G | static const double mixi4 = 0.195214015766; |
510 | 1.80G | static const double mixi5 = 0.568019861857; |
511 | 1.80G | static const double mixi6 = 0.0860755536007; |
512 | 1.80G | static const double mixi7 = 1.1510118369; |
513 | 1.80G | static const double mixi8 = 0.07374607900105684; |
514 | 1.80G | static const double mixi9 = 0.06142425304154509; |
515 | 1.80G | static const double mixi10 = 0.24416850520714256; |
516 | 1.80G | static const double mixi11 = 1.20481945273; |
517 | | |
518 | 1.80G | const V mix0(mixi0); |
519 | 1.80G | const V mix1(mixi1); |
520 | 1.80G | const V mix2(mixi2); |
521 | 1.80G | const V mix3(mixi3); |
522 | 1.80G | const V mix4(mixi4); |
523 | 1.80G | const V mix5(mixi5); |
524 | 1.80G | const V mix6(mixi6); |
525 | 1.80G | const V mix7(mixi7); |
526 | 1.80G | const V mix8(mixi8); |
527 | 1.80G | const V mix9(mixi9); |
528 | 1.80G | const V mix10(mixi10); |
529 | 1.80G | const V mix11(mixi11); |
530 | | |
531 | 1.80G | *out0 = mix0 * in0 + mix1 * in1 + mix2 * in2 + mix3; |
532 | 1.80G | *out1 = mix4 * in0 + mix5 * in1 + mix6 * in2 + mix7; |
533 | 1.80G | *out2 = mix8 * in0 + mix9 * in1 + mix10 * in2 + mix11; |
534 | 1.80G | } Unexecuted instantiation: void butteraugli::OpsinAbsorbance<double>(double const&, double const&, double const&, double*, double*, double*) void butteraugli::OpsinAbsorbance<float>(float const&, float const&, float const&, float*, float*, float*) Line | Count | Source | 503 | 1.80G | V *BUTTERAUGLI_RESTRICT out2) { | 504 | | // https://en.wikipedia.org/wiki/Photopsin absorbance modeling. | 505 | 1.80G | static const double mixi0 = 0.254462330846; | 506 | 1.80G | static const double mixi1 = 0.488238255095; | 507 | 1.80G | static const double mixi2 = 0.0635278003854; | 508 | 1.80G | static const double mixi3 = 1.01681026909; | 509 | 1.80G | static const double mixi4 = 0.195214015766; | 510 | 1.80G | static const double mixi5 = 0.568019861857; | 511 | 1.80G | static const double mixi6 = 0.0860755536007; | 512 | 1.80G | static const double mixi7 = 1.1510118369; | 513 | 1.80G | static const double mixi8 = 0.07374607900105684; | 514 | 1.80G | static const double mixi9 = 0.06142425304154509; | 515 | 1.80G | static const double mixi10 = 0.24416850520714256; | 516 | 1.80G | static const double mixi11 = 1.20481945273; | 517 | | | 518 | 1.80G | const V mix0(mixi0); | 519 | 1.80G | const V mix1(mixi1); | 520 | 1.80G | const V mix2(mixi2); | 521 | 1.80G | const V mix3(mixi3); | 522 | 1.80G | const V mix4(mixi4); | 523 | 1.80G | const V mix5(mixi5); | 524 | 1.80G | const V mix6(mixi6); | 525 | 1.80G | const V mix7(mixi7); | 526 | 1.80G | const V mix8(mixi8); | 527 | 1.80G | const V mix9(mixi9); | 528 | 1.80G | const V mix10(mixi10); | 529 | 1.80G | const V mix11(mixi11); | 530 | | | 531 | 1.80G | *out0 = mix0 * in0 + mix1 * in1 + mix2 * in2 + mix3; | 532 | 1.80G | *out1 = mix4 * in0 + mix5 * in1 + mix6 * in2 + mix7; | 533 | 1.80G | *out2 = mix8 * in0 + mix9 * in1 + mix10 * in2 + mix11; | 534 | 1.80G | } |
|
535 | | |
536 | | std::vector<ImageF> OpsinDynamicsImage(const std::vector<ImageF>& rgb); |
537 | | |
538 | | ImageF Blur(const ImageF& in, float sigma, float border_ratio); |
539 | | |
540 | | double SimpleGamma(double v); |
541 | | |
542 | | double GammaMinArg(); |
543 | | double GammaMaxArg(); |
544 | | |
545 | | // Polynomial evaluation via Clenshaw's scheme (similar to Horner's). |
546 | | // Template enables compile-time unrolling of the recursion, but must reside |
547 | | // outside of a class due to the specialization. |
548 | | template <int INDEX> |
549 | | static inline void ClenshawRecursion(const double x, const double *coefficients, |
550 | 27.0G | double *b1, double *b2) { |
551 | 27.0G | const double x_b1 = x * (*b1); |
552 | 27.0G | const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; |
553 | 27.0G | *b2 = *b1; |
554 | 27.0G | *b1 = t; |
555 | | |
556 | 27.0G | ClenshawRecursion<INDEX - 1>(x, coefficients, b1, b2); |
557 | 27.0G | } Unexecuted instantiation: processor.cc:void butteraugli::ClenshawRecursion<5>(double, double const*, double*, double*) Unexecuted instantiation: processor.cc:void butteraugli::ClenshawRecursion<4>(double, double const*, double*, double*) Unexecuted instantiation: processor.cc:void butteraugli::ClenshawRecursion<3>(double, double const*, double*, double*) Unexecuted instantiation: processor.cc:void butteraugli::ClenshawRecursion<2>(double, double const*, double*, double*) Unexecuted instantiation: processor.cc:void butteraugli::ClenshawRecursion<1>(double, double const*, double*, double*) Unexecuted instantiation: butteraugli_comparator.cc:void butteraugli::ClenshawRecursion<5>(double, double const*, double*, double*) Unexecuted instantiation: butteraugli_comparator.cc:void butteraugli::ClenshawRecursion<4>(double, double const*, double*, double*) Unexecuted instantiation: butteraugli_comparator.cc:void butteraugli::ClenshawRecursion<3>(double, double const*, double*, double*) Unexecuted instantiation: butteraugli_comparator.cc:void butteraugli::ClenshawRecursion<2>(double, double const*, double*, double*) Unexecuted instantiation: butteraugli_comparator.cc:void butteraugli::ClenshawRecursion<1>(double, double const*, double*, double*) butteraugli.cc:void butteraugli::ClenshawRecursion<5>(double, double const*, double*, double*) Line | Count | Source | 550 | 5.41G | double *b1, double *b2) { | 551 | 5.41G | const double x_b1 = x * (*b1); | 552 | 5.41G | const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; | 553 | 5.41G | *b2 = *b1; | 554 | 5.41G | *b1 = t; | 555 | | | 556 | 5.41G | ClenshawRecursion<INDEX - 1>(x, coefficients, b1, b2); | 557 | 5.41G | } |
butteraugli.cc:void butteraugli::ClenshawRecursion<4>(double, double const*, double*, double*) Line | Count | Source | 550 | 5.41G | double *b1, double *b2) { | 551 | 5.41G | const double x_b1 = x * (*b1); | 552 | 5.41G | const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; | 553 | 5.41G | *b2 = *b1; | 554 | 5.41G | *b1 = t; | 555 | | | 556 | 5.41G | ClenshawRecursion<INDEX - 1>(x, coefficients, b1, b2); | 557 | 5.41G | } |
butteraugli.cc:void butteraugli::ClenshawRecursion<3>(double, double const*, double*, double*) Line | Count | Source | 550 | 5.41G | double *b1, double *b2) { | 551 | 5.41G | const double x_b1 = x * (*b1); | 552 | 5.41G | const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; | 553 | 5.41G | *b2 = *b1; | 554 | 5.41G | *b1 = t; | 555 | | | 556 | 5.41G | ClenshawRecursion<INDEX - 1>(x, coefficients, b1, b2); | 557 | 5.41G | } |
butteraugli.cc:void butteraugli::ClenshawRecursion<2>(double, double const*, double*, double*) Line | Count | Source | 550 | 5.41G | double *b1, double *b2) { | 551 | 5.41G | const double x_b1 = x * (*b1); | 552 | 5.41G | const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; | 553 | 5.41G | *b2 = *b1; | 554 | 5.41G | *b1 = t; | 555 | | | 556 | 5.41G | ClenshawRecursion<INDEX - 1>(x, coefficients, b1, b2); | 557 | 5.41G | } |
butteraugli.cc:void butteraugli::ClenshawRecursion<1>(double, double const*, double*, double*) Line | Count | Source | 550 | 5.41G | double *b1, double *b2) { | 551 | 5.41G | const double x_b1 = x * (*b1); | 552 | 5.41G | const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; | 553 | 5.41G | *b2 = *b1; | 554 | 5.41G | *b1 = t; | 555 | | | 556 | 5.41G | ClenshawRecursion<INDEX - 1>(x, coefficients, b1, b2); | 557 | 5.41G | } |
|
558 | | |
559 | | // Base case |
560 | | template <> |
561 | | inline void ClenshawRecursion<0>(const double x, const double *coefficients, |
562 | 5.41G | double *b1, double *b2) { |
563 | 5.41G | const double x_b1 = x * (*b1); |
564 | | // The final iteration differs - no 2 * x_b1 here. |
565 | 5.41G | *b1 = x_b1 - (*b2) + coefficients[0]; |
566 | 5.41G | } Unexecuted instantiation: processor.cc:void butteraugli::ClenshawRecursion<0>(double, double const*, double*, double*) Unexecuted instantiation: butteraugli_comparator.cc:void butteraugli::ClenshawRecursion<0>(double, double const*, double*, double*) butteraugli.cc:void butteraugli::ClenshawRecursion<0>(double, double const*, double*, double*) Line | Count | Source | 562 | 5.41G | double *b1, double *b2) { | 563 | 5.41G | const double x_b1 = x * (*b1); | 564 | | // The final iteration differs - no 2 * x_b1 here. | 565 | 5.41G | *b1 = x_b1 - (*b2) + coefficients[0]; | 566 | 5.41G | } |
|
567 | | |
568 | | // Rational polynomial := dividing two polynomial evaluations. These are easier |
569 | | // to find than minimax polynomials. |
570 | | struct RationalPolynomial { |
571 | | template <int N> |
572 | | static double EvaluatePolynomial(const double x, |
573 | 5.41G | const double (&coefficients)[N]) { |
574 | 5.41G | double b1 = 0.0; |
575 | 5.41G | double b2 = 0.0; |
576 | 5.41G | ClenshawRecursion<N - 1>(x, coefficients, &b1, &b2); |
577 | 5.41G | return b1; |
578 | 5.41G | } |
579 | | |
580 | | // Evaluates the polynomial at x (in [min_value, max_value]). |
581 | 2.70G | inline double operator()(const double x) const { |
582 | | // First normalize to [0, 1]. |
583 | 2.70G | const double x01 = (x - min_value) / (max_value - min_value); |
584 | | // And then to [-1, 1] domain of Chebyshev polynomials. |
585 | 2.70G | const double xc = 2.0 * x01 - 1.0; |
586 | | |
587 | 2.70G | const double yp = EvaluatePolynomial(xc, p); |
588 | 2.70G | const double yq = EvaluatePolynomial(xc, q); |
589 | 2.70G | if (yq == 0.0) return 0.0; |
590 | 2.70G | return static_cast<float>(yp / yq); |
591 | 2.70G | } |
592 | | |
593 | | // Domain of the polynomials; they are undefined elsewhere. |
594 | | double min_value; |
595 | | double max_value; |
596 | | |
597 | | // Coefficients of T_n (Chebyshev polynomials of the first kind). |
598 | | // Degree 5/5 is a compromise between accuracy (0.1%) and numerical stability. |
599 | | double p[5 + 1]; |
600 | | double q[5 + 1]; |
601 | | }; |
602 | | |
603 | 2.70G | static inline double GammaPolynomial(double value) { |
604 | 2.70G | static const RationalPolynomial r = { |
605 | 2.70G | 0.971783, 590.188894, |
606 | 2.70G | { |
607 | 2.70G | 98.7821300963361, 164.273222212631, 92.948112871376, |
608 | 2.70G | 33.8165311212688, 6.91626704983562, 0.556380877028234 |
609 | 2.70G | }, |
610 | 2.70G | { |
611 | 2.70G | 1, 1.64339473427892, 0.89392405219969, 0.298947051776379, |
612 | 2.70G | 0.0507146002577288, 0.00226495093949756 |
613 | 2.70G | }}; |
614 | 2.70G | return r(value); |
615 | 2.70G | } Unexecuted instantiation: processor.cc:butteraugli::GammaPolynomial(double) Unexecuted instantiation: butteraugli_comparator.cc:butteraugli::GammaPolynomial(double) butteraugli.cc:butteraugli::GammaPolynomial(double) Line | Count | Source | 603 | 2.70G | static inline double GammaPolynomial(double value) { | 604 | 2.70G | static const RationalPolynomial r = { | 605 | 2.70G | 0.971783, 590.188894, | 606 | 2.70G | { | 607 | 2.70G | 98.7821300963361, 164.273222212631, 92.948112871376, | 608 | 2.70G | 33.8165311212688, 6.91626704983562, 0.556380877028234 | 609 | 2.70G | }, | 610 | 2.70G | { | 611 | 2.70G | 1, 1.64339473427892, 0.89392405219969, 0.298947051776379, | 612 | 2.70G | 0.0507146002577288, 0.00226495093949756 | 613 | 2.70G | }}; | 614 | 2.70G | return r(value); | 615 | 2.70G | } |
|
616 | | |
617 | | } // namespace butteraugli |
618 | | |
619 | | #endif // BUTTERAUGLI_BUTTERAUGLI_H_ |