Thursday, November 29, 2012

กลับไปสู่ภาพต้นฉบับ

ในบทความนี้เราจะลองสร้างเมนูใหม่ คือ Edit และมีคำสั่ง Restore Image เพื่อให้เราสามารถย้อนกลับไปสู่ภาพต้นฉบับ ในกรณีที่ทำการประมวลผลไปแล้ว




หลักการก็ตรงไปตรงมาครับ สำเนารูปต้นฉบับไว้ก่อน ด้วยคำสั่ง
Image.copy()

ตัวอย่างเช่น
imOrigin = im.copy()

จากนั้นเมื่อต้องการกลับไปสู่ต้นฉบับ ก็สำเนากลับ โดยใช้คำสั่ง
im = imOrigin.copy()

ลองมาดูโค้ดเต็มๆที่เพิ่มเมนูนี้ครับ
from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")
        self.lbl = Label(self.mainwindow)

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)
        #EDIT menu
        editMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Edit", menu=editMenu)
        editMenu.add_command(label="Restore Image", command=self.imgRestore)
        #PROCESS menu
        processMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Process", menu=processMenu)
        processMenu.add_command(label="Rotate", command=self.imgRotate)

    def loadImage(self):
        self.im = Image.open("lena.jpg")
        self.imOrigin = self.im.copy()
        self.showImage()

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        self.lbl.destroy()
        self.lbl = Label(self.mainwindow, image=self.imTk)
        self.lbl.pack()

    def imgRestore(self):
        self.im = self.imOrigin.copy()
        self.showImage()

    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
app.loadImage()
root.mainloop()

Tuesday, November 27, 2012

เมนู+รูป+การประมวลผล 2

ต่อเนื่องจากคราวที่แล้วครับ แก้ปัญหา Label เก่าซ้อนกับอันใหม่ เพื่อให้ได้ผลลัพธ์ตามนี้


วิธีการแก้ปัญหาคือ ต้องทำลาย Label อันเก่าที่ค้างอยู่บนหน้าต่างก่อนที่จะแสดง Label ใหม่ นั่นคือ
1. ให้ตัวแปร Label เป็นตัวแปรของคลาส และ
2. ใช้คำสั่ง destroy เพื่อทำลาย Label อันเก่า

โคัดทั้งหมดหลังจากการแก้ไขก็จะเป็นดังนี้ครับ
from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")
        self.lbl = Label(self.mainwindow)

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)
        #PROCESS menu
        processMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Process", menu=processMenu)
        processMenu.add_command(label="Rotate",command=self.imgRotate)

    def loadImage(self):
        self.im = Image.open("lena.jpg")
        self.showImage()

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        self.lbl.destroy()
        self.lbl = Label(self.mainwindow, image=self.imTk)
        self.lbl.pack()

    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
app.loadImage()
root.mainloop()

โค้ดข้างต้นก็จะเป็นต้นแบบหลักๆในการประมวลผลรูปไ้ด้แล้วครับ ครั้งหน้าเราจะลองเพิ่มเมนูในการเรียกรูปต้นฉบับกลับคืนมา หลังจากประมวลผลไปแล้วครับ

เมนู+รูป+การประมวลผล

คราวนี้เราจะลองเอา เมนูในบทความที่แล้ว มาประกอบกับรูปในบทความก่อนหน้านั้นครับ


โคัดเมื่อรวมๆกันแล้วก็จะได้แบบนี้
from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)

    def loadImage(self):
        self.im = Image.open("lena.jpg")
        self.showImage()

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        lbl = Label(self.mainwindow, image=self.imTk)
        lbl.pack()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
app.loadImage()
root.mainloop()

จากนั้น เราจะลองเพิ่มเมนู Process สำหรับการประมวลผลภาพ เพื่อทำการหมุนรูปใหัได้ผลคือ


ลองเพิ่มโค้ดต่อไปนี้ในส่วนของ createMenu() ครับ
#PROCESS menu
processMenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="Process", menu=processMenu)
processMenu.add_command(label="Rotate",command=self.imgRotate) 

จะเห็นว่าเราได้เพิ่มคำสั่ง Rotate ในส่วนของเมนู Process ดังนั้น เราจำเป็นต้องเขียน method ใหม่ให้สอดคล้องกับคำสั่งดังกล่าว เพื่อหมุนรูปดังนี้
    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

เมื่อแก้ไขข้างต้นแล้ว หากรันโปรแกรมแ้ล้วเลือกเมนู Process / Rotate จะได้ผลลัพธ์คือ



อ้าว ทำไมเป็นแบบนี้ล่ะ มีส่วนว่างๆข้างบนมาได้อย่างไร?

เหตุผลก็เป็นเพราะว่า ใน showImage() มีการสร้าง Label ใหม่เรื่อยๆ ส่วน Label เก่าที่ถูกสร้างไว้ก็ยังเหลืออยู่ (ยกเว้นรูป เพราะถูก garbage collector ทำลายไปแล้ว) ทำให้เกิดพื้นที่ว่างที่เป็นของ Label เก่าค้างอยู่ในหน้าต่าง

เราจะมาลองแก้ปัญหานี้ในคราวถัดไปครับ

Monday, November 26, 2012

การสร้างเมนู

ตอนที่แล้วเรามีการประมวลผลภาพและแสดงผลในหน้าต่าง ซึ่งจะเรียกใช้งานคำสั่งผ่านโค้ดโดยตรง

