[wxPython-users] wxImage from PNG with an alpha channel (try #2 with code)

664 views
Skip to first unread message

Ray Pasco

unread,
May 14, 2010, 5:01:43 PM5/14/10
to Wxpython Users
Apparently, the wxImage.HasAlpha() function is lying to me most of the time. That is, only for some PNG files with transparency. Also, method wxImage.GetOrFindMaskColour() returns a 3-tuple, even for files that do read correctly !  This is baffling since alpha values singular in PNG RGBA files. When reading does work, it works well. It's a go/no-go situation which seems semi-random depending on the particular file.

        self.wxImage = wx.Image( 'TRY.PNG', wx.BITMAP_TYPE_PNG )
        hasAlpha = self.wxImage.HasAlpha()

File "C:\PROGRA~1\Python25\lib\site-packages\wx-2.8-msw-unicode\wx\_core.py",
line 2829, in GetAlpha
return _core_.Image_GetAlpha(*args, **kwargs)
wx._core.PyAssertionError: C++ assertion "HasAlpha()" failed at ..\..\src\common
\image.cpp(1643) in wxImage::GetAlpha(): no alpha channel

Have I smacked into a bug, or perhaps a known shortcoming ?  Is there another way to get images to always handle detecting the alpha channel properly ?

I have attached 2 small images and the code file.

Platform  Windows 6.1.7600
Python    2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)]
Python wx 2.8.10.1
WIN7 64-bit

--
To unsubscribe, send email to wxPython-user...@googlegroups.com
or visit http://groups.google.com/group/wxPython-users?hl=en
Z_IMAGE_MANIP.PY
TRY.PNG
SMALL_PIC.PNG

Robin Dunn

unread,
May 15, 2010, 3:24:12 PM5/15/10
to wxpytho...@googlegroups.com
On 5/14/10 2:01 PM, Ray Pasco wrote:
> Apparently, the wxImage.HasAlpha() function is lying to me most of the
> time. That is, only for some PNG files with transparency. Also, method
> wxImage.GetOrFindMaskColour() returns a 3-tuple, even for files that do
> read correctly ! This is baffling since alpha values singular in PNG
> RGBA files. When reading does work, it works well. It's a go/no-go
> situation which seems semi-random depending on the particular file.
>
> self.wxImage = wx.Image( 'TRY.PNG', wx.BITMAP_TYPE_PNG )
> hasAlpha = self.wxImage.HasAlpha()
>
> File
> "C:\PROGRA~1\Python25\lib\site-packages\wx-2.8-msw-unicode\wx\_core.py",
> line 2829, in GetAlpha
> return _core_.Image_GetAlpha(*args, **kwargs)
> wx._core.PyAssertionError: C++ assertion "HasAlpha()" failed at
> ..\..\src\common
> \image.cpp(1643) in wxImage::GetAlpha(): no alpha channel
>
> Have I smacked into a bug, or perhaps a known shortcoming ? Is there
> another way to get images to always handle detecting the alpha channel
> properly ?

In wx there is a difference between masks and alpha channels, while in
PNG files a mask is just a special case of an alpha channel, so this can
easily be the source of confusion sometimes. When a PNG file is loaded
by wx.Image then if all pixels are either fully transparent or fully
opaque, then the image is assigned a mask colour and when it is
converted to a wx.Bitmap it will use a 24-bit format (usually, depending
on platform) and a wx.Mask object will be assigned to it. If the PNG
has any pixel with a transparency value between 0 and 255 then it is
given an alpha channel instead.

This dichotomy between masks and alpha exists because wx has been around
longer than PNG has existed, and using a mask colour is how transparency
was (and still is) handled in earlier image file formats that don't deal
with alpha channels.

GetOrFindMaskColour returning only the RGB values is correct, since if
you are interested in the mask it is assumed that you are not interested
in the alpha channel, and because in wx.Image a mask is defined by
pixels that have an otherwise unused colour value.

--
Robin Dunn
Software Craftsman
http://wxPython.org

Ray Pasco

