12 września 2020

Przetwarzanie obrazów, zdjęć i grafiki w PHP (GD)

Kategoria: Programowanie
Tagi: dla profesjonalistów,
Autor: Paweł Mansfeld

Przetwarzanie obrazów, zdjęć i grafiki w PHP (GD)

W tym artykule pokażę jak za pomocą PHP dokonać podstawowych operacji takich jak skalowanie czy kadrowanie obrazów. Tego typu umiejętności przydadzą się w przypadku chęci stworzenia własnej aplikacji do obróbki grafiki lub stworzenia funkcjonalności, które będą wymagać tego typu operacji np:

  • własna biblioteka mediów,
  • autorski CMS,
  • panele klienta,

oraz w wielu innych sytuacjach. PHP jako język wyspecjalizowany do tworzenia stron i aplikacji WWW posiada wbudowane funkcje, które ułatwiają tego typu manipulacje, zresztą, bardzo często wykorzystywane przez fora internetowe, portale społecznościowe czy CMSy. Użytkownik wysyłając swoje zdjęcie profilowe czy załącznik do strony wysyła zdjęcia w różnych formatach i rozmiarach – jako typowy user nie ma obowiązku dopasowywać wgrywanego zdjęcia na serwer lub przejmować się problemami optymalizacji dla potrzeb WWW. Przerzucanie na użytkownika takich operacji jest mało profesjonalne.

To my jako programiści musimy zadbać, aby zdjęcie miało odpowiedni rozmiar i format niezależnie od tego jakie zdjęcie czy grafikę będzie chciał wysłać użytkownik. Czytaj więcej w artykule: optymalizacja zdjęć, grafik i obrazów dla potrzeb stron internetowych.

Upload zdjęć i grafik na serwer w PHP

Upload plików w różnych formatach nie różni się od siebie. Przypomnę, że już miałem przyjemność publikować tego typu artykuł: upload wielu plików na serwer z paskiem postępu PHP dlatego nie będziemy się nad tym krokiem długo rozwodzić. Ponieważ jest tam zaprezentowany złożony przykład wykorzystujący AJAX, w telegraficznym skrócie zaprezentuję jak wygląda najprostszy kod potrzebny do wysłania zdjęcia na serwer w PHP.

Rozpoczynamy od stworzenia standardowego formularza, wykorzystujemy pole “file”:

<form method="POST" enctype="multipart/form-data">
   <input type="file" name="plik">
   <input type="submit">
</form>

Taki kod wystarczy aby po stornie przeglądarki otrzymać to co trzeba dla wysłania obrazka (lub pliku o dowolnie innej zawartości i formacie).

Standardowy formularz do wysyłania plików

Po stronie PHP sprawdzamy czy formularz został wysłany:

if (isset($_POST["submit"])) {
   $tmp_name = $_FILES["plik"]["tmp_name"];
   $name = basename($_FILES["plik"]["name"]);
   move_uploaded_file($tmp_name, "uploads/".$name);
}

Zdjęcie możemy wyświetlić:

echo '<img src="uploads/'$name.'">';
Przykładowe zdjęcie do ćwiczeń.

O tym jak obsługiwać błędy, sprawdzić rozszerzenia czy ustawić limit wielkości dla jednego pliku jest opisane we wyżej podlinkowanym artykule.

Skalowanie

Czym jest skalowanie? Skalowanie w grafice komputerowej i w kontekście grafiki rastrowej to transformacja, czyli przekształcenie obrazu polegająca na zapisaniu go przy użyciu mniejszej bądź większej ilości pikseli.

Skalowanie (zmiana rozmiaru) zdjęć lub obrazów w PHP

Najbardziej podstawową operacją na zdjęciu jest jego skalowanie czyli zmiana rozmiaru zdjęcia. Zdjęcia przesyłane na stronę internetową zazwyczaj pomniejszamy dlatego taki scenariusz wykorzystamy w poniższym przykładzie.

Użytkownicy mają tendencję do wysyłania zdjęć w przypadkowych rozmiarach. Nie interesuje ich waga zdjęcia i też nie specjalnie przejmują się jego optymalizacją do zastosowania na stronie. Na konwencjonalnej stronie internetowej czy w portalu internetowym nie potrzeba zdjęć, które ma większy rozmiar od rozdzielczości FullHD. Stosowanie zdjęć o wyższej rozdzielczości zwyczajnie marnuje transfer nie przyczyniając się znacząco do wzrostu wizualnej jakości.

