Commit | Line | Data |
---|---|---|
b86ca09c | 1 | <?php |
64a820cf | 2 | namespace wcf\system\image\adapter; |
a3399fc5 | 3 | use wcf\system\exception\SystemException; |
a17de04e | 4 | use 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 | */ |
16 | class 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 | } |