diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index 170716bbd7e5e..14dc2d9d175fc 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -278,22 +278,28 @@ void transformLTRB(Matrix4 transform, Float32List ltrb) { _tempPointMatrix.multiplyTranspose(transform); + // Handle non-homogenous matrices. + double w = transform[15]; + if (w == 0.0) { + w = 1.0; + } + ltrb[0] = math.min( math.min( math.min(_tempPointData[0], _tempPointData[1]), _tempPointData[2]), - _tempPointData[3]); + _tempPointData[3]) / w; ltrb[1] = math.min( math.min( math.min(_tempPointData[4], _tempPointData[5]), _tempPointData[6]), - _tempPointData[7]); + _tempPointData[7]) / w; ltrb[2] = math.max( math.max( math.max(_tempPointData[0], _tempPointData[1]), _tempPointData[2]), - _tempPointData[3]); + _tempPointData[3]) / w; ltrb[3] = math.max( math.max( math.max(_tempPointData[4], _tempPointData[5]), _tempPointData[6]), - _tempPointData[7]); + _tempPointData[7]) / w; } /// Returns true if [rect] contains every point that is also contained by the diff --git a/lib/web_ui/test/canvaskit/layer_test.dart b/lib/web_ui/test/canvaskit/layer_test.dart index 81b86c086b78e..05011521ad834 100644 --- a/lib/web_ui/test/canvaskit/layer_test.dart +++ b/lib/web_ui/test/canvaskit/layer_test.dart @@ -26,8 +26,8 @@ void testMain() { ui.window.platformDispatcher as EnginePlatformDispatcher; final CkPicture picture = - paintPicture(ui.Rect.fromLTRB(0, 0, 30, 30), (CkCanvas canvas) { - canvas.drawRect(ui.Rect.fromLTRB(0, 0, 30, 30), + paintPicture(ui.Rect.fromLTRB(0, 0, 60, 60), (CkCanvas canvas) { + canvas.drawRect(ui.Rect.fromLTRB(0, 0, 60, 60), CkPaint()..style = ui.PaintingStyle.fill); }); diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart index 24f5e7545389a..c964d4b22d627 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -27,7 +27,8 @@ void testMain() { await webOnlyInitializeEngine(); }); - group('SceneBuilder', () { + group('SceneBuilder', () + { test('pushOffset implements surface lifecycle', () { testLayerLifeCycle((SceneBuilder sceneBuilder, EngineLayer oldLayer) { return sceneBuilder.pushOffset(10, 20, oldLayer: oldLayer); @@ -328,6 +329,65 @@ void testMain() { } }); + test('does not skip painting picture when picture is ' + 'inside transform with offset', () async { + final Picture picture = _drawPicture(); + // Picture should not be clipped out since transform will offset it to 500,500 + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + builder.pushOffset(0, 0); + builder.pushClipRect(const Rect.fromLTRB(0, 0, 1000, 1000)) as PersistedContainerSurface; + builder.pushTransform((Matrix4.identity()..scale(0.5, 0.5)).toFloat64()); + builder.addPicture(Offset(1000, 1000), picture); + builder.pop(); + builder.pop(); + builder.pop(); + html.HtmlElement content = builder.build().webOnlyRootElement; + expect(content.querySelectorAll('flt-picture').single.children, isNotEmpty); + }); + + test('does not skip painting picture when picture is ' + 'inside transform', () async { + final Picture picture = _drawPicture(); + // Picture should not be clipped out since transform will offset it to 500,500 + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + builder.pushOffset(0, 0); + builder.pushClipRect(const Rect.fromLTRB(0, 0, 1000, 1000)) as PersistedContainerSurface; + builder.pushTransform((Matrix4.identity()..scale(0.5, 0.5)).toFloat64()); + builder.pushOffset(1000, 1000); + builder.addPicture(Offset.zero, picture); + builder.pop(); + builder.pop(); + builder.pop(); + html.HtmlElement content = builder.build().webOnlyRootElement; + expect(content.querySelectorAll('flt-picture').single.children, isNotEmpty); + }); + + test( + 'skips painting picture when picture fully clipped out with' + ' transform and offset', () async { + final Picture picture = _drawPicture(); + // Picture should be clipped out since transform will offset it to 500,500 + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + builder.pushOffset(50, 50); + builder.pushClipRect( + const Rect.fromLTRB(0, 0, 1000, 1000)) as PersistedContainerSurface; + builder.pushTransform((Matrix4.identity() + ..scale(2, 2)).toFloat64()); + builder.pushOffset(500, 500); + builder.addPicture(Offset.zero, picture); + builder.pop(); + builder.pop(); + builder.pop(); + builder.pop(); + html.HtmlElement content = builder + .build() + .webOnlyRootElement; + expect(content + .querySelectorAll('flt-picture') + .single + .children, isEmpty); + }); + test('releases old canvas when picture is fully clipped out after addRetained', () async { final Picture picture = _drawPicture(); @@ -416,8 +476,8 @@ void testMain() { paragraph.layout(ParagraphConstraints(width: 1000)); canvas.drawParagraph(paragraph, Offset.zero); final EngineLayer newLayer = useOffset - ? builder.pushOffset(0, 0, oldLayer: oldLayer) - : builder.pushOpacity(100, oldLayer: oldLayer); + ? builder.pushOffset(0, 0, oldLayer: oldLayer) + : builder.pushOpacity(100, oldLayer: oldLayer); builder.addPicture(Offset.zero, recorder.endRecording()); builder.pop(); return newLayer; @@ -758,6 +818,7 @@ class MockPersistedPicture extends PersistedPicture { int get bitmapPixelCount => 0; } +/// Draw 4 circles within 50, 50, 120, 120 bounds Picture _drawPicture() { const double offsetX = 50; const double offsetY = 50;