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 | 14 | public function __construct(string $image) |
|
20 | { |
||
21 | 14 | $this->init($image); |
|
22 | } |
||
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 | 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 | 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 | 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; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
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 | 12 | protected function setSizes(): void |
|
234 | { |
||
235 | 12 | $this->setWidth(imagesx($this->getImage())); |
|
236 | 12 | $this->setHeight(imagesy($this->getImage())); |
|
237 | 12 | $this->setOrientation(); |
|
238 | } |
||
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 | public function __toString(): string |
||
282 | { |
||
283 | $temp = new SplFileObject(tempnam(sys_get_temp_dir(), 'image' . mt_rand()), 'w+'); |
||
284 | imagepng($this->getImage(), $temp->getRealPath(), 9, PNG_ALL_FILTERS); |
||
285 | |||
286 | 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 | 0, |
||
318 | 0, |
||
319 | $newWidth, |
||
320 | $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 | 13 | protected function tmp(string $source): ImageInterface |
|
337 | { |
||
338 | 13 | if (!$image = @imagecreatefromstring($source)) { |
|
339 | 1 | throw new InvalidArgumentException('Image create failed'); |
|
340 | } |
||
341 | 12 | $this->setImage($image); |
|
342 | 12 | $this->setSizes(); |
|
343 | // save transparent |
||
344 | 12 | imagesavealpha($this->getImage(), true); |
|
345 | 12 | imagealphablending($this->getImage(), false); |
|
346 | |||
347 | 12 | 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 |