PDF: support perspective in simple shaders. (this version does not work well with tilling)

Review URL: https://codereview.chromium.org/26389006

git-svn-id: http://skia.googlecode.com/svn/trunk/src@11864 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/pdf/SkPDFDevice.cpp b/pdf/SkPDFDevice.cpp
index e90edb1..e3134f4 100644
--- a/pdf/SkPDFDevice.cpp
+++ b/pdf/SkPDFDevice.cpp
@@ -648,16 +648,8 @@
     void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
               const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
         fDstFormXObject = NULL;
-        if (matrix.hasPerspective() ||
-                (paint.getShader() &&
-                 paint.getShader()->getLocalMatrix().hasPerspective())) {
-            // Just report that PDF does not supports perspective
-            // TODO(edisonn): update the shape when possible
-            // or dump in an image otherwise
-            NOT_IMPLEMENTED(true, false);
-            return;
-        }
-
+        // Shape has to be flatten before we get here.
+        NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
         if (paint.getXfermode()) {
             paint.getXfermode()->asMode(&fXfermode);
         }
@@ -705,9 +697,8 @@
       fLastMarginContentEntry(NULL),
       fClipStack(NULL),
       fEncoder(NULL) {
-    // just report that PDF does not supports perspective
-    // TODO(edisonn): update the shape when possible
-    // or dump in an image otherwise
+    // Just report that PDF does not supports perspective in the
+    // initial transform.
     NOT_IMPLEMENTED(initialTransform.hasPerspective(), true);
 
     // Skia generally uses the top left as the origin but PDF natively has the
diff --git a/pdf/SkPDFShader.cpp b/pdf/SkPDFShader.cpp
index 70fb616..3f1b3c8 100644
--- a/pdf/SkPDFShader.cpp
+++ b/pdf/SkPDFShader.cpp
@@ -210,16 +210,74 @@
     }
 }
 
-static SkString linearCode(const SkShader::GradientInfo& info) {
-    SkString function("{pop\n");  // Just ditch the y value.
+/**
+ *  Returns PS function code that would apply perspective to a x, y point.
+ *  The function assumes that the stack has at least two elements,
+ *  and that the top 2 elements are numeric values.
+ *  After ececuting this code on a PS stack, the last 2 elements are updated
+ *  while the rest of the stack is preserved intact.
+ *  xy2xy is the inverse perspective matrix.
+ */
+static SkString apply_perspective_to_coordinates(
+        const SkMatrix& inversePerspectiveMatrix) {
+    SkString code;
+    if (!inversePerspectiveMatrix.hasPerspective()) {
+        return code;
+    }
+
+    // Perspective matrix should be:
+    // 1   0  0
+    // 0   1  0
+    // p0 p1 p2
+
+    SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
+    SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
+    SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];
+
+    // y = y / (p2 + p0 x + p1 y)
+    // x = x / (p2 + p0 x + p1 y)
+
+    // Input on stack: x y
+    code.append("dup ");                // x y y
+    code.appendScalar(p1);              // x y y p1
+    code.append(" mul "                 // x y y*p1
+                " 2 index ");           // x y y*p1 x
+    code.appendScalar(p0);              // x y y p1 x p0
+    code.append(" mul ");               // x y y*p1 x*p0
+    code.appendScalar(p2);              // x y y p1 x*p0 p2
+    code.append("add "                  // x y y*p1 x*p0+p2
+                "add "                  // x y y*p1+x*p0+p2
+                "1 index "              // x y y*p1+x*p0+p2 y
+                "1 index "              // x y y*p1+x*p0+p2 y y*p1+x*p0+p2
+                "div "                  // x y y*p1+x*p0+p2 y/(y*p1+x*p0+p2)
+                "4 1 roll "             // y/(y*p1+x*p0+p2) x y y*p1+x*p0+p2
+                "exch "                 // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 y
+                "pop "                  // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
+                "div "                  // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
+                "exch\n");              // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
+
+    return code;
+}
+
+static SkString linearCode(const SkShader::GradientInfo& info,
+                           const SkMatrix& perspectiveRemover) {
+    SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
+    function.append("pop\n");  // Just ditch the y value.
     tileModeCode(info.fTileMode, &function);
     gradientFunctionCode(info, &function);
     function.append("}");
     return function;
 }
 
