Skip to content

Commit

Permalink
FEATURE: Only output existing images and allow upscaling
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelmeister committed Sep 27, 2023
1 parent 83b512e commit 66bab25
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 47 deletions.
31 changes: 3 additions & 28 deletions Classes/Domain/AbstractImageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Neos\Eel\ProtectedContextAwareInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\Utility\Arrays;
use Psr\Log\LoggerInterface;
use Sitegeist\Kaleidoscope\EelHelpers\ImageSourceHelperInterface;

Expand Down Expand Up @@ -197,39 +196,15 @@ public function withVariantPreset(string $presetIdentifier, string $presetVarian
}

/**
* Render sourceset Attribute for various media descriptors.
* Render sourceset Attribute non-scalable media.
*
* @param mixed $mediaDescriptors
* @param bool $allowUpScaling
*
* @return string
*/
public function srcset($mediaDescriptors): string
public function srcset($mediaDescriptors, bool $allowUpScaling = false): string
{
if ($this instanceof ScalableImageSourceInterface) {
$srcsetArray = [];

if (is_array($mediaDescriptors) || $mediaDescriptors instanceof \Traversable) {
$descriptors = $mediaDescriptors;
} else {
$descriptors = Arrays::trimExplode(',', (string) $mediaDescriptors);
}

foreach ($descriptors as $descriptor) {
if (preg_match('/^(?<width>[0-9]+)w$/u', $descriptor, $matches)) {
$width = (int) $matches['width'];
$scaleFactor = $width / $this->width();
$scaled = $this->scale($scaleFactor);
$srcsetArray[] = $scaled->src() . ' ' . $width . 'w';
} elseif (preg_match('/^(?<factor>[0-9\\.]+)x$/u', $descriptor, $matches)) {
$factor = (float) $matches['factor'];
$scaled = $this->scale($factor);
$srcsetArray[] = $scaled->src() . ' ' . $factor . 'x';
}
}

return implode(', ', $srcsetArray);
}

return $this->src();
}

Expand Down
105 changes: 99 additions & 6 deletions Classes/Domain/AbstractScalableImageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
use Imagine\Image\Box;
use Imagine\Image\ImagineInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\Media\Domain\Model\Adjustment\CropImageAdjustment;
use Neos\Media\Domain\Model\Adjustment\ImageAdjustmentInterface;
use Neos\Media\Domain\Model\Adjustment\ResizeImageAdjustment;
use Neos\Media\Domain\ValueObject\Configuration\Adjustment;
use Neos\Media\Domain\ValueObject\Configuration\VariantPreset;
use Neos\Utility\ObjectAccess;
use Neos\Utility\Arrays;
use Sitegeist\Kaleidoscope\EelHelpers\ScalableImageSourceHelperInterface;

abstract class AbstractScalableImageSource extends AbstractImageSource implements ScalableImageSourceInterface, ScalableImageSourceHelperInterface
Expand All @@ -34,13 +36,18 @@ abstract class AbstractScalableImageSource extends AbstractImageSource implement
*/
protected $baseHeight;

/**
* @var bool
*/
protected $allowUpScaling = false;

/**
* @param int|null $targetWidth
* @param bool $preserveAspect
*
* @return ImageSourceInterface
* @return ScalableImageSourceInterface
*/
public function withWidth(int $targetWidth = null, bool $preserveAspect = false): ImageSourceInterface
public function withWidth(int $targetWidth = null, bool $preserveAspect = false): ScalableImageSourceInterface

Check failure on line 50 in Classes/Domain/AbstractScalableImageSource.php

View workflow job for this annotation

GitHub Actions / Test (PHP 7.3, Neos 7.3)

