Index: src/pdf/SkPDFShader.cpp |
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp |
index c30d8a4281daf3b6b8b77f3adda312f38c824ff1..9a2e4a91030b95ab17eea92acb9071bce5e8e4d9 100644 |
--- a/src/pdf/SkPDFShader.cpp |
+++ b/src/pdf/SkPDFShader.cpp |
@@ -122,6 +122,8 @@ static void interpolateColorCode(SkScalar range, SkScalar* curColor, |
} |
} |
*/ |
+static const int kColorComponents = 3; |
+typedef SkScalar ColorTuple[kColorComponents]; |
static void gradientFunctionCode(const SkShader::GradientInfo& info, |
SkDynamicMemoryWStream* result) { |
/* We want to linearly interpolate from the previous color to the next. |
@@ -129,8 +131,7 @@ static void gradientFunctionCode(const SkShader::GradientInfo& info, |
for interpolation. |
C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}. |
*/ |
- static const int kColorComponents = 3; |
- typedef SkScalar ColorTuple[kColorComponents]; |
+ |
SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount); |
ColorTuple *colorData = colorDataAlloc.get(); |
const SkScalar scale = SkScalarInvert(SkIntToScalar(255)); |
@@ -183,6 +184,109 @@ static void gradientFunctionCode(const SkShader::GradientInfo& info, |
} |
} |
+static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1, |
+ const ColorTuple& color2) { |
+ auto retval = sk_make_sp<SkPDFDict>(); |
+ |
+ auto c0 = sk_make_sp<SkPDFArray>(); |
+ c0->appendScalar(color1[0]); |
+ c0->appendScalar(color1[1]); |
+ c0->appendScalar(color1[2]); |
+ retval->insertObject("C0", std::move(c0)); |
+ |
+ auto c1 = sk_make_sp<SkPDFArray>(); |
+ c1->appendScalar(color2[0]); |
+ c1->appendScalar(color2[1]); |
+ c1->appendScalar(color2[2]); |
+ retval->insertObject("C1", std::move(c1)); |
+ |
+ auto domain = sk_make_sp<SkPDFArray>(); |
+ domain->appendScalar(0); |
+ domain->appendScalar(1.0f); |
+ retval->insertObject("Domain", std::move(domain)); |
+ |
+ retval->insertInt("FunctionType", 2); |
+ retval->insertScalar("N", 1.0f); |
+ |
+ return retval; |
+} |
+ |
+static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) { |
+ auto retval = sk_make_sp<SkPDFDict>(); |
+ |
+ // normalize color stops |
+ int colorCount = info.fColorCount; |
+ SkTDArray<SkColor> colors(info.fColors, colorCount); |
+ SkTDArray<SkScalar> colorOffsets(info.fColorOffsets, colorCount); |
+ |
+ int i = 1; |
+ while (i < colorCount - 1) { |
+ // ensure stops are in order |
+ if (colorOffsets[i - 1] > colorOffsets[i]) { |
+ colorOffsets[i] = colorOffsets[i - 1]; |
+ } |
+ |
+ // remove points that are between 2 coincident points |
+ if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) { |
+ colorCount -= 1; |
+ colors.remove(i); |
+ colorOffsets.remove(i); |
+ } else { |
+ i++; |
+ } |
+ } |
+ // find coincident points and slightly move them over |
+ for (i = 1; i < colorCount - 1; i++) { |
+ if (colorOffsets[i - 1] == colorOffsets[i]) { |
+ colorOffsets[i] += 0.00001f; |
+ } |
+ } |
+ // check if last 2 stops coincide |
+ if (colorOffsets[i - 1] == colorOffsets[i]) { |
+ colorOffsets[i - 1] -= 0.00001f; |
+ } |
+ |
+ SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount); |
+ ColorTuple *colorData = colorDataAlloc.get(); |
+ const SkScalar scale = SkScalarInvert(SkIntToScalar(255)); |
+ for (int i = 0; i < colorCount; i++) { |
+ colorData[i][0] = SkScalarMul(SkColorGetR(colors[i]), scale); |
+ colorData[i][1] = SkScalarMul(SkColorGetG(colors[i]), scale); |
+ colorData[i][2] = SkScalarMul(SkColorGetB(colors[i]), scale); |
+ } |
+ |
+ // no need for a stitch function if there are only 2 stops. |
+ if (colorCount == 2) |
+ return createInterpolationFunction(colorData[0], colorData[1]); |
+ |
+ auto encode = sk_make_sp<SkPDFArray>(); |
+ auto bounds = sk_make_sp<SkPDFArray>(); |
+ auto functions = sk_make_sp<SkPDFArray>(); |
+ |
+ auto domain = sk_make_sp<SkPDFArray>(); |
+ domain->appendScalar(0); |
+ domain->appendScalar(1.0f); |
+ retval->insertObject("Domain", std::move(domain)); |
+ retval->insertInt("FunctionType", 3); |
+ |
+ for (int i = 1; i < colorCount; i++) { |
+ if (i > 1) { |
+ bounds->appendScalar(colorOffsets[i-1]); |
+ } |
+ |
+ encode->appendScalar(0); |
+ encode->appendScalar(1.0f); |
+ |
+ functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i])); |
+ } |
+ |
+ retval->insertObject("Encode", std::move(encode)); |
+ retval->insertObject("Bounds", std::move(bounds)); |
+ retval->insertObject("Functions", std::move(functions)); |
+ |
+ return retval; |
+} |
+ |
/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */ |
static void tileModeCode(SkShader::TileMode mode, |
SkDynamicMemoryWStream* result) { |
@@ -705,6 +809,21 @@ static sk_sp<SkPDFStream> make_ps_function( |
return result; |
} |
+// catch cases where the inner just touches the outer circle |
+// and make the inner circle just inside the outer one to match raster |
+static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) { |
+ // detect touching circles |
+ SkScalar distance = SkPoint::Distance(p1, p2); |
+ SkScalar subtractRadii = fabs(r1 - r2); |
+ if (fabs(distance - subtractRadii) < 0.002f) { |
+ if (r1 > r2) { |
+ r1 += 0.002f; |
+ } else { |
+ r2 += 0.002f; |
+ } |
+ } |
+} |
+ |
SkPDFFunctionShader* SkPDFFunctionShader::Create( |
SkPDFCanon* canon, std::unique_ptr<SkPDFShader::State>* autoState) { |
const SkPDFShader::State& state = **autoState; |
@@ -713,109 +832,170 @@ SkPDFFunctionShader* SkPDFFunctionShader::Create( |
const SkMatrix& perspectiveRemover, |
SkDynamicMemoryWStream* function) = nullptr; |
SkPoint transformPoints[2]; |
- |
- // Depending on the type of the gradient, we want to transform the |
- // coordinate space in different ways. |
const SkShader::GradientInfo* info = &state.fInfo; |
- transformPoints[0] = info->fPoint[0]; |
- transformPoints[1] = info->fPoint[1]; |
- switch (state.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::kConical_GradientType: { |
- transformPoints[1] = transformPoints[0]; |
- transformPoints[1].fX += SK_Scalar1; |
- codeFunction = &twoPointConicalCode; |
- break; |
- } |
- case SkShader::kSweep_GradientType: |
- transformPoints[1] = transformPoints[0]; |
- transformPoints[1].fX += SK_Scalar1; |
- codeFunction = &sweepCode; |
- break; |
- case SkShader::kColor_GradientType: |
- case SkShader::kNone_GradientType: |
- default: |
- return nullptr; |
- } |
- |
- // 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 = state.fCanvasTransform; |
finalMatrix.preConcat(state.fShaderTransform); |
- finalMatrix.preConcat(mapperMatrix); |
- |
- // Preserves as much as posible in the final matrix, and only removes |
- // the perspective. The inverse of the perspective is stored in |
- // perspectiveInverseOnly matrix and has 3 useful numbers |
- // (p0, p1, p2), while everything else is either 0 or 1. |
- // In this way the shader will handle it eficiently, with minimal code. |
- SkMatrix perspectiveInverseOnly = SkMatrix::I(); |
- if (finalMatrix.hasPerspective()) { |
- if (!split_perspective(finalMatrix, |
- &finalMatrix, &perspectiveInverseOnly)) { |
- return nullptr; |
- } |
- } |
- SkRect bbox; |
- bbox.set(state.fBBox); |
- if (!inverse_transform_bbox(finalMatrix, &bbox)) { |
- return nullptr; |
- } |
+ bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType || |
+ state.fType == SkShader::kRadial_GradientType || |
+ state.fType == SkShader::kConical_GradientType) && |
+ info->fTileMode == SkShader::kClamp_TileMode && |
+ !finalMatrix.hasPerspective(); |
auto domain = sk_make_sp<SkPDFArray>(); |
- domain->reserve(4); |
- domain->appendScalar(bbox.fLeft); |
- domain->appendScalar(bbox.fRight); |
- domain->appendScalar(bbox.fTop); |
- domain->appendScalar(bbox.fBottom); |
- SkDynamicMemoryWStream functionCode; |
+ int32_t shadingType = 1; |
+ auto pdfShader = sk_make_sp<SkPDFDict>(); |
// The two point radial gradient further references |
// state.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 (state.fType == SkShader::kConical_GradientType) { |
- SkShader::GradientInfo twoPointRadialInfo = *info; |
- SkMatrix inverseMapperMatrix; |
- if (!mapperMatrix.invert(&inverseMapperMatrix)) { |
- return nullptr; |
+ if (doStitchFunctions) { |
+ pdfShader->insertObject("Function", gradientStitchCode(*info)); |
+ shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3; |
+ |
+ auto extend = sk_make_sp<SkPDFArray>(); |
+ extend->reserve(2); |
+ extend->appendBool(true); |
+ extend->appendBool(true); |
+ pdfShader->insertObject("Extend", std::move(extend)); |
+ |
+ auto coords = sk_make_sp<SkPDFArray>(); |
+ if (state.fType == SkShader::kConical_GradientType) { |
+ coords->reserve(6); |
+ SkScalar r1 = info->fRadius[0]; |
+ SkScalar r2 = info->fRadius[1]; |
+ SkPoint pt1 = info->fPoint[0]; |
+ SkPoint pt2 = info->fPoint[1]; |
+ FixUpRadius(pt1, r1, pt2, r2); |
+ |
+ coords->appendScalar(pt1.fX); |
+ coords->appendScalar(pt1.fY); |
+ coords->appendScalar(r1); |
+ |
+ coords->appendScalar(pt2.fX); |
+ coords->appendScalar(pt2.fY); |
+ coords->appendScalar(r2); |
+ } else if (state.fType == SkShader::kRadial_GradientType) { |
+ coords->reserve(6); |
+ const SkPoint& pt1 = info->fPoint[0]; |
+ |
+ coords->appendScalar(pt1.fX); |
+ coords->appendScalar(pt1.fY); |
+ coords->appendScalar(0); |
+ |
+ coords->appendScalar(pt1.fX); |
+ coords->appendScalar(pt1.fY); |
+ coords->appendScalar(info->fRadius[0]); |
+ } else { |
+ coords->reserve(4); |
+ const SkPoint& pt1 = info->fPoint[0]; |
+ const SkPoint& pt2 = info->fPoint[1]; |
+ |
+ coords->appendScalar(pt1.fX); |
+ coords->appendScalar(pt1.fY); |
+ |
+ coords->appendScalar(pt2.fX); |
+ coords->appendScalar(pt2.fY); |
} |
- inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); |
- twoPointRadialInfo.fRadius[0] = |
- inverseMapperMatrix.mapRadius(info->fRadius[0]); |
- twoPointRadialInfo.fRadius[1] = |
- inverseMapperMatrix.mapRadius(info->fRadius[1]); |
- codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode); |
+ |
+ pdfShader->insertObject("Coords", std::move(coords)); |
} else { |
- codeFunction(*info, perspectiveInverseOnly, &functionCode); |
+ // Depending on the type of the gradient, we want to transform the |
+ // coordinate space in different ways. |
+ transformPoints[0] = info->fPoint[0]; |
+ transformPoints[1] = info->fPoint[1]; |
+ switch (state.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::kConical_GradientType: { |
+ transformPoints[1] = transformPoints[0]; |
+ transformPoints[1].fX += SK_Scalar1; |
+ codeFunction = &twoPointConicalCode; |
+ break; |
+ } |
+ case SkShader::kSweep_GradientType: |
+ transformPoints[1] = transformPoints[0]; |
+ transformPoints[1].fX += SK_Scalar1; |
+ codeFunction = &sweepCode; |
+ break; |
+ case SkShader::kColor_GradientType: |
+ case SkShader::kNone_GradientType: |
+ default: |
+ return nullptr; |
+ } |
+ |
+ // 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); |
+ |
+ finalMatrix.preConcat(mapperMatrix); |
+ |
+ // Preserves as much as posible in the final matrix, and only removes |
+ // the perspective. The inverse of the perspective is stored in |
+ // perspectiveInverseOnly matrix and has 3 useful numbers |
+ // (p0, p1, p2), while everything else is either 0 or 1. |
+ // In this way the shader will handle it eficiently, with minimal code. |
+ SkMatrix perspectiveInverseOnly = SkMatrix::I(); |
+ if (finalMatrix.hasPerspective()) { |
+ if (!split_perspective(finalMatrix, |
+ &finalMatrix, &perspectiveInverseOnly)) { |
+ return nullptr; |
+ } |
+ } |
+ |
+ SkRect bbox; |
+ bbox.set(state.fBBox); |
+ if (!inverse_transform_bbox(finalMatrix, &bbox)) { |
+ return nullptr; |
+ } |
+ domain->reserve(4); |
+ domain->appendScalar(bbox.fLeft); |
+ domain->appendScalar(bbox.fRight); |
+ domain->appendScalar(bbox.fTop); |
+ domain->appendScalar(bbox.fBottom); |
+ |
+ SkDynamicMemoryWStream functionCode; |
+ |
+ if (state.fType == SkShader::kConical_GradientType) { |
+ SkShader::GradientInfo twoPointRadialInfo = *info; |
+ SkMatrix inverseMapperMatrix; |
+ if (!mapperMatrix.invert(&inverseMapperMatrix)) { |
+ return nullptr; |
+ } |
+ inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); |
+ twoPointRadialInfo.fRadius[0] = |
+ inverseMapperMatrix.mapRadius(info->fRadius[0]); |
+ twoPointRadialInfo.fRadius[1] = |
+ inverseMapperMatrix.mapRadius(info->fRadius[1]); |
+ codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode); |
+ } else { |
+ codeFunction(*info, perspectiveInverseOnly, &functionCode); |
+ } |
+ |
+ pdfShader->insertObject("Domain", sk_ref_sp(domain.get())); |
+ |
+ // Call canon->makeRangeObject() instead of |
+ // SkPDFShader::MakeRangeObject() so that the canon can |
+ // deduplicate. |
+ std::unique_ptr<SkStreamAsset> functionStream( |
+ functionCode.detachAsStream()); |
+ auto function = make_ps_function(std::move(functionStream), domain.get(), |
+ canon->makeRangeObject()); |
+ pdfShader->insertObjRef("Function", std::move(function)); |
} |
- auto pdfShader = sk_make_sp<SkPDFDict>(); |
- pdfShader->insertInt("ShadingType", 1); |
+ pdfShader->insertInt("ShadingType", shadingType); |
pdfShader->insertName("ColorSpace", "DeviceRGB"); |
- pdfShader->insertObject("Domain", sk_ref_sp(domain.get())); |
- |
- // Call canon->makeRangeObject() instead of |
- // SkPDFShader::MakeRangeObject() so that the canon can |
- // deduplicate. |
- std::unique_ptr<SkStreamAsset> functionStream( |
- functionCode.detachAsStream()); |
- auto function = make_ps_function(std::move(functionStream), domain.get(), |
- canon->makeRangeObject()); |
- pdfShader->insertObjRef("Function", std::move(function)); |
sk_sp<SkPDFFunctionShader> pdfFunctionShader( |
new SkPDFFunctionShader(autoState->release())); |