[wxPython] scaling bitmaps

Cliff Wells wrote:

I haven't been following this thread much, but are you simply looking to
convert PIL images to wxImages?

Well, no, not at all, but I may very well need to do that some day, so
thanks anyway!

Geoff Gerrietts wrote:

> How do I draw to a Numeric Array???

Well, you need Numeric, obviously. You create a matrix that's x by y
by depth elements, then you do the math to draw onto that surface.

I'm really trying not to re-invent the wheel here, and I'm trying to get
a high performace solution. Writing my own drawing code in Python will
ackomplidh neither!

If your question is "how do I get a wx API call that draws a line to
draw on the numeric array" that I can't answer.

That would be nice, but I don't imagine it's easy.

My understanding of your problem is that the display of the bitmap is
really not particularly relevant, only the generation of a bitmap with
enough depth to it that you can identify an object by its colour
signature. As I said, I may be misunderstanding the problem domain.

NO, that's exactly it. But it's not just that I need enough depth, it's
that wxColors are all 24 bit (r,g,b), one byte each, so if you draw to a
lower depth bitmap, the color gets mapped to it's closest match. When you
then check the color, two different wxColors that you drew can end up
coming back as the same color, which will kill my scheme.

A lot of the strategy you choose in solving the problem will be a
question of how often your image is changing, your goals for the image
you're generating, etc.

yep. It's changing enough that I need the performace to be as good as
possible. I havn't compared drawing in PIL to a DC, but I suspect it's
slower. Enough suspicions... I need to profile!

It sounds like a lot of the suggestions I have to offer, are things
you've had some experience with, and I'm not opening any new doors for
you.

Some I have, and some not, I love hearing new ideas...

It sounds to me like the problems you've set for yourself are
very hard, and while my angle of approach would be to look outside the
wxWindows API for my image handling, and maybe that's wrongheaded.

I was going for an easy, clever hack. It just might not work (or not be
so easy)

Here's a sample of what I have. run it (on a 24 bit or better display)
and left click somewhere while watching the console window.

#!/usr/bin/env python2.1

from wxPython.wx import *
import math
from random import randint
import time

···

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

_MinHitTestLineWidth = 3

WXFLOATCANVASEVENT = wxNewEventType()

def EVT_WXFLOATCANVASEVENT( window, function ):
    """Your documentation here"""
    window.Connect( -1, -1, WXFLOATCANVASEVENT, function )

class wxCanvasObjectEvent(wxPyCommandEvent):
    def __init__(self, WindowID,Object):
        wxPyCommandEvent.__init__(self, WXFLOATCANVASEVENT, WindowID)
        self.Object = Object

    def Clone( self ):
        self.__class__( self.GetId() )

class CanvasRectangle:
    def __init__(self,x,y,width,height,FillColor,ID = -1):
        self.X = x
        self.Y = y
        self.Width = width
        self.Height = height
        self.FillColor = FillColor
        self.ID = ID

        self.Pen = wxBLACK_PEN
        self.Brush = wxBrush(FillColor)

    def _Draw(self,dc,color = None):
        X, Y = self.X, self.Y
        Width, Height = self.Width, self.Height

        if color:
            dc.SetPen(wxPen(color))
            dc.SetBrush(wxBrush(color))
        else:
            dc.SetPen(self.Pen)
            dc.SetBrush(self.Brush)
        dc.DrawRectangle(X,Y,Width,Height)

    def _HitTest(self,x,y):
        if ((x >= self.X and x <= self.X + self.Width) and
            (y >= self.Y and y <= self.Y + self.Height)):
            return 1
        else:
            return 0
        
class CanvasLine:
    def __init__(self,Points,LineColor,LineWidth,ID = -1):
        self.Points = Points

        self.LineColor = LineColor
        self.LineWidth = int(LineWidth)
        self.ID = ID

        self.Pen = wxPen(LineColor,LineWidth)

        self.half_linewidth = max(1, (self.LineWidth + 1)/2) # add one
to round up
integer division,a nd make sure it's at least 1

    def _Draw(self,dc,HitTestColor=None):
        if HitTestColor:
            dc.SetPen(wxPen(HitTestColor,max(self.LineWidth,_MinHitTestLineWidth)))
        else:
            dc.SetPen(self.Pen)
        dc.DrawLines(self.Points)

    def _HitTest(self,x,y):
        ## Note: This is what my memory of analytical geometry came up with
        ## I imagine there are faster algorithms
        half_linewidth = self.half_linewidth
        for segnum in range(len(self.Points)-1):
            x1,y1 = self.Points[segnum]
            x2,y2 = self.Points[segnum + 1]
            # first check bounding box:
            if ((x >= (min(x1,x2)-half_linewidth) and x <= (max(x1,x2) +
half_linewidth)) and
                (y >= (min(y1,y2)-half_linewidth) and y <= (max(y1,y2) +
half_linewidth)) ):
                # if line is horizontal or vertical, it has been hit
                if x1 == x2 or y1==y2:
                    return 1
                else:
                    #NOTE: if all lines are vertical or horizontal, you
don't need
this
                    # now check distance to line
                    m = float(y2-y1)/(x2-x1)
                    b = y1 - m*x1
                    d = abs((m*x - y + b) / (math.sqrt(m**2 +1)))
                    if d <= half_linewidth:
                        return 1
        else:
            return 0

