Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 35 additions & 25 deletions kiva/quartz/ABCGI.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1523,30 +1523,39 @@ cdef class CGBitmapContext(CGContext):
def width(self):
return CGBitmapContextGetWidth(self.context)

def __getsegcount__(self, void* tmp):
cdef int *lenp
lenp = <int *>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] = <char*>(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 = <Py_ssize_t *>PyMem_Malloc(2 * sizeof(Py_ssize_t))
cdef Py_ssize_t* strides = <Py_ssize_t *>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 = <char *>(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)
Comment on lines +1549 to +1558
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronayres35 A bit more detail for future readers 😉


def clear(self, object clear_color=(1.0, 1.0, 1.0, 1.0)):
"""Paint over the whole image with a solid color.
Expand Down Expand Up @@ -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')
Expand Down
14 changes: 10 additions & 4 deletions kiva/tests/drawing_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is from the if 'A' in mode: block in the above file correct?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not. The premultiplied alpha setting happens in the __init__ method of the graphics context. I couldn't find a way to have normal alpha, so I changed the test and added this note.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there are a few constraints here:

  • alpha_info can only be kCGImageAlphaPremultipliedLast or kCGImageAlphaPremultipliedFirst if save() is called on a graphics context.
  • From the "Supported Pixel Formats" table on this page, you can see that only 8, 16, and 32bit pixel formats are supported, so we can't even make a packed 24bit pixel buffer which would make alpha-free saving possible.

We can definitely implement saving RGB/BGR images, but it's a question of time allocation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thank you!

# 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")
23 changes: 23 additions & 0 deletions kiva/tests/test_quartz_drawing.py
Original file line number Diff line number Diff line change
@@ -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))