diff --git a/source/App/DecoderApp/DecApp.cpp b/source/App/DecoderApp/DecApp.cpp
index d82d17e8e..945a1d718 100644
--- a/source/App/DecoderApp/DecApp.cpp
+++ b/source/App/DecoderApp/DecApp.cpp
@@ -559,7 +559,44 @@ uint32_t DecApp::decode()
             }
           }
         }
-
+#if JVET_AG0328
+        if (!m_SEIFGRFileName.empty() && !m_videoIOYuvSEIFGRFile[nalu.m_nuhLayerId].isOpen())
+        {
+          std::string SEIFGRFileName = m_SEIFGRFileName;
+          if (m_SEIFGRFileName.compare("/dev/null") && m_cDecLib.getVPS() != nullptr && m_cDecLib.getVPS()->getMaxLayers() > 1 && xIsNaluWithinTargetOutputLayerIdSet(&nalu))
+          {
+            size_t      pos         = SEIFGRFileName.find_last_of('.');
+            std::string layerString = std::string(".layer") + std::to_string(nalu.m_nuhLayerId);
+            if (pos != std::string::npos)
+            {
+              SEIFGRFileName.insert(pos, layerString);
+            }
+            else
+            {
+              SEIFGRFileName.append(layerString);
+            }
+          }
+          if ((m_cDecLib.getVPS() != nullptr && (m_cDecLib.getVPS()->getMaxLayers() == 1 || xIsNaluWithinTargetOutputLayerIdSet(&nalu))) || m_cDecLib.getVPS() == nullptr)
+          {
+            m_videoIOYuvSEIFGRFile[nalu.m_nuhLayerId].open(SEIFGRFileName, true, layerOutputBitDepth,layerOutputBitDepth, bitDepths);   // write mode
+          }
+        }
+        // update file bitdepth shift if recon bitdepth changed between sequences
+        if (!m_SEIFGRFileName.empty())
+        {
+          for (const auto channelType: { ChannelType::LUMA, ChannelType::CHROMA })
+          {
+            int reconBitdepth = (*iterPicLayer)->m_bitDepths[( ChannelType) channelType];
+            int fileBitdepth  = m_videoIOYuvSEIFGRFile[nalu.m_nuhLayerId].getFileBitdepth(channelType);
+            int bitdepthShift = m_videoIOYuvSEIFGRFile[nalu.m_nuhLayerId].getBitdepthShift(channelType);
+            if (fileBitdepth + bitdepthShift != reconBitdepth)
+            {
+              m_videoIOYuvSEIFGRFile[nalu.m_nuhLayerId].setBitdepthShift(channelType, reconBitdepth - fileBitdepth);
+            }
+          }
+        }
+#endif
+        
         if (!m_SEICTIFileName.empty() && !m_cVideoIOYuvSEICTIFile[nalu.m_nuhLayerId].isOpen())
         {
           std::string SEICTIFileName = m_SEICTIFileName;
@@ -953,6 +990,15 @@ void DecApp::xDestroyDecLib()
       recFile.second.close();
     }
   }
+#if JVET_AG0328
+  if (!m_SEIFGRFileName.empty())
+  {
+    for (auto &recFile: m_videoIOYuvSEIFGRFile)
+    {
+      recFile.second.close();
+    }
+  }
+#endif
   if (!m_SEICTIFileName.empty())
   {
     for (auto& recFile : m_cVideoIOYuvSEICTIFile)
@@ -1161,6 +1207,32 @@ void DecApp::xWriteOutput( PicList* pcListPic, uint32_t tId )
           }
         }
 
+#if JVET_AG0328
+        // Perform region based FGS on decoded frame and write to output FGR file
+        if (!m_SEIFGRFileName.empty())
+        {
+         const Window& conf            = pcPic->getConformanceWindow();
+          const SPS* sps                = pcPic->cs->sps;
+          ChromaFormat  chromaFormatIdc    = sps->getChromaFormatIdc();
+          if (m_upscaledOutput)
+          {
+            m_videoIOYuvSEIFGRFile[pcPic->layerId].writeUpscaledPicture(
+              *sps, *pcPic->cs->pps, pcPic->getDisplayBufFGR(), m_outputColourSpaceConvert, m_packedYUVMode,
+              m_upscaledOutput, ChromaFormat::UNDEFINED, m_clipOutputVideoToRec709Range, m_upscaleFilterForDisplay);
+          }
+          else
+          {
+            m_videoIOYuvSEIFGRFile[pcPic->layerId].write(
+              pcPic->getRecoBuf().get(COMPONENT_Y).width, pcPic->getRecoBuf().get(COMPONENT_Y).height,
+              pcPic->getDisplayBufFGR(), m_outputColourSpaceConvert, m_packedYUVMode,
+              conf.getWindowLeftOffset() * SPS::getWinUnitX(chromaFormatIdc),
+              conf.getWindowRightOffset() * SPS::getWinUnitX(chromaFormatIdc),
+              conf.getWindowTopOffset() * SPS::getWinUnitY(chromaFormatIdc),
+              conf.getWindowBottomOffset() * SPS::getWinUnitY(chromaFormatIdc), ChromaFormat::UNDEFINED,
+              m_clipOutputVideoToRec709Range);
+          }
+        }
+#endif
 
         if (!m_shutterIntervalPostFileName.empty() && getShutterFilterFlag())
         {
@@ -1375,7 +1447,33 @@ void DecApp::xFlushOutput( PicList* pcListPic, const int layerId )
                 m_clipOutputVideoToRec709Range);
             }
           }
-
+#if JVET_AG0328
+        // Perform region based FGS on decoded frame and write to output FGR file
+        if (!m_SEIFGRFileName.empty())
+        {
+          const Window& conf            = pcPic->getConformanceWindow();
+          const SPS*    sps             = pcPic->cs->sps;
+          ChromaFormat  chromaFormatIdc = sps->getChromaFormatIdc();
+          if (m_upscaledOutput)
+          {
+            m_videoIOYuvSEIFGRFile[pcPic->layerId].writeUpscaledPicture(
+              *sps, *pcPic->cs->pps, pcPic->getDisplayBufFGR(), m_outputColourSpaceConvert, m_packedYUVMode,
+              m_upscaledOutput, ChromaFormat::UNDEFINED, m_clipOutputVideoToRec709Range, m_upscaleFilterForDisplay);
+          }
+          else
+          {
+            m_videoIOYuvSEIFGRFile[pcPic->layerId].write(
+              pcPic->getRecoBuf().get(COMPONENT_Y).width, pcPic->getRecoBuf().get(COMPONENT_Y).height,
+              pcPic->getDisplayBufFGR(), m_outputColourSpaceConvert, m_packedYUVMode,
+              conf.getWindowLeftOffset() * SPS::getWinUnitX(chromaFormatIdc),
+              conf.getWindowRightOffset() * SPS::getWinUnitX(chromaFormatIdc),
+              conf.getWindowTopOffset() * SPS::getWinUnitY(chromaFormatIdc),
+              conf.getWindowBottomOffset() * SPS::getWinUnitY(chromaFormatIdc), ChromaFormat::UNDEFINED,
+              m_clipOutputVideoToRec709Range);
+          }
+        }
+#endif
+        
           if (!m_shutterIntervalPostFileName.empty() && getShutterFilterFlag())
           {
             int blendingRatio = getBlendingRatio();
diff --git a/source/App/DecoderApp/DecApp.h b/source/App/DecoderApp/DecApp.h
index c92890252..e25adf395 100644
--- a/source/App/DecoderApp/DecApp.h
+++ b/source/App/DecoderApp/DecApp.h
@@ -62,6 +62,9 @@ private:
   DecLib          m_cDecLib;                     ///< decoder class
   std::unordered_map<int, VideoIOYuv>      m_cVideoIOYuvReconFile;        ///< reconstruction YUV class
   std::unordered_map<int, VideoIOYuv>      m_videoIOYuvSEIFGSFile;       ///< reconstruction YUV with FGS class
+#if JVET_AG0328
+  std::unordered_map<int, VideoIOYuv>      m_videoIOYuvSEIFGRFile;       ///< reconstruction YUV with FGR class
+#endif
   std::unordered_map<int, VideoIOYuv>      m_cVideoIOYuvSEICTIFile;       ///< reconstruction YUV with CTI class
 
   bool                                    m_ShutterFilterEnable;          ///< enable Post-processing with Shutter Interval SEI
diff --git a/source/App/DecoderApp/DecAppCfg.cpp b/source/App/DecoderApp/DecAppCfg.cpp
index eaf90dd50..ba79c4142 100644
--- a/source/App/DecoderApp/DecAppCfg.cpp
+++ b/source/App/DecoderApp/DecAppCfg.cpp
@@ -98,6 +98,9 @@ bool DecAppCfg::parseCfg( int argc, char* argv[] )
   ("SEIColourRemappingInfoFilename", m_colourRemapSEIFileName,         std::string(""), "Colour Remapping YUV output file name. If empty, no remapping is applied (ignore SEI message)\n")
   ("SEICTIFilename",            m_SEICTIFileName,                      std::string(""), "CTI YUV output file name. If empty, no Colour Transform is applied (ignore SEI message)\n")
   ("SEIFGSFilename",            m_SEIFGSFileName,                      std::string(""), "FGS YUV output file name. If empty, no film grain is applied (ignore SEI message)\n")
+#if JVET_AG0328
+  ("SEIFGRFilename",            m_SEIFGRFileName,                      std::string(""), "FGR YUV output file name. If empty, no film grain region is applied (ignore SEI message)\n")
+#endif
   ("SEIAnnotatedRegionsInfoFilename", m_annotatedRegionsSEIFileName,   std::string(""), "Annotated regions output file name. If empty, no object information will be saved (ignore SEI message)\n")
   ("OutputDecodedSEIMessagesFilename", m_outputDecodedSEIMessagesFilename, std::string(""), "When non empty, output decoded SEI messages to the indicated file. If file is '-', then output to stdout\n")
 #if JVET_S0257_DUMP_360SEI_MESSAGE
@@ -267,6 +270,9 @@ DecAppCfg::DecAppCfg()
   , m_colourRemapSEIFileName()
   , m_SEICTIFileName()
   , m_SEIFGSFileName()
+#if JVET_AG0328
+, m_SEIFGRFileName()
+#endif
   , m_annotatedRegionsSEIFileName()
   , m_targetDecLayerIdSet()
   , m_outputDecodedSEIMessagesFilename()
diff --git a/source/App/DecoderApp/DecAppCfg.h b/source/App/DecoderApp/DecAppCfg.h
index cd38e1515..7fdbf1230 100644
--- a/source/App/DecoderApp/DecAppCfg.h
+++ b/source/App/DecoderApp/DecAppCfg.h
@@ -77,6 +77,9 @@ protected:
   std::string   m_colourRemapSEIFileName;             ///< output Colour Remapping file name
   std::string   m_SEICTIFileName;                     ///< output Recon with CTI file name
   std::string   m_SEIFGSFileName;                     ///< output file name for reconstructed sequence with film grain
+#if JVET_AG0328
+  std::string   m_SEIFGRFileName;                     ///< output file name for reconstructed sequence with film grain regions
+#endif
   std::string   m_annotatedRegionsSEIFileName;        ///< annotated regions file name
   std::vector<int> m_targetDecLayerIdSet;             ///< set of LayerIds to be included in the sub-bitstream extraction process.
   std::string   m_outputDecodedSEIMessagesFilename;   ///< filename to output decoded SEI messages to. If '-', then use stdout. If empty, do not output details.
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 064736a83..1f018a0a6 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -1089,6 +1089,49 @@ void EncApp::xInitLibCfg( int layerIdx )
       }
     }
   }
+#if JVET_AG0328
+  // film grain regions charcteristics
+    m_cEncLib.setFilmGrainRegionsCharactersticsSEIEnabled          (m_fgrSEIEnabled);
+    m_cEncLib.setFilmGrainRegionsCharactersticsSEICancelFlag       (m_fgrSEICancelFlag);
+    m_cEncLib.setFilmGrainRegionsCharactersticsSEIPersistenceFlag  (m_fgrSEIPersistenceFlag);
+    m_cEncLib.setFilmGrainRegionsCharactersticsSEIModelID          ((uint8_t)m_fgrSEIModelID);
+    m_cEncLib.setFilmGrainRegionsCharactersticsSEISepColourDescPresent    (m_fgrSEISepColourDescPresentFlag);
+    m_cEncLib.setFilmGrainRegionsCharactersticsSEIBlendingModeID          ((uint8_t)m_fgrSEIBlendingModeID);
+    m_cEncLib.setFilmGrainRegionsCharactersticsSEILog2ScaleFactor         ((uint8_t)m_fgrSEILog2ScaleFactor);
+    m_cEncLib.setFilmGrainRegionsAnalysisEnabled                          (m_fgrSEIAnalysisEnabled);
+    m_cEncLib.setFilmGrainRegionsExternalMask                             (m_fgrSEIExternalMask);
+    m_cEncLib.setFilmGrainRegionsExternalDenoised                         (m_fgrSEIExternalDenoised);
+    m_cEncLib.setFilmGrainRegionsTemporalFilterPastRefs                   (m_fgrSEITemporalFilterPastRefs);
+    m_cEncLib.setFilmGrainRegionsTemporalFilterFutureRefs                 (m_fgrSEITemporalFilterFutureRefs);
+    m_cEncLib.setFilmGrainRegionsTemporalFilterStrengths                  (m_fgrSEITemporalFilterStrengths);
+    for (int i = 0; i < MAX_NUM_COMPONENT; i++)
+    {
+      m_cEncLib.setFGRSEICompModelPresent                          (m_fgrSEICompModelPresent[i], i);
+      if (m_fgrSEICompModelPresent[i]) 
+      {
+        m_cEncLib.setFGRSEINumIntensityIntervalMinus1              ((uint8_t)m_fgrSEINumIntensityIntervalMinus1[i], i);
+        m_cEncLib.setFGRSEINumModelValuesMinus1                    ((uint8_t)m_fgrSEINumModelValuesMinus1[i], i);
+        for (int j = 0; j <= m_fgrSEINumIntensityIntervalMinus1[i]; j++) 
+        {
+          m_cEncLib.setFGRSEIIntensityIntervalLowerBound           ((uint8_t)m_fgrSEIIntensityIntervalLowerBound[i][j], i, j);
+          m_cEncLib.setFGRSEIIntensityIntervalUpperBound           ((uint8_t)m_fgrSEIIntensityIntervalUpperBound[i][j], i, j);
+          for (int k = 0; k <= m_fgrSEINumModelValuesMinus1[i]; k++) 
+          {
+            m_cEncLib.setFGRSEICompModelValue                      (m_fgrSEICompModelValue[i][j][k], i, j, k);
+          }
+        }
+      }
+    }
+    m_cEncLib.setFilmGrainRegionInformationPresentFlag           (m_fgrSEI_region_information_present_flag);
+    m_cEncLib.setFilmGrainRegionBasedAdaptationFlag              (m_fgrSEI_region_based_adaptation_flag);
+    m_cEncLib.setFilmGrainAlphaChannelAdaptationFlag             (m_fgrSEI_alpha_channel_adaptation_flag);
+    m_cEncLib.setFilmGrainNumActiveRegionsMinus1                 (m_fgrSEINumActiveRegionsMinus1);
+
+    if(m_fgrSEI_region_based_adaptation_flag)
+    {
+        m_cEncLib.setFilmGrainRegionCharacteristics(m_fgrSEIregionCharacteristics,m_fgrSEINumActiveRegionsMinus1);
+    }
+#endif
   // content light level
   m_cEncLib.setCLLSEIEnabled                                     (m_cllSEIEnabled);
   m_cEncLib.setCLLSEIMaxContentLightLevel                        ((uint16_t)m_cllSEIMaxContentLevel);
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index e7040d294..b195d9787 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -152,7 +152,56 @@ std::istringstream &operator>>(std::istringstream &in, GOPEntry &entry)     //in
   return in;
 }
 
+#if JVET_AG0328
+template <typename T>
+static void readValueFromFile(T            &returnedValue, /// value returned
+                           bool         &failed,        /// used and updated
+                           std::istream &is,            /// stream to read token from
+                           const char  *pToken)        /// token string
+{
+  returnedValue=T();
+  if (failed)
+  {
+    return;
+  }
 
+  int c;
+  // Ignore any whitespace
+  while ((c=is.get())!=EOF && isspace(c));
+  // test for comment mark
+  while (c=='#')
+  {
+    // Ignore to the end of the line
+    while ((c=is.get())!=EOF && (c!=10 && c!=13));
+    // Ignore any white space at the start of the next line
+    while ((c=is.get())!=EOF && isspace(c));
+  }
+  // test first character of token
+  failed=(c!=pToken[0]);
+  // test remaining characters of token
+  int pos;
+  for(pos=1;!failed && pToken[pos]!=0 && is.get()==pToken[pos]; pos++);
+  failed|=(pToken[pos]!=0);
+  // Ignore any whitespace before the ':'
+  while (!failed && (c=is.get())!=EOF && isspace(c));
+  failed|=(c!=':');
+  // Now read the value associated with the token:
+  if (!failed)
+  {
+    is >> returnedValue;
+    failed=!is.good();
+    if (!failed)
+    {
+      c=is.get();
+      failed=(c!=EOF && !isspace(c));
+    }
+  }
+  if (failed)
+  {
+    std::cerr << "Unable to read Value '" << pToken << "'\n";
+  }
+}
+#endif
 
 bool confirmPara(bool bflag, const char* message);
 
@@ -727,6 +776,31 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   SMultiValueInput<uint32_t>   cfg_FgcSEICompModelValueComp0              (0, 65535,  0, 256 * 6);
   SMultiValueInput<uint32_t>   cfg_FgcSEICompModelValueComp1              (0, 65535,  0, 256 * 6);
   SMultiValueInput<uint32_t>   cfg_FgcSEICompModelValueComp2              (0, 65535,  0, 256 * 6);
+#if JVET_AG0328
+    //film grain regions SEI
+    SMultiValueInput<uint32_t>   cfg_FgrSEIIntensityIntervalLowerBoundComp0 (0, 255, 0, 256);
+    SMultiValueInput<uint32_t>   cfg_FgrSEIIntensityIntervalLowerBoundComp1 (0, 255, 0, 256);
+    SMultiValueInput<uint32_t>   cfg_FgrSEIIntensityIntervalLowerBoundComp2 (0, 255, 0, 256);
+    SMultiValueInput<uint32_t>   cfg_FgrSEIIntensityIntervalUpperBoundComp0 (0, 255, 0, 256);
+    SMultiValueInput<uint32_t>   cfg_FgrSEIIntensityIntervalUpperBoundComp1 (0, 255, 0, 256);
+    SMultiValueInput<uint32_t>   cfg_FgrSEIIntensityIntervalUpperBoundComp2 (0, 255, 0, 256);
+    SMultiValueInput<uint32_t>   cfg_FgrSEICompModelValueComp0              (0, 65535,  0, 256 * 6);
+    SMultiValueInput<uint32_t>   cfg_FgrSEICompModelValueComp1              (0, 65535,  0, 256 * 6);
+    SMultiValueInput<uint32_t>   cfg_FgrSEICompModelValueComp2              (0, 65535,  0, 256 * 6);
+
+    SMultiValueInput<uint32_t>    cfg_fgr_region_top(0, 65535, 0, 256 * 6);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_left(0, 65535, 0, 256 * 6);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_width(0, 65535, 0, 256 * 6);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_height(0, 65535, 0, 256 * 6);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_fg_enable(0, 1, 0, 256);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_interval_min_comp0(0, 255, 0, 256);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_interval_min_comp1(0, 255, 0, 256);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_interval_min_comp2(0, 255, 0, 256);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_interval_max_comp0(0, 255, 0, 256);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_interval_max_comp1(0, 255, 0, 256);
+    SMultiValueInput<uint32_t>    cfg_fgr_region_interval_max_comp2(0, 255, 0, 256);
+#endif
+  
   SMultiValueInput<unsigned>   cfg_siiSEIInputNumUnitsInSI(0, std::numeric_limits<uint32_t>::max(), 0, 7);
   SMultiValueInput<bool>       cfg_poSEIWrappingFlag(false, true, 0, 256);
   SMultiValueInput<bool>       cfg_poSEIImportanceFlag(false, true, 0, 256);
@@ -1504,6 +1578,57 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   ("SEIFGCCompModelValuesComp0",             cfg_FgcSEICompModelValueComp0,              cfg_FgcSEICompModelValueComp0,              "Specifies the component model values on colour component 0.")
   ("SEIFGCCompModelValuesComp1",             cfg_FgcSEICompModelValueComp1,              cfg_FgcSEICompModelValueComp1,              "Specifies the component model values on colour component 1.")
   ("SEIFGCCompModelValuesComp2",             cfg_FgcSEICompModelValueComp2,              cfg_FgcSEICompModelValueComp2,              "Specifies the component model values on colour component 2.")
+#if JVET_AG0328
+  // film grain region characteristics SEI
+  ("SEIFGREnabled",                                   m_fgrSEIEnabled,                                   false, "Control generation of the film grain regions characteristics SEI message")
+  ("SEIFGRCancelFlag",                                m_fgrSEICancelFlag,                                 true, "Specifies the persistence of any previous film grain regions characteristics SEI message in output order.")
+  ("SEIFGRPersistenceFlag",                           m_fgrSEIPersistenceFlag,                           false, "Specifies the persistence of the film grain regions characteristics SEI message for the current layer.")
+  ("SEIFGRModelID",                                   m_fgrSEIModelID,                                      0u, "Specifies the film grain simulation model. 0: frequency filtering; 1: auto-regression.")
+  ("SEIFGRSepColourDescPresentFlag",                  m_fgrSEISepColourDescPresentFlag,                  false, "Specifies the presence of a distinct colour space description for the film grain characteristics specified in the SEI message.")
+  ("SEIFGRBlendingModeID",                            m_fgrSEIBlendingModeID,                               0u, "Specifies the blending mode used to blend the simulated film grain with the decoded images. 0: additive; 1: multiplicative.")
+  ("SEIFGRLog2ScaleFactor",                           m_fgrSEILog2ScaleFactor,                              0u, "Specifies a scale factor used in the film grain characterization equations.")
+  ("SEIFGRCompModelPresentComp0",                     m_fgrSEICompModelPresent[0],                       false, "Specifies the presence of film grain modelling on colour component 0.")
+  ("SEIFGRCompModelPresentComp1",                     m_fgrSEICompModelPresent[1],                       false, "Specifies the presence of film grain modelling on colour component 1.")
+  ("SEIFGRCompModelPresentComp2",                     m_fgrSEICompModelPresent[2],                       false, "Specifies the presence of film grain modelling on colour component 2.")
+  ("SEIFGRAnalysisEnabled",                           m_fgrSEIAnalysisEnabled,                           false, "Control adaptive film grain parameter estimation - film grain analysis")
+  ("SEIFGRExternalMask",                              m_fgrSEIExternalMask,                       std::string( "" ), "Read external file with mask for film grain analysis. If empty string, use internally calculated mask.")
+  ("SEIFGRExternalDenoised",                          m_fgrSEIExternalDenoised,                   std::string( "" ), "Read external file with denoised sequence for film grain analysis. If empty string, use MCTF for denoising.")
+  ("SEIFGRTemporalFilterPastRefs",                    m_fgrSEITemporalFilterPastRefs,          TF_DEFAULT_REFS, "Number of past references for temporal prefilter")
+  ("SEIFGRTemporalFilterFutureRefs",                  m_fgrSEITemporalFilterFutureRefs,        TF_DEFAULT_REFS, "Number of future references for temporal prefilter")
+  ("SEIFGRTemporalFilterStrengthFrame*",              m_fgrSEITemporalFilterStrengths, std::map<int, double>(), "Strength for every * frame in FGC-specific temporal filter, where * is an integer.")
+  ("SEIFGRPerPictureSEI",                             m_fgrSEIPerPictureSEI,                             false, "Film Grain Regions SEI is added for each picture as speciffied in RDD5 to ensure bit accurate synthesis in tricky mode")
+  ("SEIFGRNumIntensityIntervalMinus1Comp0",           m_fgrSEINumIntensityIntervalMinus1[0],                0u, "Specifies the number of intensity intervals minus1 on colour component 0.")
+  ("SEIFGRNumIntensityIntervalMinus1Comp1",           m_fgrSEINumIntensityIntervalMinus1[1],                0u, "Specifies the number of intensity intervals minus1 on colour component 1.")
+  ("SEIFGRNumIntensityIntervalMinus1Comp2",           m_fgrSEINumIntensityIntervalMinus1[2],                0u, "Specifies the number of intensity intervals minus1 on colour component 2.")
+  ("SEIFGRNumModelValuesMinus1Comp0",                 m_fgrSEINumModelValuesMinus1[0],                      0u, "Specifies the number of component model values minus1 on colour component 0.")
+  ("SEIFGRNumModelValuesMinus1Comp1",                 m_fgrSEINumModelValuesMinus1[1],                      0u, "Specifies the number of component model values minus1 on colour component 1.")
+  ("SEIFGRNumModelValuesMinus1Comp2",                 m_fgrSEINumModelValuesMinus1[2],                      0u, "Specifies the number of component model values minus1 on colour component 2.")
+  ("SEIFGRIntensityIntervalLowerBoundComp0", cfg_FgrSEIIntensityIntervalLowerBoundComp0, cfg_FgrSEIIntensityIntervalLowerBoundComp0, "Specifies the lower bound for the intensity intervals on colour component 0.")
+  ("SEIFGRIntensityIntervalLowerBoundComp1", cfg_FgrSEIIntensityIntervalLowerBoundComp1, cfg_FgrSEIIntensityIntervalLowerBoundComp1, "Specifies the lower bound for the intensity intervals on colour component 1.")
+  ("SEIFGRIntensityIntervalLowerBoundComp2", cfg_FgrSEIIntensityIntervalLowerBoundComp2, cfg_FgrSEIIntensityIntervalLowerBoundComp2, "Specifies the lower bound for the intensity intervals on colour component 2.")
+  ("SEIFGRIntensityIntervalUpperBoundComp0", cfg_FgrSEIIntensityIntervalUpperBoundComp0, cfg_FgrSEIIntensityIntervalUpperBoundComp0, "Specifies the upper bound for the intensity intervals on colour component 0.")
+  ("SEIFGRIntensityIntervalUpperBoundComp1", cfg_FgrSEIIntensityIntervalUpperBoundComp1, cfg_FgrSEIIntensityIntervalUpperBoundComp1, "Specifies the upper bound for the intensity intervals on colour component 1.")
+  ("SEIFGRIntensityIntervalUpperBoundComp2", cfg_FgrSEIIntensityIntervalUpperBoundComp2, cfg_FgrSEIIntensityIntervalUpperBoundComp2, "Specifies the upper bound for the intensity intervals on colour component 2.")
+  ("SEIFGRCompModelValuesComp0",             cfg_FgrSEICompModelValueComp0,              cfg_FgrSEICompModelValueComp0,              "Specifies the component model values on colour component 0.")
+  ("SEIFGRCompModelValuesComp1",             cfg_FgrSEICompModelValueComp1,              cfg_FgrSEICompModelValueComp1,              "Specifies the component model values on colour component 1.")
+  ("SEIFGRCompModelValuesComp2",             cfg_FgrSEICompModelValueComp2,              cfg_FgrSEICompModelValueComp2,              "Specifies the component model values on colour component 2.")
+  ("SEIFGRSpatialAdaptation",                m_fgrSEI_region_information_present_flag,     false, "Specifies if spatial adaptation if enabled or not.")
+  ("SEIFGRegionBasedAdaptation",             m_fgrSEI_region_based_adaptation_flag,     false, "Spatial adaptation based on rectangle regions.")
+  ("SEIFGAlphaChannelBasedAdaptation",       m_fgrSEI_alpha_channel_adaptation_flag,     false, "Spatial adaptation based on alpha Mask.")
+  ("SEIFGRegionsNumberMinus1",       m_fgrSEINumActiveRegionsMinus1,     0u, "Number of active regions with adaptive parameters.")
+
+  ("SEIFGRegionTop", cfg_fgr_region_top, cfg_fgr_region_top, "Specifies the Top coordinate of the regions.")
+  ("SEIFGRegionLeft", cfg_fgr_region_left, cfg_fgr_region_left, "Specifies the Left coordinate of the regions.")
+  ("SEIFGRegionWidth", cfg_fgr_region_width, cfg_fgr_region_left, "Specifies the width coordinate of the regions.")
+  ("SEIFGRegionHeight", cfg_fgr_region_height, cfg_fgr_region_height, "Specifies the height coordinate of the regions.")
+  ("SEIFGRFilmGrainEnabled", cfg_fgr_region_fg_enable, cfg_fgr_region_fg_enable, "Enable/disable Film grain for the regions.")
+  ("SEIFGRegionIntervalMinComp0", cfg_fgr_region_interval_min_comp0, cfg_fgr_region_interval_min_comp0, "Specifies the min interval for Comp0 for the regions.")
+  ("SEIFGRegionIntervalMinComp1", cfg_fgr_region_interval_min_comp1, cfg_fgr_region_interval_min_comp1, "Specifies the min interval for Comp1 for the regions.")
+  ("SEIFGRegionIntervalMinComp2", cfg_fgr_region_interval_min_comp2, cfg_fgr_region_interval_min_comp2, "Specifies the min interval for Comp2 for the regions.")
+  ("SEIFGRegionIntervalMaxComp0", cfg_fgr_region_interval_max_comp0, cfg_fgr_region_interval_max_comp0, "Specifies the max interval for Comp0 for the regions.")
+  ("SEIFGRegionIntervalMaxComp1", cfg_fgr_region_interval_max_comp1, cfg_fgr_region_interval_max_comp1, "Specifies the max interval for Comp1 for the regions.")
+  ("SEIFGRegionIntervalMaxComp2", cfg_fgr_region_interval_max_comp2, cfg_fgr_region_interval_max_comp2, "Specifies the max interval for Comp2 for the regions.")
+#endif
 // content light level SEI
   ("SEICLLEnabled",                                   m_cllSEIEnabled,                                   false, "Control generation of the content light level SEI message")
   ("SEICLLMaxContentLightLevel",                      m_cllSEIMaxContentLevel,                              0u, "When not equal to 0, specifies an upper bound on the maximum light level among all individual samples in a 4:4:4 representation "
@@ -3232,6 +3357,121 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
     }
     m_fgcSEILog2ScaleFactor = m_fgcSEILog2ScaleFactor ? m_fgcSEILog2ScaleFactor : 2;
   }
+  
+#if JVET_AG0328
+  // set sei film grain regions parameters.
+  CHECK(!m_fgrSEIEnabled && m_fgrSEIAnalysisEnabled, "FGR SEI must be enabled in order to perform film grain regions analysis!");
+  if (m_fgrSEIEnabled)
+  {
+    if (m_iQP < 17 && m_fgrSEIAnalysisEnabled == true)
+    {   // TODO: JVET_Z0047_FG_IMPROVEMENT: check this; the constraint may have gone
+      msg(WARNING, "*************************************************************************\n");
+      msg(WARNING, "* WARNING: Film Grain Estimation is disabled for Qp<17! FGR SEI will use default parameters for film grain! *\n");
+      msg(WARNING, "*************************************************************************\n");
+      m_fgrSEIAnalysisEnabled = false;
+    }
+    if (m_intraPeriod < 1)
+    {   // low delay configuration
+      msg(WARNING, "*************************************************************************\n");
+      msg(WARNING, "* WARNING: For low delay configuration, FGR SEI is inserted for first frame only!*\n");
+      msg(WARNING, "*************************************************************************\n");
+      m_fgrSEIPerPictureSEI   = false;
+      m_fgrSEIPersistenceFlag = true;
+    }
+    else if (m_intraPeriod == 1)
+    {   // all intra configuration
+      msg(WARNING, "*************************************************************************\n");
+      msg(WARNING, "* WARNING: For Intra Period = 1, FGR SEI is inserted per frame!*\n");
+      msg(WARNING, "*************************************************************************\n");
+      m_fgrSEIPerPictureSEI   = true;
+      m_fgrSEIPersistenceFlag = false;
+    }
+    if (!m_fgrSEIPerPictureSEI && !m_fgrSEIPersistenceFlag) {
+      msg(WARNING, "*************************************************************************\n");
+      msg(WARNING, "* WARNING: SEIPerPictureSEI is set to 0, SEIPersistenceFlag needs to be set to 1! *\n");
+      msg(WARNING, "*************************************************************************\n");
+      m_fgrSEIPersistenceFlag = true;
+    }
+    else if (m_fgrSEIPerPictureSEI && m_fgrSEIPersistenceFlag) {
+      msg(WARNING, "*************************************************************************\n");
+      msg(WARNING, "* WARNING: SEIPerPictureSEI is set to 1, SEIPersistenceFlag needs to be set to 0! *\n");
+      msg(WARNING, "*************************************************************************\n");
+      m_fgrSEIPersistenceFlag = false;
+    }
+    if (m_fgrSEIAnalysisEnabled && m_fgrSEITemporalFilterStrengths.empty())
+    {
+      // By default: in random-acces = filter RAPs, in all-intra = filter every frame, otherwise = filter every 2s
+      int filteredFrame = m_intraPeriod < 1 ? 2 * m_frameRate.getIntValRound() : m_intraPeriod;
+      m_fgrSEITemporalFilterStrengths[filteredFrame] = 1.5;
+    }
+    uint32_t numModelCtr;
+    if (m_fgrSEICompModelPresent[0])
+    {
+      numModelCtr = 0;
+      for (uint8_t i = 0; i <= m_fgrSEINumIntensityIntervalMinus1[0]; i++)
+      {
+        m_fgrSEIIntensityIntervalLowerBound[0][i] = uint32_t((cfg_FgrSEIIntensityIntervalLowerBoundComp0.values.size() > i) ? cfg_FgrSEIIntensityIntervalLowerBoundComp0.values[i] : 10);
+        m_fgrSEIIntensityIntervalUpperBound[0][i] = uint32_t((cfg_FgrSEIIntensityIntervalUpperBoundComp0.values.size() > i) ? cfg_FgrSEIIntensityIntervalUpperBoundComp0.values[i] : 250);
+        for (uint8_t j = 0; j <= m_fgrSEINumModelValuesMinus1[0]; j++)
+        {
+          m_fgrSEICompModelValue[0][i][j] = uint32_t((cfg_FgrSEICompModelValueComp0.values.size() > numModelCtr) ? cfg_FgrSEICompModelValueComp0.values[numModelCtr] : 24);
+          numModelCtr++;
+        }
+      }
+    }
+    if (m_fgrSEICompModelPresent[1])
+    {
+      numModelCtr = 0;
+      for (uint8_t i = 0; i <= m_fgrSEINumIntensityIntervalMinus1[1]; i++)
+      {
+        m_fgrSEIIntensityIntervalLowerBound[1][i] = uint32_t((cfg_FgrSEIIntensityIntervalLowerBoundComp1.values.size() > i) ? cfg_FgrSEIIntensityIntervalLowerBoundComp1.values[i] : 60);
+        m_fgrSEIIntensityIntervalUpperBound[1][i] = uint32_t((cfg_FgrSEIIntensityIntervalUpperBoundComp1.values.size() > i) ? cfg_FgrSEIIntensityIntervalUpperBoundComp1.values[i] : 200);
+
+        for (uint8_t j = 0; j <= m_fgrSEINumModelValuesMinus1[1]; j++)
+        {
+          m_fgrSEICompModelValue[1][i][j] = uint32_t((cfg_FgrSEICompModelValueComp1.values.size() > numModelCtr) ? cfg_FgrSEICompModelValueComp1.values[numModelCtr] : 16);
+          numModelCtr++;
+        }
+      }
+    }
+    if (m_fgrSEICompModelPresent[2])
+    {
+      numModelCtr = 0;
+      for (uint8_t i = 0; i <= m_fgrSEINumIntensityIntervalMinus1[2]; i++)
+      {
+        m_fgrSEIIntensityIntervalLowerBound[2][i] = uint32_t((cfg_FgrSEIIntensityIntervalLowerBoundComp2.values.size() > i) ? cfg_FgrSEIIntensityIntervalLowerBoundComp2.values[i] : 60);
+        m_fgrSEIIntensityIntervalUpperBound[2][i] = uint32_t((cfg_FgrSEIIntensityIntervalUpperBoundComp2.values.size() > i) ? cfg_FgrSEIIntensityIntervalUpperBoundComp2.values[i] : 250);
+
+        for (uint8_t j = 0; j <= m_fgrSEINumModelValuesMinus1[2]; j++)
+        {
+          m_fgrSEICompModelValue[2][i][j] = uint32_t((cfg_FgrSEICompModelValueComp2.values.size() > numModelCtr) ? cfg_FgrSEICompModelValueComp2.values[numModelCtr] : 12);
+          numModelCtr++;
+        }
+      }
+    }
+    m_fgrSEILog2ScaleFactor = m_fgrSEILog2ScaleFactor ? m_fgrSEILog2ScaleFactor : 2;
+
+    if (m_fgrSEI_region_based_adaptation_flag)
+    {
+      m_fgrSEIregionCharacteristics.resize(m_fgrSEINumActiveRegionsMinus1 + 1);
+      for (int regionIdx = 0; regionIdx <= m_fgrSEINumActiveRegionsMinus1; regionIdx++)
+      {
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_region_top = cfg_fgr_region_top.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_region_left = cfg_fgr_region_left.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_region_width = cfg_fgr_region_width.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_region_height = cfg_fgr_region_height.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_film_grain_enabled_flag = cfg_fgr_region_fg_enable.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_regionIntervals[0].m_fgr_region_interval_min = cfg_fgr_region_interval_min_comp0.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_regionIntervals[1].m_fgr_region_interval_min = cfg_fgr_region_interval_min_comp1.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_regionIntervals[2].m_fgr_region_interval_min = cfg_fgr_region_interval_min_comp2.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_regionIntervals[0].m_fgr_region_interval_max = cfg_fgr_region_interval_max_comp0.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_regionIntervals[1].m_fgr_region_interval_max = cfg_fgr_region_interval_max_comp1.values[regionIdx];
+        m_fgrSEIregionCharacteristics[regionIdx].m_fgr_regionIntervals[2].m_fgr_region_interval_max = cfg_fgr_region_interval_max_comp2.values[regionIdx];
+      }
+    }
+  }
+ 
+#endif
   if (m_ctiSEIEnabled)
   {
     CHECK(!m_ctiSEICrossComponentFlag && m_ctiSEICrossComponentInferred, "CTI CrossComponentFlag is 0, but CTI CrossComponentInferred is 1 (must be 0 for CrossComponentFlag 0)");
@@ -5651,7 +5891,9 @@ void EncAppCfg::xPrintParameter()
   msg(VERBOSE, "SEI CTI:%d ", m_ctiSEIEnabled);
   msg(VERBOSE, "BIM:%d ", m_bimEnabled);
   msg(VERBOSE, "SEI FGC:%d ", m_fgcSEIEnabled);
-
+#if JVET_AG0328
+    msg(VERBOSE, "SEI FGR:%d ", m_fgrSEIEnabled);
+#endif
   msg(VERBOSE, "SEI processing Order:%d ", m_poSEIEnabled);
 
 #if EXTENSION_360_VIDEO
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index a9bcd15b3..04bac1d43 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -563,6 +563,34 @@ protected:
   uint32_t  m_fgcSEIIntensityIntervalLowerBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
   uint32_t  m_fgcSEIIntensityIntervalUpperBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
   uint32_t  m_fgcSEICompModelValue                [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES][MAX_NUM_MODEL_VALUES];
+#if JVET_AG0328
+  // film grain regions characterstics sei
+    bool      m_fgrSEIEnabled;
+    bool      m_fgrSEICancelFlag;
+    bool      m_fgrSEIPersistenceFlag;
+    uint32_t  m_fgrSEIModelID;
+    bool      m_fgrSEISepColourDescPresentFlag;
+    uint32_t  m_fgrSEIBlendingModeID;
+    uint32_t  m_fgrSEILog2ScaleFactor;
+    bool      m_fgrSEICompModelPresent[MAX_NUM_COMPONENT];
+    bool      m_fgrSEIAnalysisEnabled;
+    std::string m_fgrSEIExternalMask;
+    std::string m_fgrSEIExternalDenoised;
+    int       m_fgrSEITemporalFilterPastRefs;
+    int       m_fgrSEITemporalFilterFutureRefs;
+    std::map<int, double> m_fgrSEITemporalFilterStrengths;
+    bool      m_fgrSEIPerPictureSEI;
+    uint32_t  m_fgrSEINumModelValuesMinus1          [MAX_NUM_COMPONENT];
+    uint32_t  m_fgrSEINumIntensityIntervalMinus1    [MAX_NUM_COMPONENT];
+    uint32_t  m_fgrSEIIntensityIntervalLowerBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
+    uint32_t  m_fgrSEIIntensityIntervalUpperBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
+    uint32_t  m_fgrSEICompModelValue                [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES][MAX_NUM_MODEL_VALUES];
+    bool      m_fgrSEI_region_information_present_flag;
+    bool      m_fgrSEI_region_based_adaptation_flag;
+    bool      m_fgrSEI_alpha_channel_adaptation_flag;
+    uint32_t  m_fgrSEINumActiveRegionsMinus1;
+    std::vector<SEIFilmGrainRegionsCharacteristics::RegionCharacteristics> m_fgrSEIregionCharacteristics;
+#endif
   // content light level SEI
   bool      m_cllSEIEnabled;
   uint32_t  m_cllSEIMaxContentLevel;
diff --git a/source/App/utils/BitrateTargeting/encodeCommand.sh b/source/App/utils/BitrateTargeting/encodeCommand.sh
old mode 100755
new mode 100644
diff --git a/source/App/utils/BitrateTargeting/targetBitrates.sh b/source/App/utils/BitrateTargeting/targetBitrates.sh
old mode 100755
new mode 100644
diff --git a/source/Lib/CommonLib/Picture.cpp b/source/Lib/CommonLib/Picture.cpp
index 48cd7befb..c1cea36f4 100644
--- a/source/Lib/CommonLib/Picture.cpp
+++ b/source/Lib/CommonLib/Picture.cpp
@@ -76,6 +76,10 @@ Picture::Picture()
   m_isMctfFiltered      = false;
   m_grainCharacteristic = nullptr;
   m_grainBuf            = nullptr;
+#if JVET_AG0328
+ m_grainRegionsCharacteristic = nullptr;
+ m_grainRegionsBuf            = nullptr;
+#endif
 }
 
 void Picture::create(const bool useWrapAround, const ChromaFormat& _chromaFormat, const Size& size,
@@ -1455,6 +1459,67 @@ PelUnitBuf Picture::getDisplayBufFG(bool wrap)
   }
 }
 
