<?php
/**
* This library is maintained by Gumlet
* https://github.com/gumlet/php-image-resize
* modifed for InstantCMS
*/
class cmsImages {
const CROPTOP = 1;
const CROPCENTRE = 2;
const CROPCENTER = 2;
const CROPBOTTOM = 3;
const CROPLEFT = 4;
const CROPRIGHT = 5;
const CROPTOPCENTER = 6;
public $quality_jpg = 90;
public $quality_webp = 90;
public $quality_png = 6;
public $quality_truecolor = true;
public $gamma_correct = false;
public $interlace = 1;
public $gif_to_gif = false;
public $source_type;
protected $source_file_path;
protected $source_image;
protected $original_w = 0;
protected $original_h = 0;
protected $dest_x = 0;
protected $dest_y = 0;
protected $source_x = 0;
protected $source_y = 0;
protected $dest_w = 0;
protected $dest_h = 0;
protected $source_w = 0;
protected $source_h = 0;
protected $source_info;
protected $filters = [];
protected $dest_dir = '';
/**
* Устанавливает директорию назначения
* @param string $dest_dir
* @return $this
*/
public function setDestinationDir($dest_dir) {
$this->dest_dir = $dest_dir;
return $this;
}
/**
* Экземпляр класса из base64 строки изображения
*
* @param string $image_data
* @return \cmsImages
*/
public static function createFromString($image_data) {
return new self('data://application/octet-stream;base64,' . base64_encode($image_data));
}
/**
* Выполняет ресайз изображения согласно параметров пресета
*
* @param array $preset
* @param string $file_name
* @param integer $user_id
* @return string Полный путь к полученному изображению
*/
public function resizeByPreset($preset, $file_name = null, $user_id = null) {
$image_type = $this->source_type;
$dest_ext = $this->getSourceExt();
if($this->dest_dir){
$dest_dir = $this->dest_dir;
} else {
$dest_dir = files_get_upload_dir($user_id === null ? cmsUser::get('id') : $user_id);
}
if(!empty($preset['convert_format'])){
$image_type = $this->getImageType($preset['convert_format']);
$dest_ext = $preset['convert_format'];
if(!empty($preset['gif_to_gif']) && $this->source_type == IMAGETYPE_GIF){
$this->gif_to_gif = true;
$image_type = $this->source_type;
$dest_ext = $this->getSourceExt();
}
} else {
$this->gif_to_gif = true;
}
if($file_name){
$dest_name = files_sanitize_name($file_name.($this->dest_dir ? '' : ' '.$preset['name']));
} else {
$dest_name = substr(md5(microtime(true)), 0, 8);
}
$dest_file = $dest_dir . $dest_name . '.' .$dest_ext;
while (file_exists($dest_file)) {
$dest_file = $dest_dir . $dest_name . '_' . substr(md5(microtime(true)), 0, 2) . '.' .$dest_ext;
}
if(!empty($preset['is_square'])){
$this->crop($preset['width'], $preset['height'], !empty($preset['allow_enlarge']), $preset['crop_position']);
} else {
if(!$preset['width'] || !$preset['height']){
if(!$preset['width']){
$this->resizeToHeight($preset['height'], !empty($preset['allow_enlarge']));
} else {
$this->resizeToWidth($preset['width'], !empty($preset['allow_enlarge']));
}
} else {
$this->resizeToBestFit($preset['width'], $preset['height'], !empty($preset['allow_enlarge']));
}
}
if (!empty($preset['is_watermark']) && $preset['wm_image']){
$this->addFilter(function ($dest_image, $cmsImages) use ($preset) {
$wm_file = cmsConfig::get('upload_path').$preset['wm_image']['original'];
$wm_size = getimagesize($wm_file);
if ($wm_size === false) { return false; }
$wm_width = $wm_size[0];
$wm_height = $wm_size[1];
$wm_format = strtolower(substr($wm_size['mime'], strpos($wm_size['mime'], '/' ) + 1));
$wm_func = 'imagecreatefrom'.$wm_format;
if (!function_exists($wm_func)) { return false; }
$wm = $wm_func($wm_file);
$img_width = imagesx($dest_image);
$img_height = imagesy($dest_image);
$wm_margin = intval($preset['wm_margin']);
switch($preset['wm_origin']){
case 'top-left':
$x = $wm_margin;
$y = $wm_margin;
break;
case 'top-center':
$x = ($img_width/2) - ($wm_width/2);
$y = $wm_margin;
break;
case 'top-right':
$x = ($img_width - $wm_width - $wm_margin);
$y = $wm_margin;
break;
case 'left':
$x = $wm_margin;
$y = ($img_height/2) - ($wm_height/2);
break;
case 'center':
$x = ($img_width/2) - ($wm_width/2);
$y = ($img_height/2) - ($wm_height/2);
break;
case 'right':
$x = ($img_width - $wm_width - $wm_margin);
$y = ($img_height/2) - ($wm_height/2);
break;
case 'bottom-left':
$x = $wm_margin;
$y = ($img_height - $wm_height - $wm_margin);
break;
case 'bottom':
$x = ($img_width/2) - ($wm_width/2);
$y = ($img_height - $wm_height - $wm_margin);
break;
case 'bottom-right':
$x = ($img_width - $wm_width - $wm_margin);
$y = ($img_height - $wm_height - $wm_margin);
break;
}
imagealphablending($dest_image, true);
imagecopy($dest_image, $wm, (int)$x, (int)$y, 0, 0, $wm_width, $wm_height);
imagedestroy($wm);
});
}
$this->gamma(!empty($preset['gamma_correct']));
$this->save($dest_file, $image_type, $preset['quality']);
$this->filters = [];
return str_replace(cmsConfig::get('upload_path'), '', $dest_file);
}
/**
* Возвращает расширение исходного файла
* @return string
*/
public function getSourceExt() {
switch ($this->source_type) {
case IMAGETYPE_GIF:
return 'gif';
case IMAGETYPE_JPEG:
return 'jpg';
case IMAGETYPE_PNG:
return 'png';
case IMAGETYPE_WEBP:
return 'webp';
default:
return '';
}
}
public function getImageType($ext) {
switch ($ext) {
case 'gif':
return IMAGETYPE_GIF;
case 'jpg':
return IMAGETYPE_JPEG;
case 'png':
return IMAGETYPE_PNG;
case 'webp':
return IMAGETYPE_WEBP;
default:
return 'jpg';
}
}
/**
* Добавляет функцию фильтра для использования перед сохранением в файл
*
* @param callable $filter
* @return $this
*/
public function addFilter(callable $filter) {
$this->filters[] = $filter;
return $this;
}
/**
* Применяет фильтры
*
* @param resource $image Идентификатор ресурса изображения
*/
protected function applyFilter($image) {
foreach ($this->filters as $function) {
if(is_callable($function)){
call_user_func_array($function, [$image, $this]);
}
}
}
/**
* Загружает источник изображения и его свойства в экземпляр объекта
*
* @param string $filename
*/
public function __construct($filename) {
if (!defined('IMAGETYPE_WEBP')) {
define('IMAGETYPE_WEBP', 18);
}
if ($filename === null || empty($filename) || (substr($filename, 0, 5) !== 'data:' && !is_file($filename))) {
throw new Exception('File does not exist');
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (strstr(finfo_file($finfo, $filename), 'image') === false) {
throw new Exception('Unsupported file type');
}
if (!$image_info = getimagesize($filename, $this->source_info)) {
$image_info = getimagesize($filename);
}
if (!$image_info) {
throw new Exception('Could not read file');
}
$this->original_w = $image_info[0];
$this->original_h = $image_info[1];
$this->source_type = $image_info[2];
switch ($this->source_type) {
case IMAGETYPE_GIF:
$this->source_image = imagecreatefromgif($filename);
break;
case IMAGETYPE_JPEG:
$this->source_image = $this->imageCreateJpegfromExif($filename);
// возможно изменили размеры, переустанавливаем
$this->original_w = imagesx($this->source_image);
$this->original_h = imagesy($this->source_image);
break;
case IMAGETYPE_PNG:
$this->source_image = imagecreatefrompng($filename);
break;
case IMAGETYPE_WEBP:
$this->source_image = imagecreatefromwebp($filename);
$this->original_w = imagesx($this->source_image);
$this->original_h = imagesy($this->source_image);
break;
default:
throw new Exception('Unsupported image type');
}
if (!$this->source_image) {
throw new Exception('Could not load image');
}
$this->source_file_path = $filename;
finfo_close($finfo);
return $this->resize($this->getSourceWidth(), $this->getSourceHeight());
}
public function imageCreateJpegfromExif($filename) {
$img = imagecreatefromjpeg($filename);
if (!function_exists('exif_read_data') || !isset($this->source_info['APP1']) || strpos($this->source_info['APP1'], 'Exif') !== 0) {
return $img;
}
try {
$exif = @exif_read_data($filename);
} catch (Exception $e) {
$exif = null;
}
if (!$exif || empty($exif['Orientation'])) {
return $img;
}
$orientation = $exif['Orientation'];
if ($orientation === 6 || $orientation === 5) {
$img = imagerotate($img, 270, null);
} elseif ($orientation === 3 || $orientation === 4) {
$img = imagerotate($img, 180, null);
} elseif ($orientation === 8 || $orientation === 7) {
$img = imagerotate($img, 90, null);
}
if ($orientation === 5 || $orientation === 4 || $orientation === 7) {
imageflip($img, IMG_FLIP_HORIZONTAL);
}
return $img;
}
public function saveGif($filename, $quality = null, $permissions = null, $exact_size = false) {
$im = new Imagick($this->source_file_path);
if (!empty($exact_size) && is_array($exact_size)) {
$width = $exact_size[0];
$height = $exact_size[1];
} else {
$width = $this->getDestWidth();
$height = $this->getDestHeight();
}
$im = $im->coalesceImages();
foreach ($im as $frame) {
$frame->cropImage($width, $height, $this->source_x, $this->source_y);
$frame->thumbnailImage($width, $height);
$frame->setImagePage($width, $height, 0, 0);
}
$im = $im->deconstructImages();
if ($im->writeImages($filename, true) && $permissions) {
chmod($filename, $permissions);
}
$im->clear();
$im->destroy();
return $this;
}
/**
* Сохраняет новое изображение
*
* @param string $filename Имя файла для сохранения
* @param integer $image_type Тип изображения
* @param integer $quality Качество
* @param integer $permissions Права доступа
* @param array $exact_size Массив размеров
* @return \cmsImages
*/
public function save($filename, $image_type = null, $quality = null, $permissions = null, $exact_size = false) {
if(!$image_type){
$image_type = $this->source_type;
}
$quality = is_numeric($quality) ? abs((int)$quality) : null;
switch ($image_type) {
case IMAGETYPE_GIF:
if ($this->gif_to_gif && extension_loaded('imagick')) {
return $this->saveGif($filename, $quality, $permissions, $exact_size);
}
if (!empty($exact_size) && is_array($exact_size)) {
$dest_image = imagecreatetruecolor($exact_size[0], $exact_size[1]);
} else {
$dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
}
$background = imagecolorallocatealpha($dest_image, 255, 255, 255, 1);
imagecolortransparent($dest_image, $background);
imagefill($dest_image, 0, 0, $background);
imagesavealpha($dest_image, true);
break;
case IMAGETYPE_JPEG:
if (!empty($exact_size) && is_array($exact_size)) {
$dest_image = imagecreatetruecolor($exact_size[0], $exact_size[1]);
$background = imagecolorallocate($dest_image, 255, 255, 255);
imagefilledrectangle($dest_image, 0, 0, $exact_size[0], $exact_size[1], $background);
} else {
$dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
$background = imagecolorallocate($dest_image, 255, 255, 255);
imagefilledrectangle($dest_image, 0, 0, $this->getDestWidth(), $this->getDestHeight(), $background);
}
break;
case IMAGETYPE_WEBP:
if (!empty($exact_size) && is_array($exact_size)) {
$dest_image = imagecreatetruecolor($exact_size[0], $exact_size[1]);
$background = imagecolorallocate($dest_image, 255, 255, 255);
imagefilledrectangle($dest_image, 0, 0, $exact_size[0], $exact_size[1], $background);
} else {
$dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
$background = imagecolorallocate($dest_image, 255, 255, 255);
imagefilledrectangle($dest_image, 0, 0, $this->getDestWidth(), $this->getDestHeight(), $background);
}
imagealphablending($dest_image, false);
imagesavealpha($dest_image, true);
break;
case IMAGETYPE_PNG:
if (!$this->quality_truecolor || !imageistruecolor($this->source_image)) {
if (!empty($exact_size) && is_array($exact_size)) {
$dest_image = imagecreate($exact_size[0], $exact_size[1]);
} else {
$dest_image = imagecreate($this->getDestWidth(), $this->getDestHeight());
}
} else {
if (!empty($exact_size) && is_array($exact_size)) {
$dest_image = imagecreatetruecolor($exact_size[0], $exact_size[1]);
} else {
$dest_image = imagecreatetruecolor($this->getDestWidth(), $this->getDestHeight());
}
}
imagealphablending($dest_image, false);
imagesavealpha($dest_image, true);
$background = imagecolorallocatealpha($dest_image, 255, 255, 255, 127);
imagecolortransparent($dest_image, $background);
imagefill($dest_image, 0, 0, $background);
break;
}
imageinterlace($dest_image, $this->interlace);
if ($this->gamma_correct) {
imagegammacorrect($this->source_image, 2.2, 1.0);
}
if (!empty($exact_size) && is_array($exact_size)) {
if ($this->getSourceHeight() < $this->getSourceWidth()) {
$this->dest_x = 0;
$this->dest_y = ($exact_size[1] - $this->getDestHeight()) / 2;
}
if ($this->getSourceHeight() > $this->getSourceWidth()) {
$this->dest_x = ($exact_size[0] - $this->getDestWidth()) / 2;
$this->dest_y = 0;
}
}
imagecopyresampled(
$dest_image,
$this->source_image,
(int)$this->dest_x,
(int)$this->dest_y,
(int)$this->source_x,
(int)$this->source_y,
$this->getDestWidth(),
$this->getDestHeight(),
(int)$this->source_w,
(int)$this->source_h
);
if ($this->gamma_correct) {
imagegammacorrect($dest_image, 1.0, 2.2);
}
$this->applyFilter($dest_image);
switch ($image_type) {
case IMAGETYPE_GIF:
imagegif($dest_image, $filename);
break;
case IMAGETYPE_JPEG:
if ($quality === null || $quality > 100) {
$quality = $this->quality_jpg;
}
imagejpeg($dest_image, $filename, $quality);
break;
case IMAGETYPE_WEBP:
if ($quality === null) {
$quality = $this->quality_webp;
}
imagewebp($dest_image, $filename, $quality);
break;
case IMAGETYPE_PNG:
$quality = (9 - ceil((9*$quality)/100));
if ($quality === null || $quality > 9) {
$quality = $this->quality_png;
}
imagepng($dest_image, $filename, $quality);
break;
}
if ($permissions) {
chmod($filename, $permissions);
}
imagedestroy($dest_image);
return $this;
}
/**
* Преобразовывает изображение в строку
*
* @param integer $image_type
* @param integer $quality
* @return string
*/
public function getImageAsString($image_type = null, $quality = null) {
$string_temp = tempnam(sys_get_temp_dir(), '');
$this->save($string_temp, $image_type, $quality);
$string = file_get_contents($string_temp);
unlink($string_temp);
return $string;
}
/**
* Преобразовывает изображение в строку с текущими настройками
*
* @return string
*/
public function __toString() {
return $this->getImageAsString();
}
/**
* Изменяет размер изображения в соответствии с заданной короткой стороной (пропорциональная длинная сторона)
*
* @param integer $max_short
* @param boolean $allow_enlarge
* @return \cmsImages
*/
public function resizeToShortSide($max_short, $allow_enlarge = false) {
if ($this->getSourceHeight() < $this->getSourceWidth()) {
$ratio = $max_short / $this->getSourceHeight();
$long = (int) ($this->getSourceWidth() * $ratio);
return $this->resize($long, $max_short, $allow_enlarge);
} else {
$ratio = $max_short / $this->getSourceWidth();
$long = (int) ($this->getSourceHeight() * $ratio);
return $this->resize($max_short, $long, $allow_enlarge);
}
}
/**
* Изменяет размер изображения в соответствии
* с заданной длинной стороной (пропорциональная короткая сторона)
*
* @param integer $max_long
* @param boolean $allow_enlarge
* @return \cmsImages
*/
public function resizeToLongSide($max_long, $allow_enlarge = false) {
if ($this->getSourceHeight() > $this->getSourceWidth()) {
$ratio = $max_long / $this->getSourceHeight();
$short = (int) ($this->getSourceWidth() * $ratio);
return $this->resize($short, $max_long, $allow_enlarge);
} else {
$ratio = $max_long / $this->getSourceWidth();
$short = (int) ($this->getSourceHeight() * $ratio);
return $this->resize($max_long, $short, $allow_enlarge);
}
}
/**
* Изменяет размер изображения в соответствии с заданной высотой (ширина пропорциональна)
*
* @param integer $height
* @param boolean $allow_enlarge
* @return \cmsImages
*/
public function resizeToHeight($height, $allow_enlarge = false) {
$ratio = $height / $this->getSourceHeight();
$width = (int) ($this->getSourceWidth() * $ratio);
return $this->resize($width, $height, $allow_enlarge);
}
/**
* Изменяет размер изображения в соответствии с заданной шириной (высота пропорциональна)
*
* @param integer $width
* @param boolean $allow_enlarge
* @return \cmsImages
*/
public function resizeToWidth($width, $allow_enlarge = false) {
$ratio = $width / $this->getSourceWidth();
$height = (int) ($this->getSourceHeight() * $ratio);
return $this->resize($width, $height, $allow_enlarge);
}
/**
* Изменяет размер изображения, чтобы оно лучше подходило к заданным размерам
*
* @param integer $max_width
* @param integer $max_height
* @param boolean $allow_enlarge
* @return \cmsImages
*/
public function resizeToBestFit($max_width, $max_height, $allow_enlarge = false) {
if ($this->getSourceWidth() <= $max_width && $this->getSourceHeight() <= $max_height && !$allow_enlarge) {
return $this;
}
$ratio = $this->getSourceHeight() / $this->getSourceWidth();
$width = $max_width;
$height = (int) ($width * $ratio);
if ($height > $max_height) {
$height = $max_height;
$width = (int) ($height / $ratio);
}
return $this->resize($width, $height, $allow_enlarge);
}
/**
* Изменяет размер изображения в соответствии с заданным масштабом (пропорционально)
*
* @param integer|float $scale
* @return \cmsImages
*/
public function scale($scale) {
$width = (int) ($this->getSourceWidth() * $scale / 100);
$height = (int) ($this->getSourceHeight() * $scale / 100);
return $this->resize($width, $height, true);
}
/**
* Изменяет размер изображения в соответствии с заданной шириной и высотой
*
* @param integer $width
* @param integer $height
* @param boolean $allow_enlarge
* @return \cmsImages
*/
public function resize($width, $height, $allow_enlarge = false) {
if (!$allow_enlarge) {
// сброс размеров к оригиналу
// если требуемые значения больше оригинала
if ($width > $this->getSourceWidth() || $height > $this->getSourceHeight()) {
$width = $this->getSourceWidth();
$height = $this->getSourceHeight();
}
}
$this->source_x = 0;
$this->source_y = 0;
$this->dest_w = round($width);
$this->dest_h = round($height);
$this->source_w = $this->getSourceWidth();
$this->source_h = $this->getSourceHeight();
return $this;
}
/**
* Обрезает изображение в соответствии с заданной шириной, высотой и положением обрезки
*
* @param integer $width
* @param integer $height
* @param boolean $allow_enlarge
* @param integer $position
* @return \cmsImages
*/
public function crop($width, $height, $allow_enlarge = false, $position = self::CROPCENTER) {
if (!$allow_enlarge) {
// эта логика немного отличается от resize()
// сброс размеров к оригиналу
// если требуемые значения больше оригинала
if ($width > $this->getSourceWidth()) {
$width = $this->getSourceWidth();
}
if ($height > $this->getSourceHeight()) {
$height = $this->getSourceHeight();
}
}
$ratio_source = $this->getSourceWidth() / $this->getSourceHeight();
$ratio_dest = $width / $height;
if ($ratio_dest < $ratio_source) {
$this->resizeToHeight($height, $allow_enlarge);
$excess_width = (int) (($this->getDestWidth() - $width) * $this->getSourceWidth() / $this->getDestWidth());
$this->source_w = $this->getSourceWidth() - $excess_width;
$this->source_x = $this->getCropPosition($excess_width, $position);
$this->dest_w = $width;
} else {
$this->resizeToWidth($width, $allow_enlarge);
$excess_height = (int) (($this->getDestHeight() - $height) * $this->getSourceHeight() / $this->getDestHeight());
$this->source_h = $this->getSourceHeight() - $excess_height;
$this->source_y = $this->getCropPosition($excess_height, $position);
$this->dest_h = $height;
}
return $this;
}
/**
* Обрезать изображение в соответствии с заданной шириной, высотой, координатами х и у
*
* @param integer $width
* @param integer $height
* @param integer $x
* @param integer $y
* @return \cmsImages
*/
public function freecrop($width, $height, $x = false, $y = false) {
if ($x === false || $y === false) {
return $this->crop($width, $height);
}
$this->source_x = $x;
$this->source_y = $y;
if ($width > $this->getSourceWidth() - $x) {
$this->source_w = $this->getSourceWidth() - $x;
} else {
$this->source_w = $width;
}
if ($height > $this->getSourceHeight() - $y) {
$this->source_h = $this->getSourceHeight() - $y;
} else {
$this->source_h = $height;
}
$this->dest_w = $width;
$this->dest_h = $height;
return $this;
}
/**
* Получает ширину источника
*
* @return integer
*/
public function getSourceWidth() {
return $this->original_w;
}
/**
* Получает высоту источника
*
* @return integer
*/
public function getSourceHeight() {
return $this->original_h;
}
/**
* Получает ширину целевого изображения
*
* @return integer
*/
public function getDestWidth() {
return $this->dest_w;
}
/**
* Получает высоту целевого изображения
* @return integer
*/
public function getDestHeight() {
return $this->dest_h;
}
/**
* Получает позицию обрезки (X или Y) в соответствии с заданной позицией
*
* @param integer $expected_size
* @param integer $position
* @return float|integer
*/
protected function getCropPosition($expected_size, $position = self::CROPCENTER) {
$size = 0;
switch ($position) {
case self::CROPBOTTOM:
case self::CROPRIGHT:
$size = $expected_size;
break;
case self::CROPCENTER:
$size = $expected_size / 2;
break;
case self::CROPTOPCENTER:
$size = $expected_size / 4;
break;
}
return $size;
}
/**
* Включить/выключить коррекцию гамма-цвета на изображении, выключено по умолчанию
*
* @param bool $enable
* @return \cmsImages
*/
public function gamma($enable = false) {
$this->gamma_correct = $enable;
return $this;
}
}