Skip to content

Commit

Permalink
TASK: Calculate a preliminary crop to the target aspect for images wi…
Browse files Browse the repository at this point in the history
…th focalPoint

The preliminary crop ensures:
1. The target image is as large as possible inside the original image dimensions
2. The focal point is as central as possible inside the generated image
  • Loading branch information
mficzel committed Jul 26, 2024
1 parent b4eaea5 commit bdb28e1
Show file tree
Hide file tree
Showing 9 changed files with 453 additions and 33 deletions.
94 changes: 94 additions & 0 deletions Neos.Media/Classes/Domain/Model/Adjustment/MarkPointAdjustment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);

namespace Neos\Media\Domain\Model\Adjustment;

/*
* This file is part of the Neos.Media package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Imagine\Image\ImageInterface as ImagineImageInterface;
use Imagine\Image\Point;
use Neos\Flow\Annotations as Flow;
use Imagine\Image\Palette;

/**
* An adjustment to draw on circle on an image
* this is solely for debugging of focal points
* @todo remove before merging
*
* @deprecated
* @Flow\Entity
*/
class MarkPointAdjustment extends AbstractImageAdjustment
{
protected $position = 99;

protected int $x;

protected int $y;

protected int $radius;

protected int $thickness = 1;

protected string $color = '#000';


public function setX(int $x): void
{
$this->x = $x;
}

public function setY(int $y): void
{
$this->y = $y;
}

public function setRadius(int $radius): void
{
$this->radius = $radius;
}

public function setThickness(int $thickness): void
{
$this->thickness = $thickness;
}

public function setColor(string $color): void
{
$this->color = $color;
}


public function applyToImage(ImagineImageInterface $image)
{
$palette = new Palette\RGB();
$color = $palette->color($this->color);
$image->draw()
->circle(
new Point($this->x, $this->y),
$this->radius,
$color,
false,
$this->thickness
)
;

return $image;
}

public function canBeApplied(ImagineImageInterface $image)
{
if (is_null($this->x) || is_null($this->y) || is_null($this->radius)) {
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,22 @@
*/

use Imagine\Image\BoxInterface;
use Imagine\Image\Point;
use Imagine\Image\PointInterface;
use Neos\Media\Domain\Model\Dto\PreliminaryCropSpecification;
use Neos\Media\Domain\Model\ImageInterface;
use Neos\Media\Domain\ValueObject\Configuration\AspectRatio;
use Neos\Media\Imagine\Box;

class ImageDimensionCalculationHelperThingy
/**
* Container for static methods to calculate the target dimensions for resizing images
*
* @see: ResizeImageAdjustment, ImageThumbnailGenerator(to calculte a preliminary crop for images with focal point),
* ThumbnailService(to calculate dimensions and focal points for async thumbnails)
*
* @internal
*/
class ResizeDimensionCalculator
{
/**
* @param BoxInterface $originalDimensions
Expand Down Expand Up @@ -173,7 +185,7 @@ protected static function calculateScalingToHeight(BoxInterface $originalDimensi
* @param BoxInterface $requestedDimensions
* @return BoxInterface
*/
public static function calculateFinalDimensions(BoxInterface $imageSize, BoxInterface $requestedDimensions, string $ratioMode = ImageInterface::RATIOMODE_INSET): BoxInterface
public static function calculateOutboundScalingDimensions(BoxInterface $imageSize, BoxInterface $requestedDimensions, string $ratioMode = ImageInterface::RATIOMODE_INSET): BoxInterface
{
if ($ratioMode === ImageInterface::RATIOMODE_OUTBOUND) {
$ratios = [
Expand All @@ -185,4 +197,56 @@ public static function calculateFinalDimensions(BoxInterface $imageSize, BoxInte
}
return $requestedDimensions;
}

/**
* Calculate the informations for a preliminary crop to ensure that the given focal point stays inside the final image
* with the requested dimensions
*
* - The cropDimensions have the aspect of requested dimensions and have the maximal possible dimensions
* - The cropOffset will position the crop with the focal point as close to the center as possible
* - The returned focal point is the position of the focal point after the crop inside the requested dimensions
*/
public static function calculatePreliminaryCropSpecification(
BoxInterface $originalDimensions,
PointInterface $originalFocalPoint,
BoxInterface $targetDimensions,
): PreliminaryCropSpecification {
$originalAspect = new AspectRatio($originalDimensions->getWidth(), $originalDimensions->getHeight());
$targetAspect = new AspectRatio($targetDimensions->getWidth(), $targetDimensions->getHeight());

if ($originalAspect->getRatio() >= $targetAspect->getRatio()) {
// target-aspect is wider as original-aspect or same: use full height, width is cropped
$factor = $originalDimensions->getHeight() / $targetDimensions->getHeight();
$cropDimensions = new \Imagine\Image\Box((int)($targetDimensions->getWidth() * $factor), $originalDimensions->getHeight());
$cropOffsetX = $originalFocalPoint->getX() - (int)($cropDimensions->getWidth() / 2);
$cropOffsetXMax = $originalDimensions->getWidth() - $cropDimensions->getWidth();
if ($cropOffsetX < 0) {
$cropOffsetX = 0;
} elseif ($cropOffsetX > $cropOffsetXMax) {
$cropOffsetX = $cropOffsetXMax;
}
$cropOffset = new Point($cropOffsetX, 0);
} else {
// target-aspect is higher than original-aspect: use full width, height is cropped
$factor = $originalDimensions->getWidth() / $targetDimensions->getWidth();
$cropDimensions = new Box($originalDimensions->getWidth(), (int)($targetDimensions->getHeight() * $factor));
$cropOffsetY = $originalFocalPoint->getY() - (int)($cropDimensions->getHeight() / 2);
$cropOffsetYMax = $originalDimensions->getHeight() - $cropDimensions->getHeight();
if ($cropOffsetY < 0) {
$cropOffsetY = 0;
} elseif ($cropOffsetY > $cropOffsetYMax) {
$cropOffsetY = $cropOffsetYMax;
}
$cropOffset = new Point(0, $cropOffsetY);
}

return new PreliminaryCropSpecification(
$cropOffset,
$cropDimensions,
new Point(
(int)round(($originalFocalPoint->getX() - $cropOffset->getX()) / $factor),
(int)round(($originalFocalPoint->getY() - $cropOffset->getY()) / $factor)
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public function setAllowUpScaling(bool $allowUpScaling): void
*/
public function canBeApplied(ImagineImageInterface $image)
{
$expectedDimensions = ImageDimensionCalculationHelperThingy::calculateRequestedDimensions(
$expectedDimensions = ResizeDimensionCalculator::calculateRequestedDimensions(
$image->getSize(),
$this->width,
$this->height,
Expand Down Expand Up @@ -319,11 +319,11 @@ public function applyToImage(ImagineImageInterface $image)
*
* @param BoxInterface $originalDimensions Dimensions of the unadjusted image
* @return BoxInterface
* @deprecated use ImageDimensionCalculationHelperThingy::calculateRequestedDimensions instead
* @deprecated use ResizeDimensionCalculator::calculateRequestedDimensions instead
*/
protected function calculateDimensions(BoxInterface $originalDimensions): BoxInterface
{
return ImageDimensionCalculationHelperThingy::calculateRequestedDimensions(
return ResizeDimensionCalculator::calculateRequestedDimensions(
$originalDimensions,
$this->width,
$this->height,
Expand Down Expand Up @@ -353,7 +353,7 @@ protected function resize(ImagineImageInterface $image, string $mode = ImageInte

$originalDimensions = $image->getSize();

$requestedDimensions = ImageDimensionCalculationHelperThingy::calculateRequestedDimensions(
$requestedDimensions = ResizeDimensionCalculator::calculateRequestedDimensions(
$originalDimensions,
$this->width,
$this->height,
Expand All @@ -363,7 +363,7 @@ protected function resize(ImagineImageInterface $image, string $mode = ImageInte
$this->ratioMode ?? ImageInterface::RATIOMODE_INSET
);

$finalDimensions = ImageDimensionCalculationHelperThingy::calculateFinalDimensions(
$finalDimensions = ResizeDimensionCalculator::calculateOutboundScalingDimensions(
$originalDimensions,
$requestedDimensions,
$this->ratioMode
Expand All @@ -389,11 +389,11 @@ protected function resize(ImagineImageInterface $image, string $mode = ImageInte
* @param BoxInterface $imageSize
* @param BoxInterface $requestedDimensions
* @return BoxInterface
* @deprecated use ImageDimensionCalculationHelperThingy::calculateFinalDimensions instead
* @deprecated use ResizeDimensionCalculator::calculateOutboundScalingDimensions instead
*/
protected function calculateOutboundScalingDimensions(BoxInterface $imageSize, BoxInterface $requestedDimensions): BoxInterface
{
return ImageDimensionCalculationHelperThingy::calculateFinalDimensions(
return ResizeDimensionCalculator::calculateOutboundScalingDimensions(
$imageSize,
$requestedDimensions,
$this->ratioMode
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);

namespace Neos\Media\Domain\Model\Dto;

/*
* This file is part of the Neos.Media package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Imagine\Image\BoxInterface;
use Imagine\Image\PointInterface;
use Neos\Flow\Annotations as Flow;

/**
* A DTO for storing the information for a preliminary in case a focal point is respected during thumbnail generation
* plus the position of the focal point in the image after cropping and resizing
*
* @internal
*/
#[Flow\Proxy(false)]
final readonly class PreliminaryCropSpecification
{
public function __construct(
public PointInterface $cropOffset,
public BoxInterface $cropDimensions,
public PointInterface $focalPoint,
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* source code.
*/

use Imagine\Image\PointInterface;

/**
* Interface for assets which provide methods for focal points
*/
Expand All @@ -25,4 +27,8 @@ public function setFocalPointX(?int $x): void;
public function getFocalPointY(): ?int;

public function setFocalPointY(?int $y): void;

public function hasFocalPoint(): bool;

public function getFocalPoint(): ?PointInterface;
}
18 changes: 18 additions & 0 deletions Neos.Media/Classes/Domain/Model/FocalPointTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/

use Doctrine\ORM\Mapping as ORM;
use Imagine\Image\Point;
use Imagine\Image\PointInterface;

/**
* Trait for assets which provide methods for focal points
Expand Down Expand Up @@ -50,4 +52,20 @@ public function setFocalPointY(?int $y): void
{
$this->focalPointY = $y;
}

public function hasFocalPoint(): bool
{
if ($this->focalPointX !== null && $this->focalPointY !== null) {
return true;
}
return false;
}

public function getFocalPoint(): ?PointInterface
{
if ($this->hasFocalPoint()) {
return new Point($this->focalPointX, $this->focalPointY);
}
return null;
}
}
Loading

0 comments on commit bdb28e1

Please sign in to comment.