+#if JVET_AG0328
+void Picture::createGrainRegionsSynthesizer(bool firstPictureInSequence, SEIFilmGrainRegionsSynthesizer *grainRegionsCharacteristics, PelStorage *grainRegionsBuf, int width, int height, ChromaFormat fmt, int bitDepth)
+{
+  m_grainRegionsCharacteristic = grainRegionsCharacteristics;
+  m_grainRegionsBuf            = grainRegionsBuf;
+
+  // Padding to make wd and ht multiple of max fgs window size(64)
+  int paddedWdFGS = ((width - 1) | 0x3F) + 1 - width;
+  int paddedHtFGS = ((height - 1) | 0x3F) + 1 - height;
+  m_padValue      = (paddedWdFGS > paddedHtFGS) ? paddedWdFGS : paddedHtFGS;
+
+  if (firstPictureInSequence)
+  {
+    // Create and initialize the Film Grain Synthesizer
+    m_grainRegionsCharacteristic->create(width, height, fmt, bitDepth, 1);
+
+    // Frame level PelStorage buffer created to blend Film Grain Noise into it
+    m_grainRegionsBuf->create(chromaFormat, Area(0, 0, width, height), 0, m_padValue, 0, false);
+
+    m_grainRegionsCharacteristic->fgrInit();
+  }
+}
+
+PelUnitBuf Picture::getDisplayBufFGR(bool wrap)
+{
+  SEI::PayloadType           payloadType;
+  std::list<SEI *>::iterator message;
+  for (message = SEIs.begin(); message != SEIs.end(); ++message)
+  {
+    payloadType = (*message)->payloadType();
+    if (payloadType == SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS)
+    {
+      m_grainRegionsCharacteristic->m_errorCode       = -1;
+      *m_grainRegionsCharacteristic->m_fgrParameters = *static_cast<SEIFilmGrainRegionsCharacteristics *>(*message);
+      /* Validation of Film grain region characteristic parameters for the constrains of SMPTE-RDD5*/
+      m_grainRegionsCharacteristic->m_errorCode = m_grainRegionsCharacteristic->grainValidateParams();
+      break;
+    }
+  }
+
+  if (FGS_SUCCESS == m_grainRegionsCharacteristic->m_errorCode)
+  {
+    m_grainRegionsBuf->copyFrom(getRecoBuf());
+    m_grainRegionsBuf->extendBorderPel(m_padValue); // Padding to make wd and ht multiple of max fgs window size(64)
+
+    m_grainRegionsCharacteristic->m_poc = getPOC();
+    m_grainRegionsCharacteristic->grainSynthesizeAndBlend(m_grainRegionsBuf, slices[0]->getIdrPicFlag());
+
+    return *m_grainRegionsBuf;
+  }
+  else
+  {
+    if (payloadType == SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS)
+    {
+      msg(WARNING, "Film Grain Region synthesis is not performed. Error code: 0x%x \n", m_grainCharacteristic->m_errorCode);
+    }
+    return M_BUFS(scheduler.getSplitPicId(), wrap ? PIC_RECON_WRAP : PIC_RECONSTRUCTION);
+  }
+}
+#endif
+
 void Picture::createColourTransfProcessor(bool firstPictureInSequence, SEIColourTransformApply* ctiCharacteristics, PelStorage* ctiBuf, int width, int height, ChromaFormat fmt, int bitDepth)
 {
   m_colourTranfParams = ctiCharacteristics;
diff --git a/source/Lib/CommonLib/Picture.h b/source/Lib/CommonLib/Picture.h
index 224e96293..8f3fe73e2 100644
--- a/source/Lib/CommonLib/Picture.h
+++ b/source/Lib/CommonLib/Picture.h
@@ -85,7 +85,13 @@ struct Picture : public UnitArea
   PelStorage*               m_grainBuf;
   void              createGrainSynthesizer(bool firstPictureInSequence, SEIFilmGrainSynthesizer* grainCharacteristics, PelStorage* grainBuf, int width, int height, ChromaFormat fmt, int bitDepth);
   PelUnitBuf        getDisplayBufFG       (bool wrap = false);
-
+#if JVET_AG0328
+  // Film Grain Regions Synthesizer
+  SEIFilmGrainRegionsSynthesizer*  m_grainRegionsCharacteristic;
+  PelStorage*               m_grainRegionsBuf;
+  void              createGrainRegionsSynthesizer(bool firstPictureInSequence, SEIFilmGrainRegionsSynthesizer* grainRegionsCharacteristics, PelStorage* grainRegionsBuf, int width, int height, ChromaFormat fmt, int bitDepth);
+  PelUnitBuf        getDisplayBufFGR       (bool wrap = false);
+#endif
   SEIColourTransformApply* m_colourTranfParams;
   PelStorage*              m_invColourTransfBuf;
   void              createColourTransfProcessor(bool firstPictureInSequence, SEIColourTransformApply* ctiCharacteristics, PelStorage* ctiBuf, int width, int height, ChromaFormat fmt, int bitDepth);
diff --git a/source/Lib/CommonLib/SEI.cpp b/source/Lib/CommonLib/SEI.cpp
index 4a46ac283..acaa59840 100644
--- a/source/Lib/CommonLib/SEI.cpp
+++ b/source/Lib/CommonLib/SEI.cpp
@@ -446,6 +446,9 @@ static const std::map<SEI::PayloadType, const char *> payloadTypeStrings = {
   { SEI::PayloadType::USER_DATA_REGISTERED_ITU_T_T35, "User data registered" },
   { SEI::PayloadType::USER_DATA_UNREGISTERED, "User data unregistered" },
   { SEI::PayloadType::FILM_GRAIN_CHARACTERISTICS, "Film grain characteristics" },
+#if JVET_AG0328
+  { SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS, "Film grain region characteristics" },
+#endif
   { SEI::PayloadType::FRAME_PACKING, "Frame packing arrangement" },
   { SEI::PayloadType::DISPLAY_ORIENTATION, "Display orientation" },
   { SEI::PayloadType::GREEN_METADATA, "Green metadata" },
@@ -882,6 +885,54 @@ SEIFilmGrainCharacteristics::SEIFilmGrainCharacteristics(const SEIFilmGrainChara
   m_filmGrainCharacteristicsPersistenceFlag = sei.m_filmGrainCharacteristicsPersistenceFlag;
 }
 
+#if JVET_AG0328
+SEIFilmGrainRegionsCharacteristics::SEIFilmGrainRegionsCharacteristics(const SEIFilmGrainRegionsCharacteristics& sei)
+{
+  m_filmGrainRegionsCharacteristicsCancelFlag = sei.m_filmGrainRegionsCharacteristicsCancelFlag;
+  m_filmGrainRegionsModelId = sei.m_filmGrainRegionsModelId;
+  m_fgr_separateColourDescriptionPresentFlag = sei.m_fgr_separateColourDescriptionPresentFlag;
+  m_filmGrainRegionsBitDepthLumaMinus8 = sei.m_filmGrainRegionsBitDepthLumaMinus8;
+  m_filmGrainRegionsBitDepthChromaMinus8 = sei.m_filmGrainRegionsBitDepthChromaMinus8;
+  m_filmGrainRegionsFullRangeFlag = sei.m_filmGrainRegionsFullRangeFlag;
+  m_filmGrainRegionsColourPrimaries = sei.m_filmGrainRegionsColourPrimaries;
+  m_filmGrainRegionsTransferCharacteristics = sei.m_filmGrainRegionsTransferCharacteristics;
+  m_filmGrainRegionsMatrixCoeffs = sei.m_filmGrainRegionsMatrixCoeffs;
+  m_fgr_blendingModeId = sei.m_fgr_blendingModeId;
+  m_fgr_log2ScaleFactor = sei.m_fgr_log2ScaleFactor;
+  for (int i = 0; i < MAX_NUM_COMPONENT; i++)
+  {
+    m_fgr_compModel[i].presentFlag = sei.m_fgr_compModel[i].presentFlag;
+    m_fgr_compModel[i].numModelValues = sei.m_fgr_compModel[i].numModelValues;
+    m_fgr_compModel[i].numIntensityIntervals = sei.m_fgr_compModel[i].numIntensityIntervals;
+    m_fgr_compModel[i].intensityValues = sei.m_fgr_compModel[i].intensityValues;
+  }
+  m_filmGrainRegionsCharacteristicsPersistenceFlag = sei.m_filmGrainRegionsCharacteristicsPersistenceFlag;
+
+  // region-based specific parameters
+  m_fgr_region_information_present_flag = sei.m_fgr_region_information_present_flag;
+  m_fgr_region_based_adaptation_flag = sei.m_fgr_region_based_adaptation_flag;
+  m_fgr_active_regions_number = sei.m_fgr_active_regions_number;
+  if(m_fgr_region_based_adaptation_flag)
+  {
+    fgr_active_regions.resize(m_fgr_active_regions_number);
+    for (int regionId = 0 ; regionId < m_fgr_active_regions_number ; regionId++)
+    {
+        fgr_active_regions[regionId].m_fgr_region_top = sei.fgr_active_regions[regionId].m_fgr_region_top;
+        fgr_active_regions[regionId].m_fgr_region_left = sei.fgr_active_regions[regionId].m_fgr_region_left;
+        fgr_active_regions[regionId].m_fgr_region_width = sei.fgr_active_regions[regionId].m_fgr_region_width;
+        fgr_active_regions[regionId].m_fgr_region_height = sei.fgr_active_regions[regionId].m_fgr_region_height;
+        fgr_active_regions[regionId].m_film_grain_enabled_flag = sei.fgr_active_regions[regionId].m_film_grain_enabled_flag;
+        for (int i = 0; i < MAX_NUM_COMPONENT; i++)
+        {
+            fgr_active_regions[regionId].m_fgr_regionIntervals[i].m_fgr_region_interval_min = sei.fgr_active_regions[regionId].m_fgr_regionIntervals[i].m_fgr_region_interval_min;
+            fgr_active_regions[regionId].m_fgr_regionIntervals[i].m_fgr_region_interval_max = sei.fgr_active_regions[regionId].m_fgr_regionIntervals[i].m_fgr_region_interval_max;
+        }
+    }
+  }
+  m_fgr_alpha_channel_adaptation_flag = sei.m_fgr_alpha_channel_adaptation_flag;
+}
+#endif
+
 SEIContentLightLevelInfo::SEIContentLightLevelInfo(const SEIContentLightLevelInfo& sei)
 {
   m_maxContentLightLevel = sei.m_maxContentLightLevel;
diff --git a/source/Lib/CommonLib/SEI.h b/source/Lib/CommonLib/SEI.h
index 3396d9b53..7d26d8d95 100644
--- a/source/Lib/CommonLib/SEI.h
+++ b/source/Lib/CommonLib/SEI.h
@@ -102,6 +102,9 @@ public:
     SEI_PROCESSING_ORDER = 213,
 #if JVET_AF0310_PO_NESTING
     SEI_PROCESSING_ORDER_NESTING = 214,
+#endif
+#if JVET_AG0328
+    FILM_GRAIN_REGIONS_CHARACTERISTICS           = 215, //arbitrary paylaod type number, to be decided by JVET
 #endif
   };
 
@@ -188,6 +191,9 @@ public:
     switch (payloadType)
     {
     case SEI::PayloadType::FILM_GRAIN_CHARACTERISTICS:
+#if JVET_AG0328
+    case SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS:
+#endif
     case SEI::PayloadType::POST_FILTER_HINT:
     case SEI::PayloadType::CONTENT_LIGHT_LEVEL_INFO:
     case SEI::PayloadType::NEURAL_NETWORK_POST_FILTER_CHARACTERISTICS:
@@ -999,6 +1005,75 @@ public:
   bool      m_filmGrainCharacteristicsPersistenceFlag;
 };
 
+#if JVET_AG0328
+class SEIFilmGrainRegionsCharacteristics : public SEI
+{
+public:
+  PayloadType payloadType() const { return PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS; }
+
+  SEIFilmGrainRegionsCharacteristics() {}
+  SEIFilmGrainRegionsCharacteristics(const SEIFilmGrainRegionsCharacteristics& sei);
+  virtual ~SEIFilmGrainRegionsCharacteristics() {}
+
+  bool        m_filmGrainRegionsCharacteristicsCancelFlag;
+  uint8_t     m_filmGrainRegionsModelId;
+  bool        m_fgr_separateColourDescriptionPresentFlag;
+  uint8_t     m_filmGrainRegionsBitDepthLumaMinus8;
+  uint8_t     m_filmGrainRegionsBitDepthChromaMinus8;
+  bool        m_filmGrainRegionsFullRangeFlag;
+  uint8_t     m_filmGrainRegionsColourPrimaries;
+  uint8_t     m_filmGrainRegionsTransferCharacteristics;
+  uint8_t     m_filmGrainRegionsMatrixCoeffs;
+  uint8_t     m_fgr_blendingModeId;
+  uint8_t     m_fgr_log2ScaleFactor;
+
+  struct CompModelIntensityValues
+  {
+    uint8_t intensityIntervalLowerBound;
+    uint8_t intensityIntervalUpperBound;
+    std::vector<int> compModelValue;
+  };
+
+  struct CompModel
+  {
+    bool  presentFlag;
+    uint8_t numModelValues;
+    uint8_t numIntensityIntervals;
+    std::vector<CompModelIntensityValues> intensityValues;
+  };
+
+  CompModel m_fgr_compModel[MAX_NUM_COMPONENT];
+ 
+  // Region-based specific parameters
+  bool      m_fgr_region_information_present_flag;
+  bool      m_fgr_region_based_adaptation_flag;
+  uint8_t   m_fgr_active_regions_number;
+
+  struct RegionIntervals
+  {
+    uint16_t m_fgr_region_interval_min;
+    uint16_t m_fgr_region_interval_max;
+  };
+
+  struct RegionCharacteristics
+  {
+    uint16_t    m_fgr_region_top;
+    uint16_t    m_fgr_region_left;
+    uint16_t    m_fgr_region_width;
+    uint16_t    m_fgr_region_height;
+    bool        m_film_grain_enabled_flag;
+    RegionIntervals m_fgr_regionIntervals[MAX_NUM_COMPONENT];
+  };
+
+  std::vector<RegionCharacteristics> fgr_active_regions;
+
+  // option 2 alpha mask
+  bool m_fgr_alpha_channel_adaptation_flag;
+    
+  bool      m_filmGrainRegionsCharacteristicsPersistenceFlag;
+};
+#endif
+
 class SEIContentLightLevelInfo : public SEI
 {
 public:
diff --git a/source/Lib/CommonLib/SEIFilmGrainSynthesizer.cpp b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.cpp
index a68ac0d3b..6b41dd510 100644
--- a/source/Lib/CommonLib/SEIFilmGrainSynthesizer.cpp
+++ b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.cpp
@@ -1261,3 +1261,1337 @@ uint32_t SEIFilmGrainSynthesizer::fgsSimulationBlending_32x32(fgsProcessArgs *in
   return FGS_SUCCESS;
 }
 
+#if JVET_AG0328
+SEIFilmGrainRegionsSynthesizer::SEIFilmGrainRegionsSynthesizer()
+  : m_width(0)
+  , m_height(0)
+  , m_chromaFormat(ChromaFormat::UNDEFINED)
+  , m_bitDepth(0)
+  , m_idrPicId(0)
+  , m_grainSynt(nullptr)
+  , m_fgsBlkSize(8)
+  , m_poc(0)
+  , m_errorCode(0)
+  , m_fgrParameters(nullptr)
+{
+
+}
+
+void SEIFilmGrainRegionsSynthesizer::create(uint32_t width, uint32_t height, ChromaFormat fmt, uint8_t bitDepth, uint32_t idrPicId)
+{
+  m_width             = width;
+  m_height            = height;
+  m_chromaFormat      = fmt;
+  m_bitDepth          = bitDepth;
+  m_idrPicId          = idrPicId;
+  m_fgsBlkSize        = 8;
+  m_errorCode         = 0;
+  int extended_height = ((height >> 5) + ((height & 0x1F) ? 1 : 0)) << 5;
+
+  if (!m_grainSynt)
+    m_grainSynt       = new GrainSynthesisStruct;
+  if (!m_fgrParameters)
+    m_fgrParameters   = new SEIFilmGrainRegionsCharacteristics;
+  // test allocation map of FGS enabled map
+  m_fgrArgs.fgr_grain_enabled_map.resize(width * extended_height);
+  // test allocation map of intervals min/max map
+  for (int i = 0 ; i < MAX_NUM_COMPONENT; i++)
+  {
+    m_fgrArgs.fgr_regionIntervals_min_map[i].resize(width * extended_height);
+    m_fgrArgs.fgr_regionIntervals_max_map[i].resize(width * extended_height);
+  }
+}
+
+SEIFilmGrainRegionsSynthesizer::~SEIFilmGrainRegionsSynthesizer()
+{
+  destroy();
+}
+
+void SEIFilmGrainRegionsSynthesizer::fgrInit()
+{
+  deriveFGSBlkSize();
+  dataBaseGen();
+}
+
+void SEIFilmGrainRegionsSynthesizer::destroy()
+{
+  if (m_fgrParameters)
+    delete m_fgrParameters;
+  if (m_grainSynt)
+    delete m_grainSynt;
+}
+
+void SEIFilmGrainRegionsSynthesizer::grainSynthesizeAndBlend(PelStorage* pGrainBuf, bool isIdrPic)
+{
+  uint8_t     compCtr; /* number of color components */
+  uint8_t     color_offset[MAX_NUM_COMPONENT];
+  uint32_t    widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT];
+  ptrdiff_t   strideComp[MAX_NUM_COMPONENT];
+  uint32_t *  offsetsArr[MAX_NUM_COMPONENT];
+  Pel *       decComp[MAX_NUM_COMPONENT];
+  uint32_t    pseudoRandValEc;
+  uint32_t    picOffset;
+
+  /* from SMPTE RDD5 */
+  color_offset[0] = COLOUR_OFFSET_LUMA;
+  color_offset[1] = COLOUR_OFFSET_CR;
+  color_offset[2] = COLOUR_OFFSET_CB;
+
+  if (0 != m_fgrParameters->m_filmGrainRegionsCharacteristicsCancelFlag)
+  {
+    return;
+  }
+
+  const int numComp = getNumberValidComponents(m_chromaFormat);
+
+  widthComp[0]  = m_width;
+  heightComp[0] = m_height;
+
+  if (ChromaFormat::_420 == m_chromaFormat)
+  {
+    widthComp[1]  = (m_width >> 1);
+    widthComp[2]  = (m_width >> 1);
+    heightComp[1] = (m_height >> 1);
+    heightComp[2] = (m_height >> 1);
+  }
+  else if (ChromaFormat::_422 == m_chromaFormat)
+  {
+    widthComp[1]  = (m_width >> 1);
+    widthComp[2]  = (m_width >> 1);
+    heightComp[1] = m_height;
+    heightComp[2] = m_height;
+  }
+
+  /*Allocate memory for offsets assuming 16x16 block size,
+  32x32 will need lesser than this*/
+  uint32_t maxNumBlocks = ((m_width >> 4) + 1) * ((m_height >> 4) + 1);
+
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    offsetsArr[compCtr] = new uint32_t[maxNumBlocks];
+  }
+
+  decComp[0] = pGrainBuf->bufs[0].buf;
+  decComp[1] = pGrainBuf->bufs[1].buf;
+  decComp[2] = pGrainBuf->bufs[2].buf;
+
+  /* component strides */
+  strideComp[0] = pGrainBuf->bufs[0].stride;
+  strideComp[1] = 0;
+  strideComp[2] = 0;
+
+  if (isChromaEnabled(m_chromaFormat))
+  {
+    strideComp[1] = pGrainBuf->bufs[1].stride;
+    strideComp[2] = pGrainBuf->bufs[2].stride;
+  }
+
+  int32_t numBlks_x[MAX_NUM_COMPONENT];
+  int32_t numBlks_y[MAX_NUM_COMPONENT];
+
+  picOffset = m_poc;
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    if (BLK_32 == m_fgsBlkSize)
+    {
+      numBlks_x[compCtr]         = (widthComp[compCtr] >> 5) + ((widthComp[compCtr] & 0x1F) ? 1 : 0);
+      numBlks_y[compCtr]         = (heightComp[compCtr] >> 5) + ((heightComp[compCtr] & 0x1F) ? 1 : 0);
+    }
+    else
+    {
+      numBlks_x[compCtr]         = (widthComp[compCtr] >> 4) + ((widthComp[compCtr] & 0xF) ? 1 : 0);
+      numBlks_y[compCtr]         = (heightComp[compCtr] >> 4) + ((heightComp[compCtr] & 0xF) ? 1 : 0);
+    }
+  }
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    if (1 == m_fgrParameters->m_fgr_compModel[compCtr].presentFlag)
+    {
+      uint32_t *tmp = offsetsArr[compCtr];
+      int       i, j;
+
+      /* Seed initialization for current picture*/
+      pseudoRandValEc = seedLUT[((picOffset + color_offset[compCtr]) & 0xFF)];
+
+      for (i = 0; i < numBlks_y[compCtr]; i++)
+      {
+        for (j = 0; j < numBlks_x[compCtr]; j++)
+        {
+          *tmp            = pseudoRandValEc;
+          pseudoRandValEc = prng(pseudoRandValEc);
+          tmp++;
+        }
+      }
+    }
+  }
+
+  m_fgrArgs.numComp = numComp;
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    if (1 == m_fgrParameters->m_fgr_compModel[compCtr].presentFlag)
+    {
+      m_fgrArgs.decComp[compCtr]    = decComp[compCtr];
+      m_fgrArgs.widthComp[compCtr]  = widthComp[compCtr];
+      m_fgrArgs.strideComp[compCtr] = strideComp[compCtr];
+      m_fgrArgs.fgsOffsets[compCtr] = offsetsArr[compCtr];
+
+      if (BLK_32 == m_fgsBlkSize)
+      {
+        m_fgrArgs.heightComp[compCtr] = numBlks_y[compCtr] * BLK_32;
+      }
+      else
+      {
+        m_fgrArgs.heightComp[compCtr] = numBlks_y[compCtr] * BLK_16;
+      }
+    }
+  }
+  m_fgrArgs.pFgrParameters  = m_fgrParameters;
+  m_fgrArgs.blkSize = m_fgsBlkSize;
+  m_fgrArgs.bitDepth =   m_bitDepth;
+  m_fgrArgs.pGrainSynt = m_grainSynt;
+
+  int extended_height = ((m_height >> 5) + ((m_height & 0x1F) ? 1 : 0)) << 5;
+
+  //region based maps setting
+  if (m_fgrParameters->m_fgr_region_based_adaptation_flag)
+  {
+    // -------FILM GRAIN ENABLED FLAG--------
+    for (int j = 0; j < extended_height; j++)
+    {
+      for (int i = 0; i < m_width; i++)
+      {
+        m_fgrArgs.fgr_grain_enabled_map[j*m_width+i] = true;
+        for (int k = 0 ; k < m_fgrParameters->m_fgr_active_regions_number; k++)
+        {
+          if ((i >= m_fgrParameters->fgr_active_regions[k].m_fgr_region_left) && 
+              (i < m_fgrParameters->fgr_active_regions[k].m_fgr_region_left+m_fgrParameters->fgr_active_regions[k].m_fgr_region_width) &&
+              (j >= m_fgrParameters->fgr_active_regions[k].m_fgr_region_top) &&
+              (j < m_fgrParameters->fgr_active_regions[k].m_fgr_region_top+m_fgrParameters->fgr_active_regions[k].m_fgr_region_height))
+          {
+            m_fgrArgs.fgr_grain_enabled_map[j * m_width + i] = m_fgrParameters->fgr_active_regions[k].m_film_grain_enabled_flag;
+          }
+        }
+      }
+    }
+
+
+    // -------FILM GRAIN MIN/MAX INTERVALS --------
+    for (int i = 0; i < m_width; i++)
+    {
+      for (int j = 0; j < extended_height; j++)
+      {
+        for (compCtr = 0; compCtr < numComp; compCtr++)
+        {
+          m_fgrArgs.fgr_regionIntervals_min_map[compCtr][j * m_width + i] = 0;
+          m_fgrArgs.fgr_regionIntervals_max_map[compCtr][j * m_width + i] = (int)m_fgrArgs.pFgrParameters->m_fgr_compModel[compCtr].intensityValues.size() - 1;
+
+         for (int k = 0 ; k < m_fgrParameters->m_fgr_active_regions_number; k++)
+          {
+            if ((i >= m_fgrParameters->fgr_active_regions[k].m_fgr_region_left) &&
+                (i < m_fgrParameters->fgr_active_regions[k].m_fgr_region_left+ m_fgrParameters->fgr_active_regions[k].m_fgr_region_width) &&
+                (j >= m_fgrParameters->fgr_active_regions[k].m_fgr_region_top) &&
+                (j < m_fgrParameters->fgr_active_regions[k].m_fgr_region_top+m_fgrParameters->fgr_active_regions[k].m_fgr_region_height))
+            {
+              m_fgrArgs.fgr_regionIntervals_min_map[compCtr][j*m_width+i] = m_fgrParameters->fgr_active_regions[k].m_fgr_regionIntervals[compCtr].m_fgr_region_interval_min;
+              m_fgrArgs.fgr_regionIntervals_max_map[compCtr][j*m_width+i] = m_fgrParameters->fgr_active_regions[k].m_fgr_regionIntervals[compCtr].m_fgr_region_interval_max;
+            }
+          }
+        }
+      }
+    }
+    if (ChromaFormat::_420 == m_chromaFormat)
+    {
+      heightComp[1] = extended_height / 2;
+      heightComp[2] = extended_height / 2;
+      for (int i = 0 ; i < widthComp[2] ; i++)
+      {
+        for (int j = 0; j < heightComp[2]; j++)
+        {
+          for (compCtr = 1; compCtr < numComp; compCtr++)
+          {
+            m_fgrArgs.fgr_regionIntervals_min_map[compCtr][j*widthComp[2]+i] = m_fgrArgs.fgr_regionIntervals_min_map[compCtr][j*2*widthComp[2]*2+i*2];
+            m_fgrArgs.fgr_regionIntervals_max_map[compCtr][j*widthComp[2]+i] = m_fgrArgs.fgr_regionIntervals_max_map[compCtr][j*2*widthComp[2]*2+i*2];
+          }
+        }
+      }
+    }
+    else if (ChromaFormat::_422 == m_chromaFormat)
+    {
+        widthComp[1]  = (m_width >> 1);
+        widthComp[2]  = (m_width >> 1);
+        heightComp[1] = m_height;
+        heightComp[2] = m_height;
+        heightComp[1] = extended_height;
+        heightComp[2] = extended_height;
+      for (int i = 0 ; i < widthComp[2] ; i++)
+      {
+        for (int j = 0 ; j < heightComp[2] ; j++)
+        {
+          for (compCtr = 1; compCtr < numComp; compCtr++)
+          {
+            m_fgrArgs.fgr_regionIntervals_min_map[compCtr][j*widthComp[2]+i] = m_fgrArgs.fgr_regionIntervals_min_map[compCtr][j*widthComp[2]+i*2];
+            m_fgrArgs.fgr_regionIntervals_max_map[compCtr][j*widthComp[2]+i] = m_fgrArgs.fgr_regionIntervals_max_map[compCtr][j*widthComp[2]+i*2];
+          }
+        }
+      }
+    }
+  }
+  
+  fgrProcess(m_fgrArgs);
+
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    delete offsetsArr[compCtr];
+  }
+  return;
+}
+
+/* Function validates film grain parameters and returns 0 for valid parameters of SMPTE-RDD5 else 1*/
+/* Also down converts the chroma model values for 4:2:0 and 4:2:2 chroma_formats */
+uint8_t SEIFilmGrainRegionsSynthesizer::grainValidateParams()
+{
+  uint8_t   compCtr, intensityCtr, multiGrainCheck[MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES] = { { 0 } };
+  uint16_t  multiGrainCtr;
+  uint8_t   limitCompModelVal1[10] = { 0 }, limitCompModelVal2[10] = { 0 };
+  uint8_t   num_comp_model_pairs = 0, limitCompModelCtr, compPairMatch;
+
+  memset(m_grainSynt->intensityInterval, INTENSITY_INTERVAL_MATCH_FAIL, sizeof(m_grainSynt->intensityInterval));
+
+  if ((m_width < MIN_WIDTH) || (m_width > MAX_WIDTH) || (m_width % 4))
+  {
+    return FGS_INVALID_WIDTH; /* Width not supported */
+  }
+  if ((m_height < MIN_HEIGHT) || (m_height > MAX_HEIGHT) || (m_height % 4))
+  {
+    return FGS_INVALID_HEIGHT; /* Height not  supported */
+  }
+  if (m_chromaFormat == ChromaFormat::UNDEFINED)
+  {
+    return FGS_INVALID_CHROMA_FORMAT; /* Chroma format not supported */
+  }
+  const int numComp = getNumberValidComponents(m_chromaFormat);
+
+  if ((m_bitDepth < MIN_BIT_DEPTH) || (m_bitDepth > MAX_BIT_DEPTH))
+  {
+    return FGS_INVALID_BIT_DEPTH; /* Bit depth not supported */
+  }
+
+  if ((0 != m_fgrParameters->m_filmGrainRegionsCharacteristicsCancelFlag) &&
+      (1 != m_fgrParameters->m_filmGrainRegionsCharacteristicsCancelFlag))
+  {
+    return FGS_INVALID_FGC_CANCEL_FLAG; /* Film grain synthesis disabled */
+  }
+
+  if (FILM_GRAIN_MODEL_ID_VALUE != m_fgrParameters->m_filmGrainRegionsModelId)
+  {
+    return FGS_INVALID_GRAIN_MODEL_ID; /* Not supported */
+  }
+ 
+  if (0 != m_fgrParameters->m_fgr_separateColourDescriptionPresentFlag)
+  {
+    return FGS_INVALID_SEP_COL_DES_FLAG; /* Not supported */
+  }
+
+  if (BLENDING_MODE_VALUE != m_fgrParameters->m_fgr_blendingModeId)
+  {
+    return FGS_INVALID_BLEND_MODE; /* Not supported */
+  }
+
+  if (m_fgrParameters->m_fgr_compModel[0].presentFlag || m_fgrParameters->m_fgr_compModel[1].presentFlag || m_fgrParameters->m_fgr_compModel[2].presentFlag) {
+    if ((m_fgrParameters->m_fgr_log2ScaleFactor < MIN_LOG2SCALE_VALUE) ||
+      (m_fgrParameters->m_fgr_log2ScaleFactor > MAX_LOG2SCALE_VALUE))
+    {
+      return FGS_INVALID_LOG2_SCALE_FACTOR; /* Not supported  */
+    }
+  }
+
+  /* validation of component model present flag */
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    if ((m_fgrParameters->m_fgr_compModel[compCtr].presentFlag != true) &&
+        (m_fgrParameters->m_fgr_compModel[compCtr].presentFlag != false))
+    {
+      return FGS_INVALID_COMP_MODEL_PRESENT_FLAG; /* Not supported  */
+    }
+
+    if (m_fgrParameters->m_fgr_compModel[compCtr].presentFlag &&
+       (m_fgrParameters->m_fgr_compModel[compCtr].numModelValues > MAX_ALLOWED_MODEL_VALUES))
+    {
+      return FGS_INVALID_NUM_MODEL_VALUES; /* Not supported  */
+    }
+  }
+
+  /* validation of intensity intervals and  */
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    if (m_fgrParameters->m_fgr_compModel[compCtr].presentFlag)
+    {
+      for (intensityCtr = 0; intensityCtr < m_fgrParameters->m_fgr_compModel[compCtr].intensityValues.size(); intensityCtr++)
+      {
+        if (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalLowerBound >
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalUpperBound)
+        {
+          return FGS_INVALID_INTENSITY_BOUNDARY_VALUES; /* Not supported  */
+        }
+
+        for (multiGrainCtr = m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalLowerBound;
+             multiGrainCtr <= m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalUpperBound; multiGrainCtr++)
+        {
+          m_grainSynt->intensityInterval[compCtr][multiGrainCtr] = intensityCtr;
+          if (multiGrainCheck[compCtr][multiGrainCtr]) /* Non over lap */
+          {
+            return FGS_INVALID_INTENSITY_BOUNDARY_VALUES; /* Not supported  */
+          }
+          else
+          {
+            multiGrainCheck[compCtr][multiGrainCtr] = 1;
+          }
+        }
+
+        m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue.resize(MAX_NUM_MODEL_VALUES);
+        /* default initialization for cut off frequencies */
+        if (1 == m_fgrParameters->m_fgr_compModel[compCtr].numModelValues)
+        {
+          m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] = DEFAULT_HORZ_CUT_OFF_FREQUENCY;
+          m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] = m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1];
+        }
+        else if (2 == m_fgrParameters->m_fgr_compModel[compCtr].numModelValues)
+        {
+          m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] = m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1];
+        }
+
+        /* Error check on model component value */
+        if (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] > (MAX_STANDARD_DEVIATION << (m_bitDepth - BIT_DEPTH_8)))
+        {
+          return FGS_INVALID_STANDARD_DEVIATION; /* Not supported  */
+        }
+        else if ((m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] < MIN_CUT_OFF_FREQUENCY) ||
+                 (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] > MAX_CUT_OFF_FREQUENCY))
+        {
+          return FGS_INVALID_CUT_OFF_FREQUENCIES; /* Not supported  */
+        }
+        else if ((m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] < MIN_CUT_OFF_FREQUENCY) ||
+                 (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] > MAX_CUT_OFF_FREQUENCY))
+        {
+          return FGS_INVALID_CUT_OFF_FREQUENCIES; /* Not supported  */
+        }
+
+        /* conversion of component model values for 4:2:0 and 4:4:4 */
+        if (ChromaFormat::_444 != m_chromaFormat && (compCtr > 0))
+        {
+          if (ChromaFormat::_420 == m_chromaFormat) /* 4:2:0 */
+          {
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] >>= 1;
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] =
+              CLIP3(MIN_CUT_OFF_FREQUENCY, MAX_CUT_OFF_FREQUENCY,
+              (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] << 1));
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] =
+              CLIP3(MIN_CUT_OFF_FREQUENCY, MAX_CUT_OFF_FREQUENCY,
+              (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] << 1));
+
+          }
+          else if (ChromaFormat::_422 == m_chromaFormat) /* 4:2:2 */
+          {
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] =
+              (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] * SCALE_DOWN_422) >> Q_FORMAT_SCALING;
+
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] =
+              CLIP3(MIN_CUT_OFF_FREQUENCY, MAX_CUT_OFF_FREQUENCY,
+              (m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] << 1));
+          }
+        }
+
+        compPairMatch = 0;
+        for (limitCompModelCtr = 0; limitCompModelCtr <= num_comp_model_pairs; limitCompModelCtr++)
+        {
+          if ((limitCompModelVal1[limitCompModelCtr] ==
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1]) &&
+            (limitCompModelVal2[limitCompModelCtr] ==
+              m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2]))
+          {
+            compPairMatch = 1;
+          }
+        }
+
+        if (0 == compPairMatch)
+        {
+          num_comp_model_pairs++;
+          /* max allowed pairs are 10 as per SMPTE -RDD5*/
+          if (num_comp_model_pairs > MAX_ALLOWED_COMP_MODEL_PAIRS)
+          {
+            return FGS_INVALID_NUM_CUT_OFF_FREQ_PAIRS; /* Not supported  */
+          }
+          limitCompModelVal1[num_comp_model_pairs - 1] =
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1];
+          limitCompModelVal2[num_comp_model_pairs - 1] =
+            m_fgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2];
+        }
+      }
+    }
+  }
+  return FGS_SUCCESS; /* Success */
+}
+
+
+
+
+void SEIFilmGrainRegionsSynthesizer::deriveFGSBlkSize()
+{
+  uint32_t picSizeInLumaSamples = m_height * m_width;
+  if (picSizeInLumaSamples <= (1920 * 1080))
+  {
+    m_fgsBlkSize = BLK_8;
+  }
+  else if (picSizeInLumaSamples <= (3840 * 2160))
+  {
+    m_fgsBlkSize = BLK_16;
+  }
+  else
+  {
+    m_fgsBlkSize = BLK_32;
+  }
+}
+void SEIFilmGrainRegionsSynthesizer::dataBaseGen()
+{
+  uint32_t      pseudoRandValEhv;
+  uint8_t       h, v; /* Horizaontal and vertical cut off frequencies (+2)*/
+  uint32_t      ScaleCutOffFh, ScaleCutOffFv, l, r, i, j, k;
+  int32_t       B[DATA_BASE_SIZE][DATA_BASE_SIZE], IDCT[DATA_BASE_SIZE][DATA_BASE_SIZE];
+  int32_t       Grain[DATA_BASE_SIZE][DATA_BASE_SIZE];
+
+  const TMatrixCoeff* Tmp = g_trCoreDCT2P64[TRANSFORM_FORWARD][0];
+  const int transform_scale = 9;                  // upscaling of original transform as specified in VVC (for 64x64 block)
+  const int add_1st = 1 << (transform_scale - 1);
+
+  TMatrixCoeff T[DATA_BASE_SIZE][DATA_BASE_SIZE]; // Original
+  TMatrixCoeff TT[DATA_BASE_SIZE][DATA_BASE_SIZE]; // Transpose
+  for (int x = 0; x < DATA_BASE_SIZE; x++)
+  {
+    for (int y = 0; y < DATA_BASE_SIZE; y++)
+    {
+      T[x][y] = Tmp[x * 64 + y]; /* Matrix Original */
+      TT[y][x] = Tmp[x * 64 + y]; /* Matrix Transpose */
+    }
+  }
+
+  for (h = 0; h < NUM_CUT_OFF_FREQ; h++)
+  {
+    for (v = 0; v < NUM_CUT_OFF_FREQ; v++)
+    {
+      memset(&B, 0, DATA_BASE_SIZE*DATA_BASE_SIZE * sizeof(int32_t));
+      memset(&IDCT, 0, DATA_BASE_SIZE*DATA_BASE_SIZE * sizeof(int32_t));
+      memset(&Grain, 0, DATA_BASE_SIZE*DATA_BASE_SIZE * sizeof(int32_t));
+      ScaleCutOffFh = ((h + 3) << 2) - 1;
+      ScaleCutOffFv = ((v + 3) << 2) - 1;
+
+      /* ehv : seed to be used for the psudo random generator for a given h and v */
+      pseudoRandValEhv = seedLUT[h + v * 13];
+
+      for (l = 0, r = 0; l <= ScaleCutOffFv; l++)
+      {
+        for (k = 0; k <= ScaleCutOffFh; k += 4)
+        {
+          B[k][l] = gaussianLUT[pseudoRandValEhv % 2048];
+          B[k + 1][l] = gaussianLUT[(pseudoRandValEhv + 1) % 2048];
+          B[k + 2][l] = gaussianLUT[(pseudoRandValEhv + 2) % 2048];
+          B[k + 3][l] = gaussianLUT[(pseudoRandValEhv + 3) % 2048];
+          r++;
+          pseudoRandValEhv = prng(pseudoRandValEhv);
+        }
+      }
+      B[0][0] = 0;
+
+      for (i = 0; i < DATA_BASE_SIZE; i++)
+      {
+        for (j = 0; j < DATA_BASE_SIZE; j++)
+        {
+          for (k = 0; k < DATA_BASE_SIZE; k++)
+          {
+            IDCT[i][j] += TT[i][k] * B[k][j];
+          }
+          IDCT[i][j] += add_1st;
+          IDCT[i][j] = IDCT[i][j] >> transform_scale;
+        }
+      }
+
+      for (i = 0; i < DATA_BASE_SIZE; i++)
+      {
+        for (j = 0; j < DATA_BASE_SIZE; j++)
+        {
+          for (k = 0; k < DATA_BASE_SIZE; k++)
+          {
+            Grain[i][j] += IDCT[i][k] * T[k][j];
+          }
+          Grain[i][j] += add_1st;
+          Grain[i][j] = Grain[i][j] >> transform_scale;
+          m_grainSynt->dataBase[h][v][j][i] = CLIP3(-127, 127, Grain[i][j]);
+        }
+      }
+
+      /* De-blocking at horizontal block edges */
+      for (l = 0; l < DATA_BASE_SIZE; l += m_fgsBlkSize)
+      {
+        for (k = 0; k < DATA_BASE_SIZE; k++)
+        {
+          m_grainSynt->dataBase[h][v][l][k] = ((m_grainSynt->dataBase[h][v][l][k]) * deblockFactor[v]) >> 7;
+          m_grainSynt->dataBase[h][v][l + m_fgsBlkSize - 1][k] = ((m_grainSynt->dataBase[h][v][l + m_fgsBlkSize - 1][k]) * deblockFactor[v]) >> 7;
+        }
+      }
+    }
+  }
+  return;
+}
+
+uint32_t SEIFilmGrainRegionsSynthesizer::prng(uint32_t x_r)
+{
+  uint32_t addVal;
+  addVal  = (1 + ((x_r & (POS_2)) > 0) + ((x_r & (POS_30)) > 0)) % 2;
+  x_r     = (x_r << 1) + addVal;
+  return x_r;
+}
+
+uint32_t SEIFilmGrainRegionsSynthesizer::fgrProcess(fgrProcessArgs &inArgs)
+{
+  uint32_t errorCode;
+  uint8_t  blkSize = inArgs.blkSize;
+  if (blkSize == 8)
+    errorCode = fgrSimulationBlending_8x8(&inArgs);
+  else if (blkSize == 16)
+    errorCode = fgrSimulationBlending_16x16(&inArgs);
+  else if (blkSize == 32)
+    errorCode = fgrSimulationBlending_32x32(&inArgs);
+  else
+    errorCode = FGS_FAIL;
+
+  return errorCode;
+}
+
+void SEIFilmGrainRegionsSynthesizer::deblockGrainStripe(Pel *grainStripe, uint32_t widthComp, uint32_t heightComp,
+  uint32_t strideComp, uint32_t blkSize)
+{
+  int32_t  left1, left0, right0, right1;
+  uint32_t pos, vertCtr;
+
+  uint32_t widthCropped = (widthComp - blkSize);
+
+  for (vertCtr = 0; vertCtr < heightComp; vertCtr++)
+  {
+    for (pos = 0; pos < widthCropped; pos += blkSize)
+    {
+      left1 = *(grainStripe + blkSize - 2);
+      left0 = *(grainStripe + blkSize - 1);
+      right0 = *(grainStripe + blkSize + 0);
+      right1 = *(grainStripe + blkSize + 1);
+      *(grainStripe + blkSize + 0) = (left0 + (right0 << 1) + right1) >> 2;
+      *(grainStripe + blkSize - 1) = (left1 + (left0 << 1) + right0) >> 2;
+      grainStripe += blkSize;
+    }
+    grainStripe = grainStripe + (strideComp - pos);
+  }
+  return;
+}
+
+void SEIFilmGrainRegionsSynthesizer::blendStripe(Pel *decSampleHbdOffsetY, Pel *grainStripe, uint32_t widthComp,
+                                          ptrdiff_t strideSrc, ptrdiff_t strideGrain, uint32_t blockHeight,
+                                          uint8_t bitDepth, uint32_t offset_in_map,std::vector<bool> fgr_grain_enabled_map, uint32_t CompFactor)
+{
+  uint32_t k, l;
+  uint16_t maxRange;
+  maxRange = (1 << bitDepth) - 1;
+
+  int32_t  grainSample;
+  uint16_t decodeSampleHbd;
+  uint8_t bitDepthShift = (bitDepth - BIT_DEPTH_8);
+  ptrdiff_t bufInc        = (strideSrc - widthComp);
+  ptrdiff_t grainBufInc   = (strideGrain - widthComp);
+  if (CompFactor==1)
+  {
+    for (l = 0; l < blockHeight; l++) // y direction
+    {
+      for (k = 0; k < widthComp; k++) // x direction
+      {
+        decodeSampleHbd = *decSampleHbdOffsetY;
+        grainSample = *grainStripe;
+        grainSample <<= bitDepthShift;
+        grainSample = CLIP3(0, maxRange, grainSample + decodeSampleHbd);
+        if (fgr_grain_enabled_map[(offset_in_map + l*widthComp + k)])
+          *decSampleHbdOffsetY = (Pel)grainSample;
+        //else
+        //  *decSampleHbdOffsetY = (Pel)0; /debud mask
+        decSampleHbdOffsetY++;
+        grainStripe++;
+      }
+      decSampleHbdOffsetY += bufInc;
+      grainStripe += grainBufInc;
+    }
+
+  }
+  else
+  {
+    for (l = 0; l < blockHeight; l++) // y direction
+    {
+      for (k = 0; k < widthComp; k++) // x direction
+      {
+        decodeSampleHbd = *decSampleHbdOffsetY;
+        grainSample = *grainStripe;
+        grainSample <<= bitDepthShift;
+        grainSample = CLIP3(0, maxRange, grainSample + decodeSampleHbd);
+        if (fgr_grain_enabled_map[(offset_in_map + ((l*CompFactor)*(widthComp*CompFactor) + k*CompFactor))])
+          *decSampleHbdOffsetY = (Pel)grainSample;
+        // else
+        //   *decSampleHbdOffsetY = (Pel)0; //debug mask
+        decSampleHbdOffsetY++;
+        grainStripe++;
+      }
+      decSampleHbdOffsetY += bufInc;
+      grainStripe += grainBufInc;
+    }
+  }
+  return;
+}
+
+void SEIFilmGrainRegionsSynthesizer::blendStripe_32x32(Pel *decSampleHbdOffsetY, Pel *grainStripe, uint32_t widthComp,
+                                                ptrdiff_t strideSrc, ptrdiff_t strideGrain, uint32_t blockHeight,
+                                                uint8_t bitDepth, uint32_t offset_in_map,std::vector<bool> fgr_grain_enabled_map, uint32_t CompFactor)
+{
+  uint32_t k, l;
+  uint16_t maxRange;
+  maxRange = (1 << bitDepth) - 1;
+
+  int32_t  grainSample;
+  uint16_t decodeSampleHbd;
+  uint8_t bitDepthShift = (bitDepth - BIT_DEPTH_8);
+  ptrdiff_t bufInc        = (strideSrc - widthComp);
+  ptrdiff_t grainBufInc   = (strideGrain - widthComp);
+
+  if (CompFactor==1)
+  {
+    for (l = 0; l < blockHeight; l++) /* y direction */
+    {
+      for (k = 0; k < widthComp; k++) /* x direction */
+      {
+        decodeSampleHbd = *decSampleHbdOffsetY;
+        grainSample = *grainStripe;
+        grainSample <<= bitDepthShift;
+        grainSample = CLIP3(0, maxRange, grainSample + decodeSampleHbd);
+        if (fgr_grain_enabled_map[(offset_in_map + l*widthComp + k)])
+          *decSampleHbdOffsetY = (Pel)grainSample;
+      //  else
+      //    *decSampleHbdOffsetY = (Pel)0;
+        decSampleHbdOffsetY++;
+        grainStripe++;
+      }
+      decSampleHbdOffsetY += bufInc;
+      grainStripe += grainBufInc;
+    }
+
+  }
+  else
+  {
+    for (l = 0; l < blockHeight; l++) /* y direction */
+    {
+      for (k = 0; k < widthComp; k++) /* x direction */
+      {
+        decodeSampleHbd = *decSampleHbdOffsetY;
+        grainSample = *grainStripe;
+        grainSample <<= bitDepthShift;
+        grainSample = CLIP3(0, maxRange, grainSample + decodeSampleHbd);
+        if (fgr_grain_enabled_map[(offset_in_map*CompFactor*CompFactor + (l*widthComp*CompFactor + k)*CompFactor)])
+          *decSampleHbdOffsetY = (Pel)grainSample;
+        //else
+        //  *decSampleHbdOffsetY = (Pel)0;
+        decSampleHbdOffsetY++;
+        grainStripe++;
+      }
+      decSampleHbdOffsetY += bufInc;
+      grainStripe += grainBufInc;
+    }
+  }  return;
+}
+
+Pel SEIFilmGrainRegionsSynthesizer::blockAverage_8x8(Pel *decSampleBlk8, ptrdiff_t widthComp, uint16_t *pNumSamples,
+                                              uint8_t ySize, uint8_t xSize, uint8_t bitDepth)
+{
+  uint32_t blockAvg = 0;
+  uint8_t  k;
+  uint8_t l;
+
+  for (k = 0; k < ySize; k++)
+  {
+    for (l = 0; l < xSize; l++)
+    {
+      blockAvg += *decSampleBlk8;
+      decSampleBlk8++;
+    }
+    decSampleBlk8 += widthComp - xSize;
+  }
+
+  blockAvg = blockAvg >> (BLK_8_shift + (bitDepth - BIT_DEPTH_8));
+  *pNumSamples = BLK_AREA_8x8;
+
+  return blockAvg;
+}
+
+uint32_t SEIFilmGrainRegionsSynthesizer::blockAverage_16x16(Pel *decSampleBlk8, ptrdiff_t widthComp, uint16_t *pNumSamples,
+                                                     uint8_t ySize, uint8_t xSize, uint8_t bitDepth)
+{
+  uint32_t blockAvg = 0;
+  uint8_t  k;
+  uint8_t l;
+  for (k = 0; k < ySize; k++)
+  {
+    for (l = 0; l < xSize; l++)
+    {
+      blockAvg += *decSampleBlk8;
+      decSampleBlk8++;
+    }
+    decSampleBlk8 += widthComp - xSize;
+  }
+
+  // blockAvg = blockAvg >> (BLK_16_shift + (bitDepth - BIT_DEPTH_8));
+  // If BLK_16 is not used or changed BLK_AREA_16x16 has to be changed
+  *pNumSamples = BLK_AREA_16x16;
+  return blockAvg;
+}
+
+uint32_t SEIFilmGrainRegionsSynthesizer::blockAverage_32x32(Pel *decSampleBlk32, ptrdiff_t strideComp, uint8_t bitDepth)
+{
+  uint32_t blockAvg = 0;
+  uint8_t  k;
+  uint8_t l;
+  ptrdiff_t bufInc = strideComp - BLK_32;
+  for (k = 0; k < BLK_32; k++)
+  {
+    for (l = 0; l < BLK_32; l++)
+    {
+      blockAvg += *decSampleBlk32++;
+    }
+    decSampleBlk32 += bufInc;
+  }
+  blockAvg = blockAvg >> (BLK_32_shift + (bitDepth - BIT_DEPTH_8));
+  return blockAvg;
+}
+
+void SEIFilmGrainRegionsSynthesizer::simulateGrainBlk8x8(Pel *grainStripe, uint32_t grainStripeOffsetBlk8,
+  GrainSynthesisStruct *grain_synt, uint32_t width,
+  uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset,
+  uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize)
+{
+  uint32_t l;
+  int8_t * database_h_v = &grain_synt->dataBase[h][v][lOffset][kOffset];
+  grainStripe += grainStripeOffsetBlk8;
+  uint32_t k;
+  for (l = 0; l < BLK_8; l++) /* y direction */
+  {
+    for (k = 0; k < xSize; k++) /* x direction */
+    {
+      *grainStripe = ((scaleFactor * (*database_h_v)) >> (log2ScaleFactor + GRAIN_SCALE));
+      grainStripe++;
+      database_h_v++;
+    }
+    grainStripe += width - xSize;
+    database_h_v += DATA_BASE_SIZE - xSize;
+  }
+  return;
+}
+
+void SEIFilmGrainRegionsSynthesizer::simulateGrainBlk16x16(Pel *grainStripe, uint32_t grainStripeOffsetBlk8,
+  GrainSynthesisStruct *grain_synt, uint32_t width,
+  uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset,
+  uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize)
+{
+  uint32_t l;
+  int8_t * database_h_v = &grain_synt->dataBase[h][v][lOffset][kOffset];
+  grainStripe += grainStripeOffsetBlk8;
+  uint32_t k;
+  for (l = 0; l < BLK_16; l++) /* y direction */
+  {
+    for (k = 0; k < xSize; k++) /* x direction */
+    {
+      *grainStripe = (int16_t)(((int32_t)scaleFactor * (*database_h_v)) >> (log2ScaleFactor + GRAIN_SCALE));
+      grainStripe++;
+      database_h_v++;
+    }
+    grainStripe += width - xSize;
+    database_h_v += DATA_BASE_SIZE - xSize;
+  }
+  return;
+}
+
+void SEIFilmGrainRegionsSynthesizer::simulateGrainBlk32x32(Pel *grainStripe, uint32_t grainStripeOffsetBlk32,
+  GrainSynthesisStruct *grain_synt, uint32_t width,
+  uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset,
+  uint32_t lOffset, uint8_t h, uint8_t v)
+{
+  uint32_t l;
+  int8_t * database_h_v = &grain_synt->dataBase[h][v][lOffset][kOffset];
+  grainStripe += grainStripeOffsetBlk32;
+  uint32_t k;
+  uint8_t shiftVal = log2ScaleFactor + GRAIN_SCALE;
+  uint32_t grainbufInc = width - BLK_32;
+
+  for (l = 0; l < BLK_32; l++) /* y direction */
+  {
+    for (k = 0; k < BLK_32; k++) /* x direction */
+    {
+      *grainStripe = ((scaleFactor * (*database_h_v)) >> shiftVal);
+      grainStripe++;
+      database_h_v++;
+    }
+    grainStripe += grainbufInc;
+    database_h_v += DATA_BASE_SIZE - BLK_32;
+  }
+  return;
+}
+
+uint32_t SEIFilmGrainRegionsSynthesizer::fgrSimulationBlending_8x8(fgrProcessArgs *inArgs)
+{
+  uint8_t  numComp, compCtr, blkId; /* number of color components */
+  uint8_t  log2ScaleFactor, h, v;
+  uint8_t  bitDepth; /*grain bit depth and decoded bit depth are assumed to be same */
+  uint32_t  widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT];
+  ptrdiff_t strideComp[MAX_NUM_COMPONENT];
+  Pel *    decSampleHbdBlk16, *decSampleHbdBlk8, *decSampleHbdOffsetY;
+  Pel *    decHbdComp[MAX_NUM_COMPONENT];
+  uint16_t numSamples;
+  int16_t  scaleFactor;
+  uint32_t  kOffset, lOffset, grainStripeOffset, grainStripeOffsetBlk8;
+  ptrdiff_t offsetBlk8x8;
+  uint32_t kOffset_const, lOffset_const;
+  int16_t  scaleFactor_const;
+  Pel *    grainStripe; /* worth a row of 16x16 : Max size : 16xw;*/
+  int32_t  yOffset8x8, xOffset8x8;
+  uint32_t x, y;
+  uint32_t blockAvg, intensityInt; /* ec : seed to be used for the psudo random generator for a given color component */
+  uint32_t grainStripeWidth;
+  uint32_t wdPadded;
+
+  bitDepth        = inArgs->bitDepth;
+  numComp         = inArgs->numComp;
+  log2ScaleFactor = inArgs->pFgrParameters->m_fgr_log2ScaleFactor;
+
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    decHbdComp[compCtr] = inArgs->decComp[compCtr];
+    strideComp[compCtr] = inArgs->strideComp[compCtr];
+    widthComp[compCtr]  = inArgs->widthComp[compCtr];
+    heightComp[compCtr] = inArgs->heightComp[compCtr];
+  }
+
+  wdPadded = ((inArgs->widthComp[0] - 1) | 0xF) + 1;
+  grainStripe = new Pel[wdPadded * BLK_16];
+
+  if (0 == inArgs->pFgrParameters->m_filmGrainRegionsCharacteristicsCancelFlag)
+  {
+    for (compCtr = 0; compCtr < numComp; compCtr++)
+    {
+      if (1 == inArgs->pFgrParameters->m_fgr_compModel[compCtr].presentFlag)
+      {
+        decSampleHbdOffsetY  = decHbdComp[compCtr];
+        uint32_t *offset_tmp = inArgs->fgsOffsets[compCtr];
+        grainStripeWidth = ((widthComp[compCtr] - 1) | 0xF) + 1;   // Make next muliptle of 16
+        /* Loop of 16x16 blocks */
+        for (y = 0; y < heightComp[compCtr]; y += BLK_16)
+        {
+          /* Initialization of grain stripe of 16xwidth size */
+          memset(grainStripe, 0, (grainStripeWidth * BLK_16 * sizeof(Pel)));
+          for (x = 0; x < widthComp[compCtr]; x += BLK_16)
+          {
+            /* start position offset of decoded sample in x direction */
+            grainStripeOffset = x;
+
+            decSampleHbdBlk16 = decSampleHbdOffsetY + x;
+
+            kOffset_const = (MSB16(*offset_tmp) % 52);
+            kOffset_const &= 0xFFFC;
+
+            lOffset_const = (LSB16(*offset_tmp) % 56);
+            lOffset_const &= 0xFFF8;
+            scaleFactor_const = 1 - 2 * BIT0(*offset_tmp);
+            for (blkId = 0; blkId < NUM_8x8_BLKS_16x16; blkId++)
+            {
+              yOffset8x8   = (blkId >> 1) * BLK_8;
+            // printf("Y offset in block 8x8: %d ", yOffset8x8);
+              xOffset8x8   = (blkId & 0x1) * BLK_8;
+            // printf("X offset in block 8x8: %d \n", xOffset8x8);
+              offsetBlk8x8 = xOffset8x8 + (yOffset8x8 * strideComp[compCtr]);
+
+              grainStripeOffsetBlk8 = grainStripeOffset + (xOffset8x8 + (yOffset8x8 * grainStripeWidth));
+
+              decSampleHbdBlk8 = decSampleHbdBlk16 + offsetBlk8x8;
+              blockAvg = blockAverage_8x8(decSampleHbdBlk8, strideComp[compCtr], &numSamples, BLK_8, BLK_8, bitDepth);
+
+              /* Selection of the component model */
+              
+              intensityInt = inArgs->pGrainSynt->intensityInterval[compCtr][blockAvg];
+              
+              // ------------------------------- check if the intensity interval is within the intensity intervals for the region
+              if (compCtr==0)
+              {
+                int interval_offset_in_map = (y + yOffset8x8) * widthComp[compCtr];
+                int intervalCompFactor = 1;
+                if (widthComp[compCtr] != widthComp[0])
+                  intervalCompFactor =intervalCompFactor*2 ;
+                if (intensityInt < inArgs->fgr_regionIntervals_min_map[compCtr][interval_offset_in_map + x + xOffset8x8])
+                {
+                  intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+                  /*for (int jj = 0 ; jj < 8 ; jj++)
+                    for (int ii = 0 ; ii < 8 ; ii++)
+                    {
+                      inArgs->fgr_grain_enabled_map[interval_offset_in_map +jj*widthComp[compCtr] + x + xOffset8x8 + ii] = false;
+                    } // DEBUD MASK */
+                      
+                }
+                if (intensityInt > inArgs->fgr_regionIntervals_max_map[compCtr][interval_offset_in_map + x + xOffset8x8])
+                {
+                  intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+                  /*for (int jj = 0 ; jj < 8 ; jj++)
+                    for (int ii = 0 ; ii < 8 ; ii++)
+                    {
+                      inArgs->fgr_grain_enabled_map[interval_offset_in_map +jj*widthComp[compCtr] + x + xOffset8x8 + ii] = false;
+                    } // DEBUG MASK */
+                 }
+              }
+              else
+              {
+                int interval_offset_in_map = (y+yOffset8x8)*widthComp[compCtr] ;
+                if (intensityInt < inArgs->fgr_regionIntervals_min_map[compCtr][interval_offset_in_map + x + xOffset8x8])
+                {
+                  intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+                 /* for (int jj = 0 ; jj < 16 ; jj++)
+                    for (int ii = 0 ; ii < 16 ; ii++)
+                    {
+                      inArgs->fgr_grain_enabled_map[interval_offset_in_map*4 +jj*widthComp[compCtr]*2 + x*2 + xOffset8x8*2 + ii] = false;
+                      //printf("index to nulify: %d", interval_offset_in_map +jj*widthComp[compCtr]*4 + x*2 + xOffset8x8*2 + ii*2 );
+                    } // DEBUG MASK */
+                      
+                }
+                if (intensityInt > inArgs->fgr_regionIntervals_max_map[compCtr][interval_offset_in_map + x + xOffset8x8])
+                {
+                  intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+                  /*for (int jj = 0 ; jj < 16 ; jj++)
+                    for (int ii = 0 ; ii < 16 ; ii++)
+                    {
+                      inArgs->fgr_grain_enabled_map[interval_offset_in_map*4 +jj*widthComp[compCtr]*2 + x*2 + xOffset8x8*2 + ii] = false;
+                     // printf("index to nulify: %d", interval_offset_in_map +jj*widthComp[compCtr]*4 + x*2 + xOffset8x8*2 + ii*2 );
+                    } // DEUBD MASK */
+                 }
+              }
+              // printf("---- For Block Average= %d  Intensisty interval = %d \n", blockAvg, intensityInt);
+              // ------------------------------- check if the intensity interval is within the intensity intervals for the region
+
+              if (INTENSITY_INTERVAL_MATCH_FAIL != intensityInt)
+              {
+                /* 8x8 grain block offset using co-ordinates of decoded 8x8 block in the frame */
+                // kOffset = kOffset_const;
+                kOffset = kOffset_const + xOffset8x8;
+
+                lOffset = lOffset_const + yOffset8x8;
+
+                scaleFactor =
+                  scaleFactor_const
+                  * inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[0];
+                h = inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[1] - 2;
+                v = inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[2] - 2;
+                /* 8x8 block grain simulation */
+                simulateGrainBlk8x8(grainStripe, grainStripeOffsetBlk8, inArgs->pGrainSynt, grainStripeWidth,
+                                    log2ScaleFactor, scaleFactor, kOffset, lOffset, h, v, BLK_8);
+              } /* only if average falls in any interval */
+              //  }/* includes corner case handling */
+            } /* 8x8 level block processing */
+
+            /* uppdate the PRNG once per 16x16 block of samples */
+            offset_tmp++;
+          } /* End of 16xwidth grain simulation */
+
+          /* deblocking at the vertical edges of 8x8 at 16xwidth*/
+          deblockGrainStripe(grainStripe, widthComp[compCtr], BLK_16, grainStripeWidth, BLK_8);
+
+          /* Blending of size 16xwidth*/
+          int offset_in_map = y*widthComp[compCtr] ;
+          int CompFactor = 1;
+          if (widthComp[compCtr] != widthComp[0])
+          {
+            offset_in_map = y*widthComp[compCtr] * 4 ; // YUV 420 4:1 ratio to access the FGS enabled map from chroma coordinates
+            CompFactor = 2 ;
+          }
+          blendStripe(decSampleHbdOffsetY, grainStripe, widthComp[compCtr], strideComp[compCtr], grainStripeWidth,BLK_16, bitDepth, offset_in_map,inArgs->fgr_grain_enabled_map,CompFactor);
+
+          decSampleHbdOffsetY += BLK_16 * strideComp[compCtr];
+
+        } /* end of component loop */
+        decSampleHbdOffsetY  = decHbdComp[compCtr];
+       }
+    }
+  }
+
+  delete [] grainStripe;
+  return FGS_SUCCESS;
+}
+
+uint32_t SEIFilmGrainRegionsSynthesizer::fgrSimulationBlending_16x16(fgrProcessArgs *inArgs)
+{
+  uint8_t  numComp, compCtr; /* number of color components */
+  uint8_t  log2ScaleFactor, h, v;
+  uint8_t  bitDepth; /*grain bit depth and decoded bit depth are assumed to be same */
+  uint32_t  widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT];
+  ptrdiff_t strideComp[MAX_NUM_COMPONENT];
+  Pel *    decSampleHbdBlk16, *decSampleHbdOffsetY;
+  Pel *    decHbdComp[MAX_NUM_COMPONENT];
+  uint16_t numSamples;
+  int16_t  scaleFactor;
+  uint32_t kOffset, lOffset, grainStripeOffset;
+  Pel *    grainStripe; /* worth a row of 16x16 : Max size : 16xw;*/
+  uint32_t x, y;
+  uint32_t blockAvg, intensityInt; /* ec : seed to be used for the psudo random generator for a given color component */
+  uint32_t grainStripeWidth;
+  uint32_t wdPadded;
+
+  bitDepth        = inArgs->bitDepth;
+  numComp         = inArgs->numComp;
+  log2ScaleFactor = inArgs->pFgrParameters->m_fgr_log2ScaleFactor;
+
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    decHbdComp[compCtr] = inArgs->decComp[compCtr];
+    strideComp[compCtr] = inArgs->strideComp[compCtr];
+    widthComp[compCtr]  = inArgs->widthComp[compCtr];
+    heightComp[compCtr] = inArgs->heightComp[compCtr];
+  }
+
+  wdPadded = ((inArgs->widthComp[0] - 1) | 0xF) + 1;
+  grainStripe = new Pel[wdPadded * BLK_16];
+
+  if (0 == inArgs->pFgrParameters->m_filmGrainRegionsCharacteristicsCancelFlag)
+  {
+    for (compCtr = 0; compCtr < numComp; compCtr++)
+    {
+      if (1 == inArgs->pFgrParameters->m_fgr_compModel[compCtr].presentFlag)
+      {
+        decSampleHbdOffsetY  = decHbdComp[compCtr];
+        uint32_t *offset_tmp = inArgs->fgsOffsets[compCtr];
+        grainStripeWidth = ((widthComp[compCtr] - 1) | 0xF) + 1;   // Make next muliptle of 16
+
+        /* Loop of 16x16 blocks */
+        for (y = 0; y < heightComp[compCtr]; y += BLK_16)
+        {
+          /* Initialization of grain stripe of 16xwidth size */
+          memset(grainStripe, 0, (grainStripeWidth * BLK_16 * sizeof(Pel)));
+          for (x = 0; x < widthComp[compCtr]; x += BLK_16)
+          {
+            /* start position offset of decoded sample in x direction */
+            grainStripeOffset = x;
+
+            decSampleHbdBlk16 = decSampleHbdOffsetY + x;
+
+            blockAvg =
+              blockAverage_16x16(decSampleHbdBlk16, strideComp[compCtr], &numSamples, BLK_16, BLK_16, bitDepth);
+            blockAvg = blockAvg >> (BLK_16_shift + (bitDepth - BIT_DEPTH_8));
+            /* Selection of the component model */
+            intensityInt = inArgs->pGrainSynt->intensityInterval[compCtr][blockAvg];
+
+            // ------------------------------- check if the intensity interval is within the intensity intervals for the region
+            if (compCtr==0)
+            {
+              int interval_offset_in_map = (y)*widthComp[compCtr] ;
+              int intervalCompFactor = 1;
+              if (widthComp[compCtr] != widthComp[0])
+                intervalCompFactor =intervalCompFactor*2 ;
+              if (intensityInt < inArgs->fgr_regionIntervals_min_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+              }
+              if (intensityInt > inArgs->fgr_regionIntervals_max_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+              }
+            }
+            else
+            {
+              int interval_offset_in_map = (y)*widthComp[compCtr] ;
+              if (intensityInt < inArgs->fgr_regionIntervals_min_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+              }
+              if (intensityInt > inArgs->fgr_regionIntervals_max_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+              }
+            }
+            // ------------------------------- check if the intensity interval is within the intensity intervals for the region
+
+            
+           if (INTENSITY_INTERVAL_MATCH_FAIL != intensityInt)
+            {
+              kOffset = (MSB16(*offset_tmp) % 52);
+              kOffset &= 0xFFFC;
+
+              lOffset = (LSB16(*offset_tmp) % 56);
+              lOffset &= 0xFFF8;
+              scaleFactor = 1 - 2 * BIT0(*offset_tmp);
+
+              scaleFactor *=
+                inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[0];
+              h = inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[1] - 2;
+              v = inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[2] - 2;
+
+              /* 16x16 block grain simulation */
+              simulateGrainBlk16x16(grainStripe, grainStripeOffset, inArgs->pGrainSynt, grainStripeWidth,
+                                    log2ScaleFactor, scaleFactor, kOffset, lOffset, h, v, BLK_16);
+
+            } /* only if average falls in any interval */
+            //  }/* includes corner case handling */
+            /* uppdate the PRNG once per 16x16 block of samples */
+            offset_tmp++;
+          } /* End of 16xwidth grain simulation */
+          /* deblocking at the vertical edges of 16x16 at 16xwidth*/
+          deblockGrainStripe(grainStripe, widthComp[compCtr], BLK_16, grainStripeWidth, BLK_16);
+          
+          /* Blending of size 16xwidth*/
+          int offset_in_map = y*widthComp[compCtr] ;
+          int CompFactor = 1;
+          if (widthComp[compCtr] != widthComp[0])
+            CompFactor =CompFactor*2 ;
+          blendStripe(decSampleHbdOffsetY, grainStripe, widthComp[compCtr], strideComp[compCtr], grainStripeWidth,
+                      BLK_16, bitDepth, offset_in_map, inArgs->fgr_grain_enabled_map,CompFactor);
+          decSampleHbdOffsetY += BLK_16 * strideComp[compCtr];
+
+        } /* end of component loop */
+      }
+    }
+  }
+
+  delete [] grainStripe;
+  return FGS_SUCCESS;
+}
+
+uint32_t SEIFilmGrainRegionsSynthesizer::fgrSimulationBlending_32x32(fgrProcessArgs *inArgs)
+{
+  uint8_t  numComp, compCtr; /* number of color components */
+  uint8_t  log2ScaleFactor, h, v;
+  uint8_t  bitDepth; /*grain bit depth and decoded bit depth are assumed to be same */
+  uint32_t  widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT];
+  ptrdiff_t strideComp[MAX_NUM_COMPONENT];
+  Pel *    decSampleBlk32, *decSampleOffsetY;
+  Pel *    decComp[MAX_NUM_COMPONENT];
+  int16_t  scaleFactor;
+  uint32_t kOffset, lOffset, grainStripeOffset;
+  Pel *    grainStripe;
+  uint32_t x, y;
+  uint32_t blockAvg, intensityInt; /* ec : seed to be used for the psudo random generator for a given color component */
+  uint32_t grainStripeWidth;
+  uint32_t wdPadded;
+
+  bitDepth = inArgs->bitDepth;
+  numComp  = inArgs->numComp;
+
+  log2ScaleFactor = inArgs->pFgrParameters->m_fgr_log2ScaleFactor;
+
+  for (compCtr = 0; compCtr < numComp; compCtr++)
+  {
+    decComp[compCtr]    = inArgs->decComp[compCtr];
+    strideComp[compCtr] = inArgs->strideComp[compCtr];
+    heightComp[compCtr] = inArgs->heightComp[compCtr];
+    widthComp[compCtr]  = inArgs->widthComp[compCtr];
+  }
+
+  wdPadded = ((inArgs->widthComp[0] - 1) | 0x1F) + 1;
+  grainStripe = new Pel[wdPadded * BLK_32];
+
+  if (0 == inArgs->pFgrParameters->m_filmGrainRegionsCharacteristicsCancelFlag)
+  {
+    for (compCtr = 0; compCtr < numComp; compCtr++)
+    {
+      if (1 == inArgs->pFgrParameters->m_fgr_compModel[compCtr].presentFlag)
+      {
+        uint32_t *offset_tmp = inArgs->fgsOffsets[compCtr];
+        decSampleOffsetY     = decComp[compCtr];
+        grainStripeWidth = ((widthComp[compCtr] - 1) | 0x1F) + 1;   // Make next muliptle of 32
+
+        /* Loop of 32x32 blocks */
+        for (y = 0; y < heightComp[compCtr]; y += BLK_32)
+        {
+          /* Initialization of grain stripe of 32xwidth size */
+          memset(grainStripe, 0, (grainStripeWidth * BLK_32 * sizeof(Pel)));
+          for (x = 0; x < widthComp[compCtr]; x += BLK_32)
+          {
+            /* start position offset of decoded sample in x direction */
+            grainStripeOffset = x;
+            decSampleBlk32    = decSampleOffsetY + x;
+            blockAvg = blockAverage_32x32(decSampleBlk32, strideComp[compCtr], bitDepth);
+
+            /* Selection of the component model */
+            intensityInt = inArgs->pGrainSynt->intensityInterval[compCtr][blockAvg];
+
+            // ------------------------------- check if the intensity interval is within the intensity intervals for the region
+            if (compCtr==0)
+            {
+              int interval_offset_in_map = (y)*widthComp[compCtr] ;
+              int intervalCompFactor = 1;
+              if (widthComp[compCtr] != widthComp[0])
+                intervalCompFactor =intervalCompFactor*2 ;
+              if (intensityInt < inArgs->fgr_regionIntervals_min_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+              }
+              if (intensityInt > inArgs->fgr_regionIntervals_max_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+               }
+            }
+            else
+            {
+              int interval_offset_in_map = (y)*widthComp[compCtr] ;
+              if (intensityInt < inArgs->fgr_regionIntervals_min_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+              }
+              if (intensityInt > inArgs->fgr_regionIntervals_max_map[compCtr][interval_offset_in_map + x])
+              {
+                intensityInt = INTENSITY_INTERVAL_MATCH_FAIL ;
+               }
+            }
+            // ------------------------------- check if the intensity interval is within the intensity intervals for the region
+            
+            
+            if (INTENSITY_INTERVAL_MATCH_FAIL != intensityInt)
+            {
+              kOffset = (MSB16(*offset_tmp) % 36);
+              kOffset &= 0xFFFC;
+
+              lOffset = (LSB16(*offset_tmp) % 40);
+              lOffset &= 0xFFF8;
+              scaleFactor = 1 - 2 * BIT0(*offset_tmp);
+
+              scaleFactor *= inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[0];
+              h = inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[1] - 2;
+              v = inArgs->pFgrParameters->m_fgr_compModel[compCtr].intensityValues[intensityInt].compModelValue[2] - 2;
+
+              /* 32x32 block grain simulation */
+              simulateGrainBlk32x32(grainStripe, grainStripeOffset, inArgs->pGrainSynt, grainStripeWidth,
+                                    log2ScaleFactor, scaleFactor, kOffset, lOffset, h, v);
+
+            } /* only if average falls in any interval */
+
+            /* uppdate the PRNG once per 16x16 block of samples */
+            offset_tmp++;
+          } /* End of 32xwidth grain simulation */
+
+          /* deblocking at the vertical edges of 8x8 at 16xwidth*/
+          deblockGrainStripe(grainStripe, widthComp[compCtr], BLK_32, grainStripeWidth, BLK_32);
+
+          int offset_in_map = y*widthComp[compCtr] ;
+          int CompFactor = 1;
+          if (widthComp[compCtr] != widthComp[0])
+            CompFactor =CompFactor*2 ;
+          blendStripe_32x32(decSampleOffsetY, grainStripe, widthComp[compCtr], strideComp[compCtr], grainStripeWidth, BLK_32, bitDepth,offset_in_map,inArgs->fgr_grain_enabled_map,CompFactor);
+          decSampleOffsetY += BLK_32 * strideComp[compCtr];
+        } /* end of component loop */
+      }
+    }
+  }
+
+  delete [] grainStripe;
+  return FGS_SUCCESS;
+}
+#endif
diff --git a/source/Lib/CommonLib/SEIFilmGrainSynthesizer.h b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.h
index ff7a364bd..d251d9f00 100644
--- a/source/Lib/CommonLib/SEIFilmGrainSynthesizer.h
+++ b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.h
@@ -225,6 +225,91 @@ private:
 
 };// END CLASS DEFINITION SEIFilmGrainSynthesizer
 
