() translation by (you can also view the original English article)
Ви завжди хотіли мати простий, універсальний спосіб зміни зображень в РНР? Саме для цього і призначені класи РНР - функції багаторазового використання, вони виконують брудну роботу за кулісами. Ми навчимося створювати свій власний клас, який буде добре побудований і гнучкий. Зміна розміру повинна бути легкою. На скільки? Як раз, два три!
Вступ
Ось швидкий огляд того, чого ми намагаємося досягти, а для цього наш клас повинен бути:
- Простим у використанні.
- Незалежним від формату. Тобто, відкриваємо, змінюємо розмір, зберігаємо зображення в різних форматах.
- Інтелектуальна зміна розміру - без спотворення зображення!
Примітка: дана стаття не є навчальним матеріалом про створення класів і об'єктів, і, хоча вам стане в пригоді цей навик, він не є обов'язковим для даного уроку.
У нас багато роботи - давайте почнемо.
Крок 1. Підготовка.
Ми почнемо з простого. В вашій робочій папці створіть два файла: один з назвою index.php, а інший - class.php.



Крок 2. Виклик об'єкта.
Щоб дати вам уявлення про те, чого ми прагнемо досягти, почнемо з кодування викликів, які ми використовуватимемо для зміни розміру зображень. Відкрийте файл index.php і додайте наступний код.
Як ви можете бачити, в тому, що ми робимо, є красива логіка. Ми відкриваємо файл зображення, встановлюємо розміри, до яких ми хочемо змінити зображення, і вказуємо тип зміни розміру.
Далі ми зберігаємо зображення, вибираємо потрібний формат і якість зображення. Збережіть і закрийте файл index.php.
1 |
|
2 |
// *** Include the class
|
3 |
include("resize-class.php"); |
4 |
|
5 |
// *** 1) Initialize / load image
|
6 |
$resizeObj = new resize('sample.jpg'); |
7 |
|
8 |
// *** 2) Resize image (options: exact, portrait, landscape, auto, crop)
|
9 |
$resizeObj -> resizeImage(150, 100, 'crop'); |
10 |
|
11 |
// *** 3) Save image
|
12 |
$resizeObj -> saveImage('sample-resized.gif', 100); |
З вищенаведеного коду видно, що ми відкриваємо файл jpg, але зберігаємо gif. Пам'ятайте, що це все стосується гнучкості.
Крок 3. Скелет класу.
З об'єктно-орієнтованим програмуванням (ООП) це цілком можливо. Подумайте про клас як про паттерн; ви можете інкапсулювати дані - сленгове слово, яке просто означає приховувати дані. В майбутньому ми можемо використовувати цей клас знову і знову без необхідності переписувати код для зміни розміру. Вам потрібно буде тільки викликати відповідні методи, як ми робили це на другому кроці. Коли паттерн створено, ми створюємо екземпляри цього паттерну, які називаються об'єктами.
"Функція побудови, відома як конструктор - це спеціальний метод класу, який викликається класом при створенні нового об'єкта".
Давайте почнемо створювати наш клас зміни розміру. Відкрийте файл resize-class.php. Нижче приведена базова структура скелету класів, яку я назвав "resize". Зверніть увагу на рядок з іменем змінної класу. Дещо пізніше ми почнемо додавати тут наші важливі змінні класу.
Функція побудови, відома як конструктор, являється спеціальним методом класу (термін "метод" аналогічний функції, проте, коли річ іде про класи і об'єкти, часто використовується термін "метод"), який викликається класом при створенні нового об'єкта. Це робить для нас можливим певну ініціалізацію, що ми і зробимо в наступному кроці.
1 |
|
2 |
Class resize |
3 |
{
|
4 |
// *** Class variables
|
5 |
|
6 |
public function __construct() |
7 |
{
|
8 |
|
9 |
}
|
10 |
}
|
Зверніть увагу, це подвійне підкреслення для методу construct.
Крок 4. Конструктор.
Ми збираємося змінити метод конструктора. По-перше, ми передамо ім'я файла (і шлях) нашого зображення, які потрібно змінити. Ми назвемо цю змінну $fileName.
Нам потрібно відкрити файл, переданий за допомогою РНР (а конкретніше PHP GD Library), щоб РНР міг прочитати зображення. Ми робимо це за допомогою методу користувача "openImage". Я розповім, як цей метод
працює буквально за мить, а поки що нам потрібно зберегти результат як змінну цього класу. Клас змінної - це просто змінна, але вона є специфічною для цього класу. Пам'ятаєте коментар змінної класу, який я згадував раніше? Додайте "image" як приватну змінну, набравши "private $image;". Встановивши змінну як "Private", ви встановлюєте область видимості цієї змінної, тому доступ о неї може отримати тільки клас. З цієї миті ми можемо зробити виклик нашому відкритому зображенню, відомому як ресурс, який ми робитимемо пізніше, коли змінимо розмір.
Поки ми тут, давайте збережемо висоту і ширину зображення. У мене таке відчуття, що вони будуть корисні пізніше.
Тепер у вас повинно бути наступне:
1 |
|
2 |
Class resize |
3 |
{
|
4 |
// *** Class variables
|
5 |
private $image; |
6 |
private $width; |
7 |
private $height; |
8 |
|
9 |
function __construct($fileName) |
10 |
{
|
11 |
// *** Open up the file
|
12 |
$this->image = $this->openImage($fileName); |
13 |
|
14 |
// *** Get width and height
|
15 |
$this->width = imagesx($this->image); |
16 |
$this->height = imagesy($this->image); |
17 |
}
|
18 |
}
|
Методи imagesx і imagesy вбудовані в функції, які є частиною бібліотеки GD. Вони, відповідно, отримують ширину та висоту вашого зображення.
Крок 5. Відкриття зображення.
В попередньому кроці ми викликали метод користувача openImage. На даному етапі ми створимо цей метод. Ми хочемо, щоб скрипт думав замість нас, тому залежно від типу файла, який передається, скрипт повинен визначити, яку функцію бібліотеки GD він викликає, щоб відкрити зображення. Цього легко досягти, порівнявши розширення файлів за допомогою аргумента switch.
Ми переходимо в наш файл, щоб змінити розмір і повернути даний файловий ресурс.
1 |
|
2 |
private function openImage($file) |
3 |
{
|
4 |
// *** Get extension
|
5 |
$extension = strtolower(strrchr($file, '.')); |
6 |
|
7 |
switch($extension) |
8 |
{
|
9 |
case '.jpg': |
10 |
case '.jpeg': |
11 |
$img = @imagecreatefromjpeg($file); |
12 |
break; |
13 |
case '.gif': |
14 |
$img = @imagecreatefromgif($file); |
15 |
break; |
16 |
case '.png': |
17 |
$img = @imagecreatefrompng($file); |
18 |
break; |
19 |
default: |
20 |
$img = false; |
21 |
break; |
22 |
}
|
23 |
return $img; |
24 |
}
|
Крок 6. Як змінити розмір.
Тут починається найцікавіше. Даний крок є всього лиш поясненням того, що ми збираємося робити - тому тут немає домашнього завдання. На наступному кроці ми створимо метод public, який викликатимемо, щоб виконати зміну розміру. Тому має сенс задати ширину і висоту, а також інформацію про те, як ми хочемо змінити зображення. Давайте одну хвилинку поговоримо про це. Повинні бути сценарії, в яких ви хотіли б змінити розмір зображення до потрібного. Чудово, давайте це включимо. Проте будуть випадки, коли вам потрібно буде змінити розмір сотні зображень, і кожне зображення має інше співвідношення сторін - подумайте про портретні зображення. Зміна їх розміру до точного приведе до викривлення зображення. Якщо ми розглянемо варіанти, щоб уникнути деформацій, ми можемо:
- Змінити розмір максимально наближено до нових розмірів зображення, зберігаючи пропорції.
- Змінити розмір зображення максимально наближено до нових розмірів зображення і відрізати залишок.
Обидва варіанти є життєздатними в залежності від ваших потреб.
Так, ми спробуємо реалізувати все вищесказане. В результаті, ми спробуємо забезпечити опціями, щоб:
- Змінити розмір точно по ширині/висоті (точно).
- Змінити розмір по ширині - буде встановлена точна ширина, а висота буде мінятися відповідно до пропорції. (пейзаж).
- Змінити розмір по висоті (як і по ширині), але висота буде вказана, а ширина буде змінюватись відповідно (портрет).
- Автоматично визначати пункти 2 і 3. Якщо ви зациклюєте папку з зображеннями різного розміру, дозвольте скрипту визначити, як діяти (авто).
- Змінити розмір, а потім обрізати. Це мій улюблений. Точний розмір, без спотворень (обрізання).
Крок 7. Зміна розміру. Давайте це зробимо!
Метод resize складається з двох частин. Суть першої в тому, щоб отримати оптимальну ширину і висоту для нашого зображення, створивши деякі спеціальні методи і, звичайно, передавши параметр "змінити розмір", як це було описано вище. Ширина і висота повертаються в якості масиву і встановлюються в відповідні змінні. Ви можете сміливо "pass as reference" - але я не фанат цього.
Друга частина – це те, що реально виконує зміну розміру. Щоб скоротити даний урок, я розповім про наступні функції GD:
Ми також зберігаємо виведення методу imagecreatetruecolor (нове істинне кольорове зображення) в якості змінної класу. Додайте "private $imageResized;" з іншими змінними класу.
Зміна розміру виконується модулем PHP, відомим як бібліотека GD. Багато методів, які ми використовуємо, надаються даною бібліотекою.
1 |
|
2 |
// *** Add to class variables
|
3 |
private $imageResized; |
1 |
|
2 |
public function resizeImage($newWidth, $newHeight, $option="auto") |
3 |
{
|
4 |
|
5 |
// *** Get optimal width and height - based on $option
|
6 |
$optionArray = $this->getDimensions($newWidth, $newHeight, strtolower($option)); |
7 |
|
8 |
$optimalWidth = $optionArray['optimalWidth']; |
9 |
$optimalHeight = $optionArray['optimalHeight']; |
10 |
|
11 |
// *** Resample - create image canvas of x, y size
|
12 |
$this->imageResized = imagecreatetruecolor($optimalWidth, $optimalHeight); |
13 |
imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $optimalWidth, $optimalHeight, $this->width, $this->height); |
14 |
|
15 |
// *** if option is 'crop', then crop too
|
16 |
if ($option == 'crop') { |
17 |
$this->crop($optimalWidth, $optimalHeight, $newWidth, $newHeight); |
18 |
}
|
19 |
}
|
Крок 8. Дерево рішень.
Чим більше роботи ви зробите зараз, тим менше доведеться робити, коли мінятимете розмір. Цей метод вибирає маршрут прийняти, з метою отримання оптимального resize ширину та висоту на основі розміру варіант. Він викличе відповідний метод, який ми створимо в наступному кроці.
1 |
|
2 |
private function getDimensions($newWidth, $newHeight, $option) |
3 |
{
|
4 |
|
5 |
switch ($option) |
6 |
{
|
7 |
case 'exact': |
8 |
$optimalWidth = $newWidth; |
9 |
$optimalHeight= $newHeight; |
10 |
break; |
11 |
case 'portrait': |
12 |
$optimalWidth = $this->getSizeByFixedHeight($newHeight); |
13 |
$optimalHeight= $newHeight; |
14 |
break; |
15 |
case 'landscape': |
16 |
$optimalWidth = $newWidth; |
17 |
$optimalHeight= $this->getSizeByFixedWidth($newWidth); |
18 |
break; |
19 |
case 'auto': |
20 |
$optionArray = $this->getSizeByAuto($newWidth, $newHeight); |
21 |
$optimalWidth = $optionArray['optimalWidth']; |
22 |
$optimalHeight = $optionArray['optimalHeight']; |
23 |
break; |
24 |
case 'crop': |
25 |
$optionArray = $this->getOptimalCrop($newWidth, $newHeight); |
26 |
$optimalWidth = $optionArray['optimalWidth']; |
27 |
$optimalHeight = $optionArray['optimalHeight']; |
28 |
break; |
29 |
}
|
30 |
return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight); |
31 |
}
|
Крок 9. Оптимальні розміри.
Ми вже обговорювали, що роблять ці чотири методи. Вони просто базові математики, які розраховують нам найкращий розмір.
1 |
|
2 |
private function getSizeByFixedHeight($newHeight) |
3 |
{
|
4 |
$ratio = $this->width / $this->height; |
5 |
$newWidth = $newHeight * $ratio; |
6 |
return $newWidth; |
7 |
}
|
8 |
|
9 |
private function getSizeByFixedWidth($newWidth) |
10 |
{
|
11 |
$ratio = $this->height / $this->width; |
12 |
$newHeight = $newWidth * $ratio; |
13 |
return $newHeight; |
14 |
}
|
15 |
|
16 |
private function getSizeByAuto($newWidth, $newHeight) |
17 |
{
|
18 |
if ($this->height < $this->width) |
19 |
// *** Image to be resized is wider (landscape)
|
20 |
{
|
21 |
$optimalWidth = $newWidth; |
22 |
$optimalHeight= $this->getSizeByFixedWidth($newWidth); |
23 |
}
|
24 |
elseif ($this->height > $this->width) |
25 |
// *** Image to be resized is taller (portrait)
|
26 |
{
|
27 |
$optimalWidth = $this->getSizeByFixedHeight($newHeight); |
28 |
$optimalHeight= $newHeight; |
29 |
}
|
30 |
else
|
31 |
// *** Image to be resizerd is a square
|
32 |
{
|
33 |
if ($newHeight < $newWidth) { |
34 |
$optimalWidth = $newWidth; |
35 |
$optimalHeight= $this->getSizeByFixedWidth($newWidth); |
36 |
} else if ($newHeight > $newWidth) { |
37 |
$optimalWidth = $this->getSizeByFixedHeight($newHeight); |
38 |
$optimalHeight= $newHeight; |
39 |
} else { |
40 |
// *** Sqaure being resized to a square
|
41 |
$optimalWidth = $newWidth; |
42 |
$optimalHeight= $newHeight; |
43 |
}
|
44 |
}
|
45 |
|
46 |
return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight); |
47 |
}
|
48 |
|
49 |
private function getOptimalCrop($newWidth, $newHeight) |
50 |
{
|
51 |
|
52 |
$heightRatio = $this->height / $newHeight; |
53 |
$widthRatio = $this->width / $newWidth; |
54 |
|
55 |
if ($heightRatio < $widthRatio) { |
56 |
$optimalRatio = $heightRatio; |
57 |
} else { |
58 |
$optimalRatio = $widthRatio; |
59 |
}
|
60 |
|
61 |
$optimalHeight = $this->height / $optimalRatio; |
62 |
$optimalWidth = $this->width / $optimalRatio; |
63 |
|
64 |
return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight); |
65 |
}
|
Крок 10. Обрізання.
Якщо ви обрали обрізку, тобто опцію crop, тоді у вас є ще один маленький крок. Ми обріжемо зображення
з центру. Обрізування - процес, дуже схожий на зміну розміру, але з передачею декількох параметрів розміру.
1 |
|
2 |
private function crop($optimalWidth, $optimalHeight, $newWidth, $newHeight) |
3 |
{
|
4 |
// *** Find center - this will be used for the crop
|
5 |
$cropStartX = ( $optimalWidth / 2) - ( $newWidth /2 ); |
6 |
$cropStartY = ( $optimalHeight/ 2) - ( $newHeight/2 ); |
7 |
|
8 |
$crop = $this->imageResized; |
9 |
//imagedestroy($this->imageResized);
|
10 |
|
11 |
// *** Now crop from center to exact requested size
|
12 |
$this->imageResized = imagecreatetruecolor($newWidth , $newHeight); |
13 |
imagecopyresampled($this->imageResized, $crop , 0, 0, $cropStartX, $cropStartY, $newWidth, $newHeight , $newWidth, $newHeight); |
14 |
}
|
Крок 11. Збереження зображення.
Ми практично дістались до фінішу. Прийшов час зберегти зображення. Ми проходимо шлях і якість зображення, яке нам потрібно, в діапазоні від 0 до 100, 100 - найкращий варіант, далі викликаємо відповідний метод. Декілька слів про якість зображення: JPG використовує діапазон від 0 до 100, 100 - найкраща якість. Зображення GIF не мають налаштувань якості зображення. PNG мають, але вони використовують діапазон від 0, до 9, 0 - найкраща якість. Це не дуже добре, оскільки ми можемо не пам'ятати про це, щоразу зберігаючи фотографію. Додамо трохи чар, щоб все стандартизувати.
1 |
|
2 |
public function saveImage($savePath, $imageQuality="100") |
3 |
{
|
4 |
// *** Get extension
|
5 |
$extension = strrchr($savePath, '.'); |
6 |
$extension = strtolower($extension); |
7 |
|
8 |
switch($extension) |
9 |
{
|
10 |
case '.jpg': |
11 |
case '.jpeg': |
12 |
if (imagetypes() & IMG_JPG) { |
13 |
imagejpeg($this->imageResized, $savePath, $imageQuality); |
14 |
}
|
15 |
break; |
16 |
|
17 |
case '.gif': |
18 |
if (imagetypes() & IMG_GIF) { |
19 |
imagegif($this->imageResized, $savePath); |
20 |
}
|
21 |
break; |
22 |
|
23 |
case '.png': |
24 |
// *** Scale quality from 0-100 to 0-9
|
25 |
$scaleQuality = round(($imageQuality/100) * 9); |
26 |
|
27 |
// *** Invert quality setting as 0 is best, not 9
|
28 |
$invertScaleQuality = 9 - $scaleQuality; |
29 |
|
30 |
if (imagetypes() & IMG_PNG) { |
31 |
imagepng($this->imageResized, $savePath, $invertScaleQuality); |
32 |
}
|
33 |
break; |
34 |
|
35 |
// ... etc
|
36 |
|
37 |
default: |
38 |
// *** No extension - No save.
|
39 |
break; |
40 |
}
|
41 |
|
42 |
imagedestroy($this->imageResized); |
43 |
}
|
Тепер найкращий час, щоб знищити ресурс зображення, щоб звільнити трохи пам'яті. Якщо ви будете користуватися цим в вашому процесі, захопити і повернути результат збереженого зображення може бути хорошою ідеєю.
Висновок
Ось і все, друзі. Дякую, що пройшли даний урок, сподіваюсь, він був корисним для вас. Буду вдячним за ваші відгуки в коментарях нижче.