diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index 186dc7925e7fe2b371e75e3a654dfadf08966154..7fb8e912451565566a46ba98e4ebc1a00826bef4 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -804,6 +804,18 @@ static const int ADAPTIVE_SUB_GROUP_SIZE_MMVD =   MMVD_MAX_REFINE_NUM;
 static const int ADAPTIVE_SUB_GROUP_SIZE_MMVD_AFF = AF_MMVD_MAX_REFINE_NUM;
 #endif
 
+#if JVET_AA0057_CCCM
+static const int CCCM_WINDOW_SIZE         = 6;
+static const int CCCM_NUM_PARAMS          = 7;
+static const int CCCM_MIN_PU_SIZE         = 0; // Set to 0 for no size restriction
+static const int CCCM_REF_LINES_ABOVE_CTU = 0; // Number of chroma lines allowed to be included in the reference area above the CTU (0: no restrictions)
+static const int CCCM_FILTER_PADDING      = 1; // E.g. 3x3 filter needs one padded sample
+static const int CCCM_MAX_REF_SAMPLES     = ( 2 * CCCM_WINDOW_SIZE * ( 2 * MAX_CU_SIZE + CCCM_WINDOW_SIZE ) );
+static const int CCCM_MATRIX_BITS         = 28;
+static const int CCCM_DECIM_BITS          = 22;
+static const int CCCM_DECIM_ROUND         = ( 1 << (CCCM_DECIM_BITS - 1 ) );
+#endif
+
 #if JVET_Y0152_TT_ENC_SPEEDUP
 static constexpr int   FAST_METHOD_TT_ENC_SPEEDUP = 0x0001;  ///< Embedding flag, which, if false, de-activates all the following ABT_ENC_SPEEDUP_* modes
 static constexpr int FAST_METHOD_HOR_XOR_VER = 0x0002;
diff --git a/source/Lib/CommonLib/Contexts.cpp b/source/Lib/CommonLib/Contexts.cpp
index c89bd1d3398889ff2c0399aee1d15c94b3e7ca6c..705198c0beea465dadf9cad4e114382a6930fa63 100644
--- a/source/Lib/CommonLib/Contexts.cpp
+++ b/source/Lib/CommonLib/Contexts.cpp
@@ -2832,6 +2832,23 @@ const CtxSet ContextSetCfg::CclmDeltaFlags = ContextSetCfg::addCtxSet
 });
 #endif
 
+#if JVET_AA0057_CCCM
+const CtxSet ContextSetCfg::CccmFlag = ContextSetCfg::addCtxSet
+({
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { DWS, },
+  { DWS, },
+  { DWS, },
+  { DWE, },
+  { DWE, },
+  { DWE, },
+  { DWO, },
+  { DWO, },
+});
+#endif
+
 #elif SLICE_TYPE_WIN_SIZE
 const CtxSet ContextSetCfg::SplitFlag = ContextSetCfg::addCtxSet
 ({
@@ -4335,6 +4352,18 @@ const CtxSet ContextSetCfg::CclmDeltaFlags = ContextSetCfg::addCtxSet
 });
 #endif
 
+#if JVET_AA0057_CCCM
+const CtxSet ContextSetCfg::CccmFlag = ContextSetCfg::addCtxSet
+({
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { DWS, },
+  { DWS, },
+  { DWS, },
+});
+#endif
+
 #else
 const CtxSet ContextSetCfg::SplitFlag = ContextSetCfg::addCtxSet
 ({
@@ -5406,6 +5435,16 @@ const CtxSet ContextSetCfg::CclmDeltaFlags = ContextSetCfg::addCtxSet
 });
 #endif
 
+#if JVET_AA0057_CCCM
+const CtxSet ContextSetCfg::CccmFlag = ContextSetCfg::addCtxSet
+({
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { DWS, },
+});
+#endif
+
 #endif
 // clang-format on
 
diff --git a/source/Lib/CommonLib/Contexts.h b/source/Lib/CommonLib/Contexts.h
index 7e0fa7fad7f1898a77e6c24bacbbebe6407421d1..fe9c0360a79efb5a162089bb4dd11bdb1a6af5d3 100644
--- a/source/Lib/CommonLib/Contexts.h
+++ b/source/Lib/CommonLib/Contexts.h
@@ -493,6 +493,9 @@ public:
 #endif
 #if JVET_Z0050_CCLM_SLOPE
   static const CtxSet   CclmDeltaFlags;
+#endif
+#if JVET_AA0057_CCCM
+  static const CtxSet   CccmFlag;
 #endif
   static const unsigned NumberOfContexts;
 
diff --git a/source/Lib/CommonLib/IntraPrediction.cpp b/source/Lib/CommonLib/IntraPrediction.cpp
index 30e5663a59a5da08a1f42dca11793ed0b07d0178..f8e0a663193035b457196dffb6d478e3cdfc1432 100644
--- a/source/Lib/CommonLib/IntraPrediction.cpp
+++ b/source/Lib/CommonLib/IntraPrediction.cpp
@@ -112,6 +112,9 @@ IntraPrediction::IntraPrediction()
 #if JVET_V0130_INTRA_TMP
   m_pppTarPatch = NULL;
 #endif
+#if JVET_AA0057_CCCM
+  m_cccmLumaBuf = nullptr;
+#endif
 }
 
 IntraPrediction::~IntraPrediction()
@@ -170,6 +173,11 @@ void IntraPrediction::destroy()
     m_pppTarPatch = NULL;
   }
 #endif
+
+#if JVET_AA0057_CCCM
+  delete[] m_cccmLumaBuf;
+  m_cccmLumaBuf = nullptr;
+#endif
 }
 
 void IntraPrediction::init(ChromaFormat chromaFormatIDC, const unsigned bitDepthY)
