1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the `liip/LiipImagineBundle` project. |
5
|
|
|
* |
6
|
|
|
* (c) https://github.com/liip/LiipImagineBundle/graphs/contributors |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE.md |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Liip\ImagineBundle\Imagine\Filter; |
13
|
|
|
|
14
|
|
|
use Imagine\Image\ImageInterface; |
15
|
|
|
use Imagine\Image\ImagineInterface; |
16
|
|
|
use Liip\ImagineBundle\Binary\BinaryInterface; |
17
|
|
|
use Liip\ImagineBundle\Binary\FileBinaryInterface; |
18
|
|
|
use Liip\ImagineBundle\Binary\MimeTypeGuesserInterface; |
19
|
|
|
use Liip\ImagineBundle\Imagine\Filter\Loader\LoaderInterface; |
20
|
|
|
use Liip\ImagineBundle\Imagine\Filter\PostProcessor\PostProcessorInterface; |
21
|
|
|
use Liip\ImagineBundle\Model\Binary; |
22
|
|
|
|
23
|
|
|
class FilterManager |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var FilterConfiguration |
27
|
|
|
*/ |
28
|
|
|
protected $filterConfig; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var ImagineInterface |
32
|
|
|
*/ |
33
|
|
|
protected $imagine; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var MimeTypeGuesserInterface |
37
|
|
|
*/ |
38
|
|
|
protected $mimeTypeGuesser; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var LoaderInterface[] |
42
|
|
|
*/ |
43
|
|
|
protected $loaders = []; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var PostProcessorInterface[] |
47
|
|
|
*/ |
48
|
|
|
protected $postProcessors = []; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @param FilterConfiguration $filterConfig |
52
|
|
|
* @param ImagineInterface $imagine |
53
|
|
|
* @param MimeTypeGuesserInterface $mimeTypeGuesser |
54
|
|
|
*/ |
55
|
|
|
public function __construct(FilterConfiguration $filterConfig, ImagineInterface $imagine, MimeTypeGuesserInterface $mimeTypeGuesser) |
56
|
|
|
{ |
57
|
|
|
$this->filterConfig = $filterConfig; |
58
|
|
|
$this->imagine = $imagine; |
59
|
|
|
$this->mimeTypeGuesser = $mimeTypeGuesser; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Adds a loader to handle the given filter. |
64
|
|
|
* |
65
|
|
|
* @param string $filter |
66
|
|
|
* @param LoaderInterface $loader |
67
|
|
|
*/ |
68
|
|
|
public function addLoader(string $filter, LoaderInterface $loader): void |
69
|
|
|
{ |
70
|
|
|
$this->loaders[$filter] = $loader; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Adds a post-processor to handle binaries. |
75
|
|
|
* |
76
|
|
|
* @param string $name |
77
|
|
|
* @param PostProcessorInterface $postProcessor |
78
|
|
|
*/ |
79
|
|
|
public function addPostProcessor(string $name, PostProcessorInterface $postProcessor): void |
80
|
|
|
{ |
81
|
|
|
$this->postProcessors[$name] = $postProcessor; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @return FilterConfiguration |
86
|
|
|
*/ |
87
|
|
|
public function getFilterConfiguration(): FilterConfiguration |
88
|
|
|
{ |
89
|
|
|
return $this->filterConfig; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @param BinaryInterface $binary |
94
|
|
|
* @param array $config |
95
|
|
|
* |
96
|
|
|
* @throws \InvalidArgumentException |
97
|
|
|
* |
98
|
|
|
* @return BinaryInterface |
99
|
|
|
*/ |
100
|
|
|
public function apply(BinaryInterface $binary, array $config): BinaryInterface |
101
|
|
|
{ |
102
|
|
|
$config += [ |
103
|
|
|
'quality' => 100, |
104
|
|
|
'animated' => false, |
105
|
|
|
]; |
106
|
|
|
|
107
|
|
|
return $this->applyPostProcessors($this->applyFilters($binary, $config), $config); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @param BinaryInterface $binary |
112
|
|
|
* @param array $config |
113
|
|
|
* |
114
|
|
|
* @return BinaryInterface |
115
|
|
|
*/ |
116
|
|
|
public function applyFilters(BinaryInterface $binary, array $config): BinaryInterface |
117
|
|
|
{ |
118
|
|
|
if ($binary instanceof FileBinaryInterface) { |
119
|
|
|
$image = $this->imagine->open($binary->getPath()); |
120
|
|
|
} else { |
121
|
|
|
$image = $this->imagine->load($binary->getContent()); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
foreach ($this->sanitizeFilters($config['filters'] ?? []) as $name => $options) { |
125
|
|
|
$prior = $image; |
126
|
|
|
$image = $this->loaders[$name]->load($image, $options); |
127
|
|
|
|
128
|
|
|
if ($prior !== $image) { |
129
|
|
|
$this->destroyImage($prior); |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
return $this->exportConfiguredImageBinary($binary, $image, $config); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Apply the provided filter set on the given binary. |
138
|
|
|
* |
139
|
|
|
* @param BinaryInterface $binary |
140
|
|
|
* @param string $filter |
141
|
|
|
* @param array $runtimeConfig |
142
|
|
|
* |
143
|
|
|
* @throws \InvalidArgumentException |
144
|
|
|
* |
145
|
|
|
* @return BinaryInterface |
146
|
|
|
*/ |
147
|
|
|
public function applyFilter(BinaryInterface $binary, $filter, array $runtimeConfig = []) |
148
|
|
|
{ |
149
|
|
|
$config = array_replace_recursive( |
150
|
|
|
$this->getFilterConfiguration()->get($filter), |
151
|
|
|
$runtimeConfig |
152
|
|
|
); |
153
|
|
|
|
154
|
|
|
return $this->apply($binary, $config); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @param BinaryInterface $binary |
159
|
|
|
* @param array $config |
160
|
|
|
* |
161
|
|
|
* @throws \InvalidArgumentException |
162
|
|
|
* |
163
|
|
|
* @return BinaryInterface |
164
|
|
|
*/ |
165
|
|
|
public function applyPostProcessors(BinaryInterface $binary, array $config): BinaryInterface |
166
|
|
|
{ |
167
|
|
|
foreach ($this->sanitizePostProcessors($config['post_processors'] ?? []) as $name => $options) { |
168
|
|
|
$binary = $this->postProcessors[$name]->process($binary, $options); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
return $binary; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @param BinaryInterface $binary |
176
|
|
|
* @param ImageInterface $image |
177
|
|
|
* @param array $config |
178
|
|
|
* |
179
|
|
|
* @return BinaryInterface |
180
|
|
|
*/ |
181
|
|
|
private function exportConfiguredImageBinary(BinaryInterface $binary, ImageInterface $image, array $config): BinaryInterface |
182
|
|
|
{ |
183
|
|
|
$options = [ |
184
|
|
|
'quality' => $config['quality'], |
185
|
|
|
]; |
186
|
|
|
|
187
|
|
|
if (isset($config['jpeg_quality'])) { |
188
|
|
|
$options['jpeg_quality'] = $config['jpeg_quality']; |
189
|
|
|
} |
190
|
|
|
if (isset($config['png_compression_level'])) { |
191
|
|
|
$options['png_compression_level'] = $config['png_compression_level']; |
192
|
|
|
} |
193
|
|
|
if (isset($config['png_compression_filter'])) { |
194
|
|
|
$options['png_compression_filter'] = $config['png_compression_filter']; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
if ('gif' === $binary->getFormat() && $config['animated']) { |
198
|
|
|
$options['animated'] = $config['animated']; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
$filteredFormat = $config['format'] ?? $binary->getFormat(); |
202
|
|
|
$filteredString = $image->get($filteredFormat, $options); |
203
|
|
|
|
204
|
|
|
$this->destroyImage($image); |
205
|
|
|
|
206
|
|
|
return new Binary( |
207
|
|
|
$filteredString, |
208
|
|
|
$filteredFormat === $binary->getFormat() ? $binary->getMimeType() : $this->mimeTypeGuesser->guess($filteredString), |
209
|
|
|
$filteredFormat |
210
|
|
|
); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @param array $filters |
215
|
|
|
* |
216
|
|
|
* @return array |
217
|
|
|
*/ |
218
|
|
View Code Duplication |
private function sanitizeFilters(array $filters): array |
|
|
|
|
219
|
|
|
{ |
220
|
|
|
$sanitized = array_filter($filters, function (string $name): bool { |
221
|
|
|
return isset($this->loaders[$name]); |
222
|
|
|
}, ARRAY_FILTER_USE_KEY); |
223
|
|
|
|
224
|
|
|
if (count($filters) !== count($sanitized)) { |
225
|
|
|
throw new \InvalidArgumentException(sprintf('Could not find filter(s): %s', implode(', ', array_map(function (string $name): string { |
226
|
|
|
return sprintf('"%s"', $name); |
227
|
|
|
}, array_diff(array_keys($filters), array_keys($sanitized)))))); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
return $sanitized; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* @param array $processors |
235
|
|
|
* |
236
|
|
|
* @return array |
237
|
|
|
*/ |
238
|
|
View Code Duplication |
private function sanitizePostProcessors(array $processors): array |
|
|
|
|
239
|
|
|
{ |
240
|
|
|
$sanitized = array_filter($processors, function (string $name): bool { |
241
|
|
|
return isset($this->postProcessors[$name]); |
242
|
|
|
}, ARRAY_FILTER_USE_KEY); |
243
|
|
|
|
244
|
|
|
if (count($processors) !== count($sanitized)) { |
245
|
|
|
throw new \InvalidArgumentException(sprintf('Could not find post processor(s): %s', implode(', ', array_map(function (string $name): string { |
246
|
|
|
return sprintf('"%s"', $name); |
247
|
|
|
}, array_diff(array_keys($processors), array_keys($sanitized)))))); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
return $sanitized; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* We are done with the image object so we can destruct the this because imagick keeps consuming memory if we don't. |
255
|
|
|
* See https://github.com/liip/LiipImagineBundle/pull/682 |
256
|
|
|
* |
257
|
|
|
* @param ImageInterface $image |
258
|
|
|
*/ |
259
|
|
|
private function destroyImage(ImageInterface $image): void |
260
|
|
|
{ |
261
|
|
|
if (method_exists($image, '__destruct')) { |
262
|
|
|
$image->__destruct(); |
|
|
|
|
263
|
|
|
} |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.