From 7b46c3c930bb20d5f45221bb87ca44d24787ffa5 Mon Sep 17 00:00:00 2001
From: Ramin Ghaznavi Youvalari <raminyouvalari@xiaomi.com>
Date: Tue, 23 Apr 2024 10:59:11 +0000
Subject: [PATCH] JVET-AH0076 (Test 2.2): Occurrence-based intra coding

---
 source/Lib/CommonLib/CommonDef.h         |   5 +
 source/Lib/CommonLib/Contexts.h          |   3 +
 source/Lib/CommonLib/Contexts_ecm12.inl  |  20 +
 source/Lib/CommonLib/Contexts_ecm13.inl  |  25 +
 source/Lib/CommonLib/IntraPrediction.cpp | 965 ++++++++++++++++++++++-
 source/Lib/CommonLib/IntraPrediction.h   |   5 +
 source/Lib/CommonLib/TypeDef.h           |   1 +
 source/Lib/CommonLib/Unit.cpp            |  18 +
 source/Lib/CommonLib/Unit.h              |   6 +
 source/Lib/CommonLib/UnitTools.cpp       | 205 +++++
 source/Lib/CommonLib/UnitTools.h         |   6 +-
 source/Lib/DecoderLib/CABACReader.cpp    |  26 +-
 source/Lib/DecoderLib/CABACReader.h      |   3 +
 source/Lib/DecoderLib/DecCu.cpp          |  15 +
 source/Lib/EncoderLib/CABACWriter.cpp    |  32 +-
 source/Lib/EncoderLib/CABACWriter.h      |   3 +
 source/Lib/EncoderLib/EncCu.cpp          |  42 +-
 source/Lib/EncoderLib/IntraSearch.cpp    | 373 ++++++++-
 source/Lib/EncoderLib/IntraSearch.h      |   8 +
 19 files changed, 1737 insertions(+), 24 deletions(-)

diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index afe29a101..7819b74f6 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -593,6 +593,11 @@ static const int LM_CHROMA_IDX = NUM_LUMA_MODE; ///< chroma mode index for deriv
 #if ENABLE_DIMD
 static const int DIMD_IDX =                                        99; ///< index for intra DIMD mode
 #endif
+#if JVET_AH0076_OBIC
+static const int OBIC_IDX =                                       250;
+static const int OBIC_FUSION_NUM =                                  6;
+static const int NUM_OBIC_CUS    =                            13 + 18; // (13: adjacent, 18: non-adjacent)
+#endif
 #if JVET_AB0155_SGPM
 static const int SGPM_IDX =                                       200;   ///< index for SGPM mode
 #endif
diff --git a/source/Lib/CommonLib/Contexts.h b/source/Lib/CommonLib/Contexts.h
index af293adb4..2ca8497de 100644
--- a/source/Lib/CommonLib/Contexts.h
+++ b/source/Lib/CommonLib/Contexts.h
@@ -723,6 +723,9 @@ public:
 #if ENABLE_DIMD
   static const CtxSet   DimdFlag;
 #endif
+#if JVET_AH0076_OBIC
+  static const CtxSet   obicFlag;
+#endif
 #if JVET_W0123_TIMD_FUSION
   static const CtxSet   TimdFlag;
 #endif
diff --git a/source/Lib/CommonLib/Contexts_ecm12.inl b/source/Lib/CommonLib/Contexts_ecm12.inl
index 7d86bb30b..f7a15cabf 100644
--- a/source/Lib/CommonLib/Contexts_ecm12.inl
+++ b/source/Lib/CommonLib/Contexts_ecm12.inl
@@ -4983,5 +4983,25 @@ const CtxSet ContextSetCfg::CCPMergeFusionType = ContextSetCfg::addCtxSet({
   { 107 },
 });
 #endif
