Днес прегледах поста – Image Resizing Made Easy with PHP от Nettuts+. Доста полезен пост, за начинаещи. Но както винаги имах няколко забележки относно от кода. Мислех да ги запиша като коментар, но той се оказа доста дълъг. Затова реших да го напиша като пост.
Като за начало ще е добре да видите началния код от тук ( променената от мен версия е тук). Така промените метод по метод.
openImage
private function openImage($file)
{
// *** Get extension
$extension = strtolower(strrchr($file, '.'));
switch($extension)
{
case '.jpg':
case '.jpeg':
$img = @imagecreatefromjpeg($file);
break;
case '.gif':
$img = @imagecreatefromgif($file);
break;
case '.png':
$img = @imagecreatefrompng($file);
break;
default:
$img = false;
break;
}
return $img;
}
// става:
private function openImage($file){
if (!is_file($file)){
throw new Exception("File {$file} doesn't exists");
}
switch(pathinfo($file, PATHINFO_EXTENSION)){
case 'jpg':
case 'jpeg': return imagecreatefromjpeg($file);
case 'gif': return imagecreatefromgif($file);
case 'png': return imagecreatefrompng($file);
}
throw new Exception("Invalid image extension for {$file}. Acceptable image types are jpg,jpeg,gif,png");
}
Като за начало тук PHP има вградена функция pathinfo, която извикана с PATHINFO_EXTENSION константа като втори аргумент връща какво е разширението на файла.
Също така няма смисъл от дефинирането на променливите $extension и $img защото реално се използват един път. $extension за switch-a, а пък $img само да се return-е.
Също така @ е нещо което НЕ ТРЯБВА да се използва! Освен че е страшно бавно (ако някой се интересува ще обясня вътрешно какви глупости прави и защо е толкова бавна операция). От друга страна не е много редно да се крият грешките в програмите.
За това тук съм добавил два Exeption-а. По-принцип не обичам да ги ползвам, но в този случаи е наложително. Трябва да се провери, първо дали файла който искаме да променяме съществува и второ дали от позволените типове.
resizeImage
public function resizeImage($newWidth, $newHeight, $option="auto")
{
// *** Get optimal width and height - based on $option
$optionArray = $this->getDimensions($newWidth, $newHeight, $option);
$optimalWidth = $optionArray['optimalWidth'];
$optimalHeight = $optionArray['optimalHeight'];
// *** Resample - create image canvas of x, y size
$this->imageResized = imagecreatetruecolor($optimalWidth, $optimalHeight);
imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $optimalWidth, $optimalHeight, $this->width, $this->height);
// *** if option is 'crop', then crop too
if ($option == 'crop') {
$this->crop($optimalWidth, $optimalHeight, $newWidth, $newHeight);
}
}
// става:
public function resizeImage($newWidth, $newHeight, $option='auto'){
list($width, $height) = $this->getDimensions($newWidth, $newHeight, $option);
$this->imageResized = imagecreatetruecolor($width, $height);
imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
if ($option == 'crop'){
$this->crop($width, $height, $newWidth, $newHeight);
}
}
List е доста подценявана PHP функционалност. Но за да може да се работи с нея промених и getDimensions да връща просто масив с два елемента на позиция 0 и 1. ( По-долу ще обясня за нея)
После промених имената на променливите $optimalWidth и $optimalHeight. От една страна са доста дълги и докато се пишат трябва да се натиска и shift заради W и H. От друга те се състоят от две думи, като едната е “optimal” която не ни трябва, даже и грешна. Защото се превеждат “оптимална ширина” и “оптимална височина” докато реално те са просто “ширина” и “височина” (т.е. $width и $height).
Описвам толкова дълго за тези променливи защото именуването на променливите е голяма част от красивия и разбираемия код.
getDimensions
private function getDimensions($newWidth, $newHeight, $option)
{
switch ($option)
{
case 'exact':
$optimalWidth = $newWidth;
$optimalHeight= $newHeight;
break;
case 'portrait':
$optimalWidth = $this->getSizeByFixedHeight($newHeight);
$optimalHeight= $newHeight;
break;
case 'landscape':
$optimalWidth = $newWidth;
$optimalHeight= $this->getSizeByFixedWidth($newWidth);
break;
case 'auto':
$optionArray = $this->getSizeByAuto($newWidth, $newHeight);
$optimalWidth = $optionArray['optimalWidth'];
$optimalHeight = $optionArray['optimalHeight'];
break;
case 'crop':
$optionArray = $this->getOptimalCrop($newWidth, $newHeight);
$optimalWidth = $optionArray['optimalWidth'];
$optimalHeight = $optionArray['optimalHeight'];
break;
}
return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getDimensions($width, $height, $option){
switch ($option){
case 'portrait': return array($this->getSizeByFixedHeight($height), $height);
case 'landscape': return array($width, $this->getSizeByFixedWidth($width));
case 'auto': return $this->getSizeByAuto($width, $height);
case 'crop': return $this->getOptimalCrop($width, $height);
case 'exact':
default: return array($width, $height);
}
}
Първо имената на променливите $newWidth и $newHeight на $width и $height. Причините ги обясних по-горе за $optimalWidth и $optimalHeight.
Промених и формата на масива който се връща от getDimensions от асоциативен масив с ключове optimalWidth и optimalHeight на прост масив с индекси 0 и 1. Кода става доста по четим и по-горе в resizeImage метода може да се ползва List.
Премахват се и излишните променливи $optimalWidth и $optimalHeight, защото реално ни трябва просто масива с двата елемента. И от съкращаването на формата му се вижда че тези две променливи са още по-излишни. Когато се намерят техните стойности веднага може да се върнат в масив, вместо да се чака да се стигне най-долу.
И накрая премествам ‘exact’ да бъде и default случая в switch.
getSizeByFixedHeight / getSizeByFixedWidth
private function getSizeByFixedHeight($newHeight)
{
$ratio = $this->width / $this->height;
$newWidth = $newHeight * $ratio;
return $newWidth;
}
private function getSizeByFixedWidth($newWidth)
{
$ratio = $this->height / $this->width;
$newHeight = $newWidth * $ratio;
return $newHeight;
}
// става:
private function getSizeByFixedHeight($height){
return ($this->width / $this->height) * $height;
}
private function getSizeByFixedWidth($width){
return ($this->height / $this->width) * $width;
}
Много мразя дефинирането на излишни променливи. Защото докато се чете кода трябва да се помни какво има във съответната променлива.
Когато се погледне примерно getSizeByFixedHeight в оригиналната версия, кода се чете:
- Дефинирам $ratio който е ширината на снимката разделена на височината и
- Дефинирам нова височина, която е новата ширина умножена по $ratio ( мисля и се сещам какво има в $ratio )
- Връщам новата височина
Докато моята версия се чете просто:
- Разделям оригналата ширината на снимката на височината, полученото го умножавам по $width и връщам стойността.
getSizeByAuto
private function getSizeByAuto($newWidth, $newHeight)
{
if ($this->height < $this->width)
// *** Image to be resized is wider (landscape)
{
$optimalWidth = $newWidth;
$optimalHeight= $this->getSizeByFixedWidth($newWidth);
}
elseif ($this->height > $this->width)
// *** Image to be resized is taller (portrait)
{
$optimalWidth = $this->getSizeByFixedHeight($newHeight);
$optimalHeight= $newHeight;
}
else
// *** Image to be resizerd is a square
{
if ($newHeight < $newWidth) {
$optimalWidth = $newWidth;
$optimalHeight= $this->getSizeByFixedWidth($newWidth);
} else if ($newHeight > $newWidth) {
$optimalWidth = $this->getSizeByFixedHeight($newHeight);
$optimalHeight= $newHeight;
} else {
// *** Sqaure being resized to a square
$optimalWidth = $newWidth;
$optimalHeight= $newHeight;
}
}
return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getSizeByAuto($width, $height){
if ($this->height < $this->width){
return array($width, $this->getSizeByFixedWidth($width));
}
if ($this->height > $this->width){
return array($this->getSizeByFixedHeight($height), $height);
}
if ($height < $width){
return array($width, $this->getSizeByFixedWidth($width));
}
if ($height > $width){
return array($this->getSizeByFixedHeight($height), $height);
}
return array($width, $height);
}
Колкото по надълбоко влизат вложените структури, толкова по-грозен е кода. Реално тук всяка ситуация може да се изнесе в отделен if и просто ако този if се изпълни се връща резултат веднага и така докато се стигне до края.
getOptimalCrop
private function getOptimalCrop($newWidth, $newHeight)
{
$heightRatio = $this->height / $newHeight;
$widthRatio = $this->width / $newWidth;
if ($heightRatio < $widthRatio) {
$optimalRatio = $heightRatio;
} else {
$optimalRatio = $widthRatio;
}
$optimalHeight = $this->height / $optimalRatio;
$optimalWidth = $this->width / $optimalRatio;
return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getOptimalCrop($width, $height){
$ratio = min($this->height / $height, $this->width / $width);
return array(
$this->width / $ratio,
$this->height / $ratio
);
}
Мin е друга малка PHP функция с чиято помощ този код се по изчиства. Защото реално на нас ни трябва минималната стойност от двете зависимости.
Тук пиша на два реда връщането на масива, отново заради четимостта.
saveImage и crop
private function crop($optimalWidth, $optimalHeight, $newWidth, $newHeight)
{
// *** Find center - this will be used for the crop
$cropStartX = ( $optimalWidth / 2) - ( $newWidth /2 );
$cropStartY = ( $optimalHeight/ 2) - ( $newHeight/2 );
$crop = $this->imageResized;
//imagedestroy($this->imageResized);
// *** Now crop from center to exact requested size
$this->imageResized = imagecreatetruecolor($newWidth , $newHeight);
imagecopyresampled($this->imageResized, $crop , 0, 0, $cropStartX, $cropStartY, $newWidth, $newHeight , $newWidth, $newHeight);
}
public function saveImage($savePath, $imageQuality="100")
{
// *** Get extension
$extension = strrchr($savePath, '.');
$extension = strtolower($extension);
switch($extension)
{
case '.jpg':
case '.jpeg':
if (imagetypes() & IMG_JPG) {
imagejpeg($this->imageResized, $savePath, $imageQuality);
}
break;
case '.gif':
if (imagetypes() & IMG_GIF) {
imagegif($this->imageResized, $savePath);
}
break;
case '.png':
// *** Scale quality from 0-100 to 0-9
$scaleQuality = round(($imageQuality/100) * 9);
// *** Invert quality setting as 0 is best, not 9
$invertScaleQuality = 9 - $scaleQuality;
if (imagetypes() & IMG_PNG) {
imagepng($this->imageResized, $savePath, $invertScaleQuality);
}
break;
// ... etc
default:
// *** No extension - No save.
break;
}
imagedestroy($this->imageResized);
}
// става:
private function crop($optimalWidth, $optimalHeight, $width, $height){
$x = ( $optimalWidth / 2) - ( $width /2 );
$y = ( $optimalHeight / 2) - ( $height /2 );
$crop = $this->imageResized;
$this->imageResized = imagecreatetruecolor($width , $height);
imagecopyresampled($this->imageResized, $crop, 0, 0, $x, $y, $width, $height , $width, $height);
}
public function saveImage($savePath, $imageQuality="100"){
switch(pathinfo($savePath, PATHINFO_EXTENSION)){
case 'jpg':
case 'jpeg':
if (imagetypes() & IMG_JPG){
imagejpeg($this->imageResized, $savePath, $imageQuality);
}
break;
case 'gif':
if (imagetypes() & IMG_GIF){
imagegif($this->imageResized, $savePath);
}
break;
case 'png':
if (imagetypes() & IMG_PNG){
// Scale quality from 0-100 to 0-9
// Invert quality setting as 0 is best, not 9
$invertScaleQuality = 9 - round(($imageQuality/100) * 9);
imagepng($this->imageResized, $savePath, $invertScaleQuality);
}
break;
}
imagedestroy($this->imageResized);
}
Промените по saveImage и crop са просто стилистични, така че няма да се спирам много подробно на тях.
Финални думи
Като започнах да чистя кода си обещах, че няма да променям имената на методите. Но ако питате мен от публичните методи – resizeImage и saveImage бих махнал “Image” и да станат само resize и save. Защото класа най-често би се използвал с променливи кръстени $resizer, $resize, $image и подобни и затова rеsize и save биха достатъчно ясно.
Като цяло урока от Nettuts+ е много добър. Въпроса че според мен в уроците за начинаещи, които главно ползват Nettuts+, трябва да има максимално добър код. Без излишен “шум”.
Надявам се този малък анализ да е бил полезен на някого. Приемам всякакви критики и съвети, под формата на коментари
