From d98288fa4e5cf548771b051af1defe1e03d1ef69 Mon Sep 17 00:00:00 2001
From: Edouard Francois <edouard.francois@technicolor.com>
Date: Mon, 5 Jul 2021 16:26:20 +0000
Subject: [PATCH] JVET-V0108: Colour transform information SEI

---
 cfg/examples_SEI_CTI/readMe.txt               |   7 +
 cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr1.cfg |  24 +++
 cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr2.cfg |  21 ++
 cfg/examples_SEI_CTI/seiCti_sdr_to_hdrPq1.cfg |  22 ++
 doc/software-manual.tex                       |  69 +++++-
 source/App/DecoderApp/DecApp.cpp              |  91 ++++++++
 source/App/DecoderApp/DecApp.h                |   3 +
 source/App/DecoderApp/DecAppCfg.cpp           |   6 +
 source/App/DecoderApp/DecAppCfg.h             |   3 +
 source/App/EncoderApp/EncApp.cpp              |  18 ++
 source/App/EncoderApp/EncAppCfg.cpp           |  77 +++++++
 source/App/EncoderApp/EncAppCfg.h             |  15 ++
 source/App/Parcat/parcat.cpp                  |  16 ++
 source/Lib/CommonLib/Buffer.cpp               |  66 ++++++
 source/Lib/CommonLib/Buffer.h                 |   4 +
 source/Lib/CommonLib/CommonDef.h              |   3 +
 source/Lib/CommonLib/Picture.cpp              |  49 +++++
 source/Lib/CommonLib/Picture.h                |   9 +
 source/Lib/CommonLib/SEI.cpp                  |   3 +
 source/Lib/CommonLib/SEI.h                    |  27 +++
 source/Lib/CommonLib/SEIColourTransform.cpp   | 200 ++++++++++++++++++
 source/Lib/CommonLib/SEIColourTransform.h     |  73 +++++++
 source/Lib/CommonLib/TypeDef.h                |  12 +-
 source/Lib/DecoderLib/DecLib.cpp              |   8 +
 source/Lib/DecoderLib/DecLib.h                |   5 +
 source/Lib/DecoderLib/SEIread.cpp             |  86 ++++++++
 source/Lib/DecoderLib/SEIread.h               |   3 +
 source/Lib/EncoderLib/EncCfg.h                |  43 ++++
 source/Lib/EncoderLib/EncGOP.cpp              |   9 +
 source/Lib/EncoderLib/SEIEncoder.cpp          |  27 +++
 source/Lib/EncoderLib/SEIEncoder.h            |   3 +
 source/Lib/EncoderLib/SEIwrite.cpp            |  61 ++++++
 source/Lib/EncoderLib/SEIwrite.h              |   3 +
 33 files changed, 1055 insertions(+), 11 deletions(-)
 create mode 100644 cfg/examples_SEI_CTI/readMe.txt
 create mode 100644 cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr1.cfg
 create mode 100644 cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr2.cfg
 create mode 100644 cfg/examples_SEI_CTI/seiCti_sdr_to_hdrPq1.cfg
 create mode 100644 source/Lib/CommonLib/SEIColourTransform.cpp
 create mode 100644 source/Lib/CommonLib/SEIColourTransform.h

diff --git a/cfg/examples_SEI_CTI/readMe.txt b/cfg/examples_SEI_CTI/readMe.txt
new file mode 100644
index 000000000..04fb12dbe
--- /dev/null
+++ b/cfg/examples_SEI_CTI/readMe.txt
@@ -0,0 +1,7 @@
+example encoding command line
+encoder -c encoder_randomaccess_vtm.cfg -c classH1.cfg -c H1_BalloonFestival.cfg -c seiCti_hdrPq_to_sdr1.cfg
+        -i BalloonFestival_1920x1080p_24_10b_pq_709_ct2020_420_rev1.yuv -ip 32 -fs 0 -f 33 -q 22 
+        -b BalloonFestival_1920x1080p_24_10b_pq_709_ct2020_420_rev1.bin -o /dev/null --InternalBitDepth=10 --OutputBitDepth=10 
+
+example decoding command line
+decoder -b BalloonFestival_1920x1080p_24_10b_pq_709_ct2020_420_rev1.bin -o dec.yuv --SEICTIFilename=dec_cti.yuv
\ No newline at end of file
diff --git a/cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr1.cfg b/cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr1.cfg
new file mode 100644
index 000000000..8ac807a9a
--- /dev/null
+++ b/cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr1.cfg
@@ -0,0 +1,24 @@
+# example mapping SDR BT.2020 std range to BT.2100 HDR-PQ-like Std range 
+# this is provided as indicative example, but in principle the mapping curve should be dynamically tuned depending on the content
+
+SEICTIEnabled                 : 1
+SEICTIId                      : 1
+
+SEICTISignalInfoFlag          : 1
+SEICTIFullRangeFlag           : 0
+SEICTIPrimaries               : 9
+SEICTITransferFunction        : 14
+SEICTIMatrixCoefs             : 9
+
+SEICTICrossCompFlag           : 1
+SEICTICrossCompInferred       : 1
+SEICTILut0                    : 64 00 41 41 41 41 44 50 56 62 70 78 87 97 109 79 00 # Lut Y 
+
+SEICTIChromaOffset            : 0      # chroma scaling offset
+
+### DO NOT ADD ANYTHING BELOW THIS LINE ###
+### DO NOT DELETE THE EMPTY LINE BELOW ###
+
+
+
+
diff --git a/cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr2.cfg b/cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr2.cfg
new file mode 100644
index 000000000..715d8756e
--- /dev/null
+++ b/cfg/examples_SEI_CTI/seiCti_hdrPq_to_sdr2.cfg
@@ -0,0 +1,21 @@
+# example mapping BT.2100 HDR-PQ Std range to SDR-like BT.2020 std range
+# this is provided as indicative example, but in principle the mapping curve should be dynamically tuned depending on the content
+
+SEICTIEnabled                 : 1
+SEICTIId                      : 1
+
+SEICTISignalInfoFlag          : 1
+SEICTIFullRangeFlag           : 0
+SEICTIPrimaries               : 9
+SEICTITransferFunction        : 14
+SEICTIMatrixCoefs             : 9
+
+SEICTICrossCompFlag           : 1      # cross-component scaling mode enabled or not
+SEICTICrossCompInferred       : 0      # chroma LUT inferred (1) or not (0) from luma LUT
+SEICTINbChromaLut             : 1      # nb of chroma LUT (1 or 2)
+SEICTILut0                    : 64 00 41 41 41 41 44 50 56 62 70 78 87 97 109 79 00 # Lut Y 
+SEICTILut1                    : 64 56 47 47 47 48 51 56 60 66 72 78 85 93 87  70 64 # Lut chroma 
+SEICTIChromaOffset            : 0      # chroma scaling offset
+
+### DO NOT ADD ANYTHING BELOW THIS LINE ###
+### DO NOT DELETE THE EMPTY LINE BELOW ###
diff --git a/cfg/examples_SEI_CTI/seiCti_sdr_to_hdrPq1.cfg b/cfg/examples_SEI_CTI/seiCti_sdr_to_hdrPq1.cfg
new file mode 100644
index 000000000..08efe7406
--- /dev/null
+++ b/cfg/examples_SEI_CTI/seiCti_sdr_to_hdrPq1.cfg
@@ -0,0 +1,22 @@
+# example mapping BT.2100 HDR-PQ Std range to SDR-like BT.2020 std range
+# this is provided as indicative example, but in principle the mapping curve should be dynamically tuned depending on the content
+
+SEICTIEnabled                 : 1
+SEICTIId                      : 1
+
+SEICTISignalInfoFlag          : 1
+SEICTIFullRangeFlag           : 0
+SEICTIPrimaries               : 9
+SEICTITransferFunction        : 16
+SEICTIMatrixCoefs             : 9
+
+SEICTICrossCompFlag           : 1
+SEICTICrossCompInferred       : 1
+SEICTILut0                    : 64 00 103 101 97 79 72 67 58 54 47 46 42 39 41 50 00 # Lut Y 
+SEICTIChromaOffset            : 0      # chroma scaling offset
+
+### DO NOT ADD ANYTHING BELOW THIS LINE ###
+### DO NOT DELETE THE EMPTY LINE BELOW ###
+
+
+
diff --git a/doc/software-manual.tex b/doc/software-manual.tex
index e6c8246ac..aab013ac1 100644
--- a/doc/software-manual.tex
+++ b/doc/software-manual.tex
@@ -3522,7 +3522,7 @@ The table below lists the SEI messages defined for Version 1 and Range-Extension
   139 & Temporal motion-constrained tile sets    & Table \ref{tab:sei-tmcts} \\
   140 & Chroma resampling filter hint            & Table \ref{tab:chroma-resampling-filter-hint} \\
   141 & Knee function information                & Table \ref{tab:sei-knee-function} \\
-  142 & Colour remapping information             & Table \ref{tab:sei-colour-remapping}\\
+  142 & Colour transform information             & Table \ref{tab:sei-colour-transform}\\
   143 & Deinterlaced field identification        & (Not handled)\\
   144 & Content light level info                 & Table \ref{tab:sei-content-light-level}\\
   147 & Alternative transfer characteristics     & Table \ref{tab:sei-alternative-transfer-characteristics}\\
@@ -4207,12 +4207,63 @@ Array of output knee point. Default table can be set to the following:
 \end{OptionTableNoShorthand}
 
 
