blob: 10773ad79c5325bda58c3be01756554382280652 [file]
// Copyright 2025 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fpdfapi/page/jpx_decode_conversion.h"
#include <optional>
#include "core/fpdfapi/page/cpdf_colorspace.h"
#include "core/fxcodec/jpx/cjpx_decoder.h"
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/retain_ptr.h"
namespace {
// ISO 32000-1:2008 section 7.4.9 says the PDF and JPX colorspaces should have
// the same number of color channels. This helper function checks the
// colorspaces match, but also tolerates unknowns.
bool IsJPXColorSpaceOrUnspecifiedOrUnknown(COLOR_SPACE actual,
COLOR_SPACE expected) {
return actual == expected || actual == OPJ_CLRSPC_UNSPECIFIED ||
actual == OPJ_CLRSPC_UNKNOWN;
}
// Decides which JpxDecodeAction to use based on the colorspace information from
// the PDF and the JPX image. Called only when the PDF's image object contains a
// "/ColorSpace" entry.
std::optional<JpxDecodeAction> GetJpxDecodeActionFromColorSpaces(
const CJPX_Decoder::JpxImageInfo& jpx_info,
const CPDF_ColorSpace* pdf_colorspace) {
if (pdf_colorspace ==
CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceGray)) {
if (!IsJPXColorSpaceOrUnspecifiedOrUnknown(/*actual=*/jpx_info.colorspace,
/*expected=*/OPJ_CLRSPC_GRAY)) {
return std::nullopt;
}
return JpxDecodeAction::kUseGray;
}
if (pdf_colorspace ==
CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceRGB)) {
if (!IsJPXColorSpaceOrUnspecifiedOrUnknown(/*actual=*/jpx_info.colorspace,
/*expected=*/OPJ_CLRSPC_SRGB)) {
return std::nullopt;
}
// The channel count of a JPX image can be different from the PDF color
// space's component count.
if (jpx_info.channels > 3) {
return JpxDecodeAction::kConvertArgbToRgb;
}
return JpxDecodeAction::kUseRgb;
}
if (pdf_colorspace ==
CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceCMYK)) {
if (!IsJPXColorSpaceOrUnspecifiedOrUnknown(/*actual=*/jpx_info.colorspace,
/*expected=*/OPJ_CLRSPC_CMYK)) {
return std::nullopt;
}
return JpxDecodeAction::kUseCmyk;
}
// Many PDFs generated by iOS meets this condition. Handle the discrepancy.
// See https://crbug.com/345431077 for example.
if (pdf_colorspace->ComponentCount() == 3 && jpx_info.channels == 4 &&
jpx_info.colorspace == OPJ_CLRSPC_SRGB) {
return JpxDecodeAction::kConvertArgbToRgb;
}
if (pdf_colorspace->GetFamily() == CPDF_ColorSpace::Family::kIndexed &&
pdf_colorspace->ComponentCount() == 1) {
return JpxDecodeAction::kUseIndexed;
}
return JpxDecodeAction::kDoNothing;
}
JpxDecodeAction GetJpxDecodeActionFromImageColorSpace(
const CJPX_Decoder::JpxImageInfo& jpx_info) {
switch (jpx_info.colorspace) {
case OPJ_CLRSPC_UNKNOWN:
case OPJ_CLRSPC_UNSPECIFIED:
return jpx_info.channels == 3 ? JpxDecodeAction::kUseRgb
: JpxDecodeAction::kDoNothing;
case OPJ_CLRSPC_SYCC:
case OPJ_CLRSPC_EYCC:
return JpxDecodeAction::kDoNothing;
case OPJ_CLRSPC_SRGB:
return jpx_info.channels > 3 ? JpxDecodeAction::kConvertArgbToRgb
: JpxDecodeAction::kUseRgb;
case OPJ_CLRSPC_GRAY:
return JpxDecodeAction::kUseGray;
case OPJ_CLRSPC_CMYK:
return JpxDecodeAction::kUseCmyk;
}
NOTREACHED();
}
int GetComponentCountFromJpxImageInfo(
const CJPX_Decoder::JpxImageInfo& jpx_info) {
switch (jpx_info.colorspace) {
case OPJ_CLRSPC_UNKNOWN:
case OPJ_CLRSPC_UNSPECIFIED:
return jpx_info.channels;
case OPJ_CLRSPC_GRAY:
return 1;
case OPJ_CLRSPC_SRGB:
case OPJ_CLRSPC_SYCC:
case OPJ_CLRSPC_EYCC:
return 3;
case OPJ_CLRSPC_CMYK:
return 4;
}
NOTREACHED();
}
} // namespace
// static
std::optional<JpxDecodeConversion> JpxDecodeConversion::Create(
const CJPX_Decoder::JpxImageInfo& jpx_info,
const CPDF_ColorSpace* pdf_colorspace) {
// When the PDF does not provide a color space, check the image color space.
std::optional<JpxDecodeAction> maybe_action =
pdf_colorspace
? GetJpxDecodeActionFromColorSpaces(jpx_info, pdf_colorspace)
: GetJpxDecodeActionFromImageColorSpace(jpx_info);
if (!maybe_action.has_value()) {
return std::nullopt;
}
JpxDecodeConversion conversion;
conversion.action_ = maybe_action.value();
switch (conversion.action_) {
case JpxDecodeAction::kDoNothing:
break;
case JpxDecodeAction::kUseGray:
conversion.override_colorspace_ =
CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceGray);
break;
case JpxDecodeAction::kUseIndexed:
break;
case JpxDecodeAction::kUseRgb:
DCHECK_GE(jpx_info.channels, 3);
conversion.override_colorspace_ = nullptr;
break;
case JpxDecodeAction::kUseCmyk:
conversion.override_colorspace_ =
CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceCMYK);
break;
case JpxDecodeAction::kConvertArgbToRgb:
conversion.override_colorspace_ = nullptr;
break;
}
// If there exists a PDF colorspace, then CPDF_DIB already has the
// components count.
if (!pdf_colorspace) {
conversion.jpx_components_count_ =
GetComponentCountFromJpxImageInfo(jpx_info);
}
return conversion;
}
JpxDecodeConversion::JpxDecodeConversion() = default;
JpxDecodeConversion::JpxDecodeConversion(JpxDecodeConversion&&) noexcept =
default;
JpxDecodeConversion& JpxDecodeConversion::operator=(
JpxDecodeConversion&&) noexcept = default;
JpxDecodeConversion::~JpxDecodeConversion() = default;