(Tutorial) First Steps with PIL: Python Imaging Library

Python programming


This is the first tutorial about Python and GeeXLab. The purpose of this series is to do an overview of the Python libraries that may be useful for GeeXLab demos AND that work with GeeXLab.

GeeXLab has a very simple way of working. Roughly speaking, a demo is made up of an initialization script (INIT, executed once) and a per frame script (FRAME, executed every frame). These scripts can be programmed either in Lua or in Python. In a script you can code what you want with both languages. There is no restriction: GeeXLab can be seen as a virtual machine for Lua and Python. That’s why, most the Python packages available will work with GeeXLab.

And to interact with GeeXLab scene data (textures, GLSL shaders, meshes, etc.) there is the Host-API which is simply GeeXLab API for Lua and Python. GeeXLab Host-API functions description is available HERE.

This quick description of GeeXLab scripting is important for this series of tutorials about Python libraries because I’m going to focuse on the functionalities offered by those libraries and I’ll try to limit the use of GeeXLab functions to the bare minimum. That way, you will be able to quickly re-use all code snippets in your own python projects.

That said, let’s talk about the first Python lib: PIL.

1 – PIL: Python Imaging Library

PIL or Python Imaging Library is a package that exposes many functions to manipulate images from a Python script. PIL official homepage is HERE. The current version of PIL is PIL 1.1.7 and is available for Python 2.3 up to Python 2.7. I’ll use PIL 1.1.7 for Python 2.6 in this article.

Under Windows (XP, Vista or Seven) the installation of PIL is rather simple: just launch the PIL Windows installer and you’re ok. Of course you need a valid Python 2.6.6 installation before.

PIL documentation is available here:

2 – Loading an image

Here is a small INIT script that uses PIL to load an image and display it. If you need it, PIL version can be found in the Image.VERSION variable.

import HYP_Utils
import sys

from PIL import Image

scriptDir = HYP_Utils.GetDemoDir()

PIL_Version = Image.VERSION

img_filename = "%s/flower.jpg" % scriptDir
im = Image.open(img_filename)
im.show()

Under Windows, the Image.show() function saves the image to a temporary file and calls the default image viewer utility. On my system, Irfanview is called to display the image:

GeeXLab, Python, PIL
Demo_Python_PIL_01.xml

3 – Saving an image

Just call the Image.save() function. You want to save to JPEG format? Just add the .jpg extension to your image filename… Same thing for the other formats.

Formats supported in reading AND writing: *.bmp, *.gif, *.jpg, *.msp, *.pcx, *.png, *.ppm, *.tiff and .xbm.

Here is a simple JPG to BMP converter:

import HYP_Utils
from PIL import Image

scriptDir = HYP_Utils.GetDemoDir()

PIL_Version = Image.VERSION

img_filename = "%s/flower.jpg" % scriptDir
im = Image.open(img_filename)
im.save("%s/flower.bmp" % scriptDir)

4 – Reading the pixels

There are two functions that make it possible to read the pixmap (or pixel data): Image.getpixel() and Image.getdata().

Image.getpixel() returns the value of a single pixel. Just give a tuple with the X and Y coordinates and getpixel() returns a 3-tuple RGB for a RGB image or a single value for a luminance image. Image.getdata() returns the complete pixmap. You need the Python function list() to create the pixmap list of RGB tuples.

Here is a code snippet that loads an image with PIL, creates a texture object with GeeXLab Python API and fills the texture with image pixels.

import HYP_Utils
import HYP_Texture
import HYP_Material
import sys

from PIL import Image

scriptDir = HYP_Utils.GetDemoDir()

PIL_Version = Image.VERSION

img_filename = "%s/flower.jpg" % scriptDir
im = Image.open(img_filename)

imageW = im.size[0]
imageH = im.size[1]

TEXTURE_2D = 2
RGB_BYTE = 2
texId = HYP_Texture.Create(TEXTURE_2D, RGB_BYTE, imageW, imageH, 0)

matId = HYP_Material.GetId("plane1_mat")
HYP_Material.AddTexture(matId, texId)

if (im.mode == "RGB"):
  for y in range(0, imageH):
    for x in range(0, imageW):
      offset = y*imageW + x
      xy = (x, y)
      rgb = im.getpixel(xy)
      HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb[0], rgb[1], rgb[2]) 
elif (imout.mode == "L"):
  for y in range(0, imageH):
    for x in range(0, imageW):
      offset = y*imageW + x
      xy = (x, y)
      rgb = im.getpixel(xy)
      HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb, rgb, rgb) 

With Image.getdata(), the last lines of the previous script would be:

pixels = list(im.getdata())
if (im.mode == "RGB"):
  for y in range(0, imageH):
    for x in range(0, imageW):
      offset = y*imageW + x
      rgb = pixels[offset]
      HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb[0], rgb[1], rgb[2]) 
elif (imout.mode == "L"):
  for y in range(0, imageH):
    for x in range(0, imageW):
      offset = y*imageW + x
      rgb = pixels[offset]
      HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb, rgb, rgb) 

GeeXLab, Python, PIL
Demo_Python_PIL_02.xml

5 – Image Processing

You can easily apply common image filters with PIL: blur, emboss, sharpen, etc. Just import the ImageFilter module:

from PIL import Image
from PIL import ImageFilter

...

i = Image.open(img_filename)
im = i.filter(ImageFilter.EMBOSS)
#im = i.filter(ImageFilter.FIND_EDGES)

...

Predefined filters are: BLUR, CONTOUR, DETAIL, EDGE_ENHANCE, EDGE_ENHANCE_MORE, EMBOSS, FIND_EDGES, SMOOTH, SMOOTH_MORE, and SHARPEN.

GeeXLab, Python, PIL
Demo_Python_PIL_03.xml – EMBOSS

GeeXLab, Python, PIL
Demo_Python_PIL_03.xml – FIND_EDGES

There is also a module called ImageOps that exposes image processing functions such as colorize(), flip(), grayscale(), invert(), mirror(), solarize(), or posterize().

GeeXLab, Python, PIL
Demo_Python_PIL_04.xml – solarise()

GeeXLab, Python, PIL
Demo_Python_PIL_04.xml – posterize()

6 – Adding a Watermark

ImageDraw and ImageFont give to PIL the capability to write text on an image as well as to draw lines or points. Here is a code snippet that shows a simple batch converter with PIL: it reads all jpg files of a folder, adds the watermark (a cross and the “GEEXLAB” string) and saves the images with the gxl_ prefix.

import HYP_Utils
import os, glob
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

scriptDir = HYP_Utils.GetDemoDir()

ft = ImageFont.load("timR24.pil")

os.chdir(scriptDir)
file_list = glob.glob("*.jpg")
for f in file_list:
  im = Image.open(scriptDir + str(f))
  draw = ImageDraw.Draw(im)
  draw.line((0, 0) + im.size, fill=(255, 255, 255))
  draw.line((0, im.size[1], im.size[0], 0), fill=(255, 255, 255))
  wh = ft.getsize("G E E X L A B")
  draw.text((im.size[0]/2 - wh[0]/2, im.size[1]/2 + 20), "G E E X L A B",\
  fill=(255, 255, 0), font=ft)
  draw.text((im.size[0]/2 - wh[0]/2, im.size[1]/2 - 60), "G E E X L A B",\        
  fill=(255, 255, 0), font=ft)
  del draw  
  im.save(scriptDir + "gxl_" + str(f))

The file timR24.pil comes from a PIL fonts package. You can download it HERE.

GeeXLab, Python, PIL
Demo_Python_PIL_05.xml

There is also a nice function ImageFont.truetype() but it didn’t work on my system because this function relies on the _imagingft.pyd library (actually a DLL) that could not be loaded due to a Visual C runtime problem. Here is the error in GeeXLab console:

# ERROR: Python – Script [initScene] has a runtime error. Error line: 29 – Error object: – Error data: DLL load failed: The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.. Script disabled.

This link has some details about this problem.

7 – Downloads

If you want to play with the GeeXLab demos, you can find them in the code samples demo pack available HERE. The demos related to PIL are in the Python_PIL/ folder.

Here is a patch for GeeXLab 0.2.5 + Python 2.6.6 that you must use to run the demos. Just unzip the archive and copy the DLL into GeeXLab_Python_Lua/ folder.

PATCH DOWNLOAD: GeeXLabCore.dll

7 thoughts on “(Tutorial) First Steps with PIL: Python Imaging Library”

  1. Pingback: [Demotool] GeeXLab 0.2.6 Released With Gamma Correction Support - 3D Tech News, Pixel Hacking, Data Visualization and 3D Programming - Geeks3D.com

  2. Alex

    Hi, in which case you run into Visual C runtime problem ?

    I just tried Python_PIL example (toggled comment on #70-#71) and it runs with no problem. So I am curious what causes this kind of error.

  3. hoff

    cool stuff! any ideas what I could use on a Linux machine to do some 2D image rotating in 3D space (not real time), ideally using python/PIL, but any other technologies are also cool…? thanks for the help in advance! hoff

  4. J Thomas

    hoff, you can do easy rotations with VPython.

    Maybe they’re easy with GeexLab, I’m just about to start that and I don’t know what it can do yet. But VPython does that. VPython is extremely weak at saving images, though, so PIL and maybe GeexLab can fill in there — assuming there’s something VPython does that’s worth mixing it in with them.

  5. vicky

    how represent image into pixel matrix form… i have to represent image in 8*8 prxel matrix i have to perform operation on tht matrix using python so , please help me..

  6. Jyoti

    I tried looking at what you suggested. Things worked pretty fine, until I wrote im.show()..it gave an error with an empty window photo viewer opened……. window photo viewer cannot open the picture because picture is either deleted or moved… I can see the temp…TMPdfjfjs.BMP, there too…I am not good at these stuff either…..can you help me what exactly this is? I am running python 2.5, with PIL 1.1.7.(I am on window 7)

    Thanks

Comments are closed.