Introduction

Usage

ImageMagick has “Usage” documentation which demonstrates hundreds of specific operations as performed by the convert utility. Part of sanpera’s test suite is built around these demos: a convert command is rewritten into Python, both are executed, and the results are compared. If you’re already familiar with convert, this is a fast way to get up to speed: just find a Usage test that does what you want and look at the equivalent Python. Usage tests are kept in sanpera/tests/im_usage.

Images versus frames

As far as sanpera is concerned, and unlike many other libraries, images and frames are separate concepts.

An image is a collection of metadata and a stack of zero or more frames. Each frame is a rectangular grid of actual pixel data. High-level operations such as converting between image formats tend to be done on an image; custom effects, drawing, and pixel inspection must be done on individual frames.

The distinction removes API ambiguity between single-frame and multi-frame images, and helps avoid some common pitfalls when programs written for single-frame images are used for multi-frame images.

Additionally, destructive image operations tend to return new image objects, whereas destructive frame operations cheerfully operate in-place.

Images are represented by the Image class. Frames are represented by the ImageFrame class. An Image acts as a sequence of frames, but the interface is somewhat hindered to prevent two images from claiming to own the same frame at the same time.

Geometry

sanpera has a small set of geometry-related utility classes. Properties of images and frames, such as size, return Size objects.

For convenience’s sake, any method or function anywhere in sanpera that expects a geometry object will also accept a plain tuple; for example, you may say img.resized((100, 100)) rather than img.resized(Size(100, 100)). Don’t forget the extra pair of parentheses!

Reading and writing

Read from a file:

img = Image.read('foo.png')

Or from a string:

img = Image.from_buffer(pngdata)

Similarly, write to a file:

img.write('foo.png', format='png')

# If the image was read from a file or string, it "remembers" its original
# format, and the format can be omitted:
img.write('foo.png')

Or a string:

buf = img.to_buffer(format='png')

# Same thing applies
buf = img.to_buffer()

Images cannot be read from or written to arbitrary file-like objects; the underlying library simply doesn’t support chunked i/o. The best sanpera could do is read everything into a single buffer and write it out all at once, which deceptively implies some optimization where there is none.

You may of course do this yourself:

img = Image.from_buffer(filelike.read())

Note that ImageMagick’s special filename syntax (miff:foobar[1] and the like) is not supported by the above methods, as it leads to surprising behavior for particular filenames and leaves the developer to sort the mess out. You can still use it explicitly:

img = Image.from_magick('png:badly_named_file.gif')

If you just want to use the built-in patterns or gradients, there are easier ways.

Resizing

img = img.resized((100, 100))

Cropping

img = img.cropped(Size(40, 40).at((30, 30)))