Введение
Иногда необходимо изменить размер изображения с сохранением пропорции сторон. Особенно, когда это очень большое количество файлов. Это приложение позволяет изменить размер изображения, сохранить его в нужную папку, а также инвертировать цвет (подходит для редактирования осциллограмм) и переименовать файл. Возможно, для существуют уже какие-то программы, но была необходимость сделать это самому. Поделюсь кодом с вами, возможно кому-то потребуется (Код программы в конце статьи).
Функции программы
Перди мной стояла задача, сделать простую программу позволяющую изменять размер изображения с сохранением пропорции сторон. Долго недумая решил написать небольшой скрипт, который удовлетворял бы такие потребности:
Возможность работы с разными типами файлов изображений (png, bmp, jpg и т.д.)
Удобно добавлять файлы в программу
Возможность добавлять сразу несколько изображений
Возможность переименовывать отредактированный файл
Возможность инвертировать цвета
Удобный выбор пути для сохранения файлов
Простой интерфейс программы
Внешний вид
Сначала рассмотрим, как выглядит программа и что выполняет, а затем перейдем к самой реализации.
Интерфейс программы выглядит следующим образом:

Как видим, программа не перегружена лишними деталями. Разберем каждый элемент подробнее.
Image files - выбор изображений, которые нужно будет редактировать. Откроется стандартный файловый проводник.
Save in folder - выбор папки куда будет сохранен редактируемый файл если пользователь не выберет папку, они будут сохранены в папке корня программы под названием Edited photos (папка создается автоматически). Откроется стандартный файловый проводник.
Image height (cm) - здесь нужно внести высоту изображения, которая необходима (ширина будет отредактирована автоматически с сохранением пропорций)
Inver color - при выборе этого чекбокса, цвета изображения будут инвертированы в RGBA (Это очень удобно если работаете с изображением осциллограмм)
Rename the file - при выборе чекбокса разблокирует поле ввода "Add ending to file name" и дописывает в название файла соответствующий текст.
Пример: название изображения "x". В поле Add ending to file name указываем "edit". Файл или файлы сохраняются с таким названием: "x_edit".
Если чекбокс не выбран, то файл сохранится с таким же названием ("x")Convert - редактирование файла и сохранение с соответствующими параметрами указанными выше.

