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

Skip to content

Commit fc3e947

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

File tree

7 files changed

+58
-66
lines changed

7 files changed

+58
-66
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
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: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
@@ -1760,10 +1761,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
17601761
"The original filename for this object.")
17611762

17621763
.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);
1764+
return self.x->get_image().request();
17671765
});
17681766

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

0 commit comments

Comments
 (0)
0