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