Photo to drawing (Python)

From LiteratePrograms
Jump to: navigation, search

This page at the site graphic-design.com describes how to transform a photograph to resemble a grayscale pencil drawing using Photoshop. The idea is to blend the image with a copy that has been blurred and whose brightness level has been inverted, using the "dodge" blend mode. We will here automate the process using Python and the Python Imaging Library (PIL).

The following PIL modules must first be imported:

<<dependencies>>=
import Image
import ImageFilter
import ImageOps
import ImageDraw

We may now use PIL to load an image from a file located at the path infile. Converting the image to the "L" mode turns it into an 8-bit grayscale image. We create two identical layers, called im1 and im2.

<<setup layers>>=
im1 = Image.open(infile).convert("L")
im2 = im1.copy()

PIL has a quick command for inverting an image, which we apply to im2:

<<invert>>=
im2 = ImageOps.invert(im2)

The next step is to apply a blur effect to im2. This can be done with PIL's BLUR filter. Applying the filter blur times allows the amount of blur to be controlled:

<<apply blur>>=
for i in range(blur):
    im2 = im2.filter(ImageFilter.BLUR)

Blending the layers now remains. PIL lacks a "dodge" effect, so it has to be coded manually. Given two brightness levels a and b both in the range [0, 1), the "dodged" level c is given by

c = \frac{a}{1-\alpha b}

where α is the opacity of the second layer, a number between 0 and 1. The 8-bit grayscale representation means the actual pixel values are represented as integers in the range [0, 255], so the Python implementation becomes

<<dodge effect>>=
def dodge(a, b, alpha):
    return min(int(a*255/(256-b*alpha)), 255)

where the min operation ensures that the result is at most 255. To apply the effect, we iterate over all pixels in the image:

<<blend layers>>=
width, height = im1.size
for x in range(width):
    for y in range(height):
        a = im1.getpixel((x, y))
        b = im2.getpixel((x, y))
        im1.putpixel((x, y), dodge(a, b, alpha))

Now we're done. All that remains is to save the result in a file:

<<save output>>=
im1.save(outfile)

Putting it together in a function and specifying the blur level and opacity level alpha, we get:

<<drawing.py>>=
dependencies
dodge effect
def drawing(infile, outfile, blur=25, alpha=1.0):
    setup layers
    invert
    apply blur
    blend layers
    save output

To create a drawing "output_filename.bmp" from an input image called "input_filename.bmp", add the following line to the end of drawing.py:

drawing("input_filename.bmp", "output_filename.bmp")

As an example, here is how wiki inventor Ward Cunningham turns out:

Ward.png

Of course, the effect will not look good on all images, and optimal results may require some experimentation with the blur and alpha parameters. It is best if the input image is sharp and has a very smooth background.

Download code
hijacker
hijacker
hijacker
hijacker