diff --git a/cfg/per-sequence/Robot_444.cfg b/cfg/per-sequence/Robot_444.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..e8495c94387ce92c117befb66b91275c3a60d939
--- /dev/null
+++ b/cfg/per-sequence/Robot_444.cfg
@@ -0,0 +1,11 @@
+#======== File I/O ===============
+InputFile                     : sc_robot_1280x720_30_8bit_300_444.yuv
+InputBitDepth                 : 8           # Input bitdepth
+InputChromaFormat             : 444         # Ratio of luminance to chrominance samples
+FrameRate                     : 30          # Frame Rate per second
+FrameSkip                     : 0           # Number of frames to be skipped in input
+SourceWidth                   : 1280        # Input  frame width
+SourceHeight                  : 720         # Input  frame height
+FramesToBeEncoded             : 300         # Number of frames to be coded
+
+Level                         : 6.2
diff --git a/cfg/per-sequence/Robot_RGB.cfg b/cfg/per-sequence/Robot_RGB.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..6fc4be981268717be82746b1119b1431680b034a
--- /dev/null
+++ b/cfg/per-sequence/Robot_RGB.cfg
@@ -0,0 +1,14 @@
+#======== File I/O ===============
+InputFile                     : sc_robot_1280x720_30_8bit_300.rgb
+InputBitDepth                 : 8           # Input bitdepth
+InputChromaFormat             : 444         # Ratio of luminance to chrominance samples
+FrameRate                     : 30          # Frame Rate per second
+FrameSkip                     : 0           # Number of frames to be skipped in input
+SourceWidth                   : 1280        # Input  frame width
+SourceHeight                  : 720         # Input  frame height
+FramesToBeEncoded             : 300         # Number of frames to be coded
+InputColourSpaceConvert       : RGBtoGBR    # Non-normative colour space conversion to apply to input video
+SNRInternalColourSpace        : 1           # Evaluate SNRs in GBR order
+OutputInternalColourSpace     : 0           # Convert recon output back to RGB order. Use --OutputColourSpaceConvert GBRtoRGB on decoder to produce a matching output file.
+
+Level                         : 6.2
diff --git a/doc/software-manual.tex b/doc/software-manual.tex
index 6f687cfb4eb51f699cc03bc7ff2dd63e8e5ab275..bc1a03fab5775f02cecae3307eb799648e6f0284 100755
--- a/doc/software-manual.tex
+++ b/doc/software-manual.tex
@@ -2365,6 +2365,12 @@ Adaptive LMCS mapping derivation options: Options 1 to 4 are for experimental te
 LMCS initial total codeword (valid values [$0 - 1023$]) to be used in LMCS mapping derivation when LMCSAdpOption is not equal to 0.
 \\
 
+\Option{ColorTransform} &
+%\ShortOption{\None} &
+\Default{false} &
+Enables or disables the use of adaptive color transform (ACT).
+\\
+
 \end{OptionTableNoShorthand}
 
 %%
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 6a645fe736d0f4b99d5ccf8bdb6fcc39bd900bd8..ca11a602377208fdc117b6b69190b344f82b4242 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -345,6 +345,10 @@ void EncApp::xInitLibCfg()
   m_cEncLib.setDMVR                                              ( m_DMVR );
   m_cEncLib.setMMVD                                              ( m_MMVD );
   m_cEncLib.setMmvdDisNum                                        (m_MmvdDisNum);
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_cEncLib.setRGBFormatFlag(m_rgbFormat);
+  m_cEncLib.setUseColorTrans(m_useColorTrans);
+#endif
   m_cEncLib.setPLTMode                                           ( m_PLTMode );
   m_cEncLib.setJointCbCr                                         ( m_JointCbCrMode );
   m_cEncLib.setIBCMode                                           ( m_IBCMode );
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index 929c39cda09471f81382ed53e0d1e60a07327ad8..8b4b0af309ad73488e632d43c4e96844c6c8be11 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -983,6 +983,9 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   ("AffineAmvrEncOpt",                                m_AffineAmvrEncOpt,                               false, "Enable encoder optimization of affine AMVR")
   ("DMVR",                                            m_DMVR,                                           false, "Decoder-side Motion Vector Refinement")
   ("MmvdDisNum",                                      m_MmvdDisNum,                                     8,     "Number of MMVD Distance Entries")
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  ("ColorTransform",                                  m_useColorTrans,                                  false, "Enable the color transform")
+#endif
   ("PLT",                                             m_PLTMode,                                           0u, "PLTMode (0x1:enabled, 0x0:disabled)  [default: disabled]")
   ("JointCbCr",                                       m_JointCbCrMode,                                  false, "Enable joint coding of chroma residuals (JointCbCr, 0:off, 1:on)")
   ( "IBC",                                            m_IBCMode,                                           0u, "IBCMode (0x1:enabled, 0x0:disabled)  [default: disabled]")
@@ -1808,6 +1811,9 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
 
 
   m_inputColourSpaceConvert = stringToInputColourSpaceConvert(inputColourSpaceConvert, true);
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_rgbFormat = (m_inputColourSpaceConvert == IPCOLOURSPACE_RGBtoGBR && m_chromaFormatIDC == CHROMA_444) ? true : false;
+#endif
 
   switch (m_conformanceWindowMode)
   {
@@ -2490,6 +2496,9 @@ bool EncAppCfg::xCheckParameter()
 #endif
     xConfirmPara( m_LMChroma, "LMChroma only allowed with NEXT profile" );
     xConfirmPara( m_ImvMode, "IMV is only allowed with NEXT profile" );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    xConfirmPara(m_useColorTrans, "ACT Mode only allowed with NEXT profile");
+#endif
     xConfirmPara( m_PLTMode, "PLT Mode only allowed with NEXT profile");
     xConfirmPara(m_IBCMode, "IBC Mode only allowed with NEXT profile");
     xConfirmPara( m_HashME, "Hash motion estimation only allowed with NEXT profile" );
@@ -3786,6 +3795,10 @@ void EncAppCfg::xPrintParameter()
     msg(VERBOSE, "MmvdDisNum:%d ", m_MmvdDisNum);
     msg(VERBOSE, "JointCbCr:%d ", m_JointCbCrMode);
   }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_useColorTrans = (m_chromaFormatIDC == CHROMA_444 && m_costMode != COST_LOSSLESS_CODING) ? m_useColorTrans : 0u;
+  msg(VERBOSE, "ACT:%d ", m_useColorTrans);
+#endif
     m_PLTMode = ( m_chromaFormatIDC == CHROMA_444) ? m_PLTMode : 0u;
     msg(VERBOSE, "PLT:%d ", m_PLTMode);
     msg(VERBOSE, "IBC:%d ", m_IBCMode);
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index 6e3f778f431bd4f0776470bbd76b4eac146d94ed..db4244e4ce1a20a83f6a36834bb20109cef819eb 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -317,6 +317,10 @@ protected:
   bool      m_DMVR;
   bool      m_MMVD;
   int       m_MmvdDisNum;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool      m_rgbFormat;
+  bool      m_useColorTrans;
+#endif
   unsigned  m_PLTMode;
   bool      m_JointCbCrMode;
 #if JVET_P0058_CHROMA_TS
diff --git a/source/Lib/CommonLib/Buffer.cpp b/source/Lib/CommonLib/Buffer.cpp
index 586f8728f27d08de6ab6402c1bfc3ab34668b687..5bd275e4cf0aef33930cfe17a09489c367192a0b 100644
--- a/source/Lib/CommonLib/Buffer.cpp
+++ b/source/Lib/CommonLib/Buffer.cpp
@@ -920,3 +920,76 @@ const CPelUnitBuf PelStorage::getBuf( const UnitArea &unit ) const
   return ( chromaFormat == CHROMA_400 ) ? CPelUnitBuf( chromaFormat, getBuf( unit.Y() ) ) : CPelUnitBuf( chromaFormat, getBuf( unit.Y() ), getBuf( unit.Cb() ), getBuf( unit.Cr() ) );
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+template<>
+void UnitBuf<Pel>::colorSpaceConvert(const UnitBuf<Pel> &other, const bool forward)
+{
+  const Pel* pOrg0 = bufs[COMPONENT_Y].buf;
+  const Pel* pOrg1 = bufs[COMPONENT_Cb].buf;
+  const Pel* pOrg2 = bufs[COMPONENT_Cr].buf;
+  const int  strideOrg = bufs[COMPONENT_Y].stride;
+
+  Pel* pDst0 = other.bufs[COMPONENT_Y].buf;
+  Pel* pDst1 = other.bufs[COMPONENT_Cb].buf;
+  Pel* pDst2 = other.bufs[COMPONENT_Cr].buf;
+  const int strideDst = other.bufs[COMPONENT_Y].stride;
+
+  int width = bufs[COMPONENT_Y].width;
+  int height = bufs[COMPONENT_Y].height;
+  int r, g, b;
+  int y0, cg, co;
+
+  CHECK(bufs[COMPONENT_Y].stride != bufs[COMPONENT_Cb].stride || bufs[COMPONENT_Y].stride != bufs[COMPONENT_Cr].stride, "unequal stride for 444 content");
+  CHECK(other.bufs[COMPONENT_Y].stride != other.bufs[COMPONENT_Cb].stride || other.bufs[COMPONENT_Y].stride != other.bufs[COMPONENT_Cr].stride, "unequal stride for 444 content");
+  CHECK(bufs[COMPONENT_Y].width != other.bufs[COMPONENT_Y].width || bufs[COMPONENT_Y].height != other.bufs[COMPONENT_Y].height, "unequal block size")
+
+    if (forward)
+    {
+      for (int y = 0; y < height; y++)
+      {
+        for (int x = 0; x < width; x++)
+        {
+          r = pOrg2[x];
+          g = pOrg0[x];
+          b = pOrg1[x];
+
+          pDst0[x] = (g << 1) + r + b;
+          pDst1[x] = (g << 1) - r - b;
+          pDst2[x] = ((r - b) << 1);
+          pDst0[x] = (pDst0[x] + 2) >> 2;
+          pDst1[x] = (pDst1[x] + 2) >> 2;
+          pDst2[x] = (pDst2[x] + 2) >> 2;
+        }
+        pOrg0 += strideOrg;
+        pOrg1 += strideOrg;
+        pOrg2 += strideOrg;
+        pDst0 += strideDst;
+        pDst1 += strideDst;
+        pDst2 += strideDst;
+      }
+    }
+    else
+    {
+      for (int y = 0; y < height; y++)
+      {
+        for (int x = 0; x < width; x++)
+        {
+          y0 = pOrg0[x];
+          cg = pOrg1[x];
+          co = pOrg2[x];
+
+          pDst0[x] = (y0 + cg);
+          pDst1[x] = (y0 - cg - co);
+          pDst2[x] = (y0 - cg + co);
+        }
+
+        pOrg0 += strideOrg;
+        pOrg1 += strideOrg;
+        pOrg2 += strideOrg;
+        pDst0 += strideDst;
+        pDst1 += strideDst;
+        pDst2 += strideDst;
+      }
+    }
+}
+#endif 
\ No newline at end of file
diff --git a/source/Lib/CommonLib/Buffer.h b/source/Lib/CommonLib/Buffer.h
index d71cd94f2b10f6db1a37854ae5cae7eb134514d5..46f26ace336ed8cb6143675596d682c6c7da2685 100644
--- a/source/Lib/CommonLib/Buffer.h
+++ b/source/Lib/CommonLib/Buffer.h
@@ -758,6 +758,9 @@ struct UnitBuf
 
         UnitBuf<      T> subBuf (const UnitArea& subArea);
   const UnitBuf<const T> subBuf (const UnitArea& subArea) const;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void colorSpaceConvert(const UnitBuf<T> &other, const bool forward);
+#endif
 };
 
 typedef UnitBuf<      Pel>  PelUnitBuf;
@@ -873,6 +876,17 @@ void UnitBuf<T>::addAvg(const UnitBuf<const T> &other1, const UnitBuf<const T> &
   }
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+template<typename T>
+void UnitBuf<T>::colorSpaceConvert(const UnitBuf<T> &other, const bool forward)
+{
+  THROW("Type not supported");
+}
+
+template<>
+void UnitBuf<Pel>::colorSpaceConvert(const UnitBuf<Pel> &other, const bool forward);
+#endif
+
 template<typename T>
 void UnitBuf<T>::extendSingleBorderPel()
 {
diff --git a/source/Lib/CommonLib/CodingStructure.cpp b/source/Lib/CommonLib/CodingStructure.cpp
index c942c5828c37c1afbb65584e3c2f836c694b5828..1928eb9377999d5bb134280f6a3a8c0972a9ef4b 100644
--- a/source/Lib/CommonLib/CodingStructure.cpp
+++ b/source/Lib/CommonLib/CodingStructure.cpp
@@ -68,6 +68,10 @@ CodingStructure::CodingStructure(CUCache& cuCache, PUCache& puCache, TUCache& tu
   , m_puCache ( puCache )
   , m_tuCache ( tuCache )
   , bestParent ( nullptr )
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  , tmpColorSpaceCost(MAX_DOUBLE)
+  , firstColorSpaceSelected(true)
+#endif
   , resetIBCBuffer (false)
 {
   for( uint32_t i = 0; i < MAX_NUM_COMPONENT; i++ )
@@ -93,6 +97,11 @@ CodingStructure::CodingStructure(CUCache& cuCache, PUCache& puCache, TUCache& tu
   features.resize( NUM_ENC_FEATURES );
   treeType = TREE_D;
   modeType = MODE_TYPE_ALL;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+  tmpColorSpaceIntraCost[0] = MAX_DOUBLE;
+  tmpColorSpaceIntraCost[1] = MAX_DOUBLE;
+  firstColorSpaceTestOnly = false;
+#endif
 }
 
 void CodingStructure::destroy()
diff --git a/source/Lib/CommonLib/CodingStructure.h b/source/Lib/CommonLib/CodingStructure.h
index d017a250beba35fbacb6fc4a2411bc1d0a7ea4ce..8bf19a426447ce4f680450a0f62852ac83cbf9af 100644
--- a/source/Lib/CommonLib/CodingStructure.h
+++ b/source/Lib/CommonLib/CodingStructure.h
@@ -243,6 +243,12 @@ private:
 
 public:
   CodingStructure *bestParent;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  double        tmpColorSpaceCost;
+  bool          firstColorSpaceSelected;
+  double        tmpColorSpaceIntraCost[2];
+  bool          firstColorSpaceTestOnly;
+#endif
   bool resetIBCBuffer;
 
   MotionBuf getMotionBuf( const     Area& _area );
diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index 365e8f6d294df2923148d2dd275c81d688edc489..acea627bf414388b56b5dea4c68aa8600292bb92 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -496,6 +496,10 @@ static const int ENC_PPS_ID_RPR =                                 3;
 static const int SCALE_RATIO_BITS =                              14;
 static const int MAX_SCALING_RATIO =                              8;  // max scaling ratio allowed in the software, it is used to allocated an internla buffer in the rescaling
 static const std::pair<int, int> SCALE_1X = std::pair<int, int>( 1 << SCALE_RATIO_BITS, 1 << SCALE_RATIO_BITS );  // scale ratio 1x
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+static const int DELTA_QP_FOR_Y_Cg =                             -5;
+static const int DELTA_QP_FOR_Co =                               -3;
+#endif
 
 // ====================================================================================================================
 // Macro functions
diff --git a/source/Lib/CommonLib/Contexts.cpp b/source/Lib/CommonLib/Contexts.cpp
index 3280c087b967de6ece82dac6bf0b424f7e688205..b04af79396d40b3c042ee61e2cefdcb257db4400 100644
--- a/source/Lib/CommonLib/Contexts.cpp
+++ b/source/Lib/CommonLib/Contexts.cpp
@@ -451,6 +451,16 @@ const CtxSet ContextSetCfg::QtRootCbf = ContextSetCfg::addCtxSet
   {   4, },
 });
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+const CtxSet ContextSetCfg::ACTFlag = ContextSetCfg::addCtxSet
+({
+  {  CNU, },
+  {  CNU, },
+  {  CNU, },
+  {  DWS, },
+  });
+#endif
+
 const CtxSet ContextSetCfg::QtCbf[] =
 {
   ContextSetCfg::addCtxSet
@@ -1216,6 +1226,16 @@ const CtxSet ContextSetCfg::QtRootCbf = ContextSetCfg::addCtxSet
   {   4, },
 });
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+const CtxSet ContextSetCfg::ACTFlag = ContextSetCfg::addCtxSet
+({
+  {  CNU, },
+  {  CNU, },
+  {  CNU, },
+  {  DWS, },
+  });
+#endif
+
 const CtxSet ContextSetCfg::QtCbf[] =
 {
   ContextSetCfg::addCtxSet
diff --git a/source/Lib/CommonLib/Contexts.h b/source/Lib/CommonLib/Contexts.h
index 519f6ac646ccd1f02df8fdb9aeae26c648f20a15..c793149f1fac7014d7a36c618c48d8712ab56782 100644
--- a/source/Lib/CommonLib/Contexts.h
+++ b/source/Lib/CommonLib/Contexts.h
@@ -226,6 +226,9 @@ public:
   static const CtxSet   Mvd;
   static const CtxSet   BDPCMMode;
   static const CtxSet   QtRootCbf;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  static const CtxSet   ACTFlag;
+#endif
   static const CtxSet   QtCbf           [3];    // [ channel ]
   static const CtxSet   SigCoeffGroup   [2];    // [ ChannelType ]
   static const CtxSet   LastX           [2];    // [ ChannelType ]
diff --git a/source/Lib/CommonLib/InterPrediction.cpp b/source/Lib/CommonLib/InterPrediction.cpp
index da35d1054a71f5d8edeb5a55a4c5ab15df5afdb6..ed4092ff381bb9a109d3e58bd8435c773bec9b71 100644
--- a/source/Lib/CommonLib/InterPrediction.cpp
+++ b/source/Lib/CommonLib/InterPrediction.cpp
@@ -127,6 +127,11 @@ void InterPrediction::destroy()
   }
 
   m_triangleBuf.destroy();
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_colorTransResiBuf[0].destroy();
+  m_colorTransResiBuf[1].destroy();
+  m_colorTransResiBuf[2].destroy();
+#endif
 
   if (m_storedMv != nullptr)
   {
@@ -190,6 +195,11 @@ void InterPrediction::init( RdCost* pcRdCost, ChromaFormat chromaFormatIDC, cons
     }
 
     m_triangleBuf.create(UnitArea(chromaFormatIDC, Area(0, 0, MAX_CU_SIZE, MAX_CU_SIZE)));
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    m_colorTransResiBuf[0].create(UnitArea(chromaFormatIDC, Area(0, 0, MAX_CU_SIZE, MAX_CU_SIZE)));
+    m_colorTransResiBuf[1].create(UnitArea(chromaFormatIDC, Area(0, 0, MAX_CU_SIZE, MAX_CU_SIZE)));
+    m_colorTransResiBuf[2].create(UnitArea(chromaFormatIDC, Area(0, 0, MAX_CU_SIZE, MAX_CU_SIZE)));
+#endif
 
     m_iRefListIdx = -1;
 
diff --git a/source/Lib/CommonLib/InterPrediction.h b/source/Lib/CommonLib/InterPrediction.h
index 42a76a9115f1f36be3abd77296a429a9da9dcd92..924186262392a6f3c27b96ec388c6687febb457e 100644
--- a/source/Lib/CommonLib/InterPrediction.h
+++ b/source/Lib/CommonLib/InterPrediction.h
@@ -180,6 +180,10 @@ protected:
 #if JVET_J0090_MEMORY_BANDWITH_MEASURE
   CacheModel      *m_cacheModel;
 #endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  PelStorage       m_colorTransResiBuf[3];  // 0-org; 1-act; 2-tmp
+#endif 
+
 public:
   InterPrediction();
   virtual ~InterPrediction();
diff --git a/source/Lib/CommonLib/Quant.cpp b/source/Lib/CommonLib/Quant.cpp
index 27eb90af5b35abb685dc4b92a2ac3b8185ea954c..9886fc514edab8f20166624a246f95bc1babf33f 100644
--- a/source/Lib/CommonLib/Quant.cpp
+++ b/source/Lib/CommonLib/Quant.cpp
@@ -530,6 +530,9 @@ void Quant::init( uint32_t uiMaxTrSize,
 #if T0196_SELECTIVE_RDOQ
   m_useSelectiveRDOQ     = useSelectiveRDOQ;
 #endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_resetStore = true;
+#endif 
 }
 
 #if ENABLE_SPLIT_PARALLELISM
@@ -1044,6 +1047,10 @@ void Quant::xInitScalingList( const Quant* other )
       }
     }
   }
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_pairCheck = 0;
+#endif
 }
 
 /** destroy quantization matrix array
@@ -1436,5 +1443,38 @@ void Quant::invTrSkipDeQuantOneSample(TransformUnit &tu, const ComponentID &comp
 #endif
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+void Quant::lambdaAdjustColorTrans(bool forward)
+{
+  if (m_resetStore)
+  {
+    for (uint8_t component = 0; component < MAX_NUM_COMPONENT; component++)
+    {
+      ComponentID compID = (ComponentID)component;
+      int       delta_QP = (compID == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+      double lamdbaAdjustRate = pow(2.0, delta_QP / 3.0);
 
+      m_lambdasStore[0][component] = m_lambdas[component];
+      m_lambdasStore[1][component] = m_lambdas[component] * lamdbaAdjustRate;
+    }
+    m_resetStore = false;
+  }
+  
+  if (forward)
+  {
+    CHECK(m_pairCheck == 1, "lambda has been already adjusted");
+    m_pairCheck = 1;
+  }
+  else
+  {
+    CHECK(m_pairCheck == 0, "lambda has not been adjusted");
+    m_pairCheck = 0;
+  }
+
+  for (uint8_t component = 0; component < MAX_NUM_COMPONENT; component++)
+  {
+    m_lambdas[component] = m_lambdasStore[m_pairCheck][component];
+  }
+}
+#endif
 //! \}
diff --git a/source/Lib/CommonLib/Quant.h b/source/Lib/CommonLib/Quant.h
index 89c0d6596019e10d45a269161b75f7aa72e2930d..b4a6aa10948fce4d8488d251529db5f829e2deca 100644
--- a/source/Lib/CommonLib/Quant.h
+++ b/source/Lib/CommonLib/Quant.h
@@ -67,6 +67,9 @@ struct TrQuantParams
 /// QP struct
 class QpParam
 {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+public:
+#endif
   int Qps[2];
   int pers[2];
   int rems[2];
@@ -121,6 +124,10 @@ public:
 #endif
   void   setLambda               ( const double dLambda )                      { m_dLambda = dLambda; }
   double getLambda               () const                                      { return m_dLambda; }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void   lambdaAdjustColorTrans(bool forward);
+  void   resetStore() { m_resetStore = true; }
+#endif
 
   int* getQuantCoeff             ( uint32_t list, int qp, uint32_t sizeX, uint32_t sizeY ) { return m_quantCoef            [sizeX][sizeY][list][qp]; };  //!< get Quant Coefficent
   int* getDequantCoeff           ( uint32_t list, int qp, uint32_t sizeX, uint32_t sizeY ) { return m_dequantCoef          [sizeX][sizeY][list][qp]; };  //!< get DeQuant Coefficent
@@ -184,12 +191,20 @@ private:
 private:
 #if RDOQ_CHROMA_LAMBDA
   double   m_lambdas[MAX_NUM_COMPONENT];
+#endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  double   m_lambdasStore[2][MAX_NUM_COMPONENT];  // 0-org; 1-act
+  bool     m_resetStore;
 #endif
   bool     m_scalingListEnabledFlag;
   bool     m_isScalingListOwner;
 
   int      *m_quantCoef            [SCALING_LIST_SIZE_NUM][SCALING_LIST_SIZE_NUM][SCALING_LIST_NUM][SCALING_LIST_REM_NUM]; ///< array of quantization matrix coefficient 4x4
   int      *m_dequantCoef          [SCALING_LIST_SIZE_NUM][SCALING_LIST_SIZE_NUM][SCALING_LIST_NUM][SCALING_LIST_REM_NUM]; ///< array of dequantization matrix coefficient 4x4
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  int      m_pairCheck;
+#endif
 };// END CLASS DEFINITION Quant
 
 
diff --git a/source/Lib/CommonLib/RdCost.cpp b/source/Lib/CommonLib/RdCost.cpp
index 0b384017a8d679f1d80c6fe6fb123f8ba440a5a1..7a028186b4fdb2a504a626ec28fd3a418aa8f213 100644
--- a/source/Lib/CommonLib/RdCost.cpp
+++ b/source/Lib/CommonLib/RdCost.cpp
@@ -92,6 +92,45 @@ void RdCost::setLambda( double dLambda, const BitDepths &bitDepths )
   m_dLambdaMotionSAD[1] = sqrt(dLambda);
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+void RdCost::lambdaAdjustColorTrans(bool forward, ComponentID componentID)
+{
+  if (m_resetStore)
+  {
+    for (uint8_t component = 0; component < MAX_NUM_COMPONENT; component++)
+    {
+      ComponentID compID = (ComponentID)component;
+      int       delta_QP = (compID == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+      double lamdbaAdjustRate = pow(2.0, delta_QP / 3.0);
+
+      m_lambdaStore[0][component] = m_dLambda;
+      m_DistScaleStore[0][component] = m_DistScale;
+
+      m_lambdaStore[1][component] = m_dLambda * lamdbaAdjustRate;
+      m_DistScaleStore[1][component] = double(1 << SCALE_BITS) / m_lambdaStore[1][component];
+    }
+    m_resetStore = false;
+  }
+  
+  if (forward)
+  {
+    CHECK(m_pairCheck == 1, "lambda has been already adjusted");
+    m_pairCheck = 1;
+  }
+  else
+  {
+    CHECK(m_pairCheck == 0, "lambda has not been adjusted");
+    m_pairCheck = 0;
+  }
+
+  m_dLambda = m_lambdaStore[m_pairCheck][componentID];
+  m_DistScale = m_DistScaleStore[m_pairCheck][componentID];
+  if (m_pairCheck == 0)
+  {
+    CHECK(m_DistScale != m_DistScaleUnadjusted, "lambda should be adjusted to the original value");
+  }
+}
+#endif
 
 // Initialize Function Pointer by [eDFunc]
 void RdCost::init()
@@ -181,6 +220,10 @@ void RdCost::init()
 
   m_motionLambda               = 0;
   m_iCostScale                 = 0;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_resetStore = true;
+  m_pairCheck    = 0;
+#endif 
 }
 
 
diff --git a/source/Lib/CommonLib/RdCost.h b/source/Lib/CommonLib/RdCost.h
index e6421d8b7c6f84ced1b89807b89db8923cdcfb5e..94fbaef5099a61c9b1f65930e1ca1eb920e25161 100644
--- a/source/Lib/CommonLib/RdCost.h
+++ b/source/Lib/CommonLib/RdCost.h
@@ -117,6 +117,12 @@ private:
 #endif
   double                  m_DistScale;
   double                  m_dLambdaMotionSAD[2 /* 0=standard, 1=for transquant bypass when mixed-lossless cost evaluation enabled*/];
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  double                  m_lambdaStore[2][3];   // 0-org; 1-act
+  double                  m_DistScaleStore[2][3]; // 0-org; 1-act
+  bool                    m_resetStore;
+  int                     m_pairCheck;
+#endif
 
   // for motion cost
   Mv                      m_mvPredictor;
