From 0fffdf5bbbfb867764d24cd22ebdba13aebb31ef Mon Sep 17 00:00:00 2001
From: Christian Helmrich <christian.helmrich@hhi.fraunhofer.de>
Date: Mon, 22 Jul 2019 20:50:19 +0200
Subject: [PATCH] JVET-O0105 with JVET-O0543: joint chroma coding extension

---
 source/Lib/CommonLib/Buffer.h         |  26 +++
 source/Lib/CommonLib/Contexts.cpp     |   7 +
 source/Lib/CommonLib/LoopFilter.cpp   |   8 +
 source/Lib/CommonLib/Quant.cpp        |   7 +
 source/Lib/CommonLib/Rom.cpp          |   4 +
 source/Lib/CommonLib/Rom.h            |   4 +
 source/Lib/CommonLib/Slice.cpp        |   9 +
 source/Lib/CommonLib/Slice.h          |  10 +
 source/Lib/CommonLib/TrQuant.cpp      | 171 +++++++++++++++
 source/Lib/CommonLib/TrQuant.h        |  15 +-
 source/Lib/CommonLib/TypeDef.h        |   3 +
 source/Lib/CommonLib/UnitTools.cpp    |  11 +
 source/Lib/CommonLib/UnitTools.h      |   4 +-
 source/Lib/DecoderLib/CABACReader.cpp |  27 +++
 source/Lib/DecoderLib/CABACReader.h   |   4 +
 source/Lib/DecoderLib/DecCu.cpp       |  51 +++++
 source/Lib/DecoderLib/VLCReader.cpp   |   6 +
 source/Lib/EncoderLib/CABACWriter.cpp |  28 +++
 source/Lib/EncoderLib/CABACWriter.h   |   4 +
 source/Lib/EncoderLib/EncSlice.cpp    |  46 ++++
 source/Lib/EncoderLib/InterSearch.cpp | 140 ++++++++++++
 source/Lib/EncoderLib/IntraSearch.cpp | 294 +++++++++++++++++++++++++-
 source/Lib/EncoderLib/VLCWriter.cpp   |   7 +
 23 files changed, 882 insertions(+), 4 deletions(-)

diff --git a/source/Lib/CommonLib/Buffer.h b/source/Lib/CommonLib/Buffer.h
index d7a10b1fd8..5c287d84f7 100644
--- a/source/Lib/CommonLib/Buffer.h
+++ b/source/Lib/CommonLib/Buffer.h
@@ -111,8 +111,10 @@ struct AreaBuf : public Size
   void copyClip             ( const AreaBuf<const T> &src, const ClpRng& clpRng);
 
   void subtract             ( const AreaBuf<const T> &other );
+#if !JVET_O0105_ICT
   void copyAndNegate        ( const AreaBuf<const T> &other );
   void subtractAndHalve     ( const AreaBuf<const T> &other );
+#endif
   void extendSingleBorderPel();
   void extendBorderPel      (  unsigned margin );
   void addWeightedAvg       ( const AreaBuf<const T> &other1, const AreaBuf<const T> &other2, const ClpRng& clpRng, const int8_t gbiIdx);
@@ -357,6 +359,7 @@ void AreaBuf<T>::subtract( const AreaBuf<const T> &other )
 #undef SUBS_INC
 }
 
+#if !JVET_O0105_ICT
 template<typename T>
 void AreaBuf<T>::copyAndNegate( const AreaBuf<const T> &other )
 {
@@ -398,6 +401,7 @@ void AreaBuf<T>::subtractAndHalve( const AreaBuf<const T> &other )
 #undef SUBS_OP
 #undef SUBS_INC
 }
+#endif
 
 template<typename T>
 void AreaBuf<T>::copyClip( const AreaBuf<const T> &src, const ClpRng& clpRng )
@@ -924,5 +928,27 @@ private:
   Pel *m_origin[MAX_NUM_COMPONENT];
 };
 
+#if JVET_O0105_ICT
+struct CompStorage : public PelBuf
+{
+  CompStorage () { m_memory = nullptr; }
+  ~CompStorage() { if (valid()) delete [] m_memory; }
+
+  void create( const Size& size )
+  {
+    CHECK( m_memory, "Trying to re-create an already initialized buffer" );
+    m_memory = new Pel [ size.area() ];
+    *static_cast<PelBuf*>(this) = PelBuf( m_memory, size );
+  }
+  void destroy()
+  {
+    if (valid()) delete [] m_memory;
+    m_memory = nullptr;
+  }
+  bool valid() { return m_memory != nullptr; }
+private:
+  Pel* m_memory;
+};
+#endif
 
 #endif
diff --git a/source/Lib/CommonLib/Contexts.cpp b/source/Lib/CommonLib/Contexts.cpp
index 6f68221e92..e3df7ce9ac 100644
--- a/source/Lib/CommonLib/Contexts.cpp
+++ b/source/Lib/CommonLib/Contexts.cpp
@@ -784,10 +784,17 @@ const CtxSet ContextSetCfg::IBCFlag = ContextSetCfg::addCtxSet
 
 const CtxSet ContextSetCfg::JointCbCrFlag = ContextSetCfg::addCtxSet
 ({
+#if JVET_O0105_ICT
+  { 156, 156, 156, },
+  { 156, 156, 156, },
+  { 184, 184, 184, },
+  {   1,   1,   1, },
+#else
   { 156, },
   { 156, },
   { 184, },
   {   1, },
+#endif
 });
 
 const CtxSet ContextSetCfg::TsSigCoeffGroup = ContextSetCfg::addCtxSet
diff --git a/source/Lib/CommonLib/LoopFilter.cpp b/source/Lib/CommonLib/LoopFilter.cpp
index 60f0bbfbcd..6d51b1c713 100644
--- a/source/Lib/CommonLib/LoopFilter.cpp
+++ b/source/Lib/CommonLib/LoopFilter.cpp
@@ -667,12 +667,20 @@ unsigned LoopFilter::xGetBoundaryStrengthSingle ( const CodingUnit& cu, const De
     tmpBs += BsSet(1, COMPONENT_Y);
   }
   // U
+#if JVET_O0105_ICT
+  if (m_aapucBS[edgeDir][rasterIdx] && (TU::getCbf(tuQ, COMPONENT_Cb) || TU::getCbf(tuP, COMPONENT_Cb) || tuQ.jointCbCr || tuP.jointCbCr))
+#else
   if (m_aapucBS[edgeDir][rasterIdx] && (TU::getCbf(tuQ, COMPONENT_Cb) || TU::getCbf(tuP, COMPONENT_Cb)))
+#endif
   {
     tmpBs += BsSet(1, COMPONENT_Cb);
   }
   // V
+#if JVET_O0105_ICT
+  if (m_aapucBS[edgeDir][rasterIdx] && (TU::getCbf(tuQ, COMPONENT_Cr) || TU::getCbf(tuP, COMPONENT_Cr) || tuQ.jointCbCr || tuP.jointCbCr))
+#else
   if (m_aapucBS[edgeDir][rasterIdx] && (TU::getCbf(tuQ, COMPONENT_Cr) || TU::getCbf(tuP, COMPONENT_Cr)))
+#endif
   {
     tmpBs += BsSet(1, COMPONENT_Cr);
   }
diff --git a/source/Lib/CommonLib/Quant.cpp b/source/Lib/CommonLib/Quant.cpp
index 43f78293fc..f994143f09 100644
--- a/source/Lib/CommonLib/Quant.cpp
+++ b/source/Lib/CommonLib/Quant.cpp
@@ -103,8 +103,15 @@ QpParam::QpParam(const TransformUnit& tu, const ComponentID &compIDX, const int
 
   if (isChroma(compID))
   {
+#if JVET_O0105_ICT
+    const bool useJQP = ( abs(TU::getICTMode(tu)) == 2 );
+
+    chromaQpOffset += tu.cs->pps->getQpOffset            ( useJQP ? JOINT_CbCr : compID );
+    chromaQpOffset += tu.cs->slice->getSliceChromaQpDelta( useJQP ? JOINT_CbCr : compID );
+#else
     chromaQpOffset += tu.cs->pps->getQpOffset            ( tu.jointCbCr ? JOINT_CbCr : compID );
     chromaQpOffset += tu.cs->slice->getSliceChromaQpDelta( tu.jointCbCr ? JOINT_CbCr : compID );
+#endif
     chromaQpOffset += tu.cs->pps->getPpsRangeExtension().getChromaQpOffsetListEntry( tu.cu->chromaQpAdj ).u.offset[int( compID ) - 1];
   }
 
diff --git a/source/Lib/CommonLib/Rom.cpp b/source/Lib/CommonLib/Rom.cpp
index 2537607d5d..7cc906bc18 100644
--- a/source/Lib/CommonLib/Rom.cpp
+++ b/source/Lib/CommonLib/Rom.cpp
@@ -585,6 +585,10 @@ int8_t                    g_aucLog2    [MAX_CU_SIZE + 1];
 int8_t                    g_aucNextLog2[MAX_CU_SIZE + 1];
 int8_t                    g_aucPrevLog2[MAX_CU_SIZE + 1];
 
+#if JVET_O0105_ICT
+const int                 g_ictModes[2][4] = { { 0, 3, 1, 2 }, { 0, -3, -1, -2 } };
+#endif
+
 UnitScale g_miScaling( MIN_CU_LOG2, MIN_CU_LOG2 );
 
 
diff --git a/source/Lib/CommonLib/Rom.h b/source/Lib/CommonLib/Rom.h
index 7557243d37..ac26cd734f 100644
--- a/source/Lib/CommonLib/Rom.h
+++ b/source/Lib/CommonLib/Rom.h
@@ -146,6 +146,10 @@ extern int8_t          g_aucLog2                       [MAX_CU_SIZE + 1];
 extern int8_t          g_aucNextLog2        [MAX_CU_SIZE + 1];
 extern int8_t          g_aucPrevLog2        [MAX_CU_SIZE + 1];
 
+#if JVET_O0105_ICT
+extern const int       g_ictModes[2][4];
+#endif
+
 inline bool is34( const SizeType& size )
 {
   return ( size & ( ( int64_t ) 1 << ( g_aucLog2[size] - 1 ) ) );
diff --git a/source/Lib/CommonLib/Slice.cpp b/source/Lib/CommonLib/Slice.cpp
index d69f165ab0..b841f2b689 100644
--- a/source/Lib/CommonLib/Slice.cpp
+++ b/source/Lib/CommonLib/Slice.cpp
@@ -97,6 +97,9 @@ Slice::Slice()
 , m_bTestWeightBiPred             ( false )
 , m_substreamSizes                ( )
 , m_cabacInitFlag                 ( false )
+#if JVET_O0105_ICT
+, m_jointCbCrSignFlag             ( false )
+#endif
 , m_bLMvdL1Zero                   ( false )
 , m_LFCrossSliceBoundaryFlag      ( false )
 , m_enableTMVPFlag                ( true )
@@ -192,6 +195,9 @@ void Slice::initSlice()
   m_disFracMMVD          = false;
   m_substreamSizes.clear();
   m_cabacInitFlag        = false;
+#if JVET_O0105_ICT
+  m_jointCbCrSignFlag    = false;
+#endif
   m_enableTMVPFlag       = true;
 }
 
@@ -672,6 +678,9 @@ void Slice::copySliceInfo(Slice *pSrc, bool cpyAlmostAll)
   }
 
   m_cabacInitFlag                 = pSrc->m_cabacInitFlag;
