Skalowanie obrazków w PHP
Ten artykuł zawiera przestarzałe i powolne rozwiązanie. Zapraszam do czytania nowej wersji!
Biblioteka GD jest narzędziem umożliwiającym generowanie grafiki w takich językach jak PHP, Perl czy też C++. Ogromna liczba funkcji, które udostępnia biblioteka, powinny zadowolić nawet najbardziej wymagających.
Umiejętne połączenie kilkunastu funkcji pozwala na np. skalowanie obrazków. W tym poradniku opiszę jak wykonać w języku PHP skrypt skalujący obrazek do wybranych wymiarów w prawidłowych proporcjach.
Ten artykuł zawiera przestarzałe i powolne rozwiązanie. Zapraszam do czytania nowej wersji!
Biblioteka GD jest narzędziem umożliwiającym generowanie grafiki w takich językach jak PHP, Perl czy też C++. Ogromna liczba funkcji, które udostępnia biblioteka, powinny zadowolić nawet najbardziej wymagających.
Umiejętne połączenie kilkunastu funkcji pozwala na np. skalowanie obrazków. W tym poradniku opiszę jak wykonać w języku PHP skrypt skalujący obrazek do wybranych wymiarów w prawidłowych proporcjach.
Skrypt
Zaczniemy od kodu. Później wszystko wytłumaczę.
<?php
function rozszerzenie($file) {
$v=explode('.',$file);
$n=count($v);
$extension=$v[$n];
return $extension;
}
if (isset($_GET['link']) && isset($_GET['width']) && isset($_GET['height'])) {
switch (rozszerzenie($_GET['link'])) {
case 'gif':
$img=imagecreatefromgif($_GET['link']);
$width=imagesx($img);
$height=imagesy($img);
$edit=false;
$valid[0]=true;
$valid[1]=true;
if ($width<=$_GET['width']) {
$valid[0]=false;
}
if ($height<=$_GET['height']) {
$valid[1]=false;
}
if ($width>$_GET['width']) {
$roznica=$width-$_GET['width'];
$wsp=$width/$roznica;
$nHeight=$height/$wsp;
$rHeight=$height-$nHeight;
$rWidth=$_GET['width'];
$edit=true;
}
if ($rHeight>$_GET['height']) {
$roznica=$rHeight-$_GET['height'];
$wsp=$rHeight/$roznica;
$nWidth=$rWidth/$wsp;
$rHeight=$_GET['height'];
$rWidth=$rWidth-$nWidth;
$edit=true;
}
if (!$edit && !$valid[0] && !$valid[1]) {
$rWidth=$width;
$rHeight=$height;
}
$img_mini=imagecreatetruecolor($rWidth,$rHeight);
imagecopyresampled($img_mini,$img,0,0,0,0,$rWidth,$rHeight,$width,$height);
header("Content-type: image/gif;");
imagegif($img_mini);
break;
case 'png':
$img=imagecreatefrompng($_GET['link']);
$width=imagesx($img);
$height=imagesy($img);
$edit=false;
$valid[0]=true;
$valid[1]=true;
if ($width<=$_GET['width']) {
$valid[0]=false;
}
if ($height<=$_GET['height']) {
$valid[1]=false;
}
if ($width>$_GET['width']) {
$roznica=$width-$_GET['width'];
$wsp=$width/$roznica;
$nHeight=$height/$wsp;
$rHeight=$height-$nHeight;
$rWidth=$_GET['width'];
$edit=true;
}
if ($rHeight>$_GET['height']) {
$roznica=$rHeight-$_GET['height'];
$wsp=$rHeight/$roznica;
$nWidth=$rWidth/$wsp;
$rHeight=$_GET['height'];
$rWidth=$rWidth-$nWidth;
$edit=true;
}
if (!$edit && !$valid[0] && !$valid[1]) {
$rWidth=$width;
$rHeight=$height;
}
$img_mini=imagecreatetruecolor($rWidth,$rHeight);
imagecopyresampled($img_mini,$img,0,0,0,0,$rWidth,$rHeight,$width,$height);
header("Content-type: image/png;");
imagepng($img_mini);
break;
case 'jpg':
$img=imagecreatefromjpeg($_GET['link']);
$width=imagesx($img);
$height=imagesy($img);
$edit=false;
$valid[0]=true;
$valid[1]=true;
if ($width<=$_GET['width']) {
$valid[0]=false;
}
if ($height<=$_GET['height']) {
$valid[1]=false;
}
if ($width>$_GET['width']) {
$roznica=$width-$_GET['width'];
$wsp=$width/$roznica;
$nHeight=$height/$wsp;
$rHeight=$height-$nHeight;
$rWidth=$_GET['width'];
$edit=true;
}
if ($rHeight>$_GET['height']) {
$roznica=$rHeight-$_GET['height'];
$wsp=$rHeight/$roznica;
$nWidth=$rWidth/$wsp;
$rHeight=$_GET['height'];
$rWidth=$rWidth-$nWidth;
$edit=true;
}
if (!$edit && !$valid[0] && !$valid[1]) {
$rWidth=$width;
$rHeight=$height;
}
$img_mini=imagecreatetruecolor($rWidth,$rHeight);
imagecopyresampled($img_mini,$img,0,0,0,0,$rWidth,$rHeight,$width,$height);
header("Content-type: image/jpeg;");
imagejpeg($img_mini);
break;
case 'jpeg':
$img=imagecreatefromjpeg($_GET['link']);
$width=imagesx($img);
$height=imagesy($img);
$edit=false;
$valid[0]=true;
$valid[1]=true;
if ($width<=$_GET['width']) {
$valid[0]=false;
}
if ($height<=$_GET['height']) {
$valid[1]=false;
}
if ($width>$_GET['width']) {
$roznica=$width-$_GET['width'];
$wsp=$width/$roznica;
$nHeight=$height/$wsp;
$rHeight=$height-$nHeight;
$rWidth=$_GET['width'];
$edit=true;
}
if ($rHeight>$_GET['height']) {
$roznica=$rHeight-$_GET['height'];
$wsp=$rHeight/$roznica;
$nWidth=$rWidth/$wsp;
$rHeight=$_GET['height'];
$rWidth=$rWidth-$nWidth;
$edit=true;
}
if (!$edit && !$valid[0] && !$valid[1]) {
$rWidth=$width;
$rHeight=$height;
}
$img_mini=imagecreatetruecolor($rWidth,$rHeight);
imagecopyresampled($img_mini,$img,0,0,0,0,$rWidth,$rHeight,$width,$height);
header("Content-type: image/jpeg;");
imagejpeg($img_mini);
break;
}
}
?>
Omówienie skryptu
Oczywiście nie jest to kod doskonały. Mógłby być lepszy. Pierwszą niedoskonałością, rzucającą się w oczy, jest podwójny kod dla plików JPG. Jest tak dlatego, że pliki te mogą mieć dwa różne rozszerzenia: *.jpg i *.jpeg. Gdybym użył instrukcji warunkowych if () { } else { }, mógłbym zmniejszyć rozmiar pliku. Jednak wybrałem funkcję switch(), gdyż jest (to tylko moje zdanie) bardziej przejrzysta.
Jak już na pewno zauważyliście, napisałem dodatkową funkcję rozszerzenie(), która zwraca rozszerzenie pliku. Jest to bezpieczne rozwiązanie, ponieważ nawet gdy ktoś użyje w nazwie pliku kropek, to i tak zostanie wybrany ciąg po tej ostatniej.
Kod będę tłumaczył na przykładnie plików z rozszerzeniem *.png, ponieważ instrukcje dla innych plików różnią się jedynie nazwami funkcji. Tak więc kiedy już zostanie określone rozszerzenie pliku, skrypt zapisuje do zmiennej $img obrazek podany w zmiennej $_GET['link'] przy pomocy funkcji imagecreatefrompng(). Kolejnym krokiem jest pobranie rozmiarów obrazu za pomocą funkcji imagesx() i imagesy() do zmiennych $width i $height. Kiedy już wymiary są przypisane do zmiennych, tworzymy zmienną $edit=false; i dwuelementową tablicę $valid (obydwa elementy na false).
Teraz rozpoczynają się "poważniejsze" działania skryptu. W pierwszej instrukcji warunkowej sprawdza on, czy docelowa szerokość obrazu nie jest większa lub równa jego oryginalnej szerokości. Jeśli tak jest, wartość zmiennej $valid[0] jest ustawiana na true. Drugi etap polega na tym samym, tyle, że teraz sprawdzamy wysokość obrazu.
Następnie wykonywane są działania mające na celu ustalenie docelowych wymiarów obrazu w taki sposób, aby wymiary były możliwie najmniejsze (ograniczają je wartości podane w zmiennych $_GET['width'] i $_GET['height']), ale także aby proporcje obrazu docelowego zgadzały się z tymi obrazu oryginalnego. Wszystko wykonywane jest według bardzo prostego algorytmu.
Ostatnim krokiem jest stworzenie i wyświetlenie obrazu o mniejszych wymiarach za pomocą kilku funkcji: createimagetruecolor(), imagecopyresampled() i imagepng(). Uwaga! Za pomocą funkcji header() trzeba wcześniej ustalić typ zawartości (Content-type: image/png). Bez tego nie zobaczymy obrazka, a jedynie adres pliku.
Skrypt nie ma żadnych rozbudowanych możliwości, ale działa w miarę szybko. Aktualnie w moim serwisowym notatniku tworzy się kolejny poradnik o skalowaniu obrazków w PHP. Tym razem będzie to profesjonalne rozwiązanie oparte o klasę. Mam nadzieję, że wszystko opisałem dość jasno. Jeśli są jakieś niejasności, proszę o komentarze.
Cześć,
Wybacz, ale takie coś to się pisało 10 lat temu. Biblioteka GD? No ok, w akcie desperacji można, ale jeśli nie trzeba, to Imagick jest *znacznie* lepszym wyborem.
Dwa - żyjemy w XXI wieku, a PHP dorobiło się już dawno wsparcia dla OOP - dlaczego więc z tego nie skorzystać?
Trzy - rozszerzenie pliku spokojnie wyciągniemy funkcjępathinfo().
Cztery - temat był wałkowany tyle razy, że istnieje sporo gotowców zdecydowanie lepszej jakości.
Pięć - formatowanie kodu leży i kwiczy (jeśli to wina bloga, to czym prędzej należy to poprawić, bo kodu czytać się nie da).
Tyle z mojej strony.
Pozdrawiam
Nad ułożeniem i kolorowaniem kodu pracuję, ale zrobienie tego samemu jest dość pracochłonne. W edytorze jest ułożony jak należy.
Co do GD, to wybrałem ją, bo znam ją dość dobrze i lepiej się w niej czuję.
Kacper - ale tu nawet nie chodzi o to, w czym się lepiej czujesz. To co Ty zrobiłeś w tyyyylu linijkach, w IMagick wygląda mniej więcej tak:
$im = new IMagick( $nazwa_pliku );
$im->cropThumbnailImage( 144, 144 );
$im->writeImage( 'thumb_' . $nazwa_pliku );
Nie dość, że prościej, to jeszcze współpracuje z całą masą formatów, a nie tylko PNG, GIF, JPG i BMP. Mniej pisania, mniej bólu głowy i więcej czasu na szarpanie strun :-)
I tak mi się jeszcze rzuciło - nie musisz powtarzać osobno kodu dla JPG/JPEG, wystarczy napisać tak:
switch( $typ ) {
case 'jpg':
case 'jpeg':
// kod właściwy
break;
}
kod w instrukcji switch wykonuje się od momentu wejścia w odpowiednią instrukcję "case", aż do napotkania pierwszego "break".
Pozdrawiam
Dokładnie za sześć minut ukaże się nowa wersja artykułu - oparta na IMagick ;)