diff --git a/cfg/examples_SEI_CTI/readMe.txt b/cfg/examples_SEI_CTI/readMe.txt
new file mode 100644
index 0000000000000000000000000000000000000000..04fb12dbe6d84e3e0a5b28d296ade17b583db3cc
--- /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 0000000000000000000000000000000000000000..8ac807a9a52d4167ccd4b233cc599662e6c585ea
--- /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 0000000000000000000000000000000000000000..715d8756ed5ac71f863711ca6fe3dfdc65a4008a
--- /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 0000000000000000000000000000000000000000..08efe740602e3424fd938576b07349fccd306db8
--- /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 e6c8246acfd7eea828820e180a57d5e82c8dd448..aab013ac12e67f24ff85fc6240de84005bc30a34 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 023ab15943aa3ae220d7d33f815e02d439758c4a..5886ea266faf1cb1a97845b30af0e4a5b2b74908 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 d3573294b6a82715728376ca35ebcf09307625ac..2b3580ab4f1b12fc9ab9b639eaac8af2f918ad0f 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 0a126cb13e1a7ff023dd7958951a24f4fc51fee7..c4ad90ebf6e0f622a4910c6c241af4d4c7ff7a79 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 bdf0db7a97ff1a75c9023319665ff332e4c24717..157c18cfccb11698d662b3e581ffba64c6f05683 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 e66809b6daebd8106f3f64572d419ed3fa0fbaa8..30c4d11199b9da5744d2fd75f724fccc5cc397b5 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 cc4c25665cb61f99c859e153d9d9bad9cce0461c..5e889d5d4adb4d95ec22df27cded8caca38018ff 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 f4d024bef6856dfa21c8d43948703bb946605bb4..53660e4450605e5705db5d20f08f050ecfa24b2b 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 d23035edf72cdc037ee2efff0c75be4d4ebab99d..db9c5d5b9ab04a3ed37b396e987ee128bd7506d2 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 a53adaf77c0e40c55a20f697099cfe6e5cb7c6e9..ce468f30a3e6770af13411b84a528237f63377be 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 331c30950e58bfb5c41f3321fda77416069449d6..ae3d874fd59b1049a0d1ba10d73c2048427bbc5e 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 dfbf09854ff6cfb8b3d51c17eaeac4b96663d3a4..c4593060710075b309993657f6a8b64aa1f97847 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 f11a3bfb4d8f79820c418a26cfab359fc6a41bc5..eda607c943f865bd7ee8127aaacf5a6b9c912b3e 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 309d7b9230183ee86431fa5e87eb28ca1c35b0a3..2fd36db4012a6e4d832eaed2e5e424eb98927b3c 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 d0767ebb64714ed13ee6c6eae781e2fc6444d220..84aef2d0d06cf48d02a9fd2e5db239e27fae5613 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 7c42f62e5fc024ca65315910a015c2fb350cc4ca..2d2eab68f0d4c810b0ae0b1899069c017b9589df 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 0000000000000000000000000000000000000000..1e463b4281a4bb2b9a390dfa622a0cea45b8c68d
--- /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 0000000000000000000000000000000000000000..83bf8190d5e4eaf1db93360152e9a608c3df114b
--- /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 4a3e499a5e68e7f8c8c535ffd983ccee73805e6b..1ab54fb3709c1707222df04cb33b99e8365f704a 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 dc03f42529129de836e897899d9e25fe3b8b5ad7..56abfb32cd18d42272cd2ce6547dea597b4e48e0 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 95137fbf6696de55044efa51c2116429739240ff..4482f2a20896120c4bbbbedc1f4f08bee934e3ae 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 67141f6cc5a5f0473b20d5c8c6b6f3637021c054..252d5b7736a2e306c25f29ca55a71e2498933412 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 3efec65081bfc21cf669e908b9312a11a090b026..ba7c814271f7fcdf237487067af3b47228a86e9b 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 b5268b8db5fe7360d4ba03b1d3130e0e9323850e..dbc90ab5b0949cbcdb7963d6417e80ca3ff1d800 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 cf74366bf4cb270d242543c94f357b38e6a6b14d..81932147c3b5c2c880199ba262d53a672d26cf08 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 a887248307d122bac9e3f1a217a9316c9552840a..da179ba69cb8346499aa8f83f219988cc873f41c 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 6534b2a554b38a5dd53eeb6049ae2e0cac1c4635..6b89bc32667206300de47f7455e63d1190c9efca 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 c0e834c4c11eab3b7a804da699eab8949be9f22d..2c8dfd120ca75a11d2af3937f73877977306d21c 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 f4cf84af5ea4659f6dfe9f7b5c6f2a772a36dd2e..7f6c9ba9bc733cb67042f2f354ae2f3cf2d4d540 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();