-\begin{OptionTableNoShorthand}{Colour remapping SEI message encoder parameters}{tab:sei-colour-remapping}
-\Option{SEIColourRemappingInfoFileRoot (-cri)} &
-\Default{\NotSet} &
-Specifies the prefix of input Colour Remapping Information file. Prefix is completed by ``_x.txt'' where x is the  POC number.
-The contents of the file are a list of the SEI message's syntax element names (in decoding order) immediately followed by a `:' and then the associated value.
-An example file can be found in cfg/misc/example_colour_remapping_sei_encoder_0.txt.
+\begin{OptionTableNoShorthand}{Colour transform information SEI message encoder parameters}{tab:sei-colour-transform}
+\Option{SEICTIEnabled} &
+\Default{false} &
+Enables (true) or disables (false) the insertion of colour transform information (CTI) SEI message.
+Examples configuration files for CTI can be found in folder cfg/examples_SEI_CTI.
+\\
+\Option{SEICTIId} &
+\Default{0} &
+Specifies the ID of the CTI SEI message.
+\\
+\Option{SEICTISignalInfoFlag} &
+\Default{false} &
+Enables (true) or disables (false) the insertion of output signal information after applying the colour transform.
+\\
+\Option{SEICTIFullRangeFlag} &
+\Default{false} &
+Specifies the range (true:full, false:limited) of the output signal after applying the colour transform.
+\\
+\Option{SEICTIPrimaries} &
+\Default{0} &
+Specifies the colour primaries of the output signal after applying the colour transform.
+\\
+\Option{SEICTITransferFunction} &
+\Default{0} &
+Specifies the transfer function (characteristics) of the output signal after applying the colour transform.
+\\
+\Option{SEICTIMatrixCoefs} &
+\Default{0} &
+Specifies the matrix coefficients type of the output signal after applying the colour transform.
+\\
+\Option{SEICTICrossCompFlag} &
+\Default{true} &
+Enables (true) or disables (false) the cross-component scaling for applying the colour transform.
+\\
+\Option{SEICTICrossCompInferred} &
+\Default{true} &
+Infers (true) or signals (false) the cross-component scaling tables for the colour transform.
+\\
+\Option{SEICTINbChromaLut} &
+\Default{0} &
+Specifies the number of chroma tables (1 or 2) for the colour transform (only used when SEICTICrossCompInferred = false).
+\\
+\Option{SEICTILut0} &
+\Default{0} &
+Specifies the transform table for colour component 0.
+\\
+\Option{SEICTILut1} &
+\Default{0} &
+Specifies the transform table for colour component 1 (only used when SEICTICrossCompFlag = false).
+\\
+\Option{SEICTILut2} &
+\Default{0} &
+Specifies the transform table for colour component 2 (only used when SEICTINbChromaLut = 2).
+\\
+\Option{SEICTIChromaOffset} &
+\Default{0} &
+Specifies the offset to be added to the values of the cross-component scaling tables (only used when SEICTICrossCompInferred = false).
 \\
 \end{OptionTableNoShorthand}
 
@@ -5169,10 +5220,10 @@ has the following behaviour:
 When a non-empty file name is specified, information regarding any decoded SEI messages will be output to the indicated file. If the file name is '-', then stdout is used instead.
 \\
 
-\Option{SEIColourRemappingInfoFilename} &
+\Option{SEICTIFilename} &
 %\ShortOption{\None} &
 \Default{\NotSet} &
-Specifies that the colour remapping SEI message should be applied to the output video, with the output written to this file.
+Specifies that the colour transform information (CTI) SEI message should be applied to the output video, with the output written to this file.
 If no value is specified, the SEI message is ignored and no mapping is applied.
 \\
 
diff --git a/source/App/DecoderApp/DecApp.cpp b/source/App/DecoderApp/DecApp.cpp
index 023ab1594..5886ea266 100644
--- a/source/App/DecoderApp/DecApp.cpp
+++ b/source/App/DecoderApp/DecApp.cpp
@@ -417,6 +417,42 @@ uint32_t DecApp::decode()
           m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].setBitdepthShift(channelType, reconBitdepth - fileBitdepth);
         }
       }
+#if JVET_V0108
+      if (!m_SEICTIFileName.empty() && !m_cVideoIOYuvSEICTIFile[nalu.m_nuhLayerId].isOpen())
+      {
+        const BitDepths& bitDepths = pcListPic->front()->cs->sps->getBitDepths(); // use bit depths of first reconstructed picture.
+        for (uint32_t channelType = 0; channelType < MAX_NUM_CHANNEL_TYPE; channelType++)
+        {
+          if (m_outputBitDepth[channelType] == 0)
+          {
+            m_outputBitDepth[channelType] = bitDepths.recon[channelType];
+          }
+        }
+
+        if (m_packedYUVMode && (m_outputBitDepth[CH_L] != 10 && m_outputBitDepth[CH_L] != 12))
+        {
+          EXIT("Invalid output bit-depth for packed YUV output, aborting\n");
+        }
+
+        std::string SEICTIFileName = m_SEICTIFileName;
+        if (m_SEICTIFileName.compare("/dev/null") && m_cDecLib.getVPS() != nullptr && m_cDecLib.getVPS()->getMaxLayers() > 1 && xIsNaluWithinTargetOutputLayerIdSet(&nalu))
+        {
+          size_t pos = SEICTIFileName.find_last_of('.');
+          if (pos != string::npos)
+          {
+            SEICTIFileName.insert(pos, std::to_string(nalu.m_nuhLayerId));
+          }
+          else
+          {
+            SEICTIFileName.append(std::to_string(nalu.m_nuhLayerId));
+          }
+        }
+        if ((m_cDecLib.getVPS() != nullptr && (m_cDecLib.getVPS()->getMaxLayers() == 1 || xIsNaluWithinTargetOutputLayerIdSet(&nalu))) || m_cDecLib.getVPS() == nullptr)
+        {
+          m_cVideoIOYuvSEICTIFile[nalu.m_nuhLayerId].open(SEICTIFileName, true, m_outputBitDepth, m_outputBitDepth, bitDepths.recon); // write mode
+        }
+      }
+#endif
       if (!m_annotatedRegionsSEIFileName.empty())
       {
         xOutputAnnotatedRegions(pcListPic);
@@ -618,6 +654,15 @@ void DecApp::xDestroyDecLib()
       recFile.second.close();
     }
   }
+#if JVET_V0108
+  if (!m_SEICTIFileName.empty())
+  {
+    for (auto& recFile : m_cVideoIOYuvSEICTIFile)
+    {
+      recFile.second.close();
+    }
+  }
+#endif
 
   // destroy decoder class
   m_cDecLib.destroy();
@@ -776,6 +821,29 @@ void DecApp::xWriteOutput( PicList* pcListPic, uint32_t tId )
                                         NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range );
             }
         }
+#if JVET_V0108
+        // Perform CTI on decoded frame and write to output CTI file
+        if (!m_SEICTIFileName.empty())
+        {
+          const Window& conf = pcPic->getConformanceWindow();
+          const SPS* sps = pcPic->cs->sps;
+          ChromaFormat chromaFormatIDC = sps->getChromaFormatIdc();
+          if (m_upscaledOutput)
+          {
+            m_cVideoIOYuvSEICTIFile[pcPic->layerId].writeUpscaledPicture(*sps, *pcPic->cs->pps, pcPic->getDisplayBuf(), m_outputColourSpaceConvert, m_packedYUVMode, m_upscaledOutput, NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range);
+          }
+          else
+          {
+            m_cVideoIOYuvSEICTIFile[pcPic->layerId].write(pcPic->getRecoBuf().get(COMPONENT_Y).width, pcPic->getRecoBuf().get(COMPONENT_Y).height, pcPic->getDisplayBuf(),
+              m_outputColourSpaceConvert, m_packedYUVMode,
+              conf.getWindowLeftOffset() * SPS::getWinUnitX(chromaFormatIDC),
+              conf.getWindowRightOffset() * SPS::getWinUnitX(chromaFormatIDC),
+              conf.getWindowTopOffset() * SPS::getWinUnitY(chromaFormatIDC),
+              conf.getWindowBottomOffset() * SPS::getWinUnitY(chromaFormatIDC),
+              NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range);
+          }
+        }
+#endif
         writeLineToOutputLog(pcPic);
 
         // update POC of display order
@@ -923,6 +991,29 @@ void DecApp::xFlushOutput( PicList* pcListPic, const int layerId )
                                         NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range );
               }
           }
+#if JVET_V0108
+          // Perform CTI on decoded frame and write to output CTI file
+          if (!m_SEICTIFileName.empty())
+          {
+            const Window& conf = pcPic->getConformanceWindow();
+            const SPS* sps = pcPic->cs->sps;
+            ChromaFormat chromaFormatIDC = sps->getChromaFormatIdc();
+            if (m_upscaledOutput)
+            {
+              m_cVideoIOYuvSEICTIFile[pcPic->layerId].writeUpscaledPicture(*sps, *pcPic->cs->pps, pcPic->getDisplayBuf(), m_outputColourSpaceConvert, m_packedYUVMode, m_upscaledOutput, NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range);
+            }
+            else
+            {
+              m_cVideoIOYuvSEICTIFile[pcPic->layerId].write(pcPic->getRecoBuf().get(COMPONENT_Y).width, pcPic->getRecoBuf().get(COMPONENT_Y).height, pcPic->getDisplayBuf(),
+                m_outputColourSpaceConvert, m_packedYUVMode,
+                conf.getWindowLeftOffset() * SPS::getWinUnitX(chromaFormatIDC),
+                conf.getWindowRightOffset() * SPS::getWinUnitX(chromaFormatIDC),
+                conf.getWindowTopOffset() * SPS::getWinUnitY(chromaFormatIDC),
+                conf.getWindowBottomOffset() * SPS::getWinUnitY(chromaFormatIDC),
+                NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range);
+            }
+          }
+#endif
           writeLineToOutputLog(pcPic);
 #if JVET_S0078_NOOUTPUTPRIORPICFLAG
         }
diff --git a/source/App/DecoderApp/DecApp.h b/source/App/DecoderApp/DecApp.h
index d3573294b..2b3580ab4 100644
--- a/source/App/DecoderApp/DecApp.h
+++ b/source/App/DecoderApp/DecApp.h
@@ -61,6 +61,9 @@ private:
   // class interface
   DecLib          m_cDecLib;                     ///< decoder class
   std::unordered_map<int, VideoIOYuv>      m_cVideoIOYuvReconFile;        ///< reconstruction YUV class
+#if JVET_V0108
+  std::unordered_map<int, VideoIOYuv>      m_cVideoIOYuvSEICTIFile;       ///< reconstruction YUV with CTI class
+#endif
 
   // for output control
   int             m_iPOCLastDisplay;              ///< last POC in display order
diff --git a/source/App/DecoderApp/DecAppCfg.cpp b/source/App/DecoderApp/DecAppCfg.cpp
index 0a126cb13..c4ad90ebf 100644
--- a/source/App/DecoderApp/DecAppCfg.cpp
+++ b/source/App/DecoderApp/DecAppCfg.cpp
@@ -96,6 +96,9 @@ bool DecAppCfg::parseCfg( int argc, char* argv[] )
   ("SEINoDisplay",              m_decodedNoDisplaySEIEnabled,          true,       "Control handling of decoded no display SEI messages")
   ("TarDecLayerIdSetFile,l",    cfg_TargetDecLayerIdSetFile,           string(""), "targetDecLayerIdSet file name. The file should include white space separated LayerId values to be decoded. Omitting the option or a value of -1 in the file decodes all layers.")
   ("SEIColourRemappingInfoFilename",  m_colourRemapSEIFileName,        string(""), "Colour Remapping YUV output file name. If empty, no remapping is applied (ignore SEI message)\n")
+#if JVET_V0108
+  ("SEICTIFilename",            m_SEICTIFileName,                      string(""), "CTI YUV output file name. If empty, no Colour Transform is applied (ignore SEI message)\n")
+#endif
   ("SEIAnnotatedRegionsInfoFilename",  m_annotatedRegionsSEIFileName,   string(""), "Annotated regions output file name. If empty, no object information will be saved (ignore SEI message)\n")
   ("OutputDecodedSEIMessagesFilename",  m_outputDecodedSEIMessagesFilename,    string(""), "When non empty, output decoded SEI messages to the indicated file. If file is '-', then output to stdout\n")
 #if JVET_S0257_DUMP_360SEI_MESSAGE
