[PDF] Add support for Shaders.

- Shaders, or as they are referred to in PDF, patterns, are drawn in the coordinate system of the initial page, so when we canonicalize them, we have to consider the current transform and where they are constructed.

- Image shaders are tiled by default, this makes repeat and mirror modes easy, but means we have to draw a pattern as large as the current clip to support clamp mode.

- Gradient shaders are implemented with type 4 functions, which are basically small snippets of post script code.  I've tried to make the code generation modular and heavily commented to make it easy to understand or expand.

Review URL: http://codereview.appspot.com/4239061

git-svn-id: http://skia.googlecode.com/svn/trunk@905 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
vandebo@chromium.org 2011-03-08 18:31:02 +00:00
Родитель e8c701cbf5
Коммит da912d61ed
8 изменённых файлов: 979 добавлений и 21 удалений

Просмотреть файл

@ -30,8 +30,10 @@ DEFINES += -DSK_SUPPORT_LCDTEXT
ifeq ($(SKIA_PDF_SUPPORT),true) ifeq ($(SKIA_PDF_SUPPORT),true)
DEFINES += -DSK_SUPPORT_PDF DEFINES += -DSK_SUPPORT_PDF
ifneq ($(SKIA_DEBUG),true)
DEFINES += -DSK_ZLIB_INCLUDE="<zlib.h>" DEFINES += -DSK_ZLIB_INCLUDE="<zlib.h>"
endif endif
endif
# start with the core (required) # start with the core (required)
include src/core/core_files.mk include src/core/core_files.mk

Просмотреть файл

