Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple svg images render blurry #3501

Open
thequver opened this issue Oct 24, 2023 · 4 comments
Open

Multiple svg images render blurry #3501

thequver opened this issue Oct 24, 2023 · 4 comments
Labels
bug Something is broken

Comments

@thequver
Copy link

Describe the bug
When adding multiple svg images from the same source but with different size, the latter image renders blurry as if it takes the texture from the first added (smaller) image.

To Reproduce
Code to reproduce:

                const IMG_SRC: egui::ImageSource = egui::include_image!("./img.svg");
                ui.add_sized(egui::Vec2{x: 20.0, y: 20.0}, egui::Image::new(IMG_SRC));
                ui.add_sized(egui::Vec2{x: 200.0, y: 200.0}, egui::Image::new(IMG_SRC));

Image i used to test:
img

Expected behavior
Both vector images must look sharp and render at respective for its size resolution

Screenshots
Screenshot

Desktop (please complete the following information):

  • OS: Win11

Additional context
Reproduced on win11, android 10, eframe, egui-winit + egui-wgpu implementation, egui 0.23.0, master branch (as the time of writing)

@thequver thequver added the bug Something is broken label Oct 24, 2023
@chriscate
Copy link
Contributor

This appears to be due to the the caching done by the ImageLoader. If you call ctx.forget_image(IMG_SRC.uri().unwrap()); between your two ui.add_sized() calls it will work as expected based on my testing. There is also a TODO here for automatic cache eviction.

If anyone has guidance on how to implement that automatic eviction I'd be happy to take a shot at it.

emilk pushed a commit that referenced this issue Dec 20, 2023
Update `resvg` from v0.28 to v0.37. 
Remove related, unnecessary entries from `deny.toml`.

⚠ In example `images` ferris is scaled differently, but I guess that now
it scales in expected way (takes all available space; before this PR it
takes up to space that, was available at first render- it does not
upscale).

This PR is minimal adaptation to new `resvg` api and small related
simplification, however it should be considered to update loaders
(currently if svg image initially was small and was scaled up it will be
blurred, see #3501). As svg image
now scales over render size, problem will be more often seen now.

(currently `SvgLoader` theoretically should rerender for different sizes
(but I guess it will result in memory leak in that case), but refreshing
is stopped earlier in `DefaultTextureLoader`).

I have initial version of loaders update, that will fix issue with svg
scaling (and also enable e.g. reloading image if file has been changed),
I will submit these changes in separate PR once this one is merged.

Closes <#3652>.
@YgorSouza
Copy link
Contributor

I was looking into this, and found that the SVG loader actually includes the size hint as part of its key:

#[derive(Default)]
pub struct SvgLoader {
cache: Mutex<HashMap<(String, SizeHint), Entry>>,
}

Which would solve this problem, but also sounds kind of dangerous, because if you have an SVG that fills the available space, you would end up with hundreds of copies at different sizes as the UI is resized.

But in any case, it isn't working as intended, because the image loader is called by the texture loader, which does not take the size into account.

fn load(
&self,
ctx: &Context,
uri: &str,
texture_options: TextureOptions,
size_hint: SizeHint,
) -> TextureLoadResult {
let mut cache = self.cache.lock();
if let Some(handle) = cache.get(&(uri.into(), texture_options)) {
let texture = SizedTexture::from_handle(handle);
Ok(TexturePoll::Ready { texture })
} else {

So one way to work around this would be to copy this default texture loader to your code and change it to take the size into account like the SVG loader does, then add it to the context using add_texture_loader. Maybe this should be added to egui itself, but it seems like it would be hard to find a balance between CPU and RAM usage and image quality that would work for every use case.

@molenick
Copy link

molenick commented May 16, 2024

I'm experiencing this as well with the SvgLoader and am trying to find a good interim solution. @YgorSouza's post has me on the right track, instead of using a TextureLoader I'm applying the same line of thinking to create a custom ImageLoader copied from egui_extras SvgLoader.

So far I've noticed the following behaviors from debugging the caching of my custom svg loader:

  • Loading an svg uri 2x, first with small size and second with large size results in blurry image
  • Loading an svg uri 2x, first with large size and second with small size results in crisp image
  • Loading an svg uri multiple times with many sizes, I never see additional cache entries added

I don't think anything special is happening here on my end, I'm just using the painter api to paint into a rectangle. I think the next thing to wrap my head around:

  • how does first-time painting of an svg into a rectangle influence the SizeHint of a cache entry?
  • why do I never see multiple cache entries for my svg uri, when I have requested paints at multiple sizes? - I think this is because we only cache when reading from disk, which happens once.
  • is there a way to load an svg from disk with an explicit size hint (instead of the implicit size hint from first read)
  • in general, in what scenarios will a loaded image have multiple cache entries?

@molenick
Copy link

Ok, I see what I was missing: TextureLoader is the abstraction level that paint uses to cache an svg.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something is broken
Projects
None yet
Development

No branches or pull requests

4 participants