Overview of Wand
masterctypes-based simple ImageMagick binding for Python. It supports Python 3.8+ and PyPy. Wand implements all functionalities of the MagickWand API.repository·master·Indexed 23 days ago
https://github.com/emcconville/wandA ctypes-based Python binding for the MagickWand API, providing access to ImageMagick functionality for Python 3.8+ and PyPy. The library allows for image manipulation including colorspace adjustments, pixel channel evaluation, complex distortions (such as perspective, polar, and polynomial), and drawing operations via the Drawing object.
ctypes-based simple ImageMagick binding for Python. It supports Python 3.8+ and PyPy. Wand implements all functionalities of the MagickWand API.When distorting images, new regions may be created outside the original bounding box. These are called 'virtual pixels'. You can control how these regions are filled by setting the Image.virtual_pixel attribute using values from wand.image.VIRTUAL_PIXEL_METHOD.
Common values include:
'transparent''black''white''background' (uses the existing background color)'dither''edge''mirror''random''tile'with Image(filename='rose:') as img:
img.resize(140, 92)
img.background_color = img[70, 46]
img.virtual_pixel = 'background'
img.distort('arc', (60, ))Wand provides a PIL compatibility layer to allow existing Python projects that depend on PIL to use Wand instead. There are two ways to implement this:
wand.pilcompat.Image instead of PIL.Image.patch() function from wand.pilcompat.monkey to modify sys.modules. This allows existing code that calls import PIL.Image to transparently use Wand's implementation.# Method 1: Module-level compatibility
try:
from wand.pilcompat import Image
except ImportError:
from PIL import Image
# Method 2: Global monkeypatcher
from wand.pilcompat.monkey import patch; patch()
import PIL.Image # it imports wand.pilcompat.Image moduleWhile Wand implements destructors that invoke Resource.destroy(), relying on automatic deallocation via Python's garbage collector is discouraged.
On CPython, reference counting often handles deallocation automatically, but this is an implementation detail. On other implementations like PyPy, which uses a non-deterministic garbage collector, the timing of destructor invocation is not guaranteed. Relying on automatic cleanup in these environments can lead to broken programs or resource leaks. Always use with statements or explicit .destroy()/.close() calls.
ImageMagick-7 enables High Dynamic Range Imaging (HDRI) by default. This can lead to color-value underflow or overflow during arithmetic operations.
To ensure consistent results, do not hard-code maximum color values; instead, use Image.quantum_range. To prevent underflow/overflow when using methods like Image.evaluate(), Image.function(), or Image.composite_channel(), use the Image.clamp() method.
with Image(width=1, height=1, background=Color("gray5")) as canvas:
canvas.evaluate("subtract", canvas.quantum_range * 0.07)
canvas.clamp()
print(canvas[0, 0]) #=> srgb(0,0,0)Wand provides access to approximately three dozen pre-built kernels via ImageMagick. To use a built-in kernel, provide a string in the following format:
label[:arg1,arg2,arg3,..]
label: A string defined in wand.image.KERNEL_INFO_TYPES (e.g., disk, square, cross).arg1, arg2, ...: Optional arguments defined as a comma-separated list of doubles.Common Kernel Examples:
kernel='cross:3'kernel='diamond:3'kernel='disk:5'kernel='octagon:5'kernel='plus:3'kernel='ring:5,4'kernel='square:3'img.morphology(method='dilate', kernel='disk:5')Wand supports sequential images (e.g., animated GIFs or multi-icon .ico files) via the sequence attribute on an Image object. The sequence attribute is a list-like object implementing the collections.MutableSequence protocol, allowing you to iterate over it, index it, slice it, or use len().
Each item within the sequence is an instance of wand.sequence.SingleImage. Both wand.image.Image and wand.sequence.SingleImage share a common superclass, wand.image.BaseImage, meaning most operations and properties are available for both.
from wand.image import Image
import urllib2
with Image(file=urllib2.urlopen('https://github.com/favicon.ico')) as ico:
max(ico.sequence, key=lambda i: i.width * i.height)wand.drawing.Drawing object acts as a buffer for drawing instructions. Instead of drawing directly to an image, you record shapes and styles (like stroke_color, stroke_width, and fill_color) onto the Drawing object. Once your instructions are complete, you can apply them to one or more wand.image.Image objects by either calling the Drawing object as a callable or using the .draw(image) method.While both inherit from wand.image.BaseImage and share methods like resize() and the size property, they serve different purposes:
wand.image.Image: A container representing the entire image file (e.g., the whole GIF). It provides file-level operations like save() and the mimetype attribute.wand.sequence.SingleImage: Represents an individual frame or icon size within a sequence. It provides frame-specific attributes like delay and index.Warning: When dealing with animated GIFs or multi-size ICO files, ensure you are using the correct type for the operation you intend to perform.
Wand provides access to image profiles (like EXIF, ICC, or XMP) via the Image.profiles dictionary. Because ImageMagick is not a tag editor, you should treat profiles as binary blobs (byte-arrays). To modify a profile, you must export the payload, modify it externally, and then import it back.
Important: Every write operation on a profile requires the raster image data to be re-encoded. For lossy formats, this can result in generation loss.
with Image(filename='wandtests/assets/beach.jpg') as image:
# Extract EXIF payload
if 'EXIF' in image.profiles:
exif_binary = image.profiles['EXIF']
# Import/replace ICC payload
with open('color_profile.icc', 'rb') as icc:
image.profiles['ICC'] = icc.read()
# Remove XMP payload
del image.profiles['XMP']For complex vector graphics, you can use the graphic-context stack to manage different styles and operations without affecting the global state of the Drawing object.
push(): Marks the beginning of a sub-routine and grows the stack.pop(): Marks the end of a sub-routine and restores the graphical context to its previous state.Other stack methods include push_clip_path(), push_defs(), push_pattern(), and their corresponding pop counterparts (pop_clip_path(), pop_defs(), pop_pattern()).
from wand.color import Color
from wand.image import Image
from wand.drawing import Drawing
from math import cos, pi, sin
with Color('lightblue') as bg, Color('transparent') as fg, Drawing() as draw:
draw.stroke_width = 3
draw.fill_color = fg
for degree in range(0, 360, 15):
draw.push() # Grow stack
draw.stroke_color = Color('hsl({0}%, 100%, 50%)'.format(degree * 100 / 360))
t = degree / 180.0 * pi
x = 35 * cos(t) + 50
y = 35 * sin(t) + 50
draw.line((50, 50), (x, y))
draw.pop() # Restore stack
with Image(width=100, height=100, background=Color('lightblue')) as img:
draw(img)Wand includes unit and regression tests. You can run them using the setup.py script, which will automatically install pytest if it is missing, or you can use pytest directly after installing it manually.
To run all tests via setup.py:
$ python setup.py testTo run tests manually using pytest:
$ pip install pytest
$ pytest