Skip to content
Snippets Groups Projects
Commit e3907770 authored by Johannes Kreiner's avatar Johannes Kreiner
Browse files

[TASK] Implement thumbnail webp rendering

parent 1201cbc1
No related branches found
No related tags found
No related merge requests found
......@@ -169,6 +169,7 @@ class YoutubeController extends ActionController {
$imageService = GeneralUtility::makeInstance(ImageService::class);
$processedImage = $imageService->applyProcessingInstructions($fileObject, $processingInstructions);
$jsonArray[$index]['thumbnail'] = $imageService->getImageUri($processedImage);
$jsonArray[$index]['thumbnailImageObject'] = $fileObject;
}
return $jsonArray;
......
<?php
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
namespace SGalinski\SgYoutube\ViewHelpers;
use Closure;
use InvalidArgumentException;
use RuntimeException;
use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException;
use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException;
use TYPO3\CMS\Extbase\Service\ImageService;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic;
use UnexpectedValueException;
/**
* Helps with generating the picture-element according to the websites setup
*
* @see \FluidTYPO3\Vhs\ViewHelpers\Media\SourceViewHelper for inspiration
* @link https://docs.typo3.org/m/typo3/reference-typoscript/main/en-us/Functions/Imgresource.html#width
*/
class PictureViewHelper extends AbstractViewHelper {
use CompileWithContentArgumentAndRenderStatic;
/** @var bool */
protected $escapeOutput = FALSE;
/** @var ImageService */
protected static ImageService $imageService;
/** @var ResourceFactory */
protected static ResourceFactory $resourceFactory;
/** @var array */
protected static array $responsiveSettings = [];
/**
* Register the ViewHelper arguments
*/
public function initializeArguments(): void {
$this->registerArgument(
'image',
'mixed',
'The reference of the image or a string like 1:/someimage.png with treatIdAsReference set',
TRUE
);
$this->registerArgument('class', 'string', 'The class attribute');
$this->registerArgument('pictureClass', 'string', 'The class attribute for the picture tag');
$this->registerArgument('id', 'string', 'The id attribute');
$this->registerArgument('alt', 'string', 'The alt attribute');
$this->registerArgument('title', 'string', 'The title attribute');
$this->registerArgument(
'width',
'string',
'Width of the image. This can be a numeric value representing the fixed width of the image in '
. 'pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See '
. 'https://docs.typo3.org/m/typo3/reference-typoscript/main/en-us/Functions/Imgresource.html#width for '
. 'possible options.'
);
$this->registerArgument(
'height',
'string',
'Height of the image. This can be a numeric value representing the fixed height of the image in '
. 'pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See '
. 'https://docs.typo3.org/m/typo3/reference-typoscript/main/en-us/Functions/Imgresource.html#width for '
. 'possible options.'
);
$this->registerArgument(
'responsiveHeights',
'array',
'Array of heights, see height description and '
. '/web/typo3conf/ext/project_base/Configuration/TypoScript/Configurations/Picture/Setup.typoscript for '
. 'the available keys'
);
$this->registerArgument(
'responsiveImages',
'array',
'Array of alternative images to use per breakpoint, see '
. '/web/typo3conf/ext/project_base/Configuration/TypoScript/Configurations/Picture/Setup.typoscript for '
. 'the available keys'
);
$this->registerArgument(
'maxWidth',
'integer',
'Maximum Width of the image. (no up-scaling)'
);
$this->registerArgument(
'maxHeight',
'integer',
'Maximum Height of the image. (no up-scaling)'
);
$this->registerArgument('minWidth', 'integer', 'Minimum Width of the image.');
$this->registerArgument('minHeight', 'integer', 'Minimum Height of the image.');
$this->registerArgument(
'treatIdAsReference',
'bool',
'Treat ID as Reference',
FALSE,
FALSE
);
$this->registerArgument(
'disableWebp',
'bool',
'Disable webp converter',
FALSE,
FALSE
);
$this->registerArgument(
'loadingDirective',
'string',
'Set the image loading to lazy, eager or none to let the browser decide',
FALSE,
'lazy'
);
}
/**
* Escapes special characters with their escaped counterparts as needed using PHPs htmlentities() function.
*
* @param array $arguments
* @param Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return string
* @throws InvalidConfigurationTypeException
* @throws ExtensionConfigurationExtensionNotConfiguredException
* @throws ExtensionConfigurationPathDoesNotExistException
*/
public static function renderStatic(
array $arguments,
Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): string {
/** @var FileReference|string $image */
$image = $arguments['image'];
if (is_string($image)) {
$image = str_replace('/fileadmin', '1:', $image);
}
// Also hide images which would be shown in the TYPO3 Backend without this check
if (
$image instanceof FileReference
&& (int) $image->getReferenceProperties()['hidden'] === 1
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()
) {
return '';
}
if (!isset(self::$imageService)) {
self::$imageService = GeneralUtility::makeInstance(ImageService::class);
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
self::$responsiveSettings = $configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT
)['plugin.']['tx_project_base.']['settings.']['responsiveImages.'];
}
$width = $arguments['width'];
$maxWidth = (int) $arguments['maxWidth'];
$minWidth = (int) $arguments['minWidth'];
$height = $arguments['height'];
$responsiveHeights = $arguments['responsiveHeights'] ?? [];
$responsiveImages = $arguments['responsiveImages'] ?? [];
$maxHeight = (int) $arguments['maxHeight'];
$minHeight = (int) $arguments['minHeight'];
$class = $arguments['class'];
$pictureClass = $arguments['pictureClass'] ?? '';
$id = $arguments['id'];
$title = $arguments['title'];
$alt = $arguments['alt'];
$disableWebp = $arguments['disableWebp'] ?? '';
try {
if ($arguments['treatIdAsReference']) {
if ($image instanceof FileReference) {
/** @var FileReference $image */
$image = self::$imageService->getImage($image->getUid(), NULL, $arguments['treatIdAsReference']);
} else {
// Hidden feature: the id used for treatIdAsReference can be an int or a string (e.g. "1:/someimage.png")
$image = self::$imageService->getImage($image, NULL, $arguments['treatIdAsReference']);
}
} else {
/** @var FileReference $image */
$image = self::$imageService->getImage('', $image, $arguments['treatIdAsReference']);
}
} catch (\Exception $exception) {
return '';
}
$originalFile = $image;
if (!($originalFile instanceof File)) {
$originalFile = $image->getOriginalFile();
}
if (!$originalFile) {
return '';
}
// Explanation: https://www.mediaevent.de/xhtml/picture.html
// Also: We need to generate the other sources even if the image is small enough as we don't know if there
// are editor changes to the crop variation.
$sources = '';
$isNoSvg = !str_contains($originalFile->getProperties()['mime_type'], 'svg');
$sizes = [
'width' => $width,
'height' => $height,
'minWidth' => $minWidth,
'minHeight' => $minHeight,
'maxWidth' => $maxWidth,
'maxHeight' => $maxHeight
];
$fileExtension = $originalFile->getProperty('extension');
$convertToWebp = !$disableWebp && (
$fileExtension === 'png'
|| $fileExtension === 'jpg'
|| $fileExtension === 'jpeg'
|| $fileExtension === 'webp'
);
$imageUrl = self::getImage($image, $sizes, 'default', $convertToWebp);
// Don't add a scaled version for SVG's. This is not necessary at all
$determinedWidth = (int) $width;
$determinedHeight = (int) $height;
if ($isNoSvg && is_array(self::$responsiveSettings['breakpoints.'])) {
$sorted = [];
foreach (self::$responsiveSettings['breakpoints.'] as $sizeKey => $value) {
$sorted[$sizeKey] = (int) self::$responsiveSettings['breakpoints.'][$sizeKey]['imageSize'];
}
asort($sorted);
foreach ($sorted as $sizeKey => $breakpoint) {
$imageSizeMaxWidth = (int) self::$responsiveSettings['breakpoints.'][$sizeKey]['imageSize'];
if ((int) $width > $imageSizeMaxWidth) {
$sizes = ['width' => $imageSizeMaxWidth];
$sizeKeyWithoutDot = str_replace('.', '', $sizeKey);
if (is_array($responsiveHeights) && isset($responsiveHeights[$sizeKeyWithoutDot])) {
$responsiveHeight = $responsiveHeights[$sizeKeyWithoutDot];
if ($responsiveHeight) {
$sizes['height'] = $responsiveHeight;
}
}
$responsiveImage = $image;
if (is_array($responsiveImages) && isset($responsiveImages[$sizeKeyWithoutDot])) {
$responsiveImage = $responsiveImages[$sizeKeyWithoutDot];
}
$sourceImageUrl = self::getImage($responsiveImage, $sizes, $sizeKeyWithoutDot, $convertToWebp);
$sources .= '<source media="(max-width: ' . $imageSizeMaxWidth . 'px)" srcset="' .
$sourceImageUrl . '" />' . "\n";
}
}
if (is_file(Environment::getPublicPath() . '/' . $imageUrl)) {
[$determinedWidth, $determinedHeight] = getimagesize(
Environment::getPublicPath() . '/' . $imageUrl
);
}
}
$idAsAttribute = $id ? ' id="' . $id . '"' : '';
$widthAsAttribute = $determinedWidth ? ' width="' . $determinedWidth . '"' : '';
$heightAsAttribute = $determinedHeight ? ' height="' . $determinedHeight . '"' : '';
$class .= ($isNoSvg ? ' image-no-svg' : '');
$alternative = $alt;
if ($image instanceof FileReference) {
if (!$alt) {
$alternative = $image->hasProperty('alternative') ? $image->getAlternative() : '';
}
if (!$title) {
$title = $image->hasProperty('title') ? $image->getTitle() : '';
}
}
$titleAsAttribute = $title ? ' title="' . htmlspecialchars($title ?? '') . '"' : '';
$loadingDirective = '';
if (isset($arguments['loadingDirective']) && $arguments['loadingDirective'] !== 'none') {
$arguments['loadingDirective'] = $arguments['loadingDirective'] !== '' ?
$arguments['loadingDirective'] : 'lazy';
$loadingDirective = ' loading="' . $arguments['loadingDirective'] . '"';
}
return '<picture' . ($pictureClass ? ' class="' . $pictureClass . '"' : '') . '>' .
$sources .
'<img ' . $idAsAttribute . ' class="image-embed-item ' . $class . '"' .
$widthAsAttribute . $heightAsAttribute . $titleAsAttribute .
' alt="' . htmlspecialchars($alternative ?? '') . '" src="' . $imageUrl . '"' . $loadingDirective . ' />' .
'</picture>';
}
/**
* Generates an image based on the FileReference
*
* @param FileInterface $image
* @param array $sizes array consisting with one or more of width, height, minWidth, maxWidth, minHeight, maxHeight
* @param string $cropVariant
* @param bool $convertToWebp
* @return string
*/
protected static function getImage(
FileInterface $image,
array $sizes = [],
string $cropVariant = 'default',
bool $convertToWebp = TRUE
): string {
try {
$cropString = '';
if ($image->hasProperty('crop') && $image->getProperty('crop')) {
$cropString = $image->getProperty('crop');
}
$cropVariantCollection = CropVariantCollection::create($cropString);
$cropArea = $cropVariantCollection->getCropArea($cropVariant);#
$allowedKeys = ['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight'];
$startingProcessingInstructions = [];
foreach ($sizes as $key => $value) {
if (in_array($key, $allowedKeys, TRUE)) {
$startingProcessingInstructions[$key] = $value;
}
}
$processingInstructions = array_merge(
$startingProcessingInstructions,
['crop' => $cropArea->isEmpty() ? NULL : $cropArea->makeAbsoluteBasedOnFile($image)]
);
if ($convertToWebp) {
$processingInstructions['fileExtension'] = 'webp';
}
$processedImage = self::$imageService->applyProcessingInstructions($image, $processingInstructions);
return self::$imageService->getImageUri($processedImage) ?? '';
} catch (UnexpectedValueException $e) {
// thrown if a file has been replaced with a folder
throw new Exception($e->getMessage(), 1509741908, $e);
} catch (RuntimeException $e) {
// RuntimeException thrown if a file is outside a storage
throw new Exception($e->getMessage(), 1509741909, $e);
} catch (InvalidArgumentException $e) {
// thrown if file storage does not exist
throw new Exception($e->getMessage(), 1509741910, $e);
}
}
}
......@@ -138,7 +138,14 @@
</div>
<div class="overflow-hidden">
<img class="sg-video__image object-fit-cover h-100 w-100" src="{feedItem.thumbnail}" alt="{feedItem.title}" />
<f:if condition="{feedItem.thumbnailImageObject}">
<f:then>
<vi:picture class="sg-video__image object-fit-cover h-100 w-100" image="{feedItem.thumbnailImageObject}" alt="{feedItem.title}" />
</f:then>
<f:else>
<vi:picture class="sg-video__image object-fit-cover h-100 w-100" image="{feedItem.thumbnail}" alt="{feedItem.title}" treatIdAsReference="TRUE" />
</f:else>
</f:if>
</div>
</a>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment