Table of 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

Toggle line numbers
   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()

PIL (Python Imaging Library)

        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

        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

Examples

        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)

        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

        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

Toggle line numbers
   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 :

Toggle line numbers
   1     screenWid = wx.SystemSettings.GetMetric( wx.SYS_SCREEN_X )
   2     screenHgt = wx.SystemSettings.GetMetric( wx.SYS_SCREEN_Y )
   3     captureSize = (screenWid, screenHgt*2)
   4     wxBitmap = ScreenCapture( startPos, captureSize )

Other wxPython add-on packages can take arbitrarily sized screen shots, too, such as ImageMagick and PIL. Python Imaging Library:

GeneralScreenShotPIL.py

-- Ray Pasco

Write Text to a Bitmap

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

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.

Toggle line numbers
   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

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