@@ -258,6 +261,9 @@ DecAppCfg::DecAppCfg()
 , m_decodedPictureHashSEIEnabled(0)
 , m_decodedNoDisplaySEIEnabled(false)
 , m_colourRemapSEIFileName()
+#if JVET_V0108
+, m_SEICTIFileName()
+#endif
 , m_annotatedRegionsSEIFileName()
 , m_targetDecLayerIdSet()
 , m_outputDecodedSEIMessagesFilename()
diff --git a/source/App/DecoderApp/DecAppCfg.h b/source/App/DecoderApp/DecAppCfg.h
index bdf0db7a9..157c18cfc 100644
--- a/source/App/DecoderApp/DecAppCfg.h
+++ b/source/App/DecoderApp/DecAppCfg.h
@@ -72,6 +72,9 @@ protected:
   int           m_decodedPictureHashSEIEnabled;       ///< Checksum(3)/CRC(2)/MD5(1)/disable(0) acting on decoded picture hash SEI message
   bool          m_decodedNoDisplaySEIEnabled;         ///< Enable(true)/disable(false) writing only pictures that get displayed based on the no display SEI message
   std::string   m_colourRemapSEIFileName;             ///< output Colour Remapping file name
+#if JVET_V0108
+  std::string   m_SEICTIFileName;                     ///< output Recon with CTI file name
+#endif
   std::string   m_annotatedRegionsSEIFileName;        ///< annotated regions file name
   std::vector<int> m_targetDecLayerIdSet;             ///< set of LayerIds to be included in the sub-bitstream extraction process.
   std::string   m_outputDecodedSEIMessagesFilename;   ///< filename to output decoded SEI messages to. If '-', then use stdout. If empty, do not output details.
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index e66809b6d..30c4d1119 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -984,6 +984,24 @@ void EncApp::xInitLibCfg()
   m_cEncLib.setAmbientViewingEnvironmentSEIIlluminance           (m_aveSEIAmbientIlluminance);
   m_cEncLib.setAmbientViewingEnvironmentSEIAmbientLightX         ((uint16_t)m_aveSEIAmbientLightX);
   m_cEncLib.setAmbientViewingEnvironmentSEIAmbientLightY         ((uint16_t)m_aveSEIAmbientLightY);
+#if JVET_V0108
+  // colour tranform information sei
+  m_cEncLib.setCtiSEIEnabled(m_ctiSEIEnabled);
+  m_cEncLib.setCtiSEIId(m_ctiSEIId);
+  m_cEncLib.setCtiSEISignalInfoFlag(m_ctiSEISignalInfoFlag);
+  m_cEncLib.setCtiSEIFullRangeFlag(m_ctiSEIFullRangeFlag);
+  m_cEncLib.setCtiSEIPrimaries(m_ctiSEIPrimaries);
+  m_cEncLib.setCtiSEITransferFunction(m_ctiSEITransferFunction);
+  m_cEncLib.setCtiSEIMatrixCoefs(m_ctiSEIMatrixCoefs);
+  m_cEncLib.setCtiSEICrossComponentFlag(m_ctiSEICrossComponentFlag);
+  m_cEncLib.setCtiSEICrossComponentInferred(m_ctiSEICrossComponentInferred);
+  m_cEncLib.setCtiSEINbChromaLut(m_ctiSEINumberChromaLut);
+  m_cEncLib.setCtiSEIChromaOffset(m_ctiSEIChromaOffset);
+  for (int i = 0; i < MAX_NUM_COMPONENT; i++) 
+  {
+    m_cEncLib.setCtiSEILut(m_ctiSEILut[i], i);
+  }
+#endif
   // content colour volume SEI
   m_cEncLib.setCcvSEIEnabled                                     (m_ccvSEIEnabled);
   m_cEncLib.setCcvSEICancelFlag                                  (m_ccvSEICancelFlag);
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index cc4c25665..5e889d5d4 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -623,6 +623,11 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   SMultiValueInput<int>  cfg_DisplayPrimariesCode            (0, 50000, 6, 6, defaultPrimaryCodes,   sizeof(defaultPrimaryCodes  )/sizeof(int));
   SMultiValueInput<int>  cfg_DisplayWhitePointCode           (0, 50000, 2, 2, defaultWhitePointCode, sizeof(defaultWhitePointCode)/sizeof(int));
 
+#if JVET_V0108
+  SMultiValueInput<int16_t>  cfg_SEICTILut0(0, 128, 0, MAX_CTI_LUT_SIZE + 1);
+  SMultiValueInput<int16_t>  cfg_SEICTILut1(0, 128, 0, MAX_CTI_LUT_SIZE + 1);
+  SMultiValueInput<int16_t>  cfg_SEICTILut2(0, 128, 0, MAX_CTI_LUT_SIZE + 1);
+#endif
   SMultiValueInput<bool> cfg_timeCodeSeiTimeStampFlag        (0,  1, 0, MAX_TIMECODE_SEI_SETS);
   SMultiValueInput<bool> cfg_timeCodeSeiNumUnitFieldBasedFlag(0,  1, 0, MAX_TIMECODE_SEI_SETS);
   SMultiValueInput<int>  cfg_timeCodeSeiCountingType         (0,  6, 0, MAX_TIMECODE_SEI_SETS);
@@ -1413,6 +1418,23 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   ("SEIAVEAmbientIlluminance",                        m_aveSEIAmbientIlluminance,                      100000u, "Specifies the environmental illluminance of the ambient viewing environment in units of 1/10000 lux for the ambient viewing environment SEI message")
   ("SEIAVEAmbientLightX",                             m_aveSEIAmbientLightX,                            15635u, "Specifies the normalized x chromaticity coordinate of the environmental ambient light in the nominal viewing enviornment according to the CIE 1931 definition in units of 1/50000 lux for the ambient viewing enviornment SEI message")
   ("SEIAVEAmbientLightY",                             m_aveSEIAmbientLightY,                            16450u, "Specifies the normalized y chromaticity coordinate of the environmental ambient light in the nominal viewing enviornment according to the CIE 1931 definition in units of 1/50000 lux for the ambient viewing enviornment SEI message")
+#if JVET_V0108
+// colour tranform information SEI
+  ("SEICTIEnabled",                                   m_ctiSEIEnabled,                                   false, "Control generation of the Colour transform information SEI message")
+  ("SEICTIId",                                        m_ctiSEIId,                                           0u, "Id of the Colour transform information SEI message")
+  ("SEICTISignalInfoFlag",                            m_ctiSEISignalInfoFlag,                            false, "indicates if signal information are present in the Colour transform information SEI message")
+  ("SEICTIFullRangeFlag",                             m_ctiSEIFullRangeFlag,                             false, "specifies signal range after applying the Colour transform information SEI message")
+  ("SEICTIPrimaries",                                 m_ctiSEIPrimaries,                                    0u, "indicates the signal primaries after applying the Colour transform information SEI message")
+  ("SEICTITransferFunction",                          m_ctiSEITransferFunction,                             0u, "indicates the signal transfer function after applying the Colour transform information SEI message")
+  ("SEICTIMatrixCoefs",                               m_ctiSEIMatrixCoefs,                                  0u, "indicates the signal matrix coefficients after applying the Colour transform information SEI message")
+  ("SEICTICrossCompFlag",                             m_ctiSEICrossComponentFlag,                         true, "Specifies if cross-component transform mode is enabled in SEI CTI")
+  ("SEICTICrossCompInferred",                         m_ctiSEICrossComponentInferred,                     true, "Specifies if cross-component transform LUT is inferred in SEI CTI")
+  ("SEICTINbChromaLut",                               m_ctiSEINumberChromaLut,                              0u, "Specifies the number of chroma LUTs in SEI CTI")
+  ("SEICTIChromaOffset",                              m_ctiSEIChromaOffset,                                  0, "Specifies the chroma offset of SEI CTI")
+  ("SEICTILut0",                                      cfg_SEICTILut0,                           cfg_SEICTILut0, "slope values for component 0 of SEI CTI")
+  ("SEICTILut1",                                      cfg_SEICTILut1,                           cfg_SEICTILut1, "slope values for component 1 of SEI CTI")
+  ("SEICTILut2",                                      cfg_SEICTILut2,                           cfg_SEICTILut2, "slope values for component 2 of SEI CTI")
+#endif
 // content colour volume SEI
   ("SEICCVEnabled",                                   m_ccvSEIEnabled,                                   false, "Control generation of the Content Colour Volume SEI message")
   ("SEICCVCancelFlag",                                m_ccvSEICancelFlag,                                 true, "Specifies the persistence of any previous content colour volume SEI message in output order.")
@@ -2479,6 +2501,52 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
       m_masteringDisplay.whitePoint[idx] = uint16_t((cfg_DisplayWhitePointCode.values.size() > idx) ? cfg_DisplayWhitePointCode.values[idx] : 0);
     }
   }
+#if JVET_V0108
+  if (m_ctiSEIEnabled) 
+  {
+    CHECK(!m_ctiSEICrossComponentFlag && m_ctiSEICrossComponentInferred, "CTI CrossComponentFlag is 0, but CTI CrossComponentInferred is 1 (must be 0 for CrossComponentFlag 0)");
+    CHECK(!m_ctiSEICrossComponentFlag && !m_ctiSEICrossComponentInferred && !m_ctiSEINumberChromaLut, "For CTI CrossComponentFlag = 0, CTI NumberChromaLut needs to be specified (1 or 2) ");
+    CHECK(m_ctiSEICrossComponentFlag && !m_ctiSEICrossComponentInferred && !m_ctiSEINumberChromaLut, "For CTI CrossComponentFlag = 1 and CrossComponentInferred = 0, CTI NumberChromaLut needs to be specified (1 or 2) ");
+
+    CHECK(cfg_SEICTILut0.values.empty(), "SEI CTI (SEICTIEnabled) but no LUT0 specified");
+    m_ctiSEILut[0].presentFlag = true;
+    m_ctiSEILut[0].numLutValues = (int)cfg_SEICTILut0.values.size();
+    m_ctiSEILut[0].lutValues = cfg_SEICTILut0.values;
+
+    if (!m_ctiSEICrossComponentFlag || (m_ctiSEICrossComponentFlag && !m_ctiSEICrossComponentInferred)) 
+    {
+      CHECK(cfg_SEICTILut1.values.empty(), "SEI CTI LUT1 not specified");
+      m_ctiSEILut[1].presentFlag = true;
+      m_ctiSEILut[1].numLutValues = (int)cfg_SEICTILut1.values.size();
+      m_ctiSEILut[1].lutValues = cfg_SEICTILut1.values;
+
+      if (m_ctiSEINumberChromaLut == 1) 
+      { // Cb lut the same as Cr lut
+        m_ctiSEILut[2].presentFlag = true;
+        m_ctiSEILut[2].numLutValues = m_ctiSEILut[1].numLutValues;
+        m_ctiSEILut[2].lutValues = m_ctiSEILut[1].lutValues;
+      }
+      else if (m_ctiSEINumberChromaLut == 2) 
+      { // read from cfg
+        CHECK(cfg_SEICTILut2.values.empty(), "SEI CTI LUT2 not specified");
+        m_ctiSEILut[2].presentFlag = true;
+        m_ctiSEILut[2].numLutValues = (int)cfg_SEICTILut2.values.size();
+        m_ctiSEILut[2].lutValues = cfg_SEICTILut2.values;
+      }
+      else 
+      {
+        CHECK(m_ctiSEINumberChromaLut < 1 && m_ctiSEINumberChromaLut > 2, "Number of chroma LUTs is missing or out of range!");
+      }
+    }
+    //  check if lut size is power of 2
+    for (int idx = 0; idx < MAX_NUM_COMPONENT; idx++) 
+    {
+      int n = m_ctiSEILut[idx].numLutValues - 1;
+      CHECK(n > 0 && (n & (n - 1)) != 0, "Size of LUT minus 1 should be power of 2!");
+      CHECK(n > MAX_CTI_LUT_SIZE, "LUT size minus 1 is larger than MAX_CTI_LUT_SIZE (64)!");
+    }
+  }
+#endif
   if ( m_omniViewportSEIEnabled && !m_omniViewportSEICancelFlag )
   {
     CHECK (!( m_omniViewportSEICntMinus1 >= 0 && m_omniViewportSEICntMinus1 < 16 ), "SEIOmniViewportCntMinus1 must be in the range of 0 to 16");
@@ -3031,6 +3099,12 @@ bool EncAppCfg::xCheckParameter()
     if (m_updateCtrl > 0 && m_adpOption > 2) { m_adpOption -= 2; }
   }
 
+#if JVET_V0108
+  if (m_ctiSEIEnabled)
+  {
+    xConfirmPara(m_ctiSEINumberChromaLut < 0 || m_ctiSEINumberChromaLut > 2, "CTI number of chroma LUTs is out of range");
+  }
+#endif
   xConfirmPara( m_cbQpOffset < -12,   "Min. Chroma Cb QP Offset is -12" );
   xConfirmPara( m_cbQpOffset >  12,   "Max. Chroma Cb QP Offset is  12" );
   xConfirmPara( m_crQpOffset < -12,   "Min. Chroma Cr QP Offset is -12" );
@@ -4419,6 +4493,9 @@ void EncAppCfg::xPrintParameter()
     msg( VERBOSE, "RPR:%d ", 0 );
   }
   msg(VERBOSE, "TemporalFilter:%d ", m_gopBasedTemporalFilterEnabled);
+#if JVET_V0108
+  msg(VERBOSE, "SEI CTI:%d ", m_ctiSEIEnabled);
+#endif 
 #if EXTENSION_360_VIDEO
   m_ext360.outputConfigurationSummary();
 #endif
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index f4d024bef..53660e445 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -546,6 +546,21 @@ protected:
   uint32_t  m_aveSEIAmbientIlluminance;
   uint32_t  m_aveSEIAmbientLightX;
   uint32_t  m_aveSEIAmbientLightY;
+#if JVET_V0108
+  // colour tranform information sei
+  bool      m_ctiSEIEnabled;
+  uint32_t  m_ctiSEIId;
+  bool      m_ctiSEISignalInfoFlag;
+  bool      m_ctiSEIFullRangeFlag;
+  uint32_t  m_ctiSEIPrimaries;
+  uint32_t  m_ctiSEITransferFunction;
+  uint32_t  m_ctiSEIMatrixCoefs;
+  bool      m_ctiSEICrossComponentFlag;
+  bool      m_ctiSEICrossComponentInferred;
+  uint32_t  m_ctiSEINumberChromaLut;
+  int       m_ctiSEIChromaOffset;
+  LutModel  m_ctiSEILut[MAX_NUM_COMPONENT];
+#endif
   // content colour volume sei
   bool      m_ccvSEIEnabled;
   bool      m_ccvSEICancelFlag;
diff --git a/source/App/Parcat/parcat.cpp b/source/App/Parcat/parcat.cpp
index d23035edf..db9c5d5b9 100644
--- a/source/App/Parcat/parcat.cpp
+++ b/source/App/Parcat/parcat.cpp
@@ -214,6 +214,9 @@ std::vector<uint8_t> filter_segment(const std::vector<uint8_t> & v, int idx, int
   int off = 0;
   int cnt = 0;
   bool idr_found = false;
+#if JVET_V0108
+  bool is_pre_sei_before_idr = true;
+#endif
 
   std::vector<uint8_t> out;
   out.reserve(v.size());
@@ -269,6 +272,12 @@ std::vector<uint8_t> filter_segment(const std::vector<uint8_t> & v, int idx, int
       parameterSetManager.storePPS( pps, inp_nalu.getBitstream().getFifo() );
     }
 
+#if JVET_V0108
+    if (nalu_type == NAL_UNIT_CODED_SLICE_IDR_W_RADL || nalu_type == NAL_UNIT_CODED_SLICE_IDR_N_LP)
+    {
+      is_pre_sei_before_idr = false;
+    }
+#endif
     if(nalu_type == NAL_UNIT_CODED_SLICE_IDR_W_RADL || nalu_type == NAL_UNIT_CODED_SLICE_IDR_N_LP)
     {
       poc = 0;
@@ -329,9 +338,16 @@ std::vector<uint8_t> filter_segment(const std::vector<uint8_t> & v, int idx, int
       skip_next_sei = true;
       idr_found = true;
     }
+#if JVET_V0108
+    if ((idx > 1 && (nalu_type == NAL_UNIT_CODED_SLICE_IDR_W_RADL || nalu_type == NAL_UNIT_CODED_SLICE_IDR_N_LP))
+      || ((idx > 1 && !idr_found) && (nalu_type == NAL_UNIT_OPI || nalu_type == NAL_UNIT_DCI || nalu_type == NAL_UNIT_VPS || nalu_type == NAL_UNIT_SPS || nalu_type == NAL_UNIT_PPS || nalu_type == NAL_UNIT_PREFIX_APS || nalu_type == NAL_UNIT_SUFFIX_APS || nalu_type == NAL_UNIT_PH || nalu_type == NAL_UNIT_ACCESS_UNIT_DELIMITER))
+      || (nalu_type == NAL_UNIT_SUFFIX_SEI && skip_next_sei)
+      || (idx > 1 && nalu_type == NAL_UNIT_PREFIX_SEI && is_pre_sei_before_idr))
+#else
     if ((idx > 1 && (nalu_type == NAL_UNIT_CODED_SLICE_IDR_W_RADL || nalu_type == NAL_UNIT_CODED_SLICE_IDR_N_LP))
       || ((idx > 1 && !idr_found) && (nalu_type == NAL_UNIT_OPI || nalu_type == NAL_UNIT_DCI || nalu_type == NAL_UNIT_VPS || nalu_type == NAL_UNIT_SPS || nalu_type == NAL_UNIT_PPS || nalu_type == NAL_UNIT_PREFIX_APS || nalu_type == NAL_UNIT_SUFFIX_APS || nalu_type == NAL_UNIT_PH || nalu_type == NAL_UNIT_ACCESS_UNIT_DELIMITER || nalu_type == NAL_UNIT_PREFIX_SEI))
       || (nalu_type == NAL_UNIT_SUFFIX_SEI && skip_next_sei))
+#endif
     {
     }
     else
diff --git a/source/Lib/CommonLib/Buffer.cpp b/source/Lib/CommonLib/Buffer.cpp
index a53adaf77..ce468f30a 100644
--- a/source/Lib/CommonLib/Buffer.cpp
+++ b/source/Lib/CommonLib/Buffer.cpp
@@ -484,6 +484,72 @@ void AreaBuf<Pel>::scaleSignal(const int scale, const bool dir, const ClpRng& cl
   }
 }
 
+#if JVET_V0108
+template<>
+void AreaBuf<Pel>::applyLumaCTI(std::vector<Pel>& pLUTY)
+{
+  Pel* dst = buf;
+  Pel* src = buf;
+  for (unsigned y = 0; y < height; y++)
+  {
+    for (unsigned x = 0; x < width; x++)
+    {
+      dst[x] = pLUTY[src[x]];
+    }
+    dst += stride;
+    src += stride;
+  }
+}
+
+template<>
+void AreaBuf<Pel>::applyChromaCTI(Pel* bufY, int strideY, std::vector<Pel>& pLUTC, int bitDepth, ChromaFormat chrFormat, bool fwdMap)
+{
+  int range = 1 << bitDepth;
+  int offset = range / 2;
+  int sx = 1;
+  int sy = 1;
+  if (CHROMA_420 == chrFormat) 
+  {
+    sx = 2; 
+    sy = 2;
+  }
+  else if (CHROMA_422 == chrFormat)
+  {
+    sx = 2;
+  }
+
+  Pel* dst = buf;
+  Pel* src = buf;
+  if (fwdMap)
+  {
+    for (unsigned y = 0; y < height; y++)
+    {
+      for (unsigned x = 0; x < width; x++)
+      {
+        int pelY = bufY[sy * y * strideY + sx * x];
+        double scale = (double)pLUTC[pelY] / (double)(1 << CSCALE_FP_PREC);
+        dst[x] = Clip3((Pel)0, (Pel)(range - 1), (Pel)(offset + (double)(src[x] - offset) / scale + .5));
+      }
+      dst += stride;
+      src += stride;
+    }
+  }
+  else
+  {
+    for (unsigned y = 0; y < height; y++)
+    {
+      for (unsigned x = 0; x < width; x++)
+      {
+        int pelY = bufY[sy * y * strideY + sx * x];
+        int scal = pLUTC[pelY];
+        dst[x] = Clip3(0, range - 1, ((offset << CSCALE_FP_PREC) + (src[x] - offset) * scal + (1 << (CSCALE_FP_PREC - 1))) >> CSCALE_FP_PREC);
+      }
+      dst += stride;
+      src += stride;
+    }
+  }
+}
+#endif
 template<>
 void AreaBuf<Pel>::addAvg( const AreaBuf<const Pel> &other1, const AreaBuf<const Pel> &other2, const ClpRng& clpRng)
 {
diff --git a/source/Lib/CommonLib/Buffer.h b/source/Lib/CommonLib/Buffer.h
index 331c30950..ae3d874fd 100644
--- a/source/Lib/CommonLib/Buffer.h
+++ b/source/Lib/CommonLib/Buffer.h
@@ -137,6 +137,10 @@ struct AreaBuf : public Size
 
   void rspSignal            ( std::vector<Pel>& pLUT );
   void scaleSignal          ( const int scale, const bool dir , const ClpRng& clpRng);
+#if JVET_V0108
+  void applyLumaCTI(std::vector<Pel>& pLUTY);
+  void applyChromaCTI(Pel* bufY, int strideY, std::vector<Pel>& pLUTUV, int bitDepth, ChromaFormat chrFormat, bool fwdMap);
+#endif
   T    computeAvg           ( ) const;
 
         T& at( const int &x, const int &y )          { return buf[y * stride + x]; }
diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index dfbf09854..c45930607 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -478,6 +478,9 @@ static const int DELTA_QP_ACT[4] =                  { -5, 1, 3, 1 };
 static const int MAX_TSRC_RICE =                                  8;  ///<Maximum supported TSRC Rice parameter
 static const int MIN_TSRC_RICE =                                  1;  ///<Minimum supported TSRC Rice parameter
 #endif
+#if JVET_V0108
+static const int MAX_CTI_LUT_SIZE =                              64;  ///<Maximum colour transform LUT size for CTI SEI
+#endif
 // ====================================================================================================================
 // Macro functions
 // ====================================================================================================================
diff --git a/source/Lib/CommonLib/Picture.cpp b/source/Lib/CommonLib/Picture.cpp
index f11a3bfb4..eda607c94 100644
--- a/source/Lib/CommonLib/Picture.cpp
+++ b/source/Lib/CommonLib/Picture.cpp
@@ -64,6 +64,9 @@ Picture::Picture()
   precedingDRAP        = false;
 #if JVET_U0084_EDRAP
   edrapRapId           = -1;
+#endif
+#if JVET_V0108
+  m_colourTranfParams  = NULL;
 #endif
   nonReferencePictureFlag = false;
 
@@ -133,6 +136,9 @@ void Picture::destroy()
     delete[] m_spliceIdx;
     m_spliceIdx = NULL;
   }
+#if JVET_V0108
+  m_invColourTransfBuf = NULL;
+#endif
 }
 
 void Picture::createTempBuffers( const unsigned _maxCUSize )
