Skip to content

Commit

Permalink
Adds wide gamut saveLayer integration test (#120131)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gaaclarke authored Feb 15, 2023
1 parent dff0955 commit f35de0c
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
}
100 changes: 92 additions & 8 deletions dev/integration_tests/wide_gamut_test/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
],
),
),
Expand Down

0 comments on commit f35de0c

Please sign in to comment.