+#if JVET_AG0328
+typedef struct fgrProcessArgs
+{
+  uint8_t                      numComp;
+  uint32_t *                   fgsOffsets[MAX_NUM_COMPONENT];
+  Pel *                        decComp[MAX_NUM_COMPONENT];
+  uint32_t                     widthComp[MAX_NUM_COMPONENT];
+  uint32_t                     heightComp[MAX_NUM_COMPONENT];
+  ptrdiff_t                    strideComp[MAX_NUM_COMPONENT];
+  SEIFilmGrainRegionsCharacteristics *pFgrParameters;
+  GrainSynthesisStruct *       pGrainSynt;
+  uint8_t                      bitDepth;
+  uint8_t                      blkSize;
+  std::vector<bool>            fgr_grain_enabled_map;
+  std::vector<uint16_t>        fgr_regionIntervals_min_map[MAX_NUM_COMPONENT];
+  std::vector<uint16_t>        fgr_regionIntervals_max_map[MAX_NUM_COMPONENT];
+} fgrProcessArgs;
+
+class SEIFilmGrainRegionsSynthesizer
+{
+
+private:
+  uint32_t                     m_width;
+  uint32_t                     m_height;
+  ChromaFormat                 m_chromaFormat;
+  uint8_t                      m_bitDepth;
+  uint32_t                     m_idrPicId;
+
+  fgrProcessArgs               m_fgrArgs;
+  GrainSynthesisStruct        *m_grainSynt;
+  uint8_t                      m_fgsBlkSize;
+
+public:
+  uint32_t                     m_poc;
+  int32_t                      m_errorCode;
+  SEIFilmGrainRegionsCharacteristics *m_fgrParameters;
+
+public:
+  SEIFilmGrainRegionsSynthesizer();
+  virtual ~SEIFilmGrainRegionsSynthesizer();
+
+  void      create(uint32_t width, uint32_t height, ChromaFormat fmt, uint8_t bitDepth, uint32_t idrPicId);
+  void      destroy   ();
+
+  void      fgrInit   ();
+  void      grainSynthesizeAndBlend (PelStorage* pGrainBuf, bool isIdrPic);
+  uint8_t   grainValidateParams     ();
+
+private:
+  void            deriveFGSBlkSize    ();
+  void            dataBaseGen         ();
+  static uint32_t prng                (uint32_t x_r);
+  static uint32_t fgrProcess          (fgrProcessArgs &inArgs);
+
+  static void     deblockGrainStripe  (Pel *grainStripe, uint32_t widthComp, uint32_t heightComp, uint32_t strideComp,
+                                      uint32_t blkSize);
+  static void     blendStripe(Pel *decSampleOffsetY, Pel *grainStripe, uint32_t widthComp, ptrdiff_t strideSrc,
+                              ptrdiff_t strideGrain, uint32_t blockHeight, uint8_t bitDepth, uint32_t offset_in_map,std::vector<bool>            fgr_grain_enabled_map, uint32_t CompFactor );
+  static void     blendStripe_32x32(Pel *decSampleOffsetY, Pel *grainStripe, uint32_t widthComp, ptrdiff_t strideSrc,
+                                    ptrdiff_t strideGrain, uint32_t blockHeight, uint8_t bitDepth,uint32_t offset_in_map,std::vector<bool> fgr_grain_enabled_map, uint32_t CompFactor);
+
+  static Pel      blockAverage_8x8(Pel *decSampleBlk8, ptrdiff_t widthComp, uint16_t *pNumSamples, uint8_t ySize,
+                                   uint8_t xSize, uint8_t bitDepth);
+  static uint32_t blockAverage_16x16(Pel *decSampleBlk8, ptrdiff_t widthComp, uint16_t *pNumSamples, uint8_t ySize,
+                                     uint8_t xSize, uint8_t bitDepth);
+  static uint32_t blockAverage_32x32(Pel *decSampleBlk32, ptrdiff_t strideComp, uint8_t bitDepth);
+
+  static void     simulateGrainBlk8x8 (Pel *grainStripe, uint32_t grainStripeOffsetBlk8, GrainSynthesisStruct *pGrainSynt,
+                                      uint32_t width, uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset,
+                                      uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize);
+  static void     simulateGrainBlk16x16(Pel *grainStripe, uint32_t grainStripeOffsetBlk8, GrainSynthesisStruct *grain_synt,
+                                        uint32_t width, uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset,
+                                        uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize);
+  static void     simulateGrainBlk32x32(Pel *grainStripe, uint32_t grainStripeOffsetBlk32, GrainSynthesisStruct *grain_synt,
+                                        uint32_t width, uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset,
+                                        uint32_t lOffset, uint8_t h, uint8_t v);
+  static uint32_t fgrSimulationBlending_8x8   (fgrProcessArgs *inArgs);
+  static uint32_t fgrSimulationBlending_16x16 (fgrProcessArgs *inArgs);
+  static uint32_t fgrSimulationBlending_32x32 (fgrProcessArgs *inArgs);
+
+};// END CLASS DEFINITION SEIFilmGrainRegionsSynthesizer
+
+#endif
+
+
 //! \}
 
 #endif // __SEIFILMGRAINSYNTHESIZER__
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 280da05f2..9de14e076 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -94,6 +94,10 @@
 #define JVET_J0090_MEMORY_BANDWITH_MEASURE                0
 #endif
 
