Skip to content

Commit

Permalink
Fix html gradient rendering (flutter#97762) (flutter#31355)
Browse files Browse the repository at this point in the history
  • Loading branch information
eyebrowsoffire authored Feb 10, 2022
1 parent 3764a8f commit 3d629c5
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 26 deletions.
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.
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

0 comments on commit 3d629c5

Please sign in to comment.