drawAtlas method Null safety
Draws many parts of an image - the atlas
- onto the canvas.
This method allows for optimization when you want to draw many parts of an image onto the canvas, such as when using sprites or zooming. It is more efficient than using multiple calls to drawImageRect and provides more functionality to individually transform each image part by a separate rotation or scale and blend or modulate those parts with a solid color.
The method takes a list of Rect objects that each define a piece of the
atlas
image to be drawn independently. Each Rect is associated with an
RSTransform entry in the transforms
list which defines the location,
rotation, and (uniform) scale with which to draw that portion of the image.
Each Rect can also be associated with an optional Color which will be
composed with the associated image part using the blendMode
before blending
the result onto the canvas. The full operation can be broken down as:
- Blend each rectangular portion of the image specified by an entry in the
rects
argument with its associated entry in thecolors
list using theblendMode
argument (if a color is specified). In this part of the operation, the image part will be considered the source of the operation and the associated color will be considered the destination. - Blend the result from the first step onto the canvas using the translation,
rotation, and scale properties expressed in the associated entry in the
transforms
list using the properties of the Paint object.
If the first stage of the operation which blends each part of the image with
a color is needed, then both the colors
and blendMode
arguments must
not be null and there must be an entry in the colors
list for each
image part. If that stage is not needed, then the colors
argument can
be either null or an empty list and the blendMode
argument may also be null.
The optional cullRect
argument can provide an estimate of the bounds of the
coordinates rendered by all components of the atlas to be compared against
the clip to quickly reject the operation if it does not intersect.
An example usage to render many sprites from a single sprite atlas with no rotations or scales:
class Sprite {
Sprite(this.index, this.center);
int index;
Offset center;
}
class MyPainter extends CustomPainter {
MyPainter(this.spriteAtlas, this.allSprites);
// assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
ui.Image spriteAtlas;
List<Sprite> allSprites;
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint();
canvas.drawAtlas(spriteAtlas, <RSTransform>[
for (Sprite sprite in allSprites)
RSTransform.fromComponents(
rotation: 0.0,
scale: 1.0,
// Center of the sprite relative to its rect
anchorX: 5.0,
anchorY: 5.0,
// Location at which to draw the center of the sprite
translateX: sprite.center.dx,
translateY: sprite.center.dy,
),
], <Rect>[
for (Sprite sprite in allSprites)
Rect.fromLTWH(sprite.index * 10.0, 0.0, 10.0, 10.0),
], null, null, null, paint);
}
...
}
Another example usage which renders sprites with an optional opacity and rotation:
class Sprite {
Sprite(this.index, this.center, this.alpha, this.rotation);
int index;
Offset center;
int alpha;
double rotation;
}
class MyPainter extends CustomPainter {
MyPainter(this.spriteAtlas, this.allSprites);
// assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
ui.Image spriteAtlas;
List<Sprite> allSprites;
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint();
canvas.drawAtlas(spriteAtlas, <RSTransform>[
for (Sprite sprite in allSprites)
RSTransform.fromComponents(
rotation: sprite.rotation,
scale: 1.0,
// Center of the sprite relative to its rect
anchorX: 5.0,
anchorY: 5.0,
// Location at which to draw the center of the sprite
translateX: sprite.center.dx,
translateY: sprite.center.dy,
),
], <Rect>[
for (Sprite sprite in allSprites)
Rect.fromLTWH(sprite.index * 10.0, 0.0, 10.0, 10.0),
], <Color>[
for (Sprite sprite in allSprites)
Colors.white.withAlpha(sprite.alpha),
], BlendMode.srcIn, null, paint);
}
...
}
The length of the transforms
and rects
lists must be equal and
if the colors
argument is not null then it must either be empty or
have the same length as the other two lists.
See also:
- drawRawAtlas, which takes its arguments as typed data lists rather than objects.
Implementation
void drawAtlas(Image atlas,
List<RSTransform> transforms,
List<Rect> rects,
List<Color>? colors,
BlendMode? blendMode,
Rect? cullRect,
Paint paint) {
assert(atlas != null); // atlas is checked on the engine side
assert(transforms != null);
assert(rects != null);
assert(colors == null || colors.isEmpty || blendMode != null);
assert(paint != null);
final int rectCount = rects.length;
if (transforms.length != rectCount) {
throw ArgumentError('"transforms" and "rects" lengths must match.');
}
if (colors != null && colors.isNotEmpty && colors.length != rectCount) {
throw ArgumentError('If non-null, "colors" length must match that of "transforms" and "rects".');
}
final Float32List rstTransformBuffer = Float32List(rectCount * 4);
final Float32List rectBuffer = Float32List(rectCount * 4);
for (int i = 0; i < rectCount; ++i) {
final int index0 = i * 4;
final int index1 = index0 + 1;
final int index2 = index0 + 2;
final int index3 = index0 + 3;
final RSTransform rstTransform = transforms[i];
final Rect rect = rects[i];
assert(_rectIsValid(rect));
rstTransformBuffer[index0] = rstTransform.scos;
rstTransformBuffer[index1] = rstTransform.ssin;
rstTransformBuffer[index2] = rstTransform.tx;
rstTransformBuffer[index3] = rstTransform.ty;
rectBuffer[index0] = rect.left;
rectBuffer[index1] = rect.top;
rectBuffer[index2] = rect.right;
rectBuffer[index3] = rect.bottom;
}
final Int32List? colorBuffer = (colors == null || colors.isEmpty) ? null : _encodeColorList(colors);
final Float32List? cullRectBuffer = cullRect?._getValue32();
final int qualityIndex = paint.filterQuality.index;
final String? error = _drawAtlas(
paint._objects, paint._data, qualityIndex, atlas._image, rstTransformBuffer, rectBuffer,
colorBuffer, (blendMode ?? BlendMode.src).index, cullRectBuffer
);
if (error != null) {
throw PictureRasterizationException._(error, stack: atlas._debugStack);
}
}