mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
As part of eliminating the Flutter buildroot (https://github.com/flutter/flutter/issues/67373), we are moving all third-party dependencies from //third_party to //flutter/third_party. Once all third-party dependencies have been migrated, tooling and config will be moved and the buildroot will be eliminated altogether. To land this PR, we'll need to: 1. Stop the Skia -> Engine autoroller 1. Update the license goldens in this PR. 1. Update https://skia.googlesource.com/skia-autoroll-internal-config/+/refs/heads/main/skia-infra-public/skia-flutter.cfg#55 1. Land this PR. 1. Re-start the Skia -> Engine autoroller
269 lines
9.6 KiB
Dart
269 lines
9.6 KiB
Dart
// 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:io';
|
|
import 'dart:typed_data';
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:litetest/litetest.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'impeller_enabled.dart';
|
|
|
|
void main() {
|
|
|
|
test('Animation metadata', () async {
|
|
Uint8List data = await _getSkiaResource('alphabetAnim.gif').readAsBytes();
|
|
ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
expect(codec, isNotNull);
|
|
expect(codec.frameCount, 13);
|
|
expect(codec.repetitionCount, 0);
|
|
codec.dispose();
|
|
|
|
data = await _getSkiaResource('test640x479.gif').readAsBytes();
|
|
codec = await ui.instantiateImageCodec(data);
|
|
expect(codec.frameCount, 4);
|
|
expect(codec.repetitionCount, -1);
|
|
});
|
|
|
|
test('Fails with invalid data', () async {
|
|
final Uint8List data = Uint8List.fromList(<int>[1, 2, 3]);
|
|
try {
|
|
await ui.instantiateImageCodec(data);
|
|
fail('exception not thrown');
|
|
} on Exception catch (e) {
|
|
expect(e.toString(), contains('Invalid image data'));
|
|
}
|
|
});
|
|
|
|
test('getNextFrame fails with invalid data', () async {
|
|
Uint8List data = await _getSkiaResource('flutter_logo.jpg').readAsBytes();
|
|
data = Uint8List.view(data.buffer, 0, 4000);
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
try {
|
|
await codec.getNextFrame();
|
|
fail('exception not thrown');
|
|
} on Exception catch (e) {
|
|
if (impellerEnabled) {
|
|
expect(e.toString(), contains('Could not decompress image.'));
|
|
} else {
|
|
expect(e.toString(), contains('Codec failed'));
|
|
}
|
|
}
|
|
});
|
|
|
|
test('nextFrame', () async {
|
|
final Uint8List data = await _getSkiaResource('test640x479.gif').readAsBytes();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
final List<List<int>> decodedFrameInfos = <List<int>>[];
|
|
for (int i = 0; i < 5; i++) {
|
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
|
decodedFrameInfos.add(<int>[
|
|
frameInfo.duration.inMilliseconds,
|
|
frameInfo.image.width,
|
|
frameInfo.image.height,
|
|
]);
|
|
}
|
|
expect(decodedFrameInfos, equals(<List<int>>[
|
|
<int>[200, 640, 479],
|
|
<int>[200, 640, 479],
|
|
<int>[200, 640, 479],
|
|
<int>[200, 640, 479],
|
|
<int>[200, 640, 479],
|
|
]));
|
|
});
|
|
|
|
test('non animated image', () async {
|
|
final Uint8List data = await _getSkiaResource('baby_tux.png').readAsBytes();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
final List<List<int>> decodedFrameInfos = <List<int>>[];
|
|
for (int i = 0; i < 2; i++) {
|
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
|
decodedFrameInfos.add(<int>[
|
|
frameInfo.duration.inMilliseconds,
|
|
frameInfo.image.width,
|
|
frameInfo.image.height,
|
|
]);
|
|
}
|
|
expect(decodedFrameInfos, equals(<List<int>>[
|
|
<int>[0, 240, 246],
|
|
<int>[0, 240, 246],
|
|
]));
|
|
});
|
|
|
|
test('with size', () async {
|
|
final Uint8List data = await _getSkiaResource('baby_tux.png').readAsBytes();
|
|
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(data);
|
|
final ui.Codec codec = await ui.instantiateImageCodecWithSize(
|
|
buffer,
|
|
getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
|
|
return ui.TargetImageSize(
|
|
width: intrinsicWidth ~/ 2,
|
|
height: intrinsicHeight ~/ 2,
|
|
);
|
|
},
|
|
);
|
|
final List<List<int>> decodedFrameInfos = <List<int>>[];
|
|
for (int i = 0; i < 2; i++) {
|
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
|
decodedFrameInfos.add(<int>[
|
|
frameInfo.duration.inMilliseconds,
|
|
frameInfo.image.width,
|
|
frameInfo.image.height,
|
|
]);
|
|
}
|
|
expect(decodedFrameInfos, equals(<List<int>>[
|
|
<int>[0, 120, 123],
|
|
<int>[0, 120, 123],
|
|
]));
|
|
});
|
|
|
|
test('disposed decoded image', () async {
|
|
final Uint8List data = await _getSkiaResource('flutter_logo.jpg').readAsBytes();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
|
expect(frameInfo.image, isNotNull);
|
|
frameInfo.image.dispose();
|
|
try {
|
|
await codec.getNextFrame();
|
|
fail('exception not thrown');
|
|
} on Exception catch (e) {
|
|
expect(e.toString(), contains('Decoded image has been disposed'));
|
|
}
|
|
});
|
|
|
|
test('Animated gif can reuse across multiple frames', () async {
|
|
// Regression test for b/271947267 and https://github.com/flutter/flutter/issues/122134
|
|
|
|
final Uint8List data = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', 'four_frame_with_reuse.gif'),
|
|
).readAsBytesSync();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
|
|
// Capture the final frame of animation. If we have not composited
|
|
// correctly, it will be clipped strangely.
|
|
late ui.FrameInfo frameInfo;
|
|
for (int i = 0; i < 4; i++) {
|
|
frameInfo = await codec.getNextFrame();
|
|
}
|
|
|
|
final ui.Image image = frameInfo.image;
|
|
final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;
|
|
|
|
final String fileName = impellerEnabled ? 'impeller_four_frame_with_reuse_end.png' : 'four_frame_with_reuse_end.png';
|
|
final Uint8List goldenData = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', fileName),
|
|
).readAsBytesSync();
|
|
|
|
expect(imageData.buffer.asUint8List(), goldenData);
|
|
});
|
|
|
|
test('Animated webp can reuse across multiple frames', () async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/61150#issuecomment-679055858
|
|
|
|
final Uint8List data = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', 'heart.webp'),
|
|
).readAsBytesSync();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
|
|
// Capture the final frame of animation. If we have not composited
|
|
// correctly, the hearts will be incorrectly repeated in the image.
|
|
late ui.FrameInfo frameInfo;
|
|
for (int i = 0; i < 69; i++) {
|
|
frameInfo = await codec.getNextFrame();
|
|
}
|
|
|
|
final ui.Image image = frameInfo.image;
|
|
final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;
|
|
|
|
final String fileName = impellerEnabled ? 'impeller_heart_end.png' : 'heart_end.png';
|
|
|
|
final Uint8List goldenData = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', fileName),
|
|
).readAsBytesSync();
|
|
|
|
expect(imageData.buffer.asUint8List(), goldenData);
|
|
|
|
});
|
|
|
|
test('Animated apng can reuse pre-pre-frame', () async {
|
|
// https://github.com/flutter/engine/pull/42153
|
|
|
|
final Uint8List data = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', '2_dispose_op_restore_previous.apng'),
|
|
).readAsBytesSync();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
|
|
// Capture the 67,68,69 frames of animation and then compare the pixels.
|
|
late ui.FrameInfo frameInfo;
|
|
for (int i = 0; i < 70; i++) {
|
|
frameInfo = await codec.getNextFrame();
|
|
if (i >= 67) {
|
|
final ui.Image image = frameInfo.image;
|
|
final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;
|
|
|
|
final String fileName = impellerEnabled ? 'impeller_2_dispose_op_restore_previous.apng.$i.png' : '2_dispose_op_restore_previous.apng.$i.png';
|
|
|
|
final Uint8List goldenData = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', fileName),
|
|
).readAsBytesSync();
|
|
|
|
expect(imageData.buffer.asUint8List(), goldenData);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Animated apng alpha type handling', () async {
|
|
final Uint8List data = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', 'alpha_animated.apng'),
|
|
).readAsBytesSync();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
|
|
// The test image contains two frames of solid red. The first has
|
|
// alpha=0.2, and the second has alpha=0.6.
|
|
ui.Image image = (await codec.getNextFrame()).image;
|
|
ByteData imageData = (await image.toByteData())!;
|
|
expect(imageData.getUint32(0), 0x33000033);
|
|
image = (await codec.getNextFrame()).image;
|
|
imageData = (await image.toByteData())!;
|
|
expect(imageData.getUint32(0), 0x99000099);
|
|
});
|
|
|
|
test('Animated apng background color restore', () async {
|
|
final Uint8List data = File(
|
|
path.join('flutter', 'lib', 'ui', 'fixtures', 'dispose_op_background.apng'),
|
|
).readAsBytesSync();
|
|
final ui.Codec codec = await ui.instantiateImageCodec(data);
|
|
|
|
// First frame is solid red
|
|
ui.Image image = (await codec.getNextFrame()).image;
|
|
ByteData imageData = (await image.toByteData())!;
|
|
expect(imageData.getUint32(0), 0xFF0000FF);
|
|
|
|
// Third frame is blue in the lower right corner.
|
|
await codec.getNextFrame();
|
|
image = (await codec.getNextFrame()).image;
|
|
imageData = (await image.toByteData())!;
|
|
expect(imageData.getUint32(imageData.lengthInBytes - 4), 0x0000FFFF);
|
|
|
|
// Fourth frame is transparent in the lower right corner
|
|
image = (await codec.getNextFrame()).image;
|
|
imageData = (await image.toByteData())!;
|
|
expect(imageData.getUint32(imageData.lengthInBytes - 4), 0x00000000);
|
|
});
|
|
}
|
|
|
|
/// Returns a File handle to a file in the skia/resources directory.
|
|
File _getSkiaResource(String fileName) {
|
|
// As Platform.script is not working for flutter_tester
|
|
// (https://github.com/flutter/flutter/issues/12847), this is currently
|
|
// assuming the curent working directory is engine/src.
|
|
// This is fragile and should be changed once the Platform.script issue is
|
|
// resolved.
|
|
final String assetPath = path.join(
|
|
'flutter', 'third_party', 'skia', 'resources', 'images', fileName,
|
|
);
|
|
return File(assetPath);
|
|
}
|