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