From fffeeda90244afc702e99cd0442eed7dc3566178 Mon Sep 17 00:00:00 2001
From: Frank Bossen <fbossen@gmail.com>
Date: Sat, 13 May 2023 19:36:20 -0400
Subject: [PATCH] Fix #1598: signal chroma location type when input is y4m

---
 doc/software-manual.tex                     |  10 +-
 source/App/DecoderApp/DecApp.cpp            |   3 +-
 source/App/EncoderApp/EncApp.cpp            |   6 +-
 source/App/EncoderApp/EncAppCfg.cpp         |  30 ++++-
 source/App/EncoderApp/EncAppCfg.h           |   6 +-
 source/Lib/CommonLib/SequenceParameterSet.h |  24 ++--
 source/Lib/CommonLib/TypeDef.h              |  12 ++
 source/Lib/DecoderLib/VLCReader.cpp         |  13 ++-
 source/Lib/EncoderLib/EncCfg.h              |  18 +--
 source/Lib/EncoderLib/VLCWriter.cpp         |   6 +-
 source/Lib/Utilities/VideoIOYuv.cpp         | 118 ++++++++++++--------
 source/Lib/Utilities/VideoIOYuv.h           |   6 +-
 12 files changed, 157 insertions(+), 95 deletions(-)

diff --git a/doc/software-manual.tex b/doc/software-manual.tex
index 1b2fcd8f2..aa92e1c28 100644
--- a/doc/software-manual.tex
+++ b/doc/software-manual.tex
@@ -3800,16 +3800,20 @@ Specifies the value of general_non_projected_constraint_flag
 \\
 \Option{ChromaLocInfoPresent} &
 \Default{false} &
-Signals whether chroma_sample_loc_type_top_field and chroma_sample_loc_type_bottom_field are present.
+Signals whether chroma_sample_loc_type_top_field, chroma_sample_loc_type_bottom_field and chroma_sample_loc_type are present.
 \\
 \Option{ChromaSampleLocTypeTopField} &
-\Default{0} &
+\Default{6 (Unspecified)} &
 Specifies the location of chroma samples for top field.
 \\
 \Option{ChromaSampleLocTypeBottomField} &
-\Default{0} &
+\Default{6 (Unspecified)} &
 Specifies the location of chroma samples for bottom field.
 \\
+\Option{ChromaSampleLocType} &
+\Default{6 (Unspecified)} &
+Specifies the location of chroma samples for frame.
+\\
 \end{OptionTableNoShorthand}
 
 
diff --git a/source/App/DecoderApp/DecApp.cpp b/source/App/DecoderApp/DecApp.cpp
index c00f4c8a7..46d77124c 100644
--- a/source/App/DecoderApp/DecApp.cpp
+++ b/source/App/DecoderApp/DecApp.cpp
@@ -487,7 +487,8 @@ uint32_t DecApp::decode()
               const int picWidth = pps->getPicWidthInLumaSamples() - (confWindow.getWindowLeftOffset() + confWindow.getWindowRightOffset()) * sx;
               const int picHeight = pps->getPicHeightInLumaSamples() - (confWindow.getWindowTopOffset() + confWindow.getWindowBottomOffset()) * sy;
               m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].setOutputY4mInfo(
-                picWidth, picHeight, frameRate, layerOutputBitDepth[ChannelType::LUMA], sps->getChromaFormatIdc());
+                picWidth, picHeight, frameRate, layerOutputBitDepth[ChannelType::LUMA], sps->getChromaFormatIdc(),
+                sps->getVuiParameters()->getChromaSampleLocType());
             }
             m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].open(reconFileName, true, layerOutputBitDepth,
                                                            layerOutputBitDepth, bitDepths);   // write mode
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 1019f1e77..a8ab6c746 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -1490,9 +1490,9 @@ void EncApp::xCreateLib( std::list<PelUnitBuf*>& recBufList, const int layerId )
     {
       const auto sx = SPS::getWinUnitX(m_chromaFormatIdc);
       const auto sy = SPS::getWinUnitY(m_chromaFormatIdc);
-      m_cVideoIOYuvReconFile.setOutputY4mInfo(m_sourceWidth - (m_confWinLeft + m_confWinRight) * sx,
-                                              m_sourceHeight - (m_confWinTop + m_confWinBottom) * sy, m_frameRate,
-                                              m_internalBitDepth[ChannelType::LUMA], m_chromaFormatIdc);
+      m_cVideoIOYuvReconFile.setOutputY4mInfo(
+        m_sourceWidth - (m_confWinLeft + m_confWinRight) * sx, m_sourceHeight - (m_confWinTop + m_confWinBottom) * sy,
+        m_frameRate, m_internalBitDepth[ChannelType::LUMA], m_chromaFormatIdc, m_chromaSampleLocType);
     }
     m_cVideoIOYuvReconFile.open( reconFileName, true, m_outputBitDepth, m_outputBitDepth, m_internalBitDepth );  // write mode
   }
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index fa33f47be..12d84e3af 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -772,6 +772,10 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
 
   bool sdr = false;
 
