Moved high bitrate texture tests to engine (#179906)

This adds a few goldens where skia supports the file format. This is a
bit more scalable and direct than managing integration tests.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
gaaclarke 2025-12-18 09:04:34 -08:00 committed by GitHub
parent 052435ce7b
commit 5632701286
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 289 additions and 502 deletions

View File

@ -5401,16 +5401,6 @@ targets:
["devicelab", "ios", "mac"]
task_name: wide_gamut_ios
- name: Mac_ios high_bitrate_images
recipe: devicelab/devicelab_drone
presubmit: true
bringup: true
timeout: 60
properties:
tags: >
["devicelab", "ios", "mac"]
task_name: high_bitrate_images_ios
- name: Mac_x64_ios hot_mode_dev_cycle_ios__benchmark
recipe: devicelab/devicelab_drone
presubmit: false

View File

@ -79,7 +79,6 @@
/dev/devicelab/bin/tasks/gradient_dynamic_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/gradient_static_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/gradle_java8_compile_test.dart @jesswrd @flutter/android
/dev/devicelab/bin/tasks/high_bitrate_images_ios.dart @gaaclarke @flutter/engine
/dev/devicelab/bin/tasks/hot_mode_dev_cycle_linux__benchmark.dart @bkonyi @flutter/tool
/dev/devicelab/bin/tasks/hybrid_android_views_integration_test.dart @gmackall @flutter/android
/dev/devicelab/bin/tasks/image_list_jit_reported_duration.dart @jtmcdole @flutter/engine

View File

@ -1,12 +0,0 @@
// Copyright 2014 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
await task(createHighBitrateImagesTest());
}

View File

@ -247,14 +247,6 @@ TaskFunction createWideGamutTest() {
).call;
}
TaskFunction createHighBitrateImagesTest() {
return IntegrationTest(
'${flutterDirectory.path}/dev/integration_tests/high_bitrate_images',
'integration_test/app_test.dart',
createPlatforms: <String>['ios'],
).call;
}
class DriverTest {
DriverTest(
this.testDirectory,

View File

@ -1,10 +0,0 @@
# high_bitrate_images
An integration test used for testing high bitrate image support in the engine.
## Local run
```sh
flutter create --platforms="ios" --no-overwrite .
flutter test integration_test/app_test.dart
```

View File

@ -1,107 +0,0 @@
// Copyright 2014 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:async' show Completer;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:high_bitrate_images/main.dart' as app;
import 'package:integration_test/integration_test.dart';
// See: https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbgr10_xr.
double _decodeBGR10(int x) {
const max = 1.25098;
const min = -0.752941;
const intercept = min;
const double slope = (max - min) / 1024.0;
return (x * slope) + intercept;
}
Uint8List _convertBGRA10XRToBGRA8888(Uint8List bgra10xr) {
final inputByteData = ByteData.sublistView(bgra10xr);
final bgra8888 = Uint8List(bgra10xr.lengthInBytes ~/ 2); // 8 bytes per pixel -> 4 bytes per pixel
final outputByteData = ByteData.view(bgra8888.buffer);
for (var i = 0, j = 0; i < bgra10xr.lengthInBytes; i += 8, j += 4) {
final int pixel = inputByteData.getUint64(i, Endian.host);
final double blue10 = _decodeBGR10((pixel >> 6) & 0x3ff);
final double green10 = _decodeBGR10((pixel >> 22) & 0x3ff);
final double red10 = _decodeBGR10((pixel >> 38) & 0x3ff);
final int blue8 = (blue10.clamp(0.0, 1.0) * 255).round();
final int green8 = (green10.clamp(0.0, 1.0) * 255).round();
final int red8 = (red10.clamp(0.0, 1.0) * 255).round();
const alpha8 = 255; // Assuming opaque for BGRA8888
final int bgra8888Pixel = (alpha8 << 24) | (red8 << 16) | (green8 << 8) | blue8;
outputByteData.setUint32(j, bgra8888Pixel, Endian.host);
}
return bgra8888;
}
Future<ui.Image> _getScreenshot() async {
const channel = MethodChannel('flutter/screenshot');
final result = await channel.invokeMethod('test') as List<Object?>;
expect(result, isNotNull);
expect(result.length, 4);
final [int width, int height, String format, Uint8List bytes] = result as List<dynamic>;
expect(format, equals('MTLPixelFormatBGRA10_XR'));
final completer = Completer<ui.Image>();
final Uint8List pixels = _convertBGRA10XRToBGRA8888(bytes);
ui.decodeImageFromPixels(
pixels,
width,
height,
ui.PixelFormat.bgra8888,
(ui.Image image) => completer.complete(image),
);
return completer.future;
}
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('renders cpu sdfs with rgba32f', (WidgetTester tester) async {
app.testToRun = app.TestType.cpuRgba32fSdf;
app.main();
await tester.pumpAndSettle(const Duration(seconds: 2));
await _getScreenshot();
// TODO(gaaclarke): Turn this into a golden test. This turned out to be
// quite involved so it's deferred.
// expect(
// screenshot,
// matchesGoldenFile('high_bitrate_images.rbga32f'),
// );
});
testWidgets('renders cpu sdfs with r32f', (WidgetTester tester) async {
app.testToRun = app.TestType.cpuR32fSdf;
app.main();
await tester.pumpAndSettle(const Duration(seconds: 2));
await _getScreenshot();
});
testWidgets('renders gpu sdfs with rgba32f', (WidgetTester tester) async {
app.testToRun = app.TestType.gpuRgba32fSdf;
app.main();
await tester.pumpAndSettle(const Duration(seconds: 2));
await _getScreenshot();
});
testWidgets('renders gpu sdfs with r32f', (WidgetTester tester) async {
app.testToRun = app.TestType.gpuR32fSdf;
app.main();
await tester.pumpAndSettle(const Duration(seconds: 2));
await _getScreenshot();
});
});
}

