Skip to content
Permalink
Browse files

Completed Image.profile, and added test case for #249

  • Loading branch information...
emcconville committed Feb 11, 2019
1 parent c95dfb8 commit cdb01f347c478f020e9168542337f22019c26089
Showing with 89 additions and 20 deletions.
  1. +1 −0 docs/changes.rst
  2. +20 −0 tests/image_test.py
  3. +18 −3 wand/cdefs/magick_property.py
  4. +50 −17 wand/image.py
@@ -26,6 +26,7 @@ Unreleased.
:meth:`~wand.color.Color.from_hsl()` class method.
- Added :attr:`Image.compose <wand.image.BaseImage.compose>` property for
identifying layer visibility.
- Added :attr:`Image.profiles <wand.image.ProfileDict>` dictionary attribute. [:issue:`249`]


.. _changelog-0.5.0:
@@ -2440,3 +2440,23 @@ def test_import_pixels(fx_asset):
dst.import_pixels(data=0xDEADBEEF)
with raises(ValueError):
dst.import_pixels(data=[0x00, 0xFF])


def test_profiles(fx_asset):
with Image(filename=str(fx_asset.join('beach.jpg'))) as img:
assert len(img.profiles) == 1
assert 'exif' in [d for d in img.profiles]
exif_data = img.profiles['exif']
assert exif_data is not None
del img.profiles['exif']
assert img.profiles['exif'] is None
img.profiles['exif'] = exif_data
assert img.profiles['exif'] == exif_data
with raises(TypeError):
img.profiles[0xDEADBEEF]
with raises(TypeError):
del img.profiles[0xDEADBEEF]
with raises(TypeError):
img.profiles[0xDEADBEEF] = 0xDEADBEEF
with raises(TypeError):
img.profiles['exif'] = 0xDEADBEEF
@@ -3,8 +3,8 @@
.. versionadded:: 0.5.0
"""
from ctypes import (POINTER, c_void_p, c_char_p, c_size_t, c_uint, c_int,
c_double)
from ctypes import (POINTER, c_void_p, c_char_p, c_size_t, c_ubyte, c_uint,
c_int, c_double)
from wand.cdefs.wandtypes import c_magick_char_p

__all__ = ('load',)
@@ -51,7 +51,14 @@ def load(lib, IM_VERSION):
c_void_p, c_char_p, POINTER(c_size_t)
]
lib.MagickGetImageArtifacts.restype = POINTER(c_char_p)

lib.MagickGetImageProfile.argtypes = [
c_void_p, c_char_p, POINTER(c_size_t)
]
lib.MagickGetImageProfile.restype = POINTER(c_ubyte)
lib.MagickGetImageProfiles.argtypes = [
c_void_p, c_char_p, POINTER(c_size_t)
]
lib.MagickGetImageProfiles.restype = POINTER(c_char_p)
lib.MagickGetImageProperty.argtypes = [c_void_p, c_char_p]
lib.MagickGetImageProperty.restype = c_magick_char_p
lib.MagickGetImageProperties.argtypes = [
@@ -79,6 +86,10 @@ def load(lib, IM_VERSION):
c_void_p, c_void_p, c_char_p
]
lib.MagickQueryMultilineFontMetrics.restype = POINTER(c_double)
lib.MagickRemoveImageProfile.argtypes = [
c_void_p, c_char_p, POINTER(c_size_t)
]
lib.MagickRemoveImageProfile.restype = POINTER(c_ubyte)
lib.MagickSetAntialias.argtypes = [c_void_p, c_int]
lib.MagickSetAntialias.restype = c_int
lib.MagickSetBackgroundColor.argtypes = [c_void_p, c_void_p]
@@ -93,6 +104,10 @@ def load(lib, IM_VERSION):
lib.MagickSetGravity.argtypes = [c_void_p, c_int]
lib.MagickSetGravity.restype = c_int
lib.MagickSetImageArtifact.argtypes = [c_void_p, c_char_p, c_char_p]
lib.MagickSetImageProfile.argtypes = [
c_void_p, c_char_p, c_void_p, c_size_t
]
lib.MagickSetImageProfile.restype = c_int
lib.MagickSetImageProperty.argtypes = [c_void_p, c_char_p, c_char_p]
lib.MagickSetOption.argtypes = [c_void_p, c_char_p, c_char_p]
lib.MagickSetOption.restype = c_int
@@ -20,7 +20,7 @@
from .api import MagickPixelPacket, PixelInfo, libc, libmagick, library
from .color import Color
from .compat import (binary, binary_type, encode_filename, file_types,
string_type, text, xrange)
PY3, string_type, text, xrange)
from .exceptions import MissingDelegateError, WandException
from .font import Font
from .resource import DestroyedResourceError, Resource
@@ -39,7 +39,7 @@
'BaseImage', 'ChannelDepthDict', 'ChannelImageDict',
'ClosedImageError', 'HistogramDict', 'Image', 'ImageProperty',
'Iterator', 'Metadata', 'OptionDict', 'manipulative',
'ArtifactTree')
'ArtifactTree', 'ProfileDict')


#: (:class:`tuple`) The list of alpha channel types
@@ -4614,11 +4614,6 @@ class Image(BaseImage):
"""

#: (:class:`Metadata`) The metadata mapping of the image. Read only.
#:
#: .. versionadded:: 0.3.0
metadata = None

#: (:class:`ArtifactTree`) A dict mapping to image artifacts.
#: Similar to :attr:`metadata`, but used to alter behavior of various
#: internal operations.
@@ -4639,6 +4634,16 @@ class Image(BaseImage):
#: .. versionadded:: 0.3.0
channel_depths = None

#: (:class:`Metadata`) The metadata mapping of the image. Read only.
#:
#: .. versionadded:: 0.3.0
metadata = None

#: (:class:`ProfileDict`) The mapping of image profiles.
#:
#: .. versionadded:: 0.5.1
profiles = None

def __init__(self, image=None, blob=None, file=None, filename=None,
format=None, width=None, height=None, depth=None,
background=None, resolution=None, pseudo=None):
@@ -4718,6 +4723,7 @@ def __init__(self, image=None, blob=None, file=None, filename=None,
self.artifacts = ArtifactTree(self)
from .sequence import Sequence
self.sequence = Sequence(self)
self.profiles = ProfileDict(self)
self.raise_exception()

def __repr__(self):
@@ -5411,33 +5417,59 @@ def __len__(self):


class ProfileDict(ImageProperty, collections.MutableMapping):
"""The mapping table of embedded image profiles. Incuding ICC, and EXIF
profile types.
"""The mapping table of embedded image profiles.
Use this to get, set, and delete whole profile payloads on an image. Each
payload is a raw binary string.
For example::
with Image(filename='photo.jpg') as img:
# Extract EXIF
with open('exif.bin', 'wb') as payload:
payload.write(img.profiles['exif])
# Import ICC
with open('color_profile.icc', 'rb') as payload:
img.profiles['icc'] = payload.read()
# Remove XMP
del imp.profiles['xmp']
.. seealso::
Use this to get, set, and delete whole profile payloads on an image.
`Embedded Image Profiles`__ for a list of supported profiles.
..versionadded:: 0.5.1
__ https://imagemagick.org/script/formats.php#embedded
.. versionadded:: 0.5.1
"""
def __init__(self, image):
if not isinstance(image, Image):
raise TypeError('expected a wand.image.Image instance, '
'not ' + repr(image))
super(ArtifactTree, self).__init__(image)
super(ProfileDict, self).__init__(image)

def __delitem__(self, k):
if not isinstance(k, string_type):
raise TypeError('key must be a string, not ' + repr(k))
num = ctypes.c_size_t(0)
profile_p = library.MagickRemoveImageProfile(self.image.wand, k, num)
profile_p = library.MagickRemoveImageProfile(self.image.wand,
binary(k), num)
library.MagickRelinquishMemory(profile_p)

def __getitem__(self, k):
if not isinstance(k, string_type):
raise TypeError('key must be a string, not ' + repr(k))
num = ctypes.c_size_t(0)
profile_p = library.MagickGetImageProfile(self.image.wand, k, num)
return_profile = profile_p[0:num.value]
library.MagickRelinquishMemory(profile_p)
profile_p = library.MagickGetImageProfile(self.image.wand,
binary(k), num)
if num.value > 0:
if PY3:
return_profile = bytes(profile_p[0:num.value])
else:
return_profile = str(bytearray(profile_p[0:num.value]))
library.MagickRelinquishMemory(profile_p)
else:
return_profile = None
return return_profile

def __iter__(self):
@@ -5458,7 +5490,8 @@ def __setitem__(self, k, v):
raise TypeError('key must be a string, not ' + repr(k))
if not isinstance(v, binary_type):
raise TypeError('value must be a binary string, not ' + repr(v))
r = library.MagickSetImageProfile(self.image.wand, k, v, len(v))
r = library.MagickSetImageProfile(self.image.wand,
binary(k), v, len(v))
if not r:
self.image.raise_exception()

0 comments on commit cdb01f3

Please sign in to comment.
You can’t perform that action at this time.