mirror of
https://github.com/gtbu/Typesetter-5.3-p8.git
synced 2024-11-24 15:19:12 +01:00
577 lines
15 KiB
PHP
577 lines
15 KiB
PHP
<?php
|
|
|
|
namespace gp\tool{
|
|
|
|
defined('is_running') or die('Not an entry point...');
|
|
|
|
includeFile('thirdparty/jpeg-icc/autoload.php');
|
|
includeFile('tool/ImageMeta.php');
|
|
|
|
class Image{
|
|
|
|
/**
|
|
* Reduce the size of an image according to $max_area
|
|
*
|
|
* @since 1.7
|
|
*
|
|
* @param string $img_path location of the image
|
|
* @param int $max_area Maximum area of the image: 600x800 pixels = 480,000 sq/pixels
|
|
* @return bool true if the image doesn't need to be reduced or if a reduction is made
|
|
*/
|
|
|
|
static function CheckArea($img_path,$max_area=1024000){
|
|
global $config, $dataDir;
|
|
|
|
$src_img = self::getSrcImg($img_path); //memory usage before and after this call are small 1093804 vs 1094616
|
|
if( $src_img === false ){
|
|
return false;
|
|
}
|
|
|
|
//Original Size
|
|
$old_x = imagesx($src_img);
|
|
$old_y = imagesy($src_img);
|
|
$old_area = ($old_x * $old_y);
|
|
|
|
//don't enlarge check 1
|
|
if( $old_area < $max_area ){
|
|
return true;
|
|
}
|
|
|
|
//Calculate the new size
|
|
$inv_ratio = $old_y / $old_x;
|
|
|
|
$new_y = sqrt($max_area * $inv_ratio);
|
|
$new_y = round($new_y);
|
|
$new_x = round($max_area / $new_y);
|
|
|
|
//don't enlarge check 2
|
|
$new_area = ($new_y * $new_x);
|
|
if( $new_area > $old_area ){
|
|
return true;
|
|
}
|
|
|
|
$src_path = $img_path;
|
|
$result = self::createImg($src_img, $img_path, 0, 0, 0, 0, $new_x, $new_y, $old_x, $old_y, false, false, $src_path);
|
|
|
|
@chmod($img_path, gp_chmod_file);
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Create a square image at $dest_path from an image at $source_path
|
|
*
|
|
* @param string $source_path location of the source image
|
|
* @param string $dest_path location of the image to be created
|
|
* @param int $size the dimension of the image
|
|
* @param string $type_file a string representing the type of the source file (png, jpg)
|
|
* @return bool
|
|
*/
|
|
static function createSquare($source_path,$dest_path,$size=50,$type_file=false){
|
|
global $config;
|
|
|
|
$img_type = self::getType($source_path);
|
|
if( strpos('svgz', $img_type) === 0 ){
|
|
// image is SVG
|
|
return self::CreateRectSVG($source_path,$dest_path,false,$size,$size);
|
|
}
|
|
|
|
$new_w = $new_h = $size;
|
|
|
|
$src_img = self::getSrcImg($source_path,$type_file);
|
|
if( $src_img === false ){
|
|
return false;
|
|
}
|
|
|
|
//Size
|
|
$old_x = imagesx($src_img);
|
|
$old_y = imagesy($src_img);
|
|
|
|
if( $old_x > $old_y ){
|
|
$off_w = ($old_x - $old_y) / 2;
|
|
$off_h = 0;
|
|
$old_x = $old_y;
|
|
}elseif( $old_y > $old_x ){
|
|
$off_w = 0;
|
|
$off_h = ($old_y - $old_x) / 2;
|
|
$old_y = $old_x;
|
|
}else{
|
|
$off_w = 0;
|
|
$off_h = 0;
|
|
}
|
|
|
|
//don't make the thumbnail larger
|
|
if( ($old_x < $size) && ($old_y < $size ) ){
|
|
$new_w = $new_h = max($old_x,$old_y);
|
|
}
|
|
|
|
$result = self::createImg($src_img, $dest_path, 0, 0, $off_w, $off_h, $new_w, $new_h, $old_x, $old_y, false, false, $source_path);
|
|
|
|
@chmod($dest_path, gp_chmod_file);
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a rectangular image at $dest_path from an image at $source_path
|
|
*
|
|
* @param string $source_path location of the source image
|
|
* @param string $dest_path location of the image to be created
|
|
* @param int $new_w The width of the new image
|
|
* @param int $new_h The height of the new image
|
|
* @param boolean $keep_aspect_ratio Scale to fit / Crop to cover new size
|
|
* @return bool
|
|
*/
|
|
static function CreateRect($source_path,$dest_path,$new_w=50,$new_h=50,$keep_aspect_ratio=false){
|
|
global $config;
|
|
|
|
$img_type = self::getType($source_path);
|
|
if( strpos('svgz', $img_type) === 0 ){
|
|
// image is SVG
|
|
return self::CreateRectSVG($source_path,$dest_path,$keep_aspect_ratio,$new_w,$new_h);
|
|
}
|
|
|
|
$src_img = self::getSrcImg($source_path,$img_type);
|
|
if( $src_img === false ){
|
|
return false;
|
|
}
|
|
|
|
// Size
|
|
$old_w = imagesx($src_img);
|
|
$old_h = imagesy($src_img);
|
|
|
|
// Ratios
|
|
$old_aspect_ratio = $old_w / $old_h;
|
|
$new_aspect_ratio = $new_w / $new_h;
|
|
|
|
$off_w = 0;
|
|
$off_h = 0;
|
|
|
|
if( $keep_aspect_ratio ){
|
|
// scale to fit into new width/height
|
|
if( $old_aspect_ratio > $new_aspect_ratio ){
|
|
// old img is wider than new one
|
|
$new_h = (int)(round($new_h / $old_aspect_ratio * $new_aspect_ratio));
|
|
}else{
|
|
// old img is narrower than new one
|
|
$new_w = (int)(round($new_w / $new_aspect_ratio * $old_aspect_ratio));
|
|
}
|
|
}else{
|
|
// crop to cover new width/height
|
|
if( $old_aspect_ratio > $new_aspect_ratio ){
|
|
// old img is wider than new one
|
|
$old_w_tmp = round($old_h * $new_aspect_ratio);
|
|
$off_w = round(($old_w -$old_w_tmp) / 2);
|
|
$old_w = $old_w_tmp;
|
|
}else{
|
|
// old img is narrower than new one
|
|
$old_h_tmp = round($old_w / $new_aspect_ratio);
|
|
$off_h = round(($old_h - $old_h_tmp) / 2);
|
|
$old_h = $old_h_tmp;
|
|
}
|
|
}
|
|
|
|
/* DEBUG */
|
|
/*
|
|
msg(
|
|
basename($source_path) . ": <br/>"
|
|
. "old_w=" . $old_w . " | old_h=" . $old_h . "<br/>"
|
|
. "off_w=" . $off_w . " | off_h=" . $off_h . "<br/>"
|
|
. "old_aspect_ratio=" . $old_aspect_ratio . "<br/>"
|
|
. "new_aspect_ratio=" . $new_aspect_ratio . "<br/>"
|
|
. "new_w=" . $new_w . " | new_h=" . $new_h . "<br/>"
|
|
// . "<br/><br/>"
|
|
);
|
|
*/
|
|
|
|
$result = self::createImg($src_img, $dest_path, 0, 0, $off_w, $off_h, $new_w, $new_h, $old_w, $old_h, false, false, $source_path);
|
|
|
|
@chmod($dest_path, gp_chmod_file);
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* SVG -- Create a rectangular SVG image at $dest_path from an SVG image at $source_path
|
|
*
|
|
* @param string $source_path location of the source SVG image
|
|
* @param string $dest_path location of the SVG image to be created
|
|
* @param int $size the dimension of the SVG image
|
|
* @param string $type_file a string representing the type of the source file (svg, svgz)
|
|
* @return bool
|
|
*/
|
|
static function CreateRectSVG($source_path, $dest_path, $keep_aspect_ratio, $width=50, $height=50, ){
|
|
|
|
$src_svg = @file_get_contents($source_path);
|
|
if( !$src_svg ){
|
|
// msg($src_svg . "does not exist!");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
if( substr($src_svg,0,2) === hex2bin('1F8B') ){
|
|
// gzip encoded svgz, decode it
|
|
$src_svg = gzdecode($src_svg);
|
|
}
|
|
*/
|
|
|
|
$internalErrors = libxml_use_internal_errors(true);
|
|
/* $disableEntities = libxml_disable_entity_loader(true); php8 deprecated */
|
|
libxml_clear_errors();
|
|
/* $doc->loadXML(file_get_contents($filename),$options); */
|
|
$doc = new \DOMDocument();
|
|
$doc->loadXML($src_svg, LIBXML_NONET);
|
|
libxml_use_internal_errors($internalErrors);
|
|
|
|
if( $error = libxml_get_last_error() ){
|
|
libxml_clear_errors();
|
|
// msg("SVG processing - LibXML Error: " . $error->message );
|
|
return false;
|
|
}
|
|
if( strtolower($doc->documentElement->tagName) !== 'svg'){
|
|
// msg("SVG processing - Error: data stream is not an svg image");
|
|
return false;
|
|
}
|
|
|
|
$svg = $doc->documentElement;
|
|
|
|
// get size
|
|
$svg_width = self::getPxVal($svg->getAttribute('width'));
|
|
$svg_height = self::getPxVal($svg->getAttribute('height'));
|
|
if( $svg->hasAttribute('viewBox') ){
|
|
$viewBox = preg_split('/[\s,]+/', $svg->getAttribute('viewBox'));
|
|
$vb_x = $viewBox[0];
|
|
$vb_y = $viewBox[1];
|
|
$vb_w = $viewBox[2];
|
|
$vb_h = $viewBox[3];
|
|
}
|
|
if( (!$svg_width && !$svg_height) && (!$vb_w && !$vb_h) ){
|
|
// msg("SVG processing - Error: width and height could not be determined");
|
|
// return false;
|
|
// no width/height provided -> we assume a 800 x 800px as default size
|
|
$svg_width = 800;
|
|
$svg_height = 800;
|
|
}
|
|
// msg("SVG file:" . basename($source_path) . " -- svg_width=".$svg_width." | svg_height=".$svg_height);
|
|
if( !$svg->hasAttribute('viewBox') ){
|
|
$svg->setAttribute('viewBox', '0 0 ' . $svg_width . ' ' . $svg_height);
|
|
$vb_x = 0;
|
|
$vb_y = 0;
|
|
$vb_w = $svg_width;
|
|
$vb_h = $svg_height;
|
|
}
|
|
|
|
$w = $vb_w - $vb_x;
|
|
$h = $vb_h - $vb_y;
|
|
// left/top offsets ### needs to be changed ###
|
|
if( $w > $h ){
|
|
$vb_x += ($w - $h) / 2;
|
|
$vb_w -= ($w - $h);
|
|
}else{
|
|
$vb_y += ($h - $w) / 2;
|
|
$vb_h -= ($h - $w);
|
|
}
|
|
|
|
$svg->setAttribute('viewBox', $vb_x . ' ' . $vb_y . ' ' . $vb_w . ' ' . $vb_h);
|
|
$svg->setAttribute('width', $width);
|
|
$svg->setAttribute('height', $height);
|
|
|
|
return self::saveSVG($doc, $dest_path);
|
|
}
|
|
|
|
/**
|
|
* Save SVG document to file
|
|
*/
|
|
static function saveSVG($doc, $path){
|
|
global $langmessage;
|
|
$svg_xml = $doc->saveXML();
|
|
|
|
$result = file_put_contents($path, $svg_xml);
|
|
if( $result ){
|
|
@chmod($path, gp_chmod_file);
|
|
return true;
|
|
}
|
|
// msg($langmessage['OOPS'] . ': Unable to save SVG to ' . $path);
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return pixel value from value+units
|
|
*/
|
|
static function getPxVal($size) {
|
|
$map = array('px'=>1, 'pt'=>1.3333333, 'mm'=>3.7795276, 'ex'=>8, 'em'=>16, 'pc'=>16, 'cm'=>37.795276, 'in'=>96);
|
|
$size = trim($size);
|
|
$value = substr($size, 0, -2);
|
|
$unit = substr($size, -2);
|
|
if( isset($map[$unit]) && is_numeric($value) ){
|
|
$size = $value * $map[$unit];
|
|
}
|
|
if( is_numeric($size) ){
|
|
return (int) round($size);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Return a type file type of a given file given by it's $path on the file system
|
|
* @static
|
|
*/
|
|
static function getType($path){
|
|
$nameParts = explode('.',$path);
|
|
$type = array_pop($nameParts);
|
|
return strtolower($type);
|
|
}
|
|
|
|
/**
|
|
* Attempt to increase php's memory limit using the current memory used and the post_max_size value
|
|
* Generally speaking, memory_limit should be larger than post_max_size http://php.net/manual/en/ini.core.php
|
|
* @static
|
|
*/
|
|
static function AdjustMemoryLimit(){
|
|
|
|
//get memory limit in bytes
|
|
$limit = @ini_get('memory_limit') or '8M';
|
|
$limit = \gp\tool::getByteValue($limit);
|
|
|
|
|
|
//get memory usage or use a default value
|
|
if( function_exists('memory_get_usage') ){
|
|
$memoryUsed = memory_get_usage();
|
|
}else{
|
|
$memoryUsed = 3*1048576; //sizable buffer 3MB
|
|
}
|
|
|
|
//since imageHeight and imageWidth aren't always available
|
|
//use post_max_size to figure maximum memory limit
|
|
$max_post = @ini_get('post_max_size') or '8M'; //defaults to 8M
|
|
$max_post = \gp\tool::getByteValue($max_post);
|
|
|
|
$needed = $max_post + $memoryUsed;
|
|
if( $limit < $needed ){
|
|
@ini_set( 'memory_limit', $needed);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @deprecated 4.3
|
|
*/
|
|
static function getByteValue($value){}
|
|
|
|
|
|
/**
|
|
* Using the given $source_path of an image, return the GD image if possible
|
|
* @param string $source_path The path of the source image
|
|
* @param string $type_file The file type of the source image
|
|
* @return mixed GD image if successful, false otherwise
|
|
*/
|
|
static function getSrcImg($source_path,$type_file=false){
|
|
if( !function_exists('imagetypes') ){
|
|
return false;
|
|
}
|
|
|
|
self::AdjustMemoryLimit();
|
|
|
|
|
|
if( $type_file !== false ){
|
|
$img_type = self::getType($type_file);
|
|
}else{
|
|
$img_type = self::getType($source_path);
|
|
}
|
|
|
|
$supported_types = imagetypes();
|
|
|
|
|
|
//start
|
|
switch($img_type){
|
|
case 'jpg':
|
|
case 'jpeg':
|
|
if( $supported_types & IMG_JPG ){
|
|
return imagecreatefromjpeg($source_path);
|
|
}
|
|
break;
|
|
case 'gif':
|
|
return imagecreatefromgif($source_path);
|
|
break;
|
|
case 'png':
|
|
if( $supported_types & IMG_PNG) {
|
|
return imagecreatefrompng($source_path);
|
|
}
|
|
break;
|
|
case 'bmp';
|
|
if( $supported_types & IMG_WBMP) {
|
|
return imagecreatefromwbmp($source_path);
|
|
}
|
|
break;
|
|
}
|
|
//msg('not supported for thumbnail: '.$img_type);
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Save the GD image ($src_img) to the desired location ($dest_path) with the sizing arguments
|
|
*
|
|
* $src_path is required for ICC/meta data
|
|
* The method will still work w/o it but will not retain ICC/meta data (for possible plugin calls)
|
|
*
|
|
*/
|
|
static function createImg($src_img, $dest_path, $dst_x, $dst_y, $off_w, $off_h, $dst_w, $dst_h, $old_x, $old_y, $new_w = false, $new_h = false, $src_path = false){
|
|
global $config;
|
|
|
|
$preserve_icc_profiles = false;
|
|
$preserve_image_metadata = false;
|
|
|
|
if( $src_path ){
|
|
$img_type = self::getType($src_path);
|
|
$dest_type = self::getType($dest_path);
|
|
if( ($img_type == 'jpg' || $img_type == 'jpeg') && ($dest_type == 'jpg' || $dest_type == 'jpeg') ){
|
|
$preserve_icc_profiles = !empty($config['preserve_icc_profiles']);
|
|
$preserve_image_metadata = !empty($config['preserve_image_metadata']);
|
|
|
|
if( $preserve_icc_profiles ){
|
|
$jpeg_icc = new \JPEG_ICC();
|
|
$has_icc = $jpeg_icc->LoadFromJPEG($src_path);
|
|
if( !$has_icc ){
|
|
$preserve_icc_profiles = false;
|
|
unset($jpeg_icc);
|
|
}
|
|
}
|
|
|
|
if( $preserve_image_metadata ){
|
|
$image_meta = \gp\tool\ImageMeta::getMeta($src_path);
|
|
|
|
/* FOR DEBUGGING - write a text file with acquired metadata and/or possible errors to the source directory */
|
|
/*
|
|
$pi = pathinfo($src_path);
|
|
$debug_metadata_file = $pi['dirname'] . '/' . $pi['filename'] . '_meta.txt';
|
|
$debug_metadata = 'Metadata for image file "' . basename($src_path) . '"' . "\n\n";
|
|
foreach( $image_meta as $mk => $mv ){
|
|
if( is_array($mv) ){
|
|
$debug_metadata .= $mk . ' : ' . "\n";
|
|
foreach( $md as $errk => $errd ){
|
|
$debug_metadata .= ' ' . $errk . ' : ' . $errv . "\n";
|
|
}
|
|
}else{
|
|
$debug_metadata .= $mk . ' : ' . $mv . "\n";
|
|
}
|
|
}
|
|
\gp\tool\Files::Save($debug_metadata_file, $debug_metadata);
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( !$new_w ){
|
|
$new_w = $dst_w;
|
|
}
|
|
|
|
if( !$new_h ){
|
|
$new_h = $dst_h;
|
|
}
|
|
|
|
|
|
$dst_img = imagecreatetruecolor($new_w, $new_h);
|
|
if( !$dst_img ){
|
|
trigger_error('dst_img not created');
|
|
return false;
|
|
}
|
|
$img_type = self::getType($dest_path);
|
|
|
|
|
|
// allow gif & png to have transparent background
|
|
switch($img_type){
|
|
case 'gif':
|
|
case 'png':
|
|
$dst_img = self::Transparency($dst_img);
|
|
break;
|
|
}
|
|
|
|
|
|
if( !imagecopyresampled($dst_img, $src_img, $dst_x, $dst_y, $off_w, $off_h, $dst_w, $dst_h, $old_x, $old_y) ){
|
|
trigger_error('copyresample failed');
|
|
imagedestroy($dst_img);
|
|
imagedestroy($src_img);
|
|
if( $preserve_icc_profiles ){
|
|
unset($jpeg_icc);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
imagedestroy($src_img);
|
|
|
|
$saved = self::SrcToImage($dst_img,$dest_path,$img_type);
|
|
|
|
if( $preserve_image_metadata ){
|
|
$iptc_embedded = \gp\tool\ImageMeta::saveMeta($dest_path, $image_meta);
|
|
}
|
|
|
|
if( $preserve_icc_profiles ){
|
|
$jpeg_icc->SaveToJPEG($dest_path);
|
|
unset($jpeg_icc);
|
|
}
|
|
|
|
return $saved;
|
|
}
|
|
|
|
|
|
static function Transparency($image){
|
|
if( function_exists('imagesavealpha') ){
|
|
imagesavealpha($image,true);
|
|
$bgcolor = imagecolorallocatealpha($image, 133, 134, 135, 127);
|
|
imagefill($image, 0, 0, $bgcolor);
|
|
}
|
|
return $image;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Output image to path based on type
|
|
* will already have checked for support via the getSrcImg function
|
|
*
|
|
*/
|
|
static function SrcToImage($src,$path,$type){
|
|
$result = false;
|
|
switch($type){
|
|
case 'jpeg':
|
|
case 'jpg':
|
|
$result = imagejpeg($src,$path,90);
|
|
break;
|
|
case 'gif':
|
|
$result = imagegif($src,$path);
|
|
break;
|
|
case 'png':
|
|
$result = imagepng($src,$path,9,PNG_ALL_FILTERS);
|
|
break;
|
|
case 'bmp':
|
|
$result = imagewbmp($src,$path);
|
|
break;
|
|
}
|
|
|
|
if( $result === false ){
|
|
@imagedestroy($src);
|
|
return false;
|
|
}
|
|
|
|
@chmod($path, gp_chmod_file);
|
|
|
|
imagedestroy($src);
|
|
return true;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
namespace{
|
|
class thumbnail extends \gp\tool\Image{}
|
|
}
|