@@ -306,6 +312,11 @@ public:
   inline std::vector<double>& getLumaLevelWeightTable        ()                   { return m_lumaLevelToWeightPLUT; }
 #endif
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void           lambdaAdjustColorTrans(bool forward, ComponentID compID);
+  void           resetStore() { m_resetStore = true; }
+#endif
+
 private:
 
   static Distortion xGetSSE           ( const DistParam& pcDtParam );
diff --git a/source/Lib/CommonLib/Slice.h b/source/Lib/CommonLib/Slice.h
index 525470cfbd9fea89dfce6905bef1a28a48f48249..abc712d29bbddc445bb6ee526b3e4b4fc3e76cea 100644
--- a/source/Lib/CommonLib/Slice.h
+++ b/source/Lib/CommonLib/Slice.h
@@ -999,6 +999,9 @@ private:
   bool              m_wrapAroundEnabledFlag;
   unsigned          m_wrapAroundOffset;
   unsigned          m_IBCFlag;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool              m_useColorTrans;
+#endif 
   unsigned          m_PLTMode;
 
   bool              m_lumaReshapeEnable;
@@ -1251,6 +1254,10 @@ public:
   bool                    getUseReshaper() const                                                          { return m_lumaReshapeEnable;                                                }
   void                    setIBCFlag(unsigned IBCFlag)                                                    { m_IBCFlag = IBCFlag; }
   unsigned                getIBCFlag() const                                                              { return m_IBCFlag; }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void                    setUseColorTrans(bool value) { m_useColorTrans = value; }
+  bool                    getUseColorTrans() const { return m_useColorTrans; }
+#endif
   void                    setPLTMode(unsigned PLTMode)                                                    { m_PLTMode = PLTMode; }
   unsigned                getPLTMode() const                                                              { return m_PLTMode; }
   void                    setUseSBT( bool b )                                                             { m_SBT = b; }
diff --git a/source/Lib/CommonLib/TrQuant.h b/source/Lib/CommonLib/TrQuant.h
index f2afdbb0862893d7a82685fc7d59fa49099e8445..369b4bb0dc1bfc8012d748571a1e962b7be43a3f 100644
--- a/source/Lib/CommonLib/TrQuant.h
+++ b/source/Lib/CommonLib/TrQuant.h
@@ -115,7 +115,10 @@ public:
   double getLambda   () const                                      { return m_quant->getLambda(); }
 
   DepQuant* getQuant() { return m_quant; }
-
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void   lambdaAdjustColorTrans(bool forward) { m_quant->lambdaAdjustColorTrans(forward); }
+  void   resetStore() { m_quant->resetStore(); }
+#endif
 
 #if ENABLE_SPLIT_PARALLELISM
   void    copyState( const TrQuant& other );
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 2ecb6695793dc05830e69573497a97cdcbe768a2..e730ceb6ed05f01dadb065b9191a7a70913f8dd5 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -112,6 +112,8 @@
 
 #define JVET_P1000_REMOVE_TRANFORMSHIFT_IN_TS_MODE        1 // JVET-P1000: Remove Transformshift in TS mode
 
+#define JVET_P0517_ADAPTIVE_COLOR_TRANSFORM               1 // JVET-P0517: adaptive color transform
+
 #define JVET_P0090_32BIT_MVD                              1 // JVET-P0090: Limitation of abs_mvd_min2 binarization within 32-bit
 
 #define JVET_P0298_DISABLE_LEVELMAPPING_IN_BYPASS         1 // JVET-P0298: Disable level mapping in bypass mode
diff --git a/source/Lib/CommonLib/Unit.cpp b/source/Lib/CommonLib/Unit.cpp
index 81b6b703c1ec2d98de300e48d12d7c7065c351a6..6db4bbe4f96e8fb0a887695b3f0c32cb835a3cbe 100644
--- a/source/Lib/CommonLib/Unit.cpp
+++ b/source/Lib/CommonLib/Unit.cpp
@@ -265,6 +265,9 @@ CodingUnit& CodingUnit::operator=( const CodingUnit& other )
   mmvdSkip = other.mmvdSkip;
   affine            = other.affine;
   affineType        = other.affineType;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  colorTransform = other.colorTransform;
+#endif 
   triangle          = other.triangle;
   transQuantBypass  = other.transQuantBypass;
   bdpcmMode         = other.bdpcmMode;
@@ -319,6 +322,9 @@ void CodingUnit::initData()
   mmvdSkip = false;
   affine            = false;
   affineType        = 0;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  colorTransform = false;
+#endif 
   triangle          = false;
   transQuantBypass  = false;
   bdpcmMode         = 0;
diff --git a/source/Lib/CommonLib/Unit.h b/source/Lib/CommonLib/Unit.h
index 01d498bb12e0f240d82a55dcd964fa8b33f5b078..872fa18a78e3dbdf1d9e2225ca3d4f6991d11b1c 100644
--- a/source/Lib/CommonLib/Unit.h
+++ b/source/Lib/CommonLib/Unit.h
@@ -308,6 +308,9 @@ struct CodingUnit : public UnitArea
   bool           mmvdSkip;
   bool           affine;
   int            affineType;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool           colorTransform;
+#endif
   bool           triangle;
   bool           transQuantBypass;
   int            bdpcmMode;
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index 3c22783a1c8ea9481387ba46cbde96693ced30f3..d3ebef1164d303b589ea5820ce412a1c3acbdf28 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -853,6 +853,9 @@ bool CABACReader::coding_unit( CodingUnit &cu, Partitioner &partitioner, CUCtx&
   // skip data
   if( cu.skip )
   {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    cu.colorTransform = false;
+#endif 
     cs.addTU         ( cu, partitioner.chType );
 #if !JVET_P0400_REMOVE_SHARED_MERGE_LIST
     pu.shareParentPos = cu.shareParentPos;
@@ -865,8 +868,17 @@ bool CABACReader::coding_unit( CodingUnit &cu, Partitioner &partitioner, CUCtx&
 
   // prediction mode and partitioning data
   pred_mode ( cu );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (CU::isIntra(cu))
+  {
+    adaptive_color_transform(cu);
+  }
+#endif
   if (CU::isPLT(cu))
   {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    cu.colorTransform = false;
+#endif 
     cs.addTU(cu, partitioner.chType);
     if (cu.isSepTree())
     {
@@ -1508,6 +1520,14 @@ bool CABACReader::intra_chroma_lmc_mode(PredictionUnit& pu)
 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);
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (pu.cu->colorTransform)
+  {
+    pu.intraDir[CHANNEL_TYPE_CHROMA] = DM_CHROMA_IDX;
+    return;
+  }
+#endif
+
   // LM chroma mode
 
 #if JVET_P0059_CHROMA_BDPCM
@@ -1566,6 +1586,9 @@ void CABACReader::cu_residual( CodingUnit& cu, Partitioner &partitioner, CUCtx&
     }
     if( !cu.rootCbf )
     {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      cu.colorTransform = false;
+#endif 
       TransformUnit& tu = cu.cs->addTU(cu, partitioner.chType);
       tu.depth = 0;
       for( unsigned c = 0; c < tu.blocks.size(); c++ )
@@ -1578,6 +1601,14 @@ void CABACReader::cu_residual( CodingUnit& cu, Partitioner &partitioner, CUCtx&
       return;
     }
   }
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (CU::isInter(cu) || CU::isIBC(cu))
+  {
+    adaptive_color_transform(cu);
+  }
+#endif
+
   cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_LUMA]   = false;
   cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_CHROMA] = false;
   cuCtx.lfnstLastScanPos                              = false;
@@ -1611,6 +1642,26 @@ void CABACReader::rqt_root_cbf( CodingUnit& cu )
   DTRACE( g_trace_ctx, D_SYNTAX, "rqt_root_cbf() ctx=0 root_cbf=%d pos=(%d,%d)\n", cu.rootCbf ? 1 : 0, cu.lumaPos().x, cu.lumaPos().y );
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+void CABACReader::adaptive_color_transform(CodingUnit& cu)
+{
+  if (!cu.slice->getSPS()->getUseColorTrans())
+  {
+    return;
+  }
+
+  if (cu.isSepTree())
+  {
+    return;
+  }
+
+  if (CU::isInter(cu) || CU::isIBC(cu) || CU::isIntra(cu))
+  {
+    cu.colorTransform = (m_BinDecoder.decodeBin(Ctx::ACTFlag()));
+  }
+}
+#endif
+
 void CABACReader::sbt_mode( CodingUnit& cu )
 {
   const uint8_t sbtAllowed = cu.checkAllowedSbt();
@@ -3076,9 +3127,16 @@ void CABACReader::transform_unit( TransformUnit& tu, CUCtx& cuCtx, Partitioner&
     }
     else
     {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      bool lumaCbfIsInferredACT = (cu.colorTransform && cu.predMode == MODE_INTRA && trDepth == 0 && !chromaCbfs.sigChroma(area.chromaFormat));
+      bool lastCbfIsInferred    = lumaCbfIsInferredACT; // ISP and ACT are mutually exclusive
+      bool previousCbf          = false;
+      bool rootCbfSoFar         = false;
+#else
       bool previousCbf = false;
       bool rootCbfSoFar = false;
-      bool lastCbfIsInferred = false;
+      bool lastCbfIsInferred = false;    
+#endif
       if (cu.ispMode)
       {
         uint32_t nTus = cu.ispMode == HOR_INTRA_SUBPARTITIONS ? cu.lheight() >> floorLog2(tu.lheight()) : cu.lwidth() >> floorLog2(tu.lwidth());
@@ -3513,7 +3571,11 @@ void CABACReader::mts_coding( TransformUnit& tu, ComponentID compID )
   
 void CABACReader::isp_mode( CodingUnit& cu )
 {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if( !CU::isIntra( cu ) || !isLuma( cu.chType ) || cu.firstPU->multiRefIdx || !cu.cs->sps->getUseISP() || cu.bdpcmMode || !CU::canUseISP( cu, getFirstComponentOfChannel( cu.chType ) ) || cu.colorTransform )
+#else
   if( !CU::isIntra( cu ) || !isLuma( cu.chType ) || cu.firstPU->multiRefIdx || !cu.cs->sps->getUseISP() || cu.bdpcmMode || !CU::canUseISP( cu, getFirstComponentOfChannel( cu.chType ) ) )
+#endif
   {
     cu.ispMode = NOT_INTRA_SUBPARTITIONS;
     return;
@@ -4281,6 +4343,7 @@ void CABACReader::mip_flag( CodingUnit& cu )
     cu.mipFlag = false;
     return;
   }
+
 #if !JVET_P0803_COMBINED_MIP_CLEANUP
   if( cu.lwidth() > cu.cs->sps->getMaxTbSize() || cu.lheight() > cu.cs->sps->getMaxTbSize())
   {
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index 95bb0abcacef1614351f28441832d41c3b69cfdc..190794e65e32cdd7535db1153e4eeeea17b33604 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -94,6 +94,9 @@ public:
   void        intra_chroma_pred_mode    ( PredictionUnit&               pu );
   void        cu_residual               ( CodingUnit&                   cu,     Partitioner&    pm,       CUCtx& cuCtx );
   void        rqt_root_cbf              ( CodingUnit&                   cu );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void        adaptive_color_transform(CodingUnit&             cu);
+#endif 
   void        sbt_mode                  ( CodingUnit&                   cu );
   bool        end_of_ctu                ( CodingUnit&                   cu,     CUCtx&          cuCtx );
   void        mip_flag                  ( CodingUnit&                   cu );
diff --git a/source/Lib/DecoderLib/DecCu.cpp b/source/Lib/DecoderLib/DecCu.cpp
index 59e452134a3f5f9f2c8d0e10c58b1bb6f0f5e759..90c4ba07b5250fce678947031d002e355600eb3d 100644
--- a/source/Lib/DecoderLib/DecCu.cpp
+++ b/source/Lib/DecoderLib/DecCu.cpp
@@ -379,6 +379,157 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
 #endif
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+void DecCu::xIntraRecACTBlk(TransformUnit& tu)
+{
+  CodingStructure      &cs = *tu.cs;
+  const PredictionUnit &pu = *tu.cs->getPU(tu.blocks[COMPONENT_Y], CHANNEL_TYPE_LUMA);
+  const Slice          &slice = *cs.slice;
+
+  CHECK(!tu.Y().valid() || !tu.Cb().valid() || !tu.Cr().valid(), "Invalid TU");
+  CHECK(&pu != tu.cu->firstPU, "wrong PU fetch");
+  CHECK(tu.cu->ispMode, "adaptive color transform cannot be applied to ISP");
+  CHECK(pu.intraDir[CHANNEL_TYPE_CHROMA] != DM_CHROMA_IDX, "chroma should use DM mode for adaptive color transform");
+
+#if JVET_P1006_PICTURE_HEADER
+  bool flag = slice.getPicHeader()->getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag()));
+  if (flag && slice.getPicHeader()->getLmcsChromaResidualScaleFlag() && (tu.cbf[COMPONENT_Cb] || tu.cbf[COMPONENT_Cr]))
+#else
+  bool flag = slice.getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag()));
+  if (flag && slice.getLmcsChromaResidualScaleFlag() && (tu.cbf[COMPONENT_Cb] || tu.cbf[COMPONENT_Cr]))
+#endif
+  {
+    const Area      area = tu.Y().valid() ? tu.Y() : Area(recalcPosition(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].pos()), recalcSize(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].size()));
+    const CompArea &areaY = CompArea(COMPONENT_Y, tu.chromaFormat, area);
+    int            adj = m_pcReshape->calculateChromaAdjVpduNei(tu, areaY);
+    tu.setChromaAdj(adj);
+  }
+
+  for (int i = 0; i < getNumberValidComponents(tu.chromaFormat); i++)
+  {
+    ComponentID          compID = (ComponentID)i;
+    const CompArea       &area = tu.blocks[compID];
+    const ChannelType    chType = toChannelType(compID);
+
+    PelBuf piPred = cs.getPredBuf(area);
+    m_pcIntraPred->initIntraPatternChType(*tu.cu, area);
+    if (PU::isMIP(pu, chType))
+    {
+#if JVET_P0803_COMBINED_MIP_CLEANUP
+      m_pcIntraPred->initIntraMip(pu, area);
+#else
+      m_pcIntraPred->initIntraMip(pu);
+#endif
+      m_pcIntraPred->predIntraMip(compID, piPred, pu);
+    }
+    else
+    {
+      m_pcIntraPred->predIntraAng(compID, piPred, pu);
+    }
+
+    PelBuf piResi = cs.getResiBuf(area);
+
+    QpParam cQP(tu, compID);
+    for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+    {
+      cQP.Qps[qpIdx] = cQP.Qps[qpIdx] + (compID == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+      cQP.pers[qpIdx] = cQP.Qps[qpIdx] / 6;
+      cQP.rems[qpIdx] = cQP.Qps[qpIdx] % 6;
+    }
+
+    if (tu.jointCbCr && isChroma(compID))
+    {
+      if (compID == COMPONENT_Cb)
+      {
+        PelBuf resiCr = cs.getResiBuf(tu.blocks[COMPONENT_Cr]);
+        if (tu.jointCbCr >> 1)
+        {
+          m_pcTrQuant->invTransformNxN(tu, COMPONENT_Cb, piResi, cQP);
+        }
+        else
+        {
+          QpParam qpCr(tu, COMPONENT_Cr);
+          for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+          {
+            qpCr.Qps[qpIdx] = qpCr.Qps[qpIdx] + DELTA_QP_FOR_Co;
+            qpCr.pers[qpIdx] = qpCr.Qps[qpIdx] / 6;
+            qpCr.rems[qpIdx] = qpCr.Qps[qpIdx] % 6;
+          }
+
+          m_pcTrQuant->invTransformNxN(tu, COMPONENT_Cr, resiCr, qpCr);
+        }
+        m_pcTrQuant->invTransformICT(tu, piResi, resiCr);
+      }
+    }
+    else
+    {
+      if (TU::getCbf(tu, compID))
+      {
+        m_pcTrQuant->invTransformNxN(tu, compID, piResi, cQP);
+      }
+      else
+      {
+        piResi.fill(0);
+      }
+    }
+
+    flag = flag && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
+#if JVET_P1006_PICTURE_HEADER
+    if (flag && (TU::getCbf(tu, compID) || tu.jointCbCr) && isChroma(compID) && slice.getPicHeader()->getLmcsChromaResidualScaleFlag())
+#else
+    if (flag && (TU::getCbf(tu, compID) || tu.jointCbCr) && isChroma(compID) && slice.getLmcsChromaResidualScaleFlag())
+#endif
+    {
+      piResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(compID));
+    }
+
+    cs.setDecomp(area);
+  }
+
+  cs.getResiBuf(tu).colorSpaceConvert(cs.getResiBuf(tu), false);
+
+  for (int i = 0; i < getNumberValidComponents(tu.chromaFormat); i++)
+  {
+    ComponentID          compID = (ComponentID)i;
+    const CompArea       &area = tu.blocks[compID];
+
+    PelBuf piPred = cs.getPredBuf(area);
+    PelBuf piResi = cs.getResiBuf(area);
+    PelBuf piReco = cs.getRecoBuf(area);
+
+    PelBuf tmpPred;
+#if JVET_P1006_PICTURE_HEADER
+    if (slice.getPicHeader()->getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
+#else
+    if (slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
+#endif
+    {
+      CompArea tmpArea(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
+      tmpPred = m_tmpStorageLCU->getBuf(tmpArea);
+      tmpPred.copyFrom(piPred);
+    }
+
+    piPred.reconstruct(piPred, piResi, tu.cu->cs->slice->clpRng(compID));
+    piReco.copyFrom(piPred);
+
+#if JVET_P1006_PICTURE_HEADER
+    if (slice.getPicHeader()->getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
+#else
+    if (slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
+#endif
+    {
+      piPred.copyFrom(tmpPred);
+    }
+
+    if (cs.pcv->isEncoder)
+    {
+      cs.picture->getRecoBuf(area).copyFrom(piReco);
+      cs.picture->getPredBuf(area).copyFrom(piPred);
+    }
+  }
+}
+#endif
+
 void DecCu::xReconIntraQT( CodingUnit &cu )
 {
 
@@ -401,6 +552,15 @@ void DecCu::xReconIntraQT( CodingUnit &cu )
     }
     return;
   }
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (cu.colorTransform)
+  {
+    xIntraRecACTQT(cu);
+  }
+  else
+  {
+#endif
   const uint32_t numChType = ::getNumberValidChannels( cu.chromaFormat );
 
   for( uint32_t chType = CHANNEL_TYPE_LUMA; chType < numChType; chType++ )
@@ -410,6 +570,9 @@ void DecCu::xReconIntraQT( CodingUnit &cu )
       xIntraRecQT( cu, ChannelType( chType ) );
     }
   }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  }
+#endif
 }
 
 void DecCu::xReconPLT(CodingUnit &cu, ComponentID compBegin, uint32_t numComp)
@@ -523,6 +686,16 @@ DecCu::xIntraRecQT(CodingUnit &cu, const ChannelType chType)
   }
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+void DecCu::xIntraRecACTQT(CodingUnit &cu)
+{
+  for (auto &currTU : CU::traverseTUs(cu))
+  {
+    xIntraRecACTBlk(currTU);
+  }
+}
+#endif
+
 /** Function for filling the PCM buffer of a CU using its reconstructed sample array
 * \param pCU   pointer to current CU
 * \param depth CU Depth
@@ -619,6 +792,12 @@ void DecCu::xReconInter(CodingUnit &cu)
 
   if (cu.rootCbf)
   {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    if (cu.colorTransform)
+    {
+      cs.getResiBuf(cu).colorSpaceConvert(cs.getResiBuf(cu), false);
+    }
+#endif
 #if REUSE_CU_RESULTS
     const CompArea &area = cu.blocks[COMPONENT_Y];
     CompArea    tmpArea(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
@@ -690,7 +869,20 @@ void DecCu::xDecodeInterTU( TransformUnit & currTU, const ComponentID compID )
   //===== inverse transform =====
   PelBuf resiBuf  = cs.getResiBuf(area);
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  QpParam cQP(currTU, compID);
+  if (currTU.cu->colorTransform)
+  {
+    for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+    {
+      cQP.Qps[qpIdx] = cQP.Qps[qpIdx] + (compID == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+      cQP.pers[qpIdx] = cQP.Qps[qpIdx] / 6;
+      cQP.rems[qpIdx] = cQP.Qps[qpIdx] % 6;
+    }
+  }
+#else
   const QpParam cQP(currTU, compID);
+#endif
 
   if( currTU.jointCbCr && isChroma(compID) )
   {
@@ -703,7 +895,20 @@ void DecCu::xDecodeInterTU( TransformUnit & currTU, const ComponentID compID )
       }
       else
       {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        QpParam qpCr(currTU, COMPONENT_Cr);
+        if (currTU.cu->colorTransform)
+        {
+          for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+          {
+            qpCr.Qps[qpIdx] = qpCr.Qps[qpIdx] + DELTA_QP_FOR_Co;
+            qpCr.pers[qpIdx] = qpCr.Qps[qpIdx] / 6;
+            qpCr.rems[qpIdx] = qpCr.Qps[qpIdx] % 6;
+          }
+        }
+#else
         const QpParam qpCr( currTU, COMPONENT_Cr );
+#endif
         m_pcTrQuant->invTransformNxN( currTU, COMPONENT_Cr, resiCr, qpCr );
       }
       m_pcTrQuant->invTransformICT( currTU, resiBuf, resiCr );
diff --git a/source/Lib/DecoderLib/DecCu.h b/source/Lib/DecoderLib/DecCu.h
index 4ef4076c087fdbc9a351563c09a61ec0e525a8da..d13ffcd07d2c36da3bc4e4b8281e1b8edf1eb389 100644
--- a/source/Lib/DecoderLib/DecCu.h
+++ b/source/Lib/DecoderLib/DecCu.h
@@ -82,6 +82,9 @@ public:
   /// reconstruct Ctu information
 protected:
   void xIntraRecQT        ( CodingUnit&      cu, const ChannelType chType );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void xIntraRecACTQT(CodingUnit&      cu);
+#endif
 
   void xReconInter        ( CodingUnit&      cu );
   void xDecodeInterTexture( CodingUnit&      cu );
@@ -89,6 +92,9 @@ protected:
   void xFillPCMBuffer     ( CodingUnit&      cu );
 
   void xIntraRecBlk       ( TransformUnit&   tu, const ComponentID compID );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void xIntraRecACTBlk(TransformUnit&   tu);
+#endif
   void xDecodeInterTU     ( TransformUnit&   tu, const ComponentID compID );
 
   void xDeriveCUMV        ( CodingUnit&      cu );
diff --git a/source/Lib/DecoderLib/VLCReader.cpp b/source/Lib/DecoderLib/VLCReader.cpp
index a422fc3dc57bddf20d99feb58982443dece46c1b..209303c8b231acd3375777d95206ca8b70c26f8b 100644
--- a/source/Lib/DecoderLib/VLCReader.cpp
+++ b/source/Lib/DecoderLib/VLCReader.cpp
@@ -1579,6 +1579,16 @@ void HLSyntaxReader::parseSPS(SPS* pcSPS)
     READ_FLAG( uiCode,  "sps_affine_amvr_enabled_flag" );           pcSPS->setAffineAmvrEnabledFlag  ( uiCode != 0 );
   }
   READ_FLAG( uiCode,    "gbi_flag" );                               pcSPS->setUseGBi                 ( uiCode != 0 );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (pcSPS->getChromaFormatIdc() == CHROMA_444)
+  {
+    READ_FLAG(uiCode, "act_flag");                                  pcSPS->setUseColorTrans(uiCode != 0);
+  }
+  else
+  {
+    pcSPS->setUseColorTrans(false);
+  }
+#endif 
   if (pcSPS->getChromaFormatIdc() == CHROMA_444)
   {
     READ_FLAG( uiCode,  "plt_flag");                                pcSPS->setPLTMode                ( uiCode != 0 );
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 3e01f31666bbe1d7d355846d238fc4658ef44459..08656e59fca661834e37f255398683eee4af4eec 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -650,6 +650,9 @@ void CABACWriter::coding_unit( const CodingUnit& cu, Partitioner& partitioner, C
   if( cu.skip )
   {
     CHECK( !cu.firstPU->mergeFlag, "Merge flag has to be on!" );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    CHECK(cu.colorTransform, "ACT should not be enabled for skip mode");
+#endif
     PredictionUnit&   pu = *cu.firstPU;
     prediction_unit ( pu );
     end_of_ctu      ( cu, cuCtx );
@@ -659,8 +662,17 @@ void CABACWriter::coding_unit( const CodingUnit& cu, Partitioner& partitioner, C
 
   // prediction mode and partitioning data
   pred_mode ( cu );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (CU::isIntra(cu))
+  {
+    adaptive_color_transform(cu);
+  }
+#endif
   if (CU::isPLT(cu))
   {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    CHECK(cu.colorTransform, "ACT should not be enabled for PLT mode");
+#endif
     if (cu.isSepTree())
     {
       if (isLuma(partitioner.chType))
@@ -1134,7 +1146,6 @@ void CABACWriter::intra_luma_pred_mode( const PredictionUnit& pu )
     return;
   }
   extend_ref_line( pu );
-
   isp_mode( *pu.cu );
 
   // prev_intra_luma_pred_flag
@@ -1257,6 +1268,13 @@ void CABACWriter::intra_chroma_pred_mode(const PredictionUnit& pu)
 #endif
 
   const unsigned intraDir = pu.intraDir[1];
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (pu.cu->colorTransform)
+  {
+    CHECK(pu.intraDir[CHANNEL_TYPE_CHROMA] != DM_CHROMA_IDX, "chroma should use DM for adaptive color transform");
+    return;
+  }
+#endif
   if (pu.cs->sps->getUseLMChroma() && pu.cu->checkCCLMAllowed())
   {
     m_BinEncoder.encodeBin(PU::isLMCMode(intraDir) ? 1 : 0, Ctx::CclmModeFlag(0));
@@ -1310,10 +1328,20 @@ void CABACWriter::cu_residual( const CodingUnit& cu, Partitioner& partitioner, C
 
     if( !cu.rootCbf )
     {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      CHECK(cu.colorTransform, "ACT should not be enabled for root_cbf = 0");
+#endif
       return;
     }
   }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (CU::isInter(cu) || CU::isIBC(cu))
+  {
+    adaptive_color_transform(cu);
+  }
+#endif
+
   cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_LUMA]   = false;
   cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_CHROMA] = false;
   cuCtx.lfnstLastScanPos                              = false;
@@ -1348,6 +1376,27 @@ void CABACWriter::rqt_root_cbf( const CodingUnit& cu )
   DTRACE( g_trace_ctx, D_SYNTAX, "rqt_root_cbf() ctx=0 root_cbf=%d pos=(%d,%d)\n", cu.rootCbf ? 1 : 0, cu.lumaPos().x, cu.lumaPos().y );
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+void CABACWriter::adaptive_color_transform(const CodingUnit& cu)
+{
+  if (!cu.slice->getSPS()->getUseColorTrans())
+  {
+    return;
+  }
+
+  if (cu.isSepTree())
+  {
+    CHECK(cu.colorTransform, "adaptive color transform should be disabled when dualtree and localtree are enabled");
+    return;
+  }
+
+  if (CU::isInter(cu) || CU::isIBC(cu) || CU::isIntra(cu))
+  {
+    m_BinEncoder.encodeBin(cu.colorTransform, Ctx::ACTFlag());
+  }
+}
+#endif
+
 void CABACWriter::sbt_mode( const CodingUnit& cu )
 {
   uint8_t sbtAllowed = cu.checkAllowedSbt();
@@ -2767,9 +2816,17 @@ void CABACWriter::transform_unit( const TransformUnit& tu, CUCtx& cuCtx, Partiti
     }
     else
     {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      bool lumaCbfIsInferredACT = (cu.colorTransform && cu.predMode == MODE_INTRA && trDepth == 0 && !chromaCbfs.sigChroma(area.chromaFormat));
+      CHECK(lumaCbfIsInferredACT && !TU::getCbfAtDepth(tu, COMPONENT_Y, trDepth), "adaptive color transform cannot have all zero coefficients");
+      bool lastCbfIsInferred    = lumaCbfIsInferredACT; // ISP and ACT are mutually exclusive
+      bool previousCbf          = false;
+      bool rootCbfSoFar         = false;
+#else
       bool previousCbf = false;
       bool rootCbfSoFar = false;
-      bool lastCbfIsInferred = false;
+      bool lastCbfIsInferred = false;    
+#endif
       if (cu.ispMode)
       {
         uint32_t nTus = cu.ispMode == HOR_INTRA_SUBPARTITIONS ? cu.lheight() >> floorLog2(tu.lheight()) : cu.lwidth() >> floorLog2(tu.lwidth());
@@ -3211,7 +3268,11 @@ void CABACWriter::mts_coding( const TransformUnit& tu, ComponentID compID )
 
 void CABACWriter::isp_mode( const CodingUnit& cu )
 {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if( !CU::isIntra( cu ) || !isLuma( cu.chType ) || cu.firstPU->multiRefIdx || !cu.cs->sps->getUseISP() || cu.bdpcmMode || !CU::canUseISP( cu, getFirstComponentOfChannel( cu.chType ) ) || cu.colorTransform )
+#else
   if( !CU::isIntra( cu ) || !isLuma( cu.chType ) || cu.firstPU->multiRefIdx || !cu.cs->sps->getUseISP() || cu.bdpcmMode || !CU::canUseISP( cu, getFirstComponentOfChannel( cu.chType ) ) )
+#endif
   {
     CHECK( cu.ispMode != NOT_INTRA_SUBPARTITIONS, "cu.ispMode != 0" );
     return;
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index 7e60fe96954b7a852138a428c790f87ad039b50e..ecb5f977002b52c708882b815a791baba4122acf 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -103,6 +103,9 @@ public:
   void        intra_chroma_pred_mode    ( const PredictionUnit&         pu );
   void        cu_residual               ( const CodingUnit&             cu,       Partitioner&      pm,         CUCtx& cuCtx );
   void        rqt_root_cbf              ( const CodingUnit&             cu );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void        adaptive_color_transform(const CodingUnit&             cu);
+#endif
   void        sbt_mode                  ( const CodingUnit&             cu );
   void        end_of_ctu                ( const CodingUnit&             cu,       CUCtx&            cuCtx );
   void        mip_flag                  ( const CodingUnit&             cu );
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index fc4f120c5d7aa9d57ee3c850363e5a726c0d407f..33401ed400f83ff0cd89d706b8141de437640fe8 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -327,6 +327,10 @@ protected:
   bool      m_DMVR;
   bool      m_MMVD;
   int       m_MmvdDisNum;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool      m_rgbFormat;
+  bool      m_useColorTrans;
+#endif
   unsigned  m_PLTMode;
   bool      m_JointCbCrMode;
   unsigned  m_IBCMode;
@@ -991,6 +995,12 @@ public:
   bool      getMMVD                         ()         const { return m_MMVD; }
   void      setMmvdDisNum                   ( int b )        { m_MmvdDisNum = b; }
   int       getMmvdDisNum                   ()         const { return m_MmvdDisNum; }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void      setRGBFormatFlag(bool value) { m_rgbFormat = value; }
+  bool      getRGBFormatFlag()                         const { return m_rgbFormat; }
+  void      setUseColorTrans(bool value) { m_useColorTrans = value; }
+  bool      getUseColorTrans()                         const { return m_useColorTrans; }
+#endif
   void      setPLTMode                   ( unsigned n)    { m_PLTMode = n; }
   unsigned  getPLTMode                   ()         const { return m_PLTMode; }
   void      setJointCbCr                    ( bool b )       { m_JointCbCrMode = b; }
diff --git a/source/Lib/EncoderLib/EncCu.cpp b/source/Lib/EncoderLib/EncCu.cpp
index 09508c8c87c10f6af8338e58a880d083be96fbf9..b04bdc86eec9bfc91f30e7711c212249cc4fac53 100644
--- a/source/Lib/EncoderLib/EncCu.cpp
+++ b/source/Lib/EncoderLib/EncCu.cpp
@@ -724,6 +724,32 @@ void EncCu::xCompressCU( CodingStructure*& tempCS, CodingStructure*& bestCS, Par
   m_pcInterSearch->resetSavedAffineMotion();
 
   double bestIntPelCost = MAX_DOUBLE;
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (tempCS->slice->getSPS()->getUseColorTrans())
+  {
+    tempCS->tmpColorSpaceCost = MAX_DOUBLE;
+    bestCS->tmpColorSpaceCost = MAX_DOUBLE;
+    tempCS->firstColorSpaceSelected = true;
+    bestCS->firstColorSpaceSelected = true;
+  }
+
+  if (tempCS->slice->getSPS()->getUseColorTrans() && !CS::isDualITree(*tempCS))
+  {
+    tempCS->firstColorSpaceTestOnly = false;
+    bestCS->firstColorSpaceTestOnly = false;
+    tempCS->tmpColorSpaceIntraCost[0] = MAX_DOUBLE;
+    tempCS->tmpColorSpaceIntraCost[1] = MAX_DOUBLE;
+    bestCS->tmpColorSpaceIntraCost[0] = MAX_DOUBLE;
+    bestCS->tmpColorSpaceIntraCost[1] = MAX_DOUBLE;
+
+    if (tempCS->bestParent && tempCS->bestParent->firstColorSpaceTestOnly)
+    {
+      tempCS->firstColorSpaceTestOnly = bestCS->firstColorSpaceTestOnly = true;
+    }
+  }
+#endif
+
   do
   {
     for (int i = compBegin; i < (compBegin + numComp); i++)
@@ -821,7 +847,40 @@ void EncCu::xCompressCU( CodingStructure*& tempCS, CodingStructure*& bestCS, Par
     }
     else if( currTestMode.type == ETM_INTRA )
     {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+      if (slice.getSPS()->getUseColorTrans() && !CS::isDualITree(*tempCS))
+      {
+        bool skipSecColorSpace = false;
+        skipSecColorSpace = xCheckRDCostIntra(tempCS, bestCS, partitioner, currTestMode, (m_pcEncCfg->getRGBFormatFlag() ? true : false));
+        
+        if (!skipSecColorSpace && !tempCS->firstColorSpaceTestOnly)
+        {
+          xCheckRDCostIntra(tempCS, bestCS, partitioner, currTestMode, (m_pcEncCfg->getRGBFormatFlag() ? false : true));
+        }
+
+        if (!tempCS->firstColorSpaceTestOnly)
+        {
+          if (tempCS->tmpColorSpaceIntraCost[0] != MAX_DOUBLE && tempCS->tmpColorSpaceIntraCost[1] != MAX_DOUBLE)
+          {
+            double skipCostRatio = m_pcEncCfg->getRGBFormatFlag() ? 1.1 : 1.0;
+            if (tempCS->tmpColorSpaceIntraCost[1] > (skipCostRatio*tempCS->tmpColorSpaceIntraCost[0]))
+            {
+              tempCS->firstColorSpaceTestOnly = bestCS->firstColorSpaceTestOnly = true;
+            }
+          }
+        }
+        else
+        {
+          CHECK(tempCS->tmpColorSpaceIntraCost[1] != MAX_DOUBLE, "the RD test of the second color space should be skipped");
+        }
+      }
+      else
+      {
+        xCheckRDCostIntra(tempCS, bestCS, partitioner, currTestMode, false);
+      }
+#else
       xCheckRDCostIntra( tempCS, bestCS, partitioner, currTestMode );
+#endif
     }
     else if (currTestMode.type == ETM_PALETTE)
     {
@@ -1704,8 +1763,11 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
   tempCS->prevQP[partitioner.chType] = oldPrevQp;
 }
 
-
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+bool EncCu::xCheckRDCostIntra(CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode, bool adaptiveColorTrans)
+#else
 void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
+#endif
 {
   double          bestInterCost             = m_modeCtrl->getBestInterCost();
   double          costSize2Nx2NmtsFirstPass = m_modeCtrl->getMtsSize2Nx2NFirstPassCost();
@@ -1750,6 +1812,24 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
   m_modeCtrl->setISPWasTested(false);
 #endif
   m_pcIntraSearch->invalidateBestModeCost();
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+  if (sps.getUseColorTrans() && !CS::isDualITree(*tempCS))
+  {
+    if ((m_pcEncCfg->getRGBFormatFlag() && adaptiveColorTrans) || (!m_pcEncCfg->getRGBFormatFlag() && !adaptiveColorTrans))
+    {
+      m_pcIntraSearch->invalidateBestRdModeFirstColorSpace();
+    }
+  }
+
+  bool foundZeroRootCbf = false;
+  if (sps.getUseColorTrans())
+  {
+    CHECK(tempCS->treeType != TREE_D || partitioner.treeType != TREE_D, "localtree should not be applied when adaptive color transform is enabled");
+    CHECK(tempCS->modeType != MODE_TYPE_ALL || partitioner.modeType != MODE_TYPE_ALL, "localtree should not be applied when adaptive color transform is enabled");
+    CHECK(adaptiveColorTrans && (CS::isDualITree(*tempCS) || partitioner.chType != CHANNEL_TYPE_LUMA), "adaptive color transform cannot be applied to dual-tree");
+  }
+#endif
+
   for( int trGrpIdx = 0; trGrpIdx < grpNumMax; trGrpIdx++ )
   {
     const uint8_t startMtsFlag = trGrpIdx > 0;
@@ -1761,6 +1841,12 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
       {
         for( uint8_t mtsFlag = startMtsFlag; mtsFlag <= endMtsFlag; mtsFlag++ )
         {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+          if (sps.getUseColorTrans() && !CS::isDualITree(*tempCS))
+          {
+            m_pcIntraSearch->setSavedRdModeIdx(trGrpIdx*(NUM_LFNST_NUM_PER_SET * 2) + lfnstIdx * 2 + mtsFlag);
+          }
+#endif
           if (mtsFlag > 0 && lfnstIdx > 0)
           {
             continue;
@@ -1787,6 +1873,9 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
           cu.lfnstIdx         = lfnstIdx;
           cu.mtsFlag          = mtsFlag;
           cu.ispMode          = NOT_INTRA_SUBPARTITIONS;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+          cu.colorTransform = adaptiveColorTrans;
+#endif
 
           CU::addPUs( cu );
 
@@ -1803,13 +1892,22 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
             {
               bestCostSoFar = encTestMode.maxCostAllowed;
             }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+            validCandRet = m_pcIntraSearch->estIntraPredLumaQT(cu, partitioner, bestCostSoFar, mtsFlag, startMTSIdx[trGrpIdx], endMTSIdx[trGrpIdx], (trGrpIdx > 0), !cu.colorTransform ? bestCS : nullptr);
+            if ((!validCandRet || (cu.ispMode && cu.firstTU->cbf[COMPONENT_Y] == 0)))
+#else
             validCandRet = m_pcIntraSearch->estIntraPredLumaQT( cu, partitioner, bestCostSoFar, mtsFlag, startMTSIdx[ trGrpIdx ], endMTSIdx[ trGrpIdx ], ( trGrpIdx > 0 ) );
-            if( sps.getUseLFNST() && ( !validCandRet || ( cu.ispMode && cu.firstTU->cbf[ COMPONENT_Y ] == 0 ) ) )
+            if (sps.getUseLFNST() && (!validCandRet || (cu.ispMode && cu.firstTU->cbf[COMPONENT_Y] == 0)))
+#endif
             {
               continue;
             }
-#if JVET_P1026_ISP_LFNST_COMBINATION
+#if JVET_P1026_ISP_LFNST_COMBINATION 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+            if (m_pcEncCfg->getUseFastISP() && validCandRet && !mtsFlag && !lfnstIdx && !cu.colorTransform)
+#else
             if (m_pcEncCfg->getUseFastISP() && validCandRet && !mtsFlag && !lfnstIdx)
+#endif
             {
               m_modeCtrl->setISPMode(cu.ispMode);
               m_modeCtrl->setISPLfnstIdx(cu.lfnstIdx);
@@ -1819,6 +1917,17 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
             }
 #endif
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+            if (sps.getUseColorTrans() && m_pcEncCfg->getRGBFormatFlag() && !CS::isDualITree(*tempCS) && !cu.colorTransform)
+            {
+              double curLumaCost = m_pcRdCost->calcRdCost(tempCS->fracBits, tempCS->dist);
+              if (curLumaCost > bestCS->cost)
+              {
+                continue;
+              }
+            }
+#endif
+
             useIntraSubPartitions = cu.ispMode != NOT_INTRA_SUBPARTITIONS;
             if( !partitioner.isSepTree( *tempCS ) )
             {
@@ -1841,12 +1950,29 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
 
             if( !partitioner.isSepTree( *tempCS ) )
             {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+              if (!cu.colorTransform)
+              {
+                cu.cs->picture->getRecoBuf(cu.Y()).copyFrom(cu.cs->getRecoBuf(COMPONENT_Y));
+                cu.cs->picture->getPredBuf(cu.Y()).copyFrom(cu.cs->getPredBuf(COMPONENT_Y));
+              }
+              else
+              {
+                cu.cs->picture->getRecoBuf(cu).copyFrom(cu.cs->getRecoBuf(cu));
+                cu.cs->picture->getPredBuf(cu).copyFrom(cu.cs->getPredBuf(cu));
+              }
+#else
               cu.cs->picture->getRecoBuf( cu.Y() ).copyFrom( cu.cs->getRecoBuf( COMPONENT_Y ) );
               cu.cs->picture->getPredBuf(cu.Y()).copyFrom(cu.cs->getPredBuf(COMPONENT_Y));
+#endif
             }
           }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if( tempCS->area.chromaFormat != CHROMA_400 && ( partitioner.chType == CHANNEL_TYPE_CHROMA || !cu.isSepTree() ) && !cu.colorTransform )
+#else
           if( tempCS->area.chromaFormat != CHROMA_400 && ( partitioner.chType == CHANNEL_TYPE_CHROMA || !cu.isSepTree() ) )
+#endif
           {
             TUIntraSubPartitioner subTuPartitioner( partitioner );
             m_pcIntraSearch->estIntraPredChromaQT( cu, ( !useIntraSubPartitions || ( cu.isSepTree() && !isLuma( CHANNEL_TYPE_CHROMA ) ) ) ? partitioner : subTuPartitioner, maxCostAllowedForChroma );
@@ -1864,6 +1990,14 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
             cu.rootCbf |= cu.firstTU->cbf[t] != 0;
           }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if (!cu.rootCbf)
+          {
+            cu.colorTransform = false;
+            foundZeroRootCbf = true;
+          }
+#endif
+
           // Get total bits for current mode: encode CU
           m_CABACEstimator->resetBits();
 
@@ -1879,6 +2013,9 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
             m_CABACEstimator->cu_skip_flag ( cu );
           }
           m_CABACEstimator->pred_mode      ( cu );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          m_CABACEstimator->adaptive_color_transform(cu);
+#endif
           m_CABACEstimator->cu_pred_data   ( cu );
           m_CABACEstimator->bdpcm_mode     ( cu, ComponentID(partitioner.chType) );
 #if JVET_P0059_CHROMA_BDPCM
@@ -1949,6 +2086,17 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
           DTRACE_MODE_COST( *tempCS, m_pcRdCost->getLambda( true ) );
 #else
           DTRACE_MODE_COST( *tempCS, m_pcRdCost->getLambda() );
+#endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+          if (sps.getUseColorTrans() && !CS::isDualITree(*tempCS))
+          {
+            int colorSpaceIdx = ((m_pcEncCfg->getRGBFormatFlag() && adaptiveColorTrans) || (!m_pcEncCfg->getRGBFormatFlag() && !adaptiveColorTrans)) ? 0 : 1;
+            if (tempCS->cost < tempCS->tmpColorSpaceIntraCost[colorSpaceIdx])
+            {
+              tempCS->tmpColorSpaceIntraCost[colorSpaceIdx] = tempCS->cost;
+              bestCS->tmpColorSpaceIntraCost[colorSpaceIdx] = tempCS->cost;
+            }
+          }
 #endif
           if( !sps.getUseLFNST() )
           {
@@ -2048,9 +2196,15 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
       }
     }
   } //trGrpIdx
-#if JVET_P1026_ISP_LFNST_COMBINATION
+#if JVET_P1026_ISP_LFNST_COMBINATION 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if(!adaptiveColorTrans)
+#endif
   m_modeCtrl->setBestNonDCT2Cost(bestNonDCT2Cost);
 #endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  return foundZeroRootCbf;
+#endif 
 }
 
 
@@ -3647,6 +3801,13 @@ void EncCu::xCheckRDCostIBCModeMerge2Nx2N(CodingStructure *&tempCS, CodingStruct
             m_CABACEstimator->getCtx() = m_CurrCtx->start;
 
             m_pcInterSearch->encodeResAndCalcRdInterCU(*tempCS, partitioner, (numResidualPass != 0), true, chroma);
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+            if (tempCS->slice->getSPS()->getUseColorTrans())
+            {
+              bestCS->tmpColorSpaceCost = tempCS->tmpColorSpaceCost;
+              bestCS->firstColorSpaceSelected = tempCS->firstColorSpaceSelected;
+            }
+#endif
             xEncodeDontSplit(*tempCS, partitioner);
 
 #if ENABLE_QPA_SUB_CTU
@@ -3737,6 +3898,13 @@ void EncCu::xCheckRDCostIBCMode(CodingStructure *&tempCS, CodingStructure *&best
         {
 
           m_pcInterSearch->encodeResAndCalcRdInterCU(*tempCS, partitioner, false, true, chroma);
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if (tempCS->slice->getSPS()->getUseColorTrans())
+          {
+            bestCS->tmpColorSpaceCost = tempCS->tmpColorSpaceCost;
+            bestCS->firstColorSpaceSelected = tempCS->firstColorSpaceSelected;
+          }
+#endif
 
           xEncodeDontSplit(*tempCS, partitioner);
 
@@ -4479,6 +4647,13 @@ void EncCu::xEncodeInterResidual(   CodingStructure *&tempCS
     if( skipResidual || histBestSbt == MAX_UCHAR || !CU::isSbtMode( histBestSbt ) )
     {
     m_pcInterSearch->encodeResAndCalcRdInterCU( *tempCS, partitioner, skipResidual );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    if (tempCS->slice->getSPS()->getUseColorTrans())
+    {
+      bestCS->tmpColorSpaceCost = tempCS->tmpColorSpaceCost;
+      bestCS->firstColorSpaceSelected = tempCS->firstColorSpaceSelected;
+    }
+#endif
     numRDOTried += mtsAllowed ? 2 : 1;
     xEncodeDontSplit( *tempCS, partitioner );
 
@@ -4626,6 +4801,13 @@ void EncCu::xEncodeInterResidual(   CodingStructure *&tempCS
 
       //try residual coding
       m_pcInterSearch->encodeResAndCalcRdInterCU( *tempCS, partitioner, skipResidual );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      if (tempCS->slice->getSPS()->getUseColorTrans())
+      {
+        bestCS->tmpColorSpaceCost = tempCS->tmpColorSpaceCost;
+        bestCS->firstColorSpaceSelected = tempCS->firstColorSpaceSelected;
+      }
+#endif
       numRDOTried++;
 
       xEncodeDontSplit( *tempCS, partitioner );
diff --git a/source/Lib/EncoderLib/EncCu.h b/source/Lib/EncoderLib/EncCu.h
index 42adec40484de60fc601de09c8326fc4a1f27172..5a7efe54bf32496cfd7ecd96231f7f7d31833f96 100644
--- a/source/Lib/EncoderLib/EncCu.h
+++ b/source/Lib/EncoderLib/EncCu.h
@@ -192,7 +192,11 @@ protected:
 
   void xCheckModeSplit        ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode, const ModeType modeTypeParent, bool &skipInterPass );
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool xCheckRDCostIntra(CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode, bool adaptiveColorTrans);
+#else
   void xCheckRDCostIntra      ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode );
+#endif
 
   void xCheckDQP              ( CodingStructure& cs, Partitioner& partitioner, bool bKeepCtx = false);
   void xFillPCMBuffer         ( CodingUnit &cu);
diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp
index 8ac19750753fe4681b37b517d4d40bdf9d5afa21..7734dd7e6953a8e793ca1b943d82abe3e9eb1f3f 100644
--- a/source/Lib/EncoderLib/EncLib.cpp
+++ b/source/Lib/EncoderLib/EncLib.cpp
@@ -1397,6 +1397,9 @@ void EncLib::xInitSPS(SPS &sps)
   sps.setBdofDmvrSlicePresentFlag(m_DMVR || m_BIO);
   sps.setAffineAmvrEnabledFlag              ( m_AffineAmvr );
   sps.setUseDMVR                            ( m_DMVR );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  sps.setUseColorTrans(m_useColorTrans);
+#endif
   sps.setPLTMode                            ( m_PLTMode);
   sps.setIBCFlag                            ( m_IBCMode);
   sps.setWrapAroundEnabledFlag                      ( m_wrapAround );
diff --git a/source/Lib/EncoderLib/EncModeCtrl.cpp b/source/Lib/EncoderLib/EncModeCtrl.cpp
index cfc71f5440569c4d5b2645a86583aac3eedc3bd6..266cc3a998e9acea19ca46d8412dc326801e5c4d 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.cpp
+++ b/source/Lib/EncoderLib/EncModeCtrl.cpp
@@ -440,6 +440,16 @@ bool CacheBlkInfoCtrl::isSkip( const UnitArea& area )
   return m_codedCUInfo[idx1][idx2][idx3][idx4]->isSkip;
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+char CacheBlkInfoCtrl::getSelectColorSpaceOption(const UnitArea& area)
+{
+  unsigned idx1, idx2, idx3, idx4;
+  getAreaIdx(area.Y(), *m_slice_chblk->getPPS()->pcv, idx1, idx2, idx3, idx4);
+
+  return m_codedCUInfo[idx1][idx2][idx3][idx4]->selectColorSpaceOption;
+}
+#endif
+
 bool CacheBlkInfoCtrl::isMMVDSkip(const UnitArea& area)
 {
   unsigned idx1, idx2, idx3, idx4;
@@ -1901,11 +1911,65 @@ bool EncModeCtrlMTnoRQT::tryMode( const EncTestMode& encTestmode, const CodingSt
           relatedCU.isSkip   |= bestCU->skip;
           relatedCU.isMMVDSkip |= bestCU->mmvdSkip;
           relatedCU.GBiIdx    = bestCU->GBiIdx;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if (bestCU->slice->getSPS()->getUseColorTrans())
+          {
+            if (m_pcEncCfg->getRGBFormatFlag())
+            {
+              if (bestCU->colorTransform && bestCU->rootCbf)
+              {
+                relatedCU.selectColorSpaceOption = 1;
+              }
+              else
+              {
+                relatedCU.selectColorSpaceOption = 2;
+              }
+            }
+            else
+            {
+              if (!bestCU->colorTransform || !bestCU->rootCbf)
+              {
+                relatedCU.selectColorSpaceOption = 1;
+              }
+              else
+              {
+                relatedCU.selectColorSpaceOption = 2;
+              }
+            }
+          }
+#endif
         }
         else if (CU::isIBC(*bestCU))
         {
           relatedCU.isIBC = true;
           relatedCU.isSkip |= bestCU->skip;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if (bestCU->slice->getSPS()->getUseColorTrans())
+          {
+            if (m_pcEncCfg->getRGBFormatFlag())
+            {
+              if (bestCU->colorTransform && bestCU->rootCbf)
+              {
+                relatedCU.selectColorSpaceOption = 1;
+              }
+              else
+              {
+                relatedCU.selectColorSpaceOption = 2;
+              }
+            }
+            else
+            {
+              if (!bestCU->colorTransform || !bestCU->rootCbf)
+              {
+                relatedCU.selectColorSpaceOption = 1;
+              }
+              else
+              {
+                relatedCU.selectColorSpaceOption = 2;
+              }
+            }
+          }
+#endif
         }
         else if( CU::isIntra( *bestCU ) )
         {
diff --git a/source/Lib/EncoderLib/EncModeCtrl.h b/source/Lib/EncoderLib/EncModeCtrl.h
index 38a3fc072271ed0f722bc5bc7481fe8af0bf0b31..55947d907ad30cb647d9b44ed66b681112abf56d 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.h
+++ b/source/Lib/EncoderLib/EncModeCtrl.h
@@ -439,6 +439,9 @@ struct CodedCUInfo
   Mv   saveMv [NUM_REF_PIC_LIST_01][MAX_STORED_CU_INFO_REFS];
 
   uint8_t GBiIdx;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  char    selectColorSpaceOption;  // 0 - test both two color spaces; 1 - only test the first color spaces; 2 - only test the second color spaces
+#endif
 #if JVET_P1026_ISP_LFNST_COMBINATION
   uint16_t ispPredModeVal;
   double   bestDCT2NonISPCost;
@@ -498,6 +501,10 @@ public:
   bool  getInter( const UnitArea& area );
   void  setGbiIdx( const UnitArea& area, uint8_t gBiIdx );
   uint8_t getGbiIdx( const UnitArea& area );
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  char  getSelectColorSpaceOption(const UnitArea& area);
+#endif
 };
 
 #if REUSE_CU_RESULTS
diff --git a/source/Lib/EncoderLib/EncSlice.cpp b/source/Lib/EncoderLib/EncSlice.cpp
index a0a043c4180840b9643da97ff07abb35bd9352f8..f7a1c54876cda07e122b79bd4be36f10346aa661 100644
--- a/source/Lib/EncoderLib/EncSlice.cpp
+++ b/source/Lib/EncoderLib/EncSlice.cpp
@@ -107,6 +107,10 @@ void EncSlice::init( EncLib* pcEncLib, const SPS& sps )
 void
 EncSlice::setUpLambda( Slice* slice, const double dLambda, int iQP)
 {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_pcRdCost->resetStore();
+  m_pcTrQuant->resetStore();
+#endif
   // store lambda
   m_pcRdCost ->setLambda( dLambda, slice->getSPS()->getBitDepths() );
 
diff --git a/source/Lib/EncoderLib/InterSearch.cpp b/source/Lib/EncoderLib/InterSearch.cpp
index d18b39381248a9bc458a357d713bed0b89558c1d..c667b281065a0e7ab697a244a3300fda2a2d05ff 100644
--- a/source/Lib/EncoderLib/InterSearch.cpp
+++ b/source/Lib/EncoderLib/InterSearch.cpp
@@ -6520,6 +6520,9 @@ uint8_t InterSearch::skipSbtByRDCost( int width, int height, int mtDepth, uint8_
 
 void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &partitioner, Distortion *puiZeroDist /*= NULL*/
   , const bool luma, const bool chroma
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  , PelUnitBuf* orgResi
+#endif
 )
 {
   const UnitArea& currArea = partitioner.currArea();
@@ -6530,6 +6533,9 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
   const uint32_t numTBlocks    = getNumberValidTBlocks   ( *cs.pcv );
   const CodingUnit &cu = *cs.getCU(partitioner.chType);
   const unsigned currDepth = partitioner.currTrDepth;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  const bool colorTransFlag = cs.cus[0]->colorTransform;
+#endif
 
   bool bCheckFull  = !partitioner.canSplit( TU_MAX_TR_SPLIT, cs );
   if( cu.sbtInfo && partitioner.canSplit( PartSplit( cu.getSbtTuSplit() ), cs ) )
@@ -6552,6 +6558,9 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 
   Distortion uiSingleDist         = 0;
   Distortion uiSingleDistComp [3] = { 0, 0, 0 };
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  uint64_t   uiSingleFracBits[3] = { 0, 0, 0 };
+#endif
   TCoeff     uiAbsSum         [3] = { 0, 0, 0 };
 
   const TempCtx ctxStart  ( m_CtxCache, m_CABACEstimator->getCtx() );
@@ -6567,6 +6576,11 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
     tu.mtsIdx         = MTS_DCT2_DCT2;
 #endif
     tu.checkTuNoResidual( partitioner.currPartIdx() );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    Position tuPos = tu.Y();
+    tuPos.relativeTo(cu.Y());
+    const UnitArea relativeUnitArea(tu.chromaFormat, Area(tuPos, tu.Y().size()));
+#endif
 
     const Slice           &slice = *cs.slice;
 #if JVET_P1006_PICTURE_HEADER
@@ -6668,6 +6682,15 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 #endif
         }
       }
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      if (colorTransFlag)
+      {
+        m_pcTrQuant->lambdaAdjustColorTrans(true);
+        m_pcRdCost->lambdaAdjustColorTrans(true, compID);
+      }
+#endif
+
       const int crossCPredictionModesToTest = preCalcAlpha != 0 ? 2 : 1;
       const int numTransformCandidates = nNumTransformCands;
       const bool isOneMode                  = crossCPredictionModesToTest == 1 && numTransformCandidates == 1;
