Coverage Report

Created: 2026-06-13 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc
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
// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com)
16
//
17
// The physical architecture of butteraugli is based on the following naming
18
// convention:
19
//   * Opsin - dynamics of the photosensitive chemicals in the retina
20
//             with their immediate electrical processing
21
//   * Xyb - hybrid opponent/trichromatic color space
22
//     x is roughly red-subtract-green.
23
//     y is yellow.
24
//     b is blue.
25
//     Xyb values are computed from Opsin mixing, not directly from rgb.
26
//   * Mask - for visual masking
27
//   * Hf - color modeling for spatially high-frequency features
28
//   * Lf - color modeling for spatially low-frequency features
29
//   * Diffmap - to cluster and build an image of error between the images
30
//   * Blur - to hold the smoothing code
31
32
#include "butteraugli/butteraugli.h"
33
34
#include <assert.h>
35
#include <math.h>
36
#include <stdint.h>
37
#include <stdio.h>
38
#include <string.h>
39
40
#include <algorithm>
41
#include <array>
42
43
44
// Restricted pointers speed up Convolution(); MSVC uses a different keyword.
45
#ifdef _MSC_VER
46
#define __restrict__ __restrict
47
#endif
48
49
#ifndef PROFILER_ENABLED
50
#define PROFILER_ENABLED 0
51
#endif
52
#if PROFILER_ENABLED
53
#else
54
#define PROFILER_FUNC
55
#define PROFILER_ZONE(name)
56
#endif
57
58
namespace butteraugli {
59
60
86.4M
void *CacheAligned::Allocate(const size_t bytes) {
61
86.4M
  char *const allocated = static_cast<char *>(malloc(bytes + kCacheLineSize));
62
86.4M
  if (allocated == nullptr) {
63
0
    return nullptr;
64
0
  }
65
86.4M
  const uintptr_t misalignment =
66
86.4M
      reinterpret_cast<uintptr_t>(allocated) & (kCacheLineSize - 1);
67
  // malloc is at least kPointerSize aligned, so we can store the "allocated"
68
  // pointer immediately before the aligned memory.
69
86.4M
  assert(misalignment % kPointerSize == 0);
70
86.4M
  char *const aligned = allocated + kCacheLineSize - misalignment;
71
86.4M
  memcpy(aligned - kPointerSize, &allocated, kPointerSize);
72
86.4M
  return BUTTERAUGLI_ASSUME_ALIGNED(aligned, 64);
73
86.4M
}
74
75
86.4M
void CacheAligned::Free(void *aligned_pointer) {
76
86.4M
  if (aligned_pointer == nullptr) {
77
0
    return;
78
0
  }
79
86.4M
  char *const aligned = static_cast<char *>(aligned_pointer);
80
86.4M
  assert(reinterpret_cast<uintptr_t>(aligned) % kCacheLineSize == 0);
81
86.4M
  char *allocated;
82
86.4M
  memcpy(&allocated, aligned - kPointerSize, kPointerSize);
83
86.4M
  assert(allocated <= aligned - kPointerSize);
84
86.4M
  assert(allocated >= aligned - kCacheLineSize);
85
86.4M
  free(allocated);
86
86.4M
}
87
88
0
static inline bool IsNan(const float x) {
89
0
  uint32_t bits;
90
0
  memcpy(&bits, &x, sizeof(bits));
91
0
  const uint32_t bitmask_exp = 0x7F800000;
92
0
  return (bits & bitmask_exp) == bitmask_exp && (bits & 0x7FFFFF);
93
0
}
94
95
0
static inline bool IsNan(const double x) {
96
0
  uint64_t bits;
97
0
  memcpy(&bits, &x, sizeof(bits));
98
0
  return (0x7ff0000000000001ULL <= bits && bits <= 0x7fffffffffffffffULL) ||
99
0
         (0xfff0000000000001ULL <= bits && bits <= 0xffffffffffffffffULL);
100
0
}
101
102
0
static inline void CheckImage(const ImageF &image, const char *name) {
103
0
  for (size_t y = 0; y < image.ysize(); ++y) {
104
0
    const float * const BUTTERAUGLI_RESTRICT row = image.Row(y);
105
0
    for (size_t x = 0; x < image.xsize(); ++x) {
106
0
      if (IsNan(row[x])) {
107
0
        printf("Image %s @ %lu,%lu (of %lu,%lu)\n", name, x, y, image.xsize(),
108
0
               image.ysize());
109
0
        exit(1);
110
0
      }
111
0
    }
112
0
  }
113
0
}
114
115
#if BUTTERAUGLI_ENABLE_CHECKS
116
117
#define CHECK_NAN(x, str)                \
118
  do {                                   \
119
    if (IsNan(x)) {                      \
120
      printf("%d: %s\n", __LINE__, str); \
121
      abort();                           \
122
    }                                    \
123
  } while (0)
124
125
#define CHECK_IMAGE(image, name) CheckImage(image, name)
126
127
#else
128
129
#define CHECK_NAN(x, str)
130
#define CHECK_IMAGE(image, name)
131
132
#endif
133
134
135
// Purpose of kInternalGoodQualityThreshold:
136
// Normalize 'ok' image degradation to 1.0 across different versions of
137
// butteraugli.
138
static const double kInternalGoodQualityThreshold = 20.35;
139
static const double kGlobalScale = 1.0 / kInternalGoodQualityThreshold;
140
141
672M
inline float DotProduct(const float u[3], const float v[3]) {
142
672M
  return u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
143
672M
}
144
145
21.3M
std::vector<float> ComputeKernel(float sigma) {
146
21.3M
  const float m = 2.25;  // Accuracy increases when m is increased.
147
21.3M
  const float scaler = -1.0 / (2 * sigma * sigma);
148
21.3M
  const int diff = std::max<int>(1, m * fabs(sigma));
149
21.3M
  std::vector<float> kernel(2 * diff + 1);
150
164M
  for (int i = -diff; i <= diff; ++i) {
151
143M
    kernel[i + diff] = exp(scaler * i * i);
152
143M
  }
153
21.3M
  return kernel;
154
21.3M
}
155
156
void ConvolveBorderColumn(
157
    const ImageF& in,
158
    const std::vector<float>& kernel,
159
    const float weight_no_border,
160
    const float border_ratio,
161
    const size_t x,
162
243M
    float* const BUTTERAUGLI_RESTRICT row_out) {
163
243M
  const int offset = kernel.size() / 2;
164
243M
  int minx = x < offset ? 0 : x - offset;
165
243M
  int maxx = std::min<int>(in.xsize() - 1, x + offset);
166
243M
  float weight = 0.0f;
167
2.83G
  for (int j = minx; j <= maxx; ++j) {
168
2.59G
    weight += kernel[j - x + offset];
169
2.59G
  }
170
  // Interpolate linearly between the no-border scaling and border scaling.
171
243M
  weight = (1.0f - border_ratio) * weight + border_ratio * weight_no_border;
172
243M
  float scale = 1.0f / weight;
173
5.97G
  for (size_t y = 0; y < in.ysize(); ++y) {
174
5.73G
    const float* const BUTTERAUGLI_RESTRICT row_in = in.Row(y);
175
5.73G
    float sum = 0.0f;
176
109G
    for (int j = minx; j <= maxx; ++j) {
177
103G
      sum += row_in[j] * kernel[j - x + offset];
178
103G
    }
179
5.73G
    row_out[y] = sum * scale;
180
5.73G
  }
181
243M
}
182
183
// Computes a horizontal convolution and transposes the result.
184
ImageF Convolution(const ImageF& in,
185
                   const std::vector<float>& kernel,
186
42.7M
                   const float border_ratio) {
187
42.7M
  ImageF out(in.ysize(), in.xsize());
188
42.7M
  const int len = kernel.size();
189
42.7M
  const int offset = kernel.size() / 2;
190
42.7M
  float weight_no_border = 0.0f;
191
329M
  for (int j = 0; j < len; ++j) {
192
286M
    weight_no_border += kernel[j];
193
286M
  }
194
42.7M
  float scale_no_border = 1.0f / weight_no_border;
195
42.7M
  const int border1 = in.xsize() <= offset ? in.xsize() : offset;
196
42.7M
  const int border2 = in.xsize() - offset;
197
42.7M
  std::vector<float> scaled_kernel = kernel;
198
329M
  for (int i = 0; i < scaled_kernel.size(); ++i) {
199
286M
    scaled_kernel[i] *= scale_no_border;
200
286M
  }
201
  // left border
202
164M
  for (int x = 0; x < border1; ++x) {
203
121M
    ConvolveBorderColumn(in, kernel, weight_no_border, border_ratio, x,
204
121M
                         out.Row(x));
205
121M
  }
206
  // middle
207
605M
  for (size_t y = 0; y < in.ysize(); ++y) {
208
563M
    const float* const BUTTERAUGLI_RESTRICT row_in = in.Row(y);
209
10.4G
    for (int x = border1; x < border2; ++x) {
210
9.86G
      const int d = x - offset;
211
9.86G
      float* const BUTTERAUGLI_RESTRICT row_out = out.Row(x);
212
9.86G
      float sum = 0.0f;
213
123G
      for (int j = 0; j < len; ++j) {
214
113G
        sum += row_in[d + j] * scaled_kernel[j];
215
113G
      }
216
9.86G
      row_out[y] = sum;
217
9.86G
    }
218
563M
  }
219
  // right border
220
164M
  for (int x = border2; x < in.xsize(); ++x) {
221
121M
    ConvolveBorderColumn(in, kernel, weight_no_border, border_ratio, x,
222
121M
                         out.Row(x));
223
121M
  }
224
42.7M
  return out;
225
42.7M
}
226
227
// A blur somewhat similar to a 2D Gaussian blur.
228
// See: https://en.wikipedia.org/wiki/Gaussian_blur
229
21.3M
ImageF Blur(const ImageF& in, float sigma, float border_ratio) {
230
21.3M
  std::vector<float> kernel = ComputeKernel(sigma);
231
21.3M
  return Convolution(Convolution(in, kernel, border_ratio),
232
21.3M
                     kernel, border_ratio);
233
21.3M
}
234
235
// Clamping linear interpolator.
236
inline double InterpolateClampNegative(const double *array,
237
2.07G
                                       int size, double ix) {
238
2.07G
  if (ix < 0) {
239
0
    ix = 0;
240
0
  }
241
2.07G
  int baseix = static_cast<int>(ix);
242
2.07G
  double res;
243
2.07G
  if (baseix >= size - 1) {
244
7.10M
    res = array[size - 1];
245
2.06G
  } else {
246
2.06G
    double mix = ix - baseix;
247
2.06G
    int nextix = baseix + 1;
248
2.06G
    res = array[baseix] + mix * (array[nextix] - array[baseix]);
249
2.06G
  }
250
2.07G
  return res;
251
2.07G
}
252
253
0
double GammaMinArg() {
254
0
  double out0, out1, out2;
255
0
  OpsinAbsorbance(0.0, 0.0, 0.0, &out0, &out1, &out2);
256
0
  return std::min(out0, std::min(out1, out2));
257
0
}
258
259
0
double GammaMaxArg() {
260
0
  double out0, out1, out2;
261
0
  OpsinAbsorbance(255.0, 255.0, 255.0, &out0, &out1, &out2);
262
0
  return std::max(out0, std::max(out1, out2));
263
0
}
264
265
0
double SimpleGamma(double v) {
266
0
  static const double kGamma = 0.372322653176;
267
0
  static const double limit = 37.8000499603;
268
0
  double bright = v - limit;
269
0
  if (bright >= 0) {
270
0
    static const double mul = 0.0950819040934;
271
0
    v -= bright * mul;
272
0
  }
273
0
  {
274
0
    static const double limit2 = 74.6154406429;
275
0
    double bright2 = v - limit2;
276
0
    if (bright2 >= 0) {
277
0
      static const double mul = 0.01;
278
0
      v -= bright2 * mul;
279
0
    }
280
0
  }
281
0
  {
282
0
    static const double limit2 = 82.8505938033;
283
0
    double bright2 = v - limit2;
284
0
    if (bright2 >= 0) {
285
0
      static const double mul = 0.0316722592629;
286
0
      v -= bright2 * mul;
287
0
    }
288
0
  }
289
0
  {
290
0
    static const double limit2 = 92.8505938033;
291
0
    double bright2 = v - limit2;
292
0
    if (bright2 >= 0) {
293
0
      static const double mul = 0.221249885752;
294
0
      v -= bright2 * mul;
295
0
    }
296
0
  }
297
0
  {
298
0
    static const double limit2 = 102.8505938033;
299
0
    double bright2 = v - limit2;
300
0
    if (bright2 >= 0) {
301
0
      static const double mul = 0.0402547853939;
302
0
      v -= bright2 * mul;
303
0
    }
304
0
  }
305
0
  {
306
0
    static const double limit2 = 112.8505938033;
307
0
    double bright2 = v - limit2;
308
0
    if (bright2 >= 0) {
309
0
      static const double mul = 0.021471798711500003;
310
0
      v -= bright2 * mul;
311
0
    }
312
0
  }
313
0
  static const double offset = 0.106544447664;
314
0
  static const double scale = 10.7950943969;
315
0
  double retval = scale * (offset + pow(v, kGamma));
316
0
  return retval;
317
0
}
318
319
3.25G
static inline double Gamma(double v) {
320
  //return SimpleGamma(v);
321
3.25G
  return GammaPolynomial(v);
322
3.25G
}
323
324
6.50M
std::vector<ImageF> OpsinDynamicsImage(const std::vector<ImageF>& rgb) {
325
6.50M
  PROFILER_FUNC;
326
6.50M
  std::vector<ImageF> xyb(3);
327
6.50M
  std::vector<ImageF> blurred(3);
328
6.50M
  const double kSigma = 1.2;
329
26.0M
  for (int i = 0; i < 3; ++i) {
330
19.5M
    xyb[i] = ImageF(rgb[i].xsize(), rgb[i].ysize());
331
19.5M
    blurred[i] = Blur(rgb[i], kSigma, 0.0f);
332
19.5M
  }
333
69.5M
  for (size_t y = 0; y < rgb[0].ysize(); ++y) {
334
63.0M
    const float* const BUTTERAUGLI_RESTRICT row_r = rgb[0].Row(y);
335
63.0M
    const float* const BUTTERAUGLI_RESTRICT row_g = rgb[1].Row(y);
336
63.0M
    const float* const BUTTERAUGLI_RESTRICT row_b = rgb[2].Row(y);
337
63.0M
    const float* const BUTTERAUGLI_RESTRICT row_blurred_r = blurred[0].Row(y);
338
63.0M
    const float* const BUTTERAUGLI_RESTRICT row_blurred_g = blurred[1].Row(y);
339
63.0M
    const float* const BUTTERAUGLI_RESTRICT row_blurred_b = blurred[2].Row(y);
340
63.0M
    float* const BUTTERAUGLI_RESTRICT row_out_x = xyb[0].Row(y);
341
63.0M
    float* const BUTTERAUGLI_RESTRICT row_out_y = xyb[1].Row(y);
342
63.0M
    float* const BUTTERAUGLI_RESTRICT row_out_b = xyb[2].Row(y);
343
1.14G
    for (size_t x = 0; x < rgb[0].xsize(); ++x) {
344
1.08G
      float sensitivity[3];
345
1.08G
      {
346
        // Calculate sensitivity based on the smoothed image gamma derivative.
347
1.08G
        float pre_mixed0, pre_mixed1, pre_mixed2;
348
1.08G
        OpsinAbsorbance(row_blurred_r[x], row_blurred_g[x], row_blurred_b[x],
349
1.08G
                        &pre_mixed0, &pre_mixed1, &pre_mixed2);
350
        // TODO: use new polynomial to compute Gamma(x)/x derivative.
351
1.08G
        sensitivity[0] = Gamma(pre_mixed0) / pre_mixed0;
352
1.08G
        sensitivity[1] = Gamma(pre_mixed1) / pre_mixed1;
353
1.08G
        sensitivity[2] = Gamma(pre_mixed2) / pre_mixed2;
354
1.08G
      }
355
1.08G
      float cur_mixed0, cur_mixed1, cur_mixed2;
356
1.08G
      OpsinAbsorbance(row_r[x], row_g[x], row_b[x],
357
1.08G
                      &cur_mixed0, &cur_mixed1, &cur_mixed2);
358
1.08G
      cur_mixed0 *= sensitivity[0];
359
1.08G
      cur_mixed1 *= sensitivity[1];
360
1.08G
      cur_mixed2 *= sensitivity[2];
361
1.08G
      RgbToXyb(cur_mixed0, cur_mixed1, cur_mixed2,
362
1.08G
               &row_out_x[x], &row_out_y[x], &row_out_b[x]);
363
1.08G
    }
364
63.0M
  }
365
6.50M
  return xyb;
366
6.50M
}
367
368
// Make area around zero less important (remove it).
369
685M
static BUTTERAUGLI_INLINE float RemoveRangeAroundZero(float w, float x) {
370
685M
  return x > w ? x - w : x < -w ? x + w : 0.0f;
371
685M
}
372
373
// Make area around zero more important (2x it until the limit).
374
342M
static BUTTERAUGLI_INLINE float AmplifyRangeAroundZero(float w, float x) {
375
342M
  return x > w ? x + w : x < -w ? x - w : 2.0f * x;
376
342M
}
377
378
// XybLowFreqToVals converts from low-frequency XYB space to the 'vals' space.
379
// Vals space can be converted to L2-norm space (Euclidean and normalized)
380
// through visual masking.
381
template <class V>
382
BUTTERAUGLI_INLINE void XybLowFreqToVals(const V &x, const V &y, const V &b_arg,
383
                                         V *BUTTERAUGLI_RESTRICT valx,
384
                                         V *BUTTERAUGLI_RESTRICT valy,
385
342M
                                         V *BUTTERAUGLI_RESTRICT valb) {
386
342M
  static const double xmuli = 5.57547552483;
387
342M
  static const double ymuli = 1.20828034498;
388
342M
  static const double bmuli = 6.08319517575;
389
342M
  static const double y_to_b_muli = -0.628811683685;
390
391
342M
  const V xmul(xmuli);
392
342M
  const V ymul(ymuli);
393
342M
  const V bmul(bmuli);
394
342M
  const V y_to_b_mul(y_to_b_muli);
395
342M
  const V b = b_arg + y_to_b_mul * y;
396
342M
  *valb = b * bmul;
397
342M
  *valx = x * xmul;
398
342M
  *valy = y * ymul;
399
342M
}
400
401
static ImageF SuppressInBrightAreas(size_t xsize, size_t ysize,
402
                                    double mul, double mul2, double reg,
403
                                    const ImageF& hf,
404
0
                                    const ImageF& brightness) {
405
0
  ImageF inew(xsize, ysize);
406
0
  for (size_t y = 0; y < ysize; ++y) {
407
0
    const float* const rowhf = hf.Row(y);
408
0
    const float* const rowbr = brightness.Row(y);
409
0
    float* const rownew = inew.Row(y);
410
0
    for (size_t x = 0; x < xsize; ++x) {
411
0
      float v = rowhf[x];
412
0
      float scaler = mul * reg / (reg + rowbr[x]);
413
0
      rownew[x] = scaler * v;
414
0
    }
415
0
  }
416
0
  return inew;
417
0
}
418
419
420
static float SuppressHfInBrightAreas(float hf, float brightness,
421
342M
                                     float mul, float reg) {
422
342M
  float scaler = mul * reg / (reg + brightness);
423
342M
  return scaler * hf;
424
342M
}
425
426
static float SuppressUhfInBrightAreas(float hf, float brightness,
427
342M
                                      float mul, float reg) {
428
342M
  float scaler = mul * reg / (reg + brightness);
429
342M
  return scaler * hf;
430
342M
}
431
432
685M
static float MaximumClamp(float v, float maxval) {
433
685M
  static const double kMul = 0.688059627878;
434
685M
  if (v >= maxval) {
435
87.7M
    v -= maxval;
436
87.7M
    v *= kMul;
437
87.7M
    v += maxval;
438
598M
  } else if (v < -maxval) {
439
116M
    v += maxval;
440
116M
    v *= kMul;
441
116M
    v -= maxval;
442
116M
  }
443
685M
  return v;
444
685M
}
445
446
static ImageF MaximumClamping(size_t xsize, size_t ysize, const ImageF& ix,
447
0
                              double yw) {
448
0
  static const double kMul = 0.688059627878;
449
0
  ImageF inew(xsize, ysize);
450
0
  for (size_t y = 0; y < ysize; ++y) {
451
0
    const float* const rowx = ix.Row(y);
452
0
    float* const rownew = inew.Row(y);
453
0
    for (size_t x = 0; x < xsize; ++x) {
454
0
      double v = rowx[x];
455
0
      if (v >= yw) {
456
0
        v -= yw;
457
0
        v *= kMul;
458
0
        v += yw;
459
0
      } else if (v < -yw) {
460
0
        v += yw;
461
0
        v *= kMul;
462
0
        v -= yw;
463
0
      }
464
0
      rownew[x] = v;
465
0
    }
466
0
  }
467
0
  return inew;
468
0
}
469
470
static ImageF SuppressXByY(size_t xsize, size_t ysize,
471
                           const ImageF& ix, const ImageF& iy,
472
145k
                           const double yw) {
473
145k
  static const double s = 0.745954517135;
474
145k
  ImageF inew(xsize, ysize);
475
6.80M
  for (size_t y = 0; y < ysize; ++y) {
476
6.66M
    const float* const rowx = ix.Row(y);
477
6.66M
    const float* const rowy = iy.Row(y);
478
6.66M
    float* const rownew = inew.Row(y);
479
349M
    for (size_t x = 0; x < xsize; ++x) {
480
342M
      const double xval = rowx[x];
481
342M
      const double yval = rowy[x];
482
342M
      const double scaler = s + (yw * (1.0 - s)) / (yw + yval * yval);
483
342M
      rownew[x] = scaler * xval;
484
342M
    }
485
6.66M
  }
486
145k
  return inew;
487
145k
}
488
489
static void SeparateFrequencies(
490
    size_t xsize, size_t ysize,
491
    const std::vector<ImageF>& xyb,
492
145k
    PsychoImage &ps) {
493
145k
  PROFILER_FUNC;
494
145k
  ps.lf.resize(3);   // XYB
495
145k
  ps.mf.resize(3);   // XYB
496
145k
  ps.hf.resize(2);   // XY
497
145k
  ps.uhf.resize(2);  // XY
498
  // Extract lf ...
499
145k
  static const double kSigmaLf = 7.46953768697;
500
145k
  static const double kSigmaHf = 3.734768843485;
501
145k
  static const double kSigmaUhf = 1.8673844217425;
502
  // At borders we move some more of the energy to the high frequency
503
  // parts, because there can be unfortunate continuations in tiling
504
  // background color etc. So we want to represent the borders with
505
  // some more accuracy.
506
145k
  static double border_lf = -0.00457628248637;
507
145k
  static double border_mf = -0.271277366628;
508
145k
  static double border_hf = 0.147068973249;
509
436k
  for (int i = 0; i < 3; ++i) {
510
436k
    ps.lf[i] = Blur(xyb[i], kSigmaLf, border_lf);
511
    // ... and keep everything else in mf.
512
436k
    ps.mf[i] = ImageF(xsize, ysize);
513
20.4M
    for (size_t y = 0; y < ysize; ++y) {
514
1.04G
      for (size_t x = 0; x < xsize; ++x) {
515
1.02G
        ps.mf[i].Row(y)[x] = xyb[i].Row(y)[x] - ps.lf[i].Row(y)[x];
516
1.02G
      }
517
19.9M
    }
518
436k
    if (i == 2) {
519
145k
      ps.mf[i] = Blur(ps.mf[i], kSigmaHf, border_mf);
520
145k
      break;
521
145k
    }
522
    // Divide mf into mf and hf.
523
291k
    ps.hf[i] = ImageF(xsize, ysize);
524
13.6M
    for (size_t y = 0; y < ysize; ++y) {
525
13.3M
      float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[i].Row(y);
526
13.3M
      float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[i].Row(y);
527
699M
      for (size_t x = 0; x < xsize; ++x) {
528
685M
        row_hf[x] = row_mf[x];
529
685M
      }
530
13.3M
    }
531
291k
    ps.mf[i] = Blur(ps.mf[i], kSigmaHf, border_mf);
532
291k
    static const double w0 = 0.120079806822;
533
291k
    static const double w1 = 0.03430529365;
534
291k
    if (i == 0) {
535
6.80M
      for (size_t y = 0; y < ysize; ++y) {
536
6.66M
        float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[0].Row(y);
537
6.66M
        float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[0].Row(y);
538
349M
        for (size_t x = 0; x < xsize; ++x) {
539
342M
          row_hf[x] -= row_mf[x];
540
342M
          row_mf[x] = RemoveRangeAroundZero(w0, row_mf[x]);
541
342M
        }
542
6.66M
      }
543
145k
    } else {
544
6.80M
      for (size_t y = 0; y < ysize; ++y) {
545
6.66M
        float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[1].Row(y);
546
6.66M
        float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[1].Row(y);
547
349M
        for (size_t x = 0; x < xsize; ++x) {
548
342M
          row_hf[x] -= row_mf[x];
549
342M
          row_mf[x] = AmplifyRangeAroundZero(w1, row_mf[x]);
550
342M
        }
551
6.66M
      }
552
145k
    }
553
291k
  }
554
  // Suppress red-green by intensity change in the high freq channels.
555
145k
  static const double suppress = 2.96534974403;
556
145k
  ps.hf[0] = SuppressXByY(xsize, ysize, ps.hf[0], ps.hf[1], suppress);
557
558
436k
  for (int i = 0; i < 2; ++i) {
559
    // Divide hf into hf and uhf.
560
291k
    ps.uhf[i] = ImageF(xsize, ysize);
561
13.6M
    for (size_t y = 0; y < ysize; ++y) {
562
13.3M
      float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[i].Row(y);
563
13.3M
      float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[i].Row(y);
564
699M
      for (size_t x = 0; x < xsize; ++x) {
565
685M
        row_uhf[x] = row_hf[x];
566
685M
      }
567
13.3M
    }
568
291k
    ps.hf[i] = Blur(ps.hf[i], kSigmaUhf, border_hf);
569
291k
    static const double kRemoveHfRange = 0.0287615200377;
570
291k
    static const double kMaxclampHf = 78.8223237675;
571
291k
    static const double kMaxclampUhf = 5.8907152736;
572
291k
    static const float kMulSuppressHf = 1.10684769012;
573
291k
    static const float kMulRegHf = 0.478741530298;
574
291k
    static const float kRegHf = 2000 * kMulRegHf;
575
291k
    static const float kMulSuppressUhf = 1.76905001176;
576
291k
    static const float kMulRegUhf = 0.310148420674;
577
291k
    static const float kRegUhf = 2000 * kMulRegUhf;
578
579
291k
    if (i == 0) {
580
6.80M
      for (size_t y = 0; y < ysize; ++y) {
581
6.66M
        float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[0].Row(y);
582
6.66M
        float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[0].Row(y);
583
349M
        for (size_t x = 0; x < xsize; ++x) {
584
342M
          row_uhf[x] -= row_hf[x];
585
342M
          row_hf[x] = RemoveRangeAroundZero(kRemoveHfRange, row_hf[x]);
586
342M
        }
587
6.66M
      }
588
145k
    } else {
589
6.80M
      for (size_t y = 0; y < ysize; ++y) {
590
6.66M
        float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[1].Row(y);
591
6.66M
        float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[1].Row(y);
592
6.66M
        float* BUTTERAUGLI_RESTRICT const row_lf = ps.lf[1].Row(y);
593
349M
        for (size_t x = 0; x < xsize; ++x) {
594
342M
          row_uhf[x] -= row_hf[x];
595
342M
          row_hf[x] = MaximumClamp(row_hf[x], kMaxclampHf);
596
342M
          row_uhf[x] = MaximumClamp(row_uhf[x], kMaxclampUhf);
597
342M
          row_uhf[x] = SuppressUhfInBrightAreas(row_uhf[x], row_lf[x],
598
342M
                                                kMulSuppressUhf, kRegUhf);
599
342M
          row_hf[x] = SuppressHfInBrightAreas(row_hf[x], row_lf[x],
600
342M
                                              kMulSuppressHf, kRegHf);
601
602
342M
        }
603
6.66M
      }
604
145k
    }
605
291k
  }
606
  // Modify range around zero code only concerns the high frequency
607
  // planes and only the X and Y channels.
608
  // Convert low freq xyb to vals space so that we can do a simple squared sum
609
  // diff on the low frequencies later.
610
6.80M
  for (size_t y = 0; y < ysize; ++y) {
611
6.66M
    float* BUTTERAUGLI_RESTRICT const row_x = ps.lf[0].Row(y);
612
6.66M
    float* BUTTERAUGLI_RESTRICT const row_y = ps.lf[1].Row(y);
613
6.66M
    float* BUTTERAUGLI_RESTRICT const row_b = ps.lf[2].Row(y);
614
349M
    for (size_t x = 0; x < xsize; ++x) {
615
342M
      float valx, valy, valb;
616
342M
      XybLowFreqToVals(row_x[x], row_y[x], row_b[x], &valx, &valy, &valb);
617
342M
      row_x[x] = valx;
618
342M
      row_y[x] = valy;
619
342M
      row_b[x] = valb;
620
342M
    }
621
6.66M
  }
622
145k
}
623
624
static void SameNoiseLevels(const ImageF& i0, const ImageF& i1,
625
                            const double kSigma,
626
                            const double w,
627
                            const double maxclamp,
628
143k
                            ImageF* BUTTERAUGLI_RESTRICT diffmap) {
629
143k
  ImageF blurred(i0.xsize(), i0.ysize());
630
6.66M
  for (size_t y = 0; y < i0.ysize(); ++y) {
631
6.51M
    const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y);
632
6.51M
    const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y);
633
6.51M
    float* BUTTERAUGLI_RESTRICT const to = blurred.Row(y);
634
342M
    for (size_t x = 0; x < i0.xsize(); ++x) {
635
336M
      double v0 = fabs(row0[x]);
636
336M
      double v1 = fabs(row1[x]);
637
336M
      if (v0 > maxclamp) v0 = maxclamp;
638
336M
      if (v1 > maxclamp) v1 = maxclamp;
639
336M
      to[x] = v0 - v1;
640
336M
    }
641
642
6.51M
  }
