diff options
author | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 14:39:34 +0300 |
---|---|---|
committer | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 16:42:24 +0300 |
commit | 77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch) | |
tree | c51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py3/src | |
parent | dd6d20cadb65582270ac23f4b3b14ae189704b9d (diff) | |
download | ydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz |
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py3/src')
29 files changed, 14905 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py3/src/_backend_agg.cpp b/contrib/python/matplotlib/py3/src/_backend_agg.cpp new file mode 100644 index 0000000000..335e409719 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_backend_agg.cpp @@ -0,0 +1,176 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#define NO_IMPORT_ARRAY + +#include "_backend_agg.h" +#include "mplutils.h" + +void BufferRegion::to_string_argb(uint8_t *buf) +{ + unsigned char *pix; + unsigned char tmp; + size_t i, j; + + memcpy(buf, data, (size_t) height * stride); + + for (i = 0; i < (size_t)height; ++i) { + pix = buf + i * stride; + for (j = 0; j < (size_t)width; ++j) { + // Convert rgba to argb + tmp = pix[2]; + pix[2] = pix[0]; + pix[0] = tmp; + pix += 4; + } + } +} + +RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) + : width(width), + height(height), + dpi(dpi), + NUMBYTES((size_t)width * (size_t)height * 4), + pixBuffer(NULL), + renderingBuffer(), + alphaBuffer(NULL), + alphaMaskRenderingBuffer(), + alphaMask(alphaMaskRenderingBuffer), + pixfmtAlphaMask(alphaMaskRenderingBuffer), + rendererBaseAlphaMask(), + rendererAlphaMask(), + scanlineAlphaMask(), + slineP8(), + slineBin(), + pixFmt(), + rendererBase(), + rendererAA(), + rendererBin(), + theRasterizer(32768), + lastclippath(NULL), + _fill_color(agg::rgba(1, 1, 1, 0)) +{ + unsigned stride(width * 4); + + pixBuffer = new agg::int8u[NUMBYTES]; + renderingBuffer.attach(pixBuffer, width, height, stride); + pixFmt.attach(renderingBuffer); + rendererBase.attach(pixFmt); + rendererBase.clear(_fill_color); + rendererAA.attach(rendererBase); + rendererBin.attach(rendererBase); + hatch_size = int(dpi); + hatchBuffer = new agg::int8u[hatch_size * hatch_size * 4]; + hatchRenderingBuffer.attach(hatchBuffer, hatch_size, hatch_size, hatch_size * 4); +} + +RendererAgg::~RendererAgg() +{ + delete[] hatchBuffer; + delete[] alphaBuffer; + delete[] pixBuffer; +} + +void RendererAgg::create_alpha_buffers() +{ + if (!alphaBuffer) { + alphaBuffer = new agg::int8u[width * height]; + alphaMaskRenderingBuffer.attach(alphaBuffer, width, height, width); + rendererBaseAlphaMask.attach(pixfmtAlphaMask); + rendererAlphaMask.attach(rendererBaseAlphaMask); + } +} + +BufferRegion *RendererAgg::copy_from_bbox(agg::rect_d in_rect) +{ + agg::rect_i rect( + (int)in_rect.x1, height - (int)in_rect.y2, (int)in_rect.x2, height - (int)in_rect.y1); + + BufferRegion *reg = NULL; + reg = new BufferRegion(rect); + + agg::rendering_buffer rbuf; + rbuf.attach(reg->get_data(), reg->get_width(), reg->get_height(), reg->get_stride()); + + pixfmt pf(rbuf); + renderer_base rb(pf); + rb.copy_from(renderingBuffer, &rect, -rect.x1, -rect.y1); + + return reg; +} + +void RendererAgg::restore_region(BufferRegion ®ion) +{ + if (region.get_data() == NULL) { + throw std::runtime_error("Cannot restore_region from NULL data"); + } + + agg::rendering_buffer rbuf; + rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride()); + + rendererBase.copy_from(rbuf, 0, region.get_rect().x1, region.get_rect().y1); +} + +// Restore the part of the saved region with offsets +void +RendererAgg::restore_region(BufferRegion ®ion, int xx1, int yy1, int xx2, int yy2, int x, int y ) +{ + if (region.get_data() == NULL) { + throw std::runtime_error("Cannot restore_region from NULL data"); + } + + agg::rect_i &rrect = region.get_rect(); + + agg::rect_i rect(xx1 - rrect.x1, (yy1 - rrect.y1), xx2 - rrect.x1, (yy2 - rrect.y1)); + + agg::rendering_buffer rbuf; + rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride()); + + rendererBase.copy_from(rbuf, &rect, x, y); +} + +bool RendererAgg::render_clippath(py::PathIterator &clippath, + const agg::trans_affine &clippath_trans, + e_snap_mode snap_mode) +{ + typedef agg::conv_transform<py::PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removed_t; + /* Unlike normal Paths, the clip path cannot be clipped to the Figure bbox, + * because it needs to remain a complete closed path, so there is no + * PathClipper<nan_removed_t> step. */ + typedef PathSnapper<nan_removed_t> snapped_t; + typedef PathSimplifier<snapped_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; + + bool has_clippath = (clippath.total_vertices() != 0); + + if (has_clippath && + (clippath.get_id() != lastclippath || clippath_trans != lastclippath_transform)) { + create_alpha_buffers(); + agg::trans_affine trans(clippath_trans); + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.0, (double)height); + + rendererBaseAlphaMask.clear(agg::gray8(0, 0)); + transformed_path_t transformed_clippath(clippath, trans); + nan_removed_t nan_removed_clippath(transformed_clippath, true, clippath.has_codes()); + snapped_t snapped_clippath(nan_removed_clippath, snap_mode, clippath.total_vertices(), 0.0); + simplify_t simplified_clippath(snapped_clippath, + clippath.should_simplify() && !clippath.has_codes(), + clippath.simplify_threshold()); + curve_t curved_clippath(simplified_clippath); + theRasterizer.add_path(curved_clippath); + rendererAlphaMask.color(agg::gray8(255, 255)); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, rendererAlphaMask); + lastclippath = clippath.get_id(); + lastclippath_transform = clippath_trans; + } + + return has_clippath; +} + +void RendererAgg::clear() +{ + //"clear the rendered buffer"; + + rendererBase.clear(_fill_color); +} diff --git a/contrib/python/matplotlib/py3/src/_backend_agg.h b/contrib/python/matplotlib/py3/src/_backend_agg.h new file mode 100644 index 0000000000..61c24232a8 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_backend_agg.h @@ -0,0 +1,1280 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +/* _backend_agg.h +*/ + +#ifndef MPL_BACKEND_AGG_H +#define MPL_BACKEND_AGG_H + +#include <cmath> +#include <vector> +#include <algorithm> + +#include "agg_alpha_mask_u8.h" +#include "agg_conv_curve.h" +#include "agg_conv_dash.h" +#include "agg_conv_stroke.h" +#include "agg_image_accessors.h" +#include "agg_pixfmt_amask_adaptor.h" +#include "agg_pixfmt_gray.h" +#include "agg_pixfmt_rgba.h" +#include "agg_rasterizer_scanline_aa.h" +#include "agg_renderer_base.h" +#include "agg_renderer_scanline.h" +#include "agg_rendering_buffer.h" +#include "agg_scanline_bin.h" +#include "agg_scanline_p.h" +#include "agg_scanline_storage_aa.h" +#include "agg_scanline_storage_bin.h" +#include "agg_scanline_u.h" +#include "agg_span_allocator.h" +#include "agg_span_converter.h" +#include "agg_span_gouraud_rgba.h" +#include "agg_span_image_filter_gray.h" +#include "agg_span_image_filter_rgba.h" +#include "agg_span_interpolator_linear.h" +#include "agg_span_pattern_rgba.h" +#include "util/agg_color_conv_rgb8.h" + +#include "_backend_agg_basic_types.h" +#include "path_converters.h" +#include "array.h" +#include "agg_workaround.h" + +/**********************************************************************/ + +// a helper class to pass agg::buffer objects around. + +class BufferRegion +{ + public: + BufferRegion(const agg::rect_i &r) : rect(r) + { + width = r.x2 - r.x1; + height = r.y2 - r.y1; + stride = width * 4; + data = new agg::int8u[stride * height]; + } + + virtual ~BufferRegion() + { + delete[] data; + }; + + agg::int8u *get_data() + { + return data; + } + + agg::rect_i &get_rect() + { + return rect; + } + + int get_width() + { + return width; + } + + int get_height() + { + return height; + } + + int get_stride() + { + return stride; + } + + void to_string_argb(uint8_t *buf); + + private: + agg::int8u *data; + agg::rect_i rect; + int width; + int height; + int stride; + + private: + // prevent copying + BufferRegion(const BufferRegion &); + BufferRegion &operator=(const BufferRegion &); +}; + +#define MARKER_CACHE_SIZE 512 + +// the renderer +class RendererAgg +{ + public: + + typedef fixed_blender_rgba_plain<agg::rgba8, agg::order_rgba> fixed_blender_rgba32_plain; + typedef agg::pixfmt_alpha_blend_rgba<fixed_blender_rgba32_plain, agg::rendering_buffer> pixfmt; + typedef agg::renderer_base<pixfmt> renderer_base; + typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_aa; + typedef agg::renderer_scanline_bin_solid<renderer_base> renderer_bin; + typedef agg::rasterizer_scanline_aa<agg::rasterizer_sl_clip_dbl> rasterizer; + + typedef agg::scanline_p8 scanline_p8; + typedef agg::scanline_bin scanline_bin; + typedef agg::amask_no_clip_gray8 alpha_mask_type; + typedef agg::scanline_u8_am<alpha_mask_type> scanline_am; + + typedef agg::renderer_base<agg::pixfmt_gray8> renderer_base_alpha_mask_type; + typedef agg::renderer_scanline_aa_solid<renderer_base_alpha_mask_type> renderer_alpha_mask_type; + + /* TODO: Remove facepair_t */ + typedef std::pair<bool, agg::rgba> facepair_t; + + RendererAgg(unsigned int width, unsigned int height, double dpi); + + virtual ~RendererAgg(); + + unsigned int get_width() + { + return width; + } + + unsigned int get_height() + { + return height; + } + + template <class PathIterator> + void draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color); + + template <class PathIterator> + void draw_markers(GCAgg &gc, + PathIterator &marker_path, + agg::trans_affine &marker_path_trans, + PathIterator &path, + agg::trans_affine &trans, + agg::rgba face); + + template <class ImageArray> + void draw_text_image(GCAgg &gc, ImageArray &image, int x, int y, double angle); + + template <class ImageArray> + void draw_image(GCAgg &gc, + double x, + double y, + ImageArray &image); + + template <class PathGenerator, + class TransformArray, + class OffsetArray, + class ColorArray, + class LineWidthArray, + class AntialiasedArray> + void draw_path_collection(GCAgg &gc, + agg::trans_affine &master_transform, + PathGenerator &path, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds); + + template <class CoordinateArray, class OffsetArray, class ColorArray> + void draw_quad_mesh(GCAgg &gc, + agg::trans_affine &master_transform, + unsigned int mesh_width, + unsigned int mesh_height, + CoordinateArray &coordinates, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + bool antialiased, + ColorArray &edgecolors); + + template <class PointArray, class ColorArray> + void draw_gouraud_triangle(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans); + + template <class PointArray, class ColorArray> + void draw_gouraud_triangles(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans); + + agg::rect_i get_content_extents(); + void clear(); + + BufferRegion *copy_from_bbox(agg::rect_d in_rect); + void restore_region(BufferRegion ®); + void restore_region(BufferRegion ®ion, int xx1, int yy1, int xx2, int yy2, int x, int y); + + unsigned int width, height; + double dpi; + size_t NUMBYTES; // the number of bytes in buffer + + agg::int8u *pixBuffer; + agg::rendering_buffer renderingBuffer; + + agg::int8u *alphaBuffer; + agg::rendering_buffer alphaMaskRenderingBuffer; + alpha_mask_type alphaMask; + agg::pixfmt_gray8 pixfmtAlphaMask; + renderer_base_alpha_mask_type rendererBaseAlphaMask; + renderer_alpha_mask_type rendererAlphaMask; + scanline_am scanlineAlphaMask; + + scanline_p8 slineP8; + scanline_bin slineBin; + pixfmt pixFmt; + renderer_base rendererBase; + renderer_aa rendererAA; + renderer_bin rendererBin; + rasterizer theRasterizer; + + void *lastclippath; + agg::trans_affine lastclippath_transform; + + size_t hatch_size; + agg::int8u *hatchBuffer; + agg::rendering_buffer hatchRenderingBuffer; + + agg::rgba _fill_color; + + protected: + inline double points_to_pixels(double points) + { + return points * dpi / 72.0; + } + + template <class R> + void set_clipbox(const agg::rect_d &cliprect, R &rasterizer); + + bool render_clippath(py::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode); + + template <class PathIteratorType> + void _draw_path(PathIteratorType &path, bool has_clippath, const facepair_t &face, GCAgg &gc); + + template <class PathIterator, + class PathGenerator, + class TransformArray, + class OffsetArray, + class ColorArray, + class LineWidthArray, + class AntialiasedArray> + void _draw_path_collection_generic(GCAgg &gc, + agg::trans_affine master_transform, + const agg::rect_d &cliprect, + PathIterator &clippath, + const agg::trans_affine &clippath_trans, + PathGenerator &path_generator, + TransformArray &transforms, + OffsetArray &offsets, + const agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds, + bool check_snap, + bool has_codes); + + template <class PointArray, class ColorArray> + void _draw_gouraud_triangle(PointArray &points, + ColorArray &colors, + agg::trans_affine trans, + bool has_clippath); + + private: + void create_alpha_buffers(); + + // prevent copying + RendererAgg(const RendererAgg &); + RendererAgg &operator=(const RendererAgg &); +}; + +/*************************************************************************** + * Implementation + */ + +template <class path_t> +inline void +RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, GCAgg &gc) +{ + typedef agg::conv_stroke<path_t> stroke_t; + typedef agg::conv_dash<path_t> dash_t; + typedef agg::conv_stroke<dash_t> stroke_dash_t; + typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type; + typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type; + typedef agg::renderer_scanline_aa_solid<amask_ren_type> amask_aa_renderer_type; + typedef agg::renderer_scanline_bin_solid<amask_ren_type> amask_bin_renderer_type; + + // Render face + if (face.first) { + theRasterizer.add_path(path); + + if (gc.isaa) { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r); + ren.color(face.second); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererAA.color(face.second); + agg::render_scanlines(theRasterizer, slineP8, rendererAA); + } + } else { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_bin_renderer_type ren(r); + ren.color(face.second); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererBin.color(face.second); + agg::render_scanlines(theRasterizer, slineP8, rendererBin); + } + } + } + + // Render hatch + if (gc.has_hatchpath()) { + // Reset any clipping that may be in effect, since we'll be + // drawing the hatch in a scratch buffer at origin (0, 0) + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + + // Create and transform the path + typedef agg::conv_transform<py::PathIterator> hatch_path_trans_t; + typedef agg::conv_curve<hatch_path_trans_t> hatch_path_curve_t; + typedef agg::conv_stroke<hatch_path_curve_t> hatch_path_stroke_t; + + py::PathIterator hatch_path(gc.hatchpath); + agg::trans_affine hatch_trans; + hatch_trans *= agg::trans_affine_scaling(1.0, -1.0); + hatch_trans *= agg::trans_affine_translation(0.0, 1.0); + hatch_trans *= agg::trans_affine_scaling(hatch_size, hatch_size); + hatch_path_trans_t hatch_path_trans(hatch_path, hatch_trans); + hatch_path_curve_t hatch_path_curve(hatch_path_trans); + hatch_path_stroke_t hatch_path_stroke(hatch_path_curve); + hatch_path_stroke.width(points_to_pixels(gc.hatch_linewidth)); + hatch_path_stroke.line_cap(agg::square_cap); + + // Render the path into the hatch buffer + pixfmt hatch_img_pixf(hatchRenderingBuffer); + renderer_base rb(hatch_img_pixf); + renderer_aa rs(rb); + rb.clear(_fill_color); + rs.color(gc.hatch_color); + + theRasterizer.add_path(hatch_path_curve); + agg::render_scanlines(theRasterizer, slineP8, rs); + theRasterizer.add_path(hatch_path_stroke); + agg::render_scanlines(theRasterizer, slineP8, rs); + + // Put clipping back on, if originally set on entry to this + // function + set_clipbox(gc.cliprect, theRasterizer); + if (has_clippath) { + render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); + } + + // Transfer the hatch to the main image buffer + typedef agg::image_accessor_wrap<pixfmt, + agg::wrap_mode_repeat_auto_pow2, + agg::wrap_mode_repeat_auto_pow2> img_source_type; + typedef agg::span_pattern_rgba<img_source_type> span_gen_type; + agg::span_allocator<agg::rgba8> sa; + img_source_type img_src(hatch_img_pixf); + span_gen_type sg(img_src, 0, 0); + theRasterizer.add_path(path); + + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type ren(pfa); + agg::render_scanlines_aa(theRasterizer, slineP8, ren, sa, sg); + } else { + agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, sa, sg); + } + } + + // Render stroke + if (gc.linewidth != 0.0) { + double linewidth = points_to_pixels(gc.linewidth); + if (!gc.isaa) { + linewidth = (linewidth < 0.5) ? 0.5 : mpl_round(linewidth); + } + if (gc.dashes.size() == 0) { + stroke_t stroke(path); + stroke.width(points_to_pixels(gc.linewidth)); + stroke.line_cap(gc.cap); + stroke.line_join(gc.join); + stroke.miter_limit(points_to_pixels(gc.linewidth)); + theRasterizer.add_path(stroke); + } else { + dash_t dash(path); + gc.dashes.dash_to_stroke(dash, dpi, gc.isaa); + stroke_dash_t stroke(dash); + stroke.line_cap(gc.cap); + stroke.line_join(gc.join); + stroke.width(linewidth); + stroke.miter_limit(points_to_pixels(gc.linewidth)); + theRasterizer.add_path(stroke); + } + + if (gc.isaa) { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r); + ren.color(gc.color); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererAA.color(gc.color); + agg::render_scanlines(theRasterizer, slineP8, rendererAA); + } + } else { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_bin_renderer_type ren(r); + ren.color(gc.color); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererBin.color(gc.color); + agg::render_scanlines(theRasterizer, slineBin, rendererBin); + } + } + } +} + +template <class PathIterator> +inline void +RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color) +{ + typedef agg::conv_transform<py::PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removed_t; + typedef PathClipper<nan_removed_t> clipped_t; + typedef PathSnapper<clipped_t> snapped_t; + typedef PathSimplifier<snapped_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; + typedef Sketch<curve_t> sketch_t; + + facepair_t face(color.a != 0.0, color); + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); + + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.0, (double)height); + bool clip = !face.first && !gc.has_hatchpath(); + bool simplify = path.should_simplify() && clip; + double snapping_linewidth = points_to_pixels(gc.linewidth); + if (gc.color.a == 0.0) { + snapping_linewidth = 0.0; + } + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, path.has_codes()); + clipped_t clipped(nan_removed, clip, width, height); + snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), snapping_linewidth); + simplify_t simplified(snapped, simplify, path.simplify_threshold()); + curve_t curve(simplified); + sketch_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness); + + _draw_path(sketch, has_clippath, face, gc); +} + +template <class PathIterator> +inline void RendererAgg::draw_markers(GCAgg &gc, + PathIterator &marker_path, + agg::trans_affine &marker_trans, + PathIterator &path, + agg::trans_affine &trans, + agg::rgba color) +{ + typedef agg::conv_transform<py::PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removed_t; + typedef PathSnapper<nan_removed_t> snap_t; + typedef agg::conv_curve<snap_t> curve_t; + typedef agg::conv_stroke<curve_t> stroke_t; + typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type; + typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type; + typedef agg::renderer_scanline_aa_solid<amask_ren_type> amask_aa_renderer_type; + + // Deal with the difference in y-axis direction + marker_trans *= agg::trans_affine_scaling(1.0, -1.0); + + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.5, (double)height + 0.5); + + transformed_path_t marker_path_transformed(marker_path, marker_trans); + nan_removed_t marker_path_nan_removed(marker_path_transformed, true, marker_path.has_codes()); + snap_t marker_path_snapped(marker_path_nan_removed, + gc.snap_mode, + marker_path.total_vertices(), + points_to_pixels(gc.linewidth)); + curve_t marker_path_curve(marker_path_snapped); + + if (!marker_path_snapped.is_snapping()) { + // If the path snapper isn't in effect, at least make sure the marker + // at (0, 0) is in the center of a pixel. This, importantly, makes + // the circle markers look centered around the point they refer to. + marker_trans *= agg::trans_affine_translation(0.5, 0.5); + } + + transformed_path_t path_transformed(path, trans); + nan_removed_t path_nan_removed(path_transformed, false, false); + snap_t path_snapped(path_nan_removed, SNAP_FALSE, path.total_vertices(), 0.0); + curve_t path_curve(path_snapped); + path_curve.rewind(0); + + facepair_t face(color.a != 0.0, color); + + // maxim's suggestions for cached scanlines + agg::scanline_storage_aa8 scanlines; + theRasterizer.reset(); + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + agg::rect_i marker_size(0x7FFFFFFF, 0x7FFFFFFF, -0x7FFFFFFF, -0x7FFFFFFF); + + agg::int8u staticFillCache[MARKER_CACHE_SIZE]; + agg::int8u staticStrokeCache[MARKER_CACHE_SIZE]; + agg::int8u *fillCache = staticFillCache; + agg::int8u *strokeCache = staticStrokeCache; + + try + { + unsigned fillSize = 0; + if (face.first) { + theRasterizer.add_path(marker_path_curve); + agg::render_scanlines(theRasterizer, slineP8, scanlines); + fillSize = scanlines.byte_size(); + if (fillSize >= MARKER_CACHE_SIZE) { + fillCache = new agg::int8u[fillSize]; + } + scanlines.serialize(fillCache); + marker_size = agg::rect_i(scanlines.min_x(), + scanlines.min_y(), + scanlines.max_x(), + scanlines.max_y()); + } + + stroke_t stroke(marker_path_curve); + stroke.width(points_to_pixels(gc.linewidth)); + stroke.line_cap(gc.cap); + stroke.line_join(gc.join); + stroke.miter_limit(points_to_pixels(gc.linewidth)); + theRasterizer.reset(); + theRasterizer.add_path(stroke); + agg::render_scanlines(theRasterizer, slineP8, scanlines); + unsigned strokeSize = scanlines.byte_size(); + if (strokeSize >= MARKER_CACHE_SIZE) { + strokeCache = new agg::int8u[strokeSize]; + } + scanlines.serialize(strokeCache); + marker_size = agg::rect_i(std::min(marker_size.x1, scanlines.min_x()), + std::min(marker_size.y1, scanlines.min_y()), + std::max(marker_size.x2, scanlines.max_x()), + std::max(marker_size.y2, scanlines.max_y())); + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, rendererBase); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); + + double x, y; + + agg::serialized_scanlines_adaptor_aa8 sa; + agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl; + + agg::rect_d clipping_rect(-1.0 - marker_size.x2, + -1.0 - marker_size.y2, + 1.0 + width - marker_size.x1, + 1.0 + height - marker_size.y1); + + if (has_clippath) { + while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) { + if (!(std::isfinite(x) && std::isfinite(y))) { + continue; + } + + /* These values are correctly snapped above -- so we don't want + to round here, we really only want to truncate */ + x = floor(x); + y = floor(y); + + // Cull points outside the boundary of the image. + // Values that are too large may overflow and create + // segfaults. + // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720 + if (!clipping_rect.hit_test(x, y)) { + continue; + } + + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r); + + if (face.first) { + ren.color(face.second); + sa.init(fillCache, fillSize, x, y); + agg::render_scanlines(sa, sl, ren); + } + ren.color(gc.color); + sa.init(strokeCache, strokeSize, x, y); + agg::render_scanlines(sa, sl, ren); + } + } else { + while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) { + if (!(std::isfinite(x) && std::isfinite(y))) { + continue; + } + + /* These values are correctly snapped above -- so we don't want + to round here, we really only want to truncate */ + x = floor(x); + y = floor(y); + + // Cull points outside the boundary of the image. + // Values that are too large may overflow and create + // segfaults. + // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720 + if (!clipping_rect.hit_test(x, y)) { + continue; + } + + if (face.first) { + rendererAA.color(face.second); + sa.init(fillCache, fillSize, x, y); + agg::render_scanlines(sa, sl, rendererAA); + } + + rendererAA.color(gc.color); + sa.init(strokeCache, strokeSize, x, y); + agg::render_scanlines(sa, sl, rendererAA); + } + } + } + catch (...) + { + if (fillCache != staticFillCache) + delete[] fillCache; + if (strokeCache != staticStrokeCache) + delete[] strokeCache; + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + throw; + } + + if (fillCache != staticFillCache) + delete[] fillCache; + if (strokeCache != staticStrokeCache) + delete[] strokeCache; + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); +} + +/** + * This is a custom span generator that converts spans in the + * 8-bit inverted greyscale font buffer to rgba that agg can use. + */ +template <class ChildGenerator> +class font_to_rgba +{ + public: + typedef ChildGenerator child_type; + typedef agg::rgba8 color_type; + typedef typename child_type::color_type child_color_type; + typedef agg::span_allocator<child_color_type> span_alloc_type; + + private: + child_type *_gen; + color_type _color; + span_alloc_type _allocator; + + public: + font_to_rgba(child_type *gen, color_type color) : _gen(gen), _color(color) + { + } + + inline void generate(color_type *output_span, int x, int y, unsigned len) + { + _allocator.allocate(len); + child_color_type *input_span = _allocator.span(); + _gen->generate(input_span, x, y, len); + + do { + *output_span = _color; + output_span->a = ((unsigned int)_color.a * (unsigned int)input_span->v) >> 8; + ++output_span; + ++input_span; + } while (--len); + } + + void prepare() + { + _gen->prepare(); + } +}; + +template <class ImageArray> +inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, int y, double angle) +{ + typedef agg::span_allocator<agg::rgba8> color_span_alloc_type; + typedef agg::span_interpolator_linear<> interpolator_type; + typedef agg::image_accessor_clip<agg::pixfmt_gray8> image_accessor_type; + typedef agg::span_image_filter_gray<image_accessor_type, interpolator_type> image_span_gen_type; + typedef font_to_rgba<image_span_gen_type> span_gen_type; + typedef agg::renderer_scanline_aa<renderer_base, color_span_alloc_type, span_gen_type> + renderer_type; + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + if (angle != 0.0) { + agg::rendering_buffer srcbuf( + image.data(), (unsigned)image.dim(1), + (unsigned)image.dim(0), (unsigned)image.dim(1)); + agg::pixfmt_gray8 pixf_img(srcbuf); + + set_clipbox(gc.cliprect, theRasterizer); + + agg::trans_affine mtx; + mtx *= agg::trans_affine_translation(0, -image.dim(0)); + 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.dim(1), 0); + rect.line_to(image.dim(1), image.dim(0)); + rect.line_to(0, image.dim(0)); + rect.line_to(0, 0); + agg::conv_transform<agg::path_storage> rect2(rect, mtx); + + agg::trans_affine inv_mtx(mtx); + inv_mtx.invert(); + + agg::image_filter_lut filter; + filter.calculate(agg::image_filter_spline36()); + interpolator_type interpolator(inv_mtx); + color_span_alloc_type sa; + image_accessor_type ia(pixf_img, agg::gray8(0)); + image_span_gen_type image_span_generator(ia, interpolator, filter); + span_gen_type output_span_generator(&image_span_generator, gc.color); + renderer_type ri(rendererBase, sa, output_span_generator); + + theRasterizer.add_path(rect2); + agg::render_scanlines(theRasterizer, slineP8, ri); + } else { + agg::rect_i fig, text; + + int deltay = y - image.dim(0); + + fig.init(0, 0, width, height); + text.init(x, deltay, x + image.dim(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; + for (int yi = text.y1; yi < text.y2; ++yi) { + pixFmt.blend_solid_hspan(text.x1, yi, deltax, gc.color, + &image(yi - deltay, deltax2)); + } + } + } +} + +class span_conv_alpha +{ + public: + typedef agg::rgba8 color_type; + + double m_alpha; + + span_conv_alpha(double alpha) : m_alpha(alpha) + { + } + + void prepare() + { + } + void generate(color_type *span, int x, int y, unsigned len) const + { + do { + span->a = (agg::int8u)((double)span->a * m_alpha); + ++span; + } while (--len); + } +}; + +template <class ImageArray> +inline void RendererAgg::draw_image(GCAgg &gc, + double x, + double y, + ImageArray &image) +{ + double alpha = gc.alpha; + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); + + agg::rendering_buffer buffer; + buffer.attach( + image.data(), (unsigned)image.dim(1), (unsigned)image.dim(0), -(int)image.dim(1) * 4); + pixfmt pixf(buffer); + + if (has_clippath) { + agg::trans_affine mtx; + agg::path_storage rect; + + mtx *= agg::trans_affine_translation((int)x, (int)(height - (y + image.dim(0)))); + + rect.move_to(0, 0); + rect.line_to(image.dim(1), 0); + rect.line_to(image.dim(1), image.dim(0)); + rect.line_to(0, image.dim(0)); + 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_allocator<agg::rgba8> color_span_alloc_type; + typedef agg::image_accessor_clip<pixfmt> image_accessor_type; + typedef agg::span_interpolator_linear<> interpolator_type; + typedef agg::span_image_filter_rgba_nn<image_accessor_type, interpolator_type> + image_span_gen_type; + typedef agg::span_converter<image_span_gen_type, span_conv_alpha> span_conv; + + color_span_alloc_type sa; + image_accessor_type ia(pixf, agg::rgba8(0, 0, 0, 0)); + interpolator_type interpolator(inv_mtx); + image_span_gen_type image_span_generator(ia, interpolator); + span_conv_alpha conv_alpha(alpha); + span_conv spans(image_span_generator, conv_alpha); + + typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type; + typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type; + typedef agg::renderer_scanline_aa<amask_ren_type, color_span_alloc_type, span_conv> + renderer_type_alpha; + + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + renderer_type_alpha ri(r, sa, spans); + + theRasterizer.add_path(rect2); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ri); + } else { + set_clipbox(gc.cliprect, rendererBase); + rendererBase.blend_from( + pixf, 0, (int)x, (int)(height - (y + image.dim(0))), (agg::int8u)(alpha * 255)); + } + + rendererBase.reset_clipping(true); +} + +template <class PathIterator, + class PathGenerator, + class TransformArray, + class OffsetArray, + class ColorArray, + class LineWidthArray, + class AntialiasedArray> +inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, + agg::trans_affine master_transform, + const agg::rect_d &cliprect, + PathIterator &clippath, + const agg::trans_affine &clippath_trans, + PathGenerator &path_generator, + TransformArray &transforms, + OffsetArray &offsets, + const agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds, + bool check_snap, + bool has_codes) +{ + typedef agg::conv_transform<typename PathGenerator::path_iterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removed_t; + typedef PathClipper<nan_removed_t> clipped_t; + typedef PathSnapper<clipped_t> snapped_t; + typedef agg::conv_curve<snapped_t> snapped_curve_t; + typedef agg::conv_curve<clipped_t> curve_t; + + size_t Npaths = path_generator.num_paths(); + size_t Noffsets = offsets.size(); + size_t N = std::max(Npaths, Noffsets); + + size_t Ntransforms = transforms.size(); + size_t Nfacecolors = facecolors.size(); + size_t Nedgecolors = edgecolors.size(); + size_t Nlinewidths = linewidths.size(); + size_t Nlinestyles = std::min(linestyles.size(), N); + size_t Naa = antialiaseds.size(); + + if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) { + return; + } + + // Handle any clipping globally + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(cliprect, theRasterizer); + bool has_clippath = render_clippath(clippath, clippath_trans, gc.snap_mode); + + // Set some defaults, assuming no face or edge + gc.linewidth = 0.0; + facepair_t face; + face.first = Nfacecolors != 0; + agg::trans_affine trans; + bool do_clip = !face.first && !gc.has_hatchpath(); + + for (int i = 0; i < (int)N; ++i) { + typename PathGenerator::path_iterator path = path_generator(i); + + if (Ntransforms) { + int it = i % Ntransforms; + trans = agg::trans_affine(transforms(it, 0, 0), + transforms(it, 1, 0), + transforms(it, 0, 1), + transforms(it, 1, 1), + transforms(it, 0, 2), + transforms(it, 1, 2)); + trans *= master_transform; + } else { + trans = master_transform; + } + + if (Noffsets) { + double xo = offsets(i % Noffsets, 0); + double yo = offsets(i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + trans *= agg::trans_affine_translation(xo, yo); + } + + // These transformations must be done post-offsets + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.0, (double)height); + + if (Nfacecolors) { + int ic = i % Nfacecolors; + face.second = agg::rgba(facecolors(ic, 0), facecolors(ic, 1), facecolors(ic, 2), facecolors(ic, 3)); + } + + if (Nedgecolors) { + int ic = i % Nedgecolors; + gc.color = agg::rgba(edgecolors(ic, 0), edgecolors(ic, 1), edgecolors(ic, 2), edgecolors(ic, 3)); + + if (Nlinewidths) { + gc.linewidth = linewidths(i % Nlinewidths); + } else { + gc.linewidth = 1.0; + } + if (Nlinestyles) { + gc.dashes = linestyles[i % Nlinestyles]; + } + } + + if (check_snap) { + gc.isaa = antialiaseds(i % Naa); + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, has_codes); + clipped_t clipped(nan_removed, do_clip, width, height); + snapped_t snapped( + clipped, gc.snap_mode, path.total_vertices(), points_to_pixels(gc.linewidth)); + if (has_codes) { + snapped_curve_t curve(snapped); + _draw_path(curve, has_clippath, face, gc); + } else { + _draw_path(snapped, has_clippath, face, gc); + } + } else { + gc.isaa = antialiaseds(i % Naa); + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, has_codes); + clipped_t clipped(nan_removed, do_clip, width, height); + if (has_codes) { + curve_t curve(clipped); + _draw_path(curve, has_clippath, face, gc); + } else { + _draw_path(clipped, has_clippath, face, gc); + } + } + } +} + +template <class PathGenerator, + class TransformArray, + class OffsetArray, + class ColorArray, + class LineWidthArray, + class AntialiasedArray> +inline void RendererAgg::draw_path_collection(GCAgg &gc, + agg::trans_affine &master_transform, + PathGenerator &path, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds) +{ + _draw_path_collection_generic(gc, + master_transform, + gc.cliprect, + gc.clippath.path, + gc.clippath.trans, + path, + transforms, + offsets, + offset_trans, + facecolors, + edgecolors, + linewidths, + linestyles, + antialiaseds, + true, + true); +} + +template <class CoordinateArray> +class QuadMeshGenerator +{ + unsigned m_meshWidth; + unsigned m_meshHeight; + CoordinateArray m_coordinates; + + class QuadMeshPathIterator + { + unsigned m_iterator; + unsigned m_m, m_n; + const CoordinateArray *m_coordinates; + + public: + QuadMeshPathIterator(unsigned m, unsigned n, const CoordinateArray *coordinates) + : m_iterator(0), m_m(m), m_n(n), m_coordinates(coordinates) + { + } + + private: + inline unsigned vertex(unsigned idx, double *x, double *y) + { + size_t m = m_m + ((idx & 0x2) >> 1); + size_t n = m_n + (((idx + 1) & 0x2) >> 1); + *x = (*m_coordinates)(n, m, 0); + *y = (*m_coordinates)(n, m, 1); + return (idx) ? agg::path_cmd_line_to : agg::path_cmd_move_to; + } + + public: + inline unsigned vertex(double *x, double *y) + { + if (m_iterator >= total_vertices()) { + return agg::path_cmd_stop; + } + return vertex(m_iterator++, x, y); + } + + inline void rewind(unsigned path_id) + { + m_iterator = path_id; + } + + inline unsigned total_vertices() + { + return 5; + } + + inline bool should_simplify() + { + return false; + } + }; + + public: + typedef QuadMeshPathIterator path_iterator; + + inline QuadMeshGenerator(unsigned meshWidth, unsigned meshHeight, CoordinateArray &coordinates) + : m_meshWidth(meshWidth), m_meshHeight(meshHeight), m_coordinates(coordinates) + { + } + + inline size_t num_paths() const + { + return (size_t) m_meshWidth * m_meshHeight; + } + + inline path_iterator operator()(size_t i) const + { + return QuadMeshPathIterator(i % m_meshWidth, i / m_meshWidth, &m_coordinates); + } +}; + +template <class CoordinateArray, class OffsetArray, class ColorArray> +inline void RendererAgg::draw_quad_mesh(GCAgg &gc, + agg::trans_affine &master_transform, + unsigned int mesh_width, + unsigned int mesh_height, + CoordinateArray &coordinates, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + bool antialiased, + ColorArray &edgecolors) +{ + QuadMeshGenerator<CoordinateArray> path_generator(mesh_width, mesh_height, coordinates); + + array::empty<double> transforms; + array::scalar<double, 1> linewidths(gc.linewidth); + array::scalar<uint8_t, 1> antialiaseds(antialiased); + DashesVector linestyles; + + _draw_path_collection_generic(gc, + master_transform, + gc.cliprect, + gc.clippath.path, + gc.clippath.trans, + path_generator, + transforms, + offsets, + offset_trans, + facecolors, + edgecolors, + linewidths, + linestyles, + antialiaseds, + true, // check_snap + false); +} + +template <class PointArray, class ColorArray> +inline void RendererAgg::_draw_gouraud_triangle(PointArray &points, + ColorArray &colors, + agg::trans_affine trans, + bool has_clippath) +{ + typedef agg::rgba8 color_t; + typedef agg::span_gouraud_rgba<color_t> span_gen_t; + typedef agg::span_allocator<color_t> span_alloc_t; + + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.0, (double)height); + + double tpoints[3][2]; + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 2; ++j) { + tpoints[i][j] = points(i, j); + } + trans.transform(&tpoints[i][0], &tpoints[i][1]); + if(std::isnan(tpoints[i][0]) || std::isnan(tpoints[i][1])) { + return; + } + } + + span_alloc_t span_alloc; + span_gen_t span_gen; + + span_gen.colors(agg::rgba(colors(0, 0), colors(0, 1), colors(0, 2), colors(0, 3)), + agg::rgba(colors(1, 0), colors(1, 1), colors(1, 2), colors(1, 3)), + agg::rgba(colors(2, 0), colors(2, 1), colors(2, 2), colors(2, 3))); + span_gen.triangle(tpoints[0][0], + tpoints[0][1], + tpoints[1][0], + tpoints[1][1], + tpoints[2][0], + tpoints[2][1], + 0.5); + + theRasterizer.add_path(span_gen); + + if (has_clippath) { + typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type; + typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type; + typedef agg::renderer_scanline_aa<amask_ren_type, span_alloc_t, span_gen_t> + amask_aa_renderer_type; + + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r, span_alloc, span_gen); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, span_alloc, span_gen); + } +} + +template <class PointArray, class ColorArray> +inline void RendererAgg::draw_gouraud_triangle(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans) +{ + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); + + _draw_gouraud_triangle(points, colors, trans, has_clippath); +} + +template <class PointArray, class ColorArray> +inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans) +{ + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); + + for (int i = 0; i < points.dim(0); ++i) { + typename PointArray::sub_t point = points.subarray(i); + typename ColorArray::sub_t color = colors.subarray(i); + + _draw_gouraud_triangle(point, color, trans, has_clippath); + } +} + +template <class R> +void RendererAgg::set_clipbox(const agg::rect_d &cliprect, R &rasterizer) +{ + // set the clip rectangle from the gc + + if (cliprect.x1 != 0.0 || cliprect.y1 != 0.0 || cliprect.x2 != 0.0 || cliprect.y2 != 0.0) { + rasterizer.clip_box(std::max(int(floor(cliprect.x1 + 0.5)), 0), + std::max(int(floor(height - cliprect.y1 + 0.5)), 0), + std::min(int(floor(cliprect.x2 + 0.5)), int(width)), + std::min(int(floor(height - cliprect.y2 + 0.5)), int(height))); + } else { + rasterizer.clip_box(0, 0, width, height); + } +} + +#endif diff --git a/contrib/python/matplotlib/py3/src/_backend_agg_basic_types.h b/contrib/python/matplotlib/py3/src/_backend_agg_basic_types.h new file mode 100644 index 0000000000..21a84bb6a5 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_backend_agg_basic_types.h @@ -0,0 +1,123 @@ +#ifndef MPL_BACKEND_AGG_BASIC_TYPES_H +#define MPL_BACKEND_AGG_BASIC_TYPES_H + +/* Contains some simple types from the Agg backend that are also used + by other modules */ + +#include <vector> + +#include "agg_color_rgba.h" +#include "agg_math_stroke.h" +#include "path_converters.h" + +#include "py_adaptors.h" + +struct ClipPath +{ + py::PathIterator path; + agg::trans_affine trans; +}; + +struct SketchParams +{ + double scale; + double length; + double randomness; +}; + +class Dashes +{ + typedef std::vector<std::pair<double, double> > dash_t; + double dash_offset; + dash_t dashes; + + public: + double get_dash_offset() const + { + return dash_offset; + } + void set_dash_offset(double x) + { + dash_offset = x; + } + void add_dash_pair(double length, double skip) + { + dashes.push_back(std::make_pair(length, skip)); + } + size_t size() const + { + return dashes.size(); + } + + template <class T> + void dash_to_stroke(T &stroke, double dpi, bool isaa) + { + double scaleddpi = dpi / 72.0; + for (dash_t::const_iterator i = dashes.begin(); i != dashes.end(); ++i) { + double val0 = i->first; + double val1 = i->second; + val0 = val0 * scaleddpi; + val1 = val1 * scaleddpi; + if (!isaa) { + val0 = (int)val0 + 0.5; + val1 = (int)val1 + 0.5; + } + stroke.add_dash(val0, val1); + } + stroke.dash_start(get_dash_offset() * scaleddpi); + } +}; + +typedef std::vector<Dashes> DashesVector; + +class GCAgg +{ + public: + GCAgg() + : linewidth(1.0), + alpha(1.0), + cap(agg::butt_cap), + join(agg::round_join), + snap_mode(SNAP_FALSE) + { + } + + ~GCAgg() + { + } + + double linewidth; + double alpha; + bool forced_alpha; + agg::rgba color; + bool isaa; + + agg::line_cap_e cap; + agg::line_join_e join; + + agg::rect_d cliprect; + + ClipPath clippath; + + Dashes dashes; + + e_snap_mode snap_mode; + + py::PathIterator hatchpath; + agg::rgba hatch_color; + double hatch_linewidth; + + SketchParams sketch; + + bool has_hatchpath() + { + return hatchpath.total_vertices() != 0; + } + + private: + // prevent copying + GCAgg(const GCAgg &); + GCAgg &operator=(const GCAgg &); +}; + +#endif diff --git a/contrib/python/matplotlib/py3/src/_backend_agg_wrapper.cpp b/contrib/python/matplotlib/py3/src/_backend_agg_wrapper.cpp new file mode 100644 index 0000000000..ee69729be7 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_backend_agg_wrapper.cpp @@ -0,0 +1,646 @@ +#include "mplutils.h" +#include "numpy_cpp.h" +#include "py_converters.h" +#include "_backend_agg.h" + +typedef struct +{ + PyObject_HEAD + RendererAgg *x; + Py_ssize_t shape[3]; + Py_ssize_t strides[3]; + Py_ssize_t suboffsets[3]; +} PyRendererAgg; + +static PyTypeObject PyRendererAggType; + +typedef struct +{ + PyObject_HEAD + BufferRegion *x; + Py_ssize_t shape[3]; + Py_ssize_t strides[3]; + Py_ssize_t suboffsets[3]; +} PyBufferRegion; + +static PyTypeObject PyBufferRegionType; + + +/********************************************************************** + * BufferRegion + * */ + +static PyObject *PyBufferRegion_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyBufferRegion *self; + self = (PyBufferRegion *)type->tp_alloc(type, 0); + self->x = NULL; + return (PyObject *)self; +} + +static void PyBufferRegion_dealloc(PyBufferRegion *self) +{ + delete self->x; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *PyBufferRegion_to_string(PyBufferRegion *self, PyObject *args) +{ + char const* msg = + "BufferRegion.to_string is deprecated since Matplotlib 3.7 and will " + "be removed two minor releases later; use np.asarray(region) instead."; + if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { + return NULL; + } + return PyBytes_FromStringAndSize((const char *)self->x->get_data(), + (Py_ssize_t) self->x->get_height() * self->x->get_stride()); +} + +/* TODO: This doesn't seem to be used internally. Remove? */ + +static PyObject *PyBufferRegion_set_x(PyBufferRegion *self, PyObject *args) +{ + int x; + if (!PyArg_ParseTuple(args, "i:set_x", &x)) { + return NULL; + } + self->x->get_rect().x1 = x; + + Py_RETURN_NONE; +} + +static PyObject *PyBufferRegion_set_y(PyBufferRegion *self, PyObject *args) +{ + int y; + if (!PyArg_ParseTuple(args, "i:set_y", &y)) { + return NULL; + } + self->x->get_rect().y1 = y; + + Py_RETURN_NONE; +} + +static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args) +{ + agg::rect_i rect = self->x->get_rect(); + + return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2); +} + +static PyObject *PyBufferRegion_to_string_argb(PyBufferRegion *self, PyObject *args) +{ + char const* msg = + "BufferRegion.to_string_argb is deprecated since Matplotlib 3.7 and " + "will be removed two minor releases later; use " + "np.take(region, [2, 1, 0, 3], axis=2) instead."; + if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { + return NULL; + } + PyObject *bufobj; + uint8_t *buf; + Py_ssize_t height, stride; + height = self->x->get_height(); + stride = self->x->get_stride(); + bufobj = PyBytes_FromStringAndSize(NULL, height * stride); + buf = (uint8_t *)PyBytes_AS_STRING(bufobj); + + CALL_CPP_CLEANUP("to_string_argb", (self->x->to_string_argb(buf)), Py_DECREF(bufobj)); + + return bufobj; +} + +int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) +{ + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = self->x->get_data(); + buf->len = (Py_ssize_t)self->x->get_width() * (Py_ssize_t)self->x->get_height() * 4; + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 3; + self->shape[0] = self->x->get_height(); + self->shape[1] = self->x->get_width(); + self->shape[2] = 4; + buf->shape = self->shape; + self->strides[0] = self->x->get_width() * 4; + self->strides[1] = 4; + self->strides[2] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyTypeObject *PyBufferRegion_init_type() +{ + static PyMethodDef methods[] = { + { "to_string", (PyCFunction)PyBufferRegion_to_string, METH_NOARGS, NULL }, + { "to_string_argb", (PyCFunction)PyBufferRegion_to_string_argb, METH_NOARGS, NULL }, + { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL }, + { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL }, + { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL }, + { NULL } + }; + + static PyBufferProcs buffer_procs; + buffer_procs.bf_getbuffer = (getbufferproc)PyBufferRegion_get_buffer; + + PyBufferRegionType.tp_name = "matplotlib.backends._backend_agg.BufferRegion"; + PyBufferRegionType.tp_basicsize = sizeof(PyBufferRegion); + PyBufferRegionType.tp_dealloc = (destructor)PyBufferRegion_dealloc; + PyBufferRegionType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + PyBufferRegionType.tp_methods = methods; + PyBufferRegionType.tp_new = PyBufferRegion_new; + PyBufferRegionType.tp_as_buffer = &buffer_procs; + + return &PyBufferRegionType; +} + +/********************************************************************** + * RendererAgg + * */ + +static PyObject *PyRendererAgg_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyRendererAgg *self; + self = (PyRendererAgg *)type->tp_alloc(type, 0); + self->x = NULL; + return (PyObject *)self; +} + +static int PyRendererAgg_init(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + unsigned int width; + unsigned int height; + double dpi; + int debug = 0; + + if (!PyArg_ParseTuple(args, "IId|i:RendererAgg", &width, &height, &dpi, &debug)) { + return -1; + } + + if (dpi <= 0.0) { + PyErr_SetString(PyExc_ValueError, "dpi must be positive"); + return -1; + } + + if (width >= 1 << 16 || height >= 1 << 16) { + PyErr_Format( + PyExc_ValueError, + "Image size of %dx%d pixels is too large. " + "It must be less than 2^16 in each direction.", + width, height); + return -1; + } + + CALL_CPP_INIT("RendererAgg", self->x = new RendererAgg(width, height, dpi)) + + return 0; +} + +static void PyRendererAgg_dealloc(PyRendererAgg *self) +{ + delete self->x; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *PyRendererAgg_draw_path(PyRendererAgg *self, PyObject *args) +{ + GCAgg gc; + py::PathIterator path; + agg::trans_affine trans; + PyObject *faceobj = NULL; + agg::rgba face; + + if (!PyArg_ParseTuple(args, + "O&O&O&|O:draw_path", + &convert_gcagg, + &gc, + &convert_path, + &path, + &convert_trans_affine, + &trans, + &faceobj)) { + return NULL; + } + + if (!convert_face(faceobj, gc, &face)) { + return NULL; + } + + CALL_CPP("draw_path", (self->x->draw_path(gc, path, trans, face))); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_draw_text_image(PyRendererAgg *self, PyObject *args) +{ + numpy::array_view<agg::int8u, 2> image; + double x; + double y; + double angle; + GCAgg gc; + + if (!PyArg_ParseTuple(args, + "O&dddO&:draw_text_image", + &image.converter_contiguous, + &image, + &x, + &y, + &angle, + &convert_gcagg, + &gc)) { + return NULL; + } + + CALL_CPP("draw_text_image", (self->x->draw_text_image(gc, image, x, y, angle))); + + Py_RETURN_NONE; +} + +PyObject *PyRendererAgg_draw_markers(PyRendererAgg *self, PyObject *args) +{ + GCAgg gc; + py::PathIterator marker_path; + agg::trans_affine marker_path_trans; + py::PathIterator path; + agg::trans_affine trans; + PyObject *faceobj = NULL; + agg::rgba face; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&O&|O:draw_markers", + &convert_gcagg, + &gc, + &convert_path, + &marker_path, + &convert_trans_affine, + &marker_path_trans, + &convert_path, + &path, + &convert_trans_affine, + &trans, + &faceobj)) { + return NULL; + } + + if (!convert_face(faceobj, gc, &face)) { + return NULL; + } + + CALL_CPP("draw_markers", + (self->x->draw_markers(gc, marker_path, marker_path_trans, path, trans, face))); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_draw_image(PyRendererAgg *self, PyObject *args) +{ + GCAgg gc; + double x; + double y; + numpy::array_view<agg::int8u, 3> image; + + if (!PyArg_ParseTuple(args, + "O&ddO&:draw_image", + &convert_gcagg, + &gc, + &x, + &y, + &image.converter_contiguous, + &image)) { + return NULL; + } + + x = mpl_round(x); + y = mpl_round(y); + + gc.alpha = 1.0; + CALL_CPP("draw_image", (self->x->draw_image(gc, x, y, image))); + + Py_RETURN_NONE; +} + +static PyObject * +PyRendererAgg_draw_path_collection(PyRendererAgg *self, PyObject *args) +{ + GCAgg gc; + agg::trans_affine master_transform; + py::PathGenerator paths; + numpy::array_view<const double, 3> transforms; + numpy::array_view<const double, 2> offsets; + agg::trans_affine offset_trans; + numpy::array_view<const double, 2> facecolors; + numpy::array_view<const double, 2> edgecolors; + numpy::array_view<const double, 1> linewidths; + DashesVector dashes; + numpy::array_view<const uint8_t, 1> antialiaseds; + PyObject *ignored; + PyObject *offset_position; // offset position is no longer used + + if (!PyArg_ParseTuple(args, + "O&O&O&O&O&O&O&O&O&O&O&OO:draw_path_collection", + &convert_gcagg, + &gc, + &convert_trans_affine, + &master_transform, + &convert_pathgen, + &paths, + &convert_transforms, + &transforms, + &convert_points, + &offsets, + &convert_trans_affine, + &offset_trans, + &convert_colors, + &facecolors, + &convert_colors, + &edgecolors, + &linewidths.converter, + &linewidths, + &convert_dashes_vector, + &dashes, + &antialiaseds.converter, + &antialiaseds, + &ignored, + &offset_position)) { + return NULL; + } + + CALL_CPP("draw_path_collection", + (self->x->draw_path_collection(gc, + master_transform, + paths, + transforms, + offsets, + offset_trans, + facecolors, + edgecolors, + linewidths, + dashes, + antialiaseds))); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *args) +{ + GCAgg gc; + agg::trans_affine master_transform; + unsigned int mesh_width; + unsigned int mesh_height; + numpy::array_view<const double, 3> coordinates; + numpy::array_view<const double, 2> offsets; + agg::trans_affine offset_trans; + numpy::array_view<const double, 2> facecolors; + bool antialiased; + numpy::array_view<const double, 2> edgecolors; + + if (!PyArg_ParseTuple(args, + "O&O&IIO&O&O&O&O&O&:draw_quad_mesh", + &convert_gcagg, + &gc, + &convert_trans_affine, + &master_transform, + &mesh_width, + &mesh_height, + &coordinates.converter, + &coordinates, + &convert_points, + &offsets, + &convert_trans_affine, + &offset_trans, + &convert_colors, + &facecolors, + &convert_bool, + &antialiased, + &convert_colors, + &edgecolors)) { + return NULL; + } + + CALL_CPP("draw_quad_mesh", + (self->x->draw_quad_mesh(gc, + master_transform, + mesh_width, + mesh_height, + coordinates, + offsets, + offset_trans, + facecolors, + antialiased, + edgecolors))); + + Py_RETURN_NONE; +} + +static PyObject * +PyRendererAgg_draw_gouraud_triangle(PyRendererAgg *self, PyObject *args) +{ + GCAgg gc; + numpy::array_view<const double, 2> points; + numpy::array_view<const double, 2> colors; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&|O:draw_gouraud_triangle", + &convert_gcagg, + &gc, + &points.converter, + &points, + &colors.converter, + &colors, + &convert_trans_affine, + &trans)) { + return NULL; + } + + if (points.dim(0) != 3 || points.dim(1) != 2) { + PyErr_Format(PyExc_ValueError, + "points must have shape (3, 2), " + "got (%" NPY_INTP_FMT ", %" NPY_INTP_FMT ")", + points.dim(0), points.dim(1)); + return NULL; + } + + if (colors.dim(0) != 3 || colors.dim(1) != 4) { + PyErr_Format(PyExc_ValueError, + "colors must have shape (3, 4), " + "got (%" NPY_INTP_FMT ", %" NPY_INTP_FMT ")", + colors.dim(0), colors.dim(1)); + return NULL; + } + + + CALL_CPP("draw_gouraud_triangle", (self->x->draw_gouraud_triangle(gc, points, colors, trans))); + + Py_RETURN_NONE; +} + +static PyObject * +PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args) +{ + GCAgg gc; + numpy::array_view<const double, 3> points; + numpy::array_view<const double, 3> colors; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&|O:draw_gouraud_triangles", + &convert_gcagg, + &gc, + &points.converter, + &points, + &colors.converter, + &colors, + &convert_trans_affine, + &trans)) { + return NULL; + } + if (points.size() && !check_trailing_shape(points, "points", 3, 2)) { + return NULL; + } + if (colors.size() && !check_trailing_shape(colors, "colors", 3, 4)) { + return NULL; + } + if (points.size() != colors.size()) { + PyErr_Format(PyExc_ValueError, + "points and colors arrays must be the same length, got " + "%" NPY_INTP_FMT " points and %" NPY_INTP_FMT "colors", + points.dim(0), colors.dim(0)); + return NULL; + } + + CALL_CPP("draw_gouraud_triangles", self->x->draw_gouraud_triangles(gc, points, colors, trans)); + + Py_RETURN_NONE; +} + +int PyRendererAgg_get_buffer(PyRendererAgg *self, Py_buffer *buf, int flags) +{ + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = self->x->pixBuffer; + buf->len = (Py_ssize_t)self->x->get_width() * (Py_ssize_t)self->x->get_height() * 4; + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 3; + self->shape[0] = self->x->get_height(); + self->shape[1] = self->x->get_width(); + self->shape[2] = 4; + buf->shape = self->shape; + self->strides[0] = self->x->get_width() * 4; + self->strides[1] = 4; + self->strides[2] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyObject *PyRendererAgg_clear(PyRendererAgg *self, PyObject *args) +{ + CALL_CPP("clear", self->x->clear()); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_copy_from_bbox(PyRendererAgg *self, PyObject *args) +{ + agg::rect_d bbox; + BufferRegion *reg; + PyObject *regobj; + + if (!PyArg_ParseTuple(args, "O&:copy_from_bbox", &convert_rect, &bbox)) { + return 0; + } + + CALL_CPP("copy_from_bbox", (reg = self->x->copy_from_bbox(bbox))); + + regobj = PyBufferRegion_new(&PyBufferRegionType, NULL, NULL); + ((PyBufferRegion *)regobj)->x = reg; + + return regobj; +} + +static PyObject *PyRendererAgg_restore_region(PyRendererAgg *self, PyObject *args) +{ + PyBufferRegion *regobj; + int xx1 = 0, yy1 = 0, xx2 = 0, yy2 = 0, x = 0, y = 0; + + if (!PyArg_ParseTuple(args, + "O!|iiiiii:restore_region", + &PyBufferRegionType, + ®obj, + &xx1, + &yy1, + &xx2, + &yy2, + &x, + &y)) { + return 0; + } + + if (PySequence_Size(args) == 1) { + CALL_CPP("restore_region", self->x->restore_region(*(regobj->x))); + } else { + CALL_CPP("restore_region", self->x->restore_region(*(regobj->x), xx1, yy1, xx2, yy2, x, y)); + } + + Py_RETURN_NONE; +} + +static PyTypeObject *PyRendererAgg_init_type() +{ + static PyMethodDef methods[] = { + {"draw_path", (PyCFunction)PyRendererAgg_draw_path, METH_VARARGS, NULL}, + {"draw_markers", (PyCFunction)PyRendererAgg_draw_markers, METH_VARARGS, NULL}, + {"draw_text_image", (PyCFunction)PyRendererAgg_draw_text_image, METH_VARARGS, NULL}, + {"draw_image", (PyCFunction)PyRendererAgg_draw_image, METH_VARARGS, NULL}, + {"draw_path_collection", (PyCFunction)PyRendererAgg_draw_path_collection, METH_VARARGS, NULL}, + {"draw_quad_mesh", (PyCFunction)PyRendererAgg_draw_quad_mesh, METH_VARARGS, NULL}, + {"draw_gouraud_triangle", (PyCFunction)PyRendererAgg_draw_gouraud_triangle, METH_VARARGS, NULL}, + {"draw_gouraud_triangles", (PyCFunction)PyRendererAgg_draw_gouraud_triangles, METH_VARARGS, NULL}, + + {"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL}, + + {"copy_from_bbox", (PyCFunction)PyRendererAgg_copy_from_bbox, METH_VARARGS, NULL}, + {"restore_region", (PyCFunction)PyRendererAgg_restore_region, METH_VARARGS, NULL}, + {NULL} + }; + + static PyBufferProcs buffer_procs; + buffer_procs.bf_getbuffer = (getbufferproc)PyRendererAgg_get_buffer; + + PyRendererAggType.tp_name = "matplotlib.backends._backend_agg.RendererAgg"; + PyRendererAggType.tp_basicsize = sizeof(PyRendererAgg); + PyRendererAggType.tp_dealloc = (destructor)PyRendererAgg_dealloc; + PyRendererAggType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + PyRendererAggType.tp_methods = methods; + PyRendererAggType.tp_init = (initproc)PyRendererAgg_init; + PyRendererAggType.tp_new = PyRendererAgg_new; + PyRendererAggType.tp_as_buffer = &buffer_procs; + + return &PyRendererAggType; +} + +static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_backend_agg" }; + +PyMODINIT_FUNC PyInit__backend_agg(void) +{ + import_array(); + PyObject *m; + if (!(m = PyModule_Create(&moduledef)) + || prepare_and_add_type(PyRendererAgg_init_type(), m) + // BufferRegion is not constructible from Python, thus not added to the module. + || PyType_Ready(PyBufferRegion_init_type()) + ) { + Py_XDECREF(m); + return NULL; + } + return m; +} diff --git a/contrib/python/matplotlib/py3/src/_c_internal_utils.c b/contrib/python/matplotlib/py3/src/_c_internal_utils.c new file mode 100644 index 0000000000..f1bd22a42c --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_c_internal_utils.c @@ -0,0 +1,211 @@ +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#ifdef __linux__ +#include <dlfcn.h> +#endif +#ifdef _WIN32 +#include <Objbase.h> +#include <Shobjidl.h> +#include <Windows.h> +#endif + +static PyObject* +mpl_display_is_valid(PyObject* module) +{ +#ifdef __linux__ + void* libX11; + // The getenv check is redundant but helps performance as it is much faster + // than dlopen(). + if (getenv("DISPLAY") + && (libX11 = dlopen("libX11.so.6", RTLD_LAZY))) { + struct Display* display = NULL; + struct Display* (* XOpenDisplay)(char const*) = + dlsym(libX11, "XOpenDisplay"); + int (* XCloseDisplay)(struct Display*) = + dlsym(libX11, "XCloseDisplay"); + if (XOpenDisplay && XCloseDisplay + && (display = XOpenDisplay(NULL))) { + XCloseDisplay(display); + } + if (dlclose(libX11)) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + return NULL; + } + if (display) { + Py_RETURN_TRUE; + } + } + void* libwayland_client; + if (getenv("WAYLAND_DISPLAY") + && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { + struct wl_display* display = NULL; + struct wl_display* (* wl_display_connect)(char const*) = + dlsym(libwayland_client, "wl_display_connect"); + void (* wl_display_disconnect)(struct wl_display*) = + dlsym(libwayland_client, "wl_display_disconnect"); + if (wl_display_connect && wl_display_disconnect + && (display = wl_display_connect(NULL))) { + wl_display_disconnect(display); + } + if (dlclose(libwayland_client)) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + return NULL; + } + if (display) { + Py_RETURN_TRUE; + } + } + Py_RETURN_FALSE; +#else + Py_RETURN_TRUE; +#endif +} + +static PyObject* +mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module) +{ +#ifdef _WIN32 + wchar_t* appid = NULL; + HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid); + if (FAILED(hr)) { + return PyErr_SetFromWindowsErr(hr); + } + PyObject* py_appid = PyUnicode_FromWideChar(appid, -1); + CoTaskMemFree(appid); + return py_appid; +#else + Py_RETURN_NONE; +#endif +} + +static PyObject* +mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg) +{ +#ifdef _WIN32 + wchar_t* appid = PyUnicode_AsWideCharString(arg, NULL); + if (!appid) { + return NULL; + } + HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid); + PyMem_Free(appid); + if (FAILED(hr)) { + return PyErr_SetFromWindowsErr(hr); + } + Py_RETURN_NONE; +#else + Py_RETURN_NONE; +#endif +} + +static PyObject* +mpl_GetForegroundWindow(PyObject* module) +{ +#ifdef _WIN32 + return PyLong_FromVoidPtr(GetForegroundWindow()); +#else + Py_RETURN_NONE; +#endif +} + +static PyObject* +mpl_SetForegroundWindow(PyObject* module, PyObject *arg) +{ +#ifdef _WIN32 + HWND handle = PyLong_AsVoidPtr(arg); + if (PyErr_Occurred()) { + return NULL; + } + if (!SetForegroundWindow(handle)) { + return PyErr_Format(PyExc_RuntimeError, "Error setting window"); + } + Py_RETURN_NONE; +#else + Py_RETURN_NONE; +#endif +} + +static PyObject* +mpl_SetProcessDpiAwareness_max(PyObject* module) +{ +#ifdef _WIN32 +#ifdef _DPI_AWARENESS_CONTEXTS_ + // These functions and options were added in later Windows 10 updates, so + // must be loaded dynamically. + typedef BOOL (WINAPI *IsValidDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); + typedef BOOL (WINAPI *SetProcessDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); + + HMODULE user32 = LoadLibrary("user32.dll"); + IsValidDpiAwarenessContext_t IsValidDpiAwarenessContextPtr = + (IsValidDpiAwarenessContext_t)GetProcAddress( + user32, "IsValidDpiAwarenessContext"); + SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContextPtr = + (SetProcessDpiAwarenessContext_t)GetProcAddress( + user32, "SetProcessDpiAwarenessContext"); + DPI_AWARENESS_CONTEXT ctxs[3] = { + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, // Win10 Creators Update + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, // Win10 + DPI_AWARENESS_CONTEXT_SYSTEM_AWARE}; // Win10 + if (IsValidDpiAwarenessContextPtr != NULL + && SetProcessDpiAwarenessContextPtr != NULL) { + for (int i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { + if (IsValidDpiAwarenessContextPtr(ctxs[i])) { + SetProcessDpiAwarenessContextPtr(ctxs[i]); + break; + } + } + } else { + // Added in Windows Vista. + SetProcessDPIAware(); + } + FreeLibrary(user32); +#else + // Added in Windows Vista. + SetProcessDPIAware(); +#endif +#endif + Py_RETURN_NONE; +} + +static PyMethodDef functions[] = { + {"display_is_valid", (PyCFunction)mpl_display_is_valid, METH_NOARGS, + "display_is_valid()\n--\n\n" + "Check whether the current X11 or Wayland display is valid.\n\n" + "On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL)\n" + "succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL)\n" + "succeeds.\n\n" + "On other platforms, always returns True."}, + {"Win32_GetCurrentProcessExplicitAppUserModelID", + (PyCFunction)mpl_GetCurrentProcessExplicitAppUserModelID, METH_NOARGS, + "Win32_GetCurrentProcessExplicitAppUserModelID()\n--\n\n" + "Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID.\n\n" + "On non-Windows platforms, always returns None."}, + {"Win32_SetCurrentProcessExplicitAppUserModelID", + (PyCFunction)mpl_SetCurrentProcessExplicitAppUserModelID, METH_O, + "Win32_SetCurrentProcessExplicitAppUserModelID(appid, /)\n--\n\n" + "Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID.\n\n" + "On non-Windows platforms, does nothing."}, + {"Win32_GetForegroundWindow", + (PyCFunction)mpl_GetForegroundWindow, METH_NOARGS, + "Win32_GetForegroundWindow()\n--\n\n" + "Wrapper for Windows' GetForegroundWindow.\n\n" + "On non-Windows platforms, always returns None."}, + {"Win32_SetForegroundWindow", + (PyCFunction)mpl_SetForegroundWindow, METH_O, + "Win32_SetForegroundWindow(hwnd, /)\n--\n\n" + "Wrapper for Windows' SetForegroundWindow.\n\n" + "On non-Windows platforms, does nothing."}, + {"Win32_SetProcessDpiAwareness_max", + (PyCFunction)mpl_SetProcessDpiAwareness_max, METH_NOARGS, + "Win32_SetProcessDpiAwareness_max()\n--\n\n" + "Set Windows' process DPI awareness to best option available.\n\n" + "On non-Windows platforms, does nothing."}, + {NULL, NULL}}; // sentinel. +static PyModuleDef util_module = { + PyModuleDef_HEAD_INIT, "_c_internal_utils", NULL, 0, functions +}; + +#pragma GCC visibility push(default) +PyMODINIT_FUNC PyInit__c_internal_utils(void) +{ + return PyModule_Create(&util_module); +} diff --git a/contrib/python/matplotlib/py3/src/_image_resample.h b/contrib/python/matplotlib/py3/src/_image_resample.h new file mode 100644 index 0000000000..10763fb01d --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_image_resample.h @@ -0,0 +1,832 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef MPL_RESAMPLE_H +#define MPL_RESAMPLE_H + +#include "agg_image_accessors.h" +#include "agg_path_storage.h" +#include "agg_pixfmt_gray.h" +#include "agg_pixfmt_rgb.h" +#include "agg_pixfmt_rgba.h" +#include "agg_renderer_base.h" +#include "agg_renderer_scanline.h" +#include "agg_rasterizer_scanline_aa.h" +#include "agg_scanline_u.h" +#include "agg_span_allocator.h" +#include "agg_span_converter.h" +#include "agg_span_image_filter_gray.h" +#include "agg_span_image_filter_rgba.h" +#include "agg_span_interpolator_adaptor.h" +#include "agg_span_interpolator_linear.h" + +#include "agg_workaround.h" + +// Based on: + +//---------------------------------------------------------------------------- +// Anti-Grain Geometry - Version 2.4 +// Copyright (C) 2002-2005 Maxim Shemanarev (http://antigrain.com/) +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all copies. +// This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +//---------------------------------------------------------------------------- +// Contact: mcseem@antigrain.com +// mcseemagg@yahoo.com +// http://antigrain.com/ +//---------------------------------------------------------------------------- +// +// Adaptation for high precision colors has been sponsored by +// Liberty Technology Systems, Inc., visit http://lib-sys.com +// +// Liberty Technology Systems, Inc. is the provider of +// PostScript and PDF technology for software developers. +// + +//===================================================================gray64 +namespace agg +{ + struct gray64 + { + typedef double value_type; + typedef double calc_type; + typedef double long_type; + typedef gray64 self_type; + + value_type v; + value_type a; + + //-------------------------------------------------------------------- + gray64() {} + + //-------------------------------------------------------------------- + explicit gray64(value_type v_, value_type a_ = 1) : + v(v_), a(a_) {} + + //-------------------------------------------------------------------- + gray64(const self_type& c, value_type a_) : + v(c.v), a(a_) {} + + //-------------------------------------------------------------------- + gray64(const gray64& c) : + v(c.v), + a(c.a) {} + + //-------------------------------------------------------------------- + static AGG_INLINE double to_double(value_type a) + { + return a; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type from_double(double a) + { + return value_type(a); + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type empty_value() + { + return 0; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type full_value() + { + return 1; + } + + //-------------------------------------------------------------------- + AGG_INLINE bool is_transparent() const + { + return a <= 0; + } + + //-------------------------------------------------------------------- + AGG_INLINE bool is_opaque() const + { + return a >= 1; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type invert(value_type x) + { + return 1 - x; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type multiply(value_type a, value_type b) + { + return value_type(a * b); + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type demultiply(value_type a, value_type b) + { + return (b == 0) ? 0 : value_type(a / b); + } + + //-------------------------------------------------------------------- + template<typename T> + static AGG_INLINE T downscale(T a) + { + return a; + } + + //-------------------------------------------------------------------- + template<typename T> + static AGG_INLINE T downshift(T a, unsigned n) + { + return n > 0 ? a / (1 << n) : a; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type mult_cover(value_type a, cover_type b) + { + return value_type(a * b / cover_mask); + } + + //-------------------------------------------------------------------- + static AGG_INLINE cover_type scale_cover(cover_type a, value_type b) + { + return cover_type(uround(a * b)); + } + + //-------------------------------------------------------------------- + // Interpolate p to q by a, assuming q is premultiplied by a. + static AGG_INLINE value_type prelerp(value_type p, value_type q, value_type a) + { + return (1 - a) * p + q; // more accurate than "p + q - p * a" + } + + //-------------------------------------------------------------------- + // Interpolate p to q by a. + static AGG_INLINE value_type lerp(value_type p, value_type q, value_type a) + { + // The form "p + a * (q - p)" avoids a multiplication, but may produce an + // inaccurate result. For example, "p + (q - p)" may not be exactly equal + // to q. Therefore, stick to the basic expression, which at least produces + // the correct result at either extreme. + return (1 - a) * p + a * q; + } + + //-------------------------------------------------------------------- + self_type& clear() + { + v = a = 0; + return *this; + } + + //-------------------------------------------------------------------- + self_type& transparent() + { + a = 0; + return *this; + } + + //-------------------------------------------------------------------- + self_type& opacity(double a_) + { + if (a_ < 0) a = 0; + else if (a_ > 1) a = 1; + else a = value_type(a_); + return *this; + } + + //-------------------------------------------------------------------- + double opacity() const + { + return a; + } + + + //-------------------------------------------------------------------- + self_type& premultiply() + { + if (a < 0) v = 0; + else if(a < 1) v *= a; + return *this; + } + + //-------------------------------------------------------------------- + self_type& demultiply() + { + if (a < 0) v = 0; + else if (a < 1) v /= a; + return *this; + } + + //-------------------------------------------------------------------- + self_type gradient(self_type c, double k) const + { + return self_type( + value_type(v + (c.v - v) * k), + value_type(a + (c.a - a) * k)); + } + + //-------------------------------------------------------------------- + static self_type no_color() { return self_type(0,0); } + }; + + + //====================================================================rgba32 + struct rgba64 + { + typedef double value_type; + typedef double calc_type; + typedef double long_type; + typedef rgba64 self_type; + + value_type r; + value_type g; + value_type b; + value_type a; + + //-------------------------------------------------------------------- + rgba64() {} + + //-------------------------------------------------------------------- + rgba64(value_type r_, value_type g_, value_type b_, value_type a_= 1) : + r(r_), g(g_), b(b_), a(a_) {} + + //-------------------------------------------------------------------- + rgba64(const self_type& c, float a_) : + r(c.r), g(c.g), b(c.b), a(a_) {} + + //-------------------------------------------------------------------- + rgba64(const rgba& c) : + r(value_type(c.r)), g(value_type(c.g)), b(value_type(c.b)), a(value_type(c.a)) {} + + //-------------------------------------------------------------------- + operator rgba() const + { + return rgba(r, g, b, a); + } + + //-------------------------------------------------------------------- + static AGG_INLINE double to_double(value_type a) + { + return a; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type from_double(double a) + { + return value_type(a); + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type empty_value() + { + return 0; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type full_value() + { + return 1; + } + + //-------------------------------------------------------------------- + AGG_INLINE bool is_transparent() const + { + return a <= 0; + } + + //-------------------------------------------------------------------- + AGG_INLINE bool is_opaque() const + { + return a >= 1; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type invert(value_type x) + { + return 1 - x; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type multiply(value_type a, value_type b) + { + return value_type(a * b); + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type demultiply(value_type a, value_type b) + { + return (b == 0) ? 0 : value_type(a / b); + } + + //-------------------------------------------------------------------- + template<typename T> + static AGG_INLINE T downscale(T a) + { + return a; + } + + //-------------------------------------------------------------------- + template<typename T> + static AGG_INLINE T downshift(T a, unsigned n) + { + return n > 0 ? a / (1 << n) : a; + } + + //-------------------------------------------------------------------- + static AGG_INLINE value_type mult_cover(value_type a, cover_type b) + { + return value_type(a * b / cover_mask); + } + + //-------------------------------------------------------------------- + static AGG_INLINE cover_type scale_cover(cover_type a, value_type b) + { + return cover_type(uround(a * b)); + } + + //-------------------------------------------------------------------- + // Interpolate p to q by a, assuming q is premultiplied by a. + static AGG_INLINE value_type prelerp(value_type p, value_type q, value_type a) + { + return (1 - a) * p + q; // more accurate than "p + q - p * a" + } + + //-------------------------------------------------------------------- + // Interpolate p to q by a. + static AGG_INLINE value_type lerp(value_type p, value_type q, value_type a) + { + // The form "p + a * (q - p)" avoids a multiplication, but may produce an + // inaccurate result. For example, "p + (q - p)" may not be exactly equal + // to q. Therefore, stick to the basic expression, which at least produces + // the correct result at either extreme. + return (1 - a) * p + a * q; + } + + //-------------------------------------------------------------------- + self_type& clear() + { + r = g = b = a = 0; + return *this; + } + + //-------------------------------------------------------------------- + self_type& transparent() + { + a = 0; + return *this; + } + + //-------------------------------------------------------------------- + AGG_INLINE self_type& opacity(double a_) + { + if (a_ < 0) a = 0; + else if (a_ > 1) a = 1; + else a = value_type(a_); + return *this; + } + + //-------------------------------------------------------------------- + double opacity() const + { + return a; + } + + //-------------------------------------------------------------------- + AGG_INLINE self_type& premultiply() + { + if (a < 1) + { + if (a <= 0) + { + r = g = b = 0; + } + else + { + r *= a; + g *= a; + b *= a; + } + } + return *this; + } + + //-------------------------------------------------------------------- + AGG_INLINE self_type& demultiply() + { + if (a < 1) + { + if (a <= 0) + { + r = g = b = 0; + } + else + { + r /= a; + g /= a; + b /= a; + } + } + return *this; + } + + //-------------------------------------------------------------------- + AGG_INLINE self_type gradient(const self_type& c, double k) const + { + self_type ret; + ret.r = value_type(r + (c.r - r) * k); + ret.g = value_type(g + (c.g - g) * k); + ret.b = value_type(b + (c.b - b) * k); + ret.a = value_type(a + (c.a - a) * k); + return ret; + } + + //-------------------------------------------------------------------- + AGG_INLINE void add(const self_type& c, unsigned cover) + { + if (cover == cover_mask) + { + if (c.is_opaque()) + { + *this = c; + return; + } + else + { + r += c.r; + g += c.g; + b += c.b; + a += c.a; + } + } + else + { + r += mult_cover(c.r, cover); + g += mult_cover(c.g, cover); + b += mult_cover(c.b, cover); + a += mult_cover(c.a, cover); + } + if (a > 1) a = 1; + if (r > a) r = a; + if (g > a) g = a; + if (b > a) b = a; + } + + //-------------------------------------------------------------------- + static self_type no_color() { return self_type(0,0,0,0); } + }; +} + + +typedef enum { + NEAREST, + BILINEAR, + BICUBIC, + SPLINE16, + SPLINE36, + HANNING, + HAMMING, + HERMITE, + KAISER, + QUADRIC, + CATROM, + GAUSSIAN, + BESSEL, + MITCHELL, + SINC, + LANCZOS, + BLACKMAN, + _n_interpolation +} interpolation_e; + + +// T is rgba if and only if it has an T::r field. +template<typename T, typename = void> struct is_grayscale : std::true_type {}; +template<typename T> struct is_grayscale<T, decltype(T::r, void())> : std::false_type {}; + + +template<typename color_type> +struct type_mapping +{ + using blender_type = typename std::conditional< + is_grayscale<color_type>::value, + agg::blender_gray<color_type>, + typename std::conditional< + std::is_same<color_type, agg::rgba8>::value, + fixed_blender_rgba_plain<color_type, agg::order_rgba>, + agg::blender_rgba_plain<color_type, agg::order_rgba> + >::type + >::type; + using pixfmt_type = typename std::conditional< + is_grayscale<color_type>::value, + agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer>, + agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer> + >::type; + using pixfmt_pre_type = typename std::conditional< + is_grayscale<color_type>::value, + pixfmt_type, + agg::pixfmt_alpha_blend_rgba< + typename std::conditional< + std::is_same<color_type, agg::rgba8>::value, + fixed_blender_rgba_pre<color_type, agg::order_rgba>, + agg::blender_rgba_pre<color_type, agg::order_rgba> + >::type, + agg::rendering_buffer> + >::type; + template<typename A> using span_gen_affine_type = typename std::conditional< + is_grayscale<color_type>::value, + agg::span_image_resample_gray_affine<A>, + agg::span_image_resample_rgba_affine<A> + >::type; + template<typename A, typename B> using span_gen_filter_type = typename std::conditional< + is_grayscale<color_type>::value, + agg::span_image_filter_gray<A, B>, + agg::span_image_filter_rgba<A, B> + >::type; + template<typename A, typename B> using span_gen_nn_type = typename std::conditional< + is_grayscale<color_type>::value, + agg::span_image_filter_gray_nn<A, B>, + agg::span_image_filter_rgba_nn<A, B> + >::type; +}; + + +template<typename color_type> +class span_conv_alpha +{ +public: + span_conv_alpha(const double alpha) : + m_alpha(alpha) + { + } + + void prepare() {} + + void generate(color_type* span, int x, int y, unsigned len) const + { + if (m_alpha != 1.0) { + do { + span->a *= m_alpha; + ++span; + } while (--len); + } + } +private: + + const double m_alpha; +}; + + +/* A class to use a lookup table for a transformation */ +class lookup_distortion +{ +public: + lookup_distortion(const double *mesh, int in_width, int in_height, + int out_width, int out_height) : + m_mesh(mesh), + m_in_width(in_width), + m_in_height(in_height), + m_out_width(out_width), + m_out_height(out_height) + {} + + void calculate(int* x, int* y) { + if (m_mesh) { + double dx = double(*x) / agg::image_subpixel_scale; + double dy = double(*y) / agg::image_subpixel_scale; + if (dx >= 0 && dx < m_out_width && + dy >= 0 && dy < m_out_height) { + const double *coord = m_mesh + (int(dy) * m_out_width + int(dx)) * 2; + *x = int(coord[0] * agg::image_subpixel_scale); + *y = int(coord[1] * agg::image_subpixel_scale); + } + } + } + +protected: + const double *m_mesh; + int m_in_width; + int m_in_height; + int m_out_width; + int m_out_height; +}; + + +struct resample_params_t { + interpolation_e interpolation; + bool is_affine; + agg::trans_affine affine; + const double *transform_mesh; + bool resample; + bool norm; + double radius; + double alpha; +}; + + +static void get_filter(const resample_params_t ¶ms, + agg::image_filter_lut &filter) +{ + switch (params.interpolation) { + case NEAREST: + case _n_interpolation: + // Never should get here. Here to silence compiler warnings. + break; + + case HANNING: + filter.calculate(agg::image_filter_hanning(), params.norm); + break; + + case HAMMING: + filter.calculate(agg::image_filter_hamming(), params.norm); + break; + + case HERMITE: + filter.calculate(agg::image_filter_hermite(), params.norm); + break; + + case BILINEAR: + filter.calculate(agg::image_filter_bilinear(), params.norm); + break; + + case BICUBIC: + filter.calculate(agg::image_filter_bicubic(), params.norm); + break; + + case SPLINE16: + filter.calculate(agg::image_filter_spline16(), params.norm); + break; + + case SPLINE36: + filter.calculate(agg::image_filter_spline36(), params.norm); + break; + + case KAISER: + filter.calculate(agg::image_filter_kaiser(), params.norm); + break; + + case QUADRIC: + filter.calculate(agg::image_filter_quadric(), params.norm); + break; + + case CATROM: + filter.calculate(agg::image_filter_catrom(), params.norm); + break; + + case GAUSSIAN: + filter.calculate(agg::image_filter_gaussian(), params.norm); + break; + + case BESSEL: + filter.calculate(agg::image_filter_bessel(), params.norm); + break; + + case MITCHELL: + filter.calculate(agg::image_filter_mitchell(), params.norm); + break; + + case SINC: + filter.calculate(agg::image_filter_sinc(params.radius), params.norm); + break; + + case LANCZOS: + filter.calculate(agg::image_filter_lanczos(params.radius), params.norm); + break; + + case BLACKMAN: + filter.calculate(agg::image_filter_blackman(params.radius), params.norm); + break; + } +} + + +template<typename color_type> +void resample( + const void *input, int in_width, int in_height, + void *output, int out_width, int out_height, + resample_params_t ¶ms) +{ + using type_mapping_t = type_mapping<color_type>; + + using input_pixfmt_t = typename type_mapping_t::pixfmt_type; + using output_pixfmt_t = typename type_mapping_t::pixfmt_type; + + using renderer_t = agg::renderer_base<output_pixfmt_t>; + using rasterizer_t = agg::rasterizer_scanline_aa<agg::rasterizer_sl_clip_dbl>; + + using reflect_t = agg::wrap_mode_reflect; + using image_accessor_t = agg::image_accessor_wrap<input_pixfmt_t, reflect_t, reflect_t>; + + using span_alloc_t = agg::span_allocator<color_type>; + using span_conv_alpha_t = span_conv_alpha<color_type>; + + using affine_interpolator_t = agg::span_interpolator_linear<>; + using arbitrary_interpolator_t = + agg::span_interpolator_adaptor<agg::span_interpolator_linear<>, lookup_distortion>; + + size_t itemsize = sizeof(color_type); + if (is_grayscale<color_type>::value) { + itemsize /= 2; // agg::grayXX includes an alpha channel which we don't have. + } + + if (params.interpolation != NEAREST && + params.is_affine && + fabs(params.affine.sx) == 1.0 && + fabs(params.affine.sy) == 1.0 && + params.affine.shx == 0.0 && + params.affine.shy == 0.0) { + params.interpolation = NEAREST; + } + + span_alloc_t span_alloc; + rasterizer_t rasterizer; + agg::scanline_u8 scanline; + + span_conv_alpha_t conv_alpha(params.alpha); + + agg::rendering_buffer input_buffer; + input_buffer.attach( + (unsigned char *)input, in_width, in_height, in_width * itemsize); + input_pixfmt_t input_pixfmt(input_buffer); + image_accessor_t input_accessor(input_pixfmt); + + agg::rendering_buffer output_buffer; + output_buffer.attach( + (unsigned char *)output, out_width, out_height, out_width * itemsize); + output_pixfmt_t output_pixfmt(output_buffer); + renderer_t renderer(output_pixfmt); + + agg::trans_affine inverted = params.affine; + inverted.invert(); + + rasterizer.clip_box(0, 0, out_width, out_height); + + agg::path_storage path; + if (params.is_affine) { + path.move_to(0, 0); + path.line_to(in_width, 0); + path.line_to(in_width, in_height); + path.line_to(0, in_height); + path.close_polygon(); + agg::conv_transform<agg::path_storage> rectangle(path, params.affine); + rasterizer.add_path(rectangle); + } else { + path.move_to(0, 0); + path.line_to(out_width, 0); + path.line_to(out_width, out_height); + path.line_to(0, out_height); + path.close_polygon(); + rasterizer.add_path(path); + } + + if (params.interpolation == NEAREST) { + if (params.is_affine) { + using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, affine_interpolator_t>; + using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; + using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; + affine_interpolator_t interpolator(inverted); + span_gen_t span_gen(input_accessor, interpolator); + span_conv_t span_conv(span_gen, conv_alpha); + nn_renderer_t nn_renderer(renderer, span_alloc, span_conv); + agg::render_scanlines(rasterizer, scanline, nn_renderer); + } else { + using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, arbitrary_interpolator_t>; + using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; + using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; + lookup_distortion dist( + params.transform_mesh, in_width, in_height, out_width, out_height); + arbitrary_interpolator_t interpolator(inverted, dist); + span_gen_t span_gen(input_accessor, interpolator); + span_conv_t span_conv(span_gen, conv_alpha); + nn_renderer_t nn_renderer(renderer, span_alloc, span_conv); + agg::render_scanlines(rasterizer, scanline, nn_renderer); + } + } else { + agg::image_filter_lut filter; + get_filter(params, filter); + + if (params.is_affine && params.resample) { + using span_gen_t = typename type_mapping_t::template span_gen_affine_type<image_accessor_t>; + using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; + using int_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; + affine_interpolator_t interpolator(inverted); + span_gen_t span_gen(input_accessor, interpolator, filter); + span_conv_t span_conv(span_gen, conv_alpha); + int_renderer_t int_renderer(renderer, span_alloc, span_conv); + agg::render_scanlines(rasterizer, scanline, int_renderer); + } else { + using span_gen_t = typename type_mapping_t::template span_gen_filter_type<image_accessor_t, arbitrary_interpolator_t>; + using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; + using int_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; + lookup_distortion dist( + params.transform_mesh, in_width, in_height, out_width, out_height); + arbitrary_interpolator_t interpolator(inverted, dist); + span_gen_t span_gen(input_accessor, interpolator, filter); + span_conv_t span_conv(span_gen, conv_alpha); + int_renderer_t int_renderer(renderer, span_alloc, span_conv); + agg::render_scanlines(rasterizer, scanline, int_renderer); + } + } +} + +#endif /* MPL_RESAMPLE_H */ diff --git a/contrib/python/matplotlib/py3/src/_image_wrapper.cpp b/contrib/python/matplotlib/py3/src/_image_wrapper.cpp new file mode 100644 index 0000000000..ca6ae8b222 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_image_wrapper.cpp @@ -0,0 +1,297 @@ +#include "mplutils.h" +#include "_image_resample.h" +#include "numpy_cpp.h" +#include "py_converters.h" + + +/********************************************************************** + * Free functions + * */ + +const char* image_resample__doc__ = +"resample(input_array, output_array, transform, interpolation=NEAREST, resample=False, alpha=1.0, norm=False, radius=1.0)\n" +"--\n\n" + +"Resample input_array, blending it in-place into output_array, using an\n" +"affine transformation.\n\n" + +"Parameters\n" +"----------\n" +"input_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8`\n" +" If 2-d, the image is grayscale. If 3-d, the image must be of size\n" +" 4 in the last dimension and represents RGBA data.\n\n" + +"output_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8`\n" +" The dtype and number of dimensions must match `input_array`.\n\n" + +"transform : matplotlib.transforms.Transform instance\n" +" The transformation from the input array to the output array.\n\n" + +"interpolation : int, default: NEAREST\n" +" The interpolation method. Must be one of the following constants\n" +" defined in this module:\n\n" + +" NEAREST, BILINEAR, BICUBIC, SPLINE16, SPLINE36,\n" +" HANNING, HAMMING, HERMITE, KAISER, QUADRIC, CATROM, GAUSSIAN,\n" +" BESSEL, MITCHELL, SINC, LANCZOS, BLACKMAN\n\n" + +"resample : bool, optional\n" +" When `True`, use a full resampling method. When `False`, only\n" +" resample when the output image is larger than the input image.\n\n" + +"alpha : float, default: 1\n" +" The transparency level, from 0 (transparent) to 1 (opaque).\n\n" + +"norm : bool, default: False\n" +" Whether to norm the interpolation function.\n\n" + +"radius: float, default: 1\n" +" The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n"; + + +static PyArrayObject * +_get_transform_mesh(PyObject *py_affine, npy_intp *dims) +{ + /* TODO: Could we get away with float, rather than double, arrays here? */ + + /* Given a non-affine transform object, create a mesh that maps + every pixel in the output image to the input image. This is used + as a lookup table during the actual resampling. */ + + PyObject *py_inverse = NULL; + npy_intp out_dims[3]; + + out_dims[0] = dims[0] * dims[1]; + out_dims[1] = 2; + + py_inverse = PyObject_CallMethod(py_affine, "inverted", NULL); + if (py_inverse == NULL) { + return NULL; + } + + numpy::array_view<double, 2> input_mesh(out_dims); + double *p = (double *)input_mesh.data(); + + for (npy_intp y = 0; y < dims[0]; ++y) { + for (npy_intp x = 0; x < dims[1]; ++x) { + *p++ = (double)x; + *p++ = (double)y; + } + } + + PyObject *output_mesh = PyObject_CallMethod( + py_inverse, "transform", "O", input_mesh.pyobj_steal()); + + Py_DECREF(py_inverse); + + if (output_mesh == NULL) { + return NULL; + } + + PyArrayObject *output_mesh_array = + (PyArrayObject *)PyArray_ContiguousFromAny( + output_mesh, NPY_DOUBLE, 2, 2); + + Py_DECREF(output_mesh); + + if (output_mesh_array == NULL) { + return NULL; + } + + return output_mesh_array; +} + + +static PyObject * +image_resample(PyObject *self, PyObject* args, PyObject *kwargs) +{ + PyObject *py_input = NULL; + PyObject *py_output = NULL; + PyObject *py_transform = NULL; + resample_params_t params; + + PyArrayObject *input = NULL; + PyArrayObject *output = NULL; + PyArrayObject *transform_mesh = NULL; + int ndim; + int type; + + params.interpolation = NEAREST; + params.transform_mesh = NULL; + params.resample = false; + params.norm = false; + params.radius = 1.0; + params.alpha = 1.0; + + const char *kwlist[] = { + "input_array", "output_array", "transform", "interpolation", + "resample", "alpha", "norm", "radius", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "OOO|iO&dO&d:resample", (char **)kwlist, + &py_input, &py_output, &py_transform, + ¶ms.interpolation, &convert_bool, ¶ms.resample, + ¶ms.alpha, &convert_bool, ¶ms.norm, ¶ms.radius)) { + return NULL; + } + + if (params.interpolation < 0 || params.interpolation >= _n_interpolation) { + PyErr_Format(PyExc_ValueError, "Invalid interpolation value %d", + params.interpolation); + goto error; + } + + input = (PyArrayObject *)PyArray_FromAny( + py_input, NULL, 2, 3, NPY_ARRAY_C_CONTIGUOUS, NULL); + if (!input) { + goto error; + } + ndim = PyArray_NDIM(input); + type = PyArray_TYPE(input); + + if (!PyArray_Check(py_output)) { + PyErr_SetString(PyExc_ValueError, "Output array must be a NumPy array"); + goto error; + } + output = (PyArrayObject *)py_output; + if (PyArray_NDIM(output) != ndim) { + PyErr_Format( + PyExc_ValueError, + "Input (%dD) and output (%dD) have different dimensionalities.", + ndim, PyArray_NDIM(output)); + goto error; + } + // PyArray_FromAny above checks that input is 2D or 3D. + if (ndim == 3 && (PyArray_DIM(input, 2) != 4 || PyArray_DIM(output, 2) != 4)) { + PyErr_Format( + PyExc_ValueError, + "If 3D, input and output arrays must be RGBA with shape (M, N, 4); " + "got trailing dimensions of %" NPY_INTP_FMT " and %" NPY_INTP_FMT + " respectively", PyArray_DIM(input, 2), PyArray_DIM(output, 2)); + goto error; + } + if (PyArray_TYPE(output) != type) { + PyErr_SetString(PyExc_ValueError, "Mismatched types"); + goto error; + } + if (!PyArray_IS_C_CONTIGUOUS(output)) { + PyErr_SetString(PyExc_ValueError, "Output array must be C-contiguous"); + goto error; + } + + if (py_transform == NULL || py_transform == Py_None) { + params.is_affine = true; + } else { + PyObject *py_is_affine; + int py_is_affine2; + py_is_affine = PyObject_GetAttrString(py_transform, "is_affine"); + if (!py_is_affine) { + goto error; + } + + py_is_affine2 = PyObject_IsTrue(py_is_affine); + Py_DECREF(py_is_affine); + + if (py_is_affine2 == -1) { + goto error; + } else if (py_is_affine2) { + if (!convert_trans_affine(py_transform, ¶ms.affine)) { + goto error; + } + params.is_affine = true; + } else { + transform_mesh = _get_transform_mesh( + py_transform, PyArray_DIMS(output)); + if (!transform_mesh) { + goto error; + } + params.transform_mesh = (double *)PyArray_DATA(transform_mesh); + params.is_affine = false; + } + } + + if (auto resampler = + (ndim == 2) ? ( + (type == NPY_UINT8) ? resample<agg::gray8> : + (type == NPY_INT8) ? resample<agg::gray8> : + (type == NPY_UINT16) ? resample<agg::gray16> : + (type == NPY_INT16) ? resample<agg::gray16> : + (type == NPY_FLOAT32) ? resample<agg::gray32> : + (type == NPY_FLOAT64) ? resample<agg::gray64> : + nullptr) : ( + // ndim == 3 + (type == NPY_UINT8) ? resample<agg::rgba8> : + (type == NPY_INT8) ? resample<agg::rgba8> : + (type == NPY_UINT16) ? resample<agg::rgba16> : + (type == NPY_INT16) ? resample<agg::rgba16> : + (type == NPY_FLOAT32) ? resample<agg::rgba32> : + (type == NPY_FLOAT64) ? resample<agg::rgba64> : + nullptr)) { + Py_BEGIN_ALLOW_THREADS + resampler( + PyArray_DATA(input), PyArray_DIM(input, 1), PyArray_DIM(input, 0), + PyArray_DATA(output), PyArray_DIM(output, 1), PyArray_DIM(output, 0), + params); + Py_END_ALLOW_THREADS + } else { + PyErr_SetString( + PyExc_ValueError, + "arrays must be of dtype byte, short, float32 or float64"); + goto error; + } + + Py_DECREF(input); + Py_XDECREF(transform_mesh); + Py_RETURN_NONE; + + error: + Py_XDECREF(input); + Py_XDECREF(transform_mesh); + return NULL; +} + +static PyMethodDef module_functions[] = { + {"resample", (PyCFunction)image_resample, METH_VARARGS|METH_KEYWORDS, image_resample__doc__}, + {NULL} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, "_image", NULL, 0, module_functions, +}; + +PyMODINIT_FUNC PyInit__image(void) +{ + PyObject *m; + + import_array(); + + m = PyModule_Create(&moduledef); + + if (m == NULL) { + return NULL; + } + + if (PyModule_AddIntConstant(m, "NEAREST", NEAREST) || + PyModule_AddIntConstant(m, "BILINEAR", BILINEAR) || + PyModule_AddIntConstant(m, "BICUBIC", BICUBIC) || + PyModule_AddIntConstant(m, "SPLINE16", SPLINE16) || + PyModule_AddIntConstant(m, "SPLINE36", SPLINE36) || + PyModule_AddIntConstant(m, "HANNING", HANNING) || + PyModule_AddIntConstant(m, "HAMMING", HAMMING) || + PyModule_AddIntConstant(m, "HERMITE", HERMITE) || + PyModule_AddIntConstant(m, "KAISER", KAISER) || + PyModule_AddIntConstant(m, "QUADRIC", QUADRIC) || + PyModule_AddIntConstant(m, "CATROM", CATROM) || + PyModule_AddIntConstant(m, "GAUSSIAN", GAUSSIAN) || + PyModule_AddIntConstant(m, "BESSEL", BESSEL) || + PyModule_AddIntConstant(m, "MITCHELL", MITCHELL) || + PyModule_AddIntConstant(m, "SINC", SINC) || + PyModule_AddIntConstant(m, "LANCZOS", LANCZOS) || + PyModule_AddIntConstant(m, "BLACKMAN", BLACKMAN) || + PyModule_AddIntConstant(m, "_n_interpolation", _n_interpolation)) { + Py_DECREF(m); + return NULL; + } + + return m; +} diff --git a/contrib/python/matplotlib/py3/src/_path.h b/contrib/python/matplotlib/py3/src/_path.h new file mode 100644 index 0000000000..61c4ed07d0 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_path.h @@ -0,0 +1,1251 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef MPL_PATH_H +#define MPL_PATH_H + +#include <limits> +#include <math.h> +#include <vector> +#include <cmath> +#include <algorithm> +#include <string> + +#include "agg_conv_contour.h" +#include "agg_conv_curve.h" +#include "agg_conv_stroke.h" +#include "agg_conv_transform.h" +#include "agg_path_storage.h" +#include "agg_trans_affine.h" + +#include "path_converters.h" +#include "_backend_agg_basic_types.h" +#include "numpy_cpp.h" + +struct XY +{ + double x; + double y; + + XY(double x_, double y_) : x(x_), y(y_) + { + } + + bool operator==(const XY& o) + { + return (x == o.x && y == o.y); + } + + bool operator!=(const XY& o) + { + return (x != o.x || y != o.y); + } +}; + +typedef std::vector<XY> Polygon; + +void _finalize_polygon(std::vector<Polygon> &result, int closed_only) +{ + if (result.size() == 0) { + return; + } + + Polygon &polygon = result.back(); + + /* Clean up the last polygon in the result. */ + if (polygon.size() == 0) { + result.pop_back(); + } else if (closed_only) { + if (polygon.size() < 3) { + result.pop_back(); + } else if (polygon.front() != polygon.back()) { + polygon.push_back(polygon.front()); + } + } +} + +// +// The following function was found in the Agg 2.3 examples (interactive_polygon.cpp). +// It has been generalized to work on (possibly curved) polylines, rather than +// just polygons. The original comments have been kept intact. +// -- Michael Droettboom 2007-10-02 +// +//======= Crossings Multiply algorithm of InsideTest ======================== +// +// By Eric Haines, 3D/Eye Inc, erich@eye.com +// +// This version is usually somewhat faster than the original published in +// Graphics Gems IV; by turning the division for testing the X axis crossing +// into a tricky multiplication test this part of the test became faster, +// which had the additional effect of making the test for "both to left or +// both to right" a bit slower for triangles than simply computing the +// intersection each time. The main increase is in triangle testing speed, +// which was about 15% faster; all other polygon complexities were pretty much +// the same as before. On machines where division is very expensive (not the +// case on the HP 9000 series on which I tested) this test should be much +// faster overall than the old code. Your mileage may (in fact, will) vary, +// depending on the machine and the test data, but in general I believe this +// code is both shorter and faster. This test was inspired by unpublished +// Graphics Gems submitted by Joseph Samosky and Mark Haigh-Hutchinson. +// Related work by Samosky is in: +// +// Samosky, Joseph, "SectionView: A system for interactively specifying and +// visualizing sections through three-dimensional medical image data", +// M.S. Thesis, Department of Electrical Engineering and Computer Science, +// Massachusetts Institute of Technology, 1993. +// +// Shoot a test ray along +X axis. The strategy is to compare vertex Y values +// to the testing point's Y and quickly discard edges which are entirely to one +// side of the test ray. Note that CONVEX and WINDING code can be added as +// for the CrossingsTest() code; it is left out here for clarity. +// +// Input 2D polygon _pgon_ with _numverts_ number of vertices and test point +// _point_, returns 1 if inside, 0 if outside. +template <class PathIterator, class PointArray, class ResultArray> +void point_in_path_impl(PointArray &points, PathIterator &path, ResultArray &inside_flag) +{ + uint8_t yflag1; + double vtx0, vty0, vtx1, vty1; + double tx, ty; + double sx, sy; + double x, y; + size_t i; + bool all_done; + + size_t n = points.size(); + + std::vector<uint8_t> yflag0(n); + std::vector<uint8_t> subpath_flag(n); + + path.rewind(0); + + for (i = 0; i < n; ++i) { + inside_flag[i] = 0; + } + + unsigned code = 0; + do { + if (code != agg::path_cmd_move_to) { + code = path.vertex(&x, &y); + if (code == agg::path_cmd_stop || + (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + continue; + } + } + + sx = vtx0 = vtx1 = x; + sy = vty0 = vty1 = y; + + for (i = 0; i < n; ++i) { + ty = points(i, 1); + + if (std::isfinite(ty)) { + // get test bit for above/below X axis + yflag0[i] = (vty0 >= ty); + + subpath_flag[i] = 0; + } + } + + do { + code = path.vertex(&x, &y); + + // The following cases denote the beginning on a new subpath + if (code == agg::path_cmd_stop || + (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + x = sx; + y = sy; + } else if (code == agg::path_cmd_move_to) { + break; + } + + for (i = 0; i < n; ++i) { + tx = points(i, 0); + ty = points(i, 1); + + if (!(std::isfinite(tx) && std::isfinite(ty))) { + continue; + } + + yflag1 = (vty1 >= ty); + // Check if endpoints straddle (are on opposite sides) of + // X axis (i.e. the Y's differ); if so, +X ray could + // intersect this edge. The old test also checked whether + // the endpoints are both to the right or to the left of + // the test point. However, given the faster intersection + // point computation used below, this test was found to be + // a break-even proposition for most polygons and a loser + // for triangles (where 50% or more of the edges which + // survive this test will cross quadrants and so have to + // have the X intersection computed anyway). I credit + // Joseph Samosky with inspiring me to try dropping the + // "both left or both right" part of my code. + if (yflag0[i] != yflag1) { + // Check intersection of pgon segment with +X ray. + // Note if >= point's X; if so, the ray hits it. The + // division operation is avoided for the ">=" test by + // checking the sign of the first vertex wrto the test + // point; idea inspired by Joseph Samosky's and Mark + // Haigh-Hutchinson's different polygon inclusion + // tests. + if (((vty1 - ty) * (vtx0 - vtx1) >= (vtx1 - tx) * (vty0 - vty1)) == yflag1) { + subpath_flag[i] ^= 1; + } + } + + // Move to the next pair of vertices, retaining info as + // possible. + yflag0[i] = yflag1; + } + + vtx0 = vtx1; + vty0 = vty1; + + vtx1 = x; + vty1 = y; + } while (code != agg::path_cmd_stop && + (code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); + + all_done = true; + for (i = 0; i < n; ++i) { + tx = points(i, 0); + ty = points(i, 1); + + if (!(std::isfinite(tx) && std::isfinite(ty))) { + continue; + } + + yflag1 = (vty1 >= ty); + if (yflag0[i] != yflag1) { + if (((vty1 - ty) * (vtx0 - vtx1) >= (vtx1 - tx) * (vty0 - vty1)) == yflag1) { + subpath_flag[i] = subpath_flag[i] ^ true; + } + } + inside_flag[i] |= subpath_flag[i]; + if (inside_flag[i] == 0) { + all_done = false; + } + } + + if (all_done) { + break; + } + } while (code != agg::path_cmd_stop); +} + +template <class PathIterator, class PointArray, class ResultArray> +inline void points_in_path(PointArray &points, + const double r, + PathIterator &path, + agg::trans_affine &trans, + ResultArray &result) +{ + typedef agg::conv_transform<PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> no_nans_t; + typedef agg::conv_curve<no_nans_t> curve_t; + typedef agg::conv_contour<curve_t> contour_t; + + size_t i; + for (i = 0; i < points.size(); ++i) { + result[i] = false; + } + + if (path.total_vertices() < 3) { + return; + } + + transformed_path_t trans_path(path, trans); + no_nans_t no_nans_path(trans_path, true, path.has_codes()); + curve_t curved_path(no_nans_path); + if (r != 0.0) { + contour_t contoured_path(curved_path); + contoured_path.width(r); + point_in_path_impl(points, contoured_path, result); + } else { + point_in_path_impl(points, curved_path, result); + } +} + +template <class PathIterator> +inline bool point_in_path( + double x, double y, const double r, PathIterator &path, agg::trans_affine &trans) +{ + npy_intp shape[] = {1, 2}; + numpy::array_view<double, 2> points(shape); + points(0, 0) = x; + points(0, 1) = y; + + int result[1]; + result[0] = 0; + + points_in_path(points, r, path, trans, result); + + return result[0] != 0; +} + +template <class PathIterator> +inline bool point_on_path( + double x, double y, const double r, PathIterator &path, agg::trans_affine &trans) +{ + typedef agg::conv_transform<PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> no_nans_t; + typedef agg::conv_curve<no_nans_t> curve_t; + typedef agg::conv_stroke<curve_t> stroke_t; + + npy_intp shape[] = {1, 2}; + numpy::array_view<double, 2> points(shape); + points(0, 0) = x; + points(0, 1) = y; + + int result[1]; + result[0] = 0; + + transformed_path_t trans_path(path, trans); + no_nans_t nan_removed_path(trans_path, true, path.has_codes()); + curve_t curved_path(nan_removed_path); + stroke_t stroked_path(curved_path); + stroked_path.width(r * 2.0); + point_in_path_impl(points, stroked_path, result); + return result[0] != 0; +} + +struct extent_limits +{ + double x0; + double y0; + double x1; + double y1; + double xm; + double ym; +}; + +void reset_limits(extent_limits &e) +{ + e.x0 = std::numeric_limits<double>::infinity(); + e.y0 = std::numeric_limits<double>::infinity(); + e.x1 = -std::numeric_limits<double>::infinity(); + e.y1 = -std::numeric_limits<double>::infinity(); + /* xm and ym are the minimum positive values in the data, used + by log scaling */ + e.xm = std::numeric_limits<double>::infinity(); + e.ym = std::numeric_limits<double>::infinity(); +} + +inline void update_limits(double x, double y, extent_limits &e) +{ + if (x < e.x0) + e.x0 = x; + if (y < e.y0) + e.y0 = y; + if (x > e.x1) + e.x1 = x; + if (y > e.y1) + e.y1 = y; + /* xm and ym are the minimum positive values in the data, used + by log scaling */ + if (x > 0.0 && x < e.xm) + e.xm = x; + if (y > 0.0 && y < e.ym) + e.ym = y; +} + +template <class PathIterator> +void update_path_extents(PathIterator &path, agg::trans_affine &trans, extent_limits &extents) +{ + typedef agg::conv_transform<PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removed_t; + double x, y; + unsigned code; + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, path.has_codes()); + + nan_removed.rewind(0); + + while ((code = nan_removed.vertex(&x, &y)) != agg::path_cmd_stop) { + if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + continue; + } + update_limits(x, y, extents); + } +} + +template <class PathGenerator, class TransformArray, class OffsetArray> +void get_path_collection_extents(agg::trans_affine &master_transform, + PathGenerator &paths, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + extent_limits &extent) +{ + if (offsets.size() != 0 && offsets.dim(1) != 2) { + throw std::runtime_error("Offsets array must have shape (N, 2)"); + } + + size_t Npaths = paths.size(); + size_t Noffsets = offsets.size(); + size_t N = std::max(Npaths, Noffsets); + size_t Ntransforms = std::min(transforms.size(), N); + size_t i; + + agg::trans_affine trans; + + reset_limits(extent); + + for (i = 0; i < N; ++i) { + typename PathGenerator::path_iterator path(paths(i % Npaths)); + if (Ntransforms) { + size_t ti = i % Ntransforms; + trans = agg::trans_affine(transforms(ti, 0, 0), + transforms(ti, 1, 0), + transforms(ti, 0, 1), + transforms(ti, 1, 1), + transforms(ti, 0, 2), + transforms(ti, 1, 2)); + } else { + trans = master_transform; + } + + if (Noffsets) { + double xo = offsets(i % Noffsets, 0); + double yo = offsets(i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + trans *= agg::trans_affine_translation(xo, yo); + } + + update_path_extents(path, trans, extent); + } +} + +template <class PathGenerator, class TransformArray, class OffsetArray> +void point_in_path_collection(double x, + double y, + double radius, + agg::trans_affine &master_transform, + PathGenerator &paths, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + bool filled, + std::vector<int> &result) +{ + size_t Npaths = paths.size(); + + if (Npaths == 0) { + return; + } + + size_t Noffsets = offsets.size(); + size_t N = std::max(Npaths, Noffsets); + size_t Ntransforms = std::min(transforms.size(), N); + size_t i; + + agg::trans_affine trans; + + for (i = 0; i < N; ++i) { + typename PathGenerator::path_iterator path = paths(i % Npaths); + + if (Ntransforms) { + size_t ti = i % Ntransforms; + trans = agg::trans_affine(transforms(ti, 0, 0), + transforms(ti, 1, 0), + transforms(ti, 0, 1), + transforms(ti, 1, 1), + transforms(ti, 0, 2), + transforms(ti, 1, 2)); + trans *= master_transform; + } else { + trans = master_transform; + } + + if (Noffsets) { + double xo = offsets(i % Noffsets, 0); + double yo = offsets(i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + trans *= agg::trans_affine_translation(xo, yo); + } + + if (filled) { + if (point_in_path(x, y, radius, path, trans)) { + result.push_back(i); + } + } else { + if (point_on_path(x, y, radius, path, trans)) { + result.push_back(i); + } + } + } +} + +template <class PathIterator1, class PathIterator2> +bool path_in_path(PathIterator1 &a, + agg::trans_affine &atrans, + PathIterator2 &b, + agg::trans_affine &btrans) +{ + typedef agg::conv_transform<PathIterator2> transformed_path_t; + typedef PathNanRemover<transformed_path_t> no_nans_t; + typedef agg::conv_curve<no_nans_t> curve_t; + + if (a.total_vertices() < 3) { + return false; + } + + transformed_path_t b_path_trans(b, btrans); + no_nans_t b_no_nans(b_path_trans, true, b.has_codes()); + curve_t b_curved(b_no_nans); + + double x, y; + b_curved.rewind(0); + while (b_curved.vertex(&x, &y) != agg::path_cmd_stop) { + if (!point_in_path(x, y, 0.0, a, atrans)) { + return false; + } + } + + return true; +} + +/** The clip_path_to_rect code here is a clean-room implementation of + the Sutherland-Hodgman clipping algorithm described here: + + https://en.wikipedia.org/wiki/Sutherland-Hodgman_clipping_algorithm +*/ + +namespace clip_to_rect_filters +{ +/* There are four different passes needed to create/remove + vertices (one for each side of the rectangle). The differences + between those passes are encapsulated in these functor classes. +*/ +struct bisectx +{ + double m_x; + + bisectx(double x) : m_x(x) + { + } + + inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const + { + *bx = m_x; + double dx = px - sx; + double dy = py - sy; + *by = sy + dy * ((m_x - sx) / dx); + } +}; + +struct xlt : public bisectx +{ + xlt(double x) : bisectx(x) + { + } + + inline bool is_inside(double x, double y) const + { + return x <= m_x; + } +}; + +struct xgt : public bisectx +{ + xgt(double x) : bisectx(x) + { + } + + inline bool is_inside(double x, double y) const + { + return x >= m_x; + } +}; + +struct bisecty +{ + double m_y; + + bisecty(double y) : m_y(y) + { + } + + inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const + { + *by = m_y; + double dx = px - sx; + double dy = py - sy; + *bx = sx + dx * ((m_y - sy) / dy); + } +}; + +struct ylt : public bisecty +{ + ylt(double y) : bisecty(y) + { + } + + inline bool is_inside(double x, double y) const + { + return y <= m_y; + } +}; + +struct ygt : public bisecty +{ + ygt(double y) : bisecty(y) + { + } + + inline bool is_inside(double x, double y) const + { + return y >= m_y; + } +}; +} + +template <class Filter> +inline void clip_to_rect_one_step(const Polygon &polygon, Polygon &result, const Filter &filter) +{ + double sx, sy, px, py, bx, by; + bool sinside, pinside; + result.clear(); + + if (polygon.size() == 0) { + return; + } + + sx = polygon.back().x; + sy = polygon.back().y; + for (Polygon::const_iterator i = polygon.begin(); i != polygon.end(); ++i) { + px = i->x; + py = i->y; + + sinside = filter.is_inside(sx, sy); + pinside = filter.is_inside(px, py); + + if (sinside ^ pinside) { + filter.bisect(sx, sy, px, py, &bx, &by); + result.push_back(XY(bx, by)); + } + + if (pinside) { + result.push_back(XY(px, py)); + } + + sx = px; + sy = py; + } +} + +template <class PathIterator> +void +clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vector<Polygon> &results) +{ + double xmin, ymin, xmax, ymax; + if (rect.x1 < rect.x2) { + xmin = rect.x1; + xmax = rect.x2; + } else { + xmin = rect.x2; + xmax = rect.x1; + } + + if (rect.y1 < rect.y2) { + ymin = rect.y1; + ymax = rect.y2; + } else { + ymin = rect.y2; + ymax = rect.y1; + } + + if (!inside) { + std::swap(xmin, xmax); + std::swap(ymin, ymax); + } + + typedef agg::conv_curve<PathIterator> curve_t; + curve_t curve(path); + + Polygon polygon1, polygon2; + double x = 0, y = 0; + unsigned code = 0; + curve.rewind(0); + + do { + // Grab the next subpath and store it in polygon1 + polygon1.clear(); + do { + if (code == agg::path_cmd_move_to) { + polygon1.push_back(XY(x, y)); + } + + code = curve.vertex(&x, &y); + + if (code == agg::path_cmd_stop) { + break; + } + + if (code != agg::path_cmd_move_to) { + polygon1.push_back(XY(x, y)); + } + } while ((code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); + + // The result of each step is fed into the next (note the + // swapping of polygon1 and polygon2 at each step). + clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt(xmax)); + clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt(xmin)); + clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt(ymax)); + clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt(ymin)); + + // Empty polygons aren't very useful, so skip them + if (polygon1.size()) { + _finalize_polygon(results, 1); + results.push_back(polygon1); + } + } while (code != agg::path_cmd_stop); + + _finalize_polygon(results, 1); +} + +template <class VerticesArray, class ResultArray> +void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result) +{ + if (vertices.size() != 0 && vertices.dim(1) != 2) { + throw std::runtime_error("Invalid vertices array."); + } + + size_t n = vertices.size(); + double x; + double y; + double t0; + double t1; + double t; + + for (size_t i = 0; i < n; ++i) { + x = vertices(i, 0); + y = vertices(i, 1); + + t0 = trans.sx * x; + t1 = trans.shx * y; + t = t0 + t1 + trans.tx; + result(i, 0) = t; + + t0 = trans.shy * x; + t1 = trans.sy * y; + t = t0 + t1 + trans.ty; + result(i, 1) = t; + } +} + +template <class VerticesArray, class ResultArray> +void affine_transform_1d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result) +{ + if (vertices.dim(0) != 2) { + throw std::runtime_error("Invalid vertices array."); + } + + double x; + double y; + double t0; + double t1; + double t; + + x = vertices(0); + y = vertices(1); + + t0 = trans.sx * x; + t1 = trans.shx * y; + t = t0 + t1 + trans.tx; + result(0) = t; + + t0 = trans.shy * x; + t1 = trans.sy * y; + t = t0 + t1 + trans.ty; + result(1) = t; +} + +template <class BBoxArray> +int count_bboxes_overlapping_bbox(agg::rect_d &a, BBoxArray &bboxes) +{ + agg::rect_d b; + int count = 0; + + if (a.x2 < a.x1) { + std::swap(a.x1, a.x2); + } + if (a.y2 < a.y1) { + std::swap(a.y1, a.y2); + } + + size_t num_bboxes = bboxes.size(); + for (size_t i = 0; i < num_bboxes; ++i) { + b = agg::rect_d(bboxes(i, 0, 0), bboxes(i, 0, 1), bboxes(i, 1, 0), bboxes(i, 1, 1)); + + if (b.x2 < b.x1) { + std::swap(b.x1, b.x2); + } + if (b.y2 < b.y1) { + std::swap(b.y1, b.y2); + } + if (!((b.x2 <= a.x1) || (b.y2 <= a.y1) || (b.x1 >= a.x2) || (b.y1 >= a.y2))) { + ++count; + } + } + + return count; +} + + +inline bool isclose(double a, double b) +{ + // relative and absolute tolerance values are chosen empirically + // it looks the atol value matters here because of round-off errors + const double rtol = 1e-10; + const double atol = 1e-13; + + // as per python's math.isclose + return fabs(a-b) <= fmax(rtol * fmax(fabs(a), fabs(b)), atol); +} + + +inline bool segments_intersect(const double &x1, + const double &y1, + const double &x2, + const double &y2, + const double &x3, + const double &y3, + const double &x4, + const double &y4) +{ + // determinant + double den = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1)); + + // If den == 0 we have two possibilities: + if (isclose(den, 0.0)) { + double t_area = (x2*y3 - x3*y2) - x1*(y3 - y2) + y1*(x3 - x2); + // 1 - If the area of the triangle made by the 3 first points (2 from the first segment + // plus one from the second) is zero, they are collinear + if (isclose(t_area, 0.0)) { + if (x1 == x2 && x2 == x3) { // segments have infinite slope (vertical lines) + // and lie on the same line + return (fmin(y1, y2) <= fmin(y3, y4) && fmin(y3, y4) <= fmax(y1, y2)) || + (fmin(y3, y4) <= fmin(y1, y2) && fmin(y1, y2) <= fmax(y3, y4)); + } + else { + return (fmin(x1, x2) <= fmin(x3, x4) && fmin(x3, x4) <= fmax(x1, x2)) || + (fmin(x3, x4) <= fmin(x1, x2) && fmin(x1, x2) <= fmax(x3, x4)); + } + } + // 2 - If t_area is not zero, the segments are parallel, but not collinear + else { + return false; + } + } + + const double n1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3)); + const double n2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3)); + + const double u1 = n1 / den; + const double u2 = n2 / den; + + return ((u1 > 0.0 || isclose(u1, 0.0)) && + (u1 < 1.0 || isclose(u1, 1.0)) && + (u2 > 0.0 || isclose(u2, 0.0)) && + (u2 < 1.0 || isclose(u2, 1.0))); +} + +template <class PathIterator1, class PathIterator2> +bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2) +{ + typedef PathNanRemover<py::PathIterator> no_nans_t; + typedef agg::conv_curve<no_nans_t> curve_t; + + if (p1.total_vertices() < 2 || p2.total_vertices() < 2) { + return false; + } + + no_nans_t n1(p1, true, p1.has_codes()); + no_nans_t n2(p2, true, p2.has_codes()); + + curve_t c1(n1); + curve_t c2(n2); + + double x11, y11, x12, y12; + double x21, y21, x22, y22; + + c1.vertex(&x11, &y11); + while (c1.vertex(&x12, &y12) != agg::path_cmd_stop) { + // if the segment in path 1 is (almost) 0 length, skip to next vertex + if ((isclose((x11 - x12) * (x11 - x12) + (y11 - y12) * (y11 - y12), 0))){ + continue; + } + c2.rewind(0); + c2.vertex(&x21, &y21); + + while (c2.vertex(&x22, &y22) != agg::path_cmd_stop) { + // if the segment in path 2 is (almost) 0 length, skip to next vertex + if ((isclose((x21 - x22) * (x21 - x22) + (y21 - y22) * (y21 - y22), 0))){ + continue; + } + + if (segments_intersect(x11, y11, x12, y12, x21, y21, x22, y22)) { + return true; + } + x21 = x22; + y21 = y22; + } + x11 = x12; + y11 = y12; + } + + return false; +} + +// returns whether the segment from (x1,y1) to (x2,y2) +// intersects the rectangle centered at (cx,cy) with size (w,h) +// see doc/segment_intersects_rectangle.svg for a more detailed explanation +inline bool segment_intersects_rectangle(double x1, double y1, + double x2, double y2, + double cx, double cy, + double w, double h) +{ + return fabs(x1 + x2 - 2.0 * cx) < fabs(x1 - x2) + w && + fabs(y1 + y2 - 2.0 * cy) < fabs(y1 - y2) + h && + 2.0 * fabs((x1 - cx) * (y1 - y2) - (y1 - cy) * (x1 - x2)) < + w * fabs(y1 - y2) + h * fabs(x1 - x2); +} + +template <class PathIterator> +bool path_intersects_rectangle(PathIterator &path, + double rect_x1, double rect_y1, + double rect_x2, double rect_y2, + bool filled) +{ + typedef PathNanRemover<py::PathIterator> no_nans_t; + typedef agg::conv_curve<no_nans_t> curve_t; + + if (path.total_vertices() == 0) { + return false; + } + + no_nans_t no_nans(path, true, path.has_codes()); + curve_t curve(no_nans); + + double cx = (rect_x1 + rect_x2) * 0.5, cy = (rect_y1 + rect_y2) * 0.5; + double w = fabs(rect_x1 - rect_x2), h = fabs(rect_y1 - rect_y2); + + double x1, y1, x2, y2; + + curve.vertex(&x1, &y1); + if (2.0 * fabs(x1 - cx) <= w && 2.0 * fabs(y1 - cy) <= h) { + return true; + } + + while (curve.vertex(&x2, &y2) != agg::path_cmd_stop) { + if (segment_intersects_rectangle(x1, y1, x2, y2, cx, cy, w, h)) { + return true; + } + x1 = x2; + y1 = y2; + } + + if (filled) { + agg::trans_affine trans; + if (point_in_path(cx, cy, 0.0, path, trans)) { + return true; + } + } + + return false; +} + +template <class PathIterator> +void convert_path_to_polygons(PathIterator &path, + agg::trans_affine &trans, + double width, + double height, + int closed_only, + std::vector<Polygon> &result) +{ + typedef agg::conv_transform<py::PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removal_t; + typedef PathClipper<nan_removal_t> clipped_t; + typedef PathSimplifier<clipped_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; + + bool do_clip = width != 0.0 && height != 0.0; + bool simplify = path.should_simplify(); + + transformed_path_t tpath(path, trans); + nan_removal_t nan_removed(tpath, true, path.has_codes()); + clipped_t clipped(nan_removed, do_clip, width, height); + simplify_t simplified(clipped, simplify, path.simplify_threshold()); + curve_t curve(simplified); + + result.push_back(Polygon()); + Polygon *polygon = &result.back(); + double x, y; + unsigned code; + + while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) { + if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + _finalize_polygon(result, 1); + result.push_back(Polygon()); + polygon = &result.back(); + } else { + if (code == agg::path_cmd_move_to) { + _finalize_polygon(result, closed_only); + result.push_back(Polygon()); + polygon = &result.back(); + } + polygon->push_back(XY(x, y)); + } + } + + _finalize_polygon(result, closed_only); +} + +template <class VertexSource> +void +__cleanup_path(VertexSource &source, std::vector<double> &vertices, std::vector<npy_uint8> &codes) +{ + unsigned code; + double x, y; + do { + code = source.vertex(&x, &y); + vertices.push_back(x); + vertices.push_back(y); + codes.push_back((npy_uint8)code); + } while (code != agg::path_cmd_stop); +} + +template <class PathIterator> +void cleanup_path(PathIterator &path, + agg::trans_affine &trans, + bool remove_nans, + bool do_clip, + const agg::rect_base<double> &rect, + e_snap_mode snap_mode, + double stroke_width, + bool do_simplify, + bool return_curves, + SketchParams sketch_params, + std::vector<double> &vertices, + std::vector<unsigned char> &codes) +{ + typedef agg::conv_transform<py::PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removal_t; + typedef PathClipper<nan_removal_t> clipped_t; + typedef PathSnapper<clipped_t> snapped_t; + typedef PathSimplifier<snapped_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; + typedef Sketch<curve_t> sketch_t; + + transformed_path_t tpath(path, trans); + nan_removal_t nan_removed(tpath, remove_nans, path.has_codes()); + clipped_t clipped(nan_removed, do_clip, rect); + snapped_t snapped(clipped, snap_mode, path.total_vertices(), stroke_width); + simplify_t simplified(snapped, do_simplify, path.simplify_threshold()); + + vertices.reserve(path.total_vertices() * 2); + codes.reserve(path.total_vertices()); + + if (return_curves && sketch_params.scale == 0.0) { + __cleanup_path(simplified, vertices, codes); + } else { + curve_t curve(simplified); + sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness); + __cleanup_path(sketch, vertices, codes); + } +} + +void quad2cubic(double x0, double y0, + double x1, double y1, + double x2, double y2, + double *outx, double *outy) +{ + + outx[0] = x0 + 2./3. * (x1 - x0); + outy[0] = y0 + 2./3. * (y1 - y0); + outx[1] = outx[0] + 1./3. * (x2 - x0); + outy[1] = outy[0] + 1./3. * (y2 - y0); + outx[2] = x2; + outy[2] = y2; +} + + +void __add_number(double val, char format_code, int precision, + std::string& buffer) +{ + if (precision == -1) { + // Special-case for compat with old ttconv code, which *truncated* + // values with a cast to int instead of rounding them as printf + // would do. The only point where non-integer values arise is from + // quad2cubic conversion (as we already perform a first truncation + // on Python's side), which can introduce additional floating point + // error (by adding 2/3 delta-x and then 1/3 delta-x), so compensate by + // first rounding to the closest 1/3 and then truncating. + char str[255]; + PyOS_snprintf(str, 255, "%d", (int)(round(val * 3)) / 3); + buffer += str; + } else { + char *str = PyOS_double_to_string( + val, format_code, precision, Py_DTSF_ADD_DOT_0, NULL); + // Delete trailing zeros and decimal point + char *c = str + strlen(str) - 1; // Start at last character. + // Rewind through all the zeros and, if present, the trailing decimal + // point. Py_DTSF_ADD_DOT_0 ensures we won't go past the start of str. + while (*c == '0') { + --c; + } + if (*c == '.') { + --c; + } + try { + buffer.append(str, c + 1); + } catch (std::bad_alloc& e) { + PyMem_Free(str); + throw e; + } + PyMem_Free(str); + } +} + + +template <class PathIterator> +bool __convert_to_string(PathIterator &path, + int precision, + char **codes, + bool postfix, + std::string& buffer) +{ + const char format_code = 'f'; + + double x[3]; + double y[3]; + double last_x = 0.0; + double last_y = 0.0; + + unsigned code; + + while ((code = path.vertex(&x[0], &y[0])) != agg::path_cmd_stop) { + if (code == CLOSEPOLY) { + buffer += codes[4]; + } else if (code < 5) { + size_t size = NUM_VERTICES[code]; + + for (size_t i = 1; i < size; ++i) { + unsigned subcode = path.vertex(&x[i], &y[i]); + if (subcode != code) { + return false; + } + } + + /* For formats that don't support quad curves, convert to + cubic curves */ + if (code == CURVE3 && codes[code - 1][0] == '\0') { + quad2cubic(last_x, last_y, x[0], y[0], x[1], y[1], x, y); + code++; + size = 3; + } + + if (!postfix) { + buffer += codes[code - 1]; + buffer += ' '; + } + + for (size_t i = 0; i < size; ++i) { + __add_number(x[i], format_code, precision, buffer); + buffer += ' '; + __add_number(y[i], format_code, precision, buffer); + buffer += ' '; + } + + if (postfix) { + buffer += codes[code - 1]; + } + + last_x = x[size - 1]; + last_y = y[size - 1]; + } else { + // Unknown code value + return false; + } + + buffer += '\n'; + } + + return true; +} + +template <class PathIterator> +bool convert_to_string(PathIterator &path, + agg::trans_affine &trans, + agg::rect_d &clip_rect, + bool simplify, + SketchParams sketch_params, + int precision, + char **codes, + bool postfix, + std::string& buffer) +{ + size_t buffersize; + typedef agg::conv_transform<py::PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removal_t; + typedef PathClipper<nan_removal_t> clipped_t; + typedef PathSimplifier<clipped_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; + typedef Sketch<curve_t> sketch_t; + + bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); + + transformed_path_t tpath(path, trans); + nan_removal_t nan_removed(tpath, true, path.has_codes()); + clipped_t clipped(nan_removed, do_clip, clip_rect); + simplify_t simplified(clipped, simplify, path.simplify_threshold()); + + buffersize = (size_t) path.total_vertices() * (precision + 5) * 4; + if (buffersize == 0) { + return true; + } + + if (sketch_params.scale != 0.0) { + buffersize *= 10; + } + + buffer.reserve(buffersize); + + if (sketch_params.scale == 0.0) { + return __convert_to_string(simplified, precision, codes, postfix, buffer); + } else { + curve_t curve(simplified); + sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness); + return __convert_to_string(sketch, precision, codes, postfix, buffer); + } + +} + +template<class T> +bool is_sorted_and_has_non_nan(PyArrayObject *array) +{ + char* ptr = PyArray_BYTES(array); + npy_intp size = PyArray_DIM(array, 0), + stride = PyArray_STRIDE(array, 0); + using limits = std::numeric_limits<T>; + T last = limits::has_infinity ? -limits::infinity() : limits::min(); + bool found_non_nan = false; + + for (npy_intp i = 0; i < size; ++i, ptr += stride) { + T current = *(T*)ptr; + // The following tests !isnan(current), but also works for integral + // types. (The isnan(IntegralType) overload is absent on MSVC.) + if (current == current) { + found_non_nan = true; + if (current < last) { + return false; + } + last = current; + } + } + return found_non_nan; +}; + + +#endif diff --git a/contrib/python/matplotlib/py3/src/_path_wrapper.cpp b/contrib/python/matplotlib/py3/src/_path_wrapper.cpp new file mode 100644 index 0000000000..369d9e0308 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_path_wrapper.cpp @@ -0,0 +1,772 @@ +#include "numpy_cpp.h" + +#include "_path.h" + +#include "py_converters.h" +#include "py_adaptors.h" + +PyObject *convert_polygon_vector(std::vector<Polygon> &polygons) +{ + PyObject *pyresult = PyList_New(polygons.size()); + + for (size_t i = 0; i < polygons.size(); ++i) { + Polygon poly = polygons[i]; + npy_intp dims[2]; + dims[1] = 2; + + dims[0] = (npy_intp)poly.size(); + + numpy::array_view<double, 2> subresult(dims); + memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2); + + if (PyList_SetItem(pyresult, i, subresult.pyobj())) { + Py_DECREF(pyresult); + return NULL; + } + } + + return pyresult; +} + +const char *Py_point_in_path__doc__ = + "point_in_path(x, y, radius, path, trans)\n" + "--\n\n"; + +static PyObject *Py_point_in_path(PyObject *self, PyObject *args) +{ + double x, y, r; + py::PathIterator path; + agg::trans_affine trans; + bool result; + + if (!PyArg_ParseTuple(args, + "dddO&O&:point_in_path", + &x, + &y, + &r, + &convert_path, + &path, + &convert_trans_affine, + &trans)) { + return NULL; + } + + CALL_CPP("point_in_path", (result = point_in_path(x, y, r, path, trans))); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_points_in_path__doc__ = + "points_in_path(points, radius, path, trans)\n" + "--\n\n"; + +static PyObject *Py_points_in_path(PyObject *self, PyObject *args) +{ + numpy::array_view<const double, 2> points; + double r; + py::PathIterator path; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&dO&O&:points_in_path", + &convert_points, + &points, + &r, + &convert_path, + &path, + &convert_trans_affine, + &trans)) { + return NULL; + } + + npy_intp dims[] = { (npy_intp)points.size() }; + numpy::array_view<uint8_t, 1> results(dims); + + CALL_CPP("points_in_path", (points_in_path(points, r, path, trans, results))); + + return results.pyobj(); +} + +const char *Py_update_path_extents__doc__ = + "update_path_extents(path, trans, rect, minpos, ignore)\n" + "--\n\n"; + +static PyObject *Py_update_path_extents(PyObject *self, PyObject *args) +{ + py::PathIterator path; + agg::trans_affine trans; + agg::rect_d rect; + numpy::array_view<double, 1> minpos; + int ignore; + int changed; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&i:update_path_extents", + &convert_path, + &path, + &convert_trans_affine, + &trans, + &convert_rect, + &rect, + &minpos.converter, + &minpos, + &ignore)) { + return NULL; + } + + if (minpos.dim(0) != 2) { + PyErr_Format(PyExc_ValueError, + "minpos must be of length 2, got %" NPY_INTP_FMT, + minpos.dim(0)); + return NULL; + } + + extent_limits e; + + if (ignore) { + CALL_CPP("update_path_extents", reset_limits(e)); + } else { + if (rect.x1 > rect.x2) { + e.x0 = std::numeric_limits<double>::infinity(); + e.x1 = -std::numeric_limits<double>::infinity(); + } else { + e.x0 = rect.x1; + e.x1 = rect.x2; + } + if (rect.y1 > rect.y2) { + e.y0 = std::numeric_limits<double>::infinity(); + e.y1 = -std::numeric_limits<double>::infinity(); + } else { + e.y0 = rect.y1; + e.y1 = rect.y2; + } + e.xm = minpos(0); + e.ym = minpos(1); + } + + CALL_CPP("update_path_extents", (update_path_extents(path, trans, e))); + + changed = (e.x0 != rect.x1 || e.y0 != rect.y1 || e.x1 != rect.x2 || e.y1 != rect.y2 || + e.xm != minpos(0) || e.ym != minpos(1)); + + npy_intp extentsdims[] = { 2, 2 }; + numpy::array_view<double, 2> outextents(extentsdims); + outextents(0, 0) = e.x0; + outextents(0, 1) = e.y0; + outextents(1, 0) = e.x1; + outextents(1, 1) = e.y1; + + npy_intp minposdims[] = { 2 }; + numpy::array_view<double, 1> outminpos(minposdims); + outminpos(0) = e.xm; + outminpos(1) = e.ym; + + return Py_BuildValue( + "NNi", outextents.pyobj(), outminpos.pyobj(), changed); +} + +const char *Py_get_path_collection_extents__doc__ = + "get_path_collection_extents(" + "master_transform, paths, transforms, offsets, offset_transform)\n" + "--\n\n"; + +static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args) +{ + agg::trans_affine master_transform; + py::PathGenerator paths; + numpy::array_view<const double, 3> transforms; + numpy::array_view<const double, 2> offsets; + agg::trans_affine offset_trans; + extent_limits e; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&O&:get_path_collection_extents", + &convert_trans_affine, + &master_transform, + &convert_pathgen, + &paths, + &convert_transforms, + &transforms, + &convert_points, + &offsets, + &convert_trans_affine, + &offset_trans)) { + return NULL; + } + + CALL_CPP("get_path_collection_extents", + (get_path_collection_extents( + master_transform, paths, transforms, offsets, offset_trans, e))); + + npy_intp dims[] = { 2, 2 }; + numpy::array_view<double, 2> extents(dims); + extents(0, 0) = e.x0; + extents(0, 1) = e.y0; + extents(1, 0) = e.x1; + extents(1, 1) = e.y1; + + npy_intp minposdims[] = { 2 }; + numpy::array_view<double, 1> minpos(minposdims); + minpos(0) = e.xm; + minpos(1) = e.ym; + + return Py_BuildValue("NN", extents.pyobj(), minpos.pyobj()); +} + +const char *Py_point_in_path_collection__doc__ = + "point_in_path_collection(" + "x, y, radius, master_transform, paths, transforms, offsets, " + "offset_trans, filled)\n" + "--\n\n"; + +static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args) +{ + double x, y, radius; + agg::trans_affine master_transform; + py::PathGenerator paths; + numpy::array_view<const double, 3> transforms; + numpy::array_view<const double, 2> offsets; + agg::trans_affine offset_trans; + bool filled; + std::vector<int> result; + + if (!PyArg_ParseTuple(args, + "dddO&O&O&O&O&O&:point_in_path_collection", + &x, + &y, + &radius, + &convert_trans_affine, + &master_transform, + &convert_pathgen, + &paths, + &convert_transforms, + &transforms, + &convert_points, + &offsets, + &convert_trans_affine, + &offset_trans, + &convert_bool, + &filled)) { + return NULL; + } + + CALL_CPP("point_in_path_collection", + (point_in_path_collection(x, + y, + radius, + master_transform, + paths, + transforms, + offsets, + offset_trans, + filled, + result))); + + npy_intp dims[] = {(npy_intp)result.size() }; + numpy::array_view<int, 1> pyresult(dims); + if (result.size() > 0) { + memcpy(pyresult.data(), &result[0], result.size() * sizeof(int)); + } + return pyresult.pyobj(); +} + +const char *Py_path_in_path__doc__ = + "path_in_path(path_a, trans_a, path_b, trans_b)\n" + "--\n\n"; + +static PyObject *Py_path_in_path(PyObject *self, PyObject *args) +{ + py::PathIterator a; + agg::trans_affine atrans; + py::PathIterator b; + agg::trans_affine btrans; + bool result; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&:path_in_path", + &convert_path, + &a, + &convert_trans_affine, + &atrans, + &convert_path, + &b, + &convert_trans_affine, + &btrans)) { + return NULL; + } + + CALL_CPP("path_in_path", (result = path_in_path(a, atrans, b, btrans))); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_clip_path_to_rect__doc__ = + "clip_path_to_rect(path, rect, inside)\n" + "--\n\n"; + +static PyObject *Py_clip_path_to_rect(PyObject *self, PyObject *args) +{ + py::PathIterator path; + agg::rect_d rect; + bool inside; + std::vector<Polygon> result; + + if (!PyArg_ParseTuple(args, + "O&O&O&:clip_path_to_rect", + &convert_path, + &path, + &convert_rect, + &rect, + &convert_bool, + &inside)) { + return NULL; + } + + CALL_CPP("clip_path_to_rect", (clip_path_to_rect(path, rect, inside, result))); + + return convert_polygon_vector(result); +} + +const char *Py_affine_transform__doc__ = + "affine_transform(points, trans)\n" + "--\n\n"; + +static PyObject *Py_affine_transform(PyObject *self, PyObject *args) +{ + PyObject *vertices_obj; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "OO&:affine_transform", + &vertices_obj, + &convert_trans_affine, + &trans)) { + return NULL; + } + + PyArrayObject* vertices_arr = (PyArrayObject *)PyArray_ContiguousFromAny(vertices_obj, NPY_DOUBLE, 1, 2); + if (vertices_arr == NULL) { + return NULL; + } + + if (PyArray_NDIM(vertices_arr) == 2) { + numpy::array_view<double, 2> vertices(vertices_arr); + Py_DECREF(vertices_arr); + + npy_intp dims[] = { (npy_intp)vertices.size(), 2 }; + numpy::array_view<double, 2> result(dims); + CALL_CPP("affine_transform", (affine_transform_2d(vertices, trans, result))); + return result.pyobj(); + } else { // PyArray_NDIM(vertices_arr) == 1 + numpy::array_view<double, 1> vertices(vertices_arr); + Py_DECREF(vertices_arr); + + npy_intp dims[] = { (npy_intp)vertices.size() }; + numpy::array_view<double, 1> result(dims); + CALL_CPP("affine_transform", (affine_transform_1d(vertices, trans, result))); + return result.pyobj(); + } +} + +const char *Py_count_bboxes_overlapping_bbox__doc__ = + "count_bboxes_overlapping_bbox(bbox, bboxes)\n" + "--\n\n"; + +static PyObject *Py_count_bboxes_overlapping_bbox(PyObject *self, PyObject *args) +{ + agg::rect_d bbox; + numpy::array_view<const double, 3> bboxes; + int result; + + if (!PyArg_ParseTuple(args, + "O&O&:count_bboxes_overlapping_bbox", + &convert_rect, + &bbox, + &convert_bboxes, + &bboxes)) { + return NULL; + } + + CALL_CPP("count_bboxes_overlapping_bbox", + (result = count_bboxes_overlapping_bbox(bbox, bboxes))); + + return PyLong_FromLong(result); +} + +const char *Py_path_intersects_path__doc__ = + "path_intersects_path(path1, path2, filled=False)\n" + "--\n\n"; + +static PyObject *Py_path_intersects_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator p1; + py::PathIterator p2; + agg::trans_affine t1; + agg::trans_affine t2; + int filled = 0; + const char *names[] = { "p1", "p2", "filled", NULL }; + bool result; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O&O&i:path_intersects_path", + (char **)names, + &convert_path, + &p1, + &convert_path, + &p2, + &filled)) { + return NULL; + } + + CALL_CPP("path_intersects_path", (result = path_intersects_path(p1, p2))); + if (filled) { + if (!result) { + CALL_CPP("path_intersects_path", + (result = path_in_path(p1, t1, p2, t2))); + } + if (!result) { + CALL_CPP("path_intersects_path", + (result = path_in_path(p2, t1, p1, t2))); + } + } + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_path_intersects_rectangle__doc__ = + "path_intersects_rectangle(" + "path, rect_x1, rect_y1, rect_x2, rect_y2, filled=False)\n" + "--\n\n"; + +static PyObject *Py_path_intersects_rectangle(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + double rect_x1, rect_y1, rect_x2, rect_y2; + bool filled = false; + const char *names[] = { "path", "rect_x1", "rect_y1", "rect_x2", "rect_y2", "filled", NULL }; + bool result; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O&dddd|O&:path_intersects_rectangle", + (char **)names, + &convert_path, + &path, + &rect_x1, + &rect_y1, + &rect_x2, + &rect_y2, + &convert_bool, + &filled)) { + return NULL; + } + + CALL_CPP("path_intersects_rectangle", (result = path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled))); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_convert_path_to_polygons__doc__ = + "convert_path_to_polygons(path, trans, width=0, height=0)\n" + "--\n\n"; + +static PyObject *Py_convert_path_to_polygons(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + agg::trans_affine trans; + double width = 0.0, height = 0.0; + int closed_only = 1; + std::vector<Polygon> result; + const char *names[] = { "path", "transform", "width", "height", "closed_only", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O&O&|ddi:convert_path_to_polygons", + (char **)names, + &convert_path, + &path, + &convert_trans_affine, + &trans, + &width, + &height, + &closed_only)) { + return NULL; + } + + CALL_CPP("convert_path_to_polygons", + (convert_path_to_polygons(path, trans, width, height, closed_only, result))); + + return convert_polygon_vector(result); +} + +const char *Py_cleanup_path__doc__ = + "cleanup_path(" + "path, trans, remove_nans, clip_rect, snap_mode, stroke_width, simplify, " + "return_curves, sketch)\n" + "--\n\n"; + +static PyObject *Py_cleanup_path(PyObject *self, PyObject *args) +{ + py::PathIterator path; + agg::trans_affine trans; + bool remove_nans; + agg::rect_d clip_rect; + e_snap_mode snap_mode; + double stroke_width; + PyObject *simplifyobj; + bool simplify = false; + bool return_curves; + SketchParams sketch; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&O&dOO&O&:cleanup_path", + &convert_path, + &path, + &convert_trans_affine, + &trans, + &convert_bool, + &remove_nans, + &convert_rect, + &clip_rect, + &convert_snap, + &snap_mode, + &stroke_width, + &simplifyobj, + &convert_bool, + &return_curves, + &convert_sketch_params, + &sketch)) { + return NULL; + } + + if (simplifyobj == Py_None) { + simplify = path.should_simplify(); + } else { + switch (PyObject_IsTrue(simplifyobj)) { + case 0: simplify = false; break; + case 1: simplify = true; break; + default: return NULL; // errored. + } + } + + bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); + + std::vector<double> vertices; + std::vector<npy_uint8> codes; + + CALL_CPP("cleanup_path", + (cleanup_path(path, + trans, + remove_nans, + do_clip, + clip_rect, + snap_mode, + stroke_width, + simplify, + return_curves, + sketch, + vertices, + codes))); + + size_t length = codes.size(); + + npy_intp vertices_dims[] = {(npy_intp)length, 2 }; + numpy::array_view<double, 2> pyvertices(vertices_dims); + + npy_intp codes_dims[] = {(npy_intp)length }; + numpy::array_view<unsigned char, 1> pycodes(codes_dims); + + memcpy(pyvertices.data(), &vertices[0], sizeof(double) * 2 * length); + memcpy(pycodes.data(), &codes[0], sizeof(unsigned char) * length); + + return Py_BuildValue("NN", pyvertices.pyobj(), pycodes.pyobj()); +} + +const char *Py_convert_to_string__doc__ = + "convert_to_string(" + "path, trans, clip_rect, simplify, sketch, precision, codes, postfix)\n" + "--\n\n" + "Convert *path* to a bytestring.\n" + "\n" + "The first five parameters (up to *sketch*) are interpreted as in\n" + "`.cleanup_path`. The following ones are detailed below.\n" + "\n" + "Parameters\n" + "----------\n" + "path : Path\n" + "trans : Transform or None\n" + "clip_rect : sequence of 4 floats, or None\n" + "simplify : bool\n" + "sketch : tuple of 3 floats, or None\n" + "precision : int\n" + " The precision used to \"%.*f\"-format the values. Trailing zeros\n" + " and decimal points are always removed. (precision=-1 is a special\n" + " case used to implement ttconv-back-compatible conversion.)\n" + "codes : sequence of 5 bytestrings\n" + " The bytes representation of each opcode (MOVETO, LINETO, CURVE3,\n" + " CURVE4, CLOSEPOLY), in that order. If the bytes for CURVE3 is\n" + " empty, quad segments are automatically converted to cubic ones\n" + " (this is used by backends such as pdf and ps, which do not support\n" + " quads).\n" + "postfix : bool\n" + " Whether the opcode comes after the values (True) or before (False).\n" + ; + +static PyObject *Py_convert_to_string(PyObject *self, PyObject *args) +{ + py::PathIterator path; + agg::trans_affine trans; + agg::rect_d cliprect; + PyObject *simplifyobj; + bool simplify = false; + SketchParams sketch; + int precision; + char *codes[5]; + bool postfix; + std::string buffer; + bool status; + + if (!PyArg_ParseTuple(args, + "O&O&O&OO&i(yyyyy)O&:convert_to_string", + &convert_path, + &path, + &convert_trans_affine, + &trans, + &convert_rect, + &cliprect, + &simplifyobj, + &convert_sketch_params, + &sketch, + &precision, + &codes[0], + &codes[1], + &codes[2], + &codes[3], + &codes[4], + &convert_bool, + &postfix)) { + return NULL; + } + + if (simplifyobj == Py_None) { + simplify = path.should_simplify(); + } else { + switch (PyObject_IsTrue(simplifyobj)) { + case 0: simplify = false; break; + case 1: simplify = true; break; + default: return NULL; // errored. + } + } + + CALL_CPP("convert_to_string", + (status = convert_to_string( + path, trans, cliprect, simplify, sketch, + precision, codes, postfix, buffer))); + + if (!status) { + PyErr_SetString(PyExc_ValueError, "Malformed path codes"); + return NULL; + } + + return PyBytes_FromStringAndSize(buffer.c_str(), buffer.size()); +} + + +const char *Py_is_sorted_and_has_non_nan__doc__ = + "is_sorted_and_has_non_nan(array, /)\n" + "--\n\n" + "Return whether the 1D *array* is monotonically increasing, ignoring NaNs,\n" + "and has at least one non-nan value."; + +static PyObject *Py_is_sorted_and_has_non_nan(PyObject *self, PyObject *obj) +{ + bool result; + + PyArrayObject *array = (PyArrayObject *)PyArray_FromAny( + obj, NULL, 1, 1, 0, NULL); + + if (array == NULL) { + return NULL; + } + + /* Handle just the most common types here, otherwise coerce to double */ + switch (PyArray_TYPE(array)) { + case NPY_INT: + result = is_sorted_and_has_non_nan<npy_int>(array); + break; + case NPY_LONG: + result = is_sorted_and_has_non_nan<npy_long>(array); + break; + case NPY_LONGLONG: + result = is_sorted_and_has_non_nan<npy_longlong>(array); + break; + case NPY_FLOAT: + result = is_sorted_and_has_non_nan<npy_float>(array); + break; + case NPY_DOUBLE: + result = is_sorted_and_has_non_nan<npy_double>(array); + break; + default: + Py_DECREF(array); + array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1); + if (array == NULL) { + return NULL; + } + result = is_sorted_and_has_non_nan<npy_double>(array); + } + + Py_DECREF(array); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + + +static PyMethodDef module_functions[] = { + {"point_in_path", (PyCFunction)Py_point_in_path, METH_VARARGS, Py_point_in_path__doc__}, + {"points_in_path", (PyCFunction)Py_points_in_path, METH_VARARGS, Py_points_in_path__doc__}, + {"update_path_extents", (PyCFunction)Py_update_path_extents, METH_VARARGS, Py_update_path_extents__doc__}, + {"get_path_collection_extents", (PyCFunction)Py_get_path_collection_extents, METH_VARARGS, Py_get_path_collection_extents__doc__}, + {"point_in_path_collection", (PyCFunction)Py_point_in_path_collection, METH_VARARGS, Py_point_in_path_collection__doc__}, + {"path_in_path", (PyCFunction)Py_path_in_path, METH_VARARGS, Py_path_in_path__doc__}, + {"clip_path_to_rect", (PyCFunction)Py_clip_path_to_rect, METH_VARARGS, Py_clip_path_to_rect__doc__}, + {"affine_transform", (PyCFunction)Py_affine_transform, METH_VARARGS, Py_affine_transform__doc__}, + {"count_bboxes_overlapping_bbox", (PyCFunction)Py_count_bboxes_overlapping_bbox, METH_VARARGS, Py_count_bboxes_overlapping_bbox__doc__}, + {"path_intersects_path", (PyCFunction)Py_path_intersects_path, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_path__doc__}, + {"path_intersects_rectangle", (PyCFunction)Py_path_intersects_rectangle, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_rectangle__doc__}, + {"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS|METH_KEYWORDS, Py_convert_path_to_polygons__doc__}, + {"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__}, + {"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__}, + {"is_sorted_and_has_non_nan", (PyCFunction)Py_is_sorted_and_has_non_nan, METH_O, Py_is_sorted_and_has_non_nan__doc__}, + {NULL} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, "_path", NULL, 0, module_functions +}; + +PyMODINIT_FUNC PyInit__path(void) +{ + import_array(); + return PyModule_Create(&moduledef); +} diff --git a/contrib/python/matplotlib/py3/src/_qhull_wrapper.cpp b/contrib/python/matplotlib/py3/src/_qhull_wrapper.cpp new file mode 100644 index 0000000000..7e4f306305 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_qhull_wrapper.cpp @@ -0,0 +1,340 @@ +/* + * Wrapper module for libqhull, providing Delaunay triangulation. + * + * This module's methods should not be accessed directly. To obtain a Delaunay + * triangulation, construct an instance of the matplotlib.tri.Triangulation + * class without specifying a triangles array. + */ +#define PY_SSIZE_T_CLEAN +#include "Python.h" +#include "numpy_cpp.h" +#ifdef _MSC_VER +/* The Qhull header does not declare this as extern "C", but only MSVC seems to + * do name mangling on global variables. We thus need to declare this before + * the header so that it treats it correctly, and doesn't mangle the name. */ +extern "C" { +extern const char qh_version[]; +} +#endif +#include "libqhull_r/qhull_ra.h" +#include <cstdio> +#include <vector> + + +#ifndef MPL_DEVNULL +#error "MPL_DEVNULL must be defined as the OS-equivalent of /dev/null" +#endif + +#define STRINGIFY(x) STR(x) +#define STR(x) #x + + +static const char* qhull_error_msg[6] = { + "", /* 0 = qh_ERRnone */ + "input inconsistency", /* 1 = qh_ERRinput */ + "singular input data", /* 2 = qh_ERRsingular */ + "precision error", /* 3 = qh_ERRprec */ + "insufficient memory", /* 4 = qh_ERRmem */ + "internal error"}; /* 5 = qh_ERRqhull */ + + +/* Return the indices of the 3 vertices that comprise the specified facet (i.e. + * triangle). */ +static void +get_facet_vertices(qhT* qh, const facetT* facet, int indices[3]) +{ + vertexT *vertex, **vertexp; + FOREACHvertex_(facet->vertices) { + *indices++ = qh_pointid(qh, vertex->point); + } +} + +/* Return the indices of the 3 triangles that are neighbors of the specified + * facet (triangle). */ +static void +get_facet_neighbours(const facetT* facet, std::vector<int>& tri_indices, + int indices[3]) +{ + facetT *neighbor, **neighborp; + FOREACHneighbor_(facet) { + *indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id]); + } +} + +/* Return true if the specified points arrays contain at least 3 unique points, + * or false otherwise. */ +static bool +at_least_3_unique_points(npy_intp npoints, const double* x, const double* y) +{ + int i; + const int unique1 = 0; /* First unique point has index 0. */ + int unique2 = 0; /* Second unique point index is 0 until set. */ + + if (npoints < 3) { + return false; + } + + for (i = 1; i < npoints; ++i) { + if (unique2 == 0) { + /* Looking for second unique point. */ + if (x[i] != x[unique1] || y[i] != y[unique1]) { + unique2 = i; + } + } + else { + /* Looking for third unique point. */ + if ( (x[i] != x[unique1] || y[i] != y[unique1]) && + (x[i] != x[unique2] || y[i] != y[unique2]) ) { + /* 3 unique points found, with indices 0, unique2 and i. */ + return true; + } + } + } + + /* Run out of points before 3 unique points found. */ + return false; +} + +/* Holds on to info from Qhull so that it can be destructed automatically. */ +class QhullInfo { +public: + QhullInfo(FILE *error_file, qhT* qh) { + this->error_file = error_file; + this->qh = qh; + } + + ~QhullInfo() { + qh_freeqhull(this->qh, !qh_ALL); + int curlong, totlong; /* Memory remaining. */ + qh_memfreeshort(this->qh, &curlong, &totlong); + if (curlong || totlong) { + PyErr_WarnEx(PyExc_RuntimeWarning, + "Qhull could not free all allocated memory", 1); + } + + if (this->error_file != stderr) { + fclose(error_file); + } + } + +private: + FILE* error_file; + qhT* qh; +}; + +/* Delaunay implementation method. + * If hide_qhull_errors is true then qhull error messages are discarded; + * if it is false then they are written to stderr. */ +static PyObject* +delaunay_impl(npy_intp npoints, const double* x, const double* y, + bool hide_qhull_errors) +{ + qhT qh_qh; /* qh variable type and name must be like */ + qhT* qh = &qh_qh; /* this for Qhull macros to work correctly. */ + facetT* facet; + int i, ntri, max_facet_id; + int exitcode; /* Value returned from qh_new_qhull(). */ + const int ndim = 2; + double x_mean = 0.0; + double y_mean = 0.0; + + QHULL_LIB_CHECK + + /* Allocate points. */ + std::vector<coordT> points(npoints * ndim); + + /* Determine mean x, y coordinates. */ + for (i = 0; i < npoints; ++i) { + x_mean += x[i]; + y_mean += y[i]; + } + x_mean /= npoints; + y_mean /= npoints; + + /* Prepare points array to pass to qhull. */ + for (i = 0; i < npoints; ++i) { + points[2*i ] = x[i] - x_mean; + points[2*i+1] = y[i] - y_mean; + } + + /* qhull expects a FILE* to write errors to. */ + FILE* error_file = NULL; + if (hide_qhull_errors) { + /* qhull errors are ignored by writing to OS-equivalent of /dev/null. + * Rather than have OS-specific code here, instead it is determined by + * setupext.py and passed in via the macro MPL_DEVNULL. */ + error_file = fopen(STRINGIFY(MPL_DEVNULL), "w"); + if (error_file == NULL) { + throw std::runtime_error("Could not open devnull"); + } + } + else { + /* qhull errors written to stderr. */ + error_file = stderr; + } + + /* Perform Delaunay triangulation. */ + QhullInfo info(error_file, qh); + qh_zero(qh, error_file); + exitcode = qh_new_qhull(qh, ndim, (int)npoints, points.data(), False, + (char*)"qhull d Qt Qbb Qc Qz", NULL, error_file); + if (exitcode != qh_ERRnone) { + PyErr_Format(PyExc_RuntimeError, + "Error in qhull Delaunay triangulation calculation: %s (exitcode=%d)%s", + qhull_error_msg[exitcode], exitcode, + hide_qhull_errors ? "; use python verbose option (-v) to see original qhull error." : ""); + return NULL; + } + + /* Split facets so that they only have 3 points each. */ + qh_triangulate(qh); + + /* Determine ntri and max_facet_id. + Note that libqhull uses macros to iterate through collections. */ + ntri = 0; + FORALLfacets { + if (!facet->upperdelaunay) { + ++ntri; + } + } + + max_facet_id = qh->facet_id - 1; + + /* Create array to map facet id to triangle index. */ + std::vector<int> tri_indices(max_facet_id+1); + + /* Allocate Python arrays to return. */ + npy_intp dims[2] = {ntri, 3}; + numpy::array_view<int, ndim> triangles(dims); + int* triangles_ptr = triangles.data(); + + numpy::array_view<int, ndim> neighbors(dims); + int* neighbors_ptr = neighbors.data(); + + /* Determine triangles array and set tri_indices array. */ + i = 0; + FORALLfacets { + if (!facet->upperdelaunay) { + int indices[3]; + tri_indices[facet->id] = i++; + get_facet_vertices(qh, facet, indices); + *triangles_ptr++ = (facet->toporient ? indices[0] : indices[2]); + *triangles_ptr++ = indices[1]; + *triangles_ptr++ = (facet->toporient ? indices[2] : indices[0]); + } + else { + tri_indices[facet->id] = -1; + } + } + + /* Determine neighbors array. */ + FORALLfacets { + if (!facet->upperdelaunay) { + int indices[3]; + get_facet_neighbours(facet, tri_indices, indices); + *neighbors_ptr++ = (facet->toporient ? indices[2] : indices[0]); + *neighbors_ptr++ = (facet->toporient ? indices[0] : indices[2]); + *neighbors_ptr++ = indices[1]; + } + } + + PyObject* tuple = PyTuple_New(2); + if (tuple == 0) { + throw std::runtime_error("Failed to create Python tuple"); + } + + PyTuple_SET_ITEM(tuple, 0, triangles.pyobj()); + PyTuple_SET_ITEM(tuple, 1, neighbors.pyobj()); + return tuple; +} + +/* Process Python arguments and call Delaunay implementation method. */ +static PyObject* +delaunay(PyObject *self, PyObject *args) +{ + numpy::array_view<double, 1> xarray; + numpy::array_view<double, 1> yarray; + PyObject* ret; + npy_intp npoints; + const double* x; + const double* y; + int verbose = 0; + + if (!PyArg_ParseTuple(args, "O&O&i:delaunay", + &xarray.converter_contiguous, &xarray, + &yarray.converter_contiguous, &yarray, + &verbose)) { + return NULL; + } + + npoints = xarray.dim(0); + if (npoints != yarray.dim(0)) { + PyErr_SetString(PyExc_ValueError, + "x and y must be 1D arrays of the same length"); + return NULL; + } + + if (npoints < 3) { + PyErr_SetString(PyExc_ValueError, + "x and y arrays must have a length of at least 3"); + return NULL; + } + + x = xarray.data(); + y = yarray.data(); + + if (!at_least_3_unique_points(npoints, x, y)) { + PyErr_SetString(PyExc_ValueError, + "x and y arrays must consist of at least 3 unique points"); + return NULL; + } + + CALL_CPP("qhull.delaunay", + (ret = delaunay_impl(npoints, x, y, verbose == 0))); + + return ret; +} + +/* Return qhull version string for assistance in debugging. */ +static PyObject* +version(PyObject *self, PyObject *arg) +{ + return PyBytes_FromString(qh_version); +} + +static PyMethodDef qhull_methods[] = { + {"delaunay", delaunay, METH_VARARGS, + "delaunay(x, y, verbose, /)\n" + "--\n\n" + "Compute a Delaunay triangulation.\n" + "\n" + "Parameters\n" + "----------\n" + "x, y : 1d arrays\n" + " The coordinates of the point set, which must consist of at least\n" + " three unique points.\n" + "verbose : int\n" + " Python's verbosity level.\n" + "\n" + "Returns\n" + "-------\n" + "triangles, neighbors : int arrays, shape (ntri, 3)\n" + " Indices of triangle vertices and indices of triangle neighbors.\n" + }, + {"version", version, METH_NOARGS, + "version()\n--\n\n" + "Return the qhull version string."}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef qhull_module = { + PyModuleDef_HEAD_INIT, + "qhull", "Computing Delaunay triangulations.\n", -1, qhull_methods +}; + +PyMODINIT_FUNC +PyInit__qhull(void) +{ + import_array(); + return PyModule_Create(&qhull_module); +} diff --git a/contrib/python/matplotlib/py3/src/_tkagg.cpp b/contrib/python/matplotlib/py3/src/_tkagg.cpp new file mode 100644 index 0000000000..5c36b3f07f --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_tkagg.cpp @@ -0,0 +1,370 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +// Where is PIL? +// +// Many years ago, Matplotlib used to include code from PIL (the Python Imaging +// Library). Since then, the code has changed a lot - the organizing principle +// and methods of operation are now quite different. Because our review of +// the codebase showed that all the code that came from PIL was removed or +// rewritten, we have removed the PIL licensing information. If you want PIL, +// you can get it at https://python-pillow.org/ + +#define PY_SSIZE_T_CLEAN +#include <Python.h> + +#ifdef _WIN32 +#define WIN32_DLL +#endif +#ifdef __CYGWIN__ +/* + * Unfortunately cygwin's libdl inherits restrictions from the underlying + * Windows OS, at least currently. Therefore, a symbol may be loaded from a + * module by dlsym() only if it is really located in the given module, + * dependencies are not included. So we have to use native WinAPI on Cygwin + * also. + */ +#define WIN32_DLL +static inline PyObject *PyErr_SetFromWindowsErr(int ierr) { + PyErr_SetString(PyExc_OSError, "Call to EnumProcessModules failed"); + return NULL; +} +#endif + +#ifdef WIN32_DLL +#include <string> +#include <windows.h> +#include <commctrl.h> +#define PSAPI_VERSION 1 +#include <psapi.h> // Must be linked with 'psapi' library +#define dlsym GetProcAddress +#else +#include <dlfcn.h> +#endif + +// Include our own excerpts from the Tcl / Tk headers +#include "_tkmini.h" + +static int convert_voidptr(PyObject *obj, void *p) +{ + void **val = (void **)p; + *val = PyLong_AsVoidPtr(obj); + return *val != NULL ? 1 : !PyErr_Occurred(); +} + +// Global vars for Tk functions. We load these symbols from the tkinter +// extension module or loaded Tk libraries at run-time. +static Tk_FindPhoto_t TK_FIND_PHOTO; +static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK; +// Global vars for Tcl functions. We load these symbols from the tkinter +// extension module or loaded Tcl libraries at run-time. +static Tcl_SetVar_t TCL_SETVAR; + +static PyObject *mpl_tk_blit(PyObject *self, PyObject *args) +{ + Tcl_Interp *interp; + char const *photo_name; + int height, width; + unsigned char *data_ptr; + int comp_rule; + int put_retval; + int o0, o1, o2, o3; + int x1, x2, y1, y2; + Tk_PhotoHandle photo; + Tk_PhotoImageBlock block; + if (!PyArg_ParseTuple(args, "O&s(iiO&)i(iiii)(iiii):blit", + convert_voidptr, &interp, &photo_name, + &height, &width, convert_voidptr, &data_ptr, + &comp_rule, + &o0, &o1, &o2, &o3, + &x1, &x2, &y1, &y2)) { + goto exit; + } + if (!(photo = TK_FIND_PHOTO(interp, photo_name))) { + PyErr_SetString(PyExc_ValueError, "Failed to extract Tk_PhotoHandle"); + goto exit; + } + if (0 > y1 || y1 > y2 || y2 > height || 0 > x1 || x1 > x2 || x2 > width) { + PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds"); + goto exit; + } + if (comp_rule != TK_PHOTO_COMPOSITE_OVERLAY && comp_rule != TK_PHOTO_COMPOSITE_SET) { + PyErr_SetString(PyExc_ValueError, "Invalid comp_rule argument"); + goto exit; + } + + Py_BEGIN_ALLOW_THREADS + block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1); + block.width = x2 - x1; + block.height = y2 - y1; + block.pitch = 4 * width; + block.pixelSize = 4; + block.offset[0] = o0; + block.offset[1] = o1; + block.offset[2] = o2; + block.offset[3] = o3; + put_retval = TK_PHOTO_PUT_BLOCK( + interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule); + Py_END_ALLOW_THREADS + if (put_retval == TCL_ERROR) { + return PyErr_NoMemory(); + } + +exit: + if (PyErr_Occurred()) { + return NULL; + } else { + Py_RETURN_NONE; + } +} + +#ifdef WIN32_DLL +LRESULT CALLBACK +DpiSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, + UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + switch (uMsg) { + case WM_DPICHANGED: + // This function is a subclassed window procedure, and so is run during + // the Tcl/Tk event loop. Unfortunately, Tkinter has a *second* lock on + // Tcl threading that is not exposed publicly, but is currently taken + // while we're in the window procedure. So while we can take the GIL to + // call Python code, we must not also call *any* Tk code from Python. + // So stay with Tcl calls in C only. + { + // This variable naming must match the name used in + // lib/matplotlib/backends/_backend_tk.py:FigureManagerTk. + std::string var_name("window_dpi"); + var_name += std::to_string((unsigned long long)hwnd); + + // X is high word, Y is low word, but they are always equal. + std::string dpi = std::to_string(LOWORD(wParam)); + + Tcl_Interp* interp = (Tcl_Interp*)dwRefData; + TCL_SETVAR(interp, var_name.c_str(), dpi.c_str(), 0); + } + return 0; + case WM_NCDESTROY: + RemoveWindowSubclass(hwnd, DpiSubclassProc, uIdSubclass); + break; + } + + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} +#endif + +static PyObject* +mpl_tk_enable_dpi_awareness(PyObject* self, PyObject*const* args, + Py_ssize_t nargs) +{ + if (nargs != 2) { + return PyErr_Format(PyExc_TypeError, + "enable_dpi_awareness() takes 2 positional " + "arguments but %zd were given", + nargs); + } + +#ifdef WIN32_DLL + HWND frame_handle = NULL; + Tcl_Interp *interp = NULL; + + if (!convert_voidptr(args[0], &frame_handle)) { + return NULL; + } + if (!convert_voidptr(args[1], &interp)) { + return NULL; + } + +#ifdef _DPI_AWARENESS_CONTEXTS_ + HMODULE user32 = LoadLibrary("user32.dll"); + + typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext_t)(HWND); + GetWindowDpiAwarenessContext_t GetWindowDpiAwarenessContextPtr = + (GetWindowDpiAwarenessContext_t)GetProcAddress( + user32, "GetWindowDpiAwarenessContext"); + if (GetWindowDpiAwarenessContextPtr == NULL) { + FreeLibrary(user32); + Py_RETURN_FALSE; + } + + typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual_t)(DPI_AWARENESS_CONTEXT, + DPI_AWARENESS_CONTEXT); + AreDpiAwarenessContextsEqual_t AreDpiAwarenessContextsEqualPtr = + (AreDpiAwarenessContextsEqual_t)GetProcAddress( + user32, "AreDpiAwarenessContextsEqual"); + if (AreDpiAwarenessContextsEqualPtr == NULL) { + FreeLibrary(user32); + Py_RETURN_FALSE; + } + + DPI_AWARENESS_CONTEXT ctx = GetWindowDpiAwarenessContextPtr(frame_handle); + bool per_monitor = ( + AreDpiAwarenessContextsEqualPtr( + ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) || + AreDpiAwarenessContextsEqualPtr( + ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)); + + if (per_monitor) { + // Per monitor aware means we need to handle WM_DPICHANGED by wrapping + // the Window Procedure, and the Python side needs to trace the Tk + // window_dpi variable stored on interp. + SetWindowSubclass(frame_handle, DpiSubclassProc, 0, (DWORD_PTR)interp); + } + FreeLibrary(user32); + return PyBool_FromLong(per_monitor); +#endif +#endif + + Py_RETURN_NONE; +} + +static PyMethodDef functions[] = { + { "blit", (PyCFunction)mpl_tk_blit, METH_VARARGS }, + { "enable_dpi_awareness", (PyCFunction)mpl_tk_enable_dpi_awareness, + METH_FASTCALL }, + { NULL, NULL } /* sentinel */ +}; + +// Functions to fill global Tcl/Tk function pointers by dynamic loading. + +template <class T> +bool load_tcl_tk(T lib) +{ + // Try to fill Tcl/Tk global vars with function pointers. Return whether + // all of them have been filled. + if (auto ptr = dlsym(lib, "Tcl_SetVar")) { + TCL_SETVAR = (Tcl_SetVar_t)ptr; + } + if (auto ptr = dlsym(lib, "Tk_FindPhoto")) { + TK_FIND_PHOTO = (Tk_FindPhoto_t)ptr; + } + if (auto ptr = dlsym(lib, "Tk_PhotoPutBlock")) { + TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)ptr; + } + return TCL_SETVAR && TK_FIND_PHOTO && TK_PHOTO_PUT_BLOCK; +} + +#ifdef WIN32_DLL + +/* On Windows, we can't load the tkinter module to get the Tcl/Tk symbols, + * because Windows does not load symbols into the library name-space of + * importing modules. So, knowing that tkinter has already been imported by + * Python, we scan all modules in the running process for the Tcl/Tk function + * names. + */ + +void load_tkinter_funcs(void) +{ + HANDLE process = GetCurrentProcess(); // Pseudo-handle, doesn't need closing. + HMODULE* modules = NULL; + DWORD size; + if (!EnumProcessModules(process, NULL, 0, &size)) { + PyErr_SetFromWindowsErr(0); + goto exit; + } + if (!(modules = static_cast<HMODULE*>(malloc(size)))) { + PyErr_NoMemory(); + goto exit; + } + if (!EnumProcessModules(process, modules, size, &size)) { + PyErr_SetFromWindowsErr(0); + goto exit; + } + for (unsigned i = 0; i < size / sizeof(HMODULE); ++i) { + if (load_tcl_tk(modules[i])) { + return; + } + } +exit: + free(modules); +} + +#else // not Windows + +/* + * On Unix, we can get the Tk symbols from the tkinter module, because tkinter + * uses these symbols, and the symbols are therefore visible in the tkinter + * dynamic library (module). + */ + +void load_tkinter_funcs(void) +{ + // Load tkinter global funcs from tkinter compiled module. + void *main_program = NULL, *tkinter_lib = NULL; + PyObject *module = NULL, *py_path = NULL, *py_path_b = NULL; + char *path; + + // Try loading from the main program namespace first. + main_program = dlopen(NULL, RTLD_LAZY); + if (load_tcl_tk(main_program)) { + goto exit; + } + // Clear exception triggered when we didn't find symbols above. + PyErr_Clear(); + + // Handle PyPy first, as that import will correctly fail on CPython. + module = PyImport_ImportModule("_tkinter.tklib_cffi"); // PyPy + if (!module) { + PyErr_Clear(); + module = PyImport_ImportModule("_tkinter"); // CPython + } + if (!(module && + (py_path = PyObject_GetAttrString(module, "__file__")) && + (py_path_b = PyUnicode_EncodeFSDefault(py_path)) && + (path = PyBytes_AsString(py_path_b)))) { + goto exit; + } + tkinter_lib = dlopen(path, RTLD_LAZY); + if (!tkinter_lib) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + goto exit; + } + if (load_tcl_tk(tkinter_lib)) { + goto exit; + } + +exit: + // We don't need to keep a reference open as the main program & tkinter + // have been imported. Try to close each library separately (otherwise the + // second dlclose could clear a dlerror from the first dlclose). + bool raised_dlerror = false; + if (main_program && dlclose(main_program) && !raised_dlerror) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + raised_dlerror = true; + } + if (tkinter_lib && dlclose(tkinter_lib) && !raised_dlerror) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + raised_dlerror = true; + } + Py_XDECREF(module); + Py_XDECREF(py_path); + Py_XDECREF(py_path_b); +} +#endif // end not Windows + +static PyModuleDef _tkagg_module = { + PyModuleDef_HEAD_INIT, "_tkagg", NULL, -1, functions +}; + +PyMODINIT_FUNC PyInit__tkagg(void) +{ + load_tkinter_funcs(); + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + // Always raise ImportError (normalizing a previously set exception if + // needed) to interact properly with backend auto-fallback. + if (value) { + PyErr_NormalizeException(&type, &value, &traceback); + PyErr_SetObject(PyExc_ImportError, value); + return NULL; + } else if (!TCL_SETVAR) { + PyErr_SetString(PyExc_ImportError, "Failed to load Tcl_SetVar"); + return NULL; + } else if (!TK_FIND_PHOTO) { + PyErr_SetString(PyExc_ImportError, "Failed to load Tk_FindPhoto"); + return NULL; + } else if (!TK_PHOTO_PUT_BLOCK) { + PyErr_SetString(PyExc_ImportError, "Failed to load Tk_PhotoPutBlock"); + return NULL; + } + return PyModule_Create(&_tkagg_module); +} diff --git a/contrib/python/matplotlib/py3/src/_tkmini.h b/contrib/python/matplotlib/py3/src/_tkmini.h new file mode 100644 index 0000000000..85f245815e --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_tkmini.h @@ -0,0 +1,110 @@ +/* Small excerpts from the Tcl / Tk 8.6 headers + * + * License terms copied from: + * http://www.tcl.tk/software/tcltk/license.html + * as of 20 May 2016. + * + * Copyright (c) 1987-1994 The Regents of the University of California. + * Copyright (c) 1993-1996 Lucent Technologies. + * Copyright (c) 1994-1998 Sun Microsystems, Inc. + * Copyright (c) 1998-2000 by Scriptics Corporation. + * Copyright (c) 2002 by Kevin B. Kenny. All rights reserved. + * + * This software is copyrighted by the Regents of the University + * of California, Sun Microsystems, Inc., Scriptics Corporation, + * and other parties. The following terms apply to all files + * associated with the software unless explicitly disclaimed in + * individual files. + * + * The authors hereby grant permission to use, copy, modify, + * distribute, and license this software and its documentation + * for any purpose, provided that existing copyright notices are + * retained in all copies and that this notice is included + * verbatim in any distributions. No written agreement, license, + * or royalty fee is required for any of the authorized uses. + * Modifications to this software may be copyrighted by their + * authors and need not follow the licensing terms described + * here, provided that the new terms are clearly indicated on + * the first page of each file where they apply. + * + * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO + * ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS + * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN + * IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON + * AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO + * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + * + * GOVERNMENT USE: If you are acquiring this software on behalf + * of the U.S. government, the Government shall have only + * "Restricted Rights" in the software and related documentation + * as defined in the Federal Acquisition Regulations (FARs) in + * Clause 52.227.19 (c) (2). If you are acquiring the software + * on behalf of the Department of Defense, the software shall be + * classified as "Commercial Computer Software" and the + * Government shall have only "Restricted Rights" as defined in + * Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the + * foregoing, the authors grant the U.S. Government and others + * acting in its behalf permission to use and distribute the + * software in accordance with the terms specified in this + * license + */ + +/* + * Unless otherwise noted, these definitions are stable from Tcl / Tk 8.5 + * through Tck / Tk master as of 21 May 2016 + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Users of versions of Tcl >= 8.6 encouraged to treat Tcl_Interp as an opaque + * pointer. The following definition results when TCL_NO_DEPRECATED defined. + */ +typedef struct Tcl_Interp Tcl_Interp; + +/* Tk header excerpts */ + +typedef void *Tk_PhotoHandle; + +typedef struct Tk_PhotoImageBlock +{ + unsigned char *pixelPtr; + int width; + int height; + int pitch; + int pixelSize; + int offset[4]; +} Tk_PhotoImageBlock; + +#define TK_PHOTO_COMPOSITE_OVERLAY 0 // apply transparency rules pixel-wise +#define TK_PHOTO_COMPOSITE_SET 1 // set image buffer directly +#define TCL_OK 0 +#define TCL_ERROR 1 + +/* Typedefs derived from function signatures in Tk header */ +/* Tk_FindPhoto typedef */ +typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char + *imageName); +/* Tk_PhotoPutBLock typedef */ +typedef int (*Tk_PhotoPutBlock_t) (Tcl_Interp *interp, Tk_PhotoHandle handle, + Tk_PhotoImageBlock *blockPtr, int x, int y, + int width, int height, int compRule); + +/* Typedefs derived from function signatures in Tcl header */ +/* Tcl_SetVar typedef */ +typedef const char *(*Tcl_SetVar_t)(Tcl_Interp *interp, const char *varName, + const char *newValue, int flags); + +#ifdef __cplusplus +} +#endif diff --git a/contrib/python/matplotlib/py3/src/_ttconv.cpp b/contrib/python/matplotlib/py3/src/_ttconv.cpp new file mode 100644 index 0000000000..72fdfba696 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_ttconv.cpp @@ -0,0 +1,96 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +/* + _ttconv.c + + Python wrapper for TrueType conversion library in ../ttconv. + */ +#include "mplutils.h" + +#include <pybind11/pybind11.h> +#include "ttconv/pprdrv.h" +#include <vector> + +namespace py = pybind11; + +/** + * An implementation of TTStreamWriter that writes to a Python + * file-like object. + */ +class PythonFileWriter : public TTStreamWriter +{ + py::function _write_method; + + public: + PythonFileWriter(py::object& file_object) + : _write_method(file_object.attr("write")) {} + + virtual void write(const char *a) + { + PyObject* decoded = PyUnicode_DecodeLatin1(a, strlen(a), ""); + if (decoded == NULL) { + throw py::error_already_set(); + } + _write_method(py::handle(decoded)); + Py_DECREF(decoded); + } +}; + +static void convert_ttf_to_ps( + const char *filename, + py::object &output, + int fonttype, + py::iterable* glyph_ids) +{ + PythonFileWriter output_(output); + + std::vector<int> glyph_ids_; + if (glyph_ids) { + for (py::handle glyph_id: *glyph_ids) { + glyph_ids_.push_back(glyph_id.cast<int>()); + } + } + + if (fonttype != 3 && fonttype != 42) { + throw py::value_error( + "fonttype must be either 3 (raw Postscript) or 42 (embedded Truetype)"); + } + + try + { + insert_ttfont(filename, output_, static_cast<font_type_enum>(fonttype), glyph_ids_); + } + catch (TTException &e) + { + throw std::runtime_error(e.getMessage()); + } + catch (...) + { + throw std::runtime_error("Unknown C++ exception"); + } +} + +PYBIND11_MODULE(_ttconv, m) { + m.doc() = "Module to handle converting and subsetting TrueType " + "fonts to Postscript Type 3, Postscript Type 42 and " + "Pdf Type 3 fonts."; + m.def("convert_ttf_to_ps", &convert_ttf_to_ps, + py::arg("filename"), + py::arg("output"), + py::arg("fonttype"), + py::arg("glyph_ids") = py::none(), + "Converts the Truetype font into a Type 3 or Type 42 Postscript font, " + "optionally subsetting the font to only the desired set of characters.\n" + "\n" + "filename is the path to a TTF font file.\n" + "output is a Python file-like object with a write method that the Postscript " + "font data will be written to.\n" + "fonttype may be either 3 or 42. Type 3 is a \"raw Postscript\" font. " + "Type 42 is an embedded Truetype font. Glyph subsetting is not supported " + "for Type 42 fonts within this module (needs to be done externally).\n" + "glyph_ids (optional) is a list of glyph ids (integers) to keep when " + "subsetting to a Type 3 font. If glyph_ids is not provided or is None, " + "then all glyphs will be included. If any of the glyphs specified are " + "composite glyphs, then the component glyphs will also be included." + ); +} diff --git a/contrib/python/matplotlib/py3/src/agg_workaround.h b/contrib/python/matplotlib/py3/src/agg_workaround.h new file mode 100644 index 0000000000..4762195192 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/agg_workaround.h @@ -0,0 +1,85 @@ +#ifndef MPL_AGG_WORKAROUND_H +#define MPL_AGG_WORKAROUND_H + +#include "agg_pixfmt_rgba.h" + +/********************************************************************** + WORKAROUND: This class is to workaround a bug in Agg SVN where the + blending of RGBA32 pixels does not preserve enough precision +*/ + +template<class ColorT, class Order> +struct fixed_blender_rgba_pre : agg::conv_rgba_pre<ColorT, Order> +{ + typedef ColorT color_type; + typedef Order order_type; + typedef typename color_type::value_type value_type; + typedef typename color_type::calc_type calc_type; + typedef typename color_type::long_type long_type; + enum base_scale_e + { + base_shift = color_type::base_shift, + base_mask = color_type::base_mask + }; + + //-------------------------------------------------------------------- + static AGG_INLINE void blend_pix(value_type* p, + value_type cr, value_type cg, value_type cb, + value_type alpha, agg::cover_type cover) + { + blend_pix(p, + color_type::mult_cover(cr, cover), + color_type::mult_cover(cg, cover), + color_type::mult_cover(cb, cover), + color_type::mult_cover(alpha, cover)); + } + + //-------------------------------------------------------------------- + static AGG_INLINE void blend_pix(value_type* p, + value_type cr, value_type cg, value_type cb, + value_type alpha) + { + alpha = base_mask - alpha; + p[Order::R] = (value_type)(((p[Order::R] * alpha) >> base_shift) + cr); + p[Order::G] = (value_type)(((p[Order::G] * alpha) >> base_shift) + cg); + p[Order::B] = (value_type)(((p[Order::B] * alpha) >> base_shift) + cb); + p[Order::A] = (value_type)(base_mask - ((alpha * (base_mask - p[Order::A])) >> base_shift)); + } +}; + + +template<class ColorT, class Order> +struct fixed_blender_rgba_plain : agg::conv_rgba_plain<ColorT, Order> +{ + typedef ColorT color_type; + typedef Order order_type; + typedef typename color_type::value_type value_type; + typedef typename color_type::calc_type calc_type; + typedef typename color_type::long_type long_type; + enum base_scale_e { base_shift = color_type::base_shift }; + + //-------------------------------------------------------------------- + static AGG_INLINE void blend_pix(value_type* p, + value_type cr, value_type cg, value_type cb, value_type alpha, agg::cover_type cover) + { + blend_pix(p, cr, cg, cb, color_type::mult_cover(alpha, cover)); + } + + //-------------------------------------------------------------------- + static AGG_INLINE void blend_pix(value_type* p, + value_type cr, value_type cg, value_type cb, value_type alpha) + { + if(alpha == 0) return; + calc_type a = p[Order::A]; + calc_type r = p[Order::R] * a; + calc_type g = p[Order::G] * a; + calc_type b = p[Order::B] * a; + a = ((alpha + a) << base_shift) - alpha * a; + p[Order::A] = (value_type)(a >> base_shift); + p[Order::R] = (value_type)((((cr << base_shift) - r) * alpha + (r << base_shift)) / a); + p[Order::G] = (value_type)((((cg << base_shift) - g) * alpha + (g << base_shift)) / a); + p[Order::B] = (value_type)((((cb << base_shift) - b) * alpha + (b << base_shift)) / a); + } +}; + +#endif diff --git a/contrib/python/matplotlib/py3/src/array.h b/contrib/python/matplotlib/py3/src/array.h new file mode 100644 index 0000000000..47d8299554 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/array.h @@ -0,0 +1,80 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +/* Utilities to create scalars and empty arrays that behave like the + Numpy array wrappers in numpy_cpp.h */ + +#ifndef MPL_SCALAR_H +#define MPL_SCALAR_H + +namespace array +{ + +template <typename T, int ND> +class scalar +{ + public: + T m_value; + + scalar(const T value) : m_value(value) + { + } + + T &operator()(int i, int j = 0, int k = 0) + { + return m_value; + } + + const T &operator()(int i, int j = 0, int k = 0) const + { + return m_value; + } + + int dim(size_t i) + { + return 1; + } + + size_t size() + { + return 1; + } +}; + +template <typename T> +class empty +{ + public: + typedef empty<T> sub_t; + + empty() + { + } + + T &operator()(int i, int j = 0, int k = 0) + { + throw std::runtime_error("Accessed empty array"); + } + + const T &operator()(int i, int j = 0, int k = 0) const + { + throw std::runtime_error("Accessed empty array"); + } + + sub_t operator[](int i) const + { + return empty<T>(); + } + + int dim(size_t i) const + { + return 0; + } + + size_t size() const + { + return 0; + } +}; +} + +#endif diff --git a/contrib/python/matplotlib/py3/src/checkdep_freetype2.c b/contrib/python/matplotlib/py3/src/checkdep_freetype2.c new file mode 100644 index 0000000000..8d9d8ca24a --- /dev/null +++ b/contrib/python/matplotlib/py3/src/checkdep_freetype2.c @@ -0,0 +1,19 @@ +#ifdef __has_include + #if !__has_include(<ft2build.h>) + #error "FreeType version 2.3 or higher is required. \ +You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it." + #endif +#endif + +#include <ft2build.h> +#include FT_FREETYPE_H + +#define XSTR(x) STR(x) +#define STR(x) #x + +#pragma message("Compiling with FreeType version " \ + XSTR(FREETYPE_MAJOR) "." XSTR(FREETYPE_MINOR) "." XSTR(FREETYPE_PATCH) ".") +#if FREETYPE_MAJOR << 16 + FREETYPE_MINOR << 8 + FREETYPE_PATCH < 0x020300 + #error "FreeType version 2.3 or higher is required. \ +You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it." +#endif diff --git a/contrib/python/matplotlib/py3/src/ft2font.cpp b/contrib/python/matplotlib/py3/src/ft2font.cpp new file mode 100644 index 0000000000..9750413741 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/ft2font.cpp @@ -0,0 +1,840 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#define NO_IMPORT_ARRAY + +#include <algorithm> +#include <iterator> +#include <sstream> +#include <stdexcept> +#include <string> + +#include "ft2font.h" +#include "mplutils.h" +#include "numpy_cpp.h" +#include "py_exceptions.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338328 +#endif + +/** + To improve the hinting of the fonts, this code uses a hack + presented here: + + http://agg.sourceforge.net/antigrain.com/research/font_rasterization/index.html + + The idea is to limit the effect of hinting in the x-direction, while + preserving hinting in the y-direction. Since freetype does not + support this directly, the dpi in the x-direction is set higher than + in the y-direction, which affects the hinting grid. Then, a global + transform is placed on the font to shrink it back to the desired + size. While it is a bit surprising that the dpi setting affects + hinting, whereas the global transform does not, this is documented + behavior of FreeType, and therefore hopefully unlikely to change. + The FreeType 2 tutorial says: + + NOTE: The transformation is applied to every glyph that is + loaded through FT_Load_Glyph and is completely independent of + any hinting process. This means that you won't get the same + results if you load a glyph at the size of 24 pixels, or a glyph + at the size at 12 pixels scaled by 2 through a transform, + because the hints will have been computed differently (except + you have disabled hints). + */ + +FT_Library _ft2Library; + +// FreeType error codes; loaded as per fterror.h. +static char const* ft_error_string(FT_Error error) { +#undef __FTERRORS_H__ +#define FT_ERROR_START_LIST switch (error) { +#define FT_ERRORDEF( e, v, s ) case v: return s; +#define FT_ERROR_END_LIST default: return NULL; } +#include FT_ERRORS_H +} + +void throw_ft_error(std::string message, FT_Error error) { + char const* s = ft_error_string(error); + std::ostringstream os(""); + if (s) { + os << message << " (" << s << "; error code 0x" << std::hex << error << ")"; + } else { // Should not occur, but don't add another error from failed lookup. + os << message << " (error code 0x" << std::hex << error << ")"; + } + throw std::runtime_error(os.str()); +} + +FT2Image::FT2Image() : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0) +{ +} + +FT2Image::FT2Image(unsigned long width, unsigned long height) + : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0) +{ + resize(width, height); +} + +FT2Image::~FT2Image() +{ + delete[] m_buffer; +} + +void FT2Image::resize(long width, long height) +{ + 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 = NULL; + m_buffer = new unsigned char[numBytes]; + } + + m_width = (unsigned long)width; + m_height = (unsigned long)height; + } + + if (numBytes && m_buffer) { + memset(m_buffer, 0, numBytes); + } + + m_dirty = true; +} + +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 char_width = bitmap->width; + FT_Int char_height = bitmap->rows; + + FT_Int x1 = std::min(std::max(x, 0), image_width); + FT_Int y1 = std::min(std::max(y, 0), image_height); + FT_Int x2 = std::min(std::max(x + char_width, 0), image_width); + FT_Int y2 = std::min(std::max(y + char_height, 0), image_height); + + FT_Int x_start = std::max(0, -x); + FT_Int y_offset = y1 - std::max(0, -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 *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 *src = bitmap->buffer + ((i - y_offset) * bitmap->pitch); + for (FT_Int j = x1; j < x2; ++j, ++dst) { + int x = (j - x1 + x_start); + int val = *(src + (x >> 3)) & (1 << (7 - (x & 0x7))); + *dst = val ? 255 : *dst; + } + } + } else { + throw std::runtime_error("Unknown pixel mode"); + } + + m_dirty = true; +} + +void FT2Image::draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1) +{ + if (x0 > m_width || x1 > m_width || y0 > m_height || y1 > m_height) { + throw std::runtime_error("Rect coords outside image bounds"); + } + + size_t top = y0 * m_width; + size_t bottom = y1 * m_width; + for (size_t i = x0; i < x1 + 1; ++i) { + m_buffer[i + top] = 255; + m_buffer[i + bottom] = 255; + } + + for (size_t j = y0 + 1; j < y1; ++j) { + m_buffer[x0 + j * m_width] = 255; + m_buffer[x1 + j * m_width] = 255; + } + + m_dirty = true; +} + +void +FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1) +{ + x0 = std::min(x0, m_width); + y0 = std::min(y0, m_height); + x1 = std::min(x1 + 1, m_width); + y1 = std::min(y1 + 1, m_height); + + for (size_t j = y0; j < y1; j++) { + for (size_t i = x0; i < x1; i++) { + m_buffer[i + j * m_width] = 255; + } + } + + m_dirty = true; +} + +static void ft_glyph_warn(FT_ULong charcode) +{ + PyObject *text_helpers = NULL, *tmp = NULL; + if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) || + !(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "k", charcode))) { + goto exit; + } +exit: + Py_XDECREF(text_helpers); + Py_XDECREF(tmp); + if (PyErr_Occurred()) { + throw py::exception(); + } +} + +static FT_UInt +ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode, bool warn = true) +{ + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + if (glyph_index) { + return glyph_index; + } + if (warn) { + ft_glyph_warn(charcode); + } + return 0; +} + +// ft_outline_decomposer should be passed to FT_Outline_Decompose. On the +// first pass, vertices and codes are set to NULL, and index is simply +// incremented for each vertex that should be inserted, so that it is set, at +// the end, to the total number of vertices. On a second pass, vertices and +// codes should point to correctly sized arrays, and index set again to zero, +// to get fill vertices and codes with the outline decomposition. +struct ft_outline_decomposer +{ + int index; + double* vertices; + unsigned char* codes; +}; + +static int +ft_outline_move_to(FT_Vector const* to, void* user) +{ + ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user); + if (d->codes) { + if (d->index) { + // Appending CLOSEPOLY is important to make patheffects work. + *(d->vertices++) = 0; + *(d->vertices++) = 0; + *(d->codes++) = CLOSEPOLY; + } + *(d->vertices++) = to->x * (1. / 64.); + *(d->vertices++) = to->y * (1. / 64.); + *(d->codes++) = MOVETO; + } + d->index += d->index ? 2 : 1; + return 0; +} + +static int +ft_outline_line_to(FT_Vector const* to, void* user) +{ + ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user); + if (d->codes) { + *(d->vertices++) = to->x * (1. / 64.); + *(d->vertices++) = to->y * (1. / 64.); + *(d->codes++) = LINETO; + } + d->index++; + return 0; +} + +static int +ft_outline_conic_to(FT_Vector const* control, FT_Vector const* to, void* user) +{ + ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user); + if (d->codes) { + *(d->vertices++) = control->x * (1. / 64.); + *(d->vertices++) = control->y * (1. / 64.); + *(d->vertices++) = to->x * (1. / 64.); + *(d->vertices++) = to->y * (1. / 64.); + *(d->codes++) = CURVE3; + *(d->codes++) = CURVE3; + } + d->index += 2; + return 0; +} + +static int +ft_outline_cubic_to( + FT_Vector const* c1, FT_Vector const* c2, FT_Vector const* to, void* user) +{ + ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user); + if (d->codes) { + *(d->vertices++) = c1->x * (1. / 64.); + *(d->vertices++) = c1->y * (1. / 64.); + *(d->vertices++) = c2->x * (1. / 64.); + *(d->vertices++) = c2->y * (1. / 64.); + *(d->vertices++) = to->x * (1. / 64.); + *(d->vertices++) = to->y * (1. / 64.); + *(d->codes++) = CURVE4; + *(d->codes++) = CURVE4; + *(d->codes++) = CURVE4; + } + d->index += 3; + return 0; +} + +static FT_Outline_Funcs ft_outline_funcs = { + ft_outline_move_to, + ft_outline_line_to, + ft_outline_conic_to, + ft_outline_cubic_to}; + +PyObject* +FT2Font::get_path() +{ + if (!face->glyph) { + PyErr_SetString(PyExc_RuntimeError, "No glyph loaded"); + return NULL; + } + ft_outline_decomposer decomposer = {}; + if (FT_Error error = + FT_Outline_Decompose( + &face->glyph->outline, &ft_outline_funcs, &decomposer)) { + PyErr_Format(PyExc_RuntimeError, + "FT_Outline_Decompose failed with error 0x%x", error); + return NULL; + } + if (!decomposer.index) { // Don't append CLOSEPOLY to null glyphs. + npy_intp vertices_dims[2] = { 0, 2 }; + numpy::array_view<double, 2> vertices(vertices_dims); + npy_intp codes_dims[1] = { 0 }; + numpy::array_view<unsigned char, 1> codes(codes_dims); + return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); + } + npy_intp vertices_dims[2] = { decomposer.index + 1, 2 }; + numpy::array_view<double, 2> vertices(vertices_dims); + npy_intp codes_dims[1] = { decomposer.index + 1 }; + numpy::array_view<unsigned char, 1> codes(codes_dims); + decomposer.index = 0; + decomposer.vertices = vertices.data(); + decomposer.codes = codes.data(); + if (FT_Error error = + FT_Outline_Decompose( + &face->glyph->outline, &ft_outline_funcs, &decomposer)) { + PyErr_Format(PyExc_RuntimeError, + "FT_Outline_Decompose failed with error 0x%x", error); + return NULL; + } + *(decomposer.vertices++) = 0; + *(decomposer.vertices++) = 0; + *(decomposer.codes++) = CLOSEPOLY; + return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); +} + +FT2Font::FT2Font(FT_Open_Args &open_args, + long hinting_factor_, + std::vector<FT2Font *> &fallback_list) + : image(), face(NULL) +{ + clear(); + + FT_Error error = FT_Open_Face(_ft2Library, &open_args, 0, &face); + if (error) { + throw_ft_error("Can not load face", error); + } + + // set default kerning factor to 0, i.e., no kerning manipulation + kerning_factor = 0; + + // set a default fontsize 12 pt at 72dpi + hinting_factor = hinting_factor_; + + error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * (unsigned int)hinting_factor, 72); + if (error) { + FT_Done_Face(face); + throw_ft_error("Could not set the fontsize", error); + } + + if (open_args.stream != NULL) { + face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM; + } + + FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; + FT_Set_Transform(face, &transform, 0); + + // Set fallbacks + std::copy(fallback_list.begin(), fallback_list.end(), std::back_inserter(fallbacks)); +} + +FT2Font::~FT2Font() +{ + for (size_t i = 0; i < glyphs.size(); i++) { + FT_Done_Glyph(glyphs[i]); + } + + if (face) { + FT_Done_Face(face); + } +} + +void FT2Font::clear() +{ + pen.x = 0; + pen.y = 0; + + for (size_t i = 0; i < glyphs.size(); i++) { + FT_Done_Glyph(glyphs[i]); + } + + glyphs.clear(); + glyph_to_font.clear(); + char_to_font.clear(); + + for (size_t i = 0; i < fallbacks.size(); i++) { + fallbacks[i]->clear(); + } +} + +void FT2Font::set_size(double ptsize, double dpi) +{ + FT_Error error = FT_Set_Char_Size( + face, (FT_F26Dot6)(ptsize * 64), 0, (FT_UInt)(dpi * hinting_factor), (FT_UInt)dpi); + if (error) { + throw_ft_error("Could not set the fontsize", error); + } + FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; + FT_Set_Transform(face, &transform, 0); + + for (size_t i = 0; i < fallbacks.size(); i++) { + fallbacks[i]->set_size(ptsize, dpi); + } +} + +void FT2Font::set_charmap(int i) +{ + if (i >= face->num_charmaps) { + throw std::runtime_error("i exceeds the available number of char maps"); + } + FT_CharMap charmap = face->charmaps[i]; + if (FT_Error error = FT_Set_Charmap(face, charmap)) { + throw_ft_error("Could not set the charmap", error); + } +} + +void FT2Font::select_charmap(unsigned long i) +{ + if (FT_Error error = FT_Select_Charmap(face, (FT_Encoding)i)) { + throw_ft_error("Could not set the charmap", error); + } +} + +int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback = false) +{ + if (fallback && glyph_to_font.find(left) != glyph_to_font.end() && + glyph_to_font.find(right) != glyph_to_font.end()) { + FT2Font *left_ft_object = glyph_to_font[left]; + FT2Font *right_ft_object = glyph_to_font[right]; + if (left_ft_object != right_ft_object) { + // we do not know how to do kerning between different fonts + return 0; + } + // if left_ft_object is the same as right_ft_object, + // do the exact same thing which set_text does. + return right_ft_object->get_kerning(left, right, mode, false); + } + else + { + FT_Vector delta; + return get_kerning(left, right, mode, delta); + } +} + +int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta) +{ + if (!FT_HAS_KERNING(face)) { + return 0; + } + + if (!FT_Get_Kerning(face, left, right, mode, &delta)) { + return (int)(delta.x) / (hinting_factor << kerning_factor); + } else { + return 0; + } +} + +void FT2Font::set_kerning_factor(int factor) +{ + kerning_factor = factor; + for (size_t i = 0; i < fallbacks.size(); i++) { + fallbacks[i]->set_kerning_factor(factor); + } +} + +void FT2Font::set_text( + size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys) +{ + FT_Matrix matrix; /* transformation matrix */ + + angle = angle * (2 * M_PI / 360.0); + + // this computes width and height in subpixels so we have to multiply by 64 + double cosangle = cos(angle) * 0x10000L; + double sinangle = sin(angle) * 0x10000L; + + matrix.xx = (FT_Fixed)cosangle; + matrix.xy = (FT_Fixed)-sinangle; + matrix.yx = (FT_Fixed)sinangle; + matrix.yy = (FT_Fixed)cosangle; + + clear(); + + bbox.xMin = bbox.yMin = 32000; + bbox.xMax = bbox.yMax = -32000; + + FT_UInt previous = 0; + FT2Font *previous_ft_object = NULL; + + for (size_t n = 0; n < N; n++) { + FT_UInt glyph_index = 0; + FT_BBox glyph_bbox; + FT_Pos last_advance; + + FT_Error charcode_error, glyph_error; + FT2Font *ft_object_with_glyph = this; + bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs, + char_to_font, glyph_to_font, codepoints[n], flags, + charcode_error, glyph_error, false); + if (!was_found) { + ft_glyph_warn((FT_ULong)codepoints[n]); + + // render missing glyph tofu + // come back to top-most font + ft_object_with_glyph = this; + char_to_font[codepoints[n]] = ft_object_with_glyph; + glyph_to_font[glyph_index] = ft_object_with_glyph; + ft_object_with_glyph->load_glyph(glyph_index, flags, ft_object_with_glyph, false); + } + + // retrieve kerning distance and move pen position + if ((ft_object_with_glyph == previous_ft_object) && // if both fonts are the same + ft_object_with_glyph->has_kerning() && // if the font knows how to kern + previous && glyph_index // and we really have 2 glyphs + ) { + FT_Vector delta; + pen.x += ft_object_with_glyph->get_kerning(previous, glyph_index, FT_KERNING_DEFAULT, delta); + } + + // extract glyph image and store it in our table + FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; + + last_advance = ft_object_with_glyph->get_face()->glyph->advance.x; + FT_Glyph_Transform(thisGlyph, 0, &pen); + FT_Glyph_Transform(thisGlyph, &matrix, 0); + xys.push_back(pen.x); + xys.push_back(pen.y); + + FT_Glyph_Get_CBox(thisGlyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph_bbox); + + bbox.xMin = std::min(bbox.xMin, glyph_bbox.xMin); + bbox.xMax = std::max(bbox.xMax, glyph_bbox.xMax); + bbox.yMin = std::min(bbox.yMin, glyph_bbox.yMin); + bbox.yMax = std::max(bbox.yMax, glyph_bbox.yMax); + + pen.x += last_advance; + + previous = glyph_index; + previous_ft_object = ft_object_with_glyph; + + } + + FT_Vector_Transform(&pen, &matrix); + advance = pen.x; + + if (bbox.xMin > bbox.xMax) { + bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0; + } +} + +void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback = false) +{ + // if this is parent FT2Font, cache will be filled in 2 ways: + // 1. set_text was previously called + // 2. set_text was not called and fallback was enabled + if (fallback && char_to_font.find(charcode) != char_to_font.end()) { + ft_object = char_to_font[charcode]; + // since it will be assigned to ft_object anyway + FT2Font *throwaway = NULL; + ft_object->load_char(charcode, flags, throwaway, false); + } else if (fallback) { + FT_UInt final_glyph_index; + FT_Error charcode_error, glyph_error; + FT2Font *ft_object_with_glyph = this; + bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font, + glyph_to_font, charcode, flags, charcode_error, glyph_error, true); + if (!was_found) { + ft_glyph_warn(charcode); + if (charcode_error) { + throw_ft_error("Could not load charcode", charcode_error); + } + else if (glyph_error) { + throw_ft_error("Could not load charcode", glyph_error); + } + } + ft_object = ft_object_with_glyph; + } else { + ft_object = this; + FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode); + + if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { + throw_ft_error("Could not load charcode", error); + } + FT_Glyph thisGlyph; + if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { + throw_ft_error("Could not get glyph", error); + } + glyphs.push_back(thisGlyph); + } +} + + +bool FT2Font::get_char_fallback_index(FT_ULong charcode, int& index) const +{ + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + if (glyph_index) { + // -1 means the host has the char and we do not need to fallback + index = -1; + return true; + } else { + int inner_index = 0; + bool was_found; + + for (size_t i = 0; i < fallbacks.size(); ++i) { + // TODO handle recursion somehow! + was_found = fallbacks[i]->get_char_fallback_index(charcode, inner_index); + if (was_found) { + index = i; + return true; + } + } + } + return false; +} + + +bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, + FT_UInt &final_glyph_index, + std::vector<FT_Glyph> &parent_glyphs, + std::unordered_map<long, FT2Font *> &parent_char_to_font, + std::unordered_map<FT_UInt, FT2Font *> &parent_glyph_to_font, + long charcode, + FT_Int32 flags, + FT_Error &charcode_error, + FT_Error &glyph_error, + bool override = false) +{ + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + + if (glyph_index || override) { + charcode_error = FT_Load_Glyph(face, glyph_index, flags); + if (charcode_error) { + return false; + } + + FT_Glyph thisGlyph; + glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); + if (glyph_error) { + return false; + } + + final_glyph_index = glyph_index; + + // cache the result for future + // need to store this for anytime a character is loaded from a parent + // FT2Font object or to generate a mapping of individual characters to fonts + ft_object_with_glyph = this; + parent_glyph_to_font[final_glyph_index] = this; + parent_char_to_font[charcode] = this; + parent_glyphs.push_back(thisGlyph); + return true; + } + + else { + for (size_t i = 0; i < fallbacks.size(); ++i) { + bool was_found = fallbacks[i]->load_char_with_fallback( + ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font, + parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, override); + if (was_found) { + return true; + } + } + return false; + } +} + +void FT2Font::load_glyph(FT_UInt glyph_index, + FT_Int32 flags, + FT2Font *&ft_object, + bool fallback = false) +{ + // cache is only for parent FT2Font + if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { + ft_object = glyph_to_font[glyph_index]; + } else { + ft_object = this; + } + + ft_object->load_glyph(glyph_index, flags); +} + +void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) +{ + if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { + throw_ft_error("Could not load glyph", error); + } + FT_Glyph thisGlyph; + if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { + throw_ft_error("Could not get glyph", error); + } + glyphs.push_back(thisGlyph); +} + +FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false) +{ + FT2Font *ft_object = NULL; + if (fallback && char_to_font.find(charcode) != char_to_font.end()) { + // fallback denotes whether we want to search fallback list. + // should call set_text/load_char_with_fallback to parent FT2Font before + // wanting to use fallback list here. (since that populates the cache) + ft_object = char_to_font[charcode]; + } else { + // set as self + ft_object = this; + } + + // historically, get_char_index never raises a warning + return ft_get_char_index_or_warn(ft_object->get_face(), charcode, false); +} + +void FT2Font::get_width_height(long *width, long *height) +{ + *width = advance; + *height = bbox.yMax - bbox.yMin; +} + +long FT2Font::get_descent() +{ + return -bbox.yMin; +} + +void FT2Font::get_bitmap_offset(long *x, long *y) +{ + *x = bbox.xMin; + *y = 0; +} + +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); + + for (size_t n = 0; n < glyphs.size(); n++) { + FT_Error error = FT_Glyph_To_Bitmap( + &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1); + if (error) { + throw_ft_error("Could not convert glyph to bitmap", error); + } + + FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n]; + // now, draw to our target surface (convert position) + + // bitmap left and top in pixel, string bbox in subpixel + 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); + } +} + +void FT2Font::get_xys(bool antialiased, std::vector<double> &xys) +{ + for (size_t n = 0; n < glyphs.size(); n++) { + + FT_Error error = FT_Glyph_To_Bitmap( + &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1); + if (error) { + throw_ft_error("Could not convert glyph to bitmap", error); + } + + FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n]; + + // bitmap left and top in pixel, string bbox in subpixel + FT_Int x = (FT_Int)(bitmap->left - bbox.xMin * (1. / 64.)); + FT_Int y = (FT_Int)(bbox.yMax * (1. / 64.) - bitmap->top + 1); + // make sure the index is non-neg + x = x < 0 ? 0 : x; + y = y < 0 ? 0 : y; + xys.push_back(x); + xys.push_back(y); + } +} + +void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased) +{ + FT_Vector sub_offset; + sub_offset.x = 0; // int((xd - (double)x) * 64.0); + sub_offset.y = 0; // int((yd - (double)y) * 64.0); + + if (glyphInd >= glyphs.size()) { + throw std::runtime_error("glyph num is out of range"); + } + + FT_Error error = FT_Glyph_To_Bitmap( + &glyphs[glyphInd], + antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, + &sub_offset, // additional translation + 1 // destroy image + ); + if (error) { + throw_ft_error("Could not convert glyph to bitmap", error); + } + + FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd]; + + im.draw_bitmap(&bitmap->bitmap, x + bitmap->left, y); +} + +void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer, bool fallback = false) +{ + if (fallback && glyph_to_font.find(glyph_number) != glyph_to_font.end()) { + // cache is only for parent FT2Font + FT2Font *ft_object = glyph_to_font[glyph_number]; + ft_object->get_glyph_name(glyph_number, buffer, false); + return; + } + if (!FT_HAS_GLYPH_NAMES(face)) { + /* Note that this generated name must match the name that + is generated by ttconv in ttfont_CharStrings_getname. */ + PyOS_snprintf(buffer, 128, "uni%08x", glyph_number); + } else { + if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer, 128)) { + throw_ft_error("Could not get glyph names", error); + } + } +} + +long FT2Font::get_name_index(char *name) +{ + return FT_Get_Name_Index(face, (FT_String *)name); +} diff --git a/contrib/python/matplotlib/py3/src/ft2font.h b/contrib/python/matplotlib/py3/src/ft2font.h new file mode 100644 index 0000000000..d566c3f9bd --- /dev/null +++ b/contrib/python/matplotlib/py3/src/ft2font.h @@ -0,0 +1,159 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +/* A python interface to FreeType */ +#pragma once +#ifndef MPL_FT2FONT_H +#define MPL_FT2FONT_H +#include <vector> +#include <stdint.h> +#include <unordered_map> + +extern "C" { +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +#include FT_SFNT_NAMES_H +#include FT_TYPE1_TABLES_H +#include FT_TRUETYPE_TABLES_H +} + +#define PY_SSIZE_T_CLEAN +#include <Python.h> + +/* + By definition, FT_FIXED as 2 16bit values stored in a single long. + */ +#define FIXED_MAJOR(val) (signed short)((val & 0xffff0000) >> 16) +#define FIXED_MINOR(val) (unsigned short)(val & 0xffff) + +// the FreeType string rendered into a width, height buffer +class FT2Image +{ + public: + FT2Image(); + FT2Image(unsigned long width, unsigned long height); + virtual ~FT2Image(); + + void resize(long width, long height); + void draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y); + void draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1); + void draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1); + + unsigned char *get_buffer() + { + return m_buffer; + } + unsigned long get_width() + { + return m_width; + } + unsigned long get_height() + { + return m_height; + } + + private: + bool m_dirty; + unsigned char *m_buffer; + unsigned long m_width; + unsigned long m_height; + + // prevent copying + FT2Image(const FT2Image &); + FT2Image &operator=(const FT2Image &); +}; + +extern FT_Library _ft2Library; + +class FT2Font +{ + + public: + FT2Font(FT_Open_Args &open_args, long hinting_factor, std::vector<FT2Font *> &fallback_list); + virtual ~FT2Font(); + void clear(); + void set_size(double ptsize, double dpi); + void set_charmap(int i); + void select_charmap(unsigned long i); + void set_text( + size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys); + int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback); + int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta); + void set_kerning_factor(int factor); + void load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback); + bool load_char_with_fallback(FT2Font *&ft_object_with_glyph, + FT_UInt &final_glyph_index, + std::vector<FT_Glyph> &parent_glyphs, + std::unordered_map<long, FT2Font *> &parent_char_to_font, + std::unordered_map<FT_UInt, FT2Font *> &parent_glyph_to_font, + long charcode, + FT_Int32 flags, + FT_Error &charcode_error, + FT_Error &glyph_error, + bool override); + void load_glyph(FT_UInt glyph_index, FT_Int32 flags, FT2Font *&ft_object, bool fallback); + void load_glyph(FT_UInt glyph_index, FT_Int32 flags); + void get_width_height(long *width, long *height); + void get_bitmap_offset(long *x, long *y); + long get_descent(); + // TODO: Since we know the size of the array upfront, we probably don't + // need to dynamically allocate like this + void get_xys(bool antialiased, std::vector<double> &xys); + void draw_glyphs_to_bitmap(bool antialiased); + void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased); + void get_glyph_name(unsigned int glyph_number, char *buffer, bool fallback); + long get_name_index(char *name); + FT_UInt get_char_index(FT_ULong charcode, bool fallback); + PyObject* get_path(); + bool get_char_fallback_index(FT_ULong charcode, int& index) const; + + FT_Face const &get_face() const + { + return face; + } + + FT2Image &get_image() + { + return image; + } + FT_Glyph const &get_last_glyph() const + { + return glyphs.back(); + } + size_t get_last_glyph_index() const + { + return glyphs.size() - 1; + } + size_t get_num_glyphs() const + { + return glyphs.size(); + } + long get_hinting_factor() const + { + return hinting_factor; + } + FT_Bool has_kerning() const + { + return FT_HAS_KERNING(face); + } + + private: + FT2Image image; + FT_Face face; + FT_Vector pen; /* untransformed origin */ + std::vector<FT_Glyph> glyphs; + std::vector<FT2Font *> fallbacks; + std::unordered_map<FT_UInt, FT2Font *> glyph_to_font; + std::unordered_map<long, FT2Font *> char_to_font; + FT_BBox bbox; + FT_Pos advance; + long hinting_factor; + int kerning_factor; + + // prevent copying + FT2Font(const FT2Font &); + FT2Font &operator=(const FT2Font &); +}; + +#endif diff --git a/contrib/python/matplotlib/py3/src/ft2font_wrapper.cpp b/contrib/python/matplotlib/py3/src/ft2font_wrapper.cpp new file mode 100644 index 0000000000..7888a9c212 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/ft2font_wrapper.cpp @@ -0,0 +1,1578 @@ +#include "mplutils.h" +#include "ft2font.h" +#include "py_converters.h" +#include "py_exceptions.h" +#include "numpy_cpp.h" + +// From Python +#include <structmember.h> + +#include <set> +#include <algorithm> + +#define STRINGIFY(s) XSTRINGIFY(s) +#define XSTRINGIFY(s) #s + +static PyObject *convert_xys_to_array(std::vector<double> &xys) +{ + npy_intp dims[] = {(npy_intp)xys.size() / 2, 2 }; + if (dims[0] > 0) { + return PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, &xys[0]); + } else { + return PyArray_SimpleNew(2, dims, NPY_DOUBLE); + } +} + +/********************************************************************** + * FT2Image + * */ + +typedef struct +{ + PyObject_HEAD + FT2Image *x; + Py_ssize_t shape[2]; + Py_ssize_t strides[2]; + Py_ssize_t suboffsets[2]; +} PyFT2Image; + +static PyTypeObject PyFT2ImageType; + +static PyObject *PyFT2Image_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyFT2Image *self; + self = (PyFT2Image *)type->tp_alloc(type, 0); + self->x = NULL; + return (PyObject *)self; +} + +static int PyFT2Image_init(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + double width; + double height; + + if (!PyArg_ParseTuple(args, "dd:FT2Image", &width, &height)) { + return -1; + } + + CALL_CPP_INIT("FT2Image", (self->x = new FT2Image(width, height))); + + return 0; +} + +static void PyFT2Image_dealloc(PyFT2Image *self) +{ + delete self->x; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +const char *PyFT2Image_draw_rect__doc__ = + "draw_rect(self, x0, y0, x1, y1)\n" + "--\n\n" + "Draw an empty rectangle to the image.\n" + "\n" + ".. deprecated:: 3.8\n"; +; + +static PyObject *PyFT2Image_draw_rect(PyFT2Image *self, PyObject *args) +{ + char const* msg = + "FT2Image.draw_rect is deprecated since Matplotlib 3.8 and will be removed " + "two minor releases later as it is not used in the library. If you rely on " + "it, please let us know."; + if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { + return NULL; + } + + double x0, y0, x1, y1; + + if (!PyArg_ParseTuple(args, "dddd:draw_rect", &x0, &y0, &x1, &y1)) { + return NULL; + } + + CALL_CPP("draw_rect", (self->x->draw_rect(x0, y0, x1, y1))); + + Py_RETURN_NONE; +} + +const char *PyFT2Image_draw_rect_filled__doc__ = + "draw_rect_filled(self, x0, y0, x1, y1)\n" + "--\n\n" + "Draw a filled rectangle to the image.\n"; + +static PyObject *PyFT2Image_draw_rect_filled(PyFT2Image *self, PyObject *args) +{ + double x0, y0, x1, y1; + + if (!PyArg_ParseTuple(args, "dddd:draw_rect_filled", &x0, &y0, &x1, &y1)) { + return NULL; + } + + CALL_CPP("draw_rect_filled", (self->x->draw_rect_filled(x0, y0, x1, y1))); + + Py_RETURN_NONE; +} + +static int PyFT2Image_get_buffer(PyFT2Image *self, Py_buffer *buf, int flags) +{ + FT2Image *im = self->x; + + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = im->get_buffer(); + buf->len = im->get_width() * im->get_height(); + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 2; + self->shape[0] = im->get_height(); + self->shape[1] = im->get_width(); + buf->shape = self->shape; + self->strides[0] = im->get_width(); + self->strides[1] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyTypeObject* PyFT2Image_init_type() +{ + static PyMethodDef methods[] = { + {"draw_rect", (PyCFunction)PyFT2Image_draw_rect, METH_VARARGS, PyFT2Image_draw_rect__doc__}, + {"draw_rect_filled", (PyCFunction)PyFT2Image_draw_rect_filled, METH_VARARGS, PyFT2Image_draw_rect_filled__doc__}, + {NULL} + }; + + static PyBufferProcs buffer_procs; + buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Image_get_buffer; + + PyFT2ImageType.tp_name = "matplotlib.ft2font.FT2Image"; + PyFT2ImageType.tp_basicsize = sizeof(PyFT2Image); + PyFT2ImageType.tp_dealloc = (destructor)PyFT2Image_dealloc; + PyFT2ImageType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + PyFT2ImageType.tp_methods = methods; + PyFT2ImageType.tp_new = PyFT2Image_new; + PyFT2ImageType.tp_init = (initproc)PyFT2Image_init; + PyFT2ImageType.tp_as_buffer = &buffer_procs; + + return &PyFT2ImageType; +} + +/********************************************************************** + * Glyph + * */ + +typedef struct +{ + PyObject_HEAD + size_t glyphInd; + long width; + long height; + long horiBearingX; + long horiBearingY; + long horiAdvance; + long linearHoriAdvance; + long vertBearingX; + long vertBearingY; + long vertAdvance; + FT_BBox bbox; +} PyGlyph; + +static PyTypeObject PyGlyphType; + +static PyObject *PyGlyph_from_FT2Font(const FT2Font *font) +{ + const FT_Face &face = font->get_face(); + const long hinting_factor = font->get_hinting_factor(); + const FT_Glyph &glyph = font->get_last_glyph(); + + PyGlyph *self; + self = (PyGlyph *)PyGlyphType.tp_alloc(&PyGlyphType, 0); + + self->glyphInd = font->get_last_glyph_index(); + FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &self->bbox); + + self->width = face->glyph->metrics.width / hinting_factor; + self->height = face->glyph->metrics.height; + self->horiBearingX = face->glyph->metrics.horiBearingX / hinting_factor; + self->horiBearingY = face->glyph->metrics.horiBearingY; + self->horiAdvance = face->glyph->metrics.horiAdvance; + self->linearHoriAdvance = face->glyph->linearHoriAdvance / hinting_factor; + self->vertBearingX = face->glyph->metrics.vertBearingX; + self->vertBearingY = face->glyph->metrics.vertBearingY; + self->vertAdvance = face->glyph->metrics.vertAdvance; + + return (PyObject *)self; +} + +static void PyGlyph_dealloc(PyGlyph *self) +{ + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *PyGlyph_get_bbox(PyGlyph *self, void *closure) +{ + return Py_BuildValue( + "llll", self->bbox.xMin, self->bbox.yMin, self->bbox.xMax, self->bbox.yMax); +} + +static PyTypeObject *PyGlyph_init_type() +{ + static PyMemberDef members[] = { + {(char *)"width", T_LONG, offsetof(PyGlyph, width), READONLY, (char *)""}, + {(char *)"height", T_LONG, offsetof(PyGlyph, height), READONLY, (char *)""}, + {(char *)"horiBearingX", T_LONG, offsetof(PyGlyph, horiBearingX), READONLY, (char *)""}, + {(char *)"horiBearingY", T_LONG, offsetof(PyGlyph, horiBearingY), READONLY, (char *)""}, + {(char *)"horiAdvance", T_LONG, offsetof(PyGlyph, horiAdvance), READONLY, (char *)""}, + {(char *)"linearHoriAdvance", T_LONG, offsetof(PyGlyph, linearHoriAdvance), READONLY, (char *)""}, + {(char *)"vertBearingX", T_LONG, offsetof(PyGlyph, vertBearingX), READONLY, (char *)""}, + {(char *)"vertBearingY", T_LONG, offsetof(PyGlyph, vertBearingY), READONLY, (char *)""}, + {(char *)"vertAdvance", T_LONG, offsetof(PyGlyph, vertAdvance), READONLY, (char *)""}, + {NULL} + }; + + static PyGetSetDef getset[] = { + {(char *)"bbox", (getter)PyGlyph_get_bbox, NULL, NULL, NULL}, + {NULL} + }; + + PyGlyphType.tp_name = "matplotlib.ft2font.Glyph"; + PyGlyphType.tp_basicsize = sizeof(PyGlyph); + PyGlyphType.tp_dealloc = (destructor)PyGlyph_dealloc; + PyGlyphType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + PyGlyphType.tp_members = members; + PyGlyphType.tp_getset = getset; + + return &PyGlyphType; +} + +/********************************************************************** + * FT2Font + * */ + +struct PyFT2Font +{ + PyObject_HEAD + FT2Font *x; + PyObject *py_file; + FT_StreamRec stream; + Py_ssize_t shape[2]; + Py_ssize_t strides[2]; + Py_ssize_t suboffsets[2]; + std::vector<PyObject *> fallbacks; +}; + +static PyTypeObject PyFT2FontType; + +static unsigned long read_from_file_callback(FT_Stream stream, + unsigned long offset, + unsigned char *buffer, + unsigned long count) +{ + PyObject *py_file = ((PyFT2Font *)stream->descriptor.pointer)->py_file; + PyObject *seek_result = NULL, *read_result = NULL; + Py_ssize_t n_read = 0; + if (!(seek_result = PyObject_CallMethod(py_file, "seek", "k", offset)) + || !(read_result = PyObject_CallMethod(py_file, "read", "k", count))) { + goto exit; + } + char *tmpbuf; + if (PyBytes_AsStringAndSize(read_result, &tmpbuf, &n_read) == -1) { + goto exit; + } + memcpy(buffer, tmpbuf, n_read); +exit: + Py_XDECREF(seek_result); + Py_XDECREF(read_result); + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(py_file); + if (!count) { + return 1; // Non-zero signals error, when count == 0. + } + } + return (unsigned long)n_read; +} + +static void close_file_callback(FT_Stream stream) +{ + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer; + PyObject *close_result = NULL; + if (!(close_result = PyObject_CallMethod(self->py_file, "close", ""))) { + goto exit; + } +exit: + Py_XDECREF(close_result); + Py_CLEAR(self->py_file); + if (PyErr_Occurred()) { + PyErr_WriteUnraisable((PyObject*)self); + } + PyErr_Restore(type, value, traceback); +} + +static PyObject *PyFT2Font_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyFT2Font *self; + self = (PyFT2Font *)type->tp_alloc(type, 0); + self->x = NULL; + self->py_file = NULL; + memset(&self->stream, 0, sizeof(FT_StreamRec)); + return (PyObject *)self; +} + +const char *PyFT2Font_init__doc__ = + "FT2Font(filename, hinting_factor=8, *, _fallback_list=None, _kerning_factor=0)\n" + "--\n\n" + "Create a new FT2Font object.\n" + "\n" + "Parameters\n" + "----------\n" + "filename : str or file-like\n" + " The source of the font data in a format (ttf or ttc) that FreeType can read\n" + "\n" + "hinting_factor : int, optional\n" + " Must be positive. Used to scale the hinting in the x-direction\n" + "_fallback_list : list of FT2Font, optional\n" + " A list of FT2Font objects used to find missing glyphs.\n" + "\n" + " .. warning::\n" + " This API is both private and provisional: do not use it directly\n" + "\n" + "_kerning_factor : int, optional\n" + " Used to adjust the degree of kerning.\n" + "\n" + " .. warning::\n" + " This API is private: do not use it directly\n" + "\n" + "Attributes\n" + "----------\n" + "num_faces : int\n" + " Number of faces in file.\n" + "face_flags, style_flags : int\n" + " Face and style flags; see the ft2font constants.\n" + "num_glyphs : int\n" + " Number of glyphs in the face.\n" + "family_name, style_name : str\n" + " Face family and style name.\n" + "num_fixed_sizes : int\n" + " Number of bitmap in the face.\n" + "scalable : bool\n" + " Whether face is scalable; attributes after this one are only\n" + " defined for scalable faces.\n" + "bbox : tuple[int, int, int, int]\n" + " Face global bounding box (xmin, ymin, xmax, ymax).\n" + "units_per_EM : int\n" + " Number of font units covered by the EM.\n" + "ascender, descender : int\n" + " Ascender and descender in 26.6 units.\n" + "height : int\n" + " Height in 26.6 units; used to compute a default line spacing\n" + " (baseline-to-baseline distance).\n" + "max_advance_width, max_advance_height : int\n" + " Maximum horizontal and vertical cursor advance for all glyphs.\n" + "underline_position, underline_thickness : int\n" + " Vertical position and thickness of the underline bar.\n" + "postscript_name : str\n" + " PostScript name of the font.\n"; + +static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *filename = NULL, *open = NULL, *data = NULL, *fallback_list = NULL; + FT_Open_Args open_args; + long hinting_factor = 8; + int kerning_factor = 0; + const char *names[] = { + "filename", "hinting_factor", "_fallback_list", "_kerning_factor", NULL + }; + std::vector<FT2Font *> fallback_fonts; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|l$Oi:FT2Font", (char **)names, &filename, + &hinting_factor, &fallback_list, &kerning_factor)) { + return -1; + } + if (hinting_factor <= 0) { + PyErr_SetString(PyExc_ValueError, + "hinting_factor must be greater than 0"); + goto exit; + } + + self->stream.base = NULL; + self->stream.size = 0x7fffffff; // Unknown size. + self->stream.pos = 0; + self->stream.descriptor.pointer = self; + self->stream.read = &read_from_file_callback; + memset((void *)&open_args, 0, sizeof(FT_Open_Args)); + open_args.flags = FT_OPEN_STREAM; + open_args.stream = &self->stream; + + if (fallback_list) { + if (!PyList_Check(fallback_list)) { + PyErr_SetString(PyExc_TypeError, "Fallback list must be a list"); + goto exit; + } + Py_ssize_t size = PyList_Size(fallback_list); + + // go through fallbacks once to make sure the types are right + for (Py_ssize_t i = 0; i < size; ++i) { + // this returns a borrowed reference + PyObject* item = PyList_GetItem(fallback_list, i); + if (!PyObject_IsInstance(item, PyObject_Type(reinterpret_cast<PyObject *>(self)))) { + PyErr_SetString(PyExc_TypeError, "Fallback fonts must be FT2Font objects."); + goto exit; + } + } + // go through a second time to add them to our lists + for (Py_ssize_t i = 0; i < size; ++i) { + // this returns a borrowed reference + PyObject* item = PyList_GetItem(fallback_list, i); + // Increase the ref count, we will undo this in dealloc this makes + // sure things do not get gc'd under us! + Py_INCREF(item); + self->fallbacks.push_back(item); + // Also (locally) cache the underlying FT2Font objects. As long as + // the Python objects are kept alive, these pointer are good. + FT2Font *fback = reinterpret_cast<PyFT2Font *>(item)->x; + fallback_fonts.push_back(fback); + } + } + + if (PyBytes_Check(filename) || PyUnicode_Check(filename)) { + if (!(open = PyDict_GetItemString(PyEval_GetBuiltins(), "open")) // Borrowed reference. + || !(self->py_file = PyObject_CallFunction(open, "Os", filename, "rb"))) { + goto exit; + } + self->stream.close = &close_file_callback; + } else if (!PyObject_HasAttrString(filename, "read") + || !(data = PyObject_CallMethod(filename, "read", "i", 0)) + || !PyBytes_Check(data)) { + PyErr_SetString(PyExc_TypeError, + "First argument must be a path to a font file or a binary-mode file object"); + Py_CLEAR(data); + goto exit; + } else { + self->py_file = filename; + self->stream.close = NULL; + Py_INCREF(filename); + } + Py_CLEAR(data); + + CALL_CPP_FULL( + "FT2Font", (self->x = new FT2Font(open_args, hinting_factor, fallback_fonts)), + Py_CLEAR(self->py_file), -1); + + CALL_CPP_INIT("FT2Font->set_kerning_factor", (self->x->set_kerning_factor(kerning_factor))); + +exit: + return PyErr_Occurred() ? -1 : 0; +} + +static void PyFT2Font_dealloc(PyFT2Font *self) +{ + delete self->x; + for (size_t i = 0; i < self->fallbacks.size(); i++) { + Py_DECREF(self->fallbacks[i]); + } + + Py_XDECREF(self->py_file); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +const char *PyFT2Font_clear__doc__ = + "clear(self)\n" + "--\n\n" + "Clear all the glyphs, reset for a new call to `.set_text`.\n"; + +static PyObject *PyFT2Font_clear(PyFT2Font *self, PyObject *args) +{ + CALL_CPP("clear", (self->x->clear())); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_set_size__doc__ = + "set_size(self, ptsize, dpi)\n" + "--\n\n" + "Set the point size and dpi of the text.\n"; + +static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args) +{ + double ptsize; + double dpi; + + if (!PyArg_ParseTuple(args, "dd:set_size", &ptsize, &dpi)) { + return NULL; + } + + CALL_CPP("set_size", (self->x->set_size(ptsize, dpi))); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_set_charmap__doc__ = + "set_charmap(self, i)\n" + "--\n\n" + "Make the i-th charmap current.\n"; + +static PyObject *PyFT2Font_set_charmap(PyFT2Font *self, PyObject *args) +{ + int i; + + if (!PyArg_ParseTuple(args, "i:set_charmap", &i)) { + return NULL; + } + + CALL_CPP("set_charmap", (self->x->set_charmap(i))); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_select_charmap__doc__ = + "select_charmap(self, i)\n" + "--\n\n" + "Select a charmap by its FT_Encoding number.\n"; + +static PyObject *PyFT2Font_select_charmap(PyFT2Font *self, PyObject *args) +{ + unsigned long i; + + if (!PyArg_ParseTuple(args, "k:select_charmap", &i)) { + return NULL; + } + + CALL_CPP("select_charmap", self->x->select_charmap(i)); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_get_kerning__doc__ = + "get_kerning(self, left, right, mode)\n" + "--\n\n" + "Get the kerning between *left* and *right* glyph indices.\n" + "*mode* is a kerning mode constant:\n\n" + "- KERNING_DEFAULT - Return scaled and grid-fitted kerning distances\n" + "- KERNING_UNFITTED - Return scaled but un-grid-fitted kerning distances\n" + "- KERNING_UNSCALED - Return the kerning vector in original font units\n"; + +static PyObject *PyFT2Font_get_kerning(PyFT2Font *self, PyObject *args) +{ + FT_UInt left, right, mode; + int result; + int fallback = 1; + + if (!PyArg_ParseTuple(args, "III:get_kerning", &left, &right, &mode)) { + return NULL; + } + + CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode, (bool)fallback))); + + return PyLong_FromLong(result); +} + +const char *PyFT2Font_get_fontmap__doc__ = + "_get_fontmap(self, string)\n" + "--\n\n" + "Get a mapping between characters and the font that includes them.\n" + "A dictionary mapping unicode characters to PyFT2Font objects."; +static PyObject *PyFT2Font_get_fontmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *textobj; + const char *names[] = { "string", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O:_get_fontmap", (char **)names, &textobj)) { + return NULL; + } + + std::set<FT_ULong> codepoints; + size_t size; + + if (PyUnicode_Check(textobj)) { + size = PyUnicode_GET_LENGTH(textobj); + for (size_t i = 0; i < size; ++i) { + codepoints.insert(PyUnicode_ReadChar(textobj, i)); + } + } else { + PyErr_SetString(PyExc_TypeError, "string must be str"); + return NULL; + } + PyObject *char_to_font; + if (!(char_to_font = PyDict_New())) { + return NULL; + } + for (auto it = codepoints.begin(); it != codepoints.end(); ++it) { + auto x = *it; + PyObject* target_font; + int index; + if (self->x->get_char_fallback_index(x, index)) { + if (index >= 0) { + target_font = self->fallbacks[index]; + } else { + target_font = (PyObject *)self; + } + } else { + // TODO Handle recursion! + target_font = (PyObject *)self; + } + + PyObject *key = NULL; + bool error = (!(key = PyUnicode_FromFormat("%c", x)) + || (PyDict_SetItem(char_to_font, key, target_font) == -1)); + Py_XDECREF(key); + if (error) { + Py_DECREF(char_to_font); + PyErr_SetString(PyExc_ValueError, "Something went very wrong"); + return NULL; + } + } + return char_to_font; +} + + +const char *PyFT2Font_set_text__doc__ = + "set_text(self, string, angle=0.0, flags=32)\n" + "--\n\n" + "Set the text *string* and *angle*.\n" + "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" + "the default value is LOAD_FORCE_AUTOHINT.\n" + "You must call this before `.draw_glyphs_to_bitmap`.\n" + "A sequence of x,y positions is returned.\n"; + +static PyObject *PyFT2Font_set_text(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *textobj; + double angle = 0.0; + FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; + std::vector<double> xys; + const char *names[] = { "string", "angle", "flags", NULL }; + + /* This makes a technically incorrect assumption that FT_Int32 is + int. In theory it can also be long, if the size of int is less + than 32 bits. This is very unlikely on modern platforms. */ + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|di:set_text", (char **)names, &textobj, &angle, &flags)) { + return NULL; + } + + std::vector<uint32_t> codepoints; + size_t size; + + if (PyUnicode_Check(textobj)) { + size = PyUnicode_GET_LENGTH(textobj); + codepoints.resize(size); + for (size_t i = 0; i < size; ++i) { + codepoints[i] = PyUnicode_ReadChar(textobj, i); + } + } else { + PyErr_SetString(PyExc_TypeError, "set_text requires str-input."); + return NULL; + } + + uint32_t* codepoints_array = NULL; + if (size > 0) { + codepoints_array = &codepoints[0]; + } + CALL_CPP("set_text", self->x->set_text(size, codepoints_array, angle, flags, xys)); + + return convert_xys_to_array(xys); +} + +const char *PyFT2Font_get_num_glyphs__doc__ = + "get_num_glyphs(self)\n" + "--\n\n" + "Return the number of loaded glyphs.\n"; + +static PyObject *PyFT2Font_get_num_glyphs(PyFT2Font *self, PyObject *args) +{ + return PyLong_FromSize_t(self->x->get_num_glyphs()); +} + +const char *PyFT2Font_load_char__doc__ = + "load_char(self, charcode, flags=32)\n" + "--\n\n" + "Load character with *charcode* in current fontfile and set glyph.\n" + "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" + "the default value is LOAD_FORCE_AUTOHINT.\n" + "Return value is a Glyph object, with attributes\n\n" + "- width: glyph width\n" + "- height: glyph height\n" + "- bbox: the glyph bbox (xmin, ymin, xmax, ymax)\n" + "- horiBearingX: left side bearing in horizontal layouts\n" + "- horiBearingY: top side bearing in horizontal layouts\n" + "- horiAdvance: advance width for horizontal layout\n" + "- vertBearingX: left side bearing in vertical layouts\n" + "- vertBearingY: top side bearing in vertical layouts\n" + "- vertAdvance: advance height for vertical layout\n"; + +static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + long charcode; + int fallback = 1; + FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; + const char *names[] = { "charcode", "flags", NULL }; + + /* This makes a technically incorrect assumption that FT_Int32 is + int. In theory it can also be long, if the size of int is less + than 32 bits. This is very unlikely on modern platforms. */ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "l|i:load_char", (char **)names, &charcode, + &flags)) { + return NULL; + } + + FT2Font *ft_object = NULL; + CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); + + return PyGlyph_from_FT2Font(ft_object); +} + +const char *PyFT2Font_load_glyph__doc__ = + "load_glyph(self, glyphindex, flags=32)\n" + "--\n\n" + "Load character with *glyphindex* in current fontfile and set glyph.\n" + "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" + "the default value is LOAD_FORCE_AUTOHINT.\n" + "Return value is a Glyph object, with attributes\n\n" + "- width: glyph width\n" + "- height: glyph height\n" + "- bbox: the glyph bbox (xmin, ymin, xmax, ymax)\n" + "- horiBearingX: left side bearing in horizontal layouts\n" + "- horiBearingY: top side bearing in horizontal layouts\n" + "- horiAdvance: advance width for horizontal layout\n" + "- vertBearingX: left side bearing in vertical layouts\n" + "- vertBearingY: top side bearing in vertical layouts\n" + "- vertAdvance: advance height for vertical layout\n"; + +static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + FT_UInt glyph_index; + FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; + int fallback = 1; + const char *names[] = { "glyph_index", "flags", NULL }; + + /* This makes a technically incorrect assumption that FT_Int32 is + int. In theory it can also be long, if the size of int is less + than 32 bits. This is very unlikely on modern platforms. */ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "I|i:load_glyph", (char **)names, &glyph_index, + &flags)) { + return NULL; + } + + FT2Font *ft_object = NULL; + CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags, ft_object, (bool)fallback))); + + return PyGlyph_from_FT2Font(ft_object); +} + +const char *PyFT2Font_get_width_height__doc__ = + "get_width_height(self)\n" + "--\n\n" + "Get the width and height in 26.6 subpixels of the current string set by `.set_text`.\n" + "The rotation of the string is accounted for. To get width and height\n" + "in pixels, divide these values by 64.\n"; + +static PyObject *PyFT2Font_get_width_height(PyFT2Font *self, PyObject *args) +{ + long width, height; + + CALL_CPP("get_width_height", (self->x->get_width_height(&width, &height))); + + return Py_BuildValue("ll", width, height); +} + +const char *PyFT2Font_get_bitmap_offset__doc__ = + "get_bitmap_offset(self)\n" + "--\n\n" + "Get the (x, y) offset in 26.6 subpixels for the bitmap if ink hangs left or below (0, 0).\n" + "Since Matplotlib only supports left-to-right text, y is always 0.\n"; + +static PyObject *PyFT2Font_get_bitmap_offset(PyFT2Font *self, PyObject *args) +{ + long x, y; + + CALL_CPP("get_bitmap_offset", (self->x->get_bitmap_offset(&x, &y))); + + return Py_BuildValue("ll", x, y); +} + +const char *PyFT2Font_get_descent__doc__ = + "get_descent(self)\n" + "--\n\n" + "Get the descent in 26.6 subpixels of the current string set by `.set_text`.\n" + "The rotation of the string is accounted for. To get the descent\n" + "in pixels, divide this value by 64.\n"; + +static PyObject *PyFT2Font_get_descent(PyFT2Font *self, PyObject *args) +{ + long descent; + + CALL_CPP("get_descent", (descent = self->x->get_descent())); + + return PyLong_FromLong(descent); +} + +const char *PyFT2Font_draw_glyphs_to_bitmap__doc__ = + "draw_glyphs_to_bitmap(self, antialiased=True)\n" + "--\n\n" + "Draw the glyphs that were loaded by `.set_text` to the bitmap.\n" + "The bitmap size will be automatically set to include the glyphs.\n"; + +static PyObject *PyFT2Font_draw_glyphs_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + bool antialiased = true; + const char *names[] = { "antialiased", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:draw_glyphs_to_bitmap", + (char **)names, &convert_bool, &antialiased)) { + return NULL; + } + + CALL_CPP("draw_glyphs_to_bitmap", (self->x->draw_glyphs_to_bitmap(antialiased))); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_get_xys__doc__ = + "get_xys(self, antialiased=True)\n" + "--\n\n" + "Get the xy locations of the current glyphs.\n" + "\n" + ".. deprecated:: 3.8\n"; + +static PyObject *PyFT2Font_get_xys(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + char const* msg = + "FT2Font.get_xys is deprecated since Matplotlib 3.8 and will be removed two " + "minor releases later as it is not used in the library. If you rely on it, " + "please let us know."; + if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { + return NULL; + } + + bool antialiased = true; + std::vector<double> xys; + const char *names[] = { "antialiased", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:get_xys", + (char **)names, &convert_bool, &antialiased)) { + return NULL; + } + + CALL_CPP("get_xys", (self->x->get_xys(antialiased, xys))); + + return convert_xys_to_array(xys); +} + +const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = + "draw_glyph_to_bitmap(self, image, x, y, glyph, antialiased=True)\n" + "--\n\n" + "Draw a single glyph to the bitmap at pixel locations x, y\n" + "Note it is your responsibility to set up the bitmap manually\n" + "with ``set_bitmap_size(w, h)`` before this call is made.\n" + "\n" + "If you want automatic layout, use `.set_text` in combinations with\n" + "`.draw_glyphs_to_bitmap`. This function is instead intended for people\n" + "who want to render individual glyphs (e.g., returned by `.load_char`)\n" + "at precise locations.\n"; + +static PyObject *PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyFT2Image *image; + double xd, yd; + PyGlyph *glyph; + bool antialiased = true; + const char *names[] = { "image", "x", "y", "glyph", "antialiased", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O!ddO!|O&:draw_glyph_to_bitmap", + (char **)names, + &PyFT2ImageType, + &image, + &xd, + &yd, + &PyGlyphType, + &glyph, + &convert_bool, + &antialiased)) { + return NULL; + } + + CALL_CPP("draw_glyph_to_bitmap", + self->x->draw_glyph_to_bitmap(*(image->x), xd, yd, glyph->glyphInd, antialiased)); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_get_glyph_name__doc__ = + "get_glyph_name(self, index)\n" + "--\n\n" + "Retrieve the ASCII name of a given glyph *index* in a face.\n" + "\n" + "Due to Matplotlib's internal design, for fonts that do not contain glyph\n" + "names (per FT_FACE_FLAG_GLYPH_NAMES), this returns a made-up name which\n" + "does *not* roundtrip through `.get_name_index`.\n"; + +static PyObject *PyFT2Font_get_glyph_name(PyFT2Font *self, PyObject *args) +{ + unsigned int glyph_number; + char buffer[128]; + int fallback = 1; + + if (!PyArg_ParseTuple(args, "I:get_glyph_name", &glyph_number)) { + return NULL; + } + CALL_CPP("get_glyph_name", (self->x->get_glyph_name(glyph_number, buffer, (bool)fallback))); + return PyUnicode_FromString(buffer); +} + +const char *PyFT2Font_get_charmap__doc__ = + "get_charmap(self)\n" + "--\n\n" + "Return a dict that maps the character codes of the selected charmap\n" + "(Unicode by default) to their corresponding glyph indices.\n"; + +static PyObject *PyFT2Font_get_charmap(PyFT2Font *self, PyObject *args) +{ + PyObject *charmap; + if (!(charmap = PyDict_New())) { + return NULL; + } + FT_UInt index; + FT_ULong code = FT_Get_First_Char(self->x->get_face(), &index); + while (index != 0) { + PyObject *key = NULL, *val = NULL; + bool error = (!(key = PyLong_FromLong(code)) + || !(val = PyLong_FromLong(index)) + || (PyDict_SetItem(charmap, key, val) == -1)); + Py_XDECREF(key); + Py_XDECREF(val); + if (error) { + Py_DECREF(charmap); + return NULL; + } + code = FT_Get_Next_Char(self->x->get_face(), code, &index); + } + return charmap; +} + + +const char *PyFT2Font_get_char_index__doc__ = + "get_char_index(self, codepoint)\n" + "--\n\n" + "Return the glyph index corresponding to a character *codepoint*.\n"; + +static PyObject *PyFT2Font_get_char_index(PyFT2Font *self, PyObject *args) +{ + FT_UInt index; + FT_ULong ccode; + int fallback = 1; + + if (!PyArg_ParseTuple(args, "k:get_char_index", &ccode)) { + return NULL; + } + + CALL_CPP("get_char_index", index = self->x->get_char_index(ccode, (bool)fallback)); + + return PyLong_FromLong(index); +} + + +const char *PyFT2Font_get_sfnt__doc__ = + "get_sfnt(self)\n" + "--\n\n" + "Load the entire SFNT names table, as a dict whose keys are\n" + "(platform-ID, ISO-encoding-scheme, language-code, and description)\n" + "tuples.\n"; + +static PyObject *PyFT2Font_get_sfnt(PyFT2Font *self, PyObject *args) +{ + PyObject *names; + + if (!(self->x->get_face()->face_flags & FT_FACE_FLAG_SFNT)) { + PyErr_SetString(PyExc_ValueError, "No SFNT name table"); + return NULL; + } + + size_t count = FT_Get_Sfnt_Name_Count(self->x->get_face()); + + names = PyDict_New(); + if (names == NULL) { + return NULL; + } + + for (FT_UInt j = 0; j < count; ++j) { + FT_SfntName sfnt; + FT_Error error = FT_Get_Sfnt_Name(self->x->get_face(), j, &sfnt); + + if (error) { + Py_DECREF(names); + PyErr_SetString(PyExc_ValueError, "Could not get SFNT name"); + return NULL; + } + + PyObject *key = Py_BuildValue( + "HHHH", sfnt.platform_id, sfnt.encoding_id, sfnt.language_id, sfnt.name_id); + if (key == NULL) { + Py_DECREF(names); + return NULL; + } + + PyObject *val = PyBytes_FromStringAndSize((const char *)sfnt.string, sfnt.string_len); + if (val == NULL) { + Py_DECREF(key); + Py_DECREF(names); + return NULL; + } + + if (PyDict_SetItem(names, key, val)) { + Py_DECREF(key); + Py_DECREF(val); + Py_DECREF(names); + return NULL; + } + + Py_DECREF(key); + Py_DECREF(val); + } + + return names; +} + +const char *PyFT2Font_get_name_index__doc__ = + "get_name_index(self, name)\n" + "--\n\n" + "Return the glyph index of a given glyph *name*.\n" + "The glyph index 0 means 'undefined character code'.\n"; + +static PyObject *PyFT2Font_get_name_index(PyFT2Font *self, PyObject *args) +{ + char *glyphname; + long name_index; + if (!PyArg_ParseTuple(args, "s:get_name_index", &glyphname)) { + return NULL; + } + CALL_CPP("get_name_index", name_index = self->x->get_name_index(glyphname)); + return PyLong_FromLong(name_index); +} + +const char *PyFT2Font_get_ps_font_info__doc__ = + "get_ps_font_info(self)\n" + "--\n\n" + "Return the information in the PS Font Info structure.\n"; + +static PyObject *PyFT2Font_get_ps_font_info(PyFT2Font *self, PyObject *args) +{ + PS_FontInfoRec fontinfo; + + FT_Error error = FT_Get_PS_Font_Info(self->x->get_face(), &fontinfo); + if (error) { + PyErr_SetString(PyExc_ValueError, "Could not get PS font info"); + return NULL; + } + + return Py_BuildValue("ssssslbhH", + fontinfo.version ? fontinfo.version : "", + fontinfo.notice ? fontinfo.notice : "", + fontinfo.full_name ? fontinfo.full_name : "", + fontinfo.family_name ? fontinfo.family_name : "", + fontinfo.weight ? fontinfo.weight : "", + fontinfo.italic_angle, + fontinfo.is_fixed_pitch, + fontinfo.underline_position, + fontinfo.underline_thickness); +} + +const char *PyFT2Font_get_sfnt_table__doc__ = + "get_sfnt_table(self, name)\n" + "--\n\n" + "Return one of the following SFNT tables: head, maxp, OS/2, hhea, " + "vhea, post, or pclt.\n"; + +static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) +{ + char *tagname; + if (!PyArg_ParseTuple(args, "s:get_sfnt_table", &tagname)) { + return NULL; + } + + int tag; + const char *tags[] = { "head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt", NULL }; + + for (tag = 0; tags[tag] != NULL; tag++) { + if (strncmp(tagname, tags[tag], 5) == 0) { + break; + } + } + + void *table = FT_Get_Sfnt_Table(self->x->get_face(), (FT_Sfnt_Tag)tag); + if (!table) { + Py_RETURN_NONE; + } + + switch (tag) { + case 0: { + char head_dict[] = + "{s:(h,H), s:(h,H), s:l, s:l, s:H, s:H," + "s:(l,l), s:(l,l), s:h, s:h, s:h, s:h, s:H, s:H, s:h, s:h, s:h}"; + TT_Header *t = (TT_Header *)table; + return Py_BuildValue(head_dict, + "version", FIXED_MAJOR(t->Table_Version), FIXED_MINOR(t->Table_Version), + "fontRevision", FIXED_MAJOR(t->Font_Revision), FIXED_MINOR(t->Font_Revision), + "checkSumAdjustment", t->CheckSum_Adjust, + "magicNumber", t->Magic_Number, + "flags", t->Flags, + "unitsPerEm", t->Units_Per_EM, + "created", t->Created[0], t->Created[1], + "modified", t->Modified[0], t->Modified[1], + "xMin", t->xMin, + "yMin", t->yMin, + "xMax", t->xMax, + "yMax", t->yMax, + "macStyle", t->Mac_Style, + "lowestRecPPEM", t->Lowest_Rec_PPEM, + "fontDirectionHint", t->Font_Direction, + "indexToLocFormat", t->Index_To_Loc_Format, + "glyphDataFormat", t->Glyph_Data_Format); + } + case 1: { + char maxp_dict[] = + "{s:(h,H), s:H, s:H, s:H, s:H, s:H, s:H," + "s:H, s:H, s:H, s:H, s:H, s:H, s:H, s:H}"; + TT_MaxProfile *t = (TT_MaxProfile *)table; + return Py_BuildValue(maxp_dict, + "version", FIXED_MAJOR(t->version), FIXED_MINOR(t->version), + "numGlyphs", t->numGlyphs, + "maxPoints", t->maxPoints, + "maxContours", t->maxContours, + "maxComponentPoints", t->maxCompositePoints, + "maxComponentContours", t->maxCompositeContours, + "maxZones", t->maxZones, + "maxTwilightPoints", t->maxTwilightPoints, + "maxStorage", t->maxStorage, + "maxFunctionDefs", t->maxFunctionDefs, + "maxInstructionDefs", t->maxInstructionDefs, + "maxStackElements", t->maxStackElements, + "maxSizeOfInstructions", t->maxSizeOfInstructions, + "maxComponentElements", t->maxComponentElements, + "maxComponentDepth", t->maxComponentDepth); + } + case 2: { + char os_2_dict[] = + "{s:H, s:h, s:H, s:H, s:H, s:h, s:h, s:h," + "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:y#, s:(kkkk)," + "s:y#, s:H, s:H, s:H}"; + TT_OS2 *t = (TT_OS2 *)table; + return Py_BuildValue(os_2_dict, + "version", t->version, + "xAvgCharWidth", t->xAvgCharWidth, + "usWeightClass", t->usWeightClass, + "usWidthClass", t->usWidthClass, + "fsType", t->fsType, + "ySubscriptXSize", t->ySubscriptXSize, + "ySubscriptYSize", t->ySubscriptYSize, + "ySubscriptXOffset", t->ySubscriptXOffset, + "ySubscriptYOffset", t->ySubscriptYOffset, + "ySuperscriptXSize", t->ySuperscriptXSize, + "ySuperscriptYSize", t->ySuperscriptYSize, + "ySuperscriptXOffset", t->ySuperscriptXOffset, + "ySuperscriptYOffset", t->ySuperscriptYOffset, + "yStrikeoutSize", t->yStrikeoutSize, + "yStrikeoutPosition", t->yStrikeoutPosition, + "sFamilyClass", t->sFamilyClass, + "panose", t->panose, Py_ssize_t(10), + "ulCharRange", t->ulUnicodeRange1, t->ulUnicodeRange2, t->ulUnicodeRange3, t->ulUnicodeRange4, + "achVendID", t->achVendID, Py_ssize_t(4), + "fsSelection", t->fsSelection, + "fsFirstCharIndex", t->usFirstCharIndex, + "fsLastCharIndex", t->usLastCharIndex); + } + case 3: { + char hhea_dict[] = + "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h," + "s:h, s:h, s:h, s:h, s:H}"; + TT_HoriHeader *t = (TT_HoriHeader *)table; + return Py_BuildValue(hhea_dict, + "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), + "ascent", t->Ascender, + "descent", t->Descender, + "lineGap", t->Line_Gap, + "advanceWidthMax", t->advance_Width_Max, + "minLeftBearing", t->min_Left_Side_Bearing, + "minRightBearing", t->min_Right_Side_Bearing, + "xMaxExtent", t->xMax_Extent, + "caretSlopeRise", t->caret_Slope_Rise, + "caretSlopeRun", t->caret_Slope_Run, + "caretOffset", t->caret_Offset, + "metricDataFormat", t->metric_Data_Format, + "numOfLongHorMetrics", t->number_Of_HMetrics); + } + case 4: { + char vhea_dict[] = + "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h," + "s:h, s:h, s:h, s:h, s:H}"; + TT_VertHeader *t = (TT_VertHeader *)table; + return Py_BuildValue(vhea_dict, + "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), + "vertTypoAscender", t->Ascender, + "vertTypoDescender", t->Descender, + "vertTypoLineGap", t->Line_Gap, + "advanceHeightMax", t->advance_Height_Max, + "minTopSideBearing", t->min_Top_Side_Bearing, + "minBottomSizeBearing", t->min_Bottom_Side_Bearing, + "yMaxExtent", t->yMax_Extent, + "caretSlopeRise", t->caret_Slope_Rise, + "caretSlopeRun", t->caret_Slope_Run, + "caretOffset", t->caret_Offset, + "metricDataFormat", t->metric_Data_Format, + "numOfLongVerMetrics", t->number_Of_VMetrics); + } + case 5: { + char post_dict[] = "{s:(h,H), s:(h,H), s:h, s:h, s:k, s:k, s:k, s:k, s:k}"; + TT_Postscript *t = (TT_Postscript *)table; + return Py_BuildValue(post_dict, + "format", FIXED_MAJOR(t->FormatType), FIXED_MINOR(t->FormatType), + "italicAngle", FIXED_MAJOR(t->italicAngle), FIXED_MINOR(t->italicAngle), + "underlinePosition", t->underlinePosition, + "underlineThickness", t->underlineThickness, + "isFixedPitch", t->isFixedPitch, + "minMemType42", t->minMemType42, + "maxMemType42", t->maxMemType42, + "minMemType1", t->minMemType1, + "maxMemType1", t->maxMemType1); + } + case 6: { + char pclt_dict[] = + "{s:(h,H), s:k, s:H, s:H, s:H, s:H, s:H, s:H, s:y#, s:y#, s:b, " + "s:b, s:b}"; + TT_PCLT *t = (TT_PCLT *)table; + return Py_BuildValue(pclt_dict, + "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), + "fontNumber", t->FontNumber, + "pitch", t->Pitch, + "xHeight", t->xHeight, + "style", t->Style, + "typeFamily", t->TypeFamily, + "capHeight", t->CapHeight, + "symbolSet", t->SymbolSet, + "typeFace", t->TypeFace, Py_ssize_t(16), + "characterComplement", t->CharacterComplement, Py_ssize_t(8), + "strokeWeight", t->StrokeWeight, + "widthType", t->WidthType, + "serifStyle", t->SerifStyle); + } + default: + Py_RETURN_NONE; + } +} + +const char *PyFT2Font_get_path__doc__ = + "get_path(self)\n" + "--\n\n" + "Get the path data from the currently loaded glyph as a tuple of vertices, " + "codes.\n"; + +static PyObject *PyFT2Font_get_path(PyFT2Font *self, PyObject *args) +{ + CALL_CPP("get_path", return self->x->get_path()); +} + +const char *PyFT2Font_get_image__doc__ = + "get_image(self)\n" + "--\n\n" + "Return the underlying image buffer for this font object.\n"; + +static PyObject *PyFT2Font_get_image(PyFT2Font *self, PyObject *args) +{ + FT2Image &im = self->x->get_image(); + npy_intp dims[] = {(npy_intp)im.get_height(), (npy_intp)im.get_width() }; + return PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, im.get_buffer()); +} + +static PyObject *PyFT2Font_postscript_name(PyFT2Font *self, void *closure) +{ + const char *ps_name = FT_Get_Postscript_Name(self->x->get_face()); + if (ps_name == NULL) { + ps_name = "UNAVAILABLE"; + } + + return PyUnicode_FromString(ps_name); +} + +static PyObject *PyFT2Font_num_faces(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_faces); +} + +static PyObject *PyFT2Font_family_name(PyFT2Font *self, void *closure) +{ + const char *name = self->x->get_face()->family_name; + if (name == NULL) { + name = "UNAVAILABLE"; + } + return PyUnicode_FromString(name); +} + +static PyObject *PyFT2Font_style_name(PyFT2Font *self, void *closure) +{ + const char *name = self->x->get_face()->style_name; + if (name == NULL) { + name = "UNAVAILABLE"; + } + return PyUnicode_FromString(name); +} + +static PyObject *PyFT2Font_face_flags(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->face_flags); +} + +static PyObject *PyFT2Font_style_flags(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->style_flags); +} + +static PyObject *PyFT2Font_num_glyphs(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_glyphs); +} + +static PyObject *PyFT2Font_num_fixed_sizes(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_fixed_sizes); +} + +static PyObject *PyFT2Font_num_charmaps(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_charmaps); +} + +static PyObject *PyFT2Font_scalable(PyFT2Font *self, void *closure) +{ + if (FT_IS_SCALABLE(self->x->get_face())) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static PyObject *PyFT2Font_units_per_EM(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->units_per_EM); +} + +static PyObject *PyFT2Font_get_bbox(PyFT2Font *self, void *closure) +{ + FT_BBox *bbox = &(self->x->get_face()->bbox); + + return Py_BuildValue("llll", + bbox->xMin, bbox->yMin, bbox->xMax, bbox->yMax); +} + +static PyObject *PyFT2Font_ascender(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->ascender); +} + +static PyObject *PyFT2Font_descender(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->descender); +} + +static PyObject *PyFT2Font_height(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->height); +} + +static PyObject *PyFT2Font_max_advance_width(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->max_advance_width); +} + +static PyObject *PyFT2Font_max_advance_height(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->max_advance_height); +} + +static PyObject *PyFT2Font_underline_position(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->underline_position); +} + +static PyObject *PyFT2Font_underline_thickness(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->underline_thickness); +} + +static PyObject *PyFT2Font_fname(PyFT2Font *self, void *closure) +{ + if (self->stream.close) { // Called passed a filename to the constructor. + return PyObject_GetAttrString(self->py_file, "name"); + } else { + Py_INCREF(self->py_file); + return self->py_file; + } +} + +static int PyFT2Font_get_buffer(PyFT2Font *self, Py_buffer *buf, int flags) +{ + FT2Image &im = self->x->get_image(); + + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = im.get_buffer(); + buf->len = im.get_width() * im.get_height(); + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 2; + self->shape[0] = im.get_height(); + self->shape[1] = im.get_width(); + buf->shape = self->shape; + self->strides[0] = im.get_width(); + self->strides[1] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyTypeObject *PyFT2Font_init_type() +{ + static PyGetSetDef getset[] = { + {(char *)"postscript_name", (getter)PyFT2Font_postscript_name, NULL, NULL, NULL}, + {(char *)"num_faces", (getter)PyFT2Font_num_faces, NULL, NULL, NULL}, + {(char *)"family_name", (getter)PyFT2Font_family_name, NULL, NULL, NULL}, + {(char *)"style_name", (getter)PyFT2Font_style_name, NULL, NULL, NULL}, + {(char *)"face_flags", (getter)PyFT2Font_face_flags, NULL, NULL, NULL}, + {(char *)"style_flags", (getter)PyFT2Font_style_flags, NULL, NULL, NULL}, + {(char *)"num_glyphs", (getter)PyFT2Font_num_glyphs, NULL, NULL, NULL}, + {(char *)"num_fixed_sizes", (getter)PyFT2Font_num_fixed_sizes, NULL, NULL, NULL}, + {(char *)"num_charmaps", (getter)PyFT2Font_num_charmaps, NULL, NULL, NULL}, + {(char *)"scalable", (getter)PyFT2Font_scalable, NULL, NULL, NULL}, + {(char *)"units_per_EM", (getter)PyFT2Font_units_per_EM, NULL, NULL, NULL}, + {(char *)"bbox", (getter)PyFT2Font_get_bbox, NULL, NULL, NULL}, + {(char *)"ascender", (getter)PyFT2Font_ascender, NULL, NULL, NULL}, + {(char *)"descender", (getter)PyFT2Font_descender, NULL, NULL, NULL}, + {(char *)"height", (getter)PyFT2Font_height, NULL, NULL, NULL}, + {(char *)"max_advance_width", (getter)PyFT2Font_max_advance_width, NULL, NULL, NULL}, + {(char *)"max_advance_height", (getter)PyFT2Font_max_advance_height, NULL, NULL, NULL}, + {(char *)"underline_position", (getter)PyFT2Font_underline_position, NULL, NULL, NULL}, + {(char *)"underline_thickness", (getter)PyFT2Font_underline_thickness, NULL, NULL, NULL}, + {(char *)"fname", (getter)PyFT2Font_fname, NULL, NULL, NULL}, + {NULL} + }; + + static PyMethodDef methods[] = { + {"clear", (PyCFunction)PyFT2Font_clear, METH_NOARGS, PyFT2Font_clear__doc__}, + {"set_size", (PyCFunction)PyFT2Font_set_size, METH_VARARGS, PyFT2Font_set_size__doc__}, + {"set_charmap", (PyCFunction)PyFT2Font_set_charmap, METH_VARARGS, PyFT2Font_set_charmap__doc__}, + {"select_charmap", (PyCFunction)PyFT2Font_select_charmap, METH_VARARGS, PyFT2Font_select_charmap__doc__}, + {"get_kerning", (PyCFunction)PyFT2Font_get_kerning, METH_VARARGS, PyFT2Font_get_kerning__doc__}, + {"set_text", (PyCFunction)PyFT2Font_set_text, METH_VARARGS|METH_KEYWORDS, PyFT2Font_set_text__doc__}, + {"_get_fontmap", (PyCFunction)PyFT2Font_get_fontmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_fontmap__doc__}, + {"get_num_glyphs", (PyCFunction)PyFT2Font_get_num_glyphs, METH_NOARGS, PyFT2Font_get_num_glyphs__doc__}, + {"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__}, + {"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__}, + {"get_width_height", (PyCFunction)PyFT2Font_get_width_height, METH_NOARGS, PyFT2Font_get_width_height__doc__}, + {"get_bitmap_offset", (PyCFunction)PyFT2Font_get_bitmap_offset, METH_NOARGS, PyFT2Font_get_bitmap_offset__doc__}, + {"get_descent", (PyCFunction)PyFT2Font_get_descent, METH_NOARGS, PyFT2Font_get_descent__doc__}, + {"draw_glyphs_to_bitmap", (PyCFunction)PyFT2Font_draw_glyphs_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyphs_to_bitmap__doc__}, + {"get_xys", (PyCFunction)PyFT2Font_get_xys, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_xys__doc__}, + {"draw_glyph_to_bitmap", (PyCFunction)PyFT2Font_draw_glyph_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyph_to_bitmap__doc__}, + {"get_glyph_name", (PyCFunction)PyFT2Font_get_glyph_name, METH_VARARGS, PyFT2Font_get_glyph_name__doc__}, + {"get_charmap", (PyCFunction)PyFT2Font_get_charmap, METH_NOARGS, PyFT2Font_get_charmap__doc__}, + {"get_char_index", (PyCFunction)PyFT2Font_get_char_index, METH_VARARGS, PyFT2Font_get_char_index__doc__}, + {"get_sfnt", (PyCFunction)PyFT2Font_get_sfnt, METH_NOARGS, PyFT2Font_get_sfnt__doc__}, + {"get_name_index", (PyCFunction)PyFT2Font_get_name_index, METH_VARARGS, PyFT2Font_get_name_index__doc__}, + {"get_ps_font_info", (PyCFunction)PyFT2Font_get_ps_font_info, METH_NOARGS, PyFT2Font_get_ps_font_info__doc__}, + {"get_sfnt_table", (PyCFunction)PyFT2Font_get_sfnt_table, METH_VARARGS, PyFT2Font_get_sfnt_table__doc__}, + {"get_path", (PyCFunction)PyFT2Font_get_path, METH_NOARGS, PyFT2Font_get_path__doc__}, + {"get_image", (PyCFunction)PyFT2Font_get_image, METH_NOARGS, PyFT2Font_get_image__doc__}, + {NULL} + }; + + static PyBufferProcs buffer_procs; + buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Font_get_buffer; + + PyFT2FontType.tp_name = "matplotlib.ft2font.FT2Font"; + PyFT2FontType.tp_doc = PyFT2Font_init__doc__; + PyFT2FontType.tp_basicsize = sizeof(PyFT2Font); + PyFT2FontType.tp_dealloc = (destructor)PyFT2Font_dealloc; + PyFT2FontType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + PyFT2FontType.tp_methods = methods; + PyFT2FontType.tp_getset = getset; + PyFT2FontType.tp_new = PyFT2Font_new; + PyFT2FontType.tp_init = (initproc)PyFT2Font_init; + PyFT2FontType.tp_as_buffer = &buffer_procs; + + return &PyFT2FontType; +} + +static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "ft2font" }; + +PyMODINIT_FUNC PyInit_ft2font(void) +{ + import_array(); + + if (FT_Init_FreeType(&_ft2Library)) { // initialize library + return PyErr_Format( + PyExc_RuntimeError, "Could not initialize the freetype2 library"); + } + FT_Int major, minor, patch; + char version_string[64]; + FT_Library_Version(_ft2Library, &major, &minor, &patch); + snprintf(version_string, sizeof(version_string), "%d.%d.%d", major, minor, patch); + + PyObject *m; + if (!(m = PyModule_Create(&moduledef)) || + prepare_and_add_type(PyFT2Image_init_type(), m) || + prepare_and_add_type(PyFT2Font_init_type(), m) || + // Glyph is not constructible from Python, thus not added to the module. + PyType_Ready(PyGlyph_init_type()) || + PyModule_AddStringConstant(m, "__freetype_version__", version_string) || + PyModule_AddStringConstant(m, "__freetype_build_type__", STRINGIFY(FREETYPE_BUILD_TYPE)) || + PyModule_AddIntConstant(m, "SCALABLE", FT_FACE_FLAG_SCALABLE) || + PyModule_AddIntConstant(m, "FIXED_SIZES", FT_FACE_FLAG_FIXED_SIZES) || + PyModule_AddIntConstant(m, "FIXED_WIDTH", FT_FACE_FLAG_FIXED_WIDTH) || + PyModule_AddIntConstant(m, "SFNT", FT_FACE_FLAG_SFNT) || + PyModule_AddIntConstant(m, "HORIZONTAL", FT_FACE_FLAG_HORIZONTAL) || + PyModule_AddIntConstant(m, "VERTICAL", FT_FACE_FLAG_VERTICAL) || + PyModule_AddIntConstant(m, "KERNING", FT_FACE_FLAG_KERNING) || + PyModule_AddIntConstant(m, "FAST_GLYPHS", FT_FACE_FLAG_FAST_GLYPHS) || + PyModule_AddIntConstant(m, "MULTIPLE_MASTERS", FT_FACE_FLAG_MULTIPLE_MASTERS) || + PyModule_AddIntConstant(m, "GLYPH_NAMES", FT_FACE_FLAG_GLYPH_NAMES) || + PyModule_AddIntConstant(m, "EXTERNAL_STREAM", FT_FACE_FLAG_EXTERNAL_STREAM) || + PyModule_AddIntConstant(m, "ITALIC", FT_STYLE_FLAG_ITALIC) || + PyModule_AddIntConstant(m, "BOLD", FT_STYLE_FLAG_BOLD) || + PyModule_AddIntConstant(m, "KERNING_DEFAULT", FT_KERNING_DEFAULT) || + PyModule_AddIntConstant(m, "KERNING_UNFITTED", FT_KERNING_UNFITTED) || + PyModule_AddIntConstant(m, "KERNING_UNSCALED", FT_KERNING_UNSCALED) || + PyModule_AddIntConstant(m, "LOAD_DEFAULT", FT_LOAD_DEFAULT) || + PyModule_AddIntConstant(m, "LOAD_NO_SCALE", FT_LOAD_NO_SCALE) || + PyModule_AddIntConstant(m, "LOAD_NO_HINTING", FT_LOAD_NO_HINTING) || + PyModule_AddIntConstant(m, "LOAD_RENDER", FT_LOAD_RENDER) || + PyModule_AddIntConstant(m, "LOAD_NO_BITMAP", FT_LOAD_NO_BITMAP) || + PyModule_AddIntConstant(m, "LOAD_VERTICAL_LAYOUT", FT_LOAD_VERTICAL_LAYOUT) || + PyModule_AddIntConstant(m, "LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT) || + PyModule_AddIntConstant(m, "LOAD_CROP_BITMAP", FT_LOAD_CROP_BITMAP) || + PyModule_AddIntConstant(m, "LOAD_PEDANTIC", FT_LOAD_PEDANTIC) || + PyModule_AddIntConstant(m, "LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH", FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH) || + PyModule_AddIntConstant(m, "LOAD_NO_RECURSE", FT_LOAD_NO_RECURSE) || + PyModule_AddIntConstant(m, "LOAD_IGNORE_TRANSFORM", FT_LOAD_IGNORE_TRANSFORM) || + PyModule_AddIntConstant(m, "LOAD_MONOCHROME", FT_LOAD_MONOCHROME) || + PyModule_AddIntConstant(m, "LOAD_LINEAR_DESIGN", FT_LOAD_LINEAR_DESIGN) || + PyModule_AddIntConstant(m, "LOAD_NO_AUTOHINT", (unsigned long)FT_LOAD_NO_AUTOHINT) || + PyModule_AddIntConstant(m, "LOAD_TARGET_NORMAL", (unsigned long)FT_LOAD_TARGET_NORMAL) || + PyModule_AddIntConstant(m, "LOAD_TARGET_LIGHT", (unsigned long)FT_LOAD_TARGET_LIGHT) || + PyModule_AddIntConstant(m, "LOAD_TARGET_MONO", (unsigned long)FT_LOAD_TARGET_MONO) || + PyModule_AddIntConstant(m, "LOAD_TARGET_LCD", (unsigned long)FT_LOAD_TARGET_LCD) || + PyModule_AddIntConstant(m, "LOAD_TARGET_LCD_V", (unsigned long)FT_LOAD_TARGET_LCD_V)) { + FT_Done_FreeType(_ft2Library); + Py_XDECREF(m); + return NULL; + } + + return m; +} diff --git a/contrib/python/matplotlib/py3/src/mplutils.h b/contrib/python/matplotlib/py3/src/mplutils.h new file mode 100644 index 0000000000..6eb89899ca --- /dev/null +++ b/contrib/python/matplotlib/py3/src/mplutils.h @@ -0,0 +1,99 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +/* Small utilities that are shared by most extension modules. */ + +#ifndef MPLUTILS_H +#define MPLUTILS_H +#define PY_SSIZE_T_CLEAN + +#include <Python.h> +#include <stdint.h> + +#ifdef _POSIX_C_SOURCE +# undef _POSIX_C_SOURCE +#endif +#ifndef _AIX +#ifdef _XOPEN_SOURCE +# undef _XOPEN_SOURCE +#endif +#endif + +// Prevent multiple conflicting definitions of swab from stdlib.h and unistd.h +#if defined(__sun) || defined(sun) +#if defined(_XPG4) +#undef _XPG4 +#endif +#if defined(_XPG3) +#undef _XPG3 +#endif +#endif + + +inline int mpl_round_to_int(double v) +{ + return (int)(v + ((v >= 0.0) ? 0.5 : -0.5)); +} + +inline double mpl_round(double v) +{ + return (double)mpl_round_to_int(v); +} + +// 'kind' codes for paths. +enum { + STOP = 0, + MOVETO = 1, + LINETO = 2, + CURVE3 = 3, + CURVE4 = 4, + CLOSEPOLY = 0x4f +}; + +const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3, 1 }; + +inline int prepare_and_add_type(PyTypeObject *type, PyObject *module) +{ + if (PyType_Ready(type)) { + return -1; + } + char const* ptr = strrchr(type->tp_name, '.'); + if (!ptr) { + PyErr_SetString(PyExc_ValueError, "tp_name should be a qualified name"); + return -1; + } + if (PyModule_AddObject(module, ptr + 1, (PyObject *)type)) { + return -1; + } + return 0; +} + +#ifdef __cplusplus // not for macosx.m +// Check that array has shape (N, d1) or (N, d1, d2). We cast d1, d2 to longs +// so that we don't need to access the NPY_INTP_FMT macro here. + +template<typename T> +inline bool check_trailing_shape(T array, char const* name, long d1) +{ + if (array.dim(1) != d1) { + PyErr_Format(PyExc_ValueError, + "%s must have shape (N, %ld), got (%ld, %ld)", + name, d1, array.dim(0), array.dim(1)); + return false; + } + return true; +} + +template<typename T> +inline bool check_trailing_shape(T array, char const* name, long d1, long d2) +{ + if (array.dim(1) != d1 || array.dim(2) != d2) { + PyErr_Format(PyExc_ValueError, + "%s must have shape (N, %ld, %ld), got (%ld, %ld, %ld)", + name, d1, d2, array.dim(0), array.dim(1), array.dim(2)); + return false; + } + return true; +} +#endif + +#endif diff --git a/contrib/python/matplotlib/py3/src/numpy_cpp.h b/contrib/python/matplotlib/py3/src/numpy_cpp.h new file mode 100644 index 0000000000..36c763d158 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/numpy_cpp.h @@ -0,0 +1,578 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef MPL_NUMPY_CPP_H +#define MPL_NUMPY_CPP_H +#define PY_SSIZE_T_CLEAN +/*************************************************************************** + * This file is based on original work by Mark Wiebe, available at: + * + * http://github.com/mwiebe/numpy-cpp + * + * However, the needs of matplotlib wrappers, such as treating an + * empty array as having the correct dimensions, have made this rather + * matplotlib-specific, so it's no longer compatible with the + * original. + */ + +#include "py_exceptions.h" + +#include <complex> + +#ifdef _POSIX_C_SOURCE +# undef _POSIX_C_SOURCE +#endif +#ifndef _AIX +#ifdef _XOPEN_SOURCE +# undef _XOPEN_SOURCE +#endif +#endif + +// Prevent multiple conflicting definitions of swab from stdlib.h and unistd.h +#if defined(__sun) || defined(sun) +#if defined(_XPG4) +#undef _XPG4 +#endif +#if defined(_XPG3) +#undef _XPG3 +#endif +#endif + +#include <Python.h> +#include <numpy/ndarrayobject.h> + +namespace numpy +{ + +// Type traits for the NumPy types +template <typename T> +struct type_num_of; + +/* Be careful with bool arrays as python has sizeof(npy_bool) == 1, but it is + * not always the case that sizeof(bool) == 1. Using the array_view_accessors + * is always fine regardless of sizeof(bool), so do this rather than using + * array.data() and pointer arithmetic which will not work correctly if + * sizeof(bool) != 1. */ +template <> struct type_num_of<bool> +{ + enum { + value = NPY_BOOL + }; +}; +template <> +struct type_num_of<npy_byte> +{ + enum { + value = NPY_BYTE + }; +}; +template <> +struct type_num_of<npy_ubyte> +{ + enum { + value = NPY_UBYTE + }; +}; +template <> +struct type_num_of<npy_short> +{ + enum { + value = NPY_SHORT + }; +}; +template <> +struct type_num_of<npy_ushort> +{ + enum { + value = NPY_USHORT + }; +}; +template <> +struct type_num_of<npy_int> +{ + enum { + value = NPY_INT + }; +}; +template <> +struct type_num_of<npy_uint> +{ + enum { + value = NPY_UINT + }; +}; +template <> +struct type_num_of<npy_long> +{ + enum { + value = NPY_LONG + }; +}; +template <> +struct type_num_of<npy_ulong> +{ + enum { + value = NPY_ULONG + }; +}; +template <> +struct type_num_of<npy_longlong> +{ + enum { + value = NPY_LONGLONG + }; +}; +template <> +struct type_num_of<npy_ulonglong> +{ + enum { + value = NPY_ULONGLONG + }; +}; +template <> +struct type_num_of<npy_float> +{ + enum { + value = NPY_FLOAT + }; +}; +template <> +struct type_num_of<npy_double> +{ + enum { + value = NPY_DOUBLE + }; +}; +#if NPY_LONGDOUBLE != NPY_DOUBLE +template <> +struct type_num_of<npy_longdouble> +{ + enum { + value = NPY_LONGDOUBLE + }; +}; +#endif +template <> +struct type_num_of<npy_cfloat> +{ + enum { + value = NPY_CFLOAT + }; +}; +template <> +struct type_num_of<std::complex<npy_float> > +{ + enum { + value = NPY_CFLOAT + }; +}; +template <> +struct type_num_of<npy_cdouble> +{ + enum { + value = NPY_CDOUBLE + }; +}; +template <> +struct type_num_of<std::complex<npy_double> > +{ + enum { + value = NPY_CDOUBLE + }; +}; +#if NPY_CLONGDOUBLE != NPY_CDOUBLE +template <> +struct type_num_of<npy_clongdouble> +{ + enum { + value = NPY_CLONGDOUBLE + }; +}; +template <> +struct type_num_of<std::complex<npy_longdouble> > +{ + enum { + value = NPY_CLONGDOUBLE + }; +}; +#endif +template <> +struct type_num_of<PyObject *> +{ + enum { + value = NPY_OBJECT + }; +}; +template <typename T> +struct type_num_of<T &> +{ + enum { + value = type_num_of<T>::value + }; +}; +template <typename T> +struct type_num_of<const T> +{ + enum { + value = type_num_of<T>::value + }; +}; + +template <typename T> +struct is_const +{ + enum { + value = false + }; +}; +template <typename T> +struct is_const<const T> +{ + enum { + value = true + }; +}; + +namespace detail +{ +template <template <typename, int> class AV, typename T, int ND> +class array_view_accessors; + +template <template <typename, int> class AV, typename T> +class array_view_accessors<AV, T, 1> +{ + public: + typedef AV<T, 1> AVC; + typedef T sub_t; + + T &operator()(npy_intp i) + { + AVC *self = static_cast<AVC *>(this); + + return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i); + } + + const T &operator()(npy_intp i) const + { + const AVC *self = static_cast<const AVC *>(this); + + return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i); + } + + T &operator[](npy_intp i) + { + AVC *self = static_cast<AVC *>(this); + + return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i); + } + + const T &operator[](npy_intp i) const + { + const AVC *self = static_cast<const AVC *>(this); + + return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i); + } +}; + +template <template <typename, int> class AV, typename T> +class array_view_accessors<AV, T, 2> +{ + public: + typedef AV<T, 2> AVC; + typedef AV<T, 1> sub_t; + + T &operator()(npy_intp i, npy_intp j) + { + AVC *self = static_cast<AVC *>(this); + + return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i + + self->m_strides[1] * j); + } + + const T &operator()(npy_intp i, npy_intp j) const + { + const AVC *self = static_cast<const AVC *>(this); + + return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i + + self->m_strides[1] * j); + } + + sub_t subarray(npy_intp i) const + { + const AVC *self = static_cast<const AVC *>(this); + + return sub_t(self->m_arr, + self->m_data + self->m_strides[0] * i, + self->m_shape + 1, + self->m_strides + 1); + } +}; + +template <template <typename, int> class AV, typename T> +class array_view_accessors<AV, T, 3> +{ + public: + typedef AV<T, 3> AVC; + typedef AV<T, 2> sub_t; + + T &operator()(npy_intp i, npy_intp j, npy_intp k) + { + AVC *self = static_cast<AVC *>(this); + + return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i + + self->m_strides[1] * j + self->m_strides[2] * k); + } + + const T &operator()(npy_intp i, npy_intp j, npy_intp k) const + { + const AVC *self = static_cast<const AVC *>(this); + + return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i + + self->m_strides[1] * j + self->m_strides[2] * k); + } + + sub_t subarray(npy_intp i) const + { + const AVC *self = static_cast<const AVC *>(this); + + return sub_t(self->m_arr, + self->m_data + self->m_strides[0] * i, + self->m_shape + 1, + self->m_strides + 1); + } + + +}; + +// When adding instantiations of array_view_accessors, remember to add entries +// to zeros[] below. + +} + +static npy_intp zeros[] = { 0, 0, 0 }; + +template <typename T, int ND> +class array_view : public detail::array_view_accessors<array_view, T, ND> +{ + friend class detail::array_view_accessors<numpy::array_view, T, ND>; + + private: + // Copies of the array data + PyArrayObject *m_arr; + npy_intp *m_shape; + npy_intp *m_strides; + char *m_data; + + public: + typedef T value_type; + + enum { + ndim = ND + }; + + array_view() : m_arr(NULL), m_data(NULL) + { + m_shape = zeros; + m_strides = zeros; + } + + array_view(PyObject *arr, bool contiguous = false) : m_arr(NULL), m_data(NULL) + { + if (!set(arr, contiguous)) { + throw py::exception(); + } + } + + array_view(const array_view &other) : m_arr(NULL), m_data(NULL) + { + m_arr = other.m_arr; + Py_XINCREF(m_arr); + m_data = other.m_data; + m_shape = other.m_shape; + m_strides = other.m_strides; + } + + array_view(PyArrayObject *arr, char *data, npy_intp *shape, npy_intp *strides) + { + m_arr = arr; + Py_XINCREF(arr); + m_data = data; + m_shape = shape; + m_strides = strides; + } + + array_view(PyArrayObject *arr) + { + m_arr = arr; + Py_XINCREF(arr); + m_shape = PyArray_DIMS(m_arr); + m_strides = PyArray_STRIDES(m_arr); + m_data = PyArray_BYTES(m_arr); + } + + array_view(npy_intp shape[ND]) : m_arr(NULL), m_shape(NULL), m_strides(NULL), m_data(NULL) + { + PyObject *arr = PyArray_SimpleNew(ND, shape, type_num_of<T>::value); + if (arr == NULL) { + throw py::exception(); + } + if (!set(arr, true)) { + Py_DECREF(arr); + throw py::exception(); + } + Py_DECREF(arr); + } + + ~array_view() + { + Py_XDECREF(m_arr); + } + + array_view& operator=(const array_view &other) + { + if (this != &other) + { + Py_XDECREF(m_arr); + m_arr = other.m_arr; + Py_XINCREF(m_arr); + m_data = other.m_data; + m_shape = other.m_shape; + m_strides = other.m_strides; + } + return *this; + } + + bool set(PyObject *arr, bool contiguous = false) + { + PyArrayObject *tmp; + + if (arr == NULL || arr == Py_None) { + Py_XDECREF(m_arr); + m_arr = NULL; + m_data = NULL; + m_shape = zeros; + m_strides = zeros; + } else { + if (contiguous) { + tmp = (PyArrayObject *)PyArray_ContiguousFromAny(arr, type_num_of<T>::value, 0, ND); + } else { + tmp = (PyArrayObject *)PyArray_FromObject(arr, type_num_of<T>::value, 0, ND); + } + if (tmp == NULL) { + return false; + } + + if (PyArray_NDIM(tmp) == 0 || PyArray_DIM(tmp, 0) == 0) { + Py_XDECREF(m_arr); + m_arr = NULL; + m_data = NULL; + m_shape = zeros; + m_strides = zeros; + if (PyArray_NDIM(tmp) == 0 && ND == 0) { + m_arr = tmp; + return true; + } + } + if (PyArray_NDIM(tmp) != ND) { + PyErr_Format(PyExc_ValueError, + "Expected %d-dimensional array, got %d", + ND, + PyArray_NDIM(tmp)); + Py_DECREF(tmp); + return false; + } + + /* Copy some of the data to the view object for faster access */ + Py_XDECREF(m_arr); + m_arr = tmp; + m_shape = PyArray_DIMS(m_arr); + m_strides = PyArray_STRIDES(m_arr); + m_data = PyArray_BYTES(tmp); + } + + return true; + } + + npy_intp dim(size_t i) const + { + if (i >= ND) { + return 0; + } + return m_shape[i]; + } + + /* + In most cases, code should use size() instead of dim(0), since + size() == 0 when any dimension is 0. + */ + size_t size() const + { + bool empty = (ND == 0); + for (size_t i = 0; i < ND; i++) { + if (m_shape[i] == 0) { + empty = true; + } + } + if (empty) { + return 0; + } else { + return (size_t)dim(0); + } + } + + bool empty() const + { + return size() == 0; + } + + // Do not use this for array_view<bool, ND>. See comment near top of file. + const T *data() const + { + return (const T *)m_data; + } + + // Do not use this for array_view<bool, ND>. See comment near top of file. + T *data() + { + return (T *)m_data; + } + + // Return a new reference. + PyObject *pyobj() + { + Py_XINCREF(m_arr); + return (PyObject *)m_arr; + } + + // Steal a reference. + PyObject *pyobj_steal() + { + return (PyObject *)m_arr; + } + + static int converter(PyObject *obj, void *arrp) + { + array_view<T, ND> *arr = (array_view<T, ND> *)arrp; + + if (!arr->set(obj)) { + return 0; + } + + return 1; + } + + static int converter_contiguous(PyObject *obj, void *arrp) + { + array_view<T, ND> *arr = (array_view<T, ND> *)arrp; + + if (!arr->set(obj, true)) { + return 0; + } + + return 1; + } +}; + +} // namespace numpy + + +#endif diff --git a/contrib/python/matplotlib/py3/src/path_converters.h b/contrib/python/matplotlib/py3/src/path_converters.h new file mode 100644 index 0000000000..8583d84855 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/path_converters.h @@ -0,0 +1,1106 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef MPL_PATH_CONVERTERS_H +#define MPL_PATH_CONVERTERS_H + +#include <cmath> +#include <stdint.h> +#include "agg_path_storage.h" +#include "agg_clip_liang_barsky.h" +#include "mplutils.h" +#include "agg_conv_segmentator.h" + +/* + This file contains a number of vertex converters that modify + paths. They all work as iterators, where the output is generated + on-the-fly, and don't require a copy of the full data. + + Each class represents a discrete step in a "path-cleansing" pipeline. + They are currently applied in the following order in the Agg backend: + + 1. Affine transformation (implemented in Agg, not here) + + 2. PathNanRemover: skips over segments containing non-finite numbers + by inserting MOVETO commands + + 3. PathClipper: Clips line segments to a given rectangle. This is + helpful for data reduction, and also to avoid a limitation in + Agg where coordinates cannot be larger than 24-bit signed + integers. + + 4. PathSnapper: Rounds the path to the nearest center-pixels. + This makes rectilinear curves look much better. + + 5. PathSimplifier: Removes line segments from highly dense paths + that would not have an impact on their appearance. Speeds up + rendering and reduces file sizes. + + 6. curve-to-line-segment conversion (implemented in Agg, not here) + + 7. stroking (implemented in Agg, not here) + */ + +/************************************************************ + This is a base class for vertex converters that need to queue their + output. It is designed to be as fast as possible vs. the STL's queue + which is more flexible. + */ +template <int QueueSize> +class EmbeddedQueue +{ + protected: + EmbeddedQueue() : m_queue_read(0), m_queue_write(0) + { + // empty + } + + struct item + { + item() + { + } + + inline void set(const unsigned cmd_, const double x_, const double y_) + { + cmd = cmd_; + x = x_; + y = y_; + } + unsigned cmd; + double x; + double y; + }; + int m_queue_read; + int m_queue_write; + item m_queue[QueueSize]; + + inline void queue_push(const unsigned cmd, const double x, const double y) + { + m_queue[m_queue_write++].set(cmd, x, y); + } + + inline bool queue_nonempty() + { + return m_queue_read < m_queue_write; + } + + inline bool queue_pop(unsigned *cmd, double *x, double *y) + { + if (queue_nonempty()) { + const item &front = m_queue[m_queue_read++]; + *cmd = front.cmd; + *x = front.x; + *y = front.y; + + return true; + } + + m_queue_read = 0; + m_queue_write = 0; + + return false; + } + + inline void queue_clear() + { + m_queue_read = 0; + m_queue_write = 0; + } +}; + +/* Defines when path segment types have more than one vertex */ +static const size_t num_extra_points_map[] = + {0, 0, 0, 1, + 2, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + +/* An implementation of a simple linear congruential random number + generator. This is a "classic" and fast RNG which works fine for + our purposes of sketching lines, but should not be used for things + that matter, like crypto. We are implementing this ourselves + rather than using the C stdlib so that the seed state is not shared + with other third-party code. There are recent C++ options, but we + still require nothing later than C++98 for compatibility + reasons. */ +class RandomNumberGenerator +{ +private: + /* These are the same constants from MS Visual C++, which + has the nice property that the modulus is 2^32, thus + saving an explicit modulo operation + */ + static const uint32_t a = 214013; + static const uint32_t c = 2531011; + uint32_t m_seed; + +public: + RandomNumberGenerator() : m_seed(0) {} + RandomNumberGenerator(int seed) : m_seed(seed) {} + + void seed(int seed) + { + m_seed = seed; + } + + double get_double() + { + m_seed = (a * m_seed + c); + return (double)m_seed / (double)(1LL << 32); + } +}; + +/* + PathNanRemover is a vertex converter that removes non-finite values + from the vertices list, and inserts MOVETO commands as necessary to + skip over them. If a curve segment contains at least one non-finite + value, the entire curve segment will be skipped. + */ +template <class VertexSource> +class PathNanRemover : protected EmbeddedQueue<4> +{ + VertexSource *m_source; + bool m_remove_nans; + bool m_has_codes; + bool valid_segment_exists; + bool m_last_segment_valid; + bool m_was_broken; + double m_initX; + double m_initY; + + public: + /* has_codes should be true if the path contains bezier curve segments, or + * closed loops, as this requires a slower algorithm to remove the NaNs. + * When in doubt, set to true. + */ + PathNanRemover(VertexSource &source, bool remove_nans, bool has_codes) + : m_source(&source), m_remove_nans(remove_nans), m_has_codes(has_codes), + m_last_segment_valid(false), m_was_broken(false), + m_initX(nan("")), m_initY(nan("")) + { + // ignore all close/end_poly commands until after the first valid + // (nan-free) command is encountered + valid_segment_exists = false; + } + + inline void rewind(unsigned path_id) + { + queue_clear(); + m_source->rewind(path_id); + } + + inline unsigned vertex(double *x, double *y) + { + unsigned code; + + if (!m_remove_nans) { + return m_source->vertex(x, y); + } + + if (m_has_codes) { + /* This is the slow method for when there might be curves or closed + * loops. */ + if (queue_pop(&code, x, y)) { + return code; + } + + bool needs_move_to = false; + while (true) { + /* The approach here is to push each full curve + segment into the queue. If any non-finite values + are found along the way, the queue is emptied, and + the next curve segment is handled. */ + code = m_source->vertex(x, y); + /* The vertices attached to STOP and CLOSEPOLY are never used, + * so we leave them as-is even if NaN. */ + if (code == agg::path_cmd_stop) { + return code; + } else if (code == (agg::path_cmd_end_poly | + agg::path_flags_close) && + valid_segment_exists) { + /* However, CLOSEPOLY only makes sense if a valid MOVETO + * command has already been emitted. But if a NaN was + * removed in the path, then we cannot close it as it is no + * longer a loop. We must emulate that by inserting a + * LINETO instead. */ + if (m_was_broken) { + if (m_last_segment_valid && ( + std::isfinite(m_initX) && + std::isfinite(m_initY))) { + /* Join to start if both ends are valid. */ + queue_push(agg::path_cmd_line_to, m_initX, m_initY); + break; + } else { + /* Skip the close, in case there are additional + * subpaths. */ + continue; + } + m_was_broken = false; + break; + } else { + return code; + } + } else if (code == agg::path_cmd_move_to) { + /* Save the initial point in order to produce the last + * segment closing a loop, *if* we broke the loop. */ + m_initX = *x; + m_initY = *y; + m_was_broken = false; + } + + if (needs_move_to) { + queue_push(agg::path_cmd_move_to, *x, *y); + } + + size_t num_extra_points = num_extra_points_map[code & 0xF]; + m_last_segment_valid = (std::isfinite(*x) && std::isfinite(*y)); + queue_push(code, *x, *y); + + /* Note: this test cannot be short-circuited, since we need to + advance through the entire curve no matter what */ + for (size_t i = 0; i < num_extra_points; ++i) { + m_source->vertex(x, y); + m_last_segment_valid = m_last_segment_valid && + (std::isfinite(*x) && std::isfinite(*y)); + queue_push(code, *x, *y); + } + + if (m_last_segment_valid) { + valid_segment_exists = true; + break; + } + + m_was_broken = true; + queue_clear(); + + /* If the last point is finite, we use that for the + moveto, otherwise, we'll use the first vertex of + the next curve. */ + if (std::isfinite(*x) && std::isfinite(*y)) { + queue_push(agg::path_cmd_move_to, *x, *y); + needs_move_to = false; + } else { + needs_move_to = true; + } + } + + if (queue_pop(&code, x, y)) { + return code; + } else { + return agg::path_cmd_stop; + } + } else // !m_has_codes + { + /* This is the fast path for when we know we have no codes. */ + code = m_source->vertex(x, y); + + if (code == agg::path_cmd_stop || + (code == (agg::path_cmd_end_poly | agg::path_flags_close) && + valid_segment_exists)) { + return code; + } + + if (!(std::isfinite(*x) && std::isfinite(*y))) { + do { + code = m_source->vertex(x, y); + if (code == agg::path_cmd_stop || + (code == (agg::path_cmd_end_poly | agg::path_flags_close) && + valid_segment_exists)) { + return code; + } + } while (!(std::isfinite(*x) && std::isfinite(*y))); + return agg::path_cmd_move_to; + } + valid_segment_exists = true; + return code; + } + } +}; + +/************************************************************ + PathClipper uses the Liang-Barsky line clipping algorithm (as + implemented in Agg) to clip the path to a given rectangle. Lines + will never extend outside of the rectangle. Curve segments are not + clipped, but are always included in their entirety. + */ +template <class VertexSource> +class PathClipper : public EmbeddedQueue<3> +{ + VertexSource *m_source; + bool m_do_clipping; + agg::rect_base<double> m_cliprect; + double m_lastX; + double m_lastY; + bool m_moveto; + double m_initX; + double m_initY; + bool m_has_init; + bool m_was_clipped; + + public: + PathClipper(VertexSource &source, bool do_clipping, double width, double height) + : m_source(&source), + m_do_clipping(do_clipping), + m_cliprect(-1.0, -1.0, width + 1.0, height + 1.0), + m_lastX(nan("")), + m_lastY(nan("")), + m_moveto(true), + m_initX(nan("")), + m_initY(nan("")), + m_has_init(false), + m_was_clipped(false) + { + // empty + } + + PathClipper(VertexSource &source, bool do_clipping, const agg::rect_base<double> &rect) + : m_source(&source), + m_do_clipping(do_clipping), + m_cliprect(rect), + m_lastX(nan("")), + m_lastY(nan("")), + m_moveto(true), + m_initX(nan("")), + m_initY(nan("")), + m_has_init(false), + m_was_clipped(false) + { + m_cliprect.x1 -= 1.0; + m_cliprect.y1 -= 1.0; + m_cliprect.x2 += 1.0; + m_cliprect.y2 += 1.0; + } + + inline void rewind(unsigned path_id) + { + m_has_init = false; + m_was_clipped = false; + m_moveto = true; + m_source->rewind(path_id); + } + + int draw_clipped_line(double x0, double y0, double x1, double y1, + bool closed=false) + { + unsigned moved = agg::clip_line_segment(&x0, &y0, &x1, &y1, m_cliprect); + // moved >= 4 - Fully clipped + // moved & 1 != 0 - First point has been moved + // moved & 2 != 0 - Second point has been moved + m_was_clipped = m_was_clipped || (moved != 0); + if (moved < 4) { + if (moved & 1 || m_moveto) { + queue_push(agg::path_cmd_move_to, x0, y0); + } + queue_push(agg::path_cmd_line_to, x1, y1); + if (closed && !m_was_clipped) { + // Close the path only if the end point hasn't moved. + queue_push(agg::path_cmd_end_poly | agg::path_flags_close, + x1, y1); + } + + m_moveto = false; + return 1; + } + + return 0; + } + + unsigned vertex(double *x, double *y) + { + unsigned code; + bool emit_moveto = false; + + if (!m_do_clipping) { + // If not doing any clipping, just pass along the vertices verbatim + return m_source->vertex(x, y); + } + + /* This is the slow path where we actually do clipping */ + + if (queue_pop(&code, x, y)) { + return code; + } + + while ((code = m_source->vertex(x, y)) != agg::path_cmd_stop) { + emit_moveto = false; + + switch (code) { + case (agg::path_cmd_end_poly | agg::path_flags_close): + if (m_has_init) { + // Queue the line from last point to the initial point, and + // if never clipped, add a close code. + draw_clipped_line(m_lastX, m_lastY, m_initX, m_initY, + true); + } else { + // An empty path that is immediately closed. + queue_push( + agg::path_cmd_end_poly | agg::path_flags_close, + m_lastX, m_lastY); + } + // If paths were not clipped, then the above code queued + // something, and we should exit the loop. Otherwise, continue + // to the next point, as there may be a new subpath. + if (queue_nonempty()) { + goto exit_loop; + } + break; + + case agg::path_cmd_move_to: + + // was the last command a moveto (and we have + // seen at least one command ? + // if so, shove it in the queue if in clip box + if (m_moveto && m_has_init && + m_lastX >= m_cliprect.x1 && + m_lastX <= m_cliprect.x2 && + m_lastY >= m_cliprect.y1 && + m_lastY <= m_cliprect.y2) { + // push the last moveto onto the queue + queue_push(agg::path_cmd_move_to, m_lastX, m_lastY); + // flag that we need to emit it + emit_moveto = true; + } + // update the internal state for this moveto + m_initX = m_lastX = *x; + m_initY = m_lastY = *y; + m_has_init = true; + m_moveto = true; + m_was_clipped = false; + // if the last command was moveto exit the loop to emit the code + if (emit_moveto) { + goto exit_loop; + } + // else, break and get the next point + break; + + case agg::path_cmd_line_to: + if (draw_clipped_line(m_lastX, m_lastY, *x, *y)) { + m_lastX = *x; + m_lastY = *y; + goto exit_loop; + } + m_lastX = *x; + m_lastY = *y; + break; + + default: + if (m_moveto) { + queue_push(agg::path_cmd_move_to, m_lastX, m_lastY); + m_moveto = false; + } + + queue_push(code, *x, *y); + m_lastX = *x; + m_lastY = *y; + goto exit_loop; + } + } + + exit_loop: + + if (queue_pop(&code, x, y)) { + return code; + } + + if (m_moveto && m_has_init && + m_lastX >= m_cliprect.x1 && + m_lastX <= m_cliprect.x2 && + m_lastY >= m_cliprect.y1 && + m_lastY <= m_cliprect.y2) { + *x = m_lastX; + *y = m_lastY; + m_moveto = false; + return agg::path_cmd_move_to; + } + + return agg::path_cmd_stop; + } +}; + +/************************************************************ + PathSnapper rounds vertices to their nearest center-pixels. This + makes rectilinear paths (rectangles, horizontal and vertical lines + etc.) look much cleaner. +*/ +enum e_snap_mode { + SNAP_AUTO, + SNAP_FALSE, + SNAP_TRUE +}; + +template <class VertexSource> +class PathSnapper +{ + private: + VertexSource *m_source; + bool m_snap; + double m_snap_value; + + static bool should_snap(VertexSource &path, e_snap_mode snap_mode, unsigned total_vertices) + { + // If this contains only straight horizontal or vertical lines, it should be + // snapped to the nearest pixels + double x0 = 0, y0 = 0, x1 = 0, y1 = 0; + unsigned code; + + switch (snap_mode) { + case SNAP_AUTO: + if (total_vertices > 1024) { + return false; + } + + code = path.vertex(&x0, &y0); + if (code == agg::path_cmd_stop) { + return false; + } + + while ((code = path.vertex(&x1, &y1)) != agg::path_cmd_stop) { + switch (code) { + case agg::path_cmd_curve3: + case agg::path_cmd_curve4: + return false; + case agg::path_cmd_line_to: + if (fabs(x0 - x1) >= 1e-4 && fabs(y0 - y1) >= 1e-4) { + return false; + } + } + x0 = x1; + y0 = y1; + } + + return true; + case SNAP_FALSE: + return false; + case SNAP_TRUE: + return true; + } + + return false; + } + + public: + /* + snap_mode should be one of: + - SNAP_AUTO: Examine the path to determine if it should be snapped + - SNAP_TRUE: Force snapping + - SNAP_FALSE: No snapping + */ + PathSnapper(VertexSource &source, + e_snap_mode snap_mode, + unsigned total_vertices = 15, + double stroke_width = 0.0) + : m_source(&source) + { + m_snap = should_snap(source, snap_mode, total_vertices); + + if (m_snap) { + int is_odd = mpl_round_to_int(stroke_width) % 2; + m_snap_value = (is_odd) ? 0.5 : 0.0; + } + + source.rewind(0); + } + + inline void rewind(unsigned path_id) + { + m_source->rewind(path_id); + } + + inline unsigned vertex(double *x, double *y) + { + unsigned code; + code = m_source->vertex(x, y); + if (m_snap && agg::is_vertex(code)) { + *x = floor(*x + 0.5) + m_snap_value; + *y = floor(*y + 0.5) + m_snap_value; + } + return code; + } + + inline bool is_snapping() + { + return m_snap; + } +}; + +/************************************************************ + PathSimplifier reduces the number of vertices in a dense path without + changing its appearance. +*/ +template <class VertexSource> +class PathSimplifier : protected EmbeddedQueue<9> +{ + public: + /* Set simplify to true to perform simplification */ + PathSimplifier(VertexSource &source, bool do_simplify, double simplify_threshold) + : m_source(&source), + m_simplify(do_simplify), + /* we square simplify_threshold so that we can compute + norms without doing the square root every step. */ + m_simplify_threshold(simplify_threshold * simplify_threshold), + + m_moveto(true), + m_after_moveto(false), + m_clipped(false), + + // the x, y values from last iteration + m_lastx(0.0), + m_lasty(0.0), + + // the dx, dy comprising the original vector, used in conjunction + // with m_currVecStart* to define the original vector. + m_origdx(0.0), + m_origdy(0.0), + + // the squared norm of the original vector + m_origdNorm2(0.0), + + // maximum squared norm of vector in forward (parallel) direction + m_dnorm2ForwardMax(0.0), + // maximum squared norm of vector in backward (anti-parallel) direction + m_dnorm2BackwardMax(0.0), + + // was the last point the furthest from lastWritten in the + // forward (parallel) direction? + m_lastForwardMax(false), + // was the last point the furthest from lastWritten in the + // backward (anti-parallel) direction? + m_lastBackwardMax(false), + + // added to queue when _push is called + m_nextX(0.0), + m_nextY(0.0), + + // added to queue when _push is called if any backwards + // (anti-parallel) vectors were observed + m_nextBackwardX(0.0), + m_nextBackwardY(0.0), + + // start of the current vector that is being simplified + m_currVecStartX(0.0), + m_currVecStartY(0.0) + { + // empty + } + + inline void rewind(unsigned path_id) + { + queue_clear(); + m_moveto = true; + m_source->rewind(path_id); + } + + unsigned vertex(double *x, double *y) + { + unsigned cmd; + + /* The simplification algorithm doesn't support curves or compound paths + so we just don't do it at all in that case... */ + if (!m_simplify) { + return m_source->vertex(x, y); + } + + /* idea: we can skip drawing many lines: we can combine + sequential parallel lines into a + single line instead of redrawing lines over the same + points. The loop below works a bit like a state machine, + where what it does depends on what it did in the last + looping. To test whether sequential lines are close to + parallel, I calculate the distance moved perpendicular to + the last line. Once it gets too big, the lines cannot be + combined. */ + + /* This code was originally written by Allan Haldane and I + have modified to work in-place -- meaning not creating an + entirely new path list each time. In order to do that + without too much additional code complexity, it keeps a + small queue around so that multiple points can be emitted + in a single call, and those points will be popped from the + queue in subsequent calls. The following block will empty + the queue before proceeding to the main loop below. + -- Michael Droettboom */ + + /* This code was originally written by Allan Haldane and + updated by Michael Droettboom. I have modified it to + handle anti-parallel vectors. This is done essentially + the same way as parallel vectors, but requires a little + additional book-keeping to track whether or not we have + observed an anti-parallel vector during the current run. + -- Kevin Rose */ + + if (queue_pop(&cmd, x, y)) { + return cmd; + } + + /* The main simplification loop. The point is to consume only + as many points as necessary until something has been added + to the outbound queue, not to run through the entire path + in one go. This eliminates the need to allocate and fill + an entire additional path array on each draw. */ + while ((cmd = m_source->vertex(x, y)) != agg::path_cmd_stop) { + /* if we are starting a new path segment, move to the first point + + init */ + + if (m_moveto || cmd == agg::path_cmd_move_to) { + /* m_moveto check is not generally needed because + m_source generates an initial moveto; but it is + retained for safety in case circumstances arise + where this is not true. */ + if (m_origdNorm2 != 0.0 && !m_after_moveto) { + /* m_origdNorm2 is nonzero only if we have a + vector; the m_after_moveto check ensures we + push this vector to the queue only once. */ + _push(x, y); + } + m_after_moveto = true; + m_lastx = *x; + m_lasty = *y; + m_moveto = false; + m_origdNorm2 = 0.0; + m_dnorm2BackwardMax = 0.0; + m_clipped = true; + if (queue_nonempty()) { + /* If we did a push, empty the queue now. */ + break; + } + continue; + } + m_after_moveto = false; + + /* NOTE: We used to skip this very short segments, but if + you have a lot of them cumulatively, you can miss + maxima or minima in the data. */ + + /* Don't render line segments less than one pixel long */ + /* if (fabs(*x - m_lastx) < 1.0 && fabs(*y - m_lasty) < 1.0) */ + /* { */ + /* continue; */ + /* } */ + + /* if we have no orig vector, set it to this vector and + continue. this orig vector is the reference vector we + will build up the line to */ + if (m_origdNorm2 == 0.0) { + if (m_clipped) { + queue_push(agg::path_cmd_move_to, m_lastx, m_lasty); + m_clipped = false; + } + + m_origdx = *x - m_lastx; + m_origdy = *y - m_lasty; + m_origdNorm2 = m_origdx * m_origdx + m_origdy * m_origdy; + + // set all the variables to reflect this new orig vector + m_dnorm2ForwardMax = m_origdNorm2; + m_dnorm2BackwardMax = 0.0; + m_lastForwardMax = true; + m_lastBackwardMax = false; + + m_currVecStartX = m_lastx; + m_currVecStartY = m_lasty; + m_nextX = m_lastx = *x; + m_nextY = m_lasty = *y; + continue; + } + + /* If got to here, then we have an orig vector and we just got + a vector in the sequence. */ + + /* Check that the perpendicular distance we have moved + from the last written point compared to the line we are + building is not too much. If o is the orig vector (we + are building on), and v is the vector from the last + written point to the current point, then the + perpendicular vector is p = v - (o.v)o/(o.o) + (here, a.b indicates the dot product of a and b). */ + + /* get the v vector */ + double totdx = *x - m_currVecStartX; + double totdy = *y - m_currVecStartY; + + /* get the dot product o.v */ + double totdot = m_origdx * totdx + m_origdy * totdy; + + /* get the para vector ( = (o.v)o/(o.o)) */ + double paradx = totdot * m_origdx / m_origdNorm2; + double parady = totdot * m_origdy / m_origdNorm2; + + /* get the perp vector ( = v - para) */ + double perpdx = totdx - paradx; + double perpdy = totdy - parady; + + /* get the squared norm of perp vector ( = p.p) */ + double perpdNorm2 = perpdx * perpdx + perpdy * perpdy; + + /* If the perpendicular vector is less than + m_simplify_threshold pixels in size, then merge + current x,y with the current vector */ + if (perpdNorm2 < m_simplify_threshold) { + /* check if the current vector is parallel or + anti-parallel to the orig vector. In either case, + test if it is the longest of the vectors + we are merging in that direction. If it is, then + update the current vector in that direction. */ + double paradNorm2 = paradx * paradx + parady * parady; + + m_lastForwardMax = false; + m_lastBackwardMax = false; + if (totdot > 0.0) { + if (paradNorm2 > m_dnorm2ForwardMax) { + m_lastForwardMax = true; + m_dnorm2ForwardMax = paradNorm2; + m_nextX = *x; + m_nextY = *y; + } + } else { + if (paradNorm2 > m_dnorm2BackwardMax) { + m_lastBackwardMax = true; + m_dnorm2BackwardMax = paradNorm2; + m_nextBackwardX = *x; + m_nextBackwardY = *y; + } + } + + m_lastx = *x; + m_lasty = *y; + continue; + } + + /* If we get here, then this vector was not similar enough to the + line we are building, so we need to draw that line and start the + next one. */ + + /* If the line needs to extend in the opposite direction from the + direction we are drawing in, move back to we start drawing from + back there. */ + _push(x, y); + + break; + } + + /* Fill the queue with the remaining vertices if we've finished the + path in the above loop. */ + if (cmd == agg::path_cmd_stop) { + if (m_origdNorm2 != 0.0) { + queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to + : agg::path_cmd_line_to, + m_nextX, + m_nextY); + if (m_dnorm2BackwardMax > 0.0) { + queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to + : agg::path_cmd_line_to, + m_nextBackwardX, + m_nextBackwardY); + } + m_moveto = false; + } + queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to : agg::path_cmd_line_to, + m_lastx, + m_lasty); + m_moveto = false; + queue_push(agg::path_cmd_stop, 0.0, 0.0); + } + + /* Return the first item in the queue, if any, otherwise + indicate that we're done. */ + if (queue_pop(&cmd, x, y)) { + return cmd; + } else { + return agg::path_cmd_stop; + } + } + + private: + VertexSource *m_source; + bool m_simplify; + double m_simplify_threshold; + + bool m_moveto; + bool m_after_moveto; + bool m_clipped; + double m_lastx, m_lasty; + + double m_origdx; + double m_origdy; + double m_origdNorm2; + double m_dnorm2ForwardMax; + double m_dnorm2BackwardMax; + bool m_lastForwardMax; + bool m_lastBackwardMax; + double m_nextX; + double m_nextY; + double m_nextBackwardX; + double m_nextBackwardY; + double m_currVecStartX; + double m_currVecStartY; + + inline void _push(double *x, double *y) + { + bool needToPushBack = (m_dnorm2BackwardMax > 0.0); + + /* If we observed any backward (anti-parallel) vectors, then + we need to push both forward and backward vectors. */ + if (needToPushBack) { + /* If the last vector seen was the maximum in the forward direction, + then we need to push the forward after the backward. Otherwise, + the last vector seen was the maximum in the backward direction, + or somewhere in between, either way we are safe pushing forward + before backward. */ + if (m_lastForwardMax) { + queue_push(agg::path_cmd_line_to, m_nextBackwardX, m_nextBackwardY); + queue_push(agg::path_cmd_line_to, m_nextX, m_nextY); + } else { + queue_push(agg::path_cmd_line_to, m_nextX, m_nextY); + queue_push(agg::path_cmd_line_to, m_nextBackwardX, m_nextBackwardY); + } + } else { + /* If we did not observe any backwards vectors, just push forward. */ + queue_push(agg::path_cmd_line_to, m_nextX, m_nextY); + } + + /* If we clipped some segments between this line and the next line + we are starting, we also need to move to the last point. */ + if (m_clipped) { + queue_push(agg::path_cmd_move_to, m_lastx, m_lasty); + } else if ((!m_lastForwardMax) && (!m_lastBackwardMax)) { + /* If the last line was not the longest line, then move + back to the end point of the last line in the + sequence. Only do this if not clipped, since in that + case lastx,lasty is not part of the line just drawn. */ + + /* Would be move_to if not for the artifacts */ + queue_push(agg::path_cmd_line_to, m_lastx, m_lasty); + } + + /* Now reset all the variables to get ready for the next line */ + m_origdx = *x - m_lastx; + m_origdy = *y - m_lasty; + m_origdNorm2 = m_origdx * m_origdx + m_origdy * m_origdy; + + m_dnorm2ForwardMax = m_origdNorm2; + m_lastForwardMax = true; + m_currVecStartX = m_queue[m_queue_write - 1].x; + m_currVecStartY = m_queue[m_queue_write - 1].y; + m_lastx = m_nextX = *x; + m_lasty = m_nextY = *y; + m_dnorm2BackwardMax = 0.0; + m_lastBackwardMax = false; + + m_clipped = false; + } +}; + +template <class VertexSource> +class Sketch +{ + public: + /* + scale: the scale of the wiggle perpendicular to the original + line (in pixels) + + length: the base wavelength of the wiggle along the + original line (in pixels) + + randomness: the factor that the sketch length will randomly + shrink and expand. + */ + Sketch(VertexSource &source, double scale, double length, double randomness) + : m_source(&source), + m_scale(scale), + m_length(length), + m_randomness(randomness), + m_segmented(source), + m_last_x(0.0), + m_last_y(0.0), + m_has_last(false), + m_p(0.0), + m_rand(0) + { + rewind(0); + const double d_M_PI = 3.14159265358979323846; + m_p_scale = (2.0 * d_M_PI) / (m_length * m_randomness); + m_log_randomness = 2.0 * log(m_randomness); + } + + unsigned vertex(double *x, double *y) + { + if (m_scale == 0.0) { + return m_source->vertex(x, y); + } + + unsigned code = m_segmented.vertex(x, y); + + if (code == agg::path_cmd_move_to) { + m_has_last = false; + m_p = 0.0; + } + + if (m_has_last) { + // We want the "cursor" along the sine wave to move at a + // random rate. + double d_rand = m_rand.get_double(); + // Original computation + // p += pow(k, 2*rand - 1) + // r = sin(p * c) + // x86 computes pow(a, b) as exp(b*log(a)) + // First, move -1 out, so + // p' += pow(k, 2*rand) + // r = sin(p * c') where c' = c / k + // Next, use x86 logic (will not be worse on other platforms as + // the log is only computed once and pow and exp are, at worst, + // the same) + // So p+= exp(2*rand*log(k)) + // lk = 2*log(k) + // p += exp(rand*lk) + m_p += exp(d_rand * m_log_randomness); + double den = m_last_x - *x; + double num = m_last_y - *y; + double len = num * num + den * den; + m_last_x = *x; + m_last_y = *y; + if (len != 0) { + len = sqrt(len); + double r = sin(m_p * m_p_scale) * m_scale; + double roverlen = r / len; + *x += roverlen * num; + *y -= roverlen * den; + } + } else { + m_last_x = *x; + m_last_y = *y; + } + + m_has_last = true; + + return code; + } + + inline void rewind(unsigned path_id) + { + m_has_last = false; + m_p = 0.0; + if (m_scale != 0.0) { + m_rand.seed(0); + m_segmented.rewind(path_id); + } else { + m_source->rewind(path_id); + } + } + + private: + VertexSource *m_source; + double m_scale; + double m_length; + double m_randomness; + agg::conv_segmentator<VertexSource> m_segmented; + double m_last_x; + double m_last_y; + bool m_has_last; + double m_p; + RandomNumberGenerator m_rand; + double m_p_scale; + double m_log_randomness; +}; + +#endif // MPL_PATH_CONVERTERS_H diff --git a/contrib/python/matplotlib/py3/src/py_adaptors.h b/contrib/python/matplotlib/py3/src/py_adaptors.h new file mode 100644 index 0000000000..7722137dc6 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/py_adaptors.h @@ -0,0 +1,248 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef MPL_PY_ADAPTORS_H +#define MPL_PY_ADAPTORS_H +#define PY_SSIZE_T_CLEAN +/*************************************************************************** + * This module contains a number of C++ classes that adapt Python data + * structures to C++ and Agg-friendly interfaces. + */ + +#include <Python.h> + +#include "numpy/arrayobject.h" + +#include "py_exceptions.h" + +extern "C" { +int convert_path(PyObject *obj, void *pathp); +} + +namespace py +{ + +/************************************************************ + * py::PathIterator acts as a bridge between Numpy and Agg. Given a + * pair of Numpy arrays, vertices and codes, it iterates over + * those vertices and codes, using the standard Agg vertex source + * interface: + * + * unsigned vertex(double* x, double* y) + */ +class PathIterator +{ + /* We hold references to the Python objects, not just the + underlying data arrays, so that Python reference counting + can work. + */ + PyArrayObject *m_vertices; + PyArrayObject *m_codes; + + unsigned m_iterator; + unsigned m_total_vertices; + + /* This class doesn't actually do any simplification, but we + store the value here, since it is obtained from the Python + object. + */ + bool m_should_simplify; + double m_simplify_threshold; + + public: + inline PathIterator() + : m_vertices(NULL), + m_codes(NULL), + m_iterator(0), + m_total_vertices(0), + m_should_simplify(false), + m_simplify_threshold(1.0 / 9.0) + { + } + + inline PathIterator(PyObject *vertices, + PyObject *codes, + bool should_simplify, + double simplify_threshold) + : m_vertices(NULL), m_codes(NULL), m_iterator(0) + { + if (!set(vertices, codes, should_simplify, simplify_threshold)) + throw py::exception(); + } + + inline PathIterator(PyObject *vertices, PyObject *codes) + : m_vertices(NULL), m_codes(NULL), m_iterator(0) + { + if (!set(vertices, codes)) + throw py::exception(); + } + + inline PathIterator(const PathIterator &other) + { + Py_XINCREF(other.m_vertices); + m_vertices = other.m_vertices; + + Py_XINCREF(other.m_codes); + m_codes = other.m_codes; + + m_iterator = 0; + m_total_vertices = other.m_total_vertices; + + m_should_simplify = other.m_should_simplify; + m_simplify_threshold = other.m_simplify_threshold; + } + + ~PathIterator() + { + Py_XDECREF(m_vertices); + Py_XDECREF(m_codes); + } + + inline int + set(PyObject *vertices, PyObject *codes, bool should_simplify, double simplify_threshold) + { + m_should_simplify = should_simplify; + m_simplify_threshold = simplify_threshold; + + Py_XDECREF(m_vertices); + m_vertices = (PyArrayObject *)PyArray_FromObject(vertices, NPY_DOUBLE, 2, 2); + + if (!m_vertices || PyArray_DIM(m_vertices, 1) != 2) { + PyErr_SetString(PyExc_ValueError, "Invalid vertices array"); + return 0; + } + + Py_XDECREF(m_codes); + m_codes = NULL; + + if (codes != NULL && codes != Py_None) { + m_codes = (PyArrayObject *)PyArray_FromObject(codes, NPY_UINT8, 1, 1); + + if (!m_codes || PyArray_DIM(m_codes, 0) != PyArray_DIM(m_vertices, 0)) { + PyErr_SetString(PyExc_ValueError, "Invalid codes array"); + return 0; + } + } + + m_total_vertices = (unsigned)PyArray_DIM(m_vertices, 0); + m_iterator = 0; + + return 1; + } + + inline int set(PyObject *vertices, PyObject *codes) + { + return set(vertices, codes, false, 0.0); + } + + inline unsigned vertex(double *x, double *y) + { + if (m_iterator >= m_total_vertices) { + *x = 0.0; + *y = 0.0; + return agg::path_cmd_stop; + } + + const size_t idx = m_iterator++; + + char *pair = (char *)PyArray_GETPTR2(m_vertices, idx, 0); + *x = *(double *)pair; + *y = *(double *)(pair + PyArray_STRIDE(m_vertices, 1)); + + if (m_codes != NULL) { + return (unsigned)(*(char *)PyArray_GETPTR1(m_codes, idx)); + } else { + return idx == 0 ? agg::path_cmd_move_to : agg::path_cmd_line_to; + } + } + + inline void rewind(unsigned path_id) + { + m_iterator = path_id; + } + + inline unsigned total_vertices() const + { + return m_total_vertices; + } + + inline bool should_simplify() const + { + return m_should_simplify; + } + + inline double simplify_threshold() const + { + return m_simplify_threshold; + } + + inline bool has_codes() const + { + return m_codes != NULL; + } + + inline void *get_id() + { + return (void *)m_vertices; + } +}; + +class PathGenerator +{ + PyObject *m_paths; + Py_ssize_t m_npaths; + + public: + typedef PathIterator path_iterator; + + PathGenerator() : m_paths(NULL), m_npaths(0) {} + + ~PathGenerator() + { + Py_XDECREF(m_paths); + } + + int set(PyObject *obj) + { + if (!PySequence_Check(obj)) { + return 0; + } + + Py_XDECREF(m_paths); + m_paths = obj; + Py_INCREF(m_paths); + + m_npaths = PySequence_Size(m_paths); + + return 1; + } + + Py_ssize_t num_paths() const + { + return m_npaths; + } + + Py_ssize_t size() const + { + return m_npaths; + } + + path_iterator operator()(size_t i) + { + path_iterator path; + PyObject *item; + + item = PySequence_GetItem(m_paths, i % m_npaths); + if (item == NULL) { + throw py::exception(); + } + if (!convert_path(item, &path)) { + Py_DECREF(item); + throw py::exception(); + } + Py_DECREF(item); + return path; + } +}; +} + +#endif diff --git a/contrib/python/matplotlib/py3/src/py_converters.cpp b/contrib/python/matplotlib/py3/src/py_converters.cpp new file mode 100644 index 0000000000..04382c5f94 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/py_converters.cpp @@ -0,0 +1,558 @@ +#define NO_IMPORT_ARRAY +#define PY_SSIZE_T_CLEAN +#include "py_converters.h" +#include "numpy_cpp.h" + +#include "agg_basics.h" +#include "agg_color_rgba.h" +#include "agg_math_stroke.h" + +extern "C" { + +static int convert_string_enum(PyObject *obj, const char *name, const char **names, int *values, int *result) +{ + PyObject *bytesobj; + char *str; + + if (obj == NULL || obj == Py_None) { + return 1; + } + + if (PyUnicode_Check(obj)) { + bytesobj = PyUnicode_AsASCIIString(obj); + if (bytesobj == NULL) { + return 0; + } + } else if (PyBytes_Check(obj)) { + Py_INCREF(obj); + bytesobj = obj; + } else { + PyErr_Format(PyExc_TypeError, "%s must be str or bytes", name); + return 0; + } + + str = PyBytes_AsString(bytesobj); + if (str == NULL) { + Py_DECREF(bytesobj); + return 0; + } + + for ( ; *names != NULL; names++, values++) { + if (strncmp(str, *names, 64) == 0) { + *result = *values; + Py_DECREF(bytesobj); + return 1; + } + } + + PyErr_Format(PyExc_ValueError, "invalid %s value", name); + Py_DECREF(bytesobj); + return 0; +} + +int convert_from_method(PyObject *obj, const char *name, converter func, void *p) +{ + PyObject *value; + + value = PyObject_CallMethod(obj, name, NULL); + if (value == NULL) { + if (!PyObject_HasAttrString(obj, name)) { + PyErr_Clear(); + return 1; + } + return 0; + } + + if (!func(value, p)) { + Py_DECREF(value); + return 0; + } + + Py_DECREF(value); + return 1; +} + +int convert_from_attr(PyObject *obj, const char *name, converter func, void *p) +{ + PyObject *value; + + value = PyObject_GetAttrString(obj, name); + if (value == NULL) { + if (!PyObject_HasAttrString(obj, name)) { + PyErr_Clear(); + return 1; + } + return 0; + } + + if (!func(value, p)) { + Py_DECREF(value); + return 0; + } + + Py_DECREF(value); + return 1; +} + +int convert_double(PyObject *obj, void *p) +{ + double *val = (double *)p; + + *val = PyFloat_AsDouble(obj); + if (PyErr_Occurred()) { + return 0; + } + + return 1; +} + +int convert_bool(PyObject *obj, void *p) +{ + bool *val = (bool *)p; + switch (PyObject_IsTrue(obj)) { + case 0: *val = false; break; + case 1: *val = true; break; + default: return 0; // errored. + } + return 1; +} + +int convert_cap(PyObject *capobj, void *capp) +{ + const char *names[] = {"butt", "round", "projecting", NULL}; + int values[] = {agg::butt_cap, agg::round_cap, agg::square_cap}; + int result = agg::butt_cap; + + if (!convert_string_enum(capobj, "capstyle", names, values, &result)) { + return 0; + } + + *(agg::line_cap_e *)capp = (agg::line_cap_e)result; + return 1; +} + +int convert_join(PyObject *joinobj, void *joinp) +{ + const char *names[] = {"miter", "round", "bevel", NULL}; + int values[] = {agg::miter_join_revert, agg::round_join, agg::bevel_join}; + int result = agg::miter_join_revert; + + if (!convert_string_enum(joinobj, "joinstyle", names, values, &result)) { + return 0; + } + + *(agg::line_join_e *)joinp = (agg::line_join_e)result; + return 1; +} + +int convert_rect(PyObject *rectobj, void *rectp) +{ + agg::rect_d *rect = (agg::rect_d *)rectp; + + if (rectobj == NULL || rectobj == Py_None) { + rect->x1 = 0.0; + rect->y1 = 0.0; + rect->x2 = 0.0; + rect->y2 = 0.0; + } else { + PyArrayObject *rect_arr = (PyArrayObject *)PyArray_ContiguousFromAny( + rectobj, NPY_DOUBLE, 1, 2); + if (rect_arr == NULL) { + return 0; + } + + if (PyArray_NDIM(rect_arr) == 2) { + if (PyArray_DIM(rect_arr, 0) != 2 || + PyArray_DIM(rect_arr, 1) != 2) { + PyErr_SetString(PyExc_ValueError, "Invalid bounding box"); + Py_DECREF(rect_arr); + return 0; + } + + } else { // PyArray_NDIM(rect_arr) == 1 + if (PyArray_DIM(rect_arr, 0) != 4) { + PyErr_SetString(PyExc_ValueError, "Invalid bounding box"); + Py_DECREF(rect_arr); + return 0; + } + } + + double *buff = (double *)PyArray_DATA(rect_arr); + rect->x1 = buff[0]; + rect->y1 = buff[1]; + rect->x2 = buff[2]; + rect->y2 = buff[3]; + + Py_DECREF(rect_arr); + } + return 1; +} + +int convert_rgba(PyObject *rgbaobj, void *rgbap) +{ + agg::rgba *rgba = (agg::rgba *)rgbap; + PyObject *rgbatuple = NULL; + int success = 1; + if (rgbaobj == NULL || rgbaobj == Py_None) { + rgba->r = 0.0; + rgba->g = 0.0; + rgba->b = 0.0; + rgba->a = 0.0; + } else { + if (!(rgbatuple = PySequence_Tuple(rgbaobj))) { + success = 0; + goto exit; + } + rgba->a = 1.0; + if (!PyArg_ParseTuple( + rgbatuple, "ddd|d:rgba", &(rgba->r), &(rgba->g), &(rgba->b), &(rgba->a))) { + success = 0; + goto exit; + } + } +exit: + Py_XDECREF(rgbatuple); + return success; +} + +int convert_dashes(PyObject *dashobj, void *dashesp) +{ + Dashes *dashes = (Dashes *)dashesp; + + double dash_offset = 0.0; + PyObject *dashes_seq = NULL; + + if (!PyArg_ParseTuple(dashobj, "dO:dashes", &dash_offset, &dashes_seq)) { + return 0; + } + + if (dashes_seq == Py_None) { + return 1; + } + + if (!PySequence_Check(dashes_seq)) { + PyErr_SetString(PyExc_TypeError, "Invalid dashes sequence"); + return 0; + } + + Py_ssize_t nentries = PySequence_Size(dashes_seq); + // If the dashpattern has odd length, iterate through it twice (in + // accordance with the pdf/ps/svg specs). + Py_ssize_t dash_pattern_length = (nentries % 2) ? 2 * nentries : nentries; + + for (Py_ssize_t i = 0; i < dash_pattern_length; ++i) { + PyObject *item; + double length; + double skip; + + item = PySequence_GetItem(dashes_seq, i % nentries); + if (item == NULL) { + return 0; + } + length = PyFloat_AsDouble(item); + if (PyErr_Occurred()) { + Py_DECREF(item); + return 0; + } + Py_DECREF(item); + + ++i; + + item = PySequence_GetItem(dashes_seq, i % nentries); + if (item == NULL) { + return 0; + } + skip = PyFloat_AsDouble(item); + if (PyErr_Occurred()) { + Py_DECREF(item); + return 0; + } + Py_DECREF(item); + + dashes->add_dash_pair(length, skip); + } + + dashes->set_dash_offset(dash_offset); + + return 1; +} + +int convert_dashes_vector(PyObject *obj, void *dashesp) +{ + DashesVector *dashes = (DashesVector *)dashesp; + + if (!PySequence_Check(obj)) { + return 0; + } + + Py_ssize_t n = PySequence_Size(obj); + + for (Py_ssize_t i = 0; i < n; ++i) { + PyObject *item; + Dashes subdashes; + + item = PySequence_GetItem(obj, i); + if (item == NULL) { + return 0; + } + + if (!convert_dashes(item, &subdashes)) { + Py_DECREF(item); + return 0; + } + Py_DECREF(item); + + dashes->push_back(subdashes); + } + + return 1; +} + +int convert_trans_affine(PyObject *obj, void *transp) +{ + agg::trans_affine *trans = (agg::trans_affine *)transp; + + /** If None assume identity transform. */ + if (obj == NULL || obj == Py_None) { + return 1; + } + + PyArrayObject *array = (PyArrayObject *)PyArray_ContiguousFromAny(obj, NPY_DOUBLE, 2, 2); + if (array == NULL) { + return 0; + } + + if (PyArray_DIM(array, 0) == 3 && PyArray_DIM(array, 1) == 3) { + double *buffer = (double *)PyArray_DATA(array); + trans->sx = buffer[0]; + trans->shx = buffer[1]; + trans->tx = buffer[2]; + + trans->shy = buffer[3]; + trans->sy = buffer[4]; + trans->ty = buffer[5]; + + Py_DECREF(array); + return 1; + } + + Py_DECREF(array); + PyErr_SetString(PyExc_ValueError, "Invalid affine transformation matrix"); + return 0; +} + +int convert_path(PyObject *obj, void *pathp) +{ + py::PathIterator *path = (py::PathIterator *)pathp; + + PyObject *vertices_obj = NULL; + PyObject *codes_obj = NULL; + PyObject *should_simplify_obj = NULL; + PyObject *simplify_threshold_obj = NULL; + bool should_simplify; + double simplify_threshold; + + int status = 0; + + if (obj == NULL || obj == Py_None) { + return 1; + } + + vertices_obj = PyObject_GetAttrString(obj, "vertices"); + if (vertices_obj == NULL) { + goto exit; + } + + codes_obj = PyObject_GetAttrString(obj, "codes"); + if (codes_obj == NULL) { + goto exit; + } + + should_simplify_obj = PyObject_GetAttrString(obj, "should_simplify"); + if (should_simplify_obj == NULL) { + goto exit; + } + switch (PyObject_IsTrue(should_simplify_obj)) { + case 0: should_simplify = 0; break; + case 1: should_simplify = 1; break; + default: goto exit; // errored. + } + + simplify_threshold_obj = PyObject_GetAttrString(obj, "simplify_threshold"); + if (simplify_threshold_obj == NULL) { + goto exit; + } + simplify_threshold = PyFloat_AsDouble(simplify_threshold_obj); + if (PyErr_Occurred()) { + goto exit; + } + + if (!path->set(vertices_obj, codes_obj, should_simplify, simplify_threshold)) { + goto exit; + } + + status = 1; + +exit: + Py_XDECREF(vertices_obj); + Py_XDECREF(codes_obj); + Py_XDECREF(should_simplify_obj); + Py_XDECREF(simplify_threshold_obj); + + return status; +} + +int convert_pathgen(PyObject *obj, void *pathgenp) +{ + py::PathGenerator *paths = (py::PathGenerator *)pathgenp; + if (!paths->set(obj)) { + PyErr_SetString(PyExc_TypeError, "Not an iterable of paths"); + return 0; + } + return 1; +} + +int convert_clippath(PyObject *clippath_tuple, void *clippathp) +{ + ClipPath *clippath = (ClipPath *)clippathp; + py::PathIterator path; + agg::trans_affine trans; + + if (clippath_tuple != NULL && clippath_tuple != Py_None) { + if (!PyArg_ParseTuple(clippath_tuple, + "O&O&:clippath", + &convert_path, + &clippath->path, + &convert_trans_affine, + &clippath->trans)) { + return 0; + } + } + + return 1; +} + +int convert_snap(PyObject *obj, void *snapp) +{ + e_snap_mode *snap = (e_snap_mode *)snapp; + if (obj == NULL || obj == Py_None) { + *snap = SNAP_AUTO; + } else { + switch (PyObject_IsTrue(obj)) { + case 0: *snap = SNAP_FALSE; break; + case 1: *snap = SNAP_TRUE; break; + default: return 0; // errored. + } + } + return 1; +} + +int convert_sketch_params(PyObject *obj, void *sketchp) +{ + SketchParams *sketch = (SketchParams *)sketchp; + + if (obj == NULL || obj == Py_None) { + sketch->scale = 0.0; + } else if (!PyArg_ParseTuple(obj, + "ddd:sketch_params", + &sketch->scale, + &sketch->length, + &sketch->randomness)) { + return 0; + } + + return 1; +} + +int convert_gcagg(PyObject *pygc, void *gcp) +{ + GCAgg *gc = (GCAgg *)gcp; + + if (!(convert_from_attr(pygc, "_linewidth", &convert_double, &gc->linewidth) && + convert_from_attr(pygc, "_alpha", &convert_double, &gc->alpha) && + convert_from_attr(pygc, "_forced_alpha", &convert_bool, &gc->forced_alpha) && + convert_from_attr(pygc, "_rgb", &convert_rgba, &gc->color) && + convert_from_attr(pygc, "_antialiased", &convert_bool, &gc->isaa) && + convert_from_attr(pygc, "_capstyle", &convert_cap, &gc->cap) && + convert_from_attr(pygc, "_joinstyle", &convert_join, &gc->join) && + convert_from_method(pygc, "get_dashes", &convert_dashes, &gc->dashes) && + convert_from_attr(pygc, "_cliprect", &convert_rect, &gc->cliprect) && + convert_from_method(pygc, "get_clip_path", &convert_clippath, &gc->clippath) && + convert_from_method(pygc, "get_snap", &convert_snap, &gc->snap_mode) && + convert_from_method(pygc, "get_hatch_path", &convert_path, &gc->hatchpath) && + convert_from_method(pygc, "get_hatch_color", &convert_rgba, &gc->hatch_color) && + convert_from_method(pygc, "get_hatch_linewidth", &convert_double, &gc->hatch_linewidth) && + convert_from_method(pygc, "get_sketch_params", &convert_sketch_params, &gc->sketch))) { + return 0; + } + + return 1; +} + +int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba) +{ + if (!convert_rgba(color, rgba)) { + return 0; + } + + if (color != NULL && color != Py_None) { + if (gc.forced_alpha || PySequence_Size(color) == 3) { + rgba->a = gc.alpha; + } + } + + return 1; +} + +int convert_points(PyObject *obj, void *pointsp) +{ + numpy::array_view<double, 2> *points = (numpy::array_view<double, 2> *)pointsp; + if (obj == NULL || obj == Py_None) { + return 1; + } + if (!points->set(obj) + || (points->size() && !check_trailing_shape(*points, "points", 2))) { + return 0; + } + return 1; +} + +int convert_transforms(PyObject *obj, void *transp) +{ + numpy::array_view<double, 3> *trans = (numpy::array_view<double, 3> *)transp; + if (obj == NULL || obj == Py_None) { + return 1; + } + if (!trans->set(obj) + || (trans->size() && !check_trailing_shape(*trans, "transforms", 3, 3))) { + return 0; + } + return 1; +} + +int convert_bboxes(PyObject *obj, void *bboxp) +{ + numpy::array_view<double, 3> *bbox = (numpy::array_view<double, 3> *)bboxp; + if (obj == NULL || obj == Py_None) { + return 1; + } + if (!bbox->set(obj) + || (bbox->size() && !check_trailing_shape(*bbox, "bbox array", 2, 2))) { + return 0; + } + return 1; +} + +int convert_colors(PyObject *obj, void *colorsp) +{ + numpy::array_view<double, 2> *colors = (numpy::array_view<double, 2> *)colorsp; + if (obj == NULL || obj == Py_None) { + return 1; + } + if (!colors->set(obj) + || (colors->size() && !check_trailing_shape(*colors, "colors", 4))) { + return 0; + } + return 1; +} +} diff --git a/contrib/python/matplotlib/py3/src/py_converters.h b/contrib/python/matplotlib/py3/src/py_converters.h new file mode 100644 index 0000000000..2c9dc6d1b8 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/py_converters.h @@ -0,0 +1,48 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef MPL_PY_CONVERTERS_H +#define MPL_PY_CONVERTERS_H + +/*************************************************************************** + * This module contains a number of conversion functions from Python types + * to C++ types. Most of them meet the Python "converter" signature: + * + * typedef int (*converter)(PyObject *, void *); + * + * and thus can be passed as conversion functions to PyArg_ParseTuple + * and friends. + */ + +#include <Python.h> +#include "_backend_agg_basic_types.h" + +extern "C" { +typedef int (*converter)(PyObject *, void *); + +int convert_from_attr(PyObject *obj, const char *name, converter func, void *p); +int convert_from_method(PyObject *obj, const char *name, converter func, void *p); + +int convert_double(PyObject *obj, void *p); +int convert_bool(PyObject *obj, void *p); +int convert_cap(PyObject *capobj, void *capp); +int convert_join(PyObject *joinobj, void *joinp); +int convert_rect(PyObject *rectobj, void *rectp); +int convert_rgba(PyObject *rgbaocj, void *rgbap); +int convert_dashes(PyObject *dashobj, void *gcp); +int convert_dashes_vector(PyObject *obj, void *dashesp); +int convert_trans_affine(PyObject *obj, void *transp); +int convert_path(PyObject *obj, void *pathp); +int convert_pathgen(PyObject *obj, void *pathgenp); +int convert_clippath(PyObject *clippath_tuple, void *clippathp); +int convert_snap(PyObject *obj, void *snapp); +int convert_sketch_params(PyObject *obj, void *sketchp); +int convert_gcagg(PyObject *pygc, void *gcp); +int convert_points(PyObject *pygc, void *pointsp); +int convert_transforms(PyObject *pygc, void *transp); +int convert_bboxes(PyObject *pygc, void *bboxp); +int convert_colors(PyObject *pygc, void *colorsp); + +int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba); +} + +#endif diff --git a/contrib/python/matplotlib/py3/src/py_exceptions.h b/contrib/python/matplotlib/py3/src/py_exceptions.h new file mode 100644 index 0000000000..c4accf2634 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/py_exceptions.h @@ -0,0 +1,72 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef MPL_PY_EXCEPTIONS_H +#define MPL_PY_EXCEPTIONS_H + +#include <exception> +#include <stdexcept> + +namespace py +{ +class exception : public std::exception +{ + public: + const char *what() const throw() + { + return "python error has been set"; + } +}; +} + +#define CALL_CPP_FULL(name, a, cleanup, errorcode) \ + try \ + { \ + a; \ + } \ + catch (const py::exception &) \ + { \ + { \ + cleanup; \ + } \ + return (errorcode); \ + } \ + catch (const std::bad_alloc &) \ + { \ + PyErr_Format(PyExc_MemoryError, "In %s: Out of memory", (name)); \ + { \ + cleanup; \ + } \ + return (errorcode); \ + } \ + catch (const std::overflow_error &e) \ + { \ + PyErr_Format(PyExc_OverflowError, "In %s: %s", (name), e.what()); \ + { \ + cleanup; \ + } \ + return (errorcode); \ + } \ + catch (const std::runtime_error &e) \ + { \ + PyErr_Format(PyExc_RuntimeError, "In %s: %s", (name), e.what()); \ + { \ + cleanup; \ + } \ + return (errorcode); \ + } \ + catch (...) \ + { \ + PyErr_Format(PyExc_RuntimeError, "Unknown exception in %s", (name)); \ + { \ + cleanup; \ + } \ + return (errorcode); \ + } + +#define CALL_CPP_CLEANUP(name, a, cleanup) CALL_CPP_FULL(name, a, cleanup, 0) + +#define CALL_CPP(name, a) CALL_CPP_FULL(name, a, , 0) + +#define CALL_CPP_INIT(name, a) CALL_CPP_FULL(name, a, , -1) + +#endif diff --git a/contrib/python/matplotlib/py3/src/tri/_tri.cpp b/contrib/python/matplotlib/py3/src/tri/_tri.cpp new file mode 100644 index 0000000000..2674a3140b --- /dev/null +++ b/contrib/python/matplotlib/py3/src/tri/_tri.cpp @@ -0,0 +1,2074 @@ +/* This file contains liberal use of asserts to assist code development and + * debugging. Standard matplotlib builds disable asserts so they cause no + * performance reduction. To enable the asserts, you need to undefine the + * NDEBUG macro, which is achieved by adding the following + * undef_macros=['NDEBUG'] + * to the appropriate make_extension call in setupext.py, and then rebuilding. + */ +#include "../mplutils.h" +#include "_tri.h" + +#include <algorithm> +#include <random> +#include <set> + + +TriEdge::TriEdge() + : tri(-1), edge(-1) +{} + +TriEdge::TriEdge(int tri_, int edge_) + : tri(tri_), edge(edge_) +{} + +bool TriEdge::operator<(const TriEdge& other) const +{ + if (tri != other.tri) + return tri < other.tri; + else + return edge < other.edge; +} + +bool TriEdge::operator==(const TriEdge& other) const +{ + return tri == other.tri && edge == other.edge; +} + +bool TriEdge::operator!=(const TriEdge& other) const +{ + return !operator==(other); +} + +std::ostream& operator<<(std::ostream& os, const TriEdge& tri_edge) +{ + return os << tri_edge.tri << ' ' << tri_edge.edge; +} + + + +XY::XY() +{} + +XY::XY(const double& x_, const double& y_) + : x(x_), y(y_) +{} + +double XY::angle() const +{ + return atan2(y, x); +} + +double XY::cross_z(const XY& other) const +{ + return x*other.y - y*other.x; +} + +bool XY::is_right_of(const XY& other) const +{ + if (x == other.x) + return y > other.y; + else + return x > other.x; +} + +bool XY::operator==(const XY& other) const +{ + return x == other.x && y == other.y; +} + +bool XY::operator!=(const XY& other) const +{ + return x != other.x || y != other.y; +} + +XY XY::operator*(const double& multiplier) const +{ + return XY(x*multiplier, y*multiplier); +} + +const XY& XY::operator+=(const XY& other) +{ + x += other.x; + y += other.y; + return *this; +} + +const XY& XY::operator-=(const XY& other) +{ + x -= other.x; + y -= other.y; + return *this; +} + +XY XY::operator+(const XY& other) const +{ + return XY(x + other.x, y + other.y); +} + +XY XY::operator-(const XY& other) const +{ + return XY(x - other.x, y - other.y); +} + +std::ostream& operator<<(std::ostream& os, const XY& xy) +{ + return os << '(' << xy.x << ' ' << xy.y << ')'; +} + + + +XYZ::XYZ(const double& x_, const double& y_, const double& z_) + : x(x_), y(y_), z(z_) +{} + +XYZ XYZ::cross(const XYZ& other) const +{ + return XYZ(y*other.z - z*other.y, + z*other.x - x*other.z, + x*other.y - y*other.x); +} + +double XYZ::dot(const XYZ& other) const +{ + return x*other.x + y*other.y + z*other.z; +} + +XYZ XYZ::operator-(const XYZ& other) const +{ + return XYZ(x - other.x, y - other.y, z - other.z); +} + +std::ostream& operator<<(std::ostream& os, const XYZ& xyz) +{ + return os << '(' << xyz.x << ' ' << xyz.y << ' ' << xyz.z << ')'; +} + + + +BoundingBox::BoundingBox() + : empty(true), lower(0.0, 0.0), upper(0.0, 0.0) +{} + +void BoundingBox::add(const XY& point) +{ + if (empty) { + empty = false; + lower = upper = point; + } else { + if (point.x < lower.x) lower.x = point.x; + else if (point.x > upper.x) upper.x = point.x; + + if (point.y < lower.y) lower.y = point.y; + else if (point.y > upper.y) upper.y = point.y; + } +} + +void BoundingBox::expand(const XY& delta) +{ + if (!empty) { + lower -= delta; + upper += delta; + } +} + + + +ContourLine::ContourLine() + : std::vector<XY>() +{} + +void ContourLine::push_back(const XY& point) +{ + if (empty() || point != back()) + std::vector<XY>::push_back(point); +} + +void ContourLine::write() const +{ + std::cout << "ContourLine of " << size() << " points:"; + for (const_iterator it = begin(); it != end(); ++it) + std::cout << ' ' << *it; + std::cout << std::endl; +} + + + +void write_contour(const Contour& contour) +{ + std::cout << "Contour of " << contour.size() << " lines." << std::endl; + for (Contour::const_iterator it = contour.begin(); it != contour.end(); ++it) + it->write(); +} + + + +Triangulation::Triangulation(const CoordinateArray& x, + const CoordinateArray& y, + const TriangleArray& triangles, + const MaskArray& mask, + const EdgeArray& edges, + const NeighborArray& neighbors, + bool correct_triangle_orientations) + : _x(x), + _y(y), + _triangles(triangles), + _mask(mask), + _edges(edges), + _neighbors(neighbors) +{ + if (_x.ndim() != 1 || _y.ndim() != 1 || _x.shape(0) != _y.shape(0)) + throw std::invalid_argument("x and y must be 1D arrays of the same length"); + + if (_triangles.ndim() != 2 || _triangles.shape(1) != 3) + throw std::invalid_argument("triangles must be a 2D array of shape (?,3)"); + + // Optional mask. + if (_mask.size() > 0 && + (_mask.ndim() != 1 || _mask.shape(0) != _triangles.shape(0))) + throw std::invalid_argument( + "mask must be a 1D array with the same length as the triangles array"); + + // Optional edges. + if (_edges.size() > 0 && + (_edges.ndim() != 2 || _edges.shape(1) != 2)) + throw std::invalid_argument("edges must be a 2D array with shape (?,2)"); + + // Optional neighbors. + if (_neighbors.size() > 0 && + (_neighbors.ndim() != 2 || _neighbors.shape() != _triangles.shape())) + throw std::invalid_argument( + "neighbors must be a 2D array with the same shape as the triangles array"); + + if (correct_triangle_orientations) + correct_triangles(); +} + +void Triangulation::calculate_boundaries() +{ + get_neighbors(); // Ensure _neighbors has been created. + + // Create set of all boundary TriEdges, which are those which do not + // have a neighbor triangle. + typedef std::set<TriEdge> BoundaryEdges; + BoundaryEdges boundary_edges; + for (int tri = 0; tri < get_ntri(); ++tri) { + if (!is_masked(tri)) { + for (int edge = 0; edge < 3; ++edge) { + if (get_neighbor(tri, edge) == -1) { + boundary_edges.insert(TriEdge(tri, edge)); + } + } + } + } + + // Take any boundary edge and follow the boundary until return to start + // point, removing edges from boundary_edges as they are used. At the same + // time, initialise the _tri_edge_to_boundary_map. + while (!boundary_edges.empty()) { + // Start of new boundary. + BoundaryEdges::iterator it = boundary_edges.begin(); + int tri = it->tri; + int edge = it->edge; + _boundaries.push_back(Boundary()); + Boundary& boundary = _boundaries.back(); + + while (true) { + boundary.push_back(TriEdge(tri, edge)); + boundary_edges.erase(it); + _tri_edge_to_boundary_map[TriEdge(tri, edge)] = + BoundaryEdge(_boundaries.size()-1, boundary.size()-1); + + // Move to next edge of current triangle. + edge = (edge+1) % 3; + + // Find start point index of boundary edge. + int point = get_triangle_point(tri, edge); + + // Find next TriEdge by traversing neighbors until find one + // without a neighbor. + while (get_neighbor(tri, edge) != -1) { + tri = get_neighbor(tri, edge); + edge = get_edge_in_triangle(tri, point); + } + + if (TriEdge(tri,edge) == boundary.front()) + break; // Reached beginning of this boundary, so finished it. + else + it = boundary_edges.find(TriEdge(tri, edge)); + } + } +} + +void Triangulation::calculate_edges() +{ + assert(!has_edges() && "Expected empty edges array"); + + // Create set of all edges, storing them with start point index less than + // end point index. + typedef std::set<Edge> EdgeSet; + EdgeSet edge_set; + for (int tri = 0; tri < get_ntri(); ++tri) { + if (!is_masked(tri)) { + for (int edge = 0; edge < 3; edge++) { + int start = get_triangle_point(tri, edge); + int end = get_triangle_point(tri, (edge+1)%3); + edge_set.insert(start > end ? Edge(start,end) : Edge(end,start)); + } + } + } + + // Convert to python _edges array. + py::ssize_t dims[2] = {static_cast<py::ssize_t>(edge_set.size()), 2}; + _edges = EdgeArray(dims); + auto edges = _edges.mutable_data(); + + int i = 0; + for (EdgeSet::const_iterator it = edge_set.begin(); it != edge_set.end(); ++it) { + edges[i++] = it->start; + edges[i++] = it->end; + } +} + +void Triangulation::calculate_neighbors() +{ + assert(!has_neighbors() && "Expected empty neighbors array"); + + // Create _neighbors array with shape (ntri,3) and initialise all to -1. + py::ssize_t dims[2] = {get_ntri(), 3}; + _neighbors = NeighborArray(dims); + auto* neighbors = _neighbors.mutable_data(); + + int tri, edge; + std::fill(neighbors, neighbors+3*get_ntri(), -1); + + // For each triangle edge (start to end point), find corresponding neighbor + // edge from end to start point. Do this by traversing all edges and + // storing them in a map from edge to TriEdge. If corresponding neighbor + // edge is already in the map, don't need to store new edge as neighbor + // already found. + typedef std::map<Edge, TriEdge> EdgeToTriEdgeMap; + EdgeToTriEdgeMap edge_to_tri_edge_map; + for (tri = 0; tri < get_ntri(); ++tri) { + if (!is_masked(tri)) { + for (edge = 0; edge < 3; ++edge) { + int start = get_triangle_point(tri, edge); + int end = get_triangle_point(tri, (edge+1)%3); + EdgeToTriEdgeMap::iterator it = + edge_to_tri_edge_map.find(Edge(end,start)); + if (it == edge_to_tri_edge_map.end()) { + // No neighbor edge exists in the edge_to_tri_edge_map, so + // add this edge to it. + edge_to_tri_edge_map[Edge(start,end)] = TriEdge(tri,edge); + } else { + // Neighbor edge found, set the two elements of _neighbors + // and remove edge from edge_to_tri_edge_map. + neighbors[3*tri + edge] = it->second.tri; + neighbors[3*it->second.tri + it->second.edge] = tri; + edge_to_tri_edge_map.erase(it); + } + } + } + } + + // Note that remaining edges in the edge_to_tri_edge_map correspond to + // boundary edges, but the boundaries are calculated separately elsewhere. +} + +Triangulation::TwoCoordinateArray Triangulation::calculate_plane_coefficients( + const CoordinateArray& z) +{ + if (z.ndim() != 1 || z.shape(0) != _x.shape(0)) + throw std::invalid_argument( + "z must be a 1D array with the same length as the triangulation x and y arrays"); + + int dims[2] = {get_ntri(), 3}; + Triangulation::TwoCoordinateArray planes_array(dims); + auto planes = planes_array.mutable_unchecked<2>(); + auto triangles = _triangles.unchecked<2>(); + auto x = _x.unchecked<1>(); + auto y = _y.unchecked<1>(); + auto z_ptr = z.unchecked<1>(); + + int point; + for (int tri = 0; tri < get_ntri(); ++tri) { + if (is_masked(tri)) { + planes(tri, 0) = 0.0; + planes(tri, 1) = 0.0; + planes(tri, 2) = 0.0; + } + else { + // Equation of plane for all points r on plane is r.normal = p + // where normal is vector normal to the plane, and p is a + // constant. Rewrite as + // r_x*normal_x + r_y*normal_y + r_z*normal_z = p + // and rearrange to give + // r_z = (-normal_x/normal_z)*r_x + (-normal_y/normal_z)*r_y + + // p/normal_z + point = triangles(tri, 0); + XYZ point0(x(point), y(point), z_ptr(point)); + point = triangles(tri, 1); + XYZ side01 = XYZ(x(point), y(point), z_ptr(point)) - point0; + point = triangles(tri, 2); + XYZ side02 = XYZ(x(point), y(point), z_ptr(point)) - point0; + + XYZ normal = side01.cross(side02); + + if (normal.z == 0.0) { + // Normal is in x-y plane which means triangle consists of + // colinear points. To avoid dividing by zero, we use the + // Moore-Penrose pseudo-inverse. + double sum2 = (side01.x*side01.x + side01.y*side01.y + + side02.x*side02.x + side02.y*side02.y); + double a = (side01.x*side01.z + side02.x*side02.z) / sum2; + double b = (side01.y*side01.z + side02.y*side02.z) / sum2; + planes(tri, 0) = a; + planes(tri, 1) = b; + planes(tri, 2) = point0.z - a*point0.x - b*point0.y; + } + else { + planes(tri, 0) = -normal.x / normal.z; // x + planes(tri, 1) = -normal.y / normal.z; // y + planes(tri, 2) = normal.dot(point0) / normal.z; // constant + } + } + } + + return planes_array; +} + +void Triangulation::correct_triangles() +{ + auto triangles = _triangles.mutable_data(); + auto neighbors = _neighbors.mutable_data(); + + for (int tri = 0; tri < get_ntri(); ++tri) { + XY point0 = get_point_coords(triangles[3*tri]); + XY point1 = get_point_coords(triangles[3*tri+1]); + XY point2 = get_point_coords(triangles[3*tri+2]); + if ( (point1 - point0).cross_z(point2 - point0) < 0.0) { + // Triangle points are clockwise, so change them to anticlockwise. + std::swap(triangles[3*tri+1], triangles[3*tri+2]); + if (has_neighbors()) + std::swap(neighbors[3*tri+1], neighbors[3*tri+2]); + } + } +} + +const Triangulation::Boundaries& Triangulation::get_boundaries() const +{ + if (_boundaries.empty()) + const_cast<Triangulation*>(this)->calculate_boundaries(); + return _boundaries; +} + +void Triangulation::get_boundary_edge(const TriEdge& triEdge, + int& boundary, + int& edge) const +{ + get_boundaries(); // Ensure _tri_edge_to_boundary_map has been created. + TriEdgeToBoundaryMap::const_iterator it = + _tri_edge_to_boundary_map.find(triEdge); + assert(it != _tri_edge_to_boundary_map.end() && + "TriEdge is not on a boundary"); + boundary = it->second.boundary; + edge = it->second.edge; +} + +int Triangulation::get_edge_in_triangle(int tri, int point) const +{ + assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds"); + assert(point >= 0 && point < get_npoints() && "Point index out of bounds."); + + auto triangles = _triangles.data(); + + for (int edge = 0; edge < 3; ++edge) { + if (triangles[3*tri + edge] == point) + return edge; + } + return -1; // point is not in triangle. +} + +Triangulation::EdgeArray& Triangulation::get_edges() +{ + if (!has_edges()) + calculate_edges(); + return _edges; +} + +int Triangulation::get_neighbor(int tri, int edge) const +{ + assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds"); + assert(edge >= 0 && edge < 3 && "Edge index out of bounds"); + if (!has_neighbors()) + const_cast<Triangulation&>(*this).calculate_neighbors(); + return _neighbors.data()[3*tri + edge]; +} + +TriEdge Triangulation::get_neighbor_edge(int tri, int edge) const +{ + int neighbor_tri = get_neighbor(tri, edge); + if (neighbor_tri == -1) + return TriEdge(-1,-1); + else + return TriEdge(neighbor_tri, + get_edge_in_triangle(neighbor_tri, + get_triangle_point(tri, + (edge+1)%3))); +} + +Triangulation::NeighborArray& Triangulation::get_neighbors() +{ + if (!has_neighbors()) + calculate_neighbors(); + return _neighbors; +} + +int Triangulation::get_npoints() const +{ + return _x.shape(0); +} + +int Triangulation::get_ntri() const +{ + return _triangles.shape(0); +} + +XY Triangulation::get_point_coords(int point) const +{ + assert(point >= 0 && point < get_npoints() && "Point index out of bounds."); + return XY(_x.data()[point], _y.data()[point]); +} + +int Triangulation::get_triangle_point(int tri, int edge) const +{ + assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds"); + assert(edge >= 0 && edge < 3 && "Edge index out of bounds"); + return _triangles.data()[3*tri + edge]; +} + +int Triangulation::get_triangle_point(const TriEdge& tri_edge) const +{ + return get_triangle_point(tri_edge.tri, tri_edge.edge); +} + +bool Triangulation::has_edges() const +{ + return _edges.size() > 0; +} + +bool Triangulation::has_mask() const +{ + return _mask.size() > 0; +} + +bool Triangulation::has_neighbors() const +{ + return _neighbors.size() > 0; +} + +bool Triangulation::is_masked(int tri) const +{ + assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds."); + return has_mask() && _mask.data()[tri]; +} + +void Triangulation::set_mask(const MaskArray& mask) +{ + if (mask.size() > 0 && + (mask.ndim() != 1 || mask.shape(0) != _triangles.shape(0))) + throw std::invalid_argument( + "mask must be a 1D array with the same length as the triangles array"); + + _mask = mask; + + // Clear derived fields so they are recalculated when needed. + _edges = EdgeArray(); + _neighbors = NeighborArray(); + _boundaries.clear(); +} + +void Triangulation::write_boundaries() const +{ + const Boundaries& bs = get_boundaries(); + std::cout << "Number of boundaries: " << bs.size() << std::endl; + for (Boundaries::const_iterator it = bs.begin(); it != bs.end(); ++it) { + const Boundary& b = *it; + std::cout << " Boundary of " << b.size() << " points: "; + for (Boundary::const_iterator itb = b.begin(); itb != b.end(); ++itb) { + std::cout << *itb << ", "; + } + std::cout << std::endl; + } +} + + + +TriContourGenerator::TriContourGenerator(Triangulation& triangulation, + const CoordinateArray& z) + : _triangulation(triangulation), + _z(z), + _interior_visited(2*_triangulation.get_ntri()), + _boundaries_visited(0), + _boundaries_used(0) +{ + if (_z.ndim() != 1 || _z.shape(0) != _triangulation.get_npoints()) + throw std::invalid_argument( + "z must be a 1D array with the same length as the x and y arrays"); +} + +void TriContourGenerator::clear_visited_flags(bool include_boundaries) +{ + // Clear _interiorVisited. + std::fill(_interior_visited.begin(), _interior_visited.end(), false); + + if (include_boundaries) { + if (_boundaries_visited.empty()) { + const Boundaries& boundaries = get_boundaries(); + + // Initialise _boundaries_visited. + _boundaries_visited.reserve(boundaries.size()); + for (Boundaries::const_iterator it = boundaries.begin(); + it != boundaries.end(); ++it) + _boundaries_visited.push_back(BoundaryVisited(it->size())); + + // Initialise _boundaries_used. + _boundaries_used = BoundariesUsed(boundaries.size()); + } + + // Clear _boundaries_visited. + for (BoundariesVisited::iterator it = _boundaries_visited.begin(); + it != _boundaries_visited.end(); ++it) + std::fill(it->begin(), it->end(), false); + + // Clear _boundaries_used. + std::fill(_boundaries_used.begin(), _boundaries_used.end(), false); + } +} + +py::tuple TriContourGenerator::contour_line_to_segs_and_kinds(const Contour& contour) +{ + // Convert all of the lines generated by a call to create_contour() into + // their Python equivalents for return to the calling function. + // A line is either a closed line loop (in which case the last point is + // identical to the first) or an open line strip. Two NumPy arrays are + // created for each line: + // vertices is a double array of shape (npoints, 2) containing the (x, y) + // coordinates of the points in the line + // codes is a uint8 array of shape (npoints,) containing the 'kind codes' + // which are defined in the Path class + // and they are appended to the Python lists vertices_list and codes_list + // respectively for return to the Python calling function. + + py::list vertices_list(contour.size()); + py::list codes_list(contour.size()); + + for (Contour::size_type i = 0; i < contour.size(); ++i) { + const ContourLine& contour_line = contour[i]; + py::ssize_t npoints = static_cast<py::ssize_t>(contour_line.size()); + + py::ssize_t segs_dims[2] = {npoints, 2}; + CoordinateArray segs(segs_dims); + double* segs_ptr = segs.mutable_data(); + + py::ssize_t codes_dims[1] = {npoints}; + CodeArray codes(codes_dims); + unsigned char* codes_ptr = codes.mutable_data(); + + for (ContourLine::const_iterator it = contour_line.begin(); + it != contour_line.end(); ++it) { + *segs_ptr++ = it->x; + *segs_ptr++ = it->y; + *codes_ptr++ = (it == contour_line.begin() ? MOVETO : LINETO); + } + + // Closed line loop has identical first and last (x, y) points. + if (contour_line.size() > 1 && + contour_line.front() == contour_line.back()) + *(codes_ptr-1) = CLOSEPOLY; + + vertices_list[i] = segs; + codes_list[i] = codes; + } + + return py::make_tuple(vertices_list, codes_list); +} + +py::tuple TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour) +{ + // Convert all of the polygons generated by a call to + // create_filled_contour() into their Python equivalents for return to the + // calling function. All of the polygons' points and kinds codes are + // combined into single NumPy arrays for each; this avoids having + // to determine which polygons are holes as this will be determined by the + // renderer. If there are ntotal points in all of the polygons, the two + // NumPy arrays created are: + // vertices is a double array of shape (ntotal, 2) containing the (x, y) + // coordinates of the points in the polygons + // codes is a uint8 array of shape (ntotal,) containing the 'kind codes' + // which are defined in the Path class + // and they are returned in the Python lists vertices_list and codes_list + // respectively. + + Contour::const_iterator line; + ContourLine::const_iterator point; + + // Find total number of points in all contour lines. + py::ssize_t n_points = 0; + for (line = contour.begin(); line != contour.end(); ++line) + n_points += static_cast<py::ssize_t>(line->size()); + + // Create segs array for point coordinates. + py::ssize_t segs_dims[2] = {n_points, 2}; + TwoCoordinateArray segs(segs_dims); + double* segs_ptr = segs.mutable_data(); + + // Create kinds array for code types. + py::ssize_t codes_dims[1] = {n_points}; + CodeArray codes(codes_dims); + unsigned char* codes_ptr = codes.mutable_data(); + + for (line = contour.begin(); line != contour.end(); ++line) { + for (point = line->begin(); point != line->end(); point++) { + *segs_ptr++ = point->x; + *segs_ptr++ = point->y; + *codes_ptr++ = (point == line->begin() ? MOVETO : LINETO); + } + + if (line->size() > 1) + *(codes_ptr-1) = CLOSEPOLY; + } + + py::list vertices_list(1); + vertices_list[0] = segs; + + py::list codes_list(1); + codes_list[0] = codes; + + return py::make_tuple(vertices_list, codes_list); +} + +py::tuple TriContourGenerator::create_contour(const double& level) +{ + clear_visited_flags(false); + Contour contour; + + find_boundary_lines(contour, level); + find_interior_lines(contour, level, false, false); + + return contour_line_to_segs_and_kinds(contour); +} + +py::tuple TriContourGenerator::create_filled_contour(const double& lower_level, + const double& upper_level) +{ + if (lower_level >= upper_level) + throw std::invalid_argument("filled contour levels must be increasing"); + + clear_visited_flags(true); + Contour contour; + + find_boundary_lines_filled(contour, lower_level, upper_level); + find_interior_lines(contour, lower_level, false, true); + find_interior_lines(contour, upper_level, true, true); + + return contour_to_segs_and_kinds(contour); +} + +XY TriContourGenerator::edge_interp(int tri, int edge, const double& level) +{ + return interp(_triangulation.get_triangle_point(tri, edge), + _triangulation.get_triangle_point(tri, (edge+1)%3), + level); +} + +void TriContourGenerator::find_boundary_lines(Contour& contour, + const double& level) +{ + // Traverse boundaries to find starting points for all contour lines that + // intersect the boundaries. For each starting point found, follow the + // line to its end before continuing. + const Triangulation& triang = _triangulation; + const Boundaries& boundaries = get_boundaries(); + for (Boundaries::const_iterator it = boundaries.begin(); + it != boundaries.end(); ++it) { + const Boundary& boundary = *it; + bool startAbove, endAbove = false; + for (Boundary::const_iterator itb = boundary.begin(); + itb != boundary.end(); ++itb) { + if (itb == boundary.begin()) + startAbove = get_z(triang.get_triangle_point(*itb)) >= level; + else + startAbove = endAbove; + endAbove = get_z(triang.get_triangle_point(itb->tri, + (itb->edge+1)%3)) >= level; + if (startAbove && !endAbove) { + // This boundary edge is the start point for a contour line, + // so follow the line. + contour.push_back(ContourLine()); + ContourLine& contour_line = contour.back(); + TriEdge tri_edge = *itb; + follow_interior(contour_line, tri_edge, true, level, false); + } + } + } +} + +void TriContourGenerator::find_boundary_lines_filled(Contour& contour, + const double& lower_level, + const double& upper_level) +{ + // Traverse boundaries to find starting points for all contour lines that + // intersect the boundaries. For each starting point found, follow the + // line to its end before continuing. + const Triangulation& triang = _triangulation; + const Boundaries& boundaries = get_boundaries(); + for (Boundaries::size_type i = 0; i < boundaries.size(); ++i) { + const Boundary& boundary = boundaries[i]; + for (Boundary::size_type j = 0; j < boundary.size(); ++j) { + if (!_boundaries_visited[i][j]) { + // z values of start and end of this boundary edge. + double z_start = get_z(triang.get_triangle_point(boundary[j])); + double z_end = get_z(triang.get_triangle_point( + boundary[j].tri, (boundary[j].edge+1)%3)); + + // Does this boundary edge's z increase through upper level + // and/or decrease through lower level? + bool incr_upper = (z_start < upper_level && z_end >= upper_level); + bool decr_lower = (z_start >= lower_level && z_end < lower_level); + + if (decr_lower || incr_upper) { + // Start point for contour line, so follow it. + contour.push_back(ContourLine()); + ContourLine& contour_line = contour.back(); + TriEdge start_tri_edge = boundary[j]; + TriEdge tri_edge = start_tri_edge; + + // Traverse interior and boundaries until return to start. + bool on_upper = incr_upper; + do { + follow_interior(contour_line, tri_edge, true, + on_upper ? upper_level : lower_level, on_upper); + on_upper = follow_boundary(contour_line, tri_edge, + lower_level, upper_level, on_upper); + } while (tri_edge != start_tri_edge); + + // Close polygon. + contour_line.push_back(contour_line.front()); + } + } + } + } + + // Add full boundaries that lie between the lower and upper levels. These + // are boundaries that have not been touched by an internal contour line + // which are stored in _boundaries_used. + for (Boundaries::size_type i = 0; i < boundaries.size(); ++i) { + if (!_boundaries_used[i]) { + const Boundary& boundary = boundaries[i]; + double z = get_z(triang.get_triangle_point(boundary[0])); + if (z >= lower_level && z < upper_level) { + contour.push_back(ContourLine()); + ContourLine& contour_line = contour.back(); + for (Boundary::size_type j = 0; j < boundary.size(); ++j) + contour_line.push_back(triang.get_point_coords( + triang.get_triangle_point(boundary[j]))); + + // Close polygon. + contour_line.push_back(contour_line.front()); + } + } + } +} + +void TriContourGenerator::find_interior_lines(Contour& contour, + const double& level, + bool on_upper, + bool filled) +{ + const Triangulation& triang = _triangulation; + int ntri = triang.get_ntri(); + for (int tri = 0; tri < ntri; ++tri) { + int visited_index = (on_upper ? tri+ntri : tri); + + if (_interior_visited[visited_index] || triang.is_masked(tri)) + continue; // Triangle has already been visited or is masked. + + _interior_visited[visited_index] = true; + + // Determine edge via which to leave this triangle. + int edge = get_exit_edge(tri, level, on_upper); + assert(edge >= -1 && edge < 3 && "Invalid exit edge"); + if (edge == -1) + continue; // Contour does not pass through this triangle. + + // Found start of new contour line loop. + contour.push_back(ContourLine()); + ContourLine& contour_line = contour.back(); + TriEdge tri_edge = triang.get_neighbor_edge(tri, edge); + follow_interior(contour_line, tri_edge, false, level, on_upper); + + // Close line loop + contour_line.push_back(contour_line.front()); + } +} + +bool TriContourGenerator::follow_boundary(ContourLine& contour_line, + TriEdge& tri_edge, + const double& lower_level, + const double& upper_level, + bool on_upper) +{ + const Triangulation& triang = _triangulation; + const Boundaries& boundaries = get_boundaries(); + + // Have TriEdge to start at, need equivalent boundary edge. + int boundary, edge; + triang.get_boundary_edge(tri_edge, boundary, edge); + _boundaries_used[boundary] = true; + + bool stop = false; + bool first_edge = true; + double z_start, z_end = 0; + while (!stop) + { + assert(!_boundaries_visited[boundary][edge] && "Boundary already visited"); + _boundaries_visited[boundary][edge] = true; + + // z values of start and end points of boundary edge. + if (first_edge) + z_start = get_z(triang.get_triangle_point(tri_edge)); + else + z_start = z_end; + z_end = get_z(triang.get_triangle_point(tri_edge.tri, + (tri_edge.edge+1)%3)); + + if (z_end > z_start) { // z increasing. + if (!(!on_upper && first_edge) && + z_end >= lower_level && z_start < lower_level) { + stop = true; + on_upper = false; + } else if (z_end >= upper_level && z_start < upper_level) { + stop = true; + on_upper = true; + } + } else { // z decreasing. + if (!(on_upper && first_edge) && + z_start >= upper_level && z_end < upper_level) { + stop = true; + on_upper = true; + } else if (z_start >= lower_level && z_end < lower_level) { + stop = true; + on_upper = false; + } + } + + first_edge = false; + + if (!stop) { + // Move to next boundary edge, adding point to contour line. + edge = (edge+1) % (int)boundaries[boundary].size(); + tri_edge = boundaries[boundary][edge]; + contour_line.push_back(triang.get_point_coords( + triang.get_triangle_point(tri_edge))); + } + } + + return on_upper; +} + +void TriContourGenerator::follow_interior(ContourLine& contour_line, + TriEdge& tri_edge, + bool end_on_boundary, + const double& level, + bool on_upper) +{ + int& tri = tri_edge.tri; + int& edge = tri_edge.edge; + + // Initial point. + contour_line.push_back(edge_interp(tri, edge, level)); + + while (true) { + int visited_index = tri; + if (on_upper) + visited_index += _triangulation.get_ntri(); + + // Check for end not on boundary. + if (!end_on_boundary && _interior_visited[visited_index]) + break; // Reached start point, so return. + + // Determine edge by which to leave this triangle. + edge = get_exit_edge(tri, level, on_upper); + assert(edge >= 0 && edge < 3 && "Invalid exit edge"); + + _interior_visited[visited_index] = true; + + // Append new point to point set. + assert(edge >= 0 && edge < 3 && "Invalid triangle edge"); + contour_line.push_back(edge_interp(tri, edge, level)); + + // Move to next triangle. + TriEdge next_tri_edge = _triangulation.get_neighbor_edge(tri,edge); + + // Check if ending on a boundary. + if (end_on_boundary && next_tri_edge.tri == -1) + break; + + tri_edge = next_tri_edge; + assert(tri_edge.tri != -1 && "Invalid triangle for internal loop"); + } +} + +const TriContourGenerator::Boundaries& TriContourGenerator::get_boundaries() const +{ + return _triangulation.get_boundaries(); +} + +int TriContourGenerator::get_exit_edge(int tri, + const double& level, + bool on_upper) const +{ + assert(tri >= 0 && tri < _triangulation.get_ntri() && + "Triangle index out of bounds."); + + unsigned int config = + (get_z(_triangulation.get_triangle_point(tri, 0)) >= level) | + (get_z(_triangulation.get_triangle_point(tri, 1)) >= level) << 1 | + (get_z(_triangulation.get_triangle_point(tri, 2)) >= level) << 2; + + if (on_upper) config = 7-config; + + switch (config) { + case 0: return -1; + case 1: return 2; + case 2: return 0; + case 3: return 2; + case 4: return 1; + case 5: return 1; + case 6: return 0; + case 7: return -1; + default: assert(0 && "Invalid config value"); return -1; + } +} + +const double& TriContourGenerator::get_z(int point) const +{ + assert(point >= 0 && point < _triangulation.get_npoints() && + "Point index out of bounds."); + return _z.data()[point]; +} + +XY TriContourGenerator::interp(int point1, + int point2, + const double& level) const +{ + assert(point1 >= 0 && point1 < _triangulation.get_npoints() && + "Point index 1 out of bounds."); + assert(point2 >= 0 && point2 < _triangulation.get_npoints() && + "Point index 2 out of bounds."); + assert(point1 != point2 && "Identical points"); + double fraction = (get_z(point2) - level) / (get_z(point2) - get_z(point1)); + return _triangulation.get_point_coords(point1)*fraction + + _triangulation.get_point_coords(point2)*(1.0 - fraction); +} + + + +TrapezoidMapTriFinder::TrapezoidMapTriFinder(Triangulation& triangulation) + : _triangulation(triangulation), + _points(0), + _tree(0) +{} + +TrapezoidMapTriFinder::~TrapezoidMapTriFinder() +{ + clear(); +} + +bool +TrapezoidMapTriFinder::add_edge_to_tree(const Edge& edge) +{ + std::vector<Trapezoid*> trapezoids; + if (!find_trapezoids_intersecting_edge(edge, trapezoids)) + return false; + assert(!trapezoids.empty() && "No trapezoids intersect edge"); + + const Point* p = edge.left; + const Point* q = edge.right; + Trapezoid* left_old = 0; // old trapezoid to the left. + Trapezoid* left_below = 0; // below trapezoid to the left. + Trapezoid* left_above = 0; // above trapezoid to the left. + + // Iterate through trapezoids intersecting edge from left to right. + // Replace each old trapezoid with 2+ new trapezoids, and replace its + // corresponding nodes in the search tree with new nodes. + size_t ntraps = trapezoids.size(); + for (size_t i = 0; i < ntraps; ++i) { + Trapezoid* old = trapezoids[i]; // old trapezoid to replace. + bool start_trap = (i == 0); + bool end_trap = (i == ntraps-1); + bool have_left = (start_trap && edge.left != old->left); + bool have_right = (end_trap && edge.right != old->right); + + // Old trapezoid is replaced by up to 4 new trapezoids: left is to the + // left of the start point p, below/above are below/above the edge + // inserted, and right is to the right of the end point q. + Trapezoid* left = 0; + Trapezoid* below = 0; + Trapezoid* above = 0; + Trapezoid* right = 0; + + // There are 4 different cases here depending on whether the old + // trapezoid in question is the start and/or end trapezoid of those + // that intersect the edge inserted. There is some code duplication + // here but it is much easier to understand this way rather than + // interleave the 4 different cases with many more if-statements. + if (start_trap && end_trap) { + // Edge intersects a single trapezoid. + if (have_left) + left = new Trapezoid(old->left, p, old->below, old->above); + below = new Trapezoid(p, q, old->below, edge); + above = new Trapezoid(p, q, edge, old->above); + if (have_right) + right = new Trapezoid(q, old->right, old->below, old->above); + + // Set pairs of trapezoid neighbours. + if (have_left) { + left->set_lower_left(old->lower_left); + left->set_upper_left(old->upper_left); + left->set_lower_right(below); + left->set_upper_right(above); + } + else { + below->set_lower_left(old->lower_left); + above->set_upper_left(old->upper_left); + } + + if (have_right) { + right->set_lower_right(old->lower_right); + right->set_upper_right(old->upper_right); + below->set_lower_right(right); + above->set_upper_right(right); + } + else { + below->set_lower_right(old->lower_right); + above->set_upper_right(old->upper_right); + } + } + else if (start_trap) { + // Old trapezoid is the first of 2+ trapezoids that the edge + // intersects. + if (have_left) + left = new Trapezoid(old->left, p, old->below, old->above); + below = new Trapezoid(p, old->right, old->below, edge); + above = new Trapezoid(p, old->right, edge, old->above); + + // Set pairs of trapezoid neighbours. + if (have_left) { + left->set_lower_left(old->lower_left); + left->set_upper_left(old->upper_left); + left->set_lower_right(below); + left->set_upper_right(above); + } + else { + below->set_lower_left(old->lower_left); + above->set_upper_left(old->upper_left); + } + + below->set_lower_right(old->lower_right); + above->set_upper_right(old->upper_right); + } + else if (end_trap) { + // Old trapezoid is the last of 2+ trapezoids that the edge + // intersects. + if (left_below->below == old->below) { + below = left_below; + below->right = q; + } + else + below = new Trapezoid(old->left, q, old->below, edge); + + if (left_above->above == old->above) { + above = left_above; + above->right = q; + } + else + above = new Trapezoid(old->left, q, edge, old->above); + + if (have_right) + right = new Trapezoid(q, old->right, old->below, old->above); + + // Set pairs of trapezoid neighbours. + if (have_right) { + right->set_lower_right(old->lower_right); + right->set_upper_right(old->upper_right); + below->set_lower_right(right); + above->set_upper_right(right); + } + else { + below->set_lower_right(old->lower_right); + above->set_upper_right(old->upper_right); + } + + // Connect to new trapezoids replacing prevOld. + if (below != left_below) { + below->set_upper_left(left_below); + if (old->lower_left == left_old) + below->set_lower_left(left_below); + else + below->set_lower_left(old->lower_left); + } + + if (above != left_above) { + above->set_lower_left(left_above); + if (old->upper_left == left_old) + above->set_upper_left(left_above); + else + above->set_upper_left(old->upper_left); + } + } + else { // Middle trapezoid. + // Old trapezoid is neither the first nor last of the 3+ trapezoids + // that the edge intersects. + if (left_below->below == old->below) { + below = left_below; + below->right = old->right; + } + else + below = new Trapezoid(old->left, old->right, old->below, edge); + + if (left_above->above == old->above) { + above = left_above; + above->right = old->right; + } + else + above = new Trapezoid(old->left, old->right, edge, old->above); + + // Connect to new trapezoids replacing prevOld. + if (below != left_below) { // below is new. + below->set_upper_left(left_below); + if (old->lower_left == left_old) + below->set_lower_left(left_below); + else + below->set_lower_left(old->lower_left); + } + + if (above != left_above) { // above is new. + above->set_lower_left(left_above); + if (old->upper_left == left_old) + above->set_upper_left(left_above); + else + above->set_upper_left(old->upper_left); + } + + below->set_lower_right(old->lower_right); + above->set_upper_right(old->upper_right); + } + + // Create new nodes to add to search tree. Below and above trapezoids + // may already have owning trapezoid nodes, in which case reuse them. + Node* new_top_node = new Node( + &edge, + below == left_below ? below->trapezoid_node : new Node(below), + above == left_above ? above->trapezoid_node : new Node(above)); + if (have_right) + new_top_node = new Node(q, new_top_node, new Node(right)); + if (have_left) + new_top_node = new Node(p, new Node(left), new_top_node); + + // Insert new_top_node in correct position or positions in search tree. + Node* old_node = old->trapezoid_node; + if (old_node == _tree) + _tree = new_top_node; + else + old_node->replace_with(new_top_node); + + // old_node has been removed from all of its parents and is no longer + // needed. + assert(old_node->has_no_parents() && "Node should have no parents"); + delete old_node; + + // Clearing up. + if (!end_trap) { + // Prepare for next loop. + left_old = old; + left_above = above; + left_below = below; + } + } + + return true; +} + +void +TrapezoidMapTriFinder::clear() +{ + delete [] _points; + _points = 0; + + _edges.clear(); + + delete _tree; + _tree = 0; +} + +TrapezoidMapTriFinder::TriIndexArray +TrapezoidMapTriFinder::find_many(const CoordinateArray& x, + const CoordinateArray& y) +{ + if (x.ndim() != 1 || x.shape(0) != y.shape(0)) + throw std::invalid_argument( + "x and y must be array-like with same shape"); + + // Create integer array to return. + auto n = x.shape(0); + TriIndexArray tri_indices_array(n); + auto tri_indices = tri_indices_array.mutable_unchecked<1>(); + auto x_data = x.data(); + auto y_data = y.data(); + + // Fill returned array. + for (py::ssize_t i = 0; i < n; ++i) + tri_indices(i) = find_one(XY(x_data[i], y_data[i])); + + return tri_indices_array; +} + +int +TrapezoidMapTriFinder::find_one(const XY& xy) +{ + const Node* node = _tree->search(xy); + assert(node != 0 && "Search tree for point returned null node"); + return node->get_tri(); +} + +bool +TrapezoidMapTriFinder::find_trapezoids_intersecting_edge( + const Edge& edge, + std::vector<Trapezoid*>& trapezoids) +{ + // This is the FollowSegment algorithm of de Berg et al, with some extra + // checks to deal with simple colinear (i.e. invalid) triangles. + trapezoids.clear(); + Trapezoid* trapezoid = _tree->search(edge); + if (trapezoid == 0) { + assert(trapezoid != 0 && "search(edge) returns null trapezoid"); + return false; + } + + trapezoids.push_back(trapezoid); + while (edge.right->is_right_of(*trapezoid->right)) { + int orient = edge.get_point_orientation(*trapezoid->right); + if (orient == 0) { + if (edge.point_below == trapezoid->right) + orient = +1; + else if (edge.point_above == trapezoid->right) + orient = -1; + else { + assert(0 && "Unable to deal with point on edge"); + return false; + } + } + + if (orient == -1) + trapezoid = trapezoid->lower_right; + else if (orient == +1) + trapezoid = trapezoid->upper_right; + + if (trapezoid == 0) { + assert(0 && "Expected trapezoid neighbor"); + return false; + } + trapezoids.push_back(trapezoid); + } + + return true; +} + +py::list +TrapezoidMapTriFinder::get_tree_stats() +{ + NodeStats stats; + _tree->get_stats(0, stats); + + py::list ret(7); + ret[0] = stats.node_count; + ret[1] = stats.unique_nodes.size(), + ret[2] = stats.trapezoid_count, + ret[3] = stats.unique_trapezoid_nodes.size(), + ret[4] = stats.max_parent_count, + ret[5] = stats.max_depth, + ret[6] = stats.sum_trapezoid_depth / stats.trapezoid_count; + return ret; +} + +void +TrapezoidMapTriFinder::initialize() +{ + clear(); + const Triangulation& triang = _triangulation; + + // Set up points array, which contains all of the points in the + // triangulation plus the 4 corners of the enclosing rectangle. + int npoints = triang.get_npoints(); + _points = new Point[npoints + 4]; + BoundingBox bbox; + for (int i = 0; i < npoints; ++i) { + XY xy = triang.get_point_coords(i); + // Avoid problems with -0.0 values different from 0.0 + if (xy.x == -0.0) + xy.x = 0.0; + if (xy.y == -0.0) + xy.y = 0.0; + _points[i] = Point(xy); + bbox.add(xy); + } + + // Last 4 points are corner points of enclosing rectangle. Enclosing + // rectangle made slightly larger in case corner points are already in the + // triangulation. + if (bbox.empty) { + bbox.add(XY(0.0, 0.0)); + bbox.add(XY(1.0, 1.0)); + } + else { + const double small = 0.1; // Any value > 0.0 + bbox.expand( (bbox.upper - bbox.lower)*small ); + } + _points[npoints ] = Point(bbox.lower); // SW point. + _points[npoints+1] = Point(bbox.upper.x, bbox.lower.y); // SE point. + _points[npoints+2] = Point(bbox.lower.x, bbox.upper.y); // NW point. + _points[npoints+3] = Point(bbox.upper); // NE point. + + // Set up edges array. + // First the bottom and top edges of the enclosing rectangle. + _edges.push_back(Edge(&_points[npoints], &_points[npoints+1],-1,-1,0,0)); + _edges.push_back(Edge(&_points[npoints+2],&_points[npoints+3],-1,-1,0,0)); + + // Add all edges in the triangulation that point to the right. Do not + // explicitly include edges that point to the left as the neighboring + // triangle will supply that, unless there is no such neighbor. + int ntri = triang.get_ntri(); + for (int tri = 0; tri < ntri; ++tri) { + if (!triang.is_masked(tri)) { + for (int edge = 0; edge < 3; ++edge) { + Point* start = _points + triang.get_triangle_point(tri,edge); + Point* end = _points + + triang.get_triangle_point(tri,(edge+1)%3); + Point* other = _points + + triang.get_triangle_point(tri,(edge+2)%3); + TriEdge neighbor = triang.get_neighbor_edge(tri,edge); + if (end->is_right_of(*start)) { + const Point* neighbor_point_below = (neighbor.tri == -1) ? + 0 : _points + triang.get_triangle_point( + neighbor.tri, (neighbor.edge+2)%3); + _edges.push_back(Edge(start, end, neighbor.tri, tri, + neighbor_point_below, other)); + } + else if (neighbor.tri == -1) + _edges.push_back(Edge(end, start, tri, -1, other, 0)); + + // Set triangle associated with start point if not already set. + if (start->tri == -1) + start->tri = tri; + } + } + } + + // Initial trapezoid is enclosing rectangle. + _tree = new Node(new Trapezoid(&_points[npoints], &_points[npoints+1], + _edges[0], _edges[1])); + _tree->assert_valid(false); + + // Randomly shuffle all edges other than first 2. + std::mt19937 rng(1234); + std::shuffle(_edges.begin()+2, _edges.end(), rng); + + // Add edges, one at a time, to tree. + size_t nedges = _edges.size(); + for (size_t index = 2; index < nedges; ++index) { + if (!add_edge_to_tree(_edges[index])) + throw std::runtime_error("Triangulation is invalid"); + _tree->assert_valid(index == nedges-1); + } +} + +void +TrapezoidMapTriFinder::print_tree() +{ + assert(_tree != 0 && "Null Node tree"); + _tree->print(); +} + +TrapezoidMapTriFinder::Edge::Edge(const Point* left_, + const Point* right_, + int triangle_below_, + int triangle_above_, + const Point* point_below_, + const Point* point_above_) + : left(left_), + right(right_), + triangle_below(triangle_below_), + triangle_above(triangle_above_), + point_below(point_below_), + point_above(point_above_) +{ + assert(left != 0 && "Null left point"); + assert(right != 0 && "Null right point"); + assert(right->is_right_of(*left) && "Incorrect point order"); + assert(triangle_below >= -1 && "Invalid triangle below index"); + assert(triangle_above >= -1 && "Invalid triangle above index"); +} + +int +TrapezoidMapTriFinder::Edge::get_point_orientation(const XY& xy) const +{ + double cross_z = (xy - *left).cross_z(*right - *left); + return (cross_z > 0.0) ? +1 : ((cross_z < 0.0) ? -1 : 0); +} + +double +TrapezoidMapTriFinder::Edge::get_slope() const +{ + // Divide by zero is acceptable here. + XY diff = *right - *left; + return diff.y / diff.x; +} + +double +TrapezoidMapTriFinder::Edge::get_y_at_x(const double& x) const +{ + if (left->x == right->x) { + // If edge is vertical, return lowest y from left point. + assert(x == left->x && "x outside of edge"); + return left->y; + } + else { + // Equation of line: left + lambda*(right - left) = xy. + // i.e. left.x + lambda(right.x - left.x) = x and similar for y. + double lambda = (x - left->x) / (right->x - left->x); + assert(lambda >= 0 && lambda <= 1.0 && "Lambda out of bounds"); + return left->y + lambda*(right->y - left->y); + } +} + +bool +TrapezoidMapTriFinder::Edge::has_point(const Point* point) const +{ + assert(point != 0 && "Null point"); + return (left == point || right == point); +} + +bool +TrapezoidMapTriFinder::Edge::operator==(const Edge& other) const +{ + return this == &other; +} + +void +TrapezoidMapTriFinder::Edge::print_debug() const +{ + std::cout << "Edge " << *this << " tri_below=" << triangle_below + << " tri_above=" << triangle_above << std::endl; +} + +TrapezoidMapTriFinder::Node::Node(const Point* point, Node* left, Node* right) + : _type(Type_XNode) +{ + assert(point != 0 && "Invalid point"); + assert(left != 0 && "Invalid left node"); + assert(right != 0 && "Invalid right node"); + _union.xnode.point = point; + _union.xnode.left = left; + _union.xnode.right = right; + left->add_parent(this); + right->add_parent(this); +} + +TrapezoidMapTriFinder::Node::Node(const Edge* edge, Node* below, Node* above) + : _type(Type_YNode) +{ + assert(edge != 0 && "Invalid edge"); + assert(below != 0 && "Invalid below node"); + assert(above != 0 && "Invalid above node"); + _union.ynode.edge = edge; + _union.ynode.below = below; + _union.ynode.above = above; + below->add_parent(this); + above->add_parent(this); +} + +TrapezoidMapTriFinder::Node::Node(Trapezoid* trapezoid) + : _type(Type_TrapezoidNode) +{ + assert(trapezoid != 0 && "Null Trapezoid"); + _union.trapezoid = trapezoid; + trapezoid->trapezoid_node = this; +} + +TrapezoidMapTriFinder::Node::~Node() +{ + switch (_type) { + case Type_XNode: + if (_union.xnode.left->remove_parent(this)) + delete _union.xnode.left; + if (_union.xnode.right->remove_parent(this)) + delete _union.xnode.right; + break; + case Type_YNode: + if (_union.ynode.below->remove_parent(this)) + delete _union.ynode.below; + if (_union.ynode.above->remove_parent(this)) + delete _union.ynode.above; + break; + case Type_TrapezoidNode: + delete _union.trapezoid; + break; + } +} + +void +TrapezoidMapTriFinder::Node::add_parent(Node* parent) +{ + assert(parent != 0 && "Null parent"); + assert(parent != this && "Cannot be parent of self"); + assert(!has_parent(parent) && "Parent already in collection"); + _parents.push_back(parent); +} + +void +TrapezoidMapTriFinder::Node::assert_valid(bool tree_complete) const +{ +#ifndef NDEBUG + // Check parents. + for (Parents::const_iterator it = _parents.begin(); + it != _parents.end(); ++it) { + Node* parent = *it; + assert(parent != this && "Cannot be parent of self"); + assert(parent->has_child(this) && "Parent missing child"); + } + + // Check children, and recurse. + switch (_type) { + case Type_XNode: + assert(_union.xnode.left != 0 && "Null left child"); + assert(_union.xnode.left->has_parent(this) && "Incorrect parent"); + assert(_union.xnode.right != 0 && "Null right child"); + assert(_union.xnode.right->has_parent(this) && "Incorrect parent"); + _union.xnode.left->assert_valid(tree_complete); + _union.xnode.right->assert_valid(tree_complete); + break; + case Type_YNode: + assert(_union.ynode.below != 0 && "Null below child"); + assert(_union.ynode.below->has_parent(this) && "Incorrect parent"); + assert(_union.ynode.above != 0 && "Null above child"); + assert(_union.ynode.above->has_parent(this) && "Incorrect parent"); + _union.ynode.below->assert_valid(tree_complete); + _union.ynode.above->assert_valid(tree_complete); + break; + case Type_TrapezoidNode: + assert(_union.trapezoid != 0 && "Null trapezoid"); + assert(_union.trapezoid->trapezoid_node == this && + "Incorrect trapezoid node"); + _union.trapezoid->assert_valid(tree_complete); + break; + } +#endif +} + +void +TrapezoidMapTriFinder::Node::get_stats(int depth, + NodeStats& stats) const +{ + stats.node_count++; + if (depth > stats.max_depth) + stats.max_depth = depth; + bool new_node = stats.unique_nodes.insert(this).second; + if (new_node) + stats.max_parent_count = std::max(stats.max_parent_count, + static_cast<long>(_parents.size())); + + switch (_type) { + case Type_XNode: + _union.xnode.left->get_stats(depth+1, stats); + _union.xnode.right->get_stats(depth+1, stats); + break; + case Type_YNode: + _union.ynode.below->get_stats(depth+1, stats); + _union.ynode.above->get_stats(depth+1, stats); + break; + default: // Type_TrapezoidNode: + stats.unique_trapezoid_nodes.insert(this); + stats.trapezoid_count++; + stats.sum_trapezoid_depth += depth; + break; + } +} + +int +TrapezoidMapTriFinder::Node::get_tri() const +{ + switch (_type) { + case Type_XNode: + return _union.xnode.point->tri; + case Type_YNode: + if (_union.ynode.edge->triangle_above != -1) + return _union.ynode.edge->triangle_above; + else + return _union.ynode.edge->triangle_below; + default: // Type_TrapezoidNode: + assert(_union.trapezoid->below.triangle_above == + _union.trapezoid->above.triangle_below && + "Inconsistent triangle indices from trapezoid edges"); + return _union.trapezoid->below.triangle_above; + } +} + +bool +TrapezoidMapTriFinder::Node::has_child(const Node* child) const +{ + assert(child != 0 && "Null child node"); + switch (_type) { + case Type_XNode: + return (_union.xnode.left == child || _union.xnode.right == child); + case Type_YNode: + return (_union.ynode.below == child || + _union.ynode.above == child); + default: // Type_TrapezoidNode: + return false; + } +} + +bool +TrapezoidMapTriFinder::Node::has_no_parents() const +{ + return _parents.empty(); +} + +bool +TrapezoidMapTriFinder::Node::has_parent(const Node* parent) const +{ + return (std::find(_parents.begin(), _parents.end(), parent) != + _parents.end()); +} + +void +TrapezoidMapTriFinder::Node::print(int depth /* = 0 */) const +{ + for (int i = 0; i < depth; ++i) std::cout << " "; + switch (_type) { + case Type_XNode: + std::cout << "XNode " << *_union.xnode.point << std::endl; + _union.xnode.left->print(depth + 1); + _union.xnode.right->print(depth + 1); + break; + case Type_YNode: + std::cout << "YNode " << *_union.ynode.edge << std::endl; + _union.ynode.below->print(depth + 1); + _union.ynode.above->print(depth + 1); + break; + case Type_TrapezoidNode: + std::cout << "Trapezoid ll=" + << _union.trapezoid->get_lower_left_point() << " lr=" + << _union.trapezoid->get_lower_right_point() << " ul=" + << _union.trapezoid->get_upper_left_point() << " ur=" + << _union.trapezoid->get_upper_right_point() << std::endl; + break; + } +} + +bool +TrapezoidMapTriFinder::Node::remove_parent(Node* parent) +{ + assert(parent != 0 && "Null parent"); + assert(parent != this && "Cannot be parent of self"); + Parents::iterator it = std::find(_parents.begin(), _parents.end(), parent); + assert(it != _parents.end() && "Parent not in collection"); + _parents.erase(it); + return _parents.empty(); +} + +void +TrapezoidMapTriFinder::Node::replace_child(Node* old_child, Node* new_child) +{ + switch (_type) { + case Type_XNode: + assert((_union.xnode.left == old_child || + _union.xnode.right == old_child) && "Not a child Node"); + assert(new_child != 0 && "Null child node"); + if (_union.xnode.left == old_child) + _union.xnode.left = new_child; + else + _union.xnode.right = new_child; + break; + case Type_YNode: + assert((_union.ynode.below == old_child || + _union.ynode.above == old_child) && "Not a child node"); + assert(new_child != 0 && "Null child node"); + if (_union.ynode.below == old_child) + _union.ynode.below = new_child; + else + _union.ynode.above = new_child; + break; + case Type_TrapezoidNode: + assert(0 && "Invalid type for this operation"); + break; + } + old_child->remove_parent(this); + new_child->add_parent(this); +} + +void +TrapezoidMapTriFinder::Node::replace_with(Node* new_node) +{ + assert(new_node != 0 && "Null replacement node"); + // Replace child of each parent with new_node. As each has parent has its + // child replaced it is removed from the _parents collection. + while (!_parents.empty()) + _parents.front()->replace_child(this, new_node); +} + +const TrapezoidMapTriFinder::Node* +TrapezoidMapTriFinder::Node::search(const XY& xy) +{ + switch (_type) { + case Type_XNode: + if (xy == *_union.xnode.point) + return this; + else if (xy.is_right_of(*_union.xnode.point)) + return _union.xnode.right->search(xy); + else + return _union.xnode.left->search(xy); + case Type_YNode: { + int orient = _union.ynode.edge->get_point_orientation(xy); + if (orient == 0) + return this; + else if (orient < 0) + return _union.ynode.above->search(xy); + else + return _union.ynode.below->search(xy); + } + default: // Type_TrapezoidNode: + return this; + } +} + +TrapezoidMapTriFinder::Trapezoid* +TrapezoidMapTriFinder::Node::search(const Edge& edge) +{ + switch (_type) { + case Type_XNode: + if (edge.left == _union.xnode.point) + return _union.xnode.right->search(edge); + else { + if (edge.left->is_right_of(*_union.xnode.point)) + return _union.xnode.right->search(edge); + else + return _union.xnode.left->search(edge); + } + case Type_YNode: + if (edge.left == _union.ynode.edge->left) { + // Coinciding left edge points. + if (edge.get_slope() == _union.ynode.edge->get_slope()) { + if (_union.ynode.edge->triangle_above == + edge.triangle_below) + return _union.ynode.above->search(edge); + else if (_union.ynode.edge->triangle_below == + edge.triangle_above) + return _union.ynode.below->search(edge); + else { + assert(0 && + "Invalid triangulation, common left points"); + return 0; + } + } + if (edge.get_slope() > _union.ynode.edge->get_slope()) + return _union.ynode.above->search(edge); + else + return _union.ynode.below->search(edge); + } + else if (edge.right == _union.ynode.edge->right) { + // Coinciding right edge points. + if (edge.get_slope() == _union.ynode.edge->get_slope()) { + if (_union.ynode.edge->triangle_above == + edge.triangle_below) + return _union.ynode.above->search(edge); + else if (_union.ynode.edge->triangle_below == + edge.triangle_above) + return _union.ynode.below->search(edge); + else { + assert(0 && + "Invalid triangulation, common right points"); + return 0; + } + } + if (edge.get_slope() > _union.ynode.edge->get_slope()) + return _union.ynode.below->search(edge); + else + return _union.ynode.above->search(edge); + } + else { + int orient = + _union.ynode.edge->get_point_orientation(*edge.left); + if (orient == 0) { + // edge.left lies on _union.ynode.edge + if (_union.ynode.edge->point_above != 0 && + edge.has_point(_union.ynode.edge->point_above)) + orient = -1; + else if (_union.ynode.edge->point_below != 0 && + edge.has_point(_union.ynode.edge->point_below)) + orient = +1; + else { + assert(0 && "Invalid triangulation, point on edge"); + return 0; + } + } + if (orient < 0) + return _union.ynode.above->search(edge); + else + return _union.ynode.below->search(edge); + } + default: // Type_TrapezoidNode: + return _union.trapezoid; + } +} + +TrapezoidMapTriFinder::Trapezoid::Trapezoid(const Point* left_, + const Point* right_, + const Edge& below_, + const Edge& above_) + : left(left_), right(right_), below(below_), above(above_), + lower_left(0), lower_right(0), upper_left(0), upper_right(0), + trapezoid_node(0) +{ + assert(left != 0 && "Null left point"); + assert(right != 0 && "Null right point"); + assert(right->is_right_of(*left) && "Incorrect point order"); +} + +void +TrapezoidMapTriFinder::Trapezoid::assert_valid(bool tree_complete) const +{ +#ifndef NDEBUG + assert(left != 0 && "Null left point"); + assert(right != 0 && "Null right point"); + + if (lower_left != 0) { + assert(lower_left->below == below && + lower_left->lower_right == this && + "Incorrect lower_left trapezoid"); + assert(get_lower_left_point() == lower_left->get_lower_right_point() && + "Incorrect lower left point"); + } + + if (lower_right != 0) { + assert(lower_right->below == below && + lower_right->lower_left == this && + "Incorrect lower_right trapezoid"); + assert(get_lower_right_point() == lower_right->get_lower_left_point() && + "Incorrect lower right point"); + } + + if (upper_left != 0) { + assert(upper_left->above == above && + upper_left->upper_right == this && + "Incorrect upper_left trapezoid"); + assert(get_upper_left_point() == upper_left->get_upper_right_point() && + "Incorrect upper left point"); + } + + if (upper_right != 0) { + assert(upper_right->above == above && + upper_right->upper_left == this && + "Incorrect upper_right trapezoid"); + assert(get_upper_right_point() == upper_right->get_upper_left_point() && + "Incorrect upper right point"); + } + + assert(trapezoid_node != 0 && "Null trapezoid_node"); + + if (tree_complete) { + assert(below.triangle_above == above.triangle_below && + "Inconsistent triangle indices from trapezoid edges"); + } +#endif +} + +XY +TrapezoidMapTriFinder::Trapezoid::get_lower_left_point() const +{ + double x = left->x; + return XY(x, below.get_y_at_x(x)); +} + +XY +TrapezoidMapTriFinder::Trapezoid::get_lower_right_point() const +{ + double x = right->x; + return XY(x, below.get_y_at_x(x)); +} + +XY +TrapezoidMapTriFinder::Trapezoid::get_upper_left_point() const +{ + double x = left->x; + return XY(x, above.get_y_at_x(x)); +} + +XY +TrapezoidMapTriFinder::Trapezoid::get_upper_right_point() const +{ + double x = right->x; + return XY(x, above.get_y_at_x(x)); +} + +void +TrapezoidMapTriFinder::Trapezoid::print_debug() const +{ + std::cout << "Trapezoid " << this + << " left=" << *left + << " right=" << *right + << " below=" << below + << " above=" << above + << " ll=" << lower_left + << " lr=" << lower_right + << " ul=" << upper_left + << " ur=" << upper_right + << " node=" << trapezoid_node + << " llp=" << get_lower_left_point() + << " lrp=" << get_lower_right_point() + << " ulp=" << get_upper_left_point() + << " urp=" << get_upper_right_point() << std::endl; +} + +void +TrapezoidMapTriFinder::Trapezoid::set_lower_left(Trapezoid* lower_left_) +{ + lower_left = lower_left_; + if (lower_left != 0) + lower_left->lower_right = this; +} + +void +TrapezoidMapTriFinder::Trapezoid::set_lower_right(Trapezoid* lower_right_) +{ + lower_right = lower_right_; + if (lower_right != 0) + lower_right->lower_left = this; +} + +void +TrapezoidMapTriFinder::Trapezoid::set_upper_left(Trapezoid* upper_left_) +{ + upper_left = upper_left_; + if (upper_left != 0) + upper_left->upper_right = this; +} + +void +TrapezoidMapTriFinder::Trapezoid::set_upper_right(Trapezoid* upper_right_) +{ + upper_right = upper_right_; + if (upper_right != 0) + upper_right->upper_left = this; +} diff --git a/contrib/python/matplotlib/py3/src/tri/_tri.h b/contrib/python/matplotlib/py3/src/tri/_tri.h new file mode 100644 index 0000000000..c176b4c0e8 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/tri/_tri.h @@ -0,0 +1,799 @@ +/* + * Unstructured triangular grid functions, particularly contouring. + * + * There are two main classes: Triangulation and TriContourGenerator. + * + * Triangulation + * ------------- + * Triangulation is an unstructured triangular grid with npoints and ntri + * triangles. It consists of point x and y coordinates, and information about + * the triangulation stored in an integer array of shape (ntri,3) called + * triangles. Each triangle is represented by three point indices (in the + * range 0 to npoints-1) that comprise the triangle, ordered anticlockwise. + * There is an optional mask of length ntri which can be used to mask out + * triangles and has the same result as removing those triangles from the + * 'triangles' array. + * + * A particular edge of a triangulation is termed a TriEdge, which is a + * triangle index and an edge index in the range 0 to 2. TriEdge(tri,edge) + * refers to the edge that starts at point index triangles(tri,edge) and ends + * at point index triangles(tri,(edge+1)%3). + * + * Various derived fields are calculated when they are first needed. The + * triangle connectivity is stored in a neighbors array of shape (ntri,3) such + * that neighbors(tri,edge) is the index of the triangle that adjoins the + * TriEdge(tri,edge), or -1 if there is no such neighbor. + * + * A triangulation has one or more boundaries, each of which is a 1D array of + * the TriEdges that comprise the boundary, in order following the boundary + * with non-masked triangles on the left. + * + * TriContourGenerator + * ------------------- + * A TriContourGenerator generates contours for a particular Triangulation. + * The process followed is different for non-filled and filled contours, with + * one and two contour levels respectively. In both cases boundary contour + * lines are found first, then interior lines. + * + * Boundary lines start and end on a boundary. They are found by traversing + * the triangulation boundary edges until a suitable start point is found, and + * then the contour line is followed across the interior of the triangulation + * until it ends on another boundary edge. For a non-filled contour this + * completes a line, whereas a filled contour continues by following the + * boundary around until either another boundary start point is found or the + * start of the contour line is reached. Filled contour generation stores + * boolean flags to indicate which boundary edges have already been traversed + * so that they are not dealt with twice. Similar flags are used to indicate + * which triangles have been used when following interior lines. + * + * Interior lines do not intersect any boundaries. They are found by + * traversing all triangles that have not yet been visited until a suitable + * starting point is found, and then the contour line is followed across the + * interior of the triangulation until it returns to the start point. For + * filled contours this process is repeated for both lower and upper contour + * levels, and the direction of traversal is reversed for upper contours. + * + * Working out in which direction a contour line leaves a triangle uses the + * a lookup table. A triangle has three points, each of which has a z-value + * which is either less than the contour level or not. Hence there are 8 + * configurations to deal with, 2 of which do not have a contour line (all + * points below or above (including the same as) the contour level) and 6 that + * do. See the function get_exit_edge for details. + */ +#ifndef MPL_TRI_H +#define MPL_TRI_H + +#include <pybind11/pybind11.h> +#include <pybind11/numpy.h> + +#include <iostream> +#include <list> +#include <map> +#include <set> +#include <vector> + +namespace py = pybind11; + + +/* An edge of a triangle consisting of an triangle index in the range 0 to + * ntri-1 and an edge index in the range 0 to 2. Edge i goes from the + * triangle's point i to point (i+1)%3. */ +struct TriEdge +{ + TriEdge(); + TriEdge(int tri_, int edge_); + bool operator<(const TriEdge& other) const; + bool operator==(const TriEdge& other) const; + bool operator!=(const TriEdge& other) const; + friend std::ostream& operator<<(std::ostream& os, const TriEdge& tri_edge); + + int tri, edge; +}; + +// 2D point with x,y coordinates. +struct XY +{ + XY(); + XY(const double& x_, const double& y_); + double angle() const; // Angle in radians with respect to x-axis. + double cross_z(const XY& other) const; // z-component of cross product. + bool is_right_of(const XY& other) const; // Compares x then y. + bool operator==(const XY& other) const; + bool operator!=(const XY& other) const; + XY operator*(const double& multiplier) const; + const XY& operator+=(const XY& other); + const XY& operator-=(const XY& other); + XY operator+(const XY& other) const; + XY operator-(const XY& other) const; + friend std::ostream& operator<<(std::ostream& os, const XY& xy); + + double x, y; +}; + +// 3D point with x,y,z coordinates. +struct XYZ +{ + XYZ(const double& x_, const double& y_, const double& z_); + XYZ cross(const XYZ& other) const; + double dot(const XYZ& other) const; + XYZ operator-(const XYZ& other) const; + friend std::ostream& operator<<(std::ostream& os, const XYZ& xyz); + + double x, y, z; +}; + +// 2D bounding box, which may be empty. +class BoundingBox +{ +public: + BoundingBox(); + void add(const XY& point); + void expand(const XY& delta); + + // Consider these member variables read-only. + bool empty; + XY lower, upper; +}; + +/* A single line of a contour, which may be a closed line loop or an open line + * strip. Identical adjacent points are avoided using push_back(), and a closed + * line loop should also not have identical first and last points. */ +class ContourLine : public std::vector<XY> +{ +public: + ContourLine(); + void push_back(const XY& point); + void write() const; +}; + +// A Contour is a collection of zero or more ContourLines. +typedef std::vector<ContourLine> Contour; + +// Debug contour writing function. +void write_contour(const Contour& contour); + + + + +/* Triangulation with npoints points and ntri triangles. Derived fields are + * calculated when they are first needed. */ +class Triangulation +{ +public: + typedef py::array_t<double, py::array::c_style | py::array::forcecast> CoordinateArray; + typedef py::array_t<double, py::array::c_style | py::array::forcecast> TwoCoordinateArray; + typedef py::array_t<int, py::array::c_style | py::array::forcecast> TriangleArray; + typedef py::array_t<bool, py::array::c_style | py::array::forcecast> MaskArray; + typedef py::array_t<int, py::array::c_style | py::array::forcecast> EdgeArray; + typedef py::array_t<int, py::array::c_style | py::array::forcecast> NeighborArray; + + /* A single boundary is a vector of the TriEdges that make up that boundary + * following it around with unmasked triangles on the left. */ + typedef std::vector<TriEdge> Boundary; + typedef std::vector<Boundary> Boundaries; + + /* Constructor with optional mask, edges and neighbors. The latter two + * are calculated when first needed. + * x: double array of shape (npoints) of points' x-coordinates. + * y: double array of shape (npoints) of points' y-coordinates. + * triangles: int array of shape (ntri,3) of triangle point indices. + * Those ordered clockwise are changed to be anticlockwise. + * mask: Optional bool array of shape (ntri) indicating which triangles + * are masked. + * edges: Optional int array of shape (?,2) of start and end point + * indices, each edge (start,end and end,start) appearing only + * once. + * neighbors: Optional int array of shape (ntri,3) indicating which + * triangles are the neighbors of which TriEdges, or -1 if + * there is no such neighbor. + * correct_triangle_orientations: Whether or not should correct triangle + * orientations so that vertices are + * ordered anticlockwise. */ + Triangulation(const CoordinateArray& x, + const CoordinateArray& y, + const TriangleArray& triangles, + const MaskArray& mask, + const EdgeArray& edges, + const NeighborArray& neighbors, + bool correct_triangle_orientations); + + /* Calculate plane equation coefficients for all unmasked triangles from + * the point (x,y) coordinates and point z-array of shape (npoints) passed + * in via the args. Returned array has shape (npoints,3) and allows + * z-value at (x,y) coordinates in triangle tri to be calculated using + * z = array[tri,0]*x + array[tri,1]*y + array[tri,2]. */ + TwoCoordinateArray calculate_plane_coefficients(const CoordinateArray& z); + + // Return the boundaries collection, creating it if necessary. + const Boundaries& get_boundaries() const; + + // Return which boundary and boundary edge the specified TriEdge is. + void get_boundary_edge(const TriEdge& triEdge, + int& boundary, + int& edge) const; + + /* Return the edges array, creating it if necessary. */ + EdgeArray& get_edges(); + + /* Return the triangle index of the neighbor of the specified triangle + * edge. */ + int get_neighbor(int tri, int edge) const; + + /* Return the TriEdge that is the neighbor of the specified triangle edge, + * or TriEdge(-1,-1) if there is no such neighbor. */ + TriEdge get_neighbor_edge(int tri, int edge) const; + + /* Return the neighbors array, creating it if necessary. */ + NeighborArray& get_neighbors(); + + // Return the number of points in this triangulation. + int get_npoints() const; + + // Return the number of triangles in this triangulation. + int get_ntri() const; + + /* Return the index of the point that is at the start of the specified + * triangle edge. */ + int get_triangle_point(int tri, int edge) const; + int get_triangle_point(const TriEdge& tri_edge) const; + + // Return the coordinates of the specified point index. + XY get_point_coords(int point) const; + + // Indicates if the specified triangle is masked or not. + bool is_masked(int tri) const; + + /* Set or clear the mask array. Clears various derived fields so they are + * recalculated when next needed. + * mask: bool array of shape (ntri) indicating which triangles are + * masked, or an empty array to clear mask. */ + void set_mask(const MaskArray& mask); + + // Debug function to write boundaries. + void write_boundaries() const; + +private: + // An edge of a triangulation, composed of start and end point indices. + struct Edge + { + Edge() : start(-1), end(-1) {} + Edge(int start_, int end_) : start(start_), end(end_) {} + bool operator<(const Edge& other) const { + return start != other.start ? start < other.start : end < other.end; + } + int start, end; + }; + + /* An edge of a boundary of a triangulation, composed of a boundary index + * and an edge index within that boundary. Used to index into the + * boundaries collection to obtain the corresponding TriEdge. */ + struct BoundaryEdge + { + BoundaryEdge() : boundary(-1), edge(-1) {} + BoundaryEdge(int boundary_, int edge_) + : boundary(boundary_), edge(edge_) {} + int boundary, edge; + }; + + /* Calculate the boundaries collection. Should normally be accessed via + * get_boundaries(), which will call this function if necessary. */ + void calculate_boundaries(); + + /* Calculate the edges array. Should normally be accessed via + * get_edges(), which will call this function if necessary. */ + void calculate_edges(); + + /* Calculate the neighbors array. Should normally be accessed via + * get_neighbors(), which will call this function if necessary. */ + void calculate_neighbors(); + + /* Correct each triangle so that the vertices are ordered in an + * anticlockwise manner. */ + void correct_triangles(); + + /* Determine which edge index (0,1 or 2) the specified point index is in + * the specified triangle, or -1 if the point is not in the triangle. */ + int get_edge_in_triangle(int tri, int point) const; + + bool has_edges() const; + + bool has_mask() const; + + bool has_neighbors() const; + + + // Variables shared with python, always set. + CoordinateArray _x, _y; // double array (npoints). + TriangleArray _triangles; // int array (ntri,3) of triangle point indices, + // ordered anticlockwise. + + // Variables shared with python, may be unset (size == 0). + MaskArray _mask; // bool array (ntri). + + // Derived variables shared with python, may be unset (size == 0). + // If unset, are recalculated when needed. + EdgeArray _edges; // int array (?,2) of start & end point indices. + NeighborArray _neighbors; // int array (ntri,3), neighbor triangle indices + // or -1 if no neighbor. + + // Variables internal to C++ only. + Boundaries _boundaries; + + // Map used to look up BoundaryEdges from TriEdges. Normally accessed via + // get_boundary_edge(). + typedef std::map<TriEdge, BoundaryEdge> TriEdgeToBoundaryMap; + TriEdgeToBoundaryMap _tri_edge_to_boundary_map; +}; + + + +// Contour generator for a triangulation. +class TriContourGenerator +{ +public: + typedef Triangulation::CoordinateArray CoordinateArray; + typedef Triangulation::TwoCoordinateArray TwoCoordinateArray; + typedef py::array_t<unsigned char> CodeArray; + + /* Constructor. + * triangulation: Triangulation to generate contours for. + * z: Double array of shape (npoints) of z-values at triangulation + * points. */ + TriContourGenerator(Triangulation& triangulation, + const CoordinateArray& z); + + /* Create and return a non-filled contour. + * level: Contour level. + * Returns new python list [segs0, segs1, ...] where + * segs0: double array of shape (?,2) of point coordinates of first + * contour line, etc. */ + py::tuple create_contour(const double& level); + + /* Create and return a filled contour. + * lower_level: Lower contour level. + * upper_level: Upper contour level. + * Returns new python tuple (segs, kinds) where + * segs: double array of shape (n_points,2) of all point coordinates, + * kinds: ubyte array of shape (n_points) of all point code types. */ + py::tuple create_filled_contour(const double& lower_level, + const double& upper_level); + +private: + typedef Triangulation::Boundary Boundary; + typedef Triangulation::Boundaries Boundaries; + + /* Clear visited flags. + * include_boundaries: Whether to clear boundary flags or not, which are + * only used for filled contours. */ + void clear_visited_flags(bool include_boundaries); + + /* Convert a non-filled Contour from C++ to Python. + * Returns new python tuple ([segs0, segs1, ...], [kinds0, kinds1...]) + * where + * segs0: double array of shape (n_points,2) of point coordinates of first + * contour line, etc. + * kinds0: ubyte array of shape (n_points) of kinds codes of first contour + * line, etc. */ + py::tuple contour_line_to_segs_and_kinds(const Contour& contour); + + /* Convert a filled Contour from C++ to Python. + * Returns new python tuple ([segs], [kinds]) where + * segs: double array of shape (n_points,2) of all point coordinates, + * kinds: ubyte array of shape (n_points) of all point code types. */ + py::tuple contour_to_segs_and_kinds(const Contour& contour); + + /* Return the point on the specified TriEdge that intersects the specified + * level. */ + XY edge_interp(int tri, int edge, const double& level); + + /* Find and follow non-filled contour lines that start and end on a + * boundary of the Triangulation. + * contour: Contour to add new lines to. + * level: Contour level. */ + void find_boundary_lines(Contour& contour, + const double& level); + + /* Find and follow filled contour lines at either of the specified contour + * levels that start and end of a boundary of the Triangulation. + * contour: Contour to add new lines to. + * lower_level: Lower contour level. + * upper_level: Upper contour level. */ + void find_boundary_lines_filled(Contour& contour, + const double& lower_level, + const double& upper_level); + + /* Find and follow lines at the specified contour level that are + * completely in the interior of the Triangulation and hence do not + * intersect any boundary. + * contour: Contour to add new lines to. + * level: Contour level. + * on_upper: Whether on upper or lower contour level. + * filled: Whether contours are filled or not. */ + void find_interior_lines(Contour& contour, + const double& level, + bool on_upper, + bool filled); + + /* Follow contour line around boundary of the Triangulation from the + * specified TriEdge to its end which can be on either the lower or upper + * levels. Only used for filled contours. + * contour_line: Contour line to append new points to. + * tri_edge: On entry, TriEdge to start from. On exit, TriEdge that is + * finished on. + * lower_level: Lower contour level. + * upper_level: Upper contour level. + * on_upper: Whether starts on upper level or not. + * Return true if finishes on upper level, false if lower. */ + bool follow_boundary(ContourLine& contour_line, + TriEdge& tri_edge, + const double& lower_level, + const double& upper_level, + bool on_upper); + + /* Follow contour line across interior of Triangulation. + * contour_line: Contour line to append new points to. + * tri_edge: On entry, TriEdge to start from. On exit, TriEdge that is + * finished on. + * end_on_boundary: Whether this line ends on a boundary, or loops back + * upon itself. + * level: Contour level to follow. + * on_upper: Whether following upper or lower contour level. */ + void follow_interior(ContourLine& contour_line, + TriEdge& tri_edge, + bool end_on_boundary, + const double& level, + bool on_upper); + + // Return the Triangulation boundaries. + const Boundaries& get_boundaries() const; + + /* Return the edge by which the a level leaves a particular triangle, + * which is 0, 1 or 2 if the contour passes through the triangle or -1 + * otherwise. + * tri: Triangle index. + * level: Contour level to follow. + * on_upper: Whether following upper or lower contour level. */ + int get_exit_edge(int tri, const double& level, bool on_upper) const; + + // Return the z-value at the specified point index. + const double& get_z(int point) const; + + /* Return the point at which the a level intersects the line connecting the + * two specified point indices. */ + XY interp(int point1, int point2, const double& level) const; + + + + // Variables shared with python, always set. + Triangulation _triangulation; + CoordinateArray _z; // double array (npoints). + + // Variables internal to C++ only. + typedef std::vector<bool> InteriorVisited; // Size 2*ntri + typedef std::vector<bool> BoundaryVisited; + typedef std::vector<BoundaryVisited> BoundariesVisited; + typedef std::vector<bool> BoundariesUsed; + + InteriorVisited _interior_visited; + BoundariesVisited _boundaries_visited; // Only used for filled contours. + BoundariesUsed _boundaries_used; // Only used for filled contours. +}; + + + +/* TriFinder class implemented using the trapezoid map algorithm from the book + * "Computational Geometry, Algorithms and Applications", second edition, by + * M. de Berg, M. van Kreveld, M. Overmars and O. Schwarzkopf. + * + * The domain of interest is composed of vertical-sided trapezoids that are + * bounded to the left and right by points of the triangulation, and below and + * above by edges of the triangulation. Each triangle is represented by 1 or + * more of these trapezoids. Edges are inserted one a time in a random order. + * + * As the trapezoid map is created, a search tree is also created which allows + * fast lookup O(log N) of the trapezoid containing the point of interest. + * There are 3 types of node in the search tree: all leaf nodes represent + * trapezoids and all branch nodes have 2 child nodes and are either x-nodes or + * y-nodes. X-nodes represent points in the triangulation, and their 2 children + * refer to those parts of the search tree to the left and right of the point. + * Y-nodes represent edges in the triangulation, and their 2 children refer to + * those parts of the search tree below and above the edge. + * + * Nodes can be repeated throughout the search tree, and each is reference + * counted through the multiple parent nodes it is a child of. + * + * The algorithm is only intended to work with valid triangulations, i.e. it + * must not contain duplicate points, triangles formed from colinear points, or + * overlapping triangles. It does have some tolerance to triangles formed from + * colinear points but only in the simplest of cases. No explicit testing of + * the validity of the triangulation is performed as this is a computationally + * more complex task than the trifinding itself. */ +class TrapezoidMapTriFinder +{ +public: + typedef Triangulation::CoordinateArray CoordinateArray; + typedef py::array_t<int, py::array::c_style | py::array::forcecast> TriIndexArray; + + /* Constructor. A separate call to initialize() is required to initialize + * the object before use. + * triangulation: Triangulation to find triangles in. */ + TrapezoidMapTriFinder(Triangulation& triangulation); + + ~TrapezoidMapTriFinder(); + + /* Return an array of triangle indices. Takes 1D arrays x and y of + * point coordinates, and returns an array of the same size containing the + * indices of the triangles at those points. */ + TriIndexArray find_many(const CoordinateArray& x, const CoordinateArray& y); + + /* Return a reference to a new python list containing the following + * statistics about the tree: + * 0: number of nodes (tree size) + * 1: number of unique nodes (number of unique Node objects in tree) + * 2: number of trapezoids (tree leaf nodes) + * 3: number of unique trapezoids + * 4: maximum parent count (max number of times a node is repeated in + * tree) + * 5: maximum depth of tree (one more than the maximum number of + * comparisons needed to search through the tree) + * 6: mean of all trapezoid depths (one more than the average number of + * comparisons needed to search through the tree) */ + py::list get_tree_stats(); + + /* Initialize this object before use. May be called multiple times, if, + * for example, the triangulation is changed by setting the mask. */ + void initialize(); + + // Print the search tree as text to stdout; useful for debug purposes. + void print_tree(); + +private: + /* A Point consists of x,y coordinates as well as the index of a triangle + * associated with the point, so that a search at this point's coordinates + * can return a valid triangle index. */ + struct Point : XY + { + Point() : XY(), tri(-1) {} + Point(const double& x, const double& y) : XY(x,y), tri(-1) {} + explicit Point(const XY& xy) : XY(xy), tri(-1) {} + + int tri; + }; + + /* An Edge connects two Points, left and right. It is always true that + * right->is_right_of(*left). Stores indices of triangles below and above + * the Edge which are used to map from trapezoid to triangle index. Also + * stores pointers to the 3rd points of the below and above triangles, + * which are only used to disambiguate triangles with colinear points. */ + struct Edge + { + Edge(const Point* left_, + const Point* right_, + int triangle_below_, + int triangle_above_, + const Point* point_below_, + const Point* point_above_); + + // Return -1 if point to left of edge, 0 if on edge, +1 if to right. + int get_point_orientation(const XY& xy) const; + + // Return slope of edge, even if vertical (divide by zero is OK here). + double get_slope() const; + + /* Return y-coordinate of point on edge with specified x-coordinate. + * x must be within the x-limits of this edge. */ + double get_y_at_x(const double& x) const; + + // Return true if the specified point is either of the edge end points. + bool has_point(const Point* point) const; + + bool operator==(const Edge& other) const; + + friend std::ostream& operator<<(std::ostream& os, const Edge& edge) + { + return os << *edge.left << "->" << *edge.right; + } + + void print_debug() const; + + + const Point* left; // Not owned. + const Point* right; // Not owned. + int triangle_below; // Index of triangle below (to right of) Edge. + int triangle_above; // Index of triangle above (to left of) Edge. + const Point* point_below; // Used only for resolving ambiguous cases; + const Point* point_above; // is 0 if corresponding triangle is -1 + }; + + class Node; // Forward declaration. + + // Helper structure used by TrapezoidMapTriFinder::get_tree_stats. + struct NodeStats + { + NodeStats() + : node_count(0), trapezoid_count(0), max_parent_count(0), + max_depth(0), sum_trapezoid_depth(0.0) + {} + + long node_count, trapezoid_count, max_parent_count, max_depth; + double sum_trapezoid_depth; + std::set<const Node*> unique_nodes, unique_trapezoid_nodes; + }; + + struct Trapezoid; // Forward declaration. + + /* Node of the trapezoid map search tree. There are 3 possible types: + * Type_XNode, Type_YNode and Type_TrapezoidNode. Data members are + * represented using a union: an XNode has a Point and 2 child nodes + * (left and right of the point), a YNode has an Edge and 2 child nodes + * (below and above the edge), and a TrapezoidNode has a Trapezoid. + * Each Node has multiple parents so it can appear in the search tree + * multiple times without having to create duplicate identical Nodes. + * The parent collection acts as a reference count to the number of times + * a Node occurs in the search tree. When the parent count is reduced to + * zero a Node can be safely deleted. */ + class Node + { + public: + Node(const Point* point, Node* left, Node* right);// Type_XNode. + Node(const Edge* edge, Node* below, Node* above); // Type_YNode. + Node(Trapezoid* trapezoid); // Type_TrapezoidNode. + + ~Node(); + + void add_parent(Node* parent); + + /* Recurse through the search tree and assert that everything is valid. + * Reduces to a no-op if NDEBUG is defined. */ + void assert_valid(bool tree_complete) const; + + // Recurse through the tree to return statistics about it. + void get_stats(int depth, NodeStats& stats) const; + + // Return the index of the triangle corresponding to this node. + int get_tri() const; + + bool has_child(const Node* child) const; + bool has_no_parents() const; + bool has_parent(const Node* parent) const; + + /* Recurse through the tree and print a textual representation to + * stdout. Argument depth used to indent for readability. */ + void print(int depth = 0) const; + + /* Remove a parent from this Node. Return true if no parents remain + * so that this Node can be deleted. */ + bool remove_parent(Node* parent); + + void replace_child(Node* old_child, Node* new_child); + + // Replace this node with the specified new_node in all parents. + void replace_with(Node* new_node); + + /* Recursive search through the tree to find the Node containing the + * specified XY point. */ + const Node* search(const XY& xy); + + /* Recursive search through the tree to find the Trapezoid containing + * the left endpoint of the specified Edge. Return 0 if fails, which + * can only happen if the triangulation is invalid. */ + Trapezoid* search(const Edge& edge); + + /* Copy constructor and assignment operator defined but not implemented + * to prevent objects being copied. */ + Node(const Node& other); + Node& operator=(const Node& other); + + private: + typedef enum { + Type_XNode, + Type_YNode, + Type_TrapezoidNode + } Type; + Type _type; + + union { + struct { + const Point* point; // Not owned. + Node* left; // Owned. + Node* right; // Owned. + } xnode; + struct { + const Edge* edge; // Not owned. + Node* below; // Owned. + Node* above; // Owned. + } ynode; + Trapezoid* trapezoid; // Owned. + } _union; + + typedef std::list<Node*> Parents; + Parents _parents; // Not owned. + }; + + /* A Trapezoid is bounded by Points to left and right, and Edges below and + * above. Has up to 4 neighboring Trapezoids to lower/upper left/right. + * Lower left neighbor is Trapezoid to left that shares the below Edge, or + * is 0 if there is no such Trapezoid (and similar for other neighbors). + * To obtain the index of the triangle corresponding to a particular + * Trapezoid, use the Edge member variables below.triangle_above or + * above.triangle_below. */ + struct Trapezoid + { + Trapezoid(const Point* left_, + const Point* right_, + const Edge& below_, + const Edge& above_); + + /* Assert that this Trapezoid is valid. Reduces to a no-op if NDEBUG + * is defined. */ + void assert_valid(bool tree_complete) const; + + /* Return one of the 4 corner points of this Trapezoid. Only used for + * debugging purposes. */ + XY get_lower_left_point() const; + XY get_lower_right_point() const; + XY get_upper_left_point() const; + XY get_upper_right_point() const; + + void print_debug() const; + + /* Set one of the 4 neighbor trapezoids and the corresponding reverse + * Trapezoid of the new neighbor (if it is not 0), so that they are + * consistent. */ + void set_lower_left(Trapezoid* lower_left_); + void set_lower_right(Trapezoid* lower_right_); + void set_upper_left(Trapezoid* upper_left_); + void set_upper_right(Trapezoid* upper_right_); + + /* Copy constructor and assignment operator defined but not implemented + * to prevent objects being copied. */ + Trapezoid(const Trapezoid& other); + Trapezoid& operator=(const Trapezoid& other); + + + const Point* left; // Not owned. + const Point* right; // Not owned. + const Edge& below; + const Edge& above; + + // 4 neighboring trapezoids, can be 0, not owned. + Trapezoid* lower_left; // Trapezoid to left that shares below + Trapezoid* lower_right; // Trapezoid to right that shares below + Trapezoid* upper_left; // Trapezoid to left that shares above + Trapezoid* upper_right; // Trapezoid to right that shares above + + Node* trapezoid_node; // Node that owns this Trapezoid. + }; + + + // Add the specified Edge to the search tree, returning true if successful. + bool add_edge_to_tree(const Edge& edge); + + // Clear all memory allocated by this object. + void clear(); + + // Return the triangle index at the specified point, or -1 if no triangle. + int find_one(const XY& xy); + + /* Determine the trapezoids that the specified Edge intersects, returning + * true if successful. */ + bool find_trapezoids_intersecting_edge(const Edge& edge, + std::vector<Trapezoid*>& trapezoids); + + + + // Variables shared with python, always set. + Triangulation& _triangulation; + + // Variables internal to C++ only. + Point* _points; // Array of all points in triangulation plus corners of + // enclosing rectangle. Owned. + + typedef std::vector<Edge> Edges; + Edges _edges; // All Edges in triangulation plus bottom and top Edges of + // enclosing rectangle. + + Node* _tree; // Root node of the trapezoid map search tree. Owned. +}; + +#endif diff --git a/contrib/python/matplotlib/py3/src/tri/_tri_wrapper.cpp b/contrib/python/matplotlib/py3/src/tri/_tri_wrapper.cpp new file mode 100644 index 0000000000..1b0c3d7555 --- /dev/null +++ b/contrib/python/matplotlib/py3/src/tri/_tri_wrapper.cpp @@ -0,0 +1,58 @@ +#include "_tri.h" + +PYBIND11_MODULE(_tri, m) { + py::class_<Triangulation>(m, "Triangulation") + .def(py::init<const Triangulation::CoordinateArray&, + const Triangulation::CoordinateArray&, + const Triangulation::TriangleArray&, + const Triangulation::MaskArray&, + const Triangulation::EdgeArray&, + const Triangulation::NeighborArray&, + bool>(), + py::arg("x"), + py::arg("y"), + py::arg("triangles"), + py::arg("mask"), + py::arg("edges"), + py::arg("neighbors"), + py::arg("correct_triangle_orientations"), + "Create a new C++ Triangulation object.\n" + "This should not be called directly, use the python class\n" + "matplotlib.tri.Triangulation instead.\n") + .def("calculate_plane_coefficients", &Triangulation::calculate_plane_coefficients, + "Calculate plane equation coefficients for all unmasked triangles.") + .def("get_edges", &Triangulation::get_edges, + "Return edges array.") + .def("get_neighbors", &Triangulation::get_neighbors, + "Return neighbors array.") + .def("set_mask", &Triangulation::set_mask, + "Set or clear the mask array."); + + py::class_<TriContourGenerator>(m, "TriContourGenerator") + .def(py::init<Triangulation&, + const TriContourGenerator::CoordinateArray&>(), + py::arg("triangulation"), + py::arg("z"), + "Create a new C++ TriContourGenerator object.\n" + "This should not be called directly, use the functions\n" + "matplotlib.axes.tricontour and tricontourf instead.\n") + .def("create_contour", &TriContourGenerator::create_contour, + "Create and return a non-filled contour.") + .def("create_filled_contour", &TriContourGenerator::create_filled_contour, + "Create and return a filled contour."); + + py::class_<TrapezoidMapTriFinder>(m, "TrapezoidMapTriFinder") + .def(py::init<Triangulation&>(), + py::arg("triangulation"), + "Create a new C++ TrapezoidMapTriFinder object.\n" + "This should not be called directly, use the python class\n" + "matplotlib.tri.TrapezoidMapTriFinder instead.\n") + .def("find_many", &TrapezoidMapTriFinder::find_many, + "Find indices of triangles containing the point coordinates (x, y).") + .def("get_tree_stats", &TrapezoidMapTriFinder::get_tree_stats, + "Return statistics about the tree used by the trapezoid map.") + .def("initialize", &TrapezoidMapTriFinder::initialize, + "Initialize this object, creating the trapezoid map from the triangulation.") + .def("print_tree", &TrapezoidMapTriFinder::print_tree, + "Print the search tree as text to stdout; useful for debug purposes."); +} |