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         """
 171         \n
 172         DOUBLE-CLICK ME TO CHANGE THE ROTATION DIRECTION.
 173         \n\n
 174         RIGHT-CLICK ME TO QUIT THIS DEMO.
 175         """
 176         
 177         wx.Frame.__init__( self, None, -1, 'Double Buffered Test',
 178                            pos=(0, 0), size=(700, 700) )
 179                            
 180         self.drawWindow = DrawWindow( self, -1, imgFilename )   # Instantiate
 181         self.imgFilename = imgFilename                          # Only for OnDrawTimer()
 182         
 183         # Set the frame size. Discard the opened pilImage afterward reading its size.
 184         pilImage = Image.open( imgFilename )
 185         imgSizeX, imgSizeY = pilImage.size
 186         clientSizeX = int( (1.414 * imgSizeX) + 25 )  # Max axis size necessary to completely show the rotated image
 187         clientSizeY = int( (1.414 * imgSizeY) + 25 )  # plus an arbitrary 25 pixel margin.
 188         maxSize = clientSizeX
 189         if clientSizeY > clientSizeX :    maxSize = clientSizeY
 190         self.SetClientSize( (maxSize, maxSize) )
 191             
 192         self.Show()                                 # The drawing window  must be shown before drawing.
 193         
 194         # Initial unrotated drawing. Subsequent timer events will call self.drawWindow.DrawRotated()
 195         # Subsequent draws will be incrementally rotated.
 196         self.drawWindow.DrawRotated( imgFilename )
 197         
 198         print self.runtimeMessage
 199                                                     
 200         #---------------
 201         
 202         # Rotate the image and redisplay it every 50 milliseconds.
 203         self.drawTimer = wx.Timer( self, id=wx.NewId() )
 204         self.Bind( wx.EVT_TIMER, self.OnDrawTimer )
 205         self.drawTimer.Start( 50, oneShot=False )
 206 
 207     #end def __init__
 208     
 209     #--------------
 210 
 211     def OnDrawTimer( self, event ) :
 212         self.drawWindow.DrawRotated( self.imgFilename )             # The file is read in only once !
 213         self.drawWindow.angle += self.drawWindow.angleIncrement     # Adjust for the next .DrawRotated()
 214     #end def                                                        #  on the next timer tick.
 215 
 216 #end class TestFrame
 217 
 218 #------------------------------------------------------------------------------
 219 
 220 if __name__ == '__main__' :
 221 
 222     inputFilename = 'STRIPES_ALPHA.PNG'     # Default filename when no given command-line filename.
 223     
 224     if len( sys.argv ) > 1 :    inputFilename = sys.argv[1]
 225     if not os.path.exists( inputFilename ) :
 226         print '\n####  Image Filename Not Found [ %s ]\n' % (inputFilename)
 227         os._exit(1)
 228     #end if
 229 
 230     myApp = wx.PySimpleApp( redirect=False )
 231     appFrame = TestFrame( inputFilename )     # TestFrame() must control when to .Show()
 232     myApp.MainLoop()
 233 
 234 #end if
 235 
 236 #------------------------------------------------------------------------------

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 pool-173-62-190-62)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.