2016-08-14 40 views
1

(Python 2.7)下的另一個圖像。我有一個Tkinter畫布,其中有兩幅圖像,既是畫布的高度也是寬度,因此它們覆蓋整個窗口。一張圖像在另一張圖像上。我想用鼠標,能夠在任何我想要的地方擦除頂部圖像的一部分,從而暴露底部圖像。這可能嗎?在Tkinter畫布上清除部分圖像,暴露

我很好奇如何實現下面的綁定到Tkinter運動事件的Home.erase方法。

# -*- coding: utf-8 -*- 

import io 
from PIL import Image, ImageTk 
import Tkinter as tk 

#Image 2 is on top of image 1. 
IMAGE1_DIR = "C:/path_to_image/image1.png" 
IMAGE2_DIR = "C:/path_to_image/image2.png" 

def create_image(filename, width=0, height=0): 
    """ 
    Returns a PIL.Image object from filename - sized 
    according to width and height parameters. 

    filename: str. 
    width: int, desired image width. 
    height: int, desired image height. 

    1) If neither width nor height is given, image will be returned as is. 
    2) If both width and height are given, image will resized accordingly. 
    3) If only width or only height is given, image will be scaled so specified 
    parameter is satisfied while keeping image's original aspect ratio the same. 
    """ 
    with open(filename, "rb") as f: 
     fh = io.BytesIO(f.read()) 

    #Create a PIL image from the data 
    img = Image.open(fh, mode="r") 

    #Resize if necessary. 
    if not width and not height: 
     return img 
    elif width and height: 
     return img.resize((int(width), int(height)), Image.ANTIALIAS) 
    else: #Keep aspect ratio. 
     w, h = img.size 
     scale = width/float(w) if width else height/float(h) 
     return img.resize((int(w*scale), int(h*scale)), Image.ANTIALIAS) 


class Home(object): 
    """ 
    master: tk.Tk window. 
    screen: tuple, (width, height). 
    """ 
    def __init__(self, master, screen): 
     self.screen = w, h = screen 
     self.master = master 

     self.frame = tk.Frame(self.master) 
     self.frame.pack() 
     self.can = tk.Canvas(self.frame, width=w, height=h) 
     self.can.pack() 

     #Photos will be as large as the screen. 
     p1 = ImageTk.PhotoImage(image=create_image(IMAGE1_DIR, w, h)) 
     p2 = ImageTk.PhotoImage(image=create_image(IMAGE2_DIR, w, h)) 

     ## Place photos in center of screen. 
     ## Create label to retain a reference to image so it doesn't dissapear. 

     self.photo1 = self.can.create_image((w//2, h//2), image=p1)   
     label1 = tk.Label(image=p1) 
     label1.image = p1 

     #On top. 
     self.photo2 = self.can.create_image((w//2, h//2), image=p2) 
     label2 = tk.Label(image=p2) 
     label2.image = p2 

     #Key bindings. 
     self.master.bind("<Return>", self.reset) 
     self.master.bind("<Motion>", self.erase) 

    #### Key Bindings #### 
    def reset(self, event): 
     """ Enter/Return key. """ 
     self.frame.destroy() 
     self.__init__(self.master, self.screen) 

    def erase(self, event): 
     """ 
     Mouse motion binding. 
     Erase part of top image (self.photo2) at location (event.x, event.y), 
     consequently exposing part of the bottom image (self.photo1). 
     """ 
     pass 


def main(screen=(500, 500)): 
    root = tk.Tk() 
    root.resizable(0, 0) 
    Home(root, screen) 

    #Place window in center of screen. 
    root.eval('tk::PlaceWindow %s center'%root.winfo_pathname(root.winfo_id())) 

    root.mainloop() 


if __name__ == '__main__': 
    main() 
+0

你可以通過加載兩個圖像到內存中,但只顯示頂部的圖像。之後,'erase()'方法可以選擇性地從最下面的模擬複製像素到模擬效果。 – martineau

+0

我將如何去有選擇地複製像素? – Joe

+1

PIL'Image'對象具有['getpixel()'](http://pillow.readthedocs.io/en/3.3.x/reference/Image.html#PIL.Image.Image.getpixel)和['putpixel )'](http://pillow.readthedocs.io/en/3.3.x/reference/Image.html#PIL.Image.Image.putpixel)方法。 – martineau

回答

1

感謝@martineau的建議!這是工作代碼。

from PIL import Image, ImageTk 
import Tkinter as tk 

#Image 2 is on top of image 1. 
IMAGE1_DIR = "C:/path/image1.PNG" 
IMAGE2_DIR = "C:/path/image2.PNG" 

#Brush size in pixels. 
BRUSH = 5 
#Actual size is 2*BRUSH 

def create_image(filename, width=0, height=0): 
    """ 
    Returns a PIL.Image object from filename - sized 
    according to width and height parameters. 

    filename: str. 
    width: int, desired image width. 
    height: int, desired image height. 

    1) If neither width nor height is given, image will be returned as is. 
    2) If both width and height are given, image will resized accordingly. 
    3) If only width or only height is given, image will be scaled so specified 
    parameter is satisfied while keeping image's original aspect ratio the same. 
    """ 
    #Create a PIL image from the file. 
    img = Image.open(filename, mode="r") 

    #Resize if necessary. 
    if not width and not height: 
     return img 
    elif width and height: 
     return img.resize((int(width), int(height)), Image.ANTIALIAS) 
    else: #Keep aspect ratio. 
     w, h = img.size 
     scale = width/float(w) if width else height/float(h) 
     return img.resize((int(w*scale), int(h*scale)), Image.ANTIALIAS) 


class Home(object): 
    """ 
    master: tk.Tk window. 
    screen: tuple, (width, height). 
    """ 
    def __init__(self, master, screen): 
     self.screen = w, h = screen 
     self.master = master 

     self.frame = tk.Frame(self.master) 
     self.frame.pack() 
     self.can = tk.Canvas(self.frame, width=w, height=h) 
     self.can.pack() 

     self.image1 = create_image(IMAGE1_DIR, w, h) 
     self.image2 = create_image(IMAGE2_DIR, w, h)   

     #Center of screen. 
     self.center = w//2, h//2 
     #Start with no photo on the screen. 
     self.photo = False 

     #Draw photo on screen. 
     self.draw() 

     #Key bindings. 
     self.master.bind("<Return>", self.reset) 
     self.master.bind("<Motion>", self.erase) 

    def draw(self): 
     """ 
     If there is a photo on the canvas, destroy it. 
     Draw self.image2 on the canvas. 
     """    
     if self.photo: 
      self.can.delete(self.photo) 
      self.label.destroy() 

     p = ImageTk.PhotoImage(image=self.image2) 
     self.photo = self.can.create_image(self.center, image=p) 
     self.label = tk.Label(image=p) 
     self.label.image = p 

    #### Key Bindings #### 
    def reset(self, event): 
     """ Enter/Return key. """ 
     self.frame.destroy() 
     self.__init__(self.master, self.screen) 

    def erase(self, event): 
     """ 
     Mouse motion binding. 
     Erase part of top image (self.photo2) at location (event.x, event.y), 
     consequently exposing part of the bottom image (self.photo1). 
     """   
     for x in xrange(event.x-BRUSH, event.x+BRUSH+1): 
      for y in xrange(event.y-BRUSH, event.y+BRUSH+1): 
       try: 
        p = self.image1.getpixel((x, y)) 
        self.image2.putpixel((x, y), p) 
       except IndexError: 
        pass 

     self.draw() 



def main(screen=(500, 500)): 
    root = tk.Tk() 
    root.resizable(0, 0) 
    Home(root, screen) 

    #Place window in center of screen. 
    root.eval('tk::PlaceWindow %s center'%root.winfo_pathname(root.winfo_id())) 

    root.mainloop() 


if __name__ == '__main__': 
    main() 
+1

優化建議:由於您的'erase()'方法有效地複製了由'BRUSH'大小從底部到頂部圖像定義的矩形區域中的所有像素,因此一次完成所有像素會更快調用['Image.paste()'](http://pillow.readthedocs.io/en/3.3.x/reference/Image.html#PIL.Image.Image.paste)方法比每次做像素單獨通過嵌套的'for'循環。 – martineau

+1

查看名爲painter.py的PIL演示,其方法有點不同:http://svn.effbot.org/public/pil/Scripts/ – Oblivion

+1

FWIW,即使您沒有使用canvas子類tiling-scheme from [ ''paint()'](http://svn.effbot.org/public/pil/Scripts/painter.py),它的'paint()'方法使用['Image.crop()'](http: //pillow.readthedocs.io/en/3.3.x/reference/Image.html#PIL.Image.Image.crop)和['Image.paste()'](http://pillow.readthedocs.io/en /3.3.x/reference/Image.html#PIL.Image.Image.paste)將是一種很好的方式來實現我之前評論中提出的建議。 – martineau