@@ -268,6 +276,13 @@ void IntraPrediction::init(ChromaFormat chromaFormatIDC, const unsigned bitDepth
   m_calcTemplateDiff = calcTemplateDiff;
 #endif
 
+#if JVET_AA0057_CCCM
+  if (m_cccmLumaBuf == nullptr)
+  {
+    m_cccmLumaBuf = new Pel[(2*MAX_CU_SIZE + CCCM_WINDOW_SIZE + 2*CCCM_FILTER_PADDING) * (2*MAX_CU_SIZE + CCCM_WINDOW_SIZE + 2*CCCM_FILTER_PADDING)];
+  }
+#endif
+
 #if ENABLE_SIMD_TMP
 #ifdef TARGET_SIMD_X86
   initIntraX86();
@@ -4731,6 +4746,14 @@ int isBelowLeftAvailable(const CodingUnit &cu, const ChannelType &chType, const
 // LumaRecPixels
 void IntraPrediction::xGetLumaRecPixels(const PredictionUnit &pu, CompArea chromaArea)
 {
+#if JVET_AA0057_CCCM
+  if ( pu.cccmFlag )
+  {
+    xCccmCreateLumaRef(pu);
+    return;
+  }
+#endif
+  
   int iDstStride = 0;
   Pel* pDst0 = 0;
   int curChromaMode = pu.intraDir[1];
@@ -6769,4 +6792,685 @@ int IntraPrediction::calcTemplateDiff( Pel* ref, unsigned int uiStride, Pel** ta
 }
 #endif
 
+#if JVET_AA0057_CCCM
+void IntraPrediction::predIntraCCCM( const PredictionUnit &pu, PelBuf &predCb, PelBuf &predCr, int intraDir )
+{
+  if ( pu.cccmFlag )
+  {
+    CccmModel cccmModelCb( pu.cu->slice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA) );
+    CccmModel cccmModelCr( pu.cu->slice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA) );
+
+    if ( PU::cccmSingleModeAvail(pu, intraDir) )
+    {
+      xCccmCalcModels(pu, cccmModelCb,  cccmModelCr, 0, 0);
+      xCccmApplyModel(pu, COMPONENT_Cb, cccmModelCb, 0, 0, predCb);
+      xCccmApplyModel(pu, COMPONENT_Cr, cccmModelCr, 0, 0, predCr);
+    }
+    else
+    {
+      // Multimode case
+      int modelThr = xCccmCalcRefAver(pu);
+      
+      xCccmCalcModels(pu, cccmModelCb,  cccmModelCr, 1, modelThr);
+      xCccmApplyModel(pu, COMPONENT_Cb, cccmModelCb, 1, modelThr, predCb);
+      xCccmApplyModel(pu, COMPONENT_Cr, cccmModelCr, 1, modelThr, predCr);
+
+      xCccmCalcModels(pu, cccmModelCb,  cccmModelCr, 2, modelThr);
+      xCccmApplyModel(pu, COMPONENT_Cb, cccmModelCb, 2, modelThr, predCb);
+      xCccmApplyModel(pu, COMPONENT_Cr, cccmModelCr, 2, modelThr, predCr);
+    }
+  }
+}
+
+void IntraPrediction::xCccmApplyModel(const PredictionUnit& pu, const ComponentID compId, CccmModel &cccmModel, int modelId, int modelThr, PelBuf &piPred) const
+{
+  const  ClpRng& clpRng(pu.cu->cs->slice->clpRng(compId));
+  static Pel     samples[CCCM_NUM_PARAMS];
+
+  CPelBuf refLumaBlk = xCccmGetLumaPuBuf(pu);
+
+  for (int y = 0; y < refLumaBlk.height; y++)
+  {
+    for (int x = 0; x < refLumaBlk.width; x++)
+    {
+      if ( modelId == 1 && refLumaBlk.at( x, y ) > modelThr ) // Model 1: Include only samples below or equal to the threshold
+      {
+        continue;
+      }
+      if ( modelId == 2 && refLumaBlk.at( x, y ) <= modelThr) // Model 2: Include only samples above the threshold
+      {
+        continue;
+      }
+      
+      // 7-tap cross
+      samples[0] = refLumaBlk.at( x  , y   ); // C
+      samples[1] = refLumaBlk.at( x  , y-1 ); // N
+      samples[2] = refLumaBlk.at( x  , y+1 ); // S
+      samples[3] = refLumaBlk.at( x-1, y   ); // W
+      samples[4] = refLumaBlk.at( x+1, y   ); // E
+      samples[5] = cccmModel.nonlinear( refLumaBlk.at( x, y) );
+      samples[6] = cccmModel.bias();
+
+      piPred.at(x, y) = ClipPel<Pel>( cccmModel.convolve(samples, CCCM_NUM_PARAMS), clpRng );
+    }
+  }
+}
+
+void IntraPrediction::xCccmCalcModels(const PredictionUnit& pu, CccmModel &cccmModelCb, CccmModel &cccmModelCr, int modelId, int modelThr) const
+{
+  int areaWidth, areaHeight, refSizeX, refSizeY, refPosPicX, refPosPicY;
+
+  const CPelBuf recoCb  = pu.cs->picture->getRecoBuf(COMPONENT_Cb);
+  const CPelBuf recoCr  = pu.cs->picture->getRecoBuf(COMPONENT_Cr);
+  PelBuf        refLuma = xCccmGetLumaRefBuf(pu, areaWidth, areaHeight, refSizeX, refSizeY, refPosPicX, refPosPicY);
+
+  int M = CCCM_NUM_PARAMS;
+  
+  int sampleNum = areaWidth * areaHeight - pu.blocks[COMPONENT_Cb].width * pu.blocks[COMPONENT_Cb].height;
+  int sampleInd = 0;
+  
+  // Collect reference data to input matrix A and target vector Y
+  static Pel A[CCCM_NUM_PARAMS][CCCM_MAX_REF_SAMPLES];
+  static Pel YCb[CCCM_MAX_REF_SAMPLES];
+  static Pel YCr[CCCM_MAX_REF_SAMPLES];
+
+  for (int y = 0; y < areaHeight; y++)
+  {
+    for (int x = 0; x < areaWidth; x++)
+    {
+      if ( x >= refSizeX && y >= refSizeY )
+      {
+        continue;
+      }
+      
+      if ( modelId == 1 && refLuma.at( x, y ) > modelThr ) // Model 1: Include only samples below or equal to the threshold
+      {
+        continue;
+      }
+      if ( modelId == 2 && refLuma.at( x, y ) <= modelThr) // Model 2: Include only samples above the threshold
+      {
+        continue;
+      }
+
+      // 7-tap cross
+      A[0][sampleInd] = refLuma.at( x  , y   ); // C
+      A[1][sampleInd] = refLuma.at( x  , y-1 ); // N
+      A[2][sampleInd] = refLuma.at( x  , y+1 ); // S
+      A[3][sampleInd] = refLuma.at( x-1, y   ); // W
+      A[4][sampleInd] = refLuma.at( x+1, y   ); // E
+      A[5][sampleInd] = cccmModelCb.nonlinear( refLuma.at( x, y) );
+      A[6][sampleInd] = cccmModelCb.bias();
+
+      YCb[sampleInd]   = recoCb.at(refPosPicX + x, refPosPicY + y);
+      YCr[sampleInd++] = recoCr.at(refPosPicX + x, refPosPicY + y);
+    }
+  }
+
+  if ( sampleInd == 0 ) // Number of samples can go to zero in the multimode case
+  {
+    cccmModelCb.clearModel(M);
+    cccmModelCr.clearModel(M);
+    return;
+  }
+  else
+  {
+    sampleNum = sampleInd;
+  }
+  
+  // Calculate autocorrelation matrix and cross-correlation vector
+  static CccmCovarianceInt::TE ATA;
+  static CccmCovarianceInt::Ty ATYCb;
+  static CccmCovarianceInt::Ty ATYCr;
+
+  memset(ATA  , 0x00, sizeof(TCccmCoeff) * CCCM_NUM_PARAMS * CCCM_NUM_PARAMS);
+  memset(ATYCb, 0x00, sizeof(TCccmCoeff) * CCCM_NUM_PARAMS);
+  memset(ATYCr, 0x00, sizeof(TCccmCoeff) * CCCM_NUM_PARAMS);
+
+  for (int coli0 = 0; coli0 < M; coli0++)
+  {
+    for (int coli1 = coli0; coli1 < M; coli1++)
+    {
+      Pel *col0 = A[coli0];
+      Pel *col1 = A[coli1];
+      
+      for (int rowi = 0; rowi < sampleNum; rowi++)
+      {
+        ATA[coli0][coli1] += col0[rowi] * col1[rowi];
+      }
+    }
+  }
+
+  for (int coli = 0; coli < M; coli++)
+  {
+    Pel *col = A[coli];
+    
+    for (int rowi = 0; rowi < sampleNum; rowi++)
+    {
+      ATYCb[coli] += col[rowi] * YCb[rowi];
+      ATYCr[coli] += col[rowi] * YCr[rowi];
+    }
+  }
+
+  // Scale the matrix and vector to selected dynamic range
+  int matrixShift = CCCM_MATRIX_BITS - 2 * pu.cu->cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA) - ceilLog2(sampleNum);
+
+  if ( matrixShift > 0 )
+  {
+    for (int coli0 = 0; coli0 < M; coli0++)
+    {
+      for (int coli1 = coli0; coli1 < M; coli1++)
+      {
+        ATA[coli0][coli1] <<= matrixShift;
+      }
+    }
+
+    for (int coli = 0; coli < M; coli++)
+    {
+      ATYCb[coli] <<= matrixShift;
+    }
+
+    for (int coli = 0; coli < M; coli++)
+    {
+      ATYCr[coli] <<= matrixShift;
+    }
+  }
+  else if ( matrixShift < 0 )
+  {
+    matrixShift = -matrixShift;
+    
+    for (int coli0 = 0; coli0 < M; coli0++)
+    {
+      for (int coli1 = coli0; coli1 < M; coli1++)
+      {
+        ATA[coli0][coli1] >>= matrixShift;
+      }
+    }
+
+    for (int coli = 0; coli < M; coli++)
+    {
+      ATYCb[coli] >>= matrixShift;
+    }
+
+    for (int coli = 0; coli < M; coli++)
+    {
+      ATYCr[coli] >>= matrixShift;
+    }
+  }
+  
+  // Solve the filter coefficients using LDL decomposition
+  CccmCovarianceInt cccmSolver;
+  CccmCovarianceInt::TE U;       // Upper triangular L' of ATA's LDL decomposition
+  CccmCovarianceInt::Ty diag;    // Diagonal of D
+
+  bool decompOk = cccmSolver.ldlDecompose(ATA, U, diag, M);
+  
+  cccmSolver.ldlSolve(U, diag, ATYCb, cccmModelCb.params, M, decompOk);
+  cccmSolver.ldlSolve(U, diag, ATYCr, cccmModelCr.params, M, decompOk);
+}
+
+// Calculate a single downsampled luma reference value (copied from IntraPrediction::xGetLumaRecPixels)
+Pel IntraPrediction::xCccmGetLumaVal(const PredictionUnit& pu, const CPelBuf pi, const int x, const int y) const
+{
+  const Pel* piSrc = pi.buf;
+  const int iRecStride = pi.stride;
+  Pel ypval = 0;
+  if (pu.chromaFormat == CHROMA_444)
+  {
+    ypval = piSrc[x + iRecStride * y];
+  }
+  else if (pu.chromaFormat == CHROMA_422)
+  {
+    int s       = 2;
+    int offLeft = x > 0 ? -1 : 0;
+    s += piSrc[2 * x +           iRecStride * y] * 2;
+    s += piSrc[2 * x + offLeft + iRecStride * y];
+    s += piSrc[2 * x +       1 + iRecStride * y];
+    ypval = s >> 2;
+  }
+  else if (pu.cs->sps->getCclmCollocatedChromaFlag())
+  {
+    int s        = 4;
+    int offLeft  = x > 0 ? -1 : 0;
+    int offAbove = y > 0 ? -1 : 0;
+    s += piSrc[2 * x +           iRecStride *  2 * y            ] * 4;
+    s += piSrc[2 * x + offLeft + iRecStride *  2 * y            ];
+    s += piSrc[2 * x +       1 + iRecStride *  2 * y            ];
+    s += piSrc[2 * x +           iRecStride * (2 * y + 1)       ];
+    s += piSrc[2 * x +           iRecStride * (2 * y + offAbove)];
+    ypval = s >> 3;
+  }
+  else
+  {
+    int s       = 4;
+    int offLeft = x > 0 ? -1 : 0;
+    s += piSrc[2 * x +           iRecStride *  y * 2            ] * 2;
+    s += piSrc[2 * x + offLeft + iRecStride *  y * 2            ];
+    s += piSrc[2 * x +       1 + iRecStride *  y * 2            ];
+    s += piSrc[2 * x +           iRecStride * (y * 2 + 1)       ] * 2;
+    s += piSrc[2 * x + offLeft + iRecStride * (y * 2 + 1)       ];
+    s += piSrc[2 * x +       1 + iRecStride * (y * 2 + 1)       ];
+    ypval = s >> 3;
+  }
+  return ypval;
+}
+
+// Using the same availability checking as in IntraPrediction::xGetLumaRecPixels
+void IntraPrediction::xCccmCalcRefArea(const PredictionUnit& pu)
+{
+  CompArea chromaArea = pu.Cb();
+  CompArea lumaArea   = CompArea( COMPONENT_Y, pu.chromaFormat, chromaArea.lumaPos(), recalcSize( pu.chromaFormat, CHANNEL_TYPE_CHROMA, CHANNEL_TYPE_LUMA, chromaArea.size() ) );//needed for correct pos/size (4x4 Tus)
+
+  const SizeType uiCWidth  = chromaArea.width;
+  const SizeType uiCHeight = chromaArea.height;
+
+  const CodingUnit& lumaCU = isChroma( pu.chType ) ? *pu.cs->picture->cs->getCU( lumaArea.pos(), CH_L ) : *pu.cu;
+  const CodingUnit&     cu = *pu.cu;
+  const CompArea& area     = isChroma( pu.chType ) ? chromaArea : lumaArea;
+
+  const uint32_t uiTuWidth  = area.width;
+  const uint32_t uiTuHeight = area.height;
+
+  int iBaseUnitSize = ( 1 << MIN_CU_LOG2 );
+
+  const int  iUnitWidth  = iBaseUnitSize >> getComponentScaleX(area.compID, area.chromaFormat );
+  const int  iUnitHeight = iBaseUnitSize >> getComponentScaleY(area.compID, area.chromaFormat);
+
+  const int  iTUWidthInUnits  = uiTuWidth / iUnitWidth;
+  const int  iTUHeightInUnits = uiTuHeight / iUnitHeight;
+  const int  iAboveUnits      = iTUWidthInUnits;
+  const int  iLeftUnits       = iTUHeightInUnits;
+  const int  chromaUnitWidth  = iBaseUnitSize >> getComponentScaleX(COMPONENT_Cb, area.chromaFormat);
+  const int  chromaUnitHeight = iBaseUnitSize >> getComponentScaleY(COMPONENT_Cb, area.chromaFormat);
+  const int  topTemplateSampNum  = 2 * uiCWidth;
+  const int  leftTemplateSampNum = 2 * uiCHeight;
+  const int  totalAboveUnits = (topTemplateSampNum  + (chromaUnitWidth  - 1)) / chromaUnitWidth;
+  const int  totalLeftUnits  = (leftTemplateSampNum + (chromaUnitHeight - 1)) / chromaUnitHeight;
+  const int  totalUnits = totalLeftUnits + totalAboveUnits + 1;
+  const int  aboveRightUnits = totalAboveUnits - iAboveUnits;
+  const int  leftBelowUnits  = totalLeftUnits - iLeftUnits;
+
+  int avaiAboveRightUnits = 0;
+  int avaiLeftBelowUnits  = 0;
+  bool  bNeighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + 1];
+  memset(bNeighborFlags, 0, totalUnits);
+  bool aboveIsAvailable, leftIsAvailable;
+
+  int availlableUnit = isLeftAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.pos(),
+                                       iLeftUnits, iUnitHeight, (bNeighborFlags + iLeftUnits + leftBelowUnits - 1));
+
+  leftIsAvailable = availlableUnit == iTUHeightInUnits;
+
+  availlableUnit = isAboveAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.pos(),
+                                    iAboveUnits, iUnitWidth, (bNeighborFlags + iLeftUnits + leftBelowUnits + 1));
+
+  aboveIsAvailable = availlableUnit == iTUWidthInUnits;
+
+  if (leftIsAvailable)   // if left is not available, then the below left is not available
+  {
+    avaiLeftBelowUnits = isBelowLeftAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.bottomLeftComp(area.compID), leftBelowUnits, iUnitHeight, (bNeighborFlags + leftBelowUnits - 1));
+  }
+
+  if (aboveIsAvailable)   // if above is not available, then  the above right is not available.
+  {
+    avaiAboveRightUnits = isAboveRightAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.topRightComp(area.compID), aboveRightUnits, iUnitWidth, (bNeighborFlags + iLeftUnits + leftBelowUnits + iAboveUnits + 1));
+  }
+
+  int columnsLeft, rowsAbove;
+  
+  PU::getCccmRefLineNum(pu, columnsLeft, rowsAbove);            // Reference lines available left and above
+
+  int refWidth  = pu.blocks[COMPONENT_Cb].width  + columnsLeft; // Reference buffer size excluding paddings
+  int refHeight = pu.blocks[COMPONENT_Cb].height + rowsAbove;
+  
+  int extWidth  = avaiAboveRightUnits * chromaUnitWidth;
+  int extHeight = avaiLeftBelowUnits  * chromaUnitHeight;
+  
+  refWidth  += rowsAbove   ? extWidth  : 0; // Add above right if above is available
+  refHeight += columnsLeft ? extHeight : 0; // Add below left if left is available
+  
+  m_cccmRefArea = Area( columnsLeft, rowsAbove, refWidth, refHeight); // Position with respect to the PU
+}
+
+// Return downsampled luma buffer that contains PU and the reference areas above and left of the PU
+PelBuf IntraPrediction::xCccmGetLumaRefBuf(const PredictionUnit& pu, int &areaWidth, int &areaHeight, int &refSizeX, int &refSizeY, int &refPosPicX, int &refPosPicY ) const
+{
+  refSizeX   = m_cccmRefArea.x;                        // Reference lines available left and above
+  refSizeY   = m_cccmRefArea.y;
+  areaWidth  = m_cccmRefArea.width;                    // Reference buffer size excluding paddings
+  areaHeight = m_cccmRefArea.height;
+  refPosPicX = pu.blocks[COMPONENT_Cb].x - refSizeX; // Position of the reference area in picture coordinates
+  refPosPicY = pu.blocks[COMPONENT_Cb].y - refSizeY;
+
+  int refStride = areaWidth + 2 * CCCM_FILTER_PADDING; // Including paddings required for the 2D filter
+  int refOrigin = refStride * CCCM_FILTER_PADDING + CCCM_FILTER_PADDING;
+
+  return PelBuf(m_cccmLumaBuf + refOrigin, refStride, areaWidth, areaHeight); // Points to the top-left corner of the reference area
+}
+
+// Return downsampled luma buffer for a PU
+PelBuf IntraPrediction::xCccmGetLumaPuBuf(const PredictionUnit& pu) const
+{
+  int puWidth   = pu.blocks[COMPONENT_Cb].width;
+  int puHeight  = pu.blocks[COMPONENT_Cb].height;
+  
+  int refStride = m_cccmRefArea.width + 2 * CCCM_FILTER_PADDING; // Including paddings required for the 2D filter
+  int refOrigin = refStride * (m_cccmRefArea.y + CCCM_FILTER_PADDING) + m_cccmRefArea.x + CCCM_FILTER_PADDING;
+
+  return PelBuf(m_cccmLumaBuf + refOrigin, refStride, puWidth, puHeight);  // Points to the top-left corner of the block
+}
+
+int IntraPrediction::xCccmCalcRefAver(const PredictionUnit& pu) const
+{
+  int areaWidth, areaHeight, refSizeX, refSizeY, refPosPicX, refPosPicY;
+
+  PelBuf refLuma = xCccmGetLumaRefBuf(pu, areaWidth, areaHeight, refSizeX, refSizeY, refPosPicX, refPosPicY);
+  
+  int numSamples = 0;
+  int sumSamples = 0;
+  
+  // Top samples
+  for (int y = 0; y < refSizeY; y++)
+  {
+    for (int x = 0; x < areaWidth; x++)
+    {
+      sumSamples += refLuma.at(x, y);
+      numSamples++;
+    }
+  }
+
+  // Left samples
+  for (int y = refSizeY; y < areaHeight; y++)
+  {
+    for (int x = 0; x < refSizeX; x++)
+    {
+      sumSamples += refLuma.at(x, y);
+      numSamples++;
+    }
+  }
+
+  return numSamples == 0 ? 512 : ( sumSamples + numSamples/2) / numSamples;
+}
+
+void IntraPrediction::xCccmCreateLumaRef(const PredictionUnit& pu)
+{
+  const CPelBuf recoLuma = pu.cs->picture->getRecoBuf(COMPONENT_Y);
+  const int  maxPosPicX  = pu.cs->picture->chromaSize().width  - 1;
+  const int  maxPosPicY  = pu.cs->picture->chromaSize().height - 1;
+
+  xCccmCalcRefArea(pu); // Find the reference area
+  
+  int areaWidth, areaHeight, refSizeX, refSizeY, refPosPicX, refPosPicY;
+
+  PelBuf refLuma = xCccmGetLumaRefBuf(pu, areaWidth, areaHeight, refSizeX, refSizeY, refPosPicX, refPosPicY);
+  
+  int puBorderX = refSizeX + pu.blocks[COMPONENT_Cb].width;
+  int puBorderY = refSizeY + pu.blocks[COMPONENT_Cb].height;
+  
+  // Generate down-sampled luma for the area covering both the PU and the top/left reference areas (+ top and left paddings)
+  for (int y = -CCCM_FILTER_PADDING; y < areaHeight; y++)
+  {
+    for (int x = -CCCM_FILTER_PADDING; x < areaWidth; x++)
+    {
+      if (( x >= puBorderX && y >= refSizeY ) ||
+          ( y >= puBorderY && x >= refSizeX ))
+      {
+        continue;
+      }
+
+      int chromaPosPicX = refPosPicX + x;
+      int chromaPosPicY = refPosPicY + y;
+      
+      chromaPosPicX = chromaPosPicX < 0 ? 0 : chromaPosPicX > maxPosPicX ? maxPosPicX : chromaPosPicX;
+      chromaPosPicY = chromaPosPicY < 0 ? 0 : chromaPosPicY > maxPosPicY ? maxPosPicY : chromaPosPicY;
+      
+      refLuma.at( x, y ) = xCccmGetLumaVal(pu, recoLuma, chromaPosPicX, chromaPosPicY);
+    }
+  }
+
+  CHECK( CCCM_FILTER_PADDING != 1, "Only padding with one sample implemented" );
+
+  // Pad right of top reference area
+  for (int y = -1; y < refSizeY; y++)
+  {
+    refLuma.at( areaWidth, y ) = refLuma.at( areaWidth - 1, y );
+  }
+
+  // Pad right of PU
+  for (int y = refSizeY; y < puBorderY; y++)
+  {
+    refLuma.at( puBorderX, y ) = refLuma.at( puBorderX - 1, y );
+  }
+
+  // Pad right of left reference area
+  for (int y = puBorderY; y < areaHeight; y++)
+  {
+    refLuma.at( refSizeX, y ) = refLuma.at( refSizeX - 1, y );
+  }
+
+  // Pad below left reference area
+  for (int x = -1; x < refSizeX + 1; x++)
+  {
+    refLuma.at( x, areaHeight ) = refLuma.at( x, areaHeight - 1 );
+  }
+
+  // Pad below PU
+  for (int x = refSizeX; x < puBorderX + 1; x++)
+  {
+    refLuma.at( x, puBorderY ) = refLuma.at( x, puBorderY - 1 );
+  }
+
+  // Pad below right reference area
+  for (int x = puBorderX + 1; x < areaWidth + 1; x++)
+  {
+    refLuma.at( x, refSizeY ) = refLuma.at( x, refSizeY - 1 );
+  }
+  
+  // In dualtree we can also use luma from the right and below (if not on CTU/picture boundary)
+  if ( CS::isDualITree( *pu.cs ) )
+  {
+    int ctuWidth  = pu.cs->sps->getMaxCUWidth()  >> getComponentScaleX(COMPONENT_Cb, pu.chromaFormat);
+    int ctuHeight = pu.cs->sps->getMaxCUHeight() >> getComponentScaleY(COMPONENT_Cb, pu.chromaFormat);
+
+    // Samples right of top reference area
+    int padPosPicX = refPosPicX + areaWidth;
+
+    if ( padPosPicX <= maxPosPicX && (padPosPicX % ctuWidth) )
+    {
+      for (int y = -1; y < refSizeY; y++)
+      {
+        int chromaPosPicY = refPosPicY + y;
+        chromaPosPicY     = chromaPosPicY < 0 ? 0 : chromaPosPicY > maxPosPicY ? maxPosPicY : chromaPosPicY;
+
+        refLuma.at( areaWidth, y ) = xCccmGetLumaVal(pu, recoLuma, padPosPicX, chromaPosPicY);
+      }
+    }
+
+    // Samples right of PU
+    padPosPicX = refPosPicX + puBorderX;
+
+    if ( padPosPicX <= maxPosPicX && (padPosPicX % ctuWidth) )
+    {
+      for (int y = refSizeY; y < puBorderY; y++)
+      {
+        int chromaPosPicY = refPosPicY + y;
+        chromaPosPicY     = chromaPosPicY < 0 ? 0 : chromaPosPicY > maxPosPicY ? maxPosPicY : chromaPosPicY;
+
+        refLuma.at( puBorderX, y ) = xCccmGetLumaVal(pu, recoLuma, padPosPicX, chromaPosPicY);
+      }
+    }
+
+    // Samples right of left reference area
+    padPosPicX = refPosPicX + refSizeX;
+
+    if ( padPosPicX <= maxPosPicX )
+    {
+      for (int y = puBorderY; y < areaHeight; y++)
+      {
+        int chromaPosPicY = refPosPicY + y;
+        chromaPosPicY     = chromaPosPicY < 0 ? 0 : chromaPosPicY > maxPosPicY ? maxPosPicY : chromaPosPicY;
+
+        refLuma.at( refSizeX, y ) = xCccmGetLumaVal(pu, recoLuma, padPosPicX, chromaPosPicY);
+      }
+    }
+    
+    // Samples below left reference area
+    int padPosPicY = refPosPicY + areaHeight;
+    
+    if ( padPosPicY <= maxPosPicY && (padPosPicY % ctuHeight) )
+    {
+      for (int x = -1; x < refSizeX + 1; x++)
+      {
+        int chromaPosPicX = refPosPicX + x;
+        chromaPosPicX     = chromaPosPicX < 0 ? 0 : chromaPosPicX > maxPosPicX ? maxPosPicX : chromaPosPicX;
+        
+        refLuma.at( x, areaHeight ) = xCccmGetLumaVal(pu, recoLuma, chromaPosPicX, padPosPicY);
+      }
+    }
+    
+    // Samples below PU
+    padPosPicY = refPosPicY + puBorderY;
+    
+    if ( padPosPicY <= maxPosPicY && (padPosPicY % ctuHeight) )
+    {
+      for (int x = refSizeX; x < puBorderX; x++) // Just go to PU border as the next sample may be out of CTU (and not needed anyways)
+      {
+        int chromaPosPicX = refPosPicX + x;
+        chromaPosPicX     = chromaPosPicX < 0 ? 0 : chromaPosPicX > maxPosPicX ? maxPosPicX : chromaPosPicX;
+        
+        refLuma.at( x, puBorderY ) = xCccmGetLumaVal(pu, recoLuma, chromaPosPicX, padPosPicY);
+      }
+    }
+
+    // Samples below right reference area
+    padPosPicY = refPosPicY + refSizeY;
+    
+    if ( padPosPicY <= maxPosPicY )
+    {
+      // Avoid going outside of right CTU border where these samples are not yet available
+      int puPosPicX        = pu.blocks[COMPONENT_Cb].x;
+      int ctuRightEdgeDist = ctuWidth - (puPosPicX % ctuWidth) + refSizeX;
+      int lastPosX         = ctuRightEdgeDist < areaWidth ? ctuRightEdgeDist : areaWidth;
+
+      for (int x = puBorderX + 1; x < lastPosX; x++) // Just go to ref area border as the next sample may be out of CTU (and not needed anyways)
+      {
+        int chromaPosPicX = refPosPicX + x;
+        chromaPosPicX     = chromaPosPicX < 0 ? 0 : chromaPosPicX > maxPosPicX ? maxPosPicX : chromaPosPicX;
+        
+        refLuma.at( x, refSizeY ) = xCccmGetLumaVal(pu, recoLuma, chromaPosPicX, padPosPicY);
+      }
+    }
+  }
+}
+
+// LDL decomposing A to U'*diag*U
+bool CccmCovarianceInt::ldlDecomp(TE A, TE U, Ty diag, int numEq) const
+{
+  for (int i = 0; i < numEq; i++)
+  {
+    diag[i] = A[i][i];
+    
+    for (int k = i - 1; k >= 0; k--)
+    {
+      TCccmCoeff tmp = FIXED_MULT(U[k][i], U[k][i]);
+      diag[i]       -= FIXED_MULT(tmp, diag[k]);
+    }
+
+    if ( diag[i] <= 0) // A is singular
+    {
+      return false;
+    }
+
+    for (int j = i + 1; j < numEq; j++)
+    {
+      TCccmCoeff scale = A[i][j];
+      
+      for (int k = i - 1; k >= 0; k--)
+      {
+        TCccmCoeff tmp = FIXED_MULT(U[k][j], U[k][i]);
+        scale         -= FIXED_MULT(tmp, diag[k]);
+      }
+
+      U[i][j] = FIXED_DIV(scale, diag[i]);
+    }
+  }
+
+  return true;
+}
+
+// Solve U'z = y for z
+void CccmCovarianceInt::ldlTransposeBacksubstitution(TE U, TCccmCoeff* y, TCccmCoeff* z, int numEq) const
+{
+  z[0] = y[0];
+  
+  for (int i = 1; i < numEq; i++)
+  {
+    TCccmCoeff sum = 0;
+
+    for (int j = 0; j < i; j++)
+    {
+      sum += FIXED_MULT(z[j], U[j][i]);
+    }
+
+    z[i] = y[i] - sum;
+  }
+}
+
+// Solve Ux = z for x
+void CccmCovarianceInt::ldlBacksubstitution(TE U, TCccmCoeff* z, TCccmCoeff* x, int numEq) const
+{
+  x[numEq - 1] = z[numEq - 1];
+
+  for (int i = numEq - 2; i >= 0; i--)
+  {
+    TCccmCoeff sum = 0;
+
+    for (int j = i + 1; j < numEq; j++)
+    {
+      sum += FIXED_MULT(U[i][j], x[j]);
+    }
+
+    x[i] = z[i] - sum;
+  }
+}
+
+bool CccmCovarianceInt::ldlDecompose(TE A, TE U, Ty diag, int numEq) const
+{
+  // Compute upper triangular U and diagonal D such that U'*D*U = A
+  // (U being the tranpose of L in LDL decomposition: L*D*L' = A)
+
+  // Regularize A to reduce singularities
+  for (int i = 0; i < numEq; i++)
+  {
+    A[i][i] += 1;
+  }
+  
+  return ldlDecomp(A, U, diag, numEq);
+}
+
+void CccmCovarianceInt::ldlSolve(TE U, Ty diag, TCccmCoeff* y, TCccmCoeff* x, int numEq, bool decompOk) const
+{
+  if ( decompOk )
+  {
+    // Now, the equation is  U'*D*U*x = y, where U is upper triangular
+    // Solve U'*aux = y for aux
+    Ty aux;
+    
+    ldlTransposeBacksubstitution(U, y, aux, numEq);
+
+    // The equation is now D*U*x = aux, remove diagonal by scaling
+    for (int i = 0; i < numEq; i++)
+    {
+      aux[i] = FIXED_DIV(aux[i], diag[i]);
+    }
+    
+    // The equation is now U*x = aux, solve it for x (filter coefficients)
+    ldlBacksubstitution(U, aux, x, numEq);
+  }
+  else // A was singular
+  {
+    std::memset(x, 0, sizeof(TCccmCoeff) * numEq);
+  }
+}
+#endif
+
 //! \}
diff --git a/source/Lib/CommonLib/IntraPrediction.h b/source/Lib/CommonLib/IntraPrediction.h
index 6be53e1da73bc1ed74e805282dfe19989b52d371..1dbf3b02ec2889e231e059907532328bb1c18a85 100644
--- a/source/Lib/CommonLib/IntraPrediction.h
+++ b/source/Lib/CommonLib/IntraPrediction.h
@@ -91,6 +91,68 @@ public:
 typedef short TrainDataType;
 #endif
 
+#if JVET_AA0057_CCCM
+typedef int64_t TCccmCoeff;
+
+#define FIXED_MULT(x, y) TCccmCoeff((int64_t(x)*(y) + CCCM_DECIM_ROUND) >> CCCM_DECIM_BITS )
+#define FIXED_DIV(x, y)  TCccmCoeff((int64_t(x)    << CCCM_DECIM_BITS ) / (y) )
+
+struct CccmModel
+{
+  TCccmCoeff params[CCCM_NUM_PARAMS];
+  int        bd;
+  int        midVal;
+  
+  CccmModel(int bitdepth)
+  {
+    bd     = bitdepth;
+    midVal = (1 << ( bitdepth - 1));
+  }
+  
+  void clearModel(int numParams)
+  {
+    for( int i = 0; i < numParams - 1; i++)
+    {
+      params[i] = 0;
+    }
+
+    params[numParams - 1] = 1 << CCCM_DECIM_BITS; // Default bias to 1
+  }
+
+  Pel convolve(Pel* vector, int numParams)
+  {
+    TCccmCoeff sum = 0;
+    
+    for( int i = 0; i < numParams; i++)
+    {
+      sum += params[i] * vector[i];
+    }
+
+    return Pel( (sum + CCCM_DECIM_ROUND ) >> CCCM_DECIM_BITS );
+  }
+  
+  Pel nonlinear(const Pel val) { return (val * val + midVal) >> bd; }
+  Pel bias     ()              { return midVal; }
+};
+
+struct CccmCovarianceInt
+{
+  using TE = TCccmCoeff[CCCM_NUM_PARAMS][CCCM_NUM_PARAMS];
+  using Ty = TCccmCoeff[CCCM_NUM_PARAMS];
+
+  CccmCovarianceInt() {}
+  ~CccmCovarianceInt() {}
+
+  bool ldlDecompose                (TE A, TE U,          Ty diag,                      int numEq) const;
+  void ldlSolve                    (TE U, Ty diag,       TCccmCoeff* y, TCccmCoeff* x, int numEq, bool decompOk) const;
+
+private:
+  void ldlBacksubstitution         (TE U, TCccmCoeff* z, TCccmCoeff* x, int numEq) const;
+  void ldlTransposeBacksubstitution(TE U, TCccmCoeff* y, TCccmCoeff* z, int numEq) const;
+  bool ldlDecomp                   (TE A, TE U,          Ty outDiag,    int numEq) const;
+};
+#endif
+
 class IntraPrediction
 {
 #if MMLM
@@ -108,6 +170,11 @@ private:
   int  m_yuvExtSize2;
 #endif
 
+#if JVET_AA0057_CCCM
+  Area m_cccmRefArea;
+  Pel* m_cccmLumaBuf;
+#endif
+  
   static const uint8_t m_aucIntraFilter[MAX_INTRA_FILTER_DEPTHS];
 #if JVET_W0123_TIMD_FUSION
   static const uint8_t m_aucIntraFilterExt[MAX_INTRA_FILTER_DEPTHS];
@@ -234,6 +301,18 @@ public:
   virtual ~IntraPrediction();
 
   void init                       (ChromaFormat chromaFormatIDC, const unsigned bitDepthY);
+
+#if JVET_AA0057_CCCM
+  void   predIntraCCCM            (const PredictionUnit& pu, PelBuf &predCb, PelBuf &predCr, int intraDir);
+  void   xCccmCalcModels          (const PredictionUnit& pu, CccmModel &cccmModelCb, CccmModel &cccmModelCr, int modelId, int modelThr) const;
+  void   xCccmApplyModel          (const PredictionUnit& pu, const ComponentID compId, CccmModel &cccmModel, int modelId, int modelThr, PelBuf &piPred) const;
+  void   xCccmCreateLumaRef       (const PredictionUnit& pu);
+  PelBuf xCccmGetLumaRefBuf       (const PredictionUnit& pu, int &areaWidth, int &areaHeight, int &refSizeX, int &refSizeY, int &refPosPicX, int &refPosPicY) const;
+  PelBuf xCccmGetLumaPuBuf        (const PredictionUnit& pu) const;
+  Pel    xCccmGetLumaVal          (const PredictionUnit& pu, const CPelBuf pi, const int x, const int y) const;
+  int    xCccmCalcRefAver         (const PredictionUnit& pu) const;
+  void   xCccmCalcRefArea         (const PredictionUnit& pu);
+#endif
 #if ENABLE_DIMD
   static void deriveDimdMode      (const CPelBuf &recoBuf, const CompArea &area, CodingUnit &cu);
 #if JVET_Z0050_DIMD_CHROMA_FUSION && ENABLE_DIMD
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 35de675264b2ff49e8e5f2c9cfddbda38e0c21a1..67201d86365cf5d7001b502058153ac20ee4303e 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -131,6 +131,7 @@
 #define JVET_Y0116_EXTENDED_MRL_LIST                      1 // JVET-Y0116: Extended MRL Candidate List
 #define JVET_Z0050_DIMD_CHROMA_FUSION                     1 // JVET-Z0050: DIMD chroma mode and fusion of chroma intra prediction modes
 #define JVET_Z0050_CCLM_SLOPE                             1 // JVET-Z0050: CCLM with slope adjustments
+#define JVET_AA0057_CCCM                                  1 // JVET-AA0057: Convolutional cross-component model (CCCM)
 
 
 //IBC
diff --git a/source/Lib/CommonLib/Unit.cpp b/source/Lib/CommonLib/Unit.cpp
index 58f16b619ea36964dde84615022fb11a30b47171..4b17a450b2f8462a2e1d0f4fc699455670f71cfa 100644
--- a/source/Lib/CommonLib/Unit.cpp
+++ b/source/Lib/CommonLib/Unit.cpp
@@ -647,6 +647,9 @@ void PredictionUnit::initData()
 #endif
 #if JVET_Z0050_CCLM_SLOPE
   cclmOffsets = {};
+#endif
+#if JVET_AA0057_CCCM
+  cccmFlag    = 0;
 #endif
   // inter data
   mergeFlag   = false;
@@ -765,6 +768,9 @@ PredictionUnit& PredictionUnit::operator=(const IntraPredictionData& predData)
 #endif
 #if JVET_Z0050_CCLM_SLOPE
   cclmOffsets = predData.cclmOffsets;
+#endif
+#if JVET_AA0057_CCCM
+  cccmFlag    = predData.cccmFlag;
 #endif
   return *this;
 }
