Skip to content

Commit 796cb7c

Browse files
committed
Snap pixel markers.
1 parent bd5b4a9 commit 796cb7c

File tree

3 files changed

+38
-8
lines changed

3 files changed

+38
-8
lines changed

src/_mplcairo.cpp

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -728,19 +728,23 @@ void GraphicsContextRenderer::draw_markers(
728728
auto const& matrix =
729729
matrix_from_transform(transform, get_additional_state().height);
730730

731-
auto const& fc_raw =
731+
auto const& fc_raw_opt =
732732
fc ? to_rgba(*fc, get_additional_state().alpha) : std::optional<rgba_t>{};
733733
auto const& ec_raw = get_rgba();
734734

735735
auto const& draw_one_marker = [&](cairo_t* cr, double x, double y) -> void {
736736
auto const& m = cairo_matrix_t{
737737
marker_matrix.xx, marker_matrix.yx, marker_matrix.xy, marker_matrix.yy,
738738
marker_matrix.x0 + x, marker_matrix.y0 + y};
739-
fill_and_stroke_exact(cr, marker_path, &m, fc_raw, ec_raw);
739+
fill_and_stroke_exact(cr, marker_path, &m, fc_raw_opt, ec_raw);
740740
};
741741

742+
// Pixel markers *must* be drawn snapped.
743+
auto const& is_pixel_marker =
744+
py_eq(marker_path, detail::PIXEL_MARKER.attr("get_path")())
745+
&& py_eq(marker_transform, detail::PIXEL_MARKER.attr("get_transform")());
742746
auto const& simplify_threshold =
743-
has_vector_surface(cr_)
747+
is_pixel_marker || has_vector_surface(cr_)
744748
? 0 : rc_param("path.simplify_threshold").cast<double>();
745749
auto patterns = std::unique_ptr<cairo_pattern_t*[]>{};
746750
auto const& n_subpix = // NOTE: Arbitrary limit of 1/16.
@@ -827,6 +831,25 @@ void GraphicsContextRenderer::draw_markers(
827831
cairo_pattern_destroy(patterns[i]);
828832
}
829833

834+
} else if (is_pixel_marker && !has_vector_surface(cr_)) {
835+
auto const& surface = cairo_get_target(cr_);
836+
auto const& raw = cairo_image_surface_get_data(surface);
837+
auto const& stride = cairo_image_surface_get_stride(surface);
838+
auto const& [r, g, b, a] = fc_raw_opt ? *fc_raw_opt : ec_raw;
839+
auto const& fc_argb32 = uint32_t(
840+
(uint8_t(255 * a) << 24) | (uint8_t(255 * a * r) << 16)
841+
| (uint8_t(255 * a * g) << 8) | (uint8_t(255 * a * b)));
842+
cairo_surface_flush(surface);
843+
for (auto i = 0; i < n_vertices; ++i) {
844+
auto x = vertices(i, 0), y = vertices(i, 1);
845+
cairo_matrix_transform_point(&matrix, &x, &y);
846+
if (!(std::isfinite(x) && std::isfinite(y))) {
847+
continue;
848+
}
849+
*reinterpret_cast<uint32_t*>(
850+
raw + std::lround(y) * stride + 4 * std::lround(x)) = fc_argb32;
851+
}
852+
cairo_surface_mark_dirty(surface);
830853
} else {
831854
for (auto i = 0; i < n_vertices; ++i) {
832855
cairo_save(cr_);
@@ -1200,7 +1223,7 @@ GraphicsContextRenderer::get_text_width_height_descent(
12001223
// - "ismath" can be True, False, "TeX" (i.e., usetex).
12011224
// FIXME[matplotlib]: RendererAgg relies on the text.usetex rcParam, whereas
12021225
// RendererBase relies (correctly?) on the value of ismath.
1203-
if (py::module::import("operator").attr("eq")(ismath, "TeX").cast<bool>()) {
1226+
if (py_eq(ismath, py::cast("TeX"))) {
12041227
return
12051228
py::module::import("matplotlib.backend_bases").attr("RendererBase")
12061229
.attr("get_text_width_height_descent")(this, s, prop, ismath)
@@ -1273,7 +1296,7 @@ Region GraphicsContextRenderer::copy_from_bbox(py::object bbox)
12731296
throw std::invalid_argument("Invalid bbox");
12741297
}
12751298
auto const& width = x1 - x0, height = y1 - y0;
1276-
// 4 bytes per pixel throughout!
1299+
// 4 bytes per pixel throughout.
12771300
auto buf = std::unique_ptr<uint8_t[]>{new uint8_t[4 * width * height]};
12781301
auto const& surface = cairo_get_target(cr_);
12791302
if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE) {
@@ -1302,9 +1325,8 @@ void GraphicsContextRenderer::restore_region(Region& region)
13021325
auto const& raw = cairo_image_surface_get_data(surface);
13031326
auto const& stride = cairo_image_surface_get_stride(surface);
13041327
cairo_surface_flush(surface);
1305-
// 4 bytes per pixel!
13061328
for (auto y = y0; y < y1; ++y) {
1307-
std::memcpy(
1329+
std::memcpy( // 4 bytes per pixel.
13081330
raw + y * stride + 4 * x0, buf.get() + (y - y0) * 4 * width, 4 * width);
13091331
}
13101332
cairo_surface_mark_dirty_rectangle(surface, x0, y0, width, height);
@@ -1448,6 +1470,8 @@ PYBIND11_MODULE(_mplcairo, m)
14481470

14491471
detail::UNIT_CIRCLE =
14501472
py::module::import("matplotlib.path").attr("Path").attr("unit_circle")();
1473+
detail::PIXEL_MARKER =
1474+
py::module::import("matplotlib.markers").attr("MarkerStyle")(",");
14511475

14521476
FT_CHECK(FT_Init_FreeType, &detail::ft_library);
14531477
auto ft_cleanup = py::cpp_function{

src/_util.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ ps_surface_set_eps_t cairo_ps_surface_set_eps;
3535
ps_surface_dsc_comment_t cairo_ps_surface_dsc_comment;
3636

3737
cairo_user_data_key_t const REFS_KEY{}, STATE_KEY{}, FT_KEY{};
38-
py::object UNIT_CIRCLE{};
38+
py::object UNIT_CIRCLE{}, PIXEL_MARKER{};
39+
}
40+
41+
bool py_eq(py::object obj1, py::object obj2) {
42+
return py::module::import("operator").attr("eq")(obj1, obj2).cast<bool>();
3943
}
4044

4145
py::object rc_param(std::string key)

src/_util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ extern cairo_user_data_key_t const
6464
STATE_KEY, // cairo_t -> additional state.
6565
FT_KEY; // cairo_font_face_t -> FT_Face.
6666
extern py::object UNIT_CIRCLE;
67+
extern py::object PIXEL_MARKER;
6768
}
6869

6970
using rectangle_t = std::tuple<double, double, double, double>;
@@ -90,6 +91,7 @@ struct AdditionalState {
9091
std::optional<std::string> url;
9192
};
9293

94+
bool py_eq(py::object obj1, py::object obj2);
9395
py::object rc_param(std::string key);
9496
rgba_t to_rgba(py::object color, std::optional<double> alpha = {});
9597
cairo_matrix_t matrix_from_transform(py::object transform, double y0 = 0);

0 commit comments

Comments
 (0)