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');
62 public function load($image, $type = '') {
63 if (!is_resource($image)) {
64 throw new SystemException("Image resource is invalid.");
68 throw new SystemException("Image type is missing.");
71 $this->image
= $image;
74 $this->height
= imagesy($this->image
);
75 $this->width
= imagesx($this->image
);
81 public function loadFile($file) {
82 list($this->width
, $this->height
, $this->type
) = getimagesize($file);
84 switch ($this->type
) {
86 $this->image
= imagecreatefromgif($file);
90 // suppress warnings and properly handle errors
91 $this->image
= @imagecreatefromjpeg
($file);
92 if ($this->image
=== false) {
93 throw new SystemException("Could not read jpeg image '".$file."'.");
98 // suppress warnings and properly handle errors
99 $this->image
= @imagecreatefrompng
($file);
100 if ($this->image
=== false) {
101 throw new SystemException("Could not read png image '".$file."'.");
106 throw new SystemException("Could not read image '".$file."', format is not recognized.");
114 public function createEmptyImage($width, $height) {
115 $this->image
= imagecreate($width, $height);
116 $this->type
= IMAGETYPE_PNG
;
117 $this->setColor(0xFF, 0xFF, 0xFF);
120 $this->width
= $width;
121 $this->height
= $height;
127 public function createThumbnail($maxWidth, $maxHeight, $preserveAspectRatio = true) {
129 $sourceWidth = $this->width
;
130 $sourceHeight = $this->height
;
132 if ($preserveAspectRatio) {
133 if ($maxWidth / $this->width
< $maxHeight / $this->height
) {
135 $height = round($this->height
* ($width / $this->width
));
138 $height = $maxHeight;
139 $width = round($this->width
* ($height / $this->height
));
144 $height = $maxHeight;
146 if ($maxWidth / $this->width
< $maxHeight / $this->height
) {
147 $cut = (($sourceWidth * ($maxHeight / $this->height
)) - $maxWidth) / ($maxHeight / $this->height
);
149 $sourceWidth = $sourceWidth - $x * 2;
152 $cut = (($sourceHeight * ($maxWidth / $this->width
)) - $maxHeight) / ($maxWidth / $this->width
);
154 $sourceHeight = $sourceHeight - $y * 2;
159 $image = imagecreatetruecolor((int) $width, (int) $height);
160 imagealphablending($image, false);
161 imagecopyresampled($image, $this->image
, 0, 0, (int) $x, (int) $y, (int) $width, (int) $height, (int) $sourceWidth, (int) $sourceHeight);
162 imagesavealpha($image, true);
170 public function clip($originX, $originY, $width, $height) {
171 $image = imagecreatetruecolor($width, $height);
172 imagealphablending($image, false);
174 imagecopy($image, $this->image
, 0, 0, (int) $originX, (int) $originY, (int) $width, (int) $height);
175 imagesavealpha($image, true);
177 // reload image to update image resource, width and height
178 $this->load($image, $this->type
);
184 public function resize($originX, $originY, $originWidth, $originHeight, $targetWidth = 0, $targetHeight = 0) {
185 $image = imagecreatetruecolor($targetWidth, $targetHeight);
186 imagealphablending($image, false);
188 imagecopyresampled($image, $this->image
, 0, 0, (int) $originX, (int) $originY, (int) $targetWidth, (int) $targetHeight, (int) $originWidth, (int) $originHeight);
189 imagesavealpha($image, true);
191 // reload image to update image resource, width and height
192 $this->load($image, $this->type
);
198 public function drawRectangle($startX, $startY, $endX, $endY) {
199 imagefilledrectangle($this->image
, $startX, $startY, $endX, $endY, $this->color
);
205 public function drawText($text, $x, $y, $font, $size, $opacity = 1.0) {
207 $color = imagecolorallocatealpha($this->image
, $this->colorData
['red'], $this->colorData
['green'], $this->colorData
['blue'], (1 - $opacity) * 127);
210 imagettftext($this->image
, $size, 0, $x, $y, $color, $font, $text);
216 public function drawTextRelative($text, $position, $margin, $offsetX, $offsetY, $font, $size, $opacity = 1.0) {
217 // split text into multiple lines
218 $lines = explode("\n", StringUtil
::unifyNewlines($text));
220 // calc text width, height and first line height
221 $box = imagettfbbox($size, 0, $font, $text);
222 $firstLineBox = imagettfbbox($size, 0, $font, $lines[0]);
223 $textWidth = abs($box[0] - $box[2]);
224 $textHeight = abs($box[7] - $box[1]);
225 $firstLineHeight = abs($firstLineBox[7] - $firstLineBox[1]);
227 // calculate x coordinate
239 $x = floor(($this->getWidth() - $textWidth) / 2);
245 $x = $this->getWidth() - $textWidth - $margin;
249 // calculate y coordinate
255 $y = $margin +
$firstLineHeight;
261 $y = floor(($this->getHeight() - $textHeight) / 2) +
$firstLineHeight;
267 $y = $this->getHeight() - $textHeight +
$firstLineHeight - $margin;
271 $this->drawText($text, $x +
$offsetX, $y +
$offsetY, $font, $size, $opacity);
277 public function textFitsImage($text, $margin, $font, $size) {
278 $box = imagettfbbox($size, 0, $font, $text);
280 $textWidth = abs($box[0] - $box[2]);
281 $textHeight = abs($box[7] - $box[1]);
283 return ($textWidth +
2 * $margin <= $this->getWidth() && $textHeight +
2 * $margin <= $this->getHeight());
289 public function adjustFontSize($text, $margin, $font, $size) {
296 public function setColor($red, $green, $blue) {
297 $this->color
= imagecolorallocate($this->image
, $red, $green, $blue);
299 // save data of the color
310 public function hasColor() {
311 return ($this->color
!== null);
317 public function setTransparentColor($red, $green, $blue) {
318 if ($this->type
== IMAGETYPE_PNG
) {
319 $color = imagecolorallocate($this->image
, $red, $green, $blue);
320 imagecolortransparent($this->image
, $color);
327 public function writeImage($image, $filename) {
328 if (!is_resource($image)) {
329 throw new SystemException("Given image is not a valid image resource.");
334 imagealphablending($image, false);
335 imagesavealpha($image, true);
336 if ($this->type
== IMAGETYPE_GIF
) {
339 else if ($this->type
== IMAGETYPE_PNG
) {
342 else if (function_exists('imageJPEG')) {
343 imagejpeg($image, null, 90);
346 $stream = ob_get_contents();
349 file_put_contents($filename, $stream);
355 public function getWidth() {
362 public function getHeight() {
363 return $this->height
;
369 public function getType() {
376 public function getImage() {
383 public function rotate($degrees) {
384 // imagerotate interpretes degrees as counter-clockwise
385 return imagerotate($this->image
, 360.0 - $degrees, ($this->color ?
: 0));
391 public function overlayImage($file, $x, $y, $opacity) {
392 $overlayImage = new self();
393 $overlayImage->loadFile($file);
395 // fix PNG alpha channel handling
396 // see http://php.net/manual/en/function.imagecopymerge.php#92787
397 $cut = imagecreatetruecolor($overlayImage->getWidth(), $overlayImage->getHeight());
398 imagealphablending($cut, false);
399 imagesavealpha($cut, true);
401 imagecopy($cut, $this->image
, 0, 0, $x, $y, $overlayImage->getWidth(), $overlayImage->getHeight());
402 imagecopy($cut, $overlayImage->image
, 0, 0, 0, 0, $overlayImage->getWidth(), $overlayImage->getHeight());
404 $this->imagecopymerge_alpha($this->image
, $cut, $x, $y, 0, 0, $overlayImage->getWidth(), $overlayImage->getHeight(), $opacity * 100);
408 * `imagecopymerge` implementation with alpha support.
410 * @see http://php.net/manual/en/function.imagecopymerge.php#88456
412 * @param resource $dst_im destination image resource
413 * @param resource $src_im source image resource
414 * @param integer $dst_x x-coordinate of destination point
415 * @param integer $dst_y y-coordinate of destination point
416 * @param integer $src_x x-coordinate of source point
417 * @param integer $src_y y-coordinate of source point
418 * @param integer $src_w source width
419 * @param integer $src_h source height
420 * @param integer $pct opacity percent
423 private function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) {
428 // Get image width and height
429 $w = imagesx($src_im);
430 $h = imagesy($src_im);
431 // Turn alpha blending off
432 imagealphablending($src_im, false);
433 // Find the most opaque pixel in the image (the one with the smallest alpha value)
435 for ($x = 0; $x < $w; $x++
) {
436 for ($y = 0; $y < $h; $y++
) {
437 $alpha = (imagecolorat($src_im, $x, $y) >> 24) & 0xFF;
438 if ($alpha < $minalpha) {
443 // loop through image pixels and modify alpha for each
444 for ($x = 0; $x < $w; $x++
) {
445 for ($y = 0; $y < $h; $y++
) {
446 // get current alpha value (represents the TANSPARENCY!)
447 $colorxy = imagecolorat($src_im, $x, $y);
448 $alpha = ($colorxy >> 24) & 0xFF;
449 // calculate new alpha
450 if ($minalpha !== 127) {
451 $alpha = 127 +
127 * $pct * ($alpha - 127) / (127 - $minalpha);
454 $alpha +
= 127 * $pct;
456 // get the color index with new alpha
457 $alphacolorxy = imagecolorallocatealpha($src_im, ($colorxy >> 16) & 0xFF, ($colorxy >> 8) & 0xFF, $colorxy & 0xFF, $alpha);
458 // set pixel with the new color + opacity
459 if (!imagesetpixel($src_im, $x, $y, $alphacolorxy)) {
465 imagecopy($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
473 public function overlayImageRelative($file, $position, $margin, $opacity) {
480 public static function isSupported() {
481 return function_exists('gd_info');