ขั้นตอนดังกล่าวอาจจะไม่สะดวกและยืดหยุ่นพอ ในกรณีที่มีการประมวลผลมีหลายรูปแบบ ซึ่งผู้ใช้งานน่าจะเลือกได้ด้วยตนเอง

ดังนั้น ทางเลือกนึงคือการสร้างเมนู เพื่อให้จัดการการประมวลผลรูปได้ง่ายขึ้น

ในบทความนี้จึงนำเสนอการสร้างเมนูอย่างง่าย เพื่อให้ได้ผลลัพธ์ดังนี้


นั่นคือ การสร้างเมนูหนึ่งเมนูชื่อ File และมีคำสั่งย่อยคือ Exit เมื่อคลิกที่คำสั่งนี้ก็จะปิดหน้าต่างไป

เราลองมาสร้างเมนูก่อน ด้วยคำสั่งดังต่อไปนี้
from Tkinter import Tk,Menu

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        #attach menubar to main window
        self.mainwindow.config(menu=menubar)
        #FILE menu, tearoff = user can drag the menu
        fileMenu = Menu(menubar, tearoff=0)
        #add FILE menu to menubar
        menubar.add_cascade(label="File", menu=fileMenu)
        #add EXIT command to FILE menu
        fileMenu.add_command(label="Exit")

#---Main app starts here---
root = Tk()
app = mainApp(root)
#create menu
app.createMenu()
root.mainloop()

ณ ตอนนี้ หากเราคลิกที่คำสั่ง Exit ก็จะยังไม่มีอะไรเกิดขึ้น เราต้องเชื่อมคำสั่งนี้กับ method ซึ่งในที่นี้ก็คือคำสั่ง root.destroy (ผมลองใช้คำสั่ง root.quit ตามตัวอย่างหลายๆแห่งแล้วมันค้างงงงง)

แก้ไขเฉพาะบรรทัดเดียวครับ คือบรรทัด
 fileMenu.add_command(label="Exit")
เปลี่ยนเป็น
fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)

จากนั้นเมนูคำสั่ง Exit ก็จะใช้งานได้แล้วครับ

ตอนหน้าเราจะลองเอาเมนูไปผูกกับคำสั่งในการประมวลผลภาพต่อครับ

การประมวลผลรูปและแสดงผลในหน้าต่าง

จากบทความที่แล้ว เราได้ลองเขียนคลาสที่มี constructor สำหรับอ่านและแสดงผลรูป ซึ่งได้ผลลัพธ์ดังนี้


ก่อนที่จะประมวลผลรูป น่าจะเป็นการดีที่เราแยกหน้าที่เหล่านี้ออกจากกันโดยใช้ method ใหม่แทน นั่นคือ เราจะแยกเป็น 3 method

1. constructor ทำหน้าที่รับหน้าต่างหลักมา แล้วกำหนด title ของหน้าต่าง
2. loadImage ทำหน้าที่อ่านไฟล์รูปมาเก็บไว้ในตัวแปรของคลาส
3. showImage ทำหน้าที่แสดงผลรูปในหน้าต่าง

เพื่อที่จะให้ตัวแปรใช้ร่วมกันในแต่ละ method ได้ ใน python จึงต้องสร้างให้เป็นตัวแปรของคลาส คือ มีคำว่า self นำหน้า

โค้ดที่ปรับปรุงใหม่ แต่ให้ผลลัพธ์เหมือนบทความที่แล้วก็จะเป็นดังนี้
from Tkinter import Tk, Label
from PIL import Image, ImageTk

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Image Label")

    def loadImage(self):
        #load image
        self.im = Image.open("lena.jpg")

    def showImage(self):
        #convert image to Tk format
        self.imTk = ImageTk.PhotoImage(self.im)
        #create a label on main window with image
        lbl = Label(self.mainwindow, image=self.imTk)
        #pack window to label and display
        lbl.pack()

#---Main app starts here---
root = Tk()
app = mainApp(root)
#load image
app.loadImage()
#show image
app.showImage()
root.mainloop()

สังเกตว่าคราวนี้เราอ่านรูปและแสดงผลด้วย method ใหม่ ซึ่งถูกเรียกใช้งานผ่าน object

ต่อไปเราจะลองเขียน method เพื่อประมวลผลรูป เช่น เปลี่ยนรูปสี ให้เป็นรูปสีเทา
หมายเหตุ ใน PIL รูปสีปกติจะเรียกว่า RGB และรูปสีเทาจะเรียกว่า L



โค้ดของโปรแกรมก็จะเพิ่ม method ใหม่สั้นๆแค่
 #----------------------------------------------
    #method to convert image to grayscale
    def imgGray(self):
        self.im = self.im.convert("L")
        self.showImage()
#----------------------------------------------

ถ้าเป็นโค้ดเต็มๆ ก็ตามนี้ครับ
from Tkinter import Tk, Label
from PIL import Image, ImageTk

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Image Label")

    def loadImage(self):
        #load image
        self.im = Image.open("lena.jpg")

    def showImage(self):
        #convert image to Tk format
        self.imTk = ImageTk.PhotoImage(self.im)
        #create a label on main window with image
        lbl = Label(self.mainwindow, image=self.imTk)
        #pack window to label and display
        lbl.pack()

    #method to convert image to grayscale
    def imgGray(self):
        self.im = self.im.convert("L")
        self.showImage()

#---Main app starts here---
root = Tk()
app = mainApp(root)
#load image
app.loadImage()
#convert to grayscale image
app.imgGray()
root.mainloop()