+#ifndef JVET_AG0328
+#define JVET_AG0328                                       1
+#endif
+
 #ifndef EXTENSION_360_VIDEO
 #define EXTENSION_360_VIDEO                               0   ///< extension for 360/spherical video coding support; this macro should be controlled by makefile, as it would be used to control whether the library is built and linked
 #endif
diff --git a/source/Lib/DecoderLib/DecLib.cpp b/source/Lib/DecoderLib/DecLib.cpp
index e796fb318..4f2f6f14c 100644
--- a/source/Lib/DecoderLib/DecLib.cpp
+++ b/source/Lib/DecoderLib/DecLib.cpp
@@ -491,6 +491,10 @@ DecLib::DecLib()
   , m_firstPictureInSequence(true)
   , m_grainCharacteristic()
   , m_grainBuf()
+#if JVET_AG0328
+  , m_grainRegionsCharacteristic()
+  , m_grainRegionsBuf()
+#endif
   , m_colourTranfParams()
   , m_firstSliceInBitstream(true)
   , m_isFirstAuInCvs(true)
@@ -2110,6 +2114,11 @@ void DecLib::xActivateParameterSets( const InputNALUnit nalu )
     m_pcPic->createGrainSynthesizer(m_firstPictureInSequence, &m_grainCharacteristic, &m_grainBuf,
                                     pps->getPicWidthInLumaSamples(), pps->getPicHeightInLumaSamples(),
                                     sps->getChromaFormatIdc(), sps->getBitDepth(ChannelType::LUMA));
