Skip to content

Commit 97308fa

Browse files
committed
Fallback to GRAY text antialiasing on transparent backgrounds.
1 parent 6defa77 commit 97308fa

File tree

6 files changed

+34
-13
lines changed

6 files changed

+34
-13
lines changed

CHANGELOG.rst

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ next
77
- Support pdf MaxVersion up to 1.7 (if the underlying cairo supports it).
88
- Support custom pdf metadata entries.
99
- `set_options` can now be used as a context manager.
10+
- Improve rendering of antialiased non-black text on transparent background.
1011

1112
v0.5 (2022-08-18)
1213
=================

ext/_mplcairo.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,10 @@ GraphicsContextRenderer::_additional_context()
449449
return {this};
450450
}
451451

452+
void GraphicsContextRenderer::_set_subpixel_antialiased_text_allowed(bool b) {
453+
subpixel_antialiased_text_allowed_ = b;
454+
}
455+
452456
void GraphicsContextRenderer::_set_path(std::optional<std::string> path) {
453457
path_ = path;
454458
}
@@ -1573,7 +1577,7 @@ void GraphicsContextRenderer::draw_text(
15731577
auto const& font_size =
15741578
points_to_pixels(prop.attr("get_size_in_points")().cast<double>());
15751579
cairo_set_font_size(cr_, font_size);
1576-
adjust_font_options(cr_);
1580+
adjust_font_options(cr_, subpixel_antialiased_text_allowed_);
15771581
auto const& gac = text_to_glyphs_and_clusters(cr_, s);
15781582
// While the warning below perhaps belongs logically to
15791583
// text_to_glyphs_and_clusters, we don't want to also emit the warning in
@@ -1633,7 +1637,7 @@ GraphicsContextRenderer::get_text_width_height_descent(
16331637
auto const& font_size =
16341638
points_to_pixels(prop.attr("get_size_in_points")().cast<double>());
16351639
cairo_set_font_size(cr_, font_size);
1636-
adjust_font_options(cr_); // Needed for correct aa.
1640+
adjust_font_options(cr_, subpixel_antialiased_text_allowed_); // For correct aa.
16371641
cairo_text_extents_t extents;
16381642
auto const& gac = text_to_glyphs_and_clusters(cr_, s);
16391643
cairo_glyph_extents(cr_, gac.glyphs, gac.num_glyphs, &extents);
@@ -1789,7 +1793,7 @@ void MathtextBackend::draw(
17891793
{
17901794
if (!std::isfinite(x) || !std::isfinite(y)) {
17911795
// This happens e.g. with empty strings, and would put cr in an invalid
1792-
// state. even though nothing is written.
1796+
// state, even though nothing is written.
17931797
return;
17941798
}
17951799
[[maybe_unused]] auto const& ac = gcr._additional_context();
@@ -1805,7 +1809,7 @@ void MathtextBackend::draw(
18051809
auto const& mtx = cairo_matrix_t{
18061810
size * glyph.extend, 0, -size * glyph.slant * glyph.extend, size, 0, 0};
18071811
cairo_set_font_matrix(cr, &mtx);
1808-
adjust_font_options(cr);
1812+
adjust_font_options(cr, gcr.subpixel_antialiased_text_allowed_);
18091813
auto ft_face =
18101814
static_cast<FT_Face>(
18111815
cairo_font_face_get_user_data(face, &detail::FT_KEY));
@@ -2155,6 +2159,8 @@ Only intended for debugging purposes.
21552159
[](GraphicsContextRenderer& gcr) -> bool {
21562160
return has_vector_surface(gcr.cr_);
21572161
})
2162+
.def("_set_subpixel_antialiased_text_allowed",
2163+
&GraphicsContextRenderer::_set_subpixel_antialiased_text_allowed)
21582164
.def("_set_path", &GraphicsContextRenderer::_set_path)
21592165
.def("_set_metadata", &GraphicsContextRenderer::_set_metadata)
21602166
.def("_set_size", &GraphicsContextRenderer::_set_size)

ext/_mplcairo.h

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ py::object renderer_base(std::string meth_name);
2424
class GraphicsContextRenderer {
2525
public:
2626
cairo_t* const cr_;
27+
bool subpixel_antialiased_text_allowed_ = true;
2728

2829
private:
2930
std::optional<std::string> path_ = {};
@@ -70,6 +71,7 @@ class GraphicsContextRenderer {
7071

7172
AdditionalContext _additional_context();
7273

74+
void _set_subpixel_antialiased_text_allowed(bool b);
7375
void _set_path(std::optional<std::string> path);
7476
void _set_metadata(std::optional<py::dict> metadata);
7577
void _set_size(double width, double height, double dpi);

ext/_util.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ long get_hinting_flag()
867867
.attr("get_hinting_flag")().cast<long>();
868868
}
869869

870-
void adjust_font_options(cairo_t* cr)
870+
void adjust_font_options(cairo_t* cr, bool subpixel_antialiased_text_allowed)
871871
{
872872
auto const& font_face = cairo_get_font_face(cr);
873873
auto const& options = cairo_font_options_create();
@@ -877,7 +877,9 @@ void adjust_font_options(cairo_t* cr)
877877
auto aa = rc_param("text.antialiased"); // Normally *exactly* a bool.
878878
cairo_font_options_set_antialias(
879879
options,
880-
aa.ptr() == Py_True ? CAIRO_ANTIALIAS_SUBPIXEL
880+
aa.ptr() == Py_True
881+
? (subpixel_antialiased_text_allowed
882+
? CAIRO_ANTIALIAS_SUBPIXEL : CAIRO_ANTIALIAS_GRAY)
881883
: aa.ptr() == Py_False ? CAIRO_ANTIALIAS_NONE
882884
: aa.cast<cairo_antialias_t>());
883885
}

ext/_util.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ cairo_font_face_t* font_face_from_path(std::string path);
186186
cairo_font_face_t* font_face_from_path(py::object path);
187187
std::vector<cairo_font_face_t*> font_faces_from_prop(py::object prop);
188188
long get_hinting_flag();
189-
void adjust_font_options(cairo_t* cr);
189+
void adjust_font_options(cairo_t* cr, bool subpixel_antialiased_text_allowed);
190190
void warn_on_missing_glyph(std::string s);
191191
GlyphsAndClusters text_to_glyphs_and_clusters(cairo_t* cr, std::string s);
192192

src/mplcairo/base.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,23 @@ def get_renderer(self, cleared=False):
232232

233233
renderer = property(get_renderer) # NOTE: Needed for FigureCanvasAgg.
234234

235-
def draw(self):
235+
def _draw_without_supercall(self):
236236
renderer = self.get_renderer()
237237
renderer.clear()
238238
with _LOCK:
239+
# Subpixel (LCD) antialiasing of non-black text looks bad on
240+
# transparent backgrounds, so interpret True as GRAY antialiasing
241+
# in that case (it is still possible to force SUBPIXEL antialiasing
242+
# by actually using that antialias_t value). For black text we
243+
# could actually keep it, but get_text_width_height_descent()
244+
# doesn't know the text color.
245+
if mpl.colors.to_rgba(self.figure.patch.get_facecolor())[3] == 0:
246+
renderer._set_subpixel_antialiased_text_allowed(False)
239247
self.figure.draw(renderer)
248+
renderer._set_subpixel_antialiased_text_allowed(True)
249+
250+
def draw(self):
251+
self._draw_without_supercall()
240252
super().draw()
241253

242254
def buffer_rgba(self): # NOTE: Needed for tests.
@@ -348,11 +360,9 @@ def _print_ps_impl(self, is_eps, path_or_stream, *,
348360
print_eps = partialmethod(_print_ps_impl, True)
349361

350362
def _get_fresh_straight_rgba8888(self):
351-
renderer = self.get_renderer()
352-
renderer.clear()
353-
with _LOCK:
354-
self.figure.draw(renderer)
355-
return _mplcairo.cairo_to_straight_rgba8888(renderer._get_buffer())
363+
self._draw_without_supercall()
364+
return _mplcairo.cairo_to_straight_rgba8888(
365+
self.get_renderer()._get_buffer())
356366

357367
def print_rgba(self, path_or_stream, *,
358368
dryrun=False, metadata=None, **kwargs):

0 commit comments

Comments
 (0)