Skip to content

Commit

Permalink
Merge pull request #717 from antoligy/implement-pngquant-mozjpeg
Browse files Browse the repository at this point in the history
Introduce mozjpeg and pngquant post-processors, add transform options.
  • Loading branch information
makasim committed May 6, 2016
2 parents aee2865 + 94457f6 commit b657b66
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 2 deletions.
7 changes: 6 additions & 1 deletion Imagine/Filter/FilterManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Liip\ImagineBundle\Binary\FileBinaryInterface;
use Liip\ImagineBundle\Binary\MimeTypeGuesserInterface;
use Liip\ImagineBundle\Imagine\Filter\PostProcessor\PostProcessorInterface;
use Liip\ImagineBundle\Imagine\Filter\PostProcessor\ConfigurablePostProcessorInterface;
use Liip\ImagineBundle\Imagine\Filter\Loader\LoaderInterface;
use Liip\ImagineBundle\Model\Binary;

Expand Down Expand Up @@ -172,7 +173,11 @@ public function applyPostProcessors(BinaryInterface $binary, $config)
'Could not find post processor "%s"', $postProcessorName
));
}
$binary = $this->postProcessors[$postProcessorName]->process($binary);
if ($this->postProcessors[$postProcessorName] instanceof ConfigurablePostProcessorInterface) {
$binary = $this->postProcessors[$postProcessorName]->processWithConfiguration($binary, $postProcessorOptions);
} else {
$binary = $this->postProcessors[$postProcessorName]->process($binary);
}
}

return $binary;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Liip\ImagineBundle\Imagine\Filter\PostProcessor;

use Liip\ImagineBundle\Binary\BinaryInterface;

/**
* Interface to make PostProcessors configurable without breaking BC.
*
* @see PostProcessorInterface for the original interface.
*
* @author Alex Wilson <[email protected]>
*/
interface ConfigurablePostProcessorInterface
{
/**
* Allows processing a BinaryInterface, with run-time options, so PostProcessors remain stateless.
*
* @param BinaryInterface $binary
* @param array $options Operation-specific options
*
* @return BinaryInterface
*/
public function processWithConfiguration(BinaryInterface $binary, array $options);
}
109 changes: 109 additions & 0 deletions Imagine/Filter/PostProcessor/MozJpegPostProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Liip\ImagineBundle\Imagine\Filter\PostProcessor;

use Liip\ImagineBundle\Binary\BinaryInterface;
use Liip\ImagineBundle\Model\Binary;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\ProcessBuilder;

/**
* mozjpeg post-processor, for noticably better jpeg compression.
*
* @see http://calendar.perfplanet.com/2014/mozjpeg-3-0/
* @see https://mozjpeg.codelove.de/binaries.html
*
* @author Alex Wilson <[email protected]>
*/
class MozJpegPostProcessor implements PostProcessorInterface, ConfigurablePostProcessorInterface
{
/** @var string Path to the mozjpeg cjpeg binary */
protected $mozjpegBin;

/** @var null|int Quality factor */
protected $quality;

/**
* Constructor.
*
* @param string $mozjpegBin Path to the mozjpeg cjpeg binary
* @param int|null $quality Quality factor
*/
public function __construct(
$mozjpegBin = '/opt/mozjpeg/bin/cjpeg',
$quality = null
) {
$this->mozjpegBin = $mozjpegBin;
$this->setQuality($quality);
}

/**
* @param int $quality
*
* @return MozJpegPostProcessor
*/
public function setQuality($quality)
{
$this->quality = $quality;

return $this;
}

/**
* @param BinaryInterface $binary
*
* @uses MozJpegPostProcessor::processWithConfiguration
*
* @throws ProcessFailedException
*
* @return BinaryInterface
*/
public function process(BinaryInterface $binary)
{
return $this->processWithConfiguration($binary, array());
}

/**
* @param BinaryInterface $binary
* @param array $options
*
* @throws ProcessFailedException
*
* @return BinaryInterface
*/
public function processWithConfiguration(BinaryInterface $binary, array $options)
{
$type = strtolower($binary->getMimeType());
if (!in_array($type, array('image/jpeg', 'image/jpg'))) {
return $binary;
}

$pb = new ProcessBuilder(array($this->mozjpegBin));

// Places emphasis on DC
$pb->add('-quant-table');
$pb->add(2);

$transformQuality = array_key_exists('quality', $options) ? $options['quality'] : $this->quality;
if ($transformQuality !== null) {
$pb->add('-quality');
$pb->add($transformQuality);
}

$pb->add('-optimise');

// Favor stdin/stdout so we don't waste time creating a new file.
$pb->setInput($binary->getContent());

$proc = $pb->getProcess();
$proc->run();

if (false !== strpos($proc->getOutput(), 'ERROR') || 0 !== $proc->getExitCode()) {
throw new ProcessFailedException($proc);
}

$result = new Binary($proc->getOutput(), $binary->getMimeType(), $binary->getFormat());

return $result;
}
}
102 changes: 102 additions & 0 deletions Imagine/Filter/PostProcessor/PngquantPostProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace Liip\ImagineBundle\Imagine\Filter\PostProcessor;

