Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Filter] Add resolution filter loader #941

Merged
merged 2 commits into from
Aug 31, 2017
Merged
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
18 changes: 18 additions & 0 deletions Exception/Imagine/Filter/LoadFilterException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the `liip/LiipImagineBundle` project.
*
* (c) https://github.com/liip/LiipImagineBundle/graphs/contributors
*
* For the full copyright and license information, please view the LICENSE.md
* file that was distributed with this source code.
*/

namespace Liip\ImagineBundle\Exception\Imagine\Filter;

use Liip\ImagineBundle\Exception\ExceptionInterface;

class LoadFilterException extends \RuntimeException implements ExceptionInterface
{
}
153 changes: 153 additions & 0 deletions Imagine/Filter/Loader/ResampleFilterLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

/*
* This file is part of the `liip/LiipImagineBundle` project.
*
* (c) https://github.com/liip/LiipImagineBundle/graphs/contributors
*
* For the full copyright and license information, please view the LICENSE.md
* file that was distributed with this source code.
*/

namespace Liip\ImagineBundle\Imagine\Filter\Loader;

use Imagine\Image\ImageInterface;
use Imagine\Image\ImagineInterface;
use Liip\ImagineBundle\Exception\Imagine\Filter\LoadFilterException;
use Liip\ImagineBundle\Utility\OptionsResolver\OptionsResolver;
use Liip\ImagineBundle\Exception\InvalidArgumentException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\Exception\ExceptionInterface;

class ResampleFilterLoader implements LoaderInterface
{
/**
* @var ImagineInterface
*/
private $imagine;

/**
* @param ImagineInterface $imagine
*/
public function __construct(ImagineInterface $imagine)
{
$this->imagine = $imagine;
}

/**
* @param ImageInterface $image
* @param array $options
*
* @throws LoadFilterException
*
* @return ImageInterface
*/
public function load(ImageInterface $image, array $options = array())
{
$options = $this->resolveOptions($options);
$tmpFile = $this->getTemporaryFile($options['temp_dir']);

try {
$image->save($tmpFile, $this->getImagineSaveOptions($options));
$image = $this->imagine->open($tmpFile);
$this->delTemporaryFile($tmpFile);
} catch (\Exception $exception) {
$this->delTemporaryFile($tmpFile);
throw new LoadFilterException('Unable to save/open file in resample filter loader.', null, $exception);
}

return $image;
}

/**
* @param string $path
*
* @throws \RuntimeException
*
* @return string
*/
private function getTemporaryFile($path)
{
if (!is_dir($path) || false === $file = tempnam($path, 'liip-imagine-bundle')) {
throw new \RuntimeException(sprintf('Unable to create temporary file in "%s" base path.', $path));
}

return $file;
}

/**
* @param $file
*
* @throws \RuntimeException
*/
private function delTemporaryFile($file)
{
if (file_exists($file)) {
unlink($file);
}
}

/**
* @param array $options
*
* @return array
*/
private function getImagineSaveOptions(array $options)
{
$saveOptions = array(
'resolution-units' => $options['unit'],
'resolution-x' => $options['x'],
'resolution-y' => $options['y'],
);

if (isset($options['filter'])) {
$saveOptions['resampling-filter'] = $options['filter'];
}

return $saveOptions;
}

/**
* @param array $options
*
* @return array
*/
private function resolveOptions(array $options)
{
$resolver = new OptionsResolver();

$resolver->setRequired(array('x', 'y', 'unit', 'temp_dir'));
$resolver->setDefined(array('filter'));
$resolver->setDefault('temp_dir', sys_get_temp_dir());
$resolver->setDefault('filter', 'UNDEFINED');

$resolver->setAllowedTypes('x', array('int', 'float'));
$resolver->setAllowedTypes('y', array('int', 'float'));
$resolver->setAllowedTypes('temp_dir', array('string'));
$resolver->setAllowedTypes('filter', array('string'));

$resolver->setAllowedValues('unit', array(
ImageInterface::RESOLUTION_PIXELSPERINCH,
ImageInterface::RESOLUTION_PIXELSPERCENTIMETER
));

$resolver->setNormalizer('filter', function (Options $options, $value) {
foreach (array('\Imagine\Image\ImageInterface::FILTER_%s', '\Imagine\Image\ImageInterface::%s', '%s') as $format) {
if (defined($constant = sprintf($format, strtoupper($value))) || defined($constant = sprintf($format, $value))) {
return constant($constant);
}
}

throw new InvalidArgumentException(
'Invalid value for "filter" option: must be a valid constant resolvable using one of formats '.
'"\Imagine\Image\ImageInterface::FILTER_%s", "\Imagine\Image\ImageInterface::%s", or "%s".'
);
});

try {
return $resolver->resolve($options);
} catch (ExceptionInterface $exception) {
throw new InvalidArgumentException(sprintf('Invalid option(s) passed to %s::load().', __CLASS__), null, $exception);
}
}
}
6 changes: 6 additions & 0 deletions Resources/config/imagine.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<parameter key="liip_imagine.filter.loader.rotate.class">Liip\ImagineBundle\Imagine\Filter\Loader\RotateFilterLoader</parameter>
<parameter key="liip_imagine.filter.loader.flip.class">Liip\ImagineBundle\Imagine\Filter\Loader\FlipFilterLoader</parameter>
<parameter key="liip_imagine.filter.loader.interlace.class">Liip\ImagineBundle\Imagine\Filter\Loader\InterlaceFilterLoader</parameter>
<parameter key="liip_imagine.filter.loader.resample.class">Liip\ImagineBundle\Imagine\Filter\Loader\ResampleFilterLoader</parameter>

