aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/pdf
diff options
context:
space:
mode:
authorGravatar edisonn@google.com <edisonn@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-10-24 13:19:28 +0000
committerGravatar edisonn@google.com <edisonn@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-10-24 13:19:28 +0000
commit83d8eda890def8c119794deeec6244c67da83ac8 (patch)
tree3a3d659a25be0f0ac4abbdfc4346dabbb4d4b175 /src/pdf
parentac90d1d0b1a02e3dfdc8780f0a35e0e6855d6ce1 (diff)
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@11937 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src/pdf')
-rw-r--r--src/pdf/SkPDFDevice.cpp16
-rw-r--r--src/pdf/SkPDFShader.cpp137
2 files changed, 133 insertions, 20 deletions
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 1aed85632a..a7d2bd459a 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -648,16 +648,11 @@ private:
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 @@ SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
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/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 70fb616a24..60992c3bf6 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);