mplcairo icon indicating copy to clipboard operation
mplcairo copied to clipboard

Poor on-screen font antialiasing/smoothing in mplcairo

Open AgilentGCMS opened this issue 1 year ago • 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. Screenshot 2023-12-28 at 4 32 03 PM Whereas with the MacOSX backend gives me noticeably smoother fonts. Screenshot 2023-12-28 at 4 33 00 PM

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}

AgilentGCMS avatar Dec 28 '23 21:12 AgilentGCMS

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?

anntzer avatar Jan 02 '24 16:01 anntzer

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.

AgilentGCMS avatar Jan 03 '24 02:01 AgilentGCMS

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.)

anntzer avatar Jan 03 '24 08:01 anntzer

With the patch, antialias_t.SUBPIXEL gets printed. I tried module://mplcairo.tk and got this.

Screenshot 2024-01-03 at 8 28 05 AM

So, same issue with font smoothing as mplcairo.macosx.

AgilentGCMS avatar Jan 03 '24 13:01 AgilentGCMS

I should also add that this seems to be a problem only for font sizes at the smaller end. In the above example, when I replace with size=16, I get this, which is smooth enough.

Screenshot 2024-01-03 at 8 32 35 AM

AgilentGCMS avatar Jan 03 '24 13:01 AgilentGCMS

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?

anntzer avatar Jan 03 '24 14:01 anntzer

I do have some stuff in my .matplotlibrc to set the font family and color order for plots, so I replaced all that with a .matplotlibrc where the only non-commented line is backend : module://mplcairo.macosx. This is the result. Screenshot 2024-01-03 at 11 46 59 AM

AgilentGCMS avatar Jan 03 '24 16:01 AgilentGCMS

And with the same "blank" .matplotlibrc, if I switch back to backend : MacOSX this is what I get. Screenshot 2024-01-03 at 11 51 12 AM

AgilentGCMS avatar Jan 03 '24 16:01 AgilentGCMS

Do you also have the problem with mathtext? (plt.text(.5, .5, "$1.23$" at whatever relevant size)

anntzer avatar Jan 04 '24 07:01 anntzer

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. Screenshot 2024-01-04 at 9 42 08 AM 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()).

AgilentGCMS avatar Jan 04 '24 14:01 AgilentGCMS

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")?

anntzer avatar Jan 04 '24 18:01 anntzer

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!

AgilentGCMS avatar Jan 07 '24 19:01 AgilentGCMS

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'}

However, the tick label fonts are still not smooth Screenshot 2024-01-07 at 2 21 12 PM

AgilentGCMS avatar Jan 07 '24 19:01 AgilentGCMS

Thanks for trying. Not any bright idea right now...

anntzer avatar Jan 07 '24 20:01 anntzer

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.

Artoria2e5 avatar Jan 31 '24 13:01 Artoria2e5

Thanks for the suggestion. Do you know how to test the hypothesis?

anntzer avatar Feb 01 '24 18:02 anntzer

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 avatar Feb 04 '24 13:02 Artoria2e5

@Artoria2e5 I tried your 95-noemb.conf and no luck, the tick labels are still not smooth. This is a screenshot. Screenshot 2024-02-20 at 10 44 36 AM 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. jagged_ticklabels_90 At 100 DPI, they're jagged. jagged_ticklabels_100 And then at 120 DPI, they're smooth again. jagged_ticklabels_120 Does this provide a clue, perhaps?

AgilentGCMS avatar Feb 20 '24 15:02 AgilentGCMS