I've finally cracked the puzzle. WX cannot always read alpha properly
from files, yet looks like it can handle images that already have it.
I used Pil to read the PNG files, rotate them, then convert them into
wxImage format. I had to rewrite the conversion routines found on
PyWiki: 'Working With Images' @
http://wiki.wxpython.org/index.cgi/WorkingWithImages
The routines now handle alpha planes under all circumstances. I'm
sure there's equivalent ways to do the same with PythonMagick. Here's
the WX<==>PIL conversions:
=============================================
"""
Based on pyWiki 'Working With Images' @
http://wiki.wxpython.org/index.cgi/WorkingWithImages
Modified to properly copy, create or delete any alpha data under *all*
possible situations.
Developed using wxPython 2.5.4 and PIL 1.1.7
Created by Ray Pasco on 2010-05-19
This code may be altered and/or distributed for any purpose
whatsoever.
Use at you own risk. Printouts are suitable for framing or wrapping
fish.
"""
import wx # wxImage <==> WxBitmap
import Image # Only if you convert from to or from Pil images.
#------------------------------------------------------------------------------
# image type image type image type
# 1 2 3
# wxBitmap <==> wxImage <==> pilImage
def WxBitmapToWxImage( wxBitmap ): # 1 ==> 2
return wx.ImageFromBitmap( wxBitmap )
#end def
def WxBitmapToPilImage( wxBitmap ): # 1 ==> 3
return WxImageToPilImage( WxBitmapToWxImage( wxBitmap ) )
#end def
#----------------------------
def PilImageToWxBitmap( pilImage ): # 3 ==> 1
return WxImageToWxBitmap( PilImageToWxImage( pilImage ) )
#end def
def PilImageToWxImage( pilImage, wantAlpha=True,
createAlpha=False ) : # 3 ==> 2
# If the pilImage has alpha, then automatically preserve it in the
wxImage
# unless alpha preservation is disabled by setting
wantAlpha=False.
#
# If the pilImage mode is RGB, the optionally create a new wxImage
alpha plane/band
# by setting createAlpha=True.
#
if (pilImage.mode == 'RGBA') : # ignore createAlpha since
mode=RGBA
# Extract the existing alpha band from the the wxImage.
wxImage = wx.EmptyImage( *pilImage.size )
# Pil's creator of "in-place" methods ought to be horse-
whipped !
pilRgbImage = pilImage # Do NOT let Pil's in-place
conversion alter the original image !
pilRgbDataStr = pilRgbImage.convert( 'RGB' ).tostring() #
"In-place" method destroys the original !!
wxImage.SetData( pilRgbDataStr ) # Just the RGB data from
the new RGB mode image.
if wantAlpha :
# Copy just the pilImage alpha into wxImage alpha plane.
pilAlphaStr = pilImage.tostring()
pilAlphaStr = pilAlphaStr[3::4]
wxImage.SetAlphaData( pilAlphaStr )
#end if
elif (pilImage.mode == 'RGB') : # RGB mode pilImage RGB planes
==> wxImage
wxImage = wx.EmptyImage( *pilImage.size )
wxImage.SetData( pilImage.tostring() )
if createAlpha : # Create a brand new alpha
plane/band/layer/channel
# Create and insert 100% opaque alpha (values=255)
# .convert( 'RGBA' ) always adds a brand new 100% opaque
pilImage alpha plane.
# .SetAlphaData() adds a brand new wxImage alpha plane in
this case
# since the wxImage doesn't originally have any alpha
plane.
pilRgbaImage = pilImage # Do NOT let Pil's in-place
conversion alter the original image !
wxImage.SetAlphaData( pilRgbaImage.convert( 'RGBA' ).tostring()
[3::4] )
#end if
#end if
return wxImage # May or may not have an alpha plane depending
on the input image mode
# and the given option flags.
#end def
#----------------------------
def WxImageToWxBitmap( wxImage ): # 2 ==> 1
return wxImage.ConvertToBitmap()
#end def
def WxImageToPilImage( wxImage, wantAlpha=True ): # 2 ==> 3 Assume
keeping any alpha channel
pilImage = Image.new( 'RGB', (wxImage.GetWidth(),
wxImage.GetHeight()), color=(255, 255, 255) )
pilImage.fromstring( wxImage.GetData() ) # RGB data
if wxImage.HasAlpha() and wantAlpha : # Convert and insert
the wxImage alpha channel
pilImage.convert( 'RGBA' ) # add an alpha channel
with dummy values
wxAlphaStr = wxImage.GetAlphaData() # extract the wxImage
alpha channel in "string" format
# Create a single band(Pil)/channel(wx) dummy image that is
the dimensions of the wxImage.
pilAlphaImage = Image.new( 'L', (wxImage.GetWidth(),
wxImage.GetHeight()) )
# Insert the wx alpha data string into the new Pil image.
pilAlphaImage = Image.fromstring( 'L', (wxImage.GetWidth(),
wxImage.GetHeight()), wxAlphaStr )
# Copy the single-band Pil alpha-data image into the alpha
channel of the target RGBA image.
pilImage.putalpha( pilAlphaImage ) # all done !
#end if
return pilImage
#end def
=============================================
Here's the sample app that simply displays a rotated image read from a
file and displayed on a dc. All I wanted to accomplish was to make
transparent the extended margins that are produced as a consequence of
rotating a rectangular image. I works for both images that both have
and don't have alpha planes and preserves the alpha in the ones that
do:
=============================================
"""
Tested on Win7 64-bit (6.1.7600) and Win XP SP3 (5.1.2600) using an
AMD processor.
Python 32-bit installed.
Platform Windows 6.1.7600
Python 2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit
(Intel)]
Python wx 2.8.10.1
Pil 1.1.7
Created by Ray Pasco on 2010-05-19
This code may be altered and/or distributed for any purpose
whatsoever.
Use at you own risk. Printouts are suitable for framing or wrapping
fish.
"""
import wx
import random # set rotation direction randomly
import Image # Pil package. Reads all common image
files automatically if the extension is correct.
import ImageConversions # Image type conversions that handle
variable-valued alpha planes properly
#------------------------------------------------------------------------------
import sys, platform
print
print 'Platform ', platform.system()
if platform.system().lower() == 'windows' :
print 'Windows ', platform.win32_ver()[1] # for MSW kbhit()
print 'Python ', sys.version
print 'Wx ', wx.VERSION_STRING
print 'Pil ', Image.VERSION
print
#------------------------------------------------------------------------------
class DrawWindow( wx.Window ) : # Window within the parent
container.
def __init__( self, parent=None, id=-1,
imgFilename='defaultPngFilename' ) :
wx.Window.__init__( self, parent=parent, id=id )
self.imgFilename = imgFilename
self.Bind( wx.EVT_SIZE, self.OnSize ) # For redraws
other than on the timer events.
self.Bind( wx.EVT_KEY_DOWN, self.OnKeyDown )
#end def
#------------------------
def OnSize( self, event ) :
self.DrawRotated( self.imgFilename )
event.Skip()
#end def
#------------------------
def OnKeyDown( self, event ) :
wx.Exit() # Easy way to end this program.
E.g., press the space-bar.
event.Skip()
#end def
#------------------------
def DrawRotated( self, imgFilename ) :
clientWid, clientHgt = self.GetClientSizeTuple()
bufferedDC = wx.BufferedDC( wx.ClientDC(self),
wx.EmptyBitmap( clientWid, clientHgt ) )
bufferedDC.SetBackground( wx.Brush( (220, 220, 240) ) ) #
powder blue .Clear() color
bufferedDC.Clear() # "Whitewash" over the
previous drawing.
try :
dummy = self.angle # ? Have the following
local vars been created yet ?
except : # No, assign only once:
self.angle = 0.0 # instantiate and
initialize
self.angleIncrement = 0.010 # heuristic value; given
in radians; for next timer enevt draw.
self.direction = ( (random.randint( 0, 1 ) * 2) ) - 1
self.angleIncrement *= self.direction # 50% chance of
positive or negative
self.DEGREES_PER_RADIAN = 180.0 / 3.14159
self.rotationCenterDummy = (0, 0) # has
no effect in wx image rotation
self.offsetAfterRotationDummy = wx.Point(100, 0) # has
no effect in wx image rotation
# Read file using Pil.
# Wx incorrectly reads the alpha channel from many PNG
files that have one.
pilImage = Image.open( imgFilename )
# Convert pil to RGBA if it is only RGB. Convert the
RGBA Pil to a wxImage.
self.wxImage =
ImageConversions.PilImageToWxImage( pilImage, createAlpha=True )
#end try
## The Pil way to rotate images.
# Pil rotation is smart enough that when pilImage margins are
enlarged by rotation
# the extended margins' "fill color" is transparent if the
image.mode is RGBA'.
# This is done automatically for 'RGBA' images and the alpha
band
# doesn't have to have any particular values. They can all
be set to 255 (opaque)
# and the extended rotation margins will still be set
totally transparent.
# This is the "naturally expected" way to extend an image.
#
# Also, Pil rotation creates less image-to-image positioning
jitter
# by specifying the filter type to be "Image.BICUBIC".
# Cropping the centered, rotated image to the original image
size
# is done by specifying "expand=False".
#
# The resultant outer edge aliasing (the "jaggies") needs to
be addressed
# with both rotation methods.
#
pilImage = ImageConversions.WxImageToPilImage( self.wxImage )
# Note that Pil's .rotate() is NOT an "in-place" method,
but .convert() IS one !
rotatedPilImage =
pilImage.rotate( self.angle*self.DEGREES_PER_RADIAN,
Image.BICUBIC,
expand=True )
rotatedImage =
ImageConversions.PilImageToWxImage( rotatedPilImage )
#
# The WX way (angle values are given in radians).
# Rotation image margins are set to black if .HasAlpha() is
False.
# Wx often misinterprets PNG file alpha channels. I.e., use
Pil to read PNG files with alpha.
## img.Rotate( angle, centre_of_rotation, interpolate,
offset_after_rotation )
#rotatedImage = self.wxImage.Rotate( self.angle,
self.rotationCenterDummy, True, self.offsetAfterRotationDummy )
# Center the rotated image on the client area.
imageWid, imageHgt = rotatedImage.GetSize()
offsetX = (clientWid - imageWid) / 2
offsetY = (clientHgt - imageHgt) / 2
bufferedDC.DrawBitmap( rotatedImage.ConvertToBitmap(),
offsetX, offsetY )
#end def UpdateDrawing
#end class DrawWindow
#------------------------------------------------------------------------------
class TestFrame( wx.Frame ) :
"""Create the bare minimum of an app frame.
This will be completely filled with the DrawWindow()."""
def __init__( self, imgFilename ) :
wx.Frame.__init__( self, None, -1, 'Double Buffered Test',
pos=(0, 0), size=(700, 700) )
self.drawWindow = DrawWindow( self, -1, imgFilename ) #
Instantiate
self.imgFilename = imgFilename # Only
for OnDrawTimer()
self.Show() # The drawing
window must be shown before drawing.
self.drawWindow.DrawRotated( imgFilename ) # Initial
(unrotated) drawing. Timer will update the draw window afterwards.
# Subsequent draws
will be incrementally rotated.
#--------------------
self.drawTimer = wx.Timer( self, id=wx.NewId() )
self.Bind( wx.EVT_TIMER, self.OnDrawTimer )
self.drawTimer.Start( 50, oneShot=False )
#end def __init__
def OnDrawTimer( self, event ) :
self.drawWindow.DrawRotated( self.imgFilename ) #
The filename is used only once.
self.drawWindow.angle += self.drawWindow.angleIncrement #
for the next .DrawRotated()
#end def
#------------------------------------------------------------------------------
# Substitute your own filename here. File may have alpha
(transparency).
# If so, it will be used as-is. Only PNG and TIFF files support
variable alpha.
# GIF (palletted) files support only either 0% or 100% transparency
per pixel.
inputFilename = 'STRIPES_ALPHA.PNG'
myApp = wx.PySimpleApp( redirect=False )
appFrame = TestFrame( inputFilename ) # TestFrame() must
control .Show()
myApp.MainLoop()
=============================================
On May 18, 10:59 am, Mike Driscoll <
kyoso...@gmail.com> wrote:
> On May 18, 9:07 am, Ray Pasco <
raoulpa...@yahoo.com> wrote:
>
> > Thanks, but I still haven't found examples or a tutorial to show how
> > alpha is handled and manipulated using wx.
>
> Seehttp://
wiki.wxpython.org/index.cgi/WorkingWithImages