/
mask.dart
187 lines (142 loc) · 5.44 KB
/
mask.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
part of '../display.dart';
/// A [Mask] describes a geometric shape to show only a portion of a
/// [DisplayObject]. Pixels inside of the shape are visible and pixels
/// outside of the shape are invisible.
///
/// The mask is placed relative to the [DisplayObject] where it is applied
/// to. However, you can set the [relativeToParent] property to `true` to
/// placed the mask relativ to the DisplayObjects parent.
///
/// Example:
///
/// var rifleScope = Sprite();
/// rifleScope.mask = Mask.circle(0, 0, 50);
/// rifleScope.addChild(world);
abstract class Mask implements RenderMask {
/// You can use the [transformationMatrix] to change the size,
/// position, scale, rotation etc. from the Mask.
final Matrix transformationMatrix = Matrix.fromIdentity();
/// Set to `true` to place the [Mask] relative to the DisplayObjects
/// parent. The default value is `false` and therefore the mask is
/// placed relative to the DisplayObject where it is applied to.
@override
bool relativeToParent = false;
@override
bool border = false;
@override
int borderColor = 0xFF000000;
@override
int borderWidth = 1;
Mask();
bool hitTest(num x, num y);
@override
void renderMask(RenderState renderState);
//---------------------------------------------------------------------------
/// Create a rectangular mask.
factory Mask.rectangle(num x, num y, num width, num height) {
final rectangle = Rectangle<num>(x, y, width, height);
return _RectangleMask(rectangle);
}
/// Create a circular mask.
factory Mask.circle(num x, num y, num radius) {
final graphics = Graphics();
graphics.circle(x, y, radius);
graphics.fillColor(Color.Magenta);
return _GraphicsMask(graphics);
}
/// Create a custom mask with a polygonal shape defined by [points].
factory Mask.custom(List<Point<num>> points) {
final graphics = Graphics();
points.forEach((p) => graphics.lineTo(p.x, p.y));
graphics.fillColor(Color.Magenta);
return _GraphicsMask(graphics);
}
/// Create a custom mask defined by a [Graphics] object.
factory Mask.graphics(Graphics graphics) => _GraphicsMask(graphics);
/// Create a custom mask defined by a [Shape] object.
factory Mask.shape(Shape shape) => _ShapeMask(shape);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
abstract class _TransformedMask extends Mask {
bool hitTestTransformed(num x, num y);
@override
bool hitTest(num x, num y) {
final mtx = transformationMatrix;
final deltaX = x - mtx.tx;
final deltaY = y - mtx.ty;
final maskX = (mtx.d * deltaX - mtx.c * deltaY) / mtx.det;
final maskY = (mtx.a * deltaY - mtx.b * deltaX) / mtx.det;
return hitTestTransformed(maskX, maskY);
}
void renderMaskTransformed(RenderState renderState);
@override
void renderMask(RenderState renderState) {
renderState.push(transformationMatrix, 1.0);
renderMaskTransformed(renderState);
renderState.pop();
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class _RectangleMask extends _TransformedMask implements ScissorRenderMask {
final Rectangle<num> rectangle;
_RectangleMask(this.rectangle);
@override
Rectangle<num>? getScissorRectangle(RenderState renderState) {
renderState.push(transformationMatrix, 1.0);
final matrix = renderState.globalMatrix;
final aligned = similar(matrix.b, 0.0) && similar(matrix.c, 0.0);
final result = aligned ? matrix.transformRectangle(rectangle) : null;
renderState.pop();
return result;
}
@override
bool hitTestTransformed(num x, num y) => rectangle.contains(x, y);
@override
void renderMaskTransformed(RenderState renderState) {
final renderContext = renderState.renderContext;
if (renderContext is RenderContextCanvas) {
renderContext.setTransform(renderState.globalMatrix);
renderContext.rawContext.rect(
rectangle.left, rectangle.top, rectangle.width, rectangle.height);
} else {
final l = rectangle.left;
final t = rectangle.top;
final r = rectangle.right;
final b = rectangle.bottom;
renderState.renderTriangle(l, t, r, t, r, b, Color.Magenta);
renderState.renderTriangle(l, t, r, b, l, b, Color.Magenta);
}
}
}
//-----------------------------------------------------------------------------
class _GraphicsMask extends _TransformedMask {
final Graphics graphics;
_GraphicsMask(this.graphics);
@override
bool hitTestTransformed(num x, num y) => graphics.hitTest(x, y);
@override
void renderMaskTransformed(RenderState renderState) {
graphics.renderMask(renderState);
}
}
//-----------------------------------------------------------------------------
class _ShapeMask extends _TransformedMask {
final Shape shape;
_ShapeMask(this.shape);
@override
bool hitTestTransformed(num x, num y) {
final mtx = shape.transformationMatrix;
final deltaX = x - mtx.tx;
final deltaY = y - mtx.ty;
final maskX = (mtx.d * deltaX - mtx.c * deltaY) / mtx.det;
final maskY = (mtx.a * deltaY - mtx.b * deltaX) / mtx.det;
return shape.graphics.hitTest(maskX, maskY);
}
@override
void renderMaskTransformed(RenderState renderState) {
renderState.globalMatrix.prepend(shape.transformationMatrix);
shape.graphics.renderMask(renderState);
}
}