Skip to content

Commit 22fb58b

Browse files
authored
update nullability of drawAtlas methods and flesh out docs (flutter#20176)
1 parent 98cfd1d commit 22fb58b

File tree

5 files changed

+607
-61
lines changed

5 files changed

+607
-61
lines changed

lib/ui/painting.dart

Lines changed: 257 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4026,13 +4026,128 @@ class Canvas extends NativeFieldWrapperClass2 {
40264026
List<dynamic>? paintObjects,
40274027
ByteData paintData) native 'Canvas_drawVertices';
40284028

4029-
/// Draws part of an image - the [atlas] - onto the canvas.
4029+
/// Draws many parts of an image - the [atlas] - onto the canvas.
4030+
///
4031+
/// This method allows for optimization when you want to draw many parts of an
4032+
/// image onto the canvas, such as when using sprites or zooming. It is more efficient
4033+
/// than using multiple calls to [drawImageRect] and provides more functionality
4034+
/// to individually transform each image part by a separate rotation or scale and
4035+
/// blend or modulate those parts with a solid color.
4036+
///
4037+
/// The method takes a list of [Rect] objects that each define a piece of the
4038+
/// [atlas] image to be drawn independently. Each [Rect] is associated with an
4039+
/// [RSTransform] entry in the [transforms] list which defines the location,
4040+
/// rotation, and (uniform) scale with which to draw that portion of the image.
4041+
/// Each [Rect] can also be associated with an optional [Color] which will be
4042+
/// composed with the associated image part using the [blendMode] before blending
4043+
/// the result onto the canvas. The full operation can be broken down as:
4044+
///
4045+
/// - Blend each rectangular portion of the image specified by an entry in the
4046+
/// [rects] argument with its associated entry in the [colors] list using the
4047+
/// [blendMode] argument (if a color is specified). In this part of the operation,
4048+
/// the image part will be considered the source of the operation and the associated
4049+
/// color will be considered the destination.
4050+
/// - Blend the result from the first step onto the canvas using the translation,
4051+
/// rotation, and scale properties expressed in the associated entry in the
4052+
/// [transforms] list using the properties of the [Paint] object.
4053+
///
4054+
/// If the first stage of the operation which blends each part of the image with
4055+
/// a color is needed, then both the [colors] and [blendMode] arguments must
4056+
/// not be null and there must be an entry in the [colors] list for each
4057+
/// image part. If that stage is not needed, then the [colors] argument can
4058+
/// be either null or an empty list and the [blendMode] argument may also be null.
4059+
///
4060+
/// The optional [cullRect] argument can provide an estimate of the bounds of the
4061+
/// coordinates rendered by all components of the atlas to be compared against
4062+
/// the clip to quickly reject the operation if it does not intersect.
4063+
///
4064+
/// An example usage to render many sprites from a single sprite atlas with no
4065+
/// rotations or scales:
40304066
///
4031-
/// This method allows for optimization when you only want to draw part of an
4032-
/// image on the canvas, such as when using sprites or zooming. It is more
4033-
/// efficient than using clips or masks directly.
4067+
/// ```dart
4068+
/// class Sprite {
4069+
/// int index;
4070+
/// double centerX;
4071+
/// double centerY;
4072+
/// }
40344073
///
4035-
/// All parameters must not be null.
4074+
/// class MyPainter extends CustomPainter {
4075+
/// // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
4076+
/// ui.Image spriteAtlas;
4077+
/// List<Sprite> allSprites;
4078+
///
4079+
/// @override
4080+
/// void paint(Canvas canvas, Size size) {
4081+
/// Paint paint = Paint();
4082+
/// canvas.drawAtlas(spriteAtlas, <RSTransform>[
4083+
/// for (Sprite sprite in allSprites)
4084+
/// RSTransform.fromComponents(
4085+
/// rotation: 0.0,
4086+
/// scale: 1.0,
4087+
/// // Center of the sprite relative to its rect
4088+
/// anchorX: 5.0,
4089+
/// anchorY: 5.0,
4090+
/// // Location at which to draw the center of the sprite
4091+
/// translateX: sprite.centerX,
4092+
/// translateY: sprite.centerY,
4093+
/// ),
4094+
/// ], <Rect>[
4095+
/// for (Sprite sprite in allSprites)
4096+
/// Rect.fromLTWH(sprite.index * 10.0, 0.0, 10.0, 10.0),
4097+
/// ], null, null, null, paint);
4098+
/// }
4099+
///
4100+
/// ...
4101+
/// }
4102+
/// ```
4103+
///
4104+
/// Another example usage which renders sprites with an optional opacity and rotation:
4105+
///
4106+
/// ```dart
4107+
/// class Sprite {
4108+
/// int index;
4109+
/// double centerX;
4110+
/// double centerY;
4111+
/// int alpha;
4112+
/// double rotation;
4113+
/// }
4114+
///
4115+
/// class MyPainter extends CustomPainter {
4116+
/// // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
4117+
/// ui.Image spriteAtlas;
4118+
/// List<Sprite> allSprites;
4119+
///
4120+
/// @override
4121+
/// void paint(Canvas canvas, Size size) {
4122+
/// Paint paint = Paint();
4123+
/// canvas.drawAtlas(spriteAtlas, <RSTransform>[
4124+
/// for (Sprite sprite in allSprites)
4125+
/// RSTransform.fromComponents(
4126+
/// rotation: sprite.rotation,
4127+
/// scale: 1.0,
4128+
/// // Center of the sprite relative to its rect
4129+
/// anchorX: 5.0,
4130+
/// anchorY: 5.0,
4131+
/// // Location at which to draw the center of the sprite
4132+
/// translateX: sprite.centerX,
4133+
/// translateY: sprite.centerY,
4134+
/// ),
4135+
/// ], <Rect>[
4136+
/// for (Sprite sprite in allSprites)
4137+
/// Rect.fromLTWH(sprite.index * 10.0, 0.0, 10.0, 10.0),
4138+
/// ], <Color>[
4139+
/// for (Sprite sprite in allSprites)
4140+
/// Color.white.withAlpha(sprite.alpha),
4141+
/// ], BlendMode.srcIn, null, paint);
4142+
/// }
4143+
///
4144+
/// ...
4145+
/// }
4146+
/// ```
4147+
///
4148+
/// The length of the [transforms] and [rects] lists must be equal and
4149+
/// if the [colors] argument is not null then it must either be empty or
4150+
/// have the same length as the other two lists.
40364151
///
40374152
/// See also:
40384153
///
@@ -4041,22 +4156,21 @@ class Canvas extends NativeFieldWrapperClass2 {
40414156
void drawAtlas(Image atlas,
40424157
List<RSTransform> transforms,
40434158
List<Rect> rects,
4044-
List<Color> colors,
4045-
BlendMode blendMode,
4159+
List<Color>? colors,
4160+
BlendMode? blendMode,
40464161
Rect? cullRect,
40474162
Paint paint) {
40484163
// ignore: unnecessary_null_comparison
40494164
assert(atlas != null); // atlas is checked on the engine side
40504165
assert(transforms != null); // ignore: unnecessary_null_comparison
40514166
assert(rects != null); // ignore: unnecessary_null_comparison
4052-
assert(colors != null); // ignore: unnecessary_null_comparison
4053-
assert(blendMode != null); // ignore: unnecessary_null_comparison
4167+
assert(colors == null || colors.isEmpty || blendMode != null);
40544168
assert(paint != null); // ignore: unnecessary_null_comparison
40554169

40564170
final int rectCount = rects.length;
40574171
if (transforms.length != rectCount)
40584172
throw ArgumentError('"transforms" and "rects" lengths must match.');
4059-
if (colors.isNotEmpty && colors.length != rectCount)
4173+
if (colors != null && colors.isNotEmpty && colors.length != rectCount)
40604174
throw ArgumentError('If non-null, "colors" length must match that of "transforms" and "rects".');
40614175

40624176
final Float32List rstTransformBuffer = Float32List(rectCount * 4);
@@ -4080,20 +4194,27 @@ class Canvas extends NativeFieldWrapperClass2 {
40804194
rectBuffer[index3] = rect.bottom;
40814195
}
40824196

4083-
final Int32List? colorBuffer = colors.isEmpty ? null : _encodeColorList(colors);
4197+
final Int32List? colorBuffer = (colors == null || colors.isEmpty) ? null : _encodeColorList(colors);
40844198
final Float32List? cullRectBuffer = cullRect?._value32;
40854199

40864200
_drawAtlas(
40874201
paint._objects, paint._data, atlas, rstTransformBuffer, rectBuffer,
4088-
colorBuffer, blendMode.index, cullRectBuffer
4202+
colorBuffer, (blendMode ?? BlendMode.src).index, cullRectBuffer
40894203
);
40904204
}
40914205

4092-
/// Draws part of an image - the [atlas] - onto the canvas.
4206+
/// Draws many parts of an image - the [atlas] - onto the canvas.
4207+
///
4208+
/// This method allows for optimization when you want to draw many parts of an
4209+
/// image onto the canvas, such as when using sprites or zooming. It is more efficient
4210+
/// than using multiple calls to [drawImageRect] and provides more functionality
4211+
/// to individually transform each image part by a separate rotation or scale and
4212+
/// blend or modulate those parts with a solid color. It is also more efficient
4213+
/// than [drawAtlas] as the data in the arguments is already packed in a format
4214+
/// that can be directly used by the rendering code.
40934215
///
4094-
/// This method allows for optimization when you only want to draw part of an
4095-
/// image on the canvas, such as when using sprites or zooming. It is more
4096-
/// efficient than using clips or masks directly.
4216+
/// A full description of how this method uses its arguments to draw onto the
4217+
/// canvas can be found in the description of the [drawAtlas] method.
40974218
///
40984219
/// The [rstTransforms] argument is interpreted as a list of four-tuples, with
40994220
/// each tuple being ([RSTransform.scos], [RSTransform.ssin],
@@ -4103,7 +4224,121 @@ class Canvas extends NativeFieldWrapperClass2 {
41034224
/// tuple being ([Rect.left], [Rect.top], [Rect.right], [Rect.bottom]).
41044225
///
41054226
/// The [colors] argument, which can be null, is interpreted as a list of
4106-
/// 32-bit colors, with the same packing as [Color.value].
4227+
/// 32-bit colors, with the same packing as [Color.value]. If the [colors]
4228+
/// argument is not null then the [blendMode] argument must also not be null.
4229+
///
4230+
/// An example usage to render many sprites from a single sprite atlas with no rotations
4231+
/// or scales:
4232+
///
4233+
/// ```dart
4234+
/// class Sprite {
4235+
/// int index;
4236+
/// double centerX;
4237+
/// double centerY;
4238+
/// }
4239+
///
4240+
/// class MyPainter extends CustomPainter {
4241+
/// // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
4242+
/// ui.Image spriteAtlas;
4243+
/// List<Sprite> allSprites;
4244+
///
4245+
/// @override
4246+
/// void paint(Canvas canvas, Size size) {
4247+
/// // For best advantage, these lists should be cached and only specific
4248+
/// // entries updated when the sprite information changes. This code is
4249+
/// // illustrative of how to set up the data and not a recommendation for
4250+
/// // optimal usage.
4251+
/// Float32List rectList = Float32List(allSprites.length * 4);
4252+
/// Float32List transformList = Float32List(allSprites.length * 4);
4253+
/// for (int i = 0; i < allSprites.length; i++) {
4254+
/// final double rectX = sprite.spriteIndex * 10.0;
4255+
/// rectList[i * 4 + 0] = rectX;
4256+
/// rectList[i * 4 + 1] = 0.0;
4257+
/// rectList[i * 4 + 2] = rectX + 10.0;
4258+
/// rectList[i * 4 + 3] = 10.0;
4259+
///
4260+
/// // This example sets the RSTransform values directly for a common case of no
4261+
/// // rotations or scales and just a translation to position the atlas entry. For
4262+
/// // more complicated transforms one could use the RSTransform class to compute
4263+
/// // the necessary values or do the same math directly.
4264+
/// transformList[i * 4 + 0] = 1.0;
4265+
/// transformList[i * 4 + 1] = 0.0;
4266+
/// transformList[i * 4 + 2] = sprite.centerX - 5.0;
4267+
/// transformList[i * 4 + 2] = sprite.centerY - 5.0;
4268+
/// }
4269+
/// Paint paint = Paint();
4270+
/// canvas.drawAtlas(spriteAtlas, transformList, rectList, null, null, null, paint);
4271+
/// }
4272+
///
4273+
/// ...
4274+
/// }
4275+
/// ```
4276+
///
4277+
/// Another example usage which renders sprites with an optional opacity and rotation:
4278+
///
4279+
/// ```dart
4280+
/// class Sprite {
4281+
/// int index;
4282+
/// double centerX;
4283+
/// double centerY;
4284+
/// int alpha;
4285+
/// double rotation;
4286+
/// }
4287+
///
4288+
/// class MyPainter extends CustomPainter {
4289+
/// // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
4290+
/// ui.Image spriteAtlas;
4291+
/// List<Sprite> allSprites;
4292+
///
4293+
/// @override
4294+
/// void paint(Canvas canvas, Size size) {
4295+
/// // For best advantage, these lists should be cached and only specific
4296+
/// // entries updated when the sprite information changes. This code is
4297+
/// // illustrative of how to set up the data and not a recommendation for
4298+
/// // optimal usage.
4299+
/// Float32List rectList = Float32List(allSprites.length * 4);
4300+
/// Float32List transformList = Float32List(allSprites.length * 4);
4301+
/// Int32List colorList = Int32List(allSprites.length);
4302+
/// for (int i = 0; i < allSprites.length; i++) {
4303+
/// final double rectX = sprite.spriteIndex * 10.0;
4304+
/// rectList[i * 4 + 0] = rectX;
4305+
/// rectList[i * 4 + 1] = 0.0;
4306+
/// rectList[i * 4 + 2] = rectX + 10.0;
4307+
/// rectList[i * 4 + 3] = 10.0;
4308+
///
4309+
/// // This example uses an RSTransform object to compute the necessary values for
4310+
/// // the transform using a factory helper method because the sprites contain
4311+
/// // rotation values which are not trivial to work with. But if the math for the
4312+
/// // values falls out from other calculations on the sprites then the values could
4313+
/// // possibly be generated directly from the sprite update code.
4314+
/// final RSTransform transform = RSTransform.fromComponents(
4315+
/// rotation: sprite.rotation,
4316+
/// scale: 1.0,
4317+
/// // Center of the sprite relative to its rect
4318+
/// anchorX: 5.0,
4319+
/// anchorY: 5.0,
4320+
/// // Location at which to draw the center of the sprite
4321+
/// translateX: sprite.centerX,
4322+
/// translateY: sprite.centerY,
4323+
/// );
4324+
/// transformList[i * 4 + 0] = transform.scos;
4325+
/// transformList[i * 4 + 1] = transform.ssin;
4326+
/// transformList[i * 4 + 2] = transform.tx;
4327+
/// transformList[i * 4 + 2] = transform.ty;
4328+
///
4329+
/// // This example computes the color value directly, but one could also compute
4330+
/// // an actual Color object and use its Color.value getter for the same result.
4331+
/// // Since we are using BlendMode.srcIn, only the alpha component matters for
4332+
/// // these colors which makes this a simple shift operation.
4333+
/// colorList[i] = sprite.alpha << 24;
4334+
/// }
4335+
/// Paint paint = Paint();
4336+
/// canvas.drawAtlas(spriteAtlas, transformList, rectList, colorList, BlendMode.srcIn, null, paint);
4337+
/// }
4338+
///
4339+
/// ...
4340+
/// }
4341+
/// ```
41074342
///
41084343
/// See also:
41094344
///
@@ -4112,29 +4347,28 @@ class Canvas extends NativeFieldWrapperClass2 {
41124347
void drawRawAtlas(Image atlas,
41134348
Float32List rstTransforms,
41144349
Float32List rects,
4115-
Int32List colors,
4116-
BlendMode blendMode,
4350+
Int32List? colors,
4351+
BlendMode? blendMode,
41174352
Rect? cullRect,
41184353
Paint paint) {
41194354
// ignore: unnecessary_null_comparison
41204355
assert(atlas != null); // atlas is checked on the engine side
41214356
assert(rstTransforms != null); // ignore: unnecessary_null_comparison
41224357
assert(rects != null); // ignore: unnecessary_null_comparison
4123-
assert(colors != null); // ignore: unnecessary_null_comparison
4124-
assert(blendMode != null); // ignore: unnecessary_null_comparison
4358+
assert(colors == null || blendMode != null);
41254359
assert(paint != null); // ignore: unnecessary_null_comparison
41264360

41274361
final int rectCount = rects.length;
41284362
if (rstTransforms.length != rectCount)
41294363
throw ArgumentError('"rstTransforms" and "rects" lengths must match.');
41304364
if (rectCount % 4 != 0)
41314365
throw ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.');
4132-
if (colors.length * 4 != rectCount)
4366+
if (colors != null && colors.length * 4 != rectCount)
41334367
throw ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".');
41344368

41354369
_drawAtlas(
41364370
paint._objects, paint._data, atlas, rstTransforms, rects,
4137-
colors, blendMode.index, cullRect?._value32
4371+
colors, (blendMode ?? BlendMode.src).index, cullRect?._value32
41384372
);
41394373
}
41404374

0 commit comments

Comments
 (0)