Move JpxDecodeConversion into its own file Over time, cpdf_dib.cpp has accumulated a lot of JPEG2000 decode logic. Move a large portion of that into a separate file. Change-Id: I5b0e8af3bf079a2d3a3bf2105550ba8c25cce923 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/135390 Reviewed-by: Tom Sepez <tsepez@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/page/BUILD.gn b/core/fpdfapi/page/BUILD.gn index f6bbd74..f752eae 100644 --- a/core/fpdfapi/page/BUILD.gn +++ b/core/fpdfapi/page/BUILD.gn
@@ -106,6 +106,8 @@ "cpdf_transparency.cpp", "cpdf_transparency.h", "ipdf_page.h", + "jpx_decode_conversion.cpp", + "jpx_decode_conversion.h", ] configs += [ "../../../:pdfium_strict_config" ] public_deps = [
diff --git a/core/fpdfapi/page/cpdf_dib.cpp b/core/fpdfapi/page/cpdf_dib.cpp index cf117c8..940497a 100644 --- a/core/fpdfapi/page/cpdf_dib.cpp +++ b/core/fpdfapi/page/cpdf_dib.cpp
@@ -19,6 +19,7 @@ #include "core/fpdfapi/page/cpdf_image.h" #include "core/fpdfapi/page/cpdf_imageobject.h" #include "core/fpdfapi/page/cpdf_indexedcs.h" +#include "core/fpdfapi/page/jpx_decode_conversion.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_document.h" @@ -40,7 +41,6 @@ #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_memcpy_wrappers.h" #include "core/fxcrt/fx_safe_types.h" -#include "core/fxcrt/notreached.h" #include "core/fxcrt/span_util.h" #include "core/fxcrt/stl_util.h" #include "core/fxcrt/zip.h" @@ -107,207 +107,6 @@ return CJPX_Decoder::ColorSpaceOption::kNormal; } -enum class JpxDecodeAction { - kDoNothing, - kUseGray, - kUseIndexed, - kUseRgb, - kUseCmyk, - kConvertArgbToRgb, -}; - -// 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(); -} - -class JpxDecodeConversion { - public: - static std::optional<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; - } - - JpxDecodeAction action() const { return action_; } - - const std::optional<RetainPtr<CPDF_ColorSpace>>& override_colorspace() const { - return override_colorspace_; - } - - const std::optional<int>& jpx_components_count() const { - return jpx_components_count_; - } - - bool swap_rgb() const { - return action_ == JpxDecodeAction::kUseRgb || - action_ == JpxDecodeAction::kConvertArgbToRgb; - } - - private: - JpxDecodeAction action_; - - // The colorspace to override the existing colorspace. - // - // std::nullopt means no override colorspace. - // nullptr means reset the colorspace. - std::optional<RetainPtr<CPDF_ColorSpace>> override_colorspace_; - - // The components count from the JPEG2000 image. - // - // std::nullopt means no new components count. - // Value <= 0 means failure. - std::optional<int> jpx_components_count_; -}; - } // namespace CPDF_DIB::CPDF_DIB(CPDF_Document* doc, RetainPtr<const CPDF_Stream> pStream)
diff --git a/core/fpdfapi/page/jpx_decode_conversion.cpp b/core/fpdfapi/page/jpx_decode_conversion.cpp new file mode 100644 index 0000000..10773ad --- /dev/null +++ b/core/fpdfapi/page/jpx_decode_conversion.cpp
@@ -0,0 +1,187 @@ +// 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;
diff --git a/core/fpdfapi/page/jpx_decode_conversion.h b/core/fpdfapi/page/jpx_decode_conversion.h new file mode 100644 index 0000000..ea2bbb1 --- /dev/null +++ b/core/fpdfapi/page/jpx_decode_conversion.h
@@ -0,0 +1,69 @@ +// 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. + +#ifndef CORE_FPDFAPI_PAGE_JPX_DECODE_CONVERSION_H_ +#define CORE_FPDFAPI_PAGE_JPX_DECODE_CONVERSION_H_ + +#include <optional> + +#include "core/fxcodec/jpx/cjpx_decoder.h" +#include "core/fxcrt/retain_ptr.h" + +class CPDF_ColorSpace; + +enum class JpxDecodeAction { + kDoNothing, + kUseGray, + kUseIndexed, + kUseRgb, + kUseCmyk, + kConvertArgbToRgb, +}; + +class JpxDecodeConversion { + public: + static std::optional<JpxDecodeConversion> Create( + const CJPX_Decoder::JpxImageInfo& jpx_info, + const CPDF_ColorSpace* pdf_colorspace); + + JpxDecodeConversion(const JpxDecodeConversion&) = delete; + JpxDecodeConversion& operator=(const JpxDecodeConversion&) = delete; + JpxDecodeConversion(JpxDecodeConversion&&) noexcept; + JpxDecodeConversion& operator=(JpxDecodeConversion&&) noexcept; + ~JpxDecodeConversion(); + + JpxDecodeAction action() const { return action_; } + + const std::optional<RetainPtr<CPDF_ColorSpace>>& override_colorspace() const { + return override_colorspace_; + } + + const std::optional<int>& jpx_components_count() const { + return jpx_components_count_; + } + + bool swap_rgb() const { + return action_ == JpxDecodeAction::kUseRgb || + action_ == JpxDecodeAction::kConvertArgbToRgb; + } + + private: + JpxDecodeConversion(); + + JpxDecodeAction action_; + + // The colorspace to override the existing colorspace. + // + // std::nullopt means no override colorspace. + // nullptr means reset the colorspace. + std::optional<RetainPtr<CPDF_ColorSpace>> override_colorspace_; + + // The components count from the JPEG2000 image. + // + // std::nullopt means no new components count. + // Value <= 0 means failure. + std::optional<int> jpx_components_count_; +}; + +#endif // CORE_FPDFAPI_PAGE_JPX_DECODE_CONVERSION_H_