Skip to content

Commit

Permalink
Fix shader mask for text and shader bounds origin (flutter#27600)
Browse files Browse the repository at this point in the history
  • Loading branch information
ferhatb authored Jul 21, 2021
1 parent a819189 commit 37ece32
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 16 deletions.
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: 10ed22e7e6a5039b84e8c028828a86a7ff98b0be
revision: 6a076fd9881e03d5f8d17180f4d51a2bd9738a34
11 changes: 10 additions & 1 deletion lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,16 @@ class BitmapCanvas extends EngineCanvas {
void drawParagraph(EngineParagraph paragraph, ui.Offset offset) {
assert(paragraph.isLaidOut);

if (paragraph.drawOnCanvas && _childOverdraw == false) {
/// - paragraph.drawOnCanvas checks that the text styling doesn't include
/// features that prevent text from being rendered correctly using canvas.
/// - _childOverdraw check prevents sandwitching multiple canvas elements
/// when we have alternating paragraphs and other drawing commands that are
/// suitable for canvas.
/// - To make sure an svg filter is applied correctly to paragraph we
/// check isInsideSvgFilterTree to make sure dom node doesn't have any
/// parents that apply one.
if (paragraph.drawOnCanvas && _childOverdraw == false &&
!_renderStrategy.isInsideSvgFilterTree) {
paragraph.paint(this, offset);
return;
}
Expand Down
14 changes: 12 additions & 2 deletions lib/web_ui/lib/src/engine/html/shaders/shader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,18 @@ class GradientLinear extends EngineGradient {
_createLinearFragmentShader(normalizedGradient, tileMode));
gl.useProgram(glProgram);

/// When creating an image to apply to a dom element, render
/// contents at 0,0 and adjust gradient vector for shaderBounds.
final bool translateToOrigin = createDataUrl && shaderBounds != null;

if (translateToOrigin) {
shaderBounds = shaderBounds.translate(-shaderBounds.left, -shaderBounds.top);
}

// Setup from/to uniforms.
//
// From/to is relative to shaderBounds.
//
// 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.
Expand Down Expand Up @@ -270,7 +280,7 @@ class GradientLinear extends EngineGradient {
? (shaderBounds.height / 2)
: (fromY + toY) / 2.0 - shaderBounds.top;

final Matrix4 translateToOrigin =
final Matrix4 originTranslation =
Matrix4.translationValues(-originX, -originY, 0);
// Rotate around Z axis.
final Matrix4 rotationZ = Matrix4.identity();
Expand Down Expand Up @@ -306,7 +316,7 @@ class GradientLinear extends EngineGradient {
}

gradientTransform.multiply(rotationZ);
gradientTransform.multiply(translateToOrigin);
gradientTransform.multiply(originTranslation);
// Setup gradient uniforms for t search.
normalizedGradient.setupUniforms(gl, glProgram);
// Setup matrix transform uniform.
Expand Down
91 changes: 79 additions & 12 deletions lib/web_ui/test/golden_tests/engine/shader_mask_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ void main() async {
if (!debugTest) {
internalBootstrapBrowserTest(() => testMain);
} else {
_renderScene(BlendMode.color);
_renderCirclesScene(BlendMode.color);
}
}

Expand All @@ -45,54 +45,61 @@ void testMain() async {

/// Should render the picture unmodified.
test('Renders shader mask with linear gradient BlendMode dst', () async {
_renderScene(BlendMode.dst);
_renderCirclesScene(BlendMode.dst);
await matchGoldenFile('shadermask_linear_dst.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

/// Should render the gradient only where circles have alpha channel.
test('Renders shader mask with linear gradient BlendMode srcIn', () async {
_renderScene(BlendMode.srcIn);
_renderCirclesScene(BlendMode.srcIn);
await matchGoldenFile('shadermask_linear_srcin.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

test('Renders shader mask with linear gradient BlendMode color', () async {
_renderScene(BlendMode.color);
_renderCirclesScene(BlendMode.color);
await matchGoldenFile('shadermask_linear_color.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

test('Renders shader mask with linear gradient BlendMode xor', () async {
_renderScene(BlendMode.xor);
_renderCirclesScene(BlendMode.xor);
await matchGoldenFile('shadermask_linear_xor.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

test('Renders shader mask with linear gradient BlendMode plus', () async {
_renderScene(BlendMode.plus);
_renderCirclesScene(BlendMode.plus);
await matchGoldenFile('shadermask_linear_plus.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

test('Renders shader mask with linear gradient BlendMode modulate', () async {
_renderScene(BlendMode.modulate);
_renderCirclesScene(BlendMode.modulate);
await matchGoldenFile('shadermask_linear_modulate.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

test('Renders shader mask with linear gradient BlendMode overlay', () async {
_renderScene(BlendMode.overlay);
_renderCirclesScene(BlendMode.overlay);
await matchGoldenFile('shadermask_linear_overlay.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

/// Should render the gradient opaque on top of content.
test('Renders shader mask with linear gradient BlendMode src', () async {
_renderScene(BlendMode.src);
_renderCirclesScene(BlendMode.src);
await matchGoldenFile('shadermask_linear_src.png',
region: Rect.fromLTWH(0, 0, 360, 200));
}, skip: isWebkit);

/// Should render text with gradient.
test('Renders text with linear gradient shader mask', () async {
_renderTextScene(BlendMode.srcIn);
await matchGoldenFile('shadermask_linear_text.png',
region: Rect.fromLTWH(0, 0, 360, 200), maxDiffRatePercent: 2.0);
}, skip: isWebkit);
}

Picture _drawTestPictureWithCircles(
Expand Down Expand Up @@ -122,7 +129,7 @@ Picture _drawTestPictureWithCircles(
return recorder.endRecording();
}

void _renderScene(BlendMode blendMode) {
void _renderCirclesScene(BlendMode blendMode) {
final Rect region = Rect.fromLTWH(0, 0, 400, 400);

final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
Expand All @@ -139,14 +146,74 @@ void _renderScene(BlendMode blendMode) {
];
final List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];

final EngineGradient shader = GradientLinear(Offset(200, 30), Offset(320, 150),
final Rect shaderBounds = Rect.fromLTWH(180, 10, 140, 140);

final EngineGradient shader = GradientLinear(
Offset(200 - shaderBounds.left, 30 - shaderBounds.top),
Offset(320 - shaderBounds.left, 150 - shaderBounds.top),
colors, stops, TileMode.clamp, Matrix4.identity().storage);

builder.pushShaderMask(shader, Rect.fromLTWH(180, 10, 140, 140), blendMode,
builder.pushShaderMask(shader, shaderBounds, blendMode,
oldLayer: null);
final Picture circles2 = _drawTestPictureWithCircles(region, 180, 10);
builder.addPicture(Offset.zero, circles2);
builder.pop();

domRenderer.sceneHostElement!.append(builder.build().webOnlyRootElement!);
}

Picture _drawTestPictureWithText(
Rect region, double offsetX, double offsetY) {
final EnginePictureRecorder recorder = EnginePictureRecorder();
final RecordingCanvas canvas = recorder.beginRecording(region);
const String text = 'Shader test';

final EngineParagraphStyle paragraphStyle = EngineParagraphStyle(
fontFamily: 'Roboto',
fontSize: 40.0,
);

final CanvasParagraphBuilder builder = CanvasParagraphBuilder(paragraphStyle);
builder.pushStyle(EngineTextStyle.only(color: Color(0xFFFF0000)));
builder.addText(text);
final CanvasParagraph paragraph = builder.build();

final double maxWidth = 200 - 10;
paragraph.layout(ParagraphConstraints(width: maxWidth));
canvas.drawParagraph(paragraph, Offset(offsetX, offsetY));
return recorder.endRecording();
}

void _renderTextScene(BlendMode blendMode) {
final Rect region = Rect.fromLTWH(0, 0, 600, 400);

final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
final Picture textPicture = _drawTestPictureWithText(region, 10, 10);
builder.addPicture(Offset.zero, textPicture);

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

final Rect shaderBounds = Rect.fromLTWH(180, 10, 140, 140);

final EngineGradient shader = GradientLinear(
Offset(200 - shaderBounds.left, 30 - shaderBounds.top),
Offset(320 - shaderBounds.left, 150 - shaderBounds.top),
colors, stops, TileMode.clamp, Matrix4.identity().storage);

builder.pushShaderMask(shader, shaderBounds, blendMode,
oldLayer: null);

final Picture textPicture2 = _drawTestPictureWithText(region, 180, 10);
builder.addPicture(Offset.zero, textPicture2);
builder.pop();

domRenderer.sceneHostElement!.append(builder.build().webOnlyRootElement!);
}

0 comments on commit 37ece32

Please sign in to comment.