Differences between revisions 44 and 45
Revision 44 as of 2002-12-26 22:33:35
Size: 11633
Editor: anonymous
Comment: missing edit-log entry for this revision
Revision 45 as of 2002-12-26 22:36:50
Size: 11604
Editor: anonymous
Comment: missing edit-log entry for this revision
Deletions are marked like this. Additions are marked like this.
Line 11: Line 11:
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. The implementations change frequently with new wxPython versions. I like to have these six individual functions with related names because there is currently unparallel naming and API support in wxWindows/wxPython. I believe that the older text in this section is outdated, but I haven't had time to go through it. -- RobbShecter 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. It 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
  • 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: TableOfContents

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. It 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 
  21 def imageToPil(image):
  22     pil = Image.new('RGB', (image.GetWidth(), image.GetHeight()))
  23     pil.fromstring(image.GetData())
  24     return pil
  25 
  26 def imageToBitmap(image):
  27     return image.ConvertToBitmap()
  28     
  • 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 ) 

    There seem to be some missing functions, not sure if these are available at a lower level of abstraction for the C peoples, but anyway, here's what hopefully will show up some day:
    • 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 = apply( wxEmptyImage, source.size )
                image.SetData( source.convert( "RGB").tostring() )
                return image.ConvertToBitmap() # wxBitmapFromImage(image)

Numeric Python

  • This example shows the creation of a bitmap using a Numeric Python array as the data source. Note that Numeric 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 'c' data type for image data in rgb format.

        def GetBitmap( self, width=32, height=32, colour = (0,0,0) ):
                array = Numeric.zeros( (height, width, 3),'c')
                array[:,:,] = colour
                image = wxEmptyImage(width,height)
                image.SetData( array.tostring())
                return image.ConvertToBitmap()# wxBitmapFromImage(image)

Slicing

  • Numeric'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) 

    To assign a value to the entire red bit plane:
    •  array[:,:,0] = 255 

    To assign a colour to the first row:
    •  array[ 0 ] = (r,g,b ) 

Examples

  • This example shows use of Numeric'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).

        def GetBitmap( self, width=32, height=32, colour = (128,128,128), border=5, borderColour=(255,255,255) ):
                # creates a bitmap with a border
                array = Numeric.zeros( (height, width, 3),'c')
                array[border:-border,border:-border,:] = colour
                array[:border,:,:] = borderColour
                array[-border:,:,:] = borderColour
                array[:,:border,:] = borderColour
                array[:,-border:,:] = borderColour
                image = wxEmptyImage(width,height)
                image.SetData( array.tostring())
                return image.ConvertToBitmap()# wxBitmapFromImage(image)
  • This example creates a 256-level red gradiant. Note that real-world programs creating gradiants are likely to use 'd' type with 0.0 to 255.0 values to allow for processing the gradiant without integer semantics making things hard, then doing  array.astype( 'c')  before converting to a string.

        def GetBitmap( self ):
                ## Create a 256-level gradiant
                array = Numeric.zeros( (height, width, 3),'c')
                array[:,:,0] = range( 256 )
                image = wxEmptyImage(width,height)
                image.SetData( array.tostring())
                return image.ConvertToBitmap()# wxBitmapFromImage(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 wxWindowDC and arbitrary parts of the screen with a wxScreenDC.

        def OnToFile( self, event ):
                context = wxClientDC( self )
                memory = wxMemoryDC( )
                x,y = self.GetClientSizeTuple()
                bitmap = wxEmptyBitmap( x,y, -1 )
                memory.SelectObject( bitmap )
                memory.Blit( 0,0,x,y, context, 0,0)
                memory.SelectObject( wxNullBitmap)
                bitmap.SaveFile( "test.bmp", wxBITMAP_TYPE_BMP )

Write Text to 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 = wxMemoryDC( )
        _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:

from wxPython.wx import *
import string

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 = wxMemoryDC( )
        font = font or memory.GetFont()
        textLines = string.split( text, '\n' )
        fit = 0
        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 = 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 = 1 # will overdraw!!!
                        else:
                                font.SetPointSize( size )
                                memory.SetFont( font )
                else:
                        fit = 1
        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 = 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 )


Great examples. Two notes about the screen capture example:

  1. The wxPaintDC should probably be a wxClientDC, since you're presumably doing this outside of an OnPaint method.

  2. To capture the whole window use a wxWindowDC. Also, you can capture arbitrary portions of the screen with a wxScreenDC.

This is my first edit, so feel free to refactor it.--Tagore Smith


Thanks Tagore, your changes integrated now

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));

WorkingWithImages (last edited 2011-06-20 15:32:02 by pool-71-244-98-82)

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