2 namespace wcf\system\image\adapter
;
3 use wcf\system\exception\SystemException
;
4 use wcf\util\StringUtil
;
7 * Image adapter for bundled GD imaging library.
9 * @author Alexander Ebert
10 * @copyright 2001-2019 WoltLab GmbH
11 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12 * @package WoltLabSuite\Core\System\Image\Adapter
14 class GDImageAdapter
implements IImageAdapter
{
19 protected $color = null;
22 * red, green, blue data of the active color
25 protected $colorData = [];
31 protected $height = 0;
37 protected $image = null;
52 * GDImageAdapter constructor.
54 public function __construct() {
55 // suppress warnings like "recoverable error: Invalid SOS parameters for sequential JPEG"
56 @ini_set
('gd.jpeg_ignore_warning', '1');
60 * Returns whether the given image is a valid GD resource / GD object
64 public function isImage($image) {
65 return (is_resource($image) && get_resource_type($image) === 'gd') ||
(is_object($image) && $image instanceof \GdImage
);
71 public function load($image, $type = '') {
72 if (!$this->isImage($image)) {
73 throw new SystemException("Image resource is invalid.");
77 throw new SystemException("Image type is missing.");
80 $this->image
= $image;
83 $this->height
= imagesy($this->image
);
84 $this->width
= imagesx($this->image
);
90 public function loadFile($file) {
91 list($this->width
, $this->height
, $this->type
) = getimagesize($file);
93 switch ($this->type
) {
95 $this->image
= imagecreatefromgif($file);
99 // suppress warnings and properly handle errors
100 $this->image
= @imagecreatefromjpeg
($file);
101 if ($this->image
=== false) {
102 throw new SystemException("Could not read jpeg image '".$file."'.");
107 // suppress warnings and properly handle errors
108 $this->image
= @imagecreatefrompng
($file);
109 if ($this->image
=== false) {
110 throw new SystemException("Could not read png image '".$file."'.");
115 throw new SystemException("Could not read image '".$file."', format is not recognized.");
123 public function createEmptyImage($width, $height) {
124 $this->image
= imagecreate($width, $height);
125 $this->type
= IMAGETYPE_PNG
;
126 $this->setColor(0xFF, 0xFF, 0xFF);
129 $this->width
= $width;
130 $this->height
= $height;
136 public function createThumbnail($maxWidth, $maxHeight, $obtainDimensions = true) {
138 $sourceWidth = $this->width
;
139 $sourceHeight = $this->height
;
141 if ($obtainDimensions) {
142 if ($maxWidth / $this->width
< $maxHeight / $this->height
) {
144 $height = round($this->height
* ($width / $this->width
));
147 $height = $maxHeight;
148 $width = round($this->width
* ($height / $this->height
));
153 $height = $maxHeight;
155 if ($maxWidth / $this->width
< $maxHeight / $this->height
) {
156 $cut = (($sourceWidth * ($maxHeight / $this->height
)) - $maxWidth) / ($maxHeight / $this->height
);
158 $sourceWidth = $sourceWidth - $x * 2;
161 $cut = (($sourceHeight * ($maxWidth / $this->width
)) - $maxHeight) / ($maxWidth / $this->width
);
163 $sourceHeight = $sourceHeight - $y * 2;
168 $image = imagecreatetruecolor((int) $width, (int) $height);
169 imagealphablending($image, false);
170 imagecopyresampled($image, $this->image
, 0, 0, (int) $x, (int) $y, (int) $width, (int) $height, (int) $sourceWidth, (int) $sourceHeight);
171 imagesavealpha($image, true);
179 public function clip($originX, $originY, $width, $height) {
180 $image = imagecreatetruecolor($width, $height);
181 imagealphablending($image, false);
183 imagecopy($image, $this->image
, 0, 0, (int) $originX, (int) $originY, (int) $width, (int) $height);
184 imagesavealpha($image, true);
186 // reload image to update image resource, width and height
187 $this->load($image, $this->type
);
193 public function resize($originX, $originY, $originWidth, $originHeight, $targetWidth = 0, $targetHeight = 0) {
194 $image = imagecreatetruecolor($targetWidth, $targetHeight);
195 imagealphablending($image, false);
197 imagecopyresampled($image, $this->image
, 0, 0, (int) $originX, (int) $originY, (int) $targetWidth, (int) $targetHeight, (int) $originWidth, (int) $originHeight);
198 imagesavealpha($image, true);
200 // reload image to update image resource, width and height
201 $this->load($image, $this->type
);
207 public function drawRectangle($startX, $startY, $endX, $endY) {
208 imagefilledrectangle($this->image
, $startX, $startY, $endX, $endY, $this->color
);
214 public function drawText($text, $x, $y, $font, $size, $opacity = 1.0) {
216 $color = imagecolorallocatealpha($this->image
, $this->colorData
['red'], $this->colorData
['green'], $this->colorData
['blue'], (1 - $opacity) * 127);
219 imagettftext($this->image
, $size, 0, $x, $y, $color, $font, $text);
225 public function drawTextRelative($text, $position, $margin, $offsetX, $offsetY, $font, $size, $opacity = 1.0) {
226 // split text into multiple lines
227 $lines = explode("\n", StringUtil
::unifyNewlines($text));
229 // calc text width, height and first line height
230 $box = imagettfbbox($size, 0, $font, $text);
231 $firstLineBox = imagettfbbox($size, 0, $font, $lines[0]);
232 $textWidth = abs($box[0] - $box[2]);
233 $textHeight = abs($box[7] - $box[1]);
234 $firstLineHeight = abs($firstLineBox[7] - $firstLineBox[1]);
236 // calculate x coordinate
248 $x = floor(($this->getWidth() - $textWidth) / 2);
254 $x = $this->getWidth() - $textWidth - $margin;
258 // calculate y coordinate
264 $y = $margin +
$firstLineHeight;
270 $y = floor(($this->getHeight() - $textHeight) / 2) +
$firstLineHeight;
276 $y = $this->getHeight() - $textHeight +
$firstLineHeight - $margin;
280 $this->drawText($text, $x +
$offsetX, $y +
$offsetY, $font, $size, $opacity);
286 public function textFitsImage($text, $margin, $font, $size) {
287 $box = imagettfbbox($size, 0, $font, $text);
289 $textWidth = abs($box[0] - $box[2]);
290 $textHeight = abs($box[7] - $box[1]);
292 return ($textWidth +
2 * $margin <= $this->getWidth() && $textHeight +
2 * $margin <= $this->getHeight());
298 public function adjustFontSize($text, $margin, $font, $size) {
305 public function setColor($red, $green, $blue) {
306 $this->color
= imagecolorallocate($this->image
, $red, $green, $blue);
308 // save data of the color
319 public function hasColor() {
320 return ($this->color
!== null);
326 public function setTransparentColor($red, $green, $blue) {
327 if ($this->type
== IMAGETYPE_PNG
) {
328 $color = imagecolorallocate($this->image
, $red, $green, $blue);
329 imagecolortransparent($this->image
, $color);
336 public function writeImage($image, $filename) {
337 if (!$this->isImage($image)) {
338 throw new SystemException("Given image is not a valid image resource.");
343 imagealphablending($image, false);
344 imagesavealpha($image, true);
345 if ($this->type
== IMAGETYPE_GIF
) {
348 else if ($this->type
== IMAGETYPE_PNG
) {
351 else if (function_exists('imageJPEG')) {
352 imagejpeg($image, null, 90);
355 $stream = ob_get_contents();
358 file_put_contents($filename, $stream);
364 public function getWidth() {
371 public function getHeight() {
372 return $this->height
;
378 public function getType() {
385 public function getImage() {
392 public function rotate($degrees) {
393 // imagerotate interpretes degrees as counter-clockwise
394 return imagerotate($this->image
, 360.0 - $degrees, ($this->color ?
: 0));
400 public function overlayImage($file, $x, $y, $opacity) {
401 $overlayImage = new self();
402 $overlayImage->loadFile($file);
404 // fix PNG alpha channel handling
405 // see http://php.net/manual/en/function.imagecopymerge.php#92787
406 $cut = imagecreatetruecolor($overlayImage->getWidth(), $overlayImage->getHeight());
407 imagealphablending($cut, false);
408 imagesavealpha($cut, true);
410 imagecopy($cut, $this->image
, 0, 0, $x, $y, $overlayImage->getWidth(), $overlayImage->getHeight());
411 imagecopy($cut, $overlayImage->image
, 0, 0, 0, 0, $overlayImage->getWidth(), $overlayImage->getHeight());
413 $this->imagecopymerge_alpha($this->image
, $cut, $x, $y, 0, 0, $overlayImage->getWidth(), $overlayImage->getHeight(), $opacity * 100);
417 * `imagecopymerge` implementation with alpha support.
419 * @see http://php.net/manual/en/function.imagecopymerge.php#88456
421 * @param resource $dst_im destination image resource
422 * @param resource $src_im source image resource
423 * @param integer $dst_x x-coordinate of destination point
424 * @param integer $dst_y y-coordinate of destination point
425 * @param integer $src_x x-coordinate of source point
426 * @param integer $src_y y-coordinate of source point
427 * @param integer $src_w source width
428 * @param integer $src_h source height
429 * @param integer $pct opacity percent
432 private function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) { // phpcs:ignore
437 // Get image width and height
438 $w = imagesx($src_im);
439 $h = imagesy($src_im);
440 // Turn alpha blending off
441 imagealphablending($src_im, false);
442 // Find the most opaque pixel in the image (the one with the smallest alpha value)
444 for ($x = 0; $x < $w; $x++
) {
445 for ($y = 0; $y < $h; $y++
) {
446 $alpha = (imagecolorat($src_im, $x, $y) >> 24) & 0xFF;
447 if ($alpha < $minalpha) {
452 // loop through image pixels and modify alpha for each
453 for ($x = 0; $x < $w; $x++
) {
454 for ($y = 0; $y < $h; $y++
) {
455 // get current alpha value (represents the TANSPARENCY!)
456 $colorxy = imagecolorat($src_im, $x, $y);
457 $alpha = ($colorxy >> 24) & 0xFF;
458 // calculate new alpha
459 if ($minalpha !== 127) {
460 $alpha = 127 +
127 * $pct * ($alpha - 127) / (127 - $minalpha);
463 $alpha +
= 127 * $pct;
465 // get the color index with new alpha
466 $alphacolorxy = imagecolorallocatealpha($src_im, ($colorxy >> 16) & 0xFF, ($colorxy >> 8) & 0xFF, $colorxy & 0xFF, $alpha);
467 // set pixel with the new color + opacity
468 if (!imagesetpixel($src_im, $x, $y, $alphacolorxy)) {
474 imagecopy($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
482 public function overlayImageRelative($file, $position, $margin, $opacity) {
489 public static function isSupported() {
490 return function_exists('gd_info');