// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include <memory>

#include "core/fxcodec/codec/ccodec_iccmodule.h"
#include "core/fxcodec/codec/codec_int.h"
#include "core/fxcrt/cfx_fixedbufgrow.h"

namespace {

bool Check3Components(cmsColorSpaceSignature cs, bool bDst) {
  switch (cs) {
    case cmsSigGrayData:
      return false;
    case cmsSigCmykData:
      if (bDst)
        return false;
      break;
    case cmsSigLabData:
    case cmsSigRgbData:
    default:
      break;
  }
  return true;
}

}  // namespace

CLcmsCmm::CLcmsCmm(int srcComponents, cmsHTRANSFORM hTransform, bool isLab)
    : m_hTransform(hTransform),
      m_nSrcComponents(srcComponents),
      m_bLab(isLab) {}

CLcmsCmm::~CLcmsCmm() {
  cmsDeleteTransform(m_hTransform);
}

CCodec_IccModule::CCodec_IccModule() : m_nComponents(0) {}

CCodec_IccModule::~CCodec_IccModule() {}

std::unique_ptr<CLcmsCmm> CCodec_IccModule::CreateTransform_sRGB(
    const unsigned char* pSrcProfileData,
    uint32_t dwSrcProfileSize,
    uint32_t* nSrcComponents) {
  *nSrcComponents = 0;
  cmsHPROFILE srcProfile =
      cmsOpenProfileFromMem(pSrcProfileData, dwSrcProfileSize);
  if (!srcProfile)
    return nullptr;

  cmsHPROFILE dstProfile;
  dstProfile = cmsCreate_sRGBProfile();
  if (!dstProfile) {
    cmsCloseProfile(srcProfile);
    return nullptr;
  }
  int srcFormat;
  bool bLab = false;
  cmsColorSpaceSignature srcCS = cmsGetColorSpace(srcProfile);

  *nSrcComponents = cmsChannelsOf(srcCS);
  // According to PDF spec, number of components must be 1, 3, or 4.
  if (*nSrcComponents != 1 && *nSrcComponents != 3 && *nSrcComponents != 4) {
    cmsCloseProfile(srcProfile);
    cmsCloseProfile(dstProfile);
    return nullptr;
  }

  if (srcCS == cmsSigLabData) {
    srcFormat =
        COLORSPACE_SH(PT_Lab) | CHANNELS_SH(*nSrcComponents) | BYTES_SH(0);
    bLab = true;
  } else {
    srcFormat =
        COLORSPACE_SH(PT_ANY) | CHANNELS_SH(*nSrcComponents) | BYTES_SH(1);
  }
  cmsColorSpaceSignature dstCS = cmsGetColorSpace(dstProfile);
  if (!Check3Components(dstCS, true)) {
    cmsCloseProfile(srcProfile);
    cmsCloseProfile(dstProfile);
    return nullptr;
  }

  cmsHTRANSFORM hTransform = nullptr;
  int intent = 0;
  switch (dstCS) {
    case cmsSigGrayData:
      hTransform = cmsCreateTransform(srcProfile, srcFormat, dstProfile,
                                      TYPE_GRAY_8, intent, 0);
      break;
    case cmsSigRgbData:
      hTransform = cmsCreateTransform(srcProfile, srcFormat, dstProfile,
                                      TYPE_BGR_8, intent, 0);
      break;
    case cmsSigCmykData:
      hTransform = cmsCreateTransform(srcProfile, srcFormat, dstProfile,
                                      TYPE_CMYK_8, intent, 0);
      break;
    default:
      break;
  }
  if (!hTransform) {
    cmsCloseProfile(srcProfile);
    cmsCloseProfile(dstProfile);
    return nullptr;
  }
  auto pCmm = pdfium::MakeUnique<CLcmsCmm>(*nSrcComponents, hTransform, bLab);
  cmsCloseProfile(srcProfile);
  cmsCloseProfile(dstProfile);
  return pCmm;
}

void CCodec_IccModule::Translate(CLcmsCmm* pTransform,
                                 const float* pSrcValues,
                                 float* pDestValues) {
  if (!pTransform)
    return;

  uint32_t nSrcComponents = m_nComponents;
  uint8_t output[4];
  if (pTransform->m_bLab) {
    CFX_FixedBufGrow<double, 16> inputs(nSrcComponents);
    double* input = inputs;
    for (uint32_t i = 0; i < nSrcComponents; ++i)
      input[i] = pSrcValues[i];
    cmsDoTransform(pTransform->m_hTransform, input, output, 1);
  } else {
    CFX_FixedBufGrow<uint8_t, 16> inputs(nSrcComponents);
    uint8_t* input = inputs;
    for (uint32_t i = 0; i < nSrcComponents; ++i) {
      input[i] =
          pdfium::clamp(static_cast<int>(pSrcValues[i] * 255.0f), 0, 255);
    }
    cmsDoTransform(pTransform->m_hTransform, input, output, 1);
  }
  pDestValues[0] = output[2] / 255.0f;
  pDestValues[1] = output[1] / 255.0f;
  pDestValues[2] = output[0] / 255.0f;
}

void CCodec_IccModule::TranslateScanline(CLcmsCmm* pTransform,
                                         unsigned char* pDest,
                                         const unsigned char* pSrc,
                                         int32_t pixels) {
  if (pTransform)
    cmsDoTransform(pTransform->m_hTransform, pSrc, pDest, pixels);
}