@@ -1211,3 +1217,46 @@ void Picture::addPictureToHashMapForInter()
     }
   }
 }
+#if JVET_V0108
+void Picture::createColourTransfProcessor(bool firstPictureInSequence, SEIColourTransformApply* ctiCharacteristics, PelStorage* ctiBuf, int width, int height, ChromaFormat fmt, int bitDepth)
+{
+  m_colourTranfParams = ctiCharacteristics;
+  m_invColourTransfBuf = ctiBuf;
+  if (firstPictureInSequence)
+  {
+    // Create and initialize the Colour Transform Processor
+    m_colourTranfParams->create(width, height, fmt, bitDepth);
+
+    //Frame level PelStorage buffer created to apply the Colour Transform
+    m_invColourTransfBuf->create(UnitArea(chromaFormat, Area(0, 0, width, height)));
+  }
+}
+
+PelUnitBuf Picture::getDisplayBuf()
+{
+  int payloadType = 0;
+  std::list<SEI*>::iterator message;
+
+  for (message = SEIs.begin(); message != SEIs.end(); ++message)
+  {
+    payloadType = (*message)->payloadType();
+    if (payloadType == SEI::COLOUR_TRANSFORM_INFO)
+    {
+      // re-init parameters
+      *m_colourTranfParams->m_pColourTransfParams = *static_cast<SEIColourTransformInfo*>(*message);
+      //m_colourTranfParams->m_pColourTransfParams = static_cast<SEIColourTransformInfo*>(*message);
+      break;
+    }
+  }
+
+  m_invColourTransfBuf->copyFrom(getRecoBuf());
+
+  if (m_colourTranfParams->m_pColourTransfParams != NULL)
+  {
+    m_colourTranfParams->generateColourTransfLUTs();
+    m_colourTranfParams->inverseColourTransform(m_invColourTransfBuf);
+  }
+
+  return *m_invColourTransfBuf;
+}
+#endif
\ No newline at end of file
diff --git a/source/Lib/CommonLib/Picture.h b/source/Lib/CommonLib/Picture.h
index 309d7b923..2fd36db40 100644
--- a/source/Lib/CommonLib/Picture.h
+++ b/source/Lib/CommonLib/Picture.h
@@ -48,6 +48,9 @@
 #include "CodingStructure.h"
 #include "Hash.h"
 #include "MCTS.h"
+#if JVET_V0108
+#include "SEIColourTransform.h"
+#endif
 #include <deque>
 
 
@@ -68,6 +71,12 @@ struct Picture : public UnitArea
 
   void createTempBuffers( const unsigned _maxCUSize );
   void destroyTempBuffers();
+#if JVET_V0108
+  SEIColourTransformApply* m_colourTranfParams;
+  PelStorage*              m_invColourTransfBuf;
+  void              createColourTransfProcessor(bool firstPictureInSequence, SEIColourTransformApply* ctiCharacteristics, PelStorage* ctiBuf, int width, int height, ChromaFormat fmt, int bitDepth);
+  PelUnitBuf        getDisplayBuf();
+#endif
 
          PelBuf     getOrigBuf(const CompArea &blk);
   const CPelBuf     getOrigBuf(const CompArea &blk) const;
diff --git a/source/Lib/CommonLib/SEI.cpp b/source/Lib/CommonLib/SEI.cpp
index d0767ebb6..84aef2d0d 100644
--- a/source/Lib/CommonLib/SEI.cpp
+++ b/source/Lib/CommonLib/SEI.cpp
@@ -443,6 +443,9 @@ const char *SEI::getSEIMessageString(SEI::PayloadType payloadType)
     case SEI::CONTENT_LIGHT_LEVEL_INFO:             return "Content light level information";
     case SEI::AMBIENT_VIEWING_ENVIRONMENT:          return "Ambient viewing environment";
     case SEI::CONTENT_COLOUR_VOLUME:                return "Content colour volume";
+#if JVET_V0108
+    case SEI::COLOUR_TRANSFORM_INFO:                return "Colour transform information";
+#endif
     case SEI::EQUIRECTANGULAR_PROJECTION:           return "Equirectangular projection";
     case SEI::SPHERE_ROTATION:                      return "Sphere rotation";
     case SEI::REGION_WISE_PACKING:                  return "Region wise packing information";
diff --git a/source/Lib/CommonLib/SEI.h b/source/Lib/CommonLib/SEI.h
index 7c42f62e5..2d2eab68f 100644
--- a/source/Lib/CommonLib/SEI.h
+++ b/source/Lib/CommonLib/SEI.h
@@ -69,6 +69,9 @@ public:
     DECODED_PICTURE_HASH                 = 132,
     SCALABLE_NESTING                     = 133,
     MASTERING_DISPLAY_COLOUR_VOLUME      = 137,
+#if JVET_V0108
+    COLOUR_TRANSFORM_INFO                = 142,
+#endif
     DEPENDENT_RAP_INDICATION             = 145,
     EQUIRECTANGULAR_PROJECTION           = 150,
     SPHERE_ROTATION                      = 154,
@@ -834,6 +837,30 @@ public:
   uint16_t m_ambientLightY;
 };
 
