Open cromachina opened 1 month ago
The behavior is correct, the analysis not quite. It's not using nearest-neighbor interpolation, you just run into the limits of bilinear interpolation at those kinds of zooms and end up with not very smooth results.
There's currently three different canvas renderers: one based on QGraphicsView that'll hopefully get thrown out at some point, a "software" renderer using QPainter directly and a hardware renderer that uses OpenGL (on Windows, it's Direct3D under the hood via ANGLE.) They all work a bit differently.
In the first two cases, there's the QGraphicsView CanvasItem paint function and the software renderer paint function. Since you can't control how QPainter does its thing, you'd probably have to generate LOD instances of the canvas to feed to it.
I can't quite tell, but Krita seems to solve this via "prescaling", chasing through what kis_qpainter_canvas.cpp
does. I guess it just interpolates the image twice. But the performance of the software canvas in Krita is pants, so maybe not the place to look for inspiration.
Adding OpenCV for this is also not tenable maintenance-wise, if you can even manage to get it to build on Android, the web browser and Windows. In the browser, doing the scaling on another thread is also not an option due to overhead. Although Android and browser should really always be using the hardware canvas.
OpenGL is implemented in glcanvas.cpp. It just uses linear interpolation currently, presumably you can convince the GPU to do better by using mipmaps or something. Krita has stuff they call "high-quality filtering" in KisOpenGLCanvasRenderer.cpp
. That's all done GPU-side, which means it shouldn't cause any performance issues, given what little it has to do to render at most a few quads.
Doing the OpenGL implementation should presumably be reasonably straight-forward. The software scaling part looks like a pretty large amount of work with huge negative performance implications, so it would need to be handled with more care. If it's even worth bothering with at all, given the limited breadth of devices that can't deal with the hardware renderer.
Thank you for the informative response!
Krita's GPU canvas view does indeed look pretty good when zoomed out and GPU definitely does seem like the most reasonable choice, given all of the constraining requirements for the different target platforms.
Is it possible to just change the interpolation method? Rather than doing two passes or adding extra steps.
In software mode, it's down to what QPainter provides, which is either nearest-neighbor or bilinear scaling. There's no other options.
OpenGL ES 2.0 provides nearest-neighbor and bilinear scaling, as well as mipmaps, which are also a form of pre-scaling. But you can manually do multiple samples in the fragment shader, so you can implement arbitrary other interpolation that way.
A small side note on the GPU canvas: Krita on Linux sometimes encounters issues with drivers corrupting or not preserving buffer data after waking up from system sleep (canvas becomes covered in random noise). Just some funny edge case to look out for in your own implementation.
Drawpile version 2.2.1, Linux
Problem
When looking at a large canvas while zoomed out (for example, 4000x4000 on a 1080p screen), the canvas view looks extremely aliased, as if using nearest neighbor downscaling. I checked in the
Preferences -> General -> Canvas View: Interpolate when view is zoomed or rotated
, and it only seems to apply when zoomed in, not zoomed out.Potential solution?
I'm not quite sure how it works in Drawpile, but I'm guessing it's because of the limited resampling options provided for
QPixmaps
,Qt::SmoothTransformation
(bilinear, probably) andQt::FastTransformation
(nearest neighbor), both of which tend to look pretty bad when downsampling large images. For my own paint tool project, I hacked around this by usingOpenCV::resize
to downscale the composited canvas view withINTER_AREA
interpolation: https://github.com/cromachina/crowpainter/blob/main/src/crowpainter/main.py#L269OpenCV::resize
seems to be really well optimized and parallelized, but you could also compute a zoom level pyramid or mipmap off of the main thread and update the view later, async (kind of like how Paint Tool SAI does it), if there are performance or library usage concerns.Aliased canvas example in Drawpile:
Smooth canvas example using my own tool: