text: // SPDX-License-Identifier: BSD-3-Clause
  // Copyright Contributors to the OpenColorIO Project.
  
  #include <cstring>
  #include <sstream>
  
  #include <OpenColorIO/OpenColorIO.h>
  
  #include "BitDepthUtils.h"
  #include "GpuShaderUtils.h"
  #include "HashUtils.h"
  #include "MathUtils.h"
  #include "ops/matrix/MatrixOp.h"
  #include "ops/matrix/MatrixOpCPU.h"
  #include "ops/matrix/MatrixOpGPU.h"
  #include "transforms/MatrixTransform.h"
  
  namespace OCIO_NAMESPACE
  {
  
  namespace
  {
  
  // The class represents the Matrix Op
  //
  // The class specifies a matrix transformation to be applied to
  // the input values. The input and output of a matrix are always
  // 4-component values.
  // An offset vector is also applied to the result.
  // The output values are calculated using the row-major order convention:
  //
  // Rout = a[0][0]*Rin + a[0][1]*Gin + a[0][2]*Bin + a[0][3]*Ain + o[0];
  // Gout = a[1][0]*Rin + a[1][1]*Gin + a[1][2]*Bin + a[1][3]*Ain + o[1];
  // Bout = a[2][0]*Rin + a[2][1]*Gin + a[2][2]*Bin + a[2][3]*Ain + o[2];
  // Aout = a[3][0]*Rin + a[3][1]*Gin + a[3][2]*Bin + a[3][3]*Ain + o[3];
  
  class MatrixOffsetOp : public Op
  {
  public:
      MatrixOffsetOp() = delete;
      MatrixOffsetOp(const MatrixOffsetOp &) = delete;
  
      MatrixOffsetOp(const double * m44,
                      const double * offset4,
                      TransformDirection direction);
  
      MatrixOffsetOp(MatrixOpDataRcPtr & matrix);
  
      virtual ~MatrixOffsetOp();
  
      OpRcPtr clone() const override;
  
      std::string getInfo() const override;
  
      bool isSameType(ConstOpRcPtr & op) const override;
      bool isInverse(ConstOpRcPtr & op) const override;
      bool canCombineWith(ConstOpRcPtr & op) const override;
      void combineWith(OpRcPtrVec & ops, ConstOpRcPtr & secondOp) const override;
  
      void finalize() override;
      std::string getCacheID() const override;
  
      ConstOpCPURcPtr getCPUOp(bool fastLogExpPow) const override;
  
      void extractGpuShaderInfo(GpuShaderCreatorRcPtr & shaderCreator) const override;
  
  protected:
      ConstMatrixOpDataRcPtr matrixData() const { return DynamicPtrCast<const MatrixOpData>(data()); }
      MatrixOpDataRcPtr matrixData() { return DynamicPtrCast<MatrixOpData>(data()); }
  };
  
  
  typedef OCIO_SHARED_PTR<MatrixOffsetOp> MatrixOffsetOpRcPtr;
  typedef OCIO_SHARED_PTR<const MatrixOffsetOp> ConstMatrixOffsetOpRcPtr;
  
  
  MatrixOffsetOp::MatrixOffsetOp(const double * m44,
                                 const double * offset4,
                                 TransformDirection direction)
      : Op()
  {
      MatrixOpDataRcPtr mat = std::make_shared<MatrixOpData>(direction);
      mat->setRGBA(m44);
      mat->setRGBAOffsets(offset4);
      data() = mat;
  }
  
  MatrixOffsetOp::MatrixOffsetOp(MatrixOpDataRcPtr & matrix)
      : Op()
  {
      data() = matrix;
  }
  
  OpRcPtr MatrixOffsetOp::clone() const
  {
      MatrixOpDataRcPtr clonedData = matrixData()->clone();
      return std::make_shared<MatrixOffsetOp>(clonedData);
  }
  
  MatrixOffsetOp::~MatrixOffsetOp()
  { }
  
  std::string MatrixOffsetOp::getInfo() const
  {
      return "<MatrixOffsetOp>";
  }
  
  bool MatrixOffsetOp::isSameType(ConstOpRcPtr & op) const
  {
      ConstMatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<const MatrixOffsetOp>(op);
      if(!typedRcPtr) return false;
      return true;
  }
  
  bool MatrixOffsetOp::isInverse(ConstOpRcPtr & /* op */) const
  {
      // It is simpler to handle a pair of inverses by combining them and then removing
      // the identity.  So we just return false here.
      return false;
  }
  
  // Ops must have been validated and finalized.
  bool MatrixOffsetOp::canCombineWith(ConstOpRcPtr & op) const
  {
      // TODO: Could combine with certain ASC_CDL ops.
      if (isSameType(op))
      {
          if (matrixData()->getDirection() == TRANSFORM_DIR_INVERSE)
          {
              throw Exception("Op::finalize has to be called.");
          }
          ConstMatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<const MatrixOffsetOp>(op);
  
          auto otherMat = typedRcPtr->matrixData();
          if (otherMat)
          {
              if (otherMat->getDirection() == TRANSFORM_DIR_INVERSE)
              {
                  throw Exception("Op::finalize has to be called.");
              }
          }
          return true;
      }
      return false;
  }
  
  void MatrixOffsetOp::combineWith(OpRcPtrVec & ops, ConstOpRcPtr & secondOp) const
  {
      if (!canCombineWith(secondOp))
      {
          throw Exception("MatrixOffsetOp: canCombineWith must be checked "
                          "before calling combineWith.");
      }
      ConstMatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<const MatrixOffsetOp>(secondOp);
  
      auto thisData = typedRcPtr->matrixData();
      MatrixOpDataRcPtr composedMat = matrixData()->compose(thisData);
      if (!composedMat->isNoOp())
      {
          CreateMatrixOp(ops, composedMat, TRANSFORM_DIR_FORWARD);
      }
  }
  
  void MatrixOffsetOp::finalize()
  {
      ConstMatrixOpDataRcPtr mat = matrixData();
      if (mat->getDirection() == TRANSFORM_DIR_INVERSE)
      {
          data() = mat->getAsForward();
      }
  }
  
  std::string MatrixOffsetOp::getCacheID() const
  {
      // Create the cacheID
      std::ostringstream cacheIDStream;
      cacheIDStream << "<MatrixOffsetOp ";
      cacheIDStream << matrixData()->getCacheID() << " ";
      cacheIDStream << ">";
  
      return cacheIDStream.str();
  }
  
  ConstOpCPURcPtr MatrixOffsetOp::getCPUOp(bool /*fastLogExpPow*/) const
  {
      ConstMatrixOpDataRcPtr data = matrixData();
      return GetMatrixRenderer(data);
  }
  
  void MatrixOffsetOp::extractGpuShaderInfo(GpuShaderCreatorRcPtr & shaderCreator) const
  {
      ConstMatrixOpDataRcPtr data = matrixData();
      if (data->getDirection() == TRANSFORM_DIR_INVERSE)
      {
          throw Exception("Op::finalize has to be called.");
      }
      GetMatrixGPUShaderProgram(shaderCreator, data);
  }
  
  }  // Anon namespace
  
  
  ///////////////////////////////////////////////////////////////////////////
  
  
  void CreateScaleOp(OpRcPtrVec & ops,
                     const double * scale4,
                     TransformDirection direction)
  {
      static constexpr double offset4[4] { 0, 0, 0, 0 };
      CreateScaleOffsetOp(ops, scale4, offset4, direction);
  }
  
  void CreateMatrixOp(OpRcPtrVec & ops,
                      const double * m44,
                      TransformDirection direction)
  {
      static constexpr double offset4[4] { 0.0, 0.0, 0.0, 0.0 };
      CreateMatrixOffsetOp(ops, m44, offset4, direction);
  }
  
  void CreateOffsetOp(OpRcPtrVec & ops,
                      const double * offset4,
                      TransformDirection direction)
  {
      static constexpr double scale4[4] { 1.0, 1.0, 1.0, 1.0 };
      CreateScaleOffsetOp(ops, scale4, offset4, direction);
  }
  
  void CreateScaleOffsetOp(OpRcPtrVec & ops,
                           const double * scale4, const double * offset4,
                           TransformDirection direction)
  {
      double m44[16]{ 0.0 };
  
      m44[0] = scale4[0];
      m44[5] = scale4[1];
      m44[10] = scale4[2];
      m44[15] = scale4[3];
  
      CreateMatrixOffsetOp(ops, m44, offset4, direction);
  }
  
  void CreateSaturationOp(OpRcPtrVec & ops,
                          double sat,
                          const double * lumaCoef3,
                          TransformDirection direction)
  {
      double matrix[16];
      double offset[4];
      MatrixTransform::Sat(matrix, offset, sat, lumaCoef3);
  
      CreateMatrixOffsetOp(ops, matrix, offset, direction);
  }
  
  void CreateMatrixOffsetOp(OpRcPtrVec & ops,
                            const double * m44, const double * offset4,
                            TransformDirection direction)
  {
      auto mat = std::make_shared<MatrixOpData>();
      mat->setRGBA(m44);
      mat->setRGBAOffsets(offset4);
      mat->setDirection(direction);
  
      CreateMatrixOp(ops, mat, TRANSFORM_DIR_FORWARD);
  }
  
  void CreateFitOp(OpRcPtrVec & ops,
                   const double * oldmin4, const double * oldmax4,
                   const double * newmin4, const double * newmax4,
                   TransformDirection direction)
  {
      double matrix[16];
      double offset[4];
      MatrixTransform::Fit(matrix, offset,
                           oldmin4, oldmax4,
                           newmin4, newmax4);
  
      CreateMatrixOffsetOp(ops, matrix, offset, direction);
  }
  
  void CreateIdentityMatrixOp(OpRcPtrVec & ops, TransformDirection direction)
  {
      double matrix[16]{ 0.0 };
      matrix[0] = 1.0;
      matrix[5] = 1.0;
      matrix[10] = 1.0;
      matrix[15] = 1.0;
      const double offset[4] = { 0.0, 0.0, 0.0, 0.0 };
  
      ops.push_back(std::make_shared<MatrixOffsetOp>(matrix,
                                                     offset,
                                                     direction));
  }
  
  void CreateMinMaxOp(OpRcPtrVec & ops,
                      const double * from_min3,
                      const double * from_max3,
                      TransformDirection direction)
  {
      double scale4[4] = { 1.0, 1.0, 1.0, 1.0 };
      double offset4[4] = { 0.0, 0.0, 0.0, 0.0 };
  
      bool somethingToDo = false;
      for (int i = 0; i < 3; ++i)
      {
          scale4[i] = 1.0 / (from_max3[i] - from_min3[i]);
          offset4[i] = -from_min3[i] * scale4[i];
          somethingToDo |= (scale4[i] != 1.0 || offset4[i] != 0.0);
      }
  
      if (somethingToDo)
      {
          CreateScaleOffsetOp(ops, scale4, offset4, direction);
      }
  }
  
  void CreateMinMaxOp(OpRcPtrVec & ops,
                      float from_min,
                      float from_max,
                      TransformDirection direction)
  {
      const double min[3] = { from_min, from_min, from_min };
      const double max[3] = { from_max, from_max, from_max };
      CreateMinMaxOp(ops, min, max, direction);
  }
  
  void CreateMatrixOp(OpRcPtrVec & ops,
                      MatrixOpData::MatrixArrayPtr & matrix,
                      TransformDirection direction)
  {
      MatrixOpDataRcPtr mat = std::make_shared<MatrixOpData>(*matrix.get());
      CreateMatrixOp(ops, mat, direction);
  }
  
  void CreateMatrixOp(OpRcPtrVec & ops, MatrixOpDataRcPtr & matrix, TransformDirection direction)
  {
      auto mat = matrix;
      if (direction == TRANSFORM_DIR_INVERSE)
      {
          mat = matrix->clone();
          auto newDir = CombineTransformDirections(mat->getDirection(), direction);
          mat->setDirection(newDir);
      }
  
      ops.push_back(std::make_shared<MatrixOffsetOp>(mat));
  }
  
  void CreateIdentityMatrixOp(OpRcPtrVec & ops)
  {
      MatrixOpDataRcPtr mat = MatrixOpData::CreateDiagonalMatrix(1.0);
  
      ops.push_back(std::make_shared<MatrixOffsetOp>(mat));
  }
  
  ///////////////////////////////////////////////////////////////////////////
  
  void CreateMatrixTransform(GroupTransformRcPtr & group, ConstOpRcPtr & op)
  {
      auto mat = DynamicPtrCast<const MatrixOffsetOp>(op);
      if (!mat)
      {
          throw Exception("CreateMatrixTransform: op has to be a MatrixOffsetOp");
      }
      auto matTransform = MatrixTransform::Create();
      MatrixOpData & data = dynamic_cast<MatrixTransformImpl*>(matTransform.get())->data();
  
      auto matDataSrc = DynamicPtrCast<const MatrixOpData>(op->data());
      data = *matDataSrc;
  
      group->appendTransform(matTransform);
  }
  
  void BuildMatrixOp(OpRcPtrVec & ops,
                     const MatrixTransform & transform,
                     TransformDirection dir)
  {
      const MatrixOpData & data = dynamic_cast<const MatrixTransformImpl &>(transform).data();
      data.validate();
  
      MatrixOpDataRcPtr mat = data.clone();
      CreateMatrixOp(ops, mat, dir);
  }
  
  } // namespace OCIO_NAMESPACE
  
  