Coverage Report

Created: 2026-10-28 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/vvenc/source/Lib/EncoderLib/SEIFilmGrainAnalyzer.cpp
Line
Count
Source
1
/* -----------------------------------------------------------------------------
2
The copyright in this software is being made available under the Clear BSD
3
License, included below. No patent rights, trademark rights and/or
4
other Intellectual Property Rights other than the copyrights concerning
5
the Software are granted under this license.
6
7
The Clear BSD License
8
9
Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVenC Authors.
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without modification,
13
are permitted (subject to the limitations in the disclaimer below) provided that
14
the following conditions are met:
15
16
     * Redistributions of source code must retain the above copyright notice,
17
     this list of conditions and the following disclaimer.
18
19
     * Redistributions in binary form must reproduce the above copyright
20
     notice, this list of conditions and the following disclaimer in the
21
     documentation and/or other materials provided with the distribution.
22
23
     * Neither the name of the copyright holder nor the names of its
24
     contributors may be used to endorse or promote products derived from this
25
     software without specific prior written permission.
26
27
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
28
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
31
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
32
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
36
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38
POSSIBILITY OF SUCH DAMAGE.
39
40
41
------------------------------------------------------------------------------------------- */
42
43
#include "SEIFilmGrainAnalyzer.h"
44
#include "SEIFilmGrainSpectra.h"
45
46
#include "CommonLib/Rom.h"
47
48
#include <algorithm>
49
#include <cmath>
50
#include <limits>
51
#include <numeric>
52
53
namespace vvenc {
54
55
namespace {
56
57
// Constants of the VTM analysis.
58
constexpr int    MAXPAIRS                  = 256;
59
constexpr int    MAXORDER                  = 8;     // maximum order of the polynomial fit
60
constexpr int    MAX_REAL_SCALE            = 16;
61
constexpr int    ORDER                     = 4;     // order of the polynomial function
62
constexpr int    QUANT_LEVELS              = 4;     // quantization levels of the Lloyd-Max quantizer
63
constexpr int    INTERVAL_SIZE             = 8;
64
constexpr int    MIN_ELEMENTS_PER_INTERVAL = 8;
65
constexpr int    POINT_STEP                = 16;    // step size of the point extension
66
constexpr int    MAX_POINTS_TO_EXTEND      = 4;
67
constexpr double POINT_SCALE               = 1.25;  // scaling of the point extension (VTM; the improved analysis keeps the level)
68
constexpr double VAR_SCALE_DOWN            = 1.2;   // filter out points far below the fitted function
69
constexpr double VAR_SCALE_UP              = 0.6;   // filter out points far above the fitted function
70
constexpr int    NUM_PASSES                = 2;     // the second pass of the fit filters the data points
71
constexpr int    NBRS                      = 1;     // neighbours a point needs within WINDOW to be kept
72
constexpr int    WINDOW                    = 1;
73
constexpr int    MIN_MEASURED_POINTS       = 2;     // measured points the improved fit needs, before the point extension
74
constexpr int    MIN_INTENSITY             = 40;
75
constexpr int    MAX_INTENSITY             = 950;
76
constexpr int    MAX_STANDARD_DEVIATION    = 255;   // at 8 bits
77
// With the improvements, the grain strength is kept in units of a quarter of twice the standard deviation (VTM: twice
78
// the standard deviation), so that the scaling factors calibrated from it keep their precision for weak grain.
79
constexpr int    IMPROVED_STRENGTH_SCALE   = 4;
80
constexpr int    BIT_DEPTH_8               = 8;
81
constexpr int    MAX_ALLOWED_MODEL_VALUES  = 3;
82
constexpr int    MAX_NUM_INTENSITIES       = 256;
83
constexpr int    MAX_CUTOFF_PAIRS          = 10;    // different cut-off pairs the synthesis supports per picture
84
constexpr double MIN_BLOCK_WEIGHT          = 1.0;   // accumulated block weight an interval needs for cut-off estimation
85
86
// Calibration of the scaling factors, per component type (luma, 4:2:0 chroma) and cut-off pair, measured with the
87
// vvdec film grain synthesis on flat grey 1920x1080 10-bit pictures:
88
//  - PATTERN_STDDEV: standard deviation of the synthesized grain for scaling factor 64 at log2_scale_factor 2, so that
89
//    a scaling factor s at log2 scale factor l gives grain of standard deviation
90
//    s * PATTERN_STDDEV / 2^( l + 14 - bitDepth ) (the synthesis of VTM gives the same);
91
//  - BLOCK_VARIANCE_SHARE: the share of that grain's variance that remains after removing the mean of every 16x16
92
//    block, as the analysis measures it.
93
constexpr int    NUM_CUTOFFS               = 13;    // cut-off frequencies 2 to 14
94
// Largest scaling factor signalled. The syntax allows 255 and so does the scalar synthesis of vvdec, but its x86 SIMD
95
// synthesis sign-extends the 8-bit scaling factors, so 128 and above would make weaker grain of the wrong sign.
96
constexpr int    MAX_SCALING_FACTOR        = 127;
97
constexpr double PATTERN_STDDEV[ 2 ][ NUM_CUTOFFS ][ NUM_CUTOFFS ] =
98
{
99
  { // luma: [ horizontal - 2 ][ vertical - 2 ]
100
    { 11.3471, 13.1953, 14.4713, 16.0922, 17.5548, 18.7151, 20.0700, 20.9957, 21.8035, 22.8604, 23.8162, 24.4313, 25.4741 },
101
    { 12.7110, 14.8901, 16.3847, 18.3015, 19.9240, 21.3337, 22.8999, 24.0157, 25.0655, 26.3700, 27.3986, 28.3434, 29.3805 },
102
    { 14.1172, 16.5524, 18.2611, 20.5952, 22.4983, 24.0530, 25.9973, 27.3273, 28.5572, 30.0424, 31.1997, 32.1487, 33.2355 },
103
    { 15.5021, 18.0297, 20.0899, 22.4546, 24.4364, 26.2005, 28.2301, 29.8060, 31.3123, 32.9112, 34.1466, 35.3053, 36.4918 },
104
    { 16.4720, 19.2467, 21.3980, 23.9320, 26.0343, 28.0799, 30.3483, 32.0788, 33.6981, 35.4062, 36.7268, 37.9420, 39.4344 },
105
    { 17.4095, 20.4055, 22.9677, 25.5371, 27.8608, 30.1310, 32.3993, 34.2167, 35.9951, 37.9442, 39.4321, 40.6859, 42.2433 },
106
    { 18.1305, 21.4469, 24.0610, 26.8472, 29.2790, 31.7692, 34.0672, 36.0396, 37.8454, 39.9101, 41.3417, 42.7916, 44.3514 },
107
    { 19.2211, 22.7276, 25.4476, 28.2473, 30.8452, 33.3751, 35.7479, 37.6866, 39.5467, 41.7627, 43.3952, 44.7700, 46.4703 },
108
    { 20.4457, 24.1150, 27.0148, 29.9963, 32.7611, 35.2320, 37.6624, 39.8350, 41.6697, 43.8564, 45.5852, 46.9930, 48.9238 },
109
    { 21.6430, 25.4942, 28.5270, 31.8942, 34.6005, 37.0724, 39.4975, 41.7995, 43.5896, 45.7560, 47.5483, 49.0574, 50.9609 },
110
    { 22.5390, 26.4786, 29.5484, 32.9731, 35.8114, 38.4931, 41.0125, 43.2637, 45.0366, 47.2199, 49.1412, 50.7580, 52.6695 },
111
    { 23.4841, 27.5594, 30.6919, 34.2237, 37.1031, 39.7887, 42.3244, 44.6668, 46.6079, 48.6607, 50.7871, 52.3455, 54.2592 },
112
    { 24.0446, 28.2479, 31.5268, 35.1441, 38.0267, 40.7036, 43.3165, 45.7409, 47.7803, 49.8510, 51.9981, 53.5674, 55.5688 },
113
  },
114
  { // 4:2:0 chroma: [ horizontal - 2 ][ vertical - 2 ]
115
    {  9.8329, 11.6354, 12.8245, 13.6625, 15.3470, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060 },
116
    { 11.7467, 14.0110, 15.6190, 16.6803, 18.4311, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934 },
117
    { 12.8707, 15.3081, 17.2100, 18.5999, 20.4333, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392 },
118
    { 14.0068, 16.3715, 18.4529, 19.9943, 21.8958, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361 },
119
    { 15.2300, 17.7825, 20.0028, 21.6539, 23.5309, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114 },
120
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
121
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
122
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
123
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
124
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
125
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
126
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
127
    { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 },
128
  },
129
};
130
131
constexpr double BLOCK_VARIANCE_SHARE[ 2 ][ NUM_CUTOFFS ][ NUM_CUTOFFS ] =
132
{
133
  { // luma: [ horizontal - 2 ][ vertical - 2 ]
134
    {  0.9137,  0.9350,  0.9456,  0.9547,  0.9619,  0.9646,  0.9688,  0.9716,  0.9733,  0.9758,  0.9777,  0.9789,  0.9805 },
135
    {  0.9299,  0.9481,  0.9568,  0.9642,  0.9699,  0.9724,  0.9756,  0.9778,  0.9795,  0.9813,  0.9828,  0.9839,  0.9850 },
136
    {  0.9429,  0.9577,  0.9650,  0.9715,  0.9762,  0.9780,  0.9809,  0.9828,  0.9840,  0.9855,  0.9867,  0.9875,  0.9883 },
137
    {  0.9520,  0.9641,  0.9708,  0.9757,  0.9796,  0.9815,  0.9837,  0.9854,  0.9867,  0.9879,  0.9888,  0.9895,  0.9902 },
138
    {  0.9568,  0.9680,  0.9740,  0.9784,  0.9818,  0.9836,  0.9858,  0.9873,  0.9884,  0.9894,  0.9902,  0.9908,  0.9915 },
139
    {  0.9609,  0.9709,  0.9770,  0.9807,  0.9839,  0.9856,  0.9874,  0.9886,  0.9896,  0.9907,  0.9914,  0.9919,  0.9925 },
140
    {  0.9634,  0.9733,  0.9786,  0.9822,  0.9851,  0.9868,  0.9883,  0.9896,  0.9904,  0.9914,  0.9920,  0.9926,  0.9931 },
141
    {  0.9674,  0.9761,  0.9809,  0.9839,  0.9866,  0.9881,  0.9894,  0.9905,  0.9913,  0.9921,  0.9928,  0.9933,  0.9938 },
142
    {  0.9711,  0.9788,  0.9830,  0.9857,  0.9881,  0.9893,  0.9904,  0.9914,  0.9921,  0.9929,  0.9934,  0.9939,  0.9945 },
143
    {  0.9744,  0.9811,  0.9848,  0.9875,  0.9894,  0.9904,  0.9914,  0.9923,  0.9929,  0.9936,  0.9941,  0.9945,  0.9950 },
144
    {  0.9762,  0.9824,  0.9859,  0.9883,  0.9901,  0.9911,  0.9920,  0.9929,  0.9934,  0.9940,  0.9945,  0.9948,  0.9953 },
145
    {  0.9782,  0.9838,  0.9869,  0.9891,  0.9907,  0.9916,  0.9925,  0.9933,  0.9938,  0.9943,  0.9948,  0.9951,  0.9956 },
146
    {  0.9792,  0.9846,  0.9876,  0.9897,  0.9911,  0.9920,  0.9928,  0.9936,  0.9941,  0.9946,  0.9951,  0.9954,  0.9959 },
147
  },
148
  { // 4:2:0 chroma: [ horizontal - 2 ][ vertical - 2 ]
149
    {  0.9488,  0.9625,  0.9686,  0.9723,  0.9781,  0.9805,  0.9805,  0.9805,  0.9805,  0.9805,  0.9805,  0.9805,  0.9805 },
150
    {  0.9621,  0.9727,  0.9776,  0.9805,  0.9840,  0.9855,  0.9855,  0.9855,  0.9855,  0.9855,  0.9855,  0.9855,  0.9855 },
151
    {  0.9684,  0.9770,  0.9817,  0.9842,  0.9870,  0.9881,  0.9881,  0.9881,  0.9881,  0.9881,  0.9881,  0.9881,  0.9881 },
152
    {  0.9733,  0.9798,  0.9839,  0.9862,  0.9886,  0.9897,  0.9897,  0.9897,  0.9897,  0.9897,  0.9897,  0.9897,  0.9897 },
153
    {  0.9772,  0.9829,  0.9862,  0.9881,  0.9901,  0.9914,  0.9914,  0.9914,  0.9914,  0.9914,  0.9914,  0.9914,  0.9914 },
154
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
155
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
156
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
157
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
158
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
159
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
160
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
161
    {  0.9807,  0.9855,  0.9882,  0.9899,  0.9914,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925,  0.9925 },
162
  },
163
};
164
165
// After this many analyses in a row whose scaling function fit failed, a component's grain is no longer signalled.
166
constexpr int    MAX_FAILED_FITS           = 3;
167
// A picture whose mean grain strength differs from that of the accumulated data by more than this factor starts the
168
// statistics anew (a new scene with different grain); it needs this many blocks for a reliable mean.
169
constexpr double GRAIN_CHANGE_RATIO        = 2.0;
170
constexpr int    GRAIN_CHANGE_MIN_BLOCKS   = 64;
171
172
// The auto-regressive model (--fga 3): vvdec's synthesis makes grain of standard deviation
173
// scale * AR_PATTERN_STDDEV * sqrt( energy ) / 2^( log2ScaleFactor + bitDepth - 10 ) for the AR filter whose impulse
174
// response has the given energy (its quantized coefficients, as the synthesis computes them). Measured with vvdec on
175
// flat grey 1920x1088 10-bit pictures over 24 filters (left coefficient 0 to 0.375) and log2 scale factors 3 to 7:
176
// the ratio stays within 3% of these values.
177
constexpr double AR_PATTERN_STDDEV[ 2 ] = { 3.83, 3.60 };   // luma, chroma
178
constexpr int    AR_SPECTRUM_BINS         = 64;    // frequency bins per direction of the AR spectrum computation
179
constexpr int    AR_FIT_ITERATIONS        = 200;   // largest number of Nelder-Mead iterations of the AR filter fit
180
constexpr double AR_FIT_TOLERANCE         = 1e-5;  // the fit stops when its simplex spans less than this L1 error
181
182
// Outliers of the temporal residual: per intensity bin (16 bins), blocks above this ratio of the median grain variance
183
// are motion compensation failures; bins need this many blocks for the median.
184
constexpr int    NUM_OUTLIER_BINS          = 16;
185
constexpr double OUTLIER_VARIANCE_RATIO    = 4.0;
186
constexpr int    MIN_OUTLIER_BIN_BLOCKS    = 8;
187
188
// Temporal residual: motion search range in samples of the 4x downsampled pictures (12 or 20 samples at full
189
// resolution), and the share of the zero motion SAD a vector must stay below, as 17 / 20 = 0.85.
190
constexpr int    RESIDUAL_RANGE            = 3;
191
constexpr int    RESIDUAL_RANGE_WIDE       = 5;     // pictures wider than 2048
192
constexpr int    ZERO_BIAS_NUM             = 17;
193
constexpr int    ZERO_BIAS_DEN             = 20;
194
// A neighbour whose mean absolute difference is above twice that of the other neighbour, plus this many sample
195
// values, is left out of the residual.
196
constexpr double NEIGHBOUR_SAD_MARGIN      = 1.0;
197
198
constexpr double LOW_THRESHOLD_RATIO       = 0.1;   // Canny: low threshold relative to the strongest gradient
199
constexpr int    HIGH_THRESHOLD_RATIO      = 3;     // Canny: high threshold relative to the low threshold
200
constexpr double LOW_INTENSITY_RATIO       = 0.1;   // samples below this share of the range are not analysed
201
202
constexpr double TAP_FILTER[ 3 ]           = { 1, 2, 1 };
203
constexpr double NORM_TAP                  = 4.0;
204
205
typedef FGAnalyzer::Plane Plane;
206
207
// ====================================================================================================================
208
// Flat area detection: Canny edge detector and morphological operations
209
// ====================================================================================================================
210
211
// Classifies the magnitudes as strong, weak or no edge, with thresholds relative to the strongest gradient.
212
void doubleThreshold( const FgaRowKernels& k, Plane& mag, int bitDepth )
213
0
{
214
0
  const Pel strongPel = (Pel) ( ( 1 << bitDepth ) - 1 );
215
0
  const Pel weakPel   = (Pel) ( ( 1 << ( bitDepth - 1 ) ) - 1 );
216
0
  const int n = mag.width * mag.height;   // the storage can be larger than the plane
217
218
0
  const Pel highest       = k.rowMax( mag.samples.data(), n );
219
0
  const Pel lowThreshold  = (Pel) ( LOW_THRESHOLD_RATIO * highest );
220
0
  const Pel highThreshold = (Pel) Clip3( 0, ( 1 << bitDepth ) - 1, HIGH_THRESHOLD_RATIO * lowThreshold );
221
0
  k.classify( mag.samples.data(), n, lowThreshold, highThreshold, weakPel, strongPel );
222
0
}
223
224
// Hysteresis: a weak edge becomes strong when a strong edge is in its 3x3 neighbourhood, otherwise it is removed.
225
// As in VTM, the picture is updated in place, column by column, so a weak edge turned strong can promote weak edges
226
// processed after it. Neighbours outside the picture repeat the border as it was before the tracking started.
227
template<class ScratchT>
228
void edgeTracking( const FgaRowKernels& k, Plane& edges, int bitDepth, ScratchT& s )
229
0
{
230
0
  const Pel strongPel = (Pel) ( ( 1 << bitDepth ) - 1 );
231
0
  const Pel weakPel   = (Pel) ( ( 1 << ( bitDepth - 1 ) ) - 1 );
232
0
  const int w = edges.width, h = edges.height;
233
234
  // The weak edges in column-major order: found row by row, then sorted by column (counting sort, stable, so the
235
  // rows of every column stay in increasing order).
236
0
  s.positions.resize( w );
237
0
  s.weakX.clear();
238
0
  s.weakY.clear();
239
0
  for( int y = 0; y < h; y++ )
240
0
  {
241
0
    const int n = k.findEqual( edges.row( y ), w, weakPel, s.positions.data() );
242
0
    for( int i = 0; i < n; i++ )
243
0
    {
244
0
      s.weakX.push_back( s.positions[ i ] );
245
0
      s.weakY.push_back( y );
246
0
    }
247
0
  }
248
0
  if( s.weakX.empty() )
249
0
  {
250
0
    return;
251
0
  }
252
0
  std::vector<int>& columnStart = s.columnStart;
253
0
  columnStart.assign( w + 1, 0 );
254
0
  for( int px : s.weakX )
255
0
  {
256
0
    columnStart[ px + 1 ]++;
257
0
  }
258
0
  for( int px = 0; px < w; px++ )
259
0
  {
260
0
    columnStart[ px + 1 ] += columnStart[ px ];
261
0
  }
262
0
  std::vector<int>& weakRows = s.positions;
263
0
  weakRows.resize( s.weakX.size() );
264
0
  {
265
0
    std::vector<int> fill( columnStart.begin(), columnStart.end() - 1 );
266
0
    for( size_t i = 0; i < s.weakX.size(); i++ )
267
0
    {
268
0
      weakRows[ fill[ s.weakX[ i ] ]++ ] = s.weakY[ i ];
269
0
    }
270
0
  }
271
272
  // Neighbours outside the picture are border samples as they were before the tracking.
273
0
  s.top.assign( edges.row( 0 ), edges.row( 0 ) + w );
274
0
  s.bottom.assign( edges.row( h - 1 ), edges.row( h - 1 ) + w );
275
0
  s.left.resize( h );
276
0
  s.right.resize( h );
277
0
  for( int y = 0; y < h; y++ )
278
0
  {
279
0
    s.left[ y ]  = edges.row( y )[ 0 ];
280
0
    s.right[ y ] = edges.row( y )[ w - 1 ];
281
0
  }
282
0
  auto borderAt = [ & ]( int bx, int by )
283
0
  {
284
0
    const int cx = Clip3( 0, w - 1, bx ), cy = Clip3( 0, h - 1, by );
285
0
    return cy == 0 ? s.top[ cx ] : cy == h - 1 ? s.bottom[ cx ] : cx == 0 ? s.left[ cy ] : s.right[ cy ];
286
0
  };
287
288
0
  for( int x = 0; x < w; x++ )
289
0
  {
290
0
    for( int i = columnStart[ x ]; i < columnStart[ x + 1 ]; i++ )
291
0
    {
292
0
      const int y = weakRows[ i ];
293
0
      bool strong = false;
294
0
      for( int ny = y - 1; ny <= y + 1 && !strong; ny++ )
295
0
      {
296
0
        for( int nx = x - 1; nx <= x + 1 && !strong; nx++ )
297
0
        {
298
0
          const bool inside = nx >= 0 && nx < w && ny >= 0 && ny < h;
299
0
          strong = ( inside ? edges.row( ny )[ nx ] : borderAt( nx, ny ) ) == strongPel;
300
0
        }
301
0
      }
302
0
      edges.row( y )[ x ] = strong ? strongPel : 0;
303
0
    }
304
0
  }
305
0
}
306
307
// Sets the binary mask to "edge" where the picture is too dark to be analysed.
308
void suppressLowIntensity( const Plane& p, Plane& mask, int bitDepth )
309
0
{
310
0
  const Pel maxIntensity = (Pel) ( ( 1 << bitDepth ) - 1 );
311
0
  const Pel threshold    = (Pel) ( LOW_INTENSITY_RATIO * maxIntensity );
312
0
  const size_t n = (size_t) mask.width * mask.height;
313
0
  const Pel* src = p.samples.data();
314
0
  Pel* dst = mask.samples.data();
315
0
  for( size_t i = 0; i < n; i++ )
316
0
  {
317
0
    dst[ i ] = src[ i ] < threshold ? maxIntensity : dst[ i ];   // a select, so that the loop vectorizes
318
0
  }
319
0
}
320
321
// Subsampling as in VTM: the average of the top-left 2x2 samples of every factor x factor block.
322
void subsample( const Plane& in, Plane& out, int factor )
323
0
{
324
0
  out.resize( in.width / factor, in.height / factor );
325
0
  for( int y = 0; y < out.height; y++ )
326
0
  {
327
0
    const Pel* r0 = in.row( y * factor );
328
0
    const Pel* r1 = in.row( y * factor + 1 );
329
0
    Pel* dst = out.row( y );
330
0
    for( int x = 0; x < out.width; x++ )
331
0
    {
332
0
      const int sx = x * factor;
333
0
      dst[ x ] = (Pel) ( ( r0[ sx ] + r1[ sx ] + r0[ sx + 1 ] + r1[ sx + 1 ] + 2 ) >> 2 );
334
0
    }
335
0
  }
336
0
}
337
338
// Adds a mask found at a lower resolution to the full-resolution mask (sample replication, then bitwise or).
339
void combineUpsampled( Plane& mask, const Plane& low, int factor, std::vector<Pel>& wide )
340
0
{
341
0
  const int w = low.width * factor;
342
0
  wide.resize( w );
343
0
  for( int ly = 0; ly < low.height; ly++ )
344
0
  {
345
0
    const Pel* src = low.row( ly );
346
0
    for( int lx = 0; lx < low.width; lx++ )
347
0
    {
348
0
      for( int f = 0; f < factor; f++ )
349
0
      {
350
0
        wide[ lx * factor + f ] = src[ lx ];
351
0
      }
352
0
    }
353
0
    for( int y = ly * factor; y < ( ly + 1 ) * factor; y++ )
354
0
    {
355
0
      Pel* dst = mask.row( y );
356
0
      for( int x = 0; x < w; x++ )
357
0
      {
358
0
        dst[ x ] |= wide[ x ];
359
0
      }
360
0
    }
361
0
  }
362
0
}
363
364
// ====================================================================================================================
365
// Block statistics
366
// ====================================================================================================================
367
368
struct BlockStats
369
{
370
  int    mean;     // truncated, as in VTM (it selects the intensity)
371
  double stddev;   // exact; VTM truncates it to an integer, which loses up to a third of the grain strength
372
};
373
374
// Mean and standard deviation of a 16x16 block.
375
BlockStats meanStdDev( const FgaRowKernels& k, const Plane& p, int x0, int y0 )
376
0
{
377
0
  int64_t sum = 0, sumSq = 0;
378
0
  k.blockSums( p.row( y0 ) + x0, p.width, sum, sumSq );
379
0
  const int n = 16 * 16;
380
0
  const double variance = ( (double) sumSq - (double) sum * sum / n ) / n;
381
0
  return { (int) ( sum / n ), std::sqrt( std::max( variance, 0.0 ) ) };
382
0
}
383
384
// Expected power of the coefficients of the orthonormal 16x16 DCT-II of grain made by the AR filter of vvdec's SEI AR
385
// mode (see FGAnalyzer::ArFilter), as spec[ vertical * 16 + horizontal ]: its power spectrum 1 / |1 - A(w)|^2,
386
// integrated against the squared spectra of the DCT basis vectors on AR_SPECTRUM_BINS x AR_SPECTRUM_BINS frequencies.
387
class ArSpectrum
388
{
389
public:
390
  ArSpectrum()
391
0
  {
392
0
    constexpr int    N  = 16;
393
0
    constexpr double PI = 3.14159265358979323846;
394
0
    for( int k = 0; k < AR_SPECTRUM_BINS; k++ )
395
0
    {
396
0
      const double w = 2.0 * PI * k / AR_SPECTRUM_BINS;
397
0
      m_cos[ k ] = std::cos( w );
398
0
      m_sin[ k ] = std::sin( w );
399
0
      for( int u = 0; u < N; u++ )
400
0
      {
401
0
        double re = 0.0, im = 0.0;
402
0
        for( int n = 0; n < N; n++ )
403
0
        {
404
0
          const double b = std::sqrt( ( u ? 2.0 : 1.0 ) / N ) * std::cos( PI * ( 2 * n + 1 ) * u / ( 2 * N ) );
405
0
          re += b * std::cos( w * n );
406
0
          im -= b * std::sin( w * n );
407
0
        }
408
0
        m_basisPower[ u ][ k ] = re * re + im * im;
409
0
      }
410
0
    }
411
0
  }
412
413
  // Scratch space of compute(), reused by the caller over the calls of a fit.
414
  static constexpr size_t SCRATCH_SIZE = AR_SPECTRUM_BINS * AR_SPECTRUM_BINS + 16 * AR_SPECTRUM_BINS;
415
416
  // spec: 256 values, the DC coefficient set to 0 and the rest normalized to sum 1; returns the share of the power
417
  // outside the DC coefficient. scratch: SCRATCH_SIZE values.
418
  double compute( double a1, double a3, double a5, double r, double* spec, std::vector<double>& scratch ) const
419
0
  {
420
0
    constexpr int N = 16, K = AR_SPECTRUM_BINS;
421
0
    scratch.resize( SCRATCH_SIZE );
422
0
    double* psd  = scratch.data();
423
0
    double* rows = psd + K * K;
424
0
    for( int ky = 0; ky < K; ky++ )
425
0
    {
426
0
      for( int kx = 0; kx < K; kx++ )
427
0
      {
428
        // A(w) = sum of coefficient * e^(-i w . offset) over the filter taps
429
0
        const int sum = ( kx + ky ) % K, diff = ( ky - kx + K ) % K, x2 = ( 2 * kx ) % K, y2 = ( 2 * ky ) % K;
430
0
        const double re = a1 * m_cos[ kx ] + a1 * r * m_cos[ ky ] + a3 * r * ( m_cos[ sum ] + m_cos[ diff ] )
431
0
                        + a5 * m_cos[ x2 ] + a5 * r * r * m_cos[ y2 ];
432
0
        const double im = -( a1 * m_sin[ kx ] + a1 * r * m_sin[ ky ] + a3 * r * ( m_sin[ sum ] + m_sin[ diff ] )
433
0
                        + a5 * m_sin[ x2 ] + a5 * r * r * m_sin[ y2 ] );
434
0
        psd[ ky * K + kx ] = 1.0 / std::max( ( 1.0 - re ) * ( 1.0 - re ) + im * im, 1e-6 );
435
0
      }
436
0
    }
437
0
    for( int v = 0; v < N; v++ )
438
0
    {
439
0
      for( int kx = 0; kx < K; kx++ )
440
0
      {
441
0
        double s = 0.0;
442
0
        for( int ky = 0; ky < K; ky++ )
443
0
        {
444
0
          s += m_basisPower[ v ][ ky ] * psd[ ky * K + kx ];
445
0
        }
446
0
        rows[ v * K + kx ] = s;
447
0
      }
448
0
    }
449
0
    double total = 0.0;
450
0
    for( int v = 0; v < N; v++ )
451
0
    {
452
0
      for( int h = 0; h < N; h++ )
453
0
      {
454
0
        double s = 0.0;
455
0
        for( int kx = 0; kx < K; kx++ )
456
0
        {
457
0
          s += rows[ v * K + kx ] * m_basisPower[ h ][ kx ];
458
0
        }
459
0
        spec[ v * N + h ] = s;
460
0
        total += s;
461
0
      }
462
0
    }
463
0
    const double nonDc = total - spec[ 0 ];
464
0
    spec[ 0 ] = 0.0;
465
0
    for( int i = 0; i < N * N; i++ )
466
0
    {
467
0
      spec[ i ] /= nonDc;
468
0
    }
469
0
    return nonDc / total;
470
0
  }
471
472
private:
473
  double m_cos[ AR_SPECTRUM_BINS ], m_sin[ AR_SPECTRUM_BINS ];
474
  double m_basisPower[ 16 ][ AR_SPECTRUM_BINS ];
475
};
476
477
// The filter coefficients of vvdec's SEI AR mode as it computes them, in units of 2^-log2: left, top, diagonal
478
// (top-left and top-right), left-left and top-top.
479
struct ArTaps
480
{
481
  int left, top, diag, leftLeft, topTop;
482
  ArTaps( const int c[ 6 ], int log2 )
483
0
    : left( c[ 1 ] ), top( ( c[ 1 ] * c[ 4 ] ) >> log2 ), diag( ( c[ 3 ] * c[ 4 ] ) >> log2 ), leftLeft( c[ 5 ] ),
484
0
      topTop( ( c[ 5 ] * c[ 4 ] * c[ 4 ] ) >> ( 2 * log2 ) ) {}
485
};
486
487
// Energy of the impulse response of vvdec's AR filter with the signalled (integer) coefficients c[ 1 ] to c[ 5 ].
488
double arEnergy( const int c[ 6 ], int log2 )
489
0
{
490
0
  constexpr int SIZE = 96;
491
0
  const ArTaps t( c, log2 );
492
0
  const double one = 1 << log2;
493
0
  std::vector<double> h( SIZE * SIZE, 0.0 );
494
0
  double energy = 0.0;
495
0
  for( int y = 0; y < SIZE; y++ )
496
0
  {
497
0
    for( int x = 2; x < SIZE - 2; x++ )
498
0
    {
499
0
      double g = t.left * h[ y * SIZE + x - 1 ] + t.leftLeft * h[ y * SIZE + x - 2 ];
500
0
      if( y >= 1 )
501
0
      {
502
0
        g += t.top * h[ ( y - 1 ) * SIZE + x ] + t.diag * ( h[ ( y - 1 ) * SIZE + x - 1 ] + h[ ( y - 1 ) * SIZE + x + 1 ] );
503
0
      }
504
0
      if( y >= 2 )
505
0
      {
506
0
        g += t.topTop * h[ ( y - 2 ) * SIZE + x ];
507
0
      }
508
0
      h[ y * SIZE + x ] = g / one + ( y == 0 && x == SIZE / 2 ? 1.0 : 0.0 );
509
0
      energy += h[ y * SIZE + x ] * h[ y * SIZE + x ];
510
0
    }
511
0
  }
512
0
  return energy;
513
0
}
514
515
double ldpow( double n, unsigned p )
516
0
{
517
0
  double x = 1;
518
0
  for( unsigned i = 0; i < p; i++ )
519
0
  {
520
0
    x = x * n;
521
0
  }
522
0
  return x;
523
0
}
524
525
Pel rowMax( const Pel* src, int n )
526
0
{
527
0
  return fgaRowMaxCore( src, n );
528
0
}
529
530
void classify( Pel* row, int n, Pel low, Pel high, Pel weak, Pel strong )
531
0
{
532
0
  fgaClassifyCore( row, n, low, high, weak, strong );
533
0
}
534
535
void sobelRow( const Pel* above, const Pel* cur, const Pel* below, int width, Pel maxVal, Pel* mag, Pel* dir )
536
0
{
537
0
  fgaSobelCore( above, cur, below, width, 0, width, maxVal, mag, dir );
538
0
}
539
540
void nonMaxRow( const Pel* magAbove, const Pel* mag, const Pel* magBelow, const Pel* dir, int width, Pel* dst )
541
0
{
542
0
  fgaNonMaxCore( magAbove, mag, magBelow, dir, width, 0, width, dst );
543
0
}
544
545
void morphRow( const Pel* src, int width, bool dilate, Pel* dst )
546
0
{
547
0
  fgaMorphRowCore( src, width, 0, width, dilate, dst );
548
0
}
549
550
void morphCol( const Pel* above, const Pel* cur, const Pel* below, int width, bool dilate, Pel* dst )
551
0
{
552
0
  fgaMorphColCore( above, cur, below, 0, width, dilate, dst );
553
0
}
554
555
// Gradient magnitude after non-maximum suppression along the edge direction.
556
void gradientWithNonMaxSuppression( const FgaRowKernels& k, const Plane& p, Plane& out, int bitDepth, Plane& mag, Plane& dir )
557
0
{
558
0
  const int w = p.width, h = p.height;
559
0
  const Pel maxVal = (Pel) ( ( 1 << bitDepth ) - 1 );
560
0
  mag.resize( w, h );
561
0
  dir.resize( w, h );
562
0
  for( int y = 0; y < h; y++ )
563
0
  {
564
0
    k.sobel( p.row( std::max( y - 1, 0 ) ), p.row( y ), p.row( std::min( y + 1, h - 1 ) ), w, maxVal, mag.row( y ), dir.row( y ) );
565
0
  }
566
0
  out.resize( w, h );
567
0
  for( int y = 0; y < h; y++ )
568
0
  {
569
0
    k.nonMax( mag.row( std::max( y - 1, 0 ) ), mag.row( y ), mag.row( std::min( y + 1, h - 1 ) ), dir.row( y ), w, out.row( y ) );
570
0
  }
571
0
}
572
573
// One 3x3 dilation or erosion of a binary mask (values 0 and "edge"): on such a mask, dilation is the 3x3 maximum and
574
// erosion the 3x3 minimum, and both are separable. Borders are replicated.
575
void morphStep( const FgaRowKernels& k, Plane& mask, Plane& tmp, bool dilate )
576
0
{
577
0
  const int w = mask.width, h = mask.height;
578
0
  tmp.resize( w, h );
579
0
  for( int y = 0; y < h; y++ )
580
0
  {
581
0
    k.morphRow( mask.row( y ), w, dilate, tmp.row( y ) );
582
0
  }
583
0
  for( int y = 0; y < h; y++ )
584
0
  {
585
0
    k.morphCol( tmp.row( std::max( y - 1, 0 ) ), tmp.row( y ), tmp.row( std::min( y + 1, h - 1 ) ), w, dilate, mask.row( y ) );
586
0
  }
587
0
}
588
589
} // anonymous namespace
590
591
// ====================================================================================================================
592
// Scalar kernels
593
// ====================================================================================================================
594
595
Pel fgaRowMaxCore( const Pel* src, int n )
596
0
{
597
0
  Pel highest = 0;
598
0
  for( int i = 0; i < n; i++ )
599
0
  {
600
0
    highest = std::max( highest, src[ i ] );
601
0
  }
602
0
  return highest;
603
0
}
604
605
void fgaClassifyCore( Pel* row, int n, Pel low, Pel high, Pel weak, Pel strong )
606
0
{
607
0
  for( int i = 0; i < n; i++ )
608
0
  {
609
0
    row[ i ] = row[ i ] > high ? strong : row[ i ] > low ? weak : 0;
610
0
  }
611
0
}
612
613
int fgaFindEqualCore( const Pel* row, int n, Pel value, int* positions )
614
0
{
615
0
  int count = 0;
616
0
  for( int i = 0; i < n; i++ )
617
0
  {
618
0
    if( row[ i ] == value )
619
0
    {
620
0
      positions[ count++ ] = i;
621
0
    }
622
0
  }
623
0
  return count;
624
0
}
625
626
bool fgaIsZeroBlockCore( const Pel* src, int stride )
627
0
{
628
0
  for( int y = 0; y < 16; y++, src += stride )
629
0
  {
630
0
    for( int x = 0; x < 16; x++ )
631
0
    {
632
0
      if( src[ x ] )
633
0
      {
634
0
        return false;
635
0
      }
636
0
    }
637
0
  }
638
0
  return true;
639
0
}
640
641
void fgaBlockSumsCore( const Pel* src, int stride, int64_t& sum, int64_t& sumSq )
642
0
{
643
0
  sum = sumSq = 0;
644
0
  for( int y = 0; y < 16; y++, src += stride )
645
0
  {
646
0
    for( int x = 0; x < 16; x++ )
647
0
    {
648
0
      sum   += src[ x ];
649
0
      sumSq += src[ x ] * src[ x ];
650
0
    }
651
0
  }
652
0
}
653
654
void fgaDownsample4Core( const Pel* src, int stride, int n, Pel* dst )
655
0
{
656
0
  for( int x = 0; x < n; x++, src += 4 )
657
0
  {
658
0
    int sum = 8;
659
0
    for( int y = 0; y < 4; y++ )
660
0
    {
661
0
      sum += src[ y * stride ] + src[ y * stride + 1 ] + src[ y * stride + 2 ] + src[ y * stride + 3 ];
662
0
    }
663
0
    dst[ x ] = (Pel) ( sum >> 4 );
664
0
  }
665
0
}
666
667
void fgaSad4x4RowCore( const Pel* cur, int curStride, const Pel* ref, int refStride, int n, int* sad )
668
0
{
669
0
  for( int b = 0; b < n; b++ )
670
0
  {
671
0
    int sum = 0;
672
0
    for( int y = 0; y < 4; y++ )
673
0
    {
674
0
      for( int x = 4 * b; x < 4 * b + 4; x++ )
675
0
      {
676
0
        sum += abs( cur[ y * curStride + x ] - ref[ y * refStride + x ] );
677
0
      }
678
0
    }
679
0
    sad[ b ] = sum;
680
0
  }
681
0
}
682
683
void fgaResidualRowCore( const Pel* cur, const Pel* prev, const Pel* next, int n, Pel* dst )
684
0
{
685
0
  if( next )
686
0
  {
687
0
    for( int x = 0; x < n; x++ )
688
0
    {
689
0
      dst[ x ] = (Pel) ( 2 * cur[ x ] - prev[ x ] - next[ x ] );
690
0
    }
691
0
  }
692
0
  else
693
0
  {
694
0
    for( int x = 0; x < n; x++ )
695
0
    {
696
0
      dst[ x ] = (Pel) ( cur[ x ] - prev[ x ] );
697
0
    }
698
0
  }
699
0
}
700
701
// VVC's 16-point DCT-II as a matrix multiplication, as VTM's film grain analysis computes it.
702
void fgaSquaredDct16Core( const Pel* src, int stride, int maxRange, double* sum )
703
0
{
704
0
  constexpr int N = 16;
705
0
  const TMatrixCoeff* tr = g_trCoreDCT2P16[ TRANSFORM_FORWARD ][ 0 ];
706
0
  int horizontal[ N ][ N ];   // [horizontal frequency][row]
707
0
  for( int u = 0; u < N; u++ )
708
0
  {
709
0
    for( int y = 0; y < N; y++ )
710
0
    {
711
0
      const Pel* row = src + y * stride;
712
0
      int acc = 0;
713
0
      for( int k = 0; k < N; k++ )
714
0
      {
715
0
        acc += tr[ u * N + k ] * row[ k ];
716
0
      }
717
0
      horizontal[ u ][ y ] = ( acc + 128 ) >> 8;
718
0
    }
719
0
  }
720
0
  for( int u = 0; u < N; u++ )
721
0
  {
722
0
    for( int v = 0; v < N; v++ )
723
0
    {
724
0
      int acc = 0;
725
0
      for( int k = 0; k < N; k++ )
726
0
      {
727
0
        acc += horizontal[ u ][ k ] * tr[ v * N + k ];
728
0
      }
729
0
      const int64_t c = Clip3( -maxRange, maxRange, ( acc + 128 ) >> 8 );
730
0
      sum[ u * N + v ] += (double) ( c * c );
731
0
    }
732
0
  }
733
0
}
734
735
// Sobel gradients as in VTM: "gx" is the difference of the rows below and above (a vertical derivative), "gy" the
736
// difference of the columns right and left. VTM quantizes the angle atan2(gx, gy) to 0, 45, 90 or 135 degrees with
737
// bounds at 22.5 + k * 45 degrees. As tan(22.5) = sqrt(2) - 1 and tan(67.5) = sqrt(2) + 1, with a = |gx| and b = |gy|
738
//   a <= (sqrt(2) - 1) * b  <=>  (a + b)^2 <= 2 * b^2           : 0 degrees,
739
//   a >  (sqrt(2) + 1) * b  <=>  a > b and (a - b)^2 > 2 * b^2  : 90 degrees,
740
// otherwise 45 degrees when gx and gy have the same sign and 135 when not. This integer rule gives the same direction
741
// as atan2 for every 10-bit gradient pair (checked exhaustively) and needs no floating point.
742
void fgaSobelCore( const Pel* above, const Pel* cur, const Pel* below, int width, int xStart, int xEnd, Pel maxVal, Pel* mag, Pel* dir )
743
0
{
744
0
  for( int x = xStart; x < xEnd; x++ )
745
0
  {
746
0
    const int l = std::max( x - 1, 0 ), r = std::min( x + 1, width - 1 );
747
0
    const int gx = ( below[ l ] + 2 * below[ x ] + below[ r ] ) - ( above[ l ] + 2 * above[ x ] + above[ r ] );
748
0
    const int gy = ( above[ r ] + 2 * cur[ r ] + below[ r ] ) - ( above[ l ] + 2 * cur[ l ] + below[ l ] );
749
0
    const int a = abs( gx ), b = abs( gy );
750
0
    mag[ x ] = (Pel) std::min<int>( ( a + b ) / 2, maxVal );
751
0
    if( ( a + b ) * ( a + b ) <= 2 * b * b )
752
0
    {
753
0
      dir[ x ] = 0;
754
0
    }
755
0
    else if( a > b && ( a - b ) * ( a - b ) > 2 * b * b )
756
0
    {
757
0
      dir[ x ] = 90;
758
0
    }
759
0
    else
760
0
    {
761
0
      dir[ x ] = ( gx ^ gy ) >= 0 ? 45 : 135;
762
0
    }
763
0
  }
764
0
}
765
766
// Neighbours along the direction: 0 degrees compares left and right, 45 lower right and upper left, 90 above and
767
// below, 135 lower left and upper right.
768
void fgaNonMaxCore( const Pel* magAbove, const Pel* mag, const Pel* magBelow, const Pel* dir, int width, int xStart, int xEnd, Pel* dst )
769
0
{
770
0
  for( int x = xStart; x < xEnd; x++ )
771
0
  {
772
0
    const int l = std::max( x - 1, 0 ), r = std::min( x + 1, width - 1 );
773
0
    Pel n1, n2;
774
0
    switch( dir[ x ] )
775
0
    {
776
0
    case 0:  n1 = mag[ r ];      n2 = mag[ l ];      break;
777
0
    case 45: n1 = magBelow[ r ]; n2 = magAbove[ l ]; break;
778
0
    case 90: n1 = magBelow[ x ]; n2 = magAbove[ x ]; break;
779
0
    default: n1 = magBelow[ l ]; n2 = magAbove[ r ]; break;
780
0
    }
781
0
    dst[ x ] = mag[ x ] < n1 || mag[ x ] < n2 ? 0 : mag[ x ];
782
0
  }
783
0
}
784
785
void fgaMorphRowCore( const Pel* src, int width, int xStart, int xEnd, bool dilate, Pel* dst )
786
0
{
787
0
  for( int x = xStart; x < xEnd; x++ )
788
0
  {
789
0
    const Pel l = src[ std::max( x - 1, 0 ) ], r = src[ std::min( x + 1, width - 1 ) ];
790
0
    dst[ x ] = dilate ? std::max( { l, src[ x ], r } ) : std::min( { l, src[ x ], r } );
791
0
  }
792
0
}
793
794
void fgaMorphColCore( const Pel* above, const Pel* cur, const Pel* below, int xStart, int xEnd, bool dilate, Pel* dst )
795
0
{
796
0
  for( int x = xStart; x < xEnd; x++ )
797
0
  {
798
0
    dst[ x ] = dilate ? std::max( { above[ x ], cur[ x ], below[ x ] } ) : std::min( { above[ x ], cur[ x ], below[ x ] } );
799
0
  }
800
0
}
801
802
Pel FGAnalyzer::Plane::clampedAt( int x, int y ) const
803
0
{
804
0
  return at( Clip3( 0, width - 1, x ), Clip3( 0, height - 1, y ) );
805
0
}
806
807
// ====================================================================================================================
808
// Initialization
809
// ====================================================================================================================
810
811
FGAnalyzer::FGAnalyzer()
812
1.49k
{
813
1.49k
  initKernels( m_kernels );
814
1.49k
}
815
816
void FGAnalyzer::initKernels( FgaRowKernels& k )
817
2.98k
{
818
2.98k
  k.sobel    = sobelRow;
819
2.98k
  k.nonMax   = nonMaxRow;
820
2.98k
  k.morphRow = morphRow;
821
2.98k
  k.morphCol = morphCol;
822
2.98k
  k.rowMax   = rowMax;
823
2.98k
  k.classify = classify;
824
2.98k
  k.findEqual = fgaFindEqualCore;
825
2.98k
  k.isZeroBlock  = fgaIsZeroBlockCore;
826
2.98k
  k.blockSums    = fgaBlockSumsCore;
827
2.98k
  k.squaredDct16 = fgaSquaredDct16Core;
828
2.98k
  k.downsample4  = fgaDownsample4Core;
829
2.98k
  k.sad4x4Row    = fgaSad4x4RowCore;
830
2.98k
  k.residualRow  = fgaResidualRowCore;
831
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
832
  initFGAnalyzerX86( k );
833
#endif
834
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM )
835
  initFGAnalyzerARM( k );
836
#endif
837
2.98k
}
838
839
void FGAnalyzer::init( int width, int height, ChromaFormat chromaFormat, const int bitDepths[ MAX_NUM_CH ], const bool doAnalysis[ MAX_NUM_COMP ], bool improved, bool arModel )
840
0
{
841
0
  m_improved      = improved || arModel;
842
0
  m_arModel       = arModel;
843
0
  for( ArFilter& f : m_arFilter )
844
0
  {
845
0
    f = ArFilter();
846
0
  }
847
0
  m_strengthScale = m_improved ? IMPROVED_STRENGTH_SCALE : 1;
848
0
  m_width        = width;
849
0
  m_height       = height;
850
0
  m_chromaFormat = chromaFormat;
851
0
  for( int ch = 0; ch < MAX_NUM_CH; ch++ )
852
0
  {
853
0
    m_bitDepths[ ch ] = bitDepths[ ch ];
854
0
  }
855
856
  // No grain is signalled until an analysis has measured it. VTM signals default parameters (scaling factor 16 for
857
  // luma and 8 for chroma) until its first successful fit, which adds strong grain to video without grain, where the
858
  // fit never succeeds.
859
0
  for( int c = 0; c < MAX_NUM_COMP; c++ )
860
0
  {
861
0
    SeiFgc::CompModel& cm   = m_compModel[ c ];
862
0
    cm.presentFlag           = false;
863
0
    cm.numModelValues        = 1;
864
0
    cm.numIntensityIntervals = 0;
865
0
    cm.intensityValues.resize( MAX_NUM_INTENSITIES );
866
0
    for( auto& iv : cm.intensityValues )
867
0
    {
868
0
      iv.intensityIntervalLowerBound = 0;
869
0
      iv.intensityIntervalUpperBound = 0;
870
0
      iv.compModelValue.assign( MAX_ALLOWED_MODEL_VALUES, 0 );
871
0
    }
872
0
    m_doAnalysis[ c ]  = doAnalysis[ c ];
873
0
    m_cutoffPairs[ c ] = 0;
874
0
    m_failedFits [ c ] = 0;
875
0
    resetStatistics( c );
876
0
  }
877
0
}
878
879
// Drops the data accumulated over the analysed pictures of a component.
880
void FGAnalyzer::resetStatistics( int c )
881
0
{
882
0
  const int numMeans = 1 << m_bitDepths[ toChannelType( ComponentID( c ) ) ];
883
0
  m_squaredDctSums[ c ].assign( numMeans, SquaredDctSum( BLOCK_SIZE * BLOCK_SIZE, 0.0 ) );
884
0
  m_numDctBlocks  [ c ].assign( numMeans, 0.0 );
885
0
  m_storedMeanIntensity[ c ].clear();
886
0
  m_storedStrengthIntensity[ c ].clear();
887
0
  m_storedElementsPerInterval[ c ].clear();
888
0
}
889
890
// ====================================================================================================================
891
// Temporal residual
892
// ====================================================================================================================
893
894
FgaTemporalResidual::FgaTemporalResidual()
895
1.49k
{
896
1.49k
  FGAnalyzer::initKernels( m_kernels );
897
1.49k
}
898
899
void FgaTemporalResidual::downsample( const CPelBuf& src, FGAnalyzer::Plane& dst ) const
900
0
{
901
0
  dst.resize( src.width / 4, src.height / 4 );
902
0
  for( int y = 0; y < dst.height; y++ )
903
0
  {
904
0
    m_kernels.downsample4( src.bufAt( 0, 4 * y ), (int) src.stride, dst.width, dst.row( y ) );
905
0
  }
906
0
}
907
908
void FgaTemporalResidual::search( const FGAnalyzer::Plane& cur, const FGAnalyzer::Plane& ref, int numBlocksX, int numBlocksY, Motion& motion ) const
909
0
{
910
0
  const int numBlocks = numBlocksX * numBlocksY;
911
0
  const int range     = cur.width * 4 > 2048 ? RESIDUAL_RANGE_WIDE : RESIDUAL_RANGE;
912
0
  motion.dx.assign( numBlocks, 0 );
913
0
  motion.dy.assign( numBlocks, 0 );
914
0
  std::vector<int> bestSad( numBlocks ), bestScore( numBlocks ), sad( numBlocksX );
915
916
  // zero motion; another vector must have a SAD below ZERO_BIAS_NUM / ZERO_BIAS_DEN of it
917
0
  for( int by = 0; by < numBlocksY; by++ )
918
0
  {
919
0
    int* s = &bestSad[ by * numBlocksX ];
920
0
    m_kernels.sad4x4Row( cur.row( 4 * by ), cur.width, ref.row( 4 * by ), ref.width, numBlocksX, s );
921
0
    for( int bx = 0; bx < numBlocksX; bx++ )
922
0
    {
923
0
      bestScore[ by * numBlocksX + bx ] = s[ bx ] * ZERO_BIAS_NUM;
924
0
    }
925
0
  }
926
0
  for( int dy = -range; dy <= range; dy++ )
927
0
  {
928
0
    for( int dx = -range; dx <= range; dx++ )
929
0
    {
930
0
      if( dx == 0 && dy == 0 )
931
0
      {
932
0
        continue;
933
0
      }
934
      // the blocks whose displaced block lies inside the reference
935
0
      const int bx0 = dx < 0 ? ( 3 - dx ) / 4 : 0;
936
0
      const int bx1 = ref.width - 4 - dx < 0 ? 0 : std::min( numBlocksX, ( ref.width - 4 - dx ) / 4 + 1 );
937
0
      if( bx1 <= bx0 )
938
0
      {
939
0
        continue;
940
0
      }
941
0
      for( int by = 0; by < numBlocksY; by++ )
942
0
      {
943
0
        const int ry = 4 * by + dy;
944
0
        if( ry < 0 || ry + 4 > ref.height )
945
0
        {
946
0
          continue;
947
0
        }
948
0
        m_kernels.sad4x4Row( cur.row( 4 * by ) + 4 * bx0, cur.width, ref.row( ry ) + 4 * bx0 + dx, ref.width, bx1 - bx0, sad.data() );
949
0
        for( int bx = bx0; bx < bx1; bx++ )
950
0
        {
951
0
          const int b = by * numBlocksX + bx;
952
0
          const int score = sad[ bx - bx0 ] * ZERO_BIAS_DEN;
953
0
          if( score < bestScore[ b ] )
954
0
          {
955
0
            bestScore[ b ] = score;
956
0
            bestSad  [ b ] = sad[ bx - bx0 ];
957
0
            motion.dx[ b ] = (int8_t) dx;
958
0
            motion.dy[ b ] = (int8_t) dy;
959
0
          }
960
0
        }
961
0
      }
962
0
    }
963
0
  }
964
0
  int64_t sum = 0;
965
0
  for( int s : bestSad )
966
0
  {
967
0
    sum += s;
968
0
  }
969
0
  motion.meanSad = (double) sum / ( 16.0 * numBlocks );
970
0
}
971
972
int FgaTemporalResidual::compute( const CPelUnitBuf& cur, const CPelUnitBuf* prev, const CPelUnitBuf* next, PelStorage& residual ) const
973
0
{
974
0
  const CPelBuf curY = cur.get( COMP_Y );
975
0
  const int numBlocksX = curY.width  / 16;
976
0
  const int numBlocksY = curY.height / 16;
977
0
  if( ( !prev && !next ) || numBlocksX == 0 || numBlocksY == 0 )
978
0
  {
979
0
    return 0;
980
0
  }
981
982
0
  FGAnalyzer::Plane curQ, refQ;
983
0
  downsample( curY, curQ );
984
0
  Motion motionPrev, motionNext;
985
0
  if( prev )
986
0
  {
987
0
    downsample( prev->get( COMP_Y ), refQ );
988
0
    search( curQ, refQ, numBlocksX, numBlocksY, motionPrev );
989
0
  }
990
0
  if( next )
991
0
  {
992
0
    downsample( next->get( COMP_Y ), refQ );
993
0
    search( curQ, refQ, numBlocksX, numBlocksY, motionNext );
994
0
  }
995
0
  if( prev && next )
996
0
  {
997
0
    if( motionPrev.meanSad > 2 * motionNext.meanSad + NEIGHBOUR_SAD_MARGIN )
998
0
    {
999
0
      prev = nullptr;
1000
0
    }
1001
0
    else if( motionNext.meanSad > 2 * motionPrev.meanSad + NEIGHBOUR_SAD_MARGIN )
1002
0
    {
1003
0
      next = nullptr;
1004
0
    }
1005
0
  }
1006
  // the first neighbour of the residual, and the second one if both are used
1007
0
  const CPelUnitBuf* first  = prev ? prev : next;
1008
0
  const CPelUnitBuf* second = prev ? next : nullptr;
1009
0
  const Motion& motionFirst  = prev ? motionPrev : motionNext;
1010
0
  const Motion& motionSecond = motionNext;
1011
1012
0
  residual.destroy();
1013
0
  residual.create( cur.chromaFormat, Area( 0, 0, curY.width, curY.height ) );
1014
0
  residual.fill( 0 );   // the samples outside the 16x16 blocks are not analysed
1015
0
  for( int c = 0; c < getNumberValidComponents( cur.chromaFormat ); c++ )
1016
0
  {
1017
0
    const ComponentID compId = ComponentID( c );
1018
0
    const int csx = getComponentScaleX( compId, cur.chromaFormat );
1019
0
    const int csy = getComponentScaleY( compId, cur.chromaFormat );
1020
0
    const int bw  = 16 >> csx;
1021
0
    const int bh  = 16 >> csy;
1022
0
    const CPelBuf o  = cur.get( compId );
1023
0
    const CPelBuf f  = first->get( compId );
1024
0
    const CPelBuf s  = second ? second->get( compId ) : CPelBuf();
1025
0
    PelBuf        r  = residual.get( compId );
1026
0
    for( int by = 0; by < numBlocksY; by++ )
1027
0
    {
1028
0
      for( int bx = 0; bx < numBlocksX; bx++ )
1029
0
      {
1030
0
        const int b  = by * numBlocksX + bx;
1031
0
        const int x0 = bx * bw;
1032
0
        const int y0 = by * bh;
1033
        // vectors in samples of this component (4 full resolution samples per downsampled sample)
1034
0
        const int fx = ( 4 * motionFirst.dx[ b ] ) >> csx, fy = ( 4 * motionFirst.dy[ b ] ) >> csy;
1035
0
        const int sx = second ? ( 4 * motionSecond.dx[ b ] ) >> csx : 0;
1036
0
        const int sy = second ? ( 4 * motionSecond.dy[ b ] ) >> csy : 0;
1037
0
        for( int y = 0; y < bh; y++ )
1038
0
        {
1039
0
          m_kernels.residualRow( o.bufAt( x0, y0 + y ), f.bufAt( x0 + fx, y0 + y + fy ),
1040
0
                                 second ? s.bufAt( x0 + sx, y0 + y + sy ) : nullptr, bw, r.bufAt( x0, y0 + y ) );
1041
0
        }
1042
0
      }
1043
0
    }
1044
0
  }
1045
0
  return second ? 6 : 2;
1046
0
}
1047
1048
// VTM's scaling factors: twice the standard deviation, at log2 scale factor 2. As in getSignalledParameters(), the 4:2:0
1049
// chroma cut-off frequencies are halved and the scaling factors stay at MAX_SCALING_FACTOR or below.
1050
void FGAnalyzer::getVtmParameters( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const
1051
0
{
1052
0
  log2ScaleFactor = 2;
1053
0
  for( int c = 0; c < MAX_NUM_COMP; c++ )
1054
0
  {
1055
0
    SeiFgc::CompModel& cm = compModel[ c ];
1056
0
    cm = m_compModel[ c ];
1057
0
    if( !cm.presentFlag || strength <= 0.0 )
1058
0
    {
1059
0
      cm.presentFlag = false;
1060
0
      continue;
1061
0
    }
1062
0
    const bool chroma420 = c != COMP_Y && m_chromaFormat == CHROMA_420;
1063
0
    const int  bitDepth  = m_bitDepths[ toChannelType( ComponentID( c ) ) ];
1064
0
    bool anyGrain = false;
1065
0
    for( int i = 0; i < cm.numIntensityIntervals; i++ )
1066
0
    {
1067
0
      std::vector<int>& v = cm.intensityValues[ i ].compModelValue;
1068
0
      for( int k = 1; chroma420 && k < cm.numModelValues; k++ )
1069
0
      {
1070
0
        v[ k ] = Clip3( 2, 14, ( v[ k ] + 1 ) >> 1 );
1071
0
      }
1072
0
      const int scale = ( v[ 0 ] << log2ScaleFactor ) >> ( bitDepth - BIT_DEPTH_8 );
1073
0
      v[ 0 ]   = std::min( MAX_SCALING_FACTOR, (int) ( scale * strength + 0.5 ) );
1074
0
      anyGrain = anyGrain || v[ 0 ] > 0;
1075
0
    }
1076
0
    cm.presentFlag = anyGrain;
1077
0
  }
1078
0
}
1079
1080
// The parameters of the auto-regressive model: model values scale, a1, 0 (no correlation between the components), a3,
1081
// r and a5, the last four in units of 2^-log2ScaleFactor. The scaling factors are calibrated with the impulse response
1082
// energy of the quantized filter (AR_PATTERN_STDDEV); the log2 scale factor is the largest (2 to 7) at which they stay
1083
// at MAX_SCALING_FACTOR or below, for the finest steps of both the scaling factors and the filter coefficients.
1084
void FGAnalyzer::getArParameters( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const
1085
0
{
1086
0
  for( log2ScaleFactor = 7; log2ScaleFactor >= 2; log2ScaleFactor-- )
1087
0
  {
1088
0
    const int one = 1 << log2ScaleFactor;
1089
0
    bool fits = true;
1090
0
    for( int c = 0; c < MAX_NUM_COMP; c++ )
1091
0
    {
1092
0
      SeiFgc::CompModel& cm = compModel[ c ];
1093
0
      cm = m_compModel[ c ];
1094
0
      const ArFilter& f = m_arFilter[ c ];
1095
0
      if( !cm.presentFlag || strength <= 0.0 || !f.valid )
1096
0
      {
1097
0
        cm.presentFlag = false;
1098
0
        continue;
1099
0
      }
1100
0
      const int coef[ 6 ] = { 0, (int) std::lround( f.a1 * one ), 0, (int) std::lround( f.a3 * one ), (int) std::lround( f.r * one ),
1101
0
                              (int) std::lround( f.a5 * one ) };
1102
0
      const int bitDepth = m_bitDepths[ toChannelType( ComponentID( c ) ) ];
1103
0
      const double patternStddev = AR_PATTERN_STDDEV[ c == COMP_Y ? 0 : 1 ] * std::sqrt( arEnergy( coef, log2ScaleFactor ) );
1104
0
      bool anyGrain = false;
1105
0
      cm.numModelValues = 6;
1106
0
      for( int i = 0; i < cm.numIntensityIntervals; i++ )
1107
0
      {
1108
0
        std::vector<int>& v = cm.intensityValues[ i ].compModelValue;
1109
0
        const double stddev = v[ 0 ] / ( 2.0 * m_strengthScale ) / std::sqrt( f.nonDcShare );
1110
0
        const double scale  = std::ldexp( stddev * strength, log2ScaleFactor + 10 - bitDepth ) / patternStddev;
1111
0
        fits     = fits && scale <= MAX_SCALING_FACTOR + 0.5;
1112
0
        v.assign( 6, 0 );
1113
0
        v[ 0 ]   = std::min( MAX_SCALING_FACTOR, (int) ( scale + 0.5 ) );
1114
0
        std::copy( coef + 1, coef + 6, v.begin() + 1 );
1115
0
        anyGrain = anyGrain || v[ 0 ] > 0;
1116
0
      }
1117
0
      cm.presentFlag = anyGrain;
1118
0
    }
1119
0
    if( fits || log2ScaleFactor == 2 )
1120
0
    {
1121
0
      return;
1122
0
    }
1123
0
  }
1124
0
}
1125
1126
// The scaling factors are calibrated, because the standard deviation of the synthesized grain depends on the cut-off
1127
// frequencies by a factor of up to 5 (PATTERN_STDDEV), and the block measurement misses up to 9% of the grain
1128
// variance (BLOCK_VARIANCE_SHARE). VTM signals twice the standard deviation, which fits only cut-off frequencies near 8.
1129
// The log2 scale factor is the largest (2 to 7, the range of the synthesis) at which all scaling factors stay at
1130
// MAX_SCALING_FACTOR or below, for the finest steps.
1131
void FGAnalyzer::getSignalledParameters( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const
1132
0
{
1133
0
  if( !m_improved )
1134
0
  {
1135
0
    getVtmParameters( strength, log2ScaleFactor, compModel );
1136
0
    return;
1137
0
  }
1138
0
  if( m_arModel )
1139
0
  {
1140
0
    getArParameters( strength, log2ScaleFactor, compModel );
1141
0
    return;
1142
0
  }
1143
0
  std::vector<double> scales[ MAX_NUM_COMP ];   // per interval, at log2 scale factor 0
1144
0
  double maxScale = 0.0;
1145
0
  for( int c = 0; c < MAX_NUM_COMP; c++ )
1146
0
  {
1147
0
    SeiFgc::CompModel& cm = compModel[ c ];
1148
0
    cm = m_compModel[ c ];
1149
0
    if( !cm.presentFlag || strength <= 0.0 )
1150
0
    {
1151
0
      cm.presentFlag = false;
1152
0
      continue;
1153
0
    }
1154
    // The analysis measures the chroma grain on the subsampled chroma planes, but for 4:2:0 the synthesis (VTM, vvdec)
1155
    // treats the chroma cut-off frequencies as luma resolution values and doubles them: they are halved here. Its
1156
    // halving of the chroma scaling factors is part of the chroma calibration tables.
1157
0
    const bool chroma420 = c != COMP_Y && m_chromaFormat == CHROMA_420;
1158
0
    const int  table     = chroma420 ? 1 : 0;
1159
0
    const int  bitDepth  = m_bitDepths[ toChannelType( ComponentID( c ) ) ];
1160
0
    for( int i = 0; i < cm.numIntensityIntervals; i++ )
1161
0
    {
1162
0
      std::vector<int>& v = cm.intensityValues[ i ].compModelValue;
1163
0
      for( int k = 1; chroma420 && k < cm.numModelValues; k++ )
1164
0
      {
1165
0
        v[ k ] = Clip3( 2, 14, ( v[ k ] + 1 ) >> 1 );
1166
0
      }
1167
0
      const int h = cm.numModelValues > 1 ? Clip3( 2, 14, v[ 1 ] ) : 8;
1168
0
      const int w = cm.numModelValues > 2 ? Clip3( 2, 14, v[ 2 ] ) : h;
1169
0
      const double stddev = v[ 0 ] / ( 2.0 * m_strengthScale ) / std::sqrt( BLOCK_VARIANCE_SHARE[ table ][ h - 2 ][ w - 2 ] );
1170
0
      scales[ c ].push_back( stddev * strength * ( 1 << ( 14 - bitDepth ) ) / PATTERN_STDDEV[ table ][ h - 2 ][ w - 2 ] );
1171
0
      maxScale = std::max( maxScale, scales[ c ].back() );
1172
0
    }
1173
0
  }
1174
1175
0
  log2ScaleFactor = 7;
1176
0
  while( log2ScaleFactor > 2 && maxScale * ( 1 << log2ScaleFactor ) > MAX_SCALING_FACTOR + 0.5 )
1177
0
  {
1178
0
    log2ScaleFactor--;
1179
0
  }
1180
0
  for( int c = 0; c < MAX_NUM_COMP; c++ )
1181
0
  {
1182
0
    SeiFgc::CompModel& cm = compModel[ c ];
1183
0
    if( !cm.presentFlag )
1184
0
    {
1185
0
      continue;
1186
0
    }
1187
0
    bool anyGrain = false;
1188
0
    for( int i = 0; i < cm.numIntensityIntervals; i++ )
1189
0
    {
1190
0
      int& scale = cm.intensityValues[ i ].compModelValue[ 0 ];
1191
0
      scale    = std::min( MAX_SCALING_FACTOR, (int) ( scales[ c ][ i ] * ( 1 << log2ScaleFactor ) + 0.5 ) );
1192
0
      anyGrain = anyGrain || scale > 0;
1193
0
    }
1194
0
    cm.presentFlag = anyGrain;
1195
0
  }
1196
0
}
1197
1198
// ====================================================================================================================
1199
// Analysis of one picture
1200
// ====================================================================================================================
1201
1202
void FGAnalyzer::estimateGrainParameters( const CPelUnitBuf& orig, const CPelUnitBuf& denoised, const CPelUnitBuf* residual, int residualWeight,
1203
                                          bool sceneCut )
1204
0
{
1205
0
  const int numComp = getNumberValidComponents( m_chromaFormat );
1206
0
  m_grainWeight = residual ? residualWeight : 1;
1207
0
  for( int c = 0; sceneCut && c < numComp; c++ )
1208
0
  {
1209
0
    resetStatistics( c );
1210
0
  }
1211
0
  for( int c = 0; c < numComp; c++ )
1212
0
  {
1213
0
    const ComponentID compId = ComponentID( c );
1214
0
    const CPelBuf o = orig.get( compId );
1215
0
    const CPelBuf d = denoised.get( compId );
1216
0
    m_denoised[ c ].resize( d.width, d.height );
1217
0
    m_grain   [ c ].resize( d.width, d.height );
1218
0
    for( int y = 0; y < (int) o.height; y++ )
1219
0
    {
1220
0
      const Pel* ro = o.bufAt( 0, y );
1221
0
      const Pel* rd = d.bufAt( 0, y );
1222
0
      Pel* td = m_denoised[ c ].row( y );
1223
0
      Pel* tg = m_grain   [ c ].row( y );
1224
0
      std::copy( rd, rd + o.width, td );
1225
0
      if( residual )
1226
0
      {
1227
0
        const Pel* rr = residual->get( compId ).bufAt( 0, y );
1228
0
        std::copy( rr, rr + o.width, tg );
1229
0
      }
1230
0
      else
1231
0
      {
1232
0
        for( int x = 0; x < (int) o.width; x++ )
1233
0
        {
1234
0
          tg[ x ] = (Pel) ( rd[ x ] - ro[ x ] );
1235
0
        }
1236
0
      }
1237
0
    }
1238
0
    if( m_doAnalysis[ c ] )
1239
0
    {
1240
0
      findMask( compId );
1241
0
    }
1242
0
  }
1243
1244
0
  m_cutoffPairs[ COMP_Y ] = m_cutoffPairs[ COMP_Cb ] = m_cutoffPairs[ COMP_Cr ] = 0;
1245
0
  for( int c = 0; c < numComp; c++ )
1246
0
  {
1247
0
    estimateComponent( ComponentID( c ) );
1248
0
  }
1249
0
}
1250
1251
// Mask of the samples that are not part of a flat area: edges found at full, half and quarter resolution, dilated.
1252
void FGAnalyzer::findMask( ComponentID compId )
1253
0
{
1254
0
  const int bitDepth  = m_bitDepths[ toChannelType( compId ) ];
1255
0
  const Plane& picture = m_denoised[ compId ];
1256
0
  Plane& mask = m_mask[ compId ];
1257
1258
0
  Scratch& s = m_scratch;
1259
0
  subsample( picture, s.half, 2 );
1260
0
  subsample( picture, s.quarter, 4 );
1261
1262
0
  maskForScale( picture, mask, bitDepth, 4 );
1263
0
  maskForScale( s.half, s.lowMask, bitDepth, 3 );
1264
0
  combineUpsampled( mask, s.lowMask, 2, s.wide );
1265
0
  maskForScale( s.quarter, s.lowMask, bitDepth, 2 );
1266
0
  combineUpsampled( mask, s.lowMask, 4, s.wide );
1267
1268
  // final dilation to fill the holes, then erosion
1269
0
  morphStep( m_kernels, mask, s.morph, true );
1270
0
  morphStep( m_kernels, mask, s.morph, true );
1271
0
  morphStep( m_kernels, mask, s.morph, false );
1272
0
}
1273
1274
void FGAnalyzer::maskForScale( const Plane& picture, Plane& mask, int bitDepth, int numDilations )
1275
0
{
1276
0
  Scratch& s = m_scratch;
1277
0
  gradientWithNonMaxSuppression( m_kernels, picture, mask, bitDepth, s.magnitude, s.direction );
1278
0
  doubleThreshold( m_kernels, mask, bitDepth );
1279
0
  edgeTracking( m_kernels, mask, bitDepth, s );
1280
0
  suppressLowIntensity( picture, mask, bitDepth );
1281
0
  for( int i = 0; i < numDilations; i++ )
1282
0
  {
1283
0
    morphStep( m_kernels, mask, s.morph, true );
1284
0
  }
1285
0
}
1286
1287
// Collects the block statistics of one component and estimates its parameters.
1288
void FGAnalyzer::estimateComponent( ComponentID compId )
1289
0
{
1290
0
  if( !m_doAnalysis[ compId ] || ( compId > COMP_Y && !m_compModel[ COMP_Y ].presentFlag ) )
1291
0
  {
1292
0
    m_compModel[ compId ].presentFlag = false;
1293
0
    m_cutoffPairs[ compId ] = 0;
1294
0
    return;
1295
0
  }
1296
1297
0
  const int bitDepth = m_bitDepths[ toChannelType( compId ) ];
1298
0
  const Plane& denoised = m_denoised[ compId ];
1299
0
  const Plane& grain    = m_grain[ compId ];
1300
0
  const Plane& mask     = m_mask[ compId ];
1301
0
  const int maxStrength = MAX_REAL_SCALE << ( bitDepth - BIT_DEPTH_8 );
1302
1303
0
  static_assert( BLOCK_SIZE == 16, "the block kernels work on 16x16 blocks" );
1304
0
  std::vector<int> vecMean;
1305
0
  std::vector<int> vecStrength;
1306
1307
  // older pictures count less
1308
0
  for( size_t m = 0; m < m_numDctBlocks[ compId ].size(); m++ )
1309
0
  {
1310
0
    if( m_numDctBlocks[ compId ][ m ] > 0 )
1311
0
    {
1312
0
      m_numDctBlocks[ compId ][ m ] *= ACCUMULATION_DECAY;
1313
0
      for( double& v : m_squaredDctSums[ compId ][ m ] )
1314
0
      {
1315
0
        v *= ACCUMULATION_DECAY;
1316
0
      }
1317
0
    }
1318
0
  }
1319
1320
  // The flat blocks, visited column by column as in VTM; the order matters for the floating point sums of the fit.
1321
0
  struct FlatBlock
1322
0
  {
1323
0
    int    x, y, mean;
1324
0
    double stddev;     // grain standard deviation
1325
0
  };
1326
0
  std::vector<FlatBlock> blocks;
1327
0
  const double weightStddev = std::sqrt( (double) m_grainWeight );   // standard deviation of m_grain per grain standard deviation
1328
0
  for( int x = 0; x + BLOCK_SIZE <= denoised.width; x += BLOCK_SIZE )
1329
0
  {
1330
0
    for( int y = 0; y + BLOCK_SIZE <= denoised.height; y += BLOCK_SIZE )
1331
0
    {
1332
0
      if( !m_kernels.isZeroBlock( mask.row( y ) + x, mask.width ) )
1333
0
      {
1334
0
        continue;   // not flat
1335
0
      }
1336
0
      const BlockStats picStats = meanStdDev( m_kernels, denoised, x, y );
1337
0
      if( 2 * picStats.stddev >= ( maxStrength >> 1 ) )
1338
0
      {
1339
0
        continue;   // the denoised block is not flat enough: the mask missed some texture
1340
0
      }
1341
0
      blocks.push_back( { x, y, picStats.mean, meanStdDev( m_kernels, grain, x, y ).stddev / weightStddev } );
1342
0
    }
1343
0
  }
1344
1345
  // Where the motion compensation of the temporal residual failed, the residual of a block is much larger than the
1346
  // grain of the other blocks of its intensity: such blocks are left out.
1347
0
  std::vector<double> maxVariance( NUM_OUTLIER_BINS, std::numeric_limits<double>::max() );
1348
0
  if( m_grainWeight > 1 )
1349
0
  {
1350
0
    const int binShift = bitDepth - 4;
1351
0
    std::vector<std::vector<double>> binVariances( NUM_OUTLIER_BINS );
1352
0
    for( const FlatBlock& b : blocks )
1353
0
    {
1354
0
      binVariances[ b.mean >> binShift ].push_back( b.stddev * b.stddev );
1355
0
    }
1356
0
    for( int i = 0; i < NUM_OUTLIER_BINS; i++ )
1357
0
    {
1358
0
      std::vector<double>& v = binVariances[ i ];
1359
0
      if( (int) v.size() >= MIN_OUTLIER_BIN_BLOCKS )
1360
0
      {
1361
0
        std::nth_element( v.begin(), v.begin() + v.size() / 2, v.end() );
1362
0
        maxVariance[ i ] = OUTLIER_VARIANCE_RATIO * v[ v.size() / 2 ];
1363
0
      }
1364
0
    }
1365
0
  }
1366
1367
0
  std::vector<const FlatBlock*> grainBlocks;
1368
0
  for( const FlatBlock& b : blocks )
1369
0
  {
1370
0
    if( b.stddev * b.stddev > maxVariance[ b.mean >> ( bitDepth - 4 ) ] )
1371
0
    {
1372
0
      continue;   // motion compensation failure
1373
0
    }
1374
0
    const int strength = (int) ( 2 * m_strengthScale * b.stddev + 0.5 );
1375
0
    if( strength >= maxStrength * m_strengthScale )
1376
0
    {
1377
0
      continue;   // too strong to be grain: probably texture or motion left in the grain estimate
1378
0
    }
1379
0
    vecMean.push_back( b.mean );
1380
0
    vecStrength.push_back( strength );
1381
0
    grainBlocks.push_back( &b );
1382
0
  }
1383
1384
  // When the grain of this picture is much stronger or weaker than that of the accumulated data, a new scene began:
1385
  // the accumulated data are dropped. Otherwise the second pass of the fit would discard the new scene's points as
1386
  // outliers of the old fit for several analyses, and the old grain would stay on.
1387
0
  double storedStrength = 0.0, storedElements = 0.0;
1388
0
  for( size_t i = 0; i < m_storedStrengthIntensity[ compId ].size(); i++ )
1389
0
  {
1390
0
    storedStrength += m_storedStrengthIntensity  [ compId ][ i ];
1391
0
    storedElements += m_storedElementsPerInterval[ compId ][ i ];
1392
0
  }
1393
0
  if( m_improved && (int) vecStrength.size() >= GRAIN_CHANGE_MIN_BLOCKS && storedElements > 0.0 )
1394
0
  {
1395
0
    const double mean   = std::accumulate( vecStrength.begin(), vecStrength.end(), 0.0 ) / vecStrength.size();
1396
0
    const double stored = storedStrength / storedElements;
1397
0
    if( mean > GRAIN_CHANGE_RATIO * stored || stored > GRAIN_CHANGE_RATIO * mean )
1398
0
    {
1399
0
      resetStatistics( compId );
1400
0
    }
1401
0
  }
1402
1403
0
  SquaredDctSum blockDct( BLOCK_SIZE * BLOCK_SIZE );
1404
0
  for( const FlatBlock* bp : grainBlocks )
1405
0
  {
1406
0
    const FlatBlock& b = *bp;
1407
0
    double* dctSum = m_squaredDctSums[ compId ][ b.mean ].data();
1408
0
    if( m_grainWeight == 1 )
1409
0
    {
1410
0
      m_kernels.squaredDct16( grain.row( b.y ) + b.x, grain.width, ( 1 << ( bitDepth + 4 ) ) - 1, dctSum );
1411
0
    }
1412
0
    else
1413
0
    {
1414
      // the spectrum in grain units, so that pictures with one or two neighbours add up correctly
1415
0
      std::fill( blockDct.begin(), blockDct.end(), 0.0 );
1416
0
      m_kernels.squaredDct16( grain.row( b.y ) + b.x, grain.width, ( 1 << ( bitDepth + 4 ) ) - 1, blockDct.data() );
1417
0
      for( int i = 0; i < BLOCK_SIZE * BLOCK_SIZE; i++ )
1418
0
      {
1419
0
        dctSum[ i ] += blockDct[ i ] / m_grainWeight;
1420
0
      }
1421
0
    }
1422
0
    m_numDctBlocks[ compId ][ b.mean ]++;
1423
0
  }
1424
1425
0
  estimateScalingFactors( vecMean, vecStrength, bitDepth, compId );
1426
0
  if( m_arModel )
1427
0
  {
1428
0
    fitArFilter( compId );
1429
0
  }
1430
0
  else
1431
0
  {
1432
0
    estimateCutoffFreq( compId, bitDepth );
1433
0
  }
1434
0
}
1435
1436
// ====================================================================================================================
1437
// Scaling function
1438
// ====================================================================================================================
1439
1440
void FGAnalyzer::estimateScalingFactors( std::vector<int>& dataX, std::vector<int>& dataY, int bitDepth, ComponentID compId )
1441
0
{
1442
0
  std::vector<double> coeffs;
1443
0
  std::vector<double> scalingVec;
1444
0
  std::vector<int>    quantVec;
1445
1446
  // fit a polynomial to the points; the second pass removes points far from the first fit and stores the data
1447
0
  bool valid  = false;
1448
0
  bool stored = false;
1449
0
  for( int i = 0; i < NUM_PASSES; i++ )
1450
0
  {
1451
0
    valid  = fitFunction( dataX, dataY, coeffs, scalingVec, ORDER, bitDepth, i == 1, compId );
1452
0
    stored = i == 1;
1453
0
    if( !valid )
1454
0
    {
1455
0
      break;
1456
0
    }
1457
0
  }
1458
0
  if( valid )
1459
0
  {
1460
0
    valid = lloydMax( scalingVec, quantVec, QUANT_LEVELS, bitDepth );
1461
0
  }
1462
0
  if( valid )
1463
0
  {
1464
0
    setEstimatedParameters( quantVec, bitDepth, compId );
1465
0
    m_failedFits[ compId ] = 0;
1466
0
    return;
1467
0
  }
1468
0
  if( !m_improved )
1469
0
  {
1470
0
    return;   // VTM keeps the previous parameters
1471
0
  }
1472
1473
  // Without a fit, for example because there were too few points, the previous parameters are kept for a while (VTM
1474
  // keeps them for good). The stored data age as if a picture without data had been added, and after MAX_FAILED_FITS
1475
  // failures in a row the grain is no longer signalled, as it was measured on pictures too long ago: otherwise the
1476
  // grain of a grainy scene stays on in the following clean scene when the fit fails there.
1477
0
  if( !stored )
1478
0
  {
1479
0
    for( auto* data : { &m_storedMeanIntensity[ compId ], &m_storedStrengthIntensity[ compId ], &m_storedElementsPerInterval[ compId ] } )
1480
0
    {
1481
0
      for( double& v : *data )
1482
0
      {
1483
0
        v *= ACCUMULATION_DECAY;
1484
0
      }
1485
0
    }
1486
0
  }
1487
0
  if( ++m_failedFits[ compId ] >= MAX_FAILED_FITS )
1488
0
  {
1489
0
    m_compModel  [ compId ].presentFlag = false;
1490
0
    m_cutoffPairs[ compId ] = 0;
1491
0
  }
1492
0
}
1493
1494
// Fits a polynomial of the given order to the strength over intensity points, averaged per intensity interval and
1495
// accumulated with the points of the previous pictures. scalingVec receives the function sampled from xmin to xmax,
1496
// followed by xmax and xmin.
1497
bool FGAnalyzer::fitFunction( std::vector<int>& dataX, std::vector<int>& dataY, std::vector<double>& coeffs, std::vector<double>& scalingVec,
1498
                                int order, int bitDepth, bool secondPass, ComponentID compId )
1499
0
{
1500
0
  const int intervalSize = INTERVAL_SIZE << ( ( bitDepth - BIT_DEPTH_8 ) >> 1 );
1501
0
  const int numIntervals = ( 1 << bitDepth ) / intervalSize;
1502
0
  std::vector<double> meanIntensity( numIntervals, 0.0 );
1503
0
  std::vector<double> strengthIntensity( numIntervals, 0.0 );
1504
0
  std::vector<double> elementsPerInterval( numIntervals, 0.0 );
1505
0
  std::vector<int> tmpDataX;
1506
0
  std::vector<int> tmpDataY;
1507
0
  double xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0;
1508
0
  double mn = 0.0, sd = 0.0;
1509
1510
0
  if( secondPass )
1511
0
  {
1512
0
    xmin = scalingVec.back();
1513
0
    scalingVec.pop_back();
1514
0
    xmax = scalingVec.back();
1515
0
    scalingVec.pop_back();
1516
0
    const int n = (int) dataY.size();
1517
0
    if( n != 0 )
1518
0
    {
1519
0
      mn = std::accumulate( dataY.begin(), dataY.end(), 0.0 ) / n;
1520
0
      for( int i = 0; i < n; i++ )
1521
0
      {
1522
0
        sd += ( dataY[ i ] - mn ) * ( dataY[ i ] - mn );
1523
0
      }
1524
0
      sd /= n;
1525
0
      sd = std::sqrt( sd );
1526
0
    }
1527
0
  }
1528
1529
0
  for( size_t i = 0; i < dataX.size(); i++ )
1530
0
  {
1531
0
    if( secondPass )
1532
0
    {
1533
0
      if( dataX[ i ] >= xmin && dataX[ i ] <= xmax )
1534
0
      {
1535
0
        const double fitted = scalingVec[ dataX[ i ] - (int) xmin ];
1536
0
        if( dataY[ i ] < fitted + sd * VAR_SCALE_UP && dataY[ i ] > fitted - sd * VAR_SCALE_DOWN )
1537
0
        {
1538
0
          const int idx = dataX[ i ] / intervalSize;
1539
0
          meanIntensity[ idx ]     += dataX[ i ];
1540
0
          strengthIntensity[ idx ] += dataY[ i ];
1541
0
          elementsPerInterval[ idx ]++;
1542
0
        }
1543
0
      }
1544
0
    }
1545
0
    else
1546
0
    {
1547
0
      const int idx = dataX[ i ] / intervalSize;
1548
0
      meanIntensity[ idx ]     += dataX[ i ];
1549
0
      strengthIntensity[ idx ] += dataY[ i ];
1550
0
      elementsPerInterval[ idx ]++;
1551
0
    }
1552
0
  }
1553
1554
  // the data of the previous pictures, with decreasing weight
1555
0
  if( !m_storedMeanIntensity[ compId ].empty() && !m_storedStrengthIntensity[ compId ].empty() )
1556
0
  {
1557
0
    for( int i = 0; i < numIntervals; i++ )
1558
0
    {
1559
0
      elementsPerInterval[ i ] += ACCUMULATION_DECAY * m_storedElementsPerInterval[ compId ][ i ];
1560
0
      meanIntensity[ i ]       += ACCUMULATION_DECAY * m_storedMeanIntensity[ compId ][ i ];
1561
0
      strengthIntensity[ i ]   += ACCUMULATION_DECAY * m_storedStrengthIntensity[ compId ][ i ];
1562
0
    }
1563
0
  }
1564
1565
0
  for( int i = 0; i < numIntervals; i++ )
1566
0
  {
1567
0
    if( elementsPerInterval[ i ] >= MIN_ELEMENTS_PER_INTERVAL )
1568
0
    {
1569
0
      tmpDataX.push_back( (int) ( meanIntensity[ i ] / elementsPerInterval[ i ] ) );
1570
0
      tmpDataY.push_back( (int) ( strengthIntensity[ i ] / elementsPerInterval[ i ] + 0.5 ) );   // VTM truncates
1571
0
    }
1572
0
  }
1573
1574
0
  if( secondPass )
1575
0
  {
1576
    // keep the data to improve the estimate of the next pictures
1577
0
    m_storedMeanIntensity[ compId ]       = meanIntensity;
1578
0
    m_storedStrengthIntensity[ compId ]   = strengthIntensity;
1579
0
    m_storedElementsPerInterval[ compId ] = elementsPerInterval;
1580
0
  }
1581
1582
  // remove single points
1583
0
  for( int i = 0; i < (int) tmpDataX.size(); i++ )
1584
0
  {
1585
0
    int check = 0;
1586
0
    for( int j = -WINDOW; j <= WINDOW; j++ )
1587
0
    {
1588
0
      const int idx = i + j;
1589
0
      if( idx >= 0 && idx < (int) tmpDataX.size() && j != 0 )
1590
0
      {
1591
0
        check += abs( tmpDataX[ i ] / intervalSize - tmpDataX[ idx ] / intervalSize ) <= WINDOW ? 1 : 0;
1592
0
      }
1593
0
    }
1594
0
    if( check < NBRS )
1595
0
    {
1596
0
      tmpDataX.erase( tmpDataX.begin() + i );
1597
0
      tmpDataY.erase( tmpDataY.begin() + i );
1598
0
      i--;
1599
0
    }
1600
0
  }
1601
1602
  // VTM needs order + 1 measured points here. Chroma block means usually fall into only a few intervals around the
1603
  // middle of the range, so the chroma fit failed for most content and VTM signalled its default chroma parameters.
1604
  // The points added by extendPoints() model the fall-off on both sides of the measured range, which is enough for the
1605
  // improved fit when at least two measured points are left.
1606
0
  if( (int) tmpDataX.size() < ( m_improved ? MIN_MEASURED_POINTS : order + 1 ) )
1607
0
  {
1608
0
    return false;   // too few points to fit the function
1609
0
  }
1610
0
  extendPoints( tmpDataX, tmpDataY, bitDepth );
1611
0
  CHECK( tmpDataX.size() > MAXPAIRS, "Maximum dataset size exceeded." );
1612
0
  if( (int) tmpDataX.size() < order + 1 )
1613
0
  {
1614
0
    return false;
1615
0
  }
1616
1617
0
  xmin = xmax = tmpDataX[ 0 ];
1618
0
  ymin = ymax = tmpDataY[ 0 ];
1619
0
  for( size_t i = 0; i < tmpDataX.size(); i++ )
1620
0
  {
1621
0
    xmin = std::min<double>( xmin, tmpDataX[ i ] );
1622
0
    xmax = std::max<double>( xmax, tmpDataX[ i ] );
1623
0
    ymin = std::min<double>( ymin, tmpDataY[ i ] );
1624
0
    ymax = std::max<double>( ymax, tmpDataY[ i ] );
1625
0
  }
1626
0
  double xlow = xmax;
1627
0
  double ylow = ymax;
1628
1629
0
  const int dataPairs = (int) tmpDataX.size();
1630
0
  std::vector<double> dataXd( dataPairs + 1 ), dataYd( dataPairs + 1 );
1631
0
  for( int i = 0; i < dataPairs; i++ )
1632
0
  {
1633
0
    dataXd[ i + 1 ] = (double) tmpDataX[ i ];
1634
0
    dataYd[ i + 1 ] = (double) tmpDataY[ i ];
1635
0
  }
1636
0
  if( secondPass )
1637
0
  {
1638
0
    std::vector<int>().swap( dataX );
1639
0
    std::vector<int>().swap( dataY );
1640
0
    std::vector<double>().swap( coeffs );
1641
0
    std::vector<double>().swap( scalingVec );
1642
0
  }
1643
1644
0
  for( int i = 1; i <= dataPairs; i++ )
1645
0
  {
1646
0
    if( dataXd[ i ] < xlow && dataXd[ i ] != 0 )
1647
0
    {
1648
0
      xlow = dataXd[ i ];
1649
0
    }
1650
0
    if( dataYd[ i ] < ylow && dataYd[ i ] != 0 )
1651
0
    {
1652
0
      ylow = dataYd[ i ];
1653
0
    }
1654
0
  }
1655
0
  const double xscale = ( xlow < .001 && xmax < 1000 ) ? 1 / xlow : ( xmax > 1000 && xlow > .001 ) ? 1 / xmax : 1;
1656
0
  const double yscale = ( ylow < .001 && ymax < 1000 ) ? 1 / ylow : ( ymax > 1000 && ylow > .001 ) ? 1 / ymax : 1;
1657
1658
  // least squares normal equations, solved by Gaussian elimination with partial pivoting (indices from 1)
1659
0
  double a[ MAXORDER + 1 ][ MAXORDER + 1 ] = { { 0 } };
1660
0
  double B[ MAXORDER + 1 ] = { 0 }, S[ MAXORDER + 1 ] = { 0 };
1661
0
  double polycoefs[ MAXORDER + 1 ] = { 0 };
1662
0
  double Y1 = 0;
1663
0
  const double maxVal = std::numeric_limits<double>::max();
1664
0
  for( int j = 1; j <= dataPairs; j++ )
1665
0
  {
1666
0
    for( int i = 1; i <= order; i++ )
1667
0
    {
1668
0
      B[ i ] = B[ i ] + dataYd[ j ] * yscale * ldpow( dataXd[ j ] * xscale, i );
1669
0
      if( B[ i ] == maxVal )
1670
0
      {
1671
0
        return false;
1672
0
      }
1673
0
      for( int k = 1; k <= order; k++ )
1674
0
      {
1675
0
        a[ i ][ k ] = a[ i ][ k ] + ldpow( dataXd[ j ] * xscale, i + k );
1676
0
        if( a[ i ][ k ] == maxVal )
1677
0
        {
1678
0
          return false;
1679
0
        }
1680
0
      }
1681
0
      S[ i ] = S[ i ] + ldpow( dataXd[ j ] * xscale, i );
1682
0
      if( S[ i ] == maxVal )
1683
0
      {
1684
0
        return false;
1685
0
      }
1686
0
    }
1687
0
    Y1 = Y1 + dataYd[ j ] * yscale;
1688
0
    if( Y1 == maxVal )
1689
0
    {
1690
0
      return false;
1691
0
    }
1692
0
  }
1693
1694
0
  for( int i = 1; i <= order; i++ )
1695
0
  {
1696
0
    for( int j = 1; j <= order; j++ )
1697
0
    {
1698
0
      a[ i ][ j ] = a[ i ][ j ] - S[ i ] * S[ j ] / (double) dataPairs;
1699
0
      if( a[ i ][ j ] == maxVal )
1700
0
      {
1701
0
        return false;
1702
0
      }
1703
0
    }
1704
0
    B[ i ] = B[ i ] - Y1 * S[ i ] / (double) dataPairs;
1705
0
    if( B[ i ] == maxVal )
1706
0
    {
1707
0
      return false;
1708
0
    }
1709
0
  }
1710
1711
0
  for( int k = 1; k <= order; k++ )
1712
0
  {
1713
0
    int    R  = k;
1714
0
    double A1 = 0;
1715
0
    for( int L = k; L <= order; L++ )
1716
0
    {
1717
0
      const double A2 = std::fabs( a[ L ][ k ] );
1718
0
      if( A2 > A1 )
1719
0
      {
1720
0
        A1 = A2;
1721
0
        R  = L;
1722
0
      }
1723
0
    }
1724
0
    if( A1 == 0 )
1725
0
    {
1726
0
      return false;
1727
0
    }
1728
0
    if( R != k )
1729
0
    {
1730
0
      for( int j = k; j <= order; j++ )
1731
0
      {
1732
0
        std::swap( a[ R ][ j ], a[ k ][ j ] );
1733
0
      }
1734
0
      std::swap( B[ R ], B[ k ] );
1735
0
    }
1736
0
    for( int i = k; i <= order; i++ )
1737
0
    {
1738
0
      const double m = a[ i ][ k ];
1739
0
      for( int j = k; j <= order; j++ )
1740
0
      {
1741
0
        a[ i ][ j ] = i == k ? a[ i ][ j ] / m : a[ i ][ j ] - m * a[ k ][ j ];
1742
0
      }
1743
0
      B[ i ] = i == k ? B[ i ] / m : B[ i ] - m * B[ k ];
1744
0
    }
1745
0
  }
1746
1747
0
  polycoefs[ order ] = B[ order ];
1748
0
  for( int k = 1; k <= order - 1; k++ )
1749
0
  {
1750
0
    const int i = order - k;
1751
0
    double S1 = 0;
1752
0
    for( int j = 1; j <= order; j++ )
1753
0
    {
1754
0
      S1 = S1 + a[ i ][ j ] * polycoefs[ j ];
1755
0
      if( S1 == maxVal )
1756
0
      {
1757
0
        return false;
1758
0
      }
1759
0
    }
1760
0
    polycoefs[ i ] = B[ i ] - S1;
1761
0
  }
1762
1763
0
  double S1 = 0;
1764
0
  for( int i = 1; i <= order; i++ )
1765
0
  {
1766
0
    S1 = S1 + polycoefs[ i ] * S[ i ] / (double) dataPairs;
1767
0
    if( S1 == maxVal )
1768
0
    {
1769
0
      return false;
1770
0
    }
1771
0
  }
1772
0
  polycoefs[ 0 ] = Y1 / (double) dataPairs - S1;
1773
1774
  // zero all coefficients smaller than +-1e-11 (avoids -0)
1775
0
  for( int i = 0; i <= order; i++ )
1776
0
  {
1777
0
    if( std::fabs( polycoefs[ i ] * 100000000000 ) < 1 )
1778
0
    {
1779
0
      polycoefs[ i ] = 0;
1780
0
    }
1781
0
  }
1782
1783
0
  for( int i = 0; i <= order; i++ )
1784
0
  {
1785
0
    polycoefs[ i ] = ( 1 / yscale ) * polycoefs[ i ] * ldpow( xscale, i );
1786
0
    coeffs.push_back( polycoefs[ i ] );
1787
0
  }
1788
1789
  // sample the scaling function from xmin to xmax
1790
0
  for( int i = (int) xmin; i <= (int) xmax; i++ )
1791
0
  {
1792
0
    double val = coeffs[ 0 ];
1793
0
    for( size_t j = 1; j < coeffs.size(); j++ )
1794
0
    {
1795
0
      val += coeffs[ j ] * ldpow( i, (unsigned) j );
1796
0
    }
1797
0
    scalingVec.push_back( Clip3( 0.0, (double) ( 1 << bitDepth ) - 1, val ) );
1798
0
  }
1799
0
  scalingVec.push_back( xmax );
1800
0
  scalingVec.push_back( xmin );
1801
0
  return true;
1802
0
}
1803
1804
// Adds up to four points beyond the lowest and the highest intensity, each a step further and weaker, then drops the
1805
// points outside the usable intensity range.
1806
void FGAnalyzer::extendPoints( std::vector<int>& dataX, std::vector<int>& dataY, int bitDepth ) const
1807
0
{
1808
0
  const int minInt = bitDepth < 10 ? MIN_INTENSITY >> ( 10 - bitDepth ) : MIN_INTENSITY << ( bitDepth - 10 );
1809
0
  const int maxInt = bitDepth < 10 ? MAX_INTENSITY >> ( 10 - bitDepth ) : MAX_INTENSITY << ( bitDepth - 10 );
1810
1811
0
  int xmin = dataX[ 0 ], xmax = dataX[ 0 ];
1812
0
  int ymin = dataY[ 0 ], ymax = dataY[ 0 ];   // strengths at xmin and xmax
1813
0
  for( size_t i = 0; i < dataX.size(); i++ )
1814
0
  {
1815
0
    if( dataX[ i ] < xmin )
1816
0
    {
1817
0
      xmin = dataX[ i ];
1818
0
      ymin = dataY[ i ];
1819
0
    }
1820
0
    if( dataX[ i ] > xmax )
1821
0
    {
1822
0
      xmax = dataX[ i ];
1823
0
      ymax = dataY[ i ];
1824
0
    }
1825
0
  }
1826
1827
0
  int numLeft = MAX_POINTS_TO_EXTEND, numRight = MAX_POINTS_TO_EXTEND;
1828
0
  while( xmin >= POINT_STEP && ymin > m_strengthScale && numLeft > 0 )
1829
0
  {
1830
0
    xmin -= POINT_STEP;
1831
0
    ymin = m_improved ? ymin : (int) ( ymin / POINT_SCALE );
1832
0
    dataX.push_back( xmin );
1833
0
    dataY.push_back( ymin );
1834
0
    numLeft--;
1835
0
  }
1836
0
  while( xmax + POINT_STEP <= ( 1 << bitDepth ) - 1 && ymax > m_strengthScale && numRight > 0 )
1837
0
  {
1838
0
    xmax += POINT_STEP;
1839
0
    ymax = m_improved ? ymax : (int) ( ymax / POINT_SCALE );
1840
0
    dataX.push_back( xmax );
1841
0
    dataY.push_back( ymax );
1842
0
    numRight--;
1843
0
  }
1844
0
  for( int i = 0; i < (int) dataX.size(); i++ )
1845
0
  {
1846
0
    if( dataX[ i ] < minInt || dataX[ i ] > maxInt )
1847
0
    {
1848
0
      dataX.erase( dataX.begin() + i );
1849
0
      dataY.erase( dataY.begin() + i );
1850
0
      i--;
1851
0
    }
1852
0
  }
1853
0
}
1854
1855
// Lloyd-Max quantization of the sampled scaling function into numQuantizedLevels levels; quantizedVec receives the
1856
// quantized function for every intensity.
1857
bool FGAnalyzer::lloydMax( std::vector<double>& scalingVec, std::vector<int>& quantizedVec, int numQuantizedLevels, int bitDepth ) const
1858
0
{
1859
0
  if( scalingVec.size() <= 2 )
1860
0
  {
1861
0
    return false;   // no sampled function (only xmax and xmin)
1862
0
  }
1863
1864
0
  const int xmin = (int) scalingVec.back();
1865
0
  scalingVec.pop_back();
1866
0
  scalingVec.pop_back();   // xmax
1867
1868
0
  const double tolerance = 0.0000001;
1869
0
  double ymin = scalingVec[ 0 ], ymax = scalingVec[ 0 ];
1870
0
  for( double v : scalingVec )
1871
0
  {
1872
0
    ymin = std::min( ymin, v );
1873
0
    ymax = std::max( ymax, v );
1874
0
  }
1875
0
  const double initTraining = ( ymax - ymin ) / numQuantizedLevels;
1876
0
  if( initTraining <= 0 )
1877
0
  {
1878
    // A constant scaling function needs no quantization. VTM returns failure here, so grain of the same strength at
1879
    // all measured intensities was never signalled.
1880
0
    quantizedVec.assign( (size_t) 1 << bitDepth, 0 );
1881
0
    for( size_t i = 0; i < scalingVec.size(); i++ )
1882
0
    {
1883
0
      quantizedVec[ i + xmin ] = Clip3( 0, ( MAX_STANDARD_DEVIATION * m_strengthScale ) << ( bitDepth - BIT_DEPTH_8 ), (int) ( ymin + .5 ) );
1884
0
    }
1885
0
    return true;
1886
0
  }
1887
1888
0
  std::vector<double> codebook( numQuantizedLevels );
1889
0
  std::vector<double> partition( numQuantizedLevels - 1 );
1890
0
  const double step = initTraining / 2;
1891
0
  for( int i = 0; i < numQuantizedLevels; i++ )
1892
0
  {
1893
0
    codebook[ i ] = ymin + i * initTraining + step;
1894
0
  }
1895
0
  for( int i = 0; i < numQuantizedLevels - 1; i++ )
1896
0
  {
1897
0
    partition[ i ] = ( codebook[ i ] + codebook[ i + 1 ] ) / 2;
1898
0
  }
1899
1900
  // Quantizes the scaling function with the current partition and codebook and returns the mean squared error.
1901
0
  std::vector<double> quantized( scalingVec.size(), 0.0 );
1902
0
  auto quantize = [ & ]()
1903
0
  {
1904
0
    for( size_t i = 0; i < scalingVec.size(); i++ )
1905
0
    {
1906
0
      double level = 0.0;
1907
0
      for( size_t j = 0; j < partition.size(); j++ )
1908
0
      {
1909
0
        level = level + ( scalingVec[ i ] > partition[ j ] );
1910
0
      }
1911
0
      quantized[ i ] = codebook[ (int) level ];
1912
0
    }
1913
0
    double distortion = 0.0;
1914
0
    for( size_t i = 0; i < scalingVec.size(); i++ )
1915
0
    {
1916
0
      distortion += ( scalingVec[ i ] - quantized[ i ] ) * ( scalingVec[ i ] - quantized[ i ] );
1917
0
    }
1918
0
    return distortion / scalingVec.size();
1919
0
  };
1920
1921
0
  double distortion = quantize();
1922
0
  double lastDistortion = 0.0;
1923
0
  const double tolerance2 = std::numeric_limits<double>::epsilon() * ymax;
1924
0
  double relDistortion = distortion > tolerance2 ? std::fabs( distortion - lastDistortion ) / distortion : distortion;
1925
1926
0
  while( relDistortion > tolerance && relDistortion > tolerance2 )
1927
0
  {
1928
0
    for( int i = 0; i < numQuantizedLevels; i++ )
1929
0
    {
1930
0
      int    count = 0;
1931
0
      double sum   = 0.0;
1932
0
      for( size_t j = 0; j < quantized.size(); j++ )
1933
0
      {
1934
0
        if( codebook[ i ] == quantized[ j ] )
1935
0
        {
1936
0
          count++;
1937
0
          sum += scalingVec[ j ];
1938
0
        }
1939
0
      }
1940
0
      if( count )
1941
0
      {
1942
0
        codebook[ i ] = sum / (double) count;
1943
0
        continue;
1944
0
      }
1945
      // empty cell: use the mean of the samples in its partition range, or the middle of the range
1946
0
      sum = 0.0;
1947
0
      count = 0;
1948
0
      const bool first = i == 0, last = i == numQuantizedLevels - 1;
1949
0
      for( size_t j = 0; j < quantized.size(); j++ )
1950
0
      {
1951
0
        const bool inCell = first ? scalingVec[ j ] <= partition[ i ]
1952
0
                          : last  ? scalingVec[ j ] >= partition[ i - 1 ]
1953
0
                                  : scalingVec[ j ] >= partition[ i - 1 ] && scalingVec[ j ] <= partition[ i ];
1954
0
        if( inCell )
1955
0
        {
1956
0
          count++;
1957
0
          sum += scalingVec[ j ];
1958
0
        }
1959
0
      }
1960
0
      if( count )
1961
0
      {
1962
0
        codebook[ i ] = sum / (double) count;
1963
0
      }
1964
0
      else
1965
0
      {
1966
0
        codebook[ i ] = first ? ( partition[ i ] + ymin ) / 2 : last ? ( partition[ i - 1 ] + ymax ) / 2 : ( partition[ i - 1 ] + partition[ i ] ) / 2;
1967
0
      }
1968
0
    }
1969
1970
0
    for( int i = 0; i < numQuantizedLevels - 1; i++ )
1971
0
    {
1972
0
      partition[ i ] = ( codebook[ i ] + codebook[ i + 1 ] ) / 2;
1973
0
    }
1974
0
    std::sort( partition.begin(), partition.end() );
1975
1976
0
    lastDistortion = distortion;
1977
0
    distortion = quantize();
1978
0
    relDistortion = distortion > tolerance2 ? std::fabs( distortion - lastDistortion ) / distortion : distortion;
1979
0
  }
1980
1981
0
  quantizedVec.assign( (size_t) 1 << bitDepth, 0 );
1982
0
  for( size_t i = 0; i < quantized.size(); i++ )
1983
0
  {
1984
0
    quantizedVec[ i + xmin ] = Clip3( 0, ( MAX_STANDARD_DEVIATION * m_strengthScale ) << ( bitDepth - BIT_DEPTH_8 ), (int) ( quantized[ i ] + .5 ) );
1985
0
  }
1986
0
  return true;
1987
0
}
1988
1989
// Turns the quantized scaling function into intensity intervals and scaling factors of the SEI message (8-bit
1990
// intensities). The cut-off frequencies are set afterwards.
1991
void FGAnalyzer::setEstimatedParameters( std::vector<int>& quantizedVec, int bitDepth, ComponentID compId )
1992
0
{
1993
  // strengths below half of VTM's unit count as no grain, as they did in VTM
1994
0
  for( int& q : quantizedVec )
1995
0
  {
1996
0
    q = q < m_strengthScale / 2 ? 0 : q;
1997
0
  }
1998
0
  if( m_improved )
1999
0
  {
2000
0
    holdEdgeStrengths( quantizedVec, bitDepth );
2001
0
  }
2002
2003
0
  std::vector<std::vector<int>> parameters( 3 );   // lower bounds, upper bounds, scaling factors
2004
2005
  // intervals of constant value
2006
0
  parameters[ 0 ].push_back( 0 );
2007
0
  parameters[ 2 ].push_back( quantizedVec[ 0 ] );
2008
0
  for( size_t i = 0; i + 1 < quantizedVec.size(); i++ )
2009
0
  {
2010
0
    if( quantizedVec[ i ] != quantizedVec[ i + 1 ] )
2011
0
    {
2012
0
      parameters[ 0 ].push_back( (int) i + 1 );
2013
0
      parameters[ 1 ].push_back( (int) i );
2014
0
      parameters[ 2 ].push_back( quantizedVec[ i + 1 ] );
2015
0
    }
2016
0
  }
2017
0
  parameters[ 1 ].push_back( ( 1 << bitDepth ) - 1 );
2018
2019
0
  mergeIntervals( parameters, bitDepth );
2020
0
  scaleDown( parameters, bitDepth );
2021
2022
  // the scaling to 8 bits can make intervals overlap: make the bounds strictly increasing
2023
0
  std::vector<int> bounds;
2024
0
  for( size_t i = 0; i < parameters[ 2 ].size(); i++ )
2025
0
  {
2026
0
    bounds.push_back( parameters[ 0 ][ i ] );
2027
0
    bounds.push_back( parameters[ 1 ][ i ] );
2028
0
  }
2029
0
  for( size_t i = 0; i + 1 < bounds.size(); i++ )
2030
0
  {
2031
0
    if( bounds[ i ] >= bounds[ i + 1 ] )
2032
0
    {
2033
0
      bounds[ i + 1 ] = bounds[ i ] + 1;
2034
0
    }
2035
0
  }
2036
0
  for( size_t i = 0; i < parameters[ 2 ].size(); i++ )
2037
0
  {
2038
0
    parameters[ 0 ][ i ] = bounds[ 2 * i ];
2039
0
    parameters[ 1 ][ i ] = bounds[ 2 * i + 1 ];
2040
0
  }
2041
2042
  // Intervals with scaling factor 0 are not signalled. The component is signalled again as soon as a later picture
2043
  // gives a scaling factor above 0 (in VTM a component stays off for the rest of the sequence once all its scaling
2044
  // factors were 0, and then the chroma components are no longer analysed either).
2045
0
  SeiFgc::CompModel& cm = m_compModel[ compId ];
2046
0
  cm.numIntensityIntervals = (uint8_t) ( parameters[ 2 ].size() - std::count( parameters[ 2 ].begin(), parameters[ 2 ].end(), 0 ) );
2047
0
  cm.presentFlag = cm.numIntensityIntervals > 0;
2048
0
  if( !cm.presentFlag )
2049
0
  {
2050
0
    m_cutoffPairs[ compId ] = 0;
2051
0
    return;
2052
0
  }
2053
0
  int j = 0;
2054
0
  for( size_t i = 0; i < parameters[ 2 ].size(); i++ )
2055
0
  {
2056
0
    if( parameters[ 2 ][ i ] != 0 )
2057
0
    {
2058
0
      auto& iv = cm.intensityValues[ j++ ];
2059
0
      iv.intensityIntervalLowerBound = (uint8_t) parameters[ 0 ][ i ];
2060
0
      iv.intensityIntervalUpperBound = (uint8_t) parameters[ 1 ][ i ];
2061
0
      iv.compModelValue[ 0 ] = parameters[ 2 ][ i ];
2062
0
      iv.compModelValue[ 1 ] = -1;   // cut-off frequencies are estimated later
2063
0
      iv.compModelValue[ 2 ] = -1;
2064
0
    }
2065
0
  }
2066
0
}
2067
2068
// The scaling function only covers the intensities with flat blocks, plus a few extension points. VTM lets it fall off
2069
// by a factor of 1.25 per extension point and signals no grain beyond, but film grain goes on there: on Beekeeper the
2070
// darkest intensities have the strongest grain of the scene, and they got half of it. The improved analysis keeps the
2071
// level of the extension points (extendPoints()) and here holds the first and last strength out to the intensity
2072
// limits of the analysis (MIN_INTENSITY and MAX_INTENSITY).
2073
void FGAnalyzer::holdEdgeStrengths( std::vector<int>& quantizedVec, int bitDepth ) const
2074
0
{
2075
0
  const int minInt = bitDepth < 10 ? MIN_INTENSITY >> ( 10 - bitDepth ) : MIN_INTENSITY << ( bitDepth - 10 );
2076
0
  const int maxInt = bitDepth < 10 ? MAX_INTENSITY >> ( 10 - bitDepth ) : MAX_INTENSITY << ( bitDepth - 10 );
2077
0
  int first = -1, last = -1;
2078
0
  for( int i = 0; i < (int) quantizedVec.size(); i++ )
2079
0
  {
2080
0
    if( quantizedVec[ i ] > 0 )
2081
0
    {
2082
0
      first = first < 0 ? i : first;
2083
0
      last  = i;
2084
0
    }
2085
0
  }
2086
0
  if( first < 0 )
2087
0
  {
2088
0
    return;   // no grain
2089
0
  }
2090
0
  for( int i = minInt; i < first; i++ )
2091
0
  {
2092
0
    quantizedVec[ i ] = quantizedVec[ first ];
2093
0
  }
2094
0
  for( int i = last + 1; i <= maxInt && i < (int) quantizedVec.size(); i++ )
2095
0
  {
2096
0
    quantizedVec[ i ] = quantizedVec[ last ];
2097
0
  }
2098
0
}
2099
2100
// Merges intervals narrower than 2 (at 8 bits) into the neighbour with the closer scaling factor.
2101
void FGAnalyzer::mergeIntervals( std::vector<std::vector<int>>& parameters, int bitDepth ) const
2102
0
{
2103
0
  for( int i = 0; i < (int) parameters[ 2 ].size(); i++ )
2104
0
  {
2105
0
    const int width = parameters[ 1 ][ i ] - parameters[ 0 ][ i ];
2106
0
    if( width >= ( 2 << ( bitDepth - BIT_DEPTH_8 ) ) )
2107
0
    {
2108
0
      continue;
2109
0
    }
2110
0
    const int last = (int) parameters[ 2 ].size() - 1;
2111
0
    const int diffRight = ( i == last || parameters[ 2 ][ i + 1 ] == 0 ) ? std::numeric_limits<int>::max() : abs( parameters[ 2 ][ i ] - parameters[ 2 ][ i + 1 ] );
2112
0
    const int diffLeft  = ( i == 0    || parameters[ 2 ][ i - 1 ] == 0 ) ? std::numeric_limits<int>::max() : abs( parameters[ 2 ][ i ] - parameters[ 2 ][ i - 1 ] );
2113
    // VTM merges to the right when both neighbours are unusable; at the last interval there is no right neighbour.
2114
0
    const bool mergeLeft = diffLeft < diffRight || ( i == last && i > 0 );
2115
0
    if( !mergeLeft && i == last )
2116
0
    {
2117
0
      continue;   // a single interval cannot be merged
2118
0
    }
2119
0
    const int n = mergeLeft ? i - 1 : i + 1;
2120
0
    const int otherWidth = parameters[ 1 ][ n ] - parameters[ 0 ][ n ];
2121
0
    const int newScale = ( otherWidth * parameters[ 2 ][ n ] + width * parameters[ 2 ][ i ] ) / ( otherWidth + width );
2122
0
    if( mergeLeft )
2123
0
    {
2124
0
      parameters[ 1 ][ i - 1 ] = parameters[ 1 ][ i ];
2125
0
      parameters[ 2 ][ i - 1 ] = newScale;
2126
0
      for( auto& p : parameters )
2127
0
      {
2128
0
        p.erase( p.begin() + i );
2129
0
      }
2130
0
    }
2131
0
    else
2132
0
    {
2133
0
      parameters[ 1 ][ i ] = parameters[ 1 ][ i + 1 ];
2134
0
      parameters[ 2 ][ i ] = newScale;
2135
0
      for( auto& p : parameters )
2136
0
      {
2137
0
        p.erase( p.begin() + i + 1 );
2138
0
      }
2139
0
    }
2140
0
    i--;
2141
0
  }
2142
0
}
2143
2144
// Scales intensities to 8 bits and the scaling factors to the SEI representation.
2145
void FGAnalyzer::scaleDown( std::vector<std::vector<int>>& parameters, int bitDepth ) const
2146
0
{
2147
0
  for( size_t i = 0; i < parameters[ 2 ].size(); i++ )
2148
0
  {
2149
    // the intensities to 8 bits; the strengths stay as they are, getSignalledParameters() converts them
2150
0
    parameters[ 0 ][ i ] >>= ( bitDepth - BIT_DEPTH_8 );
2151
0
    parameters[ 1 ][ i ] >>= ( bitDepth - BIT_DEPTH_8 );
2152
0
  }
2153
0
}
2154
2155
// ====================================================================================================================
2156
// Cut-off frequencies
2157
// ====================================================================================================================
2158
2159
// The cut-off pair whose synthesized grain has the spectrum closest to the measured one, for the improved analysis:
2160
// the horizontal and vertical marginals of the mean squared DCT (without the DC coefficient), normalized, against those
2161
// of the synthesis (GRAIN_SPECTRUM_MARGINALS), by the sum of squared differences. AN0237's estimator instead takes,
2162
// per direction, the frequency where the marginal falls closest to a fixed fraction of its low-frequency level, which
2163
// reads about one step too high on grain of the model itself (9/9 for grain synthesized with 8/8, 13/6 for 12/5), and
2164
// the horizontal and vertical cut-offs together decide the shape of both marginals, which a per-direction rule cannot
2165
// take into account. For 4:2:0 chroma the measured spectrum is that of the chroma plane, where the
2166
// synthesis doubles the signalled cut-offs; the result is returned at chroma plane resolution like AN0237's, twice the
2167
// signalled pair, which getSignalledParameters() halves.
2168
void FGAnalyzer::fitCutoffPair( const std::vector<double>& meanSq, bool chroma420, int& horizontal, int& vertical ) const
2169
0
{
2170
0
  constexpr int N = BLOCK_SIZE;
2171
0
  double measured[ 2 ][ N ] = { { 0 } };   // horizontal and vertical marginal
2172
0
  double total = 0.0;
2173
0
  for( int x = 0; x < N; x++ )
2174
0
  {
2175
0
    for( int y = 0; y < N; y++ )
2176
0
    {
2177
0
      const double p = x || y ? meanSq[ x * N + y ] : 0.0;
2178
0
      measured[ 0 ][ x ] += p;
2179
0
      measured[ 1 ][ y ] += p;
2180
0
      total              += p;
2181
0
    }
2182
0
  }
2183
0
  horizontal = vertical = -1;
2184
0
  if( total <= 0.0 )
2185
0
  {
2186
0
    return;
2187
0
  }
2188
0
  for( int k = 0; k < N; k++ )
2189
0
  {
2190
0
    measured[ 0 ][ k ] /= total;
2191
0
    measured[ 1 ][ k ] /= total;
2192
0
  }
2193
2194
0
  const int table = chroma420 ? 1 : 0;
2195
0
  double bestError = std::numeric_limits<double>::max();
2196
0
  for( int h = 0; h < NUM_CUTOFFS; h++ )
2197
0
  {
2198
0
    for( int v = 0; v < NUM_CUTOFFS; v++ )
2199
0
    {
2200
0
      double error = 0.0;
2201
0
      for( int d = 0; d < 2; d++ )
2202
0
      {
2203
0
        for( int k = 0; k < N; k++ )
2204
0
        {
2205
0
          const double diff = measured[ d ][ k ] - GRAIN_SPECTRUM_MARGINALS[ table ][ h ][ v ][ d ][ k ];
2206
0
          error += diff * diff;
2207
0
        }
2208
0
      }
2209
0
      if( error < bestError )
2210
0
      {
2211
0
        bestError  = error;
2212
0
        horizontal = h + 2;
2213
0
        vertical   = v + 2;
2214
0
      }
2215
0
    }
2216
0
  }
2217
0
  if( chroma420 )
2218
0
  {
2219
0
    horizontal *= 2;
2220
0
    vertical   *= 2;
2221
0
  }
2222
0
}
2223
2224
// Fits the AR filter of a component to the spectrum of its grain, accumulated over all intensities: the Nelder-Mead
2225
// minimum of the L1 distance between the normalized spectra, starting from the previous filter, or from a mild
2226
// low-pass one for the first fit. Filters that are not stable (sum of the absolute tap weights 0.98 or more) are
2227
// excluded. For chroma the model spectrum is the one vvdec synthesizes, the analytic spectrum times
2228
// AR_SYNTHESIS_RATIO_CHROMA, renormalized: the fitted filters then give 0.02-0.06 lower chroma spectrum errors on
2229
// Beekeeper. Luma is fitted to the analytic spectrum: the same correction for luma brought no gain (there the fixed
2230
// pattern of the synthesis is the limit) and made one 4K clip worse.
2231
void FGAnalyzer::fitArFilter( ComponentID compId )
2232
0
{
2233
0
  constexpr int N = BLOCK_SIZE;
2234
0
  const SeiFgc::CompModel& cm = m_compModel[ compId ];
2235
0
  if( !cm.presentFlag )
2236
0
  {
2237
0
    return;
2238
0
  }
2239
0
  double measured[ N * N ] = { 0 };   // [ vertical * N + horizontal ]
2240
0
  double total = 0.0;
2241
0
  for( const SquaredDctSum& s : m_squaredDctSums[ compId ] )
2242
0
  {
2243
0
    for( int x = 0; x < N; x++ )
2244
0
    {
2245
0
      for( int y = 0; y < N; y++ )
2246
0
      {
2247
0
        measured[ y * N + x ] += x || y ? s[ x * N + y ] : 0.0;
2248
0
      }
2249
0
    }
2250
0
  }
2251
0
  for( double v : measured )
2252
0
  {
2253
0
    total += v;
2254
0
  }
2255
0
  if( total <= 0.0 )
2256
0
  {
2257
0
    return;
2258
0
  }
2259
0
  for( double& v : measured )
2260
0
  {
2261
0
    v /= total;
2262
0
  }
2263
2264
0
  static const ArSpectrum arSpectrum;
2265
0
  const float* ratio = compId == COMP_Y ? nullptr : &AR_SYNTHESIS_RATIO_CHROMA[ 0 ][ 0 ];   // [ vertical * N + horizontal ]
2266
0
  double model[ N * N ];
2267
0
  std::vector<double> scratch( ArSpectrum::SCRATCH_SIZE );
2268
0
  auto error = [ & ]( const double p[ 4 ] )
2269
0
  {
2270
0
    const double a1 = p[ 0 ], a3 = p[ 1 ], a5 = p[ 2 ], r = p[ 3 ];
2271
0
    if( r < 0.0 || std::fabs( a1 ) * ( 1 + r ) + 2 * std::fabs( a3 * r ) + std::fabs( a5 ) * ( 1 + r * r ) >= 0.98 )
2272
0
    {
2273
0
      return std::numeric_limits<double>::max();
2274
0
    }
2275
0
    arSpectrum.compute( a1, a3, a5, r, model, scratch );
2276
0
    double sum = 1.0;   // the analytic spectrum is normalized; the one weighted by the ratio is renormalized
2277
0
    if( ratio )
2278
0
    {
2279
0
      sum = 0.0;
2280
0
      for( int i = 0; i < N * N; i++ )
2281
0
      {
2282
0
        model[ i ] *= ratio[ i ];
2283
0
        sum        += model[ i ];
2284
0
      }
2285
0
    }
2286
0
    double e = 0.0;
2287
0
    for( int i = 0; i < N * N; i++ )
2288
0
    {
2289
0
      e += std::fabs( model[ i ] / sum - measured[ i ] );
2290
0
    }
2291
0
    return e;
2292
0
  };
2293
2294
0
  ArFilter& f = m_arFilter[ compId ];
2295
0
  const double starts[ 2 ][ 4 ] = { { f.a1, f.a3, f.a5, f.r }, { 0.3, 0.0, 0.0, 1.0 } };
2296
0
  double best[ 4 ] = { 0 }, bestError = std::numeric_limits<double>::max();
2297
0
  for( int s = f.valid ? 0 : 1; s < ( f.valid ? 1 : 2 ); s++ )
2298
0
  {
2299
    // Nelder-Mead on the four parameters
2300
0
    double pts[ 5 ][ 4 ], vals[ 5 ];
2301
0
    for( int i = 0; i < 5; i++ )
2302
0
    {
2303
0
      for( int j = 0; j < 4; j++ )
2304
0
      {
2305
0
        pts[ i ][ j ] = starts[ s ][ j ] + ( i == j + 1 ? 0.15 : 0.0 );
2306
0
      }
2307
0
      vals[ i ] = error( pts[ i ] );
2308
0
    }
2309
0
    for( int it = 0; it < AR_FIT_ITERATIONS; it++ )
2310
0
    {
2311
0
      int order[ 5 ] = { 0, 1, 2, 3, 4 };
2312
0
      std::sort( order, order + 5, [ & ]( int a, int b ) { return vals[ a ] < vals[ b ]; } );
2313
0
      const int worst = order[ 4 ], second = order[ 3 ], bestIdx = order[ 0 ];
2314
0
      if( vals[ worst ] - vals[ bestIdx ] < AR_FIT_TOLERANCE )
2315
0
      {
2316
0
        break;   // converged
2317
0
      }
2318
0
      double c[ 4 ] = { 0 }, xr[ 4 ], xe[ 4 ], xc[ 4 ];
2319
0
      for( int i = 0; i < 5; i++ )
2320
0
      {
2321
0
        for( int j = 0; j < 4 && i != worst; j++ )
2322
0
        {
2323
0
          c[ j ] += pts[ i ][ j ] / 4;
2324
0
        }
2325
0
      }
2326
0
      for( int j = 0; j < 4; j++ )
2327
0
      {
2328
0
        xr[ j ] = c[ j ] + ( c[ j ] - pts[ worst ][ j ] );
2329
0
      }
2330
0
      const double fr = error( xr );
2331
0
      if( fr < vals[ bestIdx ] )
2332
0
      {
2333
0
        for( int j = 0; j < 4; j++ )
2334
0
        {
2335
0
          xe[ j ] = c[ j ] + 2 * ( c[ j ] - pts[ worst ][ j ] );
2336
0
        }
2337
0
        const double fe = error( xe );
2338
0
        std::copy( fe < fr ? xe : xr, ( fe < fr ? xe : xr ) + 4, pts[ worst ] );
2339
0
        vals[ worst ] = std::min( fe, fr );
2340
0
      }
2341
0
      else if( fr < vals[ second ] )
2342
0
      {
2343
0
        std::copy( xr, xr + 4, pts[ worst ] );
2344
0
        vals[ worst ] = fr;
2345
0
      }
2346
0
      else
2347
0
      {
2348
0
        for( int j = 0; j < 4; j++ )
2349
0
        {
2350
0
          xc[ j ] = c[ j ] + 0.5 * ( pts[ worst ][ j ] - c[ j ] );
2351
0
        }
2352
0
        const double fc = error( xc );
2353
0
        if( fc < vals[ worst ] )
2354
0
        {
2355
0
          std::copy( xc, xc + 4, pts[ worst ] );
2356
0
          vals[ worst ] = fc;
2357
0
        }
2358
0
        else
2359
0
        {
2360
0
          for( int i = 0; i < 5; i++ )
2361
0
          {
2362
0
            if( i == bestIdx )
2363
0
            {
2364
0
              continue;
2365
0
            }
2366
0
            for( int j = 0; j < 4; j++ )
2367
0
            {
2368
0
              pts[ i ][ j ] = pts[ bestIdx ][ j ] + 0.5 * ( pts[ i ][ j ] - pts[ bestIdx ][ j ] );
2369
0
            }
2370
0
            vals[ i ] = error( pts[ i ] );
2371
0
          }
2372
0
        }
2373
0
      }
2374
0
    }
2375
0
    for( int i = 0; i < 5; i++ )
2376
0
    {
2377
0
      if( vals[ i ] < bestError )
2378
0
      {
2379
0
        bestError = vals[ i ];
2380
0
        std::copy( pts[ i ], pts[ i ] + 4, best );
2381
0
      }
2382
0
    }
2383
0
  }
2384
0
  if( bestError == std::numeric_limits<double>::max() )
2385
0
  {
2386
0
    return;
2387
0
  }
2388
0
  f.a1 = best[ 0 ];
2389
0
  f.a3 = best[ 1 ];
2390
0
  f.a5 = best[ 2 ];
2391
0
  f.r  = best[ 3 ];
2392
0
  f.nonDcShare = arSpectrum.compute( f.a1, f.a3, f.a5, f.r, model, scratch );
2393
0
  f.valid = true;
2394
0
}
2395
2396
// Estimates horizontal and vertical cut-off frequencies per intensity interval from the accumulated squared DCT of the
2397
// grain, then fills missing values, limits the spread and the number of different pairs.
2398
void FGAnalyzer::estimateCutoffFreq( ComponentID compId, int bitDepth )
2399
0
{
2400
0
  SeiFgc::CompModel& cm = m_compModel[ compId ];
2401
0
  if( !cm.presentFlag )
2402
0
  {
2403
0
    m_cutoffPairs[ compId ] = 0;
2404
0
    return;
2405
0
  }
2406
0
  constexpr int N = BLOCK_SIZE;
2407
0
  const int intervals = cm.numIntensityIntervals;
2408
2409
  // intensity interval of every 8-bit intensity
2410
0
  int intensityInterval[ MAX_NUM_INTENSITIES ];
2411
0
  std::fill_n( intensityInterval, MAX_NUM_INTENSITIES, -1 );
2412
0
  for( int i = 0; i < intervals; i++ )
2413
0
  {
2414
0
    for( int v = cm.intensityValues[ i ].intensityIntervalLowerBound; v <= cm.intensityValues[ i ].intensityIntervalUpperBound; v++ )
2415
0
    {
2416
0
      intensityInterval[ v ] = i;
2417
0
    }
2418
0
  }
2419
2420
0
  const std::vector<SquaredDctSum>& blocks = m_squaredDctSums[ compId ];
2421
0
  const std::vector<double>&        numEl  = m_numDctBlocks[ compId ];
2422
0
  std::vector<double> blocksPerInterval( intervals, 0.0 );
2423
0
  for( size_t i = 0; i < blocks.size(); i++ )
2424
0
  {
2425
0
    const int idx = intensityInterval[ i >> ( bitDepth - BIT_DEPTH_8 ) ];
2426
0
    if( idx != -1 )
2427
0
    {
2428
0
      blocksPerInterval[ idx ] += numEl[ i ];
2429
0
    }
2430
0
  }
2431
2432
  // mean squared DCT per interval, and its row and column averages without the DC coefficient
2433
0
  std::vector<std::vector<double>> meanSq( intervals, std::vector<double>( N * N, 0.0 ) );
2434
0
  std::vector<std::vector<double>> rowMean( intervals, std::vector<double>( N, 0.0 ) );   // per horizontal frequency
2435
0
  std::vector<std::vector<double>> colMean( intervals, std::vector<double>( N, 0.0 ) );   // per vertical frequency
2436
0
  for( int x = 0; x < N; x++ )
2437
0
  {
2438
0
    for( int y = 0; y < N; y++ )
2439
0
    {
2440
0
      for( size_t i = 0; i < blocks.size(); i++ )
2441
0
      {
2442
0
        const int idx = intensityInterval[ i >> ( bitDepth - BIT_DEPTH_8 ) ];
2443
0
        if( idx != -1 )
2444
0
        {
2445
0
          meanSq[ idx ][ x * N + y ] += blocks[ i ][ x * N + y ];
2446
0
        }
2447
0
      }
2448
0
      for( int i = 0; i < intervals; i++ )
2449
0
      {
2450
0
        if( blocksPerInterval[ i ] >= MIN_BLOCK_WEIGHT )
2451
0
        {
2452
0
          meanSq[ i ][ x * N + y ] /= blocksPerInterval[ i ];
2453
0
          rowMean[ i ][ x ] += ( x != 0 || y != 0 ) ? meanSq[ i ][ x * N + y ] : 0.0;
2454
0
          colMean[ i ][ y ] += ( x != 0 || y != 0 ) ? meanSq[ i ][ x * N + y ] : 0.0;
2455
0
        }
2456
0
      }
2457
0
    }
2458
0
  }
2459
2460
0
  int avgCutoffHorizontal = 0, avgCutoffVertical = 0;
2461
0
  int numCutoffHorizontal = 0, numCutoffVertical = 0;
2462
0
  cm.numModelValues = 3;   // always signal all three values
2463
0
  for( int i = 0; i < intervals; i++ )
2464
0
  {
2465
0
    auto& iv = cm.intensityValues[ i ];
2466
0
    if( blocksPerInterval[ i ] < MIN_BLOCK_WEIGHT )
2467
0
    {
2468
0
      iv.compModelValue[ 1 ] = -1;
2469
0
      iv.compModelValue[ 2 ] = -1;
2470
0
      continue;
2471
0
    }
2472
0
    for( int x = 0; x < N; x++ )
2473
0
    {
2474
0
      rowMean[ i ][ x ] /= x == 0 ? N - 1 : N;
2475
0
      colMean[ i ][ x ] /= x == 0 ? N - 1 : N;
2476
0
    }
2477
    // rowMean is indexed by the horizontal frequency, colMean by the vertical one. VTM takes the vertical cut-off from
2478
    // rowMean and the horizontal one from colMean, so its grain comes out rotated by 90 degrees (comp_model_value[1]
2479
    // is the horizontal cut-off, for VTM's own synthesis as well).
2480
0
    int cutoffHorizontal = -1, cutoffVertical = -1;
2481
0
    if( m_improved )
2482
0
    {
2483
0
      fitCutoffPair( meanSq[ i ], compId != COMP_Y && m_chromaFormat == CHROMA_420, cutoffHorizontal, cutoffVertical );
2484
0
    }
2485
0
    else
2486
0
    {
2487
0
      cutoffHorizontal = cutoffFrequency( rowMean[ i ] );
2488
0
      cutoffVertical   = cutoffFrequency( colMean[ i ] );
2489
0
    }
2490
0
    const int intervalWidth    = iv.intensityIntervalUpperBound - iv.intensityIntervalLowerBound;
2491
0
    if( cutoffHorizontal != -1 )
2492
0
    {
2493
0
      iv.compModelValue[ 1 ] = cutoffHorizontal;
2494
0
      avgCutoffHorizontal   += intervalWidth * cutoffHorizontal;
2495
0
      numCutoffHorizontal   += intervalWidth;
2496
0
    }
2497
0
    if( cutoffVertical != -1 )
2498
0
    {
2499
0
      iv.compModelValue[ 2 ] = cutoffVertical;
2500
0
      avgCutoffVertical     += intervalWidth * cutoffVertical;
2501
0
      numCutoffVertical     += intervalWidth;
2502
0
    }
2503
0
  }
2504
2505
0
  avgCutoffHorizontal = numCutoffHorizontal ? avgCutoffHorizontal / numCutoffHorizontal : -1;
2506
0
  if( numCutoffVertical )
2507
0
  {
2508
0
    avgCutoffVertical = avgCutoffVertical / numCutoffVertical;
2509
0
  }
2510
0
  else
2511
0
  {
2512
0
    avgCutoffVertical = -1;   // VTM master sets avgCutoffHorizontal here, losing the valid horizontal average
2513
0
  }
2514
2515
0
  replaceCutoff( compId, avgCutoffHorizontal, avgCutoffVertical );
2516
0
  limitCutoff( compId, avgCutoffHorizontal, avgCutoffVertical );
2517
0
  limitCutoffConsecutive( compId );
2518
0
  if( !cm.presentFlag )
2519
0
  {
2520
0
    m_cutoffPairs[ compId ] = 0;
2521
0
    return;
2522
0
  }
2523
2524
  // limit the number of different cut-off pairs to what the synthesis supports, shared by the components
2525
0
  std::vector<CutoffPair> pairs( intervals );
2526
0
  for( int i = 0; i < intervals; i++ )
2527
0
  {
2528
0
    pairs[ i ].p = { cm.intensityValues[ i ].compModelValue[ 1 ], cm.intensityValues[ i ].compModelValue[ 2 ] };
2529
0
    pairs[ i ].w = cm.intensityValues[ i ].intensityIntervalUpperBound - cm.intensityValues[ i ].intensityIntervalLowerBound;
2530
0
  }
2531
0
  const int numLumaPairs = MAX_CUTOFF_PAIRS - 2 + !m_doAnalysis[ COMP_Cb ] + !m_doAnalysis[ COMP_Cr ];
2532
0
  int maxUnique = numLumaPairs;
2533
0
  if( compId == COMP_Cb )
2534
0
  {
2535
0
    maxUnique = 1 + !m_doAnalysis[ COMP_Cr ] + ( numLumaPairs - m_cutoffPairs[ COMP_Y ] ) / ( m_doAnalysis[ COMP_Cb ] + m_doAnalysis[ COMP_Cr ] );
2536
0
  }
2537
0
  else if( compId == COMP_Cr )
2538
0
  {
2539
0
    maxUnique = MAX_CUTOFF_PAIRS - m_cutoffPairs[ COMP_Y ] - m_cutoffPairs[ COMP_Cb ];
2540
0
  }
2541
0
  m_cutoffPairs[ compId ] = limitCutoffPairs( pairs, maxUnique );
2542
0
  for( int i = 0; i < intervals; i++ )
2543
0
  {
2544
0
    cm.intensityValues[ i ].compModelValue[ 1 ] = pairs[ i ].p.first;
2545
0
    cm.intensityValues[ i ].compModelValue[ 2 ] = pairs[ i ].p.second;
2546
0
  }
2547
0
}
2548
2549
// Cut-off frequency of a 16-sample row or column average: the smoothed average is compared with a threshold derived
2550
// from its low frequencies; the frequency closest to the threshold is the cut-off. Returns -1 when no cut-off is found.
2551
int FGAnalyzer::cutoffFrequency( const std::vector<double>& mean ) const
2552
0
{
2553
0
  constexpr int NUM = 13;   // cut-off frequencies 2 to 14
2554
0
  constexpr int a = 6;
2555
0
  constexpr int w[ NUM ] = { 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
2556
2557
0
  double sum[ NUM ];
2558
0
  for( int j = 2; j < 15; j++ )
2559
0
  {
2560
0
    sum[ j - 2 ] = ( TAP_FILTER[ 0 ] * mean[ j - 1 ] + TAP_FILTER[ 1 ] * mean[ j ] + TAP_FILTER[ 2 ] * mean[ j + 1 ] ) / NORM_TAP;
2561
0
  }
2562
0
  double target = 0;
2563
0
  for( int j = 0; j < NUM; j++ )
2564
0
  {
2565
0
    target += w[ j ] * sum[ j ];
2566
0
  }
2567
0
  target /= a;
2568
2569
0
  double bestDiff = std::numeric_limits<double>::max();
2570
0
  int    bestIdx  = -1;
2571
0
  for( int j = 0; j < NUM; j++ )
2572
0
  {
2573
0
    const double d = std::fabs( sum[ j ] - target );
2574
0
    if( d < bestDiff || ( d == bestDiff && j > bestIdx ) )   // the larger index when equally close
2575
0
    {
2576
0
      bestDiff = d;
2577
0
      bestIdx  = j;
2578
0
    }
2579
0
  }
2580
0
  if( bestIdx == -1 || ( bestIdx == NUM - 1 && target < sum[ NUM - 1 ] && bestDiff > target / 2 ) )
2581
0
  {
2582
0
    return -1;
2583
0
  }
2584
0
  return Clip3( 2, 14, bestIdx + 2 );   // RDD 5 range
2585
0
}
2586
2587
// Replaces missing cut-offs (-1) with the averages; one missing average is replaced by the other.
2588
void FGAnalyzer::replaceCutoff( ComponentID compId, int& replacementH, int& replacementV )
2589
0
{
2590
0
  SeiFgc::CompModel& cm = m_compModel[ compId ];
2591
0
  if( !cm.presentFlag )
2592
0
  {
2593
0
    return;
2594
0
  }
2595
0
  if( replacementH == -1 && replacementV == -1 )
2596
0
  {
2597
0
    cm.presentFlag = false;
2598
0
    return;
2599
0
  }
2600
0
  if( replacementH == -1 )
2601
0
  {
2602
0
    replacementH = replacementV;
2603
0
  }
2604
0
  else if( replacementV == -1 )
2605
0
  {
2606
0
    replacementV = replacementH;
2607
0
  }
2608
0
  for( int i = 0; i < cm.numIntensityIntervals; i++ )
2609
0
  {
2610
0
    auto& v = cm.intensityValues[ i ].compModelValue;
2611
0
    if( v[ 1 ] == -1 )
2612
0
    {
2613
0
      v[ 1 ] = replacementH;
2614
0
    }
2615
0
    if( v[ 2 ] == -1 )
2616
0
    {
2617
0
      v[ 2 ] = replacementV;
2618
0
    }
2619
0
  }
2620
0
}
2621
2622
// Limits every cut-off to the average +-2.
2623
void FGAnalyzer::limitCutoff( ComponentID compId, int avgH, int avgV )
2624
0
{
2625
0
  SeiFgc::CompModel& cm = m_compModel[ compId ];
2626
0
  if( !cm.presentFlag )
2627
0
  {
2628
0
    return;
2629
0
  }
2630
0
  constexpr int delta = 2;
2631
0
  for( int i = 0; i < cm.numIntensityIntervals; i++ )
2632
0
  {
2633
0
    auto& v = cm.intensityValues[ i ].compModelValue;
2634
0
    v[ 1 ] = Clip3( avgH - delta, avgH + delta, v[ 1 ] );
2635
0
    v[ 2 ] = Clip3( avgV - delta, avgV + delta, v[ 2 ] );
2636
0
  }
2637
0
}
2638
2639
// Smooths the cut-offs over consecutive intervals with a [1 2 1] filter ([3 1] at the ends), in place as in VTM.
2640
void FGAnalyzer::limitCutoffConsecutive( ComponentID compId )
2641
0
{
2642
0
  SeiFgc::CompModel& cm = m_compModel[ compId ];
2643
0
  if( !cm.presentFlag )
2644
0
  {
2645
0
    return;
2646
0
  }
2647
0
  const int n = cm.numIntensityIntervals;
2648
0
  for( int i = 0; i < n; i++ )
2649
0
  {
2650
0
    for( int k = 1; k <= 2; k++ )
2651
0
    {
2652
0
      int& cur = cm.intensityValues[ i ].compModelValue[ k ];
2653
0
      if( i == 0 )
2654
0
      {
2655
0
        cur = ( 3 * cur + cm.intensityValues[ i + 1 ].compModelValue[ k ] + 2 ) / 4;
2656
0
      }
2657
0
      else if( i == n - 1 )
2658
0
      {
2659
0
        cur = ( cm.intensityValues[ i - 1 ].compModelValue[ k ] + 3 * cur + 2 ) / 4;
2660
0
      }
2661
0
      else
2662
0
      {
2663
0
        cur = ( cm.intensityValues[ i - 1 ].compModelValue[ k ] + 2 * cur + cm.intensityValues[ i + 1 ].compModelValue[ k ] + 2 ) / 4;
2664
0
      }
2665
0
    }
2666
0
  }
2667
0
}
2668
2669
// Reduces the number of different cut-off pairs to maxUnique with a weighted k-medoids clustering (Manhattan distance,
2670
// weights are interval widths) and maps every interval to its medoid. Returns the number of different pairs.
2671
int FGAnalyzer::limitCutoffPairs( std::vector<CutoffPair>& pairs, int maxUnique ) const
2672
0
{
2673
0
  const int s = (int) pairs.size();
2674
0
  if( s == 0 )
2675
0
  {
2676
0
    return 0;
2677
0
  }
2678
2679
  // distinct pairs in order of first appearance, with summed weights and the intervals using them
2680
0
  std::vector<CutoffPair> nodes;
2681
0
  for( int i = 0; i < s; i++ )
2682
0
  {
2683
0
    auto it = std::find_if( nodes.begin(), nodes.end(), [ & ]( const CutoffPair& n ) { return n.p == pairs[ i ].p; } );
2684
0
    if( it == nodes.end() )
2685
0
    {
2686
0
      nodes.push_back( CutoffPair() );
2687
0
      nodes.back().p = pairs[ i ].p;
2688
0
      nodes.back().w = pairs[ i ].w;
2689
0
      nodes.back().idx.push_back( i );
2690
0
    }
2691
0
    else
2692
0
    {
2693
0
      it->w += pairs[ i ].w;
2694
0
      it->idx.push_back( i );
2695
0
    }
2696
0
  }
2697
0
  const int numNodes = (int) nodes.size();
2698
0
  if( numNodes <= maxUnique )
2699
0
  {
2700
0
    return numNodes;
2701
0
  }
2702
2703
0
  std::vector<std::vector<int>> dist( numNodes, std::vector<int>( numNodes, 0 ) );
2704
0
  for( int u = 0; u < numNodes; u++ )
2705
0
  {
2706
0
    for( int v = u + 1; v < numNodes; v++ )
2707
0
    {
2708
0
      dist[ u ][ v ] = dist[ v ][ u ] = abs( nodes[ u ].p.first - nodes[ v ].p.first ) + abs( nodes[ u ].p.second - nodes[ v ].p.second );
2709
0
    }
2710
0
  }
2711
2712
  // start with the heaviest pair, then add heavy pairs far from the medoids chosen so far
2713
0
  std::vector<int> md;
2714
0
  int first = 0;
2715
0
  for( int u = 1; u < numNodes; u++ )
2716
0
  {
2717
0
    if( nodes[ u ].w > nodes[ first ].w )
2718
0
    {
2719
0
      first = u;
2720
0
    }
2721
0
  }
2722
0
  md.push_back( first );
2723
0
  auto isMedoid = [ & ]( int u ) { return std::find( md.begin(), md.end(), u ) != md.end(); };
2724
0
  while( (int) md.size() < maxUnique )
2725
0
  {
2726
0
    long long bestScore = -1;
2727
0
    int bestU = -1;
2728
0
    for( int u = 0; u < numNodes; u++ )
2729
0
    {
2730
0
      if( isMedoid( u ) )
2731
0
      {
2732
0
        continue;
2733
0
      }
2734
0
      int dmin = std::numeric_limits<int>::max();
2735
0
      for( int m : md )
2736
0
      {
2737
0
        dmin = std::min( dmin, dist[ u ][ m ] );
2738
0
      }
2739
0
      const long long score = 1LL * nodes[ u ].w * dmin;
2740
0
      if( score > bestScore )
2741
0
      {
2742
0
        bestScore = score;
2743
0
        bestU = u;
2744
0
      }
2745
0
    }
2746
0
    if( bestU < 0 )
2747
0
    {
2748
0
      break;
2749
0
    }
2750
0
    md.push_back( bestU );
2751
0
  }
2752
2753
0
  std::vector<int> assignTo( numNodes, -1 );
2754
0
  auto assignAll = [ & ]()
2755
0
  {
2756
0
    for( int u = 0; u < numNodes; u++ )
2757
0
    {
2758
0
      int bestM = md.front();
2759
0
      int bestD = dist[ u ][ bestM ];
2760
0
      for( int m : md )
2761
0
      {
2762
0
        const int d = dist[ u ][ m ];
2763
0
        if( d < bestD || ( d == bestD && m < bestM ) )
2764
0
        {
2765
0
          bestD = d;
2766
0
          bestM = m;
2767
0
        }
2768
0
      }
2769
0
      assignTo[ u ] = bestM;
2770
0
    }
2771
0
  };
2772
0
  assignAll();
2773
2774
  // swap a medoid with a non-medoid while that lowers the total weighted distance (at most 15 times)
2775
0
  bool improved = true;
2776
0
  for( int iter = 0; improved && iter < 15; iter++ )
2777
0
  {
2778
0
    improved = false;
2779
0
    long long bestDelta = 0;
2780
0
    int bestOut = -1, bestIn = -1;
2781
0
    std::vector<char> isM( numNodes, 0 );
2782
0
    for( int m : md )
2783
0
    {
2784
0
      isM[ m ] = 1;
2785
0
    }
2786
0
    for( int mOut : md )
2787
0
    {
2788
0
      for( int o = 0; o < numNodes; o++ )
2789
0
      {
2790
0
        if( isM[ o ] )
2791
0
        {
2792
0
          continue;
2793
0
        }
2794
0
        long long delta = 0;
2795
0
        for( int u = 0; u < numNodes; u++ )
2796
0
        {
2797
0
          const int duOld = dist[ u ][ assignTo[ u ] ];
2798
0
          const int dToO  = dist[ u ][ o ];
2799
0
          int duNew;
2800
0
          if( assignTo[ u ] == mOut )
2801
0
          {
2802
0
            int dAlt = std::numeric_limits<int>::max();
2803
0
            for( int m : md )
2804
0
            {
2805
0
              if( m != mOut )
2806
0
              {
2807
0
                dAlt = std::min( dAlt, dist[ u ][ m ] );
2808
0
              }
2809
0
            }
2810
0
            duNew = std::min( dAlt, dToO );
2811
0
          }
2812
0
          else
2813
0
          {
2814
0
            duNew = std::min( duOld, dToO );
2815
0
          }
2816
0
          delta += 1LL * nodes[ u ].w * ( duNew - duOld );
2817
0
        }
2818
0
        if( delta < bestDelta )
2819
0
        {
2820
0
          bestDelta = delta;
2821
0
          bestOut = mOut;
2822
0
          bestIn = o;
2823
0
        }
2824
0
      }
2825
0
    }
2826
0
    if( bestDelta < 0 )
2827
0
    {
2828
0
      *std::find( md.begin(), md.end(), bestOut ) = bestIn;
2829
0
      assignAll();
2830
0
      improved = true;
2831
0
    }
2832
0
  }
2833
2834
0
  for( int u = 0; u < numNodes; u++ )
2835
0
  {
2836
0
    for( int idx : nodes[ u ].idx )
2837
0
    {
2838
0
      pairs[ idx ].p = nodes[ assignTo[ u ] ].p;
2839
0
    }
2840
0
  }
2841
0
  return (int) md.size();
2842
0
}
2843
2844
} // namespace vvenc