Skip to content

Commit

Permalink
Add CropResizer
Browse files Browse the repository at this point in the history
  • Loading branch information
core23 committed Mar 19, 2021
1 parent 54de03d commit 9353e2f
Show file tree
Hide file tree
Showing 7 changed files with 425 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/reference/advanced_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ Full configuration options:
image:
service: sonata.media.provider.image
resizer: sonata.media.resizer.simple # sonata.media.resizer.square
resizer: sonata.media.resizer.simple # sonata.media.resizer.square, sonata.media.resizer.crop
filesystem: sonata.media.filesystem.local
cdn: sonata.media.cdn.server
generator: sonata.media.generator.default
Expand Down
5 changes: 4 additions & 1 deletion docs/reference/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ SonataMediaBundle Configuration
a transversal ``admin`` format to be used by the ``mediaadmin`` class.

Also, you can determine the resizer to use; the default value is
``sonata.media.resizer.simple`` but you can change it to ``sonata.media.resizer.square``
``sonata.media.resizer.simple`` but you can change it to ``sonata.media.resizer.square`` or ``sonata.media.resizer.crop``

.. code-block:: yaml
Expand All @@ -128,6 +128,9 @@ Also, you can determine the resizer to use; the default value is
only the width. But if you specify the height the resizer crop the image in
the lower size.

The crop resizer crops the image to the exact width and height. This is done by
resizing the image first and cropping the unwanted parts at the end.

Doctrine ORM Configuration
--------------------------

Expand Down
10 changes: 10 additions & 0 deletions src/DependencyInjection/SonataMediaExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,16 @@ private function configureAdapters(ContainerBuilder $container, array $config):

