Skip to content

Conversation

@gspencergoog
Copy link
Contributor

@gspencergoog gspencergoog commented Apr 19, 2018

This adds a toImage function to RenderRepaintBoundary that returns an uncompressed raw image of the RenderRepaintBoundary and its children. A device pixel ratio different from the physical ratio may be specified for the captured image. A value of 1.0 will give an image in logical pixels.

@gspencergoog gspencergoog force-pushed the widget_image branch 7 times, most recently from 13717d2 to 28caa4d Compare April 19, 2018 22:17
@gspencergoog gspencergoog changed the title Add toImage to RenderObject [WIP] Add toImage to RenderObject Apr 19, 2018
@gspencergoog gspencergoog force-pushed the widget_image branch 3 times, most recently from 573b758 to 6ef6eb7 Compare April 20, 2018 21:17
@gspencergoog gspencergoog changed the title [WIP] Add toImage to RenderObject Add toImage to RenderObject Apr 20, 2018
@tvolkert tvolkert requested review from Hixie and tvolkert April 20, 2018 21:23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't say "multiplied by the [window.devicePixelRatio]" since it's covered below in the discussion of the [pixelRatio] named parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sorry, I guess that's irrelevant now that I added pixelRatio.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the render object must not need layout, painting, or compositing

How does the caller know whether this is the case?

Also, can we assert it below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added asserts, and an explanation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is anti-aliasing usually done when the render object is drawn to the screen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's antialiased at the device level (in the GPU), but since the engine downsamples it with nearest neighbor, anything smaller than the device resolution is not effectively antialiased. At least I think that's the case, and Jason says that it can actually take one of several paths in the engine, so it's not really clear to me when it happens. I decided to take out the advice: they can look at the result and decide for themselves to increase the size or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand how we're actually rendering this, but I would presume we're just uploading the whole scene to the GPU, having it render to a texture, then grabbing the texture? In which case I'd expect antialiasing to work fine?

cc @jason-simmons or @chinmaygarde

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, what @Hixie said. We have to render to texture on the GPU first because the scene may reference images that are only resident on the GPU. AA should work just fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be [ui.Scene.toImage]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I wasn't sure if dartdoc would pick it up correctly or not if I did that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

search for [ui. or [dart:ui. and compare to actual output, I forget which one is the right one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth optimizing out this matrix multiply for the case where pixelRatio is 1.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not really. From what I can tell, calling Matrix4.identity() is slower than the diagonal3Values call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try/finally with a call to scene.dispose() in the finally?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test that expects our assertion error if the render object isn't a repaint boundary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don't usually test asserts as far as I can tell, and if my call doesn't assert, then the layer getter will.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do test asserts sometimes (check for takeException), but this point will be moot anyway if you move it to RenderRepaintBoundary.

Copy link
Contributor

@tvolkert tvolkert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gspencergoog gspencergoog force-pushed the widget_image branch 2 times, most recently from 15bce3f to ebf0fb8 Compare April 20, 2018 22:26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid people depending on particular widgets that happen to be repaint boundaries but might not always be, I recommend putting this on RenderRepaintBoundary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess that makes sense. It means that they'll have to cast the result when they get the render object in order to call this function, but that's not horrible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paintBounds is a bit of a dubious choice because it's just advisory really.
If you move this to RenderRepaintBoundary, you can just use size, which is much more authoritative.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should replace "dimensions" with the actual property that we use to determine the dimensions (currently [paintBounds], though if you take my feedback below, [size]).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, size it is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than "must not need painting" it might be more positive to say "must have gone through the paint phase" or some such (the parenthetical is fine though)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than using multiplied() and allocating a new matrix, you can just use translate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should change toImage to just take a Size... cc @tvolkert

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the use cases we've heard for this, I don't have a strong opinion either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add a test that verifies that it works (gives the right pixels) with two layers, since that was a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@gspencergoog gspencergoog changed the title Add toImage to RenderObject Add toImage to RenderRepaintBoundary Apr 20, 2018
@gspencergoog gspencergoog force-pushed the widget_image branch 5 times, most recently from 09eeb2b to 681a657 Compare April 21, 2018 02:12
@tvolkert
Copy link
Contributor

I tried this out in a simple widget test:

testWidgets('test', (WidgetTester tester) async {
  await tester.pumpWidget(new MyApp());

  RenderRepaintBoundary repaintRenderBoundary = tester
      .allRenderObjects
      .firstWhere((RenderObject object) => object is RenderRepaintBoundary);
  ui.Image image = await repaintRenderBoundary.toImage();
  ByteData bytes = await image.toByteData(format: ui.ImageByteFormat.png);
});

If I run that test on an Android device/emulator, I get a correct PNG capture of the app. However, if I run that on the host in flutter_tester, the PNG is all white.

Am I holding it wrong?

@tvolkert
Copy link
Contributor

aha - indeed I was holding it wrong. I was calling:

await new File('/path/fo/foo.png').writeAsBytes(bytes);

Which doesn't play nicely with our fake async. When I call writeAsBytesSync(bytes), it all worked great.

@gspencergoog gspencergoog merged commit a043ac4 into flutter:master Apr 23, 2018
DaveShuckerow pushed a commit to DaveShuckerow/flutter that referenced this pull request May 14, 2018
…tter#16758)

This adds a toImage function to RenderRepaintBoundary that returns an uncompressed raw image of the RenderRepaintBoundary and its children. A device pixel ratio different from the physical ratio may be specified for the captured image. A value of 1.0 will give an image in logical pixels.
@gspencergoog gspencergoog deleted the widget_image branch May 30, 2018 21:16
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants