8000 Replace FT2Image by plain numpy arrays. · matplotlib/matplotlib@7e9782a · GitHub
[go: up one dir, main page]

Skip to content
Sign in

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7e9782a

Browse files
committed
Replace FT2Image by plain numpy arrays.
1 parent de00c49 commit 7e9782a

File tree

7 files changed

+75
-73
lines changed

7 files changed

+75
-73
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
``FT2Image``
2+
~~~~~~~~~~~~
3+
... is deprecated. Use 2D uint8 arrays instead. In particular:
4+
5+
- `.FT2Font.draw_glyph_to_bitmap` now (also) takes 2D uint8 arrays as input.
6+
- ``FT2Image.draw_rect_filled`` should be replaced by directly setting pixel
7+
values to black.
8+
- The ``image`` attribute of the object returned by ``MathTextParser("agg").parse``
9+
is now a 2D uint8 array.

lib/matplotlib/_mathtext.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import enum
1010
import functools
1111
import logging
12+
import math
1213
import os
1314
import re
1415
import types
@@ -19,6 +20,7 @@
1920
from typing import NamedTuple
2021

2122
import numpy as np
23+
from numpy.typing import NDArray
2224
from pyparsing import (
2325
Empty, Forward, Literal, Group, NotAny, OneOrMore, Optional,
2426
ParseBaseException, ParseException, ParseExpression, ParseFatalException,
@@ -30,7 +32,7 @@
3032
from ._mathtext_data import (
3133
latex_to_bakoma, stix_glyph_fixes, stix_virtual_fonts, tex2uni)
3234
from .font_manager import FontProperties, findfont, get_font
33-
from .ft2font import FT2Font, FT2Image, Kerning, LoadFlags
35+
from .ft2font import FT2Font, Kerning, LoadFlags
3436

3537

3638
if T.TYPE_CHECKING:
@@ -99,15 +101,15 @@ class RasterParse(NamedTuple):
99101
The offsets are always zero.
100102
width, height, depth : float
101103
The global metrics.
102-
image : FT2Image
104+
image : 2D array of uint8
103105
A raster image.
104106
"""
105107
ox: float
106108
oy: float
107109
width: float
108110
height: float
109111
depth: float
110-
image: FT2Image
112+
image: NDArray[np.uint8]
111113

112114
RasterParse.__module__ = "matplotlib.mathtext"
113115

@@ -148,7 +150,7 @@ def to_raster(self, *, antialiased: bool) -> RasterParse:
148150
w = xmax - xmin
149151
h = ymax - ymin - self.box.depth
150152
d = ymax - ymin - self.box.height
151-
image = FT2Image(int(np.ceil(w)), int(np.ceil(h + max(d, 0))))
153+
image = np.zeros((math.ceil(h + max(d, 0)), math.ceil(w)), np.uint8)
152154

153155
# Ideally, we could just use self.glyphs and self.rects here, shifting
154156
# their coordinates by (-xmin, -ymin), but this yields slightly
@@ -167,7 +169,9 @@ def to_raster(self, *, antialiased: bool) -> RasterParse:
167169
y = int(center - (height + 1) / 2)
168170
else:
169171
y = int(y1)
170-
image.draw_rect_filled(int(x1), y, int(np.ceil(x2)), y + height)
172+
x1 = math.floor(x1)
173+
x2 = math.ceil(x2)
174+
image[y:y+height+1, x1:x2+1] = 0xff
171175
return RasterParse(0, 0, w, h + d, d, image)
172176

173177

lib/matplotlib/ft2font.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class FT2Font(Buffer):
198198
def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
199199
def clear(self) -> None: ...
200200
def draw_glyph_to_bitmap(
201-
self, image: FT2Image, x: int, y: int, glyph: Glyph, antialiased: bool = ...
201+
self, image: NDArray[np.uint8], x: int, y: int, glyph: Glyph, antialiased: bool = ...
202202
) -> None: ...
203203
def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ...
204204
def get_bitmap_offset(self) -> tuple[int, int]: ...

lib/matplotlib/tests/test_ft2font.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ def test_ft2image_draw_rect_filled():
1818
width = 23
1919
height = 42
2020
for x0, y0, x1, y1 in itertools.product([1, 100], [2, 200], [4, 400], [8, 800]):
21-
im = ft2font.FT2Image(width, height)
21+
with pytest.warns(mpl.MatplotlibDeprecationWarning):
22+
im = ft2font.FT2Image(width, height)
2223
im.draw_rect_filled(x0, y0, x1, y1)
2324
10000 a = np.asarray(im)
2425
assert a.dtype == np.uint8
@@ -823,7 +824,7 @@ def test_ft2font_drawing():
823824
np.testing.assert_array_equal(image, expected)
824825
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
825826
glyph = font.load_char(ord('M'))
826-
image = ft2font.FT2Image(expected.shape[1], expected.shape[0])
827+
image = np.zeros(expected.shape, np.uint8)
827828
font.draw_glyph_to_bitmap(image, -1, 1, glyph, antialiased=False)
828829
np.testing.assert_array_equal(image, expected)
829830

src/ft2font.cpp

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -63,51 +63,23 @@ void throw_ft_error(std::string message, FT_Error error) {
6363
throw std::runtime_error(os.str());
6464
}
6565

66-
FT2Image::FT2Image() : m_buffer(nullptr), m_width(0), m_height(0)
67-
{
68-
}
69-
7066
FT2Image::FT2Image(unsigned long width, unsigned long height)
71-
: m_buffer(nullptr), m_width(0), m_height(0)
67+
: m_buffer((unsigned char *)calloc(width * height, 1)), m_width(width), m_height(height)
7268
{
73-
resize(width, height);
7469
}
7570

7671
FT2Image::~FT2Image()
7772
{
78-
delete[] m_buffer;
73+
free(m_buffer);
7974
}
8075

81-
void FT2Image::resize(long width, long height)
76+
void draw_bitmap(
77+
py::array_t<uint8_t, py::array::c_style> im, FT_Bitmap *bitmap, FT_Int x, FT_Int y)
8278
{
83-
if (width <= 0) {
84-
width = 1;
85-
}
86-
if (height <= 0) {
87-
height = 1;
88-
}
89-
size_t numBytes = width * height;
90-
91-
if ((unsigned long)width != m_width || (unsigned long)height != m_height) {
92-
if (numBytes > m_width * m_height) {
93-
delete[] m_buffer;
94-
m_buffer = nullptr;
95-
m_buffer = new unsigned char[numBytes];
96-
}
79+
auto buf = im.mutable_data(0);
9780

98-
m_width = (unsigned long)width;
99-
m_height = (unsigned long)height;
100-
}
101-
102-
if (numBytes && m_buffer) {
103-
memset(m_buffer, 0, numBytes);
104-
}
105-
}
106-
107-
void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y)
108-
{
109-
FT_Int image_width = (FT_Int)m_width;
110-
FT_Int image_height = (FT_Int)m_height;
81+
FT_Int image_width = (FT_Int)im.shape(1);
82+
FT_Int image_height = (FT_Int)im.shape(0);
11183
FT_Int char_width = bitmap->width;
11284
FT_Int char_height = bitmap->rows;
11385

@@ -121,14 +93,14 @@ void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y)
12193

12294
if (bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) {
12395
for (FT_Int i = y1; i < y2; ++i) {
124-
unsigned char *dst = m_buffer + (i * image_width + x1);
96+
unsigned char *dst = buf + (i * image_width + x1);
12597
unsigned char *src = bitmap->buffer + (((i - y_offset) * bitmap->pitch) + x_start);
12698
for (FT_Int j = x1; j < x2; ++j, ++dst, ++src)
12799
*dst |= *src;
128100
}
129101
} else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) {
130102
for (FT_Int i = y1; i < y2; ++i) {
131-
unsigned char *dst = m_buffer + (i * image_width + x1);
103+
unsigned char *dst = buf + (i * image_width + x1);
132104
unsigned char *src = bitmap->buffer + ((i - y_offset) * bitmap->pitch);
133105
for (FT_Int j = x1; j < x2; ++j, ++dst) {
134106
int x = (j - x1 + x_start);
@@ -259,7 +231,7 @@ FT2Font::FT2Font(FT_Open_Args &open_args,
259231
long hinting_factor_,
260232
std::vector<FT2Font *> &fallback_list,
261233
FT2Font::WarnFunc warn, bool warn_if_used)
262-
: ft_glyph_warn(warn), warn_if_used(warn_if_used), image(), face(nullptr),
234+
: ft_glyph_warn(warn), warn_if_used(warn_if_used), image({1, 1}), face(nullptr),
263235
hinting_factor(hinting_factor_),
264236
// set default kerning factor to 0, i.e., no kerning manipulation
265237
kerning_factor(0)
@@ -676,7 +648,8 @@ void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
676648
long width = (bbox.xMax - bbox.xMin) / 64 + 2;
677649
long height = (bbox.yMax - bbox.yMin) / 64 + 2;
678650

679-
image.resize(width, height);
651+
image = py::array_t<uint8_t>{{height, width}};
652+
std::memset(image.mutable_data(0), 0, image.nbytes());
680653

681654
for (auto & glyph : glyphs) {
682655
FT_Error error = FT_Glyph_To_Bitmap(
@@ -692,11 +665,13 @@ void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
692665
FT_Int x = (FT_Int)(bitmap->left - (bbox.xMin * (1. / 64.)));
693666
FT_Int y = (FT_Int)((bbox.yMax * (1. / 64.)) - bitmap->top + 1);
694667

695-
image.draw_bitmap(&bitmap->bitmap, x, y);
668+
draw_bitmap(image, &bitmap->bitmap, x, y);
696669
}
697670
}
698671

699-
void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased)
672+
void FT2Font::draw_glyph_to_bitmap(
673+
py::array_t<uint8_t, py::array::c_style> im,
674+
int x, int y, size_t glyphInd, bool antialiased)
700675
{
701676
FT_Vector sub_offset;
702677
sub_offset.x = 0; // int((xd - (double)x) * 64.0);
@@ -718,7 +693,7 @@ void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd,
718693

719694
FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd];
720695

721-
im.draw_bitmap(&bitmap->bitmap, x + bitmap->left, y);
696+
draw_bitmap(im, &bitmap->bitmap, x + bitmap->left, y);
722697
}
723698

724699
void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer,

src/ft2font.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ extern "C" {
2222
#include FT_TRUETYPE_TABLES_H
2323
}
2424

25+
#include <pybind11/pybind11.h>
26+
#include <pybind11/numpy.h>
27+
namespace py = pybind11;
28+
2529
/*
2630
By definition, FT_FIXED as 2 16bit values stored in a single long.
2731
*/
@@ -32,7 +36,6 @@ extern "C" {
3236
class FT2Image
3337
{
3438
public:
35-
FT2Image();
3639
FT2Image(unsigned long width, unsigned long height);
3740
virtual ~FT2Image();
3841

@@ -101,7 +104,9 @@ class FT2Font
101104
void get_bitmap_offset(long *x, long *y);
102105
long get_descent();
103106
void draw_glyphs_to_bitmap(bool antialiased);
104-
void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased);
107+
void draw_glyph_to_bitmap(
108+
py::array_t<uint8_t, py::array::c_style> im,
109+
int x, int y, size_t glyphInd, bool antialiased);
105110
void get_glyph_name(unsigned int glyph_number, std::string &buffer, bool fallback);
106111
long get_name_index(char *name);
107112
FT_UInt get_char_index(FT_ULong charcode, bool fallback);
@@ -113,7 +118,7 @@ class FT2Font
113118
return face;
114119
}
115120

116-
FT2Image &get_image()
121+
py::array_t<uint8_t, py::array::c_style> &get_image()
117122
{
118123
return image;
119124
}
@@ -141,7 +146,7 @@ class FT2Font
141146
private:
142147
WarnFunc ft_glyph_warn;
143148
bool warn_if_used;
144-
FT2Image image;
149+
py::array_t<uint8_t, py::array::c_style> image;
145150
FT_Face face;
146151
FT_Vector pen; /* untransformed origin */
147152
std::vector<FT_Glyph> glyphs;

src/ft2font_wrapper.cpp

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,7 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""(
968968
969969
Parameters
970970
----------
971-
image : FT2Image
971+
image : 2d array of uint8
972972
The image buffer on which to draw the glyph.
973973
x, y : int
974974
The pixel location at which to draw the glyph.
@@ -983,14 +983,16 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""(
983983
)""";
984984

985985
static void
986-
PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, FT2Image &image,
986+
PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image,
987987
double_or_<int> vxd, double_or_<int> vyd,
988988
PyGlyph *glyph, bool antialiased = true)
989989
{
990990
auto xd = _double_to_<int>("x", vxd);
991991
auto yd = _double_to_<int>("y", vyd);
992992

993-
self->x->draw_glyph_to_bitmap(image, xd, yd, glyph->glyphInd, antialiased);
993+
self->x->draw_glyph_to_bitmap(
994+
py::array_t<uint8_t, py::array::c_style>{image},
995+
xd, yd, glyph->glyphInd, antialiased);
994996
}
995997

