From: <md...@us...> - 2008-12-18 13:47:23
|
Revision: 6663 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6663&view=rev Author: mdboom Date: 2008-12-18 13:47:19 +0000 (Thu, 18 Dec 2008) Log Message: ----------- Merged revisions 6660-6662 via svnmerge from https://matplotlib.svn.sf.net/svnroot/matplotlib/branches/v0_98_5_maint ........ r6660 | jdh2358 | 2008-12-18 07:10:51 -0500 (Thu, 18 Dec 2008) | 1 line applied maxosx backend update ........ r6661 | mdboom | 2008-12-18 08:41:35 -0500 (Thu, 18 Dec 2008) | 2 lines Fix bug where a line with NULL data limits prevents subsequent data limits from calculating correctly ........ r6662 | mdboom | 2008-12-18 08:42:13 -0500 (Thu, 18 Dec 2008) | 2 lines Fix docstring typo. ........ Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/doc/_templates/index.html trunk/matplotlib/doc/api/font_manager_api.rst trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py trunk/matplotlib/src/_macosx.m trunk/matplotlib/src/_path.cpp Property Changed: ---------------- trunk/matplotlib/ trunk/matplotlib/doc/pyplots/README trunk/matplotlib/doc/sphinxext/gen_gallery.py Property changes on: trunk/matplotlib ___________________________________________________________________ Modified: svnmerge-integrated - /branches/v0_91_maint:1-6428 /branches/v0_98_5_maint:1-6658 + /branches/v0_91_maint:1-6428 /branches/v0_98_5_maint:1-6662 Modified: svn:mergeinfo - /branches/v0_91_maint:5753-5771 /branches/v0_98_5_maint:6581,6585,6587,6589-6609,6614,6616,6625,6652 + /branches/v0_91_maint:5753-5771 /branches/v0_98_5_maint:6581,6585,6587,6589-6609,6614,6616,6625,6652,6660-6662 Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2008-12-18 13:42:13 UTC (rev 6662) +++ trunk/matplotlib/CHANGELOG 2008-12-18 13:47:19 UTC (rev 6663) @@ -1,3 +1,11 @@ +2008-12-18 Fix bug where a line with NULL data limits prevents + subsequent data limits from calculating correctly - MGD + +2008-12-17 Major documentation generator changes - MGD + +2008-12-17 Applied macosx backend patch with support for path + collections, quadmesh, etc... - JDH + 2008-12-17 fix dpi-dependent behavior of text bbox and arrow in annotate -JJL Modified: trunk/matplotlib/doc/_templates/index.html =================================================================== --- trunk/matplotlib/doc/_templates/index.html 2008-12-18 13:42:13 UTC (rev 6662) +++ trunk/matplotlib/doc/_templates/index.html 2008-12-18 13:47:19 UTC (rev 6663) @@ -1,5 +1,5 @@ {% extends "layout.html" %} -{% set title = 'Overview' %} +{% set title = 'matplotlib: python plotting' %} {% block body %} Modified: trunk/matplotlib/doc/api/font_manager_api.rst =================================================================== --- trunk/matplotlib/doc/api/font_manager_api.rst 2008-12-18 13:42:13 UTC (rev 6662) +++ trunk/matplotlib/doc/api/font_manager_api.rst 2008-12-18 13:47:19 UTC (rev 6663) @@ -11,7 +11,7 @@ :show-inheritance: :mod:`matplotlib.fontconfig_pattern` -======================================== +==================================== .. automodule:: matplotlib.fontconfig_pattern :members: Property changes on: trunk/matplotlib/doc/pyplots/README ___________________________________________________________________ Modified: svn:mergeinfo - /branches/v0_98_5_maint/doc/pyplots/README:6581,6585,6587,6589-6609,6614,6616,6625,6652 + /branches/v0_98_5_maint/doc/pyplots/README:6581,6585,6587,6589-6609,6614,6616,6625,6652,6660-6662 Property changes on: trunk/matplotlib/doc/sphinxext/gen_gallery.py ___________________________________________________________________ Modified: svn:mergeinfo - /branches/v0_91_maint/doc/_templates/gen_gallery.py:5753-5771 + /branches/v0_91_maint/doc/_templates/gen_gallery.py:5753-5771 /branches/v0_98_5_maint/doc/sphinxext/gen_gallery.py:6660-6662 Modified: trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py 2008-12-18 13:42:13 UTC (rev 6662) +++ trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py 2008-12-18 13:47:19 UTC (rev 6663) @@ -10,6 +10,7 @@ from matplotlib.figure import Figure from matplotlib.path import Path from matplotlib.mathtext import MathTextParser +from matplotlib.colors import colorConverter @@ -48,34 +49,47 @@ self.width, self.height = width, height def draw_path(self, gc, path, transform, rgbFace=None): - path = transform.transform_path(path) - for points, code in path.iter_segments(): - if code == Path.MOVETO: - gc.moveto(points) - elif code == Path.LINETO: - gc.lineto(points) - elif code == Path.CURVE3: - gc.curve3(points) - elif code == Path.CURVE4: - gc.curve4(points) - elif code == Path.CLOSEPOLY: - gc.closepoly() if rgbFace is not None: rgbFace = tuple(rgbFace) - gc.stroke(rgbFace) + if gc!=self.gc: + n = self.gc.level() - gc.level() + for i in range(n): self.gc.restore() + self.gc = gc + gc.draw_path(path, transform, rgbFace) + def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): + if rgbFace is not None: + rgbFace = tuple(rgbFace) + if gc!=self.gc: + n = self.gc.level() - gc.level() + for i in range(n): self.gc.restore() + self.gc = gc + gc.draw_markers(marker_path, marker_trans, path, trans, rgbFace) + + def draw_path_collection(self, *args): + gc = self.gc + args = args[:13] + gc.draw_path_collection(*args) + + def draw_quad_mesh(self, *args): + gc = self.gc + gc.draw_quad_mesh(*args) + def new_gc(self): self.gc.reset() return self.gc def draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None): - self.gc.set_clip_rectangle(bbox) im.flipud_out() nrows, ncols, data = im.as_rgba_str() - self.gc.draw_image(x, y, nrows, ncols, data) + self.gc.draw_image(x, y, nrows, ncols, data, bbox, clippath, clippath_trans) im.flipud_out() def draw_tex(self, gc, x, y, s, prop, angle): + if gc!=self.gc: + n = self.gc.level() - gc.level() + for i in range(n): self.gc.restore() + self.gc = gc # todo, handle props, angle, origins size = prop.get_size_in_points() texmanager = self.get_texmanager() @@ -88,12 +102,20 @@ gc.draw_mathtext(x, y, angle, Z) def _draw_mathtext(self, gc, x, y, s, prop, angle): + if gc!=self.gc: + n = self.gc.level() - gc.level() + for i in range(n): self.gc.restore() + self.gc = gc size = prop.get_size_in_points() ox, oy, width, height, descent, image, used_characters = \ self.mathtext_parser.parse(s, self.dpi, prop) gc.draw_mathtext(x, y, angle, 255 - image.as_array()) def draw_text(self, gc, x, y, s, prop, angle, ismath=False): + if gc!=self.gc: + n = self.gc.level() - gc.level() + for i in range(n): self.gc.restore() + self.gc = gc if ismath: self._draw_mathtext(gc, x, y, s, prop, angle) else: @@ -143,6 +165,11 @@ GraphicsContextBase.__init__(self) _macosx.GraphicsContext.__init__(self) + def set_foreground(self, fg, isRGB=False): + if not isRGB: + fg = colorConverter.to_rgb(fg) + _macosx.GraphicsContext.set_foreground(self, fg) + def set_clip_rectangle(self, box): GraphicsContextBase.set_clip_rectangle(self, box) if not box: return @@ -152,20 +179,8 @@ GraphicsContextBase.set_clip_path(self, path) if not path: return path = path.get_fully_transformed_path() - for points, code in path.iter_segments(): - if code == Path.MOVETO: - self.moveto(points) - elif code == Path.LINETO: - self.lineto(points) - elif code == Path.CURVE3: - self.curve3(points) - elif code == Path.CURVE4: - self.curve4(points) - elif code == Path.CLOSEPOLY: - self.closepoly() - self.clip_path() + _macosx.GraphicsContext.set_clip_path(self, path) - ######################################################################## # # The following functions and classes are for pylab and implement Modified: trunk/matplotlib/src/_macosx.m =================================================================== --- trunk/matplotlib/src/_macosx.m 2008-12-18 13:42:13 UTC (rev 6662) +++ trunk/matplotlib/src/_macosx.m 2008-12-18 13:47:19 UTC (rev 6663) @@ -4,15 +4,33 @@ #include <Python.h> #include "numpy/arrayobject.h" -static int nwin = 0; +static int nwin = 0; /* The number of open windows */ +static int ngc = 0; /* The number of graphics contexts in use */ -/* Varius NSApplicationDefined event subtypes */ +/* For drawing Unicode strings with ATSUI */ +static ATSUStyle style = NULL; +static ATSUTextLayout layout = NULL; + +/* CGFloat was defined in Mac OS X 10.5 */ +#ifndef CGFloat +#define CGFloat float +#endif + + +/* Various NSApplicationDefined event subtypes */ #define STDIN_READY 0 #define SIGINT_CALLED 1 #define STOP_EVENT_LOOP 2 #define WINDOW_CLOSING 3 +/* Path definitions */ +#define STOP 0 +#define MOVETO 1 +#define LINETO 2 +#define CURVE3 3 +#define CURVE4 4 +#define CLOSEPOLY 5 /* -------------------------- Helper function ---------------------------- */ static void stdin_ready(CFReadStreamRef readStream, CFStreamEventType eventType, void* context) @@ -147,8 +165,44 @@ return 1; } -static char show__doc__[] = "Show all the figures and enter the main loop.\nThis function does not return until all Matplotlib windows are closed,\nand is normally not needed in interactive sessions."; +static int _init_atsui(void) +{ + OSStatus status; + status = ATSUCreateStyle(&style); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUCreateStyle failed"); + return 0; + } + + status = ATSUCreateTextLayout(&layout); + if (status!=noErr) + { + status = ATSUDisposeStyle(style); + if (status!=noErr) + PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1); + PyErr_SetString(PyExc_RuntimeError, "ATSUCreateTextLayout failed"); + return 0; + } + + + return 1; +} + +static void _dealloc_atsui(void) +{ + OSStatus status; + + status = ATSUDisposeStyle(style); + if (status!=noErr) + PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1); + + status = ATSUDisposeTextLayout(layout); + if (status!=noErr) + PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeTextLayout failed", 1); +} + /* ---------------------------- Cocoa classes ---------------------------- */ @@ -220,76 +274,38 @@ typedef struct { PyObject_HEAD CGContextRef cr; - PyObject* converter; /* Convert color specifications to r,g,b triples */ CGPatternRef pattern; /* For drawing hatches */ - ATSUStyle style; /* For drawing Unicode strings with ATSUI */ - ATSUTextLayout layout; /* For drawing Unicode strings with ATSUI */ } GraphicsContext; static PyObject* GraphicsContext_new(PyTypeObject* type, PyObject *args, PyObject *kwds) { - OSStatus status; - GraphicsContext* self = (GraphicsContext*)type->tp_alloc(type, 0); if (!self) return NULL; self->cr = NULL; - PyObject* module = PyImport_AddModule("matplotlib.colors"); - if (!module) return NULL; - PyObject* dict = PyObject_GetAttrString(module, "__dict__"); - if (!dict) return NULL; - PyObject* colorConverter = PyDict_GetItemString(dict, "colorConverter"); - Py_DECREF(dict); - if (!colorConverter) - { - PyErr_SetString(PyExc_KeyError, - "failed to find colorConverter in matplotlib.colors"); - return NULL; - } - self->converter = PyObject_GetAttrString(colorConverter, "to_rgb"); - if (!self->converter) return NULL; - self->pattern = NULL; - status = ATSUCreateStyle(&self->style); - if (status!=noErr) + if (ngc==0) { - Py_DECREF(self->converter); - PyErr_SetString(PyExc_RuntimeError, "ATSUCreateStyle failed"); - return NULL; + int ok = _init_atsui(); + if (!ok) + { + return NULL; + } } + ngc++; - status = ATSUCreateTextLayout(&self->layout); - if (status!=noErr) - { - Py_DECREF(self->converter); - status = ATSUDisposeStyle(self->style); - if (status!=noErr) - PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1); - PyErr_SetString(PyExc_RuntimeError, "ATSUCreateTextLayout failed"); - return NULL; - } - return (PyObject*) self; } static void GraphicsContext_dealloc(GraphicsContext *self) { - Py_DECREF(self->converter); + CGPatternRelease(self->pattern); - if (self->pattern) CGPatternRelease(self->pattern); + ngc--; + if (ngc==0) _dealloc_atsui(); - OSStatus status; - - status = ATSUDisposeStyle(self->style); - if (status!=noErr) - PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1); - - status = ATSUDisposeTextLayout(self->layout); - if (status!=noErr) - PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeTextLayout failed", 1); - self->ob_type->tp_free((PyObject*)self); } @@ -308,6 +324,13 @@ PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); return NULL; } + + if (self->pattern) + { + CGPatternRelease(self->pattern); + self->pattern = NULL; + } + CGContextRestoreGState(cr); CGContextSaveGState(cr); Py_INCREF(Py_None); @@ -400,7 +423,7 @@ } static PyObject* -GraphicsContext_clip_path (GraphicsContext* self) +GraphicsContext_set_clip_path (GraphicsContext* self, PyObject* args) { CGContextRef cr = self->cr; if (!cr) @@ -408,29 +431,216 @@ PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); return NULL; } - CGContextRestoreGState(cr); - CGContextSaveGState(cr); - CGContextClip(cr); - Py_INCREF(Py_None); - return Py_None; -} +#ifdef BUH + CGContextRestoreGState(cr); /* FIXME */ + CGContextSaveGState(cr); /* FIXME */ +#endif -static PyObject* -GraphicsContext_set_dashes (GraphicsContext* self, PyObject* args) -{ - float phase = 0.0; - PyObject* offset; - PyObject* dashes; + PyObject* path; - if (!PyArg_ParseTuple(args, "OO", &offset, &dashes)) return NULL; + if(!PyArg_ParseTuple(args, "O", &path)) return NULL; - CGContextRef cr = self->cr; - if (!cr) + PyObject* vertices = PyObject_GetAttrString(path, "vertices"); + if (vertices==NULL) { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + PyErr_SetString(PyExc_AttributeError, "path has no vertices"); return NULL; } + Py_DECREF(vertices); /* Don't keep a reference here */ + PyObject* codes = PyObject_GetAttrString(path, "codes"); + if (codes==NULL) + { + PyErr_SetString(PyExc_AttributeError, "path has no codes"); + return NULL; + } + Py_DECREF(codes); /* Don't keep a reference here */ + + PyArrayObject* coordinates; + coordinates = (PyArrayObject*)PyArray_FromObject(vertices, + NPY_DOUBLE, 2, 2); + if (!coordinates) + { + PyErr_SetString(PyExc_ValueError, "failed to convert vertices array"); + return NULL; + } + + if (PyArray_NDIM(coordinates) != 2 || PyArray_DIM(coordinates, 1) != 2) + { + Py_DECREF(coordinates); + PyErr_SetString(PyExc_ValueError, "invalid vertices array"); + return NULL; + } + + npy_intp n = PyArray_DIM(coordinates, 0); + + if (n==0) /* Nothing to do here */ + { + Py_DECREF(coordinates); + return NULL; + } + + PyArrayObject* codelist = NULL; + if (codes != Py_None) + { + codelist = (PyArrayObject*)PyArray_FromObject(codes, + NPY_UINT8, 1, 1); + if (!codelist) + { + Py_DECREF(coordinates); + PyErr_SetString(PyExc_ValueError, "invalid codes array"); + return NULL; + } + } + + CGFloat x, y; + + if (codelist==NULL) + { + npy_intp i; + npy_uint8 code = MOVETO; + for (i = 0; i < n; i++) + { + x = (CGFloat)(*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y = (CGFloat)(*(double*)PyArray_GETPTR2(coordinates, i, 1)); + if (isnan(x) || isnan(y)) + { + code = MOVETO; + } + else + { + switch (code) + { + case MOVETO: + CGContextMoveToPoint(cr, x, y); + break; + case LINETO: + CGContextAddLineToPoint(cr, x, y); + break; + } + code = LINETO; + } + } + } + else + { + npy_intp i = 0; + BOOL was_nan = false; + npy_uint8 code; + CGFloat x1, y1, x2, y2, x3, y3; + while (i < n) + { + code = *(npy_uint8*)PyArray_GETPTR1(codelist, i); + if (code == CLOSEPOLY) + { + CGContextClosePath(cr); + i++; + } + else if (code == STOP) + { + break; + } + else if (was_nan) + { + if (code==CURVE3) i++; + else if (code==CURVE4) i+=2; + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1)) + { + was_nan = true; + } + else + { + CGContextMoveToPoint(cr, x1, y1); + was_nan = false; + } + } + else if (code==MOVETO) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1)) + { + was_nan = true; + } + else + { + CGContextMoveToPoint(cr, x1, y1); + was_nan = false; + } + } + else if (code==LINETO) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1)) + { + was_nan = true; + } + else + { + CGContextAddLineToPoint(cr, x1, y1); + was_nan = false; + } + } + else if (code==CURVE3) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + x2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1) || isnan(x2) || isnan(y2)) + { + was_nan = true; + } + else + { + CGContextAddQuadCurveToPoint(cr, x1, y1, x2, y2); + was_nan = false; + } + } + else if (code==CURVE4) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + x2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + x3 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y3 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1) || isnan(x2) || isnan(y2) || isnan(x3) || isnan(y3)) + { + was_nan = true; + } + else + { + CGContextAddCurveToPoint(cr, x1, y1, x2, y2, x3, y3); + was_nan = false; + } + } + } + Py_DECREF(codelist); + } + + Py_DECREF(coordinates); + + CGContextClip(cr); + Py_INCREF(Py_None); + return Py_None; +} + +static BOOL +_set_dashes(CGContextRef cr, PyObject* offset, PyObject* dashes) +{ + float phase = 0.0; if (offset!=Py_None) { if (PyFloat_Check(offset)) phase = PyFloat_AsDouble(offset); @@ -439,7 +649,7 @@ { PyErr_SetString(PyExc_TypeError, "offset should be a floating point value"); - return NULL; + return false; } } @@ -451,7 +661,7 @@ { PyErr_SetString(PyExc_TypeError, "dashes should be a tuple or a list"); - return NULL; + return false; } int n = PyTuple_GET_SIZE(dashes); int i; @@ -460,7 +670,7 @@ { PyErr_SetString(PyExc_MemoryError, "Failed to store dashes"); Py_DECREF(dashes); - return NULL; + return false; } for (i = 0; i < n; i++) { @@ -476,7 +686,7 @@ { free(lengths); PyErr_SetString(PyExc_TypeError, "Failed to read dashes"); - return NULL; + return false; } CGContextSetLineDash(cr, phase, lengths, n); free(lengths); @@ -484,29 +694,40 @@ else CGContextSetLineDash(cr, phase, NULL, 0); - Py_INCREF(Py_None); - return Py_None; + return true; } static PyObject* -GraphicsContext_set_foreground(GraphicsContext* self, PyObject* args, PyObject* keywords) -{ float r, g, b; - PyObject* fg; - int isRGB = 0; - static char* kwlist[] = {"fg", "isRGB", NULL}; - if(!PyArg_ParseTupleAndKeywords(args, keywords, "O|i", kwlist, - &fg, &isRGB)) return NULL; - if (isRGB) +GraphicsContext_set_dashes (GraphicsContext* self, PyObject* args) +{ + PyObject* offset; + PyObject* dashes; + + if (!PyArg_ParseTuple(args, "OO", &offset, &dashes)) return NULL; + + CGContextRef cr = self->cr; + if (!cr) { - if(!PyArg_ParseTuple(fg, "fff", &r, &g, &b)) return NULL; + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; } + + BOOL ok = _set_dashes(cr, offset, dashes); + if (ok) + { + Py_INCREF(Py_None); + return Py_None; + } else - { fg = PyObject_CallFunctionObjArgs(self->converter, fg, NULL); - if(!fg) return NULL; - if(!PyArg_ParseTuple(fg, "fff", &r, &g, &b)) return NULL; - Py_DECREF(fg); - } + return NULL; +} +static PyObject* +GraphicsContext_set_foreground(GraphicsContext* self, PyObject* args) +{ + float r, g, b; + if(!PyArg_ParseTuple(args, "(fff)", &r, &g, &b)) return NULL; + CGContextRef cr = self->cr; if (!cr) { @@ -538,7 +759,7 @@ return Py_None; } -static void drawHatch (void *info, CGContextRef cr) +static void _draw_hatch (void *info, CGContextRef cr) { int i; @@ -603,12 +824,20 @@ Py_DECREF(string); } +static void _release_hatch(void* info) +{ + PyObject* hatches = info; + Py_DECREF(hatches); +} + static PyObject* GraphicsContext_set_hatch(GraphicsContext* self, PyObject* args) { PyObject* hatches; const float size = 12.0; - static const CGPatternCallbacks callbacks = {0, &drawHatch, NULL}; + static const CGPatternCallbacks callbacks = {0, + &_draw_hatch, + &_release_hatch}; CGContextRef cr = self->cr; if (!cr) @@ -687,122 +916,828 @@ return Py_None; } -static PyObject* -GraphicsContext_moveto(GraphicsContext* self, PyObject* args) -{ - float x; - float y; - - if(!PyArg_ParseTuple(args, "(ff)", &x, &y)) return NULL; - - CGContextRef cr = self->cr; - if (!cr) +static int +_convert_affine_transform(PyObject* object, CGAffineTransform* transform) +/* Reads a Numpy affine transformation matrix and returns + * a CGAffineTransform. + */ +{ + PyArrayObject* matrix = NULL; + if (object==Py_None) { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; + PyErr_SetString(PyExc_ValueError, + "Found affine transformation matrix equal to None"); + return 0; } - CGContextMoveToPoint(cr, x, y); + matrix = (PyArrayObject*) PyArray_FromObject(object, NPY_DOUBLE, 2, 2); + if (!matrix) + { + PyErr_SetString(PyExc_ValueError, + "Invalid affine transformation matrix"); + return 0; + } + if (PyArray_NDIM(matrix) != 2 || PyArray_DIM(matrix, 0) != 3 || PyArray_DIM(matrix, 1) != 3) + { + Py_DECREF(matrix); + PyErr_SetString(PyExc_ValueError, + "Affine transformation matrix has invalid dimensions"); + return 0; + } - Py_INCREF(Py_None); - return Py_None; + size_t stride0 = (size_t)PyArray_STRIDE(matrix, 0); + size_t stride1 = (size_t)PyArray_STRIDE(matrix, 1); + char* row0 = PyArray_BYTES(matrix); + char* row1 = row0 + stride0; + + double a = *(double*)(row0); + row0 += stride1; + double c = *(double*)(row0); + row0 += stride1; + double e = *(double*)(row0); + double b = *(double*)(row1); + row1 += stride1; + double d = *(double*)(row1); + row1 += stride1; + double f = *(double*)(row1); + *transform = CGAffineTransformMake(a, b, c, d, e, f); + + Py_DECREF(matrix); + return 1; } -static PyObject* -GraphicsContext_lineto(GraphicsContext* self, PyObject* args) +static int +_draw_path(CGContextRef cr, PyObject* path, CGAffineTransform affine) { - float x; - float y; + CGPoint point; - if(!PyArg_ParseTuple(args, "(ff)", &x, &y)) return NULL; + PyObject* vertices = PyObject_GetAttrString(path, "vertices"); + if (vertices==NULL) + { + PyErr_SetString(PyExc_AttributeError, "path has no vertices"); + return -1; + } + Py_DECREF(vertices); /* Don't keep a reference here */ - CGContextRef cr = self->cr; - if (!cr) + PyObject* codes = PyObject_GetAttrString(path, "codes"); + if (codes==NULL) { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; + PyErr_SetString(PyExc_AttributeError, "path has no codes"); + return -1; } - CGContextAddLineToPoint(cr, x, y); + Py_DECREF(codes); /* Don't keep a reference here */ - Py_INCREF(Py_None); - return Py_None; + PyArrayObject* coordinates; + coordinates = (PyArrayObject*)PyArray_FromObject(vertices, + NPY_DOUBLE, 2, 2); + if (!coordinates) + { + PyErr_SetString(PyExc_ValueError, "failed to convert vertices array"); + return -1; + } + + if (PyArray_NDIM(coordinates) != 2 || PyArray_DIM(coordinates, 1) != 2) + { + Py_DECREF(coordinates); + PyErr_SetString(PyExc_ValueError, "invalid vertices array"); + return -1; + } + + npy_intp n = PyArray_DIM(coordinates, 0); + + if (n==0) /* Nothing to do here */ + { + Py_DECREF(coordinates); + return 0; + } + + PyArrayObject* codelist = NULL; + if (codes != Py_None) + { + codelist = (PyArrayObject*)PyArray_FromObject(codes, + NPY_UINT8, 1, 1); + if (!codelist) + { + Py_DECREF(coordinates); + PyErr_SetString(PyExc_ValueError, "invalid codes array"); + return -1; + } + } + + if (codelist==NULL) + { + npy_intp i; + npy_uint8 code = MOVETO; + for (i = 0; i < n; i++) + { + point.x = (CGFloat)(*(double*)PyArray_GETPTR2(coordinates, i, 0)); + point.y = (CGFloat)(*(double*)PyArray_GETPTR2(coordinates, i, 1)); + if (isnan(point.x) || isnan(point.y)) + { + code = MOVETO; + } + else + { + point = CGPointApplyAffineTransform(point, affine); + switch (code) + { + case MOVETO: + CGContextMoveToPoint(cr, point.x, point.y); + break; + case LINETO: + CGContextAddLineToPoint(cr, point.x, point.y); + break; + } + code = LINETO; + } + } + } + else + { + npy_intp i = 0; + BOOL was_nan = false; + npy_uint8 code; + CGFloat x1, y1, x2, y2, x3, y3; + while (i < n) + { + code = *(npy_uint8*)PyArray_GETPTR1(codelist, i); + if (code == CLOSEPOLY) + { + CGContextClosePath(cr); + i++; + } + else if (code == STOP) + { + break; + } + else if (was_nan) + { + if (code==CURVE3) i++; + else if (code==CURVE4) i+=2; + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1)) + { + was_nan = true; + } + else + { + point.x = x1; + point.y = y1; + point = CGPointApplyAffineTransform(point, affine); + CGContextMoveToPoint(cr, point.x, point.y); + was_nan = false; + } + } + else if (code==MOVETO) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1)) + { + was_nan = true; + } + else + { + point.x = x1; + point.y = y1; + point = CGPointApplyAffineTransform(point, affine); + CGContextMoveToPoint(cr, point.x, point.y); + was_nan = false; + } + } + else if (code==LINETO) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1)) + { + was_nan = true; + } + else + { + point.x = x1; + point.y = y1; + point = CGPointApplyAffineTransform(point, affine); + CGContextAddLineToPoint(cr, point.x, point.y); + was_nan = false; + } + } + else if (code==CURVE3) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + x2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1) || isnan(x2) || isnan(y2)) + { + was_nan = true; + } + else + { + point.x = x1; + point.y = y1; + point = CGPointApplyAffineTransform(point, affine); + x1 = point.x; + y1 = point.y; + point.x = x2; + point.y = y2; + point = CGPointApplyAffineTransform(point, affine); + x2 = point.x; + y2 = point.y; + CGContextAddQuadCurveToPoint(cr, x1, y1, x2, y2); + was_nan = false; + } + } + else if (code==CURVE4) + { + x1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y1 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + x2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y2 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + x3 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + y3 = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + i++; + if (isnan(x1) || isnan(y1) || isnan(x2) || isnan(y2) || isnan(x3) || isnan(y3)) + { + was_nan = true; + } + else + { + point.x = x1; + point.y = y1; + point = CGPointApplyAffineTransform(point, affine); + x1 = point.x; + y1 = point.y; + point.x = x2; + point.y = y2; + point = CGPointApplyAffineTransform(point, affine); + x2 = point.x; + y2 = point.y; + point.x = x3; + point.y = y3; + point = CGPointApplyAffineTransform(point, affine); + x3 = point.x; + y3 = point.y; + CGContextAddCurveToPoint(cr, x1, y1, x2, y2, x3, y3); + was_nan = false; + } + } + } + } + + Py_DECREF(coordinates); + Py_XDECREF(codelist); + return n; } static PyObject* -GraphicsContext_curve3(GraphicsContext* self, PyObject* args) -{ - float cpx; - float cpy; - float x; - float y; +GraphicsContext_draw_path (GraphicsContext* self, PyObject* args) +{ + PyObject* path; + PyObject* transform; + PyObject* rgbFace; + int ok; + CGContextRef cr = self->cr; + if (!cr) { PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); return NULL; } - if(!PyArg_ParseTuple(args, "(ffff)", &cpx, - &cpy, - &x, - &y)) return NULL; + if(!PyArg_ParseTuple(args, "OO|O", + &path, + &transform, + &rgbFace)) return NULL; - CGContextAddQuadCurveToPoint(cr, cpx, cpy, x, y); + if(rgbFace==Py_None) rgbFace = NULL; + CGAffineTransform affine; + ok = _convert_affine_transform(transform, &affine); + if (!ok) return NULL; + + int n = _draw_path(cr, path, affine); + if (n==-1) return NULL; + + if (n > 0) + { + if(rgbFace) + { + float r, g, b; + ok = PyArg_ParseTuple(rgbFace, "fff", &r, &g, &b); + if (!ok) + { + return NULL; + } + CGContextSaveGState(cr); + if(self->pattern) + { + float components[4]; + components[0] = r; + components[1] = g; + components[2] = b; + components[3] = 1.0; + CGContextSetFillPattern(cr, self->pattern, components); + CGPatternRelease(self->pattern); + self->pattern = nil; + } + else CGContextSetRGBFillColor(cr, r, g, b, 1.0); + CGContextDrawPath(cr, kCGPathFillStroke); + CGContextRestoreGState(cr); + } + else CGContextStrokePath(cr); + } + Py_INCREF(Py_None); return Py_None; } static PyObject* -GraphicsContext_curve4 (GraphicsContext* self, PyObject* args) +GraphicsContext_draw_markers (GraphicsContext* self, PyObject* args) { - float cp1x; - float cp1y; - float cp2x; - float cp2y; - float x; - float y; + PyObject* marker_path; + PyObject* marker_transform; + PyObject* path; + PyObject* transform; + PyObject* rgbFace; + int ok; + float r, g, b; + CGContextRef cr = self->cr; + if (!cr) { PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); return NULL; } - if(!PyArg_ParseTuple(args, "(ffffff)", &cp1x, - &cp1y, - &cp2x, - &cp2y, - &x, - &y)) return NULL; + if(!PyArg_ParseTuple(args, "OOOO|O", + &marker_path, + &marker_transform, + &path, + &transform, + &rgbFace)) return NULL; - CGContextAddCurveToPoint(cr, cp1x, cp1y, cp2x, cp2y, x, y); + if(rgbFace==Py_None) rgbFace = NULL; + if (rgbFace) + { + ok = PyArg_ParseTuple(rgbFace, "fff", &r, &g, &b); + if (!ok) + { + return NULL; + } + if(self->pattern) + { + float components[4]; + components[0] = r; + components[1] = g; + components[2] = b; + components[3] = 1.0; + CGContextSetFillPattern(cr, self->pattern, components); + CGPatternRelease(self->pattern); + self->pattern = nil; + } + else CGContextSetRGBFillColor(cr, r, g, b, 1.0); + } + + CGAffineTransform affine; + ok = _convert_affine_transform(transform, &affine); + if (!ok) return NULL; + + CGAffineTransform marker_affine; + ok = _convert_affine_transform(marker_transform, &marker_affine); + if (!ok) return NULL; + + PyObject* vertices = PyObject_GetAttrString(path, "vertices"); + if (vertices==NULL) + { + PyErr_SetString(PyExc_AttributeError, "path has no vertices"); + return NULL; + } + Py_DECREF(vertices); /* Don't keep a reference here */ + + PyArrayObject* coordinates; + coordinates = (PyArrayObject*)PyArray_FromObject(vertices, + NPY_DOUBLE, 2, 2); + if (!coordinates) + { + PyErr_SetString(PyExc_ValueError, "failed to convert vertices array"); + return NULL; + } + + if (PyArray_NDIM(coordinates) != 2 || PyArray_DIM(coordinates, 1) != 2) + { + Py_DECREF(coordinates); + PyErr_SetString(PyExc_ValueError, "invalid vertices array"); + return NULL; + } + + npy_intp i; + npy_intp n = PyArray_DIM(coordinates, 0); + CGPoint point; + CGAffineTransform t; + int m = 0; + for (i = 0; i < n; i++) + { + point.x = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 0)); + point.y = (CGFloat) (*(double*)PyArray_GETPTR2(coordinates, i, 1)); + point = CGPointApplyAffineTransform(point, affine); + t = marker_affine; + t.tx += point.x; + t.ty += point.y; + m = _draw_path(cr, marker_path, t); + + if (m > 0) + { + if(rgbFace) CGContextDrawPath(cr, kCGPathFillStroke); + else CGContextStrokePath(cr); + } + } + + Py_DECREF(coordinates); + Py_INCREF(Py_None); return Py_None; } +static BOOL _clip(CGContextRef cr, PyObject* object) +{ + if (object == Py_None) return true; + + PyArrayObject* array = NULL; + array = (PyArrayObject*) PyArray_FromObject(object, PyArray_DOUBLE, 2, 2); + if (!array) + { + PyErr_SetString(PyExc_ValueError, "failed to read clipping bounding box"); + return false; + } + + if (PyArray_NDIM(array)!=2 || PyArray_DIM(array, 0)!=2 || PyArray_DIM(array, 1)!=2) + { + Py_DECREF(array); + PyErr_SetString(PyExc_ValueError, "clipping bounding box should be a 2x2 array"); + return false; + } + + const double l = *(double*)PyArray_GETPTR2(array, 0, 0); + const double b = *(double*)PyArray_GETPTR2(array, 0, 1); + const double r = *(double*)PyArray_GETPTR2(array, 1, 0); + const double t = *(double*)PyArray_GETPTR2(array, 1, 1); + + Py_DECREF(array); + + CGRect rect; + rect.origin.x = (CGFloat) l; + rect.origin.y = (CGFloat) b; + rect.size.width = (CGFloat) (r-l); + rect.size.height = (CGFloat) (t-b); + + CGContextClipToRect(cr, rect); + + return true; +} + + static PyObject* -GraphicsContext_closepoly (GraphicsContext* self) +GraphicsContext_draw_path_collection (GraphicsContext* self, PyObject* args) { + PyObject* master_transform_obj; + PyObject* cliprect; + PyObject* clippath; + PyObject* clippath_transform; + PyObject* paths; + PyObject* transforms_obj; + PyObject* offsets_obj; + PyObject* offset_transform_obj; + PyObject* facecolors_obj; + PyObject* edgecolors_obj; + PyObject* linewidths; + PyObject* linestyles; + PyObject* antialiaseds; + CGContextRef cr = self->cr; + if (!cr) { PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); return NULL; } - CGContextClosePath(cr); + if(!PyArg_ParseTuple(args, "OOOOOOOOOOOOO", &master_transform_obj, + &cliprect, + &clippath, + &clippath_transform, + &paths, + &transforms_obj, + &offsets_obj, + &offset_transform_obj, + &facecolors_obj, + &edgecolors_obj, + &linewidths, + &linestyles, + &antialiaseds)) + return NULL; + CGContextSaveGState(cr); + + CGAffineTransform transform; + CGAffineTransform master_transform; + CGAffineTransform offset_transform; + CGAffineTransform* transforms = NULL; + + if (!_convert_affine_transform(master_transform_obj, &master_transform)) return NULL; + if (!_convert_affine_transform(offset_transform_obj, &offset_transform)) return NULL; + + if (!_clip(cr, cliprect)) return NULL; + if (clippath!=Py_None) + { + if (!_convert_affine_transform(clippath_transform, &transform)) return NULL; + int n = _draw_path(cr, clippath, transform); + if (n==-1) return NULL; + else if (n > 0) CGContextClip(cr); + } + + PyArrayObject* offsets = NULL; + PyArrayObject* facecolors = NULL; + PyArrayObject* edgecolors = NULL; + + /* ------------------- Check offsets array ---------------------------- */ + + offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj, NPY_DOUBLE, 0, 2); + if (!offsets || + (PyArray_NDIM(offsets)==2 && PyArray_DIM(offsets, 1)!=2) || + (PyArray_NDIM(offsets)==1 && PyArray_DIM(offsets, 0)!=0)) + { + PyErr_SetString(PyExc_ValueError, "Offsets array must be Nx2"); + goto error; + } + + /* ------------------- Check facecolors array ------------------------- */ + + facecolors = (PyArrayObject*)PyArray_FromObject(facecolors_obj, + NPY_DOUBLE, 1, 2); + if (!facecolors || + (PyArray_NDIM(facecolors)==1 && PyArray_DIM(facecolors, 0)!=0) || + (PyArray_NDIM(facecolors)==2 && PyArray_DIM(facecolors, 1)!=4)) + { + PyErr_SetString(PyExc_ValueError, "Facecolors must by a Nx4 numpy array or empty"); + goto error; + } + + /* ------------------- Check edgecolors array ------------------------- */ + + edgecolors = (PyArrayObject*)PyArray_FromObject(edgecolors_obj, + NPY_DOUBLE, 1, 2); + if (!edgecolors || + (PyArray_NDIM(edgecolors)==1 && PyArray_DIM(edgecolors, 0)!=0) || + (PyArray_NDIM(edgecolors)==2 && PyArray_DIM(edgecolors, 1)!=4)) + { + PyErr_SetString(PyExc_ValueError, "Edgecolors must by a Nx4 numpy array or empty"); + goto error; + } + + /* ------------------- Check the other arguments ---------------------- */ + + if (!PySequence_Check(paths)) + { + PyErr_SetString(PyExc_ValueError, "paths must be a sequence object"); + goto error; + } + if (!PySequence_Check(transforms_obj)) + { + PyErr_SetString(PyExc_ValueError, "transforms must be a sequence object"); + goto error; + } + if (!PySequence_Check(linewidths)) + { + PyErr_SetString(PyExc_ValueError, "linewidths must be a sequence object"); + goto error; + } + if (!PySequence_Check(linestyles)) + { + PyErr_SetString(PyExc_ValueError, "linestyles must be a sequence object"); + goto error; + } + if (!PySequence_Check(antialiaseds)) + { + PyErr_SetString(PyExc_ValueError, "antialiaseds must be a sequence object"); + goto error; + } + + size_t Npaths = (size_t) PySequence_Size(paths); + size_t Noffsets = (size_t) PyArray_DIM(offsets, 0); + size_t N = Npaths > Noffsets ? Npaths : Noffsets; + size_t Ntransforms = (size_t) PySequence_Size(transforms_obj); + size_t Nfacecolors = (size_t) PyArray_DIM(facecolors, 0); + size_t Nedgecolors = (size_t) PyArray_DIM(edgecolors, 0); + size_t Nlinewidths = (size_t) PySequence_Size(linewidths); + size_t Nlinestyles = (size_t) PySequence_Size(linestyles); + size_t Naa = (size_t) PySequence_Size(antialiaseds); + if (N < Ntransforms) Ntransforms = N; + if (N < Nlinestyles) Nlinestyles = N; + if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) + { + goto success; + } + + size_t i = 0; + + /* Convert all of the transforms up front */ + if (Ntransforms > 0) + { + transforms = malloc(Ntransforms*sizeof(CGAffineTransform)); + if (!transforms) goto error; + for (i = 0; i < Ntransforms; i++) + { + PyObject* transform_obj = PySequence_ITEM(transforms_obj, i); + if(!_convert_affine_transform(transform_obj, &transforms[i])) goto error; + transforms[i] = CGAffineTransformConcat(transforms[i], master_transform); + } + } + + CGPoint offset; + PyObject* path; + + /* Preset graphics context properties if possible */ + if (Naa==1) + { + switch(PyObject_IsTrue(PySequence_ITEM(antialiaseds, 0))) + { + case 1: CGContextSetShouldAntialias(cr, true); break; + case 0: CGContextSetShouldAntialias(cr, false); break; + case -1: + { + PyErr_SetString(PyExc_ValueError, + "Failed to read antialiaseds array"); + goto error; + } + } + } + + if (Nlinewidths==1) + { + double linewidth = PyFloat_AsDouble(PySequence_ITEM(linewidths, 0)); + CGContextSetLineWidth(cr, (CGFloat)linewidth); + } + else if (Nlinewidths==0) + CGContextSetLineWidth(cr, 0.0); + + if (Nlinestyles==1) + { + PyObject* offset; + PyObject* dashes; + PyObject* linestyle = PySequence_ITEM(linestyles, 0); + if (!PyArg_ParseTuple(linestyle, "OO", &offset, &dashes)) goto error; + if (!_set_dashes(cr, offset, dashes)) goto error; + } + + if (Nedgecolors==1) + { + const double r = *(double*)PyArray_GETPTR2(edgecolors, 0, 0); + const double g = *(double*)PyArray_GETPTR2(edgecolors, 0, 1); + const double b = *(double*)PyArray_GETPTR2(edgecolors, 0, 2); + const double a = *(double*)PyArray_GETPTR2(edgecolors, 0, 3); + CGContextSetRGBStrokeColor(cr, r, g, b, a); + } + + if (Nfacecolors==1) + { + const double r = *(double*)PyArray_GETPTR2(facecolors, 0, 0); + const double g = *(double*)PyArray_GETPTR2(facecolors, 0, 1); + const double b = *(double*)PyArray_GETPTR2(facecolors, 0, 2); + const double a = *(double*)PyArray_GETPTR2(facecolors, 0, 3); + CGContextSetRGBFillColor(cr, r, g, b, a); + } + + for (i = 0; i < N; i++) + { + + if (Ntransforms) + { + transform = transforms[i % Ntransforms]; + } + else + { + transform = master_transform; + } + + if (Noffsets) + { + offset.x = (CGFloat) (*(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0)); + offset.y = (CGFloat) (*(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1)); + offset = CGPointApplyAffineTransform(offset, offset_transform); + transform.tx += offset.x; + transform.ty += offset.y; + } + + if (Naa > 1) + { + switch(PyObject_IsTrue(PySequence_ITEM(antialiaseds, i % Naa))) + { + case 1: CGContextSetShouldAntialias(cr, true); break; + case 0: CGContextSetShouldAntialias(cr, false); break; + case -1: + { + PyErr_SetString(PyExc_ValueError, + "Failed to read antialiaseds array"); + goto error; + } + } + } + + path = PySequence_ITEM(paths, i % Npaths); + int n = _draw_path(cr, path, transform); + if (n==-1) goto error; + else if (n==0) continue; + + if (Nlinewidths > 1) + { + double linewidth = PyFloat_AsDouble(PySequence_ITEM(linewidths, i % Nlinewidths)); + CGContextSetLineWidth(cr, (CGFloat)linewidth); + } + + if (Nlinestyles > 1) + { + PyObject* offset; + PyObject* dashes; + PyObject* linestyle = PySequence_ITEM(linestyles, i % Nlinestyles); + if (!PyArg_ParseTuple(linestyle, "OO", &offset, &dashes)) goto error; + if (!_set_dashes(cr, offset, dashes)) goto error; + } + + if (Nedgecolors > 1) + { + npy_intp fi = i % Nedgecolors; + const double r = *(double*)PyArray_GETPTR2(edgecolors, fi, 0); + const double g = *(double*)PyArray_GETPTR2(edgecolors, fi, 1); + const double b = *(double*)PyArray_GETPTR2(edgecolors, fi, 2); + const double a = *(double*)PyArray_GETPTR2(edgecolors, fi, 3); + CGContextSetRGBStrokeColor(cr, r, g, b, a); + } + + if (Nfacecolors > 1) + { + npy_intp fi = i % Nfacecolors; + const double r = *(double*)PyArray_GETPTR2(facecolors, fi, 0); + const double g = *(double*)PyArray_GETPTR2(facecolors, fi, 1); + const double b = *(double*)PyArray_GETPTR2(facecolors, fi, 2); + const double a = *(double*)PyArray_GETPTR2(facecolors, fi, 3); + CGContextSetRGBFillColor(cr, r, g, b, a); + CGContextDrawPath(cr, kCGPathFillStroke); + } + else if (Nfacecolors==1) + CGContextDrawPath(cr, kCGPathFillStroke); + else + CGContextStrokePath(cr); + } + +success: + CGContextRestoreGState(cr); + if (transforms) free(transforms); + Py_DECREF(offsets); + Py_DECREF(facecolors); + Py_DECREF(edgecolors); + Py_INCREF(Py_None); return Py_None; + +error: + CGContextRestoreGState(cr); + if (transforms) free(transforms); + Py_XDECREF(offsets); + Py_XDECREF(facecolors); + Py_XDECREF(edgecolors); + + return NULL; } static PyObject* -GraphicsContext_stroke (GraphicsContext* self, PyObject* args) +GraphicsContext_draw_quad_mesh (GraphicsContext* self, PyObject* args) { - PyObject* color; + PyObject* master_transform_obj; + PyObject* cliprect; + PyObject* clippath; + PyObject* clippath_transform; + int meshWidth; + int meshHeight; + PyObject* vertices; + PyObject* offsets_obj; + PyObject* offset_transform_obj; + PyObject* facecolors_obj; + int antialiased; + int showedges; + CGContextRef cr = self->cr; if (!cr) @@ -811,35 +1746,219 @@ return NULL; } - if(!PyArg_ParseTuple(args, "O", &color)) return NULL; + if(!PyArg_ParseTuple(args, "OOOOiiOOOOii", + &master_transform_obj, + &cliprect, + &clippath, + &clippath_transform, + &meshWidth, + &meshHeight, + &vertices, + &offsets_obj, + &offset_transform_obj, + &facecolors_obj, + &antialiased, + &showedges)) return NULL; - if(color!=Py_None) + PyArrayObject* offsets = NULL; + PyArrayObject* facecolors = NULL; + + CGAffineTransform transform; + CGAffineTransform master_transform; + CGAffineTransform offset_transform; + + if (!_convert_affine_transform(master_transform_obj, &master_transform)) + return NULL; + if (!_convert_affine_transform(offset_transform_obj, &offset_transform)) + return NULL; + + /* clipping */ + + if (!_clip(cr, cliprect)) return NULL; + if (clippath!=Py_None) { - float r, g, b; - if(!PyArg_ParseTuple(color, "fff", &r, &g, &b)) return NULL; - if(self->pattern) + if (!_convert_affine_transform(clippath_transform, &transform)) + return NULL; + int n = _draw_path(cr, clippath, transform); + if (n==-1) return NULL; + else if (n > 0) CGContextClip(cr); + } + + PyArrayObject* coordinates; + coordinates = (PyArrayObject*)PyArray_FromObject(vertices, + NPY_DOUBLE, 3, 3); + if (!coordinates || + PyArray_NDIM(coordinates) != 3 || PyArray_DIM(coordinates, 2) != 2) + { + PyErr_SetString(PyExc_ValueError, "Invalid coordinates array"); + goto error; + } + + /* ------------------- Check offsets array ---------------------------- */ + + offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj, NPY_DOUBLE, 0, 2); + if (!offsets || + (PyArray_NDIM(offsets)==2 && PyArray_DIM(offsets, 1)!=2) || + (PyArray_NDIM(offsets)==1 && PyArray_DIM(offsets, 0)!=0)) + { + PyErr_SetString(PyExc_ValueError, "Offsets array must be Nx2"); + goto error; + } + + /* ------------------- Check facecolors array ------------------------- */ + + facecolors = (PyArrayObject*)PyArray_FromObject(facecolors_obj, + NPY_DOUBLE, 1, 2); + if (!facecolors || + (PyArray_NDIM(facecolors)==1 && PyArray_DIM(facecolors, 0)!=0) || + (PyArray_NDIM(facecolors)==2 && PyArray_DIM(facecolors, 1)!=4)) + { + PyErr_SetString(PyExc_ValueError, "Facecolors must by a Nx4 numpy array or empty"); + goto error; + } + + /* ------------------- Check the other arguments ---------------------- */ + + size_t Noffsets = (size_t) PyArray_DIM(offsets, 0); + size_t Npaths = meshWidth * meshHeight; + size_t Nfacecolors = (size_t) PyArray_DIM(facecolors, 0); + if ((Nfacecolors == 0 && !showedges) || Npaths == 0) + { + /* Nothing to do here */ + goto success; + } + + size_t i = 0; + size_t iw = 0; + size_t ih = 0; + + CGPoint offset; + + /* Preset graphics context properties if possible */ + if (antialiased) CGContextSetShouldAntialias(cr, true); + else CGContextSetShouldAntialias(cr, false); + + CGContextSetLineWidth(cr, 0.0); + + if (Nfacecolors==1) + { + const double r = *(double*)PyArray_GETPTR2(facecolors, 0, 0); + const double g = *(double*)PyArray_GETPTR2(facecolors, 0, 1); + const double b = *(double*)PyArray_GETPTR2(facecolors, 0, 2); + const double a = *(double*)PyArray_GETPTR2(facecolors, 0, 3); + CGContextSetRGBFillColor(cr, r, g, b, a); + if (antialiased && !showedges) { - float components[4]; - components[0] = r; - components[1] = g; - components[2] = b; - components[3] = 1.0; - CGContextSetFillPattern(cr, self->pattern, components); - CGPatternRelease (self->pattern); - self->pattern = nil; + CGContextSetRGBStrokeColor(cr, r, g, b, a); } - else CGContextSetRGBFillColor(cr, r, g, b, 1.0); - CGContextDrawPath(cr, kCGPathFillStroke); } - else CGContextStrokePath(cr); + if (showedges) + { + CGContextSetRGBStrokeColor(cr, 0, 0, 0, 1); + } + + for (ih = 0; ih < meshHeight; ih++) + { + for (iw = 0; iw < meshWidth; iw++, i++) + { + + transform = master_transform; + + if (Noffsets) + { + offset.x = (CGFloat) (*(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0)); + offset.y = (CGFloat) (*(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1)); + offset = CGPointApplyAffineTransform(offset, offset_transform); + transform.tx += offset.x; + transform.ty += offset.y; + } + + CGPoint p; + CGPoint points[4]; + + p.x = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih, iw, 0)); + p.y = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih, iw, 1)); + if (isnan(p.x) || isnan(p.y)) continue; + points[0] = CGPointApplyAffineTransform(p, transform); + + p.x = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih, iw+1, 0)); + p.y = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih, iw+1, 1)); + if (isnan(p.x) || isnan(p.y)) continue; + points[1] = CGPointApplyAffineTransform(p, transform); + + p.x = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih+1, iw+1, 0)); + p.y = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih+1, iw+1, 1)); + if (isnan(p.x) || isnan(p.y)) continue; + points[2] = CGPointApplyAffineTransform(p, transform); + + p.x = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih+1, iw, 0)); + p.y = (CGFloat)(*(double*)PyArray_GETPTR3(coordinates, ih+1, iw, 1)); + if (isnan(p.x) || isnan(p.y)) continue; + points[3] = CGPointApplyAffineTransform(p, transf... [truncated message content] |