1/*
2 * Copyright 2006 The Android Open Source Project
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 "src/core/SkDraw.h"
9
10#include "include/core/SkCanvas.h"
11#include "include/core/SkMatrix.h"
12#include "include/core/SkPaint.h"
13#include "include/core/SkPathEffect.h"
14#include "include/core/SkRRect.h"
15#include "include/core/SkShader.h"
16#include "include/core/SkString.h"
17#include "include/core/SkStrokeRec.h"
18#include "include/private/SkColorData.h"
19#include "include/private/SkMacros.h"
20#include "include/private/SkTemplates.h"
21#include "include/private/SkTo.h"
22#include "src/core/SkArenaAlloc.h"
23#include "src/core/SkAutoBlitterChoose.h"
24#include "src/core/SkBlendModePriv.h"
25#include "src/core/SkBlitter.h"
26#include "src/core/SkDevice.h"
27#include "src/core/SkDrawProcs.h"
28#include "src/core/SkMaskFilterBase.h"
29#include "src/core/SkMatrixUtils.h"
30#include "src/core/SkPathPriv.h"
31#include "src/core/SkRasterClip.h"
32#include "src/core/SkRectPriv.h"
33#include "src/core/SkScan.h"
34#include "src/core/SkStroke.h"
35#include "src/core/SkTLazy.h"
36#include "src/core/SkUtils.h"
37
38#include <utility>
39
40static SkPaint make_paint_with_image(
41 const SkPaint& origPaint, const SkBitmap& bitmap, SkMatrix* matrix = nullptr) {
42 SkPaint paint(origPaint);
43 paint.setShader(SkMakeBitmapShaderForPaint(origPaint, bitmap, SkTileMode::kClamp,
44 SkTileMode::kClamp, matrix,
45 kNever_SkCopyPixelsMode));
46 return paint;
47}
48
49///////////////////////////////////////////////////////////////////////////////
50
51SkDraw::SkDraw() {}
52
53bool SkDraw::computeConservativeLocalClipBounds(SkRect* localBounds) const {
54 if (fRC->isEmpty()) {
55 return false;
56 }
57
58 SkMatrix inverse;
59 if (!fMatrix->invert(&inverse)) {
60 return false;
61 }
62
63 SkIRect devBounds = fRC->getBounds();
64 // outset to have slop for antialasing and hairlines
65 devBounds.outset(1, 1);
66 inverse.mapRect(localBounds, SkRect::Make(devBounds));
67 return true;
68}
69
70///////////////////////////////////////////////////////////////////////////////
71
72void SkDraw::drawPaint(const SkPaint& paint) const {
73 SkDEBUGCODE(this->validate();)
74
75 if (fRC->isEmpty()) {
76 return;
77 }
78
79 SkIRect devRect;
80 devRect.setWH(fDst.width(), fDst.height());
81
82 SkAutoBlitterChoose blitter(*this, nullptr, paint);
83 SkScan::FillIRect(devRect, *fRC, blitter.get());
84}
85
86///////////////////////////////////////////////////////////////////////////////
87
88struct PtProcRec {
89 SkCanvas::PointMode fMode;
90 const SkPaint* fPaint;
91 const SkRegion* fClip;
92 const SkRasterClip* fRC;
93
94 // computed values
95 SkRect fClipBounds;
96 SkScalar fRadius;
97
98 typedef void (*Proc)(const PtProcRec&, const SkPoint devPts[], int count,
99 SkBlitter*);
100
101 bool init(SkCanvas::PointMode, const SkPaint&, const SkMatrix* matrix,
102 const SkRasterClip*);
103 Proc chooseProc(SkBlitter** blitter);
104
105private:
106 SkAAClipBlitterWrapper fWrapper;
107};
108
109static void bw_pt_rect_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
110 int count, SkBlitter* blitter) {
111 SkASSERT(rec.fClip->isRect());
112 const SkIRect& r = rec.fClip->getBounds();
113
114 for (int i = 0; i < count; i++) {
115 int x = SkScalarFloorToInt(devPts[i].fX);
116 int y = SkScalarFloorToInt(devPts[i].fY);
117 if (r.contains(x, y)) {
118 blitter->blitH(x, y, 1);
119 }
120 }
121}
122
123static void bw_pt_rect_16_hair_proc(const PtProcRec& rec,
124 const SkPoint devPts[], int count,
125 SkBlitter* blitter) {
126 SkASSERT(rec.fRC->isRect());
127 const SkIRect& r = rec.fRC->getBounds();
128 uint32_t value;
129 const SkPixmap* dst = blitter->justAnOpaqueColor(&value);
130 SkASSERT(dst);
131
132 uint16_t* addr = dst->writable_addr16(0, 0);
133 size_t rb = dst->rowBytes();
134
135 for (int i = 0; i < count; i++) {
136 int x = SkScalarFloorToInt(devPts[i].fX);
137 int y = SkScalarFloorToInt(devPts[i].fY);
138 if (r.contains(x, y)) {
139 ((uint16_t*)((char*)addr + y * rb))[x] = SkToU16(value);
140 }
141 }
142}
143
144static void bw_pt_rect_32_hair_proc(const PtProcRec& rec,
145 const SkPoint devPts[], int count,
146 SkBlitter* blitter) {
147 SkASSERT(rec.fRC->isRect());
148 const SkIRect& r = rec.fRC->getBounds();
149 uint32_t value;
150 const SkPixmap* dst = blitter->justAnOpaqueColor(&value);
151 SkASSERT(dst);
152
153 SkPMColor* addr = dst->writable_addr32(0, 0);
154 size_t rb = dst->rowBytes();
155
156 for (int i = 0; i < count; i++) {
157 int x = SkScalarFloorToInt(devPts[i].fX);
158 int y = SkScalarFloorToInt(devPts[i].fY);
159 if (r.contains(x, y)) {
160 ((SkPMColor*)((char*)addr + y * rb))[x] = value;
161 }
162 }
163}
164
165static void bw_pt_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
166 int count, SkBlitter* blitter) {
167 for (int i = 0; i < count; i++) {
168 int x = SkScalarFloorToInt(devPts[i].fX);
169 int y = SkScalarFloorToInt(devPts[i].fY);
170 if (rec.fClip->contains(x, y)) {
171 blitter->blitH(x, y, 1);
172 }
173 }
174}
175
176static void bw_line_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
177 int count, SkBlitter* blitter) {
178 for (int i = 0; i < count; i += 2) {
179 SkScan::HairLine(&devPts[i], 2, *rec.fRC, blitter);
180 }
181}
182
183static void bw_poly_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
184 int count, SkBlitter* blitter) {
185 SkScan::HairLine(devPts, count, *rec.fRC, blitter);
186}
187
188// aa versions
189
190static void aa_line_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
191 int count, SkBlitter* blitter) {
192 for (int i = 0; i < count; i += 2) {
193 SkScan::AntiHairLine(&devPts[i], 2, *rec.fRC, blitter);
194 }
195}
196
197static void aa_poly_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
198 int count, SkBlitter* blitter) {
199 SkScan::AntiHairLine(devPts, count, *rec.fRC, blitter);
200}
201
202// square procs (strokeWidth > 0 but matrix is square-scale (sx == sy)
203
204static SkRect make_square_rad(SkPoint center, SkScalar radius) {
205 return {
206 center.fX - radius, center.fY - radius,
207 center.fX + radius, center.fY + radius
208 };
209}
210
211static SkXRect make_xrect(const SkRect& r) {
212 SkASSERT(SkRectPriv::FitsInFixed(r));
213 return {
214 SkScalarToFixed(r.fLeft), SkScalarToFixed(r.fTop),
215 SkScalarToFixed(r.fRight), SkScalarToFixed(r.fBottom)
216 };
217}
218
219static void bw_square_proc(const PtProcRec& rec, const SkPoint devPts[],
220 int count, SkBlitter* blitter) {
221 for (int i = 0; i < count; i++) {
222 SkRect r = make_square_rad(devPts[i], rec.fRadius);
223 if (r.intersect(rec.fClipBounds)) {
224 SkScan::FillXRect(make_xrect(r), *rec.fRC, blitter);
225 }
226 }
227}
228
229static void aa_square_proc(const PtProcRec& rec, const SkPoint devPts[],
230 int count, SkBlitter* blitter) {
231 for (int i = 0; i < count; i++) {
232 SkRect r = make_square_rad(devPts[i], rec.fRadius);
233 if (r.intersect(rec.fClipBounds)) {
234 SkScan::AntiFillXRect(make_xrect(r), *rec.fRC, blitter);
235 }
236 }
237}
238
239// If this guy returns true, then chooseProc() must return a valid proc
240bool PtProcRec::init(SkCanvas::PointMode mode, const SkPaint& paint,
241 const SkMatrix* matrix, const SkRasterClip* rc) {
242 if ((unsigned)mode > (unsigned)SkCanvas::kPolygon_PointMode) {
243 return false;
244 }
245 if (paint.getPathEffect()) {
246 return false;
247 }
248 SkScalar width = paint.getStrokeWidth();
249 SkScalar radius = -1; // sentinel value, a "valid" value must be > 0
250
251 if (0 == width) {
252 radius = 0.5f;
253 } else if (paint.getStrokeCap() != SkPaint::kRound_Cap &&
254 matrix->isScaleTranslate() && SkCanvas::kPoints_PointMode == mode) {
255 SkScalar sx = matrix->get(SkMatrix::kMScaleX);
256 SkScalar sy = matrix->get(SkMatrix::kMScaleY);
257 if (SkScalarNearlyZero(sx - sy)) {
258 radius = SkScalarHalf(width * SkScalarAbs(sx));
259 }
260 }
261 if (radius > 0) {
262 SkRect clipBounds = SkRect::Make(rc->getBounds());
263 // if we return true, the caller may assume that the constructed shapes can be represented
264 // using SkFixed (after clipping), so we preflight that here.
265 if (!SkRectPriv::FitsInFixed(clipBounds)) {
266 return false;
267 }
268 fMode = mode;
269 fPaint = &paint;
270 fClip = nullptr;
271 fRC = rc;
272 fClipBounds = clipBounds;
273 fRadius = radius;
274 return true;
275 }
276 return false;
277}
278
279PtProcRec::Proc PtProcRec::chooseProc(SkBlitter** blitterPtr) {
280 Proc proc = nullptr;
281
282 SkBlitter* blitter = *blitterPtr;
283 if (fRC->isBW()) {
284 fClip = &fRC->bwRgn();
285 } else {
286 fWrapper.init(*fRC, blitter);
287 fClip = &fWrapper.getRgn();
288 blitter = fWrapper.getBlitter();
289 *blitterPtr = blitter;
290 }
291
292 // for our arrays
293 SkASSERT(0 == SkCanvas::kPoints_PointMode);
294 SkASSERT(1 == SkCanvas::kLines_PointMode);
295 SkASSERT(2 == SkCanvas::kPolygon_PointMode);
296 SkASSERT((unsigned)fMode <= (unsigned)SkCanvas::kPolygon_PointMode);
297
298 if (fPaint->isAntiAlias()) {
299 if (0 == fPaint->getStrokeWidth()) {
300 static const Proc gAAProcs[] = {
301 aa_square_proc, aa_line_hair_proc, aa_poly_hair_proc
302 };
303 proc = gAAProcs[fMode];
304 } else if (fPaint->getStrokeCap() != SkPaint::kRound_Cap) {
305 SkASSERT(SkCanvas::kPoints_PointMode == fMode);
306 proc = aa_square_proc;
307 }
308 } else { // BW
309 if (fRadius <= 0.5f) { // small radii and hairline
310 if (SkCanvas::kPoints_PointMode == fMode && fClip->isRect()) {
311 uint32_t value;
312 const SkPixmap* bm = blitter->justAnOpaqueColor(&value);
313 if (bm && kRGB_565_SkColorType == bm->colorType()) {
314 proc = bw_pt_rect_16_hair_proc;
315 } else if (bm && kN32_SkColorType == bm->colorType()) {
316 proc = bw_pt_rect_32_hair_proc;
317 } else {
318 proc = bw_pt_rect_hair_proc;
319 }
320 } else {
321 static Proc gBWProcs[] = {
322 bw_pt_hair_proc, bw_line_hair_proc, bw_poly_hair_proc
323 };
324 proc = gBWProcs[fMode];
325 }
326 } else {
327 proc = bw_square_proc;
328 }
329 }
330 return proc;
331}
332
333// each of these costs 8-bytes of stack space, so don't make it too large
334// must be even for lines/polygon to work
335#define MAX_DEV_PTS 32
336
337void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count,
338 const SkPoint pts[], const SkPaint& paint,
339 SkBaseDevice* device) const {
340 // if we're in lines mode, force count to be even
341 if (SkCanvas::kLines_PointMode == mode) {
342 count &= ~(size_t)1;
343 }
344
345 if ((long)count <= 0) {
346 return;
347 }
348
349 SkASSERT(pts != nullptr);
350 SkDEBUGCODE(this->validate();)
351
352 // nothing to draw
353 if (fRC->isEmpty()) {
354 return;
355 }
356
357 PtProcRec rec;
358 if (!device && rec.init(mode, paint, fMatrix, fRC)) {
359 SkAutoBlitterChoose blitter(*this, nullptr, paint);
360
361 SkPoint devPts[MAX_DEV_PTS];
362 const SkMatrix* matrix = fMatrix;
363 SkBlitter* bltr = blitter.get();
364 PtProcRec::Proc proc = rec.chooseProc(&bltr);
365 // we have to back up subsequent passes if we're in polygon mode
366 const size_t backup = (SkCanvas::kPolygon_PointMode == mode);
367
368 do {
369 int n = SkToInt(count);
370 if (n > MAX_DEV_PTS) {
371 n = MAX_DEV_PTS;
372 }
373 matrix->mapPoints(devPts, pts, n);
374 if (!SkScalarsAreFinite(&devPts[0].fX, n * 2)) {
375 return;
376 }
377 proc(rec, devPts, n, bltr);
378 pts += n - backup;
379 SkASSERT(SkToInt(count) >= n);
380 count -= n;
381 if (count > 0) {
382 count += backup;
383 }
384 } while (count != 0);
385 } else {
386 switch (mode) {
387 case SkCanvas::kPoints_PointMode: {
388 // temporarily mark the paint as filling.
389 SkPaint newPaint(paint);
390 newPaint.setStyle(SkPaint::kFill_Style);
391
392 SkScalar width = newPaint.getStrokeWidth();
393 SkScalar radius = SkScalarHalf(width);
394
395 if (newPaint.getStrokeCap() == SkPaint::kRound_Cap) {
396 if (device) {
397 for (size_t i = 0; i < count; ++i) {
398 SkRect r = SkRect::MakeLTRB(pts[i].fX - radius, pts[i].fY - radius,
399 pts[i].fX + radius, pts[i].fY + radius);
400 device->drawOval(r, newPaint);
401 }
402 } else {
403 SkPath path;
404 SkMatrix preMatrix;
405
406 path.addCircle(0, 0, radius);
407 for (size_t i = 0; i < count; i++) {
408 preMatrix.setTranslate(pts[i].fX, pts[i].fY);
409 // pass true for the last point, since we can modify
410 // then path then
411 path.setIsVolatile((count-1) == i);
412 this->drawPath(path, newPaint, &preMatrix, (count-1) == i);
413 }
414 }
415 } else {
416 SkRect r;
417
418 for (size_t i = 0; i < count; i++) {
419 r.fLeft = pts[i].fX - radius;
420 r.fTop = pts[i].fY - radius;
421 r.fRight = r.fLeft + width;
422 r.fBottom = r.fTop + width;
423 if (device) {
424 device->drawRect(r, newPaint);
425 } else {
426 this->drawRect(r, newPaint);
427 }
428 }
429 }
430 break;
431 }
432 case SkCanvas::kLines_PointMode:
433 if (2 == count && paint.getPathEffect()) {
434 // most likely a dashed line - see if it is one of the ones
435 // we can accelerate
436 SkStrokeRec rec(paint);
437 SkPathEffect::PointData pointData;
438
439 SkPath path;
440 path.moveTo(pts[0]);
441 path.lineTo(pts[1]);
442
443 SkRect cullRect = SkRect::Make(fRC->getBounds());
444
445 if (paint.getPathEffect()->asPoints(&pointData, path, rec,
446 *fMatrix, &cullRect)) {
447 // 'asPoints' managed to find some fast path
448
449 SkPaint newP(paint);
450 newP.setPathEffect(nullptr);
451 newP.setStyle(SkPaint::kFill_Style);
452
453 if (!pointData.fFirst.isEmpty()) {
454 if (device) {
455 device->drawPath(pointData.fFirst, newP);
456 } else {
457 this->drawPath(pointData.fFirst, newP);
458 }
459 }
460
461 if (!pointData.fLast.isEmpty()) {
462 if (device) {
463 device->drawPath(pointData.fLast, newP);
464 } else {
465 this->drawPath(pointData.fLast, newP);
466 }
467 }
468
469 if (pointData.fSize.fX == pointData.fSize.fY) {
470 // The rest of the dashed line can just be drawn as points
471 SkASSERT(pointData.fSize.fX == SkScalarHalf(newP.getStrokeWidth()));
472
473 if (SkPathEffect::PointData::kCircles_PointFlag & pointData.fFlags) {
474 newP.setStrokeCap(SkPaint::kRound_Cap);
475 } else {
476 newP.setStrokeCap(SkPaint::kButt_Cap);
477 }
478
479 if (device) {
480 device->drawPoints(SkCanvas::kPoints_PointMode,
481 pointData.fNumPoints,
482 pointData.fPoints,
483 newP);
484 } else {
485 this->drawPoints(SkCanvas::kPoints_PointMode,
486 pointData.fNumPoints,
487 pointData.fPoints,
488 newP,
489 device);
490 }
491 break;
492 } else {
493 // The rest of the dashed line must be drawn as rects
494 SkASSERT(!(SkPathEffect::PointData::kCircles_PointFlag &
495 pointData.fFlags));
496
497 SkRect r;
498
499 for (int i = 0; i < pointData.fNumPoints; ++i) {
500 r.setLTRB(pointData.fPoints[i].fX - pointData.fSize.fX,
501 pointData.fPoints[i].fY - pointData.fSize.fY,
502 pointData.fPoints[i].fX + pointData.fSize.fX,
503 pointData.fPoints[i].fY + pointData.fSize.fY);
504 if (device) {
505 device->drawRect(r, newP);
506 } else {
507 this->drawRect(r, newP);
508 }
509 }
510 }
511
512 break;
513 }
514 }
515 // couldn't take fast path so fall through!
516 case SkCanvas::kPolygon_PointMode: {
517 count -= 1;
518 SkPath path;
519 SkPaint p(paint);
520 p.setStyle(SkPaint::kStroke_Style);
521 size_t inc = (SkCanvas::kLines_PointMode == mode) ? 2 : 1;
522 path.setIsVolatile(true);
523 for (size_t i = 0; i < count; i += inc) {
524 path.moveTo(pts[i]);
525 path.lineTo(pts[i+1]);
526 if (device) {
527 device->drawPath(path, p, true);
528 } else {
529 this->drawPath(path, p, nullptr, true);
530 }
531 path.rewind();
532 }
533 break;
534 }
535 }
536 }
537}
538
539static inline SkPoint compute_stroke_size(const SkPaint& paint, const SkMatrix& matrix) {
540 SkASSERT(matrix.rectStaysRect());
541 SkASSERT(SkPaint::kFill_Style != paint.getStyle());
542
543 SkVector size;
544 SkPoint pt = { paint.getStrokeWidth(), paint.getStrokeWidth() };
545 matrix.mapVectors(&size, &pt, 1);
546 return SkPoint::Make(SkScalarAbs(size.fX), SkScalarAbs(size.fY));
547}
548
549static bool easy_rect_join(const SkPaint& paint, const SkMatrix& matrix,
550 SkPoint* strokeSize) {
551 if (SkPaint::kMiter_Join != paint.getStrokeJoin() ||
552 paint.getStrokeMiter() < SK_ScalarSqrt2) {
553 return false;
554 }
555
556 *strokeSize = compute_stroke_size(paint, matrix);
557 return true;
558}
559
560SkDraw::RectType SkDraw::ComputeRectType(const SkPaint& paint,
561 const SkMatrix& matrix,
562 SkPoint* strokeSize) {
563 RectType rtype;
564 const SkScalar width = paint.getStrokeWidth();
565 const bool zeroWidth = (0 == width);
566 SkPaint::Style style = paint.getStyle();
567
568 if ((SkPaint::kStrokeAndFill_Style == style) && zeroWidth) {
569 style = SkPaint::kFill_Style;
570 }
571
572 if (paint.getPathEffect() || paint.getMaskFilter() ||
573 !matrix.rectStaysRect() || SkPaint::kStrokeAndFill_Style == style) {
574 rtype = kPath_RectType;
575 } else if (SkPaint::kFill_Style == style) {
576 rtype = kFill_RectType;
577 } else if (zeroWidth) {
578 rtype = kHair_RectType;
579 } else if (easy_rect_join(paint, matrix, strokeSize)) {
580 rtype = kStroke_RectType;
581 } else {
582 rtype = kPath_RectType;
583 }
584 return rtype;
585}
586
587static const SkPoint* rect_points(const SkRect& r) {
588 return reinterpret_cast<const SkPoint*>(&r);
589}
590
591static SkPoint* rect_points(SkRect& r) {
592 return reinterpret_cast<SkPoint*>(&r);
593}
594
595static void draw_rect_as_path(const SkDraw& orig, const SkRect& prePaintRect,
596 const SkPaint& paint, const SkMatrix* matrix) {
597 SkDraw draw(orig);
598 draw.fMatrix = matrix;
599 SkPath tmp;
600 tmp.addRect(prePaintRect);
601 tmp.setFillType(SkPathFillType::kWinding);
602 draw.drawPath(tmp, paint, nullptr, true);
603}
604
605void SkDraw::drawRect(const SkRect& prePaintRect, const SkPaint& paint,
606 const SkMatrix* paintMatrix, const SkRect* postPaintRect) const {
607 SkDEBUGCODE(this->validate();)
608
609 // nothing to draw
610 if (fRC->isEmpty()) {
611 return;
612 }
613
614 const SkMatrix* matrix;
615 SkMatrix combinedMatrixStorage;
616 if (paintMatrix) {
617 SkASSERT(postPaintRect);
618 combinedMatrixStorage.setConcat(*fMatrix, *paintMatrix);
619 matrix = &combinedMatrixStorage;
620 } else {
621 SkASSERT(!postPaintRect);
622 matrix = fMatrix;
623 }
624
625 SkPoint strokeSize;
626 RectType rtype = ComputeRectType(paint, *fMatrix, &strokeSize);
627
628 if (kPath_RectType == rtype) {
629 draw_rect_as_path(*this, prePaintRect, paint, matrix);
630 return;
631 }
632
633 SkRect devRect;
634 const SkRect& paintRect = paintMatrix ? *postPaintRect : prePaintRect;
635 // skip the paintMatrix when transforming the rect by the CTM
636 fMatrix->mapPoints(rect_points(devRect), rect_points(paintRect), 2);
637 devRect.sort();
638
639 // look for the quick exit, before we build a blitter
640 SkRect bbox = devRect;
641 if (paint.getStyle() != SkPaint::kFill_Style) {
642 // extra space for hairlines
643 if (paint.getStrokeWidth() == 0) {
644 bbox.outset(1, 1);
645 } else {
646 // For kStroke_RectType, strokeSize is already computed.
647 const SkPoint& ssize = (kStroke_RectType == rtype)
648 ? strokeSize
649 : compute_stroke_size(paint, *fMatrix);
650 bbox.outset(SkScalarHalf(ssize.x()), SkScalarHalf(ssize.y()));
651 }
652 }
653 if (SkPathPriv::TooBigForMath(bbox)) {
654 return;
655 }
656
657 if (!SkRectPriv::FitsInFixed(bbox) && rtype != kHair_RectType) {
658 draw_rect_as_path(*this, prePaintRect, paint, matrix);
659 return;
660 }
661
662 SkIRect ir = bbox.roundOut();
663 if (fRC->quickReject(ir)) {
664 return;
665 }
666
667 SkAutoBlitterChoose blitterStorage(*this, matrix, paint);
668 const SkRasterClip& clip = *fRC;
669 SkBlitter* blitter = blitterStorage.get();
670
671 // we want to "fill" if we are kFill or kStrokeAndFill, since in the latter
672 // case we are also hairline (if we've gotten to here), which devolves to
673 // effectively just kFill
674 switch (rtype) {
675 case kFill_RectType:
676 if (paint.isAntiAlias()) {
677 SkScan::AntiFillRect(devRect, clip, blitter);
678 } else {
679 SkScan::FillRect(devRect, clip, blitter);
680 }
681 break;
682 case kStroke_RectType:
683 if (paint.isAntiAlias()) {
684 SkScan::AntiFrameRect(devRect, strokeSize, clip, blitter);
685 } else {
686 SkScan::FrameRect(devRect, strokeSize, clip, blitter);
687 }
688 break;
689 case kHair_RectType:
690 if (paint.isAntiAlias()) {
691 SkScan::AntiHairRect(devRect, clip, blitter);
692 } else {
693 SkScan::HairRect(devRect, clip, blitter);
694 }
695 break;
696 default:
697 SkDEBUGFAIL("bad rtype");
698 }
699}
700
701void SkDraw::drawDevMask(const SkMask& srcM, const SkPaint& paint) const {
702 if (srcM.fBounds.isEmpty()) {
703 return;
704 }
705
706 const SkMask* mask = &srcM;
707
708 SkMask dstM;
709 if (paint.getMaskFilter() &&
710 as_MFB(paint.getMaskFilter())->filterMask(&dstM, srcM, *fMatrix, nullptr)) {
711 mask = &dstM;
712 }
713 SkAutoMaskFreeImage ami(dstM.fImage);
714
715 SkAutoBlitterChoose blitterChooser(*this, nullptr, paint);
716 SkBlitter* blitter = blitterChooser.get();
717
718 SkAAClipBlitterWrapper wrapper;
719 const SkRegion* clipRgn;
720
721 if (fRC->isBW()) {
722 clipRgn = &fRC->bwRgn();
723 } else {
724 wrapper.init(*fRC, blitter);
725 clipRgn = &wrapper.getRgn();
726 blitter = wrapper.getBlitter();
727 }
728 blitter->blitMaskRegion(*mask, *clipRgn);
729}
730
731static SkScalar fast_len(const SkVector& vec) {
732 SkScalar x = SkScalarAbs(vec.fX);
733 SkScalar y = SkScalarAbs(vec.fY);
734 if (x < y) {
735 using std::swap;
736 swap(x, y);
737 }
738 return x + SkScalarHalf(y);
739}
740
741bool SkDrawTreatAAStrokeAsHairline(SkScalar strokeWidth, const SkMatrix& matrix,
742 SkScalar* coverage) {
743 SkASSERT(strokeWidth > 0);
744 // We need to try to fake a thick-stroke with a modulated hairline.
745
746 if (matrix.hasPerspective()) {
747 return false;
748 }
749
750 SkVector src[2], dst[2];
751 src[0].set(strokeWidth, 0);
752 src[1].set(0, strokeWidth);
753 matrix.mapVectors(dst, src, 2);
754 SkScalar len0 = fast_len(dst[0]);
755 SkScalar len1 = fast_len(dst[1]);
756 if (len0 <= SK_Scalar1 && len1 <= SK_Scalar1) {
757 if (coverage) {
758 *coverage = SkScalarAve(len0, len1);
759 }
760 return true;
761 }
762 return false;
763}
764
765void SkDraw::drawRRect(const SkRRect& rrect, const SkPaint& paint) const {
766 SkDEBUGCODE(this->validate());
767
768 if (fRC->isEmpty()) {
769 return;
770 }
771
772 {
773 // TODO: Investigate optimizing these options. They are in the same
774 // order as SkDraw::drawPath, which handles each case. It may be
775 // that there is no way to optimize for these using the SkRRect path.
776 SkScalar coverage;
777 if (SkDrawTreatAsHairline(paint, *fMatrix, &coverage)) {
778 goto DRAW_PATH;
779 }
780
781 if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
782 goto DRAW_PATH;
783 }
784 }
785
786 if (paint.getMaskFilter()) {
787 // Transform the rrect into device space.
788 SkRRect devRRect;
789 if (rrect.transform(*fMatrix, &devRRect)) {
790 SkAutoBlitterChoose blitter(*this, nullptr, paint);
791 if (as_MFB(paint.getMaskFilter())->filterRRect(devRRect, *fMatrix,
792 *fRC, blitter.get())) {
793 return; // filterRRect() called the blitter, so we're done
794 }
795 }
796 }
797
798DRAW_PATH:
799 // Now fall back to the default case of using a path.
800 SkPath path;
801 path.addRRect(rrect);
802 this->drawPath(path, paint, nullptr, true);
803}
804
805SkScalar SkDraw::ComputeResScaleForStroking(const SkMatrix& matrix) {
806 // Not sure how to handle perspective differently, so we just don't try (yet)
807 SkScalar sx = SkPoint::Length(matrix[SkMatrix::kMScaleX], matrix[SkMatrix::kMSkewY]);
808 SkScalar sy = SkPoint::Length(matrix[SkMatrix::kMSkewX], matrix[SkMatrix::kMScaleY]);
809 if (SkScalarsAreFinite(sx, sy)) {
810 SkScalar scale = std::max(sx, sy);
811 if (scale > 0) {
812 return scale;
813 }
814 }
815 return 1;
816}
817
818void SkDraw::drawDevPath(const SkPath& devPath, const SkPaint& paint, bool drawCoverage,
819 SkBlitter* customBlitter, bool doFill) const {
820 if (SkPathPriv::TooBigForMath(devPath)) {
821 return;
822 }
823 SkBlitter* blitter = nullptr;
824 SkAutoBlitterChoose blitterStorage;
825 if (nullptr == customBlitter) {
826 blitter = blitterStorage.choose(*this, nullptr, paint, drawCoverage);
827 } else {
828 blitter = customBlitter;
829 }
830
831 if (paint.getMaskFilter()) {
832 SkStrokeRec::InitStyle style = doFill ? SkStrokeRec::kFill_InitStyle
833 : SkStrokeRec::kHairline_InitStyle;
834 if (as_MFB(paint.getMaskFilter())->filterPath(devPath, *fMatrix, *fRC, blitter, style)) {
835 return; // filterPath() called the blitter, so we're done
836 }
837 }
838
839 void (*proc)(const SkPath&, const SkRasterClip&, SkBlitter*);
840 if (doFill) {
841 if (paint.isAntiAlias()) {
842 proc = SkScan::AntiFillPath;
843 } else {
844 proc = SkScan::FillPath;
845 }
846 } else { // hairline
847 if (paint.isAntiAlias()) {
848 switch (paint.getStrokeCap()) {
849 case SkPaint::kButt_Cap:
850 proc = SkScan::AntiHairPath;
851 break;
852 case SkPaint::kSquare_Cap:
853 proc = SkScan::AntiHairSquarePath;
854 break;
855 case SkPaint::kRound_Cap:
856 proc = SkScan::AntiHairRoundPath;
857 break;
858 default:
859 proc SK_INIT_TO_AVOID_WARNING;
860 SkDEBUGFAIL("unknown paint cap type");
861 }
862 } else {
863 switch (paint.getStrokeCap()) {
864 case SkPaint::kButt_Cap:
865 proc = SkScan::HairPath;
866 break;
867 case SkPaint::kSquare_Cap:
868 proc = SkScan::HairSquarePath;
869 break;
870 case SkPaint::kRound_Cap:
871 proc = SkScan::HairRoundPath;
872 break;
873 default:
874 proc SK_INIT_TO_AVOID_WARNING;
875 SkDEBUGFAIL("unknown paint cap type");
876 }
877 }
878 }
879
880 proc(devPath, *fRC, blitter);
881}
882
883void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& origPaint,
884 const SkMatrix* prePathMatrix, bool pathIsMutable,
885 bool drawCoverage, SkBlitter* customBlitter) const {
886 SkDEBUGCODE(this->validate();)
887
888 // nothing to draw
889 if (fRC->isEmpty()) {
890 return;
891 }
892
893 SkPath* pathPtr = (SkPath*)&origSrcPath;
894 bool doFill = true;
895 SkPath tmpPathStorage;
896 SkPath* tmpPath = &tmpPathStorage;
897 SkMatrix tmpMatrix;
898 const SkMatrix* matrix = fMatrix;
899 tmpPath->setIsVolatile(true);
900
901 if (prePathMatrix) {
902 if (origPaint.getPathEffect() || origPaint.getStyle() != SkPaint::kFill_Style) {
903 SkPath* result = pathPtr;
904
905 if (!pathIsMutable) {
906 result = tmpPath;
907 pathIsMutable = true;
908 }
909 pathPtr->transform(*prePathMatrix, result);
910 pathPtr = result;
911 } else {
912 tmpMatrix.setConcat(*matrix, *prePathMatrix);
913 matrix = &tmpMatrix;
914 }
915 }
916 // at this point we're done with prePathMatrix
917 SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
918
919 SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
920
921 {
922 SkScalar coverage;
923 if (SkDrawTreatAsHairline(origPaint, *matrix, &coverage)) {
924 if (SK_Scalar1 == coverage) {
925 paint.writable()->setStrokeWidth(0);
926 } else if (SkBlendMode_SupportsCoverageAsAlpha(origPaint.getBlendMode())) {
927 U8CPU newAlpha;
928#if 0
929 newAlpha = SkToU8(SkScalarRoundToInt(coverage *
930 origPaint.getAlpha()));
931#else
932 // this is the old technique, which we preserve for now so
933 // we don't change previous results (testing)
934 // the new way seems fine, its just (a tiny bit) different
935 int scale = (int)(coverage * 256);
936 newAlpha = origPaint.getAlpha() * scale >> 8;
937#endif
938 SkPaint* writablePaint = paint.writable();
939 writablePaint->setStrokeWidth(0);
940 writablePaint->setAlpha(newAlpha);
941 }
942 }
943 }
944
945 if (paint->getPathEffect() || paint->getStyle() != SkPaint::kFill_Style) {
946 SkRect cullRect;
947 const SkRect* cullRectPtr = nullptr;
948 if (this->computeConservativeLocalClipBounds(&cullRect)) {
949 cullRectPtr = &cullRect;
950 }
951 doFill = paint->getFillPath(*pathPtr, tmpPath, cullRectPtr,
952 ComputeResScaleForStroking(*fMatrix));
953 pathPtr = tmpPath;
954 }
955
956 // avoid possibly allocating a new path in transform if we can
957 SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath;
958
959 // transform the path into device space
960 pathPtr->transform(*matrix, devPathPtr);
961
962 this->drawDevPath(*devPathPtr, *paint, drawCoverage, customBlitter, doFill);
963}
964
965void SkDraw::drawBitmapAsMask(const SkBitmap& bitmap, const SkPaint& paint) const {
966 SkASSERT(bitmap.colorType() == kAlpha_8_SkColorType);
967
968 // nothing to draw
969 if (fRC->isEmpty()) {
970 return;
971 }
972
973 if (SkTreatAsSprite(*fMatrix, bitmap.dimensions(), paint)) {
974 int ix = SkScalarRoundToInt(fMatrix->getTranslateX());
975 int iy = SkScalarRoundToInt(fMatrix->getTranslateY());
976
977 SkPixmap pmap;
978 if (!bitmap.peekPixels(&pmap)) {
979 return;
980 }
981 SkMask mask;
982 mask.fBounds.setXYWH(ix, iy, pmap.width(), pmap.height());
983 mask.fFormat = SkMask::kA8_Format;
984 mask.fRowBytes = SkToU32(pmap.rowBytes());
985 // fImage is typed as writable, but in this case it is used read-only
986 mask.fImage = (uint8_t*)pmap.addr8(0, 0);
987
988 this->drawDevMask(mask, paint);
989 } else { // need to xform the bitmap first
990 SkRect r;
991 SkMask mask;
992
993 r.setIWH(bitmap.width(), bitmap.height());
994 fMatrix->mapRect(&r);
995 r.round(&mask.fBounds);
996
997 // set the mask's bounds to the transformed bitmap-bounds,
998 // clipped to the actual device and further limited by the clip bounds
999 {
1000 SkASSERT(fDst.bounds().contains(fRC->getBounds()));
1001 SkIRect devBounds = fDst.bounds();
1002 devBounds.intersect(fRC->getBounds().makeOutset(1, 1));
1003 // need intersect(l, t, r, b) on irect
1004 if (!mask.fBounds.intersect(devBounds)) {
1005 return;
1006 }
1007 }
1008
1009 mask.fFormat = SkMask::kA8_Format;
1010 mask.fRowBytes = SkAlign4(mask.fBounds.width());
1011 size_t size = mask.computeImageSize();
1012 if (0 == size) {
1013 // the mask is too big to allocated, draw nothing
1014 return;
1015 }
1016
1017 // allocate (and clear) our temp buffer to hold the transformed bitmap
1018 SkAutoTMalloc<uint8_t> storage(size);
1019 mask.fImage = storage.get();
1020 memset(mask.fImage, 0, size);
1021
1022 // now draw our bitmap(src) into mask(dst), transformed by the matrix
1023 {
1024 SkBitmap device;
1025 device.installPixels(SkImageInfo::MakeA8(mask.fBounds.width(), mask.fBounds.height()),
1026 mask.fImage, mask.fRowBytes);
1027
1028 SkCanvas c(device);
1029 // need the unclipped top/left for the translate
1030 c.translate(-SkIntToScalar(mask.fBounds.fLeft),
1031 -SkIntToScalar(mask.fBounds.fTop));
1032 c.concat(*fMatrix);
1033
1034 // We can't call drawBitmap, or we'll infinitely recurse. Instead
1035 // we manually build a shader and draw that into our new mask
1036 SkPaint tmpPaint;
1037 tmpPaint.setAntiAlias(paint.isAntiAlias());
1038 tmpPaint.setDither(paint.isDither());
1039 tmpPaint.setFilterQuality(paint.getFilterQuality());
1040 SkPaint paintWithShader = make_paint_with_image(tmpPaint, bitmap);
1041 SkRect rr;
1042 rr.setIWH(bitmap.width(), bitmap.height());
1043 c.drawRect(rr, paintWithShader);
1044 }
1045 this->drawDevMask(mask, paint);
1046 }
1047}
1048
1049static bool clipped_out(const SkMatrix& m, const SkRasterClip& c,
1050 const SkRect& srcR) {
1051 SkRect dstR;
1052 m.mapRect(&dstR, srcR);
1053 return c.quickReject(dstR.roundOut());
1054}
1055
1056static bool clipped_out(const SkMatrix& matrix, const SkRasterClip& clip,
1057 int width, int height) {
1058 SkRect r;
1059 r.setIWH(width, height);
1060 return clipped_out(matrix, clip, r);
1061}
1062
1063static bool clipHandlesSprite(const SkRasterClip& clip, int x, int y, const SkPixmap& pmap) {
1064 return clip.isBW() || clip.quickContains(x, y, x + pmap.width(), y + pmap.height());
1065}
1066
1067void SkDraw::drawBitmap(const SkBitmap& bitmap, const SkMatrix& prematrix,
1068 const SkRect* dstBounds, const SkPaint& origPaint) const {
1069 SkDEBUGCODE(this->validate();)
1070
1071 // nothing to draw
1072 if (fRC->isEmpty() ||
1073 bitmap.width() == 0 || bitmap.height() == 0 ||
1074 bitmap.colorType() == kUnknown_SkColorType) {
1075 return;
1076 }
1077
1078 SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
1079 if (origPaint.getStyle() != SkPaint::kFill_Style) {
1080 paint.writable()->setStyle(SkPaint::kFill_Style);
1081 }
1082
1083 SkMatrix matrix;
1084 matrix.setConcat(*fMatrix, prematrix);
1085
1086 if (clipped_out(matrix, *fRC, bitmap.width(), bitmap.height())) {
1087 return;
1088 }
1089
1090 if (bitmap.colorType() != kAlpha_8_SkColorType
1091 && SkTreatAsSprite(matrix, bitmap.dimensions(), *paint)) {
1092 //
1093 // It is safe to call lock pixels now, since we know the matrix is
1094 // (more or less) identity.
1095 //
1096 SkPixmap pmap;
1097 if (!bitmap.peekPixels(&pmap)) {
1098 return;
1099 }
1100 int ix = SkScalarRoundToInt(matrix.getTranslateX());
1101 int iy = SkScalarRoundToInt(matrix.getTranslateY());
1102 if (clipHandlesSprite(*fRC, ix, iy, pmap)) {
1103 SkSTArenaAlloc<kSkBlitterContextSize> allocator;
1104 // blitter will be owned by the allocator.
1105 SkBlitter* blitter = SkBlitter::ChooseSprite(fDst, *paint, pmap, ix, iy, &allocator,
1106 fRC->clipShader());
1107 if (blitter) {
1108 SkScan::FillIRect(SkIRect::MakeXYWH(ix, iy, pmap.width(), pmap.height()),
1109 *fRC, blitter);
1110 return;
1111 }
1112 // if !blitter, then we fall-through to the slower case
1113 }
1114 }
1115
1116 // now make a temp draw on the stack, and use it
1117 //
1118 SkDraw draw(*this);
1119 draw.fMatrix = &matrix;
1120
1121 if (bitmap.colorType() == kAlpha_8_SkColorType && !paint->getColorFilter()) {
1122 draw.drawBitmapAsMask(bitmap, *paint);
1123 } else {
1124 SkPaint paintWithShader = make_paint_with_image(*paint, bitmap);
1125 const SkRect srcBounds = SkRect::MakeIWH(bitmap.width(), bitmap.height());
1126 if (dstBounds) {
1127 this->drawRect(srcBounds, paintWithShader, &prematrix, dstBounds);
1128 } else {
1129 draw.drawRect(srcBounds, paintWithShader);
1130 }
1131 }
1132}
1133
1134void SkDraw::drawSprite(const SkBitmap& bitmap, int x, int y, const SkPaint& origPaint) const {
1135 SkDEBUGCODE(this->validate();)
1136
1137 // nothing to draw
1138 if (fRC->isEmpty() ||
1139 bitmap.width() == 0 || bitmap.height() == 0 ||
1140 bitmap.colorType() == kUnknown_SkColorType) {
1141 return;
1142 }
1143
1144 const SkIRect bounds = SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height());
1145
1146 if (fRC->quickReject(bounds)) {
1147 return; // nothing to draw
1148 }
1149
1150 SkPaint paint(origPaint);
1151 paint.setStyle(SkPaint::kFill_Style);
1152
1153 SkPixmap pmap;
1154 if (!bitmap.peekPixels(&pmap)) {
1155 return;
1156 }
1157
1158 if (nullptr == paint.getColorFilter() && clipHandlesSprite(*fRC, x, y, pmap)) {
1159 // blitter will be owned by the allocator.
1160 SkSTArenaAlloc<kSkBlitterContextSize> allocator;
1161 SkBlitter* blitter = SkBlitter::ChooseSprite(fDst, paint, pmap, x, y, &allocator,
1162 fRC->clipShader());
1163 if (blitter) {
1164 SkScan::FillIRect(bounds, *fRC, blitter);
1165 return;
1166 }
1167 }
1168
1169 SkMatrix matrix;
1170 SkRect r;
1171
1172 // get a scalar version of our rect
1173 r.set(bounds);
1174
1175 // create shader with offset
1176 matrix.setTranslate(r.fLeft, r.fTop);
1177 SkPaint paintWithShader = make_paint_with_image(paint, bitmap, &matrix);
1178 SkDraw draw(*this);
1179 matrix.reset();
1180 draw.fMatrix = &matrix;
1181 // call ourself with a rect
1182 draw.drawRect(r, paintWithShader);
1183}
1184
1185////////////////////////////////////////////////////////////////////////////////////////////////
1186
1187#ifdef SK_DEBUG
1188
1189void SkDraw::validate() const {
1190 SkASSERT(fMatrix != nullptr);
1191 SkASSERT(fRC != nullptr);
1192
1193 const SkIRect& cr = fRC->getBounds();
1194 SkIRect br;
1195
1196 br.setWH(fDst.width(), fDst.height());
1197 SkASSERT(cr.isEmpty() || br.contains(cr));
1198}
1199
1200#endif
1201
1202////////////////////////////////////////////////////////////////////////////////////////////////
1203
1204#include "include/core/SkPath.h"
1205#include "include/core/SkRegion.h"
1206#include "src/core/SkBlitter.h"
1207#include "src/core/SkDraw.h"
1208
1209bool SkDraw::ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect* clipBounds,
1210 const SkMaskFilter* filter, const SkMatrix* filterMatrix,
1211 SkIRect* bounds) {
1212 // init our bounds from the path
1213 *bounds = devPathBounds.makeOutset(SK_ScalarHalf, SK_ScalarHalf).roundOut();
1214
1215 SkIPoint margin = SkIPoint::Make(0, 0);
1216 if (filter) {
1217 SkASSERT(filterMatrix);
1218
1219 SkMask srcM, dstM;
1220
1221 srcM.fBounds = *bounds;
1222 srcM.fFormat = SkMask::kA8_Format;
1223 if (!as_MFB(filter)->filterMask(&dstM, srcM, *filterMatrix, &margin)) {
1224 return false;
1225 }
1226 }
1227
1228 // (possibly) trim the bounds to reflect the clip
1229 // (plus whatever slop the filter needs)
1230 if (clipBounds) {
1231 // Ugh. Guard against gigantic margins from wacky filters. Without this
1232 // check we can request arbitrary amounts of slop beyond our visible
1233 // clip, and bring down the renderer (at least on finite RAM machines
1234 // like handsets, etc.). Need to balance this invented value between
1235 // quality of large filters like blurs, and the corresponding memory
1236 // requests.
1237 static const int MAX_MARGIN = 128;
1238 if (!bounds->intersect(clipBounds->makeOutset(std::min(margin.fX, MAX_MARGIN),
1239 std::min(margin.fY, MAX_MARGIN)))) {
1240 return false;
1241 }
1242 }
1243
1244 return true;
1245}
1246
1247static void draw_into_mask(const SkMask& mask, const SkPath& devPath,
1248 SkStrokeRec::InitStyle style) {
1249 SkDraw draw;
1250 if (!draw.fDst.reset(mask)) {
1251 return;
1252 }
1253
1254 SkRasterClip clip;
1255 SkMatrix matrix;
1256 SkPaint paint;
1257
1258 clip.setRect(SkIRect::MakeWH(mask.fBounds.width(), mask.fBounds.height()));
1259 matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft),
1260 -SkIntToScalar(mask.fBounds.fTop));
1261
1262 draw.fRC = &clip;
1263 draw.fMatrix = &matrix;
1264 paint.setAntiAlias(true);
1265 switch (style) {
1266 case SkStrokeRec::kHairline_InitStyle:
1267 SkASSERT(!paint.getStrokeWidth());
1268 paint.setStyle(SkPaint::kStroke_Style);
1269 break;
1270 case SkStrokeRec::kFill_InitStyle:
1271 SkASSERT(paint.getStyle() == SkPaint::kFill_Style);
1272 break;
1273
1274 }
1275 draw.drawPath(devPath, paint);
1276}
1277
1278bool SkDraw::DrawToMask(const SkPath& devPath, const SkIRect* clipBounds,
1279 const SkMaskFilter* filter, const SkMatrix* filterMatrix,
1280 SkMask* mask, SkMask::CreateMode mode,
1281 SkStrokeRec::InitStyle style) {
1282 if (devPath.isEmpty()) {
1283 return false;
1284 }
1285
1286 if (SkMask::kJustRenderImage_CreateMode != mode) {
1287 if (!ComputeMaskBounds(devPath.getBounds(), clipBounds, filter,
1288 filterMatrix, &mask->fBounds))
1289 return false;
1290 }
1291
1292 if (SkMask::kComputeBoundsAndRenderImage_CreateMode == mode) {
1293 mask->fFormat = SkMask::kA8_Format;
1294 mask->fRowBytes = mask->fBounds.width();
1295 size_t size = mask->computeImageSize();
1296 if (0 == size) {
1297 // we're too big to allocate the mask, abort
1298 return false;
1299 }
1300 mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc);
1301 }
1302
1303 if (SkMask::kJustComputeBounds_CreateMode != mode) {
1304 draw_into_mask(*mask, devPath, style);
1305 }
1306
1307 return true;
1308}
1309