未验证 提交 1bf5c8b0 编写于 作者: F Ferhat 提交者: GitHub

[web] Implement tilemode for gradient shaders. (#22597)

上级 a9f332c0
repository: https://github.com/flutter/goldens.git
revision: 67f22ef933be27ba2be8b27df1b71b2c69eb86e5
revision: 06e0333b8371965dce5dc05e140e6dfb454f33fa
......@@ -66,7 +66,7 @@ class CkGradientLinear extends CkShader implements ui.Gradient {
assert(_offsetIsValid(to)),
assert(colors != null), // ignore: unnecessary_null_comparison
assert(tileMode != null), // ignore: unnecessary_null_comparison
this.matrix4 = matrix == null ? null : _FastMatrix64(matrix) {
this.matrix4 = matrix {
if (assertionsEnabled) {
_validateColorStops(colors, colorStops);
}
......@@ -77,7 +77,7 @@ class CkGradientLinear extends CkShader implements ui.Gradient {
final List<ui.Color> colors;
final List<double>? colorStops;
final ui.TileMode tileMode;
final _FastMatrix64? matrix4;
final Float64List? matrix4;
@override
SkShader createDefault() {
......
......@@ -126,7 +126,7 @@ class _WebGlRenderer implements _GlRenderer {
final String fragmentShader = _writeVerticesFragmentShader();
_GlContext gl = _GlContextCache.createGlContext(widthInPixels, heightInPixels)!;
_GlProgram glProgram = gl.useAndCacheProgram(vertexShader, fragmentShader)!;
_GlProgram glProgram = gl.useAndCacheProgram(vertexShader, fragmentShader);
Object transformUniform = gl.getUniformLocation(glProgram.program,
'u_ctransform');
......@@ -487,7 +487,7 @@ class _GlContext {
left, top, _widthInPixels, _heightInPixels]);
}
_GlProgram? useAndCacheProgram(
_GlProgram useAndCacheProgram(
String vertexShaderSource, String fragmentShaderSource) {
String cacheKey = '$vertexShaderSource||$fragmentShaderSource';
_GlProgram? cachedProgram = _programCache[cacheKey];
......
......@@ -50,7 +50,7 @@ class GradientSweep extends EngineGradient {
_GlProgram glProgram = gl.useAndCacheProgram(
_WebGlRenderer.writeBaseVertexShader(),
_createSweepFragmentShader(normalizedGradient, tileMode))!;
_createSweepFragmentShader(normalizedGradient, tileMode));
Object tileOffset = gl.getUniformLocation(glProgram.program, 'u_tile_offset');
double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width);
......@@ -98,34 +98,10 @@ class GradientSweep extends EngineGradient {
method.addStatement(''
'float st = angle;');
method.addStatement('vec4 bias;');
method.addStatement('vec4 scale;');
// Write uniforms for each threshold, bias and scale.
for (int i = 0; i < (gradient.thresholdCount - 1) ~/ 4 + 1; i++) {
builder.addUniform(ShaderType.kVec4, name: 'threshold_${i}');
}
for (int i = 0; i < gradient.thresholdCount; i++) {
builder.addUniform(ShaderType.kVec4, name: 'bias_$i');
builder.addUniform(ShaderType.kVec4, name: 'scale_$i');
}
String probeName = 'st';
switch (tileMode) {
case ui.TileMode.clamp:
break;
case ui.TileMode.repeated:
method.addStatement('float tiled_st = fract(st);');
probeName = 'tiled_st';
break;
case ui.TileMode.mirror:
method.addStatement('float t_1 = (st - 1.0);');
method.addStatement('float tiled_st = abs((t_1 - 2.0 * floor(t_1 * 0.5)) - 1.0);');
probeName = 'tiled_st';
break;
}
_writeUnrolledBinarySearch(method, 0, gradient.thresholdCount - 1,
probe: probeName, sourcePrefix: 'threshold',
biasName: 'bias', scaleName: 'scale');
final String probeName =
_writeSharedGradientShader(builder, method, gradient, tileMode);
method.addStatement('${fragColor.name} = ${probeName} * scale + bias;');
String shader = builder.build();
return shader;
}
......@@ -140,18 +116,17 @@ class GradientSweep extends EngineGradient {
}
class GradientLinear extends EngineGradient {
GradientLinear(
this.from,
this.to,
this.colors,
this.colorStops,
this.tileMode,
Float64List? matrix,
) : assert(_offsetIsValid(from)),
GradientLinear(this.from,
this.to,
this.colors,
this.colorStops,
this.tileMode,
Float32List? matrix,)
: assert(_offsetIsValid(from)),
assert(_offsetIsValid(to)),
assert(colors != null), // ignore: unnecessary_null_comparison
assert(tileMode != null), // ignore: unnecessary_null_comparison
this.matrix4 = matrix == null ? null : _FastMatrix64(matrix),
this.matrix4 = matrix == null ? null : _FastMatrix32(matrix),
super._() {
if (assertionsEnabled) {
_validateColorStops(colors, colorStops);
......@@ -163,12 +138,22 @@ class GradientLinear extends EngineGradient {
final List<ui.Color> colors;
final List<double>? colorStops;
final ui.TileMode tileMode;
final _FastMatrix64? matrix4;
final _FastMatrix32? matrix4;
@override
html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D? ctx,
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
if (tileMode == ui.TileMode.clamp) {
return _createCanvasGradient(ctx, shaderBounds, density);
} else {
initWebGl();
return _createGlGradient(ctx, shaderBounds, density);
}
}
html.CanvasGradient _createCanvasGradient(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
_FastMatrix64? matrix4 = this.matrix4;
_FastMatrix32? matrix4 = this.matrix4;
html.CanvasGradient gradient;
final double offsetX = shaderBounds!.left;
final double offsetY = shaderBounds.top;
......@@ -180,29 +165,175 @@ class GradientLinear extends EngineGradient {
final double fromY = matrix4.transformedY + centerY;
matrix4.transform(to.dx - centerX, to.dy - centerY);
gradient = ctx!.createLinearGradient(fromX - offsetX, fromY - offsetY,
matrix4.transformedX + centerX - offsetX, matrix4.transformedY - offsetY + centerY);
matrix4.transformedX + centerX - offsetX,
matrix4.transformedY - offsetY + centerY);
} else {
gradient = ctx!.createLinearGradient(from.dx - offsetX, from.dy - offsetY, to.dx - offsetX, to.dy - offsetY);
gradient = ctx!.createLinearGradient(
from.dx - offsetX, from.dy - offsetY, to.dx - offsetX,
to.dy - offsetY);
}
_addColorStopsToCanvasGradient(gradient, colors, colorStops);
return gradient;
}
/// Creates a linear gradient with tiling repeat or mirror.
html.CanvasPattern _createGlGradient(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
assert(shaderBounds != null);
int widthInPixels = shaderBounds!.width.ceil();
int heightInPixels = shaderBounds.height.ceil();
assert(widthInPixels > 0 && heightInPixels > 0);
// Render gradient into a bitmap and create a canvas pattern.
_OffScreenCanvas offScreenCanvas =
_OffScreenCanvas(widthInPixels, heightInPixels);
_GlContext gl = _OffScreenCanvas.supported
? _GlContext.fromOffscreenCanvas(offScreenCanvas._canvas!)
: _GlContext.fromCanvas(offScreenCanvas._glCanvas!,
webGLVersion == WebGLVersion.webgl1);
gl.setViewportSize(widthInPixels, heightInPixels);
NormalizedGradient normalizedGradient = NormalizedGradient(
colors, stops: colorStops);
_GlProgram glProgram = gl.useAndCacheProgram(
_WebGlRenderer.writeBaseVertexShader(),
_createLinearFragmentShader(normalizedGradient, tileMode));
final List<double>? colorStops = this.colorStops;
if (colorStops == null) {
assert(colors.length == 2);
gradient.addColorStop(0, colorToCssString(colors[0])!);
gradient.addColorStop(1, colorToCssString(colors[1])!);
return gradient;
// Setup from/to uniforms.
//
// To compute t value between 0..1 for any point on the screen,
// we need to use from,to point pair to construct a matrix that will
// take any fragment coordinate and transform it to a t value.
//
// We compute the matrix by:
// 1- Shift from,to vector to origin.
// 2- Rotate the vector to align with x axis.
// 3- Scale it to unit vector.
double dx = to.dx - from.dx;
double dy = to.dy - from.dy;
double length = math.sqrt(dx * dx + dy * dy);
// sin(theta) = dy / length.
// cos(theta) = dx / length.
// Flip dy for gl flip.
double sinVal = length < kFltEpsilon ? 0 : -dy / length;
double cosVal = length < kFltEpsilon ? 1 : dx / length;
final Matrix4 translateToOrigin = matrix4 == null
? Matrix4.translationValues(-from.dx, -from.dy, 0)
: Matrix4.fromFloat32List(matrix4!.matrix)
..translate(-from.dx, -from.dy);
// Rotate around Z axis.
final Matrix4 rotationZ = Matrix4.identity();
final Float32List storage = rotationZ.storage;
storage[0] = cosVal;
storage[1] = -sinVal;
storage[4] = sinVal;
storage[5] = cosVal;
Matrix4 gradientTransform = Matrix4.identity();
if (length > kFltEpsilon) {
gradientTransform.scale(1.0 / length);
}
gradientTransform.multiply(rotationZ);
gradientTransform.multiply(translateToOrigin);
// Setup gradient uniforms for t search.
normalizedGradient.setupUniforms(gl, glProgram);
// Setup matrix transform uniform.
Object gradientMatrix = gl.getUniformLocation(
glProgram.program, 'm_gradient');
gl.setUniformMatrix4fv(gradientMatrix, false, gradientTransform.storage);
Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution');
gl.setUniform2f(
uRes, widthInPixels.toDouble(), heightInPixels.toDouble());
// Draw gradient and convert to pattern.
Object? imageBitmap = _glRenderer!.drawRect(ui.Rect.fromLTWH(
0, 0, shaderBounds.width, shaderBounds.height) /* !! shaderBounds */,
gl,
glProgram, normalizedGradient, widthInPixels, heightInPixels,
);
return ctx!.createPattern(imageBitmap!, 'no-repeat')!;
}
String _createLinearFragmentShader(NormalizedGradient gradient,
ui.TileMode tileMode) {
ShaderBuilder builder = ShaderBuilder.fragment(webGLVersion);
builder.floatPrecision = ShaderPrecision.kMedium;
builder.addIn(ShaderType.kVec4, name: 'v_color');
builder.addUniform(ShaderType.kVec2, name: 'u_resolution');
builder.addUniform(ShaderType.kMat4, name: 'm_gradient');
ShaderDeclaration fragColor = builder.fragmentColor;
ShaderMethod method = builder.addMethod('main');
// Linear gradient.
// Multiply with m_gradient transform to convert from fragment coordinate to
// distance on the from-to line.
method.addStatement(
'vec4 localCoord = vec4(gl_FragCoord.x, '
'u_resolution.y - gl_FragCoord.y, 0, 1) * m_gradient;');
method.addStatement('float st = localCoord.x;');
final String probeName =
_writeSharedGradientShader(builder, method, gradient, tileMode);
method.addStatement('${fragColor.name} = ${probeName} * scale + bias;');
String shader = builder.build();
return shader;
}
}
void _addColorStopsToCanvasGradient(html.CanvasGradient gradient,
List<ui.Color> colors, List<double>? colorStops) {
if (colorStops == null) {
assert(colors.length == 2);
gradient.addColorStop(0, colorToCssString(colors[0])!);
gradient.addColorStop(1, colorToCssString(colors[1])!);
} else {
for (int i = 0; i < colors.length; i++) {
gradient.addColorStop(colorStops[i], colorToCssString(colors[i])!);
}
return gradient;
}
}
// TODO(flutter_web): For transforms and tile modes implement as webgl
// For now only GradientRotation is supported in flutter which is implemented
// for linear gradient.
// See https://github.com/flutter/flutter/issues/32819
/// Writes shader code to map fragment value to gradient color.
///
/// Returns name of gradient treshold variable to use to compute color.
String _writeSharedGradientShader(ShaderBuilder builder,
ShaderMethod method,
NormalizedGradient gradient,
ui.TileMode tileMode) {
method.addStatement('vec4 bias;');
method.addStatement('vec4 scale;');
// Write uniforms for each threshold, bias and scale.
for (int i = 0; i < (gradient.thresholdCount - 1) ~/ 4 + 1; i++) {
builder.addUniform(ShaderType.kVec4, name: 'threshold_${i}');
}
for (int i = 0; i < gradient.thresholdCount; i++) {
builder.addUniform(ShaderType.kVec4, name: 'bias_$i');
builder.addUniform(ShaderType.kVec4, name: 'scale_$i');
}
// Use st variable name if clamped, otherwise write code to comnpute
// tiled_st.
String probeName = 'st';
switch (tileMode) {
case ui.TileMode.clamp:
break;
case ui.TileMode.repeated:
method.addStatement('float tiled_st = fract(st);');
probeName = 'tiled_st';
break;
case ui.TileMode.mirror:
method.addStatement('float t_1 = (st - 1.0);');
method.addStatement(
'float tiled_st = abs((t_1 - 2.0 * floor(t_1 * 0.5)) - 1.0);');
probeName = 'tiled_st';
break;
}
_writeUnrolledBinarySearch(method, 0, gradient.thresholdCount - 1,
probe: probeName, sourcePrefix: 'threshold',
biasName: 'bias', scaleName: 'scale');
return probeName;
}
class GradientRadial extends EngineGradient {
GradientRadial(this.center, this.radius, this.colors, this.colorStops,
this.tileMode, this.matrix4)
......@@ -218,30 +349,95 @@ class GradientRadial extends EngineGradient {
@override
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
if (!useCanvasKit) {
if (tileMode != ui.TileMode.clamp) {
throw UnimplementedError(
'TileMode not supported in GradientRadial shader');
}
if (tileMode == ui.TileMode.clamp) {
return _createCanvasGradient(ctx, shaderBounds, density);
} else {
initWebGl();
return _createGlGradient(ctx, shaderBounds, density);
}
}
Object _createCanvasGradient(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
final double offsetX = shaderBounds!.left;
final double offsetY = shaderBounds.top;
final html.CanvasGradient gradient = ctx!.createRadialGradient(
center.dx - offsetX, center.dy - offsetY, 0,
center.dx - offsetX, center.dy - offsetY, radius);
final List<double>? colorStops = this.colorStops;
if (colorStops == null) {
assert(colors.length == 2);
gradient.addColorStop(0, colorToCssString(colors[0])!);
gradient.addColorStop(1, colorToCssString(colors[1])!);
return gradient;
} else {
for (int i = 0; i < colors.length; i++) {
gradient.addColorStop(colorStops[i], colorToCssString(colors[i])!);
}
}
_addColorStopsToCanvasGradient(gradient, colors, colorStops);
return gradient;
}
/// Creates a radial gradient with tiling repeat or mirror.
html.CanvasPattern _createGlGradient(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
assert(shaderBounds != null);
int widthInPixels = shaderBounds!.width.ceil();
int heightInPixels = shaderBounds.height.ceil();
assert(widthInPixels > 0 && heightInPixels > 0);
initWebGl();
// Render gradient into a bitmap and create a canvas pattern.
_OffScreenCanvas offScreenCanvas =
_OffScreenCanvas(widthInPixels, heightInPixels);
_GlContext gl = _OffScreenCanvas.supported
? _GlContext.fromOffscreenCanvas(offScreenCanvas._canvas!)
: _GlContext.fromCanvas(offScreenCanvas._glCanvas!,
webGLVersion == WebGLVersion.webgl1);
gl.setViewportSize(widthInPixels, heightInPixels);
NormalizedGradient normalizedGradient = NormalizedGradient(
colors, stops: colorStops);
_GlProgram glProgram = gl.useAndCacheProgram(
_WebGlRenderer.writeBaseVertexShader(),
_createRadialFragmentShader(normalizedGradient, tileMode));
Object tileOffset = gl.getUniformLocation(glProgram.program, 'u_tile_offset');
double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width);
double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height);
gl.setUniform2f(tileOffset,
2 * (shaderBounds.width * (centerX - 0.5)),
2 * (shaderBounds.height * (centerY - 0.5)));
Object radiusUniform = gl.getUniformLocation(glProgram.program, 'u_radius');
gl.setUniform1f(radiusUniform, radius);
normalizedGradient.setupUniforms(gl, glProgram);
Object gradientMatrix = gl.getUniformLocation(
glProgram.program, 'm_gradient');
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 == null ? Matrix4.identity().storage : matrix4!);
Object? imageBitmap = _glRenderer!.drawRect(ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl, glProgram, normalizedGradient, widthInPixels, heightInPixels);
return ctx!.createPattern(imageBitmap!, 'no-repeat')!;
}
String _createRadialFragmentShader(NormalizedGradient gradient,
ui.TileMode tileMode) {
ShaderBuilder builder = ShaderBuilder.fragment(webGLVersion);
builder.floatPrecision = ShaderPrecision.kMedium;
builder.addIn(ShaderType.kVec4, name: 'v_color');
builder.addUniform(ShaderType.kVec2, name: 'u_resolution');
builder.addUniform(ShaderType.kVec2, name: 'u_tile_offset');
builder.addUniform(ShaderType.kFloat, name: 'u_radius');
builder.addUniform(ShaderType.kMat4, name: 'm_gradient');
ShaderDeclaration fragColor = builder.fragmentColor;
ShaderMethod method = builder.addMethod('main');
// Sweep gradient
method.addStatement(
'vec2 center = 0.5 * (u_resolution + u_tile_offset);');
method.addStatement(
'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;');
method.addStatement(
'float dist = length(localCoord);');
method.addStatement(''
'float st = abs(dist / u_radius);');
final String probeName =
_writeSharedGradientShader(builder, method, gradient, tileMode);
method.addStatement('${fragColor.name} = ${probeName} * scale + bias;');
String shader = builder.build();
return shader;
}
}
class GradientConical extends EngineGradient {
......
......@@ -526,10 +526,10 @@ FutureOr<void> sendFontChangeMessage() async {
}
// Stores matrix in a form that allows zero allocation transforms.
class _FastMatrix64 {
final Float64List matrix;
class _FastMatrix32 {
final Float32List matrix;
double transformedX = 0, transformedY = 0;
_FastMatrix64(this.matrix);
_FastMatrix32(this.matrix);
void transform(double x, double y) {
transformedX = matrix[12] + (matrix[0] * x) + (matrix[4] * y);
......
......@@ -280,7 +280,8 @@ abstract class Gradient extends Shader {
Float64List? matrix4,
]) => engine.useCanvasKit
? engine.CkGradientLinear(from, to, colors, colorStops, tileMode, matrix4)
: engine.GradientLinear(from, to, colors, colorStops, tileMode, matrix4);
: engine.GradientLinear(from, to, colors, colorStops, tileMode,
matrix4 == null ? null : engine.toMatrix32(matrix4));
factory Gradient.radial(
Offset center,
double radius,
......
......@@ -3,45 +3,23 @@
// found in the LICENSE file.
// @dart = 2.6
import 'dart:html' as html;
import 'dart:math' as math;
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/ui.dart' hide TextStyle;
import 'package:ui/src/engine.dart';
import 'package:web_engine_tester/golden_tester.dart';
import 'screenshot.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() async {
const double screenWidth = 600.0;
const double screenHeight = 800.0;
const double screenWidth = 500.0;
const double screenHeight = 500.0;
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
// Commit a recording canvas to a bitmap, and compare with the expected
Future<void> _checkScreenshot(RecordingCanvas rc, String fileName,
{Rect region = const Rect.fromLTWH(0, 0, 500, 500)}) async {
final EngineCanvas engineCanvas = BitmapCanvas(screenRect);
rc.endRecording();
rc.apply(engineCanvas, screenRect);
// Wrap in <flt-scene> so that our CSS selectors kick in.
final html.Element sceneElement = html.Element.tag('flt-scene');
try {
sceneElement.append(engineCanvas.rootElement);
html.document.body.append(sceneElement);
await matchGoldenFile('$fileName.png', region: region);
} finally {
// The page is reused across tests, so remove the element after taking the
// golden screenshot.
sceneElement.remove();
}
}
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
......@@ -59,7 +37,9 @@ void testMain() async {
[Color(0xFFcfdfd2), Color(0xFF042a85)]);
rc.drawRect(shaderRect, paint);
expect(rc.hasArbitraryPaint, isTrue);
await _checkScreenshot(rc, 'linear_gradient_rect');
await canvasScreenshot(rc, 'linear_gradient_rect',
region: screenRect,
maxDiffRatePercent: 0.01);
});
test('Should draw linear gradient with transform.', () async {
......@@ -85,7 +65,9 @@ void testMain() async {
yOffset += 120;
}
expect(rc.hasArbitraryPaint, isTrue);
await _checkScreenshot(rc, 'linear_gradient_oval_matrix');
await canvasScreenshot(rc, 'linear_gradient_oval_matrix',
region: screenRect,
maxDiffRatePercent: 0.2);
});
// Regression test for https://github.com/flutter/flutter/issues/50010
......@@ -99,6 +81,62 @@ void testMain() async {
[Color(0xFFcfdfd2), Color(0xFF042a85)]);
rc.drawRRect(RRect.fromRectAndRadius(shaderRect, Radius.circular(16)), paint);
expect(rc.hasArbitraryPaint, isTrue);
await _checkScreenshot(rc, 'linear_gradient_rounded_rect');
await canvasScreenshot(rc, 'linear_gradient_rounded_rect',
region: screenRect,
maxDiffRatePercent: 0.1);
});
test('Should draw tiled repeated linear gradient with transform.', () async {
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
List<double> angles = [0.0, 30.0, 210.0];
double yOffset = 0;
for (double angle in angles) {
final Rect shaderRect = Rect.fromLTWH(50, 50 + yOffset, 100, 100);
final Paint paint = Paint()
..shader = Gradient.linear(
Offset(shaderRect.left, shaderRect.top),
Offset(shaderRect.left + shaderRect.width / 2, shaderRect.top),
[Color(0xFFFF0000), Color(0xFF042a85)],
null,
TileMode.repeated,
Matrix4
.rotationZ((angle / 180) * math.pi)
.toFloat64());
rc.drawRect(shaderRect, Paint()
..color = Color(0xFF000000));
rc.drawOval(shaderRect, paint);
yOffset += 120;
}
expect(rc.hasArbitraryPaint, isTrue);
await canvasScreenshot(rc, 'linear_gradient_tiled_repeated_rect',
region: screenRect);
});
test('Should draw tiled mirrored linear gradient with transform.', () async {
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
List<double> angles = [0.0, 30.0, 210.0];
double yOffset = 0;
for (double angle in angles) {
final Rect shaderRect = Rect.fromLTWH(50, 50 + yOffset, 100, 100);
final Paint paint = Paint()
..shader = Gradient.linear(
Offset(shaderRect.left, shaderRect.top),
Offset(shaderRect.left + shaderRect.width / 2, shaderRect.top),
[Color(0xFFFF0000), Color(0xFF042a85)],
null,
TileMode.mirror,
Matrix4
.rotationZ((angle / 180) * math.pi)
.toFloat64());
rc.drawRect(shaderRect, Paint()
..color = Color(0xFF000000));
rc.drawOval(shaderRect, paint);
yOffset += 120;
}
expect(rc.hasArbitraryPaint, isTrue);
await canvasScreenshot(rc, 'linear_gradient_tiled_mirrored_rect',
region: screenRect);
});
}
......@@ -3,44 +3,17 @@
// found in the LICENSE file.
// @dart = 2.6
import 'dart:html' as html;
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/ui.dart' hide TextStyle;
import 'package:ui/src/engine.dart';
import 'package:web_engine_tester/golden_tester.dart';
import 'screenshot.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() async {
const double screenWidth = 600.0;
const double screenHeight = 800.0;
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
// Commit a recording canvas to a bitmap, and compare with the expected
Future<void> _checkScreenshot(RecordingCanvas rc, String fileName,
{Rect region = const Rect.fromLTWH(0, 0, 500, 500),
bool write = false}) async {
final EngineCanvas engineCanvas = BitmapCanvas(screenRect);
rc.endRecording();
rc.apply(engineCanvas, screenRect);
// Wrap in <flt-scene> so that our CSS selectors kick in.
final html.Element sceneElement = html.Element.tag('flt-scene');
try {
sceneElement.append(engineCanvas.rootElement);
html.document.body.append(sceneElement);
await matchGoldenFile('$fileName.png', region: region, write: write);
} finally {
// The page is reused across tests, so remove the element after taking the
// golden screenshot.
sceneElement.remove();
}
}
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
......@@ -51,14 +24,17 @@ void testMain() async {
Future<void> _testGradient(String fileName, Shader shader,
{Rect paintRect = const Rect.fromLTRB(50, 50, 300, 300),
Rect shaderRect = const Rect.fromLTRB(50, 50, 300, 300)}) async {
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
Rect shaderRect = const Rect.fromLTRB(50, 50, 300, 300),
bool write = false,
double maxDiffRatePercent = 0,
Rect region = const Rect.fromLTWH(0, 0, 500, 500)}) async {
final RecordingCanvas rc = RecordingCanvas(region);
final Paint paint = Paint()..shader = shader;
final Path path = Path();
path.addRect(paintRect);
rc.drawPath(path, paint);
await _checkScreenshot(rc, fileName);
await canvasScreenshot(rc, fileName, write: write, region: region,
maxDiffRatePercent: maxDiffRatePercent);
}
test('Should draw centered radial gradient.', () async {
......@@ -73,7 +49,8 @@ void testMain() async {
const Color.fromARGB(255, 0, 0, 0),
const Color.fromARGB(255, 0, 0, 255)
]),
shaderRect: shaderRect);
shaderRect: shaderRect,
maxDiffRatePercent: 0.2);
});
test('Should draw right bottom centered radial gradient.', () async {
......@@ -85,7 +62,8 @@ void testMain() async {
const Color.fromARGB(255, 0, 0, 0),
const Color.fromARGB(255, 0, 0, 255)
]),
shaderRect: shaderRect);
shaderRect: shaderRect,
maxDiffRatePercent: 0.3);
});
test('Should draw with radial gradient with TileMode.clamp.', () async {
......@@ -102,6 +80,46 @@ void testMain() async {
],
<double>[0.0, 1.0],
TileMode.clamp),
shaderRect: shaderRect);
shaderRect: shaderRect,
maxDiffRatePercent: 0.2);
});
const List<Color> colors = <Color>[
Color(0xFF000000),
Color(0xFFFF3C38),
Color(0xFFFF8C42),
Color(0xFFFFF275),
Color(0xFF6699CC),
Color(0xFF656D78),];
const List<double> colorStops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
test('Should draw with radial gradient with TileMode.repeated.', () async {
Rect shaderRect = const Rect.fromLTRB(50, 50, 100, 100);
await _testGradient(
'radial_gradient_tilemode_repeated',
Gradient.radial(
Offset((shaderRect.left + shaderRect.right) / 2,
(shaderRect.top + shaderRect.bottom) / 2),
shaderRect.width / 2,
colors,
colorStops,
TileMode.repeated),
shaderRect: shaderRect,
region: const Rect.fromLTWH(0, 0, 600, 800));
});
test('Should draw with radial gradient with TileMode.mirrored.', () async {
Rect shaderRect = const Rect.fromLTRB(50, 50, 100, 100);
await _testGradient(
'radial_gradient_tilemode_mirror',
Gradient.radial(
Offset((shaderRect.left + shaderRect.right) / 2,
(shaderRect.top + shaderRect.bottom) / 2),
shaderRect.width / 2,
colors,
colorStops,
TileMode.mirror),
shaderRect: shaderRect,
region: const Rect.fromLTWH(0, 0, 600, 800));
});
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册