From 81f5d38bc43d31ec613c071e5d1e41b9c7a0c5bc Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Thu, 12 Oct 2023 13:03:12 +0200 Subject: [PATCH] [FEATURE] Add webp support for GifBuilder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is now possible - if PHP was compiled with gdlib that supports webp (available since PHP 7.1), to generate webp files via GifBuilder. Example: page.10 = IMAGE page.10 { file = GIFBUILDER file { backColor = yellow XY = 1024,199 format = webp quality = 44 10 = IMAGE 10.offset = 10,10 10.file = 1:/backimage.jpg } } Resolves: #102177 Releases: main Change-Id: I48dac569da5904b4871ee2ad3b3759c54004e8c0 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/81417 Reviewed-by: Benni Mack <benni@typo3.org> Tested-by: core-ci <typo3@b13.com> Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Andreas Kienast <a.fernandez@scripting-base.de> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Andreas Kienast <a.fernandez@scripting-base.de> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Benni Mack <benni@typo3.org> --- .../Configuration/DefaultConfiguration.php | 1 + .../DefaultConfigurationDescription.yaml | 3 + ...PSupportForImagesGeneratedByGIFBUILDER.rst | 55 ++++++++++++++++++ .../frontend/Classes/Imaging/GifBuilder.php | 42 ++++++++----- .../Functional/Imaging/GifBuilderTest.php | 16 ++++- .../Controller/EnvironmentController.php | 55 ++++++++++++------ .../Environment/ImageProcessing.html | 5 +- .../Images/TestReference/Gdlib-box.webp | Bin 0 -> 15502 bytes 8 files changed, 140 insertions(+), 37 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/13.0/Feature-102177-WebPSupportForImagesGeneratedByGIFBUILDER.rst create mode 100644 typo3/sysext/install/Resources/Public/Images/TestReference/Gdlib-box.webp diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php index b7e03b8959ba..34136f543faf 100644 --- a/typo3/sysext/core/Configuration/DefaultConfiguration.php +++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php @@ -36,6 +36,7 @@ return [ 'processor_colorspace' => '', 'processor_interlace' => 'None', 'jpg_quality' => 85, + 'webp_quality' => 85, ], 'SYS' => [ // System related concerning both frontend and backend. diff --git a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml index c8cf5ac27238..ba9bce29e958 100644 --- a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml +++ b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml @@ -44,6 +44,9 @@ GFX: jpg_quality: type: int description: 'Integer: Default JPEG generation quality' + webp_quality: + type: int + description: 'Integer: Default WebP generation quality' SYS: type: container description: 'System' diff --git a/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102177-WebPSupportForImagesGeneratedByGIFBUILDER.rst b/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102177-WebPSupportForImagesGeneratedByGIFBUILDER.rst new file mode 100644 index 000000000000..03132ab1cf9a --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102177-WebPSupportForImagesGeneratedByGIFBUILDER.rst @@ -0,0 +1,55 @@ +.. include:: /Includes.rst.txt + +.. _feature-102177-1697483829: + +================================================================== +Feature: #102177 - WebP support for images generated by GIFBUILDER +================================================================== + +See :issue:`102177` + +Description +=========== + +GIFBUILDER, the image manipulation library for TypoScript based on GDlib, a PHP +extension bundled into PHP, now also supports generating resulting files of +type "webp". + +WebP is an image format, that is supported my all moderns browsers, and usually +has a better compression (= smaller file size) than jpg files. + + +Impact +====== + +If defined via format=webp within a GifBuilder setup, the generated files are +now webp instead of png (the default). + +It is possible to define the quality of a webp image similar to jpg images +globally via :php:`$TYPO3_CONF_VARS['GFX']['webp_quality']` or via TypoScript's +"quality" property on a per-image basis. + +Example +------- + +.. code-block:: typoscript + + page.10 = IMAGE + page.10 { + file = GIFBUILDER + file { + backColor = yellow + XY = 1024,199 + format = webp + quality = 44 + + 10 = IMAGE + 10.offset = 10,10 + 10.file = 1:/my-image.jpg + } + } + +A new test in the Environment module / Install Tool can be used to check if the +bundled GDlib extension of your PHP version supports the WebP image format. + +.. index:: FAL, TypoScript, ext:frontend diff --git a/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php b/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php index 26b9f4b1a2cc..67fb4e0b8c94 100644 --- a/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php +++ b/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php @@ -163,6 +163,10 @@ class GifBuilder * @var int<10, 100> */ protected int $jpegQuality = 85; + /** + * @var int<10, 100> + */ + protected int $webpQuality = 85; public function __construct() { @@ -170,8 +174,8 @@ class GifBuilder if ($gfxConf['processor_effects'] ?? false) { $this->processorEffectsEnabled = true; } - // Setting default JPG parameters: $this->jpegQuality = MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, 85); + $this->webpQuality = MathUtility::forceIntegerInRange($gfxConf['webp_quality'], 10, 100, 85); if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) { $this->gdlibExtensions[] = 'jpg'; $this->gdlibExtensions[] = 'jpeg'; @@ -179,6 +183,9 @@ class GifBuilder if (function_exists('imagecreatefrompng') && function_exists('imagepng')) { $this->gdlibExtensions[] = 'png'; } + if (function_exists('imagecreatefromwebp') && function_exists('imagewebp')) { + $this->gdlibExtensions[] = 'webp'; + } if (function_exists('imagecreatefromgif') && function_exists('imagegif')) { $this->gdlibExtensions[] = 'gif'; } @@ -456,6 +463,7 @@ class GifBuilder break; case 'jpg': case 'jpeg': + case 'webp': // Use the default $quality = isset($this->setup['quality']) ? MathUtility::forceIntegerInRange((int)$this->setup['quality'], 10, 100) : 0; $this->ImageWrite($gdImage, $file, $quality); @@ -1397,20 +1405,15 @@ class GifBuilder /** * Returns the file extension used in the filename - * - * @return string Extension; "jpg" or "gif"/"png" */ protected function extension(): string { - switch (strtolower($this->setup['format'] ?? '')) { - case 'jpg': - case 'jpeg': - return 'jpg'; - case 'gif': - return 'gif'; - default: - return 'png'; - } + return match (strtolower($this->setup['format'] ?? '')) { + 'jpg', 'jpeg' => 'jpg', + 'gif' => 'gif', + 'webp' => 'webp', + default => 'png', + }; } /** @@ -2613,10 +2616,12 @@ class GifBuilder case 'jpg': case 'jpeg': if (function_exists('imagejpeg')) { - if ($quality === 0) { - $quality = $this->jpegQuality; - } - $result = imagejpeg($destImg, $theImage, $quality); + $result = imagejpeg($destImg, $theImage, ($quality ?: $this->jpegQuality)); + } + break; + case 'webp': + if (function_exists('imagewebp')) { + $result = imagewebp($destImg, $theImage, ($quality ?: $this->webpQuality)); } break; case 'gif': @@ -2669,6 +2674,11 @@ class GifBuilder return imagecreatefromjpeg($sourceImg); } break; + case 'webp': + if (function_exists('imagecreatefromwebp')) { + return imagecreatefromwebp($sourceImg); + } + break; } // If none of the above: $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $sourceImg); diff --git a/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php b/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php index e6b72b34741a..b34da1c90945 100644 --- a/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php +++ b/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php @@ -59,16 +59,27 @@ final class GifBuilderTest extends FunctionalTestCase ); } + public static function fileExtensionDataProvider(): array + { + return [ + 'jpg' => ['jpg'], + 'png' => ['png'], + 'gif' => ['gif'], + 'webp' => ['webp'], + ]; + } + /** * @test + * @dataProvider fileExtensionDataProvider */ - public function buildSimpleGifBuilderImageInComposerMode(): void + public function buildSimpleGifBuilderImageInComposerMode(string $fileExtension): void { $this->simulateCliRequestInComposerMode(); $conf = [ 'XY' => '10,10', - 'format' => 'jpg', + 'format' => $fileExtension, ]; $gifBuilder = new GifBuilder(); @@ -77,6 +88,7 @@ final class GifBuilderTest extends FunctionalTestCase self::assertFileDoesNotExist(Environment::getProjectPath() . '/' . $gifFileName); self::assertFileExists(Environment::getPublicPath() . '/' . $gifFileName); + self::assertStringEndsWith('.' . $fileExtension, $gifFileName); } /** diff --git a/typo3/sysext/install/Classes/Controller/EnvironmentController.php b/typo3/sysext/install/Classes/Controller/EnvironmentController.php index 9c6a0b2eea19..753e9d499d85 100644 --- a/typo3/sysext/install/Classes/Controller/EnvironmentController.php +++ b/typo3/sysext/install/Classes/Controller/EnvironmentController.php @@ -668,8 +668,7 @@ class EnvironmentController extends AbstractController $inputFile = $imageBasePath . 'TestInput/BackgroundOrange.gif'; $overlayFile = $imageBasePath . 'TestInput/Test.jpg'; $maskFile = $imageBasePath . 'TestInput/MaskBlackWhite.gif'; - $resultFile = $this->getImagesPath() . $imageService->filenamePrefix - . StringUtility::getUniqueId($imageService->alternativeOutputKey . 'combine1') . '.jpg'; + $resultFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId($imageService->alternativeOutputKey . 'combine1') . '.jpg'; $imageService->combineExec($inputFile, $overlayFile, $maskFile, $resultFile); $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $resultFile); if ($imageInfo->getWidth() > 0) { @@ -704,8 +703,7 @@ class EnvironmentController extends AbstractController $inputFile = $imageBasePath . 'TestInput/BackgroundCombine.jpg'; $overlayFile = $imageBasePath . 'TestInput/Test.jpg'; $maskFile = $imageBasePath . 'TestInput/MaskCombine.jpg'; - $resultFile = $this->getImagesPath() . $imageService->filenamePrefix - . StringUtility::getUniqueId($imageService->alternativeOutputKey . 'combine2') . '.jpg'; + $resultFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId($imageService->alternativeOutputKey . 'combine2') . '.jpg'; $imageService->combineExec($inputFile, $overlayFile, $maskFile, $resultFile); $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $resultFile); if ($imageInfo->getWidth() > 0) { @@ -739,7 +737,7 @@ class EnvironmentController extends AbstractController 'color' => 'olive', ]; $gifBuilder->makeBox($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('gdSimple') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdSimple') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $result = [ 'fileExists' => true, @@ -765,7 +763,7 @@ class EnvironmentController extends AbstractController 'color' => 'olive', ]; $gifBuilder->makeBox($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('gdBox') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdBox') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $result = [ 'fileExists' => true, @@ -776,6 +774,32 @@ class EnvironmentController extends AbstractController return $this->getImageTestResponse($result); } + /** + * GD from image with box exported as webp file + */ + public function imageProcessingGdlibFromFileToWebpAction(): ResponseInterface + { + $gifBuilder = $this->initializeGifBuilder(); + $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; + $inputFile = $imageBasePath . 'TestInput/Test.webp'; + $image = $gifBuilder->imageCreateFromFile($inputFile); + $workArea = [0, 0, 400, 300]; + $conf = [ + 'dimensions' => '20,100,740,100', + 'color' => 'olive', + ]; + $gifBuilder->makeBox($image, $conf, $workArea); + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdBox') . '.webp'; + $gifBuilder->ImageWrite($image, $outputFile); + $result = [ + 'fileExists' => true, + 'outputFile' => $outputFile, + 'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-box.webp', + 'command' => $gifBuilder->getGraphicalFunctions()->IM_commands, + ]; + return $this->getImageTestResponse($result); + } + /** * GD with text */ @@ -798,7 +822,7 @@ class EnvironmentController extends AbstractController ]; $conf['BBOX'] = $gifBuilder->calcBBox($conf); $gifBuilder->makeText($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('gdText') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdText') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $result = [ 'fileExists' => true, @@ -837,12 +861,12 @@ class EnvironmentController extends AbstractController ]; $conf['BBOX'] = $gifBuilder->calcBBox($conf); $gifBuilder->makeText($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('gdText') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdText') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $conf['offset'] = '30,120'; $conf['niceText'] = 1; $gifBuilder->makeText($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('gdNiceText') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdNiceText') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $result = [ 'fileExists' => true, @@ -881,12 +905,12 @@ class EnvironmentController extends AbstractController ]; $conf['BBOX'] = $gifBuilder->calcBBox($conf); $gifBuilder->makeText($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('gdText') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdText') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $conf['offset'] = '30,120'; $conf['niceText'] = 1; $gifBuilder->makeText($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('gdNiceText') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdNiceText') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $conf['offset'] = '30,160'; $conf['niceText'] = 1; @@ -899,7 +923,7 @@ class EnvironmentController extends AbstractController // Warning: Re-uses $image from above! $gifBuilder->makeShadow($image, $conf['shadow.'], $workArea, $conf); $gifBuilder->makeText($image, $conf, $workArea); - $outputFile = $this->getImagesPath() . $gifBuilder->getGraphicalFunctions()->filenamePrefix . StringUtility::getUniqueId('GDwithText-niceText-shadow') . '.png'; + $outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('GDwithText-niceText-shadow') . '.png'; $gifBuilder->ImageWrite($image, $outputFile); $result = [ 'fileExists' => true, @@ -915,12 +939,7 @@ class EnvironmentController extends AbstractController */ protected function initializeGifBuilder(): GifBuilder { - $gifBuilder = GeneralUtility::makeInstance(GifBuilder::class); - $gifBuilder->getGraphicalFunctions()->dontCheckForExistingTempFile = true; - $gifBuilder->getGraphicalFunctions()->filenamePrefix = 'installTool-'; - $gifBuilder->getGraphicalFunctions()->alternativeOutputKey = 'typo3InstallTest'; - $gifBuilder->getGraphicalFunctions()->setImageFileExt(self::IMAGE_FILE_EXT); - return $gifBuilder; + return GeneralUtility::makeInstance(GifBuilder::class); } /** diff --git a/typo3/sysext/install/Resources/Private/Templates/Environment/ImageProcessing.html b/typo3/sysext/install/Resources/Private/Templates/Environment/ImageProcessing.html index 3133b935bab7..3193f02eb65c 100644 --- a/typo3/sysext/install/Resources/Private/Templates/Environment/ImageProcessing.html +++ b/typo3/sysext/install/Resources/Private/Templates/Environment/ImageProcessing.html @@ -92,9 +92,12 @@ <h3>Create simple image</h3> <div class="t3js-imageProcessing-twinContainer" data-test="imageProcessingGdlibSimple"></div> -<h3>Create image from file</h3> +<h3>Create image with box from file</h3> <div class="t3js-imageProcessing-twinContainer" data-test="imageProcessingGdlibFromFile"></div> +<h3>Create webp image with box from webp file</h3> +<div class="t3js-imageProcessing-twinContainer" data-test="imageProcessingGdlibFromFileToWebp"></div> + <h3>Render text with TrueType font</h3> <div class="t3js-imageProcessing-twinContainer" data-test="imageProcessingGdlibRenderText"></div> diff --git a/typo3/sysext/install/Resources/Public/Images/TestReference/Gdlib-box.webp b/typo3/sysext/install/Resources/Public/Images/TestReference/Gdlib-box.webp new file mode 100644 index 0000000000000000000000000000000000000000..91de68b140f109941e66a3117155a6057bc50ed4 GIT binary patch literal 15502 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 BP<;RZ literal 0 HcmV?d00001 -- GitLab