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 clientHints(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 background(array $background): self |
107
|
|
|
{ |
108
|
|
|
$this->adapter->setBackground($background); |
109
|
|
|
|
110
|
|
|
return $this; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Inverts the image vertically. |
115
|
|
|
*/ |
116
|
|
|
public function flip(): self |
117
|
|
|
{ |
118
|
|
|
$this->adapter->flip(); |
119
|
|
|
|
120
|
|
|
return $this; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Inverts the image horizontally. |
125
|
|
|
*/ |
126
|
|
|
public function flop(): self |
127
|
|
|
{ |
128
|
|
|
$this->adapter->flop(); |
129
|
|
|
|
130
|
|
|
return $this; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Saves the image in a file (or override the previous opened file). |
135
|
|
|
*/ |
136
|
|
|
public function save(string $filename = null): self |
137
|
|
|
{ |
138
|
|
|
$this->adapter->save($filename ?: $this->filename); |
139
|
|
|
|
140
|
|
|
return $this; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
public function getAdapter(): AdapterInterface |
144
|
|
|
{ |
145
|
|
|
return $this->adapter; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Gets the image data in a string. |
150
|
|
|
*/ |
151
|
|
|
public function getString(): string |
152
|
|
|
{ |
153
|
|
|
return $this->adapter->getString(); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Gets the mime type of the image. |
158
|
|
|
*/ |
159
|
|
|
public function getMimeType(): string |
160
|
|
|
{ |
161
|
|
|
return $this->adapter->getMimeType(); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Gets the width of the image in pixels. |
166
|
|
|
*/ |
167
|
|
|
public function getWidth(int $percentage = null): int |
168
|
|
|
{ |
169
|
|
|
$width = $this->adapter->getWidth(); |
170
|
|
|
|
171
|
|
|
return isset($percentage) ? ($percentage / 100) * $width : $width; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Gets the height of the image in pixels. |
176
|
|
|
*/ |
177
|
|
|
public function getHeight(int $percentage = null): int |
178
|
|
|
{ |
179
|
|
|
$height = $this->adapter->getHeight(); |
180
|
|
|
|
181
|
|
|
return isset($percentage) ? ($percentage / 100) * $height : $height; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Converts the image to other format (png, jpg, gif or webp). |
186
|
|
|
*/ |
187
|
|
|
public function format($format): self |
188
|
|
|
{ |
189
|
|
|
$this->adapter->format($format); |
190
|
|
|
|
191
|
|
|
return $this; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Resizes the image maintaining the proportion (A 800x600 image resized to 400x400 becomes to 400x300). |
196
|
|
|
* |
197
|
|
|
* @param int|string $width The max width of the image. It can be a number (pixels) or percentaje |
198
|
|
|
* @param int|string $height The max height of the image. It can be a number (pixels) or percentaje |
199
|
|
|
*/ |
200
|
|
|
public function resize($width, $height = 0, bool $cover = false): self |
201
|
|
|
{ |
202
|
|
|
$imageWidth = $this->getWidth(); |
203
|
|
|
$imageHeight = $this->getHeight(); |
204
|
|
|
|
205
|
|
|
if ($percentage = self::getPercentage($width)) { |
206
|
|
|
$width = $this->getWidth($percentage); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
if ($percentage = self::getPercentage($height)) { |
210
|
|
|
$height = $this->getHeight($percentage); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
list($width, $height) = Dimmensions::getResizeDimmensions($imageWidth, $imageHeight, $width, $height, $cover); |
214
|
|
|
list($width, $height) = $this->calculateClientSize($width, $height); |
215
|
|
|
|
216
|
|
|
if ($width >= $imageWidth && !$cover) { |
217
|
|
|
return $this; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
$this->adapter->resize($width, $height); |
221
|
|
|
|
222
|
|
|
return $this; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Crops the image. |
227
|
|
|
* |
228
|
|
|
* @param int|string $width The new width of the image. It can be a number (pixels) or percentaje |
229
|
|
|
* @param int|string $height The new height of the image. It can be a number (pixels) or percentaje |
230
|
|
|
* @param int|string $x The "x" position to crop. It can be number (pixels), percentaje or one of the Image::CROP_* constants |
231
|
|
|
* @param int|string $y The "y" position to crop. It can be number (pixels) or percentaje |
232
|
|
|
*/ |
233
|
|
|
public function crop($width, $height, $x = '50%', $y = '50%'): self |
234
|
|
|
{ |
235
|
|
|
$imageWidth = $this->getWidth(); |
236
|
|
|
$imageHeight = $this->getHeight(); |
237
|
|
|
|
238
|
|
|
if ($percentage = self::getPercentage($width)) { |
239
|
|
|
$width = $this->getWidth($percentage); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
if ($percentage = self::getPercentage($height)) { |
243
|
|
|
$height = $this->getHeight($percentage); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
list($width, $height) = $this->calculateClientSize($width, $height); |
247
|
|
|
|
248
|
|
|
if (in_array($x, [self::CROP_BALANCED, self::CROP_ENTROPY, self::CROP_FACE], true)) { |
249
|
|
|
list($x, $y) = $this->adapter->getCropOffsets($width, $height, $x); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
$x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth); |
253
|
|
|
$y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight); |
254
|
|
|
|
255
|
|
|
$this->adapter->crop($width, $height, $x, $y); |
256
|
|
|
|
257
|
|
|
return $this; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Adjust the image to the given dimmensions. Resizes and crops the image maintaining the proportions. |
262
|
|
|
* |
263
|
|
|
* @param int|string $width The new width in number (pixels) or percentaje |
264
|
|
|
* @param int|string $height The new height in number (pixels) or percentaje |
265
|
|
|
* @param int|string $x The "x" position to crop. It can be number (pixels), percentaje or one of the Image::CROP_* constants |
266
|
|
|
* @param int|string $y The "y" position to crop. It can be number (pixels) or percentaje |
267
|
|
|
*/ |
268
|
|
|
public function resizeCrop($width, $height, $x = '50%', $y = '50%'): self |
269
|
|
|
{ |
270
|
|
|
$this->resize($width, $height, true); |
271
|
|
|
$this->crop($width, $height, $x, $y); |
272
|
|
|
|
273
|
|
|
return $this; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Rotates the image (in degrees, anticlockwise). |
278
|
|
|
*/ |
279
|
|
|
public function rotate(int $angle): self |
280
|
|
|
{ |
281
|
|
|
if (($angle = intval($angle)) !== 0) { |
282
|
|
|
$this->adapter->rotate($angle); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
return $this; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Apply blur to image |
290
|
|
|
*/ |
291
|
|
|
public function blur(int $loops = 4): self |
292
|
|
|
{ |
293
|
|
|
$this->adapter->blur($loops); |
294
|
|
|
|
295
|
|
|
return $this; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Define the image compression quality for jpg images (from 0 to 100). |
300
|
|
|
*/ |
301
|
|
|
public function quality(int $quality): self |
302
|
|
|
{ |
303
|
|
|
$quality = intval($quality); |
304
|
|
|
|
305
|
|
|
if ($quality < 0) { |
306
|
|
|
$quality = 0; |
307
|
|
|
} elseif ($quality > 100) { |
308
|
|
|
$quality = 100; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$this->adapter->setCompressionQuality($quality); |
312
|
|
|
|
313
|
|
|
return $this; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Add a watermark to current image. |
318
|
|
|
* |
319
|
|
|
* @param string|int $x Horizontal position |
320
|
|
|
* @param string|int $y Vertical position |
321
|
|
|
*/ |
322
|
|
|
public function watermark(Image $image, $x = '100%', $y = '100%'): self |
323
|
|
|
{ |
324
|
|
|
$imageWidth = $this->getWidth(); |
325
|
|
|
$imageHeight = $this->getHeight(); |
326
|
|
|
|
327
|
|
|
$width = $image->getWidth(); |
328
|
|
|
$height = $image->getHeight(); |
329
|
|
|
|
330
|
|
|
$x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth); |
331
|
|
|
$y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight); |
332
|
|
|
|
333
|
|
|
$this->adapter->watermark($image->getImage(), $x, $y); |
|
|
|
|
334
|
|
|
|
335
|
|
|
return $this; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Add opacity to image from 0 (transparent) to 100 (opaque). |
340
|
|
|
*/ |
341
|
|
|
public function opacity(int $opacity): self |
342
|
|
|
{ |
343
|
|
|
$this->adapter->opacity($opacity); |
344
|
|
|
|
345
|
|
|
return $this; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Set the image progressive or not |
350
|
|
|
*/ |
351
|
|
|
public function progressive(bool $progressive = true): self |
352
|
|
|
{ |
353
|
|
|
$this->adapter->setProgressive((bool) $progressive); |
354
|
|
|
|
355
|
|
|
return $this; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Reads the EXIF data from a JPEG and returns an associative array |
360
|
|
|
* (requires the exif PHP extension enabled). |
361
|
|
|
* |
362
|
|
|
* @param null|string $key |
363
|
|
|
* |
364
|
|
|
* @return null|array |
365
|
|
|
*/ |
366
|
|
|
public function getExifData($key = null) |
367
|
|
|
{ |
368
|
|
|
if ($this->filename !== null && ($this->getMimeType() === 'image/jpeg')) { |
369
|
|
|
$exif = exif_read_data($this->filename); |
370
|
|
|
|
371
|
|
|
if ($key !== null) { |
372
|
|
|
return isset($exif[$key]) ? $exif[$key] : null; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
return $exif; |
376
|
|
|
} |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* Transform the image executing various operations of crop, resize, resizeCrop and format. |
381
|
|
|
*/ |
382
|
|
|
public function transform(string $operations = null): self |
383
|
|
|
{ |
384
|
|
|
//No transform operations, resize to fix the client size |
385
|
|
|
if (empty($operations)) { |
386
|
|
|
return $this->resize($this->getWidth(), $this->getHeight()); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
$operations = self::parseOperations($operations); |
390
|
|
|
|
391
|
|
|
foreach ($operations as $operation) { |
392
|
|
|
switch ($operation['function']) { |
393
|
|
|
case 'crop': |
394
|
|
|
case 'resizecrop': |
395
|
|
|
if (empty($operation['params'][2])) { |
396
|
|
|
break; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
switch ($operation['params'][2]) { |
400
|
|
|
case 'CROP_ENTROPY': |
401
|
|
|
$operation['params'][2] = self::CROP_ENTROPY; |
402
|
|
|
break; |
403
|
|
|
|
404
|
|
|
case 'CROP_BALANCED': |
405
|
|
|
$operation['params'][2] = self::CROP_BALANCED; |
406
|
|
|
break; |
407
|
|
|
|
408
|
|
|
case 'CROP_FACE': |
409
|
|
|
$operation['params'][2] = self::CROP_FACE; |
410
|
|
|
break; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
break; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
call_user_func_array([$this, $operation['function']], $operation['params']); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
return $this; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
/** |
423
|
|
|
* Send the HTTP header with the content-type, output the image data and die. |
424
|
|
|
*/ |
425
|
|
|
public function show() |
426
|
|
|
{ |
427
|
|
|
if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) { |
428
|
|
|
header('Content-Type: '.$mimetype); |
429
|
|
|
die($string); |
430
|
|
|
} |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Returns the image as base64 url. |
435
|
|
|
* |
436
|
|
|
* @return string|null |
437
|
|
|
*/ |
438
|
|
|
public function base64() |
439
|
|
|
{ |
440
|
|
|
if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) { |
441
|
|
|
$string = base64_encode($string); |
442
|
|
|
|
443
|
|
|
return "data:{$mimetype};base64,{$string}"; |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Auto-rotate the image according with its exif data |
449
|
|
|
* Taken from: http://php.net/manual/en/function.exif-read-data.php#76964. |
450
|
|
|
*/ |
451
|
|
|
public function autoRotate(): self |
452
|
|
|
{ |
453
|
|
|
switch ($this->getExifData('Orientation')) { |
454
|
|
|
case 2: |
455
|
|
|
$this->flop(); |
456
|
|
|
break; |
457
|
|
|
|
458
|
|
|
case 3: |
459
|
|
|
$this->rotate(180); |
460
|
|
|
break; |
461
|
|
|
|
462
|
|
|
case 4: |
463
|
|
|
$this->flip(); |
464
|
|
|
break; |
465
|
|
|
|
466
|
|
|
case 5: |
467
|
|
|
$this->flip()->rotate(90); |
468
|
|
|
break; |
469
|
|
|
|
470
|
|
|
case 6: |
471
|
|
|
$this->rotate(90); |
472
|
|
|
break; |
473
|
|
|
|
474
|
|
|
case 7: |
475
|
|
|
$this->flop()->rotate(90); |
476
|
|
|
break; |
477
|
|
|
|
478
|
|
|
case 8: |
479
|
|
|
$this->rotate(-90); |
480
|
|
|
break; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
return $this; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Get the fixed size according with the client hints. |
488
|
|
|
*/ |
489
|
|
|
private function calculateClientSize(int $width, int $height): array |
490
|
|
|
{ |
491
|
|
|
if ($this->clientHints['width'] !== null && $this->clientHints['width'] < $width) { |
492
|
|
|
return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['width'], null); |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
if ($this->clientHints['viewport-width'] !== null && $this->clientHints['viewport-width'] < $width) { |
496
|
|
|
return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['viewport-width'], null); |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
if ($this->clientHints['dpr'] !== null) { |
500
|
|
|
$width *= $this->clientHints['dpr']; |
501
|
|
|
$height *= $this->clientHints['dpr']; |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
return [$width, $height]; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Check whether the image is an animated gif. |
509
|
|
|
* Copied from: https://github.com/Sybio/GifFrameExtractor/blob/master/src/GifFrameExtractor/GifFrameExtractor.php#L181. |
510
|
|
|
* |
511
|
|
|
* @param resource A stream pointer opened by fopen() |
512
|
|
|
*/ |
513
|
|
|
private static function isAnimatedGif($stream): bool |
514
|
|
|
{ |
515
|
|
|
$count = 0; |
516
|
|
|
|
517
|
|
|
while (!feof($stream) && $count < 2) { |
518
|
|
|
$chunk = fread($stream, 1024 * 100); //read 100kb at a time |
519
|
|
|
$count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
return $count > 1; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Converts a string with operations in an array. |
527
|
|
|
*/ |
528
|
|
|
private static function parseOperations(string $operations): array |
529
|
|
|
{ |
530
|
|
|
$valid_operations = ['resize', 'resizecrop', 'crop', 'format', 'quality']; |
531
|
|
|
$operations = explode('|', str_replace(' ', '', $operations)); |
532
|
|
|
$return = []; |
533
|
|
|
|
534
|
|
|
foreach ($operations as $operations) { |
535
|
|
|
$params = explode(',', $operations); |
536
|
|
|
$function = strtolower(trim(array_shift($params))); |
537
|
|
|
|
538
|
|
|
if (!in_array($function, $valid_operations, true)) { |
539
|
|
|
throw new ImageException("The transform function '{$function}' is not valid"); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
$return[] = [ |
543
|
|
|
'function' => $function, |
544
|
|
|
'params' => $params, |
545
|
|
|
]; |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
return $return; |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
/** |
552
|
|
|
* Checks the library to use and returns its class. |
553
|
|
|
* |
554
|
|
|
* @throws ImageException if the image library does not exists. |
555
|
|
|
*/ |
556
|
|
|
private static function getAdapterClass(string $adapter = null): string |
557
|
|
|
{ |
558
|
|
|
if (!$adapter) { |
|
|
|
|
559
|
|
|
return ImagickAdapter::checkCompatibility() ? ImagickAdapter::class : GdAdapter::class; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
if (!class_exists($adapter)) { |
563
|
|
|
throw new ImageException(sprintf('The class %s does not exists', $adapter)); |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
if (!$adapter::checkCompatibility()) { |
567
|
|
|
throw new ImageException(sprintf('The class %s cannot be used in this computer', $adapter)); |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
return $adapter; |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
private static function getPercentage($value): ?float |
574
|
|
|
{ |
575
|
|
|
if (is_string($value)) { |
576
|
|
|
if (substr($value, -1) === '%') { |
577
|
|
|
return floatval(substr($value, 0, -1)); |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
throw new ImageException(sprintf('Invalid value: %s', $value)); |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
return null; |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
/** |
587
|
|
|
* Calculates the x/y position. |
588
|
|
|
* |
589
|
|
|
* @param string|int $position |
590
|
|
|
* @param int $cropWidth |
591
|
|
|
* |
592
|
|
|
* @return int |
593
|
|
|
*/ |
594
|
|
|
private function getPositionX($position, int $cropWidth): int |
595
|
|
|
{ |
596
|
|
|
$width = $this->getWidth(); |
|
|
|
|
597
|
|
|
|
598
|
|
|
if ($percentage = self::getPercentage($position)) { |
599
|
|
|
$position = $this->getWidth($percentage) - ($cropWidth / 100 * $percentage); |
|
|
|
|
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
//Offset |
603
|
|
|
$offset = isset($split[2]) ? $split[1].$split[2] : 0; |
|
|
|
|
604
|
|
|
|
605
|
|
|
if (is_numeric($offset)) { |
606
|
|
|
$offset = (int) $offset; |
607
|
|
|
} else { |
608
|
|
|
$offset = static::getIntegerValue($direction, $offset, $oldValue, true); |
|
|
|
|
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
return $value + $offset; |
|
|
|
|
612
|
|
|
|
613
|
|
|
} |
614
|
|
|
} |
615
|
|
|
|
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.