Perspective transform

While working on a Dungeon Master clone/game engine, I needed some way of converting a perpendicular wall texture to one suitable for a wall seen in perspective. PIL, since version 1.1.5c2, provides the PERSPECTIVE keyword for image objects' transform() method. Unfortunately it isn't documented in the online manual, and it works like this:

im.transform(size, PERSPECTIVE, data) => image

Applies a perspective transform to the image, and places the result in a new image with the given size.

Data is a 8-tuple (a, b, c, d, e, f, g, h) which contains the coefficients for a perspective transform. For each pixel (x, y) in the output image, the new value is taken from a position (a x + b y + c)/(g x + h y + 1), (d x + e y + f)/(g x + h y + 1) in the input image, rounded to nearest pixel.

This function can be used to change the 2D perspective of the original image.

User-friendliness at it's finest, I'm sure you'll agree. So, after much head-scratching and scribbling of forumulae, I came up with the following wrapper-function:

import Image
 
def applyPerspective (img, corners):
"""

Returns a new surface containing the image reshaped to fit inside the quadrilateral defined by the given vertices (top-left, top-right, bottom-right, bottom-left).

For the sake of my sanity, we're assuming that the left and right sides, post-transform, will be straight vertical lines. If you intend them to be otherwise, I shall punch you in the cock. Also assumed is that the smaller end doesn't extend above or below the longer end (though I'm not sure that it doing so would actually be a problem).

"""
 
tl, tr, br, bl = corners
sourceSize = img.size
newSize = (max(tr[0], br[0]), max(bl[1], br[1]))
 
a = float(sourceSize[0] * (br[1] - tr[1])) / \
(newSize[0] * (bl[1] - tl[1]))
b = 0
c = 0
d = float(sourceSize[1] * (tl[1] - tr[1])) / \
(newSize[0] * (bl[1] - tl[1]))
e = float(sourceSize[1]) / \
(bl[1] - tl[1])
f = float(-(sourceSize[1] * tl[1])) / \
(bl[1] - tl[1])
g = float(tl[1] - tr[1] + br[1] - bl[1]) / \
(newSize[0] * (bl[1] - tl[1]))
h = 0
vals = [a, b, c, d, e, f, g, h]
 
return img.transform (newSize, Image.PERSPECTIVE, vals, Image.BILINEAR)

As this is just a little bit of mathematical jiggery-pokery, I consider it public domain.

See also

The original post by Jeff Breidenbach describing the usage of his perspective patch. This is the only documentation on this feature I have ever been able to find.