+#if JVET_AH0076_OBIC
+const CtxSet ContextSetCfg::obicFlag = ContextSetCfg::addCtxSet
+({
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { DWS, },
+  { DWS, },
+  { DWS, },
+  { DWE, },
+  { DWE, },
+  { DWE, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  });
+#endif
 // CONTEXTS WSA STOP
 #endif
diff --git a/source/Lib/CommonLib/Contexts_ecm13.inl b/source/Lib/CommonLib/Contexts_ecm13.inl
index e7ba65fbe..1b05c9d71 100644
--- a/source/Lib/CommonLib/Contexts_ecm13.inl
+++ b/source/Lib/CommonLib/Contexts_ecm13.inl
@@ -6273,5 +6273,30 @@ const CtxSet ContextSetCfg::CCPMergeFusionType = ContextSetCfg::addCtxSet({
   { 119 },
 });
 #endif
+#if JVET_AH0076_OBIC
+const CtxSet ContextSetCfg::obicFlag = ContextSetCfg::addCtxSet
+({
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { DWS, },
+  { DWS, },
+  { DWS, },
+  { DWS, },
+  { DWE, },
+  { DWE, },
+  { DWE, },
+  { DWE, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  });
+#endif
 // CONTEXTS WSA STOP
 #endif
diff --git a/source/Lib/CommonLib/IntraPrediction.cpp b/source/Lib/CommonLib/IntraPrediction.cpp
index ea9f99873..57b1fcc33 100644
--- a/source/Lib/CommonLib/IntraPrediction.cpp
+++ b/source/Lib/CommonLib/IntraPrediction.cpp
@@ -1496,14 +1496,21 @@ void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, co
 
 #if ENABLE_DIMD
 #if JVET_AB0157_INTRA_FUSION
+#if JVET_AH0076_OBIC
+  if (pu.cu->dimd && pu.cu->dimdBlending && isLuma(compID) && !pu.cu->obicFlag)
+#else
   if (pu.cu->dimd && pu.cu->dimdBlending && isLuma(compID))
+#endif
   {
     int width = piPred.width;
     int height = piPred.height;
     const UnitArea localUnitArea( pu.chromaFormat, Area( 0, 0, width, height ) );
 
     PelBuf planarBuffer = m_tempBuffer[0].getBuf( localUnitArea.Y() );
-
+#if JVET_AH0076_OBIC
+    const bool applyPdpc = m_ipaParam.applyPDPC;
+    initIntraPatternChType(*pu.cu, pu.Y(), true, 0, false);
+#endif
 #if JVET_AG0146_DIMD_ITMP_IBC
     if (pu.cu->isBvDimd && pu.cu->ispMode == 0)
     {
@@ -1512,16 +1519,35 @@ void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, co
     else
     {
 #endif
+#if JVET_AH0076_OBIC
+      {
+        PredictionUnit puTmp = pu;
+        puTmp.intraDir[0] = PLANAR_IDX;
+        initPredIntraParams(puTmp, pu.Y(), *(pu.cs->sps));
+        const CPelBuf &srcBuf = CPelBuf(getPredictorPtr(compID), srcStride, srcHStride);
+#if JVET_AC0105_DIRECTIONAL_PLANAR
+        xPredIntraPlanar(srcBuf, planarBuffer, 0);
+#else
+        xPredIntraPlanar(srcBuf, planarBuffer);
+#endif
+        if (m_ipaParam.applyPDPC)
+        {
+          xIntraPredPlanarDcPdpc( srcBuf, planarBuffer.buf, planarBuffer.stride, width, height, false );
+        }
+      }
+#else
 #if JVET_AC0105_DIRECTIONAL_PLANAR
     xPredIntraPlanar(srcBuf, planarBuffer, 0);
 #else
     xPredIntraPlanar(srcBuf, planarBuffer);
 #endif
+#endif
 #if JVET_AG0146_DIMD_ITMP_IBC
     }
 #endif
-
+#if !JVET_AH0076_OBIC
     const bool applyPdpc = m_ipaParam.applyPDPC;
+#endif
 
     bool blendModes[DIMD_FUSION_NUM-2] = {false};
     PelBuf predAngExtra[DIMD_FUSION_NUM-2];
@@ -1538,6 +1564,9 @@ void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, co
         PredictionUnit puTmp = pu;
         puTmp.intraDir[0] = pu.cu->dimdBlendMode[i];
         initPredIntraParams(puTmp, pu.Y(), *(pu.cs->sps));
+#if JVET_AH0076_OBIC
+        const CPelBuf &srcBuf = CPelBuf(getPredictorPtr(compID), srcStride, srcHStride);
+#endif
 #if JVET_W0123_TIMD_FUSION
         xPredIntraAng(srcBuf, predAngExtra[i], channelType, clpRng, false, srcBuf2nd, pu.cu->ispMode!=NOT_INTRA_SUBPARTITIONS);
 #else
@@ -1933,7 +1962,86 @@ void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, co
   }
 #endif
 #endif
-
+#if JVET_AH0076_OBIC
+  if (pu.cu->obicFlag && pu.cu->obicIsBlended && isLuma(compID))
+  {
+    int width = piPred.width;
+    int height = piPred.height;
+    const UnitArea localUnitArea( pu.chromaFormat, Area( 0, 0, width, height ) );
+    bool blendModes[OBIC_FUSION_NUM-1] = {false};
+    PelBuf predFusion[OBIC_FUSION_NUM-1];
+    const bool applyPdpc = m_ipaParam.applyPDPC;
+    initIntraPatternChType(*pu.cu, pu.Y(), true, 0, false);
+    PredictionUnit pu2 = pu;
+    for (int i = 0; i < OBIC_FUSION_NUM - 1; i++)
+    {
+      blendModes[i] = false;
+      predFusion[i] = m_tempBuffer[i].getBuf( localUnitArea.Y() );
+      if (pu.cu->obicMode[i + 1] >= 0)
+      {
+        blendModes[i] = true;
+        pu2.intraDir[0]  = pu.cu->obicMode[i + 1];
+        initPredIntraParams(pu2, pu.Y(), *(pu.cs->sps));
+        pu2.intraDir[0]  = pu.cu->obicMode[0];
+        const CPelBuf &srcBuf = CPelBuf(getPredictorPtr(compID), srcStride, srcHStride);
+        switch (pu.cu->obicMode[i + 1])
+        {
+#if JVET_AC0105_DIRECTIONAL_PLANAR
+          case (PLANAR_IDX): pu.cu->isBvDimd ? predUsingBv(predFusion[i].buf, predFusion[i].stride, pu.cu->bvDimd, *pu.cu) : xPredIntraPlanar(srcBuf, predFusion[i], 0); break;
+#else
+          case (PLANAR_IDX): pu.cu->isBvDimd ? predUsingBv(predFusion[i].buf, predFusion[i].stride, pu.cu->bvDimd, *pu.cu) : xPredIntraPlanar(srcBuf, predFusion[i]); break;
+#endif
+          case(DC_IDX):     xPredIntraDc(srcBuf, predFusion[i], channelType, false); break;
+#if JVET_AB0157_INTRA_FUSION
+          default:          xPredIntraAng(srcBuf, predFusion[i], channelType, clpRng, false, srcBuf2nd, pu.cu->ispMode!=NOT_INTRA_SUBPARTITIONS); break;
+#else
+          default:          xPredIntraAng(srcBuf, predFusion[i], channelType, clpRng, bExtIntraDir); break;
+#endif
+        }
+        if( (m_ipaParam.applyPDPC || pu.ciipPDPC) && (pu.cu->obicMode[i + 1] == PLANAR_IDX || pu.cu->obicMode[i + 1] == DC_IDX) )
+        {
+          if (pu.cu->obicMode[i + 1] == PLANAR_IDX && pu.cu->isBvDimd)
+          {
+            continue;
+          }
+          xIntraPredPlanarDcPdpc( srcBuf, predFusion[i].buf, predFusion[i].stride, width, height, false );
+        }
+      }
+    }
+    m_ipaParam.applyPDPC = applyPdpc;
+    const int log2WeightSum = 6;
+    Pel *pelPred = piPred.buf;
+    Pel *pelFusion[OBIC_FUSION_NUM - 1];
+    int weight[OBIC_FUSION_NUM];
+    weight[0] = pu.cu->obicFusionWeight[0];
+    for (int i = 0; i < OBIC_FUSION_NUM - 1; i++)
+    {
+      pelFusion[i] = predFusion[i].buf;
+      weight[i + 1] = pu.cu->obicFusionWeight[i + 1];
+    }
+    for( int y = 0; y < height; y++ )
+    {
+      for( int x = 0; x < width; x++ )
+      {
+        int blend = pelPred[x] * weight[0];
+        for (int i = 0; i < OBIC_FUSION_NUM - 1; i++)
+        {
+          if (blendModes[i])
+          {
+            blend += pelFusion[i][x] * weight[ i + 1];
+          }
+        }
+        pelPred[x] = (Pel)(blend >> log2WeightSum);
+      }
+      pelPred += piPred.stride;
+      for (int i = 0; i < OBIC_FUSION_NUM - 1; i++)
+      {
+        pelFusion[i] += predFusion[i].stride;
+      }
+    }
+    return;
+  }
+#endif
 #if JVET_W0123_TIMD_FUSION
   if (pu.cu->timd && pu.cu->timdIsBlended && isLuma(compID))
   {
@@ -3939,24 +4047,327 @@ void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const Ch
         }
       }
     }
-  }
+  }
+
+  // Flip the block if this is the horizontal mode
+  if( !bIsModeVer )
+  {
+    for( int y = 0; y < height; y++ )
+    {
+      Pel *dst = pDst.buf + y;
+
+      for( int x = 0; x < width; x++ )
+      {
+        *dst = pDstBuf[x];
+        dst += pDst.stride;
+      }
+      pDstBuf += dstStride;
+    }
+  }
+}
+#if JVET_AH0076_OBIC
+void IntraPrediction::generateObicBlending(PelBuf &piPred, const PredictionUnit &pu, PelBuf predFusion[OBIC_FUSION_NUM - 1], bool blendModes[OBIC_FUSION_NUM - 1], int planarIdx)
+{
+  const int height = piPred.height;
+  const int width = piPred.width;
+  const UnitArea localUnitArea( pu.chromaFormat, Area( 0, 0, width, height ) );
+  PelBuf predFusionBV = m_tempBuffer[7].getBuf( localUnitArea.Y() );
+  if (pu.cu->isBvDimd)
+  {
+    predUsingBv(predFusionBV.buf, predFusionBV.stride, pu.cu->bvDimd, *pu.cu);
+  }
+  const int log2WeightSum = 6;
+  Pel *pelPred = piPred.buf;
+  Pel *pelFusion[OBIC_FUSION_NUM - 1];
+  Pel *pelFusionBv = predFusionBV.buf;
+  int weight[OBIC_FUSION_NUM];
+  weight[0] = pu.cu->obicFusionWeight[0];
+  for (int i = 0; i < OBIC_FUSION_NUM - 1; i++)
+  {
+    pelFusion[i] = predFusion[i].buf;
+    weight[i + 1] = pu.cu->obicFusionWeight[i + 1];
+  }
+  for( int y = 0; y < height; y++ )
+  {
+    for( int x = 0; x < width; x++ )
+    {
+      int blend = pelPred[x] * weight[0];
+      for (int i = 0; i < OBIC_FUSION_NUM - 1; i++)
+      {
+        if (blendModes[i])
+        {
+          if (i == planarIdx && pu.cu->isBvDimd)
+          {
+            blend += pelFusionBv[x] * weight[ i + 1];
+          }
+          else
+          {
+            blend += pelFusion[i][x] * weight[ i + 1];
+          }
+        }
+      }
+      pelPred[x] = (Pel)(blend >> log2WeightSum);
+    }
+    pelPred += piPred.stride;
+    for (int i = 0; i < OBIC_FUSION_NUM - 1; i++)
+    {
+      pelFusion[i] += predFusion[i].stride;
+    }
+    pelFusionBv += predFusionBV.stride;
+  }
+  return;
+}
+void IntraPrediction::generateDimdBlending(PelBuf &piPred, const PredictionUnit &pu, PelBuf &piBlock0, PelBuf &piBlock1, PelBuf &piBlock2, PelBuf &piBlock3, PelBuf &plnBlock)
+{
+    // do blending
+    int width = piPred.width;
+    int height = piPred.height;
+    const UnitArea localUnitArea( pu.chromaFormat, Area( 0, 0, width, height ) );
+    PelBuf planarBuffer = m_tempBuffer[0].getBuf( localUnitArea.Y() );
+#if JVET_AG0146_DIMD_ITMP_IBC
+    if (pu.cu->isBvDimd && pu.cu->ispMode == 0)
+    {
+      predUsingBv(planarBuffer.buf, planarBuffer.stride, pu.cu->bvDimd, *pu.cu);
+    }
+    else
+    {
+#endif
+      planarBuffer.copyFrom(plnBlock);
+#if JVET_AG0146_DIMD_ITMP_IBC
+    }
+#endif
+
+    bool blendModes[DIMD_FUSION_NUM-2] = {false};
+    for( int i = 0; i < DIMD_FUSION_NUM-2; ++i)
+    {
+#if JVET_AC0098_LOC_DEP_DIMD
+      blendModes[i] = (pu.cu->dimdBlendMode[i] != PLANAR_IDX);
+#else
+      blendModes[i] = (i==0 || pu.cu->dimdBlendMode[i] != PLANAR_IDX);
+#endif
+
+    }
+    PelBuf predAngExtra[DIMD_FUSION_NUM-2];
+    predAngExtra[0] = m_tempBuffer[1].getBuf( localUnitArea.Y() );
+    predAngExtra[1] = m_tempBuffer[2].getBuf( localUnitArea.Y() );
+    predAngExtra[2] = m_tempBuffer[3].getBuf( localUnitArea.Y() );
+    predAngExtra[3] = m_tempBuffer[4].getBuf( localUnitArea.Y() );
+    predAngExtra[0].copyFrom(piBlock0);
+    predAngExtra[1].copyFrom(piBlock1);
+    predAngExtra[2].copyFrom(piBlock2);
+    predAngExtra[3].copyFrom(piBlock3);
+
+#if JVET_AC0098_LOC_DEP_DIMD
+    PelBuf predAngNonLocDep = m_tempBuffer[7].getBuf( localUnitArea.Y() );
+    PelBuf predAngVer       = m_tempBuffer[5].getBuf( localUnitArea.Y() );
+    PelBuf predAngHor       = m_tempBuffer[6].getBuf( localUnitArea.Y() );
+
+    Pel* pelVer = predAngVer.buf;
+    int strideVer = predAngVer.stride;
+    Pel* pelHor = predAngHor.buf;
+    int strideHor = predAngHor.stride;
+    Pel *pelNonLocDep = predAngNonLocDep.buf;
+    int strideNonLocDep = predAngNonLocDep.stride;
+
+    bool useLocDepBlending = false;
+    int weightVer = 0, weightHor = 0, weightNonLocDep = 0;
+    weightNonLocDep += pu.cu->dimdRelWeight[1];
+    for (int i = 0; i < DIMD_FUSION_NUM-1; i++)
+    {
+      if (i == 0  || blendModes[i-1])
+      {
+        if (pu.cu->dimdLocDep[i] == 1)
+        {
+          weightVer += (i == 0 ? pu.cu->dimdRelWeight[0] : pu.cu->dimdRelWeight[i+ 1]);
+        }
+        else if (pu.cu->dimdLocDep[i] == 2)
+        {
+          weightHor += (i == 0 ? pu.cu->dimdRelWeight[0] : pu.cu->dimdRelWeight[i+ 1]);
+        }
+        else
+        {
+          weightNonLocDep += (i == 0 ? pu.cu->dimdRelWeight[0] : pu.cu->dimdRelWeight[i+1]);
+        }
+      }
+    }
+
+    if(weightHor || weightVer)
+    {
+      useLocDepBlending = true;
+    }
+
+    if(!useLocDepBlending)
+    {
+      pelNonLocDep = piPred.buf;
+      strideNonLocDep = piPred.stride;
+    }
+
+    for (int locDep = 0; locDep < 3; locDep++)
+    {
+      int totWeight = (locDep == 0 ? weightNonLocDep : (locDep == 1 ? weightVer : weightHor));
+      if (totWeight == 0)
+      {
+        continue;
+      }
+
+      int weights[6] = {0};
+      weights[0] =  (pu.cu->dimdLocDep[0] == locDep) ? pu.cu->dimdRelWeight[0] : 0;
+      weights[1] =  (blendModes[0] && pu.cu->dimdLocDep[1] == locDep) ? pu.cu->dimdRelWeight[2] : 0;
+      weights[2] =  (blendModes[1] && pu.cu->dimdLocDep[2] == locDep) ? pu.cu->dimdRelWeight[3] : 0;
+      weights[3] =  (blendModes[2] && pu.cu->dimdLocDep[3] == locDep) ? pu.cu->dimdRelWeight[4] : 0;
+      weights[4] =  (blendModes[3] && pu.cu->dimdLocDep[4] == locDep) ? pu.cu->dimdRelWeight[5] : 0;
+      weights[5] =  (locDep == 0) ? pu.cu->dimdRelWeight[1] : 0;
+
+      int num2blend = 0;
+      int blendIndexes[DIMD_FUSION_NUM] = {0};
+      for (int i = 0; i < DIMD_FUSION_NUM; i++)
+      {
+        if (weights[i] != 0)
+        {
+          blendIndexes[num2blend] = i;
+          num2blend++;
+        }
+      }
+#if JVET_W0123_TIMD_FUSION
+      if( (num2blend == 1 ) || (num2blend <=3 && (totWeight == (1 << (floorLog2(totWeight))) ) ))
+      {
+        int index = blendIndexes[0];
+        if(locDep == 0)
+        {
+          pelNonLocDep = (index == 0 ? piPred.buf : (index == 5 ? planarBuffer.buf : predAngExtra[index-1].buf));
+          strideNonLocDep = (index == 0 ? piPred.stride : (index == 5 ? planarBuffer.stride : predAngExtra[index-1].stride));
+        }
+        else if(locDep == 1)
+        {
+          pelVer = (index == 0 ? piPred.buf : predAngExtra[index-1].buf);
+          strideVer = (index == 0 ? piPred.stride : predAngExtra[index-1].stride);
+        }
+        else
+        {
+          pelHor = (index == 0 ? piPred.buf : predAngExtra[index-1].buf);
+          strideHor = (index == 0 ? piPred.stride : predAngExtra[index-1].stride);
+        }
+        Pel* pCur = (locDep == 0 ? pelNonLocDep : (locDep == 1 ? pelVer : pelHor));
+        int strideCur = (locDep == 0 ? strideNonLocDep : (locDep == 1 ? strideVer : strideHor));
+
+        int factor = 64 / totWeight;
+        if (num2blend == 2)
+        {
+          int index1 = blendIndexes[1];
+          Pel* p1 = (index1 == 0 ? piPred.buf : (index1 == 5 ?  planarBuffer.buf : predAngExtra[index1-1].buf));
+          int stride1 = (index1 == 0 ? piPred.stride : (index1 == 5 ? planarBuffer.stride : predAngExtra[index1-1].stride));
+          int w0 = (weights[index]*factor);
+          int w1 = 64 - w0;
+          m_timdBlending(pCur, strideCur, p1, stride1, w0, w1,width, height);
+        }
+        else if(num2blend == 3)
+        {
+          int index1 = blendIndexes[1];
+          Pel* p1 = (index1 == 0 ? piPred.buf : (index1 == 5 ?  planarBuffer.buf : predAngExtra[index1-1].buf));
+          int stride1 = (index1 == 0 ? piPred.stride : (index1 == 5 ? planarBuffer.stride : predAngExtra[index1-1].stride));
+
+          int index2 = blendIndexes[2];
+          Pel* p2 = (index2 == 0 ? piPred.buf : (index2 == 5 ?  planarBuffer.buf : predAngExtra[index2-1].buf));
+          int stride2 = (index2 == 0 ? piPred.stride : (index2 == 5 ? planarBuffer.stride : predAngExtra[index2-1].stride));
+          int w0 = (weights[index]*factor);
+          int w1 = (weights[index1]*factor);
+          int w2 = 64  - w0 - w1;
+          m_dimdBlending(pCur, strideCur, p1, stride1, p2, stride2, w0, w1, w2, width, height);
+        }
+      }
+      else
+#endif
+      {
+        Pel* pCur = (locDep == 0 ? pelNonLocDep : (locDep == 1 ? pelVer : pelHor));
+        int strideCur = (locDep == 0 ? strideNonLocDep : (locDep == 1 ? strideVer : strideHor));
+        Pel *pelPlanar = planarBuffer.buf;
+        int stridePlanar = planarBuffer.stride;
+        Pel *pelPredAng0 = piPred.buf;
+        Pel *pelPredAng1 = predAngExtra[0].buf;
+        Pel *pelPredAng2 = predAngExtra[1].buf;
+        Pel *pelPredAng3 = predAngExtra[2].buf;
+        Pel *pelPredAng4 = predAngExtra[3].buf;
+        int stride0 = piPred.stride;
+        int stride1 = predAngExtra[0].stride;
+        int stride2 = predAngExtra[1].stride;
+        int stride3 = predAngExtra[2].stride;
+        int stride4 = predAngExtra[3].stride;
+        for( int y = 0; y < height; y++ )
+        {
+          for( int x = 0; x < width; x++ )
+          {
+            int blend = pelPredAng0[x] * weights[0];
+            blend += blendModes[0] ? pelPredAng1[x] * weights[1] : 0;
+            blend += blendModes[1] ? pelPredAng2[x] * weights[2] : 0;
+            blend += blendModes[2] ? pelPredAng3[x] * weights[3] : 0;
+            blend += blendModes[3] ? pelPredAng4[x] * weights[4] : 0;
+            blend += pelPlanar[x] * weights[5];
+            pCur[x] = (Pel)(blend / totWeight);
+          }
+          pCur += strideCur;
+          pelPredAng0 += stride0;
+          pelPredAng1 += stride1;
+          pelPredAng2 += stride2;
+          pelPredAng3 += stride3;
+          pelPredAng4 += stride4;
+          pelPlanar += stridePlanar;
+        }
+      }
+    }
+
+    if (useLocDepBlending)
+    {
+      int mode = ((weightHor > 0 && weightVer > 0) ? 0 : (weightVer > 0 ? 1 : 2));
+
+      Pel *pelDst = piPred.buf;
+      int strideDst = piPred.stride;
+#if JVET_W0123_TIMD_FUSION && JVET_AG0092_ENHANCED_TIMD_FUSION
+      xLocationdepBlending(pelDst, strideDst, pelVer, strideVer, pelHor, strideHor, pelNonLocDep, strideNonLocDep, width, height,mode, weightVer, weightHor, weightNonLocDep);
+#else
+      xDimdLocationdepBlending(pelDst, strideDst, pelVer, strideVer, pelHor, strideHor, pelNonLocDep, strideNonLocDep, width, height,mode, weightVer, weightHor, weightNonLocDep);
+#endif
+    }
+#else
+    const int log2WeightSum = 6;
+    Pel *pelPred = piPred.buf;
+    Pel *pelPlanar = planarBuffer.buf;
+    Pel *pelPredAng = predAngExtra[0].buf;
+    int  w0 = pu.cu->dimdRelWeight[0], w1 = pu.cu->dimdRelWeight[1], w2 = pu.cu->dimdRelWeight[2];
+
+    Pel *pelPredAng2 = predAngExtra[1].buf;
+    Pel *pelPredAng3 = predAngExtra[2].buf;
+    Pel *pelPredAng4 = predAngExtra[3].buf;
+    int  w3         = pu.cu->dimdRelWeight[3];
+    int  w4         = pu.cu->dimdRelWeight[4];
+    int  w5         = pu.cu->dimdRelWeight[5];
 
-  // Flip the block if this is the horizontal mode
-  if( !bIsModeVer )
-  {
     for( int y = 0; y < height; y++ )
     {
-      Pel *dst = pDst.buf + y;
-
       for( int x = 0; x < width; x++ )
       {
-        *dst = pDstBuf[x];
-        dst += pDst.stride;
+        int blend = pelPred[x] * w0;
+        blend += pelPlanar[x] * w1;
+#if JVET_AC0098_LOC_DEP_DIMD
+        blend += blendModes[0] ? pelPredAng[x] * w2 : 0;
+#else
+        blend += pelPredAng[x] * w2;
+#endif
+        blend += blendModes[1] ? pelPredAng2[x] * w3 : 0;
+        blend += blendModes[2] ? pelPredAng3[x] * w4 : 0;
+        blend += blendModes[3] ? pelPredAng4[x] * w5 : 0;
+        pelPred[x] = (Pel)(blend >> log2WeightSum);
       }
-      pDstBuf += dstStride;
+
+      pelPred += piPred.stride;
+      pelPlanar += planarBuffer.stride;
+      pelPredAng += predAngExtra[0].stride;
+      pelPredAng2 += predAngExtra[1].stride;
+      pelPredAng3 += predAngExtra[2].stride;
+      pelPredAng4 += predAngExtra[3].stride;
     }
-  }
+#endif
 }