+#if JVET_AG0328
+    m_pcPic->createGrainRegionsSynthesizer(m_firstPictureInSequence, &m_grainRegionsCharacteristic, &m_grainRegionsBuf,
+                                    pps->getPicWidthInLumaSamples(), pps->getPicHeightInLumaSamples(),
+                                    sps->getChromaFormatIdc(), sps->getBitDepth(ChannelType::LUMA));
+#endif
     m_pcPic->createColourTransfProcessor(m_firstPictureInSequence, &m_colourTranfParams, &m_invColourTransfBuf,
                                          pps->getPicWidthInLumaSamples(), pps->getPicHeightInLumaSamples(),
                                          sps->getChromaFormatIdc(), sps->getBitDepth(ChannelType::LUMA));
diff --git a/source/Lib/DecoderLib/DecLib.h b/source/Lib/DecoderLib/DecLib.h
index 1476f7390..9cc3f9277 100644
--- a/source/Lib/DecoderLib/DecLib.h
+++ b/source/Lib/DecoderLib/DecLib.h
@@ -140,6 +140,10 @@ private:
   bool                    m_firstPictureInSequence;
   SEIFilmGrainSynthesizer m_grainCharacteristic;
   PelStorage              m_grainBuf;
+#if JVET_AG0328
+  SEIFilmGrainRegionsSynthesizer m_grainRegionsCharacteristic;
+  PelStorage              m_grainRegionsBuf;
+#endif
   SEIColourTransformApply m_colourTranfParams;
   PelStorage              m_invColourTransfBuf;
   bool                    m_firstSliceInSequence[MAX_VPS_LAYERS];
