[Tutorial] First Steps with PIL: Python Imaging Library
- 1 – PIL: Python Imaging Library
- 2 – Loading an image
- 3 – Saving an image
- 4 – Reading the pixels
- 5 – Image Processing
- 6 – Adding a Watermark
- 7 – Downloads
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.
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:
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 imageH = im.size 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, rgb, rgb) 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, rgb, rgb) 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)
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.
Demo_Python_PIL_03.xml – EMBOSS
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().
Demo_Python_PIL_04.xml – solarise()
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, im.size, 0), fill=(255, 255, 255)) wh = ft.getsize("G E E X L A B") draw.text((im.size/2 - wh/2, im.size/2 + 20), "G E E X L A B",\ fill=(255, 255, 0), font=ft) draw.text((im.size/2 - wh/2, im.size/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.
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