Skip to content

Commit

Permalink
refactor: Clean up of top-level tests (#1386)
Browse files Browse the repository at this point in the history
  • Loading branch information
st-pasha committed Feb 21, 2022
1 parent 08e8eac commit e50003e
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 222 deletions.
161 changes: 1 addition & 160 deletions packages/flame/lib/image_composition.dart
Original file line number Diff line number Diff line change
@@ -1,160 +1 @@
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.
///
/// **Note:** Composing images is a heavy async operation and should not be
/// called inside the game loop.
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,
});

/// 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,
}) {
final imageRect = image.getBoundingRect();
source ??= imageRect;
anchor ??= source.toVector2() / 2;
blendMode ??= defaultBlendMode;
isAntiAlias ??= defaultAntiAlias;

assert(
imageRect.topLeft <= source.topLeft &&
imageRect.bottomRight >= 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<Image> 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());
}
}
export 'src/image_composition.dart';
160 changes: 160 additions & 0 deletions packages/flame/lib/src/image_composition.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import 'dart:async';
import 'dart:ui';

import '../extensions.dart';

export '../extensions.dart';

/// The [ImageComposition] allows for composing multiple images onto a single
/// image.
///
/// **Note:** Composing images is a heavy async operation and should not be
/// called inside the game loop.
class ImageComposition {
ImageComposition({
this.defaultBlendMode = BlendMode.srcOver,
this.defaultAntiAlias = false,
});

/// The values that will be used to compose the image
final List<_Fragment> _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;

/// 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,
}) {
final imageRect = image.getBoundingRect();
source ??= imageRect;
anchor ??= source.toVector2() / 2;
blendMode ??= defaultBlendMode;
isAntiAlias ??= defaultAntiAlias;

assert(
imageRect.topLeft <= source.topLeft &&
imageRect.bottomRight >= source.bottomRight,
'Source rect should fit within the image',
);

_composes.add(
_Fragment(
image,
position,
source,
angle,
anchor,
isAntiAlias,
blendMode,
),
);
}

void clear() => _composes.clear();

/// Compose all the images into a single composition.
Future<Image> 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());
}
}

class _Fragment {
_Fragment(
this.image,
this.position,
this.source,
this.angle,
this.anchor,
this.isAntiAlias,
this.blendMode,
);

/// 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;
}
2 changes: 1 addition & 1 deletion packages/flame/lib/src/sprite.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'dart:ui';

import '../image_composition.dart';
import 'anchor.dart';
import 'assets/images.dart';
import 'flame.dart';
import 'image_composition.dart';
import 'palette.dart';

/// A [Sprite] is a region of an [Image] that can be rendered in the Canvas.
Expand Down
2 changes: 1 addition & 1 deletion packages/flame/lib/src/timer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'dart:ui';
/// Simple utility class that helps handling time counting and implementing
/// interval like events.
///
/// Timer auto start by default.
/// Timer auto-starts by default.
class Timer {
final double limit;
VoidCallback? onTick;
Expand Down
13 changes: 7 additions & 6 deletions packages/flame/test/anchor_test.dart
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import 'package:flame/extensions.dart';
import 'package:flame/src/anchor.dart';
import 'package:flame_test/flame_test.dart';
import 'package:test/test.dart';

void main() {
group('Anchor', () {
test('can parse to and from string', () {
test('parses to and from string', () {
expect(Anchor.center.toString(), 'center');
expect(Anchor.valueOf('topRight'), Anchor.topRight);

expect(Anchor.values.length, 9);

for (final value in Anchor.values) {
final thereAndBack = Anchor.valueOf(value.toString());
expect(thereAndBack, value);
}
});

test('can parse custom anchor', () {
expect(const Anchor(0.2, 0.2).toString(), 'Anchor(0.2, 0.2)');
expect(Anchor.valueOf('Anchor(0.2, 0.2)'), const Anchor(0.2, 0.2));
test('parses custom anchor', () {
const anchor = Anchor(0.2, 0.2);
expect(anchor.toString(), 'Anchor(0.2, 0.2)');
expect(Anchor.valueOf('Anchor(0.2, 0.2)'), anchor);
});

test('fail to parse invalid anchor', () {
expect(
() => Anchor.valueOf('foobar'),
throwsA(isA<AssertionError>()),
failsAssert(),
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MockImage extends Mock implements Image {
}

void main() {
group('ImagesCache', () {
group('Images', () {
test('clear', () {
final cache = Images();
final image = MockImage();
Expand Down
File renamed without changes.
Loading

0 comments on commit e50003e

Please sign in to comment.