Skip to content

Commit

Permalink
Merge fa4412c into d76a2f3
Browse files Browse the repository at this point in the history
  • Loading branch information
emcconville committed Mar 3, 2020
2 parents d76a2f3 + fa4412c commit f3e5653
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 110 deletions.
18 changes: 18 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
Wand Changelog
==============

.. _changelog-0.6:

0.6 series
~~~~~~~~~~


.. _changelog-0.6.0:

Version 0.6.0
~~~~~~~~~~~~~

Unreleased

- Updated :mod:`numpy` array interface methods to accept / generate
:attr:`shape` data values as ``rows``, ``columns``, and ``channels``.
This change should match other python-image numpy integrations. [:issue:`447`]


.. _changelog-0.5:

0.5 series
Expand Down
40 changes: 0 additions & 40 deletions tests/_resource_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,6 @@
from wand import exceptions, resource


def test_refcount():
"""Refcount maintains the global instance."""
genesis = resource.genesis
terminus = resource.terminus
called = {'genesis': False, 'terminus': False}

def decorated_genesis():
genesis()
called['genesis'] = True

def decorated_terminus():
terminus()
called['terminus'] = True

resource.genesis = decorated_genesis
resource.terminus = decorated_terminus
assert not called['genesis']
assert not called['terminus']
assert resource.reference_count == 0
resource.increment_refcount()
assert called['genesis']
assert not called['terminus']
assert resource.reference_count == 1
resource.increment_refcount()
assert not called['terminus']
assert resource.reference_count == 2
resource.decrement_refcount()
assert not called['terminus']
assert resource.reference_count == 1
resource.decrement_refcount()
assert called['terminus']
assert resource.reference_count == 0


def test_negative_refcount():
"""reference_count cannot be negative"""
with raises(RuntimeError):
resource.decrement_refcount()


class DummyResource(resource.Resource):

def set_exception_type(self, idx):
Expand Down
2 changes: 1 addition & 1 deletion tests/image_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ def test_array_interface():
with Image(filename='rose:') as img:
img.alpha_channel = 'off'
array = np.array(img)
assert array.shape == (70, 46, 3)
assert array.shape == (46, 70, 3)


@mark.skipif(np is None, reason='Numpy not available.')
Expand Down
71 changes: 51 additions & 20 deletions wand/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,9 @@ def __array_interface__(self):
:raises ValueError: if image has no data.
.. versionadded:: 0.5.0
.. versionchanged:: 0.6.0
The :attr:`shape` property is now ordered by ``height``, ``width``,
and ``channel``.
"""
if not self.signature:
raise ValueError("No image data to interface with.")
Expand All @@ -1267,7 +1270,7 @@ def __array_interface__(self):
if not r:
self.raise_exception()
return dict(data=(ctypes.addressof(self._c_buffer), True),
shape=(width, height, channel_number),
shape=(height, width, channel_number),
typestr='|u1',
version=3)

Expand Down Expand Up @@ -3235,6 +3238,7 @@ def coalesce(self):
if not r: # pragma: no cover
self.raise_exception()
self.wand = r
self.reset_sequence()

@manipulative
@trap_exception
Expand Down Expand Up @@ -3487,6 +3491,7 @@ def combine(self, channel='rgb_channels', colorspace='rgb'):
new_wand = library.MagickCombineImages(self.wand, colorspace_c)
if new_wand:
self.wand = new_wand
self.reset_sequence()
else:
self.raise_exception()

Expand Down Expand Up @@ -3771,6 +3776,7 @@ def concat(self, stacked=False):
if not r: # pragma: no cover
self.raise_exception()
self.wand = r
self.reset_sequence()

def connected_components(self, connectivity=4, area_threshold=None,
mean_color=False, keep=None, remove=None):
Expand Down Expand Up @@ -4073,6 +4079,7 @@ def abs_(n, m, null=None):
return True
if self.animation:
self.wand = library.MagickCoalesceImages(self.wand)
self.reset_sequence()
library.MagickSetLastIterator(self.wand)
n = library.MagickGetIteratorIndex(self.wand)
library.MagickResetIterator(self.wand)
Expand Down Expand Up @@ -5534,6 +5541,7 @@ def merge_layers(self, method):
self.raise_exception()
else:
self.wand = r
self.reset_sequence()

@manipulative
def mode(self, width, height=None):
Expand Down Expand Up @@ -6565,6 +6573,7 @@ def resample(self, x_res=None, y_res=None, filter='undefined', blur=1):
blur = ctypes.c_double(float(blur))
if self.animation:
self.wand = library.MagickCoalesceImages(self.wand)
self.reset_sequence()
library.MagickSetLastIterator(self.wand)
n = library.MagickGetIteratorIndex(self.wand)
library.MagickResetIterator(self.wand)
Expand All @@ -6588,6 +6597,14 @@ def reset_coords(self):
"""
library.MagickResetImagePage(self.wand, None)

def reset_sequence(self):
"""Abstract method prototype.
See :meth:`wand.image.Image.reset_sequence()`.
.. versionadded:: 0.6.0
"""
pass

@manipulative
def resize(self, width=None, height=None, filter='undefined', blur=1):
"""Resizes the image.
Expand Down Expand Up @@ -6638,6 +6655,7 @@ def resize(self, width=None, height=None, filter='undefined', blur=1):
blur = ctypes.c_double(float(blur))
if self.animation:
self.wand = library.MagickCoalesceImages(self.wand)
self.reset_sequence()
library.MagickSetLastIterator(self.wand)
n = library.MagickGetIteratorIndex(self.wand)
library.MagickResetIterator(self.wand)
Expand Down Expand Up @@ -6683,6 +6701,7 @@ def rotate(self, degree, background=None, reset_coords=True):
with background:
if self.animation:
self.wand = library.MagickCoalesceImages(self.wand)
self.reset_sequence()
library.MagickSetLastIterator(self.wand)
n = library.MagickGetIteratorIndex(self.wand)
library.MagickResetIterator(self.wand)
Expand Down Expand Up @@ -6761,6 +6780,7 @@ def sample(self, width=None, height=None):
assertions.assert_counting_number(width=width, height=height)
if self.animation:
self.wand = library.MagickCoalesceImages(self.wand)
self.reset_sequence()
library.MagickSetLastIterator(self.wand)
n = library.MagickGetIteratorIndex(self.wand)
library.MagickResetIterator(self.wand)
Expand Down Expand Up @@ -7166,6 +7186,7 @@ def smush(self, stacked=False, offset=0):
result = library.MagickSmushImages(self.wand, bool(stacked), offset)
if result:
self.wand = result
self.reset_sequence()
else: # pragma: no cover
self.raise_exception()

Expand Down Expand Up @@ -7432,6 +7453,7 @@ def stegano(self, watermark, offset=0):
offset)
if new_wand:
self.wand = new_wand
self.reset_sequence()
else: # pragma: no cover
self.raise_exception()

Expand Down Expand Up @@ -8363,6 +8385,9 @@ def from_array(cls, array, channel_map=None, storage=None):
:rtype: :class:`~wand.image.Image`
.. versionadded:: 0.5.3
.. versionchanged:: 0.6.0
Input ``array`` now expects the :attr:`shape` property to be defined
as ```( 'height', 'width', 'channels' )```.
"""
arr_itr = array.__array_interface__
typestr = arr_itr['typestr'] # Required by interface.
Expand Down Expand Up @@ -8398,7 +8423,7 @@ def from_array(cls, array, channel_map=None, storage=None):
else:
data_ptr, _ = arr_itr.get('data')
storage_idx = STORAGE_TYPES.index(storage)
width, height = shape[:2]
height, width = shape[:2]
wand = library.NewMagickWand()
instance = cls(BaseImage(wand))
r = library.MagickConstituteImage(instance.wand,
Expand Down Expand Up @@ -8643,17 +8668,6 @@ def convert(self, format):
cloned.format = format
return cloned

def destroy(self):
"""Manually remove :class:`~.sequence.SingleImage`'s in
the :class:`~.sequence.Sequence`, allowing it to
be properly garbage collected after using a ``with Image()`` context
manager.
"""
while self.sequence:
self.sequence.pop()
super(Image, self).destroy()

def make_blob(self, format=None):
"""Makes the binary string of the image.
Expand Down Expand Up @@ -8782,20 +8796,37 @@ def read(self, file=None, filename=None, blob=None, resolution=None,
if units is not None:
self.units = units

def save(self, file=None, filename=None):
def reset_sequence(self):
"""Remove any previously allocated :class:`~wand.sequence.SingleImage`
instances in :attr:`sequence` attribute.
.. versionadded:: 0.6.0
"""
for instance in self.sequence.instances:
if hasattr(instance, 'destroy'):
instance.destroy()
self.sequence.instances = []

def save(self, file=None, filename=None, adjoin=True):
"""Saves the image into the ``file`` or ``filename``. It takes
only one argument at a time.
:param file: a file object to write to
:type file: file object
:param filename: a filename string to write to
:type filename: :class:`basestring`
.. versionadded:: 0.1.5
The ``file`` parameter.
:param adjoin: write all images to a single multi-image file. Only
available if file format supports frames, layers, & etc.
:type adjoin: :class:`bool`
.. versionadded:: 0.1.1
.. versionchanged:: 0.1.5
The ``file`` parameter was added.
.. versionchanged:: 6.0.0
The ``adjoin`` parameter was added.
"""
if file is None and filename is None:
raise TypeError('expected an argument')
Expand All @@ -8808,7 +8839,7 @@ def save(self, file=None, filename=None):
'.save(filename={0!r})?'.format(file))
elif isinstance(file, file_types) and hasattr(libc, 'fdopen'):
fd = libc.fdopen(file.fileno(), file.mode)
if len(self.sequence) > 1:
if library.MagickGetNumberImages(self.wand) > 1:
r = library.MagickWriteImagesFile(self.wand, fd)
else:
r = library.MagickWriteImageFile(self.wand, fd)
Expand All @@ -8827,8 +8858,8 @@ def save(self, file=None, filename=None):
raise TypeError('filename must be a string, not ' +
repr(filename))
filename = encode_filename(filename)
if len(self.sequence) > 1:
r = library.MagickWriteImages(self.wand, filename, True)
if library.MagickGetNumberImages(self.wand) > 1:
r = library.MagickWriteImages(self.wand, filename, adjoin)
else:
r = library.MagickWriteImage(self.wand, filename)
if not r:
Expand Down

0 comments on commit f3e5653

Please sign in to comment.