From a29425ab4e7a75d584030abeab1254014c28efa7 Mon Sep 17 00:00:00 2001
From: Frank Bossen <frank@bossentech.com>
Date: Mon, 27 Jan 2020 03:10:59 +0100
Subject: [PATCH] JVET-Q0795: Cross-component ALF

---
 doc/software-manual.tex                       |   12 +
 source/App/EncoderApp/EncApp.cpp              |    7 +
 source/App/EncoderApp/EncAppCfg.cpp           |   18 +-
 source/App/EncoderApp/EncAppCfg.h             |    7 +
 source/Lib/CommonLib/AdaptiveLoopFilter.cpp   |  269 +++-
 source/Lib/CommonLib/AdaptiveLoopFilter.h     |   26 +
 source/Lib/CommonLib/AlfParameters.h          |   55 +
 source/Lib/CommonLib/CodingStatistics.h       |    6 +
 source/Lib/CommonLib/CommonDef.h              |   11 +
 source/Lib/CommonLib/Contexts.cpp             |   10 +
 source/Lib/CommonLib/Contexts.h               |    3 +
 source/Lib/CommonLib/Slice.cpp                |   32 +-
 source/Lib/CommonLib/Slice.h                  |   66 +-
 source/Lib/CommonLib/TypeDef.h                |    1 +
 source/Lib/DecoderLib/CABACReader.cpp         |   58 +
 source/Lib/DecoderLib/CABACReader.h           |    5 +
 source/Lib/DecoderLib/DecLib.cpp              |   79 +-
 source/Lib/DecoderLib/VLCReader.cpp           |  143 +-
 source/Lib/DecoderLib/VLCReader.h             |    3 +
 source/Lib/EncoderLib/CABACWriter.cpp         |   63 +
 source/Lib/EncoderLib/CABACWriter.h           |    4 +
 .../Lib/EncoderLib/EncAdaptiveLoopFilter.cpp  | 1346 +++++++++++++++++
 source/Lib/EncoderLib/EncAdaptiveLoopFilter.h |   68 +
 source/Lib/EncoderLib/EncCfg.h                |   18 +-
 source/Lib/EncoderLib/EncGOP.cpp              |   40 +
 source/Lib/EncoderLib/EncLib.cpp              |    6 +
 source/Lib/EncoderLib/VLCWriter.cpp           |  104 ++
 27 files changed, 2415 insertions(+), 45 deletions(-)

diff --git a/doc/software-manual.tex b/doc/software-manual.tex
index 582dcbe4f..4b25c8218 100644
--- a/doc/software-manual.tex
+++ b/doc/software-manual.tex
@@ -2233,6 +2233,18 @@ switched at CTB level. Set to 1 to disable alternative chroma filters.
 Value shall be in the range 1..8.
 \\
 
+\Option{CCALF} &
+%\ShortOption{\None} &
+\Default{true} &
+Enables cross-component ALF.
+\\
+
+\Option{CCALFQpTh} &
+%\ShortOption{\None} &
+\Default{37} &
+QP threshold above which the encoder reduces cross-component ALF usage.
+\\
+
 \Option{SMVD} &
 %\ShortOption{\None} &
 \Default{false} &
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 77f765178..51e75c630 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -212,6 +212,9 @@ void EncApp::xInitLibCfg()
   m_cEncLib.setNoPartitionConstraintsOverrideConstraintFlag      ( !m_SplitConsOverrideEnabledFlag );
   m_cEncLib.setNoSaoConstraintFlag                               ( !m_bUseSAO );
   m_cEncLib.setNoAlfConstraintFlag                               ( !m_alf );
+#if JVET_Q0795_CCALF
+  m_cEncLib.setNoAlfConstraintFlag                               ( !m_ccalf );
+#endif
   m_cEncLib.setNoRefWraparoundConstraintFlag                     ( m_bNoRefWraparoundConstraintFlag );
   m_cEncLib.setNoTemporalMvpConstraintFlag                       ( m_TMVPModeId ? false : true );
   m_cEncLib.setNoSbtmvpConstraintFlag                            ( m_SubPuMvpMode ? false : true );
@@ -755,6 +758,10 @@ void EncApp::xInitLibCfg()
   m_cEncLib.setForceSingleSplitThread                            ( m_forceSplitSequential );
 #endif
   m_cEncLib.setUseALF                                            ( m_alf );
+#if JVET_Q0795_CCALF
+  m_cEncLib.setUseCCALF                                          ( m_ccalf );
+  m_cEncLib.setCCALFQpThreshold                                  ( m_ccalfQpThreshold );
+#endif
   m_cEncLib.setLmcs                                              ( m_lmcsEnabled );
   m_cEncLib.setReshapeSignalType                                 ( m_reshapeSignalType );
   m_cEncLib.setReshapeIntraCMD                                   ( m_intraCMD );
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index cdad7cff4..5feb6cdb8 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -87,6 +87,9 @@ EncAppCfg::EncAppCfg()
 , m_noPartitionConstraintsOverrideConstraintFlag(false)
 , m_bNoSaoConstraintFlag(false)
 , m_bNoAlfConstraintFlag(false)
+#if JVET_Q0795_CCALF
+, m_noCCAlfConstraintFlag(false)
+#endif
 , m_bNoRefWraparoundConstraintFlag(false)
 , m_bNoTemporalMvpConstraintFlag(false)
 , m_bNoSbtmvpConstraintFlag(false)
@@ -1357,7 +1360,11 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   ("NumWppExtraLines",                                m_numWppExtraLines,                           0, "Number of additional wpp lines to switch when threads are blocked")
   ("DebugCTU",                                        m_debugCTU,                                  -1, "If DebugBitstream is present, load frames up to this POC from this bitstream. Starting with DebugPOC-frame at CTUline containin debug CTU.")
   ("EnsureWppBitEqual",                               m_ensureWppBitEqual,                      false, "Ensure the results are equal to results with WPP-style parallelism, even if WPP is off")
-  ( "ALF",                                             m_alf,                                    true, "Adpative Loop Filter\n" )
+  ( "ALF",                                             m_alf,                                    true, "Adaptive Loop Filter\n" )
+#if JVET_Q0795_CCALF
+  ( "CCALF",                                           m_ccalf,                                  true, "Cross-component Adaptive Loop Filter" )
+  ( "CCALFQpTh",                                       m_ccalfQpThreshold,                         37, "QP threshold above which encoder reduces CCALF usage")
+#endif
   ( "ScalingRatioHor",                                m_scalingRatioHor,                          1.0, "Scaling ratio in hor direction" )
   ( "ScalingRatioVer",                                m_scalingRatioVer,                          1.0, "Scaling ratio in ver direction" )
   ( "FractionNumFrames",                              m_fractionOfFrames,                         1.0, "Encode a fraction of the specified in FramesToBeEncoded frames" )
@@ -2533,6 +2540,12 @@ bool EncAppCfg::xCheckParameter()
   }
 
 
+#if JVET_Q0795_CCALF
+  if (!m_alf)
+  {
+    xConfirmPara( m_ccalf, "CCALF cannot be enabled when ALF is disabled" );
+  }
+#endif
 
 
   xConfirmPara( m_iSourceWidth  % SPS::getWinUnitX(m_chromaFormatIDC) != 0, "Picture width must be an integer multiple of the specified chroma subsampling");
@@ -3604,6 +3617,9 @@ void EncAppCfg::xPrintParameter()
   msg( VERBOSE, "MCTS:%d ", m_MCTSEncConstraint );
   msg( VERBOSE, "SAO:%d ", (m_bUseSAO)?(1):(0));
   msg( VERBOSE, "ALF:%d ", m_alf ? 1 : 0 );
+#if JVET_Q0795_CCALF
+  msg( VERBOSE, "CCALF:%d ", m_ccalf ? 1 : 0 );
+#endif
 
   msg( VERBOSE, "WPP:%d ", (int)m_useWeightedPred);
   msg( VERBOSE, "WPB:%d ", (int)m_useWeightedBiPred);
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index 4ce37e402..f71b66571 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -137,6 +137,9 @@ protected:
   bool      m_noPartitionConstraintsOverrideConstraintFlag;
   bool      m_bNoSaoConstraintFlag;
   bool      m_bNoAlfConstraintFlag;
+#if JVET_Q0795_CCALF
+  bool      m_noCCAlfConstraintFlag;
+#endif
   bool      m_bNoRefWraparoundConstraintFlag;
   bool      m_bNoTemporalMvpConstraintFlag;
   bool      m_bNoSbtmvpConstraintFlag;
@@ -664,6 +667,10 @@ protected:
   bool        m_forceDecodeBitstream1;
 
   bool        m_alf;                                          ///< Adaptive Loop Filter
+#if JVET_Q0795_CCALF
+  bool        m_ccalf;
+  int         m_ccalfQpThreshold;
+#endif
 
   double      m_scalingRatioHor;
   double      m_scalingRatioVer;
diff --git a/source/Lib/CommonLib/AdaptiveLoopFilter.cpp b/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
index 998041f9c..b1ef4b056 100644
--- a/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
+++ b/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
@@ -63,6 +63,9 @@ AdaptiveLoopFilter::AdaptiveLoopFilter()
   }
 
   m_deriveClassificationBlk = deriveClassificationBlk;
+#if JVET_Q0795_CCALF
+  m_filterCcAlf = filterBlkCcAlf<CC_ALF>;
+#endif
   m_filter5x5Blk = filterBlk<ALF_FILTER_5>;
   m_filter7x7Blk = filterBlk<ALF_FILTER_7>;
 
@@ -285,6 +288,47 @@ const int AdaptiveLoopFilter::m_classToFilterMapping[NUM_FIXED_FILTER_SETS][MAX_
   { 16,  31,  32,  15,  60,  30,   4,  17,  19,  25,  22,  20,   4,  53,  19,  21,  22,  46,  25,  55,  26,  48,  63,  58,  55 },
 };
 
+#if JVET_Q0795_CCALF
+void AdaptiveLoopFilter::applyCcAlfFilter(CodingStructure &cs, ComponentID compID, const PelBuf &dstBuf,
+                                          const PelUnitBuf &recYuvExt, uint8_t *filterControl,
+                                          const short filterSet[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF],
+                                          const int   selectedFilterIdx)
+{
+  int ctuIdx = 0;
+  for( int yPos = 0; yPos < m_picHeight; yPos += m_maxCUHeight )
+  {
+    for( int xPos = 0; xPos < m_picWidth; xPos += m_maxCUWidth )
+    {
+      const int width = ( xPos + m_maxCUWidth > m_picWidth ) ? ( m_picWidth - xPos ) : m_maxCUWidth;
+      const int height = ( yPos + m_maxCUHeight > m_picHeight ) ? ( m_picHeight - yPos ) : m_maxCUHeight;
+      const UnitArea area( m_chromaFormat, Area( xPos, yPos, width, height ) );
+      const int chromaScaleX = getComponentScaleX( compID, m_chromaFormat );
+      const int chromaScaleY = getComponentScaleY( compID, m_chromaFormat );
+
+      Area blkDst(xPos >> chromaScaleX, yPos >> chromaScaleY, width >> chromaScaleX, height >> chromaScaleY);
+      Area blkSrc(xPos, yPos, width, height);
+
+      int filterIdx =
+        (filterControl == nullptr)
+          ? selectedFilterIdx
+          : filterControl[(yPos >> cs.pcv->maxCUHeightLog2) * cs.pcv->widthInCtus + (xPos >> cs.pcv->maxCUWidthLog2)];
+      bool skipFiltering = (filterControl != nullptr && filterIdx == 0) ? true : false;
+      if (!skipFiltering)
+      {
+        if (filterControl != nullptr)
+          filterIdx--;
+
+        const int16_t *filterCoeff = filterSet[filterIdx];
+
+        m_filterCcAlf(dstBuf, recYuvExt, blkDst, blkSrc, compID, filterCoeff, m_clpRngs, cs, m_alfVBLumaCTUHeight,
+                      m_alfVBLumaPos);
+      }
+      ctuIdx++;
+    }
+  }
+}
+#endif
+
 void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
 {
 
@@ -342,6 +386,12 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
       for( int compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++ )
       {
         ctuEnableFlag |= m_ctuEnableFlag[compIdx][ctuIdx] > 0;
+#if JVET_Q0795_CCALF
+        if (cu->slice->m_ccAlfFilterParam.ccAlfFilterEnabled[compIdx - 1])
+        {
+          ctuEnableFlag |= m_ccAlfFilterControl[compIdx - 1][ctuIdx] > 0;
+        }
+#endif
       }
       int rasterSliceAlfPad = 0;
       if( ctuEnableFlag && isCrossedByVirtualBoundaries( cs, xPos, yPos, width, height, clipTop, clipBottom, clipLeft, clipRight, numHorVirBndry, numVerVirBndry, horVirBndryPos, verVirBndryPos, rasterSliceAlfPad ) )
@@ -417,6 +467,24 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
                   , m_alfVBChmaCTUHeight
                    , m_alfVBChmaPos );
               }
+#if JVET_Q0795_CCALF
+              if (cu->slice->m_ccAlfFilterParam.ccAlfFilterEnabled[compIdx - 1])
+              {
+                const int filterIdx = m_ccAlfFilterControl[compIdx - 1][ctuIdx];
+
+                if (filterIdx != 0)
+                {
+                  const Area blkSrc(0, 0, w >> chromaScaleX, h >> chromaScaleY);
+                  Area       blkDst(xPos >> chromaScaleX, yPos >> chromaScaleY, width >> chromaScaleX,
+                              height >> chromaScaleY);
+
+                  const int16_t *filterCoeff = m_ccAlfFilterParam.ccAlfCoeff[compIdx - 1][filterIdx - 1];
+
+                  m_filterCcAlf(recYuv.get(compID), tmpYuv, blkDst, blkSrc, compID, filterCoeff, m_clpRngs, cs,
+                                m_alfVBLumaCTUHeight, m_alfVBLumaPos);
+                }
+              }
+#endif
             }
 
             xStart = xEnd;
@@ -427,46 +495,63 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
       }
       else
       {
-      const UnitArea area( cs.area.chromaFormat, Area( xPos, yPos, width, height ) );
-      if( m_ctuEnableFlag[COMPONENT_Y][ctuIdx] )
-      {
-        Area blk( xPos, yPos, width, height );
-        deriveClassification( m_classifier, tmpYuv.get( COMPONENT_Y ), blk, blk );
-        short filterSetIndex = alfCtuFilterIndex[ctuIdx];
-        short *coeff;
-        short *clip;
-        if (filterSetIndex >= NUM_FIXED_FILTER_SETS)
+        const UnitArea area( cs.area.chromaFormat, Area( xPos, yPos, width, height ) );
+        if( m_ctuEnableFlag[COMPONENT_Y][ctuIdx] )
         {
-          coeff = m_coeffApsLuma[filterSetIndex - NUM_FIXED_FILTER_SETS];
-          clip = m_clippApsLuma[filterSetIndex - NUM_FIXED_FILTER_SETS];
+          Area blk( xPos, yPos, width, height );
+          deriveClassification( m_classifier, tmpYuv.get( COMPONENT_Y ), blk, blk );
+          short filterSetIndex = alfCtuFilterIndex[ctuIdx];
+          short *coeff;
+          short *clip;
+          if (filterSetIndex >= NUM_FIXED_FILTER_SETS)
+          {
+            coeff = m_coeffApsLuma[filterSetIndex - NUM_FIXED_FILTER_SETS];
+            clip = m_clippApsLuma[filterSetIndex - NUM_FIXED_FILTER_SETS];
+          }
+          else
+          {
+            coeff = m_fixedFilterSetCoeffDec[filterSetIndex];
+            clip = m_clipDefault;
+          }
+          m_filter7x7Blk(m_classifier, recYuv, tmpYuv, blk, blk, COMPONENT_Y, coeff, clip, m_clpRngs.comp[COMPONENT_Y], cs
+            , m_alfVBLumaCTUHeight
+            , m_alfVBLumaPos
+          );
         }
-        else
+
+        for( int compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++ )
         {
-          coeff = m_fixedFilterSetCoeffDec[filterSetIndex];
-          clip = m_clipDefault;
-        }
-        m_filter7x7Blk(m_classifier, recYuv, tmpYuv, blk, blk, COMPONENT_Y, coeff, clip, m_clpRngs.comp[COMPONENT_Y], cs
-          , m_alfVBLumaCTUHeight
-          , m_alfVBLumaPos
-        );
-      }
+          ComponentID compID = ComponentID( compIdx );
+          const int chromaScaleX = getComponentScaleX( compID, tmpYuv.chromaFormat );
+          const int chromaScaleY = getComponentScaleY( compID, tmpYuv.chromaFormat );
 
-      for( int compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++ )
-      {
-        ComponentID compID = ComponentID( compIdx );
-        const int chromaScaleX = getComponentScaleX( compID, tmpYuv.chromaFormat );
-        const int chromaScaleY = getComponentScaleY( compID, tmpYuv.chromaFormat );
+          if (m_ctuEnableFlag[compIdx][ctuIdx])
+          {
+            Area    blk(xPos >> chromaScaleX, yPos >> chromaScaleY, width >> chromaScaleX, height >> chromaScaleY);
+            uint8_t alt_num = m_ctuAlternative[compIdx][ctuIdx];
+            m_filter5x5Blk(m_classifier, recYuv, tmpYuv, blk, blk, compID, m_chromaCoeffFinal[alt_num],
+                           m_chromaClippFinal[alt_num], m_clpRngs.comp[compIdx], cs, m_alfVBChmaCTUHeight,
+                           m_alfVBChmaPos);
+          }
+#if JVET_Q0795_CCALF
+          if (cu->slice->m_ccAlfFilterParam.ccAlfFilterEnabled[compIdx - 1])
+          {
+            const int filterIdx = m_ccAlfFilterControl[compIdx - 1][ctuIdx];
 
-        if( m_ctuEnableFlag[compIdx][ctuIdx] )
-        {
-          Area blk( xPos >> chromaScaleX, yPos >> chromaScaleY, width >> chromaScaleX, height >> chromaScaleY );
-          uint8_t alt_num = m_ctuAlternative[compIdx][ctuIdx];
-          m_filter5x5Blk(m_classifier, recYuv, tmpYuv, blk, blk, compID, m_chromaCoeffFinal[alt_num], m_chromaClippFinal[alt_num], m_clpRngs.comp[compIdx], cs
-            , m_alfVBChmaCTUHeight
-            , m_alfVBChmaPos);
+            if (filterIdx != 0)
+            {
+              Area blkDst(xPos >> chromaScaleX, yPos >> chromaScaleY, width >> chromaScaleX, height >> chromaScaleY);
+              Area blkSrc(xPos, yPos, width, height);
+
+              const int16_t *filterCoeff = m_ccAlfFilterParam.ccAlfCoeff[compIdx - 1][filterIdx - 1];
+
+              m_filterCcAlf(recYuv.get(compID), tmpYuv, blkDst, blkSrc, compID, filterCoeff, m_clpRngs, cs,
+                            m_alfVBLumaCTUHeight, m_alfVBLumaPos);
+            }
+          }
+#endif
         }
       }
-      }
       ctuIdx++;
     }
   }
@@ -581,6 +666,10 @@ void AdaptiveLoopFilter::create( const int picWidth, const int picHeight, const
   m_numCTUsInWidth = ( m_picWidth / m_maxCUWidth ) + ( ( m_picWidth % m_maxCUWidth ) ? 1 : 0 );
   m_numCTUsInHeight = ( m_picHeight / m_maxCUHeight ) + ( ( m_picHeight % m_maxCUHeight ) ? 1 : 0 );
   m_numCTUsInPic = m_numCTUsInHeight * m_numCTUsInWidth;
+#if JVET_Q0795_CCALF
+  m_filterShapesCcAlf[0].push_back(AlfFilterShape(size_CC_ALF));
+  m_filterShapesCcAlf[1].push_back(AlfFilterShape(size_CC_ALF));
+#endif
   m_filterShapes[CHANNEL_TYPE_LUMA].push_back( AlfFilterShape( 7 ) );
   m_filterShapes[CHANNEL_TYPE_CHROMA].push_back( AlfFilterShape( 5 ) );
   m_alfVBLumaPos = m_maxCUHeight - ALF_VB_POS_ABOVE_CTUROW_LUMA;
@@ -657,6 +746,11 @@ void AdaptiveLoopFilter::create( const int picWidth, const int picHeight, const
     m_clipDefault[i] = m_alfClippingValues[CHANNEL_TYPE_LUMA][0];
   }
   m_created = true;
+
+#if JVET_Q0795_CCALF
+  m_ccAlfFilterControl[0] = new uint8_t[m_numCTUsInPic];
+  m_ccAlfFilterControl[1] = new uint8_t[m_numCTUsInPic];
+#endif
 }
 
 void AdaptiveLoopFilter::destroy()
@@ -678,6 +772,22 @@ void AdaptiveLoopFilter::destroy()
   m_filterShapes[CHANNEL_TYPE_LUMA].clear();
   m_filterShapes[CHANNEL_TYPE_CHROMA].clear();
   m_created = false;
+
+#if JVET_Q0795_CCALF
+  m_filterShapesCcAlf[0].clear();
+  m_filterShapesCcAlf[1].clear();
+  if ( m_ccAlfFilterControl[0] )
+  {
+    delete [] m_ccAlfFilterControl[0];
+    m_ccAlfFilterControl[0] = nullptr;
+  }
+
+  if ( m_ccAlfFilterControl[1] )
+  {
+    delete [] m_ccAlfFilterControl[1];
+    m_ccAlfFilterControl[1] = nullptr;
+  }
+#endif
 }
 
 void AdaptiveLoopFilter::deriveClassification( AlfClassifier** classifier, const CPelBuf& srcLuma, const Area& blkDst, const Area& blk )
@@ -1152,3 +1262,96 @@ void AdaptiveLoopFilter::filterBlk(AlfClassifier **classifier, const PelUnitBuf
     pImgYPad6 += srcStride2;
   }
 }
+
+#if JVET_Q0795_CCALF
+template<AlfFilterType filtTypeCcAlf>
+void AdaptiveLoopFilter::filterBlkCcAlf(const PelBuf &dstBuf, const CPelUnitBuf &recSrc, const Area &blkDst,
+                                        const Area &blkSrc, const ComponentID compId, const int16_t *filterCoeff,
+                                        const ClpRngs &clpRngs, CodingStructure &cs, int vbCTUHeight, int vbPos)
+{
+  CHECK(1 << floorLog2(vbCTUHeight) != vbCTUHeight, "Not a power of 2");
+
+  CHECK(!isChroma(compId), "Must be chroma");
+
+  const SPS*     sps           = cs.slice->getSPS();
+  ChromaFormat nChromaFormat   = sps->getChromaFormatIdc();
+  const int clsSizeY           = 4;
+  const int clsSizeX           = 4;
+  const int      startHeight        = blkDst.y;
+  const int      endHeight          = blkDst.y + blkDst.height;
+  const int      startWidth         = blkDst.x;
+  const int      endWidth           = blkDst.x + blkDst.width;
+  const int scaleX             = getComponentScaleX(compId, nChromaFormat);
+  const int scaleY             = getComponentScaleY(compId, nChromaFormat);
+
+  CHECK( startHeight % clsSizeY, "Wrong startHeight in filtering" );
+  CHECK( startWidth % clsSizeX, "Wrong startWidth in filtering" );
+  CHECK( ( endHeight - startHeight ) % clsSizeY, "Wrong endHeight in filtering" );
+  CHECK( ( endWidth - startWidth ) % clsSizeX, "Wrong endWidth in filtering" );
+
+  CPelBuf     srcBuf     = recSrc.get(COMPONENT_Y);
+  const int   lumaStride = srcBuf.stride;
+  const Pel * lumaPtr    = srcBuf.buf + blkSrc.y * lumaStride + blkSrc.x;
+
+  const int   chromaStride = dstBuf.stride;
+  Pel *       chromaPtr    = dstBuf.buf + blkDst.y * chromaStride + blkDst.x;
+
+  for( int i = 0; i < endHeight - startHeight; i += clsSizeY )
+  {
+    for( int j = 0; j < endWidth - startWidth; j += clsSizeX )
+    {
+      for( int ii = 0; ii < clsSizeY; ii++ )
+      {
+        int row       = ii;
+        int col       = j;
+        Pel *srcSelf  = chromaPtr + col + row * chromaStride;
+
+        int offset1 = lumaStride;
+        int offset2 = -lumaStride;
+        int offset3 = 2 * lumaStride;
+        row <<= scaleY;
+        col <<= scaleX;
+        const Pel *srcCross = lumaPtr + col + row * lumaStride;
+
+        int pos = ((startHeight + i + ii) << scaleY) & (vbCTUHeight - 1);
+        if (pos == (vbPos - 2) || pos == (vbPos + 1))
+        {
+          offset3 = offset1;
+        }
+        else if (pos == (vbPos - 1) || pos == vbPos)
+        {
+          offset1 = 0;
+          offset2 = 0;
+          offset3 = 0;
+        }
+
+        for (int jj = 0; jj < clsSizeX; jj++)
+        {
+          const int jj2     = (jj << scaleX);
+          const int offset0 = 0;
+
+          int sum = 0;
+          const Pel currSrcCross = srcCross[offset0 + jj2];
+          sum += filterCoeff[0] * (srcCross[offset2 + jj2    ] - currSrcCross);
+          sum += filterCoeff[1] * (srcCross[offset0 + jj2 - 1] - currSrcCross);
+          sum += filterCoeff[2] * (srcCross[offset0 + jj2 + 1] - currSrcCross);
+          sum += filterCoeff[3] * (srcCross[offset1 + jj2 - 1] - currSrcCross);
+          sum += filterCoeff[4] * (srcCross[offset1 + jj2    ] - currSrcCross);
+          sum += filterCoeff[5] * (srcCross[offset1 + jj2 + 1] - currSrcCross);
+          sum += filterCoeff[6] * (srcCross[offset3 + jj2    ] - currSrcCross);
+
+          sum = (sum + ((1 << m_scaleBits ) >> 1)) >> m_scaleBits;
+          const int offset = 1 << clpRngs.comp[compId].bd >> 1;
+          sum = ClipPel(sum + offset, clpRngs.comp[compId]) - offset;
+          sum += srcSelf[jj];
+          srcSelf[jj] = ClipPel(sum, clpRngs.comp[compId]);
+        }
+      }
+    }
+
+    chromaPtr += chromaStride * clsSizeY;
+
+    lumaPtr += lumaStride * clsSizeY << getComponentScaleY(compId, nChromaFormat);
+  }
+}
+#endif
diff --git a/source/Lib/CommonLib/AdaptiveLoopFilter.h b/source/Lib/CommonLib/AdaptiveLoopFilter.h
index f93fd8e6c..39d173114 100644
--- a/source/Lib/CommonLib/AdaptiveLoopFilter.h
+++ b/source/Lib/CommonLib/AdaptiveLoopFilter.h
@@ -91,6 +91,13 @@ public:
                                       const CPelBuf &srcLuma, const Area &blkDst, const Area &blk, const int shift,
                                       const int vbCTUHeight, int vbPos);
   void deriveClassification( AlfClassifier** classifier, const CPelBuf& srcLuma, const Area& blkDst, const Area& blk );
+#if JVET_Q0795_CCALF
+  template<AlfFilterType filtTypeCcAlf>
+  static void filterBlkCcAlf(const PelBuf &dstBuf, const CPelUnitBuf &recSrc, const Area &blkDst, const Area &blkSrc,
+                             const ComponentID compId, const int16_t *filterCoeff, const ClpRngs &clpRngs,
+                             CodingStructure &cs, int vbCTUHeight, int vbPos);
+#endif
+
   template<AlfFilterType filtType>
   static void filterBlk(AlfClassifier **classifier, const PelUnitBuf &recDst, const CPelUnitBuf &recSrc,
                         const Area &blkDst, const Area &blk, const ComponentID compId, const short *filterSet,
@@ -100,6 +107,17 @@ public:
                                     const Area &blkDst, const Area &blk, const int shift, const int vbCTUHeight,
                                     int vbPos);
 
+#if JVET_Q0795_CCALF
+  void (*m_filterCcAlf)(const PelBuf &dstBuf, const CPelUnitBuf &recSrc, const Area &blkDst, const Area &blkSrc,
+                        const ComponentID compId, const int16_t *filterCoeff, const ClpRngs &clpRngs,
+                        CodingStructure &cs, int vbCTUHeight, int vbPos);
+  void applyCcAlfFilter(CodingStructure &cs, ComponentID compID, const PelBuf &dstBuf, const PelUnitBuf &recYuvExt,
+                        uint8_t *   filterControl,
+                        const short filterSet[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF],
+                        const int   selectedFilterIdx);
+  CcAlfFilterParam &getCcAlfFilterParam() { return m_ccAlfFilterParam; }
+  uint8_t* getCcAlfControlIdc(const ComponentID compID)   { return m_ccAlfFilterControl[compID-1]; }
+#endif
   void (*m_filter5x5Blk)(AlfClassifier **classifier, const PelUnitBuf &recDst, const CPelUnitBuf &recSrc,
                          const Area &blkDst, const Area &blk, const ComponentID compId, const short *filterSet,
                          const short *fClipSet, const ClpRng &clpRng, CodingStructure &cs, const int vbCTUHeight,
@@ -117,6 +135,11 @@ public:
 
 protected:
   bool isCrossedByVirtualBoundaries( const CodingStructure& cs, const int xPos, const int yPos, const int width, const int height, bool& clipTop, bool& clipBottom, bool& clipLeft, bool& clipRight, int& numHorVirBndry, int& numVerVirBndry, int horVirBndryPos[], int verVirBndryPos[], int& rasterSliceAlfPad );
+#if JVET_Q0795_CCALF
+  static constexpr int   m_scaleBits = 7; // 8-bits
+  CcAlfFilterParam       m_ccAlfFilterParam;
+  uint8_t*               m_ccAlfFilterControl[2];
+#endif
   static const int             m_classToFilterMapping[NUM_FIXED_FILTER_SETS][MAX_NUM_ALF_CLASSES];
   static const int             m_fixedFilterSetCoeff[ALF_FIXED_FILTER_NUM][MAX_NUM_ALF_LUMA_COEFF];
   short                        m_fixedFilterSetCoeffDec[NUM_FIXED_FILTER_SETS][MAX_NUM_ALF_CLASSES * MAX_NUM_ALF_LUMA_COEFF];
@@ -127,6 +150,9 @@ protected:
   short                        m_chromaCoeffFinal[MAX_NUM_ALF_ALTERNATIVES_CHROMA][MAX_NUM_ALF_CHROMA_COEFF];
   AlfParam*                    m_alfParamChroma;
   Pel                          m_alfClippingValues[MAX_NUM_CHANNEL_TYPE][MaxAlfNumClippingValues];
+#if JVET_Q0795_CCALF
+  std::vector<AlfFilterShape>  m_filterShapesCcAlf[2];
+#endif
   std::vector<AlfFilterShape>  m_filterShapes[MAX_NUM_CHANNEL_TYPE];
   AlfClassifier**              m_classifier;
   short                        m_coeffFinal[MAX_NUM_ALF_CLASSES * MAX_NUM_ALF_LUMA_COEFF];
diff --git a/source/Lib/CommonLib/AlfParameters.h b/source/Lib/CommonLib/AlfParameters.h
index abaef3a49..45fa9b275 100644
--- a/source/Lib/CommonLib/AlfParameters.h
+++ b/source/Lib/CommonLib/AlfParameters.h
@@ -48,9 +48,17 @@ enum AlfFilterType
 {
   ALF_FILTER_5,
   ALF_FILTER_7,
+#if JVET_Q0795_CCALF
+  CC_ALF,
+#endif
   ALF_NUM_OF_FILTER_TYPES
 };
 
+
+#if JVET_Q0795_CCALF
+static const int size_CC_ALF = -1;
+#endif
+
 struct AlfFilterShape
 {
   AlfFilterShape( int size )
@@ -97,6 +105,16 @@ struct AlfFilterShape
 
       filterType = ALF_FILTER_7;
     }
+#if JVET_Q0795_CCALF
+    else if (size == size_CC_ALF)
+    {
+      size = 4;
+      filterLength = 8;
+      numCoeff = 8;
+      filterSize = 8;
+      filterType   = CC_ALF;
+    }
+#endif
     else
     {
       filterType = ALF_NUM_OF_FILTER_TYPES;
@@ -231,6 +249,43 @@ struct AlfParam
   }
 };
 
+#if JVET_Q0795_CCALF
+struct CcAlfFilterParam
+{
+  bool    ccAlfFilterEnabled[2];
+  bool    ccAlfFilterIdxEnabled[2][MAX_NUM_CC_ALF_FILTERS];
+  uint8_t ccAlfFilterCount[2];
+  short   ccAlfCoeff[2][MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF];
+  int     newCcAlfFilter[2];
+  int     numberValidComponents;
+  CcAlfFilterParam()
+  {
+    reset();
+  }
+  void reset()
+  {
+    std::memset( ccAlfFilterEnabled, false, sizeof( ccAlfFilterEnabled ) );
+    std::memset( ccAlfFilterIdxEnabled, false, sizeof( ccAlfFilterIdxEnabled ) );
+    std::memset( ccAlfCoeff, 0, sizeof( ccAlfCoeff ) );
+    ccAlfFilterCount[0] = ccAlfFilterCount[1] = MAX_NUM_CC_ALF_FILTERS;
+    numberValidComponents = 3;
+    newCcAlfFilter[0] = newCcAlfFilter[1] = 0;
+  }
+  const CcAlfFilterParam& operator = ( const CcAlfFilterParam& src )
+  {
+    std::memcpy( ccAlfFilterEnabled, src.ccAlfFilterEnabled, sizeof( ccAlfFilterEnabled ) );
+    std::memcpy( ccAlfFilterIdxEnabled, src.ccAlfFilterIdxEnabled, sizeof( ccAlfFilterIdxEnabled ) );
+    std::memcpy( ccAlfCoeff, src.ccAlfCoeff, sizeof( ccAlfCoeff ) );
+    ccAlfFilterCount[0] = src.ccAlfFilterCount[0];
+    ccAlfFilterCount[1] = src.ccAlfFilterCount[1];
+    numberValidComponents = src.numberValidComponents;
+    newCcAlfFilter[0] = src.newCcAlfFilter[0];
+    newCcAlfFilter[1] = src.newCcAlfFilter[1];
+
+    return *this;
+  }
+};
+#endif
 //! \}
 
 #endif  // end of #ifndef  __ALFPARAMETERS__
diff --git a/source/Lib/CommonLib/CodingStatistics.h b/source/Lib/CommonLib/CodingStatistics.h
index 375ed6ec2..59b610717 100644
--- a/source/Lib/CommonLib/CodingStatistics.h
+++ b/source/Lib/CommonLib/CodingStatistics.h
@@ -117,6 +117,9 @@ enum CodingStatisticsType
   STATS__CABAC_BITS__MULTI_REF_LINE,
   STATS__CABAC_BITS__SYMMVD_FLAG,
   STATS__CABAC_BITS__BDPCM_MODE,
+#if JVET_Q0795_CCALF
+  STATS__CABAC_BITS__CROSS_COMPONENT_ALF_BLOCK_LEVEL_IDC,
+#endif
   STATS__TOOL_TOTAL_FRAME,// This is a special case and is not included in the report.
   STATS__TOOL_AFF,
   STATS__TOOL_EMT,
@@ -209,6 +212,9 @@ static inline const char* getName(CodingStatisticsType name)
     "CABAC_BITS__MULTI_REF_LINE",
     "CABAC_BITS__SYMMVD_FLAG",
     "CABAC_BITS__BDPCM_MODE",
+#if JVET_Q0795_CCALF
+    "CABAC_BITS__CROSS_COMPONENT_ALF_BLOCK_LEVEL_IDC",
+#endif
     "TOOL_FRAME",
     "TOOL_AFFINE",
     "TOOL_EMT",
diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index f24085c10..03f20a9b0 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -187,6 +187,12 @@ static const int MAX_NUM_ALF_CHROMA_COEFF    =                      7;
 static const int MAX_ALF_FILTER_LENGTH       =                      7;
 static const int MAX_NUM_ALF_COEFF           =                     MAX_ALF_FILTER_LENGTH * MAX_ALF_FILTER_LENGTH / 2 + 1;
 static const int MAX_ALF_PADDING_SIZE        =                      4;
+#if JVET_Q0795_CCALF
+#define MAX_NUM_CC_ALF_FILTERS                                      4
+static constexpr int MAX_NUM_CC_ALF_CHROMA_COEFF    =               8;
+static constexpr int CCALF_DYNAMIC_RANGE            =               6;
+static constexpr int CCALF_BITS_PER_COEFF_LEVEL     =               3;
+#endif
 
 static const int ALF_FIXED_FILTER_NUM        =                     64;
 static const int ALF_CTB_MAX_NUM_APS         =                      8;
@@ -700,6 +706,11 @@ static inline int ceilLog2(uint32_t x)
 #define PARL_PARAM0(DEF)
 #endif
 
+#if JVET_Q0795_CCALF
+static const uint32_t CCALF_CANDS_COEFF_NR = 8;
+static const int CCALF_SMALL_TAB[CCALF_CANDS_COEFF_NR] = { 0, 1, 2, 4, 8, 16, 32, 64 };
+#endif
+
 //! \}
 
 #endif // end of #ifndef  __COMMONDEF__
diff --git a/source/Lib/CommonLib/Contexts.cpp b/source/Lib/CommonLib/Contexts.cpp
index 749b86739..840e71802 100644
--- a/source/Lib/CommonLib/Contexts.cpp
+++ b/source/Lib/CommonLib/Contexts.cpp
@@ -825,6 +825,16 @@ const CtxSet ContextSetCfg::AlfUseTemporalFilt = ContextSetCfg::addCtxSet
   {   0, },
 });
 
+#if JVET_Q0795_CCALF
+const CtxSet ContextSetCfg::CcAlfFilterControlFlag = ContextSetCfg::addCtxSet
+({
+  {  35,  35,  35,  35,  35,  35 },
+  {  35,  35,  35,  35,  35,  35 },
+  {  35,  35,  35,  35,  35,  35 },
+  { DWS, DWS, DWS, DWS, DWS, DWS },
+});
+#endif
+
 const CtxSet ContextSetCfg::CiipFlag = ContextSetCfg::addCtxSet
 ({
   {  50, },
diff --git a/source/Lib/CommonLib/Contexts.h b/source/Lib/CommonLib/Contexts.h
index e831206ce..d17ce051c 100644
--- a/source/Lib/CommonLib/Contexts.h
+++ b/source/Lib/CommonLib/Contexts.h
@@ -263,6 +263,9 @@ public:
   static const CtxSet   ctbAlfFlag;
   static const CtxSet   ctbAlfAlternative;
   static const CtxSet   AlfUseTemporalFilt;
+#if JVET_Q0795_CCALF
+  static const CtxSet   CcAlfFilterControlFlag;
+#endif
   static const CtxSet   CiipFlag;
   static const CtxSet   SmvdFlag;
   static const CtxSet   IBCFlag;
diff --git a/source/Lib/CommonLib/Slice.cpp b/source/Lib/CommonLib/Slice.cpp
index e923fdf52..a612ec221 100644
--- a/source/Lib/CommonLib/Slice.cpp
+++ b/source/Lib/CommonLib/Slice.cpp
@@ -128,6 +128,12 @@ Slice::Slice()
   }
 
   memset(m_alfApss, 0, sizeof(m_alfApss));
+#if JVET_Q0795_CCALF
+  m_ccAlfFilterParam.reset();
+  resetTileGroupAlfEnabledFlag();
+  resetTileGroupCcAlCbfEnabledFlag();
+  resetTileGroupCcAlCrfEnabledFlag();
+#endif
 
   m_sliceMap.initSliceMap();
 }
@@ -171,6 +177,13 @@ void Slice::initSlice()
   m_isDRAP               = false;
   m_latestDRAPPOC        = MAX_INT;
   resetTileGroupAlfEnabledFlag();
+#if JVET_Q0795_CCALF
+  m_ccAlfFilterParam.reset();
+  m_tileGroupCcAlfCbEnabledFlag = 0;
+  m_tileGroupCcAlfCrEnabledFlag = 0;
+  m_tileGroupCcAlfCbApsId = -1;
+  m_tileGroupCcAlfCrApsId = -1;
+#endif
 }
 
 void Slice::inheritFromPicHeader( PicHeader *picHeader, const PPS *pps, const SPS *sps )
@@ -212,7 +225,15 @@ void Slice::inheritFromPicHeader( PicHeader *picHeader, const PPS *pps, const SP
   setTileGroupAlfEnabledFlag(COMPONENT_Cr, picHeader->getAlfEnabledFlag(COMPONENT_Cr));
   setTileGroupNumAps(picHeader->getNumAlfAps());
   setAlfAPSs(picHeader->getAlfAPSs());
-  setTileGroupApsIdChroma(picHeader->getAlfApsIdChroma());   
+  setTileGroupApsIdChroma(picHeader->getAlfApsIdChroma());
+#if JVET_Q0795_CCALF
+  setTileGroupCcAlfCbEnabledFlag(picHeader->getCcAlfEnabledFlag(COMPONENT_Cb));
+  setTileGroupCcAlfCrEnabledFlag(picHeader->getCcAlfEnabledFlag(COMPONENT_Cr));
+  setTileGroupCcAlfCbApsId(picHeader->getCcAlfCbApsId());
+  setTileGroupCcAlfCrApsId(picHeader->getCcAlfCrApsId());
+  m_ccAlfFilterParam.ccAlfFilterEnabled[COMPONENT_Cb - 1] = picHeader->getCcAlfEnabledFlag(COMPONENT_Cb);
+  m_ccAlfFilterParam.ccAlfFilterEnabled[COMPONENT_Cr - 1] = picHeader->getCcAlfEnabledFlag(COMPONENT_Cr);
+#endif
 }
 
 void  Slice::setNumEntryPoints( const PPS *pps ) 
@@ -806,6 +827,15 @@ void Slice::copySliceInfo(Slice *pSrc, bool cpyAlmostAll)
       m_scalingRatio[i][j]          = pSrc->m_scalingRatio[i][j];
     }
   }
+#if JVET_Q0795_CCALF
+  m_ccAlfFilterParam                        = pSrc->m_ccAlfFilterParam;
+  m_ccAlfFilterControl[0]                   = pSrc->m_ccAlfFilterControl[0];
+  m_ccAlfFilterControl[1]                   = pSrc->m_ccAlfFilterControl[1];
+  m_tileGroupCcAlfCbEnabledFlag             = pSrc->m_tileGroupCcAlfCbEnabledFlag;
+  m_tileGroupCcAlfCrEnabledFlag             = pSrc->m_tileGroupCcAlfCrEnabledFlag;
+  m_tileGroupCcAlfCbApsId                   = pSrc->m_tileGroupCcAlfCbApsId;
+  m_tileGroupCcAlfCrApsId                   = pSrc->m_tileGroupCcAlfCrApsId;
+#endif
 }
 
 
diff --git a/source/Lib/CommonLib/Slice.h b/source/Lib/CommonLib/Slice.h
index b95485113..584b3f952 100644
--- a/source/Lib/CommonLib/Slice.h
+++ b/source/Lib/CommonLib/Slice.h
@@ -242,6 +242,9 @@ class ConstraintInfo
   bool              m_noPartitionConstraintsOverrideConstraintFlag;
   bool              m_noSaoConstraintFlag;
   bool              m_noAlfConstraintFlag;
+#if JVET_Q0795_CCALF
+  bool              m_noCCAlfConstraintFlag;
+#endif
   bool              m_noRefWraparoundConstraintFlag;
   bool              m_noTemporalMvpConstraintFlag;
   bool              m_noSbtmvpConstraintFlag;
@@ -286,6 +289,9 @@ public:
     , m_noPartitionConstraintsOverrideConstraintFlag(false)
     , m_noSaoConstraintFlag      (false)
     , m_noAlfConstraintFlag      (false)
+#if JVET_Q0795_CCALF
+    , m_noCCAlfConstraintFlag      (false)
+#endif
     , m_noRefWraparoundConstraintFlag(false)
     , m_noTemporalMvpConstraintFlag(false)
     , m_noSbtmvpConstraintFlag   (false)
@@ -353,6 +359,10 @@ public:
   void          setNoSaoConstraintFlag(bool bVal) { m_noSaoConstraintFlag = bVal; }
   bool          getNoAlfConstraintFlag() const { return m_noAlfConstraintFlag; }
   void          setNoAlfConstraintFlag(bool bVal) { m_noAlfConstraintFlag = bVal; }
+#if JVET_Q0795_CCALF
+  bool          getNoCCAlfConstraintFlag() const { return m_noCCAlfConstraintFlag; }
+  void          setNoCCAlfConstraintFlag(bool val) { m_noCCAlfConstraintFlag = val; }
+#endif
   bool          getNoJointCbCrConstraintFlag() const { return m_noJointCbCrConstraintFlag; }
   void          setNoJointCbCrConstraintFlag(bool bVal) { m_noJointCbCrConstraintFlag = bVal; }
   bool          getNoRefWraparoundConstraintFlag() const { return m_noRefWraparoundConstraintFlag; }
@@ -1128,7 +1138,9 @@ private:
   ProfileTierLevel  m_profileTierLevel;
 
   bool              m_alfEnabledFlag;
-
+#if JVET_Q0795_CCALF
+  bool              m_ccalfEnabledFlag;
+#endif
   bool              m_wrapAroundEnabledFlag;
   unsigned          m_wrapAroundOffset;
   unsigned          m_IBCFlag;
@@ -1323,6 +1335,10 @@ public:
 
   bool                    getALFEnabledFlag() const                                                       { return m_alfEnabledFlag; }
   void                    setALFEnabledFlag( bool b )                                                     { m_alfEnabledFlag = b; }
+#if JVET_Q0795_CCALF
+bool                    getCCALFEnabledFlag() const                                                       { return m_ccalfEnabledFlag; }
+void                    setCCALFEnabledFlag( bool b )                                                     { m_ccalfEnabledFlag = b; }
+#endif
   void                    setJointCbCrEnabledFlag(bool bVal)                                              { m_JointCbCrEnabledFlag = bVal; }
   bool                    getJointCbCrEnabledFlag() const                                                 { return m_JointCbCrEnabledFlag; }
 
@@ -1835,6 +1851,9 @@ private:
   AlfParam               m_alfAPSParam;
   SliceReshapeInfo       m_reshapeAPSInfo;
   ScalingList            m_scalingListApsInfo;
+#if JVET_Q0795_CCALF
+  CcAlfFilterParam       m_ccAlfAPSParam;
+#endif
 
 public:
   APS();
@@ -1857,6 +1876,10 @@ public:
   SliceReshapeInfo&      getReshaperAPSInfo()                                             { return m_reshapeAPSInfo;                      }
   void                   setScalingList( ScalingList& scalingListAPSInfo )                { m_scalingListApsInfo = scalingListAPSInfo;    }
   ScalingList&           getScalingList()                                                 { return m_scalingListApsInfo;                  }
+#if JVET_Q0795_CCALF
+  void                   setCcAlfAPSParam(CcAlfFilterParam& ccAlfAPSParam)                { m_ccAlfAPSParam = ccAlfAPSParam;              }
+  CcAlfFilterParam&      getCcAlfAPSParam()  { return m_ccAlfAPSParam; }
+#endif
 };
 struct WPScalingParam
 {
@@ -1933,6 +1956,11 @@ private:
   int                         m_numAlfAps;                                              //!< number of alf aps active for the picture
   std::vector<int>            m_alfApsId;                                               //!< list of alf aps for the picture
   int                         m_alfChromaApsId;                                         //!< chroma alf aps ID
+#if JVET_Q0795_CCALF
+  bool m_ccalfEnabledFlag[MAX_NUM_COMPONENT];
+  int  m_ccalfCbApsId;
+  int  m_ccalfCrApsId;
+#endif
   bool                        m_depQuantEnabledFlag;                                    //!< dependent quantization enabled flag
   bool                        m_signDataHidingEnabledFlag;                              //!< sign data hiding enabled flag
   bool                        m_deblockingFilterOverridePresentFlag;                    //!< deblocking filter override controls present in picture header
@@ -2053,7 +2081,16 @@ public:
   void                        setNumAlfAps(int i)                                       { m_numAlfAps = i;                                                                             }
   int                         getNumAlfAps() const                                      { return m_numAlfAps;                                                                          }
   void                        setAlfApsIdChroma(int i)                                  { m_alfChromaApsId = i;                                                                        }
-  int                         getAlfApsIdChroma() const                                 { return m_alfChromaApsId;                                                                     }  
+  int                         getAlfApsIdChroma() const                                 { return m_alfChromaApsId;                                                                     }
+#if JVET_Q0795_CCALF
+  void setCcAlfEnabledFlag(ComponentID compId, bool b) { m_ccalfEnabledFlag[compId] = b; }
+  bool getCcAlfEnabledFlag(ComponentID compId) const { return m_ccalfEnabledFlag[compId]; }
+
+  void setCcAlfCbApsId(int i) { m_ccalfCbApsId = i; }
+  int  getCcAlfCbApsId() const { return m_ccalfCbApsId; }
+  void setCcAlfCrApsId(int i) { m_ccalfCrApsId = i; }
+  int  getCcAlfCrApsId() const { return m_ccalfCrApsId; }
+#endif
   void                        setDepQuantEnabledFlag( bool b )                          { m_depQuantEnabledFlag = b;                                                                   }
   bool                        getDepQuantEnabledFlag() const                            { return m_depQuantEnabledFlag;                                                                }  
   void                        setSignDataHidingEnabledFlag( bool b )                    { m_signDataHidingEnabledFlag = b;                                                             }
@@ -2225,6 +2262,12 @@ private:
   int                        m_tileGroupNumAps;
   std::vector<int>           m_tileGroupLumaApsId;
   int                        m_tileGroupChromaApsId;
+#if JVET_Q0795_CCALF
+  bool                       m_tileGroupCcAlfCbEnabledFlag;
+  bool                       m_tileGroupCcAlfCrEnabledFlag;
+  int                        m_tileGroupCcAlfCbApsId;
+  int                        m_tileGroupCcAlfCrApsId;
+#endif
   bool                       m_disableSATDForRd;
 public:
                               Slice();
@@ -2468,6 +2511,20 @@ public:
       m_tileGroupLumaApsId[i] = ApsIDs[i];
     }
   }
+#if JVET_Q0795_CCALF
+  void resetTileGroupCcAlCbfEnabledFlag() { m_tileGroupCcAlfCbEnabledFlag = 0; }
+  void resetTileGroupCcAlCrfEnabledFlag() { m_tileGroupCcAlfCrEnabledFlag = 0; }
+
+  void setTileGroupCcAlfCbEnabledFlag(bool b) { m_tileGroupCcAlfCbEnabledFlag = b; }
+  void setTileGroupCcAlfCrEnabledFlag(bool b) { m_tileGroupCcAlfCrEnabledFlag = b; }
+  void setTileGroupCcAlfCbApsId(int i) { m_tileGroupCcAlfCbApsId = i; }
+  void setTileGroupCcAlfCrApsId(int i) { m_tileGroupCcAlfCrApsId = i; }
+
+  bool getTileGroupCcAlfCbEnabledFlag() { return m_tileGroupCcAlfCbEnabledFlag; }
+  bool getTileGroupCcAlfCrEnabledFlag() { return m_tileGroupCcAlfCrEnabledFlag; }
+  int  getTileGroupCcAlfCbApsId() { return m_tileGroupCcAlfCbApsId; }
+  int  getTileGroupCcAlfCrApsId() { return m_tileGroupCcAlfCrApsId; }
+#endif
   void                        setDisableSATDForRD(bool b) { m_disableSATDForRd = b; }
   bool                        getDisableSATDForRD() { return m_disableSATDForRd; }
   void                        scaleRefPicList( Picture *scaledRefPic[ ], PicHeader *picHeader, APS** apss, APS* lmcsAps, APS* scalingListAps, const bool isDecoder );
@@ -2477,6 +2534,11 @@ public:
   void                        setNumEntryPoints( const PPS *pps );
   uint32_t                    getNumEntryPoints( ) const { return m_numEntryPoints;  }
 
+#if JVET_Q0795_CCALF
+  CcAlfFilterParam            m_ccAlfFilterParam;
+  uint8_t*                    m_ccAlfFilterControl[2];
+#endif
+
 protected:
   Picture*              xGetRefPic( PicList& rcListPic, int poc, const int layerId );
   Picture*              xGetLongTermRefPic( PicList& rcListPic, int poc, bool pocHasMsb, const int layerId );
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index fbcf3e131..8f7d179c6 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -74,6 +74,7 @@
 #define JVET_Q0249_ALF_CHROMA_CLIPFLAG                    1 // JVET-Q0249: Cleanup of chroma clipping flags for ALF
 #define JVET_Q0150                                        1 // fix for ALF virtual horizontal CTU boundary processing
 #define JVET_Q0054                                        1 // fix for long luma deblocking decision
+#define JVET_Q0795_CCALF                                  1 // Cross-component ALF
 
 #define JVET_Q0483_CLIP_TMVP                              1 // JVET-Q0483: Clip TMVP when no scaling is applied
 
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index 2b6e06881..b8f20a7de 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -194,6 +194,25 @@ void CABACReader::coding_tree_unit( CodingStructure& cs, const UnitArea& area, i
       }
     }
   }
+#if JVET_Q0795_CCALF
+  if (cs.sps->getCCALFEnabledFlag())
+  {
+    for ( int compIdx = 1; compIdx < getNumberValidComponents( cs.pcv->chrFormat ); compIdx++ )
+    {
+      if (cs.slice->m_ccAlfFilterParam.ccAlfFilterEnabled[compIdx - 1])
+      {
+        const int filterCount   = cs.slice->m_ccAlfFilterParam.ccAlfFilterCount[compIdx - 1];
+
+        const int      ry = ctuRsAddr / cs.pcv->widthInCtus;
+        const int      rx = ctuRsAddr % cs.pcv->widthInCtus;
+        const Position lumaPos(rx * cs.pcv->maxCUWidth, ry * cs.pcv->maxCUHeight);
+
+        ccAlfFilterControlIdc(cs, ComponentID(compIdx), ctuRsAddr, cs.slice->m_ccAlfFilterControl[compIdx - 1], lumaPos,
+                              filterCount);
+      }
+    }
+  }
+#endif
 
 
   if ( CS::isDualITree(cs) && cs.pcv->chrFormat != CHROMA_400 && cs.pcv->maxCUWidth > 64 )
@@ -251,6 +270,45 @@ void CABACReader::readAlfCtuFilterIndex(CodingStructure& cs, unsigned ctuRsAddr)
   }
   alfCtbFilterSetIndex[ctuRsAddr] = filtIndex;
 }
+#if JVET_Q0795_CCALF
+void CABACReader::ccAlfFilterControlIdc(CodingStructure &cs, const ComponentID compID, const int curIdx,
+                                        uint8_t *filterControlIdc, Position lumaPos, int filterCount)
+{
+  RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET( STATS__CABAC_BITS__CROSS_COMPONENT_ALF_BLOCK_LEVEL_IDC );
+
+  Position       leftLumaPos    = lumaPos.offset(-(int)cs.pcv->maxCUWidth, 0);
+  Position       aboveLumaPos   = lumaPos.offset(0, -(int)cs.pcv->maxCUWidth);
+  const uint32_t curSliceIdx    = cs.slice->getIndependentSliceIdx();
+  const uint32_t curTileIdx     = cs.pps->getTileIdx( lumaPos );
+  bool           leftAvail      = cs.getCURestricted( leftLumaPos,  lumaPos, curSliceIdx, curTileIdx, CH_L ) ? true : false;
+  bool           aboveAvail     = cs.getCURestricted( aboveLumaPos, lumaPos, curSliceIdx, curTileIdx, CH_L ) ? true : false;
+  int            ctxt           = 0;
+
+  if (leftAvail)
+  {
+    ctxt += ( filterControlIdc[curIdx - 1] ) ? 1 : 0;
+  }
+  if (aboveAvail)
+  {
+    ctxt += ( filterControlIdc[curIdx - cs.pcv->widthInCtus] ) ? 1 : 0;
+  }
+  ctxt += ( compID == COMPONENT_Cr ) ? 3 : 0;
+
+  int idcVal  = m_BinDecoder.decodeBin( Ctx::CcAlfFilterControlFlag( ctxt ) );
+  if ( idcVal )
+  {
+    while ( ( idcVal != filterCount ) && m_BinDecoder.decodeBinEP() )
+    {
+      idcVal++;
+    }
+  }
+  filterControlIdc[curIdx] = idcVal;
+
+  DTRACE(g_trace_ctx, D_SYNTAX, "ccAlfFilterControlIdc() compID=%d pos=(%d,%d) ctxt=%d, filterCount=%d, idcVal=%d\n",
+         compID, lumaPos.x, lumaPos.y, ctxt, filterCount, idcVal);
+}
+#endif
+
 //================================================================================
 //  clause 7.3.8.3
 //--------------------------------------------------------------------------------
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index 132c50232..e1b33238b 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -70,6 +70,11 @@ public:
 
   void        readAlfCtuFilterIndex(CodingStructure&              cs, unsigned        ctuRsAddr);
 
+#if JVET_Q0795_CCALF
+  void ccAlfFilterControlIdc(CodingStructure &cs, const ComponentID compID, const int curIdx, uint8_t *filterControlIdc,
+                             Position lumaPos, int filterCount);
+#endif
+
   // coding (quad)tree (clause 7.3.8.4)
   void        coding_tree               ( CodingStructure&              cs,     Partitioner&    pm,       CUCtx& cuCtx, Partitioner* pPartitionerChroma = nullptr, CUCtx* pCuCtxChroma = nullptr);
   PartSplit   split_cu_mode             ( CodingStructure&              cs,     Partitioner&    pm );
diff --git a/source/Lib/DecoderLib/DecLib.cpp b/source/Lib/DecoderLib/DecLib.cpp
index 6b62da634..322ddce36 100644
--- a/source/Lib/DecoderLib/DecLib.cpp
+++ b/source/Lib/DecoderLib/DecLib.cpp
@@ -222,6 +222,12 @@ bool tryDecodePicture( Picture* pcEncPic, const int expectedPoc, const std::stri
                     pcEncPic->slices[i]->setTileGroupAlfEnabledFlag(COMPONENT_Y,  pic->slices[i]->getTileGroupAlfEnabledFlag(COMPONENT_Y));
                     pcEncPic->slices[i]->setTileGroupAlfEnabledFlag(COMPONENT_Cb, pic->slices[i]->getTileGroupAlfEnabledFlag(COMPONENT_Cb));
                     pcEncPic->slices[i]->setTileGroupAlfEnabledFlag(COMPONENT_Cr, pic->slices[i]->getTileGroupAlfEnabledFlag(COMPONENT_Cr));
+#if JVET_Q0795_CCALF
+                    pcEncPic->slices[i]->setTileGroupCcAlfCbApsId(pic->slices[i]->getTileGroupCcAlfCbApsId());
+                    pcEncPic->slices[i]->setTileGroupCcAlfCbEnabledFlag(pic->slices[i]->getTileGroupCcAlfCbEnabledFlag());
+                    pcEncPic->slices[i]->setTileGroupCcAlfCrApsId(pic->slices[i]->getTileGroupCcAlfCrApsId());
+                    pcEncPic->slices[i]->setTileGroupCcAlfCrEnabledFlag(pic->slices[i]->getTileGroupCcAlfCrEnabledFlag());
+#endif
                   }
                 }
 
@@ -579,11 +585,13 @@ void DecLib::executeLoopFilters()
 
   if( cs.sps->getALFEnabledFlag() )
   {
-      // ALF decodes the differentially coded coefficients and stores them in the parameters structure.
-      // Code could be restructured to do directly after parsing. So far we just pass a fresh non-const
-      // copy in case the APS gets used more than once.
-      m_cALF.ALFProcess(cs);
-
+#if JVET_Q0795_CCALF
+    m_cALF.getCcAlfFilterParam() = cs.slice->m_ccAlfFilterParam;
+#endif
+    // ALF decodes the differentially coded coefficients and stores them in the parameters structure.
+    // Code could be restructured to do directly after parsing. So far we just pass a fresh non-const
+    // copy in case the APS gets used more than once.
+    m_cALF.ALFProcess(cs);
   }
 
 #if SUBPIC_DECCHECK 
@@ -985,6 +993,60 @@ void DecLib::xActivateParameterSets( const int layerId )
     APS* scalinglistAPS = nullptr;
     activateAPS(&m_picHeader, m_apcSlicePilot, m_parameterSetManager, apss, lmcsAPS, scalinglistAPS);
 
