Suppose you need to create images pixel by pixel, and they are large images, and you want to do it quickly… How do you do it? with PIL putpixel()? with wxImage SetRGB()? No, they are to slow, you need to use Numpy.
You might be ok with smaller images, but when you start generating images that are around 5000,5000 pixels large, speed starts to matter.
if setting each pixel individually using an image library is too slow, how can you do it? You build your image as an array and then load it into an image tool in bulk.
So whats the catch?
The catch is understanding how PIL’s frombuffer() method works. It expects raw data. You can think of it as a single integer for each pixel. But how do you store all 4 color values (Red,Green,Blue + Alpha transparency) in a single integer value? You will need to do a little hex editing.
If you are familiar with writing colors as hex values (ie #FF0000
for red) you know that the first two characters are for red, the second two are for green and the last two are for blue. You could also think of that as the three separate hex values 0xFF 0x00 0x00
.
In a python prompt it’s easy to convert from Hex to Decimal
[python]
>>> 0xFF
255
>>> 0x00
0
>>> 0x80
128
[/python]
We need to add another value for alpha transparency, as you can see above, 0x80 will give us 50% transparency.
Now, you can make a single integer out of those 4 values. The only trick is to now put them together in reverse order (apha,blue,green,red) or in the format 0xAABBGGRR).
[python]
>>> 0x800000FF
2147483903L
[/python]
Now we have an integer which represents 50% transparent red to set in our numeric array.
Now we get to creating the array for our image, we only need to know what type of array to create. Our integers for a RGBA image are unsigned 32bit integers. If we were creating a RGB image we would use unsigned 16bit integers.
[python]
import numpy
w,h=1024,768 ## this is the size image we want to create
img = numpy.empty((w,h),numpy.uint32)
img.shape=h,w ## set the array shape to our image shape; yes i know it seems backwards, but it's not!
[/python]
Now img
is a numpy array we can use to set the pixels to whatever value we want. By default it is already a Black image with 100% alpha transparency which is convenient. It may seem odd to set the shape to height,width; and well, it is odd. I’m not quite sure why that seems backwards.
Here is how to set the top left pixel (the one at position 0,0) to our semi-transparent red color.
[python]
img[0,0]=0x800000FF
[/python]
With numpy you can also use a fancy notation to set multiple contingent pixels to the same value. This is where we get a huge speed improvement over setting pixels individually. To set the right half of the image (pixels 512-1024) of rows 10-20 solid blue, we would do this:
[python]
img[10:21,512:1024]=0xFFFF0000
[/python]
The generic format is img[startrow:finishrow+1,starx:endx]
Now a few more fun rows
[python]
img[21:401,758:769]=0xFFFF0000
img[401:411,512:769]=0xFFFF0000
[/python]
The last step is to convert the array into an image and save it as a png image.
[python]
from PIL import Image
pilImage = Image.frombuffer('RGBA',(w,h),img,'raw','RGBA',0,1)
pilImage.save('my.png')
[/python]
And there you have it