use Liip\ImagineBundle\Binary\BinaryInterface;
use Liip\ImagineBundle\Model\Binary;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\ProcessBuilder;

/**
* pngquant post-processor, for optimal, web-safe, lossy png compression
* This requires a recent version of pngquant (so 2.3 or higher?)
* See pngqaunt.org if you are unable to find a binary package for your distribution.
*
* @see https://pngquant.org/
*
* @author Alex Wilson <[email protected]>
*/
class PngquantPostProcessor implements PostProcessorInterface, ConfigurablePostProcessorInterface
{
/** @var string Path to pngquant binary */
protected $pngquantBin;

/** @var string Quality to pass to pngquant */
protected $quality;

/**
* Constructor.
*
* @param string $pngquantBin Path to the pngquant binary
*/
public function __construct($pngquantBin = '/usr/bin/pngquant', $quality = '80-100')
{
$this->pngquantBin = $pngquantBin;
$this->setQuality($quality);
}

/**
* @param string $quality
*
* @return PngquantPostProcessor
*/
public function setQuality($quality)
{
$this->quality = $quality;

return $this;
}

/**
* @param BinaryInterface $binary
*
* @uses PngquantPostProcessor::processWithConfiguration
*
* @throws ProcessFailedException
*
* @return BinaryInterface
*/
public function process(BinaryInterface $binary)
{
return $this->processWithConfiguration($binary, array());
}

/**
* @param BinaryInterface $binary
* @param array $options
*
* @throws ProcessFailedException
*
* @return BinaryInterface
*/
public function processWithConfiguration(BinaryInterface $binary, array $options)
{
$type = strtolower($binary->getMimeType());
if (!in_array($type, array('image/png'))) {
return $binary;
}

$pb = new ProcessBuilder(array($this->pngquantBin));

// Specify quality.
$tranformQuality = array_key_exists('quality', $options) ? $options['quality'] : $this->quality;
$pb->add('--quality');
$pb->add($tranformQuality);

// Read to/from stdout to save resources.
$pb->add('-');
$pb->setInput($binary->getContent());

$proc = $pb->getProcess();
$proc->run();

// 98 and 99 are "quality too low" to compress current current image which, while isn't ideal, is not a failure
if (!in_array($proc->getExitCode(), array(0, 98, 99))) {
throw new ProcessFailedException($proc);
}

$result = new Binary($proc->getOutput(), $binary->getMimeType(), $binary->getFormat());

return $result;
}
}
2 changes: 2 additions & 0 deletions Imagine/Filter/PostProcessor/PostProcessorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
/**
* Interface for PostProcessors - handlers which can operate on binaries prepared in FilterManager.
*
* @see ConfigurablePostProcessorInterface For a means to configure these at run-time.
*
* @author Konstantin Tjuterev <[email protected]>
*/
interface PostProcessorInterface
Expand Down
14 changes: 14 additions & 0 deletions Resources/config/imagine.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
<parameter key="liip_imagine.filter.post_processor.optipng.class">Liip\ImagineBundle\Imagine\Filter\PostProcessor\OptiPngPostProcessor</parameter>
<parameter key="liip_imagine.optipng.binary">/usr/bin/optipng</parameter>

<parameter key="liip_imagine.filter.post_processor.pngquant.class">Liip\ImagineBundle\Imagine\Filter\PostProcessor\PngquantPostProcessor</parameter>
<parameter key="liip_imagine.pngquant.binary">/usr/bin/pngquant</parameter>

