Skip to content

Commit 2ef0f42

Browse files
authored
Revert "[web] Fix image gap due to svg element without position attribute (flutter#21939)" (flutter#21986)
This reverts commit 7987980.
1 parent 326157e commit 2ef0f42

File tree

3 files changed

+62
-146
lines changed

3 files changed

+62
-146
lines changed

lib/web_ui/lib/src/engine/html/color_filter.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,7 @@ String? svgFilterFromBlendMode(
208208
case ui.BlendMode.dstOver:
209209
case ui.BlendMode.clear:
210210
case ui.BlendMode.srcOver:
211-
assert(
212-
false,
213-
'Invalid svg filter request for blend-mode '
211+
assert(false, 'Invalid svg filter request for blend-mode '
214212
'$colorFilterBlendMode');
215213
break;
216214
}
@@ -234,7 +232,7 @@ int _filterIdCounter = 0;
234232
// A' = a1*R + a2*G + a3*B + a4*A + a5
235233
String _srcInColorFilterToSvg(ui.Color? color) {
236234
_filterIdCounter += 1;
237-
return '$kSvgResourceHeader'
235+
return '<svg width="0" height="0">'
238236
'<filter id="_fcf$_filterIdCounter" '
239237
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
240238
'<feColorMatrix values="0 0 0 0 1 ' // Ignore input, set it to absolute.
@@ -251,7 +249,7 @@ String _srcInColorFilterToSvg(ui.Color? color) {
251249

252250
String _srcOutColorFilterToSvg(ui.Color? color) {
253251
_filterIdCounter += 1;
254-
return '$kSvgResourceHeader'
252+
return '<svg width="0" height="0">'
255253
'<filter id="_fcf$_filterIdCounter" '
256254
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
257255
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
@@ -263,7 +261,7 @@ String _srcOutColorFilterToSvg(ui.Color? color) {
263261

264262
String _xorColorFilterToSvg(ui.Color? color) {
265263
_filterIdCounter += 1;
266-
return '$kSvgResourceHeader'
264+
return '<svg width="0" height="0">'
267265
'<filter id="_fcf$_filterIdCounter" '
268266
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
269267
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
@@ -278,7 +276,7 @@ String _xorColorFilterToSvg(ui.Color? color) {
278276
String _compositeColorFilterToSvg(
279277
ui.Color? color, double k1, double k2, double k3, double k4) {
280278
_filterIdCounter += 1;
281-
return '$kSvgResourceHeader'
279+
return '<svg width="0" height="0">'
282280
'<filter id="_fcf$_filterIdCounter" '
283281
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
284282
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
@@ -297,7 +295,7 @@ String _modulateColorFilterToSvg(ui.Color color) {
297295
final double r = color.red / 255.0;
298296
final double b = color.blue / 255.0;
299297
final double g = color.green / 255.0;
300-
return '$kSvgResourceHeader'
298+
return '<svg width="0" height="0">'
301299
'<filter id="_fcf$_filterIdCounter" '
302300
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
303301
'<feColorMatrix values="0 0 0 0 $r ' // Ignore input, set it to absolute.
@@ -314,7 +312,7 @@ String _modulateColorFilterToSvg(ui.Color color) {
314312
String _blendColorFilterToSvg(ui.Color? color, String? feBlend,
315313
{bool swapLayers = false}) {
316314
_filterIdCounter += 1;
317-
return '$kSvgResourceHeader'
315+
return '<svg width="0" height="0">'
318316
'<filter id="_fcf$_filterIdCounter" filterUnits="objectBoundingBox" '
319317
'x="0%" y="0%" width="100%" height="100%">'
320318
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'

lib/web_ui/lib/src/engine/util.dart

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ TransformKind transformKindOf(Float32List matrix) {
111111

112112
// If matrix contains scaling, rotation, z translation or
113113
// perspective transform, it is not considered simple.
114-
final bool isSimple2dTransform = m[15] ==
115-
1.0 && // start reading from the last element to eliminate range checks in subsequent reads.
114+
final bool isSimple2dTransform =
115+
m[15] == 1.0 && // start reading from the last element to eliminate range checks in subsequent reads.
116116
m[14] == 0.0 && // z translation is NOT simple
117117
// m[13] - y translation is simple
118118
// m[12] - x translation is simple
@@ -126,8 +126,8 @@ TransformKind transformKindOf(Float32List matrix) {
126126
// m[4] - 2D rotation is simple
127127
m[3] == 0.0 &&
128128
m[2] == 0.0;
129-
// m[1] - 2D rotation is simple
130-
// m[0] - scale x is simple
129+
// m[1] - 2D rotation is simple
130+
// m[0] - scale x is simple
131131

132132
if (!isSimple2dTransform) {
133133
return TransformKind.complex;
@@ -136,7 +136,8 @@ TransformKind transformKindOf(Float32List matrix) {
136136
// From this point on we're sure the transform is 2D, but we don't know if
137137
// it's identity or not. To check, we need to look at the remaining elements
138138
// that were not checked above.
139-
final bool isIdentityTransform = m[0] == 1.0 &&
139+
final bool isIdentityTransform =
140+
m[0] == 1.0 &&
140141
m[1] == 0.0 &&
141142
m[4] == 0.0 &&
142143
m[5] == 1.0 &&
@@ -164,7 +165,7 @@ bool isIdentityFloat32ListTransform(Float32List matrix) {
164165
/// transform. Consider removing the CSS `transform` property from elements
165166
/// that apply identity transform.
166167
String float64ListToCssTransform2d(Float32List matrix) {
167-
assert(transformKindOf(matrix) != TransformKind.complex);
168+
assert (transformKindOf(matrix) != TransformKind.complex);
168169
return 'matrix(${matrix[0]},${matrix[1]},${matrix[4]},${matrix[5]},${matrix[12]},${matrix[13]})';
169170
}
170171

@@ -278,22 +279,10 @@ void transformLTRB(Matrix4 transform, Float32List ltrb) {
278279

279280
_tempPointMatrix.multiplyTranspose(transform);
280281

281-
ltrb[0] = math.min(
282-
math.min(
283-
math.min(_tempPointData[0], _tempPointData[1]), _tempPointData[2]),
284-
_tempPointData[3]);
285-
ltrb[1] = math.min(
286-
math.min(
287-
math.min(_tempPointData[4], _tempPointData[5]), _tempPointData[6]),
288-
_tempPointData[7]);
289-
ltrb[2] = math.max(
290-
math.max(
291-
math.max(_tempPointData[0], _tempPointData[1]), _tempPointData[2]),
292-
_tempPointData[3]);
293-
ltrb[3] = math.max(
294-
math.max(
295-
math.max(_tempPointData[4], _tempPointData[5]), _tempPointData[6]),
296-
_tempPointData[7]);
282+
ltrb[0] = math.min(math.min(math.min(_tempPointData[0], _tempPointData[1]), _tempPointData[2]), _tempPointData[3]);
283+
ltrb[1] = math.min(math.min(math.min(_tempPointData[4], _tempPointData[5]), _tempPointData[6]), _tempPointData[7]);
284+
ltrb[2] = math.max(math.max(math.max(_tempPointData[0], _tempPointData[1]), _tempPointData[2]), _tempPointData[3]);
285+
ltrb[3] = math.max(math.max(math.max(_tempPointData[4], _tempPointData[5]), _tempPointData[6]), _tempPointData[7]);
297286
}
298287

299288
/// Returns true if [rect] contains every point that is also contained by the
@@ -311,13 +300,6 @@ bool rectContainsOther(ui.Rect rect, ui.Rect other) {
311300
/// Counter used for generating clip path id inside an svg <defs> tag.
312301
int _clipIdCounter = 0;
313302

314-
/// Used for clipping and filter svg resources.
315-
///
316-
/// Position needs to be absolute since these svgs are sandwiched between
317-
/// canvas elements and can cause layout shifts otherwise.
318-
const String kSvgResourceHeader = '<svg width="0" height="0" '
319-
'style="position:absolute">';
320-
321303
/// Converts Path to svg element that contains a clip-path definition.
322304
///
323305
/// Calling this method updates [_clipIdCounter]. The HTML id of the generated
@@ -329,7 +311,8 @@ String _pathToSvgClipPath(ui.Path path,
329311
double scaleY = 1.0}) {
330312
_clipIdCounter += 1;
331313
final StringBuffer sb = StringBuffer();
332-
sb.write(kSvgResourceHeader);
314+
sb.write('<svg width="0" height="0" '
315+
'style="position:absolute">');
333316
sb.write('<defs>');
334317

335318
final String clipId = 'svgClip$_clipIdCounter';
@@ -437,11 +420,10 @@ const Set<String> _genericFontFamilies = <String>{
437420
///
438421
/// For iOS, default to -apple-system, where it should be available, otherwise
439422
/// default to Arial. BlinkMacSystemFont is used for Chrome on iOS.
440-
final String _fallbackFontFamily =
441-
_isMacOrIOS ? '-apple-system, BlinkMacSystemFont' : 'Arial';
423+
final String _fallbackFontFamily = _isMacOrIOS ?
424+
'-apple-system, BlinkMacSystemFont' : 'Arial';
442425

443-
bool get _isMacOrIOS =>
444-
operatingSystem == OperatingSystem.iOs ||
426+
bool get _isMacOrIOS => operatingSystem == OperatingSystem.iOs ||
445427
operatingSystem == OperatingSystem.macOs;
446428

447429
/// Create a font-family string appropriate for CSS.
@@ -457,10 +439,8 @@ String? canonicalizeFontFamily(String? fontFamily) {
457439
// on sans-serif.
458440
// Map to San Francisco Text/Display fonts, use -apple-system,
459441
// BlinkMacSystemFont.
460-
if (fontFamily == '.SF Pro Text' ||
461-
fontFamily == '.SF Pro Display' ||
462-
fontFamily == '.SF UI Text' ||
463-
fontFamily == '.SF UI Display') {
442+
if (fontFamily == '.SF Pro Text' || fontFamily == '.SF Pro Display' ||
443+
fontFamily == '.SF UI Text' || fontFamily == '.SF UI Display') {
464444
return _fallbackFontFamily;
465445
}
466446
}
@@ -494,27 +474,27 @@ void applyWebkitClipFix(html.Element? containerElement) {
494474
}
495475
}
496476

497-
final ByteData? _fontChangeMessage =
498-
JSONMessageCodec().encodeMessage(<String, dynamic>{'type': 'fontsChange'});
477+
final ByteData? _fontChangeMessage = JSONMessageCodec().encodeMessage(<String, dynamic>{'type': 'fontsChange'});
499478

500479
// Font load callbacks will typically arrive in sequence, we want to prevent
501480
// sendFontChangeMessage of causing multiple synchronous rebuilds.
502481
// This flag ensures we properly schedule a single call to framework.
503482
bool _fontChangeScheduled = false;
504483

505484
FutureOr<void> sendFontChangeMessage() async {
506-
if (window._onPlatformMessage != null && !_fontChangeScheduled) {
507-
_fontChangeScheduled = true;
508-
// Batch updates into next animationframe.
509-
html.window.requestAnimationFrame((num _) {
510-
_fontChangeScheduled = false;
511-
window.invokeOnPlatformMessage(
512-
'flutter/system',
513-
_fontChangeMessage,
514-
(_) {},
515-
);
516-
});
517-
}
485+
if (window._onPlatformMessage != null)
486+
if (!_fontChangeScheduled) {
487+
_fontChangeScheduled = true;
488+
// Batch updates into next animationframe.
489+
html.window.requestAnimationFrame((num _) {
490+
_fontChangeScheduled = false;
491+
window.invokeOnPlatformMessage(
492+
'flutter/system',
493+
_fontChangeMessage,
494+
(_) {},
495+
);
496+
});
497+
}
518498
}
519499

520500
// Stores matrix in a form that allows zero allocation transforms.
@@ -555,12 +535,14 @@ bool isUnsoundNull(dynamic object) {
555535
}
556536

557537
bool _offsetIsValid(ui.Offset offset) {
538+
assert(offset != null, 'Offset argument was null.'); // ignore: unnecessary_null_comparison
558539
assert(!offset.dx.isNaN && !offset.dy.isNaN,
559540
'Offset argument contained a NaN value.');
560541
return true;
561542
}
562543

563544
bool _matrix4IsValid(Float32List matrix4) {
545+
assert(matrix4 != null, 'Matrix4 argument was null.'); // ignore: unnecessary_null_comparison
564546
assert(matrix4.length == 16, 'Matrix4 must have 16 entries.');
565547
return true;
566548
}

lib/web_ui/test/golden_tests/engine/color_filter_golden_test.dart

Lines changed: 21 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// @dart = 2.10
5+
// @dart = 2.6
66
import 'dart:html' as html;
7-
import 'dart:js_util' as js_util;
87

98
import 'package:test/bootstrap/browser.dart';
109
import 'package:test/test.dart';
@@ -36,118 +35,55 @@ void testMain() async {
3635
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
3736
final Picture backgroundPicture = _drawBackground();
3837
builder.addPicture(Offset.zero, backgroundPicture);
39-
builder.pushColorFilter(
40-
EngineColorFilter.mode(Color(0xF0000080), BlendMode.color));
38+
builder.pushColorFilter(EngineColorFilter.mode(Color(0xF0000080),
39+
BlendMode.color));
4140
final Picture circles1 = _drawTestPictureWithCircles(30, 30);
4241
builder.addPicture(Offset.zero, circles1);
4342
builder.pop();
44-
html.document.body!.append(builder.build().webOnlyRootElement!);
43+
html.document.body.append(builder
44+
.build()
45+
.webOnlyRootElement);
4546

4647
await matchGoldenFile('color_filter_blendMode_color.png', region: region);
4748
});
48-
49-
/// Regression test for https://github.com/flutter/flutter/issues/59451.
50-
///
51-
/// Picture with overlay blend inside a physical shape. Should show image
52-
/// at 0,0. In the filed issue it was leaving a gap on top.
53-
test('Should render image with color filter without gap', () async {
54-
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
55-
final Path path = Path();
56-
path.addRRect(RRect.fromRectAndRadius(
57-
Rect.fromLTRB(0, 0, 400, 400), Radius.circular(2)));
58-
PhysicalShapeEngineLayer oldLayer = builder.pushPhysicalShape(
59-
path: path, color: Color(0xFFFFFFFF), elevation: 0);
60-
final Picture circles1 = _drawTestPictureWithImage(
61-
ColorFilter.mode(Color(0x3C4043), BlendMode.overlay));
62-
builder.addPicture(Offset(10, 0), circles1);
63-
builder.pop();
64-
builder.build();
65-
66-
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
67-
builder2.pushPhysicalShape(
68-
path: path, color: Color(0xFFFFFFFF), elevation: 0, oldLayer: oldLayer);
69-
builder2.addPicture(Offset(10, 0), circles1);
70-
builder2.pop();
71-
72-
html.document.body!.append(builder2.build().webOnlyRootElement!);
73-
74-
await matchGoldenFile('color_filter_blendMode_overlay.png',
75-
region: region);
76-
});
7749
}
7850

7951
Picture _drawTestPictureWithCircles(double offsetX, double offsetY) {
80-
final EnginePictureRecorder recorder =
81-
PictureRecorder() as EnginePictureRecorder;
52+
final EnginePictureRecorder recorder = PictureRecorder();
8253
final RecordingCanvas canvas =
83-
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
84-
canvas.drawCircle(Offset(offsetX + 10, offsetY + 10), 10,
85-
(Paint()..style = PaintingStyle.fill) as SurfacePaint);
54+
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
55+
canvas.drawCircle(
56+
Offset(offsetX + 10, offsetY + 10), 10, Paint()..style = PaintingStyle.fill);
8657
canvas.drawCircle(
8758
Offset(offsetX + 60, offsetY + 10),
8859
10,
89-
(Paint()
60+
Paint()
9061
..style = PaintingStyle.fill
91-
..color = const Color.fromRGBO(255, 0, 0, 1)) as SurfacePaint);
62+
..color = const Color.fromRGBO(255, 0, 0, 1));
9263
canvas.drawCircle(
9364
Offset(offsetX + 10, offsetY + 60),
9465
10,
95-
(Paint()
66+
Paint()
9667
..style = PaintingStyle.fill
97-
..color = const Color.fromRGBO(0, 255, 0, 1)) as SurfacePaint);
68+
..color = const Color.fromRGBO(0, 255, 0, 1));
9869
canvas.drawCircle(
9970
Offset(offsetX + 60, offsetY + 60),
10071
10,
101-
(Paint()
72+
Paint()
10273
..style = PaintingStyle.fill
103-
..color = const Color.fromRGBO(0, 0, 255, 1)) as SurfacePaint);
104-
return recorder.endRecording();
105-
}
106-
107-
Picture _drawTestPictureWithImage(ColorFilter filter) {
108-
final EnginePictureRecorder recorder =
109-
PictureRecorder() as EnginePictureRecorder;
110-
final RecordingCanvas canvas =
111-
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
112-
final Image testImage = createTestImage();
113-
canvas.drawImageRect(
114-
testImage,
115-
Rect.fromLTWH(0, 0, 200, 150),
116-
Rect.fromLTWH(0, 0, 300, 300),
117-
(Paint()
118-
..style = PaintingStyle.fill
119-
..colorFilter = filter
120-
..color = const Color.fromRGBO(0, 0, 255, 1)) as SurfacePaint);
74+
..color = const Color.fromRGBO(0, 0, 255, 1));
12175
return recorder.endRecording();
12276
}
12377

12478
Picture _drawBackground() {
125-
final EnginePictureRecorder recorder =
126-
PictureRecorder() as EnginePictureRecorder;
79+
final EnginePictureRecorder recorder = PictureRecorder();
12780
final RecordingCanvas canvas =
128-
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
81+
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
12982
canvas.drawRect(
13083
Rect.fromLTWH(8, 8, 400.0 - 16, 400.0 - 16),
131-
(Paint()
84+
Paint()
13285
..style = PaintingStyle.fill
133-
..color = Color(0xFFE0FFE0)) as SurfacePaint);
86+
..color = Color(0xFFE0FFE0)
87+
);
13488
return recorder.endRecording();
13589
}
136-
137-
HtmlImage createTestImage({int width = 200, int height = 150}) {
138-
html.CanvasElement canvas =
139-
new html.CanvasElement(width: width, height: height);
140-
html.CanvasRenderingContext2D ctx = canvas.context2D;
141-
ctx.fillStyle = '#E04040';
142-
ctx.fillRect(0, 0, width / 3, height);
143-
ctx.fill();
144-
ctx.fillStyle = '#40E080';
145-
ctx.fillRect(width / 3, 0, width / 3, height);
146-
ctx.fill();
147-
ctx.fillStyle = '#2040E0';
148-
ctx.fillRect(2 * width / 3, 0, width / 3, height);
149-
ctx.fill();
150-
html.ImageElement imageElement = html.ImageElement();
151-
imageElement.src = js_util.callMethod(canvas, 'toDataURL', <dynamic>[]);
152-
return HtmlImage(imageElement, width, height);
153-
}

0 commit comments

Comments
 (0)