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. 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 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()); } }