tkinter로 이미지 합치기 프로그램 만들기

설계, 완성된 프로그램

image

이미지를 합치는 프로그램의 완성 사진이다. 기능은 단순하게 이 정도가 있다

  • 파일을 추가해서 리스트에 넣기
  • 파일을 삭제하기
  • 저장 경로 지정해주기
  • 옵션 1 : 이미지 크기 조정하기
  • 옵션 2 : 이미지 간격 조정하기
  • 옵션 3 : FORMAT(PNG, JPG, BMP) 형식 지정하기
  • 프로그레스 바 연동하기

이미지를 조정해주고 추가해주기 위해서 Pillow 모듈을 사용했다.

레이아웃을 먼저 짜고 그 이후에 기능을 추가했다.

완성된 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
from tkinter import *
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import filedialog
from PIL import Image
import os

root = Tk()

PVERSION = "1.0"
PROGRAM_NAME = "IMAGE COMBINE PROGRAM - {}".format(PVERSION)
FIRST_SAVE_PATH = "C:/"

root.title("{}".format(PROGRAM_NAME))


def add_file():
    files = filedialog.askopenfilenames(
        title="IMAGE SELECT",
        filetypes=(("PNG", "*.png"), ("JPG", "*.jpg"), ("BMP", "*.bmp"), ("All file", "*.*")),
        initialdir="PythonWorkspace/game_basic",
    )

    # LIST_FILE
    for file in files:
        list_file.insert(END, file)


def del_file():
    for i in reversed(list_file.curselection()):
        list_file.delete(i)


def save_path():
    path = filedialog.askdirectory(title="SAVE PATH")
    print(path)
    if path == "":
        return

    txt_save_path.delete(0, END)
    txt_save_path.insert(0, path)


def start():
    try:
        print(
            "WIDTH = {}\nDISTANCE = {}\nFORMAT = {}\n\nSAVE PATH = {}\n".format(
                cbbox_width.get(), cbbox_distance.get(), cbbox_format.get(), txt_save_path.get()
            )
        )

        if list_file.size() == 0:  # IF EMPTY FILE LIST
            msgbox.showerror("NO FILE", "Please add file")
            return

        if len(txt_save_path.get()) == 0:  # IF EMPTY SAVE PATH
            msgbox.showerror("NO SAVE PATH", "Please specify path")
            return

        # IMAGE COMBINE
        img_combine()
        msgbox.showinfo("DONE")
    except Exception as err:
        msgbox.showerror("ERR", err)


def img_combine():
    images = [Image.open(x) for x in list_file.get(0, END)]  # FROM list_file SAVE images

    img_width = cbbox_width.get()
    image_sizes = []
    if img_width == "Keep size":
        img_width = -1
    else:
        img_width = int(img_width)

    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]

    widths, heights = zip(*(image_sizes))

    total_distance = 0
    type_distance = 0

    if cbbox_distance.get() == "NONE":  # OPTION2 DISTANCE
        pass
    elif cbbox_distance.get() == "Narrow":
        for i in range(0, list_file.size()):
            total_distance += 20
            type_distance = 20
    elif cbbox_distance.get() == "Normal":
        for i in range(0, list_file.size()):
            total_distance += 40
            type_distance = 40
    elif cbbox_distance.get() == "Wide":
        for i in range(0, list_file.size()):
            total_distance += 80
            type_distance = 80

    print(list_file.size())
    print(type(list_file.size()))
    print("{} / {}".format(widths, heights))

    max_width, total_height = max(widths), sum(heights) + total_distance

    print("{} / {}".format(max_width, total_height))

    result_img = Image.new("RGB", (max_width, total_height), (255, 255, 255))
    y_offset = 0  # pos y location

    for idx, img in enumerate(images):
        if img_width > -1:
            img = img.resize(image_sizes[idx])

        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1] + type_distance

        progress = (idx + 1) / len(images) * 100
        p_var.set(progress)
        progress_bar.update()

    save_combine_image_path = os.path.join(
        txt_save_path.get(), "combine_img.{}".format(cbbox_format.get().lower())
    )
    result_img.save(save_combine_image_path)


