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