1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* File containing the {@link ImageHelper} class. |
4
|
|
|
* |
5
|
|
|
* @package Application Utils |
6
|
|
|
* @subpackage ImageHelper |
7
|
|
|
* @see ImageHelper |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
declare(strict_types=1); |
11
|
|
|
|
12
|
|
|
namespace AppUtils; |
13
|
|
|
|
14
|
|
|
use AppUtils\ClassHelper\ClassNotExistsException; |
15
|
|
|
use AppUtils\ClassHelper\ClassNotImplementsException; |
16
|
|
|
use AppUtils\ImageHelper\ComputedTextSize; |
17
|
|
|
use AppUtils\ImageHelper\ImageTrimmer; |
18
|
|
|
use AppUtils\RGBAColor\ColorException; |
19
|
|
|
use AppUtils\RGBAColor\ColorFactory; |
20
|
|
|
use GdImage; |
21
|
|
|
use JsonException; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Image helper class that can be used to transform images, |
25
|
|
|
* and retrieve information about them. |
26
|
|
|
* |
27
|
|
|
* @package Application Utils |
28
|
|
|
* @subpackage ImageHelper |
29
|
|
|
* @author Sebastian Mordziol <[email protected]> |
30
|
|
|
*/ |
31
|
|
|
class ImageHelper |
32
|
|
|
{ |
33
|
|
|
public const ERROR_CANNOT_CREATE_IMAGE_CANVAS = 513001; |
34
|
|
|
public const ERROR_IMAGE_FILE_DOES_NOT_EXIST = 513002; |
35
|
|
|
public const ERROR_CANNOT_GET_IMAGE_SIZE = 513003; |
36
|
|
|
public const ERROR_UNSUPPORTED_IMAGE_TYPE = 513004; |
37
|
|
|
public const ERROR_FAILED_TO_CREATE_NEW_IMAGE = 513005; |
38
|
|
|
public const ERROR_SAVE_NO_IMAGE_CREATED = 513006; |
39
|
|
|
public const ERROR_CANNOT_WRITE_NEW_IMAGE_FILE = 513007; |
40
|
|
|
public const ERROR_CREATED_AN_EMPTY_FILE = 513008; |
41
|
|
|
public const ERROR_QUALITY_VALUE_BELOW_ZERO = 513009; |
42
|
|
|
public const ERROR_QUALITY_ABOVE_ONE_HUNDRED = 513010; |
43
|
|
|
public const ERROR_CANNOT_CREATE_IMAGE_OBJECT = 513011; |
44
|
|
|
public const ERROR_CANNOT_COPY_RESAMPLED_IMAGE_DATA = 513012; |
45
|
|
|
public const ERROR_HEADERS_ALREADY_SENT = 513013; |
46
|
|
|
public const ERROR_CANNOT_READ_SVG_IMAGE = 513014; |
47
|
|
|
public const ERROR_SVG_SOURCE_VIEWBOX_MISSING = 513015; |
48
|
|
|
public const ERROR_SVG_VIEWBOX_INVALID = 513016; |
49
|
|
|
public const ERROR_NOT_A_RESOURCE = 513017; |
50
|
|
|
public const ERROR_INVALID_STREAM_IMAGE_TYPE = 513018; |
51
|
|
|
public const ERROR_NO_TRUE_TYPE_FONT_SET = 513019; |
52
|
|
|
public const ERROR_POSITION_OUT_OF_BOUNDS = 513020; |
53
|
|
|
public const ERROR_IMAGE_CREATION_FAILED = 513021; |
54
|
|
|
public const ERROR_CANNOT_CREATE_IMAGE_CROP = 513023; |
55
|
|
|
public const ERROR_GD_LIBRARY_NOT_INSTALLED = 513024; |
56
|
|
|
public const ERROR_UNEXPECTED_COLOR_VALUE = 513025; |
57
|
|
|
public const ERROR_HASH_NO_IMAGE_LOADED = 513026; |
58
|
|
|
|
59
|
|
|
protected string $file = ''; |
60
|
|
|
protected ImageHelper_Size $info; |
61
|
|
|
protected ?string $type = null; |
62
|
|
|
protected int $width; |
63
|
|
|
protected int $height; |
64
|
|
|
protected int $newWidth = 0; |
65
|
|
|
protected int $newHeight = 0; |
66
|
|
|
protected int $quality = 85; |
67
|
|
|
protected bool $alpha = false; |
68
|
|
|
protected string $TTFFile = ''; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @var array<string,int> |
72
|
|
|
*/ |
73
|
|
|
protected array $colors = array(); |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @var resource|NULL |
77
|
|
|
*/ |
78
|
|
|
protected $newImage; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @var resource |
82
|
|
|
*/ |
83
|
|
|
protected $sourceImage; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @var array<string,string> |
87
|
|
|
*/ |
88
|
|
|
protected static array $imageTypes = array( |
89
|
|
|
'png' => 'png', |
90
|
|
|
'jpg' => 'jpeg', |
91
|
|
|
'jpeg' => 'jpeg', |
92
|
|
|
'gif' => 'gif', |
93
|
|
|
'svg' => 'svg' |
94
|
|
|
); |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @var array<string,mixed> |
98
|
|
|
*/ |
99
|
|
|
protected static array $config = array( |
100
|
|
|
'auto-memory-adjustment' => true |
101
|
|
|
); |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* @var string[] |
105
|
|
|
*/ |
106
|
|
|
protected static array $streamTypes = array( |
107
|
|
|
'jpeg', |
108
|
|
|
'png', |
109
|
|
|
'gif' |
110
|
|
|
); |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @param string|null $sourceFile |
114
|
|
|
* @param resource|GdImage|null $resource |
115
|
|
|
* @param string|null $type The image type, e.g. "png", "jpeg". |
116
|
|
|
* |
117
|
|
|
* @throws ClassNotExistsException |
118
|
|
|
* @throws ClassNotImplementsException |
119
|
|
|
* @throws ImageHelper_Exception |
120
|
|
|
* |
121
|
|
|
* @see ImageHelper::ERROR_GD_LIBRARY_NOT_INSTALLED |
122
|
|
|
*/ |
123
|
|
|
public function __construct(?string $sourceFile=null, $resource=null, ?string $type=null) |
124
|
|
|
{ |
125
|
|
|
// ensure that the GD library is installed |
126
|
|
|
if(!function_exists('imagecreate')) |
127
|
|
|
{ |
128
|
|
|
throw new ImageHelper_Exception( |
129
|
|
|
'The PHP GD extension is not installed or not enabled.', |
130
|
|
|
null, |
131
|
|
|
self::ERROR_GD_LIBRARY_NOT_INSTALLED |
132
|
|
|
); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
if(is_resource($resource) || $resource instanceof GdImage) |
136
|
|
|
{ |
137
|
|
|
$this->sourceImage = $resource; |
|
|
|
|
138
|
|
|
$this->type = $type; |
139
|
|
|
$this->info = self::getImageSize($resource); |
140
|
|
|
} |
141
|
|
|
else |
142
|
|
|
{ |
143
|
|
|
$this->file = $sourceFile; |
144
|
|
|
if (!file_exists($this->file)) { |
145
|
|
|
throw new ImageHelper_Exception( |
146
|
|
|
'Image file does not exist', |
147
|
|
|
sprintf( |
148
|
|
|
'Could not find the image file on disk at location [%s]', |
149
|
|
|
$this->file |
150
|
|
|
), |
151
|
|
|
self::ERROR_IMAGE_FILE_DOES_NOT_EXIST |
152
|
|
|
); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$type = self::getFileImageType($this->file); |
156
|
|
|
if ($type === null) { |
157
|
|
|
throw new ImageHelper_Exception( |
158
|
|
|
'Error opening image', |
159
|
|
|
'Not a valid supported image type for image ' . $this->file, |
160
|
|
|
self::ERROR_UNSUPPORTED_IMAGE_TYPE |
161
|
|
|
); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
$this->type = $type; |
165
|
|
|
$this->info = self::getImageSize($this->file); |
166
|
|
|
|
167
|
|
|
if(!$this->isVector()) |
168
|
|
|
{ |
169
|
|
|
$method = 'imagecreatefrom' . $this->type; |
170
|
|
|
$this->sourceImage = $method($this->file); |
171
|
|
|
if (!$this->sourceImage) { |
172
|
|
|
throw new ImageHelper_Exception( |
173
|
|
|
'Error creating new image', |
174
|
|
|
$method . ' failed', |
175
|
|
|
self::ERROR_FAILED_TO_CREATE_NEW_IMAGE |
176
|
|
|
); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
imagesavealpha($this->sourceImage, true); |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$this->width = $this->info->getWidth(); |
184
|
|
|
$this->height = $this->info->getHeight(); |
185
|
|
|
|
186
|
|
|
if(!$this->isVector()) { |
187
|
|
|
$this->setNewImage($this->duplicateImage($this->sourceImage)); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Factory method: creates a new helper with a blank image. |
193
|
|
|
* |
194
|
|
|
* @param integer $width |
195
|
|
|
* @param integer $height |
196
|
|
|
* @param string $type The target file type when saving |
197
|
|
|
* @return ImageHelper |
198
|
|
|
* @throws ImageHelper_Exception |
199
|
|
|
* |
200
|
|
|
* @see ImageHelper::ERROR_CANNOT_CREATE_IMAGE_OBJECT |
201
|
|
|
*/ |
202
|
|
|
public static function createNew(int $width, int $height, string $type='png') : self |
203
|
|
|
{ |
204
|
|
|
$img = imagecreatetruecolor($width, $height); |
205
|
|
|
if($img !== false) { |
206
|
|
|
return self::createFromResource($img, $type); |
|
|
|
|
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
throw new ImageHelper_Exception( |
210
|
|
|
'Could not create new true color image.', |
211
|
|
|
null, |
212
|
|
|
self::ERROR_CANNOT_CREATE_IMAGE_OBJECT |
213
|
|
|
); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Factory method: creates an image helper from an |
218
|
|
|
* existing image resource. |
219
|
|
|
* |
220
|
|
|
* Note: while the resource is type independent, the |
221
|
|
|
* type parameter is required for some methods, as well |
222
|
|
|
* as to be able to save the image. |
223
|
|
|
* |
224
|
|
|
* @param resource $resource |
225
|
|
|
* @param string|null $type The target image type, e.g. "jpeg", "png", etc. |
226
|
|
|
* @return ImageHelper |
227
|
|
|
* @throws ImageHelper_Exception |
228
|
|
|
*/ |
229
|
|
|
public static function createFromResource($resource, ?string $type=null) : ImageHelper |
230
|
|
|
{ |
231
|
|
|
self::requireResource($resource); |
232
|
|
|
|
233
|
|
|
return new ImageHelper(null, $resource, $type); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Factory method: creates an image helper from an |
238
|
|
|
* image file on disk. |
239
|
|
|
* |
240
|
|
|
* @param string $file |
241
|
|
|
* @return ImageHelper |
242
|
|
|
* |
243
|
|
|
* @throws ClassNotExistsException |
244
|
|
|
* @throws ClassNotImplementsException |
245
|
|
|
* @throws ImageHelper_Exception |
246
|
|
|
*/ |
247
|
|
|
public static function createFromFile(string $file) : ImageHelper |
248
|
|
|
{ |
249
|
|
|
return new ImageHelper($file, null, self::getFileImageType($file)); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Sets a global image helper configuration value. Available |
254
|
|
|
* configuration settings are: |
255
|
|
|
* |
256
|
|
|
* <ul> |
257
|
|
|
* <li><code>auto-memory-adjustment</code> <i>boolean</i> Whether to try and adjust the memory limit automatically so there will be enough to load/process the target image.</li> |
258
|
|
|
* </ul> |
259
|
|
|
* |
260
|
|
|
* @param string $name |
261
|
|
|
* @param mixed|NULL $value |
262
|
|
|
*/ |
263
|
|
|
public static function setConfig(string $name, $value) : void |
264
|
|
|
{ |
265
|
|
|
if(isset(self::$config[$name])) { |
266
|
|
|
self::$config[$name] = $value; |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Shorthand for setting the automatic memory adjustment |
272
|
|
|
* global configuration setting. |
273
|
|
|
* |
274
|
|
|
* @param bool $enabled |
275
|
|
|
* @return void |
276
|
|
|
*/ |
277
|
|
|
public static function setAutoMemoryAdjustment(bool $enabled=true) : void |
278
|
|
|
{ |
279
|
|
|
self::setConfig('auto-memory-adjustment', $enabled); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Duplicates an image resource. |
284
|
|
|
* @param resource $img |
285
|
|
|
* @return resource |
286
|
|
|
* @throws ImageHelper_Exception |
287
|
|
|
*/ |
288
|
|
|
protected function duplicateImage($img) |
289
|
|
|
{ |
290
|
|
|
self::requireResource($img); |
291
|
|
|
|
292
|
|
|
$width = imagesx($img); |
293
|
|
|
$height = imagesy($img); |
294
|
|
|
$duplicate = $this->createNewImage($width, $height); |
295
|
|
|
imagecopy($duplicate, $img, 0, 0, 0, 0, $width, $height); |
296
|
|
|
return $duplicate; |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Duplicates the current state of the image into a new |
301
|
|
|
* image helper instance. |
302
|
|
|
* |
303
|
|
|
* @return ImageHelper |
304
|
|
|
* @throws ImageHelper_Exception |
305
|
|
|
*/ |
306
|
|
|
public function duplicate() : ImageHelper |
307
|
|
|
{ |
308
|
|
|
return self::createFromResource($this->duplicateImage($this->newImage)); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* @return $this |
313
|
|
|
* @throws ImageHelper_Exception |
314
|
|
|
*/ |
315
|
|
|
public function enableAlpha() : self |
316
|
|
|
{ |
317
|
|
|
if(!$this->alpha) |
318
|
|
|
{ |
319
|
|
|
self::addAlphaSupport($this->newImage, false); |
320
|
|
|
$this->alpha = true; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
return $this; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* @param int $width |
328
|
|
|
* @param int $height |
329
|
|
|
* @return $this |
330
|
|
|
* @throws ImageHelper_Exception |
331
|
|
|
*/ |
332
|
|
|
public function resize(int $width, int $height) : self |
333
|
|
|
{ |
334
|
|
|
$new = $this->createNewImage($width, $height); |
335
|
|
|
|
336
|
|
|
imagecopy($new, $this->newImage, 0, 0, 0, 0, $width, $height); |
337
|
|
|
|
338
|
|
|
$this->setNewImage($new); |
339
|
|
|
|
340
|
|
|
return $this; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* @return array{0:int,1:int} |
345
|
|
|
*/ |
346
|
|
|
public function getNewSize() : array |
347
|
|
|
{ |
348
|
|
|
return array($this->newWidth, $this->newHeight); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Sharpens the image by the specified percentage. |
353
|
|
|
* |
354
|
|
|
* @param int|float $percent |
355
|
|
|
* @return $this |
356
|
|
|
*/ |
357
|
|
|
public function sharpen($percent=0) : self |
358
|
|
|
{ |
359
|
|
|
if($percent <= 0) { |
360
|
|
|
return $this; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
// the factor goes from 0 to 64 for sharpening. |
364
|
|
|
$factor = $percent * 64 / 100; |
365
|
|
|
return $this->convolute($factor); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* @param int|float $percent |
370
|
|
|
* @return $this |
371
|
|
|
*/ |
372
|
|
|
public function blur($percent=0) : self |
373
|
|
|
{ |
374
|
|
|
if($percent <= 0) { |
375
|
|
|
return $this; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
// the factor goes from -64 to 0 for blurring. |
379
|
|
|
$factor = ($percent * 64 / 100) * -1; |
380
|
|
|
return $this->convolute($factor); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* @param int|float $factor |
385
|
|
|
* @return $this |
386
|
|
|
*/ |
387
|
|
|
protected function convolute($factor) : self |
388
|
|
|
{ |
389
|
|
|
// get a value that's equal to 64 - abs( factor ) |
390
|
|
|
// ( using min/max to limit the factor to 0 - 64 to not get out of range values ) |
391
|
|
|
$val1Adjustment = 64 - min( 64, max( 0, abs( $factor ) ) ); |
392
|
|
|
|
393
|
|
|
// the base factor for the "current" pixel depends on if we are blurring or sharpening. |
394
|
|
|
// If we are blurring use 1, if sharpening use 9. |
395
|
|
|
$val1Base = 9; |
396
|
|
|
if( abs( $factor ) !== $factor ) { |
397
|
|
|
$val1Base = 1; |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
// value for the center/current pixel is: |
401
|
|
|
// 1 + 0 - max blurring |
402
|
|
|
// 1 + 64- minimal blurring |
403
|
|
|
// 9 + 64- minimal sharpening |
404
|
|
|
// 9 + 0 - maximum sharpening |
405
|
|
|
$val1 = $val1Base + $val1Adjustment; |
406
|
|
|
|
407
|
|
|
// the value for the surrounding pixels is either positive or negative depending on if we are blurring or sharpening. |
408
|
|
|
$val2 = -1; |
409
|
|
|
if( abs( $factor ) !== $factor ) { |
410
|
|
|
$val2 = 1; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
// setup matrix .. |
414
|
|
|
$matrix = array( |
415
|
|
|
array( $val2, $val2, $val2 ), |
416
|
|
|
array( $val2, $val1, $val2 ), |
417
|
|
|
array( $val2, $val2, $val2 ) |
418
|
|
|
); |
419
|
|
|
|
420
|
|
|
// calculate the correct divisor |
421
|
|
|
// actual divisor is equal to "$divisor = $val1 + $val2 * 8;" |
422
|
|
|
// but the following line is more generic |
423
|
|
|
$divisor = array_sum( array_map( 'array_sum', $matrix ) ); |
424
|
|
|
|
425
|
|
|
// apply the matrix |
426
|
|
|
imageconvolution( $this->newImage, $matrix, $divisor, 0 ); |
427
|
|
|
|
428
|
|
|
return $this; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* Whether the image is an SVG image. |
433
|
|
|
* @return boolean |
434
|
|
|
*/ |
435
|
|
|
public function isTypeSVG() : bool |
436
|
|
|
{ |
437
|
|
|
return $this->type === 'svg'; |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* Whether the image is a PNG image. |
442
|
|
|
* @return boolean |
443
|
|
|
*/ |
444
|
|
|
public function isTypePNG() : bool |
445
|
|
|
{ |
446
|
|
|
return $this->type === 'png'; |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Whether the image is a JPEG image. |
451
|
|
|
* @return boolean |
452
|
|
|
*/ |
453
|
|
|
public function isTypeJPEG() : bool |
454
|
|
|
{ |
455
|
|
|
return $this->type === 'jpeg'; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* Whether the image is a vector image. |
460
|
|
|
* @return boolean |
461
|
|
|
*/ |
462
|
|
|
public function isVector() : bool |
463
|
|
|
{ |
464
|
|
|
return $this->isTypeSVG(); |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Retrieves the type of the image. |
469
|
|
|
* @return string e.g. "jpeg", "png" |
470
|
|
|
*/ |
471
|
|
|
public function getType() : string |
472
|
|
|
{ |
473
|
|
|
return $this->type; |
|
|
|
|
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* Calculates the size of the image by the specified width, |
478
|
|
|
* and returns an indexed array with the width and height size. |
479
|
|
|
* |
480
|
|
|
* @param integer $width |
481
|
|
|
* @return ImageHelper_Size |
482
|
|
|
*/ |
483
|
|
|
public function getSizeByWidth(int $width) : ImageHelper_Size |
484
|
|
|
{ |
485
|
|
|
$height = (int)floor(($width * $this->height) / $this->width); |
486
|
|
|
|
487
|
|
|
return new ImageHelper_Size(array( |
488
|
|
|
$width, |
489
|
|
|
$height, |
490
|
|
|
$this->info['bits'], |
491
|
|
|
$this->info['channels'] |
492
|
|
|
)); |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* Calculates the size of the image by the specified height, |
497
|
|
|
* and returns an indexed array with the width and height size. |
498
|
|
|
* |
499
|
|
|
* @param integer $height |
500
|
|
|
* @return ImageHelper_Size |
501
|
|
|
*/ |
502
|
|
|
public function getSizeByHeight(int $height) : ImageHelper_Size |
503
|
|
|
{ |
504
|
|
|
$width = (int)floor(($height * $this->width) / $this->height); |
505
|
|
|
|
506
|
|
|
return new ImageHelper_Size(array( |
507
|
|
|
$width, |
508
|
|
|
$height, |
509
|
|
|
$this->info['bits'], |
510
|
|
|
$this->info['channels'] |
511
|
|
|
)); |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
/** |
515
|
|
|
* Resamples the image to a new width, maintaining |
516
|
|
|
* aspect ratio. |
517
|
|
|
* |
518
|
|
|
* @param int $width |
519
|
|
|
* @return ImageHelper |
520
|
|
|
* @throws ImageHelper_Exception |
521
|
|
|
*/ |
522
|
|
|
public function resampleByWidth(int $width) : ImageHelper |
523
|
|
|
{ |
524
|
|
|
$size = $this->getSizeByWidth($width); |
525
|
|
|
|
526
|
|
|
$this->resampleImage($size->getWidth(), $size->getHeight()); |
527
|
|
|
|
528
|
|
|
return $this; |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
/** |
532
|
|
|
* Resamples the image by height, and creates a new image file on disk. |
533
|
|
|
* |
534
|
|
|
* @param int $height |
535
|
|
|
* @return ImageHelper |
536
|
|
|
* @throws ImageHelper_Exception |
537
|
|
|
*/ |
538
|
|
|
public function resampleByHeight(int $height) : ImageHelper |
539
|
|
|
{ |
540
|
|
|
$size = $this->getSizeByHeight($height); |
541
|
|
|
|
542
|
|
|
return $this->resampleImage($size->getWidth(), $size->getHeight()); |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* Resamples the image without keeping the aspect ratio. |
547
|
|
|
* |
548
|
|
|
* @param int|null $width |
549
|
|
|
* @param int|null $height |
550
|
|
|
* @return ImageHelper |
551
|
|
|
* @throws ImageHelper_Exception |
552
|
|
|
*/ |
553
|
|
|
public function resample(?int $width = null, ?int $height = null) : ImageHelper |
554
|
|
|
{ |
555
|
|
|
if($this->isVector()) { |
556
|
|
|
return $this; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
if ($width === null && $height === null) { |
560
|
|
|
return $this->resampleByWidth($this->width); |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
if ($width === null) { |
564
|
|
|
return $this->resampleByHeight($height); |
|
|
|
|
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
if ($height === null) { |
568
|
|
|
return $this->resampleByWidth($width); |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
return $this->resampleAndCrop($width, $height); |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
public function resampleAndCrop(int $width, int $height) : ImageHelper |
575
|
|
|
{ |
576
|
|
|
if($this->isVector()) { |
577
|
|
|
return $this; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
if ($this->width <= $this->height) |
581
|
|
|
{ |
582
|
|
|
$this->resampleByWidth($width); |
583
|
|
|
} |
584
|
|
|
else |
585
|
|
|
{ |
586
|
|
|
$this->resampleByHeight($height); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
$newCanvas = $this->createNewImage($width, $height); |
590
|
|
|
|
591
|
|
|
// and now we can add the crop |
592
|
|
|
if (!imagecopy( |
593
|
|
|
$newCanvas, |
594
|
|
|
$this->newImage, |
595
|
|
|
0, // destination X |
596
|
|
|
0, // destination Y |
597
|
|
|
0, // source X |
598
|
|
|
0, // source Y |
599
|
|
|
$width, |
600
|
|
|
$height |
601
|
|
|
) |
602
|
|
|
) { |
603
|
|
|
throw new ImageHelper_Exception( |
604
|
|
|
'Error creating new image', |
605
|
|
|
'Cannot create crop of the image', |
606
|
|
|
self::ERROR_CANNOT_CREATE_IMAGE_CROP |
607
|
|
|
); |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
$this->setNewImage($newCanvas); |
611
|
|
|
|
612
|
|
|
return $this; |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Configures the specified image resource to make it alpha compatible. |
617
|
|
|
* |
618
|
|
|
* @param resource $canvas |
619
|
|
|
* @param bool $fill Whether to fill the whole canvas with the transparency |
620
|
|
|
* @throws ImageHelper_Exception |
621
|
|
|
*/ |
622
|
|
|
public static function addAlphaSupport($canvas, bool $fill=true) : void |
623
|
|
|
{ |
624
|
|
|
self::requireResource($canvas); |
625
|
|
|
|
626
|
|
|
imagealphablending($canvas,true); |
627
|
|
|
imagesavealpha($canvas, true); |
628
|
|
|
|
629
|
|
|
if($fill) { |
630
|
|
|
self::fillImageTransparent($canvas); |
631
|
|
|
} |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
public function isAlpha() : bool |
635
|
|
|
{ |
636
|
|
|
return $this->alpha; |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* @param string $targetFile |
641
|
|
|
* @param bool $dispose |
642
|
|
|
* @return $this |
643
|
|
|
* @throws ImageHelper_Exception |
644
|
|
|
*/ |
645
|
|
|
public function save(string $targetFile, bool $dispose=true) : self |
646
|
|
|
{ |
647
|
|
|
if($this->isVector()) { |
648
|
|
|
return $this; |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
if(!is_resource($this->newImage)) { |
652
|
|
|
throw new ImageHelper_Exception( |
653
|
|
|
'Error creating new image', |
654
|
|
|
'Cannot save an image, no valid image resource was created. You have to call one of the resample methods to create a new image.', |
655
|
|
|
self::ERROR_SAVE_NO_IMAGE_CREATED |
656
|
|
|
); |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
if (file_exists($targetFile)) { |
660
|
|
|
unlink($targetFile); |
661
|
|
|
} |
662
|
|
|
|
663
|
|
|
$method = 'image' . $this->type; |
664
|
|
|
if (!$method($this->newImage, $targetFile, $this->resolveQuality())) { |
665
|
|
|
throw new ImageHelper_Exception( |
666
|
|
|
'Error creating new image', |
667
|
|
|
sprintf( |
668
|
|
|
'The %s method could not write the new image to %s', |
669
|
|
|
$method, |
670
|
|
|
$targetFile |
671
|
|
|
), |
672
|
|
|
self::ERROR_CANNOT_WRITE_NEW_IMAGE_FILE |
673
|
|
|
); |
674
|
|
|
} |
675
|
|
|
|
676
|
|
|
if (filesize($targetFile) < 1) { |
677
|
|
|
throw new ImageHelper_Exception( |
678
|
|
|
'Error creating new image', |
679
|
|
|
'Resampling completed successfully, but the generated file is 0 bytes big.', |
680
|
|
|
self::ERROR_CREATED_AN_EMPTY_FILE |
681
|
|
|
); |
682
|
|
|
} |
683
|
|
|
|
684
|
|
|
if($dispose) { |
685
|
|
|
$this->dispose(); |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
return $this; |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
/** |
692
|
|
|
* @return $this |
693
|
|
|
*/ |
694
|
|
|
public function dispose() : self |
695
|
|
|
{ |
696
|
|
|
if(is_resource($this->sourceImage)) { |
697
|
|
|
imagedestroy($this->sourceImage); |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
if(is_resource($this->newImage)) { |
701
|
|
|
imagedestroy($this->newImage); |
702
|
|
|
} |
703
|
|
|
|
704
|
|
|
return $this; |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
protected function resolveQuality() : int |
708
|
|
|
{ |
709
|
|
|
switch ($this->type) |
710
|
|
|
{ |
711
|
|
|
case 'jpeg': |
712
|
|
|
return $this->quality; |
713
|
|
|
|
714
|
|
|
case 'png': |
715
|
|
|
default: |
716
|
|
|
return 0; |
717
|
|
|
} |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
/** |
721
|
|
|
* Sets the quality for image types like jpg that use compression. |
722
|
|
|
* |
723
|
|
|
* @param int $quality |
724
|
|
|
* @return ImageHelper |
725
|
|
|
* @throws ImageHelper_Exception |
726
|
|
|
*/ |
727
|
|
|
public function setQuality(int $quality) : self |
728
|
|
|
{ |
729
|
|
|
if ($quality < 0) { |
730
|
|
|
throw new ImageHelper_Exception( |
731
|
|
|
'Invalid configuration', |
732
|
|
|
'Cannot set a quality less than 0.', |
733
|
|
|
self::ERROR_QUALITY_VALUE_BELOW_ZERO |
734
|
|
|
); |
735
|
|
|
} |
736
|
|
|
|
737
|
|
|
if ($quality > 100) { |
738
|
|
|
throw new ImageHelper_Exception( |
739
|
|
|
'Invalid configuration', |
740
|
|
|
'Cannot set a quality higher than 100.', |
741
|
|
|
self::ERROR_QUALITY_ABOVE_ONE_HUNDRED |
742
|
|
|
); |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
$this->quality = $quality; |
746
|
|
|
|
747
|
|
|
return $this; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
/** |
751
|
|
|
* Attempts to adjust the memory to the required size |
752
|
|
|
* to work with the current image. |
753
|
|
|
* |
754
|
|
|
* @return boolean |
755
|
|
|
*/ |
756
|
|
|
protected function adjustMemory() : bool |
757
|
|
|
{ |
758
|
|
|
if(!self::$config['auto-memory-adjustment']) { |
759
|
|
|
return true; |
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
$MB = 1048576; // number of bytes in 1M |
763
|
|
|
$K64 = 65536; // number of bytes in 64K |
764
|
|
|
$tweakFactor = 25; // magic adjustment value as safety threshold |
765
|
|
|
$memoryNeeded = ceil( |
766
|
|
|
( |
767
|
|
|
$this->info->getWidth() |
768
|
|
|
* |
769
|
|
|
$this->info->getHeight() |
770
|
|
|
* |
771
|
|
|
$this->info->getBits() |
772
|
|
|
* |
773
|
|
|
($this->info->getChannels() / 8) |
774
|
|
|
+ |
775
|
|
|
$K64 |
776
|
|
|
) |
777
|
|
|
* $tweakFactor |
778
|
|
|
); |
779
|
|
|
|
780
|
|
|
// ini_get('memory_limit') only works if compiled with "--enable-memory-limit". |
781
|
|
|
// Also, default memory limit is 8MB, so we will stick with that. |
782
|
|
|
$memoryLimit = 8 * $MB; |
783
|
|
|
|
784
|
|
|
if (function_exists('memory_get_usage') && memory_get_usage() + $memoryNeeded > $memoryLimit) { |
785
|
|
|
$newLimit = ($memoryLimit + (memory_get_usage() + $memoryNeeded)) / $MB; |
786
|
|
|
$newLimit = ceil($newLimit); |
787
|
|
|
ini_set('memory_limit', $newLimit . 'M'); |
788
|
|
|
|
789
|
|
|
return true; |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
return false; |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
/** |
796
|
|
|
* Stretches the image to the specified dimensions. |
797
|
|
|
* |
798
|
|
|
* @param int $width |
799
|
|
|
* @param int $height |
800
|
|
|
* @return ImageHelper |
801
|
|
|
* @throws ImageHelper_Exception |
802
|
|
|
*/ |
803
|
|
|
public function stretch(int $width, int $height) : ImageHelper |
804
|
|
|
{ |
805
|
|
|
return $this->resampleImage($width, $height); |
806
|
|
|
} |
807
|
|
|
|
808
|
|
|
/** |
809
|
|
|
* Creates a new image from the current image, |
810
|
|
|
* resampling it to the new size. |
811
|
|
|
* |
812
|
|
|
* @param int $newWidth |
813
|
|
|
* @param int $newHeight |
814
|
|
|
* @throws ImageHelper_Exception |
815
|
|
|
* @return ImageHelper |
816
|
|
|
*/ |
817
|
|
|
protected function resampleImage(int $newWidth, int $newHeight) : ImageHelper |
818
|
|
|
{ |
819
|
|
|
if($this->isVector()) { |
820
|
|
|
return $this; |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
if($this->newWidth===$newWidth && $this->newHeight===$newHeight) { |
824
|
|
|
return $this; |
825
|
|
|
} |
826
|
|
|
|
827
|
|
|
if($newWidth < 1) { $newWidth = 1; } |
828
|
|
|
if($newHeight < 1) { $newHeight = 1; } |
829
|
|
|
|
830
|
|
|
$this->adjustMemory(); |
831
|
|
|
|
832
|
|
|
$new = $this->createNewImage($newWidth, $newHeight); |
833
|
|
|
|
834
|
|
|
if (!imagecopyresampled($new, $this->newImage, 0, 0, 0, 0, $newWidth, $newHeight, $this->newWidth, $this->newHeight)) |
835
|
|
|
{ |
836
|
|
|
throw new ImageHelper_Exception( |
837
|
|
|
'Error creating new image', |
838
|
|
|
'Cannot copy resampled image data', |
839
|
|
|
self::ERROR_CANNOT_COPY_RESAMPLED_IMAGE_DATA |
840
|
|
|
); |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
$this->setNewImage($new); |
844
|
|
|
|
845
|
|
|
return $this; |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
/** |
849
|
|
|
* Gets the image type for the specified file name. |
850
|
|
|
* Like {@link getImageType()}, except that it automatically |
851
|
|
|
* extracts the file extension from the file name. |
852
|
|
|
* |
853
|
|
|
* @param string |resource|GdImage $pathOrResource |
854
|
|
|
* @return string|NULL |
855
|
|
|
* @see getImageType() |
856
|
|
|
*/ |
857
|
|
|
public static function getFileImageType($pathOrResource) : ?string |
858
|
|
|
{ |
859
|
|
|
if(!is_string($pathOrResource)) { |
860
|
|
|
return 'png'; |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
return self::getImageType(strtolower(pathinfo($pathOrResource, PATHINFO_EXTENSION))); |
|
|
|
|
864
|
|
|
} |
865
|
|
|
|
866
|
|
|
/** |
867
|
|
|
* Gets the image type for the specified file extension, |
868
|
|
|
* or NULL if the extension is not among the supported |
869
|
|
|
* file types. |
870
|
|
|
* |
871
|
|
|
* @param string $extension |
872
|
|
|
* @return string|NULL |
873
|
|
|
*/ |
874
|
|
|
public static function getImageType(string $extension) : ?string |
875
|
|
|
{ |
876
|
|
|
return self::$imageTypes[$extension] ?? null; |
877
|
|
|
} |
878
|
|
|
|
879
|
|
|
/** |
880
|
|
|
* @return string[] |
881
|
|
|
*/ |
882
|
|
|
public static function getImageTypes() : array |
883
|
|
|
{ |
884
|
|
|
$types = array_values(self::$imageTypes); |
885
|
|
|
return array_unique($types); |
886
|
|
|
} |
887
|
|
|
|
888
|
|
|
/** |
889
|
|
|
* Displays an existing image resource. |
890
|
|
|
* |
891
|
|
|
* @param resource $resource |
892
|
|
|
* @param string $imageType The image format to send, i.e. "jpeg", "png" |
893
|
|
|
* @param int $quality The quality to use for the image. This is 0-9 (0=no compression, 9=max) for PNG, and 0-100 (0=lowest, 100=highest quality) for JPG |
894
|
|
|
* |
895
|
|
|
* @throws ImageHelper_Exception |
896
|
|
|
* @see ImageHelper::ERROR_NOT_A_RESOURCE |
897
|
|
|
* @see ImageHelper::ERROR_INVALID_STREAM_IMAGE_TYPE |
898
|
|
|
*/ |
899
|
|
|
public static function displayImageStream($resource, string $imageType, int $quality=-1) : void |
900
|
|
|
{ |
901
|
|
|
self::requireResource($resource); |
902
|
|
|
|
903
|
|
|
$imageType = self::requireValidStreamType($imageType); |
904
|
|
|
|
905
|
|
|
header('Content-type:image/' . $imageType); |
906
|
|
|
|
907
|
|
|
$function = 'image' . $imageType; |
908
|
|
|
|
909
|
|
|
$function($resource, null, $quality); |
910
|
|
|
} |
911
|
|
|
|
912
|
|
|
/** |
913
|
|
|
* @param string $imageType |
914
|
|
|
* @return string |
915
|
|
|
* |
916
|
|
|
* @throws ImageHelper_Exception |
917
|
|
|
* @see ImageHelper::ERROR_INVALID_STREAM_IMAGE_TYPE |
918
|
|
|
* @see ImageHelper::$streamTypes |
919
|
|
|
*/ |
920
|
|
|
public static function requireValidStreamType(string $imageType) : string |
921
|
|
|
{ |
922
|
|
|
$imageType = strtolower($imageType); |
923
|
|
|
|
924
|
|
|
if(in_array($imageType, self::$streamTypes)) |
925
|
|
|
{ |
926
|
|
|
return $imageType; |
927
|
|
|
} |
928
|
|
|
|
929
|
|
|
throw new ImageHelper_Exception( |
930
|
|
|
'Invalid image stream type', |
931
|
|
|
sprintf( |
932
|
|
|
'The image type [%s] cannot be used for a stream.', |
933
|
|
|
$imageType |
934
|
|
|
), |
935
|
|
|
self::ERROR_INVALID_STREAM_IMAGE_TYPE |
936
|
|
|
); |
937
|
|
|
} |
938
|
|
|
|
939
|
|
|
/** |
940
|
|
|
* Displays an image from an existing image file. |
941
|
|
|
* @param string $imageFile |
942
|
|
|
* @throws ImageHelper_Exception |
943
|
|
|
*/ |
944
|
|
|
public static function displayImage(string $imageFile) : void |
945
|
|
|
{ |
946
|
|
|
$file = null; |
947
|
|
|
$line = null; |
948
|
|
|
if (headers_sent($file, $line)) { |
949
|
|
|
throw new ImageHelper_Exception( |
950
|
|
|
'Error displaying image', |
951
|
|
|
'Headers have already been sent: in file ' . $file . ':' . $line, |
952
|
|
|
self::ERROR_HEADERS_ALREADY_SENT |
953
|
|
|
); |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
if (!file_exists($imageFile)) { |
957
|
|
|
throw new ImageHelper_Exception( |
958
|
|
|
'Image file does not exist', |
959
|
|
|
sprintf( |
960
|
|
|
'Cannot display image, the file does not exist on disk: [%s].', |
961
|
|
|
$imageFile |
962
|
|
|
), |
963
|
|
|
self::ERROR_IMAGE_FILE_DOES_NOT_EXIST |
964
|
|
|
); |
965
|
|
|
} |
966
|
|
|
|
967
|
|
|
$format = self::getFileImageType($imageFile); |
968
|
|
|
if($format === 'svg') { |
969
|
|
|
$format = 'svg+xml'; |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
$contentType = 'image/' . $format; |
973
|
|
|
|
974
|
|
|
header('Content-Type: '.$contentType); |
975
|
|
|
header("Last-Modified: " . gmdate("D, d M Y H:i:s", filemtime($imageFile)) . " GMT"); |
976
|
|
|
header('Cache-Control: public'); |
977
|
|
|
header('Content-Length: ' . filesize($imageFile)); |
978
|
|
|
|
979
|
|
|
readfile($imageFile); |
980
|
|
|
} |
981
|
|
|
|
982
|
|
|
/** |
983
|
|
|
* Displays the current image. |
984
|
|
|
* |
985
|
|
|
* NOTE: You must call `exit()` manually after this. |
986
|
|
|
*/ |
987
|
|
|
public function display() : void |
988
|
|
|
{ |
989
|
|
|
self::displayImageStream( |
990
|
|
|
$this->newImage, |
991
|
|
|
$this->getType(), |
992
|
|
|
$this->resolveQuality() |
993
|
|
|
); |
994
|
|
|
} |
995
|
|
|
|
996
|
|
|
/** |
997
|
|
|
* Trims the current loaded image. |
998
|
|
|
* |
999
|
|
|
* @param RGBAColor|null $color A color definition, as an associative array with red, green, and blue keys. If not specified, the color at pixel position 0,0 will be used. |
1000
|
|
|
* |
1001
|
|
|
* @return ImageHelper |
1002
|
|
|
* @throws ImageHelper_Exception |
1003
|
|
|
* @throws ColorException |
1004
|
|
|
* |
1005
|
|
|
* @see ImageHelper::ERROR_NOT_A_RESOURCE |
1006
|
|
|
* @see ImageHelper::ERROR_CANNOT_CREATE_IMAGE_CANVAS |
1007
|
|
|
*/ |
1008
|
|
|
public function trim(?RGBAColor $color=null) : ImageHelper |
1009
|
|
|
{ |
1010
|
|
|
return $this->trimImage($this->newImage, $color); |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
|
|
/** |
1014
|
|
|
* Retrieves a color definition by its index. |
1015
|
|
|
* |
1016
|
|
|
* @param resource $img A valid image resource. |
1017
|
|
|
* @param int $colorIndex The color index, as returned by `imagecolorat` for example. |
1018
|
|
|
* @return RGBAColor An array with red, green, blue and alpha keys. |
1019
|
|
|
* |
1020
|
|
|
* @throws ImageHelper_Exception |
1021
|
|
|
* @see ImageHelper::ERROR_NOT_A_RESOURCE |
1022
|
|
|
*/ |
1023
|
|
|
public function getIndexedColors($img, int $colorIndex) : RGBAColor |
1024
|
|
|
{ |
1025
|
|
|
self::requireResource($img); |
1026
|
|
|
|
1027
|
|
|
return ColorFactory::createFromIndex($img, $colorIndex); |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
/** |
1031
|
|
|
* @param resource $img |
1032
|
|
|
* @param int $x |
1033
|
|
|
* @param int $y |
1034
|
|
|
* @return RGBAColor |
1035
|
|
|
* @throws ImageHelper_Exception |
1036
|
|
|
*/ |
1037
|
|
|
public function getIndexedColorsAt($img, int $x, int $y) : RGBAColor |
1038
|
|
|
{ |
1039
|
|
|
self::requireResource($img); |
1040
|
|
|
|
1041
|
|
|
return $this->getIndexedColors( |
1042
|
|
|
$img, |
1043
|
|
|
imagecolorat($this->sourceImage, $x, $y) |
1044
|
|
|
); |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
/** |
1048
|
|
|
* Trims the specified image resource by removing the specified color. |
1049
|
|
|
* Also works with transparency. |
1050
|
|
|
* |
1051
|
|
|
* @param resource|GdImage $img |
1052
|
|
|
* @param RGBAColor|null $trimColor |
1053
|
|
|
* @return ImageHelper |
1054
|
|
|
* |
1055
|
|
|
* @throws ImageHelper_Exception |
1056
|
|
|
*/ |
1057
|
|
|
protected function trimImage($img, ?RGBAColor $trimColor=null) : ImageHelper |
1058
|
|
|
{ |
1059
|
|
|
if($this->isVector()) { |
1060
|
|
|
return $this; |
1061
|
|
|
} |
1062
|
|
|
|
1063
|
|
|
self::requireResource($img); |
1064
|
|
|
|
1065
|
|
|
$trimmer = new ImageTrimmer($this, $img, $trimColor); |
1066
|
|
|
$new = $trimmer->trim(); |
1067
|
|
|
|
1068
|
|
|
if($new === null) { |
1069
|
|
|
return $this; |
1070
|
|
|
} |
1071
|
|
|
|
1072
|
|
|
// To finish up, we replace the old image which is referenced. |
1073
|
|
|
imagedestroy($img); |
1074
|
|
|
|
1075
|
|
|
$this->setNewImage($new); |
|
|
|
|
1076
|
|
|
|
1077
|
|
|
return $this; |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
/** |
1081
|
|
|
* Sets the new image after a transformation operation: |
1082
|
|
|
* automatically adjusts the new size information. |
1083
|
|
|
* |
1084
|
|
|
* @param resource $image |
1085
|
|
|
* |
1086
|
|
|
* @throws ImageHelper_Exception |
1087
|
|
|
* @see ImageHelper::ERROR_NOT_A_RESOURCE |
1088
|
|
|
*/ |
1089
|
|
|
protected function setNewImage($image) : ImageHelper |
1090
|
|
|
{ |
1091
|
|
|
self::requireResource($image); |
1092
|
|
|
|
1093
|
|
|
$this->newImage = $image; |
1094
|
|
|
$this->newWidth = imagesx($image); |
1095
|
|
|
$this->newHeight= imagesy($image); |
1096
|
|
|
|
1097
|
|
|
return $this; |
1098
|
|
|
} |
1099
|
|
|
|
1100
|
|
|
/** |
1101
|
|
|
* Requires the subject to be a resource. |
1102
|
|
|
* |
1103
|
|
|
* @param resource|GdImage|mixed $subject |
1104
|
|
|
* |
1105
|
|
|
* @throws ImageHelper_Exception |
1106
|
|
|
* @see ImageHelper::ERROR_NOT_A_RESOURCE |
1107
|
|
|
*/ |
1108
|
|
|
public static function requireResource($subject) : void |
1109
|
|
|
{ |
1110
|
|
|
if(is_resource($subject) && imagesx($subject)) { |
1111
|
|
|
return; |
1112
|
|
|
} |
1113
|
|
|
|
1114
|
|
|
if($subject instanceof GdImage) { |
1115
|
|
|
return; |
1116
|
|
|
} |
1117
|
|
|
|
1118
|
|
|
throw new ImageHelper_Exception( |
1119
|
|
|
'Not an image resource', |
1120
|
|
|
sprintf( |
1121
|
|
|
'Specified image should be a resource, [%s] given.', |
1122
|
|
|
gettype($subject) |
1123
|
|
|
), |
1124
|
|
|
self::ERROR_NOT_A_RESOURCE |
1125
|
|
|
); |
1126
|
|
|
} |
1127
|
|
|
|
1128
|
|
|
/** |
1129
|
|
|
* Creates a new image resource, with transparent background. |
1130
|
|
|
* |
1131
|
|
|
* @param int $width |
1132
|
|
|
* @param int $height |
1133
|
|
|
* @throws ImageHelper_Exception |
1134
|
|
|
* @return resource |
1135
|
|
|
*/ |
1136
|
|
|
public function createNewImage(int $width, int $height) |
1137
|
|
|
{ |
1138
|
|
|
$img = imagecreatetruecolor($width, $height); |
1139
|
|
|
|
1140
|
|
|
if($img === false) |
1141
|
|
|
{ |
1142
|
|
|
throw new ImageHelper_Exception( |
1143
|
|
|
'Error creating new image', |
1144
|
|
|
'Cannot create new image canvas', |
1145
|
|
|
self::ERROR_CANNOT_CREATE_IMAGE_CANVAS |
1146
|
|
|
); |
1147
|
|
|
} |
1148
|
|
|
|
1149
|
|
|
self::addAlphaSupport($img); |
|
|
|
|
1150
|
|
|
|
1151
|
|
|
return $img; |
|
|
|
|
1152
|
|
|
} |
1153
|
|
|
|
1154
|
|
|
/** |
1155
|
|
|
* @param int $x |
1156
|
|
|
* @param int $y |
1157
|
|
|
* @return $this |
1158
|
|
|
*/ |
1159
|
|
|
public function fillWhite(int $x=0, int $y=0) : self |
1160
|
|
|
{ |
1161
|
|
|
$this->addRGBColor('white', 255, 255, 255); |
1162
|
|
|
return $this->fill('white', $x, $y); |
1163
|
|
|
} |
1164
|
|
|
|
1165
|
|
|
/** |
1166
|
|
|
* @return $this |
1167
|
|
|
* @throws ImageHelper_Exception |
1168
|
|
|
*/ |
1169
|
|
|
public function fillTransparent() : self |
1170
|
|
|
{ |
1171
|
|
|
$this->enableAlpha(); |
1172
|
|
|
|
1173
|
|
|
self::fillImageTransparent($this->newImage); |
1174
|
|
|
|
1175
|
|
|
return $this; |
1176
|
|
|
} |
1177
|
|
|
|
1178
|
|
|
/** |
1179
|
|
|
* @param resource $resource |
1180
|
|
|
* @return void |
1181
|
|
|
* @throws ImageHelper_Exception |
1182
|
|
|
*/ |
1183
|
|
|
public static function fillImageTransparent($resource) : void |
1184
|
|
|
{ |
1185
|
|
|
self::requireResource($resource); |
1186
|
|
|
|
1187
|
|
|
$transparent = imagecolorallocatealpha($resource, 89, 14, 207, 127); |
1188
|
|
|
imagecolortransparent ($resource, $transparent); |
1189
|
|
|
imagefill($resource, 0, 0, $transparent); |
1190
|
|
|
} |
1191
|
|
|
|
1192
|
|
|
/** |
1193
|
|
|
* @param string $colorName |
1194
|
|
|
* @param int $x |
1195
|
|
|
* @param int $y |
1196
|
|
|
* @return $this |
1197
|
|
|
*/ |
1198
|
|
|
public function fill(string $colorName, int $x=0, int $y=0) : self |
1199
|
|
|
{ |
1200
|
|
|
imagefill($this->newImage, $x, $y, $this->colors[$colorName]); |
1201
|
|
|
return $this; |
1202
|
|
|
} |
1203
|
|
|
|
1204
|
|
|
/** |
1205
|
|
|
* @param string $name |
1206
|
|
|
* @param int $red |
1207
|
|
|
* @param int $green |
1208
|
|
|
* @param int $blue |
1209
|
|
|
* @return $this |
1210
|
|
|
*/ |
1211
|
|
|
public function addRGBColor(string $name, int $red, int $green, int $blue) : self |
1212
|
|
|
{ |
1213
|
|
|
$this->colors[$name] = (int)imagecolorallocate($this->newImage, $red, $green, $blue); |
1214
|
|
|
return $this; |
1215
|
|
|
} |
1216
|
|
|
|
1217
|
|
|
/** |
1218
|
|
|
* @param string $text |
1219
|
|
|
* @param int|float $size |
1220
|
|
|
* @param string $colorName |
1221
|
|
|
* @param int $x |
1222
|
|
|
* @param int $y |
1223
|
|
|
* @param int|float $angle |
1224
|
|
|
* @return $this |
1225
|
|
|
*/ |
1226
|
|
|
public function textTTF(string $text, $size, string $colorName, int $x=0, int $y=0, $angle=0) : self |
1227
|
|
|
{ |
1228
|
|
|
imagealphablending($this->newImage, true); |
1229
|
|
|
|
1230
|
|
|
imagettftext( |
1231
|
|
|
$this->newImage, |
1232
|
|
|
(float)$size, |
1233
|
|
|
(float)$angle, |
1234
|
|
|
$x, |
1235
|
|
|
$y, |
1236
|
|
|
$this->colors[$colorName], |
1237
|
|
|
$this->TTFFile, |
1238
|
|
|
$text |
1239
|
|
|
); |
1240
|
|
|
|
1241
|
|
|
imagealphablending($this->newImage, false); |
1242
|
|
|
|
1243
|
|
|
return $this; |
1244
|
|
|
} |
1245
|
|
|
|
1246
|
|
|
/** |
1247
|
|
|
* @return resource |
1248
|
|
|
*/ |
1249
|
|
|
public function getImage() |
1250
|
|
|
{ |
1251
|
|
|
return $this->newImage; |
1252
|
|
|
} |
1253
|
|
|
|
1254
|
|
|
/** |
1255
|
|
|
* @param ImageHelper $target |
1256
|
|
|
* @param int $xpos |
1257
|
|
|
* @param int $ypos |
1258
|
|
|
* @param int $sourceX |
1259
|
|
|
* @param int $sourceY |
1260
|
|
|
* @return $this |
1261
|
|
|
* @throws ImageHelper_Exception |
1262
|
|
|
*/ |
1263
|
|
|
public function paste(ImageHelper $target, int $xpos=0, int $ypos=0, int $sourceX=0, int $sourceY=0) : self |
1264
|
|
|
{ |
1265
|
|
|
$img = $target->getImage(); |
1266
|
|
|
|
1267
|
|
|
if($target->isAlpha()) { |
1268
|
|
|
$this->enableAlpha(); |
1269
|
|
|
} |
1270
|
|
|
|
1271
|
|
|
imagecopy( |
1272
|
|
|
$this->newImage, |
1273
|
|
|
$img, |
1274
|
|
|
$xpos, |
1275
|
|
|
$ypos, |
1276
|
|
|
$sourceX, |
1277
|
|
|
$sourceY, |
1278
|
|
|
imagesx($img), |
1279
|
|
|
imagesy($img) |
1280
|
|
|
); |
1281
|
|
|
|
1282
|
|
|
return $this; |
1283
|
|
|
} |
1284
|
|
|
|
1285
|
|
|
/** |
1286
|
|
|
* Retrieves the size of the image. |
1287
|
|
|
* |
1288
|
|
|
* @return ImageHelper_Size |
1289
|
|
|
* |
1290
|
|
|
* @throws ClassNotExistsException |
1291
|
|
|
* @throws ClassNotImplementsException |
1292
|
|
|
* @throws ImageHelper_Exception |
1293
|
|
|
* |
1294
|
|
|
* @see ImageHelper::ERROR_CANNOT_GET_IMAGE_SIZE |
1295
|
|
|
*/ |
1296
|
|
|
public function getSize() : ImageHelper_Size |
1297
|
|
|
{ |
1298
|
|
|
return self::getImageSize($this->newImage); |
1299
|
|
|
} |
1300
|
|
|
|
1301
|
|
|
/** |
1302
|
|
|
* Sets the TTF font file to use for text operations. |
1303
|
|
|
* |
1304
|
|
|
* @param string $filePath |
1305
|
|
|
* @return $this |
1306
|
|
|
*/ |
1307
|
|
|
public function setFontTTF(string $filePath) : self |
1308
|
|
|
{ |
1309
|
|
|
$this->TTFFile = $filePath; |
1310
|
|
|
return $this; |
1311
|
|
|
} |
1312
|
|
|
|
1313
|
|
|
/** |
1314
|
|
|
* Goes through a series of text sizes to find the closest match to |
1315
|
|
|
* fit the text into the target width. |
1316
|
|
|
* |
1317
|
|
|
* @param string $text |
1318
|
|
|
* @param integer $matchWidth |
1319
|
|
|
* @return ComputedTextSize |
1320
|
|
|
* @throws ImageHelper_Exception |
1321
|
|
|
*/ |
1322
|
|
|
public function fitText(string $text, int $matchWidth) : ComputedTextSize |
1323
|
|
|
{ |
1324
|
|
|
/** |
1325
|
|
|
* @var ComputedTextSize[] |
1326
|
|
|
*/ |
1327
|
|
|
$sizes = array(); |
1328
|
|
|
|
1329
|
|
|
for($i=1; $i<=1000; $i += 0.1) { |
1330
|
|
|
$size = $this->calcTextSize($text, $i); |
1331
|
|
|
$sizes[] = $size; |
1332
|
|
|
if($size->getWidth() >= $matchWidth) { |
1333
|
|
|
break; |
1334
|
|
|
} |
1335
|
|
|
} |
1336
|
|
|
|
1337
|
|
|
$last = array_pop($sizes); |
1338
|
|
|
$prev = array_pop($sizes); |
1339
|
|
|
|
1340
|
|
|
// determine which is the closest match, and use that |
1341
|
|
|
$diffLast = $last->getWidth() - $matchWidth; |
1342
|
|
|
$diffPrev = $matchWidth - $prev->getWidth(); |
1343
|
|
|
|
1344
|
|
|
if($diffLast <= $diffPrev) { |
1345
|
|
|
return $last; |
1346
|
|
|
} |
1347
|
|
|
|
1348
|
|
|
return $prev; |
1349
|
|
|
} |
1350
|
|
|
|
1351
|
|
|
/** |
1352
|
|
|
* @param string $text |
1353
|
|
|
* @param int|float $fontSize |
1354
|
|
|
* @return ComputedTextSize |
1355
|
|
|
* @throws ImageHelper_Exception |
1356
|
|
|
*/ |
1357
|
|
|
public function calcTextSize(string $text, $fontSize) : ComputedTextSize |
1358
|
|
|
{ |
1359
|
|
|
$this->requireTTFFont(); |
1360
|
|
|
|
1361
|
|
|
$box = imagettfbbox((float)$fontSize, 0, $this->TTFFile, $text); |
1362
|
|
|
|
1363
|
|
|
return new ComputedTextSize(array( |
1364
|
|
|
'size' => (float)$fontSize, |
1365
|
|
|
'top_left_x' => $box[6], |
1366
|
|
|
'top_left_y' => $box[7], |
1367
|
|
|
'top_right_x' => $box[4], |
1368
|
|
|
'top_right_y' => $box[5], |
1369
|
|
|
'bottom_left_x' => $box[0], |
1370
|
|
|
'bottom_left_y' => $box[1], |
1371
|
|
|
'bottom_right_x' => $box[2], |
1372
|
|
|
'bottom_right_y' => $box[3], |
1373
|
|
|
'width' => $box[4]-$box[0], |
1374
|
|
|
'height' => $box[1]-$box[7] |
1375
|
|
|
)); |
1376
|
|
|
} |
1377
|
|
|
|
1378
|
|
|
protected function requireTTFFont() : void |
1379
|
|
|
{ |
1380
|
|
|
if(!empty($this->TTFFile)) { |
1381
|
|
|
return; |
1382
|
|
|
} |
1383
|
|
|
|
1384
|
|
|
throw new ImageHelper_Exception( |
1385
|
|
|
'No true type font specified', |
1386
|
|
|
'This functionality requires a TTF font file to be specified with the [setFontTTF] method.', |
1387
|
|
|
self::ERROR_NO_TRUE_TYPE_FONT_SET |
1388
|
|
|
); |
1389
|
|
|
} |
1390
|
|
|
|
1391
|
|
|
/** |
1392
|
|
|
* Retrieves the size of an image file on disk, or |
1393
|
|
|
* an existing image resource. |
1394
|
|
|
* |
1395
|
|
|
* @param resource|GdImage|string $pathOrResource |
1396
|
|
|
* @return ImageHelper_Size Size object, can also be accessed like the traditional array from getimagesize |
1397
|
|
|
* @throws ClassNotExistsException |
1398
|
|
|
* @throws ClassNotImplementsException |
1399
|
|
|
* @throws ImageHelper_Exception |
1400
|
|
|
* |
1401
|
|
|
* @see ImageHelper_Size |
1402
|
|
|
* @see ImageHelper::ERROR_CANNOT_GET_IMAGE_SIZE |
1403
|
|
|
* @see ImageHelper::ERROR_CANNOT_READ_SVG_IMAGE |
1404
|
|
|
* @see ImageHelper::ERROR_SVG_SOURCE_VIEWBOX_MISSING |
1405
|
|
|
* @see ImageHelper::ERROR_SVG_VIEWBOX_INVALID |
1406
|
|
|
*/ |
1407
|
|
|
public static function getImageSize($pathOrResource) : ImageHelper_Size |
1408
|
|
|
{ |
1409
|
|
|
if(is_resource($pathOrResource) || $pathOrResource instanceof GdImage) |
1410
|
|
|
{ |
1411
|
|
|
return new ImageHelper_Size(array( |
1412
|
|
|
'width' => imagesx($pathOrResource), |
1413
|
|
|
'height' => imagesy($pathOrResource), |
1414
|
|
|
'channels' => 1, |
1415
|
|
|
'bits' => 8 |
1416
|
|
|
)); |
1417
|
|
|
} |
1418
|
|
|
|
1419
|
|
|
$type = self::getFileImageType($pathOrResource); |
1420
|
|
|
|
1421
|
|
|
$sizeMethods = array( |
1422
|
|
|
'svg' => array(self::class, 'getImageSize_svg') |
1423
|
|
|
); |
1424
|
|
|
|
1425
|
|
|
if(isset($sizeMethods[$type])) |
1426
|
|
|
{ |
1427
|
|
|
return ClassHelper::requireObjectInstanceOf( |
1428
|
|
|
ImageHelper_Size::class, |
1429
|
|
|
$sizeMethods[$type]($pathOrResource) |
1430
|
|
|
); |
1431
|
|
|
} |
1432
|
|
|
|
1433
|
|
|
$info = getimagesize($pathOrResource); |
1434
|
|
|
|
1435
|
|
|
if($info !== false) { |
1436
|
|
|
return new ImageHelper_Size($info); |
1437
|
|
|
} |
1438
|
|
|
|
1439
|
|
|
throw new ImageHelper_Exception( |
1440
|
|
|
'Error opening image file', |
1441
|
|
|
sprintf( |
1442
|
|
|
'Could not get image size for image [%s]', |
1443
|
|
|
$pathOrResource |
1444
|
|
|
), |
1445
|
|
|
self::ERROR_CANNOT_GET_IMAGE_SIZE |
1446
|
|
|
); |
1447
|
|
|
} |
1448
|
|
|
|
1449
|
|
|
/** |
1450
|
|
|
* @param string $imagePath |
1451
|
|
|
* @return ImageHelper_Size |
1452
|
|
|
* |
1453
|
|
|
* @throws ImageHelper_Exception |
1454
|
|
|
* @throws XMLHelper_Exception |
1455
|
|
|
* @throws JsonException |
1456
|
|
|
*/ |
1457
|
|
|
protected static function getImageSize_svg(string $imagePath) : ImageHelper_Size |
1458
|
|
|
{ |
1459
|
|
|
$xml = XMLHelper::createSimplexml(); |
1460
|
|
|
$xml->loadFile($imagePath); |
1461
|
|
|
|
1462
|
|
|
if($xml->hasErrors()) { |
1463
|
|
|
throw new ImageHelper_Exception( |
1464
|
|
|
'Error opening SVG image', |
1465
|
|
|
sprintf( |
1466
|
|
|
'The XML content of the image [%s] could not be parsed.', |
1467
|
|
|
$imagePath |
1468
|
|
|
), |
1469
|
|
|
self::ERROR_CANNOT_READ_SVG_IMAGE |
1470
|
|
|
); |
1471
|
|
|
} |
1472
|
|
|
|
1473
|
|
|
$data = $xml->toArray(); |
1474
|
|
|
$xml->dispose(); |
1475
|
|
|
unset($xml); |
1476
|
|
|
|
1477
|
|
|
if(!isset($data['@attributes']['viewBox'])) { |
1478
|
|
|
throw new ImageHelper_Exception( |
1479
|
|
|
'SVG Image is corrupted', |
1480
|
|
|
sprintf( |
1481
|
|
|
'The [viewBox] attribute is missing in the XML of the image at path [%s].', |
1482
|
|
|
$imagePath |
1483
|
|
|
), |
1484
|
|
|
self::ERROR_SVG_SOURCE_VIEWBOX_MISSING |
1485
|
|
|
); |
1486
|
|
|
} |
1487
|
|
|
|
1488
|
|
|
$svgWidth = parseNumber($data['@attributes']['width'])->getNumber(); |
1489
|
|
|
$svgHeight = parseNumber($data['@attributes']['height'])->getNumber(); |
1490
|
|
|
|
1491
|
|
|
$viewBox = str_replace(' ', ',', $data['@attributes']['viewBox']); |
1492
|
|
|
$size = explode(',', $viewBox); |
1493
|
|
|
|
1494
|
|
|
if(count($size) !== 4) |
1495
|
|
|
{ |
1496
|
|
|
throw new ImageHelper_Exception( |
1497
|
|
|
'SVG image has an invalid viewBox attribute', |
1498
|
|
|
sprintf( |
1499
|
|
|
'The [viewBox] attribute does not have an expected value: [%s] in path [%s].', |
1500
|
|
|
$viewBox, |
1501
|
|
|
$imagePath |
1502
|
|
|
), |
1503
|
|
|
self::ERROR_SVG_VIEWBOX_INVALID |
1504
|
|
|
); |
1505
|
|
|
} |
1506
|
|
|
|
1507
|
|
|
$boxWidth = (float)$size[2]; |
1508
|
|
|
$boxHeight = (float)$size[3]; |
1509
|
|
|
|
1510
|
|
|
// calculate the x and y units of the document: |
1511
|
|
|
// @see http://tutorials.jenkov.com/svg/svg-viewport-view-box.html#viewbox |
1512
|
|
|
// |
1513
|
|
|
// The viewbox combined with the width and height of the svg |
1514
|
|
|
// allow calculating how many pixels are in one unit of the |
1515
|
|
|
// width and height of the document. |
1516
|
|
|
// |
1517
|
|
|
$xUnits = $svgWidth / $boxWidth; |
1518
|
|
|
$yUnits = $svgHeight / $boxHeight; |
1519
|
|
|
|
1520
|
|
|
$pxWidth = $xUnits * $svgWidth; |
1521
|
|
|
$pxHeight = $yUnits * $svgHeight; |
1522
|
|
|
|
1523
|
|
|
return new ImageHelper_Size(array( |
1524
|
|
|
(int)$pxWidth, |
1525
|
|
|
(int)$pxHeight, |
1526
|
|
|
'bits' => 8 |
1527
|
|
|
)); |
1528
|
|
|
} |
1529
|
|
|
|
1530
|
|
|
/** |
1531
|
|
|
* Crops the image to the specified width and height, optionally |
1532
|
|
|
* specifying the origin position to crop from. |
1533
|
|
|
* |
1534
|
|
|
* @param integer $width |
1535
|
|
|
* @param integer $height |
1536
|
|
|
* @param integer $x |
1537
|
|
|
* @param integer $y |
1538
|
|
|
* @return $this |
1539
|
|
|
* @throws ImageHelper_Exception |
1540
|
|
|
*/ |
1541
|
|
|
public function crop(int $width, int $height, int $x=0, int $y=0) : ImageHelper |
1542
|
|
|
{ |
1543
|
|
|
$new = $this->createNewImage($width, $height); |
1544
|
|
|
|
1545
|
|
|
imagecopy($new, $this->newImage, 0, 0, $x, $y, $width, $height); |
1546
|
|
|
|
1547
|
|
|
$this->setNewImage($new); |
1548
|
|
|
|
1549
|
|
|
return $this; |
1550
|
|
|
} |
1551
|
|
|
|
1552
|
|
|
public function getWidth() : int |
1553
|
|
|
{ |
1554
|
|
|
return $this->newWidth; |
1555
|
|
|
} |
1556
|
|
|
|
1557
|
|
|
public function getHeight() : int |
1558
|
|
|
{ |
1559
|
|
|
return $this->newHeight; |
1560
|
|
|
} |
1561
|
|
|
|
1562
|
|
|
/** |
1563
|
|
|
* Calculates the average color value used in |
1564
|
|
|
* the image. Returns an associative array |
1565
|
|
|
* with the red, green, blue and alpha components, |
1566
|
|
|
* or a HEX color string depending on the selected |
1567
|
|
|
* format. |
1568
|
|
|
* |
1569
|
|
|
* NOTE: Use the calcAverageColorXXX methods for |
1570
|
|
|
* strict return types. |
1571
|
|
|
* |
1572
|
|
|
* @return RGBAColor |
1573
|
|
|
* |
1574
|
|
|
* @throws ImageHelper_Exception |
1575
|
|
|
* |
1576
|
|
|
* @see ImageHelper::calcAverageColorHEX() |
1577
|
|
|
* @see ImageHelper::calcAverageColorRGB() |
1578
|
|
|
*/ |
1579
|
|
|
public function calcAverageColor() : RGBAColor |
1580
|
|
|
{ |
1581
|
|
|
$image = $this->duplicate(); |
1582
|
|
|
$image->resample(1, 1); |
1583
|
|
|
|
1584
|
|
|
return $image->getColorAt(0, 0); |
1585
|
|
|
} |
1586
|
|
|
|
1587
|
|
|
/** |
1588
|
|
|
* Calculates the image's average color value, and |
1589
|
|
|
* returns an associative array with red, green, |
1590
|
|
|
* blue and alpha keys. |
1591
|
|
|
* |
1592
|
|
|
* @throws ImageHelper_Exception |
1593
|
|
|
* @return RGBAColor |
1594
|
|
|
*/ |
1595
|
|
|
public function calcAverageColorRGB() : RGBAColor |
1596
|
|
|
{ |
1597
|
|
|
return $this->calcAverageColor(); |
1598
|
|
|
} |
1599
|
|
|
|
1600
|
|
|
/** |
1601
|
|
|
* Calculates the image's average color value, and |
1602
|
|
|
* returns a hex color string (without the #). |
1603
|
|
|
* |
1604
|
|
|
* @throws ImageHelper_Exception |
1605
|
|
|
* @return string |
1606
|
|
|
*/ |
1607
|
|
|
public function calcAverageColorHex() : string |
1608
|
|
|
{ |
1609
|
|
|
return $this->calcAverageColor()->toHEX(); |
1610
|
|
|
} |
1611
|
|
|
|
1612
|
|
|
/** |
1613
|
|
|
* Retrieves the color value at the specified pixel |
1614
|
|
|
* coordinates in the image. |
1615
|
|
|
* |
1616
|
|
|
* @param int $x |
1617
|
|
|
* @param int $y |
1618
|
|
|
* @return RGBAColor |
1619
|
|
|
* |
1620
|
|
|
* @throws ImageHelper_Exception |
1621
|
|
|
* @see ImageHelper::ERROR_POSITION_OUT_OF_BOUNDS |
1622
|
|
|
*/ |
1623
|
|
|
public function getColorAt(int $x, int $y) : RGBAColor |
1624
|
|
|
{ |
1625
|
|
|
if($x > $this->getWidth() || $y > $this->getHeight()) |
1626
|
|
|
{ |
1627
|
|
|
throw new ImageHelper_Exception( |
1628
|
|
|
'Position out of bounds', |
1629
|
|
|
sprintf( |
1630
|
|
|
'The position [%sx%s] does not exist in the image, it is [%sx%s] pixels in size.', |
1631
|
|
|
$x, |
1632
|
|
|
$y, |
1633
|
|
|
$this->getWidth(), |
1634
|
|
|
$this->getHeight() |
1635
|
|
|
), |
1636
|
|
|
self::ERROR_POSITION_OUT_OF_BOUNDS |
1637
|
|
|
); |
1638
|
|
|
} |
1639
|
|
|
|
1640
|
|
|
$idx = imagecolorat($this->newImage, $x, $y); |
1641
|
|
|
|
1642
|
|
|
return $this->getIndexedColors($this->newImage, $idx); |
1643
|
|
|
} |
1644
|
|
|
|
1645
|
|
|
/** |
1646
|
|
|
* Retrieves the brightness of the image, in percent. |
1647
|
|
|
* |
1648
|
|
|
* @return float |
1649
|
|
|
* @throws ImageHelper_Exception |
1650
|
|
|
*/ |
1651
|
|
|
public function getBrightness() : float |
1652
|
|
|
{ |
1653
|
|
|
return $this |
1654
|
|
|
->calcAverageColorRGB() |
1655
|
|
|
->getBrightness() |
1656
|
|
|
->getValue(); |
1657
|
|
|
} |
1658
|
|
|
|
1659
|
|
|
/** |
1660
|
|
|
* Retrieves a md5 hash of the source image file. |
1661
|
|
|
* |
1662
|
|
|
* NOTE: Only works when the helper has been created |
1663
|
|
|
* from a file. Otherwise, an exception is thrown. |
1664
|
|
|
* |
1665
|
|
|
* @return string |
1666
|
|
|
* @throws ImageHelper_Exception|OutputBuffering_Exception |
1667
|
|
|
*/ |
1668
|
|
|
public function getHash() : string |
1669
|
|
|
{ |
1670
|
|
|
if($this->newImage === null) |
1671
|
|
|
{ |
1672
|
|
|
throw new ImageHelper_Exception( |
1673
|
|
|
'No image loaded to create a hash for.', |
1674
|
|
|
'The newImage property is null.', |
1675
|
|
|
self::ERROR_HASH_NO_IMAGE_LOADED |
1676
|
|
|
); |
1677
|
|
|
} |
1678
|
|
|
|
1679
|
|
|
OutputBuffering::start(); |
1680
|
|
|
imagepng($this->newImage); |
1681
|
|
|
return md5(OutputBuffering::get()); |
1682
|
|
|
} |
1683
|
|
|
} |
1684
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.