返回介绍

Clipping & masking in PyCairo

发布于 2025-02-22 22:19:56 字数 8776 浏览 0 评论 0 收藏 0

In this part of the PyCairo tutorial, we will talk about clipping and masking operations.

Clipping

Clipping is restricting of drawing to a certain area. This is done for efficiency reasons and to create interesting effects. PyCairo has a clip() method to set the clipping.

#!/usr/bin/python

'''
ZetCode PyCairo tutorial 

This program shows how to perform
clipping in PyCairo.

author: Jan Bodnar
website: zetcode.com 
last edited: August 2012
'''


from gi.repository import Gtk, GLib
import cairo
import math
import random


class Example(Gtk.Window):

  def __init__(self):
    super(Example, self).__init__()
    
    self.init_ui()
    self.load_image()
    self.init_vars()
    
    
  def init_ui(self):  

    self.darea = Gtk.DrawingArea()
    self.darea.connect("draw", self.on_draw)
    self.add(self.darea)
    
    GLib.timeout_add(100, self.on_timer)

    self.set_title("Clipping")
    self.resize(300, 200)
    self.set_position(Gtk.WindowPosition.CENTER)
    self.connect("delete-event", Gtk.main_quit)
    self.show_all()
    
    
  def load_image(self):
    
    self.image = cairo.ImageSurface.create_from_png("beckov.png")
 
    
  def init_vars(self):  
  
    self.pos_x = 128
    self.pos_y = 128
    self.radius = 40

    self.delta = [3, 3]    
    
    
  def on_timer(self):
    
    self.pos_x += self.delta[0]
    self.pos_y += self.delta[1]    
       
    self.darea.queue_draw()
    return True       
    
  
  def on_draw(self, wid, cr):

    w, h = self.get_size()

    if (self.pos_x < 0 + self.radius):
      self.delta[0] = random.randint(5, 9)
    elif (self.pos_x > w - self.radius):
      self.delta[0] = -random.randint(5, 9)
    
    if (self.pos_y < 0 + self.radius): 
      self.delta[1] = random.randint(5, 9)
    elif (self.pos_y > h - self.radius):
      self.delta[1] = -random.randint(5, 9)    

    cr.set_source_surface(self.image, 1, 1)
    cr.arc(self.pos_x, self.pos_y, self.radius, 0, 2*math.pi)
    cr.clip()
    cr.paint()    
    
  
def main():
  
  app = Example()
  Gtk.main()
    
    
if __name__ == "__main__":  
  main()

In this example, we will clip an image. A circle is moving on the window area and showing a part of the underlying image. This is as if we looked through a hole.

def load_image(self):
    
  self.image = cairo.ImageSurface.create_from_png("beckov.png")

This is the underlying image. Each timer cycle, we will see a portion of this image.

if (self.pos_x < 0 + self.radius):
  self.delta[0] = random.randint(5, 9)
elif (self.pos_x > w - self.radius):
  self.delta[0]= -random.randint(5, 9) 

If the circle hits the left or the right side of the window, the direction of the circle movement changes randomly. Same applies for the top and bottom sides.

cr.arc(self.pos_x, self.pos_y, self.radius, 0, 2*math.pi)

This line adds a circular path to the Cairo context.

cr.clip()

The clip() sets a clipping region. The clipping region is the current path used. The current path was created by the arc() method call.

cr.paint()

The paint() paints the current source everywhere within the current clip region.

Clipping
Figure: Clipping

Masking

Before the source is applied to the surface, it is filtered first. The mask is used as a filter. The mask determines where the source is applied and where not. Opaque parts of the mask allow to copy the source. Transparent parts do not let to copy the source to the surface.

#!/usr/bin/python

'''
ZetCode PyCairo tutorial 

This program demonstrates masking.

author: Jan Bodnar
website: zetcode.com 
last edited: August 2012
'''


from gi.repository import Gtk
import cairo


class Example(Gtk.Window):

  def __init__(self):
    super(Example, self).__init__()
    
    self.init_ui()
    self.load_image()
    
    
  def init_ui(self):  

    darea = Gtk.DrawingArea()
    darea.connect("draw", self.on_draw)
    self.add(darea)

    self.set_title("Masking")
    self.resize(310, 100)
    self.set_position(Gtk.WindowPosition.CENTER)
    self.connect("delete-event", Gtk.main_quit)
    self.show_all()
    
    
  def load_image(self):  
    
    self.ims = cairo.ImageSurface.create_from_png("omen.png")
    
  
  def on_draw(self, wid, cr):

    cr.mask_surface(self.ims, 0, 0);
    cr.fill()

    
def main():
  
  app = Example()
  Gtk.main()
    
    
if __name__ == "__main__":  
  main()

In the example, the mask determines where to paint and where not to paint.

cr.mask_surface(self.ims, 0, 0);
cr.fill()

We use an image as a mask, thus displaying it on the window.

Masking
Figure: Masking

Blind down effect

In this code example, we will blind down our image. This is similar to what we do with a roller-blind.

#!/usr/bin/python

'''
ZetCode PyCairo tutorial 

This program creates a blind down
effect using masking operation.

author: Jan Bodnar
website: zetcode.com 
last edited: August 2012
'''


from gi.repository import Gtk, GLib
import cairo
import math


class Example(Gtk.Window):

  def __init__(self):
    super(Example, self).__init__()
    
    self.init_ui()
    self.load_image()
    self.init_vars()
    
    
  def init_ui(self):  

    self.darea = Gtk.DrawingArea()
    self.darea.connect("draw", self.on_draw)
    self.add(self.darea)
         
    GLib.timeout_add(35, self.on_timer)

    self.set_title("Blind down")
    self.resize(325, 250)
    self.set_position(Gtk.WindowPosition.CENTER)
    self.connect("delete-event", Gtk.main_quit)
    self.show_all()
    
    
    
  def load_image(self):
    
    self.image = cairo.ImageSurface.create_from_png("beckov.png")    
         
      
  def init_vars(self):    
    
    self.timer = True
    self.h = 0
    self.iw = self.image.get_width()
    self.ih = self.image.get_height()   
    
    self.ims = cairo.ImageSurface(cairo.FORMAT_ARGB32, 
      self.iw, self.ih)       
    
    
  def on_timer(self):
         
    if (not self.timer):
      return False
           
    self.darea.queue_draw()
    return True       
    
  
  def on_draw(self, wid, cr):
    
    ic = cairo.Context(self.ims)

    ic.rectangle(0, 0, self.iw, self.h)
    ic.fill()

    self.h += 1
    
    if (self.h == self.ih): 
      self.timer = False

    cr.set_source_surface(self.image, 10, 10)
    cr.mask_surface(self.ims, 10, 10)    
    
  
def main():
  
  app = Example()
  Gtk.main()
    
    
if __name__ == "__main__":  
  main()

The idea behind the blind down effect is quite simple. The image is h pixels high. We draw 0, 1, 2 ... lines of 1px height. Each cycle the portion of the image is 1px higher, until the whole image is visible.

def load_image(self):
  
  self.image = cairo.ImageSurface.create_from_png("beckov.png")      

In the load_image() method, we create an image surface from a PNG image.

def init_vars(self):    
  
  self.timer = True
  self.h = 0
  self.iw = self.image.get_width()
  self.ih = self.image.get_height()   
  
  self.ims = cairo.ImageSurface(cairo.FORMAT_ARGB32, 
    self.iw, self.ih)         

In the init_vars() method, we initiate some variables. We initiate the self.timer and the self.h variables. We get the width and height of the loaded image. And we create an empty image surface. It is going to be filled with lines of pixels from the image surface that we have created earlier.

ic = cairo.Context(self.ims)

We create a cairo context from the empty image source.

ic.rectangle(0, 0, self.iw, self.h)
ic.fill()

We draw a rectangle into the initially empty image. The rectangle will be 1px higher each cycle. The image created this way will serve as a mask later.

self.h += 1

The height of the image to show is increased by one unit.

if (self.h == self.ih): 
  self.timer = False

We stop the timer method when we draw the whole image on the GTK window.

cr.set_source_surface(self.image, 10, 10)
cr.mask_surface(self.ims, 10, 10)

The image of a castle is set as a source for painting. The mask_surface() paints the current source using the alpha channel of surface as a mask.

This chapter covered clipping and masking in PyCairo.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文