Реализация программы
Код был написан на Python с использованием следующих библиотек:
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
import PIL.ImageOps
from PIL import Image
import errno
import os
Полный код программы на Python:
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
import PIL.ImageOps
from PIL import Image
import errno
import os
##--------------------------------------------------------------------------------------------------------------------##
##Creating a folder at the root of the program
def make_sure_path_exists(path):
try: os.makedirs(path)
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
##Фуfunction that translates from cm to pixel
def cm_in_px(cm):
global px
px = int(cm) * 38
return px
##Creating a folder at the root of the program-------------------------------------------------------------------------#
make_sure_path_exists('Edited photos')
#----------------------------------------------------------------------------------------------------------------------#
##ФScale and store aspect ratio
def scale_image(input_image_path,
output_image_path,
width=None,
height=None
):
original_image = Image.open(input_image_path)
w, h = original_image.size
print('The original image size is {wide} wide x {height} '
'high'.format(wide=w, height=h))
if width and height:
max_size = (width, height)
elif width:
max_size = (width, h)
elif height:
max_size = (w, height)
else:
# No width or height specified
raise RuntimeError('Width or height required!')
original_image.thumbnail(max_size, Image.ANTIALIAS)
original_image.save(output_image_path)
scaled_image = Image.open(output_image_path)
width, height = scaled_image.size
print('The scaled image size is {wide} wide x {height} '
'high'.format(wide=width, height=height))
#----------------------------------------------------------------------------------------------------------------------#
##Signature checkbox 2
def chek_cb2():
global message_entry
if ismarried2.get() == 0:
message_entry = Entry(window, state=DISABLED, bd=2, width = 48)
message_entry.place(x=10, y=275)
return Label(window, text='Add ending to file name:', font=('Arial', 10)).place(x=10, y=250)
else:
message_entry = Entry(window, state=NORMAL, bd=2, width = 48)
message_entry.place(x=10, y=275)
return Label(window, text='Add ending to file name:', font=('Arial', 10)).place(x=10, y=250)
#----------------------------------------------------------------------------------------------------------------------#
##file renaming
def rename():
global last_name
if ismarried2.get() == 1:
last_name = '_' + message_entry.get()
else:
last_name = ''
return last_name
#----------------------------------------------------------------------------------------------------------------------#
#----------------------------------------------------------------------------------------------------------------------#
##Path to upload images
number_f = 0
def clicked_dialogOpen():
global choosefile
global number_f
choosefile = filedialog.askopenfilename(multiple=True, parent = window, filetypes=(("Image files", "*.png"), ("all files", "*.*")))
number_f = len(choosefile)
label_file()
#----------------------------------------------------------------------------------------------------------------------#
##Check for characters in the string
def check_name():
global d
d = 0
for i in message_entry.get():
if i.isalpha():
d += 1
elif i.isdigit():
d+= 1
else:
d+= 1
return d
#----------------------------------------------------------------------------------------------------------------------#
##Display information about the number of selected images
def label_file():
if number_f == 0:
lbl2 = Label(window, text="Image not selected", font=('Arial', 9), fg = 'red')
lbl2.place(x=start_pos_x, y=start_pos_y + step_des * 0.9)
elif number_f == 1:
lbl2 = Label(window, text='File selected '.format(number_f), font=('Arial', 9), fg = 'green')
lbl2.place(x=start_pos_x, y=start_pos_y + step_des * 0.9)
else:
lbl2 = Label(window, text='Files selected - {} '.format(number_f), font=('Arial', 9), fg = 'green')
lbl2.place(x=start_pos_x, y=start_pos_y + step_des * 0.9)
#----------------------------------------------------------------------------------------------------------------------#
##Open the path to save the file
filename = 0
def browse_button():
global filename
filename = filedialog.askdirectory()
label_folder()
#----------------------------------------------------------------------------------------------------------------------#
##Display save directory information
def label_folder():
if filename == 0:
lbl = Label(window, text="Path not selected", font=('Arial', 9), fg = 'red')
lbl.place(x=start_pos_x, y=start_pos_y + step_des * 2.3)
elif filename == '':
lbl = Label(window, text=os.getcwd() + '/Edited photos/', font=('Arial', 9), fg = 'green')
lbl.place(x=start_pos_x, y=start_pos_y + step_des * 2.3)
else:
lbl = Label(window, text=filename, font=('Arial', 9), fg = 'green')
lbl.place(x=start_pos_x, y=start_pos_y + step_des * 2.3)
#----------------------------------------------------------------------------------------------------------------------#
##Zoom and record an image
def scale():
check_name()
if number_f < 1:
messagebox.showerror("Error", "No file selected")
if ismarried2.get() == 1 and d < 1:
messagebox.showerror("Error", "Parameter not entered: Add ending to file name")
else:
for i in range(number_f):
try:
with open(choosefile[i]) as im:
r = os.path.splitext(choosefile[i])
var = (os.path.basename(r[0]), r[1])
if filename == 0:
folder = os.getcwd() + '/Edited photos/'
output_name = folder + var[0] + rename() + var[1]
scale_image(input_image_path=choosefile[i],
output_image_path=output_name,
height=cm_in_px(message_cm.get()))
if ismarried.get() == 1:
image = Image.open(output_name)
if image.mode == 'RGBA':
r, g, b, a = image.split()
rgb_image = Image.merge('RGB', (r, g, b))
inverted_image = PIL.ImageOps.invert(rgb_image)
r2, g2, b2 = inverted_image.split()
final_transparent_image = Image.merge('RGBA', (r2, g2, b2, a))
final_transparent_image.save(output_name)
else:
inverted_image = PIL.ImageOps.invert(image)
inverted_image.save(output_name)
else:
folder = filename
output_name = filename + '/' + var[0] + rename() + var[1]
scale_image(input_image_path=choosefile[i],
output_image_path = output_name,
height=cm_in_px(message_cm.get()))
if ismarried.get() == 1:
image = Image.open(output_name)
if image.mode == 'RGBA':
r, g, b, a = image.split()
rgb_image = Image.merge('RGB', (r, g, b))
inverted_image = PIL.ImageOps.invert(rgb_image)
r2, g2, b2 = inverted_image.split()
final_transparent_image = Image.merge('RGBA', (r2, g2, b2, a))
final_transparent_image.save(output_name)
else:
inverted_image = PIL.ImageOps.invert(image)
inverted_image.save(output_name)
print("Çhose",choosefile[0])
except:
print('Error')
messagebox.showerror("Error", "An error has occurred.\n\nThe program is intended for image processing only.\n\nContact the e-mail address:\nolehlastovetskyi99@gmail.com")
quit()
messagebox.showinfo("Message", "Completed!\nChanged {} files.\nFiles saved in the directory:\n{}".format(number_f, folder))
#----------------------------------------------------------------------------------------------------------------------#
##--------------------------------------------------------------------------------------------------------------------##
window = Tk()
ismarried = IntVar(value= 2)
ismarried.set(0)
ismarried2 = IntVar(value= 2)
ismarried2.set(0)
chek_cb2()
rename()
##------------------------------------------------------------------------------------------------------------------------##
start_pos_x = 10
start_pos_y = 10
height_button = 2
width_button = 32
font_button = ("Arial Bold", 11)
font_checkbox = ("Arial", 11)
font_combobox = ('Arial', 11)
font_label = ("Arial Bold", 11)
step_des = 60
label_folder()
label_file()
window.title("Scale")
btn_dialogOpen = Button(window, text="Image files", command=clicked_dialogOpen, height=height_button,
width=width_button, font=font_button)
btn_dialogOpen.place(x=start_pos_x, y=start_pos_y)
btn_browsebutton = Button(window, text="Save in folder", command=browse_button, height=height_button,
width=width_button, font=font_button)
btn_browsebutton.place(x=start_pos_x, y=start_pos_y + step_des + 25)
lbl = Label(window, text="Image height (cm):", font=font_label)
lbl.place(x=start_pos_x + 1, y=start_pos_y + step_des * 2.95)
message_cm = Entry(width=7)
message_cm.place(x=start_pos_x + 150, y=start_pos_y + step_des * 2.97)
message_cm.insert(0, "9")
ismarried_checkbutton = Checkbutton(text="Invert color", variable=ismarried, font =font_checkbox)
ismarried_checkbutton.place(x=start_pos_x + 1, y=start_pos_y + step_des * 3.4)
ismarried_checkbutton2 = Checkbutton(text="Rename the file", variable=ismarried2,
font = font_checkbox, command = chek_cb2)
ismarried_checkbutton2.place(x=start_pos_x + 170, y=start_pos_y + step_des * 3.4)
btn_scale = Button(window, text="Convert", command=scale, height=height_button, width=width_button,
font=font_button, state = NORMAL)
btn_scale.place(x=start_pos_x, y=start_pos_y + step_des * 5.2)
Label(window, text='Github:', font=('Arial', 8)).place(x=10, y=385)
x = (window.winfo_screenwidth() - window.winfo_reqwidth()) / 2
y = (window.winfo_screenheight() - window.winfo_reqheight()) / 2
window.wm_geometry("+%d+%d" % (x, y))
window.maxsize(320,410)
window.minsize(320,410)
window.resizable(0, 0)
window.mainloop()