diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index d70c218c525c..c35d20b51a9e 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -998,7 +998,8 @@ def _blit_clear(self, artists, bg_cache): # cache and restore. axes = set(a.axes for a in artists) for a in axes: - a.figure.canvas.restore_region(bg_cache[a]) + if a in bg_cache: + a.figure.canvas.restore_region(bg_cache[a]) def _setup_blit(self): # Setting up the blit requires: a cache of the background for the diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index a7fe0f181986..ea4d78f2a1b3 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -510,7 +510,7 @@ def print_raw(self, filename_or_obj, *args, **kwargs): finally: if close: filename_or_obj.close() - renderer.dpi = original_dpi + renderer.dpi = original_dpi print_rgba = print_raw def print_png(self, filename_or_obj, *args, **kwargs): @@ -529,16 +529,18 @@ def print_png(self, filename_or_obj, *args, **kwargs): finally: if close: filename_or_obj.close() - renderer.dpi = original_dpi + renderer.dpi = original_dpi def print_to_buffer(self): FigureCanvasAgg.draw(self) renderer = self.get_renderer() original_dpi = renderer.dpi renderer.dpi = self.figure.dpi - result = (renderer._renderer.buffer_rgba(), - (int(renderer.width), int(renderer.height))) - renderer.dpi = original_dpi + try: + result = (renderer._renderer.buffer_rgba(), + (int(renderer.width), int(renderer.height))) + finally: + renderer.dpi = original_dpi return result if _has_pil: diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 3aa17977ee51..00f938cc3190 100755 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -4,18 +4,13 @@ from matplotlib.externals import six import os -import numpy from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ - FigureManagerBase, FigureCanvasBase, NavigationToolbar2, TimerBase +from matplotlib.backend_bases import FigureManagerBase, FigureCanvasBase, \ + NavigationToolbar2, TimerBase from matplotlib.backend_bases import ShowBase -from matplotlib.cbook import maxdict from matplotlib.figure import Figure -from matplotlib.path import Path -from matplotlib.mathtext import MathTextParser -from matplotlib.colors import colorConverter from matplotlib import rcParams from matplotlib.widgets import SubplotTool @@ -23,6 +18,8 @@ import matplotlib from matplotlib.backends import _macosx +from .backend_agg import RendererAgg, FigureCanvasAgg + class Show(ShowBase): def mainloop(self): @@ -30,205 +27,6 @@ def mainloop(self): show = Show() -class RendererMac(RendererBase): - """ - The renderer handles drawing/rendering operations. Most of the renderer's - methods forward the command to the renderer's graphics context. The - renderer does not wrap a C object and is written in pure Python. - """ - - texd = maxdict(50) # a cache of tex image rasters - - def __init__(self, dpi, width, height): - RendererBase.__init__(self) - self.dpi = dpi - self.width = width - self.height = height - self.gc = GraphicsContextMac() - self.gc.set_dpi(self.dpi) - self.mathtext_parser = MathTextParser('MacOSX') - - def set_width_height (self, width, height): - self.width, self.height = width, height - - def draw_path(self, gc, path, transform, rgbFace=None): - if rgbFace is not None: - rgbFace = tuple(rgbFace) - linewidth = gc.get_linewidth() - gc.draw_path(path, transform, linewidth, rgbFace) - - def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): - if rgbFace is not None: - rgbFace = tuple(rgbFace) - linewidth = gc.get_linewidth() - gc.draw_markers(marker_path, marker_trans, path, trans, linewidth, rgbFace) - - def draw_path_collection(self, gc, master_transform, paths, all_transforms, - offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls, - offset_position): - if offset_position=='data': - offset_position = True - else: - offset_position = False - path_ids = [] - for path, transform in self._iter_collection_raw_paths( - master_transform, paths, all_transforms): - path_ids.append((path, transform)) - master_transform = master_transform.get_matrix() - offsetTrans = offsetTrans.get_matrix() - gc.draw_path_collection(master_transform, path_ids, all_transforms, - offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, - offset_position) - - def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight, - coordinates, offsets, offsetTrans, facecolors, - antialiased, edgecolors): - gc.draw_quad_mesh(master_transform.get_matrix(), - meshWidth, - meshHeight, - coordinates, - offsets, - offsetTrans.get_matrix(), - facecolors, - antialiased, - edgecolors) - - def new_gc(self): - self.gc.save() - self.gc.set_hatch(None) - self.gc._alpha = 1.0 - self.gc._forced_alpha = False # if True, _alpha overrides A from RGBA - return self.gc - - def draw_gouraud_triangle(self, gc, points, colors, transform): - points = transform.transform(points) - gc.draw_gouraud_triangle(points, colors) - - def get_image_magnification(self): - return self.gc.get_image_magnification() - - def draw_image(self, gc, x, y, im): - nrows, ncols, data = im.as_rgba_str() - gc.draw_image(x, y, nrows, ncols, data) - - def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None): - # todo, handle props, angle, origins - scale = self.gc.get_image_magnification() - size = prop.get_size_in_points() - texmanager = self.get_texmanager() - key = s, size, self.dpi, angle, texmanager.get_font_config() - im = self.texd.get(key) # Not sure what this does; just copied from backend_agg.py - if im is None: - Z = texmanager.get_grey(s, size, self.dpi*scale) - Z = numpy.array(255.0 - Z * 255.0, numpy.uint8) - - gc.draw_mathtext(x, y, angle, Z) - - def _draw_mathtext(self, gc, x, y, s, prop, angle): - scale = self.gc.get_image_magnification() - ox, oy, width, height, descent, image, used_characters = \ - self.mathtext_parser.parse(s, self.dpi*scale, prop) - descent /= scale - xd = descent * numpy.sin(numpy.deg2rad(angle)) - yd = descent * numpy.cos(numpy.deg2rad(angle)) - x = numpy.round(x + ox + xd) - y = numpy.round(y + oy - yd) - gc.draw_mathtext(x, y, angle, 255 - image.as_array()) - - def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): - if ismath: - self._draw_mathtext(gc, x, y, s, prop, angle) - else: - family = prop.get_family() - weight = prop.get_weight() - # transform weight into string for the native backend - if weight >= 700: - weight = 'bold' - else: - weight = 'normal' - style = prop.get_style() - points = prop.get_size_in_points() - size = self.points_to_pixels(points) - gc.draw_text(x, y, six.text_type(s), family, size, weight, style, angle) - - def get_text_width_height_descent(self, s, prop, ismath): - if ismath=='TeX': - # todo: handle props - texmanager = self.get_texmanager() - fontsize = prop.get_size_in_points() - w, h, d = texmanager.get_text_width_height_descent(s, fontsize, - renderer=self) - return w, h, d - if ismath: - ox, oy, width, height, descent, fonts, used_characters = \ - self.mathtext_parser.parse(s, self.dpi, prop) - return width, height, descent - family = prop.get_family() - weight = prop.get_weight() - # transform weight into string for the native backend - if weight >= 700: - weight = 'bold' - else: - weight = 'normal' - style = prop.get_style() - points = prop.get_size_in_points() - size = self.points_to_pixels(points) - width, height, descent = self.gc.get_text_width_height_descent( - six.text_type(s), family, size, weight, style) - return width, height, descent - - def flipy(self): - return False - - def points_to_pixels(self, points): - return points/72.0 * self.dpi - - def option_image_nocomposite(self): - return True - - -class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase): - """ - The GraphicsContext wraps a Quartz graphics context. All methods - are implemented at the C-level in macosx.GraphicsContext. These - methods set drawing properties such as the line style, fill color, - etc. The actual drawing is done by the Renderer, which draws into - the GraphicsContext. - """ - def __init__(self): - GraphicsContextBase.__init__(self) - _macosx.GraphicsContext.__init__(self) - - def set_alpha(self, alpha): - GraphicsContextBase.set_alpha(self, alpha) - _alpha = self.get_alpha() - _macosx.GraphicsContext.set_alpha(self, _alpha, self.get_forced_alpha()) - rgb = self.get_rgb() - _macosx.GraphicsContext.set_foreground(self, rgb) - - def set_foreground(self, fg, isRGBA=False): - GraphicsContextBase.set_foreground(self, fg, isRGBA) - rgb = self.get_rgb() - _macosx.GraphicsContext.set_foreground(self, rgb) - - def set_graylevel(self, fg): - GraphicsContextBase.set_graylevel(self, fg) - _macosx.GraphicsContext.set_graylevel(self, fg) - - def set_clip_rectangle(self, box): - GraphicsContextBase.set_clip_rectangle(self, box) - if not box: return - _macosx.GraphicsContext.set_clip_rectangle(self, box.bounds) - - def set_clip_path(self, path): - GraphicsContextBase.set_clip_path(self, path) - if not path: return - path = path.get_fully_transformed_path() - _macosx.GraphicsContext.set_clip_path(self, path) - - ######################################################################## # # The following functions and classes are for pylab and implement @@ -285,7 +83,7 @@ class TimerMac(_macosx.Timer, TimerBase): # completely implemented at the C-level (in _macosx.Timer) -class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase): +class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasAgg): """ The canvas the figure renders into. Calls the draw and print fig methods, creates the renderers, etc... @@ -300,61 +98,65 @@ class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase): key_press_event, and key_release_event are called from there. """ - filetypes = FigureCanvasBase.filetypes.copy() - filetypes['bmp'] = 'Windows bitmap' - filetypes['jpeg'] = 'JPEG' - filetypes['jpg'] = 'JPEG' - filetypes['gif'] = 'Graphics Interchange Format' - filetypes['tif'] = 'Tagged Image Format File' - filetypes['tiff'] = 'Tagged Image Format File' - def __init__(self, figure): FigureCanvasBase.__init__(self, figure) width, height = self.get_width_height() - self.renderer = RendererMac(figure.dpi, width, height) _macosx.FigureCanvas.__init__(self, width, height) + self._needs_draw = True + self._device_scale = 1.0 + + def _set_device_scale(self, value): + if self._device_scale != value: + self.figure.dpi = self.figure.dpi / self._device_scale * value + self._device_scale = value + + def get_renderer(self, cleared=False): + l, b, w, h = self.figure.bbox.bounds + key = w, h, self.figure.dpi + try: + self._lastKey, self._renderer + except AttributeError: + need_new_renderer = True + else: + need_new_renderer = (self._lastKey != key) - def draw_idle(self, *args, **kwargs): - self.invalidate() + if need_new_renderer: + self._renderer = RendererAgg(w, h, self.figure.dpi) + self._lastKey = key + elif cleared: + self._renderer.clear() - def resize(self, width, height): - self.renderer.set_width_height(width, height) - dpi = self.figure.dpi - width /= dpi - height /= dpi - self.figure.set_size_inches(width, height) - FigureCanvasBase.resize_event(self) + return self._renderer - def _print_bitmap(self, filename, *args, **kwargs): - # In backend_bases.py, print_figure changes the dpi of the figure. - # But since we are essentially redrawing the picture, we need the - # original dpi. Pick it up from the renderer. - dpi = kwargs['dpi'] - old_dpi = self.figure.dpi - self.figure.dpi = self.renderer.dpi - width, height = self.figure.get_size_inches() - width, height = width*dpi, height*dpi - filename = six.text_type(filename) - self.write_bitmap(filename, width, height, dpi) - self.figure.dpi = old_dpi + def _draw(self): + renderer = self.get_renderer() - def print_bmp(self, filename, *args, **kwargs): - self._print_bitmap(filename, *args, **kwargs) + if not self._needs_draw: + return renderer - def print_jpg(self, filename, *args, **kwargs): - self._print_bitmap(filename, *args, **kwargs) + self.figure.draw(renderer) + self._needs_draw = False + return renderer - def print_jpeg(self, filename, *args, **kwargs): - self._print_bitmap(filename, *args, **kwargs) + def draw(self): + self._draw() + self.invalidate() - def print_tif(self, filename, *args, **kwargs): - self._print_bitmap(filename, *args, **kwargs) + def draw_idle(self, *args, **kwargs): + self._needs_draw = True + self.invalidate() - def print_tiff(self, filename, *args, **kwargs): - self._print_bitmap(filename, *args, **kwargs) + def blit(self, bbox): + self.invalidate() - def print_gif(self, filename, *args, **kwargs): - self._print_bitmap(filename, *args, **kwargs) + def resize(self, width, height): + dpi = self.figure.dpi + width /= dpi + height /= dpi + self.figure.set_size_inches(width * self._device_scale, + height * self._device_scale) + FigureCanvasBase.resize_event(self) + self.draw_idle() def new_timer(self, *args, **kwargs): """ diff --git a/setupext.py b/setupext.py index c9e4366b7327..4e5e2e93c335 100755 --- a/setupext.py +++ b/setupext.py @@ -2140,14 +2140,10 @@ def check_requirements(self): def get_extension(self): sources = [ - 'src/_macosx.m', - 'src/py_converters.cpp', - 'src/path_cleanup.cpp' + 'src/_macosx.m' ] ext = make_extension('matplotlib.backends._macosx', sources) - Numpy().add_flags(ext) - LibAgg().add_flags(ext) ext.extra_link_args.extend(['-framework', 'Cocoa']) return ext diff --git a/src/_macosx.m b/src/_macosx.m index 10f6eb1e2550..66c8c7d389ca 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -2,9 +2,6 @@ #include #include #include -#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION -#include "numpy/arrayobject.h" -#include "path_cleanup.h" #define PYOSINPUTHOOK_REPETITIVE 1 /* Remove this once Python is fixed */ @@ -25,12 +22,6 @@ # define Py_TYPE(o) ((o)->ob_type) #endif -/* Must define PyVarObject_HEAD_INIT for Python 2.5 or older */ -#ifndef PyVarObject_HEAD_INIT -#define PyVarObject_HEAD_INIT(type, size) \ - PyObject_HEAD_INIT(type) size, -#endif - /* Proper way to check for the OS X version we are compiling for, from http://developer.apple.com/documentation/DeveloperTools/Conceptual/cross_development */ #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 @@ -52,9 +43,6 @@ #include -/* For drawing Unicode strings with ATSUI */ -static ATSUStyle style = NULL; -static ATSUTextLayout layout = NULL; #endif /* CGFloat was defined in Mac OS X 10.5 */ @@ -67,17 +55,6 @@ #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 0x4f - -/* Hatching */ -#define HATCH_SIZE 72 - /* -------------------------- Helper function ---------------------------- */ static void @@ -219,162 +196,6 @@ static int wait_for_stdin(void) return 1; } -#ifndef COMPILING_FOR_10_5 -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); -} -#endif - -static int _draw_path(CGContextRef cr, void* iterator, int nmax) -{ - double x1, y1, x2, y2; - static unsigned code = STOP; - static double xs, ys; - CGPoint current; - int n = 0; - - if (code == MOVETO) CGContextMoveToPoint(cr, xs, ys); - - while (true) - { - code = get_vertex(iterator, &x1, &y1); - if (code == CLOSEPOLY) - { - if (n > 0) CGContextClosePath(cr); - n++; - } - else if (code == STOP) - { - break; - } - else if (code == MOVETO) - { - CGContextMoveToPoint(cr, x1, y1); - } - else if (code==LINETO) - { - CGContextAddLineToPoint(cr, x1, y1); - n++; - } - else if (code==CURVE3) - { - get_vertex(iterator, &xs, &ys); - CGContextAddQuadCurveToPoint(cr, x1, y1, xs, ys); - n+=2; - } - else if (code==CURVE4) - { - get_vertex(iterator, &x2, &y2); - get_vertex(iterator, &xs, &ys); - CGContextAddCurveToPoint(cr, x1, y1, x2, y2, xs, ys); - n+=3; - } - if (n >= nmax) - { - switch (code) - { - case MOVETO: - case LINETO: - xs = x1; - ys = y1; - break; - case CLOSEPOLY: - current = CGContextGetPathCurrentPoint(cr); - xs = current.x; - ys = current.y; - break; - /* nothing needed for CURVE3, CURVE4 */ - } - code = MOVETO; - return -n; - } - } - return n; -} - -static void _draw_hatch(void *info, CGContextRef cr) -{ - int n; - PyObject* hatchpath = (PyObject*)info; - PyObject* transform; - int nd = 2; - npy_intp dims[2] = {3, 3}; - int typenum = NPY_DOUBLE; - double data[9] = {HATCH_SIZE, 0, 0, 0, HATCH_SIZE, 0, 0, 0, 1}; - double rect[4] = { 0.0, 0.0, HATCH_SIZE, HATCH_SIZE}; - transform = PyArray_SimpleNewFromData(nd, dims, typenum, data); - if (!transform) - { - PyGILState_STATE gstate = PyGILState_Ensure(); - PyErr_Print(); - PyGILState_Release(gstate); - return; - } - void* iterator = get_path_iterator(hatchpath, - transform, - 0, - 0, - rect, - SNAP_FALSE, - 1.0, - 0); - Py_DECREF(transform); - if (!iterator) - { - PyGILState_STATE gstate = PyGILState_Ensure(); - PyErr_SetString(PyExc_RuntimeError, "failed to obtain path iterator for hatching"); - PyErr_Print(); - PyGILState_Release(gstate); - return; - } - n = _draw_path(cr, iterator, INT_MAX); - free_path_iterator(iterator); - if (n==0) return; - CGContextSetLineWidth(cr, 1.0); - CGContextSetLineCap(cr, kCGLineCapSquare); - CGContextDrawPath(cr, kCGPathFillStroke); -} - -static void _release_hatch(void* info) -{ - PyObject* hatchpath = (PyObject*)info; - Py_DECREF(hatchpath); -} - /* ---------------------------- Cocoa classes ---------------------------- */ @interface WindowServerConnectionManager : NSObject @@ -387,3090 +208,91 @@ - (void)launch:(NSNotification*)notification; @interface Window : NSWindow { PyObject* manager; } -- (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager; -- (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen; -- (BOOL)closeButtonPressed; -- (void)dealloc; -@end - -@interface ToolWindow : NSWindow -{ -} -- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window; -- (void)masterCloses:(NSNotification*)notification; -- (void)close; -@end - -#ifdef COMPILING_FOR_10_6 -@interface View : NSView -#else -@interface View : NSView -#endif -{ PyObject* canvas; - NSRect rubberband; - BOOL inside; - NSTrackingRectTag tracking; -} -- (void)dealloc; -- (void)drawRect:(NSRect)rect; -- (void)windowDidResize:(NSNotification*)notification; -- (View*)initWithFrame:(NSRect)rect; -- (void)setCanvas: (PyObject*)newCanvas; -- (void)windowWillClose:(NSNotification*)notification; -- (BOOL)windowShouldClose:(NSNotification*)notification; -- (BOOL)isFlipped; -- (void)mouseEntered:(NSEvent*)event; -- (void)mouseExited:(NSEvent*)event; -- (void)mouseDown:(NSEvent*)event; -- (void)mouseUp:(NSEvent*)event; -- (void)mouseDragged:(NSEvent*)event; -- (void)mouseMoved:(NSEvent*)event; -- (void)rightMouseDown:(NSEvent*)event; -- (void)rightMouseUp:(NSEvent*)event; -- (void)rightMouseDragged:(NSEvent*)event; -- (void)otherMouseDown:(NSEvent*)event; -- (void)otherMouseUp:(NSEvent*)event; -- (void)otherMouseDragged:(NSEvent*)event; -- (void)setRubberband:(NSRect)rect; -- (void)removeRubberband; -- (const char*)convertKeyEvent:(NSEvent*)event; -- (void)keyDown:(NSEvent*)event; -- (void)keyUp:(NSEvent*)event; -- (void)scrollWheel:(NSEvent *)event; -- (BOOL)acceptsFirstResponder; -//- (void)flagsChanged:(NSEvent*)event; -@end - -@interface ScrollableButton : NSButton -{ - SEL scrollWheelUpAction; - SEL scrollWheelDownAction; -} -- (void)setScrollWheelUpAction:(SEL)action; -- (void)setScrollWheelDownAction:(SEL)action; -- (void)scrollWheel:(NSEvent *)event; -@end - -@interface MenuItem: NSMenuItem -{ int index; -} -+ (MenuItem*)menuItemWithTitle:(NSString*)title; -+ (MenuItem*)menuItemSelectAll; -+ (MenuItem*)menuItemInvertAll; -+ (MenuItem*)menuItemForAxis:(int)i; -- (void)toggle:(id)sender; -- (void)selectAll:(id)sender; -- (void)invertAll:(id)sender; -- (int)index; -@end - -/* ---------------------------- Python classes ---------------------------- */ - -typedef struct { - PyObject_HEAD - CGContextRef cr; - NSSize size; - int level; - BOOL forced_alpha; - CGFloat color[4]; - float dpi; -} GraphicsContext; - -static CGMutablePathRef _create_path(void* iterator) -{ - unsigned code; - CGMutablePathRef p; - double x1, y1, x2, y2, x3, y3; - - p = CGPathCreateMutable(); - if (!p) return NULL; - - while (true) - { - code = get_vertex(iterator, &x1, &y1); - if (code == CLOSEPOLY) - { - CGPathCloseSubpath(p); - } - else if (code == STOP) - { - break; - } - else if (code == MOVETO) - { - CGPathMoveToPoint(p, NULL, x1, y1); - } - else if (code==LINETO) - { - CGPathAddLineToPoint(p, NULL, x1, y1); - } - else if (code==CURVE3) - { - get_vertex(iterator, &x2, &y2); - CGPathAddQuadCurveToPoint(p, NULL, x1, y1, x2, y2); - } - else if (code==CURVE4) - { - get_vertex(iterator, &x2, &y2); - get_vertex(iterator, &x3, &y3); - CGPathAddCurveToPoint(p, NULL, x1, y1, x2, y2, x3, y3); - } - } - - return p; -} - -static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) -{ - PyObject* snap = PyObject_CallMethod((PyObject*)self, "get_snap", ""); - if(!snap) return 0; - if(snap==Py_None) *mode = SNAP_AUTO; - else if (PyBool_Check(snap)) *mode = SNAP_TRUE; - else *mode = SNAP_FALSE; - Py_DECREF(snap); - return 1; -} - -static PyObject* -GraphicsContext_new(PyTypeObject* type, PyObject *args, PyObject *kwds) -{ - GraphicsContext* self = (GraphicsContext*)type->tp_alloc(type, 0); - if (!self) return NULL; - self->cr = NULL; - self->level = 0; - self->forced_alpha = FALSE; - -#ifndef COMPILING_FOR_10_5 - if (ngc==0) - { - int ok = _init_atsui(); - if (!ok) - { - return NULL; - } - } - ngc++; -#endif - - return (PyObject*) self; -} - -#ifndef COMPILING_FOR_10_5 -static void -GraphicsContext_dealloc(GraphicsContext *self) -{ - ngc--; - if (ngc==0) _dealloc_atsui(); - - Py_TYPE(self)->tp_free((PyObject*)self); -} -#endif - -static PyObject* -GraphicsContext_repr(GraphicsContext* self) -{ -#if PY3K - return PyUnicode_FromFormat("GraphicsContext object %p wrapping the Quartz 2D graphics context %p", (void*)self, (void*)(self->cr)); -#else - return PyString_FromFormat("GraphicsContext object %p wrapping the Quartz 2D graphics context %p", (void*)self, (void*)(self->cr)); -#endif -} - -static PyObject* -GraphicsContext_save (GraphicsContext* self) -{ - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - CGContextSaveGState(cr); - self->level++; - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_restore (GraphicsContext* self) -{ - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - if (self->level==0) - { - PyErr_SetString(PyExc_RuntimeError, - "Attempting to execute CGContextRestoreGState on an empty stack"); - return NULL; - } - CGContextRestoreGState(cr); - self->level--; - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_alpha (GraphicsContext* self, PyObject* args) -{ - float alpha; - int forced = 0; - if (!PyArg_ParseTuple(args, "f|i", &alpha, &forced)) return NULL; - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - CGContextSetAlpha(cr, alpha); - self->forced_alpha = (BOOL)(forced || (alpha != 1.0)); - - Py_INCREF(Py_None); - return Py_None; -} - -static BOOL -_set_antialiased(CGContextRef cr, PyObject* antialiased) -{ - const int shouldAntialias = PyObject_IsTrue(antialiased); - if (shouldAntialias < 0) - { - PyErr_SetString(PyExc_ValueError, - "Failed to read antialiaseds variable"); - return false; - } - CGContextSetShouldAntialias(cr, shouldAntialias); - return true; -} - -static PyObject* -GraphicsContext_set_antialiased (GraphicsContext* self, PyObject* args) -{ - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - if (!_set_antialiased(cr, args)) return NULL; - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_capstyle (GraphicsContext* self, PyObject* args) -{ - char* string; - CGLineCap cap; - - if (!PyArg_ParseTuple(args, "s", &string)) return NULL; - - if (!strcmp(string, "butt")) cap = kCGLineCapButt; - else if (!strcmp(string, "round")) cap = kCGLineCapRound; - else if (!strcmp(string, "projecting")) cap = kCGLineCapSquare; - else - { - PyErr_SetString(PyExc_ValueError, - "capstyle should be 'butt', 'round', or 'projecting'"); - return NULL; - } - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - CGContextSetLineCap(cr, cap); - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_clip_rectangle (GraphicsContext* self, PyObject* args) -{ - CGRect rect; - float x, y, width, height; - if (!PyArg_ParseTuple(args, "(ffff)", &x, &y, &width, &height)) return NULL; - - rect.origin.x = x; - rect.origin.y = y; - rect.size.width = width; - rect.size.height = height; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - CGContextClipToRect(cr, rect); - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_clip_path (GraphicsContext* self, PyObject* args) -{ - int n; - CGContextRef cr = self->cr; - - PyObject* path; - int nd = 2; - npy_intp dims[2] = {3, 3}; - int typenum = NPY_DOUBLE; - double data[] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; - - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "O", &path)) return NULL; - - PyObject* transform = PyArray_SimpleNewFromData(nd, dims, typenum, data); - if (!transform) return NULL; - - double rect[4] = {0.0, 0.0, self->size.width, self->size.height}; - void* iterator = get_path_iterator(path, - transform, - 0, - 0, - rect, - SNAP_AUTO, - 1.0, - 0); - Py_DECREF(transform); - if (!iterator) - { - PyErr_SetString(PyExc_RuntimeError, - "set_clip_path: failed to obtain path iterator for clipping"); - return NULL; - } - n = _draw_path(cr, iterator, INT_MAX); - free_path_iterator(iterator); - - if (n > 0) CGContextClip(cr); - - Py_INCREF(Py_None); - return Py_None; -} - -static BOOL -_set_dashes(CGContextRef cr, PyObject* linestyle) -{ - CGFloat phase = 0.0; - PyObject* offset; - PyObject* dashes; - - if (!PyArg_ParseTuple(linestyle, "OO", &offset, &dashes)) - { - PyErr_SetString(PyExc_TypeError, - "failed to obtain the offset and dashes from the linestyle"); - return false; - } - - if (offset!=Py_None) - { - if (PyFloat_Check(offset)) phase = PyFloat_AS_DOUBLE(offset); -#if PY3K - else if (PyLong_Check(offset)) phase = PyLong_AS_LONG(offset); -#else - else if (PyInt_Check(offset)) phase = PyInt_AS_LONG(offset); -#endif - else - { - PyErr_SetString(PyExc_TypeError, - "offset should be a floating point value"); - return false; - } - } - - if (dashes!=Py_None) - { - if (PyList_Check(dashes)) dashes = PyList_AsTuple(dashes); - else if (PyTuple_Check(dashes)) Py_INCREF(dashes); - else - { - PyErr_SetString(PyExc_TypeError, - "dashes should be a tuple or a list"); - return false; - } - int n = PyTuple_GET_SIZE(dashes); - int i; - CGFloat* lengths = malloc(n*sizeof(CGFloat)); - if(!lengths) - { - PyErr_SetString(PyExc_MemoryError, "Failed to store dashes"); - Py_DECREF(dashes); - return false; - } - for (i = 0; i < n; i++) - { - PyObject* value = PyTuple_GET_ITEM(dashes, i); - if (PyFloat_Check(value)) - lengths[i] = (CGFloat) PyFloat_AS_DOUBLE(value); -#if PY3K - else if (PyLong_Check(value)) - lengths[i] = (CGFloat) PyLong_AS_LONG(value); -#else - else if (PyInt_Check(value)) - lengths[i] = (CGFloat) PyInt_AS_LONG(value); -#endif - else break; - } - Py_DECREF(dashes); - if (i < n) /* break encountered */ - { - free(lengths); - PyErr_SetString(PyExc_TypeError, "Failed to read dashes"); - return false; - } - CGContextSetLineDash(cr, phase, lengths, n); - free(lengths); - } - else - CGContextSetLineDash(cr, phase, NULL, 0); - - return true; -} - -static PyObject* -GraphicsContext_set_dashes (GraphicsContext* self, PyObject* args) -{ - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if (!_set_dashes(cr, args)) - return NULL; - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_foreground(GraphicsContext* self, PyObject* args) -{ - float r, g, b, a; - if(!PyArg_ParseTuple(args, "(ffff)", &r, &g, &b, &a)) return NULL; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if (self->forced_alpha) - { - // Transparency is applied to layer - // Let it override (rather than multiply with) the alpha of the - // stroke/fill colors - a = 1.0; - } - - CGContextSetRGBStrokeColor(cr, r, g, b, a); - CGContextSetRGBFillColor(cr, r, g, b, a); - - self->color[0] = r; - self->color[1] = g; - self->color[2] = b; - self->color[3] = a; - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_graylevel(GraphicsContext* self, PyObject* args) -{ float gray; - if(!PyArg_ParseTuple(args, "f", &gray)) return NULL; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - CGContextSetGrayStrokeColor(cr, gray, 1.0); - CGContextSetGrayFillColor(cr, gray, 1.0); - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_dpi (GraphicsContext* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, "f", &(self->dpi))) return NULL; - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_linewidth (GraphicsContext* self, PyObject* args) -{ - float width; - if (!PyArg_ParseTuple(args, "f", &width)) return NULL; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - // Convert points to pixels - width *= self->dpi / 72.0; - CGContextSetLineWidth(cr, width); - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_set_joinstyle(GraphicsContext* self, PyObject* args) -{ char* string; - CGLineJoin join; - - if (!PyArg_ParseTuple(args, "s", &string)) return NULL; - - if (!strcmp(string, "miter")) join = kCGLineJoinMiter; - else if (!strcmp(string, "round")) join = kCGLineJoinRound; - else if (!strcmp(string, "bevel")) join = kCGLineJoinBevel; - else - { - PyErr_SetString(PyExc_ValueError, - "joinstyle should be 'miter', 'round', or 'bevel'"); - return NULL; - } - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - CGContextSetLineJoin(cr, join); - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_draw_path (GraphicsContext* self, PyObject* args) -{ - PyObject* path; - PyObject* transform; - PyObject* rgbFace; - float linewidth; - - int n; - - void* iterator; - - CGContextRef cr = self->cr; - double rect[4] = { 0.0, 0.0, self->size.width, self->size.height}; - - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "OOf|O", - &path, - &transform, - &linewidth, - &rgbFace)) return NULL; - - if(rgbFace==Py_None) rgbFace = NULL; - - iterator = get_path_iterator(path, - transform, - 1, - 0, - rect, - SNAP_AUTO, - linewidth, - rgbFace == NULL); - if (!iterator) - { - PyErr_SetString(PyExc_RuntimeError, - "draw_path: failed to obtain path iterator"); - return NULL; - } - - if(rgbFace) - { - float r, g, b, a; - a = 1.0; - if (!PyArg_ParseTuple(rgbFace, "fff|f", &r, &g, &b, &a)) - return NULL; - if (self->forced_alpha) - a = 1.0; - - n = _draw_path(cr, iterator, INT_MAX); - if (n > 0) - { - CGContextSaveGState(cr); - CGContextSetRGBFillColor(cr, r, g, b, a); - CGContextDrawPath(cr, kCGPathFillStroke); - CGContextRestoreGState(cr); - } - } - else - { - const int nmax = 100; - while (true) - { - n = _draw_path(cr, iterator, nmax); - if (n != 0) CGContextStrokePath(cr); - if (n >= 0) break; - } - } - free_path_iterator(iterator); - - PyObject* hatchpath; - hatchpath = PyObject_CallMethod((PyObject*)self, "get_hatch_path", ""); - if (!hatchpath) - { - return NULL; - } - else if (hatchpath==Py_None) - { - Py_DECREF(hatchpath); - } - else - { - CGPatternRef pattern; - CGColorSpaceRef baseSpace; - CGColorSpaceRef patternSpace; - static const CGPatternCallbacks callbacks = {0, - &_draw_hatch, - &_release_hatch}; - baseSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); - if (!baseSpace) - { - Py_DECREF(hatchpath); - PyErr_SetString(PyExc_RuntimeError, - "draw_path: CGColorSpaceCreateWithName failed"); - return NULL; - } - patternSpace = CGColorSpaceCreatePattern(baseSpace); - CGColorSpaceRelease(baseSpace); - if (!patternSpace) - { - Py_DECREF(hatchpath); - PyErr_SetString(PyExc_RuntimeError, - "draw_path: CGColorSpaceCreatePattern failed"); - return NULL; - } - CGContextSetFillColorSpace(cr, patternSpace); - CGColorSpaceRelease(patternSpace); - - pattern = CGPatternCreate((void*)hatchpath, - CGRectMake(0, 0, HATCH_SIZE, HATCH_SIZE), - CGAffineTransformIdentity, - HATCH_SIZE, HATCH_SIZE, - kCGPatternTilingNoDistortion, - false, - &callbacks); - CGContextSetFillPattern(cr, pattern, self->color); - CGPatternRelease(pattern); - iterator = get_path_iterator(path, - transform, - 1, - 0, - rect, - SNAP_AUTO, - linewidth, - 0); - if (!iterator) - { - Py_DECREF(hatchpath); - PyErr_SetString(PyExc_RuntimeError, - "draw_path: failed to obtain path iterator for hatching"); - return NULL; - } - n = _draw_path(cr, iterator, INT_MAX); - free_path_iterator(iterator); - if (n > 0) CGContextFillPath(cr); - } - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_draw_markers (GraphicsContext* self, PyObject* args) -{ - PyObject* marker_path; - PyObject* marker_transform; - PyObject* path; - PyObject* transform; - float linewidth; - PyObject* rgbFace; - - int ok; - float r, g, b, a; - - CGMutablePathRef marker; - void* iterator; - double rect[4] = {0.0, 0.0, self->size.width, self->size.height}; - enum e_snap_mode mode; - double xc, yc; - unsigned code; - - CGContextRef cr = self->cr; - - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "OOOOf|O", - &marker_path, - &marker_transform, - &path, - &transform, - &linewidth, - &rgbFace)) return NULL; - - if(rgbFace==Py_None) rgbFace = NULL; - - if (rgbFace) - { - a = 1.0; - ok = PyArg_ParseTuple(rgbFace, "fff|f", &r, &g, &b, &a); - if (!ok) - { - return NULL; - } - if (self->forced_alpha) - a = 1.0; - CGContextSetRGBFillColor(cr, r, g, b, a); - } - - ok = _get_snap(self, &mode); - if (!ok) - { - return NULL; - } - - iterator = get_path_iterator(marker_path, - marker_transform, - 0, - 0, - rect, - mode, - linewidth, - 0); - if (!iterator) - { - PyErr_SetString(PyExc_RuntimeError, - "draw_markers: failed to obtain path iterator for marker"); - return NULL; - } - marker = _create_path(iterator); - free_path_iterator(iterator); - if (!marker) - { - PyErr_SetString(PyExc_RuntimeError, - "draw_markers: failed to draw marker path"); - return NULL; - } - iterator = get_path_iterator(path, - transform, - 1, - 1, - rect, - SNAP_TRUE, - 1.0, - 0); - if (!iterator) - { - CGPathRelease(marker); - PyErr_SetString(PyExc_RuntimeError, - "draw_markers: failed to obtain path iterator"); - return NULL; - } - - while (true) - { - code = get_vertex(iterator, &xc, &yc); - if (code == STOP) - { - break; - } - else if (code == MOVETO || code == LINETO || code == CURVE3 || code ==CURVE4) - { - CGContextSaveGState(cr); - CGContextTranslateCTM(cr, xc, yc); - CGContextAddPath(cr, marker); - CGContextRestoreGState(cr); - } - if(rgbFace) CGContextDrawPath(cr, kCGPathFillStroke); - else CGContextStrokePath(cr); - } - free_path_iterator(iterator); - CGPathRelease(marker); - - Py_INCREF(Py_None); - return Py_None; -} - -static int _transformation_converter(PyObject* object, void* pointer) -{ - CGAffineTransform* matrix = (CGAffineTransform*)pointer; - if (!PyArray_Check(object)) - { - PyErr_SetString(PyExc_ValueError, - "transformation matrix is not a NumPy array"); - return 0; - } - PyArrayObject* aobject = (PyArrayObject*)object; - - if (PyArray_NDIM(aobject)!=2 || PyArray_DIM(aobject, 0)!=3 - || PyArray_DIM(aobject, 1)!=3) - { - PyErr_SetString(PyExc_ValueError, - "transformation matrix is not a 3x3 NumPy array"); - return 0; - } - const double a = *(double*)PyArray_GETPTR2(aobject, 0, 0); - const double b = *(double*)PyArray_GETPTR2(aobject, 1, 0); - const double c = *(double*)PyArray_GETPTR2(aobject, 0, 1); - const double d = *(double*)PyArray_GETPTR2(aobject, 1, 1); - const double tx = *(double*)PyArray_GETPTR2(aobject, 0, 2); - const double ty = *(double*)PyArray_GETPTR2(aobject, 1, 2); - *matrix = CGAffineTransformMake(a, b, c, d, tx, ty); - return 1; -} - -static PyObject* -GraphicsContext_draw_path_collection (GraphicsContext* self, PyObject* args) -{ - CGAffineTransform master; - PyObject* path_ids; - PyObject* all_transforms; - PyObject* offsets; - PyArrayObject* offsets_arr = 0; - CGAffineTransform offset_transform; - PyObject* facecolors; - PyArrayObject* facecolors_arr = 0; - PyObject* edgecolors; - PyArrayObject* edgecolors_arr = 0; - PyObject* linewidths; - PyObject* linestyles; - PyObject* antialiaseds; - - int offset_position = 0; - - CGContextRef cr = self->cr; - - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "O&OOOO&OOOOOi", - _transformation_converter, &master, - &path_ids, - &all_transforms, - &offsets, - _transformation_converter, &offset_transform, - &facecolors, - &edgecolors, - &linewidths, - &linestyles, - &antialiaseds, - &offset_position)) - return NULL; - - int ok = 1; - Py_ssize_t i; - - CGMutablePathRef* p = NULL; - CGAffineTransform* transforms = NULL; - CGPoint *toffsets = NULL; - CGPatternRef pattern = NULL; - CGColorSpaceRef patternSpace = NULL; - - PyObject* hatchpath; - hatchpath = PyObject_CallMethod((PyObject*)self, "get_hatch_path", ""); - if (!hatchpath) - { - return NULL; - } - else if (hatchpath==Py_None) - { - Py_DECREF(hatchpath); - } - else - { - CGColorSpaceRef baseSpace; - static const CGPatternCallbacks callbacks = {0, - &_draw_hatch, - &_release_hatch}; - baseSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); - if (!baseSpace) - { - Py_DECREF(hatchpath); - PyErr_SetString(PyExc_RuntimeError, - "draw_path: CGColorSpaceCreateWithName failed"); - return NULL; - } - patternSpace = CGColorSpaceCreatePattern(baseSpace); - CGColorSpaceRelease(baseSpace); - if (!patternSpace) - { - Py_DECREF(hatchpath); - PyErr_SetString(PyExc_RuntimeError, - "draw_path: CGColorSpaceCreatePattern failed"); - return NULL; - } - pattern = CGPatternCreate((void*)hatchpath, - CGRectMake(0, 0, HATCH_SIZE, HATCH_SIZE), - CGAffineTransformIdentity, - HATCH_SIZE, HATCH_SIZE, - kCGPatternTilingNoDistortion, - false, - &callbacks); - } - - /* --------- Prepare some variables for the path iterator ------------- */ - void* iterator; - double rect[4] = {0.0, 0.0, self->size.width, self->size.height}; - enum e_snap_mode mode; - ok = _get_snap(self, &mode); - if (!ok) - { - return NULL; - } - - /* ------------------- Check paths ------------------------------------ */ - - if (!PySequence_Check(path_ids)) - { - PyErr_SetString(PyExc_ValueError, "paths must be a sequence object"); - return NULL; - } - const Py_ssize_t Npaths = PySequence_Size(path_ids); - - /* -------------------------------------------------------------------- */ - - CGContextSaveGState(cr); - - /* ------------------- Check facecolors array ------------------------- */ - - facecolors_arr = (PyArrayObject*) PyArray_FromObject(facecolors, NPY_DOUBLE, 1, 2); - if (!facecolors_arr || - (PyArray_NDIM(facecolors_arr)==1 && PyArray_DIM(facecolors_arr, 0)!=0) || - (PyArray_NDIM(facecolors_arr)==2 && PyArray_DIM(facecolors_arr, 1)!=4)) - { - PyErr_SetString(PyExc_ValueError, "Facecolors must by a Nx4 numpy array or empty"); - ok = 0; - goto exit; - } - Py_ssize_t Nfacecolors = PyArray_DIM(facecolors_arr, 0); - - /* ------------------- Check edgecolors array ------------------------- */ - - edgecolors_arr = (PyArrayObject*) PyArray_FromObject(edgecolors, NPY_DOUBLE, 1, 2); - if (!edgecolors_arr || - (PyArray_NDIM(edgecolors_arr)==1 && PyArray_DIM(edgecolors_arr, 0)!=0) || - (PyArray_NDIM(edgecolors_arr)==2 && PyArray_DIM(edgecolors_arr, 1)!=4)) - { - PyErr_SetString(PyExc_ValueError, "Edgecolors must by a Nx4 numpy array or empty"); - ok = 0; - goto exit; - } - Py_ssize_t Nedgecolors = PyArray_DIM(edgecolors_arr, 0); - - /* -------------------------------------------------------------------- */ - - if ((Nfacecolors==0 && Nedgecolors==0) || Npaths==0) /* Nothing to do */ - goto exit; - - /* ------------------- Check offsets array ---------------------------- */ - - offsets_arr = (PyArrayObject*) PyArray_FromObject(offsets, NPY_DOUBLE, 0, 2); - - if (!offsets_arr || - (PyArray_NDIM(offsets_arr)==2 && PyArray_DIM(offsets_arr, 1)!=2) || - (PyArray_NDIM(offsets_arr)==1 && PyArray_DIM(offsets_arr, 0)!=0)) - { - Py_XDECREF(offsets_arr); - PyErr_SetString(PyExc_ValueError, "Offsets array must be Nx2"); - ok = 0; - goto exit; - } - const Py_ssize_t Noffsets = PyArray_DIM(offsets_arr, 0); - if (Noffsets > 0) { - toffsets = malloc(Noffsets*sizeof(CGPoint)); - if (!toffsets) - { - Py_DECREF(offsets_arr); - ok = 0; - goto exit; - } - CGPoint point; - for (i = 0; i < Noffsets; i++) - { - point.x = (CGFloat) (*(double*)PyArray_GETPTR2(offsets_arr, i, 0)); - point.y = (CGFloat) (*(double*)PyArray_GETPTR2(offsets_arr, i, 1)); - toffsets[i] = CGPointApplyAffineTransform(point, offset_transform); - } - } - Py_DECREF(offsets_arr); - - /* ------------------- Check transforms ------------------------------- */ - - if (!PySequence_Check(all_transforms)) - { - PyErr_SetString(PyExc_ValueError, "transforms must be a sequence object"); - return NULL; - } - const Py_ssize_t Ntransforms = PySequence_Size(all_transforms); - if (Ntransforms > 0) - { - transforms = malloc(Ntransforms*sizeof(CGAffineTransform)); - if (!transforms) - goto exit; - for (i = 0; i < Ntransforms; i++) - { - PyObject* transform = PySequence_ITEM(all_transforms, i); - if (!transform) goto exit; - ok = _transformation_converter(transform, &transforms[i]); - Py_DECREF(transform); - if (!ok) goto exit; - } - } - - /* -------------------------------------------------------------------- */ - - p = malloc(Npaths*sizeof(CGMutablePathRef)); - if (!p) - { - ok = 0; - goto exit; - } - for (i = 0; i < Npaths; i++) - { - PyObject* path; - PyObject* transform; - p[i] = NULL; - PyObject* path_id = PySequence_ITEM(path_ids, i); - if (!path_id) - { - ok = 0; - goto exit; - } - if (!PyTuple_Check(path_id) || PyTuple_Size(path_id)!=2) - { - ok = 0; - PyErr_SetString(PyExc_RuntimeError, - "path_id should be a tuple of two items"); - goto exit; - } - path = PyTuple_GET_ITEM(path_id, 0); - transform = PyTuple_GET_ITEM(path_id, 1); - iterator = get_path_iterator(path, - transform, - 1, - 0, - rect, - mode, - 1.0, - /* Hardcoding stroke width to 1.0 - here, but for true - correctness, the paths would - need to be set up for each - different linewidth that may - be applied below. This - difference is very minute in - practice, so this hardcoding - is probably ok for now. -- - MGD */ - 0); - if (!iterator) - { - PyErr_SetString(PyExc_RuntimeError, - "failed to obtain path iterator"); - ok = 0; - goto exit; - } - p[i] = _create_path(iterator); - free_path_iterator(iterator); - Py_DECREF(path_id); - if (!p[i]) - { - PyErr_SetString(PyExc_RuntimeError, "failed to create path"); - ok = 0; - goto exit; - } - } - - /* ------------------- Check the other arguments ---------------------- */ - - if (!PySequence_Check(linewidths)) - { - PyErr_SetString(PyExc_ValueError, "linewidths must be a sequence object"); - ok = 0; - goto exit; - } - if (!PySequence_Check(linestyles)) - { - PyErr_SetString(PyExc_ValueError, "linestyles must be a sequence object"); - ok = 0; - goto exit; - } - if (!PySequence_Check(antialiaseds)) - { - PyErr_SetString(PyExc_ValueError, "antialiaseds must be a sequence object"); - ok = 0; - goto exit; - } - - Py_ssize_t Nlinewidths = PySequence_Size(linewidths); - Py_ssize_t Nlinestyles = PySequence_Size(linestyles); - Py_ssize_t Naa = PySequence_Size(antialiaseds); - - /* Preset graphics context properties if possible */ - if (Naa==1) - { - PyObject* antialiased = PySequence_ITEM(antialiaseds, 0); - if (antialiased) - { - ok = _set_antialiased(cr, antialiased); - Py_DECREF(antialiased); - } - else - { - PyErr_SetString(PyExc_SystemError, - "Failed to read element from antialiaseds array"); - ok = 0; - } - if (!ok) goto exit; - } - - if (Nlinewidths==0 || Nedgecolors==0) - CGContextSetLineWidth(cr, 0.0); - else if (Nlinewidths==1) - { - PyObject* linewidth = PySequence_ITEM(linewidths, 0); - if (!linewidth) - { - PyErr_SetString(PyExc_SystemError, - "Failed to read element from linewidths array"); - ok = 0; - goto exit; - } - CGContextSetLineWidth(cr, (CGFloat)PyFloat_AsDouble(linewidth)); - Py_DECREF(linewidth); - } - - if (Nlinestyles==1) - { - PyObject* linestyle = PySequence_ITEM(linestyles, 0); - if (!linestyle) - { - PyErr_SetString(PyExc_SystemError, - "Failed to read element from linestyles array"); - ok = 0; - goto exit; - } - ok = _set_dashes(cr, linestyle); - Py_DECREF(linestyle); - if (!ok) goto exit; - } - - if (Nedgecolors==1) - { - const double r = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 0); - const double g = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 1); - const double b = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 2); - const double a = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 3); - CGContextSetRGBStrokeColor(cr, r, g, b, a); - self->color[0] = r; - self->color[1] = g; - self->color[2] = b; - self->color[3] = a; - } - else /* We may need these for hatching */ - { - self->color[0] = 0; - self->color[1] = 0; - self->color[2] = 0; - self->color[3] = 1; - } - - if (Nfacecolors==1) - { - const double r = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 0); - const double g = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 1); - const double b = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 2); - const double a = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 3); - CGContextSetRGBFillColor(cr, r, g, b, a); - } - - CGPoint translation = CGPointZero; - - const Py_ssize_t N = Npaths > Noffsets ? Npaths : Noffsets; - for (i = 0; i < N; i++) - { - if (CGPathIsEmpty(p[i % Npaths])) continue; - - if (Noffsets) - { - CGAffineTransform t; - CGPoint origin; - translation = toffsets[i % Noffsets]; - if (offset_position) - { - t = master; - if (Ntransforms) - t = CGAffineTransformConcat(transforms[i % Ntransforms], t); - translation = CGPointApplyAffineTransform(translation, t); - origin = CGPointApplyAffineTransform(CGPointZero, t); - translation.x = - (origin.x - translation.x); - translation.y = - (origin.y - translation.y); - } - /* in case of missing values, translation may contain NaN's */ - if (!isfinite(translation.x) || !isfinite(translation.y)) continue; - CGContextTranslateCTM(cr, translation.x, translation.y); - } - - if (Naa > 1) - { - PyObject* antialiased = PySequence_ITEM(antialiaseds, i % Naa); - if (antialiased) - { - ok = _set_antialiased(cr, antialiased); - Py_DECREF(antialiased); - } - else - { - PyErr_SetString(PyExc_SystemError, - "Failed to read element from antialiaseds array"); - ok = 0; - } - if (!ok) goto exit; - } - - if (Nlinewidths > 1) - { - PyObject* linewidth = PySequence_ITEM(linewidths, i % Nlinewidths); - if (!linewidth) - { - PyErr_SetString(PyExc_SystemError, - "Failed to read element from linewidths array"); - ok = 0; - goto exit; - } - CGContextSetLineWidth(cr, (CGFloat)PyFloat_AsDouble(linewidth)); - Py_DECREF(linewidth); - } - - if (Nlinestyles > 1) - { - PyObject* linestyle = PySequence_ITEM(linestyles, i % Nlinestyles); - if (!linestyle) - { - PyErr_SetString(PyExc_SystemError, - "Failed to read element from linestyles array"); - ok = 0; - goto exit; - } - ok = _set_dashes(cr, linestyle); - Py_DECREF(linestyle); - if (!ok) goto exit; - } - - if (Nedgecolors > 1) - { - npy_intp fi = i % Nedgecolors; - const double r = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 0); - const double g = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 1); - const double b = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 2); - const double a = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 3); - CGContextSetRGBStrokeColor(cr, r, g, b, a); - } - - CGContextAddPath(cr, p[i % Npaths]); - - if (Nfacecolors > 1) - { - npy_intp fi = i % Nfacecolors; - const double r = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 0); - const double g = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 1); - const double b = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 2); - const double a = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 3); - CGContextSetRGBFillColor(cr, r, g, b, a); - if (Nedgecolors > 0) CGContextDrawPath(cr, kCGPathFillStroke); - else CGContextFillPath(cr); - } - else if (Nfacecolors==1) - { - if (Nedgecolors > 0) CGContextDrawPath(cr, kCGPathFillStroke); - else CGContextFillPath(cr); - } - else /* We checked Nedgecolors != 0 above */ - CGContextStrokePath(cr); - - if (pattern) - { - CGContextSaveGState(cr); - CGContextSetFillColorSpace(cr, patternSpace); - CGContextSetFillPattern(cr, pattern, self->color); - CGContextAddPath(cr, p[i % Npaths]); - CGContextFillPath(cr); - CGContextRestoreGState(cr); - } - - if (Noffsets) - CGContextTranslateCTM(cr, -translation.x, -translation.y); - } - -exit: - CGContextRestoreGState(cr); - Py_XDECREF(facecolors_arr); - Py_XDECREF(edgecolors_arr); - if (pattern) CGPatternRelease(pattern); - if (patternSpace) CGColorSpaceRelease(patternSpace); - if (transforms) free(transforms); - if (toffsets) free(toffsets); - if (p) - { - for (i = 0; i < Npaths; i++) - { - if (!p[i]) break; - CGPathRelease(p[i]); - } - free(p); - } - if (!ok) return NULL; - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_draw_quad_mesh (GraphicsContext* self, PyObject* args) -{ - CGAffineTransform master; - int meshWidth; - int meshHeight; - PyObject* coordinates; - PyArrayObject* coordinates_arr = 0; - PyObject* offsets; - PyArrayObject* offsets_arr = 0; - CGAffineTransform offset_transform; - PyObject* facecolors; - PyArrayObject* facecolors_arr = 0; - int antialiased; - PyObject* edgecolors; - PyArrayObject* edgecolors_arr = 0; - - CGPoint *toffsets = NULL; - - CGContextRef cr = self->cr; - - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "O&iiOOO&OiO", - _transformation_converter, &master, - &meshWidth, - &meshHeight, - &coordinates, - &offsets, - _transformation_converter, &offset_transform, - &facecolors, - &antialiased, - &edgecolors)) return NULL; - - int ok = 1; - CGContextSaveGState(cr); - - /* ------------------- Check coordinates array ------------------------ */ - - coordinates_arr = (PyArrayObject*) PyArray_FromObject(coordinates, NPY_DOUBLE, 3, 3); - if (!coordinates_arr || - PyArray_NDIM(coordinates_arr) != 3 || PyArray_DIM(coordinates_arr, 2) != 2) - { - PyErr_SetString(PyExc_ValueError, "Invalid coordinates array"); - ok = 0; - goto exit; - } - - /* ------------------- Check offsets array ---------------------------- */ - - offsets_arr = (PyArrayObject*) PyArray_FromObject(offsets, NPY_DOUBLE, 0, 2); - - if (!offsets_arr || - (PyArray_NDIM(offsets_arr)==2 && PyArray_DIM(offsets_arr, 1)!=2) || - (PyArray_NDIM(offsets_arr)==1 && PyArray_DIM(offsets_arr, 0)!=0)) - { - Py_XDECREF(offsets_arr); - PyErr_SetString(PyExc_ValueError, "Offsets array must be Nx2"); - ok = 0; - goto exit; - } - const Py_ssize_t Noffsets = PyArray_DIM(offsets_arr, 0); - if (Noffsets > 0) { - int i; - toffsets = malloc(Noffsets*sizeof(CGPoint)); - if (!toffsets) - { - Py_DECREF(offsets_arr); - ok = 0; - goto exit; - } - CGPoint point; - for (i = 0; i < Noffsets; i++) - { - point.x = (CGFloat) (*(double*)PyArray_GETPTR2(offsets_arr, i, 0)); - point.y = (CGFloat) (*(double*)PyArray_GETPTR2(offsets_arr, i, 1)); - toffsets[i] = CGPointApplyAffineTransform(point, offset_transform); - } - } - Py_DECREF(offsets_arr); - - /* ------------------- Check facecolors array ------------------------- */ - - facecolors_arr = (PyArrayObject*) PyArray_FromObject(facecolors, NPY_DOUBLE, 1, 2); - if (!facecolors_arr || - (PyArray_NDIM(facecolors_arr)==1 && PyArray_DIM(facecolors_arr, 0)!=0) || - (PyArray_NDIM(facecolors_arr)==2 && PyArray_DIM(facecolors_arr, 1)!=4)) - { - PyErr_SetString(PyExc_ValueError, "facecolors must by a Nx4 numpy array or empty"); - ok = 0; - goto exit; - } - - /* ------------------- Check edgecolors array ------------------------- */ - - edgecolors_arr = (PyArrayObject*) PyArray_FromObject(edgecolors, NPY_DOUBLE, 1, 2); - if (!edgecolors_arr || - (PyArray_NDIM(edgecolors_arr)==1 && PyArray_DIM(edgecolors_arr, 0)!=0) || - (PyArray_NDIM(edgecolors_arr)==2 && PyArray_DIM(edgecolors_arr, 1)!=4)) - { - PyErr_SetString(PyExc_ValueError, "edgecolors must by a Nx4 numpy array or empty"); - ok = 0; - goto exit; - } - - /* ------------------- Check the other arguments ---------------------- */ - - size_t Npaths = meshWidth * meshHeight; - size_t Nfacecolors = (size_t) PyArray_DIM(facecolors_arr, 0); - size_t Nedgecolors = (size_t) PyArray_DIM(edgecolors_arr, 0); - if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) - { - /* Nothing to do here */ - goto exit; - } - - size_t i = 0; - int iw = 0; - int ih = 0; - - /* Preset graphics context properties if possible */ - CGContextSetShouldAntialias(cr, antialiased); - - if (Nfacecolors==1) - { - const double r = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 0); - const double g = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 1); - const double b = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 2); - const double a = *(double*)PyArray_GETPTR2(facecolors_arr, 0, 3); - CGContextSetRGBFillColor(cr, r, g, b, a); - if (antialiased && Nedgecolors==0) - { - CGContextSetRGBStrokeColor(cr, r, g, b, a); - } - } - if (Nedgecolors==1) - { - const double r = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 0); - const double g = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 1); - const double b = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 2); - const double a = *(double*)PyArray_GETPTR2(edgecolors_arr, 0, 3); - CGContextSetRGBStrokeColor(cr, r, g, b, a); - } - - CGPoint translation = CGPointZero; - double x, y; - for (ih = 0; ih < meshHeight; ih++) - { - for (iw = 0; iw < meshWidth; iw++, i++) - { - CGPoint points[4]; - - x = *(double*)PyArray_GETPTR3(coordinates_arr, ih, iw, 0); - y = *(double*)PyArray_GETPTR3(coordinates_arr, ih, iw, 1); - if (isnan(x) || isnan(y)) continue; - points[0].x = (CGFloat)x; - points[0].y = (CGFloat)y; - - x = *(double*)PyArray_GETPTR3(coordinates_arr, ih, iw+1, 0); - y = *(double*)PyArray_GETPTR3(coordinates_arr, ih, iw+1, 1); - if (isnan(x) || isnan(y)) continue; - points[1].x = (CGFloat)x; - points[1].y = (CGFloat)y; - - x = *(double*)PyArray_GETPTR3(coordinates_arr, ih+1, iw+1, 0); - y = *(double*)PyArray_GETPTR3(coordinates_arr, ih+1, iw+1, 1); - if (isnan(x) || isnan(y)) continue; - points[2].x = (CGFloat)x; - points[2].y = (CGFloat)y; - - x = *(double*)PyArray_GETPTR3(coordinates_arr, ih+1, iw, 0); - y = *(double*)PyArray_GETPTR3(coordinates_arr, ih+1, iw, 1); - if (isnan(x) || isnan(y)) continue; - points[3].x = (CGFloat)x; - points[3].y = (CGFloat)y; - - points[0] = CGPointApplyAffineTransform(points[0], master); - points[1] = CGPointApplyAffineTransform(points[1], master); - points[2] = CGPointApplyAffineTransform(points[2], master); - points[3] = CGPointApplyAffineTransform(points[3], master); - - if (Noffsets) - { - translation = toffsets[i % Noffsets]; - CGContextTranslateCTM(cr, translation.x, translation.y); - } - - CGContextMoveToPoint(cr, points[3].x, points[3].y); - CGContextAddLines(cr, points, 4); - CGContextClosePath(cr); - - if (Nfacecolors > 1) - { - npy_intp fi = i % Nfacecolors; - const double r = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 0); - const double g = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 1); - const double b = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 2); - const double a = *(double*)PyArray_GETPTR2(facecolors_arr, fi, 3); - CGContextSetRGBFillColor(cr, r, g, b, a); - if (antialiased && Nedgecolors==0) - { - CGContextSetRGBStrokeColor(cr, r, g, b, a); - } - } - if (Nedgecolors > 1) - { - npy_intp fi = i % Nedgecolors; - const double r = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 0); - const double g = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 1); - const double b = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 2); - const double a = *(double*)PyArray_GETPTR2(edgecolors_arr, fi, 3); - CGContextSetRGBStrokeColor(cr, r, g, b, a); - } - - if (Nfacecolors > 0) - { - if (Nedgecolors > 0 || antialiased) - { - CGContextDrawPath(cr, kCGPathFillStroke); - } - else - { - CGContextFillPath(cr); - } - } - else if (Nedgecolors > 0) - { - CGContextStrokePath(cr); - } - if (Noffsets) - { - CGContextTranslateCTM(cr, -translation.x, -translation.y); - } - } - } - -exit: - CGContextRestoreGState(cr); - if (toffsets) free(toffsets); - Py_XDECREF(facecolors_arr); - Py_XDECREF(edgecolors_arr); - Py_XDECREF(coordinates_arr); - - if (!ok) return NULL; - - Py_INCREF(Py_None); - return Py_None; -} - -static int _find_minimum(CGFloat values[3]) -{ - int i = 0; - CGFloat minimum = values[0]; - if (values[1] < minimum) - { - minimum = values[1]; - i = 1; - } - if (values[2] < minimum) - i = 2; - return i; -} - -static int _find_maximum(CGFloat values[3]) -{ - int i = 0; - CGFloat maximum = values[0]; - if (values[1] > maximum) - { - maximum = values[1]; - i = 1; - } - if (values[2] > maximum) - i = 2; - return i; -} - -static void -_rgba_color_evaluator(void* info, const CGFloat input[], CGFloat outputs[]) -{ - const CGFloat c1 = input[0]; - const CGFloat c0 = 1.0 - c1; - CGFloat(* color)[4] = info; - outputs[0] = c0 * color[0][0] + c1 * color[1][0]; - outputs[1] = c0 * color[0][1] + c1 * color[1][1]; - outputs[2] = c0 * color[0][2] + c1 * color[1][2]; - outputs[3] = c0 * color[0][3] + c1 * color[1][3]; -} - -static void -_gray_color_evaluator(void* info, const CGFloat input[], CGFloat outputs[]) -{ - const CGFloat c1 = input[0]; - const CGFloat c0 = 1.0 - c1; - CGFloat(* color)[2] = info; - outputs[0] = c0 * color[0][0] + c1 * color[1][0]; - outputs[1] = c0 * color[0][1] + c1 * color[1][1]; -} - -static int -_shade_one_color(CGContextRef cr, CGFloat colors[3], CGPoint points[3], int icolor) -{ - const int imin = _find_minimum(colors); - const int imax = _find_maximum(colors); - - float numerator; - float denominator; - float ac; - float as; - float phi; - float distance; - CGPoint start; - CGPoint end; - static CGFunctionCallbacks callbacks = {0, &_rgba_color_evaluator, free}; - CGFloat domain[2] = {0.0, 1.0}; - CGFloat range[8] = {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0}; - CGFunctionRef function; - - CGFloat(* rgba)[4] = malloc(2*sizeof(CGFloat[4])); - if (!rgba) return -1; - else { - rgba[0][0] = 0.0; - rgba[0][1] = 0.0; - rgba[0][2] = 0.0; - rgba[0][3] = 1.0; - rgba[1][0] = 0.0; - rgba[1][1] = 0.0; - rgba[1][2] = 0.0; - rgba[1][3] = 1.0; - } - - denominator = (points[1].x-points[0].x)*(points[2].y-points[0].y) - - (points[2].x-points[0].x)*(points[1].y-points[0].y); - numerator = (colors[1]-colors[0])*(points[2].y-points[0].y) - - (colors[2]-colors[0])*(points[1].y-points[0].y); - ac = numerator / denominator; - numerator = (colors[2]-colors[0])*(points[1].x-points[0].x) - - (colors[1]-colors[0])*(points[2].x-points[0].x); - as = numerator / denominator; - phi = atan2(as, ac); - - start.x = points[imin].x; - start.y = points[imin].y; - - rgba[0][icolor] = colors[imin]; - rgba[1][icolor] = colors[imax]; - - distance = (points[imax].x-points[imin].x) * cos(phi) + (points[imax].y-points[imin].y) * sin(phi); - - end.x = start.x + distance * cos(phi); - end.y = start.y + distance * sin(phi); - - function = CGFunctionCreate(rgba, - 1, /* one input (position) */ - domain, - 4, /* rgba output */ - range, - &callbacks); - if (function) - { - CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); - CGShadingRef shading = CGShadingCreateAxial(colorspace, - start, - end, - function, - true, - true); - CGColorSpaceRelease(colorspace); - CGFunctionRelease(function); - if (shading) - { - CGContextDrawShading(cr, shading); - CGShadingRelease(shading); - return 1; - } - } - free(rgba); - return -1; -} - -static CGRect _find_enclosing_rect(CGPoint points[3]) -{ - CGFloat left = points[0].x; - CGFloat right = points[0].x; - CGFloat bottom = points[0].y; - CGFloat top = points[0].y; - if (points[1].x < left) left = points[1].x; - if (points[1].x > right) right = points[1].x; - if (points[2].x < left) left = points[2].x; - if (points[2].x > right) right = points[2].x; - if (points[1].y < bottom) bottom = points[1].y; - if (points[1].y > top) top = points[1].y; - if (points[2].y < bottom) bottom = points[2].y; - if (points[2].y > top) top = points[2].y; - return CGRectMake(left,bottom,right-left,top-bottom); -} - -static int -_shade_alpha(CGContextRef cr, CGFloat alphas[3], CGPoint points[3]) -{ - const int imin = _find_minimum(alphas); - const int imax = _find_maximum(alphas); - - if (alphas[imin]==1.0) return 0; - - CGRect rect = _find_enclosing_rect(points); - const size_t width = (size_t)rect.size.width; - const size_t height = (size_t)rect.size.height; - if (width==0 || height==0) return 0; - - void* data = malloc(width*height); - - CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray(); - CGContextRef bitmap = CGBitmapContextCreate(data, - width, - height, - 8, - width, - colorspace, - 0); - CGColorSpaceRelease(colorspace); - - if (imin==imax) - { - CGRect bitmap_rect = rect; - bitmap_rect.origin = CGPointZero; - CGContextSetGrayFillColor(bitmap, alphas[0], 1.0); - CGContextFillRect(bitmap, bitmap_rect); - } - else - { - float numerator; - float denominator; - float ac; - float as; - float phi; - float distance; - CGPoint start; - CGPoint end; - CGFloat(*gray)[2] = malloc(2*sizeof(CGFloat[2])); - - static CGFunctionCallbacks callbacks = {0, &_gray_color_evaluator, free}; - CGFloat domain[2] = {0.0, 1.0}; - CGFloat range[2] = {0.0, 1.0}; - CGShadingRef shading = NULL; - CGFunctionRef function; - - gray[0][1] = 1.0; - gray[1][1] = 1.0; - - denominator = (points[1].x-points[0].x)*(points[2].y-points[0].y) - - (points[2].x-points[0].x)*(points[1].y-points[0].y); - numerator = (alphas[1]-alphas[0])*(points[2].y-points[0].y) - - (alphas[2]-alphas[0])*(points[1].y-points[0].y); - ac = numerator / denominator; - numerator = (alphas[2]-alphas[0])*(points[1].x-points[0].x) - - (alphas[1]-alphas[0])*(points[2].x-points[0].x); - as = numerator / denominator; - phi = atan2(as, ac); - - start.x = points[imin].x - rect.origin.x; - start.y = points[imin].y - rect.origin.y; - - gray[0][0] = alphas[imin]; - gray[1][0] = alphas[imax]; - - distance = (points[imax].x-points[imin].x) * cos(phi) + (points[imax].y-points[imin].y) * sin(phi); - - end.x = start.x + distance * cos(phi); - end.y = start.y + distance * sin(phi); - - function = CGFunctionCreate(gray, - 1, /* one input (position) */ - domain, - 1, /* one output (gray level) */ - range, - &callbacks); - if (function) - { - shading = CGShadingCreateAxial(colorspace, - start, - end, - function, - true, - true); - CGFunctionRelease(function); - } - if (shading) - { - CGContextDrawShading(bitmap, shading); - CGShadingRelease(shading); - } - else - { - free(gray); - } - } - - CGImageRef mask = CGBitmapContextCreateImage(bitmap); - CGContextClipToMask(cr, rect, mask); - CGImageRelease(mask); - CGContextRelease(bitmap); - free(data); - return 0; -} - - -static CGFloat _get_device_scale(CGContextRef cr) -{ - CGSize pixelSize = CGContextConvertSizeToDeviceSpace(cr, CGSizeMake(1,1)); - return pixelSize.width; -} - - -static PyObject* -GraphicsContext_draw_gouraud_triangle (GraphicsContext* self, PyObject* args) - -{ - PyObject* coordinates; - PyArrayObject* coordinates_arr = 0; - PyObject* colors; - PyArrayObject* colors_arr = 0; - - CGPoint points[3]; - CGFloat intensity[3]; - - int i = 0; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "OO", &coordinates, &colors)) return NULL; - - /* ------------------- Check coordinates array ------------------------ */ - - coordinates_arr = (PyArrayObject*) PyArray_FromObject(coordinates, NPY_DOUBLE, 2, 2); - if (!coordinates_arr || - PyArray_DIM(coordinates_arr, 0) != 3 || PyArray_DIM(coordinates_arr, 1) != 2) - { - PyErr_SetString(PyExc_ValueError, "Invalid coordinates array"); - Py_XDECREF(coordinates_arr); - return NULL; - } - points[0].x = *((double*)(PyArray_GETPTR2(coordinates_arr, 0, 0))); - points[0].y = *((double*)(PyArray_GETPTR2(coordinates_arr, 0, 1))); - points[1].x = *((double*)(PyArray_GETPTR2(coordinates_arr, 1, 0))); - points[1].y = *((double*)(PyArray_GETPTR2(coordinates_arr, 1, 1))); - points[2].x = *((double*)(PyArray_GETPTR2(coordinates_arr, 2, 0))); - points[2].y = *((double*)(PyArray_GETPTR2(coordinates_arr, 2, 1))); - - /* ------------------- Check colors array ----------------------------- */ - - colors_arr = (PyArrayObject*) PyArray_FromObject(colors, NPY_DOUBLE, 2, 2); - if (!colors_arr || - PyArray_DIM(colors_arr, 0) != 3 || PyArray_DIM(colors_arr, 1) != 4) - { - PyErr_SetString(PyExc_ValueError, "colors must by a 3x4 array"); - Py_DECREF(coordinates_arr); - Py_XDECREF(colors_arr); - return NULL; - } - - /* ----- Draw the gradients separately for each color component ------- */ - CGContextSaveGState(cr); - CGContextMoveToPoint(cr, points[0].x, points[0].y); - CGContextAddLineToPoint(cr, points[1].x, points[1].y); - CGContextAddLineToPoint(cr, points[2].x, points[2].y); - CGContextClip(cr); - intensity[0] = *((double*)(PyArray_GETPTR2(colors_arr, 0, 3))); - intensity[1] = *((double*)(PyArray_GETPTR2(colors_arr, 1, 3))); - intensity[2] = *((double*)(PyArray_GETPTR2(colors_arr, 2, 3))); - if (_shade_alpha(cr, intensity, points)!=-1) { - CGContextBeginTransparencyLayer(cr, NULL); - CGContextSetBlendMode(cr, kCGBlendModeScreen); - for (i = 0; i < 3; i++) - { - intensity[0] = *((double*)(PyArray_GETPTR2(colors_arr, 0, i))); - intensity[1] = *((double*)(PyArray_GETPTR2(colors_arr, 1, i))); - intensity[2] = *((double*)(PyArray_GETPTR2(colors_arr, 2, i))); - if (!_shade_one_color(cr, intensity, points, i)) break; - } - CGContextEndTransparencyLayer(cr); - } - CGContextRestoreGState(cr); - - Py_DECREF(coordinates_arr); - Py_DECREF(colors_arr); - - if (i < 3) /* break encountered */ - { - PyErr_SetString(PyExc_MemoryError, "insufficient memory in draw_gouraud_triangle"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -#ifdef COMPILING_FOR_10_5 -static CTFontRef -#else -static ATSFontRef -#endif -setfont(CGContextRef cr, PyObject* family, float size, const char weight[], - const char italic[]) -{ -#define NMAP 40 -#define NFONT 31 - int i, j, n; - const char* temp; - const char* name = "Times-Roman"; - CFStringRef string; -#ifdef COMPILING_FOR_10_5 - CTFontRef font = 0; -#else - ATSFontRef font = 0; -#endif - PyObject* ascii = NULL; - - const int k = (strcmp(italic, "italic") ? 0 : 2) - + (strcmp(weight, "bold") ? 0 : 1); - - struct {char* name; int index;} map[NMAP] = { - {"New Century Schoolbook", 0}, - {"Century Schoolbook L", 0}, - {"Utopia", 1}, - {"ITC Bookman", 2}, - {"Bookman", 2}, - {"Bitstream Vera Serif", 3}, - {"Nimbus Roman No9 L", 4}, - {"Times New Roman", 5}, - {"Times", 6}, - {"Palatino", 7}, - {"Charter", 8}, - {"serif", 0}, - {"Lucida Grande", 9}, - {"Verdana", 10}, - {"Geneva", 11}, - {"Lucida", 12}, - {"Bitstream Vera Sans", 13}, - {"Arial", 14}, - {"Helvetica", 15}, - {"Avant Garde", 16}, - {"sans-serif", 15}, - {"Apple Chancery", 17}, - {"Textile", 18}, - {"Zapf Chancery", 19}, - {"Sand", 20}, - {"cursive", 17}, - {"Comic Sans MS", 21}, - {"Chicago", 22}, - {"Charcoal", 23}, - {"Impact", 24}, - {"Western", 25}, - {"fantasy", 21}, - {"Andale Mono", 26}, - {"Bitstream Vera Sans Mono", 27}, - {"Nimbus Mono L", 28}, - {"Courier", 29}, - {"Courier New", 30}, - {"Fixed", 30}, - {"Terminal", 30}, - {"monospace", 30}, - }; - - const char* psnames[NFONT][4] = { - {"CenturySchoolbook", /* 0 */ - "CenturySchoolbook-Bold", - "CenturySchoolbook-Italic", - "CenturySchoolbook-BoldItalic"}, - {"Utopia", /* 1 */ - "Utopia-Bold", - "Utopia-Italic", - "Utopia-BoldItalic"}, - {"Bookman-Light", /* 2 */ - "Bookman-Bold", - "Bookman-LightItalic", - "Bookman-BoldItalic"}, - {"BitstreamVeraSerif-Roman", /* 3 */ - "BitstreamVeraSerif-Bold", - "", - ""}, - {"NimbusRomNo9L-Reg", /* 4 */ - "NimbusRomNo9T-Bol", - "NimbusRomNo9L-RegIta", - "NimbusRomNo9T-BolIta"}, - {"TimesNewRomanPSMT", /* 5 */ - "TimesNewRomanPS-BoldMT", - "TimesNewRomanPS-ItalicMT", - "TimesNewRomanPS-BoldItalicMT"}, - {"Times-Roman", /* 6 */ - "Times-Bold", - "Times-Italic", - "Times-BoldItalic"}, - {"Palatino-Roman", /* 7 */ - "Palatino-Bold", - "Palatino-Italic", - "Palatino-BoldItalic"}, - {"CharterBT-Roman", /* 8 */ - "CharterBT-Bold", - "CharterBT-Italic", - "CharterBT-BoldItalic"}, - {"LucidaGrande", /* 9 */ - "LucidaGrande-Bold", - "", - ""}, - {"Verdana", /* 10 */ - "Verdana-Bold", - "Verdana-Italic", - "Verdana-BoldItalic"}, - {"Geneva", /* 11 */ - "", - "", - ""}, - {"LucidaSans", /* 12 */ - "LucidaSans-Demi", - "LucidaSans-Italic", - "LucidaSans-DemiItalic"}, - {"BitstreamVeraSans-Roman", /* 13 */ - "BitstreamVeraSans-Bold", - "BitstreamVeraSans-Oblique", - "BitstreamVeraSans-BoldOblique"}, - {"ArialMT", /* 14 */ - "Arial-BoldMT", - "Arial-ItalicMT", - "Arial-BoldItalicMT"}, - {"Helvetica", /* 15 */ - "Helvetica-Bold", - "Arial-ItalicMT", - "Arial-BoldItalicMT"}, - {"AvantGardeITC-Book", /* 16 */ - "AvantGardeITC-Demi", - "AvantGardeITC-BookOblique", - "AvantGardeITC-DemiOblique"}, - {"Apple-Chancery", /* 17 */ - "", - "", - ""}, - {"TextileRegular", /* 18 */ - "", - "", - ""}, - {"ZapfChancery-Roman", /* 19 */ - "ZapfChancery-Bold", - "ZapfChancery-Italic", - "ZapfChancery-MediumItalic"}, - {"SandRegular", /* 20 */ - "", - "", - ""}, - {"ComicSansMS", /* 21 */ - "ComicSansMS-Bold", - "", - ""}, - {"Chicago", /* 22 */ - "", - "", - ""}, - {"Charcoal", /* 23 */ - "", - "", - ""}, - {"Impact", /* 24 */ - "", - "", - ""}, - {"Playbill", /* 25 */ - "", - "", - ""}, - {"AndaleMono", /* 26 */ - "", - "", - ""}, - {"BitstreamVeraSansMono-Roman", /* 27 */ - "BitstreamVeraSansMono-Bold", - "BitstreamVeraSansMono-Oblique", - "BitstreamVeraSansMono-BoldOb"}, - {"NimbusMonL-Reg", /* 28 */ - "NimbusMonL-Bol", - "NimbusMonL-RegObl", - "NimbusMonL-BolObl"}, - {"Courier", /* 29 */ - "Courier-Bold", - "", - ""}, - {"CourierNewPS", /* 30 */ - "CourierNewPS-BoldMT", - "CourierNewPS-ItalicMT", - "CourierNewPS-Bold-ItalicMT"}, - }; - - if(!PyList_Check(family)) - { - PyErr_SetString(PyExc_ValueError, "family should be a list"); - return 0; - } - n = PyList_GET_SIZE(family); - - for (i = 0; i < n; i++) - { - PyObject* item = PyList_GET_ITEM(family, i); - ascii = PyUnicode_AsASCIIString(item); - if(!ascii) - { - PyErr_SetString(PyExc_ValueError, - "failed to convert font family name to ASCII"); - return 0; - } - temp = PyBytes_AS_STRING(ascii); - for (j = 0; j < NMAP; j++) - { if (!strcmp(map[j].name, temp)) - { temp = psnames[map[j].index][k]; - break; - } - } - /* If the font name is not found in mapping, we assume */ - /* that the user specified the Postscript name directly */ - - /* Check if this font can be found on the system */ - string = CFStringCreateWithCString(kCFAllocatorDefault, - temp, - kCFStringEncodingMacRoman); -#ifdef COMPILING_FOR_10_5 - font = CTFontCreateWithName(string, size, NULL); -#else - font = ATSFontFindFromPostScriptName(string, kATSOptionFlagsDefault); -#endif - - CFRelease(string); - - if(font) - { - name = temp; - break; - } - Py_DECREF(ascii); - ascii = NULL; - } - if(!font) - { string = CFStringCreateWithCString(kCFAllocatorDefault, - name, - kCFStringEncodingMacRoman); -#ifdef COMPILING_FOR_10_5 - font = CTFontCreateWithName(string, size, NULL); -#else - font = ATSFontFindFromPostScriptName(string, kATSOptionFlagsDefault); -#endif - CFRelease(string); - } - if (!font) - { - PyErr_SetString(PyExc_ValueError, "Could not load font"); - } -#ifndef COMPILING_FOR_10_5 - else { - CGContextSelectFont(cr, name, size, kCGEncodingMacRoman); - } -#endif -#if PY3K - Py_XDECREF(ascii); -#endif - return font; -} - -#ifdef COMPILING_FOR_10_5 -static PyObject* -GraphicsContext_draw_text (GraphicsContext* self, PyObject* args) -{ - float x; - float y; - int n; - PyObject* family; - float size; - const char* weight; - const char* italic; - float angle; - CTFontRef font; - CGFloat descent; -#if PY33 - const char* text; -#else - const UniChar* text; -#endif - - CFStringRef keys[2]; - CFTypeRef values[2]; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } -#if PY33 - if(!PyArg_ParseTuple(args, "ffs#Ofssf", - &x, - &y, - &text, - &n, - &family, - &size, - &weight, - &italic, - &angle)) return NULL; - CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8); -#else - if(!PyArg_ParseTuple(args, "ffu#Ofssf", - &x, - &y, - &text, - &n, - &family, - &size, - &weight, - &italic, - &angle)) return NULL; - CFStringRef s = CFStringCreateWithCharacters(kCFAllocatorDefault, text, n); -#endif - - if (!(font = setfont(cr, family, size, weight, italic))) - { - CFRelease(s); - return NULL; - } - - keys[0] = kCTFontAttributeName; - keys[1] = kCTForegroundColorFromContextAttributeName; - values[0] = font; - values[1] = kCFBooleanTrue; - CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault, - (const void**)&keys, - (const void**)&values, - 2, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - CFRelease(font); - - CFAttributedStringRef string = CFAttributedStringCreate(kCFAllocatorDefault, - s, - attributes); - CFRelease(s); - CFRelease(attributes); - - CTLineRef line = CTLineCreateWithAttributedString(string); - CFRelease(string); - - CTLineGetTypographicBounds(line, NULL, &descent, NULL); - - if (!line) - { - PyErr_SetString(PyExc_RuntimeError, - "CTLineCreateWithAttributedString failed"); - return NULL; - } - - CGContextSetTextMatrix(cr, CGAffineTransformIdentity); - if (angle) - { - CGContextSaveGState(cr); - CGContextTranslateCTM(cr, x, y); - CGContextRotateCTM(cr, angle*M_PI/180); - CTLineDraw(line, cr); - CGContextRestoreGState(cr); - } - else - { - CGContextSetTextPosition(cr, x, y); - CTLineDraw(line, cr); - } - CFRelease(line); - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_get_text_width_height_descent(GraphicsContext* self, PyObject* args) -{ - int n; - PyObject* family; - float size; - const char* weight; - const char* italic; - -#if PY33 - const char* text; -#else - const UniChar* text; -#endif - - float descent; - float width; - float height; - - CGRect rect; - CGPoint point; - - CTFontRef font; - - char data[8]; - - CGContextRef cr = self->cr; - if (!cr) - { - CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray(); - if (!colorspace) { - PyErr_SetString(PyExc_MemoryError, "Failed to create color space"); - goto exit; - } - cr = CGBitmapContextCreate(data, - 1, - 1, - 8, - 1, - colorspace, - 0); - CGColorSpaceRelease(colorspace); - if (!cr) { - PyErr_SetString(PyExc_MemoryError, "Failed to create bitmap context"); - goto exit; - } - } - -#if PY33 - if(!PyArg_ParseTuple(args, "s#Ofss", - &text, - &n, - &family, - &size, - &weight, - &italic)) goto exit; - CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8); -#else - if(!PyArg_ParseTuple(args, "u#Ofss", - &text, - &n, - &family, - &size, - &weight, - &italic)) return NULL; - CFStringRef s = CFStringCreateWithCharacters(kCFAllocatorDefault, text, n); -#endif - - if (!(font = setfont(cr, family, size, weight, italic))) - { - CFRelease(s); - goto exit; - }; - - CFStringRef keys[1]; - CFTypeRef values[1]; - - keys[0] = kCTFontAttributeName; - values[0] = font; - CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault, - (const void**)&keys, - (const void**)&values, - 1, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - CFRelease(font); - - CFAttributedStringRef string = CFAttributedStringCreate(kCFAllocatorDefault, - s, - attributes); - CFRelease(s); - CFRelease(attributes); - - CTLineRef line = CTLineCreateWithAttributedString(string); - CFRelease(string); - - if (!line) - { - PyErr_SetString(PyExc_RuntimeError, - "CTLineCreateWithAttributedString failed"); - goto exit; - } - - point = CGContextGetTextPosition(cr); - rect = CTLineGetImageBounds(line, cr); - CFRelease(line); - if (!self->cr) CGContextRelease(cr); - - width = rect.size.width; - height = rect.size.height; - descent = point.y - rect.origin.y; - - return Py_BuildValue("fff", width, height, descent); -exit: - if (cr && !self->cr) CGContextRelease(cr); - return NULL; -} - -#else // Text drawing for OSX versions <10.5 - -static PyObject* -GraphicsContext_draw_text (GraphicsContext* self, PyObject* args) -{ - float x; - float y; - const UniChar* text; - int n; - PyObject* family; - float size; - const char* weight; - const char* italic; - float angle; - ATSFontRef atsfont; - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - if(!PyArg_ParseTuple(args, "ffu#Ofssf", - &x, - &y, - &text, - &n, - &family, - &size, - &weight, - &italic, - &angle)) return NULL; - - if (!(atsfont = setfont(cr, family, size, weight, italic))) - { - return NULL; - } - - OSStatus status; - - ATSUAttributeTag tags[] = {kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag}; - ByteCount sizes[] = {sizeof(ATSUFontID), sizeof(Fixed), sizeof(Boolean)}; - Fixed atsuSize = Long2Fix(size); - Boolean isBold = FALSE; /* setfont takes care of this */ - - ATSUAttributeValuePtr values[] = {&atsfont, &atsuSize, &isBold}; - status = ATSUSetAttributes(style, 3, tags, sizes, values); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUSetAttributes failed"); - return NULL; - } - - status = ATSUSetTextPointerLocation(layout, - text, - kATSUFromTextBeginning, /* offset from beginning */ - kATSUToTextEnd, /* length of text range */ - n); /* length of text buffer */ - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, - "ATSUCreateTextLayoutWithTextPtr failed"); - return NULL; - } - - status = ATSUSetRunStyle(layout, - style, - kATSUFromTextBeginning, - kATSUToTextEnd); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUSetRunStyle failed"); - return NULL; - } - - Fixed atsuAngle = X2Fix(angle); - ATSUAttributeTag tags2[] = {kATSUCGContextTag, kATSULineRotationTag}; - ByteCount sizes2[] = {sizeof (CGContextRef), sizeof(Fixed)}; - ATSUAttributeValuePtr values2[] = {&cr, &atsuAngle}; - status = ATSUSetLayoutControls(layout, 2, tags2, sizes2, values2); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUSetLayoutControls failed"); - return NULL; - } - - status = ATSUDrawText(layout, - kATSUFromTextBeginning, - kATSUToTextEnd, - X2Fix(x), - X2Fix(y)); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUDrawText failed"); - return NULL; - } - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_get_text_width_height_descent(GraphicsContext* self, PyObject* args) -{ - const UniChar* text; - int n; - PyObject* family; - float size; - const char* weight; - const char* italic; - - ATSFontRef atsfont; - Rect rect; - - char data[8]; - - CGContextRef cr = self->cr; - if (!cr) - { - CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray(); - if (!colorspace) { - PyErr_SetString(PyExc_MemoryError, "Failed to create color space"); - goto exit; - } - cr = CGBitmapContextCreate(data, - 1, - 1, - 8, - 1, - colorspace, - 0); - CGColorSpaceRelease(colorspace); - if (!cr) { - PyErr_SetString(PyExc_MemoryError, "Failed to create bitmap context"); - goto exit; - } - } - - if(!PyArg_ParseTuple(args, "u#Ofss", &text, &n, &family, &size, &weight, &italic)) goto exit; - - if (!(atsfont = setfont(cr, family, size, weight, italic))) goto exit; - - OSStatus status = noErr; - ATSUAttributeTag tags[] = {kATSUFontTag, - kATSUSizeTag, - kATSUQDBoldfaceTag, - kATSUQDItalicTag}; - ByteCount sizes[] = {sizeof(ATSUFontID), - sizeof(Fixed), - sizeof(Boolean), - sizeof(Boolean)}; - Fixed atsuSize = Long2Fix(size); - Boolean isBold = FALSE; /* setfont takes care of this */ - Boolean isItalic = FALSE; /* setfont takes care of this */ - ATSUAttributeValuePtr values[] = {&atsfont, &atsuSize, &isBold, &isItalic}; - - status = ATSUSetAttributes(style, 4, tags, sizes, values); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUSetAttributes failed"); - goto exit; - } - - status = ATSUSetTextPointerLocation(layout, - text, - kATSUFromTextBeginning, /* offset from beginning */ - kATSUToTextEnd, /* length of text range */ - n); /* length of text buffer */ - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, - "ATSUCreateTextLayoutWithTextPtr failed"); - goto exit; - } - - status = ATSUSetRunStyle(layout, - style, - kATSUFromTextBeginning, - kATSUToTextEnd); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUSetRunStyle failed"); - goto exit; - } - - ATSUAttributeTag tag = kATSUCGContextTag; - ByteCount bc = sizeof (CGContextRef); - ATSUAttributeValuePtr value = &cr; - status = ATSUSetLayoutControls(layout, 1, &tag, &bc, &value); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUSetLayoutControls failed"); - goto exit; - } - - status = ATSUMeasureTextImage(layout, - kATSUFromTextBeginning, kATSUToTextEnd, - 0, 0, &rect); - if (status!=noErr) - { - PyErr_SetString(PyExc_RuntimeError, "ATSUMeasureTextImage failed"); - goto exit; - } - - const float width = rect.right-rect.left; - const float height = rect.bottom-rect.top; - const float descent = rect.bottom; - - return Py_BuildValue("fff", width, height, descent); -exit: - if (cr && !self->cr) CGContextRelease(cr); - return NULL; -} -#endif - -static void _data_provider_release(void* info, const void* data, size_t size) -{ - PyObject* image = (PyObject*)info; - Py_DECREF(image); -} - - - - -static PyObject* -GraphicsContext_draw_mathtext(GraphicsContext* self, PyObject* args) -{ - float x, y, angle; - npy_intp nrows, ncols; - int n; - - PyObject* object; - PyArrayObject* image; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "fffO", &x, &y, &angle, &object)) return NULL; - - /* ------------- Check the image ---------------------------- */ - if(!PyArray_Check (object)) - { - PyErr_SetString(PyExc_TypeError, "image should be a NumPy array."); - return NULL; - } - image = (PyArrayObject*) object; - if(PyArray_NDIM(image) != 2) - { - PyErr_Format(PyExc_TypeError, - "image has incorrect rank (%d expected 2)", - PyArray_NDIM(image)); - return NULL; - } - if (PyArray_TYPE(image) != NPY_UBYTE) - { - PyErr_SetString(PyExc_TypeError, - "image has incorrect type (should be uint8)"); - return NULL; - } - if (!PyArray_ISCONTIGUOUS(image)) - { - PyErr_SetString(PyExc_TypeError, "image array is not contiguous"); - return NULL; - } - - nrows = PyArray_DIM(image, 0); - ncols = PyArray_DIM(image, 1); - if (nrows != (int) nrows || ncols != (int) ncols) - { - PyErr_SetString(PyExc_RuntimeError, "bitmap image too large"); - return NULL; - } - n = nrows * ncols; - Py_INCREF(object); - - const size_t bytesPerComponent = 1; - const size_t bitsPerComponent = 8 * bytesPerComponent; - const size_t nComponents = 1; /* gray */ - const size_t bitsPerPixel = bitsPerComponent * nComponents; - const size_t bytesPerRow = nComponents * bytesPerComponent * ncols; - CGDataProviderRef provider = CGDataProviderCreateWithData(object, - PyArray_DATA(image), - n, - _data_provider_release); - CGImageRef bitmap = CGImageMaskCreate ((int) ncols, - (int) nrows, - bitsPerComponent, - bitsPerPixel, - bytesPerRow, - provider, - NULL, - false); - CGDataProviderRelease(provider); - - if(!bitmap) - { - PyErr_SetString(PyExc_RuntimeError, "CGImageMaskCreate failed"); - return NULL; - } - - CGFloat deviceScale = _get_device_scale(cr); - - if (angle==0.0) - { - CGContextDrawImage(cr, CGRectMake(x, y, ncols/deviceScale, nrows/deviceScale), bitmap); - } - else - { - CGContextSaveGState(cr); - CGContextTranslateCTM(cr, x, y); - CGContextRotateCTM(cr, angle*M_PI/180); - CGContextDrawImage(cr, CGRectMake(0, 0, ncols/deviceScale, nrows/deviceScale), bitmap); - CGContextRestoreGState(cr); - } - CGImageRelease(bitmap); - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -GraphicsContext_draw_image(GraphicsContext* self, PyObject* args) -{ - float x, y; - int nrows, ncols; - const char* data; - int n; - PyObject* image; - - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } - - if(!PyArg_ParseTuple(args, "ffiiO", &x, - &y, - &nrows, - &ncols, - &image)) return NULL; - - CGColorSpaceRef colorspace; - CGDataProviderRef provider; - - if (!PyBytes_Check(image)) - { -#if PY3K - PyErr_SetString(PyExc_RuntimeError, "image is not a bytes object"); -#else - PyErr_SetString(PyExc_RuntimeError, "image is not a str object"); -#endif - return NULL; - } - - const size_t bytesPerComponent = 1; - const size_t bitsPerComponent = 8 * bytesPerComponent; - const size_t nComponents = 4; /* red, green, blue, alpha */ - const size_t bitsPerPixel = bitsPerComponent * nComponents; - const size_t bytesPerRow = nComponents * bytesPerComponent * ncols; - - colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); - if (!colorspace) - { - PyErr_SetString(PyExc_RuntimeError, "failed to create a color space"); - return NULL; - } - - Py_INCREF(image); -#ifdef PY3K - n = PyBytes_GET_SIZE(image); - data = PyBytes_AS_STRING(image); -#else - n = PyString_GET_SIZE(image); - data = PyString_AS_STRING(image); -#endif - - provider = CGDataProviderCreateWithData(image, - data, - n, - _data_provider_release); - - CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast; - CGImageRef bitmap = CGImageCreate (ncols, - nrows, - bitsPerComponent, - bitsPerPixel, - bytesPerRow, - colorspace, - bitmapInfo, - provider, - NULL, - false, - kCGRenderingIntentDefault); - CGColorSpaceRelease(colorspace); - CGDataProviderRelease(provider); - - if(!bitmap) - { - PyErr_SetString(PyExc_RuntimeError, "CGImageMaskCreate failed"); - return NULL; - } - - CGFloat deviceScale = _get_device_scale(cr); - - CGContextSaveGState(cr); - CGContextTranslateCTM(cr, 0, y + nrows/deviceScale); - CGContextScaleCTM(cr, 1.0, -1.0); - - CGContextDrawImage(cr, CGRectMake(x, 0, ncols/deviceScale, nrows/deviceScale), bitmap); - CGImageRelease(bitmap); - - CGContextRestoreGState(cr); - - Py_INCREF(Py_None); - return Py_None; -} +- (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager; +- (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen; +- (BOOL)closeButtonPressed; +- (void)dealloc; +@end -static PyObject* -GraphicsContext_get_image_magnification(GraphicsContext* self) +@interface ToolWindow : NSWindow { - CGContextRef cr = self->cr; - if (!cr) - { - PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); - return NULL; - } +} +- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window; +- (void)masterCloses:(NSNotification*)notification; +- (void)close; +@end - return PyFloat_FromDouble(_get_device_scale(cr)); +#ifdef COMPILING_FOR_10_6 +@interface View : NSView +#else +@interface View : NSView +#endif +{ PyObject* canvas; + NSRect rubberband; + BOOL inside; + NSTrackingRectTag tracking; + @public double device_scale; } +- (void)dealloc; +- (void)drawRect:(NSRect)rect; +- (void)windowDidResize:(NSNotification*)notification; +- (View*)initWithFrame:(NSRect)rect; +- (void)setCanvas: (PyObject*)newCanvas; +- (void)windowWillClose:(NSNotification*)notification; +- (BOOL)windowShouldClose:(NSNotification*)notification; +- (BOOL)isFlipped; +- (void)mouseEntered:(NSEvent*)event; +- (void)mouseExited:(NSEvent*)event; +- (void)mouseDown:(NSEvent*)event; +- (void)mouseUp:(NSEvent*)event; +- (void)mouseDragged:(NSEvent*)event; +- (void)mouseMoved:(NSEvent*)event; +- (void)rightMouseDown:(NSEvent*)event; +- (void)rightMouseUp:(NSEvent*)event; +- (void)rightMouseDragged:(NSEvent*)event; +- (void)otherMouseDown:(NSEvent*)event; +- (void)otherMouseUp:(NSEvent*)event; +- (void)otherMouseDragged:(NSEvent*)event; +- (void)setRubberband:(NSRect)rect; +- (void)removeRubberband; +- (const char*)convertKeyEvent:(NSEvent*)event; +- (void)keyDown:(NSEvent*)event; +- (void)keyUp:(NSEvent*)event; +- (void)scrollWheel:(NSEvent *)event; +- (BOOL)acceptsFirstResponder; +//- (void)flagsChanged:(NSEvent*)event; +@end +@interface ScrollableButton : NSButton +{ + SEL scrollWheelUpAction; + SEL scrollWheelDownAction; +} +- (void)setScrollWheelUpAction:(SEL)action; +- (void)setScrollWheelDownAction:(SEL)action; +- (void)scrollWheel:(NSEvent *)event; +@end -static PyMethodDef GraphicsContext_methods[] = { - {"save", - (PyCFunction)GraphicsContext_save, - METH_NOARGS, - "Saves the current graphics context onto the stack." - }, - {"restore", - (PyCFunction)GraphicsContext_restore, - METH_NOARGS, - "Restores the current graphics context from the stack." - }, - {"get_text_width_height_descent", - (PyCFunction)GraphicsContext_get_text_width_height_descent, - METH_VARARGS, - "Returns the width, height, and descent of the text." - }, - {"set_alpha", - (PyCFunction)GraphicsContext_set_alpha, - METH_VARARGS, - "Sets the opacitiy level for objects drawn in a graphics context" - }, - {"set_antialiased", - (PyCFunction)GraphicsContext_set_antialiased, - METH_VARARGS, - "Sets anti-aliasing on or off for a graphics context." - }, - {"set_capstyle", - (PyCFunction)GraphicsContext_set_capstyle, - METH_VARARGS, - "Sets the style for the endpoints of lines in a graphics context." - }, - {"set_clip_rectangle", - (PyCFunction)GraphicsContext_set_clip_rectangle, - METH_VARARGS, - "Sets the clipping path to the area defined by the specified rectangle." - }, - {"set_clip_path", - (PyCFunction)GraphicsContext_set_clip_path, - METH_VARARGS, - "Sets the clipping path." - }, - {"set_dashes", - (PyCFunction)GraphicsContext_set_dashes, - METH_VARARGS, - "Sets the pattern for dashed lines in a graphics context." - }, - {"set_foreground", - (PyCFunction)GraphicsContext_set_foreground, - METH_VARARGS, - "Sets the current stroke and fill color to a value in the DeviceRGB color space." - }, - {"set_graylevel", - (PyCFunction)GraphicsContext_set_graylevel, - METH_VARARGS, - "Sets the current stroke and fill color to a value in the DeviceGray color space." - }, - {"set_dpi", - (PyCFunction)GraphicsContext_set_dpi, - METH_VARARGS, - "Sets the dpi for a graphics context." - }, - {"set_linewidth", - (PyCFunction)GraphicsContext_set_linewidth, - METH_VARARGS, - "Sets the line width for a graphics context." - }, - {"set_joinstyle", - (PyCFunction)GraphicsContext_set_joinstyle, - METH_VARARGS, - "Sets the style for the joins of connected lines in a graphics context." - }, - {"draw_path", - (PyCFunction)GraphicsContext_draw_path, - METH_VARARGS, - "Draw a path in the graphics context and strokes and (if rgbFace is not None) fills it." - }, - {"draw_markers", - (PyCFunction)GraphicsContext_draw_markers, - METH_VARARGS, - "Draws a marker in the graphics context at each of the vertices in path." - }, - {"draw_path_collection", - (PyCFunction)GraphicsContext_draw_path_collection, - METH_VARARGS, - "Draw a collection of paths in the graphics context." - }, - {"draw_quad_mesh", - (PyCFunction)GraphicsContext_draw_quad_mesh, - METH_VARARGS, - "Draws a mesh in the graphics context." - }, - {"draw_gouraud_triangle", - (PyCFunction)GraphicsContext_draw_gouraud_triangle, - METH_VARARGS, - "Draws a Gouraud-shaded triangle in the graphics context." - }, - {"draw_text", - (PyCFunction)GraphicsContext_draw_text, - METH_VARARGS, - "Draw a string at (x,y) with the given properties in the graphics context." - }, - {"draw_mathtext", - (PyCFunction)GraphicsContext_draw_mathtext, - METH_VARARGS, - "Draw a TeX string at (x,y) as a bitmap in the graphics context." - }, - {"draw_image", - (PyCFunction)GraphicsContext_draw_image, - METH_VARARGS, - "Draw an image at (x,y) in the graphics context." - }, - {"get_image_magnification", - (PyCFunction)GraphicsContext_get_image_magnification, - METH_NOARGS, - "Returns the scale factor between user and device coordinates." - }, - {NULL} /* Sentinel */ -}; +@interface MenuItem: NSMenuItem +{ int index; +} ++ (MenuItem*)menuItemWithTitle:(NSString*)title; ++ (MenuItem*)menuItemSelectAll; ++ (MenuItem*)menuItemInvertAll; ++ (MenuItem*)menuItemForAxis:(int)i; +- (void)toggle:(id)sender; +- (void)selectAll:(id)sender; +- (void)invertAll:(id)sender; +- (int)index; +@end -static char GraphicsContext_doc[] = -"A GraphicsContext object wraps a Quartz 2D graphics context\n" -"(CGContextRef). Most methods either draw into the graphics context\n" -"(moveto, lineto, etc.) or set the drawing properties (set_linewidth,\n" -"set_joinstyle, etc.).\n"; +/* ---------------------------- Python classes ---------------------------- */ -static PyTypeObject GraphicsContextType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_macosx.GraphicsContext", /*tp_name*/ - sizeof(GraphicsContext), /*tp_basicsize*/ - 0, /*tp_itemsize*/ -#ifdef COMPILING_FOR_10_5 - 0, /*tp_dealloc*/ -#else - (destructor)GraphicsContext_dealloc, /*tp_dealloc*/ -#endif - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)GraphicsContext_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - GraphicsContext_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - GraphicsContext_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - GraphicsContext_new, /* tp_new */ -}; +static CGFloat _get_device_scale(CGContextRef cr) +{ + CGSize pixelSize = CGContextConvertSizeToDeviceSpace(cr, CGSizeMake(1, 1)); + return pixelSize.width; +} typedef struct { PyObject_HEAD @@ -3586,6 +408,12 @@ static void _data_provider_release(void* info, const void* data, size_t size) return NULL; } if(!PyArg_ParseTuple(args, "iiii", &x0, &y0, &x1, &y1)) return NULL; + + x0 /= view->device_scale; + x1 /= view->device_scale; + y0 /= view->device_scale; + y1 /= view->device_scale; + if (x1 > x0) { rubberband.origin.x = x0; @@ -3606,6 +434,7 @@ static void _data_provider_release(void* info, const void* data, size_t size) rubberband.origin.y = y1; rubberband.size.height = y0 - y1; } + [view setRubberband: rubberband]; Py_INCREF(Py_None); return Py_None; @@ -3665,90 +494,6 @@ static void _data_provider_release(void* info, const void* data, size_t size) return image; } -static PyObject* -FigureCanvas_write_bitmap(FigureCanvas* self, PyObject* args) -{ - View* view = self->view; - int n; - const unichar* characters; - NSSize size; - double width, height, dpi; - - if(!view) - { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); - return NULL; - } - /* NSSize contains CGFloat; cannot use size directly */ - if(!PyArg_ParseTuple(args, "u#ddd", - &characters, &n, &width, &height, &dpi)) return NULL; - size.width = width; - size.height = height; - - /* This function may be called from inside the event loop, when an - * autorelease pool is available, or from Python, when no autorelease - * pool is available. To be able to handle the latter case, we need to - * create an autorelease pool here. */ - - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - - NSRect rect = [view bounds]; - - NSString* filename = [NSString stringWithCharacters: characters - length: (unsigned)n]; - NSString* extension = [filename pathExtension]; - - /* Calling dataWithPDFInsideRect on the view causes its update status - * to be cleared. Save the status here, and invalidate the view if not - * up to date after calling dataWithPDFInsideRect. */ - const BOOL invalid = [view needsDisplay]; - NSData* data = [view dataWithPDFInsideRect: rect]; - if (invalid) [view setNeedsDisplay: YES]; - - NSImage* image = [[NSImage alloc] initWithData: data]; - NSImage *resizedImage = [[NSImage alloc] initWithSize:size]; - - [resizedImage lockFocus]; - [image drawInRect:NSMakeRect(0, 0, width, height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; - [resizedImage unlockFocus]; - data = [resizedImage TIFFRepresentation]; - [image release]; - [resizedImage release]; - - NSBitmapImageRep* rep = [NSBitmapImageRep imageRepWithData:data]; - - NSSize pxlSize = NSMakeSize([rep pixelsWide], [rep pixelsHigh]); - NSSize newSize = NSMakeSize(72.0 * pxlSize.width / dpi, 72.0 * pxlSize.height / dpi); - - [rep setSize:newSize]; - - NSBitmapImageFileType filetype; - if ([extension isEqualToString: @"bmp"]) - filetype = NSBMPFileType; - else if ([extension isEqualToString: @"gif"]) - filetype = NSGIFFileType; - else if ([extension isEqualToString: @"jpg"] || - [extension isEqualToString: @"jpeg"]) - filetype = NSJPEGFileType; - else if ([extension isEqualToString: @"png"]) - filetype = NSPNGFileType; - else if ([extension isEqualToString: @"tiff"] || - [extension isEqualToString: @"tif"]) - filetype = NSTIFFFileType; - else - { PyErr_SetString(PyExc_ValueError, "Unknown file type"); - return NULL; - } - - data = [rep representationUsingType:filetype properties:[NSDictionary dictionary]]; - - [data writeToFile: filename atomically: YES]; - [pool release]; - - Py_INCREF(Py_None); - return Py_None; -} - static PyObject* FigureCanvas_start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) { @@ -3864,12 +609,6 @@ static void _data_provider_release(void* info, const void* data, size_t size) METH_NOARGS, "Removes the current rubberband rectangle." }, - {"write_bitmap", - (PyCFunction)FigureCanvas_write_bitmap, - METH_VARARGS, - "Saves the figure to the specified file as a bitmap\n" - "(bmp, gif, jpeg, or png).\n" - }, {"start_event_loop", (PyCFunction)FigureCanvas_start_event_loop, METH_KEYWORDS | METH_VARARGS, @@ -4099,11 +838,7 @@ static void _data_provider_release(void* info, const void* data, size_t size) NSString* title = [window title]; if (title) { const char* cTitle = [title UTF8String]; -#if PY3K || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6) result = PyUnicode_FromString(cTitle); -#else - result = PyString_FromString(cTitle); -#endif } [pool release]; } @@ -5358,6 +2093,7 @@ - (View*)initWithFrame:(NSRect)rect rubberband = NSZeroRect; inside = false; tracking = 0; + device_scale = 1; return self; } @@ -5374,55 +2110,122 @@ - (void)setCanvas: (PyObject*)newCanvas canvas = newCanvas; } +static void _buffer_release(void* info, const void* data, size_t size) { + PyBuffer_Release((Py_buffer *)info); +} + +static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) +{ + Py_buffer buffer; + + if (PyObject_GetBuffer(renderer, &buffer, PyBUF_CONTIG_RO) == -1) { + PyErr_Print(); + return 1; + } + + if (buffer.ndim != 3 || buffer.shape[2] != 4) { + PyBuffer_Release(&buffer); + return 1; + } + + const Py_ssize_t nrows = buffer.shape[0]; + const Py_ssize_t ncols = buffer.shape[1]; + const size_t bytesPerComponent = 1; + const size_t bitsPerComponent = 8 * bytesPerComponent; + const size_t nComponents = 4; /* red, green, blue, alpha */ + const size_t bitsPerPixel = bitsPerComponent * nComponents; + const size_t bytesPerRow = nComponents * bytesPerComponent * ncols; + + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + if (!colorspace) { + PyBuffer_Release(&buffer); + return 1; + } + + CGDataProviderRef provider = CGDataProviderCreateWithData(&buffer, + buffer.buf, + buffer.len, + _buffer_release); + if (!provider) { + PyBuffer_Release(&buffer); + CGColorSpaceRelease(colorspace); + return 1; + } + + CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast; + CGImageRef bitmap = CGImageCreate(ncols, + nrows, + bitsPerComponent, + bitsPerPixel, + bytesPerRow, + colorspace, + bitmapInfo, + provider, + NULL, + false, + kCGRenderingIntentDefault); + CGColorSpaceRelease(colorspace); + CGDataProviderRelease(provider); + + if (!bitmap) { + PyBuffer_Release(&buffer); + return 1; + } + + CGFloat deviceScale = _get_device_scale(cr); + CGContextSaveGState(cr); + CGContextDrawImage(cr, CGRectMake(0, 0, ncols/deviceScale, nrows/deviceScale), bitmap); + CGImageRelease(bitmap); + CGContextRestoreGState(cr); + + return 0; +} + -(void)drawRect:(NSRect)rect { - PyObject* result; + PyObject* renderer = NULL; + PyObject* renderer_buffer = NULL; + PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* figure = PyObject_GetAttrString(canvas, "figure"); - if (!figure) - { - PyErr_Print(); - PyGILState_Release(gstate); - return; + CGContextRef cr = [[NSGraphicsContext currentContext] graphicsPort]; + + double new_device_scale = _get_device_scale(cr); + + if (device_scale != new_device_scale) { + device_scale = new_device_scale; + if (!PyObject_CallMethod(canvas, "_set_device_scale", "d", device_scale, NULL)) { + PyErr_Print(); + goto exit; + } } - PyObject* renderer = PyObject_GetAttrString(canvas, "renderer"); + + renderer = PyObject_CallMethod(canvas, "_draw", "", NULL); if (!renderer) { PyErr_Print(); - Py_DECREF(figure); - PyGILState_Release(gstate); - return; + goto exit; } - GraphicsContext* gc = (GraphicsContext*) PyObject_GetAttrString(renderer, "gc"); - if (!gc) - { + + renderer_buffer = PyObject_GetAttrString(renderer, "_renderer"); + if (!renderer_buffer) { PyErr_Print(); - Py_DECREF(figure); - Py_DECREF(renderer); - PyGILState_Release(gstate); - return; + goto exit; } - gc->size = [self frame].size; - - CGContextRef cr = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; - gc->cr = cr; - gc->level = 0; - - result = PyObject_CallMethod(figure, "draw", "O", renderer); - if(result) - Py_DECREF(result); - else - PyErr_Print(); + if (_copy_agg_buffer(cr, renderer_buffer)) { + printf("copy_agg_buffer failed\n"); + goto exit; + } - gc->cr = nil; - if (!NSIsEmptyRect(rubberband)) NSFrameRect(rubberband); + if (!NSIsEmptyRect(rubberband)) { + NSFrameRect(rubberband); + } - Py_DECREF(gc); - Py_DECREF(figure); - Py_DECREF(renderer); + exit: + Py_XDECREF(renderer_buffer); + Py_XDECREF(renderer); PyGILState_Release(gstate); } @@ -5441,7 +2244,8 @@ - (void)windowDidResize: (NSNotification*)notification [self setFrameSize: size]; PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallMethod(canvas, "resize", "ii", width, height); + PyObject* result = PyObject_CallMethod( + canvas, "resize", "ii", width, height); if(result) Py_DECREF(result); else @@ -5538,8 +2342,8 @@ - (void)mouseDown:(NSEvent *)event PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; switch ([event type]) { case NSLeftMouseDown: { unsigned int modifier = [event modifierFlags]; @@ -5582,8 +2386,8 @@ - (void)mouseUp:(NSEvent *)event PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; switch ([event type]) { case NSLeftMouseUp: num = 1; @@ -5609,8 +2413,8 @@ - (void)mouseMoved:(NSEvent *)event int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) @@ -5626,8 +2430,8 @@ - (void)mouseDragged:(NSEvent *)event int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) @@ -5647,8 +2451,8 @@ - (void)rightMouseDown:(NSEvent *)event PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; gstate = PyGILState_Ensure(); if ([event clickCount] == 2) { dblclick = 1; @@ -5670,8 +2474,8 @@ - (void)rightMouseUp:(NSEvent *)event PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); if(result) @@ -5687,8 +2491,8 @@ - (void)rightMouseDragged:(NSEvent *)event int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) @@ -5708,8 +2512,8 @@ - (void)otherMouseDown:(NSEvent *)event PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; gstate = PyGILState_Ensure(); if ([event clickCount] == 2) { dblclick = 1; @@ -5731,8 +2535,8 @@ - (void)otherMouseUp:(NSEvent *)event PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; gstate = PyGILState_Ensure(); result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); if(result) @@ -5748,8 +2552,8 @@ - (void)otherMouseDragged:(NSEvent *)event int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; - x = location.x; - y = location.y; + x = location.x * device_scale; + y = location.y * device_scale; PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); if(result) @@ -5888,8 +2692,8 @@ - (void)scrollWheel:(NSEvent*)event else return; NSPoint location = [event locationInWindow]; NSPoint point = [self convertPoint: location fromView: nil]; - int x = (int)round(point.x); - int y = (int)round(point.y - 1); + int x = (int)round(point.x * device_scale); + int y = (int)round(point.y * device_scale - 1); PyObject* result; PyGILState_STATE gstate = PyGILState_Ensure(); @@ -6333,10 +3137,8 @@ void init_macosx(void) #endif { PyObject *module; - import_array(); - if (PyType_Ready(&GraphicsContextType) < 0 - || PyType_Ready(&FigureCanvasType) < 0 + if (PyType_Ready(&FigureCanvasType) < 0 || PyType_Ready(&FigureManagerType) < 0 || PyType_Ready(&NavigationToolbarType) < 0 || PyType_Ready(&NavigationToolbar2Type) < 0 @@ -6367,13 +3169,11 @@ void init_macosx(void) PYTHON_API_VERSION); #endif - Py_INCREF(&GraphicsContextType); Py_INCREF(&FigureCanvasType); Py_INCREF(&FigureManagerType); Py_INCREF(&NavigationToolbarType); Py_INCREF(&NavigationToolbar2Type); Py_INCREF(&TimerType); - PyModule_AddObject(module, "GraphicsContext", (PyObject*) &GraphicsContextType); PyModule_AddObject(module, "FigureCanvas", (PyObject*) &FigureCanvasType); PyModule_AddObject(module, "FigureManager", (PyObject*) &FigureManagerType); PyModule_AddObject(module, "NavigationToolbar", (PyObject*) &NavigationToolbarType);