Skip to content

Commit 55debbd

Browse files
Add support for saveLayer/restore (#10)
Adds support for encoding saveLayer and restore in the vector_graphics_codec. Makes the transform property of nodes in the compiler lazy, and re-arranges how nodes are built in order to make it easier to determine if a saveLayer is required.
1 parent 881812b commit 55debbd

File tree

16 files changed

+266
-83
lines changed

16 files changed

+266
-83
lines changed

packages/vector_graphics/lib/src/listener.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,14 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
127127
_paths.add(path);
128128
_currentPath = path;
129129
}
130+
131+
@override
132+
void onRestoreLayer() {
133+
_canvas.restore();
134+
}
135+
136+
@override
137+
void onSaveLayer(int paintId) {
138+
_canvas.saveLayer(null, _paints[paintId]);
139+
}
130140
}

packages/vector_graphics/lib/vector_graphics.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ class _RenderVectorGraphics extends RenderProxyBox {
204204

205205
@override
206206
void paint(PaintingContext context, ui.Offset offset) {
207+
if (offset != Offset.zero) {
208+
context.canvas.translate(offset.dx, offset.dy);
209+
}
207210
context.canvas.drawPicture(picture);
208211
}
209212
}

packages/vector_graphics_codec/lib/vector_graphics_codec.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class VectorGraphicsCodec {
1818
static const int _cubicToTag = 34;
1919
static const int _closeTag = 35;
2020
static const int _finishPathTag = 36;
21+
static const int _saveLayer = 37;
22+
static const int _restore = 38;
2123

2224
static const int _version = 1;
2325
static const int _magicNumber = 0x00882d62;
@@ -78,6 +80,12 @@ class VectorGraphicsCodec {
7880
case _finishPathTag:
7981
listener?.onPathFinished();
8082
continue;
83+
case _restore:
84+
listener?.onRestoreLayer();
85+
continue;
86+
case _saveLayer:
87+
_readSaveLayer(buffer, listener);
88+
continue;
8189
default:
8290
throw StateError('Unknown type tag $type');
8391
}
@@ -313,6 +321,15 @@ class VectorGraphicsCodec {
313321
buffer._currentPathId = -1;
314322
}
315323

324+
void writeSaveLayer(VectorGraphicsBuffer buffer, int paint) {
325+
buffer._putUint8(_saveLayer);
326+
buffer._putInt32(paint);
327+
}
328+
329+
void writeRestoreLayer(VectorGraphicsBuffer buffer) {
330+
buffer._putUint8(_restore);
331+
}
332+
316333
void _readPath(_ReadBuffer buffer, VectorGraphicsCodecListener? listener) {
317334
final int fillType = buffer.getUint8();
318335
final int id = buffer.getInt32();
@@ -364,6 +381,12 @@ class VectorGraphicsCodec {
364381
}
365382
listener?.onDrawVertices(vertices, indices, paintId);
366383
}
384+
385+
void _readSaveLayer(
386+
_ReadBuffer buffer, VectorGraphicsCodecListener? listener) {
387+
final int paintId = buffer.getInt32();
388+
listener?.onSaveLayer(paintId);
389+
}
367390
}
368391

369392
/// Implement this listener class to support decoding of vector_graphics binary
@@ -424,6 +447,12 @@ abstract class VectorGraphicsCodecListener {
424447
Uint16List? indices,
425448
int? paintId,
426449
);
450+
451+
/// Save a new layer with the given [paintId].
452+
void onSaveLayer(int paintId);
453+
454+
/// Restore the save stack.
455+
void onRestoreLayer();
427456
}
428457

429458
enum _CurrentSection {

packages/vector_graphics_codec/test/vector_graphics_codec_test.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,31 @@ void main() {
128128
], null, paintId),
129129
]);
130130
});
131+
132+
test('Can encode opacity/save/restore layers', () {
133+
final buffer = VectorGraphicsBuffer();
134+
final TestListener listener = TestListener();
135+
final int paintId = codec.writeFill(buffer, 0xAA000000, 0);
136+
137+
codec.writeSaveLayer(buffer, paintId);
138+
codec.writeRestoreLayer(buffer);
139+
codec.decode(buffer.done(), listener);
140+
141+
expect(listener.commands, [
142+
OnPaintObject(
143+
color: 0xAA000000,
144+
strokeCap: null,
145+
strokeJoin: null,
146+
blendMode: 0,
147+
strokeMiterLimit: null,
148+
strokeWidth: null,
149+
paintStyle: 0,
150+
id: paintId,
151+
),
152+
OnSaveLayer(paintId),
153+
const OnRestoreLayer(),
154+
]);
155+
});
131156
}
132157

133158
class TestListener extends VectorGraphicsCodecListener {
@@ -197,6 +222,32 @@ class TestListener extends VectorGraphicsCodecListener {
197222
void onPathStart(int id, int fillType) {
198223
commands.add(OnPathStart(id, fillType));
199224
}
225+
226+
@override
227+
void onRestoreLayer() {
228+
commands.add(const OnRestoreLayer());
229+
}
230+
231+
@override
232+
void onSaveLayer(int id) {
233+
commands.add(OnSaveLayer(id));
234+
}
235+
}
236+
237+
class OnSaveLayer {
238+
const OnSaveLayer(this.id);
239+
240+
final int id;
241+
242+
@override
243+
int get hashCode => id.hashCode;
244+
245+
@override
246+
bool operator ==(Object other) => other is OnSaveLayer && other.id == id;
247+
}
248+
249+
class OnRestoreLayer {
250+
const OnRestoreLayer();
200251
}
201252

202253
class OnDrawPath {

packages/vector_graphics_compiler/lib/src/geometry/path.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,10 @@ class PathBuilder implements PathProxy {
315315

316316
/// Adds a rectangle to the new path.
317317
void addRect(Rect rect) {
318-
lineTo(rect.top, rect.right);
318+
moveTo(rect.top, rect.right);
319319
lineTo(rect.bottom, rect.right);
320320
lineTo(rect.bottom, rect.left);
321+
lineTo(rect.top, rect.left);
321322
close();
322323
}
323324

packages/vector_graphics_compiler/lib/src/optimizers.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ class PaintDeduplicator extends Optimizer {
3737

3838
final Map<Paint, int> paints = <Paint, int>{};
3939
for (final DrawCommand command in original.commands) {
40+
if (command.paintId == -1) {
41+
result.commands.add(command);
42+
continue;
43+
}
4044
final Paint originalPaint = original.paints[command.paintId];
4145
final int paintId = paints.putIfAbsent(
4246
original.paints[command.paintId],

packages/vector_graphics_compiler/lib/src/paint.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class Color {
6161
/// The blue channel value from 0..255.
6262
int get b => (0x000000ff & value) >> 0;
6363

64+
/// The opacity channel value from 0..255.
65+
int get a => value >> 24;
66+
6467
@override
6568
String toString() => 'Color(0x${value.toRadixString(16).padLeft(8, '0')})';
6669

packages/vector_graphics_compiler/lib/src/svg/node.dart

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ abstract class Node {
3333
/// the current paint applied as a parent.
3434
Node adoptPaint(Paint? newPaint);
3535

36-
/// Calls `addDrawPath` for all paths contained in this subtree with the
37-
/// specified `transform` in painting order..
36+
/// Calls `build` for all nodes contained in this subtree with the
37+
/// specified `transform` in painting order.
3838
///
3939
/// The transform will be multiplied with any transforms present on
4040
/// [ParentNode]s in the subtree, and applied to any [Path] objects in leaf
4141
/// nodes in the tree. It may be [AffineMatrix.identity] to indicate that no
4242
/// additional transformation is needed.
43-
void addPaths(DrawCommandConcatentor addDrawPath, AffineMatrix transform);
43+
void build(DrawCommandBuilder builder, AffineMatrix transform);
4444
}
4545

4646
/// A graphics node describing a viewport area, which has a [width] and [height]
@@ -124,10 +124,40 @@ class ParentNode extends Node {
124124
);
125125
}
126126

127+
// Whether or not a save layer should be inserted at this node.
128+
bool _requiresSaveLayer() {
129+
final Paint? localPaint = paint;
130+
if (localPaint == null) {
131+
return false;
132+
}
133+
final Fill? fill = localPaint.fill;
134+
if (fill == null) {
135+
return false;
136+
}
137+
if (fill.shader != null) {
138+
return true;
139+
}
140+
if (localPaint.blendMode == null) {
141+
return false;
142+
}
143+
return fill.color == null || fill.color != const Color(0xFF000000);
144+
}
145+
127146
@override
128-
void addPaths(DrawCommandConcatentor addDrawPath, AffineMatrix transform) {
147+
void build(DrawCommandBuilder builder, AffineMatrix transform) {
148+
final bool requiresLayer = _requiresSaveLayer();
149+
if (requiresLayer) {
150+
builder.addSaveLayer(paint!);
151+
}
152+
final AffineMatrix nextTransform = this.transform == null
153+
? transform
154+
: transform.multiplied(this.transform!);
129155
for (final Node child in children) {
130-
child.addPaths(addDrawPath, transform);
156+
child.build(builder, nextTransform);
157+
}
158+
159+
if (requiresLayer) {
160+
builder.restore();
131161
}
132162
}
133163
}
@@ -170,7 +200,7 @@ class PathNode extends Node {
170200
}
171201

172202
@override
173-
void addPaths(DrawCommandConcatentor addDrawPath, AffineMatrix transform) {
174-
addDrawPath(path.transformed(transform), paint!, id);
203+
void build(DrawCommandBuilder builder, AffineMatrix transform) {
204+
builder.addPath(path.transformed(transform), paint!, id);
175205
}
176206
}

0 commit comments

Comments
 (0)