Skip to content

Commit edcc88a

Browse files
committed
[LazyImage] Cache BlurHash, close #2
1 parent e6b827b commit edcc88a

File tree

6 files changed

+148
-25
lines changed

6 files changed

+148
-25
lines changed

src/LazyImage/composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"require-dev": {
3737
"intervention/image": "^2.5",
3838
"kornrunner/blurhash": "^1.1",
39+
"symfony/cache-contracts": "^2.2",
3940
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
4041
"symfony/phpunit-bridge": "^5.2|^6.0|^7.0",
4142
"symfony/twig-bundle": "^5.4|^6.0|^7.0",

src/LazyImage/doc/index.rst

+17
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,28 @@ The ``data_uri_thumbnail`` function receives 3 arguments:
103103
- the width of the BlurHash to generate
104104
- the height of the BlurHash to generate
105105

106+
Performance considerations
107+
~~~~~~~~~~~~~~~~~~~~~~~~~~
108+
106109
You should try to generate small BlurHash images as generating the image
107110
can be CPU-intensive. Instead, you can rely on the browser scaling
108111
abilities by generating a small image and using the ``width`` and
109112
``height`` HTML attributes to scale up the image.
110113

114+
You can also configure a cache pool to store the generated BlurHash,
115+
this way you can avoid generating the same BlurHash multiple times:
116+
117+
.. code-block:: yaml
118+
119+
# config/packages/lazy_image.yaml
120+
framework:
121+
cache:
122+
pools:
123+
cache.lazy_image: cache.adapter.redis # or any other cache adapter depending on your needs
124+
125+
lazy_image:
126+
cache: cache.lazy_image # the cache pool to use
127+
111128
Extend the default behavior
112129
~~~~~~~~~~~~~~~~~~~~~~~~~~~
113130

src/LazyImage/src/BlurHash/BlurHash.php

+35-24
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Intervention\Image\ImageManager;
1515
use kornrunner\Blurhash\Blurhash as BlurhashEncoder;
16+
use Symfony\Contracts\Cache\CacheInterface;
1617

1718
/**
1819
* @author Titouan Galopin <[email protected]>
@@ -21,11 +22,10 @@
2122
*/
2223
class BlurHash implements BlurHashInterface
2324
{
24-
private $imageManager;
25-
26-
public function __construct(?ImageManager $imageManager = null)
27-
{
28-
$this->imageManager = $imageManager;
25+
public function __construct(
26+
private ?ImageManager $imageManager = null,
27+
private ?CacheInterface $cache = null,
28+
) {
2929
}
3030

3131
public function createDataUriThumbnail(string $filename, int $width, int $height, int $encodingWidth = 75, int $encodingHeight = 75): string
@@ -62,28 +62,39 @@ public function encode(string $filename, int $encodingWidth = 75, int $encodingH
6262
throw new \LogicException('To use the Blurhash feature, install kornrunner/blurhash.');
6363
}
6464

65-
// Resize image to increase encoding performance
66-
$image = $this->imageManager->make(file_get_contents($filename));
67-
$image->resize($encodingWidth, $encodingHeight, static function ($constraint) {
68-
$constraint->aspectRatio();
69-
$constraint->upsize();
70-
});
71-
72-
// Encode using BlurHash
73-
$width = $image->getWidth();
74-
$height = $image->getHeight();
75-
76-
$pixels = [];
77-
for ($y = 0; $y < $height; ++$y) {
78-
$row = [];
79-
for ($x = 0; $x < $width; ++$x) {
80-
$color = $image->pickColor($x, $y);
81-
$row[] = [$color[0], $color[1], $color[2]];
65+
$doEncode = function (string $filename, int $encodingWidth, int $encodingHeight) {
66+
// Resize image to increase encoding performance
67+
$image = $this->imageManager->make(file_get_contents($filename));
68+
$image->resize($encodingWidth, $encodingHeight, static function ($constraint) {
69+
$constraint->aspectRatio();
70+
$constraint->upsize();
71+
});
72+
73+
// Encode using BlurHash
74+
$width = $image->getWidth();
75+
$height = $image->getHeight();
76+
77+
$pixels = [];
78+
for ($y = 0; $y < $height; ++$y) {
79+
$row = [];
80+
for ($x = 0; $x < $width; ++$x) {
81+
$color = $image->pickColor($x, $y);
82+
$row[] = [$color[0], $color[1], $color[2]];
83+
}
84+
85+
$pixels[] = $row;
8286
}
8387

84-
$pixels[] = $row;
88+
return BlurhashEncoder::encode($pixels, 4, 3);
89+
};
90+
91+
if (null === $this->cache) {
92+
return $doEncode($filename, $encodingWidth, $encodingHeight);
8593
}
8694

87-
return BlurhashEncoder::encode($pixels, 4, 3);
95+
return $this->cache->get(
96+
'blurhash.'.hash('xxh3', $filename.$encodingWidth.$encodingHeight),
97+
fn () => $doEncode($filename, $encodingWidth, $encodingHeight),
98+
);
8899
}
89100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LazyImage\DependencyInjection;
13+
14+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
15+
use Symfony\Component\Config\Definition\ConfigurationInterface;
16+
17+
/**
18+
* @author Hugo Alliaume <[email protected]>
19+
*
20+
* @internal
21+
*/
22+
final class Configuration implements ConfigurationInterface
23+
{
24+
public function getConfigTreeBuilder(): TreeBuilder
25+
{
26+
$treeBuilder = new TreeBuilder('ux_lazy_image');
27+
$rootNode = $treeBuilder->getRootNode();
28+
$rootNode
29+
->children()
30+
->scalarNode('cache')->end()
31+
->end()
32+
;
33+
34+
return $treeBuilder;
35+
}
36+
}

src/LazyImage/src/DependencyInjection/LazyImageExtension.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class LazyImageExtension extends Extension implements PrependExtensionInterface
3232
{
3333
public function load(array $configs, ContainerBuilder $container)
3434
{
35+
$configuration = new Configuration();
36+
$config = $this->processConfiguration($configuration, $configs);
37+
3538
if (class_exists(ImageManager::class)) {
3639
$container
3740
->setDefinition('lazy_image.image_manager', new Definition(ImageManager::class))
@@ -41,10 +44,14 @@ public function load(array $configs, ContainerBuilder $container)
4144

4245
$container
4346
->setDefinition('lazy_image.blur_hash', new Definition(BlurHash::class))
44-
->addArgument(new Reference('lazy_image.image_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE))
47+
->setArgument(0, new Reference('lazy_image.image_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE))
4548
->setPublic(false)
4649
;
4750

51+
if (isset($config['cache'])) {
52+
$container->getDefinition('lazy_image.blur_hash')->setArgument(1, new Reference($config['cache']));
53+
}
54+
4855
$container->setAlias(BlurHashInterface::class, 'lazy_image.blur_hash')->setPublic(false);
4956

5057
$container

src/LazyImage/tests/BlurHash/BlurHashTest.php

+51
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
namespace Symfony\UX\LazyImage\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
16+
use Symfony\Component\Config\Loader\LoaderInterface;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
1518
use Symfony\UX\LazyImage\BlurHash\BlurHashInterface;
1619
use Symfony\UX\LazyImage\Tests\Kernel\TwigAppKernel;
1720

@@ -37,6 +40,54 @@ public function testEncode()
3740
);
3841
}
3942

43+
public function testEncodeWithCache()
44+
{
45+
$kernel = new class('test', true) extends TwigAppKernel {
46+
public function registerContainerConfiguration(LoaderInterface $loader)
47+
{
48+
parent::registerContainerConfiguration($loader);
49+
50+
$loader->load(static function (ContainerBuilder $container) {
51+
$container->loadFromExtension('framework', [
52+
'cache' => [
53+
'pools' => [
54+
'cache.lazy_image' => [
55+
'adapter' => 'cache.adapter.array',
56+
],
57+
],
58+
],
59+
]);
60+
61+
$container->loadFromExtension('lazy_image', [
62+
'cache' => 'cache.lazy_image',
63+
]);
64+
65+
$container->setAlias('test.cache.lazy_image', 'cache.lazy_image')->setPublic(true);
66+
});
67+
}
68+
};
69+
70+
$kernel->boot();
71+
$container = $kernel->getContainer()->get('test.service_container');
72+
73+
/** @var BlurHashInterface $blurHash */
74+
$blurHash = $container->get('test.lazy_image.blur_hash');
75+
76+
$cache = $container->get('test.cache.lazy_image');
77+
static::assertInstanceOf(ArrayAdapter::class, $cache);
78+
static::assertEmpty($cache->getValues());
79+
80+
$this->assertSame(
81+
'L54ec*~q_3?bofoffQWB9F9FD%IU',
82+
$blurHash->encode(__DIR__.'/../Fixtures/logo.png')
83+
);
84+
85+
static::assertSame(
86+
'L54ec*~q_3?bofoffQWB9F9FD%IU',
87+
$cache->getItem('blurhash.70d3aaba5c301af7')->get()
88+
);
89+
}
90+
4091
public function testCreateDataUriThumbnail()
4192
{
4293
$kernel = new TwigAppKernel('test', true);

0 commit comments

Comments
 (0)