使用PyGobject(Python3)绘图

4
我正在尝试使用PyGObject和Python 3编写简单的图形编辑器。我需要使用鼠标绘制具有不同颜色和宽度的线条。我找到了许多类似这个的示例,但没有更复杂的示例。
在“draw”事件之间如何保存绘制的图像?是否有增量绘制的方法,还是必须在每个“draw”事件上重新绘制窗格?我发现我可以保存路径,但如何保存绘制线条的宽度和颜色?是否有一种方法在“draw”回调之外创建图像,并仅在回调中应用(绘制)它? 这里是目前的代码。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from gi.repository import Gtk, Gdk
import os

class App(object):

    main_ui = os.path.join(os.path.dirname(__file__), 'gui.glade')

    def __init__(self):
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.main_ui)

        self.main_window.connect('destroy', self.quit)
        self.mw_quit_button.connect('clicked', self.quit)

        self.mw_graph_editor_button.connect('clicked', self.show_window, self.graph_editor_window)
        self.graph_editor_window.connect('delete-event', self.hide_window_delete)

        self.ge_menubar_file_quit.connect('activate', self.hide_window, self.graph_editor_window)
        self.ge_toolbar_quit.connect('clicked', self.hide_window, self.graph_editor_window)

        self.ge_drawingarea.connect('motion-notify-event', self.pointer_motion)
        self.ge_drawingarea.connect('motion-notify-event', self.show_coordinates)
        self.ge_drawingarea.connect('draw', self.draw_callback)

        self.path = None
        self.coord = (0, 0)
        self.rgb = (0, 0, 0)

    def __getattr__(self, name):
        obj = self.builder.get_object(name)
        if not obj:
            raise AttributeError("Object {0} has no attribute {1}".format(self, name))
        setattr(self, name, obj)
        return obj

    def draw_callback(self, drawingarea, cr):
        if self.path:
            cr.append_path(self.path)
        cr.line_to(self.coord[0], self.coord[1])
        cr.set_source_rgba(*self.rgb)
        self.path = cr.copy_path_flat()
        cr.stroke()

    def show_coordinates(self, window, event):
        self.ge_mouse_coordinates.set_label('X: {0:.0f} Y: {1:.0f}'.format(event.x, event.y))

    def pointer_motion(self, widget, event):
        if event.state & Gdk.ModifierType.BUTTON1_MASK:
            self.draw(widget, event.x, event.y)
        elif event.state & Gdk.ModifierType.BUTTON3_MASK:
            self.draw(widget, event.x, event.y, True)

    def draw(self, widget, x, y, erase=False):
        self.coord = (x,y)
        if erase:
            self.rgb = (256, 256, 256)
        else:
            self.rgb = (0, 0, 0)
        widget.queue_draw()

    def show_window(self, widget, data):
        data.show_all()

    def hide_window_delete(self, widget, event):
        widget.hide()
        return True

    def hide_window(self, widget, window):
        window.hide()

    def run(self):
        self.main_window.show_all()
        Gtk.main()

    def quit(self, widget=None, data=None):
        self.main_window.destroy()
        Gtk.main_quit()


if __name__ == "__main__":
    app = App()
    app.run()

抱歉我的英语不太好,这不是我的母语。

这里的内容涉及IT技术,请您尽可能理解。

你可以查看Qt中的涂鸦示例来获取逻辑。我会做同样的事情。在一个内部拥有的cairo_surface上绘制,然后在每次绘制信号时将缓冲区复制到小部件上下文中,完成。 - erick2red
1个回答

8

你需要使用双缓冲技术:

http://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics

你拥有一张图片,然后在这张图片上进行绘制:这张图片是“幕后缓冲区”。你可以有很多方法来将某些内容绘制到这张图片上。

然后,在响应“绘制”信号的回调函数中,也就是实际将某些内容绘制到图形内存中的方法中,你只需要使用你的“幕后缓冲区”图片即可。

代码理论(test.py):

import cairo
from gi.repository import Gtk
from os.path import abspath, dirname, join

WHERE_AM_I = abspath(dirname(__file__))

class MyApp(object):
    """Double buffer in PyGObject with cairo"""

    def __init__(self):
        # Build GUI
        self.builder = Gtk.Builder()
        self.glade_file = join(WHERE_AM_I, 'test.glade')
        self.builder.add_from_file(self.glade_file)

        # Get objects
        go = self.builder.get_object
        self.window = go('window')

        # Create buffer
        self.double_buffer = None

        # Connect signals
        self.builder.connect_signals(self)

        # Everything is ready
        self.window.show()

    def draw_something(self):
        """Draw something into the buffer"""
        db = self.double_buffer
        if db is not None:
            # Create cairo context with double buffer as is DESTINATION
            cc = cairo.Context(db)

            # Scale to device coordenates
            cc.scale(db.get_width(), db.get_height())

            # Draw a white background
            cc.set_source_rgb(1, 1, 1)

            # Draw something, in this case a matrix
            rows = 10
            columns = 10
            cell_size = 1.0 / rows
            line_width = 1.0
            line_width, notused = cc.device_to_user(line_width, 0.0)

            for i in range(rows):
                for j in range(columns):
                    cc.rectangle(j * cell_size, i * cell_size, cell_size, cell_size)
                    cc.set_line_width(line_width)
                    cc.set_source_rgb(0, 0, 0)
                    cc.stroke()

            # Flush drawing actions
            db.flush()
            
        else:
            print('Invalid double buffer')

    def main_quit(self, widget):
        """Quit Gtk"""
        Gtk.main_quit()

    def on_draw(self, widget, cr):
        """Throw double buffer into widget drawable"""
        
        if self.double_buffer is not None:
            cr.set_source_surface(self.double_buffer, 0.0, 0.0)
            cr.paint()
        else:
            print('Invalid double buffer')

        return False

    def on_configure(self, widget, event, data=None):
        """Configure the double buffer based on size of the widget"""

        # Destroy previous buffer
        if self.double_buffer is not None:
            self.double_buffer.finish()
            self.double_buffer = None

        # Create a new buffer
        self.double_buffer = cairo.ImageSurface(\
                cairo.FORMAT_ARGB32,
                widget.get_allocated_width(),
                widget.get_allocated_height()
            )

        # Initialize the buffer
        self.draw_something()

        return False

if __name__ == '__main__':
    gui = MyApp()
    Gtk.main()

Glade文件(test.glade):

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <property name="window_position">center-always</property>
    <property name="default_width">800</property>
    <property name="default_height">600</property>
    <signal name="destroy" handler="main_quit" swapped="no"/>
    <child>
      <object class="GtkDrawingArea" id="drawingarea1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <signal name="draw" handler="on_draw" swapped="no"/>
        <signal name="configure-event" handler="on_configure" swapped="no"/>
      </object>
    </child>
  </object>
</interface>

依赖项:

Python 2:

sudo apt-get install python-cairo

Python 3:

sudo apt-get install python3-gi-cairo

现在执行以下命令:
python test.py

或者

python3 test.py

它的样子是什么:

enter image description here

所有cairo的文档都可以在http://cairographics.org/documentation/pycairo/3/reference/index.html中找到。
此致敬礼。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接