Skip to content

Commit

Permalink
[FEATURE] Implement image processing with new fluid component interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
ulrichmathes committed Oct 8, 2024
1 parent 827837a commit 8e0b8ce
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 52 deletions.
104 changes: 67 additions & 37 deletions Classes/Domain/Model/ImageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@
use Sitegeist\MediaComponents\Domain\Model\CropArea;
use Sitegeist\MediaComponents\Domain\Model\SourceSet;
use Sitegeist\MediaComponents\Interfaces\ConstructibleFromImage;
use SMS\FluidComponents\Domain\Model\FalImage;
use SMS\FluidComponents\Domain\Model\FalFile;
use SMS\FluidComponents\Domain\Model\Image;
use SMS\FluidComponents\Interfaces\ConstructibleFromArray;
use SMS\FluidComponents\Interfaces\ConstructibleFromExtbaseFile;
use SMS\FluidComponents\Interfaces\ConstructibleFromFileInterface;
use SMS\FluidComponents\Interfaces\ConstructibleFromInteger;
use SMS\FluidComponents\Interfaces\ConstructibleFromString;
use SMS\FluidComponents\Interfaces\ImageWithCropVariants;
use SMS\FluidComponents\Interfaces\ImageWithDimensions;
use SMS\FluidComponents\Interfaces\ProcessableImage;
use SMS\FluidComponents\Utility\ComponentArgumentConverter;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Service\ImageService;

class ImageSource implements
ConstructibleFromArray,
Expand All @@ -32,8 +34,6 @@ class ImageSource implements
*/
protected ?Image $image = null;

protected ?ImageService $imageService = null;

protected float $scale = 1.0;

protected ?CropArea $crop = null;
Expand All @@ -49,7 +49,6 @@ class ImageSource implements
public function __construct(
protected ?Image $originalImage = null
) {
$this->imageService = GeneralUtility::makeInstance(ImageService::class);
$this->setCrop(new CropArea);
}

Expand All @@ -58,7 +57,7 @@ public static function fromArray(array $value): ImageSource
$argumentConverter = GeneralUtility::makeInstance(ComponentArgumentConverter::class);

