Change behaviour of FPDF_RenderPageBitmapWithMatrix

This CL changes the behavior of FPDF_RenderPageBitmapWithMatrix so it
transforms the bitmap. Before, the page would be transformed and the
assumption was that it would be drawn on a bitmap with the same
dimensions as the original page. This does not work well because a
transformation generally changes the dimensions of the page. The
rectangles test is modified to include small rectangles in the corner
of the page, so that it's clear that the whole original page is being
displayed.

Bug: pdfium:849
Change-Id: Ie89f959a1605fea59a15d239ca871ccd939ec92b
Reviewed-on: https://pdfium-review.googlesource.com/13510
Commit-Queue: Nicolás Peña <npm@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Henrique Nakashima <hnakashima@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_page.cpp b/core/fpdfapi/page/cpdf_page.cpp
index dfbd342..6213e5f 100644
--- a/core/fpdfapi/page/cpdf_page.cpp
+++ b/core/fpdfapi/page/cpdf_page.cpp
@@ -178,6 +178,35 @@
   return matrix;
 }
 
+// This method follows the same apparent logic as GetDisplayMatrix(). For
+// example, consider point 0. First, take the top left coordinate of the
+// rectangle in the transformed space. Now, calculate what this point was in the
+// original space by inverting. Note that this is not necessarily the same as
+// the top left corner of the original rectangle.
+CFX_Matrix CPDF_Page::GetDisplayMatrixWithTransformation(
+    int xPos,
+    int yPos,
+    int xSize,
+    int ySize,
+    const CFX_Matrix& transformation) {
+  CFX_FloatRect rect(xPos, yPos, xPos + xSize, yPos + ySize);
+  rect = transformation.TransformRect(rect);
+  CFX_Matrix inverse = transformation.GetInverse();
+  CFX_PointF point0(rect.left, rect.top);
+  CFX_PointF point1(rect.left, rect.bottom);
+  CFX_PointF point2(rect.right, rect.top);
+  point0 = inverse.Transform(point0);
+  point1 = inverse.Transform(point1);
+  point2 = inverse.Transform(point2);
+
+  CFX_Matrix matrix = m_PageMatrix;
+  matrix.Concat(CFX_Matrix(
+      (point2.x - point0.x) / m_PageWidth, (point2.y - point0.y) / m_PageWidth,
+      (point1.x - point0.x) / m_PageHeight,
+      (point1.y - point0.y) / m_PageHeight, point0.x, point0.y));
+  return matrix;
+}
+
 int CPDF_Page::GetPageRotation() const {
   CPDF_Object* pRotate = GetPageAttr("Rotate");
   int rotate = pRotate ? (pRotate->GetInteger() / 90) % 4 : 0;
diff --git a/core/fpdfapi/page/cpdf_page.h b/core/fpdfapi/page/cpdf_page.h
index 1891e65..47fba3c 100644
--- a/core/fpdfapi/page/cpdf_page.h
+++ b/core/fpdfapi/page/cpdf_page.h
@@ -38,11 +38,16 @@
                               int xSize,
                               int ySize,
                               int iRotate) const;
+  CFX_Matrix GetDisplayMatrixWithTransformation(
+      int xPos,
+      int yPos,
+      int xSize,
+      int ySize,
+      const CFX_Matrix& transformation);
 
   float GetPageWidth() const { return m_PageWidth; }
   float GetPageHeight() const { return m_PageHeight; }
   CFX_FloatRect GetPageBBox() const { return m_BBox; }
-  const CFX_Matrix& GetPageMatrix() const { return m_PageMatrix; }
   int GetPageRotation() const;
   CPDF_PageRenderCache* GetRenderCache() const { return m_pPageRender.get(); }
 
diff --git a/fpdfsdk/fpdfview.cpp b/fpdfsdk/fpdfview.cpp
index e93e8bc..5bcc643 100644
--- a/fpdfsdk/fpdfview.cpp
+++ b/fpdfsdk/fpdfview.cpp
@@ -1004,15 +1004,13 @@
     clipping_rect.top = clipping->top;
   }
   FX_RECT clip_rect = clipping_rect.ToFxRect();
-
-  CFX_Matrix transform_matrix = pPage->GetDisplayMatrix(
-      clip_rect.left, clip_rect.top, clip_rect.Width(), clip_rect.Height(), 0);
-  if (matrix) {
-    transform_matrix.Concat(CFX_Matrix(matrix->a, matrix->b, matrix->c,
-                                       matrix->d, matrix->e, matrix->f));
-  }
-  RenderPageImpl(pContext, pPage, transform_matrix, clip_rect, flags, true,
-                 nullptr);
+  RenderPageImpl(
+      pContext, pPage,
+      pPage->GetDisplayMatrixWithTransformation(
+          clip_rect.left, clip_rect.top, clip_rect.Width(), clip_rect.Height(),
+          CFX_Matrix(matrix->a, matrix->b, matrix->c, matrix->d, matrix->e,
+                     matrix->f)),
+      clip_rect, flags, true, nullptr);
 
   pPage->SetRenderContext(nullptr);
 }
diff --git a/fpdfsdk/fpdfview_embeddertest.cpp b/fpdfsdk/fpdfview_embeddertest.cpp
index 8576104..97ba9d7 100644
--- a/fpdfsdk/fpdfview_embeddertest.cpp
+++ b/fpdfsdk/fpdfview_embeddertest.cpp
@@ -5,6 +5,7 @@
 #include <limits>
 #include <string>
 
+#include "core/fxcrt/fx_coordinates.h"
 #include "fpdfsdk/fpdfview_c_api_test.h"
 #include "public/fpdfview.h"
 #include "testing/embedder_test.h"
@@ -350,75 +351,119 @@
 }
 
 TEST_F(FPDFViewEmbeddertest, FPDF_RenderPageBitmapWithMatrix) {
-  const char kOriginalMD5[] = "210157942bcce97b057a1107a1fd62f8";
-  const char kTopLeftQuarterMD5[] = "c54d58dda13e3cd04eb63e1d0db0feda";
-  const char kTrimmedMD5[] = "88225d7951a21d0eef191cfed06c36ce";
-  const char kRotatedMD5[] = "7d38bc58aa50ad271bc432e77256d3de";
+  const char* const kRotatedMD5[4] = {
+      "0a90de37f52127619c3dfb642b5fa2fe", "d599429574ff0dcad3bc898ea8b874ca",
+      "0113386bb0bd45125bacc6dee78bfe78", "051fcfa4c1f9de28765705633a8ef3a9"};
+  const char kTopLeftQuarterMD5[] = "4982be08db3f6d2e6409186ebbced9eb";
+  const char kHoriStretchedMD5[] = "004bf38f3c5c76a644e6fca204747f21";
+  const char kRotateandStretchMD5[] = "0ea95cacc716d003cf063a2c5ed6c8d7";
 
   EXPECT_TRUE(OpenDocument("rectangles.pdf"));
   FPDF_PAGE page = LoadPage(0);
   EXPECT_NE(nullptr, page);
-  const int width = static_cast<int>(FPDF_GetPageWidth(page));
-  const int height = static_cast<int>(FPDF_GetPageHeight(page));
-  EXPECT_EQ(200, width);
-  EXPECT_EQ(200, height);
+  const int initial_width = static_cast<int>(FPDF_GetPageWidth(page));
+  const int initial_height = static_cast<int>(FPDF_GetPageHeight(page));
+  EXPECT_EQ(200, initial_width);
+  EXPECT_EQ(300, initial_height);
 
   FPDF_BITMAP bitmap = RenderPage(page);
-  CompareBitmap(bitmap, width, height, kOriginalMD5);
+  CompareBitmap(bitmap, initial_width, initial_height, kRotatedMD5[0]);
   FPDFBitmap_Destroy(bitmap);
 
-  // Try rendering with an identity matrix. The output should be the same as
-  // the RenderPage() output.
-  FS_MATRIX matrix;
-  matrix.a = 1;
-  matrix.b = 0;
-  matrix.c = 0;
-  matrix.d = 1;
-  matrix.e = 0;
-  matrix.f = 0;
-
+  int width;
+  int height;
   FS_RECTF rect;
   rect.left = 0;
   rect.top = 0;
-  rect.right = width;
-  rect.bottom = height;
+  FS_MATRIX matrix;
 
-  bitmap = FPDFBitmap_Create(width, height, 0);
-  FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF);
-  FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0);
-  CompareBitmap(bitmap, width, height, kOriginalMD5);
-  FPDFBitmap_Destroy(bitmap);
+  // Try the easy rotations: 0, 90, 180, 270 clockwise. The output should be the
+  // same as FPDF_RenderPageBitmap with the appropriate rotation flag. Per PDF
+  // spec section 4.2.2, a t degree rotation is represented by [cos(t) sin(t)
+  // -sin(t) cos(t) 0 0] (matrix goes on the right in the multiplication).
+  rect.right = initial_width;
+  rect.bottom = initial_height;
+  CFX_Matrix rot_matrices[4] = {
+      CFX_Matrix(1, 0, 0, 1, 0, 0), CFX_Matrix(0, -1, 1, 0, 0, 0),
+      CFX_Matrix(-1, 0, 0, -1, 0, 0), CFX_Matrix(0, 1, -1, 0, 0, 0)};
+  for (int rot = 0; rot < 4; ++rot) {
+    matrix.a = rot_matrices[rot].a;
+    matrix.b = rot_matrices[rot].b;
+    matrix.c = rot_matrices[rot].c;
+    matrix.d = rot_matrices[rot].d;
+    matrix.e = rot_matrices[rot].e;
+    matrix.f = rot_matrices[rot].f;
+    if (rot % 2 == 0) {
+      width = initial_width;
+      height = initial_height;
+    } else {
+      width = initial_height;
+      height = initial_width;
+    }
+    rect.right = width;
+    rect.bottom = height;
+
+    bitmap = FPDFBitmap_Create(width, height, 0);
+    FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF);
+    FPDF_RenderPageBitmap(bitmap, page, 0, 0, width, height, rot, 0);
+    CompareBitmap(bitmap, width, height, kRotatedMD5[rot]);
+    FPDFBitmap_Destroy(bitmap);
+
+    bitmap = FPDFBitmap_Create(width, height, 0);
+    FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF);
+    FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0);
+    CompareBitmap(bitmap, width, height, kRotatedMD5[rot]);
+    FPDFBitmap_Destroy(bitmap);
+  }
+  // TODO(npm): what to do with transformations that do not align the page with
+  // the axis, like a 45 degree rotation (currently, part of the page gets cut
+  // out). pdfium:849
 
   // Now render again with the image scaled smaller.
+  width = initial_width / 2;
+  height = initial_height / 2;
   matrix.a = 0.5;
+  matrix.b = 0;
+  matrix.c = 0;
   matrix.d = 0.5;
 
+  rect.right = width;
+  rect.bottom = height;
+
   bitmap = FPDFBitmap_Create(width, height, 0);
   FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF);
   FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0);
   CompareBitmap(bitmap, width, height, kTopLeftQuarterMD5);
   FPDFBitmap_Destroy(bitmap);
 
-  // Now render again with the image scaled larger horizontally (will be
-  // trimmed).
+  // Now render again with the image scaled larger horizontally.
+  width = initial_width * 2;
+  height = initial_height;
   matrix.a = 2;
   matrix.d = 1;
+  rect.right = width;
+  rect.bottom = height;
   bitmap = FPDFBitmap_Create(width, height, 0);
   FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF);
   FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0);
-  CompareBitmap(bitmap, width, height, kTrimmedMD5);
+  CompareBitmap(bitmap, width, height, kHoriStretchedMD5);
   FPDFBitmap_Destroy(bitmap);
 
-  // Now try a 90 degree rotation
+  // Test a rotation followed by a stretch.
+  width = initial_height * 2;
+  height = initial_width;
   matrix.a = 0;