@@ -6719,7 +6742,20 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
           }
           tu.compAlpha[compID]      = bUseCrossCPrediction ? preCalcAlpha : 0;
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          QpParam cQP(tu, compID);  // note: uses tu.transformSkip[compID]
+          if (colorTransFlag)
+          {
+            for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+            {
+              cQP.Qps[qpIdx] = cQP.Qps[qpIdx] + (compID == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+              cQP.pers[qpIdx] = cQP.Qps[qpIdx] / 6;
+              cQP.rems[qpIdx] = cQP.Qps[qpIdx] % 6;
+            }
+          }
+#else
           const QpParam cQP(tu, compID);  // note: uses tu.transformSkip[compID]
+#endif
 
 #if RDOQ_CHROMA_LAMBDA
           m_pcTrQuant->selectLambda(compID);
@@ -6824,7 +6860,18 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
             }
             else
 #endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+              if (cs.slice->getSPS()->getUseColorTrans())
+              {
+                nonCoeffCost = m_pcRdCost->calcRdCost(nonCoeffFracBits, nonCoeffDist, false);
+              }
+              else
+              {
+                nonCoeffCost = m_pcRdCost->calcRdCost(nonCoeffFracBits, nonCoeffDist);
+              }
+#else
             nonCoeffCost     = m_pcRdCost->calcRdCost(nonCoeffFracBits, nonCoeffDist);
+#endif
           }
 
           if ((puiZeroDist != NULL) && isFirstMode)
