Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(352)

Unified Diff: src/pdf/SkPDFShader.cpp

Issue 1925233003: SkPDF: Use type 2/3 shading for gradient shaders (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: removed whitespace Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « AUTHORS ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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()));
« no previous file with comments | « AUTHORS ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698