Skip to content

Commit

Permalink
Merge pull request #281 from nixscripter/master
Browse files Browse the repository at this point in the history
Added merge_layers function to wrap MagickMergeImageLayers
  • Loading branch information
dahlia committed Mar 18, 2016
2 parents aa6ce79 + 3a8a22e commit 4726601
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 1 deletion.
68 changes: 67 additions & 1 deletion tests/image_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from pytest import mark, raises

from wand.image import ClosedImageError, Image
from wand.image import ClosedImageError, Image, IMAGE_LAYER_METHOD
from wand.color import Color
from wand.compat import PY3, string_type, text, text_type
from wand.exceptions import OptionError, MissingDelegateError
Expand Down Expand Up @@ -1798,3 +1798,69 @@ def test_transform_colorspace(fx_asset):

img.transform_colorspace('srgb')
assert img.colorspace == 'srgb'


def test_merge_layers_basic(fx_asset):
for method in ['merge', 'flatten', 'mosaic']:
with Image(filename=str(fx_asset.join('cmyk.jpg'))) as img1:
orig_size = img1.size
with Image(filename=str(fx_asset.join('cmyk.jpg'))) as img2:
img1.sequence.append(img2)
assert len(img1.sequence) == 2
img1.merge_layers(method)
assert len(img1.sequence) == 1
assert img1.size == orig_size


def test_merge_layers_bad_method(fx_asset):
with Image(filename=str(fx_asset.join('cmyk.jpg'))) as img:
for method in IMAGE_LAYER_METHOD + ('', 'junk'):
if method in ['merge', 'flatten', 'mosaic']:
continue # skip the valid ones
with raises(TypeError):
img.merge_layers(method)


def test_merge_layers_method_merge(fx_asset):
with Image(width=16, height=16) as img1:
img1.background_color = Color('black')
img1.alpha_channel = False
with Image(width=32, height=32) as img2:
img2.background_color = Color('white')
img2.alpha_channel = False
img2.transform(crop='16x16+8+8')

img1.sequence.append(img2)
img1.merge_layers('merge')
assert img1.size == (24, 24)


def test_merge_layers_method_flatten(fx_asset):
with Image(width=16, height=16) as img1:
img1.background_color = Color('black')
img1.alpha_channel = False
with Image(width=32, height=32) as img2:
img2.background_color = Color('white')
img2.alpha_channel = False
img2.transform(crop='16x16+8+8')

img1.sequence.append(img2)
img1.merge_layers('flatten')
assert img1.size == (16, 16)


def test_merge_layers_method_mosaic(fx_asset):
with Image(width=16, height=16) as img1:
img1.background_color = Color('black')
img1.alpha_channel = False
with Image(width=32, height=32) as img2:
img2.background_color = Color('white')
img2.alpha_channel = False
img2.transform(crop='16x16+8+8')

img1.sequence.append(img2)
img1.merge_layers('mosaic')
# TODO: this should also check negative offsets, which is the
# difference between merge and mosaic. Too bad Wand doesn't support
# access to the virtual canvas to create one.
assert img1.size == (24, 24)
4 changes: 4 additions & 0 deletions wand/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ class AffineMatrix(ctypes.Structure):
library.MagickBorderImage.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
ctypes.c_size_t, ctypes.c_size_t]

library.MagickMergeImageLayers.argtypes = [ctypes.c_void_p, # wand
ctypes.c_int] # method
library.MagickMergeImageLayers.restype = ctypes.c_void_p

library.MagickResetIterator.argtypes = [ctypes.c_void_p]

library.MagickSetLastIterator.argtypes = [ctypes.c_void_p]
Expand Down
54 changes: 54 additions & 0 deletions wand/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,32 @@
'vertical_tile_edge', 'checker_tile')


#: (:class:`tuple`) The list of :attr:`~BaseImage.layer_method` types.
#: - ``'undefined'``
#: - ``'coalesce'``
#: - ``'compareany'``
#: - ``'compareclear'``
#: - ``'compareoverlay'``
#: - ``'dispose'``
#: - ``'optimize'``
#: - ``'optimizeimage'``
#: - ``'optimizeplus'``
#: - ``'optimizetrans'``
#: - ``'removedups'``
#: - ``'removezero'``
#: - ``'composite'``
#: - ``'merge'``
#: - ``'flatten'``
#: - ``'mosaic'``
#: - ``'trimbounds'``
#: .. versionadded:: 0.4.3
IMAGE_LAYER_METHOD = ('undefined', 'coalesce', 'compareany', 'compareclear',
'compareoverlay', 'dispose', 'optimize', 'optimizeimage',
'optimizeplus', 'optimizetrans', 'removedups',
'removezero', 'composite', 'merge', 'flatten', 'mosaic',
'trimbounds')


def manipulative(function):
"""Mark the operation manipulating itself instead of returning new one."""
@functools.wraps(function)
Expand Down Expand Up @@ -2163,6 +2189,34 @@ def modulate(self, brightness=100.0, saturation=100.0, hue=100.0):
if not r:
self.raise_exception()

@manipulative
def merge_layers(self, method):
"""Composes all the image layers from the current given image onward
to produce a single image of the merged layers.
The inital canvas's size depends on the given ImageLayerMethod, and is
initialized using the first images background color. The images
are then compositied onto that image in sequence using the given
composition that has been assigned to each individual image.
The method must be set with a value from :const:`IMAGE_LAYER_METHOD`
that is acceptable to this operation. (See ImageMagick documentation
for more details.)
:param method: the method of selecting the size of the initial canvas.
:type method: :class:`basestring`
.. versionadded:: 0.4.3
"""
if method not in ['merge', 'flatten', 'mosaic']:
raise TypeError('method must be one of: merge, flatten, mosaic')

m = IMAGE_LAYER_METHOD.index(method)
r = library.MagickMergeImageLayers(self.wand, m)
if not r:
self.raise_exception()
self.wand = r

@manipulative
def threshold(self, threshold=0.5, channel=None):
"""Changes the value of individual pixels based on the intensity
Expand Down

0 comments on commit 4726601

Please sign in to comment.