-static SkString radialCode(const SkShader::GradientInfo& info) {
+static SkString radialCode(const SkShader::GradientInfo& info,
+                           const SkMatrix& perspectiveRemover) {
     SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
     // Find the distance from the origin.
     function.append("dup "      // x y y
                     "mul "      // x y^2
@@ -239,7 +297,8 @@
    with one simplification, the coordinate space has been scaled so that
    Dr = 1.  This means we don't need to scale the entire equation by 1/Dr^2.
  */
-static SkString twoPointRadialCode(const SkShader::GradientInfo& info) {
+static SkString twoPointRadialCode(const SkShader::GradientInfo& info,
+                                   const SkMatrix& perspectiveRemover) {
     SkScalar dx = info.fPoint[0].fX - info.fPoint[1].fX;
     SkScalar dy = info.fPoint[0].fY - info.fPoint[1].fY;
     SkScalar sr = info.fRadius[0];
@@ -249,6 +308,9 @@
     // We start with a stack of (x y), copy it and then consume one copy in
     // order to calculate b and the other to calculate c.
     SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
     function.append("2 copy ");
 
     // Calculate -b and b^2.
@@ -286,7 +348,8 @@
 /* Conical gradient shader, based on the Canvas spec for radial gradients
    See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
  */
-static SkString twoPointConicalCode(const SkShader::GradientInfo& info) {
+static SkString twoPointConicalCode(const SkShader::GradientInfo& info,
+                                    const SkMatrix& perspectiveRemover) {
     SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
     SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
     SkScalar r0 = info.fRadius[0];
@@ -300,6 +363,9 @@
     // We start with a stack of (x y), copy it and then consume one copy in
     // order to calculate b and the other to calculate c.
     SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
     function.append("2 copy ");
 
     // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
@@ -395,7 +461,8 @@
     return function;
 }
 
-static SkString sweepCode(const SkShader::GradientInfo& info) {
+static SkString sweepCode(const SkShader::GradientInfo& info,
+                          const SkMatrix& perspectiveRemover) {
     SkString function("{exch atan 360 div\n");
     tileModeCode(info.fTileMode, &function);
     gradientFunctionCode(info, &function);
@@ -725,10 +792,49 @@
                                  SkMatrix::I());
 }
 
+// Finds affine and persp such that in = affine * persp.
+// but it returns the inverse of perspective matrix.
+static bool split_perspective(const SkMatrix in, SkMatrix* affine,
+                              SkMatrix* perspectiveInverse) {
+    const SkScalar p2 = in[SkMatrix::kMPersp2];
+
+    if (SkScalarNearlyZero(p2)) {
+        return false;
+    }
+
+    const SkScalar zero = SkIntToScalar(0);
+    const SkScalar one = SkIntToScalar(1);
+
+    const SkScalar sx = in[SkMatrix::kMScaleX];
+    const SkScalar kx = in[SkMatrix::kMSkewX];
+    const SkScalar tx = in[SkMatrix::kMTransX];
+    const SkScalar ky = in[SkMatrix::kMSkewY];
+    const SkScalar sy = in[SkMatrix::kMScaleY];
+    const SkScalar ty = in[SkMatrix::kMTransY];
+    const SkScalar p0 = in[SkMatrix::kMPersp0];
+    const SkScalar p1 = in[SkMatrix::kMPersp1];
+
+    // Perspective matrix would be:
+    // 1  0  0
+    // 0  1  0
+    // p0 p1 p2
+    // But we need the inverse of persp.
+    perspectiveInverse->setAll(one,          zero,       zero,
+                               zero,         one,        zero,
+                               -p0 / p2,     -p1/p2,     1/p2);
+
+    affine->setAll(sx - p0 * tx / p2,       kx - p1 * tx / p2,      tx / p2,
+                   ky - p0 * ty / p2,       sy - p1 * ty / p2,      ty / p2,
+                   zero,                    zero,                   one);
+
+    return true;
+}
+
 SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
         : SkPDFDict("Pattern"),
           fState(state) {
-    SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL;
+    SkString (*codeFunction)(const SkShader::GradientInfo& info,
+                             const SkMatrix& perspectiveRemover) = NULL;
     SkPoint transformPoints[2];
 
     // Depending on the type of the gradient, we want to transform the
@@ -780,8 +886,24 @@
     // the gradient can be drawn on on the unit segment.
     SkMatrix mapperMatrix;
     unitToPointsMatrix(transformPoints, &mapperMatrix);
+
+    SkMatrix perspectiveInverseOnly = SkMatrix::I();
+
     SkMatrix finalMatrix = fState.get()->fCanvasTransform;
     finalMatrix.preConcat(fState.get()->fShaderTransform);
+
+    // Preserves as much as posible in the final matrix, and only removes
+    // the perspective. The inverse of the perspective is stored in
+    // perspectiveInverseOnly matrix and has 3 useful numbers
+    // (p0, p1, p2), while everything else is either 0 or 1.
+    // In this way the shader will handle it eficiently, with minimal code.
+    if (finalMatrix.hasPerspective()) {
+        if (!split_perspective(finalMatrix,
+                               &finalMatrix, &perspectiveInverseOnly)) {
+            return;
+        }
+    }
+
     finalMatrix.preConcat(mapperMatrix);
 
     SkRect bbox;
@@ -812,9 +934,9 @@
             inverseMapperMatrix.mapRadius(info->fRadius[0]);
         twoPointRadialInfo.fRadius[1] =
             inverseMapperMatrix.mapRadius(info->fRadius[1]);
-        functionCode = codeFunction(twoPointRadialInfo);
+        functionCode = codeFunction(twoPointRadialInfo, perspectiveInverseOnly);
     } else {
-        functionCode = codeFunction(*info);
+        functionCode = codeFunction(*info, perspectiveInverseOnly);
     }
 
     SkAutoTUnref<SkPDFDict> pdfShader(new SkPDFDict);