643
143k
  blurred = Blur(blurred, kSigma, 0.0);
644
6.66M
  for (size_t y = 0; y < i0.ysize(); ++y) {
645
6.51M
    const float* BUTTERAUGLI_RESTRICT const row = blurred.Row(y);
646
6.51M
    float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y);
647
342M
    for (size_t x = 0; x < i0.xsize(); ++x) {
648
336M
      double diff = row[x];
649
336M
      row_diff[x] += w * diff * diff;
650
336M
    }
651
6.51M
  }
652
143k
}
653
654
static void L2Diff(const ImageF& i0, const ImageF& i1, const double w,
655
858k
                   ImageF* BUTTERAUGLI_RESTRICT diffmap) {
656
858k
  if (w == 0) {
657
572k
    return;
658
572k
  }
659
13.3M
  for (size_t y = 0; y < i0.ysize(); ++y) {
660
13.0M
    const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y);
661
13.0M
    const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y);
662
13.0M
    float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y);
663
685M
    for (size_t x = 0; x < i0.xsize(); ++x) {
664
672M
      double diff = row0[x] - row1[x];
665
672M
      row_diff[x] += w * diff * diff;
666
672M
    }
667
13.0M
  }
668
286k
}
669
670
// i0 is the original image.
671
// i1 is the deformed copy.
672
static void L2DiffAsymmetric(const ImageF& i0, const ImageF& i1,
673
                             double w_0gt1,
674
                             double w_0lt1,
675
286k
                             ImageF* BUTTERAUGLI_RESTRICT diffmap) {
676
286k
  if (w_0gt1 == 0 && w_0lt1 == 0) {
677
143k
    return;
678
143k
  }
679
143k
  w_0gt1 *= 0.8;
680
143k
  w_0lt1 *= 0.8;
681
6.66M
  for (size_t y = 0; y < i0.ysize(); ++y) {
682
6.51M
    const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y);
683
6.51M
    const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y);