+#if JVET_V0108
+class SEIColourTransformInfo : public SEI
+{
+public:
+  PayloadType payloadType() const { return COLOUR_TRANSFORM_INFO; }
+  SEIColourTransformInfo() { }
+
+  virtual ~SEIColourTransformInfo() { }
+
+  uint16_t m_id;
+  bool     m_signalInfoFlag;
+  bool     m_fullRangeFlag;
+  uint16_t m_primaries;
+  uint16_t m_transferFunction;
+  uint16_t m_matrixCoefs;
+  bool     m_crossComponentFlag;
+  bool     m_crossComponentInferred;
+  uint16_t m_numberChromaLutMinus1;
+  int      m_chromaOffset;
+  uint16_t m_bitdepth;
+  uint16_t m_log2NumberOfPointsPerLut;
+  LutModel m_lut[MAX_NUM_COMPONENT];
+};
+#endif
 class SEIContentColourVolume : public SEI
 {
 public:
diff --git a/source/Lib/CommonLib/SEIColourTransform.cpp b/source/Lib/CommonLib/SEIColourTransform.cpp
new file mode 100644
index 000000000..1e463b428
--- /dev/null
+++ b/source/Lib/CommonLib/SEIColourTransform.cpp
@@ -0,0 +1,200 @@
+/* The copyright in this software is being made available under the BSD
+ * License, included below. This software may be subject to other third party
+ * and contributor rights, including patent rights, and no such rights are
+ * granted under this license.
+ *
+ * Copyright (c) 2010-2021, ITU/ISO/IEC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
+ *    be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /** \file     SEIColourTransform.cpp
+     \brief    Colour transform SEI
+ */
+
+#include "SEIColourTransform.h"
+
+#if JVET_V0108
+#include "SEI.h"
+#include "Unit.h"
+#include "Buffer.h"
+
+SEIColourTransformApply::SEIColourTransformApply()
+  : m_width               (0)
+  , m_height              (0)
+  , m_chromaFormat        (NUM_CHROMA_FORMAT)
+  , m_bitDepth            (0)
+  , m_pColourTransfParams (NULL)
+{
+}
+
+void SEIColourTransformApply::create(uint32_t width, uint32_t height, ChromaFormat fmt, uint8_t bitDepth)
+{
+  m_width               = width;
+  m_height              = height;
+  m_chromaFormat        = fmt;
+  m_bitDepth            = bitDepth;
+  m_pColourTransfParams = new SEIColourTransformInfo;
+  m_lutSize             = 1 << m_bitDepth;
+  for (int i = 0; i < MAX_NUM_COMPONENT; i++)
+  {
+    m_mapLut[i].resize(m_lutSize, 0);
+  }
+}
+
+SEIColourTransformApply::~SEIColourTransformApply()
+{
+}
+
+void SEIColourTransformApply::inverseColourTransform(PelStorage* transformBuf)
+{
+  uint8_t   numComp = m_chromaFormat ? MAX_NUM_COMPONENT : 1;
+  PelBuf*   buffY   = &transformBuf->Y();
+  PelBuf*   buffCb  = &transformBuf->Cb();
+  PelBuf*   buffCr  = &transformBuf->Cr();
+
+  if (numComp == 3)
+  {
+    if (m_pColourTransfParams->m_crossComponentFlag) 
+    {
+      buffCb->applyChromaCTI(buffY->buf, buffY->stride, m_mapLut[COMPONENT_Cb], m_bitDepth, m_chromaFormat, false);
+      buffCr->applyChromaCTI(buffY->buf, buffY->stride, m_mapLut[COMPONENT_Cr], m_bitDepth, m_chromaFormat, false);
+    }
+    else 
+    {
+      buffCb->applyLumaCTI(m_mapLut[COMPONENT_Cb]); // apply direct mapping like in luma (no cross component mapping); same function, but different lut.
+      buffCr->applyLumaCTI(m_mapLut[COMPONENT_Cr]);
+    }
+  }
+  buffY->applyLumaCTI(m_mapLut[COMPONENT_Y]);
+}
+
+void SEIColourTransformApply::generateColourTransfLUTs()
+{
+  uint8_t numComp     = m_chromaFormat ? MAX_NUM_COMPONENT : 1;
+  int numPreLutPoints = 1 << m_pColourTransfParams->m_log2NumberOfPointsPerLut;
+  int dynamicRange    = 1 << m_bitDepth;
+  const uint32_t orgCW     = dynamicRange / numPreLutPoints;
+  int scalingPreLut   = 1 << ( 11 - (int)floorLog2(orgCW) ); // scale-up values from cfg file (chroma preLut is scaled down in cfg)
+
+  std::vector<Pel> pivotInPoints;
+  std::vector<Pel> pivotMappedPointsY(numPreLutPoints+1);
+  std::vector<Pel> pivotMappedPointsX(numPreLutPoints+1);
+
+  // Create Inverse Luma LUT - same for all possible combinations of ctiCrossComp and ctiChromaLutInferred
+
+  std::vector<int> invScale(numPreLutPoints);
+
+  pivotInPoints = m_pColourTransfParams->m_lut[0].lutValues;
+  pivotMappedPointsX[0] = pivotInPoints[0];
+  pivotMappedPointsY[0] = 0;
+  for (int j = 1; j < numPreLutPoints; j++) 
+  {
+    pivotMappedPointsX[j] = pivotMappedPointsX[j - 1] + pivotInPoints[j];
+    pivotMappedPointsY[j] = j * orgCW;
+  }
+
+  for (int i = 0; i < numPreLutPoints; i++)
+  {
+    invScale[i] = ((int32_t)m_pColourTransfParams->m_lut[0].lutValues[i + 1] * (1 << FP_PREC) + (1 << (floorLog2(orgCW) - 1))) >> floorLog2(orgCW);
+  }
+
+  for (int i = 0; i < dynamicRange; i++)
+  {
+    int idx = i / orgCW;
+    int tempVal = pivotMappedPointsX[idx] + ((invScale[idx] * (i - pivotMappedPointsY[idx]) + (1 << (FP_PREC - 1))) >> FP_PREC);
+    m_mapLut[0][i] = Clip3((Pel)0, (Pel)(dynamicRange - 1), (Pel)(tempVal));
+  }
+
+  //  calculate chroma LUTs
+  if (m_pColourTransfParams->m_crossComponentInferred == 0)
+  {    
+    for (int i = 1; i < numComp; i++) 
+    { // loop for U and V
+      if (m_pColourTransfParams->m_crossComponentFlag == 1)
+      {
+        // cross-component U and V LUT
+        for (int j = 0; j < dynamicRange; j++) 
+        {
+          int     idx     = j / orgCW;
+          int  slope = scalingPreLut * (m_pColourTransfParams->m_lut[i].lutValues[idx + 1] - m_pColourTransfParams->m_lut[i].lutValues[idx]);
+          m_mapLut[i][j] = scalingPreLut * m_pColourTransfParams->m_lut[i].lutValues[idx] + slope * (j - pivotMappedPointsY[idx]) / orgCW;
+        }
+      }
+      else
+      {
+        // intra-component Chroma (U and V) LUT
+        // initialize pivot points
+        pivotInPoints = m_pColourTransfParams->m_lut[i].lutValues;
+        pivotMappedPointsX[0] = pivotInPoints[0];
+        for (int j = 1; j <= numPreLutPoints; j++) 
+        {
+          pivotMappedPointsX[j] = pivotMappedPointsX[j-1] + pivotInPoints[j];
+        }
+
+        for (int i = 0; i < numPreLutPoints; i++)
+        {
+          invScale[i] = ((int32_t)m_pColourTransfParams->m_lut[0].lutValues[i + 1] * (1 << FP_PREC) + (1 << (floorLog2(orgCW) - 1))) >> floorLog2(orgCW);
+        }
+
+        for (int j = 0; j < dynamicRange; j++) 
+        {
+          int idx = j / orgCW;
+          int tempVal = pivotMappedPointsX[idx] + ((invScale[idx] * (j - pivotMappedPointsY[idx]) + (1 << (FP_PREC - 1))) >> FP_PREC);
+          m_mapLut[i][j] = Clip3((Pel)0, (Pel)(dynamicRange - 1), (Pel)(tempVal));
+        }
+      }
+    }
+  }
+  else
+  {
+    int chrOffset = m_pColourTransfParams->m_chromaOffset;
+
+    std::vector<int> chromaAdjHelpLUT (numPreLutPoints);
+    for (int i = 0; i < numPreLutPoints; i++)
+    {
+      chromaAdjHelpLUT[i] = (m_pColourTransfParams->m_lut[0].lutValues[i + 1] == 0) ? (1 << CSCALE_FP_PREC) : ((int32_t)((m_pColourTransfParams->m_lut[0].lutValues[i + 1] + chrOffset) * (1 << FP_PREC) / orgCW));
+    }
+
+    // generate smoothed chroma LUT as done by JVET-U0078
+    std::vector<int> interpLut(numPreLutPoints + 1);
+    for (int i = 1; i < numPreLutPoints; i++) 
+    {
+      interpLut[i] = (chromaAdjHelpLUT[i] + chromaAdjHelpLUT[i - 1] + 1) / 2;
+    }
+    interpLut[0]                = chromaAdjHelpLUT[0];
+    interpLut[numPreLutPoints]  = chromaAdjHelpLUT[numPreLutPoints - 1];
+
+    for (int i = 0; i < dynamicRange; i++)
+    {
+      int idx = i / orgCW;
+      int slope = interpLut[idx + 1] - interpLut[idx];
+      m_mapLut[1][i] = interpLut[idx] + slope * (i - pivotMappedPointsY[idx]) / orgCW;
+      m_mapLut[2][i] = m_mapLut[1][i];
+    }
+  }
+}
+#endif
diff --git a/source/Lib/CommonLib/SEIColourTransform.h b/source/Lib/CommonLib/SEIColourTransform.h
new file mode 100644
index 000000000..83bf8190d
--- /dev/null
+++ b/source/Lib/CommonLib/SEIColourTransform.h
@@ -0,0 +1,73 @@
+/* The copyright in this software is being made available under the BSD
+ * License, included below. This software may be subject to other third party
+ * and contributor rights, including patent rights, and no such rights are
+ * granted under this license.
+ *
+ * Copyright (c) 2010-2021, ITU/ISO/IEC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
+ *    be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /** \file     SEIColourTransform.h
+     \brief    Colour transform SEI
+ */
+
+#ifndef __SEIFILMCOLOURTRANFORMAPPLY__
+#define __SEIFILMCOLOURTRANFORMAPPLY__
+
+#include "CommonDef.h"
+//! \ingroup CommonLib
+//! \{
+
+#if JVET_V0108
+struct PelStorage;
+class SEIColourTransformInfo;
+
+class SEIColourTransformApply
+{
+private:
+  uint32_t                     m_width;
+  uint32_t                     m_height;
+  ChromaFormat                 m_chromaFormat;
+  uint8_t                      m_bitDepth;
+  uint32_t                     m_lutSize;
+  std::vector<Pel>             m_mapLut[MAX_NUM_COMPONENT];
+
+public:
+  SEIColourTransformInfo*      m_pColourTransfParams;
+
+public:
+  SEIColourTransformApply();
+  virtual ~SEIColourTransformApply();
+
+  void create                   (uint32_t width, uint32_t height, ChromaFormat fmt, uint8_t bitDepth);
+  void inverseColourTransform   (PelStorage* transformBuf);
+  void generateColourTransfLUTs ();
+
+};// END CLASS DEFINITION SEIColourTransformApply
+#endif
+
+#endif
\ No newline at end of file
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 4a3e499a5..1ab54fb37 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -74,6 +74,8 @@
 
 #define JVET_V0061_SEI                                    1 // JVET-V0061 Display orientation SEI message
 
+#define JVET_V0108                                        1 // JVET_V0108: Colour Transform Information SEI 
+
 //########### place macros to be be kept below this line ###############
 #define GDR_ENABLED   1
 
@@ -917,8 +919,14 @@ struct LFCUParam
   bool leftEdge;                         ///< indicates left edge
   bool topEdge;                          ///< indicates top edge
 };