-  matrix.b = 1;
-  matrix.c = -1;
+  matrix.b = -1;
+  matrix.c = 2;
   matrix.d = 0;
-  matrix.e = width;
+  matrix.e = 0;
+  matrix.f = 0;
+  rect.right = width;
+  rect.bottom = height;
   bitmap = FPDFBitmap_Create(width, height, 0);
   FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF);
   FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0);
-  CompareBitmap(bitmap, width, height, kRotatedMD5);
+  CompareBitmap(bitmap, width, height, kRotateandStretchMD5);
   FPDFBitmap_Destroy(bitmap);
 
   UnloadPage(page);
diff --git a/public/fpdfview.h b/public/fpdfview.h
index c74fcb9..135d00a 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -666,7 +666,7 @@
 //                          by FPDFBitmap_Create or retrieved by
 //                          FPDFImageObj_GetBitmap.
 //          page        -   Handle to the page. Returned by FPDF_LoadPage
-//          matrix      -   The transform matrix.
+//          matrix      -   The transform matrix. It must be invertible.
 //          clipping    -   The rect to clip to.
 //          flags       -   0 for normal display, or combination of the Page
 //                          Rendering flags defined above. With the FPDF_ANNOT
@@ -674,7 +674,7 @@
 //                          user-interaction, which are all annotations except
 //                          widget and popup annotations.
 // Return value:
-//          None.
+//          None. Note that behavior is undefined if det of |matrix| is 0.
 FPDF_EXPORT void FPDF_CALLCONV
 FPDF_RenderPageBitmapWithMatrix(FPDF_BITMAP bitmap,
                                 FPDF_PAGE page,
diff --git a/testing/resources/rectangles.in b/testing/resources/rectangles.in
index ed71cf8..49932ff 100644
--- a/testing/resources/rectangles.in
+++ b/testing/resources/rectangles.in
@@ -6,7 +6,7 @@
 endobj
 {{object 2 0}} <<
   /Type /Pages
-  /MediaBox [ 0 0 200 200 ]
+  /MediaBox [ 0 0 200 300 ]
   /Count 1
   /Kids [ 3 0 R ]
 >>
@@ -22,13 +22,17 @@
 stream
 q
 0 0 0 rg
-10 80 50 30 re B*
+0 290 10 10 re B*
+10 150 50 30 re B*
 0 0 1 rg
-70 135 50 30 re B*
+190 290 10 10 re B*
+70 232 50 30 re B*
 0 1 0 rg
-130 80 50 30 re B*
+190 0 10 10 re B*
+130 150 50 30 re B*
 1 0 0 rg
-70 25 50 30 re B*
+0 0 10 10 re B*
+70 67 50 30 re B*
 Q
 endstream
 endobj
diff --git a/testing/resources/rectangles.pdf b/testing/resources/rectangles.pdf
index 718bee5..7bad251 100644
--- a/testing/resources/rectangles.pdf
+++ b/testing/resources/rectangles.pdf
@@ -7,7 +7,7 @@
 endobj
 2 0 obj <<
   /Type /Pages
-  /MediaBox [ 0 0 200 200 ]
+  /MediaBox [ 0 0 200 300 ]
   /Count 1
   /Kids [ 3 0 R ]
 >>
@@ -23,13 +23,17 @@
 stream
 q
 0 0 0 rg
-10 80 50 30 re B*
+0 290 10 10 re B*
+10 150 50 30 re B*
 0 0 1 rg
-70 135 50 30 re B*
+190 290 10 10 re B*
+70 232 50 30 re B*
 0 1 0 rg
-130 80 50 30 re B*
+190 0 10 10 re B*
+130 150 50 30 re B*
 1 0 0 rg
-70 25 50 30 re B*
+0 0 10 10 re B*
+70 67 50 30 re B*
 Q
 endstream
 endobj
@@ -42,5 +46,5 @@
 0000000230 00000 n 
 trailer<< /Root 1 0 R /Size 5 >>
 startxref
-382
+456
 %%EOF