N°3124 - Refactorize ResizeImageToFit methods (#734)

This commit is contained in:
Timmy38
2025-08-08 10:43:35 +02:00
committed by GitHub
parent 5b9e0a1d4f
commit 71386198cf
7 changed files with 227 additions and 190 deletions

View File

@@ -4313,24 +4313,15 @@ HTML;
case 'Image':
$value = null;
$aDimensions = null;
$oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents');
if (!is_null($oImage->GetData()))
{
$aSize = utils::GetImageSize($oImage->GetData());
if (is_array($aSize) && $aSize[0] > 0 && $aSize[1] > 0)
{
$oImage = utils::ResizeImageToFit(
$oImage,
$aSize[0],
$aSize[1],
$oAttDef->Get('storage_max_width'),
$oAttDef->Get('storage_max_height')
);
}
else
{
IssueLog::Warning($sClass . ':' . $this->GetKey() . '/' . $sAttCode . ': Image could not be resized. Mimetype: ' . $oImage->GetMimeType() . ', filename: ' . $oImage->GetFileName());
}
$oImage = $oImage->ResizeImageToFit(
$oAttDef->Get('storage_max_width'),
$oAttDef->Get('storage_max_height'),
$aDimensions
);
if (is_null($aDimensions)) {
IssueLog::Warning($sClass . ':' . $this->GetKey() . '/' . $sAttCode . ': Image could not be resized. Mimetype: ' . $oImage->GetMimeType() . ', filename: ' . $oImage->GetFileName());
}
$aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
if (is_array($aOtherData))

View File

@@ -2312,97 +2312,6 @@ SQL;
return @getimagesizefromstring($sImageData);
}
/**
* Resize an image attachment so that it fits in the given dimensions
* @param ormDocument $oImage The original image stored as an ormDocument
* @param int $iWidth Image's original width
* @param int $iHeight Image's original height
* @param int $iMaxImageWidth Maximum width for the resized image
* @param int $iMaxImageHeight Maximum height for the resized image
* @return ormDocument The resampled image
*/
public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
{
// If image size smaller than maximums, we do nothing
if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
{
return $oImage;
}
// If gd extension is not loaded, we put a warning in the log and return the image as is
if (extension_loaded('gd') === false)
{
IssueLog::Warning('Image could not be resized as the "gd" extension does not seem to be loaded. It will remain as ' . $iWidth . 'x' . $iHeight . ' instead of ' . $iMaxImageWidth . 'x' . $iMaxImageHeight);
return $oImage;
}
switch($oImage->GetMimeType())
{
case 'image/gif':
case 'image/jpeg':
case 'image/png':
$img = @imagecreatefromstring($oImage->GetData());
break;
default:
// Unsupported image type, return the image as-is
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
return $oImage;
}
if ($img === false)
{
//throw new Exception("Warning: corrupted image: '".$oImage->GetFileName()." / ".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
return $oImage;
}
else
{
// Let's scale the image, preserving the transparency for GIFs and PNGs
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
imagealphablending($new, false);
imagesavealpha($new, true);
}
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($oImage->GetMimeType())
{
case 'image/gif':
imagegif($new); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean();
imagedestroy($img);
imagedestroy($new);
return $oResampledImage;
}
}
/**
* Create a 128 bit UUID in the format: {########-####-####-####-############}
*

View File

@@ -407,86 +407,12 @@ JS
* Resize an image so that it fits the maximum width/height defined in the config file
* @param ormDocument $oImage The original image stored as an array (content / mimetype / filename)
* @return ormDocument The resampled image (or the original one if it already fit)
* @deprecated Replaced by ormDocument::ResizeImageToFit
*/
public static function ResizeImageToFit(ormDocument $oImage, &$aDimensions = null)
{
$img = false;
switch($oImage->GetMimeType())
{
case 'image/gif':
case 'image/jpeg':
case 'image/png':
$img = @imagecreatefromstring($oImage->GetData());
break;
default:
// Unsupported image type, return the image as-is
$aDimensions = null;
return $oImage;
}
if ($img === false)
{
$aDimensions = null;
return $oImage;
}
else
{
// Let's scale the image, preserving the transparency for GIFs and PNGs
$iWidth = imagesx($img);
$iHeight = imagesy($img);
$aDimensions = array('width' => $iWidth, 'height' => $iHeight);
$iMaxImageSize = (int)MetaModel::GetConfig()->Get('inline_image_max_storage_width', 0);
if (($iMaxImageSize > 0) && ($iWidth <= $iMaxImageSize) && ($iHeight <= $iMaxImageSize))
{
// No need to resize
return $oImage;
}
$fScale = min($iMaxImageSize / $iWidth, $iMaxImageSize / $iHeight);
$iNewWidth = (int) ($iWidth * $fScale);
$iNewHeight = (int) ($iHeight * $fScale);
$aDimensions['width'] = $iNewWidth;
$aDimensions['height'] = $iNewHeight;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
imagealphablending($new, false);
imagesavealpha($new, true);
}
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($oImage->GetMimeType())
{
case 'image/gif':
imagegif($new); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oNewImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean();
imagedestroy($img);
imagedestroy($new);
return $oNewImage;
}
$iMaxImageSize = (int)MetaModel::GetConfig()->Get('inline_image_max_storage_width', 0);
return $oImage->ResizeImageToFit($iMaxImageSize, $iMaxImageSize, $aDimensions);
}
/**

View File

@@ -405,6 +405,101 @@ class ormDocument
}
}
/**
* Resize an image so that it fits in the given dimensions
* @param int $iMaxImageWidth Maximum width for the resized image
* @param int $iMaxImageHeight Maximum height for the resized image
* @param array|null $aFinalDimensions Image dimensions after resizing or null if unable to read the image
* @return ormDocument The resampled image
*
*/
public function ResizeImageToFit(int $iMaxWidth, int $iMaxHeight, array|null &$aFinalDimensions = null) : static
{
$aFinalDimensions = null;
// If gd extension is not loaded, we put a warning in the log and return the image as is
if (extension_loaded('gd') === false) {
IssueLog::Warning('Image could not be resized as the "gd" extension does not seem to be loaded. Its dimensions will remain the same instead of ' . $iMaxWidth . 'x' . $iMaxHeight);
return $this;
}
$oGdImage = false;
switch($this->GetMimeType()) {
case 'image/gif':
case 'image/jpeg':
case 'image/png':
$oGdImage = @imagecreatefromstring($this->GetData());
break;
default:
// Unsupported image type, return the image as-is
return $this;
}
if ($oGdImage === false) {
IssueLog::Warning('Image could not be resized as . It will remain as imagecreatefromstring could not read its data.Its dimensions will remain the same instead of ' . $iMaxWidth . 'x' . $iMaxHeight);
return $this;
}
$iWidth = imagesx($oGdImage);
$iHeight = imagesy($oGdImage);
if ( ($iMaxWidth === 0 || $iWidth <= $iMaxWidth) && ($iMaxHeight === 0 || $iHeight <= $iMaxHeight)) {
// No need to resize
$aFinalDimensions = [
'width' => $iWidth,
'height' =>$iHeight
];
return $this;
}
$fScale = 1.0;
if ($iMaxWidth > 0) {
$fScale = min($fScale, $iMaxWidth / $iWidth);
}
if ($iMaxHeight > 0) {
$fScale = min($fScale, $iMaxHeight / $iHeight);
}
$iNewWidth = (int)($iWidth * $fScale);
$iNewHeight = (int)($iHeight * $fScale);
$oNewGdImage = imagecreatetruecolor($iNewWidth, $iNewHeight);
$aFinalDimensions = [
'width' => $iNewWidth,
'height' =>$iNewHeight
];
// Preserve transparency
if($this->GetMimeType() == "image/gif" || $this->GetMimeType() == "image/png") {
imagecolortransparent($oNewGdImage, imagecolorallocatealpha($oNewGdImage, 0, 0, 0, 127));
imagealphablending($oNewGdImage, false);
imagesavealpha($oNewGdImage, true);
}
imagecopyresampled($oNewGdImage, $oGdImage, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($this->GetMimeType()) {
case 'image/gif':
imagegif($oNewGdImage); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($oNewGdImage, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($oNewGdImage, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oResampledImage = new ormDocument(ob_get_contents(), $this->GetMimeType(), $this->GetFileName());
@ob_end_clean();
imagedestroy($oGdImage);
imagedestroy($oNewGdImage);
return $oResampledImage;
}
/**
* @return string
*/
@@ -412,4 +507,6 @@ class ormDocument
{
return md5($this->GetData() ?? '');
}
}

View File

@@ -433,9 +433,10 @@ class UserProfileBrickController extends BrickController
$oCurContact = UserRights::GetContactObject();
// Resizing image
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurContact), $sPictureAttCode);
$aSize = utils::GetImageSize($oImage->GetData());
$oImage = utils::ResizeImageToFit($oImage, $aSize[0], $aSize[1], $oAttDef->Get('storage_max_width'),
$oAttDef->Get('storage_max_height'));
$oImage = $oImage->ResizeImageToFit(
$oAttDef->Get('storage_max_width'),
$oAttDef->Get('storage_max_height')
);
// Setting it to the contact
$oCurContact->Set($sPictureAttCode, $oImage);
// Forcing allowed writing on the object if necessary.

View File

@@ -2002,7 +2002,8 @@ EOF
$oDoc = utils::ReadPostedDocument('upload');
if (InlineImage::IsImage($oDoc->GetMimeType())) {
$aDimensions = null;
$oDoc = InlineImage::ResizeImageToFit($oDoc, $aDimensions);
$iMaxImageSize = (int)MetaModel::GetConfig()->Get('inline_image_max_storage_width', 0);
$oDoc = $oDoc->ResizeImageToFit($iMaxImageSize, $iMaxImageSize, $aDimensions);
/** @var InlineImage $oAttachment */
$oAttachment = MetaModel::NewObject('InlineImage');
$oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime'));
@@ -2059,7 +2060,8 @@ EOF
));
} else {
$aDimensions = null;
$oDoc = InlineImage::ResizeImageToFit($oDoc, $aDimensions);
$iMaxImageSize = (int)MetaModel::GetConfig()->Get('inline_image_max_storage_width', 0);
$oDoc = $oDoc->ResizeImageToFit($iMaxImageSize, $iMaxImageSize, $aDimensions);
/** @var InlineImage $oAttachment */
$oAttachment = MetaModel::NewObject('InlineImage');
$oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime'));

View File

@@ -139,4 +139,115 @@ class ormDocumentTest extends ItopDataTestCase
],
];
}
public function testResizeImageToFitShouldResizeImageWhenImageIsTooBig()
{
$sImageData = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAAe0lEQVQI132OMQoCMRRE3/9Z3M126V0kB9BCvICnziXs7QIWlttqpWMRFQT1VcMbGMb4xPoQ18uWL4eTxxglSaq1Au8OwM1TSi3nnLGnzxKA4fM8N1VKQVyPZ6Br6s4Xhj7st9OwcNy61yUsGEK3Nmu+mUawcbfiN85fHsBoHdXt5HATAAAAAElFTkSuQmCC');
$sMimeType = 'image/png';
$sFileName = 'MyImage.png';
$oDoc = new ormDocument($sImageData, $sMimeType, $sFileName);
$iMawWidth = 6;
$iMaxHeight = 5;
$oResult = $oDoc->ResizeImageToFit($iMawWidth, $iMaxHeight, $aDimensions);
$aRealDimensions = \utils::GetImageSize($oResult->GetData());
$aActualDimensions = [
'width' => $aRealDimensions[0],
'height' => $aRealDimensions[1],
];
$this->assertNotSame( $oDoc, $oResult, 'ResizeImageToFit should return a new object when there have been some modifications');
$this->assertIsArray( $aDimensions, 'ResizeImageToFit should fill aDimension with the dimensions of the new image when there are no issues');
$this->assertEquals( $aDimensions, $aActualDimensions, 'The returned dimensions should match the real dimensions of the image');
$this->assertLessThanOrEqual($iMawWidth, $aActualDimensions['width'], 'The new width should be less than or equal to max width');
$this->assertLessThanOrEqual($iMaxHeight, $aActualDimensions['height'], 'The new height should be less than or equal to max height');
}
public function testResizeImageToFitShouldDoNothingWhenImageIsAlreadySmallEnough()
{
$sImageData = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAAe0lEQVQI132OMQoCMRRE3/9Z3M126V0kB9BCvICnziXs7QIWlttqpWMRFQT1VcMbGMb4xPoQ18uWL4eTxxglSaq1Au8OwM1TSi3nnLGnzxKA4fM8N1VKQVyPZ6Br6s4Xhj7st9OwcNy61yUsGEK3Nmu+mUawcbfiN85fHsBoHdXt5HATAAAAAElFTkSuQmCC');
$sMimeType = 'image/png';
$sFileName = 'MyImage.png';
$oDoc = new ormDocument($sImageData, $sMimeType, $sFileName);
$iMawWidth = 10;
$iMaxHeight = 8;
$oResult = $oDoc->ResizeImageToFit($iMawWidth, $iMaxHeight, $aDimensions);
$this->assertSame( $oDoc, $oResult, 'ResizeImageToFit should return the same object when there have been no modifications');
$this->assertIsArray( $aDimensions, 'ResizeImageToFit should fill aDimension with the dimensions of the image when there are no issues');
}
public function testResizeImageToFitShouldDoNothingWhenItCannotReadTheImage()
{
$sImageData = 'garbagedata';
$sMimeType = 'image/png';
$sFileName = 'MyImage.png';
$oDoc = new ormDocument($sImageData, $sMimeType, $sFileName);
$iMawWidth = 10;
$iMaxHeight = 8;
$oResult = $oDoc->ResizeImageToFit($iMawWidth, $iMaxHeight, $aDimensions);
$this->assertSame( $oDoc, $oResult, 'ResizeImageToFit should return the same object when there have been no modifications');
$this->assertNull( $aDimensions, 'ResizeImageToFit should fill aDimension with null when there are issues');
}
public function testResizeImageToFitShouldDoNothingWhenItDoesNotHandleTheMimeType()
{
$sImageData = base64_decode('Qk3mAAAAAAAAAEYAAAA4AAAACgAAAAgAAAABABAAAwAAAKAAAAAjLgAAIy4AAAAAAAAAAAAAAHwAAOADAAAfAAAAAAAAAMQExATEBMQExATEBMQExATEBMQExATEBMQExATEBMQExATEBMQExAQAAAAAAAAAAAAAAAAgBMQgxATEBAAAAAAAAAAAAAAAACAExCAgBAAAIQT/f/9/1loAACAAxATEGMQEAABjDP9//3//fwAAxATEBMQUxAQAACEE/3//f3tvAADEBMQExATEBAAAAAAAAAAAAAAAACAAxATEBMQEAAA=');
$sMimeType = 'image/bmp';
$sFileName = 'MyImage.bmp';
$oDoc = new ormDocument($sImageData, $sMimeType, $sFileName);
$iMawWidth = 5;
$iMaxHeight = 5;
$oResult = $oDoc->ResizeImageToFit($iMawWidth, $iMaxHeight, $aDimensions);
$this->assertSame( $oDoc, $oResult, 'ResizeImageToFit should return the same object when there have been no modifications');
$this->assertNull( $aDimensions, 'ResizeImageToFit should fill aDimension with null when there are issues');
}
public function testResizeImageToFitShouldNotResizeWhenMaximumIs0()
{
$sImageData = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAAe0lEQVQI132OMQoCMRRE3/9Z3M126V0kB9BCvICnziXs7QIWlttqpWMRFQT1VcMbGMb4xPoQ18uWL4eTxxglSaq1Au8OwM1TSi3nnLGnzxKA4fM8N1VKQVyPZ6Br6s4Xhj7st9OwcNy61yUsGEK3Nmu+mUawcbfiN85fHsBoHdXt5HATAAAAAElFTkSuQmCC');
$sMimeType = 'image/png';
$sFileName = 'MyImage.png';
$oDoc = new ormDocument($sImageData, $sMimeType, $sFileName);
$iMawWidth = 0;
$iMaxHeight = 0;
$oResult = $oDoc->ResizeImageToFit($iMawWidth, $iMaxHeight, $aDimensions);
$this->assertSame( $oDoc, $oResult, 'ResizeImageToFit should return the same object when there have been no modifications');
$this->assertIsArray( $aDimensions, 'ResizeImageToFit should fill aDimension with the dimensions of the image when there are no issues');
}
public function testResizeImageToFitShouldIgnoreMaximum0Axis()
{
$sImageData = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAAe0lEQVQI132OMQoCMRRE3/9Z3M126V0kB9BCvICnziXs7QIWlttqpWMRFQT1VcMbGMb4xPoQ18uWL4eTxxglSaq1Au8OwM1TSi3nnLGnzxKA4fM8N1VKQVyPZ6Br6s4Xhj7st9OwcNy61yUsGEK3Nmu+mUawcbfiN85fHsBoHdXt5HATAAAAAElFTkSuQmCC');
$sMimeType = 'image/png';
$sFileName = 'MyImage.png';
$oDoc = new ormDocument($sImageData, $sMimeType, $sFileName);
$iMawWidth = 5;
$iMaxHeight = 0;
$oResult = $oDoc->ResizeImageToFit($iMawWidth, $iMaxHeight, $aDimensions);
$aRealDimensions = \utils::GetImageSize($oResult->GetData());
$aActualDimensions = [
'width' => $aRealDimensions[0],
'height' => $aRealDimensions[1],
];
$this->assertNotSame( $oDoc, $oResult, 'ResizeImageToFit should return a new object when there have been some modifications');
$this->assertIsArray( $aDimensions, 'ResizeImageToFit should fill aDimension with the dimensions of the new image when there are no issues');
$this->assertEquals( $aDimensions, $aActualDimensions, 'The returned dimensions should match the real dimensions of the image');
$this->assertEquals($iMawWidth, $aActualDimensions['width'], 'The new width should be exactly the max width');
$this->assertGreaterThanOrEqual($iMaxHeight, $aActualDimensions['height'], 'The new height should not be 0');
}
}