Coverage Report

Created: 2026-04-01 07:49

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