Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Fix html gradient rendering (https://github.com/flutter/flutter/issues/97762) #31355

Merged
merged 4 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/html/render_vertices.dart
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class _WebGlRenderer implements GlRenderer {
NormalizedGradient gradient, int widthInPixels, int heightInPixels) {
drawRectToGl(
targetRect, gl, glProgram, gradient, widthInPixels, heightInPixels);
final Object? image = gl.readPatternData();
final Object? image = gl.readPatternData(gradient.isOpaque);
gl.bindArrayBuffer(null);
gl.bindElementArrayBuffer(null);
return image;
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/html/shaders/image_shader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ class EngineImageShader implements ui.ImageShader {
gl.unbindVertexArray();
}

final Object? bitmapImage = gl.readPatternData();
final Object? bitmapImage = gl.readPatternData(false);
gl.bindArrayBuffer(null);
gl.bindElementArrayBuffer(null);
return context!.createPattern(bitmapImage!, 'no-repeat')!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class NormalizedGradient {
final Float32List _bias;
final Float32List _scale;
final int thresholdCount;
final bool isOpaque;

factory NormalizedGradient(List<ui.Color> colors, {List<double>? stops}) {
// If colorStops is not provided, then only two stops, at 0.0 and 1.0,
Expand All @@ -34,6 +35,7 @@ class NormalizedGradient {
stops ??= const <double>[0.0, 1.0];
final int colorCount = colors.length;
int normalizedCount = colorCount;
final bool isOpaque = !colors.any((ui.Color c) => c.alpha < 1.0);
final bool addFirst = stops[0] != 0.0;
final bool addLast = stops.last != 1.0;
if (addFirst) {
Expand Down Expand Up @@ -94,11 +96,11 @@ class NormalizedGradient {
bias[colorIndex + 2] -= t * scale[colorIndex + 2];
bias[colorIndex + 3] -= t * scale[colorIndex + 3];
}
return NormalizedGradient._(normalizedCount, thresholds, scale, bias);
return NormalizedGradient._(normalizedCount, thresholds, scale, bias, isOpaque);
}

NormalizedGradient._(
this.thresholdCount, this._thresholds, this._scale, this._bias);
this.thresholdCount, this._thresholds, this._scale, this._bias, this.isOpaque);

/// Sets uniforms for threshold, bias and scale for program.
void setupUniforms(GlContext gl, GlProgram glProgram) {
Expand Down
10 changes: 3 additions & 7 deletions lib/web_ui/lib/src/engine/html/shaders/shader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,9 @@ class GradientSweep extends EngineGradient {
final Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range');
gl.setUniform2f(angleRange, startAngle, endAngle);
normalizedGradient.setupUniforms(gl, glProgram);
if (matrix4 != null) {
final Object gradientMatrix =
final Object gradientMatrix =
gl.getUniformLocation(glProgram.program, 'm_gradient');
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4!);
}
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 ?? Matrix4.identity().storage);
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
Expand Down Expand Up @@ -293,9 +291,7 @@ class GradientLinear extends EngineGradient {
// We compute location based on gl_FragCoord to center distance which
// returns 0.0 at center. To make sure we align center of gradient to this
// point, we shift by 0.5 to get st value for center of gradient.
if (tileMode != ui.TileMode.repeated) {
gradientTransform.translate(0.5, 0);
}
gradientTransform.translate(0.5, 0);
if (length > kFltEpsilon) {
gradientTransform.scale(1.0 / length);
}
Expand Down
8 changes: 5 additions & 3 deletions lib/web_ui/lib/src/engine/safe_browser_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -814,12 +814,14 @@ class GlContext {

/// Returns image data in a form that can be used to create Canvas
/// context patterns.
Object? readPatternData() {
Object? readPatternData(bool isOpaque) {
// When using OffscreenCanvas and transferToImageBitmap is supported by
// browser create ImageBitmap otherwise use more expensive canvas
// allocation.
// allocation. However, transferToImageBitmap does not properly preserve
// the alpha channel, so only use it if the pattern is opaque.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is because on the following line we should also specify the alpha parameter:

: glContext = canvas.getContext('webgl2', <String, dynamic>{'premultipliedAlpha': false})!,

According to MDN docs: "alpha: Boolean that indicates if the canvas contains an alpha channel. If set to false, the browser now knows that the backdrop is always opaque, which can speed up drawing of transparent content and images."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually tried this already! It did nothing unfortunately. I believe alpha is true by default.

if (_canvas != null &&
js_util.hasProperty(_canvas!, 'transferToImageBitmap')) {
js_util.hasProperty(_canvas!, 'transferToImageBitmap') &&
isOpaque) {
// TODO(yjbanov): find out why we need to call getContext and ignore the return value.
js_util.callMethod<void>(_canvas!, 'getContext', <dynamic>['webgl2']);
final Object? imageBitmap = js_util.callMethod(_canvas!, 'transferToImageBitmap',
Expand Down
24 changes: 12 additions & 12 deletions lib/web_ui/test/html/shaders/gradient_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Future<void> testMain() async {
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
0, 360.0 / 180.0 * math.pi,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
Expand All @@ -105,7 +105,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
canvas.drawRect(rectBounds,
Expand All @@ -117,7 +117,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.repeated,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

canvas.drawRect(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
Expand All @@ -128,7 +128,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.mirror,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);
canvas.drawRect(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
canvas.drawRect(rectBounds, borderPaint);
Expand Down Expand Up @@ -159,7 +159,7 @@ Future<void> testMain() async {
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
0, 360.0 / 180.0 * math.pi,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
Expand All @@ -184,7 +184,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
canvas.drawOval(rectBounds,
Expand All @@ -196,7 +196,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.repeated,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

canvas.drawOval(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
Expand All @@ -207,7 +207,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.mirror,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);
canvas.drawOval(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
canvas.drawRect(rectBounds, borderPaint);
Expand Down Expand Up @@ -238,7 +238,7 @@ Future<void> testMain() async {
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
0, 360.0 / 180.0 * math.pi,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
Expand All @@ -265,7 +265,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
path = samplePathFromRect(rectBounds);
Expand All @@ -278,7 +278,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.repeated,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);

path = samplePathFromRect(rectBounds);
canvas.drawPath(path,
Expand All @@ -290,7 +290,7 @@ Future<void> testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.mirror,
math.pi / 6, 3 * math.pi / 4,
Matrix4.rotationZ(math.pi / 6.0).storage);
null);
path = samplePathFromRect(rectBounds);
canvas.drawPath(path,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
Expand Down
21 changes: 21 additions & 0 deletions lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ Future<void> testMain() async {
maxDiffRatePercent: 0.01);
});

test('Should blend linear gradient with alpha channel correctly.', () async {
const Rect canvasRect = Rect.fromLTRB(0, 0, 500, 500);
final RecordingCanvas rc =
RecordingCanvas(canvasRect);
final SurfacePaint backgroundPaint = SurfacePaint()
..style = PaintingStyle.fill
..color = const Color(0xFFFF0000);
rc.drawRect(canvasRect, backgroundPaint);

const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
final SurfacePaint paint = SurfacePaint()..shader = Gradient.linear(
Offset(shaderRect.left, shaderRect.top),
Offset(shaderRect.right, shaderRect.bottom),
const <Color>[Color(0x00000000), Color(0xFF0000FF)]);
rc.drawRect(shaderRect, paint);
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
await canvasScreenshot(rc, 'linear_gradient_rect_alpha',
region: screenRect,
maxDiffRatePercent: 0.01);
});

test('Should draw linear gradient with transform.', () async {
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
Expand Down