-
Notifications
You must be signed in to change notification settings - Fork 69
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
Support mipmap downscaling #13
Comments
I'm using resvg for handling a variety of SVG files, and noticed that the quality of scaled down PNGs is definitely more blocky compared to other renderers, and I assume this is the missing/required feature? I'll probably implement something simple for my usecase, but I'd be interested in the general design you had in mind for this. Is this a specific pipeline component, or an alternative path when in the high quality mode for either bilinear or bicubic? Is full mipmap generation/caching essential, or would just scaling down by some power of ½ quickly in simd then doing bilinear or bicubic on that provide the same results? I'd probably implement the latter, although mostly running via wasm so no simd support anyway. |
No, it's a separate issue of an unknown origin: linebender/resvg#653
We do support some WASM SIMD. |
Ah, thank you. I somehow missed that one. My understanding is that it generates a set of mipmaps from the base (upper) size to the preferred lower size which calculated through
Ah, apologies. My ideas of what's possible in WASM must be stuck somewhat in the past! I'll see what compiling with |
For what it's worth, I finally pushed an implementation of mipmaps I did a couple of months ago on this branch . It's definitely not got all the features of the Skia implementation like sensible caching, but seems to generate almost identical renders in my testing. |
Looks good to me. I'm interested in merging it. Does it fixes the downscaling issue in How close the code to the Skia version? I understand that there are no caching, but are there any other changes? I'm curious how hard it would be to maintain it in the future. |
It should address the downscaling quality in resvg, yes. I haven't benchmarked it, and sadly haven't added any tests. In this patch it will be invoked if you are downscaling less than half the picture size with either bilinear or bicubic scalers, using the next largest size as a source. Skia does something a bit more interesting when Apart from that sampling change and per-source caching, I think the main other thing Skia does differently is all the varied GPU-assisted generation of mipmaps. And obviously selection of mipmapmode as an independent setting from filtering/sampling so it can be enabled and disabled as desired. Here are some output of running resvg with or without this patch on those files mentioned in linebender/resvg#653, where:
The smileys (source) have been zoomed up from 49x49 by 400% using feh, so I guess there's a small chance that softens edges.
I was mostly looking for the "Chromium" and "After" to be consistent, and I don't think they're far off. I don't really know what the future/maintenance would be like - I'm not much of a rust programmer, and somebody who is one could probably find a better way to handle a mipmap cache being against the source pixmap, in particular getting the right lifetimes on that when it's passed into the Blitter. I also switched to always using the It's probably a bit messy the way that it modifies the pipeline transform to scale the content back up again, but it was easier for me so I could keep that logic in one place in |
Thanks! You code looks fine to me. By maintenance I was mostly curious about how different it to Skia. The Rust code itself is fine. Since you have already dived into it, do you have any idea why simply doing bilinear/bicubic doesn't work? My understanding was that mipmaps are just an optimization, that's why I have skipped them. But it appears like they are mandatory. Very strange. As for mipmaps cache, I'm sure you can guess that doing a thread-safe cache would be a total pain in Rust. Even in Skia/C++ it's pretty convoluted with unique per-image ID generation and stuff. |
Oh, right - that's relatively simple. If you do a bilinear scale - that is, take 4 corners of a square and interpolate them to a new pixel - then as soon as you scale to less than 50% of the original you are missing pixels from the source image. At exactly 50% scale you create 1 pixel from 4 source pixels. Any less than that and you are effectively creating 1 pixel from 9 potential source pixels, but only sampling 4 of them; hence the loss of quality. By the time you get to 25%, you are creating 1 pixel from 16 and ignoring the values in 12 of those! Bicubic will fare a bit better because it takes 4x4 samples to produce one pixel, but below 25% will suffer the same fate. There are some good notes about resampling by Jason Summers which I see is referenced a couple of times in the Skia source. In particular "Reducing" on the Image Resampling page that notes some of the issues. The "correct" way to do downsampling is to create a rectangular region of the source image that maps to a destination pixel, then find an average pixel value by weighting the source pixels covered by the box by how much the box "overlaps" that pixel. There's an optimisation that Jason mentions of precalculating a weight lookup table, but either way it does have to deal with the literal edge case of how to weight pixels near the edge of the image. Or there's mipmaps, traditionally used for quick rendering of distant textures in 3D engines, where you resize the image down in a series of really predictable/easy half-scales and use one of those as your source instead. As long as no less than 50% scale is being done from a mipmap source image, all of the pixels from your source will be appropriately used in the output. The other neat thing for libraries like Skia is that GPUs can typically generate mipmaps very quickly for 3D textures so the CPU doesn't have to do any work. Even in the CPU it's all integer addition and bitshifts so should be pretty fast. |
Thanks! I really appreciate your explanation. I'm still learning 2D graphics basics. Back to the Rust version, afair, Skia uses mipmaps only during bilinear scaling, so bicubic should work out of the box, but the Also, are you generating all scales at once? Shouldn't we generate only the one we need? Since we do not cache them anyway. Or do I misunderstood the code? |
I've spent far too long looking through Skia code now, and not completely sure if it makes any sense. Yes, This has changed from previous code - you used to set a quality and it might switch from bicubic to bilinear. Or at least I thought it could. I can't find that now. Since there is now no option to use mipmaps with bicubic it looks predictably rubbish when downsampling - on this Skia fiddle only the 3rd linear + mipmapped render looks any good and the 4th bicubic looks no better than nearest sampled one on the left. I guess when you use Skia for rendering SVGs you have to know which bits you are downsampling and which are upsampling and select different options? I gather they've been keen to move away from low/medium/high quality, but IMHO this seems like it just makes things tougher for a user who now has to care a bit more about whether they are upscaling or downscaling. Probably mipmap+bicubic isn't going to get you anything better than mipmap+bilinear, and my patch could almost certainly set Otherwise, to answer your other question: Only the number of scales required are produced - |
Thanks again! My Skia knowledge stops at 2020, when I was writing From my perspective, I think Basically, |
Skia supports bilinear down-scaling using mipmap. It's not that easy to implement and Skia also caches them.
The text was updated successfully, but these errors were encountered: