PHP-класс для ресайза изображений

PHP-класс для ресайза изображений

Внимание! Статья устарела. Появилась новая версия продукта. По ней также написана статья, создана документация и написаны демо. Эта статья может использоваться лишь в образовательных целях. Прочитать о новой версии продукта вы можете, перейдя по ссылке.

Задача масштабирования и обрезки изображений в интернете встречается очень часто. Это и социальные сети, и фотохостинги, где из загруженного изображения необходимо создать миниатюру. Однако каждый раз писать php-скрипты для ресайза под какую-то конкретную задачу или заказ – утомительно. Именно поэтому я написал свой класс на php, позволяющий очень удобно изменять размеры изображений и выполнять их обрезку. В этой статье я расскажи о его возможностях и попробую описать все методы масштабирования и кропа (обрезки) изображений. Если вам неинтересно или ненужно знать, как всё устроено и работает, то стоит обратить внимание лишь на использование класса, а сам исходник найдёте в конце статьи. Если же вы более любознательны и хотите полностью понимать, как именно происходит масштабирование, то читаем статью дальше.

Внимание! В статье не будут рассмотрены базовые принципы ООП-программирования. Статья о ресайзе изображений, поэтому, прежде я рекомендую ознакомиться с темой объектно-ориентированного программирования на PHP.

Функциональность

Прежде всего, необходимо определиться с тем, что должен уметь делать наш класс. Я долго изучал различные необходимые методы изменения размеров изображений, вспоминал, какие мне реально пригодились в жизни, и в конце концов мой класс обрёл следующую функциональность:

  • вписывание изображений в указанные рамки;
  • сжатие по указанной ширине;
  • сжатие по указанной высоте;
  • обрезка произвольной области изображения;
  • обрезка квадратной области изображения;
  • “умное” создание миниатюр.

Реализация

Итак, приступим к написанию каркаса самого класса и некоторых вспомогательных функций.

class acResizeImage
{
   private $image; //идентификатор самого изображения
   private $width; //исходная ширина
   private $height; //исходная высота
   private $type; //тип изображения (jpg, png, gif)
 
   function __construct($file)
   {
      if (@!file_exists($file)) exit("File does not exist");
      if(!$this->setType($file)) exit("File is not an image");
      $this->openImage($file);
      $this->setSize();
   }
 
   //функция проверяет, является ли файл изображением и устанавливает его тип
   private function setType($file)
   {
      $mime = mime_content_type($file);
      switch($mime)
      {
         case 'image/jpeg':
            $this->type = "jpg";
            return true;
         case 'image/png':
            $this->type = "png";
            return true;
         case 'image/gif':
            $this->type = "gif";
            return true;
         default:
            return false;
      }
   }
 
   //создаёт в зависимости от типа на основе файла идентификатор изображения
   private function openImage($file)
   {
      switch($this->type)
      {
         case 'jpg':
            $this->image = @imagecreatefromjpeg($file);
            break;
         case 'png':
            $this->image = @imagecreatefrompng($file);
            break;
         case 'gif':
            $this->image = @imagecreatefromgif($file);
            break;
         default:
            exit("File is not an image");
      }
   }
 
   //устанавливает размеры изображения
   private function setSize()
   {
      $this->width = imagesx($this->image);
      $this->height = imagesy($this->image);
   }
}

Конструктор в качестве аргумента принимает адрес файла, после чего проверяет его на существование. Функция setType($file) устанавливает в приватный член класса type тип изображения. Функция, разумеется, приватная, ибо извне класса использоваться не будет. Она узнаёт mime-тип файла и если файл является изображением, в поле type записывает его тип.

После этого в конструкторе вызывается функция openImage($file). Она создаёт на основе файла идентификатор изображения и записывает его в член класса — image. Ну и наконец, setSize() сохраняет размеры изображения в соответствующие поля. Все функции также приватны.

Теперь мы можем создать экземпляр класса acResizeImage:

$img = new acResizeImage('image.jpg');

Естественно, изображение image.jpg должно существовать.

После того, как создан экземпляр класса, мы можем им оперировать (изменять размеры изображения, обрезать его и т. д.). Итак, приступим.

Вписывание в рамки

Предположим, у нас есть фотоальбом и при клике на миниатюру в окошке должно появляться большое фото. Но ведь размеры окошка ограничены. Скажем, мы хотим, чтобы оно было не больше, чем 800×800. Значит, все загружаемые в фотоальбом фотографии должны быть вписаны в эти рамки. Разумеется, фото будет ужиматься пропорционально.
Вписывание в рамки
Для подобного ресайза следует вызвать следующую функцию класса:

$img->resize(800, 800);

Вот сам код функции resize() (добавьте его в класс):

function resize($width = false, $height = false)
{
   /**
   * В зависимости от типа ресайза, запишем в $newSize новые размеры изображения.
   */
   if(is_numeric($width) && is_numeric($height) && $width > 0 && $height > 0)
   {
      $newSize = $this->getSizeByFramework($width, $height);
   }
   else if(is_numeric($width) && $width > 0)
   {
      $newSize = $this->getSizeByWidth($width);
   }
   else if(is_numeric($height) && $height > 0)
   {
      $newSize = $this->getSizeByHeight($height);
   }
   else $newSize = array($this->width, $this->height);
   //создаём новое пустое изображение
   $newImage = imagecreatetruecolor($newSize[0], $newSize[1]);
   imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $newSize[0], $newSize[1], $this->width, $this->height);
   $this->image = $newImage;
   $this->setSize();
   return $this;
}

Заметьте, оба параметра, принимаемые функцией, необязательны. Позже объясню, зачем это нужно.

В массив $newSize мы записываем новые размеры изображения. В зависимости от параметров, переданных в функцию, новые размеры изображения возвращаются разными функциями:

  • getSizeByFramework($width, $height);
  • getSizeByWidth($width);
  • getSizeByHeight($height).

Если переданы оба параметра (и ширина, и высота), то будущие размеры картинки нам вернёт функция getSizeByFramework(). Это значит, что изображение необходимо вписать в указанные рамки.
Вот код вспомогательной приватной функции:

private function getSizeByFramework($width, $height)
{
   if($this->width <= $width && $this->height <= height) 
	return array($this->width, $this->height);
   if($this->width / $width > $this->height / $height)
   {
      $newSize[0] = $width;
      $newSize[1] = round($this->height * $width / $this->width);
   }
   else
   {
      $newSize[1] = $height;
      $newSize[0] = round($this->width * $height / $this->height);
   }
   return $newSize;
}

Функция возвращает пропорционально изменённые размеры вписанного в рамки изображения. Но вернёмся обратно к функции resize(). После того, как новые размеры картинки получены, на их основе создаётся новое пустое изображение $newImage, после чего вызывается функция:

imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $newSize[0], $newSize[1], $this->width, $this->height);

Она-то и копирует масштабированное изображение на новое ($newImage).

Внимание, существует ещё одна функция – imagecopyresized(). Она принимает те же параметры, но сжимает изображения без сглаживания. Я не рекомендую её использовать.

Ну и наконец, последние 3 строки перезаписывают исходное изображение на новое, сохраняют новые размеры и (!) возвращают текущий экземпляр класса (об этом мы поговорим позже):

$this->image = $newImage;
$this->setSize();
return $this;

Сжатие изображения по ширине

Вспомните аву «вконтакте»… Вы загружаете фото и оно всегда получается одинаковым по ширине, а высота разная, в зависимости от размеров исходного изображения. Вернёмся опять к функции resize() и посмотрим, что произойдёт, если в функцию передать только ширину:

$img->resize(800);

В данном случае новые размеры изображения нам вернёт функция getSizeByWidth($width), которая считает новые размеры изображения, сжимая картинку пропорционально по ширине.
Сжатие изображения по ширине
Добавьте в класс ещё одну приватную функцию:

private function getSizeByWidth($width)
{
   if($width >= $this->width) return array($this->width, $this->height);
   $newSize[0] = $width;
   $newSize[1] = round($this->height * $width / $this->width);
   return $newSize;
}

Дальше всё происходит так же, как и в случае с вписыванием в рамки — создаётся пустое изображение, на него копируется масштабированный оригинал, фото и размеры перезаписываются на новые, и в программу возвращается текущий объект.

Сжатие изображения по высоте

Задача аналогична предыдущей, но сжимается фото, подгоняясь уже под высоту:

$img->resize(false, 800);

Сжатие изображения по высоте
Размеры высчитываются следующей функцией, которую также не забываем добавить в класс:

private function getSizeByHeight($height)
{
   if($height >= $this->height) return array($this->width, $this->height);
   $newSize[1] = $height;
   $newSize[0] = round($this->width * $height / $this->height);
   return $newSize;
}

Дальнейшие действия в функции resize() аналогичны предыдущим двум случаям.

Crop (произвольная обрезка изображений)

Часто требуется вырезать из исходного изображения какую-либо область. Для этого реализуем функцию crop(). Принцип её работы прекрасно иллюстрирует следующее изображение:
Crop (произвольная обрезка изображений)
X0, Y0 – верхние левые координаты вырезаемой области. Width, height – её, соответственно, ширина и высота.

Используется наш кроп следующим образом:

$img->crop(30, 40, 500, 500);

В этом случае из исходного изображения будет вырезана область 500×500 px., начиная с координаты (30, 40).

Вот сама функция (также добавляем её в класс):

function crop($x0 = 0, $y0 = 0, $w = false, $h = false)
{
   if(!is_numeric($x0) || $x0 < 0 || $x0 >= $this->width) $x0 = 0;
   if(!is_numeric($y0) || $y0 < 0 || $y0 >= $this->height) $y0 = 0;
   if(!is_numeric($w) || $w  $this->width - $x0) $w = $this->width - $x0;
   if(!is_numeric($h) || $h  $this->height - $y0) $h = $this->height - $y0;
   return $this->cropSave($x0, $y0, $w, $h);
}

Как видно, все параметры необязательны. Так, если не передать точки начала обрезки, то это будет точка (0, 0). Передать координаты, находящиеся за пределами фото, также не получится. Если ширина и высота такие, что вырезаемая область выходит за пределы исходного изображения, вырезана будет максимально возможная область. Функция cropSave($x0, $y0, $w, $h) непосредственно вырезает необходимую область и возвращает текущий объект класса. Вот её код:

private function cropSave($x0, $y0, $w, $h)
{
   $newImage = imagecreatetruecolor($w, $h);
   imagecopyresampled($newImage, $this->image, 0, 0, $x0, $y0, $w, $h, $w, $h);
   $this->image = $newImage;
   $this->setSize();
   return $this;
}

Как и в случае с ресайзом, сохраняется новое изображение, размеры и возвращается текущий объект.

Обрезка квадратной области изображения

Вспомним обратно “вконтакте”. Ава у нас может быть прямоугольной, но миниатюры рядом с комментариями, разумеется, квадратные. Причём, какую именно область оригинальной авы отображать на миниатюре, выбираем мы сами. Поэтому, в свой класс я добавил частный случай кропа – квадратный кроп. Его-то сейчас и опишем.

Кстати! В следующих статьях мы обязательно опишем, как с помощью этого класса и JavaScript реализовать такой же принцип обрезки авы, как «вконтакте».
Обрезка квадратной области изображения
В отличие от прошлой функции, в эту можно передать лишь три параметра, которые, как и ранее, все необязательны.

Пример использования:

$img->cropSquare(40, 30, 400);

Первые два параметра – координаты верхнего левого угла вырезаемой области, а третий параметр – сторона самой квадратной области. Как ни странно, но квадратный кроп было значительно тяжелее реализовать. Представьте, что в функцию мы не передали ни одного параметра. Что же тогда делать? Я решил, что в данном случае нужно вырезать максимально большую квадратную область, находящуюся в центре изображения. Примерно вот так:
Обрезка максимальной квадратной области изображения
Если же указаны координаты верхнего левого угла, но не указана сторона квадратной области, то функция вырежет максимальную квадратную область из исходного изображения. Выглядит это так:
Обрезка квадратной области максимального размера
Это нюансы наложили некоторые сложности при написании функции квадратного кропа и теперь она выглядит так:

function cropSquare($x0 = false, $y0 = false, $size = false)
{
   if(!is_numeric($size) || $size width < $this->height)
      {
         $x0 = 0;
         if(!$size || $size > $this->width)
         {
            $size = $this->width;
            $y0 = round(($this->height - $size) / 2);
         }
         else $y0 = 0;
      }
      else
      {
         $y0 = 0;
         if(!$size || $size < $this->height)
         {
            $size = $this->height;
            $x0 = round(($this->width - $size) / 2);
         }
         else $x0 = 0;
      }
   }
   else
   {
      if(!is_numeric($x0) || $x0 = $this->width) $x0 = 0;
      if(!is_numeric($y0) || $y0 = $this->height) $y0 = 0;
      if(!$size || $this->width < $size + $x0) $size = $this->width - $x0;
      if(!$size || $this->height < $size + $y0) $size = $this->height - $y0;
   }
   return $this->cropSave($x0, $y0, $size, $size);
}

Обратите внимание, в конце вызывается уже известная нам функция сохранения обрезаемой области:

return $this->cropSave($x0, $y0, $size, $size);

Вот только ширина и высота вырезаемого изображения равны одной величине – стороне квадрата. Эта функция вернёт текущий экземпляр класса.

«Умное» создание миниатюр

Из заявленного функционала осталось описать лишь создание миниатюр. Попробую рассказать, в чём состоит сама задача. Итак, предположим, мы на своём сайте решили реализовать фотоальбом. Миниатюры в альбомах будут расположены в виде сетки. В чём же проблема? – спросите вы, — Мы ведь умеем уже масштабировать изображения! Тогда смотрите, как у нас будет выглядеть «таблица» миниатюр в альбомах, при классическом ресайзе:
Таблица миниатюр при классическом ресайзе
Не очень, правда? Изображения бывают разные – горизонтальные, вертикальные, квадратные и уж очень вычурные (например, 1000×150). Что только пользователь не загрузит!..

Да, конечно же, можно сжимать все фотографии для миниатюр, скажем, по ширине. В таком случае альбом станет намного миловиднее, но высота превьюшек будет разной. Всё-равно получается не очень. Что же сделаем мы? Мы будем пытаться «заполнить» область одной ячейки таблицы по-максимуму. Да, часть изображения видна не будет, но для миниатюр это не критично. Более того, наша функция будет ещё умнее – если фото слишком сильно вытянуто по одной из сторон, то мы попытаемся его масштабировать так, чтобы в область миниатюры попала наибольшая часть изображения. Рассмотрим всё на примерах:

Сразу договоримся – миниатюры будут отображаться в сетке 3×3, а размеры каждой ячейки соотноситься как 4×3. Пусть для примера размер ячейки, которую будем пытаться максимально заполнить превьюшкой, будет равен 120×90.

Понятно, что фото с пропорциями 4×3 и так заполнит всю ячейку. Этот пример рассматривать не будем. Рассмотрим сперва 2 фото: 16×9 и 9×16. Вот как это будет выглядеть:
Создание миниатюр изображений
Части картинок, которые изображены более тускло, видны не будут – они не поместятся в ячейку, зато вся ячейка будет полностью заполнена и лишь незначительная часть изображения останется “за кадром”. Нас это вполне устраивает.

Но что, если фото сильно вытянуто? Получится, что бОльшая часть изображения не видна, а это нас уже не устраивает, ведь часто не получится даже понять, что изображено на фотографии. Тут придётся идти на “уступки”, заполняя не полностью ячейку, зато сохраняя большую часть изображения на виду. Чуть позже мы поговорим о некотором коэффициенте. Сейчас же скажу, что этот коэффициент указывает, какая доля изображения может быть не видна на миниатюре. Скажем, если он равен двум, то в том случае, когда после сжатия по меньшей стороне, большая сторона более чем в 2 раза превышает соответствующую сторону ячейки, будем немного жертвовать “заполненностью” рамки, уменьшая большую сторону, до 2*сторона_ячейки px.

Покажу на примере:
Хороший и плохой примеры создания миниатюр
В первом случае меньше половины изображения на виду. А во втором случае небольшая область ячейки остаётся незаполненной, но этим мы жертвуем. Стоит заметить, что алгоритм создания миниатюр у меня написан таким образом, что ячейка не может быть заполненной менее чем на половину, кроме, разумеется, тех случаев, когда исходное изображение заведомо меньше ячейки таблицы.

Если картинка ещё более вытянута и одна из её сторон превышает соответствующую сторону рамки более чем в 2*коэффициент раз, то уменьшаем эту сторону ровно в 2 раза, но так, чтобы ячейка не оказалась заполненной менее чем на половину.

С помощью этого механизма таблица с фото, расположенная выше, будет выглядеть следующим образом:
Таблица миниатюр в фотоальбоме
Не правда ли – намного лучше?

Создаём миниатюру следующим образом:

$img->thumbnail(120, 90, 2);

Первые 2 параметра – размеры рамки, которую необходимо заполнить. Третий параметр – тот самый коэффициент (по-умолчанию равен двум).

Добавляем в класс функцию:

