Coverage Report

Created: 2026-10-17 06:56

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
45
#include "CommonLib/MCTF.h"
46
#include "TrQuant_EMT.h"
47
48
using namespace vvenc;
49
50
// POLYFIT
51
static constexpr int      MAXORDER = 8;                                   // maximum order of polynomial fitting
52
static constexpr int      MAX_REAL_SCALE = 16;
53
static constexpr int      ORDER = 4;                                      // order of polynomial function
54
static constexpr int      QUANT_LEVELS = 4;                               // number of quantization levels in lloyd max quantization
55
56
static constexpr int      MIN_ELEMENT_NUMBER_PER_INTENSITY_INTERVAL = 8;
57
static constexpr int      MIN_POINTS_FOR_INTENSITY_ESTIMATION = 40;       // 5*8 = 40; 5 intervals with at least 8 points
58
static constexpr int      MIN_BLOCKS_FOR_CUTOFF_ESTIMATION = 2;           // 2 blocks of 64 x 64 size
59
static constexpr int      POINT_STEP = 16;                                // step size in point extension
60
static constexpr int      MAX_NUM_POINT_TO_EXTEND = 4;                    // max point in extension
61
static constexpr double   POINT_SCALE = 1.25;                             // scaling in point extension
62
static constexpr double   VAR_SCALE_DOWN = 1.2;                           // filter out large points
63
static constexpr double   VAR_SCALE_UP = 0.6;                             // filter out large points
64
static constexpr int      NUM_PASSES = 2;                                 // number of passes when fitting the function
65
static constexpr int      NBRS = 1;                                       // minimum number of surrounding points in order to keep it for further analysis (within the widnow range)
66
static constexpr int      WINDOW = 1;                                     // window to check surrounding points
67
static constexpr int      MIN_INTENSITY = 40;
68
static constexpr int      MAX_INTENSITY = 950;
69
70
static constexpr int      MAX_ALLOWED_MODEL_VALUES = 3;
71
static constexpr int      MAX_NUM_MODEL_VALUES = 6;                       // Maximum number of model values supported in FGC SEI
72
73
static constexpr int      BLK_8 = 8;
74
static constexpr int      BLK_16 = 16;
75
static constexpr int      BLK_32 = 32;
76
static constexpr int      BIT_DEPTH_8 = 8;
77
78
79
static constexpr int      MAX_BLOCKS = 40000;                             // higher than (3840*2160)/(16*16)
80
81
const int m_gx[CONV_HEIGHT_S][CONV_WIDTH_S]{ { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
82
const int m_gy[CONV_HEIGHT_S][CONV_WIDTH_S]{ { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };
83
84
constexpr double FGAnalyzer::m_tapFilter[3];
85
86
void gradient_core ( PelStorage *buff1,
87
                     PelStorage *buff2,
88
                     PelStorage *tmpBuf1,
89
                     PelStorage *tmpBuf2,
90
                     uint32_t width,
91
                     uint32_t height,
92
                     uint32_t bitDepth,
93
                     ComponentID compID )
94
0
{
95
  // buff1 - magnitude; buff2 - orientation (Only luma in buff2)
96
0
  const uint32_t convWidthS = CONV_WIDTH_S;
97
0
  const uint32_t convHeightS = CONV_HEIGHT_S;
98
0
  const int maxClpRange = (1 << bitDepth) - 1;
99
0
  const int padding     = convWidthS / 2;
100
101
0
  buff1->get(compID).extendBorderPel( padding,
102
0
                                      padding );
103
104
  // Gx
105
0
  for (int i = 0; i < width; i++)
106
0
  {
107
0
    for (int j = 0; j < height; j++)
108
0
    {
109
0
      int acc = 0;
110
0
      int xOffset = i - convWidthS / 2;
111
0
      int yOffset = j - convHeightS / 2;
112
0
      for (int x = 0; x < convWidthS; x++)
113
0
      {
114
0
        for (int y = 0; y < convHeightS; y++)
115
0
        {
116
0
          acc += ( buff1->get(compID).at( x + xOffset, y + yOffset ) * m_gx[x][y] );
117
0
        }
118
0
      }
119
0
      tmpBuf1->Y().at(i, j) = acc;
120
0
    }
121
0
  }
122
123
  // Gy
124
0
  for ( int i = 0; i < width; i++ )
125
0
  {
126
0
    for ( int j = 0; j < height; j++ )
127
0
    {
128
0
      int acc = 0;
129
0
      for ( int x = 0; x < convWidthS; x++ )
130
0
      {
131
0
        for ( int y = 0; y < convHeightS; y++ )
132
0
        {
133
0
          acc += (buff1->get(compID).at(x - convWidthS / 2 + i, y - convHeightS / 2 + j) * m_gy[x][y]);
134
0
        }
135
0
      }
136
0
      tmpBuf2->Y().at(i, j) = acc;
137
0
    }
138
0
  }
139
140
  // magnitude
141
0
  for ( int i = 0; i < width; i++ )
142
0
  {
143
0
    for ( int j = 0; j < height; j++ )
144
0
    {
145
0
      Pel tmp                     = static_cast<Pel>((abs(tmpBuf1->Y().at(i, j)) + abs(tmpBuf2->Y().at(i, j))) / 2);
146
0
      buff1->get(compID).at(i, j) = static_cast<Pel>( Clip3((Pel) 0, (Pel) maxClpRange, tmp) );
147
0
    }
148
0
  }
149
150
  // Loop through each pixel
151
0
  for ( int i = 0; i < width; i++ )
152
0
  {
153
0
    for ( int j = 0; j < height; j++ )
154
0
    {
155
      // Calculate edge direction angle
156
0
      Pel Dx = tmpBuf1->Y().at( i, j );
157
0
      Pel Dy = tmpBuf2->Y().at( i, j );
158
0
      float theta = 0.0;
159
0
      int quantized_direction = 0;
160
161
0
      if ( Dx == 0 )
162
0
      {
163
0
        if ( Dy == 0 )
164
0
          quantized_direction = 0;
165
0
        else
166
0
          quantized_direction = 90;
167
0
      }
168
0
      else
169
0
      {
170
0
        theta= ( atan( static_cast<double>( Dy )/(double)static_cast<double>( Dx ) ) ) ;
171
0
        if ( Dx < 0 )
172
0
        {
173
0
          if ( Dy >= 0 )
174
0
            theta += static_cast<float>( PI );
175
0
          else
176
0
            theta -= static_cast<float>( PI );
177
0
        }
178
0
        theta = std::fabs( theta );
179
        /* Convert actual edge direction to approximate value - quantize directions */
180
0
        if (( theta <= pi_8 ) || ( pi_7_8 < theta ))
181
0
        {
182
0
          quantized_direction = 0;
183
0
        }
184
0
        if (( pi_8 < theta ) && ( theta <= pi_3_8 ))
185
0
        {
186
0
          if ( Dy > 0 )
187
0
            quantized_direction = 45;
188
0
          else
189
0
            quantized_direction = 135;
190
0
        }
191
0
        if (( pi_3_8 < theta ) && ( theta <= pi_5_8 ))
192
0
        {
193
0
          quantized_direction = 90;
194
0
        }
195
0
        if (( pi_5_8 < theta ) && ( theta <= pi_7_8 ))
196
0
        {
197
0
          if ( Dy > 0 )
198
0
            quantized_direction = 135;
199
0
          else
200
0
            quantized_direction = 45;
201
0
        }
202
0
      }
203
0
      buff2->get(ComponentID(0)).at( i, j ) = quantized_direction;
204
0
    }
205
0
  }
206
0
  buff1->get(compID).extendBorderPel( padding, 
207
0
                                      padding );   // extend border for the next steps
208
0
}
209
210
// ====================================================================================================================
211
// Edge detection - Canny
212
// ====================================================================================================================
213
214
Canny::Canny( bool enableOpt )
215
1.15k
{
216
  // init();
217
1.15k
  gradient=gradient_core;
218
219
1.15k
  if( enableOpt )
220
1.15k
  {
221
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
222
    initFGACannyX86();
223
#endif
224
1.15k
  }
225
1.15k
}
226
227
Canny::~Canny()
228
1.15k
{
229
  // uninit();
230
1.15k
}
231
232
void Canny::init ( uint32_t width,
233
                   uint32_t height,
234
                   ChromaFormat inputChroma )
235
0
{
236
0
  if (!m_orientationBuf)
237
0
  {
238
0
    m_orientationBuf = new PelStorage;
239
0
    m_orientationBuf->create( inputChroma,
240
0
                              Area(0, 0, width, height) );
241
0
  }
242
243
0
  if ( !m_gradientBufX )
244
0
  {
245
0
    m_gradientBufX = new PelStorage;
246
0
    m_gradientBufX->create ( inputChroma,
247
0
                             Area(0, 0, width, height) );
248
0
  }
249
250
0
  if ( !m_gradientBufY )
251
0
  {
252
0
    m_gradientBufY = new PelStorage;
253
0
    m_gradientBufY->create ( inputChroma,
254
0
                             Area(0, 0, width, height) );
255
0
  }
256
0
}
257
258
void Canny::destroy()
259
0
{
260
0
  if ( m_orientationBuf )
261
0
  {
262
0
    m_orientationBuf->destroy();
263
0
    delete m_orientationBuf;
264
0
    m_orientationBuf = nullptr;
265
0
  }
266
267
0
  if ( m_gradientBufX )
268
0
  {
269
0
    m_gradientBufX->destroy();
270
0
    delete m_gradientBufX;
271
0
    m_gradientBufX = nullptr;
272
0
  }
273
274
0
  if ( m_gradientBufY )
275
0
  {
276
0
    m_gradientBufY->destroy();
277
0
    delete m_gradientBufY;
278
0
    m_gradientBufY = nullptr;
279
0
  }
280
0
}
281
282
void Canny::suppressNonMax ( PelStorage *buff1,
283
                             PelStorage *buff2,
284
                             uint32_t width,
285
                             uint32_t height,
286
                             ComponentID compID )
287
0
{
288
0
  for ( int i = 0; i < width; i++ )
289
0
  {
290
0
    for ( int j = 0; j < height; j++ )
291
0
    {
292
0
      int rowShift = 0, colShift = 0;
293
0
      switch ( buff2->get( ComponentID(0) ).at( i, j ) )
294
0
      {
295
0
      case 0:
296
0
        rowShift = 1;
297
0
        colShift = 0;
298
0
        break;
299
0
      case 45:
300
0
        rowShift = 1;
301
0
        colShift = 1;
302
0
        break;
303
0
      case 90:
304
0
        rowShift = 0;
305
0
        colShift = 1;
306
0
        break;
307
0
      case 135:
308
0
        rowShift = -1;
309
0
        colShift = 1;
310
0
        break;
311
0
      default: THROW("Unsupported gradient direction."); break;
312
0
      }
313
314
0
      Pel pelCurrent             = buff1->get(compID).at( i, j );
315
0
      Pel pelEdgeDirectionTop    = buff1->get(compID).at( i + rowShift, j + colShift );
316
0
      Pel pelEdgeDirectionBottom = buff1->get(compID).at( i - rowShift, j - colShift );
317
0
      if (( pelCurrent < pelEdgeDirectionTop ) || ( pelCurrent < pelEdgeDirectionBottom ))
318
0
      {
319
0
        buff2->get(ComponentID(0)).at( i, j ) = 0;   // supress
320
0
      }
321
0
      else
322
0
      {
323
0
        buff2->get(ComponentID(0)).at( i, j ) = buff1->get(compID).at( i, j );   // keep
324
0
      }
325
0
    }
326
0
  }
327
0
  buff1->get(compID).copyFrom( buff2->get( ComponentID(0) ) );
328
0
}
329
330
void Canny::doubleThreshold ( PelStorage *buff,
331
                              uint32_t width,
332
                              uint32_t height,
333
                              uint32_t bitDepth,
334
                              ComponentID compID )
335
0
{
336
0
  Pel strongPel = ( static_cast<Pel>( 1 ) << bitDepth) - 1;
337
0
  Pel weekPel   = ( static_cast<Pel>( 1 ) << (bitDepth - 1)) - 1;
338
339
0
  Pel highThreshold = 0;
340
0
  Pel lowThreshold  = strongPel;
341
0
  for ( int i = 0; i < width; i++ )
342
0
  {
343
0
    for ( int j = 0; j < height; j++ )
344
0
    {
345
0
      highThreshold = std::max<Pel>( highThreshold,
346
0
                                     buff->get(compID).at( i, j ) );
347
0
    }
348
0
  }
349
350
  // global low and high threshold
351
0
  lowThreshold = static_cast<Pel>( m_lowThresholdRatio * highThreshold );
352
0
  highThreshold = Clip3( 0,
353
0
                         (1 << bitDepth) - 1,
354
0
                         m_highThresholdRatio * lowThreshold);   // Canny recommended a upper:lower ratio between 2:1 and 3:1.
355
356
  // strong, week, supressed
357
0
  for ( int i = 0; i < width; i++ )
358
0
  {
359
0
    for ( int j = 0; j < height; j++ )
360
0
    {
361
0
      if ( buff->get(compID).at( i, j ) > highThreshold )
362
0
      {
363
0
        buff->get(compID).at( i, j ) = strongPel;
364
0
      }
365
0
      else if ( buff->get(compID).at( i, j ) <= highThreshold && buff->get(compID).at( i, j ) > lowThreshold )
366
0
      {
367
0
        buff->get(compID).at( i, j ) = weekPel;
368
0
      }
369
0
      else
370
0
      {
371
0
        buff->get(compID).at( i, j ) = 0;
372
0
      }
373
0
    }
374
0
  }
375
376
0
  buff->get(compID).extendBorderPel ( 1, 1 );   // extend one pixel on each side for the next step
377
0
}
378
379
void Canny::edgeTracking ( PelStorage *buff,
380
                           uint32_t width,
381
                           uint32_t height,
382
                           uint32_t windowWidth,
383
                           uint32_t windowHeight,
384
                           uint32_t bitDepth,
385
                           ComponentID compID )
386
0
{
387
0
  Pel strongPel = (static_cast<Pel>(1) << bitDepth) - 1;
388
0
  Pel weakPel   = (static_cast<Pel>(1) << (bitDepth - 1)) - 1;
389
390
0
  for ( int i = 0; i < width; i++ )
391
0
  {
392
0
    for ( int j = 0; j < height; j++ )
393
0
    {
394
0
      if ( buff->get(compID).at( i, j ) == weakPel )
395
0
      {
396
0
        bool strong = false;
397
398
0
        for ( int x = 0; x < windowWidth; x++ )
399
0
        {
400
0
          for ( int y = 0; y < windowHeight; y++ )
401
0
          {
402
0
            if ( buff->get(compID).at( x - windowWidth / 2 + i, y - windowHeight / 2 + j ) == strongPel )
403
0
            {
404
0
              strong = true;
405
0
              break;
406
0
            }
407
0
          }
408
0
        }
409
410
0
        if ( strong )
411
0
        {
412
0
          buff->get(compID).at( i, j ) = strongPel;
413
0
        }
414
0
        else
415
0
        {
416
0
          buff->get(compID).at( i, j ) = 0;   // supress
417
0
        }
418
0
      }
419
0
    }
420
0
  }
421
0
}
422
423
void Canny::detect_edges ( const PelStorage *orig,
424
                           PelStorage *dest,
425
                           uint32_t uiBitDepth,
426
                           ComponentID compID )
427
0
{
428
  /* No noise reduction - Gaussian blur is skipped;
429
   Gradient calculation;
430
   Non-maximum suppression;
431
   Double threshold;
432
   Edge Tracking by Hysteresis.*/
433
434
0
  uint32_t width      = orig->get( compID ).width,
435
0
           height     = orig->get( compID ).height;       // Width and Height of current frame
436
0
  uint32_t convWidthS  = CONV_WIDTH_S,
437
0
           convHeightS = CONV_HEIGHT_S;                 // Pixel's row and col positions for Sobel filtering
438
0
  uint32_t bitDepth    = uiBitDepth;
439
440
0
  dest->get(compID).copyFrom( orig->getBuf( compID ) );   // we skip blur in canny detector to catch as much as possible edges and textures
441
442
  /* Gradient calculation */
443
0
  gradient ( dest,
444
0
             m_orientationBuf,
445
0
             m_gradientBufX,
446
0
             m_gradientBufY,
447
0
             width,
448
0
             height,
449
0
             bitDepth,
450
0
             compID );
451
452
  /* Non - maximum suppression */
453
0
  suppressNonMax ( dest,
454
0
                   m_orientationBuf,
455
0
                   width,
456
0
                   height,
457
0
                   compID );
458
459
  /* Double threshold */
460
0
  doubleThreshold ( dest,
461
0
                    width,
462
0
                    height, 
463
0
                    bitDepth,
464
0
                    compID );
465
466
  /* Edge Tracking by Hysteresis */
467
0
  edgeTracking ( dest,
468
0
                 width,
469
0
                 height,
470
0
                 convWidthS,
471
0
                 convHeightS,
472
0
                 bitDepth,
473
0
                 compID ); 
474
0
}
475
476
// ====================================================================================================================
477
// Morphologigal operations - Dilation and Erosion
478
// ====================================================================================================================
479
int dilation_core ( PelStorage *buff,
480
                    PelStorage *Wbuf,
481
                    uint32_t bitDepth,
482
                    ComponentID compID,
483
                    int numIter,
484
                    int iter,
485
                    Pel Value )
486
0
{
487
0
  if ( iter == numIter )
488
0
  {
489
0
    return iter;
490
0
  }
491
0
  uint32_t width      = buff->get( compID ).width;
492
0
  uint32_t height     = buff->get( compID ).height;   // Width and Height of current frame
493
0
  uint32_t windowSize = KERNELSIZE;
494
0
  uint32_t padding    = windowSize / 2;
495
496
0
  Wbuf->bufs[0].copyFrom( buff->get( compID ) );
497
498
0
  buff->get(compID).extendBorderPel( padding,
499
0
                                     padding );
500
501
0
  for ( int i = 0; i < width; i++ )
502
0
  {
503
0
    for ( int j = 0; j < height; j++ )
504
0
    {
505
0
      bool strong = false;
506
0
      for ( int x = 0; x < windowSize; x++ )
507
0
      {
508
0
        for ( int y = 0; y < windowSize; y++ )
509
0
        {
510
0
          if ( buff->get( compID ).at( x - windowSize / 2 + i, y - windowSize / 2 + j ) == Value )
511
0
          {
512
0
            strong = true;
513
0
            break;
514
0
          }
515
0
        }
516
0
        if ( strong ) break;
517
0
      }
518
0
      if ( strong )
519
0
      {
520
0
        Wbuf->get(ComponentID(0)).at( i, j ) = Value;
521
0
      }
522
0
    }
523
0
  }
524
525
0
  buff->get(compID).copyFrom( Wbuf->bufs[0] );
526
527
0
  return dilation_core ( buff,
528
0
                         Wbuf,
529
0
                         bitDepth,
530
0
                         compID,
531
0
                         numIter,
532
0
                         ++iter,
533
0
                         Value );
534
535
0
}
536
537
Morph::Morph( bool enableOpt )
538
1.15k
{
539
  // init();
540
1.15k
  dilation=dilation_core;
541
542
1.15k
  if( enableOpt )
543
1.15k
  {
544
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
545
    initFGAMorphX86();
546
#endif
547
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM )
548
    initFGAMorphARM();
549
#endif
550
1.15k
  }
551
1.15k
}
552
553
Morph::~Morph()
554
1.15k
{
555
  // uninit();
556
1.15k
}
557
558
void Morph::init ( uint32_t width,
559
                   uint32_t height )
560
0
{
561
0
  if ( !m_dilationBuf )
562
0
  {
563
0
    m_dilationBuf = new PelStorage;
564
0
    m_dilationBuf->create ( VVENC_CHROMA_400,
565
0
                            Area( 0, 0, width, height ) );
566
0
  }
567
0
  if ( !m_dilationBuf2 )
568
0
  {
569
0
    m_dilationBuf2 = new PelStorage;
570
0
    m_dilationBuf2->create ( VVENC_CHROMA_400,
571
0
                             Area( 0, 0, width >> 1, height >> 1 ) );
572
0
  }
573
0
  if ( !m_dilationBuf4 )
574
0
  {
575
0
    m_dilationBuf4 = new PelStorage;
576
0
    m_dilationBuf4->create( VVENC_CHROMA_400,
577
0
                              Area( 0, 0, width >> 2, height >> 2 ) );
578
0
  }
579
0
}
580
581
void Morph::destroy ()
582
0
{
583
0
  if ( m_dilationBuf )
584
0
  {
585
0
    m_dilationBuf->destroy();
586
0
    delete m_dilationBuf;
587
0
    m_dilationBuf = nullptr;
588
0
  }
589
0
  if ( m_dilationBuf2 )
590
0
  {
591
0
    m_dilationBuf2->destroy();
592
0
    delete m_dilationBuf2;
593
0
    m_dilationBuf2 = nullptr;
594
0
  }
595
0
  if ( m_dilationBuf4 )
596
0
  {
597
0
    m_dilationBuf4->destroy();
598
0
    delete m_dilationBuf4;
599
0
    m_dilationBuf4 = nullptr;
600
0
  }
601
0
}
602
603
int calcMeanCore ( const Pel* org,
604
                   const ptrdiff_t origStride,
605
                   const int w,
606
                   const int h )
607
0
{
608
  // calculate average
609
0
  int avg = 0;
610
0
  for( int y1 = 0; y1 < h; y1++ )
611
0
  {
612
0
    for( int x1 = 0; x1 < w; x1++ )
613
0
    {
614
0
      avg = avg + *( org + x1 + y1 * origStride );
615
0
    }
616
0
  }
617
0
  return avg;
618
0
}
619
620
// ====================================================================================================================
621
// Film Grain Analysis Functions
622
// ====================================================================================================================
623
FGAnalyzer::FGAnalyzer( bool enableOpt )
624
1.15k
  : m_edgeDetector( enableOpt )
625
1.15k
  , m_morphOperation( enableOpt )
626
1.15k
{
627
1.15k
  calcVar     = calcVarCore;
628
1.15k
  calcMean    = calcMeanCore;
629
1.15k
  fastDCT2_64 = fastForwardDCT2_B64;
630
631
1.15k
  if( enableOpt )
632
1.15k
  {
633
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
634
    initFGAnalyzerX86();
635
#endif
636
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM )
637
    initFGAnalyzerARM();
638
#endif
639
1.15k
  }
640
1.15k
}
641
642
FGAnalyzer::~FGAnalyzer()
643
1.15k
{
644
1.15k
}
645
646
// initialize film grain parameters
647
void FGAnalyzer::init ( const int width,
648
                        const int height,
649
                        const ChromaFormat inputChroma,
650
                        const int *outputBitDepths,
651
                        const bool doAnalysis[] )
652
0
{
653
0
  m_log2ScaleFactor = 2;
654
0
  for (int i = 0; i < ComponentID::MAX_NUM_COMP; i++)
655
0
  {
656
0
    m_compModel[i].presentFlag           = true;
657
0
    m_compModel[i].numModelValues        = 3;
658
0
    m_compModel[i].numIntensityIntervals = 1;
659
0
    m_compModel[i].intensityValues.resize(VVENC_MAX_NUM_INTENSITIES);
660
0
    for ( int j = 0; j < VVENC_MAX_NUM_INTENSITIES; j++ )
661
0
    {
662
0
      m_compModel[i].intensityValues[j].intensityIntervalLowerBound = 10;
663
0
      m_compModel[i].intensityValues[j].intensityIntervalUpperBound = 250;
664
0
      m_compModel[i].intensityValues[j].compModelValue.resize( MAX_ALLOWED_MODEL_VALUES );
665
0
      for ( int k = 0; k < m_compModel[i].numModelValues; k++ )
666
0
      {
667
        // half intensity for chroma. Provided value is default value, manually tuned.
668
0
        m_compModel[i].intensityValues[j].compModelValue[k] = i == 0 ? 26 : 13;
669
0
      }
670
0
    }
671
0
    m_doAnalysis[i] = doAnalysis[i];
672
0
  }
673
674
  // initialize picture parameters and create buffers
675
0
  m_bitDepths                   = const_cast<int*>( outputBitDepths );
676
0
  m_inputChromaFormat           = inputChroma;
677
  // Allocate memory for m_coeffBuf and m_dctGrainBlockList
678
0
  m_coeffBuf = (TCoeff*)xMalloc( TCoeff, width * height );
679
0
  int N = (width * height) / (DATA_BASE_SIZE * DATA_BASE_SIZE);
680
0
  m_dctGrainBlockList = new CoeffBuf[N];
681
682
0
  std::fill( std::begin(vecMean), std::end(vecMean), 0 );
683
0
  std::fill( std::begin(vecVar), std::end(vecVar), 0 );
684
685
  // Connect portions of m_coeffBuf memory with m_dctGrainBlockList
686
0
  for ( int i = 0; i < N; ++i )
687
0
  {
688
0
    m_dctGrainBlockList[i].buf = m_coeffBuf + i * ( DATA_BASE_SIZE * DATA_BASE_SIZE );
689
0
    m_dctGrainBlockList[i].stride = DATA_BASE_SIZE;
690
0
    m_dctGrainBlockList[i].height = m_dctGrainBlockList[i].width = DATA_BASE_SIZE;
691
0
  }
692
693
0
  m_edgeDetector.init ( width,
694
0
                        height,
695
0
                        inputChroma );
696
697
0
  m_morphOperation.init ( width,
698
0
                          height );
699
700
0
  int margin = m_edgeDetector.m_convWidthG / 2;   // set margin for padding for filtering
701
0
  int      newWidth2 = width / 2;
702
0
  int      newHeight2 = height / 2;
703
0
  int      newWidth4 = width / 4;
704
0
  int      newHeight4 = height / 4;
705
706
0
  if ( !m_maskBuf )
707
0
  {
708
0
    m_maskBuf = new PelStorage;
709
0
    m_maskBuf->create ( inputChroma,
710
0
                        Area(0, 0, width, height),
711
0
                        0, margin,
712
0
                        0, false );
713
0
  }
714
715
0
  if ( !m_grainEstimateBuf )
716
0
  {
717
0
     m_grainEstimateBuf = new PelStorage;
718
0
     m_grainEstimateBuf->create( inputChroma,
719
0
                                Area(0, 0, width, height),
720
0
                                0, 0,
721
0
                                0, false );
722
0
  }
723
724
0
  if ( !m_workingBufSubsampled2 )
725
0
  {
726
0
    m_workingBufSubsampled2 = new PelStorage;
727
0
    m_workingBufSubsampled2->create( inputChroma,
728
0
                                     Area(0, 0, newWidth2, newHeight2),
729
0
                                     0, margin,
730
0
                                     0, false );
731
0
  }
732
733
0
  if ( !m_maskSubsampled2 )
734
0
  {
735
0
    m_maskSubsampled2 = new PelStorage;
736
0
    m_maskSubsampled2->create( inputChroma,
737
0
                               Area(0, 0, newWidth2, newHeight2),
738
0
                               0, margin,
739
0
                               0, false );
740
0
  }
741
0
  if ( !m_workingBufSubsampled4 )
742
0
  {
743
0
    m_workingBufSubsampled4 = new PelStorage;
744
0
    m_workingBufSubsampled4->create( inputChroma,
745
0
                                     Area(0, 0, newWidth4, newHeight4),
746
0
                                     0, margin,
747
0
                                     0, false );
748
0
  }
749
750
0
  if ( !m_maskSubsampled4 )
751
0
  {
752
0
    m_maskSubsampled4 = new PelStorage;
753
0
    m_maskSubsampled4->create( inputChroma,
754
0
                               Area(0, 0, newWidth4, newHeight4),
755
0
                               0, margin,
756
0
                               0, false );
757
0
  }
758
0
  if ( !m_maskUpsampled )
759
0
  {
760
0
    m_maskUpsampled = new PelStorage;
761
0
    m_maskUpsampled->create( inputChroma,
762
0
                             Area(0, 0, width, height),
763
0
                             0, margin,
764
0
                             0, false );
765
0
  }
766
0
  if ( !m_DCTinout )
767
0
  {
768
0
    m_DCTinout = ( TCoeff* ) xMalloc( TCoeff, DATA_BASE_SIZE * DATA_BASE_SIZE );
769
0
  }
770
0
  if ( !m_DCTtemp )
771
0
  {
772
0
    m_DCTtemp = ( TCoeff* ) xMalloc( TCoeff, DATA_BASE_SIZE * DATA_BASE_SIZE );
773
0
  }
774
775
0
}
776
777
// delete picture buffers
778
void FGAnalyzer::destroy()
779
0
{
780
0
  if ( m_maskBuf != nullptr )
781
0
  {
782
0
    m_maskBuf->destroy();
783
0
    delete m_maskBuf;
784
0
    m_maskBuf = nullptr;
785
0
  }
786
787
0
  if ( m_grainEstimateBuf )
788
0
  {
789
0
    m_grainEstimateBuf->destroy();
790
0
    delete m_grainEstimateBuf;
791
0
    m_grainEstimateBuf = nullptr;
792
0
  }
793
794
0
  if ( m_workingBufSubsampled2 )
795
0
  {
796
0
    m_workingBufSubsampled2->destroy();
797
0
    delete m_workingBufSubsampled2;
798
0
    m_workingBufSubsampled2 = nullptr;
799
0
  }
800
0
  if ( m_maskSubsampled2 )
801
0
  {
802
0
    m_maskSubsampled2->destroy();
803
0
    delete m_maskSubsampled2;
804
0
    m_maskSubsampled2 = nullptr;
805
0
  }
806
  
807
0
  if ( m_workingBufSubsampled4 )
808
0
  {
809
0
    m_workingBufSubsampled4->destroy();
810
0
    delete m_workingBufSubsampled4;
811
0
    m_workingBufSubsampled4 = nullptr;
812
0
  }
813
0
  if ( m_maskSubsampled4 )
814
0
  {
815
0
    m_maskSubsampled4->destroy();
816
0
    delete m_maskSubsampled4;
817
0
    m_maskSubsampled4 = nullptr;
818
0
  }
819
0
  if ( m_maskUpsampled )
820
0
  {
821
0
    m_maskUpsampled->destroy();
822
0
    delete m_maskUpsampled;
823
0
    m_maskUpsampled = nullptr;
824
0
  }
825
0
  if ( m_DCTinout )
826
0
  {
827
0
    xFree( m_DCTinout );
828
0
    m_DCTinout = nullptr;
829
0
  }
830
0
  if ( m_DCTtemp )
831
0
  {
832
0
    xFree( m_DCTtemp );
833
0
    m_DCTtemp = nullptr;
834
0
  }
835
836
0
  xFree ( m_coeffBuf );
837
838
0
  if ( m_dctGrainBlockList )
839
0
  {
840
0
    delete[] m_dctGrainBlockList;
841
0
    m_dctGrainBlockList = nullptr;
842
0
  }
843
844
  // Clear vectors to release memory
845
0
  finalIntervalsandScalingFactors.clear();
846
0
  vec_mean_intensity.clear();
847
0
  vec_variance_intensity.clear();
848
0
  element_number_per_interval.clear();
849
0
  vecMean.clear();
850
0
  vecVar.clear();
851
0
  tmp_data_x.clear();
852
0
  tmp_data_y.clear();
853
0
  scalingVec.clear();
854
0
  quantVec.clear();
855
0
  coeffs.clear();
856
857
0
  m_edgeDetector.destroy ();
858
0
  m_morphOperation.destroy ();
859
0
}
860
861
// find flat and low complexity regions of the frame
862
void FGAnalyzer::findMask( ComponentID compId )
863
0
{
864
0
  const unsigned padding    = m_edgeDetector.m_convWidthG / 2;   // for filtering
865
0
  int bitDepth  = m_bitDepths[toChannelType( compId )];
866
867
  // Step 1: Subsample the original picture to two lower resolutions.
868
0
  subsample ( *m_workingBufSubsampled2,
869
0
              2,
870
0
              padding,
871
0
              compId );
872
0
  subsample ( *m_workingBufSubsampled4,
873
0
              4,
874
0
              padding,
875
0
              compId );
876
877
  /* Step 2: Full Resolution processing:
878
   * For each component(luma and chroma), detect edges and suppress low intensity regions.
879
   * Apply dilation to each component.*/
880
0
  m_edgeDetector.detect_edges ( m_workingBuf,
881
0
                                m_maskBuf,
882
0
                                bitDepth,
883
0
                                compId );
884
0
  suppressLowIntensity ( *m_workingBuf,
885
0
                         *m_maskBuf,
886
0
                         bitDepth,
887
0
                         compId );
888
  
889
0
  Pel strongPel = ( static_cast<Pel>( 1 ) << bitDepth ) - 1;
890
0
  m_morphOperation.dilation ( m_maskBuf,
891
0
                              m_morphOperation.m_dilationBuf,
892
0
                              bitDepth,
893
0
                              compId,
894
0
                              4,
895
0
                              0,
896
0
                              strongPel );
897
  
898
  
899
  /* Step 3: Subsampled 2 processing:
900
   * Detect edges and suppresses low intensity regions for each component.
901
   * Apply dilation to each component.
902
   * Upsample the result and combine it with the full-resolution mask.*/
903
0
  m_edgeDetector.detect_edges ( m_workingBufSubsampled2,
904
0
                                m_maskSubsampled2,
905
0
                                bitDepth,
906
0
                                compId );
907
0
  suppressLowIntensity ( *m_workingBufSubsampled2,
908
0
                         *m_maskSubsampled2,
909
0
                         bitDepth,
910
0
                         compId );
911
  
912
    
913
0
  m_morphOperation.dilation ( m_maskSubsampled2,
914
0
                              m_morphOperation.m_dilationBuf2,
915
0
                              bitDepth,
916
0
                              compId,
917
0
                              3,
918
0
                              0,
919
0
                              strongPel );
920
921
922
  // upsample, combine maskBuf and maskUpsampled
923
0
  upsample ( *m_maskSubsampled2,
924
0
             2,
925
0
             compId );
926
0
  combineMasks ( compId );
927
928
  /* Step 4: Subsampled 4 processing:
929
   * Detect edges and suppresses low intensity regions for each component.
930
   * Apply dilation to each component.
931
   * Upsample the result and combine it with the full-resolution mask.*/
932
0
  m_edgeDetector.detect_edges ( m_workingBufSubsampled4,
933
0
                                m_maskSubsampled4,
934
0
                                bitDepth,
935
0
                                compId );
936
0
  suppressLowIntensity ( *m_workingBufSubsampled4,
937
0
                         *m_maskSubsampled4,
938
0
                         bitDepth,
939
0
                         compId );
940
941
0
  m_morphOperation.dilation ( m_maskSubsampled4,
942
0
                              m_morphOperation.m_dilationBuf4,
943
0
                              bitDepth,
944
0
                              compId,
945
0
                              2,
946
0
                              0,
947
0
                              strongPel );
948
949
  // upsample, combine maskBuf and maskUpsampled
950
0
  upsample ( *m_maskSubsampled4,
951
0
             4,
952
0
             compId );
953
0
  combineMasks ( compId );
954
955
  /* Step 5: Final dilation and erosion
956
   * Apply final dilation to fill the holes and erosion for each component. */
957
0
  m_morphOperation.dilation ( m_maskBuf,
958
0
                              m_morphOperation.m_dilationBuf,
959
0
                              bitDepth,
960
0
                              compId,
961
0
                              2,
962
0
                              0,
963
0
                              strongPel );
964
  // erosion -> dilation with value 0
965
0
  m_morphOperation.dilation ( m_maskBuf,
966
0
                              m_morphOperation.m_dilationBuf,
967
0
                              bitDepth,
968
0
                              compId,
969
0
                              1,
970
0
                              0,
971
0
                              0 );
972
0
}
973
974
void FGAnalyzer::suppressLowIntensity ( const PelStorage &buff1,
975
                                        PelStorage &buff2,
976
                                        uint32_t bitDepth, 
977
                                        ComponentID compId )
978
0
{
979
  // buff1 - intensity values ( luma or chroma samples); buff2 - mask
980
981
0
  int width                 = buff2.get( compId ).width;
982
0
  int height                = buff2.get( compId ).height;
983
0
  Pel maxIntensity          = static_cast <Pel>( 1 << bitDepth ) - 1;
984
0
  Pel lowIntensityThreshold = static_cast<Pel>( m_lowIntensityRatio * maxIntensity );
985
986
  // strong, weak, supressed
987
0
  for ( int i = 0; i < width; i++ )
988
0
  {
989
0
    for ( int j = 0; j < height; j++ )
990
0
    {
991
      // Check if the intensity is below the threshold
992
0
      if ( buff1.get( compId ).at( i, j ) < lowIntensityThreshold )
993
0
      {
994
        // Set the corresponding mask value to maxIntensity
995
0
        buff2.get( compId ).at( i, j ) = maxIntensity;
996
0
      }
997
0
    }
998
0
  }
999
0
}
1000
1001
void FGAnalyzer::subsample ( PelStorage &output,
1002
                             const int factor,
1003
                             const int padding,
1004
                             ComponentID compId ) const
1005
0
{
1006
0
  const int newWidth  = m_workingBuf->get( compId ).width / factor;
1007
0
  const int newHeight = m_workingBuf->get( compId ).height / factor;
1008
1009
0
  const Pel *srcRow    = m_workingBuf->get( compId ).buf;
1010
0
  const ptrdiff_t srcStride = m_workingBuf->get( compId ).stride;
1011
0
  Pel *dstRow    = output.get( compId ).buf;   // output is tmp buffer with only one component for binary mask
1012
0
  const ptrdiff_t dstStride = output.get( compId ).stride;
1013
1014
0
  for ( int y = 0; y < newHeight; y++, srcRow += factor * srcStride, dstRow += dstStride )
1015
0
  {
1016
0
    const Pel *inRow      = srcRow;
1017
0
    const Pel *inRowBelow = srcRow + srcStride;
1018
0
    Pel *      target     = dstRow;
1019
1020
0
    for ( int x = 0; x < newWidth; x++ )
1021
0
    {
1022
0
      target[x] = ( inRow[0] + inRowBelow[0] + inRow[1] + inRowBelow[1] + 2 ) >> 2;
1023
0
      inRow += factor;
1024
0
      inRowBelow += factor;
1025
0
    }
1026
0
  }
1027
1028
0
  if ( padding )
1029
0
  {
1030
    // Extend border with padding
1031
0
    output.get( compId ).extendBorderPel ( padding,
1032
0
                                           padding );
1033
0
  }
1034
0
}
1035
1036
void FGAnalyzer::upsample ( const PelStorage &input,
1037
                            const int factor,
1038
                            const int padding,
1039
                            ComponentID compId ) const
1040
0
{
1041
  // binary mask upsampling
1042
  // use simple replication of pixels
1043
1044
0
  const int width  = input.get(compId).width;
1045
0
  const int height = input.get(compId).height;
1046
1047
0
  for ( int i = 0; i < width; i++ )
1048
0
  {
1049
0
    for ( int j = 0; j < height; j++ )
1050
0
    {
1051
0
      Pel currentPel = input.get( compId ).at( i, j );
1052
1053
0
      for ( int x = 0; x < factor; x++ )
1054
0
      {
1055
0
        for ( int y = 0; y < factor; y++ )
1056
0
        {
1057
0
          m_maskUpsampled->get( compId ).at( i * factor + x, j * factor + y ) = currentPel;
1058
0
        }
1059
0
      }
1060
0
    }
1061
0
  }
1062
1063
0
  if ( padding )
1064
0
  {
1065
0
    m_maskUpsampled->get( compId ).extendBorderPel( padding,
1066
0
                                                    padding );
1067
0
  }
1068
0
}
1069
1070
void FGAnalyzer::combineMasks( ComponentID compId )
1071
0
{
1072
0
  const int width = m_maskBuf->get( compId ).width;
1073
0
  const int height = m_maskBuf->get( compId ).height;
1074
1075
0
  for ( int i = 0; i < width; i++ )
1076
0
  {
1077
0
    for ( int j = 0; j < height; j++ )
1078
0
    {
1079
0
      m_maskBuf->get( compId ).at( i, j ) = ( m_maskBuf->get( compId ).at( i, j ) | m_maskUpsampled->get( compId ).at( i, j ) );
1080
0
    }
1081
0
  }
1082
0
}
1083
1084
// estimate cut-off frequencies and scaling factors for different intensity intervals
1085
void FGAnalyzer::estimateGrainParameters ( Picture *pic )
1086
0
{
1087
0
  m_originalBuf = &pic->getOrigBuffer();                                   // original frame
1088
0
  m_workingBuf = &pic->getFilteredOrigBuffer();                            // mctf filtered frame
1089
1090
  // Determine blockSize dynamically based on the frame resolution
1091
0
  int blockSize = BLK_8;
1092
0
  uint32_t picSizeInLumaSamples = m_workingBuf->Y().height * m_workingBuf->Y().width;
1093
0
  if ( picSizeInLumaSamples >= 7680 * 4320 )
1094
0
  {
1095
    // 8K resolution
1096
0
    blockSize = BLK_32;
1097
0
  }
1098
0
  else if ( picSizeInLumaSamples >= 3840 * 2160 )
1099
0
  {
1100
    // 4K resolution
1101
0
    blockSize = BLK_16;
1102
0
  }
1103
0
  else
1104
0
  {
1105
0
    blockSize = BLK_8;
1106
0
  }
1107
1108
0
  findMask( COMP_Y );                                                       // Generate mask for luma only
1109
1110
  // find difference between original and filtered/reconstructed frame => film grain estimate
1111
0
  m_grainEstimateBuf->subtract( pic->getOrigBuffer(),
1112
0
                                pic->getFilteredOrigBuffer() );
1113
1114
0
  for ( int compIdx = 0; compIdx < getNumberValidComponents( m_inputChromaFormat ); compIdx++ )
1115
0
  {
1116
0
    ComponentID  compID          = ComponentID( compIdx );
1117
0
    uint32_t     width           = m_workingBuf->getBuf( compID ).width;    // Width of current frame
1118
0
    uint32_t     height          = m_workingBuf->getBuf( compID ).height;   // Height of current frame
1119
0
    uint32_t     windowSize      = DATA_BASE_SIZE;                          // Size for Film Grain block
1120
0
    int          bitDepth        = m_bitDepths[toChannelType( compID )];
1121
0
    int          detect_edges    = 0;
1122
0
    int          mean            = 0;
1123
0
    int          var             = 0;
1124
0
    m_numDctGrainBlocks          = 0;
1125
1126
    // Clear vectors before computing for each component
1127
0
    vecMean.clear();
1128
0
    vecVar.clear();
1129
0
    tmp_data_x.clear();
1130
0
    tmp_data_y.clear();
1131
0
    scalingVec.clear();
1132
0
    quantVec.clear();
1133
0
    coeffs.clear();
1134
1135
    // Only whole windows are analyzed. The bounds add instead of subtract because width - windowSize is unsigned
1136
    // and wraps around for a plane smaller than the window, e.g. the 60-row chroma of a 208x120 4:2:0 picture.
1137
0
    for ( int i = 0; i + windowSize <= width; i += windowSize )
1138
0
    { // loop over windowSize x windowSize blocks
1139
0
      for ( int j = 0; j + windowSize <= height; j += windowSize )
1140
0
      {
1141
0
        if ( compID == COMP_Y )
1142
0
        {
1143
0
          detect_edges = countEdges ( windowSize,
1144
0
                                      i,
1145
0
                                      j,
1146
0
                                      compID );  // for flat region without edges
1147
0
        }
1148
0
        else
1149
0
        {
1150
0
          detect_edges = 1;                      // always process for chroma
1151
0
        }
1152
0
        if ( detect_edges )   // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance.
1153
0
        { // find transformed blocks; cut-off frequency estimation is done on 64 x 64 blocks as low-pass filtering on synthesis side is done on 64 x 64 blocks.
1154
0
          CoeffBuf& currentCoeffBuf = m_dctGrainBlockList[m_numDctGrainBlocks++];
1155
0
          blockTransform ( currentCoeffBuf,
1156
0
                           i,
1157
0
                           j,
1158
0
                           bitDepth,
1159
0
                           compID );
1160
0
        }
1161
1162
0
        int step = windowSize / blockSize;
1163
0
        for ( int k = 0; k < step; k++ )
1164
0
        {
1165
0
          for ( int m = 0; m < step; m++ )
1166
0
          {
1167
0
            if ( compID == COMP_Y )
1168
0
            {
1169
0
              detect_edges = countEdges ( blockSize,
1170
0
                                          i + k * blockSize,
1171
0
                                          j + m * blockSize,
1172
0
                                          compID );   // for flat region without edges
1173
0
            }
1174
0
            else
1175
0
            {
1176
0
              detect_edges = 1;  // always process for chroma
1177
0
            }
1178
0
            if ( detect_edges )   // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance.
1179
0
            {
1180
              // collect all data for parameter estimation; mean and variance are caluclated on blockSize x blockSize blocks
1181
0
              uint32_t stride = m_grainEstimateBuf->get( compID ).stride;
1182
0
              double varD = calcVar ( m_grainEstimateBuf->get( compID ).buf + ( ( j + m * blockSize ) * stride ) + i + ( k * blockSize ),
1183
0
                                      stride,
1184
0
                                      blockSize,
1185
0
                                      blockSize );
1186
0
              varD = varD / (( blockSize * blockSize ));
1187
0
              var = static_cast<int>( varD + 0.5 );
1188
0
              stride = m_workingBuf->get( compID ).stride;
1189
0
              mean = calcMean ( m_workingBuf->get( compID ).buf + ( ( j + m * blockSize ) * stride ) + i + ( k * blockSize ),
1190
0
                                stride,
1191
0
                                blockSize,
1192
0
                                blockSize );
1193
0
              mean = static_cast<int>(static_cast<double>( mean ) / ( blockSize * blockSize ) + 0.5 );
1194
1195
              // regularize high variations; controls excessively fluctuating points
1196
0
              double tmp = 2.75 * pow( static_cast<double>( var ), 0.5 ) + 0.5;
1197
0
              var = static_cast<int>( tmp ); 
1198
              // limit data points to meaningful values. higher variance can be result of not perfect mask estimation (non-flat regions fall in estimation process)
1199
0
              if ( var < ( MAX_REAL_SCALE << ( bitDepth - BIT_DEPTH_8 ) ) )
1200
0
              {
1201
0
                vecMean.push_back( mean );    // mean of the filtered frame
1202
0
                vecVar.push_back( var );      // variance of the film grain estimate
1203
0
              }
1204
0
            }
1205
0
          }
1206
0
        }
1207
0
      }
1208
0
    }
1209
1210
    // calculate film grain parameters
1211
0
    estimateCutoffFreqAdaptive( compID );
1212
0
    estimateScalingFactors ( bitDepth,
1213
0
                             compID );
1214
1215
    // Clear vectors after estimation
1216
0
    vecMean.clear();
1217
0
    vecVar.clear();
1218
0
    finalIntervalsandScalingFactors.clear();
1219
0
  }
1220
0
}
1221
1222
/* This function calculates the scaling factors for film grain by analyzing the variance of intensity intervals.
1223
 * The primary steps include fitting a polynomial regression function to the intensity - variance data points,
1224
 * smoothing the resulting scaling function, and performing Lloyd - Max quantization to derive the final scaling factors.
1225
 * The estimated parameters are then set for each intensity interval.*/
1226
void FGAnalyzer::estimateScalingFactors ( uint32_t bitDepth,
1227
                                          ComponentID compId )
1228
0
{
1229
  // if cutoff frequencies are not estimated previously, do not proceed since presentFlag is set to false in a previous step
1230
0
  if ( !m_compModel[compId].presentFlag || vecMean.size() < MIN_POINTS_FOR_INTENSITY_ESTIMATION )
1231
0
  {
1232
0
    return;   // If there is no enough points to estimate film grain intensities, default or previously estimated
1233
              // parameters are used
1234
0
  }
1235
1236
0
  double              distortion = 0.0;
1237
1238
  // Fit the points with the curve and perform Lloyd Max quantization.
1239
0
  bool valid;
1240
0
  for ( int i = 0; i < NUM_PASSES; i++ )   // if num_passes = 2, filtering of the dataset points is performed
1241
0
  {
1242
0
    valid = fitFunction ( ORDER,
1243
0
                          bitDepth,
1244
0
                          i );   // n-th order polynomial regression for scaling function estimation
1245
0
    if ( !valid )
1246
0
    {
1247
0
      coeffs.clear();
1248
0
      scalingVec.clear();
1249
0
      quantVec.clear();
1250
0
      break;
1251
0
    }
1252
0
  }
1253
1254
0
  if ( valid )
1255
0
  {
1256
0
    avgScalingVec ( bitDepth );   // scale with previously fitted function to smooth the intensity
1257
0
    valid = lloydMax ( distortion,
1258
0
                       bitDepth );   // train quantizer and quantize curve using Lloyd Max
1259
0
  }
1260
1261
  // Based on quantized intervals, set intensity region and scaling parameter
1262
0
  if ( valid )   // if not valid, reuse previous parameters (for example, if var is all zero)
1263
0
  {
1264
0
    setEstimatedParameters ( bitDepth,
1265
0
                             compId );
1266
0
  }
1267
1268
0
  coeffs.clear();
1269
0
  scalingVec.clear();
1270
0
  quantVec.clear();
1271
1272
0
}
1273
1274
/*This function divides the specified range(rows or columns) of the `meanSquaredDctGrain` matrix into bins
1275
* and calculates the average value of each bin.If the average value of a bin exceeds the given threshold,
1276
* the bin is considered significant and its starting index is recorded in the `significantIndices` vector.
1277
* The function can be used to adaptively refine the search for significant values in the matrix by focusing
1278
* on specific rows or columns iteratively.*/
1279
void FGAnalyzer::adaptiveSampling ( int bins,
1280
                                    double threshold,
1281
                                    std::vector<int>& significantIndices,
1282
                                    bool isRow,
1283
                                    int startIdx )
1284
0
{
1285
0
  int binSize = DATA_BASE_SIZE / bins;
1286
0
  for ( int i = 0; i < bins; i++ )
1287
0
  {
1288
0
    double sum = 0;
1289
0
    for ( int j = 0; j < binSize; j++ )
1290
0
    {
1291
0
      int idx = startIdx + i * binSize + j;
1292
0
      if ( idx >= DATA_BASE_SIZE )
1293
0
          break;  // Ensure we don't go out of bounds
1294
0
      sum += isRow ? meanSquaredDctGrain[idx][0] : meanSquaredDctGrain[0][idx];
1295
0
    }
1296
0
    sum /= binSize;
1297
0
    if ( sum > threshold )
1298
0
    {
1299
0
      significantIndices.push_back( startIdx + i * binSize );
1300
0
    }
1301
0
  }
1302
0
}
1303
1304
1305
/*This function refines the cutoff frequency estimation by adaptively sampling the mean squared DCT grain values
1306
 * matrix. Instead of analyzing every row and column, it focuses on significant bins determined by the adaptive sampling
1307
 * method. The horizontal and vertical cutoff frequencies are estimated by examining the mean values of these significant
1308
 * bins, making the process more efficient and reducing computational overhead.
1309
 * The function performs the following steps :
1310
 * 1. Initializes mean squared DCT grain matrix and mean vectors for rows and columns.
1311
 * 2. Iterates through the DCT grain blocks to calculate the average block for each coefficient.
1312
 * 3. Uses the adaptive sampling method to identify significant rows and columns.
1313
 * 4. Estimates the cutoff frequencies based on the mean values of the significant rows and columns.
1314
 * 5. Updates the component model with the estimated cutoff frequencies.*/
1315
void FGAnalyzer::estimateCutoffFreqAdaptive( ComponentID compId )
1316
0
{
1317
0
  const int coarseBins = 8; // Initial coarse sampling bins
1318
0
  const int refineBins = 4; // Bins for each refinement step
1319
0
  const int maxIterations = 3; // Maximum refinement iterations
1320
0
  const double threshold = 0.1; // Threshold to identify significant bins
1321
1322
0
  std::memset( meanSquaredDctGrain, 0, sizeof( meanSquaredDctGrain ) );
1323
1324
  // Calculate mean squared DCT grain values
1325
0
  for ( int x = 0; x < DATA_BASE_SIZE; x++ )
1326
0
  {
1327
0
    for ( int y = 0; y < DATA_BASE_SIZE; y++ )
1328
0
    {
1329
0
      for ( int i = 0; i < m_numDctGrainBlocks; i++ )
1330
0
      {
1331
0
        meanSquaredDctGrain[x][y] += m_dctGrainBlockList[i].at( x, y );
1332
0
      }
1333
0
      meanSquaredDctGrain[x][y] /= m_numDctGrainBlocks;
1334
0
    }
1335
0
  }
1336
1337
  // Identify initial coarse bins with significant grain values
1338
0
  std::vector<int> significantRows, significantCols;
1339
0
  adaptiveSampling ( coarseBins,
1340
0
                     threshold,
1341
0
                     significantRows,
1342
0
                     true,
1343
0
                     0 ); // Rows
1344
0
  adaptiveSampling ( coarseBins,
1345
0
                     threshold,
1346
0
                     significantCols,
1347
0
                     false,
1348
0
                     0 );  // Columns
1349
1350
  // Iterative Refinement
1351
0
  for ( int iter = 0; iter < maxIterations; iter++ )
1352
0
  {
1353
0
    std::vector<int> refinedRows, refinedCols;
1354
0
    for ( int row : significantRows )
1355
0
    {
1356
0
      adaptiveSampling ( refineBins,
1357
0
                         threshold,
1358
0
                         refinedRows,
1359
0
                         true,
1360
0
                         row );
1361
0
    }
1362
0
    for ( int col : significantCols )
1363
0
    {
1364
0
      adaptiveSampling ( refineBins,
1365
0
                         threshold,
1366
0
                         refinedCols,
1367
0
                         false,
1368
0
                         col );
1369
0
    }
1370
0
    significantRows = refinedRows;
1371
0
    significantCols = refinedCols;
1372
0
  }
1373
1374
  // Determine cut-off frequencies from the refined significant bins
1375
0
  int cutoffVertical = significantRows.empty() ? 0 : significantRows.back() / ( DATA_BASE_SIZE / 16 );
1376
0
  int cutoffHorizontal = significantCols.empty() ? 0 : significantCols.back() / ( DATA_BASE_SIZE / 16 );
1377
1378
  // Set the cut-off frequencies in the model
1379
0
  if ( cutoffVertical && cutoffHorizontal )
1380
0
  {
1381
0
    m_compModel[compId].presentFlag = true;
1382
0
    m_compModel[compId].numModelValues = 3;
1383
0
    m_compModel[compId].intensityValues[0].compModelValue[1] = cutoffHorizontal;
1384
0
    m_compModel[compId].intensityValues[0].compModelValue[2] = cutoffVertical;
1385
0
  }
1386
0
  else
1387
0
  {
1388
0
    m_compModel[compId].presentFlag = false;
1389
0
  }
1390
0
}
1391
1392
// DCT-2 64x64 as defined in VVC
1393
void FGAnalyzer::blockTransform ( CoeffBuf &currentCoeffBuf,
1394
                                  int offsetX,
1395
                                  int offsetY,
1396
                                  uint32_t bitDepth,
1397
                                  ComponentID compId )
1398
0
{
1399
0
  uint32_t      windowSize      = DATA_BASE_SIZE;   // Size for Film Grain block
1400
0
  const int     transform_scale = 9;                // upscaling of original transform as specified in VVC (for 64x64 block)
1401
1402
  // copy input -> 32 Bit
1403
0
  for ( uint32_t y = 0; y < DATA_BASE_SIZE; y++ )
1404
0
  {
1405
0
    for ( uint32_t x = 0; x < DATA_BASE_SIZE; x++ )
1406
0
    {
1407
0
      m_DCTinout[x + DATA_BASE_SIZE * y] = m_grainEstimateBuf->get( compId ).at( offsetX + x,
1408
0
                                                                                 offsetY + y );
1409
0
    }
1410
0
  }
1411
1412
0
  fastForwardDCT2_B64 ( m_DCTinout,
1413
0
                        m_DCTtemp,
1414
0
                        transform_scale,
1415
0
                        windowSize,
1416
0
                        0,
1417
0
                        0 );
1418
0
  fastForwardDCT2_B64 ( m_DCTtemp,
1419
0
                        m_DCTinout,
1420
0
                        transform_scale,
1421
0
                        windowSize,
1422
0
                        0,
1423
0
                        0 );
1424
1425
  // Calculate squared transformed block
1426
0
  for ( int y = 0; y < DATA_BASE_SIZE; y++ )
1427
0
  {
1428
0
    for ( int x = 0; x < DATA_BASE_SIZE; x++ )
1429
0
    {
1430
0
      currentCoeffBuf.at( x, y ) = m_DCTinout[x + DATA_BASE_SIZE * y] * m_DCTinout[x + DATA_BASE_SIZE * y];
1431
0
    }
1432
0
  }
1433
0
}
1434
1435
// check edges
1436
int FGAnalyzer::countEdges ( int windowSize,
1437
                             int offsetX,
1438
                             int offsetY, 
1439
                             ComponentID compId )
1440
0
{
1441
0
  for ( int x = 0; x < windowSize; x++ )
1442
0
  {
1443
0
    for ( int y = 0; y < windowSize; y++ )
1444
0
    {
1445
0
      if ( m_maskBuf->get( compId ).at( offsetX + x,
1446
0
                                        offsetY + y ) )
1447
0
      {
1448
0
        return 0;
1449
0
      }
1450
0
    }
1451
0
  }
1452
1453
0
  return 1;
1454
0
}
1455
1456
// Fit data to a function using n-th order polynomial interpolation
1457
bool FGAnalyzer::fitFunction ( int order,
1458
                               int bitDepth,
1459
                               bool second_pass )
1460
0
{
1461
0
  long double         a[MAXPAIRS + 1][MAXPAIRS + 1];
1462
0
  long double         B[MAXPAIRS + 1], C[MAXPAIRS + 1], S[MAXPAIRS + 1];
1463
0
  long double         A1 = 0.0, A2 = 0.0, Y1 = 0.0, m = 0.0, S1 = 0.0, x1 = 0.0;
1464
0
  long double         xscale = 0.0, yscale = 0.0;
1465
0
  long double         xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0;
1466
0
  long double         polycoefs[MAXORDER + 1];
1467
0
  int i, j, k, L, R;
1468
1469
  // several data filtering and data manipulations before fitting the function
1470
  // create interval points for function fitting
1471
0
  int INTENSITY_INTERVAL_NUMBER = (1 << bitDepth) / INTERVAL_SIZE;
1472
0
  vec_mean_intensity.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1473
0
  vec_variance_intensity.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1474
0
  element_number_per_interval.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1475
1476
0
  double              mn = 0.0, sd = 0.0;
1477
1478
0
  std::memset( a, 0, sizeof(a) );
1479
0
  std::memset( B, 0, sizeof(B) );
1480
0
  std::memset( C, 0, sizeof(C) );
1481
0
  std::memset( S, 0, sizeof(S) );
1482
0
  std::memset( polycoefs, 0, sizeof(polycoefs) );
1483
1484
0
  if ( second_pass )   // in second pass, filter based on the variance of the data_y. remove all high and low points
1485
0
  {
1486
0
    xmin = scalingVec.back();
1487
0
    scalingVec.pop_back();
1488
0
    xmax = scalingVec.back();
1489
0
    scalingVec.pop_back();
1490
0
    int n = static_cast<int>( vecVar.size() );
1491
0
    if ( n != 0 )
1492
0
    {
1493
0
      mn = std::accumulate ( vecVar.begin(), vecVar.end(), 0.0 ) / n;
1494
0
      for ( int cnt = 0; cnt < n; cnt++ )
1495
0
      {
1496
0
        sd += ( vecVar[cnt] - mn ) * ( vecVar[cnt] - mn );
1497
0
      }
1498
0
      sd /= n;
1499
0
      sd = std::sqrt( sd );
1500
0
    }
1501
0
  }
1502
1503
0
  for ( int cnt = 0; cnt < vecMean.size(); cnt++ )
1504
0
  {
1505
0
    if ( second_pass )
1506
0
    {
1507
0
      if ( vecMean[cnt] >= xmin && vecMean[cnt] <= xmax )
1508
0
      {
1509
0
        if (( vecVar[cnt] < scalingVec[vecMean[cnt] - static_cast<int>(xmin)] + sd * VAR_SCALE_UP ) && ( vecVar[cnt] > scalingVec[vecMean[cnt] - static_cast<int>(xmin)] - sd * VAR_SCALE_DOWN ))
1510
0
        {
1511
0
          int block_index = vecMean[cnt] / INTERVAL_SIZE;
1512
0
          vec_mean_intensity[block_index] += vecMean[cnt];
1513
0
          vec_variance_intensity[block_index] += vecVar[cnt];
1514
0
          element_number_per_interval[block_index]++;
1515
0
        }
1516
0
      }
1517
0
    }
1518
0
    else
1519
0
    {
1520
0
      int block_index = vecMean[cnt] / INTERVAL_SIZE;
1521
0
      vec_mean_intensity[block_index] += vecMean[cnt];
1522
0
      vec_variance_intensity[block_index] += vecVar[cnt];
1523
0
      element_number_per_interval[block_index]++;
1524
0
    }
1525
0
  }
1526
1527
  // create points per intensity interval
1528
0
  for ( int block_idx = 0; block_idx < INTENSITY_INTERVAL_NUMBER; block_idx++ )
1529
0
  {
1530
0
    if ( element_number_per_interval[block_idx] >= MIN_ELEMENT_NUMBER_PER_INTENSITY_INTERVAL )
1531
0
    {
1532
0
      tmp_data_x.push_back ( vec_mean_intensity[block_idx] / element_number_per_interval[block_idx] );
1533
0
      tmp_data_y.push_back( vec_variance_intensity[block_idx] / element_number_per_interval[block_idx] );
1534
0
    }
1535
0
  }
1536
1537
  // There needs to be at least ORDER+1 points to fit the function
1538
0
  if ( tmp_data_x.size() < ( order + 1 ) )
1539
0
  {
1540
0
    return false;   // if there is no enough blocks to estimate film grain parameters, default or previously estimated
1541
                    // parameters are used
1542
0
  }
1543
1544
0
  for ( i = 0; i < tmp_data_x.size(); i++ ) // remove single points before extending and fitting
1545
0
  {
1546
0
    int check = 0;
1547
0
    for ( j = -WINDOW; j <= WINDOW; j++ )
1548
0
    {
1549
0
      int idx = i + j;
1550
0
      if ( idx >= 0 && idx < tmp_data_x.size() && j != 0 )
1551
0
      {
1552
0
        check += abs( tmp_data_x[i] / INTERVAL_SIZE - tmp_data_x[idx] / INTERVAL_SIZE ) <= WINDOW ? 1 : 0;
1553
0
      }
1554
0
    }
1555
1556
0
    if ( check < NBRS )
1557
0
    {
1558
0
      for ( int k = i; k < tmp_data_x.size() - 1; k++ )
1559
0
      {
1560
0
        tmp_data_x[k] = tmp_data_x[k + 1];
1561
0
        tmp_data_y[k] = tmp_data_y[k + 1];
1562
0
      }
1563
0
      tmp_data_x.pop_back();
1564
0
      tmp_data_y.pop_back();
1565
0
      i--;
1566
0
    }
1567
0
  }
1568
1569
0
  extendPoints( bitDepth );     // find the most left and the most right point, and extend edges
1570
1571
0
  CHECK( tmp_data_x.size() > MAXPAIRS, "Maximum dataset size exceeded." );
1572
1573
  // fitting the function starts here
1574
0
  xmin = tmp_data_x[0];
1575
0
  xmax = tmp_data_x[0];
1576
0
  ymin = tmp_data_y[0];
1577
0
  ymax = tmp_data_y[0];
1578
0
  for ( i = 0; i < tmp_data_x.size(); i++ )
1579
0
  {
1580
0
    if ( tmp_data_x[i] < xmin )
1581
0
    {
1582
0
      xmin = tmp_data_x[i];
1583
0
    }
1584
0
    if ( tmp_data_x[i] > xmax )
1585
0
    {
1586
0
      xmax = tmp_data_x[i];
1587
0
    }
1588
0
    if ( tmp_data_y[i] < ymin )
1589
0
    {
1590
0
      ymin = tmp_data_y[i];
1591
0
    }
1592
0
    if ( tmp_data_y[i] > ymax )
1593
0
    {
1594
0
      ymax = tmp_data_y[i];
1595
0
    }
1596
0
  }
1597
1598
0
  long double xlow = xmax;
1599
0
  long double ylow = ymax;
1600
1601
0
  int data_pairs = static_cast<int>( tmp_data_x.size() );
1602
1603
0
  double data_array[2][MAXPAIRS + 1];
1604
0
  std::memset( data_array, 0, sizeof(data_array) );
1605
0
  for ( i = 0; i < data_pairs; i++ )
1606
0
  {
1607
0
    data_array[0][i + 1] = static_cast<double>( tmp_data_x[i] );
1608
0
    data_array[1][i + 1] = static_cast<double>( tmp_data_y[i] );
1609
0
  }
1610
1611
  // Clear previous vectors by resizing them to 0
1612
0
  tmp_data_x.clear();
1613
0
  tmp_data_y.clear();
1614
1615
0
  if ( second_pass )
1616
0
  {
1617
0
    coeffs.resize( 0 );
1618
0
    scalingVec.resize( 0 );
1619
0
  }
1620
1621
0
  for ( i = 1; i <= data_pairs; i++ )
1622
0
  {
1623
0
    if ( data_array[0][i] < xlow && data_array[0][i] != 0 )
1624
0
    {
1625
0
      xlow = data_array[0][i];
1626
0
    }
1627
0
    if ( data_array[1][i] < ylow && data_array[1][i] != 0 )
1628
0
    {
1629
0
      ylow = data_array[1][i];
1630
0
    }
1631
0
  }
1632
1633
0
  if ( xlow < .001 && xmax < 1000 )
1634
0
  {
1635
0
    xscale = 1 / xlow;
1636
0
  }
1637
0
  else if ( xmax > 1000 && xlow > .001 )
1638
0
  {
1639
0
    xscale = 1 / xmax;
1640
0
  }
1641
0
  else
1642
0
  {
1643
0
    xscale = 1;
1644
0
  }
1645
1646
0
  if ( ylow < .001 && ymax < 1000 )
1647
0
  {
1648
0
    yscale = 1 / ylow;
1649
0
  }
1650
0
  else if ( ymax > 1000 && ylow > .001 )
1651
0
  {
1652
0
    yscale = 1 / ymax;
1653
0
  }
1654
0
  else
1655
0
  {
1656
0
    yscale = 1;
1657
0
  }
1658
1659
  // initialise array variables
1660
0
  for ( j = 1; j <= data_pairs; j++ )
1661
0
  {
1662
0
    for ( i = 1; i <= order; i++ )
1663
0
    {
1664
0
      B[i] = B[i] + data_array[1][j] * yscale * ldpow( data_array[0][j] * xscale, i );
1665
0
      if ( B[i] == std::numeric_limits<long double>::max() )
1666
0
      {
1667
0
        return false;
1668
0
      }
1669
0
      for ( k = 1; k <= order; k++ )
1670
0
      {
1671
0
        a[i][k] = a[i][k] + ldpow( data_array[0][j] * xscale, ( i + k ) );
1672
0
        if ( a[i][k] == std::numeric_limits<long double>::max() )
1673
0
        {
1674
0
          return false;
1675
0
        }
1676
0
      }
1677
0
      S[i] = S[i] + ldpow( data_array[0][j] * xscale, i );
1678
0
      if ( S[i] == std::numeric_limits<long double>::max() )
1679
0
      {
1680
0
        return false;
1681
0
      }
1682
0
    }
1683
0
    Y1 = Y1 + data_array[1][j] * yscale;
1684
0
    if ( Y1 == std::numeric_limits<long double>::max() )
1685
0
    {
1686
0
      return false;
1687
0
    }
1688
0
  }
1689
1690
0
  for ( i = 1; i <= order; i++ )
1691
0
  {
1692
0
    for ( j = 1; j <= order; j++ )
1693
0
    {
1694
0
      a[i][j] = a[i][j] - S[i] * S[j] / static_cast<long double>( data_pairs );
1695
0
      if (a[i][j] == std::numeric_limits<long double>::max())
1696
0
      {
1697
0
        return false;
1698
0
      }
1699
0
    }
1700
0
    B[i] = B[i] - Y1 * S[i] / static_cast<long double>( data_pairs );
1701
0
    if ( B[i] == std::numeric_limits<long double>::max() )
1702
0
    {
1703
0
      return false;
1704
0
    }
1705
0
  }
1706
1707
0
  for ( k = 1; k <= order; k++ )
1708
0
  {
1709
0
    R  = k;
1710
0
    A1 = 0;
1711
0
    for ( L = k; L <= order; L++ )
1712
0
    {
1713
0
      A2 = fabsl( a[L][k] );
1714
0
      if ( A2 > A1 )
1715
0
      {
1716
0
        A1 = A2;
1717
0
        R  = L;
1718
0
      }
1719
0
    }
1720
0
    if ( A1 == 0 )
1721
0
    {
1722
0
      return false;
1723
0
    }
1724
0
    if ( R != k )
1725
0
    {
1726
0
      for ( j = k; j <= order; j++ )
1727
0
      {
1728
0
        x1      = a[R][j];
1729
0
        a[R][j] = a[k][j];
1730
0
        a[k][j] = x1;
1731
0
      }
1732
0
      x1   = B[R];
1733
0
      B[R] = B[k];
1734
0
      B[k] = x1;
1735
0
    }
1736
0
    for ( i = k; i <= order; i++ )
1737
0
    {
1738
0
      m = a[i][k];
1739
0
      for ( j = k; j <= order; j++ )
1740
0
      {
1741
0
        if ( i == k )
1742
0
        {
1743
0
          a[i][j] = a[i][j] / m;
1744
0
        }
1745
0
        else
1746
0
        {
1747
0
          a[i][j] = a[i][j] - m * a[k][j];
1748
0
        }
1749
0
      }
1750
0
      if ( i == k )
1751
0
      {
1752
0
        B[i] = B[i] / m;
1753
0
      }
1754
0
      else
1755
0
      {
1756
0
        B[i] = B[i] - m * B[k];
1757
0
      }
1758
0
    }
1759
0
  }
1760
1761
0
  polycoefs[order] = B[order];
1762
0
  for ( k = 1; k <= order - 1; k++ )
1763
0
  {
1764
0
    i  = order - k;
1765
0
    S1 = 0;
1766
0
    for ( j = 1; j <= order; j++ )
1767
0
    {
1768
0
      S1 = S1 + a[i][j] * polycoefs[j];
1769
0
      if ( S1 == std::numeric_limits<long double>::max() )
1770
0
      {
1771
0
        return false;
1772
0
      }
1773
0
    }
1774
0
    polycoefs[i] = B[i] - S1;
1775
0
  }
1776
1777
0
  S1 = 0;
1778
0
  for ( i = 1; i <= order; i++ )
1779
0
  {
1780
0
    S1 = S1 + polycoefs[i] * S[i] / static_cast<long double>( data_pairs );
1781
0
    if ( S1 == std::numeric_limits<long double>::max() )
1782
0
    {
1783
0
      return false;
1784
0
    }
1785
0
  }
1786
0
  polycoefs[0] = (Y1 / static_cast<long double>( data_pairs ) - S1);
1787
1788
  // zero all coeficient values smaller than +/- .00000000001 (avoids -0)
1789
0
  for ( i = 0; i <= order; i++ )
1790
0
  {
1791
0
    if ( fabsl(polycoefs[i] * 100000000000) < 1 )
1792
0
    {
1793
0
      polycoefs[i] = 0;
1794
0
    }
1795
0
  }
1796
1797
  // rescale parameters
1798
0
  for ( i = 0; i <= order; i++ )
1799
0
  {
1800
0
    polycoefs[i] = (1 / yscale) * polycoefs[i] * ldpow( xscale, i );
1801
0
    coeffs.push_back( polycoefs[i] );
1802
0
  }
1803
1804
  // create fg scaling function. interpolation based on coeffs which returns lookup table from 0 - 2^B-1. n-th order polinomial regression
1805
0
  for ( i = static_cast<int>( xmin ); i <= static_cast<int>( xmax ); i++ )
1806
0
  {
1807
0
    double val = coeffs[0];
1808
0
    for ( j = 1; j < coeffs.size(); j++ )
1809
0
    {
1810
0
      val += (coeffs[j] * ldpow( i, j ));
1811
0
    }
1812
1813
0
    val = Clip3( 0.0,
1814
0
                 static_cast<double>( 1 << bitDepth ) - 1,
1815
0
                 val );
1816
0
    scalingVec.push_back( val );
1817
0
  }
1818
1819
  // save in scalingVec min and max value for further use
1820
0
  scalingVec.push_back( xmax );
1821
0
  scalingVec.push_back( xmin );
1822
1823
0
  vec_mean_intensity.clear();
1824
0
  vec_variance_intensity.clear();
1825
0
  element_number_per_interval.clear();
1826
0
  tmp_data_x.clear();
1827
0
  tmp_data_y.clear();
1828
1829
0
  return true;
1830
0
}
1831
1832
// avg scaling vector with previous result to smooth transition betweeen frames
1833
void FGAnalyzer::avgScalingVec ( int bitDepth )
1834
0
{
1835
0
  int xmin = static_cast<int>( scalingVec.back() );
1836
0
  scalingVec.pop_back();
1837
0
  int xmax = static_cast<int>( scalingVec.back() );
1838
0
  scalingVec.pop_back();
1839
1840
0
  std::vector<double> scalingVecAvg( static_cast<int>( 1 << bitDepth ) );
1841
0
  bool isFirstScalingEst = true;
1842
1843
0
  if ( isFirstScalingEst )
1844
0
  {
1845
0
    for (int i = xmin; i <= xmax; i++)
1846
0
    {
1847
0
      scalingVecAvg[i] = scalingVec[i - xmin];
1848
0
    }
1849
0
    isFirstScalingEst = false;
1850
0
  }
1851
0
  else
1852
0
  {
1853
0
    for ( int i = xmin; i <= xmax; i++ )
1854
0
    {
1855
0
      scalingVecAvg[i] = ( scalingVecAvg[i] + scalingVec[i - xmin] ) / 2.0;
1856
0
    }
1857
0
  }
1858
1859
0
  int new_xmin = 0;
1860
0
  while ( new_xmin <= xmax && scalingVecAvg[new_xmin] == 0 )
1861
0
  {
1862
0
    new_xmin++;
1863
0
  }
1864
1865
0
  int new_xmax = static_cast<int>( scalingVecAvg.size() ) - 1;
1866
0
  while ( new_xmax >= 0 && scalingVecAvg[new_xmax] == 0 )
1867
0
  {
1868
0
    new_xmax--;
1869
0
  }
1870
1871
0
  if ( new_xmax < new_xmin )
1872
0
  {
1873
    // Handle the case where all entries are zero
1874
0
    scalingVec.clear();
1875
0
    scalingVec.push_back( 0 ); // Minimum value
1876
0
    scalingVec.push_back( 0 ); // Maximum value
1877
0
    return;
1878
0
  }
1879
1880
0
  scalingVec.assign( scalingVecAvg.begin() + new_xmin,
1881
0
                     scalingVecAvg.begin() + new_xmax + 1 );
1882
0
  scalingVec.push_back( new_xmax );
1883
0
  scalingVec.push_back( new_xmin );
1884
0
}
1885
1886
1887
// Lloyd Max quantizer
1888
bool FGAnalyzer::lloydMax ( double &distortion,
1889
                            int bitDepth )
1890
0
{
1891
0
  if ( !scalingVec.size() )
1892
0
  {
1893
    // Film grain parameter estimation is not performed. Default or previously estimated parameters are reused.
1894
0
    return false;
1895
0
  }
1896
1897
0
  int xmin = static_cast<int>( scalingVec.back() );
1898
0
  scalingVec.pop_back();
1899
0
  scalingVec.pop_back();   // dummy pop_pack ==> int xmax = (int)scalingVec.back();
1900
1901
0
  double ymin          = 0.0;
1902
0
  double ymax          = 0.0;
1903
0
  double init_training = 0.0;
1904
0
  double tolerance     = 0.0000001;
1905
0
  double last_distor   = 0.0;
1906
0
  double rel_distor    = 0.0;
1907
1908
0
  double codebook[QUANT_LEVELS];
1909
0
  double partition[QUANT_LEVELS - 1];
1910
1911
0
  std::vector<double> tmpVec( scalingVec.size(), 0.0 );
1912
0
  distortion = 0.0;
1913
1914
0
  ymin = scalingVec[0];
1915
0
  ymax = scalingVec[0];
1916
0
  for ( int i = 0; i < scalingVec.size(); i++ )
1917
0
  {
1918
0
    if ( scalingVec[i] < ymin )
1919
0
    {
1920
0
      ymin = scalingVec[i];
1921
0
    }
1922
0
    if ( scalingVec[i] > ymax )
1923
0
    {
1924
0
      ymax = scalingVec[i];
1925
0
    }
1926
0
  }
1927
1928
0
  init_training = ( ymax - ymin ) / QUANT_LEVELS;
1929
1930
0
  if ( init_training <= 0 )
1931
0
  {
1932
    // msg(WARNING, "Invalid training dataset. Film grain parameter estimation is not performed. Default or previously estimated parameters are reused.\n");
1933
0
    return false;
1934
0
  }
1935
1936
  // initial codebook
1937
0
  double step = init_training / 2;
1938
0
  for ( int i = 0; i < QUANT_LEVELS; i++ )
1939
0
  {
1940
0
    codebook[i] = ymin + i * init_training + step;
1941
0
  }
1942
1943
  // initial partition
1944
0
  for ( int i = 0; i < QUANT_LEVELS - 1; i++ )
1945
0
  {
1946
0
    partition[i] = (codebook[i] + codebook[i + 1]) / 2;
1947
0
  }
1948
1949
  // quantizer initialization
1950
0
  quantize ( tmpVec,
1951
0
             distortion,
1952
0
             partition,
1953
0
             codebook );
1954
1955
0
  double tolerance2 = std::numeric_limits<double>::epsilon() * ymax;
1956
0
  if ( distortion > tolerance2 )
1957
0
  {
1958
0
    rel_distor = std::fabs( distortion - last_distor ) / distortion;
1959
0
  }
1960
0
  else
1961
0
  {
1962
0
    rel_distor = distortion;
1963
0
  }
1964
1965
  // optimization: find optimal codebook and partition
1966
0
  while ( ( rel_distor > tolerance ) && ( rel_distor > tolerance2 ) )
1967
0
  {
1968
0
    for ( int i = 0; i < QUANT_LEVELS; i++ )
1969
0
    {
1970
0
      int count = 0;
1971
0
      double sum = 0.0;
1972
1973
0
      for ( int j = 0; j < tmpVec.size(); j++ )
1974
0
      {
1975
0
        if ( codebook[i] == tmpVec[j] )
1976
0
        {
1977
0
          count++;
1978
0
          sum += scalingVec[j];
1979
0
        }
1980
0
      }
1981
1982
0
      if ( count )
1983
0
      {
1984
0
        codebook[i] = sum / static_cast<double>( count );
1985
0
      }
1986
0
      else
1987
0
      {
1988
0
        sum   = 0.0;
1989
0
        count = 0;
1990
0
        if ( i == 0 )
1991
0
        {
1992
0
          for ( int j = 0; j < tmpVec.size(); j++ )
1993
0
          {
1994
0
            if ( scalingVec[j] <= partition[i] )
1995
0
            {
1996
0
              count++;
1997
0
              sum += scalingVec[j];
1998
0
            }
1999
0
          }
2000
0
          if ( count )
2001
0
          {
2002
0
            codebook[i] = sum / static_cast<double>( count );
2003
0
          }
2004
0
          else
2005
0
          {
2006
0
            codebook[i] = ( partition[i] + ymin ) / 2;
2007
0
          }
2008
0
        }
2009
0
        else if ( i == QUANT_LEVELS - 1 )
2010
0
        {
2011
0
          for ( int j = 0; j < tmpVec.size(); j++ )
2012
0
          {
2013
0
            if (scalingVec[j] >= partition[i - 1])
2014
0
            {
2015
0
              count++;
2016
0
              sum += scalingVec[j];
2017
0
            }
2018
0
          }
2019
0
          if ( count )
2020
0
          {
2021
0
            codebook[i] = sum / static_cast<double>( count );
2022
0
          }
2023
0
          else
2024
0
          {
2025
0
            codebook[i] = ( partition[i - 1] + ymax ) / 2;
2026
0
          }
2027
0
        }
2028
0
        else
2029
0
        {
2030
0
          for ( int j = 0; j < tmpVec.size(); j++ )
2031
0
          {
2032
0
            if ( scalingVec[j] >= partition[i - 1] && scalingVec[j] <= partition[i] )
2033
0
            {
2034
0
              count++;
2035
0
              sum += scalingVec[j];
2036
0
            }
2037
0
          }
2038
0
          if ( count )
2039
0
          {
2040
0
            codebook[i] = sum / static_cast<double>( count );
2041
0
          }
2042
0
          else
2043
0
          {
2044
0
            codebook[i] = ( partition[i - 1] + partition[i] ) / 2;
2045
0
          }
2046
0
        }
2047
0
      }
2048
0
    }
2049
2050
    // compute and sort partition
2051
0
    for ( int i = 0; i < QUANT_LEVELS - 1; i++ )
2052
0
    {
2053
0
      partition[i] = ( codebook[i] + codebook[i + 1] ) / 2;
2054
0
    }
2055
0
    std::sort( partition, partition + QUANT_LEVELS - 1 );
2056
2057
    // final quantization - testing condition
2058
0
    last_distor = distortion;
2059
0
    quantize ( tmpVec,
2060
0
               distortion,
2061
0
               partition,
2062
0
               codebook );
2063
2064
0
    if ( distortion > tolerance2 )
2065
0
    {
2066
0
      rel_distor = std::fabs( distortion - last_distor ) / distortion;
2067
0
    }
2068
0
    else
2069
0
    {
2070
0
      rel_distor = distortion;
2071
0
    }
2072
0
  }
2073
2074
  // fill the final quantized vector
2075
0
  int maxVal = ( 1 << bitDepth ) - 1;  // Full range max value for given bit depth
2076
0
  quantVec.resize( static_cast<int>( 1 << bitDepth ), 0 );
2077
0
  for ( int i = 0; i < tmpVec.size(); i++ )
2078
0
  {
2079
0
    quantVec[i + xmin] = Clip3( 0, 
2080
0
                                maxVal,                                    
2081
0
                                static_cast<int>( tmpVec[i] + 0.5 ) );
2082
0
  }
2083
2084
0
  return true;
2085
0
}
2086
2087
void FGAnalyzer::quantize ( std::vector<double>& quantizedVec,
2088
                            double& distortion,
2089
                            double partition[],
2090
                            double codebook[] )
2091
0
{
2092
  // Reset previous quantizedVec to 0 and distortion to 0
2093
0
  std::fill(quantizedVec.begin(), quantizedVec.end(), 0.0);
2094
0
  distortion = 0.0;
2095
2096
  // Quantize input vector
2097
0
  for ( int i = 0; i < scalingVec.size(); i++ )
2098
0
  {
2099
0
    double quantizedValue = 0.0;
2100
0
    for ( int j = 0; j < QUANT_LEVELS - 1; j++ )
2101
0
    {
2102
0
      quantizedValue += ( scalingVec[i] > partition[j] );
2103
0
    }
2104
0
    quantizedVec[i] = codebook[static_cast<int>( quantizedValue )];
2105
0
  }
2106
2107
  // Compute distortion (MSE)
2108
0
  for ( int i = 0; i < scalingVec.size(); i++ )
2109
0
  {
2110
0
    double error = scalingVec[i] - quantizedVec[i];
2111
0
    distortion += ( error * error );
2112
0
  }
2113
0
  distortion /= scalingVec.size();
2114
0
}
2115
2116
// Set correctlly SEI parameters based on the quantized curve
2117
void FGAnalyzer::setEstimatedParameters ( uint32_t bitDepth,
2118
                                          ComponentID compId )
2119
0
{
2120
  // calculate intervals and scaling factors
2121
0
  defineIntervalsAndScalings ( bitDepth );
2122
2123
  // Merge small intervals with left or right interval
2124
0
  for ( size_t i = 0; i < finalIntervalsandScalingFactors.size(); ++i )
2125
0
  {
2126
0
    int tmp1 = finalIntervalsandScalingFactors[i][1] - finalIntervalsandScalingFactors[i][0];
2127
2128
0
    if ( tmp1 < ( 2 << ( bitDepth - BIT_DEPTH_8 ) ) )
2129
0
    {
2130
0
      int diffRight = ( i == finalIntervalsandScalingFactors.size() - 1 ) || ( finalIntervalsandScalingFactors[i + 1][2] == 0 )
2131
0
          ? std::numeric_limits<int>::max()
2132
0
          : abs( finalIntervalsandScalingFactors[i][2] - finalIntervalsandScalingFactors[i + 1][2] );
2133
0
      int diffLeft = ( i == 0 ) || ( finalIntervalsandScalingFactors[i - 1][2] == 0 )
2134
0
          ? std::numeric_limits<int>::max()
2135
0
          : abs( finalIntervalsandScalingFactors[i][2] - finalIntervalsandScalingFactors[i - 1][2] );
2136
2137
0
      if ( diffLeft < diffRight )
2138
0
      {
2139
0
        int tmp2 = finalIntervalsandScalingFactors[i - 1][1] - finalIntervalsandScalingFactors[i - 1][0];
2140
0
        int newScale = ( tmp2 * finalIntervalsandScalingFactors[i - 1][2] + tmp1 * finalIntervalsandScalingFactors[i][2] ) / ( tmp2 + tmp1 );
2141
2142
0
        finalIntervalsandScalingFactors[i - 1][1] = finalIntervalsandScalingFactors[i][1];
2143
0
        finalIntervalsandScalingFactors[i - 1][2] = newScale;
2144
0
        finalIntervalsandScalingFactors.erase( finalIntervalsandScalingFactors.begin() + i );
2145
0
        --i;
2146
0
      }
2147
0
      else
2148
0
      {
2149
0
        int tmp2 = finalIntervalsandScalingFactors[i + 1][1] - finalIntervalsandScalingFactors[i + 1][0];
2150
0
        int newScale = ( tmp2 * finalIntervalsandScalingFactors[i + 1][2] + tmp1 * finalIntervalsandScalingFactors[i][2] ) / ( tmp2 + tmp1 );
2151
2152
0
        finalIntervalsandScalingFactors[i][1] = finalIntervalsandScalingFactors[i + 1][1];
2153
0
        finalIntervalsandScalingFactors[i][2] = newScale;
2154
0
        finalIntervalsandScalingFactors.erase( finalIntervalsandScalingFactors.begin() + i + 1 );
2155
0
        --i;
2156
0
      }
2157
0
    }
2158
0
  }
2159
2160
  // scale to 8-bit range as supported by current sei and rdd5
2161
0
  scaleDown ( bitDepth );
2162
2163
  // because of scaling in previous step, some intervals may overlap. Check intervals for errors.
2164
0
  confirmIntervals ( );
2165
2166
  // Set number of intervals; exclude intervals with scaling factor 0.
2167
0
  m_compModel[compId].numIntensityIntervals =
2168
0
      static_cast<uint8_t>( finalIntervalsandScalingFactors.size() - std::count_if ( finalIntervalsandScalingFactors.begin(),
2169
0
                                                                                     finalIntervalsandScalingFactors.end(),
2170
0
                                                                                     []( const std::array<int, 3>& interval )
2171
0
                                                                                     {
2172
0
                                                                                       return interval[2] == 0;
2173
0
                                                                                     }
2174
0
                                                                                   ) );
2175
2176
  // check if all intervals are 0, and if yes set presentFlag to false
2177
0
  if ( m_compModel[compId].numIntensityIntervals == 0 )
2178
0
  { 
2179
0
    m_compModel[compId].presentFlag = false;
2180
0
    return;
2181
0
  }
2182
2183
  // Set final interval boundaries and scaling factors.
2184
  // Check if some interval has scaling factor 0, and do not encode them within SEI.
2185
0
  int j = 0;
2186
0
  for ( const auto& interval : finalIntervalsandScalingFactors )
2187
0
  {
2188
0
    if ( interval[2] != 0 )
2189
0
    {
2190
0
      m_compModel[compId].intensityValues[j].intensityIntervalLowerBound = interval[0];
2191
0
      m_compModel[compId].intensityValues[j].intensityIntervalUpperBound = interval[1];
2192
0
      m_compModel[compId].intensityValues[j].compModelValue[0] = interval[2];
2193
0
      m_compModel[compId].intensityValues[j].compModelValue[1] = m_compModel[compId].intensityValues[0].compModelValue[1];
2194
0
      m_compModel[compId].intensityValues[j].compModelValue[2] = m_compModel[compId].intensityValues[0].compModelValue[2];
2195
0
      ++j;
2196
0
    }
2197
0
  }
2198
0
  CHECK( j != m_compModel[compId].numIntensityIntervals, "Check film grain intensity levels" );
2199
0
}
2200
2201
long double FGAnalyzer::ldpow ( long double n,
2202
                                unsigned p )
2203
0
{
2204
0
  long double result = 1.0;
2205
2206
  // Handle special cases for p = 0 and p = 1
2207
0
  if ( p == 0 ) return 1.0;
2208
0
  if ( p == 1 ) return n;
2209
2210
  // Exponentiation by squaring
2211
0
  while ( p > 0 )
2212
0
  {
2213
0
    if ( p % 2 == 1 )
2214
0
      result *= n;
2215
0
    n *= n;
2216
0
    p /= 2;
2217
0
  }
2218
0
  return result;
2219
0
}
2220
2221
// find bounds of intensity intervals and scaling factors for each interval
2222
void FGAnalyzer::defineIntervalsAndScalings ( int bitDepth )
2223
0
{
2224
0
  finalIntervalsandScalingFactors.clear();
2225
0
  std::array<int, 3> interval = { 0, 0, quantVec[0] };
2226
2227
0
  for ( int i = 0; i < (1 << bitDepth) - 1; ++i )
2228
0
  {
2229
0
    if ( quantVec[i] != quantVec[i + 1] )
2230
0
    {
2231
0
      interval[1] = i;
2232
0
      finalIntervalsandScalingFactors.push_back ( interval );
2233
0
      interval[0] = i + 1;
2234
0
      interval[2] = quantVec[i + 1];
2235
0
    }
2236
0
  }
2237
0
  interval[1] = ( 1 << bitDepth ) - 1;
2238
0
  finalIntervalsandScalingFactors.push_back ( interval );
2239
0
}
2240
2241
// scale everything to 8-bit ranges as supported by SEI message
2242
void FGAnalyzer::scaleDown ( int bitDepth )
2243
0
{
2244
0
  for ( auto& interval : finalIntervalsandScalingFactors )
2245
0
  {
2246
0
    interval[0] >>= ( bitDepth - BIT_DEPTH_8 );
2247
0
    interval[1] >>= ( bitDepth - BIT_DEPTH_8 );
2248
0
    interval[2] <<= m_log2ScaleFactor;
2249
0
    interval[2] >>= ( bitDepth - BIT_DEPTH_8 );
2250
0
  }
2251
0
}
2252
2253
// check if intervals are properly set after scaling to 8-bit representation
2254
void FGAnalyzer::confirmIntervals ( )
2255
0
{
2256
0
  for ( size_t i = 0; i < finalIntervalsandScalingFactors.size() - 1; ++i )
2257
0
  {
2258
0
    if ( finalIntervalsandScalingFactors[i][1] >= finalIntervalsandScalingFactors[i + 1][0] )
2259
0
    {
2260
0
      finalIntervalsandScalingFactors[i][1] = finalIntervalsandScalingFactors[i + 1][0] - 1;
2261
0
    }
2262
0
  }
2263
0
}
2264
2265
void FGAnalyzer::extendPoints ( int bitDepth )
2266
0
{
2267
0
  int xmin = tmp_data_x[0];
2268
0
  int xmax = tmp_data_x[0];
2269
0
  int ymin = tmp_data_y[0];
2270
0
  int ymax = tmp_data_y[0];
2271
0
  for ( int i = 0; i < tmp_data_x.size(); i++ )
2272
0
  {
2273
0
    if ( tmp_data_x[i] < xmin )
2274
0
    {
2275
0
      xmin = tmp_data_x[i];
2276
0
      ymin = tmp_data_y[i];   // not real ymin
2277
0
    }
2278
0
    if ( tmp_data_x[i] > xmax )
2279
0
    {
2280
0
      xmax = tmp_data_x[i];
2281
0
      ymax = tmp_data_y[i];   // not real ymax
2282
0
    }
2283
0
  }
2284
2285
  // extend points to the left
2286
0
  int    step = POINT_STEP;
2287
0
  double scale = POINT_SCALE;
2288
0
  int num_extra_point_left = MAX_NUM_POINT_TO_EXTEND;
2289
0
  int num_extra_point_right = MAX_NUM_POINT_TO_EXTEND;
2290
0
  while ( xmin >= step && ymin > 1 && num_extra_point_left > 0 )
2291
0
  {
2292
0
    xmin -= step;
2293
0
    ymin = static_cast<int>( ymin / scale );
2294
0
    tmp_data_x.push_back( xmin );
2295
0
    tmp_data_y.push_back( ymin );
2296
0
    num_extra_point_left--;
2297
0
  }
2298
2299
  // extend points to the right
2300
0
  while ( xmax + step <= ((1 << bitDepth) - 1) && ymax > 1 && num_extra_point_right > 0 )
2301
0
  {
2302
0
    xmax += step;
2303
0
    ymax = static_cast<int>( ymax / scale );
2304
0
    tmp_data_x.push_back( xmax );
2305
0
    tmp_data_y.push_back( ymax );
2306
0
    num_extra_point_right--;
2307
0
  }
2308
2309
  // filter out points outside the range
2310
0
  auto isValid = []( int x )
2311
0
  {
2312
0
    return x >= MIN_INTENSITY && x <= MAX_INTENSITY;
2313
0
  };
2314
2315
0
  std::vector<int> valid_x, valid_y;
2316
0
  for ( int i = 0; i < tmp_data_x.size(); i++ )
2317
0
  {
2318
0
    if ( isValid( tmp_data_x[i] ) )
2319
0
    {
2320
0
      valid_x.push_back( tmp_data_x[i] );
2321
0
      valid_y.push_back( tmp_data_y[i] );
2322
0
    }
2323
0
  }
2324
0
  tmp_data_x = std::move( valid_x );
2325
0
  tmp_data_y = std::move( valid_y );
2326
0
}
2327