PyCairo 中的透明度
最后修改于 2023 年 7 月 17 日
在 PyCairo 教程的这一部分,我们将讨论透明度。我们提供了一些基本定义和三个有趣的透明度示例。
透明度是指能够看穿材料的性质。理解透明度的最简单方法是想象一块玻璃或水。从技术上讲,光线可以穿过玻璃,这样我们就能看到玻璃后面的物体。
在计算机图形学中,我们可以通过alpha 混合来实现透明效果。Alpha 混合是组合图像与背景以创建部分透明外观的过程。组合过程使用alpha 通道。Alpha 通道是图形文件格式中的一个 8 位图层,用于表示半透明度(透明度)。每像素额外的八位用作遮罩,代表 256 个半透明度级别。
透明矩形
第一个示例将绘制十个具有不同透明度级别的矩形。
def on_draw(self, wid, cr): for i in range(1, 11): cr.set_source_rgba(0, 0, 1, i*0.1) cr.rectangle(50*i, 20, 40, 40) cr.fill()
set_source_rgba
方法有一个 alpha 参数用于提供透明度。
for i in range(1, 11): cr.set_source_rgba(0, 0, 1, i*0.1) cr.rectangle(50*i, 20, 40, 40) cr.fill()
此代码创建十个 alpha 值从 0.1 到 1 的矩形。