+#if JVET_Q0795_CCALF
+    CcAlfFilterParam &filterParam = m_apcSlicePilot->m_ccAlfFilterParam;
+
+    // CC ALF Cb APS
+    int  apsCcAlfCbId = m_apcSlicePilot->getTileGroupCcAlfCbApsId();
+    APS *apsCcAlfCb   = m_parameterSetManager.getAPS(apsCcAlfCbId, ALF_APS);
+    if (apsCcAlfCb)
+    {
+      m_parameterSetManager.clearAPSChangedFlag(apsCcAlfCbId, ALF_APS);
+      apss[apsCcAlfCbId] = apsCcAlfCb;
+      if (false == m_parameterSetManager.activateAPS(apsCcAlfCbId, ALF_APS))
+      {
+        THROW("APS activation failed!");
+      }
+      // cleanup before copying
+      for ( int filterIdx = 0; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+      {
+        memset( filterParam.ccAlfCoeff[COMPONENT_Cb - 1][filterIdx], 0, sizeof(filterParam.ccAlfCoeff[COMPONENT_Cb - 1][filterIdx]) );
+      }
+      memset( filterParam.ccAlfFilterIdxEnabled[COMPONENT_Cb - 1], false, sizeof(filterParam.ccAlfFilterIdxEnabled[COMPONENT_Cb - 1]) );
+      filterParam.ccAlfFilterCount[COMPONENT_Cb - 1] = apsCcAlfCb->getCcAlfAPSParam().ccAlfFilterCount[COMPONENT_Cb - 1];
+      for (int filterIdx=0; filterIdx < filterParam.ccAlfFilterCount[COMPONENT_Cb - 1]; filterIdx++ )
+      {
+        filterParam.ccAlfFilterIdxEnabled[COMPONENT_Cb - 1][filterIdx] = apsCcAlfCb->getCcAlfAPSParam().ccAlfFilterIdxEnabled[COMPONENT_Cb - 1][filterIdx];
+        memcpy(filterParam.ccAlfCoeff[COMPONENT_Cb - 1][filterIdx], apsCcAlfCb->getCcAlfAPSParam().ccAlfCoeff[COMPONENT_Cb - 1][filterIdx], sizeof(apsCcAlfCb->getCcAlfAPSParam().ccAlfCoeff[COMPONENT_Cb - 1][filterIdx]));
+      }
+    }
+
+    // CC ALF Cr APS
+    int  apsCcAlfCrId = m_apcSlicePilot->getTileGroupCcAlfCrApsId();
+    APS *apsCcAlfCr   = m_parameterSetManager.getAPS(apsCcAlfCrId, ALF_APS);
+    if (apsCcAlfCr)
+    {
+      m_parameterSetManager.clearAPSChangedFlag(apsCcAlfCrId, ALF_APS);
+      apss[apsCcAlfCrId] = apsCcAlfCr;
+      if (false == m_parameterSetManager.activateAPS(apsCcAlfCrId, ALF_APS))
+      {
+        THROW("APS activation failed!");
+      }
+      // cleanup before copying
+      for ( int filterIdx = 0; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+      {
+        memset( filterParam.ccAlfCoeff[COMPONENT_Cr - 1][filterIdx], 0, sizeof(filterParam.ccAlfCoeff[COMPONENT_Cr - 1][filterIdx]) );
+      }
+      memset( filterParam.ccAlfFilterIdxEnabled[COMPONENT_Cr - 1], false, sizeof(filterParam.ccAlfFilterIdxEnabled[COMPONENT_Cr - 1]) );
+      filterParam.ccAlfFilterCount[COMPONENT_Cr - 1] = apsCcAlfCr->getCcAlfAPSParam().ccAlfFilterCount[COMPONENT_Cr - 1];
+      for (int filterIdx=0; filterIdx < filterParam.ccAlfFilterCount[COMPONENT_Cr - 1]; filterIdx++ )
+      {
+        filterParam.ccAlfFilterIdxEnabled[COMPONENT_Cr - 1][filterIdx] = apsCcAlfCr->getCcAlfAPSParam().ccAlfFilterIdxEnabled[COMPONENT_Cr - 1][filterIdx];
+        memcpy(filterParam.ccAlfCoeff[COMPONENT_Cr - 1][filterIdx], apsCcAlfCr->getCcAlfAPSParam().ccAlfCoeff[COMPONENT_Cr - 1][filterIdx], sizeof(apsCcAlfCr->getCcAlfAPSParam().ccAlfCoeff[COMPONENT_Cr - 1][filterIdx]));
+      }
+    }
+#endif
+
     xParsePrefixSEImessages();
 
 #if RExt__HIGH_BIT_DEPTH_SUPPORT==0
@@ -1076,6 +1138,10 @@ void DecLib::xActivateParameterSets( const int layerId )
     {
       m_cALF.create( pps->getPicWidthInLumaSamples(), pps->getPicHeightInLumaSamples(), sps->getChromaFormatIdc(), sps->getMaxCUWidth(), sps->getMaxCUHeight(), sps->getMaxCodingDepth(), sps->getBitDepths().recon );
     }
+#if JVET_Q0795_CCALF
+    pSlice->m_ccAlfFilterControl[0] = m_cALF.getCcAlfControlIdc(COMPONENT_Cb);
+    pSlice->m_ccAlfFilterControl[1] = m_cALF.getCcAlfControlIdc(COMPONENT_Cr);
+#endif
   }
   else
   {
@@ -1249,6 +1315,9 @@ bool DecLib::xDecodeSlice(InputNALUnit &nalu, int &iSkipFrame, int iPOCLastDispl
     CHECK(nalu.m_temporalId != 0, "Current GDR picture has TemporalId not equal to 0");
 
   m_HLSReader.setBitstream( &nalu.getBitstream() );
+#if JVET_Q0795_CCALF
+  m_apcSlicePilot->m_ccAlfFilterParam = m_cALF.getCcAlfFilterParam();
+#endif
   m_HLSReader.parseSliceHeader( m_apcSlicePilot, &m_picHeader, &m_parameterSetManager, m_prevTid0POC );
 
   // update independent slice index
diff --git a/source/Lib/DecoderLib/VLCReader.cpp b/source/Lib/DecoderLib/VLCReader.cpp
index fd516bb03..7766f6052 100644
--- a/source/Lib/DecoderLib/VLCReader.cpp
+++ b/source/Lib/DecoderLib/VLCReader.cpp
@@ -825,8 +825,17 @@ void HLSyntaxReader::parseAlfAps( APS* aps )
   READ_FLAG(code, "alf_chroma_new_filter");
   param.newFilterFlag[CHANNEL_TYPE_CHROMA] = code;
 
-  CHECK(param.newFilterFlag[CHANNEL_TYPE_LUMA] == 0 && param.newFilterFlag[CHANNEL_TYPE_CHROMA] == 0,
-    "bitstream conformance error, alf_luma_filter_signal_flag and alf_chroma_filter_signal_flag shall not equal to zero at the same time");
+#if JVET_Q0795_CCALF
+  CcAlfFilterParam ccAlfParam = aps->getCcAlfAPSParam();
+  READ_FLAG(code, "alf_cc_cb_filter_signal_flag");
+  ccAlfParam.newCcAlfFilter[COMPONENT_Cb - 1] = code;
+  READ_FLAG(code, "alf_cc_cr_filter_signal_flag");
+  ccAlfParam.newCcAlfFilter[COMPONENT_Cr - 1] = code;
+  CHECK(param.newFilterFlag[CHANNEL_TYPE_LUMA] == 0 && param.newFilterFlag[CHANNEL_TYPE_CHROMA] == 0
+          && ccAlfParam.newCcAlfFilter[COMPONENT_Cb - 1] == 0 && ccAlfParam.newCcAlfFilter[COMPONENT_Cr - 1] == 0,
+        "bitstream conformance error: one of alf_luma_filter_signal_flag, alf_chroma_filter_signal_flag, "
+        "alf_cross_component_cb_filter_signal_flag, and alf_cross_component_cr_filter_signal_flag shall be nonzero");
+#endif
 
   if (param.newFilterFlag[CHANNEL_TYPE_LUMA])
   {
@@ -876,6 +885,62 @@ void HLSyntaxReader::parseAlfAps( APS* aps )
       alfFilter( param, true, altIdx );
     }
   }
+
+#if JVET_Q0795_CCALF
+  for (int ccIdx = 0; ccIdx < 2; ccIdx++)
+  {
+    if (ccAlfParam.newCcAlfFilter[ccIdx])
+    {
+      if (MAX_NUM_CC_ALF_FILTERS > 1)
+      {
+        READ_UVLC(code, ccIdx == 0 ? "alf_cc_cb_filters_signalled_minus1" : "alf_cc_cr_filters_signalled_minus1");
+      }
+      else
+      {
+        code = 0;
+      }
+      ccAlfParam.ccAlfFilterCount[ccIdx] = code + 1;
+
+      for (int filterIdx = 0; filterIdx < ccAlfParam.ccAlfFilterCount[ccIdx]; filterIdx++)
+      {
+        ccAlfParam.ccAlfFilterIdxEnabled[ccIdx][filterIdx] = true;
+        AlfFilterShape alfShape(size_CC_ALF);
+
+        short *coeff = ccAlfParam.ccAlfCoeff[ccIdx][filterIdx];
+        // Filter coefficients
+        for (int i = 0; i < alfShape.numCoeff - 1; i++)
+        {
+          READ_CODE(CCALF_BITS_PER_COEFF_LEVEL, code,
+                    ccIdx == 0 ? "alf_cc_cb_mapped_coeff_abs" : "alf_cc_cr_mapped_coeff_abs");
+          if (code == 0)
+          {
+            coeff[i] = 0;
+          }
+          else
+          {
+            coeff[i] = 1 << (code - 1);
+            READ_FLAG(code, ccIdx == 0 ? "alf_cc_cb_coeff_sign" : "alf_cc_cr_coeff_sign");
+            coeff[i] *= 1 - 2 * code;
+          }
+        }
+
+        DTRACE(g_trace_ctx, D_SYNTAX, "%s coeff filterIdx %d: ", ccIdx == 0 ? "Cb" : "Cr", filterIdx);
+        for (int i = 0; i < alfShape.numCoeff; i++)
+        {
+          DTRACE(g_trace_ctx, D_SYNTAX, "%d ", coeff[i]);
+        }
+        DTRACE(g_trace_ctx, D_SYNTAX, "\n");
+      }
+
+      for (int filterIdx = ccAlfParam.ccAlfFilterCount[ccIdx]; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++)
+      {
+        ccAlfParam.ccAlfFilterIdxEnabled[ccIdx][filterIdx] = false;
+      }
+    }
+  }
+  aps->setCcAlfAPSParam(ccAlfParam);
+#endif
+
   aps->setAlfAPSParam(param);
 }
 
@@ -1334,6 +1399,16 @@ void HLSyntaxReader::parseSPS(SPS* pcSPS)
 
   READ_FLAG( uiCode, "sps_sao_enabled_flag" );                      pcSPS->setSAOEnabledFlag ( uiCode ? true : false );
   READ_FLAG( uiCode, "sps_alf_enabled_flag" );                      pcSPS->setALFEnabledFlag ( uiCode ? true : false );
+#if JVET_Q0795_CCALF
+  if (pcSPS->getALFEnabledFlag() && pcSPS->getChromaFormatIdc() != CHROMA_400)
+  {
+    READ_FLAG( uiCode, "sps_ccalf_enabled_flag" );                      pcSPS->setCCALFEnabledFlag ( uiCode ? true : false );
+  }
+  else
+  {
+    pcSPS->setCCALFEnabledFlag(false);
+  }
+#endif
 
   READ_FLAG(uiCode, "sps_transform_skip_enabled_flag"); pcSPS->setTransformSkipEnabledFlag(uiCode ? true : false);
   if (pcSPS->getTransformSkipEnabledFlag())
@@ -2211,6 +2286,10 @@ void HLSyntaxReader::parsePictureHeader( PicHeader* picHeader, ParameterSetManag
   }
   
   // alf enable flags and aps IDs
+#if JVET_Q0795_CCALF
+  picHeader->setCcAlfEnabledFlag(COMPONENT_Cb, false);
+  picHeader->setCcAlfEnabledFlag(COMPONENT_Cr, false);
+#endif
   if( sps->getALFEnabledFlag() )
   {
     READ_FLAG(uiCode, "pic_alf_enabled_present_flag");  
@@ -2250,6 +2329,30 @@ void HLSyntaxReader::parsePictureHeader( PicHeader* picHeader, ParameterSetManag
           READ_CODE(3, uiCode, "pic_alf_aps_id_chroma");
           picHeader->setAlfApsIdChroma(uiCode);
         }
+#if JVET_Q0795_CCALF
+        if (sps->getCCALFEnabledFlag())
+        {
+          READ_FLAG(uiCode, "ph_cc_alf_cb_enabled_flag");
+          picHeader->setCcAlfEnabledFlag(COMPONENT_Cb, uiCode != 0);
+          picHeader->setCcAlfCbApsId(-1);
+          if (picHeader->getCcAlfEnabledFlag(COMPONENT_Cb))
+          {
+            // parse APS ID
+            READ_CODE(3, uiCode, "ph_cc_alf_cb_aps_id");
+            picHeader->setCcAlfCbApsId(uiCode);
+          }
+          // Cr
+          READ_FLAG(uiCode, "ph_cc_alf_cr_enabled_flag");
+          picHeader->setCcAlfEnabledFlag(COMPONENT_Cr, uiCode != 0);
+          picHeader->setCcAlfCrApsId(-1);
+          if (picHeader->getCcAlfEnabledFlag(COMPONENT_Cr))
+          {
+            // parse APS ID
+            READ_CODE(3, uiCode, "ph_cc_alf_cr_aps_id");
+            picHeader->setCcAlfCrApsId(uiCode);
+          }
+        }
+#endif
       }
       else
       {
@@ -2951,6 +3054,39 @@ void HLSyntaxReader::parseSliceHeader (Slice* pcSlice, PicHeader* picHeader, Par
       }
       pcSlice->setTileGroupAlfEnabledFlag(COMPONENT_Cb, alfChromaIdc & 1);
       pcSlice->setTileGroupAlfEnabledFlag(COMPONENT_Cr, alfChromaIdc >> 1);
+
+#if JVET_Q0795_CCALF
+      CcAlfFilterParam &filterParam = pcSlice->m_ccAlfFilterParam;
+      if (sps->getCCALFEnabledFlag() && pcSlice->getTileGroupAlfEnabledFlag(COMPONENT_Y))
+      {
+        READ_FLAG(uiCode, "slice_cc_alf_cb_enabled_flag");
+        filterParam.ccAlfFilterEnabled[COMPONENT_Cb - 1] = (uiCode == 1) ? true : false;
+        pcSlice->setTileGroupCcAlfCbApsId(-1);
+        if (filterParam.ccAlfFilterEnabled[COMPONENT_Cb - 1])
+        {
+          // parse APS ID
+          READ_CODE(3, uiCode, "slice_cc_alf_cb_aps_id");
+          pcSlice->setTileGroupCcAlfCbApsId(uiCode);
+        }
+        // Cr
+        READ_FLAG(uiCode, "slice_cc_alf_cr_enabled_flag");
+        filterParam.ccAlfFilterEnabled[COMPONENT_Cr - 1] = (uiCode == 1) ? true : false;
+        pcSlice->setTileGroupCcAlfCrApsId(-1);
+        if (filterParam.ccAlfFilterEnabled[COMPONENT_Cr - 1])
+        {
+          // parse APS ID
+          READ_CODE(3, uiCode, "slice_cc_alf_cr_aps_id");
+          pcSlice->setTileGroupCcAlfCrApsId(uiCode);
+        }
+      }
+      else
+      {
+        filterParam.ccAlfFilterEnabled[COMPONENT_Cb - 1] = false;
+        filterParam.ccAlfFilterEnabled[COMPONENT_Cr - 1] = false;
+        pcSlice->setTileGroupCcAlfCbApsId(-1);
+        pcSlice->setTileGroupCcAlfCrApsId(-1);
+      }
+#endif
     }
 
 
@@ -3130,6 +3266,9 @@ void HLSyntaxReader::parseConstraintInfo(ConstraintInfo *cinfo)
   READ_FLAG(symbol, "no_partition_constraints_override_constraint_flag"); cinfo->setNoPartitionConstraintsOverrideConstraintFlag(symbol > 0 ? true : false);
   READ_FLAG(symbol,  "no_sao_constraint_flag");                    cinfo->setNoSaoConstraintFlag(symbol > 0 ? true : false);
   READ_FLAG(symbol,  "no_alf_constraint_flag");                    cinfo->setNoAlfConstraintFlag(symbol > 0 ? true : false);
+#if JVET_Q0795_CCALF
+  READ_FLAG(symbol,  "no_ccalf_constraint_flag");                  cinfo->setNoCCAlfConstraintFlag(symbol > 0 ? true : false);
+#endif
   READ_FLAG(symbol,  "no_joint_cbcr_constraint_flag");             cinfo->setNoJointCbCrConstraintFlag(symbol > 0 ? true : false);
 
   READ_FLAG(symbol,  "no_ref_wraparound_constraint_flag");         cinfo->setNoRefWraparoundConstraintFlag(symbol > 0 ? true : false);
diff --git a/source/Lib/DecoderLib/VLCReader.h b/source/Lib/DecoderLib/VLCReader.h
index 69e3f479c..67eaee7ed 100644
--- a/source/Lib/DecoderLib/VLCReader.h
+++ b/source/Lib/DecoderLib/VLCReader.h
@@ -178,6 +178,9 @@ public:
   void  decodeScalingList   ( ScalingList *scalingList, uint32_t scalingListId, bool isPredictor);
   void parseReshaper        ( SliceReshapeInfo& sliceReshaperInfo, const SPS* pcSPS, const bool isIntra );
   void alfFilter( AlfParam& alfParam, const bool isChroma, const int altIdx );
+#if JVET_Q0795_CCALF
+  void ccAlfFilter( Slice *pcSlice );
+#endif
 
 private:
   int  alfGolombDecode( const int k, const bool signed_val=true );
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index c814c521a..4044ba80e 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -187,6 +187,26 @@ void CABACWriter::coding_tree_unit( CodingStructure& cs, const UnitArea& area, i
     }
   }
 
+#if JVET_Q0795_CCALF
+  if ( !skipAlf )
+  {
+    for ( int compIdx = 1; compIdx < getNumberValidComponents( cs.pcv->chrFormat ); compIdx++ )
+    {
+      if (cs.slice->m_ccAlfFilterParam.ccAlfFilterEnabled[compIdx - 1])
+      {
+        const int filterCount   = cs.slice->m_ccAlfFilterParam.ccAlfFilterCount[compIdx - 1];
+
+        const int      ry = ctuRsAddr / cs.pcv->widthInCtus;
+        const int      rx = ctuRsAddr % cs.pcv->widthInCtus;
+        const Position lumaPos(rx * cs.pcv->maxCUWidth, ry * cs.pcv->maxCUHeight);
+
+        codeCcAlfFilterControlIdc(cs.slice->m_ccAlfFilterControl[compIdx - 1][ctuRsAddr], cs, ComponentID(compIdx),
+                                  ctuRsAddr, cs.slice->m_ccAlfFilterControl[compIdx - 1], lumaPos, filterCount);
+      }
+    }
+  }
+#endif
+
   if ( CS::isDualITree(cs) && cs.pcv->chrFormat != CHROMA_400 && cs.pcv->maxCUWidth > 64 )
   {
     CUCtx chromaCuCtx(qps[CH_C]);
@@ -3351,6 +3371,49 @@ void CABACWriter::codeAlfCtuEnableFlag( CodingStructure& cs, uint32_t ctuRsAddr,
   }
 }
 
+#if JVET_Q0795_CCALF
+void CABACWriter::codeCcAlfFilterControlIdc(uint8_t idcVal, CodingStructure &cs, const ComponentID compID,
+                                            const int curIdx, const uint8_t *filterControlIdc, Position lumaPos,
+                                            const int filterCount)
+{
+  CHECK(idcVal > filterCount, "Filter index is too large");
+
+  const uint32_t curSliceIdx    = cs.slice->getIndependentSliceIdx();
+  const uint32_t curTileIdx     = cs.pps->getTileIdx( lumaPos );
+  Position       leftLumaPos    = lumaPos.offset(-(int)cs.pcv->maxCUWidth, 0);
+  Position       aboveLumaPos   = lumaPos.offset(0, -(int)cs.pcv->maxCUWidth);
+  bool           leftAvail      = cs.getCURestricted( leftLumaPos,  lumaPos, curSliceIdx, curTileIdx, CH_L ) ? true : false;
+  bool           aboveAvail     = cs.getCURestricted( aboveLumaPos, lumaPos, curSliceIdx, curTileIdx, CH_L ) ? true : false;
+  int            ctxt           = 0;
+
+  if (leftAvail)
+  {
+    ctxt += ( filterControlIdc[curIdx - 1]) ? 1 : 0;
+  }
+  if (aboveAvail)
+  {
+    ctxt += (filterControlIdc[curIdx - cs.pcv->widthInCtus]) ? 1 : 0;
+  }
+  ctxt += ( compID == COMPONENT_Cr ) ? 3 : 0;
+
+  m_BinEncoder.encodeBin( ( idcVal == 0 ) ? 0 : 1, Ctx::CcAlfFilterControlFlag( ctxt ) ); // ON/OFF flag is context coded
+  if ( idcVal > 0 )
+  {
+    int val = (idcVal - 1);
+    while ( val )
+    {
+      m_BinEncoder.encodeBinEP( 1 );
+      val--;
+    }
+    if ( idcVal < filterCount )
+    {
+      m_BinEncoder.encodeBinEP( 0 );
+    }
+  }
+  DTRACE( g_trace_ctx, D_SYNTAX, "ccAlfFilterControlIdc() compID=%d pos=(%d,%d) ctxt=%d, filterCount=%d, idcVal=%d\n", compID, lumaPos.x, lumaPos.y, ctxt, filterCount, idcVal );
+}
+#endif
+
 void CABACWriter::code_unary_fixed( unsigned symbol, unsigned ctxId, unsigned unary_max, unsigned fixed )
 {
   bool unary = (symbol <= unary_max);
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index 940fa4c22..8f2124456 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -164,6 +164,10 @@ public:
   void        codeAlfCtuAlternatives     ( CodingStructure& cs, ChannelType channel, AlfParam* alfParam);
   void        codeAlfCtuAlternatives     ( CodingStructure& cs, ComponentID compID, AlfParam* alfParam);
   void        codeAlfCtuAlternative      ( CodingStructure& cs, uint32_t ctuRsAddr, const int compIdx, const AlfParam* alfParam = NULL );
+#if JVET_Q0795_CCALF
+  void codeCcAlfFilterControlIdc(uint8_t idcVal, CodingStructure &cs, const ComponentID compID, const int curIdx,
+                                 const uint8_t *filterControlIdc, Position lumaPos, const int filterCount);
+#endif
 
 private:
   void        unary_max_symbol          ( unsigned symbol, unsigned ctxId0, unsigned ctxIdN, unsigned maxSymbol );
diff --git a/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp b/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp
index 6de3317a9..252cea9d5 100644
--- a/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp
+++ b/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp
@@ -42,6 +42,20 @@
 #define AlfCtx(c) SubCtx( Ctx::Alf, c)
 std::vector<double> EncAdaptiveLoopFilter::m_lumaLevelToWeightPLUT;
 
+#if JVET_Q0795_CCALF
+#include <algorithm>
+
+#if MAX_NUM_CC_ALF_FILTERS>1
+struct FilterIdxCount
+{
+  uint64_t count;
+  uint8_t filterIdx;
+};
+
+bool compareCounts(FilterIdxCount a, FilterIdxCount b) { return a.count > b.count; }
+#endif
+#endif
+
 void AlfCovariance::getClipMax(const AlfFilterShape& alfShape, int *clip_max) const
 {
   for( int k = 0; k < numCoeff-1; ++k )
@@ -241,6 +255,27 @@ double AlfCovariance::calcErrorForCoeffs( const int *clip, const int *coeff, con
   return error / factor;
 }
 
+#if JVET_Q0795_CCALF
+double AlfCovariance::calcErrorForCcAlfCoeffs(const int* coeff, const int numCoeff, const int bitDepth) const
+{
+  double factor = 1 << (bitDepth - 1);
+  double error = 0;
+
+  for (int i = 0; i < numCoeff; i++)   // diagonal
+  {
+    double sum = 0;
+    for (int j = i + 1; j < numCoeff; j++)
+    {
+      // E[j][i] = E[i][j], sum will be multiplied by 2 later
+      sum += E[0][0][i][j] * coeff[j];
+    }
+    error += ((E[0][0][i][i] * coeff[i] + sum * 2) / factor - 2 * y[0][i]) * coeff[i];
+  }
+
+  return error / factor;
+}
+#endif
+
 double AlfCovariance::calculateError( const int *clip, const double *coeff, const int numCoeff ) const
 {
   double sum = 0;
@@ -417,6 +452,13 @@ EncAdaptiveLoopFilter::EncAdaptiveLoopFilter( int& apsIdStart )
   m_diffFilterCoeff = nullptr;
 
   m_alfWSSD = 0;
+
+#if JVET_Q0795_CCALF
+  m_alfCovarianceCcAlf[0] = nullptr;
+  m_alfCovarianceCcAlf[1] = nullptr;
+  m_alfCovarianceFrameCcAlf[0] = nullptr;
+  m_alfCovarianceFrameCcAlf[1] = nullptr;
+#endif
 }
 
 void EncAdaptiveLoopFilter::create( const EncCfg* encCfg, const int picWidth, const int picHeight, const ChromaFormat chromaFormatIDC, const int maxCUWidth, const int maxCUHeight, const int maxCUDepth, const int inputBitDepth[MAX_NUM_CHANNEL_TYPE], const int internalBitDepth[MAX_NUM_CHANNEL_TYPE] )
@@ -499,6 +541,51 @@ void EncAdaptiveLoopFilter::create( const EncCfg* encCfg, const int picWidth, co
   }
   m_alfCtbFilterSetIndexTmp.resize(m_numCTUsInPic);
   memset(m_clipDefaultEnc, 0, sizeof(m_clipDefaultEnc));
+#if JVET_Q0795_CCALF
+  m_apsIdCcAlfStart[0] = (int) MAX_NUM_APS;
+  m_apsIdCcAlfStart[1] = (int) MAX_NUM_APS;
+  for( int compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++ )
+  {
+    int numFilters = MAX_NUM_CC_ALF_FILTERS;
+    m_alfCovarianceCcAlf[compIdx-1] = new AlfCovariance**[m_filterShapesCcAlf[compIdx-1].size()];
+    m_alfCovarianceFrameCcAlf[compIdx-1] = new AlfCovariance*[m_filterShapesCcAlf[compIdx-1].size()];
+    for( int i = 0; i != m_filterShapesCcAlf[compIdx-1].size(); i++ )
+    {
+      m_alfCovarianceFrameCcAlf[compIdx - 1][i] = new AlfCovariance[numFilters];
+      for (int k = 0; k < numFilters; k++)
+      {
+        m_alfCovarianceFrameCcAlf[compIdx - 1][i][k].create(m_filterShapesCcAlf[compIdx - 1][i].numCoeff);
+      }
+
+      m_alfCovarianceCcAlf[compIdx - 1][i] = new AlfCovariance *[numFilters];
+      for (int j = 0; j < numFilters; j++)
+      {
+        m_alfCovarianceCcAlf[compIdx - 1][i][j] = new AlfCovariance[m_numCTUsInPic];
+        for (int k = 0; k < m_numCTUsInPic; k++)
+        {
+          m_alfCovarianceCcAlf[compIdx - 1][i][j][k].create(m_filterShapesCcAlf[compIdx - 1][i].numCoeff);
+        }
+      }
+    }
+  }
+  m_trainingCovControl   = new uint8_t[m_numCTUsInPic];
+  m_unfilteredDistortion = new uint64_t *[1];
+  for (int i = 0; i < 1; i++)
+  {
+    m_unfilteredDistortion[i] = new uint64_t[m_numCTUsInPic];
+  }
+  for ( int i = 0; i < MAX_NUM_CC_ALF_FILTERS; i++ )
+  {
+    m_trainingDistortion[i] = new uint64_t[m_numCTUsInPic];
+  }
+  m_filterControl         = new uint8_t[m_numCTUsInPic];
+  m_bestFilterControl     = new uint8_t[m_numCTUsInPic];
+  uint32_t area           = (picWidth >> getComponentScaleX(COMPONENT_Cb,chromaFormatIDC))*(picHeight >> getComponentScaleY(COMPONENT_Cb,chromaFormatIDC));
+  m_bufOrigin             = ( Pel* ) xMalloc( Pel, area );
+  m_buf                   = new PelBuf( m_bufOrigin, picWidth >> getComponentScaleX(COMPONENT_Cb,chromaFormatIDC), picWidth >> getComponentScaleX(COMPONENT_Cb,chromaFormatIDC), picHeight >> getComponentScaleY(COMPONENT_Cb,chromaFormatIDC) );
+  m_lumaSwingGreaterThanThresholdCount = new uint64_t[m_numCTUsInPic];
+  m_chromaSampleCountNearMidPoint = new uint64_t[m_numCTUsInPic];
+#endif
 }
 
 void EncAdaptiveLoopFilter::destroy()
@@ -622,6 +709,106 @@ void EncAdaptiveLoopFilter::destroy()
     delete[] m_ctbDistortionUnfilter[comp];
     m_ctbDistortionUnfilter[comp] = nullptr;
   }
+
+#if JVET_Q0795_CCALF
+  for (int compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++)
+  {
+    int numFilters = MAX_NUM_CC_ALF_FILTERS;
+    if (m_alfCovarianceFrameCcAlf[compIdx - 1])
+    {
+      for (int i = 0; i != m_filterShapesCcAlf[compIdx - 1].size(); i++)
+      {
+        for (int k = 0; k < numFilters; k++)
+        {
+          m_alfCovarianceFrameCcAlf[compIdx - 1][i][k].destroy();
+        }
+        delete[] m_alfCovarianceFrameCcAlf[compIdx - 1][i];
+      }
+      delete[] m_alfCovarianceFrameCcAlf[compIdx - 1];
+      m_alfCovarianceFrameCcAlf[compIdx - 1] = nullptr;
+    }
+
+    if (m_alfCovarianceCcAlf[compIdx - 1])
+    {
+      for (int i = 0; i != m_filterShapesCcAlf[compIdx - 1].size(); i++)
+      {
+        for (int j = 0; j < numFilters; j++)
+        {
+          for (int k = 0; k < m_numCTUsInPic; k++)
+          {
+            m_alfCovarianceCcAlf[compIdx - 1][i][j][k].destroy();
+          }
+          delete[] m_alfCovarianceCcAlf[compIdx - 1][i][j];
+        }
+        delete[] m_alfCovarianceCcAlf[compIdx - 1][i];
+      }
+      delete[] m_alfCovarianceCcAlf[compIdx - 1];
+      m_alfCovarianceCcAlf[compIdx - 1] = nullptr;
+    }
+  }
+
+  if (m_trainingCovControl)
+  {
+    delete[] m_trainingCovControl;
+    m_trainingCovControl = nullptr;
+  }
+
+  for (int i = 0; i < 1; i++)
+  {
+    if (m_unfilteredDistortion[i])
+    {
+      delete[] m_unfilteredDistortion[i];
+      m_unfilteredDistortion[i] = nullptr;
+    }
+  }
+  delete[] m_unfilteredDistortion;
+  m_unfilteredDistortion = nullptr;
+
+  for ( int i = 0; i < MAX_NUM_CC_ALF_FILTERS; i++ )
+  {
+    if (m_trainingDistortion[i])
+    {
+      delete[] m_trainingDistortion[i];
+      m_trainingDistortion[i] = nullptr;
+    }
+  }
+
+  if (m_filterControl)
+  {
+    delete[] m_filterControl;
+    m_filterControl = nullptr;
+  }
+
+  if (m_bestFilterControl)
+  {
+    delete[] m_bestFilterControl;
+    m_bestFilterControl = nullptr;
+  }
+
+  if (m_bufOrigin)
+  {
+    xFree(m_bufOrigin);
+    m_bufOrigin = nullptr;
+  }
+
+  if (m_buf)
+  {
+    delete m_buf;
+    m_buf = nullptr;
+  }
+
+  if (m_lumaSwingGreaterThanThresholdCount)
+  {
+    delete[] m_lumaSwingGreaterThanThresholdCount;
+    m_lumaSwingGreaterThanThresholdCount = nullptr;
+  }
+  if (m_chromaSampleCountNearMidPoint)
+  {
+    delete[] m_chromaSampleCountNearMidPoint;
+    m_chromaSampleCountNearMidPoint = nullptr;
+  }
+#endif
+
   AdaptiveLoopFilter::destroy();
 }
 void EncAdaptiveLoopFilter::initCABACEstimator( CABACEncoder* cabacEncoder, CtxCache* ctxCache, Slice* pcSlice
@@ -634,6 +821,76 @@ void EncAdaptiveLoopFilter::initCABACEstimator( CABACEncoder* cabacEncoder, CtxC
   m_CABACEstimator->resetBits();
 }
 
+#if JVET_Q0795_CCALF
+void EncAdaptiveLoopFilter::xSetupCcAlfAPS( CodingStructure &cs )
+{
+  if (m_ccAlfFilterParam.ccAlfFilterEnabled[COMPONENT_Cb - 1])
+  {
+    int  ccAlfCbApsId = cs.slice->getTileGroupCcAlfCbApsId();
+    APS* aps = m_apsMap->getPS((cs.slice->getTileGroupCcAlfCbApsId() << NUM_APS_TYPE_LEN) + ALF_APS);
+    if (aps == NULL)
+    {
+      aps = m_apsMap->allocatePS((ccAlfCbApsId << NUM_APS_TYPE_LEN) + ALF_APS);
+      aps->setTemporalId(cs.slice->getTLayer());
+    }
+    aps->getCcAlfAPSParam().ccAlfFilterEnabled[COMPONENT_Cb - 1] = 1;
+    aps->getCcAlfAPSParam().ccAlfFilterCount[COMPONENT_Cb - 1] = m_ccAlfFilterParam.ccAlfFilterCount[COMPONENT_Cb - 1];
+    for ( int filterIdx = 0; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+    {
+      aps->getCcAlfAPSParam().ccAlfFilterIdxEnabled[COMPONENT_Cb - 1][filterIdx] =
+        m_ccAlfFilterParam.ccAlfFilterIdxEnabled[COMPONENT_Cb - 1][filterIdx];
+      memcpy(aps->getCcAlfAPSParam().ccAlfCoeff[COMPONENT_Cb - 1][filterIdx],
+             m_ccAlfFilterParam.ccAlfCoeff[COMPONENT_Cb - 1][filterIdx], sizeof(short) * MAX_NUM_CC_ALF_CHROMA_COEFF);
+    }
+    aps->setAPSId(ccAlfCbApsId);
+    aps->setAPSType(ALF_APS);
+    if (m_reuseApsId[COMPONENT_Cb - 1] < 0)
+    {
+      aps->getCcAlfAPSParam().newCcAlfFilter[COMPONENT_Cb - 1] = 1;
+      m_apsMap->setChangedFlag((ccAlfCbApsId << NUM_APS_TYPE_LEN) + ALF_APS, true);
+      aps->setTemporalId(cs.slice->getTLayer());
+    }
+    cs.slice->setTileGroupCcAlfCbEnabledFlag(true);
+  }
+  else
+  {
+    cs.slice->setTileGroupCcAlfCbEnabledFlag(false);
+  }
+  if (m_ccAlfFilterParam.ccAlfFilterEnabled[COMPONENT_Cr - 1])
+  {
+    int  ccAlfCrApsId = cs.slice->getTileGroupCcAlfCrApsId();
+    APS* aps = m_apsMap->getPS((cs.slice->getTileGroupCcAlfCrApsId() << NUM_APS_TYPE_LEN) + ALF_APS);
+    if (aps == NULL)
+    {
+      aps = m_apsMap->allocatePS((ccAlfCrApsId << NUM_APS_TYPE_LEN) + ALF_APS);
+      aps->setTemporalId(cs.slice->getTLayer());
+    }
+    aps->getCcAlfAPSParam().ccAlfFilterEnabled[COMPONENT_Cr - 1] = 1;
+    aps->getCcAlfAPSParam().ccAlfFilterCount[COMPONENT_Cr - 1] = m_ccAlfFilterParam.ccAlfFilterCount[COMPONENT_Cr - 1];
+    for ( int filterIdx = 0; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+    {
+      aps->getCcAlfAPSParam().ccAlfFilterIdxEnabled[COMPONENT_Cr - 1][filterIdx] =
+        m_ccAlfFilterParam.ccAlfFilterIdxEnabled[COMPONENT_Cr - 1][filterIdx];
+      memcpy(aps->getCcAlfAPSParam().ccAlfCoeff[COMPONENT_Cr - 1][filterIdx],
+             m_ccAlfFilterParam.ccAlfCoeff[COMPONENT_Cr - 1][filterIdx], sizeof(short) * MAX_NUM_CC_ALF_CHROMA_COEFF);
+    }
+    aps->setAPSId(ccAlfCrApsId);
+    if (m_reuseApsId[COMPONENT_Cr - 1] < 0)
+    {
+      aps->getCcAlfAPSParam().newCcAlfFilter[COMPONENT_Cr - 1] = 1;
+      m_apsMap->setChangedFlag((ccAlfCrApsId << NUM_APS_TYPE_LEN) + ALF_APS, true);
+      aps->setTemporalId(cs.slice->getTLayer());
+    }
+    aps->setAPSType(ALF_APS);
+    cs.slice->setTileGroupCcAlfCrEnabledFlag(true);
+  }
+  else
+  {
+    cs.slice->setTileGroupCcAlfCrEnabledFlag(false);
+  }
+}
+#endif
+
 void EncAdaptiveLoopFilter::ALFProcess(CodingStructure& cs, const double *lambdas
 #if ENABLE_QPA
                                        , const double lambdaChromaWeight
@@ -656,6 +913,9 @@ void EncAdaptiveLoopFilter::ALFProcess(CodingStructure& cs, const double *lambda
       if (alfAPS)
       {
         alfAPS->getAlfAPSParam().reset();
+#if JVET_Q0795_CCALF
+        alfAPS->getCcAlfAPSParam().reset();
+#endif
         alfAPS = nullptr;
       }
     }
@@ -663,6 +923,11 @@ void EncAdaptiveLoopFilter::ALFProcess(CodingStructure& cs, const double *lambda
   AlfParam alfParam;
   alfParam.reset();
   const TempCtx  ctxStart(m_CtxCache, AlfCtx(m_CABACEstimator->getCtx()));
+
+#if JVET_Q0795_CCALF
+  const TempCtx ctxStartCcAlf(m_CtxCache, SubCtx(Ctx::CcAlfFilterControlFlag, m_CABACEstimator->getCtx()));
+#endif
+
   // set available filter shapes
   alfParam.filterShapes = m_filterShapes;
 
@@ -796,6 +1061,55 @@ void EncAdaptiveLoopFilter::ALFProcess(CodingStructure& cs, const double *lambda
   );
 
   alfReconstructor(cs, recYuv);
+
+#if JVET_Q0795_CCALF
+  // Do not transmit CC ALF if it is unchanged
+  if (cs.slice->getTileGroupAlfEnabledFlag(COMPONENT_Y))
+  {
+    for (int32_t lumaAlfApsId : cs.slice->getTileGroupApsIdLuma())
+    {
+      APS* aps = (lumaAlfApsId >= 0) ? m_apsMap->getPS((lumaAlfApsId << NUM_APS_TYPE_LEN) + ALF_APS) : nullptr;
+      if (aps && m_apsMap->getChangedFlag((lumaAlfApsId << NUM_APS_TYPE_LEN) + ALF_APS))
+      {
+        aps->getCcAlfAPSParam().newCcAlfFilter[0] = false;
+          aps->getCcAlfAPSParam().newCcAlfFilter[1] = false;
+      }
+    }
+  }
+  int chromaAlfApsId = ( cs.slice->getTileGroupAlfEnabledFlag(COMPONENT_Cb) || cs.slice->getTileGroupAlfEnabledFlag(COMPONENT_Cr) ) ? cs.slice->getTileGroupApsIdChroma() : -1;
+  APS* aps = (chromaAlfApsId >= 0) ? m_apsMap->getPS((chromaAlfApsId << NUM_APS_TYPE_LEN) + ALF_APS) : nullptr;
+  if (aps && m_apsMap->getChangedFlag((chromaAlfApsId << NUM_APS_TYPE_LEN) + ALF_APS))
+  {
+    aps->getCcAlfAPSParam().newCcAlfFilter[0] = false;
+    aps->getCcAlfAPSParam().newCcAlfFilter[1] = false;
+  }
+
+  if (!cs.slice->getSPS()->getCCALFEnabledFlag())
+  {
+    return;
+  }
+
+  m_tempBuf.get(COMPONENT_Cb).copyFrom(cs.getRecoBuf().get(COMPONENT_Cb));
+  m_tempBuf.get(COMPONENT_Cr).copyFrom(cs.getRecoBuf().get(COMPONENT_Cr));
+  recYuv = m_tempBuf.getBuf(cs.area);
+  recYuv.extendBorderPel(MAX_ALF_FILTER_LENGTH >> 1);
+  m_CABACEstimator->getCtx() = SubCtx(Ctx::CcAlfFilterControlFlag, ctxStartCcAlf);
+  deriveCcAlfFilter(cs, COMPONENT_Cb, orgYuv, recYuv, cs.getRecoBuf());
+  m_CABACEstimator->getCtx() = SubCtx(Ctx::CcAlfFilterControlFlag, ctxStartCcAlf);
+  deriveCcAlfFilter(cs, COMPONENT_Cr, orgYuv, recYuv, cs.getRecoBuf());
+
+  xSetupCcAlfAPS(cs);
+
+  for (int compIdx = 1; compIdx < getNumberValidComponents(cs.pcv->chrFormat); compIdx++)
+  {
+    ComponentID compID     = ComponentID(compIdx);
+    if (m_ccAlfFilterParam.ccAlfFilterEnabled[compIdx - 1])
+    {
+      applyCcAlfFilter(cs, compID, cs.getRecoBuf().get(compID), recYuv, m_ccAlfFilterControl[compIdx - 1],
+                       m_ccAlfFilterParam.ccAlfCoeff[compIdx - 1], -1);
+    }
+  }
+#endif
 }
 
 double EncAdaptiveLoopFilter::deriveCtbAlfEnableFlags( CodingStructure& cs, const int iShapeIdx, ChannelType channel,
@@ -1604,6 +1918,29 @@ void EncAdaptiveLoopFilter::roundFiltCoeff( int *filterCoeffQuant, double *filte
   }
 }
 
+#if JVET_Q0795_CCALF
+void EncAdaptiveLoopFilter::roundFiltCoeffCCALF( int *filterCoeffQuant, double *filterCoeff, const int numCoeff, const int factor )
+{
+  for( int i = 0; i < numCoeff; i++ )
+  {
+    int sign = filterCoeff[i] > 0 ? 1 : -1;
+    double best_err = 128.0*128.0;
+    int best_index = 0;
+    for(int k = 0; k < CCALF_CANDS_COEFF_NR; k++)
+    {
+      double err = (filterCoeff[i] * sign * factor - CCALF_SMALL_TAB[k]);
+      err = err*err;
+      if(err < best_err)
+      {
+        best_err = err;
+        best_index = k;
+      }
+    }
+    filterCoeffQuant[i] = CCALF_SMALL_TAB[best_index] * sign;
+  }
+}
+#endif
+
 void EncAdaptiveLoopFilter::mergeClasses( const AlfFilterShape& alfShape, AlfCovariance* cov, AlfCovariance* covMerged, int clipMerged[MAX_NUM_ALF_CLASSES][MAX_NUM_ALF_CLASSES][MAX_NUM_ALF_LUMA_COEFF], const int numClasses, short filterIndices[MAX_NUM_ALF_CLASSES][MAX_NUM_ALF_CLASSES] )
 {
   static int tmpClip[MAX_NUM_ALF_LUMA_COEFF];
@@ -2538,6 +2875,11 @@ void  EncAdaptiveLoopFilter::alfEncoderCtb(CodingStructure& cs, AlfParam& alfPar
     }// for (int numTemporalAps = 0; numTemporalAps < apsIds.size(); numTemporalAps++)
   }//for (int useNewFilter = 0; useNewFilter <= 1; useNewFilter++)
 
+#if JVET_Q0795_CCALF
+  cs.slice->setTileGroupCcAlfCbApsId(newApsId);
+  cs.slice->setTileGroupCcAlfCrApsId(newApsId);
+#endif
+
   if (costOff <= costMin)
   {
     cs.slice->resetTileGroupAlfEnabledFlag();
@@ -2735,6 +3077,14 @@ void  EncAdaptiveLoopFilter::alfEncoderCtb(CodingStructure& cs, AlfParam& alfPar
       copyCtuAlternativeChroma(m_ctuAlternativeTmp, m_ctuAlternative);
     }
   }
+
+#if JVET_Q0795_CCALF
+  if (newApsIdChroma >= 0)
+  {
+    cs.slice->setTileGroupCcAlfCbApsId(newApsIdChroma);
+    cs.slice->setTileGroupCcAlfCrApsId(newApsIdChroma);
+  }
+#endif
   if (costOff < costMin)
   {
     cs.slice->setTileGroupAlfEnabledFlag(COMPONENT_Cb, false);
@@ -2971,3 +3321,999 @@ int EncAdaptiveLoopFilter::getMaxNumAlternativesChroma( )
 {
   return std::min<int>( m_numCTUsInPic * 2, m_encCfg->getMaxNumAlfAlternativesChroma() );
 }
+
+#if JVET_Q0795_CCALF
+int EncAdaptiveLoopFilter::getCoeffRateCcAlf(short chromaCoeff[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF], bool filterEnabled[MAX_NUM_CC_ALF_FILTERS], uint8_t filterCount, ComponentID compID)
+{
+  int bits = 0;
+
+  if ( filterCount > 0 )
+  {
+    bits += lengthUvlc(filterCount - 1);
+    int signaledFilterCount = 0;
+    for ( int filterIdx=0; filterIdx<MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+    {
+      if (filterEnabled[filterIdx])
+      {
+        AlfFilterShape alfShape(size_CC_ALF);
+        // Filter coefficients
+        for (int i = 0; i < alfShape.numCoeff - 1; i++)
+        {
+          bits += CCALF_BITS_PER_COEFF_LEVEL + (chromaCoeff[filterIdx][i] == 0 ? 0 : 1);
+        }
+
+        signaledFilterCount++;
+      }
+    }
+    CHECK(signaledFilterCount != filterCount, "Number of filter signaled not same as indicated");
+  }
+
+  return bits;
+}
+
+void EncAdaptiveLoopFilter::deriveCcAlfFilterCoeff( ComponentID compID, const PelUnitBuf& recYuv, const PelUnitBuf& recYuvExt, short filterCoeff[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF], const uint8_t filterIdx )
+{
+  int forward_tab[CCALF_CANDS_COEFF_NR * 2 - 1] = {0};
+  for (int i = 0; i < CCALF_CANDS_COEFF_NR; i++)
+  {
+    forward_tab[CCALF_CANDS_COEFF_NR - 1 + i] = CCALF_SMALL_TAB[i];
+    forward_tab[CCALF_CANDS_COEFF_NR - 1 - i] = (-1) * CCALF_SMALL_TAB[i];
+  }
+  using TE = double[MAX_NUM_ALF_LUMA_COEFF][MAX_NUM_ALF_LUMA_COEFF];
+  using Ty = double[MAX_NUM_ALF_LUMA_COEFF];
+
+  static double filterCoeffDbl[MAX_NUM_CC_ALF_CHROMA_COEFF];
+  static int    filterCoeffInt[MAX_NUM_CC_ALF_CHROMA_COEFF];
+
+  TE        kE;
+  Ty        ky;
+  const int size = m_filterShapesCcAlf[compID - 1][0].numCoeff - 1;
+
+  for (int k = 0; k < size; k++)
+  {
+    ky[k] = m_alfCovarianceFrameCcAlf[compID - 1][0][filterIdx].y[0][k];
+    for (int l = 0; l < size; l++)
+    {
+      kE[k][l] = m_alfCovarianceFrameCcAlf[compID - 1][0][filterIdx].E[0][0][k][l];
+    }
+  }
+
+  m_alfCovarianceFrameCcAlf[compID - 1][0][filterIdx].gnsSolveByChol(kE, ky, filterCoeffDbl, size);
+  roundFiltCoeffCCALF(filterCoeffInt, filterCoeffDbl, size, (1 << m_scaleBits));
+
+  for (int k = 0; k < size; k++)
+  {
+    CHECK( filterCoeffInt[k] < -(1 << CCALF_DYNAMIC_RANGE), "this is not possible: filterCoeffInt[k] <  -(1 << CCALF_DYNAMIC_RANGE)");
+    CHECK( filterCoeffInt[k] > (1 << CCALF_DYNAMIC_RANGE), "this is not possible: filterCoeffInt[k] >  (1 << CCALF_DYNAMIC_RANGE)");
+  }
+
+  // Refine quanitzation
+  int modified       = 1;
+  double errRef      = m_alfCovarianceFrameCcAlf[compID - 1][0][filterIdx].calcErrorForCcAlfCoeffs(filterCoeffInt, size, (m_scaleBits+1));
+  while (modified)
+  {
+    modified = 0;
+    for (int delta : { 1, -1 })
+    {
+      double errMin = MAX_DOUBLE;
+      int    idxMin = -1;
+      int minIndex = -1;
+
+      for (int k = 0; k < size; k++)
+      {
+        int org_idx = -1;
+        for (int i = 0; i < CCALF_CANDS_COEFF_NR * 2 - 1; i++)
+        {
+          if (forward_tab[i] == filterCoeffInt[k])
+          {
+            org_idx = i;
+            break;
+          }
+        }
+        CHECK( org_idx < 0, "this is wrong, does not find coeff from forward_tab");
+        if ( (org_idx - delta < 0) || (org_idx - delta >= CCALF_CANDS_COEFF_NR * 2 - 1) )
+          continue;
+
+        filterCoeffInt[k] = forward_tab[org_idx - delta];
+        double error = m_alfCovarianceFrameCcAlf[compID - 1][0][filterIdx].calcErrorForCcAlfCoeffs(filterCoeffInt, size, (m_scaleBits+1));
+        if( error < errMin )
+        {
+          errMin = error;
+          idxMin = k;
+          minIndex = org_idx;
+        }
+        filterCoeffInt[k] = forward_tab[org_idx];
+      }
+      if (errMin < errRef)
+      {
+        minIndex -= delta;
+        CHECK( minIndex < 0, "this is wrong, index - delta < 0");
+        CHECK( minIndex >= CCALF_CANDS_COEFF_NR * 2 - 1, "this is wrong, index - delta >= CCALF_CANDS_COEFF_NR * 2 - 1");
+        filterCoeffInt[idxMin] = forward_tab[minIndex];
+        modified++;
+        errRef = errMin;
+      }
+    }
+  }
+
+
+  for (int k = 0; k < (size + 1); k++)
+  {
+    CHECK((filterCoeffInt[k] < -(1 << CCALF_DYNAMIC_RANGE)) || (filterCoeffInt[k] > (1 << CCALF_DYNAMIC_RANGE)), "Exceeded valid range for CC ALF coefficient");
+    filterCoeff[filterIdx][k] = filterCoeffInt[k];
+  }
+}
+
+
+void EncAdaptiveLoopFilter::computeLog2BlockSizeDistortion(const Pel *org, int orgStride, const Pel *dec, int decStride,
+                                                           int height, int width, uint64_t *distortionBuf,
+                                                           int distortionBufStride, int log2BlockWidth,
+                                                           int log2BlockHeight, uint64_t& totalDistortion)
+{
+  totalDistortion = 0;
+  for (int y = 0; y < height; y += (1 << log2BlockHeight))
+  {
+    for (int x = 0; x < width; x += (1 << log2BlockWidth))
+    {
+      int      err;
+      uint64_t ssd = 0;
+
+      for (int yOff = 0; yOff < (1 << log2BlockHeight); yOff++)
+      {
+        for (int xOff = 0; xOff < (1 << log2BlockWidth); xOff++)
+        {
+          if ((y + yOff) >= height || (x + xOff) >= width)
+          {
+            continue;
+          }
+
+          err = org[yOff * orgStride + x + xOff] - dec[yOff * decStride + x + xOff];
+          ssd += err * err;
+        }
+      }
+
+      distortionBuf[(y >> log2BlockHeight) * distortionBufStride + (x >> log2BlockWidth)] = ssd;
+      totalDistortion += ssd;
+    }
+    org += (orgStride << log2BlockHeight);
+    dec += (decStride << log2BlockHeight);
+  }
+}
+
+void EncAdaptiveLoopFilter::determineControlIdcValues(CodingStructure &cs, const ComponentID compID, const PelBuf *buf,
+                                                      const int ctuWidthC, const int ctuHeightC, const int picWidthC,
+                                                      const int picHeightC, uint64_t **unfilteredDistortion,
+                                                      uint64_t *trainingDistortion[MAX_NUM_CC_ALF_FILTERS],
+                                                      uint64_t *lumaSwingGreaterThanThresholdCount,
+                                                      uint64_t *chromaSampleCountNearMidPoint,
+                                                      bool reuseTemporalFilterCoeff, uint8_t *trainingCovControl,
+                                                      uint8_t *filterControl, uint64_t &curTotalDistortion,
+                                                      double &curTotalRate, bool filterEnabled[MAX_NUM_CC_ALF_FILTERS],
+                                                      uint8_t  mapFilterIdxToFilterIdc[MAX_NUM_CC_ALF_FILTERS + 1],
+                                                      uint8_t &ccAlfFilterCount)
+{
+  bool curFilterEnabled[MAX_NUM_CC_ALF_FILTERS];
+  std::fill_n(curFilterEnabled, MAX_NUM_CC_ALF_FILTERS, false);
+
+#if MAX_NUM_CC_ALF_FILTERS>1
+  FilterIdxCount filterIdxCount[MAX_NUM_CC_ALF_FILTERS];
+  for (int i = 0; i < MAX_NUM_CC_ALF_FILTERS; i++)
+  {
+    filterIdxCount[i].count     = 0;
+    filterIdxCount[i].filterIdx = i;
+  }
+
+  double prevRate = curTotalRate;
+#endif
+
+  TempCtx ctxInitial(m_CtxCache);
+  TempCtx ctxBest(m_CtxCache);
+  TempCtx ctxStart(m_CtxCache);
+  ctxInitial = SubCtx(Ctx::CcAlfFilterControlFlag, m_CABACEstimator->getCtx());
+  ctxBest    = SubCtx(Ctx::CcAlfFilterControlFlag, m_CABACEstimator->getCtx());
+
+  int ctuIdx = 0;
+  for (int yCtu = 0; yCtu < buf->height; yCtu += ctuHeightC)
+  {
+    for (int xCtu = 0; xCtu < buf->width; xCtu += ctuWidthC)
+    {
+      uint64_t ssd;
+      double   rate;
+      double   cost;
+
+      uint64_t bestSSD       = MAX_UINT64;
+      double   bestRate      = MAX_DOUBLE;
+      double   bestCost      = MAX_DOUBLE;
+      uint8_t  bestFilterIdc = 0;
+      uint8_t  bestFilterIdx = 0;
+      const uint32_t thresholdS = std::min<int>(buf->height - yCtu, ctuHeightC) << getComponentScaleY(COMPONENT_Cb, m_chromaFormat);
+      const uint32_t numberOfChromaSamples = std::min<int>(buf->height - yCtu, ctuHeightC) * std::min<int>(buf->width - xCtu, ctuWidthC);
+      const uint32_t thresholdC = (numberOfChromaSamples >> 2);
+
+      m_CABACEstimator->getCtx() = ctxBest;
+      ctxStart                   = SubCtx(Ctx::CcAlfFilterControlFlag, m_CABACEstimator->getCtx());
+
+      for (int filterIdx = 0; filterIdx <= MAX_NUM_CC_ALF_FILTERS; filterIdx++)
+      {
+        uint8_t filterIdc = mapFilterIdxToFilterIdc[filterIdx];
+        if (filterIdx < MAX_NUM_CC_ALF_FILTERS && !filterEnabled[filterIdx])
+        {
+          continue;
+        }
+
+        if (filterIdx == MAX_NUM_CC_ALF_FILTERS)
+        {
+          ssd = unfilteredDistortion[0][ctuIdx];   // restore saved distortion computation
+        }
+        else
+        {
+          ssd = trainingDistortion[filterIdx][ctuIdx];
+        }
+        m_CABACEstimator->getCtx() = ctxStart;
+        m_CABACEstimator->resetBits();
+        const Position lumaPos = Position({ xCtu << getComponentScaleX(compID, cs.pcv->chrFormat),
+          yCtu << getComponentScaleY(compID, cs.pcv->chrFormat) });
+        m_CABACEstimator->codeCcAlfFilterControlIdc(filterIdc, cs, compID, ctuIdx, filterControl, lumaPos,
+                                                    ccAlfFilterCount);
+        rate = FRAC_BITS_SCALE * m_CABACEstimator->getEstFracBits();
+        cost = rate * m_lambda[compID] + ssd;
+
+        bool limitationExceeded = false;
+        if (m_limitCcAlf && filterIdx < MAX_NUM_CC_ALF_FILTERS)
+        {
+          limitationExceeded = limitationExceeded || (lumaSwingGreaterThanThresholdCount[ctuIdx] >= thresholdS);
+          limitationExceeded = limitationExceeded || (chromaSampleCountNearMidPoint[ctuIdx] >= thresholdC);
+        }
+        if (cost < bestCost && !limitationExceeded)
+        {
+          bestCost      = cost;
+          bestRate      = rate;
+          bestSSD       = ssd;
+          bestFilterIdc = filterIdc;
+          bestFilterIdx = filterIdx;
+
+          ctxBest = SubCtx(Ctx::CcAlfFilterControlFlag, m_CABACEstimator->getCtx());
+
+          trainingCovControl[ctuIdx] = (filterIdx == MAX_NUM_CC_ALF_FILTERS) ? 0 : (filterIdx + 1);
+          filterControl[ctuIdx]      = (filterIdx == MAX_NUM_CC_ALF_FILTERS) ? 0 : (filterIdx + 1);
+        }
+      }
+      if (bestFilterIdc != 0)
+      {
+        curFilterEnabled[bestFilterIdx] = true;
+#if MAX_NUM_CC_ALF_FILTERS>1
+        filterIdxCount[bestFilterIdx].count++;
+#endif
+      }
+      curTotalRate += bestRate;
+      curTotalDistortion += bestSSD;
+      ctuIdx++;
+    }
+  }
+
+#if MAX_NUM_CC_ALF_FILTERS>1
+  if (!reuseTemporalFilterCoeff)
+  {
+    std::copy_n(curFilterEnabled, MAX_NUM_CC_ALF_FILTERS, filterEnabled);
+
+    std::sort(filterIdxCount, filterIdxCount + MAX_NUM_CC_ALF_FILTERS, compareCounts);
+
+    int filterIdc = 1;
+    ccAlfFilterCount = 0;
+    for ( FilterIdxCount &s : filterIdxCount )
+    {
+      const int filterIdx = s.filterIdx;
+      if (filterEnabled[filterIdx])
+      {
+        mapFilterIdxToFilterIdc[filterIdx] = filterIdc;
+        filterIdc++;
+        ccAlfFilterCount++;
+      }
+    }
+
+    curTotalRate = prevRate;
+    m_CABACEstimator->getCtx() = ctxInitial;
+    m_CABACEstimator->resetBits();
+    int ctuIdx = 0;
+    for (int y = 0; y < buf->height; y += ctuHeightC)
+    {
+      for (int x = 0; x < buf->width; x += ctuWidthC)
+      {
+        const int filterIdxPlus1 = filterControl[ctuIdx];
+
+        const Position lumaPos = Position(
+                                          { x << getComponentScaleX(compID, cs.pcv->chrFormat), y << getComponentScaleY(compID, cs.pcv->chrFormat) });
+
+        m_CABACEstimator->codeCcAlfFilterControlIdc(filterIdxPlus1 == 0 ? 0
+                                                    : mapFilterIdxToFilterIdc[filterIdxPlus1 - 1],
+                                                    cs, compID, ctuIdx, filterControl, lumaPos, ccAlfFilterCount);
+
+        ctuIdx++;
+      }
+    }
+    curTotalRate += FRAC_BITS_SCALE*m_CABACEstimator->getEstFracBits();
+  }
+#endif
+
+  // restore for next iteration
+  m_CABACEstimator->getCtx() = ctxInitial;
+}
+
+std::vector<int> EncAdaptiveLoopFilter::getAvailableCcAlfApsIds(CodingStructure& cs, ComponentID compID)
+{
+  APS** apss = cs.slice->getAlfAPSs();
+  for (int i = 0; i < ALF_CTB_MAX_NUM_APS; i++)
+  {
+    apss[i] = m_apsMap->getPS((i << NUM_APS_TYPE_LEN) + ALF_APS);
+  }
+
+  std::vector<int> result;
+  int apsIdChecked = 0, curApsId = m_apsIdStart;
+  if (curApsId < ALF_CTB_MAX_NUM_APS)
+  {
+    while (apsIdChecked < ALF_CTB_MAX_NUM_APS && !cs.slice->isIntra() && result.size() < ALF_CTB_MAX_NUM_APS && !cs.slice->getPendingRasInit() && !cs.slice->isIDRorBLA())
+    {
+      APS* curAPS = cs.slice->getAlfAPSs()[curApsId];
+      if (curAPS && curAPS->getTemporalId() <= cs.slice->getTLayer() && curAPS->getCcAlfAPSParam().newCcAlfFilter[compID - 1])
+      {
+        result.push_back(curApsId);
+      }
+      apsIdChecked++;
+      curApsId = (curApsId + 1) % ALF_CTB_MAX_NUM_APS;
+    }
+  }
+  return result;
+}
+
+void EncAdaptiveLoopFilter::deriveCcAlfFilter( CodingStructure& cs, ComponentID compID, const PelUnitBuf& orgYuv, const PelUnitBuf& tempDecYuvBuf, const PelUnitBuf& dstYuv )
+{
+  if (!cs.slice->getTileGroupAlfEnabledFlag(COMPONENT_Y))
+  {
+    m_ccAlfFilterParam.ccAlfFilterEnabled[compID - 1] = false;
+    return;
+  }
+
+  m_limitCcAlf = m_encCfg->getBaseQP() >= m_encCfg->getCCALFQpThreshold();
+  if (m_limitCcAlf && cs.slice->getSliceQp() <= m_encCfg->getBaseQP() + 1)
+  {
+    m_ccAlfFilterParam.ccAlfFilterEnabled[compID - 1] = false;
+    return;
+  }
+
+  uint8_t bestMapFilterIdxToFilterIdc[MAX_NUM_CC_ALF_FILTERS+1];
+  const int scaleX               = getComponentScaleX(compID, cs.pcv->chrFormat);
+  const int scaleY               = getComponentScaleY(compID, cs.pcv->chrFormat);
+  const int ctuWidthC            = cs.pcv->maxCUWidth >> scaleX;
+  const int ctuHeightC           = cs.pcv->maxCUHeight >> scaleY;
+  const int picWidthC            = cs.pcv->lumaWidth >> scaleX;
+  const int picHeightC           = cs.pcv->lumaHeight >> scaleY;
+  const int maxTrainingIterCount = 15;
+
+  if (m_limitCcAlf)
+  {
+    countLumaSwingGreaterThanThreshold(dstYuv.get(COMPONENT_Y).bufAt(0, 0), dstYuv.get(COMPONENT_Y).stride, dstYuv.get(COMPONENT_Y).height, dstYuv.get(COMPONENT_Y).width, cs.pcv->maxCUWidthLog2, cs.pcv->maxCUHeightLog2, m_lumaSwingGreaterThanThresholdCount, m_numCTUsInWidth);
+  }
+  if (m_limitCcAlf)
+  {
+    countChromaSampleValueNearMidPoint(dstYuv.get(compID).bufAt(0, 0), dstYuv.get(compID).stride, dstYuv.get(compID).height, dstYuv.get(compID).width, cs.pcv->maxCUWidthLog2 - scaleX, cs.pcv->maxCUHeightLog2 - scaleY, m_chromaSampleCountNearMidPoint, m_numCTUsInWidth);
+  }
+
+  for ( int filterIdx = 0; filterIdx <= MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+  {
+    if ( filterIdx < MAX_NUM_CC_ALF_FILTERS)
+    {
+      memset( m_bestFilterCoeffSet[filterIdx], 0, sizeof(m_bestFilterCoeffSet[filterIdx]) );
+      bestMapFilterIdxToFilterIdc[filterIdx] = filterIdx + 1;
+    }
+    else
+    {
+      bestMapFilterIdxToFilterIdc[filterIdx] = 0;
+    }
+  }
+  memset(m_bestFilterControl, 0, sizeof(uint8_t) * m_numCTUsInPic);
+  int ccalfReuseApsId      = -1;
+  m_reuseApsId[compID - 1] = -1;
+
+  const TempCtx ctxStartCcAlfFilterControlFlag  ( m_CtxCache, SubCtx( Ctx::CcAlfFilterControlFlag, m_CABACEstimator->getCtx() ) );
+
+  // compute cost of not filtering
+  const Pel *org                = orgYuv.get( compID ).bufAt(0,0);
+  const Pel *unfiltered         = dstYuv.get( compID ).bufAt(0,0);
+  const int orgStride           = orgYuv.get( compID ).stride;
+  const int unfilteredStride    = dstYuv.get( compID ).stride;
+  const Pel *filtered           = m_buf->bufAt(0,0);
+  const int filteredStride      = m_buf->stride;
+  uint64_t unfilteredDistortion = 0;
+  computeLog2BlockSizeDistortion(org, orgStride, unfiltered, unfilteredStride, m_buf->height, m_buf->width,
+                                 m_unfilteredDistortion[0], m_numCTUsInWidth, cs.pcv->maxCUWidthLog2 - scaleX,
+                                 cs.pcv->maxCUHeightLog2 - scaleY, unfilteredDistortion);
+  double bestUnfilteredTotalCost = 1 * m_lambda[compID] + unfilteredDistortion;   // 1 bit is for gating flag
+
+  static bool  ccAlfFilterIdxEnabled[MAX_NUM_CC_ALF_FILTERS];
+  static short ccAlfFilterCoeff[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF];
+  static uint8_t ccAlfFilterCount     = MAX_NUM_CC_ALF_FILTERS;
+  double bestFilteredTotalCost        = MAX_DOUBLE;
+  bool   bestreuseTemporalFilterCoeff = false;
+  std::vector<int> apsIds             = getAvailableCcAlfApsIds(cs, compID);
+
+  for (int testFilterIdx = 0; testFilterIdx < ( apsIds.size() + 1 ); testFilterIdx++ )
+  {
+    bool referencingExistingAps   = (testFilterIdx < apsIds.size()) ? true : false;
+    int maxNumberOfFiltersBeingTested = MAX_NUM_CC_ALF_FILTERS - (testFilterIdx - static_cast<int>(apsIds.size()));
+
+    if (maxNumberOfFiltersBeingTested < 0)
+    {
+      maxNumberOfFiltersBeingTested = 1;
+    }
+
+    {
+      // Instead of rewriting the control buffer for every training iteration just keep a mapping from filterIdx to filterIdc
+      uint8_t mapFilterIdxToFilterIdc[MAX_NUM_CC_ALF_FILTERS + 1];
+      for (int filterIdx = 0; filterIdx <= MAX_NUM_CC_ALF_FILTERS; filterIdx++)
+      {
+        if (filterIdx == MAX_NUM_CC_ALF_FILTERS)
+        {
+          mapFilterIdxToFilterIdc[filterIdx] = 0;
+        }
+        else
+        {
+          mapFilterIdxToFilterIdc[filterIdx] = filterIdx + 1;
+        }
+      }
+
+      // initialize filters
+      for ( int filterIdx = 0; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+      {
+        ccAlfFilterIdxEnabled[filterIdx] = false;
+        memset(ccAlfFilterCoeff[filterIdx], 0, sizeof(ccAlfFilterCoeff[filterIdx]));
+      }
+      if ( referencingExistingAps )
+      {
+        maxNumberOfFiltersBeingTested = m_apsMap->getPS((apsIds[testFilterIdx] << NUM_APS_TYPE_LEN) + ALF_APS)->getCcAlfAPSParam().ccAlfFilterCount[compID - 1];
+        ccAlfFilterCount = maxNumberOfFiltersBeingTested;
+        for (int filterIdx = 0; filterIdx < maxNumberOfFiltersBeingTested; filterIdx++)
+        {
+          ccAlfFilterIdxEnabled[filterIdx] = true;
+          memcpy(ccAlfFilterCoeff[filterIdx], m_ccAlfFilterParam.ccAlfCoeff[compID - 1][filterIdx],
+                 sizeof(ccAlfFilterCoeff[filterIdx]));
+        }
+        memcpy( ccAlfFilterCoeff, m_apsMap->getPS((apsIds[testFilterIdx] << NUM_APS_TYPE_LEN) + ALF_APS)->getCcAlfAPSParam().ccAlfCoeff[compID - 1], sizeof(ccAlfFilterCoeff) );
+      }
+      else
+      {
+        for (int i = 0; i < maxNumberOfFiltersBeingTested; i++)
+        {
+          ccAlfFilterIdxEnabled[i] = true;
+        }
+        ccAlfFilterCount = maxNumberOfFiltersBeingTested;
+      }
+
+      // initialize
+      int controlIdx = 0;
+      const int columnSize = ( m_buf->width / maxNumberOfFiltersBeingTested);
+      for (int y = 0; y < m_buf->height; y += ctuHeightC)
+      {
+        for (int x = 0; x < m_buf->width; x += ctuWidthC)
+        {
+          m_trainingCovControl[controlIdx] = ( x / columnSize ) + 1;
+          controlIdx++;
+        }
+      }
+
+      // compute cost of filtering
+      int    trainingIterCount = 0;
+      bool   keepTraining      = true;
+      bool   improvement       = false;
+      double prevTotalCost     = MAX_DOUBLE;
+      while (keepTraining)
+      {
+        improvement = false;
+        for (int filterIdx = 0; filterIdx < maxNumberOfFiltersBeingTested; filterIdx++)
+        {
+          if (ccAlfFilterIdxEnabled[filterIdx])
+          {
+            if (!referencingExistingAps)
+            {
+              deriveStatsForCcAlfFiltering(orgYuv, tempDecYuvBuf, compID, m_numCTUsInWidth, (filterIdx + 1), cs);
+              deriveCcAlfFilterCoeff(compID, dstYuv, tempDecYuvBuf, ccAlfFilterCoeff, filterIdx);
+            }
+            m_buf->copyFrom(dstYuv.get(compID));
+            applyCcAlfFilter(cs, compID, *m_buf, tempDecYuvBuf, nullptr, ccAlfFilterCoeff, filterIdx);
+
+            uint64_t distortion = 0;
+            computeLog2BlockSizeDistortion(
+                                           org, orgStride, filtered, filteredStride, m_buf->height, m_buf->width, m_trainingDistortion[filterIdx],
+                                           m_numCTUsInWidth, cs.pcv->maxCUWidthLog2 - scaleX, cs.pcv->maxCUHeightLog2 - scaleY, distortion);
+          }
+        }
+
+        m_CABACEstimator->getCtx() = ctxStartCcAlfFilterControlFlag;
+
+        uint64_t curTotalDistortion = 0;
+        double curTotalRate = 0;
+        determineControlIdcValues(cs, compID, m_buf, ctuWidthC, ctuHeightC, picWidthC, picHeightC,
+                                  m_unfilteredDistortion, m_trainingDistortion,
+                                  m_lumaSwingGreaterThanThresholdCount,
+                                  m_chromaSampleCountNearMidPoint,
+                                  (referencingExistingAps == true),
+                                  m_trainingCovControl, m_filterControl, curTotalDistortion, curTotalRate,
+                                  ccAlfFilterIdxEnabled, mapFilterIdxToFilterIdc, ccAlfFilterCount);
+
+        // compute coefficient coding bit cost
+        if (ccAlfFilterCount > 0)
+        {
+          if (referencingExistingAps)
+          {
+            curTotalRate += 1 + 3 + lengthUvlc(ccAlfFilterCount - 1); // +1 for enable flag, +3 APS ID in slice header
+          }
+          else
+          {
+            curTotalRate += getCoeffRateCcAlf(ccAlfFilterCoeff, ccAlfFilterIdxEnabled, ccAlfFilterCount, compID) + 1 + lengthUvlc(ccAlfFilterCount - 1)
+            + 7;   // +1 for the enable flag, +7 two 3-bit APS ID, one in slice header/one in APS, a 1-bit
+            // new filter flags (ignore shared cost such as other new-filter flags/NALU header/RBSP
+            // terminating bit/byte alignment bits)
+          }
+
+          double curTotalCost = curTotalRate * m_lambda[compID] + curTotalDistortion;
+
+          if (curTotalCost < prevTotalCost)
+          {
+            prevTotalCost = curTotalCost;
+            improvement = true;
+          }
+
+          if (curTotalCost < bestFilteredTotalCost)
+          {
+            bestFilteredTotalCost = curTotalCost;
+            memcpy(m_bestFilterIdxEnabled, ccAlfFilterIdxEnabled, sizeof(ccAlfFilterIdxEnabled));
+            memcpy(m_bestFilterCoeffSet, ccAlfFilterCoeff, sizeof(ccAlfFilterCoeff));
+            memcpy(m_bestFilterControl, m_filterControl, sizeof(uint8_t) * m_numCTUsInPic);
+            m_bestFilterCount = ccAlfFilterCount;
+            ccalfReuseApsId = referencingExistingAps ? apsIds[testFilterIdx] : -1;
+            memcpy(bestMapFilterIdxToFilterIdc, mapFilterIdxToFilterIdc, sizeof(mapFilterIdxToFilterIdc));
+          }
+        }
+
+        trainingIterCount++;
+        if (!improvement || trainingIterCount > maxTrainingIterCount || referencingExistingAps)
+        {
+          keepTraining = false;
+        }
+      }
+    }
+  }
+
+  if (bestUnfilteredTotalCost < bestFilteredTotalCost)
+  {
+    memset(m_bestFilterControl, 0, sizeof(uint8_t) * m_numCTUsInPic);
+  }
+
+  // save best coeff and control
+  bool atleastOneBlockUndergoesFitlering = false;
+  for (int controlIdx = 0; m_bestFilterCount > 0 && controlIdx < m_numCTUsInPic; controlIdx++)
+  {
+    if (m_bestFilterControl[controlIdx])
+    {
+      atleastOneBlockUndergoesFitlering = true;
+      break;
+    }
+  }
+  m_ccAlfFilterParam.numberValidComponents          = getNumberValidComponents(m_chromaFormat);
+  m_ccAlfFilterParam.ccAlfFilterEnabled[compID - 1] = atleastOneBlockUndergoesFitlering;
+  if (atleastOneBlockUndergoesFitlering)
+  {
+    // update the filter control indicators
+    if (bestreuseTemporalFilterCoeff!=1)
+    {
+      short storedBestFilterCoeffSet[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF];
+      for (int filterIdx=0; filterIdx<MAX_NUM_CC_ALF_FILTERS; filterIdx++)
+      {
+        memcpy(storedBestFilterCoeffSet[filterIdx], m_bestFilterCoeffSet[filterIdx], sizeof(m_bestFilterCoeffSet[filterIdx]));
+      }
+      memcpy(m_filterControl, m_bestFilterControl, sizeof(uint8_t) * m_numCTUsInPic);
+
+      int filterCount = 0;
+      for ( int filterIdx = 0; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+      {
+        uint8_t curFilterIdc = bestMapFilterIdxToFilterIdc[filterIdx];
+        if (m_bestFilterIdxEnabled[filterIdx])
+        {
+          for (int controlIdx = 0; controlIdx < m_numCTUsInPic; controlIdx++)
+          {
+            if (m_filterControl[controlIdx] == (filterIdx+1) )
+            {
+              m_bestFilterControl[controlIdx] = curFilterIdc;
+            }
+          }
+          memcpy( m_bestFilterCoeffSet[curFilterIdc-1], storedBestFilterCoeffSet[filterIdx], sizeof(storedBestFilterCoeffSet[filterIdx]) );
+          filterCount++;
+        }
+        m_bestFilterIdxEnabled[filterIdx] = ( filterIdx < m_bestFilterCount ) ? true : false;
+      }
+      CHECK( filterCount != m_bestFilterCount, "Number of filters enabled did not match the filter count");
+    }
+
+    m_ccAlfFilterParam.ccAlfFilterCount[compID - 1] = m_bestFilterCount;
+    // cleanup before copying
+    memset(m_ccAlfFilterControl[compID - 1], 0, sizeof(uint8_t) * m_numCTUsInPic);
+    for ( int filterIdx = 0; filterIdx < MAX_NUM_CC_ALF_FILTERS; filterIdx++ )
+    {
+      memset(m_ccAlfFilterParam.ccAlfCoeff[compID - 1][filterIdx], 0,
+             sizeof(m_ccAlfFilterParam.ccAlfCoeff[compID - 1][filterIdx]));
+    }
+    memset(m_ccAlfFilterParam.ccAlfFilterIdxEnabled[compID - 1], false,
+           sizeof(m_ccAlfFilterParam.ccAlfFilterIdxEnabled[compID - 1]));
+    for ( int filterIdx = 0; filterIdx < m_bestFilterCount; filterIdx++ )
+    {
+      m_ccAlfFilterParam.ccAlfFilterIdxEnabled[compID - 1][filterIdx] = m_bestFilterIdxEnabled[filterIdx];
+      memcpy(m_ccAlfFilterParam.ccAlfCoeff[compID - 1][filterIdx], m_bestFilterCoeffSet[filterIdx],
+             sizeof(m_bestFilterCoeffSet[filterIdx]));
+    }
+    memcpy(m_ccAlfFilterControl[compID - 1], m_bestFilterControl, sizeof(uint8_t) * m_numCTUsInPic);
+    if ( ccalfReuseApsId >= 0 )
+    {
+      m_reuseApsId[compID - 1] = ccalfReuseApsId;
+      if (compID == COMPONENT_Cb)
+      {
+        cs.slice->setTileGroupCcAlfCbApsId(ccalfReuseApsId);
+      }
+      else
+      {
+        cs.slice->setTileGroupCcAlfCrApsId(ccalfReuseApsId);
+      }
+    }
+  }
+}
+
+void EncAdaptiveLoopFilter::deriveStatsForCcAlfFiltering(const PelUnitBuf &orgYuv, const PelUnitBuf &recYuv,
+                                                         const int compIdx, const int maskStride,
+                                                         const uint8_t filterIdc, CodingStructure &cs)
+{
+  const int filterIdx = filterIdc - 1;
+
+  // init CTU stats buffers
+  for( int shape = 0; shape != m_filterShapesCcAlf[compIdx-1].size(); shape++ )
+  {
+    for (int ctuIdx = 0; ctuIdx < m_numCTUsInPic; ctuIdx++)
+    {
+      m_alfCovarianceCcAlf[compIdx - 1][shape][filterIdx][ctuIdx].reset();
+    }
+  }
+
+  // init Frame stats buffers
+  for (int shape = 0; shape != m_filterShapesCcAlf[compIdx - 1].size(); shape++)
+  {
+    m_alfCovarianceFrameCcAlf[compIdx - 1][shape][filterIdx].reset();
+  }
+
+  int                  ctuRsAddr = 0;
+  const PreCalcValues &pcv       = *cs.pcv;
+  bool                 clipTop = false, clipBottom = false, clipLeft = false, clipRight = false;
+  int                  numHorVirBndry = 0, numVerVirBndry = 0;
+  int                  horVirBndryPos[] = { 0, 0, 0 };
+  int                  verVirBndryPos[] = { 0, 0, 0 };
+
+  for (int yPos = 0; yPos < m_picHeight; yPos += m_maxCUHeight)
+  {
+    for (int xPos = 0; xPos < m_picWidth; xPos += m_maxCUWidth)
+    {
+      if (m_trainingCovControl[ctuRsAddr] == filterIdc)
+      {
+        const int width             = (xPos + m_maxCUWidth > m_picWidth) ? (m_picWidth - xPos) : m_maxCUWidth;
+        const int height            = (yPos + m_maxCUHeight > m_picHeight) ? (m_picHeight - yPos) : m_maxCUHeight;
+        int       rasterSliceAlfPad = 0;
+        if (isCrossedByVirtualBoundaries(cs, xPos, yPos, width, height, clipTop, clipBottom, clipLeft, clipRight,
+                                         numHorVirBndry, numVerVirBndry, horVirBndryPos, verVirBndryPos,
+                                         rasterSliceAlfPad))
+        {
+          int yStart = yPos;
+          for (int i = 0; i <= numHorVirBndry; i++)
+          {
+            const int  yEnd   = i == numHorVirBndry ? yPos + height : horVirBndryPos[i];
+            const int  h      = yEnd - yStart;
+            const bool clipT  = (i == 0 && clipTop) || (i > 0) || (yStart == 0);
+            const bool clipB  = (i == numHorVirBndry && clipBottom) || (i < numHorVirBndry) || (yEnd == pcv.lumaHeight);
+            int        xStart = xPos;
+            for (int j = 0; j <= numVerVirBndry; j++)
+            {
+              const int  xEnd   = j == numVerVirBndry ? xPos + width : verVirBndryPos[j];
+              const int  w      = xEnd - xStart;
+              const bool clipL  = (j == 0 && clipLeft) || (j > 0) || (xStart == 0);
+              const bool clipR  = (j == numVerVirBndry && clipRight) || (j < numVerVirBndry) || (xEnd == pcv.lumaWidth);
+              const int  wBuf   = w + (clipL ? 0 : MAX_ALF_PADDING_SIZE) + (clipR ? 0 : MAX_ALF_PADDING_SIZE);
+              const int  hBuf   = h + (clipT ? 0 : MAX_ALF_PADDING_SIZE) + (clipB ? 0 : MAX_ALF_PADDING_SIZE);
+              PelUnitBuf recBuf = m_tempBuf2.subBuf(UnitArea(cs.area.chromaFormat, Area(0, 0, wBuf, hBuf)));
+              recBuf.copyFrom(recYuv.subBuf(
+                UnitArea(cs.area.chromaFormat, Area(xStart - (clipL ? 0 : MAX_ALF_PADDING_SIZE),
+                                                    yStart - (clipT ? 0 : MAX_ALF_PADDING_SIZE), wBuf, hBuf))));
+              // pad top-left unavailable samples for raster slice
+              if (xStart == xPos && yStart == yPos && (rasterSliceAlfPad & 1))
+              {
+                recBuf.padBorderPel(MAX_ALF_PADDING_SIZE, 1);
+              }
+
+              // pad bottom-right unavailable samples for raster slice
+              if (xEnd == xPos + width && yEnd == yPos + height && (rasterSliceAlfPad & 2))
+              {
+                recBuf.padBorderPel(MAX_ALF_PADDING_SIZE, 2);
+              }
+              recBuf.extendBorderPel(MAX_ALF_PADDING_SIZE);
+              recBuf = recBuf.subBuf(UnitArea(
+                cs.area.chromaFormat, Area(clipL ? 0 : MAX_ALF_PADDING_SIZE, clipT ? 0 : MAX_ALF_PADDING_SIZE, w, h)));
+
+              const UnitArea area(m_chromaFormat, Area(0, 0, w, h));
+              const UnitArea areaDst(m_chromaFormat, Area(xStart, yStart, w, h));
+
+              const ComponentID compID = ComponentID(compIdx);
+
+              for (int shape = 0; shape != m_filterShapesCcAlf[compIdx - 1].size(); shape++)
+              {
+                getBlkStatsCcAlf(m_alfCovarianceCcAlf[compIdx - 1][0][filterIdx][ctuRsAddr],
+                                 m_filterShapesCcAlf[compIdx - 1][shape], orgYuv, recBuf, areaDst, area, compID, yPos);
+                m_alfCovarianceFrameCcAlf[compIdx - 1][shape][filterIdx] +=
+                  m_alfCovarianceCcAlf[compIdx - 1][shape][filterIdx][ctuRsAddr];
+              }
+
+              xStart = xEnd;
+            }
+
+            yStart = yEnd;
+          }
+        }
+        else
+        {
+          const UnitArea area(m_chromaFormat, Area(xPos, yPos, width, height));
+
+          const ComponentID compID = ComponentID(compIdx);
+
+          for (int shape = 0; shape != m_filterShapesCcAlf[compIdx - 1].size(); shape++)
+          {
+            getBlkStatsCcAlf(m_alfCovarianceCcAlf[compIdx - 1][0][filterIdx][ctuRsAddr],
+                             m_filterShapesCcAlf[compIdx - 1][shape], orgYuv, recYuv, area, area, compID, yPos);
+            m_alfCovarianceFrameCcAlf[compIdx - 1][shape][filterIdx] +=
+              m_alfCovarianceCcAlf[compIdx - 1][shape][filterIdx][ctuRsAddr];
+          }
+        }
+      }
+      ctuRsAddr++;
+    }
+  }
+}
+
+void EncAdaptiveLoopFilter::getBlkStatsCcAlf(AlfCovariance &alfCovariance, const AlfFilterShape &shape,
+                                             const PelUnitBuf &orgYuv, const PelUnitBuf &recYuv,
+                                             const UnitArea &areaDst, const UnitArea &area, const ComponentID compID,
+                                             const int yPos)
+{
+  const int numberOfComponents = getNumberValidComponents( m_chromaFormat );
+  const CompArea &compArea           = areaDst.block(compID);
+  int  recStride[MAX_NUM_COMPONENT];
+  const Pel* rec[MAX_NUM_COMPONENT];
+  for ( int cIdx = 0; cIdx < numberOfComponents; cIdx++ )
+  {
+    recStride[cIdx] = recYuv.get(ComponentID(cIdx)).stride;
+    rec[cIdx] = recYuv.get(ComponentID(cIdx)).bufAt(isLuma(ComponentID(cIdx)) ? area.lumaPos() : area.chromaPos());
+  }
+
+  int        orgStride = orgYuv.get(compID).stride;
+  const Pel *org       = orgYuv.get(compID).bufAt(compArea);
+  const int  numBins   = 1;
+
+  int vbCTUHeight = m_alfVBLumaCTUHeight;
+  int vbPos       = m_alfVBLumaPos;
+  if ((yPos + m_maxCUHeight) >= m_picHeight)
+  {
+    vbPos = m_picHeight;
+  }
+
+  int ELocal[MAX_NUM_CC_ALF_CHROMA_COEFF][1];
+
+  for (int i = 0; i < compArea.height; i++)
+  {
+    int vbDistance = ((i << getComponentScaleX(compID, m_chromaFormat)) % vbCTUHeight) - vbPos;
+    for (int j = 0; j < compArea.width; j++)
+    {
+      std::memset(ELocal, 0, sizeof(ELocal));
+
+      double weight = 1.0;
+      if (m_alfWSSD)
+      {
+        weight = m_lumaLevelToWeightPLUT[org[j]];
+      }
+
+      int yLocal = org[j] - rec[compID][j];
+
+      calcCovarianceCcAlf( ELocal, rec[COMPONENT_Y] + ( j << getComponentScaleX(compID, m_chromaFormat)), recStride[COMPONENT_Y], shape, vbDistance );
+
+      for( int k = 0; k < (shape.numCoeff - 1); k++ )
+      {
+        for( int l = k; l < (shape.numCoeff - 1); l++ )
+        {
+          for( int b0 = 0; b0 < numBins; b0++ )
+          {
+            for (int b1 = 0; b1 < numBins; b1++)
+            {
+              if (m_alfWSSD)
+              {
+                alfCovariance.E[b0][b1][k][l] += weight * (double) (ELocal[k][b0] * ELocal[l][b1]);
+              }
+              else
+              {
+                alfCovariance.E[b0][b1][k][l] += ELocal[k][b0] * ELocal[l][b1];
+              }
+            }
+          }
+        }
+        for (int b = 0; b < numBins; b++)
+        {
+          if (m_alfWSSD)
+          {
+            alfCovariance.y[b][k] += weight * (double) (ELocal[k][b] * yLocal);
+          }
+          else
+          {
+            alfCovariance.y[b][k] += ELocal[k][b] * yLocal;
+          }
+        }
+      }
+      if (m_alfWSSD)
+      {
+        alfCovariance.pixAcc += weight * (double) (yLocal * yLocal);
+      }
+      else
+      {
+        alfCovariance.pixAcc += yLocal * yLocal;
+      }
+    }
+    org += orgStride;
+    for (int srcCIdx = 0; srcCIdx < numberOfComponents; srcCIdx++)
+    {
+      ComponentID srcCompID = ComponentID(srcCIdx);
+      if (toChannelType(srcCompID) == toChannelType(compID))
+      {
+        rec[srcCIdx] += recStride[srcCIdx];
+      }
+      else
+      {
+        if (isLuma(compID))
+        {
+          rec[srcCIdx] += (recStride[srcCIdx] >> getComponentScaleY(srcCompID, m_chromaFormat));
+        }
+        else
+        {
+          rec[srcCIdx] += (recStride[srcCIdx] << getComponentScaleY(compID, m_chromaFormat));
+        }
+      }
+    }
+  }
+
+  for (int k = 1; k < (MAX_NUM_CC_ALF_CHROMA_COEFF - 1); k++)
+  {
+    for (int l = 0; l < k; l++)
+    {
+      for (int b0 = 0; b0 < numBins; b0++)
+      {
+        for (int b1 = 0; b1 < numBins; b1++)
+        {
+          alfCovariance.E[b0][b1][k][l] = alfCovariance.E[b1][b0][l][k];
+        }
+      }
+    }
+  }
+}
+
+void EncAdaptiveLoopFilter::calcCovarianceCcAlf(int ELocal[MAX_NUM_CC_ALF_CHROMA_COEFF][1], const Pel *rec, const int stride, const AlfFilterShape& shape, int vbDistance)
+{
+  CHECK(shape.filterType != CC_ALF, "Bad CC ALF shape");
+
+  const Pel *recYM1 = rec - 1 * stride;
+  const Pel *recY0  = rec;
+  const Pel *recYP1 = rec + 1 * stride;
+  const Pel *recYP2 = rec + 2 * stride;
+
+  if (vbDistance == -2 || vbDistance == +1)
+  {
+    recYP2 = recYP1;
+  }
+  else if (vbDistance == -1 || vbDistance == 0)
+  {
+    recYM1 = recY0;
+    recYP2 = recYP1 = recY0;
+  }
+
+  for (int b = 0; b < 1; b++)
+  {
+    const Pel centerValue = recY0[+0];
+    ELocal[0][b] += recYM1[+0] - centerValue;
+    ELocal[1][b] += recY0[-1] - centerValue;
+    ELocal[2][b] += recY0[+1] - centerValue;
+    ELocal[3][b] += recYP1[-1] - centerValue;
+    ELocal[4][b] += recYP1[+0] - centerValue;
+    ELocal[5][b] += recYP1[+1] - centerValue;
+    ELocal[6][b] += recYP2[+0] - centerValue;
+  }
+}
+
+void EncAdaptiveLoopFilter::countLumaSwingGreaterThanThreshold(const Pel* luma, int lumaStride, int height, int width, int log2BlockWidth, int log2BlockHeight, uint64_t* lumaSwingGreaterThanThresholdCount, int lumaCountStride)
+{
+  const int lumaBitDepth = m_inputBitDepth[CH_L];
+  const int threshold = (1 << ( m_inputBitDepth[CH_L] - 2 )) - 1;
+
+  // 3x4 Diamond
+  int xSupport[] = {  0, -1, 0, 1, -1, 0, 1, 0 };
+  int ySupport[] = { -1,  0, 0, 0,  1, 1, 1, 2 };
+
+  for (int y = 0; y < height; y += (1 << log2BlockHeight))
+  {
+    for (int x = 0; x < width; x += (1 << log2BlockWidth))
+    {
+      lumaSwingGreaterThanThresholdCount[(y >> log2BlockHeight) * lumaCountStride + (x >> log2BlockWidth)] = 0;
+
+      for (int yOff = 0; yOff < (1 << log2BlockHeight); yOff++)
+      {
+        for (int xOff = 0; xOff < (1 << log2BlockWidth); xOff++)
+        {
+          if ((y + yOff) >= (height - 2) || (x + xOff) >= (width - 1) || (y + yOff) < 1 || (x + xOff) < 1) // only consider samples that are fully supported by picture
+          {
+            continue;
+          }
+
+          int minVal = ((1 << lumaBitDepth) - 1);
+          int maxVal = 0;
+          for (int i = 0; i < 8; i++)
+          {
+            Pel p = luma[(yOff + ySupport[i]) * lumaStride + x + xOff + xSupport[i]];
+
+            if ( p < minVal )
+            {
+              minVal = p;
+            }
+            if ( p > maxVal )
+            {
+              maxVal = p;
+            }
+          }
+
+          if ((maxVal - minVal) > threshold)
+          {
+            lumaSwingGreaterThanThresholdCount[(y >> log2BlockHeight) * lumaCountStride + (x >> log2BlockWidth)]++;
+          }
+        }
+      }
+    }
+    luma += (lumaStride << log2BlockHeight);
+  }
+}
+
+void EncAdaptiveLoopFilter::countChromaSampleValueNearMidPoint(const Pel* chroma, int chromaStride, int height, int width, int log2BlockWidth, int log2BlockHeight, uint64_t* chromaSampleCountNearMidPoint, int chromaSampleCountNearMidPointStride)
+{
+  const int midPoint  = (1 << m_inputBitDepth[CH_C]) >> 1;
+  const int threshold = 16;
+
+  for (int y = 0; y < height; y += (1 << log2BlockHeight))
+  {
+    for (int x = 0; x < width; x += (1 << log2BlockWidth))
+    {
+      chromaSampleCountNearMidPoint[(y >> log2BlockHeight)* chromaSampleCountNearMidPointStride + (x >> log2BlockWidth)] = 0;
+
+      for (int yOff = 0; yOff < (1 << log2BlockHeight); yOff++)
+      {
+        for (int xOff = 0; xOff < (1 << log2BlockWidth); xOff++)
+        {
+          if ((y + yOff) >= height || (x + xOff) >= width)
+          {
+            continue;
+          }
+
+          int distanceToMidPoint = abs(chroma[yOff * chromaStride + x + xOff] - midPoint);
+          if (distanceToMidPoint < threshold)
+          {
+            chromaSampleCountNearMidPoint[(y >> log2BlockHeight)* chromaSampleCountNearMidPointStride + (x >> log2BlockWidth)]++;
+          }
+        }
+      }
+    }
+    chroma += (chromaStride << log2BlockHeight);
+  }
+}
+#endif
diff --git a/source/Lib/EncoderLib/EncAdaptiveLoopFilter.h b/source/Lib/EncoderLib/EncAdaptiveLoopFilter.h
index d2ceb026f..5c620b053 100644
--- a/source/Lib/EncoderLib/EncAdaptiveLoopFilter.h
+++ b/source/Lib/EncoderLib/EncAdaptiveLoopFilter.h
@@ -202,15 +202,24 @@ struct AlfCovariance
   double calculateError( const int *clip, const double *coeff ) const { return calculateError(clip, coeff, numCoeff); }
   double calculateError( const int *clip, const double *coeff, const int numCoeff ) const;
   double calcErrorForCoeffs( const int *clip, const int *coeff, const int numCoeff, const int bitDepth ) const;
+#if JVET_Q0795_CCALF
+  double calcErrorForCcAlfCoeffs(const int* coeff, const int numCoeff, const int bitDepth) const;
+#endif
 
   void getClipMax(const AlfFilterShape& alfShape, int *clip_max) const;
   void reduceClipCost(const AlfFilterShape& alfShape, int *clip) const;
 
+#if JVET_Q0795_CCALF
+  int  gnsSolveByChol( TE LHS, double* rhs, double *x, int numEq ) const;
+#endif
+
 private:
   // Cholesky decomposition
 
   int  gnsSolveByChol( const int *clip, double *x, int numEq ) const;
+#if !JVET_Q0795_CCALF
   int  gnsSolveByChol( TE LHS, double* rhs, double *x, int numEq ) const;
+#endif
   void gnsBacksubstitution( TE R, double* z, int size, double* A ) const;
   void gnsTransposeBacksubstitution( TE U, double* rhs, double* x, int order ) const;
   int  gnsCholeskyDec( TE inpMatr, TE outMatr, int numEq ) const;
@@ -231,6 +240,10 @@ private:
   uint8_t*               m_ctuEnableFlagTmp[MAX_NUM_COMPONENT];
   uint8_t*               m_ctuEnableFlagTmp2[MAX_NUM_COMPONENT];
   uint8_t*               m_ctuAlternativeTmp[MAX_NUM_COMPONENT];
+#if JVET_Q0795_CCALF
+  AlfCovariance***       m_alfCovarianceCcAlf[2];           // [compIdx-1][shapeIdx][ctbAddr][filterIdx]
+  AlfCovariance**        m_alfCovarianceFrameCcAlf[2];      // [compIdx-1][shapeIdx][filterIdx]
+#endif
 
   //for RDO
   AlfParam               m_alfParamTemp;
@@ -255,6 +268,25 @@ private:
   int                    m_filterTmp[MAX_NUM_ALF_LUMA_COEFF];
   int                    m_clipTmp[MAX_NUM_ALF_LUMA_COEFF];
 
+#if JVET_Q0795_CCALF
+  int m_apsIdCcAlfStart[2];
+
+  short                  m_bestFilterCoeffSet[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF];
+  bool                   m_bestFilterIdxEnabled[MAX_NUM_CC_ALF_FILTERS];
+  uint8_t                m_bestFilterCount;
+  uint8_t*               m_trainingCovControl;
+  Pel*                   m_bufOrigin;
+  PelBuf*                m_buf;
+  uint64_t**             m_unfilteredDistortion;  // for different block size
+  uint64_t*              m_trainingDistortion[MAX_NUM_CC_ALF_FILTERS];    // for current block size
+  uint64_t*              m_lumaSwingGreaterThanThresholdCount;
+  uint64_t*              m_chromaSampleCountNearMidPoint;
+  uint8_t*               m_filterControl;         // current iterations filter control
+  uint8_t*               m_bestFilterControl;     // best saved filter control
+  int                    m_reuseApsId[2];
+  bool                   m_limitCcAlf;
+#endif
+
 public:
   EncAdaptiveLoopFilter( int& apsIdStart );
   virtual ~EncAdaptiveLoopFilter() {}
@@ -271,6 +303,9 @@ public:
     , const double lambdaChromaWeight
 #endif
   );
+#if JVET_Q0795_CCALF
+  int getNewCcAlfApsId(CodingStructure &cs, int cIdx);
+#endif
   void initCABACEstimator( CABACEncoder* cabacEncoder, CtxCache* ctxCache, Slice* pcSlice, ParameterSetMap<APS>* apsMap );
   void create( const EncCfg* encCfg, const int picWidth, const int picHeight, const ChromaFormat chromaFormatIDC, const int maxCUWidth, const int maxCUHeight, const int maxCUDepth, const int inputBitDepth[MAX_NUM_CHANNEL_TYPE], const int internalBitDepth[MAX_NUM_CHANNEL_TYPE] );
   void destroy();
@@ -292,6 +327,14 @@ private:
   void   deriveStatsForFiltering( PelUnitBuf& orgYuv, PelUnitBuf& recYuv, CodingStructure& cs );
   void   getBlkStats(AlfCovariance* alfCovariace, const AlfFilterShape& shape, AlfClassifier** classifier, Pel* org, const int orgStride, Pel* rec, const int recStride, const CompArea& areaDst, const CompArea& area, const ChannelType channel, int vbCTUHeight, int vbPos);
   void   calcCovariance(int ELocal[MAX_NUM_ALF_LUMA_COEFF][MaxAlfNumClippingValues], const Pel *rec, const int stride, const AlfFilterShape& shape, const int transposeIdx, const ChannelType channel, int vbDistance);
+#if JVET_Q0795_CCALF
+  void   deriveStatsForCcAlfFiltering(const PelUnitBuf &orgYuv, const PelUnitBuf &recYuv, const int compIdx,
+                                      const int maskStride, const uint8_t filterIdc, CodingStructure &cs);
+  void   getBlkStatsCcAlf(AlfCovariance &alfCovariance, const AlfFilterShape &shape, const PelUnitBuf &orgYuv,
+                          const PelUnitBuf &recYuv, const UnitArea &areaDst, const UnitArea &area,
+                          const ComponentID compID, const int yPos);
+  void   calcCovarianceCcAlf(int ELocal[MAX_NUM_CC_ALF_CHROMA_COEFF][1], const Pel* rec, const int stride, const AlfFilterShape& shape, int vbDistance);
+#endif
   void   mergeClasses(const AlfFilterShape& alfShape, AlfCovariance* cov, AlfCovariance* covMerged, int clipMerged[MAX_NUM_ALF_CLASSES][MAX_NUM_ALF_CLASSES][MAX_NUM_ALF_LUMA_COEFF], const int numClasses, short filterIndices[MAX_NUM_ALF_CLASSES][MAX_NUM_ALF_CLASSES]);
 
 
@@ -305,6 +348,9 @@ private:
 #endif
                                   const int numClasses, const int numCoeff, double& distUnfilter );
   void   roundFiltCoeff( int *filterCoeffQuant, double *filterCoeff, const int numCoeff, const int factor );
+#if JVET_Q0795_CCALF
+  void   roundFiltCoeffCCALF( int *filterCoeffQuant, double *filterCoeff, const int numCoeff, const int factor );
+#endif
 
   double getDistCoeffForce0( bool* codedVarBins, double errorForce0CoeffTab[MAX_NUM_ALF_CLASSES][2], int* bitsVarBin, int zeroBitsVarBin, const int numFilters);
   int    lengthUvlc( int uiCode );
@@ -329,6 +375,28 @@ private:
   void setCtuAlternativeChroma( uint8_t* ctuAlts[MAX_NUM_COMPONENT], uint8_t val );
   void copyCtuAlternativeChroma( uint8_t* ctuAltsDst[MAX_NUM_COMPONENT], uint8_t* ctuAltsSrc[MAX_NUM_COMPONENT] );
   int getMaxNumAlternativesChroma( );
+#if JVET_Q0795_CCALF
+  int  getCoeffRateCcAlf(short chromaCoeff[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF], bool filterEnabled[MAX_NUM_CC_ALF_FILTERS], uint8_t filterCount, ComponentID compID);
+  void deriveCcAlfFilterCoeff( ComponentID compID, const PelUnitBuf& recYuv, const PelUnitBuf& recYuvExt, short filterCoeff[MAX_NUM_CC_ALF_FILTERS][MAX_NUM_CC_ALF_CHROMA_COEFF], const uint8_t filterIdx );
+  void computeLog2BlockSizeDistortion(const Pel *org, int orgStride, const Pel *dec, int decStride, int height,
+                                      int width, uint64_t *distortionBuf, int distortionBufStride, int log2BlockWidth,
+                                      int log2BlockHeight, uint64_t &totalDistortion);
+  void determineControlIdcValues(CodingStructure &cs, const ComponentID compID, const PelBuf *buf, const int ctuWidthC,
+                                 const int ctuHeightC, const int picWidthC, const int picHeightC,
+                                 uint64_t **unfilteredDistortion, uint64_t *trainingDistortion[MAX_NUM_CC_ALF_FILTERS],
+                                 uint64_t *lumaSwingGreaterThanThresholdCount,
+                                 uint64_t *chromaSampleCountNearMidPoint,
+                                 bool reuseFilterCoeff, uint8_t *trainingCovControl, uint8_t *filterControl,
+                                 uint64_t &curTotalDistortion, double &curTotalRate,
+                                 bool     filterEnabled[MAX_NUM_CC_ALF_FILTERS],
+                                 uint8_t  mapFilterIdxToFilterIdc[MAX_NUM_CC_ALF_FILTERS + 1],
+                                 uint8_t &ccAlfFilterCount);
+  void deriveCcAlfFilter( CodingStructure& cs, ComponentID compID, const PelUnitBuf& orgYuv, const PelUnitBuf& tempDecYuvBuf, const PelUnitBuf& dstYuv );
+  std::vector<int> getAvailableCcAlfApsIds(CodingStructure& cs, ComponentID compID);
+  void xSetupCcAlfAPS( CodingStructure& cs );
+  void countLumaSwingGreaterThanThreshold(const Pel* luma, int lumaStride, int height, int width, int log2BlockWidth, int log2BlockHeight, uint64_t* lumaSwingGreaterThanThresholdCount, int lumaCountStride);
+  void countChromaSampleValueNearMidPoint(const Pel* chroma, int chromaStride, int height, int width, int log2BlockWidth, int log2BlockHeight, uint64_t* chromaSampleCountNearMidPoint, int chromaSampleCountNearMidPointStride);
+#endif
 };
 
 
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index f27bb3c10..ceadd3249 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -171,6 +171,9 @@ protected:
   bool      m_noPartitionConstraintsOverrideConstraintFlag;
   bool      m_bNoSaoConstraintFlag;
   bool      m_bNoAlfConstraintFlag;
+#if JVET_Q0795_CCALF
+  bool      m_noCCAlfConstraintFlag;
+#endif
   bool      m_bNoRefWraparoundConstraintFlag;
   bool      m_bNoTemporalMvpConstraintFlag;
   bool      m_bNoSbtmvpConstraintFlag;
@@ -683,6 +686,10 @@ protected:
 #endif
 
   bool        m_alf;                                          ///< Adaptive Loop Filter
+#if JVET_Q0795_CCALF
+  bool        m_ccalf;
+  int         m_ccalfQpThreshold;
+#endif
 #if JVET_O0756_CALCULATE_HDRMETRICS
   double                       m_whitePointDeltaE[hdrtoolslib::NB_REF_WHITE];
   double                       m_maxSampleValue;
@@ -732,6 +739,10 @@ public:
   void      setNoSaoConstraintFlag(bool bVal) { m_bNoSaoConstraintFlag = bVal; }
   bool      getNoAlfConstraintFlag() const { return m_bNoAlfConstraintFlag; }
   void      setNoAlfConstraintFlag(bool bVal) { m_bNoAlfConstraintFlag = bVal; }
+#if JVET_Q0795_CCALF
+  bool      getNoCCAlfConstraintFlag() const { return m_noCCAlfConstraintFlag; }
+  void      setNoCCAlfConstraintFlag(bool bVal) { m_noCCAlfConstraintFlag = bVal; }
+#endif
   bool      getNoRefWraparoundConstraintFlag() const { return m_bNoRefWraparoundConstraintFlag; }
   void      setNoRefWraparoundConstraintFlag(bool bVal) { m_bNoRefWraparoundConstraintFlag = bVal; }
   bool      getNoTemporalMvpConstraintFlag() const { return m_bNoTemporalMvpConstraintFlag; }
@@ -1770,7 +1781,12 @@ public:
 #endif
   void         setUseALF( bool b ) { m_alf = b; }
   bool         getUseALF()                                      const { return m_alf; }
-
+#if JVET_Q0795_CCALF
+  void         setUseCCALF( bool b )                                  { m_ccalf = b; }
+  bool         getUseCCALF()                                    const { return m_ccalf; }
+  void         setCCALFQpThreshold( int b )                           { m_ccalfQpThreshold = b; }
+  int          getCCALFQpThreshold()                            const { return m_ccalfQpThreshold; }
+#endif
 #if JVET_O0756_CALCULATE_HDRMETRICS
   void        setWhitePointDeltaE( uint32_t index, double value )     { m_whitePointDeltaE[ index ] = value; }
   double      getWhitePointDeltaE( uint32_t index )             const { return m_whitePointDeltaE[ index ]; }
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index 0bc3ea911..62d57e16c 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -2788,7 +2788,9 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
       m_pcLoopFilter->loopFilterPic( cs );
 
       CS::setRefinedMotionField(cs);
+#if !JVET_Q0795_CCALF
       DTRACE_UPDATE( g_trace_ctx, ( std::make_pair( "final", 1 ) ) );
+#endif
 
       if( pcSlice->getSPS()->getSAOEnabledFlag() )
       {
@@ -2842,8 +2844,15 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
           }
           pcPic->slices[s]->setAlfAPSs(cs.slice->getAlfAPSs());
           pcPic->slices[s]->setTileGroupApsIdChroma(cs.slice->getTileGroupApsIdChroma());
+#if JVET_Q0795_CCALF
+          pcPic->slices[s]->setTileGroupCcAlfCbApsId(cs.slice->getTileGroupCcAlfCbApsId());
+          pcPic->slices[s]->setTileGroupCcAlfCrApsId(cs.slice->getTileGroupCcAlfCrApsId());
+#endif
         }
       }
+#if JVET_Q0795_CCALF
+      DTRACE_UPDATE( g_trace_ctx, ( std::make_pair( "final", 1 ) ) );
+#endif
       if (m_pcCfg->getUseCompositeRef() && getPrepareLTRef())
       {
         updateCompositeReference(pcSlice, rcListPic, pocCurr);
@@ -2949,7 +2958,11 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
         }
       }
 
+#if JVET_Q0795_CCALF
+      if (pcSlice->getSPS()->getALFEnabledFlag() && (pcSlice->getTileGroupAlfEnabledFlag(COMPONENT_Y) || pcSlice->getTileGroupCcAlfCbEnabledFlag() || pcSlice->getTileGroupCcAlfCrEnabledFlag()))
+#else
       if (pcSlice->getSPS()->getALFEnabledFlag() && pcSlice->getTileGroupAlfEnabledFlag(COMPONENT_Y))
+#endif
       {
         for (int apsId = 0; apsId < ALF_CTB_MAX_NUM_APS; apsId++)
         {
@@ -2964,12 +2977,28 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
             *apsMap->allocatePS(apsId) = *aps; //allocate and cpy
             m_pcALF->setApsIdStart( apsId );
           }
+#if JVET_Q0795_CCALF
+          else if (pcSlice->getTileGroupCcAlfCbEnabledFlag() && !aps && apsId == pcSlice->getTileGroupCcAlfCbApsId())
+          {
+            writeAPS = true;
+            aps = apsMap->getPS((pcSlice->getTileGroupCcAlfCbApsId() << NUM_APS_TYPE_LEN) + ALF_APS);
+          }
+          else if (pcSlice->getTileGroupCcAlfCrEnabledFlag() && !aps && apsId == pcSlice->getTileGroupCcAlfCrApsId())
+          {
+            writeAPS = true;
+            aps = apsMap->getPS((pcSlice->getTileGroupCcAlfCrApsId() << NUM_APS_TYPE_LEN) + ALF_APS);
+          }
+#endif
 
           if (writeAPS )
           {
             actualTotalBits += xWriteAPS( accessUnit, aps, m_pcEncLib->getLayerId(), true );
             apsMap->clearChangedFlag((apsId << NUM_APS_TYPE_LEN) + ALF_APS);
+#if JVET_Q0795_CCALF
+            CHECK(aps != pcSlice->getAlfAPSs()[apsId] && apsId != pcSlice->getTileGroupCcAlfCbApsId() && apsId != pcSlice->getTileGroupCcAlfCrApsId(), "Wrong APS pointer in compressGOP");
+#else
             CHECK(aps != pcSlice->getAlfAPSs()[apsId], "Wrong APS pointer in compressGOP");
+#endif
           }
         }
       }
@@ -3067,6 +3096,12 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
             picHeader->setNumAlfAps(pcSlice->getTileGroupNumAps());
             picHeader->setAlfAPSs(pcSlice->getTileGroupApsIdLuma());
             picHeader->setAlfApsIdChroma(pcSlice->getTileGroupApsIdChroma());
+#if JVET_Q0795_CCALF
+            picHeader->setCcAlfEnabledFlag(COMPONENT_Cb, pcSlice->getTileGroupCcAlfCbEnabledFlag());
+            picHeader->setCcAlfEnabledFlag(COMPONENT_Cr, pcSlice->getTileGroupCcAlfCrEnabledFlag());
+            picHeader->setCcAlfCbApsId(pcSlice->getTileGroupCcAlfCbApsId());
+            picHeader->setCcAlfCrApsId(pcSlice->getTileGroupCcAlfCrApsId());
+#endif
           }
           else {
             picHeader->setAlfEnabledPresentFlag( false );
@@ -3089,6 +3124,11 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
 
 
         tmpBitsBeforeWriting = m_HLSWriter->getNumberOfWrittenBits();
+#if JVET_Q0795_CCALF
+        pcSlice->m_ccAlfFilterParam      = m_pcALF->getCcAlfFilterParam();
+        pcSlice->m_ccAlfFilterControl[0] = m_pcALF->getCcAlfControlIdc(COMPONENT_Cb);
+        pcSlice->m_ccAlfFilterControl[1] = m_pcALF->getCcAlfControlIdc(COMPONENT_Cr);
+#endif
         m_HLSWriter->codeSliceHeader( pcSlice );
         actualHeadBits += ( m_HLSWriter->getNumberOfWrittenBits() - tmpBitsBeforeWriting );
 
diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp
index 3eb5c0846..834b3e98b 100644
--- a/source/Lib/EncoderLib/EncLib.cpp
+++ b/source/Lib/EncoderLib/EncLib.cpp
@@ -976,6 +976,9 @@ void EncLib::xInitSPS( SPS& sps, VPS& vps )
   cinfo->setNoPartitionConstraintsOverrideConstraintFlag(m_noPartitionConstraintsOverrideConstraintFlag);
   cinfo->setNoSaoConstraintFlag(m_bNoSaoConstraintFlag);
   cinfo->setNoAlfConstraintFlag(m_bNoAlfConstraintFlag);
+#if JVET_Q0795_CCALF
+  cinfo->setNoCCAlfConstraintFlag(m_noCCAlfConstraintFlag);
+#endif
   cinfo->setNoRefWraparoundConstraintFlag(m_bNoRefWraparoundConstraintFlag);
   cinfo->setNoTemporalMvpConstraintFlag(m_bNoTemporalMvpConstraintFlag);
   cinfo->setNoSbtmvpConstraintFlag(m_bNoSbtmvpConstraintFlag);
@@ -1146,6 +1149,9 @@ void EncLib::xInitSPS( SPS& sps, VPS& vps )
 
   sps.setScalingListFlag ( (m_useScalingListId == SCALING_LIST_OFF) ? 0 : 1 );
   sps.setALFEnabledFlag( m_alf );
+#if JVET_Q0795_CCALF
+  sps.setCCALFEnabledFlag( m_ccalf );
+#endif
   sps.setVuiParametersPresentFlag(getVuiParametersPresentFlag());
 
   if (sps.getVuiParametersPresentFlag())
diff --git a/source/Lib/EncoderLib/VLCWriter.cpp b/source/Lib/EncoderLib/VLCWriter.cpp
index 4a1fdcc80..2c97b4d44 100644
--- a/source/Lib/EncoderLib/VLCWriter.cpp
+++ b/source/Lib/EncoderLib/VLCWriter.cpp
@@ -515,6 +515,12 @@ void HLSWriter::codeAlfAps( APS* pcAPS )
   WRITE_FLAG(param.newFilterFlag[CHANNEL_TYPE_LUMA], "alf_luma_new_filter");
   WRITE_FLAG(param.newFilterFlag[CHANNEL_TYPE_CHROMA], "alf_chroma_new_filter");
 
+#if JVET_Q0795_CCALF
+  CcAlfFilterParam paramCcAlf = pcAPS->getCcAlfAPSParam();
+  WRITE_FLAG(paramCcAlf.newCcAlfFilter[COMPONENT_Cb - 1], "alf_cc_cb_filter_signal_flag");
+  WRITE_FLAG(paramCcAlf.newCcAlfFilter[COMPONENT_Cr - 1], "alf_cc_cr_filter_signal_flag");
+#endif
+
   if (param.newFilterFlag[CHANNEL_TYPE_LUMA])
   {
 #if JVET_Q0249_ALF_CHROMA_CLIPFLAG
@@ -550,6 +556,52 @@ void HLSWriter::codeAlfAps( APS* pcAPS )
       alfFilter(param, true, altIdx);
     }
   }
+#if JVET_Q0795_CCALF
+  for (int ccIdx = 0; ccIdx < 2; ccIdx++)
+  {
+    if (paramCcAlf.newCcAlfFilter[ccIdx])
+    {
+      const int filterCount = paramCcAlf.ccAlfFilterCount[ccIdx];
+      CHECK(filterCount > MAX_NUM_CC_ALF_FILTERS, "CC ALF Filter count is too large");
+      CHECK(filterCount == 0, "CC ALF Filter count is too small");
+
+      if (MAX_NUM_CC_ALF_FILTERS > 1)
+      {
+        WRITE_UVLC(filterCount - 1,
+                   ccIdx == 0 ? "alf_cc_cb_filters_signalled_minus1" : "alf_cc_cr_filters_signalled_minus1");
+      }
+
+      for (int filterIdx = 0; filterIdx < filterCount; filterIdx++)
+      {
+        AlfFilterShape alfShape(size_CC_ALF);
+
+        const short *coeff = paramCcAlf.ccAlfCoeff[ccIdx][filterIdx];
+        // Filter coefficients
+        for (int i = 0; i < alfShape.numCoeff - 1; i++)
+        {
+          if (coeff[i] == 0)
+          {
+            WRITE_CODE(0, CCALF_BITS_PER_COEFF_LEVEL,
+                       ccIdx == 0 ? "alf_cc_cb_mapped_coeff_abs" : "alf_cc_cr_mapped_coeff_abs");
+          }
+          else
+          {
+            WRITE_CODE(1 + floorLog2(abs(coeff[i])), CCALF_BITS_PER_COEFF_LEVEL,
+                       ccIdx == 0 ? "alf_cc_cb_mapped_coeff_abs" : "alf_cc_cr_mapped_coeff_abs");
+            WRITE_FLAG(coeff[i] < 0 ? 1 : 0, ccIdx == 0 ? "alf_cc_cb_coeff_sign" : "alf_cc_cr_coeff_sign");
+          }
+        }
+
+        DTRACE(g_trace_ctx, D_SYNTAX, "%s coeff filterIdx %d: ", ccIdx == 0 ? "Cb" : "Cr", filterIdx);
+        for (int i = 0; i < alfShape.numCoeff; i++)
+        {
+          DTRACE(g_trace_ctx, D_SYNTAX, "%d ", coeff[i]);
+        }
+        DTRACE(g_trace_ctx, D_SYNTAX, "\n");
+      }
+    }
+  }
+#endif
 }
 
 void HLSWriter::codeLmcsAps( APS* pcAPS )
@@ -864,6 +916,12 @@ void HLSWriter::codeSPS( const SPS* pcSPS )
 
   WRITE_FLAG( pcSPS->getSAOEnabledFlag(),                                            "sps_sao_enabled_flag");
   WRITE_FLAG( pcSPS->getALFEnabledFlag(),                                            "sps_alf_enabled_flag" );
+#if JVET_Q0795_CCALF
+  if (pcSPS->getALFEnabledFlag() && pcSPS->getChromaFormatIdc() != CHROMA_400)
+  {
+    WRITE_FLAG( pcSPS->getCCALFEnabledFlag(),                                            "sps_ccalf_enabled_flag" );
+  }
+#endif
 
   WRITE_FLAG(pcSPS->getTransformSkipEnabledFlag() ? 1 : 0, "sps_transform_skip_enabled_flag");
   if (pcSPS->getTransformSkipEnabledFlag())
@@ -1597,6 +1655,21 @@ void HLSWriter::codePictureHeader( PicHeader* picHeader )
         {
           WRITE_CODE(picHeader->getAlfApsIdChroma(), 3, "pic_alf_aps_id_chroma");
         }
+#if JVET_Q0795_CCALF
+        if (sps->getCCALFEnabledFlag())
+        {
+          WRITE_FLAG(picHeader->getCcAlfEnabledFlag(COMPONENT_Cb), "ph_cc_alf_cb_enabled_flag");
+          if (picHeader->getCcAlfEnabledFlag(COMPONENT_Cb))
+          {
+            WRITE_CODE(picHeader->getCcAlfCbApsId(), 3, "ph_cc_alf_cb_aps_id");
+          }
+          WRITE_FLAG(picHeader->getCcAlfEnabledFlag(COMPONENT_Cr), "ph_cc_alf_cr_enabled_flag");
+          if (picHeader->getCcAlfEnabledFlag(COMPONENT_Cr))
+          {
+            WRITE_CODE(picHeader->getCcAlfCrApsId(), 3, "ph_cc_alf_cr_aps_id");
+          }
+        }
+#endif
       }
     }
     else 
