suricate-php /
framework
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace Suricate; |
||
| 6 | |||
| 7 | use InvalidArgumentException; |
||
| 8 | use RuntimeException; |
||
| 9 | |||
| 10 | class Image |
||
| 11 | { |
||
| 12 | use Traits\ImageFilter; |
||
| 13 | |||
| 14 | public $source; |
||
| 15 | private $destination; |
||
| 16 | |||
| 17 | private $width; |
||
| 18 | private $height; |
||
| 19 | |||
| 20 | public function load($filename) |
||
| 21 | { |
||
| 22 | if (is_file($filename) && ($imgString = file_get_contents($filename))) { |
||
| 23 | $imgString = @imagecreatefromstring($imgString); |
||
| 24 | if ($imgString !== false) { |
||
| 25 | $this->source = $imgString; |
||
| 26 | |||
| 27 | if (is_callable('exif_read_data') && is_callable('exif_imagetype')) { |
||
| 28 | if (exif_imagetype($filename) === IMAGETYPE_JPEG) { |
||
| 29 | $exif = @exif_read_data($filename, 'IFD0'); |
||
| 30 | $exifOrientation = $exif['Orientation'] ?? 0; |
||
| 31 | $orientation = 0; |
||
| 32 | if (in_array($exifOrientation, [3, 6, 8])) { |
||
| 33 | $orientation = $exifOrientation; |
||
| 34 | } |
||
| 35 | if ($orientation == 3) { |
||
| 36 | $this->source = imagerotate($this->source, 180, 0); |
||
| 37 | } |
||
| 38 | if ($orientation == 8) { |
||
| 39 | $this->source = imagerotate($this->source, 90, 0); |
||
| 40 | } |
||
| 41 | if ($orientation == 6) { |
||
| 42 | $this->source = imagerotate($this->source, -90, 0); |
||
| 43 | } |
||
| 44 | } |
||
| 45 | } |
||
| 46 | $this->destination = $this->source; |
||
| 47 | $this->width = imagesx($this->source); |
||
| 48 | $this->height = imagesy($this->source); |
||
| 49 | return $this; |
||
| 50 | } |
||
| 51 | |||
| 52 | throw new InvalidArgumentException( |
||
| 53 | 'Cannot load ' . $filename . ', not an image' |
||
| 54 | ); |
||
| 55 | } |
||
| 56 | |||
| 57 | throw new InvalidArgumentException( |
||
| 58 | 'Cannot load ' . $filename . ', file unreadable' |
||
| 59 | ); |
||
| 60 | } |
||
| 61 | |||
| 62 | public function getWidth() |
||
| 63 | { |
||
| 64 | return $this->width; |
||
| 65 | } |
||
| 66 | |||
| 67 | public function getHeight() |
||
| 68 | { |
||
| 69 | return $this->height; |
||
| 70 | } |
||
| 71 | |||
| 72 | public function getResource() |
||
| 73 | { |
||
| 74 | return $this->source; |
||
| 75 | } |
||
| 76 | |||
| 77 | public function setResource($source) |
||
| 78 | { |
||
| 79 | if (!is_resource($source) && !($source instanceof \GdImage)) { |
||
| 80 | throw new InvalidArgumentException("Invalid source"); |
||
| 81 | } |
||
| 82 | |||
| 83 | $this->source = $source; |
||
| 84 | $this->width = imagesx($this->source); |
||
| 85 | $this->height = imagesy($this->source); |
||
| 86 | |||
| 87 | return $this; |
||
| 88 | } |
||
| 89 | |||
| 90 | public function create(int $width, int $height) |
||
| 91 | { |
||
| 92 | $this->source = imagecreatetruecolor($width, $height); |
||
| 93 | $this->destination = $this->source; |
||
| 94 | $this->width = $width; |
||
| 95 | $this->height = $height; |
||
| 96 | |||
| 97 | return $this; |
||
| 98 | } |
||
| 99 | |||
| 100 | /** |
||
| 101 | * Fill an image with a color |
||
| 102 | * |
||
| 103 | * @param integer $pointX X point coordinate |
||
| 104 | * @param integer $pointY Y point coordinate |
||
| 105 | * @param array $color RGB color |
||
| 106 | * |
||
| 107 | */ |
||
| 108 | public function fill(int $pointX, int $pointY, $color = [0, 0, 0]) |
||
| 109 | { |
||
| 110 | $color = imagecolorallocate( |
||
| 111 | $this->destination, |
||
| 112 | $color[0], |
||
| 113 | $color[1], |
||
| 114 | $color[2] |
||
| 115 | ); |
||
| 116 | |||
| 117 | imagefill($this->destination, $pointX, $pointY, $color); |
||
| 118 | |||
| 119 | return $this->chain(); |
||
| 120 | } |
||
| 121 | |||
| 122 | /** |
||
| 123 | * Return true if image is in portrait mode |
||
| 124 | * |
||
| 125 | * @return boolean |
||
| 126 | */ |
||
| 127 | public function isPortrait(): bool |
||
| 128 | { |
||
| 129 | return $this->width < $this->height; |
||
| 130 | } |
||
| 131 | |||
| 132 | /** |
||
| 133 | * Return true if image is in landscape mode |
||
| 134 | * |
||
| 135 | * @return boolean |
||
| 136 | */ |
||
| 137 | public function isLandscape(): bool |
||
| 138 | { |
||
| 139 | return $this->height < $this->width; |
||
| 140 | } |
||
| 141 | |||
| 142 | /** |
||
| 143 | * Apply modification to destination image |
||
| 144 | * |
||
| 145 | */ |
||
| 146 | public function chain(): self |
||
| 147 | { |
||
| 148 | $this->source = $this->destination; |
||
| 149 | $this->width = imagesx($this->source); |
||
| 150 | $this->height = imagesy($this->source); |
||
| 151 | |||
| 152 | return $this; |
||
| 153 | } |
||
| 154 | |||
| 155 | public function resize($width = null, $height = null) |
||
| 156 | { |
||
| 157 | if ($this->source) { |
||
| 158 | if ($width == null) { |
||
| 159 | $width = intval( |
||
| 160 | round(($height / $this->height) * $this->width, 0) |
||
| 161 | ); |
||
| 162 | } elseif ($height == null) { |
||
| 163 | $height = intval( |
||
| 164 | round(($width / $this->width) * $this->height, 0) |
||
| 165 | ); |
||
| 166 | } |
||
| 167 | |||
| 168 | $this->destination = imagecreatetruecolor($width, $height); |
||
| 169 | if ($this->destination === false) { |
||
| 170 | throw new RuntimeException("Can't create destination image"); |
||
| 171 | } |
||
| 172 | imagecopyresampled( |
||
| 173 | $this->destination, |
||
| 174 | $this->source, |
||
| 175 | 0, |
||
| 176 | 0, |
||
| 177 | 0, |
||
| 178 | 0, |
||
| 179 | $width, |
||
| 180 | $height, |
||
| 181 | $this->width, |
||
| 182 | $this->height |
||
| 183 | ); |
||
| 184 | |||
| 185 | return $this->chain(); |
||
| 186 | } |
||
| 187 | return $this; |
||
| 188 | } |
||
| 189 | |||
| 190 | public function crop($width, $height) |
||
| 191 | { |
||
| 192 | $centerX = round($this->width / 2); |
||
| 193 | $centerY = round($this->height / 2); |
||
| 194 | |||
| 195 | $cropWidthHalf = round($width / 2); |
||
| 196 | $cropHeightHalf = round($height / 2); |
||
| 197 | |||
| 198 | $x1 = intval(max(0, $centerX - $cropWidthHalf)); |
||
| 199 | $y1 = intval(max(0, $centerY - $cropHeightHalf)); |
||
| 200 | |||
| 201 | $this->destination = imagecreatetruecolor($width, $height); |
||
| 202 | if ($this->destination === false) { |
||
| 203 | throw new RuntimeException("Can't create destination image"); |
||
| 204 | } |
||
| 205 | imagecopy( |
||
| 206 | $this->destination, |
||
| 207 | $this->source, |
||
| 208 | 0, |
||
| 209 | 0, |
||
| 210 | $x1, |
||
| 211 | $y1, |
||
| 212 | $width, |
||
| 213 | $height |
||
| 214 | ); |
||
| 215 | |||
| 216 | return $this->chain(); |
||
| 217 | } |
||
| 218 | |||
| 219 | public function resizeCanvas( |
||
| 220 | $width, |
||
| 221 | $height, |
||
| 222 | $position = null, |
||
| 223 | $color = [0, 0, 0] |
||
| 224 | ) { |
||
| 225 | $this->destination = imagecreatetruecolor($width, $height); |
||
| 226 | if ($this->destination === false) { |
||
| 227 | throw new RuntimeException("Can't create destination image"); |
||
| 228 | } |
||
| 229 | $colorRes = imagecolorallocate( |
||
| 230 | $this->destination, |
||
| 231 | $color[0], |
||
| 232 | $color[1], |
||
| 233 | $color[2] |
||
| 234 | ); |
||
| 235 | $imageObj = new Image(); |
||
| 236 | $imageObj->width = $width; |
||
| 237 | $imageObj->height = $height; |
||
| 238 | imagefill($this->destination, 0, 0, $colorRes); |
||
| 239 | |||
| 240 | if ($position !== null) { |
||
| 241 | list($x, $y) = $imageObj->getCoordinatesFromString( |
||
| 242 | $position, |
||
| 243 | $this->width, |
||
| 244 | $this->height |
||
| 245 | ); |
||
| 246 | } else { |
||
| 247 | $x = 0; |
||
| 248 | $y = 0; |
||
| 249 | } |
||
| 250 | imagecopy( |
||
| 251 | $this->destination, |
||
| 252 | $this->source, |
||
| 253 | $x, |
||
| 254 | $y, |
||
| 255 | 0, |
||
| 256 | 0, |
||
| 257 | $this->width, |
||
| 258 | $this->height |
||
| 259 | ); |
||
| 260 | |||
| 261 | return $this->chain(); |
||
| 262 | } |
||
| 263 | |||
| 264 | public function rotate() |
||
| 265 | { |
||
| 266 | } |
||
| 267 | |||
| 268 | public function mirror() |
||
| 269 | { |
||
| 270 | } |
||
| 271 | |||
| 272 | public function flip() |
||
| 273 | { |
||
| 274 | } |
||
| 275 | |||
| 276 | public function merge( |
||
| 277 | $source, |
||
| 278 | $position = null, |
||
| 279 | $x = null, |
||
| 280 | $y = null, |
||
| 281 | $percent = 100 |
||
| 282 | ) { |
||
| 283 | if ($source instanceof \Suricate\Image) { |
||
| 284 | } else { |
||
| 285 | $source = with(new Image())->load($source); |
||
| 286 | } |
||
| 287 | |||
| 288 | if ($position !== null) { |
||
| 289 | list($x, $y) = $this->getCoordinatesFromString( |
||
| 290 | $position, |
||
| 291 | $source->width, |
||
| 292 | $source->height |
||
| 293 | ); |
||
| 294 | } |
||
| 295 | $x = $x !== null ? $x : 0; |
||
| 296 | $y = $y !== null ? $y : 0; |
||
| 297 | |||
| 298 | // Handle transparent image |
||
| 299 | // creating a cut resource |
||
| 300 | $cut = imagecreatetruecolor($source->getWidth(), $source->getHeight()); |
||
| 301 | if ($cut === false) { |
||
| 302 | throw new RuntimeException("Can't create destination image"); |
||
| 303 | } |
||
| 304 | // copying relevant section from background to the cut resource |
||
| 305 | imagecopy( |
||
| 306 | $cut, |
||
| 307 | $this->destination, |
||
| 308 | 0, |
||
| 309 | 0, |
||
| 310 | $x, |
||
| 311 | $y, |
||
| 312 | $source->getWidth(), |
||
| 313 | $source->getHeight() |
||
| 314 | ); |
||
| 315 | |||
| 316 | // copying relevant section from watermark to the cut resource |
||
| 317 | imagecopy( |
||
| 318 | $cut, |
||
| 319 | $source->source, |
||
| 320 | 0, |
||
| 321 | 0, |
||
| 322 | 0, |
||
| 323 | 0, |
||
| 324 | $source->getWidth(), |
||
| 325 | $source->getHeight() |
||
| 326 | ); |
||
| 327 | |||
| 328 | imagecopymerge( |
||
| 329 | $this->destination, |
||
| 330 | $cut, |
||
| 331 | $x, |
||
| 332 | $y, |
||
| 333 | 0, |
||
| 334 | 0, |
||
| 335 | $source->getWidth(), |
||
| 336 | $source->getHeight(), |
||
| 337 | $percent |
||
| 338 | ); |
||
| 339 | |||
| 340 | return $this->chain(); |
||
| 341 | } |
||
| 342 | |||
| 343 | public function writeText($text, $x = 0, $y = 0, ?\Closure $callback = null) |
||
| 344 | { |
||
| 345 | if ($x < 0) { |
||
| 346 | $x = $this->width + $x; |
||
| 347 | } |
||
| 348 | if ($y < 0) { |
||
| 349 | $y = $this->height + $y; |
||
| 350 | } |
||
| 351 | $imageFont = new ImageFont($text); |
||
| 352 | |||
| 353 | if ($callback != null) { |
||
| 354 | $callback($imageFont); |
||
| 355 | } |
||
| 356 | |||
| 357 | $imageFont->apply($this->source, $x, $y); |
||
| 358 | |||
| 359 | return $this; |
||
| 360 | } |
||
| 361 | |||
| 362 | public function line($x1, $y1, $x2, $y2, ?\Closure $callback = null) |
||
| 363 | { |
||
| 364 | $imageShape = new ImageShape(); |
||
| 365 | $imageShape->setImage($this->source); |
||
| 366 | if ($callback != null) { |
||
| 367 | $callback($imageShape); |
||
| 368 | } |
||
| 369 | |||
| 370 | $imageShape->drawLine($x1, $y1, $x2, $y2); |
||
| 371 | |||
| 372 | return $this; |
||
| 373 | } |
||
| 374 | |||
| 375 | /** |
||
| 376 | * Export image |
||
| 377 | * |
||
| 378 | * @param string $outputType output format |
||
| 379 | * @param integer $quality Output quality, when available |
||
| 380 | * |
||
| 381 | * @return void |
||
| 382 | */ |
||
| 383 | public function export($outputType, $quality = 70) |
||
| 384 | { |
||
| 385 | switch ($outputType) { |
||
| 386 | case 'jpg': |
||
| 387 | case 'jpeg': |
||
| 388 | imagejpeg($this->source, null, $quality); |
||
| 389 | break; |
||
| 390 | case 'png': |
||
| 391 | imagepng($this->source); |
||
| 392 | break; |
||
| 393 | case 'gif': |
||
| 394 | imagegif($this->source); |
||
| 395 | break; |
||
| 396 | case 'webp': |
||
| 397 | imagewebp($this->source, null, $quality); |
||
| 398 | break; |
||
| 399 | default: |
||
| 400 | throw new InvalidArgumentException( |
||
| 401 | sprintf("Invalid output format %s", $outputType) |
||
| 402 | ); |
||
| 403 | } |
||
| 404 | } |
||
| 405 | |||
| 406 | public function save($filename, $outputType = null, $quality = 70) |
||
| 407 | { |
||
| 408 | $result = false; |
||
| 409 | |||
| 410 | $extension = |
||
| 411 | $outputType === null |
||
| 412 | ? pathinfo($filename, PATHINFO_EXTENSION) |
||
| 413 | : $outputType; |
||
| 414 | |||
| 415 | if ($extension !== false) { |
||
| 416 | switch (strtolower($extension)) { |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 417 | case 'jpg': |
||
| 418 | case 'jpeg': |
||
| 419 | $result = imagejpeg($this->source, $filename, $quality); |
||
| 420 | break; |
||
| 421 | case 'png': |
||
| 422 | $result = imagepng($this->source, $filename); |
||
| 423 | break; |
||
| 424 | case 'gif': |
||
| 425 | $result = imagegif($this->source, $filename); |
||
| 426 | break; |
||
| 427 | case 'webp': |
||
| 428 | $result = imagewebp($this->source, $filename, $quality); |
||
| 429 | break; |
||
| 430 | } |
||
| 431 | } |
||
| 432 | |||
| 433 | return $result; |
||
| 434 | } |
||
| 435 | |||
| 436 | private function getCoordinatesFromString( |
||
| 437 | $position, |
||
| 438 | $offsetWidth = 0, |
||
| 439 | $offsetHeight = 0 |
||
| 440 | ) { |
||
| 441 | switch ($position) { |
||
| 442 | case 'top': |
||
| 443 | $x = floor($this->width / 2 - $offsetWidth / 2); |
||
| 444 | $y = 0; |
||
| 445 | break; |
||
| 446 | case 'top-right': |
||
| 447 | $x = $this->width - $offsetWidth; |
||
| 448 | $y = 0; |
||
| 449 | break; |
||
| 450 | case 'left': |
||
| 451 | $x = 0; |
||
| 452 | $y = floor($this->height / 2 - $offsetHeight / 2); |
||
| 453 | break; |
||
| 454 | case 'center': |
||
| 455 | $x = floor($this->width / 2 - $offsetWidth / 2); |
||
| 456 | $y = floor($this->height / 2 - $offsetHeight / 2); |
||
| 457 | break; |
||
| 458 | case 'right': |
||
| 459 | $x = $this->width - $offsetWidth; |
||
| 460 | $y = floor($this->height / 2 - $offsetHeight / 2); |
||
| 461 | break; |
||
| 462 | case 'bottom-left': |
||
| 463 | $x = 0; |
||
| 464 | $y = $this->height - $offsetHeight; |
||
| 465 | break; |
||
| 466 | case 'bottom': |
||
| 467 | $x = floor($this->width / 2 - $offsetWidth / 2); |
||
| 468 | $y = $this->height - $offsetHeight; |
||
| 469 | break; |
||
| 470 | case 'bottom-right': |
||
| 471 | $x = $this->width - $offsetWidth; |
||
| 472 | $y = $this->height - $offsetHeight; |
||
| 473 | break; |
||
| 474 | |||
| 475 | case 'top-left': |
||
| 476 | default: |
||
| 477 | $x = 0; |
||
| 478 | $y = 0; |
||
| 479 | break; |
||
| 480 | } |
||
| 481 | |||
| 482 | return [intval($x), intval($y)]; |
||
| 483 | } |
||
| 484 | } |
||
| 485 |