On 5/5/2013 3:00 PM, [email protected] wrote:
>> I am wondering if it is possible to redraw only part of a canvas.
>>
>> I have a canvas with a large amount of objects, and I am moving an object
>> that is very small to a new position.  As it stands, moving the object is
>> very slow, because it requires the canvas to be completely redrawn.
>>   However, if I was able to redraw only the area behind the old and new
>> position of the object, it would be very fast.
>>
>> Currently, FloatCanvas checks to see which objects overlap the bounding
>
Drew:

Chris's recommendation of creating layers so that you only need to 
update the layer the object is in is the best/correct way to do what you 
want to do, but I can imagine for some cases the numbers of layers you 
would need would be a lot, which means lots of buffers.

If you make some assumptions you can speed up considerably what you want 
to do with what I believe is not much work - Chris feel free to tell me 
why what I am proposing is bad, but my Update() function has made my 
testers happy for the moment.

I have a function called floatcanvas.Update() which I use to show that 
an object is "selected". It is a modification of floatcanvas.Draw() 
which makes use of a modified floatcanvas._DrawObjects() function. It 
takes a  list of objects and "updates" any internal changes they have 
(in my case it is the color and linestyle; e.g. the user selects an 
object and it turns blue or gets a red outline). I also make use of some 
a key assumption to do this though: (1) Objects don't overlap.

def Update(self, Objects = []):
   """Update a set of objects by just redrawing them"""
   if N.sometrue(self.PanelSize <= 2 ): return
   #Create the dc
   ScreenDC =  wx.ClientDC(self)
   #Get the viewport
   ViewPortWorld = N.array(( self.PixelToWorld((0,0)),
                             self.PixelToWorld(self.PanelSize) )
                           )
   self.ViewPortBB = N.array( ( N.minimum.reduce(ViewPortWorld),
                                N.maximum.reduce(ViewPortWorld) ) )
   #Create the memory dc to draw to, then use for blit
   dc = wx.MemoryDC()
   dc.SelectObject(self._Buffer)
   dc.SetBackground(self.BackgroundBrush)
   #Note the "update" flag which is used by the modfiied _DrawObjects 
function
   self._DrawObjects(dc, Objects, ScreenDC, self.ViewPortBB,update = True)
   #Do the blit
   ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)

def _DrawObjects(self, dc, DrawList, ScreenDC, ViewPortBB, HTdc = 
None,foreground = False,update = False):
   """Modified _DrawObjects function that can force a drawlist, without 
using _ShowRedraw"""
   dc.SetBackground(self.BackgroundBrush)
   dc.BeginDrawing()
   PanelSize0, PanelSize1 = self.PanelSize # for speed
   WorldToPixel = self.WorldToPixel # for speed
   ScaleWorldToPixel = self.ScaleWorldToPixel # for speed
   Blit = ScreenDC.Blit # for speed
   NumBetweenBlits = self.NumBetweenBlits # for speed
   #If we are just doing an update, use the DrawList with no additional 
filter
   if update: redrawlist = DrawList
   else:
     #Otherwise filter the list, since we are using the full draw list
     redrawlist = self._ShouldRedraw(DrawList, ViewPortBB)
     #Save the redraw list
     if foreground: self._BeingDrawnList_ForeGround = redrawlist
     else: self._BeingDrawnList = redrawlist
   #etc keep doing with the _DrawObjects() function....

So now to try to tackle your problem; If you make the assumption that 
the area the object is moving "From" is empty, then you could do 
something like the following. If you have a constant line width it is a 
little easier, since you don't need to create a new pen to overwrite the 
line of the old object (if it has one at all):

import copy

DefaultLineWidth = 2
BackgroundPen = wx.Pen("WHITE",DefaultLineWidth ,wx.SOLID)
BackgroundBrush = wx.Brush((255,255,255))

def MoveAndDraw(self, Object, Delta):
   """Draw an object in a new location, then draw an empty box where it 
used to be."""
   print "%s.MoveAndDraw:" % (self)
   #Get the old data before the move
   oldBB = copy.deepcopy(Object.BoundingBox)
   oldXY = copy.deepcopy(Object.XY)
   oldWH = (oldBB._getWidth(),oldBB._getHeight())
   #Move object
   Object.Move(Delta)
   if N.sometrue(self.PanelSize <= 2 ): return
   #Create the dc
   ScreenDC =  wx.ClientDC(self)
   #Get the viewport
   ViewPortWorld = N.array(( self.PixelToWorld((0,0)),
                             self.PixelToWorld(self.PanelSize) )
                           )
   self.ViewPortBB = N.array( ( N.minimum.reduce(ViewPortWorld),
                                N.maximum.reduce(ViewPortWorld) ) )
   #Create the memory dc to draw to, then use for blit
   dc = wx.MemoryDC()
   dc.SelectObject(self._Buffer)
   dc.SetBackground(self.BackgroundBrush)
   #Call _DrawObjects() with a new "update" flag
   self._DrawObjects(dc, [Object], ScreenDC, self.ViewPortBB, update = True)
   ##Coverup the old location
   #1.Get Pen
   #2.Get Brush
   #3.Draw a box overtop the old objects bounding box
   if Object.LineWidth == DefaultLineWidth:
     dc.SetPen(BackgroundPen)
   else:
     dc.SetPen(wx.Pen("WHITE",Object.LineWidth,wx.SOLID)
   dc.SetBrush(BackgroundBrush)
   dc.DrawRectanglePointSize(oldXY,oldWH)
   #Do the blit
   ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)

I didn't have time to test this idea, so it may be a failure in 
implementation, but I think the concept is just two of my "Updates" 
combined together, but initialized differently. If the object does not 
have a bounding box, you could make a copy.deepcopy of it and pass that 
to the modified _DrawObjects() function after changing the pen and brush 
to be the background.

I hope that this helps to give some ideas and at the very least does not 
spread any floatcanvas "heresy" :)

Thanks,
BSJ


_______________________________________________
FloatCanvas mailing list
[email protected]
http://paulmcnett.com/cgi-bin/mailman/listinfo/floatcanvas

Reply via email to