<parameter key="liip_imagine.filter.post_processor.mozjpeg.class">Liip\ImagineBundle\Imagine\Filter\PostProcessor\MozJpegPostProcessor</parameter>
<parameter key="liip_imagine.mozjpeg.binary">/opt/mozjpeg/bin/cjpeg</parameter>

</parameters>

<services>
Expand Down Expand Up @@ -281,5 +287,13 @@
<argument>%liip_imagine.optipng.binary%</argument>
<tag name="liip_imagine.filter.post_processor" post_processor="optipng" />
</service>
<service id="liip_imagine.filter.post_processor.pngquant" class="%liip_imagine.filter.post_processor.pngquant.class%">
<argument>%liip_imagine.pngquant.binary%</argument>
<tag name="liip_imagine.filter.post_processor" post_processor="pngquant" />
</service>
<service id="liip_imagine.filter.post_processor.mozjpeg" class="%liip_imagine.filter.post_processor.mozjpeg.class%">
<argument>%liip_imagine.mozjpeg.binary%</argument>
<tag name="liip_imagine.filter.post_processor" post_processor="mozjpeg" />
</service>
</services>
</container>
67 changes: 66 additions & 1 deletion Resources/doc/filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@ implement ``Liip\ImagineBundle\Imagine\Filter\PostProcessor\PostProcessorInterfa
basically, the file containing an image after all filters have been applied. It
should return the ``BinaryInterface`` as well.

Post-Processors, for this reason, may be safely chained. This is true even if they
operate on different mime-types, meaning that they are perfect for image-specific
optimisation techniques. A number of optimisers, lossy and loss-less, are provided
by default.

To tell the bundle about your post-processor, register it in the service
container and apply the ``liip_imagine.filter.post_processor`` tag to it:

Expand Down Expand Up @@ -382,7 +387,7 @@ parameters, for example:
.. _`Symfony Service Container`: http://symfony.com/doc/current/book/service_container.html


The ``OptiPngPostProcessor`` is also available by default and can be used just as jpegoptim.
The ``OptiPngPostProcessor`` is also available and can be used just as jpegoptim.
Make sure that optipng binary is installed on the system and change the
``liip_imagine.optipng.binary`` in parameters if needed.

Expand All @@ -392,3 +397,63 @@ Make sure that optipng binary is installed on the system and change the
liip_imagine.optipng.binary: /usr/local/bin/optipng
.. _`Symfony Service Container`: http://symfony.com/doc/current/book/service_container.html


The ``MozJpegPostProcessor`` can be used to provide safe lossy JPEG optimization.
Optionally, a quality parameter may be passed down to each instance.
More parameters may surface in the future.

.. code-block:: yaml
liip_imagine:
filter_sets:
my_thumb:
filters:
thumbnail: { size: [150, 150], mode: outbound }
post_processors:
mozjpeg: {}
my_other_thumb:
filters:
thumbnail: { size: [150, 150], mode: outbound }
post_processors:
mozjpeg: { quality: 90 }
Make sure that you have installed the mozjpeg tools on your system, and please adjust the
``liip_imagine.mozjpeg.binary`` in parameters if needed.

.. code-block:: yaml
parameters:
liip_imagine.mozjpeg.binary: /opt/mozjpeg/bin/cjpeg
.. _`Symfony Service Container`: http://symfony.com/doc/current/book/service_container.html


The ``PngquantPostProcessor`` can be used to provide safe lossy PNG optimization.
Optionally, a quality parameter may be passed down to each instance.
More parameters may surface in the future.

.. code-block:: yaml
liip_imagine:
filter_sets:
my_thumb:
filters:
thumbnail: { size: [150, 150], mode: outbound }
post_processors:
pngquant: {}
my_other_thumb:
filters:
thumbnail: { size: [150, 150], mode: outbound }
post_processors:
pngquant: { quality: "80-100" }
Make sure that you have installed a recent version (at least 2.3) of pngquant on your system, and please adjust the
``liip_imagine.pngquant.binary`` in parameters if needed.

.. code-block:: yaml
parameters:
liip_imagine.pngquant.binary: /usr/bin/pngquant
.. _`Symfony Service Container`: http://symfony.com/doc/current/book/service_container.html

0 comments on commit b657b66

Please sign in to comment.