我有以下层次结构:
滚动窗口
视口
- 绘图区
我已经实现了一个缩放工具,它基本上可以缩放我在 DrawingArea 中绘制的 GdkPixbuf。最初,图像是 1280x1040。移动滚动条时,Draw 回调函数需要大约 0.005 秒来绘制 GdkPixbuf - 它看起来非常流畅。
但是,当应用 300% 的缩放级别时,最多需要 0.03 秒,使其看起来不太平滑。可见的 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()