+  int chromaSampleLocType;
+  int chromaSampleLocTypeTopField;
+  int chromaSampleLocTypeBottomField;
+
   // clang-format off
   po::Options opts;
   opts.addOptions()
@@ -1333,9 +1337,9 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   ("NonPackedSourceConstraintFlag",                   m_nonPackedConstraintFlag,                        false, "Indicate that source does not contain frame packing")
   ("NonProjectedConstraintFlag",                      m_nonProjectedConstraintFlag,                     false, "Indicate that the bitstream contains projection SEI messages")
   ("ChromaLocInfoPresent",                            m_chromaLocInfoPresentFlag,                       false, "Signals whether chroma_sample_loc_type_top_field and chroma_sample_loc_type_bottom_field are present")
-  ("ChromaSampleLocTypeTopField",                     m_chromaSampleLocTypeTopField,                        0, "Specifies the location of chroma samples for top field")
-  ("ChromaSampleLocTypeBottomField",                  m_chromaSampleLocTypeBottomField,                     0, "Specifies the location of chroma samples for bottom field")
-  ("ChromaSampleLocType",                             m_chromaSampleLocType,                                0, "Specifies the location of chroma samples for progressive content")
+  ("ChromaSampleLocTypeTopField",                     chromaSampleLocTypeTopField,    static_cast<int>(Chroma420LocType::UNSPECIFIED), "Specifies the location of chroma samples for top field")
+  ("ChromaSampleLocTypeBottomField",                  chromaSampleLocTypeBottomField, static_cast<int>(Chroma420LocType::UNSPECIFIED), "Specifies the location of chroma samples for bottom field")
+  ("ChromaSampleLocType",                             chromaSampleLocType,            static_cast<int>(Chroma420LocType::UNSPECIFIED), "Specifies the location of chroma samples for progressive content")
   ("OverscanInfoPresent",                             m_overscanInfoPresentFlag,                        false, "Indicates whether conformant decoded pictures are suitable for display using overscan\n")
   ("OverscanAppropriate",                             m_overscanAppropriateFlag,                        false, "Indicates whether conformant decoded pictures are suitable for display using overscan\n")
   ("VideoFullRange",                                  m_videoFullRangeFlag,                             false, "Indicates the black level and range of luma and chroma signals");
@@ -2470,6 +2474,11 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
     }
   }
 