@@ -6925,6 +6972,9 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 
             uiAbsSum[compID]         = currAbsSum;
             uiSingleDistComp[compID] = currCompDist;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+            uiSingleFracBits[compID] = currCompFracBits;
+#endif
             minCost[compID]          = currCompCost;
 
             if (uiAbsSum[compID] == 0)
@@ -6959,8 +7009,33 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
         tu.copyComponentFrom( bestTU, compID );
         csFull->getResiBuf( compArea ).copyFrom( saveCS.getResiBuf( compArea ) );
       }
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      if (colorTransFlag)
+      {
+        m_pcTrQuant->lambdaAdjustColorTrans(false);
+        m_pcRdCost->lambdaAdjustColorTrans(false, compID);
+      }
+#endif 
+
     } // component loop
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    if (colorTransFlag)
+    {
+      PelUnitBuf     orgResidual = orgResi->subBuf(relativeUnitArea);
+      PelUnitBuf     invColorTransResidual = m_colorTransResiBuf[2].getBuf(relativeUnitArea);
+      csFull->getResiBuf(currArea).colorSpaceConvert(invColorTransResidual, false);
+
+      for (uint32_t c = 0; c < numTBlocks; c++)
+      {
+        const ComponentID compID = (ComponentID)c;
+        uiSingleDistComp[c] = m_pcRdCost->getDistPart(orgResidual.bufs[c], invColorTransResidual.bufs[c], sps.getBitDepth(toChannelType(compID)), compID, DF_SSE);
+        minCost[c] = m_pcRdCost->calcRdCost(uiSingleFracBits[c], uiSingleDistComp[c]);
+      }
+    }
+#endif
+
     if ( chroma && tu.blocks[COMPONENT_Cb].valid() )
     {
       const CompArea& cbArea = tu.blocks[COMPONENT_Cb];
@@ -6974,6 +7049,12 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 #endif
                                && tu.blocks[COMPONENT_Cb].width * tu.blocks[COMPONENT_Cb].height > 4;
       double minCostCbCr = minCost[COMPONENT_Cb] + minCost[COMPONENT_Cr];
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      if (colorTransFlag)
+      {
+        minCostCbCr += minCost[COMPONENT_Y];  // ACT should consider three-component cost
+      }
+#endif
       bool   isLastBest  = false;
 
       CompStorage      orgResiCb[4], orgResiCr[4];   // 0:std, 1-3:jointCbCr
@@ -7006,10 +7087,26 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
         // encoder bugfix: initialize mtsIdx for chroma under JointCbCrMode.
         tu.mtsIdx[COMPONENT_Cb] = tu.mtsIdx[COMPONENT_Cr] = MTS_DCT2_DCT2;
 #endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        int         codedCbfMask = 0;
+        ComponentID codeCompId = (tu.jointCbCr >> 1 ? COMPONENT_Cb : COMPONENT_Cr);
+        ComponentID otherCompId = (codeCompId == COMPONENT_Cr ? COMPONENT_Cb : COMPONENT_Cr);
+
+        if (colorTransFlag)
+        {
+          m_pcTrQuant->lambdaAdjustColorTrans(true);
+          m_pcTrQuant->selectLambda(codeCompId);
+        }
+        else
+        {
+          m_pcTrQuant->selectLambda(codeCompId);
+        }
+#else
         const QpParam cQP(tu, COMPONENT_Cb);  // note: uses tu.transformSkip[compID]
 
 #if RDOQ_CHROMA_LAMBDA
         m_pcTrQuant->selectLambda(COMPONENT_Cb);
+#endif
 #endif
         // Lambda is loosened for the joint mode with respect to single modes as the same residual is used for both chroma blocks
         const int    absIct = abs( TU::getICTMode(tu) );
@@ -7034,10 +7131,24 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
           m_pcTrQuant->setLambda(m_pcTrQuant->getLambda() / (cRescale*cRescale));
         }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        Distortion currCompDistY = MAX_UINT64;
+        QpParam qpCbCr(tu, codeCompId);
+        if (colorTransFlag)
+        {
+          for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+          {
+            qpCbCr.Qps[qpIdx] = qpCbCr.Qps[qpIdx] + (codeCompId == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+            qpCbCr.pers[qpIdx] = qpCbCr.Qps[qpIdx] / 6;
+            qpCbCr.rems[qpIdx] = qpCbCr.Qps[qpIdx] % 6;
+          }
+        }
+#else
         int         codedCbfMask = 0;
         ComponentID codeCompId   = (tu.jointCbCr >> 1 ? COMPONENT_Cb : COMPONENT_Cr);
         ComponentID otherCompId  = (codeCompId == COMPONENT_Cr ? COMPONENT_Cb : COMPONENT_Cr);
         const QpParam qpCbCr(tu, codeCompId);
+#endif
 
         tu.getCoeffs(otherCompId).fill(0);   // do we need that?
         TU::setCbfAtDepth(tu, otherCompId, tu.depth, false);
@@ -7084,12 +7195,30 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
             crResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(COMPONENT_Cr));
           }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if (colorTransFlag)
+          {
+            PelUnitBuf     orgResidual = orgResi->subBuf(relativeUnitArea);
+            PelUnitBuf     invColorTransResidual = m_colorTransResiBuf[2].getBuf(relativeUnitArea);
+            csFull->getResiBuf(currArea).colorSpaceConvert(invColorTransResidual, false);
+
+            currCompDistY = m_pcRdCost->getDistPart(orgResidual.bufs[COMPONENT_Y], invColorTransResidual.bufs[COMPONENT_Y], sps.getBitDepth(toChannelType(COMPONENT_Y)), COMPONENT_Y, DF_SSE);
+            currCompDistCb = m_pcRdCost->getDistPart(orgResidual.bufs[COMPONENT_Cb], invColorTransResidual.bufs[COMPONENT_Cb], sps.getBitDepth(toChannelType(COMPONENT_Cb)), COMPONENT_Cb, DF_SSE);
+            currCompDistCr = m_pcRdCost->getDistPart(orgResidual.bufs[COMPONENT_Cr], invColorTransResidual.bufs[COMPONENT_Cr], sps.getBitDepth(toChannelType(COMPONENT_Cr)), COMPONENT_Cr, DF_SSE);
+            currCompCost = m_pcRdCost->calcRdCost(uiSingleFracBits[COMPONENT_Y] + currCompFracBits, currCompDistY + currCompDistCr + currCompDistCb, false);
+          }
+          else
+          {
+#endif
           currCompDistCb = m_pcRdCost->getDistPart(csFull->getOrgResiBuf(cbArea), cbResi, channelBitDepth, COMPONENT_Cb, DF_SSE);
           currCompDistCr = m_pcRdCost->getDistPart(csFull->getOrgResiBuf(crArea), crResi, channelBitDepth, COMPONENT_Cr, DF_SSE);
 #if WCG_EXT
           currCompCost   = m_pcRdCost->calcRdCost(currCompFracBits, currCompDistCr + currCompDistCb, false);
 #else
           currCompCost   = m_pcRdCost->calcRdCost(currCompFracBits, currCompDistCr + currCompDistCb);
+#endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          }
 #endif
         }
         else
@@ -7102,6 +7231,12 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
           uiAbsSum[COMPONENT_Cr]         = currAbsSum;
           uiSingleDistComp[COMPONENT_Cb] = currCompDistCb;
           uiSingleDistComp[COMPONENT_Cr] = currCompDistCr;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if (colorTransFlag)
+          {
+            uiSingleDistComp[COMPONENT_Y] = currCompDistY;
+          }
+#endif
           minCostCbCr                    = currCompCost;
           isLastBest = (cbfMask == jointCbfMasksToTest.back());
           if (!isLastBest)
@@ -7121,6 +7256,12 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
           csFull->getResiBuf( cbArea ).copyFrom( saveCS.getResiBuf( cbArea ) );
           csFull->getResiBuf( crArea ).copyFrom( saveCS.getResiBuf( crArea ) );
         }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        if (colorTransFlag)
+        {
+          m_pcTrQuant->lambdaAdjustColorTrans(false);
+        }
+#endif
       }
     }
 
@@ -7209,6 +7350,9 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
     {
       xEstimateInterResidualQT(*csSplit, partitioner, bCheckFull ? nullptr : puiZeroDist
         , luma, chroma
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        , orgResi
+#endif
       );
 
       csSplit->cost = m_pcRdCost->calcRdCost( csSplit->fracBits, csSplit->dist );
@@ -7298,10 +7442,22 @@ void InterSearch::encodeResAndCalcRdInterCU(CodingStructure &cs, Partitioner &pa
   const SPS &sps                = *cs.sps;
   const PPS &pps                = *cs.pps;
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool colorTransAllowed = cs.slice->getSPS()->getUseColorTrans() && luma && chroma;
+  if (cs.slice->getSPS()->getUseColorTrans())
+  {
+    CHECK(cu.treeType != TREE_D || partitioner.treeType != TREE_D, "localtree should not be applied when adaptive color transform is enabled");
+    CHECK(cu.modeType != MODE_TYPE_ALL || partitioner.modeType != MODE_TYPE_ALL, "localtree should not be applied when adaptive color transform is enabled");
+  }
+#endif 
+
   if( skipResidual ) //  No residual coding : SKIP mode
   {
     cu.skip    = true;
     cu.rootCbf = false;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    cu.colorTransform = false;
+#endif 
     CHECK( cu.sbtInfo != 0, "sbtInfo shall be 0 if CU has no residual" );
     cs.getResiBuf().fill(0);
     {
@@ -7405,20 +7561,152 @@ void InterSearch::encodeResAndCalcRdInterCU(CodingStructure &cs, Partitioner &pa
     cs.getResiBuf().bufs[1].subtract(cs.getPredBuf().bufs[1]);
     cs.getResiBuf().bufs[2].subtract(cs.getPredBuf().bufs[2]);
   }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  const UnitArea curUnitArea = partitioner.currArea();
+  CodingStructure &saveCS = *m_pSaveCS[1];
+  saveCS.pcv = cs.pcv;
+  saveCS.picture = cs.picture;
+  saveCS.area.repositionTo(curUnitArea);
+  saveCS.clearCUs();
+  saveCS.clearPUs();
+  saveCS.clearTUs();
+  for (const auto &ppcu : cs.cus)
+  {
+    CodingUnit &pcu = saveCS.addCU(*ppcu, ppcu->chType);
+    pcu = *ppcu;
+  }
+  for (const auto &ppu : cs.pus)
+  {
+    PredictionUnit &pu = saveCS.addPU(*ppu, ppu->chType);
+    pu = *ppu;
+  }
+
+  PelUnitBuf orgResidual, colorTransResidual;
+  const UnitArea localUnitArea(cs.area.chromaFormat, Area(0, 0, cu.Y().width, cu.Y().height));
+  orgResidual = m_colorTransResiBuf[0].getBuf(localUnitArea);
+  colorTransResidual = m_colorTransResiBuf[1].getBuf(localUnitArea);
+  orgResidual.copyFrom(cs.getResiBuf());
+  if (colorTransAllowed)
+  {
+    cs.getResiBuf().colorSpaceConvert(colorTransResidual, true);
+  }
+
+  const TempCtx ctxStart(m_CtxCache, m_CABACEstimator->getCtx());
+  int           numAllowedColorSpace = (colorTransAllowed ? 2 : 1);
+  Distortion    zeroDistortion = 0;
+
+  double  bestCost = MAX_DOUBLE;
+  bool    bestColorTrans = false;
+  bool    bestRootCbf = false;
+  uint8_t bestsbtInfo = 0;
+  uint8_t orgSbtInfo = cu.sbtInfo;
+  int     bestIter = 0;
+
+  auto blkCache = dynamic_cast<CacheBlkInfoCtrl*>(m_modeCtrl);
+  bool rootCbfFirstColorSpace = true;
+
+  for (int iter = 0; iter < numAllowedColorSpace; iter++)
+  {
+    if (colorTransAllowed && !m_pcEncCfg->getRGBFormatFlag() && iter)
+    {
+      continue;
+    }
+    char colorSpaceOption = blkCache->getSelectColorSpaceOption(cu);
+    if (colorTransAllowed)
+    {
+      if (colorSpaceOption)
+      {
+        CHECK(colorSpaceOption > 2 || colorSpaceOption < 0, "invalid color space selection option");
+        if (colorSpaceOption == 1 && iter)
+        {
+          continue;
+        }
+        if (colorSpaceOption == 2 && !iter)
+        {
+          continue;
+        }
+      }
+    }
+    if (!colorSpaceOption)
+    {
+      if (iter && !rootCbfFirstColorSpace)
+      {
+        continue;
+      }
+      if (colorTransAllowed && cs.bestParent && cs.bestParent->tmpColorSpaceCost != MAX_DOUBLE)
+      {
+        if (cs.bestParent->firstColorSpaceSelected && iter)
+        {
+          continue;
+        }
+        if (m_pcEncCfg->getRGBFormatFlag())
+        {
+          if (!cs.bestParent->firstColorSpaceSelected && !iter)
+          {
+            continue;
+          }
+        }
+      }
+    }
+    bool colorTransFlag = (colorTransAllowed && m_pcEncCfg->getRGBFormatFlag()) ? (1 - iter) : iter;
+    cu.colorTransform = colorTransFlag;
+    cu.sbtInfo = orgSbtInfo;
+
+    m_CABACEstimator->resetBits();
+    m_CABACEstimator->getCtx() = ctxStart;
+    cs.clearTUs();
+    cs.fracBits = 0;
+    cs.dist = 0;
+    cs.cost = 0;
+#else
   Distortion zeroDistortion = 0;
 
   const TempCtx ctxStart( m_CtxCache, m_CABACEstimator->getCtx() );
+#endif
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (colorTransFlag)
+  {
+    cs.getOrgResiBuf().bufs[0].copyFrom(colorTransResidual.bufs[0]);
+    cs.getOrgResiBuf().bufs[1].copyFrom(colorTransResidual.bufs[1]);
+    cs.getOrgResiBuf().bufs[2].copyFrom(colorTransResidual.bufs[2]);
 
+    memset(m_pTempPel, 0, sizeof(Pel) * localUnitArea.blocks[0].area());
+    zeroDistortion = 0;
+    for (int compIdx = 0; compIdx < 3; compIdx++)
+    {
+      ComponentID componentID = (ComponentID)compIdx;
+      const CPelBuf zeroBuf(m_pTempPel, localUnitArea.blocks[compIdx]);
+      zeroDistortion += m_pcRdCost->getDistPart(zeroBuf, orgResidual.bufs[compIdx], sps.getBitDepth(toChannelType(componentID)), componentID, DF_SSE);
+    }
+    xEstimateInterResidualQT(cs, partitioner, NULL, luma, chroma, &orgResidual);
+  }
+  else
+  {
+    zeroDistortion = 0;
+#endif
   if (luma)
   {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    cs.getOrgResiBuf().bufs[0].copyFrom(orgResidual.bufs[0]);
+#else
     cs.getOrgResiBuf().bufs[0].copyFrom(cs.getResiBuf().bufs[0]);
+#endif
   }
   if (chroma)
   {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    cs.getOrgResiBuf().bufs[1].copyFrom(orgResidual.bufs[1]);
+    cs.getOrgResiBuf().bufs[2].copyFrom(orgResidual.bufs[2]);
+#else
     cs.getOrgResiBuf().bufs[1].copyFrom(cs.getResiBuf().bufs[1]);
     cs.getOrgResiBuf().bufs[2].copyFrom(cs.getResiBuf().bufs[2]);
+#endif
   }
   xEstimateInterResidualQT(cs, partitioner, &zeroDistortion, luma, chroma);
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  }
+#endif
   TransformUnit &firstTU = *cs.getTU( partitioner.chType );
 
   cu.rootCbf = false;
@@ -7449,6 +7737,10 @@ void InterSearch::encodeResAndCalcRdInterCU(CodingStructure &cs, Partitioner &pa
 
   if (zeroCost < cs.cost || !cu.rootCbf)
   {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    cs.cost = zeroCost;
+    cu.colorTransform = false;
+#endif
     cu.sbtInfo = 0;
     cu.rootCbf = false;
 
@@ -7463,7 +7755,52 @@ void InterSearch::encodeResAndCalcRdInterCU(CodingStructure &cs, Partitioner &pa
     }
     cu.firstTU = cu.lastTU = &tu;
   }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (!iter)
+  {
+    rootCbfFirstColorSpace = cu.rootCbf;
+  }
+  if (cs.cost < bestCost)
+  {
+    bestIter = iter;
+    if (cu.rootCbf && cu.colorTransform)
+    {
+      cs.getResiBuf(curUnitArea).colorSpaceConvert(cs.getResiBuf(curUnitArea), false);
+    }
+
+    if (iter != (numAllowedColorSpace - 1))
+    {
+      bestCost = cs.cost;
+      bestColorTrans = cu.colorTransform;
+      bestRootCbf = cu.rootCbf;
+      bestsbtInfo = cu.sbtInfo;
+
+      saveCS.clearTUs();
+      for (const auto &ptu : cs.tus)
+      {
+        TransformUnit &tu = saveCS.addTU(*ptu, ptu->chType);
+        tu = *ptu;
+      }
+      saveCS.getResiBuf(curUnitArea).copyFrom(cs.getResiBuf(curUnitArea));
+    }
+  }
+  }
+
+  if (bestIter != (numAllowedColorSpace - 1))
+  {
+    cu.colorTransform = bestColorTrans;
+    cu.rootCbf = bestRootCbf;
+    cu.sbtInfo = bestsbtInfo;
 
+    cs.clearTUs();
+    for (const auto &ptu : saveCS.tus)
+    {
+      TransformUnit &tu = cs.addTU(*ptu, ptu->chType);
+      tu = *ptu;
+    }
+    cs.getResiBuf(curUnitArea).copyFrom(saveCS.getResiBuf(curUnitArea));
+  }
+#endif
 
   // all decisions now made. Fully encode the CU, including the headers:
   m_CABACEstimator->getCtx() = ctxStart;
@@ -7564,6 +7901,23 @@ void InterSearch::encodeResAndCalcRdInterCU(CodingStructure &cs, Partitioner &pa
   cs.dist     = finalDistortion;
   cs.fracBits = finalFracBits;
   cs.cost     = m_pcRdCost->calcRdCost(cs.fracBits, cs.dist);
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (cs.slice->getSPS()->getUseColorTrans())
+  {
+    if (cs.cost < cs.tmpColorSpaceCost)
+    {
+      cs.tmpColorSpaceCost = cs.cost;
+      if (m_pcEncCfg->getRGBFormatFlag())
+      {
+        cs.firstColorSpaceSelected = cu.colorTransform || !cu.rootCbf;
+      }
+      else
+      {
+        cs.firstColorSpaceSelected = !cu.colorTransform || !cu.rootCbf;
+      }
+    }
+  }
+#endif
 
   CHECK(cs.tus.size() == 0, "No TUs present");
 }
@@ -7578,7 +7932,9 @@ uint64_t InterSearch::xGetSymbolFracBitsInter(CodingStructure &cs, Partitioner &
   if( cu.firstPU->mergeFlag && !cu.rootCbf )
   {
     cu.skip = true;
-
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    CHECK(cu.colorTransform, "ACT should not be enabled for skip mode");
+#endif
     if( cs.pps->getTransquantBypassEnabledFlag() )
     {
       m_CABACEstimator->cu_transquant_bypass_flag( cu );
diff --git a/source/Lib/EncoderLib/InterSearch.h b/source/Lib/EncoderLib/InterSearch.h
index bd0982b2e12e509ea661bb0d92a0fc9967f662e1..de6ed7729f6470d85a1600c02ab0d97d9c66c8b1 100644
--- a/source/Lib/EncoderLib/InterSearch.h
+++ b/source/Lib/EncoderLib/InterSearch.h
@@ -600,6 +600,9 @@ public:
   void xEncodeInterResidualQT     (CodingStructure &cs, Partitioner &partitioner, const ComponentID &compID);
   void xEstimateInterResidualQT   (CodingStructure &cs, Partitioner &partitioner, Distortion *puiZeroDist = NULL
     , const bool luma = true, const bool chroma = true
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+    , PelUnitBuf* orgResi = NULL
+#endif
   );
   uint64_t xGetSymbolFracBitsInter  (CodingStructure &cs, Partitioner &partitioner);
   uint64_t xCalcPuMeBits            (PredictionUnit& pu);
diff --git a/source/Lib/EncoderLib/IntraSearch.cpp b/source/Lib/EncoderLib/IntraSearch.cpp
index edfc7e944595e0512797b06d5fb4d839658791c6..e99c01be6140dcebb85af90edc6f6404f6bf065f 100644
--- a/source/Lib/EncoderLib/IntraSearch.cpp
+++ b/source/Lib/EncoderLib/IntraSearch.cpp
@@ -163,6 +163,9 @@ void IntraSearch::destroy()
   }
 
   m_tmpStorageLCU.destroy();
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_colorTransResiBuf.destroy();
+#endif 
   m_isInitialized = false;
 #if JVET_P0077_LINE_CG_PALETTE
   if (m_truncBinBits != nullptr)
@@ -238,6 +241,9 @@ void IntraSearch::init( EncCfg*        pcEncCfg,
 
   IntraPrediction::init( cform, pcEncCfg->getBitDepth( CHANNEL_TYPE_LUMA ) );
   m_tmpStorageLCU.create(UnitArea(cform, Area(0, 0, MAX_CU_SIZE, MAX_CU_SIZE)));
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  m_colorTransResiBuf.create(UnitArea(cform, Area(0, 0, MAX_CU_SIZE, MAX_CU_SIZE)));
+#endif
 
   for( uint32_t ch = 0; ch < MAX_NUM_TBLOCKS; ch++ )
   {
@@ -370,7 +376,11 @@ double IntraSearch::findInterCUCost( CodingUnit &cu )
   return COST_UNKNOWN;
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, const double bestCostSoFar, bool mtsCheckRangeFlag, int mtsFirstCheckId, int mtsLastCheckId, bool moreProbMTSIdxFirst, CodingStructure* bestCS)
+#else
 bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner, const double bestCostSoFar, bool mtsCheckRangeFlag, int mtsFirstCheckId, int mtsLastCheckId, bool moreProbMTSIdxFirst )
+#endif
 {
   CodingStructure       &cs            = *cu.cs;
   const SPS             &sps           = *cs.sps;
@@ -421,13 +431,36 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
     mtsUsageFlag = 0;
   }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+  const bool colorTransformIsEnabled = sps.getUseColorTrans() && !CS::isDualITree(cs);
+  const bool isFirstColorSpace       = colorTransformIsEnabled && ((m_pcEncCfg->getRGBFormatFlag() && cu.colorTransform) || (!m_pcEncCfg->getRGBFormatFlag() && !cu.colorTransform));
+  const bool isSecondColorSpace      = colorTransformIsEnabled && ((m_pcEncCfg->getRGBFormatFlag() && !cu.colorTransform) || (!m_pcEncCfg->getRGBFormatFlag() && cu.colorTransform));
+#endif
+
   double bestCurrentCost = bestCostSoFar;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool ispCanBeUsed   = sps.getUseISP() && cu.mtsFlag == 0 && cu.lfnstIdx == 0 && CU::canUseISP(width, height, cu.cs->sps->getMaxTbSize());
+  bool saveDataForISP = ispCanBeUsed && (!colorTransformIsEnabled || isFirstColorSpace);
+  bool testISP        = ispCanBeUsed && (!colorTransformIsEnabled || !cu.colorTransform);
+#else
   bool testISP = sps.getUseISP() && cu.mtsFlag == 0 && cu.lfnstIdx == 0 && CU::canUseISP( width, height, cu.cs->sps->getMaxTbSize() );
+#endif
+
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if ( saveDataForISP )
+  {
+    //reset the intra modes lists variables
+    m_ispCandListHor.clear();
+    m_ispCandListVer.clear();
+  }
+#endif
   if( testISP )
   {
     //reset the variables used for the tests
+#if !JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
     m_ispCandListHor.clear();
     m_ispCandListVer.clear();
+#endif
     m_regIntraRDListWithCosts.clear();
     int numTotalPartsHor = (int)width  >> floorLog2(CU::getISPSplitDim(width, height, TU_1D_VERT_SPLIT));
     int numTotalPartsVer = (int)height >> floorLog2(CU::getISPSplitDim(width, height, TU_1D_HORZ_SPLIT));
@@ -485,6 +518,25 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
     numModesForFullRD = numModesAvailable;
 #endif
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+    if (isSecondColorSpace)
+    {
+      uiRdModeList.clear();
+      if (m_numSavedRdModeFirstColorSpace[m_savedRdModeIdx] > 0)
+      {
+        for (int i = 0; i < m_numSavedRdModeFirstColorSpace[m_savedRdModeIdx]; i++)
+        {
+          uiRdModeList.push_back(m_savedRdModeFirstColorSpace[m_savedRdModeIdx][i]);
+        }
+      }
+      else
+      {
+        return false;
+      }
+    }
+    else
+    {
+#endif
     if( mtsUsageFlag != 2 )
     {
       // this should always be true
@@ -671,7 +723,11 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
             }
           }
         }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        if ( saveDataForISP )
+#else
         if ( testISP )
+#endif
         {
           // we save the regular intra modes list
           m_ispCandListHor = uiRdModeList;
@@ -871,7 +927,11 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
               CandCostList.push_back(0);
             }
           }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+          if ( saveDataForISP )
+#else
           if ( testISP )
+#endif
           {
             // we add the MPMs to the list that contains only regular intra modes
             for (int j = 0; j < numCand; j++)
@@ -934,8 +994,6 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
       }
     }
 
-
-
     CHECK( numModesForFullRD != uiRdModeList.size(), "Inconsistent state!" );
 
     // after this point, don't use numModesForFullRD
@@ -971,7 +1029,11 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
             uiRdModeList.push_back(bestMipMode);
           }
         }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        if ( saveDataForISP )
+#else
         if ( testISP )
+#endif
         {
           m_ispCandListHor.resize(std::min<size_t>(m_ispCandListHor.size(), maxSize));
         }
@@ -991,6 +1053,9 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
         return false;
       }
     }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+    }
