2022. 8. 6. 20:08ㆍPROJECT/Python_Project
안녕하세요 여러분 Python 첫 번째 프로젝트 글로 돌아온 경로당입니다.
앞서 요번 프로젝트는 Python 언어 tkinter의 Gui 를 독학하여 처음으로 "PGR 이미지 합치기"를 제작하였습니다.
바로 시작해보도록 하죠.
1. Import 내용
import os #dest_path 부분에 쓰이기 위해서
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import *
from tkinter import filedialog #file 불러오기 위해서
from PIL import Image #이미지 합치기
2. frame 내용
# 파일 프레임 (파일 추가, 선택 삭제 영역)
file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # pad 간격 띄움.
# 파일 추가, 선택삭제 버튼 만듦 (커맨드는 우선 pass로 해둠)
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은 파일을 추가하여 리스트에 시각적으로 보이게 할 것임
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)
# 저장 경로를 지정하면 텍스트엔트리에 경로가 뜸
txt_dest_path = Entry(path_frame) # Entry는 "0" 부터 END 까지, 텍스트 위젯이면 "1.0" 부터 END 까지
txt_dest_path.pack(side="left", fill="x", expand=True, padx=3, pady=3, ipady=5) #ipady 높이 변경
btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=3, pady=3)
# 옵션 프레임 (각종 옵션들이 들어갈 프레임)
frame_option = LabelFrame(root, text="옵션")
frame_option.pack()
# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lb1_wdith = Label(frame_option, text="가로넓이", width=8)
lb1_wdith.pack(side="left", padx=3, pady=3)
# 가로 넓이 콤보 박스
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=3, pady=3)
# 2. 간격 옵션
# 간격 옵션 레이블
lb1_space = Label(frame_option, text="간격", width=8)
lb1_space.pack(side="left", padx=3, pady=3)
# 간격 옵션 콤보 박스
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=3, pady=3)
# 3. 파일 포맷 옵션
# 파일 포맷 레이블
lb1_format = Label(frame_option, text="파일포맷", width=8)
lb1_format.pack(side="left", padx=3, pady=3)
# 파일 콤보 박스
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=3, pady=3)
각 옵션들의 콤보박스만 만들었을뿐 기능은 아직.
# 진행 상황 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=3, pady=3)
btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=12, command=start)
btn_start.pack(side="right", padx=3, pady=3)
3. 각 Button들과 option들의 details Function seting
# 파일 추가
# filetypes의 type을 정해줌 (PNG 파일, 모든파일)
def add_file():
files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요.", \
filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*")), \
initialdir=r"C:/바탕 화면")
# 사용자가 선택한 파일 목록을 list_file에 입력
for file in files:
list_file.insert(END, file)
# 선택한 파일 삭제
def del_file():
# print(list_file.curselection())
for index in reversed(list_file.curselection()):
list_file.delete(index)
# 이미지 통합 옵션
def merge_image():
try:
# 가로 넓이
img_width = cmb_width.get()
if img_width == "원본유지":
img_width = -1 # -1 일 때는 원본 기준으로
else:
img_width = int(img_width)
# 간격 "==" or "=" 부호 중요
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 값을 받아와서 소문자로 변경
##############################################################
images = [Image.open(x) for x in list_file.get(0, END)]
# 이미지 사이즈 리스트에 넣어서 하나씩 처리
image_sizes = [] # [(width1, height1), (width2, height2), ...]
if img_width > -1:
# width 값 변경
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]
widths, heights = zip(*(image_sizes))
# 최대 넓이, 전체 높이 구해옴
max_width, total_height = max(widths), sum(heights)
# 스케치북 준비
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 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) # height 값 + 사용자가 지정한 간격
# Progressbar 실제 진행도
progress = (idx + 1) / len(images) * 100 # 실제 진행도 정보를 계산
p_var.set(progress)
progress_bar.update()
# 포맷 옵션 처리
file_name = "PGR_PHOTO." + img_format
dest_path = os.path.join(txt_dest_path.get(), file_name)
result_img.save(dest_path)
msgbox.showinfo("알림", "작업이 완료되었습니다.")
except Exception as err: # 예외처리
msgbox.showerror("에러", err)
# 저장 경로 (폴더)
def browse_dest_path():
folder_selected = filedialog.askdirectory()
if folder_selected == '': # 사용자가 취소를 눌를 때
return
# print(folder_selected)
txt_dest_path.delete(0, END)
txt_dest_path.insert(0, folder_selected)
# ---------------------------------------------------------------------------------
# 시작
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
4. 전체적인 명령어와 완성된 GUI 프로그램
import os
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import *
from tkinter import filedialog
from PIL import Image
root = Tk()
root.title("PGR 이미지 합치기")
root.resizable(False, False) # 창크기 고정
# 파일 추가
def add_file():
files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요.", \
filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*")), \
initialdir=r"C:/바탕 화면")
# 사용자가 선택한 파일 목록
for file in files:
list_file.insert(END, file)
def del_file():
# print(list_file.curselection())
for index in reversed(list_file.curselection()):
list_file.delete(index)
# 저장 경로 (폴더)
def browse_dest_path():
folder_selected = filedialog.askdirectory()
if folder_selected == '': # 사용자가 취소를 눌를 때
return
# print(folder_selected)
txt_dest_path.delete(0, END)
txt_dest_path.insert(0, folder_selected)
# 이미지 통합"
def merge_image():
try:
# 가로 넓이
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 값을 받아와서 소문자로 변경
##############################################################
images = [Image.open(x) for x in list_file.get(0, END)]
# 이미지 사이즈 리스트에 넣어서 하나씩 처리
image_sizes = [] # [(width1, height1), (width2, height2), ...]
if img_width > -1:
# width 값 변경
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]
widths, heights = zip(*(image_sizes))
# 최대 넓이, 전체 높이 구해옴
max_width, total_height = max(widths), sum(heights)
# 스케치북 준비
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 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) # height 값 + 사용자가 지정한 간격
# Progressbar 실제 진행도
progress = (idx + 1) / len(images) * 100 # 실제 진행도 정보를 계산
p_var.set(progress)
progress_bar.update()
# 포맷 옵션 처리
file_name = "PGR_PHOTO." + img_format
dest_path = os.path.join(txt_dest_path.get(), file_name)
result_img.save(dest_path)
msgbox.showinfo("알림", "작업이 완료되었습니다.")
except Exception as err: # 예외처리
msgbox.showerror("에러", err)
# 시작
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) # pad 간격 띄움.
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)
txt_dest_path = Entry(path_frame) # Entry는 "0" 부터 END 까지, 텍스트 위젯이면 "1.0" 부터 END 까지
txt_dest_path.pack(side="left", fill="x", expand=True, padx=3, pady=3, ipady=5) #ipady 높이 변경
btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=3, pady=3)
# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack()
# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lb1_wdith = Label(frame_option, text="가로넓이", width=8)
lb1_wdith.pack(side="left", padx=3, pady=3)
# 가로 넓이 콤보 박스
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=3, pady=3)
# 2. 간격 옵션
# 간격 옵션 레이블
lb1_space = Label(frame_option, text="간격", width=8)
lb1_space.pack(side="left", padx=3, pady=3)
# 간격 옵션 콤보 박스
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=3, pady=3)
# 3. 파일 포맷 옵션
# 파일 포맷 레이블
lb1_format = Label(frame_option, text="파일포맷", width=8)
lb1_format.pack(side="left", padx=3, pady=3)
# 파일 콤보 박스
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=3, pady=3)
# 진행 상황 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=3, pady=3)
btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=12, command=start)
btn_start.pack(side="right", padx=3, pady=3)
root.mainloop()
이 외 Function들도 모두 정상 작동함.
첫 번째 프로젝트를 끝내고 느낀 점:
tkinter GUI를 독학하고 처음으로 만든 프로그램이자 첫 프로젝트.
전체적인 틀은 독학할 때 시청한 영상의 틀을 사용하였으나 틀 안의 Function들은 직접 생각하여 만듦.
총 걸린 시간은 쉬는시간 제외 7시간 다 완성되고 이미지가 합쳐지는 모습을 보니 잊지못할 성취감을 추가로 느낌.
이상으로
First project: PGR Image combine program을 마침.
'PROJECT > Python_Project' 카테고리의 다른 글
Third project: Up down game (0) | 2022.08.06 |
---|---|
Second project: MSW_Pyramid_Stackup (2) | 2022.08.02 |