+  // TODO: check whether values are within valid range
+  m_chromaSampleLocType            = static_cast<Chroma420LocType>(chromaSampleLocType);
+  m_chromaSampleLocTypeTopField    = static_cast<Chroma420LocType>(chromaSampleLocTypeTopField);
+  m_chromaSampleLocTypeBottomField = static_cast<Chroma420LocType>(chromaSampleLocTypeBottomField);
+
   if (isY4mFileExt(m_inputFileName))
   {
     int          width          = 0;
@@ -2477,10 +2486,13 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
     Fraction     frameRate;
     int          inputBitDepth  = 0;
     ChromaFormat chromaFormat = ChromaFormat::_420;
+    Chroma420LocType locType        = Chroma420LocType::UNSPECIFIED;
+
     VideoIOYuv   inputFile;
-    inputFile.parseY4mFileHeader(m_inputFileName, width, height, frameRate, inputBitDepth, chromaFormat);
+    inputFile.parseY4mFileHeader(m_inputFileName, width, height, frameRate, inputBitDepth, chromaFormat, locType);
     if (width != m_sourceWidth || height != m_sourceHeight || frameRate != m_frameRate
-        || inputBitDepth != m_inputBitDepth[ChannelType::LUMA] || chromaFormat != m_chromaFormatIdc)
+        || inputBitDepth != m_inputBitDepth[ChannelType::LUMA] || chromaFormat != m_chromaFormatIdc
+        || locType != m_chromaSampleLocType)
     {
       msg(WARNING, "\nWarning: Y4M file info is different from input setting. Using the info from Y4M file\n");
       m_sourceWidth            = width;
@@ -2489,6 +2501,14 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
       m_inputBitDepth.fill(inputBitDepth);
       m_chromaFormatIdc        = chromaFormat;
       m_msbExtendedBitDepth    = m_inputBitDepth;
+      m_chromaSampleLocType    = locType;
+    }
+
+    m_progressiveSourceFlag = true;   // TODO: update when processing of interlaced y4m files is supported
+    if (m_chromaFormatIdc == ChromaFormat::_420 && m_chromaSampleLocType != Chroma420LocType::UNSPECIFIED)
+    {
+      m_chromaLocInfoPresentFlag = true;
+      m_vuiParametersPresentFlag = true;
     }
   }
 
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index 4985ac7d4..712830e72 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -877,9 +877,9 @@ protected:
   bool      m_progressiveSourceFlag;                          ///< Indicates if the content is progressive
   bool      m_interlacedSourceFlag;                           ///< Indicates if the content is interlaced
   bool      m_chromaLocInfoPresentFlag;                       ///< Signals whether chroma_sample_loc_type_top_field and chroma_sample_loc_type_bottom_field are present
-  int       m_chromaSampleLocTypeTopField;                    ///< Specifies the location of chroma samples for top field
-  int       m_chromaSampleLocTypeBottomField;                 ///< Specifies the location of chroma samples for bottom field
-  int       m_chromaSampleLocType;                            ///< Specifies the location of chroma samples for progressive content
+  Chroma420LocType m_chromaSampleLocTypeTopField;      // Specifies the location of chroma samples for top field
+  Chroma420LocType m_chromaSampleLocTypeBottomField;   // Specifies the location of chroma samples for bottom field
+  Chroma420LocType m_chromaSampleLocType;   // Specifies the location of chroma samples for progressive content
   bool      m_overscanInfoPresentFlag;                        ///< Signals whether overscan_appropriate_flag is present
   bool      m_overscanAppropriateFlag;                        ///< Indicates whether conformant decoded pictures are suitable for display using overscan
   bool      m_videoFullRangeFlag;                             ///< Indicates the black level and range of luma and chroma signals
diff --git a/source/Lib/CommonLib/SequenceParameterSet.h b/source/Lib/CommonLib/SequenceParameterSet.h
index de81bd397..983a2f3ae 100644
--- a/source/Lib/CommonLib/SequenceParameterSet.h
+++ b/source/Lib/CommonLib/SequenceParameterSet.h
@@ -57,10 +57,10 @@ private:
   int  m_transferCharacteristics;
   int  m_matrixCoefficients;
   bool m_videoFullRangeFlag;
