1 /*
2 * Copyright 2023 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkMatrix.h"
9 #include "include/core/SkPaint.h"
10 #include "include/core/SkPath.h"
11 #include "include/core/SkPathEffect.h"
12 #include "include/core/SkPathTypes.h"
13 #include "include/core/SkPathUtils.h"
14 #include "include/core/SkPixmap.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkRRect.h"
17 #include "include/core/SkRect.h"
18 #include "include/core/SkScalar.h"
19 #include "include/core/SkStrokeRec.h"
20 #include "include/private/base/SkAssert.h"
21 #include "include/private/base/SkCPUTypes.h"
22 #include "include/private/base/SkDebug.h"
23 #include "include/private/base/SkFloatingPoint.h"
24 #include "include/private/base/SkTemplates.h"
25 #include "src/base/SkTLazy.h"
26 #include "src/base/SkZip.h"
27 #include "src/core/SkAutoBlitterChoose.h"
28 #include "src/core/SkBlendModePriv.h"
29 #include "src/core/SkBlitter_A8.h"
30 #include "src/core/SkDevice.h"
31 #include "src/core/SkDrawBase.h"
32 #include "src/core/SkDrawProcs.h"
33 #include "src/core/SkMask.h"
34 #include "src/core/SkMaskFilterBase.h"
35 #include "src/core/SkPathEffectBase.h"
36 #include "src/core/SkPathPriv.h"
37 #include "src/core/SkRasterClip.h"
38 #include "src/core/SkRectPriv.h"
39 #include "src/core/SkScan.h"
40 #include <algorithm>
41 #include <cstddef>
42 #include <optional>
43
44 class SkBitmap;
45 class SkBlitter;
46 class SkGlyph;
47 class SkMaskFilter;
48
49 using namespace skia_private;
50
51 ///////////////////////////////////////////////////////////////////////////////
52
SkDrawBase()53 SkDrawBase::SkDrawBase() {}
54
computeConservativeLocalClipBounds(SkRect * localBounds) const55 bool SkDrawBase::computeConservativeLocalClipBounds(SkRect* localBounds) const {
56 if (fRC->isEmpty()) {
57 return false;
58 }
59
60 SkMatrix inverse;
61 if (!fCTM->invert(&inverse)) {
62 return false;
63 }
64
65 SkIRect devBounds = fRC->getBounds();
66 // outset to have slop for antialasing and hairlines
67 devBounds.outset(1, 1);
68 inverse.mapRect(localBounds, SkRect::Make(devBounds));
69 return true;
70 }
71
72 ///////////////////////////////////////////////////////////////////////////////
73
drawPaint(const SkPaint & paint) const74 void SkDrawBase::drawPaint(const SkPaint& paint) const {
75 SkDEBUGCODE(this->validate();)
76
77 if (fRC->isEmpty()) {
78 return;
79 }
80
81 SkIRect devRect;
82 devRect.setWH(fDst.width(), fDst.height());
83
84 SkAutoBlitterChoose blitter(*this, nullptr, paint);
85 SkScan::FillIRect(devRect, *fRC, blitter.get());
86 }
87
88 ///////////////////////////////////////////////////////////////////////////////
89
compute_stroke_size(const SkPaint & paint,const SkMatrix & matrix)90 static inline SkPoint compute_stroke_size(const SkPaint& paint, const SkMatrix& matrix) {
91 SkASSERT(matrix.rectStaysRect());
92 SkASSERT(SkPaint::kFill_Style != paint.getStyle());
93
94 SkVector size;
95 SkPoint pt = { paint.getStrokeWidth(), paint.getStrokeWidth() };
96 matrix.mapVectors(&size, &pt, 1);
97 return SkPoint::Make(SkScalarAbs(size.fX), SkScalarAbs(size.fY));
98 }
99
easy_rect_join(const SkRect & rect,const SkPaint & paint,const SkMatrix & matrix,SkPoint * strokeSize)100 static bool easy_rect_join(const SkRect& rect, const SkPaint& paint, const SkMatrix& matrix,
101 SkPoint* strokeSize) {
102 if (rect.isEmpty() || SkPaint::kMiter_Join != paint.getStrokeJoin() ||
103 paint.getStrokeMiter() < SK_ScalarSqrt2) {
104 return false;
105 }
106
107 *strokeSize = compute_stroke_size(paint, matrix);
108 return true;
109 }
110
ComputeRectType(const SkRect & rect,const SkPaint & paint,const SkMatrix & matrix,SkPoint * strokeSize)111 SkDrawBase::RectType SkDrawBase::ComputeRectType(const SkRect& rect,
112 const SkPaint& paint,
113 const SkMatrix& matrix,
114 SkPoint* strokeSize) {
115 RectType rtype;
116 const SkScalar width = paint.getStrokeWidth();
117 const bool zeroWidth = (0 == width);
118 SkPaint::Style style = paint.getStyle();
119
120 if ((SkPaint::kStrokeAndFill_Style == style) && zeroWidth) {
121 style = SkPaint::kFill_Style;
122 }
123
124 if (paint.getPathEffect() || paint.getMaskFilter() ||
125 !matrix.rectStaysRect() || SkPaint::kStrokeAndFill_Style == style) {
126 rtype = kPath_RectType;
127 } else if (SkPaint::kFill_Style == style) {
128 rtype = kFill_RectType;
129 } else if (zeroWidth) {
130 rtype = kHair_RectType;
131 } else if (easy_rect_join(rect, paint, matrix, strokeSize)) {
132 rtype = kStroke_RectType;
133 } else {
134 rtype = kPath_RectType;
135 }
136 return rtype;
137 }
138
rect_points(const SkRect & r)139 static const SkPoint* rect_points(const SkRect& r) {
140 return reinterpret_cast<const SkPoint*>(&r);
141 }
142
rect_points(SkRect & r)143 static SkPoint* rect_points(SkRect& r) {
144 return reinterpret_cast<SkPoint*>(&r);
145 }
146
draw_rect_as_path(const SkDrawBase & orig,const SkRect & prePaintRect,const SkPaint & paint,const SkMatrix & ctm)147 static void draw_rect_as_path(const SkDrawBase& orig,
148 const SkRect& prePaintRect,
149 const SkPaint& paint,
150 const SkMatrix& ctm) {
151 SkDrawBase draw(orig);
152 draw.fCTM = &ctm;
153 SkPath tmp;
154 tmp.addRect(prePaintRect);
155 tmp.setFillType(SkPathFillType::kWinding);
156 draw.drawPath(tmp, paint, nullptr, true);
157 }
158
drawRect(const SkRect & prePaintRect,const SkPaint & paint,const SkMatrix * paintMatrix,const SkRect * postPaintRect) const159 void SkDrawBase::drawRect(const SkRect& prePaintRect, const SkPaint& paint,
160 const SkMatrix* paintMatrix, const SkRect* postPaintRect) const {
161 SkDEBUGCODE(this->validate();)
162
163 // nothing to draw
164 if (fRC->isEmpty()) {
165 return;
166 }
167
168 SkTCopyOnFirstWrite<SkMatrix> matrix(fCTM);
169 if (paintMatrix) {
170 SkASSERT(postPaintRect);
171 matrix.writable()->preConcat(*paintMatrix);
172 } else {
173 SkASSERT(!postPaintRect);
174 }
175
176 SkPoint strokeSize;
177 RectType rtype = ComputeRectType(prePaintRect, paint, *fCTM, &strokeSize);
178
179 if (kPath_RectType == rtype) {
180 draw_rect_as_path(*this, prePaintRect, paint, *matrix);
181 return;
182 }
183
184 SkRect devRect;
185 const SkRect& paintRect = paintMatrix ? *postPaintRect : prePaintRect;
186 // skip the paintMatrix when transforming the rect by the CTM
187 fCTM->mapPoints(rect_points(devRect), rect_points(paintRect), 2);
188 devRect.sort();
189
190 // look for the quick exit, before we build a blitter
191 SkRect bbox = devRect;
192 if (paint.getStyle() != SkPaint::kFill_Style) {
193 // extra space for hairlines
194 if (paint.getStrokeWidth() == 0) {
195 bbox.outset(1, 1);
196 } else {
197 // For kStroke_RectType, strokeSize is already computed.
198 const SkPoint& ssize = (kStroke_RectType == rtype)
199 ? strokeSize
200 : compute_stroke_size(paint, *fCTM);
201 bbox.outset(SkScalarHalf(ssize.x()), SkScalarHalf(ssize.y()));
202 }
203 }
204 if (SkPathPriv::TooBigForMath(bbox)) {
205 return;
206 }
207
208 if (!SkRectPriv::FitsInFixed(bbox) && rtype != kHair_RectType) {
209 draw_rect_as_path(*this, prePaintRect, paint, *matrix);
210 return;
211 }
212
213 SkIRect ir = bbox.roundOut();
214 if (fRC->quickReject(ir)) {
215 return;
216 }
217
218 SkAutoBlitterChoose blitterStorage(*this, matrix, paint);
219 const SkRasterClip& clip = *fRC;
220 SkBlitter* blitter = blitterStorage.get();
221
222 // we want to "fill" if we are kFill or kStrokeAndFill, since in the latter
223 // case we are also hairline (if we've gotten to here), which devolves to
224 // effectively just kFill
225 switch (rtype) {
226 case kFill_RectType:
227 if (paint.isAntiAlias()) {
228 SkScan::AntiFillRect(devRect, clip, blitter);
229 } else {
230 SkScan::FillRect(devRect, clip, blitter);
231 }
232 break;
233 case kStroke_RectType:
234 if (paint.isAntiAlias()) {
235 SkScan::AntiFrameRect(devRect, strokeSize, clip, blitter);
236 } else {
237 SkScan::FrameRect(devRect, strokeSize, clip, blitter);
238 }
239 break;
240 case kHair_RectType:
241 if (paint.isAntiAlias()) {
242 SkScan::AntiHairRect(devRect, clip, blitter);
243 } else {
244 SkScan::HairRect(devRect, clip, blitter);
245 }
246 break;
247 default:
248 SkDEBUGFAIL("bad rtype");
249 }
250 }
251
fast_len(const SkVector & vec)252 static SkScalar fast_len(const SkVector& vec) {
253 SkScalar x = SkScalarAbs(vec.fX);
254 SkScalar y = SkScalarAbs(vec.fY);
255 if (x < y) {
256 using std::swap;
257 swap(x, y);
258 }
259 return x + SkScalarHalf(y);
260 }
261
SkDrawTreatAAStrokeAsHairline(SkScalar strokeWidth,const SkMatrix & matrix,SkScalar * coverage)262 bool SkDrawTreatAAStrokeAsHairline(SkScalar strokeWidth, const SkMatrix& matrix,
263 SkScalar* coverage) {
264 SkASSERT(strokeWidth > 0);
265 // We need to try to fake a thick-stroke with a modulated hairline.
266
267 if (matrix.hasPerspective()) {
268 return false;
269 }
270
271 SkVector src[2], dst[2];
272 src[0].set(strokeWidth, 0);
273 src[1].set(0, strokeWidth);
274 matrix.mapVectors(dst, src, 2);
275 SkScalar len0 = fast_len(dst[0]);
276 SkScalar len1 = fast_len(dst[1]);
277 if (len0 <= SK_Scalar1 && len1 <= SK_Scalar1) {
278 if (coverage) {
279 *coverage = SkScalarAve(len0, len1);
280 }
281 return true;
282 }
283 return false;
284 }
285
drawRRect(const SkRRect & rrect,const SkPaint & paint) const286 void SkDrawBase::drawRRect(const SkRRect& rrect, const SkPaint& paint) const {
287 SkDEBUGCODE(this->validate());
288
289 if (fRC->isEmpty()) {
290 return;
291 }
292
293 {
294 // TODO: Investigate optimizing these options. They are in the same
295 // order as SkDrawBase::drawPath, which handles each case. It may be
296 // that there is no way to optimize for these using the SkRRect path.
297 SkScalar coverage;
298 if (SkDrawTreatAsHairline(paint, *fCTM, &coverage)) {
299 goto DRAW_PATH;
300 }
301
302 if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
303 goto DRAW_PATH;
304 }
305 }
306
307 if (paint.getMaskFilter()) {
308 // Transform the rrect into device space.
309 SkRRect devRRect;
310 if (rrect.transform(*fCTM, &devRRect)) {
311 SkAutoBlitterChoose blitter(*this, nullptr, paint);
312 if (as_MFB(paint.getMaskFilter())->filterRRect(devRRect, *fCTM, *fRC, blitter.get())) {
313 return; // filterRRect() called the blitter, so we're done
314 }
315 }
316 }
317
318 DRAW_PATH:
319 // Now fall back to the default case of using a path.
320 SkPath path;
321 path.addRRect(rrect);
322 this->drawPath(path, paint, nullptr, true);
323 }
324
drawDevPath(const SkPath & devPath,const SkPaint & paint,bool drawCoverage,SkBlitter * customBlitter,bool doFill) const325 void SkDrawBase::drawDevPath(const SkPath& devPath, const SkPaint& paint, bool drawCoverage,
326 SkBlitter* customBlitter, bool doFill) const {
327 if (SkPathPriv::TooBigForMath(devPath)) {
328 return;
329 }
330 SkBlitter* blitter = nullptr;
331 SkAutoBlitterChoose blitterStorage;
332 if (nullptr == customBlitter) {
333 blitter = blitterStorage.choose(*this, nullptr, paint, drawCoverage);
334 } else {
335 blitter = customBlitter;
336 }
337
338 if (paint.getMaskFilter()) {
339 SkStrokeRec::InitStyle style = doFill ? SkStrokeRec::kFill_InitStyle
340 : SkStrokeRec::kHairline_InitStyle;
341 if (as_MFB(paint.getMaskFilter())->filterPath(devPath, *fCTM, *fRC, blitter, style)) {
342 return; // filterPath() called the blitter, so we're done
343 }
344 }
345
346 void (*proc)(const SkPath&, const SkRasterClip&, SkBlitter*);
347 if (doFill) {
348 if (paint.isAntiAlias()) {
349 proc = SkScan::AntiFillPath;
350 } else {
351 proc = SkScan::FillPath;
352 }
353 } else { // hairline
354 if (paint.isAntiAlias()) {
355 switch (paint.getStrokeCap()) {
356 case SkPaint::kButt_Cap:
357 proc = SkScan::AntiHairPath;
358 break;
359 case SkPaint::kSquare_Cap:
360 proc = SkScan::AntiHairSquarePath;
361 break;
362 case SkPaint::kRound_Cap:
363 proc = SkScan::AntiHairRoundPath;
364 break;
365 }
366 } else {
367 switch (paint.getStrokeCap()) {
368 case SkPaint::kButt_Cap:
369 proc = SkScan::HairPath;
370 break;
371 case SkPaint::kSquare_Cap:
372 proc = SkScan::HairSquarePath;
373 break;
374 case SkPaint::kRound_Cap:
375 proc = SkScan::HairRoundPath;
376 break;
377 }
378 }
379 }
380
381 proc(devPath, *fRC, blitter);
382 }
383
drawPath(const SkPath & origSrcPath,const SkPaint & origPaint,const SkMatrix * prePathMatrix,bool pathIsMutable,bool drawCoverage,SkBlitter * customBlitter) const384 void SkDrawBase::drawPath(const SkPath& origSrcPath, const SkPaint& origPaint,
385 const SkMatrix* prePathMatrix, bool pathIsMutable,
386 bool drawCoverage, SkBlitter* customBlitter) const {
387 SkDEBUGCODE(this->validate();)
388
389 // nothing to draw
390 if (fRC->isEmpty()) {
391 return;
392 }
393
394 SkPath* pathPtr = const_cast<SkPath*>(&origSrcPath);
395 bool doFill = true;
396 SkPath tmpPathStorage;
397 SkPath* tmpPath = &tmpPathStorage;
398 SkTCopyOnFirstWrite<SkMatrix> matrix(fCTM);
399 tmpPath->setIsVolatile(true);
400
401 if (prePathMatrix) {
402 if (origPaint.getPathEffect() || origPaint.getStyle() != SkPaint::kFill_Style) {
403 SkPath* result = pathPtr;
404
405 if (!pathIsMutable) {
406 result = tmpPath;
407 pathIsMutable = true;
408 }
409 pathPtr->transform(*prePathMatrix, result);
410 pathPtr = result;
411 } else {
412 matrix.writable()->preConcat(*prePathMatrix);
413 }
414 }
415
416 SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
417
418 {
419 SkScalar coverage;
420 if (SkDrawTreatAsHairline(origPaint, *matrix, &coverage)) {
421 const auto bm = origPaint.asBlendMode();
422 if (SK_Scalar1 == coverage) {
423 paint.writable()->setStrokeWidth(0);
424 } else if (bm && SkBlendMode_SupportsCoverageAsAlpha(bm.value())) {
425 U8CPU newAlpha;
426 #if 0
427 newAlpha = SkToU8(SkScalarRoundToInt(coverage * origPaint.getAlpha()));
428 #else
429 // this is the old technique, which we preserve for now so
430 // we don't change previous results (testing)
431 // the new way seems fine, its just (a tiny bit) different
432 int scale = (int)(coverage * 256);
433 newAlpha = origPaint.getAlpha() * scale >> 8;
434 #endif
435 SkPaint* writablePaint = paint.writable();
436 writablePaint->setStrokeWidth(0);
437 writablePaint->setAlpha(newAlpha);
438 }
439 }
440 }
441
442 if (paint->getPathEffect() || paint->getStyle() != SkPaint::kFill_Style) {
443 SkRect cullRect;
444 const SkRect* cullRectPtr = nullptr;
445 if (this->computeConservativeLocalClipBounds(&cullRect)) {
446 cullRectPtr = &cullRect;
447 }
448 doFill = skpathutils::FillPathWithPaint(*pathPtr, *paint, tmpPath, cullRectPtr, *fCTM);
449 pathPtr = tmpPath;
450 }
451
452 // avoid possibly allocating a new path in transform if we can
453 SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath;
454
455 // transform the path into device space
456 pathPtr->transform(*matrix, devPathPtr);
457
458 #if defined(SK_BUILD_FOR_FUZZER)
459 if (devPathPtr->countPoints() > 1000) {
460 return;
461 }
462 #endif
463
464 this->drawDevPath(*devPathPtr, *paint, drawCoverage, customBlitter, doFill);
465 }
466
paintMasks(SkZip<const SkGlyph *,SkPoint>,const SkPaint &) const467 void SkDrawBase::paintMasks(SkZip<const SkGlyph*, SkPoint>, const SkPaint&) const {
468 SkASSERT(false);
469 }
drawBitmap(const SkBitmap &,const SkMatrix &,const SkRect *,const SkSamplingOptions &,const SkPaint &) const470 void SkDrawBase::drawBitmap(const SkBitmap&, const SkMatrix&, const SkRect*,
471 const SkSamplingOptions&, const SkPaint&) const {
472 SkASSERT(false);
473 }
474
475 ////////////////////////////////////////////////////////////////////////////////////////////////
476
477 #ifdef SK_DEBUG
478
validate() const479 void SkDrawBase::validate() const {
480 SkASSERT(fCTM != nullptr);
481 SkASSERT(fRC != nullptr);
482
483 const SkIRect& cr = fRC->getBounds();
484 SkIRect br;
485
486 br.setWH(fDst.width(), fDst.height());
487 SkASSERT(cr.isEmpty() || br.contains(cr));
488 }
489
490 #endif
491
492 ////////////////////////////////////////////////////////////////////////////////////////////////
493
ComputeMaskBounds(const SkRect & devPathBounds,const SkIRect & clipBounds,const SkMaskFilter * filter,const SkMatrix * filterMatrix,SkIRect * bounds)494 bool SkDrawBase::ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect& clipBounds,
495 const SkMaskFilter* filter, const SkMatrix* filterMatrix,
496 SkIRect* bounds) {
497 // init our bounds from the path
498 *bounds = devPathBounds.makeOutset(SK_ScalarHalf, SK_ScalarHalf).roundOut();
499
500 SkIPoint margin = SkIPoint::Make(0, 0);
501 if (filter) {
502 SkASSERT(filterMatrix);
503
504 SkMask srcM(nullptr, *bounds, 0, SkMask::kA8_Format);
505 SkMaskBuilder dstM;
506 if (!as_MFB(filter)->filterMask(&dstM, srcM, *filterMatrix, &margin)) {
507 return false;
508 }
509 }
510
511 // trim the bounds to reflect the clip (plus whatever slop the filter needs)
512 // Ugh. Guard against gigantic margins from wacky filters. Without this
513 // check we can request arbitrary amounts of slop beyond our visible
514 // clip, and bring down the renderer (at least on finite RAM machines
515 // like handsets, etc.). Need to balance this invented value between
516 // quality of large filters like blurs, and the corresponding memory
517 // requests.
518 static constexpr int kMaxMargin = 128;
519 if (!bounds->intersect(clipBounds.makeOutset(std::min(margin.fX, kMaxMargin),
520 std::min(margin.fY, kMaxMargin)))) {
521 return false;
522 }
523
524 return true;
525 }
526
draw_into_mask(const SkMask & mask,const SkPath & devPath,SkStrokeRec::InitStyle style)527 static void draw_into_mask(const SkMask& mask, const SkPath& devPath,
528 SkStrokeRec::InitStyle style) {
529 SkDrawBase draw;
530 draw.fBlitterChooser = SkA8Blitter_Choose;
531 if (!draw.fDst.reset(mask)) {
532 return;
533 }
534
535 SkRasterClip clip;
536 SkMatrix matrix;
537 SkPaint paint;
538
539 clip.setRect(SkIRect::MakeWH(mask.fBounds.width(), mask.fBounds.height()));
540 matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft),
541 -SkIntToScalar(mask.fBounds.fTop));
542
543 draw.fRC = &clip;
544 draw.fCTM = &matrix;
545 paint.setAntiAlias(true);
546 switch (style) {
547 case SkStrokeRec::kHairline_InitStyle:
548 SkASSERT(!paint.getStrokeWidth());
549 paint.setStyle(SkPaint::kStroke_Style);
550 break;
551 case SkStrokeRec::kFill_InitStyle:
552 SkASSERT(paint.getStyle() == SkPaint::kFill_Style);
553 break;
554
555 }
556 draw.drawPath(devPath, paint);
557 }
558
DrawToMask(const SkPath & devPath,const SkIRect & clipBounds,const SkMaskFilter * filter,const SkMatrix * filterMatrix,SkMaskBuilder * dst,SkMaskBuilder::CreateMode mode,SkStrokeRec::InitStyle style)559 bool SkDrawBase::DrawToMask(const SkPath& devPath, const SkIRect& clipBounds,
560 const SkMaskFilter* filter, const SkMatrix* filterMatrix,
561 SkMaskBuilder* dst, SkMaskBuilder::CreateMode mode,
562 SkStrokeRec::InitStyle style) {
563 if (devPath.isEmpty()) {
564 return false;
565 }
566
567 if (SkMaskBuilder::kJustRenderImage_CreateMode != mode) {
568 // By using infinite bounds for inverse fills, ComputeMaskBounds is able to clip it to
569 // 'clipBounds' outset by whatever extra margin the mask filter requires.
570 static const SkRect kInverseBounds = { SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity,
571 SK_ScalarInfinity, SK_ScalarInfinity};
572 SkRect pathBounds = devPath.isInverseFillType() ? kInverseBounds
573 : devPath.getBounds();
574 if (!ComputeMaskBounds(pathBounds, clipBounds, filter,
575 filterMatrix, &dst->bounds()))
576 return false;
577 }
578
579 if (SkMaskBuilder::kComputeBoundsAndRenderImage_CreateMode == mode) {
580 dst->format() = SkMask::kA8_Format;
581 dst->rowBytes() = dst->fBounds.width();
582 size_t size = dst->computeImageSize();
583 if (0 == size) {
584 // we're too big to allocate the mask, abort
585 return false;
586 }
587 dst->image() = SkMaskBuilder::AllocImage(size, SkMaskBuilder::kZeroInit_Alloc);
588 }
589
590 if (SkMaskBuilder::kJustComputeBounds_CreateMode != mode) {
591 draw_into_mask(*dst, devPath, style);
592 }
593
594 return true;
595 }
596
drawDevicePoints(SkCanvas::PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint,SkDevice * device) const597 void SkDrawBase::drawDevicePoints(SkCanvas::PointMode mode, size_t count,
598 const SkPoint pts[], const SkPaint& paint,
599 SkDevice* device) const {
600 // if we're in lines mode, force count to be even
601 if (SkCanvas::kLines_PointMode == mode) {
602 count &= ~(size_t)1;
603 }
604
605 SkASSERT(pts != nullptr);
606 SkDEBUGCODE(this->validate();)
607
608 // nothing to draw
609 if (!count || fRC->isEmpty()) {
610 return;
611 }
612
613 // needed?
614 if (!SkIsFinite(&pts[0].fX, count * 2)) {
615 return;
616 }
617
618 switch (mode) {
619 case SkCanvas::kPoints_PointMode: {
620 // temporarily mark the paint as filling.
621 SkPaint newPaint(paint);
622 newPaint.setStyle(SkPaint::kFill_Style);
623
624 SkScalar width = newPaint.getStrokeWidth();
625 SkScalar radius = SkScalarHalf(width);
626
627 if (newPaint.getStrokeCap() == SkPaint::kRound_Cap) {
628 if (device) {
629 for (size_t i = 0; i < count; ++i) {
630 SkRect r = SkRect::MakeLTRB(pts[i].fX - radius, pts[i].fY - radius,
631 pts[i].fX + radius, pts[i].fY + radius);
632 device->drawOval(r, newPaint);
633 }
634 } else {
635 SkPath path;
636 SkMatrix preMatrix;
637
638 path.addCircle(0, 0, radius);
639 for (size_t i = 0; i < count; i++) {
640 preMatrix.setTranslate(pts[i].fX, pts[i].fY);
641 // pass true for the last point, since we can modify
642 // then path then
643 path.setIsVolatile((count-1) == i);
644 this->drawPath(path, newPaint, &preMatrix, (count-1) == i);
645 }
646 }
647 } else {
648 SkRect r;
649
650 for (size_t i = 0; i < count; i++) {
651 r.fLeft = pts[i].fX - radius;
652 r.fTop = pts[i].fY - radius;
653 r.fRight = r.fLeft + width;
654 r.fBottom = r.fTop + width;
655 if (device) {
656 device->drawRect(r, newPaint);
657 } else {
658 this->drawRect(r, newPaint);
659 }
660 }
661 }
662 break;
663 }
664 case SkCanvas::kLines_PointMode:
665 if (2 == count && paint.getPathEffect()) {
666 // most likely a dashed line - see if it is one of the ones
667 // we can accelerate
668 SkStrokeRec stroke(paint);
669 SkPathEffectBase::PointData pointData;
670
671 SkPath path = SkPath::Line(pts[0], pts[1]);
672
673 SkRect cullRect = SkRect::Make(fRC->getBounds());
674
675 if (as_PEB(paint.getPathEffect())->asPoints(&pointData, path, stroke, *fCTM,
676 &cullRect)) {
677 // 'asPoints' managed to find some fast path
678
679 SkPaint newP(paint);
680 newP.setPathEffect(nullptr);
681 newP.setStyle(SkPaint::kFill_Style);
682
683 if (!pointData.fFirst.isEmpty()) {
684 if (device) {
685 device->drawPath(pointData.fFirst, newP);
686 } else {
687 this->drawPath(pointData.fFirst, newP);
688 }
689 }
690
691 if (!pointData.fLast.isEmpty()) {
692 if (device) {
693 device->drawPath(pointData.fLast, newP);
694 } else {
695 this->drawPath(pointData.fLast, newP);
696 }
697 }
698
699 if (pointData.fSize.fX == pointData.fSize.fY) {
700 // The rest of the dashed line can just be drawn as points
701 SkASSERT(pointData.fSize.fX == SkScalarHalf(newP.getStrokeWidth()));
702
703 if (SkPathEffectBase::PointData::kCircles_PointFlag & pointData.fFlags) {
704 newP.setStrokeCap(SkPaint::kRound_Cap);
705 } else {
706 newP.setStrokeCap(SkPaint::kButt_Cap);
707 }
708
709 if (device) {
710 device->drawPoints(SkCanvas::kPoints_PointMode,
711 pointData.fNumPoints,
712 pointData.fPoints,
713 newP);
714 } else {
715 this->drawDevicePoints(SkCanvas::kPoints_PointMode,
716 pointData.fNumPoints,
717 pointData.fPoints,
718 newP,
719 device);
720 }
721 break;
722 } else {
723 // The rest of the dashed line must be drawn as rects
724 SkASSERT(!(SkPathEffectBase::PointData::kCircles_PointFlag &
725 pointData.fFlags));
726
727 SkRect r;
728
729 for (int i = 0; i < pointData.fNumPoints; ++i) {
730 r.setLTRB(pointData.fPoints[i].fX - pointData.fSize.fX,
731 pointData.fPoints[i].fY - pointData.fSize.fY,
732 pointData.fPoints[i].fX + pointData.fSize.fX,
733 pointData.fPoints[i].fY + pointData.fSize.fY);
734 if (device) {
735 device->drawRect(r, newP);
736 } else {
737 this->drawRect(r, newP);
738 }
739 }
740 }
741
742 break;
743 }
744 }
745 [[fallthrough]]; // couldn't take fast path
746 case SkCanvas::kPolygon_PointMode: {
747 count -= 1;
748 SkPath path;
749 SkPaint p(paint);
750 p.setStyle(SkPaint::kStroke_Style);
751 size_t inc = (SkCanvas::kLines_PointMode == mode) ? 2 : 1;
752 path.setIsVolatile(true);
753 for (size_t i = 0; i < count; i += inc) {
754 path.moveTo(pts[i]);
755 path.lineTo(pts[i+1]);
756 if (device) {
757 device->drawPath(path, p, true);
758 } else {
759 this->drawPath(path, p, nullptr, true);
760 }
761 path.rewind();
762 }
763 break;
764 }
765 }
766 }
767