Skip to content

Commit

Permalink
add resample filter implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
robfrawley committed May 23, 2017
1 parent d2551d8 commit c697f12
Show file tree
Hide file tree
Showing 8 changed files with 491 additions and 9 deletions.
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
{
}
149 changes: 149 additions & 0 deletions Imagine/Filter/Loader/ResampleFilterLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?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'));
$resolver->setAllowedTypes('y', array('int'));
$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', '%s') as $format) {
if (defined($constant = sprintf($format, strtoupper($value))) || defined($constant = sprintf($format, $value))) {
return constant($constant);
}
}

throw new InvalidArgumentException('The "filter" option must resolve to a valid constant using one of '.
'the following formats: "\Imagine\Image\ImageInterface::FILTER_%s" or '.
'"\Imagine\Image\ImageInterface::%s" or "\%s"');
});

try {
return $resolver->resolve($options);
} catch (ExceptionInterface $e) {
throw new InvalidArgumentException('Invalid options provided. The "unit" option must be "ppi" (pixels per '.
'inch) or "ppc" (pixels per centimeter) and "x" and "y" must be set to the desired ppi/ppc.', null, $e);
}
}
}
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``
Sets the horizontal (x) pixel density to resample the image to.

:strong:`y:` ``int``
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

0 comments on commit c697f12

Please sign in to comment.