+#if JVET_O0105_ICT
+  m_jointCbCrSignFlag             = pSrc->m_jointCbCrSignFlag;
+#endif
   memcpy(m_alfApss, pSrc->m_alfApss, sizeof(m_alfApss)); // this might be quite unsafe
   memcpy( m_tileGroupAlfEnabledFlag, pSrc->m_tileGroupAlfEnabledFlag, sizeof(m_tileGroupAlfEnabledFlag));
   m_tileGroupNumAps               = pSrc->m_tileGroupNumAps;
diff --git a/source/Lib/CommonLib/Slice.h b/source/Lib/CommonLib/Slice.h
index cbe18bce93..690419edea 100644
--- a/source/Lib/CommonLib/Slice.h
+++ b/source/Lib/CommonLib/Slice.h
@@ -1467,6 +1467,10 @@ private:
 
   bool                       m_cabacInitFlag;
 
+#if JVET_O0105_ICT
+  bool                       m_jointCbCrSignFlag;
+#endif
+
   bool                       m_bLMvdL1Zero;
   bool                       m_LFCrossSliceBoundaryFlag;
 
@@ -1739,6 +1743,12 @@ public:
 
   void                        setCabacInitFlag( bool val )                           { m_cabacInitFlag = val;                                        } //!< set CABAC initial flag
   bool                        getCabacInitFlag()                               const { return m_cabacInitFlag;                                       } //!< get CABAC initial flag
+
+#if JVET_O0105_ICT
+  void                        setJointCbCrSignFlag( bool b )                         { m_jointCbCrSignFlag = b; }
+  bool                        getJointCbCrSignFlag()                           const { return m_jointCbCrSignFlag; }
+#endif
+
   void                        setLFCrossSliceBoundaryFlag( bool   val )              { m_LFCrossSliceBoundaryFlag = val;                             }
   bool                        getLFCrossSliceBoundaryFlag()                    const { return m_LFCrossSliceBoundaryFlag;                            }
 
diff --git a/source/Lib/CommonLib/TrQuant.cpp b/source/Lib/CommonLib/TrQuant.cpp
index 51e628011f..3206bef3e6 100644
--- a/source/Lib/CommonLib/TrQuant.cpp
+++ b/source/Lib/CommonLib/TrQuant.cpp
@@ -83,6 +83,80 @@ InvTrans *fastInvTrans[NUM_TRANS_TYPE][g_numTransformMatrixSizes] =
 //! \ingroup CommonLib
 //! \{
 
+#if JVET_O0105_ICT
+static inline int64_t square( const int d ) { return d * (int64_t)d; }
+
+template<int signedMode> std::pair<int64_t,int64_t> fwdTransformCbCr( const PelBuf &resCb, const PelBuf &resCr, PelBuf& resC1, PelBuf& resC2 )
+{
+  const Pel*  cb  = resCb.buf;
+  const Pel*  cr  = resCr.buf;
+  Pel*        c1  = resC1.buf;
+  Pel*        c2  = resC2.buf;
+  int64_t     d1  = 0;
+  int64_t     d2  = 0;
+  for( SizeType y = 0; y < resCb.height; y++, cb += resCb.stride, cr += resCr.stride, c1 += resC1.stride, c2 += resC2.stride )
+  {
+    for( SizeType x = 0; x < resCb.width; x++ )
+    {
+      int cbx = cb[x], crx = cr[x];
+      if      ( signedMode ==  1 )
+      { 
+        c1[x] = Pel( ( 4*cbx + 2*crx ) / 5 );
+        d1   += square( cbx - c1[x] ) + square( crx - (c1[x]>>1) );
+      } 
+      else if ( signedMode == -1 )
+      {
+        c1[x] = Pel( ( 4*cbx - 2*crx ) / 5 );
+        d1   += square( cbx - c1[x] ) + square( crx - (-c1[x]>>1) );
+      } 
+      else if ( signedMode ==  2 )
+      {
+        c1[x] = Pel( ( cbx + crx ) / 2 );
+        d1   += square( cbx - c1[x] ) + square( crx - c1[x] );
+      } 
+      else if ( signedMode == -2 )
+      {
+        c1[x] = Pel( ( cbx - crx ) / 2 );
+        d1   += square( cbx - c1[x] ) + square( crx + c1[x] );
+      } 
+      else if ( signedMode ==  3 )
+      {
+        c2[x] = Pel( ( 4*crx + 2*cbx ) / 5 );
+        d1   += square( cbx - (c2[x]>>1) ) + square( crx - c2[x] );
+      }
+      else if ( signedMode == -3 )
+      {
+        c2[x] = Pel( ( 4*crx - 2*cbx ) / 5 );
+        d1   += square( cbx - (-c2[x]>>1) ) + square( crx - c2[x] );
+      } 
+      else
+      {
+        d1   += square( cbx );
+        d2   += square( crx );
+      }
+    }
+  }
+  return std::make_pair(d1,d2);
+}
+
+template<int signedMode> void invTransformCbCr( PelBuf &resCb, PelBuf &resCr )
+{
+  Pel*  cb  = resCb.buf;
+  Pel*  cr  = resCr.buf;
+  for( SizeType y = 0; y < resCb.height; y++, cb += resCb.stride, cr += resCr.stride )
+  {
+    for( SizeType x = 0; x < resCb.width; x++ )
+    {
+      if      ( signedMode ==  1 )  { cr[x] =  cb[x] >> 1;  } 
+      else if ( signedMode == -1 )  { cr[x] = -cb[x] >> 1;  }
+      else if ( signedMode ==  2 )  { cr[x] =  cb[x]; }
+      else if ( signedMode == -2 )  { cr[x] = -cb[x]; }
+      else if ( signedMode ==  3 )  { cb[x] =  cr[x] >> 1; }
+      else if ( signedMode == -3 )  { cb[x] = -cr[x] >> 1; }
+    }
+  }
+}
+#endif
 
 // ====================================================================================================================
 // TrQuant class member functions
@@ -96,6 +170,26 @@ TrQuant::TrQuant() : m_quant( nullptr )
   {
     m_mtsCoeffs[i] = (TCoeff*) xMalloc( TCoeff, MAX_CU_SIZE * MAX_CU_SIZE );
   }
+#if JVET_O0105_ICT
+  {
+    m_invICT      = m_invICTMem + maxAbsIctMode;
+    m_invICT[ 0]  = invTransformCbCr< 0>;
+    m_invICT[ 1]  = invTransformCbCr< 1>;
+    m_invICT[-1]  = invTransformCbCr<-1>;
+    m_invICT[ 2]  = invTransformCbCr< 2>;
+    m_invICT[-2]  = invTransformCbCr<-2>;
+    m_invICT[ 3]  = invTransformCbCr< 3>;
+    m_invICT[-3]  = invTransformCbCr<-3>;
+    m_fwdICT      = m_fwdICTMem + maxAbsIctMode;
+    m_fwdICT[ 0]  = fwdTransformCbCr< 0>;
+    m_fwdICT[ 1]  = fwdTransformCbCr< 1>;
+    m_fwdICT[-1]  = fwdTransformCbCr<-1>;
+    m_fwdICT[ 2]  = fwdTransformCbCr< 2>;
+    m_fwdICT[-2]  = fwdTransformCbCr<-2>;
+    m_fwdICT[ 3]  = fwdTransformCbCr< 3>;
+    m_fwdICT[-3]  = fwdTransformCbCr<-3>;
+  }
+#endif
 }
 
 TrQuant::~TrQuant()
@@ -556,6 +650,83 @@ void TrQuant::invRdpcmNxN(TransformUnit& tu, const ComponentID &compID, PelBuf &
   }
 }
 
