// 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/fxcrt/check.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/notreached.h"
#include "core/fxge/dib/cfx_cmyk_to_srgb.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* doc,
                               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();
}

std::optional<FX_RGB_STRUCT<float>> CPDF_DeviceCS::GetRGB(
    pdfium::span<const float> pBuf) const {
  switch (GetFamily()) {
    case Family::kDeviceGray: {
      const float pix = NormalizeChannel(pBuf.front());
      return FX_RGB_STRUCT<float>{pix, pix, pix};
    }
    case Family::kDeviceRGB: {
      const auto& rgb =
          fxcrt::reinterpret_span<const FX_RGB_STRUCT<float>>(pBuf).front();
      return FX_RGB_STRUCT<float>{
          NormalizeChannel(rgb.red),
          NormalizeChannel(rgb.green),
          NormalizeChannel(rgb.blue),
      };
    }
    case Family::kDeviceCMYK: {
      const auto& cmyk =
          fxcrt::reinterpret_span<const FX_CMYK_STRUCT<float>>(pBuf).front();
      if (IsStdConversionEnabled()) {
        return FX_RGB_STRUCT<float>{
            1.0f - std::min(1.0f, cmyk.cyan + cmyk.key),
            1.0f - std::min(1.0f, cmyk.magenta + cmyk.key),
            1.0f - std::min(1.0f, cmyk.yellow + cmyk.key),
        };
      }
      return AdobeCmykToStandardRgbF(
          NormalizeChannel(cmyk.cyan), NormalizeChannel(cmyk.magenta),
          NormalizeChannel(cmyk.yellow), NormalizeChannel(cmyk.key));
    }
    default:
      NOTREACHED();
  }
}

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 {
  auto rgb_out = fxcrt::reinterpret_span<FX_RGB_STRUCT<uint8_t>>(dest_span);
  switch (GetFamily()) {
    case Family::kDeviceGray:
      CHECK(!bTransMask);  // bTransMask only allowed for CMYK colorspaces.
      // Compiler can't conclude src/dest don't overlap, avoid interleaved
      // loads and stores by not using an auto& reference here.
      for (const auto pix : src_span.first(static_cast<size_t>(pixels))) {
        rgb_out.front().red = pix;
        rgb_out.front().green = pix;
        rgb_out.front().blue = pix;
        rgb_out = rgb_out.subspan<1u>();
      }
      break;
    case Family::kDeviceRGB:
      CHECK(!bTransMask);  // bTransMask only allowed for CMYK colorspaces.
      fxcodec::ReverseRGB(dest_span, src_span, pixels);
      break;
    case Family::kDeviceCMYK: {
      auto cmyk_in =
          fxcrt::reinterpret_span<const FX_CMYK_STRUCT<uint8_t>>(src_span);
      if (bTransMask) {
        // Compiler can't conclude src/dest don't overlap, avoid interleaved
        // loads and stores by not using an auto& reference here.
        for (const auto cmyk : cmyk_in.first(static_cast<size_t>(pixels))) {
          const int k = 255 - cmyk.key;
          rgb_out.front().red = ((255 - cmyk.cyan) * k) / 255;
          rgb_out.front().green = ((255 - cmyk.magenta) * k) / 255;
          rgb_out.front().blue = ((255 - cmyk.yellow) * k) / 255;
          rgb_out = rgb_out.subspan<1u>();
        }
        break;
      }
      if (IsStdConversionEnabled()) {
        // Compiler can't conclude src/dest don't overlap, avoid interleaved
        // loads and stores by not using am auto& reference here,
        for (const auto cmyk : cmyk_in.first(static_cast<size_t>(pixels))) {
          const uint8_t k = cmyk.key;
          rgb_out.front().blue = 255 - std::min(255, cmyk.cyan + k);
          rgb_out.front().green = 255 - std::min(255, cmyk.magenta + k);
          rgb_out.front().red = 255 - std::min(255, cmyk.yellow + k);
          rgb_out = rgb_out.subspan<1u>();
        }
        break;
      }
      for (const auto& cmyk : cmyk_in.first(static_cast<size_t>(pixels))) {
        // TODO(tsepez): maybe this is a FX_BGR_STRUCT in reality?
        FX_RGB_STRUCT<uint8_t> rgb = AdobeCmykToStandardRgb(
            cmyk.cyan, cmyk.magenta, cmyk.yellow, cmyk.key);
        rgb_out.front().red = rgb.blue;
        rgb_out.front().green = rgb.green;
        rgb_out.front().blue = rgb.red;
        rgb_out = rgb_out.subspan<1u>();
      }
      break;
    }
    default:
      NOTREACHED();
  }
}
