Coverage Report

Created: 2026-02-26 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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_