684
6.51M
    float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y);
685
342M
    for (size_t x = 0; x < i0.xsize(); ++x) {
686
      // Primary symmetric quadratic objective.
687
336M
      double diff = row0[x] - row1[x];
688
336M
      row_diff[x] += w_0gt1 * diff * diff;
689
690
      // Secondary half-open quadratic objectives.
691
336M
      const double fabs0 = fabs(row0[x]);
692
336M
      const double too_small = 0.4 * fabs0;
693
336M
      const double too_big = 1.0 * fabs0;
694
695
336M
      if (row0[x] < 0) {
696
164M
        if (row1[x] > -too_small) {
697
12.4M
          double v = row1[x] + too_small;
698
12.4M
          row_diff[x] += w_0lt1 * v * v;
699
152M
        } else if (row1[x] < -too_big) {
700
71.1M
          double v = -row1[x] - too_big;
701
71.1M
          row_diff[x] += w_0lt1 * v * v;
702
71.1M
        }
703
171M
      } else {
704
171M
        if (row1[x] < too_small) {
705
12.7M
          double v = too_small - row1[x];
706
12.7M
          row_diff[x] += w_0lt1 * v * v;
707
159M
        } else if (row1[x] > too_big) {
708
72.0M
          double v = row1[x] - too_big;
709
72.0M
          row_diff[x] += w_0lt1 * v * v;
710
72.0M
        }
711
171M
      }
712
336M
    }
713
6.51M
  }
714
143k
}
715
716
// Making a cluster of local errors to be more impactful than
717
// just a single error.
718
143k
ImageF CalculateDiffmap(const ImageF& diffmap_in) {
719
143k
  PROFILER_FUNC;
720
  // Take square root.
721
143k
  ImageF diffmap(diffmap_in.xsize(), diffmap_in.ysize());
722
143k
  static const float kInitialSlope = 100.0f;
723
6.66M
  for (size_t y = 0; y < diffmap.ysize(); ++y) {
724
6.51M
    const float* const BUTTERAUGLI_RESTRICT row_in = diffmap_in.Row(y);
725
6.51M
    float* const BUTTERAUGLI_RESTRICT row_out = diffmap.Row(y);
726
342M
    for (size_t x = 0; x < diffmap.xsize(); ++x) {
727
336M
      const float orig_val = row_in[x];
728
      // TODO(b/29974893): Until that is fixed do not call sqrt on very small
729
      // numbers.
730
336M
      row_out[x] = (orig_val < (1.0f / (kInitialSlope * kInitialSlope))
731
336M
                    ? kInitialSlope * orig_val
732
336M
                    : std::sqrt(orig_val));
733
336M
    }
734
6.51M
  }
735
143k
  {
736
143k
    static const double kSigma = 1.72547472444;
737
143k
    static const double mul1 = 0.458794906198;
738
143k
    static const float scale = 1.0f / (1.0f + mul1);
739
143k
    static const double border_ratio = 1.0; // 2.01209066992;
740
143k
    ImageF blurred = Blur(diffmap, kSigma, border_ratio);
741
6.66M
    for (int y = 0; y < diffmap.ysize(); ++y) {
742
6.51M
      const float* const BUTTERAUGLI_RESTRICT row_blurred = blurred.Row(y);
743
6.51M
      float* const BUTTERAUGLI_RESTRICT row = diffmap.Row(y);
744
342M
      for (int x = 0; x < diffmap.xsize(); ++x) {
745
336M
        row[x] += mul1 * row_blurred[x];
746
336M
        row[x] *= scale;
747
336M
      }
748
6.51M
    }
749
143k
  }
750
143k
  return diffmap;
751
143k
}
752
753
void MaskPsychoImage(const PsychoImage& pi0, const PsychoImage& pi1,
754
                     const size_t xsize, const size_t ysize,
755
                     std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask,
756
143k
                     std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask_dc) {
757
143k
  std::vector<ImageF> mask_xyb0 = CreatePlanes<float>(xsize, ysize, 3);
758
143k
  std::vector<ImageF> mask_xyb1 = CreatePlanes<float>(xsize, ysize, 3);
759
143k
  static const double muls[4] = {
760
143k
    0,
761
143k
    1.64178305129,
762
143k
    0.831081703362,
763
143k
    3.23680933546,
764
143k
  };
765
429k
  for (int i = 0; i < 2; ++i) {
766
286k
    double a = muls[2 * i];
767
286k
    double b = muls[2 * i + 1];
768
13.3M
    for (size_t y = 0; y < ysize; ++y) {
769
13.0M
      const float* const BUTTERAUGLI_RESTRICT row_hf0 = pi0.hf[i].Row(y);
770
13.0M
      const float* const BUTTERAUGLI_RESTRICT row_hf1 = pi1.hf[i].Row(y);
771
13.0M
      const float* const BUTTERAUGLI_RESTRICT row_uhf0 = pi0.uhf[i].Row(y);
772
13.0M
      const float* const BUTTERAUGLI_RESTRICT row_uhf1 = pi1.uhf[i].Row(y);
773
13.0M
      float* const BUTTERAUGLI_RESTRICT row0 = mask_xyb0[i].Row(y);
774
13.0M
      float* const BUTTERAUGLI_RESTRICT row1 = mask_xyb1[i].Row(y);
775
685M
      for (size_t x = 0; x < xsize; ++x) {
776
672M
        row0[x] = a * row_uhf0[x] + b * row_hf0[x];
777
672M
        row1[x] = a * row_uhf1[x] + b * row_hf1[x];
778
672M
      }
779
13.0M
    }
780
286k
  }
781
143k
  Mask(mask_xyb0, mask_xyb1, mask, mask_dc);
782
143k
}
783
784
ButteraugliComparator::ButteraugliComparator(const std::vector<ImageF>& rgb0)
785
2.54k
    : xsize_(rgb0[0].xsize()),
786
2.54k
      ysize_(rgb0[0].ysize()),
