python - 在 PyGobject 中绘图 (python3)

标签 python python-3.x gtk cairo pygobject

我正在尝试使用 PyGObject 和 python 3 编写简单的图形编辑器。 我需要使用鼠标绘制不同颜色和宽度的线条。我发现了很多例子,比如 this但没有比这更复杂的了。

如何在“绘制”事件之间保存绘制的图像?是否有渐进的绘图方式,或者我是否必须在每个“绘制”事件上重新绘制 Pane ?我发现我可以保存路径,但如何保存绘制线条的宽度和颜色?有没有办法在“绘制”回调之外创建图像并仅在回调内应用(绘制)它?

Here这是我现在拥有的。

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

对不起我的英语,它不是我的母语。

最佳答案

你需要使用双缓冲技术:

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()

空地文件(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

开罗的所有文档都可以在 http://cairographics.org/documentation/pycairo/3/reference/index.html 中找到

亲切的问候

关于python - 在 PyGobject 中绘图 (python3),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10270795/

相关文章:

gtk - gdk_device_get_source != GDK_SOURCE_KEYBOARD

c - GTK+ 每隔 x 秒调用一次函数

python - 为什么空 python 正则表达式搜索的返回值匹配?

python:如何拥有一个属性和一个setter函数来检测值发生的所有变化

python - Pandas 在单独的列中合并带有 id 的行

python - 理解 python 中的 cmp 和递归

python - 如何在 pandas 中处理带有超链接/url 的 excel 文件?

python - 比较两个不同大小的一维数组中的元素,具有公差

python - 在 matplotlib 中传递用于绘图的元组会在第 10 行抛出 "Tuple Object is not callable"错误。 3个

c++ - 从opencv读取时gtkmm DrawingArea分割错误