unread,
May 16, 2010, 12:08:59 PM5/16/10
to wxPython-users
"since if you are interested in the mask it is assumed that you are
not interested
> in the alpha channel, and because in wx.Image a mask is defined by
> pixels that have an otherwise unused colour value."

This sounds like the wx.Image mask was designed *only* for .GIF-type
files that have a limited palette of colors, with one "color" value
that is assigned to be the 100% transparency flag.

Then, that leaves me the questions:

-) How is variable (0..255) transparency (alpha) implemented in wx ?

-) It it better or even mandatory to use another package such as Pil
to manipulate RGBA images to maintain alpha transparency ?, e.g.:

manipulatePilImageReadByPilFromPngFile( pilImage )
if (pilImage.mode == 'RGBA') :
wxImage = wx.EmptyImage( *pilImage.size )
wxImage.SetData( pilImage.convert( 'RGB' ).tostring() )
wxImage.SetAlphaData( pilImage.convert( 'RGBA' ).tostring()
[3::4] )

dc.DrawBitmap( wxImage.ConvertToBitmap(), offX, offY )

-) How does wx determine whether to use (100% transparency) values on
a PNG file that has does have an alpha plane ? ("RGBA" images, in PIL-
speak) The functions HasAlpha() and wxImage.GetAlphaData() fail on
*some*, but *only* some, perfectly valid RGBA type PNG files.
> Software Craftsmanhttp://wxPython.org

Robin Dunn

unread,
May 17, 2010, 1:23:20 PM5/17/10
to wxpytho...@googlegroups.com
On 5/16/10 9:08 AM, Ray Pasco wrote:
> "since if you are interested in the mask it is assumed that you are
> not interested
>> in the alpha channel, and because in wx.Image a mask is defined by
>> pixels that have an otherwise unused colour value."
>
> This sounds like the wx.Image mask was designed *only* for .GIF-type
> files that have a limited palette of colors, with one "color" value
> that is assigned to be the 100% transparency flag.

Yes, that was the era in which wxImage was created, but it has grown
since then too.

>
> Then, that leaves me the questions:
>
> -) How is variable (0..255) transparency (alpha) implemented in wx ?

I didn't say that wxImage can't do alpha, just that if all the pixels in
the loaded image are all either fully transparent or fully opaque then a
mask will be used instead. It is functionally equivalent.

Because of its heritage wxImage does store the alpha in a separate array
than the RGB bytes, but for most things the API hides that enough that
you shouldn't need to care about it.

>
> -) It it better or even mandatory to use another package such as Pil
> to manipulate RGBA images to maintain alpha transparency ?, e.g.:

You can, but for many things it is not necessary.

>
> manipulatePilImageReadByPilFromPngFile( pilImage )
> if (pilImage.mode == 'RGBA') :
> wxImage = wx.EmptyImage( *pilImage.size )
> wxImage.SetData( pilImage.convert( 'RGB' ).tostring() )
> wxImage.SetAlphaData( pilImage.convert( 'RGBA' ).tostring()
> [3::4] )
>
> dc.DrawBitmap( wxImage.ConvertToBitmap(), offX, offY )
>
> -) How does wx determine whether to use (100% transparency) values on
> a PNG file that has does have an alpha plane ? ("RGBA" images, in PIL-
> speak) The functions HasAlpha() and wxImage.GetAlphaData() fail on
> *some*, but *only* some, perfectly valid RGBA type PNG files.

If any pixel is partially transparent then an alpha channel is used.
Otherwise it is treated as a mask.


--
Robin Dunn
Software Craftsman

Ray Pasco

unread,
May 18, 2010, 10:07:34 AM5/18/10
to wxPython-users
Thanks, but I still haven't found examples or a tutorial to show how
alpha is handled and manipulated using wx.
> Software Craftsmanhttp://wxPython.org

Mike Driscoll

unread,
May 18, 2010, 10:59:11 AM5/18/10
to wxPython-users


On May 18, 9:07 am, Ray Pasco <raoulpa...@yahoo.com> wrote:
> Thanks, but I still haven't found examples or a tutorial to show how
> alpha is handled and manipulated using wx.
>

See http://wiki.wxpython.org/index.cgi/WorkingWithImages

Also download the wxPython Demo. In the "Using Images" section, there
is an Alpha Drawing demo and an ImageAlpha demo. There's probably some
others there too.

-------------------
Mike Driscoll

Blog: http://blog.pythonlibrary.org

Ray Pasco

unread,
May 20, 2010, 10:55:43 AM5/20/10
to wxPython-users
I've finally cracked the puzzle. WX cannot always read alpha properly
from files, yet looks like it can handle images that already have it.
I used Pil to read the PNG files, rotate them, then convert them into
wxImage format. I had to rewrite the conversion routines found on
PyWiki: 'Working With Images' @ http://wiki.wxpython.org/index.cgi/WorkingWithImages
The routines now handle alpha planes under all circumstances. I'm
sure there's equivalent ways to do the same with PythonMagick. Here's
the WX<==>PIL conversions:
=============================================
"""
Based on pyWiki 'Working With Images' @ http://wiki.wxpython.org/index.cgi/WorkingWithImages
Modified to properly copy, create or delete any alpha data under *all*
possible situations.
Developed using wxPython 2.5.4 and PIL 1.1.7
Created by Ray Pasco on 2010-05-19

This code may be altered and/or distributed for any purpose
whatsoever.
Use at you own risk. Printouts are suitable for framing or wrapping
fish.
"""

import wx # wxImage <==> WxBitmap
import Image # Only if you convert from to or from Pil images.

#------------------------------------------------------------------------------

# image type image type image type
# 1 2 3
# wxBitmap <==> wxImage <==> pilImage

def WxBitmapToWxImage( wxBitmap ): # 1 ==> 2
return wx.ImageFromBitmap( wxBitmap )
#end def

def WxBitmapToPilImage( wxBitmap ): # 1 ==> 3
return WxImageToPilImage( WxBitmapToWxImage( wxBitmap ) )
#end def

#----------------------------

def PilImageToWxBitmap( pilImage ): # 3 ==> 1
return WxImageToWxBitmap( PilImageToWxImage( pilImage ) )
#end def

def PilImageToWxImage( pilImage, wantAlpha=True,
createAlpha=False ) : # 3 ==> 2

# If the pilImage has alpha, then automatically preserve it in the
wxImage
# unless alpha preservation is disabled by setting
wantAlpha=False.
#
# If the pilImage mode is RGB, the optionally create a new wxImage
alpha plane/band
# by setting createAlpha=True.
#
if (pilImage.mode == 'RGBA') : # ignore createAlpha since
mode=RGBA

# Extract the existing alpha band from the the wxImage.
wxImage = wx.EmptyImage( *pilImage.size )

# Pil's creator of "in-place" methods ought to be horse-
whipped !
pilRgbImage = pilImage # Do NOT let Pil's in-place
conversion alter the original image !
pilRgbDataStr = pilRgbImage.convert( 'RGB' ).tostring() #
"In-place" method destroys the original !!
wxImage.SetData( pilRgbDataStr ) # Just the RGB data from
the new RGB mode image.

if wantAlpha :
# Copy just the pilImage alpha into wxImage alpha plane.
pilAlphaStr = pilImage.tostring()
pilAlphaStr = pilAlphaStr[3::4]
wxImage.SetAlphaData( pilAlphaStr )
#end if

elif (pilImage.mode == 'RGB') : # RGB mode pilImage RGB planes
==> wxImage

wxImage = wx.EmptyImage( *pilImage.size )
wxImage.SetData( pilImage.tostring() )

if createAlpha : # Create a brand new alpha
plane/band/layer/channel
# Create and insert 100% opaque alpha (values=255)
# .convert( 'RGBA' ) always adds a brand new 100% opaque
pilImage alpha plane.
# .SetAlphaData() adds a brand new wxImage alpha plane in
this case
# since the wxImage doesn't originally have any alpha
plane.
pilRgbaImage = pilImage # Do NOT let Pil's in-place
conversion alter the original image !

wxImage.SetAlphaData( pilRgbaImage.convert( 'RGBA' ).tostring()
[3::4] )
#end if

#end if

return wxImage # May or may not have an alpha plane depending
on the input image mode
# and the given option flags.

#end def

#----------------------------

def WxImageToWxBitmap( wxImage ): # 2 ==> 1
return wxImage.ConvertToBitmap()
#end def

def WxImageToPilImage( wxImage, wantAlpha=True ): # 2 ==> 3 Assume
keeping any alpha channel

pilImage = Image.new( 'RGB', (wxImage.GetWidth(),
wxImage.GetHeight()), color=(255, 255, 255) )
pilImage.fromstring( wxImage.GetData() ) # RGB data

if wxImage.HasAlpha() and wantAlpha : # Convert and insert
the wxImage alpha channel

pilImage.convert( 'RGBA' ) # add an alpha channel
with dummy values
wxAlphaStr = wxImage.GetAlphaData() # extract the wxImage
alpha channel in "string" format

# Create a single band(Pil)/channel(wx) dummy image that is
the dimensions of the wxImage.
pilAlphaImage = Image.new( 'L', (wxImage.GetWidth(),
wxImage.GetHeight()) )

# Insert the wx alpha data string into the new Pil image.
pilAlphaImage = Image.fromstring( 'L', (wxImage.GetWidth(),
wxImage.GetHeight()), wxAlphaStr )

# Copy the single-band Pil alpha-data image into the alpha
channel of the target RGBA image.
pilImage.putalpha( pilAlphaImage ) # all done !

#end if

return pilImage

#end def

=============================================

Here's the sample app that simply displays a rotated image read from a
file and displayed on a dc. All I wanted to accomplish was to make
transparent the extended margins that are produced as a consequence of
rotating a rectangular image. I works for both images that both have
and don't have alpha planes and preserves the alpha in the ones that
do:
=============================================

"""
Tested on Win7 64-bit (6.1.7600) and Win XP SP3 (5.1.2600) using an
AMD processor.
Python 32-bit installed.

Platform Windows 6.1.7600
Python 2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit
(Intel)]
Python wx 2.8.10.1
Pil 1.1.7

Created by Ray Pasco on 2010-05-19
This code may be altered and/or distributed for any purpose
whatsoever.
Use at you own risk. Printouts are suitable for framing or wrapping
fish.
"""

import wx
import random # set rotation direction randomly
import Image # Pil package. Reads all common image
files automatically if the extension is correct.
import ImageConversions # Image type conversions that handle
variable-valued alpha planes properly

#------------------------------------------------------------------------------

import sys, platform
print
print 'Platform ', platform.system()
if platform.system().lower() == 'windows' :
print 'Windows ', platform.win32_ver()[1] # for MSW kbhit()
print 'Python ', sys.version
print 'Wx ', wx.VERSION_STRING
print 'Pil ', Image.VERSION
print

#------------------------------------------------------------------------------

class DrawWindow( wx.Window ) : # Window within the parent
container.

def __init__( self, parent=None, id=-1,
imgFilename='defaultPngFilename' ) :

wx.Window.__init__( self, parent=parent, id=id )

self.imgFilename = imgFilename

self.Bind( wx.EVT_SIZE, self.OnSize ) # For redraws
other than on the timer events.
self.Bind( wx.EVT_KEY_DOWN, self.OnKeyDown )
#end def

#------------------------

def OnSize( self, event ) :
self.DrawRotated( self.imgFilename )
event.Skip()
#end def

#------------------------

def OnKeyDown( self, event ) :
wx.Exit() # Easy way to end this program.
E.g., press the space-bar.
event.Skip()
#end def

#------------------------

def DrawRotated( self, imgFilename ) :

clientWid, clientHgt = self.GetClientSizeTuple()
bufferedDC = wx.BufferedDC( wx.ClientDC(self),
wx.EmptyBitmap( clientWid, clientHgt ) )
bufferedDC.SetBackground( wx.Brush( (220, 220, 240) ) ) #
powder blue .Clear() color
bufferedDC.Clear() # "Whitewash" over the
previous drawing.

