From 71386198cfa4f4305a9237acf980c0e6a87bee60 Mon Sep 17 00:00:00 2001 From: Timmy38 <101416770+Timmy38@users.noreply.github.com> Date: Fri, 8 Aug 2025 10:43:35 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B03124=20-=20Refactorize=20ResizeImageToFi?= =?UTF-8?q?t=20methods=20(#734)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/cmdbabstract.class.inc.php | 25 ++-- application/utils.inc.php | 91 -------------- core/inlineimage.class.inc.php | 80 +------------ core/ormdocument.class.inc.php | 97 +++++++++++++++ .../Controller/UserProfileBrickController.php | 7 +- pages/ajax.render.php | 6 +- .../unitary-tests/core/ormDocumentTest.php | 111 ++++++++++++++++++ 7 files changed, 227 insertions(+), 190 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 4d3b4e7ba..97de38f25 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -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)) diff --git a/application/utils.inc.php b/application/utils.inc.php index 56957079d..932a7f4f4 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -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: {########-####-####-####-############} * diff --git a/core/inlineimage.class.inc.php b/core/inlineimage.class.inc.php index bc59cb7dd..6d99363d9 100644 --- a/core/inlineimage.class.inc.php +++ b/core/inlineimage.class.inc.php @@ -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); } /** diff --git a/core/ormdocument.class.inc.php b/core/ormdocument.class.inc.php index 27af2d970..45a573096 100644 --- a/core/ormdocument.class.inc.php +++ b/core/ormdocument.class.inc.php @@ -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() ?? ''); } + + } diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/UserProfileBrickController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/UserProfileBrickController.php index 32f9c2184..6dcd59c78 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Controller/UserProfileBrickController.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/UserProfileBrickController.php @@ -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. diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 46eee56ce..465650ea0 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -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')); diff --git a/tests/php-unit-tests/unitary-tests/core/ormDocumentTest.php b/tests/php-unit-tests/unitary-tests/core/ormDocumentTest.php index 9d2c82319..b6a33d4f0 100644 --- a/tests/php-unit-tests/unitary-tests/core/ormDocumentTest.php +++ b/tests/php-unit-tests/unitary-tests/core/ormDocumentTest.php @@ -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'); + } + }