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: