diff options
-rw-r--r-- | samplecode/SampleAndroidShadows.cpp | 55 | ||||
-rwxr-xr-x | src/utils/SkOffsetPolygon.cpp | 35 | ||||
-rwxr-xr-x | src/utils/SkOffsetPolygon.h | 4 | ||||
-rwxr-xr-x | src/utils/SkShadowTessellator.cpp | 548 | ||||
-rw-r--r-- | tests/OffsetSimplePolyTest.cpp | 1 |
5 files changed, 422 insertions, 221 deletions
diff --git a/samplecode/SampleAndroidShadows.cpp b/samplecode/SampleAndroidShadows.cpp index 8f076f4c66..5d4cd26956 100644 --- a/samplecode/SampleAndroidShadows.cpp +++ b/samplecode/SampleAndroidShadows.cpp @@ -28,9 +28,13 @@ class ShadowsView : public SampleView { SkPath fCirclePath; SkPath fFunkyRRPath; SkPath fCubicPath; + SkPath fStarPath; SkPath fSquareRRectPath; SkPath fWideRectPath; SkPath fWideOvalPath; + SkPath fNotchPath; + SkPath fTabPath; + SkPoint3 fLightPos; SkScalar fZDelta; SkScalar fAnimTranslate; @@ -68,11 +72,38 @@ protected: fCubicPath.cubicTo(100 * SK_Scalar1, 50 * SK_Scalar1, 20 * SK_Scalar1, 100 * SK_Scalar1, 0 * SK_Scalar1, 0 * SK_Scalar1); + fStarPath.moveTo(0.0f, -50.0f); + fStarPath.lineTo(14.43f, -25.0f); + fStarPath.lineTo(43.30f, -25.0f); + fStarPath.lineTo(28.86f, 0.0f); + fStarPath.lineTo(43.30f, 25.0f); + fStarPath.lineTo(14.43f, 25.0f); + fStarPath.lineTo(0.0f, 50.0f); + fStarPath.lineTo(-14.43f, 25.0f); + fStarPath.lineTo(-43.30f, 25.0f); + fStarPath.lineTo(-28.86f, 0.0f); + fStarPath.lineTo(-43.30f, -25.0f); + fStarPath.lineTo(-14.43f, -25.0f); fSquareRRectPath.addRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-50, -50, 100, 100), 10, 10)); fWideRectPath.addRect(SkRect::MakeXYWH(0, 0, 630, 70)); fWideOvalPath.addOval(SkRect::MakeXYWH(0, 0, 630, 70)); + fNotchPath.moveTo(0, 80); + fNotchPath.arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), -90, -90, false); + fNotchPath.lineTo(-75, 100); + fNotchPath.lineTo(-75, -100); + fNotchPath.lineTo(75, -100); + fNotchPath.lineTo(75, 100); + fNotchPath.arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), 0, -90, false); + + fTabPath.moveTo(-75, -100); + fTabPath.lineTo(75, -100); + fTabPath.lineTo(75, 100); + fTabPath.arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), 0, 90, false); + fTabPath.arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), 90, 90, false); + fTabPath.lineTo(-75, 100); + fLightPos = SkPoint3::Make(350, 0, 600); } @@ -141,8 +172,8 @@ protected: const SkPaint& paint, SkScalar ambientAlpha, const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) { if (fIgnoreShadowAlpha) { - ambientAlpha = 255; - spotAlpha = 255; + ambientAlpha = 1; + spotAlpha = 1; } if (!fShowAmbient) { ambientAlpha = 0; @@ -220,6 +251,24 @@ protected: this->drawShadowedPath(canvas, fCubicPath, zPlaneParams, paint, fAnimAlpha*kAmbientAlpha, lightPos, kLightWidth, fAnimAlpha*kSpotAlpha); + paint.setColor(SK_ColorWHITE); + canvas->translate(250, -180); + zPlaneParams.fZ = SkTMax(1.0f, 8 + fZDelta); + this->drawShadowedPath(canvas, fStarPath, zPlaneParams, paint, + kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); + + paint.setColor(SK_ColorWHITE); + canvas->translate(150, 0); + zPlaneParams.fZ = SkTMax(1.0f, 2 + fZDelta); + this->drawShadowedPath(canvas, fNotchPath, zPlaneParams, paint, + kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); + + paint.setColor(SK_ColorWHITE); + canvas->translate(200, 0); + zPlaneParams.fZ = SkTMax(1.0f, 16 + fZDelta); + this->drawShadowedPath(canvas, fTabPath, zPlaneParams, paint, + kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); + // circular reveal SkPath tmpPath; SkPath tmpClipPath; @@ -227,7 +276,7 @@ protected: Op(fSquareRRectPath, tmpClipPath, kIntersect_SkPathOp, &tmpPath); paint.setColor(SK_ColorMAGENTA); - canvas->translate(-125, 60); + canvas->translate(-725, 240); zPlaneParams.fZ = SkTMax(1.0f, 32 + fZDelta); this->drawShadowedPath(canvas, tmpPath, zPlaneParams, paint, .1f, lightPos, kLightWidth, .5f); diff --git a/src/utils/SkOffsetPolygon.cpp b/src/utils/SkOffsetPolygon.cpp index bfd12d2bc8..94fe96562d 100755 --- a/src/utils/SkOffsetPolygon.cpp +++ b/src/utils/SkOffsetPolygon.cpp @@ -218,11 +218,26 @@ struct EdgeData { OffsetSegment fInset; SkPoint fIntersection; SkScalar fTValue; + uint16_t fStart; + uint16_t fEnd; + uint16_t fIndex; bool fValid; void init() { fIntersection = fInset.fP0; fTValue = SK_ScalarMin; + fStart = 0; + fEnd = 0; + fIndex = 0; + fValid = true; + } + + void init(uint16_t start, uint16_t end) { + fIntersection = fInset.fP0; + fTValue = SK_ScalarMin; + fStart = start; + fEnd = end; + fIndex = start; fValid = true; } }; @@ -571,7 +586,8 @@ static bool is_simple_polygon(const SkPoint* polygon, int polygonSize) { // TODO: assuming a constant offset here -- do we want to support variable offset? bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize, - SkScalar offset, SkTDArray<SkPoint>* offsetPolygon) { + SkScalar offset, SkTDArray<SkPoint>* offsetPolygon, + SkTDArray<int>* polygonIndices) { if (inputPolygonSize < 3) { return false; } @@ -625,20 +641,20 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz EdgeData& edge = edgeData.push_back(); edge.fInset.fP0 = inputPolygonVerts[currIndex] + prevNormal; edge.fInset.fP1 = inputPolygonVerts[currIndex] + currNormal; - edge.init(); + edge.init(currIndex, currIndex); prevNormal = currNormal; } EdgeData& edge = edgeData.push_back(); edge.fInset.fP0 = inputPolygonVerts[currIndex] + prevNormal; edge.fInset.fP1 = inputPolygonVerts[currIndex] + normals[currIndex]; - edge.init(); + edge.init(currIndex, currIndex); } // Add the edge EdgeData& edge = edgeData.push_back(); edge.fInset.fP0 = inputPolygonVerts[currIndex] + normals[currIndex]; edge.fInset.fP1 = inputPolygonVerts[nextIndex] + normals[currIndex]; - edge.init(); + edge.init(currIndex, nextIndex); prevIndex = currIndex; currIndex++; @@ -654,6 +670,10 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz prevIndex = (prevIndex + edgeDataSize - 1) % edgeDataSize; continue; } + if (!edgeData[currIndex].fValid) { + currIndex = (currIndex + 1) % edgeDataSize; + continue; + } SkScalar s, t; SkPoint intersection; @@ -676,6 +696,7 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz // add intersection edgeData[currIndex].fIntersection = intersection; edgeData[currIndex].fTValue = t; + edgeData[currIndex].fIndex = edgeData[prevIndex].fEnd; // go to next segment prevIndex = currIndex; @@ -714,6 +735,9 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz (*offsetPolygon)[currIndex], kCleanupTolerance))) { *offsetPolygon->push() = edgeData[i].fIntersection; + if (polygonIndices) { + *polygonIndices->push() = edgeData[i].fIndex; + } currIndex++; } } @@ -722,6 +746,9 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz SkPointPriv::EqualsWithinTolerance((*offsetPolygon)[0], (*offsetPolygon)[currIndex], kCleanupTolerance)) { offsetPolygon->pop(); + if (polygonIndices) { + polygonIndices->pop(); + } } // compute signed area to check winding (it should be same as the original polygon) diff --git a/src/utils/SkOffsetPolygon.h b/src/utils/SkOffsetPolygon.h index a0555ab51e..b6c3a222ad 100755 --- a/src/utils/SkOffsetPolygon.h +++ b/src/utils/SkOffsetPolygon.h @@ -44,10 +44,12 @@ inline bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPoly * @param offset How far we wish to offset the polygon. * Positive value means inset, negative value means outset. * @param offsetPolgon The resulting offset polygon, if any. + * @param polygonIndices The indices of the original polygon that map to the new one. * @return true if an offset simple polygon exists, false otherwise. */ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize, - SkScalar offset, SkTDArray<SkPoint>* offsetPolygon); + SkScalar offset, SkTDArray<SkPoint>* offsetPolygon, + SkTDArray<int>* polygonIndices = nullptr); /** * Offset a segment by the given distance at each point. diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp index 75e4059222..5d6a4cc15d 100755 --- a/src/utils/SkShadowTessellator.cpp +++ b/src/utils/SkShadowTessellator.cpp @@ -59,6 +59,9 @@ protected: bool addArc(const SkVector& nextNormal, bool finishArc); + void appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2); + void appendQuad(uint16_t index0, uint16_t index1, uint16_t index2, uint16_t index3); + SkScalar heightFunc(SkScalar x, SkScalar y) { return fZPlaneParams.fX*x + fZPlaneParams.fY*y + fZPlaneParams.fZ; } @@ -258,24 +261,41 @@ bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, bool finishArc) currNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin; *fPositions.push() = fPrevPoint + currNormal; *fColors.push() = fPenumbraColor; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 2; + this->appendTriangle(fPrevUmbraIndex, fPositions.count() - 1, fPositions.count() - 2); prevNormal = currNormal; } if (finishArc && numSteps) { *fPositions.push() = fPrevPoint + nextNormal; *fColors.push() = fPenumbraColor; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 2; + this->appendTriangle(fPrevUmbraIndex, fPositions.count() - 1, fPositions.count() - 2); } fPrevOutset = nextNormal; return (numSteps > 0); } +void SkBaseShadowTessellator::appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2) { + auto indices = fIndices.append(3); + + indices[0] = index0; + indices[1] = index1; + indices[2] = index2; +} + +void SkBaseShadowTessellator::appendQuad(uint16_t index0, uint16_t index1, + uint16_t index2, uint16_t index3) { + auto indices = fIndices.append(6); + + indices[0] = index0; + indices[1] = index1; + indices[2] = index2; + + indices[3] = index2; + indices[4] = index1; + indices[5] = index3; +} + bool SkBaseShadowTessellator::setTransformedHeightFunc(const SkMatrix& ctm) { if (SkScalarNearlyZero(fZPlaneParams.fX) && SkScalarNearlyZero(fZPlaneParams.fY)) { fTransformedHeightFunc = [this](const SkPoint& p) { @@ -481,21 +501,11 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, *fColors.push() = fPenumbraColor; if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 2; - - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 2; + this->appendQuad(fPrevUmbraIndex, fPositions.count() - 3, + fPositions.count() - 2, fPositions.count() - 1); } else { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 3; + this->appendQuad(fPositions.count() - 2, fPositions.count() - 1, + fPrevUmbraIndex, fPositions.count() - 3); } // if transparent, add point to first one in array and add to center fan @@ -516,21 +526,11 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, *fColors.push() = fPenumbraColor; if (fColors[fPrevUmbraIndex] > fColors[fFirstVertexIndex]) { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fFirstVertexIndex; - - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fFirstVertexIndex; + this->appendQuad(fPrevUmbraIndex, fPositions.count() - 2, + fFirstVertexIndex, fPositions.count() - 1); } else { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fFirstVertexIndex; + this->appendQuad(fPositions.count() - 2, fPositions.count() - 1, + fPrevUmbraIndex, fFirstVertexIndex); } fPrevOutset = normal; } @@ -540,9 +540,7 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, fPositions[0] *= SkScalarFastInvert(fCentroidCount); fColors[0] = this->umbraColor(fTransformedHeightFunc(fPositions[0])); - *fIndices.push() = 0; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fFirstVertexIndex; + this->appendTriangle(0, fPrevUmbraIndex, fFirstVertexIndex); } // final fan @@ -551,9 +549,7 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, fPrevPoint = fFirstPoint; fRadius = this->offset(fTransformedHeightFunc(fPrevPoint)); if (this->addArc(fFirstOutset, false)) { - *fIndices.push() = fFirstVertexIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fFirstVertexIndex + 1; + this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1, fFirstVertexIndex + 1); } else { // arc is too small, set the first penumbra point to be the same position // as the last one @@ -668,21 +664,11 @@ void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVecto // set triangularization to get best interpolation of color if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 2; - - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 2; + this->appendQuad(fPrevUmbraIndex, fPositions.count() - 3, + fPositions.count() - 2, fPositions.count() - 1); } else { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 3; + this->appendQuad(fPositions.count() - 2, fPositions.count() - 1, + fPrevUmbraIndex, fPositions.count() - 3); } // if transparent, add point to first one in array and add to center fan @@ -690,9 +676,7 @@ void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVecto fPositions[0] += centerPoint; ++fCentroidCount; - *fIndices.push() = 0; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; + this->appendTriangle(0, fPrevUmbraIndex, fPositions.count() - 2); } fSplitPreviousEdge = true; @@ -712,21 +696,11 @@ void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVecto // set triangularization to get best interpolation of color if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 2; - - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 2; + this->appendQuad(fPrevUmbraIndex, fPositions.count() - 3, + fPositions.count() - 2, fPositions.count() - 1); } else { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 3; + this->appendQuad(fPositions.count() - 2, fPositions.count() - 1, + fPrevUmbraIndex, fPositions.count() - 3); } // if transparent, add point to first one in array and add to center fan @@ -734,9 +708,7 @@ void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVecto fPositions[0] += nextPoint; ++fCentroidCount; - *fIndices.push() = 0; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; + this->appendTriangle(0, fPrevUmbraIndex, fPositions.count() - 2); } fPrevUmbraIndex = fPositions.count() - 2; @@ -759,6 +731,9 @@ private: bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint); int getClosestUmbraPoint(const SkPoint& point); + bool computeConvexShadow(SkScalar radius); + bool computeConcaveShadow(SkScalar radius); + void handleLine(const SkPoint& p) override; bool handlePolyPoint(const SkPoint& p); @@ -804,9 +779,6 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat , fFirstUmbraOutside(false) , fValidUmbra(true) { - // TODO: support some concave paths - SkASSERT(path.isConvex()); - // make sure we're not below the canvas plane if (this->setZOffset(path.getBounds(), ctm.hasPerspective())) { // Adjust light height and radius @@ -864,125 +836,55 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat } // check to see if umbra collapses - SkScalar minDistSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, fPathPolygon[0], - fPathPolygon[1]); - SkRect bounds; - bounds.setBounds(&fPathPolygon[0], fPathPolygon.count()); - for (int i = 1; i < fPathPolygon.count(); ++i) { - int j = i + 1; - if (i == fPathPolygon.count() - 1) { - j = 0; + if (path.isConvex()) { + SkScalar minDistSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, + fPathPolygon[0], + fPathPolygon[1]); + SkRect bounds; + bounds.setBounds(&fPathPolygon[0], fPathPolygon.count()); + for (int i = 1; i < fPathPolygon.count(); ++i) { + int j = i + 1; + if (i == fPathPolygon.count() - 1) { + j = 0; + } + SkPoint currPoint = fPathPolygon[i]; + SkPoint nextPoint = fPathPolygon[j]; + SkScalar distSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, currPoint, + nextPoint); + if (distSq < minDistSq) { + minDistSq = distSq; + } } - SkPoint currPoint = fPathPolygon[i]; - SkPoint nextPoint = fPathPolygon[j]; - SkScalar distSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, currPoint, - nextPoint); - if (distSq < minDistSq) { - minDistSq = distSq; + static constexpr auto kTolerance = 1.0e-2f; + if (minDistSq < (radius + kTolerance)*(radius + kTolerance)) { + // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha + SkScalar newRadius = SkScalarSqrt(minDistSq) - kTolerance; + fOffsetAdjust = newRadius - radius; + SkScalar ratio = 128 * (newRadius + radius) / radius; + // they aren't PMColors, but the interpolation algorithm is the same + fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio); + radius = newRadius; } } - static constexpr auto kTolerance = 1.0e-2f; - if (minDistSq < (radius + kTolerance)*(radius + kTolerance)) { - // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha - SkScalar newRadius = SkScalarSqrt(minDistSq) - kTolerance; - fOffsetAdjust = newRadius - radius; - SkScalar ratio = 128 * (newRadius + radius) / radius; - // they aren't PMColors, but the interpolation algorithm is the same - fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio); - radius = newRadius; - } // compute vectors for clip tests this->computeClipVectorsAndTestCentroid(); - // generate inner ring - if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), radius, - &fUmbraPolygon)) { - // this shouldn't happen, but just in case we'll inset using the centroid - fValidUmbra = false; - } - - // walk around the path polygon, generate outer ring and connect to inner ring - if (fTransparent) { - *fPositions.push() = fCentroid; - *fColors.push() = fUmbraColor; - } - fCurrUmbraPoint = 0; - for (int i = 0; i < fPathPolygon.count(); ++i) { - if (!this->handlePolyPoint(fPathPolygon[i])) { - return; + if (ctm.hasPerspective()) { + for (int i = 0; i < fPositions.count(); ++i) { + SkScalar pathZ = fTransformedHeightFunc(fPositions[i]); + SkScalar factor = SkScalarInvert(fLightZ - pathZ); + fPositions[i].fX = (fPositions[i].fX*fLightZ - lightPos.fX*pathZ)*factor; } } - if (!this->indexCount()) { - return; - } - - // finish up the final verts - SkVector normal; - if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) { - normal *= fRadius; - this->addArc(normal, true); - - // add to center fan - if (fTransparent) { - *fIndices.push() = 0; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fFirstVertexIndex; - // or to clip ring - } else { - if (fFirstUmbraOutside) { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fFirstVertexIndex; - *fIndices.push() = fFirstVertexIndex + 1; - if (fPrevUmbraOutside) { - // fill out quad - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fFirstVertexIndex + 1; - *fIndices.push() = fPrevUmbraIndex + 1; - } - } else if (fPrevUmbraOutside) { - // add tri - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fFirstVertexIndex; - *fIndices.push() = fPrevUmbraIndex + 1; - } + if (path.isConvex()) { + if (!this->computeConvexShadow(radius)) { + return; } - - // add final edge - *fPositions.push() = fFirstPoint + normal; - *fColors.push() = fPenumbraColor; - - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fFirstVertexIndex; - - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fFirstVertexIndex; - - fPrevOutset = normal; - } - - // final fan - if (fPositions.count() >= 3) { - fPrevUmbraIndex = fFirstVertexIndex; - fPrevPoint = fFirstPoint; - if (this->addArc(fFirstOutset, false)) { - *fIndices.push() = fFirstVertexIndex; - *fIndices.push() = fPositions.count() - 1; - if (fFirstUmbraOutside) { - *fIndices.push() = fFirstVertexIndex + 2; - } else { - *fIndices.push() = fFirstVertexIndex + 1; - } - } else { - // no arc added, fix up by setting first penumbra point position to last one - if (fFirstUmbraOutside) { - fPositions[fFirstVertexIndex + 2] = fPositions[fPositions.count() - 1]; - } else { - fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1]; - } + } else { + if (!this->computeConcaveShadow(radius)) { + return; } } @@ -1010,13 +912,8 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat *fPositions.push() = fCentroid + SkVector::Make(2, 2); *fColors.push() = SkColorSetARGB(255, 0, 255, 255); - *fIndices.push() = fPositions.count() - 4; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - - *fIndices.push() = fPositions.count() - 4; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 3; + this->appendQuad(fPositions.count() - 2, fPositions.count() - 1, + fPositions.count() - 4, fPositions.count() - 3); #endif fSucceeded = true; @@ -1199,6 +1096,244 @@ int SkSpotShadowTessellator::getClosestUmbraPoint(const SkPoint& p) { return index; } +bool SkSpotShadowTessellator::computeConvexShadow(SkScalar radius) { + // generate inner ring + if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), radius, + &fUmbraPolygon)) { + // this shouldn't happen, but just in case we'll inset using the centroid + fValidUmbra = false; + } + + // walk around the path polygon, generate outer ring and connect to inner ring + if (fTransparent) { + *fPositions.push() = fCentroid; + *fColors.push() = fUmbraColor; + } + fCurrUmbraPoint = 0; + for (int i = 0; i < fPathPolygon.count(); ++i) { + if (!this->handlePolyPoint(fPathPolygon[i])) { + return false; + } + } + + if (!this->indexCount()) { + return false; + } + + // finish up the final verts + SkVector normal; + if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) { + normal *= fRadius; + this->addArc(normal, true); + + // add to center fan + if (fTransparent) { + this->appendTriangle(0, fPrevUmbraIndex, fFirstVertexIndex); + // or to clip ring + } else { + if (fFirstUmbraOutside) { + this->appendTriangle(fPrevUmbraIndex, fFirstVertexIndex, fFirstVertexIndex + 1); + if (fPrevUmbraOutside) { + // fill out quad + this->appendTriangle(fPrevUmbraIndex, fFirstVertexIndex + 1, + fPrevUmbraIndex + 1); + } + } else if (fPrevUmbraOutside) { + // add tri + this->appendTriangle(fPrevUmbraIndex, fFirstVertexIndex, fPrevUmbraIndex + 1); + } + } + + // add final edge + *fPositions.push() = fFirstPoint + normal; + *fColors.push() = fPenumbraColor; + + this->appendQuad(fPrevUmbraIndex, fPositions.count() - 2, + fFirstVertexIndex, fPositions.count() - 1); + + fPrevOutset = normal; + } + + // final fan + if (fPositions.count() >= 3) { + fPrevUmbraIndex = fFirstVertexIndex; + fPrevPoint = fFirstPoint; + if (this->addArc(fFirstOutset, false)) { + if (fFirstUmbraOutside) { + this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1, + fFirstVertexIndex + 2); + } else { + this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1, + fFirstVertexIndex + 1); + } + } else { + // no arc added, fix up by setting first penumbra point position to last one + if (fFirstUmbraOutside) { + fPositions[fFirstVertexIndex + 2] = fPositions[fPositions.count() - 1]; + } else { + fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1]; + } + } + } + + return true; +} + +bool SkSpotShadowTessellator::computeConcaveShadow(SkScalar radius) { + // TODO: remove when we support filling the penumbra + if (fTransparent) { + return false; + } + + // generate inner ring + SkTDArray<int> umbraIndices; + umbraIndices.setReserve(fPathPolygon.count()); + if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), radius, + &fUmbraPolygon, &umbraIndices)) { + // TODO: figure out how to handle this case + return false; + } + + // generate outer ring + SkTDArray<SkPoint> penumbraPolygon; + SkTDArray<int> penumbraIndices; + penumbraPolygon.setReserve(fUmbraPolygon.count()); + penumbraIndices.setReserve(fUmbraPolygon.count()); + if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), -radius, + &penumbraPolygon, &penumbraIndices)) { + // TODO: figure out how to handle this case + return false; + } + + if (!fUmbraPolygon.count() || !penumbraPolygon.count()) { + return false; + } + + // attach the rings together + + // find minimum indices + int minIndex = 0; + int min = penumbraIndices[0]; + for (int i = 1; i < penumbraIndices.count(); ++i) { + if (penumbraIndices[i] < min) { + min = penumbraIndices[i]; + minIndex = i; + } + } + int currPenumbra = minIndex; + + minIndex = 0; + min = umbraIndices[0]; + for (int i = 1; i < umbraIndices.count(); ++i) { + if (umbraIndices[i] < min) { + min = umbraIndices[i]; + minIndex = i; + } + } + int currUmbra = minIndex; + + // now find a case where the indices are equal (there should be at least one) + int maxPenumbraIndex = fPathPolygon.count()-1; + int maxUmbraIndex = fPathPolygon.count()-1; + while (penumbraIndices[currPenumbra] != umbraIndices[currUmbra]) { + if (penumbraIndices[currPenumbra] < umbraIndices[currUmbra]) { + penumbraIndices[currPenumbra] += fPathPolygon.count(); + maxPenumbraIndex = penumbraIndices[currPenumbra]; + currPenumbra = (currPenumbra + 1) % penumbraPolygon.count(); + } else { + umbraIndices[currUmbra] += fPathPolygon.count(); + maxUmbraIndex = umbraIndices[currUmbra]; + currUmbra = (currUmbra + 1) % fUmbraPolygon.count(); + } + } + + *fPositions.push() = penumbraPolygon[currPenumbra]; + *fColors.push() = fPenumbraColor; + int prevPenumbraIndex = 0; + *fPositions.push() = fUmbraPolygon[currUmbra]; + *fColors.push() = fUmbraColor; + fPrevUmbraIndex = 1; + + int nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count(); + int nextUmbra = (currUmbra + 1) % fUmbraPolygon.count(); + while (penumbraIndices[nextPenumbra] <= maxPenumbraIndex || + umbraIndices[nextUmbra] <= maxUmbraIndex) { + + if (umbraIndices[nextUmbra] == penumbraIndices[nextPenumbra]) { + // advance both one step + *fPositions.push() = penumbraPolygon[nextPenumbra]; + *fColors.push() = fPenumbraColor; + int currPenumbraIndex = fPositions.count() - 1; + + *fPositions.push() = fUmbraPolygon[nextUmbra]; + *fColors.push() = fUmbraColor; + int currUmbraIndex = fPositions.count() - 1; + + this->appendQuad(prevPenumbraIndex, currPenumbraIndex, + fPrevUmbraIndex, currUmbraIndex); + + prevPenumbraIndex = currPenumbraIndex; + penumbraIndices[currPenumbra] += fPathPolygon.count(); + currPenumbra = nextPenumbra; + nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count(); + + fPrevUmbraIndex = currUmbraIndex; + umbraIndices[currUmbra] += fPathPolygon.count(); + currUmbra = nextUmbra; + nextUmbra = (currUmbra + 1) % fUmbraPolygon.count(); + } + + while (penumbraIndices[nextPenumbra] < umbraIndices[nextUmbra] && + penumbraIndices[nextPenumbra] <= maxPenumbraIndex) { + // fill out penumbra arc + *fPositions.push() = penumbraPolygon[nextPenumbra]; + *fColors.push() = fPenumbraColor; + int currPenumbraIndex = fPositions.count() - 1; + + this->appendTriangle(prevPenumbraIndex, currPenumbraIndex, fPrevUmbraIndex); + + prevPenumbraIndex = currPenumbraIndex; + // this ensures the ordering when we wrap around + penumbraIndices[currPenumbra] += fPathPolygon.count(); + currPenumbra = nextPenumbra; + nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count(); + } + + while (umbraIndices[nextUmbra] < penumbraIndices[nextPenumbra] && + umbraIndices[nextUmbra] <= maxUmbraIndex) { + // fill out umbra arc + *fPositions.push() = fUmbraPolygon[nextUmbra]; + *fColors.push() = fUmbraColor; + int currUmbraIndex = fPositions.count() - 1; + + this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex); + + fPrevUmbraIndex = currUmbraIndex; + // this ensures the ordering when we wrap around + umbraIndices[currUmbra] += fPathPolygon.count(); + currUmbra = nextUmbra; + nextUmbra = (currUmbra + 1) % fUmbraPolygon.count(); + } + } + // finish up by advancing both one step + *fPositions.push() = penumbraPolygon[nextPenumbra]; + *fColors.push() = fPenumbraColor; + int currPenumbraIndex = fPositions.count() - 1; + + *fPositions.push() = fUmbraPolygon[nextUmbra]; + *fColors.push() = fUmbraColor; + int currUmbraIndex = fPositions.count() - 1; + + this->appendQuad(prevPenumbraIndex, currPenumbraIndex, + fPrevUmbraIndex, currUmbraIndex); + + if (fTransparent) { + // TODO: fill penumbra + } + + return true; +} + void SkSpotShadowTessellator::mapPoints(SkScalar scale, const SkVector& xlate, SkPoint* pts, int count) { // TODO: vectorize @@ -1356,33 +1491,27 @@ void SkSpotShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& if (!duplicate) { // add to center fan if transparent or centroid showing if (fTransparent) { - *fIndices.push() = 0; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = currUmbraIndex; + this->appendTriangle(0, fPrevUmbraIndex, currUmbraIndex); // otherwise add to clip ring } else { SkPoint clipPoint; bool isOutside = this->clipUmbraPoint(fPositions[currUmbraIndex], fCentroid, &clipPoint); + if (isOutside) { *fPositions.push() = clipPoint; *fColors.push() = fUmbraColor; - - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = currUmbraIndex; - *fIndices.push() = currUmbraIndex + 1; + this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, currUmbraIndex + 1); if (fPrevUmbraOutside) { // fill out quad - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = currUmbraIndex + 1; - *fIndices.push() = fPrevUmbraIndex + 1; + this->appendTriangle(fPrevUmbraIndex, currUmbraIndex + 1, + fPrevUmbraIndex + 1); } } else if (fPrevUmbraOutside) { // add tri - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = currUmbraIndex; - *fIndices.push() = fPrevUmbraIndex + 1; + this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, fPrevUmbraIndex + 1); } + fPrevUmbraOutside = isOutside; } } @@ -1393,14 +1522,9 @@ void SkSpotShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& *fColors.push() = fPenumbraColor; if (!duplicate) { - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = prevPenumbraIndex; - *fIndices.push() = currUmbraIndex; + this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex); } - - *fIndices.push() = prevPenumbraIndex; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = currUmbraIndex; + this->appendTriangle(prevPenumbraIndex, fPositions.count() - 1, currUmbraIndex); fPrevUmbraIndex = currUmbraIndex; fPrevOutset = nextNormal; diff --git a/tests/OffsetSimplePolyTest.cpp b/tests/OffsetSimplePolyTest.cpp index 50f680ef23..de720b5cf2 100644 --- a/tests/OffsetSimplePolyTest.cpp +++ b/tests/OffsetSimplePolyTest.cpp @@ -92,7 +92,6 @@ DEF_TEST(OffsetSimplePoly, reporter) { // past full inset result = SkOffsetSimplePolygon(&rrectPoly[0], rrectPoly.count(), 75, &offsetPoly); REPORTER_ASSERT(reporter, !result); - REPORTER_ASSERT(reporter, offsetPoly.count() == 0); // troublesome case SkTDArray<SkPoint> clippedRRectPoly; |