787
2.54k
      num_pixels_(xsize_ * ysize_) {
788
2.54k
  if (xsize_ < 8 || ysize_ < 8) return;
789
2.54k
  std::vector<ImageF> xyb0 = OpsinDynamicsImage(rgb0);
790
2.54k
  SeparateFrequencies(xsize_, ysize_, xyb0, pi0_);
791
2.54k
}
792
793
void ButteraugliComparator::Mask(
794
    std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask,
795
0
    std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask_dc) const {
796
0
  MaskPsychoImage(pi0_, pi0_, xsize_, ysize_, mask, mask_dc);
797
0
}
798
799
void ButteraugliComparator::Diffmap(const std::vector<ImageF>& rgb1,
800
143k
                                    ImageF &result) const {
801
143k
  PROFILER_FUNC;
802
143k
  if (xsize_ < 8 || ysize_ < 8) return;
803
143k
  DiffmapOpsinDynamicsImage(OpsinDynamicsImage(rgb1), result);
804
143k
}
805
806
void ButteraugliComparator::DiffmapOpsinDynamicsImage(
807
    const std::vector<ImageF>& xyb1,
808
143k
    ImageF &result) const {
809
143k
  PROFILER_FUNC;
810
143k
  if (xsize_ < 8 || ysize_ < 8) return;
811
143k
  PsychoImage pi1;
812
143k
  SeparateFrequencies(xsize_, ysize_, xyb1, pi1);
813
143k
  result = ImageF(xsize_, ysize_);
814
143k
  DiffmapPsychoImage(pi1, result);
815
143k
}
816
817
void ButteraugliComparator::DiffmapPsychoImage(const PsychoImage& pi1,
818
143k
                                               ImageF& result) const {
819
143k
  PROFILER_FUNC;
820
143k
  const float hf_asymmetry_ = 0.8f;
821
143k
  if (xsize_ < 8 || ysize_ < 8) {
822
0
    return;
823
0
  }
824
143k
  std::vector<ImageF> block_diff_dc(3);
825
143k
  std::vector<ImageF> block_diff_ac(3);
826
572k
  for (int c = 0; c < 3; ++c) {
827
429k
    block_diff_dc[c] = ImageF(xsize_, ysize_, 0.0);
828
429k
    block_diff_ac[c] = ImageF(xsize_, ysize_, 0.0);
829
429k
  }
830
831
143k
  static const double wUhfMalta = 5.1409625726;
832
143k
  static const double norm1Uhf = 58.5001247061;
833
143k
  MaltaDiffMap(pi0_.uhf[1], pi1.uhf[1],
834
143k
               wUhfMalta * hf_asymmetry_,
835
143k
               wUhfMalta / hf_asymmetry_,
836
143k
               norm1Uhf,
837
143k
               &block_diff_ac[1]);
838
839
143k
  static const double wUhfMaltaX = 4.91743441556;
840
143k
  static const double norm1UhfX = 687196.39002;
841
143k
  MaltaDiffMap(pi0_.uhf[0], pi1.uhf[0],
842
143k
               wUhfMaltaX * hf_asymmetry_,
843
143k
               wUhfMaltaX / hf_asymmetry_,
844
143k
               norm1UhfX,
845
143k
               &block_diff_ac[0]);
846
847
143k
  static const double wHfMalta = 153.671655716;
848
143k
  static const double norm1Hf = 83150785.9592;
849
143k
  MaltaDiffMapLF(pi0_.hf[1], pi1.hf[1],
850
143k
                 wHfMalta * sqrt(hf_asymmetry_),
851
143k
                 wHfMalta / sqrt(hf_asymmetry_),
852
143k
                 norm1Hf,
853
143k
                 &block_diff_ac[1]);
854
855
143k
  static const double wHfMaltaX = 668.358918152;
856
143k
  static const double norm1HfX = 0.882954368025;
857
143k
  MaltaDiffMapLF(pi0_.hf[0], pi1.hf[0],
858
143k
                 wHfMaltaX * sqrt(hf_asymmetry_),
859
143k
                 wHfMaltaX / sqrt(hf_asymmetry_),
860
143k
                 norm1HfX,
861
143k
                 &block_diff_ac[0]);
862
863
143k
  static const double wMfMalta = 6841.81248144;
864
143k
  static const double norm1Mf = 0.0135134962487;
865
143k
  MaltaDiffMapLF(pi0_.mf[1], pi1.mf[1], wMfMalta, wMfMalta, norm1Mf,
866
143k
                 &block_diff_ac[1]);
867
868
143k
  static const double wMfMaltaX = 813.901703816;
869
143k
  static const double norm1MfX = 16792.9322251;
870
143k
  MaltaDiffMapLF(pi0_.mf[0], pi1.mf[0], wMfMaltaX, wMfMaltaX, norm1MfX,
871
143k
                 &block_diff_ac[0]);
872
873
143k
  static const double wmul[9] = {
874
143k
    0,
875
143k
    32.4449876135,
876
143k
    0,
877
143k
    0,
878
143k
    0,
879
143k
    0,
880
143k
    1.01370836411,
881
143k
    0,
882
143k
    1.74566011615,
883
143k
  };
884
885
143k
  static const double maxclamp = 85.7047444518;
886
143k
  static const double kSigmaHfX = 10.6666499623;
887
143k
  static const double w = 884.809801415;
888
143k
  SameNoiseLevels(pi0_.hf[1], pi1.hf[1], kSigmaHfX, w, maxclamp,
889
143k
                  &block_diff_ac[1]);
890
891
572k
  for (int c = 0; c < 3; ++c) {
892
429k
    if (c < 2) {
893
286k
      L2DiffAsymmetric(pi0_.hf[c], pi1.hf[c],
894
286k
                       wmul[c] * hf_asymmetry_,
895
286k
                       wmul[c] / hf_asymmetry_,
896
286k
                       &block_diff_ac[c]);
897
286k
    }
898
429k
    L2Diff(pi0_.mf[c], pi1.mf[c], wmul[3 + c], &block_diff_ac[c]);
899
429k
    L2Diff(pi0_.lf[c], pi1.lf[c], wmul[6 + c], &block_diff_dc[c]);
900
429k
  }
901
902
143k
  std::vector<ImageF> mask_xyb;
903
143k
  std::vector<ImageF> mask_xyb_dc;
904
143k
  MaskPsychoImage(pi0_, pi1, xsize_, ysize_, &mask_xyb, &mask_xyb_dc);
905
906
143k
  result = CalculateDiffmap(
907
143k
      CombineChannels(mask_xyb, mask_xyb_dc, block_diff_dc, block_diff_ac));
908
143k
}
909
910
// Allows PaddedMaltaUnit to call either function via overloading.
911
struct MaltaTagLF {};
912
struct MaltaTag {};
913
914
static float MaltaUnit(MaltaTagLF, const float* BUTTERAUGLI_RESTRICT d,
915
1.34G
                       const int xs) {
916
1.34G
  const int xs3 = 3 * xs;
917
1.34G
  float retval = 0;
918
1.34G
  {
919
    // x grows, y constant
920
1.34G
    float sum =
921
1.34G
        d[-4] +
922
1.34G
        d[-2] +
923
1.34G
        d[0] +
924
1.34G
        d[2] +
925
1.34G
        d[4];
926
1.34G
    retval += sum * sum;
927
1.34G
  }
928
1.34G
  {
929
    // y grows, x constant
930
1.34G
    float sum =
931
1.34G
        d[-xs3 - xs] +
932
1.34G
        d[-xs - xs] +
933
1.34G
        d[0] +
934
1.34G
        d[xs + xs] +
935
1.34G
        d[xs3 + xs];
936
1.34G
    retval += sum * sum;
937
1.34G
  }
938
1.34G
  {
939
    // both grow
940
1.34G
    float sum =
941
1.34G
        d[-xs3 - 3] +
942
1.34G
        d[-xs - xs - 2] +
943
1.34G
        d[0] +
944
1.34G
        d[xs + xs + 2] +
945
1.34G
        d[xs3 + 3];
946
1.34G
    retval += sum * sum;
947
1.34G
  }
948
1.34G
  {
949
    // y grows, x shrinks
950
1.34G
    float sum =
951
1.34G
        d[-xs3 + 3] +
952
1.34G
        d[-xs - xs + 2] +
953
1.34G
        d[0] +
954
1.34G
        d[xs + xs - 2] +
955
1.34G
        d[xs3 - 3];
956
1.34G
    retval += sum * sum;
957
1.34G
  }
958
1.34G
  {
959
    // y grows -4 to 4, x shrinks 1 -> -1
960
1.34G
    float sum =
961
1.34G
        d[-xs3 - xs + 1] +
962
1.34G
        d[-xs - xs + 1] +
963
1.34G
        d[0] +
964
1.34G
        d[xs + xs - 1] +
965
1.34G
        d[xs3 + xs - 1];
966
1.34G
    retval += sum * sum;
967
1.34G
  }
968
1.34G
  {
969
    //  y grows -4 to 4, x grows -1 -> 1
970
1.34G
    float sum =
971
1.34G
        d[-xs3 - xs - 1] +
972
1.34G
        d[-xs - xs - 1] +
973
1.34G
        d[0] +
974
1.34G
        d[xs + xs + 1] +
975
1.34G
        d[xs3 + xs + 1];
976
1.34G
    retval += sum * sum;
977
1.34G
  }
978
1.34G
  {
979
    // x grows -4 to 4, y grows -1 to 1
980
1.34G
    float sum =
981
1.34G
        d[-4 - xs] +
982
1.34G
        d[-2 - xs] +
983
1.34G
        d[0] +
984
1.34G
        d[2 + xs] +
985
1.34G
        d[4 + xs];
986
1.34G
    retval += sum * sum;
987
1.34G
  }
988
1.34G
  {
989
    // x grows -4 to 4, y shrinks 1 to -1
990
1.34G
    float sum =
991
1.34G
        d[-4 + xs] +
992
1.34G
        d[-2 + xs] +
993
1.34G
        d[0] +
994
1.34G
        d[2 - xs] +
995
1.34G
        d[4 - xs];
996
1.34G
    retval += sum * sum;
997
1.34G
  }
998
1.34G
  {
999
    /* 0_________
1000
       1__*______
1001
       2___*_____
1002
       3_________
1003
       4____0____
1004
       5_________
1005
       6_____*___
1006
       7______*__
1007
       8_________ */
1008
1.34G
    float sum =
1009
1.34G
        d[-xs3 - 2] +
1010
1.34G
        d[-xs - xs - 1] +
1011
1.34G
        d[0] +
1012
1.34G
        d[xs + xs + 1] +
1013
1.34G
        d[xs3 + 2];
1014
1.34G
    retval += sum * sum;
1015
1.34G
  }
1016
1.34G
  {
1017
    /* 0_________
1018
       1______*__
1019
       2_____*___
1020
       3_________
1021
       4____0____
1022
       5_________
1023
       6___*_____
1024
       7__*______
1025
       8_________ */
1026
1.34G
    float sum =
1027
1.34G
        d[-xs3 + 2] +
1028
1.34G
        d[-xs - xs + 1] +
1029
1.34G
        d[0] +
1030
1.34G
        d[xs + xs - 1] +
1031
1.34G
        d[xs3 - 2];
1032
1.34G
    retval += sum * sum;
1033
1.34G
  }
1034
1.34G
  {
1035
    /* 0_________
1036
       1_________
1037
       2_*_______
1038
       3__*______
1039
       4____0____
1040
       5______*__
1041
       6_______*_
1042
       7_________
1043
       8_________ */
1044
1.34G
    float sum =
1045
1.34G
        d[-xs - xs - 3] +
1046
1.34G
        d[-xs - 2] +
1047
1.34G
        d[0] +
1048
1.34G
        d[xs + 2] +
1049
1.34G
        d[xs + xs + 3];
1050
1.34G
    retval += sum * sum;
1051
1.34G
  }
1052
1.34G
  {
1053
    /* 0_________
1054
       1_________
1055
       2_______*_
1056
       3______*__
1057
       4____0____
1058
       5__*______
1059
       6_*_______
1060
       7_________
1061
       8_________ */
1062
1.34G
    float sum =
1063
1.34G
        d[-xs - xs + 3] +
1064
1.34G
        d[-xs + 2] +
1065
1.34G
        d[0] +
1066
1.34G
        d[xs - 2] +
1067
1.34G
        d[xs + xs - 3];
1068
1.34G
    retval += sum * sum;
1069
1.34G
  }
1070
1.34G
  {
1071
    /* 0_________
1072
       1_________
1073
       2________*
1074
       3______*__
1075
       4____0____
1076
       5__*______
1077
       6*________
1078
       7_________
1079
       8_________ */
1080
1081
1.34G
    float sum =
1082
1.34G
        d[xs + xs - 4] +
1083
1.34G
        d[xs - 2] +
1084
1.34G
        d[0] +
1085
1.34G
        d[-xs + 2] +
1086
1.34G
        d[-xs - xs + 4];
1087
1.34G
    retval += sum * sum;
1088
1.34G
  }
1089
1.34G
  {
1090
    /* 0_________
1091
       1_________
1092
       2*________
1093
       3__*______
1094
       4____0____
1095
       5______*__
1096
       6________*
1097
       7_________
1098
       8_________ */
1099
1.34G
    float sum =
1100
1.34G
        d[-xs - xs - 4] +
1101
1.34G
        d[-xs - 2] +
1102
1.34G
        d[0] +
1103
1.34G
        d[xs + 2] +
1104
1.34G
        d[xs + xs + 4];
1105
1.34G
    retval += sum * sum;
1106
1.34G
  }
1107
1.34G
  {
1108
    /* 0__*______
1109
       1_________
1110
       2___*_____
1111
       3_________
1112
       4____0____
1113
       5_________
1114
       6_____*___
1115
       7_________
1116
       8______*__ */
1117
1.34G
    float sum =
1118
1.34G
        d[-xs3 - xs - 2] +
1119
1.34G
        d[-xs - xs - 1] +
1120
1.34G
        d[0] +
1121
1.34G
        d[xs + xs + 1] +
1122
1.34G
        d[xs3 + xs + 2];
1123
1.34G
    retval += sum * sum;
1124
1.34G
  }
1125
1.34G
  {
1126
    /* 0______*__
1127
       1_________
1128
       2_____*___
1129
       3_________
1130
       4____0____
1131
       5_________
1132
       6___*_____
1133
       7_________
1134
       8__*______ */
1135
1.34G
    float sum =
1136
1.34G
        d[-xs3 - xs + 2] +
1137
1.34G
        d[-xs - xs + 1] +
1138
1.34G
        d[0] +
1139
1.34G
        d[xs + xs - 1] +
1140
1.34G
        d[xs3 + xs - 2];
1141
1.34G
    retval += sum * sum;
1142
1.34G
  }
1143
1.34G
  return retval;
1144
1.34G
}
1145
1146
static float MaltaUnit(MaltaTag, const float* BUTTERAUGLI_RESTRICT d,
1147
672M
                       const int xs) {
1148
672M
  const int xs3 = 3 * xs;
1149
672M
  float retval = 0;
1150
672M
  {
1151
    // x grows, y constant
1152
672M
    float sum =
1153
672M
        d[-4] +
1154
672M
        d[-3] +
1155
672M
        d[-2] +
1156
672M
        d[-1] +
1157
672M
        d[0] +
1158
672M
        d[1] +
1159
672M
        d[2] +
1160
672M
        d[3] +
1161
672M
        d[4];
1162
672M
    retval += sum * sum;
1163
672M
  }
1164
672M
  {
1165
    // y grows, x constant
1166
672M
    float sum =
1167
672M
        d[-xs3 - xs] +
1168
672M
        d[-xs3] +
1169
672M
        d[-xs - xs] +
1170
672M
        d[-xs] +
1171
672M
        d[0] +
1172
672M
        d[xs] +
1173
672M
        d[xs + xs] +
1174
672M
        d[xs3] +
1175
672M
        d[xs3 + xs];
1176
672M
    retval += sum * sum;
1177
672M
  }
1178
672M
  {
1179
    // both grow
1180
672M
    float sum =
1181
672M
        d[-xs3 - 3] +
1182
672M
        d[-xs - xs - 2] +
1183
672M
        d[-xs - 1] +
1184
672M
        d[0] +
1185
672M
        d[xs + 1] +
1186
672M
        d[xs + xs + 2] +
1187
672M
        d[xs3 + 3];
1188
672M
    retval += sum * sum;
1189
672M
  }
1190
672M
  {
1191
    // y grows, x shrinks
1192
672M
    float sum =
1193
672M
        d[-xs3 + 3] +
1194
672M
        d[-xs - xs + 2] +
1195
672M
        d[-xs + 1] +
1196
672M
        d[0] +
1197
672M
        d[xs - 1] +
1198
672M
        d[xs + xs - 2] +
1199
672M
        d[xs3 - 3];
1200
672M
    retval += sum * sum;
1201
672M
  }
1202
672M
  {
1203
    // y grows -4 to 4, x shrinks 1 -> -1
1204
672M
    float sum =
1205
672M
        d[-xs3 - xs + 1] +
1206
672M
        d[-xs3 + 1] +
1207
672M
        d[-xs - xs + 1] +
1208
672M
        d[-xs] +
1209
672M
        d[0] +
1210
672M
        d[xs] +
1211
672M
        d[xs + xs - 1] +
1212
672M
        d[xs3 - 1] +
1213
672M
        d[xs3 + xs - 1];
1214
672M
    retval += sum * sum;
1215
672M
  }
1216
672M
  {
1217
    //  y grows -4 to 4, x grows -1 -> 1
1218
672M
    float sum =
1219
672M
        d[-xs3 - xs - 1] +
1220
672M
        d[-xs3 - 1] +
1221
672M
        d[-xs - xs - 1] +
1222
672M
        d[-xs] +
1223
672M
        d[0] +
1224
672M
        d[xs] +
1225
672M
        d[xs + xs + 1] +
1226
672M
        d[xs3 + 1] +
1227
672M
        d[xs3 + xs + 1];
1228
672M
    retval += sum * sum;
1229
672M
  }
1230
672M
  {
1231
    // x grows -4 to 4, y grows -1 to 1
1232
672M
    float sum =
1233
672M
        d[-4 - xs] +
1234
672M
        d[-3 - xs] +
1235
672M
        d[-2 - xs] +
1236
672M
        d[-1] +
1237
672M
        d[0] +
1238
672M
        d[1] +
1239
672M
        d[2 + xs] +
1240
672M
        d[3 + xs] +
1241
672M
        d[4 + xs];
1242
672M
    retval += sum * sum;
1243
672M
  }
1244
672M
  {
1245
    // x grows -4 to 4, y shrinks 1 to -1
1246
672M
    float sum =
1247
672M
        d[-4 + xs] +
1248
672M
        d[-3 + xs] +
1249
672M
        d[-2 + xs] +
1250
672M
        d[-1] +
1251
672M
        d[0] +
1252
672M
        d[1] +
1253
672M
        d[2 - xs] +
1254
672M
        d[3 - xs] +
1255
672M
        d[4 - xs];
1256
672M
    retval += sum * sum;
1257
672M
  }
1258
672M
  {
1259
    /* 0_________
1260
       1__*______
1261
       2___*_____
1262
       3___*_____
1263
       4____0____
1264
       5_____*___
1265
       6_____*___
1266
       7______*__
1267
       8_________ */
1268
672M
    float sum =
1269
672M
        d[-xs3 - 2] +
1270
672M
        d[-xs - xs - 1] +
1271
672M
        d[-xs - 1] +
1272
672M
        d[0] +
1273
672M
        d[xs + 1] +
1274
672M
        d[xs + xs + 1] +
1275
672M
        d[xs3 + 2];
1276
672M
    retval += sum * sum;
1277
672M
  }
1278
672M
  {
1279
    /* 0_________
1280
       1______*__
1281
       2_____*___
1282
       3_____*___
1283
       4____0____
1284
       5___*_____
1285
       6___*_____
1286
       7__*______
1287
       8_________ */
1288
672M
    float sum =
1289
672M
        d[-xs3 + 2] +
1290
672M
        d[-xs - xs + 1] +
1291
672M
        d[-xs + 1] +
1292
672M
        d[0] +
1293
672M
        d[xs - 1] +
1294
672M
        d[xs + xs - 1] +
1295
672M
        d[xs3 - 2];
1296
672M
    retval += sum * sum;
1297
672M
  }
1298
672M
  {
1299
    /* 0_________
1300
       1_________
1301
       2_*_______
1302
       3__**_____
1303
       4____0____
1304
       5_____**__
1305
       6_______*_
1306
       7_________
1307
       8_________ */
1308
672M
    float sum =
1309
672M
        d[-xs - xs - 3] +
1310
672M
        d[-xs - 2] +
1311
672M
        d[-xs - 1] +
1312
672M
        d[0] +
1313
672M
        d[xs + 1] +
1314
672M
        d[xs + 2] +
1315
672M
        d[xs + xs + 3];
1316
672M
    retval += sum * sum;
1317
672M
  }
1318
672M
  {
1319
    /* 0_________
1320
       1_________
1321
       2_______*_
1322
       3_____**__
1323
       4____0____
1324
       5__**_____
1325
       6_*_______
1326
       7_________
1327
       8_________ */
1328
672M
    float sum =
1329
672M
        d[-xs - xs + 3] +
1330
672M
        d[-xs + 2] +
1331
672M
        d[-xs + 1] +
1332
672M
        d[0] +
1333
672M
        d[xs - 1] +
1334
672M
        d[xs - 2] +
1335
672M
        d[xs + xs - 3];
1336
672M
    retval += sum * sum;
1337
672M
  }
1338
672M
  {
1339
    /* 0_________
1340
       1_________
1341
       2_________
1342
       3______**_
1343
       4____0*___
1344
       5__**_____
1345
       6**_______
1346
       7_________
1347
       8_________ */
1348
1349
672M
    float sum =
1350
672M
        d[xs + xs - 4] +
1351
672M
        d[xs + xs - 3] +
1352
672M
        d[xs - 2] +
1353
672M
        d[xs - 1] +
1354
672M
        d[0] +
1355
672M
        d[1] +
1356
672M
        d[-xs + 2] +
1357
672M
        d[-xs + 3];
1358
672M
    retval += sum * sum;
1359
672M
  }
1360
672M
  {
1361
    /* 0_________
1362
       1_________
1363
       2**_______
1364
       3__**_____
1365
       4____0*___
1366
       5______**_
1367
       6_________
1368
       7_________
1369
       8_________ */
1370
672M
    float sum =
1371
672M
        d[-xs - xs - 4] +
1372
672M
        d[-xs - xs - 3] +
1373
672M
        d[-xs - 2] +
1374
672M
        d[-xs - 1] +
1375
672M
        d[0] +
1376
672M
        d[1] +
1377
672M
        d[xs + 2] +
1378
672M
        d[xs + 3];
1379
672M
    retval += sum * sum;
1380
672M
  }
1381
672M
  {
1382
    /* 0__*______
1383
       1__*______
1384
       2___*_____
1385
       3___*_____
1386
       4____0____
1387
       5____*____
1388
       6_____*___
1389
       7_____*___
1390
       8_________ */
1391
672M
    float sum =
1392
672M
        d[-xs3 - xs - 2] +
1393
672M
        d[-xs3 - 2] +
1394
672M
        d[-xs - xs - 1] +
1395
672M
        d[-xs - 1] +
1396
672M
        d[0] +
1397
672M
        d[xs] +
1398
672M
        d[xs + xs + 1] +
1399
672M
        d[xs3 + 1];
1400
672M
    retval += sum * sum;
1401
672M
  }
1402
672M
  {
1403
    /* 0______*__
1404
       1______*__
1405
       2_____*___
1406
       3_____*___
1407
       4____0____
1408
       5____*____
1409
       6___*_____
1410
       7___*_____
1411
       8_________ */
1412
672M
    float sum =
1413
672M
        d[-xs3 - xs + 2] +
1414
672M
        d[-xs3 + 2] +
1415
672M
        d[-xs - xs + 1] +
1416
672M
        d[-xs + 1] +
1417
672M
        d[0] +
1418
672M
        d[xs] +
1419
672M
        d[xs + xs - 1] +
1420
672M
        d[xs3 - 1];
1421
672M
    retval += sum * sum;
1422
672M
  }
1423
672M
  return retval;
1424
672M
}
1425
1426
// Returns MaltaUnit. "fastMode" avoids bounds-checks when x0 and y0 are known
1427
// to be far enough from the image borders.
1428
template <bool fastMode, class Tag>
1429
static BUTTERAUGLI_INLINE float PaddedMaltaUnit(
1430
    float* const BUTTERAUGLI_RESTRICT diffs, const size_t x0, const size_t y0,
1431
2.01G
    const size_t xsize_, const size_t ysize_) {
1432
2.01G
  int ix0 = y0 * xsize_ + x0;
1433
2.01G
  const float* BUTTERAUGLI_RESTRICT d = &diffs[ix0];
1434
2.01G
  if (fastMode ||
1435
1.41G
      (x0 >= 4 && y0 >= 4 && x0 < (xsize_ - 4) && y0 < (ysize_ - 4))) {
1436
1.41G
    return MaltaUnit(Tag(), d, xsize_);
1437
1.41G
  }
1438
1439
601M
  float borderimage[9 * 9];
1440
6.01G
  for (int dy = 0; dy < 9; ++dy) {
1441
5.41G
    int y = y0 + dy - 4;
1442
5.41G
    if (y < 0 || y >= ysize_) {
1443
8.59G
      for (int dx = 0; dx < 9; ++dx) {
1444
7.73G
        borderimage[dy * 9 + dx] = 0.0f;
1445
7.73G
      }
1446
4.55G
    } else {
1447
45.5G
      for (int dx = 0; dx < 9; ++dx) {
1448
41.0G
        int x = x0 + dx - 4;
1449
41.0G
        if (x < 0 || x >= xsize_) {
1450
6.69G
          borderimage[dy * 9 + dx] = 0.0f;
1451
34.3G
        } else {
1452
34.3G
          borderimage[dy * 9 + dx] = diffs[y * xsize_ + x];
1453
34.3G
        }
1454
41.0G
      }
1455
4.55G
    }
1456
5.41G
  }
1457
601M
  return MaltaUnit(Tag(), &borderimage[4 * 9 + 4], 9);
1458
2.01G
}
butteraugli.cc:float butteraugli::PaddedMaltaUnit<false, butteraugli::MaltaTag>(float*, unsigned long, unsigned long, unsigned long, unsigned long)
Line
Count
Source
1431
200M
    const size_t xsize_, const size_t ysize_) {
1432
200M
  int ix0 = y0 * xsize_ + x0;
1433
200M
  const float* BUTTERAUGLI_RESTRICT d = &diffs[ix0];
1434
200M
  if (fastMode ||
1435
200M
      (x0 >= 4 && y0 >= 4 && x0 < (xsize_ - 4) && y0 < (ysize_ - 4))) {
1436
0
    return MaltaUnit(Tag(), d, xsize_);
1437
0
  }
1438
1439
200M
  float borderimage[9 * 9];
1440
2.00G
  for (int dy = 0; dy < 9; ++dy) {
1441
1.80G
    int y = y0 + dy - 4;
1442
1.80G
    if (y < 0 || y >= ysize_) {
1443
2.86G
      for (int dx = 0; dx < 9; ++dx) {
1444
2.57G
        borderimage[dy * 9 + dx] = 0.0f;
1445
2.57G
      }
1446
1.51G
    } else {
1447
15.1G
      for (int dx = 0; dx < 9; ++dx) {
1448
13.6G
        int x = x0 + dx - 4;
1449
13.6G
        if (x < 0 || x >= xsize_) {
1450
2.23G
          borderimage[dy * 9 + dx] = 0.0f;
1451
11.4G
        } else {
1452
11.4G
          borderimage[dy * 9 + dx] = diffs[y * xsize_ + x];
1453
11.4G
        }
1454
13.6G
      }
1455
1.51G
    }
1456
1.80G
  }
1457
200M
  return MaltaUnit(Tag(), &borderimage[4 * 9 + 4], 9);
1458
200M
}
butteraugli.cc:float butteraugli::PaddedMaltaUnit<true, butteraugli::MaltaTag>(float*, unsigned long, unsigned long, unsigned long, unsigned long)
Line
Count
Source
1431
472M
    const size_t xsize_, const size_t ysize_) {
1432
472M
  int ix0 = y0 * xsize_ + x0;
1433
472M
  const float* BUTTERAUGLI_RESTRICT d = &diffs[ix0];
1434
472M
  if (fastMode ||
1435
472M
      (x0 >= 4 && y0 >= 4 && x0 < (xsize_ - 4) && y0 < (ysize_ - 4))) {
1436
472M
    return MaltaUnit(Tag(), d, xsize_);
1437
472M
  }
1438
1439
0
  float borderimage[9 * 9];
1440
0
  for (int dy = 0; dy < 9; ++dy) {
1441
0
    int y = y0 + dy - 4;
1442
0
    if (y < 0 || y >= ysize_) {
1443
0
      for (int dx = 0; dx < 9; ++dx) {
1444
0
        borderimage[dy * 9 + dx] = 0.0f;
1445
0
      }
1446
0
    } else {
1447
0
      for (int dx = 0; dx < 9; ++dx) {
1448
0
        int x = x0 + dx - 4;
1449
0
        if (x < 0 || x >= xsize_) {
1450
0
          borderimage[dy * 9 + dx] = 0.0f;
1451
0
        } else {
1452
0
          borderimage[dy * 9 + dx] = diffs[y * xsize_ + x];
1453
0
        }
1454
0
      }
1455
0
    }
1456
0
  }
1457
0
  return MaltaUnit(Tag(), &borderimage[4 * 9 + 4], 9);
1458
472M
}
butteraugli.cc:float butteraugli::PaddedMaltaUnit<false, butteraugli::MaltaTagLF>(float*, unsigned long, unsigned long, unsigned long, unsigned long)
Line
Count
Source
1431
401M
    const size_t xsize_, const size_t ysize_) {
1432
401M
  int ix0 = y0 * xsize_ + x0;
1433
401M
  const float* BUTTERAUGLI_RESTRICT d = &diffs[ix0];
1434
401M
  if (fastMode ||
1435
401M
      (x0 >= 4 && y0 >= 4 && x0 < (xsize_ - 4) && y0 < (ysize_ - 4))) {
1436
0
    return MaltaUnit(Tag(), d, xsize_);
1437
0
  }
1438
1439
401M
  float borderimage[9 * 9];
1440
4.01G
  for (int dy = 0; dy < 9; ++dy) {
1441
3.61G
    int y = y0 + dy - 4;
1442
3.61G
    if (y < 0 || y >= ysize_) {
1443
5.72G
      for (int dx = 0; dx < 9; ++dx) {
1444
5.15G
        borderimage[dy * 9 + dx] = 0.0f;
1445
5.15G
      }
1446
3.03G
    } else {
1447
30.3G
      for (int dx = 0; dx < 9; ++dx) {
1448
27.3G
        int x = x0 + dx - 4;
1449
27.3G
        if (x < 0 || x >= xsize_) {
1450
4.46G
          borderimage[dy * 9 + dx] = 0.0f;
1451
22.8G
        } else {
1452
22.8G
          borderimage[dy * 9 + dx] = diffs[y * xsize_ + x];
1453
22.8G
        }
1454
27.3G
      }
1455
3.03G
    }
1456
3.61G
  }
1457
401M
  return MaltaUnit(Tag(), &borderimage[4 * 9 + 4], 9);
1458
401M
}
butteraugli.cc:float butteraugli::PaddedMaltaUnit<true, butteraugli::MaltaTagLF>(float*, unsigned long, unsigned long, unsigned long, unsigned long)
Line
Count
Source
1431
944M
    const size_t xsize_, const size_t ysize_) {
1432
944M
  int ix0 = y0 * xsize_ + x0;
1433
944M
  const float* BUTTERAUGLI_RESTRICT d = &diffs[ix0];
1434
944M
  if (fastMode ||
1435
944M
      (x0 >= 4 && y0 >= 4 && x0 < (xsize_ - 4) && y0 < (ysize_ - 4))) {
1436
944M
    return MaltaUnit(Tag(), d, xsize_);
1437
944M
  }
1438
1439
0
  float borderimage[9 * 9];
1440
0
  for (int dy = 0; dy < 9; ++dy) {
1441
0
    int y = y0 + dy - 4;
1442
0
    if (y < 0 || y >= ysize_) {
1443
0
      for (int dx = 0; dx < 9; ++dx) {
1444
0
        borderimage[dy * 9 + dx] = 0.0f;
1445
0
      }
1446
0
    } else {
1447
0
      for (int dx = 0; dx < 9; ++dx) {
1448
0
        int x = x0 + dx - 4;
1449
0
        if (x < 0 || x >= xsize_) {
1450
0
          borderimage[dy * 9 + dx] = 0.0f;
1451
0
        } else {
1452
0
          borderimage[dy * 9 + dx] = diffs[y * xsize_ + x];
1453
0
        }
1454
0
      }
1455
0
    }
1456
0
  }
1457
0
  return MaltaUnit(Tag(), &borderimage[4 * 9 + 4], 9);
1458
944M
}
1459
1460
template <class Tag>
1461
static void MaltaDiffMapImpl(const ImageF& lum0, const ImageF& lum1,
1462
                             const size_t xsize_, const size_t ysize_,
1463
                             const double w_0gt1,
1464
                             const double w_0lt1,
1465
                             double norm1,
1466
                             const double len, const double mulli,
1467
858k
                             ImageF* block_diff_ac) {
1468
858k
  const float kWeight0 = 0.5;
1469
858k
  const float kWeight1 = 0.33;
1470
1471
858k
  const double w_pre0gt1 = mulli * sqrt(kWeight0 * w_0gt1) / (len * 2 + 1);
1472
858k
  const double w_pre0lt1 = mulli * sqrt(kWeight1 * w_0lt1) / (len * 2 + 1);
1473
858k
  const float norm2_0gt1 = w_pre0gt1 * norm1;
1474
858k
  const float norm2_0lt1 = w_pre0lt1 * norm1;
1475
1476
858k
  std::vector<float> diffs(ysize_ * xsize_);
1477
39.9M
  for (size_t y = 0, ix = 0; y < ysize_; ++y) {
1478
39.1M
    const float* BUTTERAUGLI_RESTRICT const row0 = lum0.Row(y);
1479
39.1M
    const float* BUTTERAUGLI_RESTRICT const row1 = lum1.Row(y);
1480
2.05G
    for (size_t x = 0; x < xsize_; ++x, ++ix) {
1481
2.01G
      const float absval = 0.5 * std::abs(row0[x]) + 0.5 * std::abs(row1[x]);
1482
2.01G
      const float diff = row0[x] - row1[x];
1483
2.01G
      const float scaler = norm2_0gt1 / (static_cast<float>(norm1) + absval);
1484
1485
      // Primary symmetric quadratic objective.
1486
2.01G
      diffs[ix] = scaler * diff;
1487
1488
2.01G
      const float scaler2 = norm2_0lt1 / (static_cast<float>(norm1) + absval);
1489
2.01G
      const double fabs0 = fabs(row0[x]);
1490
1491
      // Secondary half-open quadratic objectives.
1492
2.01G
      const double too_small = 0.55 * fabs0;
1493
2.01G
      const double too_big = 1.05 * fabs0;
1494
1495
2.01G
      if (row0[x] < 0) {
1496
828M
        if (row1[x] > -too_small) {
1497
83.8M
          double impact = scaler2 * (row1[x] + too_small);
1498
83.8M
          if (diff < 0) {
1499
83.8M
            diffs[ix] -= impact;
1500
83.8M
          } else {
1501
0
            diffs[ix] += impact;
1502
0
          }
1503
745M
        } else if (row1[x] < -too_big) {
1504
189M
          double impact = scaler2 * (-row1[x] - too_big);
1505
189M
          if (diff < 0) {
1506
0
            diffs[ix] -= impact;
1507
189M
          } else {
1508
189M
            diffs[ix] += impact;
1509
189M
          }
1510
189M
        }
1511
1.18G
      } else {
1512
1.18G
        if (row1[x] < too_small) {
1513
90.1M
          double impact = scaler2 * (too_small - row1[x]);
1514
90.1M
          if (diff < 0) {
1515
0
            diffs[ix] -= impact;
1516
90.1M
          } else {
1517
90.1M
            diffs[ix] += impact;
1518
90.1M
          }
1519
1.09G
        } else if (row1[x] > too_big) {
1520
189M
          double impact = scaler2 * (row1[x] - too_big);
1521
189M
          if (diff < 0) {
1522
189M
            diffs[ix] -= impact;
1523
189M
          } else {
1524
0
            diffs[ix] += impact;
1525
0
          }
1526
189M
        }
1527
1.18G
      }
1528
2.01G
    }
1529
39.1M
  }
1530
1531
858k
  size_t y0 = 0;
1532
  // Top
1533
4.29M
  for (; y0 < 4; ++y0) {
1534
3.43M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1535
175M
    for (size_t x0 = 0; x0 < xsize_; ++x0) {
1536
171M
      row_diff[x0] +=
1537
171M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1538
171M
    }
1539
3.43M
  }
1540
1541
  // Middle
1542
33.1M
  for (; y0 < ysize_ - 4; ++y0) {
1543
32.2M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1544
32.2M
    size_t x0 = 0;
1545
161M
    for (; x0 < 4; ++x0) {
1546
129M
      row_diff[x0] +=
1547
129M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1548
129M
    }
1549
1.44G
    for (; x0 < xsize_ - 4; ++x0) {
1550
1.41G
      row_diff[x0] +=
1551
1.41G
          PaddedMaltaUnit<true, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1552
1.41G
    }
1553
1554
161M
    for (; x0 < xsize_; ++x0) {
1555
129M
      row_diff[x0] +=
1556
129M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1557
129M
    }
1558
32.2M
  }
1559
1560
  // Bottom
1561
4.29M
  for (; y0 < ysize_; ++y0) {
1562
3.43M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1563
175M
    for (size_t x0 = 0; x0 < xsize_; ++x0) {
1564
171M
      row_diff[x0] +=
1565
171M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1566
171M
    }
1567
3.43M
  }
1568
858k
}
butteraugli.cc:void butteraugli::MaltaDiffMapImpl<butteraugli::MaltaTag>(butteraugli::Image<float> const&, butteraugli::Image<float> const&, unsigned long, unsigned long, double, double, double, double, double, butteraugli::Image<float>*)
Line
Count
Source
1467
286k
                             ImageF* block_diff_ac) {
1468
286k
  const float kWeight0 = 0.5;
1469
286k
  const float kWeight1 = 0.33;
1470
1471
286k
  const double w_pre0gt1 = mulli * sqrt(kWeight0 * w_0gt1) / (len * 2 + 1);
1472
286k
  const double w_pre0lt1 = mulli * sqrt(kWeight1 * w_0lt1) / (len * 2 + 1);
1473
286k
  const float norm2_0gt1 = w_pre0gt1 * norm1;
1474
286k
  const float norm2_0lt1 = w_pre0lt1 * norm1;
1475
1476
286k
  std::vector<float> diffs(ysize_ * xsize_);
1477
13.3M
  for (size_t y = 0, ix = 0; y < ysize_; ++y) {
1478
13.0M
    const float* BUTTERAUGLI_RESTRICT const row0 = lum0.Row(y);
1479
13.0M
    const float* BUTTERAUGLI_RESTRICT const row1 = lum1.Row(y);
1480
685M
    for (size_t x = 0; x < xsize_; ++x, ++ix) {
1481
672M
      const float absval = 0.5 * std::abs(row0[x]) + 0.5 * std::abs(row1[x]);
1482
672M
      const float diff = row0[x] - row1[x];
1483
672M
      const float scaler = norm2_0gt1 / (static_cast<float>(norm1) + absval);
1484
1485
      // Primary symmetric quadratic objective.
1486
672M
      diffs[ix] = scaler * diff;
1487
1488
672M
      const float scaler2 = norm2_0lt1 / (static_cast<float>(norm1) + absval);
1489
672M
      const double fabs0 = fabs(row0[x]);
1490
1491
      // Secondary half-open quadratic objectives.
1492
672M
      const double too_small = 0.55 * fabs0;
1493
672M
      const double too_big = 1.05 * fabs0;
1494
1495
672M
      if (row0[x] < 0) {
1496
350M
        if (row1[x] > -too_small) {
1497
41.5M
          double impact = scaler2 * (row1[x] + too_small);
1498
41.5M
          if (diff < 0) {
1499
41.5M
            diffs[ix] -= impact;
1500
41.5M
          } else {
1501
0
            diffs[ix] += impact;
1502
0
          }
1503
309M
        } else if (row1[x] < -too_big) {
1504
79.3M
          double impact = scaler2 * (-row1[x] - too_big);
1505
79.3M
          if (diff < 0) {
1506
0
            diffs[ix] -= impact;
1507
79.3M
          } else {
1508
79.3M
            diffs[ix] += impact;
1509
79.3M
          }
1510
79.3M
        }
1511
350M
      } else {
1512
321M
        if (row1[x] < too_small) {
1513
41.0M
          double impact = scaler2 * (too_small - row1[x]);
1514
41.0M
          if (diff < 0) {
1515
0
            diffs[ix] -= impact;
1516
41.0M
          } else {
1517
41.0M
            diffs[ix] += impact;
1518
41.0M
          }
1519
280M
        } else if (row1[x] > too_big) {
1520
75.0M
          double impact = scaler2 * (row1[x] - too_big);
1521
75.0M
          if (diff < 0) {
1522
75.0M
            diffs[ix] -= impact;
1523
75.0M
          } else {
1524
0
            diffs[ix] += impact;
1525
0
          }
1526
75.0M
        }
1527
321M
      }
1528
672M
    }
1529
13.0M
  }
1530
1531
286k
  size_t y0 = 0;
1532
  // Top
1533
1.43M
  for (; y0 < 4; ++y0) {
1534
1.14M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1535
58.4M
    for (size_t x0 = 0; x0 < xsize_; ++x0) {
1536
57.2M
      row_diff[x0] +=
1537
57.2M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1538
57.2M
    }
1539
1.14M
  }
1540
1541
  // Middle
1542
11.0M
  for (; y0 < ysize_ - 4; ++y0) {
1543
10.7M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1544
10.7M
    size_t x0 = 0;
1545
53.7M
    for (; x0 < 4; ++x0) {
1546
43.0M
      row_diff[x0] +=
1547
43.0M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1548
43.0M
    }
1549
482M
    for (; x0 < xsize_ - 4; ++x0) {
1550
472M
      row_diff[x0] +=
1551
472M
          PaddedMaltaUnit<true, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1552
472M
    }
1553
1554
53.7M
    for (; x0 < xsize_; ++x0) {
1555
43.0M
      row_diff[x0] +=
1556
43.0M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1557
43.0M
    }
1558
10.7M
  }
1559
1560
  // Bottom
1561
1.43M
  for (; y0 < ysize_; ++y0) {
1562
1.14M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1563
58.4M
    for (size_t x0 = 0; x0 < xsize_; ++x0) {
1564
57.2M
      row_diff[x0] +=
1565
57.2M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1566
57.2M
    }
1567
1.14M
  }
1568
286k
}
butteraugli.cc:void butteraugli::MaltaDiffMapImpl<butteraugli::MaltaTagLF>(butteraugli::Image<float> const&, butteraugli::Image<float> const&, unsigned long, unsigned long, double, double, double, double, double, butteraugli::Image<float>*)
Line
Count
Source
1467
572k
                             ImageF* block_diff_ac) {
1468
572k
  const float kWeight0 = 0.5;
1469
572k
  const float kWeight1 = 0.33;
1470
1471
572k
  const double w_pre0gt1 = mulli * sqrt(kWeight0 * w_0gt1) / (len * 2 + 1);
1472
572k
  const double w_pre0lt1 = mulli * sqrt(kWeight1 * w_0lt1) / (len * 2 + 1);
1473
572k
  const float norm2_0gt1 = w_pre0gt1 * norm1;
1474
572k
  const float norm2_0lt1 = w_pre0lt1 * norm1;
1475
1476
572k
  std::vector<float> diffs(ysize_ * xsize_);
1477
26.6M
  for (size_t y = 0, ix = 0; y < ysize_; ++y) {
1478
26.0M
    const float* BUTTERAUGLI_RESTRICT const row0 = lum0.Row(y);
1479
26.0M
    const float* BUTTERAUGLI_RESTRICT const row1 = lum1.Row(y);
1480
1.37G
    for (size_t x = 0; x < xsize_; ++x, ++ix) {
1481
1.34G
      const float absval = 0.5 * std::abs(row0[x]) + 0.5 * std::abs(row1[x]);
1482
1.34G
      const float diff = row0[x] - row1[x];
1483
1.34G
      const float scaler = norm2_0gt1 / (static_cast<float>(norm1) + absval);
1484
1485
      // Primary symmetric quadratic objective.
1486
1.34G
      diffs[ix] = scaler * diff;
1487
1488
1.34G
      const float scaler2 = norm2_0lt1 / (static_cast<float>(norm1) + absval);
1489
1.34G
      const double fabs0 = fabs(row0[x]);
1490
1491
      // Secondary half-open quadratic objectives.
1492
1.34G
      const double too_small = 0.55 * fabs0;
1493
1.34G
      const double too_big = 1.05 * fabs0;
1494
1495
1.34G
      if (row0[x] < 0) {
1496
478M
        if (row1[x] > -too_small) {
1497
42.3M
          double impact = scaler2 * (row1[x] + too_small);
1498
42.3M
          if (diff < 0) {
1499
42.3M
            diffs[ix] -= impact;
1500
42.3M
          } else {
1501
0
            diffs[ix] += impact;
1502
0
          }
1503
435M
        } else if (row1[x] < -too_big) {
1504
110M
          double impact = scaler2 * (-row1[x] - too_big);
1505
110M
          if (diff < 0) {
1506
0
            diffs[ix] -= impact;
1507
110M
          } else {
1508
110M
            diffs[ix] += impact;
1509
110M
          }
1510
110M
        }
1511
867M
      } else {
1512
867M
        if (row1[x] < too_small) {
1513
49.0M
          double impact = scaler2 * (too_small - row1[x]);
1514
49.0M
          if (diff < 0) {
1515
0
            diffs[ix] -= impact;
1516
49.0M
          } else {
1517
49.0M
            diffs[ix] += impact;
1518
49.0M
          }
1519
818M
        } else if (row1[x] > too_big) {
1520
114M
          double impact = scaler2 * (row1[x] - too_big);
1521
114M
          if (diff < 0) {
1522
114M
            diffs[ix] -= impact;
1523
114M
          } else {
1524
0
            diffs[ix] += impact;
1525
0
          }
1526
114M
        }
1527
867M
      }
1528
1.34G
    }
1529
26.0M
  }
1530
1531
572k
  size_t y0 = 0;
1532
  // Top
1533
2.86M
  for (; y0 < 4; ++y0) {
1534
2.28M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1535
116M
    for (size_t x0 = 0; x0 < xsize_; ++x0) {
1536
114M
      row_diff[x0] +=
1537
114M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1538
114M
    }
1539
2.28M
  }
1540
1541
  // Middle
1542
22.0M
  for (; y0 < ysize_ - 4; ++y0) {
1543
21.5M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1544
21.5M
    size_t x0 = 0;
1545
107M
    for (; x0 < 4; ++x0) {
1546
86.0M
      row_diff[x0] +=
1547
86.0M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1548
86.0M
    }
1549
965M
    for (; x0 < xsize_ - 4; ++x0) {
1550
944M
      row_diff[x0] +=
1551
944M
          PaddedMaltaUnit<true, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1552
944M
    }
1553
1554
107M
    for (; x0 < xsize_; ++x0) {
1555
86.0M
      row_diff[x0] +=
1556
86.0M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1557
86.0M
    }
1558
21.5M
  }
1559
1560
  // Bottom
1561
2.86M
  for (; y0 < ysize_; ++y0) {
1562
2.28M
    float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0);
1563
116M
    for (size_t x0 = 0; x0 < xsize_; ++x0) {
1564
114M
      row_diff[x0] +=
1565
114M
          PaddedMaltaUnit<false, Tag>(&diffs[0], x0, y0, xsize_, ysize_);
1566
114M
    }
1567
2.28M
  }
