Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Neos.Media/Classes/Command/MediaCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@
use Neos\Flow\Cli\CommandController;
use Neos\Flow\Cli\Exception\StopCommandException;
use Neos\Flow\Persistence\Exception\IllegalObjectTypeException;
use Neos\Flow\Persistence\Exception\InvalidQueryException;
use Neos\Flow\Persistence\Exception\UnknownObjectException;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Flow\Reflection\ReflectionService;
use Neos\Flow\ResourceManagement\PersistentResource;
use Neos\Media\Domain\Model\Asset;
use Neos\Media\Domain\Model\AssetCollection;
use Neos\Media\Domain\Model\AssetInterface;
use Neos\Media\Domain\Model\AssetSource\AssetSourceAwareInterface;
use Neos\Media\Domain\Model\Image;
use Neos\Media\Domain\Model\Tag;
use Neos\Media\Domain\Model\VariantSupportInterface;
use Neos\Media\Domain\Repository\AssetRepository;
use Neos\Media\Domain\Repository\ImageRepository;
use Neos\Media\Domain\Repository\ImageVariantRepository;
use Neos\Media\Domain\Repository\ThumbnailRepository;
use Neos\Media\Domain\Service\AssetService;
Expand All @@ -40,6 +44,7 @@
use Neos\Media\Domain\Strategy\AssetModelMappingStrategyInterface;
use Neos\Media\Exception\AssetServiceException;
use Neos\Media\Exception\AssetVariantGeneratorException;
use Neos\Media\Exception\ImageFileException;
use Neos\Media\Exception\ThumbnailServiceException;
use Neos\Utility\Arrays;
use Neos\Utility\Files;
Expand Down Expand Up @@ -118,6 +123,9 @@ class MediaCommandController extends CommandController
*/
protected $assetVariantGenerator;

#[Flow\Inject]
protected ImageRepository $imageRepository;

/**
* Import resources to asset management
*
Expand Down Expand Up @@ -593,6 +601,54 @@ public function renderVariantsCommand(?int $limit = null, bool $quiet = false, b
!$quiet && $this->outputLine($resultMessage ?? sprintf('Generated %u variants', $generatedVariants));
}

/**
* Calculate missing dimensions for SVG assets
*
* @param bool $force update all svg asset dimensions
* @throws InvalidQueryException
* @throws ImageFileException
* @throws UnknownObjectException
*/
public function refreshSvgDimensionsCommand(bool $force = false): void
{
$this->outputLine('Looking for SVG Assets without dimensions');

$queryResult = $this->imageRepository->findAll();
$totalCount = $queryResult->count();
$this->output->progressStart($totalCount);
$updatedCount = 0;

/** @var Image $image */
foreach ($queryResult as $image) {
$this->output->progressAdvance(1);

if (!$this->isSvgImage($image)) {
continue;
}

if ($force || $this->hasMissingDimensions($image)) {
$updatedCount++;
$image->refresh();
// the image repository tries magic we need to circumvent
$this->persistenceManager->update($image);
}
}

$this->output->progressFinish();
$this->outputLine();
$this->outputLine('Added dimensions to %s SVG Assets', [$updatedCount]);
}

private function isSvgImage(Image $image): bool
{
return $image->getResource()->getMediaType() === 'image/svg+xml';
}

private function hasMissingDimensions(Image $image): bool
{
return $image->getWidth() === 0 || $image->getHeight() === 0;
}

/**
* Used as a callback when iterating large results sets
*/
Expand Down
14 changes: 12 additions & 2 deletions Neos.Media/Classes/Domain/Model/Adjustment/CropImageAdjustment.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* source code.
*/

use Contao\ImagineSvg\Image as ContaoSvgImage;
use Contao\ImagineSvg\SvgBox;
use Doctrine\ORM\Mapping as ORM;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface as ImagineImageInterface;
Expand Down Expand Up @@ -255,14 +257,22 @@ public function applyToImage(ImagineImageInterface $image): ImagineImageInterfac
[$newX, $newY, $newWidth, $newHeight] = self::calculateDimensionsByAspectRatio($originalWidth, $originalHeight, $desiredAspectRatio);

$point = new Point($newX, $newY);
$box = new Box($newWidth, $newHeight);
$box = $this->createBox($image, $newWidth, $newHeight);
} else {
$point = new Point($this->x, $this->y);
$box = new Box($this->width, $this->height);
$box = $this->createBox($image, $this->width, $this->height);
}
return $image->crop($point, $box);
}

private function createBox(ImagineImageInterface $image, float $width, float $height): Box|SvgBox
{
if ($image instanceof ContaoSvgImage) {
return new SvgBox((int)round($width), (int)round($height));
}
return new Box($width, $height);
}

/**
* Refits the crop proportions to be the maximum size within the image boundaries.
*
Expand Down
104 changes: 92 additions & 12 deletions Neos.Media/Classes/Domain/Service/ImageService.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* source code.
*/

