summaryrefslogtreecommitdiffstats
path: root/skia/sgl/SkStroke.cpp
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-27 00:09:42 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-27 00:09:42 +0000
commitae2c20f398933a9e86c387dcc465ec0f71065ffc (patch)
treede668b1411e2ee0b4e49b6d8f8b68183134ac990 /skia/sgl/SkStroke.cpp
parent09911bf300f1a419907a9412154760efd0b7abc3 (diff)
downloadchromium_src-ae2c20f398933a9e86c387dcc465ec0f71065ffc.zip
chromium_src-ae2c20f398933a9e86c387dcc465ec0f71065ffc.tar.gz
chromium_src-ae2c20f398933a9e86c387dcc465ec0f71065ffc.tar.bz2
Add skia to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'skia/sgl/SkStroke.cpp')
-rw-r--r--skia/sgl/SkStroke.cpp585
1 files changed, 585 insertions, 0 deletions
diff --git a/skia/sgl/SkStroke.cpp b/skia/sgl/SkStroke.cpp
new file mode 100644
index 0000000..530f0fa
--- /dev/null
+++ b/skia/sgl/SkStroke.cpp
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2006-2008 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 "SkStrokerPriv.h"
+#include "SkGeometry.h"
+#include "SkPath.h"
+
+#define kMaxQuadSubdivide 5
+#define kMaxCubicSubdivide 4
+
+static inline bool degenerate_vector(const SkVector& v) {
+ return SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY);
+}
+
+static inline bool degenerate_line(const SkPoint& a, const SkPoint& b,
+ SkScalar tolerance = SK_ScalarNearlyZero) {
+ return SkScalarNearlyZero(a.fX - b.fX, tolerance) &&
+ SkScalarNearlyZero(a.fY - b.fY, tolerance);
+}
+
+static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
+ /* root2/2 is a 45-degree angle
+ make this constant bigger for more subdivisions (but not >= 1)
+ */
+ static const SkScalar kFlatEnoughNormalDotProd =
+ SK_ScalarSqrt2/2 + SK_Scalar1/10;
+
+ SkASSERT(kFlatEnoughNormalDotProd > 0 &&
+ kFlatEnoughNormalDotProd < SK_Scalar1);
+
+ return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
+}
+
+static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
+ static const SkScalar kTooPinchyNormalDotProd = -SK_Scalar1 * 999 / 1000;
+
+ return SkPoint::DotProduct(norm0, norm1) <= kTooPinchyNormalDotProd;
+}
+
+static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
+ SkScalar radius,
+ SkVector* normal, SkVector* unitNormal) {
+ if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
+ return false;
+ }
+ unitNormal->rotateCCW();
+ unitNormal->scale(radius, normal);
+ return true;
+}
+
+static bool set_normal_unitnormal(const SkVector& vec,
+ SkScalar radius,
+ SkVector* normal, SkVector* unitNormal) {
+ if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
+ return false;
+ }
+ unitNormal->rotateCCW();
+ unitNormal->scale(radius, normal);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkPathStroker {
+public:
+ SkPathStroker(SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
+ SkPaint::Join join);
+
+ void moveTo(const SkPoint&);
+ void lineTo(const SkPoint&);
+ void quadTo(const SkPoint&, const SkPoint&);
+ void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
+ void close(bool isLine) { this->finishContour(true, isLine); }
+
+ void done(SkPath* dst, bool isLine) {
+ this->finishContour(false, isLine);
+ fOuter.addPath(fExtra);
+ dst->swap(fOuter);
+ }
+
+private:
+ SkScalar fRadius;
+ SkScalar fInvMiterLimit;
+
+ SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
+ SkPoint fFirstPt, fPrevPt; // on original path
+ SkPoint fFirstOuterPt;
+ int fSegmentCount;
+ bool fPrevIsLine;
+
+ SkStrokerPriv::CapProc fCapper;
+ SkStrokerPriv::JoinProc fJoiner;
+
+ SkPath fInner, fOuter; // outer is our working answer, inner is temp
+ SkPath fExtra; // added as extra complete contours
+
+ void finishContour(bool close, bool isLine);
+ void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
+ bool isLine);
+ void postJoinTo(const SkPoint&, const SkVector& normal,
+ const SkVector& unitNormal);
+
+ void line_to(const SkPoint& currPt, const SkVector& normal);
+ void quad_to(const SkPoint pts[3],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalBC, SkVector* unitNormalBC,
+ int subDivide);
+ void cubic_to(const SkPoint pts[4],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalCD, SkVector* unitNormalCD,
+ int subDivide);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
+ SkVector* unitNormal, bool currIsLine) {
+ SkASSERT(fSegmentCount >= 0);
+
+ SkScalar prevX = fPrevPt.fX;
+ SkScalar prevY = fPrevPt.fY;
+
+ SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
+ unitNormal));
+
+ if (fSegmentCount == 0) {
+ fFirstNormal = *normal;
+ fFirstUnitNormal = *unitNormal;
+ fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
+
+ fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
+ fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
+ } else { // we have a previous segment
+ fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
+ fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
+ }
+ fPrevIsLine = currIsLine;
+}
+
+void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
+ const SkVector& unitNormal) {
+ fPrevPt = currPt;
+ fPrevUnitNormal = unitNormal;
+ fPrevNormal = normal;
+ fSegmentCount += 1;
+}
+
+void SkPathStroker::finishContour(bool close, bool currIsLine) {
+ if (fSegmentCount > 0) {
+ SkPoint pt;
+
+ if (close) {
+ fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
+ fFirstUnitNormal, fRadius, fInvMiterLimit,
+ fPrevIsLine, currIsLine);
+ fOuter.close();
+ // now add fInner as its own contour
+ fInner.getLastPt(&pt);
+ fOuter.moveTo(pt.fX, pt.fY);
+ fOuter.reversePathTo(fInner);
+ fOuter.close();
+ } else { // add caps to start and end
+ // cap the end
+ fInner.getLastPt(&pt);
+ fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
+ currIsLine ? &fInner : NULL);
+ fOuter.reversePathTo(fInner);
+ // cap the start
+ fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
+ fPrevIsLine ? &fInner : NULL);
+ fOuter.close();
+ }
+ }
+ fInner.reset();
+ fSegmentCount = -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPathStroker::SkPathStroker(SkScalar radius, SkScalar miterLimit,
+ SkPaint::Cap cap, SkPaint::Join join)
+ : fRadius(radius) {
+
+ /* This is only used when join is miter_join, but we initialize it here
+ so that it is always defined, to fis valgrind warnings.
+ */
+ fInvMiterLimit = 0;
+
+ if (join == SkPaint::kMiter_Join) {
+ if (miterLimit <= SK_Scalar1) {
+ join = SkPaint::kBevel_Join;
+ } else {
+ fInvMiterLimit = SkScalarInvert(miterLimit);
+ }
+ }
+ fCapper = SkStrokerPriv::CapFactory(cap);
+ fJoiner = SkStrokerPriv::JoinFactory(join);
+ fSegmentCount = -1;
+ fPrevIsLine = false;
+}
+
+void SkPathStroker::moveTo(const SkPoint& pt) {
+ if (fSegmentCount > 0) {
+ this->finishContour(false, false);
+ }
+ fSegmentCount = 0;
+ fFirstPt = fPrevPt = pt;
+}
+
+void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
+ fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
+ fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
+}
+
+void SkPathStroker::lineTo(const SkPoint& currPt) {
+ if (degenerate_line(fPrevPt, currPt)) {
+ return;
+ }
+ SkVector normal, unitNormal;
+
+ this->preJoinTo(currPt, &normal, &unitNormal, true);
+ this->line_to(currPt, normal);
+ this->postJoinTo(currPt, normal, unitNormal);
+}
+
+void SkPathStroker::quad_to(const SkPoint pts[3],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalBC, SkVector* unitNormalBC,
+ int subDivide) {
+ if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
+ normalBC, unitNormalBC)) {
+ // pts[1] nearly equals pts[2], so just draw a line to pts[2]
+ this->line_to(pts[2], normalAB);
+ *normalBC = normalAB;
+ *unitNormalBC = unitNormalAB;
+ return;
+ }
+
+ if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
+ SkPoint tmp[5];
+ SkVector norm, unit;
+
+ SkChopQuadAtHalf(pts, tmp);
+ this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
+ this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
+ } else {
+ SkVector normalB, unitB;
+ SkAssertResult(set_normal_unitnormal(pts[0], pts[2], fRadius,
+ &normalB, &unitB));
+
+ fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
+ pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
+ fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
+ pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
+ }
+}
+
+void SkPathStroker::cubic_to(const SkPoint pts[4],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalCD, SkVector* unitNormalCD,
+ int subDivide) {
+ SkVector ab = pts[1] - pts[0];
+ SkVector cd = pts[3] - pts[2];
+ SkVector normalBC, unitNormalBC;
+
+ bool degenerateAB = degenerate_vector(ab);
+ bool degenerateCD = degenerate_vector(cd);
+
+ if (degenerateAB && degenerateCD) {
+DRAW_LINE:
+ this->line_to(pts[3], normalAB);
+ *normalCD = normalAB;
+ *unitNormalCD = unitNormalAB;
+ return;
+ }
+
+ if (degenerateAB) {
+ ab = pts[2] - pts[0];
+ degenerateAB = degenerate_vector(ab);
+ }
+ if (degenerateCD) {
+ cd = pts[3] - pts[1];
+ degenerateCD = degenerate_vector(cd);
+ }
+ if (degenerateAB || degenerateCD) {
+ goto DRAW_LINE;
+ }
+ SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
+ bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
+ &normalBC, &unitNormalBC);
+
+ if (--subDivide >= 0 &&
+ (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
+ normals_too_curvy(unitNormalBC, *unitNormalCD))) {
+ SkPoint tmp[7];
+ SkVector norm, unit, dummy, unitDummy;
+
+ SkChopCubicAtHalf(pts, tmp);
+ this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
+ subDivide);
+ // we use dummys since we already have a valid (and more accurate)
+ // normals for CD
+ this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
+ } else {
+ SkVector normalB, normalC;
+
+ // need normals to inset/outset the off-curve pts B and C
+
+ if (0) { // this is normal to the line between our adjacent pts
+ normalB = pts[2] - pts[0];
+ normalB.rotateCCW();
+ SkAssertResult(normalB.setLength(fRadius));
+
+ normalC = pts[3] - pts[1];
+ normalC.rotateCCW();
+ SkAssertResult(normalC.setLength(fRadius));
+ } else { // miter-join
+ SkVector unitBC = pts[2] - pts[1];
+ unitBC.normalize();
+ unitBC.rotateCCW();
+
+ normalB = unitNormalAB + unitBC;
+ normalC = *unitNormalCD + unitBC;
+
+ SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
+ SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
+ SkScalarSqrt((SK_Scalar1 + dot)/2))));
+ dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
+ SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
+ SkScalarSqrt((SK_Scalar1 + dot)/2))));
+ }
+
+ fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
+ pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
+ pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
+
+ fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
+ pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
+ pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
+ }
+}
+
+void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
+ bool degenerateAB = degenerate_line(fPrevPt, pt1);
+ bool degenerateBC = degenerate_line(pt1, pt2);
+
+ if (degenerateAB | degenerateBC) {
+ if (degenerateAB ^ degenerateBC) {
+ this->lineTo(pt2);
+ }
+ return;
+ }
+
+ SkVector normalAB, unitAB, normalBC, unitBC;
+
+ this->preJoinTo(pt1, &normalAB, &unitAB, false);
+
+ {
+ SkPoint pts[3], tmp[5];
+ pts[0] = fPrevPt;
+ pts[1] = pt1;
+ pts[2] = pt2;
+
+ if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
+ unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
+ unitBC.rotateCCW();
+ if (normals_too_pinchy(unitAB, unitBC)) {
+ normalBC = unitBC;
+ normalBC.scale(fRadius);
+
+ fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
+ fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
+ fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
+
+ fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
+ fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
+ fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
+
+ fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
+ SkPath::kCW_Direction);
+ } else {
+ this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
+ kMaxQuadSubdivide);
+ SkVector n = normalBC;
+ SkVector u = unitBC;
+ this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
+ kMaxQuadSubdivide);
+ }
+ } else {
+ this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
+ kMaxQuadSubdivide);
+ }
+ }
+
+ this->postJoinTo(pt2, normalBC, unitBC);
+}
+
+void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
+ const SkPoint& pt3) {
+ bool degenerateAB = degenerate_line(fPrevPt, pt1);
+ bool degenerateBC = degenerate_line(pt1, pt2);
+ bool degenerateCD = degenerate_line(pt2, pt3);
+
+ if (degenerateAB + degenerateBC + degenerateCD >= 2) {
+ this->lineTo(pt3);
+ return;
+ }
+
+ SkVector normalAB, unitAB, normalCD, unitCD;
+
+ // find the first tangent (which might be pt1 or pt2
+ {
+ const SkPoint* nextPt = &pt1;
+ if (degenerateAB)
+ nextPt = &pt2;
+ this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
+ }
+
+ {
+ SkPoint pts[4], tmp[13];
+ int i, count;
+ SkVector n, u;
+ SkScalar tValues[3];
+
+ pts[0] = fPrevPt;
+ pts[1] = pt1;
+ pts[2] = pt2;
+ pts[3] = pt3;
+
+#if 1
+ count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
+#else
+ count = 1;
+ memcpy(tmp, pts, 4 * sizeof(SkPoint));
+#endif
+ n = normalAB;
+ u = unitAB;
+ for (i = 0; i < count; i++) {
+ this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
+ kMaxCubicSubdivide);
+ if (i == count - 1) {
+ break;
+ }
+ n = normalCD;
+ u = unitCD;
+
+ }
+
+ // check for too pinchy
+ for (i = 1; i < count; i++) {
+ SkPoint p;
+ SkVector v, c;
+
+ SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c);
+
+ SkScalar dot = SkPoint::DotProduct(c, c);
+ v.scale(SkScalarInvert(dot));
+
+ if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) {
+ fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction);
+ }
+ }
+
+ }
+
+ this->postJoinTo(pt3, normalCD, unitCD);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkPaint.h"
+
+SkStroke::SkStroke() {
+ fWidth = SK_DefaultStrokeWidth;
+ fMiterLimit = SK_DefaultMiterLimit;
+ fCap = SkPaint::kDefault_Cap;
+ fJoin = SkPaint::kDefault_Join;
+ fDoFill = false;
+}
+
+SkStroke::SkStroke(const SkPaint& p) {
+ fWidth = p.getStrokeWidth();
+ fMiterLimit = p.getStrokeMiter();
+ fCap = (uint8_t)p.getStrokeCap();
+ fJoin = (uint8_t)p.getStrokeJoin();
+ fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
+}
+
+SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
+ fWidth = width;
+ fMiterLimit = p.getStrokeMiter();
+ fCap = (uint8_t)p.getStrokeCap();
+ fJoin = (uint8_t)p.getStrokeJoin();
+ fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
+}
+
+void SkStroke::setWidth(SkScalar width) {
+ SkASSERT(width >= 0);
+ fWidth = width;
+}
+
+void SkStroke::setMiterLimit(SkScalar miterLimit) {
+ SkASSERT(miterLimit >= 0);
+ fMiterLimit = miterLimit;
+}
+
+void SkStroke::setCap(SkPaint::Cap cap) {
+ SkASSERT((unsigned)cap < SkPaint::kCapCount);
+ fCap = SkToU8(cap);
+}
+
+void SkStroke::setJoin(SkPaint::Join join) {
+ SkASSERT((unsigned)join < SkPaint::kJoinCount);
+ fJoin = SkToU8(join);
+}
+
+void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
+ SkASSERT(&src != NULL && dst != NULL);
+
+ dst->reset();
+ if (SkScalarHalf(fWidth) <= 0) {
+ return;
+ }
+
+ SkPathStroker stroker(SkScalarHalf(fWidth), fMiterLimit, this->getCap(),
+ this->getJoin());
+
+ SkPath::Iter iter(src, false);
+ SkPoint pts[4];
+ SkPath::Verb verb, lastSegment = SkPath::kMove_Verb;
+
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ stroker.moveTo(pts[0]);
+ break;
+ case SkPath::kLine_Verb:
+ stroker.lineTo(pts[1]);
+ lastSegment = verb;
+ break;
+ case SkPath::kQuad_Verb:
+ stroker.quadTo(pts[1], pts[2]);
+ lastSegment = verb;
+ break;
+ case SkPath::kCubic_Verb:
+ stroker.cubicTo(pts[1], pts[2], pts[3]);
+ lastSegment = verb;
+ break;
+ case SkPath::kClose_Verb:
+ stroker.close(lastSegment == SkPath::kLine_Verb);
+ break;
+ default:
+ break;
+ }
+ }
+ stroker.done(dst, lastSegment == SkPath::kLine_Verb);
+
+ if (fDoFill) {
+ dst->addPath(src);
+ }
+}
+
+void SkStroke::strokeLine(const SkPoint& p0, const SkPoint& p1,
+ SkPath* dst) const {
+ SkPath tmp;
+
+ tmp.moveTo(p0);
+ tmp.lineTo(p1);
+ this->strokePath(tmp, dst);
+}
+