상세 컨텐츠

본문 제목

[Python 파이썬 독학 활용2편 4일차] GUI(tkinter) - 4-2

파이썬 스터디/파이썬-Python 활용편2

by 후즈테크 2021. 8. 25. 20:56

본문

반응형

30. 옵션 후반전

import os
from tkinter import *
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import filedialog # 파일 open 기능 사용
from PIL import Image


root = Tk()
root.title("WT GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title ="이미지 파일을 선택하세요", \
        filetypes=(("PNG 파일", "*.png"),("모든 파일", "*.*")), \
            #initialdir="C:/") # 최초에 C:/ 경로를 보여줌
            initialdir=r"D:\personal\study") # 경로 스트링 정상적으로 나옴 탈출 문자 예외

    # 사용자가 선택한 파일 목록
    for file in files:
        list_file.insert(END, file)




# 선택삭제
def del_file():
    # 앞에서 부터 지우면 인덱스번호가 달라짐. 뒤에서 부터 삭제
    for index in reversed(list_file.curselection()):
        list_file.delete(index)


# 저장 경로(폴더)
def browse_dest_path():
    forlder_selected = filedialog.askdirectory()
    if forlder_selected =='':
        return
    #print(forlder_selected)
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, forlder_selected)


# 이미지 통합
def merge_image():

    # print("가로 넓이 : ", cmb_width.get())
    # print("간격 : ", cmb_space.get())
    # print("포맷 : ", cmb_format.get())

    # 가로 넓이
    img_width = cmb_width.get()
    if img_width == "원본유지":
        img_width = -1 # -1 일때는 원본 기준으로
    else :
        img_width = int(img_width)

    # 간격
    img_space = cmb_space.get()
    if img_space =="좁게":
        img_space = 30
    elif img_space == "보통" :
        img_space = 60
    elif img_space == "넓게" :
        img_space = 90
    else: # 없음
        img_space = 0

    # 포맷
    img_format = cmb_format.get().lower() # PNG, JPG, BMP 값을 받아와서 소문자로 변경

    ###################



    #print(list_file.get(0, END)) # 모든 파일 목록을 가지고 오기
    images = [Image.open(x) for x in list_file.get(0, END)]
    
    # 이미지 사이즈 리스트에 넣어서 하나씩 처리
    image_sizes = [] # [(width1, height1), (width2, height2), (width3, height3), ...]
    
    if img_width > -1 :
        image_sizes = [(int(img_width) , int(img_width * x.size[1] / x.size[0])) for x in images]
    else:
        #원본 사이즈 이용
        image_sizes = [(x.size[0], x.size[1]) for x in images]

    # 계산식
    # 100 * 60 -> 80 heght 는
    # 100 : 60  = 80 : h
    # h = 60*80/100

    # 코드 대입
    # x = width = size[0]
    # y = height = size[1]
    # x' = img_width # 이 값으로 변경
    # y' = x'y/x = img_widt * size[1] / size[0]

    
    #size -> size[0] : width, size[1] height : 
    
    
    # widths = [x.size[0] for x in images]
    # heights = [x.size[1] for x in images]

    # zip 이용하여 단순화

    widths, heights = zip(*(image_sizes))

    # 최대 넓이, 전체 높이 구해옴
    max_width, total_height = max(widths), sum(heights)
    # print(max_width)
    # print(total_height)

    # 스케치북 준비

    if img_space > 0 : # 이미지 간격 옵션
        total_height += (img_space * (len(images) - 1))

    result_img = Image.new("RGB",(max_width, total_height), (255,255,255)) # 배경 흰색
    y_offset = 0 # y 위치 정보

    # for img in images:
    #     result_img.paste(img, (0, y_offset))
    #     y_offset += img.size[1] # 높이 값 만큼 더해줌

    for idx, img in enumerate(images):
        # width 가 "원본유지"가 아닐 때에는 이미지 크기 조정
        if img_width > -1:
            img = img.resize(image_sizes[idx])


        result_img.paste(img, (0, y_offset)) 
        y_offset += (img.size[1] + img_space)# 높이 값 만큼 더해줌 + 사용자가 지정한 간격

        progress = (idx + 1) / len(images) *100 # 실제 percent 정보를 계산 / index 0 부터 시작
        p_var.set(progress)
        progress_bar.update()



    # 포맷 옵션 처리
    file_name = "nodophoto." + img_format
    dest_path = os.path.join(txt_dest_path.get(), file_name)
    result_img.save(dest_path)

    msgbox.showinfo("알림","작업이 완료되었습니다.")

# 시작
def start():
    # 각 옵션들 값을 확인
    # print("가로 넓이 : ", cmb_width.get())
    # print("간격 : ", cmb_space.get())
    # print("포맷 : ", cmb_format.get())

    # 파일 목록 확인
    if list_file.size() ==0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요.")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) ==0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요.")
        return

    #이미지 통합 작업
    merge_image()
    