@@ -881,6 +887,9 @@ PredictionUnit& PredictionUnit::operator=( const PredictionUnit& other )
 #if JVET_Z0050_CCLM_SLOPE
   cclmOffsets = other.cclmOffsets;
 #endif
+#if JVET_AA0057_CCCM
+  cccmFlag    = other.cccmFlag;
+#endif
 
   mergeFlag   = other.mergeFlag;
   regularMergeFlag = other.regularMergeFlag;
diff --git a/source/Lib/CommonLib/Unit.h b/source/Lib/CommonLib/Unit.h
index bf5c717a20063cb3ce43cbc2b664c12165a4f5b0..72b66531d4fa420513d258aa052281d180bea337 100644
--- a/source/Lib/CommonLib/Unit.h
+++ b/source/Lib/CommonLib/Unit.h
@@ -430,6 +430,9 @@ struct IntraPredictionData
 #if JVET_Z0050_CCLM_SLOPE
   CclmOffsets cclmOffsets;
 #endif
+#if JVET_AA0057_CCCM
+  int       cccmFlag;
+#endif
 };
 
 struct InterPredictionData
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index 99e3374c8835ee88aad5e8dff65fad2c44edb6b8..96efd102c1e39b3696602fe30d87b61f0597feb7 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -1538,6 +1538,49 @@ bool PU::isDMChromaMIP(const PredictionUnit &pu)
 #endif
 }
 