Return type Sitegeist\Kaleidoscope\Domain\ScalableImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\AbstractScalableImageSource::withWidth() is not compatible with return type Sitegeist\Kaleidoscope\Domain\ImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\ImageSourceInterface::withWidth().
{
$newSource = clone $this;
$newSource->targetWidth = $targetWidth;
Expand All @@ -60,9 +67,9 @@ public function withWidth(int $targetWidth = null, bool $preserveAspect = false)
* @param int|null $targetHeight
* @param bool $preserveAspect
*
* @return ImageSourceInterface
* @return ScalableImageSourceInterface
*/
public function withHeight(int $targetHeight = null, bool $preserveAspect = false): ImageSourceInterface
public function withHeight(int $targetHeight = null, bool $preserveAspect = false): ScalableImageSourceInterface

Check failure on line 72 in Classes/Domain/AbstractScalableImageSource.php

View workflow job for this annotation

GitHub Actions / Test (PHP 7.3, Neos 7.3)

Return type Sitegeist\Kaleidoscope\Domain\ScalableImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\AbstractScalableImageSource::withHeight() is not compatible with return type Sitegeist\Kaleidoscope\Domain\ImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\ImageSourceInterface::withHeight().
{
$newSource = clone $this;
$newSource->targetHeight = $targetHeight;
Expand All @@ -78,14 +85,32 @@ public function withHeight(int $targetHeight = null, bool $preserveAspect = fals
return $newSource;
}

/**
* @param int $targetWidth
* @param int $targetHeight
*
* @return ScalableImageSourceInterface
*/
public function withDimensions(int $targetWidth, int $targetHeight): ScalableImageSourceInterface

Check failure on line 94 in Classes/Domain/AbstractScalableImageSource.php

View workflow job for this annotation

GitHub Actions / Test (PHP 7.3, Neos 7.3)

Return type Sitegeist\Kaleidoscope\Domain\ScalableImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\AbstractScalableImageSource::withDimensions() is not compatible with return type Sitegeist\Kaleidoscope\Domain\ImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\ImageSourceInterface::withDimensions().
{
$newSource = clone $this;
$newSource->targetWidth = $targetWidth;
$newSource->targetHeight = $targetHeight;

return $newSource;
}


/**
* @param float $factor
* @param bool $allowUpScaling
*
* @return ImageSourceInterface
* @return ScalableImageSourceInterface
*/
public function scale(float $factor): ImageSourceInterface
public function scale(float $factor, bool $allowUpScaling = false): ScalableImageSourceInterface

Check failure on line 110 in Classes/Domain/AbstractScalableImageSource.php

View workflow job for this annotation

GitHub Actions / Test (PHP 7.3, Neos 7.3)

Return type Sitegeist\Kaleidoscope\Domain\ScalableImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\AbstractScalableImageSource::scale() is not compatible with return type Sitegeist\Kaleidoscope\Domain\ImageSourceInterface of method Sitegeist\Kaleidoscope\Domain\ScalableImageSourceInterface::scale().
{
$scaledHelper = clone $this;
$scaledHelper->allowUpScaling = $allowUpScaling;

if ($this->targetWidth && $this->targetHeight) {
$scaledHelper = $scaledHelper->withDimensions((int) round($factor * $this->targetWidth), (int) round($factor * $this->targetHeight));
Expand Down Expand Up @@ -213,4 +238,72 @@ protected function createAdjustment(Adjustment $adjustmentConfiguration): ImageA

return $adjustment;
}

/**
* Render srcset Attribute for various media descriptors.
*
* If upscaling is not allowed and the width is greater than the base width,
* use the base width.
*
* @param $mediaDescriptors
* @param bool $allowUpScaling
*
* @return string
*/
public function srcset($mediaDescriptors, bool $allowUpScaling = false): string
{
$srcsetArray = [];

if (is_array($mediaDescriptors) || $mediaDescriptors instanceof \Traversable) {
$descriptors = $mediaDescriptors;
} else {
$descriptors = Arrays::trimExplode(',', (string)$mediaDescriptors);
}

$srcsetType = null;
$maxScaleFactor = min($this->baseWidth / $this->targetWidth, $this->baseHeight / $this->targetHeight);

foreach ($descriptors as $descriptor) {
$hasDescriptor = preg_match('/^(?<width>[0-9]+)w$|^(?<factor>[0-9\\.]+)x$/u', $descriptor, $matches);

if (!$hasDescriptor) {
$this->logger->warning(sprintf('Invalid media descriptor "%s". Missing type "x" or "w"', $descriptor), LogEnvironment::fromMethodName(__METHOD__));
continue;
}

if (!$srcsetType) {
$srcsetType = isset($matches['width']) ? 'width' : 'factor';
} elseif (($srcsetType === 'width' && isset($matches['factor'])) || ($srcsetType === 'factor' && isset($matches['width']))) {
$this->logger->warning(sprintf('Mixed media descriptors are not valid: [%s]', implode(', ', is_array($descriptors) ? $descriptors : iterator_to_array($descriptors))), LogEnvironment::fromMethodName(__METHOD__));
break;
}

if ($srcsetType === 'width') {
$width = (int)$matches['width'];
$scaleFactor = $width / $this->width();
if (!$allowUpScaling && ($width / $this->baseWidth > 1)) {
$srcsetArray[] = $this->src() . ' ' . $this->baseWidth . 'w';
} else {
$scaled = $this->scale($scaleFactor, $allowUpScaling);
$srcsetArray[] = $scaled->src() . ' ' . $width . 'w';
}
} elseif ($srcsetType === 'factor') {
$factor = (float)$matches['factor'];
if (
!$allowUpScaling && (
($this->targetHeight && ($maxScaleFactor < $factor)) ||
($this->targetWidth && ($maxScaleFactor < $factor))
)
) {
$scaled = $this->scale($maxScaleFactor, $allowUpScaling);
$srcsetArray[] = $scaled->src() . ' ' . $maxScaleFactor . 'x';
} else {
$scaled = $this->scale($factor, $allowUpScaling);
$srcsetArray[] = $scaled->src() . ' ' . $factor . 'x';
}
}
}

return implode(', ', array_unique($srcsetArray));
}
}
4 changes: 2 additions & 2 deletions Classes/Domain/AssetImageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public function src(): string

$async = $this->request ? $this->async : false;
$allowCropping = true;
$allowUpScaling = false;
$allowUpScaling = $this->allowUpScaling;
$thumbnailConfiguration = new ThumbnailConfiguration(
$width,
$width,
Expand Down Expand Up @@ -154,7 +154,7 @@ public function dataSrc(): string

$async = false;
$allowCropping = true;
$allowUpScaling = false;
$allowUpScaling = $this->allowUpScaling;
$thumbnailConfiguration = new ThumbnailConfiguration(
$width,
$width,
Expand Down
2 changes: 1 addition & 1 deletion Classes/Domain/ScalableImageSourceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

interface ScalableImageSourceInterface extends ImageSourceInterface
{
public function scale(float $factor): ImageSourceInterface;
public function scale(float $factor, bool $allowUpScaling = false): ImageSourceInterface;
}
10 changes: 7 additions & 3 deletions Resources/Private/Fusion/Prototypes/Image.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,18 @@ prototype(Sitegeist.Kaleidoscope:Image) < prototype(Neos.Fusion:Component) {
format = null
attributes = Neos.Fusion:DataStructure
renderDimensionAttributes = true
allowSrcsetUpScaling = false
preserveAspect = true

renderer = Neos.Fusion:Component {
@if.hasImageSource = ${props.imageSource && Type.instance(props.imageSource, '\\Sitegeist\\Kaleidoscope\\Domain\\ImageSourceInterface')}
isScalableSource = ${props.imageSource && Type.instance(props.imageSource, '\\Sitegeist\\Kaleidoscope\\Domain\\ScalableImageSourceInterface')}

# apply format, width and height to the imageSource
imageSource = ${props.imageSource}
[email protected] = ${props.width ? value.withWidth(props.width) : value}
[email protected] = ${props.height ? value.withHeight(props.height) : value}
[email protected] = ${(props.width && props.height) ? value.withDimensions(props.width, props.height) : value}
[email protected] = ${(props.width && !props.height) ? value.withWidth(props.width, props.preserveAspect) : value}
[email protected] = ${(props.height && !props.width) ? value.withHeight(props.height, props.preserveAspect) : value}
[email protected] = ${props.format ? value.withFormat(props.format) : value}

srcset = ${props.srcset}
Expand All @@ -145,11 +148,12 @@ prototype(Sitegeist.Kaleidoscope:Image) < prototype(Neos.Fusion:Component) {
class = ${props.class}
attributes = ${props.attributes}
renderDimensionAttributes = ${props.renderDimensionAttributes}
allowSrcsetUpScaling = ${props.allowSrcsetUpScaling}

renderer = afx`
<img
src={props.imageSource.src()}
srcset={props.imageSource.srcset(props.srcset)}
srcset={props.imageSource.srcset(props.srcset, props.allowSrcsetUpScaling)}
[email protected]={props.isScalableSource && props.srcset}
sizes={props.sizes}
[email protected]={props.isScalableSource && props.sizes}
Expand Down
14 changes: 11 additions & 3 deletions Resources/Private/Fusion/Prototypes/Picture.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ prototype(Sitegeist.Kaleidoscope:Picture) < prototype(Neos.Fusion:Component) {
imgAttributes = Neos.Fusion:DataStructure
content = ''
renderDimensionAttributes = true
allowSrcsetUpScaling = false
preserveAspect = true

#
# put the values that shall be applied to sources automatically to the context
Expand All @@ -83,9 +85,10 @@ prototype(Sitegeist.Kaleidoscope:Picture) < prototype(Neos.Fusion:Component) {

# apply format, width and height to the imageSource
imageSource = ${props.imageSource}
[email protected] = ${props.width ? value.setWidth(props.width) : value}
[email protected] = ${props.height ? value.setHeight(props.height) : value}
[email protected] = ${props.format ? value.setFormat(props.format) : value}
[email protected] = ${(props.width && props.height) ? value.withDimensions(props.width, props.height) : value}
[email protected] = ${(props.width && !props.height) ? value.withWidth(props.width, props.preserveAspect) : value}
[email protected] = ${(props.height && !props.width) ? value.withHeight(props.height, props.preserveAspect) : value}
[email protected] = ${props.format ? value.withFormat(props.format) : value}

srcset = ${props.srcset}
sizes = ${props.sizes}
Expand All @@ -99,6 +102,8 @@ prototype(Sitegeist.Kaleidoscope:Picture) < prototype(Neos.Fusion:Component) {
imgAttributes = ${props.imgAttributes}
content = ${props.content}
renderDimensionAttributes = ${props.renderDimensionAttributes}
allowSrcsetUpscaling = ${props.allowSrcsetUpscaling}
preserveAspect = ${props.preserveAspect}

renderer = afx`
<picture class={props.class} {...props.attributes}>
Expand All @@ -114,6 +119,8 @@ prototype(Sitegeist.Kaleidoscope:Picture) < prototype(Neos.Fusion:Component) {
srcset={source.srcset ? source.srcset : props.srcset}
sizes={source.sizes ? source.sizes : props.sizes}
renderDimensionAttributes={props.renderDimensionAttributes}
allowSrcsetUpScaling={props.allowSrcsetUpScaling}
preserveAspect={props.preserveAspect}
/>
</Neos.Fusion:Collection>
<Neos.Fusion:Collection
Expand All @@ -135,6 +142,7 @@ prototype(Sitegeist.Kaleidoscope:Picture) < prototype(Neos.Fusion:Component) {
title={props.title}
attributes={props.imgAttributes}
renderDimensionAttributes={props.renderDimensionAttributes}
preserveAspect={props.preserveAspect}
/>
</picture>
`
Expand Down
12 changes: 8 additions & 4 deletions Resources/Private/Fusion/Prototypes/Source.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ prototype(Sitegeist.Kaleidoscope:Source) < prototype(Neos.Fusion:Component) {
type = null
media = null
renderDimensionAttributes = true
allowSrcsetUpScaling = false
preserveAspect = true

renderer = Neos.Fusion:Component {

Expand All @@ -29,19 +31,21 @@ prototype(Sitegeist.Kaleidoscope:Source) < prototype(Neos.Fusion:Component) {
isScalableSource = ${imageSource && Type.instance(imageSource, '\\Sitegeist\\Kaleidoscope\\Domain\\ScalableImageSourceInterface')}

imageSource = ${imageSource}
[email protected] = ${width ? value.withWidth(width) : value}
[email protected] = ${height ? value.withHeight(height) : value}
[email protected] = ${format ? value.withFormat(format) : value}
[email protected] = ${(props.width && props.height) ? value.withDimensions(props.width, props.height) : value}
[email protected] = ${(props.width && !props.height) ? value.withWidth(props.width, props.preserveAspect) : value}
[email protected] = ${(props.height && !props.width) ? value.withHeight(props.height, props.preserveAspect) : value}
[email protected] = ${props.format ? value.withFormat(props.format) : value}

type = ${format ? 'image/' + format : props.type}
srcset = ${srcset}
sizes = ${sizes}
media = ${props.media}
renderDimensionAttributes = ${props.renderDimensionAttributes}
allowSrcsetUpScaling = ${props.allowSrcsetUpScaling}

renderer = afx`
<source @if.has={props.imageSource}
srcset={props.imageSource.srcset(props.srcset)}
srcset={props.imageSource.srcset(props.srcset, props.allowSrcsetUpScaling)}
sizes={props.sizes}
[email protected]={props.sizes && props.isScalableSource}
[email protected]={Type.isArray(value) ? Array.join(value, ', ') : value}
Expand Down

0 comments on commit 66bab25

Please sign in to comment.