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_