Skip to content

Commit b657b66

Browse files
committed
Merge pull request #717 from antoligy/implement-pngquant-mozjpeg
Introduce mozjpeg and pngquant post-processors, add transform options.
2 parents aee2865 + 94457f6 commit b657b66

File tree

7 files changed

+324
-2
lines changed

7 files changed

+324
-2
lines changed

Imagine/Filter/FilterManager.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Liip\ImagineBundle\Binary\FileBinaryInterface;
88
use Liip\ImagineBundle\Binary\MimeTypeGuesserInterface;
99
use Liip\ImagineBundle\Imagine\Filter\PostProcessor\PostProcessorInterface;
10+
use Liip\ImagineBundle\Imagine\Filter\PostProcessor\ConfigurablePostProcessorInterface;
1011
use Liip\ImagineBundle\Imagine\Filter\Loader\LoaderInterface;
1112
use Liip\ImagineBundle\Model\Binary;
1213

@@ -172,7 +173,11 @@ public function applyPostProcessors(BinaryInterface $binary, $config)
172173
'Could not find post processor "%s"', $postProcessorName
173174
));
174175
}
175-
$binary = $this->postProcessors[$postProcessorName]->process($binary);
176+
if ($this->postProcessors[$postProcessorName] instanceof ConfigurablePostProcessorInterface) {
177+
$binary = $this->postProcessors[$postProcessorName]->processWithConfiguration($binary, $postProcessorOptions);
178+
} else {
179+
$binary = $this->postProcessors[$postProcessorName]->process($binary);
180+
}
176181
}
177182

178183
return $binary;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Liip\ImagineBundle\Imagine\Filter\PostProcessor;
4+
5+
use Liip\ImagineBundle\Binary\BinaryInterface;
6+
7+
/**
8+
* Interface to make PostProcessors configurable without breaking BC.
9+
*
10+
* @see PostProcessorInterface for the original interface.
11+
*
12+
* @author Alex Wilson <[email protected]>
13+
*/
14+
interface ConfigurablePostProcessorInterface
15+
{
16+
/**
17+
* Allows processing a BinaryInterface, with run-time options, so PostProcessors remain stateless.
18+
*
19+
* @param BinaryInterface $binary
20+
* @param array $options Operation-specific options
21+
*
22+
* @return BinaryInterface
23+
*/
24+
public function processWithConfiguration(BinaryInterface $binary, array $options);
25+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Liip\ImagineBundle\Imagine\Filter\PostProcessor;
4+
5+
use Liip\ImagineBundle\Binary\BinaryInterface;
6+
use Liip\ImagineBundle\Model\Binary;
7+
use Symfony\Component\Process\Exception\ProcessFailedException;
8+
use Symfony\Component\Process\ProcessBuilder;
9+
10+
/**
11+
* mozjpeg post-processor, for noticably better jpeg compression.
12+
*
13+
* @see http://calendar.perfplanet.com/2014/mozjpeg-3-0/
14+
* @see https://mozjpeg.codelove.de/binaries.html
15+
*
16+
* @author Alex Wilson <[email protected]>
17+
*/
18+
class MozJpegPostProcessor implements PostProcessorInterface, ConfigurablePostProcessorInterface
19+
{
20+
/** @var string Path to the mozjpeg cjpeg binary */
21+
protected $mozjpegBin;
22+
23+
/** @var null|int Quality factor */
24+
protected $quality;
25+
26+
/**
27+
* Constructor.
28+
*
29+
* @param string $mozjpegBin Path to the mozjpeg cjpeg binary
30+
* @param int|null $quality Quality factor
31+
*/
32+
public function __construct(
33+
$mozjpegBin = '/opt/mozjpeg/bin/cjpeg',
34+
$quality = null
35+
) {
36+
$this->mozjpegBin = $mozjpegBin;
37+
$this->setQuality($quality);
38+
}
39+
40+
/**
41+
* @param int $quality
42+
*
43+
* @return MozJpegPostProcessor
44+
*/
45+
public function setQuality($quality)
46+
{
47+
$this->quality = $quality;
48+
49+
return $this;
50+
}
51+
52+
/**
53+
* @param BinaryInterface $binary
54+
*
55+
* @uses MozJpegPostProcessor::processWithConfiguration
56+
*
57+
* @throws ProcessFailedException
58+
*
59+
* @return BinaryInterface
60+
*/
61+
public function process(BinaryInterface $binary)
62+
{
63+
return $this->processWithConfiguration($binary, array());
64+
}
65+
66+
/**
67+
* @param BinaryInterface $binary
68+
* @param array $options
69+
*
70+
* @throws ProcessFailedException
71+
*
72+
* @return BinaryInterface
73+
*/
74+
public function processWithConfiguration(BinaryInterface $binary, array $options)
75+
{
76+
$type = strtolower($binary->getMimeType());
77+
if (!in_array($type, array('image/jpeg', 'image/jpg'))) {
78+
return $binary;
79+
}
80+
81+
$pb = new ProcessBuilder(array($this->mozjpegBin));
82+
83+
// Places emphasis on DC
84+
$pb->add('-quant-table');
85+
$pb->add(2);
86+
87+
$transformQuality = array_key_exists('quality', $options) ? $options['quality'] : $this->quality;
88+
if ($transformQuality !== null) {
89+
$pb->add('-quality');
90+
$pb->add($transformQuality);
91+
}
92+
93+
$pb->add('-optimise');
94+
95+
// Favor stdin/stdout so we don't waste time creating a new file.
96+
$pb->setInput($binary->getContent());
97+
98+
$proc = $pb->getProcess();
99+
$proc->run();
100+
101+
if (false !== strpos($proc->getOutput(), 'ERROR') || 0 !== $proc->getExitCode()) {
102+
throw new ProcessFailedException($proc);
103+
}
104+
105+
$result = new Binary($proc->getOutput(), $binary->getMimeType(), $binary->getFormat());
106+
107+
return $result;
108+
}
109+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace Liip\ImagineBundle\Imagine\Filter\PostProcessor;
4+
5+
use Liip\ImagineBundle\Binary\BinaryInterface;
6+
use Liip\ImagineBundle\Model\Binary;
7+
use Symfony\Component\Process\Exception\ProcessFailedException;
8+
use Symfony\Component\Process\ProcessBuilder;
9+
10+
/**
11+
* pngquant post-processor, for optimal, web-safe, lossy png compression
12+
* This requires a recent version of pngquant (so 2.3 or higher?)
13+
* See pngqaunt.org if you are unable to find a binary package for your distribution.
14+
*
15+
* @see https://pngquant.org/
16+
*
17+
* @author Alex Wilson <[email protected]>
18+
*/
19+
class PngquantPostProcessor implements PostProcessorInterface, ConfigurablePostProcessorInterface
20+
{
21+
/** @var string Path to pngquant binary */
22+
protected $pngquantBin;
23+
24+
/** @var string Quality to pass to pngquant */
25+
protected $quality;
26+
27+
/**
28+
* Constructor.
29+
*
30+
* @param string $pngquantBin Path to the pngquant binary
31+
*/
32+
public function __construct($pngquantBin = '/usr/bin/pngquant', $quality = '80-100')
33+
{
34+
$this->pngquantBin = $pngquantBin;
35+
$this->setQuality($quality);
36+
}
37+
38+
/**
39+
* @param string $quality
40+
*
41+
* @return PngquantPostProcessor
42+
*/
43+
public function setQuality($quality)
44+
{
45+
$this->quality = $quality;
46+
47+
return $this;
48+
}
49+
50+
/**
51+
* @param BinaryInterface $binary
52+
*
53+
* @uses PngquantPostProcessor::processWithConfiguration
54+
*
55+
* @throws ProcessFailedException
56+
*
57+
* @return BinaryInterface
58+
*/
59+
public function process(BinaryInterface $binary)
60+
{
61+
return $this->processWithConfiguration($binary, array());
62+
}
63+
64+
/**
65+
* @param BinaryInterface $binary
66+
* @param array $options
67+
*
68+
* @throws ProcessFailedException
69+
*
70+
* @return BinaryInterface
71+
*/
72+
public function processWithConfiguration(BinaryInterface $binary, array $options)
73+
{
74+
$type = strtolower($binary->getMimeType());
75+
if (!in_array($type, array('image/png'))) {
76+
return $binary;
77+
}
78+
79+
$pb = new ProcessBuilder(array($this->pngquantBin));
80+
81+
// Specify quality.
82+
$tranformQuality = array_key_exists('quality', $options) ? $options['quality'] : $this->quality;
83+
$pb->add('--quality');
84+
$pb->add($tranformQuality);
85+
86+
// Read to/from stdout to save resources.
87+
$pb->add('-');
88+
$pb->setInput($binary->getContent());
89+
90+
$proc = $pb->getProcess();
91+
$proc->run();
92+
93+
// 98 and 99 are "quality too low" to compress current current image which, while isn't ideal, is not a failure
94+
if (!in_array($proc->getExitCode(), array(0, 98, 99))) {
95+
throw new ProcessFailedException($proc);
96+
}
97+
98+
$result = new Binary($proc->getOutput(), $binary->getMimeType(), $binary->getFormat());
99+
100+
return $result;
101+
}
102+
}

Imagine/Filter/PostProcessor/PostProcessorInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
/**
88
* Interface for PostProcessors - handlers which can operate on binaries prepared in FilterManager.
99
*
10+
* @see ConfigurablePostProcessorInterface For a means to configure these at run-time.
11+
*
1012
* @author Konstantin Tjuterev <[email protected]>
1113
*/
1214
interface PostProcessorInterface

Resources/config/imagine.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@
7272
<parameter key="liip_imagine.filter.post_processor.optipng.class">Liip\ImagineBundle\Imagine\Filter\PostProcessor\OptiPngPostProcessor</parameter>
7373
<parameter key="liip_imagine.optipng.binary">/usr/bin/optipng</parameter>
7474

75+
<parameter key="liip_imagine.filter.post_processor.pngquant.class">Liip\ImagineBundle\Imagine\Filter\PostProcessor\PngquantPostProcessor</parameter>
76+
<parameter key="liip_imagine.pngquant.binary">/usr/bin/pngquant</parameter>
77+
78+
<parameter key="liip_imagine.filter.post_processor.mozjpeg.class">Liip\ImagineBundle\Imagine\Filter\PostProcessor\MozJpegPostProcessor</parameter>
79+
<parameter key="liip_imagine.mozjpeg.binary">/opt/mozjpeg/bin/cjpeg</parameter>
80+
7581
</parameters>
7682

7783
<services>
@@ -281,5 +287,13 @@
281287
<argument>%liip_imagine.optipng.binary%</argument>
282288
<tag name="liip_imagine.filter.post_processor" post_processor="optipng" />
283289
</service>
290+
<service id="liip_imagine.filter.post_processor.pngquant" class="%liip_imagine.filter.post_processor.pngquant.class%">
291+
<argument>%liip_imagine.pngquant.binary%</argument>
292+
<tag name="liip_imagine.filter.post_processor" post_processor="pngquant" />
293+
</service>
294+
<service id="liip_imagine.filter.post_processor.mozjpeg" class="%liip_imagine.filter.post_processor.mozjpeg.class%">
295+
<argument>%liip_imagine.mozjpeg.binary%</argument>
296+
<tag name="liip_imagine.filter.post_processor" post_processor="mozjpeg" />
297+
</service>
284298
</services>
285299
</container>

Resources/doc/filters.rst

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,11 @@ implement ``Liip\ImagineBundle\Imagine\Filter\PostProcessor\PostProcessorInterfa
319319
basically, the file containing an image after all filters have been applied. It
320320
should return the ``BinaryInterface`` as well.
321321

322+
Post-Processors, for this reason, may be safely chained. This is true even if they
323+
operate on different mime-types, meaning that they are perfect for image-specific
324+
optimisation techniques. A number of optimisers, lossy and loss-less, are provided
325+
by default.
326+
322327
To tell the bundle about your post-processor, register it in the service
323328
container and apply the ``liip_imagine.filter.post_processor`` tag to it:
324329

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

384389

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

@@ -392,3 +397,63 @@ Make sure that optipng binary is installed on the system and change the
392397
liip_imagine.optipng.binary: /usr/local/bin/optipng
393398
394399
.. _`Symfony Service Container`: http://symfony.com/doc/current/book/service_container.html
400+
401+
402+
The ``MozJpegPostProcessor`` can be used to provide safe lossy JPEG optimization.
403+
Optionally, a quality parameter may be passed down to each instance.
404+
More parameters may surface in the future.
405+
406+
.. code-block:: yaml
407+
408+
liip_imagine:
409+
filter_sets:
410+
my_thumb:
411+
filters:
412+
thumbnail: { size: [150, 150], mode: outbound }
413+
post_processors:
414+
mozjpeg: {}
415+
my_other_thumb:
416+
filters:
417+
thumbnail: { size: [150, 150], mode: outbound }
418+
post_processors:
419+
mozjpeg: { quality: 90 }
420+
421+
Make sure that you have installed the mozjpeg tools on your system, and please adjust the
422+
``liip_imagine.mozjpeg.binary`` in parameters if needed.
423+
424+
.. code-block:: yaml
425+
426+
parameters:
427+
liip_imagine.mozjpeg.binary: /opt/mozjpeg/bin/cjpeg
428+
429+
.. _`Symfony Service Container`: http://symfony.com/doc/current/book/service_container.html
430+
431+
432+
The ``PngquantPostProcessor`` can be used to provide safe lossy PNG optimization.
433+
Optionally, a quality parameter may be passed down to each instance.
434+
More parameters may surface in the future.
435+
436+
.. code-block:: yaml
437+
438+
liip_imagine:
439+
filter_sets:
440+
my_thumb:
441+
filters:
442+
thumbnail: { size: [150, 150], mode: outbound }
443+
post_processors:
444+
pngquant: {}
445+
my_other_thumb:
446+
filters:
447+
thumbnail: { size: [150, 150], mode: outbound }
448+
post_processors:
449+
pngquant: { quality: "80-100" }
450+
451+
Make sure that you have installed a recent version (at least 2.3) of pngquant on your system, and please adjust the
452+
``liip_imagine.pngquant.binary`` in parameters if needed.
453+
454+
.. code-block:: yaml
455+
456+
parameters:
457+
liip_imagine.pngquant.binary: /usr/bin/pngquant
458+
459+
.. _`Symfony Service Container`: http://symfony.com/doc/current/book/service_container.html

0 commit comments

Comments
 (0)