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