烟雾效果
在下面的示例中,我们创建了一个烟雾效果。该示例将显示一个不断增长的居中文本,该文本将从某个点逐渐淡出。这是我们在 Flash 动画中经常看到的非常常见的效果。paint_with_alpha
方法对于创建此效果至关重要。
#!/usr/bin/python ''' ZetCode PyCairo tutorial This program creates a 'puff' effect. author: Jan Bodnar website: zetcode.com ''' from gi.repository import Gtk, GLib import cairo class cv(object): SPEED = 14 TEXT_SIZE_MAX = 20 ALPHA_DECREASE = 0.01 SIZE_INCREASE = 0.8 class Example(Gtk.Window): def __init__(self): super(Example, self).__init__() self.init_ui() def init_ui(self): self.darea = Gtk.DrawingArea() self.darea.connect("draw", self.on_draw) self.add(self.darea) self.timer = True self.alpha = 1.0 self.size = 1.0 GLib.timeout_add(cv.SPEED, self.on_timer) self.set_title("Puff") self.resize(350, 200) self.set_position(Gtk.WindowPosition.CENTER) self.connect("delete-event", Gtk.main_quit) self.show_all() def on_timer(self): if not self.timer: return False self.darea.queue_draw() return True def on_draw(self, wid, cr): w, h = self.get_size() cr.set_source_rgb(0.5, 0, 0) cr.paint() cr.select_font_face("Courier", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) self.size = self.size + cv.SIZE_INCREASE if self.size > cv.TEXT_SIZE_MAX: self.alpha = self.alpha - cv.ALPHA_DECREASE cr.set_font_size(self.size) cr.set_source_rgb(1, 1, 1) (x, y, width, height, dx, dy) = cr.text_extents("ZetCode") cr.move_to(w/2 - width/2, h/2) cr.text_path("ZetCode") cr.clip() cr.paint_with_alpha(self.alpha) if self.alpha <= 0: self.timer = False def main(): app = Example() Gtk.main() if __name__ == "__main__": main()
该示例在窗口上创建一个不断增长和淡出的文本。
class cv(object): SPEED = 14 TEXT_SIZE_MAX = 20 ALPHA_DECREASE = 0.01 SIZE_INCREASE = 0.8
在这里,我们定义了示例中使用的一些常量。
self.alpha = 1.0 self.size = 1.0
这两个变量存储当前 alpha 值和文本大小。
GLib.timeout_add(cv.SPEED, self.on_timer)
每隔 14 毫秒调用一次 on_timer() 方法。
def on_timer(self): if not self.timer: return False self.darea.queue_draw() return True
在 on_timer
方法中,我们使用 queue_draw
方法重绘绘图区域控件。
def on_draw(self, wid, cr): w, h = self.get_size() cr.set_source_rgb(0.5, 0, 0) cr.paint() cr.select_font_face("Courier", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ...
在 on_draw
方法中,我们获取窗口客户区的宽度和高度。这些值用于居中文本。我们用一种深红色填充窗口背景。我们为文本选择了一种 Courier 字体。
(x, y, width, height, dx, dy) = cr.text_extents("ZetCode")
我们获取文本指标。我们只使用文本宽度。
cr.move_to(w/2 - width/2, h/2)
我们移动到一个文本将居中于窗口的位置。
cr.text_path("ZetCode") cr.clip() cr.paint_with_alpha(self.alpha)
我们使用 text_path
方法获取文本路径。我们使用 clip
方法将绘制限制在当前路径内。paint_with_alpha
方法在当前裁剪区域内的任何地方使用 alpha 值的遮罩绘制当前源。

倒影图像
在下一个示例中,我们展示了一个倒影图像。这种效果产生了图像好像倒映在水中的错觉。
#!/usr/bin/python ''' ZetCode PyCairo tutorial This program creates an image reflection. author: Jan Bodnar website: zetcode.com ''' from gi.repository import Gtk import cairo import sys class Example(Gtk.Window): def __init__(self): super(Example, self).__init__() self.init_ui() self.load_image() self.init_vars() def init_ui(self): darea = Gtk.DrawingArea() darea.connect("draw", self.on_draw) self.add(darea) self.set_title("Reflection") self.resize(300, 350) self.set_position(Gtk.WindowPosition.CENTER) self.connect("delete-event", Gtk.main_quit) self.show_all() def load_image(self): try: self.s = cairo.ImageSurface.create_from_png("slanec.png") except Exception, e: print e.message sys.exit(1) def init_vars(self): self.imageWidth = self.s.get_width() self.imageHeight = self.s.get_height() self.gap = 40 self.border = 20 def on_draw(self, wid, cr): w, h = self.get_size() lg = cairo.LinearGradient(w/2, 0, w/2, h*3) lg.add_color_stop_rgba(0, 0, 0, 0, 1) lg.add_color_stop_rgba(h, 0.2, 0.2, 0.2, 1) cr.set_source(lg) cr.paint() cr.set_source_surface(self.s, self.border, self.border) cr.paint() alpha = 0.7 step = 1.0 / self.imageHeight cr.translate(0, 2 * self.imageHeight + self.gap) cr.scale(1, -1) i = 0 while(i < self.imageHeight): cr.rectangle(self.border, self.imageHeight-i, self.imageWidth, 1) i = i + 1 cr.save() cr.clip() cr.set_source_surface(self.s, self.border, self.border) alpha = alpha - step cr.paint_with_alpha(alpha) cr.restore() def main(): app = Example() Gtk.main() if __name__ == "__main__": main()
窗口上显示着一座城堡的倒映遗迹。
def load_image(self): try: self.s = cairo.ImageSurface.create_from_png("slanec.png") except Exception, e: print e.message sys.exit(1)
在 load_image
方法中,从 PNG 图像创建了一个图像表面。
def init_vars(self): self.imageWidth = self.s.get_width() self.imageHeight = self.s.get_height() self.gap = 40 self.border = 20
在 init_vars
方法中,我们获取图像的宽度和高度。我们还定义了两个变量。
lg = cairo.LinearGradient(w/2, 0, w/2, h*3) lg.add_color_stop_rgba(0, 0, 0, 0, 1) lg.add_color_stop_rgba(h, 0.2, 0.2, 0.2, 1) cr.set_source(lg) cr.paint()
窗口背景填充了渐变画笔。画笔是从黑色到深灰色的平滑混合。
cr.translate(0, 2 * self.imageHeight + self.gap) cr.scale(1, -1)
此代码翻转图像并将其平移到原始图像下方。平移操作是必要的,因为缩放操作使图像上下颠倒并将图像向上平移。要理解发生了什么,只需拿一张照片放在桌子上。然后翻转它。
i = 0 while(i < self.imageHeight): cr.rectangle(self.border, self.imageHeight-i, self.imageWidth, 1) i = i + 1 cr.save() cr.clip() cr.set_source_surface(self.s, self.border, self.border) alpha = alpha - step cr.paint_with_alpha(alpha) cr.restore()
这是最后一部分。我们使第二个图像透明。但透明度不是恒定的。图像逐渐淡出。倒影图像是逐行绘制的。clip
方法将绘制限制在高度为 1 的矩形内。paint_with_alpha
在绘制图像表面的当前裁剪时会考虑透明度。

等待演示
在这个例子中,我们使用透明效果创建了一个等待演示。我们绘制了八条逐渐淡出的线条,产生了线条正在移动的错觉。这种效果通常用于告知用户后台正在进行一项耗时的任务。例如,互联网上的流媒体视频。
#!/usr/bin/python ''' ZetCode PyCairo tutorial This program creates a 'waiting' effect. author: Jan Bodnar website: zetcode.com ''' from gi.repository import Gtk, GLib import cairo import math class cv(object): trs = ( ( 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 ), ( 1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9 ), ( 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8 ), ( 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65 ), ( 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5 ), ( 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3 ), ( 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15 ), ( 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, ) ) SPEED = 100 CLIMIT = 1000 NLINES = 8 class Example(Gtk.Window): def __init__(self): super(Example, self).__init__() self.init_ui() def init_ui(self): self.darea = Gtk.DrawingArea() self.darea.connect("draw", self.on_draw) self.add(self.darea) self.count = 0 GLib.timeout_add(cv.SPEED, self.on_timer) self.set_title("Waiting") self.resize(250, 150) self.set_position(Gtk.WindowPosition.CENTER) self.connect("delete-event", Gtk.main_quit) self.show_all() def on_timer(self): self.count = self.count + 1 if self.count >= cv.CLIMIT: self.count = 0 self.darea.queue_draw() return True def on_draw(self, wid, cr): cr.set_line_width(3) cr.set_line_cap(cairo.LINE_CAP_ROUND) w, h = self.get_size() cr.translate(w/2, h/2) for i in range(cv.NLINES): cr.set_source_rgba(0, 0, 0, cv.trs[self.count%8][i]) cr.move_to(0.0, -10.0) cr.line_to(0.0, -40.0) cr.rotate(math.pi/4) cr.stroke() def main(): app = Example() Gtk.main() if __name__ == "__main__": main()
我们绘制八条线,每条线有八个不同的 alpha 值。
class cv(object): trs = ( ( 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 ), ( 1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9 ), ( 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8 ), ( 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65 ), ( 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5 ), ( 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3 ), ( 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15 ), ( 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, ) ) ...
这是一个用于此演示的二维透明度值元组。有 8 行,每行代表一种状态。这 8 条线将连续使用这些值。
SPEED = 100 CLIMIT = 1000 NLINES = 8
SPEED
常量控制动画的速度。CLIMIT
是 self.count
变量的最大值。达到此限制后,变量将被重置为 0。NLINES
是示例中绘制的行数。
GLib.timeout_add(cv.SPEED, self.on_timer)
我们使用计时器函数来创建动画。每隔 cv.SPEED
毫秒调用一次 on_timer
方法。
def on_timer(self): self.count = self.count + 1 if self.count >= cv.CLIMIT: self.count = 0 self.darea.queue_draw() return True
在 on_timer
方法中,我们增加 self.count
变量。如果变量达到 cv.CLIMIT
常量,则将其设置为 0。我们防止溢出,并且不处理大数。
def on_draw(self, wid, cr): cr.set_line_width(3) cr.set_line_cap(cairo.LINE_CAP_ROUND) ...
我们使线条稍粗,以便它们更可见。 我们用圆角绘制线条。
w, h = self.get_size() cr.translate(w/2, h/2)
我们将我们的绘制定位在窗口的中心。
for i in range(cv.NLINES): cr.set_source_rgba(0, 0, 0, cv.trs[self.count%8][i]) cr.move_to(0.0, -10.0) cr.line_to(0.0, -40.0) cr.rotate(math.pi/4) cr.stroke()
在 for 循环中,我们绘制了八条具有不同透明度值的旋转线。线之间以 45 度角分隔。

在 PyCairo 教程的这一部分,我们已经介绍了透明度。