[Image-SIG] Proof-of-concept version of pildriver
Eric S. Raymond
esr@snark.thyrsus.com
Sat, 18 Jul 1998 12:08:48 -0400
--ibTvN161/egqYuK8
Content-Type: text/plain; charset=us-ascii
Here's the first alpha version. It's not complete, and there are some
things in it that don't yet work right (either due to underlying PIL
problems or because I don't understand intended usage). It should
demonstrate, however, that pildriver has the potential to replace
several of the ad-hoc scripts in the present distribution with a
single simple utility that serves as an image batch processor, a test
driver, and a tutorial exerciser.
I'd have documented this, but I don't know what the preferred
format for PIL documentation is, or if there is one.
Feedback, comments, and corrections are solicited.
--
<a href="http://www.tuxedo.org/~esr">Eric S. Raymond</a>
To make inexpensive guns impossible to get is to say that you're
putting a money test on getting a gun. It's racism in its worst form.
-- Roy Innis, president of the Congress of Racial Equality (CORE), 1988
--ibTvN161/egqYuK8
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=pildriver
#!/usr/bin/env python
#
# pildriver -- an image-processing calculator for PIL
#
# by Eric S. Raymond <esr@thyrsus.com>
# $Id: pildriver,v 1.1 1998/07/18 16:08:25 esr Exp $
#
# This is essentially a Polish-notation interpreter for sequencing
# PIL image transformations. All operations consume their arguments
# off the stack (use `dup' to keep copies around). Use `verbose 1`
# to see the stack state before each operation.
#
# PILDriver doesn't catch any exceptions, on the theory that these
# are actually diagnostic information that should be interpreted by
# the calling code.
#
# TO DO:
# 1. Add PILFont capabilities, once that's documented.
# 2. Add PILDraw operations.
# 3. Add support for composing and decomposing multiple-image files.
#
import Image, string
class PILDriver:
verbose = 0
def do_verbose(self):
# Set verbosity flag from top of stack
self.verbose = self.do_pop()
# The evaluation stack (internal only)
stack = [] # Stack of pending operations
def push(self, item):
# Push an argument onto the stack
self.stack = [item] + self.stack
def top(self):
# Return the top-of-stack element
return self.stack[0]
# Stack manipulation (callable)
def do_clear(self):
# Clear the stack
self.stack = []
def do_pop(self):
# Pop (and return) the top element on the stack
top = self.stack[0]
self.stack = self.stack[1:]
return top
def do_dup(self):
# Duplicate the top-of-stack item
if hasattr(self, 'format'): # If it's an image, do a real copy
dup = self.stack[0].copy()
else:
dup = self.stack[0]
self.stack = [dup] + self.stack
def do_swap(self):
# Swap the top-of-stack item with the next one down
self.stack = [self.stack[1], self.stack[0]] + self.stack[2:]
# Image module functions (callable)
def do_new(self):
# Create a greyscale image of given size and color, push on stack
color = int(self.do_pop())
ysize = int(self.do_pop())
xsize = int(self.do_pop())
self.push(Image.new("L", (xsize, ysize), color))
def do_open(self):
# Open the indicated image, read it, push it on the stack
self.push(Image.open(self.do_pop()))
def do_blend(self):
# Replace two images and an alpha with the blended image
alpha = float(self.do_pop())
image2 = self.do_pop()
image1 = self.do_pop()
self.push(Image.blend(image1, image2, alpha))
def do_composite(self):
# Replace two images and a mask with their composite
mask = self.do_pop()
image2 = self.do_pop()
image1 = self.do_pop()
self.push(Image.composite(image1, image2, mask))
def do_merge(self):
# Merge top-of stack images in a way described by the mode
bandlist = []
mode = self.do_pop()
for band in mode:
bandlist.append(self.do_pop())
bandlist.reverse()
self.push(Image.merge(mode, bandlist))
# Image class methods
def do_convert(self):
# Convert the top image to the given mode
mode = self.do_pop()
image = self.do_pop()
self.push(image.convert(mode))
def do_copy(self):
# Make a true copy of the top image, push it on the stack
self.dup()
def do_crop(self):
# Crop a rectangular region from the current image
lower = int(self.do_pop())
right = int(self.do_pop())
upper = int(self.do_pop())
left = int(self.do_pop())
image = self.do_pop()
self.push(image.crop((left, upper, right, lower)))
def do_draft(self):
# Configure the loader for a given mode
ysize = int(self.do_pop())
xsize = int(self.do_pop())
mode = self.do_pop()
self.push(self.draft(mode, (xsize, ysize)))
def do_filter(self):
# Filter the top image with the given filter
import ImageFilter
filter = eval("ImageFilter." + string.upper(self.do_pop()))
image = self.do_pop()
self.push(image.filter(filter))
def do_offset():
# Offset the top image
yoff = int(self.do_pop())
xoff = int(self.do_pop())
image = self.do_pop()
self.push(image.offset(xoff, yoff))
def do_paste(self):
# Interpret top image as figure, next down as ground
yoff = int(self.do_pop())
xoff = int(self.do_pop())
figure = self.do_pop()
ground = self.do_pop()
if figure.mode == "RGBA":
self.push(ground.paste(figure, (xoff, yoff), figure))
else:
self.push(ground.paste(figure, (xoff, yoff)))
def do_resize(self):
# Resize the top image
ysize = int(self.do_pop())
xsize = int(self.do_pop())
image = self.do_pop()
self.push(image.resize(xsize, ysize))
def do_rotate(self):
# Rotate image through a given angle
angle = int(self.do_pop())
image = self.do_pop()
self.push(image.rotate(angle))
def do_save(self):
# Save image with default options
filename = self.do_pop()
image = self.do_pop()
image.save(filename)
def do_save2(self):
# Save image with specified options
filename = self.do_pop()
options = self.do_pop()
image = self.do_pop()
image.save(filename, None, options)
def do_show(self):
# Display the top image in the stack
self.pop().show()
def do_thumbnail(self):
# Modify the top image in the stack to contain a thumbnail of itself
ysize = int(self.do_pop())
xsize = int(self.do_pop())
self.top().thumbnail((xsize, ysize))
def do_transpose(self):
# Transpose the top image
transpose = string.upper(self.do_pop())
image = self.do_pop()
self.push(image.transpose(transpose))
# Image attributes
def do_format(self):
# Push the format of the top image onto the stack
self.push(self.pop().format)
def do_mode(self):
# Push the mode of the top image onto the stack
self.push(self.pop().mode)
def do_size(self):
# Push the image size on the stack as (y, x)
size = self.pop().size
self.push(size[0])
self.push(size[1])
# ImageChops operations
def do_invert(self):
# Invert the top image
import ImageChops
self.push(ImageChops.invert(self.do_pop()))
def do_lighter(self):
# Pop the two top images, push an image of the lighter pixels of both
import ImageChops
image2 = self.do_pop()
image1 = self.do_pop()
self.push(ImageChops.lighter(image1, image2))
def do_darker(self):
# Pop the two top images, push an image of the darker pixels of both
import ImageChops
image2 = self.do_pop()
image1 = self.do_pop()
self.push(ImageChops.darker(image1, image2))
def do_difference(self):
# Pop the two top images, push the difference image
import ImageChops
image2 = self.do_pop()
image1 = self.do_pop()
self.push(ImageChops.difference(image1, image2))
def do_multiply(self):
# Pop the two top images, push the multiplication image
import ImageChops
image2 = self.do_pop()
image1 = self.do_pop()
self.push(ImageChops.multiply(image1, image2))
def do_screen(self):
# Pop the two top images, superimpose their inverted versions
import ImageChops
image2 = self.do_pop()
image1 = self.do_pop()
self.push(ImageChops.screen(image1, image2))
def do_add(self):
# Pop the two top images, produce the scaled sum with offset
import ImageChops
offset = float(self.do_pop())
scale = float(self.do_pop())
image2 = self.do_pop()
image1 = self.do_pop()
self.push(ImageChops.add(image1, image2, scale, offset))
def do_subtract(self):
# Pop the two top images, produce the scaled difference with offset
import ImageChops
offset = float(self.do_pop())
scale = float(self.do_pop())
image2 = self.do_pop()
image1 = self.do_pop()
self.push(ImageChops.subtract(image1, image2, scale, offset))
# ImageEnhance classes
def do_color(self):
# Enhance color in the top image
import ImageEnhance
factor = float(self.do_pop())
image = self.do_pop()
enhancer = ImageEnhance.Color(image)
self.push(enhancer.enhance(factor))
def do_contrast(self):
# Enhance contrast in the top image
import ImageEnhance
factor = float(self.do_pop())
image = self.do_pop()
enhancer = ImageEnhance.Color(image)
self.push(enhancer.enhance(factor))
def do_brightness(self):
# Enhance brightness in the top image
import ImageEnhance
factor = float(self.do_pop())
image = self.do_pop()
enhancer = ImageEnhance.Color(image)
self.push(enhancer.enhance(factor))
def do_sharpness(self):
# Enhance sharpness in the top image
import ImageEnhance
factor = float(self.do_pop())
image = self.do_pop()
enhancer = ImageEnhance.Color(image)
self.push(enhancer.enhance(factor))
# The interpreter loop
def execute(self, list):
# Interpret a list of PILDriver commands
list.reverse()
while len(list) > 0:
self.push(list[0])
list = list[1:]
if self.verbose:
print "Stack: " + `self.stack`
top = self.top()
if type(top) != type(""):
continue;
funcname = "do_" + top
if not hasattr(self, funcname):
continue
else:
self.do_pop()
func = getattr(self, funcname)
func()
if __name__ == '__main__':
import readline, sys
# If we see command-line arguments, interpret them as a stack state
# and execute. Otherwise go interactive.
driver = PILDriver()
if len(sys.argv[1:]) > 0:
driver.execute(sys.argv[1:])
else:
print "PILDriver says hello"
while 1:
try:
line = raw_input('pildriver> ');
except EOFError:
print "\nPILDriver says goodbye"
break
driver.execute(string.split(line))
print driver.stack
# The following sets edit modes for GNU EMACS
# Local Variables:
# mode:python
# End:
--ibTvN161/egqYuK8--