Skip to content

Bad font hinting / quality with Agg renderer - #719

Merged
mdboom merged 2 commits into
matplotlib:masterfrom
mdboom:hinting
Feb 29, 2012
Merged

mdboom merged 2 commits into
matplotlib:masterfrom
mdboom:hinting

Conversation

@mdboom

@mdboom mdboom commented Feb 27, 2012

Copy link
Copy Markdown
Member

When using the agg backend to create PNGs, fonts are poorly hinted, and always look blurry. Setting text.hinting = True has no effect.

The below script creates two images that demonstrate the problem. It generates some text in multiple sizes, with both the agg and the cairo backends. While the progression of sizes generally looks smoother with the agg backend, there seems to be no way to replicate the crispness of the cairo backend, particularly around sizes 9, 10, 11, and 12.

I have copies of the images, and zoomed-in versions for comparision, available at
http://jim.sh/~jim/tmp/matplotlib-font

# Test rendering of different font sizes with Cairo vs. Agg backend

import sys, os
if "DISPLAY" in os.environ:
    del os.environ["DISPLAY"]

for backend in [ 'cairo', 'agg' ]:
    # Reload with proper backend
    print "Rendering with " + backend
    for module in list(sys.modules):
        if module.startswith(('matplotlib','pylab')):
            sys.modules.pop(module)
    import matplotlib
    matplotlib.use(backend)
    import pylab

    # This has no effect, but try it anyway
    matplotlib.rcParams['text.hinting'] = True

    # Make plot
    fig = pylab.figure(figsize=[2,5])
    ax = fig.add_subplot(1,1,1)
    ax.set_frame_on(False)
    ax.axes.get_xaxis().set_visible(False)
    ax.axes.get_yaxis().set_visible(False)
    ax.set_xlim([0, 1])
    ax.set_ylim([15, 0])
    pylab.title(backend)

    for size in range(1,25):
        ax.text(0.1, (size ** 1.6) / 10, "hello " + str(size), 
                fontdict = { "size" : size, "family" : "arial" })
    pylab.savefig("test-" + backend + ".png", format="png")
    pylab.clf()

@ghost ghost assigned mdboom Feb 27, 2012
The hinting factor is the amount of softness in hinting is applied in
the horizontal direction.  For years, matplotlib has hinted to 1/8
pixels in the horizontal direction and whole pixels in the vertical
direction.  This results in text that is often more
legible (particularly at smaller sizes) than standard 1:1 hinting.
This is based on idea from this paper from the author of Agg:

    http://www.antigrain.com/research/font_rasterization/

However, sometimes the user may want full-on hinting, so this value is
now tweakable using the `text.hinting_factor` rcParam.
@mdboom

mdboom commented Feb 27, 2012

Copy link
Copy Markdown
Member

What you're seeing is the effect of "soft hinting" where it hints to subpixels in the x direction. Years ago we did a sort of survey on the mailing list of which kind of hinting looked best and that's what we determined. Of course, it doesn't have to be hardcoded, so I've attached some changes that make the amount of softness tweakable with an rcParam. Can you try this out (setting "text.hinting_factor" to 1) and let me know if this resolves your issue? I'm hesitant to change the default as many users (and the testing framework) are relying on the present behavior.

Attached is a pull request that makes the "hinting factor" tweakable at runtime.

The hinting factor is the amount of softness in hinting is applied in
the horizontal direction.  For years, matplotlib has hinted to 1/8
pixels in the horizontal direction and whole pixels in the vertical
direction.  This results in text that is often more
legible (particularly at smaller sizes) than standard 1:1 hinting.
This is based on idea from this paper from the author of Agg:

    http://www.antigrain.com/research/font_rasterization/

However, sometimes the user may want full-on hinting, so this value is
now tweakable using the `text.hinting_factor` rcParam.

@jimparis

Copy link
Copy Markdown
Author

Thanks for the patch! It does improve things, but the hinting still leaves a bit to be desired, at least compared to the Cairo backend. I've updated the images at http://jim.sh/~jim/tmp/matplotlib-font to show how the text.hinting_factor = 1 changes things. I also included a plot that attempts to force hinting with text.hinting = True, but that doesn't seem to make a difference. Notice how vertical letters like the Ls are now sharper horizontally, but still don't fully snap to pixels like in the Cairo case.

Regarding the default, I agree that the present behavior shouldn't be changed at this point. I'm fine with setting a variable in my own scripts.

@jimparis

Copy link
Copy Markdown
Author

I think I figured it out: currently, text.hinting can choose between the FreeType options LOAD_NO_HINTING or LOAD_FORCE_AUTOHINT. The option I need is LOAD_DEFAULT -- use the font's native hinting if available. If I hack lib/matplotlib/backends/backend_agg.py so that _get_hinting_flag always returns LOAD_DEFAULT, then the Agg font rendering matches Cairo exactly.

Maybe text.hinting could be extended in a compatible way, e.g.:

{
False: LOAD_NO_HINTING,
True: LOAD_FORCE_AUTOHINT,
"auto": LOAD_FORCE_AUTOHINT,
"native": LOAD_NO_AUTOHINT,
"either": LOAD_DEFAULT,
}

@mdboom

mdboom commented Feb 28, 2012

Copy link
Copy Markdown
Member

Ok. I've incorporated this.

@jimparis

Copy link
Copy Markdown
Author

Works perfectly. Now text.hinting_factor: 1 and text.hinting: native gives me exactly what I wanted. Thanks so much!

@mdboom

mdboom commented Feb 29, 2012

Copy link
Copy Markdown
Member

Great! Merging...

mdboom added a commit that referenced this pull request Feb 29, 2012
Bad font hinting / quality with Agg renderer
@mdboom
mdboom merged commit d9186fe into matplotlib:master Feb 29, 2012
@mdboom
mdboom deleted the hinting branch March 3, 2015 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants