Index: src/pdf/SkPDFShader.cpp |
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp |
index 70fb616a24f672cbf0b57603cf400defb69d0973..60992c3bf6dfe06281827ea08fdde120e5f43418 100644 |
--- a/src/pdf/SkPDFShader.cpp |
+++ b/src/pdf/SkPDFShader.cpp |
@@ -210,16 +210,73 @@ static void tileModeCode(SkShader::TileMode mode, SkString* result) { |
} |
} |
-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 @@ static SkString radialCode(const SkShader::GradientInfo& info) { |
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 @@ static SkString twoPointRadialCode(const SkShader::GradientInfo& info) { |
// 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 @@ static SkString twoPointRadialCode(const SkShader::GradientInfo& info) { |
/* 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 @@ static SkString twoPointConicalCode(const SkShader::GradientInfo& info) { |
// 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 @@ static SkString twoPointConicalCode(const SkShader::GradientInfo& info) { |
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 @@ SkPDFAlphaFunctionShader::SkPDFAlphaFunctionShader(SkPDFShader::State* state) |
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 @@ SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state) |
// 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 @@ SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state) |
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); |