class DrawCanvas(wxPanel):
    def __init__(self, parent, id = -1, size = wxDefaultSize):
        
        wxPanel.__init__(self, parent, id, wxPoint(0, 0), size, wxSUNKEN_BORDER)

        EVT_PAINT(self, self.OnPaint)
        EVT_LEFT_DOWN(self, self.LeftDown)
        EVT_RIGHT_DOWN(self, self.RightDown)
        EVT_RIGHT_UP(self, self.RightUp)
        EVT_MOTION(self, self.MouseMoving)

        EVT_WXFLOATCANVASEVENT(self, self.ObjectClicked)

        self.SetBackgroundColour(wxNamedColor("WHITE"))
        self.ObjectList =
        self.RectList =

        self.RectMoving = 0
        self.LastPosition = 0
        bitmap = wxEmptyBitmap(self.GetSize()[0],self.GetSize()[1],-1)
        print "bitmap is %i by %i, with a depth of %i"%(bitmap.GetWidth(),
bitmap.GetHeight(), bitmap.GetDepth())
        self._HitTestImage = wxMemoryDC()
        self._HitTestImage.SelectObject(bitmap)
         
    def OnPaint(self, event):
        self.Draw()
        
    def Draw(self):
        dc = wxPaintDC(self)
        dc.Clear()
        self._HitTestImage.Clear()
        i = 0
        self.HitTestDict = {16777215 : None}
        for object in self.ObjectList:
            i += 8
            color = wxColor(i&255, (i&65280) >> 8 , (i&16711680) >> 16)
            object._Draw(dc)
            self.HitTestDict[i] = object
            
            object._Draw(self._HitTestImage,color)
        dc.EndDrawing()

    def HitTest(self,event):
        color = self._HitTestImage.GetPixel(event.GetX(),event.GetY())
        i = color.Red() + (color.Green() << 8) + (color.Blue() << 16)
        if self.HitTestDict[i]:
            print "Object %s has been hit"%(self.HitTestDict[i].ID)

    def LeftDown(self,event):
        start = time.clock()
        self.HitTest(event)
        print "Hit Test took %f seconds"%(time.clock()-start)

        start = time.clock()
        for object in self.ObjectList:
            if object._HitTest(event.GetX(),event.GetY()):
                event = wxCanvasObjectEvent( self.GetId(), object )
                self.GetEventHandler().AddPendingEvent( event )
                break
        print "Hit Test2 took %f seconds"%(time.clock()-start)
        
    def RightDown(self,event):
        for rect in self.RectList:
            if rect._HitTest(event.GetX(),event.GetY()):
                self.RectMoving = 1
                self.MovingRect = rect
                self.StartMousePosition = ((event.GetX(),event.GetY()))
                break
    def RightUp(self,event):
        if self.RectMoving:
            self.MovingRect.X = self.MovingRect.X + event.GetX() -
self.StartMousePosition[0]
            self.MovingRect.Y = self.MovingRect.Y + event.GetY() -
self.StartMousePosition[1]
            self.RectMoving = 0
            self.LastPosition = 0
            self.Draw()

    def MouseMoving(self,event):
        if self.RectMoving:
            x = self.MovingRect.X + event.GetX() - self.StartMousePosition[0]
            y = self.MovingRect.Y + event.GetY() - self.StartMousePosition[1]
            w, h = self.MovingRect.Width, self.MovingRect.Height
            dc = wxClientDC(self)
            dc.BeginDrawing()
            dc.SetPen(wxPen(wxNamedColour('BLACK'), 1,))
            dc.SetBrush(wxTRANSPARENT_BRUSH)
            dc.SetLogicalFunction(wxINVERT)
            if self.LastPosition:
                dc.DrawRectangle(self.LastPosition[0],self.LastPosition[1],w,h)
            dc.DrawRectangle(x,y,w,h)
            dc.EndDrawing()
            self.LastPosition = (x,y)
        else:
            pass
            ##self.HitTest(event)

    def ObjectClicked(self,event):
        print "Object %s was clicked"%event.Object.ID
    
class DrawFrame(wxFrame):
    def __init__(self,parent, id,title,position,size):
        wxFrame.__init__(self,parent, id,title,position, size)
        
        EVT_CLOSE(self, self.OnCloseWindow)
        self.Canvas = DrawCanvas(self,size = (800,800))

