Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Replace FT2Image by plain numpy arrays. #30044

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/next_api_changes/deprecations/30044-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
``FT2Image``
~~~~~~~~~~~~
... is deprecated. Use 2D uint8 ndarrays instead. In particular:

- The ``FT2Image`` constructor took ``width, height`` as separate parameters
but the ndarray constructor takes ``(height, width)`` as single tuple
parameter.
- `.FT2Font.draw_glyph_to_bitmap` now (also) takes 2D uint8 arrays as input.
- ``FT2Image.draw_rect_filled`` should be replaced by directly setting pixel
values to black.
- The ``image`` attribute of the object returned by ``MathTextParser("agg").parse``
is now a 2D uint8 array.
14 changes: 9 additions & 5 deletions lib/matplotlib/_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import enum
import functools
import logging
import math
import os
import re
import types
Expand All @@ -19,6 +20,7 @@
from typing import NamedTuple

import numpy as np
from numpy.typing import NDArray
from pyparsing import (
Empty, Forward, Literal, Group, NotAny, OneOrMore, Optional,
ParseBaseException, ParseException, ParseExpression, ParseFatalException,
Expand All @@ -30,7 +32,7 @@
from ._mathtext_data import (
latex_to_bakoma, stix_glyph_fixes, stix_virtual_fonts, tex2uni)
from .font_manager import FontProperties, findfont, get_font
from .ft2font import FT2Font, FT2Image, Kerning, LoadFlags
from .ft2font import FT2Font, Kerning, LoadFlags


if T.TYPE_CHECKING:
Expand Down Expand Up @@ -99,15 +101,15 @@ class RasterParse(NamedTuple):
The offsets are always zero.
width, height, depth : float
The global metrics.
image : FT2Image
image : 2D array of uint8
A raster image.
"""
ox: float
oy: float
width: float
height: float
depth: float
image: FT2Image
image: NDArray[np.uint8]

RasterParse.__module__ = "matplotlib.mathtext"

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

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


Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/ft2font.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class FT2Font(Buffer):
def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
def clear(self) -> None: ...
def draw_glyph_to_bitmap(
self, image: FT2Image, x: int, y: int, glyph: Glyph, antialiased: bool = ...
self, image: NDArray[np.uint8], x: int, y: int, glyph: Glyph, antialiased: bool = ...
) -> None: ...
def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ...
def get_bitmap_offset(self) -> tuple[int, int]: ...
Expand Down
5 changes: 3 additions & 2 deletions lib/matplotlib/tests/test_ft2font.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def test_ft2image_draw_rect_filled():
width = 23
height = 42
for x0, y0, x1, y1 in itertools.product([1, 100], [2, 200], [4, 400], [8, 800]):
im = ft2font.FT2Image(width, height)
with pytest.warns(mpl.MatplotlibDeprecationWarning):
im = ft2font.FT2Image(width, height)
im.draw_rect_filled(x0, y0, x1, y1)
a = np.asarray(im)
assert a.dtype == np.uint8
Expand Down Expand Up @@ -823,7 +824,7 @@ def test_ft2font_drawing():
np.testing.assert_array_equal(image, expected)
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
glyph = font.load_char(ord('M'))
image = ft2font.FT2Image(expected.shape[1], expected.shape[0])
image = np.zeros(expected.shape, np.uint8)
font.draw_glyph_to_bitmap(image, -1, 1, glyph, antialiased=False)
np.testing.assert_array_equal(image, expected)

Expand Down
59 changes: 17 additions & 42 deletions src/ft2font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,51 +63,23 @@ void throw_ft_error(std::string message, FT_Error error) {
throw std::runtime_error(os.str());
}

FT2Image::FT2Image() : m_buffer(nullptr), m_width(0), m_height(0)
{
}

FT2Image::FT2Image(unsigned long width, unsigned long height)
: m_buffer(nullptr), m_width(0), m_height(0)
: m_buffer((unsigned char *)calloc(width * height, 1)), m_width(width), m_height(height)
{
resize(width, height);
}

FT2Image::~FT2Image()
{
delete[] m_buffer;
free(m_buffer);
}

void FT2Image::resize(long width, long height)
void draw_bitmap(
py::array_t<uint8_t, py::array::c_style> im, FT_Bitmap *bitmap, FT_Int x, FT_Int y)
{
if (width <= 0) {
width = 1;
}
if (height <= 0) {
height = 1;
}
size_t numBytes = width * height;

if ((unsigned long)width != m_width || (unsigned long)height != m_height) {
if (numBytes > m_width * m_height) {
delete[] m_buffer;
m_buffer = nullptr;
m_buffer = new unsigned char[numBytes];
}
auto buf = im.mutable_data(0);

m_width = (unsigned long)width;
m_height = (unsigned long)height;
}

if (numBytes && m_buffer) {
memset(m_buffer, 0, numBytes);
}
}

void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y)
{
FT_Int image_width = (FT_Int)m_width;
FT_Int image_height = (FT_Int)m_height;
FT_Int image_width = (FT_Int)im.shape(1);
FT_Int image_height = (FT_Int)im.shape(0);
FT_Int char_width = bitmap->width;
FT_Int char_height = bitmap->rows;

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

if (bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) {
for (FT_Int i = y1; i < y2; ++i) {
unsigned char *dst = m_buffer + (i * image_width + x1);
unsigned char *dst = buf + (i * image_width + x1);
unsigned char *src = bitmap->buffer + (((i - y_offset) * bitmap->pitch) + x_start);
for (FT_Int j = x1; j < x2; ++j, ++dst, ++src)
*dst |= *src;
}
} else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) {
for (FT_Int i = y1; i < y2; ++i) {
unsigned char *dst = m_buffer + (i * image_width + x1);
unsigned char *dst = buf + (i * image_width + x1);
unsigned char *src = bitmap->buffer + ((i - y_offset) * bitmap->pitch);
for (FT_Int j = x1; j < x2; ++j, ++dst) {
int x = (j - x1 + x_start);
Expand Down Expand Up @@ -259,7 +231,7 @@ FT2Font::FT2Font(FT_Open_Args &open_args,
long hinting_factor_,
std::vector<FT2Font *> &fallback_list,
FT2Font::WarnFunc warn, bool warn_if_used)
: ft_glyph_warn(warn), warn_if_used(warn_if_used), image(), face(nullptr),
: ft_glyph_warn(warn), warn_if_used(warn_if_used), image({1, 1}), face(nullptr),
hinting_factor(hinting_factor_),
// set default kerning factor to 0, i.e., no kerning manipulation
kerning_factor(0)
Expand Down Expand Up @@ -676,7 +648,8 @@ void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
long width = (bbox.xMax - bbox.xMin) / 64 + 2;
long height = (bbox.yMax - bbox.yMin) / 64 + 2;

image.resize(width, height);
image = py::array_t<uint8_t>{{height, width}};
std::memset(image.mutable_data(0), 0, image.nbytes());

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

image.draw_bitmap(&bitmap->bitmap, x, y);
draw_bitmap(image, &bitmap->bitmap, x, y);
}
}

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

FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd];

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

void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer,
Expand Down
13 changes: 9 additions & 4 deletions src/ft2font.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ extern "C" {
#include FT_TRUETYPE_TABLES_H
}

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;

/*
By definition, FT_FIXED as 2 16bit values stored in a single long.
*/
Expand All @@ -32,7 +36,6 @@ extern "C" {
class FT2Image
{
public:
FT2Image();
FT2Image(unsigned long width, unsigned long height);
virtual ~FT2Image();

Expand Down Expand Up @@ -101,7 +104,9 @@ class FT2Font
void get_bitmap_offset(long *x, long *y);
long get_descent();
void draw_glyphs_to_bitmap(bool antialiased);
void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased);
void draw_glyph_to_bitmap(
py::array_t<uint8_t, py::array::c_style> im,
int x, int y, size_t glyphInd, bool antialiased);
void get_glyph_name(unsigned int glyph_number, std::string &buffer, bool fallback);
long get_name_index(char *name);
FT_UInt get_char_index(FT_ULong charcode, bool fallback);
Expand All @@ -113,7 +118,7 @@ class FT2Font
return face;
}

FT2Image &get_image()
py::array_t<uint8_t, py::array::c_style> &get_image()
{
return image;
}
Expand Down Expand Up @@ -141,7 +146,7 @@ class FT2Font
private:
WarnFunc ft_glyph_warn;
bool warn_if_used;
FT2Image image;
py::array_t<uint8_t, py::array::c_style> image;
FT_Face face;
FT_Vector pen; /* untransformed origin */
std::vector<FT_Glyph> glyphs;
Expand Down
46 changes: 27 additions & 19 deletions src/ft2font_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""(

Parameters
----------
image : FT2Image
image : 2d array of uint8
The image buffer on which to draw the glyph.
x, y : int
The pixel location at which to draw the glyph.
Expand All @@ -983,14 +983,16 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""(
)""";

static void
PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, FT2Image &image,
PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image,
double_or_<int> vxd, double_or_<int> vyd,
PyGlyph *glyph, bool antialiased = true)
{
auto xd = _double_to_<int>("x", vxd);
auto yd = _double_to_<int>("y", vyd);

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

const char *PyFT2Font_get_glyph_name__doc__ = R"""(
Expand Down Expand Up @@ -1440,12 +1442,7 @@ const char *PyFT2Font_get_image__doc__ = R"""(
static py::array
PyFT2Font_get_image(PyFT2Font *self)
{
FT2Image &im = self->x->get_image();
py::ssize_t dims[] = {
static_cast<py::ssize_t>(im.get_height()),
static_cast<py::ssize_t>(im.get_width())
};
return py::array_t<unsigned char>(dims, im.get_buffer());
return self->x->get_image();
}

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

py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
PyFT2Font__doc__)
auto cls = py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
PyFT2Font__doc__)
.def(py::init(&PyFT2Font_init),
"filename"_a, "hinting_factor"_a=8, py::kw_only(),
"_fallback_list"_a=py::none(), "_kerning_factor"_a=0,
Expand Down Expand Up @@ -1639,10 +1640,20 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
.def("get_descent", &PyFT2Font_get_descent, PyFT2Font_get_descent__doc__)
.def("draw_glyphs_to_bitmap", &PyFT2Font_draw_glyphs_to_bitmap,
py::kw_only(), "antialiased"_a=true,
PyFT2Font_draw_glyphs_to_bitmap__doc__)
.def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap,
"image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true,
PyFT2Font_draw_glyph_to_bitmap__doc__)
PyFT2Font_draw_glyphs_to_bitmap__doc__);
// The generated docstring uses an unqualified "Buffer" as type hint,
// which causes an error in sphinx. This is fixed as of pybind11
// master (since #5566) which now uses "collections.abc.Buffer";
// restore the signature once that version is released.
{
py::options options{};
options.disable_function_signatures();
cls
.def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap,
"image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true,
PyFT2Font_draw_glyph_to_bitmap__doc__);
}
cls
.def("get_glyph_name", &PyFT2Font_get_glyph_name, "index"_a,
PyFT2Font_get_glyph_name__doc__)
.def("get_charmap", &PyFT2Font_get_charmap, PyFT2Font_get_charmap__doc__)
Expand Down Expand Up @@ -1760,10 +1771,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
"The original filename for this object.")

.def_buffer([](PyFT2Font &self) -> py::buffer_info {
FT2Image &im = self.x->get_image();
std::vector<py::size_t> shape { im.get_height(), im.get_width() };
std::vector<py::size_t> strides { im.get_width(), 1 };
return py::buffer_info(im.get_buffer(), shape, strides);
return self.x->get_image().request();
});

m.attr("__freetype_version__") = version_string;
Expand Down
Loading