@@ -1604,6 +1677,10 @@ void HLSWriter::codePictureHeader( PicHeader* picHeader )
       picHeader->setAlfEnabledFlag(COMPONENT_Y,  true);
       picHeader->setAlfEnabledFlag(COMPONENT_Cb, true);
       picHeader->setAlfEnabledFlag(COMPONENT_Cr, true);
+#if JVET_Q0795_CCALF
+      picHeader->setCcAlfEnabledFlag(COMPONENT_Cb, sps->getCCALFEnabledFlag());
+      picHeader->setCcAlfEnabledFlag(COMPONENT_Cr, sps->getCCALFEnabledFlag());
+#endif
     }
   }
   else 
@@ -1611,6 +1688,10 @@ void HLSWriter::codePictureHeader( PicHeader* picHeader )
     picHeader->setAlfEnabledFlag(COMPONENT_Y,  false);
     picHeader->setAlfEnabledFlag(COMPONENT_Cb, false);
     picHeader->setAlfEnabledFlag(COMPONENT_Cr, false);
+#if JVET_Q0795_CCALF
+    picHeader->setCcAlfEnabledFlag(COMPONENT_Cb, false);
+    picHeader->setCcAlfEnabledFlag(COMPONENT_Cr, false);
+#endif
   }
 
   // dependent quantization
