46

I have already taken a look at this question: SO question and seem to have implemented a very similar technique for replacing a single color including the alpha values:

c = Image.open(f)
c = c.convert("RGBA")
w, h = c.size
cnt = 0
for px in c.getdata():
    c.putpixel((int(cnt % w), int(cnt / w)), (255, 0, 0, px[3]))
    cnt += 1                                                                                                   

However, this is very slow. I found this recipe out on the interwebs, but have not had success using it thus far.

What I am trying to do is take various PNG images that consist of a single color, white. Each pixel is 100% white with various alpha values, including alpha = 0. What I want to do is basically colorize the image with a new set color, for instance #ff0000<00-ff>. SO my starting and resulting images would look like this where the left side is my starting image and the right is my ending image (NOTE: background has been changed to a light gray so you can see it since it is actually transparent and you wouldn't be able to see the dots on the left.)

alt text

Any better way to do this?

mimarcel
  • 695
  • 7
  • 20
sberry
  • 128,281
  • 18
  • 138
  • 165

3 Answers3

90

If you have numpy, it provides a much, much faster way to operate on PIL images.

E.g.:

import Image
import numpy as np

im = Image.open('test.png')
im = im.convert('RGBA')

data = np.array(im)   # "data" is a height x width x 4 numpy array
red, green, blue, alpha = data.T # Temporarily unpack the bands for readability

# Replace white with red... (leaves alpha values alone...)
white_areas = (red == 255) & (blue == 255) & (green == 255)
data[..., :-1][white_areas.T] = (255, 0, 0) # Transpose back needed

im2 = Image.fromarray(data)
im2.show()

Edit: It's a slow Monday, so I figured I'd add a couple of examples:

Just to show that it's leaving the alpha values alone, here's the results for a version of your example image with a radial gradient applied to the alpha channel:

Original: alt text

Result: alt text

jucor
  • 21
  • 5
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • 4
    Awesome! Awesome! Awesome! +1 and marked as "Accepted." Huge time saver and results are perfect. – sberry Sep 21 '10 at 13:48
  • shouldn't be , Red Green Blue ? white_areas = – Aysennoussi Dec 18 '14 at 19:29
  • 2
    data[..., :-1][white_areas] = (255, 0, 0) IndexError: index 49 is out of bounds for axis 0 with size 40 – Aysennoussi Dec 18 '14 at 19:46
  • similar error here as Sekai :/ IndexError: index 200 is out of bounds for axis 0 with size 200 – Alex Hall May 01 '15 at 01:58
  • @JoeKington can you please let us know how to solve this error: IndexError: index 1668 is out of bounds for axis 1 with size 1668 ? – Sai Krishna Jul 29 '15 at 07:08
  • It took me a minute to wrap my head around numpy, but it took my image processing (for example) from 0.58s to 0.18s. I am more than impressed, and can't thank you enough for this one Joe. – eklingen Mar 24 '16 at 15:33
  • The answer is some days old, but i really wanted to thank you for this! – tres2k Feb 22 '17 at 17:44
  • Thank you... this saved me a ton of time! – Chris Cogdon Mar 14 '17 at 07:14
  • 1
    I don't get the part `data[...`. What is that `...` supposed to mean? – Eray Erdin Dec 04 '18 at 09:15
  • Can someone explain why there are two transpose? red, green, blue, alpha = data.T # Temporarily unpack the bands for readability # Replace white with red... (leaves alpha values alone...) data[..., :-1][white_areas.T] = (255, 0, 0) # Transpose back needed . – BeHappy Aug 06 '19 at 17:37
13

Try this , in this sample we set the color to black if color is not white .

#!/usr/bin/python
from PIL import Image
import sys

img = Image.open(sys.argv[1])
img = img.convert("RGBA")

pixdata = img.load()

# Clean the background noise, if color != white, then set to black.

for y in xrange(img.size[1]):
    for x in xrange(img.size[0]):
        if pixdata[x, y] == (255, 255, 255, 255):
            pixdata[x, y] = (0, 0, 0, 255)

you can use color picker in gimp to absorb the color and see that's rgba color

Yuda Prawira
  • 12,075
  • 10
  • 46
  • 54
  • on an image created by code instead of Image.open, that pixdata PixelAccess seems not working, i wonder why – Dee Dec 14 '22 at 15:41
5

The Pythonware PIL online book chapter for the Image module stipulates that putpixel() is slow and suggests that it can be sped up by inlining. Or depending on PIL version, using load() instead.

Jonathan Root
  • 535
  • 2
  • 14
  • 31
T.P.
  • 2,975
  • 1
  • 21
  • 20