# # 파일 프레임(파일 추가, 선택 삭제)

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5)

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text ="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12, text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장 경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame)
txt_dest_path.pack(side="left", fill="x",padx=5, pady=5,expand=True, ipady=4)

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)


# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)


#1. 가로 넓이 옵션

# 가로 넓이 레이블
lbl_width = Label(frame_option, text ="가로넓이" , width=8)
lbl_width.pack(side="left", padx=5, pady=5)

# 가로 넓이 콤보

opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values = opt_width, width=10)

cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)


#2. 간격 옵션

# 간격 옵션 레이블

lbl_space = Label(frame_option, text ="간격" , width=8)
lbl_space.pack(side="left", padx=5, pady=5)

# 간격 옵션 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values = opt_space, width=10)

cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)



#3. 파일 포맷 옵션

# 파일포맷 옵션 레이블

lbl_format = Label(frame_option, text ="포맷" , width=8)
lbl_format.pack(side="left", padx=5, pady=5)

# 간격 옵션 콤보
opt_format = ["PNG","JPG","BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values = opt_format, width=10)

cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)


# 진행상황 progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)


p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)


# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)


btn_close = Button(frame_run, padx=5, pady=5, text="종료", width=12, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=12 , command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False,False) # x너비, y 변경 불가
root.mainloop()
반응형

 

 

30 -1. 항목 추가 (내 맘 대로)

 : 실제 나도 코딩님의 프로그램이 정상동작했다.... 하지만 실제 사용하려다 보니 완성본을 확인 하는 과정에서 번거로움을 느낄수 있었다. 그래서 버튼의 위치를 조금씩 조정하고, 저장 경로를 Windows Explorer를 이용하여 여는 버튼을 하나 추가해보기로 했다.

import os
from tkinter import *
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import filedialog # 파일 open 기능 사용
from PIL import Image
import subprocess


root = Tk()
root.title("WT GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title ="이미지 파일을 선택하세요", \
        filetypes=(("PNG 파일", "*.png"),("모든 파일", "*.*")), \
            #initialdir="C:/") # 최초에 C:/ 경로를 보여줌
            initialdir=r"D:\personal\study") # 경로 스트링 정상적으로 나옴 탈출 문자 예외

    # 사용자가 선택한 파일 목록
    for file in files:
        list_file.insert(END, file)




# 선택삭제
def del_file():
    # 앞에서 부터 지우면 인덱스번호가 달라짐. 뒤에서 부터 삭제
    for index in reversed(list_file.curselection()):
        list_file.delete(index)


# 저장 경로(폴더)
def browse_dest_path():
    forlder_selected = filedialog.askdirectory()
    if forlder_selected =='':
        return
    #print(forlder_selected)
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, forlder_selected)



# 저장 경로(폴더) 열기
def pathopen():
    if len(txt_dest_path.get()) == 0 :
        msgbox.showwarning("저장 경로 미입력", "저장 경로를 지정해주세요.")
    else :
        
    #print(txt_dest_path.get())
        path = os.path.realpath(txt_dest_path.get())
        os.startfile(path)
    

# 이미지 통합
def merge_image():

    # print("가로 넓이 : ", cmb_width.get())
    # print("간격 : ", cmb_space.get())
    # print("포맷 : ", cmb_format.get())

    # 가로 넓이
    img_width = cmb_width.get()
    if img_width == "원본유지":
        img_width = -1 # -1 일때는 원본 기준으로
    else :
        img_width = int(img_width)

    # 간격
    img_space = cmb_space.get()
    if img_space =="좁게":
        img_space = 30
    elif img_space == "보통" :
        img_space = 60
    elif img_space == "넓게" :
        img_space = 90
    else: # 없음
        img_space = 0

    # 포맷
    img_format = cmb_format.get().lower() # PNG, JPG, BMP 값을 받아와서 소문자로 변경

    ###################



    #print(list_file.get(0, END)) # 모든 파일 목록을 가지고 오기
    images = [Image.open(x) for x in list_file.get(0, END)]
    
    # 이미지 사이즈 리스트에 넣어서 하나씩 처리
    image_sizes = [] # [(width1, height1), (width2, height2), (width3, height3), ...]
    
    if img_width > -1 :
        image_sizes = [(int(img_width) , int(img_width * x.size[1] / x.size[0])) for x in images]
    else:
        #원본 사이즈 이용
        image_sizes = [(x.size[0], x.size[1]) for x in images]

    # 계산식
    # 100 * 60 -> 80 heght 는
    # 100 : 60  = 80 : h
    # h = 60*80/100

    # 코드 대입
    # x = width = size[0]
    # y = height = size[1]
    # x' = img_width # 이 값으로 변경
    # y' = x'y/x = img_widt * size[1] / size[0]

    
    #size -> size[0] : width, size[1] height : 
    
    
    # widths = [x.size[0] for x in images]
    # heights = [x.size[1] for x in images]

    # zip 이용하여 단순화

    widths, heights = zip(*(image_sizes))

    # 최대 넓이, 전체 높이 구해옴
    max_width, total_height = max(widths), sum(heights)
    # print(max_width)
    # print(total_height)

    # 스케치북 준비

    if img_space > 0 : # 이미지 간격 옵션
        total_height += (img_space * (len(images) - 1))

    result_img = Image.new("RGB",(max_width, total_height), (255,255,255)) # 배경 흰색
    y_offset = 0 # y 위치 정보

    # for img in images:
    #     result_img.paste(img, (0, y_offset))
    #     y_offset += img.size[1] # 높이 값 만큼 더해줌

    for idx, img in enumerate(images):
        # width 가 "원본유지"가 아닐 때에는 이미지 크기 조정
        if img_width > -1:
            img = img.resize(image_sizes[idx])


        result_img.paste(img, (0, y_offset)) 
        y_offset += (img.size[1] + img_space)# 높이 값 만큼 더해줌 + 사용자가 지정한 간격

        progress = (idx + 1) / len(images) *100 # 실제 percent 정보를 계산 / index 0 부터 시작
        p_var.set(progress)
        progress_bar.update()



    # 포맷 옵션 처리
    file_name = "nodophoto." + img_format
    dest_path = os.path.join(txt_dest_path.get(), file_name)
    result_img.save(dest_path)

    msgbox.showinfo("알림","작업이 완료되었습니다.")

