1286 lines
38 KiB
PHP
1286 lines
38 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* @package SimpleImage class
|
||
|
* @version 2.5.5
|
||
|
* @author Cory LaViska for A Beautiful Site, LLC (http://www.abeautifulsite.net/)
|
||
|
* @author Nazar Mokrynskyi <nazar@mokrynskyi.com> - merging of forks, namespace support, PhpDoc editing, adaptive_resize() method, other fixes
|
||
|
* @license This software is licensed under the MIT license: http://opensource.org/licenses/MIT
|
||
|
* @copyright A Beautiful Site, LLC
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
namespace abeautifulsite;
|
||
|
use Exception;
|
||
|
|
||
|
/**
|
||
|
* Class SimpleImage
|
||
|
* This class makes image manipulation in PHP as simple as possible.
|
||
|
* @package SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
class SimpleImage {
|
||
|
|
||
|
/**
|
||
|
* @var int Default output image quality
|
||
|
*
|
||
|
*/
|
||
|
public $quality = 80;
|
||
|
|
||
|
protected $image, $filename, $original_info, $width, $height, $imagestring, $exif;
|
||
|
|
||
|
/**
|
||
|
* Create instance and load an image, or create an image from scratch
|
||
|
*
|
||
|
* @param null|string $filename Path to image file (may be omitted to create image from scratch)
|
||
|
* @param int $width Image width (is used for creating image from scratch)
|
||
|
* @param int|null $height If omitted - assumed equal to $width (is used for creating image from scratch)
|
||
|
* @param null|string $color Hex color string, array(red, green, blue) or array(red, green, blue, alpha).
|
||
|
* Where red, green, blue - integers 0-255, alpha - integer 0-127<br>
|
||
|
* (is used for creating image from scratch)
|
||
|
*
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
public function __construct($filename = null, $width = null, $height = null, $color = null) {
|
||
|
if ($filename !== null) {
|
||
|
$this->load($filename);
|
||
|
} elseif ($width !== null) {
|
||
|
$this->create($width, $height, $color);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Destroy image resource
|
||
|
*
|
||
|
*/
|
||
|
public function __destruct() {
|
||
|
if( get_resource_type($this->image) === 'gd' ) {
|
||
|
imagedestroy($this->image);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adaptive resize
|
||
|
*
|
||
|
* This function has been deprecated and will be removed in an upcoming release. Please
|
||
|
* update your code to use the `thumbnail()` method instead. The arguments for both
|
||
|
* methods are exactly the same.
|
||
|
*
|
||
|
* @param int $width
|
||
|
* @param int|null $height If omitted - assumed equal to $width
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function adaptive_resize($width, $height = null) {
|
||
|
|
||
|
return $this->thumbnail($width, $height);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rotates and/or flips an image automatically so the orientation will be correct (based on exif 'Orientation')
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function auto_orient() {
|
||
|
|
||
|
// stop if there's no exif data
|
||
|
if(!isset($this->original_info['exif']['Orientation'])) {
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
switch ($this->original_info['exif']['Orientation']) {
|
||
|
case 1:
|
||
|
// Do nothing
|
||
|
break;
|
||
|
case 2:
|
||
|
// Flip horizontal
|
||
|
$this->flip('x');
|
||
|
break;
|
||
|
case 3:
|
||
|
// Rotate 180 counterclockwise
|
||
|
$this->rotate(-180);
|
||
|
break;
|
||
|
case 4:
|
||
|
// vertical flip
|
||
|
$this->flip('y');
|
||
|
break;
|
||
|
case 5:
|
||
|
// Rotate 90 clockwise and flip vertically
|
||
|
$this->flip('y');
|
||
|
$this->rotate(90);
|
||
|
break;
|
||
|
case 6:
|
||
|
// Rotate 90 clockwise
|
||
|
$this->rotate(90);
|
||
|
break;
|
||
|
case 7:
|
||
|
// Rotate 90 clockwise and flip horizontally
|
||
|
$this->flip('x');
|
||
|
$this->rotate(90);
|
||
|
break;
|
||
|
case 8:
|
||
|
// Rotate 90 counterclockwise
|
||
|
$this->rotate(-90);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Best fit (proportionally resize to fit in specified width/height)
|
||
|
*
|
||
|
* Shrink the image proportionally to fit inside a $width x $height box
|
||
|
*
|
||
|
* @param int $max_width
|
||
|
* @param int $max_height
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function best_fit($max_width, $max_height) {
|
||
|
|
||
|
// If it already fits, there's nothing to do
|
||
|
if ($this->width <= $max_width && $this->height <= $max_height) {
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
// Determine aspect ratio
|
||
|
$aspect_ratio = $this->height / $this->width;
|
||
|
|
||
|
// Make width fit into new dimensions
|
||
|
if ($this->width > $max_width) {
|
||
|
$width = $max_width;
|
||
|
$height = $width * $aspect_ratio;
|
||
|
} else {
|
||
|
$width = $this->width;
|
||
|
$height = $this->height;
|
||
|
}
|
||
|
|
||
|
// Make height fit into new dimensions
|
||
|
if ($height > $max_height) {
|
||
|
$height = $max_height;
|
||
|
$width = $height / $aspect_ratio;
|
||
|
}
|
||
|
|
||
|
return $this->resize($width, $height);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Blur
|
||
|
*
|
||
|
* @param string $type selective|gaussian
|
||
|
* @param int $passes Number of times to apply the filter
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function blur($type = 'selective', $passes = 1) {
|
||
|
switch (strtolower($type)) {
|
||
|
case 'gaussian':
|
||
|
$type = IMG_FILTER_GAUSSIAN_BLUR;
|
||
|
break;
|
||
|
default:
|
||
|
$type = IMG_FILTER_SELECTIVE_BLUR;
|
||
|
break;
|
||
|
}
|
||
|
for ($i = 0; $i < $passes; $i++) {
|
||
|
imagefilter($this->image, $type);
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Brightness
|
||
|
*
|
||
|
* @param int $level Darkest = -255, lightest = 255
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function brightness($level) {
|
||
|
imagefilter($this->image, IMG_FILTER_BRIGHTNESS, $this->keep_within($level, -255, 255));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Contrast
|
||
|
*
|
||
|
* @param int $level Min = -100, max = 100
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*
|
||
|
*/
|
||
|
public function contrast($level) {
|
||
|
imagefilter($this->image, IMG_FILTER_CONTRAST, $this->keep_within($level, -100, 100));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Colorize
|
||
|
*
|
||
|
* @param string $color Hex color string, array(red, green, blue) or array(red, green, blue, alpha).
|
||
|
* Where red, green, blue - integers 0-255, alpha - integer 0-127
|
||
|
* @param float|int $opacity 0-1
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function colorize($color, $opacity) {
|
||
|
$rgba = $this->normalize_color($color);
|
||
|
$alpha = $this->keep_within(127 - (127 * $opacity), 0, 127);
|
||
|
imagefilter($this->image, IMG_FILTER_COLORIZE, $this->keep_within($rgba['r'], 0, 255), $this->keep_within($rgba['g'], 0, 255), $this->keep_within($rgba['b'], 0, 255), $alpha);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create an image from scratch
|
||
|
*
|
||
|
* @param int $width Image width
|
||
|
* @param int|null $height If omitted - assumed equal to $width
|
||
|
* @param null|string $color Hex color string, array(red, green, blue) or array(red, green, blue, alpha).
|
||
|
* Where red, green, blue - integers 0-255, alpha - integer 0-127
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function create($width, $height = null, $color = null) {
|
||
|
|
||
|
$height = $height ?: $width;
|
||
|
$this->width = $width;
|
||
|
$this->height = $height;
|
||
|
$this->image = imagecreatetruecolor($width, $height);
|
||
|
$this->original_info = array(
|
||
|
'width' => $width,
|
||
|
'height' => $height,
|
||
|
'orientation' => $this->get_orientation(),
|
||
|
'exif' => null,
|
||
|
'format' => 'png',
|
||
|
'mime' => 'image/png'
|
||
|
);
|
||
|
|
||
|
if ($color !== null) {
|
||
|
$this->fill($color);
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Crop an image
|
||
|
*
|
||
|
* @param int $x1 Left
|
||
|
* @param int $y1 Top
|
||
|
* @param int $x2 Right
|
||
|
* @param int $y2 Bottom
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function crop($x1, $y1, $x2, $y2) {
|
||
|
|
||
|
// Determine crop size
|
||
|
if ($x2 < $x1) {
|
||
|
list($x1, $x2) = array($x2, $x1);
|
||
|
}
|
||
|
if ($y2 < $y1) {
|
||
|
list($y1, $y2) = array($y2, $y1);
|
||
|
}
|
||
|
$crop_width = $x2 - $x1;
|
||
|
$crop_height = $y2 - $y1;
|
||
|
|
||
|
// Perform crop
|
||
|
$new = imagecreatetruecolor($crop_width, $crop_height);
|
||
|
imagealphablending($new, false);
|
||
|
imagesavealpha($new, true);
|
||
|
imagecopyresampled($new, $this->image, 0, 0, $x1, $y1, $crop_width, $crop_height, $crop_width, $crop_height);
|
||
|
|
||
|
// Update meta data
|
||
|
$this->width = $crop_width;
|
||
|
$this->height = $crop_height;
|
||
|
$this->image = $new;
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Desaturate
|
||
|
*
|
||
|
* @param int $percentage Level of desaturization.
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function desaturate($percentage = 100) {
|
||
|
|
||
|
// Determine percentage
|
||
|
$percentage = $this->keep_within($percentage, 0, 100);
|
||
|
|
||
|
if( $percentage === 100 ) {
|
||
|
imagefilter($this->image, IMG_FILTER_GRAYSCALE);
|
||
|
} else {
|
||
|
// Make a desaturated copy of the image
|
||
|
$new = imagecreatetruecolor($this->width, $this->height);
|
||
|
imagealphablending($new, false);
|
||
|
imagesavealpha($new, true);
|
||
|
imagecopy($new, $this->image, 0, 0, 0, 0, $this->width, $this->height);
|
||
|
imagefilter($new, IMG_FILTER_GRAYSCALE);
|
||
|
|
||
|
// Merge with specified percentage
|
||
|
$this->imagecopymerge_alpha($this->image, $new, 0, 0, 0, 0, $this->width, $this->height, $percentage);
|
||
|
imagedestroy($new);
|
||
|
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Edge Detect
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function edges() {
|
||
|
imagefilter($this->image, IMG_FILTER_EDGEDETECT);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Emboss
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function emboss() {
|
||
|
imagefilter($this->image, IMG_FILTER_EMBOSS);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fill image with color
|
||
|
*
|
||
|
* @param string $color Hex color string, array(red, green, blue) or array(red, green, blue, alpha).
|
||
|
* Where red, green, blue - integers 0-255, alpha - integer 0-127
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function fill($color = '#000000') {
|
||
|
|
||
|
$rgba = $this->normalize_color($color);
|
||
|
$fill_color = imagecolorallocatealpha($this->image, $rgba['r'], $rgba['g'], $rgba['b'], $rgba['a']);
|
||
|
imagealphablending($this->image, false);
|
||
|
imagesavealpha($this->image, true);
|
||
|
imagefilledrectangle($this->image, 0, 0, $this->width, $this->height, $fill_color);
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fit to height (proportionally resize to specified height)
|
||
|
*
|
||
|
* @param int $height
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function fit_to_height($height) {
|
||
|
|
||
|
$aspect_ratio = $this->height / $this->width;
|
||
|
$width = $height / $aspect_ratio;
|
||
|
|
||
|
return $this->resize($width, $height);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fit to width (proportionally resize to specified width)
|
||
|
*
|
||
|
* @param int $width
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function fit_to_width($width) {
|
||
|
|
||
|
$aspect_ratio = $this->height / $this->width;
|
||
|
$height = $width * $aspect_ratio;
|
||
|
|
||
|
return $this->resize($width, $height);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Flip an image horizontally or vertically
|
||
|
*
|
||
|
* @param string $direction x|y
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function flip($direction) {
|
||
|
|
||
|
$new = imagecreatetruecolor($this->width, $this->height);
|
||
|
imagealphablending($new, false);
|
||
|
imagesavealpha($new, true);
|
||
|
|
||
|
switch (strtolower($direction)) {
|
||
|
case 'y':
|
||
|
for ($y = 0; $y < $this->height; $y++) {
|
||
|
imagecopy($new, $this->image, 0, $y, 0, $this->height - $y - 1, $this->width, 1);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
for ($x = 0; $x < $this->width; $x++) {
|
||
|
imagecopy($new, $this->image, $x, 0, $this->width - $x - 1, 0, 1, $this->height);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$this->image = $new;
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the current height
|
||
|
*
|
||
|
* @return int
|
||
|
*
|
||
|
*/
|
||
|
public function get_height() {
|
||
|
return $this->height;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the current orientation
|
||
|
*
|
||
|
* @return string portrait|landscape|square
|
||
|
*
|
||
|
*/
|
||
|
public function get_orientation() {
|
||
|
|
||
|
if (imagesx($this->image) > imagesy($this->image)) {
|
||
|
return 'landscape';
|
||
|
}
|
||
|
|
||
|
if (imagesx($this->image) < imagesy($this->image)) {
|
||
|
return 'portrait';
|
||
|
}
|
||
|
|
||
|
return 'square';
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get info about the original image
|
||
|
*
|
||
|
* @return array <pre> array(
|
||
|
* width => 320,
|
||
|
* height => 200,
|
||
|
* orientation => ['portrait', 'landscape', 'square'],
|
||
|
* exif => array(...),
|
||
|
* mime => ['image/jpeg', 'image/gif', 'image/png'],
|
||
|
* format => ['jpeg', 'gif', 'png']
|
||
|
* )</pre>
|
||
|
*
|
||
|
*/
|
||
|
public function get_original_info() {
|
||
|
return $this->original_info;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the current width
|
||
|
*
|
||
|
* @return int
|
||
|
*
|
||
|
*/
|
||
|
public function get_width() {
|
||
|
return $this->width;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invert
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function invert() {
|
||
|
imagefilter($this->image, IMG_FILTER_NEGATE);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load an image
|
||
|
*
|
||
|
* @param string $filename Path to image file
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
public function load($filename) {
|
||
|
|
||
|
// Require GD library
|
||
|
if (!extension_loaded('gd')) {
|
||
|
throw new Exception('Required extension GD is not loaded.');
|
||
|
}
|
||
|
$this->filename = $filename;
|
||
|
return $this->get_meta_data();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load a base64 string as image
|
||
|
*
|
||
|
* @param string $base64string base64 string
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function load_base64($base64string) {
|
||
|
if (!extension_loaded('gd')) {
|
||
|
throw new Exception('Required extension GD is not loaded.');
|
||
|
}
|
||
|
//remove data URI scheme and spaces from base64 string then decode it
|
||
|
$this->imagestring = base64_decode(str_replace(' ', '+',preg_replace('#^data:image/[^;]+;base64,#', '', $base64string)));
|
||
|
$this->image = imagecreatefromstring($this->imagestring);
|
||
|
return $this->get_meta_data();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Mean Remove
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function mean_remove() {
|
||
|
imagefilter($this->image, IMG_FILTER_MEAN_REMOVAL);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Changes the opacity level of the image
|
||
|
*
|
||
|
* @param float|int $opacity 0-1
|
||
|
*
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
public function opacity($opacity) {
|
||
|
|
||
|
// Determine opacity
|
||
|
$opacity = $this->keep_within($opacity, 0, 1) * 100;
|
||
|
|
||
|
// Make a copy of the image
|
||
|
$copy = imagecreatetruecolor($this->width, $this->height);
|
||
|
imagealphablending($copy, false);
|
||
|
imagesavealpha($copy, true);
|
||
|
imagecopy($copy, $this->image, 0, 0, 0, 0, $this->width, $this->height);
|
||
|
|
||
|
// Create transparent layer
|
||
|
$this->create($this->width, $this->height, array(0, 0, 0, 127));
|
||
|
|
||
|
// Merge with specified opacity
|
||
|
$this->imagecopymerge_alpha($this->image, $copy, 0, 0, 0, 0, $this->width, $this->height, $opacity);
|
||
|
imagedestroy($copy);
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Outputs image without saving
|
||
|
*
|
||
|
* @param null|string $format If omitted or null - format of original file will be used, may be gif|jpg|png
|
||
|
* @param int|null $quality Output image quality in percents 0-100
|
||
|
*
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
public function output($format = null, $quality = null) {
|
||
|
|
||
|
// Determine quality
|
||
|
$quality = $quality ?: $this->quality;
|
||
|
|
||
|
// Determine mimetype
|
||
|
switch (strtolower($format)) {
|
||
|
case 'gif':
|
||
|
$mimetype = 'image/gif';
|
||
|
break;
|
||
|
case 'jpeg':
|
||
|
case 'jpg':
|
||
|
imageinterlace($this->image, true);
|
||
|
$mimetype = 'image/jpeg';
|
||
|
break;
|
||
|
case 'png':
|
||
|
$mimetype = 'image/png';
|
||
|
break;
|
||
|
default:
|
||
|
$info = (empty($this->imagestring)) ? getimagesize($this->filename) : getimagesizefromstring($this->imagestring);
|
||
|
$mimetype = $info['mime'];
|
||
|
unset($info);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Output the image
|
||
|
header('Content-Type: '.$mimetype);
|
||
|
switch ($mimetype) {
|
||
|
case 'image/gif':
|
||
|
imagegif($this->image);
|
||
|
break;
|
||
|
case 'image/jpeg':
|
||
|
imagejpeg($this->image, null, round($quality));
|
||
|
break;
|
||
|
case 'image/png':
|
||
|
imagepng($this->image, null, round(9 * $quality / 100));
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception('Unsupported image format: '.$this->filename);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Outputs image as data base64 to use as img src
|
||
|
*
|
||
|
* @param null|string $format If omitted or null - format of original file will be used, may be gif|jpg|png
|
||
|
* @param int|null $quality Output image quality in percents 0-100
|
||
|
*
|
||
|
* @return string
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
public function output_base64($format = null, $quality = null) {
|
||
|
|
||
|
// Determine quality
|
||
|
$quality = $quality ?: $this->quality;
|
||
|
|
||
|
// Determine mimetype
|
||
|
switch (strtolower($format)) {
|
||
|
case 'gif':
|
||
|
$mimetype = 'image/gif';
|
||
|
break;
|
||
|
case 'jpeg':
|
||
|
case 'jpg':
|
||
|
imageinterlace($this->image, true);
|
||
|
$mimetype = 'image/jpeg';
|
||
|
break;
|
||
|
case 'png':
|
||
|
$mimetype = 'image/png';
|
||
|
break;
|
||
|
default:
|
||
|
$info = getimagesize($this->filename);
|
||
|
$mimetype = $info['mime'];
|
||
|
unset($info);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Output the image
|
||
|
ob_start();
|
||
|
switch ($mimetype) {
|
||
|
case 'image/gif':
|
||
|
imagegif($this->image);
|
||
|
break;
|
||
|
case 'image/jpeg':
|
||
|
imagejpeg($this->image, null, round($quality));
|
||
|
break;
|
||
|
case 'image/png':
|
||
|
imagepng($this->image, null, round(9 * $quality / 100));
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception('Unsupported image format: '.$this->filename);
|
||
|
}
|
||
|
$image_data = ob_get_contents();
|
||
|
ob_end_clean();
|
||
|
|
||
|
// Returns formatted string for img src
|
||
|
return 'data:'.$mimetype.';base64,'.base64_encode($image_data);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Overlay
|
||
|
*
|
||
|
* Overlay an image on top of another, works with 24-bit PNG alpha-transparency
|
||
|
*
|
||
|
* @param string $overlay An image filename or a SimpleImage object
|
||
|
* @param string $position center|top|left|bottom|right|top left|top right|bottom left|bottom right
|
||
|
* @param float|int $opacity Overlay opacity 0-1
|
||
|
* @param int $x_offset Horizontal offset in pixels
|
||
|
* @param int $y_offset Vertical offset in pixels
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function overlay($overlay, $position = 'center', $opacity = 1, $x_offset = 0, $y_offset = 0) {
|
||
|
|
||
|
// Load overlay image
|
||
|
if( !($overlay instanceof SimpleImage) ) {
|
||
|
$overlay = new SimpleImage($overlay);
|
||
|
}
|
||
|
|
||
|
// Convert opacity
|
||
|
$opacity = $opacity * 100;
|
||
|
|
||
|
// Determine position
|
||
|
switch (strtolower($position)) {
|
||
|
case 'top left':
|
||
|
$x = 0 + $x_offset;
|
||
|
$y = 0 + $y_offset;
|
||
|
break;
|
||
|
case 'top right':
|
||
|
$x = $this->width - $overlay->width + $x_offset;
|
||
|
$y = 0 + $y_offset;
|
||
|
break;
|
||
|
case 'top':
|
||
|
$x = ($this->width / 2) - ($overlay->width / 2) + $x_offset;
|
||
|
$y = 0 + $y_offset;
|
||
|
break;
|
||
|
case 'bottom left':
|
||
|
$x = 0 + $x_offset;
|
||
|
$y = $this->height - $overlay->height + $y_offset;
|
||
|
break;
|
||
|
case 'bottom right':
|
||
|
$x = $this->width - $overlay->width + $x_offset;
|
||
|
$y = $this->height - $overlay->height + $y_offset;
|
||
|
break;
|
||
|
case 'bottom':
|
||
|
$x = ($this->width / 2) - ($overlay->width / 2) + $x_offset;
|
||
|
$y = $this->height - $overlay->height + $y_offset;
|
||
|
break;
|
||
|
case 'left':
|
||
|
$x = 0 + $x_offset;
|
||
|
$y = ($this->height / 2) - ($overlay->height / 2) + $y_offset;
|
||
|
break;
|
||
|
case 'right':
|
||
|
$x = $this->width - $overlay->width + $x_offset;
|
||
|
$y = ($this->height / 2) - ($overlay->height / 2) + $y_offset;
|
||
|
break;
|
||
|
case 'center':
|
||
|
default:
|
||
|
$x = ($this->width / 2) - ($overlay->width / 2) + $x_offset;
|
||
|
$y = ($this->height / 2) - ($overlay->height / 2) + $y_offset;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Perform the overlay
|
||
|
$this->imagecopymerge_alpha($this->image, $overlay->image, $x, $y, 0, 0, $overlay->width, $overlay->height, $opacity);
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pixelate
|
||
|
*
|
||
|
* @param int $block_size Size in pixels of each resulting block
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function pixelate($block_size = 10) {
|
||
|
imagefilter($this->image, IMG_FILTER_PIXELATE, $block_size, true);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resize an image to the specified dimensions
|
||
|
*
|
||
|
* @param int $width
|
||
|
* @param int $height
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function resize($width, $height) {
|
||
|
|
||
|
// Generate new GD image
|
||
|
$new = imagecreatetruecolor($width, $height);
|
||
|
|
||
|
if( $this->original_info['format'] === 'gif' ) {
|
||
|
// Preserve transparency in GIFs
|
||
|
$transparent_index = imagecolortransparent($this->image);
|
||
|
$palletsize = imagecolorstotal($this->image);
|
||
|
if ($transparent_index >= 0 && $transparent_index < $palletsize) {
|
||
|
$transparent_color = imagecolorsforindex($this->image, $transparent_index);
|
||
|
$transparent_index = imagecolorallocate($new, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
|
||
|
imagefill($new, 0, 0, $transparent_index);
|
||
|
imagecolortransparent($new, $transparent_index);
|
||
|
}
|
||
|
} else {
|
||
|
// Preserve transparency in PNGs (benign for JPEGs)
|
||
|
imagealphablending($new, false);
|
||
|
imagesavealpha($new, true);
|
||
|
}
|
||
|
|
||
|
// Resize
|
||
|
imagecopyresampled($new, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
|
||
|
|
||
|
// Update meta data
|
||
|
$this->width = $width;
|
||
|
$this->height = $height;
|
||
|
$this->image = $new;
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rotate an image
|
||
|
*
|
||
|
* @param int $angle 0-360
|
||
|
* @param string $bg_color Hex color string, array(red, green, blue) or array(red, green, blue, alpha).
|
||
|
* Where red, green, blue - integers 0-255, alpha - integer 0-127
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function rotate($angle, $bg_color = '#000000') {
|
||
|
|
||
|
// Perform the rotation
|
||
|
$rgba = $this->normalize_color($bg_color);
|
||
|
$bg_color = imagecolorallocatealpha($this->image, $rgba['r'], $rgba['g'], $rgba['b'], $rgba['a']);
|
||
|
$new = imagerotate($this->image, -($this->keep_within($angle, -360, 360)), $bg_color);
|
||
|
imagesavealpha($new, true);
|
||
|
imagealphablending($new, true);
|
||
|
|
||
|
// Update meta data
|
||
|
$this->width = imagesx($new);
|
||
|
$this->height = imagesy($new);
|
||
|
$this->image = $new;
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Save an image
|
||
|
*
|
||
|
* The resulting format will be determined by the file extension.
|
||
|
*
|
||
|
* @param null|string $filename If omitted - original file will be overwritten
|
||
|
* @param null|int $quality Output image quality in percents 0-100
|
||
|
* @param null|string $format The format to use; determined by file extension if null
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
public function save($filename = null, $quality = null, $format = null) {
|
||
|
|
||
|
// Determine quality, filename, and format
|
||
|
$quality = $quality ?: $this->quality;
|
||
|
$filename = $filename ?: $this->filename;
|
||
|
if( $format === null ) {
|
||
|
$format = $this->file_ext($filename) ?: $this->original_info['format'];
|
||
|
}
|
||
|
|
||
|
// Create the image
|
||
|
switch (strtolower($format)) {
|
||
|
case 'gif':
|
||
|
$result = imagegif($this->image, $filename);
|
||
|
break;
|
||
|
case 'jpg':
|
||
|
case 'jpeg':
|
||
|
imageinterlace($this->image, true);
|
||
|
$result = imagejpeg($this->image, $filename, round($quality));
|
||
|
break;
|
||
|
case 'png':
|
||
|
$result = imagepng($this->image, $filename, round(9 * $quality / 100));
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception('Unsupported format');
|
||
|
}
|
||
|
|
||
|
if (!$result) {
|
||
|
throw new Exception('Unable to save image: ' . $filename);
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sepia
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function sepia() {
|
||
|
imagefilter($this->image, IMG_FILTER_GRAYSCALE);
|
||
|
imagefilter($this->image, IMG_FILTER_COLORIZE, 100, 50, 0);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sketch
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function sketch() {
|
||
|
imagefilter($this->image, IMG_FILTER_MEAN_REMOVAL);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Smooth
|
||
|
*
|
||
|
* @param int $level Min = -10, max = 10
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function smooth($level) {
|
||
|
imagefilter($this->image, IMG_FILTER_SMOOTH, $this->keep_within($level, -10, 10));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add text to an image
|
||
|
*
|
||
|
* @param string $text
|
||
|
* @param string $font_file
|
||
|
* @param float|int $font_size
|
||
|
* @param string $color
|
||
|
* @param string $position
|
||
|
* @param int $x_offset
|
||
|
* @param int $y_offset
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
public function text($text, $font_file, $font_size = 12, $color = '#000000', $position = 'center', $x_offset = 0, $y_offset = 0) {
|
||
|
|
||
|
// todo - this method could be improved to support the text angle
|
||
|
$angle = 0;
|
||
|
|
||
|
// Determine text color
|
||
|
$rgba = $this->normalize_color($color);
|
||
|
$color = imagecolorallocatealpha($this->image, $rgba['r'], $rgba['g'], $rgba['b'], $rgba['a']);
|
||
|
|
||
|
// Determine textbox size
|
||
|
$box = imagettfbbox($font_size, $angle, $font_file, $text);
|
||
|
if (empty($box)) {
|
||
|
throw new Exception('Unable to load font: '.$font_file);
|
||
|
}
|
||
|
$box_width = abs($box[6] - $box[2]);
|
||
|
$box_height = abs($box[7] - $box[1]);
|
||
|
|
||
|
// Determine position
|
||
|
switch (strtolower($position)) {
|
||
|
case 'top left':
|
||
|
$x = 0 + $x_offset;
|
||
|
$y = 0 + $y_offset + $box_height;
|
||
|
break;
|
||
|
case 'top right':
|
||
|
$x = $this->width - $box_width + $x_offset;
|
||
|
$y = 0 + $y_offset + $box_height;
|
||
|
break;
|
||
|
case 'top':
|
||
|
$x = ($this->width / 2) - ($box_width / 2) + $x_offset;
|
||
|
$y = 0 + $y_offset + $box_height;
|
||
|
break;
|
||
|
case 'bottom left':
|
||
|
$x = 0 + $x_offset;
|
||
|
$y = $this->height - $box_height + $y_offset + $box_height;
|
||
|
break;
|
||
|
case 'bottom right':
|
||
|
$x = $this->width - $box_width + $x_offset;
|
||
|
$y = $this->height - $box_height + $y_offset + $box_height;
|
||
|
break;
|
||
|
case 'bottom':
|
||
|
$x = ($this->width / 2) - ($box_width / 2) + $x_offset;
|
||
|
$y = $this->height - $box_height + $y_offset + $box_height;
|
||
|
break;
|
||
|
case 'left':
|
||
|
$x = 0 + $x_offset;
|
||
|
$y = ($this->height / 2) - (($box_height / 2) - $box_height) + $y_offset;
|
||
|
break;
|
||
|
case 'right';
|
||
|
$x = $this->width - $box_width + $x_offset;
|
||
|
$y = ($this->height / 2) - (($box_height / 2) - $box_height) + $y_offset;
|
||
|
break;
|
||
|
case 'center':
|
||
|
default:
|
||
|
$x = ($this->width / 2) - ($box_width / 2) + $x_offset;
|
||
|
$y = ($this->height / 2) - (($box_height / 2) - $box_height) + $y_offset;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Add the text
|
||
|
imagesavealpha($this->image, true);
|
||
|
imagealphablending($this->image, true);
|
||
|
imagettftext($this->image, $font_size, $angle, $x, $y, $color, $font_file, $text);
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Thumbnail
|
||
|
*
|
||
|
* This function attempts to get the image to as close to the provided dimensions as possible, and then crops the
|
||
|
* remaining overflow (from the center) to get the image to be the size specified. Useful for generating thumbnails.
|
||
|
*
|
||
|
* @param int $width
|
||
|
* @param int|null $height If omitted - assumed equal to $width
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
*
|
||
|
*/
|
||
|
public function thumbnail($width, $height = null) {
|
||
|
|
||
|
// Determine height
|
||
|
$height = $height ?: $width;
|
||
|
|
||
|
// Determine aspect ratios
|
||
|
$current_aspect_ratio = $this->height / $this->width;
|
||
|
$new_aspect_ratio = $height / $width;
|
||
|
|
||
|
// Fit to height/width
|
||
|
if ($new_aspect_ratio > $current_aspect_ratio) {
|
||
|
$this->fit_to_height($height);
|
||
|
} else {
|
||
|
$this->fit_to_width($width);
|
||
|
}
|
||
|
$left = floor(($this->width / 2) - ($width / 2));
|
||
|
$top = floor(($this->height / 2) - ($height / 2));
|
||
|
|
||
|
// Return trimmed image
|
||
|
return $this->crop($left, $top, $width + $left, $height + $top);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the file extension of the specified file
|
||
|
*
|
||
|
* @param string $filename
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function file_ext($filename) {
|
||
|
|
||
|
if (!preg_match('/\./', $filename)) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
return preg_replace('/^.*\./', '', $filename);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get meta data of image or base64 string
|
||
|
*
|
||
|
* @return SimpleImage
|
||
|
* @throws Exception
|
||
|
*
|
||
|
*/
|
||
|
protected function get_meta_data() {
|
||
|
//gather meta data
|
||
|
if(empty($this->imagestring)) {
|
||
|
$info = getimagesize($this->filename);
|
||
|
|
||
|
switch ($info['mime']) {
|
||
|
case 'image/gif':
|
||
|
$this->image = imagecreatefromgif($this->filename);
|
||
|
break;
|
||
|
case 'image/jpeg':
|
||
|
$this->image = imagecreatefromjpeg($this->filename);
|
||
|
break;
|
||
|
case 'image/png':
|
||
|
$this->image = imagecreatefrompng($this->filename);
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception('Invalid image: '.$this->filename);
|
||
|
}
|
||
|
} elseif (function_exists('getimagesizefromstring')) {
|
||
|
$info = getimagesizefromstring($this->imagestring);
|
||
|
} else {
|
||
|
throw new Exception('PHP 5.4 is required to use method getimagesizefromstring');
|
||
|
}
|
||
|
|
||
|
$this->original_info = array(
|
||
|
'width' => $info[0],
|
||
|
'height' => $info[1],
|
||
|
'orientation' => $this->get_orientation(),
|
||
|
'exif' => function_exists('exif_read_data') && $info['mime'] === 'image/jpeg' && $this->imagestring === null ? $this->exif = @exif_read_data($this->filename) : null,
|
||
|
'format' => preg_replace('/^image\//', '', $info['mime']),
|
||
|
'mime' => $info['mime']
|
||
|
);
|
||
|
$this->width = $info[0];
|
||
|
$this->height = $info[1];
|
||
|
|
||
|
imagesavealpha($this->image, true);
|
||
|
imagealphablending($this->image, true);
|
||
|
|
||
|
return $this;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Same as PHP's imagecopymerge() function, except preserves alpha-transparency in 24-bit PNGs
|
||
|
*
|
||
|
* @param $dst_im
|
||
|
* @param $src_im
|
||
|
* @param $dst_x
|
||
|
* @param $dst_y
|
||
|
* @param $src_x
|
||
|
* @param $src_y
|
||
|
* @param $src_w
|
||
|
* @param $src_h
|
||
|
* @param $pct
|
||
|
*
|
||
|
* @link http://www.php.net/manual/en/function.imagecopymerge.php#88456
|
||
|
*
|
||
|
*/
|
||
|
protected function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) {
|
||
|
|
||
|
// Get image width and height and percentage
|
||
|
$pct /= 100;
|
||
|
$w = imagesx($src_im);
|
||
|
$h = imagesy($src_im);
|
||
|
|
||
|
// Turn alpha blending off
|
||
|
imagealphablending($src_im, false);
|
||
|
|
||
|
// Find the most opaque pixel in the image (the one with the smallest alpha value)
|
||
|
$minalpha = 127;
|
||
|
for ($x = 0; $x < $w; $x++) {
|
||
|
for ($y = 0; $y < $h; $y++) {
|
||
|
$alpha = (imagecolorat($src_im, $x, $y) >> 24) & 0xFF;
|
||
|
if ($alpha < $minalpha) {
|
||
|
$minalpha = $alpha;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Loop through image pixels and modify alpha for each
|
||
|
for ($x = 0; $x < $w; $x++) {
|
||
|
for ($y = 0; $y < $h; $y++) {
|
||
|
// Get current alpha value (represents the TANSPARENCY!)
|
||
|
$colorxy = imagecolorat($src_im, $x, $y);
|
||
|
$alpha = ($colorxy >> 24) & 0xFF;
|
||
|
// Calculate new alpha
|
||
|
if ($minalpha !== 127) {
|
||
|
$alpha = 127 + 127 * $pct * ($alpha - 127) / (127 - $minalpha);
|
||
|
} else {
|
||
|
$alpha += 127 * $pct;
|
||
|
}
|
||
|
// Get the color index with new alpha
|
||
|
$alphacolorxy = imagecolorallocatealpha($src_im, ($colorxy >> 16) & 0xFF, ($colorxy >> 8) & 0xFF, $colorxy & 0xFF, $alpha);
|
||
|
// Set pixel with the new color + opacity
|
||
|
if (!imagesetpixel($src_im, $x, $y, $alphacolorxy)) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Copy it
|
||
|
imagesavealpha($dst_im, true);
|
||
|
imagealphablending($dst_im, true);
|
||
|
imagesavealpha($src_im, true);
|
||
|
imagealphablending($src_im, true);
|
||
|
imagecopy($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensures $value is always within $min and $max range.
|
||
|
*
|
||
|
* If lower, $min is returned. If higher, $max is returned.
|
||
|
*
|
||
|
* @param int|float $value
|
||
|
* @param int|float $min
|
||
|
* @param int|float $max
|
||
|
*
|
||
|
* @return int|float
|
||
|
*
|
||
|
*/
|
||
|
protected function keep_within($value, $min, $max) {
|
||
|
|
||
|
if ($value < $min) {
|
||
|
return $min;
|
||
|
}
|
||
|
|
||
|
if ($value > $max) {
|
||
|
return $max;
|
||
|
}
|
||
|
|
||
|
return $value;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a hex color value to its RGB equivalent
|
||
|
*
|
||
|
* @param string $color Hex color string, array(red, green, blue) or array(red, green, blue, alpha).
|
||
|
* Where red, green, blue - integers 0-255, alpha - integer 0-127
|
||
|
*
|
||
|
* @return array|bool
|
||
|
*
|
||
|
*/
|
||
|
protected function normalize_color($color) {
|
||
|
|
||
|
if (is_string($color)) {
|
||
|
|
||
|
$color = trim($color, '#');
|
||
|
|
||
|
if (strlen($color) == 6) {
|
||
|
list($r, $g, $b) = array(
|
||
|
$color[0].$color[1],
|
||
|
$color[2].$color[3],
|
||
|
$color[4].$color[5]
|
||
|
);
|
||
|
} elseif (strlen($color) == 3) {
|
||
|
list($r, $g, $b) = array(
|
||
|
$color[0].$color[0],
|
||
|
$color[1].$color[1],
|
||
|
$color[2].$color[2]
|
||
|
);
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
return array(
|
||
|
'r' => hexdec($r),
|
||
|
'g' => hexdec($g),
|
||
|
'b' => hexdec($b),
|
||
|
'a' => 0
|
||
|
);
|
||
|
|
||
|
} elseif (is_array($color) && (count($color) == 3 || count($color) == 4)) {
|
||
|
|
||
|
if (isset($color['r'], $color['g'], $color['b'])) {
|
||
|
return array(
|
||
|
'r' => $this->keep_within($color['r'], 0, 255),
|
||
|
'g' => $this->keep_within($color['g'], 0, 255),
|
||
|
'b' => $this->keep_within($color['b'], 0, 255),
|
||
|
'a' => $this->keep_within(isset($color['a']) ? $color['a'] : 0, 0, 127)
|
||
|
);
|
||
|
} elseif (isset($color[0], $color[1], $color[2])) {
|
||
|
return array(
|
||
|
'r' => $this->keep_within($color[0], 0, 255),
|
||
|
'g' => $this->keep_within($color[1], 0, 255),
|
||
|
'b' => $this->keep_within($color[2], 0, 255),
|
||
|
'a' => $this->keep_within(isset($color[3]) ? $color[3] : 0, 0, 127)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
}
|