## for row in range(40):
## for column in range(40):
##
self.Canvas.ObjectList.append(CanvasRectangle(20*row,20*column,20,20,wxRED,`(row,column)`))
                
        for i in range(50):
            line =
            color = wxColor(randint(0,254),randint(0,254),randint(0,254))
            width = randint(1,5)
            for j in range(20):
                line.append((randint(0,799),randint(0,799)))
                self.Canvas.ObjectList.append(CanvasLine(line,color,width,"Line
%i"%i))
            
        self.Show(true)

        return None

    def OnCloseWindow(self, event):
        self.Destroy()
    
class App(wxApp):
    def OnInit(self):
        frame = DrawFrame(NULL, -1, "Test of Hit Test
Code",wxDefaultPosition,wxSize(800,800))
        self.SetTopWindow(frame)
        return true
app = App(0)
app.MainLoop()

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

I have found this bug is caused because wxwindows is

sending a size message to an item within a gridsizer after it has been
destroyed.

Removing the items from the sizer before closing the window cures the
problem.

I think this is fixed already (windows now remove themselves from the sizer
when they are destroyed) but if you could send me a sample to be sure I
would appreciate it.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

Hi,

Not sure if I've understood the main issue here but here's my thought:

NO, that's exactly it. But it's not just that I need enough depth, it's
that wxColors are all 24 bit (r,g,b), one byte each, so if you draw to a
lower depth bitmap, the color gets mapped to it's closest match. When you
then check the color, two different wxColors that you drew can end up
coming back as the same color, which will kill my scheme.

How about drawing all your objects to a 24bit RGB wxBitmap/wxImage held
in memory. Then Blit this to the screen, or whatever device needed to
view it at lower colour depth. When you select an object with the mouse,
instead of interogating the pixel colour of the displayed image,
interogate the Memory image at the same pixel-point (with the original
RGB colour).

Bryan

···

> A lot of the strategy you choose in solving the problem will be a
> question of how often your image is changing, your goals for the image
> you're generating, etc.

yep. It's changing enough that I need the performace to be as good as
possible. I havn't compared drawing in PIL to a DC, but I suspect it's
slower. Enough suspicions... I need to profile!

> It sounds like a lot of the suggestions I have to offer, are things
> you've had some experience with, and I'm not opening any new doors for
> you.

Some I have, and some not, I love hearing new ideas...

>It sounds to me like the problems you've set for yourself are
> very hard, and while my angle of approach would be to look outside the
> wxWindows API for my image handling, and maybe that's wrongheaded.

I was going for an easy, clever hack. It just might not work (or not be
so easy)

Here's a sample of what I have. run it (on a 24 bit or better display)
and left click somewhere while watching the console window.

#!/usr/bin/env python2.1

from wxPython.wx import *
import math
from random import randint
import time
#---------------------------------------------------------------------------

_MinHitTestLineWidth = 3

WXFLOATCANVASEVENT = wxNewEventType()

def EVT_WXFLOATCANVASEVENT( window, function ):
    """Your documentation here"""
    window.Connect( -1, -1, WXFLOATCANVASEVENT, function )

class wxCanvasObjectEvent(wxPyCommandEvent):
    def __init__(self, WindowID,Object):
        wxPyCommandEvent.__init__(self, WXFLOATCANVASEVENT, WindowID)
        self.Object = Object

    def Clone( self ):
        self.__class__( self.GetId() )

class CanvasRectangle:
    def __init__(self,x,y,width,height,FillColor,ID = -1):
        self.X = x
        self.Y = y
        self.Width = width
        self.Height = height
        self.FillColor = FillColor
        self.ID = ID

        self.Pen = wxBLACK_PEN
        self.Brush = wxBrush(FillColor)

    def _Draw(self,dc,color = None):
        X, Y = self.X, self.Y
        Width, Height = self.Width, self.Height

        if color:
            dc.SetPen(wxPen(color))
            dc.SetBrush(wxBrush(color))
        else:
            dc.SetPen(self.Pen)
            dc.SetBrush(self.Brush)
        dc.DrawRectangle(X,Y,Width,Height)

    def _HitTest(self,x,y):
        if ((x >= self.X and x <= self.X + self.Width) and
            (y >= self.Y and y <= self.Y + self.Height)):
            return 1
        else:
            return 0
        
class CanvasLine:
    def __init__(self,Points,LineColor,LineWidth,ID = -1):
        self.Points = Points

        self.LineColor = LineColor
        self.LineWidth = int(LineWidth)
        self.ID = ID

        self.Pen = wxPen(LineColor,LineWidth)

        self.half_linewidth = max(1, (self.LineWidth + 1)/2) # add one
to round up
integer division,a nd make sure it's at least 1

    def _Draw(self,dc,HitTestColor=None):
        if HitTestColor:
            dc.SetPen(wxPen(HitTestColor,max(self.LineWidth,_MinHitTestLineWidth)))
        else:
            dc.SetPen(self.Pen)
        dc.DrawLines(self.Points)

    def _HitTest(self,x,y):
        ## Note: This is what my memory of analytical geometry came up with
        ## I imagine there are faster algorithms
        half_linewidth = self.half_linewidth
        for segnum in range(len(self.Points)-1):
            x1,y1 = self.Points[segnum]
            x2,y2 = self.Points[segnum + 1]
            # first check bounding box:
            if ((x >= (min(x1,x2)-half_linewidth) and x <= (max(x1,x2) +
half_linewidth)) and
                (y >= (min(y1,y2)-half_linewidth) and y <= (max(y1,y2) +
half_linewidth)) ):
                # if line is horizontal or vertical, it has been hit
                if x1 == x2 or y1==y2:
                    return 1
                else:
                    #NOTE: if all lines are vertical or horizontal, you
don't need
this
                    # now check distance to line
                    m = float(y2-y1)/(x2-x1)
                    b = y1 - m*x1
                    d = abs((m*x - y + b) / (math.sqrt(m**2 +1)))
                    if d <= half_linewidth:
                        return 1
        else:
            return 0

class DrawCanvas(wxPanel):
    def __init__(self, parent, id = -1, size = wxDefaultSize):
        
        wxPanel.__init__(self, parent, id, wxPoint(0, 0), size, wxSUNKEN_BORDER)

        EVT_PAINT(self, self.OnPaint)
        EVT_LEFT_DOWN(self, self.LeftDown)
        EVT_RIGHT_DOWN(self, self.RightDown)
        EVT_RIGHT_UP(self, self.RightUp)
        EVT_MOTION(self, self.MouseMoving)

        EVT_WXFLOATCANVASEVENT(self, self.ObjectClicked)

        self.SetBackgroundColour(wxNamedColor("WHITE"))
        self.ObjectList =
        self.RectList =

        self.RectMoving = 0
        self.LastPosition = 0
        bitmap = wxEmptyBitmap(self.GetSize()[0],self.GetSize()[1],-1)
        print "bitmap is %i by %i, with a depth of %i"%(bitmap.GetWidth(),
bitmap.GetHeight(), bitmap.GetDepth())
        self._HitTestImage = wxMemoryDC()
        self._HitTestImage.SelectObject(bitmap)
         
    def OnPaint(self, event):
        self.Draw()
        
    def Draw(self):
        dc = wxPaintDC(self)
        dc.Clear()
        self._HitTestImage.Clear()
        i = 0
        self.HitTestDict = {16777215 : None}
        for object in self.ObjectList:
            i += 8
            color = wxColor(i&255, (i&65280) >> 8 , (i&16711680) >> 16)
            object._Draw(dc)
            self.HitTestDict[i] = object
            
            object._Draw(self._HitTestImage,color)
        dc.EndDrawing()

    def HitTest(self,event):
        color = self._HitTestImage.GetPixel(event.GetX(),event.GetY())
        i = color.Red() + (color.Green() << 8) + (color.Blue() << 16)
        if self.HitTestDict[i]:
            print "Object %s has been hit"%(self.HitTestDict[i].ID)

    def LeftDown(self,event):
        start = time.clock()
        self.HitTest(event)
        print "Hit Test took %f seconds"%(time.clock()-start)

        start = time.clock()
        for object in self.ObjectList:
            if object._HitTest(event.GetX(),event.GetY()):
                event = wxCanvasObjectEvent( self.GetId(), object )
                self.GetEventHandler().AddPendingEvent( event )
                break
        print "Hit Test2 took %f seconds"%(time.clock()-start)
        
    def RightDown(self,event):
        for rect in self.RectList:
            if rect._HitTest(event.GetX(),event.GetY()):
                self.RectMoving = 1
                self.MovingRect = rect
                self.StartMousePosition = ((event.GetX(),event.GetY()))
                break
    def RightUp(self,event):
        if self.RectMoving:
            self.MovingRect.X = self.MovingRect.X + event.GetX() -
self.StartMousePosition[0]
            self.MovingRect.Y = self.MovingRect.Y + event.GetY() -
self.StartMousePosition[1]
            self.RectMoving = 0
            self.LastPosition = 0
            self.Draw()

    def MouseMoving(self,event):
        if self.RectMoving:
            x = self.MovingRect.X + event.GetX() - self.StartMousePosition[0]
            y = self.MovingRect.Y + event.GetY() - self.StartMousePosition[1]
            w, h = self.MovingRect.Width, self.MovingRect.Height
            dc = wxClientDC(self)
            dc.BeginDrawing()
            dc.SetPen(wxPen(wxNamedColour('BLACK'), 1,))
            dc.SetBrush(wxTRANSPARENT_BRUSH)
            dc.SetLogicalFunction(wxINVERT)
            if self.LastPosition:
                dc.DrawRectangle(self.LastPosition[0],self.LastPosition[1],w,h)
            dc.DrawRectangle(x,y,w,h)
            dc.EndDrawing()
            self.LastPosition = (x,y)
        else:
            pass
            ##self.HitTest(event)

    def ObjectClicked(self,event):
        print "Object %s was clicked"%event.Object.ID
    
class DrawFrame(wxFrame):
    def __init__(self,parent, id,title,position,size):
        wxFrame.__init__(self,parent, id,title,position, size)
        
        EVT_CLOSE(self, self.OnCloseWindow)
        self.Canvas = DrawCanvas(self,size = (800,800))

## for row in range(40):
## for column in range(40):
##
self.Canvas.ObjectList.append(CanvasRectangle(20*row,20*column,20,20,wxRED,`(row,column)`))
                
        for i in range(50):
            line =
            color = wxColor(randint(0,254),randint(0,254),randint(0,254))
            width = randint(1,5)
            for j in range(20):
                line.append((randint(0,799),randint(0,799)))
                self.Canvas.ObjectList.append(CanvasLine(line,color,width,"Line
%i"%i))
            
        self.Show(true)

        return None

    def OnCloseWindow(self, event):
        self.Destroy()
    
class App(wxApp):
    def OnInit(self):
        frame = DrawFrame(NULL, -1, "Test of Hit Test
Code",wxDefaultPosition,wxSize(800,800))
        self.SetTopWindow(frame)
        return true
app = App(0)
app.MainLoop()

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

_______________________________________________
wxpython-users mailing list
wxpython-users@lists.wxwindows.org
http://lists.wxwindows.org/mailman/listinfo/wxpython-users

--
Bryan Cole
Teraview Ltd., 302-304 Cambridge Science Park, Milton Road, Cambridge
CB4 0WG, United Kingdom.
tel: +44 (1223) 435380 / 435386 (direct-dial) fax: +44 (1223) 435382

Thanks Robin, I will try and make a simple example. Unfortunately at the
moment it is part of a very large project.
Cheers,
Andrew

PS. any idea what went wrong with the debugging info in the distributed dll?

···

-----Original Message-----
From: wxpython-users-admin@lists.wxwindows.org
[mailto:wxpython-users-admin@lists.wxwindows.org]On Behalf Of Robin Dunn
Sent: 22 February 2002 21:34
To: wxpython-users@lists.wxwindows.org
Subject: Re: [wxPython] Debugging help needed

>
> I have found this bug is caused because wxwindows is
>
> sending a size message to an item within a gridsizer after it has been
> destroyed.
>
> Removing the items from the sizer before closing the window cures the
> problem.

I think this is fixed already (windows now remove themselves from
the sizer
when they are destroyed) but if you could send me a sample to be sure I
would appreciate it.

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

_______________________________________________
wxpython-users mailing list
wxpython-users@lists.wxwindows.org
http://lists.wxwindows.org/mailman/listinfo/wxpython-users

_____________________________________________________________________
This e-mail is confidential and may be privileged. It may be
read, copied and used only by the intended recipient. No
communication sent by e-mail to or from Eutechnyx is intended to
give rise to contractual or other legal liability, apart from
liability which cannot be excluded under English law.

This message has been checked for all known viruses by Star
Internet delivered through the MessageLabs Virus Control Centre.

www.eutechnyx.com Eutechnyx Limited. Registered in England No: 2172322

_____________________________________________________________________
This e-mail is confidential and may be privileged. It may be read, copied and used only by the intended recipient. No communication sent by e-mail to or from Eutechnyx is intended to give rise to contractual or other legal liability, apart from liability which cannot be excluded under English law.

This message has been checked for all known viruses by Star Internet delivered through the MessageLabs Virus Control Centre.

www.eutechnyx.com Eutechnyx Limited. Registered in England No: 2172322

OK, I'm sure this effect is real (on MSWindows) and severe. I've
attached a Python test-script for people to try (MSWindows users in
particular). I'm posting this to the wx-dev list as this is, I think,
mostly a wxWindows issue (or maybe a MSWin issue).

Summary for wx-developers: drawing lines to a MemoryDC, using the
wxDC::DrawLines command appears to take over an order of magnitude
longer for wxPen widths>1, than for wxPen-width=1. This is specific to
MSWindows.

Note: I forgot to mention before, I'm drawing to a MemoryDC, and
subsequently Blit'ing the bitmap to the screen/window. Would
Hardware-acceleration be involved in drawing to a MemoryDC???

On linux/X11 the problem is absent: I have plotted 100,000 lines with
width=10-pixels with no problems. The wxDC::DrawLines command returned
in about 0.4 seconds, however the full 100,000 lines took about 6
seconds to render (v. approx.) by X11 (hurrah for X!!!). Setting the
linewidth to 1-pixel resulting ni the same function-call-return time but
rendering took only about 1-2 secs.

However, on Windows, I couldn't even plot 10,000 lines!!! (on a 1.3 GHz
P4 w/ NVIDIA 32MB graphics card and >600MB rdram). With 5000 lines, the
windows plotted very fast with line-wdth=1, however setting the
linewidth to 2 or more resulted in a segfault (or whatever the call the
windows equivalent). Reducing the line-number to 2000, gave the
following results:

linewidth: Function-call-time (output from drawtest.py, attached):
1 0.00251 sec
2 2.55 sec
3 2.56 sec
4 2.57 sec
5 2.58 sec

Can the wxWindows developers comment on this, please!
cheers,

Bryan

drawtest.py (1.14 KB)

···

On Fri, 2002-02-22 at 18:26, Chris Barker wrote:

Bernhard Herzog wrote:
> > Drawing a 10,000 point polyline (from a Numeric array) took:
> > width time
> > 1 .03 s
> > 2 .04 s
> > 3 .04 s
> > 4 .05 s
>
> How exactly did you measure this? With X you have to be a bit careful
> when measuring such things, otherwise you only measure how fast Xlib can
> queue the requests instead of how fast the Xserver actually executes
> them. You either have to run in synchronous mode or perhaps execute a
> request that requires a round-trip, such as querying the current mouse
> pointer position.

Bingo! I just wrapped my timing around teh Drawlines() call, so I'm sure
I didn't measure the actual rendering speed. Qualitativly, from watching
it, there was NOT an order of magnitude difference, however.

-Chris

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

_______________________________________________
wxpython-users mailing list
wxpython-users@lists.wxwindows.org
http://lists.wxwindows.org/mailman/listinfo/wxpython-users

--
Bryan Cole
Teraview Ltd., 302-304 Cambridge Science Park, Milton Road, Cambridge
CB4 0WG, United Kingdom.
tel: +44 (1223) 435380 / 435386 (direct-dial) fax: +44 (1223) 435382

Sorry, there is really nothing we can do about it. You should try drawing
the lines using the Python win32 module(s) directly - I'm almost sure the
result is going to be the same and I think it must be due to a problem in
the video driver.

Regards,
VZ

···

On 25 Feb 2002 11:30:11 +0000 bryan cole <bryan.cole@teraview.co.uk> wrote:

Summary for wx-developers: drawing lines to a MemoryDC, using the
wxDC::DrawLines command appears to take over an order of magnitude
longer for wxPen widths>1, than for wxPen-width=1. This is specific to
MSWindows.

bryan cole wrote:

How about drawing all your objects to a 24bit RGB wxBitmap/wxImage held
in memory. Then Blit this to the screen, or whatever device needed to
view it at lower colour depth. When you select an object with the mouse,
instead of interogating the pixel colour of the displayed image,
interogate the Memory image at the same pixel-point (with the original
RGB colour).

That was exactly my plan, but it won't work because a wxBitmap is always
created at the same depth as the screen. There is a parameter to set the
depth, but it doesn't work. I think the issue is that a wxMemoryDC wants
to use native drarwing code, so it has to be the same depth. Also, the
whole point of a wxBitmap is to be a platform independent representation
of a native Bitmap, so it should be a truly native bitmap.

I have come up with a workable solution to the problem: I increment my
colors far enough appart taht they will be unique at any depth >=16bpp,
like so:

(0,0,0)
(8,0,0)
(16,0,0)
.
.
.
(0,8,0)
etc..

now it should work just fine on any display with at least 16 bit depth.
By the way, how common are dispalays of >8 but less that 16 bits? I
don't need to support 8bit, as the whole point of this is to have fast
hit tests for lots of object, far more than 255.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

bryan cole wrote:
> How about drawing all your objects to a 24bit RGB wxBitmap/wxImage held
> in memory. Then Blit this to the screen, or whatever device needed to
> view it at lower colour depth. When you select an object with the

mouse,

> instead of interogating the pixel colour of the displayed image,
> interogate the Memory image at the same pixel-point (with the original
> RGB colour).

That was exactly my plan, but it won't work because a wxBitmap is always
created at the same depth as the screen. There is a parameter to set the
depth, but it doesn't work. I think the issue is that a wxMemoryDC wants
to use native drarwing code, so it has to be the same depth. Also, the
whole point of a wxBitmap is to be a platform independent representation
of a native Bitmap, so it should be a truly native bitmap.

If you build a 24-bit wxImage offscreen, then use wxBitmapFromImage() to
convert down to whatever screen depth you have for display, then as Bryan
said, when the user clicks the displayed wxBitmap, interrogate the color of
the wxImage at the same point, this should work. Basically, use the
wxBitmap only to gather the coordinates of the mouse click, and get all the
color information from the offscreen wxImage, which can be at whatever
depth you need.

···

On Mon, 25 Feb 2002 10:08:46 -0800 Chris Barker wrote:

--
Cliff Wells, Software Engineer
Logiplex Corporation (www.logiplex.net)
(503) 978-6726 x308 (800) 735-0555 x308

Cliff Wells wrote:

If you build a 24-bit wxImage offscreen, then use wxBitmapFromImage() to
convert down to whatever screen depth you have for display, then as Bryan
said, when the user clicks the displayed wxBitmap, interrogate the color of
the wxImage at the same point, this should work. Basically, use the
wxBitmap only to gather the coordinates of the mouse click, and get all the
color information from the offscreen wxImage, which can be at whatever
depth you need.

I think we are going around in circles here...

How do I draw to a wxImage with the DC api? If it can't be donel, we're
back to where we started. I'm drawing to the screen with the DC API, so
my scheme is only nifty if I can draw to my hit-test image with the same
calls (or very similar calls) and get the same results.

I ahve learned afait bit about all the imaging and bitmap options from
this discussion, however.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Cliff Wells wrote:
> If you build a 24-bit wxImage offscreen, then use wxBitmapFromImage()

to

> convert down to whatever screen depth you have for display, then as

Bryan

> said, when the user clicks the displayed wxBitmap, interrogate the

color of

> the wxImage at the same point, this should work. Basically, use the
> wxBitmap only to gather the coordinates of the mouse click, and get all

the

> color information from the offscreen wxImage, which can be at whatever
> depth you need.

I think we are going around in circles here...

How do I draw to a wxImage with the DC api? If it can't be donel, we're
back to where we started. I'm drawing to the screen with the DC API, so
my scheme is only nifty if I can draw to my hit-test image with the same
calls (or very similar calls) and get the same results.

Ah. _That's_ what I was missing (I figured there was something). In that
case, here's what I would do: Use PIL to draw your images. First draw to
an offscreen PIL image, then convert it to a wxBitmap for display. You'll
still have to do the offscreen color lookup in the PIL image, because
you're going to lose some information in the conversion (since your screen
depth is less than 24 bits).

I can't think of anything you can do with a DC that you can't do with PIL,
so you should be able to do a pretty straightforward translation of wxDC
calls to PIL calls. This may give you some long-term benefits as well,
since PIL can do a lot of things a DC can't.

IMHO, if you are doing serious image manipulation, the wxImage/wxBitmap
combo can be frustrating as they don't have much overlapping functionality,
and it alway seems as if the method you need is in the format you don't
have :stuck_out_tongue: PIL pretty much solves this. The only drawback is that there is
no way to directly display a PIL image in a wxWindow, so you'll have to use
the routines I provided you a couple of days ago (or something similar) to
coax the images into a usable format.

Regards,

···

On Mon, 25 Feb 2002 13:27:52 -0800 Chris Barker wrote:

--
Cliff Wells, Software Engineer
Logiplex Corporation (www.logiplex.net)
(503) 978-6726 x308 (800) 735-0555 x308

Cliff Wells wrote:

I can't think of anything you can do with a DC that you can't do with PIL,
so you should be able to do a pretty straightforward translation of wxDC
calls to PIL calls. This may give you some long-term benefits as well,
since PIL can do a lot of things a DC can't.

HMMM. So I would do ALL the drawing in PIL...That could work. Here are
my suspected problems:

-I'm guessing that using PIL would be slower, as it can't use harddware
acceleration, and I think DCs (some of them, anyway) can.

-Robin just added a DC.DrawPointList() and DC.DrawLineList() to
wxPython, and we might add other DrawStuffList() calls. These can make a
big difference when you have thousands of points to draw (I do). See
performance above.

IMHO, if you are doing serious image manipulation, the wxImage/wxBitmap
combo can be frustrating as they don't have much overlapping functionality,

Well, yes, I agree. I didn't think I was doing serious image manipulation....

The only drawback is that there is
no way to directly display a PIL image in a wxWindow, so you'll have to use
the routines I provided you a couple of days ago

So the Blit operation is going to be slower as well.

I guess I need to do a little testing, and see if my performance issues
really are a problem. I suspect they are, any slower drawing and I my
Canvas would be unusable.

Stay tuned: I'm working on my Zoomable,Scrollable, Object graphics
Canvas, and I am about to add Hit-Testing of objects, (and event
generation), as well as encapsulating it as a panel that can be put
anywhere a wxWindow can be put.

When I have these pieces together, I'll post it here.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Cliff Wells wrote:
> I can't think of anything you can do with a DC that you can't do with

PIL,

> so you should be able to do a pretty straightforward translation of

wxDC

> calls to PIL calls. This may give you some long-term benefits as well,
> since PIL can do a lot of things a DC can't.

HMMM. So I would do ALL the drawing in PIL...That could work. Here are
my suspected problems:

-I'm guessing that using PIL would be slower, as it can't use harddware
acceleration, and I think DCs (some of them, anyway) can.

Can't be sure, as I've never done any testing, performance-wise. PIL,
IMHO, is more than fast enough for most tasks. I doubt it does HW
acceleration as it doesn't display anything, however I suspect it may be
more optimized in general than the wx routines for drawing to a bitmap.

-Robin just added a DC.DrawPointList() and DC.DrawLineList() to
wxPython, and we might add other DrawStuffList() calls. These can make a
big difference when you have thousands of points to draw (I do). See
performance above.

No doubt, as these will be done with C, not Python. However, PIL does
support these things, as ImageDraw.point() and ImageDraw.line can take
lists of points as well.

> IMHO, if you are doing serious image manipulation, the wxImage/wxBitmap
> combo can be frustrating as they don't have much overlapping

functionality,

Well, yes, I agree. I didn't think I was doing serious image

manipulation....

If you're having problems using the standard wxPython tools, then maybe
it's more serious than you thought... or maybe you mean you laugh a lot
while you're coding? :wink:

> The only drawback is that there is
> no way to directly display a PIL image in a wxWindow, so you'll have to

use

> the routines I provided you a couple of days ago

So the Blit operation is going to be slower as well.

Well, technically, not the actual blit, but you'll have an extra conversion
prior to the blit. However, I've used it for fairly large images without
any significant delay. If you are trying to display several images in
rapid sequence, this would be an issue, but for displaying a single image I
doubt you'll notice much difference.

I guess I need to do a little testing, and see if my performance issues
really are a problem. I suspect they are, any slower drawing and I my
Canvas would be unusable.

Unless, of course, the PIL routines /are/ faster and so the time is made up
elsewhere =)

Stay tuned: I'm working on my Zoomable,Scrollable, Object graphics
Canvas, and I am about to add Hit-Testing of objects, (and event
generation), as well as encapsulating it as a panel that can be put
anywhere a wxWindow can be put.

Same wxBat-time, wxBat-channel?

···

On Mon, 25 Feb 2002 14:04:56 -0800 Chris Barker wrote:

--
Cliff Wells, Software Engineer
Logiplex Corporation (www.logiplex.net)
(503) 978-6726 x308 (800) 735-0555 x308

PS. any idea what went wrong with the debugging info in the distributed

dll?

It's put into a separate file (*.pdb) which is not put into the
distribution. I figured that if you have the ability to use the debug info
you have the ability to rebuild wxPython too, so there's no need to put
another 20M into the distribution.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

I don't know if it's really needed or even possible, but if somebody wants
to do the C/C++ for a tight integration between wxWindows/wxPython and PIL
then I'll add it to wxPython. A couple ideas come to mind. The obvious one
is a PIL Image -->wxBitmap converter, that avoids using a wxImage in between
(if possible.) The second idea is a class derived from wxDCBase that
implements all the DC drawing methods like wxMemoryDC but using a PIL image
instead of a wxBitmap.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

Robin Dunn wrote:

I don't know if it's really needed or even possible, but if somebody wants
to do the C/C++ for a tight integration between wxWindows/wxPython and PIL
then I'll add it to wxPython.

I'm not sure how to define "needed", but I know I'd use it, and it looks
like are at least a few other here that would.

A couple ideas come to mind. The obvious one
is a PIL Image -->wxBitmap converter, that avoids using a wxImage in between
(if possible.) The second idea is a class derived from wxDCBase that
implements all the DC drawing methods like wxMemoryDC but using a PIL image
instead of a wxBitmap.

I think this looks like a great idea. If only I had teh C/C++ skills to
do it.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

I don't know if it's really needed or even possible, but if somebody

wants

to do the C/C++ for a tight integration between wxWindows/wxPython and

PIL

then I'll add it to wxPython. A couple ideas come to mind. The obvious

one

is a PIL Image -->wxBitmap converter, that avoids using a wxImage in

between

Perhaps the easiest way (although perhaps not the most efficient) would be
to add a SetData() method to the wxBitmap class. This would work similar
to what I've been using, but would avoid the intermediate wxImage. What
concerns me is that I suspect there's a reason this method doesn't already
exist... I haven't looked at the source for wxWindows enough to know what
limitations are imposed by these classes. However, PIL is flexible enough
that I suspect that it could be coaxed into supplying the data in a usable
format.

(if possible.) The second idea is a class derived from wxDCBase that
implements all the DC drawing methods like wxMemoryDC but using a PIL

image

···

On Mon, 25 Feb 2002 17:07:45 -0800 Robin Dunn wrote:

instead of a wxBitmap.

--
Cliff Wells, Software Engineer
Logiplex Corporation (www.logiplex.net)
(503) 978-6726 x308 (800) 735-0555 x308

Cliff Wells wrote:

Perhaps the easiest way (although perhaps not the most efficient) would be
to add a SetData() method to the wxBitmap class. This would work similar
to what I've been using, but would avoid the intermediate wxImage. What
concerns me is that I suspect there's a reason this method doesn't already
exist...

Having not looked at the code either, I'm guessing the issue is that the
format of the data required by a wxBitmap depends on teh platform and
color depth at run time. So you would have to be able to query the
wxBitmap to find out what format the data needed to be in. This would
make for some pretty inconvenient user code. The other option would be
to have a standard data format (say 24bpp), and let wxBitmap convert it
internally, which would take some code, and be kindo f inefficient if,
in fact, your PIL image and wxBitmap did happen to be in the same format
to begin with!

I think it would take some work to do this right!

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

If SetPIL and GetPIL calls for wxBitmap simply did the necessary wxImage
translation at the C++ level that would probably be good enough. So, don't
try converting directly to wxBitmap, continue using wxImage, but wrap all
the necessary work in C++ instead of Python. I guess there could be SetPIL
and GetPIL for wxImage too. For those that don't already know the relevant
wiki link is:

http://wiki.wxpython.org/index.cgi/WorkingWithImages#line35

These are the conversion routines I'm doing for the wxBitmap wrapper class,
Bitmap in graphic.py, for PythonCard adapted from the wiki and some code by
Cliff Wells. Note these routines don't do alpha transparency correctly.

    def setPILBits(self, image):
        if (image.mode != 'RGB'):
            image = image.convert('RGB')
        imageData = image.tostring('raw', 'RGB')
        imageWx = wx.wxEmptyImage(image.size[0], image.size[1])
        imageWx.SetData(imageData)
        self._bits = wx.wxBitmapFromImage(imageWx)

    def getPILBits(self):
        imageData = wx.wxImageFromBitmap(self._bits).GetData()
        imagePIL = fromstring('RGB', self.getSize(), imageData)
        imagePIL = imagePIL.convert('RGB')
        return imagePIL

I was using the setPILBits routine in the PythonCard worldclock sample to
avoid writing a JPEG image to disk that I was downloading every five minutes
from www.time.gov server. This is PythonCard "dot notation" syntax, but it
should be pretty clear what is going on.

        fp = urllib.urlopen(url)
        jpg = fp.read()
        fp.close()

        wImageButtonWorld = self.components.imageButtonWorld
        file = StringIO.StringIO(jpg)
        image = Image.open(file)
        bmp = wImageButtonWorld.bitmap
        bmp.setPILBits(image)
        wImageButtonWorld.bitmap = bmp

I stopped using this code because it leaks memory and I'm not exactly sure
where. I probably should be setting some references to None, suggestions
welcome.

ka

···

-----Original Message-----
From: wxpython-users-admin@lists.wxwindows.org
[mailto:wxpython-users-admin@lists.wxwindows.org]On Behalf Of Chris
Barker
Sent: Tuesday, February 26, 2002 9:54 AM
To: wxpython-users@lists.wxwindows.org
Subject: Re: [wxPython] scaling bitmaps

Cliff Wells wrote:

> Perhaps the easiest way (although perhaps not the most
efficient) would be
> to add a SetData() method to the wxBitmap class. This would
work similar
> to what I've been using, but would avoid the intermediate wxImage. What
> concerns me is that I suspect there's a reason this method
doesn't already
> exist...

Having not looked at the code either, I'm guessing the issue is that the
format of the data required by a wxBitmap depends on teh platform and
color depth at run time. So you would have to be able to query the
wxBitmap to find out what format the data needed to be in. This would
make for some pretty inconvenient user code. The other option would be
to have a standard data format (say 24bpp), and let wxBitmap convert it
internally, which would take some code, and be kindo f inefficient if,
in fact, your PIL image and wxBitmap did happen to be in the same format
to begin with!

I think it would take some work to do this right!

-Chris

--
Christopher Barker, Ph.D.
Oceanographer

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

Chris.Barker@noaa.gov

Cliff Wells wrote:

> Perhaps the easiest way (although perhaps not the most efficient) would

be

> to add a SetData() method to the wxBitmap class. This would work

similar

> to what I've been using, but would avoid the intermediate wxImage.

What

> concerns me is that I suspect there's a reason this method doesn't

already

> exist...

Having not looked at the code either, I'm guessing the issue is that the
format of the data required by a wxBitmap depends on teh platform and
color depth at run time. So you would have to be able to query the
wxBitmap to find out what format the data needed to be in. This would
make for some pretty inconvenient user code. The other option would be

PIL is quite flexible regarding depth/format:
"""
Mode
The mode of an image defines the type and depth of a pixel in the image.
The current release supports the following standard modes:
1 (1-bit pixels, black and white, stored as 8-bit pixels)
L (8-bit pixels, black and white)
P (8-bit pixels, mapped to any other mode using a colour palette)
RGB (3x8-bit pixels, true colour)
RGBA (4x8-bit pixels, true colour with transparency mask)
CMYK (4x8-bit pixels, colour separation)
YCbCr (3x8-bit pixels, colour video format)
I (32-bit integer pixels)
F (32-bit floating point pixels)
PIL also supports a few special modes, including RGBX (true colour with
padding) and RGBa (true colour with premultiplied alpha).
You can read the mode of an image through the mode attribute. This is a
string containing one of the above values.
"""

So it seems the following bit of code (with some added error handling)
would work (given a hypothetical wxBitmap.SetData() method):

depthmap = {
    1: 'L',
    8: 'P', # need a palette for this depth
    16: None, # not supported by PIL?
    24: 'RGB',
    32: 'I',
}
depth = bitmap.GetDepth()
if depthmap.has_key(depth):
    bitmap.SetData(image.convert(depthmap[depth]).tostring('raw',
depthmap[depth]))

I'm not sure if the image.convert is strictly necessary. I'll have to see
if image.tostring can handle the conversion on its own.

I don't think this is _too_ unwieldy, although possibly a bit inefficient
due to all the conversions.

to have a standard data format (say 24bpp), and let wxBitmap convert it
internally, which would take some code, and be kindo f inefficient if,
in fact, your PIL image and wxBitmap did happen to be in the same format
to begin with!
I think it would take some work to do this right!

Work? I'm outta here! :wink: Maybe the most straightforward approach would be
to look at the code for wxImage.SetData() and wxBitmapFromImage() and
refactor them into a wxBitmap.SetData() method.

Regards,

···

On Tue, 26 Feb 2002 09:53:37 -0800 Chris Barker wrote:

--
Cliff Wells, Software Engineer
Logiplex Corporation (www.logiplex.net)
(503) 978-6726 x308 (800) 735-0555 x308

If SetPIL and GetPIL calls for wxBitmap simply did the necessary wxImage
translation at the C++ level that would probably be good enough. So,

don't

try converting directly to wxBitmap, continue using wxImage, but wrap all
the necessary work in C++ instead of Python. I guess there could be

SetPIL

and GetPIL for wxImage too. For those that don't already know the

relevant

wiki link is:

Hi Kevin,

I'm hesitant to suggest directly supporting PIL from wxPython for a couple
of reasons:
1. PIL is Python-specific (i.e. no support from the wxWindows community,
where the better C++ programmers are). If this is going to be an efficient
translation, it'll need to be done in C++, preferably in the wxBitmap class
and I foresee some possible resistance from the wxWindows developers to
adding Python-specific methods to their C++ code. (Unless we're going to
subclass wxBitmap and maintain the code ourselves... maybe not a bad idea).
It would probably be easier to hack out a SetData method for wxBitmap and
submit it to the wxWindows developers.
2. PIL is not necessarily the end-all for image manipulation (although it
seems like it at the moment :wink: Having a general-purpose SetData() method
on the wxBitmap class allows other toolkits to interface in a more
efficient fashion.
3. I don't know that having [GS]etPIL methods would be any more efficient
than a generic SetData method.

Why don't you think we should skip the wxImage step? It seems that
wxImages are weak counterparts to PIL images and lose their usefulness once
you start using PIL. wxBitmaps, on the other hand, are necessary, so it
makes sense to just cut wxImage out of the loop.

I stopped using this code because it leaks memory and I'm not exactly

sure

where. I probably should be setting some references to None, suggestions
welcome.

It's probably something to do with using Windows. Why don't you grow up
and get a _real_ OS.
BTW, I'll try and get you those screenshots tonight (I /promise/ <crossing

···

On Tue, 26 Feb 2002 11:00:26 -0800 Kevin Altis wrote:

).

--
Cliff Wells, Software Engineer
Logiplex Corporation (www.logiplex.net)
(503) 978-6726 x308 (800) 735-0555 x308