| /* |
| * Copyright 2025 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| #include "src/core/SkPathRawShapes.h" |
| |
| #include "include/core/SkPathTypes.h" |
| #include "include/core/SkRRect.h" |
| #include "include/private/base/SkAssert.h" |
| #include "src/core/SkPathMakers.h" |
| |
| const SkPathFillType kDefFillType = SkPathFillType::kWinding; |
| |
| const SkPathVerb gRectVerbs[] = { |
| SkPathVerb::kMove, |
| SkPathVerb::kLine, |
| SkPathVerb::kLine, |
| SkPathVerb::kLine, |
| SkPathVerb::kClose |
| }; |
| |
| const uint8_t gRectSegMask = kLine_SkPathSegmentMask; |
| |
| static void set_as_rect(SkPathRaw* raw, SkSpan<SkPoint> storage, |
| const SkRect& r, SkPathDirection dir, unsigned index) { |
| SkASSERT(storage.size() >= 4); |
| |
| raw->fPoints = { storage.data(), 4 }; |
| raw->fVerbs = gRectVerbs; |
| raw->fConics = {}; |
| raw->fBounds = r; |
| raw->fFillType = kDefFillType; |
| raw->fConvexity = SkPathDirection_ToConvexity(dir); |
| raw->fSegmentMask = gRectSegMask; |
| |
| SkPath_RectPointIterator iter(r, dir, index); |
| |
| storage[0] = iter.current(); |
| storage[1] = iter.next(); |
| storage[2] = iter.next(); |
| storage[3] = iter.next(); |
| } |
| |
| ////////////////// |
| |
| const SkPathVerb gOvalVerbs[] = { |
| SkPathVerb::kMove, |
| SkPathVerb::kConic, |
| SkPathVerb::kConic, |
| SkPathVerb::kConic, |
| SkPathVerb::kConic, |
| SkPathVerb::kClose |
| }; |
| |
| const uint8_t gOvalSegMask = kConic_SkPathSegmentMask; |
| |
| const float gFourQuarterCircleConics[] = { |
| SK_ScalarRoot2Over2, |
| SK_ScalarRoot2Over2, |
| SK_ScalarRoot2Over2, |
| SK_ScalarRoot2Over2, |
| }; |
| |
| static void set_as_oval(SkPathRaw* raw, SkSpan<SkPoint> storage, |
| const SkRect& r, SkPathDirection dir, unsigned index) { |
| SkASSERT(storage.size() >= 9); |
| |
| raw->fPoints = { storage.data(), 9 }; |
| raw->fVerbs = gOvalVerbs; |
| raw->fConics = gFourQuarterCircleConics; |
| raw->fBounds = r; |
| raw->fFillType = kDefFillType; |
| raw->fConvexity = SkPathDirection_ToConvexity(dir); |
| raw->fSegmentMask = gOvalSegMask; |
| |
| SkPath_OvalPointIterator ovalIter(r, dir, index); |
| SkPath_RectPointIterator rectIter(r, dir, index + (dir == SkPathDirection::kCW ? 0 : 1)); |
| |
| storage[0] = ovalIter.current(); |
| for (unsigned i = 0; i < 4; ++i) { |
| storage[i*2 + 1] = rectIter.next(); |
| storage[i*2 + 2] = ovalIter.next(); |
| } |
| } |
| |
| ///////////////////////////// |
| |
| const SkPathVerb gRRectVerbs_LineStart[] = { |
| SkPathVerb::kMove, |
| SkPathVerb::kLine, SkPathVerb::kConic, |
| SkPathVerb::kLine, SkPathVerb::kConic, |
| SkPathVerb::kLine, SkPathVerb::kConic, |
| SkPathVerb::kLine, SkPathVerb::kConic, |
| SkPathVerb::kClose |
| }; |
| |
| const SkPathVerb gRRectVerbs_ConicStart[] = { |
| SkPathVerb::kMove, |
| SkPathVerb::kConic, SkPathVerb::kLine, |
| SkPathVerb::kConic, SkPathVerb::kLine, |
| SkPathVerb::kConic, SkPathVerb::kLine, |
| SkPathVerb::kConic, // we can skip the last line |
| SkPathVerb::kClose |
| }; |
| |
| const uint8_t gRRectSegMask = kLine_SkPathSegmentMask | kConic_SkPathSegmentMask; |
| |
| static void set_as_rrect(SkPathRaw* raw, SkSpan<SkPoint> storage, |
| const SkRRect& rrect, SkPathDirection dir, unsigned index) { |
| // we start with a conic on odd indices when moving CW vs. even indices when moving CCW |
| const bool startsWithConic = ((index & 1) == (dir == SkPathDirection::kCW)); |
| // if we start with a conic, we end with a line, which we can skip (relying on close()) |
| const size_t npoints = 13 - startsWithConic; |
| const SkRect& bounds = rrect.getBounds(); |
| |
| SkASSERT(storage.size() >= npoints); |
| |
| raw->fPoints = { storage.data(), npoints }; |
| if (startsWithConic) { |
| raw->fVerbs = gRRectVerbs_ConicStart; |
| } else { |
| raw->fVerbs = gRRectVerbs_LineStart; |
| } |
| raw->fConics = gFourQuarterCircleConics; |
| raw->fBounds = bounds; |
| raw->fFillType = kDefFillType; |
| raw->fConvexity = SkPathDirection_ToConvexity(dir); |
| raw->fSegmentMask = gRRectSegMask; |
| |
| SkPath_RRectPointIterator rrectIter(rrect, dir, index); |
| // Corner iterator indices follow the collapsed radii model, |
| // adjusted such that the start pt is "behind" the radii start pt. |
| const unsigned rectStartIndex = index / 2 + (dir == SkPathDirection::kCW ? 0 : 1); |
| SkPath_RectPointIterator rectIter(bounds, dir, rectStartIndex); |
| |
| storage[0] = rrectIter.current(); |
| if (startsWithConic) { |
| for (unsigned i = 0; i < 3; ++i) { |
| // conic points |
| storage[i*3 + 1] = rectIter.next(); |
| storage[i*3 + 2] = rrectIter.next(); |
| // line point |
| storage[i*3 + 3] = rrectIter.next(); |
| } |
| // last conic points |
| storage[10] = rectIter.next(); |
| storage[11] = rrectIter.next(); |
| // the final line is accomplished by close() |
| } else { |
| for (unsigned i = 0; i < 4; ++i) { |
| // line point |
| storage[i*3 + 1] = rrectIter.next(); |
| // conic points |
| storage[i*3 + 2] = rectIter.next(); |
| storage[i*3 + 3] = rrectIter.next(); |
| } |
| } |
| // close |
| } |
| |
| ///////////////////////////// |
| |
| SkPathRawShapes::Rect::Rect(const SkRect& r, SkPathDirection dir, unsigned index) { |
| set_as_rect(this, fStorage, r, dir, index); |
| } |
| |
| SkPathRawShapes::Oval::Oval(const SkRect& r, SkPathDirection dir, unsigned index) { |
| set_as_oval(this, fStorage, r, dir, index); |
| } |
| |
| SkPathRawShapes::RRect::RRect(const SkRRect& rrect, SkPathDirection dir, unsigned index) { |
| const SkRect& bounds = rrect.getBounds(); |
| |
| if (rrect.isRect() || rrect.isEmpty()) { |
| // degenerate(rect) => radii points are collapsing |
| set_as_rect(this, fStorage, bounds, dir, (index + 1) / 2); |
| } else if (rrect.isOval()) { |
| // degenerate(oval) => line points are collapsing |
| set_as_oval(this, fStorage, bounds, dir, index / 2); |
| } else { |
| set_as_rrect(this, fStorage, rrect, dir, index); |
| } |
| } |
| |
| ///////////////////////////// |
| |
| const SkPathVerb gTriangle_Verbs[] = { |
| SkPathVerb::kMove, |
| SkPathVerb::kLine, |
| SkPathVerb::kLine, |
| SkPathVerb::kClose |
| }; |
| |
| static SkPathConvexity tri_to_convexity(SkSpan<const SkPoint> pts) { |
| SkVector u = pts[1] - pts[0], |
| v = pts[2] - pts[1]; |
| float cross = u.fX * v.fY - u.fY * v.fX; |
| |
| return cross > 0 ? SkPathConvexity::kConvex_CW |
| : (cross < 0) ? SkPathConvexity::kConvex_CCW |
| : SkPathConvexity::kConvex_Degenerate; |
| } |
| |
| SkPathRawShapes::Triangle::Triangle(SkSpan<const SkPoint> threePoints, const SkRect& bounds) |
| : SkPathRaw{threePoints, gTriangle_Verbs, {}, bounds, |
| SkPathFillType::kDefault, tri_to_convexity(threePoints), kLine_SkPathSegmentMask} |
| { |
| SkASSERT(threePoints.size() == 3); |
| SkASSERT(SkRect::Bounds(threePoints).value() == bounds); |
| } |