
Skrypt do łączenia wielu zdjęć w jedno (Galeria Grid)
Pierwszy wpis z serii gotowy skrypt do automatyzacji konkretnej czynności. Ten, pomaga generować “miniatury”, łącząc obrazy wielu przedmiotów na jednym “płótnie” (tak będę nazywał obszar obrazu naszej galerii). Pomocne dla osób, które chcą umieścić na aukcji, karcie produktu, zdjęcie poglądowe wszystkich wariantów (np. wielu kolorów, wzorów).
Plan działania
Skrypty są idealne do realizowania określonych zadań, a więc musimy określić co konkretnie potrzebujemy zautomatyzować.
Zadania:
- Zebranie informacji o wszystkich obrazkach (ilość i wymiary)
- Ustalenie optymalnych wymiarów miniatury na płótnie, uwzględniając
- Ilość wszystkich obrazków
- Ilość obrazków w jednej linii
- Szerokość płótna
- Modyfikacja obrazka i wklejenie go na płótno
- Dla każdego obrazka
- Zapis wyniku do pliku (w tym przypadku PNG)
Skrypt
Ważne! Każdy skrypt piszę w najnowszej wersji Python (3.5 i wyżej).
Potrzebować będziemy następujące biblioteki:
import os
from PIL import Image
from math import ceil
Ad. 1
Funkcja zbiera informacje o plikach w podanym folderze. Buduje “słownik” (dict) który użyjemy aby zachować informacje o ilości plików oraz ich wymiarach.
def get_images_info(path):
images = {}
for root, dirs, files in os.walk(path):
for name in files:
if os.path.isfile(os.path.join(path, name)):
with Image.open(os.path.join(root, name)) as temp_img:
images[name] = temp_img.size
return images
Przykład co zwraca funkcja:
{'koszulka-czarna.jpg': (600, 600), 'main.png': (760, 376), 'koszulka-meska-oliwkowa.jpg': (308, 308), 'koszulka-oliwkowa.jpg': (308, 308), 'koszulka-oliwkowa (copy).jpg': (308, 308), 'koszulka-czarna (copy).jpg': (600, 600), 'koszulka-meska-oliwkowa (copy).jpg': (308, 308)}
Ad. 2
Korzystając z informacji z punktu 1, możemy wyliczyć średnie (optymalne) wymiary dla obszaru jednego obrazka na płótnie. Najlepiej kiedy obrazki nie odbiegają znacząco wymiarami od siebie (przykład kłopotliwego zestawu: 64x64, 1200x3000, 800x12000).
def get_standard_size(size_list):
count = len(size_list)
standard_x = 0
standard_y = 0
for size in size_list:
standard_x += size[0]
standard_y += size[1]
else:
standard_x = standard_x / count
standard_y = standard_y / count
return standard_x, standard_y
Pozostałe potrzebne dane wczytamy “po drodze”
Ad. 3
Czas na główną funkcję, która zrobi robotę.
Funkcja pomocnicza, do wczytywania obrazków:
def open_image(file_path):
with Image.open(file_path) as current_image:
temp_img = current_image.copy()
return temp_img
Danie główne:
def merge_images(path, canvas_width=760, in_row=5, output_file="main.png"):
img_dict = get_images_info(path)
if not img_dict:
raise BaseException("Couldn't gather information about images!")
img_count = len(img_dict.keys())
standard_size = get_standard_size(img_dict.values())
space_between = 2
thumb_offset_y = 0
thumb_offset_x = 0
img_index = 0
temp_img_index = 0
if img_count < in_row:
thumb_x = int(canvas_width / img_count - space_between)
width_percent = (float(thumb_x) / float(standard_size[0]))
thumb_y = int((float(standard_size[1]) * float(width_percent)))
canvas_height = thumb_y + space_between
else:
thumb_x = int(canvas_width / in_row - space_between)
width_percent = (float(thumb_x) / float(standard_size[0]))
thumb_y = int((float(standard_size[1]) * float(width_percent)))
canvas_height = int(ceil(img_count / in_row) * thumb_y)
main_img = Image.new('RGB', (canvas_width, canvas_height), (255, 255, 255, 255))
for img_filename in img_dict.keys():
original_img = open_image(os.path.join(path, img_filename))
thumb_img = original_img.resize((thumb_x, thumb_y), Image.ANTIALIAS)
if img_index >= in_row:
thumb_offset_y += thumb_y + space_between
thumb_offset_x = 0
temp_img_index += img_index
images_left = img_count - temp_img_index
if (img_count - temp_img_index) < in_row:
thumb_offset_x = ((canvas_width - (2*space_between)) - (images_left * thumb_x)) / 2
img_index = 0
offset = (int(thumb_offset_x), int(thumb_offset_y))
main_img.paste(thumb_img, offset)
img_index += 1
thumb_offset_x += thumb_x + space_between
main_img.save(os.path.join(path, output_file), "PNG")
main_img.close()
Dobra, time out, spróbuję trochę wyjaśnić co się dzieje na górze.
# dla ułatwienia, tylko podanie ścieżki jest wymagane, reszta argumentów jest opcjonalna
# i posiada przypisane wartości domyślne
def merge_images(path, canvas_width=760, in_row=5, output_file="main.png"):
# używamy naszej funkcji aby zebrac informację o wszystkich obrazkach w podanej ścieżce
img_dict = get_images_info(path)
# małe zabezpieczenie na wypadek gdyby nie udało się wczytać informacji o plikach
# (np brak plików w podanej ścieżce)
if not img_dict:
raise BaseException("Couldn't gather information about images!")
# ilość obrazków
img_count = len(img_dict.keys())
# nasza funkcja przyjmuje listę, więc korzystamy z values()
# które zwraca nam wartości danego słownika w formie listy
# czyli wszystkie wartości rozmiarów [(x, y), ..., (x, y)]
standard_size = get_standard_size(img_dict.values())
# zmienna która określa odstęp między powierzchniami dla każdego obrazka na płótnie
space_between = 2
# wstępna deklaracja innych zmiennych
thumb_offset_y = 0
thumb_offset_x = 0
img_index = 0
temp_img_index = 0
W tym miejscu sprawdzamy czy ilość obrazków jest większa niż maksymalna ilość dla jednego rzędu. Jeżeli nie, nie uwzględnimy np. 5 miejsc w rzędzie, ale tyle ile realnie mamy obrazków.
if img_count < in_row:
thumb_x = int(canvas_width / img_count - space_between)
width_percent = (float(thumb_x) / float(standard_size[0]))
thumb_y = int((float(standard_size[1]) * float(width_percent)))
canvas_height = thumb_y + space_between
else:
thumb_x = int(canvas_width / in_row - space_between)
width_percent = (float(thumb_x) / float(standard_size[0]))
thumb_y = int((float(standard_size[1]) * float(width_percent)))
canvas_height = int(ceil(img_count / in_row) * thumb_y)
Wyposażeni w niezbędne dane, tworzymy obszar galerii, czyli nasze przysłowiowe płótno.
main_img = Image.new('RGB', (canvas_width, canvas_height), (255, 255, 255, 255))
I konkretna robota, czyli otwieramy kolejne obrazki, zmniejszamy do wielkości obliczonej wcześniej, jeżeli jest to zdjęcie które nie zmieści się już w obecnym rzędzie przenosimy je na następny (pod spodem), wstawiając przy tym pomiędzy każdy obszar wolne miejsce, które zadeklarowaliśmy w zmiennej space_between.
for img_filename in img_dict.keys():
original_img = open_image(os.path.join(path, img_filename))
thumb_img = original_img.resize((thumb_x, thumb_y), Image.ANTIALIAS)
if img_index >= in_row:
thumb_offset_y += thumb_y + space_between
thumb_offset_x = 0
temp_img_index += img_index
images_left = img_count - temp_img_index
if (img_count - temp_img_index) < in_row:
thumb_offset_x = ((canvas_width - (2*space_between)) - (images_left * thumb_x)) / 2
img_index = 0
offset = (int(thumb_offset_x), int(thumb_offset_y))
main_img.paste(thumb_img, offset)
img_index += 1
thumb_offset_x += thumb_x + space_between
Ad. 4
Nie zapominamy o zapisie i zamknięciu pliku
main_img.save(os.path.join(path, output_file), "PNG")
main_img.close()