Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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/dev/goldens_lock.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
repository: https://github.com/flutter/goldens.git
revision: 578ecb91ea33004cd0ba0af513884cc28ba88cf4
revision: a1e78be938fe79544eeacccce1e2a5ace3d4058e
14 changes: 12 additions & 2 deletions lib/web_ui/lib/src/engine/html/render_vertices.dart
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,11 @@ class _WebGlRenderer implements _GlRenderer {
gl.bindElementArrayBuffer(indexBuffer);
gl.bufferElementData(_vertexIndicesForRect, gl.kStaticDraw);

Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution');
gl.setUniform2f(uRes, widthInPixels.toDouble(), heightInPixels.toDouble());
if (gl.containsUniform(glProgram.program, 'u_resolution')) {
Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution');
gl.setUniform2f(
uRes, widthInPixels.toDouble(), heightInPixels.toDouble());
}

gl.clear();
gl.viewport(0, 0, widthInPixels.toDouble(), heightInPixels.toDouble());
Expand Down Expand Up @@ -684,6 +687,13 @@ class _GlContext {
}
}

/// Returns true if uniform exists.
bool containsUniform(Object program, String uniformName) {
Object? res = js_util
.callMethod(glContext, 'getUniformLocation', <dynamic>[program, uniformName]);
return res != null;
}

/// Returns reference to uniform in program.
Object getAttributeLocation(Object program, String attribName) {
Object? res = js_util
Expand Down
81 changes: 56 additions & 25 deletions lib/web_ui/lib/src/engine/html/shaders/shader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class GradientSweep extends EngineGradient {
'float st = angle;');

final String probeName =
_writeSharedGradientShader(builder, method, gradient, tileMode, false);
_writeSharedGradientShader(builder, method, gradient, tileMode);
method.addStatement('${fragColor.name} = ${probeName} * scale + bias;');

String shader = builder.build();
Expand Down Expand Up @@ -172,7 +172,8 @@ class GradientLinear extends EngineGradient {
from.dx - offsetX, from.dy - offsetY, to.dx - offsetX,
to.dy - offsetY);
}
_addColorStopsToCanvasGradient(gradient, colors, colorStops, tileMode == ui.TileMode.decal);
_addColorStopsToCanvasGradient(gradient, colors, colorStops,
tileMode == ui.TileMode.decal);
return gradient;
}

Expand Down Expand Up @@ -210,32 +211,63 @@ class GradientLinear extends EngineGradient {
// 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);
final double fromX = from.dx;
final double fromY = from.dy;
final double toX = to.dx;
final double toY = to.dy;

final double dx = toX - fromX;
final double dy = toY - fromY;
final 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);
final double sinVal = length < kFltEpsilon ? 0 : -dy / length;
final double cosVal = length < kFltEpsilon ? 1 : dx / length;
// If tile mode is repeated we need to shift the center of from->to
// vector to the center of shader bounds.
final bool isRepeated = tileMode != ui.TileMode.clamp;
double originX = isRepeated
? (shaderBounds.width / 2)
: (fromX + toX) / 2.0 - shaderBounds.left;
double originY = isRepeated
? (shaderBounds.height / 2)
: (fromY + toY) / 2.0 - shaderBounds.top;

final Matrix4 translateToOrigin = Matrix4.translationValues(-originX, -originY, 0);
// Rotate around Z axis.
final Matrix4 rotationZ = Matrix4.identity();
final Float32List storage = rotationZ.storage;
storage[0] = cosVal;
storage[1] = -sinVal;
storage[4] = sinVal;
// Sign is flipped since gl coordinate system is flipped around y axis.
storage[1] = sinVal;
storage[4] = -sinVal;
storage[5] = cosVal;
Matrix4 gradientTransform = Matrix4.identity();
// 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);
}
if (length > kFltEpsilon) {
gradientTransform.scale(1.0 / length);
}
if (matrix4 != null) {
// Flutter GradientTransform is defined in shaderBounds coordinate system
// with flipped y axis.
// We flip y axis, translate to center, multiply matrix and translate
// and flip back so it is applied correctly.
final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!.matrix);
gradientTransform.scale(1, -1);
gradientTransform.translate(-shaderBounds.center.dx, -shaderBounds.center.dy);
gradientTransform.multiply(m4);
gradientTransform.translate(shaderBounds.center.dx, shaderBounds.center.dy);
gradientTransform.scale(1, -1);
}

gradientTransform.multiply(rotationZ);
gradientTransform.multiply(translateToOrigin);

// Setup gradient uniforms for t search.
normalizedGradient.setupUniforms(gl, glProgram);
// Setup matrix transform uniform.
Expand Down Expand Up @@ -269,11 +301,11 @@ class GradientLinear extends EngineGradient {
// 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;');
'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x, '
'u_resolution.y - gl_FragCoord.y, 0, 1);');
method.addStatement('float st = localCoord.x;');
final String probeName =
_writeSharedGradientShader(builder, method, gradient, tileMode, true);
_writeSharedGradientShader(builder, method, gradient, tileMode);
method.addStatement('${fragColor.name} = ${probeName} * scale + bias;');
String shader = builder.build();
return shader;
Expand Down Expand Up @@ -311,7 +343,7 @@ void _addColorStopsToCanvasGradient(html.CanvasGradient gradient,
String _writeSharedGradientShader(ShaderBuilder builder,
ShaderMethod method,
NormalizedGradient gradient,
ui.TileMode tileMode, bool shiftOrigin) {
ui.TileMode tileMode) {
method.addStatement('vec4 bias;');
method.addStatement('vec4 scale;');
// Write uniforms for each threshold, bias and scale.
Expand All @@ -328,21 +360,20 @@ String _writeSharedGradientShader(ShaderBuilder builder,
String probeName = 'st';
switch (tileMode) {
case ui.TileMode.clamp:
method.addStatement('float tiled_st = clamp(st, 0.0, 1.0);');
probeName = 'tiled_st';
break;
case ui.TileMode.decal:
break;
case ui.TileMode.repeated:
// st represents our distance from center. Flutter maps the center to
// center of gradient ramp so we need to add 0.5 to make sure repeated
// pattern center is at origin.
method.addStatement(shiftOrigin ?
'float tiled_st = fract(st + 0.5);'
: 'float tiled_st = fract(st);');
method.addStatement('float tiled_st = fract(st);');
probeName = 'tiled_st';
break;
case ui.TileMode.mirror:
method.addStatement(shiftOrigin ?
'float t_1 = (st - 0.5);'
: 'float t_1 = (st - 1.0);');
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';
Expand Down Expand Up @@ -453,7 +484,7 @@ class GradientRadial extends EngineGradient {
method.addStatement(''
'float st = abs(dist / u_radius);');
final String probeName =
_writeSharedGradientShader(builder, method, gradient, tileMode, false);
_writeSharedGradientShader(builder, method, gradient, tileMode);
method.addStatement('${fragColor.name} = ${probeName} * scale + bias;');
String shader = builder.build();
return shader;
Expand Down
56 changes: 56 additions & 0 deletions lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,54 @@ void testMain() async {
canvas.restore();
await _checkScreenshot(canvas, 'sweep_gradient_path');
});

/// Regression test for https://github.com/flutter/flutter/issues/74137.
test('Paints rotated and shifted linear gradient', () async {
final RecordingCanvas canvas =
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
canvas.save();

final Paint borderPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1
..color = Color(0xFF000000);

List<Color> colors = <Color>[
Color(0xFF000000),
Color(0xFFFF3C38),
Color(0xFFFF8C42),
Color(0xFFFFF275),
Color(0xFF6699CC),
Color(0xFF656D78),];
List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];

EngineGradient linearGradient = GradientLinear(Offset(50, 50),
Offset(200,130),
colors, stops, TileMode.clamp,
Matrix4.identity().storage);

const double kBoxWidth = 150;
const double kBoxHeight = 80;
// Gradient with default center.
Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
canvas.drawRect(rectBounds,
Paint()..shader = engineLinearGradientToShader(linearGradient, rectBounds));
canvas.drawRect(rectBounds, borderPaint);

// Tile mode repeat
rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
linearGradient = GradientLinear(Offset(50, 50),
Offset(200,130),
colors, stops, TileMode.repeated,
Matrix4.identity().storage);

canvas.drawRect(rectBounds,
new Paint()..shader = engineLinearGradientToShader(linearGradient, rectBounds));
canvas.drawRect(rectBounds, borderPaint);

canvas.restore();
await _checkScreenshot(canvas, 'linear_gradient_rect_shifted');
});
}

Shader engineGradientToShader(GradientSweep gradient, Rect rect) {
Expand All @@ -310,6 +358,14 @@ Shader engineGradientToShader(GradientSweep gradient, Rect rect) {
);
}

Shader engineLinearGradientToShader(GradientLinear gradient, Rect rect) {
return Gradient.linear(gradient.from, gradient.to,
gradient.colors, gradient.colorStops, gradient.tileMode,
gradient.matrix4 == null ? null : Float64List.fromList(
gradient.matrix4.matrix),
);
}

Path samplePathFromRect(Rect rectBounds) =>
Path()
..moveTo(rectBounds.center.dx, rectBounds.top)
Expand Down