diff options
author | edisonn@google.com <edisonn@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-10-24 13:19:28 +0000 |
---|---|---|
committer | edisonn@google.com <edisonn@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-10-24 13:19:28 +0000 |
commit | 83d8eda890def8c119794deeec6244c67da83ac8 (patch) | |
tree | 3a3d659a25be0f0ac4abbdfc4346dabbb4d4b175 /src/pdf | |
parent | ac90d1d0b1a02e3dfdc8780f0a35e0e6855d6ce1 (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.cpp | 16 | ||||
-rw-r--r-- | src/pdf/SkPDFShader.cpp | 137 |
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); |