function thumbnail($width, $height, $c = 2)
{
   if(!is_numeric($width) || $width width;
   if(!is_numeric($height) || $height height;
   if(!is_numeric($c) || $c getSizeByThumbnail($width, $height, $c);
   $newImage = imagecreatetruecolor($newSize[0], $newSize[1]);
   imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $newSize[0], $newSize[1], $this->width, $this-height);
   $this->image = $newImage;
   $this->setSize();
   return $this;
}

В данной функции необязательным параметром является лишь коэффициент. Как и при ресайзе, но уже при помощи функции getSizeByThumbnail(), получаем новые размеры изображения. Дальнейшие действия абсолютно аналогичны.

Вот код функции getSizeByThumbnail():

private function getSizeByThumbnail($width, $height, $c)
{
   if($this->width height width, $this->height);
   $realW = $this->width;
   $realH = $this->height;
 
   $rotate = false;
   if($width / $realW  $width)
   {
      if($possH = $width / 2)
            {
               $newSize[1] = $limY;
               $newSize[0] = $realW * $limY / $realH;
            }
            else
            {
               $newSize[0] = $width / 2;
               $newSize[1] = $realH * $newSize[0] / $realW;
            }
         }
         else
         {
            $newSize[0] = $width / 2;
            $newSize[1] = $realH * $newSize[0] / $realW;
         }
      }
   }
   if($rotate)
   {
      $t = $newSize[0];
      $newSize[0] = $newSize[1];
      $newSize[1] = $t;
   }
   return $newSize;
}

Код очень сложно ветвится, так как возможно большое количество нюансов. В первый раз код ветвится, когда мы определяем, больше ли исходная ширина картинки, чем ширина ячейки, в которую её необходимо вписать. Но заметьте, в коде нигде не проверяется, не меньше ли высота изображения, чем высота рамки. Если бы я это написал, код удвоился бы вдвое, поэтому я прибегнул к некоторой хитрости.

Рассмотрим следующие строки кода:

if($width / $realW <= $height / $realH)
{
   $t = $realH;
   $realH = $realW;
   $realW = $t;
   $t = $width;
   $width = $height;
   $height = $t;
   $rotate = true;
}

Этот участок кода проверяет, по какой стороне необходимо производить сжатие.

$width, $height – размеры ячейки.

$realW, $realH – размеры исходного изображения.

$rotate – флаг, говорящий, что мы как бы повернули изображение.

В этом участке кода мы меняем понятия местами. Ширина становится длиной и наоборот. Напомню – это необходимо для того, чтобы размер кода не удвоился.

Также в конце функции есть ещё один блок, который меняет местами значения в возвращаемом массиве сторон:

if($rotate)
{
   $t = $newSize[0];
   $newSize[0] = $newSize[1];
   $newSize[1] = $t;
}

Пожалуй, стоит объяснить пару переменных из кода:

$limX – максимальная ширина нового изображения. Если она превышается, то изображение сжимается по вышеописанным правилам.

$limY – максимальная высота нового изображения.

$possH – высота нового изображения после пропорционального масштабирования по ширине.

