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_