# 여러 이미지를 합치기
# [사용자 시나리오]
# 1. 사용자는 합치려는 이미지를 1개 이상 선택한다.
# 2. 합쳐진 이미지가 저장될 경로를 지정한다.
# 3. 가로넓이, 간격, 포맷 옵션을 지정한다.
# 4. 시작 버튼을 통해 이미지를 합친다.
# 5. 닫기 버튼을 통해 프로그램을 종료한다.
# [기능 명세]
# 1. 파일추가 : 리스트 박스에 파일 추가
# 2. 선택삭제 : 리스트 박스에서 선택된 항복 삭제
# 3. 찾아보기 : 저장 폴더를 선택하면 텍스트 위젯에 입력
# 4. 가로넓이 : 이미지 넓이 지정 (원본유지, 1024, 880, 640)
# 5. 간격 : 이미지 간의 간격 지정 (없음, 좁게, 보통, 넓게)
# 6. 포맷 : 저장 이미지 포맷 지정 (PNG, JPG, BMP)
# 7. 시작 : 이미지 합치기 작업 실행
# 8. 진행상황 : 현재 진행중인 파일 순서에 맞게 반영
# 9. 닫기 : 프로그램 종료
import os
import tkinter.ttk as tk
import tkinter.messagebox as msbx
from tkinter import *
from tkinter import filedialog # 위에 *에 의해 호출도 가능하지만 filedialog는 서브 모듈이기 때문에 별도로 지정을 해줘야 한다.
from PIL import Image
root = Tk()
root.title("이미지 합치기")
# root.geometry("640x480") # 가로 x 세로 + x좌표 + y좌표 (창 크기, xy좌표 지정하지 않으면 가변되게 나타남)
root.resizable(False, False)
def file_add():
# r 의 의미는 "" 안에 탈출 문자 상관없이 그대로 이용하겠다는 의미이다.
files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요.", \
filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*")), initialdir=r"C:\Users\JHROOT\Documents\아이리치 고시텔" )
# 사용자가 선택한 파일 목록 출력
for i in files:
lstBox.insert(END, i)
# prograss.setvar
# start(10) # 10ms 마다 움직인다.
# print(i)
def select_delete():
# 앞에것부터 삭제하면 인덱스가 변할 수 있으므로 뒤에꺼 부터 삭제한다. reversed는 변수의 뒤에서 부터라는 내부함수이다.
# 예를 들어서
# lst = [1, 2, 3, 4, 5]
# print(lst) 를 하면 순서대로 출력된다.
# lst.reverse() 를 하고 출력을 하면 순서가 거꾸로 출력이 된다.
# lst3 = reversed(lst)
# print(list(lst3)) 를 하면 순서가 거꾸로 출력이 된다. 그러나 lst는 순서가 바뀌지 않는다.
for deleteIdx in reversed(lstBox.curselection()):
lstBox.delete(deleteIdx)
def search_file():
folder_select = filedialog.askdirectory()
if folder_select == '': # 사용자가 취소를 눌렀을때 작동
return
pathText.delete(0, END)
pathText.insert(0, folder_select)
def merge_image():
try:
# 가로넓이 옵션 적용
img_width = widthCombobox.get()
if img_width == "원본유지":
img_width = -1
else:
img_width = int(img_width)
img_between = betweenCombobox.get()
if img_between == "좁게":
img_between = 30
elif img_between == "보통":
img_between = 60
elif img_between == "넓게":
img_between = 90
else:
img_between = 0
img_format = formatCombobox.get().lower() # 소문자로 변경
# print(lstBox.get(0, END))
# lstBox의 모든 Image 파일 값을 images 에 넣겠다는 의미
images = [Image.open(x) for x in lstBox.get(0, END)]
# 이미지 사이즈 리스트에 넣어서 하나씩 처리
images_sizes = [] # (widht1, height1), (widht2, height2) 로 만들어서 리스트에 넣는다.
if img_width > -1:
# 이미지의 width를 변동 했을 때 height 구하는 계산
# (원본 width : 원본 height) = (변경 width : 변경 height)
# 원본 height 곱하기 변경 width = 원본 width 곱하기 변경 height 가 나와야한다.
# 100 : 60 = 80 : ?
# x : y = x' : y'
# xy' = x'y
# y' = x'y / x => 이 식을 적용하면 된다.
# x = width = size[0]
# y = height = size[1]
# x' = img_width
# y' = img_width * size[1] / size[0]
images_sizes = [(int(img_width), (int(img_width) * x.size[1]) / x.size[0]) for x in images]
else:
images_sizes = [(x.size[0], x.size[1]) for x in images]
# size -> size[0] : width, size[1] : height 의 값을 갖는다.
# width = [x.size[0] for x in images] # images의 size[0] 즉 width 값을 모두 갖는다.
# height = [x.size[1] for x in images] # images의 size[1] 즉 height 값을 모두 갖는다.
# width, height = zip(*(x.size for x in images)) # 위 두문장과 같은 의미이다. (unzip 기능)
width, height = zip(*(images_sizes))
# print("width : {0}, height : {1}".format(width, height))
# 최대넓이, 전체 높이 구하기
max_width, total_height = max(width), sum(height)
# print("max width : {0}, total height : {1}".format(max_width, total_height))
if img_between > 0: # 이미지 간격
img_between += (img_between * (len(images)-1))
# 스케치북 준비 (배경은 흰색)
result_image = Image.new("RGB", (max_width, total_height), (255, 255, 255))
y_offset = 0 # Y 위치 정보
# 이미지들 아래로 붙여 넣기
# for img in images:
# result_image.paste(img, (0, y_offset)) # img 즉, images의 값을 새로운 스케치북에 붙여넣기를 한다.
# y_offset += img.size[1] # height 값 만큼 더해줌
for idx, img in enumerate(images): # enumerate에 의해 images의 인덱스와 이미지 값을 갖고 온다.
# width 가 원본이 아닌 경우에는 이미지 크기 조절
if img_width > -1:
img = img.resize(images_sizes[idx])
result_image.paste(img, (0, y_offset))
y_offset += (img.size[1] + img_between) # height 값 + 사용자가 지정한 간격
# 프로그래스바에 몇 %의 값을 채울것인지 설정한다.
# idx는 0부터 시작하기 때문에 + 1을 해준다.
progressVar = (idx + 1) / len(images) * 100 # 100은 퍼센트 표시를 위함
pVar.set(float(progressVar))
prograss.update()
# 포맷 옵션 처리
file_name = "save." + img_format
# save.jpg 화일로 저장한다.
dest_path = os.path.join(pathText.get(), file_name)
result_image.save(dest_path)
msbx.showinfo("알림", "작업이 완료되었습니다.")
except Exception as err:
msbx.showerror("에러", err)
def start():
# 선택한 화일이 존재하는 지 검증
if lstBox.size() == 0:
msbx.showwarning("경고", "선택한 이미지가 없습니다.")
return
# 저장 경로가 선택 되어져 있는지 검증
if len(pathText.get()) == 0:
msbx.showwarning("경고", "저장 경로가 설정되지 않았습니다.")
searchButton.focus()
return
merge_image()
# print("가로옵션 : {0}, 간격 : {1}, 포맷 : {2}".format(widthCombobox.get(), betweenCombobox.get(), formatCombobox.get()))
#-----------------------------
# 버튼 frame
mainFrame = Frame(root)
mainFrame.pack(fill="x", padx=5, pady=5)
# 파일추가 버튼
btnFileAdd = Button(mainFrame, width=20, height=1, text="파일추가", command=file_add)
btnFileAdd.pack(side="left")
#선택삭제 버튼
btnSelectDelete = Button(mainFrame, width=20, height=1, text="선택삭제", command=select_delete)
btnSelectDelete.pack(side="right")
#-----------------------------
# 리스트 프레임
lstFrame = Frame(root)
lstFrame.pack(fill="both", padx=5, pady=5)
# 리스트박스에 스크롤바 정의
lstScrollbar = Scrollbar(lstFrame)
lstScrollbar.pack(side="right", fill="y")
#리스트 박스
lstBox = Listbox(lstFrame, selectmode="extended", height=15, yscrollcommand=lstScrollbar)
lstBox.pack(side="left", fill="both", expand=True, ipady=4) # ipady 높이변경
lstScrollbar.config(command=lstBox.yview)
#-----------------------------
# 저장경로 프레임
# padx : 너비 간격, pady : 높이 간격
# ipady : 프레임의 너비 설정, ipady : 프레임의 높이 설정
# fill : 채우기 (Align)
savePathFrame = LabelFrame(root, text="저장경로")
savePathFrame.pack(fill="x", padx=5, pady=5, ipady=5)
# text
pathText = Entry(savePathFrame)
pathText.insert(0, r"C:\Users\JHROOT\Documents\아이리치 고시텔")
pathText.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4)
# 찾기
searchButton = Button(savePathFrame, text="찾아보기", width=10, command=search_file)
searchButton.pack(side="right", padx=5, pady=5)
#-----------------------------
# 옵션 프레임
optionFrame = LabelFrame(root, text="옵션")
optionFrame.pack(fill="x", padx=5, pady=5, ipady=5)
# 가로넓이 옵션지정 (레이블, 콤보박스)
widthLabel = Label(optionFrame, text="가로넓이", width=8).pack(side="left", pady=5)
widthValue = ["원본유지", "1024", "880", "640"]
widthCombobox = tk.Combobox(optionFrame, height=5, state="readonly", values=widthValue)
widthCombobox.current(0)
widthCombobox.pack(side="left", pady=5)
# 간격 옵션
betweenLabel = Label(optionFrame, text="간격", width=8).pack(side="left")
betweenValue = ["없음", "좁게", "보통", "넓게"]
betweenCombobox = tk.Combobox(optionFrame, height=5, state="readonly", values=betweenValue)
betweenCombobox.current(0)
betweenCombobox.pack(side="left")
# 파일 포맷 옵션
formatLabel = Label(optionFrame, text="파일포맷", width=8).pack(side="left")
formatValue = ["PNG", "JPG", "BMP"]
formatCombobox = tk.Combobox(optionFrame, height=5, state="readonly", values=formatValue)
formatCombobox.current(0)
formatCombobox.pack(side="left", padx=5)
#-----------------------------
# 진행상황 프레임
prograssFrame = Frame(root)
prograssFrame.pack(fill="x", padx=5, pady=3)
pVar = DoubleVar()
prograss = tk.Progressbar(prograssFrame, maximum=100, variable=pVar, mode="determinate")
prograss.pack(fill="x", padx=5, pady=3)
#-----------------------------
# 실행 프레임
exeFrame = Frame(root)
exeFrame.pack(fill="x", padx=5, pady=5)
#종료 버튼
btnEnd = Button(exeFrame, width=12, height=1, text="종료", command=root.quit)
btnEnd.pack(side="right")
# 시작 버튼
btnStart = Button(exeFrame, width=12, height=1, text="시작", command=start)
btnStart.pack(side="right", padx=5)
root.mainloop()
'프로그램 > 파이썬(Python)' 카테고리의 다른 글
디장고 프로젝트 만들기와 흐름 (Make the Django project and Flow) (0) | 2021.01.14 |
---|---|
디장고 환경 설정 (Setting Django environment) (0) | 2021.01.14 |
파이썬 GUI 샘플 소스 (Python GUI sample source) (0) | 2021.01.14 |
Hello Python 파이썬 기본 명령어 소스 (Hello python) (0) | 2021.01.14 |
파이썬(Python) 시작하기 (0) | 2021.01.14 |