API and test for retrieving metadata from image objects

Added FPDFImageObj_GetImageMetadata() for retriving the image metadata
of image objects, including its dimension, DPI, bits per pixel, and
colorspace.
    * Added a corresponding embedder test.

Bug=pdfium:677

Change-Id: I4229334d1ac2125b21a46e2e44ea937ea2e94b51
Reviewed-on: https://pdfium-review.googlesource.com/10110
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
Commit-Queue: Jane Liu <janeliulwq@google.com>
diff --git a/core/fpdfapi/render/cpdf_dibsource.h b/core/fpdfapi/render/cpdf_dibsource.h
index 50e9a03..66a6d4e 100644
--- a/core/fpdfapi/render/cpdf_dibsource.h
+++ b/core/fpdfapi/render/cpdf_dibsource.h
@@ -60,6 +60,7 @@
                           int clip_left,
                           int clip_width) const override;
 
+  const CPDF_ColorSpace* GetColorSpace() const { return m_pColorSpace; }
   uint32_t GetMatteColor() const { return m_MatteColor; }
 
   int StartLoadDIBSource(CPDF_Document* pDoc,
diff --git a/fpdfsdk/fpdfedit_embeddertest.cpp b/fpdfsdk/fpdfedit_embeddertest.cpp
index e62ef21..6dc177e 100644
--- a/fpdfsdk/fpdfedit_embeddertest.cpp
+++ b/fpdfsdk/fpdfedit_embeddertest.cpp
@@ -1084,3 +1084,50 @@
 
   UnloadPage(page);
 }
+
+TEST_F(FPDFEditEmbeddertest, GetImageMetadata) {
+  ASSERT_TRUE(OpenDocument("embedded_images.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  // Check that getting the metadata of a null object would fail.
+  FPDF_IMAGEOBJ_METADATA metadata;
+  EXPECT_FALSE(FPDFImageObj_GetImageMetadata(nullptr, page, &metadata));
+
+  // Check that receiving the metadata with a null metadata object would fail.
+  FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 35);
+  EXPECT_FALSE(FPDFImageObj_GetImageMetadata(obj, page, nullptr));
+
+  // Check that when retrieving an image object's metadata without passing in
+  // |page|, all values are correct, with the last two being default values.
+  ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj));
+  ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, nullptr, &metadata));
+  EXPECT_EQ(92u, metadata.width);
+  EXPECT_EQ(68u, metadata.height);
+  EXPECT_NEAR(96.000000, metadata.horizontal_dpi, 0.001);
+  EXPECT_NEAR(96.000000, metadata.vertical_dpi, 0.001);
+  EXPECT_EQ(0u, metadata.bits_per_pixel);
+  EXPECT_EQ(FPDF_COLORSPACE_UNKNOWN, metadata.colorspace);
+
+  // Verify the metadata of a bitmap image with indexed colorspace.
+  ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, page, &metadata));
+  EXPECT_EQ(92u, metadata.width);
+  EXPECT_EQ(68u, metadata.height);
+  EXPECT_NEAR(96.000000, metadata.horizontal_dpi, 0.001);
+  EXPECT_NEAR(96.000000, metadata.vertical_dpi, 0.001);
+  EXPECT_EQ(1u, metadata.bits_per_pixel);
+  EXPECT_EQ(FPDF_COLORSPACE_INDEXED, metadata.colorspace);
+
+  // Verify the metadata of an image with RGB colorspace.
+  obj = FPDFPage_GetObject(page, 37);
+  ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj));
+  ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, page, &metadata));
+  EXPECT_EQ(126u, metadata.width);
+  EXPECT_EQ(106u, metadata.height);
+  EXPECT_NEAR(162.173752, metadata.horizontal_dpi, 0.001);
+  EXPECT_NEAR(162.555878, metadata.vertical_dpi, 0.001);
+  EXPECT_EQ(24u, metadata.bits_per_pixel);
+  EXPECT_EQ(FPDF_COLORSPACE_DEVICERGB, metadata.colorspace);
+
+  UnloadPage(page);
+}
diff --git a/fpdfsdk/fpdfeditimg.cpp b/fpdfsdk/fpdfeditimg.cpp
index f19e21c..662ad23 100644
--- a/fpdfsdk/fpdfeditimg.cpp
+++ b/fpdfsdk/fpdfeditimg.cpp
@@ -9,14 +9,40 @@
 #include "core/fpdfapi/cpdf_modulemgr.h"
 #include "core/fpdfapi/page/cpdf_image.h"
 #include "core/fpdfapi/page/cpdf_imageobject.h"
+#include "core/fpdfapi/page/cpdf_page.h"
 #include "core/fpdfapi/page/cpdf_pageobject.h"
 #include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_name.h"
+#include "core/fpdfapi/render/cpdf_dibsource.h"
 #include "fpdfsdk/fsdk_define.h"
 #include "third_party/base/ptr_util.h"
 
 namespace {
 
+// These checks ensure the consistency of colorspace values across core/ and
+// public/.
+static_assert(PDFCS_DEVICEGRAY == FPDF_COLORSPACE_DEVICEGRAY,
+              "PDFCS_DEVICEGRAY value mismatch");
+static_assert(PDFCS_DEVICERGB == FPDF_COLORSPACE_DEVICERGB,
+              "PDFCS_DEVICERGB value mismatch");
+static_assert(PDFCS_DEVICECMYK == FPDF_COLORSPACE_DEVICECMYK,
+              "PDFCS_DEVICECMYK value mismatch");
+static_assert(PDFCS_CALGRAY == FPDF_COLORSPACE_CALGRAY,
+              "PDFCS_CALGRAY value mismatch");
+static_assert(PDFCS_CALRGB == FPDF_COLORSPACE_CALRGB,
+              "PDFCS_CALRGB value mismatch");
+static_assert(PDFCS_LAB == FPDF_COLORSPACE_LAB, "PDFCS_LAB value mismatch");
+static_assert(PDFCS_ICCBASED == FPDF_COLORSPACE_ICCBASED,
+              "PDFCS_ICCBASED value mismatch");
+static_assert(PDFCS_SEPARATION == FPDF_COLORSPACE_SEPARATION,
+              "PDFCS_SEPARATION value mismatch");
+static_assert(PDFCS_DEVICEN == FPDF_COLORSPACE_DEVICEN,
+              "PDFCS_DEVICEN value mismatch");
+static_assert(PDFCS_INDEXED == FPDF_COLORSPACE_INDEXED,
+              "PDFCS_INDEXED value mismatch");
+static_assert(PDFCS_PATTERN == FPDF_COLORSPACE_PATTERN,
+              "PDFCS_PATTERN value mismatch");
+
 bool LoadJpegHelper(FPDF_PAGE* pages,
                     int nCount,
                     FPDF_PAGEOBJECT image_object,
@@ -226,3 +252,49 @@
 
   return Utf16EncodeMaybeCopyAndReturnLength(wsFilters, buffer, buflen);
 }
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object,
+                              FPDF_PAGE page,
+                              FPDF_IMAGEOBJ_METADATA* metadata) {
+  CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object);
+  if (!pObj || !pObj->IsImage() || !metadata)
+    return false;
+
+  CFX_RetainPtr<CPDF_Image> pImg = pObj->AsImage()->GetImage();
+  if (!pImg)
+    return false;
+
+  const int nPixelWidth = pImg->GetPixelWidth();
+  const int nPixelHeight = pImg->GetPixelHeight();
+  metadata->width = nPixelWidth;
+  metadata->height = nPixelHeight;
+
+  const float nWidth = pObj->m_Right - pObj->m_Left;
+  const float nHeight = pObj->m_Top - pObj->m_Bottom;
+  constexpr int nPointsPerInch = 72;
+  if (nWidth != 0 && nHeight != 0) {
+    metadata->horizontal_dpi = nPixelWidth / nWidth * nPointsPerInch;
+    metadata->vertical_dpi = nPixelHeight / nHeight * nPointsPerInch;
+  }
+
+  metadata->bits_per_pixel = 0;
+  metadata->colorspace = FPDF_COLORSPACE_UNKNOWN;
+
+  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
+  if (!pPage || !pPage->m_pDocument.Get() || !pImg->GetStream())
+    return true;
+
+  auto pSource = pdfium::MakeRetain<CPDF_DIBSource>();
+  if (!pSource->StartLoadDIBSource(pPage->m_pDocument.Get(), pImg->GetStream(),
+                                   false, nullptr,
+                                   pPage->m_pPageResources.Get())) {
+    return true;
+  }
+
+  metadata->bits_per_pixel = pSource->GetBPP();
+  if (pSource->GetColorSpace())
+    metadata->colorspace = pSource->GetColorSpace()->GetFamily();
+
+  return true;
+}
diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c
index 88b7465..4dd4995 100644
--- a/fpdfsdk/fpdfview_c_api_test.c
+++ b/fpdfsdk/fpdfview_c_api_test.c
@@ -138,6 +138,7 @@
     CHK(FPDFImageObj_GetImageDataRaw);
     CHK(FPDFImageObj_GetImageFilterCount);
     CHK(FPDFImageObj_GetImageFilter);
