프로그램 사용 영상
목차
- 프로젝트 설명
- 개발 환경
- 주요 기능
- 주요 메서드
- 프로젝트 코드 및 주석 설명
1. 프로젝트 설명
이미지 파일들을 사용자 선택 옵션에 따라 하나의 이미지로 병합하는 프로그램
2. 개발 환경
Anaconda, Python 3.12.4, Jupyter Notebook
2. 주요 기능
- 옵션
- 너비 설정(원본 유지, 1024, 800, 640): 병합할 이미지의 너비를 설정
- 간격 설정(좁게, 보통, 넓게): 병합할 이미지들간의 간격 설정
- 포맷(png, jpg): 저장할 이미지 포맷 설정
- 정렬(수직, 수평): 병합할 이미지의 방향 설정
- 파일관리
- 파일 추가 기능
- 파일 삭제 기능
- 파일 순서 변경 기능
- 파일 리스트 확인 기능
- 미리보기: 파일 리스트에 선택된 이미지를 보여주는 기능
- 저장경로 선택 기능
- 찾아보기: 다이얼로그로 저장 경로 선택
- 이미지 병합 진행상태를 프로그레스바로 볼 수 있음
- 시작 및 닫기: 이미지 병합을 실행 및 프로그램 종료
- 저장 결과 화면: 병합된 이미지를 저장결과 화면에 보여줌
- 줌: 병합된 결과 이미지를 확대/축소 가능
3. 주요 메서드
__init__(root)
: GUI 초기화 및 기본 설정- on_drop(): 드래그 앤 드롭, 파일을 마우스로 옮겨서 리스트에 추가
- save_config(): 현재 사용자 설정을 config.json 파일에 저장
- load_config(): 저장된 사용자 설정 불러옴
- create_*_frame(): GUI 구성(옵션, 파일 관리, 리스트 박스 img 미리보기, 저장 경로, 진행 상황, 실행, 저장결과 보기)
- add_file(): 파일 추가 다이얼로그를 열어 선택된 이미지를 리스트에 추가
- del_file(): 선택된 파일 삭제
- move_up()/move_down(): 리스트에서 선택된 파일 순서 변경
- browse_dest_path(): 저장 경로 설정
- start(): 파일유효성 검사 및 병합 작업 실행
- merge_image(): 이미지 병합 실행
- update_progress_bar(): 진행바 업데이트
- create_result_image(): 병합된 이미지 결과 생성
- loading_window = Toplevel(self.root): 이미지 병합 실행시 이미지 저장을 대기하는 모달창 생성
- update_preview(): 선택된 이미지, 결과 이미지 미리보기 뷰에 업데이트
- zoom(): 저장 결과 미리보기 확대/축소 기능
4. 사용된 라이브러리
- tkinter: GUI 생성
- tkinterdnd2: 드래그 앤 드롭 지원
pip install tkinterdnd2==0.4.2
- pillow(PIL): 이미지 처리
pip install pillow==10.3.0
- json
import os
from tkinter import *
from tkinter import filedialog, messagebox, ttk
from tkinterdnd2 import TkinterDnD, DND_FILES
from PIL import Image, ImageTk
import json
class ImageMergerApp:
def __init__(self, root):
self.root = root # main gui 생성
self.root.title("YongSeokHa Project - Image Merge Program") # gui 제목 설정
self.window_width, self.window_height = 1500, 900 # gui 가로, 세로 길이
self.root.geometry(f"{self.window_width}x{self.window_height}") # gui 크기 설정
# 드래그 앤 드롭 초기화 (파일을 드래그해서 App 으로 옮기는 기능)
self.root.drop_target_register(DND_FILES) # 파일 드래그 앤 드롭을 위한 설정
self.root.dnd_bind('<<Drop>>', self.on_drop) # 파일이 드롭될 때 호출될 함수 지정
self.list_image_preview = None # 리스트 박스에 img파일 미리보기 저장 변수 초기화
self.result_image_preview = None # 저장 결과 view 프레임에 img 변수 초기화
#self.last_opened_dir = os.path.expanduser("~") # 기본 열기 경로를 사용자 홈 디렉토리로 설정
self.last_opened_dir = os.path.expanduser("../Media/images/") # 파일추가 기본 열기 디렉토리 경로 설정
self.result_opened_dir = os.path.expanduser("../Media/images/") # 저장결과 기본 열기 디렉토리 경로 설정
self.config_file = "config.json" # 사용자 설정 저장 파일
# 왼쪽 프레임, 오른쪽 프레임 분할
self.left_frame = Frame(self.root)
self.right_frame = Frame(self.root)
self.left_frame.pack(side="left", fill="both", expand=True)
self.right_frame.pack(side="left", fill="both", expand=True)
# 내부 위젯 크기가 프레임 크기를 변경하지 않도록 설정
self.left_frame.pack_propagate(False)
self.right_frame.pack_propagate(False)
# 각 프레임 가로 길이 설정
self.left_frame.config(width=self.window_width / 2) # 왼쪽 프레임 너비 설정
self.right_frame.config(width=self.window_width / 2) # 오른쪽 프레임 너비 설정
# 왼쪽 프레임: 옵션, 파일 관리, 리스트 박스 img 미리보기, 저장 경로, 진행 상황, 실행 버튼
self.create_option_frame()
self.create_file_frame()
self.create_preview_frame()
self.create_path_frame()
self.create_progress_frame()
self.create_run_frame()
# 오른쪽 프레임: 저장 결과 img 보기
self.create_result_preview_frame()
# 사용자 설정 로드
self.load_config()
def on_drop(self, event):
"""드래그 앤 드롭 이벤트 처리"""
files = self.root.tk.splitlist(event.data) # 드롭된 파일 목록 가져오기
for file in files:
if file.lower().endswith(('.png', '.jpg', '.jpeg')): # 이미지 파일만 추가
self.list_file.insert(END, file) # 파일 목록에 추가
def load_config(self):
"""사용자 설정 값 불러오기"""
with open(self.config_file, 'r') as f:
config = json.load(f) # json 파일 읽기
self.cmb_width.set(config.get("width", "원본유지")) # 넓이
self.cmb_space.set(config.get("space", "없음")) # 간격
self.cmb_format.set(config.get("format", "PNG")) # img 포맷
self.cmb_align.set(config.get("align", "수직")) # 정렬 방식
def save_config(self):
"""사용자 설정 값 저장"""
config = {
"width": self.cmb_width.get(), # 가로넓이
"space": self.cmb_space.get(), # 간격
"format": self.cmb_format.get(), # img 포맷
"align": self.cmb_align.get() # 정렬 방식
}
with open(self.config_file, 'w') as f:
json.dump(config, f) # 설정 파일로 저장
def create_file_frame(self):
"""파일 추가/삭제 및 순서 변경 프레임 생성"""
frame = LabelFrame(self.left_frame, text="파일 관리") # 파일 관리 프레임
frame.pack(fill="both", padx=5, pady=5, expand=True)
list_frame = Frame(frame) # 파일 목록 프레임
list_frame.pack(side="left", fill="both", expand=True, padx=5)
scrollbar = Scrollbar(list_frame) # 스크롤바 생성
scrollbar.pack(side="right", fill="y")
self.list_file = Listbox(
list_frame, selectmode="extended", height=10, yscrollcommand=scrollbar.set # 파일 목록 리스트박스
)
self.list_file.pack(side="left", fill="both", expand=True)
self.list_file.bind("<<ListboxSelect>>", lambda event: self.update_preview("listbox_preview")) # 항목 선택 시 미리보기 업데이트
scrollbar.config(command=self.list_file.yview) # 스크롤바와 리스트박스 연결
button_frame = Frame(frame) # 버튼 프레임
button_frame.pack(side="right", fill="y", padx=5)
Button(button_frame, text="파일 추가", command=self.add_file).pack(fill="x", pady=2) # 파일 추가 버튼
Button(button_frame, text="선택 삭제", command=self.del_file).pack(fill="x", pady=2) # 선택 삭제 버튼
Button(button_frame, text="↑", command=self.move_up, width=3, height=1).pack(padx=(0, 70), pady=(80, 10)) # 위로 이동 버튼
Button(button_frame, text="↓", command=self.move_down, width=3, height=1).pack(padx=(0, 70)) # 아래로 이동 버튼
def create_preview_frame(self):
"""미리보기 프레임 생성"""
frame = LabelFrame(self.left_frame, text="미리보기") # 미리보기 프레임
frame.pack(fill="both", padx=5, pady=5, expand=True)
self.lbl_listfile_preview = Label(frame, text="이미지를 선택하세요", anchor="center") # 미리보기 텍스트 라벨
self.lbl_listfile_preview.pack(fill="both", expand=True, padx=5, pady=5)
def create_result_preview_frame(self):
"""저장 결과 미리보기 프레임 생성"""
frame = LabelFrame(self.right_frame, text="저장 결과 화면", bg="lightgray") # 결과 미리보기 프레임
frame.pack(side='top', fill="both", padx=5, pady=5, expand=True)
# Canvas 생성 및 추가
self.canvas = Canvas(frame, bg="white") # 캔버스 생성
self.canvas.pack(side="top", fill="both", expand=True)
# 스크롤바 추가
self.x_scroll = Scrollbar(self.canvas, orient=HORIZONTAL, command=self.canvas.xview) # 가로 스크롤바
self.x_scroll.pack(side="bottom", fill="x")
self.y_scroll = Scrollbar(self.canvas, orient=VERTICAL, command=self.canvas.yview) # 세로 스크롤바
self.y_scroll.pack(side="right", fill="y")
# Canvas와 스크롤바 연결
self.canvas.config(xscrollcommand=self.x_scroll.set, yscrollcommand=self.y_scroll.set)
# 확대/축소 슬라이더 추가
self.scale_preview = Scale(frame, from_=0, to=400, orient="horizontal", label="Zoom (%)", command=self.zoom) # 슬라이더 생성
self.scale_preview.set(100) # 기본값 100%
self.scale_preview.pack(side="top", fill="x", padx=5, pady=5)
def create_option_frame(self):
"""옵션 선택 프레임 생성"""
frame = LabelFrame(self.left_frame, text="옵션") # 옵션 설정 프레임
frame.pack(fill="x", padx=5, pady=5)
self.label_width = Label(frame, text="가로넓이", width=10) # 넓이 라벨
self.label_width.pack(side="left", padx=5, pady=5)
self.cmb_width = ttk.Combobox(frame, state="readonly", values=["원본유지", "1024", "800", "640"], width=10) # 넓이 옵션 콤보박스 생성
self.cmb_width.current(0) # 기본값 설정
self.cmb_width.pack(side="left", padx=5, pady=5)
Label(frame, text="간격", width=10).pack(side="left", padx=5, pady=5) # 간격 라벨
self.cmb_space = ttk.Combobox(frame, state="readonly", values=["없음", "좁게", "보통", "넓게"], width=10) # 간격 옵션 콤보박스 생성
self.cmb_space.current(0)
self.cmb_space.pack(side="left", padx=5, pady=5)
Label(frame, text="포맷", width=10).pack(side="left", padx=5, pady=5) # 포맷 라벨
self.cmb_format = ttk.Combobox(frame, state="readonly", values=["PNG", "JPG"], width=10) # 포맷 옵션 콤보박스 생성
self.cmb_format.current(0)
self.cmb_format.pack(side="left", padx=5, pady=5)
Label(frame, text="정렬", width=8).pack(side="left", padx=5, pady=5) # 정렬 라벨 # 정렬 옵션 리스트
self.cmb_align = ttk.Combobox(frame, state="readonly", values=["수직", "수평"], width=10) # 콤보박스 생성
self.cmb_align.current(0)
self.cmb_align.pack(side="left", padx=5, pady=5)
self.update_label() # 정렬 옵션이 수직/수평 에 따라 넓이 라벨 가로/세로 변경
self.cmb_align.bind("<<ComboboxSelected>>", self.update_label) # 콤보 박스 선택에 따라 update_label 합수 호출
def create_path_frame(self):
"""저장 경로 선택 프레임 생성"""
frame = LabelFrame(self.left_frame, text="저장 경로") # 저장 경로 프레임
frame.pack(fill="x", padx=5, pady=5)
self.txt_dest_path = Entry(frame) # 경로 입력 필드 생성
self.txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5)
Button(frame, text="찾아보기", command=self.browse_dest_path).pack(side="right", padx=5, pady=5) # 찾아보기 버튼
def create_progress_frame(self):
"""진행 상황 표시 프레임 생성"""
frame = LabelFrame(self.left_frame, text="진행 상황") # 진행 상황 프레임
frame.pack(fill="x", padx=5, pady=5)
self.p_var = DoubleVar() # 진행 상황 변수 생성
self.progress_bar = ttk.Progressbar(frame, maximum=100, variable=self.p_var) # 진행바 생성
self.progress_bar.pack(fill="x", padx=5, pady=5)
self.progress_label = Label(frame, text="") # 진행률 표시
self.progress_label.pack(pady=10)
def create_run_frame(self):
"""실행 버튼 프레임 생성"""
frame = Frame(self.left_frame) # 실행 버튼 프레임
frame.pack(fill="x", padx=5, pady=5)
Button(frame, text="닫기", command=self.root.destroy).pack(side="right", padx=5, pady=5) # 닫기 버튼
Button(frame, text="시작", command=self.start).pack(side="right", padx=5, pady=5) # 시작 버튼
def update_label(self, event=None):
"""정렬 옵션 선택에 따라 가로넓이/세로넓이 변경"""
# 콤보박스에서 선택된 값 가져오기
selected_align = self.cmb_align.get()
# 선택 값에 따라 라벨 텍스트 변경
if selected_align == "수평":
self.label_width.config(text="세로넓이")
else:
self.label_width.config(text="가로넓이")
def add_file(self):
"""파일 추가"""
# 파일 추가하기 위한 다이얼로그 열기
files = filedialog.askopenfilenames(
title="이미지 파일을 선택하세요", # 다이얼로그 제목
filetypes=(("PNG 파일", "*.png"), ("JPG 파일", "*.jpg"), ("모든 파일", "*.*")), # 파일 유형 필터
initialdir=self.last_opened_dir, # 마지막으로 열었던 디렉토리로 시작
)
if files: # 파일이 선택되었으면
self.last_opened_dir = os.path.dirname(files[0]) # 마지막 열었던 디렉토리 갱신
for file in files: # 선택된 각 파일을 목록에 추가
self.list_file.insert(END, file)
def del_file(self):
"""파일 삭제"""
# 선택된 파일들을 삭제
for index in reversed(self.list_file.curselection()): # 선택된 항목을 역순으로 삭제
self.list_file.delete(index)
self.update_preview() # 미리보기 업데이트
def move_up(self):
"""파일 순서 위로 이동"""
# 선택된 파일들을 위로 이동
indices = self.list_file.curselection() # 선택된 인덱스 목록
for i in indices:
if i > 0: # 첫 번째 항목이 아니면
text = self.list_file.get(i) # 항목 내용 가져오기
self.list_file.delete(i) # 항목 삭제
self.list_file.insert(i - 1, text) # 위로 이동하여 재삽입
self.list_file.selection_set(i - 1) # 새 위치를 선택 상태로 설정
self.update_preview() # 미리보기 업데이트
def move_down(self):
"""파일 순서 아래로 이동"""
# 선택된 파일들을 아래로 이동
indices = self.list_file.curselection() # 선택된 인덱스 목록
for i in reversed(indices): # 역순으로 이동
if i < self.list_file.size() - 1: # 마지막 항목이 아니면
text = self.list_file.get(i) # 항목 내용 가져오기
self.list_file.delete(i) # 항목 삭제
self.list_file.insert(i + 1, text) # 아래로 이동하여 재삽입
self.list_file.selection_set(i + 1) # 새 위치를 선택 상태로 설정
self.update_preview() # 미리보기 업데이트
def browse_dest_path(self):
"""저장 경로 선택"""
# 저장 경로를 선택하는 다이얼로그 열기
folder_selected = filedialog.askdirectory(
title="저장 경로를 선택하세요", # 다이얼로그 제목
initialdir=self.result_opened_dir # 마지막으로 열었던 디렉토리로 시작
)
if folder_selected: # 폴더가 선택되었으면
self.txt_dest_path.delete(0, END) # 경로 입력란 초기화
self.txt_dest_path.insert(0, folder_selected) # 경로 입력란에 선택된 폴더 경로 삽입
self.result_opened_dir = folder_selected # 마지막 열었던 디렉토리 갱신
def check_file_and_path(self):
"""유효성 검사"""
if not self.list_file.size(): # 파일 목록이 비어 있으면
messagebox.showwarning("경고", "이미지 파일을 추가하세요")
return False
if not self.txt_dest_path.get(): # 저장 경로가 비어 있으면
messagebox.showwarning("경고", "저장 경로를 선택하세요")
return False
return True
def start(self):
"""유효성 검사 및 이미지 병합 실행"""
self.save_config()
if not self.check_file_and_path(): # 파일과 경로 확인
return
self.merge_image() # 이미지 병합 실행
self.scale_preview.set(100) # 줌 값 초기화
def merge_image(self):
"""이미지 합치기"""
try:
# 사용자 설정 값 가져오기
img_width = -1 if self.cmb_width.get() == "원본유지" else int(self.cmb_width.get()) # 넓이 설정
img_space = {"없음": 0, "좁게": 30, "보통": 60, "넓게": 90}[self.cmb_space.get()] # 간격 설정
img_format = self.cmb_format.get().lower() # 포맷 설정 (소문자)
align = self.cmb_align.get() # "정렬 설정
# 이미지 설정
images = [Image.open(x) for x in self.list_file.get(0, END)] # 리스트 박스 이미지 목록 가져오기
resized_images = [self.resize_and_center_image(img, img_width, align) for img in images] # 이미지 크기 조정
# 파일 저장
self.result_img_file_path = filedialog.asksaveasfilename(
title="파일 저장", # 다이얼로그 제목
defaultextension=f".{img_format}", # 기본 확장자 설정
filetypes=( # 파일 유형 설정
("모든 파일", "*.*"),
("PNG 파일", "*.png"),
("JPG 파일", "*.jpg"),
),
)
# 이미지 결과
result_img = self.create_result_image(resized_images, align, img_space) # 이미지 병합 처리
# 로딩 창 설정 (모달로 동작)
loading_window = Toplevel(self.root) # 로딩 창 생성
loading_window.title("저장 대기중") # 로딩 창 제목
Label(loading_window, text="파일 이름을 설정하고 저장이 완료되면 자동으로 닫힙니다. \n기다려 주세요..!").pack(padx=20, pady=20) # 라벨 표시
# 로딩 창을 모달로 설정
loading_window.transient(self.root) # loading_window 창은 root 창 위에 고정
loading_window.grab_set() # loading_window가 닫히기 전까지는 root 및 다른 창과의 상호작용이 제한
loading_window.focus_set() # 로딩 창에 포커스 설정
loading_window.update() # 로딩 창이 생성되자마자 즉시 렌더링되도록 보장
# update()가 없으면 로딩 창이 잠깐 멈추거나 렌더링이 지연될 수 있음
if self.result_img_file_path: # 파일 경로 설정 확인
# 결과 저장
result_img.save(self.result_img_file_path)
# img 결과 저장 후 미리보기 view 업데이트
self.update_preview(place='result_preview')
# 로딩 창 닫기
loading_window.destroy()
# 프로그레스바, 결과 img 저장을 기다리는 시간 있어서 한칸 남겨 놓고 저장 되면 100% 채움
self.update_progress_bar(len(resized_images), len(resized_images))
messagebox.showinfo("알림", "작업이 완료되었습니다!") # 완료 메시지 표시
else:
loading_window.destroy() # 로딩 창 닫기
messagebox.showinfo("알림", "저장이 취소되었습니다.") # 취소 메시지 표시
except Exception as e:
messagebox.showerror("에러", f"이미지 병합 중 오류가 발생했습니다.\n{e}") # 에러 메시지 표시
def resize_and_center_image(self, img, img_width, align):
"""이미지를 비율 유지하며 크기 조정하고 흰색 배경에 배치."""
try:
if img_width > -1: # 너비가 지정된 경우
# 비율에 맞춰 크기 변경
if align == "수직":
new_size = (int(img_width), int(img_width * img.size[1] / img.size[0]))
elif align == "수평":
new_size = (int(img_width * img.size[0] / img.size[1]), int(img_width))
else: # 원본유지
new_size = (int(img.size[0]), int(img.size[1]))
resized_img = img.resize(new_size, Image.Resampling.LANCZOS) # img 크기 조정
canvas = Image.new("RGB", resized_img.size, (255, 255, 255)) # 흰색 배경의 새로운 캔버스 생성
canvas.paste(resized_img) # 이미지를 캔버스에 붙여넣기
except Exception as e:
raise ValueError(f"resize_and_center_image - 이미지 조정 과정 에러 : {e}") from e # 이미지 조정 에러 처리
return canvas # 캔버스를 반환
def create_result_image(self, resized_images, align, img_space):
"""이미지를 정렬 방향에 따라 병합."""
try:
if align == "수직": # 수직 정렬인 경우
max_width = max(img.size[0] for img in resized_images) # 최대 너비 계산
total_height = sum(img.size[1] for img in resized_images) + img_space * (len(resized_images) - 1) # 총 높이 계산
result_img = Image.new("RGB", (max_width, total_height), (255, 255, 255)) # 새로운 이미지를 생성
offset = 0 # 시작 오프셋 설정
for idx, img in enumerate(resized_images): # 각 이미지를 수직으로 배치
result_img.paste(img, (0, offset))
offset += img.size[1] + img_space # 다음 이미지의 오프셋 계산
self.update_progress_bar(idx, len(resized_images))
elif align == "수평": # 수평 정렬인 경우
total_width = sum(img.size[0] for img in resized_images) + img_space * (len(resized_images) - 1) # 총 너비 계산
max_height = max(img.size[1] for img in resized_images) # 최대 높이 계산
result_img = Image.new("RGB", (total_width, max_height), (255, 255, 255))
offset = 0
for idx, img in enumerate(resized_images): # 각 이미지를 수평으로 배치
result_img.paste(img, (offset, 0))
offset += img.size[0] + img_space
self.update_progress_bar(idx, len(resized_images))
except Exception as e:
raise ValueError(f"create_result_image - 이미지 병합 과정 에러 : {e}") from e # 이미지 병합 에러 처리
return result_img # 병합된 결과 이미지 반환
def update_progress_bar(self, current, total):
"""진행 상태를 업데이트하고, 텍스트로 진행 상황을 표시."""
progress = (current / total) * 100
self.p_var.set(progress) # 진행률 % 설정
self.progress_bar.update() # 진행률 업데이트
self.progress_label.config(text=f"{int(progress)}% 완료") # 진행률 라벨로 표시시
def selected_listbox_image_path(self):
"""리스트 박스 선택된 이미지 경로 가져오기"""
selected = self.list_file.curselection() # 리스트박스에서 선택된 항목 가져오기
if not selected: # 선택된 항목이 없으면
self.lbl_listfile_preview.config(image='', text='이미지를 선택하세요') # 선택 메시지 표시
return
list_image_filepath = self.list_file.get(selected[0]) # 선택된 파일 경로 가져오기
return list_image_filepath # 파일 경로 반환
def update_preview(self, place='listbox_preview'):
"""미리보기 업데이트"""
if place == 'listbox_preview': # 리스트박스 미리보기
try:
list_image_filepath = self.selected_listbox_image_path() # 이미지 파일 경로 가져오기
if list_image_filepath is None: # listbox에서 파일삭제시 미리보기 return
return
img = Image.open(list_image_filepath) # 이미지 열기
img.thumbnail((self.window_width / 3, self.window_height / 3)) # 미리보기 크기 조정
self.list_image_preview = ImageTk.PhotoImage(img) # Tkinter 이미지 객체로 변환print("lbl_listfile_preview
self.lbl_listfile_preview.config(image=self.list_image_preview, text="") # 미리보기 업데이트
except Exception as e:
self.lbl_listfile_preview.config(text=f"listbox_preview 오류: {str(e)}", image="") # 오류 처리
elif place == 'result_preview': # 결과 미리보기
try:
self.img = Image.open(self.result_img_file_path) # 결과 이미지 열기
# 캔버스 넓이 가져오기
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
self.img.thumbnail((canvas_width, canvas_height)) # 미리보기 크기 조정
#if self.canvas_image_id is None: # 저장 결과 이미지를 새로 로딩할 때
self.result_image_preview = ImageTk.PhotoImage(self.img) # Tkinter 이미지 객체로 변환
# Canvas 중앙 좌표 계산
x_center = canvas_width // 2
y_center = canvas_height // 2
# 이미지 중앙에 배치
self.canvas_image_id = self.canvas.create_image(x_center, y_center, image=self.result_image_preview, anchor="center")
except Exception as e: # 에러시 canvas 로 저장 결과 preview 에 표출
self.canvas.create_text(
self.x_center,
self.y_center,
text="에러 발생: 파일을 불러올 수 없습니다.",
fill="red",
font=("Arial", 16),
anchor="center"
)
def zoom(self, event=None): # event=None 추가
"""확대/축소 비율 계산 및 이미지 업데이트"""
scale = self.scale_preview.get() / 100 # 확대/축소 비율 계산
if not hasattr(self, "img"): # 이미지가 없으면
return
# 원본 이미지를 기준으로 크기 변경
width, height = int(self.img.width * scale), int(self.img.height * scale) # 새로운 크기 계산
resized_image = self.img.resize((width, height), Image.Resampling.LANCZOS) # 이미지 크기 조정
# ImageTk.PhotoImage 객체로 변환
self.result_image_preview = ImageTk.PhotoImage(resized_image)
# Canvas의 중심 좌표 계산
canvas_width = max(self.canvas.winfo_width(), width) # 캔버스 크기 조정
canvas_height = max(self.canvas.winfo_height(), height)
x_center = canvas_width // 2 # 캔버스 중앙값 산
y_center = canvas_height // 2
# Canvas에 이미지 업데이트
self.canvas.delete("all") # 기존의 모든 항목 삭제
self.canvas.create_image(x_center, y_center, image=self.result_image_preview, anchor="center") # 새로운 이미지 배치
# Scrollregion 설정 (캔버스의 스크롤 범위 설정)
self.canvas.configure(scrollregion=(0, 0, width, height)) # 캔버스의 전체 영역을 스크롤 범위로 설정
if __name__ == "__main__":
root = TkinterDnD.Tk() # 드래그 앤 드롭 가능한 Tkinter 루트 창 생성
app = ImageMergerApp(root) # 앱 인스턴스 생성
root.mainloop() # Tkinter 이벤트 루프 실행
'Project' 카테고리의 다른 글
영화 추천 프로그램 - Sicikit Learn 정리중.. (1) | 2024.12.01 |
---|---|
얼굴 추적하여 이미지 띄우기 - OpenCV 정리중.. (0) | 2024.12.01 |