Следует заметить, что изображение не обрезается под размеры ячейки. На самом деле все невидимые на странице фотоальбома части картинок остаются в целости и сохранности, просто на странице они ставятся на фон своих ячеек (div`ов) и центрируются. Получается, что области, которые нам угодно не видеть, дабы сохранить ровную сетку фотографий, остаются за кадром. Делается это так:

<div class="”photo”"></div>

И css-свойства:

.photo {
 width: 120px;
 height: 90px;
 background: #e5e5e5 url(image.jpg) no-repeat center center;
}

Вот и всё, все возможности нашего класса мы рассмотрели, однако до сих пор не знаем, как сохранить изображение…

Сохранение изображения

Для сохранения изображения используется функция save(). Она сохраняет изображение, идентификатор которого хранится в члене класса – image.

Вот так можно сохранить получившееся изображение:

$img->save('img/', 'image', 'jpg', false, 100);

Первый параметр указывает каталог, куда следует сохранить изображение. Второй – имя файла. Третий – его тип (jpg, png, gif). Четвёртый параметр отвечает за то, стоит ли перезаписывать файл, если файл с таким именем уже существует. Четвёртый параметр – качество jpg-изображения.

А вот и сам код функции:

function save($path = '', $fileName, $type = false, $rewrite = false, $quality = 100)
{
   if(trim($fileName) == '' || $this->image === false) return false;
   $type = strtolower($type);
   switch($type)
   {
      case false:
         $savePath = $path.trim($fileName).".".$this->type;
         switch($this->type)
         {
            case 'jpg':
               if(!$rewrite && @file_exists($savePath)) return false;
               if(!is_numeric($quality) || $quality < 0 || $quality > 100) $quality = 100;
               imagejpeg($this->image, $savePath, $quality);
               return $savePath;
            case 'png':
               if(!$rewrite && @file_exists($savePath)) return false;
               imagepng($this->image, $savePath);
               return $savePath;
            case 'gif':
               if(!$rewrite && @file_exists($savePath)) return false;
               imagegif($this->image, $savePath);
               return $savePath;
            default:
               return false;
         }
         break;
      case 'jpg':
         $savePath = $path.trim($fileName).".".$type;
         if(!$rewrite && @file_exists($savePath)) return false;
         if(!is_numeric($quality) || $quality < 0 || $quality > 100) $quality = 100;
         imagejpeg($this->image, $savePath, $quality);
         return $savePath;
      case 'png':
         $savePath = $path.trim($fileName).".".$type;
         if(!$rewrite && @file_exists($savePath)) return false;
         imagepng($this->image, $savePath);
         return $savePath;
      case 'gif':
         $savePath = $path.trim($fileName).".".$type;
         if(!$rewrite && @file_exists($savePath)) return false;
         imagegif($this->image, $savePath);
         return $savePath;
      default:
         return false;
   }
}

Если тип изображения передан не был, фото сохранится в исходном формате. Если переданный тип не является одним из трёх основных типов, то вернётся false. В случае успеха вернётся адрес нового изображения.

Использование

Часто может потребоваться сделать несколько изменений с исходным изображением. Это не возбраняется и делается следующим образом:

$img = new acResizeImage('image.jpg'); //создали экземпляр класса
$img->cropSquare(100, 200, 500); //вырезали квадратную область
$img->resize(200, 150); //масштабировали изображение, вписав его в рамки
$path = $img->save('img/', 'image', 'jpg', false, 100); //сохранили

Я же рекомендую делать короче и элегантнее – через цепочку вызовов. Теперь станет понятно, почему при ресайзе, кропе и создании миниатюр мы всегда возвращали текущий объект класса. Вот как это будет выглядеть:

$img = new acResizeImage('image.jpg'); //создали экземпляр класса
$path = $img->cropSquare(100, 200, 1500)->resize(200, 300)->save('img/', 'image', 'jpg', true, 50);

Не правда ли, красивее и удобнее?

Всё, весь функционал описан. На данный момент не могу ручаться, что класс работоспособен в абсолютно всех случаях, но в большинстве их – это так. Я его продолжу дорабатывать и добавлять, возможно, новый функционал. От вас жду отзывов, критики и предложений. Пользуйтесь на здоровье! 🙂

Исходник класса

  • Пока добавил в закладки бегло просмотрел, но одна из тем, которая меня интересует для будущего сайта. Вижу написано подробно и добротно, будут вопросы обращусь.
    Спасибо!

  • Уважаемый Андрей, здравствуйте !
    Хороший и подробный материал Вашего поста.
    Желательно выделить цветом ключевые слова.

    А теперь вопрос. По работе в панели сисадмина
    WordPress Вы не консультируете (меня интересует
    вопрос премодерации комментариев) ?
    Пыхтелкин.

  • Интересный класс. На досуге надо будет попробывать. Раньше я обычно исользовал для этих целей class.upload.php

  • Спасибо за отзывы. Жду конкретных результатов испытания класса)

    Виктор, главное — пожалуй использование. Например, $img->resize(400, 300) и т. д. А сам код в статье — для любознательных и любителей поковыряться в коде)
    На вопрос о премодерации ответит Вам Вадим. Я не сведущь в этой теме (в контексте движка, конечно. сам я писал премодерацию для одного сайта — сложностей там нету).

    Иван, посмотрел тот класс. Он безумно громоздкий, но и возможностей там очень много. Также в скором времени планирую внедрить некоторый функционал либо в этот класс, либо написать ещё один. Например, водяные знаки, лого на фото и т. д.

  • Viktor, по поводу премодерации сообщений.
    Мы используем плагин Antispam Bee. Он прячет поле для ввода комментария, не удаляя его из html-кода, и вставляет свое дополнительное поле в котором обычный человек пишет комментарий. А спам-бот заполнит и спрятанное поле и такой комментарий со 100%-ой вероятностью попадет в спам. Также у плагина есть опция «Помечать спам, но не удалять», так что можно не бояться за сохранность комментариев.

    Таким образом мы избавляемся от неудобных капч и прочего, позволяя пользователи просто отправить комментарий и вместе с тем избавляем себя от головной боли с модерацией комментариев.

  • Дмитрий

    Добрый день, первое что хочу сказать — это спасибо за отличный класс!
    Сам совсем недавно стал изучать php и еще очень плохо знаком с работой с изображениями, по этому возникло пару вопросов:
    1) Если вставляешь прозрачный png фон становится черным, не подскажите как это исправить? (На белый)
    2) Подскажите класс или способ загрузки изображений на сервер, сейчас использую вот этот способ, но он далек от идеала:
    copy($_FILES['image']['tmp_name'], '../temp/'.$_FILES['image']['name']);
    $img = new acResizeImage('../temp/'.$_FILES['image']['name']);
    $img->resize(200, 200);
    $path = $img->save('../img/', $img_id, 'jpg', tree, 90);
    unlink('../temp/'.$_FILES['image']['name']);

    Во первых не проверяю на то что загружают и какого размера, хотелось бы это исправить, заранее спасибо!

  • Дмитрий, да, есть проблема с чёрным цветом прозрачного некогда фона. Планирую это поправить во второй версии класса, которую обязательно опишу на блоге. Также на некоторых хостингах скорее всего воникнет проблема с определением типа изображения, ибо, как оказалось, функция mime_content_type устаревшая. Это также обязательно поправлю.

    При беглом юзании поиска, нашёл такое вот решение проблемы чёрного фона:
    Функцию openImage в классе немного меняем:
    private function openImage($file)
    {
    switch($this->type)
    {
    case 'jpg':
    $this->image = @imagecreatefromjpeg($file);
    break;
    case 'png':
    $this->image = @imagecreatefrompng($file);
    imagesavealpha($this->image, true);
    break;
    case 'gif':
    $this->image = @imagecreatefromgif($file);
    imagesavealpha($this->image, true);
    break;
    default:
    exit("File is not an image");
    }
    }

    Если верить источнику, то это создаст альфа канал у изображений в формате PNG и JPG.

    Дмитрий, в приведённом Вами примере, вы сохраняете изображение в формате jpg. А если сохранить в png?

    Также нашёл ещё инфу тут:
    http://www.php.ru/forum/viewtopic.php?t=30338
    И тут:
    http://lamp-dev.ru/php/gd2-save-transparent-png/

    Но пока сам не напишу, точно гарантировать ничего не смогу. Думаю, в скором времени немного допишу этот класс и результат обязательно окажется на блоге.

    По поводу Вашего кода:
    Вот эта строчка не нужна:
    copy($_FILES['image']['tmp_name'], '../temp/'.$_FILES['image']['name']);
    Ведь Вы копируете временный файл в другой файл, который вскоре удалите. А почему бы не использовать для дальнейших манипуляций не копию, а сам временный файл? Просто передавайте в конструктор класса временный файл:
    $img = new acResizeImage($_FILES['image']['tmp_name']);

    Размер картинок нас не интересует, всё будет корректно обработано в любом случае. А перед загрузкой стоит проверить, не содержит ли массив $_FILES ошибок и, собственно, сам файл.
    Про загрузку файлов на сервер лучше, чем тут я рассказать не смогу, а про ошибки написано тут.

    Пожалуй, скоро напишу дочерний класс для загрузки фото на сервер.

  • Дмитрий

    Андрей спасибо за развернутый ответ, сейчас буду пробовать, и добавлю минимальные проверки на загружаемые файлы.
    Бужу с нетерпением ждать допиленный класс, и новый класс для загрузки изображений.

  • Дмитрий

    Этот способ не помог:
    imagesavealpha($this->image, true); пробовал добавить еще и imageAlphaBlending($this->image, false); но тоже не помогло. Почитав инфу в инете вроде как этого не достаточно, это только сохраняет прозрачность, а эту под эту прозрачность в свое время надо подложить картинку с белым фоном.
    Также хотелось чтобы класс возвращал ошибки в форму, а не выводил их на отдельной странице.

  • $mime = getimagesize($file);
    switch($mime[‘mime’]) {

  • Юрий Лысюк

    $mime = getimagesize($file);
    switch($mime['mime']) {

    getimagesize() — ресурсоемкая функция.
    вместо getimagesize() и устаревшей mime_content_type можно использовать следующий вариант:
    1. Меняем немного функцию setType в классе
    private function setType($file) {
    $finfo = finfo_open(FILEINFO_MIME_TYPE); // возвращает mime-тип
    $mime = finfo_file($finfo, $file);
    finfo_close($finfo);

    switch ($mime) {
    case 'image/jpeg':
    $this->type = "jpg";
    return true;
    case 'image/png':
    $this->type = "png";
    return true;
    case 'image/gif':
    $this->type = "gif";
    return true;
    default:
    return false;
    }
    }

    2. В php.ini нужно раскомментировать строчку
    extension=php_fileinfo.dll
    Этот модуль включен по умолчанию начиная с PHP 5.3.0
    3. Информацию брал здесь

  • Юрий Лысюк, спасибо большое. Обязательно попробую и в случае успеха добавлю в класс.

  • Отличный пост, написан грамотно, любому будет понятно. Данным классом несколько раз пользовался при создании фотогалерей. Хотя для своего блога у меня есть свои хитрости:)

  • sslab

    а как быть в случае, когда создаем экземпляр класса не с локальным файлом, а с так сказать удаленным
    $url = «http://qqq.qq/qwerty.jpg»;
    $img = new acResizeImage($url);
    ?
    В данном случае он просто пишет, что «File does not exist», что логично, глядя на то, как идет проверка.
    Как можно обойтись без загрузки изображения на сервер?

  • sslab

    Разобрался. Внес небольшие изменения. Спасибо огромное за функционал, ибо уже сколько раз сталкивался с подобными задачами, и каждый раз все не удосужился привести все к единому классу…

  • Да, действительно, не подумал)
    Функция file_exists проверяет на существование лишь локальные файлы.
    +1 вещь, которая будет добавлена в новую версию класса, который уже пишется. Спасибо большое.

    Если не секрет, каким образом решили проблему? 😉

  • Нашёл вот такой способ проверки удалённого файла на существование:
    http://habrahabr.ru/post/50846/

    Понравился, буду его юзать.

  • Армен

    Ваш скрипт не заработал, когда нет файла ппишет нет файла, когда файл есть пустота!
    В чем дело?
    Можете ответить почтой!

  • Maxim

    Спасибо, все работает. Благодаря Вам сумел разобраться со своим кодом и много нового почерпнул для себя. Хотелось бы узнать, когда примерно можно ожидать статьи о нанесении лого, водяных знаков? И работа с классом через JavaScript.

  • Maxim, пожалуйста. Думаю, скоро будет вторая версия класса. Через JS классы, написанные на PHP использовать нельзя. А что конкретно вы хотите сделать?

  • Maxim

    Само собой на прямую работать нельзя, но использовать JS в качестве средства получения данных для обрезки(например, в ф-ии Crop координаты слева и сверху) для последующей передачи скрипту php можно. Разве нет:)

  • Да, понял, о чём вы:) Я так понимаю, говорите вы об аналоге выбора миниатюры из авы, как вконтакте. Хотели это реализовать на одном своём проекте. Думается, можно будет написать сразу после выхода статьи с новым классом ещё и урок, в котором опишем и этот механизм.

    Новая версия класса, к слову, готова, но есть небольшая загвоздка с прозрачностью при нанесении лого. Как только разберусь с этим нюансом, сразу же будет релиз )

  • Сергей Красин

    Здравствуйте, Андрей.
    большое спасибо за скрипт. Хорошая работа )

    Единственное не могу понять как сделать кроп изображения автоматом по центру. например я загружаю изображение размером 1000х600 , а хочу на выходе получить изображение 650х300 (т.е. чтоб верхние края немного обрезались сверху и снизу), но в итоге получаю изображение с размером 650х390 (т.е. получается просто обычный пропорциональный резайз).

    изспользовал ф-ию thumbnail:

    $img = new acResizeImage(‘images/items/’.$orig_filename); //создали экземпляр класса
    $img->thumbnail(650, 300, 2); // уменьшаем до размеров
    $new_filename = $data[‘orig_filename’][‘raw_name’].’_nf’;
    $path = $img->save(‘images/items/’, $new_filename, ‘jpg’, true, 94);

    или может я допускаю где ошибку ?
    помогите разобраться, пожалуйста

    Заранее спасибо

  • Сергей Красин, функция thumbnail нужна для создания красивых и ровных превьюшек.
    Не до конца понял суть вашего вопроса. Вам нужно сперва ресайзить изображение, а потом сделать кроп, или всё же кроп делается сразу же, без ресайза?

  • Сергей Красин

    например у меня есть болшое изображение и я хочу сделать превьюшку строго размера 650х300
    Чтоб изображение само обрезалось грамотно. (т.е. суть как у cropSquare — брать наибольшую область, но только с пропрциями 1 к 1, а 650 к 300 )

  • Сергей

    Спасибо автору. $img->thumbnail — почему-то не работает. Пытался, например, сделать миниатюру, чтобы была строго определенной ширины и высоты (92×130 или 130×90) из изображения 485×300, не получается

  • Сергей

    Почти такая же проблема как у Сергей Красин.

  • Сергей

    Еще ошибка вылезла:
    Warning: Cannot modify header information — headers already sent by (output started at L:\home\localhost\www\lib\ac_image_class.php:1) in L:\home\localhost\www\myfunctions.php on line 967

    в 967 строке у меня: header(«Location: http://«.$_SERVER[‘HTTP_HOST’].$back_url);

    Значит в вашем классе где-то вывод заголовков, а где не пойму. Заранее спасибо.

  • Сергей

    В исходнике нет завершающего ?>, но ошибка все равно выходит. Помогите.

  • Сергей

    Разобрался, это криворукий автор сохранил исходник в хрен знает, наверное в Worde 95.

  • Сергей, с криворукостью поаккуратнее! Да, закрывающие ?> отсутствуют, но это не является ошибкой. Исходник сохранён в блокноте.

    Насчёт заголовков — ищите у себя, ибо ни одного заголовка не выводится в классе.

    Насчёт превьюшки… Вы привели пример, в котором не получается сделать желаемое. Хотел бы узнать, какой размер получается у картинки. Спасибо.

  • Maxim

    Андрей, а как уменьшить анимацию gif не теряя самой анимации?

  • Сергей

    Насчёт заголовков — ищите у себя, ибо ни одного заголовка не выводится в классе.

    Я пересохранил без BOM и нормально все заработало, а ваш исходник сохранен с BOM.

    Хотел бы узнать, какой размер получается у картинки

    Что то вроде 92×160, пробовал и коэффициенты ставить разные, не получилось. Вышел из положения так:

    $img = new acResizeImage($target); //создали экземпляр класса

    $thumb_width = 92;
    $thumb_height = 130;

    $ratio=$thumb_width / $thumb_height;

    $size = getimagesize($target);

    if ($size === FALSE)
    return false;

    $width=$size[0];
    $height=$size[1];

    $new_height=round($width / $ratio);

    if ($new_heightcrop(0, $y, $width, $new_height);
    }
    else if ($new_height>$height)
    {
    $new_width=round($height * $ratio);
    $x=round(($width-$new_width) / 2);

    $img->crop($x, 0, $new_width, $height);
    }

    $img->resize($thumb_width, $thumb_height);

    $path = $img->save($path_to_poster_directory.’92×130/’, $movie_img, ‘jpg’, true, 100);

  • Петр

    С нетерпением жду новой версии, хотя и существующая меня вполне устраивает

  • Leonid

    Спс автору. Почему-то jpg после обработки увеличиваются в весе файла почти в 2 раза. Как исправить?

  • Praesens

    Пробовал сделать обрезку изображений, но выдаёт ошибку: Parse error: syntax error, unexpected T_VARIABLE. Буду признателен, если сможете помочь.

  • Praesens

    Ругается на строчку:

    81
    
    if(!is_numeric($w) || $w <= 0 || $w > $this->width - $x0) $w = $this->width - $x0;
  • Игорь

    $new_height=round($width / $ratio);

    if ($new_heightcrop(0, $y, $width, $new_height);
    }
    else if ($new_height>$height)

    Тут потеряна часть кода, вы не могли бы скинуть еще раз? Очень нужна такая функция.

    Загорцев Андрей:
    Есть такая проблема и нужна такая возможность:
    Допустим я хочу чтобы картинки на сайте сохранялись всегда размерами 150х100.
    И какую бы я большую фото не грузил, надо чтобы она сжималась по понятию «минимальная ширина 150рх, минимальная высота 100рх» (а у вас функция «resize» работает по понятию «максимальная ширина 150рх, максимальная высота 100рх).
    Ну вот, получаются определенные размеры фото, а потом надо вырезать из середины(!) фото определенные размеры, в данном случае «150х100».
    Тогда все фото на сайте (например список товаров в каталоге) будут абсолютно одинаковые, и лишь потеряются слегка края фото — что возмещается тем, что кликнув на фото открывается побольше — полностью (данный функционал у вас есть — за это спасибо).

    Вы не могли бы сделать такое решение?

  • Игорь

    Я когда то пытался сделать такую функцию, но я запутался окончательно и забил на нее и начал использовать вашу.
    Но у меня типа можно было указать:
    Макс. ширину
    либо Новую ширину
    либо Мин. ширину
    и так же Высоту.
    и можно было указать как угодно, напр. Макс. ширину и Новую высоту.
    и типа все грамотно считалось, но в конце концов когда надо было еще чота добавить, я афигел уже =) у меня нет таких знаний, по этому не могу продумать =)

  • Sergey

    Просили скинуть

    $ratio=$thumb_width / $thumb_height;

    $size = getimagesize($target);

    if ($size === FALSE)
    return false;

    $width=$size[0];
    $height=$size[1];

    $new_height=round($width / $ratio);

    if ($new_heightcrop(0, $y, $width, $new_height);
    }
    else if ($new_height>$height)
    {
    $new_width=round($height * $ratio);
    $x=round(($width-$new_width) / 2);

    $img->crop($x, 0, $new_width, $height);
    }
    $img->resize($thumb_width, $thumb_height);

  • Sergey
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    	$ratio=$thumb_width / $thumb_height;
     
    	$size = getimagesize($target);
     
    	if ($size === FALSE)
    		return false;
     
    	$width=$size[0];
    	$height=$size[1];
     
    	$new_height=round($width / $ratio);
     
    	if ($new_heightcrop(0, $y, $width, $new_height);
    	}
    	else if ($new_height&gt;$height)
    	{
    		$new_width=round($height * $ratio);
    		$x=round(($width-$new_width) / 2);
     
    		$img-&gt;crop($x, 0, $new_width, $height);
    	}
    	$img-&gt;resize($thumb_width, $thumb_height);
  • Алексей

    Андрей, у меня есть такой код :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    		if (($this-&gt;image_size['height'] image_size['width'] image_size['height'] / $max_height) &gt; ($this-&gt;image_size['width'] / $max_width))
    		{
    			$this-&gt;thumb_height	= $max_height;
    			$this-&gt;thumb_width	= round($max_width * (($this-&gt;image_size['width'] / $max_width) / ($this-&gt;image_size['height'] / $max_height)));
    		}
    		else
    		{
    			$this-&gt;thumb_height	= round($max_height * (($this-&gt;image_size['height'] / $max_height) / ($this-&gt;image_size['width'] / $max_width)));
    			$this-&gt;thumb_width	= $max_width;
    		}

    Этот код создает миниатюру пропорционально либо по ширине либо по высоте. А мне нужно что бы миниатюры обрезались до указанной ширины и высоты. Как такое можно реализовать в этом коде?

    Спасибо)))…

  • Если я правильно понял Вас, то данный функционал мы уже реализовали и протестировали в новой версии класса, которая уже очень скоро будет. Там появится возможность вырезать превью указанного размера, а также — пропорций)

  • Алексей

    Андрей, а скажите кода выйдет данный класс? Или что можно сделать с тем что у меня есть?

    • Алексей, класс уже написан, как и статья. Осталось внести минимальные правки, сделать документацию и подготовить иллюстрации. Думаю, это будет среда-четверг.

  • Алексей

    Спасибо))

  • Андрей

    Есть предложение, как добавить лого(копирайт) на фотографию.

    Меняем функцию resize на:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    
    function resize($width = false, $height = false, $logo = false)
    	{
    		/**
    		* В зависимости от типа ресайза, запишем в $newSize новые размеры изображения.
    		*/
    		if(is_numeric($width) &amp;&amp; is_numeric($height) &amp;&amp; $width &gt; 0 &amp;&amp; $height &gt; 0)
    		{
    			$newSize = $this-&gt;getSizeByFramework($width, $height);
    		}
    		else if(is_numeric($width) &amp;&amp; $width &gt; 0)
    		{
    			$newSize = $this-&gt;getSizeByWidth($width);
    		}
    		else if(is_numeric($height) &amp;&amp; $height &gt; 0)
    		{
    			$newSize = $this-&gt;getSizeByHeight($height);
    		}
    		else $newSize = array($this-&gt;width, $this-&gt;height);
    		$newImage = imagecreatetruecolor($newSize[0], $newSize[1]);
    		//завернуть в отдельную функцию, сжимающую изображение поэтапно
    		imagecopyresampled($newImage, $this-&gt;image, 0, 0, 0, 0, $newSize[0], $newSize[1], $this-&gt;width, $this-&gt;height);
     
    		// Наложение логотипа НАЧАЛО
    			if ($logo == 'logo') {	
    		// изображение
    		//$image = imagecreatefromjpeg($newImage);
     
    		// ширина изображения
    		$image_width = imagesx($newImage);
     
    		// высота изображения
    		$image_height = imagesy($newImage);
     
    		// логотип, такой путь верен, если лого лежит в одной папке с данным файлом
    		$logo = imagecreatefrompng('logo.png'); 
     
    		// ширина логотипа
    		$logo_width = imagesx($logo);
     
    		// высота логотипа
    		$logo_height = imagesy($logo);
     
    		// Размещение в правом нижнем углу с отступом в 10 пикселей
    		$image_x = $image_width - $logo_width - 10;
    		$image_y = $image_height - $logo_height - 10;
     
    		imagecopy($newImage, $logo, $image_x, $image_y, 0, 0, $logo_width, $logo_height);
     
    		// Освобождаем память изображения-логотипа
    		imagedestroy($logo);
    		}
    		// Наложение логотипа КОНЕЦ
     
    		$this-&gt;image = $newImage;
    		$this-&gt;setSize();
    		//return $this-&gt;width;
    		return $this;
    	}
  • Андрей

    Прошу прощения, забыл добавить главное )))

    Для того, чтобы лого появилось на картинке:

    $img->resize(800, 800,logo); // в месте вывода добавляем logo

  • Андрей, лого уже реализовано в классе и весьма гибко)
    В вашем, случае, как мне показалось при беглом просмотре, прозрачность лого сохранена не будет.

    К сожалению, релиз новой версии класса чуток задержался, но будет уже очень скоро, правда)

  • Написана новая версия продукта. Эту тему, как и сам класс, можно считать устаревшей.
    Новая статья по ссылке:
    http://true-coder.ru/php/toolkit-dlya-resajza-i-kropa-izobrazhenij-na-php.html

  • Andrey

    Добрый день знатоки!

    Ситуация такова, мне нужно сперва до определенной ширины пропорционально уменьшить входящее изображение, а потом отрезать от него фрагмент с левого верхнего угла. По отдельности imagecopyresampled — выполняет первую задачу, а imagecopy — вторую, но если обе функции в деле, то выполняется только вторая, как это можно решить?

    [PHP]

    function resize(){
    $ratio = $width / $this->getWidth();
    $height = $this->getheight() * $ratio;

    $width = 148;
    $height = 110;

    $w = $this->getWidth();
    $h = $this->getHeight();

    imagecopyresampled($new_image, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
    imagecopy( $new_image, $this->image, 0, 0, $width, $height, $w, $h);

    $this->image = $new_image;
    return $this->image;

    }

    function getWidth() {
    return imagesx($this->image);
    }

    function getHeight() {
    return imagesy($this->image);
    }

    [/PHP]

  • Павел Семенов

    Вот эти 2 функции:

    function resize($width = false, $height = false)
    function cropSquare($x0 = false, $y0 = false, $size = false)

    я находил в НЕТе и использовал отдельно. А вот собрать их в один класс, это хорошая идея. удобно. спасибо.

  • Вадим

    Спасибо огромное))) мне было лень писать все это, а Вы крепко помогли)) Спасибо =))))