@@ -2030,6 +2111,26 @@ void HLSWriter::codeSliceHeader         ( Slice* pcSlice )
         {
           WRITE_CODE(pcSlice->getTileGroupApsIdChroma(), 3, "slice_alf_aps_id_chroma");
         }
+
+#if JVET_Q0795_CCALF
+        if (pcSlice->getSPS()->getCCALFEnabledFlag())
+        {
+          CcAlfFilterParam &filterParam = pcSlice->m_ccAlfFilterParam;
+          WRITE_FLAG(filterParam.ccAlfFilterEnabled[COMPONENT_Cb - 1] ? 1 : 0, "slice_cc_alf_cb_enabled_flag");
+          if (filterParam.ccAlfFilterEnabled[COMPONENT_Cb - 1])
+          {
+            // write CC ALF Cb APS ID
+            WRITE_CODE(pcSlice->getTileGroupCcAlfCbApsId(), 3, "slice_cc_alf_cb_aps_id");
+          }
+          // Cr
+          WRITE_FLAG(filterParam.ccAlfFilterEnabled[COMPONENT_Cr - 1] ? 1 : 0, "slice_cc_alf_cr_enabled_flag");
+          if (filterParam.ccAlfFilterEnabled[COMPONENT_Cr - 1])
+          {
+            // write CC ALF Cr APS ID
+            WRITE_CODE(pcSlice->getTileGroupCcAlfCrApsId(), 3, "slice_cc_alf_cr_aps_id");
+          }
+        }
+#endif
       }
     }
 
@@ -2088,6 +2189,9 @@ void  HLSWriter::codeConstraintInfo  ( const ConstraintInfo* cinfo )
   WRITE_FLAG(cinfo->getNoPartitionConstraintsOverrideConstraintFlag() ? 1 : 0, "no_partition_constraints_override_constraint_flag");
   WRITE_FLAG(cinfo->getNoSaoConstraintFlag() ? 1 : 0, "no_sao_constraint_flag");
   WRITE_FLAG(cinfo->getNoAlfConstraintFlag() ? 1 : 0, "no_alf_constraint_flag");
+#if JVET_Q0795_CCALF
+  WRITE_FLAG(cinfo->getNoCCAlfConstraintFlag() ? 1 : 0, "no_ccalf_constraint_flag");
+#endif
   WRITE_FLAG(cinfo->getNoJointCbCrConstraintFlag() ? 1 : 0, "no_joint_cbcr_constraint_flag");
   WRITE_FLAG(cinfo->getNoRefWraparoundConstraintFlag() ? 1 : 0, "no_ref_wraparound_constraint_flag");
   WRITE_FLAG(cinfo->getNoTemporalMvpConstraintFlag() ? 1 : 0, "no_temporal_mvp_constraint_flag");
-- 
GitLab