1568
572k
}
1569
1570
void ButteraugliComparator::MaltaDiffMap(
1571
    const ImageF& lum0, const ImageF& lum1,
1572
    const double w_0gt1,
1573
    const double w_0lt1,
1574
286k
    const double norm1, ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const {
1575
286k
  PROFILER_FUNC;
1576
286k
  const double len = 3.75;
1577
286k
  static const double mulli = 0.354191303559;
1578
286k
  MaltaDiffMapImpl<MaltaTag>(lum0, lum1, xsize_, ysize_, w_0gt1, w_0lt1,
1579
286k
                             norm1, len,
1580
286k
                             mulli, block_diff_ac);
1581
286k
}
1582
1583
void ButteraugliComparator::MaltaDiffMapLF(
1584
    const ImageF& lum0, const ImageF& lum1,
1585
    const double w_0gt1,
1586
    const double w_0lt1,
1587
572k
    const double norm1, ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const {
1588
572k
  PROFILER_FUNC;
1589
572k
  const double len = 3.75;
1590
572k
  static const double mulli = 0.405371989604;
1591
572k
  MaltaDiffMapImpl<MaltaTagLF>(lum0, lum1, xsize_, ysize_,
1592
572k
                               w_0gt1, w_0lt1,
1593
572k
                               norm1, len,
1594
572k
                               mulli, block_diff_ac);
1595
572k
}
1596
1597
ImageF ButteraugliComparator::CombineChannels(
1598
    const std::vector<ImageF>& mask_xyb,
1599
    const std::vector<ImageF>& mask_xyb_dc,
1600
    const std::vector<ImageF>& block_diff_dc,
1601
143k
    const std::vector<ImageF>& block_diff_ac) const {
1602
143k
  PROFILER_FUNC;
1603
143k
  ImageF result(xsize_, ysize_);
1604
6.66M
  for (size_t y = 0; y < ysize_; ++y) {
1605
6.51M
    float* const BUTTERAUGLI_RESTRICT row_out = result.Row(y);
1606
342M
    for (size_t x = 0; x < xsize_; ++x) {
1607
336M
      float mask[3];
1608
336M
      float dc_mask[3];
1609
336M
      float diff_dc[3];
1610
336M
      float diff_ac[3];
1611
1.34G
      for (int i = 0; i < 3; ++i) {
1612
1.00G
        mask[i] = mask_xyb[i].Row(y)[x];
1613
1.00G
        dc_mask[i] = mask_xyb_dc[i].Row(y)[x];
1614
1.00G
        diff_dc[i] = block_diff_dc[i].Row(y)[x];
1615
1.00G
        diff_ac[i] = block_diff_ac[i].Row(y)[x];
1616
1.00G
      }
1617
336M
      row_out[x] = (DotProduct(diff_dc, dc_mask) + DotProduct(diff_ac, mask));
1618
336M
    }
1619
6.51M
  }
1620
143k
  return result;
1621
143k
}
1622
1623
143k
double ButteraugliScoreFromDiffmap(const ImageF& diffmap) {
1624
143k
  PROFILER_FUNC;
1625
143k
  float retval = 0.0f;
1626
6.66M
  for (size_t y = 0; y < diffmap.ysize(); ++y) {
1627
6.51M
    const float * const BUTTERAUGLI_RESTRICT row = diffmap.Row(y);
1628
342M
    for (size_t x = 0; x < diffmap.xsize(); ++x) {
1629
336M
      retval = std::max(retval, row[x]);
1630
336M
    }
1631
6.51M
  }
1632
143k
  return retval;
1633
143k
}
1634
1635
#include <stdio.h>
1636
1637
// ===== Functions used by Mask only =====
1638
static std::array<double, 512> MakeMask(
1639
    double extmul, double extoff,
1640
    double mul, double offset,
1641
4
    double scaler) {
1642
4
  std::array<double, 512> lut;
1643
2.05k
  for (int i = 0; i < lut.size(); ++i) {
1644
2.04k
    const double c = mul / ((0.01 * scaler * i) + offset);
1645
2.04k
    lut[i] = kGlobalScale * (1.0 + extmul * (c + extoff));
1646
2.04k
    if (lut[i] < 1e-5) {
1647
0
      lut[i] = 1e-5;
1648
0
    }
1649
2.04k
    assert(lut[i] >= 0.0);
1650
2.04k
    lut[i] *= lut[i];
1651
2.04k
  }
1652
4
  return lut;
1653
4
}
1654
1655
345M
double MaskX(double delta) {
1656
345M
  static const double extmul = 2.59885507073;
1657
345M
  static const double extoff = 3.08805636789;
1658
345M
  static const double offset = 0.315424196682;
1659
345M
  static const double scaler = 16.2770141832;
1660
345M
  static const double mul = 5.62939030582;
1661
345M
  static const std::array<double, 512> lut =
1662
345M
                MakeMask(extmul, extoff, mul, offset, scaler);
1663
345M
  return InterpolateClampNegative(lut.data(), lut.size(), delta);
1664
345M
}
1665
1666
690M
double MaskY(double delta) {
1667
690M
  static const double extmul = 0.9613705131;
1668
690M
  static const double extoff = -0.581933100068;
1669
690M
  static const double offset = 1.00846207765;
1670
690M
  static const double scaler = 2.2342321176;
1671
690M
  static const double mul = 6.64307621174;
1672
690M
  static const std::array<double, 512> lut =
1673
690M
      MakeMask(extmul, extoff, mul, offset, scaler);
1674
690M
  return InterpolateClampNegative(lut.data(), lut.size(), delta);
1675
690M
}
1676
1677
345M
double MaskDcX(double delta) {
1678
345M
  static const double extmul = 10.0470705878;
1679
345M
  static const double extoff = 3.18472654033;
1680
345M
  static const double offset = 0.0551512255218;
1681
345M
  static const double scaler = 70.0;
1682
345M
  static const double mul = 0.373092999662;
1683
345M
  static const std::array<double, 512> lut =
1684
345M
      MakeMask(extmul, extoff, mul, offset, scaler);
1685
345M
  return InterpolateClampNegative(lut.data(), lut.size(), delta);
1686
345M
}
1687
1688
690M
double MaskDcY(double delta) {
1689
690M
  static const double extmul = 0.0115640939227;
1690
690M
  static const double extoff = 45.9483175519;
1691
690M
  static const double offset = 0.0142290066313;
1692
690M
  static const double scaler = 5.0;
1693
690M
  static const double mul = 2.52611324247;
1694
690M
  static const std::array<double, 512> lut =
1695
690M
      MakeMask(extmul, extoff, mul, offset, scaler);
1696
690M
  return InterpolateClampNegative(lut.data(), lut.size(), delta);
1697
690M
}
1698
1699
293k
ImageF DiffPrecompute(const ImageF& xyb0, const ImageF& xyb1) {
1700
293k
  PROFILER_FUNC;
1701
293k
  const size_t xsize = xyb0.xsize();
1702
293k
  const size_t ysize = xyb0.ysize();
1703
293k
  ImageF result(xsize, ysize);
1704
293k
  size_t x2, y2;
1705
13.7M
  for (size_t y = 0; y < ysize; ++y) {
1706
13.4M
    if (y + 1 < ysize) {
1707
13.1M
      y2 = y + 1;
1708
13.1M
    } else if (y > 0) {
1709
293k
      y2 = y - 1;
1710
293k
    } else {
1711
0
      y2 = y;
1712
0
    }
1713
13.4M
    const float* const BUTTERAUGLI_RESTRICT row0_in = xyb0.Row(y);
1714
13.4M
    const float* const BUTTERAUGLI_RESTRICT row1_in = xyb1.Row(y);
1715
13.4M
    const float* const BUTTERAUGLI_RESTRICT row0_in2 = xyb0.Row(y2);
1716
13.4M
    const float* const BUTTERAUGLI_RESTRICT row1_in2 = xyb1.Row(y2);
1717
13.4M
    float* const BUTTERAUGLI_RESTRICT row_out = result.Row(y);
1718
704M
    for (size_t x = 0; x < xsize; ++x) {
1719
690M
      if (x + 1 < xsize) {
1720
677M
        x2 = x + 1;
1721
677M
      } else if (x > 0) {
1722
13.4M
        x2 = x - 1;
1723
13.4M
      } else {
1724
0
        x2 = x;
1725
0
      }
1726
690M
      double sup0 = (fabs(row0_in[x] - row0_in[x2]) +
1727
690M
                     fabs(row0_in[x] - row0_in2[x]));
1728
690M
      double sup1 = (fabs(row1_in[x] - row1_in[x2]) +
1729
690M
                     fabs(row1_in[x] - row1_in2[x]));
1730
690M
      static const double mul0 = 0.918416534734;
1731
690M
      row_out[x] = mul0 * std::min(sup0, sup1);
1732
690M
      static const double cutoff = 55.0184555849;
1733
690M
      if (row_out[x] >= cutoff) {
1734
114M
        row_out[x] = cutoff;
1735
114M
      }
1736
690M
    }
1737
13.4M
  }
1738
293k
  return result;
1739
293k
}
1740
1741
void Mask(const std::vector<ImageF>& xyb0,
1742
          const std::vector<ImageF>& xyb1,
1743
          std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask,
1744
146k
          std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask_dc) {
1745
146k
  PROFILER_FUNC;
1746
146k
  const size_t xsize = xyb0[0].xsize();
1747
146k
  const size_t ysize = xyb0[0].ysize();
1748
146k
  mask->resize(3);
1749
146k
  *mask_dc = CreatePlanes<float>(xsize, ysize, 3);
1750
146k
  double muls[2] = {
1751
146k
    0.207017089891,
1752
146k
    0.267138152891,
1753
146k
  };
1754
146k
  double normalizer = {
1755
146k
    1.0 / (muls[0] + muls[1]),
1756
146k
  };
1757
146k
  static const double r0 = 2.3770330432;
1758
146k
  static const double r1 = 9.04353323561;
1759
146k
  static const double r2 = 9.24456601467;
1760
146k
  static const double border_ratio = -0.0724948220913;
1761
1762
146k
  {
1763
    // X component
1764
146k
    ImageF diff = DiffPrecompute(xyb0[0], xyb1[0]);
1765
146k
    ImageF blurred = Blur(diff, r2, border_ratio);
1766
146k
    (*mask)[0] = ImageF(xsize, ysize);
1767
6.85M
    for (size_t y = 0; y < ysize; ++y) {
1768
352M
      for (size_t x = 0; x < xsize; ++x) {
1769
345M
        (*mask)[0].Row(y)[x] = blurred.Row(y)[x];
1770
345M
      }
1771
6.70M
    }
1772
146k
  }
1773
146k
  {
1774
    // Y component
1775
146k
    (*mask)[1] = ImageF(xsize, ysize);
1776
146k
    ImageF diff = DiffPrecompute(xyb0[1], xyb1[1]);
1777
146k
    ImageF blurred1 = Blur(diff, r0, border_ratio);
1778
146k
    ImageF blurred2 = Blur(diff, r1, border_ratio);
1779
6.85M
    for (size_t y = 0; y < ysize; ++y) {
1780
352M
      for (size_t x = 0; x < xsize; ++x) {
1781
345M
        const double val = normalizer * (
1782
345M
            muls[0] * blurred1.Row(y)[x] +
1783
345M
            muls[1] * blurred2.Row(y)[x]);
1784
345M
        (*mask)[1].Row(y)[x] = val;
1785
345M
      }
1786
6.70M
    }
1787
146k
  }
1788
  // B component
1789
146k
  (*mask)[2] = ImageF(xsize, ysize);
1790
146k
  static const double mul[2] = {
1791
146k
    16.6963293877,
1792
146k
    2.1364621982,
1793
146k
  };
1794
146k
  static const double w00 = 36.4671237619;
1795
146k
  static const double w11 = 2.1887170895;
1796
146k
  static const double w_ytob_hf = std::max<double>(
1797
146k
      0.086624184478,
1798
146k
      0.0);
1799
146k
  static const double w_ytob_lf = 21.6804277046;
1800
146k
  static const double p1_to_p0 = 0.0513061271723;
1801
1802
6.85M
  for (size_t y = 0; y < ysize; ++y) {
1803
352M
    for (size_t x = 0; x < xsize; ++x) {
1804
345M
      const double s0 = (*mask)[0].Row(y)[x];
1805
345M
      const double s1 = (*mask)[1].Row(y)[x];
1806
345M
      const double p1 = mul[1] * w11 * s1;
1807
345M
      const double p0 = mul[0] * w00 * s0 + p1_to_p0 * p1;
1808
1809
345M
      (*mask)[0].Row(y)[x] = MaskX(p0);
1810
345M
      (*mask)[1].Row(y)[x] = MaskY(p1);
1811
345M
      (*mask)[2].Row(y)[x] = w_ytob_hf * MaskY(p1);
1812
345M
      (*mask_dc)[0].Row(y)[x] = MaskDcX(p0);
1813
345M
      (*mask_dc)[1].Row(y)[x] = MaskDcY(p1);
1814
345M
      (*mask_dc)[2].Row(y)[x] = w_ytob_lf * MaskDcY(p1);
1815
345M
    }
1816
6.70M
  }
1817
146k
}
1818
1819
void ButteraugliDiffmap(const std::vector<ImageF> &rgb0_image,
1820
                        const std::vector<ImageF> &rgb1_image,
1821
0
                        ImageF &result_image) {
1822
0
  const size_t xsize = rgb0_image[0].xsize();
1823
0
  const size_t ysize = rgb0_image[0].ysize();
1824
0
  static const int kMax = 8;
1825
0
  if (xsize < kMax || ysize < kMax) {
1826
    // Butteraugli values for small (where xsize or ysize is smaller
1827
    // than 8 pixels) images are non-sensical, but most likely it is
1828
    // less disruptive to try to compute something than just give up.
1829
    // Temporarily extend the borders of the image to fit 8 x 8 size.
1830
0
    int xborder = xsize < kMax ? (kMax - xsize) / 2 : 0;
1831
0
    int yborder = ysize < kMax ? (kMax - ysize) / 2 : 0;
1832
0
    size_t xscaled = std::max<size_t>(kMax, xsize);
1833
0
    size_t yscaled = std::max<size_t>(kMax, ysize);
1834
0
    std::vector<ImageF> scaled0 = CreatePlanes<float>(xscaled, yscaled, 3);
1835
0
    std::vector<ImageF> scaled1 = CreatePlanes<float>(xscaled, yscaled, 3);
1836
0
    for (int i = 0; i < 3; ++i) {
1837
0
      for (int y = 0; y < yscaled; ++y) {
1838
0
        for (int x = 0; x < xscaled; ++x) {
1839
0
          size_t x2 = std::min<size_t>(xsize - 1, std::max(0, x - xborder));
1840
0
          size_t y2 = std::min<size_t>(ysize - 1, std::max(0, y - yborder));
1841
0
          scaled0[i].Row(y)[x] = rgb0_image[i].Row(y2)[x2];
1842
0
          scaled1[i].Row(y)[x] = rgb1_image[i].Row(y2)[x2];
1843
0
        }
1844
0
      }
1845
0
    }
1846
0
    ImageF diffmap_scaled;
1847
0
    ButteraugliDiffmap(scaled0, scaled1, diffmap_scaled);
1848
0
    result_image = ImageF(xsize, ysize);
1849
0
    for (int y = 0; y < ysize; ++y) {
1850
0
      for (int x = 0; x < xsize; ++x) {
1851
0
        result_image.Row(y)[x] = diffmap_scaled.Row(y + yborder)[x + xborder];
1852
0
      }
1853
0
    }
1854
0
    return;
1855
0
  }
1856
0
  ButteraugliComparator butteraugli(rgb0_image);
1857
0
  butteraugli.Diffmap(rgb1_image, result_image);
1858
0
}
1859
1860
bool ButteraugliInterface(const std::vector<ImageF> &rgb0,
1861
                          const std::vector<ImageF> &rgb1,
1862
                          ImageF &diffmap,
1863
0
                          double &diffvalue) {
1864
0
  const size_t xsize = rgb0[0].xsize();
1865
0
  const size_t ysize = rgb0[0].ysize();
1866
0
  if (xsize < 1 || ysize < 1) {
1867
0
    return false;  // No image.
1868
0
  }
1869
0
  for (int i = 1; i < 3; i++) {
1870
0
    if (rgb0[i].xsize() != xsize || rgb0[i].ysize() != ysize ||
1871
0
        rgb1[i].xsize() != xsize || rgb1[i].ysize() != ysize) {
1872
0
      return false;  // Image planes must have same dimensions.
1873
0
    }
1874
0
  }
1875
0
  ButteraugliDiffmap(rgb0, rgb1, diffmap);
1876
0
  diffvalue = ButteraugliScoreFromDiffmap(diffmap);
1877
0
  return true;
1878
0
}
1879
1880
bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize,
1881
0
    const std::vector<std::vector<float> > &rgb, std::vector<float> &quant) {
1882
0
  if (xsize < 16 || ysize < 16) {
1883
0
    return false;  // Butteraugli is undefined for small images.
1884
0
  }
1885
0
  size_t size = xsize * ysize;
1886
1887
0
  std::vector<ImageF> rgb_planes = PlanesFromPacked(xsize, ysize, rgb);
1888
0
  std::vector<ImageF> scale_xyb;
1889
0
  std::vector<ImageF> scale_xyb_dc;
1890
0
  Mask(rgb_planes, rgb_planes, &scale_xyb, &scale_xyb_dc);
1891
0
  quant.reserve(size);
1892
1893
  // Mask gives us values in 3 color channels, but for now we take only
1894
  // the intensity channel.
1895
0
  for (size_t y = 0; y < ysize; ++y) {
1896
0
    for (size_t x = 0; x < xsize; ++x) {
1897
0
      quant.push_back(scale_xyb[1].Row(y)[x]);
1898
0
    }
1899
0
  }
1900
0
  return true;
1901
0
}
1902
1903
0
double ButteraugliFuzzyClass(double score) {
1904
0
  static const double fuzzy_width_up = 6.07887388532;
1905
0
  static const double fuzzy_width_down = 5.50793514384;
1906
0
  static const double m0 = 2.0;
1907
0
  static const double scaler = 0.840253347958;
1908
0
  double val;
1909
0
  if (score < 1.0) {
1910
    // val in [scaler .. 2.0]
1911
0
    val = m0 / (1.0 + exp((score - 1.0) * fuzzy_width_down));
1912
0
    val -= 1.0;  // from [1 .. 2] to [0 .. 1]
1913
0
    val *= 2.0 - scaler;  // from [0 .. 1] to [0 .. 2.0 - scaler]
1914
0
    val += scaler;  // from [0 .. 2.0 - scaler] to [scaler .. 2.0]
1915
0
  } else {
1916
    // val in [0 .. scaler]
1917
0
    val = m0 / (1.0 + exp((score - 1.0) * fuzzy_width_up));
1918
0
    val *= scaler;
1919
0
  }
1920
0
  return val;
1921
0
}
1922
1923
0
double ButteraugliFuzzyInverse(double seek) {
1924
0
  double pos = 0;
1925
0
  for (double range = 1.0; range >= 1e-10; range *= 0.5) {
1926
0
    double cur = ButteraugliFuzzyClass(pos);
1927
0
    if (cur < seek) {
1928
0
      pos -= range;
1929
0
    } else {
1930
0
      pos += range;
1931
0
    }
1932
0
  }
1933
0
  return pos;
1934
0
}
1935
1936
namespace {
1937
1938
void ScoreToRgb(double score, double good_threshold, double bad_threshold,
1939
0
                uint8_t rgb[3]) {
1940
0
  double heatmap[12][3] = {
1941
0
      {0, 0, 0},
1942
0
      {0, 0, 1},
1943
0
      {0, 1, 1},
1944
0
      {0, 1, 0},  // Good level
1945
0
      {1, 1, 0},
1946
0
      {1, 0, 0},  // Bad level
1947
0
      {1, 0, 1},
1948
0
      {0.5, 0.5, 1.0},
1949
0
      {1.0, 0.5, 0.5},  // Pastel colors for the very bad quality range.
1950
0
      {1.0, 1.0, 0.5},
1951
0
      {
1952
0
          1, 1, 1,
1953
0
      },
1954
0
      {
1955
0
          1, 1, 1,
1956
0
      },
1957
0
  };
1958
0
  if (score < good_threshold) {
1959
0
    score = (score / good_threshold) * 0.3;
1960
0
  } else if (score < bad_threshold) {
1961
0
    score = 0.3 +
1962
0
            (score - good_threshold) / (bad_threshold - good_threshold) * 0.15;
1963
0
  } else {
1964
0
    score = 0.45 + (score - bad_threshold) / (bad_threshold * 12) * 0.5;
1965
0
  }
1966
0
  static const int kTableSize = sizeof(heatmap) / sizeof(heatmap[0]);
1967
0
  score = std::min<double>(std::max<double>(score * (kTableSize - 1), 0.0),
1968
0
                           kTableSize - 2);
1969
0
  int ix = static_cast<int>(score);
1970
0
  double mix = score - ix;
1971
0
  for (int i = 0; i < 3; ++i) {
1972
0
    double v = mix * heatmap[ix + 1][i] + (1 - mix) * heatmap[ix][i];
1973
0
    rgb[i] = static_cast<uint8_t>(255 * pow(v, 0.5) + 0.5);
1974
0
  }
1975
0
}
1976
1977
}  // namespace
1978
1979
void CreateHeatMapImage(const std::vector<float>& distmap,
1980
                        double good_threshold, double bad_threshold,
1981
                        size_t xsize, size_t ysize,
1982
0
                        std::vector<uint8_t>* heatmap) {
1983
0
  heatmap->resize(3 * xsize * ysize);
1984
0
  for (size_t y = 0; y < ysize; ++y) {
1985
0
    for (size_t x = 0; x < xsize; ++x) {
1986
0
      int px = xsize * y + x;
1987
0
      double d = distmap[px];
1988
0
      uint8_t* rgb = &(*heatmap)[3 * px];
1989
0
      ScoreToRgb(d, good_threshold, bad_threshold, rgb);
1990
0
    }
1991
0
  }
1992
0
}
1993
1994
}  // namespace butteraugli