Poor on-screen font antialiasing/smoothing in mplcairo
Opened this issue · 18 comments
I'm trying to switch to using the mplcairo
backend because it does certain things better than the MacOSX
backend, especially when colormaps have transparency. However, I've noticed that the font smoothing, at least for plots displayed on the screen, is noticeably poorer. The code below offers a MWE:
import numpy as np
from matplotlib import pyplot as plt
fig = plt.figure()
plt.plot(np.linspace(0,1,10), np.linspace(0,1,10), '-o')
plt.setp(plt.gca().get_xticklabels(), size=14)
plt.setp(plt.gca().get_yticklabels(), size=14)
With the mplcairo
backend, the tick labels look jagged and not smooth, as below.
Whereas with the MacOSX
backend gives me noticeably smoother fonts.
Everything is identical for the production of the two figures except the backend, which I switch in .matplotlibrc
. Am I missing something? Is there a way to get mplcairo
to render fonts with proper smoothing?
import mplcairo
mplcairo.get_versions()
{'python': '3.11.6 (main, Oct 12 2023, 21:46:57) [Clang 15.0.0 (clang-1500.0.40.1)]',
'mplcairo': '0.5.post32+ge771c74',
'matplotlib': '3.8.0',
'cairo': '1.17.6 @ /Users/sbasu1/packages/macports/lib/libcairo.2.dylib',
'freetype': '2.13.2 @ /Users/sbasu1/packages/macports/lib/libfreetype.6.dylib',
'pybind11': '2.11.1',
'raqm': None,
'harfbuzz': None}
Unfortunately I cannot reproduce the issue. What is the value of rcParams["text.antialiased"]
? If you apply the patch below, what antialiasing value gets printed?
diff --git i/ext/_util.cpp w/ext/_util.cpp
index 55ed9d2..57da84d 100644
--- i/ext/_util.cpp
+++ w/ext/_util.cpp
@@ -856,6 +856,7 @@ void adjust_font_options(cairo_t* cr)
// The hint style is not set here: it is passed directly as load_flags to
// cairo_ft_font_face_create_for_ft_face.
cairo_set_font_options(cr, options);
+ py::print(cairo_font_options_get_antialias(options));
cairo_font_options_destroy(options);
}
Also, can you try with cairo 1.18?
from matplotlib import pyplot as plt
plt.rcParams['text.antialiased']
True
I would be happy to apply the patch you mentioned, but could you tell me how? Due to this issue, I can't install from a source tarball. Instead, I do
pip install -v git+https://github.com/anntzer/mplcairo
So where/how do I apply that patch?
OK, I will try updating cairo
.
To test the patch: Clone the repository, apply the patch, then pip install .
from the root directory.
Also, perhaps try the module://mplcairo.qt or module://mplcairo.tk backends? (I think you're on the mplcairo.macosx backend right now; perhaps there are some issues with HiDPI.)
Do you have anything special in your matplotlibrc? Does the issue remain if you remove all its contents except for the backend setting, or if you call matplotlib.rcdefaults() first?
Do you also have the problem with mathtext? (plt.text(.5, .5, "$1.23$"
at whatever relevant size)
With the following (to get a few different sized mathtext texts)
plt.text(.6, .2, r"$1.23$", ha='center', va='center', size=14)
plt.text(.6, .1, r"$1.23$", ha='center', va='center', size=12)
plt.text(.6, .3, r"$1.23$", ha='center', va='center', size=10)
I get the following, where the mathtext instances are smooth even down to size 10.
I should say here that I do have raqm
and harfbuzz
installed on the system, so I'm not sure why mplcairo
does not use those (according to mplcairo.get_versions()
).
Hmm, that is interesting. What happens if you try to force loading raqm with mplcairo.set_options(raqm=True)
? What if you hard-code the path of the raqm dylib in load_raqm() (in _raqm.cpp), replacing the whole initial part (up to just before #define LOAD_API
) with raqm::_handle = os::dlopen("/path/to/your/libraqm.dylib")
?
OK, this is interesting and slightly puzzling. I have raqm
installed via macports,
$ ls -l /Users/sbasu1/packages/macports/lib/libraqm.dylib
lrwxr-xr-x 1 sbasu1 staff 15 2024 Jan 04 01:32:15 /Users/sbasu1/packages/macports/lib/libraqm.dylib -> libraqm.0.dylib
and that path is both in LD_LIBRARY_PATH
and LD_RUN_PATH
,
$ env | grep LD_
LD_LIBRARY_PATH=/Users/sbasu1/packages/lib:/Users/sbasu1/packages/macports/lib:
LD_RUN_PATH=/Users/sbasu1/packages/lib:/Users/sbasu1/packages/macports/lib:
Yet, when I try to force load as follows,
In [1]: import mplcairo
In [2]: mplcairo.set_options(raqm=True)
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
Cell In[2], line 1
----> 1 mplcairo.set_options(raqm=True)
OSError: dlopen(libraqm.dylib, 0x0001): tried: '/opt/intel/compilers_and_libraries_2019.5.281/mac/compiler/lib/libraqm.dylib' (no such file), '/opt/intel/compilers_and_libraries_2019.5.281/mac/compiler/lib/intel64/libraqm.dylib' (no such file), '/opt/intel/compilers_and_libraries_2019.5.281/mac/tbb/lib/libraqm.dylib' (no such file), '/opt/intel/compilers_and_libraries_2019.5.281/mac/compiler/lib/libraqm.dylib' (no such file), '/opt/intel/compilers_and_libraries_2019.5.281/mac/mkl/lib/libraqm.dylib' (no such file), 'libraqm.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OSlibraqm.dylib' (no such file), '/usr/lib/libraqm.dylib' (no such file, not in dyld cache), 'libraqm.dylib' (no such file), '/usr/local/lib/libraqm.dylib' (no such file), '/usr/lib/libraqm.dylib' (no such file, not in dyld cache)
It's not even looking in LD_LIBRARY_PATH
and LD_RUN_PATH
!
So, I hardcoded the path to libraqm.dylib
as you suggested, and indeed mplcairo
now knows about raqm
(and also harfbuzz
, somehow, although I did no hardcoding for that),
In [1]: import mplcairo
In [2]: mplcairo.get_versions()
Out[2]:
{'python': '3.11.7 (main, Jan 3 2024, 08:15:29) [Clang 15.0.0 (clang-1500.1.0.2.5)]',
'mplcairo': '0.5.post32+ge771c74',
'matplotlib': '3.8.2',
'cairo': '1.17.6 @ /Users/sbasu1/packages/macports/lib/libcairo.2.dylib',
'freetype': '2.13.2 @ /Users/sbasu1/packages/macports/lib/libfreetype.6.dylib',
'pybind11': '2.11.1',
'raqm': '0.10.1 @ /Users/sbasu1/packages/macports/lib/libraqm.0.dylib',
'harfbuzz': '8.3.0 @ /Users/sbasu1/packages/macports/lib/libharfbuzz.0.dylib'}
Thanks for trying. Not any bright idea right now...
My silly guess is that
- the size-dependent smoothness at size 16 has something to do with the font's built-in bitmaps being used (macOS, as far as I know, doesn't like to use it at all)
- the different threshold for
plt.text
is because it handles retina screen scale (DisplayFramebufferScale) differently
... I think there's something in fontconfig that might turn off embeddedbitmaps -- if it works, my hunch would be in the right direction.
Thanks for the suggestion. Do you know how to test the hypothesis?
Having a bit of an issue reproducing on my end (but don't worry, anntzer also failed to repro). Probably something to do with how homebrew cairo and the whole stack below is configured differently compared to macports.
You should be able to (hopefully) turn off those bitmaps with a new file. Write the following into a new file called /opt/local/etc/fonts/conf.d/95-noemb.conf
:
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<match target="font">
<edit name="embeddedbitmap" mode="assign">
<bool>false</bool>
</edit>
</match>
</fontconfig>
@Artoria2e5 I tried your 95-noemb.conf
and no luck, the tick labels are still not smooth. This is a screenshot.
Note that if I save the figure with plt.savefig('jagged_ticklabels.png')
they are rendered smooth for some DPI's but not all. I would have thought that below a certain DPI the fonts would have been jagged, and smooth above that threshold. However, what I see is that at 90 DPI, they're smooth.
At 100 DPI, they're jagged.
And then at 120 DPI, they're smooth again.
Does this provide a clue, perhaps?