[TASK] CGL, switch(): Indent "case" correctly; no "break" in default
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Imaging / GraphicalFunctions.php
1 <?php
2 namespace TYPO3\CMS\Core\Imaging;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 /**
31 * Standard graphical functions
32 *
33 * Class contains a bunch of cool functions for manipulating graphics with GDlib/Freetype and ImageMagick.
34 * VERY OFTEN used with gifbuilder that extends this class and provides a TypoScript API to using these functions
35 *
36 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
37 */
38 class GraphicalFunctions {
39
40 // Internal configuration, set in init()
41 // The ImageMagick filename used for combining two images. This name changed during the versions.
42 /**
43 * @todo Define visibility
44 */
45 public $combineScript = 'combine';
46
47 // If set, there is no frame pointer prepended to the filenames.
48 /**
49 * @todo Define visibility
50 */
51 public $noFramePrepended = 0;
52
53 // This should be changed to 'png' if you want this class to read/make PNG-files instead!
54 /**
55 * @todo Define visibility
56 */
57 public $gifExtension = 'gif';
58
59 // File formats supported by gdlib. This variable get's filled in "init" method
60 /**
61 * @todo Define visibility
62 */
63 public $gdlibExtensions = '';
64
65 // Set to TRUE if generated png's should be truecolor by default
66 /**
67 * @todo Define visibility
68 */
69 public $png_truecolor = FALSE;
70
71 /**
72 * defines the RGB colorspace to use
73 *
74 * @var string
75 */
76 protected $colorspace = 'RGB';
77
78 /**
79 * colorspace names allowed
80 *
81 * @var array
82 */
83 protected $allowedColorSpaceNames = array(
84 'CMY',
85 'CMYK',
86 'Grey',
87 'HCL',
88 'HSB',
89 'HSL',
90 'HWB',
91 'Lab',
92 'LCH',
93 'LMS',
94 'Log',
95 'Luv',
96 'OHTA',
97 'Rec601Luma',
98 'Rec601YCbCr',
99 'Rec709Luma',
100 'Rec709YCbCr',
101 'RGB',
102 'sRGB',
103 'Transparent',
104 'XYZ',
105 'YCbCr',
106 'YCC',
107 'YIQ',
108 'YCbCr',
109 'YUV'
110 );
111
112 // 16777216 Colors is the maximum value for PNG, JPEG truecolor images (24-bit, 8-bit / Channel)
113 /**
114 * @todo Define visibility
115 */
116 public $truecolorColors = 16777215;
117
118 // If set, then all files in typo3temp will be logged in a database table. In addition to being a log of the files with original filenames, it also serves to secure that the same image is not rendered simultaneously by two different processes.
119 /**
120 * @todo Define visibility
121 */
122 public $enable_typo3temp_db_tracking = 0;
123
124 // Commalist of file extensions perceived as images by TYPO3. List should be set to 'gif,png,jpeg,jpg' if IM is not available. Lowercase and no spaces between!
125 /**
126 * @todo Define visibility
127 */
128 public $imageFileExt = 'gif,jpg,jpeg,png,tif,bmp,tga,pcx,ai,pdf';
129
130 // Commalist of web image extensions (can be shown by a webbrowser)
131 /**
132 * @todo Define visibility
133 */
134 public $webImageExt = 'gif,jpg,jpeg,png';
135
136 // Will be ' -negate' if ImageMagick ver 5.2+. See init();
137 /**
138 * @todo Define visibility
139 */
140 public $maskNegate = '';
141
142 /**
143 * @todo Define visibility
144 */
145 public $NO_IM_EFFECTS = '';
146
147 /**
148 * @todo Define visibility
149 */
150 public $cmds = array(
151 'jpg' => '',
152 'jpeg' => '',
153 'gif' => '',
154 'png' => '-colors 64'
155 );
156
157 /**
158 * @todo Define visibility
159 */
160 public $NO_IMAGE_MAGICK = '';
161
162 /**
163 * @todo Define visibility
164 */
165 public $V5_EFFECTS = 0;
166
167 /**
168 * @todo Define visibility
169 */
170 public $mayScaleUp = 1;
171
172 // Variables for testing, alternative usage etc.
173 // Filename prefix for images scaled in imageMagickConvert()
174 /**
175 * @todo Define visibility
176 */
177 public $filenamePrefix = '';
178
179 // Forcing the output filename of imageMagickConvert() to this value. However after calling imageMagickConvert() it will be set blank again.
180 /**
181 * @todo Define visibility
182 */
183 public $imageMagickConvert_forceFileNameBody = '';
184
185 // This flag should always be FALSE. If set TRUE, imageMagickConvert will always write a new file to the tempdir! Used for debugging.
186 /**
187 * @todo Define visibility
188 */
189 public $dontCheckForExistingTempFile = 0;
190
191 // Prevents imageMagickConvert() from compressing the gif-files with \TYPO3\CMS\Core\Utility\GeneralUtility::gif_compress()
192 /**
193 * @todo Define visibility
194 */
195 public $dontCompress = 0;
196
197 // For debugging ONLY!
198 /**
199 * @todo Define visibility
200 */
201 public $dontUnlinkTempFiles = 0;
202
203 // For debugging only. Filenames will not be based on mtime and only filename (not path) will be used. This key is also included in the hash of the filename...
204 /**
205 * @todo Define visibility
206 */
207 public $alternativeOutputKey = '';
208
209 // Internal:
210 // All ImageMagick commands executed is stored in this array for tracking. Used by the Install Tools Image section
211 /**
212 * @todo Define visibility
213 */
214 public $IM_commands = array();
215
216 /**
217 * @todo Define visibility
218 */
219 public $workArea = array();
220
221 /**
222 * Preserve the alpha transparency layer of read PNG images
223 *
224 * @var boolean
225 */
226 protected $saveAlphaLayer = FALSE;
227
228 // Constants:
229 // The temp-directory where to store the files. Normally relative to PATH_site but is allowed to be the absolute path AS LONG AS it is a subdir to PATH_site.
230 /**
231 * @todo Define visibility
232 */
233 public $tempPath = 'typo3temp/';
234
235 // Prefix for relative paths. Used in "show_item.php" script. Is prefixed the output file name IN imageMagickConvert()
236 /**
237 * @todo Define visibility
238 */
239 public $absPrefix = '';
240
241 // ImageMagick scaling command; "-geometry" eller "-sample". Used in makeText() and imageMagickConvert()
242 /**
243 * @todo Define visibility
244 */
245 public $scalecmd = '-geometry';
246
247 // Used by v5_blur() to simulate 10 continuous steps of blurring
248 /**
249 * @todo Define visibility
250 */
251 public $im5fx_blurSteps = '1x2,2x2,3x2,4x3,5x3,5x4,6x4,7x5,8x5,9x5';
252
253 // Used by v5_sharpen() to simulate 10 continuous steps of sharpening.
254 /**
255 * @todo Define visibility
256 */
257 public $im5fx_sharpenSteps = '1x2,2x2,3x2,2x3,3x3,4x3,3x4,4x4,4x5,5x5';
258
259 // This is the limit for the number of pixels in an image before it will be rendered as JPG instead of GIF/PNG
260 /**
261 * @todo Define visibility
262 */
263 public $pixelLimitGif = 10000;
264
265 // Array mapping HTML color names to RGB values.
266 /**
267 * @todo Define visibility
268 */
269 public $colMap = array(
270 'aqua' => array(0, 255, 255),
271 'black' => array(0, 0, 0),
272 'blue' => array(0, 0, 255),
273 'fuchsia' => array(255, 0, 255),
274 'gray' => array(128, 128, 128),
275 'green' => array(0, 128, 0),
276 'lime' => array(0, 255, 0),
277 'maroon' => array(128, 0, 0),
278 'navy' => array(0, 0, 128),
279 'olive' => array(128, 128, 0),
280 'purple' => array(128, 0, 128),
281 'red' => array(255, 0, 0),
282 'silver' => array(192, 192, 192),
283 'teal' => array(0, 128, 128),
284 'yellow' => array(255, 255, 0),
285 'white' => array(255, 255, 255)
286 );
287
288 /**
289 * Charset conversion object:
290 *
291 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
292 * @todo Define visibility
293 */
294 public $csConvObj;
295
296 // Is set to the native character set of the input strings.
297 /**
298 * @todo Define visibility
299 */
300 public $nativeCharset = '';
301
302 /**
303 * Init function. Must always call this when using the class.
304 * This function will read the configuration information from $GLOBALS['TYPO3_CONF_VARS']['GFX'] can set some values in internal variables.
305 *
306 * @return void
307 * @todo Define visibility
308 */
309 public function init() {
310 $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
311 if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
312 $this->gdlibExtensions .= ',jpg,jpeg';
313 }
314 if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
315 $this->gdlibExtensions .= ',png';
316 }
317 if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
318 $this->gdlibExtensions .= ',gif';
319 }
320 if ($gfxConf['png_truecolor']) {
321 $this->png_truecolor = TRUE;
322 }
323
324 if ($gfxConf['colorspace'] && in_array($gfxConf['colorspace'], $this->allowedColorSpaceNames, TRUE)) {
325 $this->colorspace = $gfxConf['colorspace'];
326 }
327
328 if (!$gfxConf['im']) {
329 $this->NO_IMAGE_MAGICK = 1;
330 }
331 if (!$this->NO_IMAGE_MAGICK && (!$gfxConf['im_version_5'] || $gfxConf['im_version_5'] === 'im4' || $gfxConf['im_version_5'] === 'im5')) {
332 throw new \RuntimeException('Your TYPO3 installation is configured to use an old version of ImageMagick, which is not supported anymore. ' . 'Please upgrade to ImageMagick version 6 or GraphicksMagick and set $TYPO3_CONF_VARS[\'GFX\'][\'im_version_5\'] appropriately.', 1305059666);
333 }
334 // When GIFBUILDER gets used in truecolor mode
335 // No colors parameter if we generate truecolor images.
336 if ($this->png_truecolor) {
337 $this->cmds['png'] = '';
338 }
339 // Setting default JPG parameters:
340 $this->jpegQuality = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, 75);
341 $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -sharpen 50 -quality ' . $this->jpegQuality);
342 if ($gfxConf['im_combine_filename']) {
343 $this->combineScript = $gfxConf['im_combine_filename'];
344 }
345 if ($gfxConf['im_noFramePrepended']) {
346 $this->noFramePrepended = 1;
347 }
348 if ($gfxConf['gdlib_png']) {
349 $this->gifExtension = 'png';
350 }
351 if ($gfxConf['enable_typo3temp_db_tracking']) {
352 $this->enable_typo3temp_db_tracking = $gfxConf['enable_typo3temp_db_tracking'];
353 }
354 $this->imageFileExt = $gfxConf['imagefile_ext'];
355 // This should be set if ImageMagick ver. 5+ is used.
356 if ($gfxConf['im_negate_mask']) {
357 // Boolean. Indicates if the mask images should be inverted first.
358 // This depends of the ImageMagick version. Below ver. 5.1 this should be FALSE.
359 // Above ImageMagick version 5.2+ it should be TRUE.
360 // Just set the flag if the masks works opposite the intension!
361 $this->maskNegate = ' -negate';
362 }
363 if ($gfxConf['im_no_effects']) {
364 // Boolean. This is necessary if using ImageMagick 5+.
365 // Effects in Imagemagick 5+ tends to render very slowly!!
366 // - therefore must be disabled in order not to perform sharpen, blurring and such.
367 $this->NO_IM_EFFECTS = 1;
368 $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . $this->jpegQuality);
369 }
370 // ... but if 'im_v5effects' is set, don't care about 'im_no_effects'
371 if ($gfxConf['im_v5effects']) {
372 $this->NO_IM_EFFECTS = 0;
373 $this->V5_EFFECTS = 1;
374 if ($gfxConf['im_v5effects'] > 0) {
375 $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . intval($gfxConf['jpg_quality']) . $this->v5_sharpen(10));
376 }
377 }
378 // Secures that images are not scaled up.
379 if ($gfxConf['im_noScaleUp']) {
380 $this->mayScaleUp = 0;
381 }
382 if (TYPO3_MODE == 'FE') {
383 $this->csConvObj = $GLOBALS['TSFE']->csConvObj;
384 } elseif (is_object($GLOBALS['LANG'])) {
385 // BE assumed:
386 $this->csConvObj = $GLOBALS['LANG']->csConvObj;
387 } else {
388 // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
389 $this->csConvObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
390 }
391 $this->nativeCharset = 'utf-8';
392 }
393
394 /*************************************************
395 *
396 * Layering images / "IMAGE" GIFBUILDER object
397 *
398 *************************************************/
399 /**
400 * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is TRUE.
401 * It reads the two images defined by $conf['file'] and $conf['mask'] and copies the $conf['file'] onto the input image pointer image using the $conf['mask'] as a grayscale mask
402 * The operation involves ImageMagick for combining.
403 *
404 * @param pointer $im GDlib image pointer
405 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
406 * @param array $workArea The current working area coordinates.
407 * @return void
408 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
409 * @todo Define visibility
410 */
411 public function maskImageOntoImage(&$im, $conf, $workArea) {
412 if ($conf['file'] && $conf['mask']) {
413 $imgInf = pathinfo($conf['file']);
414 $imgExt = strtolower($imgInf['extension']);
415 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->gdlibExtensions, $imgExt)) {
416 $BBimage = $this->imageMagickConvert($conf['file'], $this->gifExtension);
417 } else {
418 $BBimage = $this->getImageDimensions($conf['file']);
419 }
420 $maskInf = pathinfo($conf['mask']);
421 $maskExt = strtolower($maskInf['extension']);
422 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->gdlibExtensions, $maskExt)) {
423 $BBmask = $this->imageMagickConvert($conf['mask'], $this->gifExtension);
424 } else {
425 $BBmask = $this->getImageDimensions($conf['mask']);
426 }
427 if ($BBimage && $BBmask) {
428 $w = imagesx($im);
429 $h = imagesy($im);
430 $tmpStr = $this->randomName();
431 $theImage = $tmpStr . '_img.' . $this->gifExtension;
432 $theDest = $tmpStr . '_dest.' . $this->gifExtension;
433 $theMask = $tmpStr . '_mask.' . $this->gifExtension;
434 // Prepare overlay image
435 $cpImg = $this->imageCreateFromFile($BBimage[3]);
436 $destImg = imagecreatetruecolor($w, $h);
437 // Preserve alpha transparency
438 if ($this->saveAlphaLayer) {
439 imagesavealpha($destImg, TRUE);
440 $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
441 imagefill($destImg, 0, 0, $Bcolor);
442 } else {
443 $Bcolor = ImageColorAllocate($destImg, 0, 0, 0);
444 ImageFilledRectangle($destImg, 0, 0, $w, $h, $Bcolor);
445 }
446 $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
447 $this->ImageWrite($destImg, $theImage);
448 imageDestroy($cpImg);
449 imageDestroy($destImg);
450 // Prepare mask image
451 $cpImg = $this->imageCreateFromFile($BBmask[3]);
452 $destImg = imagecreatetruecolor($w, $h);
453 if ($this->saveAlphaLayer) {
454 imagesavealpha($destImg, TRUE);
455 $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
456 imagefill($destImg, 0, 0, $Bcolor);
457 } else {
458 $Bcolor = ImageColorAllocate($destImg, 0, 0, 0);
459 ImageFilledRectangle($destImg, 0, 0, $w, $h, $Bcolor);
460 }
461 $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
462 $this->ImageWrite($destImg, $theMask);
463 imageDestroy($cpImg);
464 imageDestroy($destImg);
465 // Mask the images
466 $this->ImageWrite($im, $theDest);
467 // Let combineExec handle maskNegation
468 $this->combineExec($theDest, $theImage, $theMask, $theDest, TRUE);
469 // The main image is loaded again...
470 $backIm = $this->imageCreateFromFile($theDest);
471 // ... and if nothing went wrong we load it onto the old one.
472 if ($backIm) {
473 if (!$this->saveAlphaLayer) {
474 ImageColorTransparent($backIm, -1);
475 }
476 $im = $backIm;
477 }
478 // Unlink files from process
479 if (!$this->dontUnlinkTempFiles) {
480 unlink($theDest);
481 unlink($theImage);
482 unlink($theMask);
483 }
484 }
485 }
486 }
487
488 /**
489 * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is FALSE (using only $conf['file'])
490 *
491 * @param pointer $im GDlib image pointer
492 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
493 * @param array $workArea The current working area coordinates.
494 * @return void
495 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), maskImageOntoImage()
496 * @todo Define visibility
497 */
498 public function copyImageOntoImage(&$im, $conf, $workArea) {
499 if ($conf['file']) {
500 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->gdlibExtensions, $conf['BBOX'][2])) {
501 $conf['BBOX'] = $this->imageMagickConvert($conf['BBOX'][3], $this->gifExtension);
502 $conf['file'] = $conf['BBOX'][3];
503 }
504 $cpImg = $this->imageCreateFromFile($conf['file']);
505 $this->copyGifOntoGif($im, $cpImg, $conf, $workArea);
506 imageDestroy($cpImg);
507 }
508 }
509
510 /**
511 * Copies two GDlib image pointers onto each other, using TypoScript configuration from $conf and the input $workArea definition.
512 *
513 * @param pointer $im GDlib image pointer, destination (bottom image)
514 * @param pointer $cpImg GDlib image pointer, source (top image)
515 * @param array $conf TypoScript array with the properties for the IMAGE GIFBUILDER object. Only used for the "tile" property value.
516 * @param array $workArea Work area
517 * @return void Works on the $im image pointer
518 * @access private
519 * @todo Define visibility
520 */
521 public function copyGifOntoGif(&$im, $cpImg, $conf, $workArea) {
522 $cpW = imagesx($cpImg);
523 $cpH = imagesy($cpImg);
524 $tile = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['tile']);
525 $tile[0] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($tile[0], 1, 20);
526 $tile[1] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($tile[1], 1, 20);
527 $cpOff = $this->objPosition($conf, $workArea, array($cpW * $tile[0], $cpH * $tile[1]));
528 for ($xt = 0; $xt < $tile[0]; $xt++) {
529 $Xstart = $cpOff[0] + $cpW * $xt;
530 // If this image is inside of the workArea, then go on
531 if ($Xstart + $cpW > $workArea[0]) {
532 // X:
533 if ($Xstart < $workArea[0]) {
534 $cpImgCutX = $workArea[0] - $Xstart;
535 $Xstart = $workArea[0];
536 } else {
537 $cpImgCutX = 0;
538 }
539 $w = $cpW - $cpImgCutX;
540 if ($Xstart > $workArea[0] + $workArea[2] - $w) {
541 $w = $workArea[0] + $workArea[2] - $Xstart;
542 }
543 // If this image is inside of the workArea, then go on
544 if ($Xstart < $workArea[0] + $workArea[2]) {
545 // Y:
546 for ($yt = 0; $yt < $tile[1]; $yt++) {
547 $Ystart = $cpOff[1] + $cpH * $yt;
548 // If this image is inside of the workArea, then go on
549 if ($Ystart + $cpH > $workArea[1]) {
550 if ($Ystart < $workArea[1]) {
551 $cpImgCutY = $workArea[1] - $Ystart;
552 $Ystart = $workArea[1];
553 } else {
554 $cpImgCutY = 0;
555 }
556 $h = $cpH - $cpImgCutY;
557 if ($Ystart > $workArea[1] + $workArea[3] - $h) {
558 $h = $workArea[1] + $workArea[3] - $Ystart;
559 }
560 // If this image is inside of the workArea, then go on
561 if ($Ystart < $workArea[1] + $workArea[3]) {
562 $this->imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, $w, $h, $w, $h);
563 }
564 }
565 }
566 }
567 }
568 }
569 }
570
571 /**
572 * Alternative function for using the similar PHP function imagecopyresized(). Used for GD2 only.
573 *
574 * OK, the reason for this stupid fix is the following story:
575 * GD1.x was capable of copying two images together and combining their palettes! GD2 is apparently not.
576 * With GD2 only the palette of the dest-image is used which mostly results in totally black images when trying to
577 * copy a color-ful image onto the destination.
578 * The GD2-fix is to
579 * 1) Create a blank TRUE-COLOR image
580 * 2) Copy the destination image onto that one
581 * 3) Then do the actual operation; Copying the source (top image) onto that
582 * 4) ... and return the result pointer.
583 * 5) Reduce colors (if we do not, the result may become strange!)
584 * It works, but the resulting images is now a true-color PNG which may be very large.
585 * So, why not use 'imagetruecolortopalette ($im, TRUE, 256)' - well because it does NOT WORK! So simple is that.
586 *
587 * @param resource $dstImg Destination image
588 * @param resource $srcImg Source image
589 * @param integer $dstX Destination x-coordinate
590 * @param integer $dstY Destination y-coordinate
591 * @param integer $srcX Source x-coordinate
592 * @param integer $srcY Source y-coordinate
593 * @param integer $dstWidth Destination width
594 * @param integer $dstHeight Destination height
595 * @param integer $srcWidth Source width
596 * @param integer $srcHeight Source height
597 * @return void
598 * @access private
599 * @see \TYPO3\CMS\Backend\Utility\IconUtility::imagecopyresized()
600 * @todo Define visibility
601 */
602 public function imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight) {
603 if (!$this->saveAlphaLayer) {
604 // Make true color image
605 $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
606 // Copy the source image onto that
607 imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
608 // Then copy the source image onto that (the actual operation!)
609 imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
610 // Set the destination image
611 $dstImg = $tmpImg;
612 } else {
613 imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
614 }
615 }
616
617 /********************************
618 *
619 * Text / "TEXT" GIFBUILDER object
620 *
621 ********************************/
622 /**
623 * Implements the "TEXT" GIFBUILDER object
624 *
625 * @param pointer $im GDlib image pointer
626 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
627 * @param array $workArea The current working area coordinates.
628 * @return void
629 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
630 * @todo Define visibility
631 */
632 public function makeText(&$im, $conf, $workArea) {
633 // Spacing
634 list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
635 // Position
636 $txtPos = $this->txtPosition($conf, $workArea, $conf['BBOX']);
637 $theText = $this->recodeString($conf['text']);
638 if ($conf['imgMap'] && is_array($conf['imgMap.'])) {
639 $this->addToMap($this->calcTextCordsForMap($conf['BBOX'][2], $txtPos, $conf['imgMap.']), $conf['imgMap.']);
640 }
641 if (!$conf['hideButCreateMap']) {
642 // Font Color:
643 $cols = $this->convertColor($conf['fontColor']);
644 // NiceText is calculated
645 if (!$conf['niceText']) {
646 $Fcolor = ImageColorAllocate($im, $cols[0], $cols[1], $cols[2]);
647 // antiAliasing is setup:
648 $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
649 for ($a = 0; $a < $conf['iterations']; $a++) {
650 // If any kind of spacing applys, we use this function:
651 if ($spacing || $wordSpacing) {
652 $this->SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, self::prependAbsolutePath($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
653 } else {
654 $this->renderTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf);
655 }
656 }
657 } else {
658 // NICETEXT::
659 // options anti_aliased and iterations is NOT available when doing this!!
660 $w = imagesx($im);
661 $h = imagesy($im);
662 $tmpStr = $this->randomName();
663 $fileMenu = $tmpStr . '_menuNT.' . $this->gifExtension;
664 $fileColor = $tmpStr . '_colorNT.' . $this->gifExtension;
665 $fileMask = $tmpStr . '_maskNT.' . $this->gifExtension;
666 // Scalefactor
667 $sF = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
668 $newW = ceil($sF * imagesx($im));
669 $newH = ceil($sF * imagesy($im));
670 // Make mask
671 $maskImg = imagecreatetruecolor($newW, $newH);
672 $Bcolor = ImageColorAllocate($maskImg, 255, 255, 255);
673 ImageFilledRectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
674 $Fcolor = ImageColorAllocate($maskImg, 0, 0, 0);
675 // If any kind of spacing applies, we use this function:
676 if ($spacing || $wordSpacing) {
677 $this->SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, self::prependAbsolutePath($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
678 } else {
679 $this->renderTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf, $sF);
680 }
681 $this->ImageWrite($maskImg, $fileMask);
682 ImageDestroy($maskImg);
683 // Downscales the mask
684 if ($this->NO_IM_EFFECTS) {
685 if ($this->maskNegate) {
686 // Negate 2 times makes no negate...
687 $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '!');
688 } else {
689 $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '! -negate');
690 }
691 } else {
692 if ($this->maskNegate) {
693 $command = trim($conf['niceText.']['before'] . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . $conf['niceText.']['after']);
694 } else {
695 $command = trim($conf['niceText.']['before'] . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . $conf['niceText.']['after'] . ' -negate');
696 }
697 if ($conf['niceText.']['sharpen']) {
698 if ($this->V5_EFFECTS) {
699 $command .= $this->v5_sharpen($conf['niceText.']['sharpen']);
700 } else {
701 $command .= ' -sharpen ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['niceText.']['sharpen'], 1, 99);
702 }
703 }
704 }
705 $this->imageMagickExec($fileMask, $fileMask, $command);
706 // Make the color-file
707 $colorImg = imagecreatetruecolor($w, $h);
708 $Ccolor = ImageColorAllocate($colorImg, $cols[0], $cols[1], $cols[2]);
709 ImageFilledRectangle($colorImg, 0, 0, $w, $h, $Ccolor);
710 $this->ImageWrite($colorImg, $fileColor);
711 ImageDestroy($colorImg);
712 // The mask is applied
713 // The main pictures is saved temporarily
714 $this->ImageWrite($im, $fileMenu);
715 $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
716 // The main image is loaded again...
717 $backIm = $this->imageCreateFromFile($fileMenu);
718 // ... and if nothing went wrong we load it onto the old one.
719 if ($backIm) {
720 if (!$this->saveAlphaLayer) {
721 ImageColorTransparent($backIm, -1);
722 }
723 $im = $backIm;
724 }
725 // Deleting temporary files;
726 if (!$this->dontUnlinkTempFiles) {
727 unlink($fileMenu);
728 unlink($fileColor);
729 unlink($fileMask);
730 }
731 }
732 }
733 }
734
735 /**
736 * Calculates text position for printing the text onto the image based on configuration like alignment and workarea.
737 *
738 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
739 * @param array $workArea Workarea definition
740 * @param array $BB Bounding box information, was set in \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
741 * @return array [0]=x, [1]=y, [2]=w, [3]=h
742 * @access private
743 * @see makeText()
744 * @todo Define visibility
745 */
746 public function txtPosition($conf, $workArea, $BB) {
747 $bbox = $BB[2];
748 $angle = intval($conf['angle']) / 180 * pi();
749 $conf['angle'] = 0;
750 $straightBB = $this->calcBBox($conf);
751 // offset, align, valign, workarea
752 // [0]=x, [1]=y, [2]=w, [3]=h
753 $result = array();
754 $result[2] = $BB[0];
755 $result[3] = $BB[1];
756 $w = $workArea[2];
757 $h = $workArea[3];
758 switch ($conf['align']) {
759 case 'right':
760
761 case 'center':
762 $factor = abs(cos($angle));
763 $sign = cos($angle) < 0 ? -1 : 1;
764 $len1 = $sign * $factor * $straightBB[0];
765 $len2 = $sign * $BB[0];
766 $result[0] = $w - ceil(($len2 * $factor + (1 - $factor) * $len1));
767 $factor = abs(sin($angle));
768 $sign = sin($angle) < 0 ? -1 : 1;
769 $len1 = $sign * $factor * $straightBB[0];
770 $len2 = $sign * $BB[1];
771 $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
772 break;
773 }
774 switch ($conf['align']) {
775 case 'right':
776 break;
777 case 'center':
778 $result[0] = round($result[0] / 2);
779 $result[1] = round($result[1] / 2);
780 break;
781 default:
782 $result[0] = 0;
783 $result[1] = 0;
784 }
785 $result = $this->applyOffset($result, \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']));
786 $result = $this->applyOffset($result, $workArea);
787 return $result;
788 }
789
790 /**
791 * Calculates bounding box information for the TEXT GIFBUILDER object.
792 *
793 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
794 * @return array Array with three keys [0]/[1] being x/y and [2] being the bounding box array
795 * @access private
796 * @see txtPosition(), \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
797 * @todo Define visibility
798 */
799 public function calcBBox($conf) {
800 $sF = $this->getTextScalFactor($conf);
801 list($spacing, $wordSpacing) = $this->calcWordSpacing($conf, $sF);
802 $theText = $this->recodeString($conf['text']);
803 $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $theText, $conf['splitRendering.'], $sF);
804 $theBBoxInfo = $charInf;
805 if ($conf['angle']) {
806 $xArr = array($charInf[0], $charInf[2], $charInf[4], $charInf[6]);
807 $yArr = array($charInf[1], $charInf[3], $charInf[5], $charInf[7]);
808 $x = max($xArr) - min($xArr);
809 $y = max($yArr) - min($yArr);
810 } else {
811 $x = $charInf[2] - $charInf[0];
812 $y = $charInf[1] - $charInf[7];
813 }
814 // Set original lineHeight (used by line breaks):
815 $theBBoxInfo['lineHeight'] = $y;
816 // If any kind of spacing applys, we use this function:
817 if ($spacing || $wordSpacing) {
818 $x = 0;
819 if (!$spacing && $wordSpacing) {
820 $bits = explode(' ', $theText);
821 foreach ($bits as $word) {
822 $word .= ' ';
823 $wordInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
824 $wordW = $wordInf[2] - $wordInf[0];
825 $x += $wordW + $wordSpacing;
826 }
827 } else {
828 $utf8Chars = $this->singleChars($theText);
829 // For each UTF-8 char, do:
830 foreach ($utf8Chars as $char) {
831 $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
832 $charW = $charInf[2] - $charInf[0];
833 $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
834 }
835 }
836 } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
837 $maxWidth = 0;
838 $currentWidth = 0;
839 $breakWidth = $conf['breakWidth'];
840 $breakSpace = $this->getBreakSpace($conf, $theBBoxInfo);
841 $wordPairs = $this->getWordPairsForLineBreak($conf['text']);
842 // Iterate through all word pairs:
843 foreach ($wordPairs as $index => $wordPair) {
844 $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
845 if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
846 $currentWidth += $wordWidth;
847 } else {
848 $maxWidth = max($maxWidth, $currentWidth);
849 $y += $breakSpace;
850 // Restart:
851 $currentWidth = $wordWidth;
852 }
853 }
854 $x = max($maxWidth, $currentWidth) * $sF;
855 }
856 if ($sF > 1) {
857 $x = ceil($x / $sF);
858 $y = ceil($y / $sF);
859 if (is_array($theBBoxInfo)) {
860 foreach ($theBBoxInfo as &$value) {
861 $value = ceil($value / $sF);
862 }
863 unset($value);
864 }
865 }
866 return array($x, $y, $theBBoxInfo);
867 }
868
869 /**
870 * Adds an <area> tag to the internal variable $this->map which is used to accumulate the content for an ImageMap
871 *
872 * @param array $cords Coordinates for a polygon image map as created by ->calcTextCordsForMap()
873 * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
874 * @return void
875 * @access private
876 * @see makeText(), calcTextCordsForMap()
877 * @todo Define visibility
878 */
879 public function addToMap($cords, $conf) {
880 $this->map .= '<area' . ' shape="poly"' . ' coords="' . implode(',', $cords) . '"' . ' href="' . htmlspecialchars($conf['url']) . '"' . ($conf['target'] ? ' target="' . htmlspecialchars($conf['target']) . '"' : '') . $JS . (strlen($conf['titleText']) ? ' title="' . htmlspecialchars($conf['titleText']) . '"' : '') . ' alt="' . htmlspecialchars($conf['altText']) . '" />';
881 }
882
883 /**
884 * Calculating the coordinates for a TEXT string on an image map. Used in an <area> tag
885 *
886 * @param array $cords Coordinates (from BBOX array)
887 * @param array $offset Offset array
888 * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
889 * @return array
890 * @access private
891 * @see makeText(), calcTextCordsForMap()
892 * @todo Define visibility
893 */
894 public function calcTextCordsForMap($cords, $offset, $conf) {
895 $pars = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['explode'] . ',');
896 $newCords[0] = $cords[0] + $offset[0] - $pars[0];
897 $newCords[1] = $cords[1] + $offset[1] + $pars[1];
898 $newCords[2] = $cords[2] + $offset[0] + $pars[0];
899 $newCords[3] = $cords[3] + $offset[1] + $pars[1];
900 $newCords[4] = $cords[4] + $offset[0] + $pars[0];
901 $newCords[5] = $cords[5] + $offset[1] - $pars[1];
902 $newCords[6] = $cords[6] + $offset[0] - $pars[0];
903 $newCords[7] = $cords[7] + $offset[1] - $pars[1];
904 return $newCords;
905 }
906
907 /**
908 * Printing text onto an image like the PHP function imageTTFText does but in addition it offers options for spacing of letters and words.
909 * Spacing is done by printing one char at a time and this means that the spacing is rather uneven and probably not very nice.
910 * See
911 *
912 * @param pointer $im (See argument for PHP function imageTTFtext())
913 * @param integer $fontSize (See argument for PHP function imageTTFtext())
914 * @param integer $angle (See argument for PHP function imageTTFtext())
915 * @param integer $x (See argument for PHP function imageTTFtext())
916 * @param integer $y (See argument for PHP function imageTTFtext())
917 * @param integer $Fcolor (See argument for PHP function imageTTFtext())
918 * @param string $fontFile (See argument for PHP function imageTTFtext())
919 * @param string $text (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
920 * @param integer $spacing The spacing of letters in pixels
921 * @param integer $wordSpacing The spacing of words in pixels
922 * @param array $splitRenderingConf Array
923 * @param integer $sF Scale factor
924 * @return void
925 * @access private
926 * @todo Define visibility
927 */
928 public function SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF = 1) {
929 $spacing *= $sF;
930 $wordSpacing *= $sF;
931 if (!$spacing && $wordSpacing) {
932 $bits = explode(' ', $text);
933 foreach ($bits as $word) {
934 $word .= ' ';
935 $wordInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
936 $wordW = $wordInf[2] - $wordInf[0];
937 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
938 $x += $wordW + $wordSpacing;
939 }
940 } else {
941 $utf8Chars = $this->singleChars($text);
942 // For each UTF-8 char, do:
943 foreach ($utf8Chars as $char) {
944 $charInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
945 $charW = $charInf[2] - $charInf[0];
946 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
947 $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
948 }
949 }
950 }
951
952 /**
953 * Function that finds the right fontsize that will render the textstring within a certain width
954 *
955 * @param array $conf The TypoScript properties of the TEXT GIFBUILDER object
956 * @return integer The new fontSize
957 * @access private
958 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
959 * @todo Define visibility
960 */
961 public function fontResize($conf) {
962 // You have to use +calc options like [10.h] in 'offset' to get the right position of your text-image, if you use +calc in XY height!!!!
963 $maxWidth = intval($conf['maxWidth']);
964 list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
965 if ($maxWidth) {
966 // If any kind of spacing applys, we use this function:
967 if ($spacing || $wordSpacing) {
968 return $conf['fontSize'];
969 } else {
970 do {
971 // Determine bounding box.
972 $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $this->recodeString($conf['text']), $conf['splitRendering.']);
973 if ($conf['angle'] < 0) {
974 $pixelWidth = abs($bounds[4] - $bounds[0]);
975 } elseif ($conf['angle'] > 0) {
976 $pixelWidth = abs($bounds[2] - $bounds[6]);
977 } else {
978 $pixelWidth = abs($bounds[4] - $bounds[6]);
979 }
980 // Size is fine, exit:
981 if ($pixelWidth <= $maxWidth) {
982 break;
983 } else {
984 $conf['fontSize']--;
985 }
986 } while ($conf['fontSize'] > 1);
987 }
988 }
989 return $conf['fontSize'];
990 }
991
992 /**
993 * Wrapper for ImageTTFBBox
994 *
995 * @param integer $fontSize (See argument for PHP function ImageTTFBBox())
996 * @param integer $angle (See argument for PHP function ImageTTFBBox())
997 * @param string $fontFile (See argument for PHP function ImageTTFBBox())
998 * @param string $string (See argument for PHP function ImageTTFBBox())
999 * @param array $splitRendering Split-rendering configuration
1000 * @param integer $sF Scale factor
1001 * @return array Information array.
1002 * @todo Define visibility
1003 */
1004 public function ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF = 1) {
1005 // Initialize:
1006 $offsetInfo = array();
1007 $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
1008 // Traverse string parts:
1009 foreach ($stringParts as $strCfg) {
1010 $fontFile = self::prependAbsolutePath($strCfg['fontFile']);
1011 if (is_readable($fontFile)) {
1012 /**
1013 * Calculate Bounding Box for part.
1014 * Due to a PHP bug, we must retry if $calc[2] is negative.
1015 *
1016 * @see https://bugs.php.net/bug.php?id=51315
1017 * @see https://bugs.php.net/bug.php?id=22513
1018 */
1019 $try = 0;
1020 do {
1021 $calc = ImageTTFBBox(\TYPO3\CMS\Core\Utility\GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
1022 } while ($calc[2] < 0 && $try++ < 10);
1023 // Calculate offsets:
1024 if (!count($offsetInfo)) {
1025 // First run, just copy over.
1026 $offsetInfo = $calc;
1027 } else {
1028 $offsetInfo[2] += $calc[2] - $calc[0] + intval($splitRendering['compX']) + intval($strCfg['xSpaceBefore']) + intval($strCfg['xSpaceAfter']);
1029 $offsetInfo[3] += $calc[3] - $calc[1] - intval($splitRendering['compY']) - intval($strCfg['ySpaceBefore']) - intval($strCfg['ySpaceAfter']);
1030 $offsetInfo[4] += $calc[4] - $calc[6] + intval($splitRendering['compX']) + intval($strCfg['xSpaceBefore']) + intval($strCfg['xSpaceAfter']);
1031 $offsetInfo[5] += $calc[5] - $calc[7] - intval($splitRendering['compY']) - intval($strCfg['ySpaceBefore']) - intval($strCfg['ySpaceAfter']);
1032 }
1033 } else {
1034 debug('cannot read file: ' . $fontFile, 'TYPO3\\CMS\\Core\\Imaging\\GraphicalFunctions::ImageTTFBBoxWrapper()');
1035 }
1036 }
1037 return $offsetInfo;
1038 }
1039
1040 /**
1041 * Wrapper for ImageTTFText
1042 *
1043 * @param pointer $im (See argument for PHP function imageTTFtext())
1044 * @param integer $fontSize (See argument for PHP function imageTTFtext())
1045 * @param integer $angle (See argument for PHP function imageTTFtext())
1046 * @param integer $x (See argument for PHP function imageTTFtext())
1047 * @param integer $y (See argument for PHP function imageTTFtext())
1048 * @param integer $color (See argument for PHP function imageTTFtext())
1049 * @param string $fontFile (See argument for PHP function imageTTFtext())
1050 * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
1051 * @param array $splitRendering Split-rendering configuration
1052 * @param integer $sF Scale factor
1053 * @return void
1054 * @todo Define visibility
1055 */
1056 public function ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF = 1) {
1057 // Initialize:
1058 $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
1059 $x = ceil($sF * $x);
1060 $y = ceil($sF * $y);
1061 // Traverse string parts:
1062 foreach ($stringParts as $i => $strCfg) {
1063 // Initialize:
1064 $colorIndex = $color;
1065 // Set custom color if any (only when niceText is off):
1066 if ($strCfg['color'] && $sF == 1) {
1067 $cols = $this->convertColor($strCfg['color']);
1068 $colorIndex = ImageColorAllocate($im, $cols[0], $cols[1], $cols[2]);
1069 $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
1070 }
1071 // Setting xSpaceBefore
1072 if ($i) {
1073 $x += intval($strCfg['xSpaceBefore']);
1074 $y -= intval($strCfg['ySpaceBefore']);
1075 }
1076 $fontFile = self::prependAbsolutePath($strCfg['fontFile']);
1077 if (is_readable($fontFile)) {
1078 // Render part:
1079 ImageTTFText($im, \TYPO3\CMS\Core\Utility\GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
1080 // Calculate offset to apply:
1081 $wordInf = ImageTTFBBox(\TYPO3\CMS\Core\Utility\GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, self::prependAbsolutePath($strCfg['fontFile']), $strCfg['str']);
1082 $x += $wordInf[2] - $wordInf[0] + intval($splitRendering['compX']) + intval($strCfg['xSpaceAfter']);
1083 $y += $wordInf[5] - $wordInf[7] - intval($splitRendering['compY']) - intval($strCfg['ySpaceAfter']);
1084 } else {
1085 debug('cannot read file: ' . $fontFile, 'TYPO3\\CMS\\Core\\Imaging\\GraphicalFunctions::ImageTTFTextWrapper()');
1086 }
1087 }
1088 }
1089
1090 /**
1091 * Splitting a string for ImageTTFBBox up into an array where each part has its own configuration options.
1092 *
1093 * @param string $string UTF-8 string
1094 * @param array $splitRendering Split-rendering configuration from GIFBUILDER TEXT object.
1095 * @param integer $fontSize Current fontsize
1096 * @param string $fontFile Current font file
1097 * @return array Array with input string splitted according to configuration
1098 * @todo Define visibility
1099 */
1100 public function splitString($string, $splitRendering, $fontSize, $fontFile) {
1101 // Initialize by setting the whole string and default configuration as the first entry.
1102 $result = array();
1103 $result[] = array(
1104 'str' => $string,
1105 'fontSize' => $fontSize,
1106 'fontFile' => $fontFile
1107 );
1108 // Traverse the split-rendering configuration:
1109 // Splitting will create more entries in $result with individual configurations.
1110 if (is_array($splitRendering)) {
1111 $sKeyArray = \TYPO3\CMS\Core\TypoScript\TemplateService::sortedKeyList($splitRendering);
1112 // Traverse configured options:
1113 foreach ($sKeyArray as $key) {
1114 $cfg = $splitRendering[$key . '.'];
1115 // Process each type of split rendering keyword:
1116 switch ((string) $splitRendering[$key]) {
1117 case 'highlightWord':
1118 if (strlen($cfg['value'])) {
1119 $newResult = array();
1120 // Traverse the current parts of the result array:
1121 foreach ($result as $part) {
1122 // Explode the string value by the word value to highlight:
1123 $explodedParts = explode($cfg['value'], $part['str']);
1124 foreach ($explodedParts as $c => $expValue) {
1125 if (strlen($expValue)) {
1126 $newResult[] = array_merge($part, array('str' => $expValue));
1127 }
1128 if ($c + 1 < count($explodedParts)) {
1129 $newResult[] = array(
1130 'str' => $cfg['value'],
1131 'fontSize' => $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1132 'fontFile' => $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1133 'color' => $cfg['color'],
1134 'xSpaceBefore' => $cfg['xSpaceBefore'],
1135 'xSpaceAfter' => $cfg['xSpaceAfter'],
1136 'ySpaceBefore' => $cfg['ySpaceBefore'],
1137 'ySpaceAfter' => $cfg['ySpaceAfter']
1138 );
1139 }
1140 }
1141 }
1142 // Set the new result as result array:
1143 if (count($newResult)) {
1144 $result = $newResult;
1145 }
1146 }
1147 break;
1148 case 'charRange':
1149 if (strlen($cfg['value'])) {
1150 // Initialize range:
1151 $ranges = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $cfg['value'], 1);
1152 foreach ($ranges as $i => $rangeDef) {
1153 $ranges[$i] = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode('-', $ranges[$i]);
1154 if (!isset($ranges[$i][1])) {
1155 $ranges[$i][1] = $ranges[$i][0];
1156 }
1157 }
1158 $newResult = array();
1159 // Traverse the current parts of the result array:
1160 foreach ($result as $part) {
1161 // Initialize:
1162 $currentState = -1;
1163 $bankAccum = '';
1164 // Explode the string value by the word value to highlight:
1165 $utf8Chars = $this->singleChars($part['str']);
1166 foreach ($utf8Chars as $utfChar) {
1167 // Find number and evaluate position:
1168 $uNumber = $this->csConvObj->utf8CharToUnumber($utfChar);
1169 $inRange = 0;
1170 foreach ($ranges as $rangeDef) {
1171 if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
1172 $inRange = 1;
1173 break;
1174 }
1175 }
1176 if ($currentState == -1) {
1177 $currentState = $inRange;
1178 }
1179 // Initialize first char
1180 // Switch bank:
1181 if ($inRange != $currentState && !\TYPO3\CMS\Core\Utility\GeneralUtility::inList('32,10,13,9', $uNumber)) {
1182 // Set result:
1183 if (strlen($bankAccum)) {
1184 $newResult[] = array(
1185 'str' => $bankAccum,
1186 'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1187 'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1188 'color' => $currentState ? $cfg['color'] : '',
1189 'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1190 'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1191 'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1192 'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1193 );
1194 }
1195 // Initialize new settings:
1196 $currentState = $inRange;
1197 $bankAccum = '';
1198 }
1199 // Add char to bank:
1200 $bankAccum .= $utfChar;
1201 }
1202 // Set result for FINAL part:
1203 if (strlen($bankAccum)) {
1204 $newResult[] = array(
1205 'str' => $bankAccum,
1206 'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1207 'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1208 'color' => $currentState ? $cfg['color'] : '',
1209 'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1210 'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1211 'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1212 'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1213 );
1214 }
1215 }
1216 // Set the new result as result array:
1217 if (count($newResult)) {
1218 $result = $newResult;
1219 }
1220 }
1221 break;
1222 }
1223 }
1224 }
1225 return $result;
1226 }
1227
1228 /**
1229 * Calculates the spacing and wordSpacing values
1230 *
1231 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1232 * @param integer $scaleFactor TypoScript value from eg $conf['niceText.']['scaleFactor']
1233 * @return array Array with two keys [0]/[1] being array($spacing,$wordSpacing)
1234 * @access private
1235 * @see calcBBox()
1236 * @todo Define visibility
1237 */
1238 public function calcWordSpacing($conf, $scaleFactor = 1) {
1239 $spacing = intval($conf['spacing']);
1240 $wordSpacing = intval($conf['wordSpacing']);
1241 $wordSpacing = $wordSpacing ? $wordSpacing : $spacing * 2;
1242 $spacing *= $scaleFactor;
1243 $wordSpacing *= $scaleFactor;
1244 return array($spacing, $wordSpacing);
1245 }
1246
1247 /**
1248 * Calculates and returns the niceText.scaleFactor
1249 *
1250 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1251 * @return integer TypoScript value from eg $conf['niceText.']['scaleFactor']
1252 * @access private
1253 * @todo Define visibility
1254 */
1255 public function getTextScalFactor($conf) {
1256 if (!$conf['niceText']) {
1257 $sF = 1;
1258 } else {
1259 // NICETEXT::
1260 $sF = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
1261 }
1262 return $sF;
1263 }
1264
1265 /**
1266 * Renders a regular text and takes care of a possible line break automatically.
1267 *
1268 * @param pointer $im (See argument for PHP function imageTTFtext())
1269 * @param integer $fontSize (See argument for PHP function imageTTFtext())
1270 * @param integer $angle (See argument for PHP function imageTTFtext())
1271 * @param integer $x (See argument for PHP function imageTTFtext())
1272 * @param integer $y (See argument for PHP function imageTTFtext())
1273 * @param integer $color (See argument for PHP function imageTTFtext())
1274 * @param string $fontFile (See argument for PHP function imageTTFtext())
1275 * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
1276 * @param array $splitRendering Split-rendering configuration
1277 * @param array $conf The configuration
1278 * @param integer $sF Scale factor
1279 * @return void
1280 */
1281 protected function renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF = 1) {
1282 if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
1283 $phrase = '';
1284 $currentWidth = 0;
1285 $breakWidth = $conf['breakWidth'];
1286 $breakSpace = $this->getBreakSpace($conf);
1287 $wordPairs = $this->getWordPairsForLineBreak($string);
1288 // Iterate through all word pairs:
1289 foreach ($wordPairs as $index => $wordPair) {
1290 $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
1291 if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
1292 $currentWidth += $wordWidth;
1293 $phrase .= $wordPair;
1294 } else {
1295 // Render the current phrase that is below breakWidth:
1296 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1297 // Calculate the news height offset:
1298 $y += $breakSpace;
1299 // Restart the phrase:
1300 $currentWidth = $wordWidth;
1301 $phrase = $wordPair;
1302 }
1303 }
1304 // Render the remaining phrase:
1305 if ($currentWidth) {
1306 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1307 }
1308 } else {
1309 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
1310 }
1311 }
1312
1313 /**
1314 * Gets the word pairs used for automatic line breaks.
1315 *
1316 * @param string $string
1317 * @return array
1318 */
1319 protected function getWordPairsForLineBreak($string) {
1320 $wordPairs = array();
1321 $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
1322 $wordsCount = count($wordsArray);
1323 for ($index = 0; $index < $wordsCount; $index += 2) {
1324 $wordPairs[] = $wordsArray[$index] . $wordsArray[($index + 1)];
1325 }
1326 return $wordPairs;
1327 }
1328
1329 /**
1330 * Gets the rendered text width.
1331 *
1332 * @param string $text
1333 * @param array $conf
1334 * @param integer
1335 */
1336 protected function getRenderedTextWidth($text, $conf) {
1337 $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $this->recodeString($text), $conf['splitRendering.']);
1338 if ($conf['angle'] < 0) {
1339 $pixelWidth = abs($bounds[4] - $bounds[0]);
1340 } elseif ($conf['angle'] > 0) {
1341 $pixelWidth = abs($bounds[2] - $bounds[6]);
1342 } else {
1343 $pixelWidth = abs($bounds[4] - $bounds[6]);
1344 }
1345 return $pixelWidth;
1346 }
1347
1348 /**
1349 * Gets the break space for each new line.
1350 *
1351 * @param array $conf TypoScript configuration for the currently rendered object
1352 * @param array $boundingBox The bounding box the the currently rendered object
1353 * @return integer The break space
1354 */
1355 protected function getBreakSpace($conf, array $boundingBox = NULL) {
1356 if (!isset($boundingBox)) {
1357 $boundingBox = $this->calcBBox($conf);
1358 $boundingBox = $boundingBox[2];
1359 }
1360 if (isset($conf['breakSpace']) && $conf['breakSpace']) {
1361 $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
1362 } else {
1363 $breakSpace = $boundingBox['lineHeight'];
1364 }
1365 return $breakSpace;
1366 }
1367
1368 /*********************************************
1369 *
1370 * Other GIFBUILDER objects related to TEXT
1371 *
1372 *********************************************/
1373 /**
1374 * Implements the "OUTLINE" GIFBUILDER object / property for the TEXT object
1375 *
1376 * @param pointer $im GDlib image pointer
1377 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1378 * @param array $workArea The current working area coordinates.
1379 * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1380 * @return void
1381 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeText()
1382 * @todo Define visibility
1383 */
1384 public function makeOutline(&$im, $conf, $workArea, $txtConf) {
1385 $thickness = intval($conf['thickness']);
1386 if ($thickness) {
1387 $txtConf['fontColor'] = $conf['color'];
1388 $outLineDist = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($thickness, 1, 2);
1389 for ($b = 1; $b <= $outLineDist; $b++) {
1390 if ($b == 1) {
1391 $it = 8;
1392 } else {
1393 $it = 16;
1394 }
1395 $outL = $this->circleOffset($b, $it);
1396 for ($a = 0; $a < $it; $a++) {
1397 $this->makeText($im, $txtConf, $this->applyOffset($workArea, $outL[$a]));
1398 }
1399 }
1400 }
1401 }
1402
1403 /**
1404 * Creates some offset values in an array used to simulate a circularly applied outline around TEXT
1405 *
1406 * access private
1407 *
1408 * @param integer $distance Distance
1409 * @param integer $iterations Iterations.
1410 * @return array
1411 * @see makeOutline()
1412 * @todo Define visibility
1413 */
1414 public function circleOffset($distance, $iterations) {
1415 $res = array();
1416 if ($distance && $iterations) {
1417 for ($a = 0; $a < $iterations; $a++) {
1418 $yOff = round(sin((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1419 if ($yOff) {
1420 $yOff = intval(ceil(abs(($yOff / 100))) * ($yOff / abs($yOff)));
1421 }
1422 $xOff = round(cos((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1423 if ($xOff) {
1424 $xOff = intval(ceil(abs(($xOff / 100))) * ($xOff / abs($xOff)));
1425 }
1426 $res[$a] = array($xOff, $yOff);
1427 }
1428 }
1429 return $res;
1430 }
1431
1432 /**
1433 * Implements the "EMBOSS" GIFBUILDER object / property for the TEXT object
1434 *
1435 * @param pointer $im GDlib image pointer
1436 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1437 * @param array $workArea The current working area coordinates.
1438 * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1439 * @return void
1440 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeShadow()
1441 * @todo Define visibility
1442 */
1443 public function makeEmboss(&$im, $conf, $workArea, $txtConf) {
1444 $conf['color'] = $conf['highColor'];
1445 $this->makeShadow($im, $conf, $workArea, $txtConf);
1446 $newOffset = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']);
1447 $newOffset[0] *= -1;
1448 $newOffset[1] *= -1;
1449 $conf['offset'] = implode(',', $newOffset);
1450 $conf['color'] = $conf['lowColor'];
1451 $this->makeShadow($im, $conf, $workArea, $txtConf);
1452 }
1453
1454 /**
1455 * Implements the "SHADOW" GIFBUILDER object / property for the TEXT object
1456 * The operation involves ImageMagick for combining.
1457 *
1458 * @param pointer $im GDlib image pointer
1459 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1460 * @param array $workArea The current working area coordinates.
1461 * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1462 * @retur void
1463 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeText(), makeEmboss()
1464 * @todo Define visibility
1465 */
1466 public function makeShadow(&$im, $conf, $workArea, $txtConf) {
1467 $workArea = $this->applyOffset($workArea, \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']));
1468 $blurRate = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(intval($conf['blur']), 0, 99);
1469 // No effects if ImageMagick ver. 5+
1470 if (!$blurRate || $this->NO_IM_EFFECTS) {
1471 $txtConf['fontColor'] = $conf['color'];
1472 $this->makeText($im, $txtConf, $workArea);
1473 } else {
1474 $w = imagesx($im);
1475 $h = imagesy($im);
1476 // Area around the blur used for cropping something
1477 $blurBorder = 3;
1478 $tmpStr = $this->randomName();
1479 $fileMenu = $tmpStr . '_menu.' . $this->gifExtension;
1480 $fileColor = $tmpStr . '_color.' . $this->gifExtension;
1481 $fileMask = $tmpStr . '_mask.' . $this->gifExtension;
1482 // BlurColor Image laves
1483 $blurColImg = imagecreatetruecolor($w, $h);
1484 $bcols = $this->convertColor($conf['color']);
1485 $Bcolor = ImageColorAllocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
1486 ImageFilledRectangle($blurColImg, 0, 0, $w, $h, $Bcolor);
1487 $this->ImageWrite($blurColImg, $fileColor);
1488 ImageDestroy($blurColImg);
1489 // The mask is made: BlurTextImage
1490 $blurTextImg = imagecreatetruecolor($w + $blurBorder * 2, $h + $blurBorder * 2);
1491 // Black background
1492 $Bcolor = ImageColorAllocate($blurTextImg, 0, 0, 0);
1493 ImageFilledRectangle($blurTextImg, 0, 0, $w + $blurBorder * 2, $h + $blurBorder * 2, $Bcolor);
1494 $txtConf['fontColor'] = 'white';
1495 $blurBordArr = array($blurBorder, $blurBorder);
1496 $this->makeText($blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
1497 // Dump to temporary file
1498 $this->ImageWrite($blurTextImg, $fileMask);
1499 // Destroy
1500 ImageDestroy($blurTextImg);
1501 $command = '';
1502 $command .= $this->maskNegate;
1503 if ($this->V5_EFFECTS) {
1504 $command .= $this->v5_blur($blurRate + 1);
1505 } else {
1506 // Blurring of the mask
1507 // How many blur-commands that is executed. Min = 1;
1508 $times = ceil($blurRate / 10);
1509 // Here I boost the blur-rate so that it is 100 already at 25. The rest is done by up to 99 iterations of the blur-command.
1510 $newBlurRate = $blurRate * 4;
1511 $newBlurRate = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($newBlurRate, 1, 99);
1512 // Building blur-command
1513 for ($a = 0; $a < $times; $a++) {
1514 $command .= ' -blur ' . $blurRate;
1515 }
1516 }
1517 $this->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
1518 // The mask is loaded again
1519 $blurTextImg_tmp = $this->imageCreateFromFile($fileMask);
1520 // If nothing went wrong we continue with the blurred mask
1521 if ($blurTextImg_tmp) {
1522 // Cropping the border from the mask
1523 $blurTextImg = imagecreatetruecolor($w, $h);
1524 $this->imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, $w, $h, $w, $h);
1525 // Destroy the temporary mask
1526 ImageDestroy($blurTextImg_tmp);
1527 // Adjust the mask
1528 $intensity = 40;
1529 if ($conf['intensity']) {
1530 $intensity = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
1531 }
1532 $intensity = ceil(255 - $intensity / 100 * 255);
1533 $this->inputLevels($blurTextImg, 0, $intensity, $this->maskNegate);
1534 $opacity = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(intval($conf['opacity']), 0, 100);
1535 if ($opacity && $opacity < 100) {
1536 $high = ceil(255 * $opacity / 100);
1537 // Reducing levels as the opacity demands
1538 $this->outputLevels($blurTextImg, 0, $high, $this->maskNegate);
1539 }
1540 // Dump the mask again
1541 $this->ImageWrite($blurTextImg, $fileMask);
1542 // Destroy the mask
1543 ImageDestroy($blurTextImg);
1544 // The pictures are combined
1545 // The main pictures is saved temporarily
1546 $this->ImageWrite($im, $fileMenu);
1547 $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
1548 // The main image is loaded again...
1549 $backIm = $this->imageCreateFromFile($fileMenu);
1550 // ... and if nothing went wrong we load it onto the old one.
1551 if ($backIm) {
1552 if (!$this->saveAlphaLayer) {
1553 ImageColorTransparent($backIm, -1);
1554 }
1555 $im = $backIm;
1556 }
1557 }
1558 // Deleting temporary files;
1559 if (!$this->dontUnlinkTempFiles) {
1560 unlink($fileMenu);
1561 unlink($fileColor);
1562 unlink($fileMask);
1563 }
1564 }
1565 }
1566
1567 /****************************
1568 *
1569 * Other GIFBUILDER objects
1570 *
1571 ****************************/
1572 /**
1573 * Implements the "BOX" GIFBUILDER object
1574 *
1575 * @param pointer $im GDlib image pointer
1576 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1577 * @param array $workArea The current working area coordinates.
1578 * @return void
1579 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1580 * @todo Define visibility
1581 */
1582 public function makeBox(&$im, $conf, $workArea) {
1583 $cords = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1584 $conf['offset'] = $cords[0] . ',' . $cords[1];
1585 $cords = $this->objPosition($conf, $workArea, array($cords[2], $cords[3]));
1586 $cols = $this->convertColor($conf['color']);
1587 $opacity = 0;
1588 if (isset($conf['opacity'])) {
1589 // conversion:
1590 // PHP 0 = opaque, 127 = transparent
1591 // TYPO3 100 = opaque, 0 = transparent
1592 $opacity = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(intval($conf['opacity']), 1, 100, 1);
1593 $opacity = abs($opacity - 100);
1594 $opacity = round(127 * $opacity / 100);
1595 }
1596 $tmpColor = ImageColorAllocateAlpha($im, $cols[0], $cols[1], $cols[2], $opacity);
1597 imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1598 }
1599
1600 /**
1601 * Implements the "Ellipse" GIFBUILDER object
1602 * Example Typoscript:
1603 * file = GIFBUILDER
1604 * file {
1605 * XY = 200,200
1606 * format = jpg
1607 * quality = 100
1608 * 10 = ELLIPSE
1609 * 10.dimensions = 100,100,50,50
1610 * 10.color = red
1611 *
1612 * $workArea = X,Y
1613 * $conf['dimensions'] = offset x, offset y, width of ellipse, height of ellipse
1614 *
1615 * @param pointer $im GDlib image pointer
1616 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1617 * @param array $workArea The current working area coordinates.
1618 * @return void
1619 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1620 */
1621 public function makeEllipse(&$im, array $conf, array $workArea) {
1622 $ellipseConfiguration = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1623 // Ellipse offset inside workArea (x/y)
1624 $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1625 // @see objPosition
1626 $imageCoordinates = $this->objPosition($conf, $workArea, array($ellipseConfiguration[2], $ellipseConfiguration[3]));
1627 $color = $this->convertColor($conf['color']);
1628 $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1629 imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1630 }
1631
1632 /**
1633 * Implements the "EFFECT" GIFBUILDER object
1634 * The operation involves ImageMagick for applying effects
1635 *
1636 * @param pointer $im GDlib image pointer
1637 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1638 * @return void
1639 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), applyImageMagickToPHPGif()
1640 * @todo Define visibility
1641 */
1642 public function makeEffect(&$im, $conf) {
1643 $commands = $this->IMparams($conf['value']);
1644 if ($commands) {
1645 $this->applyImageMagickToPHPGif($im, $commands);
1646 }
1647 }
1648
1649 /**
1650 * Creating ImageMagick paramters from TypoScript property
1651 *
1652 * @param string $setup A string with effect keywords=value pairs separated by "|
1653 * @return string ImageMagick prepared parameters.
1654 * @access private
1655 * @see makeEffect()
1656 * @todo Define visibility
1657 */
1658 public function IMparams($setup) {
1659 if (!trim($setup)) {
1660 return '';
1661 }
1662 $effects = explode('|', $setup);
1663 $commands = '';
1664 foreach ($effects as $val) {
1665 $pairs = explode('=', $val, 2);
1666 $value = trim($pairs[1]);
1667 $effect = strtolower(trim($pairs[0]));
1668 switch ($effect) {
1669 case 'gamma':
1670 $commands .= ' -gamma ' . doubleval($value);
1671 break;
1672 case 'blur':
1673 if (!$this->NO_IM_EFFECTS) {
1674 if ($this->V5_EFFECTS) {
1675 $commands .= $this->v5_blur($value);
1676 } else {
1677 $commands .= ' -blur ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 1, 99);
1678 }
1679 }
1680 break;
1681 case 'sharpen':
1682 if (!$this->NO_IM_EFFECTS) {
1683 if ($this->V5_EFFECTS) {
1684 $commands .= $this->v5_sharpen($value);
1685 } else {
1686 $commands .= ' -sharpen ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 1, 99);
1687 }
1688 }
1689 break;
1690 case 'rotate':
1691 $commands .= ' -rotate ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 360);
1692 break;
1693 case 'solarize':
1694 $commands .= ' -solarize ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 99);
1695 break;
1696 case 'swirl':
1697 $commands .= ' -swirl ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 1000);
1698 break;
1699 case 'wave':
1700 $params = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $value);
1701 $commands .= ' -wave ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($params[1], 0, 99);
1702 break;
1703 case 'charcoal':
1704 $commands .= ' -charcoal ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 100);
1705 break;
1706 case 'gray':
1707 $commands .= ' -colorspace GRAY';
1708 break;
1709 case 'edge':
1710 $commands .= ' -edge ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 99);
1711 break;
1712 case 'emboss':
1713 $commands .= ' -emboss';
1714 break;
1715 case 'flip':
1716 $commands .= ' -flip';
1717 break;
1718 case 'flop':
1719 $commands .= ' -flop';
1720 break;
1721 case 'colors':
1722 $commands .= ' -colors ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 2, 255);
1723 break;
1724 case 'shear':
1725 $commands .= ' -shear ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, -90, 90);
1726 break;
1727 case 'invert':
1728 $commands .= ' -negate';
1729 break;
1730 }
1731 }
1732 return $commands;
1733 }
1734
1735 /**
1736 * Implements the "ADJUST" GIFBUILDER object
1737 *
1738 * @param pointer $im GDlib image pointer
1739 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1740 * @return void
1741 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), autoLevels(), outputLevels(), inputLevels()
1742 * @todo Define visibility
1743 */
1744 public function adjust(&$im, $conf) {
1745 $setup = $conf['value'];
1746 if (!trim($setup)) {
1747 return '';
1748 }
1749 $effects = explode('|', $setup);
1750 foreach ($effects as $val) {
1751 $pairs = explode('=', $val, 2);
1752 $value = trim($pairs[1]);
1753 $effect = strtolower(trim($pairs[0]));
1754 switch ($effect) {
1755 case 'inputlevels':
1756 // low,high
1757 $params = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $value);
1758 $this->inputLevels($im, $params[0], $params[1]);
1759 break;
1760 case 'outputlevels':
1761 $params = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $value);
1762 $this->outputLevels($im, $params[0], $params[1]);
1763 break;
1764 case 'autolevels':
1765 $this->autoLevels($im);
1766 break;
1767 }
1768 }
1769 }
1770
1771 /**
1772 * Implements the "CROP" GIFBUILDER object
1773 *
1774 * @param pointer $im GDlib image pointer
1775 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1776 * @return void
1777 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1778 * @todo Define visibility
1779 */
1780 public function crop(&$im, $conf) {
1781 // Clears workArea to total image
1782 $this->setWorkArea('');
1783 $cords = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1784 $conf['offset'] = $cords[0] . ',' . $cords[1];
1785 $cords = $this->objPosition($conf, $this->workArea, array($cords[2], $cords[3]));
1786 $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1787 $cols = $this->convertColor($conf['backColor'] ? $conf['backColor'] : $this->setup['backColor']);
1788 $Bcolor = ImageColorAllocate($newIm, $cols[0], $cols[1], $cols[2]);
1789 ImageFilledRectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1790 $newConf = array();
1791 $workArea = array(0, 0, $cords[2], $cords[3]);
1792 if ($cords[0] < 0) {
1793 $workArea[0] = abs($cords[0]);
1794 } else {
1795 $newConf['offset'] = -$cords[0];
1796 }
1797 if ($cords[1] < 0) {
1798 $workArea[1] = abs($cords[1]);
1799 } else {
1800 $newConf['offset'] .= ',' . -$cords[1];
1801 }
1802 $this->copyGifOntoGif($newIm, $im, $newConf, $workArea);
1803 $im = $newIm;
1804 $this->w = imagesx($im);
1805 $this->h = imagesy($im);
1806 // Clears workArea to total image
1807 $this->setWorkArea('');
1808 }
1809
1810 /**
1811 * Implements the "SCALE" GIFBUILDER object
1812 *
1813 * @param pointer $im GDlib image pointer
1814 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1815 * @return void
1816 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1817 * @todo Define visibility
1818 */
1819 public function scale(&$im, $conf) {
1820 if ($conf['width'] || $conf['height'] || $conf['params']) {
1821 $tmpStr = $this->randomName();
1822 $theFile = $tmpStr . '.' . $this->gifExtension;
1823 $this->ImageWrite($im, $theFile);
1824 $theNewFile = $this->imageMagickConvert($theFile, $this->gifExtension, $conf['width'], $conf['height'], $conf['params']);
1825 $tmpImg = $this->imageCreateFromFile($theNewFile[3]);
1826 if ($tmpImg) {
1827 ImageDestroy($im);
1828 $im = $tmpImg;
1829 $this->w = imagesx($im);
1830 $this->h = imagesy($im);
1831 // Clears workArea to total image
1832 $this->setWorkArea('');
1833 }
1834 if (!$this->dontUnlinkTempFiles) {
1835 unlink($theFile);
1836 if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1837 unlink($theNewFile[3]);
1838 }
1839 }
1840 }
1841 }
1842
1843 /**
1844 * Implements the "WORKAREA" GIFBUILDER object when setting it
1845 * Setting internal working area boundaries (->workArea)
1846 *
1847 * @param string $workArea Working area dimensions, comma separated
1848 * @return void
1849 * @access private
1850 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1851 * @todo Define visibility
1852 */
1853 public function setWorkArea($workArea) {
1854 $this->workArea = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $workArea);
1855 $this->workArea = $this->applyOffset($this->workArea, $this->OFFSET);
1856 if (!$this->workArea[2]) {
1857 $this->workArea[2] = $this->w;
1858 }
1859 if (!$this->workArea[3]) {
1860 $this->workArea[3] = $this->h;
1861 }
1862 }
1863
1864 /*************************
1865 *
1866 * Adjustment functions
1867 *
1868 ************************/
1869 /**
1870 * Apply auto-levels to input image pointer
1871 *
1872 * @param integer $im GDlib Image Pointer
1873 * @return void
1874 * @todo Define visibility
1875 */
1876 public function autolevels(&$im) {
1877 $totalCols = ImageColorsTotal($im);
1878 $min = 255;
1879 $max = 0;
1880 for ($c = 0; $c < $totalCols; $c++) {
1881 $cols = ImageColorsForIndex($im, $c);
1882 $grayArr[] = round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
1883 }
1884 $min = min($grayArr);
1885 $max = max($grayArr);
1886 $delta = $max - $min;
1887 if ($delta) {
1888 for ($c = 0; $c < $totalCols; $c++) {
1889 $cols = ImageColorsForIndex($im, $c);
1890 $cols['red'] = floor(($cols['red'] - $min) / $delta * 255);
1891 $cols['green'] = floor(($cols['green'] - $min) / $delta * 255);
1892 $cols['blue'] = floor(($cols['blue'] - $min) / $delta * 255);
1893 ImageColorSet($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1894 }
1895 }
1896 }
1897
1898 /**
1899 * Apply output levels to input image pointer (decreasing contrast)
1900 *
1901 * @param integer $im GDlib Image Pointer
1902 * @param integer $low The "low" value (close to 0)
1903 * @param integer $high The "high" value (close to 255)
1904 * @param boolean $swap If swap, then low and high are swapped. (Useful for negated masks...)
1905 * @return void
1906 * @todo Define visibility
1907 */
1908 public function outputLevels(&$im, $low, $high, $swap = '') {
1909 if ($low < $high) {
1910 $low = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($low, 0, 255);
1911 $high = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($high, 0, 255);
1912 if ($swap) {
1913 $temp = $low;
1914 $low = 255 - $high;
1915 $high = 255 - $temp;
1916 }
1917 $delta = $high - $low;
1918 $totalCols = ImageColorsTotal($im);
1919 for ($c = 0; $c < $totalCols; $c++) {
1920 $cols = ImageColorsForIndex($im, $c);
1921 $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
1922 $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
1923 $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
1924 ImageColorSet($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1925 }
1926 }
1927 }
1928
1929 /**
1930 * Apply input levels to input image pointer (increasing contrast)
1931 *
1932 * @param integer $im GDlib Image Pointer
1933 * @param integer $low The "low" value (close to 0)
1934 * @param integer $high The "high" value (close to 255)
1935 * @param boolean $swap If swap, then low and high are swapped. (Useful for negated masks...)
1936 * @return void
1937 * @todo Define visibility
1938 */
1939 public function inputLevels(&$im, $low, $high, $swap = '') {
1940 if ($low < $high) {
1941 $low = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($low, 0, 255);
1942 $high = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($high, 0, 255);
1943 if ($swap) {
1944 $temp = $low;
1945 $low = 255 - $high;
1946 $high = 255 - $temp;
1947 }
1948 $delta = $high - $low;
1949 $totalCols = ImageColorsTotal($im);
1950 for ($c = 0; $c < $totalCols; $c++) {
1951 $cols = ImageColorsForIndex($im, $c);
1952 $cols['red'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(($cols['red'] - $low) / $delta * 255, 0, 255);
1953 $cols['green'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(($cols['green'] - $low) / $delta * 255, 0, 255);
1954 $cols['blue'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(($cols['blue'] - $low) / $delta * 255, 0, 255);
1955 ImageColorSet($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1956 }
1957 }
1958 }
1959
1960 /**
1961 * Reduce colors in image using IM and create a palette based image if possible (<=256 colors)
1962 *
1963 * @param string $file Image file to reduce
1964 * @param integer $cols Number of colors to reduce the image to.
1965 * @return string Reduced file
1966 * @todo Define visibility
1967 */
1968 public function IMreduceColors($file, $cols) {
1969 $fI = \TYPO3\CMS\Core\Utility\GeneralUtility::split_fileref($file);
1970 $ext = strtolower($fI['fileext']);
1971 $result = $this->randomName() . '.' . $ext;
1972 if (($reduce = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($cols, 0, $ext == 'gif' ? 256 : $this->truecolorColors, 0)) > 0) {
1973 $params = ' -colors ' . $reduce;
1974 if ($reduce <= 256) {
1975 $params .= ' -type Palette';
1976 }
1977 if ($ext == 'png' && $reduce <= 256) {
1978 $prefix = 'png8:';
1979 }
1980 $this->imageMagickExec($file, $prefix . $result, $params);
1981 if ($result) {
1982 return $result;
1983 }
1984 }
1985 return '';
1986 }
1987
1988 /*********************************
1989 *
1990 * GIFBUILDER Helper functions
1991 *
1992 *********************************/
1993 /**
1994 * Checks if the $fontFile is already at an absolute path and if not, prepends the correct path.
1995 * Use PATH_site unless we are in the backend.
1996 * Call it by \TYPO3\CMS\Core\Imaging\GraphicalFunctions::prependAbsolutePath()
1997 *
1998 * @param string $fontFile The font file
1999 * @return string The font file with absolute path.
2000 * @todo Define visibility
2001 */
2002 public function prependAbsolutePath($fontFile) {
2003 $absPath = defined('PATH_typo3') ? dirname(PATH_thisScript) . '/' : PATH_site;
2004 $fontFile = \TYPO3\CMS\Core\Utility\GeneralUtility::isAbsPath($fontFile) ? $fontFile : \TYPO3\CMS\Core\Utility\GeneralUtility::resolveBackPath($absPath . $fontFile);
2005 return $fontFile;
2006 }
2007
2008 /**
2009 * Returns the IM command for sharpening with ImageMagick 5 (when $this->V5_EFFECTS is set).
2010 * Uses $this->im5fx_sharpenSteps for translation of the factor to an actual command.
2011 *
2012 * @param integer $factor The sharpening factor, 0-100 (effectively in 10 steps)
2013 * @return string The sharpening command, eg. " -sharpen 3x4
2014 * @see makeText(), IMparams(), v5_blur()
2015 * @todo Define visibility
2016 */
2017 public function v5_sharpen($factor) {
2018 $factor = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
2019 $sharpenArr = explode(',', ',' . $this->im5fx_sharpenSteps);
2020 $sharpenF = trim($sharpenArr[$factor]);
2021 if ($sharpenF) {
2022 $cmd = ' -sharpen ' . $sharpenF;
2023 return $cmd;
2024 }
2025 }
2026
2027 /**
2028 * Returns the IM command for blurring with ImageMagick 5 (when $this->V5_EFFECTS is set).
2029 * Uses $this->im5fx_blurSteps for translation of the factor to an actual command.
2030 *
2031 * @param integer $factor The blurring factor, 0-100 (effectively in 10 steps)
2032 * @return string The blurring command, eg. " -blur 3x4
2033 * @see makeText(), IMparams(), v5_sharpen()
2034 * @todo Define visibility
2035 */
2036 public function v5_blur($factor) {
2037 $factor = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
2038 $blurArr = explode(',', ',' . $this->im5fx_blurSteps);
2039 $blurF = trim($blurArr[$factor]);
2040 if ($blurF) {
2041 $cmd = ' -blur ' . $blurF;
2042 return $cmd;
2043 }
2044 }
2045
2046 /**
2047 * Returns a random filename prefixed with "temp_" and then 32 char md5 hash (without extension) from $this->tempPath.
2048 * Used by functions in this class to create truely temporary files for the on-the-fly processing. These files will most likely be deleted right away.
2049 *
2050 * @return string
2051 * @todo Define visibility
2052 */
2053 public function randomName() {
2054 $this->createTempSubDir('temp/');
2055 return $this->tempPath . 'temp/' . md5(uniqid(''));
2056 }
2057
2058 /**
2059 * Applies offset value to coordinated in $cords.
2060 * Basically the value of key 0/1 of $OFFSET is added to keys 0/1 of $cords
2061 *
2062 * @param array $cords Integer coordinates in key 0/1
2063 * @param array $OFFSET Offset values in key 0/1
2064 * @return array Modified $cords array
2065 * @todo Define visibility
2066 */
2067 public function applyOffset($cords, $OFFSET) {
2068 $cords[0] = intval($cords[0]) + intval($OFFSET[0]);
2069 $cords[1] = intval($cords[1]) + intval($OFFSET[1]);
2070 return $cords;
2071 }
2072
2073 /**
2074 * Converts a "HTML-color" TypoScript datatype to RGB-values.
2075 * Default is 0,0,0
2076 *
2077 * @param string $string "HTML-color" data type string, eg. 'red', '#ffeedd' or '255,0,255'. You can also add a modifying operator afterwards. There are two options: "255,0,255 : 20" - will add 20 to values, result is "255,20,255". Or "255,0,255 : *1.23" which will multiply all RGB values with 1.23
2078 * @return array RGB values in key 0/1/2 of the array
2079 * @todo Define visibility
2080 */
2081 public function convertColor($string) {
2082 $col = array();
2083 $cParts = explode(':', $string, 2);
2084 // Finding the RGB definitions of the color:
2085 $string = $cParts[0];
2086 if (strstr($string, '#')) {
2087 $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string);
2088 $col[] = HexDec(substr($string, 0, 2));
2089 $col[] = HexDec(substr($string, 2, 2));
2090 $col[] = HexDec(substr($string, 4, 2));
2091 } elseif (strstr($string, ',')) {
2092 $string = preg_replace('/[^,0-9]*/', '', $string);
2093 $strArr = explode(',', $string);
2094 $col[] = intval($strArr[0]);
2095 $col[] = intval($strArr[1]);
2096 $col[] = intval($strArr[2]);
2097 } else {
2098 $string = strtolower(trim($string));
2099 if ($this->colMap[$string]) {
2100 $col = $this->colMap[$string];
2101 } else {
2102 $col = array(0, 0, 0);
2103 }
2104 }
2105 // ... and possibly recalculating the value
2106 if (trim($cParts[1])) {
2107 $cParts[1] = trim($cParts[1]);
2108 if (substr($cParts[1], 0, 1) == '*') {
2109 $val = doubleval(substr($cParts[1], 1));
2110 $col[0] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[0] * $val, 0, 255);
2111 $col[1] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[1] * $val, 0, 255);
2112 $col[2] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[2] * $val, 0, 255);
2113 } else {
2114 $val = intval($cParts[1]);
2115 $col[0] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[0] + $val, 0, 255);
2116 $col[1] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[1] + $val, 0, 255);
2117 $col[2] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[2] + $val, 0, 255);
2118 }
2119 }
2120 return $col;
2121 }
2122
2123 /**
2124 * Recode string
2125 * Used with text strings for fonts when languages has other character sets.
2126 *
2127 * @param string The text to recode
2128 * @return string The recoded string. Should be UTF-8 output. MAY contain entities (eg. &#123; or &#quot; which should render as real chars).
2129 * @todo Define visibility
2130 */
2131 public function recodeString($string) {
2132 // Recode string to UTF-8 from $this->nativeCharset:
2133 if ($this->nativeCharset && $this->nativeCharset != 'utf-8') {
2134 // Convert to UTF-8
2135 $string = $this->csConvObj->utf8_encode($string, $this->nativeCharset);
2136 }
2137 return $string;
2138 }
2139
2140 /**
2141 * Split a string into an array of individual characters
2142 * The function will look at $this->nativeCharset and if that is set, the input string is expected to be UTF-8 encoded, possibly with entities in it. Otherwise the string is supposed to be a single-byte charset which is just splitted by a for-loop.
2143 *
2144 * @param string $theText The text string to split
2145 * @param boolean $returnUnicodeNumber Return Unicode numbers instead of chars.
2146 * @return array Numerical array with a char as each value.
2147 * @todo Define visibility
2148 */
2149 public function singleChars($theText, $returnUnicodeNumber = FALSE) {
2150 if ($this->nativeCharset) {
2151 // Get an array of separated UTF-8 chars
2152 return $this->csConvObj->utf8_to_numberarray($theText, 1, $returnUnicodeNumber ? 0 : 1);
2153 } else {
2154 $output = array();
2155 $c = strlen($theText);
2156 for ($a = 0; $a < $c; $a++) {
2157 $output[] = substr($theText, $a, 1);
2158 }
2159 return $output;
2160 }
2161 }
2162
2163 /**
2164 * Create an array with object position/boundaries based on input TypoScript configuration (such as the "align" property is used), the work area definition and $BB array
2165 *
2166 * @param array $conf TypoScript configuration for a GIFBUILDER object
2167 * @param array makeBox Workarea definition
2168 * @param array $BB BB (Bounding box) array. Not just used for TEXT objects but also for others
2169 * @return array [0]=x, [1]=y, [2]=w, [3]=h
2170 * @access private
2171 * @see copyGifOntoGif(), makeBox(), crop()
2172 * @todo Define visibility
2173 */
2174 public function objPosition($conf, $workArea, $BB) {
2175 // offset, align, valign, workarea
2176 $result = array();
2177 $result[2] = $BB[0];
2178 $result[3] = $BB[1];
2179 $w = $workArea[2];
2180 $h = $workArea[3];
2181 $align = explode(',', $conf['align']);
2182 $align[0] = strtolower(substr(trim($align[0]), 0, 1));
2183 $align[1] = strtolower(substr(trim($align[1]), 0, 1));
2184 switch ($align[0]) {
2185 case 'r':
2186 $result[0] = $w - $result[2];
2187 break;
2188 case 'c':
2189 $result[0] = round(($w - $result[2]) / 2);
2190 break;
2191 default:
2192 $result[0] = 0;
2193 }
2194 switch ($align[1]) {
2195 case 'b':
2196 // y pos
2197 $result[1] = $h - $result[3];
2198 break;
2199 case 'c':
2200 $result[1] = round(($h - $result[3]) / 2);
2201 break;
2202 default:
2203 $result[1] = 0;
2204 }
2205 $result = $this->applyOffset($result, \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']));
2206 $result = $this->applyOffset($result, $workArea);
2207 return $result;
2208 }
2209
2210 /***********************************
2211 *
2212 * Scaling, Dimensions of images
2213 *
2214 ***********************************/
2215 /**
2216 * Converts $imagefile to another file in temp-dir of type $newExt (extension).
2217 *
2218 * @param string $imagefile The image filepath
2219 * @param string $newExt New extension, eg. "gif", "png", "jpg", "tif". If $newExt is NOT set, the new imagefile will be of the original format. If newExt = 'WEB' then one of the web-formats is applied.
2220 * @param string $w Width. $w / $h is optional. If only one is given the image is scaled proportionally. If an 'm' exists in the $w or $h and if both are present the $w and $h is regarded as the Maximum w/h and the proportions will be kept
2221 * @param string $h Height. See $w
2222 * @param string $params Additional ImageMagick parameters.
2223 * @param string $frame Refers to which frame-number to select in the image. '' or 0 will select the first frame, 1 will select the next and so on...
2224 * @param array $options An array with options passed to getImageScale (see this function).
2225 * @param boolean $mustCreate If set, then another image than the input imagefile MUST be returned. Otherwise you can risk that the input image is good enough regarding messures etc and is of course not rendered to a new, temporary file in typo3temp/. But this option will force it to.
2226 * @return array [0]/[1] is w/h, [2] is file extension and [3] is the filename.
2227 * @see getImageScale(), typo3/show_item.php, fileList_ext::renderImage(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource(), SC_tslib_showpic::show(), maskImageOntoImage(), copyImageOntoImage(), scale()
2228 * @todo Define visibility
2229 */
2230 public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $params = '', $frame = '', $options = array(), $mustCreate = FALSE) {
2231 if ($this->NO_IMAGE_MAGICK) {
2232 // Returning file info right away
2233 return $this->getImageDimensions($imagefile);
2234 }
2235 if ($info = $this->getImageDimensions($imagefile)) {
2236 $newExt = strtolower(trim($newExt));
2237 // If no extension is given the original extension is used
2238 if (!$newExt) {
2239 $newExt = $info[2];
2240 }
2241 if ($newExt == 'web') {
2242 if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->webImageExt, $info[2])) {
2243 $newExt = $info[2];
2244 } else {
2245 $newExt = $this->gif_or_jpg($info[2], $info[0], $info[1]);
2246 if (!$params) {
2247 $params = $this->cmds[$newExt];
2248 }
2249 }
2250 }
2251 if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->imageFileExt, $newExt)) {
2252 if (strstr($w . $h, 'm')) {
2253 $max = 1;
2254 } else {
2255 $max = 0;
2256 }
2257 $data = $this->getImageScale($info, $w, $h, $options);
2258 $w = $data['origW'];
2259 $h = $data['origH'];
2260 // If no conversion should be performed
2261 // this flag is TRUE if the width / height does NOT dictate
2262 // the image to be scaled!! (that is if no width / height is
2263 // given or if the destination w/h matches the original image
2264 // dimensions or if the option to not scale the image is set)
2265 $noScale = !$w && !$h || $data[0] == $info[0] && $data[1] == $info[1] || $options['noScale'];
2266 if ($noScale && !$data['crs'] && !$params && !$frame && $newExt == $info[2] && !$mustCreate) {
2267 // Set the new width and height before returning,
2268 // if the noScale option is set
2269 if (!empty($options['noScale'])) {
2270 $info[0] = $data[0];
2271 $info[1] = $data[1];
2272 }
2273 $info[3] = $imagefile;
2274 return $info;
2275 }
2276 $info[0] = $data[0];
2277 $info[1] = $data[1];
2278 $frame = $this->noFramePrepended ? '' : intval($frame);
2279 if (!$params) {
2280 $params = $this->cmds[$newExt];
2281 }
2282 // Cropscaling:
2283 if ($data['crs']) {
2284 if (!$data['origW']) {
2285 $data['origW'] = $data[0];
2286 }
2287 if (!$data['origH']) {
2288 $data['origH'] = $data[1];
2289 }
2290 $offsetX = intval(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
2291 $offsetY = intval(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
2292 $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . ' ';
2293 }
2294 $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
2295 $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
2296 if ($this->alternativeOutputKey) {
2297 $theOutputName = \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($command . $cropscale . basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
2298 } else {
2299 $theOutputName = \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($command . $cropscale . $imagefile . filemtime($imagefile) . '[' . $frame . ']');
2300 }
2301 if ($this->imageMagickConvert_forceFileNameBody) {
2302 $theOutputName = $this->imageMagickConvert_forceFileNameBody;
2303 $this->imageMagickConvert_forceFileNameBody = '';
2304 }
2305 // Making the temporary filename:
2306 $this->createTempSubDir('pics/');
2307 $output = $this->absPrefix . $this->tempPath . 'pics/' . $this->filenamePrefix . $theOutputName . '.' . $newExt;
2308 // Register temporary filename:
2309 $GLOBALS['TEMP_IMAGES_ON_PAGE'][] = $output;
2310 if ($this->dontCheckForExistingTempFile || !$this->file_exists_typo3temp_file($output, $imagefile)) {
2311 $this->imageMagickExec($imagefile, $output, $command, $frame);
2312 }
2313 if (file_exists($output)) {
2314 $info[3] = $output;
2315 $info[2] = $newExt;
2316 // params could realisticly change some imagedata!
2317 if ($params) {
2318 $info = $this->getImageDimensions($info[3]);
2319 }
2320 if ($info[2] == $this->gifExtension && !$this->dontCompress) {
2321 // Compress with IM (lzw) or GD (rle) (Workaround for the absence of lzw-compression in GD)
2322 \TYPO3\CMS\Core\Utility\GeneralUtility::gif_compress($info[3], '');
2323 }
2324 return $info;
2325 }
2326 }
2327 }
2328 }
2329
2330 /**
2331 * Gets the input image dimensions.
2332 *
2333 * @param string $imageFile The image filepath
2334 * @return array Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2335 * @see imageMagickConvert(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
2336 * @todo Define visibility
2337 */
2338 public function getImageDimensions($imageFile) {
2339 preg_match('/([^\\.]*)$/', $imageFile, $reg);
2340 if (file_exists($imageFile) && \TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->imageFileExt, strtolower($reg[0]))) {
2341 if ($returnArr = $this->getCachedImageDimensions($imageFile)) {
2342 return $returnArr;
2343 } else {
2344 if ($temp = @getImageSize($imageFile)) {
2345 $returnArr = array($temp[0], $temp[1], strtolower($reg[0]), $imageFile);
2346 } else {
2347 $returnArr = $this->imageMagickIdentify($imageFile);
2348 }
2349 if ($returnArr) {
2350 $this->cacheImageDimensions($returnArr);
2351 return $returnArr;
2352 }
2353 }
2354 }
2355 return FALSE;
2356 }
2357
2358 /**
2359 * Cache the result of the getImageDimensions function into the database. Does not check if the
2360 * file exists!
2361 *
2362 * @param array $identifyResult Result of the getImageDimensions function
2363 * @return boolean TRUE if operation was successful
2364 * @todo Define visibility
2365 */
2366 public function cacheImageDimensions($identifyResult) {
2367 // Create md5 hash of filemtime and filesize
2368 $md5Hash = md5(filemtime($identifyResult[3]) . filesize($identifyResult[3]));
2369 $result = FALSE;
2370 if ($md5Hash) {
2371 $fieldArray = array(
2372 'md5hash' => $md5Hash,
2373 'md5filename' => md5($identifyResult[3]),
2374 'tstamp' => $GLOBALS['EXEC_TIME'],
2375 'filename' => $identifyResult[3],
2376 'imagewidth' => $identifyResult[0],
2377 'imageheight' => $identifyResult[1]
2378 );
2379 $GLOBALS['TYPO3_DB']->exec_INSERTquery('cache_imagesizes', $fieldArray);
2380 if (!($err = $GLOBALS['TYPO3_DB']->sql_error())) {
2381 $result = TRUE;
2382 }
2383 }
2384 return $result;
2385 }
2386
2387 /**
2388 * Fetch the cached imageDimensions from the MySQL database. Does not check if the image file exists!
2389 *
2390 * @param string $imageFile The image filepath
2391 * @return array Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2392 * @todo Define visibility
2393 */
2394 public function getCachedImageDimensions($imageFile) {
2395 // Create md5 hash of filemtime and filesize
2396 $md5Hash = md5(filemtime($imageFile) . filesize($imageFile));
2397 $cachedImageDimensions = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('md5hash, md5filename, imagewidth, imageheight', 'cache_imagesizes', 'md5filename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr(md5($imageFile), 'cache_imagesizes'));
2398 $result = FALSE;
2399 if (is_array($cachedImageDimensions)) {
2400 if ($cachedImageDimensions['md5hash'] != $md5Hash) {
2401 // File has changed, delete the row
2402 $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_imagesizes', 'md5filename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($cachedImageDimensions['md5filename'], 'cache_imagesizes'));
2403 } else {
2404 preg_match('/([^\\.]*)$/', $imageFile, $imageExtension);
2405 $result = array(
2406 (int) $cachedImageDimensions['imagewidth'],
2407 (int) $cachedImageDimensions['imageheight'],
2408 strtolower($imageExtension[0]),
2409 $imageFile
2410 );
2411 }
2412 }
2413 return $result;
2414 }
2415
2416 /**
2417 * Get numbers for scaling the image based on input
2418 *
2419 * @param array $info Current image information: Width, Height etc.
2420 * @param integer $w "required" width
2421 * @param integer $h "required" height
2422 * @param array $options Options: Keys are like "maxW", "maxH", "minW", "minH
2423 * @return array
2424 * @access private
2425 * @see imageMagickConvert()
2426 * @todo Define visibility
2427 */
2428 public function getImageScale($info, $w, $h, $options) {
2429 if (strstr($w . $h, 'm')) {
2430 $max = 1;
2431 } else {
2432 $max = 0;
2433 }
2434 if (strstr($w . $h, 'c')) {
2435 $out['cropH'] = intval(substr(strstr($w, 'c'), 1));
2436 $out['cropV'] = intval(substr(strstr($h, 'c'), 1));
2437 $crs = TRUE;
2438 } else {
2439 $crs = FALSE;
2440 }
2441 $out['crs'] = $crs;
2442 $w = intval($w);
2443 $h = intval($h);
2444 // If there are max-values...
2445 if (!empty($options['maxW'])) {
2446 // If width is given...
2447 if ($w) {
2448 if ($w > $options['maxW']) {
2449 $w = $options['maxW'];
2450 // Height should follow
2451 $max = 1;
2452 }
2453 } else {
2454 if ($info[0] > $options['maxW']) {
2455 $w = $options['maxW'];
2456 // Height should follow
2457 $max = 1;
2458 }
2459 }
2460 }
2461 if (!empty($options['maxH'])) {
2462 // If height is given...
2463 if ($h) {
2464 if ($h > $options['maxH']) {
2465 $h = $options['maxH'];
2466 // Height should follow
2467 $max = 1;
2468 }
2469 } else {
2470 // Changed [0] to [1] 290801
2471 if ($info[1] > $options['maxH']) {
2472 $h = $options['maxH'];
2473 // Height should follow
2474 $max = 1;
2475 }
2476 }
2477 }
2478 $out['origW'] = $w;
2479 $out['origH'] = $h;
2480 $out['max'] = $max;
2481 if (!$this->mayScaleUp) {
2482 if ($w > $info[0]) {
2483 $w = $info[0];
2484 }
2485 if ($h > $info[1]) {
2486 $h = $info[1];
2487 }
2488 }
2489 // If scaling should be performed
2490 if ($w || $h) {
2491 if ($w && !$h) {
2492 $info[1] = ceil($info[1] * ($w / $info[0]));
2493 $info[0] = $w;
2494 }
2495 if (!$w && $h) {
2496 $info[0] = ceil($info[0] * ($h / $info[1]));
2497 $info[1] = $h;
2498 }
2499 if ($w && $h) {
2500 if ($max) {
2501 $ratio = $info[0] / $info[1];
2502 if ($h * $ratio > $w) {
2503 $h = round($w / $ratio);
2504 } else {
2505 $w = round($h * $ratio);
2506 }
2507 }
2508 if ($crs) {
2509 $ratio = $info[0] / $info[1];
2510 if ($h * $ratio < $w) {
2511 $h = round($w / $ratio);
2512 } else {
2513 $w = round($h * $ratio);
2514 }
2515 }
2516 $info[0] = $w;
2517 $info[1] = $h;
2518 }
2519 }
2520 $out[0] = $info[0];
2521 $out[1] = $info[1];
2522 // Set minimum-measures!
2523 if (isset($options['minW']) && $out[0] < $options['minW']) {
2524 if (($max || $crs) && $out[0]) {
2525 $out[1] = round($out[1] * $options['minW'] / $out[0]);
2526 }
2527 $out[0] = $options['minW'];
2528 }
2529 if (isset($options['minH']) && $out[1] < $options['minH']) {
2530 if (($max || $crs) && $out[1]) {
2531 $out[0] = round($out[0] * $options['minH'] / $out[1]);
2532 }
2533 $out[1] = $options['minH'];
2534 }
2535 return $out;
2536 }
2537
2538 /**
2539 * Used to check if a certain process of scaling an image is already being carried out (can be logged in the SQL database)
2540 *
2541 * @param string $output Output imagefile
2542 * @param string $orig Original basis file
2543 * @return boolean Returns TRUE if the file is already being made; thus "TRUE" means "Don't render the image again
2544 * @access private
2545 * @todo Define visibility
2546 */
2547 public function file_exists_typo3temp_file($output, $orig = '') {
2548 if ($this->enable_typo3temp_db_tracking) {
2549 // If file exists, then we return immediately
2550 if (file_exists($output)) {
2551 return 1;
2552 } else {
2553 // If not, we look up in the cache_typo3temp_log table to see if there is a image being rendered right now.
2554 $md5Hash = md5($output);
2555 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('md5hash', 'cache_typo3temp_log', 'md5hash=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($md5Hash, 'cache_typo3temp_log') . ' AND tstamp>' . ($GLOBALS['EXEC_TIME'] - 30));
2556 // If there was a record, the image is being generated by another process (we assume)
2557 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
2558 $GLOBALS['TYPO3_DB']->sql_free_result($res);
2559 if (is_object($GLOBALS['TSFE'])) {
2560 $GLOBALS['TSFE']->set_no_cache('Another process renders this file now: ' . $output);
2561 }
2562 // ...so we set no_cache, because we dont want this page (which will NOT display an image...!) to be cached! (Only a page with the correct image on...)
2563 if (is_object($GLOBALS['TT'])) {
2564 $GLOBALS['TT']->setTSlogMessage('typo3temp_log: Assume this file is being rendered now: ' . $output);
2565 }
2566 // Return 'success - 2'
2567 return 2;
2568 } else {
2569 // If the current time is more than 30 seconds since this record was written, we clear the record, write a new and render the image.
2570 $insertFields = array(
2571 'md5hash' => $md5Hash,
2572 'tstamp' => $GLOBALS['EXEC_TIME'],
2573 'filename' => $output,
2574 'orig_filename' => $orig
2575 );
2576 $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_typo3temp_log', 'md5hash=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($md5Hash, 'cache_typo3temp_log'));
2577 $GLOBALS['TYPO3_DB']->exec_INSERTquery('cache_typo3temp_log', $insertFields);
2578 if (is_object($GLOBALS['TT'])) {
2579 $GLOBALS['TT']->setTSlogMessage('typo3temp_log: The row did not exist, so a new is written and file is being processed: ' . $output);
2580 }
2581 return 0;
2582 }
2583 }
2584 } else {
2585 return file_exists($output);
2586 }
2587 }
2588
2589 /***********************************
2590 *
2591 * ImageMagick API functions
2592 *
2593 ***********************************/
2594 /**
2595 * Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2596 * Using ImageMagick
2597 *
2598 * @param string $imagefile The relative (to PATH_site) image filepath
2599 * @return array
2600 * @todo Define visibility
2601 */
2602 public function imageMagickIdentify($imagefile) {
2603 if (!$this->NO_IMAGE_MAGICK) {
2604 $frame = $this->noFramePrepended ? '' : '[0]';
2605 $cmd = \TYPO3\CMS\Core\Utility\GeneralUtility::imageMagickCommand('identify', $this->wrapFileName($imagefile) . $frame);
2606 $returnVal = array();
2607 \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd, $returnVal);
2608 $splitstring = $returnVal[0];
2609 $this->IM_commands[] = array('identify', $cmd, $returnVal[0]);
2610 if ($splitstring) {
2611 preg_match('/([^\\.]*)$/', $imagefile, $reg);
2612 $splitinfo = explode(' ', $splitstring);
2613 foreach ($splitinfo as $key => $val) {
2614 $temp = '';
2615 if ($val) {
2616 $temp = explode('x', $val);
2617 }
2618 if (intval($temp[0]) && intval($temp[1])) {
2619 $dim = $temp;
2620 break;
2621 }
2622 }
2623 if ($dim[0] && $dim[1]) {
2624 return array($dim[0], $dim[1], strtolower($reg[0]), $imagefile);
2625 }
2626 }
2627 }
2628 }
2629
2630 /**
2631 * Executes a ImageMagick "convert" on two filenames, $input and $output using $params before them.
2632 * Can be used for many things, mostly scaling and effects.
2633 *
2634 * @param string $input The relative (to PATH_site) image filepath, input file (read from)
2635 * @param string $output The relative (to PATH_site) image filepath, output filename (written to)
2636 * @param string $params ImageMagick parameters
2637 * @param integer $frame Optional, refers to which frame-number to select in the image. '' or 0
2638 * @return string The result of a call to PHP function "exec()
2639 * @todo Define visibility
2640 */
2641 public function imageMagickExec($input, $output, $params, $frame = 0) {
2642 if (!$this->NO_IMAGE_MAGICK) {
2643 // Unless noFramePrepended is set in the Install Tool, a frame number is added to
2644 // select a specific page of the image (by default this will be the first page)
2645 if (!$this->noFramePrepended) {
2646 $frame = '[' . intval($frame) . ']';
2647 } else {
2648 $frame = '';
2649 }
2650 $cmd = \TYPO3\CMS\Core\Utility\GeneralUtility::imageMagickCommand('convert', $params . ' ' . $this->wrapFileName($input) . $frame . ' ' . $this->wrapFileName($output));
2651 $this->IM_commands[] = array($output, $cmd);
2652 $ret = \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd);
2653 // Change the permissions of the file
2654 \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($output);
2655 return $ret;
2656 }
2657 }
2658
2659 /**
2660 * Executes a ImageMagick "combine" (or composite in newer times) on four filenames - $input, $overlay and $mask as input files and $output as the output filename (written to)
2661 * Can be used for many things, mostly scaling and effects.
2662 *
2663 * @param string $input The relative (to PATH_site) image filepath, bottom file
2664 * @param string $overlay The relative (to PATH_site) image filepath, overlay file (top)
2665 * @param string $mask The relative (to PATH_site) image filepath, the mask file (grayscale)
2666 * @param string $output The relative (to PATH_site) image filepath, output filename (written to)
2667 * @param boolean $handleNegation
2668 * @return void
2669 * @todo Define visibility
2670 */
2671 public function combineExec($input, $overlay, $mask, $output, $handleNegation = FALSE) {
2672 if (!$this->NO_IMAGE_MAGICK) {
2673 $params = '-colorspace GRAY +matte';
2674 if ($handleNegation) {
2675 if ($this->maskNegate) {
2676 $params .= ' ' . $this->maskNegate;
2677 }
2678 }
2679 $theMask = $this->randomName() . '.' . $this->gifExtension;
2680 $this->imageMagickExec($mask, $theMask, $params);
2681 $cmd = \TYPO3\CMS\Core\Utility\GeneralUtility::imageMagickCommand('combine', '-compose over +matte ' . $this->wrapFileName($input) . ' ' . $this->wrapFileName($overlay) . ' ' . $this->wrapFileName($theMask) . ' ' . $this->wrapFileName($output));
2682 // +matte = no alpha layer in output
2683 $this->IM_commands[] = array($output, $cmd);
2684 $ret = \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd);
2685 // Change the permissions of the file
2686 \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($output);
2687 if (is_file($theMask)) {