Skip to content

Commit b65cb97

Browse files
authored
Apply transforms to more nodes, fix bugs in image transforms (#150)
1 parent dfa0ca6 commit b65cb97

File tree

5 files changed

+78
-138
lines changed

5 files changed

+78
-138
lines changed

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

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,46 +68,14 @@ class AffineMatrix {
6868
);
6969
}
7070

71-
/// Create a new [AffineMatrix] with the translation components, if any,
72-
/// removed.
73-
AffineMatrix removeTranslation() {
74-
return AffineMatrix(
75-
a,
76-
b,
77-
c,
78-
d,
79-
0,
80-
0,
81-
_m4_10,
82-
);
83-
}
84-
85-
/// Return the x, y scale transform as a point if possible.
86-
Point? getScale() {
87-
if (b == 0.0 && c == 0.0 && _m4_10 == a) {
88-
return Point(a, d);
89-
}
90-
return null;
91-
}
92-
93-
/// Return a new [AffineMatrix] with any scaling removed, if possible.
94-
AffineMatrix? removeScale() {
95-
if (a == 1.0 && b == 1.0 && _m4_10 == 1.0) {
96-
return this;
97-
}
98-
if (b == 0.0 && c == 0.0 && _m4_10 == a) {
99-
return AffineMatrix(
100-
1.0,
101-
b,
102-
c,
103-
1.0,
104-
e,
105-
f,
106-
1.0,
107-
);
108-
}
109-
// Non-trivial transform.
110-
return null;
71+
/// Whether this matrix can be expressed be applied to a rect without any loss
72+
/// of inforamtion.
73+
///
74+
/// In other words, if this matrix is a simple translate and/or non-negative
75+
/// scale with no rotation or skew, this property is true. Otherwise, it is
76+
/// false.
77+
bool get encodableInRect {
78+
return a > 0 && b == 0 && c == 0 && d > 0 && _m4_10 == a;
11179
}
11280

11381
/// Creates a new affine matrix rotated by `x` and `y`.

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

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import 'dart:typed_data';
66

7+
import 'package:meta/meta.dart';
8+
79
import '../geometry/basic_types.dart';
810
import '../geometry/matrix.dart';
911
import '../geometry/path.dart';
@@ -57,10 +59,30 @@ class _EmptyNode extends Node {
5759
void visitChildren(NodeCallback visitor) {}
5860
}
5961

62+
/// A node that contains a transform operation in the tree of graphics
63+
/// operations.
64+
abstract class TransformableNode extends Node {
65+
/// Constructs a new tree node with [transform].
66+
TransformableNode(this.transform);
67+
68+
/// The descendant child's transform
69+
final AffineMatrix transform;
70+
71+
@override
72+
@mustCallSuper
73+
AffineMatrix concatTransform(AffineMatrix currentTransform) {
74+
if (transform == AffineMatrix.identity) {
75+
return currentTransform;
76+
}
77+
return currentTransform.multiplied(transform);
78+
}
79+
}
80+
6081
/// A node that has attributes in the tree of graphics operations.
61-
abstract class AttributedNode extends Node {
62-
/// Constructs a new tree node with [id] and [paint].
63-
AttributedNode(this.attributes);
82+
abstract class AttributedNode extends TransformableNode {
83+
/// Constructs a new tree node with [attributes].
84+
AttributedNode(this.attributes, {AffineMatrix? precalculatedTransform})
85+
: super(precalculatedTransform ?? attributes.transform);
6486

6587
/// A collection of painting attributes.
6688
///
@@ -116,13 +138,9 @@ class ParentNode extends AttributedNode {
116138
/// Creates a new [ParentNode].
117139
ParentNode(
118140
super.attributes, {
119-
AffineMatrix? precalculatedTransform,
141+
super.precalculatedTransform,
120142
List<Node>? children,
121-
}) : transform = precalculatedTransform ?? attributes.transform,
122-
_children = children ?? <Node>[];
123-
124-
/// The transform to apply to this subtree, if any.
125-
final AffineMatrix transform;
143+
}) : _children = children ?? <Node>[];
126144

127145
/// The child nodes of this node.
128146
final List<Node> _children;
@@ -177,14 +195,6 @@ class ParentNode extends AttributedNode {
177195
_children.add(wrappedChild);
178196
}
179197

180-
@override
181-
AffineMatrix concatTransform(AffineMatrix currentTransform) {
182-
if (transform == AffineMatrix.identity) {
183-
return currentTransform;
184-
}
185-
return currentTransform.multiplied(transform);
186-
}
187-
188198
@override
189199
AttributedNode applyAttributes(SvgAttributes newAttributes) {
190200
return ParentNode(
@@ -237,15 +247,15 @@ class SaveLayerNode extends ParentNode {
237247
}
238248

239249
/// A parent node that applies a clip to its children.
240-
class ClipNode extends Node {
250+
class ClipNode extends TransformableNode {
241251
/// Creates a new clip node that applies clip paths to [child].
242252
ClipNode({
243253
required this.resolver,
244254
required this.child,
245255
required this.clipId,
246-
required this.transform,
256+
required AffineMatrix transform,
247257
String? id,
248-
});
258+
}) : super(transform);
249259

250260
/// Called by visitors to resolve [clipId] to a list of paths.
251261
final Resolver<List<Path>> resolver;
@@ -260,17 +270,6 @@ class ClipNode extends Node {
260270
/// The child to clip.
261271
final Node child;
262272

263-
/// The descendant child's transform
264-
final AffineMatrix transform;
265-
266-
@override
267-
AffineMatrix concatTransform(AffineMatrix currentTransform) {
268-
if (transform == AffineMatrix.identity) {
269-
return currentTransform;
270-
}
271-
return currentTransform.multiplied(transform);
272-
}
273-
274273
@override
275274
void visitChildren(NodeCallback visitor) {
276275
visitor(child);
@@ -283,15 +282,15 @@ class ClipNode extends Node {
283282
}
284283

285284
/// A parent node that applies a mask to its child.
286-
class MaskNode extends Node {
285+
class MaskNode extends TransformableNode {
287286
/// Creates a new mask node that applies [mask] to [child] using [blendMode].
288287
MaskNode({
289288
required this.child,
290289
required this.maskId,
291290
this.blendMode,
292291
required this.resolver,
293-
required this.transform,
294-
});
292+
required AffineMatrix transform,
293+
}) : super(transform);
295294

296295
/// The mask to apply.
297296
final String maskId;
@@ -302,20 +301,9 @@ class MaskNode extends Node {
302301
/// The blend mode to apply when saving a layer for the mask, if any.
303302
final BlendMode? blendMode;
304303

305-
/// The descendant child's transform
306-
final AffineMatrix transform;
307-
308304
/// Called by visitors to resolve [maskId] to an [AttributedNode].
309305
final Resolver<AttributedNode?> resolver;
310306

311-
@override
312-
AffineMatrix concatTransform(AffineMatrix currentTransform) {
313-
if (transform == AffineMatrix.identity) {
314-
return currentTransform;
315-
}
316-
return currentTransform.multiplied(transform);
317-
}
318-
319307
@override
320308
void visitChildren(NodeCallback visitor) {
321309
visitor(child);
@@ -522,35 +510,24 @@ class ImageNode extends AttributedNode {
522510
}
523511

524512
/// A leaf node in the tree that reprents an patterned-node.
525-
class PatternNode extends Node {
513+
class PatternNode extends TransformableNode {
526514
/// Creates a new pattern node that aaples [pattern] to [child].
527515
PatternNode({
528516
required this.child,
529517
required this.patternId,
530518
required this.resolver,
531-
required this.transform,
532-
});
519+
required AffineMatrix transform,
520+
}) : super(transform);
533521

534522
/// This id will match any path or text element that has a non-null patternId.
535523
final String patternId;
536524

537525
/// The child(ren) to apply the pattern to.
538526
final Node child;
539527

540-
/// The descendant child's transform.
541-
final AffineMatrix transform;
542-
543528
/// Called by visitors to resolve [patternId] to an [AttributedNode].
544529
final Resolver<AttributedNode?> resolver;
545530

546-
@override
547-
AffineMatrix concatTransform(AffineMatrix currentTransform) {
548-
if (transform == AffineMatrix.identity) {
549-
return currentTransform;
550-
}
551-
return currentTransform.multiplied(transform);
552-
}
553-
554531
@override
555532
void visitChildren(NodeCallback visitor) {
556533
visitor(child);

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ class ResolvingVisitor extends Visitor<Node, AffineMatrix> {
205205

206206
@override
207207
Node visitImageNode(ImageNode imageNode, AffineMatrix data) {
208+
final AffineMatrix childTransform = imageNode.concatTransform(data);
209+
208210
final SvgAttributes attributes = imageNode.attributes;
209211
final double left = double.parse(attributes.raw['x'] ?? '0');
210212
final double top = double.parse(attributes.raw['y'] ?? '0');
@@ -220,23 +222,11 @@ class ResolvingVisitor extends Visitor<Node, AffineMatrix> {
220222

221223
// Determine if this image can be drawn without any transforms because
222224
// it only has an offset and/or scale.
223-
final AffineMatrix removedTranslation = data.removeTranslation();
224-
225-
final Point? scale = removedTranslation.getScale();
226-
if (scale == null) {
227-
// Non-trivial transform.
228-
return ResolvedImageNode(
229-
data: imageNode.data,
230-
rect: rect,
231-
transform: data,
232-
);
233-
}
234-
final AffineMatrix removedScale = removedTranslation.removeScale()!;
235-
if (removedScale == AffineMatrix.identity) {
225+
if (childTransform.encodableInRect) {
236226
// trivial transform.
237227
return ResolvedImageNode(
238228
data: imageNode.data,
239-
rect: data.transformRect(rect),
229+
rect: childTransform.transformRect(rect),
240230
transform: null,
241231
);
242232
}
@@ -245,7 +235,7 @@ class ResolvingVisitor extends Visitor<Node, AffineMatrix> {
245235
return ResolvedImageNode(
246236
data: imageNode.data,
247237
rect: rect,
248-
transform: data,
238+
transform: childTransform,
249239
);
250240
}
251241

packages/vector_graphics_compiler/test/matrix_test.dart

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,29 +125,17 @@ void main() {
125125
expect(matrixA.hashCode != matrixB.hashCode, true);
126126
});
127127

128-
test('removeTranslation', () {
129-
final AffineMatrix matrixA = AffineMatrix.identity.translated(1, 3);
130-
final AffineMatrix matrixB = AffineMatrix.identity.translated(0, 3);
131-
final AffineMatrix matrixC =
132-
AffineMatrix.identity.translated(1, 3).scaled(10);
133-
134-
expect(matrixA.removeTranslation(), AffineMatrix.identity);
135-
expect(matrixB.removeTranslation(), AffineMatrix.identity);
136-
expect(matrixC.removeTranslation(), AffineMatrix.identity.scaled(10));
137-
expect(AffineMatrix.identity.removeTranslation(), AffineMatrix.identity);
138-
});
139-
140-
test('removeScale', () {
141-
final AffineMatrix matrixA = AffineMatrix.identity.scaled(2);
142-
final AffineMatrix matrixB = AffineMatrix.identity.scaled(2, 3);
143-
final AffineMatrix matrixC = AffineMatrix.identity.xSkewed(2);
144-
final AffineMatrix matrixD = AffineMatrix.identity.ySkewed(2);
145-
final AffineMatrix matrixE = AffineMatrix.identity.rotated(math.pi);
146-
147-
expect(matrixA.removeScale(), AffineMatrix.identity);
148-
expect(matrixB.removeScale(), AffineMatrix.identity);
149-
expect(matrixC.removeScale(), null);
150-
expect(matrixD.removeScale(), null);
151-
expect(matrixE.removeScale(), null);
128+
test('encodableInRect', () {
129+
final AffineMatrix matrixA = AffineMatrix.identity.scaled(2, 3);
130+
final AffineMatrix matrixB = AffineMatrix.identity.scaled(2, -2);
131+
final AffineMatrix matrixC = AffineMatrix.identity.xSkewed(5);
132+
final AffineMatrix matrixD = AffineMatrix.identity.ySkewed(5);
133+
final AffineMatrix matrixE = AffineMatrix.identity.rotated(1.3);
134+
135+
expect(matrixA.encodableInRect, true);
136+
expect(matrixB.encodableInRect, false);
137+
expect(matrixC.encodableInRect, false);
138+
expect(matrixD.encodableInRect, false);
139+
expect(matrixE.encodableInRect, false);
152140
});
153141
}

packages/vector_graphics_compiler/test/resolver_test.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,21 @@ void main() {
100100

101101
expect(visitCount, 2);
102102
});
103+
104+
test('Image transform', () async {
105+
final Node node = await parseToNodeTree('''
106+
<svg width="100" height="100" viewBox="0 0 100 100"
107+
xmlns="http://www.w3.org/2000/svg"
108+
xmlns:xlink="http://www.w3.org/1999/xlink">
109+
<image xlink:href="data:image/png;base64,iVBO" transform="scale(1 -1) translate(50, -50)" x="0" y="0" width="50" height="50"/>
110+
</svg>''');
111+
final Node resolvedNode =
112+
node.accept(ResolvingVisitor(), AffineMatrix.identity);
113+
final ResolvedImageNode imageNode =
114+
queryChildren<ResolvedImageNode>(resolvedNode).single;
115+
expect(
116+
imageNode.transform,
117+
const AffineMatrix(1.0, 0.0, 0.0, -1.0, 50.0, 50.0),
118+
);
119+
});
103120
}

0 commit comments

Comments
 (0)