Skip to content

Commit

Permalink
[FEATURE] Add support for AVIF files in GIFBuilder
Browse files Browse the repository at this point in the history
The Image AVIF format is supported by "libgd" since
PHP 8.1 which can now be used to create images by
GIFBUILDER.

See https://en.wikipedia.org/wiki/AVIF

Example:
    page.10 = IMAGE
    page.10 {
      file = GIFBUILDER
      file {
        backColor = yellow
        XY = 1024,199
        format = avif
        speed = 8
        quality = 44

        10 = IMAGE
        10.offset = 10,10
        10.file = 1:/my-image.jpg
      }
    }

Resolves: #102353
Releases: main
Change-Id: Ibb0697f1c7120ba596ac94f31e51d4863c3999b3
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/81712
Tested-by: core-ci <[email protected]>
Tested-by: Garvin Hicking <[email protected]>
Tested-by: Stefan Bürk <[email protected]>
Reviewed-by: Stefan Bürk <[email protected]>
Reviewed-by: Garvin Hicking <[email protected]>
  • Loading branch information
bmack authored and sbuerk committed Sep 16, 2024
1 parent 5a7748b commit 4dad327
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 5 deletions.
5 changes: 5 additions & 0 deletions typo3/sysext/backend/Resources/Private/tsref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,11 @@ If value is tx_myextension[input][  |  ]  then the fieldname "email" would be
<default><![CDATA[
]]></default>
</property>
<property name="speed" type="int">
<description><![CDATA[posint (0-10)
Speed parameter (if ".format" = avif)]]></description>
<default><![CDATA[-1]]></default>
</property>
<property name="transparentBackground" type="boolean">
<description><![CDATA[Set this flag to render the background transparent. TYPO3 makes the color found at position 0,0 of the image (upper left corner) transparent.
If you render text you should leave the niceText option OFF as the result with probably be more precise without the niceText antialiasing hack]]></description>
Expand Down
1 change: 1 addition & 0 deletions typo3/sysext/core/Configuration/DefaultConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
'processor_interlace' => 'None',
'jpg_quality' => 85,
'webp_quality' => 85,
'avif_quality' => 85,
],
'SYS' => [
// System related concerning both frontend and backend.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ GFX:
webp_quality:
type: int
description: 'Integer: Default WebP generation quality'
avif_quality:
type: int
description: 'Integer: Default AVIF generation quality'
SYS:
type: container
description: 'System'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ 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 by all moderns browsers, and usually
WebP is an image format, that is supported by all modern browsers, and usually
has a better compression (= smaller file size) than jpg files.


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.. include:: /Includes.rst.txt

.. _feature-102353-1699523309:

==================================================================
Feature: #102353 - AVIF support for images generated by GIFBUILDER
==================================================================

See :issue:`102353`

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 "avif".

AVIF is an image format, that is supported by most modern browsers, and usually
has a better compression (= smaller file size) than jpg files.

.. important::

Before using this feature, please check whether the used operating system
actually supports de/encoding AVIF files. Especially Debian 11 (Bullseye)
and older or systems forked from that may lack AVIF support.

Impact
======

If defined via format=avif within a GifBuilder setup, the generated files are
now AVIF files instead of png (the default).

It is possible to define the quality of a AVIF image similar to jpg images
globally via :php:`$TYPO3_CONF_VARS['GFX']['avif_quality']` or via TypoScript's
"quality" property on a per-image basis. Via TypoScript it is also possible
to use the new property "speed" - see https://www.php.net/manual/en/function.imageavif.php
for more details.

Example
-------

.. code-block:: typoscript
page.10 = IMAGE
page.10 {
file = GIFBUILDER
file {
backColor = yellow
XY = 1024,199
format = avif
quality = 44
speed = 1
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 AVIF image format.

.. index:: Frontend, TypoScript, ext:frontend
34 changes: 30 additions & 4 deletions typo3/sysext/frontend/Classes/Imaging/GifBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
* The concept is known from TypoScript as "GIFBUILDER" where you can define a "numerical array" (TypoScript term as well)
* of "GIFBUILDER OBJECTS" (like "TEXT", "IMAGE", etc.) and they will be rendered onto an image one by one.
* The name "GIFBUILDER" comes from the time when GIF was the only file format supported.
* .png, .jpg and .webp files are just as well to create today (configured with TYPO3_CONF_VARS[GFX])
* .png, .jpg, .webp and .avif files are just as well to create today (configured with TYPO3_CONF_VARS[GFX])
*
* Here is an example of how to use this class:
*
Expand Down Expand Up @@ -172,13 +172,19 @@ class GifBuilder
*/
protected int $webpQuality = 85;

/**
* @var int<-1, 100>
*/
protected int $avifQuality = 85;

public function __construct()
{
$gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
if ($gfxConf['processor_effects'] ?? false) {
$this->processorEffectsEnabled = true;
}
$this->jpegQuality = MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, $this->jpegQuality);
$this->avifQuality = MathUtility::forceIntegerInRange($gfxConf['avif_quality'] ?? 0, -1, 100, $this->avifQuality);
if (isset($gfxConf['webp_quality'])) {
// see IMG_WEBP_LOSSLESS // https://www.php.net/manual/en/image.constants.php
if ($gfxConf['webp_quality'] === 'lossless') {
Expand All @@ -197,6 +203,9 @@ public function __construct()
if (function_exists('imagecreatefromwebp') && function_exists('imagewebp')) {
$this->gdlibExtensions[] = 'webp';
}
if (function_exists('imagecreatefromavif') && function_exists('imageavif')) {
$this->gdlibExtensions[] = 'avif';
}
if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
$this->gdlibExtensions[] = 'gif';
}
Expand Down Expand Up @@ -468,6 +477,11 @@ protected function output(\GdImage $gdImage, string $file): void
$quality = isset($this->setup['quality']) ? MathUtility::forceIntegerInRange((int)$this->setup['quality'], 10, 101) : 0;
$this->ImageWrite($gdImage, $file, $quality);
break;
case 'avif':
$quality = isset($this->setup['quality']) ? MathUtility::forceIntegerInRange((int)$this->setup['quality'], -1, 100) : 0;
$speed = isset($this->setup['speed']) ? MathUtility::forceIntegerInRange((int)$this->setup['speed'], -1, 10) : -1;
$this->ImageWrite($gdImage, $file, $quality, $speed);
break;
}
}

Expand Down Expand Up @@ -1411,6 +1425,7 @@ protected function extension(): string
'jpg', 'jpeg' => 'jpg',
'gif' => 'gif',
'webp' => 'webp',
'avif' => 'avif',
default => 'png',
};
}
Expand Down Expand Up @@ -2580,13 +2595,14 @@ protected function applyImageMagickToPHPGif(\GdImage &$im, string $command): voi
*
* @param \GdImage $destImg The GDlib image resource pointer
* @param string $theImage The absolute file path to write to
* @param int $quality The image quality (for JPEGs)
* @return bool The output of either imageGif, imagePng or imageJpeg based on the filename to write
* @param int $quality The image quality (for JPEG, WebP and AVIF files)
* @param int<-1,10> $speed The image speed (for AVIFs), 0 (slow, smaller file) to 10 (fast, larger file), -1 for default (=6)
* @return bool The output of either imageGif, imagePng, imageJpeg, imagewebp or imageavif based on the filename to write
* @see maskImageOntoImage()
* @see scale()
* @see output()
*/
public function ImageWrite(\GdImage &$destImg, string $theImage, int $quality = 0): bool
public function ImageWrite(\GdImage &$destImg, string $theImage, int $quality = 0, int $speed = -1): bool
{
imageinterlace($destImg, false);
$ext = strtolower(substr($theImage, (int)strrpos($theImage, '.') + 1));
Expand All @@ -2603,6 +2619,11 @@ public function ImageWrite(\GdImage &$destImg, string $theImage, int $quality =
$result = imagewebp($destImg, $theImage, ($quality ?: $this->webpQuality));
}
break;
case 'avif':
if (function_exists('imageavif')) {
$result = imageavif($destImg, $theImage, ($quality ?: $this->avifQuality), $speed);
}
break;
case 'gif':
if (function_exists('imagegif')) {
imagetruecolortopalette($destImg, true, 256);
Expand Down Expand Up @@ -2658,6 +2679,11 @@ public function imageCreateFromFile(string $sourceImg): \GdImage
return imagecreatefromwebp($sourceImg);
}
break;
case 'avif':
if (function_exists('imagecreatefromavif')) {
return imagecreatefromavif($sourceImg);
}
break;
}
// If none of the above:
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $sourceImg);
Expand Down
42 changes: 42 additions & 0 deletions typo3/sysext/install/Classes/Controller/EnvironmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,39 @@ public function imageProcessingGdlibFromFileToWebpAction(): ResponseInterface
return $this->getImageTestResponse($result);
}

/**
* GD from image with box exported as AVIF file
*/
public function imageProcessingGdlibFromFileToAvifAction(): ResponseInterface
{
$gifBuilder = $this->initializeGifBuilder();
$imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
$inputFile = $imageBasePath . 'TestInput/Test.avif';
$image = $gifBuilder->imageCreateFromFile($inputFile);
$workArea = [0, 0, 400, 300];
$conf = [
'dimensions' => '10,50,380,50',
'color' => 'olive',
];
$gifBuilder->makeBox($image, $conf, $workArea);
$outputFile = $this->getImagesPath() . 'installTool-' . StringUtility::getUniqueId('gdBox') . '.avif';
$success = $gifBuilder->ImageWrite($image, $outputFile);
if ($success) {
$result = [
'fileExists' => true,
'outputFile' => $outputFile,
'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-box.avif',
'command' => $gifBuilder->getGraphicalFunctions()->IM_commands,
];
} else {
$result = [
'status' => [$this->avifImageGenerationFailedMessage()],
'command' => $gifBuilder->getGraphicalFunctions()->IM_commands,
];
}
return $this->getImageTestResponse($result);
}

/**
* GD with text
*/
Expand Down Expand Up @@ -1151,6 +1184,15 @@ protected function imageGenerationFailedMessage(): FlashMessage
);
}

protected function avifImageGenerationFailedMessage(): FlashMessage
{
return new FlashMessage(
'Exporting AVIF format failed. Probably your system does not provide the necessary codec libraries for this.',
'Skipped test',
ContextualFeedbackSeverity::INFO
);
}

/**
* Find out if ImageMagick or GraphicsMagick is enabled and set up
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ <h3>Create image with box from file</h3>
<h3>Create webp image with box from webp file</h3>
<div class="t3js-imageProcessing-twinContainer" data-test="imageProcessingGdlibFromFileToWebp"></div>

<h3>Create avif image with box from file</h3>
<div class="t3js-imageProcessing-twinContainer" data-test="imageProcessingGdlibFromFileToAvif"></div>

<h3>Render text with TrueType font</h3>
<div class="t3js-imageProcessing-twinContainer" data-test="imageProcessingGdlibRenderText"></div>

Expand Down
Binary file not shown.
Binary file not shown.

0 comments on commit 4dad327

Please sign in to comment.