Files
conservatorio-tomadini/plugins/system/nrframework/NRFramework/Image.php
2024-12-31 11:07:09 +01:00

832 lines
20 KiB
PHP

<?php
/**
* @author Tassos Marinos <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework;
// No direct access
defined('_JEXEC') or die;
use NRFramework\Mimes;
use NRFramework\File;
use Joomla\CMS\Image\Image as JoomlaImage;
use Joomla\Filesystem\Path;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;
class Image
{
/**
* Resize an image.
*
* @param string $source
* @param string $width
* @param string $height
* @param integer $quality
* @param string $mode
* @param boolean $unique_filename
* @param boolean $fix_orientation
* @param string $gif_mode If the uploaded image is a GIF image, how will it be copied? Options: "copy" source, "resize" source
*
* @return mixed
*/
public static function resize($source, $width, $height, $quality = 70, $mode = 'crop', $destination = '', $unique_filename = false, $fix_orientation = true, $gif_mode = 'copy')
{
$width = (int) $width;
$height = (int) $height;
// Destination file name
$destination = empty($destination) ? $source : $destination;
// size must be WIDTHxHEIGHT
$size = $width . 'x' . $height;
switch ($mode)
{
// Crop and Resize
case 'crop':
$mode = 5;
break;
// Scale Fill
case 'stretch':
$mode = 1;
break;
// Fit, will fill empty space with black
case 'fit':
$mode = 6;
break;
default:
$mode = 5;
break;
}
try {
$image = new JoomlaImage($source);
$origWidth = $image->getWidth();
$origHeight = $image->getHeight();
/**
* If the image width is less than the given width,
* set the image width we are resizing to the image's width.
*/
if ($origWidth < $width)
{
$size = $origWidth . 'x';
if ($origHeight < $height)
{
$size .= $origHeight;
}
else
{
$size .= $height;
}
}
else if ($origHeight < $height)
{
$prefix = $width;
if ($origWidth < $width)
{
$prefix = $origWidth;
}
$size = $prefix . 'x' . $origHeight;
}
// Fix orientation
if ($fix_orientation)
{
self::fixOrientation($image);
}
// Determine the MIME of the original file to get the proper type
$mime = Mimes::detectFileType($source);
// PNG images should not have a quality value
$options = $mime == 'image/png' ? ['quality' => 9] : ['quality' => $quality];
// Get the image type
$image_type = self::getImageType($mime);
if ($unique_filename)
{
// Make destination file unique
File::uniquefy($destination);
}
$destination = Path::clean($destination);
// Resize image
if ($mime === 'image/gif')
{
if ($gif_mode === 'copy')
{
File::copy($source, $destination, true);
}
else
{
foreach ($image->generateThumbs($size, $mode) as $thumb)
{
$thumb->toFile($destination, $image_type, $options);
}
}
}
else
{
foreach ($image->generateThumbs($size, $mode) as $thumb)
{
$thumb->toFile($destination, $image_type, $options);
}
}
return $destination;
} catch(\Exception $e) {}
return false;
}
/**
* Resizes an image by height.
*
* @param string $src
* @param string $height
* @param string $destination
* @param int $quality
* @param bool $unique_filename
* @param bool $fix_orientation
* @param string $gif_mode If the uploaded image is a GIF image, how will it be copied? Options: "copy" source, "resize" source
*
* @return bool
*/
public static function resizeByHeight($src, $height, $destination = null, $quality = 70, $unique_filename = false, $fix_orientation = true, $gif_mode = 'copy')
{
$height = (int) $height;
// Create a new JImage object from the source image path
$image = new JoomlaImage($src);
// Fix orientation
if ($fix_orientation)
{
self::fixOrientation($image);
}
// Determine the MIME of the original file to get the proper type
$mime = Mimes::detectFileType($src);
// Get the image type
$image_type = self::getImageType($mime);
// Output file name
$destination = empty($destination) ? $src : $destination;
if ($unique_filename)
{
// Make destination file unique
File::uniquefy($destination);
}
$destination = Path::clean($destination);
// PNG images should not have a quality value
$options = $mime == 'image/png' ? ['quality' => 9] : ['quality' => $quality];
// Get the original width and height of the image
$origWidth = $image->getWidth();
$origHeight = $image->getHeight();
// Calculate the new width based on the desired height
$newWidth = ($origWidth / $origHeight) * $height;
// Resize image
if ($mime === 'image/gif')
{
if ($gif_mode === 'copy')
{
File::copy($source, $destination, true);
}
else
{
$resizedImage = $image->resize($newWidth, $height);
$resizedImage->toFile($destination, $image_type, $options);
}
}
else
{
$resizedImage = $image->resize($newWidth, $height);
$resizedImage->toFile($destination, $image_type, $options);
}
// Return true if the image was successfully resized and saved, false otherwise
return $destination;
}
/**
* Resizes an image by keeping the aspect ratio
*
* @param string $source
* @param array $width
* @param integer $quality
* @param array $destination
* @param boolean $unique_filename
* @param boolean $fix_orientation
* @param string $gif_mode If the uploaded image is a GIF image, how will it be copied? Options: "copy" source, "resize" source
*
* @return boolean
*/
public static function resizeAndKeepAspectRatio($source, $width, $quality = 70, $destination = '', $unique_filename = false, $fix_orientation = true, $gif_mode = 'copy')
{
// Ensure we have received valid image dimensions
if (!count($image_dimensions = getimagesize($source)))
{
return false;
}
// Get the image width
if (!$uploaded_image_width = (int) $image_dimensions[0])
{
return false;
}
// Get the image height
if (!$uploaded_image_height = (int) $image_dimensions[1])
{
return false;
}
$width = (int) $width;
/**
* If the image width is less than the given width,
* set the image width we are resizing to the image's width.
*/
if ($uploaded_image_width < (int) $width)
{
$width = $uploaded_image_width;
}
// Determine the MIME of the original file to get the proper type
$mime = Mimes::detectFileType($source);
// PNG images should not have a quality value
$options = $mime == 'image/png' ? ['quality' => 9] : ['quality' => $quality];
// Get the image type
$image_type = self::getImageType($mime);
try {
// Get image object
$image = new JoomlaImage($source);
// Fix orientation
if ($fix_orientation)
{
self::fixOrientation($image);
}
// Calculate aspect ratio
$ratio = $uploaded_image_width / $uploaded_image_height;
// Get new height based on aspect ratio
$targetHeight = $width / $ratio;
// Output file name
$destination = empty($destination) ? $source : $destination;
if ($unique_filename)
{
// Make destination file unique
File::uniquefy($destination);
}
$destination = Path::clean($destination);
// Resize image
if ($mime === 'image/gif')
{
if ($gif_mode === 'copy')
{
File::copy($source, $destination, true);
}
else
{
$resizedImage = $image->resize($width, $targetHeight, true);
$resizedImage->toFile($destination, $image_type, $options);
}
}
else
{
$resizedImage = $image->resize($width, $targetHeight, true);
$resizedImage->toFile($destination, $image_type, $options);
}
return $destination;
} catch(\Exception $e) {}
return false;
}
public static function resizeByWidthOrHeight($source, $width, $height, $quality = 80, $destination = '', $resize_method = 'crop', $unique_filename = false, $fix_orientation = true)
{
$resized_image = null;
// If width is null, and we have height set, we are resizing by height
if (is_null($width) && $height && !is_null($height))
{
$resized_image = Image::resizeByHeight($source, $height, $destination, 80, $unique_filename, $fix_orientation);
}
else
{
/**
* If height is zero, then we suppose we want to keep aspect ratio.
*
* Resize with width & height: If height is not set
* Resize and keep aspect ratio: If height is set
*/
$resized_image = $height && !is_null($height)
?
Image::resize($source, $width, $height, 80, $resize_method, $destination, $unique_filename, $fix_orientation)
:
Image::resizeAndKeepAspectRatio($source, $width, 80, $destination, $unique_filename, $fix_orientation);
}
return $resized_image;
}
/**
* Returns the orientation of the image.
*
* @param string $path
*
* @return int
*/
public static function getOrientation($path)
{
if (!$exif = @exif_read_data($path))
{
return;
}
return intval(@$exif['Orientation']);
}
/**
* Fixes the orientation of the generated image and ensures it appears with the same orientation as the source.
*
* @param string $path
* @param int $orientation
*
* @return void
*/
public static function fixOrientation(&$image, $orientation = null)
{
$orientation = self::getOrientation($image->getPath());
if(!in_array($orientation, [3, 6, 8]))
{
return;
}
switch ($orientation)
{
case 3:
$image->rotate(180, -1, false);
break;
case 6:
$image->rotate(270, -1, false);
break;
case 8:
$image->rotate(90, -1, false);
break;
}
return true;
}
/**
* Returns the image type based on its mime type
*
* @param string $mime
*
* @return int
*/
public static function getImageType($mime)
{
switch ($mime)
{
case 'image/png':
return IMAGETYPE_PNG;
break;
case 'image/gif':
return IMAGETYPE_GIF;
break;
case 'image/webp':
return IMAGETYPE_WEBP;
break;
case 'image/jpeg':
default:
return IMAGETYPE_JPEG;
break;
}
}
/**
* Creates a watermark from text.
*
* @param string $text
* @param integer $font_size
* @param integer $opacity
* @param string $color
* @param integer $originalWidth
* @param integer $originalHeight
*
* @return object
*/
public static function createWatermarkText($text = '', $_font_size = 30, $opacity = 60, $color = '#ffffff', $originalWidth = null, $originalHeight = null)
{
$font = implode(DIRECTORY_SEPARATOR, [JPATH_SITE, 'media', 'plg_system_nrframework', 'font', 'arial.ttf']);
// Scale down font size based on the original image width or height
$dimension = $originalWidth > $originalHeight ? $originalWidth : $originalHeight;
$font_size = $dimension ? $_font_size * ($dimension / 1000) * 1.2 : $_font_size;
if ($font_size > $_font_size)
{
$font_size = $_font_size;
}
if ($font_size < 14)
{
$font_size = 14;
}
$TextSize = @ImageTTFBBox($font_size, 0, $font, $text) or die;
$TextWidth = abs($TextSize[2]) + abs($TextSize[0]);
$TextHeight = abs($TextSize[7]) + abs($TextSize[1]);
$watermarkImage = imagecreatetruecolor($TextWidth, $TextHeight);
imagealphablending($watermarkImage, false);
imagesavealpha($watermarkImage, true);
$bgText = imagecolorallocatealpha($watermarkImage, 255, 255, 255, 127);
imagefill($watermarkImage, 0, 0, $bgText);
$wmTransp = 127 - ($opacity * 1.27);
$rgb = self::hex2rgb($color, false);
$colorResource = imagecolorallocatealpha($watermarkImage, $rgb[0], $rgb[1], $rgb[2], $wmTransp);
// Create watermark
imagettftext($watermarkImage, $font_size, 0, 0, abs($TextSize[5]), $colorResource, $font, $text);
return $watermarkImage;
}
/**
* Apply the watermark.
*
* @param array $opts
*
* @return void
*/
public static function applyWatermark($opts = [])
{
$defaults = [
'source' => null,
'destination' => null,
'preset' => 'custom',
'type' => 'text',
'text' => null,
'position' => 'bottom-right',
'angle' => 0,
'opacity' => 50,
'size' => 30,
'color' => '#fff'
];
$opts = array_merge($defaults, $opts);
if (!$opts['source'])
{
return false;
}
if (!is_file($opts['source']))
{
return false;
}
$destination = $opts['destination'] ? $opts['destination'] : $opts['source'];
$originalImage = new JoomlaImage($opts['source']);
// Get the dimensions of the original image
$originalWidth = $originalImage->getWidth();
$originalHeight = $originalImage->getHeight();
$watermarkSource = null;
switch ($opts['type'])
{
case 'image':
$watermarkSource = $opts['image'];
break;
case 'text':
default:
if (!$watermarkText = self::getWatermarkText($opts['text_preset'], $opts['text'], $destination))
{
return;
}
$watermarkSource = self::createWatermarkText($watermarkText, (int) $opts['size'], 100, $opts['color'], $originalWidth, $originalHeight);
break;
}
if (!$watermarkSource)
{
return;
}
$original_image_mime = $originalImage->getImageFileProperties($opts['source'])->mime;
// Create the final image
$finalImage = imagecreatetruecolor($originalWidth, $originalHeight);
$watermarkOpacity = (int) $opts['opacity'];
// Add a black background color if the image is PNG and watermark opacity is not 100, as imagecopymerge() doesn't work with transparent PNG images
if ($original_image_mime === 'image/png')
{
if ($watermarkOpacity === 100)
{
imagesavealpha($finalImage, true);
$trans_background = imagecolorallocatealpha($finalImage, 0, 0, 0, 127);
imagefill($finalImage, 0, 0, $trans_background);
}
else
{
$black = imagecolorallocate($finalImage, 0, 0, 0);
imagefill($finalImage, 0, 0, $black);
}
}
// Copy original image to the final image
imagecopy($finalImage, $originalImage->getHandle(), 0, 0, 0, 0, $originalWidth, $originalHeight);
// Get watermark image
$watermarkImage = new JoomlaImage($watermarkSource);
// Rotate it
if ($opts['angle'])
{
$angle = $opts['angle'] ? 360 - (int) $opts['angle'] : 0;
$watermarkImage->rotate($angle, -1, false);
}
// Get the dimensions of the watermark image
$watermarkWidth = $watermarkImage->getWidth();
$watermarkHeight = $watermarkImage->getHeight();
if (!$watermarkWidth || !$watermarkHeight)
{
return false;
}
// Final watermark width/height
$width = $watermarkWidth;
$height = $watermarkHeight;
// Scale watermark image
if ($opts['type'] === 'image')
{
$scaleFactor = min($originalWidth / $watermarkWidth, $originalHeight / $watermarkHeight);
// Calculate the new dimensions of the watermark image
$width = $watermarkWidth * $scaleFactor;
$height = $watermarkHeight * $scaleFactor;
if ($width > $watermarkWidth)
{
$width = $watermarkWidth;
$height = $watermarkHeight;
}
}
$width = (int) $width;
$height = (int) $height;
list($dest_x, $dest_y) = self::getWatermarkPosition($opts['position'], $originalWidth, $originalHeight, $width, $height);
// Resize watermark image before applying it into the final image
$watermarkImage = $watermarkImage->resize($width, $height);
// Copy the watermark image into the final image
if ($watermarkOpacity === 100)
{
imagecopy($finalImage, $watermarkImage->getHandle(), round($dest_x), round($dest_y), 0, 0, $width, $height);
}
else
{
self::imagecopymerge_alpha($finalImage, $watermarkImage->getHandle(), round($dest_x), round($dest_y), 0, 0, $width, $height, $watermarkOpacity);
}
// Save final image
switch ($original_image_mime)
{
case 'image/gif':
imagegif($finalImage, $destination);
break;
case 'image/webp':
imagewebp($finalImage, $destination, 70);
break;
case 'image/png':
imagepng($finalImage, $destination, 6);
break;
default:
imagejpeg($finalImage, $destination, 70);
break;
}
imagedestroy($finalImage);
$originalImage->destroy();
$watermarkImage->destroy();
}
/**
* imagecopy but with alpha channel support.
*
* @param object $dst_im
* @param object $src_im
* @param integer $dst_x
* @param integer $dst_y
* @param integer $src_x
* @param integer $src_y
* @param integer $src_w
*
* @return void
*/
public static function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct)
{
$cut = imagecreatetruecolor($src_w, $src_h);
// copying relevant section from background to the cut resource
imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
// copying relevant section from watermark to the cut resource
imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
// insert cut resource to destination image
imagecopymerge($dst_im, $cut, $dst_x, $dst_y, 0, 0, $src_w, $src_h, $pct);
}
/**
* Returns the watermark position.
*
* @param string $position
* @param integer $originalWidth
* @param integer $originalHeight
* @param integer $width
* @param integer $height
*
* @return array
*/
public static function getWatermarkPosition($position, $originalWidth, $originalHeight, $width, $height)
{
/**
* Position watermark based on given position.
*
* top-left
* top-center
* top-right
* center-left
* center-center
* center-right
* bottom-left
* bottom-center
* bottom-right
*/
$position = explode('-', $position);
// Padding from corner
$yPOS = $xPOS = 10;
$dest_x = $dest_y = 0;
if (isset($position[0]))
{
switch ($position[0])
{
case 'top':
$dest_y = 0 + $yPOS;
break;
case 'center':
$dest_y = round($originalHeight / 2) - round($height / 2);
break;
case 'bottom':
$dest_y = $originalHeight - $height - $yPOS;
break;
}
}
if (isset($position[1]))
{
switch ($position[1])
{
case 'left':
$dest_x = 0 + $xPOS;
break;
case 'center':
$dest_x = round($originalWidth / 2) - round($width / 2);
break;
case 'right':
$dest_x = $originalWidth - $width - $xPOS;
break;
}
}
return [$dest_x, $dest_y];
}
/**
* Returns the watermark text.
*
* @param string $preset
* @param string $text
* @param string $filename
*
* @return string
*/
public static function getWatermarkText($preset = '', $text = '', $filename = '')
{
switch ($preset)
{
case 'site_name':
$text = Factory::getApplication()->get('sitename');
break;
case 'site_url':
$text = Uri::root();
break;
case 'custom':
$st = new \NRFramework\SmartTags();
// Add file Smart Tags
$file_data = File::pathinfo($filename);
$source_basename = $file_data['basename'];
$file_data['filename'] = $file_data['filename'];
$file_data['basename'] = $source_basename;
$st->add($file_data, 'file.');
$text = $st->replace($text);
break;
}
return $text;
}
/**
* Converts hexidecimal color value to rgb values and returns as array/string
*
* @param string $hex
* @param bool $asString
*
* @return array|string
*/
public static function hex2rgb($hex, $asString = false)
{
// strip off any leading #
if (0 === strpos($hex, '#'))
{
$hex = substr($hex, 1);
}
else if (0 === strpos($hex, '&H'))
{
$hex = substr($hex, 2);
}
// break into hex 3-tuple
$cutpoint = ceil(strlen($hex) / 2)-1;
$rgb = explode(':', wordwrap($hex, $cutpoint, ':', $cutpoint), 3);
// convert each tuple to decimal
$rgb[0] = (isset($rgb[0]) ? hexdec($rgb[0]) : 0);
$rgb[1] = (isset($rgb[1]) ? hexdec($rgb[1]) : 0);
$rgb[2] = (isset($rgb[2]) ? hexdec($rgb[2]) : 0);
return ($asString ? "{$rgb[0]} {$rgb[1]} {$rgb[2]}" : $rgb);
}
}