Hack This: Edit an Image with Python

Algorithmic Photoshop, at your fingertips.

Whether they're being registered by the rods and cones of a human eye or being sent across a computer network in binary streams, images are just data. Pixels and parameters. Image editing thus consists only of data operations, usually a whole lot of them. Even a cheap-feeling Instagram filter can consist of many pixel-by-pixel passes over an image file, while the computational demands of software like Lightroom make for some of the heaviest lifting in consumer computing.

As images are just data, it's easy enough to treat them as such. That is, we can readily trim away all of the GUI trappings of image editing software and get right down to bare computation—editing images via bare code, that is.

Why would you do such a thing? Well, for one thing it opens up image editing to all kinds of clever hacks and automation. For example, if you had a website like this one and wanted to make sure that it wasn't ever linking to/referencing images above a certain size or resolution (in the interests of reasonable page-load times) you might have a script somewhere that would chew through the site's HTML automatically resizing images as it encounters them. Or maybe you just wanted to make some of your own image filters that could be invoked from your system's command line or a Python shell. Or maybe you wanted to make algorithmic Photoshop art.

Or maybe you just want to understand better how computers handle images. Computer vision is a huge and increasingly intense field nowadays, but it remains a very challenging one. We're still on the ground floor (OK, maybe the mezzanine) when it comes to facial recognition, and even recognizing characters from books gives computers a hard time. A computer that can look at a visual scene and make sense of it isn't that much better than sci-fi.

Python makes code-based image editing pretty easy, which is no surprise because Python makes code-based anything pretty easy. (Most code-based how-tos you'll see on here will be in Python.) There are two main tools we can use within the language to accomplish this, but we're going to stick to the more basic Python Imaging Library (the other is OpenSource Computer Vision or OpenCV).

Before going on, you'll obviously need to have Python installed. It can be downloaded from here. For this how-to, you can do everything from within the Python interpreter, which is just a shell that you can punch Python commands into. For writing Python scripts (bundles of commands functioning as a program, basically), you'll need a text editor like Sublime Text or Atom (but any text editor will do).

If you've just downloaded Python, you should do two more things first. One: spend 10 minutes doing this "Hello, World" Python for non-programmers tutorial. Two: spend another five minutes doing this tutorial on using Python modules.

0) Image representation 101

I said above that images are all just data, but what does that really mean for a computer?

First of all, all images are displayed as bitmaps. A bitmap isn't really a format so much as it a thing: a two-dimensional grid of pixels. A bitmap is defined by only two fundamental features: resolution (the number of pixels it contains) and color depth per-pixel. Color depth just refers to its maximum information content of a pixel in terms of bits. A one-bit pixel can only represent black or white; eight bits allows for the unique representation of 256 grayscale states between black and white; 24 bits gives us RGB color, with eight bits representing each of the red, blue, and green components for 16,777,216 possible colors.

We mostly live in a 24 bit world, but color depth can increase further with the addition of extra channels, like an alpha channel. This fourth channel, which increases the pixel's depth to 32 bits, carries transparency information, which can specify how the different RGB channels are to be blended together.

A RAW data file just lists bitmap image information row by row, which can wind up being way too much data to reasonably edit and-or send around a network. So, we have formats like JPGs, GIFs, and PNGs. The differences between them have to do with supported compression schemes and allowed pixel depth. A PNG, for example, might have an alpha channel, whereas a JPG most definitely won't.

1) PIL to Pillow to PIL

The actively developed version of the Python Imaging Library is actually called Pillow, which you can install using Python's pip or easy_install tools. If you did the modules tutorial above, this will make sense. If not, go back and do it. It takes all of five minutes and will save you some bafflement.

pip install Pillow


easy_install Pillow

Next, we need to actually import the the components of Pillow/PIL that we want:

from PIL import Image, ImageFilter

2) Open an image

Assuming you have an image somewhere on your computer to open, the "open" function will give us a representation of the image that we can do stuff to:

img = Image.open('jpl_pioneer_galaxy_full.jpg')

You'll probably have to give it some path information as well, assuming the image isn't in your currently active directory (e.g. where in your file system you opened Python from). You can find out what that directory is by using the getcwd() function from the os module, like this:

import os os.getcwd()

If the image you want to work on isn't in the directory returned, then you need to be more specific in your open command. For example:

img = Image.open('C:\\main_jpl_pioneer_galaxy_full.jpg')

You need the double slashes when specifying file paths in Python because a single backslash is often used as an escape character.

3) Look at your image

Next, let's make sure we can at least see the thing:


Did it work? Probably not, actually, particularly if you're trying this on Windows. The show() function is basically just a script that tries to tell your system to do its default image opening thing and it's not guaranteed to work. It doesn't for me and I'm not even gonna bother trying to find out why. All it does is open the picture in some other photo viewer.

Here's a workaround using Python's webbrowser module:

import webbrowser webbrowser.open('C:\\path\\to\\image.jpg')

That should definitely work with the image opening in your system's current default image viewing program. Mine is Paint.

4) Apply a filter

So, what do you actually want to do to this image? Let's start with a filter. To see your options, just run the help() function on the ImageFilter submodule (help(img.ImageFilter)) you imported at the beginning. It's going to give you a huge list of functions corresponding to image editing stuff you're probably at least somewhat familiar with (blur, sharpen, etc.).

Before going on, I need to resize the image I selected above. It's currently a big honking thing and I don't really need to deal with a big honking thing for this 640 px-wide space.

Resizing my image while keeping the same aspect ratio isn't completely obvious. The pure way would be taking the desired width and then just doing the math to come up with the desired height matching that aspect ratio. Then you could use the resize() function, which you can find explained here: help(img.resize).

The resizing fudge is to the use the Image module's thumbnail method. First, you need to specify a desired size as a height/width pair:

size = (640,640)

And thumbnail will take the specified height and automatically calculate a width. Like so:


Did it work? Nope. I forgot to save that shit.

Let's try:

img.save('C:\\main_jpl_pioneer_galaxy_full_small.jpg') webbrowser.open('C:\\main_jpl_pioneer_galaxy_full_small.jpg')

Again, I use webbrowser.open to view the newly resized image. Now let's apply a filter.

img = img.filter(ImageFilter.BLUR) img.save("C:\\C_S\\blurred.jpg", "JPEG") webbrowser.open('C:\\C_S\\blurred.jpg')

Hot damn, it worked.

That's not exactly mind-blowing stuff, but at this point we've just barely stuck a toe into the PIL library.

5) ImageOps, ImageChops

There are a dozen or so PIL submodules in all. Image and ImageFilter are just two of them, while ImageOps is more recent, "experimental" module. It's purpose to provide a set of ready-made image processing functions. Here you can do things like cropping photos, changing photos from color to grayscale and back, deforming photos, inverting photos, solarizing photos, and quite a few more.

ImageChops, meanwhile, is a module that provides tools for doing channel operations, e.g. performing arithmetical operations on images. Most of the ImageChops functions simply take in one image and return another. I could, for example, take two images and add them together, pixel by pixel. First, here's the code:

The result of adding the first two is the third one:


6) Going deeper

Things start to get deep pretty fast. An obvious extension of this is to start packaging sequences of operations and commands into scripts. I could have done the addition above and even the stitching of the three images (into a single image, which I really did in Paint) in Python code.

I could also write my own filters and image manipulation functions that work within/with PIL. Elsewhere in the module, built-in functions exist for pixel by pixel math operations; coloring pixels; creating masks and transparency; convolution; and even applying binary arithmetic to images. You could write an entire Photoshop bot here and task it with the creation of algorithmically generated dank memes, or maybe even write something really useful. For that, you are are your own.