blob: 3fe451784bcf16353f96215a087d01aa00630d96 [file] [log] [blame] [edit]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'dart:ui';
import 'package:test/test.dart';
import 'package:vector_math/vector_math_64.dart';
import 'goldens.dart';
typedef CanvasCallback = void Function(Canvas canvas);
void main() {
test('Vertices checks', () {
try {
Vertices(
VertexMode.triangles,
const <Offset>[Offset.zero, Offset.zero, Offset.zero],
indices: Uint16List.fromList(const <int>[0, 2, 5]),
);
throw 'Vertices did not throw the expected error.';
} on ArgumentError catch (e) {
expect('$e', 'Invalid argument(s): "indices" values must be valid indices in the positions list (i.e. numbers in the range 0..2), but indices[2] is 5, which is too big.');
}
Vertices( // This one does not throw.
VertexMode.triangles,
const <Offset>[Offset.zero],
).dispose();
Vertices( // This one should not throw.
VertexMode.triangles,
const <Offset>[Offset.zero, Offset.zero, Offset.zero],
indices: Uint16List.fromList(const <int>[0, 2, 1, 2, 0, 1, 2, 0]), // Uint16List implements List<int> so this is ok.
).dispose();
});
test('Vertices.raw checks', () {
expect(() {
Vertices.raw(
VertexMode.triangles,
Float32List.fromList(const <double>[0.0]),
);
}, throwsA(isA<ArgumentError>().having((ArgumentError e) => '$e', 'message', 'Invalid argument(s): "positions" must have an even number of entries (each coordinate is an x,y pair).')));
Object? indicesError;
try {
Vertices.raw(
VertexMode.triangles,
Float32List.fromList(const <double>[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
indices: Uint16List.fromList(const <int>[0, 2, 5]),
);
throw 'Vertices.raw did not throw the expected error.';
} on ArgumentError catch (e) {
indicesError = e;
}
expect('$indicesError', 'Invalid argument(s): "indices" values must be valid indices in the positions list (i.e. numbers in the range 0..2), but indices[2] is 5, which is too big.');
Vertices.raw( // This one does not throw.
VertexMode.triangles,
Float32List.fromList(const <double>[0.0, 0.0]),
).dispose();
Vertices.raw( // This one should not throw.
VertexMode.triangles,
Float32List.fromList(const <double>[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
indices: Uint16List.fromList(const <int>[0, 2, 1, 2, 0, 1, 2, 0]),
).dispose();
});
test('BackdropFilter with multiple clips', () async {
// Regression test for https://github.com/flutter/flutter/issues/144211
Picture makePicture(CanvasCallback callback) {
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
callback(canvas);
return recorder.endRecording();
}
final SceneBuilder sceneBuilder = SceneBuilder();
final Picture redClippedPicture = makePicture((Canvas canvas) {
canvas.drawPaint(Paint()..color = const Color(0xFFFFFFFF));
canvas.clipRect(const Rect.fromLTRB(10, 10, 200, 200));
canvas.clipRect(const Rect.fromLTRB(11, 10, 300, 200));
canvas.drawPaint(Paint()..color = const Color(0xFFFF0000));
});
sceneBuilder.addPicture(Offset.zero, redClippedPicture);
final Float64List matrix = Float64List(16);
sceneBuilder.pushBackdropFilter(ImageFilter.matrix(matrix));
final Picture whitePicture = makePicture((Canvas canvas) {
canvas.drawPaint(Paint()..color = const Color(0xFFFFFFFF));
});
sceneBuilder.addPicture(Offset.zero, whitePicture);
final Scene scene = sceneBuilder.build();
final Image image = scene.toImageSync(20, 20);
final ByteData data = (await image.toByteData())!;
expect(data.buffer.asUint32List().length, 20 * 20);
// If clipping went wrong as in the linked issue, there will be red pixels.
for (final int color in data.buffer.asUint32List()) {
expect(color, 0xFFFFFFFF);
}
scene.dispose();
image.dispose();
whitePicture.dispose();
redClippedPicture.dispose();
});
Image backdropBlurWithTileMode(TileMode tileMode) {
Picture makePicture(CanvasCallback callback) {
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
callback(canvas);
return recorder.endRecording();
}
const double rectSize = 10;
const int count = 50;
const double imgSize = rectSize * count;
final Picture blueGreenGridPicture = makePicture((Canvas canvas) {
const Color white = Color(0xFFFFFFFF);
const Color purple = Color(0xFFFF00FF);
const Color blue = Color(0xFF0000FF);
const Color green = Color(0xFF00FF00);
const Color yellow = Color(0xFFFFFF00);
const Color red = Color(0xFFFF0000);
canvas.drawColor(white, BlendMode.src);
for (int i = 0; i < count; i++) {
for (int j = 0; j < count; j++) {
final bool rectOdd = (i + j) & 1 == 0;
final Color fg = (i < count / 2)
? ((j < count / 2) ? green : blue)
: ((j < count / 2) ? yellow : red);
canvas.drawRect(Rect.fromLTWH(i * rectSize, j * rectSize, rectSize, rectSize),
Paint()..color = rectOdd ? fg : white);
}
}
canvas.drawRect(const Rect.fromLTWH(0, 0, imgSize, 1), Paint()..color = purple);
canvas.drawRect(const Rect.fromLTWH(0, 0, 1, imgSize), Paint()..color = purple);
canvas.drawRect(const Rect.fromLTWH(0, imgSize - 1, imgSize, 1), Paint()..color = purple);
canvas.drawRect(const Rect.fromLTWH(imgSize - 1, 0, 1, imgSize), Paint()..color = purple);
});
final SceneBuilder sceneBuilder = SceneBuilder();
sceneBuilder.addPicture(Offset.zero, blueGreenGridPicture);
sceneBuilder.pushBackdropFilter(ImageFilter.blur(sigmaX: 20, sigmaY: 20, tileMode: tileMode));
final Scene scene = sceneBuilder.build();
final Image image = scene.toImageSync(imgSize.round(), imgSize.round());
scene.dispose();
blueGreenGridPicture.dispose();
return image;
}
test('BackdropFilter with Blur honors TileMode.decal', () async {
final Image image = backdropBlurWithTileMode(TileMode.decal);
final ImageComparer comparer = await ImageComparer.create();
await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_decal_tile_mode.png');
image.dispose();
});
test('BackdropFilter with Blur honors TileMode.clamp', () async {
final Image image = backdropBlurWithTileMode(TileMode.clamp);
final ImageComparer comparer = await ImageComparer.create();
await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_clamp_tile_mode.png');
image.dispose();
});
test('BackdropFilter with Blur honors TileMode.mirror', () async {
final Image image = backdropBlurWithTileMode(TileMode.mirror);
final ImageComparer comparer = await ImageComparer.create();
await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_mirror_tile_mode.png');
image.dispose();
});
test('BackdropFilter with Blur honors TileMode.repeated', () async {
final Image image = backdropBlurWithTileMode(TileMode.repeated);
final ImageComparer comparer = await ImageComparer.create();
await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_repeated_tile_mode.png');
image.dispose();
});
test('ImageFilter.matrix defaults to FilterQuality.medium', () {
final Float64List data = Matrix4.identity().storage;
expect(
ImageFilter.matrix(data).toString(),
'ImageFilter.matrix($data, FilterQuality.medium)',
);
});
test('Picture.toImage generates mip maps', () async {
// Draw a grid of red and blue squares. When averaged together via
// mip maps, this should result in a purplish color. If there are no
// mip maps, the original red and blue colors will be preserved regardless
// of scale or number of pixels.
late final Image image;
{
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
final Color color = (i + j).isEven ? const Color(0xFFFF0000) : const Color(0xFF0000FF);
canvas.drawRect(Rect.fromLTWH(i * 5, j * 5, 5, 5), Paint()..color = color);
}
}
final Picture picture = recorder.endRecording();
image = await picture.toImage(100, 100);
}
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.save();
canvas.scale(0.25, 0.25);
canvas.drawImage(image, Offset.zero, Paint()..filterQuality = FilterQuality.medium);
canvas.restore();
final Picture picture = recorder.endRecording();
final Image resultImage = await picture.toImage(10, 10);
final ByteData data = (await resultImage.toByteData())!;
final Int32List colors = data.buffer.asInt32List();
for (int i = 0; i < colors.length; i++) {
expect(colors[i], isNot(const Color(0xFFFF0000)));
expect(colors[i], isNot(const Color(0xFF0000FF)));
}
});
}