[Image-SIG] Conversion from RGB(A) to P(A)
Ray Pasco
pascor at hotpop.com
Mon Nov 15 23:40:58 CET 2004
This isn't a question, but an answer. After getting frustrated that
saving a low color count
RGB or RGBA image to a paletted P file would cause color dithering and
loss of any transparency (alpha) info, I wrote a routine that would
create an "exact
palette" for RGB(A) images that started out with 256 or less colors. No
dithering.
Also, if a RGBA image is passed in, all pixels that have alpha values
less than 255
("active" alpha) will be transformed into a single "transparent" color
in the P file. If the input
RGB(A) has more than 256 colors, the default PIL palette/color dithering
will be used,
but "active" alpha pixels will still become transparent
This isn't optimized or particularly pretty code, but gets the job done.
The attached file calls for various types of PNG RGB(A) images, so
substitute
your own that meet the description at the top of the blocks of test code.
Ray Pasco
-------------- next part --------------
import Image
import sys, os
from listpalette import ListPalette
def RgbHistogram (imgRgb, verbose=False):
hist = None
# Form a histogram dictionary whose keys are the repr(color)
if verbose: print 'RgbHistogram: Getting datalist ...'
datalist = list (imgRgb.getdata())
if verbose: print ' ... finished'
dicthist = {}
xsize, ysize = imgRgb.size
numcolors = 0
for i in xrange (xsize * ysize):
color = datalist [i]
key = repr (color)
if dicthist.has_key (key): # color already exists
dicthist [key] += 1 # increment the count
else: # make a new key
dicthist [key] = 1 # instantiate a new entry and init the count
numcolors += 1
if numcolors > 256:
return None # Error flag: use PIL default color palette/dithering
#end if
#end if
#end for
# reform the dictionary into a sorted histogram of the form: (count, (r, g, b))
hist = []
for key in dicthist.iterkeys():
count = dicthist [key]
color = eval (key)
hist.append ( (count, color) )
#end for
hist.sort()
hist.reverse() # make largest counts first
return hist
#end def RgbHistogram
def Getalphaindex (imgP, maskinv):
# Find the least used color (palette entry, actually)
# This will be the color to which transparency will be set when saving the file
xsize, ysize = imgP.size
hist = imgP.histogram (maskinv) # get counts for all colors having non-active alphas
indexleastused = 255 # arbitrary starting least used palette index
leastcount = xsize * ysize # max possible count
for i in xrange (len (hist)): # palette size
if hist [i] < leastcount:
leastcount = hist [i] # the count
indexleastused = i # the palette index
#end if
if hist [i] == 0: break # first 0 entry: done
#end if
return (indexleastused, leastcount)
#end def Getalphaindex
def Rgb2p (imgRgb, verbose=False): # image could be a "RGBA"
verbose = False
imgP = None
datalist = None
size = imgRgb.size
xsize = size [0]
ysize = size [1]
if verbose:
print
print 'xsize, ysize =', xsize, ysize
#end if
hasalpha = False
if imgRgb.mode == 'RGBA':
hasalpha = True # for post=processing to create transparency
if verbose: print 'Rgb2p: Input image is RGBA'
# Create a mask and its inverse
source = imgRgb.split()
R, G, B, A = 0, 1, 2, 3 # band indices
mask = source [A].point (lambda i: i < 255 and 255) # = True on active alphas
maskinv = source [A].point (lambda i: i == 255 ) # = True on inactive alphas
#end if
# find the most popular colors, limiting the max number to 256
# any "excess" colors with be transformed later to the closest palette match
# create a basic palette and stuff it with the found colors
palette = [] # will be [0, 0, 0, ... 255, 255, 255]
for i in xrange (256):
palette.append (i); palette.append (i); palette.append (i)
#end for
hist = RgbHistogram (imgRgb, verbose=verbose)
if hist == None: # colors > 256: use PIL dithered image & palette
if verbose: print 'Number of colors > 256: Using PIL dithered palette.'
imgP = imgRgb.convert ('P')
if hasalpha:
indexleastused, leastcount = Getalphaindex (imgP, maskinv)
if verbose:
print '\nHigh color-count image: least used color index, leastcount =', indexleastused, leastcount
#end if
return (imgP, indexleastused, mask)
else:
return (imgP)
#end if
#end if
# Make two lists of the colors.
colors = []
colorsAndIndices = []
for i in xrange (len (hist)):
if hasalpha:
r, g, b, a = hist [i][1] # pick off the color tuple
else:
r, g, b = hist [i][1] # pick off the color tuple
#end if
palette [i*3 + 0] = r
palette [i*3 + 1] = g
palette [i*3 + 2] = b
colors.append (hist [i][1]) # [color_tuple, color_tuple, ...]
colorsAndIndices.append ( (hist[i][1], i) ) # [(color_tuple, palette_index), ...]
#end for
imgP = Image.new ('P', size) # Create a brand new paletted image
imgP.putpalette (palette) # Install the palette
# Rewrite the entire image using new palette's indices.
if verbose: print 'Defining the new image using the newly created palette ...'
# Each pixel gets a palette color index
if datalist == None:
datalist = list (imgRgb.getdata()) # xsize*ysize list of color 3-tuples
#end if
pxlctr = 0
colctr = 0
for yord in xrange (ysize):
for xord in xrange (xsize):
pxlcolor = datalist [yord*xsize + xord] # org image color tuple
try:
index = colors.index (pxlcolor)
paletteindex = colorsAndIndices [index] [1] # a simple lookup
except:
print '### RGB2P.py:INTERNAL ERROR: index =', index,
print 'of color', pxlcolor, 'NOT found in colors =', colors
sys.exit (1)
#end try
imgP.putpixel ((xord, yord), paletteindex)
# A simple progress indicator:
pxlctr += 1
if verbose and ((pxlctr % 1000) == 0):
valstr = '%8i' % pxlctr
print valstr,
colctr += 1
if colctr >= 8:
colctr = 0
print
#end if
#end if
#end for xord
#end for yord
if verbose: print
if hasalpha:
indexleastused, leastcount = Getalphaindex (imgP, maskinv)
if verbose:
print
print 'Low color-count image: least used color index, leastcount =', indexleastused, leastcount
#end if
return (imgP, indexleastused, mask)
else:
return (imgP)
#end if
#end def Rgb2p
#====================================================================
if __name__ == '__main__':
if 1: # low color, no active alpha ;squares inside squares
imgRgb = Image.new ('RGB', (100, 100))
imgRgb.paste ( (185, 85, 0), (0, 0, 100, 100) ) # red
imgRgb.paste ( (0, 185, 0), (15, 15, 85, 85) ) # green
imgRgb.paste ( (0, 85, 185), (30, 30, 70, 70) ) # blue
#imgRgb.save ('THREECOLOR.RGB.png')
imgP = Rgb2p (imgRgb, verbose=True) # I know only a 1-tuple will be returned
imgP.save ('THREECOLOR.P.png')
#end if
if 1: # high color count, no active alpha, large spatial gradients
ifilename = 'AURORA.RGB.png'
ofilename = 'AURORA.P.png'
imgRgb = Image.open (ifilename)
imgP = Rgb2p (imgRgb, verbose=True) # I know only a 1-tuple will be returned
imgP.save (ofilename)
#end if
if 1: # high color count, no active alpha, small spatial gradients
ifilename = 'LORI.jpg'
ofilename = 'LORI.P.png'
imgRgb = Image.open (ifilename)
imgP = Rgb2p (imgRgb, verbose=True) # I know only a 1-tuple will be returned
imgP.save (ofilename)
#end if
if 1: # high color count, active & varying alpha, small spatial gradients
ifilename = 'TRANS.RGB.png'
ofilename = 'TRANS.PA.png'
imgRgba = Image.open (ifilename)
imgP = Rgb2p (imgRgba, verbose=True) # I know a 3-tuple will be returned
if len (imgP) == 3:
imgP, indexleastused, mask = imgP
print
print 'An alpha-tized paletted image has been returned for image [%s]' % ifilename
xsize, ysize = imgP.size
imgP.paste (indexleastused, (0, 0, xsize, ysize), mask)
imgP.save (ofilename, transparency=indexleastused)
#end if
#end if
if 1: # low color, active alpha ;squares inside squares
imgRgba = Image.new ('RGBA', (100, 100))
imgRgba.paste ( (185, 85, 0, 255), (0, 0, 100, 100) ) # red
imgRgba.paste ( (0, 185, 0, 255), (15, 15, 85, 85) ) # green
imgRgba.paste ( (0, 85, 185, 0), (30, 30, 70, 70) ) # blue
#imgRgba.save ('THREECOLOR.RGBA.png')
imgP = Rgb2p (imgRgba, verbose=True) # I know a 3-tuple will be returned
if len (imgP) == 3:
imgP, indexleastused, mask = imgP
print
print 'An alpha-tized paletted image has been returned for image [%s]' % 'THREECOLOR.RGBA.png'
xsize, ysize = imgP.size
imgP.paste (indexleastused, (0, 0, xsize, ysize), mask)
imgP.save ('THREECOLOR.PA.png', transparency=indexleastused)
#end if
#end if
#end if
More information about the Image-SIG
mailing list