diff --git a/kiva/quartz/ABCGI.pyx b/kiva/quartz/ABCGI.pyx index d6fa365e7..12d0a21f1 100644 --- a/kiva/quartz/ABCGI.pyx +++ b/kiva/quartz/ABCGI.pyx @@ -1523,30 +1523,39 @@ cdef class CGBitmapContext(CGContext): def width(self): return CGBitmapContextGetWidth(self.context) - def __getsegcount__(self, void* tmp): - cdef int *lenp - lenp = tmp - if lenp != NULL: - lenp[0] = self.height()*self.bytes_per_row - return 1 - - def __getreadbuffer__(self, int segment, void** ptr): - # ignore invalid segment; the caller can't mean anything but the only - # segment available; we're all adults - ptr[0] = self.data - return self.height()*self.bytes_per_row - - def __getwritebuffer__(self, int segment, void** ptr): - # ignore invalid segment; the caller can't mean anything but the only - # segment available; we're all adults - ptr[0] = self.data - return self.height()*self.bytes_per_row - - def __getcharbuffer__(self, int segment, char** ptr): - # ignore invalid segment; the caller can't mean anything but the only - # segment available; we're all adults - ptr[0] = (self.data) - return self.height()*self.bytes_per_row + def __getbuffer__(self, Py_buffer *buffer, int flags): + """ When another object calls PyObject_GetBuffer on us. + """ + cdef Py_ssize_t* shape = PyMem_Malloc(2 * sizeof(Py_ssize_t)) + cdef Py_ssize_t* strides = PyMem_Malloc(2 * sizeof(Py_ssize_t)) + + shape[0] = self.bytes_per_row + shape[1] = self.height() + strides[0] = self.bytes_per_row + strides[1] = 1 + + buffer.buf = (self.data) + buffer.obj = self + buffer.len = self.height() * self.bytes_per_row + buffer.readonly = 1 + buffer.itemsize = 1 + buffer.format = 'b' + buffer.ndim = 2 + buffer.shape = shape + buffer.strides = strides + buffer.suboffsets = NULL + buffer.internal = NULL + + def __releasebuffer__(self, Py_buffer *buffer): + """ When PyBuffer_Release is called on buffers from __getbuffer__. + """ + # Just deallocate the shape and strides allocated by __getbuffer__. + # Since buffer.obj is a referenced counted reference to this object + # (thus keeping this object alive as long a connected buffer exists) + # and we don't mutate `self.data` outside of __init__ and __dealloc__, + # we have nothing further to do here. + PyMem_Free(buffer.shape) + PyMem_Free(buffer.strides) def clear(self, object clear_color=(1.0, 1.0, 1.0, 1.0)): """Paint over the whole image with a solid color. @@ -1601,7 +1610,8 @@ cdef class CGBitmapContext(CGContext): if file_format is None: file_format = '' - img = PilImage.frombytes(mode, (self.width(), self.height()), self) + img = PilImage.frombuffer(mode, (self.width(), self.height()), self, + 'raw', mode, 0, 1) if 'A' in mode: # Check the output format to see if it can handle an alpha channel. no_alpha_formats = ('jpg', 'bmp', 'eps', 'jpeg') diff --git a/kiva/tests/drawing_tester.py b/kiva/tests/drawing_tester.py index 9c7acc56b..1badaaf2b 100644 --- a/kiva/tests/drawing_tester.py +++ b/kiva/tests/drawing_tester.py @@ -186,17 +186,23 @@ def assertImageSavedWithContent(self, filename): """ Load the image and check that there is some content in it. """ image = numpy.array(Image.open(filename)) - # default is expected to be a totally white image + # Default is expected to be a totally white image. + # Therefore we check if the whole image is not white. + + # Previously this method checked for red pixels. However this is + # not [currently] possible with the quartz backend because it writes + # out image with premultiplied alpha and none of its pixels are the + # exact red expected here. self.assertEqual(image.shape[:2], (600, 600)) if image.shape[2] == 3: - check = numpy.sum(image == [255, 0, 0], axis=2) == 3 + check = numpy.sum(image == [255, 255, 255]) != (600 * 600 * 3) elif image.shape[2] == 4: - check = numpy.sum(image == [255, 0, 0, 255], axis=2) == 4 + check = numpy.sum(image == [255, 255, 255, 255]) != (600 * 600 * 4) else: self.fail( "Pixel size is not 3 or 4, but {0}".format(image.shape[2]) ) - if check.any(): + if check: return self.fail("The image looks empty, no red pixels were drawn") diff --git a/kiva/tests/test_quartz_drawing.py b/kiva/tests/test_quartz_drawing.py new file mode 100644 index 000000000..a7203e06a --- /dev/null +++ b/kiva/tests/test_quartz_drawing.py @@ -0,0 +1,23 @@ +# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +import sys +import unittest + +from kiva.tests.drawing_tester import DrawingImageTester + +is_not_macos = not sys.platform == "darwin" + + +@unittest.skipIf(is_not_macos, "Not macOS") +class TestQuartzDrawing(DrawingImageTester, unittest.TestCase): + def create_graphics_context(self, width, height, pixel_scale): + from kiva.quartz import ABCGI + + return ABCGI.CGBitmapContext((width, height))