View File

@ -1,154 +0,0 @@
// Copyright 2014 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:async';
import 'dart:math' show sqrt;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class CpuSdfCanvas extends StatefulWidget {
const CpuSdfCanvas({super.key, required this.targetFormat});
final ui.TargetPixelFormat targetFormat;
@override
State<CpuSdfCanvas> createState() => _CpuSdfCanvasState();
}
class _CpuSdfCanvasState extends State<CpuSdfCanvas> {
ui.FragmentShader? _shader;
ui.Image? _sdfImage;
@override
void initState() {
super.initState();
_loadShader().then((ui.FragmentShader shader) {
setState(() {
_shader = shader;
});
});
switch (widget.targetFormat) {
case ui.TargetPixelFormat.rgbaFloat32:
_loadRGBA32FloatSdfImage().then((ui.Image image) {
setState(() {
_sdfImage = image;
});
});
case ui.TargetPixelFormat.rFloat32:
_loadR32FloatSdfImage().then((ui.Image image) {
setState(() {
_sdfImage = image;
});
});
case ui.TargetPixelFormat.dontCare:
assert(false);
}
}
Future<ui.FragmentShader> _loadShader() async {
final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset('shaders/sdf.frag');
return program.fragmentShader();
}
Future<ui.Image> _loadRGBA32FloatSdfImage() async {
const width = 1024;
const height = 1024;
const double radius = width / 4.0;
final floats = List<double>.filled(width * height * 4, 0.0);
for (var i = 0; i < height; ++i) {
for (var j = 0; j < width; ++j) {
double x = j.toDouble();
double y = i.toDouble();
x -= width / 2.0;
y -= height / 2.0;
final double length = sqrt(x * x + y * y);
final int idx = i * width * 4 + j * 4;
floats[idx + 0] = length - radius;
floats[idx + 1] = 0.0;
floats[idx + 2] = 0.0;
floats[idx + 3] = 1.0;
}
}
final floatList = Float32List.fromList(floats);
final intList = Uint8List.view(floatList.buffer);
final completer = Completer<ui.Image>();
ui.decodeImageFromPixels(
intList,
width,
height,
ui.PixelFormat.rgbaFloat32,
targetFormat: widget.targetFormat,
(ui.Image image) {
completer.complete(image);
},
);
return completer.future;
}
Future<ui.Image> _loadR32FloatSdfImage() async {
const width = 1024;
const height = 1024;
const double radius = width / 4.0;
final floats = List<double>.filled(width * height, 0.0);
for (var i = 0; i < height; ++i) {
for (var j = 0; j < width; ++j) {
double x = j.toDouble();
double y = i.toDouble();
x -= width / 2.0;
y -= height / 2.0;
final double length = sqrt(x * x + y * y);
final int idx = i * width + j;
floats[idx] = length - radius;
}
}
final floatList = Float32List.fromList(floats);
final intList = Uint8List.view(floatList.buffer);
final completer = Completer<ui.Image>();
ui.decodeImageFromPixels(
intList,
width,
height,
ui.PixelFormat.rFloat32,
targetFormat: ui.TargetPixelFormat.rFloat32,
(ui.Image image) {
completer.complete(image);
},
);
return completer.future;
}
@override
Widget build(BuildContext context) {
if (_shader == null) {
return const Center(child: CircularProgressIndicator());
}
return SizedBox.expand(
child: (_shader != null && _sdfImage != null)
? CustomPaint(painter: CpuSdfPainter(_shader!, _sdfImage!))
: Container(),
);
}
}
class CpuSdfPainter extends CustomPainter {
CpuSdfPainter(this.shader, this.image);
final ui.FragmentShader shader;
final ui.Image image;
@override
void paint(Canvas canvas, Size size) {
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
shader.setImageSampler(0, image);
final paint = Paint()..shader = shader;
canvas.drawRect(Offset.zero & size, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

View File

@ -1,94 +0,0 @@
// Copyright 2014 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:ui' as ui;
import 'package:flutter/material.dart';
class GpuSdfCanvas extends StatefulWidget {
const GpuSdfCanvas({super.key, required this.targetFormat});
final ui.TargetPixelFormat targetFormat;
@override
State<GpuSdfCanvas> createState() => _GpuSdfCanvasState();
}
class _GpuSdfCanvasState extends State<GpuSdfCanvas> {
ui.Image? _image;
ui.FragmentShader? _circle;
ui.FragmentShader? _sdfShader;
@override
void initState() {
super.initState();
_loadCircleShader().then((ui.FragmentShader shader) {
setState(() {
_circle = shader;
});
});
_loadSdfShader().then((ui.FragmentShader shader) {
setState(() {
_sdfShader = shader;
});
});
}
Future<ui.FragmentShader> _loadCircleShader() async {
final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset(
'shaders/circle_sdf.frag',
);
return program.fragmentShader();
}
Future<ui.FragmentShader> _loadSdfShader() async {
final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset('shaders/sdf.frag');
return program.fragmentShader();
}
ui.Image _loadImage(ui.FragmentShader shader) {
const size = Size(512, 512);
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
final paint = Paint()..shader = _circle;
canvas.drawRect(Offset.zero & size, paint);
final ui.Picture picture = recorder.endRecording();
return picture.toImageSync(
size.width.toInt(),
size.height.toInt(),
targetFormat: widget.targetFormat,
);
}
@override
Widget build(BuildContext context) {
if (_circle == null || _sdfShader == null) {
return const Center(child: CircularProgressIndicator());
}
_image ??= _loadImage(_circle!);
return SizedBox.expand(child: CustomPaint(painter: GpuSdfPainter(_image!, _sdfShader!)));
}
}
class GpuSdfPainter extends CustomPainter {
GpuSdfPainter(this.image, this.shader);
final ui.Image image;
final ui.FragmentShader shader;
@override
void paint(Canvas canvas, Size size) {
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
shader.setImageSampler(0, image);
final paint = Paint()..shader = shader;
canvas.drawRect(Offset.zero & size, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

View File

@ -1,52 +0,0 @@
// Copyright 2014 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:ui' as ui;
import 'package:flutter/material.dart';
import 'cpu_sdf_canvas.dart';
import 'gpu_sdf_canvas.dart';
enum TestType { cpuR32fSdf, cpuRgba32fSdf, gpuR32fSdf, gpuRgba32fSdf }
TestType testToRun = TestType.cpuR32fSdf;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'SDF Demo', theme: ThemeData.dark(), home: const MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
Widget child;
switch (testToRun) {
case TestType.cpuR32fSdf:
child = const CpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rFloat32);
case TestType.cpuRgba32fSdf:
child = const CpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rgbaFloat32);
case TestType.gpuR32fSdf:
child = const GpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rFloat32);
case TestType.gpuRgba32fSdf:
child = const GpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rgbaFloat32);
}
return Scaffold(body: child);
}
}

View File

@ -1,27 +0,0 @@
name: high_bitrate_images
description: "A new Flutter project."
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ^3.11.0-88.0.dev
resolution: workspace
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
flutter:
uses-material-design: true
shaders:
- shaders/circle_sdf.frag
- shaders/sdf.frag
# PUBSPEC CHECKSUM: vrimb3

View File

@ -1,18 +0,0 @@
#version 320 es
// Copyright 2014 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.
#include <flutter/runtime_effect.glsl>
out vec4 fragColor;
uniform vec2 uSize;
void main() {
vec2 p = FlutterFragCoord().xy / uSize;
vec2 center = vec2(0.5, 0.5);
float radius = 0.25;
float d = length(p - center) - radius;
fragColor = vec4(d, 0.0, 0.0, 1.0);
}

View File

@ -43,9 +43,30 @@ if (enable_unittests) {
iplr = true
}
impellerc("compile_sdf_shaders") {
mnemonic = "IMPELLERC_SDF"
shaders = [
"circle_sdf.frag",
"sdf.frag",
]
shader_target_flags = [
"--sksl",
"--runtime-stage-metal",
"--runtime-stage-gles",
"--runtime-stage-vulkan",
]
intermediates_subdir = "iplr"
sl_file_extension = "iplr"
iplr = true
}
test_fixtures("fixtures") {
deps = [ ":compile_general_shaders" ]
fixtures = get_target_outputs(":compile_general_shaders")
deps = [
":compile_general_shaders",
":compile_sdf_shaders",
]
fixtures = get_target_outputs(":compile_general_shaders") +
get_target_outputs(":compile_sdf_shaders")
dest = "$root_gen_dir/flutter/lib/ui"
}
}

View File

@ -7,13 +7,12 @@
#include <flutter/runtime_effect.glsl>
out vec4 fragColor;
uniform vec2 uSize;
uniform sampler2D uTex;
void main() {
vec2 p = FlutterFragCoord().xy / uSize;
float d = texture(uTex, p).r;
vec3 col = d > 0.0 ? vec3(0.0) : vec3(1.0);
fragColor = vec4(col, 1.0);
vec2 p = FlutterFragCoord().xy / uSize;
vec2 center = vec2(0.5, 0.5);
float radius = 0.25;
float d = length(p - center) - radius;
fragColor = vec4(d, 0.0, 0.0, 1.0);
}

View File

@ -0,0 +1,21 @@
#version 320 es
// Copyright 2014 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.
#include <flutter/runtime_effect.glsl>
out vec4 fragColor;
uniform vec2 uSize;
uniform sampler2D uTex;
void main() {
vec2 p = FlutterFragCoord().xy / uSize;
float d = texture(uTex, p).r;
// d > 0 means outside the shape (positive distance), so black.
// d <= 0 means inside the shape (negative distance), so white.
vec3 col = d > 0.0 ? vec3(0.0) : vec3(1.0);
fragColor = vec4(col, 1.0);
}

View File

@ -20,6 +20,7 @@ tests = [
"gesture_settings_test.dart",
"gpu_test.dart",
"gradient_test.dart",
"high_bitrate_texture_test.dart",
"http_allow_http_connections_test.dart",
"http_disallow_http_connections_test.dart",
"image_descriptor_test.dart",

View File

@ -0,0 +1,239 @@
// 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:async';
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui';
import 'package:test/test.dart';
import 'goldens.dart';
import 'impeller_enabled.dart';
void main() async {
final ImageComparer comparer = await ImageComparer.create();
test('decodeImageFromPixels with RGBA Float32', () async {
const dimension = 1024;
final Image image = await _createRGBA32FloatImage(dimension, dimension);
final Image shaderImage = await _drawIntoImage(image);
final ByteData data = (await shaderImage.toByteData())!;
// Check top left is Red
var offset = 0;
expect(data.getUint8(offset), 255, reason: 'Top left Red');
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
// Check center is Black
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
expect(data.getUint8(offset), 0, reason: 'Center Red');
expect(data.getUint8(offset + 1), 0, reason: 'Center Green');
expect(data.getUint8(offset + 2), 0, reason: 'Center Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
await comparer.addGoldenImage(shaderImage, 'decode_image_from_pixels_rgba_float32.png');
image.dispose();
});
test('decodeImageFromPixels with R Float32', () async {
if (!impellerEnabled) {
print('Skipped for Skia');
return;
}
const dimension = 1024;
final Image image = await _createR32FloatImage(dimension, dimension);
final Image shaderImage = await _drawIntoImage(image);
final ByteData data = (await shaderImage.toByteData())!;
// Check top left is Red
var offset = 0;
expect(data.getUint8(offset), 255, reason: 'Top left Red');
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
// Check center is Black
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
expect(data.getUint8(offset), 0, reason: 'Center Red');
expect(data.getUint8(offset + 1), 0, reason: 'Center Green');
expect(data.getUint8(offset + 2), 0, reason: 'Center Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
image.dispose();
});
test('Picture.toImageSync with rgbaFloat32', () async {
const dimension = 1024;
final Image image = await _drawWithCircleShader(
dimension,
dimension,
TargetPixelFormat.rgbaFloat32,
);
final Image shaderImage = await _drawWithShader(image);
final ByteData data = (await shaderImage.toByteData())!;
// Check top left is Black (outside circle, d > 0 -> vec3(0.0))
var offset = 0;
expect(data.getUint8(offset), 0, reason: 'Top left Red');
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
// Check center is White (inside circle, d <= 0 -> vec3(1.0))
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
expect(data.getUint8(offset), 255, reason: 'Center Red');
expect(data.getUint8(offset + 1), 255, reason: 'Center Green');
expect(data.getUint8(offset + 2), 255, reason: 'Center Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
await comparer.addGoldenImage(shaderImage, 'picture_to_image_rgba_float32.png');
image.dispose();
});
test('Picture.toImageSync with rFloat32', () async {
if (!impellerEnabled) {
print('Skipped for Skia');
return;
}
const dimension = 1024;
final Image image = await _drawWithCircleShader(
dimension,
dimension,
TargetPixelFormat.rFloat32,
);
final Image shaderImage = await _drawWithShader(image);
final ByteData data = (await shaderImage.toByteData())!;
// Check top left is Black
var offset = 0;
expect(data.getUint8(offset), 0, reason: 'Top left Red');
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
// Check center is White
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
expect(data.getUint8(offset), 255, reason: 'Center Red');
expect(data.getUint8(offset + 1), 255, reason: 'Center Green');
expect(data.getUint8(offset + 2), 255, reason: 'Center Blue');
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
image.dispose();
});
}
Future<Image> _drawWithShader(Image image) async {
final FragmentProgram program = await FragmentProgram.fromAsset('sdf.frag.iplr');
final FragmentShader shader = program.fragmentShader();
shader.setFloat(0, image.width.toDouble());
shader.setFloat(1, image.height.toDouble());
shader.setImageSampler(0, image);
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
canvas.drawRect(
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
Paint()..shader = shader,
);
final Picture picture = recorder.endRecording();
return picture.toImageSync(image.width, image.height);
}
Future<Image> _drawWithCircleShader(int width, int height, TargetPixelFormat format) async {
final FragmentProgram program = await FragmentProgram.fromAsset('circle_sdf.frag.iplr');
final FragmentShader shader = program.fragmentShader();
shader.setFloat(0, width.toDouble());
shader.setFloat(1, height.toDouble());
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
canvas.drawRect(
Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()),
Paint()..shader = shader,
);
final Picture picture = recorder.endRecording();
return picture.toImageSync(width, height, targetFormat: format);
}
Future<Image> _drawIntoImage(Image image) {
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
canvas.drawImage(image, Offset.zero, Paint());
final Picture picture = recorder.endRecording();
return picture.toImage(image.width, image.height);
}
/// Draws an ellipsis with radii half the dimensions in the rgbaFloat32 pixel
/// format.
Future<Image> _createRGBA32FloatImage(int width, int height) async {
final double radius = width / 4.0;
final floats = List<double>.filled(width * height * 4, 0.0);
for (var i = 0; i < height; ++i) {
for (var j = 0; j < width; ++j) {
double x = j.toDouble();
double y = i.toDouble();
x -= width / 2.0;
y -= height / 2.0;
final double length = math.sqrt(x * x + y * y);
final int idx = i * width * 4 + j * 4;
floats[idx + 0] = length - radius;
floats[idx + 1] = 0.0;
floats[idx + 2] = 0.0;
floats[idx + 3] = 1.0;
}
}
final floatList = Float32List.fromList(floats);
final intList = Uint8List.view(floatList.buffer);
final completer = Completer<Image>();
decodeImageFromPixels(
intList,
width,
height,
PixelFormat.rgbaFloat32,
targetFormat: TargetPixelFormat.rgbaFloat32,
(Image image) {
completer.complete(image);
},
);
return completer.future;
}
/// Draws an ellipsis with radii half the dimensions in the rFloat32 pixel
/// format.
Future<Image> _createR32FloatImage(int width, int height) async {
final double radius = width / 4.0;
final floats = List<double>.filled(width * height, 0.0);
for (var i = 0; i < height; ++i) {
for (var j = 0; j < width; ++j) {
double x = j.toDouble();
double y = i.toDouble();
x -= width / 2.0;
y -= height / 2.0;
final double length = math.sqrt(x * x + y * y);
final int idx = i * width + j;
floats[idx] = length - radius;
}
}
final floatList = Float32List.fromList(floats);
final intList = Uint8List.view(floatList.buffer);
final completer = Completer<Image>();
decodeImageFromPixels(
intList,
width,
height,
PixelFormat.rFloat32,
targetFormat: TargetPixelFormat.rFloat32,
(Image image) {
completer.complete(image);
},
);
return completer.future;
}

View File

@ -28,7 +28,6 @@ workspace:
- dev/integration_tests/external_textures
- dev/integration_tests/flavors
- dev/integration_tests/flutter_gallery
- dev/integration_tests/high_bitrate_images
- dev/integration_tests/hook_user_defines
- dev/integration_tests/ios_add2app_life_cycle/flutterapp
- dev/integration_tests/ios_app_with_extensions