Введение
Иногда необходимо изменить размер изображения с сохранением пропорции сторон. Особенно, когда это очень большое количество файлов. Это приложение позволяет изменить размер изображения, сохранить его в нужную папку, а также инвертировать цвет (подходит для редактирования осциллограмм) и переименовать файл. Возможно, для существуют уже какие-то программы, но была необходимость сделать это самому. Поделюсь кодом с вами, возможно кому-то потребуется (Код программы в конце статьи).
Функции программы
Перди мной стояла задача, сделать простую программу позволяющую изменять размер изображения с сохранением пропорции сторон. Долго недумая решил написать небольшой скрипт, который удовлетворял бы такие потребности:
Возможность работы с разными типами файлов изображений (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()
Комментарии (32)
Akon32
30.01.2022 21:53+13Для таких вещей достаточно imagemagic + bash.
unsignedchar
30.01.2022 21:58+3У каждого есть наготове свой микроскоп ;) Но из однострочника на bash статья не получится.
kAIST
30.01.2022 22:11+10Не, я конечно тоже через подобное проходил - писал свой резайзер на питоне с блекджеком и куртизанками (поддержка многоядерности, заморочки с резкостью и пр.), но в голову не приходило писать об этом. Потому что это все было очень просто с точки зрения программирования и нужно только мне (как и размеры в сантиметрах с прибитым гвоздями странным dpi у вас).
P.S. Советую начинать читать про классы в питоне, очень уж страшно выглядит код.
Gaikotsu
30.01.2022 23:26+4Судя по скринам, делалось для винды?
Тогда, вместо изобретения велосипеда, вполне можно было просто воспользоваться к примеру xnview, позволяющим делать кучу разных действий над целыми папками с изображениями, притом разом можно делать целую цепочку действий над каждым файлом - то же самое изменение размера, инвертирование цветов, наложение фильтров и т.д. и т.п.
DaneSoul
31.01.2022 10:46XnViewMP вполне себе кросс-платформенный, не первый год использую под Linux.
А так, действительно, мега мощный и удобный софт для работы с изображениями — и как просмотрщик и как пакетный обработчик.Gaikotsu
31.01.2022 11:16А, ну я сам просто до сих пор пользуюсь classic-версией, которая имеется только для windows.
Glomberg
30.01.2022 23:34+8Хосспади, коллеги! Призываю поддержать велосипедостроение! Ведь только так мы проходим тернии и образумливаеся. Объективная критика усилит, безусловно, эффек.
Hivemaster
31.01.2022 22:17Велосипедостроение - дело хорошее. Но писать о велоспедах на Хабр - это слишком.
KarmicDude
31.01.2022 00:09+1это делается все парой команд :\ зачем?
vodopad
31.01.2022 00:18+2Да ладно, статья может пригодится какому-нибудь студенту. Может лаба будет какая-нибудь похожая. Или новчику в Python.
DonAgosto
31.01.2022 00:26+11каждый начинающий хардварщик обязан сделать:
1. Часики с будильником
2. Погодную станцию (с часиками с будильником)
каждый начинающий софтварщик обязан сделать:
1. Прогу для пакетного ресайза картинок
2. Прогу для пакетного поиска/замены текста (с опцией пакетного ресайза картинок)rostislav-zp
31.01.2022 21:49Собрав поголную станцию, сравнил ее с готовой китайской в красивом корпусе.стало жаль потраченных денег и времени.купил готовую,а коробка с запчастями так и валяется в шкафу.но было интересно конечно проходить весь путь
bungu
31.01.2022 01:00+6Ну и говнокод конечно. За global надо вообще палкой по пальцам бить. Про f-string, os.path.join() автор конечно не слышал. Вообщем смеялись всей маршруткой (отделом разработки)
def cm_in_px(cm):
global px
px = int(cm) * 38
return pxВот тут вообще не понял. А как же dpi?
Maccimo
31.01.2022 03:48Ну какая палка, коллега, вы что? Мы же не в каменном веке!
Для этого давно уже изобрели киянку.
13_beta2
31.01.2022 02:01+4Выскажу крамолу, но по моим наблюдениям стабильно вызывают печаль большинство статей и "статей" с заголовком подходящим под маску %python%. Первопричина хайпа (так ещё говорят или новое слово изобрели?) вокруг языка понятна, причина низкого качества и кода и статей легко вмещается в понятие "новички". Однако как с этим бороться решительно не ясно. Пока сформировалось что-то вроде баннерной слепоты, но такой подход не решает самой проблемы, интересные материалы могут пройти мимо, случайные тапы, опять же.
serginfo2009
31.01.2022 08:11Давно пишу на Python и всем сердцем люблю этот язык. В свете изложенного статьи про Python на Хабре не читаю принципиально.
MentalBlood
31.01.2022 09:40Специфичного решения действительно не видно, остается только плюсовать то что хорошо и минусовать то что плохо
ertaquo
31.01.2022 10:30Для Windows есть довольно удобный пакет программ PowerToys от самих Microsoft, который включает в себя утилиту для изменения размера изображений.
Для *nix гораздо проще воспользоваться imagemagick.
cadovvl
31.01.2022 14:50+3Та ну что такое.
Увидев синопсис, теги и оценку, пропустил статью и сразу полез в комментарии. А комментарии что-то не огонь.
Пойду поищу статью про новую сортировку и "окончательные точки над И в вопросе производительности С# и C++". Там веселее.
Hivemaster
При чём тут big data?
fedorro
Первая версия этой программы могла обрабатывать один файл за раз, путь к которому задавался строкой в текстбоксе, а тут вон какая мега-автоматизация:
????
unsignedchar
Big data это наверное что то другое :)
Goupil
Надо же. Код, который я написал вчера вокруг нейронки, тоже может в обработку батчей картинок, тысяч их . Могу с гордостью сказать что я тоже работаю с big data.