+#if JVET_AA0057_CCCM
+void PU::getCccmRefLineNum(const PredictionUnit& pu, int& th, int& tv)
+{
+  const Area area = pu.blocks[COMPONENT_Cb];
+  
+  th = area.x < CCCM_WINDOW_SIZE ? area.x : CCCM_WINDOW_SIZE;
+  tv = area.y < CCCM_WINDOW_SIZE ? area.y : CCCM_WINDOW_SIZE;
+
+#if CCCM_REF_LINES_ABOVE_CTU
+  int ctuHeight  = pu.cs->sps->getMaxCUHeight() >> getComponentScaleY(COMPONENT_Cb, pu.chromaFormat);
+  int borderDist = area.y % ctuHeight;
+  int tvMax      = borderDist + CCCM_REF_LINES_ABOVE_CTU;
+  
+  tv = tv > tvMax ? tvMax : tv;
+#endif
+}
+
+bool PU::cccmSingleModeAvail(const PredictionUnit& pu, int intraMode)
+{
+  const Area area = pu.blocks[COMPONENT_Cb];
+  bool modeIsOk   = intraMode == LM_CHROMA_IDX;
+  modeIsOk        = modeIsOk && ( area.width * area.height >= CCCM_MIN_PU_SIZE );
+              
+  return modeIsOk && (area.x > 0 || area.y > 0);
+}
+  
+bool PU::cccmMultiModeAvail(const PredictionUnit& pu, int intraMode)
+{
+#if MMLM
+  const Area area = pu.blocks[COMPONENT_Cb];
+  bool modeIsOk   = intraMode == MMLM_CHROMA_IDX;
+  modeIsOk        = modeIsOk && ( area.width * area.height >= CCCM_MIN_PU_SIZE );
+
+  int th, tv;
+  PU::getCccmRefLineNum(pu, th, tv);
+  const int nsamples = ((area.width + th) * (area.height + tv) - (area.area()));
+  return modeIsOk && nsamples >= 128;
+#else
+  return false;
+#endif
+}
+#endif
+
 uint32_t PU::getIntraDirLuma( const PredictionUnit &pu )
 {
 #if JVET_V0130_INTRA_TMP
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index 047b568789e73419693c06441c7516413f8b468b..12f2a13350218a528d9433756bcfec9f07025cf9 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -459,6 +459,11 @@ namespace PU
 #if JVET_Z0050_CCLM_SLOPE
   bool hasCclmDeltaFlag(const PredictionUnit &pu, const int mode = -1);
 #endif
+#if JVET_AA0057_CCCM
+  void getCccmRefLineNum  (const PredictionUnit& pu, int& th, int& tv);
+  bool cccmSingleModeAvail(const PredictionUnit& pu, int intraMode);
+  bool cccmMultiModeAvail (const PredictionUnit& pu, int intraMode);
+#endif
 }
 
 // TU tools
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index 5df22c2119f856115a58e8f446e78d9d40293db3..7341fc244d9f1dc9ad49031d5a4e509f1c4cdf07 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -2166,6 +2166,13 @@ void CABACReader::cclmDelta(PredictionUnit& pu, int8_t &delta)
 
 void CABACReader::cclmDeltaSlope(PredictionUnit& pu)
 {
+#if JVET_AA0057_CCCM
+  if ( pu.cccmFlag )
+  {
+    return;
+  }
+#endif
+
   if ( PU::hasCclmDeltaFlag( pu ) )
   {
     bool deltaActive = m_BinDecoder.decodeBin(Ctx::CclmDeltaFlags(0));
@@ -2275,6 +2282,10 @@ bool CABACReader::intra_chroma_lmc_mode(PredictionUnit& pu)
 #endif
   }
   
+#if JVET_AA0057_CCCM
+  cccmFlag( pu );
+#endif
+
 #if JVET_Z0050_CCLM_SLOPE
   cclmDeltaSlope( pu );
 #endif
@@ -2282,6 +2293,18 @@ bool CABACReader::intra_chroma_lmc_mode(PredictionUnit& pu)
   return true; //it will only enter this function for LMC modes, so always return true ;
 }
 
