Wximage From Png With An Alpha Channel

Ray Pasco figured out how to make wx.Image work with a PNG image that had an Alpha Channel. You can read the entire thread on the subject on the Google Groups list. Let's take a look at his solution:

   1 # rotate_image.py
   2 
   3 """
   4 Image rotation using an OnSize event and a wx.bufferedDC.
   5 
   6 Tested on Win7 64-bit (6.1.7600) and Win XP SP3 (5.1.2600)
   7 Python 32-bit installed.
   8 
   9 Platform  Windows 6.1.7600
  10 Python    2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)]
  11 Python wx 2.8.10.1
  12 Pil       1.1.7
  13 
  14 Ray Pasco      2010-06-09
  15 pascor(at)verizon(dot)net
  16 
  17 This code may be altered and/or distributed for any purpose whatsoever.
  18 Use at you own risk. Printouts are suitable for framing or wrapping fish.
  19 """
  20 import sys, os
  21 import wx
  22 import random       # set random rotation direction and to vary other settings
  23 import Image        # Pil package for rotation+filtering all-in-one.
  24 import ImgConv      # wxBitmap <==> wxImage <==> pilImage
  25 
  26 #------------------------------------------------------------------------------
  27 
  28 # What packages are installed ?
  29 import sys, platform
  30 print
  31 print 'Platform ', platform.system()
  32 if platform.system().lower() == 'windows' :
  33     print 'Windows  ', platform.win32_ver()[1]
  34 print 'Python   ', sys.version
  35 print 'Wx       ', wx.VERSION_STRING
  36 print 'Pil      ', Image.VERSION
  37 print
  38 
  39 #------------------------------------------------------------------------------
  40 
  41 class DrawWindow( wx.Window ) :    # Window within the parent container.
  42 
  43     def __init__( self, parent=None, id=-1, imgFilename='defaultPngFilename' ) :
  44 
  45         wx.Window.__init__( self, parent=parent, id=id )
  46 
  47         self.imgFilename = imgFilename
  48 
  49         self.Bind( wx.EVT_SIZE, self.OnSize )           # For redraws other than on the timer events.
  50         self.Bind( wx.EVT_KEY_DOWN, self.OnKeyDown )
  51 
  52         self.Bind( wx.EVT_LEFT_DCLICK, self.OnDoubleClick )     # Change rotation direction
  53 
  54         self.Bind( wx.EVT_RIGHT_UP, self.OnRightUp )  # Quit the app
  55 
  56     #end def
  57 
  58     #------------------------
  59 
  60     def OnDoubleClick( self, event ):
  61 
  62         self.angleIncrement = 0.0 - self.angleIncrement
  63         event.Skip()
  64 
  65     #end def
  66 
  67     #------------------------
  68 
  69     def OnRightUp( self, event ):
  70         wx.Exit()
  71         event.Skip()
  72     #end def
  73 
  74     #------------------------
  75 
  76     def OnSize( self, event ) :
  77         self.DrawRotated( self.imgFilename )
  78         event.Skip()
  79     #end def
  80 
  81     #------------------------
  82 
  83     def OnKeyDown( self, event ) :
  84         wx.Exit()                   # Easy way to end this program.  E.g., press the space-bar.
  85         event.Skip()
  86     #end def
  87 
  88     #------------------------
  89 
  90     def DrawRotated( self, imgFilename ) :
  91 
  92         clientWid, clientHgt = self.GetClientSizeTuple()
  93         bufferedDC = wx.BufferedDC( wx.ClientDC(self), wx.EmptyBitmap( clientWid, clientHgt ) )
  94         bufferedDC.SetBackground( wx.Brush( (220, 220, 240) ) )      # powder blue for .Clear()
  95         bufferedDC.Clear()                  # "Whitewash" over the previous drawing.
  96 
  97         try :                               # ? Have the local vars to be saved been created yet ?
  98             dummy = self.angle              # Try to access any one of the local vars.
  99 
 100         except :                            # No, so instantiate all the local variables only once:   
 101             self.angle = 0.0                # instantiate and initialize
 102             self.angleIncrement = 0.010     # heuristic value specified in radians for next timer event draw.
 103             self.direction = ( (random.randint( 0, 1 ) * 2) ) - 1       # Randomly either 0 or 1.
 104             self.angleIncrement *= self.direction               # 50% chance each of +1 or -1
 105             self.DEGREES_PER_RADIAN = 180.0 / 3.14159
 106             self.rotationCenterDummy = (0, 0)                   # has no effect in wx image rotation
 107             self.offsetAfterRotationDummy = wx.Point(100, 0)    # has no effect in wx image rotation
 108             self.readUsingPilOrWx = True
 109             self.drawCtr = 0
 110 
 111             self.pilImage = Image.open( imgFilename )
 112             self.wxImage  = wx.Image( imgFilename )
 113 
 114         #end try
 115 
 116         """
 117          Pil's  rotation .rotate() method enlarges the image just enough to hold the rotated image.
 118          The extended margins are completely transparent if the image has transparency (alpha).
 119          The alpha values can all be set to 255 (100% opaque) but the extended margins 
 120          will still be settransparent. This is the most reasonable way to extend an image 
 121          with any kind of transparency. (PNG, GIF and TIFF/TIF)
 122         
 123          Also, Pil rotation creates less image-to-image positioning jitter 
 124          when specifying the filter type to be "Image.BICUBIC". This needs more processing time.
 125          WX can also rotate and filter, but it must be done in 2 separate operations.
 126          Note that WX rotation specifies angles in degrees while Pil uses radians.
 127            
 128          Cropping the centered, rotated Pil image to the original image size is optionally done 
 129          by specifying "expand=False".
 130         
 131          The resultant image's outer edge aliasing (the "jaggies") needs to be addressed 
 132          equally with both rotation methods. (I left that to be done as an exercise.)
 133         """
 134         # Note that Pil's .rotate() is NOT an "in-place" method. E.g. :
 135         rotatedPilImage = self.pilImage.rotate( self.angle*self.DEGREES_PER_RADIAN,
 136                                                 Image.BICUBIC, expand=True )
 137 
 138         rotated_wxImage = ImgConv.WxImageFromPilImage( rotatedPilImage )  # Alpha is preserved
 139         #
 140         # The WX way (angle values are given in degrees).
 141         # The specified center-of-rotation and offset-after-rotation don't seem to have any effect.
 142         # Rotation image margins are set to black if .HasAlpha() is False.
 143         #
 144         #rotatedImage = wxImage.Rotate( self.angle, rotationCenterDummy, 
 145         #                               True, offsetAfterRotationDummy )
 146         # Insert a call to a wx filtering method here.
 147 
 148         # Center the rotated image on the client area.
 149         imageWid, imageHgt = rotated_wxImage.GetSize()
 150         offsetX = (clientWid - imageWid) / 2
 151         offsetY = (clientHgt - imageHgt) / 2
 152         # Display the rotated image. Only wxBitmaps can be displayed, not wxImages.
 153         # .DrawBitmap() autmatically "closes" the dc, meaning it finalizes the bitmap in some way.
 154         bufferedDC.DrawBitmap( rotated_wxImage.ConvertToBitmap(), offsetX, offsetY )
 155 
 156     #end def UpdateDrawing
 157 
 158 #end class DrawWindow
 159 
 160 #------------------------------------------------------------------------------
 161 
 162 class TestFrame( wx.Frame ) :
 163     """Create a very simple app frame.
 164     This will be completely filled with the DrawWindow().
 165     """
 166 
 167     def __init__( self, imgFilename ) :
 168 
 169         self.runtimeMessage =  \
        """
 170         \n
 171         DOUBLE-CLICK ME TO CHANGE THE ROTATION DIRECTION.
 172         \n\n
 173         RIGHT-CLICK ME TO QUIT THIS DEMO.
 174         """
 175 
 176         wx.Frame.__init__( self, None, -1, 'Double Buffered Test',
 177                            pos=(0, 0), size=(700, 700) )
 178 
 179         self.drawWindow = DrawWindow( self, -1, imgFilename )   # Instantiate
 180         self.imgFilename = imgFilename                          # Only for OnDrawTimer()
 181 
 182         # Set the frame size. Discard the opened pilImage afterward reading its size.
 183         pilImage = Image.open( imgFilename )
 184         imgSizeX, imgSizeY = pilImage.size
 185         clientSizeX = int( (1.414 * imgSizeX) + 25 )  # Max axis size necessary to completely show the rotated image
 186         clientSizeY = int( (1.414 * imgSizeY) + 25 )  # plus an arbitrary 25 pixel margin.
 187         maxSize = clientSizeX
 188         if clientSizeY > clientSizeX :    maxSize = clientSizeY
 189         self.SetClientSize( (maxSize, maxSize) )
 190 
 191         self.Show()                                 # The drawing window  must be shown before drawing.
 192 
 193         # Initial unrotated drawing. Subsequent timer events will call self.drawWindow.DrawRotated()
 194         # Subsequent draws will be incrementally rotated.
 195         self.drawWindow.DrawRotated( imgFilename )
 196 
 197         print self.runtimeMessage
 198 
 199         #---------------
 200 
 201         # Rotate the image and redisplay it every 50 milliseconds.
 202         self.drawTimer = wx.Timer( self, id=wx.NewId() )
 203         self.Bind( wx.EVT_TIMER, self.OnDrawTimer )
 204         self.drawTimer.Start( 50, oneShot=False )
 205 
 206     #end def __init__
 207 
 208     #--------------
 209 
 210     def OnDrawTimer( self, event ) :
 211         self.drawWindow.DrawRotated( self.imgFilename )             # The file is read in only once !
 212         self.drawWindow.angle += self.drawWindow.angleIncrement     # Adjust for the next .DrawRotated()
 213     #end def                                                        #  on the next timer tick.
 214 
 215 #end class TestFrame
 216 
 217 #------------------------------------------------------------------------------
 218 
 219 if __name__ == '__main__' :
 220 
 221     inputFilename = 'STRIPES_ALPHA.PNG'     # Default filename when no given command-line filename.
 222 
 223     if len( sys.argv ) > 1 :    inputFilename = sys.argv[1]
 224     if not os.path.exists( inputFilename ) :
 225         print '\n####  Image Filename Not Found [ %s ]\n' % (inputFilename)
 226         os._exit(1)
 227     #end if
 228 
 229     myApp = wx.PySimpleApp( redirect=False )
 230     appFrame = TestFrame( inputFilename )     # TestFrame() must control when to .Show()
 231     myApp.MainLoop()
 232 
 233 #end if
 234 
 235 #------------------------------------------------------------------------------