diff --git a/source/Lib/DecoderLib/SEIread.cpp b/source/Lib/DecoderLib/SEIread.cpp
index c033c4b1e..7650240e7 100644
--- a/source/Lib/DecoderLib/SEIread.cpp
+++ b/source/Lib/DecoderLib/SEIread.cpp
@@ -462,6 +462,12 @@ bool SEIReader::xReadSEImessage(SEIMessages& seis, const NalUnitType nalUnitType
       sei = new SEIFilmGrainCharacteristics;
       xParseSEIFilmGrainCharacteristics((SEIFilmGrainCharacteristics &) *sei, payloadSize, pDecodedMessageOutputStream);
       break;
+#if JVET_AG0328
+      case SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS:
+      sei = new SEIFilmGrainRegionsCharacteristics;
+      xParseSEIFilmGrainRegionsCharacteristics((SEIFilmGrainRegionsCharacteristics &) *sei, payloadSize, pDecodedMessageOutputStream);
+      break;
+#endif
     case SEI::PayloadType::CONTENT_LIGHT_LEVEL_INFO:
       sei = new SEIContentLightLevelInfo;
       xParseSEIContentLightLevelInfo((SEIContentLightLevelInfo &) *sei, payloadSize, pDecodedMessageOutputStream);
@@ -1406,6 +1412,9 @@ void SEIReader::xCheckScalableNestingConstraints(const SEIScalableNesting& sei,
   const std::vector<SEI::PayloadType> vclAssociatedSeiList{
     SEI::PayloadType::FILLER_PAYLOAD,
     SEI::PayloadType::FILM_GRAIN_CHARACTERISTICS,
+#if JVET_AG0328
+    SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS,
+#endif
     SEI::PayloadType::FRAME_PACKING,
     SEI::PayloadType::GREEN_METADATA,
     SEI::PayloadType::PARAMETER_SETS_INCLUSION_INDICATION,
@@ -2181,9 +2190,97 @@ void SEIReader::xParseSEIFilmGrainCharacteristics(SEIFilmGrainCharacteristics& s
         }
       }
     } // for c
-    sei_read_flag(pDecodedMessageOutputStream, code, "fg_characteristics_persistence_flag");         sei.m_filmGrainCharacteristicsPersistenceFlag = code != 0;
+      sei_read_flag(pDecodedMessageOutputStream, code, "fg_characteristics_persistence_flag");         sei.m_filmGrainCharacteristicsPersistenceFlag = code != 0;
+  } // cancel flag
+}
+
+#if JVET_AG0328
+void SEIReader::xParseSEIFilmGrainRegionsCharacteristics(SEIFilmGrainRegionsCharacteristics& sei, uint32_t payloadSize, std::ostream *pDecodedMessageOutputStream)
+{
+  uint32_t code;
+  output_sei_message_header(sei, pDecodedMessageOutputStream, payloadSize);
+
+  sei_read_flag(pDecodedMessageOutputStream, code, "fgr_characteristics_cancel_flag");                sei.m_filmGrainRegionsCharacteristicsCancelFlag = code != 0;
+  if (!sei.m_filmGrainRegionsCharacteristicsCancelFlag)
+  {
+    sei_read_code(pDecodedMessageOutputStream, 2, code, "fgr_model_id");                              sei.m_filmGrainRegionsModelId = code;
+    sei_read_flag(pDecodedMessageOutputStream, code, "fgr_separate_colour_description_present_flag"); sei.m_fgr_separateColourDescriptionPresentFlag = code != 0;
+    if (sei.m_fgr_separateColourDescriptionPresentFlag)
+    {
+      sei_read_code(pDecodedMessageOutputStream, 3, code, "fgr_bit_depth_luma_minus8");               sei.m_filmGrainRegionsBitDepthLumaMinus8 = code;
+      sei_read_code(pDecodedMessageOutputStream, 3, code, "fgr_bit_depth_chroma_minus8");             sei.m_filmGrainRegionsBitDepthChromaMinus8 = code;
+      sei_read_flag(pDecodedMessageOutputStream, code, "fgr_full_range_flag");                        sei.m_filmGrainRegionsFullRangeFlag = code != 0;
+      sei_read_code(pDecodedMessageOutputStream, 8, code, "fgr_colour_primaries");                    sei.m_filmGrainRegionsColourPrimaries = code;
+      sei_read_code(pDecodedMessageOutputStream, 8, code, "fgr_transfer_characteristics");            sei.m_filmGrainRegionsTransferCharacteristics = code;
+      sei_read_code(pDecodedMessageOutputStream, 8, code, "fgr_matrix_coeffs");                       sei.m_filmGrainRegionsMatrixCoeffs = code;
+    }
+    sei_read_code(pDecodedMessageOutputStream, 2, code, "fgr_blending_mode_id");                      sei.m_fgr_blendingModeId = code;
+    sei_read_code(pDecodedMessageOutputStream, 4, code, "fgr_log2_scale_factor");                     sei.m_fgr_log2ScaleFactor = code;
+    for (int c = 0; c<3; c++)
+    {
+      sei_read_flag(pDecodedMessageOutputStream, code, "fgr_comp_model_present_flag[c]");             sei.m_fgr_compModel[c].presentFlag = code != 0;
+    }
+    for (int c = 0; c<3; c++)
+    {
+      SEIFilmGrainRegionsCharacteristics::CompModel &cm = sei.m_fgr_compModel[c];
+      if (cm.presentFlag)
+      {
+        sei_read_code(pDecodedMessageOutputStream, 8, code, "fgr_num_intensity_intervals_minus1[c]"); cm.numIntensityIntervals = code + 1;
+        sei_read_code(pDecodedMessageOutputStream, 3, code, "fgr_num_model_values_minus1[c]");        cm.numModelValues = code + 1;
+        cm.intensityValues.resize(cm.numIntensityIntervals);
+        for (uint32_t i = 0; i < cm.numIntensityIntervals; i++)
+        {
+          SEIFilmGrainRegionsCharacteristics::CompModelIntensityValues &cmiv = cm.intensityValues[i];
+          sei_read_code(pDecodedMessageOutputStream, 8, code, "fgr_intensity_interval_lower_bound[c][i]"); cmiv.intensityIntervalLowerBound = code;
+          sei_read_code(pDecodedMessageOutputStream, 8, code, "fgr_intensity_interval_upper_bound[c][i]"); cmiv.intensityIntervalUpperBound = code;
+          cmiv.compModelValue.resize(cm.numModelValues);
+          for (uint32_t j = 0; j<cm.numModelValues; j++)
+          {
+            sei_read_svlc(pDecodedMessageOutputStream, cmiv.compModelValue[j], "fgr_comp_model_value[c][i][j]");
+          }
+        }
+      }
+    } // for c
+    // Region based specific section
+    sei_read_flag(pDecodedMessageOutputStream, code, "fgr_region_information_present_flag");         sei.m_fgr_region_information_present_flag = code != 0;
+    if (sei.m_fgr_region_information_present_flag)
+    {
+      sei_read_flag(pDecodedMessageOutputStream, code, "fgr_region_based_adaptation_flag");          sei.m_fgr_region_based_adaptation_flag = code != 0;
+      if (sei.m_fgr_region_based_adaptation_flag)
+      {
+        sei_read_code(pDecodedMessageOutputStream, 8, code, "fgr_active_regions_number_minus1");     sei.m_fgr_active_regions_number = code + 1;
+        sei.fgr_active_regions.resize(sei.m_fgr_active_regions_number);
+        for (int i = 0 ; i < sei.m_fgr_active_regions_number; i++)
+        {
+          sei_read_code(pDecodedMessageOutputStream, 16, code, "fgr_active_region_top[i]");          sei.fgr_active_regions[i].m_fgr_region_top = code;
+          sei_read_code(pDecodedMessageOutputStream, 16, code, "fgr_active_region_left[i]");         sei.fgr_active_regions[i].m_fgr_region_left = code;
+          sei_read_code(pDecodedMessageOutputStream, 16, code, "fgr_active_region_width[i]");        sei.fgr_active_regions[i].m_fgr_region_width = code;
+          sei_read_code(pDecodedMessageOutputStream, 16, code, "fgr_active_region_height[i]");       sei.fgr_active_regions[i].m_fgr_region_height = code;
+          sei_read_flag(pDecodedMessageOutputStream, code, "fgr_film_grain_enabled[i]");             sei.fgr_active_regions[i].m_film_grain_enabled_flag = code != 0;
+          if (sei.fgr_active_regions[i].m_film_grain_enabled_flag)
+          {
+            for (int c=0; c<3; c++)
+            {
+              SEIFilmGrainRegionsCharacteristics::CompModel &cm = sei.m_fgr_compModel[c];
+              if (cm.presentFlag)
+              {
+                sei_read_code(pDecodedMessageOutputStream, 8, code, "m_fgr_region_interval_min[i][c]"); sei.fgr_active_regions[i].m_fgr_regionIntervals[c].m_fgr_region_interval_min = code;
+                sei_read_code(pDecodedMessageOutputStream, 8, code, "m_fgr_region_interval_max[i][c]"); sei.fgr_active_regions[i].m_fgr_regionIntervals[c].m_fgr_region_interval_max = code;
+              }
+            }
+          }
+        }
+      }
+      else
+      {
+        sei_read_flag(pDecodedMessageOutputStream, code, "fgr_alpha_channel_adaptation_flag");          sei.m_fgr_alpha_channel_adaptation_flag = code != 0;
+      }
+    }
+    sei_read_flag(pDecodedMessageOutputStream, code, "fgr_characteristics_persistence_flag");         sei.m_filmGrainRegionsCharacteristicsPersistenceFlag = code != 0;
   } // cancel flag
+
 }
+#endif
 
 void SEIReader::xParseSEIContentLightLevelInfo(SEIContentLightLevelInfo& sei, uint32_t payloadSize, std::ostream *pDecodedMessageOutputStream)
 {
diff --git a/source/Lib/DecoderLib/SEIread.h b/source/Lib/DecoderLib/SEIread.h
index 68d681c8c..723d15033 100644
--- a/source/Lib/DecoderLib/SEIread.h
+++ b/source/Lib/DecoderLib/SEIread.h
@@ -104,6 +104,9 @@ protected:
   void xParseSEISampleAspectRatioInfo         (SEISampleAspectRatioInfo& sei,         uint32_t payloadSize,                     std::ostream *pDecodedMessageOutputStream);
   void xParseSEIUserDataRegistered            (SEIUserDataRegistered& sei,            uint32_t payloadSize,                     std::ostream *pDecodedMessageOutputStream);
   void xParseSEIFilmGrainCharacteristics      (SEIFilmGrainCharacteristics& sei,      uint32_t payloadSize,                     std::ostream *pDecodedMessageOutputStream);
+#if JVET_AG0328
+  void xParseSEIFilmGrainRegionsCharacteristics (SEIFilmGrainRegionsCharacteristics& sei, uint32_t payloadSize,                 std::ostream *pDecodedMessageOutputStream);
+#endif
   void xParseSEIContentLightLevelInfo         (SEIContentLightLevelInfo& sei,         uint32_t payloadSize,                     std::ostream *pDecodedMessageOutputStream);
   void xParseSEIAmbientViewingEnvironment     (SEIAmbientViewingEnvironment& sei,     uint32_t payloadSize,                     std::ostream *pDecodedMessageOutputStream);
   void xParseSEIContentColourVolume           (SEIContentColourVolume& sei,           uint32_t payloadSize,                     std::ostream *pDecodedMessageOutputStream);
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index 150aa0109..354abcffa 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -781,6 +781,39 @@ protected:
   uint8_t   m_fgcSEIIntensityIntervalLowerBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
   uint8_t   m_fgcSEIIntensityIntervalUpperBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
   uint32_t  m_fgcSEICompModelValue                [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES][MAX_NUM_MODEL_VALUES];
+#if JVET_AG0328
+  // film grain region characterstics SEI
+  bool      m_fgrSEIEnabled;
+  bool      m_fgrSEICancelFlag;
+  bool      m_fgrSEIPersistenceFlag;
+  uint8_t   m_fgrSEIModelID;
+  bool      m_fgrSEISepColourDescPresentFlag;
+  uint8_t   m_fgrSEIBlendingModeID;
+  uint8_t   m_fgrSEILog2ScaleFactor;
+  bool      m_fgrSEICompModelPresent[MAX_NUM_COMPONENT];
+  bool      m_fgrSEIAnalysisEnabled;
+  std::string m_fgrSEIExternalMask;
+  std::string m_fgrSEIExternalDenoised;
+  int       m_fgrSEITemporalFilterPastRefs;
+  int       m_fgrSEITemporalFilterFutureRefs;
+  std::map<int, double> m_fgrSEITemporalFilterStrengths;
+  bool      m_fgrSEIPerPictureSEI;
+  uint8_t   m_fgrSEINumModelValuesMinus1          [MAX_NUM_COMPONENT];
+  uint8_t   m_fgrSEINumIntensityIntervalMinus1    [MAX_NUM_COMPONENT];
+  uint8_t   m_fgrSEIIntensityIntervalLowerBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
+  uint8_t   m_fgrSEIIntensityIntervalUpperBound   [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES];
+  uint32_t  m_fgrSEICompModelValue                [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES][MAX_NUM_MODEL_VALUES];
+
+  bool      m_fgrSEI_region_information_present_flag;
+  bool      m_fgrSEI_region_based_adaptation_flag;
+  bool      m_fgrSEI_alpha_channel_adaptation_flag;
+  uint32_t  m_fgrSEINumActiveRegionsMinus1;
+
+public:
+  std::vector<SEIFilmGrainRegionsCharacteristics::RegionCharacteristics> m_fgrSEIregionCharacteristics;
+protected:
+#endif
+
 // cll SEI
   bool      m_cllSEIEnabled;
   uint16_t  m_cllSEIMaxContentLevel;
@@ -2369,6 +2402,102 @@ public:
   uint8_t   getFGCSEIIntensityIntervalUpperBound(int index, int ctr)            { return m_fgcSEIIntensityIntervalUpperBound[index][ctr]; }
   void      setFGCSEICompModelValue             (uint32_t v, int index, int ctr, int modelCtr)  { m_fgcSEICompModelValue[index][ctr][modelCtr] = v; }
   uint32_t  getFGCSEICompModelValue             (int index, int ctr, int modelCtr)              { return m_fgcSEICompModelValue[index][ctr][modelCtr]; }
+#if JVET_AG0328
+  // film grain region SEI
+  void  setFilmGrainRegionsCharactersticsSEIEnabled (bool b)                { m_fgrSEIEnabled = b; }
+  bool  getFilmGrainRegionsCharactersticsSEIEnabled()                       { return m_fgrSEIEnabled; }
+  void  setFilmGrainRegionsCharactersticsSEICancelFlag(bool b)              { m_fgrSEICancelFlag = b; }
+  bool  getFilmGrainRegionsCharactersticsSEICancelFlag()                    { return m_fgrSEICancelFlag; }
+  void  setFilmGrainRegionsCharactersticsSEIPersistenceFlag(bool b)         { m_fgrSEIPersistenceFlag = b; }
+  bool  getFilmGrainRegionsCharactersticsSEIPersistenceFlag()               { return m_fgrSEIPersistenceFlag; }
+  void  setFilmGrainRegionsCharactersticsSEIModelID(uint8_t v )             { m_fgrSEIModelID = v; }
+  uint8_t getFilmGrainRegionsCharactersticsSEIModelID()                     { return m_fgrSEIModelID; }
+  void  setFilmGrainRegionsCharactersticsSEISepColourDescPresent(bool b)    { m_fgrSEISepColourDescPresentFlag = b; }
+  bool  getFilmGrainRegionsCharactersticsSEISepColourDescPresent()          { return m_fgrSEISepColourDescPresentFlag; }
+  void  setFilmGrainRegionsCharactersticsSEIBlendingModeID(uint8_t v )      { m_fgrSEIBlendingModeID = v; }
+  uint8_t getFilmGrainRegionsCharactersticsSEIBlendingModeID()              { return m_fgrSEIBlendingModeID; }
+  void  setFilmGrainRegionsCharactersticsSEILog2ScaleFactor(uint8_t v )     { m_fgrSEILog2ScaleFactor = v; }
+  uint8_t getFilmGrainRegionsCharactersticsSEILog2ScaleFactor()             { return m_fgrSEILog2ScaleFactor; }
+  void  setFGRSEICompModelPresent(bool b, int index)                 { m_fgrSEICompModelPresent[index] = b; }
+  bool  getFGRSEICompModelPresent(int index)                         { return m_fgrSEICompModelPresent[index]; }
+  bool*     getFGRSEICompModelPresent                 ()                        { return m_fgrSEICompModelPresent; }
+  void      setFilmGrainRegionsAnalysisEnabled               (bool b)                  { m_fgrSEIAnalysisEnabled = b; }
+  bool      getFilmGrainRegionsAnalysisEnabled               ()                        { return m_fgrSEIAnalysisEnabled; }
+  void        setFilmGrainRegionsExternalMask(std::string s) { m_fgrSEIExternalMask = s; }
+  void        setFilmGrainRegionsExternalDenoised(std::string s) { m_fgrSEIExternalDenoised = s; }
+  std::string getFilmGrainRegionsExternalMask() { return m_fgrSEIExternalMask; }
+  std::string getFilmGrainRegionsExternalDenoised() { return m_fgrSEIExternalDenoised; }
+  void        setFilmGrainRegionsTemporalFilterPastRefs(int v)                         { m_fgrSEITemporalFilterPastRefs = v; }
+  void        setFilmGrainRegionsTemporalFilterFutureRefs(int v)                       { m_fgrSEITemporalFilterFutureRefs = v; }
+  void        setFilmGrainRegionsTemporalFilterStrengths(std::map<int, double> v)      { m_fgrSEITemporalFilterStrengths = v; }
+  int         getFilmGrainRegionsTemporalFilterPastRefs()                              { return m_fgrSEITemporalFilterPastRefs; };
+  int         getFilmGrainRegionsTemporalFilterFutureRef()                             { return m_fgrSEITemporalFilterFutureRefs; };
+  std::map<int, double> getFilmGrainRegionsTemporalFilterStrengths()                   { return m_fgrSEITemporalFilterStrengths; };
+  void      setFilmGrainRegionsCharactersticsSEIPerPictureSEI(bool b)                  { m_fgrSEIPerPictureSEI = b; }
+  bool      getFilmGrainRegionsCharactersticsSEIPerPictureSEI()                        { return m_fgrSEIPerPictureSEI; }
+  void      setFGRSEINumIntensityIntervalMinus1 (uint8_t v, int index)          { m_fgrSEINumIntensityIntervalMinus1[index] = v; }
+  uint8_t   getFGRSEINumIntensityIntervalMinus1 (int index)                     { return m_fgrSEINumIntensityIntervalMinus1[index]; }
+  void      setFGRSEINumModelValuesMinus1       (uint8_t v, int index)          { m_fgrSEINumModelValuesMinus1[index] = v; }
+  uint8_t   getFGRSEINumModelValuesMinus1       (int index)                     { return m_fgrSEINumModelValuesMinus1[index]; }
+  void      setFGRSEIIntensityIntervalLowerBound(uint8_t v, int index, int ctr) { m_fgrSEIIntensityIntervalLowerBound[index][ctr] = v; }
+  uint8_t   getFGRSEIIntensityIntervalLowerBound(int index, int ctr)            { return m_fgrSEIIntensityIntervalLowerBound[index][ctr]; }
+  void      setFGRSEIIntensityIntervalUpperBound(uint8_t v, int index, int ctr) { m_fgrSEIIntensityIntervalUpperBound[index][ctr] = v; }
+  uint8_t   getFGRSEIIntensityIntervalUpperBound(int index, int ctr)            { return m_fgrSEIIntensityIntervalUpperBound[index][ctr]; }
+  void      setFGRSEICompModelValue             (uint32_t v, int index, int ctr, int modelCtr)  { m_fgrSEICompModelValue[index][ctr][modelCtr] = v; }
+  uint32_t  getFGRSEICompModelValue             (int index, int ctr, int modelCtr)              { return m_fgrSEICompModelValue[index][ctr][modelCtr]; }
+  void      setFilmGrainRegionInformationPresentFlag(bool b)                  { m_fgrSEI_region_information_present_flag = b; }
+  bool      getFilmGrainRegionInformationPresentFlag()                        { return m_fgrSEI_region_information_present_flag; }
+  void      setFilmGrainRegionBasedAdaptationFlag(bool b)                  { m_fgrSEI_region_based_adaptation_flag = b; }
+  bool      getFilmGrainRegionBasedAdaptationFlag()                        { return m_fgrSEI_region_based_adaptation_flag; }
+  void      setFilmGrainAlphaChannelAdaptationFlag(bool b)                  { m_fgrSEI_alpha_channel_adaptation_flag = b; }
+  bool      getFilmGrainAlphaChannelAdaptationFlag()                        { return m_fgrSEI_alpha_channel_adaptation_flag; }
+  void      setFilmGrainNumActiveRegionsMinus1(int b)                  { m_fgrSEINumActiveRegionsMinus1 = b; }
+  int       getFilmGrainNumActiveRegionsMinus1()                        { return m_fgrSEINumActiveRegionsMinus1; }
+  void      setFilmGrainRegionCharacteristics(std::vector<SEIFilmGrainRegionsCharacteristics::RegionCharacteristics> RegionCharacteristics, int NumRegionsMinus1 )
+  {
+    if(m_fgrSEI_region_based_adaptation_flag)
+    {
+      m_fgrSEIregionCharacteristics.resize(NumRegionsMinus1+1);
+      for (int i = 0 ; i <= NumRegionsMinus1; i++)
+      {
+        m_fgrSEIregionCharacteristics[i].m_fgr_region_top = RegionCharacteristics[i].m_fgr_region_top;
+        m_fgrSEIregionCharacteristics[i].m_fgr_region_left = RegionCharacteristics[i].m_fgr_region_left;
+        m_fgrSEIregionCharacteristics[i].m_fgr_region_width = RegionCharacteristics[i].m_fgr_region_width;
+        m_fgrSEIregionCharacteristics[i].m_fgr_region_height = RegionCharacteristics[i].m_fgr_region_height;
+        m_fgrSEIregionCharacteristics[i].m_film_grain_enabled_flag = RegionCharacteristics[i].m_film_grain_enabled_flag;
+        if (RegionCharacteristics[i].m_film_grain_enabled_flag)
+        {
+          m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_min = RegionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_min ;
+          m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_max = RegionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_max ;
+          m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_min = RegionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_min ;
+          m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_max = RegionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_max ;
+          m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_min = RegionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_min ;
+          m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_max = RegionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_max ;
+        }
+      }
+    }
+  }
+  void      getFilmGrainRegionCharacteristics(std::vector<SEIFilmGrainRegionsCharacteristics::RegionCharacteristics>& RegionCharacteristics, int NumRegionsMinus1 )
+  {
+    for (int i = 0 ; i <= NumRegionsMinus1; i++)
+    {
+      RegionCharacteristics[i].m_fgr_region_top            = m_fgrSEIregionCharacteristics[i].m_fgr_region_top;
+      RegionCharacteristics[i].m_fgr_region_left           = m_fgrSEIregionCharacteristics[i].m_fgr_region_left ;
+      RegionCharacteristics[i].m_fgr_region_width          = m_fgrSEIregionCharacteristics[i].m_fgr_region_width ;
+      RegionCharacteristics[i].m_fgr_region_height         = m_fgrSEIregionCharacteristics[i].m_fgr_region_height ;
+      RegionCharacteristics[i].m_film_grain_enabled_flag   = m_fgrSEIregionCharacteristics[i].m_film_grain_enabled_flag ;
+      if (RegionCharacteristics[i].m_film_grain_enabled_flag)
+      {
+        RegionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_min = m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_min ;
+        RegionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_max = m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[0].m_fgr_region_interval_max ;
+        RegionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_min = m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_min ;
+        RegionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_max = m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[1].m_fgr_region_interval_max ;
+        RegionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_min = m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_min ;
+        RegionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_max = m_fgrSEIregionCharacteristics[i].m_fgr_regionIntervals[2].m_fgr_region_interval_max ;
+      }
+    }
+  }
+#endif
   // cll SEI
   void  setCLLSEIEnabled(bool b)                                     { m_cllSEIEnabled = b; }
   bool  getCLLSEIEnabled()                                           { return m_cllSEIEnabled; }
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index c53985989..a98945f0d 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -801,7 +801,27 @@ void EncGOP::xCreateIRAPLeadingSEIMessages (SEIMessages& seiMessages, const SPS
     }
     seiMessages.push_back(sei);
   }