+#endif
 
 #if JVET_Z0050_DIMD_CHROMA_FUSION
 #if JVET_AD0188_CCP_MERGE
@@ -4680,7 +5091,12 @@ void IntraPrediction::initIntraPatternChType(const CodingUnit &cu, const CompAre
   auto mrlIndex2D = cu.firstPU->multiRefIdx;
   m_ipaParam.fetchRef2nd   = area.compID == COMPONENT_Y;
   m_ipaParam.fetchRef2nd  &= area.width * area.height >= SIZE_CONSTRAINT;
+#if JVET_AH0076_OBIC
+  m_ipaParam.fetchRef2nd  &= !((cu.dimd && !cu.obicFlag && cu.dimdBlending) || (cu.dimd && cu.obicFlag && cu.obicIsBlended));
+#else
   m_ipaParam.fetchRef2nd  &= !(cu.dimd && cu.dimdBlending);
+#endif
+
 #if JVET_AH0065_RELAX_LINE_BUFFER
   m_ipaParam.fetchRef2nd  &= !(mrlIndex2D == MULTI_REF_LINE_IDX[MRL_NUM_REF_LINES - 1] && (cu.block(COMPONENT_Y).y <= MULTI_REF_LINE_IDX[MRL_NUM_REF_LINES - 1]));
 #else
@@ -6882,6 +7298,529 @@ int IntraPrediction::getBestNonAnglularMode(const CPelBuf& recoBuf, const CompAr
   return 0;
 }
 #endif
+  
+#if JVET_AH0076_OBIC
+  void IntraPrediction::deriveObicMode( const CPelBuf &recoBuf, const CompArea &area, CodingUnit &cu )
+  {
+    /* -------------------------------------------------------------------
+    Step 1: Collect adjacent neighbour cands
+    Step 2: Collect non-adjacent neighbour cands
+    Step 3: Sort neighbours by distance
+    Step 4: Build Histogram of oCcurrence (HoC) from remaining neighbours
+    Step 5: Get top 6 amplitudes from the HoC
+    Step 6: Compute the fusion weights from amplitudes and store in CU
+    ---------------------------------------------------------------------- */
+
+    cu.obicIsBlended = false;
+    for (int i = 0; i < OBIC_FUSION_NUM; i++)
+    {
+      cu.obicMode[i] = -1;
+      cu.obicFusionWeight[i] = 0;
+    }
+    
+    int histogram[NUM_LUMA_MODE];
+    for (int i = 0; i < NUM_LUMA_MODE; i++)
+    {
+      histogram[i] = 0;
+    }
+    const int step = 4;
+    const int numCUs = NUM_OBIC_CUS;
+    const CodingUnit* cuNeighbours[numCUs];
+
+    /* -----------------------------------------------------------------
+    ----------- Step 1: Collect adjacent neighbour cands ---------------
+    ----------------------------------------------------------------- */
+
+    cuNeighbours[0] = cu.cs->getCURestricted(cu.lumaPos().offset(-1, 0), cu, CH_L);
+    cuNeighbours[1] = cu.cs->getCURestricted(cu.lumaPos().offset(0, -1), cu, CH_L);
+    cuNeighbours[2] = cu.cs->getCURestricted(cu.lumaPos().offset(-1, -1), cu, CH_L);
+    
+    const CodingUnit* cuTemp;
+    for (int i = 0; i <= cu.lheight(); i += step)
+    {
+      cuTemp = cu.cs->getCURestricted(cu.lumaPos().offset(-1, i), cu, CH_L);
+      if (cuTemp && CU::isIntra(*cuTemp))
+      {
+        cuNeighbours[0] = cuTemp;
+        break;
+      }
+    }
+    for (int i = 0; i <= cu.lwidth(); i += step)
+    {
+      cuTemp = cu.cs->getCURestricted(cu.lumaPos().offset(i, -1), cu, CH_L);
+      if (cuTemp && CU::isIntra(*cuTemp))
+      {
+        cuNeighbours[1] = cuTemp;
+        break;
+      }
+    }
+    
+    const CodingUnit* cuLeft = cu.cs->getCURestricted(cu.lumaPos().offset(-1, 0), cu, CH_L);
+    const CodingUnit* cuTop  = cu.cs->getCURestricted(cu.lumaPos().offset(0, -1), cu, CH_L);
+    cuNeighbours[3] = cuLeft ? cu.cs->getCURestricted(cuLeft->lumaPos().offset(cuLeft->lwidth()-1, cuLeft->lheight()), cu, CH_L) : NULL;
+    cuNeighbours[4] = cuTop ? cu.cs->getCURestricted(cuTop->lumaPos().offset(cuTop->lwidth(), cuTop->lheight() - 1), cu, CH_L) : NULL;
+    cuNeighbours[5] = cuNeighbours[3] ? cu.cs->getCURestricted(cuNeighbours[3]->lumaPos().offset(cuNeighbours[3]->lwidth()-1, cuNeighbours[3]->lheight()), cu, CH_L) : NULL;
+    cuNeighbours[6] = cuNeighbours[4] ? cu.cs->getCURestricted(cuNeighbours[4]->lumaPos().offset(cuNeighbours[4]->lwidth(), cuNeighbours[4]->lheight() - 1), cu, CH_L) : NULL;
+    cuNeighbours[7] = cuLeft ? cu.cs->getCURestricted(cuLeft->lumaPos().offset(-1, 0), cu, CH_L) : NULL;
+    cuNeighbours[8] = cuTop ? cu.cs->getCURestricted(cuTop->lumaPos().offset(0, -1), cu, CH_L) : NULL;
+    cuNeighbours[9] = cuNeighbours[3] ? cu.cs->getCURestricted(cuNeighbours[3]->lumaPos().offset(-1, 0), cu, CH_L) : NULL;
+    cuNeighbours[10] = cuNeighbours[4] ? cu.cs->getCURestricted(cuNeighbours[4]->lumaPos().offset(0, -1), cu, CH_L) : NULL;
+    cuNeighbours[11] = cuNeighbours[5] ? cu.cs->getCURestricted(cuNeighbours[5]->lumaPos().offset(-1, 0), cu, CH_L) : NULL;
+    cuNeighbours[12] = cuNeighbours[6] ? cu.cs->getCURestricted(cuNeighbours[6]->lumaPos().offset(0, -1), cu, CH_L) : NULL;
+    
+    if ((!cuNeighbours[9]) && cuNeighbours[2])
+    {
+      cuNeighbours[9] = cu.cs->getCURestricted(cuNeighbours[2]->lumaPos().offset(-1, cuNeighbours[2]->lheight()-1), cu, CH_L);
+    }
+    if ((!cuNeighbours[10]) && cuNeighbours[2])
+    {
+      cuNeighbours[10] = cu.cs->getCURestricted(cuNeighbours[2]->lumaPos().offset(cuNeighbours[2]->lwidth()-1, -1), cu, CH_L);
+    }
+    if ((!cuNeighbours[11]) && cuLeft && cuNeighbours[7])
+    {
+      cuNeighbours[11] = cu.cs->getCURestricted(cuNeighbours[7]->lumaPos().offset(-1, 0), cu, CH_L);
+    }
+    if ((!cuNeighbours[12]) && cuTop && cuNeighbours[8])
+    {
+      cuNeighbours[12] = cu.cs->getCURestricted(cuNeighbours[8]->lumaPos().offset(0, -1), cu, CH_L);
+    }
+    
+    /* -----------------------------------------------------------------
+    ---------- Step 2: Collect non-adjacent neighbour cands-------------
+    ----------------------------------------------------------------- */
+
+    const Position  topLeft = area.topLeft();
+    int       offsetX           = 0;
+    int       offsetY           = 0;
+    int       cout              = 13;
+    const int numNACandidate[4] = { 3, 5, 5, 5 };
+    const int idxMap[4][5]      = { { 0, 1, 4 }, { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 4 } };
+    for (int iDistanceIndex = 0; iDistanceIndex < NADISTANCE_LEVEL; iDistanceIndex++)
+    {
+      const int iNADistanceHor = cu.Y().width * (iDistanceIndex + 1);
+      const int iNADistanceVer = cu.Y().height * (iDistanceIndex + 1);
+      for (int iNASPIdx = 0; iNASPIdx < numNACandidate[iDistanceIndex]; iNASPIdx++)
+      {
+        switch (idxMap[iDistanceIndex][iNASPIdx])
+        {
+          case 0: offsetX = -iNADistanceHor - 1; offsetY = cu.Y().height + iNADistanceVer - 1; break;
+          case 1: offsetX = cu.Y().width + iNADistanceHor - 1; offsetY = -iNADistanceVer - 1; break;
+          case 2: offsetX = cu.Y().width >> 1; offsetY = -iNADistanceVer - 1; break;
+          case 3: offsetX = -iNADistanceHor - 1; offsetY = cu.Y().height >> 1; break;
+          case 4: offsetX = -iNADistanceHor - 1; offsetY = -iNADistanceVer - 1; break;
+          default: printf("error!"); exit(0); break;
+        }
+        cuNeighbours[cout++] = cu.cs->getCURestricted(topLeft.offset(offsetX, offsetY), cu, CH_L);
+      }
+    }
+
+    /* -----------------------------------------------------------------
+    ---------------- Step 3: Sort neighbours by distance ---------------
+    ----------------------------------------------------------------- */
+
+    int limitMaxNeigh = 20;
+    bool useNeighbour[numCUs];
+    int  neighboursInDistOrder[numCUs];
+    int  dists[numCUs];
+    for (int i = 0; i < numCUs; i++)
+    {
+      useNeighbour[i] = false;
+      neighboursInDistOrder[i] = 0;
+      dists[i] = 0;
+    }
+    for (int i = 0; i < numCUs; i++)
+    {
+      dists[i] = MAX_INT;
+    }
+    int numToMix = 0;
+    for (int i = 0; i < numCUs; i++)
+    {
+      useNeighbour[i] = (cuNeighbours[i] && CU::isIntra(*cuNeighbours[i]));
+      if (useNeighbour[i])
+      {
+        for (int j = i-1; j >= 0; j--)
+        {
+          if (!useNeighbour[j])
+          {
+            continue;
+          }
+          useNeighbour[i] &= (cuNeighbours[i]->lx() != cuNeighbours[j]->lx() || cuNeighbours[i]->ly() != cuNeighbours[j]->ly());
+        }
+      }
+      int curNeigh = i;
+      int curDist = (useNeighbour[i] ? abs( (int)(cu.lx()) - (int)(cuNeighbours[i]->lx())) + abs( (int)(cu.ly()) - (int)(cuNeighbours[i]->ly())) : 0);
+      
+      for (int j = 0; j < numCUs; j++)
+      {
+        if (curDist < dists[j])
+        {
+          for (int k = numCUs - 1; k > j; k--)
+          {
+            dists[k] = dists[k - 1];
+            neighboursInDistOrder[k] = neighboursInDistOrder[k - 1];
+          }
+          dists[j] = curDist;
+          neighboursInDistOrder[j] = curNeigh;
+          break;
+        }
+      }
+    }
+    for (int i = 0; i < numCUs; i++)
+    {
+      int j = neighboursInDistOrder[i];
+      if (limitMaxNeigh > 0 && numToMix >= limitMaxNeigh)
+      {
+        useNeighbour[j] = false;
+        continue;
+      }
+      if (useNeighbour[j])
+      {
+        numToMix ++;
+      }
+    }
+    
+
+     /* -----------------------------------------------------------------
+     --------------------------- Step 4: --------------------------------
+     ---------------- Build Histogram of oCcurrence (HoC) ---------------
+     --------------------- from remaining neighbours --------------------
+     ----------------------------------------------------------------- */
+
+
+    for (int i = 0; i < numCUs; i++)
+    {
+      if (!useNeighbour[i])
+      {
+        continue;
+      }
+      int numSamples = cuNeighbours[i]->lumaSize().width * cuNeighbours[i]->lumaSize().height;
+      if (cuNeighbours[i]->timd)
+      {
+        int m = MAP131TO67(cuNeighbours[i]->timdMode);
+        histogram[m] += numSamples;
+        if (cuNeighbours[i]->timdIsBlended && cuNeighbours[i]->timdFusionWeight[1] > 0)
+        {
+          int m = MAP131TO67(cuNeighbours[i]->timdModeSecondary);
+          histogram[m] += numSamples;
+          if (cuNeighbours[i]->timdFusionWeight[2] > 0)
+          {
+            int m = MAP131TO67(cuNeighbours[i]->timdModeNonAng);
+            histogram[m] += numSamples;
+          }
+        }
+      }
+      else if (cuNeighbours[i]->dimd && !cuNeighbours[i]->obicFlag)
+      {
+        int m = cuNeighbours[i]->dimdMode;
+        if (m >= 0 && m < NUM_LUMA_MODE)
+        {
+          histogram[m] += numSamples;
+        }
+        if (cuNeighbours[i]->dimdBlending)
+        {
+          for (int idx = 0; idx < DIMD_FUSION_NUM - 1; idx++)
+          {
+            m = cuNeighbours[i]->dimdBlendMode[idx];
+            if (m >= 0 && m < NUM_LUMA_MODE && cuNeighbours[i]->dimdRelWeight[idx + 1] > 0)
+            {
+              histogram[m] += numSamples;
+            }
+          }
+        }
+      }
+      else if (cuNeighbours[i]->dimd && cuNeighbours[i]->obicFlag)
+      {
+        int m = cuNeighbours[i]->obicMode[0];
+        histogram[m] += numSamples;
+        if (cuNeighbours[i]->obicIsBlended)
+        {
+          for (int idx = 1; idx < OBIC_FUSION_NUM; idx++)
+          {
+            m = cuNeighbours[i]->obicMode[idx];
+            if (m >= 0 && cuNeighbours[i]->obicFusionWeight[idx] > 0)
+            {
+              histogram[m] += numSamples;
+            }
+          }
+        }
+      }
+      else if (cuNeighbours[i]->sgpm)
+      {
+        int m1 = cuNeighbours[i]->sgpmMode0;
+        int m2 = cuNeighbours[i]->sgpmMode1;
+        if (m1 >= 0 && m1 < NUM_LUMA_MODE)
+        {
+          histogram[m1] += numSamples;
+        }
+        if (m2 >= 0 && m2 < NUM_LUMA_MODE)
+        {
+          histogram[m2] += numSamples;
+        }
+      }
+      else if (cuNeighbours[i]->tmrlFlag)
+      {
+        int m = MAP131TO67(cuNeighbours[i]->firstPU->intraDir[0]);
+        histogram[m] += numSamples;
+      }
+#if JVET_AG0058_EIP
+      else if (cuNeighbours[i]->eipFlag && cu.slice->getSliceType() != I_SLICE)
+      {
+        int m = cuNeighbours[i]->eipModel.eipDimdMode;
+        histogram[m] += numSamples;
+      }
+#endif
+#if JVET_AC0115_INTRA_TMP_DIMD_MTS_LFNST
+      else if (cuNeighbours[i]->tmpFlag && cu.slice->getSliceType() != I_SLICE)
+      {
+        int m = cuNeighbours[i]->intraTmpDimdMode;
+        histogram[m] += numSamples;
+      }
+#endif
+#if JVET_AB0067_MIP_DIMD_LFNST
+      else if (cuNeighbours[i]->mipFlag && cu.slice->getSliceType() != I_SLICE)
+      {
+        int m = cuNeighbours[i]->mipDimdMode;
+        histogram[m] += numSamples;
+      }
+#endif
+      else if (CU::isIntra(*cuNeighbours[i]) && !cuNeighbours[i]->tmpFlag && !cuNeighbours[i]->mipFlag && !cuNeighbours[i]->eipFlag && !CU::isIBC(*cuNeighbours[i]) && !CU::isPLT(*cuNeighbours[i]))
+      {
+        int m = cuNeighbours[i]->firstPU->intraDir[0];
+        histogram[m] += numSamples;
+      }
+    }
+
+    // Penalize Dimd modes to impose diversity between OBIC and DIMD
+    if (cu.dimdMode >= 0 && cu.dimdMode < NUM_LUMA_MODE)
+    {
+      histogram[cu.dimdMode] >>= 1;
+    }
+    if (cu.dimdBlending && cu.dimdBlendMode[0] >= 0 && cu.dimdBlendMode[0] < NUM_LUMA_MODE && cu.dimdRelWeight[1] > 0)
+    {
+      histogram[cu.dimdBlendMode[0]] >>= 1;
+    }
+    
+  /* -----------------------------------------------------------------
+  ------------------- Step 5: Get top 6 amplitudes -------------------
+  -------------------------- from the HoC ----------------------------
+  ----------------------------------------------------------------- */
+
+    int bestModes[OBIC_FUSION_NUM], bestAmps[OBIC_FUSION_NUM];
+    for (int i = 0; i < OBIC_FUSION_NUM; i++)
+    {
+      bestModes[i] = -1;
+      bestAmps[i] = 0;
+    }
+    for (int i = 0; i < NUM_LUMA_MODE; i++)
+    {
+      int curMode = i;
+      if (curMode == PLANAR_IDX)
+      {
+        continue;
+      }
+      if (histogram[curMode] > bestAmps[0])
+      {
+        bestAmps[5] = bestAmps[4];
+        bestAmps[4] = bestAmps[3];
+        bestAmps[3] = bestAmps[2];
+        bestAmps[2] = bestAmps[1];
+        bestAmps[1] = bestAmps[0];
+        bestAmps[0] = histogram[curMode];
+        bestModes[5] = bestModes[4];
+        bestModes[4] = bestModes[3];
+        bestModes[3] = bestModes[2];
+        bestModes[2] = bestModes[1];
+        bestModes[1] = bestModes[0];
+        bestModes[0] = curMode;
+      }
+      else if (histogram[curMode] > bestAmps[1])
+      {
+        bestAmps[5] = bestAmps[4];
+        bestAmps[4] = bestAmps[3];
+        bestAmps[3] = bestAmps[2];
+        bestAmps[2] = bestAmps[1];
+        bestAmps[1] = histogram[curMode];
+        bestModes[5] = bestModes[4];
+        bestModes[4] = bestModes[3];
+        bestModes[3] = bestModes[2];
+        bestModes[2] = bestModes[1];
+        bestModes[1] = curMode;
+      }
+      else if (histogram[curMode] > bestAmps[2])
+      {
+        bestAmps[5] = bestAmps[4];
+        bestAmps[4] = bestAmps[3];
+        bestAmps[3] = bestAmps[2];
+        bestAmps[2] = histogram[curMode];
+        bestModes[5] = bestModes[4];
+        bestModes[4] = bestModes[3];
+        bestModes[3] = bestModes[2];
+        bestModes[2] = curMode;
+      }
+      else if (histogram[curMode] > bestAmps[3])
+      {
+        bestAmps[5] = bestAmps[4];
+        bestAmps[4] = bestAmps[3];
+        bestAmps[3] = histogram[curMode];
+        bestModes[5] = bestModes[4];
+        bestModes[4] = bestModes[3];
+        bestModes[3] = curMode;
+      }
+      else if (histogram[curMode] > bestAmps[4])
+      {
+        bestAmps[5] = bestAmps[4];
+        bestAmps[4] = histogram[curMode];
+        bestModes[5] = bestModes[4];
+        bestModes[4] = curMode;
+      }
+      else if (histogram[curMode] > bestAmps[5])
+      {
+        bestAmps[5] = histogram[curMode];
+        bestModes[5] = curMode;
+      }
+    }
+    if (bestModes[0] < 0)
+    {
+      return;
+    }
+
+    int count = 0;
+    for (int i = 0; i < OBIC_FUSION_NUM; i++)
+    {
+      count += bestModes[i] >= 0 ? 1 : 0;
+    }
+
+
+    /* -----------------------------------------------------------------
+    -------------- Step 6: Compute the fusion weights ------------------
+    ---------------- from amplitudes and store in CU -------------------
+    ----------------------------------------------------------------- */
+
+    int planarWeight = count == 1 ? 21 : 64/4;
+    int log2BlendWeight = 6;
+    int sumWeight = (1 << log2BlendWeight);
+    if (bestModes[1] < 0)
+    {
+      cu.obicMode[0] = bestModes[0];
+      cu.obicIsBlended = false;
+      cu.obicFusionWeight[0] = sumWeight;
+      for (int i = 1; i < OBIC_FUSION_NUM; i++)
+      {
+        cu.obicMode[i] = -1;
+        cu.obicFusionWeight[i] = 0;
+      }
+      cu.obicIsBlended = true;
+      cu.obicMode[1] = PLANAR_IDX;
+      cu.obicFusionWeight[0] = sumWeight - planarWeight;
+      cu.obicFusionWeight[1] = planarWeight;
+    }
+    else
+    {
+      sumWeight = sumWeight - planarWeight;
+      if (count == OBIC_FUSION_NUM)
+      {
+        bestAmps[count - 1] = 0;
+      }
+      
+      int s1 = 0;
+      for (int i = 0; i < OBIC_FUSION_NUM; i++)
+      {
+        bestAmps[i] = 10 * bestAmps[i];
+      }
+      for (int i = 0; i < OBIC_FUSION_NUM; i++)
+      {
+        s1 = s1 + bestAmps[i];
+      }
+      int x = floorLog2(s1);
+      CHECK(x < 0, "floor log2 value should be no negative");
+      int normS1 = (s1 << 4 >> x) & 15;
+      int v = g_gradDivTable[normS1] | 8;
+      x += (normS1 != 0);
+      int shift = x + 3;
+      int add = (1 << (shift - 1));
+      int iRatio[OBIC_FUSION_NUM] = { 0 };
+      for (int i = 0; i < OBIC_FUSION_NUM; i++)
+      {
+        iRatio[i] = (bestAmps[i] * v * sumWeight + add) >> shift;
+        if (bestAmps[i] == 0)
+        {
+          iRatio[i] = 0;
+        }
+        if( iRatio[i] > sumWeight )
+        {
+          iRatio[i] = sumWeight;
+        }
+        CHECK( iRatio[i] > sumWeight, "Wrong ratio in OBIC" );
+      }
+      int sumTmp = 0;
+      for (int i = 0; i < OBIC_FUSION_NUM; i++)
+      {
+        sumTmp += iRatio[i];
+        cu.obicMode[i] = bestModes[i];
+        cu.obicFusionWeight[i] = iRatio[i];
+      }
+      if (sumTmp != sumWeight)
+      {
+        int d = sumTmp - sumWeight;
+        if (d > 0)
+        {
+          for (int i = OBIC_FUSION_NUM - 1; i >= 0; i--)
+          {
+            int diff = d;
+            for (int k = 0; k < diff; k++)
+            {
+              if (cu.obicFusionWeight[i] > 0)
+              {
+                cu.obicFusionWeight[i] -= 1;
+                d -= 1;
+              }
+            }
+          }
+        }
+        else
+        {
+          for (int i = 0; i < OBIC_FUSION_NUM; i++)
+          {
+            int diff = d;
+            if (cu.obicFusionWeight[i] > 0 && (cu.obicFusionWeight[i] + d) < sumWeight)
+            {
+              for (int k = 0; k < abs(diff); k++)
+              {
+                if (d == 0)
+                {
+                  break;
+                }
+                if (cu.obicFusionWeight[i] > 0)
+                {
+                  cu.obicFusionWeight[i] += 1;
+                  d += 1;
+                }
+              }
+            }
+          }
+        }
+      }
+      sumTmp = 0;
+      for (int i = 0; i < OBIC_FUSION_NUM; i++)
+      {
+        sumTmp += cu.obicFusionWeight[i];
+      }
+      CHECK( sumTmp != sumWeight, "Wrong sum!" );
+      if (count == OBIC_FUSION_NUM)
+      {
+        cu.obicMode[count - 1] = PLANAR_IDX;
+        cu.obicFusionWeight[count - 1] = planarWeight;
+      }
+      else
+      {
+        cu.obicMode[count] = PLANAR_IDX;
+        cu.obicFusionWeight[count] = planarWeight;
+      }
+      cu.obicIsBlended = true;
+    }
+  }
+#endif
+  
 #if JVET_AB0155_SGPM
 int IntraPrediction::deriveTimdMode(const CPelBuf &recoBuf, const CompArea &area, CodingUnit &cu, bool bFull, bool bHorVer)
 {
diff --git a/source/Lib/CommonLib/IntraPrediction.h b/source/Lib/CommonLib/IntraPrediction.h
index bca6799e8..eb8b6da01 100644
--- a/source/Lib/CommonLib/IntraPrediction.h
+++ b/source/Lib/CommonLib/IntraPrediction.h
@@ -781,6 +781,11 @@ public:
 #if JVET_AG0146_DIMD_ITMP_IBC
   int getBestNonAnglularMode(const CPelBuf& recoBuf, const CompArea& area, CodingUnit& cu, std::vector<Mv> BVs);
 #endif
+#if JVET_AH0076_OBIC
+  void deriveObicMode             ( const CPelBuf &recoBuf, const CompArea &area, CodingUnit &cu );
+  void generateObicBlending(PelBuf &piPred, const PredictionUnit &pu, PelBuf predFusion[OBIC_FUSION_NUM - 1], bool blendModes[OBIC_FUSION_NUM - 1], int planarIdx);
+  void generateDimdBlending(PelBuf &piPred, const PredictionUnit &pu, PelBuf &piBlock0, PelBuf &piBlock1, PelBuf &piBlock2, PelBuf &piBlock3, PelBuf &plnBlock);
+#endif
 #if JVET_AB0155_SGPM
   int deriveTimdMode              ( const CPelBuf &recoBuf, const CompArea &area, CodingUnit &cu, bool bFull = true, bool bHorVer = false );
 #else
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index e95c92f46..26dd739a6 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -151,6 +151,7 @@
 
 #if ENABLE_DIMD
 #define JVET_AC0098_LOC_DEP_DIMD                          1 // JVET-AC0098: Location-dependent Decoder-side Intra Mode Derivation
+#define JVET_AH0076_OBIC                                  1 // JVET_AH0076: OBIC
 #endif
 
 #endif
diff --git a/source/Lib/CommonLib/Unit.cpp b/source/Lib/CommonLib/Unit.cpp
index 0c38590f1..fc5d743a2 100644
--- a/source/Lib/CommonLib/Unit.cpp
+++ b/source/Lib/CommonLib/Unit.cpp
@@ -287,6 +287,15 @@ CodingUnit& CodingUnit::operator=( const CodingUnit& other )
   plIdx = other.plIdx;
 #endif
 #if ENABLE_DIMD
+#if JVET_AH0076_OBIC
+  obicFlag = other.obicFlag;
+  obicIsBlended = other.obicIsBlended;
+  for (int i = 0; i < OBIC_FUSION_NUM; i++)
+  {
+    obicMode[i] = other.obicMode[i];
+    obicFusionWeight[i] = other.obicFusionWeight[i];
+  }
+#endif
 #if JVET_AG0146_DIMD_ITMP_IBC
   isBvDimd = other.isBvDimd;
   bvDimd = other.bvDimd;
@@ -529,6 +538,15 @@ void CodingUnit::initData()
   plIdx = 0;
 #endif
 #if ENABLE_DIMD
+#if JVET_AH0076_OBIC
+  obicFlag = false;
+  obicIsBlended = false;
+  for (int i = 0; i < OBIC_FUSION_NUM; i++)
+  {
+    obicMode[i] = -1;
+    obicFusionWeight[i] = 0;
+  }
+#endif
 #if JVET_AG0146_DIMD_ITMP_IBC
   isBvDimd  = 0;
   bvDimd    = Mv(0, 0);
diff --git a/source/Lib/CommonLib/Unit.h b/source/Lib/CommonLib/Unit.h
index 806a4e4c0..c761201a4 100644
--- a/source/Lib/CommonLib/Unit.h
+++ b/source/Lib/CommonLib/Unit.h
@@ -325,6 +325,12 @@ struct CodingUnit : public UnitArea
   uint8_t        plIdx;
 #endif
 #if ENABLE_DIMD
+#if JVET_AH0076_OBIC
+  bool obicFlag;
+  bool obicIsBlended;
+  int obicMode[OBIC_FUSION_NUM];
+  int obicFusionWeight[OBIC_FUSION_NUM];
+#endif
 #if JVET_AG0146_DIMD_ITMP_IBC
   bool           isBvDimd;
   Mv             bvDimd;
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index e52371936..7f04e00fe 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -4574,6 +4574,211 @@ bool PU::isDMChromaSgpm(const PredictionUnit &pu)
 }
 #endif
 
+#if JVET_AH0076_OBIC
+bool PU::isObicAvail(const PredictionUnit &pu)
+{
+  if (!pu.lumaPos().x && !pu.lumaPos().y)
+  {
+    return false;
+  }
+  if (!pu.Y().valid() || pu.cu->predMode != MODE_INTRA || !isLuma(pu.cu->chType) || !pu.cu->slice->getSPS()->getUseDimd() || pu.Y().area() <= 32)
+  {
+    return false;
+  }
+  if (!PU::checkAvailBlocks(pu))
+  {
+    return false;
+  }
+  return true;
+}
+bool PU::checkAvailBlocks(const PredictionUnit &pu)
+{
+  const int step = 4;
+  const CodingUnit& cu = *pu.cu;
+  const CompArea &area = cu.Y();
+  const int numCUs = NUM_OBIC_CUS;
+  const CodingUnit* cuNeighbours[numCUs];
+  cuNeighbours[0] = cu.cs->getCURestricted(cu.lumaPos().offset(-1, 0), cu, CH_L);
+  cuNeighbours[1] = cu.cs->getCURestricted(cu.lumaPos().offset(0, -1), cu, CH_L);
+  cuNeighbours[2] = cu.cs->getCURestricted(cu.lumaPos().offset(-1, -1), cu, CH_L);
+  
+  const CodingUnit* cuTemp;
+  for (int i = 0; i <= cu.lheight(); i += step)
+  {
+    cuTemp = cu.cs->getCURestricted(cu.lumaPos().offset(-1, i), cu, CH_L);
+    if (cuTemp && CU::isIntra(*cuTemp))
+    {
+      cuNeighbours[0] = cuTemp;
+      break;
+    }
+  }
+  for (int i = 0; i <= cu.lwidth(); i += step)
+  {
+    cuTemp = cu.cs->getCURestricted(cu.lumaPos().offset(i, -1), cu, CH_L);
+    if (cuTemp && CU::isIntra(*cuTemp))
+    {
+      cuNeighbours[1] = cuTemp;
+      break;
+    }
+  }
+  
+  const CodingUnit* cuLeft = cu.cs->getCURestricted(cu.lumaPos().offset(-1, 0), cu, CH_L);
+  const CodingUnit* cuTop  = cu.cs->getCURestricted(cu.lumaPos().offset(0, -1), cu, CH_L);
+  cuNeighbours[3] = cuLeft ? cu.cs->getCURestricted(cuLeft->lumaPos().offset(cuLeft->lwidth()-1, cuLeft->lheight()), cu, CH_L) : NULL;
+  cuNeighbours[4] = cuTop ? cu.cs->getCURestricted(cuTop->lumaPos().offset(cuTop->lwidth(), cuTop->lheight() - 1), cu, CH_L) : NULL;
+  cuNeighbours[5] = cuNeighbours[3] ? cu.cs->getCURestricted(cuNeighbours[3]->lumaPos().offset(cuNeighbours[3]->lwidth()-1, cuNeighbours[3]->lheight()), cu, CH_L) : NULL;
+  cuNeighbours[6] = cuNeighbours[4] ? cu.cs->getCURestricted(cuNeighbours[4]->lumaPos().offset(cuNeighbours[4]->lwidth(), cuNeighbours[4]->lheight() - 1), cu, CH_L) : NULL;
+  cuNeighbours[7] = cuLeft ? cu.cs->getCURestricted(cuLeft->lumaPos().offset(-1, 0), cu, CH_L) : NULL;
+  cuNeighbours[8] = cuTop ? cu.cs->getCURestricted(cuTop->lumaPos().offset(0, -1), cu, CH_L) : NULL;
+  cuNeighbours[9] = cuNeighbours[3] ? cu.cs->getCURestricted(cuNeighbours[3]->lumaPos().offset(-1, 0), cu, CH_L) : NULL;
+  cuNeighbours[10] = cuNeighbours[4] ? cu.cs->getCURestricted(cuNeighbours[4]->lumaPos().offset(0, -1), cu, CH_L) : NULL;
+  cuNeighbours[11] = cuNeighbours[5] ? cu.cs->getCURestricted(cuNeighbours[5]->lumaPos().offset(-1, 0), cu, CH_L) : NULL;
+  cuNeighbours[12] = cuNeighbours[6] ? cu.cs->getCURestricted(cuNeighbours[6]->lumaPos().offset(0, -1), cu, CH_L) : NULL;
+  
+  if ((!cuNeighbours[9]) && cuNeighbours[2])
+  {
+    cuNeighbours[9] = cu.cs->getCURestricted(cuNeighbours[2]->lumaPos().offset(-1, cuNeighbours[2]->lheight()-1), cu, CH_L);
+  }
+  if ((!cuNeighbours[10]) && cuNeighbours[2])
+  {
+    cuNeighbours[10] = cu.cs->getCURestricted(cuNeighbours[2]->lumaPos().offset(cuNeighbours[2]->lwidth()-1, -1), cu, CH_L);
+  }
+  if ((!cuNeighbours[11]) && cuLeft && cuNeighbours[7])
+  {
+    cuNeighbours[11] = cu.cs->getCURestricted(cuNeighbours[7]->lumaPos().offset(-1, 0), cu, CH_L);
+  }
+  if ((!cuNeighbours[12]) && cuTop && cuNeighbours[8])
+  {
+    cuNeighbours[12] = cu.cs->getCURestricted(cuNeighbours[8]->lumaPos().offset(0, -1), cu, CH_L);
+  }
+  
+  const Position  topLeft = area.topLeft();
+  // non-adjacent spatial candidates
+  int       offsetX           = 0;
+  int       offsetY           = 0;
+  int       cout              = 13;
+  const int numNACandidate[4] = { 3, 5, 5, 5 };
+  const int idxMap[4][5]      = { { 0, 1, 4 }, { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 4 } };
+  for (int iDistanceIndex = 0; iDistanceIndex < NADISTANCE_LEVEL; iDistanceIndex++)
+  {
+    const int iNADistanceHor = cu.Y().width * (iDistanceIndex + 1);
+    const int iNADistanceVer = cu.Y().height * (iDistanceIndex + 1);
+    for (int iNASPIdx = 0; iNASPIdx < numNACandidate[iDistanceIndex]; iNASPIdx++)
+    {
+      switch (idxMap[iDistanceIndex][iNASPIdx])
+      {
+        case 0: offsetX = -iNADistanceHor - 1; offsetY = cu.Y().height + iNADistanceVer - 1; break;
+        case 1: offsetX = cu.Y().width + iNADistanceHor - 1; offsetY = -iNADistanceVer - 1; break;
+        case 2: offsetX = cu.Y().width >> 1; offsetY = -iNADistanceVer - 1; break;
+        case 3: offsetX = -iNADistanceHor - 1; offsetY = cu.Y().height >> 1; break;
+        case 4: offsetX = -iNADistanceHor - 1; offsetY = -iNADistanceVer - 1; break;
+        default: printf("error!"); exit(0); break;
+      }
+      cuNeighbours[cout++] = cu.cs->getCURestricted(topLeft.offset(offsetX, offsetY), cu, CH_L);
+    }
+  }
+  int numBlocks = 0;
+  int limitMaxNeigh = NUM_OBIC_CUS;
+  bool useNeighbour[numCUs];
+  int  neighboursInDistOrder[numCUs];
+  int  dists[numCUs];
+  for (int i = 0; i < numCUs; i++)
+  {
+    useNeighbour[i] = false;
+    neighboursInDistOrder[i] = 0;
+    dists[i] = 0;
+  }
+  for (int i = 0; i < numCUs; i++)
+  {
+    dists[i] = MAX_INT;
+  }
+  int numToMix = 0;
+  for (int i = 0; i < numCUs; i++)
+  {
+    useNeighbour[i] = (cuNeighbours[i] && CU::isIntra(*cuNeighbours[i]));
+    if (useNeighbour[i])
+    {
+      for (int j = i-1; j >= 0; j--)
+      {
+        if (!useNeighbour[j])
+        {
+          continue;
+        }
+        useNeighbour[i] &= (cuNeighbours[i]->lx() != cuNeighbours[j]->lx() || cuNeighbours[i]->ly() != cuNeighbours[j]->ly());
+      }
+    }
+    int curNeigh = i;
+    int curDist = (useNeighbour[i] ? abs( (int)(cu.lx()) - (int)(cuNeighbours[i]->lx())) + abs( (int)(cu.ly()) - (int)(cuNeighbours[i]->ly())) : 0);
+    
+    for (int j = 0; j < numCUs; j++)
+    {
+      if (curDist < dists[j])
+      {
+        for (int k = numCUs - 1; k > j; k--)
+        {
+          dists[k] = dists[k - 1];
+          neighboursInDistOrder[k] = neighboursInDistOrder[k - 1];
+        }
+        dists[j] = curDist;
+        neighboursInDistOrder[j] = curNeigh;
+        break;
+      }
+    }
+  }
+  for (int i = 0; i < numCUs; i++)
+  {
+    int j = neighboursInDistOrder[i];
+    if (limitMaxNeigh > 0 && numToMix >= limitMaxNeigh)
+    {
+      useNeighbour[j] = false;
+      continue;
+    }
+    if (useNeighbour[j])
+    {
+      numToMix ++;
+    }
+  }
+  for (int i = 0; i < numCUs; i++)
+  {
+    if (!useNeighbour[i])
+    {
+      continue;
+    }
+    if (cuNeighbours[i]->timd || cuNeighbours[i]->dimd || cuNeighbours[i]->sgpm || cuNeighbours[i]->tmrlFlag)
+    {
+      numBlocks++;
+    }
+#if JVET_AG0058_EIP
+    else if (cuNeighbours[i]->eipFlag && cu.slice->getSliceType() != I_SLICE)
+    {
+      numBlocks++;
+    }
+#endif
+#if JVET_AC0115_INTRA_TMP_DIMD_MTS_LFNST
+    else if (cuNeighbours[i]->tmpFlag && cu.slice->getSliceType() != I_SLICE)
+    {
+      numBlocks++;
+    }
+#endif
+#if JVET_AB0067_MIP_DIMD_LFNST
+    else if (cuNeighbours[i]->mipFlag && cu.slice->getSliceType() != I_SLICE)
+    {
+      numBlocks++;
+    }
+#endif
+    else if (CU::isIntra(*cuNeighbours[i]) && !CU::isIBC(*cuNeighbours[i]) && !cuNeighbours[i]->mipFlag && !cuNeighbours[i]->tmpFlag && !cuNeighbours[i]->eipFlag && !CU::isPLT(*cuNeighbours[i]))
+    {
+      numBlocks++;
+    }
+  }
+  if (numBlocks < 1)
+  {
+    return false;
+  }
+  return true;
+}
+#endif
+  
 #if JVET_AB0155_SGPM
 uint32_t PU::getIntraDirLuma(const PredictionUnit &pu, const int partIdx)
 {
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index 4e5066898..cd7a642de 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -938,6 +938,10 @@ namespace PU
   bool isOppositeLIC(const PredictionUnit &pu);
   bool hasOppositeLICFlag(const PredictionUnit &pu);
 #endif
+#if JVET_AH0076_OBIC
+  bool isObicAvail(const PredictionUnit &pu);
+  bool checkAvailBlocks(const PredictionUnit &pu);
+#endif
 }
 
 // TU tools
@@ -1368,4 +1372,4 @@ int getAllowedCurEip(const CodingUnit& cu, const ComponentID compId, static_vect
 #endif
 #if JVET_AG0061_INTER_LFNST_NSPT
 int buildHistogram(const Pel *pReco, int iStride, uint32_t uiHeight, uint32_t uiWidth, int *piHistogram, int direction, int bw, int bh);
-#endif
\ No newline at end of file
+#endif
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index d1d3735d3..fcdfcf6f0 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -1968,7 +1968,12 @@ void CABACReader::intra_luma_pred_modes( CodingUnit &cu )
   {
     return;
   }
-
+#if JVET_AH0076_OBIC
+  if (cu.obicFlag)
+  {
+    return;
+  }
+#endif
   if( cu.bdpcmMode )
   {
     cu.firstPU->intraDir[0] = cu.bdpcmMode == 2? VER_IDX : HOR_IDX;
@@ -2235,10 +2240,29 @@ void CABACReader::cu_dimd_flag(CodingUnit& cu)
 
   unsigned ctxId = DeriveCtx::CtxDIMDFlag(cu);
   cu.dimd = m_BinDecoder.decodeBin(Ctx::DimdFlag(ctxId));
+#if JVET_AH0076_OBIC
+  cu_obic_flag(cu);
+#endif
   DTRACE(g_trace_ctx, D_SYNTAX, "cu_dimd_flag() ctx=%d pos=(%d,%d) dimd=%d\n", ctxId, cu.lumaPos().x, cu.lumaPos().y, cu.dimd);
 }
 #endif
 
+#if JVET_AH0076_OBIC
+void CABACReader::cu_obic_flag(CodingUnit& cu )
+{
+  cu.obicFlag = false;
+  if (!cu.dimd || !cu.Y().valid() || cu.predMode != MODE_INTRA || !isLuma(cu.chType))
+  {
+    return;
+  }
+  if (!PU::isObicAvail(*cu.firstPU))
+  {
+    return;
+  }
+  cu.obicFlag = m_BinDecoder.decodeBin( Ctx::obicFlag(0) );
+}
+#endif
+
 #if JVET_AG0058_EIP
 void CABACReader::cu_eip_flag(CodingUnit& cu)
 {
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index 3c9513c38..76bb3c56a 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -144,6 +144,9 @@ public:
 #if ENABLE_DIMD
   void        cu_dimd_flag              (CodingUnit&                   cu);
  #endif
+#if JVET_AH0076_OBIC
+  void        cu_obic_flag              ( CodingUnit&                   cu );
+#endif
   void        cu_residual               ( CodingUnit&                   cu,     Partitioner&    pm,       CUCtx& cuCtx );
   void        rqt_root_cbf              ( CodingUnit&                   cu );
 
diff --git a/source/Lib/DecoderLib/DecCu.cpp b/source/Lib/DecoderLib/DecCu.cpp
index e6adc62f4..172098601 100644
--- a/source/Lib/DecoderLib/DecCu.cpp
+++ b/source/Lib/DecoderLib/DecCu.cpp
@@ -333,6 +333,13 @@ void DecCu::decompressCtu( CodingStructure& cs, const UnitArea& ctuArea )
           pu->intraDir[0] = currCU.dimdMode;
 #if JVET_AG0146_DIMD_ITMP_IBC
           m_pcIntraPred->getBestNonAnglularMode(currCU.cs->picture->getRecoBuf(area), area, currCU, m_pcIntraPred->m_bvBasedMergeCandidates);
+#endif
+#if JVET_AH0076_OBIC
+          if (currCU.obicFlag)
+          {
+            m_pcIntraPred->deriveObicMode(currCU.cs->picture->getRecoBuf(area), area, currCU);
+            pu->intraDir[0] = currCU.obicMode[0];
+          }
 #endif
         }
 #if JVET_W0123_TIMD_FUSION
@@ -1056,7 +1063,11 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
     {
       m_pcIntraPred->initIntraMip( pu, area );
 #if JVET_AB0067_MIP_DIMD_LFNST
+#if JVET_AH0076_OBIC
+      m_pcIntraPred->predIntraMip( compID, piPred, pu, true);
+#else
       m_pcIntraPred->predIntraMip( compID, piPred, pu, pu.cu->lfnstIdx > 0 ? true : false);
+#endif
 #else
       m_pcIntraPred->predIntraMip( compID, piPred, pu );
 #endif
@@ -1390,7 +1401,11 @@ void DecCu::xIntraRecACTBlk(TransformUnit& tu)
     {
       m_pcIntraPred->initIntraMip(pu, area);
 #if JVET_AB0067_MIP_DIMD_LFNST
+#if JVET_AH0076_OBIC
+      m_pcIntraPred->predIntraMip(compID, piPred, pu, true);
+#else
       m_pcIntraPred->predIntraMip(compID, piPred, pu, pu.cu->lfnstIdx > 0 ? true : false);
+#endif
 #else
       m_pcIntraPred->predIntraMip(compID, piPred, pu);
 #endif
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 0dd19a85a..69bb99c0e 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -1508,7 +1508,12 @@ void CABACWriter::intra_luma_pred_modes( const CodingUnit& cu )
   {
     return;
   }
-
+#if JVET_AH0076_OBIC
+  if (cu.obicFlag)
+  {
+    return;
+  }
+#endif
   if( cu.bdpcmMode )
   {
     cu.firstPU->intraDir[0] = cu.bdpcmMode == 2? VER_IDX : HOR_IDX;
@@ -1731,7 +1736,12 @@ void CABACWriter::intra_luma_pred_modes( const CodingUnit& cu )
 
 void CABACWriter::intra_luma_pred_mode( const PredictionUnit& pu )
 {
-
+#if JVET_AH0076_OBIC
+  if (pu.cu->obicFlag)
+  {
+    return;
+  }
+#endif
   if( pu.cu->bdpcmMode ) return;
 #if JVET_V0130_INTRA_TMP
   // check if sufficient search range is available
@@ -2035,10 +2045,28 @@ void CABACWriter::cu_dimd_flag(const CodingUnit& cu)
   }
   unsigned ctxId = DeriveCtx::CtxDIMDFlag(cu);
   m_BinEncoder.encodeBin(cu.dimd, Ctx::DimdFlag(ctxId));
+#if JVET_AH0076_OBIC
+  cu_obic_flag(cu);
+#endif
   DTRACE(g_trace_ctx, D_SYNTAX, "cu_dimd_flag() ctx=%d pos=(%d,%d) dimd=%d\n", ctxId, cu.lumaPos().x, cu.lumaPos().y, cu.dimd);
 }
 #endif
 
+#if JVET_AH0076_OBIC
+void CABACWriter::cu_obic_flag(const CodingUnit& cu )
+{
+  if (!cu.dimd || !cu.Y().valid() || cu.predMode != MODE_INTRA || !isLuma(cu.chType))
+  {
+    return;
+  }
+  if (!PU::isObicAvail(*cu.firstPU))
+  {
+    return;
+  }
+  m_BinEncoder.encodeBin(cu.obicFlag ? 1 : 0, Ctx::obicFlag(0));
+}
+#endif
+
 void CABACWriter::intra_chroma_pred_modes( const CodingUnit& cu )
 {
 #if INTRA_RM_SMALL_BLOCK_SIZE_CONSTRAINTS
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index d6c18e9c1..45e3b5933 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -130,6 +130,9 @@ public:
 #if ENABLE_DIMD
   void        cu_dimd_flag              ( const CodingUnit&             cu );
 #endif
+#if JVET_AH0076_OBIC
+  void        cu_obic_flag              ( const CodingUnit&             cu );
+#endif
 #if JVET_W0123_TIMD_FUSION
   void        cu_timd_flag              ( const CodingUnit&             cu );
 #endif
diff --git a/source/Lib/EncoderLib/EncCu.cpp b/source/Lib/EncoderLib/EncCu.cpp
index fdcd97c1b..c8d243a75 100644
--- a/source/Lib/EncoderLib/EncCu.cpp
+++ b/source/Lib/EncoderLib/EncCu.cpp
@@ -2594,7 +2594,11 @@ bool EncCu::xCheckRDCostIntra(CodingStructure *&tempCS, CodingStructure *&bestCS
   int8_t dimdChromaMode = -1;
   int8_t dimdChromaModeSecond = -1;
 #endif
-
+#if JVET_AH0076_OBIC
+  bool obicIsBlended = false;
+  int obicMode[OBIC_FUSION_NUM] = { -1 };
+  int obicFusionWeight[OBIC_FUSION_NUM] = { 0 };
+#endif
   if (isLuma(partitioner.chType))
   {
     CodingUnit cu(tempCS->area);
@@ -2703,7 +2707,9 @@ bool EncCu::xCheckRDCostIntra(CodingStructure *&tempCS, CodingStructure *&bestCS
     m_pcIntraSearch->getMpmListSize() = PU::getIntraMPMs( pu, m_pcIntraSearch->getMPMList(), m_pcIntraSearch->getNonMPMList() );
   }
 #endif
-
+#if JVET_AH0076_OBIC
+  bool obicModeDerived = false;
+#endif
 #if JVET_W0123_TIMD_FUSION
   bool timdDerived = false;
 #endif
@@ -2717,6 +2723,10 @@ bool EncCu::xCheckRDCostIntra(CodingStructure *&tempCS, CodingStructure *&bestCS
   m_pcIntraSearch->m_skipTimdLfnstMtsPass = false;
   m_modeCtrl->resetLfnstCost();
 #endif
+#if JVET_AH0076_OBIC
+  m_pcIntraSearch->m_skipObicLfnstMtsPass = false;
+  m_pcIntraSearch->m_skipDimdLfnstMtsPass = false;
+#endif
 #if JVET_AC0147_CCCM_NO_SUBSAMPLING
   m_pcIntraSearch->m_skipCCCMSATD = false;
 #endif
@@ -2947,7 +2957,33 @@ bool EncCu::xCheckRDCostIntra(CodingStructure *&tempCS, CodingStructure *&bestCS
             }
           }
 #endif
-
+#if JVET_AH0076_OBIC
+          cu.obicFlag = false;
+          if (isLuma(partitioner.chType))
+          {
+            if (!obicModeDerived)
+            {
+              const CompArea &area = cu.Y();
+              m_pcIntraSearch->deriveObicMode(bestCS->picture->getRecoBuf(area), area, cu);
+              for (int i = 0; i < OBIC_FUSION_NUM; i++)
+              {
+                obicMode[i] = cu.obicMode[i];
+                obicFusionWeight[i] = cu.obicFusionWeight[i];
+              }
+              obicIsBlended = cu.obicIsBlended;
+              obicModeDerived = true;
+            }
+            else
+            {
+              for (int i = 0; i < OBIC_FUSION_NUM; i++)
+              {
+                cu.obicMode[i] = obicMode[i];
+                cu.obicFusionWeight[i] = obicFusionWeight[i];
+              }
+              cu.obicIsBlended = obicIsBlended;
+            }
+          }
+#endif
 #if TMP_FAST_ENC
           if (isLuma(partitioner.chType) && cu.slice->getSPS()->getUseIntraTMP())
           {
diff --git a/source/Lib/EncoderLib/IntraSearch.cpp b/source/Lib/EncoderLib/IntraSearch.cpp
index e44f95cb7..b4f9886a0 100644
--- a/source/Lib/EncoderLib/IntraSearch.cpp
+++ b/source/Lib/EncoderLib/IntraSearch.cpp
@@ -97,6 +97,10 @@ IntraSearch::IntraSearch()
   {
     m_eipMergePredBuf[i] = nullptr;
   }
+#endif
+#if JVET_AH0076_OBIC
+  m_dimdPredBuf = nullptr;
+  m_obicPredBuf = nullptr;
 #endif
   m_truncBinBits = nullptr;
   m_escapeNumBins = nullptr;
@@ -276,6 +280,12 @@ void IntraSearch::destroy()
     delete[] m_eipMergePredBuf[i];
     m_eipMergePredBuf[i] = nullptr;
   }
+#endif
+#if JVET_AH0076_OBIC
+  delete[] m_dimdPredBuf;
+  m_dimdPredBuf = nullptr;
+  delete[] m_obicPredBuf;
+  m_obicPredBuf = nullptr;
 #endif
   m_isInitialized = false;
   if (m_truncBinBits != nullptr)
@@ -403,14 +413,18 @@ void IntraSearch::init( EncCfg*        pcEncCfg,
     m_ddCcpFusionStorage[i].create(UnitArea(cform, Area(0, 0, maxCUWidth, maxCUHeight)));
   }
 #endif
-#if JVET_AB0155_SGPM
+#if JVET_AB0155_SGPM || JVET_AH0076_OBIC
 #if JVET_AG0152_SGPM_ITMP_IBC
   for (int i = 0; i < NUM_LUMA_MODE + SGPM_NUM_BVS; i++)
 #else
   for (int i = 0; i < NUM_LUMA_MODE; i++)
 #endif
   {
+#if JVET_AH0076_OBIC
+    m_intraPredBuf[i] = new Pel[(MAX_CU_SIZE>>1) * (MAX_CU_SIZE>>1)];
+#else
     m_intraPredBuf[i] = new Pel[GEO_MAX_CU_SIZE_EX * GEO_MAX_CU_SIZE_EX];
+#endif
   }
   for (int i = 0; i < SGPM_NUM; i++)
   {
@@ -426,6 +440,10 @@ void IntraSearch::init( EncCfg*        pcEncCfg,
   {
     m_eipMergePredBuf[i] = new Pel[MAX_EIP_SIZE * MAX_EIP_SIZE];
   }
+#endif
+#if JVET_AH0076_OBIC
+  m_dimdPredBuf = new Pel[(MAX_CU_SIZE>>1) * (MAX_CU_SIZE>>1)];
+  m_obicPredBuf = new Pel[(MAX_CU_SIZE>>1) * (MAX_CU_SIZE>>1)];
 #endif
   for( uint32_t ch = 0; ch < MAX_NUM_TBLOCKS; ch++ )
   {
@@ -556,6 +574,10 @@ void IntraSearch::init( EncCfg*        pcEncCfg,
 #if INTRA_TRANS_ENC_OPT
   m_skipTimdLfnstMtsPass = false;
 #endif
+#if JVET_AH0076_OBIC
+  m_skipObicLfnstMtsPass = false;
+  m_skipDimdLfnstMtsPass = false;
+#endif
 }
 
 
@@ -729,7 +751,12 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
 #if JVET_AB0155_SGPM
   bool SGPMSaveFlag = (cu.lfnstIdx == 0 && cu.mtsFlag == 0);
 #endif
-
+#if JVET_AH0076_OBIC
+  bool testDimd = isLuma(partitioner.chType) && cu.slice->getSPS()->getUseDimd();
+  bool testObic = testDimd && (PU::isObicAvail(*cu.firstPU) && cu.obicMode[0] >= 0);
+  bool obicSaveFlag = testObic && (cu.lfnstIdx == 0 && cu.mtsFlag == 0);
+  bool dimdSaveFlag = testDimd && (cu.lfnstIdx == 0 && cu.mtsFlag == 0);
+#endif
   const uint32_t lfnstIdx = cu.lfnstIdx;
 #if !INTRA_RM_SMALL_BLOCK_SIZE_CONSTRAINTS
   double costInterCU = findInterCUCost( cu );
@@ -830,6 +857,10 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
   double regAngCost = MAX_DOUBLE;
   bool setSkipTimdControl = (m_pcEncCfg->getIntraPeriod() == 1) && !cu.lfnstIdx && !cu.mtsFlag;
   double timdAngCost = MAX_DOUBLE;
+#endif
+#if JVET_AH0076_OBIC
+  double obicAngCost = MAX_DOUBLE, dimdAngCost = MAX_DOUBLE;
+  bool setSkipDimdControl = (m_pcEncCfg->getIntraPeriod() == 1) && !cu.lfnstIdx && !cu.mtsFlag;
 #endif
   const bool testBDPCM = sps.getBDPCMEnabledFlag() && CU::bdpcmAllowed(cu, ComponentID(partitioner.chType)) && cu.mtsFlag == 0 && cu.lfnstIdx == 0;
   static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> uiHadModeList;
@@ -873,6 +904,10 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
 #if ENABLE_DIMD
     bool bestDimdMode = false;
 #endif
+#if JVET_AH0076_OBIC
+    bool bestObicMode = false;
+    int bestMipDimd = 0;
+#endif
 #if JVET_W0123_TIMD_FUSION
     bool bestTimdMode = false;
 #endif
@@ -908,7 +943,11 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
       if (mtsUsageFlag != 2)
       {
 #if JVET_AB0155_SGPM
+#if JVET_AH0076_OBIC
+        if ((testSgpm && SGPMSaveFlag) || obicSaveFlag || dimdSaveFlag)
+#else
         if (testSgpm && SGPMSaveFlag)
+#endif
         {
 #if JVET_AG0152_SGPM_ITMP_IBC
           for (int i = 0; i < NUM_LUMA_MODE + SGPM_NUM_BVS; i++)
@@ -1059,6 +1098,38 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
           }
 #endif
 
+#if JVET_AH0076_OBIC
+          int dimdNeededMode[NUM_LUMA_MODE] = {0};
+          if (obicSaveFlag)
+          {
+            for (int idx = 0; idx < OBIC_FUSION_NUM; idx++)
+            {
+              int iMode = cu.obicMode[idx];
+              if (iMode < 0)
+              {
+                continue;
+              }
+              dimdNeededMode[iMode] = 1;
+            }
+          }
+          if (dimdSaveFlag)
+          {
+            if (cu.dimdBlending)
+            {
+              for (int dimdIdx = 0; dimdIdx < DIMD_FUSION_NUM - 1; dimdIdx++)
+              {
+                int dimdMode = (dimdIdx == 0 ? cu.dimdMode : cu.dimdBlendMode[dimdIdx-1]);
+                if (dimdMode <= 0)
+                {
+                  break;
+                }
+                dimdNeededMode[dimdMode] = 1;
+              }
+              dimdNeededMode[PLANAR_IDX] = 1;
+            }
+          }
+#endif
+          
 #if JVET_AB0157_TMRL
           double tmrlCostList[MRL_LIST_SIZE]{ MAX_DOUBLE };
 #endif
@@ -1111,6 +1182,14 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
                 m_intraModeReady[uiMode] = 1;
               }
 #endif
+#if JVET_AH0076_OBIC
+              if ((obicSaveFlag || dimdSaveFlag) && dimdNeededMode[uiMode] && !m_intraModeReady[uiMode])
+              {
+                PelBuf   predBuf(m_intraPredBuf[uiMode], tmpArea);
+                predBuf.copyFrom(piPred);
+                m_intraModeReady[uiMode] = 1;
+              }
+#endif
 
               // Use the min between SAD and HAD as the cost criterion
               // SAD is scaled by 2 to align with the scaling of HAD
@@ -1237,6 +1316,14 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
                       m_intraModeReady[mode] = 1;
                     }
 #endif
+#if JVET_AH0076_OBIC
+                    if ((obicSaveFlag || dimdSaveFlag) && dimdNeededMode[mode] && !m_intraModeReady[mode])
+                    {
+                      PelBuf   predBuf(m_intraPredBuf[mode], tmpArea);
+                      predBuf.copyFrom(piPred);
+                      m_intraModeReady[mode] = 1;
+                    }
+#endif
 
                     // Use the min between SAD and SATD as the cost criterion
                     // SAD is scaled by 2 to align with the scaling of HAD
@@ -1825,6 +1912,86 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
 #endif
               );
             }
+#if JVET_AH0076_OBIC
+            if (obicSaveFlag || dimdSaveFlag)
+            {
+              cu.dimd = false;
+              cu.timd = false;
+              cu.mipFlag = false;
+              pu.multiRefIdx = 0;
+#if JVET_V0130_INTRA_TMP
+              cu.tmpFlag = false;
+#endif
+              cu.sgpm = false;
+#if JVET_AB0157_INTRA_FUSION
+              initIntraPatternChType(cu, pu.Y(), true, 0, false);
+#else
+              initIntraPatternChType(cu, pu.Y(), true);
+#endif
+              if (obicSaveFlag)
+              {
+                for (int idx = 0; idx < OBIC_FUSION_NUM; idx++)
+                {
+                  int iMode = cu.obicMode[idx];
+                  if (iMode < 0)
+                  {
+                    continue;
+                  }
+                  if (dimdNeededMode[iMode] && !m_intraModeReady[iMode])
+                  {
+                    pu.intraDir[0] = iMode;
+                    initPredIntraParams(pu, pu.Y(), sps);
+#if JVET_AB0157_INTRA_FUSION
+                    predIntraAng(COMPONENT_Y, piPred, pu, false);
+#else
+                    predIntraAng(COMPONENT_Y, piPred, pu);
+#endif
+                    PelBuf predBuf(m_intraPredBuf[iMode], tmpArea);
+                    predBuf.copyFrom(piPred);
+                    m_intraModeReady[iMode] = 1;
+                  }
+                }
+              }
+              if (dimdSaveFlag)
+              {
+                if (dimdNeededMode[PLANAR_IDX] && !m_intraModeReady[PLANAR_IDX])
+                {
+                  pu.intraDir[0] = PLANAR_IDX;
+                  initPredIntraParams(pu, pu.Y(), sps);
+#if JVET_AB0157_INTRA_FUSION
+                  predIntraAng(COMPONENT_Y, piPred, pu, false);
+#else
+                  predIntraAng(COMPONENT_Y, piPred, pu);
+#endif
+                  PelBuf predBuf(m_intraPredBuf[PLANAR_IDX], tmpArea);
+                  predBuf.copyFrom(piPred);
+                  m_intraModeReady[PLANAR_IDX] = 1;
+                }
+              }
+              for (int dimdIdx = 0; dimdIdx < DIMD_FUSION_NUM - 1; dimdIdx++)
+              {
+                int      dimdMode = (dimdIdx == 0 ? cu.dimdMode : cu.dimdBlendMode[dimdIdx-1]);
+                if (dimdMode <= 0)
+                {
+                  break;
+                }
+
+                if (dimdNeededMode[dimdMode] && !m_intraModeReady[dimdMode])
+                {
+                  pu.intraDir[0] = dimdMode;
+                  initPredIntraParams(pu, pu.Y(), sps);
+#if JVET_AB0157_INTRA_FUSION
+                  predIntraAng(COMPONENT_Y, piPred, pu, false);
+#else
+                  predIntraAng(COMPONENT_Y, piPred, pu);
+#endif
+                  PelBuf predBuf(m_intraPredBuf[dimdMode], tmpArea);
+                  predBuf.copyFrom(piPred);
+                  m_intraModeReady[dimdMode] = 1;
+                }
+              }
+            }
+#endif
             if (sps.getUseMIP() && LFNSTSaveFlag)
             {
               // save found best modes
@@ -1847,7 +2014,93 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
             uiHadModeList = m_uiSavedHadModeListLFNST;
             CandHadList   = m_dSavedHadListLFNST;
           }
-
+#if JVET_AH0076_OBIC
+          if (obicSaveFlag || dimdSaveFlag)
+          {
+            cu.dimd = true;
+            cu.obicFlag = false;
+            cu.timd = false;
+            cu.mipFlag = false;
+            cu.tmpFlag = false;
+            cu.tmrlFlag = false;
+            cu.firstPU->multiRefIdx = 0;
+            cu.ispMode = NOT_INTRA_SUBPARTITIONS;
+            int iWidth = cu.lwidth();
+            int iHeight = cu.lheight();
+            if (obicSaveFlag)
+            {
+              cu.obicFlag = true;
+              int obicMode = cu.obicMode[0];
+              pu.intraDir[CHANNEL_TYPE_LUMA] = obicMode;
+              bool blendModes[OBIC_FUSION_NUM - 1] = {false};
+              PelBuf predFusion[OBIC_FUSION_NUM - 1];
+              CHECK(!m_intraModeReady[obicMode], "OBIC mode is not ready!");
+              const UnitArea localUnitArea( pu.chromaFormat, Area( 0, 0, iWidth, iHeight ) );
+              PelBuf predBuf(m_intraPredBuf[obicMode], pu.Y());
+              piPred.copyFrom(predBuf);
+              int planarIdx = 0;
+              for (int idx = 0; idx < OBIC_FUSION_NUM - 1; idx++)
+              {
+                blendModes[idx] = false;
+                predFusion[idx] = m_tempBuffer[idx].getBuf( localUnitArea.Y() );
+                int iMode = cu.obicMode[idx + 1];
+                if (iMode >= 0)
+                {
+                  blendModes[idx] = true;
+                  CHECK(!m_intraModeReady[iMode], "OBIC mode is not ready!");
+                  PelBuf predBufTmp(m_intraPredBuf[iMode], pu.Y());
+                  predFusion[idx].copyFrom(predBufTmp);
+                  if (iMode == PLANAR_IDX)
+                  {
+                    planarIdx = idx;
+                  }
+                }
+                else
+                {
+                  PelBuf planarBuf(m_intraPredBuf[PLANAR_IDX], pu.Y());
+                  predFusion[idx].copyFrom(planarBuf);
+                }
+              }
+              if (cu.obicIsBlended)
+              {
+                generateObicBlending(piPred, pu, predFusion, blendModes, planarIdx);
+              }
+              else
+              {
+                initIntraPatternChType(cu, pu.Y(), false);
+                predIntraAng(COMPONENT_Y, piPred, pu);
+              }
+              PelBuf obicSaveBuf(m_obicPredBuf, pu.Y());
+              obicSaveBuf.copyFrom(piPred);
+            }
+            if (dimdSaveFlag)
+            {
+              cu.obicFlag = false;
+              int dimdMode = cu.dimdMode;
+              pu.intraDir[CHANNEL_TYPE_LUMA] = dimdMode;
+              if (cu.dimdBlending)
+              {
+                PelBuf predBuf(m_intraPredBuf[dimdMode], tmpArea);
+                piPred.copyFrom(predBuf);
+                PelBuf blendBuf0((m_intraPredBuf[cu.dimdBlendMode[0] > 0 ?cu.dimdBlendMode[0] : PLANAR_IDX]), tmpArea);
+                PelBuf blendBuf1((m_intraPredBuf[cu.dimdBlendMode[1] > 0 ?cu.dimdBlendMode[1] : PLANAR_IDX]), tmpArea);
+                PelBuf blendBuf2((m_intraPredBuf[cu.dimdBlendMode[2] > 0 ?cu.dimdBlendMode[2] : PLANAR_IDX]), tmpArea);
+                PelBuf blendBuf3((m_intraPredBuf[cu.dimdBlendMode[3] > 0 ?cu.dimdBlendMode[3] : PLANAR_IDX]), tmpArea);
+                PelBuf planarBuf(m_intraPredBuf[PLANAR_IDX], tmpArea);
+                generateDimdBlending(piPred, pu, blendBuf0, blendBuf1, blendBuf2, blendBuf3, planarBuf);
+              }
+              else
+              {
+                initIntraPatternChType(cu, pu.Y(), false);
+                predIntraAng(COMPONENT_Y, piPred, pu);
+              }
+              PelBuf dimdSaveBuf(m_dimdPredBuf, pu.Y());
+              dimdSaveBuf.copyFrom(piPred);
+            }
+          }
+          cu.dimd = false;
+          cu.obicFlag = false;
+#endif
 #if JVET_AB0155_SGPM
           if (testSgpm)
           {
@@ -2257,6 +2510,14 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
       CHECK(numModesForFullRD != uiRdModeList.size(), "Inconsistent state!");
 #endif
 
+#if JVET_AH0076_OBIC
+      cu.obicFlag = false;
+      if (testObic)
+      {
+        ModeInfo m = ModeInfo( false, false, 0, NOT_INTRA_SUBPARTITIONS, OBIC_IDX );
+        uiRdModeList.push_back(m);
+      }
+#endif
       // after this point, don't use numModesForFullRD
       // PBINTRA fast
       if (m_pcEncCfg->getUsePbIntraFast() && !cs.slice->isIntra() && uiRdModeList.size() < numModesAvailable
@@ -2492,7 +2753,11 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
         uiOrgMode   = uiRdModeList[mode];
       }
 #if ENABLE_DIMD && INTRA_TRANS_ENC_OPT
-      if ((m_pcEncCfg->getIntraPeriod() == 1) && cu.slice->getSPS()->getUseDimd() && mode >= 0 && !cu.dimdBlending && uiOrgMode.ispMod == 0 && uiOrgMode.mRefId == 0 && uiOrgMode.modeId != TIMD_IDX && uiOrgMode.modeId != DIMD_IDX)
+      if ((m_pcEncCfg->getIntraPeriod() == 1) && cu.slice->getSPS()->getUseDimd() && mode >= 0 && !cu.dimdBlending && uiOrgMode.ispMod == 0 && uiOrgMode.mRefId == 0 && uiOrgMode.modeId != TIMD_IDX && uiOrgMode.modeId != DIMD_IDX
+#if JVET_AH0076_OBIC
+        && uiOrgMode.modeId != OBIC_IDX
+#endif
+          )
       {
         bool modeDuplicated = (uiOrgMode.modeId == cu.dimdMode);
         if (modeDuplicated)
@@ -2506,10 +2771,33 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
       cu.dimd = false;
       if( mode >= 0 && uiOrgMode.modeId == DIMD_IDX ) /*to check*/
       {
+#if JVET_AH0076_OBIC
+        if (m_skipDimdLfnstMtsPass)
+        {
+          CHECK(!cu.lfnstIdx && !cu.mtsFlag, "invalid logic");
+          continue;
+        }
+#endif
         uiOrgMode.modeId = cu.dimdMode;
         cu.dimd = true;
       }
 #endif
+#if JVET_AH0076_OBIC
+      cu.obicFlag = false;
+      if( mode >= 0 && uiOrgMode.modeId == OBIC_IDX) /*to check*/
+      {
+        if (m_skipObicLfnstMtsPass)
+        {
+          CHECK(!cu.lfnstIdx && !cu.mtsFlag, "invalid logic");
+          continue;
+        }
+        cu.obicFlag = true;
+        cu.dimd = true;
+        uiOrgMode.modeId = cu.obicMode[0];
+        pu.intraDir[CHANNEL_TYPE_LUMA] = uiOrgMode.modeId;
+        pu.multiRefIdx = 0;
+      }
+#endif
 #if JVET_AC0105_DIRECTIONAL_PLANAR
       cu.plIdx = 0;
       if (mode >= 0 && uiOrgMode.modeId == PL_HOR_IDX)
@@ -2861,6 +3149,25 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
           }
 #endif
         }
+#endif
+#if JVET_AH0076_OBIC
+        if (setSkipDimdControl && cu.dimd)
+        {
+          if (cu.obicFlag)
+          {
+            if (csTemp->cost < obicAngCost)
+            {
+              obicAngCost = csTemp->cost;
+            }
+          }
+          else
+          {
+            if (csTemp->cost < dimdAngCost)
+            {
+              dimdAngCost = csTemp->cost;
+            }
+          }
+        }
 #endif
         // check r-d cost
         if( csTemp->cost < csBest->cost )
@@ -2872,6 +3179,9 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
 #if ENABLE_DIMD
           bestDimdMode = cu.dimd;
 #endif
+#if JVET_AH0076_OBIC
+          bestObicMode = cu.obicFlag;
+#endif
 #if JVET_W0123_TIMD_FUSION
           bestTimdMode = cu.timd;
 #endif
@@ -2888,6 +3198,13 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
             intraTmpDimdMode = curCu->intraTmpDimdMode;
           }
 #endif 
+#if JVET_AH0076_OBIC
+          if (cu.mipFlag)
+          {
+            CodingUnit* curCu = csBest->getCU(partitioner.currArea().lumaPos(), partitioner.chType);
+            bestMipDimd = curCu->mipDimdMode;
+          }
+#endif
 
           if( sps.getUseLFNST() && mtsUsageFlag == 1 && !cu.ispMode )
           {
@@ -2961,6 +3278,19 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
         m_skipTimdLfnstMtsPass = true;
       }
     }
+#endif
+#if JVET_AH0076_OBIC
+    if (setSkipDimdControl)
+    {
+      if (regAngCost != MAX_DOUBLE && dimdAngCost != MAX_DOUBLE && regAngCost * 1.5 < dimdAngCost)
+      {
+        m_skipDimdLfnstMtsPass = true;
+      }
+      if (regAngCost != MAX_DOUBLE && obicAngCost != MAX_DOUBLE && regAngCost * 1.5 < obicAngCost)
+      {
+        m_skipObicLfnstMtsPass = true;
+      }
+    }
 #endif
     cu.ispMode = uiBestPUMode.ispMod;
     cu.lfnstIdx = bestLfnstIdx;
@@ -3015,6 +3345,15 @@ bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, c
       {
         CHECK(pu.multiRefIdx > 0, "use of DIMD");
       }
+#endif
+#if JVET_AH0076_OBIC
+      cu.obicFlag = bestObicMode;
+      cu.mipDimdMode = bestMipDimd;
+      if (cu.obicFlag)
+      {
+        pu.intraDir[ CHANNEL_TYPE_LUMA ] = cu.obicMode[0];
+        cu.dimd = true;
+      }
 #endif
       cu.bdpcmMode = bestBDPCMMode;
 #if JVET_W0123_TIMD_FUSION
@@ -8314,7 +8653,11 @@ void IntraSearch::xSelectAMTForFullRD(TransformUnit &tu
   else if (PU::isMIP(pu, chType))
   {
     initIntraMip(pu, area);
+#if JVET_AH0076_OBIC
+    predIntraMip(COMPONENT_Y, piPred, pu, true);
+#else
     predIntraMip(COMPONENT_Y, piPred, pu);
+#endif
   }
 #if JVET_AG0058_EIP
   else if (PU::isEIP(pu, chType))
@@ -8322,6 +8665,13 @@ void IntraSearch::xSelectAMTForFullRD(TransformUnit &tu
     const CPelBuf eipSaveBuf(pu.cu->eipMerge ? m_eipMergePredBuf[pu.intraDir[0]] : m_eipPredBuf[pu.intraDir[0]], pu.Y());
     piPred.copyFrom(eipSaveBuf);
   }
+#endif
+#if JVET_AH0076_OBIC
+  else if (pu.cu->dimd && chType == CHANNEL_TYPE_LUMA && pu.cu->ispMode == NOT_INTRA_SUBPARTITIONS)
+  {
+    const CPelBuf dimdSaveBuf(pu.cu->obicFlag ? m_obicPredBuf : m_dimdPredBuf, pu.Y());
+    piPred.copyFrom(dimdSaveBuf);
+  }
 #endif
   else
   {
@@ -8632,7 +8982,11 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
         {
           initIntraMip( pu, area );
 #if JVET_AB0067_MIP_DIMD_LFNST
+#if JVET_AH0076_OBIC
+          predIntraMip( compID, piPred, pu, true);
+#else
           predIntraMip( compID, piPred, pu, pu.cu->lfnstIdx > 0 ? true : false);
+#endif
 #else
           predIntraMip( compID, piPred, pu );
 #endif
@@ -8643,6 +8997,13 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
           const CPelBuf eipSaveBuf(pu.cu->eipMerge ? m_eipMergePredBuf[pu.intraDir[0]] : m_eipPredBuf[pu.intraDir[0]], pu.Y());
           piPred.copyFrom(eipSaveBuf);
         }
+#endif
+#if JVET_AH0076_OBIC
+        else if (pu.cu->dimd && compID == COMPONENT_Y && pu.cu->ispMode == NOT_INTRA_SUBPARTITIONS)
+        {
+          const CPelBuf dimdSaveBuf(pu.cu->obicFlag ? m_obicPredBuf : m_dimdPredBuf, pu.Y());
+          piPred.copyFrom(dimdSaveBuf);
+        }
 #endif
         else
         {
@@ -10321,7 +10682,11 @@ bool IntraSearch::xRecurIntraCodingACTQT(CodingStructure &cs, Partitioner &parti
       {
         initIntraMip(pu, area);
 #if JVET_AB0067_MIP_DIMD_LFNST
+#if JVET_AH0076_OBIC
+        predIntraMip(compID, piPred, pu, true);
+#else
         predIntraMip(compID, piPred, pu, pu.cu->lfnstIdx > 0 ? true : false);
+#endif
 #else
         predIntraMip(compID, piPred, pu);
 #endif
diff --git a/source/Lib/EncoderLib/IntraSearch.h b/source/Lib/EncoderLib/IntraSearch.h
index a6c47448d..f46906d58 100644
--- a/source/Lib/EncoderLib/IntraSearch.h
+++ b/source/Lib/EncoderLib/IntraSearch.h
@@ -609,6 +609,10 @@ private:
   static_vector<ModeInfo, NUM_DERIVED_EIP + MAX_MERGE_EIP> m_uiSavedHadModeListEip;
   static_vector<double, NUM_DERIVED_EIP + MAX_MERGE_EIP>   m_dSavedModeCostEip;
   static_vector<double, NUM_DERIVED_EIP + MAX_MERGE_EIP>   m_dSavedHadListEip;
+#endif
+#if JVET_AH0076_OBIC
+  Pel* m_dimdPredBuf;
+  Pel* m_obicPredBuf;
 #endif
   PelStorage      m_tmpStorageLCU;
   PelStorage      m_colorTransResiBuf;
@@ -691,6 +695,10 @@ public:
 #if INTRA_TRANS_ENC_OPT
   bool            m_skipTimdLfnstMtsPass;
 #endif
+#if JVET_AH0076_OBIC
+  bool            m_skipObicLfnstMtsPass;
+  bool            m_skipDimdLfnstMtsPass;
+#endif
 #if JVET_AC0147_CCCM_NO_SUBSAMPLING
   bool            m_skipCCCMSATD;
   int             m_isCccmNoSubModeEnabledInRdo[MMLM_T_IDX + 1];
-- 
GitLab