aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/matplotlib/py2/src
diff options
context:
space:
mode:
authorshumkovnd <shumkovnd@yandex-team.com>2023-11-10 14:39:34 +0300
committershumkovnd <shumkovnd@yandex-team.com>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py2/src
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
downloadydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py2/src')
-rw-r--r--contrib/python/matplotlib/py2/src/_backend_agg.cpp234
-rw-r--r--contrib/python/matplotlib/py2/src/_backend_agg.h1294
-rw-r--r--contrib/python/matplotlib/py2/src/_backend_agg_basic_types.h127
-rw-r--r--contrib/python/matplotlib/py2/src/_backend_agg_wrapper.cpp777
-rw-r--r--contrib/python/matplotlib/py2/src/_contour.cpp1790
-rw-r--r--contrib/python/matplotlib/py2/src/_contour.h530
-rw-r--r--contrib/python/matplotlib/py2/src/_contour_wrapper.cpp203
-rw-r--r--contrib/python/matplotlib/py2/src/_image.cpp175
-rw-r--r--contrib/python/matplotlib/py2/src/_image.h200
-rw-r--r--contrib/python/matplotlib/py2/src/_image_resample.h1013
-rw-r--r--contrib/python/matplotlib/py2/src/_image_wrapper.cpp510
-rw-r--r--contrib/python/matplotlib/py2/src/_path.h1316
-rw-r--r--contrib/python/matplotlib/py2/src/_path_wrapper.cpp900
-rw-r--r--contrib/python/matplotlib/py2/src/_png.cpp793
-rw-r--r--contrib/python/matplotlib/py2/src/_tkagg.cpp475
-rw-r--r--contrib/python/matplotlib/py2/src/_tkmini.h128
-rw-r--r--contrib/python/matplotlib/py2/src/_ttconv.cpp307
-rw-r--r--contrib/python/matplotlib/py2/src/agg_workaround.h85
-rw-r--r--contrib/python/matplotlib/py2/src/array.h80
-rw-r--r--contrib/python/matplotlib/py2/src/file_compat.h240
-rw-r--r--contrib/python/matplotlib/py2/src/ft2font.cpp808
-rw-r--r--contrib/python/matplotlib/py2/src/ft2font.h139
-rw-r--r--contrib/python/matplotlib/py2/src/ft2font_wrapper.cpp1805
-rw-r--r--contrib/python/matplotlib/py2/src/mplutils.cpp21
-rw-r--r--contrib/python/matplotlib/py2/src/mplutils.h72
-rw-r--r--contrib/python/matplotlib/py2/src/numpy_cpp.h569
-rw-r--r--contrib/python/matplotlib/py2/src/path_converters.h1011
-rw-r--r--contrib/python/matplotlib/py2/src/py_adaptors.h251
-rw-r--r--contrib/python/matplotlib/py2/src/py_converters.cpp619
-rw-r--r--contrib/python/matplotlib/py2/src/py_converters.h49
-rw-r--r--contrib/python/matplotlib/py2/src/py_exceptions.h72
-rw-r--r--contrib/python/matplotlib/py2/src/qhull_wrap.c377
-rw-r--r--contrib/python/matplotlib/py2/src/ya.make66
33 files changed, 17036 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py2/src/_backend_agg.cpp b/contrib/python/matplotlib/py2/src/_backend_agg.cpp
new file mode 100644
index 0000000000..3dc35f6782
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_backend_agg.cpp
@@ -0,0 +1,234 @@
+/* -*- 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, 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(width * height * 4),
+ pixBuffer(NULL),
+ renderingBuffer(),
+ alphaBuffer(NULL),
+ alphaMaskRenderingBuffer(),
+ alphaMask(alphaMaskRenderingBuffer),
+ pixfmtAlphaMask(alphaMaskRenderingBuffer),
+ rendererBaseAlphaMask(),
+ rendererAlphaMask(),
+ scanlineAlphaMask(),
+ slineP8(),
+ slineBin(),
+ pixFmt(),
+ rendererBase(),
+ rendererAA(),
+ rendererBin(),
+ theRasterizer(8192),
+ 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 &region)
+{
+ 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 &region, 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)
+{
+ typedef agg::conv_transform<py::PathIterator> transformed_path_t;
+ typedef agg::conv_curve<transformed_path_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);
+ curve_t curved_clippath(transformed_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::tostring_rgb(uint8_t *buf)
+{
+ // "Return the rendered buffer as an RGB string"
+
+ int row_len = width * 3;
+
+ agg::rendering_buffer renderingBufferTmp;
+ renderingBufferTmp.attach(buf, width, height, row_len);
+
+ agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_rgb24());
+}
+
+void RendererAgg::tostring_argb(uint8_t *buf)
+{
+ //"Return the rendered buffer as an RGB string";
+
+ int row_len = width * 4;
+
+ agg::rendering_buffer renderingBufferTmp;
+ renderingBufferTmp.attach(buf, width, height, row_len);
+ agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_argb32());
+}
+
+void RendererAgg::tostring_bgra(uint8_t *buf)
+{
+ //"Return the rendered buffer as an RGB string";
+
+ int row_len = width * 4;
+
+ agg::rendering_buffer renderingBufferTmp;
+ renderingBufferTmp.attach(buf, width, height, row_len);
+
+ agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_bgra32());
+}
+
+agg::rect_i RendererAgg::get_content_extents()
+{
+ agg::rect_i r(width, height, 0, 0);
+
+ // Looks at the alpha channel to find the minimum extents of the image
+ unsigned char *pixel = pixBuffer + 3;
+ for (int y = 0; y < (int)height; ++y) {
+ for (int x = 0; x < (int)width; ++x) {
+ if (*pixel) {
+ if (x < r.x1)
+ r.x1 = x;
+ if (y < r.y1)
+ r.y1 = y;
+ if (x > r.x2)
+ r.x2 = x;
+ if (y > r.y2)
+ r.y2 = y;
+ }
+ pixel += 4;
+ }
+ }
+
+ if (r.x1 == width && r.x2 == 0) {
+ // The buffer is completely empty.
+ r.x1 = r.y1 = r.x2 = r.y2 = 0;
+ } else {
+ r.x1 = std::max(0, r.x1);
+ r.y1 = std::max(0, r.y1);
+ r.x2 = std::min(r.x2 + 1, (int)width);
+ r.y2 = std::min(r.y2 + 1, (int)height);
+ }
+
+ return r;
+}
+
+void RendererAgg::clear()
+{
+ //"clear the rendered buffer";
+
+ rendererBase.clear(_fill_color);
+}
diff --git a/contrib/python/matplotlib/py2/src/_backend_agg.h b/contrib/python/matplotlib/py2/src/_backend_agg.h
new file mode 100644
index 0000000000..53b73f179b
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_backend_agg.h
@@ -0,0 +1,1294 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* _backend_agg.h
+*/
+
+#ifndef __BACKEND_AGG_H__
+#define __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. agg::buffer is
+// a class in the swig wrapper
+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,
+ e_offset_position offset_position);
+
+ 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);
+
+ void tostring_rgb(uint8_t *buf);
+ void tostring_argb(uint8_t *buf);
+ void tostring_bgra(uint8_t *buf);
+ agg::rect_i get_content_extents();
+ void clear();
+
+ BufferRegion *copy_from_bbox(agg::rect_d in_rect);
+ void restore_region(BufferRegion &reg);
+ void restore_region(BufferRegion &region, 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);
+
+ 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,
+ e_offset_position offset_position,
+ int check_snap,
+ int has_curves);
+
+ 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);
+ }
+
+ // 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);
+
+ 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() && !path.has_curves();
+ 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_curves());
+ clipped_t clipped(nan_removed, clip && !path.has_curves(), 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_curves());
+ 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);
+
+ 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;
+
+ fig.init(0, 0, width, height);
+ text.init(x, y - image.dim(0), 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(int(mpl_round(gc.cliprect.x1)),
+ int(mpl_round(height - gc.cliprect.y2)),
+ int(mpl_round(gc.cliprect.x2)),
+ int(mpl_round(height - gc.cliprect.y1)));
+ text.clip(clip);
+ }
+
+ if (text.x2 > text.x1) {
+ for (int yi = text.y1; yi < text.y2; ++yi) {
+ pixFmt.blend_solid_hspan(text.x1, yi, (text.x2 - text.x1), gc.color,
+ &image(yi - (y - image.dim(0)), text.x1 - x));
+ }
+ }
+ }
+}
+
+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);
+
+ 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,
+ e_offset_position offset_position,
+ int check_snap,
+ int has_curves)
+{
+ 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);
+
+ // Set some defaults, assuming no face or edge
+ gc.linewidth = 0.0;
+ facepair_t face;
+ face.first = Nfacecolors != 0;
+ agg::trans_affine trans;
+
+ 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);
+ if (offset_position == OFFSET_POSITION_DATA) {
+ trans = agg::trans_affine_translation(xo, yo) * trans;
+ } else {
+ 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];
+ }
+ }
+
+ bool do_clip = !face.first && !gc.has_hatchpath() && !has_curves;
+
+ if (check_snap) {
+ gc.isaa = antialiaseds(i % Naa);
+
+ transformed_path_t tpath(path, trans);
+ nan_removed_t nan_removed(tpath, true, has_curves);
+ clipped_t clipped(nan_removed, do_clip && !has_curves, width, height);
+ snapped_t snapped(
+ clipped, gc.snap_mode, path.total_vertices(), points_to_pixels(gc.linewidth));
+ if (has_curves) {
+ 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_curves);
+ clipped_t clipped(nan_removed, do_clip, width, height);
+ if (has_curves) {
+ 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,
+ e_offset_position offset_position)
+{
+ _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,
+ offset_position,
+ 1,
+ 1);
+}
+
+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 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;
+ ColorArray *edgecolors_ptr = &edgecolors;
+
+ if (edgecolors.size() == 0) {
+ if (antialiased) {
+ edgecolors_ptr = &facecolors;
+ }
+ }
+
+ _draw_path_collection_generic(gc,
+ master_transform,
+ gc.cliprect,
+ gc.clippath.path,
+ gc.clippath.trans,
+ path_generator,
+ transforms,
+ offsets,
+ offset_trans,
+ facecolors,
+ *edgecolors_ptr,
+ linewidths,
+ linestyles,
+ antialiaseds,
+ OFFSET_POSITION_FIGURE,
+ 0,
+ 0);
+}
+
+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]);
+ }
+
+ 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);
+
+ _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);
+
+ 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/py2/src/_backend_agg_basic_types.h b/contrib/python/matplotlib/py2/src/_backend_agg_basic_types.h
new file mode 100644
index 0000000000..74a318e7d2
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_backend_agg_basic_types.h
@@ -0,0 +1,127 @@
+#ifndef __BACKEND_AGG_BASIC_TYPES_H__
+#define __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)
+ {
+ for (dash_t::const_iterator i = dashes.begin(); i != dashes.end(); ++i) {
+ double val0 = i->first;
+ double val1 = i->second;
+ val0 = val0 * dpi / 72.0;
+ val1 = val1 * dpi / 72.0;
+ if (!isaa) {
+ val0 = (int)val0 + 0.5;
+ val1 = (int)val1 + 0.5;
+ }
+ stroke.add_dash(val0, val1);
+ }
+ stroke.dash_start(get_dash_offset() * dpi / 72.0);
+ }
+};
+
+typedef std::vector<Dashes> DashesVector;
+
+enum e_offset_position {
+ OFFSET_POSITION_FIGURE,
+ OFFSET_POSITION_DATA
+};
+
+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();
+ }
+
+ private:
+ // prevent copying
+ GCAgg(const GCAgg &);
+ GCAgg &operator=(const GCAgg &);
+};
+
+#endif
diff --git a/contrib/python/matplotlib/py2/src/_backend_agg_wrapper.cpp b/contrib/python/matplotlib/py2/src/_backend_agg_wrapper.cpp
new file mode 100644
index 0000000000..ea6c7b1267
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_backend_agg_wrapper.cpp
@@ -0,0 +1,777 @@
+#include "mplutils.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;
+
+typedef struct
+{
+ PyObject_HEAD
+ BufferRegion *x;
+ Py_ssize_t shape[3];
+ Py_ssize_t strides[3];
+ Py_ssize_t suboffsets[3];
+} PyBufferRegion;
+
+
+/**********************************************************************
+ * 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, PyObject *kwds)
+{
+ return PyBytes_FromStringAndSize((const char *)self->x->get_data(),
+ 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, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ PyObject *bufobj;
+ uint8_t *buf;
+
+ bufobj = PyBytes_FromStringAndSize(NULL, self->x->get_height() * self->x->get_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 = self->x->get_width() * 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 PyBufferRegionType;
+
+static PyTypeObject *PyBufferRegion_init_type(PyObject *m, PyTypeObject *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;
+ memset(&buffer_procs, 0, sizeof(PyBufferProcs));
+ buffer_procs.bf_getbuffer = (getbufferproc)PyBufferRegion_get_buffer;
+
+ memset(type, 0, sizeof(PyTypeObject));
+ type->tp_name = "matplotlib.backends._backend_agg.BufferRegion";
+ type->tp_basicsize = sizeof(PyBufferRegion);
+ type->tp_dealloc = (destructor)PyBufferRegion_dealloc;
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER;
+ type->tp_methods = methods;
+ type->tp_new = PyBufferRegion_new;
+ type->tp_as_buffer = &buffer_procs;
+
+ if (PyType_Ready(type) < 0) {
+ return NULL;
+ }
+
+ /* Don't need to add to module, since you can't create buffer
+ regions directly from Python */
+
+ return type;
+}
+
+/**********************************************************************
+ * 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, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ GCAgg gc;
+ agg::trans_affine master_transform;
+ PyObject *pathobj;
+ 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;
+ e_offset_position offset_position;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&OO&O&O&O&O&O&O&O&OO&:draw_path_collection",
+ &convert_gcagg,
+ &gc,
+ &convert_trans_affine,
+ &master_transform,
+ &pathobj,
+ &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,
+ &convert_offset_position,
+ &offset_position)) {
+ return NULL;
+ }
+
+ try
+ {
+ py::PathGenerator path(pathobj);
+
+ CALL_CPP("draw_path_collection",
+ (self->x->draw_path_collection(gc,
+ master_transform,
+ path,
+ transforms,
+ offsets,
+ offset_trans,
+ facecolors,
+ edgecolors,
+ linewidths,
+ dashes,
+ antialiaseds,
+ offset_position)));
+ }
+ catch (const py::exception &)
+ {
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+ 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;
+ int antialiased;
+ numpy::array_view<const double, 2> edgecolors;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&IIO&O&O&O&iO&: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,
+ &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, PyObject *kwds)
+{
+ 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 be a 3x2 array, got %" NPY_INTP_FMT "x%" 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 be a 3x4 array, got %" NPY_INTP_FMT "x%" 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, PyObject *kwds)
+{
+ 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() != 0 && (points.dim(1) != 3 || points.dim(2) != 2)) {
+ PyErr_Format(PyExc_ValueError,
+ "points must be a Nx3x2 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT "x%" NPY_INTP_FMT,
+ points.dim(0), points.dim(1), points.dim(2));
+ return NULL;
+ }
+
+ if (colors.size() != 0 && (colors.dim(1) != 3 || colors.dim(2) != 4)) {
+ PyErr_Format(PyExc_ValueError,
+ "colors must be a Nx3x4 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT "x%" NPY_INTP_FMT,
+ colors.dim(0), colors.dim(1), colors.dim(2));
+ return NULL;
+ }
+
+ if (points.size() != colors.size()) {
+ PyErr_Format(PyExc_ValueError,
+ "points and colors arrays must be the same length, got %" NPY_INTP_FMT " and %" NPY_INTP_FMT,
+ 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;
+}
+
+static PyObject *PyRendererAgg_tostring_rgb(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *buffobj = NULL;
+
+ buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 3);
+ if (buffobj == NULL) {
+ return NULL;
+ }
+
+ CALL_CPP_CLEANUP("tostring_rgb",
+ (self->x->tostring_rgb((uint8_t *)PyBytes_AS_STRING(buffobj))),
+ Py_DECREF(buffobj));
+
+ return buffobj;
+}
+
+static PyObject *PyRendererAgg_tostring_argb(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *buffobj = NULL;
+
+ buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 4);
+ if (buffobj == NULL) {
+ return NULL;
+ }
+
+ CALL_CPP_CLEANUP("tostring_argb",
+ (self->x->tostring_argb((uint8_t *)PyBytes_AS_STRING(buffobj))),
+ Py_DECREF(buffobj));
+
+ return buffobj;
+}
+
+static PyObject *PyRendererAgg_tostring_bgra(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *buffobj = NULL;
+
+ buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 4);
+ if (buffobj == NULL) {
+ return NULL;
+ }
+
+ CALL_CPP_CLEANUP("to_string_bgra",
+ (self->x->tostring_bgra((uint8_t *)PyBytes_AS_STRING(buffobj))),
+ Py_DECREF(buffobj));
+
+ return buffobj;
+}
+
+static PyObject *
+PyRendererAgg_get_content_extents(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+ agg::rect_i extents;
+
+ CALL_CPP("get_content_extents", (extents = self->x->get_content_extents()));
+
+ return Py_BuildValue(
+ "iiii", extents.x1, extents.y1, extents.x2 - extents.x1, extents.y2 - extents.y1);
+}
+
+static PyObject *PyRendererAgg_buffer_rgba(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+#if PY3K
+ return PyBytes_FromStringAndSize((const char *)self->x->pixBuffer,
+ self->x->get_width() * self->x->get_height() * 4);
+#else
+ return PyBuffer_FromReadWriteMemory(self->x->pixBuffer,
+ self->x->get_width() * self->x->get_height() * 4);
+#endif
+}
+
+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 = self->x->get_width() * 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, PyObject *kwds)
+{
+ CALL_CPP("clear", self->x->clear());
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyRendererAgg_copy_from_bbox(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+ 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, PyObject *kwds)
+{
+ PyBufferRegion *regobj;
+ int xx1 = 0, yy1 = 0, xx2 = 0, yy2 = 0, x = 0, y = 0;
+
+ if (!PyArg_ParseTuple(args,
+ "O!|iiiiii:restore_region",
+ &PyBufferRegionType,
+ &regobj,
+ &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;
+}
+
+PyTypeObject PyRendererAggType;
+
+static PyTypeObject *PyRendererAgg_init_type(PyObject *m, PyTypeObject *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},
+
+ {"tostring_rgb", (PyCFunction)PyRendererAgg_tostring_rgb, METH_NOARGS, NULL},
+ {"tostring_argb", (PyCFunction)PyRendererAgg_tostring_argb, METH_NOARGS, NULL},
+ {"tostring_bgra", (PyCFunction)PyRendererAgg_tostring_bgra, METH_NOARGS, NULL},
+ {"get_content_extents", (PyCFunction)PyRendererAgg_get_content_extents, METH_NOARGS, NULL},
+ {"buffer_rgba", (PyCFunction)PyRendererAgg_buffer_rgba, METH_NOARGS, 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;
+ memset(&buffer_procs, 0, sizeof(PyBufferProcs));
+ buffer_procs.bf_getbuffer = (getbufferproc)PyRendererAgg_get_buffer;
+
+ memset(type, 0, sizeof(PyTypeObject));
+ type->tp_name = "matplotlib.backends._backend_agg.RendererAgg";
+ type->tp_basicsize = sizeof(PyRendererAgg);
+ type->tp_dealloc = (destructor)PyRendererAgg_dealloc;
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER;
+ type->tp_methods = methods;
+ type->tp_init = (initproc)PyRendererAgg_init;
+ type->tp_new = PyRendererAgg_new;
+ type->tp_as_buffer = &buffer_procs;
+
+ if (PyType_Ready(type) < 0) {
+ return NULL;
+ }
+
+ if (PyModule_AddObject(m, "RendererAgg", (PyObject *)type)) {
+ return NULL;
+ }
+
+ return type;
+}
+
+extern "C" {
+
+#if PY3K
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_backend_agg",
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+#define INITERROR return NULL
+
+PyMODINIT_FUNC PyInit__backend_agg(void)
+
+#else
+#define INITERROR return
+
+PyMODINIT_FUNC init_backend_agg(void)
+#endif
+
+{
+ PyObject *m;
+
+#if PY3K
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("_backend_agg", NULL, NULL);
+#endif
+
+ if (m == NULL) {
+ INITERROR;
+ }
+
+ import_array();
+
+ if (!PyRendererAgg_init_type(m, &PyRendererAggType)) {
+ INITERROR;
+ }
+
+ if (!PyBufferRegion_init_type(m, &PyBufferRegionType)) {
+ INITERROR;
+ }
+
+#if PY3K
+ return m;
+#endif
+}
+
+} // extern "C"
diff --git a/contrib/python/matplotlib/py2/src/_contour.cpp b/contrib/python/matplotlib/py2/src/_contour.cpp
new file mode 100644
index 0000000000..aecb442c7e
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_contour.cpp
@@ -0,0 +1,1790 @@
+// 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.
+#define NO_IMPORT_ARRAY
+
+#include "src/mplutils.h"
+#include "src/_contour.h"
+#include <algorithm>
+
+
+// 'kind' codes.
+#define MOVETO 1
+#define LINETO 2
+#define CLOSEPOLY 79
+
+// Point indices from current quad index.
+#define POINT_SW (quad)
+#define POINT_SE (quad+1)
+#define POINT_NW (quad+_nx)
+#define POINT_NE (quad+_nx+1)
+
+// CacheItem masks, only accessed directly to set. To read, use accessors
+// detailed below. 1 and 2 refer to level indices (lower and upper).
+#define MASK_Z_LEVEL 0x0003 // Combines the following two.
+#define MASK_Z_LEVEL_1 0x0001 // z > lower_level.
+#define MASK_Z_LEVEL_2 0x0002 // z > upper_level.
+#define MASK_VISITED_1 0x0004 // Algorithm has visited this quad.
+#define MASK_VISITED_2 0x0008
+#define MASK_SADDLE_1 0x0010 // quad is a saddle quad.
+#define MASK_SADDLE_2 0x0020
+#define MASK_SADDLE_LEFT_1 0x0040 // Contours turn left at saddle quad.
+#define MASK_SADDLE_LEFT_2 0x0080
+#define MASK_SADDLE_START_SW_1 0x0100 // Next visit starts on S or W edge.
+#define MASK_SADDLE_START_SW_2 0x0200
+#define MASK_BOUNDARY_S 0x0400 // S edge of quad is a boundary.
+#define MASK_BOUNDARY_W 0x0800 // W edge of quad is a boundary.
+// EXISTS_QUAD bit is always used, but the 4 EXISTS_CORNER are only used if
+// _corner_mask is true. Only one of EXISTS_QUAD or EXISTS_??_CORNER is ever
+// set per quad, hence not using unique bits for each; care is needed when
+// testing for these flags as they overlap.
+#define MASK_EXISTS_QUAD 0x1000 // All of quad exists (is not masked).
+#define MASK_EXISTS_SW_CORNER 0x2000 // SW corner exists, NE corner is masked.
+#define MASK_EXISTS_SE_CORNER 0x3000
+#define MASK_EXISTS_NW_CORNER 0x4000
+#define MASK_EXISTS_NE_CORNER 0x5000
+#define MASK_EXISTS 0x7000 // Combines all 5 EXISTS masks.
+
+// The following are only needed for filled contours.
+#define MASK_VISITED_S 0x10000 // Algorithm has visited S boundary.
+#define MASK_VISITED_W 0x20000 // Algorithm has visited W boundary.
+#define MASK_VISITED_CORNER 0x40000 // Algorithm has visited corner edge.
+
+
+// Accessors for various CacheItem masks. li is shorthand for level_index.
+#define Z_LEVEL(quad) (_cache[quad] & MASK_Z_LEVEL)
+#define Z_NE Z_LEVEL(POINT_NE)
+#define Z_NW Z_LEVEL(POINT_NW)
+#define Z_SE Z_LEVEL(POINT_SE)
+#define Z_SW Z_LEVEL(POINT_SW)
+#define VISITED(quad,li) (_cache[quad] & (li==1 ? MASK_VISITED_1 : MASK_VISITED_2))
+#define VISITED_S(quad) (_cache[quad] & MASK_VISITED_S)
+#define VISITED_W(quad) (_cache[quad] & MASK_VISITED_W)
+#define VISITED_CORNER(quad) (_cache[quad] & MASK_VISITED_CORNER)
+#define SADDLE(quad,li) (_cache[quad] & (li==1 ? MASK_SADDLE_1 : MASK_SADDLE_2))
+#define SADDLE_LEFT(quad,li) (_cache[quad] & (li==1 ? MASK_SADDLE_LEFT_1 : MASK_SADDLE_LEFT_2))
+#define SADDLE_START_SW(quad,li) (_cache[quad] & (li==1 ? MASK_SADDLE_START_SW_1 : MASK_SADDLE_START_SW_2))
+#define BOUNDARY_S(quad) (_cache[quad] & MASK_BOUNDARY_S)
+#define BOUNDARY_W(quad) (_cache[quad] & MASK_BOUNDARY_W)
+#define BOUNDARY_N(quad) BOUNDARY_S(quad+_nx)
+#define BOUNDARY_E(quad) BOUNDARY_W(quad+1)
+#define EXISTS_QUAD(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_QUAD)
+#define EXISTS_NONE(quad) ((_cache[quad] & MASK_EXISTS) == 0)
+// The following are only used if _corner_mask is true.
+#define EXISTS_SW_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_SW_CORNER)
+#define EXISTS_SE_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_SE_CORNER)
+#define EXISTS_NW_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_NW_CORNER)
+#define EXISTS_NE_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_NE_CORNER)
+#define EXISTS_ANY_CORNER(quad) (!EXISTS_NONE(quad) && !EXISTS_QUAD(quad))
+#define EXISTS_W_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_SW_CORNER(quad) || EXISTS_NW_CORNER(quad))
+#define EXISTS_E_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_SE_CORNER(quad) || EXISTS_NE_CORNER(quad))
+#define EXISTS_S_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_SW_CORNER(quad) || EXISTS_SE_CORNER(quad))
+#define EXISTS_N_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_NW_CORNER(quad) || EXISTS_NE_CORNER(quad))
+// Note that EXISTS_NE_CORNER(quad) is equivalent to BOUNDARY_SW(quad), etc.
+
+
+
+QuadEdge::QuadEdge()
+ : quad(-1), edge(Edge_None)
+{}
+
+QuadEdge::QuadEdge(long quad_, Edge edge_)
+ : quad(quad_), edge(edge_)
+{}
+
+bool QuadEdge::operator<(const QuadEdge& other) const
+{
+ if (quad != other.quad)
+ return quad < other.quad;
+ else
+ return edge < other.edge;
+}
+
+bool QuadEdge::operator==(const QuadEdge& other) const
+{
+ return quad == other.quad && edge == other.edge;
+}
+
+bool QuadEdge::operator!=(const QuadEdge& other) const
+{
+ return !operator==(other);
+}
+
+std::ostream& operator<<(std::ostream& os, const QuadEdge& quad_edge)
+{
+ return os << quad_edge.quad << ' ' << quad_edge.edge;
+}
+
+
+// conflict with code from matplotlib/tri/_tri.cpp
+#if 0
+XY::XY()
+{}
+
+XY::XY(const double& x_, const double& y_)
+ : x(x_), y(y_)
+{}
+
+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 << ')';
+}
+#endif
+
+
+ContourLine::ContourLine(bool is_hole)
+ : std::vector<XY>(),
+ _is_hole(is_hole),
+ _parent(0)
+{}
+
+void ContourLine::add_child(ContourLine* child)
+{
+ assert(!_is_hole && "Cannot add_child to a hole");
+ assert(child != 0 && "Null child ContourLine");
+ _children.push_back(child);
+}
+
+void ContourLine::clear_parent()
+{
+ assert(is_hole() && "Cannot clear parent of non-hole");
+ assert(_parent != 0 && "Null parent ContourLine");
+ _parent = 0;
+}
+
+const ContourLine::Children& ContourLine::get_children() const
+{
+ assert(!_is_hole && "Cannot get_children of a hole");
+ return _children;
+}
+
+const ContourLine* ContourLine::get_parent() const
+{
+ assert(_is_hole && "Cannot get_parent of a non-hole");
+ return _parent;
+}
+
+ContourLine* ContourLine::get_parent()
+{
+ assert(_is_hole && "Cannot get_parent of a non-hole");
+ return _parent;
+}
+
+bool ContourLine::is_hole() const
+{
+ return _is_hole;
+}
+
+// conflict with code from matplotlib/tri/_tri.cpp
+#if 0
+void ContourLine::push_back(const XY& point)
+{
+ if (empty() || point != back())
+ std::vector<XY>::push_back(point);
+}
+#endif
+
+void ContourLine::set_parent(ContourLine* parent)
+{
+ assert(_is_hole && "Cannot set parent of a non-hole");
+ assert(parent != 0 && "Null parent ContourLine");
+ _parent = parent;
+}
+
+// conflict with code from matplotlib/tri/_tri.cpp
+#if 0
+void ContourLine::write() const
+{
+ std::cout << "ContourLine " << this << " of " << size() << " points:";
+ for (const_iterator it = begin(); it != end(); ++it)
+ std::cout << ' ' << *it;
+ if (is_hole())
+ std::cout << " hole, parent=" << get_parent();
+ else {
+ std::cout << " not hole";
+ if (!_children.empty()) {
+ std::cout << ", children=";
+ for (Children::const_iterator it = _children.begin();
+ it != _children.end(); ++it)
+ std::cout << *it << ' ';
+ }
+ }
+ std::cout << std::endl;
+}
+#endif
+
+
+Contour::Contour()
+{}
+
+Contour::~Contour()
+{
+ delete_contour_lines();
+}
+
+void Contour::delete_contour_lines()
+{
+ for (iterator line_it = begin(); line_it != end(); ++line_it) {
+ delete *line_it;
+ *line_it = 0;
+ }
+ std::vector<ContourLine*>::clear();
+}
+
+void Contour::write() const
+{
+ std::cout << "Contour of " << size() << " lines." << std::endl;
+ for (const_iterator it = begin(); it != end(); ++it)
+ (*it)->write();
+}
+
+
+
+ParentCache::ParentCache(long nx, long x_chunk_points, long y_chunk_points)
+ : _nx(nx),
+ _x_chunk_points(x_chunk_points),
+ _y_chunk_points(y_chunk_points),
+ _lines(0), // Initialised when first needed.
+ _istart(0),
+ _jstart(0)
+{
+ assert(_x_chunk_points > 0 && _y_chunk_points > 0 &&
+ "Chunk sizes must be positive");
+}
+
+ContourLine* ParentCache::get_parent(long quad)
+{
+ long index = quad_to_index(quad);
+ ContourLine* parent = _lines[index];
+ while (parent == 0) {
+ index -= _x_chunk_points;
+ assert(index >= 0 && "Failed to find parent in chunk ParentCache");
+ parent = _lines[index];
+ }
+ assert(parent != 0 && "Failed to find parent in chunk ParentCache");
+ return parent;
+}
+
+long ParentCache::quad_to_index(long quad) const
+{
+ long i = quad % _nx;
+ long j = quad / _nx;
+ long index = (i-_istart) + (j-_jstart)*_x_chunk_points;
+
+ assert(i >= _istart && i < _istart + _x_chunk_points &&
+ "i-index outside chunk");
+ assert(j >= _jstart && j < _jstart + _y_chunk_points &&
+ "j-index outside chunk");
+ assert(index >= 0 && index < static_cast<long>(_lines.size()) &&
+ "ParentCache index outside chunk");
+
+ return index;
+}
+
+void ParentCache::set_chunk_starts(long istart, long jstart)
+{
+ assert(istart >= 0 && jstart >= 0 &&
+ "Chunk start indices cannot be negative");
+ _istart = istart;
+ _jstart = jstart;
+ if (_lines.empty())
+ _lines.resize(_x_chunk_points*_y_chunk_points, 0);
+ else
+ std::fill(_lines.begin(), _lines.end(), (ContourLine*)0);
+}
+
+void ParentCache::set_parent(long quad, ContourLine& contour_line)
+{
+ assert(!_lines.empty() &&
+ "Accessing ParentCache before it has been initialised");
+ long index = quad_to_index(quad);
+ if (_lines[index] == 0)
+ _lines[index] = (contour_line.is_hole() ? contour_line.get_parent()
+ : &contour_line);
+}
+
+
+
+QuadContourGenerator::QuadContourGenerator(const CoordinateArray& x,
+ const CoordinateArray& y,
+ const CoordinateArray& z,
+ const MaskArray& mask,
+ bool corner_mask,
+ long chunk_size)
+ : _x(x),
+ _y(y),
+ _z(z),
+ _nx(static_cast<long>(_x.dim(1))),
+ _ny(static_cast<long>(_x.dim(0))),
+ _n(_nx*_ny),
+ _corner_mask(corner_mask),
+ _chunk_size(chunk_size > 0 ? std::min(chunk_size, std::max(_nx, _ny)-1)
+ : std::max(_nx, _ny)-1),
+ _nxchunk(calc_chunk_count(_nx)),
+ _nychunk(calc_chunk_count(_ny)),
+ _chunk_count(_nxchunk*_nychunk),
+ _cache(new CacheItem[_n]),
+ _parent_cache(_nx,
+ chunk_size > 0 ? chunk_size+1 : _nx,
+ chunk_size > 0 ? chunk_size+1 : _ny)
+{
+ assert(!_x.empty() && !_y.empty() && !_z.empty() && "Empty array");
+ assert(_y.dim(0) == _x.dim(0) && _y.dim(1) == _x.dim(1) &&
+ "Different-sized y and x arrays");
+ assert(_z.dim(0) == _x.dim(0) && _z.dim(1) == _x.dim(1) &&
+ "Different-sized z and x arrays");
+ assert((mask.empty() ||
+ (mask.dim(0) == _x.dim(0) && mask.dim(1) == _x.dim(1))) &&
+ "Different-sized mask and x arrays");
+
+ init_cache_grid(mask);
+}
+
+QuadContourGenerator::~QuadContourGenerator()
+{
+ delete [] _cache;
+}
+
+void QuadContourGenerator::append_contour_line_to_vertices(
+ ContourLine& contour_line,
+ PyObject* vertices_list) const
+{
+ assert(vertices_list != 0 && "Null python vertices_list");
+
+ // Convert ContourLine to python equivalent, and clear it.
+ npy_intp dims[2] = {static_cast<npy_intp>(contour_line.size()), 2};
+ numpy::array_view<double, 2> line(dims);
+ npy_intp i = 0;
+ for (ContourLine::const_iterator point = contour_line.begin();
+ point != contour_line.end(); ++point, ++i) {
+ line(i, 0) = point->x;
+ line(i, 1) = point->y;
+ }
+ if (PyList_Append(vertices_list, line.pyobj_steal())) {
+ Py_XDECREF(vertices_list);
+ throw std::runtime_error("Unable to add contour line to vertices_list");
+ }
+
+ contour_line.clear();
+}
+
+void QuadContourGenerator::append_contour_to_vertices_and_codes(
+ Contour& contour,
+ PyObject* vertices_list,
+ PyObject* codes_list) const
+{
+ assert(vertices_list != 0 && "Null python vertices_list");
+ assert(codes_list != 0 && "Null python codes_list");
+
+ // Convert Contour to python equivalent, and clear it.
+ for (Contour::iterator line_it = contour.begin(); line_it != contour.end();
+ ++line_it) {
+ ContourLine& line = **line_it;
+ if (line.is_hole()) {
+ // If hole has already been converted to python its parent will be
+ // set to 0 and it can be deleted.
+ if (line.get_parent() != 0) {
+ delete *line_it;
+ *line_it = 0;
+ }
+ }
+ else {
+ // Non-holes are converted to python together with their child
+ // holes so that they are rendered correctly.
+ ContourLine::const_iterator point;
+ ContourLine::Children::const_iterator children_it;
+
+ const ContourLine::Children& children = line.get_children();
+ npy_intp npoints = static_cast<npy_intp>(line.size() + 1);
+ for (children_it = children.begin(); children_it != children.end();
+ ++children_it)
+ npoints += static_cast<npy_intp>((*children_it)->size() + 1);
+
+ npy_intp vertices_dims[2] = {npoints, 2};
+ numpy::array_view<double, 2> vertices(vertices_dims);
+ double* vertices_ptr = vertices.data();
+
+ npy_intp codes_dims[1] = {npoints};
+ numpy::array_view<unsigned char, 1> codes(codes_dims);
+ unsigned char* codes_ptr = codes.data();
+
+ for (point = line.begin(); point != line.end(); ++point) {
+ *vertices_ptr++ = point->x;
+ *vertices_ptr++ = point->y;
+ *codes_ptr++ = (point == line.begin() ? MOVETO : LINETO);
+ }
+ point = line.begin();
+ *vertices_ptr++ = point->x;
+ *vertices_ptr++ = point->y;
+ *codes_ptr++ = CLOSEPOLY;
+
+ for (children_it = children.begin(); children_it != children.end();
+ ++children_it) {
+ ContourLine& child = **children_it;
+ for (point = child.begin(); point != child.end(); ++point) {
+ *vertices_ptr++ = point->x;
+ *vertices_ptr++ = point->y;
+ *codes_ptr++ = (point == child.begin() ? MOVETO : LINETO);
+ }
+ point = child.begin();
+ *vertices_ptr++ = point->x;
+ *vertices_ptr++ = point->y;
+ *codes_ptr++ = CLOSEPOLY;
+
+ child.clear_parent(); // To indicate it can be deleted.
+ }
+
+ if (PyList_Append(vertices_list, vertices.pyobj_steal()) ||
+ PyList_Append(codes_list, codes.pyobj_steal())) {
+ Py_XDECREF(vertices_list);
+ Py_XDECREF(codes_list);
+ contour.delete_contour_lines();
+ throw std::runtime_error("Unable to add contour line to vertices and codes lists");
+ }
+
+ delete *line_it;
+ *line_it = 0;
+ }
+ }
+
+ // Delete remaining contour lines.
+ contour.delete_contour_lines();
+}
+
+long QuadContourGenerator::calc_chunk_count(long point_count) const
+{
+ assert(point_count > 0 && "point count must be positive");
+ assert(_chunk_size > 0 && "Chunk size must be positive");
+
+ if (_chunk_size > 0) {
+ long count = (point_count-1) / _chunk_size;
+ if (count*_chunk_size < point_count-1)
+ ++count;
+
+ assert(count >= 1 && "Invalid chunk count");
+ return count;
+ }
+ else
+ return 1;
+}
+
+PyObject* QuadContourGenerator::create_contour(const double& level)
+{
+ init_cache_levels(level, level);
+
+ PyObject* vertices_list = PyList_New(0);
+ if (vertices_list == 0)
+ throw std::runtime_error("Failed to create Python list");
+
+ // Lines that start and end on boundaries.
+ long ichunk, jchunk, istart, iend, jstart, jend;
+ for (long ijchunk = 0; ijchunk < _chunk_count; ++ijchunk) {
+ get_chunk_limits(ijchunk, ichunk, jchunk, istart, iend, jstart, jend);
+
+ for (long j = jstart; j < jend; ++j) {
+ long quad_end = iend + j*_nx;
+ for (long quad = istart + j*_nx; quad < quad_end; ++quad) {
+ if (EXISTS_NONE(quad) || VISITED(quad,1)) continue;
+
+ if (BOUNDARY_S(quad) && Z_SW >= 1 && Z_SE < 1 &&
+ start_line(vertices_list, quad, Edge_S, level)) continue;
+
+ if (BOUNDARY_W(quad) && Z_NW >= 1 && Z_SW < 1 &&
+ start_line(vertices_list, quad, Edge_W, level)) continue;
+
+ if (BOUNDARY_N(quad) && Z_NE >= 1 && Z_NW < 1 &&
+ start_line(vertices_list, quad, Edge_N, level)) continue;
+
+ if (BOUNDARY_E(quad) && Z_SE >= 1 && Z_NE < 1 &&
+ start_line(vertices_list, quad, Edge_E, level)) continue;
+
+ if (_corner_mask) {
+ // Equates to NE boundary.
+ if (EXISTS_SW_CORNER(quad) && Z_SE >= 1 && Z_NW < 1 &&
+ start_line(vertices_list, quad, Edge_NE, level)) continue;
+
+ // Equates to NW boundary.
+ if (EXISTS_SE_CORNER(quad) && Z_NE >= 1 && Z_SW < 1 &&
+ start_line(vertices_list, quad, Edge_NW, level)) continue;
+
+ // Equates to SE boundary.
+ if (EXISTS_NW_CORNER(quad) && Z_SW >= 1 && Z_NE < 1 &&
+ start_line(vertices_list, quad, Edge_SE, level)) continue;
+
+ // Equates to SW boundary.
+ if (EXISTS_NE_CORNER(quad) && Z_NW >= 1 && Z_SE < 1 &&
+ start_line(vertices_list, quad, Edge_SW, level)) continue;
+ }
+ }
+ }
+ }
+
+ // Internal loops.
+ ContourLine contour_line(false); // Reused for each contour line.
+ for (long ijchunk = 0; ijchunk < _chunk_count; ++ijchunk) {
+ get_chunk_limits(ijchunk, ichunk, jchunk, istart, iend, jstart, jend);
+
+ for (long j = jstart; j < jend; ++j) {
+ long quad_end = iend + j*_nx;
+ for (long quad = istart + j*_nx; quad < quad_end; ++quad) {
+ if (EXISTS_NONE(quad) || VISITED(quad,1))
+ continue;
+
+ Edge start_edge = get_start_edge(quad, 1);
+ if (start_edge == Edge_None)
+ continue;
+
+ QuadEdge quad_edge(quad, start_edge);
+ QuadEdge start_quad_edge(quad_edge);
+
+ // To obtain output identical to that produced by legacy code,
+ // sometimes need to ignore the first point and add it on the
+ // end instead.
+ bool ignore_first = (start_edge == Edge_N);
+ follow_interior(contour_line, quad_edge, 1, level,
+ !ignore_first, &start_quad_edge, 1, false);
+ if (ignore_first && !contour_line.empty())
+ contour_line.push_back(contour_line.front());
+ append_contour_line_to_vertices(contour_line, vertices_list);
+
+ // Repeat if saddle point but not visited.
+ if (SADDLE(quad,1) && !VISITED(quad,1))
+ --quad;
+ }
+ }
+ }
+
+ return vertices_list;
+}
+
+PyObject* QuadContourGenerator::create_filled_contour(const double& lower_level,
+ const double& upper_level)
+{
+ init_cache_levels(lower_level, upper_level);
+
+ Contour contour;
+
+ PyObject* vertices = PyList_New(0);
+ if (vertices == 0)
+ throw std::runtime_error("Failed to create Python list");
+
+ PyObject* codes = PyList_New(0);
+ if (codes == 0) {
+ Py_XDECREF(vertices);
+ throw std::runtime_error("Failed to create Python list");
+ }
+
+ long ichunk, jchunk, istart, iend, jstart, jend;
+ for (long ijchunk = 0; ijchunk < _chunk_count; ++ijchunk) {
+ get_chunk_limits(ijchunk, ichunk, jchunk, istart, iend, jstart, jend);
+ _parent_cache.set_chunk_starts(istart, jstart);
+
+ for (long j = jstart; j < jend; ++j) {
+ long quad_end = iend + j*_nx;
+ for (long quad = istart + j*_nx; quad < quad_end; ++quad) {
+ if (!EXISTS_NONE(quad))
+ single_quad_filled(contour, quad, lower_level, upper_level);
+ }
+ }
+
+ // Clear VISITED_W and VISITED_S flags that are reused by later chunks.
+ if (jchunk < _nychunk-1) {
+ long quad_end = iend + jend*_nx;
+ for (long quad = istart + jend*_nx; quad < quad_end; ++quad)
+ _cache[quad] &= ~MASK_VISITED_S;
+ }
+
+ if (ichunk < _nxchunk-1) {
+ long quad_end = iend + jend*_nx;
+ for (long quad = iend + jstart*_nx; quad < quad_end; quad += _nx)
+ _cache[quad] &= ~MASK_VISITED_W;
+ }
+
+ // Create python objects to return for this chunk.
+ append_contour_to_vertices_and_codes(contour, vertices, codes);
+ }
+
+ PyObject* tuple = PyTuple_New(2);
+ if (tuple == 0) {
+ Py_XDECREF(vertices);
+ Py_XDECREF(codes);
+ throw std::runtime_error("Failed to create Python tuple");
+ }
+
+ // No error checking here as filling in a brand new pre-allocated tuple.
+ PyTuple_SET_ITEM(tuple, 0, vertices);
+ PyTuple_SET_ITEM(tuple, 1, codes);
+
+ return tuple;
+}
+
+XY QuadContourGenerator::edge_interp(const QuadEdge& quad_edge,
+ const double& level)
+{
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds");
+ assert(quad_edge.edge != Edge_None && "Invalid edge");
+ return interp(get_edge_point_index(quad_edge, true),
+ get_edge_point_index(quad_edge, false),
+ level);
+}
+
+unsigned int QuadContourGenerator::follow_boundary(
+ ContourLine& contour_line,
+ QuadEdge& quad_edge,
+ const double& lower_level,
+ const double& upper_level,
+ unsigned int level_index,
+ const QuadEdge& start_quad_edge)
+{
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds");
+ assert(quad_edge.edge != Edge_None && "Invalid edge");
+ assert(is_edge_a_boundary(quad_edge) && "Not a boundary edge");
+ assert((level_index == 1 || level_index == 2) &&
+ "level index must be 1 or 2");
+ assert(start_quad_edge.quad >= 0 && start_quad_edge.quad < _n &&
+ "Start quad index out of bounds");
+ assert(start_quad_edge.edge != Edge_None && "Invalid start edge");
+
+ // Only called for filled contours, so always updates _parent_cache.
+ unsigned int end_level = 0;
+ bool first_edge = true;
+ bool stop = false;
+ long& quad = quad_edge.quad;
+
+ while (true) {
+ // Levels of start and end points of quad_edge.
+ unsigned int start_level =
+ (first_edge ? Z_LEVEL(get_edge_point_index(quad_edge, true))
+ : end_level);
+ long end_point = get_edge_point_index(quad_edge, false);
+ end_level = Z_LEVEL(end_point);
+
+ if (level_index == 1) {
+ if (start_level <= level_index && end_level == 2) {
+ // Increasing z, switching levels from 1 to 2.
+ level_index = 2;
+ stop = true;
+ }
+ else if (start_level >= 1 && end_level == 0) {
+ // Decreasing z, keeping same level.
+ stop = true;
+ }
+ }
+ else { // level_index == 2
+ if (start_level <= level_index && end_level == 2) {
+ // Increasing z, keeping same level.
+ stop = true;
+ }
+ else if (start_level >= 1 && end_level == 0) {
+ // Decreasing z, switching levels from 2 to 1.
+ level_index = 1;
+ stop = true;
+ }
+ }
+
+ if (!first_edge && !stop && quad_edge == start_quad_edge)
+ // Return if reached start point of contour line. Do this before
+ // checking/setting VISITED flags as will already have been
+ // visited.
+ break;
+
+ switch (quad_edge.edge) {
+ case Edge_E:
+ assert(!VISITED_W(quad+1) && "Already visited");
+ _cache[quad+1] |= MASK_VISITED_W;
+ break;
+ case Edge_N:
+ assert(!VISITED_S(quad+_nx) && "Already visited");
+ _cache[quad+_nx] |= MASK_VISITED_S;
+ break;
+ case Edge_W:
+ assert(!VISITED_W(quad) && "Already visited");
+ _cache[quad] |= MASK_VISITED_W;
+ break;
+ case Edge_S:
+ assert(!VISITED_S(quad) && "Already visited");
+ _cache[quad] |= MASK_VISITED_S;
+ break;
+ case Edge_NE:
+ case Edge_NW:
+ case Edge_SW:
+ case Edge_SE:
+ assert(!VISITED_CORNER(quad) && "Already visited");
+ _cache[quad] |= MASK_VISITED_CORNER;
+ break;
+ default:
+ assert(0 && "Invalid Edge");
+ break;
+ }
+
+ if (stop) {
+ // Exiting boundary to enter interior.
+ contour_line.push_back(edge_interp(quad_edge,
+ level_index == 1 ? lower_level
+ : upper_level));
+ break;
+ }
+
+ move_to_next_boundary_edge(quad_edge);
+
+ // Just moved to new quad edge, so label parent of start of quad edge.
+ switch (quad_edge.edge) {
+ case Edge_W:
+ case Edge_SW:
+ case Edge_S:
+ case Edge_SE:
+ if (!EXISTS_SE_CORNER(quad))
+ _parent_cache.set_parent(quad, contour_line);
+ break;
+ case Edge_E:
+ case Edge_NE:
+ case Edge_N:
+ case Edge_NW:
+ if (!EXISTS_SW_CORNER(quad))
+ _parent_cache.set_parent(quad + 1, contour_line);
+ break;
+ default:
+ assert(0 && "Invalid edge");
+ break;
+ }
+
+ // Add point to contour.
+ contour_line.push_back(get_point_xy(end_point));
+
+ if (first_edge)
+ first_edge = false;
+ }
+
+ return level_index;
+}
+
+void QuadContourGenerator::follow_interior(ContourLine& contour_line,
+ QuadEdge& quad_edge,
+ unsigned int level_index,
+ const double& level,
+ bool want_initial_point,
+ const QuadEdge* start_quad_edge,
+ unsigned int start_level_index,
+ bool set_parents)
+{
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds.");
+ assert(quad_edge.edge != Edge_None && "Invalid edge");
+ assert((level_index == 1 || level_index == 2) &&
+ "level index must be 1 or 2");
+ assert((start_quad_edge == 0 ||
+ (start_quad_edge->quad >= 0 && start_quad_edge->quad < _n)) &&
+ "Start quad index out of bounds.");
+ assert((start_quad_edge == 0 || start_quad_edge->edge != Edge_None) &&
+ "Invalid start edge");
+ assert((start_level_index == 1 || start_level_index == 2) &&
+ "start level index must be 1 or 2");
+
+ long& quad = quad_edge.quad;
+ Edge& edge = quad_edge.edge;
+
+ if (want_initial_point)
+ contour_line.push_back(edge_interp(quad_edge, level));
+
+ CacheItem visited_mask = (level_index == 1 ? MASK_VISITED_1 : MASK_VISITED_2);
+ CacheItem saddle_mask = (level_index == 1 ? MASK_SADDLE_1 : MASK_SADDLE_2);
+ Dir dir = Dir_Straight;
+
+ while (true) {
+ assert(!EXISTS_NONE(quad) && "Quad does not exist");
+ assert(!(_cache[quad] & visited_mask) && "Quad already visited");
+
+ // Determine direction to move to next quad. If the quad is already
+ // labelled as a saddle quad then the direction is easily read from
+ // the cache. Otherwise the direction is determined differently
+ // depending on whether the quad is a corner quad or not.
+
+ if (_cache[quad] & saddle_mask) {
+ // Already identified as a saddle quad, so direction is easy.
+ dir = (SADDLE_LEFT(quad,level_index) ? Dir_Left : Dir_Right);
+ _cache[quad] |= visited_mask;
+ }
+ else if (EXISTS_ANY_CORNER(quad)) {
+ // Need z-level of point opposite the entry edge, as that
+ // determines whether contour turns left or right.
+ long point_opposite = -1;
+ switch (edge) {
+ case Edge_E:
+ point_opposite = (EXISTS_SE_CORNER(quad) ? POINT_SW
+ : POINT_NW);
+ break;
+ case Edge_N:
+ point_opposite = (EXISTS_NW_CORNER(quad) ? POINT_SW
+ : POINT_SE);
+ break;
+ case Edge_W:
+ point_opposite = (EXISTS_SW_CORNER(quad) ? POINT_SE
+ : POINT_NE);
+ break;
+ case Edge_S:
+ point_opposite = (EXISTS_SW_CORNER(quad) ? POINT_NW
+ : POINT_NE);
+ break;
+ case Edge_NE: point_opposite = POINT_SW; break;
+ case Edge_NW: point_opposite = POINT_SE; break;
+ case Edge_SW: point_opposite = POINT_NE; break;
+ case Edge_SE: point_opposite = POINT_NW; break;
+ default: assert(0 && "Invalid edge"); break;
+ }
+ assert(point_opposite != -1 && "Failed to find opposite point");
+
+ // Lower-level polygons (level_index == 1) always have higher
+ // values to the left of the contour. Upper-level contours
+ // (level_index == 2) are reversed, which is what the fancy XOR
+ // does below.
+ if ((Z_LEVEL(point_opposite) >= level_index) ^ (level_index == 2))
+ dir = Dir_Right;
+ else
+ dir = Dir_Left;
+ _cache[quad] |= visited_mask;
+ }
+ else {
+ // Calculate configuration of this quad.
+ long point_left = -1, point_right = -1;
+ switch (edge) {
+ case Edge_E: point_left = POINT_SW; point_right = POINT_NW; break;
+ case Edge_N: point_left = POINT_SE; point_right = POINT_SW; break;
+ case Edge_W: point_left = POINT_NE; point_right = POINT_SE; break;
+ case Edge_S: point_left = POINT_NW; point_right = POINT_NE; break;
+ default: assert(0 && "Invalid edge"); break;
+ }
+
+ unsigned int config = (Z_LEVEL(point_left) >= level_index) << 1 |
+ (Z_LEVEL(point_right) >= level_index);
+
+ // Upper level (level_index == 2) polygons are reversed compared to
+ // lower level ones, i.e. higher values on the right rather than
+ // the left.
+ if (level_index == 2)
+ config = 3 - config;
+
+ // Calculate turn direction to move to next quad along contour line.
+ if (config == 1) {
+ // New saddle quad, set up cache bits for it.
+ double zmid = 0.25*(get_point_z(POINT_SW) +
+ get_point_z(POINT_SE) +
+ get_point_z(POINT_NW) +
+ get_point_z(POINT_NE));
+ _cache[quad] |= (level_index == 1 ? MASK_SADDLE_1 : MASK_SADDLE_2);
+ if ((zmid > level) ^ (level_index == 2)) {
+ dir = Dir_Right;
+ }
+ else {
+ dir = Dir_Left;
+ _cache[quad] |= (level_index == 1 ? MASK_SADDLE_LEFT_1
+ : MASK_SADDLE_LEFT_2);
+ }
+ if (edge == Edge_N || edge == Edge_E) {
+ // Next visit to this quad must start on S or W.
+ _cache[quad] |= (level_index == 1 ? MASK_SADDLE_START_SW_1
+ : MASK_SADDLE_START_SW_2);
+ }
+ }
+ else {
+ // Normal (non-saddle) quad.
+ dir = (config == 0 ? Dir_Left
+ : (config == 3 ? Dir_Right : Dir_Straight));
+ _cache[quad] |= visited_mask;
+ }
+ }
+
+ // Use dir to determine exit edge.
+ edge = get_exit_edge(quad_edge, dir);
+
+ if (set_parents) {
+ if (edge == Edge_E)
+ _parent_cache.set_parent(quad+1, contour_line);
+ else if (edge == Edge_W)
+ _parent_cache.set_parent(quad, contour_line);
+ }
+
+ // Add new point to contour line.
+ contour_line.push_back(edge_interp(quad_edge, level));
+
+ // Stop if reached boundary.
+ if (is_edge_a_boundary(quad_edge))
+ break;
+
+ move_to_next_quad(quad_edge);
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds");
+
+ // Return if reached start point of contour line.
+ if (start_quad_edge != 0 &&
+ quad_edge == *start_quad_edge &&
+ level_index == start_level_index)
+ break;
+ }
+}
+
+void QuadContourGenerator::get_chunk_limits(long ijchunk,
+ long& ichunk,
+ long& jchunk,
+ long& istart,
+ long& iend,
+ long& jstart,
+ long& jend)
+{
+ assert(ijchunk >= 0 && ijchunk < _chunk_count && "ijchunk out of bounds");
+ ichunk = ijchunk % _nxchunk;
+ jchunk = ijchunk / _nxchunk;
+ istart = ichunk*_chunk_size;
+ iend = (ichunk == _nxchunk-1 ? _nx : (ichunk+1)*_chunk_size);
+ jstart = jchunk*_chunk_size;
+ jend = (jchunk == _nychunk-1 ? _ny : (jchunk+1)*_chunk_size);
+}
+
+Edge QuadContourGenerator::get_corner_start_edge(long quad,
+ unsigned int level_index) const
+{
+ assert(quad >= 0 && quad < _n && "Quad index out of bounds");
+ assert((level_index == 1 || level_index == 2) &&
+ "level index must be 1 or 2");
+ assert(EXISTS_ANY_CORNER(quad) && "Quad is not a corner");
+
+ // Diagram for NE corner. Rotate for other corners.
+ //
+ // edge12
+ // point1 +---------+ point2
+ // \ |
+ // \ | edge23
+ // edge31 \ |
+ // \ |
+ // + point3
+ //
+ long point1, point2, point3;
+ Edge edge12, edge23, edge31;
+ switch (_cache[quad] & MASK_EXISTS) {
+ case MASK_EXISTS_SW_CORNER:
+ point1 = POINT_SE; point2 = POINT_SW; point3 = POINT_NW;
+ edge12 = Edge_S; edge23 = Edge_W; edge31 = Edge_NE;
+ break;
+ case MASK_EXISTS_SE_CORNER:
+ point1 = POINT_NE; point2 = POINT_SE; point3 = POINT_SW;
+ edge12 = Edge_E; edge23 = Edge_S; edge31 = Edge_NW;
+ break;
+ case MASK_EXISTS_NW_CORNER:
+ point1 = POINT_SW; point2 = POINT_NW; point3 = POINT_NE;
+ edge12 = Edge_W; edge23 = Edge_N; edge31 = Edge_SE;
+ break;
+ case MASK_EXISTS_NE_CORNER:
+ point1 = POINT_NW; point2 = POINT_NE; point3 = POINT_SE;
+ edge12 = Edge_N; edge23 = Edge_E; edge31 = Edge_SW;
+ break;
+ default:
+ assert(0 && "Invalid EXISTS for quad");
+ return Edge_None;
+ }
+
+ unsigned int config = (Z_LEVEL(point1) >= level_index) << 2 |
+ (Z_LEVEL(point2) >= level_index) << 1 |
+ (Z_LEVEL(point3) >= level_index);
+
+ // Upper level (level_index == 2) polygons are reversed compared to lower
+ // level ones, i.e. higher values on the right rather than the left.
+ if (level_index == 2)
+ config = 7 - config;
+
+ switch (config) {
+ case 0: return Edge_None;
+ case 1: return edge23;
+ case 2: return edge12;
+ case 3: return edge12;
+ case 4: return edge31;
+ case 5: return edge23;
+ case 6: return edge31;
+ case 7: return Edge_None;
+ default: assert(0 && "Invalid config"); return Edge_None;
+ }
+}
+
+long QuadContourGenerator::get_edge_point_index(const QuadEdge& quad_edge,
+ bool start) const
+{
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds");
+ assert(quad_edge.edge != Edge_None && "Invalid edge");
+
+ // Edges are ordered anticlockwise around their quad, as indicated by
+ // directions of arrows in diagrams below.
+ // Full quad NW corner (others similar)
+ //
+ // POINT_NW Edge_N POINT_NE POINT_NW Edge_N POINT_NE
+ // +----<-----+ +----<-----+
+ // | | | /
+ // | | | quad /
+ // Edge_W V quad ^ Edge_E Edge_W V ^
+ // | | | / Edge_SE
+ // | | | /
+ // +---->-----+ +
+ // POINT_SW Edge_S POINT_SE POINT_SW
+ //
+ const long& quad = quad_edge.quad;
+ switch (quad_edge.edge) {
+ case Edge_E: return (start ? POINT_SE : POINT_NE);
+ case Edge_N: return (start ? POINT_NE : POINT_NW);
+ case Edge_W: return (start ? POINT_NW : POINT_SW);
+ case Edge_S: return (start ? POINT_SW : POINT_SE);
+ case Edge_NE: return (start ? POINT_SE : POINT_NW);
+ case Edge_NW: return (start ? POINT_NE : POINT_SW);
+ case Edge_SW: return (start ? POINT_NW : POINT_SE);
+ case Edge_SE: return (start ? POINT_SW : POINT_NE);
+ default: assert(0 && "Invalid edge"); return 0;
+ }
+}
+
+Edge QuadContourGenerator::get_exit_edge(const QuadEdge& quad_edge,
+ Dir dir) const
+{
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds");
+ assert(quad_edge.edge != Edge_None && "Invalid edge");
+
+ const long& quad = quad_edge.quad;
+ const Edge& edge = quad_edge.edge;
+ if (EXISTS_ANY_CORNER(quad)) {
+ // Corner directions are always left or right. A corner is a triangle,
+ // entered via one edge so the other two edges are the left and right
+ // ones.
+ switch (edge) {
+ case Edge_E:
+ return (EXISTS_SE_CORNER(quad)
+ ? (dir == Dir_Left ? Edge_S : Edge_NW)
+ : (dir == Dir_Right ? Edge_N : Edge_SW));
+ case Edge_N:
+ return (EXISTS_NW_CORNER(quad)
+ ? (dir == Dir_Right ? Edge_W : Edge_SE)
+ : (dir == Dir_Left ? Edge_E : Edge_SW));
+ case Edge_W:
+ return (EXISTS_SW_CORNER(quad)
+ ? (dir == Dir_Right ? Edge_S : Edge_NE)
+ : (dir == Dir_Left ? Edge_N : Edge_SE));
+ case Edge_S:
+ return (EXISTS_SW_CORNER(quad)
+ ? (dir == Dir_Left ? Edge_W : Edge_NE)
+ : (dir == Dir_Right ? Edge_E : Edge_NW));
+ case Edge_NE: return (dir == Dir_Left ? Edge_S : Edge_W);
+ case Edge_NW: return (dir == Dir_Left ? Edge_E : Edge_S);
+ case Edge_SW: return (dir == Dir_Left ? Edge_N : Edge_E);
+ case Edge_SE: return (dir == Dir_Left ? Edge_W : Edge_N);
+ default: assert(0 && "Invalid edge"); return Edge_None;
+ }
+ }
+ else {
+ // A full quad has four edges, entered via one edge so that other three
+ // edges correspond to left, straight and right directions.
+ switch (edge) {
+ case Edge_E:
+ return (dir == Dir_Left ? Edge_S :
+ (dir == Dir_Right ? Edge_N : Edge_W));
+ case Edge_N:
+ return (dir == Dir_Left ? Edge_E :
+ (dir == Dir_Right ? Edge_W : Edge_S));
+ case Edge_W:
+ return (dir == Dir_Left ? Edge_N :
+ (dir == Dir_Right ? Edge_S : Edge_E));
+ case Edge_S:
+ return (dir == Dir_Left ? Edge_W :
+ (dir == Dir_Right ? Edge_E : Edge_N));
+ default: assert(0 && "Invalid edge"); return Edge_None;
+ }
+ }
+}
+
+XY QuadContourGenerator::get_point_xy(long point) const
+{
+ assert(point >= 0 && point < _n && "Point index out of bounds.");
+ return XY(_x.data()[static_cast<npy_intp>(point)],
+ _y.data()[static_cast<npy_intp>(point)]);
+}
+
+const double& QuadContourGenerator::get_point_z(long point) const
+{
+ assert(point >= 0 && point < _n && "Point index out of bounds.");
+ return _z.data()[static_cast<npy_intp>(point)];
+}
+
+Edge QuadContourGenerator::get_quad_start_edge(long quad,
+ unsigned int level_index) const
+{
+ assert(quad >= 0 && quad < _n && "Quad index out of bounds");
+ assert((level_index == 1 || level_index == 2) &&
+ "level index must be 1 or 2");
+ assert(EXISTS_QUAD(quad) && "Quad does not exist");
+
+ unsigned int config = (Z_NW >= level_index) << 3 |
+ (Z_NE >= level_index) << 2 |
+ (Z_SW >= level_index) << 1 |
+ (Z_SE >= level_index);
+
+ // Upper level (level_index == 2) polygons are reversed compared to lower
+ // level ones, i.e. higher values on the right rather than the left.
+ if (level_index == 2)
+ config = 15 - config;
+
+ switch (config) {
+ case 0: return Edge_None;
+ case 1: return Edge_E;
+ case 2: return Edge_S;
+ case 3: return Edge_E;
+ case 4: return Edge_N;
+ case 5: return Edge_N;
+ case 6:
+ // If already identified as a saddle quad then the start edge is
+ // read from the cache. Otherwise return either valid start edge
+ // and the subsequent call to follow_interior() will correctly set
+ // up saddle bits in cache.
+ if (!SADDLE(quad,level_index) || SADDLE_START_SW(quad,level_index))
+ return Edge_S;
+ else
+ return Edge_N;
+ case 7: return Edge_N;
+ case 8: return Edge_W;
+ case 9:
+ // See comment for 6 above.
+ if (!SADDLE(quad,level_index) || SADDLE_START_SW(quad,level_index))
+ return Edge_W;
+ else
+ return Edge_E;
+ case 10: return Edge_S;
+ case 11: return Edge_E;
+ case 12: return Edge_W;
+ case 13: return Edge_W;
+ case 14: return Edge_S;
+ case 15: return Edge_None;
+ default: assert(0 && "Invalid config"); return Edge_None;
+ }
+}
+
+Edge QuadContourGenerator::get_start_edge(long quad,
+ unsigned int level_index) const
+{
+ if (EXISTS_ANY_CORNER(quad))
+ return get_corner_start_edge(quad, level_index);
+ else
+ return get_quad_start_edge(quad, level_index);
+}
+
+void QuadContourGenerator::init_cache_grid(const MaskArray& mask)
+{
+ long i, j, quad;
+
+ if (mask.empty()) {
+ // No mask, easy to calculate quad existence and boundaries together.
+ quad = 0;
+ for (j = 0; j < _ny; ++j) {
+ for (i = 0; i < _nx; ++i, ++quad) {
+ _cache[quad] = 0;
+
+ if (i < _nx-1 && j < _ny-1)
+ _cache[quad] |= MASK_EXISTS_QUAD;
+
+ if ((i % _chunk_size == 0 || i == _nx-1) && j < _ny-1)
+ _cache[quad] |= MASK_BOUNDARY_W;
+
+ if ((j % _chunk_size == 0 || j == _ny-1) && i < _nx-1)
+ _cache[quad] |= MASK_BOUNDARY_S;
+ }
+ }
+ }
+ else {
+ // Casting avoids problem when sizeof(bool) != sizeof(npy_bool).
+ const npy_bool* mask_ptr =
+ reinterpret_cast<const npy_bool*>(mask.data());
+
+ // Have mask so use two stages.
+ // Stage 1, determine if quads/corners exist.
+ quad = 0;
+ for (j = 0; j < _ny; ++j) {
+ for (i = 0; i < _nx; ++i, ++quad) {
+ _cache[quad] = 0;
+
+ if (i < _nx-1 && j < _ny-1) {
+ unsigned int config = mask_ptr[POINT_NW] << 3 |
+ mask_ptr[POINT_NE] << 2 |
+ mask_ptr[POINT_SW] << 1 |
+ mask_ptr[POINT_SE];
+
+ if (_corner_mask) {
+ switch (config) {
+ case 0: _cache[quad] = MASK_EXISTS_QUAD; break;
+ case 1: _cache[quad] = MASK_EXISTS_NW_CORNER; break;
+ case 2: _cache[quad] = MASK_EXISTS_NE_CORNER; break;
+ case 4: _cache[quad] = MASK_EXISTS_SW_CORNER; break;
+ case 8: _cache[quad] = MASK_EXISTS_SE_CORNER; break;
+ default:
+ // Do nothing, quad is masked out.
+ break;
+ }
+ }
+ else if (config == 0)
+ _cache[quad] = MASK_EXISTS_QUAD;
+ }
+ }
+ }
+
+ // Stage 2, calculate W and S boundaries. For each quad use boundary
+ // data already calculated for quads to W and S, so must iterate
+ // through quads in correct order (increasing i and j indices).
+ // Cannot use boundary data for quads to E and N as have not yet
+ // calculated it.
+ quad = 0;
+ for (j = 0; j < _ny; ++j) {
+ for (i = 0; i < _nx; ++i, ++quad) {
+ if (_corner_mask) {
+ bool W_exists_none = (i == 0 || EXISTS_NONE(quad-1));
+ bool S_exists_none = (j == 0 || EXISTS_NONE(quad-_nx));
+ bool W_exists_E_edge = (i > 0 && EXISTS_E_EDGE(quad-1));
+ bool S_exists_N_edge = (j > 0 && EXISTS_N_EDGE(quad-_nx));
+
+ if ((EXISTS_W_EDGE(quad) && W_exists_none) ||
+ (EXISTS_NONE(quad) && W_exists_E_edge) ||
+ (i % _chunk_size == 0 && EXISTS_W_EDGE(quad) &&
+ W_exists_E_edge))
+ _cache[quad] |= MASK_BOUNDARY_W;
+
+ if ((EXISTS_S_EDGE(quad) && S_exists_none) ||
+ (EXISTS_NONE(quad) && S_exists_N_edge) ||
+ (j % _chunk_size == 0 && EXISTS_S_EDGE(quad) &&
+ S_exists_N_edge))
+ _cache[quad] |= MASK_BOUNDARY_S;
+ }
+ else {
+ bool W_exists_quad = (i > 0 && EXISTS_QUAD(quad-1));
+ bool S_exists_quad = (j > 0 && EXISTS_QUAD(quad-_nx));
+
+ if ((EXISTS_QUAD(quad) != W_exists_quad) ||
+ (i % _chunk_size == 0 && EXISTS_QUAD(quad) &&
+ W_exists_quad))
+ _cache[quad] |= MASK_BOUNDARY_W;
+
+ if ((EXISTS_QUAD(quad) != S_exists_quad) ||
+ (j % _chunk_size == 0 && EXISTS_QUAD(quad) &&
+ S_exists_quad))
+ _cache[quad] |= MASK_BOUNDARY_S;
+ }
+ }
+ }
+ }
+}
+
+void QuadContourGenerator::init_cache_levels(const double& lower_level,
+ const double& upper_level)
+{
+ assert(upper_level >= lower_level &&
+ "upper and lower levels are wrong way round");
+
+ bool two_levels = (lower_level != upper_level);
+ CacheItem keep_mask =
+ (_corner_mask ? MASK_EXISTS | MASK_BOUNDARY_S | MASK_BOUNDARY_W
+ : MASK_EXISTS_QUAD | MASK_BOUNDARY_S | MASK_BOUNDARY_W);
+
+ if (two_levels) {
+ const double* z_ptr = _z.data();
+ for (long quad = 0; quad < _n; ++quad, ++z_ptr) {
+ _cache[quad] &= keep_mask;
+ if (*z_ptr > upper_level)
+ _cache[quad] |= MASK_Z_LEVEL_2;
+ else if (*z_ptr > lower_level)
+ _cache[quad] |= MASK_Z_LEVEL_1;
+ }
+ }
+ else {
+ const double* z_ptr = _z.data();
+ for (long quad = 0; quad < _n; ++quad, ++z_ptr) {
+ _cache[quad] &= keep_mask;
+ if (*z_ptr > lower_level)
+ _cache[quad] |= MASK_Z_LEVEL_1;
+ }
+ }
+}
+
+XY QuadContourGenerator::interp(
+ long point1, long point2, const double& level) const
+{
+ assert(point1 >= 0 && point1 < _n && "Point index 1 out of bounds.");
+ assert(point2 >= 0 && point2 < _n && "Point index 2 out of bounds.");
+ assert(point1 != point2 && "Identical points");
+ double fraction = (get_point_z(point2) - level) /
+ (get_point_z(point2) - get_point_z(point1));
+ return get_point_xy(point1)*fraction + get_point_xy(point2)*(1.0 - fraction);
+}
+
+bool QuadContourGenerator::is_edge_a_boundary(const QuadEdge& quad_edge) const
+{
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds");
+ assert(quad_edge.edge != Edge_None && "Invalid edge");
+
+ switch (quad_edge.edge) {
+ case Edge_E: return BOUNDARY_E(quad_edge.quad);
+ case Edge_N: return BOUNDARY_N(quad_edge.quad);
+ case Edge_W: return BOUNDARY_W(quad_edge.quad);
+ case Edge_S: return BOUNDARY_S(quad_edge.quad);
+ case Edge_NE: return EXISTS_SW_CORNER(quad_edge.quad);
+ case Edge_NW: return EXISTS_SE_CORNER(quad_edge.quad);
+ case Edge_SW: return EXISTS_NE_CORNER(quad_edge.quad);
+ case Edge_SE: return EXISTS_NW_CORNER(quad_edge.quad);
+ default: assert(0 && "Invalid edge"); return true;
+ }
+}
+
+void QuadContourGenerator::move_to_next_boundary_edge(QuadEdge& quad_edge) const
+{
+ assert(is_edge_a_boundary(quad_edge) && "QuadEdge is not a boundary");
+
+ long& quad = quad_edge.quad;
+ Edge& edge = quad_edge.edge;
+
+ quad = get_edge_point_index(quad_edge, false);
+
+ // quad is now such that POINT_SW is the end point of the quad_edge passed
+ // to this function.
+
+ // To find the next boundary edge, first attempt to turn left 135 degrees
+ // and if that edge is a boundary then move to it. If not, attempt to turn
+ // left 90 degrees, then left 45 degrees, then straight on, etc, until can
+ // move.
+ // First determine which edge to attempt first.
+ int index = 0;
+ switch (edge) {
+ case Edge_E: index = 0; break;
+ case Edge_SE: index = 1; break;
+ case Edge_S: index = 2; break;
+ case Edge_SW: index = 3; break;
+ case Edge_W: index = 4; break;
+ case Edge_NW: index = 5; break;
+ case Edge_N: index = 6; break;
+ case Edge_NE: index = 7; break;
+ default: assert(0 && "Invalid edge"); break;
+ }
+
+ // If _corner_mask not set, only need to consider odd index in loop below.
+ if (!_corner_mask)
+ ++index;
+
+ // Try each edge in turn until a boundary is found.
+ int start_index = index;
+ do
+ {
+ switch (index) {
+ case 0:
+ if (EXISTS_SE_CORNER(quad-_nx-1)) { // Equivalent to BOUNDARY_NW
+ quad -= _nx+1;
+ edge = Edge_NW;
+ return;
+ }
+ break;
+ case 1:
+ if (BOUNDARY_N(quad-_nx-1)) {
+ quad -= _nx+1;
+ edge = Edge_N;
+ return;
+ }
+ break;
+ case 2:
+ if (EXISTS_SW_CORNER(quad-1)) { // Equivalent to BOUNDARY_NE
+ quad -= 1;
+ edge = Edge_NE;
+ return;
+ }
+ break;
+ case 3:
+ if (BOUNDARY_E(quad-1)) {
+ quad -= 1;
+ edge = Edge_E;
+ return;
+ }
+ break;
+ case 4:
+ if (EXISTS_NW_CORNER(quad)) { // Equivalent to BOUNDARY_SE
+ edge = Edge_SE;
+ return;
+ }
+ break;
+ case 5:
+ if (BOUNDARY_S(quad)) {
+ edge = Edge_S;
+ return;
+ }
+ break;
+ case 6:
+ if (EXISTS_NE_CORNER(quad-_nx)) { // Equivalent to BOUNDARY_SW
+ quad -= _nx;
+ edge = Edge_SW;
+ return;
+ }
+ break;
+ case 7:
+ if (BOUNDARY_W(quad-_nx)) {
+ quad -= _nx;
+ edge = Edge_W;
+ return;
+ }
+ break;
+ default: assert(0 && "Invalid index"); break;
+ }
+
+ if (_corner_mask)
+ index = (index + 1) % 8;
+ else
+ index = (index + 2) % 8;
+ } while (index != start_index);
+
+ assert(0 && "Failed to find next boundary edge");
+}
+
+void QuadContourGenerator::move_to_next_quad(QuadEdge& quad_edge) const
+{
+ assert(quad_edge.quad >= 0 && quad_edge.quad < _n &&
+ "Quad index out of bounds");
+ assert(quad_edge.edge != Edge_None && "Invalid edge");
+
+ // Move from quad_edge.quad to the neighbouring quad in the direction
+ // specified by quad_edge.edge.
+ switch (quad_edge.edge) {
+ case Edge_E: quad_edge.quad += 1; quad_edge.edge = Edge_W; break;
+ case Edge_N: quad_edge.quad += _nx; quad_edge.edge = Edge_S; break;
+ case Edge_W: quad_edge.quad -= 1; quad_edge.edge = Edge_E; break;
+ case Edge_S: quad_edge.quad -= _nx; quad_edge.edge = Edge_N; break;
+ default: assert(0 && "Invalid edge"); break;
+ }
+}
+
+void QuadContourGenerator::single_quad_filled(Contour& contour,
+ long quad,
+ const double& lower_level,
+ const double& upper_level)
+{
+ assert(quad >= 0 && quad < _n && "Quad index out of bounds");
+
+ // Order of checking is important here as can have different ContourLines
+ // from both lower and upper levels in the same quad. First check the S
+ // edge, then move up the quad to the N edge checking as required.
+
+ // Possible starts from S boundary.
+ if (BOUNDARY_S(quad) && EXISTS_S_EDGE(quad)) {
+
+ // Lower-level start from S boundary into interior.
+ if (!VISITED_S(quad) && Z_SW >= 1 && Z_SE == 0)
+ contour.push_back(start_filled(quad, Edge_S, 1, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from S boundary into interior.
+ if (!VISITED_S(quad) && Z_SW < 2 && Z_SE == 2)
+ contour.push_back(start_filled(quad, Edge_S, 2, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Lower-level start following S boundary from W to E.
+ if (!VISITED_S(quad) && Z_SW <= 1 && Z_SE == 1)
+ contour.push_back(start_filled(quad, Edge_S, 1, NotHole, Boundary,
+ lower_level, upper_level));
+
+ // Upper-level start following S boundary from W to E.
+ if (!VISITED_S(quad) && Z_SW == 2 && Z_SE == 1)
+ contour.push_back(start_filled(quad, Edge_S, 2, NotHole, Boundary,
+ lower_level, upper_level));
+ }
+
+ // Possible starts from W boundary.
+ if (BOUNDARY_W(quad) && EXISTS_W_EDGE(quad)) {
+
+ // Lower-level start from W boundary into interior.
+ if (!VISITED_W(quad) && Z_NW >= 1 && Z_SW == 0)
+ contour.push_back(start_filled(quad, Edge_W, 1, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from W boundary into interior.
+ if (!VISITED_W(quad) && Z_NW < 2 && Z_SW == 2)
+ contour.push_back(start_filled(quad, Edge_W, 2, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Lower-level start following W boundary from N to S.
+ if (!VISITED_W(quad) && Z_NW <= 1 && Z_SW == 1)
+ contour.push_back(start_filled(quad, Edge_W, 1, NotHole, Boundary,
+ lower_level, upper_level));
+
+ // Upper-level start following W boundary from N to S.
+ if (!VISITED_W(quad) && Z_NW == 2 && Z_SW == 1)
+ contour.push_back(start_filled(quad, Edge_W, 2, NotHole, Boundary,
+ lower_level, upper_level));
+ }
+
+ // Possible starts from NE boundary.
+ if (EXISTS_SW_CORNER(quad)) { // i.e. BOUNDARY_NE
+
+ // Lower-level start following NE boundary from SE to NW, hole.
+ if (!VISITED_CORNER(quad) && Z_NW == 1 && Z_SE == 1)
+ contour.push_back(start_filled(quad, Edge_NE, 1, Hole, Boundary,
+ lower_level, upper_level));
+ }
+ // Possible starts from SE boundary.
+ else if (EXISTS_NW_CORNER(quad)) { // i.e. BOUNDARY_SE
+
+ // Lower-level start from N to SE.
+ if (!VISITED(quad,1) && Z_NW == 0 && Z_SW == 0 && Z_NE >= 1)
+ contour.push_back(start_filled(quad, Edge_N, 1, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from SE to N, hole.
+ if (!VISITED(quad,2) && Z_NW < 2 && Z_SW < 2 && Z_NE == 2)
+ contour.push_back(start_filled(quad, Edge_SE, 2, Hole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from N to SE.
+ if (!VISITED(quad,2) && Z_NW == 2 && Z_SW == 2 && Z_NE < 2)
+ contour.push_back(start_filled(quad, Edge_N, 2, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Lower-level start from SE to N, hole.
+ if (!VISITED(quad,1) && Z_NW >= 1 && Z_SW >= 1 && Z_NE == 0)
+ contour.push_back(start_filled(quad, Edge_SE, 1, Hole, Interior,
+ lower_level, upper_level));
+ }
+ // Possible starts from NW boundary.
+ else if (EXISTS_SE_CORNER(quad)) { // i.e. BOUNDARY_NW
+
+ // Lower-level start from NW to E.
+ if (!VISITED(quad,1) && Z_SW == 0 && Z_SE == 0 && Z_NE >= 1)
+ contour.push_back(start_filled(quad, Edge_NW, 1, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from E to NW, hole.
+ if (!VISITED(quad,2) && Z_SW < 2 && Z_SE < 2 && Z_NE == 2)
+ contour.push_back(start_filled(quad, Edge_E, 2, Hole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from NW to E.
+ if (!VISITED(quad,2) && Z_SW == 2 && Z_SE == 2 && Z_NE < 2)
+ contour.push_back(start_filled(quad, Edge_NW, 2, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Lower-level start from E to NW, hole.
+ if (!VISITED(quad,1) && Z_SW >= 1 && Z_SE >= 1 && Z_NE == 0)
+ contour.push_back(start_filled(quad, Edge_E, 1, Hole, Interior,
+ lower_level, upper_level));
+ }
+ // Possible starts from SW boundary.
+ else if (EXISTS_NE_CORNER(quad)) { // i.e. BOUNDARY_SW
+
+ // Lower-level start from SW boundary into interior.
+ if (!VISITED_CORNER(quad) && Z_NW >= 1 && Z_SE == 0)
+ contour.push_back(start_filled(quad, Edge_SW, 1, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from SW boundary into interior.
+ if (!VISITED_CORNER(quad) && Z_NW < 2 && Z_SE == 2)
+ contour.push_back(start_filled(quad, Edge_SW, 2, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Lower-level start following SW boundary from NW to SE.
+ if (!VISITED_CORNER(quad) && Z_NW <= 1 && Z_SE == 1)
+ contour.push_back(start_filled(quad, Edge_SW, 1, NotHole, Boundary,
+ lower_level, upper_level));
+
+ // Upper-level start following SW boundary from NW to SE.
+ if (!VISITED_CORNER(quad) && Z_NW == 2 && Z_SE == 1)
+ contour.push_back(start_filled(quad, Edge_SW, 2, NotHole, Boundary,
+ lower_level, upper_level));
+ }
+
+ // A full (unmasked) quad can only have a start on the NE corner, i.e. from
+ // N to E (lower level) or E to N (upper level). Any other start will have
+ // already been created in a call to this function for a prior quad so we
+ // don't need to test for it again here.
+ //
+ // The situation is complicated by the possibility that the quad is a
+ // saddle quad, in which case a contour line starting on the N could leave
+ // by either the W or the E. We only need to consider those leaving E.
+ //
+ // A NE corner can also have a N to E or E to N start.
+ if (EXISTS_QUAD(quad) || EXISTS_NE_CORNER(quad)) {
+
+ // Lower-level start from N to E.
+ if (!VISITED(quad,1) && Z_NW == 0 && Z_SE == 0 && Z_NE >= 1 &&
+ (!SADDLE(quad,1) || SADDLE_LEFT(quad,1)))
+ contour.push_back(start_filled(quad, Edge_N, 1, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from E to N, hole.
+ if (!VISITED(quad,2) && Z_NW < 2 && Z_SE < 2 && Z_NE == 2 &&
+ (!SADDLE(quad,2) || !SADDLE_LEFT(quad,2)))
+ contour.push_back(start_filled(quad, Edge_E, 2, Hole, Interior,
+ lower_level, upper_level));
+
+ // Upper-level start from N to E.
+ if (!VISITED(quad,2) && Z_NW == 2 && Z_SE == 2 && Z_NE < 2 &&
+ (!SADDLE(quad,2) || SADDLE_LEFT(quad,2)))
+ contour.push_back(start_filled(quad, Edge_N, 2, NotHole, Interior,
+ lower_level, upper_level));
+
+ // Lower-level start from E to N, hole.
+ if (!VISITED(quad,1) && Z_NW >= 1 && Z_SE >= 1 && Z_NE == 0 &&
+ (!SADDLE(quad,1) || !SADDLE_LEFT(quad,1)))
+ contour.push_back(start_filled(quad, Edge_E, 1, Hole, Interior,
+ lower_level, upper_level));
+
+ // All possible contours passing through the interior of this quad
+ // should have already been created, so assert this.
+ assert((VISITED(quad,1) || get_start_edge(quad, 1) == Edge_None) &&
+ "Found start of contour that should have already been created");
+ assert((VISITED(quad,2) || get_start_edge(quad, 2) == Edge_None) &&
+ "Found start of contour that should have already been created");
+ }
+
+ // Lower-level start following N boundary from E to W, hole.
+ // This is required for an internal masked region which is a hole in a
+ // surrounding contour line.
+ if (BOUNDARY_N(quad) && EXISTS_N_EDGE(quad) &&
+ !VISITED_S(quad+_nx) && Z_NW == 1 && Z_NE == 1)
+ contour.push_back(start_filled(quad, Edge_N, 1, Hole, Boundary,
+ lower_level, upper_level));
+}
+
+ContourLine* QuadContourGenerator::start_filled(
+ long quad,
+ Edge edge,
+ unsigned int start_level_index,
+ HoleOrNot hole_or_not,
+ BoundaryOrInterior boundary_or_interior,
+ const double& lower_level,
+ const double& upper_level)
+{
+ assert(quad >= 0 && quad < _n && "Quad index out of bounds");
+ assert(edge != Edge_None && "Invalid edge");
+ assert((start_level_index == 1 || start_level_index == 2) &&
+ "start level index must be 1 or 2");
+
+ ContourLine* contour_line = new ContourLine(hole_or_not == Hole);
+ if (hole_or_not == Hole) {
+ // Find and set parent ContourLine.
+ ContourLine* parent = _parent_cache.get_parent(quad + 1);
+ assert(parent != 0 && "Failed to find parent ContourLine");
+ contour_line->set_parent(parent);
+ parent->add_child(contour_line);
+ }
+
+ QuadEdge quad_edge(quad, edge);
+ const QuadEdge start_quad_edge(quad_edge);
+ unsigned int level_index = start_level_index;
+
+ // If starts on interior, can only finish on interior.
+ // If starts on boundary, can only finish on boundary.
+
+ while (true) {
+ if (boundary_or_interior == Interior) {
+ double level = (level_index == 1 ? lower_level : upper_level);
+ follow_interior(*contour_line, quad_edge, level_index, level,
+ false, &start_quad_edge, start_level_index, true);
+ }
+ else {
+ level_index = follow_boundary(
+ *contour_line, quad_edge, lower_level,
+ upper_level, level_index, start_quad_edge);
+ }
+
+ if (quad_edge == start_quad_edge && (boundary_or_interior == Boundary ||
+ level_index == start_level_index))
+ break;
+
+ if (boundary_or_interior == Boundary)
+ boundary_or_interior = Interior;
+ else
+ boundary_or_interior = Boundary;
+ }
+
+ return contour_line;
+}
+
+bool QuadContourGenerator::start_line(
+ PyObject* vertices_list, long quad, Edge edge, const double& level)
+{
+ assert(vertices_list != 0 && "Null python vertices list");
+ assert(is_edge_a_boundary(QuadEdge(quad, edge)) &&
+ "QuadEdge is not a boundary");
+
+ QuadEdge quad_edge(quad, edge);
+ ContourLine contour_line(false);
+ follow_interior(contour_line, quad_edge, 1, level, true, 0, 1, false);
+ append_contour_line_to_vertices(contour_line, vertices_list);
+ return VISITED(quad,1);
+}
+
+void QuadContourGenerator::write_cache(bool grid_only) const
+{
+ std::cout << "-----------------------------------------------" << std::endl;
+ for (long quad = 0; quad < _n; ++quad)
+ write_cache_quad(quad, grid_only);
+ std::cout << "-----------------------------------------------" << std::endl;
+}
+
+void QuadContourGenerator::write_cache_quad(long quad, bool grid_only) const
+{
+ long j = quad / _nx;
+ long i = quad - j*_nx;
+ std::cout << quad << ": i=" << i << " j=" << j
+ << " EXISTS=" << EXISTS_QUAD(quad);
+ if (_corner_mask)
+ std::cout << " CORNER=" << EXISTS_SW_CORNER(quad) << EXISTS_SE_CORNER(quad)
+ << EXISTS_NW_CORNER(quad) << EXISTS_NE_CORNER(quad);
+ std::cout << " BNDY=" << (BOUNDARY_S(quad)>0) << (BOUNDARY_W(quad)>0);
+ if (!grid_only) {
+ std::cout << " Z=" << Z_LEVEL(quad)
+ << " SAD=" << (SADDLE(quad,1)>0) << (SADDLE(quad,2)>0)
+ << " LEFT=" << (SADDLE_LEFT(quad,1)>0) << (SADDLE_LEFT(quad,2)>0)
+ << " NW=" << (SADDLE_START_SW(quad,1)>0) << (SADDLE_START_SW(quad,2)>0)
+ << " VIS=" << (VISITED(quad,1)>0) << (VISITED(quad,2)>0)
+ << (VISITED_S(quad)>0) << (VISITED_W(quad)>0)
+ << (VISITED_CORNER(quad)>0);
+ }
+ std::cout << std::endl;
+}
diff --git a/contrib/python/matplotlib/py2/src/_contour.h b/contrib/python/matplotlib/py2/src/_contour.h
new file mode 100644
index 0000000000..e01c3bc732
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_contour.h
@@ -0,0 +1,530 @@
+/*
+ * QuadContourGenerator
+ * --------------------
+ * A QuadContourGenerator generates contours for scalar fields defined on
+ * quadrilateral grids. A single QuadContourGenerator object can create both
+ * line contours (at single levels) and filled contours (between pairs of
+ * levels) for the same field.
+ *
+ * A field to be contoured has nx, ny points in the x- and y-directions
+ * respectively. The quad grid is defined by x and y arrays of shape(ny, nx),
+ * and the field itself is the z array also of shape(ny, nx). There is an
+ * optional boolean mask; if it exists then it also has shape(ny, nx). The
+ * mask applies to grid points rather than quads.
+ *
+ * How quads are masked based on the point mask is determined by the boolean
+ * 'corner_mask' flag. If false then any quad that has one or more of its four
+ * corner points masked is itself masked. If true the behaviour is the same
+ * except that any quad which has exactly one of its four corner points masked
+ * has only the triangular corner (half of the quad) adjacent to that point
+ * masked; the opposite triangular corner has three unmasked points and is not
+ * masked.
+ *
+ * By default the entire domain of nx*ny points is contoured together which can
+ * result in some very long polygons. The alternative is to break up the
+ * domain into subdomains or 'chunks' of smaller size, each of which is
+ * independently contoured. The size of these chunks is controlled by the
+ * 'nchunk' (or 'chunk_size') parameter. Chunking not only results in shorter
+ * polygons but also requires slightly less RAM. It can result in rendering
+ * artifacts though, depending on backend, antialiased flag and alpha value.
+ *
+ * Notation
+ * --------
+ * i and j are array indices in the x- and y-directions respectively. Although
+ * a single element of an array z can be accessed using z[j][i] or z(j,i), it
+ * is often convenient to use the single quad index z[quad], where
+ * quad = i + j*nx
+ * and hence
+ * i = quad % nx
+ * j = quad / nx
+ *
+ * Rather than referring to x- and y-directions, compass directions are used
+ * instead such that W, E, S, N refer to the -x, +x, -y, +y directions
+ * respectively. To move one quad to the E you would therefore add 1 to the
+ * quad index, to move one quad to the N you would add nx to the quad index.
+ *
+ * Cache
+ * -----
+ * Lots of information that is reused during contouring is stored as single
+ * bits in a mesh-sized cache, indexed by quad. Each quad's cache entry stores
+ * information about the quad itself such as if it is masked, and about the
+ * point at the SW corner of the quad, and about the W and S edges. Hence
+ * information about each point and each edge is only stored once in the cache.
+ *
+ * Cache information is divided into two types: that which is constant over the
+ * lifetime of the QuadContourGenerator, and that which changes for each
+ * contouring operation. The former is all grid-specific information such
+ * as quad and corner masks, and which edges are boundaries, either between
+ * masked and non-masked regions or between adjacent chunks. The latter
+ * includes whether points lie above or below the current contour levels, plus
+ * some flags to indicate how the contouring is progressing.
+ *
+ * Line Contours
+ * -------------
+ * A line contour connects points with the same z-value. Each point of such a
+ * contour occurs on an edge of the grid, at a point linearly interpolated to
+ * the contour z-level from the z-values at the end points of the edge. The
+ * direction of a line contour is such that higher values are to the left of
+ * the contour, so any edge that the contour passes through will have a left-
+ * hand end point with z > contour level and a right-hand end point with
+ * z <= contour level.
+ *
+ * Line contours are of two types. Firstly there are open line strips that
+ * start on a boundary, traverse the interior of the domain and end on a
+ * boundary. Secondly there are closed line loops that occur completely within
+ * the interior of the domain and do not touch a boundary.
+ *
+ * The QuadContourGenerator makes two sweeps through the grid to generate line
+ * contours for a particular level. In the first sweep it looks only for start
+ * points that occur on boundaries, and when it finds one it follows the
+ * contour through the interior until it finishes on another boundary edge.
+ * Each quad that is visited by the algorithm has a 'visited' flag set in the
+ * cache to indicate that the quad does not need to be visited again. In the
+ * second sweep all non-visited quads are checked to see if they contain part
+ * of an interior closed loop, and again each time one is found it is followed
+ * through the domain interior until it returns back to its start quad and is
+ * therefore completed.
+ *
+ * The situation is complicated by saddle quads that have two opposite corners
+ * with z >= contour level and the other two corners with z < contour level.
+ * These therefore contain two segments of a line contour, and the visited
+ * flags take account of this by only being set on the second visit. On the
+ * first visit a number of saddle flags are set in the cache to indicate which
+ * one of the two segments has been completed so far.
+ *
+ * Filled Contours
+ * ---------------
+ * Filled contours are produced between two contour levels and are always
+ * closed polygons. They can occur completely within the interior of the
+ * domain without touching a boundary, following either the lower or upper
+ * contour levels. Those on the lower level are exactly like interior line
+ * contours with higher values on the left. Those on the upper level are
+ * reversed such that higher values are on the right.
+ *
+ * Filled contours can also involve a boundary in which case they consist of
+ * one or more sections along a boundary and one or more sections through the
+ * interior. Interior sections can be on either level, and again those on the
+ * upper level have higher values on the right. Boundary sections can remain
+ * on either contour level or switch between the two.
+ *
+ * Once the start of a filled contour is found, the algorithm is similar to
+ * that for line contours in that it follows the contour to its end, which
+ * because filled contours are always closed polygons will be by returning
+ * back to the start. However, because two levels must be considered, each
+ * level has its own set of saddle and visited flags and indeed some extra
+ * visited flags for boundary edges.
+ *
+ * The major complication for filled contours is that some polygons can be
+ * holes (with points ordered clockwise) within other polygons (with points
+ * ordered anticlockwise). When it comes to rendering filled contours each
+ * non-hole polygon must be rendered along with its zero or more contained
+ * holes or the rendering will not be correct. The filled contour finding
+ * algorithm could progress pretty much as the line contour algorithm does,
+ * taking each polygon as it is found, but then at the end there would have to
+ * be an extra step to identify the parent non-hole polygon for each hole.
+ * This is not a particularly onerous task but it does not scale well and can
+ * easily dominate the execution time of the contour finding for even modest
+ * problems. It is much better to identity each hole's parent non-hole during
+ * the sweep algorithm.
+ *
+ * This requirement dictates the order that filled contours are identified. As
+ * the algorithm sweeps up through the grid, every time a polygon passes
+ * through a quad a ParentCache object is updated with the new possible parent.
+ * When a new hole polygon is started, the ParentCache is used to find the
+ * first possible parent in the same quad or to the S of it. Great care is
+ * needed each time a new quad is checked to see if a new polygon should be
+ * started, as a single quad can have multiple polygon starts, e.g. a quad
+ * could be a saddle quad for both lower and upper contour levels, meaning it
+ * has four contour line segments passing through it which could all be from
+ * different polygons. The S-most polygon must be started first, then the next
+ * S-most and so on until the N-most polygon is started in that quad.
+ */
+#ifndef _CONTOUR_H
+#define _CONTOUR_H
+
+#include "src/numpy_cpp.h"
+#include <stdint.h>
+#include <list>
+#include <iostream>
+#include <vector>
+
+
+// Edge of a quad including diagonal edges of masked quads if _corner_mask true.
+typedef enum
+{
+ // Listing values here so easier to check for debug purposes.
+ Edge_None = -1,
+ Edge_E = 0,
+ Edge_N = 1,
+ Edge_W = 2,
+ Edge_S = 3,
+ // The following are only used if _corner_mask is true.
+ Edge_NE = 4,
+ Edge_NW = 5,
+ Edge_SW = 6,
+ Edge_SE = 7
+} Edge;
+
+// Combination of a quad and an edge of that quad.
+// An invalid quad edge has quad of -1.
+struct QuadEdge
+{
+ QuadEdge();
+ QuadEdge(long quad_, Edge edge_);
+ bool operator<(const QuadEdge& other) const;
+ bool operator==(const QuadEdge& other) const;
+ bool operator!=(const QuadEdge& other) const;
+ friend std::ostream& operator<<(std::ostream& os,
+ const QuadEdge& quad_edge);
+
+ long quad;
+ Edge edge;
+};
+
+// 2D point with x,y coordinates.
+struct XY
+{
+ XY();
+ XY(const double& x_, const double& 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;
+};
+
+// 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().
+// A ContourLine is either a hole (points ordered clockwise) or it is not
+// (points ordered anticlockwise). Each hole has a parent ContourLine that is
+// not a hole; each non-hole contains zero or more child holes. A non-hole and
+// its child holes must be rendered together to obtain the correct results.
+class ContourLine : public std::vector<XY>
+{
+public:
+ typedef std::list<ContourLine*> Children;
+
+ ContourLine(bool is_hole);
+ void add_child(ContourLine* child);
+ void clear_parent();
+ const Children& get_children() const;
+ const ContourLine* get_parent() const;
+ ContourLine* get_parent();
+ bool is_hole() const;
+ void push_back(const XY& point);
+ void set_parent(ContourLine* parent);
+ void write() const;
+
+private:
+ bool _is_hole;
+ ContourLine* _parent; // Only set if is_hole, not owned.
+ Children _children; // Only set if !is_hole, not owned.
+};
+
+
+// A Contour is a collection of zero or more ContourLines.
+class Contour : public std::vector<ContourLine*>
+{
+public:
+ Contour();
+ virtual ~Contour();
+ void delete_contour_lines();
+ void write() const;
+};
+
+
+// Single chunk of ContourLine parents, indexed by quad. As a chunk's filled
+// contours are created, the ParentCache is updated each time a ContourLine
+// passes through each quad. When a new ContourLine is created, if it is a
+// hole its parent ContourLine is read from the ParentCache by looking at the
+// start quad, then each quad to the S in turn until a non-zero ContourLine is
+// found.
+class ParentCache
+{
+public:
+ ParentCache(long nx, long x_chunk_points, long y_chunk_points);
+ ContourLine* get_parent(long quad);
+ void set_chunk_starts(long istart, long jstart);
+ void set_parent(long quad, ContourLine& contour_line);
+
+private:
+ long quad_to_index(long quad) const;
+
+ long _nx;
+ long _x_chunk_points, _y_chunk_points; // Number of points not quads.
+ std::vector<ContourLine*> _lines; // Not owned.
+ long _istart, _jstart;
+};
+
+
+// See overview of algorithm at top of file.
+class QuadContourGenerator
+{
+public:
+ typedef numpy::array_view<const double, 2> CoordinateArray;
+ typedef numpy::array_view<const bool, 2> MaskArray;
+
+ // Constructor with optional mask.
+ // x, y, z: double arrays of shape (ny,nx).
+ // mask: boolean array, ether empty (if no mask), or of shape (ny,nx).
+ // corner_mask: flag for different masking behaviour.
+ // chunk_size: 0 for no chunking, or +ve integer for size of chunks that
+ // the domain is subdivided into.
+ QuadContourGenerator(const CoordinateArray& x,
+ const CoordinateArray& y,
+ const CoordinateArray& z,
+ const MaskArray& mask,
+ bool corner_mask,
+ long chunk_size);
+
+ // Destructor.
+ ~QuadContourGenerator();
+
+ // Create and return polygons for a line (i.e. non-filled) contour at the
+ // specified level.
+ PyObject* create_contour(const double& level);
+
+ // Create and return polygons for a filled contour between the two
+ // specified levels.
+ PyObject* create_filled_contour(const double& lower_level,
+ const double& upper_level);
+
+private:
+ // Typedef for following either a boundary of the domain or the interior;
+ // clearer than using a boolean.
+ typedef enum
+ {
+ Boundary,
+ Interior
+ } BoundaryOrInterior;
+
+ // Typedef for direction of movement from one quad to the next.
+ typedef enum
+ {
+ Dir_Right = -1,
+ Dir_Straight = 0,
+ Dir_Left = +1
+ } Dir;
+
+ // Typedef for a polygon being a hole or not; clearer than using a boolean.
+ typedef enum
+ {
+ NotHole,
+ Hole
+ } HoleOrNot;
+
+ // Append a C++ ContourLine to the end of a python list. Used for line
+ // contours where each ContourLine is converted to a separate numpy array
+ // of (x,y) points.
+ // Clears the ContourLine too.
+ void append_contour_line_to_vertices(ContourLine& contour_line,
+ PyObject* vertices_list) const;
+
+ // Append a C++ Contour to the end of two python lists. Used for filled
+ // contours where each non-hole ContourLine and its child holes are
+ // represented by a numpy array of (x,y) points and a second numpy array of
+ // 'kinds' or 'codes' that indicates where the points array is split into
+ // individual polygons.
+ // Clears the Contour too, freeing each ContourLine as soon as possible
+ // for minimum RAM usage.
+ void append_contour_to_vertices_and_codes(Contour& contour,
+ PyObject* vertices_list,
+ PyObject* codes_list) const;
+
+ // Return number of chunks that fit in the specified point_count.
+ long calc_chunk_count(long point_count) const;
+
+ // Return the point on the specified QuadEdge that intersects the specified
+ // level.
+ XY edge_interp(const QuadEdge& quad_edge, const double& level);
+
+ // Follow a contour along a boundary, appending points to the ContourLine
+ // as it progresses. Only called for filled contours. Stops when the
+ // contour leaves the boundary to move into the interior of the domain, or
+ // when the start_quad_edge is reached in which case the ContourLine is a
+ // completed closed loop. Always adds the end point of each boundary edge
+ // to the ContourLine, regardless of whether moving to another boundary
+ // edge or leaving the boundary into the interior. Never adds the start
+ // point of the first boundary edge to the ContourLine.
+ // contour_line: ContourLine to append points to.
+ // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge
+ // that is stopped on.
+ // lower_level: lower contour z-value.
+ // upper_level: upper contour z-value.
+ // level_index: level index started on (1 = lower, 2 = upper level).
+ // start_quad_edge: QuadEdge that the ContourLine started from, which is
+ // used to check if the ContourLine is finished.
+ // Returns the end level_index.
+ unsigned int follow_boundary(ContourLine& contour_line,
+ QuadEdge& quad_edge,
+ const double& lower_level,
+ const double& upper_level,
+ unsigned int level_index,
+ const QuadEdge& start_quad_edge);
+
+ // Follow a contour across the interior of the domain, appending points to
+ // the ContourLine as it progresses. Called for both line and filled
+ // contours. Stops when the contour reaches a boundary or, if the
+ // start_quad_edge is specified, when quad_edge == start_quad_edge and
+ // level_index == start_level_index. Always adds the end point of each
+ // quad traversed to the ContourLine; only adds the start point of the
+ // first quad if want_initial_point flag is true.
+ // contour_line: ContourLine to append points to.
+ // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge
+ // that is stopped on.
+ // level_index: level index started on (1 = lower, 2 = upper level).
+ // level: contour z-value.
+ // want_initial_point: whether want to append the initial point to the
+ // ContourLine or not.
+ // start_quad_edge: the QuadEdge that the ContourLine started from to
+ // check if the ContourLine is finished, or 0 if no check should occur.
+ // start_level_index: the level_index that the ContourLine started from.
+ // set_parents: whether should set ParentCache as it progresses or not.
+ // This is true for filled contours, false for line contours.
+ void follow_interior(ContourLine& contour_line,
+ QuadEdge& quad_edge,
+ unsigned int level_index,
+ const double& level,
+ bool want_initial_point,
+ const QuadEdge* start_quad_edge,
+ unsigned int start_level_index,
+ bool set_parents);
+
+ // Return the index limits of a particular chunk.
+ void get_chunk_limits(long ijchunk,
+ long& ichunk,
+ long& jchunk,
+ long& istart,
+ long& iend,
+ long& jstart,
+ long& jend);
+
+ // Check if a contour starts within the specified corner quad on the
+ // specified level_index, and if so return the start edge. Otherwise
+ // return Edge_None.
+ Edge get_corner_start_edge(long quad, unsigned int level_index) const;
+
+ // Return index of point at start or end of specified QuadEdge, assuming
+ // anticlockwise ordering around non-masked quads.
+ long get_edge_point_index(const QuadEdge& quad_edge, bool start) const;
+
+ // Return the edge to exit a quad from, given the specified entry quad_edge
+ // and direction to move in.
+ Edge get_exit_edge(const QuadEdge& quad_edge, Dir dir) const;
+
+ // Return the (x,y) coordinates of the specified point index.
+ XY get_point_xy(long point) const;
+
+ // Return the z-value of the specified point index.
+ const double& get_point_z(long point) const;
+
+ // Check if a contour starts within the specified non-corner quad on the
+ // specified level_index, and if so return the start edge. Otherwise
+ // return Edge_None.
+ Edge get_quad_start_edge(long quad, unsigned int level_index) const;
+
+ // Check if a contour starts within the specified quad, whether it is a
+ // corner or a full quad, and if so return the start edge. Otherwise
+ // return Edge_None.
+ Edge get_start_edge(long quad, unsigned int level_index) const;
+
+ // Initialise the cache to contain grid information that is constant
+ // across the lifetime of this object, i.e. does not vary between calls to
+ // create_contour() and create_filled_contour().
+ void init_cache_grid(const MaskArray& mask);
+
+ // Initialise the cache with information that is specific to contouring the
+ // specified two levels. The levels are the same for contour lines,
+ // different for filled contours.
+ void init_cache_levels(const double& lower_level,
+ const double& upper_level);
+
+ // Return the (x,y) point at which the level intersects the line connecting
+ // the two specified point indices.
+ XY interp(long point1, long point2, const double& level) const;
+
+ // Return true if the specified QuadEdge is a boundary, i.e. is either an
+ // edge between a masked and non-masked quad/corner or is a chunk boundary.
+ bool is_edge_a_boundary(const QuadEdge& quad_edge) const;
+
+ // Follow a boundary from one QuadEdge to the next in an anticlockwise
+ // manner around the non-masked region.
+ void move_to_next_boundary_edge(QuadEdge& quad_edge) const;
+
+ // Move from the quad specified by quad_edge.quad to the neighbouring quad
+ // by crossing the edge specified by quad_edge.edge.
+ void move_to_next_quad(QuadEdge& quad_edge) const;
+
+ // Check for filled contours starting within the specified quad and
+ // complete any that are found, appending them to the specified Contour.
+ void single_quad_filled(Contour& contour,
+ long quad,
+ const double& lower_level,
+ const double& upper_level);
+
+ // Start and complete a filled contour line.
+ // quad: index of quad to start ContourLine in.
+ // edge: edge of quad to start ContourLine from.
+ // start_level_index: the level_index that the ContourLine starts from.
+ // hole_or_not: whether the ContourLine is a hole or not.
+ // boundary_or_interior: whether the ContourLine starts on a boundary or
+ // the interior.
+ // lower_level: lower contour z-value.
+ // upper_level: upper contour z-value.
+ // Returns newly created ContourLine.
+ ContourLine* start_filled(long quad,
+ Edge edge,
+ unsigned int start_level_index,
+ HoleOrNot hole_or_not,
+ BoundaryOrInterior boundary_or_interior,
+ const double& lower_level,
+ const double& upper_level);
+
+ // Start and complete a line contour that both starts and end on a
+ // boundary, traversing the interior of the domain.
+ // vertices_list: Python list that the ContourLine should be appended to.
+ // quad: index of quad to start ContourLine in.
+ // edge: boundary edge to start ContourLine from.
+ // level: contour z-value.
+ // Returns true if the start quad does not need to be visited again, i.e.
+ // VISITED(quad,1).
+ bool start_line(PyObject* vertices_list,
+ long quad,
+ Edge edge,
+ const double& level);
+
+ // Debug function that writes the cache status to stdout.
+ void write_cache(bool grid_only = false) const;
+
+ // Debug function that writes that cache status for a single quad to
+ // stdout.
+ void write_cache_quad(long quad, bool grid_only) const;
+
+
+
+ // Note that mask is not stored as once it has been used to initialise the
+ // cache it is no longer needed.
+ CoordinateArray _x, _y, _z;
+ long _nx, _ny; // Number of points in each direction.
+ long _n; // Total number of points (and hence quads).
+
+ bool _corner_mask;
+ long _chunk_size; // Number of quads per chunk (not points).
+ // Always > 0, unlike python nchunk which is 0
+ // for no chunking.
+
+ long _nxchunk, _nychunk; // Number of chunks in each direction.
+ long _chunk_count; // Total number of chunks.
+
+ typedef uint32_t CacheItem;
+ CacheItem* _cache;
+
+ ParentCache _parent_cache; // On W quad sides.
+};
+
+#endif // _CONTOUR_H
diff --git a/contrib/python/matplotlib/py2/src/_contour_wrapper.cpp b/contrib/python/matplotlib/py2/src/_contour_wrapper.cpp
new file mode 100644
index 0000000000..eedc8a1aec
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_contour_wrapper.cpp
@@ -0,0 +1,203 @@
+#include "src/_contour.h"
+#include "src/mplutils.h"
+#include "src/py_exceptions.h"
+
+/* QuadContourGenerator */
+
+typedef struct
+{
+ PyObject_HEAD
+ QuadContourGenerator* ptr;
+} PyQuadContourGenerator;
+
+static PyTypeObject PyQuadContourGeneratorType;
+
+static PyObject* PyQuadContourGenerator_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
+{
+ PyQuadContourGenerator* self;
+ self = (PyQuadContourGenerator*)type->tp_alloc(type, 0);
+ self->ptr = NULL;
+ return (PyObject*)self;
+}
+
+const char* PyQuadContourGenerator_init__doc__ =
+ "QuadContourGenerator(x, y, z, mask, corner_mask, chunk_size)\n"
+ "\n"
+ "Create a new C++ QuadContourGenerator object\n";
+
+static int PyQuadContourGenerator_init(PyQuadContourGenerator* self, PyObject* args, PyObject* kwds)
+{
+ QuadContourGenerator::CoordinateArray x, y, z;
+ QuadContourGenerator::MaskArray mask;
+ int corner_mask;
+ long chunk_size;
+
+ if (!PyArg_ParseTuple(args, "O&O&O&O&il",
+ &x.converter_contiguous, &x,
+ &y.converter_contiguous, &y,
+ &z.converter_contiguous, &z,
+ &mask.converter_contiguous, &mask,
+ &corner_mask,
+ &chunk_size)) {
+ return -1;
+ }
+
+ if (x.empty() || y.empty() || z.empty() ||
+ y.dim(0) != x.dim(0) || z.dim(0) != x.dim(0) ||
+ y.dim(1) != x.dim(1) || z.dim(1) != x.dim(1)) {
+ PyErr_SetString(PyExc_ValueError,
+ "x, y and z must all be 2D arrays with the same dimensions");
+ return -1;
+ }
+
+ if (z.dim(0) < 2 || z.dim(1) < 2) {
+ PyErr_SetString(PyExc_ValueError,
+ "x, y and z must all be at least 2x2 arrays");
+ return -1;
+ }
+
+ // Mask array is optional, if set must be same size as other arrays.
+ if (!mask.empty() && (mask.dim(0) != x.dim(0) || mask.dim(1) != x.dim(1))) {
+ PyErr_SetString(PyExc_ValueError,
+ "If mask is set it must be a 2D array with the same dimensions as x.");
+ return -1;
+ }
+
+ CALL_CPP_INIT("QuadContourGenerator",
+ (self->ptr = new QuadContourGenerator(
+ x, y, z, mask, corner_mask, chunk_size)));
+ return 0;
+}
+
+static void PyQuadContourGenerator_dealloc(PyQuadContourGenerator* self)
+{
+ delete self->ptr;
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+const char* PyQuadContourGenerator_create_contour__doc__ =
+ "create_contour(level)\n"
+ "\n"
+ "Create and return a non-filled contour.";
+
+static PyObject* PyQuadContourGenerator_create_contour(PyQuadContourGenerator* self, PyObject* args, PyObject* kwds)
+{
+ double level;
+ if (!PyArg_ParseTuple(args, "d:create_contour", &level)) {
+ return NULL;
+ }
+
+ PyObject* result;
+ CALL_CPP("create_contour", (result = self->ptr->create_contour(level)));
+ return result;
+}
+
+const char* PyQuadContourGenerator_create_filled_contour__doc__ =
+ "create_filled_contour(lower_level, upper_level)\n"
+ "\n"
+ "Create and return a filled contour";
+
+static PyObject* PyQuadContourGenerator_create_filled_contour(PyQuadContourGenerator* self, PyObject* args, PyObject* kwds)
+{
+ double lower_level, upper_level;
+ if (!PyArg_ParseTuple(args, "dd:create_filled_contour",
+ &lower_level, &upper_level)) {
+ return NULL;
+ }
+
+ if (lower_level >= upper_level)
+ {
+ PyErr_SetString(PyExc_ValueError,
+ "filled contour levels must be increasing");
+ return NULL;
+ }
+
+ PyObject* result;
+ CALL_CPP("create_filled_contour",
+ (result = self->ptr->create_filled_contour(lower_level,
+ upper_level)));
+ return result;
+}
+
+static PyTypeObject* PyQuadContourGenerator_init_type(PyObject* m, PyTypeObject* type)
+{
+ static PyMethodDef methods[] = {
+ {"create_contour", (PyCFunction)PyQuadContourGenerator_create_contour, METH_VARARGS, PyQuadContourGenerator_create_contour__doc__},
+ {"create_filled_contour", (PyCFunction)PyQuadContourGenerator_create_filled_contour, METH_VARARGS, PyQuadContourGenerator_create_filled_contour__doc__},
+ {NULL}
+ };
+
+ memset(type, 0, sizeof(PyTypeObject));
+ type->tp_name = "matplotlib.QuadContourGenerator";
+ type->tp_doc = PyQuadContourGenerator_init__doc__;
+ type->tp_basicsize = sizeof(PyQuadContourGenerator);
+ type->tp_dealloc = (destructor)PyQuadContourGenerator_dealloc;
+ type->tp_flags = Py_TPFLAGS_DEFAULT;
+ type->tp_methods = methods;
+ type->tp_new = PyQuadContourGenerator_new;
+ type->tp_init = (initproc)PyQuadContourGenerator_init;
+
+ if (PyType_Ready(type) < 0) {
+ return NULL;
+ }
+
+ if (PyModule_AddObject(m, "QuadContourGenerator", (PyObject*)type)) {
+ return NULL;
+ }
+
+ return type;
+}
+
+
+/* Module */
+
+extern "C" {
+
+#if PY3K
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_contour",
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+#define INITERROR return NULL
+
+PyMODINIT_FUNC PyInit__contour(void)
+
+#else
+#define INITERROR return
+
+PyMODINIT_FUNC init_contour(void)
+#endif
+
+{
+ PyObject *m;
+
+#if PY3K
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("_contour", NULL, NULL);
+#endif
+
+ if (m == NULL) {
+ INITERROR;
+ }
+
+ if (!PyQuadContourGenerator_init_type(m, &PyQuadContourGeneratorType)) {
+ INITERROR;
+ }
+
+ import_array();
+
+#if PY3K
+ return m;
+#endif
+}
+
+} // extern "C"
diff --git a/contrib/python/matplotlib/py2/src/_image.cpp b/contrib/python/matplotlib/py2/src/_image.cpp
new file mode 100644
index 0000000000..8fc386fccb
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_image.cpp
@@ -0,0 +1,175 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#define NO_IMPORT_ARRAY
+
+#include <math.h>
+
+// utilities for irregular grids
+void _bin_indices_middle(
+ unsigned int *irows, int nrows, const float *ys1, unsigned long ny, float dy, float y_min)
+{
+ int i, j, j_last;
+ unsigned int *rowstart = irows;
+ const float *ys2 = ys1 + 1;
+ const float *yl = ys1 + ny;
+ float yo = y_min + dy / 2.0;
+ float ym = 0.5f * (*ys1 + *ys2);
+ // y/rows
+ j = 0;
+ j_last = j;
+ for (i = 0; i < nrows; i++, yo += dy, rowstart++) {
+ while (ys2 != yl && yo > ym) {
+ ys1 = ys2;
+ ys2 = ys1 + 1;
+ ym = 0.5f * (*ys1 + *ys2);
+ j++;
+ }
+ *rowstart = j - j_last;
+ j_last = j;
+ }
+}
+
+void _bin_indices_middle_linear(float *arows,
+ unsigned int *irows,
+ int nrows,
+ const float *y,
+ unsigned long ny,
+ float dy,
+ float y_min)
+{
+ int i;
+ int ii = 0;
+ int iilast = (int)ny - 1;
+ float sc = 1 / dy;
+ int iy0 = (int)floor(sc * (y[ii] - y_min));
+ int iy1 = (int)floor(sc * (y[ii + 1] - y_min));
+ float invgap = 1.0f / (iy1 - iy0);
+ for (i = 0; i < nrows && i <= iy0; i++) {
+ irows[i] = 0;
+ arows[i] = 1.0;
+ }
+ for (; i < nrows; i++) {
+ while (i > iy1 && ii < iilast) {
+ ii++;
+ iy0 = iy1;
+ iy1 = (int)floor(sc * (y[ii + 1] - y_min));
+ invgap = 1.0f / (iy1 - iy0);
+ }
+ if (i >= iy0 && i <= iy1) {
+ irows[i] = ii;
+ arows[i] = (iy1 - i) * invgap;
+ } else
+ break;
+ }
+ for (; i < nrows; i++) {
+ irows[i] = iilast - 1;
+ arows[i] = 0.0;
+ }
+}
+
+void _bin_indices(int *irows, int nrows, const double *y, unsigned long ny, double sc, double offs)
+{
+ int i;
+ if (sc * (y[ny - 1] - y[0]) > 0) {
+ int ii = 0;
+ int iilast = (int)ny - 1;
+ int iy0 = (int)floor(sc * (y[ii] - offs));
+ int iy1 = (int)floor(sc * (y[ii + 1] - offs));
+ for (i = 0; i < nrows && i < iy0; i++) {
+ irows[i] = -1;
+ }
+ for (; i < nrows; i++) {
+ while (i > iy1 && ii < iilast) {
+ ii++;
+ iy0 = iy1;
+ iy1 = (int)floor(sc * (y[ii + 1] - offs));
+ }
+ if (i >= iy0 && i <= iy1)
+ irows[i] = ii;
+ else
+ break;
+ }
+ for (; i < nrows; i++) {
+ irows[i] = -1;
+ }
+ } else {
+ int iilast = (int)ny - 1;
+ int ii = iilast;
+ int iy0 = (int)floor(sc * (y[ii] - offs));
+ int iy1 = (int)floor(sc * (y[ii - 1] - offs));
+ for (i = 0; i < nrows && i < iy0; i++) {
+ irows[i] = -1;
+ }
+ for (; i < nrows; i++) {
+ while (i > iy1 && ii > 1) {
+ ii--;
+ iy0 = iy1;
+ iy1 = (int)floor(sc * (y[ii - 1] - offs));
+ }
+ if (i >= iy0 && i <= iy1)
+ irows[i] = ii - 1;
+ else
+ break;
+ }
+ for (; i < nrows; i++) {
+ irows[i] = -1;
+ }
+ }
+}
+
+void _bin_indices_linear(
+ float *arows, int *irows, int nrows, double *y, unsigned long ny, double sc, double offs)
+{
+ int i;
+ if (sc * (y[ny - 1] - y[0]) > 0) {
+ int ii = 0;
+ int iilast = (int)ny - 1;
+ int iy0 = (int)floor(sc * (y[ii] - offs));
+ int iy1 = (int)floor(sc * (y[ii + 1] - offs));
+ float invgap = 1.0 / (iy1 - iy0);
+ for (i = 0; i < nrows && i < iy0; i++) {
+ irows[i] = -1;
+ }
+ for (; i < nrows; i++) {
+ while (i > iy1 && ii < iilast) {
+ ii++;
+ iy0 = iy1;
+ iy1 = (int)floor(sc * (y[ii + 1] - offs));
+ invgap = 1.0 / (iy1 - iy0);
+ }
+ if (i >= iy0 && i <= iy1) {
+ irows[i] = ii;
+ arows[i] = (iy1 - i) * invgap;
+ } else
+ break;
+ }
+ for (; i < nrows; i++) {
+ irows[i] = -1;
+ }
+ } else {
+ int iilast = (int)ny - 1;
+ int ii = iilast;
+ int iy0 = (int)floor(sc * (y[ii] - offs));
+ int iy1 = (int)floor(sc * (y[ii - 1] - offs));
+ float invgap = 1.0 / (iy1 - iy0);
+ for (i = 0; i < nrows && i < iy0; i++) {
+ irows[i] = -1;
+ }
+ for (; i < nrows; i++) {
+ while (i > iy1 && ii > 1) {
+ ii--;
+ iy0 = iy1;
+ iy1 = (int)floor(sc * (y[ii - 1] - offs));
+ invgap = 1.0 / (iy1 - iy0);
+ }
+ if (i >= iy0 && i <= iy1) {
+ irows[i] = ii - 1;
+ arows[i] = (i - iy0) * invgap;
+ } else
+ break;
+ }
+ for (; i < nrows; i++) {
+ irows[i] = -1;
+ }
+ }
+}
diff --git a/contrib/python/matplotlib/py2/src/_image.h b/contrib/python/matplotlib/py2/src/_image.h
new file mode 100644
index 0000000000..629714d2ec
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_image.h
@@ -0,0 +1,200 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* image.h
+ *
+ */
+
+#ifndef _IMAGE_H
+#define _IMAGE_H
+
+#include <vector>
+
+
+// utilities for irregular grids
+void _bin_indices_middle(
+ unsigned int *irows, int nrows, const float *ys1, unsigned long ny, float dy, float y_min);
+void _bin_indices_middle_linear(float *arows,
+ unsigned int *irows,
+ int nrows,
+ const float *y,
+ unsigned long ny,
+ float dy,
+ float y_min);
+void _bin_indices(int *irows, int nrows, const double *y, unsigned long ny, double sc, double offs);
+void _bin_indices_linear(
+ float *arows, int *irows, int nrows, double *y, unsigned long ny, double sc, double offs);
+
+template <class CoordinateArray, class ColorArray, class OutputArray>
+void pcolor(CoordinateArray &x,
+ CoordinateArray &y,
+ ColorArray &d,
+ unsigned int rows,
+ unsigned int cols,
+ float bounds[4],
+ int interpolation,
+ OutputArray &out)
+{
+ if (rows >= 32768 || cols >= 32768) {
+ throw std::runtime_error("rows and cols must both be less than 32768");
+ }
+
+ float x_min = bounds[0];
+ float x_max = bounds[1];
+ float y_min = bounds[2];
+ float y_max = bounds[3];
+ float width = x_max - x_min;
+ float height = y_max - y_min;
+ float dx = width / ((float)cols);
+ float dy = height / ((float)rows);
+
+ // Check we have something to output to
+ if (rows == 0 || cols == 0) {
+ throw std::runtime_error("Cannot scale to zero size");
+ }
+
+ if (d.dim(2) != 4) {
+ throw std::runtime_error("data must be in RGBA format");
+ }
+
+ // Check dimensions match
+ unsigned long nx = x.dim(0);
+ unsigned long ny = y.dim(0);
+ if (nx != (unsigned long)d.dim(1) || ny != (unsigned long)d.dim(0)) {
+ throw std::runtime_error("data and axis dimensions do not match");
+ }
+
+ // Allocate memory for pointer arrays
+ std::vector<unsigned int> rowstarts(rows);
+ std::vector<unsigned int> colstarts(cols);
+
+ // Calculate the pointer arrays to map input x to output x
+ unsigned int i, j;
+ unsigned int *colstart = &colstarts[0];
+ unsigned int *rowstart = &rowstarts[0];
+ const float *xs1 = x.data();
+ const float *ys1 = y.data();
+
+ // Copy data to output buffer
+ const unsigned char *start;
+ const unsigned char *inposition;
+ size_t inrowsize = nx * 4;
+ size_t rowsize = cols * 4;
+ unsigned char *position = (unsigned char *)out.data();
+ unsigned char *oldposition = NULL;
+ start = d.data();
+
+ if (interpolation == NEAREST) {
+ _bin_indices_middle(colstart, cols, xs1, nx, dx, x_min);
+ _bin_indices_middle(rowstart, rows, ys1, ny, dy, y_min);
+ for (i = 0; i < rows; i++, rowstart++) {
+ if (i > 0 && *rowstart == 0) {
+ memcpy(position, oldposition, rowsize * sizeof(unsigned char));
+ oldposition = position;
+ position += rowsize;
+ } else {
+ oldposition = position;
+ start += *rowstart * inrowsize;
+ inposition = start;
+ for (j = 0, colstart = &colstarts[0]; j < cols; j++, position += 4, colstart++) {
+ inposition += *colstart * 4;
+ memcpy(position, inposition, 4 * sizeof(unsigned char));
+ }
+ }
+ }
+ } else if (interpolation == BILINEAR) {
+ std::vector<float> acols(cols);
+ std::vector<float> arows(rows);
+
+ _bin_indices_middle_linear(&acols[0], colstart, cols, xs1, nx, dx, x_min);
+ _bin_indices_middle_linear(&arows[0], rowstart, rows, ys1, ny, dy, y_min);
+ double a00, a01, a10, a11, alpha, beta;
+
+ // Copy data to output buffer
+ for (i = 0; i < rows; i++) {
+ for (j = 0; j < cols; j++) {
+ alpha = arows[i];
+ beta = acols[j];
+
+ a00 = alpha * beta;
+ a01 = alpha * (1.0 - beta);
+ a10 = (1.0 - alpha) * beta;
+ a11 = 1.0 - a00 - a01 - a10;
+
+ for (size_t k = 0; k < 4; ++k) {
+ position[k] =
+ d(rowstart[i], colstart[j], k) * a00 +
+ d(rowstart[i], colstart[j] + 1, k) * a01 +
+ d(rowstart[i] + 1, colstart[j], k) * a10 +
+ d(rowstart[i] + 1, colstart[j] + 1, k) * a11;
+ }
+ position += 4;
+ }
+ }
+ }
+}
+
+template <class CoordinateArray, class ColorArray, class Color, class OutputArray>
+void pcolor2(CoordinateArray &x,
+ CoordinateArray &y,
+ ColorArray &d,
+ unsigned int rows,
+ unsigned int cols,
+ float bounds[4],
+ Color &bg,
+ OutputArray &out)
+{
+ double x_left = bounds[0];
+ double x_right = bounds[1];
+ double y_bot = bounds[2];
+ double y_top = bounds[3];
+
+ // Check we have something to output to
+ if (rows == 0 || cols == 0) {
+ throw std::runtime_error("rows or cols is zero; there are no pixels");
+ }
+
+ if (d.dim(2) != 4) {
+ throw std::runtime_error("data must be in RGBA format");
+ }
+
+ // Check dimensions match
+ unsigned long nx = x.dim(0);
+ unsigned long ny = y.dim(0);
+ if (nx != (unsigned long)d.dim(1) + 1 || ny != (unsigned long)d.dim(0) + 1) {
+ throw std::runtime_error("data and axis bin boundary dimensions are incompatible");
+ }
+
+ if (bg.dim(0) != 4) {
+ throw std::runtime_error("bg must be in RGBA format");
+ }
+
+ std::vector<int> irows(rows);
+ std::vector<int> jcols(cols);
+
+ // Calculate the pointer arrays to map input x to output x
+ size_t i, j;
+ const double *x0 = x.data();
+ const double *y0 = y.data();
+ double sx = cols / (x_right - x_left);
+ double sy = rows / (y_top - y_bot);
+ _bin_indices(&jcols[0], cols, x0, nx, sx, x_left);
+ _bin_indices(&irows[0], rows, y0, ny, sy, y_bot);
+
+ // Copy data to output buffer
+ unsigned char *position = (unsigned char *)out.data();
+
+ for (i = 0; i < rows; i++) {
+ for (j = 0; j < cols; j++) {
+ if (irows[i] == -1 || jcols[j] == -1) {
+ memcpy(position, (const unsigned char *)bg.data(), 4 * sizeof(unsigned char));
+ } else {
+ for (size_t k = 0; k < 4; ++k) {
+ position[k] = d(irows[i], jcols[j], k);
+ }
+ }
+ position += 4;
+ }
+ }
+}
+
+#endif
diff --git a/contrib/python/matplotlib/py2/src/_image_resample.h b/contrib/python/matplotlib/py2/src/_image_resample.h
new file mode 100644
index 0000000000..86cbef0324
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_image_resample.h
@@ -0,0 +1,1013 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef RESAMPLE_H
+#define 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://www.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://www.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;
+
+
+template <typename T>
+class type_mapping;
+
+
+template <> class type_mapping<agg::rgba8>
+{
+ public:
+ typedef agg::rgba8 color_type;
+ typedef fixed_blender_rgba_plain<color_type, agg::order_rgba> blender_type;
+ typedef fixed_blender_rgba_pre<color_type, agg::order_rgba> pre_blender_type;
+ typedef agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef agg::pixfmt_alpha_blend_rgba<pre_blender_type, agg::rendering_buffer> pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_rgba_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_rgba<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_rgba_nn<A, B> type;
+ };
+};
+
+
+template <> class type_mapping<agg::rgba16>
+{
+ public:
+ typedef agg::rgba16 color_type;
+ typedef fixed_blender_rgba_plain<color_type, agg::order_rgba> blender_type;
+ typedef fixed_blender_rgba_pre<color_type, agg::order_rgba> pre_blender_type;
+ typedef agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef agg::pixfmt_alpha_blend_rgba<pre_blender_type, agg::rendering_buffer> pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_rgba_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_rgba<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_rgba_nn<A, B> type;
+ };
+};
+
+
+template <> class type_mapping<agg::rgba32>
+{
+ public:
+ typedef agg::rgba32 color_type;
+ typedef agg::blender_rgba_plain<color_type, agg::order_rgba> blender_type;
+ typedef agg::blender_rgba_pre<color_type, agg::order_rgba> pre_blender_type;
+ typedef agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef agg::pixfmt_alpha_blend_rgba<pre_blender_type, agg::rendering_buffer> pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_rgba_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_rgba<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_rgba_nn<A, B> type;
+ };
+};
+
+
+template <> class type_mapping<agg::rgba64>
+{
+ public:
+ typedef agg::rgba64 color_type;
+ typedef agg::blender_rgba_plain<color_type, agg::order_rgba> blender_type;
+ typedef agg::blender_rgba_pre<color_type, agg::order_rgba> pre_blender_type;
+ typedef agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef agg::pixfmt_alpha_blend_rgba<pre_blender_type, agg::rendering_buffer> pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_rgba_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_rgba<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_rgba_nn<A, B> type;
+ };
+};
+
+
+template <> class type_mapping<double>
+{
+ public:
+ typedef agg::gray64 color_type;
+ typedef agg::blender_gray<color_type> blender_type;
+ typedef agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef pixfmt_type pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_gray_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_gray<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_gray_nn<A, B> type;
+ };
+};
+
+
+template <> class type_mapping<float>
+{
+ public:
+ typedef agg::gray32 color_type;
+ typedef agg::blender_gray<color_type> blender_type;
+ typedef agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef pixfmt_type pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_gray_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_gray<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_gray_nn<A, B> type;
+ };
+};
+
+
+template <> class type_mapping<unsigned short>
+{
+ public:
+ typedef agg::gray16 color_type;
+ typedef agg::blender_gray<color_type> blender_type;
+ typedef agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef pixfmt_type pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_gray_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_gray<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_gray_nn<A, B> type;
+ };
+};
+
+
+template <> class type_mapping<unsigned char>
+{
+ public:
+ typedef agg::gray8 color_type;
+ typedef agg::blender_gray<color_type> blender_type;
+ typedef agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer> pixfmt_type;
+ typedef pixfmt_type pixfmt_pre_type;
+
+ template <typename A>
+ struct span_gen_affine_type
+ {
+ typedef agg::span_image_resample_gray_affine<A> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_filter_type
+ {
+ typedef agg::span_image_filter_gray<A, B> type;
+ };
+
+ template <typename A, typename B>
+ struct span_gen_nn_type
+ {
+ typedef agg::span_image_filter_gray_nn<A, B> type;
+ };
+};
+
+
+
+template<class 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;
+ double norm;
+ double radius;
+ double alpha;
+};
+
+
+static void get_filter(const resample_params_t &params,
+ 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<class T>
+void resample(
+ const T *input, int in_width, int in_height,
+ T *output, int out_width, int out_height,
+ resample_params_t &params)
+{
+ typedef type_mapping<T> type_mapping_t;
+
+ typedef typename type_mapping_t::pixfmt_type input_pixfmt_t;
+ typedef typename type_mapping_t::pixfmt_type output_pixfmt_t;
+
+ typedef agg::renderer_base<output_pixfmt_t> renderer_t;
+ typedef agg::rasterizer_scanline_aa<agg::rasterizer_sl_clip_dbl> rasterizer_t;
+
+ typedef agg::wrap_mode_reflect reflect_t;
+ typedef agg::image_accessor_wrap<input_pixfmt_t, reflect_t, reflect_t> image_accessor_t;
+
+ typedef agg::span_allocator<typename type_mapping_t::color_type> span_alloc_t;
+ typedef span_conv_alpha<typename type_mapping_t::color_type> span_conv_alpha_t;
+
+ typedef agg::span_interpolator_linear<> affine_interpolator_t;
+ typedef agg::span_interpolator_adaptor<agg::span_interpolator_linear<>, lookup_distortion>
+ arbitrary_interpolator_t;
+
+ 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 * sizeof(T));
+ 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 * sizeof(T));
+ 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) {
+ typedef typename type_mapping_t::template span_gen_nn_type<image_accessor_t, affine_interpolator_t>::type span_gen_t;
+ typedef agg::span_converter<span_gen_t, span_conv_alpha_t> span_conv_t;
+ typedef agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t> nn_renderer_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 {
+ typedef typename type_mapping_t::template span_gen_nn_type<image_accessor_t, arbitrary_interpolator_t>::type span_gen_t;
+ typedef agg::span_converter<span_gen_t, span_conv_alpha_t> span_conv_t;
+ typedef agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t> nn_renderer_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) {
+ typedef typename type_mapping_t::template span_gen_affine_type<image_accessor_t>::type span_gen_t;
+ typedef agg::span_converter<span_gen_t, span_conv_alpha_t> span_conv_t;
+ typedef agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t> int_renderer_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 {
+ typedef typename type_mapping_t::template span_gen_filter_type<image_accessor_t, arbitrary_interpolator_t>::type span_gen_t;
+ typedef agg::span_converter<span_gen_t, span_conv_alpha_t> span_conv_t;
+ typedef agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t> int_renderer_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 /* RESAMPLE_H */
diff --git a/contrib/python/matplotlib/py2/src/_image_wrapper.cpp b/contrib/python/matplotlib/py2/src/_image_wrapper.cpp
new file mode 100644
index 0000000000..ee0bfe84c7
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_image_wrapper.cpp
@@ -0,0 +1,510 @@
+#include "mplutils.h"
+#include "_image_resample.h"
+#include "_image.h"
+#include "py_converters.h"
+
+
+#ifndef NPY_1_7_API_VERSION
+#define NPY_ARRAY_C_CONTIGUOUS NPY_C_CONTIGUOUS
+#endif
+
+
+/**********************************************************************
+ * Free functions
+ * */
+
+const char* image_resample__doc__ =
+"resample(input_array, output_array, matrix, interpolation=NEAREST, alpha=1.0, norm=0, radius=1)\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 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 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\n"
+" array.\n\n"
+
+"interpolation : int, optional\n"
+" The interpolation method. Must be one of the following constants\n"
+" defined in this module:\n\n"
+
+" NEAREST (default), 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, optional\n"
+" The level of transparency to apply. 1.0 is completely opaque.\n"
+" 0.0 is completely transparent.\n\n"
+
+"norm : float, optional\n"
+" The norm for the interpolation function. Default is 0.\n\n"
+
+"radius: float, optional\n"
+" The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n"
+" Default is 1.\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, (char *)"inverted", (char *)"", 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, (char *)"transform", (char *)"O",
+ (char *)input_mesh.pyobj(), NULL);
+
+ 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_array = NULL;
+ PyObject *py_output_array = NULL;
+ PyObject *py_transform = NULL;
+ resample_params_t params;
+ int resample_;
+
+ PyArrayObject *input_array = NULL;
+ PyArrayObject *output_array = NULL;
+ PyArrayObject *transform_mesh_array = NULL;
+
+ params.transform_mesh = NULL;
+
+ const char *kwlist[] = {
+ "input_array", "output_array", "transform", "interpolation",
+ "resample", "alpha", "norm", "radius", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwargs, "OOO|iiddd:resample", (char **)kwlist,
+ &py_input_array, &py_output_array, &py_transform,
+ &params.interpolation, &resample_, &params.alpha, &params.norm,
+ &params.radius)) {
+ return NULL;
+ }
+
+ if (params.interpolation < 0 || params.interpolation >= _n_interpolation) {
+ PyErr_Format(PyExc_ValueError, "invalid interpolation value %d",
+ params.interpolation);
+ goto error;
+ }
+
+ params.resample = (resample_ != 0);
+
+ input_array = (PyArrayObject *)PyArray_FromAny(
+ py_input_array, NULL, 2, 3, NPY_ARRAY_C_CONTIGUOUS, NULL);
+ if (input_array == NULL) {
+ goto error;
+ }
+
+ output_array = (PyArrayObject *)PyArray_FromAny(
+ py_output_array, NULL, 2, 3, NPY_ARRAY_C_CONTIGUOUS, NULL);
+ if (output_array == NULL) {
+ 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 == NULL) {
+ 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, &params.affine)) {
+ goto error;
+ }
+ params.is_affine = true;
+ } else {
+ transform_mesh_array = _get_transform_mesh(
+ py_transform, PyArray_DIMS(output_array));
+ if (transform_mesh_array == NULL) {
+ goto error;
+ }
+ params.transform_mesh = (double *)PyArray_DATA(transform_mesh_array);
+ params.is_affine = false;
+ }
+ }
+
+ if (PyArray_NDIM(input_array) != PyArray_NDIM(output_array)) {
+ PyErr_Format(
+ PyExc_ValueError,
+ "Mismatched number of dimensions. Got %d and %d.",
+ PyArray_NDIM(input_array), PyArray_NDIM(output_array));
+ goto error;
+ }
+
+ if (PyArray_TYPE(input_array) != PyArray_TYPE(output_array)) {
+ PyErr_SetString(PyExc_ValueError, "Mismatched types");
+ goto error;
+ }
+
+ if (PyArray_NDIM(input_array) == 3) {
+ if (PyArray_DIM(output_array, 2) != 4) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "Output array must be RGBA");
+ goto error;
+ }
+
+ if (PyArray_DIM(input_array, 2) == 4) {
+ switch(PyArray_TYPE(input_array)) {
+ case NPY_BYTE:
+ case NPY_UINT8:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (agg::rgba8 *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (agg::rgba8 *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ case NPY_UINT16:
+ case NPY_INT16:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (agg::rgba16 *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (agg::rgba16 *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ case NPY_FLOAT32:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (agg::rgba32 *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (agg::rgba32 *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ case NPY_FLOAT64:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (agg::rgba64 *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (agg::rgba64 *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ default:
+ PyErr_SetString(
+ PyExc_ValueError,
+ "3-dimensional arrays must be of dtype unsigned byte, "
+ "unsigned short, float32 or float64");
+ goto error;
+ }
+ } else {
+ PyErr_Format(
+ PyExc_ValueError,
+ "If 3-dimensional, array must be RGBA. Got %" NPY_INTP_FMT " planes.",
+ PyArray_DIM(input_array, 2));
+ goto error;
+ }
+ } else { // NDIM == 2
+ switch (PyArray_TYPE(input_array)) {
+ case NPY_DOUBLE:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (double *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (double *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ case NPY_FLOAT:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (float *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (float *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ case NPY_UINT8:
+ case NPY_BYTE:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (unsigned char *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (unsigned char *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ case NPY_UINT16:
+ case NPY_INT16:
+ Py_BEGIN_ALLOW_THREADS
+ resample(
+ (unsigned short *)PyArray_DATA(input_array),
+ PyArray_DIM(input_array, 1),
+ PyArray_DIM(input_array, 0),
+ (unsigned short *)PyArray_DATA(output_array),
+ PyArray_DIM(output_array, 1),
+ PyArray_DIM(output_array, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ break;
+ default:
+ PyErr_SetString(PyExc_ValueError, "Unsupported dtype");
+ goto error;
+ }
+ }
+
+ Py_DECREF(input_array);
+ Py_XDECREF(transform_mesh_array);
+ return (PyObject *)output_array;
+
+ error:
+ Py_XDECREF(input_array);
+ Py_XDECREF(output_array);
+ Py_XDECREF(transform_mesh_array);
+ return NULL;
+}
+
+
+const char *image_pcolor__doc__ =
+ "pcolor(x, y, data, rows, cols, bounds)\n"
+ "\n"
+ "Generate a pseudo-color image from data on a non-uniform grid using\n"
+ "nearest neighbour or linear interpolation.\n"
+ "bounds = (x_min, x_max, y_min, y_max)\n"
+ "interpolation = NEAREST or BILINEAR \n";
+
+static PyObject *image_pcolor(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ numpy::array_view<const float, 1> x;
+ numpy::array_view<const float, 1> y;
+ numpy::array_view<const agg::int8u, 3> d;
+ npy_intp rows, cols;
+ float bounds[4];
+ int interpolation;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&nn(ffff)i:pcolor",
+ &x.converter,
+ &x,
+ &y.converter,
+ &y,
+ &d.converter_contiguous,
+ &d,
+ &rows,
+ &cols,
+ &bounds[0],
+ &bounds[1],
+ &bounds[2],
+ &bounds[3],
+ &interpolation)) {
+ return NULL;
+ }
+
+ npy_intp dim[3] = {rows, cols, 4};
+ numpy::array_view<const agg::int8u, 3> output(dim);
+
+ CALL_CPP("pcolor", (pcolor(x, y, d, rows, cols, bounds, interpolation, output)));
+
+ return output.pyobj();
+}
+
+const char *image_pcolor2__doc__ =
+ "pcolor2(x, y, data, rows, cols, bounds, bg)\n"
+ "\n"
+ "Generate a pseudo-color image from data on a non-uniform grid\n"
+ "specified by its cell boundaries.\n"
+ "bounds = (x_left, x_right, y_bot, y_top)\n"
+ "bg = ndarray of 4 uint8 representing background rgba\n";
+
+static PyObject *image_pcolor2(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ numpy::array_view<const double, 1> x;
+ numpy::array_view<const double, 1> y;
+ numpy::array_view<const agg::int8u, 3> d;
+ npy_intp rows, cols;
+ float bounds[4];
+ numpy::array_view<const agg::int8u, 1> bg;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&nn(ffff)O&:pcolor2",
+ &x.converter_contiguous,
+ &x,
+ &y.converter_contiguous,
+ &y,
+ &d.converter_contiguous,
+ &d,
+ &rows,
+ &cols,
+ &bounds[0],
+ &bounds[1],
+ &bounds[2],
+ &bounds[3],
+ &bg.converter,
+ &bg)) {
+ return NULL;
+ }
+
+ npy_intp dim[3] = {rows, cols, 4};
+ numpy::array_view<const agg::int8u, 3> output(dim);
+
+ CALL_CPP("pcolor2", (pcolor2(x, y, d, rows, cols, bounds, bg, output)));
+
+ return output.pyobj();
+}
+
+static PyMethodDef module_functions[] = {
+ {"resample", (PyCFunction)image_resample, METH_VARARGS|METH_KEYWORDS, image_resample__doc__},
+ {"pcolor", (PyCFunction)image_pcolor, METH_VARARGS, image_pcolor__doc__},
+ {"pcolor2", (PyCFunction)image_pcolor2, METH_VARARGS, image_pcolor2__doc__},
+ {NULL}
+};
+
+extern "C" {
+
+#if PY3K
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_image",
+ NULL,
+ 0,
+ module_functions,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+#define INITERROR return NULL
+
+PyMODINIT_FUNC PyInit__image(void)
+
+#else
+#define INITERROR return
+
+PyMODINIT_FUNC init_image(void)
+#endif
+
+{
+ PyObject *m;
+
+#if PY3K
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("_image", module_functions, NULL);
+#endif
+
+ if (m == NULL) {
+ INITERROR;
+ }
+
+ 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)) {
+ INITERROR;
+ }
+
+ import_array();
+
+#if PY3K
+ return m;
+#endif
+}
+
+} // extern "C"
diff --git a/contrib/python/matplotlib/py2/src/_path.h b/contrib/python/matplotlib/py2/src/_path.h
new file mode 100644
index 0000000000..76f1894c4a
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_path.h
@@ -0,0 +1,1316 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef __PATH_H__
+#define __PATH_H__
+
+#include <limits>
+#include <math.h>
+#include <vector>
+#include <cmath>
+#include <algorithm>
+
+#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_curves());
+ 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 (bool)result[0];
+}
+
+template <class PathIterator, class PointArray, class ResultArray>
+void points_on_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_stroke<curve_t> stroke_t;
+
+ size_t i;
+ for (i = 0; i < points.size(); ++i) {
+ result[i] = false;
+ }
+
+ transformed_path_t trans_path(path, trans);
+ no_nans_t nan_removed_path(trans_path, true, path.has_curves());
+ 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);
+}
+
+template <class PathIterator>
+inline bool point_on_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_on_path(points, r, path, trans, result);
+
+ return (bool)result[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_curves());
+
+ 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 be Nx2");
+ }
+
+ 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,
+ e_offset_position offset_position,
+ 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);
+ if (offset_position == OFFSET_POSITION_DATA) {
+ trans = agg::trans_affine_translation(xo, yo) * trans;
+ } else {
+ 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_curves());
+ 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:
+
+ http://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 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)
+{
+ double den = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
+ if (den == 0.0) {
+ return false;
+ }
+
+ double n1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
+ double n2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));
+
+ double u1 = n1 / den;
+ double u2 = n2 / den;
+
+ return (u1 >= 0.0 && u1 <= 1.0 && u2 >= 0.0 && 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_curves());
+ no_nans_t n2(p2, true, p2.has_curves());
+
+ 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) {
+ c2.rewind(0);
+ c2.vertex(&x21, &y21);
+ while (c2.vertex(&x22, &y22) != agg::path_cmd_stop) {
+ 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_curves());
+ 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_curves());
+ clipped_t clipped(nan_removed, do_clip && !path.has_curves(), 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_curves());
+ clipped_t clipped(nan_removed, do_clip && !path.has_curves(), 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;
+}
+
+char *__append_to_string(char *p, char **buffer, size_t *buffersize,
+ const char *content)
+{
+ for (const char *i = content; *i; ++i) {
+ if (p < *buffer) {
+ /* This is just an internal error */
+ return NULL;
+ }
+ if ((size_t)(p - *buffer) >= *buffersize) {
+ ptrdiff_t diff = p - *buffer;
+ *buffersize *= 2;
+ *buffer = (char *)realloc(*buffer, *buffersize);
+ if (*buffer == NULL) {
+ return NULL;
+ }
+ p = *buffer + diff;
+ }
+
+ *p++ = *i;
+ }
+
+ return p;
+}
+
+
+char *__add_number(double val, const char *format, int precision,
+ char **buffer, char *p, size_t *buffersize)
+{
+ char *result;
+
+#if PY_VERSION_HEX >= 0x02070000
+ char *str;
+ str = PyOS_double_to_string(val, format[0], precision, 0, NULL);
+#else
+ char str[64];
+ PyOS_ascii_formatd(str, 64, format, val);
+#endif
+
+ // Delete trailing zeros and decimal point
+ char *q = str;
+ for (; *q != 0; ++q) {
+ // Find the end of the string
+ }
+
+ --q;
+ for (; q >= str && *q == '0'; --q) {
+ // Rewind through all the zeros
+ }
+
+ // If the end is a decimal qoint, delete that too
+ if (q >= str && *q == '.') {
+ --q;
+ }
+
+ // Truncate the string
+ ++q;
+ *q = 0;
+
+#if PY_VERSION_HEX >= 0x02070000
+ if ((result = __append_to_string(p, buffer, buffersize, str)) == NULL) {
+ PyMem_Free(str);
+ return NULL;
+ }
+ PyMem_Free(str);
+#else
+ if ((result = __append_to_string(p, buffer, buffersize, str)) == NULL) {
+ return NULL;
+ }
+#endif
+
+ return result;
+}
+
+
+template <class PathIterator>
+int __convert_to_string(PathIterator &path,
+ int precision,
+ char **codes,
+ bool postfix,
+ char **buffer,
+ size_t *buffersize)
+{
+#if PY_VERSION_HEX >= 0x02070000
+ const char *format = "f";
+#else
+ char format[64];
+ snprintf(format, 64, "%s.%df", "%", precision);
+#endif
+
+ char *p = *buffer;
+ double x[3];
+ double y[3];
+ double last_x = 0.0;
+ double last_y = 0.0;
+
+ const int sizes[] = { 1, 1, 2, 3 };
+ int size = 0;
+ unsigned code;
+
+ while ((code = path.vertex(&x[0], &y[0])) != agg::path_cmd_stop) {
+ if (code == 0x4f) {
+ if ((p = __append_to_string(p, buffer, buffersize, codes[4])) == NULL) return 1;
+ } else if (code < 5) {
+ size = sizes[code - 1];
+
+ for (int i = 1; i < size; ++i) {
+ unsigned subcode = path.vertex(&x[i], &y[i]);
+ if (subcode != code) {
+ return 2;
+ }
+ }
+
+ /* 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) {
+ if ((p = __append_to_string(p, buffer, buffersize, codes[code - 1])) == NULL) return 1;
+ if ((p = __append_to_string(p, buffer, buffersize, " ")) == NULL) return 1;
+ }
+
+ for (int i = 0; i < size; ++i) {
+ if ((p = __add_number(x[i], format, precision, buffer, p, buffersize)) == NULL) return 1;
+ if ((p = __append_to_string(p, buffer, buffersize, " ")) == NULL) return 1;
+ if ((p = __add_number(y[i], format, precision, buffer, p, buffersize)) == NULL) return 1;
+ if ((p = __append_to_string(p, buffer, buffersize, " ")) == NULL) return 1;
+ }
+
+ if (postfix) {
+ if ((p = __append_to_string(p, buffer, buffersize, codes[code - 1])) == NULL) return 1;
+ }
+
+ last_x = x[size - 1];
+ last_y = y[size - 1];
+ } else {
+ // Unknown code value
+ return 2;
+ }
+
+ if ((p = __append_to_string(p, buffer, buffersize, "\n")) == NULL) return 1;
+ }
+
+ *buffersize = p - *buffer;
+
+ return 0;
+}
+
+template <class PathIterator>
+int 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,
+ char **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_curves());
+ clipped_t clipped(nan_removed, do_clip && !path.has_curves(), clip_rect);
+ simplify_t simplified(clipped, simplify, path.simplify_threshold());
+
+ *buffersize = path.total_vertices() * (precision + 5) * 4;
+ if (*buffersize == 0) {
+ return 0;
+ }
+
+ if (sketch_params.scale != 0.0) {
+ *buffersize *= 10.0;
+ }
+
+ *buffer = (char *)malloc(*buffersize);
+ if (*buffer == NULL) {
+ return 1;
+ }
+
+ if (sketch_params.scale == 0.0) {
+ return __convert_to_string(simplified, precision, codes, postfix, buffer, buffersize);
+ } 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, buffersize);
+ }
+
+}
+
+template<class T>
+struct _is_sorted
+{
+ bool operator()(PyArrayObject *array)
+ {
+ npy_intp size;
+ npy_intp i;
+ T last_value;
+ T current_value;
+
+ size = PyArray_DIM(array, 0);
+
+ // std::isnan is only in C++11, which we don't yet require,
+ // so we use the "self == self" trick
+ for (i = 0; i < size; ++i) {
+ last_value = *((T *)PyArray_GETPTR1(array, i));
+ if (last_value == last_value) {
+ break;
+ }
+ }
+
+ if (i == size) {
+ // The whole array is non-finite
+ return false;
+ }
+
+ for (; i < size; ++i) {
+ current_value = *((T *)PyArray_GETPTR1(array, i));
+ if (current_value == current_value) {
+ if (current_value < last_value) {
+ return false;
+ }
+ last_value = current_value;
+ }
+ }
+
+ return true;
+ }
+};
+
+
+template<class T>
+struct _is_sorted_int
+{
+ bool operator()(PyArrayObject *array)
+ {
+ npy_intp size;
+ npy_intp i;
+ T last_value;
+ T current_value;
+
+ size = PyArray_DIM(array, 0);
+
+ last_value = *((T *)PyArray_GETPTR1(array, 0));
+
+ for (i = 1; i < size; ++i) {
+ current_value = *((T *)PyArray_GETPTR1(array, i));
+ if (current_value < last_value) {
+ return false;
+ }
+ last_value = current_value;
+ }
+
+ return true;
+ }
+};
+
+
+#endif
diff --git a/contrib/python/matplotlib/py2/src/_path_wrapper.cpp b/contrib/python/matplotlib/py2/src/_path_wrapper.cpp
new file mode 100644
index 0000000000..08a595e7c4
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_path_wrapper.cpp
@@ -0,0 +1,900 @@
+#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)";
+
+static PyObject *Py_point_in_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ 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)";
+
+static PyObject *Py_points_in_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ 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_point_on_path__doc__ = "point_on_path(x, y, radius, path, trans)";
+
+static PyObject *Py_point_on_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ double x, y, r;
+ py::PathIterator path;
+ agg::trans_affine trans;
+ bool result;
+
+ if (!PyArg_ParseTuple(args,
+ "dddO&O&:point_on_path",
+ &x,
+ &y,
+ &r,
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans)) {
+ return NULL;
+ }
+
+ CALL_CPP("point_on_path", (result = point_on_path(x, y, r, path, trans)));
+
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+const char *Py_points_on_path__doc__ = "points_on_path(points, radius, path, trans)";
+
+static PyObject *Py_points_on_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ numpy::array_view<const double, 2> points;
+ double r;
+ py::PathIterator path;
+ agg::trans_affine trans;
+
+ if (!PyArg_ParseTuple(args,
+ "O&dO&O&:points_on_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_on_path", (points_on_path(points, r, path, trans, results)));
+
+ return results.pyobj();
+}
+
+const char *Py_get_path_extents__doc__ = "get_path_extents(path, trans)";
+
+static PyObject *Py_get_path_extents(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+
+ if (!PyArg_ParseTuple(
+ args, "O&O&:get_path_extents", &convert_path, &path, &convert_trans_affine, &trans)) {
+ return NULL;
+ }
+
+ extent_limits e;
+
+ CALL_CPP("get_path_extents", (reset_limits(e)));
+ CALL_CPP("get_path_extents", (update_path_extents(path, 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;
+
+ return extents.pyobj();
+}
+
+const char *Py_update_path_extents__doc__ =
+ "update_path_extents(path, trans, rect, minpos, ignore)";
+
+static PyObject *Py_update_path_extents(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ 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(";
+
+static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ agg::trans_affine master_transform;
+ PyObject *pathsobj;
+ 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&OO&O&O&:get_path_collection_extents",
+ &convert_trans_affine,
+ &master_transform,
+ &pathsobj,
+ &convert_transforms,
+ &transforms,
+ &convert_points,
+ &offsets,
+ &convert_trans_affine,
+ &offset_trans)) {
+ return NULL;
+ }
+
+ try
+ {
+ py::PathGenerator paths(pathsobj);
+
+ CALL_CPP("get_path_collection_extents",
+ (get_path_collection_extents(
+ master_transform, paths, transforms, offsets, offset_trans, e)));
+ }
+ catch (const py::exception &)
+ {
+ return NULL;
+ }
+
+ 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;
+
+ return extents.pyobj();
+}
+
+const char *Py_point_in_path_collection__doc__ =
+ "point_in_path_collection(x, y, radius, master_transform, paths, transforms, offsets, "
+ "offset_trans, filled, offset_position)";
+
+static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ double x, y, radius;
+ agg::trans_affine master_transform;
+ PyObject *pathsobj;
+ numpy::array_view<const double, 3> transforms;
+ numpy::array_view<const double, 2> offsets;
+ agg::trans_affine offset_trans;
+ int filled;
+ e_offset_position offset_position;
+ std::vector<int> result;
+
+ if (!PyArg_ParseTuple(args,
+ "dddO&OO&O&O&iO&:point_in_path_collection",
+ &x,
+ &y,
+ &radius,
+ &convert_trans_affine,
+ &master_transform,
+ &pathsobj,
+ &convert_transforms,
+ &transforms,
+ &convert_points,
+ &offsets,
+ &convert_trans_affine,
+ &offset_trans,
+ &filled,
+ &convert_offset_position,
+ &offset_position)) {
+ return NULL;
+ }
+
+ try
+ {
+ py::PathGenerator paths(pathsobj);
+
+ CALL_CPP("point_in_path_collection",
+ (point_in_path_collection(x,
+ y,
+ radius,
+ master_transform,
+ paths,
+ transforms,
+ offsets,
+ offset_trans,
+ filled,
+ offset_position,
+ result)));
+ }
+ catch (const py::exception &)
+ {
+ return NULL;
+ }
+
+ 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)";
+
+static PyObject *Py_path_in_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ 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)";
+
+static PyObject *Py_clip_path_to_rect(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator path;
+ agg::rect_d rect;
+ int inside;
+ std::vector<Polygon> result;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&i:clip_path_to_rect",
+ &convert_path,
+ &path,
+ &convert_rect,
+ &rect,
+ &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)";
+
+static PyObject *Py_affine_transform(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *vertices_obj;
+ agg::trans_affine trans;
+
+ if (!PyArg_ParseTuple(args,
+ "OO&:affine_transform",
+ &vertices_obj,
+ &convert_trans_affine,
+ &trans)) {
+ return NULL;
+ }
+
+ try {
+ numpy::array_view<double, 2> vertices(vertices_obj);
+ 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();
+ } catch (py::exception &) {
+ PyErr_Clear();
+ try {
+ numpy::array_view<double, 1> vertices(vertices_obj);
+ 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();
+ } catch (py::exception &) {
+ return NULL;
+ }
+ }
+}
+
+const char *Py_count_bboxes_overlapping_bbox__doc__ = "count_bboxes_overlapping_bbox(bbox, bboxes)";
+
+static PyObject *Py_count_bboxes_overlapping_bbox(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ 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)";
+
+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)";
+
+static PyObject *Py_path_intersects_rectangle(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator path;
+ double rect_x1, rect_y1, rect_x2, rect_y2;
+ int filled = 0;
+ const char *names[] = { "path", "rect_x1", "rect_y1", "rect_x2", "rect_y2", "filled", NULL };
+ bool result;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ "O&dddd|i:path_intersects_rectangle",
+ (char **)names,
+ &convert_path,
+ &path,
+ &rect_x1,
+ &rect_y1,
+ &rect_x2,
+ &rect_y2,
+ &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)";
+
+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)";
+
+static PyObject *Py_cleanup_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+ int remove_nans;
+ agg::rect_d clip_rect;
+ e_snap_mode snap_mode;
+ double stroke_width;
+ PyObject *simplifyobj;
+ bool simplify = false;
+ int return_curves;
+ SketchParams sketch;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&iO&O&dOiO&:cleanup_path",
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &remove_nans,
+ &convert_rect,
+ &clip_rect,
+ &convert_snap,
+ &snap_mode,
+ &stroke_width,
+ &simplifyobj,
+ &return_curves,
+ &convert_sketch_params,
+ &sketch)) {
+ return NULL;
+ }
+
+ if (simplifyobj == Py_None) {
+ simplify = path.should_simplify();
+ } else if (PyObject_IsTrue(simplifyobj)) {
+ simplify = true;
+ }
+
+ 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)";
+
+static PyObject *Py_convert_to_string(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+ agg::rect_d cliprect;
+ PyObject *simplifyobj;
+ bool simplify = false;
+ SketchParams sketch;
+ int precision;
+ PyObject *codesobj;
+ char *codes[5];
+ int postfix;
+ char *buffer = NULL;
+ size_t buffersize;
+ PyObject *result;
+ int status;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&OO&iOi:convert_to_string",
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &convert_rect,
+ &cliprect,
+ &simplifyobj,
+ &convert_sketch_params,
+ &sketch,
+ &precision,
+ &codesobj,
+ &postfix)) {
+ return NULL;
+ }
+
+ if (simplifyobj == Py_None) {
+ simplify = path.should_simplify();
+ } else if (PyObject_IsTrue(simplifyobj)) {
+ simplify = true;
+ }
+
+ if (!PySequence_Check(codesobj)) {
+ return NULL;
+ }
+ if (PySequence_Size(codesobj) != 5) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "codes must be a 5-length sequence of byte strings");
+ return NULL;
+ }
+ for (int i = 0; i < 5; ++i) {
+ PyObject *item = PySequence_GetItem(codesobj, i);
+ if (item == NULL) {
+ return NULL;
+ }
+ codes[i] = PyBytes_AsString(item);
+ if (codes[i] == NULL) {
+ return NULL;
+ }
+ }
+
+ CALL_CPP("convert_to_string",
+ (status = convert_to_string(
+ path, trans, cliprect, simplify, sketch,
+ precision, codes, (bool)postfix, &buffer,
+ &buffersize)));
+
+ if (status) {
+ free(buffer);
+ if (status == 1) {
+ PyErr_SetString(PyExc_MemoryError, "Memory error");
+ } else if (status == 2) {
+ PyErr_SetString(PyExc_ValueError, "Malformed path codes");
+ }
+ return NULL;
+ }
+
+ if (buffersize == 0) {
+ result = PyBytes_FromString("");
+ } else {
+ result = PyBytes_FromStringAndSize(buffer, buffersize);
+ }
+
+ free(buffer);
+
+ return result;
+}
+
+
+const char *Py_is_sorted__doc__ = "is_sorted(array)\n\n"
+ "Returns True if 1-D array is monotonically increasing, ignoring NaNs\n";
+
+static PyObject *Py_is_sorted(PyObject *self, PyObject *obj)
+{
+ npy_intp size;
+ bool result;
+
+ PyArrayObject *array = (PyArrayObject *)PyArray_FromAny(
+ obj, NULL, 1, 1, 0, NULL);
+
+ if (array == NULL) {
+ return NULL;
+ }
+
+ size = PyArray_DIM(array, 0);
+
+ if (size < 2) {
+ Py_DECREF(array);
+ Py_RETURN_TRUE;
+ }
+
+ /* Handle just the most common types here, otherwise coerce to
+ double */
+ switch(PyArray_TYPE(array)) {
+ case NPY_INT:
+ {
+ _is_sorted_int<npy_int> is_sorted;
+ result = is_sorted(array);
+ }
+ break;
+
+ case NPY_LONG:
+ {
+ _is_sorted_int<npy_long> is_sorted;
+ result = is_sorted(array);
+ }
+ break;
+
+ case NPY_LONGLONG:
+ {
+ _is_sorted_int<npy_longlong> is_sorted;
+ result = is_sorted(array);
+ }
+ break;
+
+ case NPY_FLOAT:
+ {
+ _is_sorted<npy_float> is_sorted;
+ result = is_sorted(array);
+ }
+ break;
+
+ case NPY_DOUBLE:
+ {
+ _is_sorted<npy_double> is_sorted;
+ result = is_sorted(array);
+ }
+ break;
+
+ default:
+ {
+ Py_DECREF(array);
+ array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1);
+
+ if (array == NULL) {
+ return NULL;
+ }
+
+ _is_sorted<npy_double> is_sorted;
+ result = is_sorted(array);
+ }
+ }
+
+ Py_DECREF(array);
+
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+
+extern "C" {
+
+ 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__},
+ {"point_on_path", (PyCFunction)Py_point_on_path, METH_VARARGS, Py_point_on_path__doc__},
+ {"points_on_path", (PyCFunction)Py_points_on_path, METH_VARARGS, Py_points_on_path__doc__},
+ {"get_path_extents", (PyCFunction)Py_get_path_extents, METH_VARARGS, Py_get_path_extents__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", (PyCFunction)Py_is_sorted, METH_O, Py_is_sorted__doc__},
+ {NULL}
+ };
+
+#if PY3K
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_path",
+ NULL,
+ 0,
+ module_functions,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ };
+
+#define INITERROR return NULL
+ PyMODINIT_FUNC PyInit__path(void)
+#else
+#define INITERROR return
+ PyMODINIT_FUNC init_path(void)
+#endif
+ {
+ PyObject *m;
+#if PY3K
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("_path", module_functions, NULL);
+#endif
+
+ if (m == NULL) {
+ INITERROR;
+ }
+
+ import_array();
+
+#if PY3K
+ return m;
+#endif
+ }
+}
diff --git a/contrib/python/matplotlib/py2/src/_png.cpp b/contrib/python/matplotlib/py2/src/_png.cpp
new file mode 100644
index 0000000000..1dcbf713f2
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_png.cpp
@@ -0,0 +1,793 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+// this code is heavily adapted from the paint license, which is in
+// the file paint.license (BSD compatible) included in this
+// distribution. TODO, add license file to MANIFEST.in and CVS
+
+/* For linux, png.h must be imported before Python.h because
+ png.h needs to be the one to define setjmp.
+ Undefining _POSIX_C_SOURCE and _XOPEN_SOURCE stops a couple
+ of harmless warnings.
+*/
+
+extern "C" {
+# include <png.h>
+# ifdef _POSIX_C_SOURCE
+# undef _POSIX_C_SOURCE
+# endif
+# ifndef _AIX
+# ifdef _XOPEN_SOURCE
+# undef _XOPEN_SOURCE
+# endif
+# endif
+}
+
+#include "numpy_cpp.h"
+#include "mplutils.h"
+#include "file_compat.h"
+
+# include <vector>
+# include "Python.h"
+
+
+// As reported in [3082058] build _png.so on aix
+#ifdef _AIX
+#undef jmpbuf
+#endif
+
+struct buffer_t {
+ PyObject *str;
+ size_t cursor;
+ size_t size;
+};
+
+
+static void write_png_data_buffer(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ buffer_t *buff = (buffer_t *)png_get_io_ptr(png_ptr);
+ if (buff->cursor + length < buff->size) {
+ memcpy(PyBytes_AS_STRING(buff->str) + buff->cursor, data, length);
+ buff->cursor += length;
+ }
+}
+
+static void flush_png_data_buffer(png_structp png_ptr)
+{
+
+}
+
+static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr);
+ PyObject *write_method = PyObject_GetAttrString(py_file_obj, "write");
+ PyObject *result = NULL;
+ if (write_method) {
+#if PY3K
+ result = PyObject_CallFunction(write_method, (char *)"y#", data, length);
+#else
+ result = PyObject_CallFunction(write_method, (char *)"s#", data, length);
+#endif
+ }
+ Py_XDECREF(write_method);
+ Py_XDECREF(result);
+}
+
+static void flush_png_data(png_structp png_ptr)
+{
+ PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr);
+ PyObject *flush_method = PyObject_GetAttrString(py_file_obj, "flush");
+ PyObject *result = NULL;
+ if (flush_method) {
+ result = PyObject_CallFunction(flush_method, (char *)"");
+ }
+ Py_XDECREF(flush_method);
+ Py_XDECREF(result);
+}
+
+const char *Py_write_png__doc__ =
+ "write_png(buffer, file, dpi=0, compression=6, filter=auto, metadata=None)\n"
+ "\n"
+ "Parameters\n"
+ "----------\n"
+ "buffer : numpy array of image data\n"
+ " Must be an MxNxD array of dtype uint8.\n"
+ " - if D is 1, the image is greyscale\n"
+ " - if D is 3, the image is RGB\n"
+ " - if D is 4, the image is RGBA\n"
+ "\n"
+ "file : str path, file-like object or None\n"
+ " - If a str, must be a file path\n"
+ " - If a file-like object, must write bytes\n"
+ " - If None, a byte string containing the PNG data will be returned\n"
+ "\n"
+ "dpi : float\n"
+ " The dpi to store in the file metadata.\n"
+ "\n"
+ "compression : int\n"
+ " The level of lossless zlib compression to apply. 0 indicates no\n"
+ " compression. Values 1-9 indicate low/fast through high/slow\n"
+ " compression. Default is 6.\n"
+ "\n"
+ "filter : int\n"
+ " Filter to apply. Must be one of the constants: PNG_FILTER_NONE,\n"
+ " PNG_FILTER_SUB, PNG_FILTER_UP, PNG_FILTER_AVG, PNG_FILTER_PAETH.\n"
+ " See the PNG standard for more information.\n"
+ " If not provided, libpng will try to automatically determine the\n"
+ " best filter on a line-by-line basis.\n"
+ "\n"
+ "metadata : dictionary\n"
+ " The keyword-text pairs that are stored as comments in the image.\n"
+ " Keys must be shorter than 79 chars. The only supported encoding\n"
+ " for both keywords and values is Latin-1 (ISO 8859-1).\n"
+ " Examples given in the PNG Specification are:\n"
+ " - Title: Short (one line) title or caption for image\n"
+ " - Author: Name of image's creator\n"
+ " - Description: Description of image (possibly long)\n"
+ " - Copyright: Copyright notice\n"
+ " - Creation Time: Time of original image creation\n"
+ " (usually RFC 1123 format, see below)\n"
+ " - Software: Software used to create the image\n"
+ " - Disclaimer: Legal disclaimer\n"
+ " - Warning: Warning of nature of content\n"
+ " - Source: Device used to create the image\n"
+ " - Comment: Miscellaneous comment; conversion\n"
+ " from other image format\n"
+ "\n"
+ "Returns\n"
+ "-------\n"
+ "buffer : bytes or None\n"
+ " Byte string containing the PNG content if None was passed in for\n"
+ " file, otherwise None is returned.\n";
+
+static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ numpy::array_view<unsigned char, 3> buffer;
+ PyObject *filein;
+ PyObject *metadata = NULL;
+ PyObject *meta_key, *meta_val;
+ png_text *text;
+ Py_ssize_t pos = 0;
+ int meta_pos = 0;
+ Py_ssize_t meta_size;
+ double dpi = 0;
+ int compression = 6;
+ int filter = -1;
+ const char *names[] = { "buffer", "file", "dpi", "compression", "filter", "metadata", NULL };
+
+ // We don't need strict contiguity, just for each row to be
+ // contiguous, and libpng has special handling for getting RGB out
+ // of RGBA, ARGB or BGR. But the simplest thing to do is to
+ // enforce contiguity using array_view::converter_contiguous.
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ "O&O|diiO:write_png",
+ (char **)names,
+ &buffer.converter_contiguous,
+ &buffer,
+ &filein,
+ &dpi,
+ &compression,
+ &filter,
+ &metadata)) {
+ return NULL;
+ }
+
+ png_uint_32 width = (png_uint_32)buffer.dim(1);
+ png_uint_32 height = (png_uint_32)buffer.dim(0);
+ int channels = buffer.dim(2);
+ std::vector<png_bytep> row_pointers(height);
+ for (png_uint_32 row = 0; row < (png_uint_32)height; ++row) {
+ row_pointers[row] = (png_bytep)&buffer(row, 0, 0);
+ }
+
+ FILE *fp = NULL;
+ mpl_off_t offset = 0;
+ bool close_file = false;
+ bool close_dup_file = false;
+ PyObject *py_file = NULL;
+
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ struct png_color_8_struct sig_bit;
+ int png_color_type;
+ buffer_t buff;
+ buff.str = NULL;
+
+ switch (channels) {
+ case 1:
+ png_color_type = PNG_COLOR_TYPE_GRAY;
+ break;
+ case 3:
+ png_color_type = PNG_COLOR_TYPE_RGB;
+ break;
+ case 4:
+ png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ break;
+ default:
+ PyErr_SetString(PyExc_ValueError,
+ "Buffer must be an NxMxD array with D in 1, 3, 4 "
+ "(grayscale, RGB, RGBA)");
+ goto exit;
+ }
+
+ if (compression < 0 || compression > 9) {
+ PyErr_Format(PyExc_ValueError,
+ "compression must be in range 0-9, got %d", compression);
+ goto exit;
+ }
+
+ if (PyBytes_Check(filein) || PyUnicode_Check(filein)) {
+ if ((py_file = mpl_PyFile_OpenFile(filein, (char *)"wb")) == NULL) {
+ goto exit;
+ }
+ close_file = true;
+ } else {
+ py_file = filein;
+ }
+
+ if (filein == Py_None) {
+ buff.size = width * height * 4 + 1024;
+ buff.str = PyBytes_FromStringAndSize(NULL, buff.size);
+ if (buff.str == NULL) {
+ goto exit;
+ }
+ buff.cursor = 0;
+ } else {
+ #if PY3K
+ if (close_file) {
+ #else
+ if (close_file || PyFile_Check(py_file)) {
+ #endif
+ fp = mpl_PyFile_Dup(py_file, (char *)"wb", &offset);
+ }
+
+ if (fp) {
+ close_dup_file = true;
+ } else {
+ PyErr_Clear();
+ PyObject *write_method = PyObject_GetAttrString(py_file, "write");
+ if (!(write_method && PyCallable_Check(write_method))) {
+ Py_XDECREF(write_method);
+ PyErr_SetString(PyExc_TypeError,
+ "Object does not appear to be a 8-bit string path or "
+ "a Python file-like object");
+ goto exit;
+ }
+ Py_XDECREF(write_method);
+ }
+ }
+
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (png_ptr == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "Could not create write struct");
+ goto exit;
+ }
+
+ png_set_compression_level(png_ptr, compression);
+ if (filter >= 0) {
+ png_set_filter(png_ptr, 0, filter);
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "Could not create info struct");
+ goto exit;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ PyErr_SetString(PyExc_RuntimeError, "libpng signaled error");
+ goto exit;
+ }
+
+ if (buff.str) {
+ png_set_write_fn(png_ptr, (void *)&buff, &write_png_data_buffer, &flush_png_data_buffer);
+ } else if (fp) {
+ png_init_io(png_ptr, fp);
+ } else {
+ png_set_write_fn(png_ptr, (void *)py_file, &write_png_data, &flush_png_data);
+ }
+ png_set_IHDR(png_ptr,
+ info_ptr,
+ width,
+ height,
+ 8,
+ png_color_type,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+
+ // Save the dpi of the image in the file
+ if (dpi > 0.0) {
+ png_uint_32 dots_per_meter = (png_uint_32)(dpi / (2.54 / 100.0));
+ png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER);
+ }
+
+#ifdef PNG_TEXT_SUPPORTED
+ // Save the metadata
+ if (metadata != NULL) {
+ meta_size = PyDict_Size(metadata);
+ text = new png_text[meta_size];
+
+ while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) {
+ text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE;
+#if PY3K
+ if (PyUnicode_Check(meta_key)) {
+ PyObject *temp_key = PyUnicode_AsEncodedString(meta_key, "latin_1", "strict");
+ if (temp_key != NULL) {
+ text[meta_pos].key = PyBytes_AsString(temp_key);
+ }
+ } else if (PyBytes_Check(meta_key)) {
+ text[meta_pos].key = PyBytes_AsString(meta_key);
+ } else {
+ char invalid_key[79];
+ sprintf(invalid_key,"INVALID KEY %d", meta_pos);
+ text[meta_pos].key = invalid_key;
+ }
+ if (PyUnicode_Check(meta_val)) {
+ PyObject *temp_val = PyUnicode_AsEncodedString(meta_val, "latin_1", "strict");
+ if (temp_val != NULL) {
+ text[meta_pos].text = PyBytes_AsString(temp_val);
+ }
+ } else if (PyBytes_Check(meta_val)) {
+ text[meta_pos].text = PyBytes_AsString(meta_val);
+ } else {
+ text[meta_pos].text = (char *)"Invalid value in metadata";
+ }
+#else
+ text[meta_pos].key = PyString_AsString(meta_key);
+ text[meta_pos].text = PyString_AsString(meta_val);
+#endif
+#ifdef PNG_iTXt_SUPPORTED
+ text[meta_pos].lang = NULL;
+#endif
+ meta_pos++;
+ }
+ png_set_text(png_ptr, info_ptr, text, meta_size);
+ delete[] text;
+ }
+#endif
+
+ sig_bit.alpha = 0;
+ switch (png_color_type) {
+ case PNG_COLOR_TYPE_GRAY:
+ sig_bit.gray = 8;
+ sig_bit.red = 0;
+ sig_bit.green = 0;
+ sig_bit.blue = 0;
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ sig_bit.alpha = 8;
+ // fall through
+ case PNG_COLOR_TYPE_RGB:
+ sig_bit.gray = 0;
+ sig_bit.red = 8;
+ sig_bit.green = 8;
+ sig_bit.blue = 8;
+ break;
+ default:
+ PyErr_SetString(PyExc_RuntimeError, "internal error, bad png_color_type");
+ goto exit;
+ }
+ png_set_sBIT(png_ptr, info_ptr, &sig_bit);
+
+ png_write_info(png_ptr, info_ptr);
+ png_write_image(png_ptr, &row_pointers[0]);
+ png_write_end(png_ptr, info_ptr);
+
+exit:
+
+ if (png_ptr && info_ptr) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ }
+
+ if (close_dup_file) {
+ mpl_PyFile_DupClose(py_file, fp, offset);
+ }
+
+ if (close_file) {
+ mpl_PyFile_CloseFile(py_file);
+ Py_DECREF(py_file);
+ }
+
+ if (PyErr_Occurred()) {
+ Py_XDECREF(buff.str);
+ return NULL;
+ } else {
+ if (buff.str) {
+ _PyBytes_Resize(&buff.str, buff.cursor);
+ return buff.str;
+ }
+ Py_RETURN_NONE;
+ }
+}
+
+static void _read_png_data(PyObject *py_file_obj, png_bytep data, png_size_t length)
+{
+ PyObject *read_method = PyObject_GetAttrString(py_file_obj, "read");
+ PyObject *result = NULL;
+ char *buffer;
+ Py_ssize_t bufflen;
+ if (read_method) {
+ result = PyObject_CallFunction(read_method, (char *)"i", length);
+ if (result) {
+ if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) {
+ if (bufflen == (Py_ssize_t)length) {
+ memcpy(data, buffer, length);
+ } else {
+ PyErr_SetString(PyExc_IOError, "read past end of file");
+ }
+ } else {
+ PyErr_SetString(PyExc_IOError, "failed to copy buffer");
+ }
+ } else {
+ PyErr_SetString(PyExc_IOError, "failed to read file");
+ }
+
+
+ }
+ Py_XDECREF(read_method);
+ Py_XDECREF(result);
+}
+
+static void read_png_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr);
+ _read_png_data(py_file_obj, data, length);
+ if (PyErr_Occurred()) {
+ png_error(png_ptr, "failed to read file");
+ }
+
+}
+
+static PyObject *_read_png(PyObject *filein, bool float_result)
+{
+ png_byte header[8]; // 8 is the maximum size that can be checked
+ FILE *fp = NULL;
+ mpl_off_t offset = 0;
+ bool close_file = false;
+ bool close_dup_file = false;
+ PyObject *py_file = NULL;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ int num_dims;
+ std::vector<png_bytep> row_pointers;
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ int bit_depth;
+ PyObject *result = NULL;
+
+ // TODO: Remove direct calls to Numpy API here
+
+ if (PyBytes_Check(filein) || PyUnicode_Check(filein)) {
+ if ((py_file = mpl_PyFile_OpenFile(filein, (char *)"rb")) == NULL) {
+ goto exit;
+ }
+ close_file = true;
+ } else {
+ py_file = filein;
+ }
+
+ #if PY3K
+ if (close_file) {
+ #else
+ if (close_file || PyFile_Check(py_file)) {
+ #endif
+ fp = mpl_PyFile_Dup(py_file, (char *)"rb", &offset);
+ }
+
+ if (fp) {
+ close_dup_file = true;
+ if (fread(header, 1, 8, fp) != 8) {
+ PyErr_SetString(PyExc_IOError, "error reading PNG header");
+ goto exit;
+ }
+ } else {
+ PyErr_Clear();
+
+ PyObject *read_method = PyObject_GetAttrString(py_file, "read");
+ if (!(read_method && PyCallable_Check(read_method))) {
+ Py_XDECREF(read_method);
+ PyErr_SetString(PyExc_TypeError,
+ "Object does not appear to be a 8-bit string path or "
+ "a Python file-like object");
+ goto exit;
+ }
+ Py_XDECREF(read_method);
+ _read_png_data(py_file, header, 8);
+ if (PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+
+ if (png_sig_cmp(header, 0, 8)) {
+ PyErr_SetString(PyExc_ValueError, "invalid PNG header");
+ goto exit;
+ }
+
+ /* initialize stuff */
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (!png_ptr) {
+ PyErr_SetString(PyExc_RuntimeError, "png_create_read_struct failed");
+ goto exit;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ PyErr_SetString(PyExc_RuntimeError, "png_create_info_struct failed");
+ goto exit;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(PyExc_RuntimeError, "error setting jump");
+ }
+ goto exit;
+ }
+
+ if (fp) {
+ png_init_io(png_ptr, fp);
+ } else {
+ png_set_read_fn(png_ptr, (void *)py_file, &read_png_data);
+ }
+ png_set_sig_bytes(png_ptr, 8);
+ png_read_info(png_ptr, info_ptr);
+
+ width = png_get_image_width(png_ptr, info_ptr);
+ height = png_get_image_height(png_ptr, info_ptr);
+
+ bit_depth = png_get_bit_depth(png_ptr, info_ptr);
+
+ // Unpack 1, 2, and 4-bit images
+ if (bit_depth < 8) {
+ png_set_packing(png_ptr);
+ }
+
+ // If sig bits are set, shift data
+ png_color_8p sig_bit;
+ if ((png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE) &&
+ png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
+ png_set_shift(png_ptr, sig_bit);
+ }
+
+#if NPY_BYTE_ORDER == NPY_LITTLE_ENDIAN
+ // Convert big endian to little
+ if (bit_depth == 16) {
+ png_set_swap(png_ptr);
+ }
+#endif
+
+ // Convert palletes to full RGB
+ if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(png_ptr);
+ bit_depth = 8;
+ }
+
+ // If there's an alpha channel convert gray to RGB
+ if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(png_ptr);
+ }
+
+ png_set_interlace_handling(png_ptr);
+ png_read_update_info(png_ptr, info_ptr);
+
+ row_pointers.resize(height);
+ for (png_uint_32 row = 0; row < height; row++) {
+ row_pointers[row] = new png_byte[png_get_rowbytes(png_ptr, info_ptr)];
+ }
+
+ png_read_image(png_ptr, &row_pointers[0]);
+
+ npy_intp dimensions[3];
+ dimensions[0] = height; // numrows
+ dimensions[1] = width; // numcols
+ if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) {
+ dimensions[2] = 4; // RGBA images
+ } else if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) {
+ dimensions[2] = 3; // RGB images
+ } else {
+ dimensions[2] = 1; // Greyscale images
+ }
+
+ if (float_result) {
+ double max_value = (1 << bit_depth) - 1;
+
+ numpy::array_view<float, 3> A(dimensions);
+
+ for (png_uint_32 y = 0; y < height; y++) {
+ png_byte *row = row_pointers[y];
+ for (png_uint_32 x = 0; x < width; x++) {
+ if (bit_depth == 16) {
+ png_uint_16 *ptr = &reinterpret_cast<png_uint_16 *>(row)[x * dimensions[2]];
+ for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) {
+ A(y, x, p) = (float)(ptr[p]) / max_value;
+ }
+ } else {
+ png_byte *ptr = &(row[x * dimensions[2]]);
+ for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) {
+ A(y, x, p) = (float)(ptr[p]) / max_value;
+ }
+ }
+ }
+ }
+
+ result = A.pyobj();
+ } else if (bit_depth == 16) {
+ numpy::array_view<png_uint_16, 3> A(dimensions);
+
+ for (png_uint_32 y = 0; y < height; y++) {
+ png_byte *row = row_pointers[y];
+ for (png_uint_32 x = 0; x < width; x++) {
+ png_uint_16 *ptr = &reinterpret_cast<png_uint_16 *>(row)[x * dimensions[2]];
+ for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) {
+ A(y, x, p) = ptr[p];
+ }
+ }
+ }
+
+ result = A.pyobj();
+ } else if (bit_depth == 8) {
+ numpy::array_view<png_byte, 3> A(dimensions);
+
+ for (png_uint_32 y = 0; y < height; y++) {
+ png_byte *row = row_pointers[y];
+ for (png_uint_32 x = 0; x < width; x++) {
+ png_byte *ptr = &(row[x * dimensions[2]]);
+ for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) {
+ A(y, x, p) = ptr[p];
+ }
+ }
+ }
+
+ result = A.pyobj();
+ } else {
+ PyErr_SetString(PyExc_RuntimeError, "image has unknown bit depth");
+ goto exit;
+ }
+
+ // free the png memory
+ png_read_end(png_ptr, info_ptr);
+
+ // For gray, return an x by y array, not an x by y by 1
+ num_dims = (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) ? 3 : 2;
+
+ if (num_dims == 2) {
+ PyArray_Dims dims = {dimensions, 2};
+ PyObject *reshaped = PyArray_Newshape((PyArrayObject *)result, &dims, NPY_CORDER);
+ Py_DECREF(result);
+ result = reshaped;
+ }
+
+exit:
+ if (png_ptr && info_ptr) {
+#ifndef png_infopp_NULL
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+#else
+ png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
+#endif
+ }
+
+ if (close_dup_file) {
+ mpl_PyFile_DupClose(py_file, fp, offset);
+ }
+
+ if (close_file) {
+ mpl_PyFile_CloseFile(py_file);
+ Py_DECREF(py_file);
+ }
+
+ for (png_uint_32 row = 0; row < height; row++) {
+ delete[] row_pointers[row];
+ }
+
+ if (PyErr_Occurred()) {
+ Py_XDECREF(result);
+ return NULL;
+ } else {
+ return result;
+ }
+}
+
+const char *Py_read_png_float__doc__ =
+ "read_png_float(file)\n"
+ "\n"
+ "Read in a PNG file, converting values to floating-point doubles\n"
+ "in the range (0, 1)\n"
+ "\n"
+ "Parameters\n"
+ "----------\n"
+ "file : str path or file-like object\n";
+
+static PyObject *Py_read_png_float(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ return _read_png(args, true);
+}
+
+const char *Py_read_png_int__doc__ =
+ "read_png_int(file)\n"
+ "\n"
+ "Read in a PNG file with original integer values.\n"
+ "\n"
+ "Parameters\n"
+ "----------\n"
+ "file : str path or file-like object\n";
+
+static PyObject *Py_read_png_int(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ return _read_png(args, false);
+}
+
+const char *Py_read_png__doc__ =
+ "read_png(file)\n"
+ "\n"
+ "Read in a PNG file, converting values to floating-point doubles\n"
+ "in the range (0, 1)\n"
+ "\n"
+ "Alias for read_png_float()\n"
+ "\n"
+ "Parameters\n"
+ "----------\n"
+ "file : str path or file-like object\n";
+
+static PyMethodDef module_methods[] = {
+ {"write_png", (PyCFunction)Py_write_png, METH_VARARGS|METH_KEYWORDS, Py_write_png__doc__},
+ {"read_png", (PyCFunction)Py_read_png_float, METH_O, Py_read_png__doc__},
+ {"read_png_float", (PyCFunction)Py_read_png_float, METH_O, Py_read_png_float__doc__},
+ {"read_png_int", (PyCFunction)Py_read_png_int, METH_O, Py_read_png_int__doc__},
+ {NULL}
+};
+
+extern "C" {
+
+#if PY3K
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_png",
+ NULL,
+ 0,
+ module_methods,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ };
+
+#define INITERROR return NULL
+
+ PyMODINIT_FUNC PyInit__png(void)
+
+#else
+#define INITERROR return
+
+ PyMODINIT_FUNC init_png(void)
+#endif
+
+ {
+ PyObject *m;
+
+#if PY3K
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("_png", module_methods, NULL);
+#endif
+
+ if (m == NULL) {
+ INITERROR;
+ }
+
+ import_array();
+
+ if (PyModule_AddIntConstant(m, "PNG_FILTER_NONE", PNG_FILTER_NONE) ||
+ PyModule_AddIntConstant(m, "PNG_FILTER_SUB", PNG_FILTER_SUB) ||
+ PyModule_AddIntConstant(m, "PNG_FILTER_UP", PNG_FILTER_UP) ||
+ PyModule_AddIntConstant(m, "PNG_FILTER_AVG", PNG_FILTER_AVG) ||
+ PyModule_AddIntConstant(m, "PNG_FILTER_PAETH", PNG_FILTER_PAETH)) {
+ INITERROR;
+ }
+
+
+#if PY3K
+ return m;
+#endif
+ }
+}
diff --git a/contrib/python/matplotlib/py2/src/_tkagg.cpp b/contrib/python/matplotlib/py2/src/_tkagg.cpp
new file mode 100644
index 0000000000..ad5289b3d6
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_tkagg.cpp
@@ -0,0 +1,475 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/*
+ * This code is derived from The Python Imaging Library and is covered
+ * by the PIL license.
+ *
+ * See LICENSE/LICENSE.PIL for details.
+ *
+ */
+
+#include <Python.h>
+#include <cstdlib>
+#include <cstdio>
+#include <sstream>
+
+#include <agg_basics.h> // agg:int8u
+
+// Include our own excerpts from the Tcl / Tk headers
+#include "_tkmini.h"
+
+#if defined(_MSC_VER)
+# define IMG_FORMAT "%d %d %Iu"
+#else
+# define IMG_FORMAT "%d %d %zu"
+#endif
+#define BBOX_FORMAT "%f %f %f %f"
+
+typedef struct
+{
+ PyObject_HEAD
+ Tcl_Interp *interp;
+} TkappObject;
+
+// Global vars for Tcl / Tk functions. We load these symbols from the tkinter
+// extension module or loaded Tcl / Tk libraries at run-time.
+static Tcl_CreateCommand_t TCL_CREATE_COMMAND;
+static Tcl_AppendResult_t TCL_APPEND_RESULT;
+static Tk_MainWindow_t TK_MAIN_WINDOW;
+static Tk_FindPhoto_t TK_FIND_PHOTO;
+static Tk_PhotoPutBlock_NoComposite_t TK_PHOTO_PUT_BLOCK_NO_COMPOSITE;
+static Tk_PhotoBlank_t TK_PHOTO_BLANK;
+
+static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int
+ argc, char **argv)
+{
+ Tk_PhotoHandle photo;
+ Tk_PhotoImageBlock block;
+
+ // vars for blitting
+
+ size_t pdata;
+ int wdata, hdata, bbox_parse;
+ float x1, x2, y1, y2;
+ bool has_bbox;
+ agg::int8u *destbuffer, *buffer;
+ int destx, desty, destwidth, destheight, deststride;
+
+ long mode;
+ long nval;
+ if (TK_MAIN_WINDOW(interp) == NULL) {
+ // Will throw a _tkinter.TclError with "this isn't a Tk application"
+ return TCL_ERROR;
+ }
+
+ if (argc != 5) {
+ TCL_APPEND_RESULT(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ /* get Tcl PhotoImage handle */
+ photo = TK_FIND_PHOTO(interp, argv[1]);
+ if (photo == NULL) {
+ TCL_APPEND_RESULT(interp, "destination photo must exist", (char *)NULL);
+ return TCL_ERROR;
+ }
+ /* get buffer from str which is "height width ptr" */
+ if (sscanf(argv[2], IMG_FORMAT, &hdata, &wdata, &pdata) != 3) {
+ TCL_APPEND_RESULT(interp,
+ "error reading data, expected height width ptr",
+ (char *)NULL);
+ return TCL_ERROR;
+ }
+ buffer = (agg::int8u*)pdata;
+
+ /* get array mode (0=mono, 1=rgb, 2=rgba) */
+ mode = atol(argv[3]);
+ if ((mode != 0) && (mode != 1) && (mode != 2)) {
+ TCL_APPEND_RESULT(interp, "illegal image mode", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ /* check for bbox/blitting */
+ bbox_parse = sscanf(argv[4], BBOX_FORMAT, &x1, &x2, &y1, &y2);
+ if (bbox_parse == 4) {
+ has_bbox = true;
+ }
+ else if ((bbox_parse == 1) && (x1 == 0)){
+ has_bbox = false;
+ } else {
+ TCL_APPEND_RESULT(interp, "illegal bbox", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ if (has_bbox) {
+ int srcstride = wdata * 4;
+ destx = (int)x1;
+ desty = (int)(hdata - y2);
+ destwidth = (int)(x2 - x1);
+ destheight = (int)(y2 - y1);
+ deststride = 4 * destwidth;
+
+ destbuffer = new agg::int8u[deststride * destheight];
+ if (destbuffer == NULL) {
+ TCL_APPEND_RESULT(interp, "could not allocate memory", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ for (int i = 0; i < destheight; ++i) {
+ memcpy(destbuffer + (deststride * i),
+ &buffer[(i + desty) * srcstride + (destx * 4)],
+ deststride);
+ }
+ } else {
+ destbuffer = NULL;
+ destx = desty = destwidth = destheight = deststride = 0;
+ }
+
+ /* setup tkblock */
+ block.pixelSize = 1;
+ if (mode == 0) {
+ block.offset[0] = block.offset[1] = block.offset[2] = 0;
+ nval = 1;
+ } else {
+ block.offset[0] = 0;
+ block.offset[1] = 1;
+ block.offset[2] = 2;
+ if (mode == 1) {
+ block.offset[3] = 0;
+ block.pixelSize = 3;
+ nval = 3;
+ } else {
+ block.offset[3] = 3;
+ block.pixelSize = 4;
+ nval = 4;
+ }
+ }
+
+ if (has_bbox) {
+ block.width = destwidth;
+ block.height = destheight;
+ block.pitch = deststride;
+ block.pixelPtr = destbuffer;
+
+ TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, destx, desty,
+ destwidth, destheight);
+ delete[] destbuffer;
+
+ } else {
+ block.width = wdata;
+ block.height = hdata;
+ block.pitch = (int)block.width * nval;
+ block.pixelPtr = buffer;
+
+ /* Clear current contents */
+ TK_PHOTO_BLANK(photo);
+ /* Copy opaque block to photo image, and leave the rest to TK */
+ TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, 0, 0, block.width,
+ block.height);
+ }
+
+ return TCL_OK;
+}
+
+static PyObject *_tkinit(PyObject *self, PyObject *args)
+{
+ Tcl_Interp *interp;
+ TkappObject *app;
+
+ PyObject *arg;
+ int is_interp;
+ if (!PyArg_ParseTuple(args, "Oi", &arg, &is_interp)) {
+ return NULL;
+ }
+
+ if (is_interp) {
+ interp = (Tcl_Interp *)PyLong_AsVoidPtr(arg);
+ } else {
+ /* Do it the hard way. This will break if the TkappObject
+ layout changes */
+ app = (TkappObject *)arg;
+ interp = app->interp;
+ }
+
+ /* This will bomb if interp is invalid... */
+
+ TCL_CREATE_COMMAND(interp,
+ "PyAggImagePhoto",
+ (Tcl_CmdProc *)PyAggImagePhoto,
+ (ClientData)0,
+ (Tcl_CmdDeleteProc *)NULL);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef functions[] = {
+ /* Tkinter interface stuff */
+ { "tkinit", (PyCFunction)_tkinit, 1 },
+ { NULL, NULL } /* sentinel */
+};
+
+// Functions to fill global TCL / Tk function pointers by dynamic loading
+#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
+
+/*
+ * On Windows, we can't load the tkinter module to get the TCL or 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 and Tk
+ * function names.
+ */
+#include <windows.h>
+#define PSAPI_VERSION 1
+#include <psapi.h>
+// Must be linked with 'psapi' library
+
+FARPROC _dfunc(HMODULE lib_handle, const char *func_name)
+{
+ // Load function `func_name` from `lib_handle`.
+ // Set Python exception if we can't find `func_name` in `lib_handle`.
+ // Returns function pointer or NULL if not present.
+
+ char message[100];
+
+ FARPROC func = GetProcAddress(lib_handle, func_name);
+ if (func == NULL) {
+ sprintf(message, "Cannot load function %s", func_name);
+ PyErr_SetString(PyExc_RuntimeError, message);
+ }
+ return func;
+}
+
+int get_tcl(HMODULE hMod)
+{
+ // Try to fill TCL global vars with function pointers. Return 0 for no
+ // functions found, 1 for all functions found, -1 for some but not all
+ // functions found.
+ TCL_CREATE_COMMAND = (Tcl_CreateCommand_t)
+ GetProcAddress(hMod, "Tcl_CreateCommand");
+ if (TCL_CREATE_COMMAND == NULL) { // Maybe not TCL module
+ return 0;
+ }
+ TCL_APPEND_RESULT = (Tcl_AppendResult_t) _dfunc(hMod,
+ "Tcl_AppendResult");
+ return (TCL_APPEND_RESULT == NULL) ? -1 : 1;
+}
+
+int get_tk(HMODULE hMod)
+{
+ // Try to fill Tk global vars with function pointers. Return 0 for no
+ // functions found, 1 for all functions found, -1 for some but not all
+ // functions found.
+ TK_MAIN_WINDOW = (Tk_MainWindow_t)
+ GetProcAddress(hMod, "Tk_MainWindow");
+ if (TK_MAIN_WINDOW == NULL) { // Maybe not Tk module
+ return 0;
+ }
+ return ( // -1 if any remaining symbols are NULL
+ ((TK_FIND_PHOTO = (Tk_FindPhoto_t)
+ _dfunc(hMod, "Tk_FindPhoto")) == NULL) ||
+ ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t)
+ _dfunc(hMod, "Tk_PhotoPutBlock_NoComposite")) == NULL) ||
+ ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t)
+ _dfunc(hMod, "Tk_PhotoBlank")) == NULL))
+ ? -1 : 1;
+}
+
+int load_tkinter_funcs(void)
+{
+ // Load TCL and Tk functions by searching all modules in current process.
+ // Return 0 for success, non-zero for failure.
+
+ HMODULE hMods[1024];
+ HANDLE hProcess;
+ DWORD cbNeeded;
+ unsigned int i;
+ int found_tcl = 0;
+ int found_tk = 0;
+
+ // Returns pseudo-handle that does not need to be closed
+ hProcess = GetCurrentProcess();
+
+ // Iterate through modules in this process looking for TCL / Tk names
+ if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
+ for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
+ if (!found_tcl) {
+ found_tcl = get_tcl(hMods[i]);
+ if (found_tcl == -1) {
+ return 1;
+ }
+ }
+ if (!found_tk) {
+ found_tk = get_tk(hMods[i]);
+ if (found_tk == -1) {
+ return 1;
+ }
+ }
+ if (found_tcl && found_tk) {
+ return 0;
+ }
+ }
+ }
+
+ if (found_tcl == 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Could not find TCL routines");
+ } else {
+ PyErr_SetString(PyExc_RuntimeError, "Could not find Tk routines");
+ }
+ return 1;
+}
+
+#else // not Windows
+
+/*
+ * On Unix, we can get the TCL and Tk synbols from the tkinter module, because
+ * tkinter uses these symbols, and the symbols are therefore visible in the
+ * tkinter dynamic library (module).
+ */
+#if PY_MAJOR_VERSION >= 3
+#define TKINTER_PKG "tkinter"
+#define TKINTER_MOD "_tkinter"
+// From module __file__ attribute to char *string for dlopen.
+char *fname2char(PyObject *fname)
+{
+ PyObject* bytes;
+ bytes = PyUnicode_EncodeFSDefault(fname);
+ if (bytes == NULL) {
+ return NULL;
+ }
+ return PyBytes_AsString(bytes);
+}
+#else
+#define TKINTER_PKG "Tkinter"
+#define TKINTER_MOD "tkinter"
+// From module __file__ attribute to char *string for dlopen
+#define fname2char(s) (PyString_AsString(s))
+#endif
+
+#include <dlfcn.h>
+
+void *_dfunc(void *lib_handle, const char *func_name)
+{
+ // Load function `func_name` from `lib_handle`.
+ // Set Python exception if we can't find `func_name` in `lib_handle`.
+ // Returns function pointer or NULL if not present.
+
+ void* func;
+ // Reset errors.
+ dlerror();
+ func = dlsym(lib_handle, func_name);
+ if (func == NULL) {
+ const char *error = dlerror();
+ PyErr_SetString(PyExc_RuntimeError, error);
+ }
+ return func;
+}
+
+int _func_loader(void *lib)
+{
+ // Fill global function pointers from dynamic lib.
+ // Return 1 if any pointer is NULL, 0 otherwise.
+ return (
+ ((TCL_CREATE_COMMAND = (Tcl_CreateCommand_t)
+ _dfunc(lib, "Tcl_CreateCommand")) == NULL) ||
+ ((TCL_APPEND_RESULT = (Tcl_AppendResult_t)
+ _dfunc(lib, "Tcl_AppendResult")) == NULL) ||
+ ((TK_MAIN_WINDOW = (Tk_MainWindow_t)
+ _dfunc(lib, "Tk_MainWindow")) == NULL) ||
+ ((TK_FIND_PHOTO = (Tk_FindPhoto_t)
+ _dfunc(lib, "Tk_FindPhoto")) == NULL) ||
+ ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t)
+ _dfunc(lib, "Tk_PhotoPutBlock_NoComposite")) == NULL) ||
+ ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t)
+ _dfunc(lib, "Tk_PhotoBlank")) == NULL));
+}
+
+int load_tkinter_funcs(void)
+{
+ // Load tkinter global funcs from tkinter compiled module.
+ // Return 0 for success, non-zero for failure.
+ int ret = -1;
+ void *main_program, *tkinter_lib;
+ char *tkinter_libname;
+ PyObject *pModule = NULL, *pSubmodule = NULL, *pString = NULL;
+
+ // Try loading from the main program namespace first
+ main_program = dlopen(NULL, RTLD_LAZY);
+ if (_func_loader(main_program) == 0) {
+ return 0;
+ }
+ // Clear exception triggered when we didn't find symbols above.
+ PyErr_Clear();
+
+ // Now try finding the tkinter compiled module
+ pModule = PyImport_ImportModule(TKINTER_PKG);
+ if (pModule == NULL) {
+ goto exit;
+ }
+ pSubmodule = PyObject_GetAttrString(pModule, TKINTER_MOD);
+ if (pSubmodule == NULL) {
+ goto exit;
+ }
+ pString = PyObject_GetAttrString(pSubmodule, "__file__");
+ if (pString == NULL) {
+ goto exit;
+ }
+ tkinter_libname = fname2char(pString);
+ if (tkinter_libname == NULL) {
+ goto exit;
+ }
+ tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY);
+ if (tkinter_lib == NULL) {
+ /* Perhaps it is a cffi module, like in PyPy? */
+ pString = PyObject_GetAttrString(pSubmodule, "tklib_cffi");
+ if (pString == NULL) {
+ goto fail;
+ }
+ pString = PyObject_GetAttrString(pString, "__file__");
+ if (pString == NULL) {
+ goto fail;
+ }
+ tkinter_libname = fname2char(pString);
+ if (tkinter_libname == NULL) {
+ goto fail;
+ }
+ tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY);
+ }
+ if (tkinter_lib == NULL) {
+ goto fail;
+ }
+ ret = _func_loader(tkinter_lib);
+ // dlclose probably safe because tkinter has been imported.
+ dlclose(tkinter_lib);
+ goto exit;
+fail:
+ PyErr_SetString(PyExc_RuntimeError,
+ "Cannot dlopen tkinter module file");
+exit:
+ Py_XDECREF(pModule);
+ Py_XDECREF(pSubmodule);
+ Py_XDECREF(pString);
+ return ret;
+}
+#endif // end not Windows
+
+#if PY_MAJOR_VERSION >= 3
+static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions,
+ NULL, NULL, NULL, NULL };
+
+PyMODINIT_FUNC PyInit__tkagg(void)
+{
+ PyObject *m;
+
+ m = PyModule_Create(&_tkagg_module);
+
+ return (load_tkinter_funcs() == 0) ? m : NULL;
+}
+#else
+PyMODINIT_FUNC init_tkagg(void)
+{
+ Py_InitModule("_tkagg", functions);
+
+ load_tkinter_funcs();
+}
+#endif
diff --git a/contrib/python/matplotlib/py2/src/_tkmini.h b/contrib/python/matplotlib/py2/src/_tkmini.h
new file mode 100644
index 0000000000..9b730b6c8c
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_tkmini.h
@@ -0,0 +1,128 @@
+/* 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
+
+/* Tcl header excerpts */
+#define TCL_OK 0
+#define TCL_ERROR 1
+
+/*
+ * Users of versions of Tcl >= 8.6 encouraged to tread Tcl_Interp as an opaque
+ * pointer. The following definition results when TCL_NO_DEPRECATED defined.
+ */
+typedef struct Tcl_Interp Tcl_Interp;
+
+typedef struct Tcl_Command_ *Tcl_Command;
+typedef void *ClientData;
+
+typedef int (Tcl_CmdProc) (ClientData clientData, Tcl_Interp
+ *interp, int argc, const char *argv[]);
+typedef void (Tcl_CmdDeleteProc) (ClientData clientData);
+
+/* Typedefs derived from function signatures in Tcl header */
+/* Tcl_CreateCommand */
+typedef Tcl_Command (*Tcl_CreateCommand_t)(Tcl_Interp *interp,
+ const char *cmdName, Tcl_CmdProc *proc,
+ ClientData clientData,
+ Tcl_CmdDeleteProc *deleteProc);
+/* Tcl_AppendResult */
+typedef void (*Tcl_AppendResult_t) (Tcl_Interp *interp, ...);
+
+/* Tk header excerpts */
+typedef struct Tk_Window_ *Tk_Window;
+
+typedef void *Tk_PhotoHandle;
+
+typedef struct Tk_PhotoImageBlock
+{
+ unsigned char *pixelPtr;
+ int width;
+ int height;
+ int pitch;
+ int pixelSize;
+ int offset[4];
+} Tk_PhotoImageBlock;
+
+/* Typedefs derived from function signatures in Tk header */
+/* Tk_MainWindow */
+typedef Tk_Window (*Tk_MainWindow_t) (Tcl_Interp *interp);
+typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char
+ *imageName);
+/* Tk_PhotoPutBLock_NoComposite typedef */
+typedef void (*Tk_PhotoPutBlock_NoComposite_t) (Tk_PhotoHandle handle,
+ Tk_PhotoImageBlock *blockPtr, int x, int y,
+ int width, int height);
+/* Tk_PhotoBlank */
+typedef void (*Tk_PhotoBlank_t) (Tk_PhotoHandle handle);
+
+/*
+ * end block for C++
+ */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/contrib/python/matplotlib/py2/src/_ttconv.cpp b/contrib/python/matplotlib/py2/src/_ttconv.cpp
new file mode 100644
index 0000000000..e0aa4611d2
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/_ttconv.cpp
@@ -0,0 +1,307 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/*
+ _ttconv.c
+
+ Python wrapper for TrueType conversion library in ../ttconv.
+ */
+
+#include "mplutils.h"
+
+#include <Python.h>
+#include "ttconv/pprdrv.h"
+#include "py_exceptions.h"
+#include <vector>
+#include <cassert>
+
+/**
+ * An implementation of TTStreamWriter that writes to a Python
+ * file-like object.
+ */
+class PythonFileWriter : public TTStreamWriter
+{
+ PyObject *_write_method;
+
+ public:
+ PythonFileWriter()
+ {
+ _write_method = NULL;
+ }
+
+ ~PythonFileWriter()
+ {
+ Py_XDECREF(_write_method);
+ }
+
+ void set(PyObject *write_method)
+ {
+ Py_XDECREF(_write_method);
+ _write_method = write_method;
+ Py_XINCREF(_write_method);
+ }
+
+ virtual void write(const char *a)
+ {
+ PyObject *result = NULL;
+ if (_write_method) {
+ PyObject *decoded = NULL;
+ decoded = PyUnicode_DecodeLatin1(a, strlen(a), "");
+ if (decoded == NULL) {
+ throw py::exception();
+ }
+ result = PyObject_CallFunction(_write_method, (char *)"O", decoded);
+ Py_DECREF(decoded);
+ if (!result) {
+ throw py::exception();
+ }
+ Py_DECREF(result);
+ }
+ }
+};
+
+int fileobject_to_PythonFileWriter(PyObject *object, void *address)
+{
+ PythonFileWriter *file_writer = (PythonFileWriter *)address;
+
+ PyObject *write_method = PyObject_GetAttrString(object, "write");
+ if (write_method == NULL || !PyCallable_Check(write_method)) {
+ PyErr_SetString(PyExc_TypeError, "Expected a file-like object with a write method.");
+ return 0;
+ }
+
+ file_writer->set(write_method);
+ Py_DECREF(write_method);
+
+ return 1;
+}
+
+int pyiterable_to_vector_int(PyObject *object, void *address)
+{
+ std::vector<int> *result = (std::vector<int> *)address;
+
+ PyObject *iterator = PyObject_GetIter(object);
+ if (!iterator) {
+ return 0;
+ }
+
+ PyObject *item;
+ while ((item = PyIter_Next(iterator))) {
+#if PY3K
+ long value = PyLong_AsLong(item);
+#else
+ long value = PyInt_AsLong(item);
+#endif
+ Py_DECREF(item);
+ if (value == -1 && PyErr_Occurred()) {
+ return 0;
+ }
+ result->push_back((int)value);
+ }
+
+ Py_DECREF(iterator);
+
+ return 1;
+}
+
+static PyObject *convert_ttf_to_ps(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ const char *filename;
+ PythonFileWriter output;
+ int fonttype;
+ std::vector<int> glyph_ids;
+
+ static const char *kwlist[] = { "filename", "output", "fonttype", "glyph_ids", NULL };
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+#if PY_MAJOR_VERSION == 3
+ "yO&i|O&:convert_ttf_to_ps",
+#else
+ "sO&i|O&:convert_ttf_to_ps",
+#endif
+ (char **)kwlist,
+ &filename,
+ fileobject_to_PythonFileWriter,
+ &output,
+ &fonttype,
+ pyiterable_to_vector_int,
+ &glyph_ids)) {
+ return NULL;
+ }
+
+ if (fonttype != 3 && fonttype != 42) {
+ PyErr_SetString(PyExc_ValueError,
+ "fonttype must be either 3 (raw Postscript) or 42 "
+ "(embedded Truetype)");
+ return NULL;
+ }
+
+ try
+ {
+ insert_ttfont(filename, output, (font_type_enum)fonttype, glyph_ids);
+ }
+ catch (TTException &e)
+ {
+ PyErr_SetString(PyExc_RuntimeError, e.getMessage());
+ return NULL;
+ }
+ catch (const py::exception &)
+ {
+ return NULL;
+ }
+ catch (...)
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+class PythonDictionaryCallback : public TTDictionaryCallback
+{
+ PyObject *_dict;
+
+ public:
+ PythonDictionaryCallback(PyObject *dict)
+ {
+ _dict = dict;
+ }
+
+ virtual void add_pair(const char *a, const char *b)
+ {
+ assert(a != NULL);
+ assert(b != NULL);
+ PyObject *value = PyBytes_FromString(b);
+ if (!value) {
+ throw py::exception();
+ }
+ if (PyDict_SetItemString(_dict, a, value)) {
+ Py_DECREF(value);
+ throw py::exception();
+ }
+ Py_DECREF(value);
+ }
+};
+
+static PyObject *py_get_pdf_charprocs(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ const char *filename;
+ std::vector<int> glyph_ids;
+ PyObject *result;
+
+ static const char *kwlist[] = { "filename", "glyph_ids", NULL };
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+#if PY_MAJOR_VERSION == 3
+ "y|O&:get_pdf_charprocs",
+#else
+ "s|O&:get_pdf_charprocs",
+#endif
+ (char **)kwlist,
+ &filename,
+ pyiterable_to_vector_int,
+ &glyph_ids)) {
+ return NULL;
+ }
+
+ result = PyDict_New();
+ if (!result) {
+ return NULL;
+ }
+
+ PythonDictionaryCallback dict(result);
+
+ try
+ {
+ ::get_pdf_charprocs(filename, glyph_ids, dict);
+ }
+ catch (TTException &e)
+ {
+ Py_DECREF(result);
+ PyErr_SetString(PyExc_RuntimeError, e.getMessage());
+ return NULL;
+ }
+ catch (const py::exception &)
+ {
+ Py_DECREF(result);
+ return NULL;
+ }
+ catch (...)
+ {
+ Py_DECREF(result);
+ PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
+ return NULL;
+ }
+
+ return result;
+}
+
+static PyMethodDef ttconv_methods[] =
+{
+ {
+ "convert_ttf_to_ps", (PyCFunction)convert_ttf_to_ps, METH_VARARGS | METH_KEYWORDS,
+ "convert_ttf_to_ps(filename, output, fonttype, glyph_ids)\n"
+ "\n"
+ "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.\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."
+ },
+ {
+ "get_pdf_charprocs", (PyCFunction)py_get_pdf_charprocs, METH_VARARGS | METH_KEYWORDS,
+ "get_pdf_charprocs(filename, glyph_ids)\n"
+ "\n"
+ "Given a Truetype font file, returns a dictionary containing the PDF Type 3\n"
+ "representation of its paths. Useful for subsetting a Truetype font inside\n"
+ "of a PDF file.\n"
+ "\n"
+ "filename is the path to a TTF font file.\n"
+ "glyph_ids is a list of the numeric glyph ids to include.\n"
+ "The return value is a dictionary where the keys are glyph names and\n"
+ "the values are the stream content needed to render that glyph. This\n"
+ "is useful to generate the CharProcs dictionary in a PDF Type 3 font.\n"
+ },
+ {0, 0, 0, 0} /* Sentinel */
+};
+
+static const char *module_docstring =
+ "Module to handle converting and subsetting TrueType "
+ "fonts to Postscript Type 3, Postscript Type 42 and "
+ "Pdf Type 3 fonts.";
+
+#if PY3K
+static PyModuleDef ttconv_module = {
+ PyModuleDef_HEAD_INIT,
+ "ttconv",
+ module_docstring,
+ -1,
+ ttconv_methods,
+ NULL, NULL, NULL, NULL
+};
+
+PyMODINIT_FUNC
+PyInit_ttconv(void)
+{
+ PyObject* m;
+
+ m = PyModule_Create(&ttconv_module);
+
+ return m;
+}
+#else
+PyMODINIT_FUNC
+initttconv(void)
+{
+ Py_InitModule3("ttconv", ttconv_methods, module_docstring);
+}
+#endif
diff --git a/contrib/python/matplotlib/py2/src/agg_workaround.h b/contrib/python/matplotlib/py2/src/agg_workaround.h
new file mode 100644
index 0000000000..bfadf39284
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/agg_workaround.h
@@ -0,0 +1,85 @@
+#ifndef __AGG_WORKAROUND_H__
+#define __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/py2/src/array.h b/contrib/python/matplotlib/py2/src/array.h
new file mode 100644
index 0000000000..8056366a1c
--- /dev/null
+++ b/contrib/python/matplotlib/py2/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 _SCALAR_H_
+#define _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/py2/src/file_compat.h b/contrib/python/matplotlib/py2/src/file_compat.h
new file mode 100644
index 0000000000..691133dcbb
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/file_compat.h
@@ -0,0 +1,240 @@
+#ifndef __FILE_COMPAT_H__
+#define __FILE_COMPAT_H__
+
+#include <Python.h>
+#include <stdio.h>
+#include "numpy/npy_common.h"
+#include "numpy/ndarrayobject.h"
+#include "mplutils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if defined(_MSC_VER) && defined(_WIN64) && (_MSC_VER > 1400)
+ #include <io.h>
+ #define mpl_fseek _fseeki64
+ #define mpl_ftell _ftelli64
+ #define mpl_lseek _lseeki64
+ #define mpl_off_t npy_int64
+
+ #if NPY_SIZEOF_INT == 8
+ #define MPL_OFF_T_PYFMT "i"
+ #elif NPY_SIZEOF_LONG == 8
+ #define MPL_OFF_T_PYFMT "l"
+ #elif NPY_SIZEOF_LONGLONG == 8
+ #define MPL_OFF_T_PYFMT "L"
+ #else
+ #error Unsupported size for type off_t
+ #endif
+#else
+ #define mpl_fseek fseek
+ #define mpl_ftell ftell
+ #define mpl_lseek lseek
+ #define mpl_off_t off_t
+
+ #if NPY_SIZEOF_INT == NPY_SIZEOF_SHORT
+ #define MPL_OFF_T_PYFMT "h"
+ #elif NPY_SIZEOF_INT == NPY_SIZEOF_INT
+ #define MPL_OFF_T_PYFMT "i"
+ #elif NPY_SIZEOF_INT == NPY_SIZEOF_LONG
+ #define MPL_OFF_T_PYFMT "l"
+ #elif NPY_SIZEOF_INT == NPY_SIZEOF_LONGLONG
+ #define MPL_OFF_T_PYFMT "L"
+ #else
+ #error Unsupported size for type off_t
+ #endif
+#endif
+
+/*
+ * PyFile_* compatibility
+ */
+#if PY3K | defined(PYPY_VERSION)
+
+/*
+ * Get a FILE* handle to the file represented by the Python object
+ */
+static NPY_INLINE FILE *mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos)
+{
+ int fd, fd2;
+ PyObject *ret, *os;
+ mpl_off_t pos;
+ FILE *handle;
+
+ if (mode[0] != 'r') {
+ /* Flush first to ensure things end up in the file in the correct order */
+ ret = PyObject_CallMethod(file, (char *)"flush", (char *)"");
+ if (ret == NULL) {
+ return NULL;
+ }
+ Py_DECREF(ret);
+ }
+
+ fd = PyObject_AsFileDescriptor(file);
+ if (fd == -1) {
+ return NULL;
+ }
+
+ /* The handle needs to be dup'd because we have to call fclose
+ at the end */
+ os = PyImport_ImportModule("os");
+ if (os == NULL) {
+ return NULL;
+ }
+ ret = PyObject_CallMethod(os, (char *)"dup", (char *)"i", fd);
+ Py_DECREF(os);
+ if (ret == NULL) {
+ return NULL;
+ }
+ fd2 = PyNumber_AsSsize_t(ret, NULL);
+ Py_DECREF(ret);
+
+/* Convert to FILE* handle */
+#ifdef _WIN32
+ handle = _fdopen(fd2, mode);
+#else
+ handle = fdopen(fd2, mode);
+#endif
+ if (handle == NULL) {
+ PyErr_SetString(PyExc_IOError, "Getting a FILE* from a Python file object failed");
+ }
+
+ /* Record the original raw file handle position */
+ *orig_pos = mpl_ftell(handle);
+ if (*orig_pos == -1) {
+ // handle is a stream, so we don't have to worry about this
+ return handle;
+ }
+
+ /* Seek raw handle to the Python-side position */
+ ret = PyObject_CallMethod(file, (char *)"tell", (char *)"");
+ if (ret == NULL) {
+ fclose(handle);
+ return NULL;
+ }
+ pos = PyNumber_AsSsize_t(ret, PyExc_OverflowError);
+ Py_DECREF(ret);
+ if (PyErr_Occurred()) {
+ fclose(handle);
+ return NULL;
+ }
+ if (mpl_fseek(handle, pos, SEEK_SET) == -1) {
+ PyErr_SetString(PyExc_IOError, "seeking file failed");
+ return NULL;
+ }
+ return handle;
+}
+
+/*
+ * Close the dup-ed file handle, and seek the Python one to the current position
+ */
+static NPY_INLINE int mpl_PyFile_DupClose(PyObject *file, FILE *handle, mpl_off_t orig_pos)
+{
+ PyObject *exc_type = NULL, *exc_value = NULL, *exc_tb = NULL;
+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
+
+ int fd;
+ PyObject *ret;
+ mpl_off_t position;
+
+ position = mpl_ftell(handle);
+
+ /* Close the FILE* handle */
+ fclose(handle);
+
+ /* Restore original file handle position, in order to not confuse
+ Python-side data structures. Note that this would fail if an exception
+ is currently set, which can happen as this function is called in cleanup
+ code, so we need to carefully fetch and restore the exception state. */
+ fd = PyObject_AsFileDescriptor(file);
+ if (fd == -1) {
+ goto fail;
+ }
+ if (mpl_lseek(fd, orig_pos, SEEK_SET) != -1) {
+ if (position == -1) {
+ PyErr_SetString(PyExc_IOError, "obtaining file position failed");
+ goto fail;
+ }
+
+ /* Seek Python-side handle to the FILE* handle position */
+ ret = PyObject_CallMethod(file, (char *)"seek", (char *)(MPL_OFF_T_PYFMT "i"), position, 0);
+ if (ret == NULL) {
+ goto fail;
+ }
+ Py_DECREF(ret);
+ }
+ PyErr_Restore(exc_type, exc_value, exc_tb);
+ return 0;
+fail:
+ Py_XDECREF(exc_type);
+ Py_XDECREF(exc_value);
+ Py_XDECREF(exc_tb);
+ return -1;
+}
+
+static NPY_INLINE int mpl_PyFile_Check(PyObject *file)
+{
+ int fd;
+ fd = PyObject_AsFileDescriptor(file);
+ if (fd == -1) {
+ PyErr_Clear();
+ return 0;
+ }
+ return 1;
+}
+
+#else
+
+static NPY_INLINE FILE *mpl_PyFile_Dup(PyObject *file, const char *mode, mpl_off_t *orig_pos)
+{
+ return PyFile_AsFile(file);
+}
+
+static NPY_INLINE int mpl_PyFile_DupClose(PyObject *file, FILE *handle, mpl_off_t orig_pos)
+{
+ // deliberately nothing
+ return 0;
+}
+
+static NPY_INLINE int mpl_PyFile_Check(PyObject *file)
+{
+ return PyFile_Check(file);
+}
+
+#endif
+
+static NPY_INLINE PyObject *mpl_PyFile_OpenFile(PyObject *filename, const char *mode)
+{
+ PyObject *open;
+ open = PyDict_GetItemString(PyEval_GetBuiltins(), "open");
+ if (open == NULL) {
+ return NULL;
+ }
+ return PyObject_CallFunction(open, (char *)"Os", filename, mode);
+}
+
+static NPY_INLINE int mpl_PyFile_CloseFile(PyObject *file)
+{
+ PyObject *type, *value, *tb;
+ PyErr_Fetch(&type, &value, &tb);
+
+ PyObject *ret;
+
+ ret = PyObject_CallMethod(file, (char *)"close", NULL);
+ if (ret == NULL) {
+ goto fail;
+ }
+ Py_DECREF(ret);
+ PyErr_Restore(type, value, tb);
+ return 0;
+fail:
+ Py_XDECREF(type);
+ Py_XDECREF(value);
+ Py_XDECREF(tb);
+ return -1;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef __FILE_COMPAT_H__ */
diff --git a/contrib/python/matplotlib/py2/src/ft2font.cpp b/contrib/python/matplotlib/py2/src/ft2font.cpp
new file mode 100644
index 0000000000..4b46ec823e
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/ft2font.cpp
@@ -0,0 +1,808 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#define NO_IMPORT_ARRAY
+
+#include <algorithm>
+#include <stdexcept>
+#include <string>
+
+#include "ft2font.h"
+#include "mplutils.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://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;
+
+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 = CLAMP(x, 0, image_width);
+ FT_Int y1 = CLAMP(y, 0, image_height);
+ FT_Int x2 = CLAMP(x + char_width, 0, image_width);
+ FT_Int y2 = CLAMP(y + char_height, 0, image_height);
+
+ FT_Int x_start = MAX(0, -x);
+ FT_Int y_offset = y1 - 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;
+}
+
+inline double conv(long v)
+{
+ return double(v) / 64.0;
+}
+
+int FT2Font::get_path_count()
+{
+ // get the glyph as a path, a list of (COMMAND, *args) as described in matplotlib.path
+ // this code is from agg's decompose_ft_outline with minor modifications
+
+ if (!face->glyph) {
+ throw std::runtime_error("No glyph loaded");
+ }
+
+ FT_Outline &outline = face->glyph->outline;
+
+ FT_Vector v_last;
+ FT_Vector v_control;
+ FT_Vector v_start;
+
+ FT_Vector *point;
+ FT_Vector *limit;
+ char *tags;
+
+ int n; // index of contour in outline
+ int first; // index of first point in contour
+ char tag; // current point's state
+ int count;
+
+ count = 0;
+ first = 0;
+ for (n = 0; n < outline.n_contours; n++) {
+ int last; // index of last point in contour
+ bool starts_with_last;
+
+ last = outline.contours[n];
+ limit = outline.points + last;
+
+ v_start = outline.points[first];
+ v_last = outline.points[last];
+
+ v_control = v_start;
+
+ point = outline.points + first;
+ tags = outline.tags + first;
+ tag = FT_CURVE_TAG(tags[0]);
+
+ // A contour cannot start with a cubic control point!
+ if (tag == FT_CURVE_TAG_CUBIC) {
+ throw std::runtime_error("A contour cannot start with a cubic control point");
+ } else if (tag == FT_CURVE_TAG_CONIC) {
+ starts_with_last = true;
+ } else {
+ starts_with_last = false;
+ }
+
+ count++;
+
+ while (point < limit) {
+ if (!starts_with_last) {
+ point++;
+ tags++;
+ }
+ starts_with_last = false;
+
+ tag = FT_CURVE_TAG(tags[0]);
+ switch (tag) {
+ case FT_CURVE_TAG_ON: // emit a single line_to
+ {
+ count++;
+ continue;
+ }
+
+ case FT_CURVE_TAG_CONIC: // consume conic arcs
+ {
+ Count_Do_Conic:
+ if (point < limit) {
+ point++;
+ tags++;
+ tag = FT_CURVE_TAG(tags[0]);
+
+ if (tag == FT_CURVE_TAG_ON) {
+ count += 2;
+ continue;
+ }
+
+ if (tag != FT_CURVE_TAG_CONIC) {
+ throw std::runtime_error("Invalid font");
+ }
+
+ count += 2;
+
+ goto Count_Do_Conic;
+ }
+
+ count += 2;
+
+ goto Count_Close;
+ }
+
+ default: // FT_CURVE_TAG_CUBIC
+ {
+ if (point + 1 > limit || FT_CURVE_TAG(tags[1]) != FT_CURVE_TAG_CUBIC) {
+ throw std::runtime_error("Invalid font");
+ }
+
+ point += 2;
+ tags += 2;
+
+ if (point <= limit) {
+ count += 3;
+ continue;
+ }
+
+ count += 3;
+
+ goto Count_Close;
+ }
+ }
+ }
+
+ Count_Close:
+ count++;
+ first = last + 1;
+ }
+
+ return count;
+}
+
+void FT2Font::get_path(double *outpoints, unsigned char *outcodes)
+{
+ FT_Outline &outline = face->glyph->outline;
+ bool flip_y = false; // todo, pass me as kwarg
+
+ FT_Vector v_last;
+ FT_Vector v_control;
+ FT_Vector v_start;
+
+ FT_Vector *point;
+ FT_Vector *limit;
+ char *tags;
+
+ int n; // index of contour in outline
+ int first; // index of first point in contour
+ char tag; // current point's state
+
+ first = 0;
+ for (n = 0; n < outline.n_contours; n++) {
+ int last; // index of last point in contour
+ bool starts_with_last;
+
+ last = outline.contours[n];
+ limit = outline.points + last;
+
+ v_start = outline.points[first];
+ v_last = outline.points[last];
+
+ v_control = v_start;
+
+ point = outline.points + first;
+ tags = outline.tags + first;
+ tag = FT_CURVE_TAG(tags[0]);
+
+ double x, y;
+ if (tag != FT_CURVE_TAG_ON) {
+ x = conv(v_last.x);
+ y = flip_y ? -conv(v_last.y) : conv(v_last.y);
+ starts_with_last = true;
+ } else {
+ x = conv(v_start.x);
+ y = flip_y ? -conv(v_start.y) : conv(v_start.y);
+ starts_with_last = false;
+ }
+
+ *(outpoints++) = x;
+ *(outpoints++) = y;
+ *(outcodes++) = MOVETO;
+
+ while (point < limit) {
+ if (!starts_with_last) {
+ point++;
+ tags++;
+ }
+ starts_with_last = false;
+
+ tag = FT_CURVE_TAG(tags[0]);
+ switch (tag) {
+ case FT_CURVE_TAG_ON: // emit a single line_to
+ {
+ double x = conv(point->x);
+ double y = flip_y ? -conv(point->y) : conv(point->y);
+ *(outpoints++) = x;
+ *(outpoints++) = y;
+ *(outcodes++) = LINETO;
+ continue;
+ }
+
+ case FT_CURVE_TAG_CONIC: // consume conic arcs
+ {
+ v_control.x = point->x;
+ v_control.y = point->y;
+
+ Do_Conic:
+ if (point < limit) {
+ FT_Vector vec;
+ FT_Vector v_middle;
+
+ point++;
+ tags++;
+ tag = FT_CURVE_TAG(tags[0]);
+
+ vec.x = point->x;
+ vec.y = point->y;
+
+ if (tag == FT_CURVE_TAG_ON) {
+ double xctl = conv(v_control.x);
+ double yctl = flip_y ? -conv(v_control.y) : conv(v_control.y);
+ double xto = conv(vec.x);
+ double yto = flip_y ? -conv(vec.y) : conv(vec.y);
+ *(outpoints++) = xctl;
+ *(outpoints++) = yctl;
+ *(outpoints++) = xto;
+ *(outpoints++) = yto;
+ *(outcodes++) = CURVE3;
+ *(outcodes++) = CURVE3;
+ continue;
+ }
+
+ v_middle.x = (v_control.x + vec.x) / 2;
+ v_middle.y = (v_control.y + vec.y) / 2;
+
+ double xctl = conv(v_control.x);
+ double yctl = flip_y ? -conv(v_control.y) : conv(v_control.y);
+ double xto = conv(v_middle.x);
+ double yto = flip_y ? -conv(v_middle.y) : conv(v_middle.y);
+ *(outpoints++) = xctl;
+ *(outpoints++) = yctl;
+ *(outpoints++) = xto;
+ *(outpoints++) = yto;
+ *(outcodes++) = CURVE3;
+ *(outcodes++) = CURVE3;
+
+ v_control = vec;
+ goto Do_Conic;
+ }
+ double xctl = conv(v_control.x);
+ double yctl = flip_y ? -conv(v_control.y) : conv(v_control.y);
+ double xto = conv(v_start.x);
+ double yto = flip_y ? -conv(v_start.y) : conv(v_start.y);
+
+ *(outpoints++) = xctl;
+ *(outpoints++) = yctl;
+ *(outpoints++) = xto;
+ *(outpoints++) = yto;
+ *(outcodes++) = CURVE3;
+ *(outcodes++) = CURVE3;
+
+ goto Close;
+ }
+
+ default: // FT_CURVE_TAG_CUBIC
+ {
+ FT_Vector vec1, vec2;
+
+ vec1.x = point[0].x;
+ vec1.y = point[0].y;
+ vec2.x = point[1].x;
+ vec2.y = point[1].y;
+
+ point += 2;
+ tags += 2;
+
+ if (point <= limit) {
+ FT_Vector vec;
+
+ vec.x = point->x;
+ vec.y = point->y;
+
+ double xctl1 = conv(vec1.x);
+ double yctl1 = flip_y ? -conv(vec1.y) : conv(vec1.y);
+ double xctl2 = conv(vec2.x);
+ double yctl2 = flip_y ? -conv(vec2.y) : conv(vec2.y);
+ double xto = conv(vec.x);
+ double yto = flip_y ? -conv(vec.y) : conv(vec.y);
+
+ (*outpoints++) = xctl1;
+ (*outpoints++) = yctl1;
+ (*outpoints++) = xctl2;
+ (*outpoints++) = yctl2;
+ (*outpoints++) = xto;
+ (*outpoints++) = yto;
+ (*outcodes++) = CURVE4;
+ (*outcodes++) = CURVE4;
+ (*outcodes++) = CURVE4;
+ continue;
+ }
+
+ double xctl1 = conv(vec1.x);
+ double yctl1 = flip_y ? -conv(vec1.y) : conv(vec1.y);
+ double xctl2 = conv(vec2.x);
+ double yctl2 = flip_y ? -conv(vec2.y) : conv(vec2.y);
+ double xto = conv(v_start.x);
+ double yto = flip_y ? -conv(v_start.y) : conv(v_start.y);
+ (*outpoints++) = xctl1;
+ (*outpoints++) = yctl1;
+ (*outpoints++) = xctl2;
+ (*outpoints++) = yctl2;
+ (*outpoints++) = xto;
+ (*outpoints++) = yto;
+ (*outcodes++) = CURVE4;
+ (*outcodes++) = CURVE4;
+ (*outcodes++) = CURVE4;
+
+ goto Close;
+ }
+ }
+ }
+
+ Close:
+ (*outpoints++) = 0.0;
+ (*outpoints++) = 0.0;
+ (*outcodes++) = ENDPOLY;
+ first = last + 1;
+ }
+}
+
+FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_) : image(), face(NULL)
+{
+ clear();
+
+ int error = FT_Open_Face(_ft2Library, &open_args, 0, &face);
+
+ if (error == FT_Err_Unknown_File_Format) {
+ throw std::runtime_error("Can not load face. Unknown file format.");
+ } else if (error == FT_Err_Cannot_Open_Resource) {
+ throw std::runtime_error("Can not load face. Can not open resource.");
+ } else if (error == FT_Err_Invalid_File_Format) {
+ throw std::runtime_error("Can not load face. Invalid file format.");
+ } else if (error) {
+ throw std::runtime_error("Can not load face.");
+ }
+
+ // 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 std::runtime_error("Could not set the fontsize");
+ }
+
+ 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);
+}
+
+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()
+{
+ angle = 0.0;
+
+ pen.x = 0;
+ pen.y = 0;
+
+ for (size_t i = 0; i < glyphs.size(); i++) {
+ FT_Done_Glyph(glyphs[i]);
+ }
+
+ glyphs.clear();
+}
+
+void FT2Font::set_size(double ptsize, double dpi)
+{
+ int error = FT_Set_Char_Size(
+ face, (long)(ptsize * 64), 0, (unsigned int)(dpi * hinting_factor), (unsigned int)dpi);
+ FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 };
+ FT_Set_Transform(face, &transform, 0);
+
+ if (error) {
+ throw std::runtime_error("Could not set the fontsize");
+ }
+}
+
+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_Set_Charmap(face, charmap)) {
+ throw std::runtime_error("Could not set the charmap");
+ }
+}
+
+void FT2Font::select_charmap(unsigned long i)
+{
+ if (FT_Select_Charmap(face, (FT_Encoding)i)) {
+ throw std::runtime_error("Could not set the charmap");
+ }
+}
+
+int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode)
+{
+ if (!FT_HAS_KERNING(face)) {
+ return 0;
+ }
+ FT_Vector delta;
+
+ if (!FT_Get_Kerning(face, left, right, mode, &delta)) {
+ return (int)(delta.x) / (hinting_factor << 6);
+ } else {
+ return 0;
+ }
+}
+
+void FT2Font::set_text(
+ size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys)
+{
+ angle = angle / 360.0 * 2 * M_PI;
+
+ // this computes width and height in subpixels so we have to divide by 64
+ matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L);
+ matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
+ matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L);
+ matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L);
+
+ FT_Bool use_kerning = FT_HAS_KERNING(face);
+ FT_UInt previous = 0;
+
+ clear();
+
+ bbox.xMin = bbox.yMin = 32000;
+ bbox.xMax = bbox.yMax = -32000;
+
+ for (unsigned int n = 0; n < N; n++) {
+ std::string thischar("?");
+ FT_UInt glyph_index;
+ FT_BBox glyph_bbox;
+ FT_Pos last_advance;
+
+ glyph_index = FT_Get_Char_Index(face, codepoints[n]);
+
+ // retrieve kerning distance and move pen position
+ if (use_kerning && previous && glyph_index) {
+ FT_Vector delta;
+ FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
+ pen.x += (delta.x << 10) / (hinting_factor << 16);
+ }
+ error = FT_Load_Glyph(face, glyph_index, flags);
+ if (error) {
+ throw std::runtime_error("could not load glyph");
+ }
+ // ignore errors, jump to next glyph
+
+ // extract glyph image and store it in our table
+
+ FT_Glyph thisGlyph;
+ error = FT_Get_Glyph(face->glyph, &thisGlyph);
+
+ if (error) {
+ throw std::runtime_error("could not get glyph");
+ }
+ // ignore errors, jump to next glyph
+
+ last_advance = 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;
+ glyphs.push_back(thisGlyph);
+ }
+
+ 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)
+{
+ int error = FT_Load_Char(face, (unsigned long)charcode, flags);
+
+ if (error) {
+ throw std::runtime_error("Could not load charcode");
+ }
+
+ FT_Glyph thisGlyph;
+ error = FT_Get_Glyph(face->glyph, &thisGlyph);
+
+ if (error) {
+ throw std::runtime_error("Could not get glyph");
+ }
+
+ glyphs.push_back(thisGlyph);
+}
+
+void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags)
+{
+ int error = FT_Load_Glyph(face, glyph_index, flags);
+
+ if (error) {
+ throw std::runtime_error("Could not load glyph");
+ }
+
+ FT_Glyph thisGlyph;
+ error = FT_Get_Glyph(face->glyph, &thisGlyph);
+
+ if (error) {
+ throw std::runtime_error("Could not load glyph");
+ }
+
+ glyphs.push_back(thisGlyph);
+}
+
+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)
+{
+ size_t width = (bbox.xMax - bbox.xMin) / 64 + 2;
+ size_t height = (bbox.yMax - bbox.yMin) / 64 + 2;
+
+ image.resize(width, height);
+
+ for (size_t n = 0; n < glyphs.size(); n++) {
+ error = FT_Glyph_To_Bitmap(
+ &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
+ if (error) {
+ throw std::runtime_error("Could not convert glyph to bitmap");
+ }
+
+ 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 / 64.));
+ FT_Int y = (FT_Int)((bbox.yMax / 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++) {
+
+ error = FT_Glyph_To_Bitmap(
+ &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
+ if (error) {
+ throw std::runtime_error("Could not convert glyph to bitmap");
+ }
+
+ 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 / 64.);
+ FT_Int y = (FT_Int)(bbox.yMax / 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");
+ }
+
+ 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 std::runtime_error("Could not convert glyph to bitmap");
+ }
+
+ 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)
+{
+ 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_Get_Glyph_Name(face, glyph_number, buffer, 128)) {
+ throw std::runtime_error("Could not get glyph names.");
+ }
+ }
+}
+
+long FT2Font::get_name_index(char *name)
+{
+ return FT_Get_Name_Index(face, (FT_String *)name);
+}
diff --git a/contrib/python/matplotlib/py2/src/ft2font.h b/contrib/python/matplotlib/py2/src/ft2font.h
new file mode 100644
index 0000000000..c60d5432cf
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/ft2font.h
@@ -0,0 +1,139 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* A python interface to FreeType */
+#ifndef _FT2FONT_H
+#define _FT2FONT_H
+#include <vector>
+#include <stdint.h>
+
+extern "C" {
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_SFNT_NAMES_H
+#include FT_TYPE1_TABLES_H
+#include FT_TRUETYPE_TABLES_H
+}
+
+/*
+ By definition, FT_FIXED as 2 16bit values stored in a single long.
+ */
+#define FIXED_MAJOR(val) (long)((val & 0xffff000) >> 16)
+#define FIXED_MINOR(val) (long)(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 write_bitmap(FILE *fp) const;
+ 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);
+ 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);
+ void load_char(long charcode, FT_Int32 flags);
+ 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);
+ long get_name_index(char *name);
+ int get_path_count();
+ void get_path(double *outpoints, unsigned char *outcodes);
+
+ FT_Face &get_face()
+ {
+ return face;
+ }
+ FT2Image &get_image()
+ {
+ return image;
+ }
+ FT_Glyph &get_last_glyph()
+ {
+ return glyphs.back();
+ }
+ size_t get_last_glyph_index()
+ {
+ return glyphs.size() - 1;
+ }
+ size_t get_num_glyphs()
+ {
+ return glyphs.size();
+ }
+ long get_hinting_factor()
+ {
+ return hinting_factor;
+ }
+
+ private:
+ FT2Image image;
+ FT_Face face;
+ FT_Matrix matrix; /* transformation matrix */
+ FT_Vector pen; /* untransformed origin */
+ FT_Error error;
+ std::vector<FT_Glyph> glyphs;
+ std::vector<FT_Vector> pos;
+ FT_BBox bbox;
+ FT_Pos advance;
+ double angle;
+ double ptsize;
+ double dpi;
+ long hinting_factor;
+
+ void set_scalable_attributes();
+
+ // prevent copying
+ FT2Font(const FT2Font &);
+ FT2Font &operator=(const FT2Font &);
+};
+
+#endif
diff --git a/contrib/python/matplotlib/py2/src/ft2font_wrapper.cpp b/contrib/python/matplotlib/py2/src/ft2font_wrapper.cpp
new file mode 100644
index 0000000000..49c33b7943
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/ft2font_wrapper.cpp
@@ -0,0 +1,1805 @@
+#include "mplutils.h"
+#include "ft2font.h"
+#include "file_compat.h"
+#include "py_exceptions.h"
+#include "numpy_cpp.h"
+
+// From Python
+#include <structmember.h>
+
+#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 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(x0, y0, x1, y1)\n"
+ "\n"
+ "Draw a rect to the image.\n"
+ "\n";
+
+static PyObject *PyFT2Image_draw_rect(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ 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(x0, y0, x1, y1)\n"
+ "\n"
+ "Draw a filled rect to the image.\n"
+ "\n";
+
+static PyObject *PyFT2Image_draw_rect_filled(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ 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;
+}
+
+const char *PyFT2Image_as_str__doc__ =
+ "s = image.as_str()\n"
+ "\n"
+ "Return the image buffer as a string\n"
+ "\n";
+
+static PyObject *PyFT2Image_as_str(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ // TODO: Use a buffer to avoid the copy
+ return PyBytes_FromStringAndSize((const char *)self->x->get_buffer(),
+ self->x->get_width() * self->x->get_height());
+}
+
+const char *PyFT2Image_as_rgba_str__doc__ =
+ "s = image.as_rgba_str()\n"
+ "\n"
+ "Return the image buffer as a RGBA string\n"
+ "\n";
+
+static PyObject *PyFT2Image_as_rgba_str(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ npy_intp dims[] = {(npy_intp)self->x->get_height(), (npy_intp)self->x->get_width(), 4 };
+ numpy::array_view<unsigned char, 3> result(dims);
+
+ unsigned char *src = self->x->get_buffer();
+ unsigned char *end = src + (self->x->get_width() * self->x->get_height());
+ unsigned char *dst = result.data();
+
+ while (src != end) {
+ *dst++ = 0;
+ *dst++ = 0;
+ *dst++ = 0;
+ *dst++ = *src++;
+ }
+
+ return result.pyobj();
+}
+
+const char *PyFT2Image_as_array__doc__ =
+ "x = image.as_array()\n"
+ "\n"
+ "Return the image buffer as a width x height numpy array of ubyte \n"
+ "\n";
+
+static PyObject *PyFT2Image_as_array(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ npy_intp dims[] = {(npy_intp)self->x->get_height(), (npy_intp)self->x->get_width() };
+ return PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, self->x->get_buffer());
+}
+
+static PyObject *PyFT2Image_get_width(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ return PyLong_FromLong(self->x->get_width());
+}
+
+static PyObject *PyFT2Image_get_height(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ return PyLong_FromLong(self->x->get_height());
+}
+
+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 PyFT2ImageType;
+
+static PyTypeObject *PyFT2Image_init_type(PyObject *m, PyTypeObject *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__},
+ {"as_str", (PyCFunction)PyFT2Image_as_str, METH_NOARGS, PyFT2Image_as_str__doc__},
+ {"as_rgba_str", (PyCFunction)PyFT2Image_as_rgba_str, METH_NOARGS, PyFT2Image_as_rgba_str__doc__},
+ {"as_array", (PyCFunction)PyFT2Image_as_array, METH_NOARGS, PyFT2Image_as_array__doc__},
+ {"get_width", (PyCFunction)PyFT2Image_get_width, METH_NOARGS, NULL},
+ {"get_height", (PyCFunction)PyFT2Image_get_height, METH_NOARGS, NULL},
+ {NULL}
+ };
+
+ static PyBufferProcs buffer_procs;
+ memset(&buffer_procs, 0, sizeof(PyBufferProcs));
+ buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Image_get_buffer;
+
+ memset(type, 0, sizeof(PyTypeObject));
+ type->tp_name = "matplotlib.ft2font.FT2Image";
+ type->tp_basicsize = sizeof(PyFT2Image);
+ type->tp_dealloc = (destructor)PyFT2Image_dealloc;
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER;
+ type->tp_methods = methods;
+ type->tp_new = PyFT2Image_new;
+ type->tp_init = (initproc)PyFT2Image_init;
+ type->tp_as_buffer = &buffer_procs;
+
+ if (PyType_Ready(type) < 0) {
+ return NULL;
+ }
+
+ if (PyModule_AddObject(m, "FT2Image", (PyObject *)type)) {
+ return NULL;
+ }
+
+ return type;
+}
+
+/**********************************************************************
+ * 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_new(const FT_Face &face, const FT_Glyph &glyph, size_t ind, long hinting_factor)
+{
+ PyGlyph *self;
+ self = (PyGlyph *)PyGlyphType.tp_alloc(&PyGlyphType, 0);
+
+ self->glyphInd = ind;
+
+ 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(
+ "iiii", self->bbox.xMin, self->bbox.yMin, self->bbox.xMax, self->bbox.yMax);
+}
+
+static PyTypeObject *PyGlyph_init_type(PyObject *m, PyTypeObject *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}
+ };
+
+ memset(type, 0, sizeof(PyTypeObject));
+ type->tp_name = "matplotlib.ft2font.Glyph";
+ type->tp_basicsize = sizeof(PyGlyph);
+ type->tp_dealloc = (destructor)PyGlyph_dealloc;
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ type->tp_members = members;
+ type->tp_getset = getset;
+
+ if (PyType_Ready(type) < 0) {
+ return NULL;
+ }
+
+ /* Don't need to add to module, since you can't create glyphs
+ directly from Python */
+
+ return type;
+}
+
+/**********************************************************************
+ * FT2Font
+ * */
+
+typedef struct
+{
+ PyObject_HEAD
+ FT2Font *x;
+ PyObject *fname;
+ PyObject *py_file;
+ FILE *fp;
+ int close_file;
+ mpl_off_t offset;
+ FT_StreamRec stream;
+ FT_Byte *mem;
+ size_t mem_size;
+ Py_ssize_t shape[2];
+ Py_ssize_t strides[2];
+ Py_ssize_t suboffsets[2];
+} PyFT2Font;
+
+static unsigned long read_from_file_callback(FT_Stream stream,
+ unsigned long offset,
+ unsigned char *buffer,
+ unsigned long count)
+{
+
+ PyFT2Font *def = (PyFT2Font *)stream->descriptor.pointer;
+
+ if (fseek(def->fp, offset, SEEK_SET) == -1) {
+ return 0;
+ }
+
+ if (count > 0) {
+ return fread(buffer, 1, count, def->fp);
+ }
+
+ return 0;
+}
+
+static void close_file_callback(FT_Stream stream)
+{
+ PyFT2Font *def = (PyFT2Font *)stream->descriptor.pointer;
+
+ if (mpl_PyFile_DupClose(def->py_file, def->fp, def->offset)) {
+ throw std::runtime_error("Couldn't close file");
+ }
+
+ if (def->close_file) {
+ mpl_PyFile_CloseFile(def->py_file);
+ }
+
+ Py_DECREF(def->py_file);
+ def->py_file = NULL;
+}
+
+static int convert_open_args(PyFT2Font *self, PyObject *py_file_arg, FT_Open_Args *open_args)
+{
+ PyObject *py_file = NULL;
+ int close_file = 0;
+ FILE *fp;
+ PyObject *data = NULL;
+ char *data_ptr;
+ Py_ssize_t data_len;
+ long file_size;
+ FT_Byte *new_memory;
+ mpl_off_t offset = 0;
+
+ int result = 0;
+
+ memset((void *)open_args, 0, sizeof(FT_Open_Args));
+
+ if (PyBytes_Check(py_file_arg) || PyUnicode_Check(py_file_arg)) {
+ if ((py_file = mpl_PyFile_OpenFile(py_file_arg, (char *)"rb")) == NULL) {
+ goto exit;
+ }
+ close_file = 1;
+ } else {
+ Py_INCREF(py_file_arg);
+ py_file = py_file_arg;
+ }
+
+ if ((fp = mpl_PyFile_Dup(py_file, (char *)"rb", &offset))) {
+ Py_INCREF(py_file);
+ self->py_file = py_file;
+ self->close_file = close_file;
+ self->fp = fp;
+ self->offset = offset;
+ fseek(fp, 0, SEEK_END);
+ file_size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ self->stream.base = NULL;
+ self->stream.size = (unsigned long)file_size;
+ self->stream.pos = 0;
+ self->stream.descriptor.pointer = self;
+ self->stream.read = &read_from_file_callback;
+ self->stream.close = &close_file_callback;
+
+ open_args->flags = FT_OPEN_STREAM;
+ open_args->stream = &self->stream;
+ } else {
+ if (PyObject_HasAttrString(py_file_arg, "read") &&
+ (data = PyObject_CallMethod(py_file_arg, (char *)"read", (char *)""))) {
+ if (PyBytes_AsStringAndSize(data, &data_ptr, &data_len)) {
+ goto exit;
+ }
+
+ if (self->mem) {
+ free(self->mem);
+ }
+ self->mem = (FT_Byte *)malloc((self->mem_size + data_len) * sizeof(FT_Byte));
+ if (self->mem == NULL) {
+ goto exit;
+ }
+ new_memory = self->mem + self->mem_size;
+ self->mem_size += data_len;
+
+ memcpy(new_memory, data_ptr, data_len);
+ open_args->flags = FT_OPEN_MEMORY;
+ open_args->memory_base = new_memory;
+ open_args->memory_size = data_len;
+ open_args->stream = NULL;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "First argument must be a path or file object reading bytes");
+ goto exit;
+ }
+ }
+
+ result = 1;
+
+exit:
+
+ Py_XDECREF(py_file);
+ Py_XDECREF(data);
+
+ return result;
+}
+
+static PyTypeObject PyFT2FontType;
+
+static PyObject *PyFT2Font_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyFT2Font *self;
+ self = (PyFT2Font *)type->tp_alloc(type, 0);
+ self->x = NULL;
+ self->fname = NULL;
+ self->py_file = NULL;
+ self->fp = NULL;
+ self->close_file = 0;
+ self->offset = 0;
+ memset(&self->stream, 0, sizeof(FT_StreamRec));
+ self->mem = 0;
+ self->mem_size = 0;
+ return (PyObject *)self;
+}
+
+const char *PyFT2Font_init__doc__ =
+ "FT2Font(ttffile)\n"
+ "\n"
+ "Create a new FT2Font object\n"
+ "The following global font attributes are defined:\n"
+ " num_faces number of faces in file\n"
+ " face_flags face flags (int type); see the ft2font constants\n"
+ " style_flags style flags (int type); see the ft2font constants\n"
+ " num_glyphs number of glyphs in the face\n"
+ " family_name face family name\n"
+ " style_name face style name\n"
+ " num_fixed_sizes number of bitmap in the face\n"
+ " scalable face is scalable\n"
+ "\n"
+ "The following are available, if scalable is true:\n"
+ " bbox face global bounding box (xmin, ymin, xmax, ymax)\n"
+ " units_per_EM number of font units covered by the EM\n"
+ " ascender ascender in 26.6 units\n"
+ " descender descender in 26.6 units\n"
+ " height height in 26.6 units; used to compute a default\n"
+ " line spacing (baseline-to-baseline distance)\n"
+ " max_advance_width maximum horizontal cursor advance for all glyphs\n"
+ " max_advance_height same for vertical layout\n"
+ " underline_position vertical position of the underline bar\n"
+ " underline_thickness vertical thickness of the underline\n"
+ " postscript_name PostScript name of the font\n";
+
+static void PyFT2Font_fail(PyFT2Font *self)
+{
+ free(self->mem);
+ self->mem = NULL;
+ Py_XDECREF(self->py_file);
+ self->py_file = NULL;
+}
+
+static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *fname;
+ FT_Open_Args open_args;
+ long hinting_factor = 8;
+ const char *names[] = { "filename", "hinting_factor", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwds, "O|l:FT2Font", (char **)names, &fname, &hinting_factor)) {
+ return -1;
+ }
+
+ if (!convert_open_args(self, fname, &open_args)) {
+ return -1;
+ }
+
+ CALL_CPP_FULL(
+ "FT2Font", (self->x = new FT2Font(open_args, hinting_factor)), PyFT2Font_fail(self), -1);
+
+ Py_INCREF(fname);
+ self->fname = fname;
+
+ return 0;
+}
+
+static void PyFT2Font_dealloc(PyFT2Font *self)
+{
+ delete self->x;
+ free(self->mem);
+ Py_XDECREF(self->py_file);
+ Py_XDECREF(self->fname);
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+const char *PyFT2Font_clear__doc__ =
+ "clear()\n"
+ "\n"
+ "Clear all the glyphs, reset for a new set_text";
+
+static PyObject *PyFT2Font_clear(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ CALL_CPP("clear", (self->x->clear()));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Font_set_size__doc__ =
+ "set_size(ptsize, dpi)\n"
+ "\n"
+ "Set the point size and dpi of the text.\n";
+
+static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ 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(i)\n"
+ "\n"
+ "Make the i-th charmap current\n";
+
+static PyObject *PyFT2Font_set_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ 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(i)\n"
+ "\n"
+ "select charmap i where i is one of the FT_Encoding number\n";
+
+static PyObject *PyFT2Font_select_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ 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__ =
+ "dx = get_kerning(left, right, mode)\n"
+ "\n"
+ "Get the kerning between left char and right glyph indices\n"
+ "mode is a kerning mode constant\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, PyObject *kwds)
+{
+ FT_UInt left, right, mode;
+ int result;
+
+ if (!PyArg_ParseTuple(args, "III:get_kerning", &left, &right, &mode)) {
+ return NULL;
+ }
+
+ CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode)));
+
+ return PyLong_FromLong(result);
+}
+
+const char *PyFT2Font_set_text__doc__ =
+ "set_text(s, angle)\n"
+ "\n"
+ "Set the text string and angle.\n"
+ "You must call this before draw_glyphs_to_bitmap\n"
+ "A sequence of x,y positions is returned";
+
+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_SIZE(textobj);
+ codepoints.resize(size);
+ Py_UNICODE *unistr = PyUnicode_AsUnicode(textobj);
+ for (size_t i = 0; i < size; ++i) {
+ codepoints[i] = unistr[i];
+ }
+ } else if (PyBytes_Check(textobj)) {
+ size = PyBytes_Size(textobj);
+ codepoints.resize(size);
+ char *bytestr = PyBytes_AsString(textobj);
+ for (size_t i = 0; i < size; ++i) {
+ codepoints[i] = bytestr[i];
+ }
+ } else {
+ PyErr_SetString(PyExc_TypeError, "String must be unicode or bytes");
+ 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()\n"
+ "\n"
+ "Return the number of loaded glyphs\n";
+
+static PyObject *PyFT2Font_get_num_glyphs(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ return PyLong_FromLong(self->x->get_num_glyphs());
+}
+
+const char *PyFT2Font_load_char__doc__ =
+ "load_char(charcode, flags=LOAD_FORCE_AUTOHINT)\n"
+ "\n"
+ "Load character with charcode in current fontfile and set glyph.\n"
+ "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n"
+ "Return value is a Glyph object, with attributes\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;
+ 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;
+ }
+
+ CALL_CPP("load_char", (self->x->load_char(charcode, flags)));
+
+ return PyGlyph_new(self->x->get_face(),
+ self->x->get_last_glyph(),
+ self->x->get_last_glyph_index(),
+ self->x->get_hinting_factor());
+}
+
+const char *PyFT2Font_load_glyph__doc__ =
+ "load_glyph(glyphindex, flags=LOAD_FORCE_AUTOHINT)\n"
+ "\n"
+ "Load character with glyphindex in current fontfile and set glyph.\n"
+ "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n"
+ "Return value is a Glyph object, with attributes\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;
+ 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;
+ }
+
+ CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags)));
+
+ return PyGlyph_new(self->x->get_face(),
+ self->x->get_last_glyph(),
+ self->x->get_last_glyph_index(),
+ self->x->get_hinting_factor());
+}
+
+const char *PyFT2Font_get_width_height__doc__ =
+ "w, h = get_width_height()\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, PyObject *kwds)
+{
+ 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__ =
+ "x, y = get_bitmap_offset()\n"
+ "\n"
+ "Get the 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, PyObject *kwds)
+{
+ 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__ =
+ "d = get_descent()\n"
+ "\n"
+ "Get the descent of the current string set by set_text in 26.6 subpixels.\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, PyObject *kwds)
+{
+ 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()\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)
+{
+ int antialiased = 1;
+ const char *names[] = { "antialiased", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwds, "|i:draw_glyphs_to_bitmap", (char **)names, &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()\n"
+ "\n"
+ "Get the xy locations of the current glyphs\n";
+
+static PyObject *PyFT2Font_get_xys(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ int antialiased = 1;
+ std::vector<double> xys;
+ const char *names[] = { "antialiased", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:get_xys", (char **)names, &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(bitmap, x, y, glyph)\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 intended for people who\n"
+ "want to render individual glyphs at precise locations, eg, a\n"
+ "a glyph returned by load_char\n";
+
+static PyObject *PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ PyFT2Image *image;
+ double xd, yd;
+ PyGlyph *glyph;
+ int antialiased = 1;
+ const char *names[] = { "image", "x", "y", "glyph", "antialiased", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ "O!ddO!|i:draw_glyph_to_bitmap",
+ (char **)names,
+ &PyFT2ImageType,
+ &image,
+ &xd,
+ &yd,
+ &PyGlyphType,
+ &glyph,
+ &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(index)\n"
+ "\n"
+ "Retrieves the ASCII name of a given glyph in a face.\n";
+
+static PyObject *PyFT2Font_get_glyph_name(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ unsigned int glyph_number;
+ char buffer[128];
+
+ 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)));
+
+ return PyUnicode_FromString(buffer);
+}
+
+const char *PyFT2Font_get_charmap__doc__ =
+ "get_charmap()\n"
+ "\n"
+ "Returns a dictionary 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 *kwds)
+{
+ PyObject *charmap;
+
+ charmap = PyDict_New();
+ if (charmap == NULL) {
+ return NULL;
+ }
+
+ FT_UInt index;
+ FT_ULong code = FT_Get_First_Char(self->x->get_face(), &index);
+ while (index != 0) {
+ PyObject *key;
+ PyObject *val;
+
+ key = PyLong_FromLong(code);
+ if (key == NULL) {
+ Py_DECREF(charmap);
+ return NULL;
+ }
+
+ val = PyLong_FromLong(index);
+ if (val == NULL) {
+ Py_DECREF(key);
+ Py_DECREF(charmap);
+ return NULL;
+ }
+
+ if (PyDict_SetItem(charmap, key, val)) {
+ Py_DECREF(key);
+ Py_DECREF(val);
+ Py_DECREF(charmap);
+ return NULL;
+ }
+
+ Py_DECREF(key);
+ Py_DECREF(val);
+
+ code = FT_Get_Next_Char(self->x->get_face(), code, &index);
+ }
+
+ return charmap;
+}
+
+
+const char *PyFT2Font_get_char_index__doc__ =
+ "get_char_index()\n"
+ "\n"
+ "Given a character code, returns a glyph index.\n";
+
+static PyObject *PyFT2Font_get_char_index(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ FT_UInt index;
+ FT_ULong ccode;
+
+ if (!PyArg_ParseTuple(args, "k:get_char_index", &ccode)) {
+ return NULL;
+ }
+
+ index = FT_Get_Char_Index(self->x->get_face(), ccode);
+
+ return PyLong_FromLong(index);
+}
+
+
+const char *PyFT2Font_get_sfnt__doc__ =
+ "get_sfnt(name)\n"
+ "\n"
+ "Get all values from the SFNT names table. Result is a dictionary whose"
+ "key is the platform-ID, ISO-encoding-scheme, language-code, and"
+ "description.\n";
+
+static PyObject *PyFT2Font_get_sfnt(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ 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(
+ "iiii", 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(name)\n"
+ "\n"
+ "Returns 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, PyObject *kwds)
+{
+ char *glyphname;
+ long name_index;
+
+ if (!PyArg_ParseTuple(args, "es:get_name_index", "ascii", &glyphname)) {
+ return NULL;
+ }
+
+ CALL_CPP("get_name_index", name_index = self->x->get_name_index(glyphname));
+
+ PyMem_Free(glyphname);
+
+ return PyLong_FromLong(name_index);
+}
+
+const char *PyFT2Font_get_ps_font_info__doc__ =
+ "get_ps_font_info()\n"
+ "\n"
+ "Return the information in the PS Font Info structure.\n";
+
+static PyObject *PyFT2Font_get_ps_font_info(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ 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("sssssliii",
+ 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(name)\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, PyObject *kwds)
+{
+ char *tagname;
+
+ if (!PyArg_ParseTuple(args, "es:get_sfnt_table", "ascii", &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;
+ }
+ }
+
+ PyMem_Free(tagname);
+
+ 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:i, s:i,"
+ "s:(l,l), s:(l,l), s:h, s:h, s:h, s:h, s:i, s:i, 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",
+ (unsigned)t->Flags,
+ "unitsPerEm",
+ (unsigned)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",
+ (unsigned)t->Mac_Style,
+ "lowestRecPPEM",
+ (unsigned)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:i, s:i, s:i, s:i, s:i, s:i,"
+ "s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i}";
+ TT_MaxProfile *t = (TT_MaxProfile *)table;
+ return Py_BuildValue(maxp_dict,
+ "version",
+ FIXED_MAJOR(t->version),
+ FIXED_MINOR(t->version),
+ "numGlyphs",
+ (unsigned)t->numGlyphs,
+ "maxPoints",
+ (unsigned)t->maxPoints,
+ "maxContours",
+ (unsigned)t->maxContours,
+ "maxComponentPoints",
+ (unsigned)t->maxCompositePoints,
+ "maxComponentContours",
+ (unsigned)t->maxCompositeContours,
+ "maxZones",
+ (unsigned)t->maxZones,
+ "maxTwilightPoints",
+ (unsigned)t->maxTwilightPoints,
+ "maxStorage",
+ (unsigned)t->maxStorage,
+ "maxFunctionDefs",
+ (unsigned)t->maxFunctionDefs,
+ "maxInstructionDefs",
+ (unsigned)t->maxInstructionDefs,
+ "maxStackElements",
+ (unsigned)t->maxStackElements,
+ "maxSizeOfInstructions",
+ (unsigned)t->maxSizeOfInstructions,
+ "maxComponentElements",
+ (unsigned)t->maxComponentElements,
+ "maxComponentDepth",
+ (unsigned)t->maxComponentDepth);
+ }
+ case 2: {
+#if PY3K
+ 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:(llll),"
+ "s:y#, s:h, s:h, s:h}";
+#else
+ 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:s#, s:(llll),"
+ "s:s#, s:h, s:h, s:h}";
+#endif
+ TT_OS2 *t = (TT_OS2 *)table;
+ return Py_BuildValue(os_2_dict,
+ "version",
+ (unsigned)t->version,
+ "xAvgCharWidth",
+ t->xAvgCharWidth,
+ "usWeightClass",
+ (unsigned)t->usWeightClass,
+ "usWidthClass",
+ (unsigned)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,
+ 10,
+ "ulCharRange",
+ (unsigned long)t->ulUnicodeRange1,
+ (unsigned long)t->ulUnicodeRange2,
+ (unsigned long)t->ulUnicodeRange3,
+ (unsigned long)t->ulUnicodeRange4,
+ "achVendID",
+ t->achVendID,
+ 4,
+ "fsSelection",
+ (unsigned)t->fsSelection,
+ "fsFirstCharIndex",
+ (unsigned)t->usFirstCharIndex,
+ "fsLastCharIndex",
+ (unsigned)t->usLastCharIndex);
+ }
+ case 3: {
+ char hhea_dict[] =
+ "{s:(h,h), s:h, s:h, s:h, s:i, s:h, s:h, s:h,"
+ "s:h, s:h, s:h, s:h, s:i}";
+ 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",
+ (unsigned)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",
+ (unsigned)t->number_Of_HMetrics);
+ }
+ case 4: {
+ char vhea_dict[] =
+ "{s:(h,h), s:h, s:h, s:h, s:i, s:h, s:h, s:h,"
+ "s:h, s:h, s:h, s:h, s:i}";
+ 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",
+ (unsigned)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",
+ (unsigned)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: {
+ #if PY3K
+ 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}";
+ #else
+ char pclt_dict[] =
+ "{s:(h,h), s:k, s:H, s:H, s:H, s:H, s:H, s:H, s:s, s:s, s:b, s:b, "
+ "s:b}";
+ #endif
+ 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,
+ "characterComplement",
+ t->CharacterComplement,
+ "strokeWeight",
+ t->StrokeWeight,
+ "widthType",
+ t->WidthType,
+ "serifStyle",
+ t->SerifStyle);
+ }
+ default:
+ Py_RETURN_NONE;
+ }
+}
+
+const char *PyFT2Font_get_path__doc__ =
+ "get_path()\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, PyObject *kwds)
+{
+ int count;
+
+ CALL_CPP("get_path", (count = self->x->get_path_count()));
+
+ npy_intp vertices_dims[2] = { count, 2 };
+ numpy::array_view<double, 2> vertices(vertices_dims);
+
+ npy_intp codes_dims[1] = { count };
+ numpy::array_view<unsigned char, 1> codes(codes_dims);
+
+ self->x->get_path(vertices.data(), codes.data());
+
+ return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj());
+}
+
+const char *PyFT2Font_get_image__doc__ =
+ "get_image()\n"
+ "\n"
+ "Returns the underlying image buffer for this font object.\n";
+
+static PyObject *PyFT2Font_get_image(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ 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("iiii", 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->fname) {
+ Py_INCREF(self->fname);
+ return self->fname;
+ }
+
+ Py_RETURN_NONE;
+}
+
+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(PyObject *m, PyTypeObject *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_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_path__doc__},
+ {NULL}
+ };
+
+ static PyBufferProcs buffer_procs;
+ memset(&buffer_procs, 0, sizeof(PyBufferProcs));
+ buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Font_get_buffer;
+
+ memset(type, 0, sizeof(PyTypeObject));
+ type->tp_name = "matplotlib.ft2font.FT2Font";
+ type->tp_doc = PyFT2Font_init__doc__;
+ type->tp_basicsize = sizeof(PyFT2Font);
+ type->tp_dealloc = (destructor)PyFT2Font_dealloc;
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER;
+ type->tp_methods = methods;
+ type->tp_getset = getset;
+ type->tp_new = PyFT2Font_new;
+ type->tp_init = (initproc)PyFT2Font_init;
+ type->tp_as_buffer = &buffer_procs;
+
+ if (PyType_Ready(type) < 0) {
+ return NULL;
+ }
+
+ if (PyModule_AddObject(m, "FT2Font", (PyObject *)type)) {
+ return NULL;
+ }
+
+ return type;
+}
+
+extern "C" {
+
+#if PY3K
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "ft2font",
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+#define INITERROR return NULL
+
+PyMODINIT_FUNC PyInit_ft2font(void)
+
+#else
+#define INITERROR return
+
+PyMODINIT_FUNC initft2font(void)
+#endif
+
+{
+ PyObject *m;
+
+#if PY3K
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("ft2font", NULL, NULL);
+#endif
+
+ if (m == NULL) {
+ INITERROR;
+ }
+
+ if (!PyFT2Image_init_type(m, &PyFT2ImageType)) {
+ INITERROR;
+ }
+
+ if (!PyGlyph_init_type(m, &PyGlyphType)) {
+ INITERROR;
+ }
+
+ if (!PyFT2Font_init_type(m, &PyFT2FontType)) {
+ INITERROR;
+ }
+
+ PyObject *d = PyModule_GetDict(m);
+
+ if (add_dict_int(d, "SCALABLE", FT_FACE_FLAG_SCALABLE) ||
+ add_dict_int(d, "FIXED_SIZES", FT_FACE_FLAG_FIXED_SIZES) ||
+ add_dict_int(d, "FIXED_WIDTH", FT_FACE_FLAG_FIXED_WIDTH) ||
+ add_dict_int(d, "SFNT", FT_FACE_FLAG_SFNT) ||
+ add_dict_int(d, "HORIZONTAL", FT_FACE_FLAG_HORIZONTAL) ||
+ add_dict_int(d, "VERTICAL", FT_FACE_FLAG_VERTICAL) ||
+ add_dict_int(d, "KERNING", FT_FACE_FLAG_KERNING) ||
+ add_dict_int(d, "FAST_GLYPHS", FT_FACE_FLAG_FAST_GLYPHS) ||
+ add_dict_int(d, "MULTIPLE_MASTERS", FT_FACE_FLAG_MULTIPLE_MASTERS) ||
+ add_dict_int(d, "GLYPH_NAMES", FT_FACE_FLAG_GLYPH_NAMES) ||
+ add_dict_int(d, "EXTERNAL_STREAM", FT_FACE_FLAG_EXTERNAL_STREAM) ||
+ add_dict_int(d, "ITALIC", FT_STYLE_FLAG_ITALIC) ||
+ add_dict_int(d, "BOLD", FT_STYLE_FLAG_BOLD) ||
+ add_dict_int(d, "KERNING_DEFAULT", FT_KERNING_DEFAULT) ||
+ add_dict_int(d, "KERNING_UNFITTED", FT_KERNING_UNFITTED) ||
+ add_dict_int(d, "KERNING_UNSCALED", FT_KERNING_UNSCALED) ||
+ add_dict_int(d, "LOAD_DEFAULT", FT_LOAD_DEFAULT) ||
+ add_dict_int(d, "LOAD_NO_SCALE", FT_LOAD_NO_SCALE) ||
+ add_dict_int(d, "LOAD_NO_HINTING", FT_LOAD_NO_HINTING) ||
+ add_dict_int(d, "LOAD_RENDER", FT_LOAD_RENDER) ||
+ add_dict_int(d, "LOAD_NO_BITMAP", FT_LOAD_NO_BITMAP) ||
+ add_dict_int(d, "LOAD_VERTICAL_LAYOUT", FT_LOAD_VERTICAL_LAYOUT) ||
+ add_dict_int(d, "LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT) ||
+ add_dict_int(d, "LOAD_CROP_BITMAP", FT_LOAD_CROP_BITMAP) ||
+ add_dict_int(d, "LOAD_PEDANTIC", FT_LOAD_PEDANTIC) ||
+ add_dict_int(d, "LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH", FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH) ||
+ add_dict_int(d, "LOAD_NO_RECURSE", FT_LOAD_NO_RECURSE) ||
+ add_dict_int(d, "LOAD_IGNORE_TRANSFORM", FT_LOAD_IGNORE_TRANSFORM) ||
+ add_dict_int(d, "LOAD_MONOCHROME", FT_LOAD_MONOCHROME) ||
+ add_dict_int(d, "LOAD_LINEAR_DESIGN", FT_LOAD_LINEAR_DESIGN) ||
+ add_dict_int(d, "LOAD_NO_AUTOHINT", (unsigned long)FT_LOAD_NO_AUTOHINT) ||
+ add_dict_int(d, "LOAD_TARGET_NORMAL", (unsigned long)FT_LOAD_TARGET_NORMAL) ||
+ add_dict_int(d, "LOAD_TARGET_LIGHT", (unsigned long)FT_LOAD_TARGET_LIGHT) ||
+ add_dict_int(d, "LOAD_TARGET_MONO", (unsigned long)FT_LOAD_TARGET_MONO) ||
+ add_dict_int(d, "LOAD_TARGET_LCD", (unsigned long)FT_LOAD_TARGET_LCD) ||
+ add_dict_int(d, "LOAD_TARGET_LCD_V", (unsigned long)FT_LOAD_TARGET_LCD_V)) {
+ INITERROR;
+ }
+
+ // initialize library
+ int error = FT_Init_FreeType(&_ft2Library);
+
+ if (error) {
+ PyErr_SetString(PyExc_RuntimeError, "Could not initialize the freetype2 library");
+ INITERROR;
+ }
+
+ {
+ FT_Int major, minor, patch;
+ char version_string[64];
+
+ FT_Library_Version(_ft2Library, &major, &minor, &patch);
+ sprintf(version_string, "%d.%d.%d", major, minor, patch);
+ if (PyModule_AddStringConstant(m, "__freetype_version__", version_string)) {
+ INITERROR;
+ }
+ }
+
+ if (PyModule_AddStringConstant(m, "__freetype_build_type__", STRINGIFY(FREETYPE_BUILD_TYPE))) {
+ INITERROR;
+ }
+
+ import_array();
+
+#if PY3K
+ return m;
+#endif
+}
+
+} // extern "C"
diff --git a/contrib/python/matplotlib/py2/src/mplutils.cpp b/contrib/python/matplotlib/py2/src/mplutils.cpp
new file mode 100644
index 0000000000..bc09db52aa
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/mplutils.cpp
@@ -0,0 +1,21 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#include "mplutils.h"
+
+int add_dict_int(PyObject *dict, const char *key, long val)
+{
+ PyObject *valobj;
+ valobj = PyLong_FromLong(val);
+ if (valobj == NULL) {
+ return 1;
+ }
+
+ if (PyDict_SetItemString(dict, (char *)key, valobj)) {
+ Py_DECREF(valobj);
+ return 1;
+ }
+
+ Py_DECREF(valobj);
+
+ return 0;
+}
diff --git a/contrib/python/matplotlib/py2/src/mplutils.h b/contrib/python/matplotlib/py2/src/mplutils.h
new file mode 100644
index 0000000000..140a815634
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/mplutils.h
@@ -0,0 +1,72 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* Small utilities that are shared by most extension modules. */
+
+#ifndef _MPLUTILS_H
+#define _MPLUTILS_H
+
+#if defined(_MSC_VER) && _MSC_VER <= 1600
+typedef unsigned __int8 uint8_t;
+#else
+#include <stdint.h>
+#endif
+
+#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>
+
+#if PY_MAJOR_VERSION >= 3
+#define PY3K 1
+#define Py_TPFLAGS_HAVE_NEWBUFFER 0
+#else
+#define PY3K 0
+#endif
+
+#undef CLAMP
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+#undef MAX
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+inline double mpl_round(double v)
+{
+ return (double)(int)(v + ((v >= 0.0) ? 0.5 : -0.5));
+}
+
+enum {
+ STOP = 0,
+ MOVETO = 1,
+ LINETO = 2,
+ CURVE3 = 3,
+ CURVE4 = 4,
+ ENDPOLY = 0x4f
+};
+
+const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3, 1 };
+
+extern "C" int add_dict_int(PyObject *dict, const char *key, long val);
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+namespace std {
+ inline bool isfinite(double num) { return _finite(num); }
+}
+#endif
+
+#endif
diff --git a/contrib/python/matplotlib/py2/src/numpy_cpp.h b/contrib/python/matplotlib/py2/src/numpy_cpp.h
new file mode 100644
index 0000000000..03b4a695d1
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/numpy_cpp.h
@@ -0,0 +1,569 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef _NUMPY_CPP_H_
+#define _NUMPY_CPP_H_
+
+/***************************************************************************
+ * 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(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;
+ }
+
+ int 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 0;
+ }
+
+ 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 1;
+ }
+ }
+ if (PyArray_NDIM(tmp) != ND) {
+ PyErr_Format(PyExc_ValueError,
+ "Expected %d-dimensional array, got %d",
+ ND,
+ PyArray_NDIM(tmp));
+ Py_DECREF(tmp);
+ return 0;
+ }
+
+ /* 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 = (char *)PyArray_BYTES(tmp);
+ }
+
+ return 1;
+ }
+
+ 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/py2/src/path_converters.h b/contrib/python/matplotlib/py2/src/path_converters.h
new file mode 100644
index 0000000000..db40c18d5a
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/path_converters.h
@@ -0,0 +1,1011 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef __PATH_CONVERTERS_H__
+#define __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 can not 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_curves;
+
+ public:
+ /* has_curves should be true if the path contains bezier curve
+ segments, as this requires a slower algorithm to remove the
+ NaNs. When in doubt, set to true.
+ */
+ PathNanRemover(VertexSource &source, bool remove_nans, bool has_curves)
+ : m_source(&source), m_remove_nans(remove_nans), m_has_curves(has_curves)
+ {
+ // empty
+ }
+
+ 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_curves) {
+ /* This is the slow method for when there might be curves. */
+ 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);
+ if (code == agg::path_cmd_stop ||
+ code == (agg::path_cmd_end_poly | agg::path_flags_close)) {
+ return code;
+ }
+
+ if (needs_move_to) {
+ queue_push(agg::path_cmd_move_to, *x, *y);
+ }
+
+ size_t num_extra_points = num_extra_points_map[code & 0xF];
+ bool has_nan = (!(std::isfinite(*x) && std::isfinite(*y)));
+ queue_push(code, *x, *y);
+
+ /* Note: this test can not 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);
+ has_nan = has_nan || !(std::isfinite(*x) && std::isfinite(*y));
+ queue_push(code, *x, *y);
+ }
+
+ if (!has_nan) {
+ break;
+ }
+
+ 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_curves
+ {
+ /* This is the fast path for when we know we have no curves */
+ code = m_source->vertex(x, y);
+
+ if (code == agg::path_cmd_stop ||
+ code == (agg::path_cmd_end_poly | agg::path_flags_close)) {
+ 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)) {
+ return code;
+ }
+ } while (!(std::isfinite(*x) && std::isfinite(*y)));
+ return agg::path_cmd_move_to;
+ }
+
+ 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;
+
+ 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_moveto(true),
+ m_has_init(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_moveto(true),
+ m_has_init(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_moveto = true;
+ m_source->rewind(path_id);
+ }
+
+ int draw_clipped_line(double x0, double y0, double x1, double y1)
+ {
+ 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
+ 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);
+
+ m_moveto = false;
+ return 1;
+ }
+
+ return 0;
+ }
+
+ unsigned vertex(double *x, double *y)
+ {
+ unsigned code;
+ bool emit_moveto = false;
+
+ if (m_do_clipping) {
+ /* 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) {
+ draw_clipped_line(m_lastX, m_lastY, m_initX, m_initY);
+ }
+ queue_push(
+ agg::path_cmd_end_poly | agg::path_flags_close,
+ m_lastX, m_lastY);
+ goto exit_loop;
+
+ 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;
+ // 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_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;
+ } else {
+ // If not doing any clipping, just pass along the vertices
+ // verbatim
+ return m_source->vertex(x, y);
+ }
+ }
+};
+
+/************************************************************
+ 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 = (int)mpl_round(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);
+ }
+
+ 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();
+ double d_M_PI = 3.14159265358979323846;
+ m_p += pow(m_randomness, d_rand * 2.0 - 1.0);
+ double r = sin(m_p / (m_length / (d_M_PI * 2.0))) * m_scale;
+ 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);
+ *x += r * num / len;
+ *y += r * -den / len;
+ }
+ } 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;
+};
+
+#endif // __PATH_CONVERTERS_H__
diff --git a/contrib/python/matplotlib/py2/src/py_adaptors.h b/contrib/python/matplotlib/py2/src/py_adaptors.h
new file mode 100644
index 0000000000..8eaa7ad6c7
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/py_adaptors.h
@@ -0,0 +1,251 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef __PY_ADAPTORS_H__
+#define __PY_ADAPTORS_H__
+
+/***************************************************************************
+ * 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_curves() 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(PyObject *obj) : m_paths(NULL), m_npaths(0)
+ {
+ if (!set(obj)) {
+ throw py::exception();
+ }
+ }
+
+ ~PathGenerator()
+ {
+ Py_XDECREF(m_paths);
+ }
+
+ int set(PyObject *obj)
+ {
+ if (!PySequence_Check(obj)) {
+ return 0;
+ }
+
+ 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)) {
+ throw py::exception();
+ }
+ Py_DECREF(item);
+ return path;
+ }
+};
+}
+
+#endif
diff --git a/contrib/python/matplotlib/py2/src/py_converters.cpp b/contrib/python/matplotlib/py2/src/py_converters.cpp
new file mode 100644
index 0000000000..c36fc59f59
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/py_converters.cpp
@@ -0,0 +1,619 @@
+#define NO_IMPORT_ARRAY
+
+#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 bytes or unicode", 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, (char *)name, NULL);
+ if (value == NULL) {
+ if (!PyObject_HasAttrString(obj, (char *)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, (char *)name);
+ if (value == NULL) {
+ if (!PyObject_HasAttrString(obj, (char *)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;
+
+ *val = PyObject_IsTrue(obj);
+
+ 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 {
+ try
+ {
+ numpy::array_view<const double, 2> rect_arr(rectobj);
+
+ if (rect_arr.dim(0) != 2 || rect_arr.dim(1) != 2) {
+ PyErr_SetString(PyExc_ValueError, "Invalid bounding box");
+ return 0;
+ }
+
+ rect->x1 = rect_arr(0, 0);
+ rect->y1 = rect_arr(0, 1);
+ rect->x2 = rect_arr(1, 0);
+ rect->y2 = rect_arr(1, 1);
+ }
+ catch (py::exception &)
+ {
+ PyErr_Clear();
+
+ try
+ {
+ numpy::array_view<const double, 1> rect_arr(rectobj);
+
+ if (rect_arr.dim(0) != 4) {
+ PyErr_SetString(PyExc_ValueError, "Invalid bounding box");
+ return 0;
+ }
+
+ rect->x1 = rect_arr(0);
+ rect->y1 = rect_arr(1);
+ rect->x2 = rect_arr(2);
+ rect->y2 = rect_arr(3);
+ }
+ catch (py::exception &)
+ {
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+int convert_rgba(PyObject *rgbaobj, void *rgbap)
+{
+ agg::rgba *rgba = (agg::rgba *)rgbap;
+
+ if (rgbaobj == NULL || rgbaobj == Py_None) {
+ rgba->r = 0.0;
+ rgba->g = 0.0;
+ rgba->b = 0.0;
+ rgba->a = 0.0;
+ } else {
+ rgba->a = 1.0;
+ if (!PyArg_ParseTuple(
+ rgbaobj, "ddd|d:rgba", &(rgba->r), &(rgba->g), &(rgba->b), &(rgba->a))) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int convert_dashes(PyObject *dashobj, void *dashesp)
+{
+ Dashes *dashes = (Dashes *)dashesp;
+
+ if (dashobj == NULL && dashobj == Py_None) {
+ return 1;
+ }
+
+ PyObject *dash_offset_obj = NULL;
+ double dash_offset = 0.0;
+ PyObject *dashes_seq = NULL;
+ Py_ssize_t nentries;
+
+ if (!PyArg_ParseTuple(dashobj, "OO:dashes", &dash_offset_obj, &dashes_seq)) {
+ return 0;
+ }
+
+ if (dash_offset_obj != Py_None) {
+ dash_offset = PyFloat_AsDouble(dash_offset_obj);
+ if (PyErr_Occurred()) {
+ return 0;
+ }
+ }
+
+ if (dashes_seq == Py_None) {
+ return 1;
+ }
+
+ if (!PySequence_Check(dashes_seq)) {
+ PyErr_SetString(PyExc_TypeError, "Invalid dashes sequence");
+ return 0;
+ }
+
+ nentries = PySequence_Size(dashes_seq);
+ if (nentries % 2 != 0) {
+ PyErr_Format(PyExc_ValueError, "dashes sequence must have an even number of elements");
+ return 0;
+ }
+
+ for (Py_ssize_t i = 0; i < nentries; ++i) {
+ PyObject *item;
+ double length;
+ double skip;
+
+ item = PySequence_GetItem(dashes_seq, i);
+ 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);
+ 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;
+ }
+
+ try
+ {
+ numpy::array_view<const double, 2> matrix(obj);
+
+ if (matrix.dim(0) == 3 && matrix.dim(1) == 3) {
+ trans->sx = matrix(0, 0);
+ trans->shx = matrix(0, 1);
+ trans->tx = matrix(0, 2);
+
+ trans->shy = matrix(1, 0);
+ trans->sy = matrix(1, 1);
+ trans->ty = matrix(1, 2);
+
+ return 1;
+ }
+ }
+ catch (py::exception &)
+ {
+ return 0;
+ }
+
+ 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;
+ }
+ should_simplify = PyObject_IsTrue(should_simplify_obj);
+
+ 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_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 if (PyObject_IsTrue(obj)) {
+ *snap = SNAP_TRUE;
+ } else {
+ *snap = SNAP_FALSE;
+ }
+
+ 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_offset_position(PyObject *obj, void *offsetp)
+{
+ e_offset_position *offset = (e_offset_position *)offsetp;
+ const char *names[] = {"data", NULL};
+ int values[] = {OFFSET_POSITION_DATA};
+ int result = (int)OFFSET_POSITION_FIGURE;
+
+ if (!convert_string_enum(obj, "offset_position", names, values, &result)) {
+ PyErr_Clear();
+ }
+
+ *offset = (e_offset_position)result;
+
+ 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;
+ }
+
+ points->set(obj);
+
+ if (points->size() == 0) {
+ return 1;
+ }
+
+ if (points->dim(1) != 2) {
+ PyErr_Format(PyExc_ValueError,
+ "Points must be Nx2 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT,
+ points->dim(0), points->dim(1));
+ 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;
+ }
+
+ trans->set(obj);
+
+ if (trans->size() == 0) {
+ return 1;
+ }
+
+ if (trans->dim(1) != 3 || trans->dim(2) != 3) {
+ PyErr_Format(PyExc_ValueError,
+ "Transforms must be Nx3x3 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT "x%" NPY_INTP_FMT,
+ trans->dim(0), trans->dim(1), trans->dim(2));
+ 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;
+ }
+
+ bbox->set(obj);
+
+ if (bbox->size() == 0) {
+ return 1;
+ }
+
+ if (bbox->dim(1) != 2 || bbox->dim(2) != 2) {
+ PyErr_Format(PyExc_ValueError,
+ "Bbox array must be Nx2x2 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT "x%" NPY_INTP_FMT,
+ bbox->dim(0), bbox->dim(1), bbox->dim(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;
+ }
+
+ colors->set(obj);
+
+ if (colors->size() == 0) {
+ return 1;
+ }
+
+ if (colors->dim(1) != 4) {
+ PyErr_Format(PyExc_ValueError,
+ "Colors array must be Nx4 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT,
+ colors->dim(0), colors->dim(1));
+ return 0;
+ }
+
+ return 1;
+}
+}
diff --git a/contrib/python/matplotlib/py2/src/py_converters.h b/contrib/python/matplotlib/py2/src/py_converters.h
new file mode 100644
index 0000000000..02d84affe8
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/py_converters.h
@@ -0,0 +1,49 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef __PY_CONVERTERS_H__
+#define __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 "numpy_cpp.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_clippath(PyObject *clippath_tuple, void *clippathp);
+int convert_snap(PyObject *obj, void *snapp);
+int convert_offset_position(PyObject *obj, void *offsetp);
+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/py2/src/py_exceptions.h b/contrib/python/matplotlib/py2/src/py_exceptions.h
new file mode 100644
index 0000000000..1ee2d51903
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/py_exceptions.h
@@ -0,0 +1,72 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef __PY_EXCEPTIONS_H__
+#define __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, NULL)
+
+#define CALL_CPP(name, a) CALL_CPP_FULL(name, a, , NULL)
+
+#define CALL_CPP_INIT(name, a) CALL_CPP_FULL(name, a, , -1)
+
+#endif
diff --git a/contrib/python/matplotlib/py2/src/qhull_wrap.c b/contrib/python/matplotlib/py2/src/qhull_wrap.c
new file mode 100644
index 0000000000..9cbaf64f01
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/qhull_wrap.c
@@ -0,0 +1,377 @@
+/*
+ * 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.
+ */
+#include "Python.h"
+#include "numpy/noprefix.h"
+#include "qhull_ra.h"
+#include <stdio.h>
+
+
+#if PY_MAJOR_VERSION >= 3
+#define PY3K 1
+#else
+#define PY3K 0
+#endif
+
+#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 qhT qhData;
+static qhT* qh = &qhData;
+
+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(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, const int* tri_indices,
+ int indices[3])
+{
+ facetT *neighbor, **neighborp;
+ FOREACHneighbor_(facet)
+ *indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id]);
+}
+
+/* Return 1 if the specified points arrays contain at least 3 unique points,
+ * or 0 otherwise. */
+static int
+at_least_3_unique_points(int 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 0;
+
+ 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 1;
+ }
+ }
+ }
+
+ /* Run out of points before 3 unique points found. */
+ return 0;
+}
+
+/* Delaunay implementation methyod. If hide_qhull_errors is 1 then qhull error
+ * messages are discarded; if it is 0 then they are written to stderr. */
+static PyObject*
+delaunay_impl(int npoints, const double* x, const double* y,
+ int hide_qhull_errors)
+{
+ coordT* points = NULL;
+ facetT* facet;
+ int i, ntri, max_facet_id;
+ FILE* error_file = NULL; /* qhull expects a FILE* to write errors to. */
+ int exitcode; /* Value returned from qh_new_qhull(). */
+ int* tri_indices = NULL; /* Maps qhull facet id to triangle index. */
+ int indices[3];
+ int curlong, totlong; /* Memory remaining after qh_memfreeshort. */
+ PyObject* tuple; /* Return tuple (triangles, neighbors). */
+ const int ndim = 2;
+ npy_intp dims[2];
+ PyArrayObject* triangles = NULL;
+ PyArrayObject* neighbors = NULL;
+ int* triangles_ptr;
+ int* neighbors_ptr;
+ double x_mean = 0.0;
+ double y_mean = 0.0;
+
+ QHULL_LIB_CHECK
+
+ /* Allocate points. */
+ points = (coordT*)malloc(npoints*ndim*sizeof(coordT));
+ if (points == NULL) {
+ PyErr_SetString(PyExc_MemoryError,
+ "Could not allocate points array in qhull.delaunay");
+ goto error_before_qhull;
+ }
+
+ /* 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. */
+ 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) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Could not open devnull in qhull.delaunay");
+ goto error_before_qhull;
+ }
+ }
+ else {
+ /* qhull errors written to stderr. */
+ error_file = stderr;
+ }
+
+ /* Perform Delaunay triangulation. */
+ exitcode = qh_new_qhull(qh, ndim, npoints, points, False,
+ "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." : "");
+ goto error;
+ }
+
+ /* 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. */
+ tri_indices = (int*)malloc((max_facet_id+1)*sizeof(int));
+ if (tri_indices == NULL) {
+ PyErr_SetString(PyExc_MemoryError,
+ "Could not allocate triangle map in qhull.delaunay");
+ goto error;
+ }
+
+ /* Allocate python arrays to return. */
+ dims[0] = ntri;
+ dims[1] = 3;
+ triangles = (PyArrayObject*)PyArray_SimpleNew(ndim, dims, NPY_INT);
+ if (triangles == NULL) {
+ PyErr_SetString(PyExc_MemoryError,
+ "Could not allocate triangles array in qhull.delaunay");
+ goto error;
+ }
+
+ neighbors = (PyArrayObject*)PyArray_SimpleNew(ndim, dims, NPY_INT);
+ if (neighbors == NULL) {
+ PyErr_SetString(PyExc_MemoryError,
+ "Could not allocate neighbors array in qhull.delaunay");
+ goto error;
+ }
+
+ triangles_ptr = (int*)PyArray_DATA(triangles);
+ neighbors_ptr = (int*)PyArray_DATA(neighbors);
+
+ /* Determine triangles array and set tri_indices array. */
+ i = 0;
+ FORALLfacets {
+ if (!facet->upperdelaunay) {
+ tri_indices[facet->id] = i++;
+ get_facet_vertices(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) {
+ 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];
+ }
+ }
+
+ /* Clean up. */
+ qh_freeqhull(qh, !qh_ALL);
+ qh_memfreeshort(qh, &curlong, &totlong);
+ if (curlong || totlong)
+ PyErr_WarnEx(PyExc_RuntimeWarning,
+ "Qhull could not free all allocated memory", 1);
+ if (hide_qhull_errors)
+ fclose(error_file);
+ free(tri_indices);
+ free(points);
+
+ tuple = PyTuple_New(2);
+ PyTuple_SetItem(tuple, 0, (PyObject*)triangles);
+ PyTuple_SetItem(tuple, 1, (PyObject*)neighbors);
+ return tuple;
+
+error:
+ /* Clean up. */
+ Py_XDECREF(triangles);
+ Py_XDECREF(neighbors);
+ qh_freeqhull(qh, !qh_ALL);
+ qh_memfreeshort(qh, &curlong, &totlong);
+ /* Don't bother checking curlong and totlong as raising error anyway. */
+ if (hide_qhull_errors)
+ fclose(error_file);
+ free(tri_indices);
+
+error_before_qhull:
+ free(points);
+
+ return NULL;
+}
+
+/* Process python arguments and call Delaunay implementation method. */
+static PyObject*
+delaunay(PyObject *self, PyObject *args)
+{
+ PyObject* xarg;
+ PyObject* yarg;
+ PyArrayObject* xarray;
+ PyArrayObject* yarray;
+ PyObject* ret;
+ int npoints;
+ const double* x;
+ const double* y;
+
+ if (!PyArg_ParseTuple(args, "OO", &xarg, &yarg)) {
+ PyErr_SetString(PyExc_ValueError, "expecting x and y arrays");
+ return NULL;
+ }
+
+ xarray = (PyArrayObject*)PyArray_ContiguousFromObject(xarg, NPY_DOUBLE,
+ 1, 1);
+ yarray = (PyArrayObject*)PyArray_ContiguousFromObject(yarg, NPY_DOUBLE,
+ 1, 1);
+ if (xarray == 0 || yarray == 0 ||
+ PyArray_DIM(xarray,0) != PyArray_DIM(yarray, 0)) {
+ Py_XDECREF(xarray);
+ Py_XDECREF(yarray);
+ PyErr_SetString(PyExc_ValueError,
+ "x and y must be 1D arrays of the same length");
+ return NULL;
+ }
+
+ npoints = PyArray_DIM(xarray, 0);
+
+ if (npoints < 3) {
+ Py_XDECREF(xarray);
+ Py_XDECREF(yarray);
+ PyErr_SetString(PyExc_ValueError,
+ "x and y arrays must have a length of at least 3");
+ return NULL;
+ }
+
+ x = (const double*)PyArray_DATA(xarray);
+ y = (const double*)PyArray_DATA(yarray);
+
+ if (!at_least_3_unique_points(npoints, x, y)) {
+ Py_XDECREF(xarray);
+ Py_XDECREF(yarray);
+ PyErr_SetString(PyExc_ValueError,
+ "x and y arrays must consist of at least 3 unique points");
+ return NULL;
+ }
+
+ ret = delaunay_impl(npoints, x, y, Py_VerboseFlag == 0);
+
+ Py_XDECREF(xarray);
+ Py_XDECREF(yarray);
+ return ret;
+}
+
+/* Return qhull version string for assistance in debugging. */
+static PyObject*
+version(void)
+{
+ return PyBytes_FromString(qh_version);
+}
+
+static PyMethodDef qhull_methods[] = {
+ {"delaunay", (PyCFunction)delaunay, METH_VARARGS, ""},
+ {"version", (PyCFunction)version, METH_NOARGS, ""},
+ {NULL, NULL, 0, NULL}
+};
+
+#if PY3K
+static struct PyModuleDef qhull_module = {
+ PyModuleDef_HEAD_INIT,
+ "qhull",
+ "Computing Delaunay triangulations.\n",
+ -1,
+ qhull_methods,
+ NULL, NULL, NULL, NULL
+};
+
+#define ERROR_RETURN return NULL
+
+PyMODINIT_FUNC
+PyInit__qhull(void)
+#else
+#define ERROR_RETURN return
+
+PyMODINIT_FUNC
+init_qhull(void)
+#endif
+{
+ PyObject* m;
+
+ #if PY3K
+ m = PyModule_Create(&qhull_module);
+ #else
+ m = Py_InitModule3("_qhull", qhull_methods,
+ "Computing Delaunay triangulations.\n");
+ #endif
+
+ if (m == NULL) {
+ ERROR_RETURN;
+ }
+
+ import_array();
+
+ #if PY3K
+ return m;
+ #endif
+}
diff --git a/contrib/python/matplotlib/py2/src/ya.make b/contrib/python/matplotlib/py2/src/ya.make
new file mode 100644
index 0000000000..544aba3996
--- /dev/null
+++ b/contrib/python/matplotlib/py2/src/ya.make
@@ -0,0 +1,66 @@
+PY2_LIBRARY()
+
+LICENSE(PSF-2.0)
+
+NO_COMPILER_WARNINGS()
+
+PEERDIR(
+ ADDINCL contrib/libs/freetype
+ ADDINCL contrib/libs/libpng
+ ADDINCL contrib/python/numpy
+ contrib/libs/qhull
+ contrib/python/matplotlib/py2/extern/agg24-svn
+ contrib/python/matplotlib/py2/extern/ttconv
+)
+
+ADDINCL(
+ contrib/libs/qhull/libqhull_r
+ contrib/python/matplotlib/py2
+ contrib/python/matplotlib/py2/extern
+ contrib/python/matplotlib/py2/extern/agg24-svn/include
+)
+
+CFLAGS(
+ -D_MULTIARRAYMODULE
+ -DFREETYPE_BUILD_TYPE=local
+ -DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION
+ -DMPL_DEVNULL=/dev/null
+)
+
+IF (OS_WINDOWS)
+ LDFLAGS(
+ Psapi.lib
+ )
+ENDIF()
+
+PY_REGISTER(
+ matplotlib._contour
+ matplotlib._image # peerdir agg24-svn
+ matplotlib._path # peerdir agg24-svn
+ matplotlib._png
+ matplotlib._qhull # peerdir libqhull
+ matplotlib.backends._backend_agg # peerdir agg24-svn
+ matplotlib.backends._tkagg
+ matplotlib.ft2font
+ matplotlib.ttconv # peerdir ttconv
+)
+
+SRCS(
+ _backend_agg.cpp
+ _backend_agg_wrapper.cpp
+ _contour.cpp
+ _contour_wrapper.cpp
+ _image.cpp
+ _image_wrapper.cpp
+ _path_wrapper.cpp
+ _png.cpp
+ _tkagg.cpp
+ _ttconv.cpp
+ ft2font.cpp
+ ft2font_wrapper.cpp
+ mplutils.cpp
+ py_converters.cpp
+ qhull_wrap.c
+)
+
+END()