private function configureResizers(ContainerBuilder $container, array $config): void
{
if ($container->hasParameter('sonata.media.resizer.crop.class')) {
$class = $container->getParameter('sonata.media.resizer.crop.class');
$definition = new Definition($class, [
new Reference('sonata.media.adapter.image.default'),
new Reference('sonata.media.metadata.proxy'),
]);
$definition->addTag('sonata.media.resizer');
$container->setDefinition('sonata.media.resizer.crop', $definition);
}

if ($container->hasParameter('sonata.media.resizer.simple.class')) {
$class = $container->getParameter('sonata.media.resizer.simple.class');
$definition = new Definition($class, [
Expand Down
168 changes: 168 additions & 0 deletions src/Resizer/CropResizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\MediaBundle\Resizer;

use Gaufrette\File;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use Imagine\Image\ImagineInterface;
use Imagine\Image\Point;
use Sonata\MediaBundle\Metadata\MetadataBuilderInterface;
use Sonata\MediaBundle\Model\MediaInterface;

/**
* @author Christian Gripp <[email protected]>
*/
final class CropResizer implements ResizerInterface
{
/**
* @var ImagineInterface
*/
private $adapter;

/**
* @var MetadataBuilderInterface
*/
private $metadata;

public function __construct(ImagineInterface $adapter, MetadataBuilderInterface $metadata)
{
$this->adapter = $adapter;
$this->metadata = $metadata;
}

public function resize(MediaInterface $media, File $in, File $out, $format, array $settings): void
{
if (!isset($settings['width'])) {
throw new \InvalidArgumentException(sprintf(
'The "width" parameter is missing in context "%s" for provider "%s".',
$media->getContext(),
$media->getProviderName()
));
}

if (!isset($settings['height'])) {
throw new \InvalidArgumentException(sprintf(
'The "height" parameter is missing in context "%s" for provider "%s".',
$media->getContext(),
$media->getProviderName()
));
}

$image = $this->adapter->load($in->getContent());

$sourceSize = $media->getBox();
$targetSize = $this->createTargetBox($settings);

if ($this->shouldModify($sourceSize, $targetSize)) {
$image = $this->cropImage($image, $sourceSize, $targetSize);
}

// Always change format and quality
$content = $image->get($format, [
'quality' => $settings['quality'],
]);

$out->setContent($content, $this->metadata->get($media, $out->getName()));
}

public function getBox(MediaInterface $media, array $settings): Box
{
$sourceSize = $media->getBox();
$targetSize = $this->createTargetBox($settings);

return new Box(
min($sourceSize->getWidth(), $targetSize->getWidth()),
min($sourceSize->getHeight(), $targetSize->getHeight())
);
}

/**
* @param array<string, int> $settings
*
* @phpstan-param array{width: int, height: int} $settings
*/
private function createTargetBox(array $settings): Box
{
return new Box($settings['width'], $settings['height']);
}

private function shouldModify(Box $sourceSize, Box $targetSize): bool
{
return !($sourceSize->getWidth() <= $targetSize->getWidth() && $sourceSize->getHeight() <= $targetSize->getHeight());
}

private function shouldResize(Box $sourceSize, Box $targetSize): bool
{
if ($sourceSize->getWidth() <= $targetSize->getWidth()) {
return false;
}

return $sourceSize->getHeight() > $targetSize->getHeight();
}

private function shouldCrop(Box $sourceSize, Box $targetSize): bool
{
return $sourceSize->getWidth() > $targetSize->getWidth() || $sourceSize->getHeight() > $targetSize->getHeight();
}

private function cropImage(ImageInterface $image, Box $sourceSize, Box $targetSize): ImageInterface
{
if ($this->shouldResize($sourceSize, $targetSize)) {
$scaleSize = $this->createBox($sourceSize, $targetSize, false);

$image = $image->thumbnail($scaleSize, 'outbound');

$sourceSize = $scaleSize;
}

if ($this->shouldCrop($sourceSize, $targetSize)) {
$cropSize = new Box(
min($sourceSize->getWidth(), $targetSize->getWidth()),
min($sourceSize->getHeight(), $targetSize->getHeight())
);

$point = new Point(
(int) (($sourceSize->getWidth() - $cropSize->getWidth()) / 2),
(int) (($sourceSize->getHeight() - $cropSize->getHeight()) / 2)
);

$image = $image->crop($point, $cropSize);
}

return $image;
}

private function createBox(Box $sourceSize, Box $targetSize, bool $smallest = true): Box
{
$widthRatio = (float) ($targetSize->getWidth() / $sourceSize->getWidth());
$heightRatio = (float) ($targetSize->getHeight() / $sourceSize->getHeight());

if (0.0 !== $widthRatio - $heightRatio) {
return $sourceSize->scale(
$smallest ? min($widthRatio, $heightRatio) : max($widthRatio, $heightRatio)
);
}

if ($targetSize->getHeight() >= $sourceSize->getHeight()) {
return $sourceSize;
}

if ($targetSize->getWidth() >= $sourceSize->getWidth()) {
return $sourceSize;
}

return $targetSize;
}
}
5 changes: 4 additions & 1 deletion src/Resizer/ResizerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
interface ResizerInterface
{
/**
* @param string $format
* @param string $format
* @param array<string, mixed> $settings
*/
public function resize(MediaInterface $media, File $in, File $out, $format, array $settings);

/**
* @param array<string, mixed> $settings
*
* @return Box
*/
public function getBox(MediaInterface $media, array $settings);
Expand Down
1 change: 1 addition & 0 deletions src/Resources/config/media.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sonata.media.resizer.crop.class">Sonata\MediaBundle\Resizer\CropResizer</parameter>
<parameter key="sonata.media.resizer.simple.class">Sonata\MediaBundle\Resizer\SimpleResizer</parameter>
<parameter key="sonata.media.resizer.square.class">Sonata\MediaBundle\Resizer\SquareResizer</parameter>
<parameter key="sonata.media.adapter.image.gd.class">Imagine\Gd\Imagine</parameter>
Expand Down
Loading

0 comments on commit 9353e2f

Please sign in to comment.