|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Imagecow; |
|
4
|
|
|
|
|
5
|
|
|
use Imagecow\Utils\Dimmensions; |
|
6
|
|
|
use Imagecow\Adapters\AdapterInterface; |
|
7
|
|
|
use Imagecow\Adapters\GdAdapter; |
|
8
|
|
|
use Imagecow\Adapters\ImagickAdapter; |
|
9
|
|
|
|
|
10
|
|
|
final class Image |
|
11
|
|
|
{ |
|
12
|
|
|
const CROP_ENTROPY = 'Entropy'; |
|
13
|
|
|
const CROP_BALANCED = 'Balanced'; |
|
14
|
|
|
const CROP_FACE = 'Face'; |
|
15
|
|
|
|
|
16
|
|
|
private $adapter; |
|
17
|
|
|
private $filename; |
|
18
|
|
|
private $clientHints = [ |
|
19
|
|
|
'dpr' => null, |
|
20
|
|
|
'viewport-width' => null, |
|
21
|
|
|
'width' => null, |
|
22
|
|
|
]; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* Create a new Imagecow instance from an image file. |
|
26
|
|
|
*/ |
|
27
|
|
|
public static function fromFile(string $filename, string $adapter = null): Image |
|
28
|
|
|
{ |
|
29
|
|
|
$adapter = self::getAdapterClass($adapter); |
|
30
|
|
|
$image = new static($adapter::createFromFile($filename), $filename); |
|
31
|
|
|
|
|
32
|
|
|
if ($image->getMimeType() !== 'image/gif') { |
|
33
|
|
|
return $image; |
|
34
|
|
|
} |
|
35
|
|
|
|
|
36
|
|
|
$stream = fopen($filename, 'rb'); |
|
37
|
|
|
|
|
38
|
|
|
if (self::isAnimatedGif($stream)) { |
|
39
|
|
|
$image->adapter->setAnimated(true); |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
|
|
fclose($stream); |
|
43
|
|
|
|
|
44
|
|
|
return $image; |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* Create a new Imagecow instance from a string. |
|
49
|
|
|
*/ |
|
50
|
|
|
public static function fromString(string $string, string $adapter = null): Image |
|
51
|
|
|
{ |
|
52
|
|
|
$adapter = self::getAdapterClass($adapter); |
|
53
|
|
|
$image = new static($adapter::createFromString($string)); |
|
54
|
|
|
|
|
55
|
|
|
if ($image->getMimeType() !== 'image/gif') { |
|
56
|
|
|
return $image; |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
$stream = fopen('php://temp', 'r+'); |
|
60
|
|
|
|
|
61
|
|
|
fwrite($stream, $string); |
|
62
|
|
|
rewind($stream); |
|
63
|
|
|
|
|
64
|
|
|
if (self::isAnimatedGif($stream)) { |
|
65
|
|
|
$image->adapter->setAnimated(true); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
fclose($stream); |
|
69
|
|
|
|
|
70
|
|
|
return $image; |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
/** |
|
74
|
|
|
* Constructor. |
|
75
|
|
|
*/ |
|
76
|
|
|
public function __construct(AdapterInterface $adapter, string $filename = null) |
|
77
|
|
|
{ |
|
78
|
|
|
$this->adapter = $adapter; |
|
79
|
|
|
$this->filename = $filename; |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* Set the available client hints. |
|
84
|
|
|
*/ |
|
85
|
|
|
public function setClientHints(array $clientHints): self |
|
86
|
|
|
{ |
|
87
|
|
|
$normalize = []; |
|
88
|
|
|
|
|
89
|
|
|
foreach ($clientHints as $key => $value) { |
|
90
|
|
|
$normalize[strtolower($key)] = is_null($value) ? null : (float) $value; |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
if (array_diff_key($normalize, $this->clientHints)) { |
|
94
|
|
|
throw new \InvalidArgumentException('Invalid client hints'); |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
$this->clientHints = array_replace($this->clientHints, $normalize); |
|
98
|
|
|
|
|
99
|
|
|
return $this; |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* Set a default background color used to fill in some transformation functions |
|
104
|
|
|
* in rgb format, for example: array(0,127,34) |
|
105
|
|
|
*/ |
|
106
|
|
|
public function setBackground(array $background): self |
|
107
|
|
|
{ |
|
108
|
|
|
$this->adapter->setBackground($background); |
|
109
|
|
|
|
|
110
|
|
|
return $this; |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* Get the fixed size according with the client hints. |
|
115
|
|
|
*/ |
|
116
|
|
|
private function calculateClientSize(int $width, int $height): array |
|
117
|
|
|
{ |
|
118
|
|
View Code Duplication |
if ($this->clientHints['width'] !== null && $this->clientHints['width'] < $width) { |
|
|
|
|
|
|
119
|
|
|
return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['width'], null); |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
View Code Duplication |
if ($this->clientHints['viewport-width'] !== null && $this->clientHints['viewport-width'] < $width) { |
|
|
|
|
|
|
123
|
|
|
return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['viewport-width'], null); |
|
124
|
|
|
} |
|
125
|
|
|
|
|
126
|
|
|
if ($this->clientHints['dpr'] !== null) { |
|
127
|
|
|
$width *= $this->clientHints['dpr']; |
|
128
|
|
|
$height *= $this->clientHints['dpr']; |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
return [$width, $height]; |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
/** |
|
135
|
|
|
* Inverts the image vertically. |
|
136
|
|
|
*/ |
|
137
|
|
|
public function flip(): self |
|
138
|
|
|
{ |
|
139
|
|
|
$this->adapter->flip(); |
|
140
|
|
|
|
|
141
|
|
|
return $this; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
/** |
|
145
|
|
|
* Inverts the image horizontally. |
|
146
|
|
|
*/ |
|
147
|
|
|
public function flop(): self |
|
148
|
|
|
{ |
|
149
|
|
|
$this->adapter->flop(); |
|
150
|
|
|
|
|
151
|
|
|
return $this; |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
/** |
|
155
|
|
|
* Saves the image in a file (or override the previous opened file). |
|
156
|
|
|
*/ |
|
157
|
|
|
public function save(string $filename = null): self |
|
158
|
|
|
{ |
|
159
|
|
|
$this->adapter->save($filename ?: $this->filename); |
|
160
|
|
|
|
|
161
|
|
|
return $this; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
public function getAdapter(): AdapterInterface |
|
165
|
|
|
{ |
|
166
|
|
|
return $this->adapter; |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* Gets the image data in a string. |
|
171
|
|
|
*/ |
|
172
|
|
|
public function getString(): string |
|
173
|
|
|
{ |
|
174
|
|
|
return $this->adapter->getString(); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
/** |
|
178
|
|
|
* Gets the mime type of the image. |
|
179
|
|
|
*/ |
|
180
|
|
|
public function getMimeType(): string |
|
181
|
|
|
{ |
|
182
|
|
|
return $this->adapter->getMimeType(); |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
/** |
|
186
|
|
|
* Gets the width of the image in pixels. |
|
187
|
|
|
*/ |
|
188
|
|
|
public function getWidth(): int |
|
189
|
|
|
{ |
|
190
|
|
|
return $this->adapter->getWidth(); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
/** |
|
194
|
|
|
* Gets the height of the image in pixels. |
|
195
|
|
|
*/ |
|
196
|
|
|
public function getHeight(): int |
|
197
|
|
|
{ |
|
198
|
|
|
return $this->adapter->getHeight(); |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
/** |
|
202
|
|
|
* Converts the image to other format (png, jpg, gif or webp). |
|
203
|
|
|
*/ |
|
204
|
|
|
public function format($format): self |
|
205
|
|
|
{ |
|
206
|
|
|
$this->adapter->format($format); |
|
207
|
|
|
|
|
208
|
|
|
return $this; |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* Resizes the image maintaining the proportion (A 800x600 image resized to 400x400 becomes to 400x300). |
|
213
|
|
|
* |
|
214
|
|
|
* @param int|string $width The max width of the image. It can be a number (pixels) or percentaje |
|
215
|
|
|
* @param int|string $height The max height of the image. It can be a number (pixels) or percentaje |
|
216
|
|
|
*/ |
|
217
|
|
|
public function resize($width, $height = 0, bool $cover = false): self |
|
218
|
|
|
{ |
|
219
|
|
|
$imageWidth = $this->getWidth(); |
|
220
|
|
|
$imageHeight = $this->getHeight(); |
|
221
|
|
|
|
|
222
|
|
|
$width = Dimmensions::getIntegerValue('x', $width, $imageWidth); |
|
223
|
|
|
$height = Dimmensions::getIntegerValue('y', $height, $imageHeight); |
|
224
|
|
|
|
|
225
|
|
|
list($width, $height) = Dimmensions::getResizeDimmensions($imageWidth, $imageHeight, $width, $height, $cover); |
|
226
|
|
|
list($width, $height) = $this->calculateClientSize($width, $height); |
|
227
|
|
|
|
|
228
|
|
|
if ($width >= $imageWidth && !$cover) { |
|
229
|
|
|
return $this; |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
$this->adapter->resize($width, $height); |
|
233
|
|
|
|
|
234
|
|
|
return $this; |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
/** |
|
238
|
|
|
* Crops the image. |
|
239
|
|
|
* |
|
240
|
|
|
* @param int|string $width The new width of the image. It can be a number (pixels) or percentaje |
|
241
|
|
|
* @param int|string $height The new height of the image. It can be a number (pixels) or percentaje |
|
242
|
|
|
* @param int|string $x The "x" position to crop. It can be number (pixels), percentaje, [left, center, right] or one of the Image::CROP_* constants |
|
243
|
|
|
* @param int|string $y The "y" position to crop. It can be number (pixels), percentaje or [top, middle, bottom] |
|
244
|
|
|
*/ |
|
245
|
|
|
public function crop($width, $height, $x = 'center', $y = 'middle'): self |
|
246
|
|
|
{ |
|
247
|
|
|
$imageWidth = $this->getWidth(); |
|
248
|
|
|
$imageHeight = $this->getHeight(); |
|
249
|
|
|
|
|
250
|
|
|
$width = Dimmensions::getIntegerValue('x', $width, $imageWidth); |
|
251
|
|
|
$height = Dimmensions::getIntegerValue('y', $height, $imageHeight); |
|
252
|
|
|
|
|
253
|
|
|
list($width, $height) = $this->calculateClientSize($width, $height); |
|
254
|
|
|
|
|
255
|
|
|
if (in_array($x, [self::CROP_BALANCED, self::CROP_ENTROPY, self::CROP_FACE], true)) { |
|
256
|
|
|
list($x, $y) = $this->adapter->getCropOffsets($width, $height, $x); |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
$x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth); |
|
260
|
|
|
$y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight); |
|
261
|
|
|
|
|
262
|
|
|
$this->adapter->crop($width, $height, $x, $y); |
|
263
|
|
|
|
|
264
|
|
|
return $this; |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
/** |
|
268
|
|
|
* Adjust the image to the given dimmensions. Resizes and crops the image maintaining the proportions. |
|
269
|
|
|
* |
|
270
|
|
|
* @param int|string $width The new width in number (pixels) or percentaje |
|
271
|
|
|
* @param int|string $height The new height in number (pixels) or percentaje |
|
272
|
|
|
* @param int|string $x The "x" position to crop. It can be number (pixels), percentaje, [left, center, right] or one of the Image::CROP_* constants |
|
273
|
|
|
* @param int|string $y The "y" position to crop. It can be number (pixels), percentaje or [top, middle, bottom] |
|
274
|
|
|
*/ |
|
275
|
|
|
public function resizeCrop($width, $height, $x = 'center', $y = 'middle'): self |
|
276
|
|
|
{ |
|
277
|
|
|
$this->resize($width, $height, true); |
|
278
|
|
|
$this->crop($width, $height, $x, $y); |
|
279
|
|
|
|
|
280
|
|
|
return $this; |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
/** |
|
284
|
|
|
* Rotates the image (in degrees, anticlockwise). |
|
285
|
|
|
*/ |
|
286
|
|
|
public function rotate(int $angle): self |
|
287
|
|
|
{ |
|
288
|
|
|
if (($angle = intval($angle)) !== 0) { |
|
289
|
|
|
$this->adapter->rotate($angle); |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
return $this; |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* Apply blur to image |
|
297
|
|
|
*/ |
|
298
|
|
|
public function blur(int $loops = 4): self |
|
299
|
|
|
{ |
|
300
|
|
|
$this->adapter->blur($loops); |
|
301
|
|
|
|
|
302
|
|
|
return $this; |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
/** |
|
306
|
|
|
* Define the image compression quality for jpg images (from 0 to 100). |
|
307
|
|
|
*/ |
|
308
|
|
|
public function quality(int $quality): self |
|
309
|
|
|
{ |
|
310
|
|
|
$quality = intval($quality); |
|
311
|
|
|
|
|
312
|
|
|
if ($quality < 0) { |
|
313
|
|
|
$quality = 0; |
|
314
|
|
|
} elseif ($quality > 100) { |
|
315
|
|
|
$quality = 100; |
|
316
|
|
|
} |
|
317
|
|
|
|
|
318
|
|
|
$this->adapter->setCompressionQuality($quality); |
|
319
|
|
|
|
|
320
|
|
|
return $this; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
/** |
|
324
|
|
|
* Add a watermark to current image. |
|
325
|
|
|
* |
|
326
|
|
|
* @param mixed $x Horizontal position |
|
327
|
|
|
* @param mixed $y Vertical position |
|
328
|
|
|
*/ |
|
329
|
|
|
public function watermark(Image $image, $x = 'right', $y = 'bottom'): self |
|
330
|
|
|
{ |
|
331
|
|
|
$imageWidth = $this->getWidth(); |
|
332
|
|
|
$imageHeight = $this->getHeight(); |
|
333
|
|
|
|
|
334
|
|
|
$width = $image->getWidth(); |
|
335
|
|
|
$height = $image->getHeight(); |
|
336
|
|
|
|
|
337
|
|
|
$x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth); |
|
338
|
|
|
$y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight); |
|
339
|
|
|
|
|
340
|
|
|
$this->adapter->watermark($image->getImage(), $x, $y); |
|
|
|
|
|
|
341
|
|
|
|
|
342
|
|
|
return $this; |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
/** |
|
346
|
|
|
* Add opacity to image from 0 (transparent) to 100 (opaque). |
|
347
|
|
|
*/ |
|
348
|
|
|
public function opacity(int $opacity): self |
|
349
|
|
|
{ |
|
350
|
|
|
$this->adapter->opacity($opacity); |
|
351
|
|
|
|
|
352
|
|
|
return $this; |
|
353
|
|
|
} |
|
354
|
|
|
|
|
355
|
|
|
/** |
|
356
|
|
|
* Set the image progressive or not |
|
357
|
|
|
*/ |
|
358
|
|
|
public function progressive(bool $progressive = true): self |
|
359
|
|
|
{ |
|
360
|
|
|
$this->adapter->setProgressive((bool) $progressive); |
|
361
|
|
|
|
|
362
|
|
|
return $this; |
|
363
|
|
|
} |
|
364
|
|
|
|
|
365
|
|
|
/** |
|
366
|
|
|
* Reads the EXIF data from a JPEG and returns an associative array |
|
367
|
|
|
* (requires the exif PHP extension enabled). |
|
368
|
|
|
* |
|
369
|
|
|
* @param null|string $key |
|
370
|
|
|
* |
|
371
|
|
|
* @return null|array |
|
372
|
|
|
*/ |
|
373
|
|
|
public function getExifData($key = null) |
|
374
|
|
|
{ |
|
375
|
|
|
if ($this->filename !== null && ($this->getMimeType() === 'image/jpeg')) { |
|
376
|
|
|
$exif = exif_read_data($this->filename); |
|
377
|
|
|
|
|
378
|
|
|
if ($key !== null) { |
|
379
|
|
|
return isset($exif[$key]) ? $exif[$key] : null; |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
return $exif; |
|
383
|
|
|
} |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
/** |
|
387
|
|
|
* Transform the image executing various operations of crop, resize, resizeCrop and format. |
|
388
|
|
|
*/ |
|
389
|
|
|
public function transform(string $operations = null): self |
|
390
|
|
|
{ |
|
391
|
|
|
//No transform operations, resize to fix the client size |
|
392
|
|
|
if (empty($operations)) { |
|
393
|
|
|
return $this->resize($this->getWidth(), $this->getHeight()); |
|
394
|
|
|
} |
|
395
|
|
|
|
|
396
|
|
|
$operations = self::parseOperations($operations); |
|
397
|
|
|
|
|
398
|
|
|
foreach ($operations as $operation) { |
|
399
|
|
|
switch ($operation['function']) { |
|
400
|
|
|
case 'crop': |
|
401
|
|
|
case 'resizecrop': |
|
402
|
|
|
if (empty($operation['params'][2])) { |
|
403
|
|
|
break; |
|
404
|
|
|
} |
|
405
|
|
|
|
|
406
|
|
|
switch ($operation['params'][2]) { |
|
407
|
|
|
case 'CROP_ENTROPY': |
|
408
|
|
|
$operation['params'][2] = self::CROP_ENTROPY; |
|
409
|
|
|
break; |
|
410
|
|
|
|
|
411
|
|
|
case 'CROP_BALANCED': |
|
412
|
|
|
$operation['params'][2] = self::CROP_BALANCED; |
|
413
|
|
|
break; |
|
414
|
|
|
|
|
415
|
|
|
case 'CROP_FACE': |
|
416
|
|
|
$operation['params'][2] = self::CROP_FACE; |
|
417
|
|
|
break; |
|
418
|
|
|
} |
|
419
|
|
|
|
|
420
|
|
|
break; |
|
421
|
|
|
} |
|
422
|
|
|
|
|
423
|
|
|
call_user_func_array([$this, $operation['function']], $operation['params']); |
|
424
|
|
|
} |
|
425
|
|
|
|
|
426
|
|
|
return $this; |
|
427
|
|
|
} |
|
428
|
|
|
|
|
429
|
|
|
/** |
|
430
|
|
|
* Send the HTTP header with the content-type, output the image data and die. |
|
431
|
|
|
*/ |
|
432
|
|
View Code Duplication |
public function show() |
|
433
|
|
|
{ |
|
434
|
|
|
if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) { |
|
435
|
|
|
header('Content-Type: '.$mimetype); |
|
436
|
|
|
die($string); |
|
437
|
|
|
} |
|
438
|
|
|
} |
|
439
|
|
|
|
|
440
|
|
|
/** |
|
441
|
|
|
* Returns the image as base64 url. |
|
442
|
|
|
* |
|
443
|
|
|
* @return string|null |
|
444
|
|
|
*/ |
|
445
|
|
View Code Duplication |
public function base64() |
|
446
|
|
|
{ |
|
447
|
|
|
if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) { |
|
448
|
|
|
$string = base64_encode($string); |
|
449
|
|
|
|
|
450
|
|
|
return "data:{$mimetype};base64,{$string}"; |
|
451
|
|
|
} |
|
452
|
|
|
} |
|
453
|
|
|
|
|
454
|
|
|
/** |
|
455
|
|
|
* Auto-rotate the image according with its exif data |
|
456
|
|
|
* Taken from: http://php.net/manual/en/function.exif-read-data.php#76964. |
|
457
|
|
|
*/ |
|
458
|
|
|
public function autoRotate(): self |
|
459
|
|
|
{ |
|
460
|
|
|
switch ($this->getExifData('Orientation')) { |
|
461
|
|
|
case 2: |
|
462
|
|
|
$this->flop(); |
|
463
|
|
|
break; |
|
464
|
|
|
|
|
465
|
|
|
case 3: |
|
466
|
|
|
$this->rotate(180); |
|
467
|
|
|
break; |
|
468
|
|
|
|
|
469
|
|
|
case 4: |
|
470
|
|
|
$this->flip(); |
|
471
|
|
|
break; |
|
472
|
|
|
|
|
473
|
|
|
case 5: |
|
474
|
|
|
$this->flip()->rotate(90); |
|
475
|
|
|
break; |
|
476
|
|
|
|
|
477
|
|
|
case 6: |
|
478
|
|
|
$this->rotate(90); |
|
479
|
|
|
break; |
|
480
|
|
|
|
|
481
|
|
|
case 7: |
|
482
|
|
|
$this->flop()->rotate(90); |
|
483
|
|
|
break; |
|
484
|
|
|
|
|
485
|
|
|
case 8: |
|
486
|
|
|
$this->rotate(-90); |
|
487
|
|
|
break; |
|
488
|
|
|
} |
|
489
|
|
|
|
|
490
|
|
|
return $this; |
|
491
|
|
|
} |
|
492
|
|
|
|
|
493
|
|
|
/** |
|
494
|
|
|
* Check whether the image is an animated gif. |
|
495
|
|
|
* Copied from: https://github.com/Sybio/GifFrameExtractor/blob/master/src/GifFrameExtractor/GifFrameExtractor.php#L181. |
|
496
|
|
|
* |
|
497
|
|
|
* @param resource A stream pointer opened by fopen() |
|
498
|
|
|
*/ |
|
499
|
|
|
private static function isAnimatedGif($stream): bool |
|
500
|
|
|
{ |
|
501
|
|
|
$count = 0; |
|
502
|
|
|
|
|
503
|
|
|
while (!feof($stream) && $count < 2) { |
|
504
|
|
|
$chunk = fread($stream, 1024 * 100); //read 100kb at a time |
|
505
|
|
|
$count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); |
|
506
|
|
|
} |
|
507
|
|
|
|
|
508
|
|
|
return $count > 1; |
|
509
|
|
|
} |
|
510
|
|
|
|
|
511
|
|
|
/** |
|
512
|
|
|
* Converts a string with operations in an array. |
|
513
|
|
|
*/ |
|
514
|
|
|
private static function parseOperations(string $operations): array |
|
515
|
|
|
{ |
|
516
|
|
|
$valid_operations = ['resize', 'resizecrop', 'crop', 'format', 'quality']; |
|
517
|
|
|
$operations = explode('|', str_replace(' ', '', $operations)); |
|
518
|
|
|
$return = []; |
|
519
|
|
|
|
|
520
|
|
|
foreach ($operations as $operations) { |
|
521
|
|
|
$params = explode(',', $operations); |
|
522
|
|
|
$function = strtolower(trim(array_shift($params))); |
|
523
|
|
|
|
|
524
|
|
|
if (!in_array($function, $valid_operations, true)) { |
|
525
|
|
|
throw new ImageException("The transform function '{$function}' is not valid"); |
|
526
|
|
|
} |
|
527
|
|
|
|
|
528
|
|
|
$return[] = [ |
|
529
|
|
|
'function' => $function, |
|
530
|
|
|
'params' => $params, |
|
531
|
|
|
]; |
|
532
|
|
|
} |
|
533
|
|
|
|
|
534
|
|
|
return $return; |
|
535
|
|
|
} |
|
536
|
|
|
|
|
537
|
|
|
/** |
|
538
|
|
|
* Checks the library to use and returns its class. |
|
539
|
|
|
* |
|
540
|
|
|
* @throws ImageException if the image library does not exists. |
|
541
|
|
|
*/ |
|
542
|
|
|
private static function getAdapterClass(string $adapter = null): string |
|
543
|
|
|
{ |
|
544
|
|
|
if (!$adapter) { |
|
|
|
|
|
|
545
|
|
|
return ImagickAdapter::checkCompatibility() ? ImagickAdapter::class : GdAdapter::class; |
|
546
|
|
|
} |
|
547
|
|
|
|
|
548
|
|
|
if (!class_exists($adapter)) { |
|
549
|
|
|
throw new ImageException(sprintf('The class %s does not exists', $adapter)); |
|
550
|
|
|
} |
|
551
|
|
|
|
|
552
|
|
|
if (!$adapter::checkCompatibility()) { |
|
553
|
|
|
throw new ImageException(sprintf('The class %s cannot be used in this computer', $adapter)); |
|
554
|
|
|
} |
|
555
|
|
|
|
|
556
|
|
|
return $adapter; |
|
557
|
|
|
} |
|
558
|
|
|
} |
|
559
|
|
|
|
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.