try :
dummy = self.angle # ? Have the following
local vars been created yet ?
except : # No, assign only once:
self.angle = 0.0 # instantiate and
initialize
self.angleIncrement = 0.010 # heuristic value; given
in radians; for next timer enevt draw.
self.direction = ( (random.randint( 0, 1 ) * 2) ) - 1
self.angleIncrement *= self.direction # 50% chance of
positive or negative
self.DEGREES_PER_RADIAN = 180.0 / 3.14159
self.rotationCenterDummy = (0, 0) # has
no effect in wx image rotation
self.offsetAfterRotationDummy = wx.Point(100, 0) # has
no effect in wx image rotation

# Read file using Pil.
# Wx incorrectly reads the alpha channel from many PNG
files that have one.
pilImage = Image.open( imgFilename )
# Convert pil to RGBA if it is only RGB. Convert the
RGBA Pil to a wxImage.
self.wxImage =
ImageConversions.PilImageToWxImage( pilImage, createAlpha=True )

#end try

## The Pil way to rotate images.
# Pil rotation is smart enough that when pilImage margins are
enlarged by rotation
# the extended margins' "fill color" is transparent if the
image.mode is RGBA'.
# This is done automatically for 'RGBA' images and the alpha
band
# doesn't have to have any particular values. They can all
be set to 255 (opaque)
# and the extended rotation margins will still be set
totally transparent.
# This is the "naturally expected" way to extend an image.
#
# Also, Pil rotation creates less image-to-image positioning
jitter
# by specifying the filter type to be "Image.BICUBIC".
# Cropping the centered, rotated image to the original image
size
# is done by specifying "expand=False".
#
# The resultant outer edge aliasing (the "jaggies") needs to
be addressed
# with both rotation methods.
#
pilImage = ImageConversions.WxImageToPilImage( self.wxImage )
# Note that Pil's .rotate() is NOT an "in-place" method,
but .convert() IS one !
rotatedPilImage =
pilImage.rotate( self.angle*self.DEGREES_PER_RADIAN,
Image.BICUBIC,
expand=True )

rotatedImage =
ImageConversions.PilImageToWxImage( rotatedPilImage )
#
# The WX way (angle values are given in radians).
# Rotation image margins are set to black if .HasAlpha() is
False.
# Wx often misinterprets PNG file alpha channels. I.e., use
Pil to read PNG files with alpha.
## img.Rotate( angle, centre_of_rotation, interpolate,
offset_after_rotation )
#rotatedImage = self.wxImage.Rotate( self.angle,
self.rotationCenterDummy, True, self.offsetAfterRotationDummy )

# Center the rotated image on the client area.
imageWid, imageHgt = rotatedImage.GetSize()
offsetX = (clientWid - imageWid) / 2
offsetY = (clientHgt - imageHgt) / 2
bufferedDC.DrawBitmap( rotatedImage.ConvertToBitmap(),
offsetX, offsetY )

#end def UpdateDrawing

#end class DrawWindow

#------------------------------------------------------------------------------

class TestFrame( wx.Frame ) :
"""Create the bare minimum of an app frame.
This will be completely filled with the DrawWindow()."""

def __init__( self, imgFilename ) :

wx.Frame.__init__( self, None, -1, 'Double Buffered Test',
pos=(0, 0), size=(700, 700) )

self.drawWindow = DrawWindow( self, -1, imgFilename ) #
Instantiate
self.imgFilename = imgFilename # Only
for OnDrawTimer()

self.Show() # The drawing
window must be shown before drawing.
self.drawWindow.DrawRotated( imgFilename ) # Initial
(unrotated) drawing. Timer will update the draw window afterwards.
# Subsequent draws
will be incrementally rotated.
#--------------------

self.drawTimer = wx.Timer( self, id=wx.NewId() )
self.Bind( wx.EVT_TIMER, self.OnDrawTimer )
self.drawTimer.Start( 50, oneShot=False )

#end def __init__

def OnDrawTimer( self, event ) :
self.drawWindow.DrawRotated( self.imgFilename ) #
The filename is used only once.
self.drawWindow.angle += self.drawWindow.angleIncrement #
for the next .DrawRotated()
#end def

#------------------------------------------------------------------------------