use Contao\ImagineSvg\Imagine as SvgImagine;
use Imagine\Image\ImageInterface;
use Imagine\Image\ImagineInterface;
use Imagine\Image\Palette\CMYK;
Expand All @@ -21,7 +22,9 @@
use Neos\Flow\ResourceManagement\ResourceManager;
use Neos\Flow\Utility\Algorithms;
use Neos\Flow\Utility\Environment;
use Neos\Media\Domain\Model\Adjustment\CropImageAdjustment;
use Neos\Media\Domain\Model\Adjustment\QualityImageAdjustment;
use Neos\Media\Domain\Model\Adjustment\ResizeImageAdjustment;
use Neos\Media\Domain\Repository\AssetRepository;
use Neos\Media\Imagine\Box;
use Neos\Flow\Annotations as Flow;
Expand Down Expand Up @@ -103,17 +106,8 @@ public function processImage(PersistentResource $originalResource, array $adjust
$additionalOptions = [];
$adjustmentsApplied = false;

// TODO: Special handling for SVG should be refactored at a later point.
if ($originalResource->getMediaType() === 'image/svg+xml') {
$originalResourceStream = $originalResource->getStream();
$resource = $this->resourceManager->importResource($originalResourceStream, $originalResource->getCollectionName());
fclose($originalResourceStream);
$resource->setFilename($originalResource->getFilename());
return [
'width' => null,
'height' => null,
'resource' => $resource
];
return $this->processSvgImage($originalResource, $adjustments);
}

$resourceUri = $originalResource->createTemporaryLocalCopy();
Expand Down Expand Up @@ -214,6 +208,71 @@ public function processImage(PersistentResource $originalResource, array $adjust
return $result;
}

/**
* Process SVG images with adjustments
*
* @param PersistentResource $originalResource
* @param array<ImageAdjustmentInterface> $adjustments
* @return array{width: int|null, height: int|null, resource: PersistentResource}
* @throws Exception
*/
protected function processSvgImage(PersistentResource $originalResource, array $adjustments): array
{
$originalResourceStream = $originalResource->getStream();

$nonAdjustedResult = [
'width' => null,
'height' => null,
'resource' => $originalResource
];

if (is_bool($originalResourceStream)) {
return $nonAdjustedResult;
}

try {
$svgImage = (new SvgImagine())->read($originalResourceStream);
} catch (\Exception) {
return $nonAdjustedResult;
}

$svgImage = $this->applySvgAdjustments($svgImage, $adjustments);
$size = $svgImage->getSize();

$transformedImageTemporaryPathAndFilename = $this->environment->getPathToTemporaryDirectory()
. 'ProcessedImage-' . Algorithms::generateRandomString(13) . '.svg';

$svgImage->save($transformedImageTemporaryPathAndFilename);
$resource = $this->resourceManager->importResource(
$transformedImageTemporaryPathAndFilename,
$originalResource->getCollectionName()
);
$resource->setFilename($originalResource->getFilename());
unlink($transformedImageTemporaryPathAndFilename);

return [
'width' => $size->getWidth(),
'height' => $size->getHeight(),
'resource' => $resource,
];
}

/**
* Apply supported adjustments to SVG image
*
* @param ImageInterface $svgImage
* @param array<ImageAdjustmentInterface> $adjustments
* @return ImageInterface
*/
protected function applySvgAdjustments(ImageInterface $svgImage, array $adjustments): ImageInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you inline this. The foreach in an external function has no benefit bur makes reading harder.

{
foreach ($adjustments as $adjustment) {
$svgImage = $adjustment->applyToImage($svgImage);
}

return $svgImage;
}

/**
* @param array $additionalOptions
* @return array
Expand Down Expand Up @@ -260,9 +319,8 @@ public function getImageSize(PersistentResource $resource)
return $imageSize;
}

// TODO: Special handling for SVG should be refactored at a later point.
if ($resource->getMediaType() === 'image/svg+xml') {
$imageSize = ['width' => null, 'height' => null];
return $this->getSvgImageSize($resource);
} else {
try {
$imagineImage = $this->imagineService->read($resource->getStream());
Expand Down Expand Up @@ -303,6 +361,28 @@ protected function applyAdjustments(ImageInterface $image, array $adjustments, &
return $image;
}

/**
* Get the size of an SVG image
*
* @param PersistentResource $resource
* @return array{width: int|null, height: int|null}
*/
protected function getSvgImageSize(PersistentResource $resource): array
{
try {
$resourceStream = $resource->getStream();
if (is_bool($resourceStream)) {
throw new \Exception('the stream of the given resource is not available');
}

$svgImage = (new SvgImagine())->read($resourceStream);
$size = $svgImage->getSize();
return ['width' => $size->getWidth(), 'height' => $size->getHeight()];
} catch (\Exception) {
return ['width' => null, 'height' => null];
}
}

/**
* Detects whether the given GIF image data contains more than one frame
*
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"enshrined/svg-sanitize": "^0.22.0",
"neos/imagine": "^3.1.0",
"imagine/imagine": "*",
"contao/imagine-svg": "^1.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be added to the composer json of the neos-development-collection

"neos/party": "~7.0.3",
"neos/fusion-form": "^2.1 || ^3.0",
"neos/form": "*",
Expand Down