-  bool m_chromaLocInfoPresentFlag;
-  int  m_chromaSampleLocTypeTopField;
-  int  m_chromaSampleLocTypeBottomField;
-  int  m_chromaSampleLocType;
+  bool             m_chromaLocInfoPresentFlag       = false;
+  Chroma420LocType m_chromaSampleLocTypeTopField    = Chroma420LocType::UNSPECIFIED;
+  Chroma420LocType m_chromaSampleLocTypeBottomField = Chroma420LocType::UNSPECIFIED;
+  Chroma420LocType m_chromaSampleLocType            = Chroma420LocType::UNSPECIFIED;
 
 public:
   VUI()
@@ -80,10 +80,6 @@ public:
     , m_transferCharacteristics(2)
     , m_matrixCoefficients(2)
     , m_videoFullRangeFlag(false)
-    , m_chromaLocInfoPresentFlag(false)
-    , m_chromaSampleLocTypeTopField(6)
-    , m_chromaSampleLocTypeBottomField(6)
-    , m_chromaSampleLocType(6)
   {}
 
   virtual ~VUI() {}
@@ -131,14 +127,14 @@ public:
   bool getChromaLocInfoPresentFlag() const       { return m_chromaLocInfoPresentFlag; }
   void setChromaLocInfoPresentFlag(bool i)       { m_chromaLocInfoPresentFlag = i; }
 
-  int  getChromaSampleLocTypeTopField() const    { return m_chromaSampleLocTypeTopField; }
-  void setChromaSampleLocTypeTopField(int i)     { m_chromaSampleLocTypeTopField = i; }
+  Chroma420LocType getChromaSampleLocTypeTopField() const { return m_chromaSampleLocTypeTopField; }
+  void             setChromaSampleLocTypeTopField(Chroma420LocType val) { m_chromaSampleLocTypeTopField = val; }
 
-  int  getChromaSampleLocTypeBottomField() const { return m_chromaSampleLocTypeBottomField; }
-  void setChromaSampleLocTypeBottomField(int i)  { m_chromaSampleLocTypeBottomField = i; }
+  Chroma420LocType getChromaSampleLocTypeBottomField() const { return m_chromaSampleLocTypeBottomField; }
+  void             setChromaSampleLocTypeBottomField(Chroma420LocType val) { m_chromaSampleLocTypeBottomField = val; }
 
-  int  getChromaSampleLocType() const            { return m_chromaSampleLocType; }
-  void setChromaSampleLocType(int i)             { m_chromaSampleLocType = i; }
+  Chroma420LocType getChromaSampleLocType() const { return m_chromaSampleLocType; }
+  void             setChromaSampleLocType(Chroma420LocType val) { m_chromaSampleLocType = val; }
 
   bool getOverscanInfoPresentFlag() const        { return m_overscanInfoPresentFlag; }
   void setOverscanInfoPresentFlag(bool i)        { m_overscanInfoPresentFlag = i; }
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index fba9189bb..335791293 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -426,6 +426,18 @@ enum class ChromaFormat : uint8_t
   UNDEFINED = NUM
 };
 