try {
$image = $argumentConverter->convertValueToType($value['originalImage'], Image::class);
$image = $argumentConverter->convertValueToType($value['originalImage'] ?? $value, Image::class);
} catch (\SMS\FluidComponents\Exception\InvalidArgumentException) {
// TODO better error handling here:
// Image is not required, but invalid combination of parameters should
Expand All @@ -76,20 +75,23 @@ public static function fromArray(array $value): ImageSource
if (isset($value['media'])) {
$imageSource->setMedia((string) $value['media']);
}
if (isset($value['scale'])) {
if (isset($value['sizes'])) {
$imageSource->setSizes((string) $value['sizes']);
}

if (isset($value['crop']) || isset($value['srcset'])) {
$argumentConverter = GeneralUtility::makeInstance(ComponentArgumentConverter::class);

if (isset($value['crop'])) {
$imageSource->setCrop($argumentConverter->convertValueToType($value['crop'], CropArea::class));
}
if (isset($value['crop'])) {
$imageSource->setCrop(
$argumentConverter->convertValueToType($value['crop'], CropArea::class)
);
} elseif ($image instanceof ImageWithCropVariants) {
$cropVariant = (isset($value['cropVariant']))
? $image->getCropVariant($value['cropVariant'])
: $image->getDefaultCrop();
$imageSource->setCrop(new CropArea($cropVariant));
}

if (isset($value['srcset'])) {
$imageSource->setSrcset($argumentConverter->convertValueToType($value['srcset'], SourceSet::class));
}
if (isset($value['srcset'])) {
$imageSource->setSrcset($argumentConverter->convertValueToType($value['srcset'], SourceSet::class));
}

return $imageSource;
Expand Down Expand Up @@ -164,6 +166,40 @@ public function setCrop(CropArea $crop): self
return $this;
}

public function getCroppedWidth(): ?int
{
if (!($this->originalImage instanceof ImageWithDimensions)) {
return null;
}

if ($this->crop->getArea()->isEmpty()) {
$this->originalImage->getWidth();
}

if ($this->getOriginalImage() instanceof FalFile) {
return (int) round($this->crop->getArea()->makeAbsoluteBasedOnFile($this->getOriginalImage()->getFile())->getWidth());
}

return (int) round($this->crop->getArea()->getWidth() * $this->originalImage->getWidth());
}

public function getCroppedHeight(): ?int
{
if (!($this->originalImage instanceof ImageWithDimensions)) {
return null;
}

if ($this->crop->getArea()->isEmpty()) {
$this->originalImage->getHeight();
}

if ($this->getOriginalImage() instanceof FalFile) {
return (int) round($this->crop->getArea()->makeAbsoluteBasedOnFile($this->getOriginalImage()->getFile())->getHeight());
}

return (int) round($this->crop->getArea()->getHeight() * $this->originalImage->getHeight());
}

public function getImage(): Image
{
// TODO throw exception if image is not defined
Expand Down Expand Up @@ -193,12 +229,14 @@ public function getDescription(): ?string

public function getHeight(): ?int
{
return $this->getImage()->getHeight();
$image = $this->getImage();
return ($image instanceof ImageWithDimensions) ? $image->getHeight() : null;
}

public function getWidth(): ?int
{
return $this->getImage()->getWidth();
$image = $this->getImage();
return ($image instanceof ImageWithDimensions) ? $image->getWidth() : null;
}

public function getMedia(): ?string
Expand Down Expand Up @@ -244,25 +282,17 @@ public function __toString(): string

protected function processImage(): void
{
$originalImage = $this->getOriginalImage();

if ($originalImage) {
$crop = null;
if ($this->getCrop()) {
$cropArea = $this->getCrop()->getArea();
if (!$cropArea->isEmpty()) {
$crop = $cropArea->makeAbsoluteBasedOnFile($originalImage->getFile());
}
}

$processingInstructions = [
'width' => round($originalImage->getWidth() * $this->getScale()),
'height' => round($originalImage->getHeight() * $this->getScale()),
'fileExtension' => $this->getFormat(),
'crop' => $crop
];

$this->image = new FalImage($this->imageService->applyProcessingInstructions($originalImage->getFile(), $processingInstructions));
// TODO implement runtime cache
$this->image = null;

$original = $this->getOriginalImage();
if ($original instanceof ProcessableImage && $original instanceof ImageWithDimensions) {
$this->image = $original->process(
(int) round($this->getCroppedWidth() * $this->getScale()),
(int) round($this->getCroppedHeight() * $this->getScale()),
$this->getFormat(),
$this->getCrop()->getArea(),
);
}
}
}
22 changes: 15 additions & 7 deletions Classes/ViewHelpers/Image/CropVariantViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@

namespace Sitegeist\MediaComponents\ViewHelpers\Image;

use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileInterface;
use Sitegeist\MediaComponents\Domain\Model\CropArea;
use Sitegeist\MediaComponents\Domain\Model\ImageSource;
use SMS\FluidComponents\Interfaces\ImageWithCropVariants;
use SMS\FluidComponents\Interfaces\ImageWithDimensions;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class CropVariantViewHelper extends AbstractViewHelper
{
public function initializeArguments(): void
{
$this->registerArgument('image', FileInterface::class, 'FAL file');
$this->registerArgument('image', ImageSource::class, 'Image object');
$this->registerArgument('name', 'string', 'name of the crop variant that should be used', false, 'default');
}

public function render(): Area
public function render(): CropArea
{
$this->arguments['image'] ??= $this->renderChildren();
$cropVariantCollection = CropVariantCollection::create((string)$this->arguments['image']->getProperty('crop'));
return $cropVariantCollection->getCropArea($this->arguments['name']);
if (!($this->arguments['image']->getOriginalImage() instanceof ImageWithDimensions)) {
return $this->arguments['image'];
}

if ($this->arguments['image']->getOriginalImage() instanceof ImageWithCropVariants) {
return new CropArea($this->arguments['image']->getOriginalImage()->getCropVariant($this->arguments['name']));
} else {
return new CropArea;
}
}
}
4 changes: 2 additions & 2 deletions Classes/ViewHelpers/Image/Modify/ScaleViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public function render(): ImageSource
return $imageSource;
}
if ($this->arguments['height'] || $this->arguments['width']) {
$heightFactor = $this->arguments['height'] ? $this->arguments['height'] / $imageSource->getOriginalImage()->getHeight() : 1;
$widthFactor = $this->arguments['width'] ? $this->arguments['width'] / $imageSource->getOriginalImage()->getWidth() : 1;
$heightFactor = $this->arguments['height'] ? $this->arguments['height'] / $imageSource->getCroppedWidth() : 1;
$widthFactor = $this->arguments['width'] ? $this->arguments['width'] / $imageSource->getCroppedHeight() : 1;
$scaleFactor = ($this->arguments['maxDimensions'])
? min($heightFactor, $widthFactor)
: max($heightFactor, $widthFactor);
Expand Down
13 changes: 8 additions & 5 deletions Classes/ViewHelpers/Image/SrcsetViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

use Sitegeist\MediaComponents\Domain\Model\ImageSource;
use Sitegeist\MediaComponents\Domain\Model\SourceSet;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
use SMS\FluidComponents\Interfaces\ImageWithDimensions;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class SrcsetViewHelper extends AbstractViewHelper
Expand All @@ -24,6 +23,11 @@ public function render(): string
return '';
}
$this->arguments['imageSource'] ??= $this->renderChildren();

if (!($this->arguments['imageSource']->getOriginalImage() instanceof ImageWithDimensions)) {
return '';
}

return self::generateSrcsetString($this->arguments['imageSource'], $this->arguments['srcset'], $this->arguments['base']);
}

Expand All @@ -32,12 +36,11 @@ public static function generateSrcsetString(ImageSource $imageSource, SourceSet
$output = [];
$base ??= $imageSource;
$widths = $srcset->getSrcsetAndWidths($base->getWidth());
$imageService = GeneralUtility::makeInstance(ImageService::class);
$localImageSource = clone $imageSource;

foreach ($widths as $widthDescriptor => $width) {
$localImageSource->setScale($width / $imageSource->getOriginalImage()->getWidth());
$output[] = $imageService->getImageUri($localImageSource->getImage()->getFile()) . ' ' . $widthDescriptor;
$localImageSource->setScale($width / $localImageSource->getCroppedWidth());
$output[] = (string) $localImageSource . ' ' . $widthDescriptor;
}

return implode(', ', $output);
Expand Down
4 changes: 4 additions & 0 deletions Resources/Private/Components/Image/Image.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<fc:param name="title" type="string" optional="1" />

<f:comment><!-- Cropping --></f:comment>
<fc:param name="cropVariant" type="string" optional="1" />
<fc:param name="crop" type="Sitegeist\MediaComponents\Domain\Model\CropArea" optional="1" />

<f:comment><!-- Scaling --></f:comment>
Expand All @@ -27,6 +28,9 @@
<fc:param name="preload" type="boolean" optional="1" />

<fc:renderer>
<f:if condition="!{crop} && {cropVariant}">
<f:variable name="crop" value="{src -> mvh:image.cropVariant(name: cropVariant)}" />
</f:if>
<f:variable name="croppedImage" value="{src -> mvh:image.modify.format(format: format) -> mvh:image.modify.crop(crop: crop)}" />
<f:variable name="fallbackImage" value="{croppedImage -> mvh:image.modify.scale(height: height, width: width, maxDimensions: maxDimensions)}" />
<ft:img
Expand Down
2 changes: 1 addition & 1 deletion Resources/Private/Components/Picture/Picture.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<ft:picture :spaceless="1">
<f:for each="{sources}" as="source">
<mc:picture.source
src="{f:if(condition: source, then: source, else: src)}"
src="{f:if(condition: source.originalImage, then: source, else: src)}"
format="{f:if(condition: source.format, then: source.format, else: src.format)}"
crop="{f:if(condition: source.crop, then: source.crop, else: src.crop)}"
srcset="{source.srcset}"
Expand Down
4 changes: 4 additions & 0 deletions Resources/Private/Components/Picture/Source/Source.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<fc:param name="src" type="Sitegeist\MediaComponents\Domain\Model\ImageSource" />

<f:comment><!-- Cropping --></f:comment>
<fc:param name="cropVariant" type="string" optional="1" />
<fc:param name="crop" type="Sitegeist\MediaComponents\Domain\Model\CropArea" optional="1" />

<f:comment><!-- Scaling --></f:comment>
Expand All @@ -22,6 +23,9 @@
<fc:param name="preload" type="boolean" optional="1" />

<fc:renderer>
<f:if condition="!{crop} && {cropVariant}">
<f:variable name="crop" value="{src -> mvh:image.cropVariant(name: cropVariant)}" />
</f:if>
<f:variable name="croppedImage" value="{src -> mvh:image.modify.format(format: format) -> mvh:image.modify.crop(crop: crop)}" />
<f:variable name="srcset" value="{croppedImage -> mvh:image.srcset(srcset: srcset, base: src)}" />
<ft:source
Expand Down

0 comments on commit 8e0b8ce

Please sign in to comment.