# 시작
def start():
    # 각 옵션들 값을 확인
    # print("가로 넓이 : ", cmb_width.get())
    # print("간격 : ", cmb_space.get())
    # print("포맷 : ", cmb_format.get())

    # 파일 목록 확인
    if list_file.size() ==0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요.")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) ==0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요.")
        return

    #이미지 통합 작업
    merge_image()
    

# # 파일 프레임(파일 추가, 선택 삭제)

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5)

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text ="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12, text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장 경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame)
txt_dest_path.pack(side="left", fill="x",padx=5, pady=5,expand=True, ipady=4)

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)


# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)


#1. 가로 넓이 옵션

# 가로 넓이 레이블
lbl_width = Label(frame_option, text ="가로넓이" , width=8)
lbl_width.pack(side="left", padx=5, pady=5)

# 가로 넓이 콤보

opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values = opt_width, width=10)

cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)


#2. 간격 옵션

# 간격 옵션 레이블

lbl_space = Label(frame_option, text ="간격" , width=8)
lbl_space.pack(side="left", padx=5, pady=5)

# 간격 옵션 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values = opt_space, width=10)

cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)



#3. 파일 포맷 옵션

# 파일포맷 옵션 레이블

lbl_format = Label(frame_option, text ="포맷" , width=8)
lbl_format.pack(side="left", padx=5, pady=5)

# 간격 옵션 콤보
opt_format = ["PNG","JPG","BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values = opt_format, width=10)

cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)


# 진행상황 progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)


p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)


# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)


btn_close = Button(frame_run, padx=5, pady=5, text="종료", width=12, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_dest_path_open = Button(frame_run, padx=5, pady=5, text="결과물 확인", width=12 , command=pathopen)
btn_dest_path_open.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=12 , command=start)
btn_start.pack(side="left", padx=5, pady=5)



root.resizable(False,False) # x너비, y 변경 불가
root.mainloop()

 :저장 경로(폴더) 열기
def pathopen():
    if len(txt_dest_path.get()) == 0 :
        msgbox.showwarning("저장 경로 미입력", "저장 경로를 지정해주세요.")
    else :
        path = os.path.realpath(txt_dest_path.get())
        os.startfile(path)

 

pathopen 이라는 함수를 하나 생성하여, txt_dest_path 값이 없으면 메세지 박스를

있으면 해당 경로를 열어주도록 설정하였다.

 

 

원하는 내용을 마음대로 추가할수 있다는 사실이 진정한 자기만의 개발의 장점이 아닐까라는 생각이 문득 들었다.

 

 

32. (보너스) 스크린 샷 프로그램 (Final)

 

#pip install keyboard
import time
import keyboard
from PIL import ImageGrab

def screenshot():
    # 2020년 6월 1일 10시 20분 30초 -> _20200601_102030
    curr_time = time.strftime("_%Y%m%d_%H%M%S")
    img = ImageGrab.grab()
    img.save("image{}.png".format(curr_time))# image_20200601_102030.png

keyboard.add_hotkey("F9", screenshot) # 사용자가 F9를 누르면 스크린 샷 저장
keyboard.add_hotkey("A", screenshot) # 사용자가 a를 누르면 스크린 샷 저장
keyboard.add_hotkey("ctrl+shift+s", screenshot) # 사용자가 ctrl + shift + s 를 누르면 스크린 샷 저장

keyboard.wait("esc") # 사용자가 esc 를 누를 때까지 프로그램 수행

 : keyboard 모듈을 이용하여 단축키로 스크린샷을 사용하고 해당 파일을 저장할 수 있다.

 

반응형

관련글 더보기

댓글 영역