mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-13 19:30:14 +08:00
162 lines
4.7 KiB
Dart
162 lines
4.7 KiB
Dart
import 'dart:async';
|
|
import 'dart:ui';
|
|
|
|
import 'extensions.dart';
|
|
|
|
export 'extensions.dart';
|
|
|
|
class _Composed {
|
|
/// The image that will be composed.
|
|
final Image image;
|
|
|
|
/// The position where the [image] will be composed.
|
|
final Vector2 position;
|
|
|
|
/// The source on the [image] that will be composed.
|
|
final Rect source;
|
|
|
|
/// The angle (in radians) used to rotate the [image] around it's [anchor].
|
|
final double angle;
|
|
|
|
/// The point around which the [image] will be rotated
|
|
/// (defaults to the centre of the [source]).
|
|
final Vector2 anchor;
|
|
|
|
final bool isAntiAlias;
|
|
|
|
/// The [BlendMode] that will be used when composing the [image].
|
|
final BlendMode blendMode;
|
|
|
|
_Composed(
|
|
this.image,
|
|
this.position,
|
|
this.source,
|
|
this.angle,
|
|
this.anchor,
|
|
this.isAntiAlias,
|
|
this.blendMode,
|
|
);
|
|
}
|
|
|
|
/// The [ImageComposition] allows for composing multiple images onto a single image.
|
|
///
|
|
/// **Note:** Composing images is a heavy async operation and should not be called on each [Game.render].
|
|
class ImageComposition {
|
|
/// The values that will be used to compose the image
|
|
final List<_Composed> _composes = [];
|
|
|
|
/// The [defaultBlendMode] can be used to change how each image will be
|
|
/// blended onto the composition. Defaults to [BlendMode.srcOver].
|
|
final BlendMode defaultBlendMode;
|
|
|
|
/// The [defaultAntiAlias] can be used to if each image will be anti aliased.
|
|
final bool defaultAntiAlias;
|
|
|
|
ImageComposition({
|
|
this.defaultBlendMode = BlendMode.srcOver,
|
|
this.defaultAntiAlias = false,
|
|
}) : assert(defaultBlendMode != null, 'defaultBlendMode can not be null'),
|
|
assert(defaultAntiAlias != null, 'defaultAntiAlias can not be null');
|
|
|
|
/// Add an image to the [ImageComposition].
|
|
///
|
|
/// The [image] will be added at the given [position] on the composition.
|
|
///
|
|
/// An optional [source] can be used to only add the data that is within the
|
|
/// [source] of the [image].
|
|
///
|
|
/// An optional [angle] (in radians) can be used to rotate the image when it
|
|
/// gets added to the composition. It will be rotated in a clock-wise direction
|
|
/// around the [anchor].
|
|
///
|
|
/// By default the [anchor] will be the [source.width] and [source.height]
|
|
/// divided by `2`.
|
|
///
|
|
/// [isAntiAlias] can be used to if the [image] will be anti aliased. Defaults
|
|
/// to [defaultAntiAlias].
|
|
///
|
|
/// The [blendMode] can be used to change how the [image] will be blended onto
|
|
/// the composition. Defaults to [defaultBlendMode].
|
|
void add(
|
|
Image image,
|
|
Vector2 position, {
|
|
Rect source,
|
|
double angle = 0,
|
|
Vector2 anchor,
|
|
bool isAntiAlias,
|
|
BlendMode blendMode,
|
|
}) {
|
|
assert(image != null, 'Image is required to add to the Atlas');
|
|
assert(position != null, 'Position is required');
|
|
assert(angle != null, 'angle can not be null');
|
|
|
|
final imageRect = image.getBoundingRect();
|
|
source ??= imageRect;
|
|
anchor ??= source.toVector2() / 2;
|
|
blendMode ??= defaultBlendMode;
|
|
isAntiAlias ??= defaultAntiAlias;
|
|
|
|
assert(
|
|
imageRect.contains(source.topLeft) &&
|
|
imageRect.contains(source.bottomRight),
|
|
'Source rect should fit within in the image constraints',
|
|
);
|
|
|
|
_composes.add(_Composed(
|
|
image,
|
|
position,
|
|
source,
|
|
angle,
|
|
anchor,
|
|
isAntiAlias,
|
|
blendMode,
|
|
));
|
|
}
|
|
|
|
void clear() => _composes.clear();
|
|
|
|
/// Compose all the images into a single composition.
|
|
Future<Image> compose() async {
|
|
// Rect used to determine how big the output image will be.
|
|
var output = const Rect.fromLTWH(0, 0, 0, 0);
|
|
final recorder = PictureRecorder();
|
|
final canvas = Canvas(recorder);
|
|
|
|
for (final compose in _composes) {
|
|
final image = compose.image;
|
|
final position = compose.position;
|
|
final source = compose.source;
|
|
final rotation = compose.angle;
|
|
final anchor = compose.anchor;
|
|
final isAntiAlias = compose.isAntiAlias;
|
|
final blendMode = compose.blendMode;
|
|
final destination = Rect.fromLTWH(0, 0, source.width, source.height);
|
|
final realDest = destination.translate(position.x, position.y);
|
|
|
|
canvas
|
|
..save()
|
|
..translateVector(position)
|
|
..translateVector(anchor)
|
|
..rotate(rotation)
|
|
..translateVector(-anchor)
|
|
..drawImageRect(
|
|
image,
|
|
source,
|
|
destination,
|
|
Paint()
|
|
..blendMode = blendMode
|
|
..isAntiAlias = isAntiAlias,
|
|
)
|
|
..restore();
|
|
|
|
// Expand the output so it can be used later on when the output image gets
|
|
// created.
|
|
output = output.expandToInclude(realDest);
|
|
}
|
|
|
|
return recorder
|
|
.endRecording()
|
|
.toImage(output.width.toInt(), output.height.toInt());
|
|
}
|
|
}
|