// Copyright 2014 The PDFium Authors
// 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 "core/fxcodec/icc/icc_transform.h"

#include <stdint.h>

#include <algorithm>
#include <memory>

#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/ptr_util.h"

namespace fxcodec {

namespace {

// For use with std::unique_ptr<cmsHPROFILE>.
struct CmsProfileDeleter {
  inline void operator()(cmsHPROFILE p) { cmsCloseProfile(p); }
};

using ScopedCmsProfile = std::unique_ptr<void, CmsProfileDeleter>;

bool Check3Components(cmsColorSpaceSignature cs) {
  switch (cs) {
    case cmsSigGrayData:
    case cmsSigCmykData:
      return false;
    default:
      return true;
  }
}

}  // namespace

IccTransform::IccTransform(cmsHTRANSFORM hTransform,
                           int srcComponents,
                           bool bIsLab,
                           bool bNormal)
    : transform_(hTransform),
      src_components_(srcComponents),
      lab_(bIsLab),
      normal_(bNormal) {}

IccTransform::~IccTransform() {
  cmsDeleteTransform(transform_);
}

// static
std::unique_ptr<IccTransform> IccTransform::CreateTransformSRGB(
    pdfium::span<const uint8_t> span) {
  ScopedCmsProfile srcProfile(cmsOpenProfileFromMem(
      span.data(), pdfium::checked_cast<cmsUInt32Number>(span.size())));
  if (!srcProfile) {
    return nullptr;
  }

  ScopedCmsProfile dstProfile(cmsCreate_sRGBProfile());
  if (!dstProfile) {
    return nullptr;
  }

  cmsColorSpaceSignature srcCS = cmsGetColorSpace(srcProfile.get());
  uint32_t nSrcComponents = cmsChannelsOf(srcCS);

  if (!IsValidIccComponents(nSrcComponents)) {
    return nullptr;
  }

  int srcFormat;
  bool bLab = false;
  bool bNormal = false;
  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);
    // TODO(thestig): Check to see if lcms2 supports more colorspaces that can
    // be considered normal.
    bNormal = srcCS == cmsSigGrayData || srcCS == cmsSigRgbData ||
              srcCS == cmsSigCmykData;
  }
  cmsColorSpaceSignature dstCS = cmsGetColorSpace(dstProfile.get());
  if (!Check3Components(dstCS)) {
    return nullptr;
  }

  cmsHTRANSFORM hTransform = nullptr;
  switch (dstCS) {
    case cmsSigRgbData:
      hTransform =
          cmsCreateTransform(srcProfile.get(), srcFormat, dstProfile.get(),
                             TYPE_BGR_8, INTENT_PERCEPTUAL, /*dwFlags=*/0);
      break;
    case cmsSigGrayData:
    case cmsSigCmykData:
      // Check3Components() already filtered these types.
      NOTREACHED();
    default:
      break;
  }
  if (!hTransform) {
    return nullptr;
  }

  // Private ctor.
  return pdfium::WrapUnique(
      new IccTransform(hTransform, nSrcComponents, bLab, bNormal));
}

void IccTransform::Translate(pdfium::span<const float> src_values,
                             pdfium::span<float, 3> dest_values) {
  uint8_t output[4];
  // TODO(npm): Currently the CmsDoTransform method is part of LCMS and it will
  // apply some member of transform_ to the input. We need to go over all the
  // places which set transform to verify that only `src_values.size()`
  // components are used.
  if (lab_) {
    DataVector<double> inputs(std::max<size_t>(src_values.size(), 16));
    for (uint32_t i = 0; i < src_values.size(); ++i) {
      inputs[i] = src_values[i];
    }
    cmsDoTransform(transform_, inputs.data(), output, 1);
  } else {
    DataVector<uint8_t> inputs(std::max<size_t>(src_values.size(), 16));
    for (size_t i = 0; i < src_values.size(); ++i) {
      inputs[i] =
          static_cast<int>(std::clamp(src_values[i] * 255.0f, 0.0f, 255.0f));
    }
    cmsDoTransform(transform_, inputs.data(), output, 1);
  }
  dest_values[0] = output[2] / 255.0f;
  dest_values[1] = output[1] / 255.0f;
  dest_values[2] = output[0] / 255.0f;
}

void IccTransform::TranslateScanline(pdfium::span<uint8_t> pDest,
                                     pdfium::span<const uint8_t> pSrc,
                                     int32_t pixels) {
  cmsDoTransform(transform_, pSrc.data(), pDest.data(), pixels);
}

// static
bool IccTransform::IsValidIccComponents(int components) {
  // According to PDF spec, number of components must be 1, 3, or 4.
  return components == 1 || components == 3 || components == 4;
}

}  // namespace fxcodec
