/* -*- mode: c++; c-basic-offset: 4 -*- */ /* A rewrite of _backend_agg using PyCXX to handle ref counting, etc.. */ /* Python API mandates Python.h is included *first* */ #include "Python.h" /* TODO: Remove this dependency */ #include "ft2font.h" #include "_image.h" #include "_backend_agg.h" #include "mplutils.h" #include #include #include #include #include #include #include #include "agg_conv_curve.h" #include "agg_conv_transform.h" #include "agg_image_accessors.h" #include "agg_renderer_primitives.h" #include "agg_scanline_storage_aa.h" #include "agg_scanline_storage_bin.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_linear.h" #include "agg_span_pattern_rgba.h" #include "agg_span_gouraud_rgba.h" #include "agg_conv_shorten_path.h" #include "util/agg_color_conv_rgb8.h" #include "MPL_isnan.h" #include "numpy/arrayobject.h" #include "agg_py_transforms.h" #include "file_compat.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef M_PI_4 #define M_PI_4 0.785398163397448309616 #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 #endif /* Convert dashes from the Python representation as nested sequences to the C++ representation as a std::vector > (GCAgg::dash_t) */ void convert_dashes(const Py::Tuple& dashes, double dpi, GCAgg::dash_t& dashes_out, double& dashOffset_out) { if (dashes.length() != 2) { throw Py::ValueError( Printf("Dash descriptor must be a length 2 tuple; found %d", dashes.length()).str() ); } dashes_out.clear(); dashOffset_out = 0.0; if (dashes[0].ptr() == Py_None) { return; } dashOffset_out = double(Py::Float(dashes[0])) * dpi / 72.0; Py::SeqBase dashSeq = dashes[1]; size_t Ndash = dashSeq.length(); if (Ndash % 2 != 0) { throw Py::ValueError( Printf("Dash sequence must be an even length sequence; found %d", Ndash).str() ); } dashes_out.clear(); dashes_out.reserve(Ndash / 2); double val0, val1; for (size_t i = 0; i < Ndash; i += 2) { val0 = double(Py::Float(dashSeq[i])) * dpi / 72.0; val1 = double(Py::Float(dashSeq[i+1])) * dpi / 72.0; dashes_out.push_back(std::make_pair(val0, val1)); } } Py::Object BufferRegion::to_string(const Py::Tuple &args) { // owned=true to prevent memory leak #if PY3K return Py::Bytes #else return Py::String #endif (PyBytes_FromStringAndSize((const char*)data, height*stride), true); } Py::Object BufferRegion::set_x(const Py::Tuple &args) { args.verify_length(1); size_t x = (long) Py::Int(args[0]); rect.x1 = x; return Py::Object(); } Py::Object BufferRegion::set_y(const Py::Tuple &args) { args.verify_length(1); size_t y = (long)Py::Int(args[0]); rect.y1 = y; return Py::Object(); } Py::Object BufferRegion::get_extents(const Py::Tuple &args) { args.verify_length(0); Py::Tuple extents(4); extents[0] = Py::Int(rect.x1); extents[1] = Py::Int(rect.y1); extents[2] = Py::Int(rect.x2); extents[3] = Py::Int(rect.y2); return extents; } Py::Object BufferRegion::to_string_argb(const Py::Tuple &args) { // owned=true to prevent memory leak Py_ssize_t length; unsigned char* pix; unsigned char* begin; unsigned char tmp; size_t i, j; PyObject* str = PyBytes_FromStringAndSize((const char*)data, height * stride); if (PyBytes_AsStringAndSize(str, (char**)&begin, &length)) { throw Py::TypeError("Could not create memory for blit"); } for (i = 0; i < (size_t)height; ++i) { pix = begin + 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; } } #if PY3K return Py::Bytes #else return Py::String #endif (str, true); } GCAgg::GCAgg(const Py::Object &gc, double dpi) : dpi(dpi), isaa(true), dashOffset(0.0) { _VERBOSE("GCAgg::GCAgg"); linewidth = points_to_pixels(gc.getAttr("_linewidth")) ; alpha = Py::Float(gc.getAttr("_alpha")); forced_alpha = Py::Boolean(gc.getAttr("_forced_alpha")); color = get_color(gc); _set_antialiased(gc); _set_linecap(gc); _set_joinstyle(gc); _set_dashes(gc); _set_clip_rectangle(gc); _set_clip_path(gc); _set_snap(gc); _set_hatch_path(gc); _set_sketch_params(gc); } void GCAgg::_set_antialiased(const Py::Object& gc) { _VERBOSE("GCAgg::antialiased"); isaa = Py::Boolean(gc.getAttr("_antialiased")); } agg::rgba GCAgg::get_color(const Py::Object& gc) { _VERBOSE("GCAgg::get_color"); Py::Tuple rgb = Py::Tuple(gc.getAttr("_rgb")); double r = Py::Float(rgb[0]); double g = Py::Float(rgb[1]); double b = Py::Float(rgb[2]); double a = Py::Float(rgb[3]); return agg::rgba(r, g, b, a); } double GCAgg::points_to_pixels(const Py::Object& points) { _VERBOSE("GCAgg::points_to_pixels"); double p = Py::Float(points) ; return p * dpi / 72.0; } void GCAgg::_set_linecap(const Py::Object& gc) { _VERBOSE("GCAgg::_set_linecap"); std::string capstyle = Py::String(gc.getAttr("_capstyle")).encode("utf-8"); if (capstyle == "butt") { cap = agg::butt_cap; } else if (capstyle == "round") { cap = agg::round_cap; } else if (capstyle == "projecting") { cap = agg::square_cap; } else { throw Py::ValueError(Printf("GC _capstyle attribute must be one of butt, round, projecting; found %s", capstyle.c_str()).str()); } } void GCAgg::_set_joinstyle(const Py::Object& gc) { _VERBOSE("GCAgg::_set_joinstyle"); std::string joinstyle = Py::String(gc.getAttr("_joinstyle")).encode("utf-8"); if (joinstyle == "miter") { join = agg::miter_join_revert; } else if (joinstyle == "round") { join = agg::round_join; } else if (joinstyle == "bevel") { join = agg::bevel_join; } else { throw Py::ValueError(Printf("GC _joinstyle attribute must be one of butt, round, projecting; found %s", joinstyle.c_str()).str()); } } void GCAgg::_set_dashes(const Py::Object& gc) { //return the dashOffset, dashes sequence tuple. _VERBOSE("GCAgg::_set_dashes"); Py::Object dash_obj(gc.getAttr("_dashes")); if (dash_obj.ptr() == Py_None) { dashes.clear(); return; } convert_dashes(dash_obj, dpi, dashes, dashOffset); } void GCAgg::_set_clip_rectangle(const Py::Object& gc) { //set the clip rectangle from the gc _VERBOSE("GCAgg::_set_clip_rectangle"); Py::Object o(gc.getAttr("_cliprect")); cliprect = o; } void GCAgg::_set_clip_path(const Py::Object& gc) { //set the clip path from the gc _VERBOSE("GCAgg::_set_clip_path"); Py::Object method_obj = gc.getAttr("get_clip_path"); Py::Callable method(method_obj); Py::Tuple path_and_transform = method.apply(Py::Tuple()); if (path_and_transform[0].ptr() != Py_None) { clippath = path_and_transform[0]; clippath_trans = py_to_agg_transformation_matrix(path_and_transform[1].ptr()); } } void GCAgg::_set_snap(const Py::Object& gc) { //set the snap setting _VERBOSE("GCAgg::_set_snap"); Py::Object method_obj = gc.getAttr("get_snap"); Py::Callable method(method_obj); Py::Object py_snap = method.apply(Py::Tuple()); if (py_snap.isNone()) { snap_mode = SNAP_AUTO; } else if (py_snap.isTrue()) { snap_mode = SNAP_TRUE; } else { snap_mode = SNAP_FALSE; } } void GCAgg::_set_hatch_path(const Py::Object& gc) { _VERBOSE("GCAgg::_set_hatch_path"); Py::Object method_obj = gc.getAttr("get_hatch_path"); Py::Callable method(method_obj); hatchpath = method.apply(Py::Tuple()); if (hatchpath.ptr() == NULL) throw Py::Exception(); } void GCAgg::_set_sketch_params(const Py::Object& gc) { _VERBOSE("GCAgg::_get_sketch_params"); Py::Object method_obj = gc.getAttr("get_sketch_params"); Py::Callable method(method_obj); Py::Object result = method.apply(Py::Tuple()); if (result.ptr() == Py_None) { sketch_scale = 0.0; } else { Py::Tuple sketch_params(result); sketch_scale = Py::Float(sketch_params[0]); sketch_length = Py::Float(sketch_params[1]); sketch_randomness = Py::Float(sketch_params[2]); } } const size_t RendererAgg::PIXELS_PER_INCH(96); RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi, int debug) : 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(), debug(debug), _fill_color(agg::rgba(1, 1, 1, 0)) { _VERBOSE("RendererAgg::RendererAgg"); 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); hatchRenderingBuffer.attach(hatchBuffer, HATCH_SIZE, HATCH_SIZE, HATCH_SIZE*4); } 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); } } template void RendererAgg::set_clipbox(const Py::Object& cliprect, R& rasterizer) { //set the clip rectangle from the gc _VERBOSE("RendererAgg::set_clipbox"); double l, b, r, t; if (py_convert_bbox(cliprect.ptr(), l, b, r, t)) { rasterizer.clip_box(std::max(int(floor(l + 0.5)), 0), std::max(int(floor(height - b + 0.5)), 0), std::min(int(floor(r + 0.5)), int(width)), std::min(int(floor(height - t + 0.5)), int(height))); } else { rasterizer.clip_box(0, 0, width, height); } _VERBOSE("RendererAgg::set_clipbox done"); } std::pair RendererAgg::_get_rgba_face(const Py::Object& rgbFace, double alpha, bool forced_alpha) { _VERBOSE("RendererAgg::_get_rgba_face"); std::pair face; if (rgbFace.ptr() == Py_None) { face.first = false; } else { face.first = true; Py::Tuple rgb = Py::Tuple(rgbFace); if (forced_alpha || rgb.length() < 4) { face.second = rgb_to_color(rgb, alpha); } else { face.second = rgb_to_color(rgb, Py::Float(rgb[3])); } } return face; } Py::Object RendererAgg::copy_from_bbox(const Py::Tuple& args) { //copy region in bbox to buffer and return swig/agg buffer object args.verify_length(1); Py::Object box_obj = args[0]; double l, b, r, t; if (!py_convert_bbox(box_obj.ptr(), l, b, r, t)) { throw Py::TypeError("Invalid bbox provided to copy_from_bbox"); } agg::rect_i rect((int)l, height - (int)t, (int)r, height - (int)b); BufferRegion* reg = NULL; try { reg = new BufferRegion(rect, true); } catch (...) { throw Py::MemoryError( "RendererAgg::copy_from_bbox could not allocate memory for buffer"); } if (!reg) { throw Py::MemoryError( "RendererAgg::copy_from_bbox could not allocate memory for buffer"); } try { agg::rendering_buffer rbuf; rbuf.attach(reg->data, reg->width, reg->height, reg->stride); pixfmt pf(rbuf); renderer_base rb(pf); rb.copy_from(renderingBuffer, &rect, -rect.x1, -rect.y1); } catch (...) { delete reg; throw Py::RuntimeError("An unknown error occurred in copy_from_bbox"); } return Py::asObject(reg); } Py::Object RendererAgg::restore_region(const Py::Tuple& args) { //copy BufferRegion to buffer args.verify_length(1); BufferRegion* region = static_cast(args[0].ptr()); if (region->data == NULL) { throw Py::ValueError("Cannot restore_region from NULL data"); } agg::rendering_buffer rbuf; rbuf.attach(region->data, region->width, region->height, region->stride); rendererBase.copy_from(rbuf, 0, region->rect.x1, region->rect.y1); return Py::Object(); } // Restore the part of the saved region with offsets Py::Object RendererAgg::restore_region2(const Py::Tuple& args) { //copy BufferRegion to buffer args.verify_length(7); int x(0), y(0), xx1(0), yy1(0), xx2(0), yy2(0); try { xx1 = Py::Int(args[1]); yy1 = Py::Int(args[2]); xx2 = Py::Int(args[3]); yy2 = Py::Int(args[4]); x = Py::Int(args[5]); y = Py::Int(args[6]); } catch (Py::TypeError) { throw Py::TypeError("Invalid input arguments to restore_region2"); } BufferRegion* region = static_cast(args[0].ptr()); if (region->data == NULL) { throw Py::ValueError("Cannot restore_region from NULL data"); } agg::rect_i rect(xx1 - region->rect.x1, (yy1 - region->rect.y1), xx2 - region->rect.x1, (yy2 - region->rect.y1)); agg::rendering_buffer rbuf; rbuf.attach(region->data, region->width, region->height, region->stride); rendererBase.copy_from(rbuf, &rect, x, y); return Py::Object(); } bool RendererAgg::render_clippath(const Py::Object& clippath, const agg::trans_affine& clippath_trans) { typedef agg::conv_transform transformed_path_t; typedef agg::conv_curve curve_t; bool has_clippath = (clippath.ptr() != Py_None); if (has_clippath && (clippath.ptr() != lastclippath.ptr() || 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); PathIterator clippath_iter(clippath); rendererBaseAlphaMask.clear(agg::gray8(0, 0)); transformed_path_t transformed_clippath(clippath_iter, trans); curve_t curved_clippath(transformed_clippath); try { theRasterizer.add_path(curved_clippath); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } rendererAlphaMask.color(agg::gray8(255, 255)); agg::render_scanlines(theRasterizer, scanlineAlphaMask, rendererAlphaMask); lastclippath = clippath; lastclippath_transform = clippath_trans; } return has_clippath; } #define MARKER_CACHE_SIZE 512 Py::Object RendererAgg::draw_markers(const Py::Tuple& args) { typedef agg::conv_transform transformed_path_t; typedef PathSnapper snap_t; typedef agg::conv_curve curve_t; typedef agg::conv_stroke stroke_t; typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; typedef agg::renderer_base amask_ren_type; typedef agg::renderer_scanline_aa_solid amask_aa_renderer_type; args.verify_length(5, 6); Py::Object gc_obj = args[0]; Py::Object marker_path_obj = args[1]; agg::trans_affine marker_trans = py_to_agg_transformation_matrix(args[2].ptr()); Py::Object path_obj = args[3]; agg::trans_affine trans = py_to_agg_transformation_matrix(args[4].ptr()); Py::Object face_obj; if (args.size() == 6) { face_obj = args[5]; } GCAgg gc(gc_obj, dpi); // 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); PathIterator marker_path(marker_path_obj); transformed_path_t marker_path_transformed(marker_path, marker_trans); snap_t marker_path_snapped(marker_path_transformed, gc.snap_mode, marker_path.total_vertices(), gc.linewidth); curve_t marker_path_curve(marker_path_snapped); PathIterator path(path_obj); transformed_path_t path_transformed(path, trans); snap_t path_snapped(path_transformed, SNAP_FALSE, path.total_vertices(), 0.0); curve_t path_curve(path_snapped); path_curve.rewind(0); facepair_t face = _get_rgba_face(face_obj, gc.alpha, gc.forced_alpha); //maxim's suggestions for cached scanlines agg::scanline_storage_aa8 scanlines; theRasterizer.reset(); theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); 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) { try { theRasterizer.add_path(marker_path_curve); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } agg::render_scanlines(theRasterizer, slineP8, scanlines); fillSize = scanlines.byte_size(); if (fillSize >= MARKER_CACHE_SIZE) { fillCache = new agg::int8u[fillSize]; } scanlines.serialize(fillCache); } stroke_t stroke(marker_path_curve); stroke.width(gc.linewidth); stroke.line_cap(gc.cap); stroke.line_join(gc.join); theRasterizer.reset(); try { theRasterizer.add_path(stroke); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } agg::render_scanlines(theRasterizer, slineP8, scanlines); unsigned strokeSize = scanlines.byte_size(); if (strokeSize >= MARKER_CACHE_SIZE) { strokeCache = new agg::int8u[strokeSize]; } scanlines.serialize(strokeCache); theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, rendererBase); bool has_clippath = render_clippath(gc.clippath, 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( -(scanlines.min_x() + 1.0), (scanlines.max_y() + 1.0), width + scanlines.max_x() + 1.0, height - scanlines.min_y() + 1.0); if (has_clippath) { while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) { if (MPL_notisfinite64(x) || MPL_notisfinite64(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 (MPL_notisfinite64(x) || MPL_notisfinite64(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); return Py::Object(); } /** * 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 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 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(); } }; // MGDTODO: Support clip paths Py::Object RendererAgg::draw_text_image(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_text"); typedef agg::span_allocator color_span_alloc_type; typedef agg::span_interpolator_linear<> interpolator_type; typedef agg::image_accessor_clip image_accessor_type; typedef agg::span_image_filter_gray image_span_gen_type; typedef font_to_rgba span_gen_type; typedef agg::renderer_scanline_aa renderer_type; args.verify_length(5); const unsigned char* buffer = NULL; int width, height; Py::Object image_obj = args[0]; if (PyArray_Check(image_obj.ptr())) { PyObject* image_array = PyArray_FromObject( image_obj.ptr(), PyArray_UBYTE, 2, 2); if (!image_array) { throw Py::ValueError( "First argument to draw_text_image must be a FT2Font.Image object or a Nx2 uint8 numpy array."); } image_obj = Py::Object(image_array, true); buffer = (unsigned char *)PyArray_DATA(image_array); width = PyArray_DIM(image_array, 1); height = PyArray_DIM(image_array, 0); } else { FT2Image* image = static_cast( Py::getPythonExtensionBase(image_obj.ptr())); if (!image->get_buffer()) { throw Py::ValueError( "First argument to draw_text_image must be a FT2Font.Image object or a Nx2 uint8 numpy array."); } buffer = image->get_buffer(); width = image->get_width(); height = image->get_height(); } int x(0), y(0); try { x = Py::Int(args[1]); y = Py::Int(args[2]); } catch (Py::TypeError) { throw Py::TypeError("Invalid input arguments to draw_text_image"); } double angle = Py::Float(args[3]); GCAgg gc(args[4], dpi); theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, theRasterizer); agg::rendering_buffer srcbuf((agg::int8u*)buffer, width, height, width); agg::pixfmt_gray8 pixf_img(srcbuf); agg::trans_affine mtx; mtx *= agg::trans_affine_translation(0, -height); mtx *= agg::trans_affine_rotation(-angle * agg::pi / 180.0); mtx *= agg::trans_affine_translation(x, y); agg::path_storage rect; rect.move_to(0, 0); rect.line_to(width, 0); rect.line_to(width, height); rect.line_to(0, height); rect.line_to(0, 0); agg::conv_transform 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, 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); try { theRasterizer.add_path(rect2); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } agg::render_scanlines(theRasterizer, slineP8, ri); return Py::Object(); } 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); } }; Py::Object RendererAgg::draw_image(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_image"); args.verify_length(4, 7); // 7 if affine matrix if given GCAgg gc(args[0], dpi); Image *image = static_cast(args[3].ptr()); bool has_clippath = false; agg::trans_affine affine_trans; bool has_affine = false; double x, y, w, h; double alpha; if (args.size() == 7) { has_affine = true; x = Py::Float(args[1]); y = Py::Float(args[2]); w = Py::Float(args[4]); h = Py::Float(args[5]); affine_trans = py_to_agg_transformation_matrix(args[6].ptr()); } else { x = mpl_round(Py::Float(args[1])); y = mpl_round(Py::Float(args[2])); w = h = 0; /* w and h not used in this case, but assign to prevent warnings from the compiler */ } alpha = gc.alpha; theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, theRasterizer); has_clippath = render_clippath(gc.clippath, gc.clippath_trans); Py::Tuple empty; image->flipud_out(empty); pixfmt pixf(*(image->rbufOut)); if (has_affine | has_clippath) { agg::trans_affine mtx; agg::path_storage rect; if (has_affine) { mtx *= agg::trans_affine_scaling(1, -1); mtx *= agg::trans_affine_translation(0, image->rowsOut); mtx *= agg::trans_affine_scaling(w / (image->colsOut), h / (image->rowsOut)); mtx *= agg::trans_affine_translation(x, y); mtx *= affine_trans; mtx *= agg::trans_affine_scaling(1.0, -1.0); mtx *= agg::trans_affine_translation(0.0, (double) height); } else { mtx *= agg::trans_affine_translation( (int)x, (int)(height - (y + image->rowsOut))); } rect.move_to(0, 0); rect.line_to(image->colsOut, 0); rect.line_to(image->colsOut, image->rowsOut); rect.line_to(0, image->rowsOut); rect.line_to(0, 0); agg::conv_transform rect2(rect, mtx); agg::trans_affine inv_mtx(mtx); inv_mtx.invert(); typedef agg::span_allocator color_span_alloc_type; typedef agg::image_accessor_clip image_accessor_type; typedef agg::span_interpolator_linear<> interpolator_type; typedef agg::span_image_filter_rgba_nn image_span_gen_type; typedef agg::span_converter 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); if (has_clippath) { typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; typedef agg::renderer_base amask_ren_type; typedef agg::renderer_scanline_aa renderer_type_alpha; pixfmt_amask_type pfa(pixFmt, alphaMask); amask_ren_type r(pfa); renderer_type_alpha ri(r, sa, spans); try { theRasterizer.add_path(rect2); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } agg::render_scanlines(theRasterizer, scanlineAlphaMask, ri); } else { typedef agg::renderer_base ren_type; typedef agg::renderer_scanline_aa renderer_type; ren_type r(pixFmt); renderer_type ri(r, sa, spans); try { theRasterizer.add_path(rect2); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } agg::render_scanlines(theRasterizer, slineP8, ri); } } else { set_clipbox(gc.cliprect, rendererBase); rendererBase.blend_from( pixf, 0, (int)x, (int)(height - (y + image->rowsOut)), (agg::int8u)(alpha * 255)); } rendererBase.reset_clipping(true); image->flipud_out(empty); return Py::Object(); } template void RendererAgg::_draw_path(path_t& path, bool has_clippath, const facepair_t& face, const GCAgg& gc) { typedef agg::conv_stroke stroke_t; typedef agg::conv_dash dash_t; typedef agg::conv_stroke stroke_dash_t; typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; typedef agg::renderer_base amask_ren_type; typedef agg::renderer_scanline_aa_solid amask_aa_renderer_type; typedef agg::renderer_scanline_bin_solid amask_bin_renderer_type; // Render face if (face.first) { try { theRasterizer.add_path(path); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } 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.hatchpath.isNone()) { // 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 hatch_path_trans_t; typedef agg::conv_curve hatch_path_curve_t; typedef agg::conv_stroke hatch_path_stroke_t; 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(1.0); 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.color); try { theRasterizer.add_path(hatch_path_curve); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } agg::render_scanlines(theRasterizer, slineP8, rs); try { theRasterizer.add_path(hatch_path_stroke); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } 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, 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 span_gen_type; agg::span_allocator sa; img_source_type img_src(hatch_img_pixf); span_gen_type sg(img_src, 0, 0); try { theRasterizer.add_path(path); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } 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 = 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(linewidth); stroke.line_cap(gc.cap); stroke.line_join(gc.join); try { theRasterizer.add_path(stroke); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } } else { dash_t dash(path); for (GCAgg::dash_t::const_iterator i = gc.dashes.begin(); i != gc.dashes.end(); ++i) { double val0 = i->first; double val1 = i->second; if (!gc.isaa) { val0 = (int)val0 + 0.5; val1 = (int)val1 + 0.5; } dash.add_dash(val0, val1); } stroke_dash_t stroke(dash); stroke.line_cap(gc.cap); stroke.line_join(gc.join); stroke.width(linewidth); try { theRasterizer.add_path(stroke); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what()); } } 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); } } } } Py::Object RendererAgg::draw_path(const Py::Tuple& args) { typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; typedef PathClipper clipped_t; typedef PathSnapper snapped_t; typedef PathSimplifier simplify_t; typedef agg::conv_curve curve_t; typedef Sketch sketch_t; _VERBOSE("RendererAgg::draw_path"); args.verify_length(3, 4); GCAgg gc(args[0], dpi); PathIterator path(args[1]); agg::trans_affine trans = py_to_agg_transformation_matrix(args[2].ptr()); Py::Object face_obj; if (args.size() == 4) face_obj = args[3]; facepair_t face = _get_rgba_face(face_obj, gc.alpha, gc.forced_alpha); theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath, 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.hatchpath.isNone() && !path.has_curves(); bool simplify = path.should_simplify() && clip; double snapping_linewidth = 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, 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); try { _draw_path(sketch, has_clippath, face, gc); } catch (const char* e) { throw Py::RuntimeError(e); } return Py::Object(); } template Py::Object RendererAgg::_draw_path_collection_generic (GCAgg& gc, agg::trans_affine master_transform, const Py::Object& cliprect, const Py::Object& clippath, const agg::trans_affine& clippath_trans, const PathGenerator& path_generator, const Py::Object& transforms_obj, const Py::Object& offsets_obj, const agg::trans_affine& offset_trans, const Py::Object& facecolors_obj, const Py::Object& edgecolors_obj, const Py::SeqBase& linewidths, const Py::SeqBase& linestyles_obj, const Py::SeqBase& antialiaseds, const bool data_offsets) { typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; typedef PathClipper clipped_t; typedef PathSnapper snapped_t; typedef agg::conv_curve snapped_curve_t; typedef agg::conv_curve curve_t; PyArrayObject* offsets = (PyArrayObject*)PyArray_FromObject (offsets_obj.ptr(), PyArray_DOUBLE, 0, 2); if (!offsets || (PyArray_NDIM(offsets) == 2 && PyArray_DIM(offsets, 1) != 2) || (PyArray_NDIM(offsets) == 1 && PyArray_DIM(offsets, 0) != 0)) { Py_XDECREF(offsets); throw Py::ValueError("Offsets array must be Nx2"); } Py::Object offsets_arr_obj((PyObject*)offsets, true); PyArrayObject* facecolors = (PyArrayObject*)PyArray_FromObject (facecolors_obj.ptr(), PyArray_DOUBLE, 1, 2); if (!facecolors || (PyArray_NDIM(facecolors) == 1 && PyArray_DIM(facecolors, 0) != 0) || (PyArray_NDIM(facecolors) == 2 && PyArray_DIM(facecolors, 1) != 4)) { Py_XDECREF(facecolors); throw Py::ValueError("Facecolors must be a Nx4 numpy array or empty"); } Py::Object facecolors_arr_obj((PyObject*)facecolors, true); PyArrayObject* edgecolors = (PyArrayObject*)PyArray_FromObject (edgecolors_obj.ptr(), PyArray_DOUBLE, 1, 2); if (!edgecolors || (PyArray_NDIM(edgecolors) == 1 && PyArray_DIM(edgecolors, 0) != 0) || (PyArray_NDIM(edgecolors) == 2 && PyArray_DIM(edgecolors, 1) != 4)) { Py_XDECREF(edgecolors); throw Py::ValueError("Edgecolors must be a Nx4 numpy array"); } Py::Object edgecolors_arr_obj((PyObject*)edgecolors, true); PyArrayObject* transforms_arr = (PyArrayObject*)PyArray_FromObject (transforms_obj.ptr(), PyArray_DOUBLE, 1, 3); if (!transforms_arr || (PyArray_NDIM(transforms_arr) == 1 && PyArray_DIM(transforms_arr, 0) != 0) || (PyArray_NDIM(transforms_arr) == 2) || (PyArray_NDIM(transforms_arr) == 3 && ((PyArray_DIM(transforms_arr, 1) != 3) || (PyArray_DIM(transforms_arr, 2) != 3)))) { Py_XDECREF(transforms_arr); throw Py::ValueError("Transforms must be a Nx3x3 numpy array"); } size_t Npaths = path_generator.num_paths(); size_t Noffsets = offsets->dimensions[0]; size_t N = std::max(Npaths, Noffsets); size_t Ntransforms = transforms_arr->dimensions[0]; size_t Nfacecolors = facecolors->dimensions[0]; size_t Nedgecolors = edgecolors->dimensions[0]; size_t Nlinewidths = linewidths.length(); size_t Nlinestyles = std::min(linestyles_obj.length(), N); size_t Naa = antialiaseds.length(); if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) { Py_XDECREF(transforms_arr); return Py::Object(); } size_t i = 0; // Convert all of the transforms up front typedef std::vector transforms_t; transforms_t transforms; transforms.reserve(Ntransforms); for (i = 0; i < Ntransforms; ++i) { /* TODO: Use a Numpy iterator */ agg::trans_affine trans( *(double *)PyArray_GETPTR3(transforms_arr, i, 0, 0), *(double *)PyArray_GETPTR3(transforms_arr, i, 1, 0), *(double *)PyArray_GETPTR3(transforms_arr, i, 0, 1), *(double *)PyArray_GETPTR3(transforms_arr, i, 1, 1), *(double *)PyArray_GETPTR3(transforms_arr, i, 0, 2), *(double *)PyArray_GETPTR3(transforms_arr, i, 1, 2)); trans *= master_transform; transforms.push_back(trans); } // Convert all the dashes up front typedef std::vector > dashes_t; dashes_t dashes; dashes.resize(Nlinestyles); i = 0; for (dashes_t::iterator d = dashes.begin(); d != dashes.end(); ++d, ++i) { convert_dashes(Py::Tuple(linestyles_obj[i]), dpi, d->second, d->first); } // 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 (i = 0; i < N; ++i) { typename PathGenerator::path_iterator path = path_generator(i); if (Ntransforms) { trans = transforms[i % Ntransforms]; } else { trans = master_transform; } if (Noffsets) { double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); offset_trans.transform(&xo, &yo); if (data_offsets) { 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) { size_t fi = i % Nfacecolors; face.second = agg::rgba( *(double*)PyArray_GETPTR2(facecolors, fi, 0), *(double*)PyArray_GETPTR2(facecolors, fi, 1), *(double*)PyArray_GETPTR2(facecolors, fi, 2), *(double*)PyArray_GETPTR2(facecolors, fi, 3)); } if (Nedgecolors) { size_t ei = i % Nedgecolors; gc.color = agg::rgba( *(double*)PyArray_GETPTR2(edgecolors, ei, 0), *(double*)PyArray_GETPTR2(edgecolors, ei, 1), *(double*)PyArray_GETPTR2(edgecolors, ei, 2), *(double*)PyArray_GETPTR2(edgecolors, ei, 3)); if (Nlinewidths) { gc.linewidth = double(Py::Float(linewidths[i % Nlinewidths])) * dpi / 72.0; } else { gc.linewidth = 1.0; } if (Nlinestyles) { gc.dashes = dashes[i % Nlinestyles].second; gc.dashOffset = dashes[i % Nlinestyles].first; } } bool do_clip = !face.first && gc.hatchpath.isNone() && !has_curves; if (check_snap) { gc.isaa = Py::Boolean(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); snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), 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 = Py::Boolean(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); } } } Py_XDECREF(transforms_arr); return Py::Object(); } class PathListGenerator { const Py::SeqBase& m_paths; size_t m_npaths; public: typedef PathIterator path_iterator; inline PathListGenerator(const Py::SeqBase& paths) : m_paths(paths), m_npaths(paths.size()) { } inline size_t num_paths() const { return m_npaths; } inline path_iterator operator()(size_t i) const { return PathIterator(m_paths[i % m_npaths]); } }; Py::Object RendererAgg::draw_path_collection(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_path_collection"); args.verify_length(13); Py::Object gc_obj = args[0]; GCAgg gc(gc_obj, dpi); agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[1].ptr()); Py::SeqBase path = args[2]; PathListGenerator path_generator(path); Py::Object transforms_obj = args[3]; Py::Object offsets_obj = args[4]; agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[5].ptr()); Py::Object facecolors_obj = args[6]; Py::Object edgecolors_obj = args[7]; Py::SeqBase linewidths = args[8]; Py::SeqBase linestyles_obj = args[9]; Py::SeqBase antialiaseds = args[10]; // We don't actually care about urls for Agg, so just ignore it. // Py::SeqBase urls = args[11]; std::string offset_position = Py::String(args[12]).encode("utf-8"); bool data_offsets = (offset_position == "data"); try { _draw_path_collection_generic (gc, master_transform, gc.cliprect, gc.clippath, gc.clippath_trans, path_generator, transforms_obj, offsets_obj, offset_trans, facecolors_obj, edgecolors_obj, linewidths, linestyles_obj, antialiaseds, data_offsets); } catch (const char *e) { throw Py::RuntimeError(e); } return Py::Object(); } class QuadMeshGenerator { size_t m_meshWidth; size_t m_meshHeight; PyArrayObject* m_coordinates; class QuadMeshPathIterator { size_t m_iterator; size_t m_m, m_n; PyArrayObject* m_coordinates; public: QuadMeshPathIterator(size_t m, size_t n, PyArrayObject* 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); double* pair = (double*)PyArray_GETPTR2(m_coordinates, n, m); *x = *pair++; *y = *pair; 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(size_t meshWidth, size_t meshHeight, PyObject* coordinates) : m_meshWidth(meshWidth), m_meshHeight(meshHeight), m_coordinates(NULL) { PyArrayObject* coordinates_array = \ (PyArrayObject*)PyArray_ContiguousFromObject( coordinates, PyArray_DOUBLE, 3, 3); if (!coordinates_array) { throw Py::ValueError("Invalid coordinates array."); } m_coordinates = coordinates_array; } inline ~QuadMeshGenerator() { Py_XDECREF(m_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); } }; Py::Object RendererAgg::draw_quad_mesh(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_quad_mesh"); args.verify_length(10); //segments, trans, clipbox, colors, linewidths, antialiaseds GCAgg gc(args[0], dpi); agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[1].ptr()); size_t mesh_width = (long)Py::Int(args[2]); size_t mesh_height = (long)Py::Int(args[3]); Py::Object coordinates = args[4]; Py::Object offsets_obj = args[5]; agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[6].ptr()); Py::Object facecolors_obj = args[7]; bool antialiased = (bool)Py::Boolean(args[8]); Py::Object edgecolors_obj = args[9]; QuadMeshGenerator path_generator(mesh_width, mesh_height, coordinates.ptr()); Py::Object transforms_obj = Py::List(0); Py::Tuple linewidths(1); linewidths[0] = Py::Float(gc.linewidth); Py::SeqBase linestyles_obj; Py::Tuple antialiaseds(1); antialiaseds[0] = Py::Int(antialiased ? 1 : 0); if (edgecolors_obj.isNone()) { if (antialiased) { edgecolors_obj = facecolors_obj; } else { npy_intp dims[] = { 0, 0 }; edgecolors_obj = PyArray_SimpleNew(1, dims, PyArray_DOUBLE); } } try { _draw_path_collection_generic (gc, master_transform, gc.cliprect, gc.clippath, gc.clippath_trans, path_generator, transforms_obj, offsets_obj, offset_trans, facecolors_obj, edgecolors_obj, linewidths, linestyles_obj, antialiaseds, false); } catch (const char* e) { throw Py::RuntimeError(e); } return Py::Object(); } void RendererAgg::_draw_gouraud_triangle(const double* points, const double* colors, agg::trans_affine trans, bool has_clippath) { typedef agg::rgba8 color_t; typedef agg::span_gouraud_rgba span_gen_t; typedef agg::span_allocator span_alloc_t; trans *= agg::trans_affine_scaling(1.0, -1.0); trans *= agg::trans_affine_translation(0.0, (double)height); double tpoints[6]; for (int i = 0; i < 6; i += 2) { tpoints[i] = points[i]; tpoints[i+1] = points[i+1]; trans.transform(&tpoints[i], &tpoints[i+1]); } span_alloc_t span_alloc; span_gen_t span_gen; span_gen.colors( agg::rgba(colors[0], colors[1], colors[2], colors[3]), agg::rgba(colors[4], colors[5], colors[6], colors[7]), agg::rgba(colors[8], colors[9], colors[10], colors[11])); span_gen.triangle( tpoints[0], tpoints[1], tpoints[2], tpoints[3], tpoints[4], tpoints[5], 0.5); try { theRasterizer.add_path(span_gen); } catch (std::overflow_error &e) { throw Py::OverflowError(e.what() ); } if (has_clippath) { typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; typedef agg::renderer_base amask_ren_type; typedef agg::renderer_scanline_aa 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); } } Py::Object RendererAgg::draw_gouraud_triangle(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_gouraud_triangle"); args.verify_length(4); GCAgg gc(args[0], dpi); Py::Object points_obj = args[1]; Py::Object colors_obj = args[2]; agg::trans_affine trans = py_to_agg_transformation_matrix(args[3].ptr()); theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath, gc.clippath_trans); PyArrayObject* points = (PyArrayObject*)PyArray_ContiguousFromAny (points_obj.ptr(), PyArray_DOUBLE, 2, 2); if (!points || PyArray_DIM(points, 0) != 3 || PyArray_DIM(points, 1) != 2) { Py_XDECREF(points); throw Py::ValueError("points must be a 3x2 numpy array"); } points_obj = Py::Object((PyObject*)points, true); PyArrayObject* colors = (PyArrayObject*)PyArray_ContiguousFromAny (colors_obj.ptr(), PyArray_DOUBLE, 2, 2); if (!colors || PyArray_DIM(colors, 0) != 3 || PyArray_DIM(colors, 1) != 4) { Py_XDECREF(colors); throw Py::ValueError("colors must be a 3x4 numpy array"); } colors_obj = Py::Object((PyObject*)colors, true); _draw_gouraud_triangle( (double*)PyArray_DATA(points), (double*)PyArray_DATA(colors), trans, has_clippath); return Py::Object(); } Py::Object RendererAgg::draw_gouraud_triangles(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_gouraud_triangles"); args.verify_length(4); typedef agg::rgba8 color_t; typedef agg::span_gouraud_rgba span_gen_t; GCAgg gc(args[0], dpi); Py::Object points_obj = args[1]; Py::Object colors_obj = args[2]; agg::trans_affine trans = py_to_agg_transformation_matrix(args[3].ptr()); double c_points[6]; double c_colors[12]; theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath, gc.clippath_trans); PyArrayObject* points = (PyArrayObject*)PyArray_FromObject (points_obj.ptr(), PyArray_DOUBLE, 3, 3); if (!points || PyArray_DIM(points, 1) != 3 || PyArray_DIM(points, 2) != 2) { Py_XDECREF(points); throw Py::ValueError("points must be a Nx3x2 numpy array"); } points_obj = Py::Object((PyObject*)points, true); PyArrayObject* colors = (PyArrayObject*)PyArray_FromObject (colors_obj.ptr(), PyArray_DOUBLE, 3, 3); if (!colors || PyArray_DIM(colors, 1) != 3 || PyArray_DIM(colors, 2) != 4) { Py_XDECREF(colors); throw Py::ValueError("colors must be a Nx3x4 numpy array"); } colors_obj = Py::Object((PyObject*)colors, true); if (PyArray_DIM(points, 0) != PyArray_DIM(colors, 0)) { throw Py::ValueError("points and colors arrays must be the same length"); } for (int i = 0; i < PyArray_DIM(points, 0); ++i) { for (int j = 0; j < 3; ++j) { for (int k = 0; k < 2; ++k) { c_points[j*2+k] = *(double *)PyArray_GETPTR3(points, i, j, k); } } for (int j = 0; j < 3; ++j) { for (int k = 0; k < 4; ++k) { c_colors[j*4+k] = *(double *)PyArray_GETPTR3(colors, i, j, k); } } _draw_gouraud_triangle( c_points, c_colors, trans, has_clippath); } return Py::Object(); } Py::Object RendererAgg::write_rgba(const Py::Tuple& args) { _VERBOSE("RendererAgg::write_rgba"); args.verify_length(1); FILE *fp = NULL; mpl_off_t offset; Py::Object py_fileobj = Py::Object(args[0]); PyObject* py_file = NULL; bool close_file = false; if (py_fileobj.isString()) { if ((py_file = mpl_PyFile_OpenFile(py_fileobj.ptr(), (char *)"wb")) == NULL) { throw Py::Exception(); } } else { py_file = py_fileobj.ptr(); } if ((fp = mpl_PyFile_Dup(py_file, (char *)"wb", &offset))) { if (fwrite(pixBuffer, 1, NUMBYTES, fp) != NUMBYTES) { if (mpl_PyFile_DupClose(py_file, fp, offset)) { throw Py::RuntimeError("Error closing dupe file handle"); } if (close_file) { mpl_PyFile_CloseFile(py_file); Py_DECREF(py_file); } throw Py::RuntimeError("Error writing to file"); } if (mpl_PyFile_DupClose(py_file, fp, offset)) { throw Py::RuntimeError("Error closing dupe file handle"); } if (close_file) { mpl_PyFile_CloseFile(py_file); Py_DECREF(py_file); } } else { PyErr_Clear(); PyObject* write_method = PyObject_GetAttrString(py_fileobj.ptr(), "write"); if (!(write_method && PyCallable_Check(write_method))) { Py_XDECREF(write_method); throw Py::TypeError( "Object does not appear to be a 8-bit string path or a Python file-like object"); } #if PY3K PyObject_CallFunction(write_method, (char *)"y#", pixBuffer, NUMBYTES); #else PyObject_CallFunction(write_method, (char *)"s#", pixBuffer, NUMBYTES); #endif Py_XDECREF(write_method); } return Py::Object(); } Py::Object RendererAgg::tostring_rgb(const Py::Tuple& args) { //"Return the rendered buffer as an RGB string"; _VERBOSE("RendererAgg::tostring_rgb"); args.verify_length(0); int row_len = width * 3; unsigned char* buf_tmp = new unsigned char[row_len * height]; if (buf_tmp == NULL) { //todo: also handle allocation throw throw Py::MemoryError( "RendererAgg::tostring_rgb could not allocate memory"); } try { agg::rendering_buffer renderingBufferTmp; renderingBufferTmp.attach(buf_tmp, width, height, row_len); agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_rgb24()); } catch (...) { delete [] buf_tmp; throw Py::RuntimeError("Unknown exception occurred in tostring_rgb"); } //todo: how to do this with native CXX #if PY3K PyObject* o = Py_BuildValue("y#", buf_tmp, row_len * height); #else PyObject* o = Py_BuildValue("s#", buf_tmp, row_len * height); #endif delete [] buf_tmp; return Py::asObject(o); } Py::Object RendererAgg::tostring_argb(const Py::Tuple& args) { //"Return the rendered buffer as an RGB string"; _VERBOSE("RendererAgg::tostring_argb"); args.verify_length(0); int row_len = width * 4; unsigned char* buf_tmp = new unsigned char[row_len * height]; if (buf_tmp == NULL) { //todo: also handle allocation throw throw Py::MemoryError("RendererAgg::tostring_argb could not allocate memory"); } try { agg::rendering_buffer renderingBufferTmp; renderingBufferTmp.attach(buf_tmp, width, height, row_len); agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_argb32()); } catch (...) { delete [] buf_tmp; throw Py::RuntimeError("Unknown exception occurred in tostring_argb"); } //todo: how to do this with native CXX #if PY3K PyObject* o = Py_BuildValue("y#", buf_tmp, row_len * height); #else PyObject* o = Py_BuildValue("s#", buf_tmp, row_len * height); #endif delete [] buf_tmp; return Py::asObject(o); } Py::Object RendererAgg::tostring_bgra(const Py::Tuple& args) { //"Return the rendered buffer as an RGB string"; _VERBOSE("RendererAgg::tostring_bgra"); args.verify_length(0); int row_len = width * 4; unsigned char* buf_tmp = new unsigned char[row_len * height]; if (buf_tmp == NULL) { //todo: also handle allocation throw throw Py::MemoryError("RendererAgg::tostring_bgra could not allocate memory"); } try { agg::rendering_buffer renderingBufferTmp; renderingBufferTmp.attach(buf_tmp, width, height, row_len); agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_bgra32()); } catch (...) { delete [] buf_tmp; throw Py::RuntimeError("Unknown exception occurred in tostring_bgra"); } //todo: how to do this with native CXX #if PY3K PyObject* o = Py_BuildValue("y#", buf_tmp, row_len * height); #else PyObject* o = Py_BuildValue("s#", buf_tmp, row_len * height); #endif delete [] buf_tmp; return Py::asObject(o); } Py::Object RendererAgg::buffer_rgba(const Py::Tuple& args) { //"expose the rendered buffer as Python buffer object, starting from postion x,y"; _VERBOSE("RendererAgg::buffer_rgba"); args.verify_length(0); #if PY3K return Py::asObject(PyMemoryView_FromObject(this)); #else int row_len = width * 4; return Py::asObject(PyBuffer_FromReadWriteMemory( pixBuffer, row_len*height)); #endif } Py::Object RendererAgg::tostring_rgba_minimized(const Py::Tuple& args) { args.verify_length(0); int xmin = width; int ymin = height; int xmax = 0; int ymax = 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 < xmin) xmin = x; if (y < ymin) ymin = y; if (x > xmax) xmax = x; if (y > ymax) ymax = y; } pixel += 4; } } int newwidth = 0; int newheight = 0; PyObject *data; if (xmin < xmax && ymin < ymax) { // Expand the bounds by 1 pixel on all sides xmin = std::max(0, xmin - 1); ymin = std::max(0, ymin - 1); xmax = std::min(xmax, (int)width); ymax = std::min(ymax, (int)height); newwidth = xmax - xmin; newheight = ymax - ymin; int newsize = newwidth * newheight * 4; // NULL pointer causes Python to allocate uninitialized memory. // We then grab Python's pointer to uninitialized memory using // the _AsString() API. unsigned int* dst; data = PyBytes_FromStringAndSize(NULL, newsize); if (data == NULL) { throw Py::MemoryError("RendererAgg::tostring_rgba_minimized could not allocate memory"); } dst = (unsigned int *)PyBytes_AsString(data); unsigned int* src = (unsigned int*)pixBuffer; for (int y = ymin; y < ymax; ++y) { for (int x = xmin; x < xmax; ++x, ++dst) { *dst = src[y * width + x]; } } } else { data = PyBytes_FromStringAndSize(NULL, 0); if (data == NULL) { throw Py::MemoryError("RendererAgg::tostring_rgba_minimized could not allocate memory"); } } Py::Tuple bounds(4); bounds[0] = Py::Int(xmin); bounds[1] = Py::Int(ymin); bounds[2] = Py::Int(newwidth); bounds[3] = Py::Int(newheight); Py::Tuple result(2); result[0] = Py::Object(data, true); result[1] = bounds; return result; } Py::Object RendererAgg::clear(const Py::Tuple& args) { //"clear the rendered buffer"; _VERBOSE("RendererAgg::clear"); args.verify_length(0); rendererBase.clear(_fill_color); return Py::Object(); } agg::rgba RendererAgg::rgb_to_color(const Py::SeqBase& rgb, double alpha) { _VERBOSE("RendererAgg::rgb_to_color"); double r = Py::Float(rgb[0]); double g = Py::Float(rgb[1]); double b = Py::Float(rgb[2]); return agg::rgba(r, g, b, alpha); } double RendererAgg::points_to_pixels(const Py::Object& points) { _VERBOSE("RendererAgg::points_to_pixels"); double p = Py::Float(points) ; return p * dpi / 72.0; } #if PY3K int RendererAgg::buffer_get( Py_buffer* buf, int flags ) { return PyBuffer_FillInfo(buf, this, pixBuffer, width * height * 4, 1, PyBUF_SIMPLE); } #endif RendererAgg::~RendererAgg() { _VERBOSE("RendererAgg::~RendererAgg"); delete [] alphaBuffer; delete [] pixBuffer; } /* ------------ module methods ------------- */ Py::Object _backend_agg_module::new_renderer(const Py::Tuple &args, const Py::Dict &kws) { if (args.length() != 3) { throw Py::RuntimeError("Incorrect # of args to RendererAgg(width, height, dpi)."); } int debug; if (kws.hasKey("debug")) { debug = Py::Int(kws["debug"]); } else { debug = 0; } unsigned int width = (int)Py::Int(args[0]); unsigned int height = (int)Py::Int(args[1]); double dpi = Py::Float(args[2]); if (width > 1 << 15 || height > 1 << 15) { throw Py::ValueError("width and height must each be below 32768"); } if (dpi <= 0.0) { throw Py::ValueError("dpi must be positive"); } RendererAgg* renderer = NULL; try { renderer = new RendererAgg(width, height, dpi, debug); } catch (std::bad_alloc) { throw Py::RuntimeError("Could not allocate memory for image"); } return Py::asObject(renderer); } void BufferRegion::init_type() { behaviors().name("BufferRegion"); behaviors().doc("A wrapper to pass agg buffer objects to and from the python level"); add_varargs_method("set_x", &BufferRegion::set_x, "set_x(x)"); add_varargs_method("set_y", &BufferRegion::set_y, "set_y(y)"); add_varargs_method("get_extents", &BufferRegion::get_extents, "get_extents()"); add_varargs_method("to_string", &BufferRegion::to_string, "to_string()"); add_varargs_method("to_string_argb", &BufferRegion::to_string_argb, "to_string_argb()"); } void RendererAgg::init_type() { behaviors().name("RendererAgg"); behaviors().doc("The agg backend extension module"); add_varargs_method("draw_path", &RendererAgg::draw_path, "draw_path(gc, path, transform, rgbFace)\n"); add_varargs_method("draw_path_collection", &RendererAgg::draw_path_collection, "draw_path_collection(gc, master_transform, paths, transforms, offsets, offsetTrans, facecolors, edgecolors, linewidths, linestyles, antialiaseds)\n"); add_varargs_method("draw_quad_mesh", &RendererAgg::draw_quad_mesh, "draw_quad_mesh(gc, master_transform, meshWidth, meshHeight, coordinates, offsets, offsetTrans, facecolors, antialiaseds, showedges)\n"); add_varargs_method("draw_gouraud_triangle", &RendererAgg::draw_gouraud_triangle, "draw_gouraud_triangle(gc, points, colors, master_transform)\n"); add_varargs_method("draw_gouraud_triangles", &RendererAgg::draw_gouraud_triangles, "draw_gouraud_triangles(gc, points, colors, master_transform)\n"); add_varargs_method("draw_markers", &RendererAgg::draw_markers, "draw_markers(gc, marker_path, marker_trans, path, rgbFace)\n"); add_varargs_method("draw_text_image", &RendererAgg::draw_text_image, "draw_text_image(font_image, x, y, r, g, b, a)\n"); add_varargs_method("draw_image", &RendererAgg::draw_image, "draw_image(gc, x, y, im)"); add_varargs_method("write_rgba", &RendererAgg::write_rgba, "write_rgba(fname)"); add_varargs_method("tostring_rgb", &RendererAgg::tostring_rgb, "s = tostring_rgb()"); add_varargs_method("tostring_argb", &RendererAgg::tostring_argb, "s = tostring_argb()"); add_varargs_method("tostring_bgra", &RendererAgg::tostring_bgra, "s = tostring_bgra()"); add_varargs_method("tostring_rgba_minimized", &RendererAgg::tostring_rgba_minimized, "s = tostring_rgba_minimized()"); add_varargs_method("buffer_rgba", &RendererAgg::buffer_rgba, "buffer = buffer_rgba()"); add_varargs_method("clear", &RendererAgg::clear, "clear()"); add_varargs_method("copy_from_bbox", &RendererAgg::copy_from_bbox, "copy_from_bbox(bbox)"); add_varargs_method("restore_region", &RendererAgg::restore_region, "restore_region(region)"); add_varargs_method("restore_region2", &RendererAgg::restore_region2, "restore_region(region, x1, y1, x2, y2, x3, y3)"); #if PY3K behaviors().supportBufferType(); #endif } PyMODINIT_FUNC #if PY3K PyInit__backend_agg(void) #else init_backend_agg(void) #endif { //static _backend_agg_module* _backend_agg = new _backend_agg_module; _VERBOSE("init_backend_agg"); import_array(); static _backend_agg_module* _backend_agg = NULL; _backend_agg = new _backend_agg_module; #if PY3K return _backend_agg->module().ptr(); #endif }