Форум сайта python.su
Возник вопрос при работе с FloatCanvas.
Требуется отрисовать на канве большое количество объектов - 2000 квадратиков (даже без текста внутри).
При клике в квадратик - он изменяет цвет, при этом вся остальная картинка остается без изменений.
Однако по канве пробегает волна перерисовки-связанная с перерисовкой всей канвы, что не очень то приятно глазу.
Внимание вопрос: как убрать неприятный для глаза эффект?
Офлайн
Супертехника назывется double buffering
Офлайн
Как я понимаю код с double buffering надо писать самому,
опираясь например на http://wiki.wxpython.org/DoubleBufferedDrawing
ибо для float canvas:
The real slowdown comes when you have to draw a lot of objects, because
you have to call the wx.DC.DrawSomething call each time. This is plenty
fast for tens of objects, OK for hundreds of objects, but pretty darn
slow for thousands of objects.
т.е. выставить во float canvas какой-нибудь флаг не получится?
Офлайн
DonVultureSlow down и flickering - разные вещи…
The real slowdown comes when you have to draw a lot of objects, because
you have to call the wx.DC.DrawSomething call each time. This is plenty
fast for tens of objects, OK for hundreds of objects, but pretty darn
slow for thousands of objects.
Офлайн
Провел натурные испытания для отрисовки 2000 квадратов просто через double buffering.
Код взял непосредственно отсюда:
http://wiki.wxpython.org/DoubleBufferedDrawing
# -*- coding: cp1251 -*-
#!/usr/bin/env python
import wx
import random
#---------------------------------------------------------------------------
# This has been set up to optionally use the wx.BufferedDC if
# USE_BUFFERED_DC is True, it will be used. Otherwise, it uses the raw
# wx.Memory DC , etc.
USE_BUFFERED_DC = True
class BufferedWindow(wx.Window):
"""
A Buffered window class.
To use it, subclass it and define a Draw(DC) method that takes a DC
to draw to. In that method, put the code needed to draw the picture
you want. The window will automatically be double buffered, and the
screen will be automatically updated when a Paint event is received.
When the drawing needs to change, you app needs to call the
UpdateDrawing() method. Since the drawing is stored in a bitmap, you
can also save the drawing to file by calling the
SaveToFile(self, file_name, file_type) method.
"""
def __init__(self, *args, **kwargs):
# make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
wx.Window.__init__(self, *args, **kwargs)
wx.EVT_PAINT(self, self.OnPaint)
wx.EVT_SIZE(self, self.OnSize)
# OnSize called to make sure the buffer is initialized.
# This might result in OnSize getting called twice on some
# platforms at initialization, but little harm done.
self.OnSize(None)
#self.paint_count = 0
def Draw(self, dc):
## just here as a place holder.
## This method should be over-ridden when subclassed
pass
def OnPaint(self, event):
# All that is needed here is to draw the buffer to screen
if USE_BUFFERED_DC:
dc = wx.BufferedPaintDC(self, self._Buffer)
else:
dc = wx.PaintDC(self)
dc.DrawBitmap(self._Buffer, 0, 0)
def OnSize(self,event):
# The Buffer init is done here, to make sure the buffer is always
# the same size as the Window
#Size = self.GetClientSizeTuple()
Size = self.ClientSize
# Make new offscreen bitmap: this bitmap will always have the
# current drawing in it, so it can be used to save the image to
# a file, or whatever.
self._Buffer = wx.EmptyBitmap(*Size)
self.UpdateDrawing()
def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
## This will save the contents of the buffer
## to the specified file. See the wxWindows docs for
## wx.Bitmap::SaveFile for the details
self._Buffer.SaveFile(FileName, FileType)
def UpdateDrawing(self):
"""
This would get called if the drawing needed to change, for whatever reason.
The idea here is that the drawing is based on some data generated
elsewhere in the system. If that data changes, the drawing needs to
be updated.
This code re-draws the buffer, then calls Update, which forces a paint event.
"""
dc = wx.MemoryDC()
dc.SelectObject(self._Buffer)
self.Draw(dc)
del dc # need to get rid of the MemoryDC before Update() is called.
self.Refresh()
self.Update()
class DrawWindow(BufferedWindow):
def __init__(self, *args, **kwargs):
## Any data the Draw() function needs must be initialized before
## calling BufferedWindow.__init__, as it will call the Draw
## function.
self.DrawData = {}
BufferedWindow.__init__(self, *args, **kwargs)
def Draw(self, dc):
dc.SetBackground( wx.Brush("White") )
dc.Clear() # make sure you clear the bitmap!
# Here's the actual drawing code.
for key, data in self.DrawData.items():
if(random.random()<0.5):
dc.SetBrush(wx.BLUE_BRUSH)
#dc.SetPen(wx.Pen('VIOLET', 4))
else:
dc.SetBrush(wx.GREEN_BRUSH)
for r in data:
dc.DrawRectangle(*r)
class TestFrame(wx.Frame):
def __init__(self, parent=None):
wx.Frame.__init__(self, parent,
size = (500,500),
title="Double Buffered Test",
style=wx.DEFAULT_FRAME_STYLE)
## Set up the MenuBar
MenuBar = wx.MenuBar()
file_menu = wx.Menu()
item = file_menu.Append(wx.ID_EXIT, text="&Exit")
self.Bind(wx.EVT_MENU, self.OnQuit, item)
MenuBar.Append(file_menu, "&File")
draw_menu = wx.Menu()
item = draw_menu.Append(wx.ID_ANY, "&New Drawing","Update the Drawing Data")
self.Bind(wx.EVT_MENU, self.NewDrawing, item)
item = draw_menu.Append(wx.ID_ANY,'&Save Drawing\tAlt-I','')
self.Bind(wx.EVT_MENU, self.SaveToFile, item)
MenuBar.Append(draw_menu, "&Draw")
self.SetMenuBar(MenuBar)
self.Window = DrawWindow(self)
self.Show()
# Initialize a drawing -- it has to be done after Show() is called
# so that the Windows has teh right size.
self.NewDrawing()
def OnQuit(self,event):
self.Close(True)
def NewDrawing(self, event=None):
self.Window.DrawData = self.MakeNewData()
self.Window.UpdateDrawing()
def SaveToFile(self,event):
dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to",
defaultDir = "",
defaultFile = "",
wildcard = "*.png",
style = wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
self.Window.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG)
dlg.Destroy()
def MakeNewData(self):
## This method makes some random data to draw things with.
MaxX, MaxY = self.Window.GetClientSizeTuple()
DrawData = {}
elIndex=0
w=6
h=6
for x in range(60):
for y in range(60):
elIndex=elIndex+1
#if elIndex<3:
x1=x*6
y1=y*6
l = []
l.append( (x1,y1,w,h) )
DrawData["Rectangles%s"%elIndex] = l
return DrawData
class DemoApp(wx.App):
def OnInit(self):
frame = TestFrame()
self.SetTopWindow(frame)
return True
if __name__ == "__main__":
app = DemoApp(0)
app.MainLoop()
#!/usr/bin/env python
import wx
from wx.lib.floatcanvas import FloatCanvas
haveNumeric = True
try:
import numpy as N
import numpy.random as RandomArray
haveNumpy = True
#print "Using numpy, version:", N.__version__
except ImportError:
# numpy isn't there
haveNumpy = False
errorText = (
"The FloatCanvas requires the numpy module, version 1.* \n\n"
"You can get info about it at:\n"
"http://numpy.scipy.org/\n\n"
)
try:
from floatcanvas import NavCanvas, FloatCanvas, Resources
except ImportError: # if it's not there locally, try the wxPython lib.
from wx.lib.floatcanvas import NavCanvas, FloatCanvas, Resources
import wx.lib.colourdb
import numpy as np
#---------------------------------------------------------------------------
class WEA1mainframe(wx.Frame):
def __init__(self,parent, id,title):
wx.Frame.__init__(self,parent, id,title, size=(600,600))
# make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
Canvas = NavCanvas.NavCanvas(self,-1,(500,500),
ProjectionFun = None,
Debug = 0,
BackgroundColor = "DARK SLATE BLUE",
).Canvas
self.Canvas = Canvas
Canvas.Bind(FloatCanvas.EVT_LEFT_UP, self.OnLeftUp )
self.PlotKart()
self.Show(True)
self.Canvas.ZoomToBB()
self.HitedObject = None
return None
def PlotKart(self):
Canvas = self.Canvas
elIndex=0
w=6
h=6
for x in range(60):
for y in range(60):
elIndex=elIndex+1
#if elIndex<3:
x1=x*6
y1=y*6
color = "Red"
R = Canvas.AddRectangle((x, y), (w, h), LineWidth = 1, FillColor = color, InForeground = False)
R.Name = color + "Rectangle"
R.HitFill = True
R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.ObjectHit)
def ObjectHit(self, object):
self.HitedObject = object
object.SetFillColor('Blue')
def OnLeftUp(self, event):
self.Canvas.Draw(True)
class WarEngineApp1(wx.App):
def OnInit(self):
frame = WEA1mainframe(None,-1,"War Engine Viewer")
frame.Show(True)
self.SetTopWindow(frame)
return True
app = WarEngineApp1(0)
app.MainLoop()
Офлайн
1. double buffering у floatcanvas включено по умолчанию, по крайней мере, когда вы работаете с NavCanvas.Canvas.
2. Реальное замедление отрисовки начинается к приближению к тысячи объектов, так что вполне может быть, что у вас это и случилось. Для этого существует зум (чтобы в области видимости все-все-все не рисовать). Ну и, конечно же, можно поэкспериментировать с объектами. Одно дело 700 объектов на 4 вершины, совсем другое, например, 200 - на 8 вершин (думаю, идея понятна).
Офлайн
У меня такая ситуация. Есть 2000 квадратиков, а перерисовать требуется один, но видны они должны быть все.
Могли бы вы пояснить первый момент:
Одно дело 700 объектов на 4 вершины, совсем другое, например, 200 - на 8 вершин (думаю, идея понятна).
Второй момент- как использовать зум для моей ситуации.
Офлайн
DonVultureДело в том, что из-за использования numpy-вских массивов (и, соответственно, функций, которые оперируют массивами), должен быть прирост в случае использования меньшего количества объектов с большим количеством вершин (но я в этом не уверен, это надо тестировать).
Одно дело 700 объектов на 4 вершины, совсем другое, например, 200 - на 8 вершин (думаю, идея понятна).
DonVultureПеред отрисовкой FloatCanvas проверяет значение зума и рисует только то, что попадает в поле зрения, соответственно, если из 2000 квадратов только 10 видимых, то только они будут отрисованы, что на порядок быстрее (если у вас все 2000 должны быть в поле видимости, зум вам не поможет)
Второй момент- как использовать зум для моей ситуации.
Офлайн
Понятно. Всем большое спасибо за помощь. Жаль, что floatcanvas не держит такого количества объектов.
Тогда встает следующий вопрос: можно ли к квадратикам, отображаемым на обычной канве wx, привязать события?
Или это можно сделать только для пикселей, а для квадратиков придется обрабатывать самописной логикой?
Офлайн
DonVultureОбъекты floatcanvas'a это доп. логика
Или это можно сделать только для пикселей, а для квадратиков придется обрабатывать самописной логикой?
Офлайн