From 5ce4ff2fbf3b2bfcc9ae12e07b23b9d2fbf48cbb Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 5 Sep 2020 13:18:55 +0200 Subject: [PATCH] Deprecate MathTextParser("bitmap") and associated APIs. `MathTextParser("bitmap")` is rather low-level APIs to convert text strings to images. Instead, one can use a much more generic method, namely directly drawing to a new Figure() and saving with `bbox_inches="tight"`. Alternatively, if one really wants a single function call, there's still `mathtext.math_to_image`. Also fix the wx example to use light text colors when so directed by the system theme. --- .../deprecations/18591-AL.rst | 24 ++++++++ .../mathtext_asarray.py | 61 ++++++++++++------- .../user_interfaces/mathtext_wx_sgskip.py | 29 +++++---- lib/matplotlib/contour.py | 6 +- lib/matplotlib/mathtext.py | 11 ++++ lib/matplotlib/sphinxext/mathmpl.py | 21 +++---- lib/matplotlib/tests/test_mathtext.py | 9 +-- 7 files changed, 107 insertions(+), 54 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/18591-AL.rst diff --git a/doc/api/next_api_changes/deprecations/18591-AL.rst b/doc/api/next_api_changes/deprecations/18591-AL.rst new file mode 100644 index 000000000000..67398d5196fc --- /dev/null +++ b/doc/api/next_api_changes/deprecations/18591-AL.rst @@ -0,0 +1,24 @@ +``MathTextParser("bitmap")`` is deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The associated APIs ``MathtextBackendBitmap``, ``MathTextParser.to_mask``, +``MathTextParser.to_rgba``, ``MathTextParser.to_png``, and +``MathTextParser.get_depth`` are likewise deprecated. + +To convert a text string to an image, either directly draw the text to an +empty `.Figure` and save the figure using a tight bbox, as demonstrated in +:doc:`/gallery/text_labels_and_annotations/mathtext_asarray`, or use +`.mathtext.math_to_image`. + +When using `.math_to_image`, text color can be set with e.g.:: + + with plt.rc_context({"text.color": "tab:blue"}): + mathtext.math_to_image(text, filename) + +and an RGBA array can be obtained with e.g.:: + + from io import BytesIO + buf = BytesIO() + mathtext.math_to_image(text, buf, format="png") + buf.seek(0) + rgba = plt.imread(buf) diff --git a/examples/text_labels_and_annotations/mathtext_asarray.py b/examples/text_labels_and_annotations/mathtext_asarray.py index 05ff43870828..b40ba3ce18e4 100644 --- a/examples/text_labels_and_annotations/mathtext_asarray.py +++ b/examples/text_labels_and_annotations/mathtext_asarray.py @@ -1,27 +1,47 @@ """ -=============================== -A mathtext image as numpy array -=============================== - -Make images from LaTeX strings. +======================= +Convert texts to images +======================= """ -import matplotlib.mathtext as mathtext -import matplotlib.pyplot as plt +from io import BytesIO -parser = mathtext.MathTextParser("Bitmap") -parser.to_png('test2.png', - r'$\left[\left\lfloor\frac{5}{\frac{\left(3\right)}{4}} ' - r'y\right)\right]$', color='green', fontsize=14, dpi=100) +from matplotlib.figure import Figure +import matplotlib.pyplot as plt +from matplotlib.transforms import IdentityTransform + + +def text_to_rgba(s, *, dpi, **kwargs): + # To convert a text string to an image, we can: + # - draw it on an empty and transparent figure; + # - save the figure to a temporary buffer using ``bbox_inches="tight", + # pad_inches=0`` which will pick the correct area to save; + # - load the buffer using ``plt.imread``. + # + # (If desired, one can also directly save the image to the filesystem.) + fig = Figure(facecolor="none") + fig.text(0, 0, s, **kwargs) + buf = BytesIO() + fig.savefig(buf, dpi=dpi, format="png", bbox_inches="tight", pad_inches=0) + buf.seek(0) + rgba = plt.imread(buf) + return rgba -rgba1, depth1 = parser.to_rgba( - r'IQ: $\sigma_i=15$', color='blue', fontsize=20, dpi=200) -rgba2, depth2 = parser.to_rgba( - r'some other string', color='red', fontsize=20, dpi=200) fig = plt.figure() -fig.figimage(rgba1, 100, 100) -fig.figimage(rgba2, 100, 300) +rgba1 = text_to_rgba(r"IQ: $\sigma_i=15$", color="blue", fontsize=20, dpi=200) +rgba2 = text_to_rgba(r"some other string", color="red", fontsize=20, dpi=200) +# One can then draw such text images to a Figure using `.Figure.figimage`. +fig.figimage(rgba1, 100, 50) +fig.figimage(rgba2, 100, 150) + +# One can also directly draw texts to a figure with positioning +# in pixel coordinates by using `.Figure.text` together with +# `.transforms.IdentityTransform`. +fig.text(100, 250, r"IQ: $\sigma_i=15$", color="blue", fontsize=20, + transform=IdentityTransform()) +fig.text(100, 350, r"some other string", color="red", fontsize=20, + transform=IdentityTransform()) plt.show() @@ -36,8 +56,7 @@ # in this example: import matplotlib -matplotlib.mathtext -matplotlib.mathtext.MathTextParser -matplotlib.mathtext.MathTextParser.to_png -matplotlib.mathtext.MathTextParser.to_rgba matplotlib.figure.Figure.figimage +matplotlib.figure.Figure.text +matplotlib.transforms.IdentityTransform +matplotlib.image.imread diff --git a/examples/user_interfaces/mathtext_wx_sgskip.py b/examples/user_interfaces/mathtext_wx_sgskip.py index ce305557b6fb..ca5a869bef5b 100644 --- a/examples/user_interfaces/mathtext_wx_sgskip.py +++ b/examples/user_interfaces/mathtext_wx_sgskip.py @@ -3,36 +3,39 @@ MathText WX =========== -Demonstrates how to convert mathtext to a wx.Bitmap for display in various +Demonstrates how to convert (math)text to a wx.Bitmap for display in various controls on wxPython. """ -import matplotlib -matplotlib.use("WxAgg") +from io import BytesIO + from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.backends.backend_wx import NavigationToolbar2Wx from matplotlib.figure import Figure import numpy as np - import wx IS_GTK = 'wxGTK' in wx.PlatformInfo IS_WIN = 'wxMSW' in wx.PlatformInfo -############################################################ -# This is where the "magic" happens. -from matplotlib.mathtext import MathTextParser -mathtext_parser = MathTextParser("Bitmap") - def mathtext_to_wxbitmap(s): - rgba, depth = mathtext_parser.to_rgba(s, dpi=150, fontsize=10) - return wx.Bitmap.FromBufferRGBA(rgba.shape[1], rgba.shape[0], rgba) -############################################################ + # We draw the text at position (0, 0) but then rely on + # ``facecolor="none"`` and ``bbox_inches="tight", pad_inches=0`` to get an + # transparent mask that is then loaded into a wx.Bitmap. + fig = Figure(facecolor="none") + text_color = ( + np.array(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) / 255) + fig.text(0, 0, s, fontsize=10, color=text_color) + buf = BytesIO() + fig.savefig(buf, format="png", dpi=150, bbox_inches="tight", pad_inches=0) + s = buf.getvalue() + return wx.Bitmap.NewFromPNGData(s, len(s)) + functions = [ (r'$\sin(2 \pi x)$', lambda x: np.sin(2*np.pi*x)), - (r'$\frac{4}{3}\pi x^3$', lambda x: (4.0/3.0)*np.pi*x**3), + (r'$\frac{4}{3}\pi x^3$', lambda x: (4/3)*np.pi*x**3), (r'$\cos(2 \pi x)$', lambda x: np.cos(2*np.pi*x)), (r'$\log(x)$', lambda x: np.log(x)) ] diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 7b6713b8e37d..c06621c272c3 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -249,9 +249,9 @@ def get_label_width(self, lev, fmt, fsize): .get_text_width_height_descent(lev, fsize)) elif ismath: if not hasattr(self, '_mathtext_parser'): - self._mathtext_parser = mathtext.MathTextParser('bitmap') - img, _ = self._mathtext_parser.parse(lev, dpi=72, - prop=self.labelFontProps) + self._mathtext_parser = mathtext.MathTextParser('agg') + _, _, _, _, _, img, _ = self._mathtext_parser.parse( + lev, dpi=72, prop=self.labelFontProps) _, lw = np.shape(img) # at dpi=72, the units are PostScript points else: # width is much less than "font size" diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py index bc77d76e4669..1e7c0fa94603 100644 --- a/lib/matplotlib/mathtext.py +++ b/lib/matplotlib/mathtext.py @@ -171,6 +171,7 @@ def get_hinting_type(self): return backend_agg.get_hinting_flag() +@cbook.deprecated("3.4", alternative="mathtext.math_to_image") class MathtextBackendBitmap(MathtextBackendAgg): def get_results(self, box, used_characters): ox, oy, width, height, depth, image, characters = \ @@ -460,8 +461,11 @@ def _parse_cached(self, s, dpi, prop, force_standard_ps_fonts): font_output.set_canvas_size(box.width, box.height, box.depth) return font_output.get_results(box) + @cbook.deprecated("3.4", alternative="mathtext.math_to_image") def to_mask(self, texstr, dpi=120, fontsize=14): r""" + Convert a mathtext string to a grayscale array and depth. + Parameters ---------- texstr : str @@ -483,8 +487,11 @@ def to_mask(self, texstr, dpi=120, fontsize=14): ftimage, depth = self.parse(texstr, dpi=dpi, prop=prop) return np.asarray(ftimage), depth + @cbook.deprecated("3.4", alternative="mathtext.math_to_image") def to_rgba(self, texstr, color='black', dpi=120, fontsize=14): r""" + Convert a mathtext string to an RGBA array and depth. + Parameters ---------- texstr : str @@ -513,6 +520,7 @@ def to_rgba(self, texstr, color='black', dpi=120, fontsize=14): RGBA[:, :, 3] = x return RGBA, depth + @cbook.deprecated("3.4", alternative="mathtext.math_to_image") def to_png(self, filename, texstr, color='black', dpi=120, fontsize=14): r""" Render a tex expression to a PNG file. @@ -540,8 +548,11 @@ def to_png(self, filename, texstr, color='black', dpi=120, fontsize=14): Image.fromarray(rgba).save(filename, format="png") return depth + @cbook.deprecated("3.4", alternative="mathtext.math_to_image") def get_depth(self, texstr, dpi=120, fontsize=14): r""" + Get the depth of a mathtext string. + Parameters ---------- texstr : str diff --git a/lib/matplotlib/sphinxext/mathmpl.py b/lib/matplotlib/sphinxext/mathmpl.py index 6c3b1c1087ab..1d7a93ca15b4 100644 --- a/lib/matplotlib/sphinxext/mathmpl.py +++ b/lib/matplotlib/sphinxext/mathmpl.py @@ -6,9 +6,7 @@ import sphinx import matplotlib as mpl -from matplotlib import cbook -from matplotlib.mathtext import MathTextParser -mathtext_parser = MathTextParser("Bitmap") +from matplotlib import cbook, mathtext # Define LaTeX math node: @@ -17,7 +15,7 @@ class latex_math(nodes.General, nodes.Element): def fontset_choice(arg): - return directives.choice(arg, MathTextParser._font_type_mapping) + return directives.choice(arg, mathtext.MathTextParser._font_type_mapping) def math_role(role, rawtext, text, lineno, inliner, @@ -50,15 +48,12 @@ def run(self): def latex2png(latex, filename, fontset='cm'): latex = "$%s$" % latex with mpl.rc_context({'mathtext.fontset': fontset}): - if Path(filename).exists(): - depth = mathtext_parser.get_depth(latex, dpi=100) - else: - try: - depth = mathtext_parser.to_png(filename, latex, dpi=100) - except Exception: - cbook._warn_external( - f"Could not render math expression {latex}") - depth = 0 + try: + depth = mathtext.math_to_image( + latex, filename, dpi=100, format="png") + except Exception: + cbook._warn_external(f"Could not render math expression {latex}") + depth = 0 return depth diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 6f4607acf47d..faea69dc6c4a 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -8,7 +8,7 @@ import matplotlib as mpl from matplotlib.testing.decorators import check_figures_equal, image_comparison import matplotlib.pyplot as plt -from matplotlib import mathtext +from matplotlib import cbook, mathtext # If test is removed, use None as placeholder @@ -357,9 +357,10 @@ def test_math_to_image(tmpdir): def test_mathtext_to_png(tmpdir): - mt = mathtext.MathTextParser('bitmap') - mt.to_png(str(tmpdir.join('example.png')), '$x^2$') - mt.to_png(io.BytesIO(), '$x^2$') + with cbook._suppress_matplotlib_deprecation_warning(): + mt = mathtext.MathTextParser('bitmap') + mt.to_png(str(tmpdir.join('example.png')), '$x^2$') + mt.to_png(io.BytesIO(), '$x^2$') @image_comparison(baseline_images=['math_fontfamily_image.png'],