423 lines
15 KiB
PHP
423 lines
15 KiB
PHP
<?php declare(strict_types=1);
|
|
|
|
/*
|
|
* image thumbnail, rotate, etc
|
|
*/
|
|
|
|
namespace CoreLibs\Output;
|
|
|
|
class Image
|
|
{
|
|
/**
|
|
* converts picture to a thumbnail with max x and max y size
|
|
* @param string $pic source image file with or without path
|
|
* @param int $size_x maximum size width
|
|
* @param int $size_y maximum size height
|
|
* @param string $dummy empty, or file_type to show an icon instead of nothing if file is not found
|
|
* @param string $path if source start is not ROOT path, if empty ROOT is choosen
|
|
* @param string $cache_source cache path, if not given TMP is used
|
|
* @param bool $clear_cache if set to true, will create thumb all the tame
|
|
* @return string|bool thumbnail name, or false for error
|
|
*/
|
|
public static function createThumbnail(
|
|
string $pic,
|
|
int $size_x,
|
|
int $size_y,
|
|
string $dummy = '',
|
|
string $path = '',
|
|
string $cache_source = '',
|
|
bool $clear_cache = false
|
|
) {
|
|
// get image type flags
|
|
$image_types = [
|
|
1 => 'gif',
|
|
2 => 'jpg',
|
|
3 => 'png'
|
|
];
|
|
$return_data = false;
|
|
$CONVERT = '';
|
|
// if CONVERT is not defined, abort
|
|
/** @phan-suppress-next-line PhanUndeclaredConstant */
|
|
if (defined('CONVERT') && is_executable(CONVERT)) {
|
|
/** @phan-suppress-next-line PhanUndeclaredConstant */
|
|
$CONVERT = CONVERT;
|
|
} else {
|
|
return $return_data;
|
|
}
|
|
if (!empty($cache_source)) {
|
|
$tmp_src = $cache_source;
|
|
} else {
|
|
$tmp_src = BASE.TMP;
|
|
}
|
|
// check if pic has a path, and override next sets
|
|
if (strstr($pic, '/') === false) {
|
|
if (empty($path)) {
|
|
$path = BASE;
|
|
}
|
|
$filename = $path.MEDIA.PICTURES.$pic;
|
|
} else {
|
|
$filename = $pic;
|
|
// and get the last part for pic (the filename)
|
|
$tmp = explode('/', $pic);
|
|
$pic = $tmp[(count($tmp) - 1)];
|
|
}
|
|
// does this picture exist and is it a picture
|
|
if (file_exists($filename) && is_file($filename)) {
|
|
list($width, $height, $type) = getimagesize($filename);
|
|
$convert_prefix = '';
|
|
$create_file = false;
|
|
$delete_filename = '';
|
|
// check if we can skip the PDF creation: if we have size, if do not have type, we assume type png
|
|
if (!$type && is_numeric($size_x) && is_numeric($size_y)) {
|
|
$check_thumb = $tmp_src.'thumb_'.$pic.'_'.$size_x.'x'.$size_y.'.'.$image_types[3];
|
|
if (!is_file($check_thumb)) {
|
|
$create_file = true;
|
|
} else {
|
|
$type = 3;
|
|
}
|
|
}
|
|
// if type is not in the list, but returns as PDF, we need to convert to JPEG before
|
|
if (!$type) {
|
|
$output = [];
|
|
$return = null;
|
|
// is this a PDF, if no, return from here with nothing
|
|
$convert_prefix = 'png:';
|
|
# TEMP convert to PNG, we then override the file name
|
|
$convert_string = $CONVERT.' '.$filename.' '.$convert_prefix.$filename.'_TEMP';
|
|
$status = exec($convert_string, $output, $return);
|
|
$filename .= '_TEMP';
|
|
// for delete, in case we need to glob
|
|
$delete_filename = $filename;
|
|
// find file, if we can't find base name, use -0 as the first one (ignore other pages in multiple ones)
|
|
if (!is_file($filename)) {
|
|
$filename .= '-0';
|
|
}
|
|
list($width, $height, $type) = getimagesize($filename);
|
|
}
|
|
// if no size given, set size to original
|
|
if (!$size_x || $size_x < 1 || !is_numeric($size_x)) {
|
|
$size_x = $width;
|
|
}
|
|
if (!$size_y || $size_y < 1 || !is_numeric($size_y)) {
|
|
$size_y = $height;
|
|
}
|
|
$thumb = 'thumb_'.$pic.'_'.$size_x.'x'.$size_y.'.'.$image_types[$type];
|
|
$thumbnail = $tmp_src.$thumb;
|
|
// check if we already have this picture converted
|
|
if (!is_file($thumbnail) || $clear_cache == true) {
|
|
// convert the picture
|
|
if ($width > $size_x) {
|
|
$convert_string = $CONVERT.' -geometry '.$size_x.'x '.$filename.' '.$thumbnail;
|
|
$status = exec($convert_string, $output, $return);
|
|
// get the size of the converted data, if converted
|
|
if (is_file($thumbnail)) {
|
|
list ($width, $height, $type) = getimagesize($thumbnail);
|
|
}
|
|
}
|
|
if ($height > $size_y) {
|
|
$convert_string = $CONVERT.' -geometry x'.$size_y.' '.$filename.' '.$thumbnail;
|
|
$status = exec($convert_string, $output, $return);
|
|
}
|
|
}
|
|
if (!is_file($thumbnail)) {
|
|
copy($filename, $thumbnail);
|
|
}
|
|
$return_data = $thumb;
|
|
// if we have a delete filename, delete here with glob
|
|
if ($delete_filename) {
|
|
array_map('unlink', glob($delete_filename.'*'));
|
|
}
|
|
} else {
|
|
if ($dummy && strstr($dummy, '/') === false) {
|
|
// check if we have the "dummy" image flag set
|
|
$filename = PICTURES.ICONS.strtoupper($dummy).".png";
|
|
if ($dummy && file_exists($filename) && is_file($filename)) {
|
|
$return_data = $filename;
|
|
} else {
|
|
$return_data = false;
|
|
}
|
|
} else {
|
|
$filename = $dummy;
|
|
}
|
|
}
|
|
return $return_data;
|
|
}
|
|
|
|
/**
|
|
* simple thumbnail creation for jpeg, png only
|
|
* TODO: add other types like gif, etc
|
|
* - bails with false on failed create
|
|
* - if either size_x or size_y are empty (0)
|
|
* the resize is to max of one size
|
|
* if both are set, those are the max sizes (aspect ration is always ekpt)
|
|
* - if path is not given will cache folder for current path set
|
|
* @param string $filename source file name with full path
|
|
* @param int $thumb_width thumbnail width
|
|
* @param int $thumb_height thumbnail height
|
|
* @param string|null $thumbnail_path altnerative path for thumbnails
|
|
* @param bool $create_dummy if we encounter an invalid file
|
|
* create a dummy image file and return it
|
|
* @param bool $use_cache default to true, set to false to skip
|
|
* creating new image if exists
|
|
* @param bool $high_quality default to true, uses sample version, set to false
|
|
* to use quick but less nice version
|
|
* @param int $jpeg_quality default 80, set image quality for jpeg only
|
|
* @return string|bool thumbnail with path
|
|
*/
|
|
public static function createThumbnailSimple(
|
|
string $filename,
|
|
int $thumb_width = 0,
|
|
int $thumb_height = 0,
|
|
?string $thumbnail_path = null,
|
|
bool $create_dummy = true,
|
|
bool $use_cache = true,
|
|
bool $high_quality = true,
|
|
int $jpeg_quality = 80
|
|
) {
|
|
$thumbnail = false;
|
|
// $this->debug('IMAGE PREPARE', "FILE: $filename (exists ".(string)file_exists($filename)."), WIDTH: $thumb_width, HEIGHT: $thumb_height");
|
|
// check that input image exists and is either jpeg or png
|
|
// also fail if the basic CACHE folder does not exist at all
|
|
if (file_exists($filename) &&
|
|
is_dir(BASE.LAYOUT.CONTENT_PATH.CACHE) &&
|
|
is_writable(BASE.LAYOUT.CONTENT_PATH.CACHE)
|
|
) {
|
|
// $this->debug('IMAGE PREPARE', "FILENAME OK, THUMB WIDTH/HEIGHT OK");
|
|
list($inc_width, $inc_height, $img_type) = getimagesize($filename);
|
|
$thumbnail_write_path = null;
|
|
$thumbnail_web_path = null;
|
|
// path set first
|
|
if ($img_type == IMAGETYPE_JPEG ||
|
|
$img_type == IMAGETYPE_PNG ||
|
|
$create_dummy === true
|
|
) {
|
|
// $this->debug('IMAGE PREPARE', "IMAGE TYPE OK: ".$inc_width.'x'.$inc_height);
|
|
// set thumbnail paths
|
|
$thumbnail_write_path = BASE.LAYOUT.CONTENT_PATH.CACHE.IMAGES;
|
|
$thumbnail_web_path = LAYOUT.CACHE.IMAGES;
|
|
// if images folder in cache does not exist create it, if failed, fall back to base cache folder
|
|
if (!is_dir($thumbnail_write_path)) {
|
|
if (false === mkdir($thumbnail_write_path)) {
|
|
$thumbnail_write_path = BASE.LAYOUT.CONTENT_PATH.CACHE;
|
|
$thumbnail_web_path = LAYOUT.CACHE;
|
|
}
|
|
}
|
|
}
|
|
// do resize or fall back on dummy run
|
|
if ($img_type == IMAGETYPE_JPEG ||
|
|
$img_type == IMAGETYPE_PNG
|
|
) {
|
|
// if missing width or height in thumb, use the set one
|
|
if ($thumb_width == 0) {
|
|
$thumb_width = $inc_width;
|
|
}
|
|
if ($thumb_height == 0) {
|
|
$thumb_height = $inc_height;
|
|
}
|
|
// check resize parameters
|
|
if ($inc_width > $thumb_width || $inc_height > $thumb_height) {
|
|
$thumb_width_r = 0;
|
|
$thumb_height_r = 0;
|
|
// we need to keep the aspect ration on longest side
|
|
if (($inc_height > $inc_width &&
|
|
// and the height is bigger than thumb set
|
|
$inc_height > $thumb_height) ||
|
|
// or the height is smaller or equal width
|
|
// but the width for the thumb is equal to the image height
|
|
($inc_height <= $inc_width &&
|
|
$inc_width == $thumb_width
|
|
)
|
|
) {
|
|
// $this->debug('IMAGE PREPARE', 'HEIGHT > WIDTH');
|
|
$ratio = $inc_height / $thumb_height;
|
|
$thumb_width_r = (int)ceil($inc_width / $ratio);
|
|
$thumb_height_r = $thumb_height;
|
|
} else {
|
|
// $this->debug('IMAGE PREPARE', 'WIDTH > HEIGHT');
|
|
$ratio = $inc_width / $thumb_width;
|
|
$thumb_width_r = $thumb_width;
|
|
$thumb_height_r = (int)ceil($inc_height / $ratio);
|
|
}
|
|
// $this->debug('IMAGE PREPARE', "Ratio: $ratio, Target size $thumb_width_r x $thumb_height_r");
|
|
// set output thumbnail name
|
|
$thumbnail = 'thumb-'.pathinfo($filename)['filename'].'-'.$thumb_width_r.'x'.$thumb_height_r;
|
|
if ($use_cache === false ||
|
|
!file_exists($thumbnail_write_path.$thumbnail)
|
|
) {
|
|
// image, copy source image, offset in image, source x/y, new size, source image size
|
|
$thumb = imagecreatetruecolor($thumb_width_r, $thumb_height_r);
|
|
if ($img_type == IMAGETYPE_PNG) {
|
|
// preservere transaprency
|
|
imagecolortransparent(
|
|
$thumb,
|
|
imagecolorallocatealpha($thumb, 0, 0, 0, 127)
|
|
);
|
|
imagealphablending($thumb, false);
|
|
imagesavealpha($thumb, true);
|
|
}
|
|
$source = null;
|
|
switch ($img_type) {
|
|
case IMAGETYPE_JPEG:
|
|
$source = imagecreatefromjpeg($filename);
|
|
break;
|
|
case IMAGETYPE_PNG:
|
|
$source = imagecreatefrompng($filename);
|
|
break;
|
|
}
|
|
// check that we have a source image resource
|
|
if ($source !== null) {
|
|
// resize no shift
|
|
if ($high_quality === true) {
|
|
imagecopyresized($thumb, $source, 0, 0, 0, 0, $thumb_width_r, $thumb_height_r, $inc_width, $inc_height);
|
|
} else {
|
|
imagecopyresampled($thumb, $source, 0, 0, 0, 0, $thumb_width_r, $thumb_height_r, $inc_width, $inc_height);
|
|
}
|
|
// write file
|
|
switch ($img_type) {
|
|
case IMAGETYPE_JPEG:
|
|
imagejpeg($thumb, $thumbnail_write_path.$thumbnail, $jpeg_quality);
|
|
break;
|
|
case IMAGETYPE_PNG:
|
|
imagepng($thumb, $thumbnail_write_path.$thumbnail);
|
|
break;
|
|
}
|
|
// free up resources (in case we are called in a loop)
|
|
imagedestroy($source);
|
|
imagedestroy($thumb);
|
|
} else {
|
|
$thumbnail = false;
|
|
}
|
|
}
|
|
} else {
|
|
// we just copy over the image as is, we never upscale
|
|
$thumbnail = 'thumb-'.pathinfo($filename)['filename'].'-'.$inc_width.'x'.$inc_height;
|
|
if ($use_cache === false ||
|
|
!file_exists($thumbnail_write_path.$thumbnail)
|
|
) {
|
|
copy($filename, $thumbnail_write_path.$thumbnail);
|
|
}
|
|
}
|
|
// add output path
|
|
if ($thumbnail !== false) {
|
|
$thumbnail = $thumbnail_web_path.$thumbnail;
|
|
}
|
|
} elseif ($create_dummy === true) {
|
|
// create dummy image in the thumbnail size
|
|
// if one side is missing, use the other side to create a square
|
|
if (!$thumb_width) {
|
|
$thumb_width = $thumb_height;
|
|
}
|
|
if (!$thumb_height) {
|
|
$thumb_height = $thumb_width;
|
|
}
|
|
// do we have an image already?
|
|
$thumbnail = 'thumb-'.pathinfo($filename)['filename'].'-'.$thumb_width.'x'.$thumb_height;
|
|
if ($use_cache === false ||
|
|
!file_exists($thumbnail_write_path.$thumbnail)
|
|
) {
|
|
// if both are unset, set to 250
|
|
if ($thumb_height == 0) {
|
|
$thumb_height = 250;
|
|
}
|
|
if ($thumb_width == 0) {
|
|
$thumb_width = 250;
|
|
}
|
|
$thumb = imagecreatetruecolor($thumb_width, $thumb_height);
|
|
// add outside border px = 5% (rounded up)
|
|
// eg 50px -> 2.5px
|
|
$gray = imagecolorallocate($thumb, 200, 200, 200);
|
|
$white = imagecolorallocate($thumb, 255, 255, 255);
|
|
// fill gray background
|
|
imagefill($thumb, 0, 0, $gray);
|
|
// now create rectangle
|
|
if (imagesx($thumb) < imagesy($thumb)) {
|
|
$width = (int)round(imagesx($thumb) / 100 * 5);
|
|
} else {
|
|
$width = (int)round(imagesy($thumb) / 100 * 5);
|
|
}
|
|
imagefilledrectangle($thumb, 0 + $width, 0 + $width, imagesx($thumb) - $width, imagesy($thumb) - $width, $white);
|
|
// add "No valid images source"
|
|
// OR add circle
|
|
// * find center
|
|
// * width/height is 75% of size - border
|
|
// smaller size is taken
|
|
$base_width = imagesx($thumb) > imagesy($thumb) ? imagesy($thumb) : imagesx($thumb);
|
|
// get 75% width
|
|
$cross_width = (int)round((($base_width - ($width * 2)) / 100 * 75) / 2);
|
|
$center_x = (int)round(imagesx($thumb) / 2);
|
|
$center_y = (int)round(imagesy($thumb) / 2);
|
|
imagefilledellipse($thumb, $center_x, $center_y, $cross_width, $cross_width, $gray);
|
|
// find top left and bottom left for first line
|
|
imagepng($thumb, $thumbnail_write_path.$thumbnail);
|
|
}
|
|
// add web path
|
|
$thumbnail = $thumbnail_web_path.$thumbnail;
|
|
}
|
|
}
|
|
// either return false or the thumbnail name + output path web
|
|
return $thumbnail;
|
|
}
|
|
|
|
/**
|
|
* reads the rotation info of an file and rotates it to be correctly upright
|
|
* this is done because not all software honers the exit Orientation flag
|
|
* only works with jpg or png
|
|
* @param string $filename path + filename to rotate. This file must be writeable
|
|
* @return void
|
|
*/
|
|
public static function correctImageOrientation($filename): void
|
|
{
|
|
if (function_exists('exif_read_data') && is_writeable($filename)) {
|
|
list($inc_width, $inc_height, $img_type) = getimagesize($filename);
|
|
// add @ to avoid "file not supported error"
|
|
$exif = @exif_read_data($filename);
|
|
$orientation = null;
|
|
$img = null;
|
|
if ($exif && isset($exif['Orientation'])) {
|
|
$orientation = $exif['Orientation'];
|
|
}
|
|
if ($orientation != 1) {
|
|
switch ($img_type) {
|
|
case IMAGETYPE_JPEG:
|
|
$img = imagecreatefromjpeg($filename);
|
|
break;
|
|
case IMAGETYPE_PNG:
|
|
$img = imagecreatefrompng($filename);
|
|
break;
|
|
}
|
|
$deg = 0;
|
|
// 1 top, 6: left, 8: right, 3: bottom
|
|
switch ($orientation) {
|
|
case 3:
|
|
$deg = 180;
|
|
break;
|
|
case 6:
|
|
$deg = -90;
|
|
break;
|
|
case 8:
|
|
$deg = 90;
|
|
break;
|
|
}
|
|
if ($img !== null) {
|
|
if ($deg) {
|
|
$img = imagerotate($img, $deg, 0);
|
|
}
|
|
// then rewrite the rotated image back to the disk as $filename
|
|
switch ($img_type) {
|
|
case IMAGETYPE_JPEG:
|
|
imagejpeg($img, $filename);
|
|
break;
|
|
case IMAGETYPE_PNG:
|
|
imagepng($img, $filename);
|
|
break;
|
|
}
|
|
// clean up image if we have an image
|
|
imagedestroy($img);
|
|
}
|
|
} // only if we need to rotate
|
|
} // function exists & file is writeable, else do nothing
|
|
}
|
|
}
|
|
|
|
// __END__
|