[cairo] Concerns about using filters for downscaling
Owen Taylor
otaylor at redhat.com
Fri Mar 21 15:33:32 PDT 2014
I was reviewing what changes have landed in 1.13.x, since I'd really to
see a stable release with cairo_set_device_scale(). The other major
change in 1.13.x is the use of pixman convolutions for downscaling for
the image backend, to get a better appearance.
Better downscaling is something that has been wanted for a long time.
Yay! But I have some concerns with the code that's in there now.
Performance
===========
Here's a rough set of timings (with a fix for BEST - a missing break
makes it the same as GOOD, and BILINEAR changed to scale-down with
BILINEAR scaling like GOOD/BEST used to rather than using the
convolution). The times are the number of microseconds
it takes to paint a 512x512 image scaled down by the given scale.
[ I'm not sure why the NEAREST/BILINEAR numbers jump around so much
depending on the scale - it seems to be reproducible, but in
any case doesn't affect the general conclusions ]
scale NEAREST BILINEAR GOOD BEST
---- ------ -------- ----- ------
1.1 656 2860 15163 149147
1.5 350 1574 7918 108820
1.9 220 965 7176 89324
2.0 51 873 6397 95665
2.5 34 114 4331 71975
3.0 25 392 4074 67211
3.5 66 290 3101 60168
4.0 15 223 3135 59207
4.5 12 37 2524 58586
5.0 34 143 2643 56798
As you can see, for small downscales, where we used to have OK (if not
great) appearance, we're now slower by a factor of > 5x for the default
GOOD filter. BEST is basically unusable.
The size of the convolution filters for a uniform scale-down of S is NxN
where N is given by:
GOOD: N = ceil(S + 2);
BEST: N = ceil(S * 6 + 6);
So for BEST at a scale-down of 5x5 we're actually convolving a 36x36
filter.
Backend consistency
===================
Since this functionality is specific to pixman, it means that
the default filter CAIRO_FILTER_GOOD has radically different performance
and appearance behaviors between the xlib/xcb backends and the pixman
based backends... not only the image backend, but also the Windows
backend, etc.
An application that looks good on Windows, might look ugly under X.
An application that performed OK on X might be unusuably slow under
Windows.
Access to bilinear scaling
==========================
If we have convolution for GOOD and BEST, then it still seems to be
important to have access to BILINEAR filtering - a reliably fast (if
ugly for large downscales) method. Also a bilinear scale down by exactly
2 averages each set of 4 pixels, which is useful.
Bugs?
=====
* For some reason, the results of downscaling images don't have
left-right symmetry. The result of scaling down:
000000 000000 000000 000000
000000 ffffff ffffff 000000
000000 ffffff ffffff 000000
000000 000000 000000 000000
By a factor of two is:
646464 3c3c3c
3c3c3c 242424
(I'm a bit suspicious about the fact that pixman generates
even length filters, and wonder if that has something to do
with the asymmetry.)
* When the filters are created, the number of subsample bits
passed to pixman is always 1 (even for the huge BEST filters)
This produces artifacts that drown out the choice of filter,
and may result in results worse than bilinear filtering
for some scale factors.
4 is a pretty safe value for subsample bits - though it
depends on a) the scale b) the image. For large scale
downs it should be fine to use less subsample bits - and
that's where the expense of generating more copies of the
filter is going to be.
* In _pixman_image_set_properties computation of scale_x,
scale_y, yx is used twice and xy not used.
* See comment on top of _cairo_pattern_analyze_filter() -
it makes assumptions that are no longer true.
Thoughts
========
The good thing about this general approach is that it's
straightforward to understand what is going on and what results it
produces and isn't much code. And it's there in the repository, not
just theory about what we *could* do. :-)
Downsides with the general approach:
* The approach is not efficient for large downscales, especially
when transformed. Approaches involving:
- Sampling from a mipmap
- Sampling a sparse set of irregularly distributed points
will be more efficient.
* On a hardware backend, running even a large convolution filter in
a shader is likely possible on current hardware, but it's not
taking efficient use of how hardware is designed to sample images.
Here are my suggestions with an eye to getting a release of 1.14
out quickly
* The convolution code is restricted to the BEST filter, leaving
GOOD and BILINEAR untouched for now. The goal in the future
is we'd try to get similar quality for all backends, whether by:
A) Triggering a fallback
B) Implementing convolution with the same filter
C) Using an entirely different technique with the similar
quality.
* For BEST a filter is used that is more like what is used for
GOOD in the current code - i.e. 10x slowdown from BILINEAR,
not 100x.
* An attempt is made to address the bugs listed above.
* In the future, the benchmark for GOOD is downscaling by
factors of two to the next biggest power of two, and sampling
from that with bilinear filtering. Pixel based backends
should do at least that well in *both* performance and
quality.
More information about the cairo
mailing list