+#if JVET_O0105_ICT
+
+std::pair<int64_t,int64_t> TrQuant::fwdTransformICT( const TransformUnit &tu, const PelBuf &resCb, const PelBuf &resCr, PelBuf &resC1, PelBuf &resC2, int jointCbCr )
+{
+  CHECK( Size(resCb) != Size(resCr), "resCb and resCr have different sizes" );
+  CHECK( Size(resCb) != Size(resC1), "resCb and resC1 have different sizes" );
+  CHECK( Size(resCb) != Size(resC2), "resCb and resC2 have different sizes" );
+  return (*m_fwdICT[ TU::getICTMode(tu, jointCbCr) ])( resCb, resCr, resC1, resC2 );
+}
+
+void TrQuant::invTransformICT( const TransformUnit &tu, PelBuf &resCb, PelBuf &resCr )
+{
+  CHECK( Size(resCb) != Size(resCr), "resCb and resCr have different sizes" );
+  (*m_invICT[ TU::getICTMode(tu) ])( resCb, resCr );
+}
+
+std::vector<int> TrQuant::selectICTCandidates( const TransformUnit &tu, CompStorage* resCb, CompStorage* resCr )
+{
+  CHECK( !resCb[0].valid() || !resCr[0].valid(), "standard components are not valid" );
+
+#if JVET_O0543_ICT_ICU_ONLY
+  if( !CU::isIntra( *tu.cu ) )
+  {
+    int cbfMask = 3;
+    resCb[cbfMask].create( tu.blocks[COMPONENT_Cb] );
+    resCr[cbfMask].create( tu.blocks[COMPONENT_Cr] );
+    fwdTransformICT( tu, resCb[0], resCr[0], resCb[cbfMask], resCr[cbfMask], cbfMask );
+    std::vector<int> cbfMasksToTest;
+    cbfMasksToTest.push_back( cbfMask );
+    return cbfMasksToTest;
+  }
+#endif
+
+  std::pair<int64_t,int64_t> pairDist[4];
+  for( int cbfMask = 0; cbfMask < 4; cbfMask++ )
+  {
+    if( cbfMask )
+    {
+      CHECK( resCb[cbfMask].valid() || resCr[cbfMask].valid(), "target components for cbfMask=" << cbfMask << " are already present" );
+      resCb[cbfMask].create( tu.blocks[COMPONENT_Cb] );
+      resCr[cbfMask].create( tu.blocks[COMPONENT_Cr] );
+    }
+    pairDist[cbfMask] = fwdTransformICT( tu, resCb[0], resCr[0], resCb[cbfMask], resCr[cbfMask], cbfMask );
+  }
+
+  std::vector<int> cbfMasksToTest;
+  int64_t minDist1  = std::min<int64_t>( pairDist[0].first, pairDist[0].second );
+  int64_t minDist2  = std::numeric_limits<int64_t>::max();
+  int     cbfMask1  = 0;
+  int     cbfMask2  = 0;
+  for( int cbfMask : { 1, 2, 3 } ) 
+  {
+    if( pairDist[cbfMask].first < minDist1 )
+    {
+      cbfMask2  = cbfMask1; minDist2  = minDist1;
+      cbfMask1  = cbfMask;  minDist1  = pairDist[cbfMask1].first;
+    }
+    else if( pairDist[cbfMask].first < minDist2 )
+    {
+      cbfMask2  = cbfMask;  minDist2  = pairDist[cbfMask2].first;
+    }
+  }
+  if( cbfMask1 )
+  {
+    cbfMasksToTest.push_back( cbfMask1 );
+  }
+  if( cbfMask2 && ( ( minDist2 < (9*minDist1)/8 ) || ( !cbfMask1 && minDist2 < (3*minDist1)/2 ) ) )
+  {
+    cbfMasksToTest.push_back( cbfMask2 );
+  }
+
+  return cbfMasksToTest;
+}
+
+#endif
+
+
 // ------------------------------------------------------------------------------------------------
 // Logical transform
 // ------------------------------------------------------------------------------------------------
diff --git a/source/Lib/CommonLib/TrQuant.h b/source/Lib/CommonLib/TrQuant.h
index b6334ecafe..f762386ac6 100644
--- a/source/Lib/CommonLib/TrQuant.h
+++ b/source/Lib/CommonLib/TrQuant.h
@@ -103,6 +103,12 @@ public:
   void transformSkipQuantOneSample(TransformUnit &tu, const ComponentID &compID, const TCoeff &resiDiff, TCoeff &coeff,    const uint32_t &uiPos, const QpParam &cQP, const bool bUseHalfRoundingPoint);
   void invTrSkipDeQuantOneSample  (TransformUnit &tu, const ComponentID &compID, const TCoeff &pcCoeff,  Pel &reconSample, const uint32_t &uiPos, const QpParam &cQP);
 
+#if JVET_O0105_ICT
+  void                        invTransformICT     ( const TransformUnit &tu, PelBuf &resCb, PelBuf &resCr );
+  std::pair<int64_t,int64_t>  fwdTransformICT     ( const TransformUnit &tu, const PelBuf &resCb, const PelBuf &resCr, PelBuf& resC1, PelBuf& resC2, int jointCbCr = -1 );
+  std::vector<int>            selectICTCandidates ( const TransformUnit &tu, CompStorage* resCb, CompStorage* resCr );
+#endif
+
   void invRdpcmNxN(TransformUnit& tu, const ComponentID &compID, PelBuf &pcResidual);
 #if RDOQ_CHROMA_LAMBDA
   void   setLambdas  ( const double lambdas[MAX_NUM_COMPONENT] )   { m_quant->setLambdas( lambdas ); }
@@ -128,10 +134,17 @@ protected:
   bool     m_scalingListEnabledFlag;
 
 private:
-	DepQuant *m_quant;          //!< Quantizer
+  DepQuant *m_quant;          //!< Quantizer
   TCoeff** m_mtsCoeffs;
   TCoeff   m_tempInMatrix [ 48 ];
   TCoeff   m_tempOutMatrix[ 48 ];
+#if JVET_O0105_ICT
+  static const int maxAbsIctMode = 3;
+  void                      (*m_invICTMem[1+2*maxAbsIctMode])(PelBuf&,PelBuf&);
+  std::pair<int64_t,int64_t>(*m_fwdICTMem[1+2*maxAbsIctMode])(const PelBuf&,const PelBuf&,PelBuf&,PelBuf&);
+  void                      (**m_invICT)(PelBuf&,PelBuf&);
+  std::pair<int64_t,int64_t>(**m_fwdICT)(const PelBuf&,const PelBuf&,PelBuf&,PelBuf&);
+#endif
 
 
   // forward Transform
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index f2adb9ece8..da17946c5e 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -52,6 +52,9 @@
 
 #define JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT      1 // JVET-O0052 Method-1: TU-level context coded bin constraint
 
+#define JVET_O0105_ICT                                    1 // JVET-O0105: inter-chroma transform (ICT) as extension of joint chroma coding (JCC)
+#define JVET_O0543_ICT_ICU_ONLY                           1 // JVET-O0543: ICT only in Intra CUs (was Intra slices, modified during adoption)
+
 #define JVET_O0216_ALF_COEFF_EG3                          1 // JVET-O0216/O0302/O0648: using EG3 for ALF coefficients coding
 
 #define JVET_O0256_ADJUST_THD_DEPQUANT                    1 // JVET-O0256: Fast encoder with adjusted threshold in dependent quantization
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index a29072fead..5cd1f3ab5e 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -4497,6 +4497,17 @@ bool TU::isMTSAllowed(const TransformUnit &tu, const ComponentID compID)
   return mtsAllowed;
 }
 
+#if JVET_O0105_ICT
+int TU::getICTMode( const TransformUnit& tu, int jointCbCr )
+{
+  if( jointCbCr < 0 )
+  {
+    jointCbCr = tu.jointCbCr;
+  }
+  return g_ictModes[ tu.cs->slice->getJointCbCrSignFlag() ][ jointCbCr ];
+}
+#endif
+
 uint32_t TU::getGolombRiceStatisticsIndex(const TransformUnit &tu, const ComponentID &compID)
 {
   const bool transformSkip    = tu.mtsIdx==MTS_SKIP;
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index 6bc6eb80c3..3dbe32cdb9 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -213,7 +213,9 @@ namespace TU
   bool needsBlockSizeTrafoScale       ( const TransformUnit &tu, const ComponentID &compID );
   TransformUnit* getPrevTU          ( const TransformUnit &tu, const ComponentID compID );
   bool           getPrevTuCbfAtDepth( const TransformUnit &tu, const ComponentID compID, const int trDepth );
-
+#if JVET_O0105_ICT
+  int            getICTMode         ( const TransformUnit &tu, int jointCbCr = -1 );
+#endif
 }
 
 uint32_t getCtuAddr        (const Position& pos, const PreCalcValues &pcv);
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index 6abf393f2e..8575af0490 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -2328,6 +2328,13 @@ void CABACReader::transform_unit( TransformUnit& tu, CUCtx& cuCtx, ChromaCbfs& c
   bool        cbfLuma    = ( tu.cbf[ COMPONENT_Y ] != 0 );
   bool        cbfChroma  = ( lumaOnly ? false : ( chromaCbfs.Cb || chromaCbfs.Cr ) );
 
+#if JVET_O0105_ICT
+  if( !lumaOnly )
+  {
+    joint_cb_cr( tu, ( tu.cbf[COMPONENT_Cb] ? 2 : 0 ) + ( tu.cbf[COMPONENT_Cr] ? 1 : 0 ) );
+  }
+#endif
+
   if( cbfLuma || cbfChroma )
   {
     if( cu.cs->pps->getUseDQP() && !cuCtx.isDQPCoded )
@@ -2419,17 +2426,36 @@ void CABACReader::cu_chroma_qp_offset( CodingUnit& cu )
 //    void        residual_coding_subblock( coeffCtx )
 //================================================================================
 
+#if JVET_O0105_ICT
+void CABACReader::joint_cb_cr( TransformUnit& tu, const int cbfMask )
+{
+#if JVET_O0543_ICT_ICU_ONLY
+  if( ( CU::isIntra( *tu.cu ) && cbfMask ) || ( cbfMask == 3 ) )
+#else
+  if( cbfMask )
+#endif
+  {
+    RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET_SIZE2( STATS__CABAC_BITS__JOINT_CB_CR, tu.blocks[COMPONENT_Cr].lumaSize(), CHANNEL_TYPE_CHROMA );
+    tu.jointCbCr = ( m_BinDecoder.decodeBin( Ctx::JointCbCrFlag( cbfMask-1 ) ) ? cbfMask : 0 );
+  }
+}
+#else
 void CABACReader::joint_cb_cr( TransformUnit& tu )
 {
   RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET_SIZE2( STATS__CABAC_BITS__JOINT_CB_CR, tu.blocks[COMPONENT_Cr].lumaSize(), CHANNEL_TYPE_CHROMA );
   tu.jointCbCr = m_BinDecoder.decodeBin( Ctx::JointCbCrFlag( 0 ) );
 }
+#endif
 
 void CABACReader::residual_coding( TransformUnit& tu, ComponentID compID )
 {
   const CodingUnit& cu = *tu.cu;
   DTRACE( g_trace_ctx, D_SYNTAX, "residual_coding() etype=%d pos=(%d,%d) size=%dx%d predMode=%d\n", tu.blocks[compID].compID, tu.blocks[compID].x, tu.blocks[compID].y, tu.blocks[compID].width, tu.blocks[compID].height, cu.predMode );
 
+#if JVET_O0105_ICT
+  if( compID == COMPONENT_Cr && tu.jointCbCr == 3 )
+    return;
+#else
   // Joint Cb-Cr residual mode is signalled if both Cb and Cr cbfs are true
   if ( compID == COMPONENT_Cr && TU::getCbf( tu, COMPONENT_Cb ) )
   {
@@ -2439,6 +2465,7 @@ void CABACReader::residual_coding( TransformUnit& tu, ComponentID compID )
     if ( tu.jointCbCr )
       return;
   }
+#endif
 
   // parse transform skip and explicit rdpcm mode
   mts_coding         ( tu, compID );
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index 72868ea7f0..670d2cc4dc 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -138,7 +138,11 @@ public:
   void        residual_coding_subblock  ( CoeffCodingContext&           cctx,   TCoeff*         coeff, const int stateTransTable, int& state );
   void        residual_codingTS         ( TransformUnit&                tu,     ComponentID     compID );
   void        residual_coding_subblockTS( CoeffCodingContext&           cctx,   TCoeff*         coeff  );
+#if JVET_O0105_ICT
+  void        joint_cb_cr               ( TransformUnit&                tu,     const int cbfMask );
+#else
   void        joint_cb_cr               ( TransformUnit&                tu );
+#endif
 
   // cross component prediction (clause 7.3.8.12)
   void        cross_comp_pred           ( TransformUnit&                tu,     ComponentID     compID );
diff --git a/source/Lib/DecoderLib/DecCu.cpp b/source/Lib/DecoderLib/DecCu.cpp
index c3c84a4b0c..31e5ca4100 100644
--- a/source/Lib/DecoderLib/DecCu.cpp
+++ b/source/Lib/DecoderLib/DecCu.cpp
@@ -228,10 +228,30 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
 
   const QpParam cQP( tu, compID );
 
+#if JVET_O0105_ICT
+  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
+      {
+        m_pcTrQuant->invTransformNxN( tu, COMPONENT_Cr, resiCr, cQP );
+      }
+      m_pcTrQuant->invTransformICT( tu, piResi, resiCr );
+    }
+  }
+  else
+#else
   // Joint chroma residual mode: Cr uses negative of the signalled Cb residual
   if ( tu.jointCbCr && compID == COMPONENT_Cr )
     piResi.copyAndNegate( cs.getResiBuf( tu.blocks[COMPONENT_Cb] ) );
   else
+#endif
   if( TU::getCbf( tu, compID ) )
   {
     m_pcTrQuant->invTransformNxN( tu, compID, piResi, cQP );
@@ -243,9 +263,14 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
 
   //===== reconstruction =====
   flag = flag && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
+#if JVET_O0105_ICT
+  if (flag && (TU::getCbf(tu, compID) || tu.jointCbCr) && isChroma(compID) && slice.getLmcsChromaResidualScaleFlag())
+  {
+#else
   if (flag && TU::getCbf(tu, compID) && isChroma(compID) && slice.getLmcsChromaResidualScaleFlag())
   {
     if ( !(tu.jointCbCr && compID == COMPONENT_Cr) ) // // Joint chroma residual mode: chroma scaling took place already when doing Cb
+#endif
     piResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(compID));
   }
   if( isChroma(compID) && tu.compAlpha[compID] != 0 )
@@ -553,10 +578,30 @@ void DecCu::xDecodeInterTU( TransformUnit & currTU, const ComponentID compID )
 
   const QpParam cQP(currTU, compID);
 
+#if JVET_O0105_ICT
+  if( currTU.jointCbCr && isChroma(compID) )
+  {
+    if( compID == COMPONENT_Cb )
+    {
+      PelBuf resiCr = cs.getResiBuf( currTU.blocks[ COMPONENT_Cr ] );
+      if( currTU.jointCbCr >> 1 )
+      {
+        m_pcTrQuant->invTransformNxN( currTU, COMPONENT_Cb, resiBuf, cQP );
+      }
+      else
+      {
+        m_pcTrQuant->invTransformNxN( currTU, COMPONENT_Cr, resiCr, cQP );
+      }
+      m_pcTrQuant->invTransformICT( currTU, resiBuf, resiCr );
+    }
+  }
+  else
+#else
   // Joint chroma residual mode: Cr uses negative of the signalled Cb residual
   if ( currTU.jointCbCr && compID == COMPONENT_Cr )
     resiBuf.copyAndNegate( cs.getResiBuf( currTU.blocks[COMPONENT_Cb] ) );
   else
+#endif
   if( TU::getCbf( currTU, compID ) )
   {
     m_pcTrQuant->invTransformNxN( currTU, compID, resiBuf, cQP );
@@ -568,9 +613,15 @@ void DecCu::xDecodeInterTU( TransformUnit & currTU, const ComponentID compID )
 
   //===== reconstruction =====
   const Slice           &slice = *cs.slice;
+#if JVET_O0105_ICT
+  if (slice.getLmcsEnabledFlag() && m_pcReshape->getCTUFlag() && isChroma(compID) && (TU::getCbf(currTU, compID) || currTU.jointCbCr)
+   && slice.getLmcsChromaResidualScaleFlag() && currTU.blocks[compID].width * currTU.blocks[compID].height > 4)
+  {
+#else
   if (slice.getLmcsEnabledFlag() && m_pcReshape->getCTUFlag() && isChroma(compID) && TU::getCbf(currTU, compID) && slice.getLmcsChromaResidualScaleFlag() && currTU.blocks[compID].width*currTU.blocks[compID].height > 4)
   {
     if ( !(currTU.jointCbCr && compID == COMPONENT_Cr) ) // Joint chroma residual mode: chroma scaling took place already when doing Cb
+#endif
     resiBuf.scaleSignal(currTU.getChromaAdj(), 0, currTU.cu->cs->slice->clpRng(compID));
   }
   if( isChroma( compID ) && currTU.compAlpha[compID] != 0 )
diff --git a/source/Lib/DecoderLib/VLCReader.cpp b/source/Lib/DecoderLib/VLCReader.cpp
index 45b84cf56f..71cc2d2f11 100644
--- a/source/Lib/DecoderLib/VLCReader.cpp
+++ b/source/Lib/DecoderLib/VLCReader.cpp
@@ -1993,6 +1993,12 @@ void HLSyntaxReader::parseSliceHeader (Slice* pcSlice, ParameterSetManager *para
         pcSlice->setMaxNumTriangleCand(0);
       }
     }
+#if JVET_O0105_ICT
+    if (bChroma)
+    {
+      READ_FLAG( uiCode, "joint_cb_cr_sign_flag" ); pcSlice->setJointCbCrSignFlag( uiCode != 0 );
+    }
+#endif
 
     READ_SVLC( iCode, "slice_qp_delta" );
     pcSlice->setSliceQp (26 + pps->getPicInitQPMinus26() + iCode);
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 4700702c86..053fec7625 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -2211,6 +2211,14 @@ void CABACWriter::transform_unit( const TransformUnit& tu, CUCtx& cuCtx, ChromaC
     }
     cbfChroma = ( cbf[ COMPONENT_Cb ] || cbf[ COMPONENT_Cr ] );
   }
+
+#if JVET_O0105_ICT
+  if( !lumaOnly )
+  {
+    joint_cb_cr( tu, ( cbf[COMPONENT_Cb] ? 2 : 0 ) + ( cbf[COMPONENT_Cr] ? 1 : 0 ) );
+  }
+#endif
+
   if( cbfLuma || cbfChroma )
   {
     if( cu.cs->pps->getUseDQP() && !cuCtx.isDQPCoded )
@@ -2304,16 +2312,35 @@ void CABACWriter::cu_chroma_qp_offset( const CodingUnit& cu )
 //    void        residual_coding_subblock( coeffCtx )
 //================================================================================
 
+#if JVET_O0105_ICT
+void CABACWriter::joint_cb_cr( const TransformUnit& tu, const int cbfMask )
+{
+  CHECK( tu.jointCbCr && tu.jointCbCr != cbfMask, "wrong value of jointCbCr (" << (int)tu.jointCbCr << " vs " << (int)cbfMask << ")" );
+#if JVET_O0543_ICT_ICU_ONLY
+  if( ( CU::isIntra( *tu.cu ) && cbfMask ) || ( cbfMask == 3 ) )
+#else
+  if( cbfMask )
+#endif
+  {
+    m_BinEncoder.encodeBin( tu.jointCbCr ? 1 : 0, Ctx::JointCbCrFlag( cbfMask - 1 ) );
+  }
+}
+#else
 void CABACWriter::joint_cb_cr( const TransformUnit& tu )
 {
   m_BinEncoder.encodeBin( tu.jointCbCr ? 1 : 0, Ctx::JointCbCrFlag( 0 ) );
 }
+#endif
 
 void CABACWriter::residual_coding( const TransformUnit& tu, ComponentID compID )
 {
   const CodingUnit& cu = *tu.cu;
   DTRACE( g_trace_ctx, D_SYNTAX, "residual_coding() etype=%d pos=(%d,%d) size=%dx%d predMode=%d\n", tu.blocks[compID].compID, tu.blocks[compID].x, tu.blocks[compID].y, tu.blocks[compID].width, tu.blocks[compID].height, cu.predMode );
 
+#if JVET_O0105_ICT
+  if( compID == COMPONENT_Cr && tu.jointCbCr == 3 )
+    return;
+#else
   // Joint Cb-Cr residual mode is signalled if both Cb and Cr cbfs are true
   if ( compID == COMPONENT_Cr && TU::getCbf( tu, COMPONENT_Cb ) )
   {
@@ -2323,6 +2350,7 @@ void CABACWriter::residual_coding( const TransformUnit& tu, ComponentID compID )
     if ( tu.jointCbCr )
       return;
   }
+#endif
 
   // code transform skip and explicit rdpcm mode
   mts_coding         ( tu, compID );
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index f67bb0e65c..90a75be39a 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -150,7 +150,11 @@ public:
   void        residual_coding_subblock  ( CoeffCodingContext&           cctx,     const TCoeff*     coeff, const int stateTransTable, int& state );
   void        residual_codingTS         ( const TransformUnit&          tu,       ComponentID       compID );
   void        residual_coding_subblockTS( CoeffCodingContext&           cctx,     const TCoeff*     coeff  );
+#if JVET_O0105_ICT
+  void        joint_cb_cr               ( const TransformUnit&          tu,       const int cbfMask );
+#else
   void        joint_cb_cr               ( const TransformUnit&          tu );
+#endif
 
   // cross component prediction (clause 7.3.8.12)
   void        cross_comp_pred           ( const TransformUnit&          tu,       ComponentID       compID );
diff --git a/source/Lib/EncoderLib/EncSlice.cpp b/source/Lib/EncoderLib/EncSlice.cpp
index 01f99ec69f..a9c4120eda 100644
--- a/source/Lib/EncoderLib/EncSlice.cpp
+++ b/source/Lib/EncoderLib/EncSlice.cpp
@@ -1459,6 +1459,47 @@ void EncSlice::checkDisFracMmvd( Picture* pcPic, uint32_t startCtuTsAddr, uint32
   }
 }
 
+
+#if JVET_O0105_ICT
+void setJointCbCrModes( CodingStructure& cs, const Position topLeftLuma, const Size sizeLuma )
+{
+  bool              sgnFlag = true;
+
+  if( isChromaEnabled( cs.picture->chromaFormat) )
+  {
+    const CompArea  cbArea  = CompArea( COMPONENT_Cb, cs.picture->chromaFormat, Area(topLeftLuma,sizeLuma), true );
+    const CompArea  crArea  = CompArea( COMPONENT_Cr, cs.picture->chromaFormat, Area(topLeftLuma,sizeLuma), true );
+    const CPelBuf   orgCb   = cs.picture->getOrigBuf( cbArea );
+    const CPelBuf   orgCr   = cs.picture->getOrigBuf( crArea );
+    const int       x0      = ( cbArea.x > 0 ? 0 : 1 );
+    const int       y0      = ( cbArea.y > 0 ? 0 : 1 );
+    const int       x1      = ( cbArea.x + cbArea.width  < cs.picture->Cb().width  ? cbArea.width  : cbArea.width  - 1 );
+    const int       y1      = ( cbArea.y + cbArea.height < cs.picture->Cb().height ? cbArea.height : cbArea.height - 1 );
+    const int       cbs     = orgCb.stride;
+    const int       crs     = orgCr.stride;
+    const Pel*      pCb     = orgCb.buf + y0 * cbs;
+    const Pel*      pCr     = orgCr.buf + y0 * crs;
+    int64_t         sumCbCr = 0;
+
+    // determine inter-chroma transform sign from correlation between high-pass filtered (i.e., zero-mean) Cb and Cr planes
+    for( int y = y0; y < y1; y++, pCb += cbs, pCr += crs )
+    {
+      for( int x = x0; x < x1; x++ )
+      {
+        int cb = ( 12*(int)pCb[x] - 2*((int)pCb[x-1] + (int)pCb[x+1] + (int)pCb[x-cbs] + (int)pCb[x+cbs]) - ((int)pCb[x-1-cbs] + (int)pCb[x+1-cbs] + (int)pCb[x-1+cbs] + (int)pCb[x+1+cbs]) );
+        int cr = ( 12*(int)pCr[x] - 2*((int)pCr[x-1] + (int)pCr[x+1] + (int)pCr[x-crs] + (int)pCr[x+crs]) - ((int)pCr[x-1-crs] + (int)pCr[x+1-crs] + (int)pCr[x-1+crs] + (int)pCr[x+1+crs]) );
+        sumCbCr += cb*cr;
+      }
+    }
+
+    sgnFlag = ( sumCbCr < 0 );
+  }
+
+  cs.slice->setJointCbCrSignFlag( sgnFlag );
+}
+#endif
+
+
 void EncSlice::encodeCtus( Picture* pcPic, const bool bCompressEntireSlice, const bool bFastDeltaQP, uint32_t startCtuTsAddr, uint32_t boundingCtuTsAddr, EncLib* pEncLib )
 {
   CodingStructure&  cs            = *pcPic->cs;
@@ -1508,6 +1549,11 @@ void EncSlice::encodeCtus( Picture* pcPic, const bool bCompressEntireSlice, cons
     }
   }
   checkDisFracMmvd( pcPic, startCtuTsAddr, boundingCtuTsAddr );
+
+#if JVET_O0105_ICT
+  setJointCbCrModes( cs, Position(0, 0), cs.area.lumaSize() );
+#endif
+
   // for every CTU in the slice segment (may terminate sooner if there is a byte limit on the slice-segment)
   uint32_t startSliceRsRow = tileMap.getCtuBsToRsAddrMap(startCtuTsAddr) / widthInCtus;
   uint32_t startSliceRsCol = tileMap.getCtuBsToRsAddrMap(startCtuTsAddr) % widthInCtus;
diff --git a/source/Lib/EncoderLib/InterSearch.cpp b/source/Lib/EncoderLib/InterSearch.cpp
index 4f876b9ac2..9827166bd1 100644
--- a/source/Lib/EncoderLib/InterSearch.cpp
+++ b/source/Lib/EncoderLib/InterSearch.cpp
@@ -5946,6 +5946,13 @@ void InterSearch::xEncodeInterResidualQT(CodingStructure &cs, Partitioner &parti
     {
       if( currArea.blocks[compID].valid() )
       {
+#if JVET_O0105_ICT
+        if( compID == COMPONENT_Cr )
+        {
+          const int cbfMask = ( TU::getCbf( currTU, COMPONENT_Cb ) ? 2 : 0) + ( TU::getCbf( currTU, COMPONENT_Cr ) ? 1 : 0 );
+          m_CABACEstimator->joint_cb_cr( currTU, cbfMask );
+        }
+#endif
         if( TU::hasCrossCompPredInfo( currTU, compID ) )
         {
           m_CABACEstimator->cross_comp_pred( currTU, compID );
@@ -6418,6 +6425,12 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 #endif
             m_pcTrQuant->setLambda(m_pcTrQuant->getLambda() / (cRescale*cRescale));
           }
+#if JVET_O0105_ICT
+          if( isChroma( compID ) && tu.cu->cs->slice->getSliceQp() > 18 )
+          {
+            m_pcTrQuant->setLambda( 1.05 * m_pcTrQuant->getLambda() );
+          }
+#endif
           TCoeff     currAbsSum = 0;
           uint64_t   currCompFracBits = 0;
           Distortion currCompDist = 0;
@@ -6503,6 +6516,13 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 
             const bool prevCbf = ( compID == COMPONENT_Cr ? tu.cbf[COMPONENT_Cb] : false );
             m_CABACEstimator->cbf_comp( *csFull, true, compArea, currDepth, prevCbf );
+#if JVET_O0105_ICT
+            if( compID == COMPONENT_Cr )
+            {
+              const int cbfMask = ( tu.cbf[COMPONENT_Cb] ? 2 : 0 ) + 1;
+              m_CABACEstimator->joint_cb_cr( tu, cbfMask );
+            }
+#endif
 
             if( isCrossCPredictionAvailable )
             {
@@ -6613,19 +6633,52 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 
       bool checkJointCbCr = !tu.noResidual && (TU::getCbf(tu, COMPONENT_Cb) || TU::getCbf(tu, COMPONENT_Cr));
 
+#if JVET_O0105_ICT
+      const int channelBitDepth = sps.getBitDepth(toChannelType(COMPONENT_Cb));
+      bool      reshape         = slice.getLmcsEnabledFlag() && m_pcReshape->getCTUFlag() && slice.getLmcsChromaResidualScaleFlag()
+                               && tu.blocks[COMPONENT_Cb].width * tu.blocks[COMPONENT_Cb].height > 4;
+      double minCostCbCr = minCost[COMPONENT_Cb] + minCost[COMPONENT_Cr];
+      bool   isLastBest  = false;
+
+      CompStorage      orgResiCb[4], orgResiCr[4];   // 0:std, 1-3:jointCbCr
+      std::vector<int> jointCbfMasksToTest;
       if ( checkJointCbCr )
       {
+        orgResiCb[0].create(cbArea);
+        orgResiCr[0].create(crArea);
+        orgResiCb[0].copyFrom(cs.getOrgResiBuf(cbArea));
+        orgResiCr[0].copyFrom(cs.getOrgResiBuf(crArea));
+        if (reshape)
+        {
+          orgResiCb[0].scaleSignal(tu.getChromaAdj(), 1, tu.cu->cs->slice->clpRng(COMPONENT_Cb));
+          orgResiCr[0].scaleSignal(tu.getChromaAdj(), 1, tu.cu->cs->slice->clpRng(COMPONENT_Cr));
+        }
+        jointCbfMasksToTest = m_pcTrQuant->selectICTCandidates(tu, orgResiCb, orgResiCr);
+      }
+
+      for (int cbfMask: jointCbfMasksToTest)
+#else
+      if ( checkJointCbCr )
+#endif
+      {
+#if !JVET_O0105_ICT
         const int  channelBitDepth  = sps.getBitDepth(toChannelType(COMPONENT_Cb));
         double     minCostCbCr      = minCost[COMPONENT_Cb] + minCost[COMPONENT_Cr];
         bool       isLastBest       = false;
+#endif
         TCoeff     currAbsSum       = 0;
         uint64_t   currCompFracBits = 0;
         Distortion currCompDistCb   = 0;
         Distortion currCompDistCr   = 0;
         double     currCompCost     = 0;
 
+#if JVET_O0105_ICT
+        tu.jointCbCr = (uint8_t) cbfMask;
+        tu.compAlpha[COMPONENT_Cb] = tu.compAlpha[COMPONENT_Cr] = 0;
+#else
         tu.jointCbCr = 1;
         tu.getCoeffs(COMPONENT_Cr).fill(0);
+#endif
 
         const QpParam cQP(tu, COMPONENT_Cb);  // note: uses tu.transformSkip[compID]
 
@@ -6633,11 +6686,27 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
         m_pcTrQuant->selectLambda(COMPONENT_Cb);
 #endif
         // Lambda is loosened for the joint mode with respect to single modes as the same residual is used for both chroma blocks
+#if JVET_O0105_ICT
+        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( tu.cu->cs->slice->getSliceQp() > 18 )
+        {
+          m_pcTrQuant->setLambda( 1.05 * m_pcTrQuant->getLambda() );
+        }
+#else
         m_pcTrQuant->setLambda( 0.60 * m_pcTrQuant->getLambda() );
+#endif
 
         m_CABACEstimator->getCtx() = ctxStart;
         m_CABACEstimator->resetBits();
 
+#if JVET_O0105_ICT
+        PelBuf cbResi = csFull->getResiBuf(cbArea);
+        PelBuf crResi = csFull->getResiBuf(crArea);
+        cbResi.copyFrom(orgResiCb[cbfMask]);
+        crResi.copyFrom(orgResiCr[cbfMask]);
+#else
         // Copy the original residual into the residual buffer
         csFull->getResiBuf(cbArea).copyFrom(cs.getOrgResiBuf(cbArea));
         csFull->getResiBuf(crArea).copyFrom(cs.getOrgResiBuf(crArea));
@@ -6648,6 +6717,7 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 
         cbResi.subtractAndHalve( crResi );
         bool reshape = slice.getLmcsEnabledFlag() && m_pcReshape->getCTUFlag() && slice.getLmcsChromaResidualScaleFlag() && tu.blocks[COMPONENT_Cb].width*tu.blocks[COMPONENT_Cb].height > 4;
+#endif
 
         if ( reshape )
         {
@@ -6657,14 +6727,65 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
           double cRescale = round((double)(1 << CSCALE_FP_PREC) / (double)(tu.getChromaAdj()));
 #endif
           m_pcTrQuant->setLambda(m_pcTrQuant->getLambda() / (cRescale*cRescale));
+#if !JVET_O0105_ICT
 
           cbResi.scaleSignal(tu.getChromaAdj(), 1, tu.cu->cs->slice->clpRng(COMPONENT_Cb));
+#endif
+        }
+
+#if JVET_O0105_ICT
+        int         codedCbfMask = 0;
+        ComponentID codeCompId   = (tu.jointCbCr >> 1 ? COMPONENT_Cb : COMPONENT_Cr);
+        ComponentID otherCompId  = (codeCompId == COMPONENT_Cr ? COMPONENT_Cb : COMPONENT_Cr);
+        tu.getCoeffs(otherCompId).fill(0);   // do we need that?
+        TU::setCbfAtDepth(tu, otherCompId, tu.depth, false);
+
+        PelBuf &codeResi   = (codeCompId == COMPONENT_Cr ? crResi : cbResi);
+        TCoeff  compAbsSum = 0;
+        m_pcTrQuant->transformNxN(tu, codeCompId, cQP, compAbsSum, m_CABACEstimator->getCtx());
+        if (compAbsSum > 0)
+        {
+          m_pcTrQuant->invTransformNxN(tu, codeCompId, codeResi, cQP);
+          codedCbfMask += (codeCompId == COMPONENT_Cb ? 2 : 1);
+        }
+        else
+        {
+          codeResi.fill(0);
         }
 
+        if (tu.jointCbCr == 3 && codedCbfMask == 2)
+        {
+          codedCbfMask = 3;
+          TU::setCbfAtDepth(tu, COMPONENT_Cr, tu.depth, true);
+        }
+        if (codedCbfMask && tu.jointCbCr != codedCbfMask)
+        {
+          codedCbfMask = 0;
+        }
+        currAbsSum = codedCbfMask;
+#else
         m_pcTrQuant->transformNxN(tu, COMPONENT_Cb, cQP, currAbsSum, m_CABACEstimator->getCtx());
+#endif
 
         if (currAbsSum > 0)
         {
+#if JVET_O0105_ICT
+          m_CABACEstimator->cbf_comp(cs, codedCbfMask >> 1, cbArea, currDepth, false);
+          m_CABACEstimator->cbf_comp(cs, codedCbfMask & 1, crArea, currDepth, codedCbfMask >> 1);
+          m_CABACEstimator->joint_cb_cr(tu, codedCbfMask);
+          if (codedCbfMask >> 1)
+            m_CABACEstimator->residual_coding(tu, COMPONENT_Cb);
+          if (codedCbfMask & 1)
+            m_CABACEstimator->residual_coding(tu, COMPONENT_Cr);
+          currCompFracBits = m_CABACEstimator->getEstFracBits();
+
+          m_pcTrQuant->invTransformICT(tu, cbResi, crResi);
+          if (reshape)
+          {
+            cbResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(COMPONENT_Cb));
+            crResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(COMPONENT_Cr));
+          }
+#else
           // Set cfb also for Cr
           TU::setCbfAtDepth (tu, COMPONENT_Cr, tu.depth, true);
 
@@ -6682,6 +6803,7 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
             cbResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(COMPONENT_Cb));;
 
           crResi.copyAndNegate( cbResi );
+#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);
@@ -6702,7 +6824,18 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
           uiSingleDistComp[COMPONENT_Cb] = currCompDistCb;
           uiSingleDistComp[COMPONENT_Cr] = currCompDistCr;
           minCostCbCr                    = currCompCost;
+#if JVET_O0105_ICT
+          isLastBest = (cbfMask == jointCbfMasksToTest.back());
+          if (!isLastBest)
+          {
+            bestTU.copyComponentFrom(tu, COMPONENT_Cb);
+            bestTU.copyComponentFrom(tu, COMPONENT_Cr);
+            saveCS.getResiBuf(cbArea).copyFrom(csFull->getResiBuf(cbArea));
+            saveCS.getResiBuf(crArea).copyFrom(csFull->getResiBuf(crArea));
+          }
+#else
           isLastBest                     = true;
+#endif
         }
 
         if( !isLastBest )
@@ -6745,6 +6878,13 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
         continue;
       if (tu.blocks[compID].valid())
       {
+#if JVET_O0105_ICT
+        if( compID == COMPONENT_Cr )
+        {
+          const int cbfMask = ( TU::getCbf( tu, COMPONENT_Cb ) ? 2 : 0 ) + ( TU::getCbf( tu, COMPONENT_Cr ) ? 1 : 0 );
+          m_CABACEstimator->joint_cb_cr(tu, cbfMask);
+        }
+#endif
         if( cs.pps->getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() && isChroma(compID) && uiAbsSum[COMPONENT_Y] )
         {
           m_CABACEstimator->cross_comp_pred( tu, compID );
diff --git a/source/Lib/EncoderLib/IntraSearch.cpp b/source/Lib/EncoderLib/IntraSearch.cpp
index c403949248..250a2d0b3f 100644
--- a/source/Lib/EncoderLib/IntraSearch.cpp
+++ b/source/Lib/EncoderLib/IntraSearch.cpp
@@ -1645,6 +1645,13 @@ void IntraSearch::xEncCoeffQT( CodingStructure &cs, Partitioner &partitioner, co
 
   if( currArea.blocks[compID].valid() )
   {
+#if JVET_O0105_ICT
+    if( compID == COMPONENT_Cr )
+    {
+      const int cbfMask = ( TU::getCbf( currTU, COMPONENT_Cb ) ? 2 : 0 ) + ( TU::getCbf( currTU, COMPONENT_Cr ) ? 1 : 0 );
+      m_CABACEstimator->joint_cb_cr( currTU, cbfMask );
+    }
+#endif
     if( TU::hasCrossCompPredInfo( currTU, compID ) )
     {
       m_CABACEstimator->cross_comp_pred( currTU, compID );
@@ -1695,7 +1702,12 @@ uint64_t IntraSearch::xGetIntraFracBitsQTSingleChromaComponent( CodingStructure
   TransformUnit &currTU = *cs.getTU( currArea.blocks[partitioner.chType], partitioner.chType );
 
   //cbf coding
+#if JVET_O0105_ICT
+  const bool prevCbf = ( compID == COMPONENT_Cr ? TU::getCbfAtDepth( currTU, COMPONENT_Cb, partitioner.currTrDepth ) : false );
+  m_CABACEstimator->cbf_comp( cs, TU::getCbfAtDepth( currTU, compID, partitioner.currTrDepth ), currArea.blocks[compID], partitioner.currTrDepth - 1, prevCbf );
+#else
   m_CABACEstimator->cbf_comp( cs, TU::getCbfAtDepth( currTU, compID, partitioner.currTrDepth ), currArea.blocks[compID], partitioner.currTrDepth - 1 );
+#endif
   //coeffs coding and cross comp coding
   if( TU::hasCrossCompPredInfo( currTU, compID ) )
   {
@@ -1724,6 +1736,17 @@ uint64_t IntraSearch::xGetIntraFracBitsQTChroma(TransformUnit& currTU, const Com
 
   if ( currTU.jointCbCr )
   {
+#if JVET_O0105_ICT
+    const int cbfMask = ( TU::getCbf( currTU, COMPONENT_Cb ) ? 2 : 0 ) + ( TU::getCbf( currTU, COMPONENT_Cr ) ? 1 : 0 );
+    m_CABACEstimator->cbf_comp( cs, cbfMask>>1, currTU.blocks[ COMPONENT_Cb ], currTU.depth, false );
+    m_CABACEstimator->cbf_comp( cs, cbfMask &1, currTU.blocks[ COMPONENT_Cr ], currTU.depth, cbfMask>>1 );
+    if( cbfMask )
+      m_CABACEstimator->joint_cb_cr( currTU, cbfMask );
+    if( cbfMask >> 1 )
+      m_CABACEstimator->residual_coding( currTU, COMPONENT_Cb );
+    if( cbfMask & 1 )
+      m_CABACEstimator->residual_coding( currTU, COMPONENT_Cr );
+#else
     if ( TU::getCbf( currTU, COMPONENT_Cb ) )
     {
       m_CABACEstimator->cbf_comp( cs, true, currTU.blocks[ COMPONENT_Cb ], currTU.depth, false );
@@ -1735,16 +1758,31 @@ uint64_t IntraSearch::xGetIntraFracBitsQTChroma(TransformUnit& currTU, const Com
       m_CABACEstimator->cbf_comp( cs, false, currTU.blocks[ COMPONENT_Cb ], currTU.depth, false );
       m_CABACEstimator->cbf_comp( cs, false, currTU.blocks[ COMPONENT_Cr ], currTU.depth, false );
     }
+#endif
   }
   else
   {
     if ( compID == COMPONENT_Cb )
       m_CABACEstimator->cbf_comp( cs, TU::getCbf( currTU, compID ), currTU.blocks[ compID ], currTU.depth, false );
     else
+#if JVET_O0105_ICT
+    {
+      const bool cbCbf    = TU::getCbf( currTU, COMPONENT_Cb );
+      const bool crCbf    = TU::getCbf( currTU, compID );
+      const int  cbfMask  = ( cbCbf ? 2 : 0 ) + ( crCbf ? 1 : 0 );
+      m_CABACEstimator->cbf_comp( cs, crCbf, currTU.blocks[ compID ], currTU.depth, cbCbf );
+      m_CABACEstimator->joint_cb_cr( currTU, cbfMask );
+    }
+#else
       m_CABACEstimator->cbf_comp( cs, TU::getCbf( currTU, compID ), currTU.blocks[ compID ], currTU.depth, TU::getCbf( currTU, COMPONENT_Cb ) );
+#endif
   }
 
+#if JVET_O0105_ICT
+  if( !currTU.jointCbCr && TU::getCbf( currTU, compID ) )
+#else
   if( TU::getCbf( currTU, compID ) )
+#endif
   {
     m_CABACEstimator->residual_coding( currTU, compID );
   }
@@ -1789,6 +1827,9 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
 
 
   //===== init availability pattern =====
+#if JVET_O0105_ICT
+  CHECK( tu.jointCbCr && compID == COMPONENT_Cr, "wrong combination of compID and jointCbCr" );
+#endif
   bool jointCbCr = tu.jointCbCr && compID == COMPONENT_Cb;
 
   if ( compID == COMPONENT_Y )
@@ -1838,6 +1879,10 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
 
   const Slice           &slice = *cs.slice;
   bool flag = slice.getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag()));
+#if JVET_O0105_ICT
+  if (isLuma(compID))
+  {
+#endif
   if (flag && slice.getLmcsChromaResidualScaleFlag() && isChroma(compID))
   {
     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()));
@@ -1878,6 +1923,9 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
     }
     CrossComponentPrediction::crossComponentPrediction(tu, compID, cs.getResiBuf(tu.Y()), piResi, piResi, false);
   }
+#if JVET_O0105_ICT
+  }
+#endif
 
   //===== transform and quantization =====
   //--- init rate estimation arrays for RDOQ ---
@@ -1900,8 +1948,10 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
     double cResScale = round((double)(1 << CSCALE_FP_PREC) / (double)cResScaleInv);
 #endif
     m_pcTrQuant->setLambda(m_pcTrQuant->getLambda() / (cResScale*cResScale));
+#if !JVET_O0105_ICT
     if ( !jointCbCr ) // Joint CbCr signal is to be scaled in the case of joint chroma
     piResi.scaleSignal(cResScaleInv, 1, tu.cu->cs->slice->clpRng(compID));
+#endif
   }
 
   const CompArea &crArea = tu.blocks     [ COMPONENT_Cr ];
@@ -1912,6 +1962,7 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
 
   if ( jointCbCr )
   {
+#if !JVET_O0105_ICT
     // Get Cr prediction and residual
     crResi.copyFrom( crOrg  );
     crResi.subtract( crPred );
@@ -1922,13 +1973,30 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
     // Scale the joint signal
     if ( flag && slice.getLmcsChromaResidualScaleFlag() )
       piResi.scaleSignal(tu.getChromaAdj(), 1, tu.cu->cs->slice->clpRng(compID));
-
+#endif
     // Lambda is loosened for the joint mode with respect to single modes as the same residual is used for both chroma blocks
+#if JVET_O0105_ICT
+    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() );
+#else
     m_pcTrQuant->setLambda( 0.60 * m_pcTrQuant->getLambda() );
+#endif
+  }
+#if JVET_O0105_ICT
+  if( isChroma(compID) && tu.cu->cs->slice->getSliceQp() > 18 )
+  {
+    m_pcTrQuant->setLambda( 1.3 * m_pcTrQuant->getLambda() );
   }
+#else
   else if ( isChroma(compID) && tu.cu->cs->slice->getSliceQp() > 18 )
     m_pcTrQuant->setLambda( 1.10 * m_pcTrQuant->getLambda() );
+#endif
 
+#if JVET_O0105_ICT
+  if( isLuma(compID) )
+  {
+#endif
   double diagRatio = 0, horVerRatio = 0;
 
   if( trModes )
@@ -1961,15 +2029,70 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
   {
     piResi.fill(0);
   }
+#if JVET_O0105_ICT
+  }
+  else // chroma
+  {
+    int         codedCbfMask  = 0;
+    ComponentID codeCompId    = ( tu.jointCbCr ? ( tu.jointCbCr>>1 ? COMPONENT_Cb : COMPONENT_Cr ) : compID );
+    if( tu.jointCbCr )
+    {
+      ComponentID otherCompId = ( codeCompId==COMPONENT_Cr ? COMPONENT_Cb : COMPONENT_Cr );
+      tu.getCoeffs( otherCompId ).fill(0); // do we need that?
+      TU::setCbfAtDepth (tu, otherCompId, tu.depth, false );
+    }
+    PelBuf& codeResi = ( codeCompId == COMPONENT_Cr ? crResi : piResi );
+    uiAbsSum = 0;
+    m_pcTrQuant->transformNxN( tu, codeCompId, cQP, uiAbsSum, m_CABACEstimator->getCtx() );
+    DTRACE( g_trace_ctx, D_TU_ABS_SUM, "%d: comp=%d, abssum=%d\n", DTRACE_GET_COUNTER( g_trace_ctx, D_TU_ABS_SUM ), codeCompId, uiAbsSum );
+    if( uiAbsSum > 0 )
+    {
+      m_pcTrQuant->invTransformNxN(tu, codeCompId, codeResi, cQP);
+      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();
+        return;
+      }
+      m_pcTrQuant->invTransformICT( tu, piResi, crResi );
+      uiAbsSum = codedCbfMask;
+    }
+  }
+#endif
 
   //===== reconstruction =====
   if ( flag && uiAbsSum > 0 && isChroma(compID) && slice.getLmcsChromaResidualScaleFlag() )
   {
     piResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(compID));
+#if JVET_O0105_ICT
+    if( jointCbCr )
+    {
+      crResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(COMPONENT_Cr));
+    }
+#endif
   }
   if (bUseCrossCPrediction)
   {
     CrossComponentPrediction::crossComponentPrediction(tu, compID, cs.getResiBuf(tu.Y()), piResi, piResi, true);
+#if JVET_O0105_ICT
+    if( jointCbCr )
+    {
+      CrossComponentPrediction::crossComponentPrediction(tu, COMPONENT_Cr, cs.getResiBuf(tu.Y()), crResi, crResi, true);
+    }
+#endif
   }
 
   if (slice.getLmcsEnabledFlag() && m_pcReshape->getCTUFlag() && compID == COMPONENT_Y)
@@ -1980,8 +2103,19 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
     piReco.reconstruct(tmpPred, piResi, cs.slice->clpRng(compID));
   }
   else
+#if JVET_O0105_ICT
+  {
+    piReco.reconstruct(piPred, piResi, cs.slice->clpRng( compID ));
+    if( jointCbCr )
+    {
+      crReco.reconstruct(crPred, crResi, cs.slice->clpRng( COMPONENT_Cr ));
+    }
+  }
+#else
   piReco.reconstruct(piPred, piResi, cs.slice->clpRng( compID ));
+#endif
 
+#if !JVET_O0105_ICT
   if ( jointCbCr )
   {
     // Cr uses negative of the signalled Cb residual
@@ -2013,6 +2147,7 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
       ruiDist += m_pcRdCost->getDistPart( crOrg, crReco, bitDepth, COMPONENT_Cr, DF_SSE );
     }
   }
+#endif
 
   //===== update distortion =====
 #if WCG_EXT
@@ -2029,12 +2164,28 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
       ruiDist += m_pcRdCost->getDistPart(piOrg, tmpRecLuma, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE_WTD, &orgLuma);
     }
     else
+#if JVET_O0105_ICT
+    {
       ruiDist += m_pcRdCost->getDistPart(piOrg, piReco, bitDepth, compID, DF_SSE_WTD, &orgLuma);
+      if( jointCbCr )
+      {
+        ruiDist += m_pcRdCost->getDistPart(crOrg, crReco, bitDepth, COMPONENT_Cr, DF_SSE_WTD, &orgLuma);
+      }
+    }
+#else
+      ruiDist += m_pcRdCost->getDistPart(piOrg, piReco, bitDepth, compID, DF_SSE_WTD, &orgLuma);
+#endif
   }
   else
 #endif
   {
     ruiDist += m_pcRdCost->getDistPart( piOrg, piReco, bitDepth, compID, DF_SSE );
+#if JVET_O0105_ICT
+    if( jointCbCr )
+    {
+      ruiDist += m_pcRdCost->getDistPart( crOrg, crReco, bitDepth, COMPONENT_Cr, DF_SSE );
+    }
+#endif
   }
 }
 
@@ -2537,7 +2688,11 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
   TransformUnit &currTU               = *cs.getTU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
   const PredictionUnit &pu            = *cs.getPU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
 
+#if JVET_O0105_ICT
+  bool lumaUsesISP                    = false;
+#else
   bool lumaUsesISP                    = !CS::isDualITree( cs ) && currTU.cu->ispMode;
+#endif
   uint32_t     currDepth                  = partitioner.currTrDepth;
   const PPS &pps                      = *cs.pps;
   ChromaCbfs cbfs                     ( false );
@@ -2610,6 +2765,65 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
       predIntraAng( COMPONENT_Cr, piPredCr, pu);
     }
 
+#if JVET_O0105_ICT
+    // determination of chroma residuals including reshaping and cross-component prediction
+    //----- get chroma residuals -----
+    PelBuf resiCb  = cs.getResiBuf(cbArea);
+    PelBuf resiCr  = cs.getResiBuf(crArea);
+    resiCb.copyFrom( cs.getOrgBuf (cbArea) );
+    resiCr.copyFrom( cs.getOrgBuf (crArea) );
+    resiCb.subtract( piPredCb );
+    resiCr.subtract( piPredCr );
+
+    //----- get reshape parameter ----
+    bool doReshaping = ( cs.slice->getLmcsEnabledFlag() && cs.slice->getLmcsChromaResidualScaleFlag()
+                         && (cs.slice->isIntra() || m_pcReshape->getCTUFlag()) && (cbArea.width * cbArea.height > 4) );
+    if( doReshaping )
+    {
+      const Area area = currTU.Y().valid() ? currTU.Y() : Area(recalcPosition(currTU.chromaFormat, currTU.chType, CHANNEL_TYPE_LUMA, currTU.blocks[currTU.chType].pos()), recalcSize(currTU.chromaFormat, currTU.chType, CHANNEL_TYPE_LUMA, currTU.blocks[currTU.chType].size()));
+      const CompArea &areaY = CompArea(COMPONENT_Y, currTU.chromaFormat, area );
+      PelBuf piPredY;
+      piPredY = cs.picture->getPredBuf(areaY);
+      const Pel avgLuma = piPredY.computeAvg();
+      int adj = m_pcReshape->calculateChromaAdj(avgLuma);
+      currTU.setChromaAdj(adj);
+    }
+
+    //----- get cross component prediction parameters -----
+    bool checkCrossComponentPrediction = PU::isChromaIntraModeCrossCheckMode( pu ) && pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() && TU::getCbf( currTU, COMPONENT_Y );
+    int  compAlpha[MAX_NUM_COMPONENT] = { 0, 0, 0 };
+    if( checkCrossComponentPrediction )
+    {
+      compAlpha[COMPONENT_Cb] = xCalcCrossComponentPredictionAlpha( currTU, COMPONENT_Cb, m_pcEncCfg->getUseReconBasedCrossCPredictionEstimate() );
+      compAlpha[COMPONENT_Cr] = xCalcCrossComponentPredictionAlpha( currTU, COMPONENT_Cr, m_pcEncCfg->getUseReconBasedCrossCPredictionEstimate() );
+      if( compAlpha[COMPONENT_Cb] == 0 && compAlpha[COMPONENT_Cr] == 0 )
+      {
+        checkCrossComponentPrediction = false;
+      }
+    }
+
+    //===== store original residual signals (std and crossCompPred) =====
+    CompStorage  orgResiCb[5], orgResiCr[5]; // 0:std, 1-3:jointCbCr (placeholder at this stage), 4:crossComp
+    for( int k = 0; k < (checkCrossComponentPrediction?5:1); k+=4 )
+    {
+      orgResiCb[k].create( cbArea );
+      orgResiCr[k].create( crArea );
+      if( k >= 4 ) {
+        CrossComponentPrediction::crossComponentPrediction( currTU, COMPONENT_Cb, cs.getResiBuf(currTU.Y()), resiCb, orgResiCb[k], false);
+        CrossComponentPrediction::crossComponentPrediction( currTU, COMPONENT_Cr, cs.getResiBuf(currTU.Y()), resiCr, orgResiCr[k], false);
+      } else {
+        orgResiCb[k].copyFrom( resiCb );
+        orgResiCr[k].copyFrom( resiCr );
+      }
+      if( doReshaping )
+      {
+        int cResScaleInv = currTU.getChromaAdj();
+        orgResiCb[k].scaleSignal( cResScaleInv, 1, currTU.cu->cs->slice->clpRng(COMPONENT_Cb) );
+        orgResiCr[k].scaleSignal( cResScaleInv, 1, currTU.cu->cs->slice->clpRng(COMPONENT_Cr) );
+      }
+    }
+#endif
+
     for( uint32_t c = COMPONENT_Cb; c < numTBlocks; c++)
     {
       const ComponentID compID  = ComponentID(c);
@@ -2620,8 +2834,9 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
       Distortion singleDistCTmp = 0;
       double     singleCostTmp  = 0;
 
+#if !JVET_O0105_ICT
       const bool checkCrossComponentPrediction = PU::isChromaIntraModeCrossCheckMode( pu ) && pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() && TU::getCbf( currTU, COMPONENT_Y );
-
+#endif
       const int  crossCPredictionModesToTest = checkCrossComponentPrediction ? 2 : 1;
       const int  totalModesToTest            = crossCPredictionModesToTest;
       const bool isOneMode                   = false;
@@ -2639,7 +2854,14 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
       {
         for (int crossCPredictionModeId = 0; crossCPredictionModeId < crossCPredictionModesToTest; crossCPredictionModeId++)
         {
+#if JVET_O0105_ICT
+          resiCb.copyFrom( orgResiCb[4*crossCPredictionModeId] );
+          resiCr.copyFrom( orgResiCr[4*crossCPredictionModeId] );
+
+          currTU.compAlpha    [compID] = ( crossCPredictionModeId ? compAlpha[compID] : 0 );
+#else
           currTU.compAlpha    [compID] = 0;
+#endif
 
           currModeId++;
 
@@ -2736,32 +2958,96 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
       double     bestCostCbCr   = bestCostCb + bestCostCr;
       Distortion bestDistCbCr   = bestDistCb + bestDistCr;
       int        bestJointCbCr  = 0;
+#if JVET_O0105_ICT
+      bool       lastIsBest     = false;
+      std::vector<int>  jointCbfMasksToTest;
+      if( TU::getCbf(tmpTU, COMPONENT_Cb) || TU::getCbf(tmpTU, COMPONENT_Cr) )
+      {
+        jointCbfMasksToTest = m_pcTrQuant->selectICTCandidates( currTU, orgResiCb, orgResiCr );
+      }
+
+      for( int cbfMask : jointCbfMasksToTest )
+#else
       bool       checkJointCbCr = TU::getCbf(tmpTU, COMPONENT_Cb) || TU::getCbf(tmpTU, COMPONENT_Cr);
 
       if ( checkJointCbCr )
+#endif
       {
         Distortion distTmp = 0;
 
+#if JVET_O0105_ICT
+        currTU.jointCbCr               = (uint8_t)cbfMask;
+#else
         currTU.jointCbCr               = 1;
+#endif
         currTU.compAlpha[COMPONENT_Cb] = 0;
+        currTU.compAlpha[COMPONENT_Cr] = 0;
 
         m_CABACEstimator->getCtx() = ctxStartTU;
 
+#if JVET_O0105_ICT
+        resiCb.copyFrom( orgResiCb[cbfMask] );
+        resiCr.copyFrom( orgResiCr[cbfMask] );
+        xIntraCodingTUBlock( currTU, COMPONENT_Cb, false, distTmp, 0 );
+
+        double costTmp = std::numeric_limits<double>::max();
+        if( distTmp < std::numeric_limits<Distortion>::max() )
+        {
+          uint64_t bits  = xGetIntraFracBitsQTChroma( currTU, COMPONENT_Cb );
+          costTmp = m_pcRdCost->calcRdCost( bits, distTmp );
+        }
+#else
         xIntraCodingTUBlock( currTU, COMPONENT_Cb, false, distTmp, 0 );
 
         uint64_t bits  = xGetIntraFracBitsQTChroma( currTU, COMPONENT_Cb );
         double costTmp = m_pcRdCost->calcRdCost( bits, distTmp );
+#endif
 
         if( costTmp < bestCostCbCr )
         {
           bestCostCbCr  = costTmp;
           bestDistCbCr  = distTmp;
+#if JVET_O0105_ICT
+          bestJointCbCr = currTU.jointCbCr;
+
+          // store data
+          if( cbfMask != jointCbfMasksToTest.back() )
+          {
+#if KEEP_PRED_AND_RESI_SIGNALS
+            saveCS.getOrgResiBuf(cbArea).copyFrom(cs.getOrgResiBuf(cbArea));
+            saveCS.getOrgResiBuf(crArea).copyFrom(cs.getOrgResiBuf(crArea));
+#endif
+            saveCS.getPredBuf   (cbArea).copyFrom(cs.getPredBuf   (cbArea));
+            saveCS.getPredBuf   (crArea).copyFrom(cs.getPredBuf   (crArea));
+            if( keepResi )
+            {
+              saveCS.getResiBuf (cbArea).copyFrom(cs.getResiBuf   (cbArea));
+              saveCS.getResiBuf (crArea).copyFrom(cs.getResiBuf   (crArea));
+            }
+            saveCS.getRecoBuf   (cbArea).copyFrom(cs.getRecoBuf   (cbArea));
+            saveCS.getRecoBuf   (crArea).copyFrom(cs.getRecoBuf   (crArea));
+
+            tmpTU.copyComponentFrom(currTU, COMPONENT_Cb);
+            tmpTU.copyComponentFrom(currTU, COMPONENT_Cr);
+
+            ctxBest = m_CABACEstimator->getCtx();
+          }
+          else
+          {
+            lastIsBest = true;
+          }
+#else
           bestJointCbCr = 1;
+#endif
         }
       }
 
       // Retrieve the best CU data (unless it was the very last one tested)
+#if JVET_O0105_ICT
+      if ( !( maxModesTested == 1 && jointCbfMasksToTest.empty() ) && !lastIsBest )
+#else
       if ( !(maxModesTested == 1 && !checkJointCbCr) && bestJointCbCr == 0 )
+#endif
       {
 #if KEEP_PRED_AND_RESI_SIGNALS
         cs.getPredBuf   (cbArea).copyFrom(saveCS.getPredBuf   (cbArea));
@@ -2795,7 +3081,11 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
       cbfs.cbf(COMPONENT_Cb) = TU::getCbf(currTU, COMPONENT_Cb);
       cbfs.cbf(COMPONENT_Cr) = TU::getCbf(currTU, COMPONENT_Cr);
 
+#if JVET_O0105_ICT
+      currTU.jointCbCr = ( (cbfs.cbf(COMPONENT_Cb) + cbfs.cbf(COMPONENT_Cr)) ? bestJointCbCr : 0 );
+#else
       currTU.jointCbCr = cbfs.cbf(COMPONENT_Cb) ? bestJointCbCr : 0;
+#endif
       cs.dist         += bestDistCbCr;
     }
   }
diff --git a/source/Lib/EncoderLib/VLCWriter.cpp b/source/Lib/EncoderLib/VLCWriter.cpp
index cfa58352f8..7d6f17a6bb 100644
--- a/source/Lib/EncoderLib/VLCWriter.cpp
+++ b/source/Lib/EncoderLib/VLCWriter.cpp
@@ -1375,6 +1375,13 @@ void HLSWriter::codeSliceHeader         ( Slice* pcSlice )
         pcSlice->setMaxNumTriangleCand(0);
       }
     }
+#if JVET_O0105_ICT
+    if (chromaEnabled)
+    {
+      WRITE_FLAG( pcSlice->getJointCbCrSignFlag() ? 1 : 0, "joint_cb_cr_sign_flag" );
+    }
+#endif
+
     int iCode = pcSlice->getSliceQp() - ( pcSlice->getPPS()->getPicInitQPMinus26() + 26 );
     WRITE_SVLC( iCode, "slice_qp_delta" );
     if (pcSlice->getPPS()->getSliceChromaQpFlag())
-- 
GitLab