added Wand 4.2 to lib
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
""":mod:`wand.sequence` --- Sequences
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 0.3.0
|
||||
|
||||
"""
|
||||
import collections
|
||||
import contextlib
|
||||
import ctypes
|
||||
import numbers
|
||||
|
||||
from .api import libmagick, library
|
||||
from .compat import binary, xrange
|
||||
from .image import BaseImage, ImageProperty
|
||||
from .version import MAGICK_VERSION_INFO
|
||||
|
||||
__all__ = 'Sequence', 'SingleImage'
|
||||
|
||||
|
||||
class Sequence(ImageProperty, collections.MutableSequence):
|
||||
"""The list-like object that contains every :class:`SingleImage`
|
||||
in the :class:`~wand.image.Image` container. It implements
|
||||
:class:`collections.Sequence` prototocol.
|
||||
|
||||
.. versionadded:: 0.3.0
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, image):
|
||||
super(Sequence, self).__init__(image)
|
||||
self.instances = []
|
||||
|
||||
def __del__(self):
|
||||
for instance in self.instances:
|
||||
if instance is not None:
|
||||
instance.c_resource = None
|
||||
|
||||
@property
|
||||
def current_index(self):
|
||||
"""(:class:`numbers.Integral`) The current index of
|
||||
its internal iterator.
|
||||
|
||||
.. note::
|
||||
|
||||
It's only for internal use.
|
||||
|
||||
"""
|
||||
return library.MagickGetIteratorIndex(self.image.wand)
|
||||
|
||||
@current_index.setter
|
||||
def current_index(self, index):
|
||||
library.MagickSetIteratorIndex(self.image.wand, index)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def index_context(self, index):
|
||||
"""Scoped setter of :attr:`current_index`. Should be
|
||||
used for :keyword:`with` statement e.g.::
|
||||
|
||||
with image.sequence.index_context(3):
|
||||
print(image.size)
|
||||
|
||||
.. note::
|
||||
|
||||
It's only for internal use.
|
||||
|
||||
"""
|
||||
index = self.validate_position(index)
|
||||
tmp_idx = self.current_index
|
||||
self.current_index = index
|
||||
yield index
|
||||
self.current_index = tmp_idx
|
||||
|
||||
def __len__(self):
|
||||
return library.MagickGetNumberImages(self.image.wand)
|
||||
|
||||
def validate_position(self, index):
|
||||
if not isinstance(index, numbers.Integral):
|
||||
raise TypeError('index must be integer, not ' + repr(index))
|
||||
length = len(self)
|
||||
if index >= length or index < -length:
|
||||
raise IndexError(
|
||||
'out of index: {0} (total: {1})'.format(index, length)
|
||||
)
|
||||
if index < 0:
|
||||
index += length
|
||||
return index
|
||||
|
||||
def validate_slice(self, slice_, as_range=False):
|
||||
if not (slice_.step is None or slice_.step == 1):
|
||||
raise ValueError('slicing with step is unsupported')
|
||||
length = len(self)
|
||||
if slice_.start is None:
|
||||
start = 0
|
||||
elif slice_.start < 0:
|
||||
start = length + slice_.start
|
||||
else:
|
||||
start = slice_.start
|
||||
start = min(length, start)
|
||||
if slice_.stop is None:
|
||||
stop = 0
|
||||
elif slice_.stop < 0:
|
||||
stop = length + slice_.stop
|
||||
else:
|
||||
stop = slice_.stop
|
||||
stop = min(length, stop or length)
|
||||
return xrange(start, stop) if as_range else slice(start, stop, None)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
slice_ = self.validate_slice(index)
|
||||
return [self[i] for i in xrange(slice_.start, slice_.stop)]
|
||||
index = self.validate_position(index)
|
||||
instances = self.instances
|
||||
instances_length = len(instances)
|
||||
if index < instances_length:
|
||||
instance = instances[index]
|
||||
if (instance is not None and
|
||||
getattr(instance, 'c_resource', None) is not None):
|
||||
return instance
|
||||
else:
|
||||
number_to_extend = index - instances_length + 1
|
||||
instances.extend(None for _ in xrange(number_to_extend))
|
||||
wand = self.image.wand
|
||||
tmp_idx = library.MagickGetIteratorIndex(wand)
|
||||
library.MagickSetIteratorIndex(wand, index)
|
||||
image = library.GetImageFromMagickWand(wand)
|
||||
exc = libmagick.AcquireExceptionInfo()
|
||||
single_image = libmagick.CloneImages(image, binary(str(index)), exc)
|
||||
libmagick.DestroyExceptionInfo(exc)
|
||||
single_wand = library.NewMagickWandFromImage(single_image)
|
||||
single_image = libmagick.DestroyImage(single_image)
|
||||
library.MagickSetIteratorIndex(wand, tmp_idx)
|
||||
instance = SingleImage(single_wand, self.image, image)
|
||||
self.instances[index] = instance
|
||||
return instance
|
||||
|
||||
def __setitem__(self, index, image):
|
||||
if isinstance(index, slice):
|
||||
tmp_idx = self.current_index
|
||||
slice_ = self.validate_slice(index)
|
||||
del self[slice_]
|
||||
self.extend(image, offset=slice_.start)
|
||||
self.current_index = tmp_idx
|
||||
else:
|
||||
if not isinstance(image, BaseImage):
|
||||
raise TypeError('image must be an instance of wand.image.'
|
||||
'BaseImage, not ' + repr(image))
|
||||
with self.index_context(index) as index:
|
||||
library.MagickRemoveImage(self.image.wand)
|
||||
library.MagickAddImage(self.image.wand, image.wand)
|
||||
|
||||
def __delitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
range_ = self.validate_slice(index, as_range=True)
|
||||
for i in reversed(range_):
|
||||
del self[i]
|
||||
else:
|
||||
with self.index_context(index) as index:
|
||||
library.MagickRemoveImage(self.image.wand)
|
||||
if index < len(self.instances):
|
||||
del self.instances[index]
|
||||
|
||||
def insert(self, index, image):
|
||||
try:
|
||||
index = self.validate_position(index)
|
||||
except IndexError:
|
||||
index = len(self)
|
||||
if not isinstance(image, BaseImage):
|
||||
raise TypeError('image must be an instance of wand.image.'
|
||||
'BaseImage, not ' + repr(image))
|
||||
if not self:
|
||||
library.MagickAddImage(self.image.wand, image.wand)
|
||||
elif index == 0:
|
||||
tmp_idx = self.current_index
|
||||
self_wand = self.image.wand
|
||||
wand = image.sequence[0].wand
|
||||
try:
|
||||
# Prepending image into the list using MagickSetFirstIterator()
|
||||
# and MagickAddImage() had not worked properly, but was fixed
|
||||
# since 6.7.6-0 (rev7106).
|
||||
if MAGICK_VERSION_INFO >= (6, 7, 6, 0):
|
||||
library.MagickSetFirstIterator(self_wand)
|
||||
library.MagickAddImage(self_wand, wand)
|
||||
else:
|
||||
self.current_index = 0
|
||||
library.MagickAddImage(self_wand,
|
||||
self.image.sequence[0].wand)
|
||||
self.current_index = 0
|
||||
library.MagickAddImage(self_wand, wand)
|
||||
self.current_index = 0
|
||||
library.MagickRemoveImage(self_wand)
|
||||
finally:
|
||||
self.current_index = tmp_idx
|
||||
else:
|
||||
with self.index_context(index - 1):
|
||||
library.MagickAddImage(self.image.wand, image.sequence[0].wand)
|
||||
self.instances.insert(index, None)
|
||||
|
||||
def append(self, image):
|
||||
if not isinstance(image, BaseImage):
|
||||
raise TypeError('image must be an instance of wand.image.'
|
||||
'BaseImage, not ' + repr(image))
|
||||
wand = self.image.wand
|
||||
tmp_idx = self.current_index
|
||||
try:
|
||||
library.MagickSetLastIterator(wand)
|
||||
library.MagickAddImage(wand, image.sequence[0].wand)
|
||||
finally:
|
||||
self.current_index = tmp_idx
|
||||
self.instances.append(None)
|
||||
|
||||
def extend(self, images, offset=None):
|
||||
tmp_idx = self.current_index
|
||||
wand = self.image.wand
|
||||
length = 0
|
||||
try:
|
||||
if offset is None:
|
||||
library.MagickSetLastIterator(self.image.wand)
|
||||
else:
|
||||
if offset == 0:
|
||||
images = iter(images)
|
||||
self.insert(0, next(images))
|
||||
offset += 1
|
||||
self.current_index = offset - 1
|
||||
if isinstance(images, type(self)):
|
||||
library.MagickAddImage(wand, images.image.wand)
|
||||
length = len(images)
|
||||
else:
|
||||
delta = 1 if MAGICK_VERSION_INFO >= (6, 7, 6, 0) else 2
|
||||
for image in images:
|
||||
if not isinstance(image, BaseImage):
|
||||
raise TypeError(
|
||||
'images must consist of only instances of '
|
||||
'wand.image.BaseImage, not ' + repr(image)
|
||||
)
|
||||
else:
|
||||
library.MagickAddImage(wand, image.sequence[0].wand)
|
||||
self.instances = []
|
||||
if offset is None:
|
||||
library.MagickSetLastIterator(self.image.wand)
|
||||
else:
|
||||
self.current_index += delta
|
||||
length += 1
|
||||
finally:
|
||||
self.current_index = tmp_idx
|
||||
null_list = [None] * length
|
||||
if offset is None:
|
||||
self.instances[offset:] = null_list
|
||||
else:
|
||||
self.instances[offset:offset] = null_list
|
||||
|
||||
def _repr_png_(self):
|
||||
library.MagickResetIterator(self.image.wand)
|
||||
repr_wand = library.MagickAppendImages(self.image.wand, 1)
|
||||
length = ctypes.c_size_t()
|
||||
blob_p = library.MagickGetImagesBlob(repr_wand,
|
||||
ctypes.byref(length))
|
||||
if blob_p and length.value:
|
||||
blob = ctypes.string_at(blob_p, length.value)
|
||||
library.MagickRelinquishMemory(blob_p)
|
||||
return blob
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class SingleImage(BaseImage):
|
||||
"""Each single image in :class:`~wand.image.Image` container.
|
||||
For example, it can be a frame of GIF animation.
|
||||
|
||||
Note that all changes on single images are invisible to their
|
||||
containers until they are :meth:`~wand.image.BaseImage.close`\ d
|
||||
(:meth:`~wand.resource.Resource.destroy`\ ed).
|
||||
|
||||
.. versionadded:: 0.3.0
|
||||
|
||||
"""
|
||||
|
||||
#: (:class:`wand.image.Image`) The container image.
|
||||
container = None
|
||||
|
||||
def __init__(self, wand, container, c_original_resource):
|
||||
super(SingleImage, self).__init__(wand)
|
||||
self.container = container
|
||||
self.c_original_resource = c_original_resource
|
||||
self._delay = None
|
||||
|
||||
@property
|
||||
def sequence(self):
|
||||
return self,
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
"""(:class:`numbers.Integral`) The index of the single image in
|
||||
the :attr:`container` image.
|
||||
|
||||
"""
|
||||
wand = self.container.wand
|
||||
library.MagickResetIterator(wand)
|
||||
image = library.GetImageFromMagickWand(wand)
|
||||
i = 0
|
||||
while self.c_original_resource != image and image:
|
||||
image = libmagick.GetNextImageInList(image)
|
||||
i += 1
|
||||
assert image
|
||||
assert self.c_original_resource == image
|
||||
return i
|
||||
|
||||
@property
|
||||
def delay(self):
|
||||
"""(:class:`numbers.Integral`) The delay to pause before display
|
||||
the next image (in the :attr:`~wand.image.BaseImage.sequence` of
|
||||
its :attr:`container`). It's hundredths of a second.
|
||||
|
||||
"""
|
||||
if self._delay is None:
|
||||
container = self.container
|
||||
with container.sequence.index_context(self.index):
|
||||
self._delay = library.MagickGetImageDelay(container.wand)
|
||||
return self._delay
|
||||
|
||||
@delay.setter
|
||||
def delay(self, delay):
|
||||
if not isinstance(delay, numbers.Integral):
|
||||
raise TypeError('delay must be an integer, not ' + repr(delay))
|
||||
elif delay < 0:
|
||||
raise ValueError('delay cannot be less than zero')
|
||||
self._delay = delay
|
||||
|
||||
def destroy(self):
|
||||
if self.dirty:
|
||||
self.container.sequence[self.index] = self
|
||||
if self._delay is not None:
|
||||
container = self.container
|
||||
with container.sequence.index_context(self.index):
|
||||
library.MagickSetImageDelay(container.wand, self._delay)
|
||||
super(SingleImage, self).destroy()
|
||||
|
||||
def __repr__(self):
|
||||
cls = type(self)
|
||||
if getattr(self, 'c_resource', None) is None:
|
||||
return '<{0}.{1}: (closed)>'.format(cls.__module__, cls.__name__)
|
||||
return '<{0}.{1}: {2} ({3}x{4})>'.format(
|
||||
cls.__module__, cls.__name__,
|
||||
self.signature[:7], self.width, self.height
|
||||
)
|
||||
Reference in New Issue
Block a user