+enum class Chroma420LocType : uint8_t
+{
+  LEFT,
+  CENTER,
+  TOP_LEFT,
+  TOP,
+  BOTTOM_LEFT,
+  BOTTOM,
+  UNSPECIFIED,
+  NUM,
+};
+
 enum class ChannelType : uint8_t
 {
   LUMA   = 0,
diff --git a/source/Lib/DecoderLib/VLCReader.cpp b/source/Lib/DecoderLib/VLCReader.cpp
index db0f35eda..64310f476 100644
--- a/source/Lib/DecoderLib/VLCReader.cpp
+++ b/source/Lib/DecoderLib/VLCReader.cpp
@@ -1156,12 +1156,19 @@ void  HLSyntaxReader::parseVUI(VUI* pcVUI, SPS *pcSPS)
   {
     if(pcVUI->getProgressiveSourceFlag() && !pcVUI->getInterlacedSourceFlag())
     {
-      xReadUvlc(   symbol, "vui_chroma_sample_loc_type" );        pcVUI->setChromaSampleLocType(symbol);
+      xReadUvlc(symbol, "vui_chroma_sample_loc_type");
+      CHECK(symbol >= to_underlying(Chroma420LocType::NUM), "vui_chroma_sample_loc_type out of range");
+      pcVUI->setChromaSampleLocType(static_cast<Chroma420LocType>(symbol));
     }
     else
     {
-      xReadUvlc(   symbol, "vui_chroma_sample_loc_type_top_field" );        pcVUI->setChromaSampleLocTypeTopField(symbol);
-      xReadUvlc(   symbol, "vui_chroma_sample_loc_type_bottom_field" );     pcVUI->setChromaSampleLocTypeBottomField(symbol);
+      xReadUvlc(symbol, "vui_chroma_sample_loc_type_top_field");
+      CHECK(symbol >= to_underlying(Chroma420LocType::NUM), "vui_chroma_sample_loc_type_top_field out of range");
+      pcVUI->setChromaSampleLocTypeTopField(static_cast<Chroma420LocType>(symbol));
+
+      xReadUvlc(symbol, "vui_chroma_sample_loc_type_bottom_field");
+      CHECK(symbol >= to_underlying(Chroma420LocType::NUM), "vui_chroma_sample_loc_type_bottom_field out of range");
+      pcVUI->setChromaSampleLocTypeBottomField(static_cast<Chroma420LocType>(symbol));
     }
   }
 
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index 017751786..188f9ce01 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -947,9 +947,9 @@ protected:
   bool      m_progressiveSourceFlag;                          ///< Indicates if the content is progressive
   bool      m_interlacedSourceFlag;                           ///< Indicates if the content is interlaced
   bool      m_chromaLocInfoPresentFlag;                       ///< Signals whether chroma_sample_loc_type_top_field and chroma_sample_loc_type_bottom_field are present
-  int       m_chromaSampleLocTypeTopField;                    ///< Specifies the location of chroma samples for top field
-  int       m_chromaSampleLocTypeBottomField;                 ///< Specifies the location of chroma samples for bottom field
-  int       m_chromaSampleLocType;                            ///< Specifies the location of chroma samples for progressive content
+  Chroma420LocType m_chromaSampleLocTypeTopField;      // Specifies the location of chroma samples for top field
+  Chroma420LocType m_chromaSampleLocTypeBottomField;   // Specifies the location of chroma samples for bottom field
+  Chroma420LocType m_chromaSampleLocType;   // Specifies the location of chroma samples for progressive content
   bool      m_overscanInfoPresentFlag;                        ///< Signals whether overscan_appropriate_flag is present
   bool      m_overscanAppropriateFlag;                        ///< Indicates whether conformant decoded pictures are suitable for display using overscan
   bool      m_videoFullRangeFlag;                             ///< Indicates the black level and range of luma and chroma signals
@@ -2635,12 +2635,12 @@ public:
   void         setMatrixCoefficients(int i)                          { m_matrixCoefficients = i; }
   bool         getChromaLocInfoPresentFlag()                         { return m_chromaLocInfoPresentFlag; }
   void         setChromaLocInfoPresentFlag(bool i)                   { m_chromaLocInfoPresentFlag = i; }
-  int          getChromaSampleLocTypeTopField()                      { return m_chromaSampleLocTypeTopField; }
-  void         setChromaSampleLocTypeTopField(int i)                 { m_chromaSampleLocTypeTopField = i; }
-  int          getChromaSampleLocTypeBottomField()                   { return m_chromaSampleLocTypeBottomField; }
-  void         setChromaSampleLocTypeBottomField(int i)              { m_chromaSampleLocTypeBottomField = i; }
-  int          getChromaSampleLocType()                              { return m_chromaSampleLocType; }
-  void         setChromaSampleLocType(int i)                         { m_chromaSampleLocType = i; }
+  Chroma420LocType getChromaSampleLocTypeTopField() { return m_chromaSampleLocTypeTopField; }
+  void             setChromaSampleLocTypeTopField(Chroma420LocType val) { m_chromaSampleLocTypeTopField = val; }
+  Chroma420LocType getChromaSampleLocTypeBottomField() { return m_chromaSampleLocTypeBottomField; }
+  void             setChromaSampleLocTypeBottomField(Chroma420LocType val) { m_chromaSampleLocTypeBottomField = val; }
+  Chroma420LocType getChromaSampleLocType() { return m_chromaSampleLocType; }
+  void             setChromaSampleLocType(Chroma420LocType val) { m_chromaSampleLocType = val; }
   bool         getOverscanInfoPresentFlag()                          { return m_overscanInfoPresentFlag; }
   void         setOverscanInfoPresentFlag(bool i)                    { m_overscanInfoPresentFlag = i; }
   bool         getOverscanAppropriateFlag()                          { return m_overscanAppropriateFlag; }
diff --git a/source/Lib/EncoderLib/VLCWriter.cpp b/source/Lib/EncoderLib/VLCWriter.cpp
index 6369e8ba9..aa14e4e15 100644
--- a/source/Lib/EncoderLib/VLCWriter.cpp
+++ b/source/Lib/EncoderLib/VLCWriter.cpp
@@ -728,12 +728,12 @@ void HLSWriter::codeVUI( const VUI *pcVUI, const SPS* pcSPS )
   {
     if(pcVUI->getProgressiveSourceFlag() && !pcVUI->getInterlacedSourceFlag())
     {
-      xWriteUvlc(pcVUI->getChromaSampleLocType(),         "vui_chroma_sample_loc_type");
+      xWriteUvlc(to_underlying(pcVUI->getChromaSampleLocType()), "vui_chroma_sample_loc_type");
     }
     else
     {
-      xWriteUvlc(pcVUI->getChromaSampleLocTypeTopField(),         "vui_chroma_sample_loc_type_top_field");
-      xWriteUvlc(pcVUI->getChromaSampleLocTypeBottomField(),      "vui_chroma_sample_loc_type_bottom_field");
+      xWriteUvlc(to_underlying(pcVUI->getChromaSampleLocTypeTopField()), "vui_chroma_sample_loc_type_top_field");
+      xWriteUvlc(to_underlying(pcVUI->getChromaSampleLocTypeBottomField()), "vui_chroma_sample_loc_type_bottom_field");
     }
   }
   if(!isByteAligned())
diff --git a/source/Lib/Utilities/VideoIOYuv.cpp b/source/Lib/Utilities/VideoIOYuv.cpp
index 98123631d..d7618f378 100644
--- a/source/Lib/Utilities/VideoIOYuv.cpp
+++ b/source/Lib/Utilities/VideoIOYuv.cpp
@@ -176,7 +176,9 @@ void VideoIOYuv::open(const std::string &fileName, bool bWriteMode, const BitDep
         Fraction     dummyFrameRate;
         int          dummyBitDepth     = 0;
         ChromaFormat dummyChromaFormat = ChromaFormat::_420;
-        parseY4mFileHeader(fileName, dummyWidth, dummyHeight, dummyFrameRate, dummyBitDepth, dummyChromaFormat);
+        Chroma420LocType dummyLocType      = Chroma420LocType::UNSPECIFIED;
+        parseY4mFileHeader(fileName, dummyWidth, dummyHeight, dummyFrameRate, dummyBitDepth, dummyChromaFormat,
+                           dummyLocType);
       }
     }
     m_cHandle.open(fileName.c_str(), std::ios::binary | std::ios::in);
@@ -195,8 +197,42 @@ void VideoIOYuv::open(const std::string &fileName, bool bWriteMode, const BitDep
   return;
 }
 
+struct Y4mChromaFormat
+{
+  char             name[16];
+  int              bitDepth;
+  ChromaFormat     chromaFormat;
+  Chroma420LocType locType;
+};
+
+static const std::array y4mChromaFormats = {
+  Y4mChromaFormat{ "mono9", 9, ChromaFormat::_400, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "mono10", 10, ChromaFormat::_400, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "mono12", 12, ChromaFormat::_400, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "mono", 8, ChromaFormat::_400, Chroma420LocType::UNSPECIFIED },
+
+  Y4mChromaFormat{ "420jpeg", 8, ChromaFormat::_420, Chroma420LocType::CENTER },
+  Y4mChromaFormat{ "420mpeg2", 8, ChromaFormat::_420, Chroma420LocType::LEFT },
+  Y4mChromaFormat{ "420paldv", 8, ChromaFormat::_420, Chroma420LocType::TOP_LEFT },
+
+  Y4mChromaFormat{ "420p9", 9, ChromaFormat::_420, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "420p10", 10, ChromaFormat::_420, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "420p12", 12, ChromaFormat::_420, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "420", 8, ChromaFormat::_420, Chroma420LocType::UNSPECIFIED },
+
+  Y4mChromaFormat{ "422p9", 9, ChromaFormat::_422, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "422p10", 10, ChromaFormat::_422, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "422p12", 12, ChromaFormat::_422, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "422", 8, ChromaFormat::_422, Chroma420LocType::UNSPECIFIED },
+
+  Y4mChromaFormat{ "444p9", 9, ChromaFormat::_444, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "444p10", 10, ChromaFormat::_444, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "444p12", 12, ChromaFormat::_444, Chroma420LocType::UNSPECIFIED },
+  Y4mChromaFormat{ "444", 8, ChromaFormat::_444, Chroma420LocType::UNSPECIFIED },
+};
+
 void VideoIOYuv::parseY4mFileHeader(const std::string& fileName, int& width, int& height, Fraction& frameRate,
-                                    int& bitDepth, ChromaFormat& chromaFormat)
+                                    int& bitDepth, ChromaFormat& chromaFormat, Chroma420LocType& locType)
 {
   m_cHandle.open(fileName.c_str(), std::ios::binary | std::ios::in);
   CHECK(m_cHandle.fail(), "File open failed.")
@@ -218,45 +254,22 @@ void VideoIOYuv::parseY4mFileHeader(const std::string& fileName, int& width, int
   // parse Y4M header info
   for (int i = Y4M_SIGNATURE_LENGTH; i < m_inY4mFileHeaderLength; i++)
   {
-    int numerator = 0, denominator = 0, pos = 0;
+    int numerator = 0, denominator = 0;
     switch (header[i])
     {
     case 'W': sscanf(header + i + 1, "%d", &width); break;
     case 'H': sscanf(header + i + 1, "%d", &height); break;
     case 'C':
-      if (strncmp(&header[i + 1], "mono", 4) == 0)
+      for (const auto& cf: y4mChromaFormats)
       {
-        chromaFormat = ChromaFormat::_400;
-        pos          = i + 5;
-      }
-      else if (strncmp(&header[i + 1], "420", 3) == 0)
-      {
-        chromaFormat = ChromaFormat::_420;
-        pos          = i + 4;
-        if (strncmp(&header[pos], "jpeg", 4) == 0)
-        {
-          pos += 4;
-        }
-        else if (strncmp(&header[pos], "paldv", 5) == 0)
+        if (strncmp(&header[i + 1], cf.name, strlen(cf.name)) == 0)
         {
-          pos += 5;
+          chromaFormat = cf.chromaFormat;
+          locType      = cf.locType;
+          bitDepth     = cf.bitDepth;
+          break;
         }
       }
-      else if (strncmp(&header[i + 1], "422", 3) == 0)
-      {
-        chromaFormat = ChromaFormat::_422;
-        pos          = i + 4;
-      }
-      else if (strncmp(&header[i + 1], "444", 3) == 0)
-      {
-        chromaFormat = ChromaFormat::_444;
-        pos          = i + 4;
-      }
-      bitDepth = 8;
-      if (header[pos] == 'p')
-      {
-        sscanf(&header[pos + 1], "%d", &bitDepth);
-      }
       break;
     case 'F':
       if (sscanf(header + i + 1, "%d:%d", &numerator, &denominator) == 2)
@@ -281,13 +294,14 @@ void VideoIOYuv::parseY4mFileHeader(const std::string& fileName, int& width, int
 }
 
 void VideoIOYuv::setOutputY4mInfo(int width, int height, const Fraction& frameRate, int bitDepth,
-                                  ChromaFormat chromaFormat)
+                                  ChromaFormat chromaFormat, Chroma420LocType locType)
 {
   m_outPicWidth     = width;
   m_outPicHeight    = height;
   m_outBitDepth     = bitDepth;
   m_outFrameRate    = frameRate;
   m_outChromaFormat = chromaFormat;
+  m_outLocType      = locType;
 }
 
 void VideoIOYuv::writeY4mFileHeader()
@@ -299,26 +313,32 @@ void VideoIOYuv::writeY4mFileHeader()
   header += "H" + std::to_string(m_outPicHeight) + " ";
   header += "F" + std::to_string(m_outFrameRate.num) + ":" + std::to_string(m_outFrameRate.den) + " ";
   header += "Ip A0:0 ";
-  switch (m_outChromaFormat)
+  header += "C";
+  bool found = false;
+  for (const auto& cf: y4mChromaFormats)
   {
-  case ChromaFormat::_400:
-    header += "Cmono";
-    break;
-  case ChromaFormat::_420:
-    header += "C420";
-    break;
-  case ChromaFormat::_422:
-    header += "C422";
-    break;
-  case ChromaFormat::_444:
-    header += "C444";
-    break;
-  default: CHECK(true, "Unknow chroma format");
+    if (m_outBitDepth == cf.bitDepth && m_outChromaFormat == cf.chromaFormat && m_outLocType == cf.locType)
+    {
+      header += cf.name;
+      found = true;
+      break;
+    }
   }
-  if (m_outBitDepth > 8)
+  if (!found)
   {
-    header += "p" + std::to_string(m_outBitDepth);
+    for (const auto& cf: y4mChromaFormats)
+    {
+      if (m_outBitDepth == cf.bitDepth && m_outChromaFormat == cf.chromaFormat
+          && Chroma420LocType::UNSPECIFIED == cf.locType)
+      {
+        header += cf.name;
+        found = true;
+        msg(WARNING, "Value for chroma sample location unsupported by y4m. Signalling unspecified location.");
+        break;
+      }
+    }
   }
+  CHECK(!found, "Format unsupported by y4m");
   header += "\n";
   // not write extension/comment
 
diff --git a/source/Lib/Utilities/VideoIOYuv.h b/source/Lib/Utilities/VideoIOYuv.h
index fc006aeb3..e353c4824 100644
--- a/source/Lib/Utilities/VideoIOYuv.h
+++ b/source/Lib/Utilities/VideoIOYuv.h
@@ -67,6 +67,7 @@ private:
   int          m_outBitDepth           = 0;
   Fraction     m_outFrameRate;
   ChromaFormat m_outChromaFormat       = ChromaFormat::_420;
+  Chroma420LocType m_outLocType            = Chroma420LocType::UNSPECIFIED;
   bool         m_outY4m                = false;
 
 public:
@@ -74,8 +75,9 @@ public:
   virtual ~VideoIOYuv()  {}
 
   void parseY4mFileHeader(const std::string& fileName, int& width, int& height, Fraction& frameRate, int& bitDepth,
-                          ChromaFormat& chromaFormat);
-  void setOutputY4mInfo(int width, int height, const Fraction& frameRate, int bitDepth, ChromaFormat chromaFormat);
+                          ChromaFormat& chromaFormat, Chroma420LocType& locType);
+  void setOutputY4mInfo(int width, int height, const Fraction& frameRate, int bitDepth, ChromaFormat chromaFormat,
+                        Chroma420LocType locType);
   void writeY4mFileHeader();
   void open(const std::string &fileName, bool bWriteMode, const BitDepths &fileBitDepth,
             const BitDepths &MSBExtendedBitDepth,
-- 
GitLab