-
+#if JVET_AG0328
+  // film grain regions
+  if (m_pcCfg->getFilmGrainRegionsCharactersticsSEIEnabled() && !m_pcCfg->getFilmGrainRegionsCharactersticsSEIPerPictureSEI())
+  {
+    SEIFilmGrainRegionsCharacteristics *sei = new SEIFilmGrainRegionsCharacteristics;
+    m_seiEncoder.initSEIFilmGrainRegionsCharacteristics(sei);
+    if (m_pcCfg->getFilmGrainRegionsAnalysisEnabled())
+    {
+      printf("Film grain analysis not implemented yet for region based\n");
+      /*sei->m_fgr_log2ScaleFactor = m_fgrAnalyzer.getLog2scaleFactor();
+      for (int compIdx = 0; compIdx < getNumberValidComponents(m_pcCfg->getChromaFormatIdc()); compIdx++)
+      {
+        if (sei->m_compModel[compIdx].presentFlag)
+        {   // higher importance of presentFlag is from cfg file
+          sei->m_compModel[compIdx] = m_fgAnalyzer.getCompModel(compIdx);
+        }
+      }*/
+    }
+    seiMessages.push_back(sei);
+  }
+#endif
   // mastering display colour volume
   if (m_pcCfg->getMasteringDisplaySEI().colourVolumeSEIEnabled)
   {
@@ -1017,6 +1037,26 @@ void EncGOP::xCreatePerPictureSEIMessages (int picInGOP, SEIMessages& seiMessage
     }
     seiMessages.push_back(fgcSEI);
   }
+#if JVET_AG0328
+  if (m_pcCfg->getFilmGrainRegionsCharactersticsSEIEnabled() && m_pcCfg->getFilmGrainRegionsCharactersticsSEIPerPictureSEI())
+  {
+    SEIFilmGrainRegionsCharacteristics *fgrSEI = new SEIFilmGrainRegionsCharacteristics;
+    m_seiEncoder.initSEIFilmGrainRegionsCharacteristics(fgrSEI);
+    if (m_pcCfg->getFilmGrainRegionsAnalysisEnabled())
+    {
+      printf("Film grain analysis not implemented yet for region based\n");
+      /*fgcSEI->m_log2ScaleFactor = m_fgAnalyzer.getLog2scaleFactor();
+       for (int compIdx = 0; compIdx < getNumberValidComponents(m_pcCfg->getChromaFormatIdc()); compIdx++)
+       {
+       if (fgcSEI->m_compModel[compIdx].presentFlag)
+       {   // higher importance of presentFlag is from cfg file
+       fgcSEI->m_compModel[compIdx] = m_fgAnalyzer.getCompModel(compIdx);
+       }
+       }*/
+    }
+    seiMessages.push_back(fgrSEI);
+  }
+#endif
 
   if (m_pcCfg->getNnPostFilterSEIActivationEnabled() && !m_pcCfg->getNnPostFilterSEIActivationUseSuffixSEI())
   {
diff --git a/source/Lib/EncoderLib/SEIEncoder.cpp b/source/Lib/EncoderLib/SEIEncoder.cpp
index 6a348d976..92e590d37 100644
--- a/source/Lib/EncoderLib/SEIEncoder.cpp
+++ b/source/Lib/EncoderLib/SEIEncoder.cpp
@@ -653,6 +653,20 @@ void SEIEncoder::initSEIProcessingOrderInfo(SEIProcessingOrderInfo *seiProcessin
 #endif
         break;
       }
+#if JVET_AG0328
+      case SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS:
+      {
+        SEIFilmGrainRegionsCharacteristics* seiFGR = new SEIFilmGrainRegionsCharacteristics;
+        initSEIFilmGrainRegionsCharacteristics(seiFGR);
+  #if JVET_AF0310_PO_NESTING
+        seiProcessingOrderNesting->m_ponWrapSeiMessages.push_back(seiFGR);
+  #else
+        seiProcessingOrderInfo->m_posWrapSeiMessages.push_back(seiFGR);
+  #endif
+        break;
+      }
+
+#endif
       case SEI::PayloadType::CONTENT_LIGHT_LEVEL_INFO:
       {
         SEIContentLightLevelInfo* seiCCL = new SEIContentLightLevelInfo;
@@ -994,6 +1008,50 @@ void SEIEncoder::initSEIFilmGrainCharacteristics(SEIFilmGrainCharacteristics *se
   }
 }
 
+#if JVET_AG0328
+void SEIEncoder::initSEIFilmGrainRegionsCharacteristics(SEIFilmGrainRegionsCharacteristics *seiFilmGrainRegion)
+{
+  CHECK(!(m_isInitialized), "Unspecified error");
+  CHECK(!(seiFilmGrainRegion != nullptr), "Unspecified error");
+  //  Set SEI message parameters read from command line options
+  seiFilmGrainRegion->m_filmGrainRegionsCharacteristicsCancelFlag      = m_pcCfg->getFilmGrainRegionsCharactersticsSEICancelFlag();
+  seiFilmGrainRegion->m_filmGrainRegionsCharacteristicsPersistenceFlag = m_pcCfg->getFilmGrainRegionsCharactersticsSEIPersistenceFlag();
+  seiFilmGrainRegion->m_filmGrainRegionsModelId                        = m_pcCfg->getFilmGrainRegionsCharactersticsSEIModelID();
+  seiFilmGrainRegion->m_fgr_separateColourDescriptionPresentFlag    = m_pcCfg->getFilmGrainRegionsCharactersticsSEISepColourDescPresent();
+  seiFilmGrainRegion->m_fgr_blendingModeId                          = m_pcCfg->getFilmGrainRegionsCharactersticsSEIBlendingModeID();
+  seiFilmGrainRegion->m_fgr_log2ScaleFactor                         = m_pcCfg->getFilmGrainRegionsCharactersticsSEILog2ScaleFactor();
+  for (int i = 0; i < MAX_NUM_COMPONENT; i++)
+  {
+    seiFilmGrainRegion->m_fgr_compModel[i].presentFlag = m_pcCfg->getFGRSEICompModelPresent(i);
+    if (seiFilmGrainRegion->m_fgr_compModel[i].presentFlag)
+    {
+      seiFilmGrainRegion->m_fgr_compModel[i].numModelValues = 1 + m_pcCfg->getFGRSEINumModelValuesMinus1(i);
+      seiFilmGrainRegion->m_fgr_compModel[i].numIntensityIntervals = 1 + m_pcCfg->getFGRSEINumIntensityIntervalMinus1(i);
+      seiFilmGrainRegion->m_fgr_compModel[i].intensityValues.resize(seiFilmGrainRegion->m_fgr_compModel[i].numIntensityIntervals);
+      for (int j = 0; j < seiFilmGrainRegion->m_fgr_compModel[i].numIntensityIntervals; j++)
+      {
+        seiFilmGrainRegion->m_fgr_compModel[i].intensityValues[j].intensityIntervalLowerBound = m_pcCfg->getFGRSEIIntensityIntervalLowerBound(i, j);
+        seiFilmGrainRegion->m_fgr_compModel[i].intensityValues[j].intensityIntervalUpperBound = m_pcCfg->getFGRSEIIntensityIntervalUpperBound(i, j);
+        seiFilmGrainRegion->m_fgr_compModel[i].intensityValues[j].compModelValue.resize(seiFilmGrainRegion->m_fgr_compModel[i].numModelValues);
+        for (int k = 0; k < seiFilmGrainRegion->m_fgr_compModel[i].numModelValues; k++)
+        {
+          seiFilmGrainRegion->m_fgr_compModel[i].intensityValues[j].compModelValue[k] = m_pcCfg->getFGRSEICompModelValue(i, j, k);
+        }
+      }
+    }
+  }
+  seiFilmGrainRegion->m_fgr_region_information_present_flag = m_pcCfg->getFilmGrainRegionInformationPresentFlag();
+  seiFilmGrainRegion->m_fgr_region_based_adaptation_flag = m_pcCfg->getFilmGrainRegionBasedAdaptationFlag();
+  seiFilmGrainRegion->m_fgr_alpha_channel_adaptation_flag = m_pcCfg->getFilmGrainAlphaChannelAdaptationFlag();
+  seiFilmGrainRegion->m_fgr_active_regions_number = m_pcCfg->getFilmGrainNumActiveRegionsMinus1() + 1 ;
+  if (seiFilmGrainRegion->m_fgr_region_based_adaptation_flag)
+  {
+    seiFilmGrainRegion->fgr_active_regions.resize(seiFilmGrainRegion->m_fgr_active_regions_number);
+    m_pcCfg->getFilmGrainRegionCharacteristics(seiFilmGrainRegion->fgr_active_regions,m_pcCfg->getFilmGrainNumActiveRegionsMinus1());
+  }
+}
+#endif
+
 void SEIEncoder::initSEIMasteringDisplayColourVolume(SEIMasteringDisplayColourVolume *seiMDCV)
 {
   CHECK(!(m_isInitialized), "Unspecified error");
diff --git a/source/Lib/EncoderLib/SEIEncoder.h b/source/Lib/EncoderLib/SEIEncoder.h
index df965ceda..1184026c1 100644
--- a/source/Lib/EncoderLib/SEIEncoder.h
+++ b/source/Lib/EncoderLib/SEIEncoder.h
@@ -77,6 +77,9 @@ public:
   void initSEISampleAspectRatioInfo(SEISampleAspectRatioInfo *sei);
   void initSEIPhaseIndication(SEIPhaseIndication* sei, int ppsId);
   void initSEIFilmGrainCharacteristics(SEIFilmGrainCharacteristics *sei);
+#if JVET_AG0328
+  void initSEIFilmGrainRegionsCharacteristics(SEIFilmGrainRegionsCharacteristics *sei);
+#endif
   void initSEIMasteringDisplayColourVolume(SEIMasteringDisplayColourVolume *sei);
   void initSEIContentLightLevel(SEIContentLightLevelInfo *sei);
   void initSEIAmbientViewingEnvironment(SEIAmbientViewingEnvironment *sei);
diff --git a/source/Lib/EncoderLib/SEIwrite.cpp b/source/Lib/EncoderLib/SEIwrite.cpp
index 678f3effd..2be643e11 100644
--- a/source/Lib/EncoderLib/SEIwrite.cpp
+++ b/source/Lib/EncoderLib/SEIwrite.cpp
@@ -137,7 +137,12 @@ void SEIWriter::xWriteSEIpayloadData(OutputBitstream &bs, const SEI &sei, HRD &h
   case SEI::PayloadType::FILM_GRAIN_CHARACTERISTICS:
     xWriteSEIFilmGrainCharacteristics(*static_cast<const SEIFilmGrainCharacteristics *>(&sei));
     break;
-  case SEI::PayloadType::CONTENT_LIGHT_LEVEL_INFO:
+#if JVET_AG0328
+  case SEI::PayloadType::FILM_GRAIN_REGIONS_CHARACTERISTICS:
+    xWriteSEIFilmGrainRegionsCharacteristics(*static_cast<const SEIFilmGrainRegionsCharacteristics *>(&sei));
+    break;
+#endif
+    case SEI::PayloadType::CONTENT_LIGHT_LEVEL_INFO:
     xWriteSEIContentLightLevelInfo(*static_cast<const SEIContentLightLevelInfo *>(&sei));
     break;
   case SEI::PayloadType::AMBIENT_VIEWING_ENVIRONMENT:
@@ -1453,6 +1458,95 @@ void SEIWriter::xWriteSEIFilmGrainCharacteristics(const SEIFilmGrainCharacterist
     xWriteFlag(sei.m_filmGrainCharacteristicsPersistenceFlag, "fg_characteristics_persistence_flag");
   } // cancel flag
 }
+#if JVET_AG0328
+void SEIWriter::xWriteSEIFilmGrainRegionsCharacteristics(const SEIFilmGrainRegionsCharacteristics &sei)
+{
+  xWriteFlag(sei.m_filmGrainRegionsCharacteristicsCancelFlag,        "fgr_cancel_flag");
+  if (!sei.m_filmGrainRegionsCharacteristicsCancelFlag)
+  {
+    xWriteCode(sei.m_filmGrainRegionsModelId, 2,                     "fgr_model_id");
+    xWriteFlag(sei.m_fgr_separateColourDescriptionPresentFlag,    "fgr_separate_colour_description_present_flag");
+    if (sei.m_fgr_separateColourDescriptionPresentFlag)
+    {
+      xWriteCode(sei.m_filmGrainRegionsBitDepthLumaMinus8, 3,        "fgr_bit_depth_luma_minus8");
+      xWriteCode(sei.m_filmGrainRegionsBitDepthChromaMinus8, 3,      "fgr_bit_depth_chroma_minus8");
+      xWriteFlag(sei.m_filmGrainRegionsFullRangeFlag,                "fgr_full_range_flag");
+      xWriteCode(sei.m_filmGrainRegionsColourPrimaries, 8,           "fgr_colour_primaries");
+      xWriteCode(sei.m_filmGrainRegionsTransferCharacteristics, 8,   "fgr_transfer_characteristics");
+      xWriteCode(sei.m_filmGrainRegionsMatrixCoeffs, 8,              "fgr_matrix_coeffs");
+    }
+    xWriteCode(sei.m_fgr_blendingModeId, 2,                       "fgr_blending_mode_id");
+    xWriteCode(sei.m_fgr_log2ScaleFactor, 4,                      "fgr_log2_scale_factor");
+    for (int c = 0; c<3; c++)
+    {
+      const SEIFilmGrainRegionsCharacteristics::CompModel &cm = sei.m_fgr_compModel[c];
+      const uint32_t numIntensityIntervals = (uint32_t) cm.numIntensityIntervals;
+      const uint32_t numModelValues = cm.numModelValues;
+      xWriteFlag(sei.m_fgr_compModel[c].presentFlag && numIntensityIntervals>0 && numModelValues>0, "fgr_comp_model_present_flag[c]");
+    }
+    for (uint32_t c = 0; c<3; c++)
+    {
+      const SEIFilmGrainRegionsCharacteristics::CompModel &cm = sei.m_fgr_compModel[c];
+      const uint32_t numIntensityIntervals = (uint32_t) cm.numIntensityIntervals;
+      const uint32_t numModelValues = cm.numModelValues;
+      if (cm.presentFlag && numIntensityIntervals>0 && numModelValues>0)
+      {
+        assert(numIntensityIntervals <= 256);
+        assert(numModelValues <= 256);
+        xWriteCode(numIntensityIntervals - 1, 8,              "fgr_num_intensity_intervals_minus1[c]");
+        xWriteCode(numModelValues - 1, 3,                     "fgr_num_model_values_minus1[c]");
+        for (uint32_t interval = 0; interval<numIntensityIntervals; interval++)
+        {
+          const SEIFilmGrainRegionsCharacteristics::CompModelIntensityValues &cmiv = cm.intensityValues[interval];
+          xWriteCode(cmiv.intensityIntervalLowerBound, 8,     "fgr_intensity_interval_lower_bound[c][i]");
+          xWriteCode(cmiv.intensityIntervalUpperBound, 8,     "fgr_intensity_interval_upper_bound[c][i]");
+          for (uint32_t j = 0; j<cm.numModelValues; j++)
+          {
+            xWriteSvlc(cmiv.compModelValue[j],                "fgr_comp_model_value[c][i]");
+          }
+        }
+      }
+    } // for c
+    xWriteFlag(sei.m_fgr_region_information_present_flag,        "fgr_region_information_present_flag");
+    
+    if(sei.m_fgr_region_information_present_flag)
+    {
+      xWriteFlag(sei.m_fgr_region_based_adaptation_flag,        "fgr_region_based_adaptation_flag");
+      if (sei.m_fgr_region_based_adaptation_flag)
+      {
+        xWriteCode(sei.m_fgr_active_regions_number -1, 8,     "fgr_active_regions_number_minus1");
+        for (int idx = 0 ; idx < sei.m_fgr_active_regions_number; idx++)
+        {
+          xWriteCode(sei.fgr_active_regions[idx].m_fgr_region_top, 16,     "fgr_region_top[i]");
+          xWriteCode(sei.fgr_active_regions[idx].m_fgr_region_left, 16,     "fgr_region_left[i]");
+          xWriteCode(sei.fgr_active_regions[idx].m_fgr_region_width, 16,     "fgr_region_width[i]");
+          xWriteCode(sei.fgr_active_regions[idx].m_fgr_region_height, 16,     "fgr_region_height[i]");
+          xWriteFlag(sei.fgr_active_regions[idx].m_film_grain_enabled_flag,     "fgr_film_grain_enabled_flag[i]");
+          if (sei.fgr_active_regions[idx].m_film_grain_enabled_flag)
+          {
+            for (int c = 0 ; c < 3 ; c++)
+            {
+              const SEIFilmGrainRegionsCharacteristics::CompModel &cm = sei.m_fgr_compModel[c];
+              const uint32_t numIntensityIntervals = (uint32_t) cm.numIntensityIntervals;
+              const uint32_t numModelValues = cm.numModelValues;
+              if (cm.presentFlag && numIntensityIntervals>0 && numModelValues>0)
+              {
+                xWriteCode(sei.fgr_active_regions[idx].m_fgr_regionIntervals[c].m_fgr_region_interval_min, 8,     "fgr_region_interval_min[i][c]");
+                xWriteCode(sei.fgr_active_regions[idx].m_fgr_regionIntervals[c].m_fgr_region_interval_max, 8,     "fgr_region_interval_max[i][c]");
+              }
+            }
+          }
+        }
+      }
+      else
+      {
+        xWriteFlag(sei.m_fgr_alpha_channel_adaptation_flag,        "fgr_alpha_channel_adaptation_flag");
+      }
+    }
+    xWriteFlag(sei.m_filmGrainRegionsCharacteristicsPersistenceFlag, "fgr_characteristics_persistence_flag");
+  } // cancel flag
+}
+#endif
 
 void SEIWriter::xWriteSEIContentLightLevelInfo(const SEIContentLightLevelInfo& sei)
 {
diff --git a/source/Lib/EncoderLib/SEIwrite.h b/source/Lib/EncoderLib/SEIwrite.h
index fd890d63d..fd3b6aef6 100644
--- a/source/Lib/EncoderLib/SEIwrite.h
+++ b/source/Lib/EncoderLib/SEIwrite.h
@@ -89,6 +89,9 @@ protected:
   void xWriteSEIConstrainedRaslIndication         (const SEIConstrainedRaslIndication &sei);
   void xWriteSEIUserDataRegistered(const SEIUserDataRegistered& sei);
   void xWriteSEIFilmGrainCharacteristics(const SEIFilmGrainCharacteristics& sei);
+#if JVET_AG0328
+  void xWriteSEIFilmGrainRegionsCharacteristics(const SEIFilmGrainRegionsCharacteristics& sei);
+#endif
   void xWriteSEIContentLightLevelInfo(const SEIContentLightLevelInfo& sei);
   void xWriteSEIAmbientViewingEnvironment(const SEIAmbientViewingEnvironment& sei);
   void xWriteSEIContentColourVolume(const SEIContentColourVolume &sei);
