Allow overriding the envelope sender in Email::getJobs()
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / image / adapter / GDImageAdapter.class.php
CommitLineData
b86ca09c 1<?php
64a820cf 2namespace wcf\system\image\adapter;
a3399fc5 3use wcf\system\exception\SystemException;
a17de04e 4use wcf\util\StringUtil;
64a820cf
AE
5
6/**
7 * Image adapter for bundled GD imaging library.
8 *
9f959ced 9 * @author Alexander Ebert
7d739af0 10 * @copyright 2001-2016 WoltLab GmbH
64a820cf
AE
11 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12 * @package com.woltlab.wcf
13 * @subpackage system.image.adapter
9f959ced 14 * @category Community Framework
64a820cf
AE
15 */
16class GDImageAdapter implements IImageAdapter {
17 /**
18 * active color
9f959ced
MS
19 * @var integer
20 */
64a820cf
AE
21 protected $color = null;
22
cfdb4fa4
MS
23 /**
24 * red, green, blue data of the active color
25 * @var array
26 */
058cbd6a 27 protected $colorData = [];
cfdb4fa4 28
b86ca09c
AE
29 /**
30 * image height
31 * @var integer
a17de04e 32 */
b86ca09c
AE
33 protected $height = 0;
34
35 /**
36 * loaded image
37 * @var resource
a17de04e 38 */
b86ca09c
AE
39 protected $image = null;
40
41 /**
42 * image type
43 * @var integer
44 */
45 protected $type = 0;
46
47 /**
48 * image width
49 * @var integer
a17de04e 50 */
b86ca09c
AE
51 protected $width = 0;
52
5cf84a99
MS
53 /**
54 * GDImageAdapter constructor.
55 */
56 public function __construct() {
57 // suppress warnings like "recoverable error: Invalid SOS parameters for sequential JPEG"
58 @ini_set('gd.jpeg_ignore_warning', 1);
59 }
60
b86ca09c 61 /**
0fcfe5f6 62 * @inheritDoc
b86ca09c
AE
63 */
64 public function load($image, $type = '') {
65 if (!is_resource($image)) {
66 throw new SystemException("Image resource is invalid.");
67 }
68
69 if (empty($type)) {
70 throw new SystemException("Image type is missing.");
71 }
72
73 $this->image = $image;
74 $this->type = $type;
75
379875ee
MS
76 $this->height = imagesy($this->image);
77 $this->width = imagesx($this->image);
b86ca09c
AE
78 }
79
80 /**
0fcfe5f6 81 * @inheritDoc
a17de04e 82 */
b86ca09c 83 public function loadFile($file) {
379875ee 84 list($this->width, $this->height, $this->type) = getimagesize($file);
b86ca09c
AE
85
86 switch ($this->type) {
87 case IMAGETYPE_GIF:
379875ee 88 $this->image = imagecreatefromgif($file);
b86ca09c
AE
89 break;
90
91 case IMAGETYPE_JPEG:
5cf84a99 92 // suppress warnings and properly handle errors
93395679 93 $this->image = @imagecreatefromjpeg($file);
5cf84a99
MS
94 if ($this->image === false) {
95 throw new SystemException("Could not read jpeg image '".$file."'.");
96 }
b86ca09c
AE
97 break;
98
99 case IMAGETYPE_PNG:
379875ee 100 $this->image = imagecreatefrompng($file);
b86ca09c
AE
101 break;
102
103 default:
104 throw new SystemException("Could not read image '".$file."', format is not recognized.");
105 break;
106 }
107 }
108
45140a75 109 /**
0fcfe5f6 110 * @inheritDoc
45140a75
TD
111 */
112 public function createEmptyImage($width, $height) {
379875ee 113 $this->image = imagecreate($width, $height);
45140a75
TD
114 $this->type = IMAGETYPE_PNG;
115 $this->setColor(0xFF, 0xFF, 0xFF);
116 $this->color = null;
117 }
118
b86ca09c 119 /**
0fcfe5f6 120 * @inheritDoc
a17de04e 121 */
b86ca09c 122 public function createThumbnail($maxWidth, $maxHeight, $obtainDimensions = true) {
13b7bb1f 123 $x = $y = 0;
32f6cd95
MW
124 $sourceWidth = $this->width;
125 $sourceHeight = $this->height;
b86ca09c
AE
126
127 if ($obtainDimensions) {
a3399fc5
AE
128 if ($maxWidth / $this->width < $maxHeight / $this->height) {
129 $width = $maxWidth;
130 $height = round($this->height * ($width / $this->width));
b86ca09c
AE
131 }
132 else {
a3399fc5
AE
133 $height = $maxHeight;
134 $width = round($this->width * ($height / $this->height));
b86ca09c
AE
135 }
136 }
137 else {
f7ac2ad1
MW
138 $width = $maxWidth;
139 $height = $maxHeight;
a3399fc5 140
f7ac2ad1
MW
141 if ($maxWidth / $this->width < $maxHeight / $this->height) {
142 $cut = (($sourceWidth * ($maxHeight / $this->height)) - $maxWidth) / ($maxHeight / $this->height);
143 $x = ceil($cut / 2);
144 $sourceWidth = $sourceWidth - $x * 2;
b86ca09c
AE
145 }
146 else {
f7ac2ad1
MW
147 $cut = (($sourceHeight * ($maxWidth / $this->width)) - $maxHeight) / ($maxWidth / $this->width);
148 $y = ceil($cut / 2);
149 $sourceHeight = $sourceHeight - $y * 2;
b86ca09c
AE
150 }
151 }
152
153 // resize image
379875ee
MS
154 $image = imagecreatetruecolor($width, $height);
155 imagealphablending($image, false);
156 imagecopyresampled($image, $this->image, 0, 0, $x, $y, $width, $height, $sourceWidth, $sourceHeight);
157 imagesavealpha($image, true);
b86ca09c
AE
158
159 return $image;
160 }
161
162 /**
0fcfe5f6 163 * @inheritDoc
b86ca09c
AE
164 */
165 public function clip($originX, $originY, $width, $height) {
379875ee
MS
166 $image = imagecreatetruecolor($width, $height);
167 imagealphablending($image, false);
b86ca09c 168
379875ee
MS
169 imagecopy($image, $this->image, 0, 0, $originX, $originY, $width, $height);
170 imagesavealpha($image, true);
b86ca09c 171
c3844f55 172 // reload image to update image resource, width and height
08c8e6f8 173 $this->load($image, $this->type);
b86ca09c
AE
174 }
175
176 /**
0fcfe5f6 177 * @inheritDoc
b86ca09c 178 */
6eb4648f 179 public function resize($originX, $originY, $originWidth, $originHeight, $targetWidth = 0, $targetHeight = 0) {
379875ee
MS
180 $image = imagecreatetruecolor($targetWidth, $targetHeight);
181 imagealphablending($image, false);
b86ca09c 182
379875ee
MS
183 imagecopyresampled($image, $this->image, 0, 0, $originX, $originY, $targetWidth, $targetHeight, $originWidth, $originHeight);
184 imagesavealpha($image, true);
b86ca09c 185
c3844f55 186 // reload image to update image resource, width and height
08c8e6f8 187 $this->load($image, $this->type);
b86ca09c
AE
188 }
189
190 /**
0fcfe5f6 191 * @inheritDoc
b86ca09c 192 */
64a820cf 193 public function drawRectangle($startX, $startY, $endX, $endY) {
379875ee 194 imagefilledrectangle($this->image, $startX, $startY, $endX, $endY, $this->color);
b86ca09c
AE
195 }
196
197 /**
0fcfe5f6 198 * @inheritDoc
b86ca09c 199 */
6840f856 200 public function drawText($text, $x, $y, $font, $size, $opacity = 1.0) {
cfdb4fa4
MS
201 // set opacity
202 $color = imagecolorallocatealpha($this->image, $this->colorData['red'], $this->colorData['green'], $this->colorData['blue'], (1 - $opacity) * 127);
d3e0ca88 203
46ce632b
MW
204 // draw text
205 imagettftext($this->image, $size, 0, $x, $y, $color, $font, $text);
cfdb4fa4
MS
206 }
207
208 /**
0fcfe5f6 209 * @inheritDoc
cfdb4fa4 210 */
6840f856 211 public function drawTextRelative($text, $position, $margin, $offsetX, $offsetY, $font, $size, $opacity = 1.0) {
46ce632b
MW
212 // split text into multiple lines
213 $lines = explode("\n", StringUtil::unifyNewlines($text));
cfdb4fa4 214
46ce632b
MW
215 // calc text width, height and first line height
216 $box = imagettfbbox($size, 0, $font, $text);
217 $firstLineBox = imagettfbbox($size, 0, $font, $lines[0]);
218 $textWidth = abs($box[0] - $box[2]);
219 $textHeight = abs($box[7] - $box[1]);
220 $firstLineHeight = abs($firstLineBox[7] - $firstLineBox[1]);
cfdb4fa4 221
46ce632b
MW
222 // calculate x coordinate
223 $x = 0;
224 switch ($position) {
225 case 'topLeft':
226 case 'middleLeft':
227 case 'bottomLeft':
228 $x = $margin;
8c56908c
MS
229 break;
230
46ce632b
MW
231 case 'topCenter':
232 case 'middleCenter':
233 case 'bottomCenter':
234 $x = floor(($this->getWidth() - $textWidth) / 2);
8c56908c
MS
235 break;
236
46ce632b
MW
237 case 'topRight':
238 case 'middleRight':
239 case 'bottomRight':
240 $x = $this->getWidth() - $textWidth - $margin;
8c56908c 241 break;
46ce632b 242 }
8c56908c 243
46ce632b
MW
244 // calculate y coordinate
245 $y = 0;
246 switch ($position) {
247 case 'topLeft':
248 case 'topCenter':
249 case 'topRight':
250 $y = $margin + $firstLineHeight;
8c56908c
MS
251 break;
252
46ce632b
MW
253 case 'middleLeft':
254 case 'middleCenter':
255 case 'middleRight':
256 $y = floor(($this->getHeight() - $textHeight) / 2) + $firstLineHeight;
8c56908c
MS
257 break;
258
46ce632b
MW
259 case 'bottomLeft':
260 case 'bottomCenter':
261 case 'bottomRight':
262 $y = $this->getHeight() - $textHeight + $firstLineHeight - $margin;
8c56908c 263 break;
cfdb4fa4 264 }
46ce632b
MW
265
266 $this->drawText($text, $x + $offsetX, $y + $offsetY, $font, $size, $opacity);
b86ca09c
AE
267 }
268
8c56908c 269 /**
0fcfe5f6 270 * @inheritDoc
8c56908c
MS
271 */
272 public function textFitsImage($text, $margin, $font, $size) {
273 $box = imagettfbbox($size, 0, $font, $text);
274
275 $textWidth = abs($box[0] - $box[2]);
276 $textHeight = abs($box[7] - $box[1]);
277
278 return ($textWidth + 2 * $margin <= $this->getWidth() && $textHeight + 2 * $margin <= $this->getHeight());
279 }
280
281 /**
0fcfe5f6 282 * @inheritDoc
8c56908c
MS
283 */
284 public function adjustFontSize($text, $margin, $font, $size) {
285 // does nothing
286 }
287
b86ca09c 288 /**
0fcfe5f6 289 * @inheritDoc
d726f13d 290 */
64a820cf 291 public function setColor($red, $green, $blue) {
379875ee 292 $this->color = imagecolorallocate($this->image, $red, $green, $blue);
cfdb4fa4
MS
293
294 // save data of the color
058cbd6a 295 $this->colorData = [
cfdb4fa4
MS
296 'red' => $red,
297 'green' => $green,
298 'blue' => $blue
058cbd6a 299 ];
b86ca09c
AE
300 }
301
302 /**
0fcfe5f6 303 * @inheritDoc
64a820cf
AE
304 */
305 public function hasColor() {
306 return ($this->color !== null);
307 }
308
04e3288d 309 /**
0fcfe5f6 310 * @inheritDoc
04e3288d
TS
311 */
312 public function setTransparentColor($red, $green, $blue) {
313 if ($this->type == IMAGETYPE_PNG) {
314 $color = imagecolorallocate($this->image, $red, $green, $blue);
379875ee 315 imagecolortransparent($this->image, $color);
04e3288d
TS
316 }
317 }
318
64a820cf 319 /**
0fcfe5f6 320 * @inheritDoc
64a820cf 321 */
b86ca09c 322 public function writeImage($image, $filename) {
a3399fc5
AE
323 if (!is_resource($image)) {
324 throw new SystemException("Given image is not a valid image resource.");
325 }
326
b86ca09c
AE
327 ob_start();
328
64a820cf 329 if ($this->type == IMAGETYPE_GIF) {
379875ee 330 imagegif($image);
b86ca09c 331 }
64a820cf 332 else if ($this->type == IMAGETYPE_PNG) {
379875ee 333 imagepng($image);
b86ca09c
AE
334 }
335 else if (function_exists('imageJPEG')) {
379875ee 336 imagejpeg($image, null, 90);
b86ca09c
AE
337 }
338
45140a75 339 $stream = ob_get_contents();
b86ca09c
AE
340 ob_end_clean();
341
45140a75 342 file_put_contents($filename, $stream);
b86ca09c
AE
343 }
344
a3399fc5 345 /**
0fcfe5f6 346 * @inheritDoc
d726f13d 347 */
a3399fc5
AE
348 public function getWidth() {
349 return $this->width;
350 }
351
352 /**
0fcfe5f6 353 * @inheritDoc
a3399fc5
AE
354 */
355 public function getHeight() {
356 return $this->height;
357 }
358
aa6ceb28 359 /**
0fcfe5f6 360 * @inheritDoc
aa6ceb28
MW
361 */
362 public function getType() {
363 return $this->type;
364 }
365
b86ca09c 366 /**
0fcfe5f6 367 * @inheritDoc
b86ca09c
AE
368 */
369 public function getImage() {
370 return $this->image;
371 }
87646f44 372
f268d97a 373 /**
0fcfe5f6 374 * @inheritDoc
f268d97a
MW
375 */
376 public function rotate($degrees) {
a8e8435c 377 // imagerotate interpretes degrees as counter-clockwise
f268d97a
MW
378 return imagerotate($this->image, (360.0 - $degrees), ($this->color ?: 0));
379 }
380
a7c0248c 381 /**
0fcfe5f6 382 * @inheritDoc
a7c0248c
MS
383 */
384 public function overlayImage($file, $x, $y, $opacity) {
385 $overlayImage = new self();
386 $overlayImage->loadFile($file);
387
388 // fix PNG alpha channel handling
389 // see http://php.net/manual/en/function.imagecopymerge.php#92787
390 $cut = imagecreatetruecolor($overlayImage->getWidth(), $overlayImage->getHeight());
391 imagecopy($cut, $this->image, 0, 0, $x, $y, $overlayImage->getWidth(), $overlayImage->getHeight());
392 imagecopy($cut, $overlayImage->image, 0, 0, 0, 0, $overlayImage->getWidth(), $overlayImage->getHeight());
393 imagecopymerge($this->image, $cut, $x, $y, 0, 0, $overlayImage->getWidth(), $overlayImage->getHeight(), $opacity * 100);
394 }
395
396 /**
0fcfe5f6 397 * @inheritDoc
a7c0248c
MS
398 */
399 public function overlayImageRelative($file, $position, $margin, $opacity) {
400 // does nothing
401 }
402
87646f44 403 /**
0fcfe5f6 404 * @inheritDoc
d726f13d 405 */
87646f44
AE
406 public static function isSupported() {
407 return true;
408 }
b86ca09c 409}