Уведомления

Группа в Telegram: @pythonsu
  • Начало
  • » GUI
  • » Рябь при отрисовке большого количества объектов на FloatCanvas [RSS Feed]

#1 Июль 25, 2011 12:35:09

DonVulture
От:
Зарегистрирован: 2011-07-20
Сообщения: 15
Репутация: +  0  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

Возник вопрос при работе с FloatCanvas.
Требуется отрисовать на канве большое количество объектов - 2000 квадратиков (даже без текста внутри).
При клике в квадратик - он изменяет цвет, при этом вся остальная картинка остается без изменений.
Однако по канве пробегает волна перерисовки-связанная с перерисовкой всей канвы, что не очень то приятно глазу.
Внимание вопрос: как убрать неприятный для глаза эффект?



Офлайн

#2 Июль 25, 2011 13:46:54

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

Рябь при отрисовке большого количества объектов на FloatCanvas

Супертехника назывется double buffering



Офлайн

#3 Июль 27, 2011 14:56:38

DonVulture
От:
Зарегистрирован: 2011-07-20
Сообщения: 15
Репутация: +  0  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

Как я понимаю код с 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 какой-нибудь флаг не получится?



Офлайн

#4 Июль 27, 2011 15:13:07

regall
От: Киев
Зарегистрирован: 2008-07-17
Сообщения: 1583
Репутация: +  3  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

DonVulture
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.
Slow down и flickering - разные вещи…

Не надо ничего самому реализовывать.
1. Используйте BufferedPaintDC, а не PaintDC (он и предназначен для подготовки изображения буфере, а не отрисовки сразу в целевое окно).
2. Не используйте Refresh(), так как по умолчанию эта команда стирает все содержимое окна (читаем как перерисовывает дефолтным цветом).

P.S.
Вообще отлично было бы, если взглянуть на программный код.



Офлайн

#5 Июль 28, 2011 13:46:53

DonVulture
От:
Зарегистрирован: 2011-07-20
Сообщения: 15
Репутация: +  0  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

Провел натурные испытания для отрисовки 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()
работает быстро-никакого моргания.
Однако этот код никак не связан floatcanvas и возможностями, которые она предоставляет.
Как сделать double buffering для floatcanvas понять пока не могу.
Вот пример кода для floatcanvas. Когда количество объектов будет близко к 700 - у меня начинается моргание.

#!/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()



Офлайн

#6 Июль 28, 2011 14:56:40

regall
От: Киев
Зарегистрирован: 2008-07-17
Сообщения: 1583
Репутация: +  3  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

1. double buffering у floatcanvas включено по умолчанию, по крайней мере, когда вы работаете с NavCanvas.Canvas.
2. Реальное замедление отрисовки начинается к приближению к тысячи объектов, так что вполне может быть, что у вас это и случилось. Для этого существует зум (чтобы в области видимости все-все-все не рисовать). Ну и, конечно же, можно поэкспериментировать с объектами. Одно дело 700 объектов на 4 вершины, совсем другое, например, 200 - на 8 вершин (думаю, идея понятна).



Офлайн

#7 Июль 28, 2011 16:03:22

DonVulture
От:
Зарегистрирован: 2011-07-20
Сообщения: 15
Репутация: +  0  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

У меня такая ситуация. Есть 2000 квадратиков, а перерисовать требуется один, но видны они должны быть все.
Могли бы вы пояснить первый момент:
Одно дело 700 объектов на 4 вершины, совсем другое, например, 200 - на 8 вершин (думаю, идея понятна).
Второй момент- как использовать зум для моей ситуации.



Офлайн

#8 Июль 28, 2011 16:40:47

regall
От: Киев
Зарегистрирован: 2008-07-17
Сообщения: 1583
Репутация: +  3  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

DonVulture
Одно дело 700 объектов на 4 вершины, совсем другое, например, 200 - на 8 вершин (думаю, идея понятна).
Дело в том, что из-за использования numpy-вских массивов (и, соответственно, функций, которые оперируют массивами), должен быть прирост в случае использования меньшего количества объектов с большим количеством вершин (но я в этом не уверен, это надо тестировать).

DonVulture
Второй момент- как использовать зум для моей ситуации.
Перед отрисовкой FloatCanvas проверяет значение зума и рисует только то, что попадает в поле зрения, соответственно, если из 2000 квадратов только 10 видимых, то только они будут отрисованы, что на порядок быстрее (если у вас все 2000 должны быть в поле видимости, зум вам не поможет)

Для того, чтобы отрисовывать часть содержимого окна на более низком уровне, чем API FloatCanvas, используется RefreshRect. Насколько я помню, floatcanvas не делает RefreshRect, а каждый раз рисует сначала в буфер в памяти все объекты, что попадают в область видимости, а потом меняет буферы местами (double buffering). 2000 объектов это уже много и сам автор говорит, что начнутся тормоза, так что вам, наверное, придется как-нибудь оптимизировать представление того, что вы хотите нарисовать.

P.S.
Если вам требуется такое большое количество графики (даже невзирая на то, что это 2D), я бы рекомендовал вам взглянуть в сторону OpenGL.



Офлайн

#9 Июль 28, 2011 17:12:55

DonVulture
От:
Зарегистрирован: 2011-07-20
Сообщения: 15
Репутация: +  0  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

Понятно. Всем большое спасибо за помощь. Жаль, что floatcanvas не держит такого количества объектов.
Тогда встает следующий вопрос: можно ли к квадратикам, отображаемым на обычной канве wx, привязать события?
Или это можно сделать только для пикселей, а для квадратиков придется обрабатывать самописной логикой?



Офлайн

#10 Июль 28, 2011 18:25:32

regall
От: Киев
Зарегистрирован: 2008-07-17
Сообщения: 1583
Репутация: +  3  -
Профиль   Отправить e-mail  

Рябь при отрисовке большого количества объектов на FloatCanvas

DonVulture
Или это можно сделать только для пикселей, а для квадратиков придется обрабатывать самописной логикой?
Объекты floatcanvas'a это доп. логика



Офлайн

  • Начало
  • » GUI
  • » Рябь при отрисовке большого количества объектов на FloatCanvas[RSS Feed]

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version