1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace Compolomus\Compomage; |
4
|
|
|
|
5
|
|
|
use Compolomus\Compomage\Interfaces\ImageInterface; |
6
|
|
|
use Exception; |
7
|
|
|
use InvalidArgumentException; |
8
|
|
|
use LogicException; |
9
|
|
|
use RangeException; |
10
|
|
|
use SplFileObject; |
11
|
|
|
|
12
|
|
|
class GD extends AbstractImage |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* GD constructor. |
16
|
|
|
* @param string $image |
17
|
|
|
* @throws Exception |
18
|
|
|
*/ |
19
|
9 |
|
public function __construct(string $image) |
20
|
|
|
{ |
21
|
9 |
|
$this->init($image); |
22
|
7 |
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @param int $width |
26
|
|
|
* @param int $height |
27
|
|
|
* @return ImageInterface |
28
|
|
|
* @throws Exception |
29
|
|
|
*/ |
30
|
|
|
public function resizeByTransparentBackground(int $width, int $height): ImageInterface |
31
|
|
|
{ |
32
|
|
|
$temp = new SplFileObject(tempnam(sys_get_temp_dir(), 'image' . mt_rand()), 'w+'); |
33
|
|
|
imagepng($this->newImage($width, $height), $temp->getRealPath(), 9, PNG_ALL_FILTERS); |
34
|
|
|
|
35
|
|
|
return $this->setBackground($width, $height, new Image($temp->getRealPath(), Image::GD)); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @param int $width |
40
|
|
|
* @param int $height |
41
|
|
|
* @return ImageInterface |
42
|
|
|
* @throws Exception |
43
|
|
|
*/ |
44
|
|
View Code Duplication |
public function resizeByBlurBackground(int $width, int $height): ImageInterface |
|
|
|
|
45
|
|
|
{ |
46
|
|
|
$background = new Image(base64_encode((string) $this), Image::GD); |
47
|
|
|
$background->blur()->resize($width, $height); |
48
|
|
|
|
49
|
|
|
return $this->setBackground($width, $height, $background); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param int $level |
54
|
|
|
* @return ImageInterface |
55
|
|
|
*/ |
56
|
|
View Code Duplication |
public function brightness(int $level): ImageInterface |
|
|
|
|
57
|
|
|
{ |
58
|
|
|
if (!$this->compareRangeValue($level, 255)) |
59
|
|
|
{ |
60
|
|
|
throw new InvalidArgumentException('Wrong brightness level, range -255 - 255, ' . $level . ' given'); |
61
|
|
|
} |
62
|
|
|
imagefilter($this->getImage(), IMG_FILTER_BRIGHTNESS, $level); |
63
|
|
|
|
64
|
|
|
return $this; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @param int $level |
69
|
|
|
* @return ImageInterface |
70
|
|
|
*/ |
71
|
|
View Code Duplication |
public function contrast(int $level): ImageInterface |
|
|
|
|
72
|
|
|
{ |
73
|
|
|
if (!$this->compareRangeValue($level, 100)) |
74
|
|
|
{ |
75
|
|
|
throw new InvalidArgumentException('Wrong contrast level, range -100 - 100, ' . $level . ' given'); |
76
|
|
|
} |
77
|
|
|
imagefilter($this->getImage(), IMG_FILTER_CONTRAST, $level); |
78
|
|
|
|
79
|
|
|
return $this; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @return ImageInterface |
84
|
|
|
*/ |
85
|
|
|
public function negate(): ImageInterface |
86
|
|
|
{ |
87
|
|
|
imagefilter($this->getImage(), IMG_FILTER_NEGATE); |
88
|
|
|
|
89
|
|
|
return $this; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @return ImageInterface |
94
|
|
|
*/ |
95
|
|
|
public function blur(): ImageInterface |
96
|
|
|
{ |
97
|
|
|
for ($i = 0; $i < 30; $i++) { |
98
|
|
|
if ($i % 5 === 0) { |
99
|
|
|
imagefilter($this->getImage(), IMG_FILTER_SMOOTH, -7); |
100
|
|
|
} |
101
|
|
|
imagefilter($this->getImage(), IMG_FILTER_GAUSSIAN_BLUR); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
return $this; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @return ImageInterface |
109
|
|
|
*/ |
110
|
|
|
public function flip(): ImageInterface |
111
|
|
|
{ |
112
|
|
|
imageflip($this->getImage(), IMG_FLIP_VERTICAL); |
113
|
|
|
|
114
|
|
|
return $this; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @return ImageInterface |
119
|
|
|
*/ |
120
|
|
|
public function flop(): ImageInterface |
121
|
|
|
{ |
122
|
|
|
imageflip($this->getImage(), IMG_FLIP_HORIZONTAL); |
123
|
|
|
|
124
|
|
|
return $this; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @return ImageInterface |
129
|
|
|
*/ |
130
|
|
|
public function grayscale(): ImageInterface |
131
|
|
|
{ |
132
|
|
|
imagefilter($this->getImage(), IMG_FILTER_GRAYSCALE); |
133
|
|
|
|
134
|
|
|
return $this; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @param string $text |
139
|
|
|
* @param string $font |
140
|
|
|
* @param string $position |
141
|
|
|
* @return $this |
142
|
|
|
* @throws InvalidArgumentException |
143
|
|
|
* @throws Exception |
144
|
|
|
*/ |
145
|
1 |
|
public function copyright(string $text, string $font, string $position = 'SouthWest'): ImageInterface |
146
|
|
|
{ |
147
|
1 |
|
if (!array_key_exists(strtoupper($position), self::POSITIONS)) { |
148
|
1 |
|
throw new InvalidArgumentException('Wrong position'); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
imagecopymerge( |
152
|
|
|
$this->getImage(), |
153
|
|
|
$image = $this->prepareImage($text, $font), |
154
|
|
|
(int) ((($this->getWidth() - imagesx($image)) / 2) * self::POSITIONS[strtoupper($position)]['x']) + self::POSITIONS[strtoupper($position)]['padX'], |
155
|
|
|
(int) ((($this->getHeight() - imagesy($image)) / 2) * self::POSITIONS[strtoupper($position)]['y']) + self::POSITIONS[strtoupper($position)]['padY'], |
156
|
|
|
0, |
157
|
|
|
0, |
158
|
|
|
$this->getWidth(), |
159
|
|
|
$this->getHeight(), |
160
|
|
|
80 |
161
|
|
|
); |
162
|
|
|
|
163
|
|
|
return $this; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @param string $text |
168
|
|
|
* @param string $font |
169
|
|
|
* @return resource |
170
|
|
|
* @throws InvalidArgumentException |
171
|
|
|
* @throws Exception |
172
|
|
|
*/ |
173
|
|
|
private function prepareImage(string $text, string $font) |
174
|
|
|
{ |
175
|
|
|
if (!$coordinates = imagettfbbox($fontSize = 15, 0, $font, $text)) { |
176
|
|
|
throw new InvalidArgumentException('Does not support font'); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$minX = min([$coordinates[0], $coordinates[2], $coordinates[4], $coordinates[6]]); |
180
|
|
|
$maxX = max([$coordinates[0], $coordinates[2], $coordinates[4], $coordinates[6]]); |
181
|
|
|
$minY = min([$coordinates[1], $coordinates[3], $coordinates[5], $coordinates[7]]); |
182
|
|
|
$maxY = max([$coordinates[1], $coordinates[3], $coordinates[5], $coordinates[7]]); |
183
|
|
|
$textX = (int) abs($minX) + 1; |
184
|
|
|
$textY = (int) abs($minY) + 1; |
185
|
|
|
$image = $this->newImage($maxX - $minX + 2, $maxY - $minY + 2); |
186
|
|
|
imagecolortransparent($image, $white = imagecolorallocate($image, 0, 0, 0)); |
187
|
|
|
imagefilledrectangle($image, 0, 0, $this->getWidth(), 20, $white); |
188
|
|
|
imagettftext($image, $fontSize, 0, $textX, $textY, $white, $font, $text); |
189
|
|
|
imagettftext($image, $fontSize, 0, $textX + 1, $textY + 1, $blue = imagecolorallocate($image, 0, 128, 128), |
190
|
|
|
$font, $text); |
191
|
|
|
imagettftext($image, $fontSize, 0, $textX + 1, $textY + 1, $red = imagecolorallocate($image, 255, 0, 0), $font, |
192
|
|
|
$text); |
193
|
|
|
imagettftext($image, $fontSize, 0, $textX + 2, $textY + 2, $black = imagecolorallocate($image, 255, 255, 255), |
194
|
|
|
$font, $text); |
195
|
|
|
imagettftext($image, $fontSize, 0, $textX + 2, $textY + 2, $gray = imagecolorallocate($image, 128, 128, 128), |
196
|
|
|
$font, $text); |
197
|
|
|
|
198
|
|
|
return $image; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @param int $width |
203
|
|
|
* @param int $height |
204
|
|
|
* @return resource |
205
|
|
|
*/ |
206
|
3 |
|
protected function newImage(int $width, int $height) |
207
|
|
|
{ |
208
|
3 |
|
$newimage = imagecreatetruecolor($width, $height); |
209
|
3 |
|
$transparent = imagecolorallocatealpha($newimage, 255, 255, 255, 127); |
210
|
3 |
|
imagefill($newimage, 0, 0, $transparent); |
211
|
3 |
|
imagealphablending($newimage, true); |
212
|
3 |
|
imagesavealpha($newimage, true); |
213
|
|
|
|
214
|
3 |
|
return $newimage; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* @param int $width |
219
|
|
|
* @param int $height |
220
|
|
|
* @return ImageInterface |
221
|
|
|
*/ |
222
|
2 |
|
public function resize(int $width, int $height): ImageInterface |
223
|
|
|
{ |
224
|
2 |
|
$newimage = $this->newImage($width, $height); |
225
|
2 |
|
imagecopyresampled($newimage, $this->getImage(), 0, 0, 0, 0, $width, $height, $this->getWidth(), |
226
|
2 |
|
$this->getHeight()); |
227
|
2 |
|
$this->setImage($newimage); |
228
|
2 |
|
$this->setSizes(); |
229
|
|
|
|
230
|
2 |
|
return $this; |
231
|
|
|
} |
232
|
|
|
|
233
|
8 |
|
protected function setSizes(): void |
234
|
|
|
{ |
235
|
8 |
|
$this->setWidth(imagesx($this->getImage())); |
236
|
8 |
|
$this->setHeight(imagesy($this->getImage())); |
237
|
8 |
|
$this->setOrientation(); |
238
|
8 |
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @param int $width |
242
|
|
|
* @param int $height |
243
|
|
|
* @param int $x |
244
|
|
|
* @param int $y |
245
|
|
|
* @return ImageInterface |
246
|
|
|
*/ |
247
|
|
|
public function crop(int $width, int $height, int $x, int $y): ImageInterface |
248
|
|
|
{ |
249
|
|
|
$width -= $x; |
250
|
|
|
$height -= $y; |
251
|
|
|
$newimage = $this->newImage($width, $height); |
252
|
|
|
imagecopyresampled($newimage, $this->getImage(), 0, 0, $x, $y, $width, $height, $width, $height); |
253
|
|
|
$this->setImage($newimage); |
254
|
|
|
$this->setSizes(); |
255
|
|
|
|
256
|
|
|
return $this; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* @param int $angle |
261
|
|
|
* @return ImageInterface |
262
|
|
|
*/ |
263
|
1 |
|
public function rotate(int $angle = 90): ImageInterface |
264
|
|
|
{ |
265
|
1 |
|
$angle *= -1; |
266
|
1 |
|
$transparent = imagecolorallocatealpha($this->image, 0, 0, 0, 127); |
267
|
1 |
|
$rotate = imagerotate($this->getImage(), $angle, $transparent); |
268
|
1 |
|
imagealphablending($rotate, true); |
269
|
1 |
|
imagesavealpha($rotate, true); |
270
|
1 |
|
$this->setImage($rotate); |
271
|
1 |
|
$this->setSizes(); |
272
|
|
|
|
273
|
1 |
|
return $this; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* @throws LogicException |
278
|
|
|
* @throws RangeException |
279
|
|
|
* @return string |
280
|
|
|
*/ |
281
|
1 |
|
public function __toString(): string |
282
|
|
|
{ |
283
|
1 |
|
$temp = new SplFileObject(tempnam(sys_get_temp_dir(), 'image' . mt_rand()), 'w+'); |
284
|
1 |
|
imagepng($this->getImage(), $temp->getRealPath(), 9, PNG_ALL_FILTERS); |
285
|
|
|
|
286
|
1 |
|
return trim(file_get_contents($temp->getRealPath())); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* @param string $filename |
291
|
|
|
* @param int $quality |
292
|
|
|
* @return bool |
293
|
|
|
*/ |
294
|
1 |
|
public function save(string $filename, $quality = 100): bool |
295
|
|
|
{ |
296
|
1 |
|
return imagepng($this->getImage(), $filename . '.png', (int) ($quality / 11), PNG_ALL_FILTERS); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* @param int $width |
301
|
|
|
* @param int $height |
302
|
|
|
* @param int $newWidth |
303
|
|
|
* @param int $newHeight |
304
|
|
|
* @return ImageInterface |
305
|
|
|
*/ |
306
|
1 |
|
protected function prepareThumbnail( |
307
|
|
|
int $width, |
308
|
|
|
int $height, |
309
|
|
|
int $newWidth = 0, |
310
|
|
|
int $newHeight = 0 |
311
|
|
|
): ImageInterface { |
312
|
1 |
|
imagecopyresampled( |
313
|
1 |
|
$newimage = $this->newImage($width, $height), |
314
|
1 |
|
$this->getImage(), |
315
|
1 |
|
0 - (int) (($newWidth - $width) / 2), |
316
|
1 |
|
0 - (int) (($newHeight - $height) / 2), |
317
|
1 |
|
0, |
318
|
1 |
|
0, |
319
|
1 |
|
$newWidth, |
320
|
1 |
|
$newHeight, |
321
|
1 |
|
$this->getWidth(), |
322
|
1 |
|
$this->getHeight() |
323
|
|
|
); |
324
|
1 |
|
$this->setImage($newimage); |
325
|
1 |
|
$this->setSizes(); |
326
|
|
|
|
327
|
1 |
|
return $this; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* @param string $source |
332
|
|
|
* @return ImageInterface |
333
|
|
|
* @throws InvalidArgumentException |
334
|
|
|
* @throws Exception |
335
|
|
|
*/ |
336
|
8 |
|
protected function tmp(string $source): ImageInterface |
337
|
|
|
{ |
338
|
8 |
|
if (@!is_resource($image = @imagecreatefromstring($source))) { |
339
|
1 |
|
throw new InvalidArgumentException('Image create failed'); |
340
|
|
|
} |
341
|
7 |
|
$this->setImage($image); |
342
|
7 |
|
$this->setSizes(); |
343
|
|
|
// save transparent |
344
|
7 |
|
imagesavealpha($this->getImage(), true); |
345
|
7 |
|
imagealphablending($this->getImage(), false); |
346
|
|
|
|
347
|
7 |
|
return $this; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* @param ImageInterface $watermark |
352
|
|
|
* @param int $x |
353
|
|
|
* @param int $y |
354
|
|
|
* @return ImageInterface |
355
|
|
|
*/ |
356
|
|
|
protected function prepareWatermark($watermark, int $x, int $y): ImageInterface |
357
|
|
|
{ |
358
|
|
|
imagealphablending($this->getImage(), true); |
359
|
|
|
imagesavealpha($this->getImage(), true); |
360
|
|
|
imagecopyresampled( |
361
|
|
|
$this->getImage(), |
362
|
|
|
$watermark->getImage(), |
363
|
|
|
$x, |
364
|
|
|
$y, |
365
|
|
|
0, |
366
|
|
|
0, |
367
|
|
|
$width = $watermark->getWidth(), |
368
|
|
|
$height = $watermark->getHeight(), |
369
|
|
|
$width, |
370
|
|
|
$height |
371
|
|
|
); |
372
|
|
|
|
373
|
|
|
return $this; |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
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.