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

R=vandebo@chromium.org

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

git-svn-id: http://skia.googlecode.com/svn/trunk/src@11937 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/pdf/SkPDFDevice.cpp b/pdf/SkPDFDevice.cpp
index 1aed856..a7d2bd4 100644
--- a/pdf/SkPDFDevice.cpp
+++ b/pdf/SkPDFDevice.cpp
@@ -648,16 +648,11 @@
     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);
+        // Shape has to be flatten before we get here.
+        if (matrix.hasPerspective()) {
+            NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
             return;
         }
-
         if (paint.getXfermode()) {
             paint.getXfermode()->asMode(&fXfermode);
         }
@@ -706,9 +701,8 @@
       fClipStack(NULL),
       fEncoder(NULL),
       fRasterDpi(SkFloatToScalar(72.0f)) {
-    // 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..60992c3 100644
--- a/pdf/SkPDFShader.cpp
+++ b/pdf/SkPDFShader.cpp
@@ -210,16 +210,73 @@
     }
 }
 
-static SkString linearCode(const SkShader::GradientInfo& info) {
-    SkString function("{pop\n");  // Just ditch the y value.
+/**
+ *  Returns PS function code that applies inverse 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 executing this code on a PS stack, the last 2 elements are updated
+ *  while the rest of the stack is preserved intact.
+ *  inversePerspectiveMatrix 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
+
+    const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
+    const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
+    const 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
+                "3 1 roll "             // y*p1+x*p0+p2 x y
+                "2 index "              // z x y y*p1+x*p0+p2
+                "div "                  // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
+                "3 1 roll "             // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
+                "exch "                 // 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 +296,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 +307,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 +347,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 +362,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 +460,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 +791,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,10 +885,24 @@
     // the gradient can be drawn on on the unit segment.
     SkMatrix mapperMatrix;
     unitToPointsMatrix(transformPoints, &mapperMatrix);
+
     SkMatrix finalMatrix = fState.get()->fCanvasTransform;
     finalMatrix.preConcat(fState.get()->fShaderTransform);
     finalMatrix.preConcat(mapperMatrix);
 
+    // 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.
+    SkMatrix perspectiveInverseOnly = SkMatrix::I();
+    if (finalMatrix.hasPerspective()) {
+        if (!split_perspective(finalMatrix,
+                               &finalMatrix, &perspectiveInverseOnly)) {
+            return;
+        }
+    }
+
     SkRect bbox;
     bbox.set(fState.get()->fBBox);
     if (!inverseTransformBBox(finalMatrix, &bbox)) {
@@ -812,9 +931,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);