+    CHK(FPDFImageObj_GetImageMetadata);
     CHK(FPDFPageObj_CreateNewPath);
     CHK(FPDFPageObj_CreateNewRect);
     CHK(FPDFPath_SetStrokeColor);
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h
index f757445..c547f07 100644
--- a/public/fpdf_edit.h
+++ b/public/fpdf_edit.h
@@ -20,6 +20,20 @@
 #define FPDF_GetRValue(argb) ((uint8_t)((argb) >> 16))
 #define FPDF_GetAValue(argb) ((uint8_t)((argb) >> 24))
 
+// Refer to PDF Reference version 1.7 table 4.12 for all color space families.
+#define FPDF_COLORSPACE_UNKNOWN 0
+#define FPDF_COLORSPACE_DEVICEGRAY 1
+#define FPDF_COLORSPACE_DEVICERGB 2
+#define FPDF_COLORSPACE_DEVICECMYK 3
+#define FPDF_COLORSPACE_CALGRAY 4
+#define FPDF_COLORSPACE_CALRGB 5
+#define FPDF_COLORSPACE_LAB 6
+#define FPDF_COLORSPACE_ICCBASED 7
+#define FPDF_COLORSPACE_SEPARATION 8
+#define FPDF_COLORSPACE_DEVICEN 9
+#define FPDF_COLORSPACE_INDEXED 10
+#define FPDF_COLORSPACE_PATTERN 11
+
 // The page object constants.
 #define FPDF_PAGEOBJ_UNKNOWN 0
 #define FPDF_PAGEOBJ_TEXT 1
@@ -47,6 +61,21 @@
 #define FPDF_PRINTMODE_POSTSCRIPT2 2
 #define FPDF_PRINTMODE_POSTSCRIPT3 3
 
+typedef struct FPDF_IMAGEOBJ_METADATA {
+  // The image width in pixels.
+  unsigned int width;
+  // The image height in pixels.
+  unsigned int height;
+  // The image's horizontal pixel-per-inch.
+  float horizontal_dpi;
+  // The image's vertical pixel-per-inch.
+  float vertical_dpi;
+  // The number of bits used to represent each pixel.
+  unsigned int bits_per_pixel;
+  // The image's colorspace. See above for the list of FPDF_COLORSPACE_*.
+  int colorspace;
+} FPDF_IMAGEOBJ_METADATA;
+
 #ifdef __cplusplus
 extern "C" {
 #endif  // __cplusplus
@@ -366,6 +395,22 @@
                             void* buffer,
                             unsigned long buflen);
 
+// Get the image metadata of |image_object|, including dimension, DPI, bits per
+// pixel, and colorspace. If the |image_object| is not an image object or if it
+// does not have an image, then the return value will be false. Otherwise,
+// failure to retrieve any specific parameter would result in its value being 0.
+//
+//   image_object - handle to an image object.
+//   page         - handle to the page that |image_object| is on. Required for
+//                  retrieving the image's bits per pixel and colorspace.
+//   metadata     - receives the image metadata; must not be NULL.
+//
+// Returns true if successful.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object,
+                              FPDF_PAGE page,
+                              FPDF_IMAGEOBJ_METADATA* metadata);
+
 // Create a new path object at an initial position.
 //
 //   x - initial horizontal position.