Python 3.x Python 3中GTK和Cairo的并行绘图
我正在制作一个GTK应用程序,它将绘制复杂的图像,这可能需要很长时间才能完成。因此,我无法在DrawingArea的“draw”回调中绘制。我决定使用Python的多处理模块,该模块允许真正的并行性,并且不存在GTK和线程安全问题 Python的多处理模块使用Pickle协议在进程之间进行通信。GTK和Cairo对象不实现该协议。我的解决方案是将Cairo曲面转换为字节,然后通过管道将其发送到另一个进程 经过一些讨论,我发现Cairo有办法获取和设置ImageSurface的内部数据,但是pycairoforpython3不支持它。(pycairoforpython2似乎得到了支持,但我有一些理由使用python3。) 但是,还有另一个Python库支持它。所以我决定:Python 3.x Python 3中GTK和Cairo的并行绘图,python-3.x,multiprocessing,pygobject,pycairo,python-cffi,Python 3.x,Multiprocessing,Pygobject,Pycairo,Python Cffi,我正在制作一个GTK应用程序,它将绘制复杂的图像,这可能需要很长时间才能完成。因此,我无法在DrawingArea的“draw”回调中绘制。我决定使用Python的多处理模块,该模块允许真正的并行性,并且不存在GTK和线程安全问题 Python的多处理模块使用Pickle协议在进程之间进行通信。GTK和Cairo对象不实现该协议。我的解决方案是将Cairo曲面转换为字节,然后通过管道将其发送到另一个进程 经过一些讨论,我发现Cairo有办法获取和设置ImageSurface的内部数据,但是pyc
转换\u magic.py
文件
main.py
#!python3.4
from multiprocessing import Process, Pipe
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib,Gtk
import cairocffi
import cairo # also known as pycairo
from conversion_magic import _UNSAFE_cairocffi_context_to_pycairo
from math import pi
import array
# Draw a circle on cairocffi context.
def circle(cr, radius):
cr.set_source_rgb(1,0,0)
cr.paint()
cr.set_source_rgb(0,1,0)
cr.arc(radius, radius, radius, 0, 2 * pi)
cr.fill()
# Drawing process
def example_target(conn):
while True:
# Wait for new task.
radius = conn.recv()
# Create a cairocffi surface, draw on it and send it as bytes.
surface = cairocffi.ImageSurface(cairo.FORMAT_ARGB32, 100,100)
cr = cairocffi.Context(surface)
circle(cr, radius)
surface.flush()
data = surface.get_data()
conn.send(bytes(data))
def main():
# Create 2 connections for two-way communication between processes.
parent,child = Pipe()
proc = Process(target=example_target, args=(child,), daemon=True)
proc.start()
# Tell process to draw circles with radius i.
for i in range(0,50):
parent.send(i)
win = Gtk.Window(default_height=300, default_width=300)
win.connect("delete-event", Gtk.main_quit)
drawing_area = Gtk.DrawingArea()
win.add(drawing_area)
def on_draw(widget,cr):
# Check if we have new images to draw.
if parent.poll():
# Convert recieved data into a cairocffi surface. The arguments of
# the ImageSurface must be the same as those in example_target.
data = parent.recv()
a = array.array('b', data)
cffi_surf = cairocffi.ImageSurface(cairo.FORMAT_ARGB32,
100,100, data = a)
# Convert cairocffi surface to pycairo surface.
cffi_cr = cairocffi.Context(cffi_surf)
pycairo_cr = _UNSAFE_cairocffi_context_to_pycairo(cffi_cr)
pycairo_surf = pycairo_cr.get_target()
# Draw pycairo surface to the surface from GTK. Using cairocffi
# surface would not work here, as GTK uses pycairo.
cr.set_source_surface(pycairo_surf)
cr.paint()
else:
pass
# TODO: Implement a buffer that holds the last image we got from
# parent.recv() and draw it. Not included in this example to make
# things easier.
return True
drawing_area.connect('draw', on_draw)
# Draw new image after each 100ms.
def cause_drawing():
drawing_area.queue_draw()
return True
GLib.timeout_add(100, cause_drawing)
win.show_all()
Gtk.main()
if __name__ == '__main__': main()
# A magical conversion function, taken from cairocffi documentation.
# http://cairocffi.readthedocs.io/en/latest/cffi_api.html#converting-cairocffi-wrappers-to-pycairo
import ctypes
import cairo # pycairo
import cairocffi
pycairo = ctypes.PyDLL(cairo._cairo.__file__)
pycairo.PycairoContext_FromContext.restype = ctypes.c_void_p
pycairo.PycairoContext_FromContext.argtypes = 3 * [ctypes.c_void_p]
ctypes.pythonapi.PyList_Append.argtypes = 2 * [ctypes.c_void_p]
def _UNSAFE_cairocffi_context_to_pycairo(cairocffi_context):
# Sanity check. Continuing with another type would probably segfault.
if not isinstance(cairocffi_context, cairocffi.Context):
raise TypeError('Expected a cairocffi.Context, got %r'
% cairocffi_context)
# Create a reference for PycairoContext_FromContext to take ownership of.
cairocffi.cairo.cairo_reference(cairocffi_context._pointer)
# Casting the pointer to uintptr_t (the integer type as wide as a pointer)
# gets the context’s integer address.
# On CPython id(cairo.Context) gives the address to the Context type,
# as expected by PycairoContext_FromContext.
address = pycairo.PycairoContext_FromContext(
int(cairocffi.ffi.cast('uintptr_t', cairocffi_context._pointer)),
id(cairo.Context),
None)
assert address
# This trick uses Python’s C API
# to get a reference to a Python object from its address.
temp_list = []
assert ctypes.pythonapi.PyList_Append(id(temp_list), address) == 0
return temp_list[0]
转换\u magic.py
#!python3.4
from multiprocessing import Process, Pipe
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib,Gtk
import cairocffi
import cairo # also known as pycairo
from conversion_magic import _UNSAFE_cairocffi_context_to_pycairo
from math import pi
import array
# Draw a circle on cairocffi context.
def circle(cr, radius):
cr.set_source_rgb(1,0,0)
cr.paint()
cr.set_source_rgb(0,1,0)
cr.arc(radius, radius, radius, 0, 2 * pi)
cr.fill()
# Drawing process
def example_target(conn):
while True:
# Wait for new task.
radius = conn.recv()
# Create a cairocffi surface, draw on it and send it as bytes.
surface = cairocffi.ImageSurface(cairo.FORMAT_ARGB32, 100,100)
cr = cairocffi.Context(surface)
circle(cr, radius)
surface.flush()
data = surface.get_data()
conn.send(bytes(data))
def main():
# Create 2 connections for two-way communication between processes.
parent,child = Pipe()
proc = Process(target=example_target, args=(child,), daemon=True)
proc.start()
# Tell process to draw circles with radius i.
for i in range(0,50):
parent.send(i)
win = Gtk.Window(default_height=300, default_width=300)
win.connect("delete-event", Gtk.main_quit)
drawing_area = Gtk.DrawingArea()
win.add(drawing_area)
def on_draw(widget,cr):
# Check if we have new images to draw.
if parent.poll():
# Convert recieved data into a cairocffi surface. The arguments of
# the ImageSurface must be the same as those in example_target.
data = parent.recv()
a = array.array('b', data)
cffi_surf = cairocffi.ImageSurface(cairo.FORMAT_ARGB32,
100,100, data = a)
# Convert cairocffi surface to pycairo surface.
cffi_cr = cairocffi.Context(cffi_surf)
pycairo_cr = _UNSAFE_cairocffi_context_to_pycairo(cffi_cr)
pycairo_surf = pycairo_cr.get_target()
# Draw pycairo surface to the surface from GTK. Using cairocffi
# surface would not work here, as GTK uses pycairo.
cr.set_source_surface(pycairo_surf)
cr.paint()
else:
pass
# TODO: Implement a buffer that holds the last image we got from
# parent.recv() and draw it. Not included in this example to make
# things easier.
return True
drawing_area.connect('draw', on_draw)
# Draw new image after each 100ms.
def cause_drawing():
drawing_area.queue_draw()
return True
GLib.timeout_add(100, cause_drawing)
win.show_all()
Gtk.main()
if __name__ == '__main__': main()
# A magical conversion function, taken from cairocffi documentation.
# http://cairocffi.readthedocs.io/en/latest/cffi_api.html#converting-cairocffi-wrappers-to-pycairo
import ctypes
import cairo # pycairo
import cairocffi
pycairo = ctypes.PyDLL(cairo._cairo.__file__)
pycairo.PycairoContext_FromContext.restype = ctypes.c_void_p
pycairo.PycairoContext_FromContext.argtypes = 3 * [ctypes.c_void_p]
ctypes.pythonapi.PyList_Append.argtypes = 2 * [ctypes.c_void_p]
def _UNSAFE_cairocffi_context_to_pycairo(cairocffi_context):
# Sanity check. Continuing with another type would probably segfault.
if not isinstance(cairocffi_context, cairocffi.Context):
raise TypeError('Expected a cairocffi.Context, got %r'
% cairocffi_context)
# Create a reference for PycairoContext_FromContext to take ownership of.
cairocffi.cairo.cairo_reference(cairocffi_context._pointer)
# Casting the pointer to uintptr_t (the integer type as wide as a pointer)
# gets the context’s integer address.
# On CPython id(cairo.Context) gives the address to the Context type,
# as expected by PycairoContext_FromContext.
address = pycairo.PycairoContext_FromContext(
int(cairocffi.ffi.cast('uintptr_t', cairocffi_context._pointer)),
id(cairo.Context),
None)
assert address
# This trick uses Python’s C API
# to get a reference to a Python object from its address.
temp_list = []
assert ctypes.pythonapi.PyList_Append(id(temp_list), address) == 0
return temp_list[0]
在Windows上使用GTK 3.18.9和Python 3.4进行了测试。您的程序是否使用了并行化?它是否显示整体渲染时间或帧速率的任何变化?这取决于您正在绘制的内容有多复杂。当然,在进程之间移动数据的部分有优化的空间,共享内存可能是最快的方式,看起来更新的python多处理将在本机上支持这一点,但可能还有其他方法可以做到这一点。