And here's the image conversion python script that the code above references:

   1 # ImgConv.py
   2 
   3 """
   4 Based on pyWiki 'Working With Images' @  http://wiki.wxpython.org/index.cgi/WorkingWithImages
   5 Modified to properly copy, create or remove any alpha in all input/output permutations.
   6 
   7 Tested on Win7 64-bit (6.1.7600) and Win XP SP3 (5.1.2600) using an AMD Athlon AM2 processor. 
   8 Python 32-bit installed.
   9 
  10 Platform  Windows 6.1.7600
  11 Python    2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)]
  12 Python wx 2.8.10.1
  13 Pil       1.1.7
  14 
  15 Ray Pasco      2010-06-09
  16 pascor(at)verizon(dot)net
  17 
  18 This code may be altered and/or distributed for any purpose whatsoever.
  19 Use at you own risk. Printouts are suitable for framing or wrapping fish.
  20 """
  21 import os
  22 import wx       # WxBmap <==> wxImage
  23 import Image    # wxImage <==> PilImage. # Needed only if you convert to or from Pil image formats
  24 
  25 #------------------------------------------------------------------------------
  26 
  27 #   image type      image type     image type
  28 #       1                2              3
  29 #    wxBmap   <==>    wxImage  <==>  pilImage
  30 
  31 def WxImageFromWxBitmap( wxBmap ) :              # 1 ==> 2
  32     return wx.ImageFromBitmap( wxBmap )
  33 #end def
  34 
  35 def PilImageFromWxBitmap( wxBmap ) :             # 1 ==> 3
  36     return PilImageFromWxImage( WxImageFromWxBitmap( wxBmap ) )
  37 #end def
  38 
  39 #----------------------------
  40 
  41 def WxBitmapFromPilImage( pilImage, wantAlpha=True, createAlpha=False, threshold=128 ) :   # 3 ==> 1
  42     return WxBitmapFromWxImage( WxImageFromPilImage( pilImage, wantAlpha, createAlpha ), threshold=128 )
  43 #end def
  44 
  45 def WxImageFromPilImage( pilImage, wantAlpha=True, createAlpha=False ) :    # 3 ==> 2
  46     """    
  47     If the given pilImage has alpha, then preserve it in the wxImage (the default)
  48       unless alpha preservation is specifically disabled by setting wantAlpha=False.
  49     
  50     If the given pilImage mode is RGB, optionally create a new wxImage alpha plane/band 
  51       by setting createAlpha=True.
  52     """
  53     if (pilImage.mode == 'RGBA') :       # ignore flag createAlpha since pilImage is already RGBA
  54 
  55         wxImage = wx.EmptyImage( *pilImage.size  )      # Also reads alpha if present.
  56 
  57         pilRgbImage = pilImage      # Do NOT let Pil's IN-PLACE conversion alter the original image !
  58         pilRgbDataStr = pilRgbImage.convert( 'RGB' ).tostring()   # "IN-PLACE" method alters the source !!
  59         wxImage.SetData( pilRgbDataStr )    # Just the RGB data from the new RGB mode image.
  60 
  61         if wantAlpha :      # This is the default case.
  62             pilRgbaStr = pilImage.tostring()    # Converts the RGBA planes into a list of data strings.
  63             # Extract just the existing alpha data from the the PilImage.
  64             pilAlphaStr = pilRgbaStr[3::4]      # start at index 3 with a stride (skip) of 4.
  65             # Copy the pilImage alpha string data into wxImage alpha plane.
  66             wxImage.SetAlphaData( pilAlphaStr )
  67         #end if
  68 
  69     else :      # For all pilImages without an alpha plane/band/layer/channel.
  70 
  71         pilRgbImage = pilImage          # Copy to prevent Pil's in-place conversion altering the original image !
  72         if pilRgbImage.mode != 'RGB' :      # Convert non-RGB formats to RGB
  73             pilRgbImage = pilRgbImage.convert( 'RGB' )    # IN_PLACE_METHOD - alters the original image
  74         #end if
  75 
  76         #wxImage = wx.EmptyImage( pilRgbImage.size[0], pilRgbImage.size[1] )    # Alternate
  77         wxImage = wx.EmptyImage( *pilRgbImage.size )
  78         wxImage.SetData( pilRgbImage.tostring() )
  79 
  80         # When createAlpha=True create a new wxImage alpha plane/channel/band/layer.
  81         if createAlpha :
  82             # Create and insert 100% opaque alpha (values=255) 
  83             # .convert( 'RGBA' ) always adds a brand new 100% opaque pilImage alpha plane.
  84             # .SetAlphaData() adds a brand new wxImage alpha plane in this case
  85             #   since the wxImage doesn't originally have any alpha plane.
  86             pilRgbaImage = pilRgbImage.convert( 'RGBA' )      # Create an alpha plane
  87             wxImage.SetAlphaData( pilRgbaImage.convert( 'RGBA' ).tostring()[3::4] )
  88         #end if
  89 
  90     #end if
  91 
  92     return wxImage      # May or may not have an alpha plane depending on 
  93                         #   the input image mode and the given option flags.
  94 #end def
  95 
  96 #----------------------------
  97 
  98 def WxBitmapFromWxImage( wxImage, threshold=128 ) :    # 2 ==> 1
  99 
 100     working_wxImage = wxImage          # Don't change the original.
 101     working_wxImage.ConvertAlphaToMask( threshold=threshold )
 102     bmap = wxImage.ConvertToBitmap()
 103 
 104     return bmap
 105 
 106 #end def
 107 
 108 def PilImageFromWxImage( wxImage, wantAlpha=True ) :   # 2 ==> 3  Default is to keep any alpha channel
 109 
 110     image_size = wxImage.GetSize()      # All images here have the same size.
 111 
 112     # Create an RGB pilImage and stuff it with RGB data from the wxImage.
 113     pilImage = Image.new( 'RGB', image_size )
 114     pilImage.fromstring( wxImage.GetData() )
 115 
 116     if wantAlpha and wxImage.HasAlpha() :   # Only wx.Bitmaps use .ConvertAlphaToMask( [0..255] )
 117 
 118         # Create an L pilImage and stuff it with the alpha data extracted from the wxImage.
 119         l_pilImage = Image.new( 'L', image_size )
 120         l_pilImage.fromstring( wxImage.GetAlphaData() )
 121 
 122         # Create an RGBA pil image from the 4 bands.
 123         r_pilImage, g_pilImage, b_pilImage = pilImage.split()
 124         pilImage = Image.merge( 'RGBA', (r_pilImage, g_pilImage, b_pilImage, l_pilImage) )
 125 
 126     #end if
 127 
 128     return pilImage
 129 
 130 #end def PilImageFromWxImage
 131 
 132 #------------------------------------------------------------------------------
 133 
 134 python

Finally, here is a sample image:

http://www.blog.pythonlibrary.org/wiki_images/STRIPES_ALPHA.PNG

PngWithAlphaChannel (last edited 2010-06-10 04:46:30 by WinCrazy)