# Substitute your own filename here. File may have alpha
(transparency).
# If so, it will be used as-is. Only PNG and TIFF files support
variable alpha.
# GIF (palletted) files support only either 0% or 100% transparency
per pixel.
inputFilename = 'STRIPES_ALPHA.PNG'

myApp = wx.PySimpleApp( redirect=False )
appFrame = TestFrame( inputFilename ) # TestFrame() must
control .Show()
myApp.MainLoop()
=============================================





On May 18, 10:59 am, Mike Driscoll <kyoso...@gmail.com> wrote:
> On May 18, 9:07 am, Ray Pasco <raoulpa...@yahoo.com> wrote:
>
> > Thanks, but I still haven't found examples or a tutorial to show how
> > alpha is handled and manipulated using wx.
>
> Seehttp://wiki.wxpython.org/index.cgi/WorkingWithImages

Chris Barker

unread,
May 20, 2010, 1:00:30 PM5/20/10
to wxpytho...@googlegroups.com
Ray Pasco wrote:
> I had to rewrite the conversion routines found on
> PyWiki: 'Working With Images' @ http://wiki.wxpython.org/index.cgi/WorkingWithImages
> The routines now handle alpha planes under all circumstances.

Have you updated the Wiki -- this sounds useful.

-Chris




--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris....@noaa.gov

Ray Pasco

unread,
May 20, 2010, 10:24:56 PM5/20/10
to wxPython-users
The procedure to make and update the wiki looks daunting to me.
Perhaps I'm just not familiar with formatting wiki pages. There's so
many text-based formatting commands.
.P
I'm getting flashbacks to Wordstar days.
.E

I'll have to look into this some more.



On May 20, 1:00 pm, Chris Barker <Chris.Bar...@noaa.gov> wrote:
> Ray Pasco wrote:
> > I had to rewrite the conversion routines found on
> > PyWiki: 'Working With Images' @  http://wiki.wxpython.org/index.cgi/WorkingWithImages
> > The routines now handle alpha planes under all circumstances.
>
> Have you updated the Wiki -- this sounds useful.
>
> -Chris
>
> --
> Christopher Barker, Ph.D.
> Oceanographer
>
> Emergency Response Division
> NOAA/NOS/OR&R            (206) 526-6959   voice
> 7600 Sand Point Way NE   (206) 526-6329   fax
> Seattle, WA  98115       (206) 526-6317   main reception
>
> Chris.Bar...@noaa.gov

Mike Driscoll

unread,
May 21, 2010, 12:16:55 PM5/21/10
to wxPython-users


On May 20, 9:24 pm, Ray Pasco <raoulpa...@yahoo.com> wrote:
> The procedure to make and update the wiki looks daunting to me.
> Perhaps I'm just not familiar with formatting wiki pages. There's so
> many text-based formatting commands.
> .P
> I'm getting flashbacks to Wordstar days.
> .E
>
>  I'll have to look into this some more.


It's actually pretty easy, but you're welcome to just write the text
up and send it to me. I can update the wiki for you...

-------------------
Mike Driscoll

Blog: http://blog.pythonlibrary.org

Mike Driscoll

unread,
May 22, 2010, 8:19:23 AM5/22/10
to wxPython-users
On May 21, 11:16 am, Mike Driscoll <kyoso...@gmail.com> wrote:
> On May 20, 9:24 pm, Ray Pasco <raoulpa...@yahoo.com> wrote:
>
> > The procedure to make and update the wiki looks daunting to me.
> > Perhaps I'm just not familiar with formatting wiki pages. There's so
> > many text-based formatting commands.
> > .P
> > I'm getting flashbacks to Wordstar days.
> > .E
>
> >  I'll have to look into this some more.
>
> It's actually pretty easy, but you're welcome to just write the text
> up and send it to me. I can update the wiki for you...
>
> -------------------
> Mike Driscoll
>
> Blog:  http://blog.pythonlibrary.org
>


For the archives, Ray gave me his code and PNG image, so I put them up
on the wiki here:

http://wiki.wxpython.org/PngWithAlphaChannel

I put it in the Wiki's cookbook with a link here:
http://wiki.wxpython.org/RecipesImagesAndGraphics
Reply all
Reply to author
Forward
0 new messages