diff options
author | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 14:39:34 +0300 |
---|---|---|
committer | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 16:42:24 +0300 |
commit | 77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch) | |
tree | c51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py2/src | |
parent | dd6d20cadb65582270ac23f4b3b14ae189704b9d (diff) | |
download | ydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz |
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py2/src')
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 ®ion) +{ + if (region.get_data() == NULL) { + throw std::runtime_error("Cannot restore_region from NULL data"); + } + + agg::rendering_buffer rbuf; + rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride()); + + rendererBase.copy_from(rbuf, 0, region.get_rect().x1, region.get_rect().y1); +} + +// Restore the part of the saved region with offsets +void +RendererAgg::restore_region(BufferRegion ®ion, int xx1, int yy1, int xx2, int yy2, int x, int y ) +{ + if (region.get_data() == NULL) { + throw std::runtime_error("Cannot restore_region from NULL data"); + } + + agg::rect_i &rrect = region.get_rect(); + + agg::rect_i rect(xx1 - rrect.x1, (yy1 - rrect.y1), xx2 - rrect.x1, (yy2 - rrect.y1)); + + agg::rendering_buffer rbuf; + rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride()); + + rendererBase.copy_from(rbuf, &rect, x, y); +} + +bool RendererAgg::render_clippath(py::PathIterator &clippath, + const agg::trans_affine &clippath_trans) +{ + 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 ®); + void restore_region(BufferRegion ®ion, int xx1, int yy1, int xx2, int yy2, int x, int y); + + unsigned int width, height; + double dpi; + size_t NUMBYTES; // the number of bytes in buffer + + agg::int8u *pixBuffer; + agg::rendering_buffer renderingBuffer; + + agg::int8u *alphaBuffer; + agg::rendering_buffer alphaMaskRenderingBuffer; + alpha_mask_type alphaMask; + agg::pixfmt_gray8 pixfmtAlphaMask; + renderer_base_alpha_mask_type rendererBaseAlphaMask; + renderer_alpha_mask_type rendererAlphaMask; + scanline_am scanlineAlphaMask; + + scanline_p8 slineP8; + scanline_bin slineBin; + pixfmt pixFmt; + renderer_base rendererBase; + renderer_aa rendererAA; + renderer_bin rendererBin; + rasterizer theRasterizer; + + void *lastclippath; + agg::trans_affine lastclippath_transform; + + size_t hatch_size; + agg::int8u *hatchBuffer; + agg::rendering_buffer hatchRenderingBuffer; + + agg::rgba _fill_color; + + protected: + inline double points_to_pixels(double points) + { + return points * dpi / 72.0; + } + + template <class R> + void set_clipbox(const agg::rect_d &cliprect, R &rasterizer); + + bool render_clippath(py::PathIterator &clippath, const agg::trans_affine &clippath_trans); + + 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, + ®obj, + &xx1, + &yy1, + &xx2, + &yy2, + &x, + &y)) { + return 0; + } + + if (PySequence_Size(args) == 1) { + CALL_CPP("restore_region", (self->x->restore_region(*(regobj->x)))); + } else { + CALL_CPP("restore_region", self->x->restore_region(*(regobj->x), xx1, yy1, xx2, yy2, x, y)); + } + + Py_RETURN_NONE; +} + +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 ¶ms, + agg::image_filter_lut &filter) +{ + switch (params.interpolation) { + case NEAREST: + case _n_interpolation: + // Never should get here. Here to silence compiler warnings. + break; + + case HANNING: + filter.calculate(agg::image_filter_hanning(), params.norm); + break; + + case HAMMING: + filter.calculate(agg::image_filter_hamming(), params.norm); + break; + + case HERMITE: + filter.calculate(agg::image_filter_hermite(), params.norm); + break; + + case BILINEAR: + filter.calculate(agg::image_filter_bilinear(), params.norm); + break; + + case BICUBIC: + filter.calculate(agg::image_filter_bicubic(), params.norm); + break; + + case SPLINE16: + filter.calculate(agg::image_filter_spline16(), params.norm); + break; + + case SPLINE36: + filter.calculate(agg::image_filter_spline36(), params.norm); + break; + + case KAISER: + filter.calculate(agg::image_filter_kaiser(), params.norm); + break; + + case QUADRIC: + filter.calculate(agg::image_filter_quadric(), params.norm); + break; + + case CATROM: + filter.calculate(agg::image_filter_catrom(), params.norm); + break; + + case GAUSSIAN: + filter.calculate(agg::image_filter_gaussian(), params.norm); + break; + + case BESSEL: + filter.calculate(agg::image_filter_bessel(), params.norm); + break; + + case MITCHELL: + filter.calculate(agg::image_filter_mitchell(), params.norm); + break; + + case SINC: + filter.calculate(agg::image_filter_sinc(params.radius), params.norm); + break; + + case LANCZOS: + filter.calculate(agg::image_filter_lanczos(params.radius), params.norm); + break; + + case BLACKMAN: + filter.calculate(agg::image_filter_blackman(params.radius), params.norm); + break; + } +} + + +template<class T> +void resample( + const T *input, int in_width, int in_height, + T *output, int out_width, int out_height, + resample_params_t ¶ms) +{ + 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, + ¶ms.interpolation, &resample_, ¶ms.alpha, ¶ms.norm, + ¶ms.radius)) { + return NULL; + } + + if (params.interpolation < 0 || params.interpolation >= _n_interpolation) { + PyErr_Format(PyExc_ValueError, "invalid interpolation value %d", + params.interpolation); + goto error; + } + + 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, ¶ms.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() |