996998
const char *PyFT2Font_get_glyph_name__doc__ = R"""(
@@ -1440,12 +1442,7 @@ const char *PyFT2Font_get_image__doc__ = R"""(
14401442
static py::array
14411443
PyFT2Font_get_image(PyFT2Font *self)
14421444
{
1443-
FT2Image &im = self->x->get_image();
1444-
py::ssize_t dims[] = {
1445-
static_cast<py::ssize_t>(im.get_height()),
1446-
static_cast<py::ssize_t>(im.get_width())
1447-
};
1448-
return py::array_t<unsigned char>(dims, im.get_buffer());
1445+
return self->x->get_image();
14491446
}
14501447

14511448
const char *PyFT2Font__get_type1_encoding_vector__doc__ = R"""(
@@ -1565,6 +1562,10 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15651562
PyFT2Image__doc__)
15661563
.def(py::init(
15671564
[](double_or_<long> width, double_or_<long> height) {
1565+
auto warn =
1566+
py::module_::import("matplotlib._api").attr("warn_deprecated");
1567+
warn("since"_a="3.11", "name"_a="FT2Image", "obj_type"_a="class",
1568+
"alternative"_a="a 2D uint8 ndarray");
15681569
return new FT2Image(
15691570
_double_to_<long>("width", width),
15701571
_double_to_<long>("height", height)
@@ -1604,8 +1605,8 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16041605
.def_property_readonly("bbox", &PyGlyph_get_bbox,
16051606
"The control box of the glyph.");
16061607

1607-
py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
1608-
PyFT2Font__doc__)
1608+
auto cls = py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
1609+
PyFT2Font__doc__)
16091610
.def(py::init(&PyFT2Font_init),
16101611
"filename"_a, "hinting_factor"_a=8, py::kw_only(),
16111612
"_fallback_list"_a=py::none(), "_kerning_factor"_a=0,
@@ -1639,10 +1640,20 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16391640
.def("get_descent", &PyFT2Font_get_descent, PyFT2Font_get_descent__doc__)
16401641
.def("draw_glyphs_to_bitmap", &PyFT2Font_draw_glyphs_to_bitmap,
16411642
py::kw_only(), "antialiased"_a=true,
1642-
PyFT2Font_draw_glyphs_to_bitmap__doc__)
1643-
.def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap,
1644-
"image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true,
1645-
PyFT2Font_draw_glyph_to_bitmap__doc__)
1643+
PyFT2Font_draw_glyphs_to_bitmap__doc__);
1644+
// The generated docstring uses an unqualified "Buffer" as type hint,
1645+
// which causes an error in sphinx. This is fixed as of pybind11
1646+
// master (since #5566) which now uses "collections.abc.Buffer";
1647+
// restore the signature once that version is released.
1648+
{
1649+
py::options options{};
1650+
options.disable_function_signatures();
1651+
cls
1652+
.def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap,
1653+
"image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true,
1654+
PyFT2Font_draw_glyph_to_bitmap__doc__);
1655+
}
1656+
cls
16461657
.def("get_glyph_name", &PyFT2Font_get_glyph_name, "index"_a,
16471658
PyFT2Font_get_glyph_name__doc__)
16481659
.def("get_charmap", &PyFT2Font_get_charmap, PyFT2Font_get_charmap__doc__)
@@ -1760,10 +1771,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
17601771
"The original filename for this object.")
17611772

17621773
.def_buffer([](PyFT2Font &self) -> py::buffer_info {
1763-
FT2Image &im = self.x->get_image();
1764-
std::vector<py::size_t> shape { im.get_height(), im.get_width() };
1765-
std::vector<py::size_t> strides { im.get_width(), 1 };
1766-
return py::buffer_info(im.get_buffer(), shape, strides);
1774+
return self.x->get_image().request();
17671775
});
17681776

17691777
m.attr("__freetype_version__") = version_string;

0 commit comments

Comments
 (0)
0