mirror of
https://github.com/24eme/signaturepdf.git
synced 2023-08-25 09:33:08 +02:00
617 lines
14 KiB
PHP
617 lines
14 KiB
PHP
<?php
|
|
|
|
/*
|
|
|
|
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
|
|
|
|
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
|
|
|
|
This is free software: you can redistribute it and/or modify it under the
|
|
terms of the GNU General Public License as published by the Free Software
|
|
Foundation, either version 3 of the License, or later.
|
|
|
|
Fat-Free Framework is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
//! Image manipulation tools
|
|
class Image {
|
|
|
|
//@{ Messages
|
|
const
|
|
E_Color='Invalid color specified: %s',
|
|
E_File='File not found',
|
|
E_Font='CAPTCHA font not found',
|
|
E_TTF='No TrueType support in GD module',
|
|
E_Length='Invalid CAPTCHA length: %s';
|
|
//@}
|
|
|
|
//@{ Positional cues
|
|
const
|
|
POS_Left=1,
|
|
POS_Center=2,
|
|
POS_Right=4,
|
|
POS_Top=8,
|
|
POS_Middle=16,
|
|
POS_Bottom=32;
|
|
//@}
|
|
|
|
protected
|
|
//! Source filename
|
|
$file,
|
|
//! Image resource
|
|
$data,
|
|
//! Enable/disable history
|
|
$flag=FALSE,
|
|
//! Filter count
|
|
$count=0;
|
|
|
|
/**
|
|
* Convert RGB hex triad to array
|
|
* @return array|FALSE
|
|
* @param $color int|string
|
|
**/
|
|
function rgb($color) {
|
|
if (is_string($color))
|
|
$color=hexdec($color);
|
|
$hex=str_pad($hex=dechex($color),$color<4096?3:6,'0',STR_PAD_LEFT);
|
|
if (($len=strlen($hex))>6)
|
|
user_error(sprintf(self::E_Color,'0x'.$hex),E_USER_ERROR);
|
|
$color=str_split($hex,$len/3);
|
|
foreach ($color as &$hue) {
|
|
$hue=hexdec(str_repeat($hue,6/$len));
|
|
unset($hue);
|
|
}
|
|
return $color;
|
|
}
|
|
|
|
/**
|
|
* Invert image
|
|
* @return object
|
|
**/
|
|
function invert() {
|
|
imagefilter($this->data,IMG_FILTER_NEGATE);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Adjust brightness (range:-255 to 255)
|
|
* @return object
|
|
* @param $level int
|
|
**/
|
|
function brightness($level) {
|
|
imagefilter($this->data,IMG_FILTER_BRIGHTNESS,$level);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Adjust contrast (range:-100 to 100)
|
|
* @return object
|
|
* @param $level int
|
|
**/
|
|
function contrast($level) {
|
|
imagefilter($this->data,IMG_FILTER_CONTRAST,$level);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Convert to grayscale
|
|
* @return object
|
|
**/
|
|
function grayscale() {
|
|
imagefilter($this->data,IMG_FILTER_GRAYSCALE);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Adjust smoothness
|
|
* @return object
|
|
* @param $level int
|
|
**/
|
|
function smooth($level) {
|
|
imagefilter($this->data,IMG_FILTER_SMOOTH,$level);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Emboss the image
|
|
* @return object
|
|
**/
|
|
function emboss() {
|
|
imagefilter($this->data,IMG_FILTER_EMBOSS);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Apply sepia effect
|
|
* @return object
|
|
**/
|
|
function sepia() {
|
|
imagefilter($this->data,IMG_FILTER_GRAYSCALE);
|
|
imagefilter($this->data,IMG_FILTER_COLORIZE,90,60,45);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Pixelate the image
|
|
* @return object
|
|
* @param $size int
|
|
**/
|
|
function pixelate($size) {
|
|
imagefilter($this->data,IMG_FILTER_PIXELATE,$size,TRUE);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Blur the image using Gaussian filter
|
|
* @return object
|
|
* @param $selective bool
|
|
**/
|
|
function blur($selective=FALSE) {
|
|
imagefilter($this->data,
|
|
$selective?IMG_FILTER_SELECTIVE_BLUR:IMG_FILTER_GAUSSIAN_BLUR);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Apply sketch effect
|
|
* @return object
|
|
**/
|
|
function sketch() {
|
|
imagefilter($this->data,IMG_FILTER_MEAN_REMOVAL);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Flip on horizontal axis
|
|
* @return object
|
|
**/
|
|
function hflip() {
|
|
$tmp=imagecreatetruecolor(
|
|
$width=$this->width(),$height=$this->height());
|
|
imagesavealpha($tmp,TRUE);
|
|
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
|
|
imagecopyresampled($tmp,$this->data,
|
|
0,0,$width-1,0,$width,$height,-$width,$height);
|
|
imagedestroy($this->data);
|
|
$this->data=$tmp;
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Flip on vertical axis
|
|
* @return object
|
|
**/
|
|
function vflip() {
|
|
$tmp=imagecreatetruecolor(
|
|
$width=$this->width(),$height=$this->height());
|
|
imagesavealpha($tmp,TRUE);
|
|
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
|
|
imagecopyresampled($tmp,$this->data,
|
|
0,0,0,$height-1,$width,$height,$width,-$height);
|
|
imagedestroy($this->data);
|
|
$this->data=$tmp;
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Crop the image
|
|
* @return object
|
|
* @param $x1 int
|
|
* @param $y1 int
|
|
* @param $x2 int
|
|
* @param $y2 int
|
|
**/
|
|
function crop($x1,$y1,$x2,$y2) {
|
|
$tmp=imagecreatetruecolor($width=$x2-$x1+1,$height=$y2-$y1+1);
|
|
imagesavealpha($tmp,TRUE);
|
|
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
|
|
imagecopyresampled($tmp,$this->data,
|
|
0,0,$x1,$y1,$width,$height,$width,$height);
|
|
imagedestroy($this->data);
|
|
$this->data=$tmp;
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Resize image (Maintain aspect ratio); Crop relative to center
|
|
* if flag is enabled; Enlargement allowed if flag is enabled
|
|
* @return object
|
|
* @param $width int
|
|
* @param $height int
|
|
* @param $crop bool
|
|
* @param $enlarge bool
|
|
**/
|
|
function resize($width=NULL,$height=NULL,$crop=TRUE,$enlarge=TRUE) {
|
|
if (is_null($width) && is_null($height))
|
|
return $this;
|
|
$origw=$this->width();
|
|
$origh=$this->height();
|
|
if (is_null($width))
|
|
$width=round(($height/$origh)*$origw);
|
|
if (is_null($height))
|
|
$height=round(($width/$origw)*$origh);
|
|
// Adjust dimensions; retain aspect ratio
|
|
$ratio=$origw/$origh;
|
|
if (!$crop) {
|
|
if ($width/$ratio<=$height)
|
|
$height=round($width/$ratio);
|
|
else
|
|
$width=round($height*$ratio);
|
|
}
|
|
if (!$enlarge) {
|
|
$width=min($origw,$width);
|
|
$height=min($origh,$height);
|
|
}
|
|
// Create blank image
|
|
$tmp=imagecreatetruecolor($width,$height);
|
|
imagesavealpha($tmp,TRUE);
|
|
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
|
|
// Resize
|
|
if ($crop) {
|
|
if ($width/$ratio<=$height) {
|
|
$cropw=round($origh*$width/$height);
|
|
imagecopyresampled($tmp,$this->data,
|
|
0,0,($origw-$cropw)/2,0,$width,$height,$cropw,$origh);
|
|
}
|
|
else {
|
|
$croph=round($origw*$height/$width);
|
|
imagecopyresampled($tmp,$this->data,
|
|
0,0,0,($origh-$croph)/2,$width,$height,$origw,$croph);
|
|
}
|
|
}
|
|
else
|
|
imagecopyresampled($tmp,$this->data,
|
|
0,0,0,0,$width,$height,$origw,$origh);
|
|
imagedestroy($this->data);
|
|
$this->data=$tmp;
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Rotate image
|
|
* @return object
|
|
* @param $angle int
|
|
**/
|
|
function rotate($angle) {
|
|
$this->data=imagerotate($this->data,$angle,
|
|
imagecolorallocatealpha($this->data,0,0,0,127));
|
|
imagesavealpha($this->data,TRUE);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Apply an image overlay
|
|
* @return object
|
|
* @param $img object
|
|
* @param $align int|array
|
|
* @param $alpha int
|
|
**/
|
|
function overlay(Image $img,$align=NULL,$alpha=100) {
|
|
if (is_null($align))
|
|
$align=self::POS_Right|self::POS_Bottom;
|
|
if (is_array($align)) {
|
|
list($posx,$posy)=$align;
|
|
$align = 0;
|
|
}
|
|
$ovr=imagecreatefromstring($img->dump());
|
|
imagesavealpha($ovr,TRUE);
|
|
$imgw=$this->width();
|
|
$imgh=$this->height();
|
|
$ovrw=imagesx($ovr);
|
|
$ovrh=imagesy($ovr);
|
|
if ($align & self::POS_Left)
|
|
$posx=0;
|
|
if ($align & self::POS_Center)
|
|
$posx=($imgw-$ovrw)/2;
|
|
if ($align & self::POS_Right)
|
|
$posx=$imgw-$ovrw;
|
|
if ($align & self::POS_Top)
|
|
$posy=0;
|
|
if ($align & self::POS_Middle)
|
|
$posy=($imgh-$ovrh)/2;
|
|
if ($align & self::POS_Bottom)
|
|
$posy=$imgh-$ovrh;
|
|
if (empty($posx))
|
|
$posx=0;
|
|
if (empty($posy))
|
|
$posy=0;
|
|
if ($alpha==100)
|
|
imagecopy($this->data,$ovr,$posx,$posy,0,0,$ovrw,$ovrh);
|
|
else {
|
|
$cut=imagecreatetruecolor($ovrw,$ovrh);
|
|
imagecopy($cut,$this->data,0,0,$posx,$posy,$ovrw,$ovrh);
|
|
imagecopy($cut,$ovr,0,0,0,0,$ovrw,$ovrh);
|
|
imagecopymerge($this->data,
|
|
$cut,$posx,$posy,0,0,$ovrw,$ovrh,$alpha);
|
|
}
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Generate identicon
|
|
* @return object
|
|
* @param $str string
|
|
* @param $size int
|
|
* @param $blocks int
|
|
**/
|
|
function identicon($str,$size=64,$blocks=4) {
|
|
$sprites=[
|
|
[.5,1,1,0,1,1],
|
|
[.5,0,1,0,.5,1,0,1],
|
|
[.5,0,1,0,1,1,.5,1,1,.5],
|
|
[0,.5,.5,0,1,.5,.5,1,.5,.5],
|
|
[0,.5,1,0,1,1,0,1,1,.5],
|
|
[1,0,1,1,.5,1,1,.5,.5,.5],
|
|
[0,0,1,0,1,.5,0,0,.5,1,0,1],
|
|
[0,0,.5,0,1,.5,.5,1,0,1,.5,.5],
|
|
[.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5],
|
|
[0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1],
|
|
[0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1],
|
|
[.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25],
|
|
[0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1],
|
|
[0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25],
|
|
[0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1],
|
|
[0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1]
|
|
];
|
|
$hash=sha1($str);
|
|
$this->data=imagecreatetruecolor($size,$size);
|
|
list($r,$g,$b)=$this->rgb(hexdec(substr($hash,-3)));
|
|
$fg=imagecolorallocate($this->data,$r,$g,$b);
|
|
imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT);
|
|
$ctr=count($sprites);
|
|
$dim=$blocks*floor($size/$blocks)*2/$blocks;
|
|
for ($j=0,$y=ceil($blocks/2);$j<$y;++$j)
|
|
for ($i=$j,$x=$blocks-1-$j;$i<$x;++$i) {
|
|
$sprite=imagecreatetruecolor($dim,$dim);
|
|
imagefill($sprite,0,0,IMG_COLOR_TRANSPARENT);
|
|
$block=$sprites[hexdec($hash[($j*$blocks+$i)*2])%$ctr];
|
|
for ($k=0,$pts=count($block);$k<$pts;++$k)
|
|
$block[$k]*=$dim;
|
|
imagefilledpolygon($sprite,$block,$pts/2,$fg);
|
|
for ($k=0;$k<4;++$k) {
|
|
imagecopyresampled($this->data,$sprite,
|
|
$i*$dim/2,$j*$dim/2,0,0,$dim/2,$dim/2,$dim,$dim);
|
|
$this->data=imagerotate($this->data,90,
|
|
imagecolorallocatealpha($this->data,0,0,0,127));
|
|
}
|
|
imagedestroy($sprite);
|
|
}
|
|
imagesavealpha($this->data,TRUE);
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Generate CAPTCHA image
|
|
* @return object|FALSE
|
|
* @param $font string
|
|
* @param $size int
|
|
* @param $len int
|
|
* @param $key string
|
|
* @param $path string
|
|
* @param $fg int
|
|
* @param $bg int
|
|
**/
|
|
function captcha($font,$size=24,$len=5,
|
|
$key=NULL,$path='',$fg=0xFFFFFF,$bg=0x000000) {
|
|
if ((!$ssl=extension_loaded('openssl')) && ($len<4 || $len>13)) {
|
|
user_error(sprintf(self::E_Length,$len),E_USER_ERROR);
|
|
return FALSE;
|
|
}
|
|
if (!function_exists('imagettftext')) {
|
|
user_error(self::E_TTF,E_USER_ERROR);
|
|
return FALSE;
|
|
}
|
|
$fw=Base::instance();
|
|
foreach ($fw->split($path?:$fw->UI.';./') as $dir)
|
|
if (is_file($path=$dir.$font)) {
|
|
$seed=strtoupper(substr(
|
|
$ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(),
|
|
-$len));
|
|
$block=$size*3;
|
|
$tmp=[];
|
|
for ($i=0,$width=0,$height=0;$i<$len;++$i) {
|
|
// Process at 2x magnification
|
|
$box=imagettfbbox($size*2,0,$path,$seed[$i]);
|
|
$w=$box[2]-$box[0];
|
|
$h=$box[1]-$box[5];
|
|
$char=imagecreatetruecolor($block,$block);
|
|
imagefill($char,0,0,$bg);
|
|
imagettftext($char,$size*2,0,
|
|
($block-$w)/2,$block-($block-$h)/2,
|
|
$fg,$path,$seed[$i]);
|
|
$char=imagerotate($char,mt_rand(-30,30),
|
|
imagecolorallocatealpha($char,0,0,0,127));
|
|
// Reduce to normal size
|
|
$tmp[$i]=imagecreatetruecolor(
|
|
($w=imagesx($char))/2,($h=imagesy($char))/2);
|
|
imagefill($tmp[$i],0,0,IMG_COLOR_TRANSPARENT);
|
|
imagecopyresampled($tmp[$i],
|
|
$char,0,0,0,0,$w/2,$h/2,$w,$h);
|
|
imagedestroy($char);
|
|
$width+=$i+1<$len?$block/2:$w/2;
|
|
$height=max($height,$h/2);
|
|
}
|
|
$this->data=imagecreatetruecolor($width,$height);
|
|
imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT);
|
|
for ($i=0;$i<$len;++$i) {
|
|
imagecopy($this->data,$tmp[$i],
|
|
$i*$block/2,($height-imagesy($tmp[$i]))/2,0,0,
|
|
imagesx($tmp[$i]),imagesy($tmp[$i]));
|
|
imagedestroy($tmp[$i]);
|
|
}
|
|
imagesavealpha($this->data,TRUE);
|
|
if ($key)
|
|
$fw->$key=$seed;
|
|
return $this->save();
|
|
}
|
|
user_error(self::E_Font,E_USER_ERROR);
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Return image width
|
|
* @return int
|
|
**/
|
|
function width() {
|
|
return imagesx($this->data);
|
|
}
|
|
|
|
/**
|
|
* Return image height
|
|
* @return int
|
|
**/
|
|
function height() {
|
|
return imagesy($this->data);
|
|
}
|
|
|
|
/**
|
|
* Send image to HTTP client
|
|
* @return NULL
|
|
**/
|
|
function render() {
|
|
$args=func_get_args();
|
|
$format=$args?array_shift($args):'png';
|
|
if (PHP_SAPI!='cli') {
|
|
header('Content-Type: image/'.$format);
|
|
header('X-Powered-By: '.Base::instance()->PACKAGE);
|
|
}
|
|
call_user_func_array(
|
|
'image'.$format,
|
|
array_merge([$this->data,NULL],$args)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Return image as a string
|
|
* @return string
|
|
**/
|
|
function dump() {
|
|
$args=func_get_args();
|
|
$format=$args?array_shift($args):'png';
|
|
ob_start();
|
|
call_user_func_array(
|
|
'image'.$format,
|
|
array_merge([$this->data,NULL],$args)
|
|
);
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Return image resource
|
|
* @return resource
|
|
**/
|
|
function data() {
|
|
return $this->data;
|
|
}
|
|
|
|
/**
|
|
* Save current state
|
|
* @return object
|
|
**/
|
|
function save() {
|
|
$fw=Base::instance();
|
|
if ($this->flag) {
|
|
if (!is_dir($dir=$fw->TEMP))
|
|
mkdir($dir,Base::MODE,TRUE);
|
|
++$this->count;
|
|
$fw->write($dir.'/'.$fw->SEED.'.'.
|
|
$fw->hash($this->file).'-'.$this->count.'.png',
|
|
$this->dump());
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Revert to specified state
|
|
* @return object
|
|
* @param $state int
|
|
**/
|
|
function restore($state=1) {
|
|
$fw=Base::instance();
|
|
if ($this->flag && is_file($file=($path=$fw->TEMP.
|
|
$fw->SEED.'.'.$fw->hash($this->file).'-').$state.'.png')) {
|
|
if (is_resource($this->data))
|
|
imagedestroy($this->data);
|
|
$this->data=imagecreatefromstring($fw->read($file));
|
|
imagesavealpha($this->data,TRUE);
|
|
foreach (glob($path.'*.png',GLOB_NOSORT) as $match)
|
|
if (preg_match('/-(\d+)\.png/',$match,$parts) &&
|
|
$parts[1]>$state)
|
|
@unlink($match);
|
|
$this->count=$state;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Undo most recently applied filter
|
|
* @return object
|
|
**/
|
|
function undo() {
|
|
if ($this->flag) {
|
|
if ($this->count)
|
|
$this->count--;
|
|
return $this->restore($this->count);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Load string
|
|
* @return object|FALSE
|
|
* @param $str string
|
|
**/
|
|
function load($str) {
|
|
if (!$this->data=@imagecreatefromstring($str))
|
|
return FALSE;
|
|
imagesavealpha($this->data,TRUE);
|
|
$this->save();
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Instantiate image
|
|
* @param $file string
|
|
* @param $flag bool
|
|
* @param $path string
|
|
**/
|
|
function __construct($file=NULL,$flag=FALSE,$path=NULL) {
|
|
$this->flag=$flag;
|
|
if ($file) {
|
|
$fw=Base::instance();
|
|
// Create image from file
|
|
$this->file=$file;
|
|
if (!isset($path))
|
|
$path=$fw->UI.';./';
|
|
foreach ($fw->split($path,FALSE) as $dir)
|
|
if (is_file($dir.$file))
|
|
return $this->load($fw->read($dir.$file));
|
|
user_error(self::E_File,E_USER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrap-up
|
|
* @return NULL
|
|
**/
|
|
function __destruct() {
|
|
if (is_resource($this->data)) {
|
|
imagedestroy($this->data);
|
|
$fw=Base::instance();
|
|
$path=$fw->TEMP.$fw->SEED.'.'.$fw->hash($this->file);
|
|
if ($glob=@glob($path.'*.png',GLOB_NOSORT))
|
|
foreach ($glob as $match)
|
|
if (preg_match('/-(\d+)\.png/',$match))
|
|
@unlink($match);
|
|
}
|
|
}
|
|
|
|
}
|