blob: 11d0b5308a17dd90b5cadfabea671488b9bb92d7 [file] [log] [blame]
#Topic Path
#Alias Path_Reference ##
#Alias Paths ##
#Class SkPath
#Code
#Populate
##
Paths contain geometry. Paths may be empty, or contain one or more Verbs that
outline a figure. Path always starts with a move verb to a Cartesian_Coordinate,
and may be followed by additional verbs that add lines or curves.
Adding a close verb makes the geometry into a continuous loop, a closed contour.
Paths may contain any number of contours, each beginning with a move verb.
Path contours may contain only a move verb, or may also contain lines,
Quadratic_Beziers, Conics, and Cubic_Beziers. Path contours may be open or
closed.
When used to draw a filled area, Path describes whether the fill is inside or
outside the geometry. Path also describes the winding rule used to fill
overlapping contours.
Internally, Path lazily computes metrics likes bounds and convexity. Call
SkPath::updateBoundsCache to make Path thread safe.
#Subtopic Verb
#Alias Verbs ##
#Line # line and curve type ##
#Enum Verb
#Line # controls how Path Points are interpreted ##
#Code
enum Verb {
kMove_Verb,
kLine_Verb,
kQuad_Verb,
kConic_Verb,
kCubic_Verb,
kClose_Verb,
kDone_Verb,
};
##
Verb instructs Path how to interpret one or more Point and optional Conic_Weight;
manage Contour, and terminate Path.
#Const kMove_Verb 0
#Line # starts new Contour at next Point ##
Consecutive kMove_Verb are preserved but all but the last kMove_Verb is
ignored. kMove_Verb after other Verbs implicitly closes the previous Contour
if SkPaint::kFill_Style is set when drawn; otherwise, stroke is drawn open.
kMove_Verb as the last Verb is preserved but ignored.
##
#Const kLine_Verb 1
#Line # adds Line from Last_Point to next Point ##
Line is a straight segment from Point to Point. Consecutive kLine_Verb
extend Contour. kLine_Verb at same position as prior kMove_Verb is
preserved, and draws Point if SkPaint::kStroke_Style is set, and
SkPaint::Cap is SkPaint::kSquare_Cap or SkPaint::kRound_Cap. kLine_Verb
at same position as prior line or curve Verb is preserved but is ignored.
##
#Const kQuad_Verb 2
#Line # adds Quad from Last_Point ##
Adds Quad from Last_Point, using control Point, and end Point.
Quad is a parabolic section within tangents from Last_Point to control Point,
and control Point to end Point.
##
#Const kConic_Verb 3
#Line # adds Conic from Last_Point ##
Adds Conic from Last_Point, using control Point, end Point, and Conic_Weight.
Conic is a elliptical, parabolic, or hyperbolic section within tangents
from Last_Point to control Point, and control Point to end Point, constrained
by Conic_Weight. Conic_Weight less than one is elliptical; equal to one is
parabolic (and identical to Quad); greater than one hyperbolic.
##
#Const kCubic_Verb 4
#Line # adds Cubic from Last_Point ##
Adds Cubic from Last_Point, using two control Points, and end Point.
Cubic is a third-order Bezier_Curve section within tangents from Last_Point
to first control Point, and from second control Point to end Point.
##
#Const kClose_Verb 5
#Line # closes Contour ##
Closes Contour, connecting Last_Point to kMove_Verb Point. Consecutive
kClose_Verb are preserved but only first has an effect. kClose_Verb after
kMove_Verb has no effect.
##
#Const kDone_Verb 6
#Line # terminates Path ##
Not in Verb_Array, but returned by Path iterator.
##
Each Verb has zero or more Points stored in Path.
Path iterator returns complete curve descriptions, duplicating shared Points
for consecutive entries.
#Table
#Legend
# Verb # Allocated Points # Iterated Points # Weights ##
##
# kMove_Verb # 1 # 1 # 0 ##
# kLine_Verb # 1 # 2 # 0 ##
# kQuad_Verb # 2 # 3 # 0 ##
# kConic_Verb # 2 # 3 # 1 ##
# kCubic_Verb # 3 # 4 # 0 ##
# kClose_Verb # 0 # 1 # 0 ##
# kDone_Verb # -- # 0 # 0 ##
##
#Example
void draw(SkCanvas* canvas) {
SkPath path;
path.lineTo(20, 20);
path.quadTo(-10, -10, 30, 30);
path.close();
path.cubicTo(1, 2, 3, 4, 5, 6);
path.conicTo(0, 0, 0, 0, 2);
uint8_t verbs[7];
int count = path.getVerbs(verbs, (int) SK_ARRAY_COUNT(verbs));
const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close" };
SkDebugf("verb count: %d\nverbs: ", count);
for (int i = 0; i < count; ++i) {
SkDebugf("k%s_Verb ", verbStr[verbs[i]]);
}
SkDebugf("\n");
}
#StdOut
verb count: 7
verbs: kMove_Verb kLine_Verb kQuad_Verb kClose_Verb kMove_Verb kCubic_Verb kConic_Verb
##
##
#Enum Verb ##
#Subtopic Verb ##
# ------------------------------------------------------------------------------
#Subtopic Direction
#Line # contour orientation, clockwise or counterclockwise ##
#Alias Directions ##
#Enum Direction
#Line # sets Contour clockwise or counterclockwise ##
#Code
enum Direction : int {
kCW_Direction,
kCCW_Direction,
};
##
Direction describes whether Contour is clockwise or counterclockwise.
When Path contains multiple overlapping Contours, Direction together with
Fill_Type determines whether overlaps are filled or form holes.
Direction also determines how Contour is measured. For instance, dashing
measures along Path to determine where to start and stop stroke; Direction
will change dashed results as it steps clockwise or counterclockwise.
Closed Contours like Rect, Round_Rect, Circle, and Oval added with
kCW_Direction travel clockwise; the same added with kCCW_Direction
travel counterclockwise.
#Const kCW_Direction 0
#Line # contour travels clockwise ##
##
#Const kCCW_Direction 1
#Line # contour travels counterclockwise ##
##
#Example
#Height 100
void draw(SkCanvas* canvas) {
const SkPoint arrow[] = { {40, -5}, {45, 0}, {40, 5} };
const SkRect rect = {10, 10, 90, 90};
SkPaint rectPaint;
rectPaint.setAntiAlias(true);
SkPaint textPaint(rectPaint);
rectPaint.setStyle(SkPaint::kStroke_Style);
SkPaint arrowPaint(rectPaint);
SkPath arrowPath;
arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 320, 0,
SkPath1DPathEffect::kRotate_Style));
for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
canvas->drawRect(rect, rectPaint);
for (unsigned start : { 0, 1, 2, 3 } ) {
SkPath path;
path.addRect(rect, direction, start);
canvas->drawPath(path, arrowPaint);
}
canvas->drawString(SkPath::kCW_Direction == direction ? "CW" : "CCW", rect.centerX(),
rect.centerY(), textPaint);
canvas->translate(120, 0);
}
}
##
#SeeAlso arcTo rArcTo isRect isNestedFillRects addRect addOval
#Enum Direction ##
#Subtopic Direction ##
# ------------------------------------------------------------------------------
#Method SkPath()
#In Constructors
#Line # constructs with default values ##
#Populate
#Example
SkPath path;
SkDebugf("path is " "%s" "empty", path.isEmpty() ? "" : "not ");
#StdOut
path is empty
##
##
#SeeAlso reset rewind
##
# ------------------------------------------------------------------------------
#Method SkPath(const SkPath& path)
#In Constructors
#Line # makes a shallow copy ##
#Populate
#Example
#Description
Modifying one path does not effect another, even if they started as copies
of each other.
##
SkPath path;
path.lineTo(20, 20);
SkPath path2(path);
path2.close();
SkDebugf("path verbs: %d\n", path.countVerbs());
SkDebugf("path2 verbs: %d\n", path2.countVerbs());
path.reset();
SkDebugf("after reset\n" "path verbs: %d\n", path.countVerbs());
SkDebugf("path2 verbs: %d\n", path2.countVerbs());
#StdOut
path verbs: 2
path2 verbs: 3
after reset
path verbs: 0
path2 verbs: 3
##
##
#SeeAlso operator=(const SkPath& path)
##
# ------------------------------------------------------------------------------
#Method ~SkPath()
#Line # decreases Reference_Count of owned objects ##
#Populate
#Example
#Description
delete calls Path destructor, but copy of original in path2 is unaffected.
##
void draw(SkCanvas* canvas) {
SkPath* path = new SkPath();
path->lineTo(20, 20);
SkPath path2(*path);
delete path;
SkDebugf("path2 is " "%s" "empty", path2.isEmpty() ? "" : "not ");
}
##
#SeeAlso SkPath() SkPath(const SkPath& path) operator=(const SkPath& path)
##
# ------------------------------------------------------------------------------
#Method SkPath& operator=(const SkPath& path)
#Line # makes a shallow copy ##
#Populate
#Example
SkPath path1;
path1.addRect({10, 20, 30, 40});
SkPath path2 = path1;
const SkRect& b1 = path1.getBounds();
SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);
const SkRect& b2 = path2.getBounds();
SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);
#StdOut
path1 bounds = 10, 20, 30, 40
path2 bounds = 10, 20, 30, 40
#StdOut ##
##
#SeeAlso swap SkPath(const SkPath& path)
##
# ------------------------------------------------------------------------------
#Method bool operator==(const SkPath& a, const SkPath& b)
#Line # compares Paths for equality ##
#Populate
#Example
#Description
rewind() removes Verb_Array but leaves storage; since storage is not compared,
Path pair are equivalent.
##
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {
SkDebugf("%s one %c= two\n", prefix, a == b ? '=' : '!');
};
SkPath one;
SkPath two;
debugster("empty", one, two);
one.moveTo(0, 0);
debugster("moveTo", one, two);
one.rewind();
debugster("rewind", one, two);
one.moveTo(0, 0);
one.reset();
debugster("reset", one, two);
}
#StdOut
empty one == two
moveTo one != two
rewind one == two
reset one == two
##
##
#SeeAlso operator!=(const SkPath& a, const SkPath& b) operator=(const SkPath& path)
##
# ------------------------------------------------------------------------------
#Method bool operator!=(const SkPath& a, const SkPath& b)
#Line # compares paths for inequality ##
#Populate
#Example
#Description
Path pair are equal though their convexity is not equal.
##
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {
SkDebugf("%s one %c= two\n", prefix, a != b ? '!' : '=');
};
SkPath one;
SkPath two;
debugster("empty", one, two);
one.addRect({10, 20, 30, 40});
two.addRect({10, 20, 30, 40});
debugster("add rect", one, two);
one.setConvexity(SkPath::kConcave_Convexity);
debugster("setConvexity", one, two);
SkDebugf("convexity %c=\n", one.getConvexity() == two.getConvexity() ? '=' : '!');
}
#StdOut
empty one == two
add rect one == two
setConvexity one == two
convexity !=
##
##
##
# ------------------------------------------------------------------------------
#Subtopic Property
#Line # metrics and attributes ##
##
#Method bool isInterpolatable(const SkPath& compare) const
#In Property
#In Interpolate
#Line # returns if pair contains equal counts of Verb_Array and Weights ##
#Populate
#Example
SkPath path, path2;
path.moveTo(20, 20);
path.lineTo(40, 40);
path.lineTo(20, 20);
path.lineTo(40, 40);
path.close();
path2.addRect({20, 20, 40, 40});
SkDebugf("paths are " "%s" "interpolatable", path.isInterpolatable(path2) ? "" : "not ");
#StdOut
paths are interpolatable
##
##
#SeeAlso isInterpolatable
##
# ------------------------------------------------------------------------------
#Subtopic Interpolate
#Line # weighted average of Path pair ##
##
#Method bool interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const
#In Interpolate
#Line # interpolates between Path pair ##
Interpolates between Paths with Point_Array of equal size.
Copy Verb_Array and Weights to out, and set out Point_Array to a weighted
average of this Point_Array and ending Point_Array, using the formula:
#Formula # (Path Point * weight) + ending Point * (1 - weight) ##.
weight is most useful when between zero (ending Point_Array) and
one (this Point_Array); will work with values outside of this
range.
interpolate() returns false and leaves out unchanged if Point_Array is not
the same size as ending Point_Array. Call isInterpolatable to check Path
compatibility prior to calling interpolate().
#Param ending Point_Array averaged with this Point_Array ##
#Param weight contribution of this Point_Array, and
one minus contribution of ending Point_Array
##
#Param out Path replaced by interpolated averages ##
#Return true if Paths contain same number of Points ##
#Example
#Height 60
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPath path, path2;
path.moveTo(20, 20);
path.lineTo(40, 40);
path.lineTo(20, 40);
path.lineTo(40, 20);
path.close();
path2.addRect({20, 20, 40, 40});
for (SkScalar i = 0; i <= 1; i += 1.f / 6) {
SkPath interp;
path.interpolate(path2, i, &interp);
canvas->drawPath(interp, paint);
canvas->translate(30, 0);
}
}
##
#SeeAlso isInterpolatable
##
# ------------------------------------------------------------------------------
#Subtopic Fill_Type
#Line # fill rule, normal and inverted ##
#Enum FillType
#Line # sets winding rule and inverse fill ##
#Code
enum FillType {
kWinding_FillType,
kEvenOdd_FillType,
kInverseWinding_FillType,
kInverseEvenOdd_FillType,
};
##
Fill_Type selects the rule used to fill Path. Path set to kWinding_FillType
fills if the sum of Contour edges is not zero, where clockwise edges add one, and
counterclockwise edges subtract one. Path set to kEvenOdd_FillType fills if the
number of Contour edges is odd. Each Fill_Type has an inverse variant that
reverses the rule:
kInverseWinding_FillType fills where the sum of Contour edges is zero;
kInverseEvenOdd_FillType fills where the number of Contour edges is even.
#Example
#Height 100
#Description
The top row has two clockwise rectangles. The second row has one clockwise and
one counterclockwise rectangle. The even-odd variants draw the same. The
winding variants draw the top rectangle overlap, which has a winding of 2, the
same as the outer parts of the top rectangles, which have a winding of 1.
##
void draw(SkCanvas* canvas) {
SkPath path;
path.addRect({10, 10, 30, 30}, SkPath::kCW_Direction);
path.addRect({20, 20, 40, 40}, SkPath::kCW_Direction);
path.addRect({10, 60, 30, 80}, SkPath::kCW_Direction);
path.addRect({20, 70, 40, 90}, SkPath::kCCW_Direction);
SkPaint strokePaint;
strokePaint.setStyle(SkPaint::kStroke_Style);
SkRect clipRect = {0, 0, 51, 100};
canvas->drawPath(path, strokePaint);
SkPaint fillPaint;
for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType,
SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {
canvas->translate(51, 0);
canvas->save();
canvas->clipRect(clipRect);
path.setFillType(fillType);
canvas->drawPath(path, fillPaint);
canvas->restore();
}
}
##
#Const kWinding_FillType 0
#Line # is enclosed by a non-zero sum of Contour Directions ##
##
#Const kEvenOdd_FillType 1
#Line # is enclosed by an odd number of Contours ##
##
#Const kInverseWinding_FillType 2
#Line # is enclosed by a zero sum of Contour Directions ##
##
#Const kInverseEvenOdd_FillType 3
#Line # is enclosed by an even number of Contours ##
##
#Example
#Height 230
void draw(SkCanvas* canvas) {
SkPath path;
path.addRect({20, 10, 80, 70}, SkPath::kCW_Direction);
path.addRect({40, 30, 100, 90}, SkPath::kCW_Direction);
SkPaint strokePaint;
strokePaint.setStyle(SkPaint::kStroke_Style);
SkRect clipRect = {0, 0, 128, 128};
canvas->drawPath(path, strokePaint);
canvas->drawLine({0, 50}, {120, 50}, strokePaint);
SkPaint textPaint;
textPaint.setAntiAlias(true);
SkScalar textHPos[] = { 10, 30, 60, 90, 110 };
canvas->drawPosTextH("01210", 5, textHPos, 48, textPaint);
textPaint.setTextSize(18);
canvas->translate(0, 128);
canvas->scale(.5f, .5f);
canvas->drawString("inverse", 384, 150, textPaint);
SkPaint fillPaint;
for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType,
SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {
canvas->save();
canvas->clipRect(clipRect);
path.setFillType(fillType);
canvas->drawPath(path, fillPaint);
canvas->restore();
canvas->drawString(fillType & 1 ? "even-odd" : "winding", 64, 170, textPaint);
canvas->translate(128, 0);
}
}
##
#SeeAlso SkPaint::Style Direction getFillType setFillType
##
# ------------------------------------------------------------------------------
#Method FillType getFillType() const
#In Fill_Type
#Line # returns Fill_Type: winding, even-odd, inverse ##
#Populate
#Example
SkPath path;
SkDebugf("default path fill type is %s\n",
path.getFillType() == SkPath::kWinding_FillType ? "kWinding_FillType" :
path.getFillType() == SkPath::kEvenOdd_FillType ? "kEvenOdd_FillType" :
path.getFillType() == SkPath::kInverseWinding_FillType ? "kInverseWinding_FillType" :
"kInverseEvenOdd_FillType");
#StdOut
default path fill type is kWinding_FillType
##
##
#SeeAlso FillType setFillType isInverseFillType
##
# ------------------------------------------------------------------------------
#Method void setFillType(FillType ft)
#In Fill_Type
#Line # sets Fill_Type: winding, even-odd, inverse ##
#Populate
#Example
#Description
If empty Path is set to inverse FillType, it fills all pixels.
##
#Height 64
SkPath path;
path.setFillType(SkPath::kInverseWinding_FillType);
SkPaint paint;
paint.setColor(SK_ColorBLUE);
canvas->drawPath(path, paint);
##
#SeeAlso FillType getFillType toggleInverseFillType
##
# ------------------------------------------------------------------------------
#Method bool isInverseFillType() const
#In Fill_Type
#Line # returns if Fill_Type fills outside geometry ##
#Populate
#Example
SkPath path;
SkDebugf("default path fill type is inverse: %s\n",
path.isInverseFillType() ? "true" : "false");
#StdOut
default path fill type is inverse: false
##
##
#SeeAlso FillType getFillType setFillType toggleInverseFillType
##
# ------------------------------------------------------------------------------
#Method void toggleInverseFillType()
#In Fill_Type
#Line # toggles Fill_Type between inside and outside geometry ##
Replaces FillType with its inverse. The inverse of FillType describes the area
unmodified by the original FillType.
#Table
#Legend
# FillType # toggled FillType ##
##
# kWinding_FillType # kInverseWinding_FillType ##
# kEvenOdd_FillType # kInverseEvenOdd_FillType ##
# kInverseWinding_FillType # kWinding_FillType ##
# kInverseEvenOdd_FillType # kEvenOdd_FillType ##
##
#Example
#Description
Path drawn normally and through its inverse touches every pixel once.
##
#Height 100
SkPath path;
SkPaint paint;
paint.setColor(SK_ColorRED);
paint.setTextSize(80);
paint.getTextPath("ABC", 3, 20, 80, &path);
canvas->drawPath(path, paint);
path.toggleInverseFillType();
paint.setColor(SK_ColorGREEN);
canvas->drawPath(path, paint);
##
#SeeAlso FillType getFillType setFillType isInverseFillType
##
#Subtopic Fill_Type ##
# ------------------------------------------------------------------------------
#Subtopic Convexity
#Line # if Path is concave or convex ##
#Enum Convexity
#Line # returns if Path is convex or concave ##
#Code
enum Convexity : uint8_t {
kUnknown_Convexity,
kConvex_Convexity,
kConcave_Convexity,
};
##
Path is convex if it contains one Contour and Contour loops no more than
360 degrees, and Contour angles all have same Direction. Convex Path
may have better performance and require fewer resources on GPU_Surface.
Path is concave when either at least one Direction change is clockwise and
another is counterclockwise, or the sum of the changes in Direction is not 360
degrees.
Initially Path Convexity is kUnknown_Convexity. Path Convexity is computed
if needed by destination Surface.
#Const kUnknown_Convexity 0
#Line # indicates Convexity has not been determined ##
##
#Const kConvex_Convexity 1
#Line # one Contour made of a simple geometry without indentations ##
##
#Const kConcave_Convexity 2
#Line # more than one Contour, or a geometry with indentations ##
##
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
const char* labels[] = { "unknown", "convex", "concave" };
for (SkScalar x : { 40, 100 } ) {
SkPath path;
quad[0].fX = x;
path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
canvas->drawPath(path, paint);
canvas->drawString(labels[(int) path.getConvexity()], 30, 100, paint);
canvas->translate(100, 100);
}
}
##
#SeeAlso Contour Direction getConvexity getConvexityOrUnknown setConvexity isConvex
#Enum Convexity ##
#Method Convexity getConvexity() const
#In Convexity
#Line # returns geometry convexity, computing if necessary ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkDebugf("%s path convexity is %s\n", prefix,
SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :
SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };
SkPath path;
debugster("initial", path);
path.lineTo(50, 0);
debugster("first line", path);
path.lineTo(50, 50);
debugster("second line", path);
path.lineTo(100, 50);
debugster("third line", path);
}
##
#SeeAlso Convexity Contour Direction getConvexityOrUnknown setConvexity isConvex
##
# ------------------------------------------------------------------------------
#Method Convexity getConvexityOrUnknown() const
#In Convexity
#Line # returns geometry convexity if known ##
#Populate
#Example
#Description
Convexity is unknown unless getConvexity is called without a subsequent call
that alters the path.
##
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkDebugf("%s path convexity is %s\n", prefix,
SkPath::kUnknown_Convexity == path.getConvexityOrUnknown() ? "unknown" :
SkPath::kConvex_Convexity == path.getConvexityOrUnknown() ? "convex" : "concave"); };
SkPath path;
debugster("initial", path);
path.lineTo(50, 0);
debugster("first line", path);
path.getConvexity();
path.lineTo(50, 50);
debugster("second line", path);
path.lineTo(100, 50);
path.getConvexity();
debugster("third line", path);
}
##
#SeeAlso Convexity Contour Direction getConvexity setConvexity isConvex
##
# ------------------------------------------------------------------------------
#Method void setConvexity(Convexity convexity)
#In Convexity
#Line # sets if geometry is convex to avoid future computation ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkDebugf("%s path convexity is %s\n", prefix,
SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :
SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };
SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
SkPath path;
path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
debugster("initial", path);
path.setConvexity(SkPath::kConcave_Convexity);
debugster("after forcing concave", path);
path.setConvexity(SkPath::kUnknown_Convexity);
debugster("after forcing unknown", path);
}
##
#SeeAlso Convexity Contour Direction getConvexity getConvexityOrUnknown isConvex
##
# ------------------------------------------------------------------------------
#Method bool isConvex() const
#In Convexity
#Line # returns if geometry is convex ##
#Populate
#Example
#Description
Concave shape is erroneously considered convex after a forced call to
setConvexity.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
for (SkScalar x : { 40, 100 } ) {
SkPath path;
quad[0].fX = x;
path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
path.setConvexity(SkPath::kConvex_Convexity);
canvas->drawPath(path, paint);
canvas->drawString(path.isConvex() ? "convex" : "not convex", 30, 100, paint);
canvas->translate(100, 100);
}
}
##
#SeeAlso Convexity Contour Direction getConvexity getConvexityOrUnknown setConvexity
##
#Subtopic Convexity ##
# ------------------------------------------------------------------------------
#Method bool isOval(SkRect* bounds) const
#In Property
#Line # returns if describes Oval ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
SkPath path;
path.addOval({20, 20, 220, 220});
SkRect bounds;
if (path.isOval(&bounds)) {
paint.setColor(0xFF9FBFFF);
canvas->drawRect(bounds, paint);
}
paint.setColor(0x3f000000);
canvas->drawPath(path, paint);
}
##
#SeeAlso Oval addCircle addOval
##
# ------------------------------------------------------------------------------
#Method bool isRRect(SkRRect* rrect) const
#In Property
#Line # returns if describes Round_Rect ##
#Populate
#Example
#Description
Draw rounded rectangle and its bounds.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
SkPath path;
path.addRRect(SkRRect::MakeRectXY({20, 20, 220, 220}, 30, 50));
SkRRect rrect;
if (path.isRRect(&rrect)) {
const SkRect& bounds = rrect.rect();
paint.setColor(0xFF9FBFFF);
canvas->drawRect(bounds, paint);
}
paint.setColor(0x3f000000);
canvas->drawPath(path, paint);
}
##
#SeeAlso Round_Rect addRoundRect addRRect
##
# ------------------------------------------------------------------------------
#Method SkPath& reset()
#In Constructors
#Line # removes Verb_Array, Point_Array, and Weights; frees memory ##
#Populate
#Example
SkPath path1, path2;
path1.setFillType(SkPath::kInverseWinding_FillType);
path1.addRect({10, 20, 30, 40});
SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
path1.reset();
SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
##
#SeeAlso rewind()
##
# ------------------------------------------------------------------------------
#Method SkPath& rewind()
#In Constructors
#Line # removes Verb_Array, Point_Array, and Weights, keeping memory ##
#Populate
#Example
#Description
Although path1 retains its internal storage, it is indistinguishable from
a newly initialized path.
##
SkPath path1, path2;
path1.setFillType(SkPath::kInverseWinding_FillType);
path1.addRect({10, 20, 30, 40});
SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
path1.rewind();
SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
##
#SeeAlso reset()
##
# ------------------------------------------------------------------------------
#Method bool isEmpty() const
#In Property
#Line # returns if verb count is zero ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkDebugf("%s path is %s" "empty\n", prefix, path.isEmpty() ? "" : "not ");
};
SkPath path;
debugster("initial", path);
path.moveTo(0, 0);
debugster("after moveTo", path);
path.rewind();
debugster("after rewind", path);
path.lineTo(0, 0);
debugster("after lineTo", path);
path.reset();
debugster("after reset", path);
}
#StdOut
initial path is empty
after moveTo path is not empty
after rewind path is empty
after lineTo path is not empty
after reset path is empty
##
##
#SeeAlso SkPath() reset() rewind()
##
# ------------------------------------------------------------------------------
#Method bool isLastContourClosed() const
#In Property
#Line # returns if final Contour forms a loop ##
#Populate
#Example
#Description
close() has no effect if Path is empty; isLastContourClosed() returns
false until Path has geometry followed by close().
##
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkDebugf("%s last contour is %s" "closed\n", prefix,
path.isLastContourClosed() ? "" : "not ");
};
SkPath path;
debugster("initial", path);
path.close();
debugster("after close", path);
path.lineTo(0, 0);
debugster("after lineTo", path);
path.close();
debugster("after close", path);
}
#StdOut
initial last contour is not closed
after close last contour is not closed
after lineTo last contour is not closed
after close last contour is closed
##
##
#SeeAlso close()
##
# ------------------------------------------------------------------------------
#Method bool isFinite() const
#In Property
#Line # returns if all Point values are finite ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkDebugf("%s path is %s" "finite\n", prefix, path.isFinite() ? "" : "not ");
};
SkPath path;
debugster("initial", path);
path.lineTo(SK_ScalarMax, SK_ScalarMax);
debugster("after line", path);
SkMatrix matrix;
matrix.setScale(2, 2);
path.transform(matrix);
debugster("after scale", path);
}
#StdOut
initial path is finite
after line path is finite
after scale path is not finite
##
##
#SeeAlso SkScalar
##
# ------------------------------------------------------------------------------
#Method bool isVolatile() const
#In Property
#In Volatile
#Line # returns if Device should not cache ##
#Populate
#Example
SkPath path;
SkDebugf("volatile by default is %s\n", path.isVolatile() ? "true" : "false");
#StdOut
volatile by default is false
##
##
#SeeAlso setIsVolatile
##
# ------------------------------------------------------------------------------
#Subtopic Volatile
#Line # caching attribute ##
##
#Method void setIsVolatile(bool isVolatile)
#In Volatile
#Line # sets if Device should not cache ##
#Populate
#Example
#Height 50
#Width 50
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
SkPath path;
path.setIsVolatile(true);
path.lineTo(40, 40);
canvas->drawPath(path, paint);
path.rewind();
path.moveTo(0, 40);
path.lineTo(40, 0);
canvas->drawPath(path, paint);
##
#ToDo tie example to bench to show how volatile affects speed or dm to show resource usage ##
#SeeAlso isVolatile
##
# ------------------------------------------------------------------------------
#Method static bool IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact)
#In Property
#Line # returns if Line is very small ##
#Populate
#Example
#Description
As single precision floats, 100 and 100.000001 have the same bit representation,
and are exactly equal. 100 and 100.0001 have different bit representations, and
are not exactly equal, but are nearly equal.
##
void draw(SkCanvas* canvas) {
SkPoint points[] = { {100, 100}, {100.000001f, 100.000001f}, {100.0001f, 100.0001f} };
for (size_t i = 0; i < SK_ARRAY_COUNT(points) - 1; ++i) {
for (bool exact : { false, true } ) {
SkDebugf("line from (%1.8g,%1.8g) to (%1.8g,%1.8g) is %s" "degenerate, %s\n",
points[i].fX, points[i].fY, points[i + 1].fX, points[i + 1].fY,
SkPath::IsLineDegenerate(points[i], points[i + 1], exact)
? "" : "not ", exact ? "exactly" : "nearly");
}
}
}
#StdOut
line from (100,100) to (100,100) is degenerate, nearly
line from (100,100) to (100,100) is degenerate, exactly
line from (100,100) to (100.0001,100.0001) is degenerate, nearly
line from (100,100) to (100.0001,100.0001) is not degenerate, exactly
#StdOut ##
##
#SeeAlso IsQuadDegenerate IsCubicDegenerate
##
# ------------------------------------------------------------------------------
#Method static bool IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
const SkPoint& p3, bool exact)
#In Property
#Line # returns if Quad is very small ##
#Populate
#Example
#Description
As single precision floats: 100, 100.00001, and 100.00002 have different bit representations
but nearly the same value. Translating all three by 1000 gives them the same bit representation;
the fractional portion of the number can not be represented by the float and is lost.
##
void draw(SkCanvas* canvas) {
auto debugster = [](const SkPath& path, bool exact) -> void {
SkDebugf("quad (%1.8g,%1.8g), (%1.8g,%1.8g), (%1.8g,%1.8g) is %s" "degenerate, %s\n",
path.getPoint(0).fX, path.getPoint(0).fY, path.getPoint(1).fX,
path.getPoint(1).fY, path.getPoint(2).fX, path.getPoint(2).fY,
SkPath::IsQuadDegenerate(path.getPoint(0), path.getPoint(1), path.getPoint(2), exact) ?
"" : "not ", exact ? "exactly" : "nearly");
};
SkPath path, offset;
path.moveTo({100, 100});
path.quadTo({100.00001f, 100.00001f}, {100.00002f, 100.00002f});
offset.addPath(path, 1000, 1000);
for (bool exact : { false, true } ) {
debugster(path, exact);
debugster(offset, exact);
}
}
#StdOut
quad (100,100), (100.00001,100.00001), (100.00002,100.00002) is degenerate, nearly
quad (1100,1100), (1100,1100), (1100,1100) is degenerate, nearly
quad (100,100), (100.00001,100.00001), (100.00002,100.00002) is not degenerate, exactly
quad (1100,1100), (1100,1100), (1100,1100) is degenerate, exactly
#StdOut ##
##
#SeeAlso IsLineDegenerate IsCubicDegenerate
##
# ------------------------------------------------------------------------------
#Method static bool IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
const SkPoint& p3, const SkPoint& p4, bool exact)
#In Property
#Line # returns if Cubic is very small ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPoint points[] = {{1, 0}, {0, 0}, {0, 0}, {0, 0}};
SkScalar step = 1;
SkScalar prior, length = 0, degenerate = 0;
do {
prior = points[0].fX;
step /= 2;
if (SkPath::IsCubicDegenerate(points[0], points[1], points[2], points[3], false)) {
degenerate = prior;
points[0].fX += step;
} else {
length = prior;
points[0].fX -= step;
}
} while (prior != points[0].fX);
SkDebugf("%1.8g is degenerate\n", degenerate);
SkDebugf("%1.8g is length\n", length);
}
#StdOut
0.00024414062 is degenerate
0.00024414065 is length
#StdOut ##
##
##
# ------------------------------------------------------------------------------
#Method bool isLine(SkPoint line[2]) const
#In Property
#Line # returns if describes Line ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkPoint line[2];
if (path.isLine(line)) {
SkDebugf("%s is line (%1.8g,%1.8g) (%1.8g,%1.8g)\n", prefix,
line[0].fX, line[0].fY, line[1].fX, line[1].fY);
} else {
SkDebugf("%s is not line\n", prefix);
}
};
SkPath path;
debugster("empty", path);
path.lineTo(0, 0);
debugster("zero line", path);
path.rewind();
path.moveTo(10, 10);
path.lineTo(20, 20);
debugster("line", path);
path.moveTo(20, 20);
debugster("second move", path);
}
#StdOut
empty is not line
zero line is line (0,0) (0,0)
line is line (10,10) (20,20)
second move is not line
##
##
##
# ------------------------------------------------------------------------------
#Subtopic Point_Array
#Line # end points and control points for lines and curves ##
#Substitute SkPoint array
Point_Array contains Points satisfying the allocated Points for
each Verb in Verb_Array. For instance, Path containing one Contour with Line
and Quad is described by Verb_Array: kMove_Verb, kLine_Verb, kQuad_Verb; and
one Point for move, one Point for Line, two Points for Quad; totaling four Points.
Point_Array may be read directly from Path with getPoints, or inspected with
getPoint, with Iter, or with RawIter.
#Method int getPoints(SkPoint points[], int max) const
#In Point_Array
#Line # returns Point_Array ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path, SkPoint* points, int max) -> void {
int count = path.getPoints(points, max);
SkDebugf("%s point count: %d ", prefix, count);
for (int i = 0; i < SkTMin(count, max) && points; ++i) {
SkDebugf("(%1.8g,%1.8g) ", points[i].fX, points[i].fY);
}
SkDebugf("\n");
};
SkPath path;
path.lineTo(20, 20);
path.lineTo(-10, -10);
SkPoint points[3];
debugster("no points", path, nullptr, 0);
debugster("zero max", path, points, 0);
debugster("too small", path, points, 2);
debugster("just right", path, points, path.countPoints());
}
#StdOut
no points point count: 3
zero max point count: 3
too small point count: 3 (0,0) (20,20)
just right point count: 3 (0,0) (20,20) (-10,-10)
##
##
#SeeAlso countPoints getPoint
##
#Method int countPoints() const
#In Point_Array
#Line # returns Point_Array length ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkDebugf("%s point count: %d\n", prefix, path.countPoints());
};
SkPath path;
debugster("empty", path);
path.lineTo(0, 0);
debugster("zero line", path);
path.rewind();
path.moveTo(10, 10);
path.lineTo(20, 20);
debugster("line", path);
path.moveTo(20, 20);
debugster("second move", path);
}
#StdOut
empty point count: 0
zero line point count: 2
line point count: 2
second move point count: 3
##
##
#SeeAlso getPoints
##
#Method SkPoint getPoint(int index) const
#In Point_Array
#Line # returns entry from Point_Array ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path;
path.lineTo(20, 20);
path.offset(-10, -10);
for (int i= 0; i < path.countPoints(); ++i) {
SkDebugf("point %d: (%1.8g,%1.8g)\n", i, path.getPoint(i).fX, path.getPoint(i).fY);
}
}
#StdOut
point 0: (-10,-10)
point 1: (10,10)
##
##
#SeeAlso countPoints getPoints
##
#Subtopic Point_Array ##
# ------------------------------------------------------------------------------
#Subtopic Verb_Array
#Line # line and curve type for points ##
Verb_Array always starts with kMove_Verb.
If kClose_Verb is not the last entry, it is always followed by kMove_Verb;
the quantity of kMove_Verb equals the Contour count.
Verb_Array does not include or count kDone_Verb; it is a convenience
returned when iterating through Verb_Array.
Verb_Array may be read directly from Path with getVerbs, or inspected with Iter,
or with RawIter.
#Method int countVerbs() const
#In Verb_Array
#Line # returns Verb_Array length ##
#Populate
#Example
SkPath path;
SkDebugf("empty verb count: %d\n", path.countVerbs());
path.addRoundRect({10, 20, 30, 40}, 5, 5);
SkDebugf("round rect verb count: %d\n", path.countVerbs());
#StdOut
empty verb count: 0
round rect verb count: 10
##
##
#SeeAlso getVerbs Iter RawIter
##
#Method int getVerbs(uint8_t verbs[], int max) const
#In Verb_Array
#Line # returns Verb_Array ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path, uint8_t* verbs, int max) -> void {
int count = path.getVerbs(verbs, max);
SkDebugf("%s verb count: %d ", prefix, count);
const char* verbStr[] = { "move", "line", "quad", "conic", "cubic", "close" };
for (int i = 0; i < SkTMin(count, max) && verbs; ++i) {
SkDebugf("%s ", verbStr[verbs[i]]);
}
SkDebugf("\n");
};
SkPath path;
path.lineTo(20, 20);
path.lineTo(-10, -10);
uint8_t verbs[3];
debugster("no verbs", path, nullptr, 0);
debugster("zero max", path, verbs, 0);
debugster("too small", path, verbs, 2);
debugster("just right", path, verbs, path.countVerbs());
}
#StdOut
no verbs verb count: 3
zero max verb count: 3
too small verb count: 3 move line
just right verb count: 3 move line line
##
##
#SeeAlso countVerbs getPoints Iter RawIter
##
#Subtopic Verb_Array ##
# ------------------------------------------------------------------------------
#Method void swap(SkPath& other)
#In Operators
#Line # exchanges Path pair ##
#Populate
#Example
SkPath path1, path2;
path1.addRect({10, 20, 30, 40});
path1.swap(path2);
const SkRect& b1 = path1.getBounds();
SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);
const SkRect& b2 = path2.getBounds();
SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);
#StdOut
path1 bounds = 0, 0, 0, 0
path2 bounds = 10, 20, 30, 40
#StdOut ##
##
#SeeAlso operator=(const SkPath& path)
##
# ------------------------------------------------------------------------------
#Method const SkRect& getBounds() const
#In Property
#Line # returns maximum and minimum of Point_Array ##
#Populate
#Example
#Description
Bounds of upright Circle can be predicted from center and radius.
Bounds of rotated Circle includes control Points outside of filled area.
##
auto debugster = [](const char* prefix, const SkPath& path) -> void {
const SkRect& bounds = path.getBounds();
SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix,
bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
};
SkPath path;
debugster("empty", path);
path.addCircle(50, 45, 25);
debugster("circle", path);
SkMatrix matrix;
matrix.setRotate(45, 50, 45);
path.transform(matrix);
debugster("rotated circle", path);
#StdOut
empty bounds = 0, 0, 0, 0
circle bounds = 25, 20, 75, 70
rotated circle bounds = 14.6447, 9.64466, 85.3553, 80.3553
##
##
#SeeAlso computeTightBounds updateBoundsCache
##
# ------------------------------------------------------------------------------
#Subtopic Utility
#Line # rarely called management functions ##
##
#Method void updateBoundsCache() const
#In Utility
#Line # refreshes result of getBounds ##
#Populate
#Example
double times[2] = { 0, 0 };
for (int i = 0; i < 10000; ++i) {
SkPath path;
for (int j = 1; j < 100; ++ j) {
path.addCircle(50 + j, 45 + j, 25 + j);
}
if (1 & i) {
path.updateBoundsCache();
}
double start = SkTime::GetNSecs();
(void) path.getBounds();
times[1 & i] += SkTime::GetNSecs() - start;
}
SkDebugf("uncached avg: %g ms\n", times[0] * 1e-6);
SkDebugf("cached avg: %g ms\n", times[1] * 1e-6);
#StdOut
#Volatile
uncached avg: 0.18048 ms
cached avg: 0.182784 ms
##
##
#SeeAlso getBounds
#ToDo the results don't make sense, need to profile to figure this out ##
##
# ------------------------------------------------------------------------------
#Method SkRect computeTightBounds() const
#In Property
#Line # returns extent of geometry ##
#Populate
#Example
auto debugster = [](const char* prefix, const SkPath& path) -> void {
const SkRect& bounds = path.computeTightBounds();
SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix,
bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
};
SkPath path;
debugster("empty", path);
path.addCircle(50, 45, 25);
debugster("circle", path);
SkMatrix matrix;
matrix.setRotate(45, 50, 45);
path.transform(matrix);
debugster("rotated circle", path);
#StdOut
empty bounds = 0, 0, 0, 0
circle bounds = 25, 20, 75, 70
rotated circle bounds = 25, 20, 75, 70
##
##
#SeeAlso getBounds
##
# ------------------------------------------------------------------------------
#Method bool conservativelyContainsRect(const SkRect& rect) const
#In Property
#Line # returns true if Rect may be inside ##
#Populate
#Example
#Height 140
#Description
Rect is drawn in blue if it is contained by red Path.
##
void draw(SkCanvas* canvas) {
SkPath path;
path.addRoundRect({10, 20, 54, 120}, 10, 20);
SkRect tests[] = {
{ 10, 40, 54, 80 },
{ 25, 20, 39, 120 },
{ 15, 25, 49, 115 },
{ 13, 27, 51, 113 },
};
for (unsigned i = 0; i < SK_ARRAY_COUNT(tests); ++i) {
SkPaint paint;
paint.setColor(SK_ColorRED);
canvas->drawPath(path, paint);
bool rectInPath = path.conservativelyContainsRect(tests[i]);
paint.setColor(rectInPath ? SK_ColorBLUE : SK_ColorBLACK);
canvas->drawRect(tests[i], paint);
canvas->translate(64, 0);
}
}
##
#SeeAlso contains Op Rect Convexity
##
# ------------------------------------------------------------------------------
#Method void incReserve(int extraPtCount)
#In Utility
#Line # reserves space for additional data ##
#Populate
#Example
#Height 192
void draw(SkCanvas* canvas) {
auto addPoly = [](int sides, SkScalar size, SkPath* path) -> void {
path->moveTo(size, 0);
for (int i = 1; i < sides; i++) {
SkScalar c, s = SkScalarSinCos(SK_ScalarPI * 2 * i / sides, &c);
path->lineTo(c * size, s * size);
}
path->close();
};
SkPath path;
path.incReserve(3 + 4 + 5 + 6 + 7 + 8 + 9);
for (int sides = 3; sides < 10; ++sides) {
addPoly(sides, sides, &path);
}
SkMatrix matrix;
matrix.setScale(10, 10, -10, -10);
path.transform(matrix);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(path, paint);
}
##
#SeeAlso Point_Array
##
#Method void shrinkToFit()
#In Utility
#Line # removes unused reserved space ##
#Populate
#NoExample
#Height 192
void draw(SkCanvas* canvas) {
SkPath skinnyPath;
skinnyPath.lineTo(100, 100);
skinnyPath.shrinkToFit();
SkDebugf("no wasted Path capacity in my house!\n");
}
##
#SeeAlso incReserve
##
# ------------------------------------------------------------------------------
#Subtopic Build
#Line # adds points and verbs to path ##
##
#Method SkPath& moveTo(SkScalar x, SkScalar y)
#In Build
#Line # starts Contour ##
#Populate
#Example
#Width 140
#Height 100
void draw(SkCanvas* canvas) {
SkRect rect = { 20, 20, 120, 80 };
SkPath path;
path.addRect(rect);
path.moveTo(rect.fLeft, rect.fTop);
path.lineTo(rect.fRight, rect.fBottom);
path.moveTo(rect.fLeft, rect.fBottom);
path.lineTo(rect.fRight, rect.fTop);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour lineTo rMoveTo quadTo conicTo cubicTo close()
##
#Method SkPath& moveTo(const SkPoint& p)
#Populate
#Example
#Width 128
#Height 128
void draw(SkCanvas* canvas) {
SkPoint data[][3] = {{{30,40},{60,60},{90,30}}, {{30,120},{60,100},{90,120}},
{{60,100},{60,40},{70,30}}, {{60,40},{50,20},{70,30}}};
SkPath path;
for (unsigned i = 0; i < SK_ARRAY_COUNT(data); ++i) {
path.moveTo(data[i][0]);
path.lineTo(data[i][1]);
path.lineTo(data[i][2]);
}
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour lineTo rMoveTo quadTo conicTo cubicTo close()
##
#Method SkPath& rMoveTo(SkScalar dx, SkScalar dy)
#In Build
#Line # starts Contour relative to Last_Point ##
#Populate
#Example
#Height 100
SkPath path;
path.addRect({20, 20, 80, 80}, SkPath::kCW_Direction, 2);
path.rMoveTo(25, 2);
SkVector arrow[] = {{0, -4}, {-20, 0}, {0, -3}, {-5, 5}, {5, 5}, {0, -3}, {20, 0}};
for (unsigned i = 0; i < SK_ARRAY_COUNT(arrow); ++i) {
path.rLineTo(arrow[i].fX, arrow[i].fY);
}
SkPaint paint;
canvas->drawPath(path, paint);
SkPoint lastPt;
path.getLastPt(&lastPt);
canvas->drawString("start", lastPt.fX, lastPt.fY, paint);
##
#SeeAlso Contour lineTo moveTo quadTo conicTo cubicTo close()
##
# ------------------------------------------------------------------------------
#Method SkPath& lineTo(SkScalar x, SkScalar y)
#In Build
#Line # appends Line ##
#Populate
#Example
#Height 100
###$
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(72);
canvas->drawString("#", 120, 80, paint);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(5);
SkPath path;
SkPoint hash[] = {{58, 28}, {43, 80}, {37, 45}, {85, 45}};
SkVector offsets[] = {{0, 0}, {17, 0}, {0, 0}, {-5, 17}};
unsigned o = 0;
for (unsigned i = 0; i < SK_ARRAY_COUNT(hash); i += 2) {
for (unsigned j = 0; j < 2; o++, j++) {
path.moveTo(hash[i].fX + offsets[o].fX, hash[i].fY + offsets[o].fY);
path.lineTo(hash[i + 1].fX + offsets[o].fX, hash[i + 1].fY + offsets[o].fY);
}
}
canvas->drawPath(path, paint);
}
$$$#
##
#SeeAlso Contour moveTo rLineTo addRect
##
# ------------------------------------------------------------------------------
#Method SkPath& lineTo(const SkPoint& p)
#Populate
#Example
#Height 100
SkPath path;
SkVector oxo[] = {{25, 25}, {35, 35}, {25, 35}, {35, 25},
{40, 20}, {40, 80}, {60, 20}, {60, 80},
{20, 40}, {80, 40}, {20, 60}, {80, 60}};
for (unsigned i = 0; i < SK_ARRAY_COUNT(oxo); i += 2) {
path.moveTo(oxo[i]);
path.lineTo(oxo[i + 1]);
}
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(path, paint);
##
#SeeAlso Contour moveTo rLineTo addRect
##
# ------------------------------------------------------------------------------
#Method SkPath& rLineTo(SkScalar dx, SkScalar dy)
#In Build
#Line # appends Line relative to Last_Point ##
#Populate
#Example
#Height 128
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPath path;
path.moveTo(10, 98);
SkScalar x = 0, y = 0;
for (int i = 10; i < 100; i += 5) {
x += i * ((i & 2) - 1);
y += i * (((i + 1) & 2) - 1);
path.rLineTo(x, y);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour moveTo lineTo addRect
##
# ------------------------------------------------------------------------------
#Subtopic Quad
#Alias Quad ##
#Alias Quads ##
#Alias Quadratic_Bezier ##
#Alias Quadratic_Beziers ##
#Line # curve described by second-order polynomial ##
Quad describes a Quadratic_Bezier, a second-order curve identical to a section
of a parabola. Quad begins at a start Point, curves towards a control Point,
and then curves to an end Point.
#Example
#Height 110
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPoint quadPts[] = {{20, 90}, {120, 10}, {220, 90}};
canvas->drawLine(quadPts[0], quadPts[1], paint);
canvas->drawLine(quadPts[1], quadPts[2], paint);
SkPath path;
path.moveTo(quadPts[0]);
path.quadTo(quadPts[1], quadPts[2]);
paint.setStrokeWidth(3);
canvas->drawPath(path, paint);
}
##
Quad is a special case of Conic where Conic_Weight is set to one.
Quad is always contained by the triangle connecting its three Points. Quad
begins tangent to the line between start Point and control Point, and ends
tangent to the line between control Point and end Point.
#Example
#Height 160
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPoint quadPts[] = {{20, 150}, {120, 10}, {220, 150}};
SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
paint.setColor(0x7fffffff & colors[i]);
paint.setStrokeWidth(1);
canvas->drawLine(quadPts[0], quadPts[1], paint);
canvas->drawLine(quadPts[1], quadPts[2], paint);
SkPath path;
path.moveTo(quadPts[0]);
path.quadTo(quadPts[1], quadPts[2]);
paint.setStrokeWidth(3);
paint.setColor(colors[i]);
canvas->drawPath(path, paint);
quadPts[1].fY += 30;
}
}
##
#Method SkPath& quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)
#In Quad
#Line # appends Quad ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPath path;
path.moveTo(0, -10);
for (int i = 0; i < 128; i += 16) {
path.quadTo( 10 + i, -10 - i, 10 + i, 0);
path.quadTo( 14 + i, 14 + i, 0, 14 + i);
path.quadTo(-18 - i, 18 + i, -18 - i, 0);
path.quadTo(-22 - i, -22 - i, 0, -22 - i);
}
path.offset(128, 128);
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour moveTo conicTo rQuadTo
##
#Method SkPath& quadTo(const SkPoint& p1, const SkPoint& p2)
#In Build
#In Quad
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setAntiAlias(true);
SkPath path;
SkPoint pts[] = {{128, 10}, {10, 214}, {236, 214}};
path.moveTo(pts[1]);
for (int i = 0; i < 3; ++i) {
path.quadTo(pts[i % 3], pts[(i + 2) % 3]);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour moveTo conicTo rQuadTo
##
#Method SkPath& rQuadTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2)
#In Build
#In Quad
#Line # appends Quad relative to Last_Point ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
SkPath path;
path.moveTo(128, 20);
path.rQuadTo(-6, 10, -7, 10);
for (int i = 1; i < 32; i += 4) {
path.rQuadTo(10 + i, 10 + i, 10 + i * 4, 10);
path.rQuadTo(-10 - i, 10 + i, -10 - (i + 2) * 4, 10);
}
path.quadTo(92, 220, 128, 215);
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour moveTo conicTo quadTo
##
#Subtopic Quad ##
# ------------------------------------------------------------------------------
#Subtopic Conic
#Alias Conic ##
#Alias Conics ##
#Line # conic section defined by three points and a weight ##
Conic describes a conical section: a piece of an ellipse, or a piece of a
parabola, or a piece of a hyperbola. Conic begins at a start Point,
curves towards a control Point, and then curves to an end Point. The influence
of the control Point is determined by Conic_Weight.
Each Conic in Path adds two Points and one Conic_Weight. Conic_Weights in Path
may be inspected with Iter, or with RawIter.
#Subtopic Weight
#Alias Conic_Weights ##
#Alias Weights ##
#Line # strength of Conic control Point ##
Weight determines both the strength of the control Point and the type of Conic.
Weight varies from zero to infinity. At zero, Weight causes the control Point to
have no effect; Conic is identical to a line segment from start Point to end
point. If Weight is less than one, Conic follows an elliptical arc.
If Weight is exactly one, then Conic is identical to Quad; Conic follows a
parabolic arc. If Weight is greater than one, Conic follows a hyperbolic
arc. If Weight is infinity, Conic is identical to two line segments, connecting
start Point to control Point, and control Point to end Point.
#Example
#Description
When Conic_Weight is one, Quad is added to path; the two are identical.
##
void draw(SkCanvas* canvas) {
const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
const int pointCount[] = { 1 , 2 , 3 , 3 , 4 , 1 , 0 };
SkPath path;
path.conicTo(20, 30, 50, 60, 1);
SkPath::Iter iter(path, false);
SkPath::Verb verb;
do {
SkPoint points[4];
verb = iter.next(points);
SkDebugf("%s ", verbNames[(int) verb]);
for (int i = 0; i < pointCount[(int) verb]; ++i) {
SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
}
if (SkPath::kConic_Verb == verb) {
SkDebugf("weight = %g", iter.conicWeight());
}
SkDebugf("\n");
} while (SkPath::kDone_Verb != verb);
}
#StdOut
move {0, 0},
quad {0, 0}, {20, 30}, {50, 60},
done
##
##
If weight is less than one, Conic is an elliptical segment.
#Example
#Description
A 90 degree circular arc has the weight #Formula # 1 / sqrt(2) ##.
##
void draw(SkCanvas* canvas) {
const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
const int pointCount[] = { 1 , 2 , 3 , 3 , 4 , 1 , 0 };
SkPath path;
path.arcTo(20, 0, 20, 20, 20);
SkPath::Iter iter(path, false);
SkPath::Verb verb;
do {
SkPoint points[4];
verb = iter.next(points);
SkDebugf("%s ", verbNames[(int) verb]);
for (int i = 0; i < pointCount[(int) verb]; ++i) {
SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
}
if (SkPath::kConic_Verb == verb) {
SkDebugf("weight = %g", iter.conicWeight());
}
SkDebugf("\n");
} while (SkPath::kDone_Verb != verb);
}
#StdOut
move {0, 0},
conic {0, 0}, {20, 0}, {20, 20}, weight = 0.707107
done
##
##
If weight is greater than one, Conic is a hyperbolic segment. As weight gets large,
a hyperbolic segment can be approximated by straight lines connecting the
control Point with the end Points.
#Example
void draw(SkCanvas* canvas) {
const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
const int pointCount[] = { 1 , 2 , 3 , 3 , 4 , 1 , 0 };
SkPath path;
path.conicTo(20, 0, 20, 20, SK_ScalarInfinity);
SkPath::Iter iter(path, false);
SkPath::Verb verb;
do {
SkPoint points[4];
verb = iter.next(points);
SkDebugf("%s ", verbNames[(int) verb]);
for (int i = 0; i < pointCount[(int) verb]; ++i) {
SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
}
if (SkPath::kConic_Verb == verb) {
SkDebugf("weight = %g", iter.conicWeight());
}
SkDebugf("\n");
} while (SkPath::kDone_Verb != verb);
}
#StdOut
move {0, 0},
line {0, 0}, {20, 0},
line {20, 0}, {20, 20},
done
##
##
#Subtopic Weight ##
#Method SkPath& conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
SkScalar w)
#In Conic
#In Build
#Line # appends Conic ##
#Populate
#Example
#Height 160
#Description
As weight increases, curve is pulled towards control point.
The bottom two curves are elliptical; the next is parabolic; the
top curve is hyperbolic.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPoint conicPts[] = {{20, 150}, {120, 10}, {220, 150}};
canvas->drawLine(conicPts[0], conicPts[1], paint);
canvas->drawLine(conicPts[1], conicPts[2], paint);
SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
paint.setStrokeWidth(3);
SkScalar weight = 0.5f;
for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
SkPath path;
path.moveTo(conicPts[0]);
path.conicTo(conicPts[1], conicPts[2], weight);
paint.setColor(colors[i]);
canvas->drawPath(path, paint);
weight += 0.25f;
}
}
##
#SeeAlso rConicTo arcTo addArc quadTo
##
#Method SkPath& conicTo(const SkPoint& p1, const SkPoint& p2, SkScalar w)
#In Build
#In Conic
#Populate
#Example
#Height 128
#Description
Conics and arcs use identical representations. As the arc sweep increases
the Conic_Weight also increases, but remains smaller than one.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkRect oval = {0, 20, 120, 140};
SkPath path;
for (int i = 0; i < 4; ++i) {
path.moveTo(oval.centerX(), oval.fTop);
path.arcTo(oval, -90, 90 - 20 * i, false);
oval.inset(15, 15);
}
path.offset(100, 0);
SkScalar conicWeights[] = { 0.707107f, 0.819152f, 0.906308f, 0.965926f };
SkPoint conicPts[][3] = { { {40, 20}, {100, 20}, {100, 80} },
{ {40, 35}, {71.509f, 35}, {82.286f, 64.6091f} },
{ {40, 50}, {53.9892f, 50}, {62.981f, 60.7164f} },
{ {40, 65}, {44.0192f, 65}, {47.5f, 67.0096f} } };
for (int i = 0; i < 4; ++i) {
path.moveTo(conicPts[i][0]);
path.conicTo(conicPts[i][1], conicPts[i][2], conicWeights[i]);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso rConicTo arcTo addArc quadTo
##
#Method SkPath& rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
SkScalar w)
#In Build
#In Conic
#Line # appends Conic relative to Last_Point ##
#Populate
#Example
#Height 140
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPath path;
path.moveTo(20, 80);
path.rConicTo( 60, 0, 60, 60, 0.707107f);
path.rConicTo( 0, -60, 60, -60, 0.707107f);
path.rConicTo(-60, 0, -60, -60, 0.707107f);
path.rConicTo( 0, 60, -60, 60, 0.707107f);
canvas->drawPath(path, paint);
}
##
#SeeAlso conicTo arcTo addArc quadTo
##
#Subtopic Conic ##
# ------------------------------------------------------------------------------
#Subtopic Cubic
#Alias Cubic ##
#Alias Cubics ##
#Alias Cubic_Bezier ##
#Alias Cubic_Beziers ##
#Line # curve described by third-order polynomial ##
Cubic describes a Bezier_Curve segment described by a third-order polynomial.
Cubic begins at a start Point, curving towards the first control Point;
and curves from the end Point towards the second control Point.
#Example
#Height 160
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPoint cubicPts[] = {{20, 150}, {90, 10}, {160, 150}, {230, 10}};
SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
paint.setColor(0x7fffffff & colors[i]);
paint.setStrokeWidth(1);
for (unsigned j = 0; j < 3; ++j) {
canvas->drawLine(cubicPts[j], cubicPts[j + 1], paint);
}
SkPath path;
path.moveTo(cubicPts[0]);
path.cubicTo(cubicPts[1], cubicPts[2], cubicPts[3]);
paint.setStrokeWidth(3);
paint.setColor(colors[i]);
canvas->drawPath(path, paint);
cubicPts[1].fY += 30;
cubicPts[2].fX += 30;
}
}
##
#Method SkPath& cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
SkScalar x3, SkScalar y3)
#In Build
#In Cubic
#Line # appends Cubic ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPath path;
path.moveTo(0, -10);
for (int i = 0; i < 128; i += 16) {
SkScalar c = i * 0.5f;
path.cubicTo( 10 + c, -10 - i, 10 + i, -10 - c, 10 + i, 0);
path.cubicTo( 14 + i, 14 + c, 14 + c, 14 + i, 0, 14 + i);
path.cubicTo(-18 - c, 18 + i, -18 - i, 18 + c, -18 - i, 0);
path.cubicTo(-22 - i, -22 - c, -22 - c, -22 - i, 0, -22 - i);
}
path.offset(128, 128);
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour moveTo rCubicTo quadTo
##
# ------------------------------------------------------------------------------
#Method SkPath& cubicTo(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3)
#In Build
#In Cubic
#Populate
#Example
#Height 84
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPoint pts[] = { {20, 20}, {300, 80}, {-140, 90}, {220, 10} };
SkPath path;
path.moveTo(pts[0]);
path.cubicTo(pts[1], pts[2], pts[3]);
canvas->drawPath(path, paint);
##
#SeeAlso Contour moveTo rCubicTo quadTo
##
# ------------------------------------------------------------------------------
#Method SkPath& rCubicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
SkScalar dx3, SkScalar dy3)
#In Build
#In Cubic
#Line # appends Cubic relative to Last_Point ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
SkPath path;
path.moveTo(24, 108);
for (int i = 0; i < 16; i++) {
SkScalar sx, sy;
sx = SkScalarSinCos(i * SK_ScalarPI / 8, &sy);
path.rCubicTo(40 * sx, 4 * sy, 4 * sx, 40 * sy, 40 * sx, 40 * sy);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso Contour moveTo cubicTo quadTo
##
#Subtopic Cubic ##
# ------------------------------------------------------------------------------
#Subtopic Arc
#Line # part of Oval or Circle ##
Arc can be constructed in a number of ways. Arc may be described by part of Oval and angles,
by start point and end point, and by radius and tangent lines. Each construction has advantages,
and some constructions correspond to Arc drawing in graphics standards.
All Arc draws are implemented by one or more Conic draws. When Conic_Weight is less than one,
Conic describes an Arc of some Oval or Circle.
arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo)
describes Arc as a piece of Oval, beginning at start angle, sweeping clockwise or counterclockwise,
which may continue Contour or start a new one. This construction is similar to PostScript and
HTML_Canvas arcs. Variation addArc always starts new Contour. SkCanvas::drawArc draws without
requiring Path.
arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius)
describes Arc as tangent to the line segment from last Point added to Path to (x1, y1); and tangent
to the line segment from (x1, y1) to (x2, y2). This construction is similar to PostScript and
HTML_Canvas arcs.
arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep,
SkScalar x, SkScalar y)
describes Arc as part of Oval with radii (rx, ry), beginning at
last Point added to Path and ending at (x, y). More than one Arc satisfies this criteria,
so additional values choose a single solution. This construction is similar to SVG arcs.
conicTo describes Arc of less than 180 degrees as a pair of tangent lines and Conic_Weight.
conicTo can represent any Arc with a sweep less than 180 degrees at any rotation. All arcTo
constructions are converted to Conic data when added to Path.
#ToDo example is spaced correctly on fiddle but spacing is too wide on pc
##
#Illustration
#List
# <sup>1</sup> arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) ##
# <sup>2</sup> parameter adds move to first point ##
# <sup>3</sup> start angle must be multiple of 90 degrees ##
# <sup>4</sup> arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) ##
# <sup>5</sup> arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
Direction sweep, SkScalar x, SkScalar y) ##
#List ##
#Example
#Height 128
void draw(SkCanvas* canvas) {
SkRect oval = {8, 8, 56, 56};
SkPaint ovalPaint;
ovalPaint.setAntiAlias(true);
SkPaint textPaint(ovalPaint);
ovalPaint.setStyle(SkPaint::kStroke_Style);
SkPaint arcPaint(ovalPaint);
arcPaint.setStrokeWidth(5);
arcPaint.setColor(SK_ColorBLUE);
canvas->translate(-64, 0);
for (char arcStyle = '1'; arcStyle <= '6'; ++arcStyle) {
'4' == arcStyle ? canvas->translate(-96, 55) : canvas->translate(64, 0);
canvas->drawText(&arcStyle, 1, 30, 36, textPaint);
canvas->drawOval(oval, ovalPaint);
SkPath path;
path.moveTo({56, 32});
switch (arcStyle) {
case '1':
path.arcTo(oval, 0, 90, false);
break;
case '2':
canvas->drawArc(oval, 0, 90, false, arcPaint);
continue;
case '3':
path.addArc(oval, 0, 90);
break;
case '4':
path.arcTo({56, 56}, {32, 56}, 24);
break;
case '5':
path.arcTo({24, 24}, 0, SkPath::kSmall_ArcSize, SkPath::kCW_Direction, {32, 56});
break;
case '6':
path.conicTo({56, 56}, {32, 56}, SK_ScalarRoot2Over2);
break;
}
canvas->drawPath(path, arcPaint);
}
}
#Example ##
In the example above:
#List
# 1 describes an arc from an oval, a starting angle, and a sweep angle. ##
# 2 is similar to 1, but does not require building a path to draw. ##
# 3 is similar to 1, but always begins new Contour. ##
# 4 describes an arc from a pair of tangent lines and a radius. ##
# 5 describes an arc from Oval center, arc start Point and arc end Point. ##
# 6 describes an arc from a pair of tangent lines and a Conic_Weight. ##
#List ##
#Method SkPath& arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo)
#In Build
#In Arc
#Line # appends Arc ##
#Populate
#Example
#Height 200
#Description
arcTo continues a previous contour when forceMoveTo is false and when Path
is not empty.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
SkPath path;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(4);
path.moveTo(0, 0);
path.arcTo({20, 20, 120, 120}, -90, 90, false);
canvas->drawPath(path, paint);
path.rewind();
path.arcTo({120, 20, 220, 120}, -90, 90, false);
canvas->drawPath(path, paint);
path.rewind();
path.moveTo(0, 0);
path.arcTo({20, 120, 120, 220}, -90, 90, true);
canvas->drawPath(path, paint);
}
##
#SeeAlso addArc SkCanvas::drawArc conicTo
##
# ------------------------------------------------------------------------------
#Method SkPath& arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius)
#In Build
#In Arc
#Populate
#Example
#Height 226
void draw(SkCanvas* canvas) {
SkPaint tangentPaint;
tangentPaint.setAntiAlias(true);
SkPaint textPaint(tangentPaint);
tangentPaint.setStyle(SkPaint::kStroke_Style);
tangentPaint.setColor(SK_ColorGRAY);
SkPaint arcPaint(tangentPaint);
arcPaint.setStrokeWidth(5);
arcPaint.setColor(SK_ColorBLUE);
SkPath path;
SkPoint pts[] = { {56, 20}, {200, 20}, {90, 190} };
SkScalar radius = 50;
path.moveTo(pts[0]);
path.arcTo(pts[1], pts[2], radius);
canvas->drawLine(pts[0], pts[1], tangentPaint);
canvas->drawLine(pts[1], pts[2], tangentPaint);
SkPoint lastPt;
(void) path.getLastPt(&lastPt);
SkVector radial = pts[2] - pts[1];
radial.setLength(radius);
SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };
canvas->drawCircle(center, radius, tangentPaint);
canvas->drawLine(lastPt, center, tangentPaint);
radial = pts[1] - pts[0];
radial.setLength(radius);
SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };
canvas->drawLine(center, arcStart, tangentPaint);
canvas->drawPath(path, arcPaint);
canvas->drawString("(x0, y0)", pts[0].fX - 5, pts[0].fY, textPaint);
canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);
canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);
canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);
canvas->drawString("radius", center.fX - 3, center.fY - 16, textPaint);
}
##
#Example
#Height 128
void draw(SkCanvas* canvas) {
SkPaint tangentPaint;
tangentPaint.setAntiAlias(true);
SkPaint textPaint(tangentPaint);
tangentPaint.setStyle(SkPaint::kStroke_Style);
tangentPaint.setColor(SK_ColorGRAY);
SkPaint arcPaint(tangentPaint);
arcPaint.setStrokeWidth(5);
arcPaint.setColor(SK_ColorBLUE);
SkPath path;
SkPoint pts[] = { {156, 20}, {200, 20}, {170, 50} };
SkScalar radius = 50;
path.moveTo(pts[0]);
path.arcTo(pts[1], pts[2], radius);
canvas->drawLine(pts[0], pts[1], tangentPaint);
canvas->drawLine(pts[1], pts[2], tangentPaint);
SkPoint lastPt;
(void) path.getLastPt(&lastPt);
SkVector radial = pts[2] - pts[1];
radial.setLength(radius);
SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };
canvas->drawLine(lastPt, center, tangentPaint);
radial = pts[1] - pts[0];
radial.setLength(radius);
SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };
canvas->drawLine(center, arcStart, tangentPaint);
canvas->drawPath(path, arcPaint);
canvas->drawString("(x0, y0)", pts[0].fX, pts[0].fY - 7, textPaint);
canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);
canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);
canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);
canvas->drawString("radius", center.fX - 5, center.fY - 20, textPaint);
}
##
#Example
#Description
arcTo is represented by Line and circular Conic in Path.
##
void draw(SkCanvas* canvas) {
SkPath path;
path.moveTo({156, 20});
path.arcTo(200, 20, 170, 50, 50);
SkPath::Iter iter(path, false);
SkPoint p[4];
SkPath::Verb verb;
while (SkPath::kDone_Verb != (verb = iter.next(p))) {
switch (verb) {
case SkPath::kMove_Verb:
SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);
break;
case SkPath::kLine_Verb:
SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
break;
case SkPath::kConic_Verb:
SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",
p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());
break;
default:
SkDebugf("unexpected verb\n");
}
}
}
#StdOut
move to (156,20)
line (156,20),(79.2893,20)
conic (79.2893,20),(200,20),(114.645,105.355) weight 0.382683
##
##
#SeeAlso conicTo
##
# ------------------------------------------------------------------------------
#Method SkPath& arcTo(const SkPoint p1, const SkPoint p2, SkScalar radius)
#In Build
#In Arc
#Populate
#Example
#Description
Because tangent lines are parallel, arcTo appends line from last Path Point to
p1, but does not append a circular Conic.
##
void draw(SkCanvas* canvas) {
SkPath path;
path.moveTo({156, 20});
path.arcTo({200, 20}, {170, 20}, 50);
SkPath::Iter iter(path, false);
SkPoint p[4];
SkPath::Verb verb;
while (SkPath::kDone_Verb != (verb = iter.next(p))) {
switch (verb) {
case SkPath::kMove_Verb:
SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);
break;
case SkPath::kLine_Verb:
SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
break;
case SkPath::kConic_Verb:
SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",
p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());
break;
default:
SkDebugf("unexpected verb\n");
}
}
}
#StdOut
move to (156,20)
line (156,20),(200,20)
##
##
#SeeAlso conicTo
##
# ------------------------------------------------------------------------------
#Enum ArcSize
#Line # used by arcTo variation ##
#Code
enum ArcSize {
kSmall_ArcSize,
kLarge_ArcSize,
};
##
Four axis-aligned Ovals with the same height and width intersect a pair of Points.
ArcSize and Direction select one of the four Ovals, by choosing the larger or smaller
arc between the Points; and by choosing the arc Direction, clockwise
or counterclockwise.
#Const kSmall_ArcSize 0
#Line # smaller of Arc pair ##
##
#Const kLarge_ArcSize 1
#Line # larger of Arc pair ##
##
#Example
#Height 160
#Description
Arc begins at top of Oval pair and ends at bottom. Arc can take four routes to get there.
Two routes are large, and two routes are counterclockwise. The one route both large
and counterclockwise is blue.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {
SkPath path;
path.moveTo({120, 50});
path.arcTo(70, 40, 30, arcSize, sweep, 156, 100);
if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {
paint.setColor(SK_ColorBLUE);
paint.setStrokeWidth(3);
}
canvas->drawPath(path, paint);
}
}
}
##
#SeeAlso arcTo Direction
##
# ------------------------------------------------------------------------------
#Method SkPath& arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
Direction sweep, SkScalar x, SkScalar y)
#In Build
#In Arc
#Populate
#Example
#Height 160
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {
SkPath path;
path.moveTo({120, 50});
path.arcTo(70, 40, 30, arcSize, sweep, 120.1, 50);
if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {
paint.setColor(SK_ColorBLUE);
paint.setStrokeWidth(3);
}
canvas->drawPath(path, paint);
}
}
}
##
#SeeAlso rArcTo ArcSize Direction
##
# ------------------------------------------------------------------------------
#Method SkPath& arcTo(const SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep,
const SkPoint xy)
#In Build
#In Arc
#Populate
#Example
#Height 108
void draw(SkCanvas* canvas) {
SkPaint paint;
SkPath path;
const SkPoint starts[] = {{20, 20}, {120, 20}, {70, 60}};
for (auto start : starts) {
path.moveTo(start.fX, start.fY);
path.rArcTo(20, 20, 0, SkPath::kSmall_ArcSize, SkPath::kCCW_Direction, 60, 0);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso rArcTo ArcSize Direction
##
# ------------------------------------------------------------------------------
#Method SkPath& rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
Direction sweep, SkScalar dx, SkScalar dy)
#In Build
#In Arc
#Line # appends Arc relative to Last_Point ##
Appends Arc to Path, relative to last Path Point. Arc is implemented by one or
more Conic, weighted to describe part of Oval with radii (rx, ry) rotated by
xAxisRotate degrees. Arc curves from last Path Point to relative end Point
(dx, dy), choosing one of four possible routes: clockwise or
counterclockwise, and smaller or larger. If Path is empty, the start Arc Point
is (0, 0).
Arc sweep is always less than 360 degrees. arcTo appends Line to end Point
if either radii are zero, or if last Path Point equals end Point.
arcTo scales radii (rx, ry) to fit last Path Point and end Point if both are
greater than zero but too small to describe an arc.
arcTo appends up to four Conic curves.
arcTo implements the functionality of SVG_Arc, although SVG "sweep-flag" value is
opposite the integer value of sweep; SVG "sweep-flag" uses 1 for clockwise, while
kCW_Direction cast to int is zero.
#Param rx radius before x-axis rotation ##
#Param ry radius before x-axis rotation ##
#Param xAxisRotate x-axis rotation in degrees; positive values are clockwise ##
#Param largeArc chooses smaller or larger Arc ##
#Param sweep chooses clockwise or counterclockwise Arc ##
#Param dx x-axis offset end of Arc from last Path Point ##
#Param dy y-axis offset end of Arc from last Path Point ##
#Return reference to Path ##
#Example
#Height 108
void draw(SkCanvas* canvas) {
SkPaint paint;
SkPath path;
const SkPoint starts[] = {{20, 20}, {120, 20}, {70, 60}};
for (auto start : starts) {
path.moveTo(start.fX, start.fY);
path.rArcTo(20, 20, 0, SkPath::kSmall_ArcSize, SkPath::kCCW_Direction, 60, 0);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso arcTo ArcSize Direction
##
#Subtopic Arc ##
# ------------------------------------------------------------------------------
#Method SkPath& close()
#In Build
#Line # makes last Contour a loop ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setStrokeWidth(15);
paint.setStrokeCap(SkPaint::kRound_Cap);
SkPath path;
const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};
path.addPoly(points, SK_ARRAY_COUNT(points), false);
for (int loop = 0; loop < 2; ++loop) {
for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
SkPaint::kStrokeAndFill_Style} ) {
paint.setStyle(style);
canvas->drawPath(path, paint);
canvas->translate(85, 0);
}
path.close();
canvas->translate(-255, 128);
}
}
##
#SeeAlso
##
# ------------------------------------------------------------------------------
#Method static bool IsInverseFillType(FillType fill)
#In Property
#Line # returns if Fill_Type represents outside geometry ##
Returns true if fill is inverted and Path with fill represents area outside
of its geometric bounds.
#Table
#Legend
# FillType # is inverse ##
##
# kWinding_FillType # false ##
# kEvenOdd_FillType # false ##
# kInverseWinding_FillType # true ##
# kInverseEvenOdd_FillType # true ##
##
#Param fill one of: kWinding_FillType, kEvenOdd_FillType,
kInverseWinding_FillType, kInverseEvenOdd_FillType
##
#Return true if Path fills outside its bounds ##
#Example
#Function
###$
#define nameValue(fill) { SkPath::fill, #fill }
$$$#
##
void draw(SkCanvas* canvas) {
struct {
SkPath::FillType fill;
const char* name;
} fills[] = {
nameValue(kWinding_FillType),
nameValue(kEvenOdd_FillType),
nameValue(kInverseWinding_FillType),
nameValue(kInverseEvenOdd_FillType),
};
for (auto fill: fills ) {
SkDebugf("IsInverseFillType(%s) == %s\n", fill.name, SkPath::IsInverseFillType(fill.fill) ?
"true" : "false");
}
}
#StdOut
IsInverseFillType(kWinding_FillType) == false
IsInverseFillType(kEvenOdd_FillType) == false
IsInverseFillType(kInverseWinding_FillType) == true
IsInverseFillType(kInverseEvenOdd_FillType) == true
##
##
#SeeAlso FillType getFillType setFillType ConvertToNonInverseFillType
##
# ------------------------------------------------------------------------------
#Method static FillType ConvertToNonInverseFillType(FillType fill)
#In Utility
#Line # returns Fill_Type representing inside geometry ##
Returns equivalent Fill_Type representing Path fill inside its bounds.
#Table
#Legend
# FillType # inside FillType ##
##
# kWinding_FillType # kWinding_FillType ##
# kEvenOdd_FillType # kEvenOdd_FillType ##
# kInverseWinding_FillType # kWinding_FillType ##
# kInverseEvenOdd_FillType # kEvenOdd_FillType ##
##
#Param fill one of: kWinding_FillType, kEvenOdd_FillType,
kInverseWinding_FillType, kInverseEvenOdd_FillType
##
#Return fill, or kWinding_FillType or kEvenOdd_FillType if fill is inverted ##
#Example
#Function
###$
#define nameValue(fill) { SkPath::fill, #fill }
$$$#
##
void draw(SkCanvas* canvas) {
struct {
SkPath::FillType fill;
const char* name;
} fills[] = {
nameValue(kWinding_FillType),
nameValue(kEvenOdd_FillType),
nameValue(kInverseWinding_FillType),
nameValue(kInverseEvenOdd_FillType),
};
for (unsigned i = 0; i < SK_ARRAY_COUNT(fills); ++i) {
if (fills[i].fill != (SkPath::FillType) i) {
SkDebugf("fills array order does not match FillType enum order");
break;
}
SkDebugf("ConvertToNonInverseFillType(%s) == %s\n", fills[i].name,
fills[(int) SkPath::ConvertToNonInverseFillType(fills[i].fill)].name);
}
}
#StdOut
ConvertToNonInverseFillType(kWinding_FillType) == kWinding_FillType
ConvertToNonInverseFillType(kEvenOdd_FillType) == kEvenOdd_FillType
ConvertToNonInverseFillType(kInverseWinding_FillType) == kWinding_FillType
ConvertToNonInverseFillType(kInverseEvenOdd_FillType) == kEvenOdd_FillType
##
##
#SeeAlso FillType getFillType setFillType IsInverseFillType
##
# ------------------------------------------------------------------------------
#Method static int ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
SkScalar w, SkPoint pts[], int pow2)
#In Utility
#Line # approximates Conic with Quad array ##
Approximates Conic with Quad array. Conic is constructed from start Point p0,
control Point p1, end Point p2, and weight w.
Quad array is stored in pts; this storage is supplied by caller.
Maximum Quad count is 2 to the pow2.
Every third point in array shares last Point of previous Quad and first Point of
next Quad. Maximum pts storage size is given by:
#Formula # (1 + 2 * (1 << pow2)) * sizeof(SkPoint) ##.
Returns Quad count used the approximation, which may be smaller
than the number requested.
Conic_Weight determines the amount of influence Conic control point has on the curve.
w less than one represents an elliptical section. w greater than one represents
a hyperbolic section. w equal to one represents a parabolic section.
Two Quad curves are sufficient to approximate an elliptical Conic with a sweep
of up to 90 degrees; in this case, set pow2 to one.
#Param p0 Conic start Point ##
#Param p1 Conic control Point ##
#Param p2 Conic end Point ##
#Param w Conic weight ##
#Param pts storage for Quad array ##
#Param pow2 Quad count, as power of two, normally 0 to 5 (1 to 32 Quad curves) ##
#Return number of Quad curves written to pts ##
#Example
#Description
A pair of Quad curves are drawn in red on top of the elliptical Conic curve in black.
The middle curve is nearly circular. The top-right curve is parabolic, which can
be drawn exactly with a single Quad.
##
void draw(SkCanvas* canvas) {
SkPaint conicPaint;
conicPaint.setAntiAlias(true);
conicPaint.setStyle(SkPaint::kStroke_Style);
SkPaint quadPaint(conicPaint);
quadPaint.setColor(SK_ColorRED);
SkPoint conic[] = { {20, 170}, {80, 170}, {80, 230} };
for (auto weight : { .25f, .5f, .707f, .85f, 1.f } ) {
SkPoint quads[5];
SkPath::ConvertConicToQuads(conic[0], conic[1], conic[2], weight, quads, 1);
SkPath path;
path.moveTo(conic[0]);
path.conicTo(conic[1], conic[2], weight);
canvas->drawPath(path, conicPaint);
path.rewind();
path.moveTo(quads[0]);
path.quadTo(quads[1], quads[2]);
path.quadTo(quads[3], quads[4]);
canvas->drawPath(path, quadPaint);
canvas->translate(50, -50);
}
}
##
#SeeAlso Conic Quad
##
# ------------------------------------------------------------------------------
#Method bool isRect(SkRect* rect, bool* isClosed = nullptr, Direction* direction = nullptr) const
#In Property
#Line # returns if describes Rect ##
#Populate
#Example
#Description
After addRect, isRect returns true. Following moveTo permits isRect to return true, but
following lineTo does not. addPoly returns true even though rect is not closed, and one
side of rect is made up of consecutive line segments.
##
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path) -> void {
SkRect rect;
SkPath::Direction direction;
bool isClosed;
path.isRect(&rect, &isClosed, &direction) ?
SkDebugf("%s is rect (%g, %g, %g, %g); is %s" "closed; direction %s\n", prefix,
rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, isClosed ? "" : "not ",
SkPath::kCW_Direction == direction ? "CW" : "CCW") :
SkDebugf("%s is not rect\n", prefix);
};
SkPath path;
debugster("empty", path);
path.addRect({10, 20, 30, 40});
debugster("addRect", path);
path.moveTo(60, 70);
debugster("moveTo", path);
path.lineTo(60, 70);
debugster("lineTo", path);
path.reset();
const SkPoint pts[] = { {0, 0}, {0, 80}, {80, 80}, {80, 0}, {40, 0}, {20, 0} };
path.addPoly(pts, SK_ARRAY_COUNT(pts), false);
debugster("addPoly", path);
}
#StdOut
empty is not rect
addRect is rect (10, 20, 30, 40); is closed; direction CW
moveTo is rect (10, 20, 30, 40); is closed; direction CW
lineTo is not rect
addPoly is rect (0, 0, 80, 80); is not closed; direction CCW
##
##
#SeeAlso computeTightBounds conservativelyContainsRect getBounds isConvex isLastContourClosed isNestedFillRects
##
# ------------------------------------------------------------------------------
#Method bool isNestedFillRects(SkRect rect[2], Direction dirs[2] = nullptr) const
#In Property
#Line # returns if describes Rect pair, one inside the other ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(5);
SkPath path;
path.addRect({10, 20, 30, 40});
paint.getFillPath(path, &path);
SkRect rects[2];
SkPath::Direction directions[2];
if (path.isNestedFillRects(rects, directions)) {
for (int i = 0; i < 2; ++i) {
SkDebugf("%s (%g, %g, %g, %g); direction %s\n", i ? "inner" : "outer",
rects[i].fLeft, rects[i].fTop, rects[i].fRight, rects[i].fBottom,
SkPath::kCW_Direction == directions[i] ? "CW" : "CCW");
}
} else {
SkDebugf("is not nested rectangles\n");
}
}
#StdOut
outer (7.5, 17.5, 32.5, 42.5); direction CW
inner (12.5, 22.5, 27.5, 37.5); direction CCW
##
##
#SeeAlso computeTightBounds conservativelyContainsRect getBounds isConvex isLastContourClosed isRect
##
# ------------------------------------------------------------------------------
#Method SkPath& addRect(const SkRect& rect, Direction dir = kCW_Direction)
#In Build
#Line # adds one Contour containing Rect ##
#Populate
#Example
#Description
The left Rect dashes starting at the top-left corner, to the right.
The right Rect dashes starting at the top-left corner, towards the bottom.
##
#Height 128
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setStrokeWidth(15);
paint.setStrokeCap(SkPaint::kSquare_Cap);
float intervals[] = { 5, 21.75f };
paint.setStyle(SkPaint::kStroke_Style);
paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
SkPath path;
path.addRect({20, 20, 100, 100}, SkPath::kCW_Direction);
canvas->drawPath(path, paint);
path.rewind();
path.addRect({140, 20, 220, 100}, SkPath::kCCW_Direction);
canvas->drawPath(path, paint);
}
##
#SeeAlso SkCanvas::drawRect Direction
##
# ------------------------------------------------------------------------------
#Method SkPath& addRect(const SkRect& rect, Direction dir, unsigned start)
Adds Rect to Path, appending kMove_Verb, three kLine_Verb, and kClose_Verb.
If dir is kCW_Direction, Rect corners are added clockwise; if dir is
kCCW_Direction, Rect corners are added counterclockwise.
start determines the first corner added.
#Table
#Legend
# start # first corner ##
#Legend ##
# 0 # top-left ##
# 1 # top-right ##
# 2 # bottom-right ##
# 3 # bottom-left ##
#Table ##
#Param rect Rect to add as a closed contour ##
#Param dir Direction to wind added contour ##
#Param start initial corner of Rect to add ##
#Return reference to Path ##
#Example
#Height 128
#Description
The arrow is just after the initial corner and points towards the next
corner appended to Path.
##
void draw(SkCanvas* canvas) {
const SkPoint arrow[] = { {5, -5}, {15, -5}, {20, 0}, {15, 5}, {5, 5}, {10, 0} };
const SkRect rect = {10, 10, 54, 54};
SkPaint rectPaint;
rectPaint.setAntiAlias(true);
rectPaint.setStyle(SkPaint::kStroke_Style);
SkPaint arrowPaint(rectPaint);
SkPath arrowPath;
arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,
SkPath1DPathEffect::kRotate_Style));
for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
for (unsigned start : { 0, 1, 2, 3 } ) {
SkPath path;
path.addRect(rect, direction, start);
canvas->drawPath(path, rectPaint);
canvas->drawPath(path, arrowPaint);
canvas->translate(64, 0);
}
canvas->translate(-256, 64);
}
}
##
#SeeAlso SkCanvas::drawRect Direction
##
# ------------------------------------------------------------------------------
#Method SkPath& addRect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom,
Direction dir = kCW_Direction)
#Populate
#Example
#Description
The left Rect dashes start at the top-left corner, and continue to the right.
The right Rect dashes start at the top-left corner, and continue down.
##
#Height 128
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setStrokeWidth(15);
paint.setStrokeCap(SkPaint::kSquare_Cap);
float intervals[] = { 5, 21.75f };
paint.setStyle(SkPaint::kStroke_Style);
paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
SkPath path;
path.addRect(20, 20, 100, 100, direction);
canvas->drawPath(path, paint);
canvas->translate(128, 0);
}
}
##
#SeeAlso SkCanvas::drawRect Direction
##
# ------------------------------------------------------------------------------
#Method SkPath& addOval(const SkRect& oval, Direction dir = kCW_Direction)
#In Build
#Line # adds one Contour containing Oval ##
#Populate
#Example
#Height 120
SkPaint paint;
SkPath oval;
oval.addOval({20, 20, 160, 80});
canvas->drawPath(oval, paint);
##
#SeeAlso SkCanvas::drawOval Direction Oval
##
# ------------------------------------------------------------------------------
#Method SkPath& addOval(const SkRect& oval, Direction dir, unsigned start)
Adds Oval to Path, appending kMove_Verb, four kConic_Verb, and kClose_Verb.
Oval is upright ellipse bounded by Rect oval with radii equal to half oval width
and half oval height. Oval begins at start and continues
clockwise if dir is kCW_Direction, counterclockwise if dir is kCCW_Direction.
#Table
#Legend
# start # Point ##
#Legend ##
# 0 # oval.centerX(), oval.fTop ##
# 1 # oval.fRight, oval.centerY() ##
# 2 # oval.centerX(), oval.fBottom ##
# 3 # oval.fLeft, oval.centerY() ##
#Table ##
#Param oval bounds of ellipse added ##
#Param dir Direction to wind ellipse ##
#Param start index of initial point of ellipse ##
#Return reference to Path ##
#Example
#Height 160
void draw(SkCanvas* canvas) {
const SkPoint arrow[] = { {0, -5}, {10, 0}, {0, 5} };
const SkRect rect = {10, 10, 54, 54};
SkPaint ovalPaint;
ovalPaint.setAntiAlias(true);
SkPaint textPaint(ovalPaint);
ovalPaint.setStyle(SkPaint::kStroke_Style);
SkPaint arrowPaint(ovalPaint);
SkPath arrowPath;
arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,
SkPath1DPathEffect::kRotate_Style));
for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
for (unsigned start : { 0, 1, 2, 3 } ) {
SkPath path;
path.addOval(rect, direction, start);
canvas->drawPath(path, ovalPaint);
canvas->drawPath(path, arrowPaint);
canvas->drawText(&"0123"[start], 1, rect.centerX(), rect.centerY() + 5, textPaint);
canvas->translate(64, 0);
}
canvas->translate(-256, 72);
canvas->drawString(SkPath::kCW_Direction == direction ? "clockwise" : "counterclockwise",
128, 0, textPaint);
}
}
##
#SeeAlso SkCanvas::drawOval Direction Oval
##
# ------------------------------------------------------------------------------
#Method SkPath& addCircle(SkScalar x, SkScalar y, SkScalar radius,
Direction dir = kCW_Direction)
#In Build
#Line # adds one Contour containing Circle ##
Adds Circle centered at (x, y) of size radius to Path, appending kMove_Verb,
four kConic_Verb, and kClose_Verb. Circle begins at: #Formula # (x + radius, y) ##, continuing
clockwise if dir is kCW_Direction, and counterclockwise if dir is kCCW_Direction.
Has no effect if radius is zero or negative.
#Param x center of Circle ##
#Param y center of Circle ##
#Param radius distance from center to edge ##
#Param dir Direction to wind Circle ##
#Return reference to Path ##
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(10);
for (int size = 10; size < 300; size += 20) {
SkPath path;
path.addCircle(128, 128, size, SkPath::kCW_Direction);
canvas->drawPath(path, paint);
}
}
##
#SeeAlso SkCanvas::drawCircle Direction Circle
##
# ------------------------------------------------------------------------------
#Method SkPath& addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle)
#In Build
#Line # adds one Contour containing Arc ##
#Populate
#Example
#Description
The middle row of the left and right columns draw differently from the entries
above and below because sweepAngle is outside of the range of +/-360,
and startAngle modulo 90 is not zero.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
for (auto start : { 0, 90, 135, 180, 270 } ) {
for (auto sweep : { -450.f, -180.f, -90.f, 90.f, 180.f, 360.1f } ) {
SkPath path;
path.addArc({10, 10, 35, 45}, start, sweep);
canvas->drawPath(path, paint);
canvas->translate(252 / 6, 0);
}
canvas->translate(-252, 255 / 5);
}
}
##
#SeeAlso Arc arcTo SkCanvas::drawArc
##
# ------------------------------------------------------------------------------
#Method SkPath& addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
Direction dir = kCW_Direction)
#In Build
#Line # adds one Contour containing Round_Rect with common corner radii ##
#Populate
#Example
#Description
If either radius is zero, path contains Rect and is drawn red.
If sides are only radii, path contains Oval and is drawn blue.
All remaining path draws are convex, and are drawn in gray; no
paths constructed from addRoundRect are concave, so none are
drawn in green.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
for (auto xradius : { 0, 7, 13, 20 } ) {
for (auto yradius : { 0, 9, 18, 40 } ) {
SkPath path;
path.addRoundRect({10, 10, 36, 46}, xradius, yradius);
paint.setColor(path.isRect(nullptr) ? SK_ColorRED : path.isOval(nullptr) ?
SK_ColorBLUE : path.isConvex() ? SK_ColorGRAY : SK_ColorGREEN);
canvas->drawPath(path, paint);
canvas->translate(64, 0);
}
canvas->translate(-256, 64);
}
}
##
#SeeAlso addRRect SkCanvas::drawRoundRect
##
# ------------------------------------------------------------------------------
#Method SkPath& addRoundRect(const SkRect& rect, const SkScalar radii[],
Direction dir = kCW_Direction)
Appends Round_Rect to Path, creating a new closed Contour. Round_Rect has bounds
equal to rect; each corner is 90 degrees of an ellipse with radii from the
array.
#Table
#Legend
# radii index # location ##
#Legend ##
# 0 # x-axis radius of top-left corner ##
# 1 # y-axis radius of top-left corner ##
# 2 # x-axis radius of top-right corner ##
# 3 # y-axis radius of top-right corner ##
# 4 # x-axis radius of bottom-right corner ##
# 5 # y-axis radius of bottom-right corner ##
# 6 # x-axis radius of bottom-left corner ##
# 7 # y-axis radius of bottom-left corner ##
#Table ##
If dir is kCW_Direction, Round_Rect starts at top-left of the lower-left corner
and winds clockwise. If dir is kCCW_Direction, Round_Rect starts at the
bottom-left of the upper-left corner and winds counterclockwise.
If both radii on any side of rect exceed its length, all radii are scaled
uniformly until the corners fit. If either radius of a corner is less than or
equal to zero, both are treated as zero.
After appending, Path may be empty, or may contain: Rect, Oval, or Round_Rect.
#Param rect bounds of Round_Rect ##
#Param radii array of 8 SkScalar values, a radius pair for each corner ##
#Param dir Direction to wind Round_Rect ##
#Return reference to Path ##
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
SkScalar radii[] = { 80, 100, 0, 0, 40, 60, 0, 0 };
SkPath path;
SkMatrix rotate90;
rotate90.setRotate(90, 128, 128);
for (int i = 0; i < 4; ++i) {
path.addRoundRect({10, 10, 110, 110}, radii);
path.transform(rotate90);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso addRRect SkCanvas::drawRoundRect
##
# ------------------------------------------------------------------------------
#Method SkPath& addRRect(const SkRRect& rrect, Direction dir = kCW_Direction)
#In Build
#Line # adds one Contour containing Round_Rect ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
SkRRect rrect;
SkVector radii[] = {{50, 50}, {0, 0}, {0, 0}, {50, 50}};
rrect.setRectRadii({10, 10, 110, 110}, radii);
SkPath path;
SkMatrix rotate90;
rotate90.setRotate(90, 128, 128);
for (int i = 0; i < 4; ++i) {
path.addRRect(rrect);
path.transform(rotate90);
}
canvas->drawPath(path, paint);
}
##
#SeeAlso addRoundRect SkCanvas::drawRRect
##
# ------------------------------------------------------------------------------
#Method SkPath& addRRect(const SkRRect& rrect, Direction dir, unsigned start)
Adds rrect to Path, creating a new closed Contour. If dir is kCW_Direction, rrect
winds clockwise; if dir is kCCW_Direction, rrect winds counterclockwise.
start determines the first point of rrect to add.
#Table
#Legend
# start # location ##
#Legend ##
# 0 # right of top-left corner ##
# 1 # left of top-right corner ##
# 2 # bottom of top-right corner ##
# 3 # top of bottom-right corner ##
# 4 # left of bottom-right corner ##
# 5 # right of bottom-left corner ##
# 6 # top of bottom-left corner ##
# 7 # bottom of top-left corner ##
#Table ##
After appending, Path may be empty, or may contain: Rect, Oval, or Round_Rect.
#Param rrect bounds and radii of rounded rectangle ##
#Param dir Direction to wind Round_Rect ##
#Param start index of initial point of Round_Rect ##
#Return reference to Path ##
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
SkRRect rrect;
rrect.setRectXY({40, 40, 215, 215}, 50, 50);
SkPath path;
path.addRRect(rrect);
canvas->drawPath(path, paint);
for (int start = 0; start < 8; ++start) {
SkPath textPath;
textPath.addRRect(rrect, SkPath::kCW_Direction, start);
SkPathMeasure pathMeasure(textPath, false);
SkPoint position;
SkVector tangent;
if (!pathMeasure.getPosTan(0, &position, &tangent)) {
continue;
}
SkRSXform rsxForm = SkRSXform::Make(tangent.fX, tangent.fY,
position.fX + tangent.fY * 5, position.fY - tangent.fX * 5);
canvas->drawTextRSXform(&"01234567"[start], 1, &rsxForm, nullptr, paint);
}
}
##
#SeeAlso addRoundRect SkCanvas::drawRRect
##
# ------------------------------------------------------------------------------
#Method SkPath& addPoly(const SkPoint pts[], int count, bool close)
#In Build
#Line # adds one Contour containing connected lines ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setStrokeWidth(15);
paint.setStrokeCap(SkPaint::kRound_Cap);
const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};
for (bool close : { false, true } ) {
SkPath path;
path.addPoly(points, SK_ARRAY_COUNT(points), close);
for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
SkPaint::kStrokeAndFill_Style} ) {
paint.setStyle(style);
canvas->drawPath(path, paint);
canvas->translate(85, 0);
}
canvas->translate(-255, 128);
}
}
##
#SeeAlso SkCanvas::drawPoints
##
#Method SkPath& addPoly(const std::initializer_list<SkPoint>& list, bool close)
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setStrokeWidth(15);
paint.setStrokeCap(SkPaint::kRound_Cap);
for (bool close : { false, true } ) {
SkPath path;
path.addPoly({{20, 20}, {70, 20}, {40, 90}}, close);
for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
SkPaint::kStrokeAndFill_Style} ) {
paint.setStyle(style);
canvas->drawPath(path, paint);
canvas->translate(85, 0);
}
canvas->translate(-255, 128);
}
}
##
#SeeAlso SkCanvas::drawPoints
##
# ------------------------------------------------------------------------------
#Enum AddPathMode
#Line # sets addPath options ##
#Code
enum AddPathMode {
kAppend_AddPathMode,
kExtend_AddPathMode,
};
##
AddPathMode chooses how addPath appends. Adding one Path to another can extend
the last Contour or start a new Contour.
#Const kAppend_AddPathMode
#Line # appended to destination unaltered ##
Path Verbs, Points, and Conic_Weights are appended to destination unaltered.
Since Path Verb_Array begins with kMove_Verb if src is not empty, this
starts a new Contour.
##
#Const kExtend_AddPathMode
#Line # add line if prior Contour is not closed ##
If destination is closed or empty, start a new Contour. If destination
is not empty, add Line from Last_Point to added Path first Point. Skip added
Path initial kMove_Verb, then append remaining Verbs, Points, and Conic_Weights.
##
#Example
#Description
test is built from path, open on the top row, and closed on the bottom row.
The left column uses kAppend_AddPathMode; the right uses kExtend_AddPathMode.
The top right composition is made up of one contour; the other three have two.
##
#Height 180
SkPath path, path2;
path.moveTo(20, 20);
path.lineTo(20, 40);
path.lineTo(40, 20);
path2.moveTo(60, 60);
path2.lineTo(80, 60);
path2.lineTo(80, 40);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
for (int i = 0; i < 2; i++) {
for (auto addPathMode : { SkPath::kAppend_AddPathMode, SkPath::kExtend_AddPathMode } ) {
SkPath test(path);
test.addPath(path2, addPathMode);
canvas->drawPath(test, paint);
canvas->translate(100, 0);
}
canvas->translate(-200, 100);
path.close();
}
##
#SeeAlso addPath reverseAddPath
##
# ------------------------------------------------------------------------------
#Method SkPath& addPath(const SkPath& src, SkScalar dx, SkScalar dy,
AddPathMode mode = kAppend_AddPathMode)
#In Build
#Line # adds contents of Path ##
#Populate
#Example
#Height 180
SkPaint paint;
paint.setTextSize(128);
paint.setFakeBoldText(true);
SkPath dest, text;
paint.getTextPath("O", 1, 50, 120, &text);
for (int i = 0; i < 3; i++) {
dest.addPath(text, i * 20, i * 20);
}
Simplify(dest, &dest);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(3);
canvas->drawPath(dest, paint);
##
#SeeAlso AddPathMode offset reverseAddPath
##
# ------------------------------------------------------------------------------
#Method SkPath& addPath(const SkPath& src, AddPathMode mode = kAppend_AddPathMode)
#Populate
#Example
#Height 80
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
SkPath dest, path;
path.addOval({-80, 20, 0, 60}, SkPath::kCW_Direction, 1);
for (int i = 0; i < 2; i++) {
dest.addPath(path, SkPath::kExtend_AddPathMode);
dest.offset(100, 0);
}
canvas->drawPath(dest, paint);
##
#SeeAlso AddPathMode reverseAddPath
##
# ------------------------------------------------------------------------------
#Method SkPath& addPath(const SkPath& src, const SkMatrix& matrix, AddPathMode mode = kAppend_AddPathMode)
#Populate
#Example
#Height 160
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
SkPath dest, path;
path.addOval({20, 20, 200, 120}, SkPath::kCW_Direction, 1);
for (int i = 0; i < 6; i++) {
SkMatrix matrix;
matrix.reset();
matrix.setPerspX(i / 400.f);
dest.addPath(path, matrix);
}
canvas->drawPath(dest, paint);
##
#SeeAlso AddPathMode transform offset reverseAddPath
##
# ------------------------------------------------------------------------------
#Method SkPath& reverseAddPath(const SkPath& src)
#In Build
#Line # adds contents of Path back to front ##
#Populate
#Example
#Height 200
SkPath path;
path.moveTo(20, 20);
path.lineTo(20, 40);
path.lineTo(40, 20);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
for (int i = 0; i < 2; i++) {
SkPath path2;
path2.moveTo(60, 60);
path2.lineTo(80, 60);
path2.lineTo(80, 40);
for (int j = 0; j < 2; j++) {
SkPath test(path);
test.reverseAddPath(path2);
canvas->drawPath(test, paint);
canvas->translate(100, 0);
path2.close();
}
canvas->translate(-200, 100);
path.close();
}
##
#SeeAlso AddPathMode transform offset addPath
##
# ------------------------------------------------------------------------------
#Method void offset(SkScalar dx, SkScalar dy, SkPath* dst) const
#In Transform
#Line # translates Point_Array ##
#Populate
#Example
#Height 60
SkPath pattern;
pattern.moveTo(20, 20);
pattern.lineTo(20, 40);
pattern.lineTo(40, 20);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
for (int i = 0; i < 10; i++) {
SkPath path;
pattern.offset(20 * i, 0, &path);
canvas->drawPath(path, paint);
}
##
#SeeAlso addPath transform
##
# ------------------------------------------------------------------------------
#Subtopic Transform
#Line # modify all points ##
##
#Method void offset(SkScalar dx, SkScalar dy)
#In Transform
#Populate
#Example
#Height 60
SkPath path;
path.moveTo(20, 20);
path.lineTo(20, 40);
path.lineTo(40, 20);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
for (int i = 0; i < 10; i++) {
canvas->drawPath(path, paint);
path.offset(20, 0);
}
##
#SeeAlso addPath transform SkCanvas::translate()
##
# ------------------------------------------------------------------------------
#Method void transform(const SkMatrix& matrix, SkPath* dst) const
#In Transform
#Line # applies Matrix to Point_Array and Weights ##
#Populate
#Example
#Height 200
SkPath pattern;
pattern.moveTo(100, 100);
pattern.lineTo(100, 20);
pattern.lineTo(20, 100);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
for (int i = 0; i < 10; i++) {
SkPath path;
SkMatrix matrix;
matrix.setRotate(36 * i, 100, 100);
pattern.transform(matrix, &path);
canvas->drawPath(path, paint);
}
##
#SeeAlso addPath offset SkCanvas::concat() SkMatrix
##
# ------------------------------------------------------------------------------
#Method void transform(const SkMatrix& matrix)
#Populate
#Example
#Height 200
SkPath path;
path.moveTo(100, 100);
path.quadTo(100, 20, 20, 100);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
for (int i = 0; i < 10; i++) {
SkMatrix matrix;
matrix.setRotate(36, 100, 100);
path.transform(matrix);
canvas->drawPath(path, paint);
}
##
#SeeAlso addPath offset SkCanvas::concat() SkMatrix
##
# ------------------------------------------------------------------------------
#Subtopic Last_Point
#Line # final Point in Contour ##
Path is defined cumulatively, often by adding a segment to the end of last
Contour. Last_Point of Contour is shared as first Point of added Line or Curve.
Last_Point can be read and written directly with getLastPt and setLastPt.
#Method bool getLastPt(SkPoint* lastPt) const
#In Property
#In Last_Point
#Line # returns Last_Point ##
#Populate
#Example
SkPath path;
path.moveTo(100, 100);
path.quadTo(100, 20, 20, 100);
SkMatrix matrix;
matrix.setRotate(36, 100, 100);
path.transform(matrix);
SkPoint last;
path.getLastPt(&last);
SkDebugf("last point: %g, %g\n", last.fX, last.fY);
#StdOut
last point: 35.2786, 52.9772
##
##
#SeeAlso setLastPt
##
#Method void setLastPt(SkScalar x, SkScalar y)
#In Utility
#In Last_Point
#Line # replaces Last_Point ##
#Populate
#Example
#Height 128
SkPaint paint;
paint.setTextSize(128);
SkPath path;
paint.getTextPath("@", 1, 60, 100, &path);
path.setLastPt(20, 120);
canvas->drawPath(path, paint);
##
#SeeAlso getLastPt
##
#Method void setLastPt(const SkPoint& p)
#Populate
#Example
#Height 128
SkPaint paint;
paint.setTextSize(128);
SkPath path, path2;
paint.getTextPath("A", 1, 60, 100, &path);
paint.getTextPath("Z", 1, 60, 100, &path2);
SkPoint pt, pt2;
path.getLastPt(&pt);
path2.getLastPt(&pt2);
path.setLastPt(pt2);
path2.setLastPt(pt);
canvas->drawPath(path, paint);
canvas->drawPath(path2, paint);
##
#SeeAlso getLastPt
##
#Subtopic Last_Point ##
# ------------------------------------------------------------------------------
#Enum SegmentMask
#Line # returns Verb types in Path ##
#Code
enum SegmentMask {
kLine_SegmentMask = 1 << 0,
kQuad_SegmentMask = 1 << 1,
kConic_SegmentMask = 1 << 2,
kCubic_SegmentMask = 1 << 3,
};
##
SegmentMask constants correspond to each drawing Verb type in Path; for
instance, if Path only contains Lines, only the kLine_SegmentMask bit is set.
#Bug 6785
#Const kLine_SegmentMask 1
#Line # contains one or more Lines ##
Set if Verb_Array contains kLine_Verb.
##
#Const kQuad_SegmentMask 2
#Line # contains one or more Quads ##
Set if Verb_Array contains kQuad_Verb. Note that conicTo may add a Quad.
##
#Const kConic_SegmentMask 4
#Line # contains one or more Conics ##
Set if Verb_Array contains kConic_Verb.
##
#Const kCubic_SegmentMask 8
#Line # contains one or more Cubics ##
Set if Verb_Array contains kCubic_Verb.
##
#Example
#Description
When conicTo has a weight of one, Quad is added to Path.
##
SkPath path;
path.conicTo(10, 10, 20, 30, 1);
SkDebugf("Path kConic_SegmentMask is %s\n", path.getSegmentMasks() &
SkPath::kConic_SegmentMask ? "set" : "clear");
SkDebugf("Path kQuad_SegmentMask is %s\n", path.getSegmentMasks() &
SkPath::kQuad_SegmentMask ? "set" : "clear");
#StdOut
Path kConic_SegmentMask is clear
Path kQuad_SegmentMask is set
##
##
#SeeAlso getSegmentMasks Verb
##
# ------------------------------------------------------------------------------
#Method uint32_t getSegmentMasks() const
#In Utility
#In Property
#Line # returns types in Verb_Array ##
#Populate
#Example
SkPath path;
path.quadTo(20, 30, 40, 50);
path.close();
const char* masks[] = { "line", "quad", "conic", "cubic" };
int index = 0;
for (auto mask : { SkPath::kLine_SegmentMask, SkPath::kQuad_SegmentMask,
SkPath::kConic_SegmentMask, SkPath::kCubic_SegmentMask } ) {
if (mask & path.getSegmentMasks()) {
SkDebugf("mask %s set\n", masks[index]);
}
++index;
}
#StdOut
mask quad set
##
##
#SeeAlso getSegmentMasks Verb
##
# ------------------------------------------------------------------------------
#Method bool contains(SkScalar x, SkScalar y) const
#In Property
#Line # returns if Point is in fill area ##
Returns true if the point (x, y) is contained by Path, taking into
account FillType.
#Table
#Legend
# FillType # contains() returns true if Point is enclosed by ##
##
# kWinding_FillType # a non-zero sum of Contour Directions. ##
# kEvenOdd_FillType # an odd number of Contours. ##
# kInverseWinding_FillType # a zero sum of Contour Directions. ##
# kInverseEvenOdd_FillType # and even number of Contours. ##
##
#Param x x-axis value of containment test ##
#Param y y-axis value of containment test ##
#Return true if Point is in Path ##
#Example
SkPath path;
SkPaint paint;
paint.setTextSize(256);
paint.getTextPath("&", 1, 30, 220, &path);
for (int y = 2; y < 256; y += 9) {
for (int x = 2; x < 256; x += 9) {
int coverage = 0;
for (int iy = -4; iy <= 4; iy += 2) {
for (int ix = -4; ix <= 4; ix += 2) {
coverage += path.contains(x + ix, y + iy);
}
}
paint.setColor(SkColorSetARGB(0x5f, 0xff * coverage / 25, 0, 0xff * (25 - coverage) / 25));
canvas->drawCircle(x, y, 8, paint);
}
}
##
#SeeAlso conservativelyContainsRect Fill_Type Op
##
# ------------------------------------------------------------------------------
#Method void dump(SkWStream* stream, bool forceClose, bool dumpAsHex) const
#In Utility
#Line # sends text representation to stream ##
#Populate
#Example
SkPath path;
path.quadTo(20, 30, 40, 50);
for (bool forceClose : { false, true } ) {
for (bool dumpAsHex : { false, true } ) {
path.dump(nullptr, forceClose, dumpAsHex);
SkDebugf("\n");
}
}
#StdOut
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(0, 0);
path.quadTo(20, 30, 40, 50);
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.quadTo(SkBits2Float(0x41a00000), SkBits2Float(0x41f00000), SkBits2Float(0x42200000), SkBits2Float(0x42480000)); // 20, 30, 40, 50
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(0, 0);
path.quadTo(20, 30, 40, 50);
path.lineTo(0, 0);
path.close();
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.quadTo(SkBits2Float(0x41a00000), SkBits2Float(0x41f00000), SkBits2Float(0x42200000), SkBits2Float(0x42480000)); // 20, 30, 40, 50
path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.close();
##
##
#SeeAlso dumpHex SkRect::dump() SkRRect::dump() SkPathMeasure::dump()
##
# ------------------------------------------------------------------------------
#Method void dump() const
#Populate
#Example
SkPath path, copy;
path.lineTo(6.f / 7, 2.f / 3);
path.dump();
copy.setFillType(SkPath::kWinding_FillType);
copy.moveTo(0, 0);
copy.lineTo(0.857143f, 0.666667f);
SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
#StdOut
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(0, 0);
path.lineTo(0.857143f, 0.666667f);
path is not equal to copy
##
##
#SeeAlso dumpHex SkRect::dump() SkRRect::dump() SkPathMeasure::dump() writeToMemory
##
# ------------------------------------------------------------------------------
#Method void dumpHex() const
#In Utility
#Line # sends text representation using hexadecimal to standard output ##
Writes text representation of Path to standard output. The representation may be
directly compiled as C++ code. Floating point values are written
in hexadecimal to preserve their exact bit pattern. The output reconstructs the
original Path.
Use instead of dump() when submitting
#A bug reports against Skia # https://bug.skia.org ##
.
#Example
SkPath path, copy;
path.lineTo(6.f / 7, 2.f / 3);
path.dumpHex();
copy.setFillType(SkPath::kWinding_FillType);
copy.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
copy.lineTo(SkBits2Float(0x3f5b6db7), SkBits2Float(0x3f2aaaab)); // 0.857143f, 0.666667f
SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
#StdOut
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.lineTo(SkBits2Float(0x3f5b6db7), SkBits2Float(0x3f2aaaab)); // 0.857143f, 0.666667f
path is equal to copy
##
##
#SeeAlso dump SkRect::dumpHex SkRRect::dumpHex writeToMemory
##
# ------------------------------------------------------------------------------
#Method size_t writeToMemory(void* buffer) const
#In Utility
#Line # copies data to buffer ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path, copy;
path.lineTo(6.f / 7, 2.f / 3);
size_t size = path.writeToMemory(nullptr);
SkTDArray<char> storage;
storage.setCount(size);
path.writeToMemory(storage.begin());
copy.readFromMemory(storage.begin(), size);
SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
}
#StdOut
path is equal to copy
##
##
#SeeAlso serialize readFromMemory dump dumpHex
##
#Method sk_sp<SkData> serialize() const
#In Utility
#Line # copies data to buffer ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path, copy;
path.lineTo(6.f / 7, 2.f / 3);
sk_sp<SkData> data = path.serialize();
copy.readFromMemory(data->data(), data->size());
SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
}
#StdOut
path is equal to copy
##
##
#SeeAlso writeToMemory readFromMemory dump dumpHex
##
# ------------------------------------------------------------------------------
#Method size_t readFromMemory(const void* buffer, size_t length)
#In Utility
#Line # initializes from buffer ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path, copy;
path.lineTo(6.f / 7, 2.f / 3);
size_t size = path.writeToMemory(nullptr);
SkTDArray<char> storage;
storage.setCount(size);
path.writeToMemory(storage.begin());
size_t wrongSize = size - 4;
size_t bytesRead = copy.readFromMemory(storage.begin(), wrongSize);
SkDebugf("length = %u; returned by readFromMemory = %u\n", wrongSize, bytesRead);
size_t largerSize = size + 4;
bytesRead = copy.readFromMemory(storage.begin(), largerSize);
SkDebugf("length = %u; returned by readFromMemory = %u\n", largerSize, bytesRead);
}
#StdOut
length = 32; returned by readFromMemory = 0
length = 40; returned by readFromMemory = 36
##
##
#SeeAlso writeToMemory
##
# ------------------------------------------------------------------------------
#Subtopic Generation_ID
#Alias Generation_IDs ##
#Line # value reflecting contents change ##
Generation_ID provides a quick way to check if Verb_Array, Point_Array, or
Conic_Weight has changed. Generation_ID is not a hash; identical Paths will
not necessarily have matching Generation_IDs.
Empty Paths have a Generation_ID of one.
#Method uint32_t getGenerationID() const
#In Generation_ID
#Line # returns unique ID ##
#Populate
#Example
SkPath path;
SkDebugf("empty genID = %u\n", path.getGenerationID());
path.lineTo(1, 2);
SkDebugf("1st lineTo genID = %u\n", path.getGenerationID());
path.rewind();
SkDebugf("empty genID = %u\n", path.getGenerationID());
path.lineTo(1, 2);
SkDebugf("2nd lineTo genID = %u\n", path.getGenerationID());
#StdOut
empty genID = 1
1st lineTo genID = 2
empty genID = 1
2nd lineTo genID = 3
##
##
#SeeAlso operator==(const SkPath& a, const SkPath& b)
##
#Subtopic ##
# ------------------------------------------------------------------------------
#Method bool isValid() const
#In Property
#In Utility
#Line # returns if data is internally consistent ##
#Populate
#NoExample
##
##
# ------------------------------------------------------------------------------
#Class Iter
#Line # data iterator ##
#Code
#Populate
##
Iterates through Verb_Array, and associated Point_Array and Conic_Weight.
Provides options to treat open Contours as closed, and to ignore
degenerate data.
#Example
#Height 128
#Description
Ignoring the actual Verbs and replacing them with Quads rounds the
path of the glyph.
##
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(256);
SkPath asterisk, path;
paint.getTextPath("*", 1, 50, 192, &asterisk);
SkPath::Iter iter(asterisk, true);
SkPoint start[4], pts[4];
iter.next(start); // skip moveTo
iter.next(start); // first quadTo
path.moveTo((start[0] + start[1]) * 0.5f);
while (SkPath::kClose_Verb != iter.next(pts)) {
path.quadTo(pts[0], (pts[0] + pts[1]) * 0.5f);
}
path.quadTo(start[0], (start[0] + start[1]) * 0.5f);
canvas->drawPath(path, paint);
}
##
#SeeAlso RawIter
#Method Iter()
#Line # constructs Path iterator ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath::Iter iter;
SkPoint points[4];
SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");
SkPath path;
iter.setPath(path, false);
SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");
}
#StdOut
iter is done
iter is done
##
##
#SeeAlso setPath
##
#Method Iter(const SkPath& path, bool forceClose)
#Line # constructs Path iterator ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {
SkDebugf("%s:\n", prefix);
const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
const int pointCount[] = { 1 , 2 , 3 , 3 , 4 , 1 , 0 };
SkPath::Verb verb;
do {
SkPoint points[4];
verb = iter.next(points);
SkDebugf("k%s_Verb ", verbStr[(int) verb]);
for (int i = 0; i < pointCount[(int) verb]; ++i) {
SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
}
if (SkPath::kConic_Verb == verb) {
SkDebugf("weight = %g", iter.conicWeight());
}
SkDebugf("\n");
} while (SkPath::kDone_Verb != verb);
SkDebugf("\n");
};
SkPath path;
path.quadTo(10, 20, 30, 40);
SkPath::Iter openIter(path, false);
debugster("open", openIter);
SkPath::Iter closedIter(path, true);
debugster("closed", closedIter);
}
#StdOut
open:
kMove_Verb {0, 0},
kQuad_Verb {0, 0}, {10, 20}, {30, 40},
kDone_Verb
closed:
kMove_Verb {0, 0},
kQuad_Verb {0, 0}, {10, 20}, {30, 40},
kLine_Verb {30, 40}, {0, 0},
kClose_Verb {0, 0},
kDone_Verb
##
##
#SeeAlso setPath
##
#Method void setPath(const SkPath& path, bool forceClose)
#Line # resets Iter to Path ##
#Populate
#Example
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {
SkDebugf("%s:\n", prefix);
const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
const int pointCount[] = { 1 , 2 , 3 , 3 , 4 , 1 , 0 };
SkPath::Verb verb;
do {
SkPoint points[4];
verb = iter.next(points);
SkDebugf("k%s_Verb ", verbStr[(int) verb]);
for (int i = 0; i < pointCount[(int) verb]; ++i) {
SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
}
if (SkPath::kConic_Verb == verb) {
SkDebugf("weight = %g", iter.conicWeight());
}
SkDebugf("\n");
} while (SkPath::kDone_Verb != verb);
SkDebugf("\n");
};
SkPath path;
path.quadTo(10, 20, 30, 40);
SkPath::Iter iter(path, false);
debugster("quad open", iter);
SkPath path2;
path2.conicTo(1, 2, 3, 4, .5f);
iter.setPath(path2, true);
debugster("conic closed", iter);
}
#StdOut
quad open:
kMove_Verb {0, 0},
kQuad_Verb {0, 0}, {10, 20}, {30, 40},
kDone_Verb
conic closed:
kMove_Verb {0, 0},
kConic_Verb {0, 0}, {1, 2}, {3, 4}, weight = 0.5
kLine_Verb {3, 4}, {0, 0},
kClose_Verb {0, 0},
kDone_Verb
##
##
#SeeAlso Iter(const SkPath& path, bool forceClose)
##
#Method Verb next(SkPoint pts[4], bool doConsumeDegenerates = true, bool exact = false)
#Line # returns next Verb ##
#Populate
#Example
#Description
skip degenerate skips the first in a kMove_Verb pair, the kMove_Verb
followed by the kClose_Verb, the zero length Line and the very small Line.
skip degenerate if exact skips the same as skip degenerate, but shows
the very small Line.
skip none shows all of the Verbs and Points in Path.
##
void draw(SkCanvas* canvas) {
auto debugster = [](const char* prefix, const SkPath& path, bool degen, bool exact) -> void {
SkPath::Iter iter(path, false);
SkDebugf("%s:\n", prefix);
const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
const int pointCount[] = { 1 , 2 , 3 , 3 , 4 , 1 , 0 };
SkPath::Verb verb;
do {
SkPoint points[4];
verb = iter.next(points, degen, exact);
SkDebugf("k%s_Verb ", verbStr[(int) verb]);
for (int i = 0; i < pointCount[(int) verb]; ++i) {
SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);
}
SkDebugf("\n");
} while (SkPath::kDone_Verb != verb);
SkDebugf("\n");
};
SkPath path;
path.moveTo(10, 10);
path.moveTo(20, 20);
path.quadTo(10, 20, 30, 40);
path.moveTo(1, 1);
path.close();
path.moveTo(30, 30);
path.lineTo(30, 30);
path.moveTo(30, 30);
path.lineTo(30.00001f, 30);
debugster("skip degenerate", path, true, false);
debugster("skip degenerate if exact", path, true, true);
debugster("skip none", path, false, false);
}
#StdOut
skip degenerate:
kMove_Verb {20, 20},
kQuad_Verb {20, 20}, {10, 20}, {30, 40},
kDone_Verb
skip degenerate if exact:
kMove_Verb {20, 20},
kQuad_Verb {20, 20}, {10, 20}, {30, 40},
kMove_Verb {30, 30},
kLine_Verb {30, 30}, {30.00001, 30},
kDone_Verb
skip none:
kMove_Verb {10, 10},
kMove_Verb {20, 20},
kQuad_Verb {20, 20}, {10, 20}, {30, 40},
kMove_Verb {1, 1},
kClose_Verb {1, 1},
kMove_Verb {30, 30},
kLine_Verb {30, 30}, {30, 30},
kMove_Verb {30, 30},
kLine_Verb {30, 30}, {30.00001, 30},
kDone_Verb
##
##
#SeeAlso Verb IsLineDegenerate IsCubicDegenerate IsQuadDegenerate
##
#Method SkScalar conicWeight() const
#Line # returns Conic_Weight ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path;
path.conicTo(1, 2, 3, 4, .5f);
SkPath::Iter iter(path, false);
SkPoint p[4];
SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,
p[2].fX, p[2].fY);
SkDebugf("conic weight: %g\n", iter.conicWeight());
}
#StdOut
first verb is move
next verb is conic
conic points: {0,0}, {1,2}, {3,4}
conic weight: 0.5
##
##
#SeeAlso Conic_Weight
##
#Method bool isCloseLine() const
#Line # returns if Line was generated by kClose_Verb ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path;
path.moveTo(6, 7);
path.conicTo(1, 2, 3, 4, .5f);
path.close();
SkPath::Iter iter(path, false);
SkPoint p[4];
SkDebugf("1st verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
SkDebugf("moveTo point: {%g,%g}\n", p[0].fX, p[0].fY);
SkDebugf("2nd verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
SkDebugf("3rd verb is " "%s" "line\n", SkPath::kLine_Verb == iter.next(p) ? "" : "not ");
SkDebugf("line points: {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
SkDebugf("line " "%s" "generated by close\n", iter.isCloseLine() ? "" : "not ");
SkDebugf("4th verb is " "%s" "close\n", SkPath::kClose_Verb == iter.next(p) ? "" : "not ");
}
#StdOut
1st verb is move
moveTo point: {6,7}
2nd verb is conic
3rd verb is line
line points: {3,4}, {6,7}
line generated by close
4th verb is close
##
##
#SeeAlso close()
##
#Method bool isClosedContour() const
#Line # returns if Contour has kClose_Verb ##
#Populate
#Example
void draw(SkCanvas* canvas) {
for (bool forceClose : { false, true } ) {
SkPath path;
path.conicTo(1, 2, 3, 4, .5f);
SkPath::Iter iter(path, forceClose);
SkDebugf("without close(), forceClose is %s: isClosedContour returns %s\n",
forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");
path.close();
iter.setPath(path, forceClose);
SkDebugf("with close(), forceClose is %s: isClosedContour returns %s\n",
forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");
}
}
#StdOut
without close(), forceClose is false: isClosedContour returns false
with close(), forceClose is false: isClosedContour returns true
without close(), forceClose is true : isClosedContour returns true
with close(), forceClose is true : isClosedContour returns true
##
##
#SeeAlso Iter(const SkPath& path, bool forceClose)
##
#Class Iter ##
#Class RawIter
#Line # raw data iterator ##
#Code
#Populate
##
Iterates through Verb_Array, and associated Point_Array and Conic_Weight.
Verb_Array, Point_Array, and Conic_Weight are returned unaltered.
#Method RawIter()
#Line # constructs empty Path iterator ##
#Populate
#NoExample
##
##
#Method RawIter(const SkPath& path)
#Line # constructs with Path to iterate over ##
#Populate
#NoExample
##
##
#Method void setPath(const SkPath& path)
#Line # sets Path to iterate over ##
#Populate
#NoExample
##
##
#Method Verb next(SkPoint pts[4])
#Line # returns next Verb and associated Points ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path;
path.moveTo(50, 60);
path.quadTo(10, 20, 30, 40);
path.close();
path.lineTo(30, 30);
path.conicTo(1, 2, 3, 4, .5f);
path.cubicTo(-1, -2, -3, -4, -5, -6);
SkPath::RawIter iter(path);
const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
const int pointCount[] = { 1 , 2 , 3 , 3 , 4 , 1 , 0 };
SkPath::Verb verb;
do {
SkPoint points[4];
verb = iter.next(points);
SkDebugf("k%s_Verb ", verbStr[(int) verb]);
for (int i = 0; i < pointCount[(int) verb]; ++i) {
SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);
}
if (SkPath::kConic_Verb == verb) {
SkDebugf("weight = %g", iter.conicWeight());
}
SkDebugf("\n");
} while (SkPath::kDone_Verb != verb);
}
#StdOut
kMove_Verb {50, 60},
kQuad_Verb {50, 60}, {10, 20}, {30, 40},
kClose_Verb {50, 60},
kMove_Verb {50, 60},
kLine_Verb {50, 60}, {30, 30},
kConic_Verb {30, 30}, {1, 2}, {3, 4}, weight = 0.5
kCubic_Verb {3, 4}, {-1, -2}, {-3, -4}, {-5, -6},
kDone_Verb
##
##
#SeeAlso peek()
##
#Method Verb peek() const
#Line # returns next Verb ##
#Populate
#Example
SkPath path;
path.quadTo(10, 20, 30, 40);
path.conicTo(1, 2, 3, 4, .5f);
path.cubicTo(1, 2, 3, 4, .5, 6);
SkPath::RawIter iter(path);
SkPath::Verb verb, peek = iter.peek();
const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
do {
SkPoint points[4];
verb = iter.next(points);
SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);
peek = iter.peek();
} while (SkPath::kDone_Verb != verb);
SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);
#StdOut
#Volatile
peek Move == verb Move
peek Quad == verb Quad
peek Conic == verb Conic
peek Cubic == verb Cubic
peek Done == verb Done
peek Done == verb Done
##
##
#Bug 6832
# StdOut is not really volatile, it just produces the wrong result.
# A simple fix changes the output of hairlines and needs to be
# investigated to see if the change is correct or not.
# see change 21340 (abandoned for now)
#SeeAlso next
##
#Method SkScalar conicWeight() const
#Line # returns Conic_Weight ##
#Populate
#Example
void draw(SkCanvas* canvas) {
SkPath path;
path.conicTo(1, 2, 3, 4, .5f);
SkPath::RawIter iter(path);
SkPoint p[4];
SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,
p[2].fX, p[2].fY);
SkDebugf("conic weight: %g\n", iter.conicWeight());
}
#StdOut
first verb is move
next verb is conic
conic points: {0,0}, {1,2}, {3,4}
conic weight: 0.5
##
##
#SeeAlso Conic_Weight
##
#Class RawIter ##
#Class SkPath ##
#Topic Path ##