drawRawAtlas method Null safety

void drawRawAtlas(
  1. Image atlas,
  2. Float32List rstTransforms,
  3. Float32List rects,
  4. Int32List? 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. It is also more efficient than drawAtlas as the data in the arguments is already packed in a format that can be directly used by the rendering code.

A full description of how this method uses its arguments to draw onto the canvas can be found in the description of the drawAtlas method.

The rstTransforms argument is interpreted as a list of four-tuples, with each tuple being (RSTransform.scos, RSTransform.ssin, RSTransform.tx, RSTransform.ty).

The rects argument is interpreted as a list of four-tuples, with each tuple being (Rect.left, Rect.top, Rect.right, Rect.bottom).

The colors argument, which can be null, is interpreted as a list of 32-bit colors, with the same packing as Color.value. If the colors argument is not null then the blendMode argument must also not be null.

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) {
    // For best advantage, these lists should be cached and only specific
    // entries updated when the sprite information changes. This code is
    // illustrative of how to set up the data and not a recommendation for
    // optimal usage.
    Float32List rectList = Float32List(allSprites.length * 4);
    Float32List transformList = Float32List(allSprites.length * 4);
    for (int i = 0; i < allSprites.length; i++) {
      Sprite sprite = allSprites[i];
      final double rectX = sprite.index * 10.0;
      rectList[i * 4 + 0] = rectX;
      rectList[i * 4 + 1] = 0.0;
      rectList[i * 4 + 2] = rectX + 10.0;
      rectList[i * 4 + 3] = 10.0;

      // This example sets the RSTransform values directly for a common case of no
      // rotations or scales and just a translation to position the atlas entry. For
      // more complicated transforms one could use the RSTransform class to compute
      // the necessary values or do the same math directly.
      transformList[i * 4 + 0] = 1.0;
      transformList[i * 4 + 1] = 0.0;
      transformList[i * 4 + 2] = sprite.center.dx - 5.0;
      transformList[i * 4 + 3] = sprite.center.dy - 5.0;
    }
    Paint paint = Paint();
    canvas.drawRawAtlas(spriteAtlas, transformList, rectList, 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) {
    // For best advantage, these lists should be cached and only specific
    // entries updated when the sprite information changes. This code is
    // illustrative of how to set up the data and not a recommendation for
    // optimal usage.
    Float32List rectList = Float32List(allSprites.length * 4);
    Float32List transformList = Float32List(allSprites.length * 4);
    Int32List colorList = Int32List(allSprites.length);
    for (int i = 0; i < allSprites.length; i++) {
      Sprite sprite = allSprites[i];
      final double rectX = sprite.index * 10.0;
      rectList[i * 4 + 0] = rectX;
      rectList[i * 4 + 1] = 0.0;
      rectList[i * 4 + 2] = rectX + 10.0;
      rectList[i * 4 + 3] = 10.0;

      // This example uses an RSTransform object to compute the necessary values for
      // the transform using a factory helper method because the sprites contain
      // rotation values which are not trivial to work with. But if the math for the
      // values falls out from other calculations on the sprites then the values could
      // possibly be generated directly from the sprite update code.
      final RSTransform transform = 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,
      );
      transformList[i * 4 + 0] = transform.scos;
      transformList[i * 4 + 1] = transform.ssin;
      transformList[i * 4 + 2] = transform.tx;
      transformList[i * 4 + 3] = transform.ty;

      // This example computes the color value directly, but one could also compute
      // an actual Color object and use its Color.value getter for the same result.
      // Since we are using BlendMode.srcIn, only the alpha component matters for
      // these colors which makes this a simple shift operation.
      colorList[i] = sprite.alpha << 24;
    }
    Paint paint = Paint();
    canvas.drawRawAtlas(spriteAtlas, transformList, rectList, colorList, BlendMode.srcIn, null, paint);
  }

  ...
}

See also:

  • drawAtlas, which takes its arguments as objects rather than typed data lists.

Implementation

void drawRawAtlas(Image atlas,
                  Float32List rstTransforms,
                  Float32List rects,
                  Int32List? colors,
                  BlendMode? blendMode,
                  Rect? cullRect,
                  Paint paint) {
  assert(atlas != null); // atlas is checked on the engine side
  assert(rstTransforms != null);
  assert(rects != null);
  assert(colors == null || blendMode != null);
  assert(paint != null);

  final int rectCount = rects.length;
  if (rstTransforms.length != rectCount) {
    throw ArgumentError('"rstTransforms" and "rects" lengths must match.');
  }
  if (rectCount % 4 != 0) {
    throw ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.');
  }
  if (colors != null && colors.length * 4 != rectCount) {
    throw ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".');
  }
  final int qualityIndex = paint.filterQuality.index;

  final String? error = _drawAtlas(
    paint._objects, paint._data, qualityIndex, atlas._image, rstTransforms, rects,
    colors, (blendMode ?? BlendMode.src).index, cullRect?._getValue32()
  );

  if (error != null) {
    throw PictureRasterizationException._(error, stack: atlas._debugStack);
  }
}