Files
2026-07-01 16:44:17 +03:00

1048 lines
26 KiB
PHP

<?php namespace ZN\Image;
/**
* ZN PHP Web Framework
*
* "Simplicity is the ultimate sophistication." ~ Da Vinci
*
* @package ZN
* @license MIT [http://opensource.org/licenses/MIT]
* @author Ozan UYKUN [ozan@znframework.com]
*/
use stdClass;
use ZN\Base;
use ZN\Helper;
use ZN\Singleton;
use ZN\Ability\Revolving;
use ZN\Image\Exception\FontNotFoundException;
use ZN\Image\Exception\InvalidArgumentException;
use ZN\Image\Exception\InvalidImageFileException;
#[\AllowDynamicProperties]
class GD implements GDInterface
{
use Revolving, CallableFilterMethod;
/**
* Call callable method
*
* @const string
*/
const call = 'callable';
/**
* Keeps canvas settings
*
* @var resource
*/
protected $canvas;
/**
* Output status
*
* @var bool
*/
protected $output = true;
/**
* Keeps mime class
*/
protected $mime;
/**
* Keeps mime class
*/
protected $imageSize;
/**
* Keeps mime type
*/
protected $mimeType;
/**
* Keeps ttf font path
*/
protected $load;
/**
* Magic Constructor
*/
public function __construct()
{
$this->mime = Singleton::class('ZN\Helpers\Mime');
}
/**
* Get info
*
* @return array
*/
public function info() : array
{
return gd_info();
}
/**
* Load ttf font
*
* @return GD
*/
public function load($ttfpath) : GD
{
$this->load = $ttfpath;
return $this;
}
/**
* Sets canvas
*
* @param mixed $width
* @param mixed $height = NULL
* @param mixed $rgb = 'transparent'
* @param mixed $real = false
*
* @return GD
*/
public function canvas($width, $height = NULL, $rgb = 'transparent', $real = false) : GD
{
if( ! is_numeric($width) )
{
# width -> image
if( $this->mime->type($width, 0) === 'image' )
{
# width -> image
# height -> width
# rgb -> height
$this->createImageCanvas($width, $height, $rgb);
}
else
{
throw new InvalidImageFileException(NULL, $width);
}
}
else
{
$this->createEmptyCanvas($width, $height, $rgb, $real);
}
return $this;
}
/**
* Creates form
*
* @param string $source
*
* @return resource
*/
public function createFrom(string $source)
{
$type = $this->mime->type($source, 1);
$function = 'imagecreatefrom' . ($type ?? 'jpeg');
if( ! function_exists($function) )
{
throw new InvalidImageFileException(NULL, $source); // @codeCoverageIgnore
}
return $function($source);
}
/**
* Set size
*
* @param string $fileName
*
* @return object
*/
public function size(string $fileName) : stdClass
{
$data = [];
if( in_array(strlen(pathinfo($fileName, PATHINFO_EXTENSION)), [3, 4]) && $this->mime->type($fileName, 0) === 'image' )
{
$data = getimagesize($fileName);
}
else if( is_string($fileName) )
{
$data = getimagesizefromstring($fileName);
}
if( empty($data) )
{
throw new InvalidArgumentException(NULL, '[file]');
}
return (object)
[
'width' => $data[0],
'height' => $data[1],
'extension' => $this->extension($data[2]),
'img' => $data[3],
'bits' => $data['bits'],
'mime' => $data['mime']
];
}
/**
* Get file extension
*
* @param string $type = 'jpeg'
* @param bool $dot = true
*
* @return string
*/
public function extension(string $type = 'jpeg', bool $dot = true) : string
{
return image_type_to_extension(Helper::toConstant($type, 'IMAGETYPE_'), $dot);
}
/**
* Get mime type
*
* @param string $type = 'jpeg'
*
* @return string
*/
public function mime(string $type = 'jpeg') : string
{
return image_type_to_mime_type(Helper::toConstant($type, 'IMAGETYPE_'));
}
/**
* Sets alpha blending
*
* @param bool $blendMode = NULL
*
* @return GD
*/
public function alphaBlending(?bool $blendMode = NULL) : GD
{
imagealphablending($this->canvas, (bool) $blendMode);
return $this;
}
/**
* Sets save alpha
*
* @param bool $save = true
*
* @return GD
*/
public function saveAlpha(bool $save = true) : GD
{
imagesavealpha($this->canvas, $save);
return $this;
}
/**
* Sets smooth
*
* @param bool $mode = true
*
* @return GD
*/
public function smooth(bool $mode = true) : GD
{
imageantialias($this->canvas, $mode);
return $this;
}
/**
* Creates Arc
*
* @param array $settings []
*
* @return GD
*/
public function arc(array $settings = []) : GD
{
$x = $settings['x'] ?? $this->x ?? 0;
$y = $settings['y'] ?? $this->y ?? 0;
$width = $settings['width'] ?? $this->width ?? 100;
$height = $settings['height'] ?? $this->height ?? 100;
$start = $settings['start'] ?? $this->start ?? 0;
$end = $settings['end'] ?? $this->end ?? 360;
$color = $settings['color'] ?? $this->color ?? '0|0|0';
$style = $settings['type'] ?? $this->type ?? NULL;
if( $style === NULL )
{
imagearc($this->canvas, $x, $y, $width, $height, $start, $end, $this->allocate($color));
}
else
{
imagefilledarc
(
$this->canvas, $x, $y, $width, $height, $start, $end,
$this->allocate($color),
Helper::toConstant($style, 'IMG_ARC_')
);
}
$this->defaultRevolvingVariables(['x', 'y', 'width', 'height', 'start', 'end', 'color', 'type']);
return $this;
}
/**
* Creates Ellipse
*
* @param array $settings []
*
* @return GD
*/
public function ellipse(array $settings = []) : GD
{
$x = $settings['x'] ?? $this->x ?? 0;
$y = $settings['y'] ?? $this->y ?? 0;
$width = $settings['width'] ?? $this->width ?? 100;
$height = $settings['height'] ?? $this->height ?? 100;
$color = $settings['color'] ?? $this->color ?? '0|0|0';
$style = $settings['type'] ?? $this->type ?? NULL;
if( $style === NULL )
{
imageellipse($this->canvas, $x, $y, $width, $height, $this->allocate($color));
}
else
{
imagefilledellipse($this->canvas, $x, $y, $width, $height, $this->allocate($color));
}
$this->defaultRevolvingVariables(['x', 'y' ,'width', 'height', 'color', 'type']);
return $this;
}
/**
* Creates Polygon
*
* @param array $settings []
*
* @return GD
*/
public function polygon(array $settings = []) : GD
{
$points = $settings['points'] ?? $this->points ?? 0;
$color = $settings['color'] ?? $this->color ?? '0|0|0';
$style = $settings['type'] ?? $this->type ?? NULL;
if( $style === NULL )
{
imagepolygon($this->canvas, $points, $this->allocate($color));
}
else
{
imagefilledpolygon($this->canvas, $points, $this->allocate($color));
}
$this->defaultRevolvingVariables(['points', 'color', 'type']);
return $this;
}
/**
* Creates Rectangle
*
* @param array $settings []
*
* @return GD
*/
public function rectangle(array $settings = []) : GD
{
$x = $settings['x'] ?? $this->x ?? 0;
$y = $settings['y'] ?? $this->y ?? 0;
$width = $settings['width'] ?? $this->width ?? 100;
$height = $settings['height'] ?? $this->height ?? 100;
$color = $settings['color'] ?? $this->color ?? '0|0|0';
$style = $settings['type'] ?? $this->type ?? NULL;
$width += $x;
$height += $y;
if( $style === NULL )
{
imagerectangle($this->canvas, $x, $y, $width, $height, $this->allocate($color));
}
else
{
imagefilledrectangle($this->canvas, $x, $y, $width, $height, $this->allocate($color));
}
$this->defaultRevolvingVariables(['x', 'y' ,'width', 'height', 'color', 'type']);
return $this;
}
/**
* Fill
*
* @param array $settings []
*
* @return GD
*/
public function fill(array $settings = []) : GD
{
$x = $settings['x'] ?? $this->x ?? 0;
$y = $settings['y'] ?? $this->y ?? 0;
$color = $settings['color'] ?? $this->color ?? '0|0|0';
$borderColor = $settings['borderColor'] ?? $this->borderColor ?? NULL;
if( $borderColor === NULL )
{
imagefill($this->canvas, $x, $y, $this->allocate($color));
}
else
{
imagefilltoborder($this->canvas, $x, $y, $this->allocate($borderColor), $this->allocate($color));
}
$this->defaultRevolvingVariables(['x', 'y', 'color', 'borderColor']);
return $this;
}
/**
* Filter
*
* @param string $filter
* @param int $arg1 = NULL
* @param int $arg2 = NULL
* @param int $arg3 = NULL
* @param int $arg4 = NULL
*
* @return GD
*/
public function filter(string $filter, ?int $arg1 = NULL, ?int $arg2 = NULL, ?int $arg3 = NULL, ?int $arg4 = NULL) : GD
{
$filters = Singleton::class('ZN\DataTypes\Collection')->data(func_get_args())
->removeFirst()
->deleteElement(NULL)
->get();
imagefilter($this->canvas, Helper::toConstant($filter, 'IMG_FILTER_'), ...$filters);
return $this;
}
/**
* Flip
*
* @param string $type = 'both'
*
* @return GD
*/
public function flip(string $type = 'both') : GD
{
imageflip($this->canvas, Helper::toConstant($type, 'IMG_FLIP_'));
return $this;
}
/**
* Creates char
*
* @param string $text
* @param array $settings = []
*
* @return GD
*/
public function char(string $char, array $settings = [], $function = 'char') : GD
{
$x = $settings['x'] ?? $this->x ?? 0;
$y = $settings['y'] ?? $this->y ?? 0;
$font = $settings['font'] ?? $this->font ?? 1;
$color = $settings['color'] ?? $this->color ?? '0|0|0';
$type = $settings['type'] ?? $this->type ?? NULL;
$method = 'image' . $function;
if( $type === 'vertical')
{
$method .= 'up';
$method($this->canvas, $font, $x, $y, $char, $this->allocate($color));
}
else
{
$method($this->canvas, $font, $x, $y, $char, $this->allocate($color));
}
$this->defaultRevolvingVariables(['x', 'y' ,'font', 'color', 'type']);
return $this;
}
/**
* Creates ttftext
*
* @param string $text
* @param array $settings = []
*
* @return GD
*/
public function ttftext(string $text, array $settings = []) : GD
{
$fontSize = $settings['fontSize'] ?? $this->fontSize ?? 14;
$angle = $settings['angle'] ?? $this->angle ?? 0;
$x = $settings['x'] ?? $this->x ?? 0;
$y = $settings['y'] ?? $this->y ?? 0;
$color = $settings['color'] ?? $this->color ?? '0|0|0';
$load = $settings['load'] ?? $this->load ?? '';
$load = Base::suffix($load, '.ttf');
if( ! is_file($load) )
{
throw new FontNotFoundException(NULL, $load);
}
imagettftext($this->canvas, $fontSize, $angle, $x, $y, $this->allocate($color), $load, $text);
$this->defaultRevolvingVariables(['fontSize', 'angle', 'x', 'y', 'color']);
return $this;
}
/**
* Creates text
*
* @param string $text
* @param array $settings = []
*
* @return GD
*/
public function text(string $text, array $settings = []) : GD
{
if( $settings['load'] ?? $this->load )
{
return $this->ttftext($text, $settings);
}
return $this->char($text, $settings, 'string');
}
/**
* Set convolution
*
* @param array $matrix
* @param float $div = 0
* @param float $offset = 0
*
* @return GD
*/
public function convolution(array $matrix, Float $div = 0, Float $offset = 0) : GD
{
imageconvolution($this->canvas, $matrix, $div, $offset);
return $this;
}
/**
* Set interlace
*
* @param int $interlace = 0
*
* @return GD
*/
public function interlace(int $interlace = 0) : GD
{
imageinterlace($this->canvas, $interlace);
return $this;
}
/**
* Copy
*
* @param string|resource $source
* @param array $settings = []
*
* @return GD
*/
public function copy($source, array $settings = []) : GD
{
if( is_file($file = $source) )
{
$source = $this->createFrom($source);
}
if( ! Base::isResourceObject($source) )
{
throw new InvalidArgumentException(NULL, '[resource]'); // @codeCoverageIgnore
}
$this->alignImageWatermark($file);
$xt = $settings['xt'] ?? $this->target[0] ?? 0;
$yt = $settings['yt'] ?? $this->target[1] ?? 0;
$xs = $settings['xs'] ?? $this->source[0] ?? 0;
$ys = $settings['ys'] ?? $this->source[1] ?? 0;
$width = $settings['width'] ?? $this->width ?? 0;
$height = $settings['height'] ?? $this->height ?? 0;
imagecopy($this->canvas, $source, $xt, $yt, $xs, $ys, $width, $height);
$this->defaultRevolvingVariables(['target', 'source' ,'width', 'height']);
return $this;
}
/**
* Mix
*
* @param string|resource $source
* @param array $settings = []
*
* @return GD
*/
public function mix($source, array $settings = [], $function = 'imagecopymerge') : GD
{
if( is_file($file = $source) )
{
$source = $this->createFrom($source);
}
if( ! Base::isResourceObject($source) )
{
throw new InvalidArgumentException(NULL, '[resource]'); // @codeCoverageIgnore
}
$this->alignImageWatermark($file);
$xt = $settings['xt'] ?? $this->target[0] ?? 0;
$yt = $settings['yt'] ?? $this->target[1] ?? 0;
$xs = $settings['xs'] ?? $this->source[0] ?? 0;
$ys = $settings['ys'] ?? $this->source[1] ?? 0;
$width = $settings['width'] ?? $this->width ?? 0;
$height = $settings['height'] ?? $this->height ?? 0;
$percent = $settings['percent'] ?? $this->percent ?? 100;
$function($this->canvas, $source, $xt, $yt, $xs, $ys, $width, $height, $percent);
$this->defaultRevolvingVariables(['target', 'source' ,'width', 'height', 'percent']);
return $this;
}
/**
* Mixgray
*
* @param string|resource $source
* @param array $settings = []
*
* @return GD
*/
public function mixGray($source, array $settings = []) : GD
{
$this->mix($source, $settings, 'imagecopymergegray');
return $this;
}
/**
* Resize / Resample
*
* @param string|resource $source
* @param array $settings = []
*
* @return GD
*/
public function resample($source, array $settings = [], $function = 'imagecopyresampled') : GD
{
if( is_file($file = $source) )
{
$source = $this->createFrom($source);
}
if( ! Base::isResourceObject($source) )
{
throw new InvalidArgumentException(NULL, '[resource]'); // @codeCoverageIgnore
}
$this->alignImageWatermark($file);
$xt = $settings['xt'] ?? $this->target[0] ?? 0;
$yt = $settings['yt'] ?? $this->target[1] ?? 0;
$xs = $settings['xs'] ?? $this->source[0] ?? 0;
$ys = $settings['ys'] ?? $this->source[1] ?? 0;
$wt = $settings['wt'] ?? $this->width ?? $this->targetWidth ?? 0;
$ht = $settings['ht'] ?? $this->height ?? $this->targetHeight ?? 0;
$ws = $settings['ws'] ?? $this->sourceWidth ?? 0;
$hs = $settings['hs'] ?? $this->sourceHeight ?? 0;
$function($this->canvas, $source, $xt, $yt, $xs, $ys, $wt, $ht, $ws, $hs);
$this->defaultRevolvingVariables(['target', 'source' ,'width', 'height', 'sourceWidth', 'sourceHeight']);
return $this;
}
/**
* Resize
*
* @param string|resource $source
* @param array $settings = []
*
* @return GD
*/
public function resize($source, array $settings = []) : GD
{
$this->resample($source, $settings, 'imagecopyresized');
return $this;
}
/**
* Crop
*
* @param array $settings = []
*
* @return GD
*/
public function crop(array $settings = []) : GD
{
$sets =
[
'x' => $settings['x'] ?? $this->x ?? 0,
'y' => $settings['y'] ?? $this->y ?? 0,
'width' => $settings['width'] ?? $this->width ?? 100,
'height' => $settings['height'] ?? $this->height ?? 0,
];
$this->canvas = imagecrop($this->canvas, $sets);
$this->defaultRevolvingVariables(['x', 'y' ,'width', 'height']);
return $this;
}
/**
* Auto crop
*
* @param string $mode = 'default'
* @param int $threshold = .5
* @param int $color = -1
*
* @return GD
*/
public function autoCrop(string $mode = 'default', $threshold = .5, $color = -1) : GD
{
$this->canvas = imagecropauto
(
$this->canvas,
Helper::toConstant($mode, 'IMG_CROP_'),
$threshold,
$mode === 'threshold' ? $this->allocate($color) : $color
);
return $this;
}
/**
* Creates a line
*
* @param array $settings = []
*
* @return GD
*/
public function line(array $settings = []) : GD
{
$x1 = $settings['x1'] ?? $this->x1 ?? 0;
$y1 = $settings['y1'] ?? $this->y1 ?? 0;
$x2 = $settings['x2'] ?? $this->x2 ?? 0;
$y2 = $settings['y2'] ?? $this->y2 ?? 0;
$rgb = $settings['color'] ?? $this->color ?? '0|0|0';
$type = $settings['type'] ?? $this->type ?? 'solid';
if( $type === 'solid' )
{
imageline($this->canvas, $x1, $y1, $x2, $y2, $this->allocate($rgb));
}
elseif( $type === 'dashed' )
{
imagedashedline($this->canvas, $x1, $y1, $x2, $y2, $this->allocate($rgb));
}
$this->defaultRevolvingVariables(['x1', 'y1', 'x2', 'y2', 'color', 'type']);
return $this;
}
/**
* Get screenshot
*
* @return GD
*
* @codeCoverageIgnore
*/
public function screenshot() : GD
{
$this->canvas = imagegrabscreen();
return $this;
}
/**
* Set rotate
*
* @param float $angle
* @param string $spaceColor = '0|0|0'
*
* @return GD
*/
public function rotate(Float $angle, string $spaceColor = '0|0|0') : GD
{
$this->canvas = imagerotate($this->canvas, $angle, $this->allocate($spaceColor));
if( $spaceColor === 'transparent' )
{
$this->saveAlpha();
}
return $this;
}
/**
* Set scale
*
* @param int $width
* @param int $height = -1
* @param string $method = 'bilinearFixed'
*
* @return GD
*/
public function scale(int $width, int $height = -1, string $mode = 'bilinearFixed') : GD
{
$this->canvas = imagescale($this->canvas, $width, $height, Helper::toConstant($mode, 'IMG_'));
return $this;
}
/**
* Set interpolation
*
* @param string $method = 'bilinearFixed'
*
* @return GD
*/
public function interpolation(string $method = 'bilinearFixed') : GD
{
imagesetinterpolation($this->canvas, Helper::toConstant($method, 'IMG_'));
return $this;
}
/**
* Set pixed
*
* @param array $settings = []
*
* @return GD
*/
public function pixel(array $settings = []) : GD
{
$x = $settings['x'] ?? $this->x ?? 0;
$y = $settings['y'] ?? $this->y ?? 0;
$rgb = $settings['color'] ?? $this->color ?? '0|0|0';
imagesetpixel($this->canvas, $x, $y, $this->allocate($rgb));
$this->defaultRevolvingVariables(['x', 'y', 'color']);
return $this;
}
/**
* Set thickness
*
* @param int $thickness = 1
*
* @return GD
*/
public function thickness(int $thickness = 1) : GD
{
imagesetthickness($this->canvas, $thickness);
return $this;
}
/**
* Set layer effect
*
* @param string $effect = 'normal'
*
* @return GD
*/
public function layerEffect(string $effect = 'normal') : GD
{
imagelayereffect($this->canvas, Helper::toConstant($effect, 'IMG_EFFECT_'));
return $this;
}
/**
* Generate Image
*
* @param string $type = NULL
* @param string $save = NULL
*
* @return resource
*/
public function generate(?string $type = NULL, ?string $save = NULL)
{
$canvas = $this->canvas;
if( ! empty($type) )
{
$this->type = $type;
}
if( ! empty($save) )
{
$this->save = $save;
}
if( empty($this->save) && $this->output === true )
{
$this->getImageContent();
}
$this->generateImageType();
$this->destroyImage();
$this->defaultGlobalVariables();
return $canvas;
}
/**
* Protected create image canvas
*/
protected function createImageCanvas($image, $width, $height)
{
$this->mimeType = $this->mime->type($image, 1);
$this->imageSize = ! isset($width) ? getimagesize($image) : [(int) $width, (int) $height];
$this->canvas = $this->createFrom($image);
}
/**
* Protected create empty canvas
*/
protected function createEmptyCanvas($width, $height, $rgb, $real)
{
$this->imageSize = [$width, $height];
if( $real === false )
{
$this->canvas = imagecreate($width, $height);
}
else
{
$this->canvas = imagecreatetruecolor($width, $height);
}
if( ! empty($rgb) )
{
$this->allocate($rgb);
}
}
/**
* Protected align image watermark
*/
protected function alignImageWatermark($source)
{
if( is_string($this->target ?? NULL) )
{
$size = getimagesize($source);
$this->width = $this->width ?? $size[0];
$this->height = $this->height ?? $size[1];
$return = WatermarkImageAligner::align($this->target, $this->width, $this->height, $this->imageSize[0], $this->imageSize[1], $this->margin ?? 0);
$this->target = $return;
if( isset($this->x) ) $this->source[0] = $this->x;
if( isset($this->y) ) $this->source[1] = $this->y;
}
}
/**
* Protected Allocate
*/
protected function allocate($rgb)
{
$rgb = explode('|', ColorConverter::run($rgb));
$red = $rgb[0] ?? 0;
$green = $rgb[1] ?? 0;
$blue = $rgb[2] ?? 0;
$alpha = $rgb[3] ?? 0;
return imagecolorallocatealpha($this->canvas, $red, $green, $blue, $alpha);
}
/**
* Protected Destroy
*/
protected function destroyImage()
{
imagedestroy($this->canvas);
return $this;
}
/**
* Protected Content
*/
protected function getImageContent()
{
header("Content-type: image/" . ($this->mimeType ?? 'jpeg') );
}
/**
* Protected Default Variables
*/
protected function defaultGlobalVariables()
{
$this->canvas = NULL;
$this->save = NULL;
$this->output = true;
$this->quality = NULL;
$this->type = NULL;
$this->imageSize = NULL;
}
/**
* Protected Types
*/
protected function generateImageType()
{
$type = strtolower($this->type ?? 'jpeg');
if( ! empty($this->save) )
{
$save = Base::suffix($this->save, '.'.($type === 'jpeg' ? 'jpg' : $type));
}
else
{
$save = NULL;
}
$function = 'image' . $type;
$function($this->canvas, $save, $this->quality ?? ($type === 'png' ? 8 : 80));
return $this;
}
}