+#if JVET_AA0057_CCCM
+void CABACReader::cccmFlag(PredictionUnit& pu)
+{
+  const unsigned intraDir = pu.intraDir[1];
+  
+  if ( PU::cccmSingleModeAvail(pu, intraDir) || PU::cccmMultiModeAvail(pu, intraDir) )
+  {
+    pu.cccmFlag = m_BinDecoder.decodeBin( Ctx::CccmFlag( 0 ) );
+  }
+}
+#endif
+
 void CABACReader::intra_chroma_pred_mode(PredictionUnit& pu)
 {
   RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET_SIZE2(STATS__CABAC_BITS__INTRA_DIR_ANG, pu.cu->blocks[pu.chType].lumaSize(), CHANNEL_TYPE_CHROMA);
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index ac06665b361aa84b4e5336957b951a4653e8421b..3e04bfaa9d565f66a70da822b123b5bb6ad22315 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -119,6 +119,9 @@ public:
   void        intra_chroma_pred_modes   ( CodingUnit&                   cu );
   bool        intra_chroma_lmc_mode     ( PredictionUnit&               pu );
   void        intra_chroma_pred_mode    ( PredictionUnit&               pu );
+#if JVET_AA0057_CCCM
+  void        cccmFlag                  ( PredictionUnit&               pu );
+#endif
 #if ENABLE_DIMD
   void        cu_dimd_flag              (CodingUnit&                   cu);
  #endif
diff --git a/source/Lib/DecoderLib/DecCu.cpp b/source/Lib/DecoderLib/DecCu.cpp
index 8df3da3ae03d8e87d33ef4e0b0c84b7e511773ad..462995d2e5835a54c32f52c56fb97cd9a3031403 100644
--- a/source/Lib/DecoderLib/DecCu.cpp
+++ b/source/Lib/DecoderLib/DecCu.cpp
@@ -535,12 +535,31 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
       m_pcIntraPred->initIntraPatternChTypeISP(*tu.cu, area, pReco);
     }
   }
