我具有以下层次结构:
ScrolledWindow
ViewPort
我已经实现了一个缩放工具,该工具基本上可以缩放我在DrawingArea中绘制的GdkPixbuf。原始图片为1280x1040。移动滚动时,Draw回调函数大约需要0.005秒的时间来绘制GdkPixbuf-看起来非常平滑。
但是,当应用300%的缩放级别时,它最多需要0.03s的时间,使其看起来不那么平滑。 DrawingArea的可见部分始终保持不变。好像绘图操作考虑了不可见的区域。
我已经设置了以下代码,以便你们可以运行它。缩放比例已经是300%。
# -*- encoding: utf-8 -*-
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
import cairo
import time
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="DrawingTool")
self.set_default_size(800, 600)
# The Zoom ratio
self.ratio = 3.
# The DrawingImage Brush
self.brush = Brush()
# Image
filename = "image.jpg"
self.original_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
self.displayed_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
self.scale_image()
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
# Zoom buttons
self.button_zoom_in = Gtk.Button(label="Zoom-In")
self.button_zoom_out = Gtk.Button(label="Zoom-Out")
# |ScrolledWindow
# |-> Viewport
# |--> DrawingArea
scrolledwindow = Gtk.ScrolledWindow()
viewport = Gtk.Viewport()
self.drawing_area = Gtk.DrawingArea()
self.drawing_area.set_size_request(
self.displayed_pixbuf.get_width(), self.displayed_pixbuf.get_height())
self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK)
# Pack
viewport.add(self.drawing_area)
scrolledwindow.add(viewport)
box.pack_start(self.button_zoom_in, False, True, 0)
box.pack_start(self.button_zoom_out, False, True, 0)
box.pack_start(scrolledwindow, True, True, 0)
self.add(box)
# Connect
self.connect("destroy", Gtk.main_quit)
self.button_zoom_in.connect("clicked", self.on_button_zoom_in_clicked)
self.button_zoom_out.connect("clicked", self.on_button_zoom_out_clicked)
self.drawing_area.connect("enter-notify-event", self.on_drawing_area_mouse_enter)
self.drawing_area.connect("leave-notify-event", self.on_drawing_area_mouse_leave)
self.drawing_area.connect("motion-notify-event", self.on_drawing_area_mouse_motion)
self.drawing_area.connect("draw", self.on_drawing_area_draw)
self.drawing_area.connect("button-press-event", self.on_drawing_area_button_press_event)
self.drawing_area.connect("button-release-event", self.on_drawing_area_button_release_event)
self.show_all()
def on_button_zoom_in_clicked(self, widget):
self.ratio += 0.1
self.scale_image()
self.drawing_area.queue_draw()
def on_button_zoom_out_clicked(self, widget):
self.ratio -= 0.1
self.scale_image()
self.drawing_area.queue_draw()
def scale_image(self):
self.displayed_pixbuf = self.original_pixbuf.scale_simple(self.original_pixbuf.get_width() * self.ratio,
self.original_pixbuf.get_height() * self.ratio, 2)
def on_drawing_area_draw(self, drawable, cairo_context):
start = time.time()
# DrawingArea size depends on Pixbuf size
self.drawing_area.get_window().resize(self.displayed_pixbuf.get_width(),
self.displayed_pixbuf .get_height())
self.drawing_area.set_size_request(self.displayed_pixbuf.get_width(),
self.displayed_pixbuf.get_height())
# Draw image
Gdk.cairo_set_source_pixbuf(cairo_context, self.displayed_pixbuf, 0, 0)
cairo_context.paint()
# Draw lines
self.brush._draw(cairo_context)
end = time.time()
print(f"Runtime of the program is {end - start}")
def on_drawing_area_mouse_enter(self, widget, event):
print("In - DrawingArea")
def on_drawing_area_mouse_leave(self, widget, event):
print("Out - DrawingArea")
def on_drawing_area_mouse_motion(self, widget, event):
(x, y) = int(event.x), int(event.y)
# Should not happen but just in case.
if not ( (x >= 0 and x < self.displayed_pixbuf.get_width()) and
(y >= 0 and y < self.displayed_pixbuf.get_height()) ):
return True
# If user is holding the left mouse button
if event.state & Gdk.EventMask.BUTTON_PRESS_MASK:
self.brush._add_point((x, y))
self.drawing_area.queue_draw()
def on_drawing_area_button_press_event(self, widget, event):
self.brush._add_point((int(event.x), int(event.y)))
def on_drawing_area_button_release_event(self, widget, event):
self.brush._line_ended()
# ## ## ## ## ## ## ## ## ## ## ## ## # ## ## ## ## ## ## ## ## ## ## ## ## #
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
# #
# Brush : #
# #
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
class Brush(object):
default_rgba_color = (0, 0, 0, 1)
def __init__(self, width=None, rgba_color=None):
if rgba_color is None:
rgba_color = self.default_rgba_color
if width is None:
width = 3
self.__width = width
self.__rgba_color = rgba_color
self.__stroke = []
self.__current_line = []
def _line_ended(self):
self.__stroke.append(self.__current_line.copy())
self.__current_line = []
def _add_point(self, point):
self.__current_line.append(point)
def _draw(self, cairo_context):
cairo_context.set_source_rgba(*self.__rgba_color)
cairo_context.set_line_width(self.__width)
cairo_context.set_line_cap(cairo.LINE_CAP_ROUND)
cairo_context.new_path()
for line in self.__stroke:
for x, y in line:
cairo_context.line_to(x, y)
cairo_context.new_sub_path()
for x, y in self.__current_line:
cairo_context.line_to(x, y)
cairo_context.stroke()
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
# ~ Getters & Setters ~ #
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
def _get_width(self):
return self.__width
def _set_width(self, width):
self.__width = width
def _get_rgba_color(self):
return self.__rgba_color
def _set_rgba_color(self, rgba_color):
self.__rgba_color = rgba_color
def _get_stroke(self):
return self.__stroke
def _get_current_line(self):
return self.__current_line
MyWindow()
Gtk.main()
那么,这是正常的不可避免的事情吗?
编辑
这是实施的解决方案的完整代码。
# -*- encoding: utf-8 -*-
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
import cairo
import time
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="DrawingTool")
self.set_default_size(800, 600)
# The Zoom ratio
self.ratio = 3.
# The DrawingImage Brush
self.brush = Brush()
# Image
filename = "image.jpg"
self.original_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
self.displayed_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
self.scale_image()
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
# Zoom buttons
self.button_zoom_in = Gtk.Button(label="Zoom-In")
self.button_zoom_out = Gtk.Button(label="Zoom-Out")
# |ScrolledWindow
# |-> Viewport
# |--> DrawingArea
scrolledwindow = Gtk.ScrolledWindow()
self.viewport = Gtk.Viewport()
self.drawing_area = Gtk.DrawingArea()
self.drawing_area.set_size_request(
self.displayed_pixbuf.get_width(), self.displayed_pixbuf.get_height())
self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK)
# Pack
self.viewport.add(self.drawing_area)
scrolledwindow.add(self.viewport)
box.pack_start(self.button_zoom_in, False, True, 0)
box.pack_start(self.button_zoom_out, False, True, 0)
box.pack_start(scrolledwindow, True, True, 0)
self.add(box)
# Connect
self.connect("destroy", Gtk.main_quit)
self.button_zoom_in.connect("clicked", self.on_button_zoom_in_clicked)
self.button_zoom_out.connect("clicked", self.on_button_zoom_out_clicked)
self.drawing_area.connect("enter-notify-event", self.on_drawing_area_mouse_enter)
self.drawing_area.connect("leave-notify-event", self.on_drawing_area_mouse_leave)
self.drawing_area.connect("motion-notify-event", self.on_drawing_area_mouse_motion)
self.drawing_area.connect("draw", self.on_drawing_area_draw)
self.drawing_area.connect("button-press-event", self.on_drawing_area_button_press_event)
self.drawing_area.connect("button-release-event", self.on_drawing_area_button_release_event)
scrolledwindow.get_hscrollbar().connect("value-changed", self.on_scrolledwindow_horizontal_scrollbar_value_changed)
scrolledwindow.get_vscrollbar().connect("value-changed", self.on_scrolledwindow_vertical_scrollbar_value_changed)
self.show_all()
def on_button_zoom_in_clicked(self, widget):
self.ratio += 0.1
self.scale_image()
self.drawing_area.queue_draw()
def on_button_zoom_out_clicked(self, widget):
self.ratio -= 0.1
self.scale_image()
self.drawing_area.queue_draw()
def scale_image(self):
self.displayed_pixbuf = self.original_pixbuf.scale_simple(self.original_pixbuf.get_width() * self.ratio,
self.original_pixbuf.get_height() * self.ratio, 2)
def on_scrolledwindow_horizontal_scrollbar_value_changed(self, scrollbar):
self.drawing_area.queue_draw()
def on_scrolledwindow_vertical_scrollbar_value_changed(self, scrollbar):
self.drawing_area.queue_draw()
def on_drawing_area_draw(self, drawable, cairo_context):
start = time.time()
# DrawingArea size depends on Pixbuf size
self.drawing_area.get_window().resize(self.displayed_pixbuf.get_width(),
self.displayed_pixbuf .get_height())
self.drawing_area.set_size_request(self.displayed_pixbuf.get_width(),
self.displayed_pixbuf.get_height())
# (x, y) offsets
pixbuf_x = int(self.viewport.get_hadjustment().get_value())
pixbuf_y = int(self.viewport.get_vadjustment().get_value())
# Width and height of the image's clip
width = cairo_context.get_target().get_width()
height = cairo_context.get_target().get_height()
if pixbuf_x + width > self.displayed_pixbuf.get_width():
width = self.displayed_pixbuf.get_width() - pixbuf_x
if pixbuf_y + height > self.displayed_pixbuf.get_height():
height = self.displayed_pixbuf.get_height() - pixbuf_y
if width > 0 and height > 0:
# Create the area of the image that will be displayed in the right position
image = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, width, height)
self.displayed_pixbuf.copy_area(pixbuf_x, pixbuf_y, width, height, image, 0, 0)
# Draw created area of the Sample's Pixbuf
Gdk.cairo_set_source_pixbuf(cairo_context, image, pixbuf_x, pixbuf_y)
cairo_context.paint()
# Draw brush strokes
self.brush._draw(cairo_context)
end = time.time()
print(f"Runtime of the program is {end - start}")
def on_drawing_area_mouse_enter(self, widget, event):
print("In - DrawingArea")
def on_drawing_area_mouse_leave(self, widget, event):
print("Out - DrawingArea")
def on_drawing_area_mouse_motion(self, widget, event):
(x, y) = int(event.x), int(event.y)
# Should not happen but just in case.
if not ( (x >= 0 and x < self.displayed_pixbuf.get_width()) and
(y >= 0 and y < self.displayed_pixbuf.get_height()) ):
return True
# If user is holding the left mouse button
if event.state & Gdk.EventMask.BUTTON_PRESS_MASK:
self.brush._add_point((x, y))
self.drawing_area.queue_draw()
def on_drawing_area_button_press_event(self, widget, event):
self.brush._add_point((int(event.x), int(event.y)))
def on_drawing_area_button_release_event(self, widget, event):
self.brush._line_ended()
# ## ## ## ## ## ## ## ## ## ## ## ## # ## ## ## ## ## ## ## ## ## ## ## ## #
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
# #
# Brush : #
# #
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
class Brush(object):
default_rgba_color = (0, 0, 0, 1)
def __init__(self, width=None, rgba_color=None):
if rgba_color is None:
rgba_color = self.default_rgba_color
if width is None:
width = 3
self.__width = width
self.__rgba_color = rgba_color
self.__stroke = []
self.__current_line = []
def _line_ended(self):
self.__stroke.append(self.__current_line.copy())
self.__current_line = []
def _add_point(self, point):
self.__current_line.append(point)
def _draw(self, cairo_context):
cairo_context.set_source_rgba(*self.__rgba_color)
cairo_context.set_line_width(self.__width)
cairo_context.set_line_cap(cairo.LINE_CAP_ROUND)
cairo_context.new_path()
for line in self.__stroke:
for x, y in line:
cairo_context.line_to(x, y)
cairo_context.new_sub_path()
for x, y in self.__current_line:
cairo_context.line_to(x, y)
cairo_context.stroke()
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
# ~ Getters & Setters ~ #
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #
def _get_width(self):
return self.__width
def _set_width(self, width):
self.__width = width
def _get_rgba_color(self):
return self.__rgba_color
def _set_rgba_color(self, rgba_color):
self.__rgba_color = rgba_color
def _get_stroke(self):
return self.__stroke
def _get_current_line(self):
return self.__current_line
MyWindow()
Gtk.main()
答案 0 :(得分:1)
我已经弄清楚了如何解决效率问题。我要做的是,我现在不绘制整个图像,而是绘制需要重绘的图像特定区域。
我将在代码下方解释每一行:
''' Draw method. '''
def _draw(self, cairo_context, pixbuf):
# Set drawing area size
self.__drawing_area.get_window().resize(pixbuf.get_width(), pixbuf.get_height())
self.__drawing_area.set_size_request(pixbuf.get_width(), pixbuf.get_height())
# (x, y) offsets
pixbuf_x = int(self.__viewport.get_hadjustment().get_value())
pixbuf_y = int(self.__viewport.get_vadjustment().get_value())
# Width and height of the image's clip
width = cairo_context.get_target().get_width()
height = cairo_context.get_target().get_height()
if pixbuf_x + width > pixbuf.get_width():
width = pixbuf.get_width() - pixbuf_x
if pixbuf_y + height > pixbuf.get_height():
height = pixbuf.get_height() - pixbuf_y
if width > 0 and height > 0:
# Create the area of the image that will be displayed in the right position
image = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, width, height)
pixbuf.copy_area(pixbuf_x, pixbuf_y, width, height, image, 0, 0)
# Draw created area of the Sample's Pixbuf
Gdk.cairo_set_source_pixbuf(cairo_context, image, pixbuf_x, pixbuf_y)
cairo_context.paint()
# Draw brush strokes
self.__brush._draw(cairo_context)
这些行设置了Gtk.DrawingArea的大小。当我的Pixbuf大小大于Gtk.DrawingArea可见区域时,Gtk.ScrolledWindow的滚动条会知道这一点,并允许您沿图像移动。当您绘制的Pixbuf小于Gtk.DrawingArea可见区域时,需要第一行,因此,例如,当您将鼠标信号连接到Gtk.DrawingArea时,仅当鼠标悬停在图像上方时才会发出这些信号。 / p>
# Set drawing area size
self.__drawing_area.get_window().resize(pixbuf.get_width(), pixbuf.get_height())
self.__drawing_area.set_size_request(pixbuf.get_width(), pixbuf.get_height())
x和y偏移量是您需要的左侧和顶部像素:
i)告诉开罗在哪里绘制Gtk.DrawingArea
ii)裁剪图像
我正在使用Gtk.Viewport,但是例如,您也可以使用Gtk.ScrolledWindow.get_hadjustment().get_value()
和Gtk.ScrolledWindow.get_vadjustment().get_value()
。
# (x, y) offsets
pixbuf_x = int(self.__viewport.get_hadjustment().get_value())
pixbuf_y = int(self.__viewport.get_vadjustment().get_value())
在下面的几行中,我仅计算要裁剪图像所需的宽度和高度。这是根据Gtk.DrawingArea可见区域的大小和您的图像大小来完成的。借助cairo_context.get_target().get_width()
,您基本上可以获得Gtk.DrawingArea可见区域的宽度,反之亦然。
# Width and height of the image's clip
width = cairo_context.get_target().get_width()
height = cairo_context.get_target().get_height()
if pixbuf_x + width > pixbuf.get_width():
width = pixbuf.get_width() - pixbuf_x
if pixbuf_y + height > pixbuf.get_height():
height = pixbuf.get_height() - pixbuf_y
最后,您只需要剪切原始图像并将其绘制在Gtk.DrawingArea的正确位置即可。 if-then-else只是我为了解决在缩小右下边缘时遇到的问题而采取的解决方法,因为Gtk组件返回以获取偏移量的值似乎在需要时不会更新。 / p>
编辑
我忘了提一下,滚动条移动时还需要重新绘制图像。否则将产生垃圾。请参阅原始问题的编辑部分中完整代码中的最后2个connect
方法。