2012-01-01 11:57:26 -05:00
< ? php
2021-07-31 17:17:40 -04:00
declare ( strict_types = 1 );
2024-08-27 10:18:42 -04:00
2012-01-01 11:57:26 -05:00
/**
2024-05-23 03:26:56 -04:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2012-01-01 11:57:26 -05:00
*/
2024-08-27 10:18:42 -04:00
namespace OC ;
2024-08-27 10:34:31 -04:00
use finfo ;
2024-08-27 10:55:36 -04:00
use GdImage ;
2024-08-27 11:03:10 -04:00
use OCP\IAppConfig ;
2024-08-27 10:55:36 -04:00
use OCP\IConfig ;
2020-02-15 20:34:09 -05:00
use OCP\IImage ;
2024-08-27 10:55:36 -04:00
use OCP\Server ;
use Psr\Log\LoggerInterface ;
2020-02-15 20:34:09 -05:00
2015-02-26 05:37:37 -05:00
/**
* Class for basic image manipulation
*/
2024-08-27 10:18:42 -04:00
class Image implements IImage {
2022-12-21 14:01:19 -05:00
// Default memory limit for images to load (256 MBytes).
protected const DEFAULT_MEMORY_LIMIT = 256 ;
2021-12-16 03:17:11 -05:00
2021-07-31 17:17:40 -04:00
// Default quality for jpeg images
protected const DEFAULT_JPEG_QUALITY = 80 ;
2024-03-26 04:58:36 -04:00
// Default quality for webp images
protected const DEFAULT_WEBP_QUALITY = 80 ;
2024-08-27 10:55:36 -04:00
// tmp resource.
protected GdImage | false $resource = false ;
// Default to png if file type isn't evident.
protected int $imageType = IMAGETYPE_PNG ;
// Default to png
protected ? string $mimeType = 'image/png' ;
protected ? string $filePath = null ;
private ? finfo $fileInfo = null ;
private LoggerInterface $logger ;
2024-08-27 11:03:10 -04:00
private IAppConfig $appConfig ;
2024-08-27 10:55:36 -04:00
private IConfig $config ;
private ? array $exif = null ;
2014-11-05 10:44:19 -05:00
2012-01-01 11:57:26 -05:00
/**
2018-01-17 05:46:30 -05:00
* @ throws \InvalidArgumentException in case the $imageRef parameter is not null
2014-03-17 03:17:56 -04:00
*/
2024-08-27 10:34:31 -04:00
public function __construct (
2024-08-27 10:55:36 -04:00
? LoggerInterface $logger = null ,
2024-08-27 11:03:10 -04:00
? IAppConfig $appConfig = null ,
2024-08-27 10:55:36 -04:00
? IConfig $config = null ,
2024-08-27 10:34:31 -04:00
) {
2024-08-27 10:55:36 -04:00
$this -> logger = $logger ? ? Server :: get ( LoggerInterface :: class );
2024-08-27 11:03:10 -04:00
$this -> appConfig = $appConfig ? ? Server :: get ( IAppConfig :: class );
2024-08-27 10:55:36 -04:00
$this -> config = $config ? ? Server :: get ( IConfig :: class );
2014-11-05 10:44:19 -05:00
2025-04-01 10:25:35 -04:00
if ( class_exists ( finfo :: class )) {
2013-08-06 10:56:50 -04:00
$this -> fileInfo = new finfo ( FILEINFO_MIME_TYPE );
}
2012-01-01 11:57:26 -05:00
}
2012-01-01 14:06:35 -05:00
/**
2014-11-05 10:44:19 -05:00
* Determine whether the object contains an image resource .
*
2024-04-09 04:48:27 -04:00
* @ psalm - assert - if - true \GdImage $this -> resource
2014-11-05 10:44:19 -05:00
* @ return bool
*/
2021-07-31 17:17:40 -04:00
public function valid () : bool {
2024-04-09 04:48:27 -04:00
if ( is_object ( $this -> resource ) && get_class ( $this -> resource ) === \GdImage :: class ) {
2020-12-03 13:50:45 -05:00
return true ;
}
return false ;
2012-01-01 17:26:24 -05:00
}
/**
2022-09-02 15:47:38 -04:00
* Returns the MIME type of the image or null if no image is loaded .
2014-11-05 10:44:19 -05:00
*
* @ return string
*/
2022-09-02 15:47:38 -04:00
public function mimeType () : ? string {
return $this -> valid () ? $this -> mimeType : null ;
2012-01-01 17:26:24 -05:00
}
/**
2014-11-05 10:44:19 -05:00
* Returns the width of the image or - 1 if no image is loaded .
*
* @ return int
*/
2021-07-31 17:17:40 -04:00
public function width () : int {
2021-12-02 09:38:42 -05:00
if ( $this -> valid ()) {
2024-04-09 04:48:27 -04:00
return imagesx ( $this -> resource );
2021-12-02 05:30:10 -05:00
}
2021-12-02 09:38:42 -05:00
return - 1 ;
2012-01-01 17:26:24 -05:00
}
/**
2014-11-05 10:44:19 -05:00
* Returns the height of the image or - 1 if no image is loaded .
*
* @ return int
*/
2021-07-31 17:17:40 -04:00
public function height () : int {
2021-12-02 09:38:42 -05:00
if ( $this -> valid ()) {
2024-04-09 04:48:27 -04:00
return imagesy ( $this -> resource );
2021-12-02 05:30:10 -05:00
}
2021-12-02 09:38:42 -05:00
return - 1 ;
2012-01-01 14:06:35 -05:00
}
2012-07-05 15:09:48 -04:00
/**
2014-11-05 10:44:19 -05:00
* Returns the width when the image orientation is top - left .
*
* @ return int
*/
2021-07-31 17:17:40 -04:00
public function widthTopLeft () : int {
2012-07-05 15:09:48 -04:00
$o = $this -> getOrientation ();
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->widthTopLeft() Orientation: ' . $o , [ 'app' => 'core' ]);
2014-11-05 10:44:19 -05:00
switch ( $o ) {
2012-07-05 15:09:48 -04:00
case - 1 :
case 1 :
case 2 : // Not tested
case 3 :
case 4 : // Not tested
return $this -> width ();
case 5 : // Not tested
case 6 :
case 7 : // Not tested
case 8 :
return $this -> height ();
}
return $this -> width ();
}
/**
2014-11-05 10:44:19 -05:00
* Returns the height when the image orientation is top - left .
*
* @ return int
*/
2021-07-31 17:17:40 -04:00
public function heightTopLeft () : int {
2012-07-05 15:09:48 -04:00
$o = $this -> getOrientation ();
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->heightTopLeft() Orientation: ' . $o , [ 'app' => 'core' ]);
2014-11-05 10:44:19 -05:00
switch ( $o ) {
2012-07-05 15:09:48 -04:00
case - 1 :
case 1 :
case 2 : // Not tested
case 3 :
case 4 : // Not tested
return $this -> height ();
case 5 : // Not tested
case 6 :
case 7 : // Not tested
case 8 :
return $this -> width ();
}
return $this -> height ();
}
2012-01-01 11:57:26 -05:00
/**
2014-05-19 11:50:53 -04:00
* Outputs the image .
2014-11-05 10:44:19 -05:00
*
2014-03-17 03:17:56 -04:00
* @ param string $mimeType
* @ return bool
*/
2024-03-28 11:13:19 -04:00
public function show ( ? string $mimeType = null ) : bool {
2014-11-05 10:44:19 -05:00
if ( $mimeType === null ) {
2014-03-14 06:13:45 -04:00
$mimeType = $this -> mimeType ();
}
2024-08-29 04:06:20 -04:00
if ( $mimeType !== null ) {
header ( 'Content-Type: ' . $mimeType );
}
2014-03-14 06:13:45 -04:00
return $this -> _output ( null , $mimeType );
2012-01-02 06:09:45 -05:00
}
/**
2014-05-19 11:50:53 -04:00
* Saves the image .
2014-11-05 10:44:19 -05:00
*
2014-03-17 03:17:56 -04:00
* @ param string $filePath
* @ param string $mimeType
* @ return bool
*/
2012-01-02 07:40:23 -05:00
2021-07-31 17:17:40 -04:00
public function save ( ? string $filePath = null , ? string $mimeType = null ) : bool {
2014-11-05 10:44:19 -05:00
if ( $mimeType === null ) {
2014-03-14 06:13:45 -04:00
$mimeType = $this -> mimeType ();
}
2017-02-22 06:17:55 -05:00
if ( $filePath === null ) {
if ( $this -> filePath === null ) {
2020-03-26 04:30:18 -04:00
$this -> logger -> error ( __METHOD__ . '(): called with no path.' , [ 'app' => 'core' ]);
2017-02-22 06:17:55 -05:00
return false ;
} else {
$filePath = $this -> filePath ;
}
2012-01-02 06:09:45 -05:00
}
2014-03-14 06:13:45 -04:00
return $this -> _output ( $filePath , $mimeType );
2012-01-02 06:09:45 -05:00
}
/**
2014-05-19 11:50:53 -04:00
* Outputs / saves the image .
2014-11-05 10:44:19 -05:00
*
2024-08-27 10:55:36 -04:00
* @ throws \Exception
2014-03-17 03:17:56 -04:00
*/
2021-07-31 17:17:40 -04:00
private function _output ( ? string $filePath = null , ? string $mimeType = null ) : bool {
2024-08-27 10:55:36 -04:00
if ( $filePath !== null && $filePath !== '' ) {
2017-02-22 06:17:55 -05:00
if ( ! file_exists ( dirname ( $filePath ))) {
2013-08-06 10:56:50 -04:00
mkdir ( dirname ( $filePath ), 0777 , true );
2017-02-22 06:17:55 -05:00
}
$isWritable = is_writable ( dirname ( $filePath ));
if ( ! $isWritable ) {
2020-03-26 04:30:18 -04:00
$this -> logger -> error ( __METHOD__ . '(): Directory \'' . dirname ( $filePath ) . '\' is not writable.' , [ 'app' => 'core' ]);
2012-01-02 07:40:23 -05:00
return false ;
2022-09-02 15:03:32 -04:00
} elseif ( file_exists ( $filePath ) && ! is_writable ( $filePath )) {
2020-03-26 04:30:18 -04:00
$this -> logger -> error ( __METHOD__ . '(): File \'' . $filePath . '\' is not writable.' , [ 'app' => 'core' ]);
2012-01-02 06:09:45 -05:00
return false ;
}
}
2012-02-07 16:33:01 -05:00
if ( ! $this -> valid ()) {
return false ;
}
2014-03-17 03:17:56 -04:00
$imageType = $this -> imageType ;
2014-11-05 10:44:19 -05:00
if ( $mimeType !== null ) {
switch ( $mimeType ) {
2014-03-14 06:13:45 -04:00
case 'image/gif' :
2014-03-14 06:17:20 -04:00
$imageType = IMAGETYPE_GIF ;
2014-03-14 06:13:45 -04:00
break ;
case 'image/jpeg' :
2014-03-14 06:17:20 -04:00
$imageType = IMAGETYPE_JPEG ;
2014-03-14 06:13:45 -04:00
break ;
case 'image/png' :
2014-03-14 06:17:20 -04:00
$imageType = IMAGETYPE_PNG ;
2014-03-14 06:13:45 -04:00
break ;
case 'image/x-xbitmap' :
2014-03-14 06:17:20 -04:00
$imageType = IMAGETYPE_XBM ;
2014-03-14 06:13:45 -04:00
break ;
case 'image/bmp' :
2016-02-13 15:55:20 -05:00
case 'image/x-ms-bmp' :
2014-03-14 06:17:20 -04:00
$imageType = IMAGETYPE_BMP ;
2014-03-14 06:13:45 -04:00
break ;
2024-03-26 04:58:36 -04:00
case 'image/webp' :
$imageType = IMAGETYPE_WEBP ;
break ;
2014-03-14 06:13:45 -04:00
default :
2024-08-27 10:34:31 -04:00
throw new \Exception ( 'Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format' );
2014-03-14 06:13:45 -04:00
}
}
2014-11-05 10:44:19 -05:00
switch ( $imageType ) {
2012-01-01 17:26:24 -05:00
case IMAGETYPE_GIF :
2013-08-06 10:56:50 -04:00
$retVal = imagegif ( $this -> resource , $filePath );
2012-01-01 17:26:24 -05:00
break ;
case IMAGETYPE_JPEG :
2024-04-22 10:55:42 -04:00
imageinterlace ( $this -> resource , true );
2017-02-22 06:11:42 -05:00
$retVal = imagejpeg ( $this -> resource , $filePath , $this -> getJpegQuality ());
2012-01-01 17:26:24 -05:00
break ;
case IMAGETYPE_PNG :
2013-08-06 10:56:50 -04:00
$retVal = imagepng ( $this -> resource , $filePath );
2012-01-01 17:26:24 -05:00
break ;
case IMAGETYPE_XBM :
2014-03-17 03:40:59 -04:00
if ( function_exists ( 'imagexbm' )) {
$retVal = imagexbm ( $this -> resource , $filePath );
} else {
2024-08-27 10:34:31 -04:00
throw new \Exception ( 'Image::_output(): imagexbm() is not supported.' );
2014-03-17 03:40:59 -04:00
}
2012-01-01 17:26:24 -05:00
break ;
case IMAGETYPE_WBMP :
2013-08-06 10:56:50 -04:00
$retVal = imagewbmp ( $this -> resource , $filePath );
2012-01-01 17:26:24 -05:00
break ;
2012-11-06 04:54:32 -05:00
case IMAGETYPE_BMP :
2021-07-31 17:18:38 -04:00
$retVal = imagebmp ( $this -> resource , $filePath );
2012-11-06 04:54:32 -05:00
break ;
2024-03-26 04:58:36 -04:00
case IMAGETYPE_WEBP :
$retVal = imagewebp ( $this -> resource , null , $this -> getWebpQuality ());
break ;
2012-01-01 17:26:24 -05:00
default :
2013-08-06 10:56:50 -04:00
$retVal = imagepng ( $this -> resource , $filePath );
2012-01-01 17:26:24 -05:00
}
2013-08-06 10:56:50 -04:00
return $retVal ;
2012-01-01 11:57:26 -05:00
}
/**
2014-11-05 10:44:19 -05:00
* Prints the image when called as $image () .
*/
2012-01-01 11:57:26 -05:00
public function __invoke () {
2012-02-09 16:44:26 -05:00
return $this -> show ();
2012-01-01 11:57:26 -05:00
}
2018-01-17 05:46:30 -05:00
/**
2024-04-09 04:48:27 -04:00
* @ param \GdImage $resource
2018-01-17 05:46:30 -05:00
*/
2024-04-09 04:48:27 -04:00
public function setResource ( \GdImage $resource ) : void {
$this -> resource = $resource ;
2018-01-17 05:46:30 -05:00
}
2012-01-01 11:57:26 -05:00
/**
2024-04-09 04:48:27 -04:00
* @ return false | \GdImage Returns the image resource if any
2014-11-05 10:44:19 -05:00
*/
2012-01-01 17:26:24 -05:00
public function resource () {
2012-02-09 16:44:26 -05:00
return $this -> resource ;
2012-01-01 11:57:26 -05:00
}
2018-01-04 04:00:07 -05:00
/**
2022-09-02 15:47:38 -04:00
* @ return string Returns the mimetype of the data . Returns null if the data is not valid .
2018-01-04 04:00:07 -05:00
*/
2022-09-02 15:47:38 -04:00
public function dataMimeType () : ? string {
2018-01-04 04:00:07 -05:00
if ( ! $this -> valid ()) {
2022-09-02 15:47:38 -04:00
return null ;
2018-01-04 04:00:07 -05:00
}
switch ( $this -> mimeType ) {
case 'image/png' :
case 'image/jpeg' :
case 'image/gif' :
2024-03-26 04:58:36 -04:00
case 'image/webp' :
2018-01-04 04:00:07 -05:00
return $this -> mimeType ;
default :
return 'image/png' ;
}
}
2012-01-01 11:57:26 -05:00
/**
2015-03-13 12:23:02 -04:00
* @ return null | string Returns the raw image data .
2014-11-05 10:44:19 -05:00
*/
2021-07-31 17:17:40 -04:00
public function data () : ? string {
2015-03-13 12:23:02 -04:00
if ( ! $this -> valid ()) {
return null ;
}
2012-01-01 11:57:26 -05:00
ob_start ();
2013-08-06 10:56:50 -04:00
switch ( $this -> mimeType ) {
2024-08-23 09:10:27 -04:00
case 'image/png' :
2013-08-06 10:56:50 -04:00
$res = imagepng ( $this -> resource );
break ;
2024-08-23 09:10:27 -04:00
case 'image/jpeg' :
2024-04-09 04:48:27 -04:00
imageinterlace ( $this -> resource , true );
2017-02-22 06:11:42 -05:00
$quality = $this -> getJpegQuality ();
2021-07-31 17:17:40 -04:00
$res = imagejpeg ( $this -> resource , null , $quality );
2013-08-06 10:56:50 -04:00
break ;
2024-08-23 09:10:27 -04:00
case 'image/gif' :
2013-08-06 10:56:50 -04:00
$res = imagegif ( $this -> resource );
break ;
2024-08-23 09:10:27 -04:00
case 'image/webp' :
2024-03-26 04:58:36 -04:00
$res = imagewebp ( $this -> resource , null , $this -> getWebpQuality ());
break ;
2013-08-06 10:56:50 -04:00
default :
$res = imagepng ( $this -> resource );
2024-08-27 10:34:31 -04:00
$this -> logger -> info ( 'Image->data. Could not guess mime-type, defaulting to png' , [ 'app' => 'core' ]);
2013-08-06 10:56:50 -04:00
break ;
}
2012-01-01 11:57:26 -05:00
if ( ! $res ) {
2024-08-27 10:34:31 -04:00
$this -> logger -> error ( 'Image->data. Error getting image data.' , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
}
2012-06-05 14:19:27 -04:00
return ob_get_clean ();
}
/**
2014-02-26 17:56:46 -05:00
* @ return string - base64 encoded , which is suitable for embedding in a VCard .
*/
2024-04-02 11:13:35 -04:00
public function __toString () : string {
2024-08-27 10:55:36 -04:00
$data = $this -> data ();
if ( $data === null ) {
return '' ;
} else {
return base64_encode ( $data );
}
2012-01-01 11:57:26 -05:00
}
2021-07-31 17:17:40 -04:00
protected function getJpegQuality () : int {
2024-08-27 11:03:10 -04:00
$quality = $this -> appConfig -> getValueInt ( 'preview' , 'jpeg_quality' , self :: DEFAULT_JPEG_QUALITY );
return min ( 100 , max ( 10 , $quality ));
2017-02-22 06:11:42 -05:00
}
2024-03-26 04:58:36 -04:00
protected function getWebpQuality () : int {
2024-08-27 11:03:10 -04:00
$quality = $this -> appConfig -> getValueInt ( 'preview' , 'webp_quality' , self :: DEFAULT_WEBP_QUALITY );
return min ( 100 , max ( 10 , $quality ));
2024-03-26 04:58:36 -04:00
}
2024-08-27 03:32:04 -04:00
private function isValidExifData ( array $exif ) : bool {
if ( ! isset ( $exif [ 'Orientation' ])) {
return false ;
}
if ( ! is_numeric ( $exif [ 'Orientation' ])) {
return false ;
}
return true ;
}
2012-01-05 11:42:40 -05:00
/**
2014-11-05 10:44:19 -05:00
* ( I ' m open for suggestions on better method name ;)
* Get the orientation based on EXIF data .
*
* @ return int The orientation or - 1 if no EXIF data is available .
*/
2021-07-31 17:17:40 -04:00
public function getOrientation () : int {
2016-10-26 15:36:33 -04:00
if ( $this -> exif !== null ) {
return $this -> exif [ 'Orientation' ];
}
2014-11-24 11:32:53 -05:00
if ( $this -> imageType !== IMAGETYPE_JPEG ) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() Image is not a JPEG.' , [ 'app' => 'core' ]);
2014-11-24 11:32:53 -05:00
return - 1 ;
}
2014-11-05 10:44:19 -05:00
if ( ! is_callable ( 'exif_read_data' )) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() Exif module not enabled.' , [ 'app' => 'core' ]);
2012-07-05 15:09:48 -04:00
return - 1 ;
2012-01-20 11:13:49 -05:00
}
2014-11-05 10:44:19 -05:00
if ( ! $this -> valid ()) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() No image loaded.' , [ 'app' => 'core' ]);
2012-07-05 15:09:48 -04:00
return - 1 ;
2012-01-05 11:42:40 -05:00
}
2014-11-05 10:44:19 -05:00
if ( is_null ( $this -> filePath ) || ! is_readable ( $this -> filePath )) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() No readable file path set.' , [ 'app' => 'core' ]);
2012-07-05 15:09:48 -04:00
return - 1 ;
2012-01-05 11:42:40 -05:00
}
2013-08-06 10:56:50 -04:00
$exif = @ exif_read_data ( $this -> filePath , 'IFD0' );
2024-08-27 10:55:36 -04:00
if ( $exif === false || ! $this -> isValidExifData ( $exif )) {
2012-07-05 15:09:48 -04:00
return - 1 ;
2012-01-05 11:42:40 -05:00
}
2016-10-26 15:36:33 -04:00
$this -> exif = $exif ;
2024-08-27 03:32:04 -04:00
return ( int ) $exif [ 'Orientation' ];
2012-07-05 15:09:48 -04:00
}
2024-08-27 10:55:36 -04:00
public function readExif ( string $data ) : void {
2016-10-26 15:36:33 -04:00
if ( ! is_callable ( 'exif_read_data' )) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() Exif module not enabled.' , [ 'app' => 'core' ]);
2016-10-26 15:36:33 -04:00
return ;
}
if ( ! $this -> valid ()) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() No image loaded.' , [ 'app' => 'core' ]);
2016-10-26 15:36:33 -04:00
return ;
}
$exif = @ exif_read_data ( 'data://image/jpeg;base64,' . base64_encode ( $data ));
2024-08-27 10:55:36 -04:00
if ( $exif === false || ! $this -> isValidExifData ( $exif )) {
2016-10-26 15:36:33 -04:00
return ;
}
$this -> exif = $exif ;
}
2012-07-05 15:09:48 -04:00
/**
2014-11-05 10:44:19 -05:00
* ( I ' m open for suggestions on better method name ;)
* Fixes orientation based on EXIF data .
*
2017-10-18 08:15:03 -04:00
* @ return bool
2014-11-05 10:44:19 -05:00
*/
2021-07-31 17:17:40 -04:00
public function fixOrientation () : bool {
2021-12-02 05:06:17 -05:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2021-12-02 05:06:17 -05:00
return false ;
}
2012-07-05 15:35:36 -04:00
$o = $this -> getOrientation ();
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() Orientation: ' . $o , [ 'app' => 'core' ]);
2012-01-05 11:42:40 -05:00
$rotate = 0 ;
2015-01-18 17:15:52 -05:00
$flip = false ;
2014-11-05 10:44:19 -05:00
switch ( $o ) {
2012-07-05 15:09:48 -04:00
case - 1 :
return false ; //Nothing to fix
2012-01-05 11:42:40 -05:00
case 1 :
$rotate = 0 ;
break ;
2015-01-18 17:15:52 -05:00
case 2 :
2012-01-05 11:42:40 -05:00
$rotate = 0 ;
2015-01-18 17:15:52 -05:00
$flip = true ;
2012-01-05 11:42:40 -05:00
break ;
case 3 :
$rotate = 180 ;
break ;
2015-01-18 17:15:52 -05:00
case 4 :
2012-01-05 11:42:40 -05:00
$rotate = 180 ;
2015-01-18 17:15:52 -05:00
$flip = true ;
2012-01-05 11:42:40 -05:00
break ;
2015-01-18 17:15:52 -05:00
case 5 :
2012-01-05 11:42:40 -05:00
$rotate = 90 ;
2015-01-18 17:15:52 -05:00
$flip = true ;
2012-01-05 11:42:40 -05:00
break ;
case 6 :
$rotate = 270 ;
break ;
2015-01-18 17:15:52 -05:00
case 7 :
2012-01-05 11:42:40 -05:00
$rotate = 270 ;
2015-01-18 17:15:52 -05:00
$flip = true ;
2012-01-05 11:42:40 -05:00
break ;
case 8 :
2012-02-07 16:33:01 -05:00
$rotate = 90 ;
2012-01-05 11:42:40 -05:00
break ;
}
2020-04-10 08:19:56 -04:00
if ( $flip && function_exists ( 'imageflip' )) {
2015-01-18 17:15:52 -05:00
imageflip ( $this -> resource , IMG_FLIP_HORIZONTAL );
}
2014-11-05 10:44:19 -05:00
if ( $rotate ) {
2014-02-12 16:35:49 -05:00
$res = imagerotate ( $this -> resource , $rotate , 0 );
2014-11-05 10:44:19 -05:00
if ( $res ) {
if ( imagealphablending ( $res , true )) {
if ( imagesavealpha ( $res , true )) {
2012-03-26 17:53:48 -04:00
imagedestroy ( $this -> resource );
2012-02-09 16:44:26 -05:00
$this -> resource = $res ;
2012-01-05 11:42:40 -05:00
return true ;
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() Error during alpha-saving' , [ 'app' => 'core' ]);
2012-01-05 11:42:40 -05:00
return false ;
}
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() Error during alpha-blending' , [ 'app' => 'core' ]);
2012-01-05 11:42:40 -05:00
return false ;
}
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->fixOrientation() Error during orientation fixing' , [ 'app' => 'core' ]);
2012-01-05 11:42:40 -05:00
return false ;
}
}
2014-02-26 17:56:46 -05:00
return false ;
2012-01-05 11:42:40 -05:00
}
2012-02-16 04:35:35 -05:00
/**
2014-11-05 10:44:19 -05:00
* Loads an image from an open file handle .
* It is the responsibility of the caller to position the pointer at the correct place and to close the handle again .
*
* @ param resource $handle
2024-04-09 04:48:27 -04:00
* @ return \GdImage | false An image resource or false on error
2014-11-05 10:44:19 -05:00
*/
2012-02-16 04:35:35 -05:00
public function loadFromFileHandle ( $handle ) {
2012-03-26 16:33:37 -04:00
$contents = stream_get_contents ( $handle );
2014-11-05 10:44:19 -05:00
if ( $this -> loadFromData ( $contents )) {
2012-02-16 04:35:35 -05:00
return $this -> resource ;
}
2014-11-05 10:44:19 -05:00
return false ;
2012-02-16 04:35:35 -05:00
}
2021-12-16 03:17:11 -05:00
/**
* Check if allocating an image with the given size is allowed .
*
* @ param int $width The image width .
* @ param int $height The image height .
* @ return bool true if allocating is allowed , false otherwise
*/
private function checkImageMemory ( $width , $height ) {
$memory_limit = $this -> config -> getSystemValueInt ( 'preview_max_memory' , self :: DEFAULT_MEMORY_LIMIT );
if ( $memory_limit < 0 ) {
// Not limited.
return true ;
}
// Assume 32 bits per pixel.
if ( $width * $height * 4 > $memory_limit * 1024 * 1024 ) {
2022-12-21 14:01:19 -05:00
$this -> logger -> info ( 'Image size of ' . $width . 'x' . $height . ' would exceed allowed memory limit of ' . $memory_limit . '. You may increase the preview_max_memory in your config.php if you need previews of this image.' );
2021-12-16 03:17:11 -05:00
return false ;
}
return true ;
}
/**
* Check if loading an image file from the given path is allowed .
*
* @ param string $path The path to a local file .
* @ return bool true if allocating is allowed , false otherwise
*/
private function checkImageSize ( $path ) {
2023-02-09 08:19:42 -05:00
$size = @ getimagesize ( $path );
2021-12-16 03:17:11 -05:00
if ( ! $size ) {
2024-07-06 18:01:25 -04:00
return false ;
2021-12-16 03:17:11 -05:00
}
$width = $size [ 0 ];
$height = $size [ 1 ];
if ( ! $this -> checkImageMemory ( $width , $height )) {
return false ;
}
return true ;
}
/**
* Check if loading an image from the given data is allowed .
*
* @ param string $data A string of image data as read from a file .
* @ return bool true if allocating is allowed , false otherwise
*/
private function checkImageDataSize ( $data ) {
2023-02-09 08:19:42 -05:00
$size = @ getimagesizefromstring ( $data );
2021-12-16 03:17:11 -05:00
if ( ! $size ) {
2024-07-06 18:01:25 -04:00
return false ;
2021-12-16 03:17:11 -05:00
}
$width = $size [ 0 ];
$height = $size [ 1 ];
if ( ! $this -> checkImageMemory ( $width , $height )) {
return false ;
}
return true ;
}
2012-01-01 11:57:26 -05:00
/**
2014-11-05 10:44:19 -05:00
* Loads an image from a local file .
*
* @ param bool | string $imagePath The path to a local file .
2024-04-09 04:48:27 -04:00
* @ return bool | \GdImage An image resource or false on error
2014-11-05 10:44:19 -05:00
*/
public function loadFromFile ( $imagePath = false ) {
2013-03-02 04:51:49 -05:00
// exif_imagetype throws "read error!" if file is less than 12 byte
2021-01-17 15:09:31 -05:00
if ( is_bool ( $imagePath ) || !@ is_file ( $imagePath ) || ! file_exists ( $imagePath ) || filesize ( $imagePath ) < 12 || ! is_readable ( $imagePath )) {
2012-01-01 11:57:26 -05:00
return false ;
}
2013-11-22 12:01:44 -05:00
$iType = exif_imagetype ( $imagePath );
2013-08-06 10:56:50 -04:00
switch ( $iType ) {
2012-01-01 17:26:24 -05:00
case IMAGETYPE_GIF :
if ( imagetypes () & IMG_GIF ) {
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageSize ( $imagePath )) {
return false ;
}
2013-11-22 12:01:44 -05:00
$this -> resource = imagecreatefromgif ( $imagePath );
2021-08-18 01:36:11 -04:00
if ( $this -> resource ) {
// Preserve transparency
imagealphablending ( $this -> resource , true );
imagesavealpha ( $this -> resource , true );
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, GIF image not valid: ' . $imagePath , [ 'app' => 'core' ]);
2021-08-18 01:36:11 -04:00
}
2012-01-02 07:40:23 -05:00
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, GIF images not supported: ' . $imagePath , [ 'app' => 'core' ]);
2012-01-01 17:26:24 -05:00
}
break ;
case IMAGETYPE_JPEG :
if ( imagetypes () & IMG_JPG ) {
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageSize ( $imagePath )) {
return false ;
}
2024-07-06 18:01:25 -04:00
if ( @ getimagesize ( $imagePath ) !== false ) {
2017-05-01 08:03:00 -04:00
$this -> resource = @ imagecreatefromjpeg ( $imagePath );
2017-03-08 15:48:30 -05:00
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, JPG image not valid: ' . $imagePath , [ 'app' => 'core' ]);
2017-03-08 15:48:30 -05:00
}
2012-01-02 07:40:23 -05:00
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, JPG images not supported: ' . $imagePath , [ 'app' => 'core' ]);
2012-01-01 17:26:24 -05:00
}
break ;
case IMAGETYPE_PNG :
if ( imagetypes () & IMG_PNG ) {
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageSize ( $imagePath )) {
return false ;
}
2017-05-01 08:03:00 -04:00
$this -> resource = @ imagecreatefrompng ( $imagePath );
2021-08-18 01:36:11 -04:00
if ( $this -> resource ) {
// Preserve transparency
imagealphablending ( $this -> resource , true );
imagesavealpha ( $this -> resource , true );
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, PNG image not valid: ' . $imagePath , [ 'app' => 'core' ]);
2021-08-18 01:36:11 -04:00
}
2012-01-02 07:40:23 -05:00
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, PNG images not supported: ' . $imagePath , [ 'app' => 'core' ]);
2012-01-01 17:26:24 -05:00
}
break ;
case IMAGETYPE_XBM :
if ( imagetypes () & IMG_XPM ) {
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageSize ( $imagePath )) {
return false ;
}
2017-05-01 08:03:00 -04:00
$this -> resource = @ imagecreatefromxbm ( $imagePath );
2012-01-02 07:40:23 -05:00
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath , [ 'app' => 'core' ]);
2012-01-01 17:26:24 -05:00
}
break ;
case IMAGETYPE_WBMP :
if ( imagetypes () & IMG_WBMP ) {
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageSize ( $imagePath )) {
return false ;
}
2017-05-01 08:03:00 -04:00
$this -> resource = @ imagecreatefromwbmp ( $imagePath );
2012-01-02 07:40:23 -05:00
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, WBMP images not supported: ' . $imagePath , [ 'app' => 'core' ]);
2012-01-01 17:26:24 -05:00
}
break ;
2012-11-06 04:54:32 -05:00
case IMAGETYPE_BMP :
2021-07-31 17:17:40 -04:00
$this -> resource = imagecreatefrombmp ( $imagePath );
2012-11-06 04:54:32 -05:00
break ;
2021-01-17 15:09:31 -05:00
case IMAGETYPE_WEBP :
if ( imagetypes () & IMG_WEBP ) {
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageSize ( $imagePath )) {
return false ;
}
2023-05-18 13:27:13 -04:00
// Check for animated header before generating preview since libgd does not handle them well
// Adapted from here: https://stackoverflow.com/a/68491679/4085517 (stripped to only to check for animations + added additional error checking)
// Header format details here: https://developers.google.com/speed/webp/docs/riff_container
// Load up the header data, if any
$fp = fopen ( $imagePath , 'rb' );
if ( ! $fp ) {
return false ;
}
$data = fread ( $fp , 90 );
2024-08-27 10:55:36 -04:00
if ( $data === false ) {
2023-05-18 13:27:13 -04:00
return false ;
}
fclose ( $fp );
unset ( $fp );
2025-06-30 09:04:05 -04:00
$headerFormat = 'A4Riff/' // get n string
. 'I1Filesize/' // get integer (file size but not actual size)
. 'A4Webp/' // get n string
. 'A4Vp/' // get n string
. 'A74Chunk' ;
2023-05-18 13:27:13 -04:00
$header = unpack ( $headerFormat , $data );
unset ( $data , $headerFormat );
2024-08-27 10:55:36 -04:00
if ( $header === false ) {
2023-05-18 13:27:13 -04:00
return false ;
}
// Check if we're really dealing with a valid WEBP header rather than just one suffixed ".webp"
if ( ! isset ( $header [ 'Riff' ]) || strtoupper ( $header [ 'Riff' ]) !== 'RIFF' ) {
return false ;
}
if ( ! isset ( $header [ 'Webp' ]) || strtoupper ( $header [ 'Webp' ]) !== 'WEBP' ) {
return false ;
}
if ( ! isset ( $header [ 'Vp' ]) || strpos ( strtoupper ( $header [ 'Vp' ]), 'VP8' ) === false ) {
return false ;
}
// Check for animation indicators
if ( strpos ( strtoupper ( $header [ 'Chunk' ]), 'ANIM' ) !== false || strpos ( strtoupper ( $header [ 'Chunk' ]), 'ANMF' ) !== false ) {
// Animated so don't let it reach libgd
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, animated WEBP images not supported: ' . $imagePath , [ 'app' => 'core' ]);
2023-05-18 13:27:13 -04:00
} else {
// We're safe so give it to libgd
$this -> resource = @ imagecreatefromwebp ( $imagePath );
}
2021-01-17 15:09:31 -05:00
} else {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, WEBP images not supported: ' . $imagePath , [ 'app' => 'core' ]);
2021-01-17 15:09:31 -05:00
}
break ;
2023-01-20 05:45:08 -05:00
/*
case IMAGETYPE_TIFF_II : // (intel byte order)
break ;
case IMAGETYPE_TIFF_MM : // (motorola byte order)
break ;
case IMAGETYPE_JPC :
break ;
case IMAGETYPE_JP2 :
break ;
case IMAGETYPE_JPX :
break ;
case IMAGETYPE_JB2 :
break ;
case IMAGETYPE_SWC :
break ;
case IMAGETYPE_IFF :
break ;
case IMAGETYPE_ICO :
break ;
case IMAGETYPE_SWF :
break ;
case IMAGETYPE_PSD :
break ;
*/
2012-01-01 17:26:24 -05:00
default :
2012-08-29 02:38:33 -04:00
2012-06-09 09:12:28 -04:00
// this is mostly file created from encrypted file
2021-12-16 03:17:11 -05:00
$data = file_get_contents ( $imagePath );
if ( ! $this -> checkImageDataSize ( $data )) {
return false ;
}
2023-06-30 15:58:44 -04:00
$this -> resource = @ imagecreatefromstring ( $data );
2013-08-06 10:56:50 -04:00
$iType = IMAGETYPE_PNG ;
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, Default' , [ 'app' => 'core' ]);
2012-01-01 17:26:24 -05:00
break ;
}
2014-11-05 10:44:19 -05:00
if ( $this -> valid ()) {
2013-08-06 10:56:50 -04:00
$this -> imageType = $iType ;
$this -> mimeType = image_type_to_mime_type ( $iType );
$this -> filePath = $imagePath ;
2012-01-01 17:26:24 -05:00
}
2012-02-09 16:44:26 -05:00
return $this -> resource ;
2012-01-01 11:57:26 -05:00
}
/**
2024-09-02 04:15:10 -04:00
* @ inheritDoc
2014-11-05 10:44:19 -05:00
*/
2024-08-27 10:55:36 -04:00
public function loadFromData ( string $str ) : GdImage | false {
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageDataSize ( $str )) {
return false ;
}
2012-06-02 09:25:50 -04:00
$this -> resource = @ imagecreatefromstring ( $str );
2013-09-01 09:50:58 -04:00
if ( $this -> fileInfo ) {
2013-08-06 10:56:50 -04:00
$this -> mimeType = $this -> fileInfo -> buffer ( $str );
}
2021-10-25 10:13:50 -04:00
if ( $this -> valid ()) {
2013-08-15 10:13:01 -04:00
imagealphablending ( $this -> resource , false );
imagesavealpha ( $this -> resource , true );
}
2013-08-15 07:21:35 -04:00
2014-11-05 10:44:19 -05:00
if ( ! $this -> resource ) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromFile, could not load' , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
2012-02-09 16:44:26 -05:00
return $this -> resource ;
2012-01-01 11:57:26 -05:00
}
/**
2014-11-05 10:44:19 -05:00
* Loads an image from a base64 encoded string .
*
* @ param string $str A string base64 encoded string of image data .
2024-04-09 04:48:27 -04:00
* @ return bool | \GdImage An image resource or false on error
2014-11-05 10:44:19 -05:00
*/
2021-07-31 17:17:40 -04:00
public function loadFromBase64 ( string $str ) {
2012-01-01 11:57:26 -05:00
$data = base64_decode ( $str );
2014-11-05 10:44:19 -05:00
if ( $data ) { // try to load from string data
2021-12-16 03:17:11 -05:00
if ( ! $this -> checkImageDataSize ( $data )) {
return false ;
}
2012-06-02 09:25:50 -04:00
$this -> resource = @ imagecreatefromstring ( $data );
2013-09-01 09:50:58 -04:00
if ( $this -> fileInfo ) {
2013-08-06 10:56:50 -04:00
$this -> mimeType = $this -> fileInfo -> buffer ( $data );
}
2014-11-05 10:44:19 -05:00
if ( ! $this -> resource ) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->loadFromBase64, could not load' , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
2012-02-09 16:44:26 -05:00
return $this -> resource ;
2012-01-01 11:57:26 -05:00
} else {
return false ;
}
}
/**
2014-11-05 10:44:19 -05:00
* Resizes the image preserving ratio .
*
2021-07-31 17:17:40 -04:00
* @ param int $maxSize The maximum size of either the width or height .
2014-11-05 10:44:19 -05:00
* @ return bool
*/
2021-07-31 17:17:40 -04:00
public function resize ( int $maxSize ) : bool {
2021-12-02 05:06:17 -05:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2021-12-02 05:06:17 -05:00
return false ;
}
2020-02-15 20:34:09 -05:00
$result = $this -> resizeNew ( $maxSize );
imagedestroy ( $this -> resource );
$this -> resource = $result ;
2021-10-25 10:13:50 -04:00
return $this -> valid ();
2020-02-15 20:34:09 -05:00
}
2024-08-27 10:55:36 -04:00
private function resizeNew ( int $maxSize ) : \GdImage | false {
2014-11-05 10:44:19 -05:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2012-01-01 12:07:46 -05:00
return false ;
2012-01-01 11:57:26 -05:00
}
2016-08-22 14:56:59 -04:00
$widthOrig = imagesx ( $this -> resource );
$heightOrig = imagesy ( $this -> resource );
2014-11-05 10:44:19 -05:00
$ratioOrig = $widthOrig / $heightOrig ;
2012-08-29 02:38:33 -04:00
2013-08-06 10:56:50 -04:00
if ( $ratioOrig > 1 ) {
2014-11-05 10:44:19 -05:00
$newHeight = round ( $maxSize / $ratioOrig );
2013-08-06 10:56:50 -04:00
$newWidth = $maxSize ;
2012-01-01 11:57:26 -05:00
} else {
2014-11-05 10:44:19 -05:00
$newWidth = round ( $maxSize * $ratioOrig );
2013-08-06 10:56:50 -04:00
$newHeight = $maxSize ;
2012-01-01 11:57:26 -05:00
}
2020-02-15 20:34:09 -05:00
return $this -> preciseResizeNew (( int ) round ( $newWidth ), ( int ) round ( $newHeight ));
2012-01-01 11:57:26 -05:00
}
2014-03-17 03:17:56 -04:00
/**
* @ param int $width
* @ param int $height
* @ return bool
*/
2018-01-12 18:34:28 -05:00
public function preciseResize ( int $width , int $height ) : bool {
2021-12-02 05:06:17 -05:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2021-12-02 05:06:17 -05:00
return false ;
}
2020-02-15 20:34:09 -05:00
$result = $this -> preciseResizeNew ( $width , $height );
imagedestroy ( $this -> resource );
$this -> resource = $result ;
2021-10-25 10:13:50 -04:00
return $this -> valid ();
2020-02-15 20:34:09 -05:00
}
2024-08-27 10:55:36 -04:00
public function preciseResizeNew ( int $width , int $height ) : \GdImage | false {
2021-11-10 09:50:15 -05:00
if ( ! ( $width > 0 ) || ! ( $height > 0 )) {
$this -> logger -> info ( __METHOD__ . '(): Requested image size not bigger than 0' , [ 'app' => 'core' ]);
return false ;
}
2012-06-02 09:25:50 -04:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2012-08-29 02:38:33 -04:00
return false ;
2012-06-02 09:25:50 -04:00
}
2016-08-22 14:56:59 -04:00
$widthOrig = imagesx ( $this -> resource );
$heightOrig = imagesy ( $this -> resource );
2012-06-02 09:25:50 -04:00
$process = imagecreatetruecolor ( $width , $height );
2019-09-01 16:13:25 -04:00
if ( $process === false ) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): Error creating true color image' , [ 'app' => 'core' ]);
2012-06-02 09:25:50 -04:00
return false ;
}
2013-02-22 06:42:40 -05:00
// preserve transparency
2025-09-27 16:56:38 -04:00
if ( $this -> imageType === IMAGETYPE_GIF || $this -> imageType === IMAGETYPE_PNG ) {
2024-08-27 10:55:36 -04:00
$alpha = imagecolorallocatealpha ( $process , 0 , 0 , 0 , 127 );
if ( $alpha === false ) {
$alpha = null ;
}
imagecolortransparent ( $process , $alpha );
2013-02-22 06:42:40 -05:00
imagealphablending ( $process , false );
imagesavealpha ( $process , true );
}
2019-09-01 16:13:25 -04:00
$res = imagecopyresampled ( $process , $this -> resource , 0 , 0 , 0 , 0 , $width , $height , $widthOrig , $heightOrig );
if ( $res === false ) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): Error re-sampling process image' , [ 'app' => 'core' ]);
2012-06-02 09:25:50 -04:00
imagedestroy ( $process );
return false ;
}
2020-02-15 20:34:09 -05:00
return $process ;
2012-06-02 09:25:50 -04:00
}
2012-01-01 11:57:26 -05:00
/**
2014-11-05 10:44:19 -05:00
* Crops the image to the middle square . If the image is already square it just returns .
*
* @ param int $size maximum size for the result ( optional )
* @ return bool for success or failure
*/
2021-07-31 17:17:40 -04:00
public function centerCrop ( int $size = 0 ) : bool {
2014-11-05 10:44:19 -05:00
if ( ! $this -> valid ()) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->centerCrop, No image loaded' , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
2016-08-22 14:56:59 -04:00
$widthOrig = imagesx ( $this -> resource );
$heightOrig = imagesy ( $this -> resource );
2025-09-27 16:56:38 -04:00
if ( $widthOrig === $heightOrig && $size == 0 ) {
2012-01-01 11:57:26 -05:00
return true ;
}
2014-11-05 10:44:19 -05:00
$ratioOrig = $widthOrig / $heightOrig ;
2013-08-06 10:56:50 -04:00
$width = $height = min ( $widthOrig , $heightOrig );
2012-01-01 11:57:26 -05:00
2013-08-06 10:56:50 -04:00
if ( $ratioOrig > 1 ) {
2024-08-23 09:10:27 -04:00
$x = ( int )(( $widthOrig / 2 ) - ( $width / 2 ));
2012-01-01 11:57:26 -05:00
$y = 0 ;
} else {
2024-08-23 09:10:27 -04:00
$y = ( int )(( $heightOrig / 2 ) - ( $height / 2 ));
2012-01-01 11:57:26 -05:00
$x = 0 ;
}
2014-11-05 10:44:19 -05:00
if ( $size > 0 ) {
$targetWidth = $size ;
$targetHeight = $size ;
} else {
$targetWidth = $width ;
$targetHeight = $height ;
2012-03-26 17:53:48 -04:00
}
$process = imagecreatetruecolor ( $targetWidth , $targetHeight );
2021-12-02 05:06:17 -05:00
if ( $process === false ) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->centerCrop, Error creating true color image' , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
2013-02-22 06:42:40 -05:00
// preserve transparency
2025-09-27 16:56:38 -04:00
if ( $this -> imageType === IMAGETYPE_GIF || $this -> imageType === IMAGETYPE_PNG ) {
2024-08-27 10:55:36 -04:00
$alpha = imagecolorallocatealpha ( $process , 0 , 0 , 0 , 127 );
if ( $alpha === false ) {
$alpha = null ;
}
imagecolortransparent ( $process , $alpha );
2013-02-22 06:42:40 -05:00
imagealphablending ( $process , false );
imagesavealpha ( $process , true );
}
2013-02-22 11:21:57 -05:00
2024-04-09 04:48:27 -04:00
$result = imagecopyresampled ( $process , $this -> resource , 0 , 0 , $x , $y , $targetWidth , $targetHeight , $width , $height );
if ( $result === false ) {
2024-08-27 10:34:31 -04:00
$this -> logger -> debug ( 'Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
2012-03-26 16:28:40 -04:00
imagedestroy ( $this -> resource );
2012-02-09 16:44:26 -05:00
$this -> resource = $process ;
2012-01-01 11:57:26 -05:00
return true ;
}
/**
2014-11-05 10:44:19 -05:00
* Crops the image from point $x $y with dimension $wx $h .
*
* @ param int $x Horizontal position
* @ param int $y Vertical position
* @ param int $w Width
* @ param int $h Height
* @ return bool for success or failure
*/
2018-01-12 18:34:28 -05:00
public function crop ( int $x , int $y , int $w , int $h ) : bool {
2021-12-02 05:06:17 -05:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2021-12-02 05:06:17 -05:00
return false ;
}
2020-02-15 20:34:09 -05:00
$result = $this -> cropNew ( $x , $y , $w , $h );
imagedestroy ( $this -> resource );
$this -> resource = $result ;
2021-10-25 10:13:50 -04:00
return $this -> valid ();
2020-02-15 20:34:09 -05:00
}
/**
* Crops the image from point $x $y with dimension $wx $h .
*
* @ param int $x Horizontal position
* @ param int $y Vertical position
* @ param int $w Width
* @ param int $h Height
2024-04-09 04:48:27 -04:00
* @ return \GdImage | false
2020-02-15 20:34:09 -05:00
*/
public function cropNew ( int $x , int $y , int $w , int $h ) {
2014-11-05 10:44:19 -05:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
$process = imagecreatetruecolor ( $w , $h );
2021-12-02 05:06:17 -05:00
if ( $process === false ) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): Error creating true color image' , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
2014-07-30 06:16:03 -04:00
// preserve transparency
2025-09-27 16:56:38 -04:00
if ( $this -> imageType === IMAGETYPE_GIF || $this -> imageType === IMAGETYPE_PNG ) {
2024-08-27 10:55:36 -04:00
$alpha = imagecolorallocatealpha ( $process , 0 , 0 , 0 , 127 );
if ( $alpha === false ) {
$alpha = null ;
}
imagecolortransparent ( $process , $alpha );
2014-07-30 06:16:03 -04:00
imagealphablending ( $process , false );
imagesavealpha ( $process , true );
}
2024-04-09 04:48:27 -04:00
$result = imagecopyresampled ( $process , $this -> resource , 0 , 0 , $x , $y , $w , $h , $w , $h );
if ( $result === false ) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h , [ 'app' => 'core' ]);
2012-01-01 11:57:26 -05:00
return false ;
}
2020-02-15 20:34:09 -05:00
return $process ;
2012-01-01 11:57:26 -05:00
}
2012-03-26 16:28:40 -04:00
2012-09-08 17:26:19 -04:00
/**
2014-11-05 10:44:19 -05:00
* Resizes the image to fit within a boundary while preserving ratio .
*
2015-06-06 10:21:36 -04:00
* Warning : Images smaller than $maxWidth x $maxHeight will end up being scaled up
*
2021-07-31 17:17:40 -04:00
* @ param int $maxWidth
* @ param int $maxHeight
2014-03-17 03:17:56 -04:00
* @ return bool
2012-09-08 17:26:19 -04:00
*/
2021-07-31 17:17:40 -04:00
public function fitIn ( int $maxWidth , int $maxHeight ) : bool {
2014-11-05 10:44:19 -05:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2012-09-08 17:26:19 -04:00
return false ;
}
2016-08-22 14:56:59 -04:00
$widthOrig = imagesx ( $this -> resource );
$heightOrig = imagesy ( $this -> resource );
2014-11-05 10:44:19 -05:00
$ratio = $widthOrig / $heightOrig ;
2012-09-08 17:26:19 -04:00
2014-11-05 10:44:19 -05:00
$newWidth = min ( $maxWidth , $ratio * $maxHeight );
$newHeight = min ( $maxHeight , $maxWidth / $ratio );
2012-10-14 15:04:08 -04:00
2018-01-12 18:34:28 -05:00
$this -> preciseResize (( int ) round ( $newWidth ), ( int ) round ( $newHeight ));
2012-09-08 17:26:19 -04:00
return true ;
}
2015-06-06 10:21:36 -04:00
/**
* Shrinks larger images to fit within specified boundaries while preserving ratio .
*
2021-07-31 17:17:40 -04:00
* @ param int $maxWidth
* @ param int $maxHeight
2015-06-06 10:21:36 -04:00
* @ return bool
*/
2021-07-31 17:17:40 -04:00
public function scaleDownToFit ( int $maxWidth , int $maxHeight ) : bool {
2015-06-08 09:10:29 -04:00
if ( ! $this -> valid ()) {
2022-05-25 12:26:42 -04:00
$this -> logger -> debug ( __METHOD__ . '(): No image loaded' , [ 'app' => 'core' ]);
2015-06-08 09:10:29 -04:00
return false ;
}
2016-08-22 14:56:59 -04:00
$widthOrig = imagesx ( $this -> resource );
$heightOrig = imagesy ( $this -> resource );
2015-06-06 10:21:36 -04:00
2015-06-08 09:10:29 -04:00
if ( $widthOrig > $maxWidth || $heightOrig > $maxHeight ) {
2015-06-06 10:21:36 -04:00
return $this -> fitIn ( $maxWidth , $maxHeight );
}
return false ;
}
2020-02-15 20:34:09 -05:00
public function copy () : IImage {
2024-08-27 11:03:10 -04:00
$image = new self ( $this -> logger , $this -> appConfig , $this -> config );
2024-08-27 10:55:36 -04:00
if ( ! $this -> valid ()) {
/* image is invalid, return an empty one */
return $image ;
}
2020-02-15 20:34:09 -05:00
$image -> resource = imagecreatetruecolor ( $this -> width (), $this -> height ());
2024-08-27 10:55:36 -04:00
if ( ! $image -> valid ()) {
/* image creation failed, cannot copy in it */
return $image ;
}
2020-02-15 20:34:09 -05:00
imagecopy (
2024-08-27 10:55:36 -04:00
$image -> resource ,
$this -> resource ,
2020-02-15 20:34:09 -05:00
0 ,
0 ,
0 ,
0 ,
$this -> width (),
$this -> height ()
);
return $image ;
}
public function cropCopy ( int $x , int $y , int $w , int $h ) : IImage {
2024-08-27 11:03:10 -04:00
$image = new self ( $this -> logger , $this -> appConfig , $this -> config );
2020-05-02 15:57:34 -04:00
$image -> imageType = $this -> imageType ;
$image -> mimeType = $this -> mimeType ;
2020-02-15 20:34:09 -05:00
$image -> resource = $this -> cropNew ( $x , $y , $w , $h );
return $image ;
}
public function preciseResizeCopy ( int $width , int $height ) : IImage {
2024-08-27 11:03:10 -04:00
$image = new self ( $this -> logger , $this -> appConfig , $this -> config );
2020-05-02 15:57:34 -04:00
$image -> imageType = $this -> imageType ;
$image -> mimeType = $this -> mimeType ;
2020-02-15 20:34:09 -05:00
$image -> resource = $this -> preciseResizeNew ( $width , $height );
return $image ;
}
public function resizeCopy ( int $maxSize ) : IImage {
2024-08-27 11:03:10 -04:00
$image = new self ( $this -> logger , $this -> appConfig , $this -> config );
2020-05-02 15:57:34 -04:00
$image -> imageType = $this -> imageType ;
$image -> mimeType = $this -> mimeType ;
2020-02-15 20:34:09 -05:00
$image -> resource = $this -> resizeNew ( $maxSize );
return $image ;
}
2015-03-13 05:10:11 -04:00
/**
* Destroys the current image and resets the object
*/
2021-07-31 17:17:40 -04:00
public function destroy () : void {
2014-11-05 10:44:19 -05:00
if ( $this -> valid ()) {
2012-03-26 16:28:40 -04:00
imagedestroy ( $this -> resource );
}
2021-12-02 05:06:17 -05:00
$this -> resource = false ;
2012-03-26 17:53:48 -04:00
}
2012-09-07 09:22:01 -04:00
public function __destruct () {
2012-03-26 17:53:48 -04:00
$this -> destroy ();
2012-03-26 16:28:40 -04:00
}
2012-01-01 11:57:26 -05:00
}
2014-11-05 10:44:19 -05:00
if ( ! function_exists ( 'exif_imagetype' )) {
2012-11-12 07:56:29 -05:00
/**
* Workaround if exif_imagetype does not exist
2014-11-05 10:44:19 -05:00
*
2020-09-17 11:23:07 -04:00
* @ link https :// www . php . net / manual / en / function . exif - imagetype . php #80383
2014-02-06 10:30:58 -05:00
* @ param string $fileName
2024-05-06 07:32:09 -04:00
* @ return int | false
2012-11-12 07:56:29 -05:00
*/
2021-07-31 17:17:40 -04:00
function exif_imagetype ( string $fileName ) {
2014-11-05 10:44:19 -05:00
if (( $info = getimagesize ( $fileName )) !== false ) {
2012-11-12 07:56:29 -05:00
return $info [ 2 ];
}
return false ;
}
}