├── ImageTool.php ├── README.mdown └── composer.json /ImageTool.php: -------------------------------------------------------------------------------- 1 | null, 33 | 'scale' => false, 34 | 'strech' => false, 35 | 'repeat' => false, 36 | 'watermark' => null, 37 | 'output' => null, 38 | 'input' => null, 39 | 'position' => 'center', 40 | 'compression' => 9, 41 | 'quality' => 100, 42 | 'chmod' => null, 43 | 'opacity' => 100 44 | ], $options); 45 | 46 | // if output path (directories) doesn't exist, try to make whole path 47 | if (!self::createPath($options['output'])) { 48 | return false; 49 | } 50 | 51 | $img = self::openImage($options['input']); 52 | unset($options['input']); 53 | if (empty($img)) { 54 | return false; 55 | } 56 | 57 | $src_wm = self::openImage($options['watermark']); 58 | unset($options['watermark']); 59 | if (empty($src_wm)) { 60 | return false; 61 | } 62 | 63 | // image size 64 | $img_im_w = imagesx($img); 65 | $img_im_h = imagesy($img); 66 | 67 | // watermark size 68 | $img_wm_w = imagesx($src_wm); 69 | $img_wm_h = imagesy($src_wm); 70 | 71 | if ($options['scale']) { 72 | if ($options['strech']) { 73 | $r = imagecopyresampled($img, $src_wm, 0, 0, 0, 0, $img_im_w, $img_im_h, $img_wm_w, $img_wm_h); 74 | } else { 75 | $x = 0; 76 | $y = 0; 77 | $w = $img_im_w; 78 | $h = $img_im_h; 79 | 80 | if (($img_im_w / $img_im_h) > ($img_wm_w / $img_wm_h)) { 81 | $ratio = $img_im_h / $img_wm_h; 82 | $w = $ratio * $img_wm_w; 83 | $x = round(($img_im_w - $w) / 2); 84 | } else { 85 | $ratio = $img_im_w / $img_wm_w; 86 | $h = $ratio * $img_wm_h; 87 | $y = round(($img_im_h - $h) / 2); 88 | } 89 | 90 | $r = imagecopyresampled($img, $src_wm, $x, $y, 0, 0, $w, $h, $img_wm_w, $img_wm_h); 91 | } 92 | } else if ($options['repeat']) { 93 | if (is_array($options['position'])) { 94 | $options['position'] = 5; 95 | } 96 | 97 | switch ($options['position']) { 98 | case 'top-left': 99 | for ($y=0; $y<$img_im_h; $y+=$img_wm_h) { 100 | for ($x=0; $x<$img_im_w; $x+=$img_wm_w) { 101 | $r = self::imagecopymerge_alpha($img, $src_wm, $x, $y, 0, 0, $img_wm_w, $img_wm_h, $options['opacity']); 102 | } 103 | } 104 | break; 105 | 106 | case 'top-right': 107 | for ($y=0; $y<$img_im_h; $y+=$img_wm_h) { 108 | for ($x=$img_im_w; $x>-$img_wm_w; $x-=$img_wm_w) { 109 | $r = self::imagecopymerge_alpha($img, $src_wm, $x, $y, 0, 0, $img_wm_w, $img_wm_h, $options['opacity']); 110 | } 111 | } 112 | break; 113 | 114 | case 'bottom-right': 115 | for ($y=$img_im_h; $y>-$img_wm_h; $y-=$img_wm_h) { 116 | for ($x=$img_im_w; $x>-$img_wm_w; $x-=$img_wm_w) { 117 | $r = self::imagecopymerge_alpha($img, $src_wm, $x, $y, 0, 0, $img_wm_w, $img_wm_h, $options['opacity']); 118 | } 119 | } 120 | break; 121 | 122 | case 'bottom-left': 123 | for ($y=$img_im_h; $y>-$img_wm_h; $y-=$img_wm_h) { 124 | for ($x=0; $x<$img_im_w; $x+=$img_wm_w) { 125 | $r = self::imagecopymerge_alpha($img, $src_wm, $x, $y, 0, 0, $img_wm_w, $img_wm_h, $options['opacity']); 126 | } 127 | } 128 | break; 129 | 130 | case 'center': 131 | default: 132 | $pos_x = -(($img_im_w%$img_wm_w)/2); 133 | $pos_y = -(($img_im_h%$img_wm_h)/2); 134 | 135 | for ($y=$pos_y; $y<$img_im_h; $y+=$img_wm_h) { 136 | for ($x=$pos_x; $x<$img_im_w; $x+=$img_wm_w) { 137 | $r = self::imagecopymerge_alpha($img, $src_wm, $x, $y, 0, 0, $img_wm_w, $img_wm_h, $options['opacity']); 138 | } 139 | } 140 | break; 141 | } 142 | } else { 143 | // custom location 144 | if (is_array($options['position'])) { 145 | list($pos_x, $pos_y) = $options['position']; 146 | } else { 147 | // predefined location 148 | switch ($options['position']) { 149 | case 'top-left': 150 | $pos_x = 0; 151 | $pos_y = 0; 152 | break; 153 | 154 | case 'top-right': 155 | $pos_x = $img_im_w - $img_wm_w; 156 | $pos_y = 0; 157 | break; 158 | 159 | case 'bottom-right': 160 | $pos_x = $img_im_w - $img_wm_w; 161 | $pos_y = $img_im_h - $img_wm_h; 162 | break; 163 | 164 | case 'bottom-left': 165 | $pos_x = 0; 166 | $pos_y = $img_im_h - $img_wm_h; 167 | break; 168 | 169 | case 'center': 170 | default: 171 | $pos_x = round(($img_im_w - $img_wm_w) / 2); 172 | $pos_y = round(($img_im_h - $img_wm_h) / 2); 173 | break; 174 | } 175 | } 176 | 177 | $r = self::imagecopymerge_alpha($img, $src_wm, $pos_x, $pos_y, 0, 0, $img_wm_w, $img_wm_h, $options['opacity']); 178 | } 179 | 180 | if (!$r) { 181 | return false; 182 | } 183 | 184 | if (!self::afterCallbacks($img, $options['afterCallbacks'])) { 185 | return false; 186 | } 187 | 188 | return self::saveImage($img, $options); 189 | } 190 | 191 | /** 192 | * Resize image 193 | * 194 | * Options: 195 | * - 'input' Input file (path or gd resource) 196 | * - 'output' Output path. If not specified, gd resource is returned 197 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 198 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 199 | * - 'units' Scale units. Percents ('%') and pixels ('px') are avalaible 200 | * - 'enlarge' if set to false and width or height of the destination image is bigger than source image's width or height, then leave source image's dimensions untouched 201 | * - 'chmod' What permissions should be applied to destination image 202 | * - 'paddings' If not empty and both output width and height is specified and mode=fit, padding borders are applied. You can specify color here. If true, then white color will be applied 203 | * - 'mode' How to handle image size. Possible options: crop, fit and stretch 204 | * - 'afterCallbacks' Functions to be executed after resize. Example: [['unsharpMask', 70, 3.9, 0]]; First passed argument is f-ion name. Executed function's first argument must be gd image instance 205 | * - 'height' Output image's height. If left empty, this is auto calculated (if possible) 206 | * - 'width' Output image's width. If left empty, this is auto calculated (if possible) 207 | * 208 | * @param array $options An array of options 209 | * @return mixed boolean or GD resource if output was set to null 210 | */ 211 | public static function resize($options = []) { 212 | $options = array_merge([ 213 | 'afterCallbacks' => null, 214 | 'compression' => null, 215 | 'paddings' => false, 216 | 'enlarge' => true, 217 | 'quality' => null, 218 | 'chmod' => null, 219 | 'mode' => 'crop', 220 | 'units' => 'px', 221 | 'height' => null, 222 | 'output' => null, 223 | 'width' => null, 224 | 'input' => null 225 | ], $options); 226 | 227 | // if output path (directories) doesn't exist, try to make whole path 228 | if (!self::createPath($options['output'])) { 229 | return false; 230 | } 231 | 232 | $input_extension = self::getImageType($options['input']); 233 | $output_extension = self::getExtension($options['output']); 234 | 235 | $src_im = self::openImage($options['input']); 236 | 237 | if (!$src_im) { 238 | return false; 239 | } 240 | 241 | // calculate new w, h, x and y 242 | 243 | if (!empty($options['width']) && !is_numeric($options['width'])) { 244 | return false; 245 | } 246 | if (!empty($options['height']) && !is_numeric($options['height'])) { 247 | return false; 248 | } 249 | 250 | // get size of the original image 251 | $input_width = imagesx($src_im); 252 | $input_height = imagesy($src_im); 253 | 254 | //calculate destination image w/h 255 | 256 | // turn % into px 257 | if ($options['units'] == '%') { 258 | if ($options['height'] != null) { 259 | $options['height'] = round($input_height * $options['height'] / 100); 260 | } 261 | 262 | if ($options['width'] != null) { 263 | $options['width'] = round($input_width * $options['width'] / 100); 264 | } 265 | } 266 | 267 | // if mode=fit, check output width/height and update them as neccessary 268 | if ($options['mode'] === 'fit' && $options['width'] != null && $options['height'] != null) { 269 | $input_ratio = $input_width / $input_height; 270 | $output_ratio = $options['width'] / $options['height']; 271 | 272 | $original_width = $options['width']; 273 | $original_height = $options['height']; 274 | 275 | if ($input_ratio > $output_ratio) { 276 | $options['height'] = $input_height * $options['width'] / $input_width; 277 | } else { 278 | $options['width'] = $input_width * $options['height'] / $input_height; 279 | } 280 | } 281 | 282 | // calculate missing width/height (if any) 283 | if ($options['width'] == null && $options['height'] == null) { 284 | $options['width'] = $input_width; 285 | $options['height'] = $input_height; 286 | } else if ($options['height'] == null) { 287 | $options['height'] = round(($options['width'] * $input_height) / $input_width); 288 | } else if ($options['width'] == null) { 289 | $options['width'] = round(($options['height'] * $input_width) / $input_height); 290 | } 291 | 292 | $src_x = 0; 293 | $src_y = 0; 294 | $src_w = $input_width; 295 | $src_h = $input_height; 296 | 297 | if ($options['enlarge'] == false && ($options['width'] > $input_width || $options['height'] > $input_height)) { 298 | $options['width'] = $input_width; 299 | $options['height'] = $input_height; 300 | } else if ($options['mode'] === 'crop') { 301 | if (($input_width / $input_height) > ($options['width'] / $options['height'])) { 302 | $ratio = $input_height / $options['height']; 303 | $src_w = $ratio * $options['width']; 304 | $src_x = round(($input_width - $src_w) / 2); 305 | } else { 306 | $ratio = $input_width / $options['width']; 307 | $src_h = $ratio * $options['height']; 308 | $src_y = round(($input_height - $src_h) / 2); 309 | } 310 | } 311 | 312 | // if possible, just move file w/o modifying it 313 | $is_local = is_string($options['input']) && !preg_match('/^https?:\/\//', $options['input']); 314 | $is_same_type = $input_extension === $output_extension; 315 | $is_same_size = $input_width === $options['width'] && $input_height === $options['height']; 316 | if ($is_same_size && $is_same_type && $is_local && empty($options['afterCallbacks'])) { 317 | $r = copy($options['input'], $options['output']); 318 | 319 | if (!empty($options['chmod'])) { 320 | chmod($options['output'], $options['chmod']); 321 | } 322 | 323 | return $r; 324 | } 325 | 326 | $dst_im = imagecreatetruecolor($options['width'], $options['height']); 327 | 328 | if (!$dst_im) { 329 | imagedestroy($src_im); 330 | return false; 331 | } 332 | 333 | // transparency or white bg instead of black 334 | if (in_array($input_extension, ['png', 'gif'])) { 335 | if (in_array($output_extension, ['png', 'gif'])) { 336 | imagealphablending($dst_im, false); 337 | imagesavealpha($dst_im, true); 338 | $transparent = imagecolorallocatealpha($dst_im, 255, 255, 255, 127); 339 | imagefilledrectangle($dst_im, 0, 0,$options['width'], $options['height'], $transparent); 340 | } else { 341 | $white = imagecolorallocate($dst_im, 255, 255, 255); 342 | imagefilledrectangle($dst_im, 0, 0, $options['width'], $options['height'], $white); 343 | } 344 | } 345 | 346 | $r = imagecopyresampled($dst_im, $src_im, 0, 0, $src_x, $src_y, $options['width'], $options['height'], $src_w, $src_h); 347 | 348 | if (!$r) { 349 | imagedestroy($src_im); 350 | return false; 351 | } 352 | 353 | if ($options['mode'] === 'fit' && $options['paddings']) { 354 | if ($options['width'] != $original_width || $options['height'] != $original_height) { 355 | $bg_im = imagecreatetruecolor($original_width, $original_height); 356 | 357 | if (!$bg_im) { 358 | imagedestroy($bg_im); 359 | return false; 360 | } 361 | 362 | if ($options['paddings'] === true) { 363 | $rgb = [255, 255, 255]; 364 | } else { 365 | $rgb = self::readColor($options['paddings']); 366 | if (!$rgb) { 367 | $rgb = [255, 255, 255]; 368 | } 369 | } 370 | 371 | $color = imagecolorallocate($bg_im, $rgb[0], $rgb[1], $rgb[2]); 372 | imagefilledrectangle($bg_im, 0, 0, $original_width, $original_height, $color); 373 | 374 | $x = round(($original_width - $options['width']) / 2); 375 | $y = round(($original_height - $options['height']) / 2); 376 | 377 | imagecopy($bg_im, $dst_im, $x, $y, 0, 0, $options['width'], $options['height']); 378 | 379 | $dst_im = $bg_im; 380 | } 381 | } 382 | 383 | if (!self::afterCallbacks($dst_im, $options['afterCallbacks'])) { 384 | return false; 385 | } 386 | 387 | return self::saveImage($dst_im, $options); 388 | } 389 | 390 | /** 391 | * Apply unsharp mask to image 392 | * 393 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 394 | * Unsharp Mask for PHP - version 2.1.1 395 | * Unsharp mask algorithm by Torstein Hønsi 2003-07. 396 | * thoensi_at_netcom_dot_no. 397 | * Please leave this notice. 398 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 399 | * 400 | * Options: 401 | * - 'input' Input file (path or gd resource) 402 | * - 'output' Output path. If not specified, gd resource is returned 403 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 404 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 405 | * - 'afterCallbacks' Functions to be executed after this one 406 | * - 'chmod' What permissions should be applied to destination image 407 | * - 'threshold' 408 | * - 'amount' 409 | * - 'radius' 410 | * 411 | * @param array $options An array of options. 412 | * @return mixed boolean or GD resource if output was set to null 413 | */ 414 | public static function unsharpMask($options = []) { 415 | $options = array_merge([ 416 | 'afterCallbacks' => null, 417 | 'compression' => null, 418 | 'quality' => null, 419 | 'threshold' => 3, 420 | 'amount' => 50, 421 | 'radius' => 0.5, 422 | 'output' => null, 423 | 'input' => null, 424 | 'chmod' => null 425 | ], $options); 426 | 427 | $img = self::openImage($options['input']); 428 | unset($options['input']); 429 | 430 | if (!$img) { 431 | return false; 432 | } 433 | 434 | // Attempt to calibrate the parameters to Photoshop: 435 | 436 | if ($options['amount'] > 500) { 437 | $options['amount'] = 500; 438 | } 439 | 440 | $options['amount'] = $options['amount'] * 0.016; 441 | 442 | if ($options['radius'] > 50) { 443 | $options['radius'] = 50; 444 | } 445 | 446 | $options['radius'] = $options['radius'] * 2; 447 | 448 | if ($options['threshold'] > 255) { 449 | $options['threshold'] = 255; 450 | } 451 | 452 | // Only integers make sense. 453 | $options['radius'] = abs(round($options['radius'])); 454 | 455 | if ($options['radius'] == 0) { 456 | return self::saveImage($img, $options); 457 | } 458 | 459 | $w = imagesx($img); 460 | $h = imagesy($img); 461 | 462 | $imgCanvas = imagecreatetruecolor($w, $h); 463 | $imgBlur = imagecreatetruecolor($w, $h); 464 | 465 | // PHP >= 5.1 466 | if (function_exists('imageconvolution')) { 467 | $matrix = [ 468 | [1, 2, 1], 469 | [2, 4, 2], 470 | [1, 2, 1] 471 | ]; 472 | imagecopy ($imgBlur, $img, 0, 0, 0, 0, $w, $h); 473 | imageconvolution($imgBlur, $matrix, 16, 0); 474 | } else { 475 | // Move copies of the image around one pixel at the time and merge them with weight 476 | // according to the matrix. The same matrix is simply repeated for higher radii. 477 | for ($i = 0; $i < $options['radius']; $i++) { 478 | imagecopy ($imgBlur, $img, 0, 0, 1, 0, $w - 1, $h); // left 479 | imagecopymerge ($imgBlur, $img, 1, 0, 0, 0, $w, $h, 50); // right 480 | imagecopymerge ($imgBlur, $img, 0, 0, 0, 0, $w, $h, 50); // center 481 | imagecopy ($imgCanvas, $imgBlur, 0, 0, 0, 0, $w, $h); 482 | imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 1, $w, $h - 1, 33.33333 ); // up 483 | imagecopymerge ($imgBlur, $imgCanvas, 0, 1, 0, 0, $w, $h, 25); // down 484 | } 485 | } 486 | 487 | if($options['threshold'] > 0) { 488 | // Calculate the difference between the blurred pixels and the original 489 | // and set the pixels 490 | for ($x = 0; $x < $w-1; $x++) { // each row 491 | for ($y = 0; $y < $h; $y++) { // each pixel 492 | $rgbOrig = ImageColorAt($img, $x, $y); 493 | $rOrig = (($rgbOrig >> 16) & 0xFF); 494 | $gOrig = (($rgbOrig >> 8) & 0xFF); 495 | $bOrig = ($rgbOrig & 0xFF); 496 | 497 | $rgbBlur = ImageColorAt($imgBlur, $x, $y); 498 | 499 | $rBlur = (($rgbBlur >> 16) & 0xFF); 500 | $gBlur = (($rgbBlur >> 8) & 0xFF); 501 | $bBlur = ($rgbBlur & 0xFF); 502 | 503 | // When the masked pixels differ less from the original 504 | // than the threshold specifies, they are set to their original value. 505 | $rNew = (abs($rOrig - $rBlur) >= $options['threshold']) ? max(0, min(255, ($options['amount'] * ($rOrig - $rBlur)) + $rOrig)) : $rOrig; 506 | $gNew = (abs($gOrig - $gBlur) >= $options['threshold']) ? max(0, min(255, ($options['amount'] * ($gOrig - $gBlur)) + $gOrig)) : $gOrig; 507 | $bNew = (abs($bOrig - $bBlur) >= $options['threshold']) ? max(0, min(255, ($options['amount'] * ($bOrig - $bBlur)) + $bOrig)) : $bOrig; 508 | 509 | if (($rOrig != $rNew) || ($gOrig != $gNew) || ($bOrig != $bNew)) { 510 | $pixCol = ImageColorAllocate($img, $rNew, $gNew, $bNew); 511 | ImageSetPixel($img, $x, $y, $pixCol); 512 | } 513 | } 514 | } 515 | } else { 516 | for ($x = 0; $x < $w; $x++) { // each row 517 | for ($y = 0; $y < $h; $y++) { // each pixel 518 | $rgbOrig = ImageColorAt($img, $x, $y); 519 | $rOrig = (($rgbOrig >> 16) & 0xFF); 520 | $gOrig = (($rgbOrig >> 8) & 0xFF); 521 | $bOrig = ($rgbOrig & 0xFF); 522 | 523 | $rgbBlur = ImageColorAt($imgBlur, $x, $y); 524 | 525 | $rBlur = (($rgbBlur >> 16) & 0xFF); 526 | $gBlur = (($rgbBlur >> 8) & 0xFF); 527 | $bBlur = ($rgbBlur & 0xFF); 528 | 529 | $rNew = ($options['amount'] * ($rOrig - $rBlur)) + $rOrig; 530 | 531 | if($rNew>255){$rNew=255;} 532 | elseif($rNew<0){$rNew=0;} 533 | $gNew = ($options['amount'] * ($gOrig - $gBlur)) + $gOrig; 534 | if($gNew>255){$gNew=255;} 535 | elseif($gNew<0){$gNew=0;} 536 | $bNew = ($options['amount'] * ($bOrig - $bBlur)) + $bOrig; 537 | if($bNew>255){$bNew=255;} 538 | elseif($bNew<0){$bNew=0;} 539 | 540 | $rgbNew = ($rNew << 16) + ($gNew <<8) + $bNew; 541 | ImageSetPixel($img, $x, $y, $rgbNew); 542 | } 543 | } 544 | } 545 | 546 | if (!self::afterCallbacks($img, $options['afterCallbacks'])) { 547 | return false; 548 | } 549 | 550 | return self::saveImage($img, $options); 551 | } 552 | 553 | /** 554 | * Get file extension 555 | * 556 | * @param string $filename Filename 557 | * @return string 558 | */ 559 | public static function getExtension($filename) { 560 | if (!is_string($filename)) { 561 | return ''; 562 | } 563 | 564 | $pos = strrpos($filename, '.'); 565 | 566 | if ($pos === false) { 567 | return ''; 568 | } 569 | 570 | return strtolower(substr($filename, $pos + 1)); 571 | } 572 | 573 | /** 574 | * Open image as gd resource 575 | * 576 | * @param string $input Input (path) image 577 | * @return mixed 578 | */ 579 | public static function openImage($input) { 580 | if (is_resource($input)) { 581 | if (get_resource_type($input) == 'gd') { 582 | return $input; 583 | } 584 | } else { 585 | if (is_string($input) && preg_match('/^https?:\/\//', $input)) { 586 | $image = file_get_contents($input); 587 | if (!$image) { 588 | return false; 589 | } 590 | 591 | return imagecreatefromstring($image); 592 | } 593 | 594 | switch (self::getImageType($input)) { 595 | case 'jpg': 596 | return imagecreatefromjpeg($input); 597 | break; 598 | 599 | case 'png': 600 | return imagecreatefrompng($input); 601 | break; 602 | 603 | case 'gif': 604 | return imagecreatefromgif($input); 605 | break; 606 | 607 | case 'webp': 608 | return imagecreatefromwebp($input); 609 | break; 610 | } 611 | } 612 | 613 | return false; 614 | } 615 | 616 | /** 617 | * Get image type from file 618 | * 619 | * @param string $input Input (path) image 620 | * @param string $extension (optional) Extension (type) 621 | * @param boolean $extension If true, check by extension 622 | * @return string 623 | */ 624 | public static function getImageType($input, $extension = false) { 625 | if ($extension) { 626 | switch (self::getExtension($input)) { 627 | case 'jpg': 628 | case 'jpeg': 629 | return 'jpg'; 630 | break; 631 | 632 | case 'png': 633 | return 'png'; 634 | break; 635 | 636 | case 'gif': 637 | return 'gif'; 638 | break; 639 | 640 | case 'webp': 641 | return 'webp'; 642 | break; 643 | } 644 | } else if (is_string($input) && is_file($input)) { 645 | $info = getimagesize($input); 646 | 647 | switch ($info['mime']) { 648 | case 'image/pjpeg': 649 | case 'image/jpeg': 650 | case 'image/jpg': 651 | return 'jpg'; 652 | break; 653 | 654 | case 'image/x-png': 655 | case 'image/png': 656 | return 'png'; 657 | break; 658 | 659 | case 'image/gif': 660 | return 'gif'; 661 | break; 662 | 663 | case 'image/webp': 664 | return 'webp'; 665 | break; 666 | } 667 | } 668 | 669 | return ''; 670 | } 671 | 672 | /** 673 | * Save image gd resource as image 674 | * 675 | * Image type is determined by $output extension so it must be present. 676 | * 677 | * Options: 678 | * - 'compression' Output image's compression. Currently only PNG (value 0-9) supports this 679 | * - 'quality' Output image's quality. Currently only JPG (value 0-100) supports this 680 | * - 'output' Output path. If not specified, image resource is returned 681 | * 682 | * @param mixed $im Image resource 683 | * @param string $output Output path 684 | * @param mixed $options An array of additional options 685 | * @return boolean 686 | */ 687 | public static function saveImage(&$im, $options = []) { 688 | foreach (['compression', 'quality', 'chmod'] as $v) { 689 | if (is_null($options[$v])) { 690 | unset($options[$v]); 691 | } 692 | } 693 | 694 | $options = array_merge([ 695 | 'compression' => 9, 696 | 'quality' => 100, 697 | 'output' => null 698 | ], $options); 699 | 700 | switch (self::getImageType($options['output'], true)) { 701 | case 'jpg': 702 | if (ImageJPEG($im, $options['output'], $options['quality'])) { 703 | if (!empty($options['chmod'])) { 704 | chmod($options['output'], $options['chmod']); 705 | } 706 | return true; 707 | } 708 | break; 709 | 710 | case 'png': 711 | if (ImagePNG($im, $options['output'], $options['compression'])) { 712 | if (!empty($options['chmod'])) { 713 | chmod($options['output'], $options['chmod']); 714 | } 715 | return true; 716 | } 717 | break; 718 | 719 | case 'gif': 720 | if (ImageGIF($im, $options['output'])) { 721 | if (!empty($options['chmod'])) { 722 | chmod($options['output'], $options['chmod']); 723 | } 724 | return true; 725 | } 726 | break; 727 | 728 | case '': 729 | return $im; 730 | break; 731 | } 732 | 733 | unset($im); 734 | return false; 735 | } 736 | 737 | /** 738 | * Try to create specified path 739 | * 740 | * If specified path is empty, return true 741 | * 742 | * @param string $output_path 743 | * @param mixed $chmod Each folder's permissions 744 | * @return boolean 745 | */ 746 | public static function createPath($output_path, $chmod = 0777) { 747 | if (empty($output_path)) { 748 | return true; 749 | } 750 | 751 | $arr_output_path = explode(DIRECTORY_SEPARATOR, $output_path); 752 | 753 | unset($arr_output_path[count($arr_output_path)-1]); 754 | 755 | $dir_path = implode(DIRECTORY_SEPARATOR, $arr_output_path).DIRECTORY_SEPARATOR; 756 | 757 | if (!file_exists($dir_path)) { 758 | if (!mkdir($dir_path, $chmod, true)) { 759 | return false; 760 | } 761 | } 762 | 763 | return true; 764 | } 765 | 766 | /** 767 | * Autorotate image 768 | * 769 | * Options: 770 | * - 'input' Input file (path or gd resource) 771 | * - 'output' Output path. If not specified, gd resource is returned 772 | * - 'afterCallbacks' Functions to be executed after this one 773 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 774 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 775 | * - 'chmod' What permissions should be applied to destination image 776 | * 777 | * @param mixed $options An array of options 778 | * @return mixed boolean or GD resource if output was set to null 779 | */ 780 | public static function autorotate($options = []) { 781 | $options = array_merge([ 782 | 'afterCallbacks' => null, 783 | 'compression' => null, 784 | 'quality' => null, 785 | 'input' => null, 786 | 'output' => null, 787 | 'chmod' => null 788 | ], $options); 789 | 790 | $type = self::getImageType($options['input']); 791 | 792 | if ($type == 'jpg' && function_exists('exif_read_data')) { 793 | $exif = exif_read_data($options['input']); 794 | } 795 | 796 | $src_im = self::openImage($options['input']); 797 | unset($options['input']); 798 | 799 | if (!$src_im) { 800 | return false; 801 | } 802 | 803 | if (!empty($exif['Orientation'])) { 804 | $orientation = $exif['Orientation']; 805 | } else if (!empty($exif['IFD0']['Orientation'])) { 806 | $orientation = $exif['IFD0']['Orientation']; 807 | } else { 808 | return self::saveImage($src_im, $options); 809 | } 810 | 811 | switch ($orientation) { 812 | case 1: 813 | return self::saveImage($src_im, $options); 814 | break; 815 | 816 | case 2: // horizontal flip 817 | $dst_im = self::flip(['input' => $src_im, 'mode' => 'horizontal']); 818 | break; 819 | 820 | case 3: // 180 rotate left 821 | $dst_im = self::rotate(['input' => $src_im, 'degrees' => 180]); 822 | break; 823 | 824 | case 4: // vertical flip 825 | $dst_im = self::flip(['input' => $src_im, 'mode' => 'vertical']); 826 | break; 827 | 828 | case 5: // vertical flip + 90 rotate right 829 | $dst_im = self::flip(['input' => $src_im, 'mode' => 'vertical']); 830 | $dst_im = self::rotate(['input' => $src_im, 'degrees' => 90]); 831 | break; 832 | 833 | case 6: // 90 rotate right 834 | $dst_im = self::rotate(['input' => $src_im, 'degrees' => 90]); 835 | break; 836 | 837 | case 7: // horizontal flip + 90 rotate right 838 | $dst_im = self::flip(['input' => $src_im, 'mode' => 'horizontal']); 839 | $dst_im = self::rotate(['input' => $src_im, 'degrees' => 90]); 840 | break; 841 | 842 | case 8: // 90 rotate left 843 | $dst_im = self::rotate(['input' => $src_im, 'degrees' => 270]); 844 | break; 845 | 846 | default: 847 | return self::saveImage($src_im, $options); 848 | } 849 | 850 | if (!$dst_im) { 851 | return false; 852 | } 853 | 854 | if (!self::afterCallbacks($dst_im, $options['afterCallbacks'])) { 855 | return false; 856 | } 857 | 858 | return self::saveImage($dst_im, $options); 859 | } 860 | 861 | 862 | /** 863 | * Rotate image by specified angle (only agles divisable by 90 are supported) 864 | * 865 | * Options: 866 | * - 'input' Input file (path or gd resource) 867 | * - 'output' Output path. If not specified, gd resource is returned 868 | * - 'degrees' Degrees to rotate by (divisible by 90) 869 | * - 'afterCallbacks' Functions to be executed after this one 870 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 871 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 872 | * - 'chmod' What permissions should be applied to destination image 873 | * 874 | * @param mixed $options An array of options 875 | * @return mixed boolean or GD resource if output was set to null 876 | */ 877 | public static function rotate($options = []) { 878 | $options = array_merge([ 879 | 'afterCallbacks' => null, 880 | 'compression' => null, 881 | 'quality' => null, 882 | 'input' => null, 883 | 'output' => null, 884 | 'degrees' => 'horizontal', 885 | 'chmod' => null 886 | ], $options); 887 | 888 | $src_im = self::openImage($options['input']); 889 | unset($options['input']); 890 | 891 | if (!$src_im) { 892 | return false; 893 | } 894 | 895 | $w = imagesx($src_im); 896 | $h = imagesy($src_im); 897 | 898 | switch ($options['degrees']) { 899 | case 90: 900 | $dst_im = imagecreatetruecolor($h, $w); 901 | break; 902 | 903 | case 180: 904 | $dst_im = imagecreatetruecolor($w, $h); 905 | break; 906 | 907 | case 270: 908 | $dst_im = imagecreatetruecolor($h, $w); 909 | break; 910 | 911 | case 360: 912 | case 0: 913 | return self::saveImage($src_im, $options); 914 | break; 915 | 916 | default: 917 | return false; 918 | } 919 | 920 | if (!$dst_im) { 921 | return false; 922 | } 923 | 924 | for ($i=0; $i<$w; $i++) { 925 | for ($j=0; $j<$h; $j++) { 926 | $reference = imagecolorat($src_im, $i, $j); 927 | switch ($options['degrees']) { 928 | case 90: 929 | if (!@imagesetpixel($dst_im, ($h-1)-$j, $i, $reference)) { 930 | return false; 931 | } 932 | break; 933 | 934 | case 180: 935 | if (!@imagesetpixel($dst_im, $w-$i, ($h-1)-$j, $reference)) { 936 | return false; 937 | } 938 | break; 939 | 940 | case 270: 941 | if (!@imagesetpixel($dst_im, $j, $w-$i, $reference)) { 942 | return false; 943 | } 944 | break; 945 | } 946 | } 947 | } 948 | 949 | if (!self::afterCallbacks($dst_im, $options['afterCallbacks'])) { 950 | return false; 951 | } 952 | 953 | return self::saveImage($dst_im, $options); 954 | } 955 | 956 | /** 957 | * Flip image 958 | * 959 | * Options: 960 | * - 'input' Input file (path or gd resource) 961 | * - 'output' Output path. If not specified, gd resource is returned 962 | * - 'mode' Flip mode: horizontal, vertical, both 963 | * - 'afterCallbacks' Functions to be executed after this one 964 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 965 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 966 | * - 'chmod' What permissions should be applied to destination image 967 | * 968 | * @param mixed $options An array of options 969 | * @return mixed boolean or GD resource if output was set to null 970 | */ 971 | public static function flip($options = []) { 972 | $options = array_merge([ 973 | 'afterCallbacks' => null, 974 | 'compression' => null, 975 | 'quality' => null, 976 | 'input' => null, 977 | 'output' => null, 978 | 'mode' => 'horizontal', 979 | 'chmod' => null 980 | ], $options); 981 | 982 | $src_im = self::openImage($options['input']); 983 | unset($options['input']); 984 | 985 | if (!$src_im) { 986 | return false; 987 | } 988 | 989 | $w = imagesx($src_im); 990 | $h = imagesy($src_im); 991 | $dst_im = imagecreatetruecolor($w, $h); 992 | 993 | switch ($options['mode']) { 994 | case 'horizontal': 995 | for ($x=0 ; $x<$w ; $x++) { 996 | for ($y=0 ; $y<$h ; $y++) { 997 | imagecopy($dst_im, $src_im, $w-$x-1, $y, $x, $y, 1, 1); 998 | } 999 | } 1000 | break; 1001 | 1002 | case 'vertical': 1003 | for ($x=0 ; $x<$w ; $x++) { 1004 | for ($y=0 ; $y<$h ; $y++) { 1005 | imagecopy($dst_im, $src_im, $x, $h-$y-1, $x, $y, 1, 1); 1006 | } 1007 | } 1008 | break; 1009 | 1010 | case 'both': 1011 | for ($x=0 ; $x<$w ; $x++) { 1012 | for ($y=0 ; $y<$h ; $y++) { 1013 | imagecopy($dst_im, $src_im, $w-$x-1, $h-$y-1, $x, $y, 1, 1); 1014 | } 1015 | } 1016 | break; 1017 | 1018 | default: 1019 | return false; 1020 | } 1021 | 1022 | if (!self::afterCallbacks($dst_im, $options['afterCallbacks'])) { 1023 | return false; 1024 | } 1025 | 1026 | return self::saveImage($dst_im, $options); 1027 | } 1028 | 1029 | /** 1030 | * Get image's average color 1031 | * 1032 | * Options: 1033 | * - 'input' Input file (path or gd resource) 1034 | * - 'format' Output format (int, hex) 1035 | * 1036 | * @param array $options An array of options. 1037 | * @returm mixed string|boolean 1038 | */ 1039 | public static function averageColor($options = []) { 1040 | $options = array_merge([ 1041 | 'input' => null, 1042 | 'format' => 'int' 1043 | ], $options); 1044 | 1045 | $img = self::openImage($options['input']); 1046 | unset($options['input']); 1047 | 1048 | if (!$img) { 1049 | return false; 1050 | } 1051 | 1052 | $dst_im = imagecreatetruecolor(1, 1); 1053 | 1054 | if (!$dst_im || !imagecopyresampled($dst_im, $img, 0, 0, 0, 0, 1, 1, imagesx($img), imagesy($img))) { 1055 | return false; 1056 | } 1057 | 1058 | $color = imagecolorat($dst_im, 0, 0); 1059 | 1060 | switch ($options['format']) { 1061 | case 'hex': 1062 | return str_pad(dechex($color), 6, '0', STR_PAD_LEFT); 1063 | break; 1064 | 1065 | case 'int': 1066 | return $color; 1067 | break; 1068 | } 1069 | 1070 | return false; 1071 | } 1072 | 1073 | /** 1074 | * Get image's dominating color 1075 | * 1076 | * Options: 1077 | * - 'input' Input file (path or gd resource) 1078 | * - 'format' Output format (int, hex) 1079 | * 1080 | * @param array $options An array of options. 1081 | * @returm mixed string|boolean 1082 | */ 1083 | public static function dominatingColor($options = []) { 1084 | $options = array_merge([ 1085 | 'input' => null, 1086 | 'format' => 'int' 1087 | ], $options); 1088 | 1089 | $img = self::openImage($options['input']); 1090 | unset($options['input']); 1091 | 1092 | if (!$img) { 1093 | return false; 1094 | } 1095 | 1096 | $dst_im = imagecreatetruecolor(100, 100); 1097 | 1098 | if (!$dst_im || !imagecopyresampled($dst_im, $img, 0, 0, 0, 0, 100, 100, imagesx($img), imagesy($img))) { 1099 | return false; 1100 | } 1101 | 1102 | $colors = []; 1103 | 1104 | for ($y=0; $y<50; $y++) { 1105 | for ($x=0; $x<50; $x++) { 1106 | $color = imagecolorat($dst_im, $x, $y); 1107 | if (isset($colors[$color])) { 1108 | $colors[$color]++; 1109 | } else { 1110 | $colors[$color] = 1; 1111 | } 1112 | } 1113 | } 1114 | 1115 | arsort($colors); 1116 | 1117 | $color = array_shift(array_keys($colors)); 1118 | 1119 | switch ($options['format']) { 1120 | case 'hex': 1121 | return str_pad(dechex($color), 6, '0', STR_PAD_LEFT); 1122 | break; 1123 | 1124 | case 'int': 1125 | return $color; 1126 | break; 1127 | } 1128 | 1129 | return false; 1130 | } 1131 | 1132 | /** 1133 | * PNG ALPHA CHANNEL SUPPORT for imagecopymerge(); 1134 | * This is a f-ion like imagecopymerge but it handle alpha channel well!!! 1135 | **/ 1136 | public static function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct){ 1137 | // getting the watermark width 1138 | $w = imagesx($src_im); 1139 | // getting the watermark height 1140 | $h = imagesy($src_im); 1141 | 1142 | // creating a cut resource 1143 | $cut = imagecreatetruecolor($src_w, $src_h); 1144 | // copying that section of the background to the cut 1145 | imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h); 1146 | 1147 | // placing the watermark now 1148 | imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h); 1149 | return imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct); 1150 | } 1151 | 1152 | /** 1153 | * Pixelate image 1154 | * 1155 | * Options: 1156 | * - 'input' Input file (path or gd resource) 1157 | * - 'output' Output path. If not specified, gd resource is returned 1158 | * - 'blocksize' Size of each pixel 1159 | * - 'afterCallbacks' Functions to be executed after this one 1160 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 1161 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 1162 | * - 'chmod' What permissions should be applied to destination image 1163 | * 1164 | * @param mixed $options An array of options 1165 | * @return mixed boolean or GD resource if output was set to null 1166 | */ 1167 | public static function pixelate($options) { 1168 | $options = array_merge([ 1169 | 'afterCallbacks' => null, 1170 | 'compression' => null, 1171 | 'quality' => null, 1172 | 'blocksize' => 10, 1173 | 'output' => null, 1174 | 'input' => null, 1175 | 'chmod' => null 1176 | ], $options); 1177 | 1178 | $img = self::openImage($options['input']); 1179 | unset($options['input']); 1180 | 1181 | if (!$img) { 1182 | return false; 1183 | } 1184 | 1185 | $w = imagesx($img); 1186 | $h = imagesy($img); 1187 | 1188 | for($x=0; $x<$w; $x+=$options['blocksize']) { 1189 | for($y=0; $y<$h; $y+=$options['blocksize']) { 1190 | $colors = [ 1191 | 'alpha' => 0, 1192 | 'red' => 0, 1193 | 'green' => 0, 1194 | 'blue' => 0, 1195 | 'total' => 0 1196 | ]; 1197 | 1198 | for ($block_x = 0 ; $block_x < $options['blocksize'] ; $block_x++) { 1199 | for ($block_y = 0 ; $block_y < $options['blocksize'] ; $block_y++) { 1200 | $current_block_x = $x + $block_x; 1201 | $current_block_y = $y + $block_y; 1202 | 1203 | if ($current_block_x >= $w || $current_block_y >= $h) { 1204 | continue; 1205 | } 1206 | 1207 | $color = imagecolorat($img, $current_block_x, $current_block_y); 1208 | imagecolordeallocate($img, $color); 1209 | 1210 | $colors['alpha'] += ($color >> 24) & 0xFF; 1211 | $colors['red'] += ($color >> 16) & 0xFF; 1212 | $colors['green'] += ($color >> 8) & 0xFF; 1213 | $colors['blue'] += $color & 0xFF; 1214 | $colors['total']++; 1215 | } 1216 | } 1217 | 1218 | $color = imagecolorallocatealpha( 1219 | $img, 1220 | $colors['red'] / $colors['total'], 1221 | $colors['green'] / $colors['total'], 1222 | $colors['blue'] / $colors['total'], 1223 | $colors['alpha'] / $colors['total'] 1224 | ); 1225 | 1226 | imagefilledrectangle($img, $x, $y, ($x + $options['blocksize'] - 1), ($y + $options['blocksize'] - 1), $color); 1227 | } 1228 | } 1229 | 1230 | if (!self::afterCallbacks($img, $options['afterCallbacks'])) { 1231 | return false; 1232 | } 1233 | 1234 | return self::saveImage($img, $options); 1235 | } 1236 | 1237 | /** 1238 | * Meshify image 1239 | * 1240 | * Options: 1241 | * - 'input' Input file (path or gd resource) 1242 | * - 'output' Output path. If not specified, gd resource is returned 1243 | * - 'afterCallbacks' Functions to be executed after this one 1244 | * - 'blocksize' Size between two filled pixels 1245 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 1246 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 1247 | * - 'chmod' What permissions should be applied to destination image 1248 | * - 'color' Mesh color (array of rgb values) 1249 | * 1250 | * @param mixed $options An array of options 1251 | * @return mixed boolean or GD resource if output was set to null 1252 | */ 1253 | public static function meshify($options) { 1254 | $options = array_merge([ 1255 | 'afterCallbacks' => null, 1256 | 'compression' => null, 1257 | 'quality' => null, 1258 | 'blocksize' => 2, 1259 | 'output' => null, 1260 | 'input' => null, 1261 | 'chmod' => null, 1262 | 'color' => [0, 0, 0] 1263 | ], $options); 1264 | 1265 | $src_im = self::openImage($options['input']); 1266 | unset($options['input']); 1267 | 1268 | $w = imagesx($src_im); 1269 | $h = imagesy($src_im); 1270 | 1271 | $rgb = self::readColor($options['color']); 1272 | if (!$rgb) { 1273 | $rgb = [0, 0, 0]; 1274 | } 1275 | 1276 | $color = imagecolorallocate($src_im, $rgb[0], $rgb[1], $rgb[2]); 1277 | 1278 | for($x=0; $x<$w; $x+=$options['blocksize']) { 1279 | for($y=0; $y<$h; $y+=$options['blocksize']) { 1280 | imagesetpixel($src_im, $x, $y, $color); 1281 | } 1282 | } 1283 | 1284 | if (!self::afterCallbacks($src_im, $options['afterCallbacks'])) { 1285 | return false; 1286 | } 1287 | 1288 | return self::saveImage($src_im, $options); 1289 | } 1290 | 1291 | /** 1292 | * Make image black and white 1293 | * 1294 | * Options: 1295 | * - 'input' Input file (path or gd resource) 1296 | * - 'output' Output path. If not specified, gd resource is returned 1297 | * - 'afterCallbacks' Functions to be executed after grayscaling 1298 | * - 'quality' Output image quality (JPG only). Value from 0 to 100 1299 | * - 'compression' Output image compression (PNG only). Value from 0 to 9 1300 | * - 'chmod' What permissions should be applied to destination image 1301 | * 1302 | * @return mixed boolean or GD resource if output was set to null 1303 | */ 1304 | public static function grayscale($options) { 1305 | $options = array_merge([ 1306 | 'afterCallbacks' => null, 1307 | 'compression' => null, 1308 | 'quality' => null, 1309 | 'output' => null, 1310 | 'input' => null, 1311 | 'chmod' => null 1312 | ], $options); 1313 | 1314 | $img = self::openImage($options['input']); 1315 | unset($options['input']); 1316 | 1317 | if (!$img) { 1318 | return false; 1319 | } 1320 | 1321 | $w = imagesx($img); 1322 | $h = imagesy($img); 1323 | 1324 | $palette = []; 1325 | 1326 | for ($c=0; $c<256; $c++) { 1327 | $palette[$c] = imagecolorallocate($img, $c, $c, $c); 1328 | } 1329 | 1330 | for ($y=0; $y<$h; $y++) { 1331 | for ($x=0; $x<$w; $x++) { 1332 | $rgb = imagecolorat($img, $x, $y); 1333 | 1334 | $r = ($rgb >> 16) & 0xFF; 1335 | $g = ($rgb >> 8) & 0xFF; 1336 | $b = $rgb & 0xFF; 1337 | 1338 | $gs = self::yiq($r, $g, $b); 1339 | 1340 | imagesetpixel($img, $x, $y, $palette[$gs]); 1341 | } 1342 | } 1343 | 1344 | if (!self::afterCallbacks($img, $options['afterCallbacks'])) { 1345 | return false; 1346 | } 1347 | 1348 | return self::saveImage($img, $options); 1349 | } 1350 | 1351 | /** 1352 | * Helper function to covert color to grayscale 1353 | */ 1354 | public static function yiq($r, $g, $b) { 1355 | return (($r*0.299)+($g*0.587)+($b*0.114)); 1356 | } 1357 | 1358 | /** 1359 | * Perform afterCallbacks on specified image 1360 | * 1361 | * @param resource $im Image to perform callback on 1362 | * @param mixed $functions Callback functions and their arguments 1363 | * @return boolean 1364 | */ 1365 | public static function afterCallbacks(&$im, $functions) { 1366 | if (empty($functions)) { 1367 | return true; 1368 | } 1369 | 1370 | foreach ($functions as $v) { 1371 | $v[1]['input'] = $im; 1372 | 1373 | $im = self::$v[0]($v[1]); 1374 | 1375 | if (!$im) { 1376 | return false; 1377 | } 1378 | } 1379 | 1380 | return true; 1381 | } 1382 | 1383 | /** 1384 | * Read color (convert various formats into rgb) 1385 | * 1386 | * Supported values: rgb (array), hex (string), int (integer) 1387 | * 1388 | * @param mixed $color Input color 1389 | * @return array Array of rgb values 1390 | */ 1391 | public static function readColor($color) { 1392 | if (is_array($color)) { 1393 | if (count($color) == 3) { 1394 | return $color; 1395 | } 1396 | } else if (is_string($color)) { 1397 | return self::hex2rgb($color); 1398 | } else if (is_int($color)) { 1399 | return self::hex2rgb(dechex($color)); 1400 | } 1401 | 1402 | return false; 1403 | } 1404 | 1405 | /** 1406 | * Convert HEX color to RGB 1407 | * 1408 | * @param string $color HEX color (3 or 6 chars) 1409 | * @return mixed 1410 | */ 1411 | public static function hex2rgb($color) { 1412 | if ($color[0] == '#') { 1413 | $color = substr($color, 1); 1414 | } 1415 | 1416 | if (strlen($color) == 6) { 1417 | list($r, $g, $b) = [$color[0].$color[1], $color[2].$color[3], $color[4].$color[5]]; 1418 | } else if (strlen($color) == 3) { 1419 | list($r, $g, $b) = [$color[0].$color[0], $color[1].$color[1], $color[2].$color[2]]; 1420 | } else { 1421 | return false; 1422 | } 1423 | 1424 | return [hexdec($r), hexdec($g), hexdec($b)]; 1425 | } 1426 | 1427 | /** 1428 | * Writes the given text with a border into the image using TrueType fonts. 1429 | * 1430 | * @author John Ciacia (code taken from: http://www.johnciacia.com/2010/01/04/using-php-and-gd-to-add-border-to-text/) 1431 | * @param mixed $image An image resource 1432 | * @param int $size The font size 1433 | * @param int $angle The angle in degrees to rotate the text 1434 | * @param int $x Upper left corner of the text 1435 | * @param int $y Lower left corner of the text 1436 | * @param mixed $textcolor This is the color of the main text 1437 | * @param mixed $strokecolor This is the color of the text border 1438 | * @param string $fontfile The path to the TrueType font you wish to use 1439 | * @param string $text The text string in UTF-8 encoding 1440 | * @param int $px Number of pixels the text border will be 1441 | * @return mixed GD resource 1442 | */ 1443 | public static function imagettfstroketext(&$image, $size, $angle, $x, $y, &$textcolor, &$strokecolor, $fontfile, $text, $px) { 1444 | for ($c1 = ($x-abs($px)); $c1 <= ($x+abs($px)); $c1++) { 1445 | for ($c2 = ($y-abs($px)); $c2 <= ($y+abs($px)); $c2++) { 1446 | imagettftext($image, $size, $angle, $c1, $c2, $strokecolor, $fontfile, $text); 1447 | } 1448 | } 1449 | 1450 | return imagettftext($image, $size, $angle, $x, $y, $textcolor, $fontfile, $text); 1451 | } 1452 | 1453 | } 1454 | 1455 | ?> -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | CakePHP ImageTool Component 2 | ================================ 3 | 4 | About 5 | -------------------------------- 6 | 7 | ImageTool is php class to perform various tasks with images. Every function accepts 8 | GD resource as input and can output it as well (just leave 'output' option empty). 9 | 10 | Changes from 1.1.* 11 | -------------------------------- 12 | 13 | This library now consists purely from static functions as there was no need for class object. 14 | This makes it very easy to use in cake's models or anywhere else and isn't cakephp specific. 15 | 16 | Installation 17 | -------------------------------- 18 | 19 | For use with cakephp: 20 | * Copy ImageTool.php to your app's Vendor/ directory 21 | * Include in your controller: App::import('Vendor', 'ImageTool'); 22 | 23 | For use as standalone library: 24 | * Include in your project with include() or similar method 25 | 26 | Error handling 27 | -------------------------------- 28 | 29 | Most image processing functions return either true or false (depending if action was successfull). Only exception is when 30 | you don't specify output (or set it to null/empty) - in such cases (if action was successfull) GD resource is returned. 31 | 32 | PHP requirements 33 | -------------------------------- 34 | Minimum PHP version is 5.4 due to new array syntax used in library. If needed you can replace [] to array() in places arrays are used if you can't use 5.4 or higher version 35 | 36 | Available functions 37 | -------------------------------- 38 | 39 | * autorotate - autorotate JPG images (by exif data) 40 | * averageColor - get image's average color 41 | * dominatingColor - get image's dominating color 42 | * flip - flip image 43 | * grayscale - desaturate image 44 | * meshify - add mesh (grid of dots) over image 45 | * pixelate - pixelate image 46 | * resize - resize image 47 | * rotate - rotate image (only degrees divisible by 90) 48 | * unsharpMask - sharpen image 49 | * watermark - add watermark 50 | 51 | Examples 52 | -------------------------------- 53 | 54 | Make thumbnail 100x100px in size 55 | 56 | $status = ImageTool::resize(array( 57 | 'input' => $input_file, 58 | 'output' => $output_file, 59 | 'width' => 100, 60 | 'height' => 100 61 | )); 62 | 63 | Resize image (while keeping ratio) with max width and height both set to 600px. After that place watermark 64 | image in bottom-right corner and sharpen end result (with default settings) 65 | 66 | $status = ImageTool::resize(array( 67 | 'input' => $input_file, 68 | 'output' => $output_file, 69 | 'width' => 600, 70 | 'height' => 600, 71 | 'mode' => 'fit', 72 | 'paddings' => false, 73 | 'afterCallbacks' => array( 74 | array('watermark', array('watermark' => $watermark_file, 'position' => 'bottom-right')), 75 | array('unsharpMask'), 76 | ) 77 | )); 78 | 79 | Autorotate image (getting back GD resource and then passing it to the next function (greyscale) - similar to previous 80 | example but without using afterCallbacks option) 81 | 82 | $image = ImageTool::autorotate(array( 83 | 'input' => $input_file, 84 | 'output' => null 85 | )); 86 | 87 | if ($image) { 88 | $status = ImageTool::grayscale(array( 89 | 'input' => $image, 90 | 'output' => $output_file 91 | )); 92 | } else { 93 | $status = false; 94 | } 95 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raitisg/imagetool", 3 | "description": "Different tools/functions to perform various tasks with images", 4 | "keywords": ["cakephp", "gd", "image", "images", "effects"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Raitis G", 9 | "email": "raitisg@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4.0" 14 | }, 15 | "require": {} 16 | } --------------------------------------------------------------------------------