+#if JVET_AA0057_CCCM
+  else if ( isLuma(compID) || !pu.cccmFlag )
+#else
   else
+#endif
   {
     m_pcIntraPred->initIntraPatternChType(*tu.cu, area);
   }
 
   //===== get prediction signal =====
+#if JVET_AA0057_CCCM
+  if( compID != COMPONENT_Y && pu.cccmFlag )
+  {
+    // Create both Cb and Cr predictions when here for Cb
+    if( compID == COMPONENT_Cb )
+    {
+      const PredictionUnit& pu = *tu.cu->firstPU;
+      PelBuf predCr            = cs.getPredBuf( tu.blocks[COMPONENT_Cr] );
+      
+      m_pcIntraPred->xGetLumaRecPixels( pu, area );
+      m_pcIntraPred->predIntraCCCM( pu, piPred, predCr, uiChFinalMode );
+    }
+  }
+  else
+#endif
   if( compID != COMPONENT_Y && PU::isLMCMode( uiChFinalMode ) )
   {
     const PredictionUnit& pu = *tu.cu->firstPU;
@@ -603,7 +622,11 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
     }
   }
 #if SIGN_PREDICTION
+#if JVET_AA0057_CCCM
+  if(isJCCR && compID == COMPONENT_Cb && !pu.cccmFlag) // Cr prediction was done already for CCCM
+#else
   if(isJCCR && compID == COMPONENT_Cb)