+#endif
 
     int numNonISPModes = (int)uiRdModeList.size();
 
@@ -1088,19 +1153,34 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
     }
 #endif
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+    for (int mode = isSecondColorSpace ? 0 : -2 * int(testBDPCM); mode < (int)uiRdModeList.size(); mode++)
+#else
     for (int mode = -2 * int(testBDPCM); mode < (int)uiRdModeList.size(); mode++)
+#endif
     {
       // set CU/PU to luma prediction mode
       ModeInfo uiOrgMode;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+      if (sps.getUseColorTrans() && !m_pcEncCfg->getRGBFormatFlag() && isSecondColorSpace && mode)
+      {
+        continue;
+      }
+
+      if (mode < 0 || (isSecondColorSpace && m_savedBDPCMModeFirstColorSpace[m_savedRdModeIdx][mode]))
+      {
+        cu.bdpcmMode = mode < 0 ? -mode : m_savedBDPCMModeFirstColorSpace[m_savedRdModeIdx][mode];
+#else
       if ( mode < 0 )
       {
         cu.bdpcmMode = -mode;
-
+#endif
 #if JVET_P0803_COMBINED_MIP_CLEANUP
         uiOrgMode = ModeInfo( false, false, 0, NOT_INTRA_SUBPARTITIONS, cu.bdpcmMode == 2 ? VER_IDX : HOR_IDX );
 #else
         uiOrgMode = ModeInfo(false, 0, NOT_INTRA_SUBPARTITIONS, cu.bdpcmMode == 2 ? VER_IDX : HOR_IDX);
 #endif
+#if !JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
         cu.mipFlag                     = uiOrgMode.mipFlg;
 #if JVET_P0803_COMBINED_MIP_CLEANUP
         pu.mipTransposedFlag           = uiOrgMode.mipTrFlg;
@@ -1108,18 +1188,25 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
         cu.ispMode                     = uiOrgMode.ispMod;
         pu.multiRefIdx                 = uiOrgMode.mRefId;
         pu.intraDir[CHANNEL_TYPE_LUMA] = uiOrgMode.modeId;
+#endif
       }
       else
       {
         cu.bdpcmMode = 0;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        uiOrgMode = uiRdModeList[mode];
+      }
+      if (!cu.bdpcmMode && uiRdModeList[mode].ispMod == INTRA_SUBPARTITIONS_RESERVED)
+#else      
         if (uiRdModeList[mode].ispMod == INTRA_SUBPARTITIONS_RESERVED)
+#endif
         {
           if (mode == numNonISPModes) // the list needs to be sorted only once
           {
 #if JVET_P1026_ISP_LFNST_COMBINATION
             if (m_pcEncCfg->getUseFastISP())
             {
-              m_modeCtrl->setBestPredModeDCT2( uiBestPUMode.modeId );
+              m_modeCtrl->setBestPredModeDCT2(uiBestPUMode.modeId);
             }
             if (!xSortISPCandList(bestCurrentCost, csBest->cost, uiBestPUMode))
               break;
@@ -1133,8 +1220,13 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
 #if JVET_P1026_ISP_LFNST_COMBINATION
           cu.lfnstIdx = m_curIspLfnstIdx;
 #endif
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+          uiOrgMode = uiRdModeList[mode];
+        }
+#else
         }
         uiOrgMode = uiRdModeList[mode];
+#endif
       cu.mipFlag                     = uiOrgMode.mipFlg;
 #if JVET_P0803_COMBINED_MIP_CLEANUP
       pu.mipTransposedFlag           = uiOrgMode.mipTrFlg;
@@ -1147,7 +1239,13 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
       CHECK(pu.multiRefIdx && (pu.intraDir[0] == PLANAR_IDX), "Error: combination of MRL and Planar mode not supported");
       CHECK(cu.ispMode && cu.mipFlag, "Error: combination of ISP and MIP not supported");
       CHECK(cu.ispMode && pu.multiRefIdx, "Error: combination of ISP and MRL not supported");
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      CHECK(cu.ispMode&& cu.colorTransform, "Error: combination of ISP and ACT not supported");
+
+      pu.intraDir[CHANNEL_TYPE_CHROMA] = cu.colorTransform ? DM_CHROMA_IDX : pu.intraDir[CHANNEL_TYPE_CHROMA];
+#else
       }
+#endif
 
       // set context models
       m_CABACEstimator->getCtx() = ctxStart;
@@ -1185,6 +1283,13 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
       }
       else
       {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+        if (cu.colorTransform)
+        {
+          tmpValidReturn = xRecurIntraCodingACTQT(*csTemp, partitioner, mtsCheckRangeFlag, mtsFirstCheckId, mtsLastCheckId, moreProbMTSIdxFirst);
+        }
+        else
+#endif
         tmpValidReturn = xRecurIntraCodingLumaQT( *csTemp, partitioner, uiBestPUMode.ispMod ? bestCurrentCost : MAX_DOUBLE, -1, TU_NO_ISP, uiBestPUMode.ispMod,
                                                   mtsCheckRangeFlag, mtsFirstCheckId, mtsLastCheckId, moreProbMTSIdxFirst );
       }
@@ -1222,6 +1327,15 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
 
       if( tmpValidReturn )
       {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+        if (isFirstColorSpace)
+        {
+          if (m_pcEncCfg->getRGBFormatFlag() || !cu.ispMode)
+          {
+            sortRdModeListFirstColorSpace(uiOrgMode, csTemp->cost, cu.bdpcmMode, m_savedRdModeFirstColorSpace[m_savedRdModeIdx], m_savedRdCostFirstColorSpace[m_savedRdModeIdx], m_savedBDPCMModeFirstColorSpace[m_savedRdModeIdx], m_numSavedRdModeFirstColorSpace[m_savedRdModeIdx]);
+          }
+        }
+#endif
         // check r-d cost
         if( csTemp->cost < csBest->cost )
         {
@@ -1280,6 +1394,18 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
           }
         }
       }
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      if (sps.getUseColorTrans() && !CS::isDualITree(cs))
+      {
+        if ((m_pcEncCfg->getRGBFormatFlag() && !cu.colorTransform) && csBest->cost != MAX_DOUBLE && bestCS->cost != MAX_DOUBLE && mode >= 0)
+        {
+          if (csBest->cost > bestCS->cost)
+          {
+            break;
+          }
+        }
+      }
+#endif
     } // Mode loop
     cu.ispMode = uiBestPUMode.ispMod;
 #if JVET_P1026_ISP_LFNST_COMBINATION
@@ -1288,6 +1414,13 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
 
     if( validReturn )
     {
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      if (cu.colorTransform)
+      {
+        cs.useSubStructure(*csBest, partitioner.chType, pu, true, true, keepResi, keepResi);
+      }
+      else
+#endif
       cs.useSubStructure( *csBest, partitioner.chType, pu.singleChan( CHANNEL_TYPE_LUMA ), true, true, keepResi, keepResi );
     }
     csBest->releaseIntermediateData();
@@ -1301,6 +1434,12 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
       pu.multiRefIdx = uiBestPUMode.mRefId;
       pu.intraDir[ CHANNEL_TYPE_LUMA ] = uiBestPUMode.modeId;
       cu.bdpcmMode = bestBDPCMMode;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+      if (cu.colorTransform)
+      {
+        CHECK(pu.intraDir[CHANNEL_TYPE_CHROMA] != DM_CHROMA_IDX, "chroma should use DM mode for adaptive color transform");
+      }
+#endif
     }
   }
 
@@ -3629,6 +3768,175 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
   }
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+void IntraSearch::xIntraCodingACTTUBlock(TransformUnit &tu, const ComponentID &compID, Distortion& ruiDist, std::vector<TrMode>* trModes, const bool loadTr)
+{
+  if (!tu.blocks[compID].valid())
+  {
+    CHECK(1, "tu does not exist");
+  }
+
+  CodingStructure     &cs = *tu.cs;
+  const SPS           &sps = *cs.sps;
+  const Slice         &slice = *cs.slice;
+  const CompArea      &area = tu.blocks[compID];
+  const CompArea &crArea = tu.blocks[COMPONENT_Cr];
+
+  PelBuf              piOrgResi = cs.getOrgResiBuf(area);
+  PelBuf              piResi = cs.getResiBuf(area);
+  PelBuf              crOrgResi = cs.getOrgResiBuf(crArea);
+  PelBuf              crResi = cs.getResiBuf(crArea);
+  TCoeff              uiAbsSum = 0;
+
+  CHECK(tu.jointCbCr && compID == COMPONENT_Cr, "wrong combination of compID and jointCbCr");
+  bool jointCbCr = tu.jointCbCr && compID == COMPONENT_Cb;
+
+  m_pcRdCost->setChromaFormat(cs.sps->getChromaFormatIdc());
+
+  m_pcTrQuant->lambdaAdjustColorTrans(true);
+
+  if (jointCbCr)
+  {
+    ComponentID compIdCode = (tu.jointCbCr >> 1 ? COMPONENT_Cb : COMPONENT_Cr);
+    m_pcTrQuant->selectLambda(compIdCode);
+  }
+  else
+  {
+    m_pcTrQuant->selectLambda(compID);
+  }
+
+#if JVET_P1006_PICTURE_HEADER
+  bool flag = slice.getPicHeader()->getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag())) && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
+  if (flag && isChroma(compID) && slice.getPicHeader()->getLmcsChromaResidualScaleFlag())
+#else
+  bool flag = slice.getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag())) && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
+  if (flag && isChroma(compID) && slice.getLmcsChromaResidualScaleFlag())
+#endif
+  {
+    int    cResScaleInv = tu.getChromaAdj();
+    double cResScale = (double)(1 << CSCALE_FP_PREC) / (double)cResScaleInv;
+    m_pcTrQuant->setLambda(m_pcTrQuant->getLambda() / (cResScale*cResScale));
+  }
+
+  if (jointCbCr)
+  {
+    // Lambda is loosened for the joint mode with respect to single modes as the same residual is used for both chroma blocks
+    const int    absIct = abs(TU::getICTMode(tu));
+    const double lfact = (absIct == 1 || absIct == 3 ? 0.8 : 0.5);
+    m_pcTrQuant->setLambda(lfact * m_pcTrQuant->getLambda());
+  }
+  if (sps.getJointCbCrEnabledFlag() && isChroma(compID) && (slice.getSliceQp() > 18))
+  {
+    m_pcTrQuant->setLambda(1.3 * m_pcTrQuant->getLambda());
+  }
+
+  if (isLuma(compID))
+  {
+    QpParam cQP(tu, compID);
+    for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+    {
+      cQP.Qps[qpIdx] = cQP.Qps[qpIdx] + (compID == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+      cQP.pers[qpIdx] = cQP.Qps[qpIdx] / 6;
+      cQP.rems[qpIdx] = cQP.Qps[qpIdx] % 6;
+    }
+
+    if (trModes)
+    {
+#if JVET_P0273_MTSIntraMaxCand
+      m_pcTrQuant->transformNxN(tu, compID, cQP, trModes, m_pcEncCfg->getMTSIntraMaxCand());
+#else
+      m_pcTrQuant->transformNxN(tu, compID, cQP, trModes, CU::isIntra(*tu.cu) ? m_pcEncCfg->getIntraMTSMaxCand() : m_pcEncCfg->getInterMTSMaxCand());
+#endif
+#if JVET_P0058_CHROMA_TS
+      tu.mtsIdx[compID] = trModes->at(0).first;
+#else
+      tu.mtsIdx = trModes->at(0).first;
+#endif
+    }
+    m_pcTrQuant->transformNxN(tu, compID, cQP, uiAbsSum, m_CABACEstimator->getCtx(), loadTr);
+
+    if (uiAbsSum > 0)
+    {
+      m_pcTrQuant->invTransformNxN(tu, compID, piResi, cQP);
+    }
+    else
+    {
+      piResi.fill(0);
+    }
+  }
+  else
+  {
+    int         codedCbfMask = 0;
+    ComponentID codeCompId = (tu.jointCbCr ? (tu.jointCbCr >> 1 ? COMPONENT_Cb : COMPONENT_Cr) : compID);
+    QpParam qpCbCr(tu, codeCompId);
+    for (int qpIdx = 0; qpIdx < 2; qpIdx++)
+    {
+      qpCbCr.Qps[qpIdx] = qpCbCr.Qps[qpIdx] + (codeCompId == COMPONENT_Cr ? DELTA_QP_FOR_Co : DELTA_QP_FOR_Y_Cg);
+      qpCbCr.pers[qpIdx] = qpCbCr.Qps[qpIdx] / 6;
+      qpCbCr.rems[qpIdx] = qpCbCr.Qps[qpIdx] % 6;
+    }
+
+    if (tu.jointCbCr)
+    {
+      ComponentID otherCompId = (codeCompId == COMPONENT_Cr ? COMPONENT_Cb : COMPONENT_Cr);
+      tu.getCoeffs(otherCompId).fill(0);
+      TU::setCbfAtDepth(tu, otherCompId, tu.depth, false);
+    }
+
+    PelBuf& codeResi = (codeCompId == COMPONENT_Cr ? crResi : piResi);
+    uiAbsSum = 0;
+    m_pcTrQuant->transformNxN(tu, codeCompId, qpCbCr, uiAbsSum, m_CABACEstimator->getCtx());
+    if (uiAbsSum > 0)
+    {
+      m_pcTrQuant->invTransformNxN(tu, codeCompId, codeResi, qpCbCr);
+      codedCbfMask += (codeCompId == COMPONENT_Cb ? 2 : 1);
+    }
+    else
+    {
+      codeResi.fill(0);
+    }
+
+    if (tu.jointCbCr)
+    {
+      if (tu.jointCbCr == 3 && codedCbfMask == 2)
+      {
+        codedCbfMask = 3;
+        TU::setCbfAtDepth(tu, COMPONENT_Cr, tu.depth, true);
+      }
+      if (tu.jointCbCr != codedCbfMask)
+      {
+        ruiDist = std::numeric_limits<Distortion>::max();
+        m_pcTrQuant->lambdaAdjustColorTrans(false);
+        return;
+      }
+      m_pcTrQuant->invTransformICT(tu, piResi, crResi);
+      uiAbsSum = codedCbfMask;
+    }
+  }
+
+#if JVET_P1006_PICTURE_HEADER
+  if (flag && uiAbsSum > 0 && isChroma(compID) && slice.getPicHeader()->getLmcsChromaResidualScaleFlag())
+#else
+  if (flag && uiAbsSum > 0 && isChroma(compID) && slice.getLmcsChromaResidualScaleFlag())
+#endif
+  {
+    piResi.scaleSignal(tu.getChromaAdj(), 0, slice.clpRng(compID));
+    if (jointCbCr)
+    {
+      crResi.scaleSignal(tu.getChromaAdj(), 0, slice.clpRng(COMPONENT_Cr));
+    }
+  }
+
+  m_pcTrQuant->lambdaAdjustColorTrans(false);
+
+  ruiDist += m_pcRdCost->getDistPart(piOrgResi, piResi, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE);
+  if (jointCbCr)
+  {
+    ruiDist += m_pcRdCost->getDistPart(crOrgResi, crResi, sps.getBitDepth(toChannelType(COMPONENT_Cr)), COMPONENT_Cr, DF_SSE);
+  }
+}
+#endif
+
 bool IntraSearch::xIntraCodingLumaISP(CodingStructure& cs, Partitioner& partitioner, const double bestCostSoFar)
 {
   int               subTuCounter = 0;
@@ -4300,66 +4608,718 @@ bool IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
   return retVal;
 }
 
-ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitioner& partitioner, const double bestCostSoFar, const PartSplit ispType )
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+bool IntraSearch::xRecurIntraCodingACTQT(CodingStructure &cs, Partitioner &partitioner, bool mtsCheckRangeFlag, int mtsFirstCheckId, int mtsLastCheckId, bool moreProbMTSIdxFirst)
 {
-  UnitArea currArea                   = partitioner.currArea();
-  const bool keepResi                 = cs.sps->getUseLMChroma() || KEEP_PRED_AND_RESI_SIGNALS;
-  if( !currArea.Cb().valid() ) return ChromaCbfs( false );
+  const UnitArea &currArea = partitioner.currArea();
+  uint32_t       currDepth = partitioner.currTrDepth;
+  const Slice    &slice = *cs.slice;
+  const SPS      &sps = *cs.sps;
 
+  bool bCheckFull = !partitioner.canSplit(TU_MAX_TR_SPLIT, cs);
+  bool bCheckSplit = !bCheckFull;
 
-  TransformUnit &currTU               = *cs.getTU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
-  const PredictionUnit &pu            = *cs.getPU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
+  TempCtx ctxStart(m_CtxCache, m_CABACEstimator->getCtx());
+  TempCtx ctxBest(m_CtxCache);
 
-  bool lumaUsesISP                    = false;
-  uint32_t     currDepth                  = partitioner.currTrDepth;
-  const PPS &pps                      = *cs.pps;
-  ChromaCbfs cbfs                     ( false );
+  CodingStructure *csSplit = nullptr;
+  CodingStructure *csFull = nullptr;
+  if (bCheckSplit)
+  {
+    csSplit = &cs;
+  }
+  else if (bCheckFull)
+  {
+    csFull = &cs;
+  }
 
-  if (currDepth == currTU.depth)
+  bool validReturnFull = false;
+
+  if (bCheckFull)
   {
-    if (!currArea.Cb().valid() || !currArea.Cr().valid())
-    {
-      return cbfs;
-    }
+    TransformUnit        &tu = csFull->addTU(CS::getArea(*csFull, currArea, partitioner.chType), partitioner.chType);
+    tu.depth = currDepth;
+    const CodingUnit     &cu = *csFull->getCU(tu.Y().pos(), CHANNEL_TYPE_LUMA);
+    const PredictionUnit &pu = *csFull->getPU(tu.Y().pos(), CHANNEL_TYPE_LUMA);
+    CHECK(!tu.Y().valid() || !tu.Cb().valid() || !tu.Cr().valid(), "Invalid TU");
+    CHECK(tu.cu != &cu, "wrong CU fetch");
+    CHECK(cu.ispMode, "adaptive color transform cannot be applied to ISP");
+    CHECK(pu.intraDir[CHANNEL_TYPE_CHROMA] != DM_CHROMA_IDX, "chroma should use DM mode for adaptive color transform");
 
+    // 1. intra prediction and forward color transform
 
-    CodingStructure &saveCS = *m_pSaveCS[1];
-    saveCS.pcv      = cs.pcv;
-    saveCS.picture  = cs.picture;
-    saveCS.area.repositionTo( cs.area );
-    saveCS.initStructData( MAX_INT, false, true );
+    PelUnitBuf orgBuf = csFull->getOrgBuf(tu);
+    PelUnitBuf predBuf = csFull->getPredBuf(tu);
+    PelUnitBuf resiBuf = csFull->getResiBuf(tu);
+    PelUnitBuf orgResiBuf = csFull->getOrgResiBuf(tu);
 
-    if( !currTU.cu->isSepTree() && currTU.cu->ispMode )
+    for (int i = 0; i < getNumberValidComponents(tu.chromaFormat); i++)
     {
-      saveCS.clearCUs();
-      CodingUnit& auxCU = saveCS.addCU( *currTU.cu, partitioner.chType );
-      auxCU.ispMode = currTU.cu->ispMode;
-      saveCS.sps = currTU.cs->sps;
-      saveCS.clearPUs();
-      saveCS.addPU( *currTU.cu->firstPU, partitioner.chType );
-    }
+      ComponentID          compID = (ComponentID)i;
+      const CompArea       &area = tu.blocks[compID];
+      const ChannelType    chType = toChannelType(compID);
 
-    TransformUnit &tmpTU = saveCS.addTU(currArea, partitioner.chType);
+      PelBuf         piOrg = orgBuf.bufs[compID];
+      PelBuf         piPred = predBuf.bufs[compID];
+      PelBuf         piResi = resiBuf.bufs[compID];
 
+      initIntraPatternChType(*tu.cu, area);
+      if (PU::isMIP(pu, chType))
+      {
+#if JVET_P0803_COMBINED_MIP_CLEANUP
+        initIntraMip(pu, area);
+#endif
+        predIntraMip(compID, piPred, pu);
+      }
+      else
+      {
+        predIntraAng(compID, piPred, pu);
+      }
 
-    cs.setDecomp(currArea.Cb(), true); // set in advance (required for Cb2/Cr2 in 4:2:2 video)
+      piResi.copyFrom(piOrg);
+#if JVET_P1006_PICTURE_HEADER
+      if (slice.getPicHeader()->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag() && compID == COMPONENT_Y)
+#else
+      if (slice.getLmcsEnabledFlag() && m_pcReshape->getCTUFlag() && compID == COMPONENT_Y)
+#endif
+      {
+        CompArea tmpArea(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
+        PelBuf   tmpPred = m_tmpStorageLCU.getBuf(tmpArea);
+        tmpPred.copyFrom(piPred);
+        piResi.rspSignal(m_pcReshape->getFwdLUT());
+        piResi.subtract(tmpPred);
+      }
+      else
+        piResi.subtract(piPred);
+    }
 
-    const unsigned      numTBlocks  = ::getNumberValidTBlocks( *cs.pcv );
+    resiBuf.colorSpaceConvert(orgResiBuf, true);
 
-    CompArea&  cbArea         = currTU.blocks[COMPONENT_Cb];
-    CompArea&  crArea         = currTU.blocks[COMPONENT_Cr];
-    double     bestCostCb     = MAX_DOUBLE;
-    double     bestCostCr     = MAX_DOUBLE;
-    Distortion bestDistCb     = 0;
-    Distortion bestDistCr     = 0;
-    int        maxModesTested = 0;
-    bool       earlyExitISP   = false;
+    // 2. luma residual optimization 
+    double     dSingleCostLuma = MAX_DOUBLE;
+    bool       checkTransformSkip = sps.getTransformSkipEnabledFlag();
+    int        bestLumaModeId = 0;
+    uint8_t    nNumTransformCands = cu.mtsFlag ? 4 : 1;
+    uint8_t    numTransformIndexCands = nNumTransformCands;
 
-    TempCtx ctxStartTU( m_CtxCache );
-    TempCtx ctxStart  ( m_CtxCache );
-    TempCtx ctxBest   ( m_CtxCache );
+    const bool tsAllowed = TU::isTSAllowed(tu, COMPONENT_Y);
+#if JVET_P1026_MTS_SIGNALLING
+    const bool mtsAllowed = CU::isMTSAllowed(cu, COMPONENT_Y);
+#else
+    const bool mtsAllowed = TU::isMTSAllowed(tu, COMPONENT_Y);
+#endif
+    std::vector<TrMode> trModes;
 
-    ctxStartTU       = m_CABACEstimator->getCtx();
+    if (sps.getUseLFNST())
+    {
+      checkTransformSkip &= tsAllowed;
+      checkTransformSkip &= !cu.mtsFlag;
+      checkTransformSkip &= !cu.lfnstIdx;
+
+      if (!cu.mtsFlag && checkTransformSkip)
+      {
+        trModes.push_back(TrMode(0, true)); //DCT2
+        trModes.push_back(TrMode(1, true)); //TS
+      }
+    }
+    else
+    {
+      nNumTransformCands = 1 + (tsAllowed ? 1 : 0) + (mtsAllowed ? 4 : 0); // DCT + TS + 4 MTS = 6 tests
+
+      trModes.push_back(TrMode(0, true)); //DCT2
+      if (tsAllowed)
+      {
+        trModes.push_back(TrMode(1, true));
+      }
+      if (mtsAllowed)
+      {
+        for (int i = 2; i < 6; i++)
+        {
+          trModes.push_back(TrMode(i, true));
+        }
+      }
+    }
+
+    CodingStructure &saveLumaCS = *m_pSaveCS[0];
+    TransformUnit   *tmpTU = nullptr;
+    Distortion      singleDistTmpLuma = 0;
+    uint64_t        singleTmpFracBits = 0;
+    double          singleCostTmp = 0;
+    int             firstCheckId = (sps.getUseLFNST() && mtsCheckRangeFlag && cu.mtsFlag) ? mtsFirstCheckId : 0;
+    int             lastCheckId = sps.getUseLFNST() ? ((mtsCheckRangeFlag && cu.mtsFlag) ? (mtsLastCheckId + (int)checkTransformSkip) : (numTransformIndexCands - (firstCheckId + 1) + (int)checkTransformSkip)) : trModes[nNumTransformCands - 1].first;
+    bool            isNotOnlyOneMode = sps.getUseLFNST() ? lastCheckId != firstCheckId : nNumTransformCands != 1;
+
+    if (isNotOnlyOneMode)
+    {
+      saveLumaCS.pcv = csFull->pcv;
+      saveLumaCS.picture = csFull->picture;
+      saveLumaCS.area.repositionTo(csFull->area);
+      saveLumaCS.clearTUs();
+      tmpTU = &saveLumaCS.addTU(currArea, partitioner.chType);
+    }
+
+    bool    cbfBestMode = false;
+    bool    cbfBestModeValid = false;
+    bool    cbfDCT2 = true;
+
+    m_pcRdCost->lambdaAdjustColorTrans(true, COMPONENT_Y);
+
+    for (int modeId = firstCheckId; modeId <= lastCheckId; modeId++)
+    {
+      uint8_t transformIndex = modeId;
+      csFull->getResiBuf(tu.Y()).copyFrom(csFull->getOrgResiBuf(tu.Y()));
+
+      m_CABACEstimator->getCtx() = ctxStart;
+      m_CABACEstimator->resetBits();
+
+      if (sps.getUseLFNST())
+      {
+        if ((transformIndex < lastCheckId) || ((transformIndex == lastCheckId) && !checkTransformSkip)) //we avoid this if the mode is transformSkip
+        {
+          // Skip checking other transform candidates if zero CBF is encountered and it is the best transform so far
+          if (m_pcEncCfg->getUseFastLFNST() && transformIndex && !cbfBestMode && cbfBestModeValid)
+          {
+            continue;
+          }
+        }
+      }
+      else
+      {
+#if JVET_AHG14_LOSSLESS
+        if (!(m_pcEncCfg->getCostMode() == COST_LOSSLESS_CODING))
+        {
+#endif
+        if (!cbfDCT2 || (m_pcEncCfg->getUseTransformSkipFast() && bestLumaModeId == 1))
+        {
+          break;
+        }
+        if (!trModes[modeId].second)
+        {
+          continue;
+        }
+#if JVET_AHG14_LOSSLESS
+        }
+#endif
+#if JVET_P0058_CHROMA_TS
+        tu.mtsIdx[COMPONENT_Y] = trModes[modeId].first;
+#else
+        tu.mtsIdx = trModes[modeId].first;
+#endif
+      }
+
+      singleDistTmpLuma = 0;
+      if (sps.getUseLFNST())
+      {
+        if (cu.mtsFlag)
+        {
+          if (moreProbMTSIdxFirst)
+          {
+            uint32_t uiIntraMode = pu.intraDir[CHANNEL_TYPE_LUMA];
+
+            if (transformIndex == 1)
+            {
+#if JVET_P0058_CHROMA_TS
+              tu.mtsIdx[COMPONENT_Y] = (uiIntraMode < 34) ? MTS_DST7_DCT8 : MTS_DCT8_DST7;
+#else
+              tu.mtsIdx = (uiIntraMode < 34) ? MTS_DST7_DCT8 : MTS_DCT8_DST7;
+#endif
+            }
+            else if (transformIndex == 2)
+            {
+#if JVET_P0058_CHROMA_TS
+              tu.mtsIdx[COMPONENT_Y] = (uiIntraMode < 34) ? MTS_DCT8_DST7 : MTS_DST7_DCT8;
+#else
+              tu.mtsIdx = (uiIntraMode < 34) ? MTS_DCT8_DST7 : MTS_DST7_DCT8;
+#endif
+            }
+            else
+            {
+#if JVET_P0058_CHROMA_TS
+              tu.mtsIdx[COMPONENT_Y] = MTS_DST7_DST7 + transformIndex;
+#else
+              tu.mtsIdx = MTS_DST7_DST7 + transformIndex;
+#endif
+            }
+          }
+          else
+          {
+#if JVET_P0058_CHROMA_TS
+            tu.mtsIdx[COMPONENT_Y] = MTS_DST7_DST7 + transformIndex;
+#else
+            tu.mtsIdx = MTS_DST7_DST7 + transformIndex;
+#endif
+          }
+        }
+        else
+        {
+#if JVET_P0058_CHROMA_TS
+          tu.mtsIdx[COMPONENT_Y] = transformIndex;
+#else
+          tu.mtsIdx = transformIndex;
+#endif
+        }
+
+        if (!cu.mtsFlag && checkTransformSkip)
+        {
+          xIntraCodingACTTUBlock(tu, COMPONENT_Y, singleDistTmpLuma, modeId == 0 ? &trModes : nullptr, true);
+          if (modeId == 0)
+          {
+            for (int i = 0; i < 2; i++)
+            {
+              if (trModes[i].second)
+              {
+                lastCheckId = trModes[i].first;
+              }
+            }
+          }
+        }
+        else
+        {
+          xIntraCodingACTTUBlock(tu, COMPONENT_Y, singleDistTmpLuma);
+        }
+      }
+      else
+      {
+        if (nNumTransformCands > 1)
+        {
+          xIntraCodingACTTUBlock(tu, COMPONENT_Y, singleDistTmpLuma, modeId == 0 ? &trModes : nullptr, true);
+          if (modeId == 0)
+          {
+            for (int i = 0; i < nNumTransformCands; i++)
+            {
+              if (trModes[i].second)
+              {
+                lastCheckId = trModes[i].first;
+              }
+            }
+          }
+        }
+        else
+        {
+          xIntraCodingACTTUBlock(tu, COMPONENT_Y, singleDistTmpLuma);
+        }
+      }
+
+      //----- determine rate and r-d cost -----
+      if ((sps.getUseLFNST() ? (modeId == lastCheckId && modeId != 0 && checkTransformSkip) : (trModes[modeId].first != 0)) && !TU::getCbfAtDepth(tu, COMPONENT_Y, currDepth))
+      {
+        //In order not to code TS flag when cbf is zero, the case for TS with cbf being zero is forbidden.
+        singleCostTmp = MAX_DOUBLE;
+      }
+      else
+      {
+        singleTmpFracBits = xGetIntraFracBitsQT(*csFull, partitioner, true, false, -1, TU_NO_ISP);
+        singleCostTmp = m_pcRdCost->calcRdCost(singleTmpFracBits, singleDistTmpLuma);
+      }
+
+      if (singleCostTmp < dSingleCostLuma)
+      {
+        dSingleCostLuma = singleCostTmp;
+        validReturnFull = true;
+
+        if (sps.getUseLFNST())
+        {
+          bestLumaModeId = modeId;
+          cbfBestMode = TU::getCbfAtDepth(tu, COMPONENT_Y, currDepth);
+          cbfBestModeValid = true;
+        }
+        else
+        {
+          bestLumaModeId = trModes[modeId].first;
+          if (trModes[modeId].first == 0)
+          {
+            cbfDCT2 = TU::getCbfAtDepth(tu, COMPONENT_Y, currDepth);
+          }
+        }
+
+        if (bestLumaModeId != lastCheckId)
+        {
+          saveLumaCS.getResiBuf(tu.Y()).copyFrom(csFull->getResiBuf(tu.Y()));
+          tmpTU->copyComponentFrom(tu, COMPONENT_Y);
+          ctxBest = m_CABACEstimator->getCtx();
+        }
+      }
+    }
+
+    m_pcRdCost->lambdaAdjustColorTrans(false, COMPONENT_Y);
+
+    if (sps.getUseLFNST())
+    {
+      if (!validReturnFull)
+      {
+        csFull->cost = MAX_DOUBLE;
+        return false;
+      }
+    }
+    else
+    {
+      CHECK(!validReturnFull, "no transform mode was tested for luma");
+    }
+
+    csFull->setDecomp(currArea.Y(), true);
+    csFull->setDecomp(currArea.Cb(), true);
+
+    if (bestLumaModeId != lastCheckId)
+    {
+      csFull->getResiBuf(tu.Y()).copyFrom(saveLumaCS.getResiBuf(tu.Y()));
+      tu.copyComponentFrom(*tmpTU, COMPONENT_Y);
+      m_CABACEstimator->getCtx() = ctxBest;
+    }
+
+    // 3 chroma residual optimization
+    CodingStructure &saveChromaCS = *m_pSaveCS[1];
+    saveChromaCS.pcv = csFull->pcv;
+    saveChromaCS.picture = csFull->picture;
+    saveChromaCS.area.repositionTo(csFull->area);
+    saveChromaCS.initStructData(MAX_INT, false, true);
+    tmpTU = &saveChromaCS.addTU(currArea, partitioner.chType);
+
+    CompArea&  cbArea = tu.blocks[COMPONENT_Cb];
+    CompArea&  crArea = tu.blocks[COMPONENT_Cr];
+
+    ctxStart = m_CABACEstimator->getCtx();
+    m_CABACEstimator->resetBits();
+    tu.jointCbCr = 0;
+
+#if JVET_P1006_PICTURE_HEADER
+    bool doReshaping = (slice.getPicHeader()->getLmcsEnabledFlag() && slice.getPicHeader()->getLmcsChromaResidualScaleFlag() && (slice.isIntra() || m_pcReshape->getCTUFlag()) && (cbArea.width * cbArea.height > 4));
+#else
+    bool doReshaping = (slice.getLmcsEnabledFlag() && slice.getLmcsChromaResidualScaleFlag() && (slice.isIntra() || m_pcReshape->getCTUFlag()) && (cbArea.width * cbArea.height > 4));
+#endif
+    if (doReshaping)
+    {
+      const Area      area = tu.Y().valid() ? tu.Y() : Area(recalcPosition(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].pos()), recalcSize(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].size()));
+      const CompArea &areaY = CompArea(COMPONENT_Y, tu.chromaFormat, area);
+      int             adj = m_pcReshape->calculateChromaAdjVpduNei(tu, areaY);
+      tu.setChromaAdj(adj);
+    }
+
+    CompStorage  orgResiCb[5], orgResiCr[5]; // 0:std, 1-3:jointCbCr (placeholder at this stage), 4:crossComp
+    orgResiCb[0].create(cbArea);
+    orgResiCr[0].create(crArea);
+    orgResiCb[0].copyFrom(csFull->getOrgResiBuf(cbArea));
+    orgResiCr[0].copyFrom(csFull->getOrgResiBuf(crArea));
+    if (doReshaping)
+    {
+      int cResScaleInv = tu.getChromaAdj();
+      orgResiCb[0].scaleSignal(cResScaleInv, 1, slice.clpRng(COMPONENT_Cb));
+      orgResiCr[0].scaleSignal(cResScaleInv, 1, slice.clpRng(COMPONENT_Cr));
+    }
+
+    // 3.1 regular chroma residual coding
+    csFull->getResiBuf(cbArea).copyFrom(orgResiCb[0]);
+    csFull->getResiBuf(crArea).copyFrom(orgResiCr[0]);
+
+    for (uint32_t c = COMPONENT_Cb; c < ::getNumberValidTBlocks(*csFull->pcv); c++)
+    {
+      const ComponentID compID = ComponentID(c);
+      Distortion singleDistChroma = 0;
+      xIntraCodingACTTUBlock(tu, compID, singleDistChroma);
+      xGetIntraFracBitsQTChroma(tu, compID);
+    }
+
+    Position tuPos = tu.Y();
+    tuPos.relativeTo(cu.Y());
+    const UnitArea relativeUnitArea(tu.chromaFormat, Area(tuPos, tu.Y().size()));
+    PelUnitBuf     invColorTransResidual = m_colorTransResiBuf.getBuf(relativeUnitArea);
+    csFull->getResiBuf(tu).colorSpaceConvert(invColorTransResidual, false);
+
+    Distortion totalDist = 0;
+    for (uint32_t c = COMPONENT_Y; c < ::getNumberValidTBlocks(*csFull->pcv); c++)
+    {
+      const ComponentID compID = ComponentID(c);
+      const CompArea&   area = tu.blocks[compID];
+      PelBuf            piOrg = csFull->getOrgBuf(area);
+      PelBuf            piReco = csFull->getRecoBuf(area);
+      PelBuf            piPred = csFull->getPredBuf(area);
+      PelBuf            piResi = invColorTransResidual.bufs[compID];
+
+      piReco.reconstruct(piPred, piResi, cs.slice->clpRng(compID));
+
+      if (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() || (m_pcEncCfg->getReshaper()
+#if JVET_P1006_PICTURE_HEADER
+        & slice.getPicHeader()->getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || (isChroma(compID) && m_pcEncCfg->getReshapeIntraCMD()))))
+#else
+        & slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || (isChroma(compID) && m_pcEncCfg->getReshapeIntraCMD()))))
+#endif
+      {
+        const CPelBuf orgLuma = csFull->getOrgBuf(csFull->area.blocks[COMPONENT_Y]);
+        if (compID == COMPONENT_Y && !(m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled()))
+        {
+          CompArea      tmpArea1(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
+          PelBuf tmpRecLuma = m_tmpStorageLCU.getBuf(tmpArea1);
+          tmpRecLuma.copyFrom(piReco);
+          tmpRecLuma.rspSignal(m_pcReshape->getInvLUT());
+          totalDist += m_pcRdCost->getDistPart(piOrg, tmpRecLuma, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE_WTD, &orgLuma);
+        }
+        else
+        {
+          totalDist += m_pcRdCost->getDistPart(piOrg, piReco, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE_WTD, &orgLuma);
+        }
+      }
+      else
+      {
+        totalDist += m_pcRdCost->getDistPart(piOrg, piReco, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE);
+      }
+    }
+
+    m_CABACEstimator->getCtx() = ctxStart;
+    uint64_t totalBits = xGetIntraFracBitsQT(*csFull, partitioner, true, true, -1, TU_NO_ISP);
+    double   totalCost = m_pcRdCost->calcRdCost(totalBits, totalDist);
+
+    saveChromaCS.getResiBuf(cbArea).copyFrom(csFull->getResiBuf(cbArea));
+    saveChromaCS.getResiBuf(crArea).copyFrom(csFull->getResiBuf(crArea));
+    saveChromaCS.getRecoBuf(tu).copyFrom(csFull->getRecoBuf(tu));
+    tmpTU->copyComponentFrom(tu, COMPONENT_Cb);
+    tmpTU->copyComponentFrom(tu, COMPONENT_Cr);
+    ctxBest = m_CABACEstimator->getCtx();
+
+    // 3.2 jointCbCr
+    double     bestCostJointCbCr = totalCost;
+    Distortion bestDistJointCbCr = totalDist;
+    uint64_t   bestBitsJointCbCr = totalBits;
+    int        bestJointCbCr = tu.jointCbCr; assert(!bestJointCbCr);
+
+    bool       lastIsBest = false;
+    std::vector<int>  jointCbfMasksToTest;
+    if (sps.getJointCbCrEnabledFlag() && (TU::getCbf(tu, COMPONENT_Cb) || TU::getCbf(tu, COMPONENT_Cr)))
+    {
+      jointCbfMasksToTest = m_pcTrQuant->selectICTCandidates(tu, orgResiCb, orgResiCr);
+    }
+
+    for (int cbfMask : jointCbfMasksToTest)
+    {
+      m_CABACEstimator->getCtx() = ctxStart;
+      m_CABACEstimator->resetBits();
+
+      Distortion distTmp = 0;
+      tu.jointCbCr = (uint8_t)cbfMask;
+
+      csFull->getResiBuf(cbArea).copyFrom(orgResiCb[cbfMask]);
+      csFull->getResiBuf(crArea).copyFrom(orgResiCr[cbfMask]);
+      xIntraCodingACTTUBlock(tu, COMPONENT_Cb, distTmp);
+
+      double   costTmp = std::numeric_limits<double>::max();
+      uint64_t bitsTmp = 0;
+      if (distTmp < std::numeric_limits<Distortion>::max())
+      {
+        csFull->getResiBuf(tu).colorSpaceConvert(invColorTransResidual, false);
+        distTmp = 0;
+        for (uint32_t c = COMPONENT_Y; c < ::getNumberValidTBlocks(*csFull->pcv); c++)
+        {
+          const ComponentID compID = ComponentID(c);
+          const CompArea&   area = tu.blocks[compID];
+          PelBuf            piOrg = csFull->getOrgBuf(area);
+          PelBuf            piReco = csFull->getRecoBuf(area);
+          PelBuf            piPred = csFull->getPredBuf(area);
+          PelBuf            piResi = invColorTransResidual.bufs[compID];
+
+          piReco.reconstruct(piPred, piResi, cs.slice->clpRng(compID));
+          if (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() || (m_pcEncCfg->getReshaper()
+#if JVET_P1006_PICTURE_HEADER
+            & slice.getPicHeader()->getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || (isChroma(compID) && m_pcEncCfg->getReshapeIntraCMD()))))
+#else
+            & slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || (isChroma(compID) && m_pcEncCfg->getReshapeIntraCMD()))))
+#endif
+          {
+            const CPelBuf orgLuma = csFull->getOrgBuf(csFull->area.blocks[COMPONENT_Y]);
+            if (compID == COMPONENT_Y && !(m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled()))
+            {
+              CompArea      tmpArea1(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
+              PelBuf tmpRecLuma = m_tmpStorageLCU.getBuf(tmpArea1);
+              tmpRecLuma.copyFrom(piReco);
+              tmpRecLuma.rspSignal(m_pcReshape->getInvLUT());
+              distTmp += m_pcRdCost->getDistPart(piOrg, tmpRecLuma, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE_WTD, &orgLuma);
+            }
+            else
+            {
+              distTmp += m_pcRdCost->getDistPart(piOrg, piReco, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE_WTD, &orgLuma);
+            }
+          }
+          else
+          {
+            distTmp += m_pcRdCost->getDistPart(piOrg, piReco, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE);
+          }
+        }
+
+        bitsTmp = xGetIntraFracBitsQT(*csFull, partitioner, true, true, -1, TU_NO_ISP);
+        costTmp = m_pcRdCost->calcRdCost(bitsTmp, distTmp);
+      }
+
+      if (costTmp < bestCostJointCbCr)
+      {
+        bestCostJointCbCr = costTmp;
+        bestDistJointCbCr = distTmp;
+        bestBitsJointCbCr = bitsTmp;
+        bestJointCbCr = tu.jointCbCr;
+        lastIsBest = (cbfMask == jointCbfMasksToTest.back());
+
+        // store data
+        if (!lastIsBest)
+        {
+          saveChromaCS.getResiBuf(cbArea).copyFrom(csFull->getResiBuf(cbArea));
+          saveChromaCS.getResiBuf(crArea).copyFrom(csFull->getResiBuf(crArea));
+          saveChromaCS.getRecoBuf(tu).copyFrom(csFull->getRecoBuf(tu));
+          tmpTU->copyComponentFrom(tu, COMPONENT_Cb);
+          tmpTU->copyComponentFrom(tu, COMPONENT_Cr);
+
+          ctxBest = m_CABACEstimator->getCtx();
+        }
+      }
+    }
+
+    if (!lastIsBest)
+    {
+      csFull->getResiBuf(cbArea).copyFrom(saveChromaCS.getResiBuf(cbArea));
+      csFull->getResiBuf(crArea).copyFrom(saveChromaCS.getResiBuf(crArea));
+      csFull->getRecoBuf(tu).copyFrom(saveChromaCS.getRecoBuf(tu));
+      tu.copyComponentFrom(*tmpTU, COMPONENT_Cb);
+      tu.copyComponentFrom(*tmpTU, COMPONENT_Cr);
+
+      m_CABACEstimator->getCtx() = ctxBest;
+    }
+    tu.jointCbCr = bestJointCbCr;
+    csFull->picture->getRecoBuf(tu).copyFrom(csFull->getRecoBuf(tu));
+
+    csFull->dist += bestDistJointCbCr;
+    csFull->fracBits += bestBitsJointCbCr;
+    csFull->cost = m_pcRdCost->calcRdCost(csFull->fracBits, csFull->dist);
+  }
+
+  bool validReturnSplit = false;
+  if (bCheckSplit)
+  {
+    if (partitioner.canSplit(TU_MAX_TR_SPLIT, *csSplit))
+    {
+      partitioner.splitCurrArea(TU_MAX_TR_SPLIT, *csSplit);
+    }
+
+    bool splitIsSelected = true;
+    do
+    {
+      bool tmpValidReturnSplit = xRecurIntraCodingACTQT(*csSplit, partitioner, mtsCheckRangeFlag, mtsFirstCheckId, mtsLastCheckId, moreProbMTSIdxFirst);
+      if (sps.getUseLFNST())
+      {
+        if (!tmpValidReturnSplit)
+        {
+          splitIsSelected = false;
+          break;
+        }
+      }
+      else
+      {
+        CHECK(!tmpValidReturnSplit, "invalid RD of sub-TU partitions for ACT");
+      }
+    } while (partitioner.nextPart(*csSplit));
+
+    partitioner.exitCurrSplit();
+
+    if (splitIsSelected)
+    {
+      unsigned compCbf[3] = { 0, 0, 0 };
+      for (auto &currTU : csSplit->traverseTUs(currArea, partitioner.chType))
+      {
+        for (unsigned ch = 0; ch < getNumberValidTBlocks(*csSplit->pcv); ch++)
+        {
+          compCbf[ch] |= (TU::getCbfAtDepth(currTU, ComponentID(ch), currDepth + 1) ? 1 : 0);
+        }
+      }
+
+      for (auto &currTU : csSplit->traverseTUs(currArea, partitioner.chType))
+      {
+        TU::setCbfAtDepth(currTU, COMPONENT_Y, currDepth, compCbf[COMPONENT_Y]);
+        TU::setCbfAtDepth(currTU, COMPONENT_Cb, currDepth, compCbf[COMPONENT_Cb]);
+        TU::setCbfAtDepth(currTU, COMPONENT_Cr, currDepth, compCbf[COMPONENT_Cr]);
+      }
+
+      m_CABACEstimator->getCtx() = ctxStart;
+      csSplit->fracBits = xGetIntraFracBitsQT(*csSplit, partitioner, true, true, -1, TU_NO_ISP);
+      csSplit->cost = m_pcRdCost->calcRdCost(csSplit->fracBits, csSplit->dist);
+
+      validReturnSplit = true;
+    }
+  }
+
+  bool retVal = false;
+  if (csFull || csSplit)
+  {
+    if (sps.getUseLFNST())
+    {
+      if (validReturnFull || validReturnSplit)
+      {
+        retVal = true;
+      }
+    }
+    else
+    {
+      CHECK(!validReturnFull && !validReturnSplit, "illegal TU optimization");
+      retVal = true;
+    }
+  }
+  return retVal;
+}
+#endif
+
+ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitioner& partitioner, const double bestCostSoFar, const PartSplit ispType )
+{
+  UnitArea currArea                   = partitioner.currArea();
+  const bool keepResi                 = cs.sps->getUseLMChroma() || KEEP_PRED_AND_RESI_SIGNALS;
+  if( !currArea.Cb().valid() ) return ChromaCbfs( false );
+
+
+  TransformUnit &currTU               = *cs.getTU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
+  const PredictionUnit &pu            = *cs.getPU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
+
+  bool lumaUsesISP                    = false;
+  uint32_t     currDepth                  = partitioner.currTrDepth;
+  const PPS &pps                      = *cs.pps;
+  ChromaCbfs cbfs                     ( false );
+
+  if (currDepth == currTU.depth)
+  {
+    if (!currArea.Cb().valid() || !currArea.Cr().valid())
+    {
+      return cbfs;
+    }
+
+
+    CodingStructure &saveCS = *m_pSaveCS[1];
+    saveCS.pcv      = cs.pcv;
+    saveCS.picture  = cs.picture;
+    saveCS.area.repositionTo( cs.area );
+    saveCS.initStructData( MAX_INT, false, true );
+
+    if( !currTU.cu->isSepTree() && currTU.cu->ispMode )
+    {
+      saveCS.clearCUs();
+      CodingUnit& auxCU = saveCS.addCU( *currTU.cu, partitioner.chType );
+      auxCU.ispMode = currTU.cu->ispMode;
+      saveCS.sps = currTU.cs->sps;
+      saveCS.clearPUs();
+      saveCS.addPU( *currTU.cu->firstPU, partitioner.chType );
+    }
+
+    TransformUnit &tmpTU = saveCS.addTU(currArea, partitioner.chType);
+
+
+    cs.setDecomp(currArea.Cb(), true); // set in advance (required for Cb2/Cr2 in 4:2:2 video)
+
+    const unsigned      numTBlocks  = ::getNumberValidTBlocks( *cs.pcv );
+
+    CompArea&  cbArea         = currTU.blocks[COMPONENT_Cb];
+    CompArea&  crArea         = currTU.blocks[COMPONENT_Cr];
+    double     bestCostCb     = MAX_DOUBLE;
+    double     bestCostCr     = MAX_DOUBLE;
+    Distortion bestDistCb     = 0;
+    Distortion bestDistCr     = 0;
+    int        maxModesTested = 0;
+    bool       earlyExitISP   = false;
+
+    TempCtx ctxStartTU( m_CtxCache );
+    TempCtx ctxStart  ( m_CtxCache );
+    TempCtx ctxBest   ( m_CtxCache );
+
+    ctxStartTU       = m_CABACEstimator->getCtx();
     currTU.jointCbCr = 0;
 
     // Do predictions here to avoid repeating the "default0Save1Load2" stuff
@@ -4831,7 +5791,74 @@ uint64_t IntraSearch::xFracModeBitsIntra(PredictionUnit &pu, const uint32_t &uiM
   return m_CABACEstimator->getEstFracBits();
 }
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+void IntraSearch::sortRdModeListFirstColorSpace(ModeInfo mode, double cost, char bdpcmMode, ModeInfo* rdModeList, double* rdCostList, char* bdpcmModeList, int& candNum)
+{
+  if (candNum == 0)
+  {
+    rdModeList[0] = mode;
+    rdCostList[0] = cost;
+    bdpcmModeList[0] = bdpcmMode;
+    candNum++;
+    return;
+  }
 
+  int insertPos = -1;
+  for (int pos = candNum - 1; pos >= 0; pos--)
+  {
+    if (cost < rdCostList[pos])
+    {
+      insertPos = pos;
+    }
+  }
+
+  if (insertPos >= 0)
+  {
+    for (int i = candNum - 1; i >= insertPos; i--)
+    {
+      rdModeList[i + 1] = rdModeList[i];
+      rdCostList[i + 1] = rdCostList[i];
+      bdpcmModeList[i + 1] = bdpcmModeList[i];
+    }
+    rdModeList[insertPos] = mode;
+    rdCostList[insertPos] = cost;
+    bdpcmModeList[insertPos] = bdpcmMode;
+    candNum++;
+  }
+  else
+  {
+    rdModeList[candNum] = mode;
+    rdCostList[candNum] = cost;
+    bdpcmModeList[candNum] = bdpcmMode;
+    candNum++;
+  }
+
+  CHECK(candNum > FAST_UDI_MAX_RDMODE_NUM, "exceed intra mode candidate list capacity");
+
+  return;
+}
+
+void IntraSearch::invalidateBestRdModeFirstColorSpace()
+{
+  int numSaveRdClass = 4 * NUM_LFNST_NUM_PER_SET * 2;
+  int savedRdModeListSize = FAST_UDI_MAX_RDMODE_NUM;
+
+  for (int i = 0; i < numSaveRdClass; i++)
+  {
+    m_numSavedRdModeFirstColorSpace[i] = 0;
+    for (int j = 0; j < savedRdModeListSize; j++)
+    {
+#if JVET_P0803_COMBINED_MIP_CLEANUP
+      m_savedRdModeFirstColorSpace[i][j] = ModeInfo(false, false, 0, NOT_INTRA_SUBPARTITIONS, 0);
+#else
+      m_savedRdModeFirstColorSpace[i][j] = ModeInfo(false, 0, NOT_INTRA_SUBPARTITIONS, 0);
+#endif
+      m_savedBDPCMModeFirstColorSpace[i][j] = 0;
+      m_savedRdCostFirstColorSpace[i][j] = MAX_DOUBLE;
+    }
+  }
+}
+#endif
 
 void IntraSearch::encPredIntraDPCM( const ComponentID &compID, PelBuf &pOrg, PelBuf &pDst, const uint32_t &uiDirMode )
 {
@@ -5196,7 +6223,6 @@ void IntraSearch::xGetNextISPMode(ModeInfo& modeInfo, const ModeInfo* lastMode,
         CHECK(leftIntraMode != candidate.modeId || rightIntraMode != candidate.modeId, "wrong intra mode and lfnstIdx values!");
         numSubPartsRefMode = m_ispTestedModes[refLfnstIdx].getNumCompletedSubParts((ISPType)candidate.ispMod, candidate.modeId);
         CHECK(numSubPartsRefMode <= 0, "Wrong value of the number of subpartitions completed!");
-
       }
       else
       {
diff --git a/source/Lib/EncoderLib/IntraSearch.h b/source/Lib/EncoderLib/IntraSearch.h
index fa157f153fc1fb44d02a993f904d9c4efe1d4411..e21e6f1778e98f70a68a0dea19b92e1082ca83be 100644
--- a/source/Lib/EncoderLib/IntraSearch.h
+++ b/source/Lib/EncoderLib/IntraSearch.h
@@ -359,6 +359,13 @@ private:
   ModeInfo   m_savedRdModeList[ NUM_LFNST_NUM_PER_SET ][ NUM_LUMA_MODE ];
   int32_t    m_savedNumRdModes[ NUM_LFNST_NUM_PER_SET ];
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  ModeInfo                                           m_savedRdModeFirstColorSpace[4 * NUM_LFNST_NUM_PER_SET * 2][FAST_UDI_MAX_RDMODE_NUM];
+  char                                               m_savedBDPCMModeFirstColorSpace[4 * NUM_LFNST_NUM_PER_SET * 2][FAST_UDI_MAX_RDMODE_NUM];
+  double                                             m_savedRdCostFirstColorSpace[4 * NUM_LFNST_NUM_PER_SET * 2][FAST_UDI_MAX_RDMODE_NUM];
+  int                                                m_numSavedRdModeFirstColorSpace[4 * NUM_LFNST_NUM_PER_SET * 2];
+  int                                                m_savedRdModeIdx;
+#endif
 
   static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> m_uiSavedRdModeListLFNST;
   static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> m_uiSavedHadModeListLFNST;
@@ -367,6 +374,9 @@ private:
   static_vector<double,   FAST_UDI_MAX_RDMODE_NUM> m_dSavedHadListLFNST;
 
   PelStorage      m_tmpStorageLCU;
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  PelStorage      m_colorTransResiBuf;
+#endif 
 protected:
   // interface to option
   EncCfg*         m_pcEncCfg;
@@ -430,8 +440,11 @@ public:
   double findInterCUCost          ( CodingUnit &cu );
 
 public:
-
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool estIntraPredLumaQT(CodingUnit &cu, Partitioner& pm, const double bestCostSoFar = MAX_DOUBLE, bool mtsCheckRangeFlag = false, int mtsFirstCheckId = 0, int mtsLastCheckId = 0, bool moreProbMTSIdxFirst = false, CodingStructure* bestCS = NULL);
+#else
   bool estIntraPredLumaQT         ( CodingUnit &cu, Partitioner& pm, const double bestCostSoFar  = MAX_DOUBLE, bool mtsCheckRangeFlag = false, int mtsFirstCheckId = 0, int mtsLastCheckId = 0, bool moreProbMTSIdxFirst = false );
+#endif
   void estIntraPredChromaQT       ( CodingUnit &cu, Partitioner& pm, const double maxCostAllowed = MAX_DOUBLE );
   void PLTSearch                  ( CodingStructure &cs, Partitioner& partitioner, ComponentID compBegin, uint32_t numComp);
 #if !JVET_P0077_LINE_CG_PALETTE
@@ -440,6 +453,12 @@ public:
   uint64_t xFracModeBitsIntra     (PredictionUnit &pu, const uint32_t &uiMode, const ChannelType &compID);
   void invalidateBestModeCost     () { for( int i = 0; i < NUM_LFNST_NUM_PER_SET; i++ ) m_bestModeCostValid[ i ] = false; };
 
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM 
+  void sortRdModeListFirstColorSpace(ModeInfo mode, double cost, char bdpcmMode, ModeInfo* rdModeList, double* rdCostList, char* bdpcmModeList, int& candNum);
+  void invalidateBestRdModeFirstColorSpace();
+  void setSavedRdModeIdx(int idx) { m_savedRdModeIdx = idx; }
+#endif
+
 protected:
 
   // -------------------------------------------------------------------------------------------------------------------
@@ -468,9 +487,15 @@ protected:
 #endif
 
   void xIntraCodingTUBlock        (TransformUnit &tu, const ComponentID &compID, const bool &checkCrossCPrediction, Distortion& ruiDist, const int &default0Save1Load2 = 0, uint32_t* numSig = nullptr, std::vector<TrMode>* trModes=nullptr, const bool loadTr=false );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  void xIntraCodingACTTUBlock(TransformUnit &tu, const ComponentID &compID, Distortion& ruiDist, std::vector<TrMode>* trModes = nullptr, const bool loadTr = false);
+#endif
 
   ChromaCbfs xRecurIntraChromaCodingQT( CodingStructure &cs, Partitioner& pm, const double bestCostSoFar = MAX_DOUBLE,                          const PartSplit ispType = TU_NO_ISP );
   bool       xRecurIntraCodingLumaQT  ( CodingStructure &cs, Partitioner& pm, const double bestCostSoFar = MAX_DOUBLE, const int subTuIdx = -1, const PartSplit ispType = TU_NO_ISP, const bool ispIsCurrentWinner = false, bool mtsCheckRangeFlag = false, int mtsFirstCheckId = 0, int mtsLastCheckId = 0, bool moreProbMTSIdxFirst = false );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  bool       xRecurIntraCodingACTQT(CodingStructure &cs, Partitioner& pm, bool mtsCheckRangeFlag = false, int mtsFirstCheckId = 0, int mtsLastCheckId = 0, bool moreProbMTSIdxFirst = false);
+#endif
   bool       xIntraCodingLumaISP      ( CodingStructure& cs, Partitioner& pm, const double bestCostSoFar = MAX_DOUBLE );
 
   void encPredIntraDPCM( const ComponentID &compID, PelBuf &pOrg, PelBuf &pDst, const uint32_t &uiDirMode );
diff --git a/source/Lib/EncoderLib/VLCWriter.cpp b/source/Lib/EncoderLib/VLCWriter.cpp
index 0bd805f5266b228ac130bdeec9a4c5dd824f5671..61e63d55ab360859e0c730ea6cdd9cddddc5ac06 100644
--- a/source/Lib/EncoderLib/VLCWriter.cpp
+++ b/source/Lib/EncoderLib/VLCWriter.cpp
@@ -989,6 +989,12 @@ void HLSWriter::codeSPS( const SPS* pcSPS )
     WRITE_FLAG( pcSPS->getAffineAmvrEnabledFlag() ? 1 : 0,                                     "sps_affine_amvr_enabled_flag" );
   }
   WRITE_FLAG( pcSPS->getUseGBi() ? 1 : 0,                                                      "gbi_flag" );
+#if JVET_P0517_ADAPTIVE_COLOR_TRANSFORM
+  if (pcSPS->getChromaFormatIdc() == CHROMA_444)
+  {
+    WRITE_FLAG(pcSPS->getUseColorTrans() ? 1 : 0, "act_flag");
+  }
+#endif
   if (pcSPS->getChromaFormatIdc() == CHROMA_444)
   {
     WRITE_FLAG(pcSPS->getPLTMode() ? 1 : 0,                                                    "plt_flag" );