Size: 23150
Comment:
|
Size: 23217
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 207: | Line 207: |
startPos = (0, -1280) # Directly above the main screen. captureSize = (1280, 800) # How I have configured this monitor within the OS. |
startPos = (0, -1280) # The opper-left coordinate in the extension screen directly above the main screen. captureSize = (1280, 1600) # Double the height of what I've configured each monitor in the OS. |
- Images and bitmaps in wxPython are normally created by loading files from disk. Working with the images in memory is, however, a common task for certain types of application. This recipe set looks at the in-memory manipulation (and particularly the generation) of images for use in wxPython applications.
Table of Contents:
Contents
Formats and Conversions
The Image classes I find myself working with most frequently are wxImage, wxBitmap, and PIL Image. Here are functions I use to convert between them, in all combinations. I like to have these broken out as six individual functions with related names because the usage and naming in the wxWindows API can be confusing. The API also changes relatively frequently. I believe that the older text in this section is outdated, but I haven't had time to go through it. -- RobbShecter
1 # Tested with wxPython 2.3.4.2 and PIL 1.1.3.
2 from wxPython import wx
3 import Image # Only if you need and use the PIL library.
4
5 def bitmapToPil(bitmap):
6 return imageToPil(bitmapToImage(bitmap))
7
8 def bitmapToImage(bitmap):
9 return wx.wxImageFromBitmap(bitmap)
10
11
12 def pilToBitmap(pil):
13 return imageToBitmap(pilToImage(pil))
14
15 def pilToImage(pil):
16 image = wx.wxEmptyImage(pil.size[0], pil.size[1])
17 image.SetData(pil.convert('RGB').tostring())
18 return image
19
20 #Or, if you want to copy alpha channels too (available from wxPython 2.5)
21 def piltoimage(pil,alpha=True):
22 if alpha:
23 image = wx.EmptyImage( *pil.size )
24 image.SetData( pil.convert( "RGB").tostring() )
25 image.SetAlphaData(pil.convert("RGBA").tostring()[3::4])
26 else:
27 image = wx.EmptyImage(pil.size[0], pil.size[1])
28 new_image = pil.convert('RGB')
29 data = new_image.tostring()
30 image.SetData(data)
31 return image
32
33
34 def imageToPil(image):
35 pil = Image.new('RGB', (image.GetWidth(), image.GetHeight()))
36 pil.fromstring(image.GetData())
37 return pil
38
39 def imageToBitmap(image):
40 return image.ConvertToBitmap()
- Conversions among wxImage, wxBitmap, wxCursor, wxIcon and DATA
wxImage to wxBitmap -- myWxImage.ConvertToBitmap() or wxBitmapFromImage(myWxImage) wxImage to DATA -- myWxImage.GetData() returning a string in width * height * 3 format DATA to wxImage -- image = wxImage(); image.SetData( data ) where data is a Python string of length width * height * 3. DATA to wxBitmap -- Go through wxImage to get to wxBitmap. DATA to wxIcon -- Should be possible, but I don't see an overloaded-constructor name for it. wxIcon to wxBitmap -- bitmap = wxEmptyBitmap( icon.GetWidth(), icon.GetHeight()); bitmap.CopyFromIcon( icon )
- wxBitmap to DATA or wxImage -- Probably the most critical missing item, currently you have to use "save to file" then load the bitmap back from disk using a wxImage. DATA or wxImage to wxCursor -- Should be an overridden constructor available, not yet. wxIcon to DATA or wxImage -- To allow for editing of icons, this would be required. wxCursor to DATA or wxImage -- Again, to allow for editing cursors. Would also need methods to get the current hot spot. wxImage to wxIcon -- See DATA to wxIcon above, either one would be fine, but would be nice to have the complete set.
PIL (Python Imaging Library)
- PIL can be used with wxPython if image processing services are required beyond loading the formats built into wxPython. DATA formatted strings can be created from PIL Image objects using the .convert and .tostring methods (as show in the example below). Note the use of the size attribute of the PIL Image to create the empty wxImage object.
def GetBitmap( self ): import Image source = Image.open( r"Z:\mcfport\portfoli\full\claire.jpg", 'r') image = wx.EmptyImage( *source.size ) image.SetData( source.convert( "RGB").tostring() ) # if the image has an alpha channel, you can set it with this line: image.SetAlphaData(source.convert("RGBA").tostring()[3::4]) return image.ConvertToBitmap() # wx.BitmapFromImage(image)
NumPy
This example shows the creation of a bitmap using a NumPy array as the data source. Note that NumPy uses reversed column-row ordering compared to wxPython, so you'll need to make sure that you generate images using height, width, not width, height coordinates. Also note the use of the 'uint8' data type for image data in rgb format.
import wx, numpy def GetBitmap( self, width=32, height=32, colour = (0,0,0) ): array = numpy.zeros( (height, width, 3),'uint8') array[:,:,] = colour image = wx.EmptyImage(width,height) image.SetData( array.tostring()) return image.ConvertToBitmap() # wx.BitmapFromImage(image)
Slicing
NumPy's slicing notation, for our purposes, works as follows: [ rowStart: rowEnd, columnStart: columnEnd, colourPlanes ] To assign a single colour to the entire image:
array[:,:] = (r,g,b)
array[:,:,0] = 255
array[ 0 ] = (r,g,b )
Examples
This example shows use of NumPy's (extended) slicing notation to alter an in-memory image. Note that the slice notation allows for standard negative-value index semantics. Also note the accomodating nature of the assignment operation. Assigning a three-tuple to a row sets every value in the row, while assigning a list of three-tuples will set each value individually (and raise an error if the number of items is incorrect).
import wx, numpy def GetBitmap( self, width=32, height=32, colour = (128,128,128), border=5, borderColour=(255,255,255) ): # creates a bitmap with a border array = numpy.zeros( (height, width, 3), 'uint8') array[border:-border,border:-border,:] = colour array[:border,:,:] = borderColour array[-border:,:,:] = borderColour array[:,:border,:] = borderColour array[:,-border:,:] = borderColour image = wx.EmptyImage(width,height) image.SetData( array.tostring()) return image.ConvertToBitmap()# wx.BitmapFromImage(image)
This example creates a horizontal gradient between two colours. Note that NumPy is automatically converting from float to uint8 data type in the assignment to array, because of the slice indexing. Without the indexing, array would just get overwritten to point to a different numpy array instead of assigning data within the existing one.
import numpy as np def GetBitmap( self, width=640, height=480, leftColour=(255,128,0), rightColour=(64,0,255) ): ## Create a horizontal gradient array = np.zeros( (height, width, 3),'uint8') # alpha is a one dimensional array with a linear gradient from 0.0 to 1.0 alpha = np.linspace( 0., 1., width ) # This uses alpha to linearly interpolate between leftColour and rightColour colourGradient = np.outer(alpha, leftColour) + np.outer((1.-alpha), rightColour) # NumPy's broadcasting rules will assign colourGradient to every row of the destination array array[:,:,:] = colourGradient image = wx.EmptyImage(width,height) image.SetData( array.tostring()) return image.ConvertToBitmap()# wx.BitmapFromImage(image)
Screen Capture
- This example captures the client area of a frame to a wxBitmap object (and from there, a file). Note that there is no error checking done here. Should likely use Ok() or something to check each context. I'm not sure what would happen if this were called before the first painting of the screen, likely the context would have the desktop or something in it. Note: code below only captures the client area of the window. Tagore notes that you can get the whole window with a wx.WindowDC and arbitrary parts of the screen with a wx.ScreenDC.
def OnToFile( self, event ): context = wx.ClientDC( self ) memory = wx.MemoryDC( ) x, y = self.ClientSize bitmap = wx.EmptyBitmap( x, y, -1 ) memory.SelectObject( bitmap ) memory.Blit( 0, 0, x, y, context, 0, 0) memory.SelectObject( wx.NullBitmap) bitmap.SaveFile( "test.bmp", wx.BITMAP_TYPE_BMP )
A Flexible Screen Capture App
Here's a general screen capture app that first captures the whole primary screen, then captures 4 smaller portions of it. GeneralScreenShotWX.py
1 def ScreenCapture( captureStartPos, captureSize ):
2 """
3 A generalized Desktop screen capture.
4
5 My particular monitor configuration:
6 wx.Display( 0 ) refers to the extended Desktop display monitor screen.
7 wx.Display( 1 ) refers to the main Desktop display monitor screen.
8
9 Any particular Desktop screen size is :
10 screenRect = wx.Display( n ).GetGeometry()
11
12 Different wx.Display's in a single system are allowed to have
13 different dimensions.
14
15 """
16
17 # Capture the entire Desktop screen by creating a wx.ScreenDC().
18 # This DC is just a tool linked to a copy of the bitmap of the entire screen.
19 # The screen's size may be extended by using multiple monitors.
20 screenDC = wx.ScreenDC()
21 screenSizeX, screenSizeY = screenDC.Size # The screen's dimensions.
22
23 # The size of the Desktop bitmap area to be returned.
24 captureSizeX, captureSizeY = captureSize
25
26 # Create a new empty (black) destination bitmap with size captureSize.
27 captureBmap = wx.EmptyBitmap( captureSizeX, captureSizeY )
28
29 # Create a DC tool that is associated with this bitmap.
30 memDC = wx.MemoryDC( captureBmap )
31
32 # Configure the upcoming Blit call's position and size parameters
33 # that will be used to copy a portion of the source screen (Desktop) bitmap
34 # into the entire capture destination bitmap.
35 #
36 # The upper-left coordinate of the screen bitmap portion to be copied.
37 screenStartX, screenStartY = captureStartPos
38
39 # Upper-left destination starting coordinate.
40 captureBmapStartX, captureBmapStartY = (0, 0)
41
42 # Copy (blit, "Block Level Transfer") a portion of the screen bitmap
43 # into the returned capture bitmap.
44 # The bitmap associated with memDC (captureBmap) is the blit destination.
45 # # Blit (copy) parameter(s):
46 memDC.Blit( captureBmapStartX, captureBmapStartY, # Copy to captureBmap starting here.
47 captureSizeX, captureSizeY, # Copy an area this size.
48 screenDC, # Copy from this source DC's bitmap.
49 screenStartX, screenStartY ) # Copy from this start coordinate.
50
51 memDC.SelectObject( wx.NullBitmap ) # Finish using this wx.MemoryDC.
52 # Release captureBmap for other uses.
53 return captureBmap
54
55 #end ScreenCapture def
In the application all five captured wxBitmap objects are saved to PNG files.
The wx.ScreenDC treats the entire Desktop screen as a single seamless conglomerate of all exiting wx.Display() areas. Negative coordinate values relative to the main Desktop screen are allowed. Negative ordinate values must be used to capture any portion of an extension monitor screen to the left of or above the primary monitor screen (the one with the Taskbar). For example, I have two monitors where the extension monitor is configured to be directly above the main monitor. I have set both monitors to 1280x800 resolution. To capture the entire extended screen :
1 startPos = (0, -1280) # The opper-left coordinate in the extension screen directly above the main screen.
2 captureSize = (1280, 1600) # Double the height of what I've configured each monitor in the OS.
3 extendedDesktopBmap = ScreenCapture( startPos, captureSize )
Other wxPython add-on packages can take arbitrarily sized screen shots, too, such as ImageMagick and PIL, the Python Imaging Library:
-- Ray Pasco
Write Text to a Bitmap
Writing text into a bitmap is done by creating a context then using the context's DrawText method to do the actual drawing. A simple example:
from wxPython.wx import * def _setupContext( memory, font=None, color=None ): if font: memory.SetFont( font ) else: memory.SetFont( wxNullFont ) if color: memory.SetTextForeground( color ) def write( text, bitmap, pos=(0,0), font=None, color=None): """Simple write into a bitmap doesn't do any checking.""" memory = wx.MemoryDC( ) _setupContext( memory, font, color ) memory.SelectObject( bitmap ) try: memory.DrawText( text, pos[0],pos[1],) finally: memory.SelectObject( wxNullBitmap) return bitmap
One of the things you might want to use this for is creating "button labels" for wxBitmapButtons. The following utility function provides basic centred-text (potentially multi-line) captions for your bitmaps. It also demonstrates the use of the context's GetTextExtent method to perform centring of the text:
import wx MINIMUMFONTSIZE = 4 def writeCaption( text, bitmap, font=None, margins = (2,2), color=None ): """ Write the given caption (text) into the bitmap using the font (or default if not given) with the margins given. Will try to make sure the text fits into the bitmap. """ memory = wx.MemoryDC( ) font = font or memory.GetFont() textLines = text.split( '\n' ) fit = False while not fit: totalWidth=0 totalHeight = 0 setLines = [] for line in textLines: if line and line[-1] == '\r': line = line[:-1] width, height = extents = memory.GetTextExtent( line ) totalWidth = max( totalWidth, width ) totalHeight += height setLines.append( (line, extents)) if ( totalWidth > (bitmap.GetWidth()- 2*margins[0]) or totalHeight > (bitmap.GetHeight()- 2*margins[0]) ): size = font.GetPointSize()-1 if size < MINIMUMFONTSIZE: fit = True # will overdraw!!! else: font.SetPointSize( size ) memory.SetFont( font ) else: fit = True if not setLines: return bitmap centreX, centreY = (bitmap.GetWidth()/2), (bitmap.GetHeight()/2) x, y = centreX-(totalWidth/2), centreY-(totalHeight/2) memory.SelectObject( bitmap ) _setupContext( memory, font, color) for line, (deltaX, deltaY) in setLines: x = centreX - (deltaX/2) memory.DrawText( line, x, y,) y += deltaY memory.SelectObject( wxNullBitmap) return bitmap def _setupContext( memory, font=None, color=None ): if font: memory.SetFont( font ) else: memory.SetFont( wxNullFont ) if color: memory.SetTextForeground( color )
Creating a Variant of a Bitmap with Transparency
To copy a bitmap with a transparent background and draw some more stuff on top, try the following:
# make a new, empty bitmap w = oldPix.GetWidth(); h = oldPix.GetHeight(); newPix = wxEmptyBitmap(w, h, oldPix.GetDepth()); # create a DC for drawing to it mem = wxMemoryDC(); mem.SelectObject(newPix); # do your custom drawing -- stuff drawn before the old # bitmap will underlay it, stuff drawn after will overlay it mem.SetPen(wxMEDIUM_GREY_PEN); mem.DrawLines(((0, h-1), (0, 0), (w-1, 0))); # now draw the source bitmap contents mem.DrawBitmap(oldPix, 0, 0, 1); # all done -- disconnect the DC mem.SelectObject(wxNullBitmap); # now get the transparency set up by assigning a new mask # that is derived from the current contents of the new # bitmap -- choose the color you use carefully! newPix.SetMask(wxMaskColour(newPix, wxBLACK));
Since transparency is a bit tricky here is another example. (Which unlike the above, works for me).
In this example two bitmaps are created. One is completly white (oldPix) the other one completly black (newPix). Then a red box is drawn on the black bitmap. Now the black color in newPix is masked and then newPix is copied over oldPix (the white one). The result is, that only the red box is copied.
from wxPython.wx import * class Test(wxApp): def OnInit(self): oldPix = wx.wxEmptyBitmap(300, 300) newPix = wx.wxEmptyBitmap(300, 300) mem = wxMemoryDC() mem.SelectObject(oldPix) mem.Clear() # The images have to be cleared mem.SelectObject(newPix) # because wxEmptyBitmap only mem.SetBackground(wxBLACK_BRUSH) # allocates the space mem.Clear() # We now have a black and a white image # Next we plot a white box in the middle of the black image mem.SetPen(wxRED_PEN) mem.DrawLines(((100, 200), (100, 100), (200, 100), (200,200), (100,200))) mem.SelectObject(oldPix) newPix.SetMask(wxMaskColour(newPix, wxBLACK)) mem.DrawBitmap(newPix, 0, 0, 1) oldPix.SaveFile("oldPix.bmp", wx.wxBITMAP_TYPE_BMP) newPix.SaveFile("newPix.bmp", wx.wxBITMAP_TYPE_BMP) return true app = Test(0) app.MainLoop()
This is a complete wxPython app, so just run it and have a look at the created Bitmaps oldPix.bmp and newPix.bmp for the results. -- Nikolai Hlubek
PythonMagick (a 16-bit Imaging Library)
Here is a short but complete PythonMagick/wxPython program. The program uses wxPython GUI, loads an image, displays the image, and does a simple image processing operation (threshold). Various conversions between PythonMagick, PIL, and wxPython images are available on the PythonMagick web site.
See the PythonMagick page for a greater description of PythonMagick.
Note that PythonMagick presently only has a Windows installer.
1 from wxPython import wx
2 import PythonMagick
3
4 ID_FILE_OPEN = wx.wxNewId()
5 ID_FILE_EXIT = wx.wxNewId()
6 ID_THRESHOLD = wx.wxNewId()
7
8 class ImagePanel(wx.wxPanel):
9 def __init__(self, parent, id):
10 wx.wxPanel.__init__(self, parent, id)
11 self.image = None # wxPython image
12 wx.EVT_PAINT(self, self.OnPaint)
13
14 def display(self, magickimage):
15 self.image = self.convertMGtoWX(magickimage)
16 self.Refresh(True)
17
18 def OnPaint(self, evt):
19 dc = wx.wxPaintDC(self)
20 if self.image:
21 dc.DrawBitmap(self.image.ConvertToBitmap(), 0,0)
22
23 def convertMGtoWX(self, magickimage):
24 img = PythonMagick.Image(magickimage) # make copy
25 img.depth = 8 # change depth only for display
26 img.magick = "RGB"
27 data = img.data
28 wximg = wx.wxEmptyImage(img.columns(), img.rows())
29 wximg.SetData(data)
30 return wximg
31
32
33 class mtFrame(wx.wxFrame):
34 def __init__(self, parent, ID, title):
35 wx.wxFrame.__init__(self, parent, ID, title, wx.wxDefaultPosition, wx.wxSize(500, 400))
36
37 self.iPanel = ImagePanel(self, -1)
38 self.im = None # Magick image
39
40 ## Construct "File" menu
41 self.menuBar = wx.wxMenuBar()
42 self.menuFile = wx.wxMenu()
43 self.menuFile.Append(ID_FILE_OPEN, "&Open image","")
44 wx.EVT_MENU(self, ID_FILE_OPEN, self.OnOpen)
45 self.menuFile.AppendSeparator()
46 self.menuFile.Append(ID_FILE_EXIT, "E&xit", "")
47 wx.EVT_MENU(self, ID_FILE_EXIT, self.OnExit)
48 self.menuBar.Append(self.menuFile, "&File");
49
50 ## Construct "Process" menu
51 self.menuProcess = wx.wxMenu()
52 self.menuProcess.Append(ID_THRESHOLD, "Threshold", "")
53 wx.EVT_MENU(self, ID_THRESHOLD, self.OnThreshold)
54
55 self.menuBar.Append(self.menuProcess, "&Process")
56 self.SetMenuBar(self.menuBar)
57
58 def OnOpen(self, event):
59 fd = wx.wxFileDialog(self, "Open Image", "", "", "*.*", wx.wxOPEN)
60
61 if fd.ShowModal() == wx.wxID_OK:
62 self.loadImage(fd.GetPath())
63 fd.Destroy()
64
65 def loadImage(self, path):
66 try:
67 self.im = PythonMagick.Image(path)
68 self.iPanel.display(self.im)
69 except IOError:
70 print "can't open the file"
71
72 ##-------------- Process ------------------------
73
74 def OnThreshold(self, event):
75 self.im = self.Threshold(self.im, 0.5)
76 self.iPanel.display(self.im)
77 #self.im.write('d:/threshold.tif')
78
79 def Threshold(self, image, threshold):
80 """
81 Threshold image. Input threshold is normalized (0-1.0)
82 """
83 img = PythonMagick.Image(image) # copy
84 img.threshold(threshold *65535.0)
85 return img
86
87 ##-----------------------------------------------
88
89 def OnCloseWindow(self, event):
90 self.Destroy()
91
92 def OnExit(self, event):
93 self.Close(True)
94
95 #---------------------------------------------------------------------------
96
97 class mtApp(wx.wxApp):
98 def OnInit(self):
99 frame = mtFrame(wx.NULL, -1, "MagickSimple1")
100 frame.Show(True)
101 self.SetTopWindow(frame)
102 return True
103
104 app = mtApp(0)
105 app.MainLoop()
-Bob Klimek 9-23-03
Thanks
Another word of thanks. I used the pil->image with alpha tricks learned from this page.
Anyone know whether there are newer techniques, or is this std? The page was updated 2003 (I think I recall.) (Get hold of me on the fonty python mailing list on google groups.) \d