Line data Source code
1 : import 'dart:async';
2 : import 'dart:ui';
3 :
4 : import 'extensions.dart';
5 :
6 : export 'extensions.dart';
7 :
8 : class _Composed {
9 : /// The image that will be composed.
10 : final Image image;
11 :
12 : /// The position where the [image] will be composed.
13 : final Vector2 position;
14 :
15 : /// The source on the [image] that will be composed.
16 : final Rect source;
17 :
18 : /// The angle (in radians) used to rotate the [image] around it's [anchor].
19 : final double angle;
20 :
21 : /// The point around which the [image] will be rotated
22 : /// (defaults to the centre of the [source]).
23 : final Vector2 anchor;
24 :
25 : final bool isAntiAlias;
26 :
27 : /// The [BlendMode] that will be used when composing the [image].
28 : final BlendMode blendMode;
29 :
30 0 : _Composed(
31 : this.image,
32 : this.position,
33 : this.source,
34 : this.angle,
35 : this.anchor,
36 : this.isAntiAlias,
37 : this.blendMode,
38 : );
39 : }
40 :
41 : /// The [ImageComposition] allows for composing multiple images onto a single image.
42 : ///
43 : /// **Note:** Composing images is a heavy async operation and should not be called inside the game loop.
44 : class ImageComposition {
45 : /// The values that will be used to compose the image
46 : final List<_Composed> _composes = [];
47 :
48 : /// The [defaultBlendMode] can be used to change how each image will be
49 : /// blended onto the composition. Defaults to [BlendMode.srcOver].
50 : final BlendMode defaultBlendMode;
51 :
52 : /// The [defaultAntiAlias] can be used to if each image will be anti aliased.
53 : final bool defaultAntiAlias;
54 :
55 1 : ImageComposition({
56 : this.defaultBlendMode = BlendMode.srcOver,
57 : this.defaultAntiAlias = false,
58 : });
59 :
60 : /// Add an image to the [ImageComposition].
61 : ///
62 : /// The [image] will be added at the given [position] on the composition.
63 : ///
64 : /// An optional [source] can be used to only add the data that is within the
65 : /// [source] of the [image].
66 : ///
67 : /// An optional [angle] (in radians) can be used to rotate the image when it
68 : /// gets added to the composition. It will be rotated in a clock-wise direction
69 : /// around the [anchor].
70 : ///
71 : /// By default the [anchor] will be the [source].width and [source].height
72 : /// divided by `2`.
73 : ///
74 : /// [isAntiAlias] can be used to if the [image] will be anti aliased. Defaults
75 : /// to [defaultAntiAlias].
76 : ///
77 : /// The [blendMode] can be used to change how the [image] will be blended onto
78 : /// the composition. Defaults to [defaultBlendMode].
79 1 : void add(
80 : Image image,
81 : Vector2 position, {
82 : Rect? source,
83 : double angle = 0,
84 : Vector2? anchor,
85 : bool? isAntiAlias,
86 : BlendMode? blendMode,
87 : }) {
88 1 : final imageRect = image.getBoundingRect();
89 : source ??= imageRect;
90 2 : anchor ??= source.toVector2() / 2;
91 1 : blendMode ??= defaultBlendMode;
92 1 : isAntiAlias ??= defaultAntiAlias;
93 :
94 : assert(
95 4 : imageRect.topLeft <= source.topLeft &&
96 3 : imageRect.bottomRight >= source.bottomRight,
97 : 'Source rect should fit within in the image constraints',
98 : );
99 :
100 0 : _composes.add(_Composed(
101 : image,
102 : position,
103 : source,
104 : angle,
105 : anchor,
106 : isAntiAlias,
107 : blendMode,
108 : ));
109 : }
110 :
111 0 : void clear() => _composes.clear();
112 :
113 : /// Compose all the images into a single composition.
114 0 : Future<Image> compose() async {
115 : // Rect used to determine how big the output image will be.
116 : var output = const Rect.fromLTWH(0, 0, 0, 0);
117 0 : final recorder = PictureRecorder();
118 0 : final canvas = Canvas(recorder);
119 :
120 0 : for (final compose in _composes) {
121 0 : final image = compose.image;
122 0 : final position = compose.position;
123 0 : final source = compose.source;
124 0 : final rotation = compose.angle;
125 0 : final anchor = compose.anchor;
126 0 : final isAntiAlias = compose.isAntiAlias;
127 0 : final blendMode = compose.blendMode;
128 0 : final destination = Rect.fromLTWH(0, 0, source.width, source.height);
129 0 : final realDest = destination.translate(position.x, position.y);
130 :
131 : canvas
132 0 : ..save()
133 0 : ..translateVector(position)
134 0 : ..translateVector(anchor)
135 0 : ..rotate(rotation)
136 0 : ..translateVector(-anchor)
137 0 : ..drawImageRect(
138 : image,
139 : source,
140 : destination,
141 0 : Paint()
142 0 : ..blendMode = blendMode
143 0 : ..isAntiAlias = isAntiAlias,
144 : )
145 0 : ..restore();
146 :
147 : // Expand the output so it can be used later on when the output image gets
148 : // created.
149 0 : output = output.expandToInclude(realDest);
150 : }
151 :
152 : return recorder
153 0 : .endRecording()
154 0 : .toImage(output.width.toInt(), output.height.toInt());
155 : }
156 : }
|