Fix JPEG2000 image decoding with an indexed colorspace

Fix a regression in JPEG2000 image decoding, where the PDF specifies an
indexed colorspace for a grayscale JPEG2000 image. Add a test case for
this, where the JPEG2000 started as a PNG with a 2-bit colormap. The PNG
then gets converted to JPEG2000 using Imagemagick as follows:

convert input.png -colorspace Gray output.jp2

Bug: 393395940
Change-Id: I1a0265091a5ce906d3461fb803984c7345f6fe8e
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/128390
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_dib.cpp b/core/fpdfapi/page/cpdf_dib.cpp
index 4e2dc2c..fa2e93c 100644
--- a/core/fpdfapi/page/cpdf_dib.cpp
+++ b/core/fpdfapi/page/cpdf_dib.cpp
@@ -109,6 +109,7 @@
   kFail,
   kDoNothing,
   kUseGray,
+  kUseIndexed,
   kUseRgb,
   kUseCmyk,
   kConvertArgbToRgb,
@@ -169,6 +170,11 @@
     return JpxDecodeAction::kConvertArgbToRgb;
   }
 
+  if (pdf_colorspace->GetFamily() == CPDF_ColorSpace::Family::kIndexed &&
+      pdf_colorspace->ComponentCount() == 1) {
+    return JpxDecodeAction::kUseIndexed;
+  }
+
   return JpxDecodeAction::kDoNothing;
 }
 
@@ -690,6 +696,9 @@
           CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceGray);
       break;
 
+    case JpxDecodeAction::kUseIndexed:
+      break;
+
     case JpxDecodeAction::kUseRgb:
       DCHECK(image_info.channels >= 3);
       swap_rgb = true;
@@ -705,6 +714,7 @@
       swap_rgb = true;
       convert_argb_to_rgb = true;
       m_pColorSpace.Reset();
+      break;
   }
 
   // If |original_colorspace| exists, then LoadColorInfo() already set
@@ -720,7 +730,8 @@
   }
 
   FXDIB_Format format;
-  if (action == JpxDecodeAction::kUseGray) {
+  if (action == JpxDecodeAction::kUseGray ||
+      action == JpxDecodeAction::kUseIndexed) {
     format = FXDIB_Format::k8bppRgb;
   } else if (action == JpxDecodeAction::kUseRgb && image_info.channels == 3) {
     format = FXDIB_Format::kBgr;
diff --git a/testing/resources/pixel/jpxdecode_indexed.in b/testing/resources/pixel/jpxdecode_indexed.in
new file mode 100644
index 0000000..a48b3fe
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_indexed.in
@@ -0,0 +1,56 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 100 100]
+  /Resources <<
+    /XObject <<
+      /ImIndexed 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+100 0 0 100 0 0 cm
+/ImIndexed Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 2
+  /ColorSpace [/Indexed /DeviceRGB 3 <FF0000 00FF00 0000FF FFFFFF>]
+  /Filter [/ASCII85Decode /FlateDecode /JPXDecode]
+  /Height 100
+  /Width 100
+  {{streamlen}}
+>>
+stream
+GhQY8@,T-6:f-*3C?K&6?k>m[W@Z+?0N%nV)5+>hONmjS@[S)_76im;N#<Md6#%83POijQ(r8m3+FXJn
+SG_:EIis@j==aW5@/uS=#R(0[(jJ;>#$)KT#0-Zt%)NJU&eYPmk^Nql.KcGe;/0Q3C5P>UV1O1_C7\LW
+X1L]$C/Jt_I\).RSuJ<trs9*=?t2!Dk"NK>+ipUkFJA?:G!Q=(g$'.Gk;o_=PS-2d/n_?"s4"Mag#u\E
+Pt?fuiTVVs8bHLN(acDfc[SuFAFj8b!9[jIk3qk7DL!d^DPGK$K,;`Lg^R$i><T%6lb<4I4obOV!,hd6
+3<~>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/jpxdecode_indexed_expected.pdf.0.png b/testing/resources/pixel/jpxdecode_indexed_expected.pdf.0.png
new file mode 100644
index 0000000..1660af1
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_indexed_expected.pdf.0.png
Binary files differ