-
-
+#if JVET_V0108
+struct LutModel
+{
+  bool             presentFlag = false;
+  int              numLutValues = 0;
+  std::vector<Pel> lutValues;
+};
+#endif
 
 struct PictureHash
 {
diff --git a/source/Lib/DecoderLib/DecLib.cpp b/source/Lib/DecoderLib/DecLib.cpp
index dc03f4252..56abfb32c 100644
--- a/source/Lib/DecoderLib/DecLib.cpp
+++ b/source/Lib/DecoderLib/DecLib.cpp
@@ -430,6 +430,10 @@ DecLib::DecLib()
   , m_prevPicPOC(MAX_INT)
   , m_prevTid0POC(0)
   , m_bFirstSliceInPicture(true)
+#if JVET_V0108
+  , m_firstPictureInSequence(true)
+  , m_colourTranfParams()
+#endif
   , m_firstSliceInBitstream(true)
   , m_isFirstAuInCvs( true )
   , m_prevSliceSkipped(false)
@@ -1684,6 +1688,10 @@ void DecLib::xActivateParameterSets( const InputNALUnit nalu )
     m_pcPic->finalInit(vps, *sps, *pps, picHeader, apss, lmcsAPS, scalinglistAPS);
 #else
     m_pcPic->finalInit( vps, *sps, *pps, &m_picHeader, apss, lmcsAPS, scalinglistAPS );
+#endif
+#if JVET_V0108
+    m_pcPic->createColourTransfProcessor(m_firstPictureInSequence, &m_colourTranfParams, &m_invColourTransfBuf, pps->getPicWidthInLumaSamples(), pps->getPicHeightInLumaSamples(), sps->getChromaFormatIdc(), sps->getBitDepth(CHANNEL_TYPE_LUMA));
+    m_firstPictureInSequence = false;
 #endif
     m_pcPic->createTempBuffers( m_pcPic->cs->pps->pcv->maxCUWidth );
     m_pcPic->cs->createCoeffs((bool)m_pcPic->cs->sps->getPLTMode());
diff --git a/source/Lib/DecoderLib/DecLib.h b/source/Lib/DecoderLib/DecLib.h
index 95137fbf6..4482f2a20 100644
--- a/source/Lib/DecoderLib/DecLib.h
+++ b/source/Lib/DecoderLib/DecLib.h
@@ -132,6 +132,11 @@ private:
   int                     m_prevPicPOC;
   int                     m_prevTid0POC;
   bool                    m_bFirstSliceInPicture;
+#if JVET_V0108
+  bool                    m_firstPictureInSequence;
+  SEIColourTransformApply m_colourTranfParams;
+  PelStorage              m_invColourTransfBuf;
+#endif
   bool                    m_firstSliceInSequence[MAX_VPS_LAYERS];
   bool                    m_firstSliceInBitstream;
   bool                    m_isFirstAuInCvs;
diff --git a/source/Lib/DecoderLib/SEIread.cpp b/source/Lib/DecoderLib/SEIread.cpp
index 67141f6cc..252d5b773 100644
--- a/source/Lib/DecoderLib/SEIread.cpp
+++ b/source/Lib/DecoderLib/SEIread.cpp
@@ -330,6 +330,12 @@ void SEIReader::xReadSEImessage(SEIMessages& seis, const NalUnitType nalUnitType
       sei = new SEIContentColourVolume;
       xParseSEIContentColourVolume((SEIContentColourVolume&)*sei, payloadSize, pDecodedMessageOutputStream);
       break;
+#if JVET_V0108
+    case SEI::COLOUR_TRANSFORM_INFO:
+      sei = new SEIColourTransformInfo;
+      xParseSEIColourTransformInfo((SEIColourTransformInfo&)*sei, payloadSize, pDecodedMessageOutputStream);
+      break;
+#endif
     default:
       for (uint32_t i = 0; i < payloadSize; i++)
       {
@@ -1373,6 +1379,86 @@ void SEIReader::xParseSEIAmbientViewingEnvironment(SEIAmbientViewingEnvironment&
   sei_read_code(pDecodedMessageOutputStream, 16, code, "ambient_light_y");     sei.m_ambientLightY = (uint16_t)code;
 }
 
+#if JVET_V0108
+void SEIReader::xParseSEIColourTransformInfo(SEIColourTransformInfo& sei, uint32_t payloadSize, std::ostream* pDecodedMessageOutputStream)
+{
+  uint32_t code;
+
+  output_sei_message_header(sei, pDecodedMessageOutputStream, payloadSize);
+
+  sei_read_uvlc(pDecodedMessageOutputStream, code, "colour_transform_id");               sei.m_id = code;
+  sei_read_flag(pDecodedMessageOutputStream, code, "colour_transform_cancel_flag");      bool colourTransformCancelFlag = code;
+
+  if (colourTransformCancelFlag == 0)
+  {
+    sei_read_flag(pDecodedMessageOutputStream, code, "colour_transform_persistence_flag");
+    sei_read_flag(pDecodedMessageOutputStream, code, "colour_transform_video_signal_info_present_flag"); sei.m_signalInfoFlag = code;
+
+    if (sei.m_signalInfoFlag)
+    {
+      sei_read_flag(pDecodedMessageOutputStream, code, "colour_transform_full_range_flag");        sei.m_fullRangeFlag = code;
+      sei_read_code(pDecodedMessageOutputStream, 8, code, "colour_transform_primaries");           sei.m_primaries = code;
+      sei_read_code(pDecodedMessageOutputStream, 8, code, "colour_transform_transfer_function");   sei.m_transferFunction = code;
+      sei_read_code(pDecodedMessageOutputStream, 8, code, "colour_transform_matrix_coefficients"); sei.m_matrixCoefs = code;
+    }
+    else
+    {
+      sei.m_fullRangeFlag = 0;
+      sei.m_primaries = 0;
+      sei.m_transferFunction = 0;
+      sei.m_matrixCoefs = 0;
+    }
+    sei_read_code(pDecodedMessageOutputStream, 4, code, "colour_transform_bit_depth_minus8");                       sei.m_bitdepth = 8+code;
+    sei_read_code(pDecodedMessageOutputStream, 3, code, "colour_transform_log2_number_of_points_per_lut_minus1");   sei.m_log2NumberOfPointsPerLut = code + 1;
+    int numLutValues = (1 << sei.m_log2NumberOfPointsPerLut) + 1;
+    sei_read_flag(pDecodedMessageOutputStream, code, "colour_transform_cross_comp_flag");                sei.m_crossComponentFlag = code;
+    sei.m_crossComponentInferred = 0;
+    if (sei.m_crossComponentFlag == true)
+    {
+      sei_read_flag(pDecodedMessageOutputStream, code, "colour_transform_cross_comp_inferred");          sei.m_crossComponentInferred = code;
+    }
+    for (int i = 0; i < MAX_NUM_COMPONENT; i++) {
+      sei.m_lut[i].lutValues.resize(numLutValues);
+    }
+
+    uint16_t lutCodingLength = 2 + sei.m_bitdepth - sei.m_log2NumberOfPointsPerLut;
+    for (uint32_t j = 0; j < numLutValues; j++)
+    {
+      sei_read_code(pDecodedMessageOutputStream, lutCodingLength, code, "colour_transform_lut[0][i]");
+      sei.m_lut[0].lutValues[j] = code;
+    }
+    sei.m_lut[0].numLutValues = numLutValues;
+    sei.m_lut[0].presentFlag = true;
+    if (sei.m_crossComponentFlag == 0 || sei.m_crossComponentInferred == 0)
+    {
+      sei_read_flag(pDecodedMessageOutputStream, code, "colour_transform_number_chroma_lut_minus1");      sei.m_numberChromaLutMinus1 = code;
+      for (uint32_t j = 0; j < numLutValues; j++)
+      {
+        sei_read_code(pDecodedMessageOutputStream, lutCodingLength, code, "colour_transform_lut[1][i]");
+        sei.m_lut[1].lutValues[j] = code;
+        sei.m_lut[2].lutValues[j] = code;
+      }
+      if (sei.m_numberChromaLutMinus1 == 1)
+      {
+        for (uint32_t j = 0; j < numLutValues; j++)
+        {
+          sei_read_code(pDecodedMessageOutputStream, lutCodingLength, code, "colour_transform_lut[2][i]");
+          sei.m_lut[2].lutValues[j] = code;
+        }
+      }
+      sei.m_lut[1].numLutValues = numLutValues;
+      sei.m_lut[2].numLutValues = numLutValues;
+      sei.m_lut[1].presentFlag = true;
+      sei.m_lut[2].presentFlag = true;
+    }
+    else
+    {
+      sei_read_code(pDecodedMessageOutputStream, lutCodingLength, code, "colour_transform_chroma_offset");
+      sei.m_chromaOffset = code;
+    }
+  }
+}
+#endif
 void SEIReader::xParseSEIContentColourVolume(SEIContentColourVolume& sei, uint32_t payloadSize, std::ostream *pDecodedMessageOutputStream)
 {
   int i;
diff --git a/source/Lib/DecoderLib/SEIread.h b/source/Lib/DecoderLib/SEIread.h
index 3efec6508..ba7c81427 100644
--- a/source/Lib/DecoderLib/SEIread.h
+++ b/source/Lib/DecoderLib/SEIread.h
@@ -100,6 +100,9 @@ protected:
 #if JVET_U0084_EDRAP
   void xParseSEIExtendedDrapIndication        (SEIExtendedDrapIndication& sei,        uint32_t payloadSize,                     std::ostream *pDecodedMessageOutputStream);
 #endif
+#if JVET_V0108
+  void xParseSEIColourTransformInfo           (SEIColourTransformInfo& sei, uint32_t payloadSize, std::ostream* pDecodedMessageOutputStream);
+#endif
 
   void sei_read_scode(std::ostream *pOS, uint32_t length, int& code, const char *pSymbolName);
   void sei_read_code(std::ostream *pOS, uint32_t uiLength, uint32_t& ruiCode, const char *pSymbolName);
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index b5268b8db..dbc90ab5b 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -660,6 +660,21 @@ protected:
   uint32_t  m_aveSEIAmbientIlluminance;
   uint16_t  m_aveSEIAmbientLightX;
   uint16_t  m_aveSEIAmbientLightY;
+#if JVET_V0108
+  // colour tranform information sei
+  bool      m_ctiSEIEnabled;
+  uint32_t  m_ctiSEIId;
+  bool      m_ctiSEISignalInfoFlag;
+  bool      m_ctiSEIFullRangeFlag;
+  uint32_t  m_ctiSEIPrimaries;
+  uint32_t  m_ctiSEITransferFunction;
+  uint32_t  m_ctiSEIMatrixCoefs;
+  bool      m_ctiSEICrossComponentFlag;
+  bool      m_ctiSEICrossComponentInferred;
+  uint32_t  m_ctiSEINumberChromaLut;
+  int       m_ctiSEIChromaOffset;
+  LutModel  m_ctiSEILut[MAX_NUM_COMPONENT];
+#endif
 // ccv sei
   bool      m_ccvSEIEnabled;
   bool      m_ccvSEICancelFlag;
@@ -1854,6 +1869,34 @@ public:
   uint16_t getAmbientViewingEnvironmentSEIAmbientLightX()            { return m_aveSEIAmbientLightX; }
   void  setAmbientViewingEnvironmentSEIAmbientLightY( uint16_t v )   { m_aveSEIAmbientLightY = v; }
   uint16_t getAmbientViewingEnvironmentSEIAmbientLightY()            { return m_aveSEIAmbientLightY; }
+#if JVET_V0108
+  // colour tranform information sei
+  void      setCtiSEIEnabled(bool b) { m_ctiSEIEnabled = b; }
+  bool      getCtiSEIEnabled() { return m_ctiSEIEnabled; }
+  void      setCtiSEIId(uint32_t b) { m_ctiSEIId = b; }
+  uint32_t  getCtiSEIId() { return m_ctiSEIId; }
+  void      setCtiSEISignalInfoFlag(bool b) { m_ctiSEISignalInfoFlag = b; }
+  bool      getCtiSEISignalInfoFlag() { return m_ctiSEISignalInfoFlag; }
+  void      setCtiSEIFullRangeFlag(bool b) { m_ctiSEIFullRangeFlag = b; }
+  bool      getCtiSEIFullRangeFlag() { return m_ctiSEIFullRangeFlag; }
+  uint32_t  getCtiSEIPrimaries() { return m_ctiSEIPrimaries; }
+  void      setCtiSEIPrimaries(uint32_t v) { m_ctiSEIPrimaries = v; }
+  uint32_t  getCtiSEITransferFunction() { return m_ctiSEITransferFunction; }
+  void      setCtiSEITransferFunction(uint32_t v) { m_ctiSEITransferFunction = v; }
+  uint32_t  getCtiSEIMatrixCoefs() { return m_ctiSEIMatrixCoefs; }
+  void      setCtiSEIMatrixCoefs(uint32_t v) { m_ctiSEIMatrixCoefs = v; }
+  void      setCtiSEICrossComponentFlag(bool b) { m_ctiSEICrossComponentFlag = b; }
+  bool      getCtiSEICrossComponentFlag() { return m_ctiSEICrossComponentFlag; }
+  void      setCtiSEICrossComponentInferred(bool b) { m_ctiSEICrossComponentInferred = b; }
+  bool      getCtiSEICrossComponentInferred() { return m_ctiSEICrossComponentInferred; }
+  uint32_t  getCtiSEINbChromaLut() { return m_ctiSEINumberChromaLut; }
+  void      setCtiSEINbChromaLut(uint32_t v) { m_ctiSEINumberChromaLut = v; }
+  int       getCtiSEIChromaOffset() { return m_ctiSEIChromaOffset; }
+  void      setCtiSEIChromaOffset(int v) { m_ctiSEIChromaOffset = v; }
+  LutModel  getCtiSEILut(int idx) { return m_ctiSEILut[idx]; }
+  void      setCtiSEILut(LutModel& cmp, int idx) { m_ctiSEILut[idx] = cmp; }
+  LutModel* getCtiSEILuts() { return m_ctiSEILut; }
+#endif
   // ccv SEI
   void     setCcvSEIEnabled(bool b)                                  { m_ccvSEIEnabled = b; }
   bool     getCcvSEIEnabled()                                        { return m_ccvSEIEnabled; }
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index cf74366bf..81932147c 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -806,6 +806,15 @@ void EncGOP::xCreateIRAPLeadingSEIMessages (SEIMessages& seiMessages, const SPS
     seiMessages.push_back(seiDepthRepresentationInfo);
   }
 #endif
+#if JVET_V0108
+  // colour transform information
+  if (m_pcCfg->getCtiSEIEnabled())
+  {
+    SEIColourTransformInfo* seiCTI = new SEIColourTransformInfo;
+    m_seiEncoder.initSEIColourTransformInfo(seiCTI);
+    seiMessages.push_back(seiCTI);
+  }
+#endif
 }
 
 void EncGOP::xCreatePerPictureSEIMessages (int picInGOP, SEIMessages& seiMessages, SEIMessages& nestedSeiMessages, Slice *slice)
diff --git a/source/Lib/EncoderLib/SEIEncoder.cpp b/source/Lib/EncoderLib/SEIEncoder.cpp
index a88724830..da179ba69 100644
--- a/source/Lib/EncoderLib/SEIEncoder.cpp
+++ b/source/Lib/EncoderLib/SEIEncoder.cpp
@@ -998,6 +998,33 @@ void SEIEncoder::initSEIDepthRepresentationInfo(SEIDepthRepresentationInfo *sei)
 }
 #endif
 
+#if JVET_V0108
+void SEIEncoder::initSEIColourTransformInfo(SEIColourTransformInfo* seiCTI)
+{
+  CHECK(!(m_isInitialized), "Unspecified error");
+  CHECK(!(seiCTI != NULL), "Unspecified error");
+
+  //  Set SEI message parameters read from command line options
+  seiCTI->m_id = m_pcCfg->getCtiSEIId();
+  seiCTI->m_signalInfoFlag = m_pcCfg->getCtiSEISignalInfoFlag();
+  seiCTI->m_fullRangeFlag = m_pcCfg->getCtiSEIFullRangeFlag();
+  seiCTI->m_primaries = m_pcCfg->getCtiSEIPrimaries();
+  seiCTI->m_transferFunction = m_pcCfg->getCtiSEITransferFunction();
+  seiCTI->m_matrixCoefs = m_pcCfg->getCtiSEIMatrixCoefs();
+  seiCTI->m_crossComponentFlag = m_pcCfg->getCtiSEICrossComponentFlag();
+  seiCTI->m_crossComponentInferred = m_pcCfg->getCtiSEICrossComponentInferred();
+  seiCTI->m_numberChromaLutMinus1 = m_pcCfg->getCtiSEINbChromaLut() - 1;
+  seiCTI->m_chromaOffset = m_pcCfg->getCtiSEIChromaOffset();
+
+  seiCTI->m_bitdepth = m_pcCfg->getBitDepth(CHANNEL_TYPE_LUMA);
+
+  for (int i = 0; i < MAX_NUM_COMPONENT; i++) {
+    seiCTI->m_lut[i] = m_pcCfg->getCtiSEILut(i);
+  }
+  seiCTI->m_log2NumberOfPointsPerLut = floorLog2(seiCTI->m_lut[0].numLutValues - 1);
+}
+#endif
+
 void SEIEncoder::initSEISubpictureLevelInfo(SEISubpicureLevelInfo *sei, const SPS *sps)
 {
   const EncCfgParam::CfgSEISubpictureLevel &cfgSubPicLevel = m_pcCfg->getSubpicureLevelInfoSEICfg();
diff --git a/source/Lib/EncoderLib/SEIEncoder.h b/source/Lib/EncoderLib/SEIEncoder.h
index 6534b2a55..6b89bc326 100644
--- a/source/Lib/EncoderLib/SEIEncoder.h
+++ b/source/Lib/EncoderLib/SEIEncoder.h
@@ -96,6 +96,9 @@ public:
   void initSEIDepthRepresentationInfo(SEIDepthRepresentationInfo *sei);
 #endif
   bool initSEIAnnotatedRegions(SEIAnnotatedRegions *sei, int currPOC);
+#if JVET_V0108
+  void initSEIColourTransformInfo(SEIColourTransformInfo* sei);
+#endif
   void readAnnotatedRegionSEI(std::istream &fic, SEIAnnotatedRegions *seiAnnoRegion, bool &failed);
 private:
   EncCfg* m_pcCfg;
diff --git a/source/Lib/EncoderLib/SEIwrite.cpp b/source/Lib/EncoderLib/SEIwrite.cpp
index c0e834c4c..2c8dfd120 100644
--- a/source/Lib/EncoderLib/SEIwrite.cpp
+++ b/source/Lib/EncoderLib/SEIwrite.cpp
@@ -145,6 +145,11 @@ void SEIWriter::xWriteSEIpayloadData(OutputBitstream &bs, const SEI& sei, HRD &h
   case SEI::CONTENT_COLOUR_VOLUME:
     xWriteSEIContentColourVolume(*static_cast<const SEIContentColourVolume*>(&sei));
     break;
+#if JVET_V0108
+  case SEI::COLOUR_TRANSFORM_INFO:
+    xWriteSEIColourTransformInfo(*static_cast<const SEIColourTransformInfo*>(&sei));
+    break;
+#endif
   case SEI::SUBPICTURE_LEVEL_INFO:
     xWriteSEISubpictureLevelInfo(*static_cast<const SEISubpicureLevelInfo*>(&sei));
     break;
@@ -1287,4 +1292,60 @@ void SEIWriter::xWriteSEIContentColourVolume(const SEIContentColourVolume &sei)
   }
 }
 
+#if JVET_V0108
+void SEIWriter::xWriteSEIColourTransformInfo(const SEIColourTransformInfo& sei)
+{
+  bool colourTransformCancelFlag = 0;
+  bool colourTransformPersistenceFlag = 0;
+
+  WRITE_UVLC(sei.m_id, "colour_transform_id");
+  WRITE_FLAG(colourTransformCancelFlag, "colour_transform_cancel_flag");
+
+  if (colourTransformCancelFlag == 0)
+  {
+    WRITE_FLAG(colourTransformPersistenceFlag, "colour_transform_persistence_flag");
+    WRITE_FLAG(sei.m_signalInfoFlag, "colour_transform_video_signal_info_present_flag");
+
+    if (sei.m_signalInfoFlag)
+    {
+      WRITE_FLAG(sei.m_fullRangeFlag, "colour_transform_full_range_flag");
+      WRITE_CODE(sei.m_primaries, 8, "colour_transform_primaries");
+      WRITE_CODE(sei.m_transferFunction, 8, "colour_transform_transfer_function");
+      WRITE_CODE(sei.m_matrixCoefs, 8, "colour_transform_matrix_coefficients");
+    }
+    WRITE_CODE(sei.m_bitdepth - 8, 4, "colour_transform_bit_depth_minus8"); 
+    WRITE_CODE(sei.m_log2NumberOfPointsPerLut - 1, 3, "colour_transform_log2_number_of_points_per_lut_minus1");
+    WRITE_FLAG(sei.m_crossComponentFlag, "colour_transform_cross_comp_flag");
+    if (sei.m_crossComponentFlag)
+    {
+      WRITE_FLAG(sei.m_crossComponentInferred, "colour_transform_cross_comp_inferred");
+    }
+
+    uint16_t lutCodingLength = 2 + sei.m_bitdepth - sei.m_log2NumberOfPointsPerLut;
+    for (uint32_t j = 0; j < sei.m_lut[0].numLutValues; j++)
+    {
+      WRITE_CODE(sei.m_lut[0].lutValues[j], lutCodingLength, "colour_transform_lut[0][i]");
+    }
+    if (sei.m_crossComponentFlag == 0 || sei.m_crossComponentInferred == 0)
+    {
+      WRITE_FLAG(sei.m_numberChromaLutMinus1, "colour_transform_number_chroma_lut_minus1");
+      for (uint32_t j = 0; j < sei.m_lut[1].numLutValues; j++)
+      {
+        WRITE_CODE(sei.m_lut[1].lutValues[j], lutCodingLength, "colour_transform_lut[1][i]");
+      }
+      if (sei.m_numberChromaLutMinus1 == 1)
+      {
+        for (uint32_t j = 0; j < sei.m_lut[2].numLutValues; j++)
+        {
+          WRITE_CODE(sei.m_lut[2].lutValues[j], lutCodingLength, "colour_transform_lut[2][i]");
+        }
+      }
+    }
+    else
+    {
+      WRITE_CODE(sei.m_chromaOffset, lutCodingLength, "colour_transform_chroma_offset");
+    }
+  }
+}
+#endif
 //! \}
diff --git a/source/Lib/EncoderLib/SEIwrite.h b/source/Lib/EncoderLib/SEIwrite.h
index f4cf84af5..7f6c9ba9b 100644
--- a/source/Lib/EncoderLib/SEIwrite.h
+++ b/source/Lib/EncoderLib/SEIwrite.h
@@ -92,6 +92,9 @@ protected:
   void xWriteSEIContentLightLevelInfo(const SEIContentLightLevelInfo& sei);
   void xWriteSEIAmbientViewingEnvironment(const SEIAmbientViewingEnvironment& sei);
   void xWriteSEIContentColourVolume(const SEIContentColourVolume &sei);
+#if JVET_V0108
+  void xWriteSEIColourTransformInfo(const SEIColourTransformInfo& sei);
+#endif
   void xWriteSEIAnnotatedRegions                  (const SEIAnnotatedRegions& sei);
   void xWriteSEIpayloadData(OutputBitstream &bs, const SEI& sei, HRD &hrd, const uint32_t temporalId);
   void xWriteByteAlign();
-- 
GitLab