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