For educational purposes I wrote a little piece of code to rotate greyscale images as "low level" as possible, that is, not using any rotate()
function, but doing the math. I was wondering if it could be improved in any way, specially in order to achieve better performance. I'm not concerned with the libraries or the programming language chosen to perform this task, I am aware that they aren't the best option if what I want is performance. I'm only concerned with the code itself.
It basically maps the pixels on the new image to the ones in the original image and interpolates using nearest neighbor or bilenear.
def affine_t(x, y, a, b, c, d, e, f):
# A lot faster than numpy.dot([[a,b],[c,d]],[x,y])+[e,f]
return round(a*x + b*y + e, 3), round(c*x + d*y + f, 3)
def mrotate(r, cx=0, cy=0):
# Returns the coefficients to an affine transformation which rotates
# a point clockwise by r radians in respect to a central point. For counter
# clockwise, just pass r as -r.
return (math.cos(r), -math.sin(r), math.sin(r), math.cos(r), cx, cy)
def lin_interp(x, x0, x1, y0, y1):
# Faster than numpy.interp()
return y0 + (y1 - y0)*((x-x0)/(x1-x0))
def bilinear(bmp, ox, oy):
# Try to interpolate. If the pixel falls on the image boundary, use the
# nearest pixel value (nearest neighbor).
try:
x0, x1, y0, y1 = int(ox), int(ox)+1, int(oy), int(oy)+1
a = lin_interp(ox, x0, x1, bmp[y0][x0], bmp[y0][x1])
b = lin_interp(ox, x0, x1, bmp[y1][x0], bmp[y1][x1])
except IndexError:
return nn(bmp, ox, oy)
return int(lin_interp(oy, y0, y1, a, b))
def nn(bmp, ox, oy):
return bmp[oy][ox] # bmp is numpy.array, it casts float indexes to int
def rotate(bmp, r, mx=0, my=0, filename=None, interpol=None):
"""Rotate bitmap bmp r radians clockwise from the center. Move it mx, my."""
# Get the bitmap's original dimensions and calculate the new ones
oh, ow = len(bmp), len(bmp[0])
nwl = ow * math.cos(r)
nwr = oh * math.sin(r)
nhl = ow * math.sin(r)
nhu = oh * math.cos(r)
nw, nh = int(math.ceil(nwl + nwr)), int(math.ceil(nhl+nhu))
cx, cy = ow/2.0 - 0.5, oh/2.0 - 0.5 # The center of the image
# Some rotations yield pixels offscren. They will be mapped anyway, so if
# the user moves the image he gets what was offscreen.
xoffset, yoffset = int(math.ceil((ow-nw)/2.0)), int(math.ceil((oh-nh)/2.0))
for x in xrange(xoffset,nw):
for y in xrange(yoffset,nh):
ox, oy = affine_t(x-cx, y-cy, *mrotate(-r, cx, cy))
if ox > -1 and ox < ow and oy > -1 and oy < oh:
pt = bilinear(bmp, ox, oy) if interpol else nn(bmp, ox, oy)
draw.point([(x+mx,y+my),],fill=pt)
if filename is not None:
im.save(filename)
The complete code, used to generate the examples below:
Some example rotations, first image is the original, to the left is one rotated 90 degrees and the one below is rotated 45 degrees.
Using Nearest Neighbors:
Using Bilinear:
Note: The original "Lenna.png" image was first resized to 0.5 the original size using bilinear interpolation and flatten to a greyscale.