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