// 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/fpdfapi/page/cpdf_devicecs.h"

#include <algorithm>

#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_stream_acc.h"
#include "core/fpdfapi/parser/cpdf_string.h"
#include "core/fxcodec/fx_codec.h"
#include "core/fxge/dib/cfx_cmyk_to_srgb.h"
#include "third_party/base/check.h"
#include "third_party/base/notreached.h"

namespace {

float NormalizeChannel(float fVal) {
  return std::clamp(fVal, 0.0f, 1.0f);
}

}  // namespace

CPDF_DeviceCS::CPDF_DeviceCS(Family family) : CPDF_ColorSpace(family) {
  DCHECK(family == Family::kDeviceGray || family == Family::kDeviceRGB ||
         family == Family::kDeviceCMYK);
  SetComponentsForStockCS(ComponentsForFamily(GetFamily()));
}

CPDF_DeviceCS::~CPDF_DeviceCS() = default;

uint32_t CPDF_DeviceCS::v_Load(CPDF_Document* pDoc,
                               const CPDF_Array* pArray,
                               std::set<const CPDF_Object*>* pVisited) {
  // Unlike other classes that inherit from CPDF_ColorSpace, CPDF_DeviceCS is
  // never loaded by CPDF_ColorSpace.
  NOTREACHED_NORETURN();
}

bool CPDF_DeviceCS::GetRGB(pdfium::span<const float> pBuf,
                           float* R,
                           float* G,
                           float* B) const {
  switch (GetFamily()) {
    case Family::kDeviceGray:
      *R = NormalizeChannel(pBuf[0]);
      *G = *R;
      *B = *R;
      return true;
    case Family::kDeviceRGB:
      *R = NormalizeChannel(pBuf[0]);
      *G = NormalizeChannel(pBuf[1]);
      *B = NormalizeChannel(pBuf[2]);
      return true;
    case Family::kDeviceCMYK:
      if (IsStdConversionEnabled()) {
        float k = pBuf[3];
        *R = 1.0f - std::min(1.0f, pBuf[0] + k);
        *G = 1.0f - std::min(1.0f, pBuf[1] + k);
        *B = 1.0f - std::min(1.0f, pBuf[2] + k);
      } else {
        std::tie(*R, *G, *B) = AdobeCMYK_to_sRGB(
            NormalizeChannel(pBuf[0]), NormalizeChannel(pBuf[1]),
            NormalizeChannel(pBuf[2]), NormalizeChannel(pBuf[3]));
      }
      return true;
    default:
      NOTREACHED_NORETURN();
  }
}

void CPDF_DeviceCS::TranslateImageLine(pdfium::span<uint8_t> dest_span,
                                       pdfium::span<const uint8_t> src_span,
                                       int pixels,
                                       int image_width,
                                       int image_height,
                                       bool bTransMask) const {
  uint8_t* pDestBuf = dest_span.data();
  const uint8_t* pSrcBuf = src_span.data();
  switch (GetFamily()) {
    case Family::kDeviceGray:
      for (int i = 0; i < pixels; i++) {
        // Compiler can not conclude that src/dest don't overlap, avoid
        // duplicate loads.
        const uint8_t pix = pSrcBuf[i];
        *pDestBuf++ = pix;
        *pDestBuf++ = pix;
        *pDestBuf++ = pix;
      }
      break;
    case Family::kDeviceRGB:
      fxcodec::ReverseRGB(pDestBuf, pSrcBuf, pixels);
      break;
    case Family::kDeviceCMYK:
      if (bTransMask) {
        for (int i = 0; i < pixels; i++) {
          // Compiler can't conclude src/dest don't overlap, avoid interleaved
          // loads and stores.
          const uint8_t s0 = pSrcBuf[0];
          const uint8_t s1 = pSrcBuf[1];
          const uint8_t s2 = pSrcBuf[2];
          const int k = 255 - pSrcBuf[3];
          pDestBuf[0] = ((255 - s0) * k) / 255;
          pDestBuf[1] = ((255 - s1) * k) / 255;
          pDestBuf[2] = ((255 - s2) * k) / 255;
          pDestBuf += 3;
          pSrcBuf += 4;
        }
      } else {
        if (IsStdConversionEnabled()) {
          for (int i = 0; i < pixels; i++) {
            // Compiler can't conclude src/dest don't overlap, avoid
            // interleaved loads and stores.
            const uint8_t s0 = pSrcBuf[0];
            const uint8_t s1 = pSrcBuf[1];
            const uint8_t s2 = pSrcBuf[2];
            const uint8_t k = pSrcBuf[3];
            pDestBuf[2] = 255 - std::min(255, s0 + k);
            pDestBuf[1] = 255 - std::min(255, s1 + k);
            pDestBuf[0] = 255 - std::min(255, s2 + k);
            pSrcBuf += 4;
            pDestBuf += 3;
          }
        } else {
          for (int i = 0; i < pixels; i++) {
            std::tie(pDestBuf[2], pDestBuf[1], pDestBuf[0]) =
                AdobeCMYK_to_sRGB1(pSrcBuf[0], pSrcBuf[1], pSrcBuf[2],
                                   pSrcBuf[3]);
            pSrcBuf += 4;
            pDestBuf += 3;
          }
        }
      }
      break;
    default:
      NOTREACHED_NORETURN();
  }
}