<!-- Data loaders' classes -->

Expand Down Expand Up @@ -230,6 +231,11 @@
<tag name="liip_imagine.filter.loader" loader="interlace" />
</service>

<service id="liip_imagine.filter.loader.resample" class="%liip_imagine.filter.loader.resample.class%">
<argument type="service" id="liip_imagine" />
<tag name="liip_imagine.filter.loader" loader="resample" />
</service>

<!-- Data loaders -->

<service id="liip_imagine.binary.loader.prototype.filesystem" class="%liip_imagine.binary.loader.filesystem.class%">
Expand Down
81 changes: 81 additions & 0 deletions Resources/doc/filters/general.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Background Options
values: ``topleft``, ``top``, ``topright``, ``left``, ``center``, ``right``, ``bottomleft``,
``bottom``, and ``bottomright``.


.. _filter-grayscale:

Grayscale
Expand Down Expand Up @@ -126,6 +127,82 @@ Interlace Options
``plane``, and ``partition``.


.. _filter-resample:

Resample
--------

The built-in ``resample`` filter provides a resampling transformation by allows you to
change the resolution of an image. This filter exposes a number of `resample options`_ which
may be used to configure its behavior.

.. tip::

Resampling changes the image resolution (also known as "pixel density") of an image
and is useful when you need to present different versions of an image dependent on
the user's screen density. For example, you may need to provide a "normal" and a
"retina" variant.

The use of "resolution" is not to be confused with "dimensions". This filter does not
affect the dimentions of an image, only the pixel density.


Example configuration:

.. code-block:: yaml

# app/config/config.yml

liip_imagine:
filter_sets:

# name our filter set "my_resample_filter"
my_resample_filter:
filters:

# use and setup the "resample" filter
resample:

# set the unit to use for pixel density
unit: ppi

# set the horizontal pixel density
x: 72

# set the vertical pixel density
y: 72

# set the resampling filter
filter: lanczos

# set the temporary path to use for resampling work
tmp_dir: /my/custom/temporary/directory/path


Resample Options
~~~~~~~~~~~~~~~~

:strong:`unit:` ``string``
Sets the unit to use for pixel density, either "pixels per inch" or "pixels per centimeter".
Valid values: ``ppi`` and ``ppc``.

:strong:`x:` ``int|float``
Sets the horizontal (x) pixel density to resample the image to.

:strong:`y:` ``int|float``
Sets the vertical (y) pixel density to resample the image to.

:strong:`filter:` ``string``
Sets the optional filter to use during the resampling operation. It must be a string resolvable
as a constant from `Imagine\Image\ImageInterface`_ (you may omit the ``FILTER_`` prefix)
or a valid fully qualified constant. By default it is set to ``FILTER_UNDEFINED``.

:strong:`tmp_dir:` ``string``
Sets the optional temporary work directory. This filter requires a temporary location to save
out and read back in the image binary, as these operations are requires to resample an image.
By default, it is set to the value of the `sys_get_temp_dir()`_ function.


.. _filter-strip:

Strip
Expand Down Expand Up @@ -211,3 +288,7 @@ Watermark Options
The **position** option and **ordering** for this filter is significant.
For example, calling a ``crop`` after this filter could unintentionally
remove the watermark entirely from the final image.


.. _`Imagine\Image\ImageInterface`: https://imagine.readthedocs.io/en/master/_static/API/Imagine/Image/ImageInterface.html
.. _`sys_get_temp_dir()`: http://php.net/manual/en/function.sys-get-temp-dir.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

/*
* This file is part of the `liip/LiipImagineBundle` project.
*
* (c) https://github.com/liip/LiipImagineBundle/graphs/contributors
*
* For the full copyright and license information, please view the LICENSE.md
* file that was distributed with this source code.
*/

namespace Liip\ImagineBundle\Tests\Functional\Imagine\Filter\Loader;

use Liip\ImagineBundle\Tests\Functional\AbstractWebTestCase;

/**
* @covers \Liip\ImagineBundle\Imagine\Filter\Loader\ResampleFilterLoader
*/
class ResampleFilterLoaderTest extends AbstractWebTestCase
{
public function testContainerHasService()
{
$this->createClient();

$this->assertInstanceOf(
'\Liip\ImagineBundle\Imagine\Filter\Loader\ResampleFilterLoader',
self::$kernel->getContainer()->get('liip_imagine.filter.loader.resample')
);
}
}
Loading