@ -29,6 +29,7 @@ class SkPDFDict;
class SkPDFFont; class SkPDFFont;
class SkPDFGraphicState; class SkPDFGraphicState;
class SkPDFObject; class SkPDFObject;
class SkPDFShader;
class SkPDFStream; class SkPDFStream;
class SkPDFDeviceFactory : public SkDeviceFactory { class SkPDFDeviceFactory : public SkDeviceFactory {
@ -146,6 +147,7 @@ private:
SkTDArray<SkPDFGraphicState*> fGraphicStateResources; SkTDArray<SkPDFGraphicState*> fGraphicStateResources;
SkTDArray<SkPDFObject*> fXObjectResources; SkTDArray<SkPDFObject*> fXObjectResources;
SkTDArray<SkPDFFont*> fFontResources; SkTDArray<SkPDFFont*> fFontResources;
SkTDArray<SkPDFShader*> fShaderResources;
// In PDF, transforms and clips can only be undone by popping the graphic // In PDF, transforms and clips can only be undone by popping the graphic
// state to before the transform or clip was applied. Because it can be // state to before the transform or clip was applied. Because it can be
@ -165,6 +167,7 @@ private:
SkScalar fTextScaleX; SkScalar fTextScaleX;
SkPaint::Style fTextFill; SkPaint::Style fTextFill;
SkPDFFont* fFont; SkPDFFont* fFont;
SkPDFShader* fShader;
SkPDFGraphicState* fGraphicState; SkPDFGraphicState* fGraphicState;
SkRegion fClip; SkRegion fClip;
SkMatrix fTransform; SkMatrix fTransform;

110
include/pdf/SkPDFShader.h Normal file
Просмотреть файл

@ -0,0 +1,110 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SkPDFShader_DEFINED
#define SkPDFShader_DEFINED
#include "SkPDFStream.h"
#include "SkPDFTypes.h"
#include "SkMatrix.h"
#include "SkRefCnt.h"
#include "SkShader.h"
class SkObjRef;
class SkPDFCatalog;
/** \class SkPDFShader
In PDF parlance, this is a pattern, used in place of a color when the
pattern color space is selected.
*/
class SkPDFShader : public SkPDFObject {
public:
virtual ~SkPDFShader();
// The SkPDFObject interface.
virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect);
virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
virtual void getResources(SkTDArray<SkPDFObject*>* resourceList);
/** Get the PDF shader for the passed SkShader. If the SkShader is
* invalid in some way, returns NULL. The reference count of
* the object is incremented and it is the caller's responsibility to
* unreference it when done. This is needed to accommodate the weak
* reference pattern used when the returned object is new and has no
* other references.
* @param shader The SkShader to emulate.
* @param matrix The current transform. (PDF shaders are absolutely
* positioned, relative to where the page is drawn.)
* @param surfceBBox The bounding box of the drawing surface (with matrix
* already applied).
*/
static SkPDFShader* getPDFShader(const SkShader& shader,
const SkMatrix& matrix,
const SkIRect& surfaceBBox);
private:
class State {
public:
SkShader::GradientType fType;
SkShader::GradientInfo fInfo;
SkAutoFree fColorData;
SkMatrix fCanvasTransform;
SkMatrix fShaderTransform;
SkIRect fBBox;
SkBitmap fImage;
uint32_t fPixelGeneration;
SkShader::TileMode fImageTileModes[2];
explicit State(const SkShader& shader, const SkMatrix& canvasTransform,
const SkIRect& bbox);
bool operator==(const State& b) const;
};
SkRefPtr<SkPDFDict> fContent;
SkTDArray<SkPDFObject*> fResources;
SkAutoTDelete<const State> fState;
class ShaderCanonicalEntry {
public:
SkPDFShader* fPDFShader;
const State* fState;
bool operator==(const ShaderCanonicalEntry& b) const {
return fPDFShader == b.fPDFShader || *fState == *b.fState;
}
ShaderCanonicalEntry(SkPDFShader* pdfShader, const State* state)
: fPDFShader(pdfShader),
fState(state) {
}
};
// This should be made a hash table if performance is a problem.
static SkTDArray<ShaderCanonicalEntry>& canonicalShaders();
static SkMutex& canonicalShadersMutex();
static SkPDFObject* rangeObject();
SkPDFShader(State* state);
void doFunctionShader();
void doImageShader();
SkPDFStream* makePSFunction(const SkString& psCode, SkPDFArray* domain);
};
#endif

Просмотреть файл

@ -26,7 +26,8 @@ class SkPDFCatalog;
/** \class SkPDFStream /** \class SkPDFStream
A stream object in a PDF. A stream object in a PDF. Note, all streams must be indirect objects (via
SkObjRef).
*/ */
class SkPDFStream : public SkPDFDict { class SkPDFStream : public SkPDFDict {
public: public:

Просмотреть файл

@ -68,6 +68,8 @@ int SkPDFCatalog::findObjectIndex(SkPDFObject* obj) const {
int SkPDFCatalog::assignObjNum(SkPDFObject* obj) { int SkPDFCatalog::assignObjNum(SkPDFObject* obj) {
int pos = findObjectIndex(obj); int pos = findObjectIndex(obj);
// If this assert fails, it means you probably forgot to add an object
// to the resource list.
SkASSERT(pos >= 0); SkASSERT(pos >= 0);
uint32_t currentIndex = pos; uint32_t currentIndex = pos;
if (fCatalog[currentIndex].fObjNumAssigned) if (fCatalog[currentIndex].fObjNumAssigned)

Просмотреть файл

@ -25,6 +25,7 @@
#include "SkPDFFont.h" #include "SkPDFFont.h"
#include "SkPDFFormXObject.h" #include "SkPDFFormXObject.h"
#include "SkPDFTypes.h" #include "SkPDFTypes.h"
#include "SkPDFShader.h"
#include "SkPDFStream.h" #include "SkPDFStream.h"
#include "SkPDFUtils.h" #include "SkPDFUtils.h"
#include "SkRect.h" #include "SkRect.h"
@ -138,6 +139,7 @@ SkPDFDevice::SkPDFDevice(int width, int height, OriginTransform flipOrigin)
fGraphicStack[0].fTextScaleX = SK_Scalar1; fGraphicStack[0].fTextScaleX = SK_Scalar1;
fGraphicStack[0].fTextFill = SkPaint::kFill_Style; fGraphicStack[0].fTextFill = SkPaint::kFill_Style;
fGraphicStack[0].fFont = NULL; fGraphicStack[0].fFont = NULL;
fGraphicStack[0].fShader = NULL;
fGraphicStack[0].fGraphicState = NULL; fGraphicStack[0].fGraphicState = NULL;
fGraphicStack[0].fClip.setRect(0,0, width, height); fGraphicStack[0].fClip.setRect(0,0, width, height);
fGraphicStack[0].fTransform.reset(); fGraphicStack[0].fTransform.reset();
@ -151,6 +153,7 @@ SkPDFDevice::~SkPDFDevice() {
fGraphicStateResources.unrefAll(); fGraphicStateResources.unrefAll();
fXObjectResources.unrefAll(); fXObjectResources.unrefAll();
fFontResources.unrefAll(); fFontResources.unrefAll();
fShaderResources.unrefAll();
} }
void SkPDFDevice::setMatrixClip(const SkMatrix& matrix, void SkPDFDevice::setMatrixClip(const SkMatrix& matrix,
@ -500,6 +503,18 @@ const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
fResourceDict->insert("Font", fonts.get()); fResourceDict->insert("Font", fonts.get());
} }
if (fShaderResources.count()) {
SkRefPtr<SkPDFDict> patterns = new SkPDFDict();
patterns->unref(); // SkRefPtr and new both took a reference.
for (int i = 0; i < fShaderResources.count(); i++) {
SkString nameString("P");
nameString.appendS32(i);
patterns->insert(nameString.c_str(),
new SkPDFObjRef(fShaderResources[i]))->unref();
}
fResourceDict->insert("Pattern", patterns.get());
}
// For compatibility, add all proc sets (only used for output to PS // For compatibility, add all proc sets (only used for output to PS
// devices). // devices).
const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"}; const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
@ -517,7 +532,8 @@ void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
resourceList->setReserve(resourceList->count() + resourceList->setReserve(resourceList->count() +
fGraphicStateResources.count() + fGraphicStateResources.count() +
fXObjectResources.count() + fXObjectResources.count() +
fFontResources.count()); fFontResources.count() +
fShaderResources.count());
for (int i = 0; i < fGraphicStateResources.count(); i++) { for (int i = 0; i < fGraphicStateResources.count(); i++) {
resourceList->push(fGraphicStateResources[i]); resourceList->push(fGraphicStateResources[i]);
fGraphicStateResources[i]->ref(); fGraphicStateResources[i]->ref();
@ -533,6 +549,11 @@ void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
fFontResources[i]->ref(); fFontResources[i]->ref();
fFontResources[i]->getResources(resourceList); fFontResources[i]->getResources(resourceList);
} }
for (int i = 0; i < fShaderResources.count(); i++) {
resourceList->push(fShaderResources[i]);
fShaderResources[i]->ref();
fShaderResources[i]->getResources(resourceList);
}
} }
SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const { SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
@ -562,17 +583,78 @@ SkStream* SkPDFDevice::content() const {
return result; return result;
} }
// Private void SkPDFDevice::updateGSFromPaint(const SkPaint& paint, bool forText) {
SkASSERT(paint.getPathEffect() == NULL);
// TODO(vandebo) handle these cases. NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
#define PAINTCHECK(x,y) NOT_IMPLEMENTED(newPaint.x() y, false) NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint, bool forText) { SkPaint newPaint = paint;
SkASSERT(newPaint.getPathEffect() == NULL);
PAINTCHECK(getMaskFilter, != NULL); // PDF treats a shader as a color, so we only set one or the other.
PAINTCHECK(getShader, != NULL); SkRefPtr<SkPDFShader> pdfShader;
PAINTCHECK(getColorFilter, != NULL); const SkShader* shader = newPaint.getShader();
if (shader) {
// PDF positions patterns relative to the initial transform, so
// we need to apply the current transform to the shader parameters.
SkMatrix transform = fGraphicStack[fGraphicStackIndex].fTransform;
if (fFlipOrigin == kFlip_OriginTransform) {
transform.postScale(1, -1);
transform.postTranslate(0, fHeight);
}
// PDF doesn't support kClamp_TileMode, so we simulate it by making
// a pattern the size of the drawing service.
SkIRect bounds = fGraphicStack[fGraphicStackIndex].fClip.getBounds();
pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds);
SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref
// A color shader is treated as an invalid shader so we don't have
// to set a shader just for a color.
if (pdfShader.get() == NULL) {
newPaint.setColor(0);
// Check for a color shader.
SkShader::GradientInfo gradientInfo;
SkColor gradientColor;
gradientInfo.fColors = &gradientColor;
gradientInfo.fColorOffsets = NULL;
gradientInfo.fColorCount = 1;
if (shader->asAGradient(&gradientInfo) ==
SkShader::kColor_GradientType) {
newPaint.setColor(gradientColor);
}
}
}
if (pdfShader) {
// pdfShader has been canonicalized so we can directly compare
// pointers.
if (fGraphicStack[fGraphicStackIndex].fShader != pdfShader.get()) {
int resourceIndex = fShaderResources.find(pdfShader.get());
if (resourceIndex < 0) {
resourceIndex = fShaderResources.count();
fShaderResources.push(pdfShader.get());
pdfShader->ref();
}
fContent.appendf("/Pattern CS /Pattern cs /P%d SCN /P%d scn\n",
resourceIndex, resourceIndex);
fGraphicStack[fGraphicStackIndex].fShader = pdfShader.get();
}
} else {
SkColor newColor = newPaint.getColor();
newColor = SkColorSetA(newColor, 0xFF);
if (fGraphicStack[fGraphicStackIndex].fShader ||
fGraphicStack[fGraphicStackIndex].fColor != newColor) {
SkString colorString = toPDFColor(newColor);
fContent.append(colorString);
fContent.append("RG ");
fContent.append(colorString);
fContent.append("rg\n");
fGraphicStack[fGraphicStackIndex].fColor = newColor;
fGraphicStack[fGraphicStackIndex].fShader = NULL;
}
}
SkRefPtr<SkPDFGraphicState> newGraphicState = SkRefPtr<SkPDFGraphicState> newGraphicState =
SkPDFGraphicState::getGraphicStateForPaint(newPaint); SkPDFGraphicState::getGraphicStateForPaint(newPaint);
@ -593,17 +675,6 @@ void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint, bool forText) {
fGraphicStack[fGraphicStackIndex].fGraphicState = newGraphicState.get(); fGraphicStack[fGraphicStackIndex].fGraphicState = newGraphicState.get();
} }
SkColor newColor = newPaint.getColor();
newColor = SkColorSetA(newColor, 0xFF);
if (fGraphicStack[fGraphicStackIndex].fColor != newColor) {
SkString colorString = toPDFColor(newColor);
fContent.append(colorString);
fContent.append("RG ");
fContent.append(colorString);
fContent.append("rg\n");
fGraphicStack[fGraphicStackIndex].fColor = newColor;
}
if (forText) { if (forText) {
if (fGraphicStack[fGraphicStackIndex].fTextScaleX != if (fGraphicStack[fGraphicStackIndex].fTextScaleX !=
newPaint.getTextScaleX()) { newPaint.getTextScaleX()) {

768
src/pdf/SkPDFShader.cpp Normal file
Просмотреть файл

@ -0,0 +1,768 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SkPDFShader.h"
#include "SkCanvas.h"
#include "SkPDFCatalog.h"
#include "SkPDFDevice.h"
#include "SkPDFTypes.h"
#include "SkPDFUtils.h"
#include "SkScalar.h"
#include "SkStream.h"
#include "SkThread.h"
#include "SkTypes.h"
static void transformBBox(const SkMatrix& matrix, SkRect* bbox) {
SkMatrix inverse;
inverse.reset();
matrix.invert(&inverse);
inverse.mapRect(bbox);
}
static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) {
SkVector vec = pts[1] - pts[0];
SkScalar mag = vec.length();
SkScalar inv = mag ? SkScalarInvert(mag) : 0;
vec.scale(inv);
matrix->setSinCos(vec.fY, vec.fX);
matrix->preTranslate(pts[0].fX, pts[0].fY);
matrix->preScale(mag, mag);
}
/* Assumes t + startOffset is on the stack and does a linear interpolation on t
between startOffset and endOffset from prevColor to curColor (for each color
component), leaving the result in component order on the stack.
@param range endOffset - startOffset
@param curColor[components] The current color components.
@param prevColor[components] The previous color components.
@param result The result ps function.
*/
static void interpolateColorCode(SkScalar range, SkScalar* curColor,
SkScalar* prevColor, int components,
SkString* result) {
// Figure out how to scale each color component.
SkScalar multiplier[components];
for (int i = 0; i < components; i++) {
multiplier[i] = SkScalarDiv(curColor[i] - prevColor[i], range);
}
// Calculate when we no longer need to keep a copy of the input parameter t.
// If the last component to use t is i, then dupInput[0..i - 1] = true
// and dupInput[i .. components] = false.
bool dupInput[components];
dupInput[components - 1] = false;
for (int i = components - 2; i >= 0; i--) {
dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
}
if (!dupInput[0] && multiplier[0] == 0) {
result->append("pop ");
}
for (int i = 0; i < components; i++) {
// If the next components needs t, make a copy.
if (dupInput[i]) {
result->append("dup ");
}
if (multiplier[i] == 0) {
result->appendScalar(prevColor[i]);
result->append(" ");
} else {
if (multiplier[i] != 1) {
result->appendScalar(multiplier[i]);
result->append(" mul ");
}
if (prevColor[i] != 0) {
result->appendScalar(prevColor[i]);
result->append(" add ");
}
}
if (dupInput[i]) {
result->append("exch\n");
}
}
}
/* Generate Type 4 function code to map t=[0,1) to the passed gradient,
clamping at the edges of the range. The generated code will be of the form:
if (t < 0) {
return colorData[0][r,g,b];
} else {
if (t < info.fColorOffsets[1]) {
return linearinterpolation(colorData[0][r,g,b],
colorData[1][r,g,b]);
} else {
if (t < info.fColorOffsets[2]) {
return linearinterpolation(colorData[1][r,g,b],
colorData[2][r,g,b]);
} else {
... } else {
return colorData[info.fColorCount - 1][r,g,b];
}
...
}
}
*/
static void gradientFunctionCode(const SkShader::GradientInfo& info,
SkString* result) {
/* We want to linearly interpolate from the previous color to the next.
Scale the colors from 0..255 to 0..1 and determine the multipliers
for interpolation.
C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}.
*/
static const int kColorComponents = 3;
SkScalar colorData[info.fColorCount][kColorComponents];
const SkScalar scale = SkScalarInvert(SkIntToScalar(255));
for (int i = 0; i < info.fColorCount; i++) {
colorData[i][0] = SkScalarMul(SkColorGetR(info.fColors[i]), scale);
colorData[i][1] = SkScalarMul(SkColorGetG(info.fColors[i]), scale);
colorData[i][2] = SkScalarMul(SkColorGetB(info.fColors[i]), scale);
}
// Clamp the initial color.
result->append("dup 0 le {pop ");
result->appendScalar(colorData[0][0]);
result->append(" ");
result->appendScalar(colorData[0][1]);
result->append(" ");
result->appendScalar(colorData[0][2]);
result->append(" }\n");
// The gradient colors.
for (int i = 1 ; i < info.fColorCount; i++) {
result->append("{dup ");
result->appendScalar(info.fColorOffsets[i]);
result->append(" le {");
if (info.fColorOffsets[i - 1] != 0) {
result->appendScalar(info.fColorOffsets[i - 1]);
result->append(" sub\n");
}
interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1],
colorData[i], colorData[i - 1], kColorComponents,
result);
result->append("}\n");
}
// Clamp the final color.
result->append("{pop ");
result->appendScalar(colorData[info.fColorCount - 1][0]);
result->append(" ");
result->appendScalar(colorData[info.fColorCount - 1][1]);
result->append(" ");
result->appendScalar(colorData[info.fColorCount - 1][2]);
for (int i = 0 ; i < info.fColorCount; i++) {
result->append("} ifelse\n");
}
}
/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
static void tileModeCode(SkShader::TileMode mode, SkString* result) {
if (mode == SkShader::kRepeat_TileMode) {
result->append("dup truncate sub\n"); // Get the fractional part.
result->append("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1)
return;
}
if (mode == SkShader::kMirror_TileMode) {
// Map t mod 2 into [0, 1, 1, 0].
// Code Stack
result->append("abs " // Map negative to positive.
"dup " // t.s t.s
"truncate " // t.s t
"dup " // t.s t t
"cvi " // t.s t T
"2 mod " // t.s t (i mod 2)
"1 eq " // t.s t true|false
"3 1 roll " // true|false t.s t
"sub " // true|false 0.s
"exch " // 0.s true|false
"{1 exch sub} if\n"); // 1 - 0.s|0.s
}
}
static SkString linearCode(const SkShader::GradientInfo& info) {
SkString function("{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) {
SkString function("{");
// Find the distance from the origin.
function.append("dup " // x y y
"mul " // x y^2
"exch " // y^2 x
"dup " // y^2 x x
"mul " // y^2 x^2
"add " // y^2+x^2
"sqrt\n"); // sqrt(y^2+x^2)
tileModeCode(info.fTileMode, &function);
gradientFunctionCode(info, &function);
function.append("}");
return function;
}
/* The math here is all based on the description in Two_Point_Radial_Gradient,
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) {
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];
SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) - SK_Scalar1;
bool posRoot = info.fRadius[1] > info.fRadius[0];
// 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("2 copy ");
// Calculate -b and b^2.
function.appendScalar(dy);
function.append(" mul exch ");
function.appendScalar(dx);
function.append(" mul add ");
function.appendScalar(sr);
function.append(" sub 2 mul neg dup dup mul\n");
// Calculate c
function.append("4 2 roll dup mul exch dup mul add ");
function.appendScalar(SkScalarMul(sr, sr));
function.append(" sub\n");
// Calculate the determinate
function.appendScalar(SkScalarMul(SkIntToScalar(4), a));
function.append(" mul sub abs sqrt\n");
// And then the final value of t.
if (posRoot) {
function.append("sub ");
} else {
function.append("add ");
}
function.appendScalar(SkScalarMul(SkIntToScalar(2), a));
function.append(" div\n");
tileModeCode(info.fTileMode, &function);
gradientFunctionCode(info, &function);
function.append("}");
return function;
}
static SkString sweepCode(const SkShader::GradientInfo& info) {
SkString function("{exch atan 360 div\n");
tileModeCode(info.fTileMode, &function);
gradientFunctionCode(info, &function);
function.append("}");
return function;
}
SkPDFShader::~SkPDFShader() {
SkAutoMutexAcquire lock(canonicalShadersMutex());
ShaderCanonicalEntry entry(this, fState.get());
int index = canonicalShaders().find(entry);
SkASSERT(index >= 0);
canonicalShaders().removeShuffle(index);
fResources.unrefAll();
}
void SkPDFShader::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect) {
if (indirect)
return emitIndirectObject(stream, catalog);
fContent->emitObject(stream, catalog, indirect);
}
size_t SkPDFShader::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
if (indirect)
return getIndirectOutputSize(catalog);
return fContent->getOutputSize(catalog, indirect);
}
void SkPDFShader::getResources(SkTDArray<SkPDFObject*>* resourceList) {
resourceList->setReserve(resourceList->count() + fResources.count());
for (int i = 0; i < fResources.count(); i++) {
resourceList->push(fResources[i]);
fResources[i]->ref();
}
}
// static
SkPDFShader* SkPDFShader::getPDFShader(const SkShader& shader,
const SkMatrix& matrix,
const SkIRect& surfaceBBox) {
SkRefPtr<SkPDFShader> pdfShader;
SkAutoMutexAcquire lock(canonicalShadersMutex());
SkAutoTDelete<State> shaderState(new State(shader, matrix, surfaceBBox));
ShaderCanonicalEntry entry(NULL, shaderState.get());
int index = canonicalShaders().find(entry);
if (index >= 0) {
SkPDFShader* result = canonicalShaders()[index].fPDFShader;
result->ref();
return result;
}
// The PDFShader takes ownership of the shaderSate.
pdfShader = new SkPDFShader(shaderState.detach());
// Check for a valid shader.
if (pdfShader->fContent.get() == NULL) {
pdfShader->unref();
return NULL;
}
entry.fPDFShader = pdfShader.get();
canonicalShaders().push(entry);
return pdfShader.get(); // return the reference that came from new.
}
// static
SkTDArray<SkPDFShader::ShaderCanonicalEntry>& SkPDFShader::canonicalShaders() {
// This initialization is only thread safe with gcc.
static SkTDArray<ShaderCanonicalEntry> gCanonicalShaders;
return gCanonicalShaders;
}
// static
SkMutex& SkPDFShader::canonicalShadersMutex() {
// This initialization is only thread safe with gcc.
static SkMutex gCanonicalShadersMutex;
return gCanonicalShadersMutex;
}
// static
SkPDFObject* SkPDFShader::rangeObject() {
// This initialization is only thread safe with gcc.
static SkPDFArray* range = NULL;
// This method is only used with canonicalShadersMutex, so it's safe to
// populate domain.
if (range == NULL) {
range = new SkPDFArray;
range->reserve(6);
range->append(new SkPDFInt(0))->unref();
range->append(new SkPDFInt(1))->unref();
range->append(new SkPDFInt(0))->unref();
range->append(new SkPDFInt(1))->unref();
range->append(new SkPDFInt(0))->unref();
range->append(new SkPDFInt(1))->unref();
}
return range;
}
SkPDFShader::SkPDFShader(State* state) : fState(state) {
if (fState.get()->fType == SkShader::kNone_GradientType) {
doImageShader();
} else {
doFunctionShader();
}
}
void SkPDFShader::doFunctionShader() {
SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL;
SkPoint transformPoints[2];
// Depending on the type of the gradient, we want to transform the
// coordinate space in different ways.
const SkShader::GradientInfo* info = &fState.get()->fInfo;
transformPoints[0] = info->fPoint[0];
transformPoints[1] = info->fPoint[1];
switch (fState.get()->fType) {
case SkShader::kLinear_GradientType:
codeFunction = &linearCode;
break;
case SkShader::kRadial_GradientType:
transformPoints[1] = transformPoints[0];
transformPoints[1].fX += info->fRadius[0];
codeFunction = &radialCode;
break;
case SkShader::kRadial2_GradientType: {
// Bail out if the radii are the same. Not setting fContent will
// cause the higher level code to detect the resulting object
// as invalid.
if (info->fRadius[0] == info->fRadius[1]) {
return;
}
transformPoints[1] = transformPoints[0];
SkScalar dr = info->fRadius[1] - info->fRadius[0];
transformPoints[1].fX += dr;
codeFunction = &twoPointRadialCode;
break;
}
case SkShader::kSweep_GradientType:
transformPoints[1] = transformPoints[0];
transformPoints[1].fX += 1;
codeFunction = &sweepCode;
break;
case SkShader::kColor_GradientType:
case SkShader::kNone_GradientType:
SkASSERT(false);
return;
}
// Move any scaling (assuming a unit gradient) or translation
// (and rotation for linear gradient), of the final gradient from
// info->fPoints to the matrix (updating bbox appropriately). Now
// the gradient can be drawn on on the unit segment.
SkMatrix mapperMatrix;
unitToPointsMatrix(transformPoints, &mapperMatrix);
SkMatrix finalMatrix = fState.get()->fCanvasTransform;
finalMatrix.preConcat(mapperMatrix);
finalMatrix.preConcat(fState.get()->fShaderTransform);
SkRect bbox;
bbox.set(fState.get()->fBBox);
transformBBox(finalMatrix, &bbox);
SkRefPtr<SkPDFArray> domain = new SkPDFArray;
domain->unref(); // SkRefPtr and new both took a reference.
domain->reserve(4);
domain->append(new SkPDFScalar(bbox.fLeft))->unref();
domain->append(new SkPDFScalar(bbox.fRight))->unref();
domain->append(new SkPDFScalar(bbox.fTop))->unref();
domain->append(new SkPDFScalar(bbox.fBottom))->unref();
SkString functionCode;
// The two point radial gradient further references fState.get()->fInfo
// in translating from x, y coordinates to the t parameter. So, we have
// to transform the points and radii according to the calculated matrix.
if (fState.get()->fType == SkShader::kRadial2_GradientType) {
SkShader::GradientInfo twoPointRadialInfo = *info;
SkMatrix inverseMapperMatrix;
mapperMatrix.invert(&inverseMapperMatrix);
inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2);
twoPointRadialInfo.fRadius[0] =
inverseMapperMatrix.mapRadius(info->fRadius[0]);
twoPointRadialInfo.fRadius[1] =
inverseMapperMatrix.mapRadius(info->fRadius[1]);
functionCode = codeFunction(twoPointRadialInfo);
} else {
functionCode = codeFunction(*info);
}
SkRefPtr<SkPDFStream> function = makePSFunction(functionCode, domain.get());
// Pass one reference to fResources, SkRefPtr and new both took a reference.
fResources.push(function.get());
SkRefPtr<SkPDFDict> pdfShader = new SkPDFDict;
pdfShader->unref(); // SkRefPtr and new both took a reference.
pdfShader->insert("ShadingType", new SkPDFInt(1))->unref();
pdfShader->insert("ColorSpace", new SkPDFName("DeviceRGB"))->unref();
pdfShader->insert("Domain", domain.get());
pdfShader->insert("Function", new SkPDFObjRef(function.get()))->unref();
fContent = new SkPDFDict("Pattern");
fContent->unref(); // SkRefPtr and new both took a reference.
fContent->insert("PatternType", new SkPDFInt(2))->unref();
fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
fContent->insert("Shading", pdfShader.get());
}
// SkShader* shader, SkMatrix matrix, const SkRect& surfaceBBox
void SkPDFShader::doImageShader() {
fState.get()->fImage.lockPixels();
SkMatrix finalMatrix = fState.get()->fCanvasTransform;
finalMatrix.preConcat(fState.get()->fShaderTransform);
SkRect surfaceBBox;
surfaceBBox.set(fState.get()->fBBox);
transformBBox(finalMatrix, &surfaceBBox);
SkPDFDevice pattern(surfaceBBox.fRight, surfaceBBox.fBottom,
SkPDFDevice::kNoFlip_OriginTransform);
SkCanvas canvas(&pattern);
canvas.clipRect(surfaceBBox, SkRegion::kReplace_Op);
const SkBitmap* image = &fState.get()->fImage;
int width = image->width();
int height = image->height();
SkShader::TileMode tileModes[2];
tileModes[0] = fState.get()->fImageTileModes[0];
tileModes[1] = fState.get()->fImageTileModes[1];
canvas.drawBitmap(*image, 0, 0);
SkRect patternBBox = SkRect::MakeWH(width, height);
// Tiling is implied. First we handle mirroring.
if (tileModes[0] == SkShader::kMirror_TileMode) {
SkMatrix xMirror;
xMirror.setScale(-1, 1);
xMirror.postTranslate(2 * width, 0);
canvas.drawBitmapMatrix(*image, xMirror);
patternBBox.fRight += width;
}
if (tileModes[1] == SkShader::kMirror_TileMode) {
SkMatrix yMirror;
yMirror.setScale(1, -1);
yMirror.postTranslate(0, 2 * height);
canvas.drawBitmapMatrix(*image, yMirror);
patternBBox.fBottom += height;
}
if (tileModes[0] == SkShader::kMirror_TileMode &&
tileModes[1] == SkShader::kMirror_TileMode) {
SkMatrix mirror;
mirror.setScale(-1, -1);
mirror.postTranslate(2 * width, 2 * height);
canvas.drawBitmapMatrix(*image, mirror);
}
// Then handle Clamping, which requires expanding the pattern canvas to
// cover the entire surfaceBBox.
// If both x and y are in clamp mode, we start by filling in the corners.
// (Which are just a rectangles of the corner colors.)
if (tileModes[0] == SkShader::kClamp_TileMode &&
tileModes[1] == SkShader::kClamp_TileMode) {
SkPaint paint;
SkRect rect;
rect = SkRect::MakeLTRB(surfaceBBox.fLeft, surfaceBBox.fTop, 0, 0);
if (!rect.isEmpty()) {
paint.setColor(image->getColor(0, 0));
canvas.drawRect(rect, paint);
}
rect = SkRect::MakeLTRB(width, surfaceBBox.fTop, surfaceBBox.fRight, 0);
if (!rect.isEmpty()) {
paint.setColor(image->getColor(width - 1, 0));
canvas.drawRect(rect, paint);
}
rect = SkRect::MakeLTRB(width, height, surfaceBBox.fRight,
surfaceBBox.fBottom);
if (!rect.isEmpty()) {
paint.setColor(image->getColor(width - 1, height - 1));
canvas.drawRect(rect, paint);
}
rect = SkRect::MakeLTRB(surfaceBBox.fLeft, height, 0,
surfaceBBox.fBottom);
if (!rect.isEmpty()) {
paint.setColor(image->getColor(0, height - 1));
canvas.drawRect(rect, paint);
}
}
// Then expand the left, right, top, then bottom.
if (tileModes[0] == SkShader::kClamp_TileMode) {
SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, height);
if (surfaceBBox.fLeft < 0) {
SkBitmap left;
SkAssertResult(image->extractSubset(&left, subset));
SkMatrix leftMatrix;
leftMatrix.setScale(-surfaceBBox.fLeft, 1);
leftMatrix.postTranslate(surfaceBBox.fLeft, 0);
canvas.drawBitmapMatrix(left, leftMatrix);
if (tileModes[1] == SkShader::kMirror_TileMode) {
leftMatrix.postScale(1, -1);
leftMatrix.postTranslate(0, 2 * height);
canvas.drawBitmapMatrix(left, leftMatrix);
}
patternBBox.fLeft = surfaceBBox.fLeft;
}
if (surfaceBBox.fRight > width) {
SkBitmap right;
subset.offset(width - 1, 0);
SkAssertResult(image->extractSubset(&right, subset));
SkMatrix rightMatrix;
rightMatrix.setScale(surfaceBBox.fRight - width, 1);
rightMatrix.postTranslate(width, 0);
canvas.drawBitmapMatrix(right, rightMatrix);
if (tileModes[1] == SkShader::kMirror_TileMode) {
rightMatrix.postScale(1, -1);
rightMatrix.postTranslate(0, 2 * height);
canvas.drawBitmapMatrix(right, rightMatrix);
}
patternBBox.fRight = surfaceBBox.fRight;
}
}
if (tileModes[1] == SkShader::kClamp_TileMode) {
SkIRect subset = SkIRect::MakeXYWH(0, 0, width, 1);
if (surfaceBBox.fTop < 0) {
SkBitmap top;
SkAssertResult(image->extractSubset(&top, subset));
SkMatrix topMatrix;
topMatrix.setScale(1, -surfaceBBox.fTop);
topMatrix.postTranslate(0, surfaceBBox.fTop);
canvas.drawBitmapMatrix(top, topMatrix);
if (tileModes[0] == SkShader::kMirror_TileMode) {
topMatrix.postScale(-1, 1);
topMatrix.postTranslate(2 * width, 0);
canvas.drawBitmapMatrix(top, topMatrix);
}
patternBBox.fTop = surfaceBBox.fTop;
}
if (surfaceBBox.fBottom > height) {
SkBitmap bottom;
subset.offset(0, height - 1);
SkAssertResult(image->extractSubset(&bottom, subset));
SkMatrix bottomMatrix;
bottomMatrix.setScale(1, surfaceBBox.fBottom - height);
bottomMatrix.postTranslate(0, height);
canvas.drawBitmapMatrix(bottom, bottomMatrix);
if (tileModes[0] == SkShader::kMirror_TileMode) {
bottomMatrix.postScale(-1, 1);
bottomMatrix.postTranslate(2 * width, 0);
canvas.drawBitmapMatrix(bottom, bottomMatrix);
}
patternBBox.fBottom = surfaceBBox.fBottom;
}
}
SkRefPtr<SkPDFArray> patternBBoxArray = new SkPDFArray;
patternBBoxArray->unref(); // SkRefPtr and new both took a reference.
patternBBoxArray->reserve(4);
patternBBoxArray->append(new SkPDFInt(patternBBox.fLeft))->unref();
patternBBoxArray->append(new SkPDFInt(patternBBox.fTop))->unref();
patternBBoxArray->append(new SkPDFInt(patternBBox.fRight))->unref();
patternBBoxArray->append(new SkPDFInt(patternBBox.fBottom))->unref();
// Put the canvas into the pattern stream (fContent).
SkRefPtr<SkStream> content = pattern.content();
content->unref(); // SkRefPtr and content() both took a reference.
pattern.getResources(&fResources);
fContent = new SkPDFStream(content.get());
fContent->unref(); // SkRefPtr and new both took a reference.
fContent->insert("Type", new SkPDFName("Pattern"))->unref();
fContent->insert("PatternType", new SkPDFInt(1))->unref();
fContent->insert("PaintType", new SkPDFInt(1))->unref();
fContent->insert("TilingType", new SkPDFInt(1))->unref();
fContent->insert("BBox", patternBBoxArray.get());
fContent->insert("XStep", new SkPDFInt(patternBBox.width()))->unref();
fContent->insert("YStep", new SkPDFInt(patternBBox.height()))->unref();
fContent->insert("Resources", pattern.getResourceDict().get());
fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
fState.get()->fImage.unlockPixels();
}
SkPDFStream* SkPDFShader::makePSFunction(const SkString& psCode,
SkPDFArray* domain) {
SkRefPtr<SkMemoryStream> funcStream =
new SkMemoryStream(psCode.c_str(), psCode.size(), true);
funcStream->unref(); // SkRefPtr and new both took a reference.
SkPDFStream* result = new SkPDFStream(funcStream.get());
result->insert("FunctionType", new SkPDFInt(4))->unref();
result->insert("Domain", domain);
result->insert("Range", rangeObject());
return result;
}
bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
if (fType != b.fType ||
fCanvasTransform != b.fCanvasTransform ||
fShaderTransform != b.fShaderTransform ||
fBBox != b.fBBox) {
return false;
}
if (fType == SkShader::kNone_GradientType) {
if (fPixelGeneration != b.fPixelGeneration ||
fPixelGeneration == 0 ||
fImageTileModes[0] != b.fImageTileModes[0] ||
fImageTileModes[1] != b.fImageTileModes[1]) {
return false;
}
} else {
if (fInfo.fColorCount != b.fInfo.fColorCount ||
memcmp(fInfo.fColors, b.fInfo.fColors,
sizeof(SkColor) * fInfo.fColorCount) != 0 ||
memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets,
sizeof(SkScalar) * fInfo.fColorCount) != 0 ||
fInfo.fPoint[0] != b.fInfo.fPoint[0] ||
fInfo.fTileMode != b.fInfo.fTileMode) {
return false;
}
switch (fType) {
case SkShader::kLinear_GradientType:
if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) {
return false;
}
break;
case SkShader::kRadial_GradientType:
if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) {
return false;
}
break;
case SkShader::kRadial2_GradientType:
if (fInfo.fPoint[1] != b.fInfo.fPoint[1] ||
fInfo.fRadius[0] != b.fInfo.fRadius[0] ||
fInfo.fRadius[1] != b.fInfo.fRadius[1]) {
return false;
}
break;
case SkShader::kSweep_GradientType:
case SkShader::kNone_GradientType:
case SkShader::kColor_GradientType:
break;
}
}
return true;
}
SkPDFShader::State::State(const SkShader& shader,
const SkMatrix& canvasTransform, const SkIRect& bbox)
: fCanvasTransform(canvasTransform),
fBBox(bbox) {
fInfo.fColorCount = 0;
fInfo.fColors = NULL;
fInfo.fColorOffsets = NULL;
shader.getLocalMatrix(&fShaderTransform);
fType = shader.asAGradient(&fInfo);
if (fType == SkShader::kNone_GradientType) {
SkShader::BitmapType bitmapType;
SkMatrix matrix;
bitmapType = shader.asABitmap(&fImage, &matrix, fImageTileModes, NULL);
if (bitmapType != SkShader::kDefault_BitmapType) {
fImage.reset();
return;
}
SkASSERT(matrix.isIdentity());
fPixelGeneration = fImage.getGenerationID();
} else {
fColorData.set(sk_malloc_throw(
fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar))));
fInfo.fColors = (SkColor*)fColorData.get();
fInfo.fColorOffsets = (SkScalar*)(fInfo.fColors + fInfo.fColorCount);
shader.asAGradient(&fInfo);
}
}

Просмотреть файл

@ -7,6 +7,7 @@ SOURCE := \
SkPDFGraphicState.cpp \ SkPDFGraphicState.cpp \
SkPDFImage.cpp \ SkPDFImage.cpp \
SkPDFPage.cpp \ SkPDFPage.cpp \
SkPDFShader.cpp \
SkPDFStream.cpp \ SkPDFStream.cpp \
SkPDFTypes.cpp \ SkPDFTypes.cpp \
SkPDFUtils.cpp SkPDFUtils.cpp