drawAtlas method Null safety

void drawAtlas(
  1. Image atlas,
  2. List<RSTransform> transforms,
  3. List<Rect> rects,
  4. List<Color>? colors,
  5. BlendMode? blendMode,
  6. Rect? cullRect,
  7. Paint paint
)

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 the colors list using the blendMode 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);
  }
}