michael@0: michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: #include "SkPDFShader.h" michael@0: michael@0: #include "SkData.h" michael@0: #include "SkPDFCatalog.h" michael@0: #include "SkPDFDevice.h" michael@0: #include "SkPDFFormXObject.h" michael@0: #include "SkPDFGraphicState.h" michael@0: #include "SkPDFResourceDict.h" michael@0: #include "SkPDFUtils.h" michael@0: #include "SkScalar.h" michael@0: #include "SkStream.h" michael@0: #include "SkTemplates.h" michael@0: #include "SkThread.h" michael@0: #include "SkTSet.h" michael@0: #include "SkTypes.h" michael@0: michael@0: static bool inverseTransformBBox(const SkMatrix& matrix, SkRect* bbox) { michael@0: SkMatrix inverse; michael@0: if (!matrix.invert(&inverse)) { michael@0: return false; michael@0: } michael@0: inverse.mapRect(bbox); michael@0: return true; michael@0: } michael@0: michael@0: static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) { michael@0: SkVector vec = pts[1] - pts[0]; michael@0: SkScalar mag = vec.length(); michael@0: SkScalar inv = mag ? SkScalarInvert(mag) : 0; michael@0: michael@0: vec.scale(inv); michael@0: matrix->setSinCos(vec.fY, vec.fX); michael@0: matrix->preScale(mag, mag); michael@0: matrix->postTranslate(pts[0].fX, pts[0].fY); michael@0: } michael@0: michael@0: /* Assumes t + startOffset is on the stack and does a linear interpolation on t michael@0: between startOffset and endOffset from prevColor to curColor (for each color michael@0: component), leaving the result in component order on the stack. It assumes michael@0: there are always 3 components per color. michael@0: @param range endOffset - startOffset michael@0: @param curColor[components] The current color components. michael@0: @param prevColor[components] The previous color components. michael@0: @param result The result ps function. michael@0: */ michael@0: static void interpolateColorCode(SkScalar range, SkScalar* curColor, michael@0: SkScalar* prevColor, SkString* result) { michael@0: SkASSERT(range != SkIntToScalar(0)); michael@0: static const int kColorComponents = 3; michael@0: michael@0: // Figure out how to scale each color component. michael@0: SkScalar multiplier[kColorComponents]; michael@0: for (int i = 0; i < kColorComponents; i++) { michael@0: multiplier[i] = SkScalarDiv(curColor[i] - prevColor[i], range); michael@0: } michael@0: michael@0: // Calculate when we no longer need to keep a copy of the input parameter t. michael@0: // If the last component to use t is i, then dupInput[0..i - 1] = true michael@0: // and dupInput[i .. components] = false. michael@0: bool dupInput[kColorComponents]; michael@0: dupInput[kColorComponents - 1] = false; michael@0: for (int i = kColorComponents - 2; i >= 0; i--) { michael@0: dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0; michael@0: } michael@0: michael@0: if (!dupInput[0] && multiplier[0] == 0) { michael@0: result->append("pop "); michael@0: } michael@0: michael@0: for (int i = 0; i < kColorComponents; i++) { michael@0: // If the next components needs t and this component will consume a michael@0: // copy, make another copy. michael@0: if (dupInput[i] && multiplier[i] != 0) { michael@0: result->append("dup "); michael@0: } michael@0: michael@0: if (multiplier[i] == 0) { michael@0: result->appendScalar(prevColor[i]); michael@0: result->append(" "); michael@0: } else { michael@0: if (multiplier[i] != 1) { michael@0: result->appendScalar(multiplier[i]); michael@0: result->append(" mul "); michael@0: } michael@0: if (prevColor[i] != 0) { michael@0: result->appendScalar(prevColor[i]); michael@0: result->append(" add "); michael@0: } michael@0: } michael@0: michael@0: if (dupInput[i]) { michael@0: result->append("exch\n"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Generate Type 4 function code to map t=[0,1) to the passed gradient, michael@0: clamping at the edges of the range. The generated code will be of the form: michael@0: if (t < 0) { michael@0: return colorData[0][r,g,b]; michael@0: } else { michael@0: if (t < info.fColorOffsets[1]) { michael@0: return linearinterpolation(colorData[0][r,g,b], michael@0: colorData[1][r,g,b]); michael@0: } else { michael@0: if (t < info.fColorOffsets[2]) { michael@0: return linearinterpolation(colorData[1][r,g,b], michael@0: colorData[2][r,g,b]); michael@0: } else { michael@0: michael@0: ... } else { michael@0: return colorData[info.fColorCount - 1][r,g,b]; michael@0: } michael@0: ... michael@0: } michael@0: } michael@0: */ michael@0: static void gradientFunctionCode(const SkShader::GradientInfo& info, michael@0: SkString* result) { michael@0: /* We want to linearly interpolate from the previous color to the next. michael@0: Scale the colors from 0..255 to 0..1 and determine the multipliers michael@0: for interpolation. michael@0: C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}. michael@0: */ michael@0: static const int kColorComponents = 3; michael@0: typedef SkScalar ColorTuple[kColorComponents]; michael@0: SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount); michael@0: ColorTuple *colorData = colorDataAlloc.get(); michael@0: const SkScalar scale = SkScalarInvert(SkIntToScalar(255)); michael@0: for (int i = 0; i < info.fColorCount; i++) { michael@0: colorData[i][0] = SkScalarMul(SkColorGetR(info.fColors[i]), scale); michael@0: colorData[i][1] = SkScalarMul(SkColorGetG(info.fColors[i]), scale); michael@0: colorData[i][2] = SkScalarMul(SkColorGetB(info.fColors[i]), scale); michael@0: } michael@0: michael@0: // Clamp the initial color. michael@0: result->append("dup 0 le {pop "); michael@0: result->appendScalar(colorData[0][0]); michael@0: result->append(" "); michael@0: result->appendScalar(colorData[0][1]); michael@0: result->append(" "); michael@0: result->appendScalar(colorData[0][2]); michael@0: result->append(" }\n"); michael@0: michael@0: // The gradient colors. michael@0: int gradients = 0; michael@0: for (int i = 1 ; i < info.fColorCount; i++) { michael@0: if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) { michael@0: continue; michael@0: } michael@0: gradients++; michael@0: michael@0: result->append("{dup "); michael@0: result->appendScalar(info.fColorOffsets[i]); michael@0: result->append(" le {"); michael@0: if (info.fColorOffsets[i - 1] != 0) { michael@0: result->appendScalar(info.fColorOffsets[i - 1]); michael@0: result->append(" sub\n"); michael@0: } michael@0: michael@0: interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1], michael@0: colorData[i], colorData[i - 1], result); michael@0: result->append("}\n"); michael@0: } michael@0: michael@0: // Clamp the final color. michael@0: result->append("{pop "); michael@0: result->appendScalar(colorData[info.fColorCount - 1][0]); michael@0: result->append(" "); michael@0: result->appendScalar(colorData[info.fColorCount - 1][1]); michael@0: result->append(" "); michael@0: result->appendScalar(colorData[info.fColorCount - 1][2]); michael@0: michael@0: for (int i = 0 ; i < gradients + 1; i++) { michael@0: result->append("} ifelse\n"); michael@0: } michael@0: } michael@0: michael@0: /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */ michael@0: static void tileModeCode(SkShader::TileMode mode, SkString* result) { michael@0: if (mode == SkShader::kRepeat_TileMode) { michael@0: result->append("dup truncate sub\n"); // Get the fractional part. michael@0: result->append("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1) michael@0: return; michael@0: } michael@0: michael@0: if (mode == SkShader::kMirror_TileMode) { michael@0: // Map t mod 2 into [0, 1, 1, 0]. michael@0: // Code Stack michael@0: result->append("abs " // Map negative to positive. michael@0: "dup " // t.s t.s michael@0: "truncate " // t.s t michael@0: "dup " // t.s t t michael@0: "cvi " // t.s t T michael@0: "2 mod " // t.s t (i mod 2) michael@0: "1 eq " // t.s t true|false michael@0: "3 1 roll " // true|false t.s t michael@0: "sub " // true|false 0.s michael@0: "exch " // 0.s true|false michael@0: "{1 exch sub} if\n"); // 1 - 0.s|0.s michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Returns PS function code that applies inverse perspective michael@0: * to a x, y point. michael@0: * The function assumes that the stack has at least two elements, michael@0: * and that the top 2 elements are numeric values. michael@0: * After executing this code on a PS stack, the last 2 elements are updated michael@0: * while the rest of the stack is preserved intact. michael@0: * inversePerspectiveMatrix is the inverse perspective matrix. michael@0: */ michael@0: static SkString apply_perspective_to_coordinates( michael@0: const SkMatrix& inversePerspectiveMatrix) { michael@0: SkString code; michael@0: if (!inversePerspectiveMatrix.hasPerspective()) { michael@0: return code; michael@0: } michael@0: michael@0: // Perspective matrix should be: michael@0: // 1 0 0 michael@0: // 0 1 0 michael@0: // p0 p1 p2 michael@0: michael@0: const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0]; michael@0: const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1]; michael@0: const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2]; michael@0: michael@0: // y = y / (p2 + p0 x + p1 y) michael@0: // x = x / (p2 + p0 x + p1 y) michael@0: michael@0: // Input on stack: x y michael@0: code.append(" dup "); // x y y michael@0: code.appendScalar(p1); // x y y p1 michael@0: code.append(" mul " // x y y*p1 michael@0: " 2 index "); // x y y*p1 x michael@0: code.appendScalar(p0); // x y y p1 x p0 michael@0: code.append(" mul "); // x y y*p1 x*p0 michael@0: code.appendScalar(p2); // x y y p1 x*p0 p2 michael@0: code.append(" add " // x y y*p1 x*p0+p2 michael@0: "add " // x y y*p1+x*p0+p2 michael@0: "3 1 roll " // y*p1+x*p0+p2 x y michael@0: "2 index " // z x y y*p1+x*p0+p2 michael@0: "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2) michael@0: "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x michael@0: "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 michael@0: "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2) michael@0: "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2) michael@0: return code; michael@0: } michael@0: michael@0: static SkString linearCode(const SkShader::GradientInfo& info, michael@0: const SkMatrix& perspectiveRemover) { michael@0: SkString function("{"); michael@0: michael@0: function.append(apply_perspective_to_coordinates(perspectiveRemover)); michael@0: michael@0: function.append("pop\n"); // Just ditch the y value. michael@0: tileModeCode(info.fTileMode, &function); michael@0: gradientFunctionCode(info, &function); michael@0: function.append("}"); michael@0: return function; michael@0: } michael@0: michael@0: static SkString radialCode(const SkShader::GradientInfo& info, michael@0: const SkMatrix& perspectiveRemover) { michael@0: SkString function("{"); michael@0: michael@0: function.append(apply_perspective_to_coordinates(perspectiveRemover)); michael@0: michael@0: // Find the distance from the origin. michael@0: function.append("dup " // x y y michael@0: "mul " // x y^2 michael@0: "exch " // y^2 x michael@0: "dup " // y^2 x x michael@0: "mul " // y^2 x^2 michael@0: "add " // y^2+x^2 michael@0: "sqrt\n"); // sqrt(y^2+x^2) michael@0: michael@0: tileModeCode(info.fTileMode, &function); michael@0: gradientFunctionCode(info, &function); michael@0: function.append("}"); michael@0: return function; michael@0: } michael@0: michael@0: /* The math here is all based on the description in Two_Point_Radial_Gradient, michael@0: with one simplification, the coordinate space has been scaled so that michael@0: Dr = 1. This means we don't need to scale the entire equation by 1/Dr^2. michael@0: */ michael@0: static SkString twoPointRadialCode(const SkShader::GradientInfo& info, michael@0: const SkMatrix& perspectiveRemover) { michael@0: SkScalar dx = info.fPoint[0].fX - info.fPoint[1].fX; michael@0: SkScalar dy = info.fPoint[0].fY - info.fPoint[1].fY; michael@0: SkScalar sr = info.fRadius[0]; michael@0: SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) - SK_Scalar1; michael@0: bool posRoot = info.fRadius[1] > info.fRadius[0]; michael@0: michael@0: // We start with a stack of (x y), copy it and then consume one copy in michael@0: // order to calculate b and the other to calculate c. michael@0: SkString function("{"); michael@0: michael@0: function.append(apply_perspective_to_coordinates(perspectiveRemover)); michael@0: michael@0: function.append("2 copy "); michael@0: michael@0: // Calculate -b and b^2. michael@0: function.appendScalar(dy); michael@0: function.append(" mul exch "); michael@0: function.appendScalar(dx); michael@0: function.append(" mul add "); michael@0: function.appendScalar(sr); michael@0: function.append(" sub 2 mul neg dup dup mul\n"); michael@0: michael@0: // Calculate c michael@0: function.append("4 2 roll dup mul exch dup mul add "); michael@0: function.appendScalar(SkScalarMul(sr, sr)); michael@0: function.append(" sub\n"); michael@0: michael@0: // Calculate the determinate michael@0: function.appendScalar(SkScalarMul(SkIntToScalar(4), a)); michael@0: function.append(" mul sub abs sqrt\n"); michael@0: michael@0: // And then the final value of t. michael@0: if (posRoot) { michael@0: function.append("sub "); michael@0: } else { michael@0: function.append("add "); michael@0: } michael@0: function.appendScalar(SkScalarMul(SkIntToScalar(2), a)); michael@0: function.append(" div\n"); michael@0: michael@0: tileModeCode(info.fTileMode, &function); michael@0: gradientFunctionCode(info, &function); michael@0: function.append("}"); michael@0: return function; michael@0: } michael@0: michael@0: /* Conical gradient shader, based on the Canvas spec for radial gradients michael@0: See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient michael@0: */ michael@0: static SkString twoPointConicalCode(const SkShader::GradientInfo& info, michael@0: const SkMatrix& perspectiveRemover) { michael@0: SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX; michael@0: SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY; michael@0: SkScalar r0 = info.fRadius[0]; michael@0: SkScalar dr = info.fRadius[1] - info.fRadius[0]; michael@0: SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) - michael@0: SkScalarMul(dr, dr); michael@0: michael@0: // First compute t, if the pixel falls outside the cone, then we'll end michael@0: // with 'false' on the stack, otherwise we'll push 'true' with t below it michael@0: michael@0: // We start with a stack of (x y), copy it and then consume one copy in michael@0: // order to calculate b and the other to calculate c. michael@0: SkString function("{"); michael@0: michael@0: function.append(apply_perspective_to_coordinates(perspectiveRemover)); michael@0: michael@0: function.append("2 copy "); michael@0: michael@0: // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr). michael@0: function.appendScalar(dy); michael@0: function.append(" mul exch "); michael@0: function.appendScalar(dx); michael@0: function.append(" mul add "); michael@0: function.appendScalar(SkScalarMul(r0, dr)); michael@0: function.append(" add -2 mul dup dup mul\n"); michael@0: michael@0: // c = x^2 + y^2 + radius0^2 michael@0: function.append("4 2 roll dup mul exch dup mul add "); michael@0: function.appendScalar(SkScalarMul(r0, r0)); michael@0: function.append(" sub dup 4 1 roll\n"); michael@0: michael@0: // Contents of the stack at this point: c, b, b^2, c michael@0: michael@0: // if a = 0, then we collapse to a simpler linear case michael@0: if (a == 0) { michael@0: michael@0: // t = -c/b michael@0: function.append("pop pop div neg dup "); michael@0: michael@0: // compute radius(t) michael@0: function.appendScalar(dr); michael@0: function.append(" mul "); michael@0: function.appendScalar(r0); michael@0: function.append(" add\n"); michael@0: michael@0: // if r(t) < 0, then it's outside the cone michael@0: function.append("0 lt {pop false} {true} ifelse\n"); michael@0: michael@0: } else { michael@0: michael@0: // quadratic case: the Canvas spec wants the largest michael@0: // root t for which radius(t) > 0 michael@0: michael@0: // compute the discriminant (b^2 - 4ac) michael@0: function.appendScalar(SkScalarMul(SkIntToScalar(4), a)); michael@0: function.append(" mul sub dup\n"); michael@0: michael@0: // if d >= 0, proceed michael@0: function.append("0 ge {\n"); michael@0: michael@0: // an intermediate value we'll use to compute the roots: michael@0: // q = -0.5 * (b +/- sqrt(d)) michael@0: function.append("sqrt exch dup 0 lt {exch -1 mul} if"); michael@0: function.append(" add -0.5 mul dup\n"); michael@0: michael@0: // first root = q / a michael@0: function.appendScalar(a); michael@0: function.append(" div\n"); michael@0: michael@0: // second root = c / q michael@0: function.append("3 1 roll div\n"); michael@0: michael@0: // put the larger root on top of the stack michael@0: function.append("2 copy gt {exch} if\n"); michael@0: michael@0: // compute radius(t) for larger root michael@0: function.append("dup "); michael@0: function.appendScalar(dr); michael@0: function.append(" mul "); michael@0: function.appendScalar(r0); michael@0: function.append(" add\n"); michael@0: michael@0: // if r(t) > 0, we have our t, pop off the smaller root and we're done michael@0: function.append(" 0 gt {exch pop true}\n"); michael@0: michael@0: // otherwise, throw out the larger one and try the smaller root michael@0: function.append("{pop dup\n"); michael@0: function.appendScalar(dr); michael@0: function.append(" mul "); michael@0: function.appendScalar(r0); michael@0: function.append(" add\n"); michael@0: michael@0: // if r(t) < 0, push false, otherwise the smaller root is our t michael@0: function.append("0 le {pop false} {true} ifelse\n"); michael@0: function.append("} ifelse\n"); michael@0: michael@0: // d < 0, clear the stack and push false michael@0: function.append("} {pop pop pop false} ifelse\n"); michael@0: } michael@0: michael@0: // if the pixel is in the cone, proceed to compute a color michael@0: function.append("{"); michael@0: tileModeCode(info.fTileMode, &function); michael@0: gradientFunctionCode(info, &function); michael@0: michael@0: // otherwise, just write black michael@0: function.append("} {0 0 0} ifelse }"); michael@0: michael@0: return function; michael@0: } michael@0: michael@0: static SkString sweepCode(const SkShader::GradientInfo& info, michael@0: const SkMatrix& perspectiveRemover) { michael@0: SkString function("{exch atan 360 div\n"); michael@0: tileModeCode(info.fTileMode, &function); michael@0: gradientFunctionCode(info, &function); michael@0: function.append("}"); michael@0: return function; michael@0: } michael@0: michael@0: class SkPDFShader::State { michael@0: public: michael@0: SkShader::GradientType fType; michael@0: SkShader::GradientInfo fInfo; michael@0: SkAutoFree fColorData; // This provides storage for arrays in fInfo. michael@0: SkMatrix fCanvasTransform; michael@0: SkMatrix fShaderTransform; michael@0: SkIRect fBBox; michael@0: michael@0: SkBitmap fImage; michael@0: uint32_t fPixelGeneration; michael@0: SkShader::TileMode fImageTileModes[2]; michael@0: michael@0: State(const SkShader& shader, const SkMatrix& canvasTransform, michael@0: const SkIRect& bbox); michael@0: michael@0: bool operator==(const State& b) const; michael@0: michael@0: SkPDFShader::State* CreateAlphaToLuminosityState() const; michael@0: SkPDFShader::State* CreateOpaqueState() const; michael@0: michael@0: bool GradientHasAlpha() const; michael@0: michael@0: private: michael@0: State(const State& other); michael@0: State operator=(const State& rhs); michael@0: void AllocateGradientInfoStorage(); michael@0: }; michael@0: michael@0: class SkPDFFunctionShader : public SkPDFDict, public SkPDFShader { michael@0: SK_DECLARE_INST_COUNT(SkPDFFunctionShader) michael@0: public: michael@0: explicit SkPDFFunctionShader(SkPDFShader::State* state); michael@0: virtual ~SkPDFFunctionShader() { michael@0: if (isValid()) { michael@0: RemoveShader(this); michael@0: } michael@0: fResources.unrefAll(); michael@0: } michael@0: michael@0: virtual bool isValid() { return fResources.count() > 0; } michael@0: michael@0: void getResources(const SkTSet& knownResourceObjects, michael@0: SkTSet* newResourceObjects) { michael@0: GetResourcesHelper(&fResources, michael@0: knownResourceObjects, michael@0: newResourceObjects); michael@0: } michael@0: michael@0: private: michael@0: static SkPDFObject* RangeObject(); michael@0: michael@0: SkTDArray fResources; michael@0: SkAutoTDelete fState; michael@0: michael@0: SkPDFStream* makePSFunction(const SkString& psCode, SkPDFArray* domain); michael@0: typedef SkPDFDict INHERITED; michael@0: }; michael@0: michael@0: /** michael@0: * A shader for PDF gradients. This encapsulates the function shader michael@0: * inside a tiling pattern while providing a common pattern interface. michael@0: * The encapsulation allows the use of a SMask for transparency gradients. michael@0: */ michael@0: class SkPDFAlphaFunctionShader : public SkPDFStream, public SkPDFShader { michael@0: public: michael@0: explicit SkPDFAlphaFunctionShader(SkPDFShader::State* state); michael@0: virtual ~SkPDFAlphaFunctionShader() { michael@0: if (isValid()) { michael@0: RemoveShader(this); michael@0: } michael@0: } michael@0: michael@0: virtual bool isValid() { michael@0: return fColorShader.get() != NULL; michael@0: } michael@0: michael@0: private: michael@0: SkAutoTDelete fState; michael@0: michael@0: SkPDFGraphicState* CreateSMaskGraphicState(); michael@0: michael@0: void getResources(const SkTSet& knownResourceObjects, michael@0: SkTSet* newResourceObjects) { michael@0: fResourceDict->getReferencedResources(knownResourceObjects, michael@0: newResourceObjects, michael@0: true); michael@0: } michael@0: michael@0: SkAutoTUnref fColorShader; michael@0: SkAutoTUnref fResourceDict; michael@0: }; michael@0: michael@0: class SkPDFImageShader : public SkPDFStream, public SkPDFShader { michael@0: public: michael@0: explicit SkPDFImageShader(SkPDFShader::State* state); michael@0: virtual ~SkPDFImageShader() { michael@0: if (isValid()) { michael@0: RemoveShader(this); michael@0: } michael@0: fResources.unrefAll(); michael@0: } michael@0: michael@0: virtual bool isValid() { return size() > 0; } michael@0: michael@0: void getResources(const SkTSet& knownResourceObjects, michael@0: SkTSet* newResourceObjects) { michael@0: GetResourcesHelper(&fResources.toArray(), michael@0: knownResourceObjects, michael@0: newResourceObjects); michael@0: } michael@0: michael@0: private: michael@0: SkTSet fResources; michael@0: SkAutoTDelete fState; michael@0: }; michael@0: michael@0: SkPDFShader::SkPDFShader() {} michael@0: michael@0: // static michael@0: SkPDFObject* SkPDFShader::GetPDFShaderByState(State* inState) { michael@0: SkPDFObject* result; michael@0: michael@0: SkAutoTDelete shaderState(inState); michael@0: if (shaderState.get()->fType == SkShader::kNone_GradientType && michael@0: shaderState.get()->fImage.isNull()) { michael@0: // TODO(vandebo) This drops SKComposeShader on the floor. We could michael@0: // handle compose shader by pulling things up to a layer, drawing with michael@0: // the first shader, applying the xfer mode and drawing again with the michael@0: // second shader, then applying the layer to the original drawing. michael@0: return NULL; michael@0: } michael@0: michael@0: ShaderCanonicalEntry entry(NULL, shaderState.get()); michael@0: int index = CanonicalShaders().find(entry); michael@0: if (index >= 0) { michael@0: result = CanonicalShaders()[index].fPDFShader; michael@0: result->ref(); michael@0: return result; michael@0: } michael@0: michael@0: bool valid = false; michael@0: // The PDFShader takes ownership of the shaderSate. michael@0: if (shaderState.get()->fType == SkShader::kNone_GradientType) { michael@0: SkPDFImageShader* imageShader = michael@0: new SkPDFImageShader(shaderState.detach()); michael@0: valid = imageShader->isValid(); michael@0: result = imageShader; michael@0: } else { michael@0: if (shaderState.get()->GradientHasAlpha()) { michael@0: SkPDFAlphaFunctionShader* gradientShader = michael@0: SkNEW_ARGS(SkPDFAlphaFunctionShader, (shaderState.detach())); michael@0: valid = gradientShader->isValid(); michael@0: result = gradientShader; michael@0: } else { michael@0: SkPDFFunctionShader* functionShader = michael@0: SkNEW_ARGS(SkPDFFunctionShader, (shaderState.detach())); michael@0: valid = functionShader->isValid(); michael@0: result = functionShader; michael@0: } michael@0: } michael@0: if (!valid) { michael@0: delete result; michael@0: return NULL; michael@0: } michael@0: entry.fPDFShader = result; michael@0: CanonicalShaders().push(entry); michael@0: return result; // return the reference that came from new. michael@0: } michael@0: michael@0: // static michael@0: void SkPDFShader::RemoveShader(SkPDFObject* shader) { michael@0: SkAutoMutexAcquire lock(CanonicalShadersMutex()); michael@0: ShaderCanonicalEntry entry(shader, NULL); michael@0: int index = CanonicalShaders().find(entry); michael@0: SkASSERT(index >= 0); michael@0: CanonicalShaders().removeShuffle(index); michael@0: } michael@0: michael@0: // static michael@0: SkPDFObject* SkPDFShader::GetPDFShader(const SkShader& shader, michael@0: const SkMatrix& matrix, michael@0: const SkIRect& surfaceBBox) { michael@0: SkAutoMutexAcquire lock(CanonicalShadersMutex()); michael@0: return GetPDFShaderByState( michael@0: SkNEW_ARGS(State, (shader, matrix, surfaceBBox))); michael@0: } michael@0: michael@0: // static michael@0: SkTDArray& SkPDFShader::CanonicalShaders() { michael@0: // This initialization is only thread safe with gcc. michael@0: static SkTDArray gCanonicalShaders; michael@0: return gCanonicalShaders; michael@0: } michael@0: michael@0: // static michael@0: SkBaseMutex& SkPDFShader::CanonicalShadersMutex() { michael@0: // This initialization is only thread safe with gcc or when michael@0: // POD-style mutex initialization is used. michael@0: SK_DECLARE_STATIC_MUTEX(gCanonicalShadersMutex); michael@0: return gCanonicalShadersMutex; michael@0: } michael@0: michael@0: // static michael@0: SkPDFObject* SkPDFFunctionShader::RangeObject() { michael@0: // This initialization is only thread safe with gcc. michael@0: static SkPDFArray* range = NULL; michael@0: // This method is only used with CanonicalShadersMutex, so it's safe to michael@0: // populate domain. michael@0: if (range == NULL) { michael@0: range = new SkPDFArray; michael@0: range->reserve(6); michael@0: range->appendInt(0); michael@0: range->appendInt(1); michael@0: range->appendInt(0); michael@0: range->appendInt(1); michael@0: range->appendInt(0); michael@0: range->appendInt(1); michael@0: } michael@0: return range; michael@0: } michael@0: michael@0: static SkPDFResourceDict* get_gradient_resource_dict( michael@0: SkPDFObject* functionShader, michael@0: SkPDFObject* gState) { michael@0: SkPDFResourceDict* dict = new SkPDFResourceDict(); michael@0: michael@0: if (functionShader != NULL) { michael@0: dict->insertResourceAsReference( michael@0: SkPDFResourceDict::kPattern_ResourceType, 0, functionShader); michael@0: } michael@0: if (gState != NULL) { michael@0: dict->insertResourceAsReference( michael@0: SkPDFResourceDict::kExtGState_ResourceType, 0, gState); michael@0: } michael@0: michael@0: return dict; michael@0: } michael@0: michael@0: static void populate_tiling_pattern_dict(SkPDFDict* pattern, michael@0: SkRect& bbox, SkPDFDict* resources, michael@0: const SkMatrix& matrix) { michael@0: const int kTiling_PatternType = 1; michael@0: const int kColoredTilingPattern_PaintType = 1; michael@0: const int kConstantSpacing_TilingType = 1; michael@0: michael@0: pattern->insertName("Type", "Pattern"); michael@0: pattern->insertInt("PatternType", kTiling_PatternType); michael@0: pattern->insertInt("PaintType", kColoredTilingPattern_PaintType); michael@0: pattern->insertInt("TilingType", kConstantSpacing_TilingType); michael@0: pattern->insert("BBox", SkPDFUtils::RectToArray(bbox))->unref(); michael@0: pattern->insertScalar("XStep", bbox.width()); michael@0: pattern->insertScalar("YStep", bbox.height()); michael@0: pattern->insert("Resources", resources); michael@0: if (!matrix.isIdentity()) { michael@0: pattern->insert("Matrix", SkPDFUtils::MatrixToArray(matrix))->unref(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Creates a content stream which fills the pattern P0 across bounds. michael@0: * @param gsIndex A graphics state resource index to apply, or <0 if no michael@0: * graphics state to apply. michael@0: */ michael@0: static SkStream* create_pattern_fill_content(int gsIndex, SkRect& bounds) { michael@0: SkDynamicMemoryWStream content; michael@0: if (gsIndex >= 0) { michael@0: SkPDFUtils::ApplyGraphicState(gsIndex, &content); michael@0: } michael@0: SkPDFUtils::ApplyPattern(0, &content); michael@0: SkPDFUtils::AppendRectangle(bounds, &content); michael@0: SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, michael@0: &content); michael@0: michael@0: return content.detachAsStream(); michael@0: } michael@0: michael@0: /** michael@0: * Creates a ExtGState with the SMask set to the luminosityShader in michael@0: * luminosity mode. The shader pattern extends to the bbox. michael@0: */ michael@0: SkPDFGraphicState* SkPDFAlphaFunctionShader::CreateSMaskGraphicState() { michael@0: SkRect bbox; michael@0: bbox.set(fState.get()->fBBox); michael@0: michael@0: SkAutoTUnref luminosityShader( michael@0: SkPDFShader::GetPDFShaderByState( michael@0: fState->CreateAlphaToLuminosityState())); michael@0: michael@0: SkAutoTUnref alphaStream(create_pattern_fill_content(-1, bbox)); michael@0: michael@0: SkAutoTUnref michael@0: resources(get_gradient_resource_dict(luminosityShader, NULL)); michael@0: michael@0: SkAutoTUnref alphaMask( michael@0: new SkPDFFormXObject(alphaStream.get(), bbox, resources.get())); michael@0: michael@0: return SkPDFGraphicState::GetSMaskGraphicState( michael@0: alphaMask.get(), false, michael@0: SkPDFGraphicState::kLuminosity_SMaskMode); michael@0: } michael@0: michael@0: SkPDFAlphaFunctionShader::SkPDFAlphaFunctionShader(SkPDFShader::State* state) michael@0: : fState(state) { michael@0: SkRect bbox; michael@0: bbox.set(fState.get()->fBBox); michael@0: michael@0: fColorShader.reset( michael@0: SkPDFShader::GetPDFShaderByState(state->CreateOpaqueState())); michael@0: michael@0: // Create resource dict with alpha graphics state as G0 and michael@0: // pattern shader as P0, then write content stream. michael@0: SkAutoTUnref alphaGs(CreateSMaskGraphicState()); michael@0: fResourceDict.reset( michael@0: get_gradient_resource_dict(fColorShader.get(), alphaGs.get())); michael@0: michael@0: SkAutoTUnref colorStream( michael@0: create_pattern_fill_content(0, bbox)); michael@0: setData(colorStream.get()); michael@0: michael@0: populate_tiling_pattern_dict(this, bbox, fResourceDict.get(), michael@0: SkMatrix::I()); michael@0: } michael@0: michael@0: // Finds affine and persp such that in = affine * persp. michael@0: // but it returns the inverse of perspective matrix. michael@0: static bool split_perspective(const SkMatrix in, SkMatrix* affine, michael@0: SkMatrix* perspectiveInverse) { michael@0: const SkScalar p2 = in[SkMatrix::kMPersp2]; michael@0: michael@0: if (SkScalarNearlyZero(p2)) { michael@0: return false; michael@0: } michael@0: michael@0: const SkScalar zero = SkIntToScalar(0); michael@0: const SkScalar one = SkIntToScalar(1); michael@0: michael@0: const SkScalar sx = in[SkMatrix::kMScaleX]; michael@0: const SkScalar kx = in[SkMatrix::kMSkewX]; michael@0: const SkScalar tx = in[SkMatrix::kMTransX]; michael@0: const SkScalar ky = in[SkMatrix::kMSkewY]; michael@0: const SkScalar sy = in[SkMatrix::kMScaleY]; michael@0: const SkScalar ty = in[SkMatrix::kMTransY]; michael@0: const SkScalar p0 = in[SkMatrix::kMPersp0]; michael@0: const SkScalar p1 = in[SkMatrix::kMPersp1]; michael@0: michael@0: // Perspective matrix would be: michael@0: // 1 0 0 michael@0: // 0 1 0 michael@0: // p0 p1 p2 michael@0: // But we need the inverse of persp. michael@0: perspectiveInverse->setAll(one, zero, zero, michael@0: zero, one, zero, michael@0: -p0/p2, -p1/p2, 1/p2); michael@0: michael@0: affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2, michael@0: ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2, michael@0: zero, zero, one); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state) michael@0: : SkPDFDict("Pattern"), michael@0: fState(state) { michael@0: SkString (*codeFunction)(const SkShader::GradientInfo& info, michael@0: const SkMatrix& perspectiveRemover) = NULL; michael@0: SkPoint transformPoints[2]; michael@0: michael@0: // Depending on the type of the gradient, we want to transform the michael@0: // coordinate space in different ways. michael@0: const SkShader::GradientInfo* info = &fState.get()->fInfo; michael@0: transformPoints[0] = info->fPoint[0]; michael@0: transformPoints[1] = info->fPoint[1]; michael@0: switch (fState.get()->fType) { michael@0: case SkShader::kLinear_GradientType: michael@0: codeFunction = &linearCode; michael@0: break; michael@0: case SkShader::kRadial_GradientType: michael@0: transformPoints[1] = transformPoints[0]; michael@0: transformPoints[1].fX += info->fRadius[0]; michael@0: codeFunction = &radialCode; michael@0: break; michael@0: case SkShader::kRadial2_GradientType: { michael@0: // Bail out if the radii are the same. Empty fResources signals michael@0: // an error and isValid will return false. michael@0: if (info->fRadius[0] == info->fRadius[1]) { michael@0: return; michael@0: } michael@0: transformPoints[1] = transformPoints[0]; michael@0: SkScalar dr = info->fRadius[1] - info->fRadius[0]; michael@0: transformPoints[1].fX += dr; michael@0: codeFunction = &twoPointRadialCode; michael@0: break; michael@0: } michael@0: case SkShader::kConical_GradientType: { michael@0: transformPoints[1] = transformPoints[0]; michael@0: transformPoints[1].fX += SK_Scalar1; michael@0: codeFunction = &twoPointConicalCode; michael@0: break; michael@0: } michael@0: case SkShader::kSweep_GradientType: michael@0: transformPoints[1] = transformPoints[0]; michael@0: transformPoints[1].fX += SK_Scalar1; michael@0: codeFunction = &sweepCode; michael@0: break; michael@0: case SkShader::kColor_GradientType: michael@0: case SkShader::kNone_GradientType: michael@0: default: michael@0: return; michael@0: } michael@0: michael@0: // Move any scaling (assuming a unit gradient) or translation michael@0: // (and rotation for linear gradient), of the final gradient from michael@0: // info->fPoints to the matrix (updating bbox appropriately). Now michael@0: // the gradient can be drawn on on the unit segment. michael@0: SkMatrix mapperMatrix; michael@0: unitToPointsMatrix(transformPoints, &mapperMatrix); michael@0: michael@0: SkMatrix finalMatrix = fState.get()->fCanvasTransform; michael@0: finalMatrix.preConcat(fState.get()->fShaderTransform); michael@0: finalMatrix.preConcat(mapperMatrix); michael@0: michael@0: // Preserves as much as posible in the final matrix, and only removes michael@0: // the perspective. The inverse of the perspective is stored in michael@0: // perspectiveInverseOnly matrix and has 3 useful numbers michael@0: // (p0, p1, p2), while everything else is either 0 or 1. michael@0: // In this way the shader will handle it eficiently, with minimal code. michael@0: SkMatrix perspectiveInverseOnly = SkMatrix::I(); michael@0: if (finalMatrix.hasPerspective()) { michael@0: if (!split_perspective(finalMatrix, michael@0: &finalMatrix, &perspectiveInverseOnly)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: SkRect bbox; michael@0: bbox.set(fState.get()->fBBox); michael@0: if (!inverseTransformBBox(finalMatrix, &bbox)) { michael@0: return; michael@0: } michael@0: michael@0: SkAutoTUnref domain(new SkPDFArray); michael@0: domain->reserve(4); michael@0: domain->appendScalar(bbox.fLeft); michael@0: domain->appendScalar(bbox.fRight); michael@0: domain->appendScalar(bbox.fTop); michael@0: domain->appendScalar(bbox.fBottom); michael@0: michael@0: SkString functionCode; michael@0: // The two point radial gradient further references fState.get()->fInfo michael@0: // in translating from x, y coordinates to the t parameter. So, we have michael@0: // to transform the points and radii according to the calculated matrix. michael@0: if (fState.get()->fType == SkShader::kRadial2_GradientType) { michael@0: SkShader::GradientInfo twoPointRadialInfo = *info; michael@0: SkMatrix inverseMapperMatrix; michael@0: if (!mapperMatrix.invert(&inverseMapperMatrix)) { michael@0: return; michael@0: } michael@0: inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); michael@0: twoPointRadialInfo.fRadius[0] = michael@0: inverseMapperMatrix.mapRadius(info->fRadius[0]); michael@0: twoPointRadialInfo.fRadius[1] = michael@0: inverseMapperMatrix.mapRadius(info->fRadius[1]); michael@0: functionCode = codeFunction(twoPointRadialInfo, perspectiveInverseOnly); michael@0: } else { michael@0: functionCode = codeFunction(*info, perspectiveInverseOnly); michael@0: } michael@0: michael@0: SkAutoTUnref pdfShader(new SkPDFDict); michael@0: pdfShader->insertInt("ShadingType", 1); michael@0: pdfShader->insertName("ColorSpace", "DeviceRGB"); michael@0: pdfShader->insert("Domain", domain.get()); michael@0: michael@0: SkPDFStream* function = makePSFunction(functionCode, domain.get()); michael@0: pdfShader->insert("Function", new SkPDFObjRef(function))->unref(); michael@0: fResources.push(function); // Pass ownership to resource list. michael@0: michael@0: insertInt("PatternType", 2); michael@0: insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref(); michael@0: insert("Shading", pdfShader.get()); michael@0: } michael@0: michael@0: SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { michael@0: fState.get()->fImage.lockPixels(); michael@0: michael@0: // The image shader pattern cell will be drawn into a separate device michael@0: // in pattern cell space (no scaling on the bitmap, though there may be michael@0: // translations so that all content is in the device, coordinates > 0). michael@0: michael@0: // Map clip bounds to shader space to ensure the device is large enough michael@0: // to handle fake clamping. michael@0: SkMatrix finalMatrix = fState.get()->fCanvasTransform; michael@0: finalMatrix.preConcat(fState.get()->fShaderTransform); michael@0: SkRect deviceBounds; michael@0: deviceBounds.set(fState.get()->fBBox); michael@0: if (!inverseTransformBBox(finalMatrix, &deviceBounds)) { michael@0: return; michael@0: } michael@0: michael@0: const SkBitmap* image = &fState.get()->fImage; michael@0: SkRect bitmapBounds; michael@0: image->getBounds(&bitmapBounds); michael@0: michael@0: // For tiling modes, the bounds should be extended to include the bitmap, michael@0: // otherwise the bitmap gets clipped out and the shader is empty and awful. michael@0: // For clamp modes, we're only interested in the clip region, whether michael@0: // or not the main bitmap is in it. michael@0: SkShader::TileMode tileModes[2]; michael@0: tileModes[0] = fState.get()->fImageTileModes[0]; michael@0: tileModes[1] = fState.get()->fImageTileModes[1]; michael@0: if (tileModes[0] != SkShader::kClamp_TileMode || michael@0: tileModes[1] != SkShader::kClamp_TileMode) { michael@0: deviceBounds.join(bitmapBounds); michael@0: } michael@0: michael@0: SkMatrix unflip; michael@0: unflip.setTranslate(0, SkScalarRoundToScalar(deviceBounds.height())); michael@0: unflip.preScale(SK_Scalar1, -SK_Scalar1); michael@0: SkISize size = SkISize::Make(SkScalarRoundToInt(deviceBounds.width()), michael@0: SkScalarRoundToInt(deviceBounds.height())); michael@0: // TODO(edisonn): should we pass here the DCT encoder of the destination device? michael@0: // TODO(edisonn): NYI Perspective, use SkPDFDeviceFlattener. michael@0: SkPDFDevice pattern(size, size, unflip); michael@0: SkCanvas canvas(&pattern); michael@0: michael@0: SkRect patternBBox; michael@0: image->getBounds(&patternBBox); michael@0: michael@0: // Translate the canvas so that the bitmap origin is at (0, 0). michael@0: canvas.translate(-deviceBounds.left(), -deviceBounds.top()); michael@0: patternBBox.offset(-deviceBounds.left(), -deviceBounds.top()); michael@0: // Undo the translation in the final matrix michael@0: finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top()); michael@0: michael@0: // If the bitmap is out of bounds (i.e. clamp mode where we only see the michael@0: // stretched sides), canvas will clip this out and the extraneous data michael@0: // won't be saved to the PDF. michael@0: canvas.drawBitmap(*image, 0, 0); michael@0: michael@0: SkScalar width = SkIntToScalar(image->width()); michael@0: SkScalar height = SkIntToScalar(image->height()); michael@0: michael@0: // Tiling is implied. First we handle mirroring. michael@0: if (tileModes[0] == SkShader::kMirror_TileMode) { michael@0: SkMatrix xMirror; michael@0: xMirror.setScale(-1, 1); michael@0: xMirror.postTranslate(2 * width, 0); michael@0: canvas.drawBitmapMatrix(*image, xMirror); michael@0: patternBBox.fRight += width; michael@0: } michael@0: if (tileModes[1] == SkShader::kMirror_TileMode) { michael@0: SkMatrix yMirror; michael@0: yMirror.setScale(SK_Scalar1, -SK_Scalar1); michael@0: yMirror.postTranslate(0, 2 * height); michael@0: canvas.drawBitmapMatrix(*image, yMirror); michael@0: patternBBox.fBottom += height; michael@0: } michael@0: if (tileModes[0] == SkShader::kMirror_TileMode && michael@0: tileModes[1] == SkShader::kMirror_TileMode) { michael@0: SkMatrix mirror; michael@0: mirror.setScale(-1, -1); michael@0: mirror.postTranslate(2 * width, 2 * height); michael@0: canvas.drawBitmapMatrix(*image, mirror); michael@0: } michael@0: michael@0: // Then handle Clamping, which requires expanding the pattern canvas to michael@0: // cover the entire surfaceBBox. michael@0: michael@0: // If both x and y are in clamp mode, we start by filling in the corners. michael@0: // (Which are just a rectangles of the corner colors.) michael@0: if (tileModes[0] == SkShader::kClamp_TileMode && michael@0: tileModes[1] == SkShader::kClamp_TileMode) { michael@0: SkPaint paint; michael@0: SkRect rect; michael@0: rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0); michael@0: if (!rect.isEmpty()) { michael@0: paint.setColor(image->getColor(0, 0)); michael@0: canvas.drawRect(rect, paint); michael@0: } michael@0: michael@0: rect = SkRect::MakeLTRB(width, deviceBounds.top(), michael@0: deviceBounds.right(), 0); michael@0: if (!rect.isEmpty()) { michael@0: paint.setColor(image->getColor(image->width() - 1, 0)); michael@0: canvas.drawRect(rect, paint); michael@0: } michael@0: michael@0: rect = SkRect::MakeLTRB(width, height, michael@0: deviceBounds.right(), deviceBounds.bottom()); michael@0: if (!rect.isEmpty()) { michael@0: paint.setColor(image->getColor(image->width() - 1, michael@0: image->height() - 1)); michael@0: canvas.drawRect(rect, paint); michael@0: } michael@0: michael@0: rect = SkRect::MakeLTRB(deviceBounds.left(), height, michael@0: 0, deviceBounds.bottom()); michael@0: if (!rect.isEmpty()) { michael@0: paint.setColor(image->getColor(0, image->height() - 1)); michael@0: canvas.drawRect(rect, paint); michael@0: } michael@0: } michael@0: michael@0: // Then expand the left, right, top, then bottom. michael@0: if (tileModes[0] == SkShader::kClamp_TileMode) { michael@0: SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image->height()); michael@0: if (deviceBounds.left() < 0) { michael@0: SkBitmap left; michael@0: SkAssertResult(image->extractSubset(&left, subset)); michael@0: michael@0: SkMatrix leftMatrix; michael@0: leftMatrix.setScale(-deviceBounds.left(), 1); michael@0: leftMatrix.postTranslate(deviceBounds.left(), 0); michael@0: canvas.drawBitmapMatrix(left, leftMatrix); michael@0: michael@0: if (tileModes[1] == SkShader::kMirror_TileMode) { michael@0: leftMatrix.postScale(SK_Scalar1, -SK_Scalar1); michael@0: leftMatrix.postTranslate(0, 2 * height); michael@0: canvas.drawBitmapMatrix(left, leftMatrix); michael@0: } michael@0: patternBBox.fLeft = 0; michael@0: } michael@0: michael@0: if (deviceBounds.right() > width) { michael@0: SkBitmap right; michael@0: subset.offset(image->width() - 1, 0); michael@0: SkAssertResult(image->extractSubset(&right, subset)); michael@0: michael@0: SkMatrix rightMatrix; michael@0: rightMatrix.setScale(deviceBounds.right() - width, 1); michael@0: rightMatrix.postTranslate(width, 0); michael@0: canvas.drawBitmapMatrix(right, rightMatrix); michael@0: michael@0: if (tileModes[1] == SkShader::kMirror_TileMode) { michael@0: rightMatrix.postScale(SK_Scalar1, -SK_Scalar1); michael@0: rightMatrix.postTranslate(0, 2 * height); michael@0: canvas.drawBitmapMatrix(right, rightMatrix); michael@0: } michael@0: patternBBox.fRight = deviceBounds.width(); michael@0: } michael@0: } michael@0: michael@0: if (tileModes[1] == SkShader::kClamp_TileMode) { michael@0: SkIRect subset = SkIRect::MakeXYWH(0, 0, image->width(), 1); michael@0: if (deviceBounds.top() < 0) { michael@0: SkBitmap top; michael@0: SkAssertResult(image->extractSubset(&top, subset)); michael@0: michael@0: SkMatrix topMatrix; michael@0: topMatrix.setScale(SK_Scalar1, -deviceBounds.top()); michael@0: topMatrix.postTranslate(0, deviceBounds.top()); michael@0: canvas.drawBitmapMatrix(top, topMatrix); michael@0: michael@0: if (tileModes[0] == SkShader::kMirror_TileMode) { michael@0: topMatrix.postScale(-1, 1); michael@0: topMatrix.postTranslate(2 * width, 0); michael@0: canvas.drawBitmapMatrix(top, topMatrix); michael@0: } michael@0: patternBBox.fTop = 0; michael@0: } michael@0: michael@0: if (deviceBounds.bottom() > height) { michael@0: SkBitmap bottom; michael@0: subset.offset(0, image->height() - 1); michael@0: SkAssertResult(image->extractSubset(&bottom, subset)); michael@0: michael@0: SkMatrix bottomMatrix; michael@0: bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height); michael@0: bottomMatrix.postTranslate(0, height); michael@0: canvas.drawBitmapMatrix(bottom, bottomMatrix); michael@0: michael@0: if (tileModes[0] == SkShader::kMirror_TileMode) { michael@0: bottomMatrix.postScale(-1, 1); michael@0: bottomMatrix.postTranslate(2 * width, 0); michael@0: canvas.drawBitmapMatrix(bottom, bottomMatrix); michael@0: } michael@0: patternBBox.fBottom = deviceBounds.height(); michael@0: } michael@0: } michael@0: michael@0: // Put the canvas into the pattern stream (fContent). michael@0: SkAutoTUnref content(pattern.content()); michael@0: setData(content.get()); michael@0: SkPDFResourceDict* resourceDict = pattern.getResourceDict(); michael@0: resourceDict->getReferencedResources(fResources, &fResources, false); michael@0: michael@0: populate_tiling_pattern_dict(this, patternBBox, michael@0: pattern.getResourceDict(), finalMatrix); michael@0: michael@0: fState.get()->fImage.unlockPixels(); michael@0: } michael@0: michael@0: SkPDFStream* SkPDFFunctionShader::makePSFunction(const SkString& psCode, michael@0: SkPDFArray* domain) { michael@0: SkAutoDataUnref funcData(SkData::NewWithCopy(psCode.c_str(), michael@0: psCode.size())); michael@0: SkPDFStream* result = new SkPDFStream(funcData.get()); michael@0: result->insertInt("FunctionType", 4); michael@0: result->insert("Domain", domain); michael@0: result->insert("Range", RangeObject()); michael@0: return result; michael@0: } michael@0: michael@0: SkPDFShader::ShaderCanonicalEntry::ShaderCanonicalEntry(SkPDFObject* pdfShader, michael@0: const State* state) michael@0: : fPDFShader(pdfShader), michael@0: fState(state) { michael@0: } michael@0: michael@0: bool SkPDFShader::ShaderCanonicalEntry::operator==( michael@0: const ShaderCanonicalEntry& b) const { michael@0: return fPDFShader == b.fPDFShader || michael@0: (fState != NULL && b.fState != NULL && *fState == *b.fState); michael@0: } michael@0: michael@0: bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const { michael@0: if (fType != b.fType || michael@0: fCanvasTransform != b.fCanvasTransform || michael@0: fShaderTransform != b.fShaderTransform || michael@0: fBBox != b.fBBox) { michael@0: return false; michael@0: } michael@0: michael@0: if (fType == SkShader::kNone_GradientType) { michael@0: if (fPixelGeneration != b.fPixelGeneration || michael@0: fPixelGeneration == 0 || michael@0: fImageTileModes[0] != b.fImageTileModes[0] || michael@0: fImageTileModes[1] != b.fImageTileModes[1]) { michael@0: return false; michael@0: } michael@0: } else { michael@0: if (fInfo.fColorCount != b.fInfo.fColorCount || michael@0: memcmp(fInfo.fColors, b.fInfo.fColors, michael@0: sizeof(SkColor) * fInfo.fColorCount) != 0 || michael@0: memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets, michael@0: sizeof(SkScalar) * fInfo.fColorCount) != 0 || michael@0: fInfo.fPoint[0] != b.fInfo.fPoint[0] || michael@0: fInfo.fTileMode != b.fInfo.fTileMode) { michael@0: return false; michael@0: } michael@0: michael@0: switch (fType) { michael@0: case SkShader::kLinear_GradientType: michael@0: if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) { michael@0: return false; michael@0: } michael@0: break; michael@0: case SkShader::kRadial_GradientType: michael@0: if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) { michael@0: return false; michael@0: } michael@0: break; michael@0: case SkShader::kRadial2_GradientType: michael@0: case SkShader::kConical_GradientType: michael@0: if (fInfo.fPoint[1] != b.fInfo.fPoint[1] || michael@0: fInfo.fRadius[0] != b.fInfo.fRadius[0] || michael@0: fInfo.fRadius[1] != b.fInfo.fRadius[1]) { michael@0: return false; michael@0: } michael@0: break; michael@0: case SkShader::kSweep_GradientType: michael@0: case SkShader::kNone_GradientType: michael@0: case SkShader::kColor_GradientType: michael@0: break; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: SkPDFShader::State::State(const SkShader& shader, michael@0: const SkMatrix& canvasTransform, const SkIRect& bbox) michael@0: : fCanvasTransform(canvasTransform), michael@0: fBBox(bbox), michael@0: fPixelGeneration(0) { michael@0: fInfo.fColorCount = 0; michael@0: fInfo.fColors = NULL; michael@0: fInfo.fColorOffsets = NULL; michael@0: fShaderTransform = shader.getLocalMatrix(); michael@0: fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode; michael@0: michael@0: fType = shader.asAGradient(&fInfo); michael@0: michael@0: if (fType == SkShader::kNone_GradientType) { michael@0: SkShader::BitmapType bitmapType; michael@0: SkMatrix matrix; michael@0: bitmapType = shader.asABitmap(&fImage, &matrix, fImageTileModes); michael@0: if (bitmapType != SkShader::kDefault_BitmapType) { michael@0: fImage.reset(); michael@0: return; michael@0: } michael@0: SkASSERT(matrix.isIdentity()); michael@0: fPixelGeneration = fImage.getGenerationID(); michael@0: } else { michael@0: AllocateGradientInfoStorage(); michael@0: shader.asAGradient(&fInfo); michael@0: } michael@0: } michael@0: michael@0: SkPDFShader::State::State(const SkPDFShader::State& other) michael@0: : fType(other.fType), michael@0: fCanvasTransform(other.fCanvasTransform), michael@0: fShaderTransform(other.fShaderTransform), michael@0: fBBox(other.fBBox) michael@0: { michael@0: // Only gradients supported for now, since that is all that is used. michael@0: // If needed, image state copy constructor can be added here later. michael@0: SkASSERT(fType != SkShader::kNone_GradientType); michael@0: michael@0: if (fType != SkShader::kNone_GradientType) { michael@0: fInfo = other.fInfo; michael@0: michael@0: AllocateGradientInfoStorage(); michael@0: for (int i = 0; i < fInfo.fColorCount; i++) { michael@0: fInfo.fColors[i] = other.fInfo.fColors[i]; michael@0: fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Create a copy of this gradient state with alpha assigned to RGB luminousity. michael@0: * Only valid for gradient states. michael@0: */ michael@0: SkPDFShader::State* SkPDFShader::State::CreateAlphaToLuminosityState() const { michael@0: SkASSERT(fType != SkShader::kNone_GradientType); michael@0: michael@0: SkPDFShader::State* newState = new SkPDFShader::State(*this); michael@0: michael@0: for (int i = 0; i < fInfo.fColorCount; i++) { michael@0: SkAlpha alpha = SkColorGetA(fInfo.fColors[i]); michael@0: newState->fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha); michael@0: } michael@0: michael@0: return newState; michael@0: } michael@0: michael@0: /** michael@0: * Create a copy of this gradient state with alpha set to fully opaque michael@0: * Only valid for gradient states. michael@0: */ michael@0: SkPDFShader::State* SkPDFShader::State::CreateOpaqueState() const { michael@0: SkASSERT(fType != SkShader::kNone_GradientType); michael@0: michael@0: SkPDFShader::State* newState = new SkPDFShader::State(*this); michael@0: for (int i = 0; i < fInfo.fColorCount; i++) { michael@0: newState->fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i], michael@0: SK_AlphaOPAQUE); michael@0: } michael@0: michael@0: return newState; michael@0: } michael@0: michael@0: /** michael@0: * Returns true if state is a gradient and the gradient has alpha. michael@0: */ michael@0: bool SkPDFShader::State::GradientHasAlpha() const { michael@0: if (fType == SkShader::kNone_GradientType) { michael@0: return false; michael@0: } michael@0: michael@0: for (int i = 0; i < fInfo.fColorCount; i++) { michael@0: SkAlpha alpha = SkColorGetA(fInfo.fColors[i]); michael@0: if (alpha != SK_AlphaOPAQUE) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void SkPDFShader::State::AllocateGradientInfoStorage() { michael@0: fColorData.set(sk_malloc_throw( michael@0: fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar)))); michael@0: fInfo.fColors = reinterpret_cast(fColorData.get()); michael@0: fInfo.fColorOffsets = michael@0: reinterpret_cast(fInfo.fColors + fInfo.fColorCount); michael@0: }