# FILE FRAME
file_frame = Frame(root)
file_frame.pack(fill=X, padx=5, pady=5)

btn_add_file = Button(file_frame, text="ADD FILE", padx=5, pady=5, width=12, command=add_file)
btn_add_file.pack(side="left")

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

# LIST FRAME
list_frame = Frame(root)
list_frame.pack(fill=BOTH, padx=5, pady=5)

scrollbar = Scrollbar(root)
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)

# SAVE PATH FRAME
path_frame = LabelFrame(root, text="SAVE PATH")
path_frame.pack(fill=X, padx=5, pady=5)

txt_save_path = Entry(path_frame)
txt_save_path.insert(END, FIRST_SAVE_PATH)

txt_save_path.pack(side=LEFT, fill=X, expand=True, padx=5, pady=5)

btn_save_path = Button(path_frame, text="Browse", width=10, command=save_path)
btn_save_path.pack(side=RIGHT, padx=5, pady=5)

# OPTION FRAME
option_frame = LabelFrame(root, text="OPTION")
option_frame.pack(fill=X, padx=5, pady=5)

lbl_width = Label(option_frame, text="WIDTH", width=8)
lbl_width.pack(side=LEFT, padx=5, pady=5)

cbbox_width_array = ["Keep size", "1024", "800", "640"]
cbbox_width = ttk.Combobox(option_frame, values=cbbox_width_array, state="readonly", width=10)
cbbox_width.current(0)
cbbox_width.pack(side=LEFT, padx=5, pady=5)

lbl_distance = Label(option_frame, text="DISTANCE", width=8)
lbl_distance.pack(side=LEFT, padx=5, pady=5)

cbbox_distance_array = ["NONE", "Narrow", "Normal", "Wide"]
cbbox_distance = ttk.Combobox(option_frame, values=cbbox_distance_array, state="readonly", width=10)
cbbox_distance.current(0)
cbbox_distance.pack(side=LEFT, padx=5, pady=5)

lbl_format = Label(option_frame, text="FORMAT", width=8)
lbl_format.pack(side=LEFT, padx=5, pady=5)

cbbox_format_array = ["PNG", "JPG", "BMP"]
cbbox_format = ttk.Combobox(option_frame, values=cbbox_format_array, state="readonly", width=10)
cbbox_format.current(0)
cbbox_format.pack(side=LEFT, padx=5, pady=5)

# PROGRESS BAR
progress_frame = LabelFrame(root, text="PROGRESS")
progress_frame.pack(fill=X, padx=5, pady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(progress_frame, maximum=100, variable=p_var)
progress_bar.pack(fill=X, padx=5, pady=5)

# RUN FRAME
run_frame = Frame(root)
run_frame.pack(fill=X, padx=5, pady=5)

btn_exit = Button(run_frame, text="EXIT", padx=5, pady=5, width=12, command=quit)
btn_exit.pack(side=RIGHT, padx=5, pady=5)

btn_start = Button(run_frame, text="START", padx=5, pady=5, width=12, command=start)
btn_start.pack(side=RIGHT, padx=5, pady=5)


root.resizable(False, False)
root.mainloop()


만들면서 나온 에러

  1. 저장경로를 지정해주지 않았을 때

  2. 저장경로의 permission denied 1, 2는 동시에 try ~ exception 구문을 추가해줘서 에러 메시지를 표시해주는걸로 해결했다.

  3. range 안 씀 리스트파일을 for문에서 활용할 때 range()안에 넣지않고 쓰는 바람에 TypeError가 나왔다. range를 써서 해결함.

정리

파이썬의 있던 모듈들을 활용해서 이렇게 간단한 프로그램들을 여럿 만들 수 있다. 난이도도 어렵지 않아서 쉽게 프로그램을 완성했다.

댓글남기기