+#endif
   {
     m_pcIntraPred->initIntraPatternChType(*tu.cu, areaCr);
     if( PU::isLMCMode( uiChFinalMode ) )
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 3633510798f7f4c83eac79a832f6c17cb1db8878..431aeb02ec198406c54976929fec121fc055fa60 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -1903,6 +1903,13 @@ void CABACWriter::cclmDelta(const PredictionUnit& pu, int8_t delta)
 
 void CABACWriter::cclmDeltaSlope(const PredictionUnit& pu)
 {
+#if JVET_AA0057_CCCM
+  if ( pu.cccmFlag )
+  {
+    return;
+  }
+#endif
+
   if ( PU::hasCclmDeltaFlag( pu ) )
   {
     bool deltaActive = pu.cclmOffsets.isActive();
@@ -2009,11 +2016,26 @@ void CABACWriter::intra_chroma_lmc_mode(const PredictionUnit& pu)
 #endif
   }
 
+#if JVET_AA0057_CCCM
+  cccmFlag( pu );
+#endif
+
 #if JVET_Z0050_CCLM_SLOPE
   cclmDeltaSlope( pu );
 #endif
 }
 
+#if JVET_AA0057_CCCM
+void CABACWriter::cccmFlag(const PredictionUnit& pu)
+{
+  const unsigned intraDir = pu.intraDir[1];
+  
+  if ( PU::cccmSingleModeAvail(pu, intraDir) || PU::cccmMultiModeAvail(pu, intraDir) )
+  {
+    m_BinEncoder.encodeBin( pu.cccmFlag ? 1 : 0, Ctx::CccmFlag( 0 ) );
+  }
+}
+#endif
 
 void CABACWriter::intra_chroma_pred_mode(const PredictionUnit& pu)
 {
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index 6025f69ed71d9e84bf371e183e9fb535a55ddf98..706ab6ffc6b27408e7ad102900f941f30c2b19dc 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -132,6 +132,9 @@ public:
   void        intra_chroma_pred_modes   ( const CodingUnit&             cu );
   void        intra_chroma_lmc_mode     ( const PredictionUnit&         pu );
   void        intra_chroma_pred_mode    ( const PredictionUnit&         pu );
+#if JVET_AA0057_CCCM
+  void        cccmFlag                  ( const PredictionUnit&         pu );
+#endif
   void        cu_residual               ( const CodingUnit&             cu,       Partitioner&      pm,         CUCtx& cuCtx );
   void        rqt_root_cbf              ( const CodingUnit&             cu );
   void        adaptive_color_transform(const CodingUnit&             cu);
diff --git a/source/Lib/EncoderLib/IntraSearch.cpp b/source/Lib/EncoderLib/IntraSearch.cpp
index 3386bc85d0d7576e8d6821679f88bec3a1b4683c..5fd007e74fccf2d1c8a421ba5f2839fc13d5408b 100644
--- a/source/Lib/EncoderLib/IntraSearch.cpp
+++ b/source/Lib/EncoderLib/IntraSearch.cpp
@@ -1879,6 +1879,9 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
     Distortion uiBestDist = 0;
     double     dBestCost = MAX_DOUBLE;
     int32_t bestBDPCMMode = 0;
+#if JVET_AA0057_CCCM
+    int      cccmModeBest = 0;
+#endif
 #if JVET_Z0050_CCLM_SLOPE
     CclmOffsets bestCclmOffsets = {};
     CclmOffsets satdCclmOffsetsBest[NUM_CHROMA_MODE];
@@ -2407,6 +2410,88 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
       pu.cclmOffsets.setAllZero();
 #endif
 
+#if JVET_AA0057_CCCM
+#if MMLM
+      for (int32_t uiMode = 0; uiMode < 2; uiMode++)
+      {
+        int chromaIntraMode = uiMode ? MMLM_CHROMA_IDX : LM_CHROMA_IDX;
+#else
+      for (int32_t uiMode = 0; uiMode < 1; uiMode++)
+      {
+        int chromaIntraMode = LM_CHROMA_IDX;
+#endif
+
+        if ( PU::cccmSingleModeAvail(pu, chromaIntraMode) || PU::cccmMultiModeAvail(pu, chromaIntraMode) )
+        {
+          pu.cccmFlag = 1;
+
+          // Original RD check code replicated from above
+          cs.setDecomp( pu.Cb(), false );
+          cs.dist = baseDist;
+          //----- restore context models -----
+          m_CABACEstimator->getCtx() = ctxStart;
+
+          //----- chroma coding -----
+          pu.intraDir[1] = chromaIntraMode;
+
+          xRecurIntraChromaCodingQT( cs, partitioner, bestCostSoFar, ispType );
+          if( lumaUsesISP && cs.dist == MAX_UINT )
+          {
+            continue;
+          }
+
+          if (cs.sps->getTransformSkipEnabledFlag())
+          {
+            m_CABACEstimator->getCtx() = ctxStart;
+          }
+
+          uint64_t fracBits   = xGetIntraFracBitsQT( cs, partitioner, false, true, -1, ispType );
+          Distortion uiDist = cs.dist;
+          double    dCost   = m_pcRdCost->calcRdCost( fracBits, uiDist - baseDist );
+
+          //----- compare -----
+          if( dCost < dBestCost )
+          {
+            if( lumaUsesISP && dCost < bestCostSoFar )
+            {
+              bestCostSoFar = dCost;
+            }
+            for( uint32_t i = getFirstComponentOfChannel( CHANNEL_TYPE_CHROMA ); i < numberValidComponents; i++ )
+            {
+              const CompArea &area = pu.blocks[i];
+
+              saveCS.getRecoBuf     ( area ).copyFrom( cs.getRecoBuf   ( area ) );
+#if KEEP_PRED_AND_RESI_SIGNALS
+              saveCS.getPredBuf     ( area ).copyFrom( cs.getPredBuf   ( area ) );
+              saveCS.getResiBuf     ( area ).copyFrom( cs.getResiBuf   ( area ) );
+#endif
+              saveCS.getPredBuf     ( area ).copyFrom( cs.getPredBuf   (area ) );
+              cs.picture->getPredBuf( area ).copyFrom( cs.getPredBuf   (area ) );
+              cs.picture->getRecoBuf( area ).copyFrom( cs.getRecoBuf( area ) );
+
+              for( uint32_t j = 0; j < saveCS.tus.size(); j++ )
+              {
+                saveCS.tus[j]->copyComponentFrom( *orgTUs[j], area.compID );
+              }
+            }
+
+            dBestCost  = dCost;
+            uiBestDist = uiDist;
+            uiBestMode = chromaIntraMode;
+            bestBDPCMMode = cu.bdpcmModeChroma;
+#if JVET_Z0050_DIMD_CHROMA_FUSION
+            isChromaFusion  = pu.isChromaFusion;
+#endif
+#if JVET_Z0050_CCLM_SLOPE
+            bestCclmOffsets = pu.cclmOffsets;
+#endif
+            cccmModeBest    = pu.cccmFlag;
+          }
+        }
+      }
+        
+      pu.cccmFlag = 0;
+#endif
       for( uint32_t i = getFirstComponentOfChannel( CHANNEL_TYPE_CHROMA ); i < numberValidComponents; i++ )
       {
         const CompArea &area = pu.blocks[i];
@@ -2438,6 +2523,9 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_CCLM_SLOPE
     pu.cclmOffsets     = bestCclmOffsets;
 #endif
+#if JVET_AA0057_CCCM
+    pu.cccmFlag        = cccmModeBest;
+#endif
 #if JVET_Z0050_DIMD_CHROMA_FUSION
     pu.isChromaFusion = isChromaFusion;
 #endif
@@ -6670,6 +6758,14 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
     initIntraPatternChType( *currTU.cu, cbArea);
     initIntraPatternChType( *currTU.cu, crArea);
 
+#if JVET_AA0057_CCCM
+    if( pu.cccmFlag )
+    {
+      xGetLumaRecPixels( pu, cbArea );
+      predIntraCCCM( pu, piPredCb, piPredCr, predMode );
+    }
+    else
+#endif
     if( PU::isLMCMode( predMode ) )
     {
       xGetLumaRecPixels( pu, cbArea );