() translation by (you can also view the original English article)
Всегда хотелось универсального, простого способа изменения размера изображений в PHP? Именно для этого и предназначены классы PHP - функции многократного использования, назначенные выполнять грязную работу за кулисами. Мы собираемся узнать, как создать собственный класс, хорошо построенный и пластичный. Изменение размера должно быть простым. Насколько простым? В три шага!
Введение
Чтобы дать вам быстрый обзор того, чего мы пытаемся достичь с помощью нашего класса, он должен быть:
- Прост в использовании
- Независимый от формата. То есть, открывает, изменяет размер и сохраняет несколько различных форматов изображений.
- Интеллектуальный размер - отсутствие искажения изображения!
Примечание. Это не учебное пособие о том, как создавать классы и объекты, и, если это вы сами умеете, нет необходимости следовать этому руководству.
Нам есть, чем заняться - Давайте начнём.
Шаг 1 Подготовка
Начнём с простого. В рабочей папке создайте два файла: один называется index.php, другой resize-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. Откройте файл 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 (более конкретно, PHP GD Library), чтобы PHP мог читать изображение. Мы делаем это с помощью настраиваемого метода '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 Library вызывает для открытия изображения. Это легко достигается сравнением расширения файлов с аргументом 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 метод, который вызовем, чтобы выполнить изменение размера; поэтому имеет смысл задать ширину и высоту, а также информацию о том, как мы хотим изменить размер изображения. Давайте поговорим об этом минутку. Должны быть сценарии, в которых вы хотели бы изменить размер изображения до нужного. Отлично, давайте включим это. Но будут моменты, когда вам нужно изменить размеры сотен изображений, и каждое изображение имеет другое соотношение сторон - подумайте о портретных изображениях. Изменение их размеров до точного приведёт к серьёзным искажениям. Если мы посмотрим на варианты, чтобы предотвратить искажения, мы можем:
- Изменить размер как можно ближе к новым размерам изображения, сохраняя пропорции.
- Изменить размер как можно ближе к новым размерам изображения и обрезать остаток.
Оба варианта жизнеспособны, в зависимости от ваших потребностей.
Ага. Мы попытаемся обработать все вышеперечисленное. В итоге мы собираемся привести опции к:
- Изменить размер точно по ширине/высоте. (точно)
- Изменить размер по ширине - будет установлена точная ширина, высота будет меняться в соответствии с форматным соотношением. (пейзаж)
- Изменить размер по высоте - например, Resize by Width, но высота будет установлена и ширина будет изменена динамически. (портрет)
- Автоматическое определение опций 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 Дерево решений
Чем больше работы вы сделаете сейчас, тем меньше нужно будет делать, когда придётся менять размер. Этот метод выбирает маршрут с целью получить оптимальную ширину и высоту в зависимости от ваших параметров. Он вызовет соответствующий метод, который мы создадим на следующем шаге.
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 |
}
|
Теперь самое подходящее время уничтожить ресурс изображения, чтобы освободить часть памяти. Если вы будете пользоваться этим в производстве, может быть хорошей идеей захватить и вернуть результат сохранённого изображения.
Заключение
Ну вот и всё, ребята. Благодарю за то, что вы следовали уроку. Надеюсь, вы сочтёте это полезным. Буду признателен за ваши отзывы в комментариях.