1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "CanvasFrontend.h"
18 #include "CanvasOps.h"
19 #include "CanvasOpBuffer.h"
20
21 namespace android::uirenderer {
22
CanvasStateHelper(int width,int height)23 CanvasStateHelper::CanvasStateHelper(int width, int height) {
24 resetState(width, height);
25 }
26
resetState(int width,int height)27 void CanvasStateHelper::resetState(int width, int height) {
28 mInitialBounds = SkIRect::MakeWH(width, height);
29 mSaveStack.clear();
30 mClipStack.clear();
31 mTransformStack.clear();
32 mSaveStack.emplace_back();
33 mClipStack.emplace_back();
34 mTransformStack.emplace_back();
35
36 clip().bounds = mInitialBounds;
37 }
38
internalSave(SaveEntry saveEntry)39 bool CanvasStateHelper::internalSave(SaveEntry saveEntry) {
40 mSaveStack.push_back(saveEntry);
41 if (saveEntry.matrix) {
42 pushEntry(&mTransformStack);
43 }
44 if (saveEntry.clip) {
45 pushEntry(&mClipStack);
46 return true;
47 }
48 return false;
49 }
50
apply(SkClipOp op,const SkMatrix & matrix,const SkRect & bounds,bool aa,bool fillsBounds)51 void CanvasStateHelper::ConservativeClip::apply(SkClipOp op, const SkMatrix& matrix,
52 const SkRect& bounds, bool aa, bool fillsBounds) {
53 this->aa |= aa;
54
55 if (op == SkClipOp::kIntersect) {
56 SkRect devBounds;
57 bool rect = matrix.mapRect(&devBounds, bounds) && fillsBounds;
58 if (!this->bounds.intersect(aa ? devBounds.roundOut() : devBounds.round())) {
59 this->bounds.setEmpty();
60 }
61 this->rect &= rect;
62 } else {
63 // Difference operations subtracts a region from the clip, so conservatively
64 // the bounds remain unchanged and the shape is unlikely to remain a rect.
65 this->rect = false;
66 }
67 }
68
internalClipRect(const SkRect & rect,SkClipOp op)69 void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
70 clip().apply(op, transform(), rect, /*aa=*/false, /*fillsBounds=*/true);
71 }
72
internalClipPath(const SkPath & path,SkClipOp op)73 void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
74 SkRect bounds = path.getBounds();
75 if (path.isInverseFillType()) {
76 // Toggle op type if the path is inverse filled
77 op = (op == SkClipOp::kIntersect ? SkClipOp::kDifference : SkClipOp::kIntersect);
78 }
79 clip().apply(op, transform(), bounds, /*aa=*/true, /*fillsBounds=*/false);
80 }
81
clip()82 CanvasStateHelper::ConservativeClip& CanvasStateHelper::clip() {
83 return writableEntry(&mClipStack);
84 }
85
transform()86 SkMatrix& CanvasStateHelper::transform() {
87 return writableEntry(&mTransformStack);
88 }
89
internalRestore()90 bool CanvasStateHelper::internalRestore() {
91 // Prevent underflows
92 if (saveCount() <= 1) {
93 return false;
94 }
95
96 SaveEntry entry = mSaveStack[mSaveStack.size() - 1];
97 mSaveStack.pop_back();
98 bool needsRestorePropagation = entry.layer;
99 if (entry.matrix) {
100 popEntry(&mTransformStack);
101 }
102 if (entry.clip) {
103 popEntry(&mClipStack);
104 needsRestorePropagation = true;
105 }
106 return needsRestorePropagation;
107 }
108
getClipBounds() const109 SkRect CanvasStateHelper::getClipBounds() const {
110 SkIRect bounds = clip().bounds;
111
112 SkMatrix inverse;
113 // if we can't invert the CTM, we can't return local clip bounds
114 if (bounds.isEmpty() || !transform().invert(&inverse)) {
115 return SkRect::MakeEmpty();
116 }
117
118 return inverse.mapRect(SkRect::Make(bounds));
119 }
120
quickReject(const SkMatrix & matrix,const SkRect & bounds) const121 bool CanvasStateHelper::ConservativeClip::quickReject(const SkMatrix& matrix,
122 const SkRect& bounds) const {
123 SkRect devRect = matrix.mapRect(bounds);
124 return devRect.isFinite() &&
125 SkIRect::Intersects(this->bounds, aa ? devRect.roundOut() : devRect.round());
126 }
127
quickRejectRect(float left,float top,float right,float bottom) const128 bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
129 return clip().quickReject(transform(), SkRect::MakeLTRB(left, top, right, bottom));
130 }
131
quickRejectPath(const SkPath & path) const132 bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
133 if (this->isClipEmpty()) {
134 // reject everything (prioritized above path inverse fill type).
135 return true;
136 } else {
137 // Don't reject inverse-filled paths, since even if they are "empty" of points/verbs,
138 // they fill out the entire clip.
139 return !path.isInverseFillType() && clip().quickReject(transform(), path.getBounds());
140 }
141 }
142
143 } // namespace android::uirenderer
144