Wyjątek stanowią monitory o rozdzielczości co najmniej 4K, gdzie przeglądarka skalując całą zawartość jest w stanie zaprezentować zdjęcie o większej szczegółowości.

Instagram nadaje zdjęciom rozmiar 1080px dla dłuższego boku a na blogach spokojnie wystarczy grafika która ma ok 1000px na szerokość (u mnie wystarczy już nieco ponad 700px).

Po przesłaniu obrazka lub po ręcznym umieszczeniu zdjęcia na serwerze wygodnie będzie przypisać do zmiennej ścieżkę do tego pliku.

$original = 'uploads/'.$name;

Wynikowe zdjęcie musi się gdzieś zapisać, dlatego tworzymy czarny obrazek tak jakby tło lub matrycę na zdjęcie które zaraz wygenerujemy:

$small = imagecreatetruecolor(640, 480);

Za pomocą instrukcji imagecreatefromjpeg() tworzymy obiekt obrazu źródłowego dzięki czemu wygenerujemy zdjęcie w pożądanym rozmiarze.

$source = imagecreatefromjpeg($original);

Kluczową funkcją jest tutaj imagecopyresized() – tworzy kopię obrazka i zmienia jego rozmiar:

imagecopyresized($small, $source, 0, 0, 0, 0, 640, 480, $width, $height);

Pierwszy argument tej funkcji to mniejszy obrazek, który utworzyliśmy wcześniej – miejsce na wynikowy obrazek. Drugi to obrazek źródłowy. Dwa kolejne argumenty to współrzędne x i y obrazka docelowego. Teraz są dwa zera bo chcemy zacząć od razu od krawędzi. Kolejne da argumenty (u nas również zera) to współrzędne x i y obrazka źródłowego. Chcemy przeskalować cały obrazek. Kolejne dwa argumenty to dolna prawa krawędź obrazka docelowego. Dwa ostatnie to dolna prawa krawędź obrazka źródłowego.

Jak widać aby przeskalować całe zdjęcie, potrzebujemy znać dokładną rozdzielczość oryginału. Służy do tego funkcja getimagesize(): Zwraca ona tablicę, którą można sobie sprawdzić za pomocą var_dump() lub print_r(), mnie w tym momencie interesują tylko dwie pierwsze wartości:

$original_dimensions = getimagesize($original);
$width = $original_dimensions[0];
$height = $original_dimensions[1];

Po wszystkim trzeba zapisać zdjęcie i dla naszej uciechy wyświetlić w przeglądarce:

imagejpeg($small, 'processed/'.$name);
echo '<img src="processed/'.$name.'">';

Folder “processed” musi istnieć.

Uruchamiamy i jak widać zdjęcie nam się troszkę rozjechało:

Skalowanie bez zachowania współczynnika proporcji.

Jest to naturalna konsekwencja tego, że po prostu podaliśmy rozmiar docelowy bez przejmowania się rozmiarem oryginału. Rozwiązaniem problemu może być:

  1. Dopasowanie wysokości lub wysokości obrazka docelowego $small, aby miał te same proporcje co oryginał.
  2. Skalować do rozmiaru wyliczonego z pomnożenia wielkości przez dany współczynnik np. 0.5 zmniejszy nam wielkość o połowę,
  3. Dokonać kadrowania zmieniając obszar obrazka źródłowego.

Skalowanie obrazów z zachowaniem współczynnika proporcji

Jeżeli chcemy otrzymać obrazek o konkretnym aspect ratio zachowując stałą szerokość np. 640px trzeba po prostu obliczyć proporcję: szerokość_oryginału / 640px. Wyliczony współczynnik przyda nam się do ustalenia nowej wysokości:

$original = 'uploads/'.$name;
$original_dimensions = getimagesize($original);
$width = $original_dimensions[0];
$height = $original_dimensions[1];
$aspect = $width/640;
$new_width = 640;
$new_height = $height/$aspect;
$small = imagecreatetruecolor($new_width, $new_height);
$source = imagecreatefromjpeg($original);
imagecopyresized($small, $source, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
imagejpeg($small, 'processed/'.$name);
echo '<img src="processed/'.$name.'">';

Jak widać, efekt jest zgodny z zamierzeniami:

Przeskalowane zdjęcie z zachowaniem współczynnika proporcji z zadaną szerokością rozmiaru docelowego.

Różnica pomiędzy imagecopyresized a imagecopyresampled

Nie trzeba jednak być grafikiem komputerowym by dostrzec, że z tym zdjęciem jest coś nie tak… Zdjęcie jest “ziarniste”, niskiej jakości z jakąś sztuczną ostrością. Dlaczego tak się stało? To wynik braku resamplingu czyli algorytmu, który spowoduje, że przemieszczane w wyniku skalowania piksele są dodatkowo cieniowane, mieszane są ich kolory aby przy mniejszej ilości pikseli wierniej odzwierciedlić oryginalne zdjęcie. Aby “oszukać oko” musimy wykorzystać nieco bardziej wyszukane algorytmy skalowania a te są zaszyte w funkcji imagecopyresampled(). Zamieniamy imagecopyresized() na imagecopyresampled():

imagecopyresampled($small, $source, 0, 0, 0, 0, $new_width, $new_height, $width, $height);

Teraz otrzymaliśmy dużo lepszą jakość, obraz po prawej jest gładszy i wierniej odzwierciedla oryginalne zdjęcie:

Kadrowanie

Kadrowanie to (mówiąc kolokwialnie) obcięcie zdjęcia. Wyobraźmy sobie, że tworzymy profile użytkownika gdzie dajemy możliwość załadowania zdjęcia profilowego.

Problem: zdjęcie profilowe ma ostatecznie postać kwadratu a użytkownicy muszą mieć możliwość łatwego przesyłania zdjęć bez wcześniejszego ręcznego obrabiania zdjęcia, które może być wykonane w orientacji pionowej bądź poziomej.

Kadrowanie zdjęć (obrazów) w PHP

Spostrzegawcze osoby pewnie już zauważyły, że do kadrowania zdjęć użyjemy dokładnie tego samego zestawu instrukcji. Cała zabawa będzie polegać na wyliczeniu odpowiednich wartości width i height.

Podgląd planowanego kadrowania

Jak powinien wyglądać algorytm pozwalający uzyskać odpowiednie współrzędne?

Na samym początku musimy ustalić, czy mamy do czynienia ze zdjęciem w orientacji poziomej czy pionowej. Ponieważ mamy już doświadczenie w pobieraniu takich informacji nie ma nic prostszego:

$original_dimensions = getimagesize($original);
$width = $original_dimensions[0];
$height = $original_dimensions[1];
if($width >= $height){
   $new_width = $height/2;
   $new_height = $height/2;
   $width_offset = ($width - $height)/2;
   $height_offset = 0;
}
else{
   $new_width = $width/2;
   $new_height = $width/2;
   $width_offset = 0;
   $height_offset = ($height - $width)/2;
}

Offset (czyli przesunięcie) to po prostu to zaciemnione pole które chcemy przyciąć. Uzyskaliśmy go bardzo prosto, poprzez różnicę pomiędzy szerokością a wysokością podzieloną przez 2.

Offset trzeba następnie użyć przy współrzędnych x i y analogicznie jak w przypadku skalowania:

$small = imagecreatetruecolor($new_width, $new_height);
$source = imagecreatefromjpeg($original);
imagecopyresampled($small, $source, 0, 0, $width_offset, $height_offset, $new_width, $new_height, ($width-$width_offset2), ($height-$height_offset2));

Jak widać program działa:

Skadrowane zdjęcie w proporcji 1 do 1

Zdjęcie zostało wykadrowane a środkowy punkt jest w tym samym miejscu co w oryginale. Obraz jest wyraźny i zgodny z oryginałem.

Po tej małej łamigłówce uspokajam, że są funkcje do automatycznego kadrowania zdjęć, które możesz znaleźć na liście w dalszych punktach tutorialu.

Obrót obrazu w PHP

Obrót stosujemy już rzadziej. Może nam się przydać w przypadku bardzo niestandardowych aplikacji lub automatyzowania pracy.

Obrót realizujemy za pomocą funkcji imagerotate(). Możemy teraz połączyć poznane funkcje i przed zapisem dodać linijkę:

 $rotate = imagerotate($small, 45, 0);

zapisujemy obrazek po obrocie:

imagejpeg($rotate, 'processed/'.$name);

I otrzymuejmy coś takiego:

Wynik działania funkcji rotate.

Filtry

W PHP obrazy można modyfikować za pomocą filtrów. Funkcja przyjmuje argumenty odpowiedzialne za konkretny zestaw filtrów. Całą listę można znaleźć tutaj: https://www.php.net/manual/en/function.imagefilter.php

Wygenerujmy zdjęcie w odcieniach szarości:

imagefilter($small, IMG_FILTER_GRAYSCALE);
Przykładowe zdjęcie z filtrem IMG_FILTER_GRAYSCALE

A tak wygląda IMG_FILTER_EMBOSS:

Przykładowe zdjęcie z filtrem IMG_FILTER_EMBOSS

ciekawy jest też IMG_FILTER_COLORIZE, gdzie możemy zdefiniować kolor i kanał alpha:

imagefilter($small, IMG_FILTER_COLORIZE, 184,3,255,56);
Przykładowe zdjęcie z filtrem IMG_FILTER_COLORIZE

Pozostałe funkcje GD związane z przetwarzaniem obrazów

W PHP mamy mnóstwo innych funkcji związanych z przetwarzaniem obrazów. Biblioteka GD zawierająca wspomniane funkcje wspiera też różne formaty odczytu i zapisu:

  • JPEG
  • PNG
  • GIF
  • BMP
  • WebP (od PHP 5.4)
  • XBL

oraz w szczególnych przypadkach parę innych. Za pomocą instrukcji print_r(gd_info()); możemy poznać możliwości naszej wersji PHP.

Niektóre często wykorzystywane funkcje nie wymienione w tutorialu to:

  • getimagesizefromstring – pobiera rozmiar obrazu zapisanego w stringu,
  • image_type_to_extension – zwraca właściwe rozszerzenie dla danego formatu obrazka,
  • image_type_to_mime_type – zwraca typ MIME,
  • imagebmp – zapisuje obrazek BMP do pliku,
  • imageconvolution – zastosowuje matrycę przekształceń 3×3,
  • imagecopy – kopiuje fragment obrazka,
  • imagecreatefrombmp – tworzy obraz z pliku BMP,
  • imagecreatefromgif – tworzy obraz z pliku GIF,
  • imagecreatefrompng – tworzy obraz z pliku JPG,
  • imagecrop – ułatwia kadrowanie,
  • imagecropauto – ułatwia kadrowanie,
  • imagedestroy – niszczy obrazek,
  • imagescale – ułatwia skalowanie,
  • imagestring – kreśli napis na obrazku (znak wodny),
  • imagesx – zwraca szerokość obrazka,
  • imagesy – zwraca wysokość obrazka,
  • imagewebp – zapisuje obraz do pliku webp,
  • imagepng – zapisuje obraz do pliku png,

Całą listę możesz poznać na stronie dokumentacji podlinkowanej w źródłach.

Skoro istnieją gotowe funkcje do łatwego kadrowania i skalowania zdjęcia dlaczego na początku tutorialu bawiliśmy się z funkcją imagecopyresampled()? Opanowanie tej funkcji pozwoli na wykonanie dosyć częstej funkcjonalności jaką jest ręczne kadrowanie z wykorzystaniem siatki, którą często można spotkać w różnego rodzaju witrynach z treścią generowaną przez użytkownika.

Przykład ręcznego kadrowania zdjęcia w aplikacji internetowej

Elastyczność funkcji imagecopyresampled() pozwoli zapisać wynik takich manipulacji w interfejsie użytkownika.

Podsumowanie

PHP po raz kolejny udowodnił, że jest idealnym językiem do tworzenia serwisów i aplikacji internetowych także takich, których funkcjonalność obejmuje proste przetwarzanie grafiki. Prostota działania i możliwość wykonania stosunkowo złożonych operacji jedną linijką kodu bez ładowania specjalnych bibliotek to spore ułatwienie dla programistów obniżenie kosztów i możliwość szybkiego tworzenia aplikacji.

Źródła

https://www.php.net/manual/en/book.image.php

Oceń artykuł na temat: Przetwarzanie obrazów, zdjęć i grafiki w PHP (GD)
Średnia : 4.7 , Maksymalnie : 5 , Głosów : 6


 

Odpowiedz lub skomentuj

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *


 

Wykryto brak połączenia z Internetem.