Unverified Commit f35de0c8 authored by gaaclarke's avatar gaaclarke Committed by GitHub

Adds wide gamut saveLayer integration test (#120131)

* Added wide gamut integration test that uses save layers.

* updated the test to support bgra too

* analysis errors

* switched blend mode to multiply to avoid future optimizations
parent dff09558
......@@ -23,40 +23,80 @@ bool _isAlmost(double x, double y, double epsilon) {
return (x - y).abs() < epsilon;
}
bool _findDeepRedBGRA10(Uint8List bytes, int width, int height) {
final ByteData byteData = ByteData.sublistView(bytes);
expect(bytes.lengthInBytes, width * height * 8);
expect(bytes.lengthInBytes, byteData.lengthInBytes);
bool foundDeepRed = false;
for (int i = 0; i < bytes.lengthInBytes; i += 8) {
final int pixel = byteData.getUint64(i, Endian.host);
final double blue = _decodeBGR10((pixel >> 6) & 0x3ff);
final double green = _decodeBGR10((pixel >> 22) & 0x3ff);
final double red = _decodeBGR10((pixel >> 38) & 0x3ff);
if (_isAlmost(red, 1.0931, 0.01) &&
_isAlmost(green, -0.2268, 0.01) &&
_isAlmost(blue, -0.1501, 0.01)) {
foundDeepRed = true;
}
}
return foundDeepRed;
}
bool _findDeepRedBGR10(Uint8List bytes, int width, int height) {
final ByteData byteData = ByteData.sublistView(bytes);
expect(bytes.lengthInBytes, width * height * 4);
expect(bytes.lengthInBytes, byteData.lengthInBytes);
bool foundDeepRed = false;
for (int i = 0; i < bytes.lengthInBytes; i += 4) {
final int pixel = byteData.getUint32(i, Endian.host);
final double blue = _decodeBGR10(pixel & 0x3ff);
final double green = _decodeBGR10((pixel >> 10) & 0x3ff);
final double red = _decodeBGR10((pixel >> 20) & 0x3ff);
if (_isAlmost(red, 1.0931, 0.01) &&
_isAlmost(green, -0.2268, 0.01) &&
_isAlmost(blue, -0.1501, 0.01)) {
foundDeepRed = true;
}
}
return foundDeepRed;
}
bool _findDeepRed(List<Object?> result) {
expect(result, isNotNull);
expect(result.length, 4);
final int width = (result[0] as int?)!;
final int height = (result[1] as int?)!;
final String format = (result[2] as String?)!;
if (format == 'MTLPixelFormatBGR10_XR') {
return _findDeepRedBGR10((result[3] as Uint8List?)!, width, height);
} else if (format == 'MTLPixelFormatBGRA10_XR') {
return _findDeepRedBGRA10((result[3] as Uint8List?)!, width, height);
} else {
fail('Unsupported pixel format: $format');
}
}
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('look for display p3 deepest red', (WidgetTester tester) async {
app.main();
app.run(app.Setup.image);
await tester.pumpAndSettle(const Duration(seconds: 2));
const MethodChannel channel = MethodChannel('flutter/screenshot');
final List<Object?> result =
await channel.invokeMethod('test') as List<Object?>;
expect(_findDeepRed(result), isTrue);
});
testWidgets('look for display p3 deepest red', (WidgetTester tester) async {
app.run(app.Setup.canvasSaveLayer);
await tester.pumpAndSettle(const Duration(seconds: 2));
const MethodChannel channel = MethodChannel('flutter/screenshot');
final List<Object?> result =
await channel.invokeMethod('test') as List<Object?>;
expect(result, isNotNull);
expect(result.length, 4);
final int width = (result[0] as int?)!;
final int height = (result[1] as int?)!;
final String format = (result[2] as String?)!;
expect(format, 'MTLPixelFormatBGR10_XR');
final Uint8List bytes = (result[3] as Uint8List?)!;
final ByteData byteData = ByteData.sublistView(bytes);
expect(bytes.lengthInBytes, width * height * 4);
expect(bytes.lengthInBytes, byteData.lengthInBytes);
bool foundDeepRed = false;
for (int i = 0; i < bytes.lengthInBytes; i += 4) {
final int pixel = byteData.getUint32(i, Endian.host);
final double blue = _decodeBGR10(pixel & 0x3ff);
final double green = _decodeBGR10((pixel >> 10) & 0x3ff);
final double red = _decodeBGR10((pixel >> 20) & 0x3ff);
if (_isAlmost(red, 1.0931, 0.01) &&
_isAlmost(green, -0.2268, 0.01) &&
_isAlmost(blue, -0.1501, 0.01)) {
foundDeepRed = true;
}
}
expect(foundDeepRed, isTrue);
expect(_findDeepRed(result), isTrue);
});
});
}
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:convert' show base64Decode;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
/// A 100x100 png in Display P3 colorspace.
......@@ -63,12 +64,21 @@ const String _displayP3Logo =
'ElWKtuy2OXm9QtxYmoawGJUL3jcwHpiBNxagGJUL3jcwHpiBNxagGJUL3jcwHpiBNxagGJUL3j'
'cwHpiBNx6gU/2fLWVmm7wQAAAABJRU5ErkJggg==';
void main() {
runApp(const MyApp());
void main() => run(Setup.canvasSaveLayer);
enum Setup {
image,
canvasSaveLayer,
}
void run(Setup setup) {
runApp(MyApp(setup));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
const MyApp(this._setup, {super.key});
final Setup _setup;
@override
Widget build(BuildContext context) {
......@@ -77,27 +87,101 @@ class MyApp extends StatelessWidget {
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Wide Gamut Test'),
home: MyHomePage(_setup, title: 'Wide Gamut Test'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
class _SaveLayerDrawer extends CustomPainter {
_SaveLayerDrawer(this._image);
final ui.Image? _image;
@override
void paint(Canvas canvas, Size size) {
if (_image != null) {
final Rect imageRect = Rect.fromCenter(
center: Offset.zero,
width: _image!.width.toDouble(),
height: _image!.height.toDouble());
canvas.saveLayer(
imageRect,
Paint());
canvas.drawRect(
imageRect.inflate(-_image!.width.toDouble() / 4.0),
Paint()
..style = PaintingStyle.stroke
..color = const Color(0xffffffff)
..strokeWidth = 3);
canvas.saveLayer(
imageRect,
Paint()..blendMode = BlendMode.multiply);
canvas.drawImage(_image!,
Offset(-_image!.width / 2.0, -_image!.height / 2.0), Paint());
canvas.restore();
canvas.restore();
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Future<ui.Image> _loadImage() async {
final ui.ImmutableBuffer buffer =
await ui.ImmutableBuffer.fromUint8List(base64Decode(_displayP3Logo));
final ui.ImageDescriptor descriptor =
await ui.ImageDescriptor.encoded(buffer);
final ui.Codec codec = await descriptor.instantiateCodec();
return (await codec.getNextFrame()).image;
}
class MyHomePage extends StatefulWidget {
const MyHomePage(this.setup, {super.key, required this.title});
final Setup setup;
final String title;
@override
State<StatefulWidget> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ui.Image? _image;
@override
void initState() {
if (widget.setup == Setup.canvasSaveLayer) {
_loadImage().then((ui.Image? value) {
setState(() {
_image = value;
});
});
}
super.initState();
}
@override
Widget build(BuildContext context) {
late Widget imageWidget;
switch (widget.setup) {
case Setup.image:
imageWidget = Image.memory(base64Decode(_displayP3Logo));
break;
case Setup.canvasSaveLayer:
imageWidget = CustomPaint(painter: _SaveLayerDrawer(_image));
break;
}
return Scaffold(
appBar: AppBar(
title: Text(title),
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.memory(base64Decode(_displayP3Logo)),
imageWidget,
],
),
),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment