FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Support colorization of text via TeX directives by MaddieM4 · Pull Request #31234 · matplotlib/matplotlib · GitHub

Repository navigation

Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .cpp  (1) .h  (1) .py  (2) .pyi  (1) All 4 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
20 changes: 20 additions & 0 deletions lib/matplotlib/backends/backend_agg.py
Show comments Show annotations View file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@
return w, h, d

def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
# Injection point
return self.draw_tex_mm4(gc, x, y, s, prop, angle)

# docstring inherited
# todo, handle props, angle, origins

Expand Down Expand Up @@ -299,6 +302,23 @@
for text in page.text),
((box.x, box.y, box.width, box.height) for box in page.boxes))

def draw_tex_mm4(self, gc, x, y, s, prop, angle, *, mtext=None):
# docstring inherited
# todo, handle props, angle, origins
size = prop.get_size_in_points()

texmanager = self.get_texmanager()

Z = texmanager.get_rgba_mm4(s, size, self.dpi)
Z = np.array(Z * 255.0, np.uint8)

w, h, d = self.get_text_width_height_descent(s, prop, ismath="TeX")
xd = d * sin(radians(angle))

Check warning on line 316 in lib/matplotlib/backends/backend_agg.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `radians` Raw Output: message:"Undefined name `radians`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/backends/backend_agg.py" range:{start:{line:316 column:22} end:{line:316 column:29}}} severity:WARNING source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}

Check warning on line 316 in lib/matplotlib/backends/backend_agg.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `sin` Raw Output: message:"Undefined name `sin`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/backends/backend_agg.py" range:{start:{line:316 column:18} end:{line:316 column:21}}} severity:WARNING source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}
yd = d * cos(radians(angle))

Check warning on line 317 in lib/matplotlib/backends/backend_agg.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `radians` Raw Output: message:"Undefined name `radians`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/backends/backend_agg.py" range:{start:{line:317 column:22} end:{line:317 column:29}}} severity:WARNING source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}

Check warning on line 317 in lib/matplotlib/backends/backend_agg.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `cos` Raw Output: message:"Undefined name `cos`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/backends/backend_agg.py" range:{start:{line:317 column:18} end:{line:317 column:21}}} severity:WARNING source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}
x = round(x + xd)
y = round(y + yd)
self._renderer.draw_text_image_mm4(Z, x, y, angle, gc)

def get_canvas_width_height(self):
# docstring inherited
return self.width, self.height
Expand Down
10 changes: 10 additions & 0 deletions lib/matplotlib/texmanager.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,16 @@ def get_grey(cls, tex, fontsize=None, dpi=None):
cls._grey_arrayd[key] = alpha = rgba[:, :, -1]
return alpha

@classmethod
def get_rgba_mm4(cls, tex, fontsize=None, dpi=None):
"""Return the full PNG image in RGBA format."""
fontsize = mpl._val_or_rc(fontsize, 'font.size')
dpi = mpl._val_or_rc(dpi, 'savefig.dpi')
key = cls._get_tex_source(tex, fontsize), dpi
pngfile = cls.make_png(tex, fontsize, dpi)
rgba = mpl.image.imread(pngfile)
return rgba

@classmethod
def get_rgba(cls, tex, fontsize=None, dpi=None, rgb=(0, 0, 0)):
r"""
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/texmanager.pyi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class TexManager:
cls, tex: str, fontsize: float | None = ..., dpi: float | None = ...
) -> np.ndarray: ...
@classmethod
def get_rgba_mm4(
cls, tex: str, fontsize: float | None = ..., dpi: float | None = ...
) -> np.ndarray: ...
@classmethod
def get_rgba(
cls,
tex: str,
Expand Down
77 changes: 77 additions & 0 deletions src/_backend_agg.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ class RendererAgg
template <class ImageArray>
void draw_text_image(GCAgg &gc, ImageArray &image, int x, int y, double angle);

template <class ImageArray>
void draw_text_image_mm4(GCAgg &gc, ImageArray &image, int x, int y, double angle);

template <class ImageArray>
void draw_image(GCAgg &gc,
double x,
Expand Down Expand Up @@ -784,6 +787,80 @@ inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, in
}
}

template <class ImageArray>
inline void RendererAgg::draw_text_image_mm4(GCAgg &gc, ImageArray &image, int x, int y, double angle)
{
theRasterizer.reset_clipping();
rendererBase.reset_clipping(true);

if (angle != 0.0) {
auto image_height = static_cast<double>(image.shape(0)),
image_width = static_cast<double>(image.shape(1));

agg::rendering_buffer srcbuf(
image.mutable_data(0, 0, 0),
(unsigned)image_width,
(unsigned)image_height,
(unsigned)image_width * sizeof(agg::rgba8));
agg::pixfmt_rgba32 pixf_img(srcbuf);

set_clipbox(gc.cliprect, theRasterizer);

agg::trans_affine mtx;
mtx *= agg::trans_affine_translation(0, -image_height);
mtx *= agg::trans_affine_rotation(-angle * (agg::pi / 180.0));
mtx *= agg::trans_affine_translation(x, y);

agg::path_storage rect;
rect.move_to(0, 0);
rect.line_to(image_width, 0);
rect.line_to(image_width, image_height);
rect.line_to(0, image_height);
rect.line_to(0, 0);
agg::conv_transform<agg::path_storage> rect2(rect, mtx);

agg::trans_affine inv_mtx(mtx);
inv_mtx.invert();

typedef agg::span_interpolator_linear<> interp_t;
interp_t interpolator(inv_mtx);
agg::span_image_filter_rgba_bilinear_clip<agg::pixfmt_rgba32, interp_t> sg(
pixf_img, agg::rgba::no_color(), interpolator
);

theRasterizer.add_path(rect2);
agg::scanline_u8 sl;
agg::span_allocator<agg::rgba8> sa;
agg::render_scanlines_aa(theRasterizer, sl, rendererBase, sa, sg);
} else {
agg::rect_i fig, text;
fig.init(0, 0, width, height);
text.init(x, y - image.shape(0), x + image.shape(1), y);
text.clip(fig);

if (gc.cliprect.x1 != 0.0 || gc.cliprect.y1 != 0.0 || gc.cliprect.x2 != 0.0 || gc.cliprect.y2 != 0.0) {
agg::rect_i clip;

clip.init(mpl_round_to_int(gc.cliprect.x1),
mpl_round_to_int(height - gc.cliprect.y2),
mpl_round_to_int(gc.cliprect.x2),
mpl_round_to_int(height - gc.cliprect.y1));
text.clip(clip);
}

if (text.x2 > text.x1) {
int deltax = text.x2 - text.x1;
int deltax2 = text.x1 - x;
int deltay = y - image.shape(0);
for (int yi = text.y1; yi < text.y2; ++yi) {
unsigned char *data = &image(yi - deltay, deltax2, 0);
agg::rgba8 *src_span = static_cast<agg::rgba8*>(static_cast<void*>(data));
pixFmt.blend_color_hspan(text.x1, yi, deltax, src_span, 0, agg::cover_full);
}
}
}
}

class span_conv_alpha
{
public:
Expand Down
44 changes: 44 additions & 0 deletions src/_backend_agg_wrapper.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,48 @@ PyRendererAgg_draw_text_image(RendererAgg *self,
self->draw_text_image(gc, image, x, y, angle);
}

static void
PyRendererAgg_draw_text_image_mm4(RendererAgg *self,
py::array_t<agg::int8u, py::array::c_style | py::array::forcecast> image_obj,
std::variant<double, int> vx,
std::variant<double, int> vy,
double angle,
GCAgg &gc)
{
int x, y;

if (auto value = std::get_if<double>(&vx)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Go straight for the newer API here, no need to introduce the old one.

auto api = py::module_::import("matplotlib._api");
auto warn = api.attr("warn_deprecated");
warn("since"_a="3.10", "name"_a="x", "obj_type"_a="parameter as float",
"alternative"_a="int(x)");
x = static_cast<int>(*value);
} else if (auto value = std::get_if<int>(&vx)) {
x = *value;
} else {
throw std::runtime_error("Should not happen");
}

if (auto value = std::get_if<double>(&vy)) {
auto api = py::module_::import("matplotlib._api");
auto warn = api.attr("warn_deprecated");
warn("since"_a="3.10", "name"_a="y", "obj_type"_a="parameter as float",
"alternative"_a="int(y)");
y = static_cast<int>(*value);
} else if (auto value = std::get_if<int>(&vy)) {
y = *value;
} else {
throw std::runtime_error("Should not happen");
}

// TODO: This really shouldn't be mutable, but Agg's renderer buffers aren't const.
// NOTE: The only difference from the original is the number of dimensions.
// In my example, the shape was (10, 164, 4). The last is the color channels.
auto image = image_obj.mutable_unchecked<3>();

self->draw_text_image_mm4(gc, image, x, y, angle);
}

static void
PyRendererAgg_draw_markers(RendererAgg *self,
GCAgg &gc,
Expand Down Expand Up @@ -227,6 +269,8 @@ PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used())
"face"_a = nullptr)
.def("draw_text_image", &PyRendererAgg_draw_text_image,
"image"_a, "x"_a, "y"_a, "angle"_a, "gc"_a)
.def("draw_text_image_mm4", &PyRendererAgg_draw_text_image_mm4,
"image"_a, "x"_a, "y"_a, "angle"_a, "gc"_a)
.def("draw_image", &PyRendererAgg_draw_image,
"gc"_a, "x"_a, "y"_a, "image"_a)
.def("draw_path_collection", &PyRendererAgg_draw_path_collection,
Expand Down
Loading

Back | FazBrowse Home | New Git URL