![](img/tr.png)
Ve velmi volném pokračování předchozího článku o vytváření webové galerie si napíšeme třídu pro vytváření automatických náhledů, jejich zobrazování a použití.
Vytvářet náhledy je u webové galerie nutnost. I fotky z běžného digitálu mají minimálně 2 MB a načítat takto velký obrázek by se jistě nikomu nechtělo. A protože jsme programátoři myslící na uživatele, který chce mít co nejméně potíží a práce, musíme vytvářet zmenšeniny sami. A nejlépe automaticky.
I PHP umožňuje pracovat s obrázky. Pro vytvoření náhledu moc pokročilých funkcí nepotřebujete, jen vytvoříte zmenšenou kopii. Generovat náhled pokaždé by bylo výpočetně náročné, proto budeme chtít náhledy i ukládat.
Během čtení doporučuji přeskakovat na ukázky kódu. Lépe si tak uděláte představu, o čem je řeč. Někdy mám sklony moc se rozepisovat, proto neváhejte pasáže, kde popisuji jasné a zbytečné, přeskočit. Pokud vám něco nebude jasné, můžete se kdykoli vrátit zpět. Dost už ale řečí, jdeme na to.
Co potřebujeme?
Budu předpokládat, že znáte PHP. Pokud už jste se setkali s objekty (třídami), budete z článku mít o něco víc. Budou se tu používat takové věci, jako jsou výjimky a podobně. Jestli nevíte, o čem je řeč, doporučuji prostudovat nějaký článek o objektovém programování (doporučuji článek Jakuba Kulhana - OOP v PHP). Dále se nebudu rozepisovat podrobně o jednotlivých funkcích - seznámím vás s jejich existencí a možným využitím, více informací můžete získat na php.net.
Možná se setkáte s tím, že vám bude v PHP chybět knihovna GD, která práci s grafikou obsluhuje. Dnes je na většině hostingů dostupná, neměl by být tedy problém, na Linuxu byste ji měli nalézt v repozitáři své distribuce.
Řekněme, že obrázky budeme načítat ze složky img
, ideální by bylo, kdybychom mysleli i na načítání z podsložek. Pro ukládání náhledů si vytvoříme složku thumb
, u které nastavíme oprávnění pro zápis a čtení všem skupinám (chmod 777
). Pokud tak neučiníte, poznáte to podle chybové hlášky. Soubor s třídou nazveme class.thumb.php
a soubor, který nám bude obsluhovat zobrazení po zadání GET parametru, se bude jmenovat thumb.php
.
Adresářová struktura
- ./img/
- ./thumb/
- ./class.thumb.php
- ./thumb.php
Soubor class.thumb.php
Na začátku souboru bychom si měli nadefinovat cesty složek, které budeme používat.
define('DIR_IMAGES', './img/');
define('DIR_THUMBS', './thumbs/');
Pokud už máme vytvořenou kostru nějaké galerie a tyto cesty máme nadefinované jinde, použijeme:
include 'soubor_s_definicemi.php';
Třída Thumb
Co bude vlastně třída Thumb dělat? Možná raději než třída bychom měli použít výraz objekt - bude nám vlastně zastupovat jakýsi abstraktní náhled. Když vytvoříme tento objekt, rozumí se tím, že vytváříme novou instanci náhledu nějakého obrázku. Budeme k tomu potřebovat nějaké vnitřní proměnné, určitě pro cestu k obrázku ($img
), cestu k vytvořenému náhledu ($thumb
) a časem se nám bude hodit i proměnná, kam uložíme formát obrázku ($extension
). Do proměnných $width
a $height
si uložíme požadovanou velikost náhledu. Protože nechceme, aby nám něco jiného na proměnné sahalo, nastavíme je všechny jako privátní.
class Thumb {
// paths and files
private $img = '';
private $thumb = '';
private $extension = '';
// thumb sizes
private $width = 0;
private $height = 0;
Konstruktor
Z těchto úvah pak budeme vycházet při tvorbě konstruktoru. Jeden parametr je jasný - bude jím název souboru, ze kterého chceme vytvářet náhled. A protože vytváříme zmenšeninu obrázku, určitě budeme požadovat alespoň jeden rozměr.
První co při vytváření nového objektu musíme udělat, je ověření, zda soubor vůbec existuje. V této fázi ještě nebudeme zjišťovat, zda ho i dokážeme zpracovat, to se lépe hodí do jiné funkce. Pomocí funkce is_file
zjistíme, zda soubor existuje. (Zde výhodnější než použití funkce file_exists
- ta by nám vrátila true
i při existenci složky.) Správně řečeno, zjistíme, zda neexistuje. Pokud totiž neexistuje, budeme pracovat v duchu objektů a vytvoříme výjimku.
Výjimku umístíme na začátku souboru pod definice cest. Já ji nazval ThumbExceptionNotFile
, vy si ji pojmenujte tak, abyste na první pohled věděli, proč výjimka existuje a k čemu je. Tím situaci, kdy soubor neexistuje, více řešit nemusíme.
Pokud soubor existuje, pokračujeme dále - zapíšeme si celou cestu k souboru do proměnné - včetně naší definice DIR_IMAGES
na začátku skriptu. K získání koncovky obrázku použijeme funkci pathinfo(string $path, PATHINFO_EXTENSION)
. Díky druhému parametru získáme pouze koncovku. Zapíšeme si i požadovanou šířku a výšku náhledu a při tom si vynutíme číselný typ zápisem (int)
. Jako poslední uložíme cestu k náhledu - protože chceme umožnit více velikostí náhledu od jednoho obrázku, zakomponujeme do názvu i jeho velikost. Abychom se zbavili nepříjemných znaků, jednoduše název zakódujeme pomocí funkce md5()
.
Poslední co v konstruktoru chceme mít, je vytvoření náhledu, pokud ještě neexistuje. Pro ověření nám v budoucnu bude sloužit metoda thumbExists()
a pro vytvoření createThumb()
.
Celá metoda __construct()
:
public function __construct($img, $width, $height=0) {
if(!is_file(DIR_IMAGES.$img)) {
throw new ThumbExceptionNotFile('File doesnt exist');
}
$this->img = DIR_IMAGES.$img;
$this->extension = pathinfo($this->img, PATHINFO_EXTENSION);
$this->width = (int) $width;
$this->height = (int) $height;
$this->thumb = DIR_THUMBS.md5($img.$width.$height);
if(!$this->thumbExists()) {
$this->createThumb();
}
}
Metoda thumbExists()
Dobrým zvykem je definovat metody bezpostředně po volání, a to ve stejném pořadí. Pokud jsme tedy jako první volali v konstruktoru metodu thumbExists()
, měla by pro dobrou čitelnost kódu následovat.
V této funkci budeme zjišťovat, zda náhled již existuje a zda není starší než originál. To můžeme smrsknout do jedné podmínky - pomocí funkce filemtime()
ověříme datum poslední modifikace náhledu a originálu, s tím, že pro situaci, kdy náhled nebude ještě existovat, potlačíme chybové hlášení pomocí @
. Obecně potlačovat chybová hlášení velice nedoporučuji, stejně jako používat zápis or die()
při každé možné příležitosti. Zde si to dovolit můžeme, i tak si raději poznamenáme, že je to schválně.
private function thumbExists() {
if(@filemtime($this->thumb) > filemtime($this->img)) { // intentionally @
return true;
}
return false;
}
Metoda createThumb()
Další metoda bude celý proces vytvoření nového náhledu. Nejdříve si zjistíme rozměry původního obrázku pomocí getimagesize()
, čímž zároveň zjistíme, zda je to ve skutečnosti obrázek. Pokud není, budou rozměry nulové a my vyhodíme další výjimku, tentokrát ThumbExceptionNotImage
.
Následuje dopočítávání velikostí stran náhledu, pokud byla zadána jen jedna. Protože je to celkem specifická operace, vytvoříme si pro to metodu calcSize()
, které předáme poměr stran.
Následuje konečné tvoření obrázku. Pomocí funkce imagecreatetruecolor()
si vytvoříme obrázek s rozměry náhledu. Pak musíme načíst původní obrázek - tam se nám možnosti větví na různé formáty, proto to vyřešíme novou metodou createFromOriginal()
. Získaný originál pak pomocí imagecopyresampled()
zmenšíme na velikost náhledu. Šlo by použít i funkci imagecopyresized()
, ta oproti funkci, která i převzorkovává, dává divný, přeostřený obrázek.
Posledním krokem je uložení náhledu. Já zvolil formát PNG, z čehož vyplývá použití funkce imagepng()
(pro JPEG je to imagejpeg()
, použití imagegif()
bych nedoporučoval).
private function createThumb() {
list($img['width'], $img['height']) = getimagesize($this->img);
if(!$img['width'] or !$img['height']) {
throw new ThumbExceptionNotImage('Size is zero. Maybe this is not image.');
}
$this->calcSize($img['width']/$img['height']);
$out = imagecreatetruecolor($this->width, $this->height);
$source = $this->createFromOriginal();
imagecopyresampled($out, $source, 0, 0, 0, 0, $this->width, $this->height, $img['width'], $img['height']);
imagepng($out, $this->thumb);
}
Metoda calcSize()
Pomocí jednoduché matematiky chybějící velikost dopočítáme z poměru stran, kdy platí, že šířka originálu ku jeho výšce se rovná šířce náhledu ku jeho výšce.
private function calcSize($ratio) {
if($this->width==0) {
$this->width = $this->height * $ratio;
} elseif($this->height==0) {
$this->height = $this->width / $ratio;
}
}
Metoda createFromOriginal()
Jednoduchým použitím switch
použijeme pro každý formát jinou funkci. Protože jsme kontrolovali pouze příponu, ne skutečný obsah, může se nám stát, že skončíme na chybové hlášce. Nic horšího. Pro případ, že formát dodán bude a bude to i obrázek (projde funkcí getimagesize()
), ale my ho nebudeme umět zpracovat, vytvoříme další výjimku: ThumbExceptionUnknownFormat
.
Vypisovat, pro jaký formát je jaká funkce, nemá cenu, přečtěte si kód:
private function createFromOriginal() {
switch(strtolower($this->extension)) {
case 'jpg':
return imagecreatefromjpeg($this->img);
case 'jpeg':
return imagecreatefromjpeg($this->img);
case 'png':
return imagecreatefrompng($this->img);
case 'gif':
return imagecreatefromgif($this->img);
default:
throw new ThumbExceptionUnknownFormat('Unknown file format');
}
}
Metoda view()
Náhled se nám vytváří, teď ho ještě poslat na monitor. To nám zajistí veřejná metoda view()
. V ní pošleme informace o typu souboru a jeho velikosti, obrázek vypíšeme funkcí file_get_contents()
a pošleme vše k uživateli pomocí flush()
, protože nechceme, aby nám k obrázku něco přibylo - pak by byl k ničemu, nezobrazil by se. To samé bychom měli udělat i se všemi texty předtím. Vyprázdníme proto na začátku funkce paměť funkcí ob_clean()
.
public function view() {
ob_clean();
header('Content-type: image/png');
header('Content-Length: '.filesize($this->thumb));
echo file_get_contents($this->thumb);
flush();
}
Tím máme třídu pro vytváření náhledů hotovou. Přímo nabízející se rozšíření funkčnosti je přidání vodotisku. Pokud jste článek pochopili, neměl by to pro vás být problém. Pokud vás téma zaujalo, berte to jako "domácí úkol". Šikovné funkce naleznete na php.net.
<?php
define('DIR_IMAGES', './img/');
define('DIR_THUMBS', './thumbs/');
class ThumbExceptionNotFile extends Exception {}
class ThumbExceptionNotImage extends Exception {}
class ThumbExceptionUnknownFormat extends Exception {}
/**
* Need PHP with GD libraries.
*/
class Thumb {
// paths and files
private $img = '';
private $thumb = '';
private $extension = '';
// thumb sizes
private $width = 0;
private $height = 0;
public function __construct($img, $width, $height=0) {
if(!is_file(DIR_IMAGES.$img)) {
throw new ThumbExceptionNotFile('File doesnt exist');
}
$this->img = DIR_IMAGES.$img;
$this->extension = pathinfo($this->img, PATHINFO_EXTENSION);
$this->width = (int) $width;
$this->height = (int) $height;
$this->thumb = DIR_THUMBS.md5($img.$width.$height);
if(!$this->thumbExists()) {
$this->createThumb();
}
}
private function thumbExists() {
if(@filemtime($this->thumb) > filemtime($this->img)) { // intentionally @
return true;
}
return false;
}
private function createThumb() {
list($img['width'], $img['height']) = getimagesize($this->img);
if(!$img['width'] or !$img['height']) {
throw new ThumbExceptionNotImage('Size is zero. Maybe this is not image.');
}
$this->calcSize($img['width']/$img['height']);
$out = imagecreatetruecolor($this->width, $this->height);
$source = $this->createFromOriginal();
imagecopyresampled($out, $source, 0, 0, 0, 0, $this->width, $this->height, $img['width'], $img['height']);
imagepng($out, $this->thumb);
}
private function calcSize($ratio) {
if($this->width==0) {
$this->width = $this->height * $ratio;
} elseif($this->height==0) {
$this->height = $this->width / $ratio;
}
}
private function createFromOriginal() {
switch(strtolower($this->extension)) {
case 'jpg':
return imagecreatefromjpeg($this->img);
case 'jpeg':
return imagecreatefromjpeg($this->img);
case 'png':
return imagecreatefrompng($this->img);
case 'gif':
return imagecreatefromgif($this->img);
default:
throw new ThumbExceptionUnknownFormat('Unknown file format');
}
}
public function view() {
ob_clean();
header ('Content-type: image/png');
header ('Content-Length: '.filesize($this->thumb));
echo file_get_contents($this->thumb);
flush();
}
}
Soubor thumb.php
Jakýmsi kontrolerem, který nám bude náhledy zobrazovat, bude soubor thumb.php. V něm budeme zpracovávat GET data - adresu obrázku, jeho velikost(i) - a pak obrázek pomocí třídy vypíšeme. Také ošetříme výjimky, které mohou nastat.
<?php
include 'class.thumb.php';
if(isset($_GET['src']) and (isset($_GET['width']) or isset($_GET['height']))) {
try {
$thumb = new Thumb($_GET['src'], $_GET['width'], $_GET['height']);
$thumb->view();
} catch (exception $e) {
# akce, ktera nastane, pokud se nepovede vytvorit nahled.
# muze to byt napriklad zobrazeni nejakeho chyboveho obrazku.
echo 'Error';
}
}
A pro úplnost ještě jak zobrazit obrázek v HTML, i když je to podle mne jasné:
<img src="thumb.php?src=image.jpg&width=500" alt="nahled obrazku">