Skip to content

SP5: render Legend into Image#3062

Merged
swharden merged 25 commits intoScottPlot:mainfrom
KroMignon:Legend
Dec 20, 2023
Merged

SP5: render Legend into Image#3062
swharden merged 25 commits intoScottPlot:mainfrom
KroMignon:Legend

Conversation

@KroMignon
Copy link
Contributor

@KroMignon KroMignon commented Dec 5, 2023

This is a first step to allow rendering legend alone as Image , which can be converted into many type of picture formats (BMP, JPEG, etc.).

This should fix #2934.

  • fix initialisation bug in PixelRects
  • add GetLegendImage() into Plot

@swharden tell me what you are thinking about this.

@swharden
Copy link
Member

swharden commented Dec 5, 2023

Hi @KroMignon, thank you for working on #2934! At first glance this looks fantastic

I see this is still "WIP" so let me know when you're ready to merge and I'll review it a little closer and merge it in good time

My only suggestion is to add XML docs to GetImage() to clarify what it does. Perhaps "Return the content of the legend as a standalone image" or something like that

- fix initialisation bug in PixelRects
- add GetLegendImage into Plot
- add optional parameter for MaxWidth and MaxHeight
- fix shadow rendering
@KroMignon
Copy link
Contributor Author

Hello @swharden, sorry for the long delay since last update, I was sick with COVID 😷.

I made some changes to fix shadow rendering, but I am not sure if shadow is required in this case?
I also add optional parameters to limit height and/or width of the legend.

I would also propose to change the Image class to allow access to SKImage member to show it on screen.
Or perhaps create a shadow copy of it is required, I am not really aware about the good practices with Skia.

- `GetLegendImage` now returns a SKImage instance to be more flexible
- fix legend shadow redering
- add XML documentation
@KroMignon
Copy link
Contributor Author

Hello @swharden, what do you think about this implementation?
Converting Legend into SKImage has for me the advantage that the user could convert it in any kind of picture format with help of the ScottPlot.Image class or display it on screen quickly with help of package SkiaImageView

@KroMignon KroMignon changed the title SP5: render Legend into Image (WIP) SP5: render Legend into Image Dec 12, 2023
@bclehmann
Copy link
Member

I don't think it's ideal that Render and GetImage are near duplicates of each other.

Instead, I think both GetImage and Render should be implemented in terms of a private function which just draws the legend onto a given canvas at a given point. Then Render simply calls this on the canvas it's given once it has calculated where it should be placed based on its alignment, padding, etc. And GetImage simply creates a canvas, calls this private function, and returns the resulting image. Both will require sizing calculations, which probably also should be refactored into a private function.

As for SKImage vs ScottPlot.Image, I'm a little on the fence. If the user wants to modify the image or composite it onto their plottables, I don't think ScottPlot.Image is fit for purpose. But if they want to save the image to disk, I don't believe SKImage is fit for purpose. It is tempting to simply give them an SKImage and document that ScottPlot.Image provides facilities to save that to disk, but I'm unsure if we want to tie our API to Skia so strongly.

@KroMignon
Copy link
Contributor Author

@bclehmann many thanks for your feedback.

Instead, I think both GetImage and Render should be implemented in terms of a private function which just draws the legend onto a given canvas at a given point. Then Render simply calls this on the canvas it's given once it has calculated where it should be placed based on its alignment, padding, etc. And GetImage simply creates a canvas, calls this private function, and returns the resulting image. Both will require sizing calculations, which probably also should be refactored into a private function.

I generally try to avoid code duplication, but sometimes it is more effective. I will check your suggestion more deeply, perhaps I find a way which is not "overengineered" / overcomplicated.

It is tempting to simply give them an SKImage and document that ScottPlot.Image provides facilities to save that to disk, but I'm unsure if we want to tie our API to Skia so strongly.

I do not understand the problem here, AFAIK ScottPlot 5 backend is Skia and not multi-backend targeted, or did I misunderstood something?

@KroMignon
Copy link
Contributor Author

KroMignon commented Dec 15, 2023

@swharden this PR comes with the following changes:

  • legend can be rendered into SKImage
  • legend image size can be constrained
  • fix legend item selection to not add hidden plotables
  • fix initialization bug in PixelRects

@swharden
Copy link
Member

Hi @KroMignon, thanks for your extensive work on this! Thanks @bclehmann too for your input along the way.

I reworked the code a bit and now this is possible:

Plot plt = new();
var sig1 = plt.Add.Signal(Generate.Sin());
sig1.Label = "Sine";
plt.Legend.IsVisible = true;

Image img = plt.GetLegendImage();
img.Save("demo.png");

SvgImage svg = plt.GetLegendSvg();
svg.Save("demo.svg");

I created a SvgImage class that creates and exposes a SVG-backed Canvas that can get passed into the Legend for rendering, then it can get saved by the user at any time. Although the API original to this PR had users pass a filename into SaveLegendAsSvg(), I think this new design is a little more consistent internally. It also prevents exposing SkiaSharp types to the user.

The basic functionality works now so I'm going to merge this, but there is definitely room for improvement. SkImage does not implement IDisposable right now. Also code throughout the Legend class could be refactored to reduce duplicate processes like item measurement, paint creation, etc. It's a pretty difficult class to work in because it's so complicated, so there's a lot of room for improvement.

I don't want refining the legend to block development of the rest of ScottPlot 5 so I think this is fine to merge for now, but I welcome refinements along the way and will probably perform a deep refactor of the Legend class at some point.

Thanks again, I'm glad to have this in ScottPlot 5! 🚀

@swharden swharden merged commit 9aedc38 into ScottPlot:main Dec 20, 2023
@KroMignon
Copy link
Contributor Author

@swharden @bclehmann

I was not 100% happy with actual code, but had no time to reorganize/rewrite it.
@bclehmann gives interesting ideas, perhaps I can find time during the next days.

Thanks for merging it into code base.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SP5: GetLegendBitmap()

3 participants