TYPO3 CMS  TYPO3_7-6
GraphicalFunctions.php
Go to the documentation of this file.
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 
20 
28 {
34  public $noFramePrepended = 0;
35 
41  public $gifExtension = 'gif';
42 
48  public $gdlibExtensions = '';
49 
55  public $png_truecolor = false;
56 
62  protected $colorspace = 'RGB';
63 
70  'CMY',
71  'CMYK',
72  'Gray',
73  'HCL',
74  'HSB',
75  'HSL',
76  'HWB',
77  'Lab',
78  'LCH',
79  'LMS',
80  'Log',
81  'Luv',
82  'OHTA',
83  'Rec601Luma',
84  'Rec601YCbCr',
85  'Rec709Luma',
86  'Rec709YCbCr',
87  'RGB',
88  'sRGB',
89  'Transparent',
90  'XYZ',
91  'YCbCr',
92  'YCC',
93  'YIQ',
94  'YCbCr',
95  'YUV'
96  ];
97 
103  public $truecolorColors = 16777215;
104 
111  public $imageFileExt = 'gif,jpg,jpeg,png,tif,bmp,tga,pcx,ai,pdf';
112 
118  public $webImageExt = 'gif,jpg,jpeg,png';
119 
123  public $NO_IM_EFFECTS = '';
124 
128  public $cmds = [
129  'jpg' => '',
130  'jpeg' => '',
131  'gif' => '',
132  'png' => '-colors 64'
133  ];
134 
138  public $NO_IMAGE_MAGICK = '';
139 
143  public $V5_EFFECTS = 0;
144 
148  public $mayScaleUp = 1;
149 
155  public $filenamePrefix = '';
156 
163 
170 
176  public $dontCompress = 0;
177 
184 
193 
199  public $IM_commands = [];
200 
204  public $workArea = [];
205 
211  protected $saveAlphaLayer = false;
212 
218  public $tempPath = 'typo3temp/';
219 
225  public $absPrefix = '';
226 
232  public $scalecmd = '-geometry';
233 
239  public $im5fx_blurSteps = '1x2,2x2,3x2,4x3,5x3,5x4,6x4,7x5,8x5,9x5';
240 
246  public $im5fx_sharpenSteps = '1x2,2x2,3x2,2x3,3x3,4x3,3x4,4x4,4x5,5x5';
247 
253  public $pixelLimitGif = 10000;
254 
260  public $colMap = [
261  'aqua' => [0, 255, 255],
262  'black' => [0, 0, 0],
263  'blue' => [0, 0, 255],
264  'fuchsia' => [255, 0, 255],
265  'gray' => [128, 128, 128],
266  'green' => [0, 128, 0],
267  'lime' => [0, 255, 0],
268  'maroon' => [128, 0, 0],
269  'navy' => [0, 0, 128],
270  'olive' => [128, 128, 0],
271  'purple' => [128, 0, 128],
272  'red' => [255, 0, 0],
273  'silver' => [192, 192, 192],
274  'teal' => [0, 128, 128],
275  'yellow' => [255, 255, 0],
276  'white' => [255, 255, 255]
277  ];
278 
284  public $csConvObj;
285 
291  public $nativeCharset = '';
292 
299  public function init()
300  {
301  $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
302  if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
303  $this->gdlibExtensions .= ',jpg,jpeg';
304  }
305  if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
306  $this->gdlibExtensions .= ',png';
307  }
308  if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
309  $this->gdlibExtensions .= ',gif';
310  }
311  if ($gfxConf['png_truecolor']) {
312  $this->png_truecolor = true;
313  }
314 
315  if ($gfxConf['colorspace'] && in_array($gfxConf['colorspace'], $this->allowedColorSpaceNames, true)) {
316  $this->colorspace = $gfxConf['colorspace'];
317  }
318 
319  if (!$gfxConf['im']) {
320  $this->NO_IMAGE_MAGICK = 1;
321  }
322  if (!$this->NO_IMAGE_MAGICK && (!$gfxConf['im_version_5'] || $gfxConf['im_version_5'] === 'im4' || $gfxConf['im_version_5'] === 'im5')) {
323  throw new \RuntimeException('Your TYPO3 installation is configured to use an old version of ImageMagick, which is not supported anymore. ' . 'Please upgrade to ImageMagick version 6 or GraphicksMagick and set $TYPO3_CONF_VARS[\'GFX\'][\'im_version_5\'] appropriately.', 1305059666);
324  }
325  // When GIFBUILDER gets used in truecolor mode
326  // No colors parameter if we generate truecolor images.
327  if ($this->png_truecolor) {
328  $this->cmds['png'] = '';
329  }
330  // Setting default JPG parameters:
331  $this->jpegQuality = MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, 75);
332  $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -sharpen 50 -quality ' . $this->jpegQuality);
333  if ($gfxConf['im_noFramePrepended']) {
334  $this->noFramePrepended = 1;
335  }
336  if ($gfxConf['gdlib_png']) {
337  $this->gifExtension = 'png';
338  }
339  $this->imageFileExt = $gfxConf['imagefile_ext'];
340 
341  // Boolean. This is necessary if using ImageMagick 5+.
342  // Effects in Imagemagick 5+ tends to render very slowly!!
343  // - therefore must be disabled in order not to perform sharpen, blurring and such.
344  $this->NO_IM_EFFECTS = 1;
345  $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . $this->jpegQuality);
346 
347  // ... but if 'im_v5effects' is set, enable effects
348  if ($gfxConf['im_v5effects']) {
349  $this->NO_IM_EFFECTS = 0;
350  $this->V5_EFFECTS = 1;
351  if ($gfxConf['im_v5effects'] > 0) {
352  $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . (int)$gfxConf['jpg_quality'] . $this->v5_sharpen(10));
353  }
354  }
355  // Secures that images are not scaled up.
356  if ($gfxConf['im_noScaleUp']) {
357  $this->mayScaleUp = 0;
358  }
359  if (TYPO3_MODE == 'FE') {
360  $this->csConvObj = $GLOBALS['TSFE']->csConvObj;
361  } elseif (is_object($GLOBALS['LANG'])) {
362  // BE assumed:
363  $this->csConvObj = $GLOBALS['LANG']->csConvObj;
364  } else {
365  // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
366  $this->csConvObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Charset\CharsetConverter::class);
367  }
368  $this->nativeCharset = 'utf-8';
369  }
370 
371  /*************************************************
372  *
373  * Layering images / "IMAGE" GIFBUILDER object
374  *
375  *************************************************/
387  public function maskImageOntoImage(&$im, $conf, $workArea)
388  {
389  if ($conf['file'] && $conf['mask']) {
390  $imgInf = pathinfo($conf['file']);
391  $imgExt = strtolower($imgInf['extension']);
392  if (!GeneralUtility::inList($this->gdlibExtensions, $imgExt)) {
393  $BBimage = $this->imageMagickConvert($conf['file'], $this->gifExtension);
394  } else {
395  $BBimage = $this->getImageDimensions($conf['file']);
396  }
397  $maskInf = pathinfo($conf['mask']);
398  $maskExt = strtolower($maskInf['extension']);
399  if (!GeneralUtility::inList($this->gdlibExtensions, $maskExt)) {
400  $BBmask = $this->imageMagickConvert($conf['mask'], $this->gifExtension);
401  } else {
402  $BBmask = $this->getImageDimensions($conf['mask']);
403  }
404  if ($BBimage && $BBmask) {
405  $w = imagesx($im);
406  $h = imagesy($im);
407  $tmpStr = $this->randomName();
408  $theImage = $tmpStr . '_img.' . $this->gifExtension;
409  $theDest = $tmpStr . '_dest.' . $this->gifExtension;
410  $theMask = $tmpStr . '_mask.' . $this->gifExtension;
411  // Prepare overlay image
412  $cpImg = $this->imageCreateFromFile($BBimage[3]);
413  $destImg = imagecreatetruecolor($w, $h);
414  // Preserve alpha transparency
415  if ($this->saveAlphaLayer) {
416  imagesavealpha($destImg, true);
417  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
418  imagefill($destImg, 0, 0, $Bcolor);
419  } else {
420  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
421  imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
422  }
423  $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
424  $this->ImageWrite($destImg, $theImage);
425  imagedestroy($cpImg);
426  imagedestroy($destImg);
427  // Prepare mask image
428  $cpImg = $this->imageCreateFromFile($BBmask[3]);
429  $destImg = imagecreatetruecolor($w, $h);
430  if ($this->saveAlphaLayer) {
431  imagesavealpha($destImg, true);
432  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
433  imagefill($destImg, 0, 0, $Bcolor);
434  } else {
435  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
436  imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
437  }
438  $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
439  $this->ImageWrite($destImg, $theMask);
440  imagedestroy($cpImg);
441  imagedestroy($destImg);
442  // Mask the images
443  $this->ImageWrite($im, $theDest);
444  // Let combineExec handle maskNegation
445  $this->combineExec($theDest, $theImage, $theMask, $theDest, true);
446  // The main image is loaded again...
447  $backIm = $this->imageCreateFromFile($theDest);
448  // ... and if nothing went wrong we load it onto the old one.
449  if ($backIm) {
450  if (!$this->saveAlphaLayer) {
451  imagecolortransparent($backIm, -1);
452  }
453  $im = $backIm;
454  }
455  // Unlink files from process
456  if (!$this->dontUnlinkTempFiles) {
457  unlink($theDest);
458  unlink($theImage);
459  unlink($theMask);
460  }
461  }
462  }
463  }
464 
474  public function copyImageOntoImage(&$im, $conf, $workArea)
475  {
476  if ($conf['file']) {
477  if (!GeneralUtility::inList($this->gdlibExtensions, $conf['BBOX'][2])) {
478  $conf['BBOX'] = $this->imageMagickConvert($conf['BBOX'][3], $this->gifExtension);
479  $conf['file'] = $conf['BBOX'][3];
480  }
481  $cpImg = $this->imageCreateFromFile($conf['file']);
482  $this->copyGifOntoGif($im, $cpImg, $conf, $workArea);
483  imagedestroy($cpImg);
484  }
485  }
486 
497  public function copyGifOntoGif(&$im, $cpImg, $conf, $workArea)
498  {
499  $cpW = imagesx($cpImg);
500  $cpH = imagesy($cpImg);
501  $tile = GeneralUtility::intExplode(',', $conf['tile']);
502  $tile[0] = MathUtility::forceIntegerInRange($tile[0], 1, 20);
503  $tile[1] = MathUtility::forceIntegerInRange($tile[1], 1, 20);
504  $cpOff = $this->objPosition($conf, $workArea, [$cpW * $tile[0], $cpH * $tile[1]]);
505  for ($xt = 0; $xt < $tile[0]; $xt++) {
506  $Xstart = $cpOff[0] + $cpW * $xt;
507  // If this image is inside of the workArea, then go on
508  if ($Xstart + $cpW > $workArea[0]) {
509  // X:
510  if ($Xstart < $workArea[0]) {
511  $cpImgCutX = $workArea[0] - $Xstart;
512  $Xstart = $workArea[0];
513  } else {
514  $cpImgCutX = 0;
515  }
516  $w = $cpW - $cpImgCutX;
517  if ($Xstart > $workArea[0] + $workArea[2] - $w) {
518  $w = $workArea[0] + $workArea[2] - $Xstart;
519  }
520  // If this image is inside of the workArea, then go on
521  if ($Xstart < $workArea[0] + $workArea[2]) {
522  // Y:
523  for ($yt = 0; $yt < $tile[1]; $yt++) {
524  $Ystart = $cpOff[1] + $cpH * $yt;
525  // If this image is inside of the workArea, then go on
526  if ($Ystart + $cpH > $workArea[1]) {
527  if ($Ystart < $workArea[1]) {
528  $cpImgCutY = $workArea[1] - $Ystart;
529  $Ystart = $workArea[1];
530  } else {
531  $cpImgCutY = 0;
532  }
533  $h = $cpH - $cpImgCutY;
534  if ($Ystart > $workArea[1] + $workArea[3] - $h) {
535  $h = $workArea[1] + $workArea[3] - $Ystart;
536  }
537  // If this image is inside of the workArea, then go on
538  if ($Ystart < $workArea[1] + $workArea[3]) {
539  $this->imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, $w, $h, $w, $h);
540  }
541  }
542  }
543  }
544  }
545  }
546  }
547 
578  public function imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight)
579  {
580  if (!$this->saveAlphaLayer) {
581  // Make true color image
582  $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
583  // Copy the source image onto that
584  imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
585  // Then copy the source image onto that (the actual operation!)
586  imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
587  // Set the destination image
588  $dstImg = $tmpImg;
589  } else {
590  imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
591  }
592  }
593 
594  /********************************
595  *
596  * Text / "TEXT" GIFBUILDER object
597  *
598  ********************************/
608  public function makeText(&$im, $conf, $workArea)
609  {
610  // Spacing
611  list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
612  // Position
613  $txtPos = $this->txtPosition($conf, $workArea, $conf['BBOX']);
614  $theText = $this->recodeString($conf['text']);
615  if ($conf['imgMap'] && is_array($conf['imgMap.'])) {
616  $this->addToMap($this->calcTextCordsForMap($conf['BBOX'][2], $txtPos, $conf['imgMap.']), $conf['imgMap.']);
617  }
618  if (!$conf['hideButCreateMap']) {
619  // Font Color:
620  $cols = $this->convertColor($conf['fontColor']);
621  // NiceText is calculated
622  if (!$conf['niceText']) {
623  $Fcolor = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
624  // antiAliasing is setup:
625  $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
626  for ($a = 0; $a < $conf['iterations']; $a++) {
627  // If any kind of spacing applys, we use this function:
628  if ($spacing || $wordSpacing) {
629  $this->SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, self::prependAbsolutePath($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
630  } else {
631  $this->renderTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf);
632  }
633  }
634  } else {
635  // NICETEXT::
636  // options anti_aliased and iterations is NOT available when doing this!!
637  $w = imagesx($im);
638  $h = imagesy($im);
639  $tmpStr = $this->randomName();
640  $fileMenu = $tmpStr . '_menuNT.' . $this->gifExtension;
641  $fileColor = $tmpStr . '_colorNT.' . $this->gifExtension;
642  $fileMask = $tmpStr . '_maskNT.' . $this->gifExtension;
643  // Scalefactor
644  $sF = MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
645  $newW = ceil($sF * imagesx($im));
646  $newH = ceil($sF * imagesy($im));
647  // Make mask
648  $maskImg = imagecreatetruecolor($newW, $newH);
649  $Bcolor = imagecolorallocate($maskImg, 255, 255, 255);
650  imagefilledrectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
651  $Fcolor = imagecolorallocate($maskImg, 0, 0, 0);
652  // If any kind of spacing applies, we use this function:
653  if ($spacing || $wordSpacing) {
654  $this->SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, self::prependAbsolutePath($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
655  } else {
656  $this->renderTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf, $sF);
657  }
658  $this->ImageWrite($maskImg, $fileMask);
659  imagedestroy($maskImg);
660  // Downscales the mask
661  if ($this->NO_IM_EFFECTS) {
662  $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '! -negate');
663  } else {
664  $command = trim($conf['niceText.']['before'] . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . $conf['niceText.']['after'] . ' -negate');
665  if ($conf['niceText.']['sharpen']) {
666  if ($this->V5_EFFECTS) {
667  $command .= $this->v5_sharpen($conf['niceText.']['sharpen']);
668  } else {
669  $command .= ' -sharpen ' . MathUtility::forceIntegerInRange($conf['niceText.']['sharpen'], 1, 99);
670  }
671  }
672  }
673  $this->imageMagickExec($fileMask, $fileMask, $command);
674  // Make the color-file
675  $colorImg = imagecreatetruecolor($w, $h);
676  $Ccolor = imagecolorallocate($colorImg, $cols[0], $cols[1], $cols[2]);
677  imagefilledrectangle($colorImg, 0, 0, $w, $h, $Ccolor);
678  $this->ImageWrite($colorImg, $fileColor);
679  imagedestroy($colorImg);
680  // The mask is applied
681  // The main pictures is saved temporarily
682  $this->ImageWrite($im, $fileMenu);
683  $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
684  // The main image is loaded again...
685  $backIm = $this->imageCreateFromFile($fileMenu);
686  // ... and if nothing went wrong we load it onto the old one.
687  if ($backIm) {
688  if (!$this->saveAlphaLayer) {
689  imagecolortransparent($backIm, -1);
690  }
691  $im = $backIm;
692  }
693  // Deleting temporary files;
694  if (!$this->dontUnlinkTempFiles) {
695  unlink($fileMenu);
696  unlink($fileColor);
697  unlink($fileMask);
698  }
699  }
700  }
701  }
702 
713  public function txtPosition($conf, $workArea, $BB)
714  {
715  $angle = (int)$conf['angle'] / 180 * pi();
716  $conf['angle'] = 0;
717  $straightBB = $this->calcBBox($conf);
718  // offset, align, valign, workarea
719  // [0]=x, [1]=y, [2]=w, [3]=h
720  $result = [];
721  $result[2] = $BB[0];
722  $result[3] = $BB[1];
723  $w = $workArea[2];
724  switch ($conf['align']) {
725  case 'right':
726 
727  case 'center':
728  $factor = abs(cos($angle));
729  $sign = cos($angle) < 0 ? -1 : 1;
730  $len1 = $sign * $factor * $straightBB[0];
731  $len2 = $sign * $BB[0];
732  $result[0] = $w - ceil(($len2 * $factor + (1 - $factor) * $len1));
733  $factor = abs(sin($angle));
734  $sign = sin($angle) < 0 ? -1 : 1;
735  $len1 = $sign * $factor * $straightBB[0];
736  $len2 = $sign * $BB[1];
737  $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
738  break;
739  }
740  switch ($conf['align']) {
741  case 'right':
742  break;
743  case 'center':
744  $result[0] = round($result[0] / 2);
745  $result[1] = round($result[1] / 2);
746  break;
747  default:
748  $result[0] = 0;
749  $result[1] = 0;
750  }
751  $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
752  $result = $this->applyOffset($result, $workArea);
753  return $result;
754  }
755 
764  public function calcBBox($conf)
765  {
766  $sF = $this->getTextScalFactor($conf);
767  list($spacing, $wordSpacing) = $this->calcWordSpacing($conf, $sF);
768  $theText = $this->recodeString($conf['text']);
769  $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $theText, $conf['splitRendering.'], $sF);
770  $theBBoxInfo = $charInf;
771  if ($conf['angle']) {
772  $xArr = [$charInf[0], $charInf[2], $charInf[4], $charInf[6]];
773  $yArr = [$charInf[1], $charInf[3], $charInf[5], $charInf[7]];
774  $x = max($xArr) - min($xArr);
775  $y = max($yArr) - min($yArr);
776  } else {
777  $x = $charInf[2] - $charInf[0];
778  $y = $charInf[1] - $charInf[7];
779  }
780  // Set original lineHeight (used by line breaks):
781  $theBBoxInfo['lineHeight'] = $y;
782  // If any kind of spacing applys, we use this function:
783  if ($spacing || $wordSpacing) {
784  $x = 0;
785  if (!$spacing && $wordSpacing) {
786  $bits = explode(' ', $theText);
787  foreach ($bits as $word) {
788  $word .= ' ';
789  $wordInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
790  $wordW = $wordInf[2] - $wordInf[0];
791  $x += $wordW + $wordSpacing;
792  }
793  } else {
794  $utf8Chars = $this->singleChars($theText);
795  // For each UTF-8 char, do:
796  foreach ($utf8Chars as $char) {
797  $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
798  $charW = $charInf[2] - $charInf[0];
799  $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
800  }
801  }
802  } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
803  $maxWidth = 0;
804  $currentWidth = 0;
805  $breakWidth = $conf['breakWidth'];
806  $breakSpace = $this->getBreakSpace($conf, $theBBoxInfo);
807  $wordPairs = $this->getWordPairsForLineBreak($conf['text']);
808  // Iterate through all word pairs:
809  foreach ($wordPairs as $index => $wordPair) {
810  $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
811  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
812  $currentWidth += $wordWidth;
813  } else {
814  $maxWidth = max($maxWidth, $currentWidth);
815  $y += $breakSpace;
816  // Restart:
817  $currentWidth = $wordWidth;
818  }
819  }
820  $x = max($maxWidth, $currentWidth) * $sF;
821  }
822  if ($sF > 1) {
823  $x = ceil($x / $sF);
824  $y = ceil($y / $sF);
825  if (is_array($theBBoxInfo)) {
826  foreach ($theBBoxInfo as &$value) {
827  $value = ceil($value / $sF);
828  }
829  unset($value);
830  }
831  }
832  return [$x, $y, $theBBoxInfo];
833  }
834 
844  public function addToMap($cords, $conf)
845  {
846  $this->map .= '<area' . ' shape="poly"' . ' coords="' . implode(',', $cords) . '"'
847  . ' href="' . htmlspecialchars($conf['url']) . '"'
848  . ($conf['target'] ? ' target="' . htmlspecialchars($conf['target']) . '"' : '')
849  . ((string)$conf['titleText'] !== '' ? ' title="' . htmlspecialchars($conf['titleText']) . '"' : '')
850  . ' alt="' . htmlspecialchars($conf['altText']) . '" />';
851  }
852 
863  public function calcTextCordsForMap($cords, $offset, $conf)
864  {
865  $pars = GeneralUtility::intExplode(',', $conf['explode'] . ',');
866  $newCords[0] = $cords[0] + $offset[0] - $pars[0];
867  $newCords[1] = $cords[1] + $offset[1] + $pars[1];
868  $newCords[2] = $cords[2] + $offset[0] + $pars[0];
869  $newCords[3] = $cords[3] + $offset[1] + $pars[1];
870  $newCords[4] = $cords[4] + $offset[0] + $pars[0];
871  $newCords[5] = $cords[5] + $offset[1] - $pars[1];
872  $newCords[6] = $cords[6] + $offset[0] - $pars[0];
873  $newCords[7] = $cords[7] + $offset[1] - $pars[1];
874  return $newCords;
875  }
876 
897  public function SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF = 1)
898  {
899  $spacing *= $sF;
900  $wordSpacing *= $sF;
901  if (!$spacing && $wordSpacing) {
902  $bits = explode(' ', $text);
903  foreach ($bits as $word) {
904  $word .= ' ';
905  $wordInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
906  $wordW = $wordInf[2] - $wordInf[0];
907  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
908  $x += $wordW + $wordSpacing;
909  }
910  } else {
911  $utf8Chars = $this->singleChars($text);
912  // For each UTF-8 char, do:
913  foreach ($utf8Chars as $char) {
914  $charInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
915  $charW = $charInf[2] - $charInf[0];
916  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
917  $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
918  }
919  }
920  }
921 
930  public function fontResize($conf)
931  {
932  // 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!!!!
933  $maxWidth = (int)$conf['maxWidth'];
934  list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
935  if ($maxWidth) {
936  // If any kind of spacing applys, we use this function:
937  if ($spacing || $wordSpacing) {
938  return $conf['fontSize'];
939  } else {
940  do {
941  // Determine bounding box.
942  $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $this->recodeString($conf['text']), $conf['splitRendering.']);
943  if ($conf['angle'] < 0) {
944  $pixelWidth = abs($bounds[4] - $bounds[0]);
945  } elseif ($conf['angle'] > 0) {
946  $pixelWidth = abs($bounds[2] - $bounds[6]);
947  } else {
948  $pixelWidth = abs($bounds[4] - $bounds[6]);
949  }
950  // Size is fine, exit:
951  if ($pixelWidth <= $maxWidth) {
952  break;
953  } else {
954  $conf['fontSize']--;
955  }
956  } while ($conf['fontSize'] > 1);
957  }
958  }
959  return $conf['fontSize'];
960  }
961 
973  public function ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF = 1)
974  {
975  // Initialize:
976  $offsetInfo = [];
977  $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
978  // Traverse string parts:
979  foreach ($stringParts as $strCfg) {
980  $fontFile = self::prependAbsolutePath($strCfg['fontFile']);
981  if (is_readable($fontFile)) {
989  $try = 0;
990  do {
991  $calc = imagettfbbox(GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
992  } while ($calc[2] < 0 && $try++ < 10);
993  // Calculate offsets:
994  if (empty($offsetInfo)) {
995  // First run, just copy over.
996  $offsetInfo = $calc;
997  } else {
998  $offsetInfo[2] += $calc[2] - $calc[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
999  $offsetInfo[3] += $calc[3] - $calc[1] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
1000  $offsetInfo[4] += $calc[4] - $calc[6] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
1001  $offsetInfo[5] += $calc[5] - $calc[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
1002  }
1003  } else {
1004  debug('cannot read file: ' . $fontFile, \TYPO3\CMS\Core\Imaging\GraphicalFunctions::class . '::ImageTTFBBoxWrapper()');
1005  }
1006  }
1007  return $offsetInfo;
1008  }
1009 
1025  public function ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF = 1)
1026  {
1027  // Initialize:
1028  $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
1029  $x = ceil($sF * $x);
1030  $y = ceil($sF * $y);
1031  // Traverse string parts:
1032  foreach ($stringParts as $i => $strCfg) {
1033  // Initialize:
1034  $colorIndex = $color;
1035  // Set custom color if any (only when niceText is off):
1036  if ($strCfg['color'] && $sF == 1) {
1037  $cols = $this->convertColor($strCfg['color']);
1038  $colorIndex = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
1039  $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
1040  }
1041  // Setting xSpaceBefore
1042  if ($i) {
1043  $x += (int)$strCfg['xSpaceBefore'];
1044  $y -= (int)$strCfg['ySpaceBefore'];
1045  }
1046  $fontFile = self::prependAbsolutePath($strCfg['fontFile']);
1047  if (is_readable($fontFile)) {
1048  // Render part:
1049  imagettftext($im, GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
1050  // Calculate offset to apply:
1051  $wordInf = imagettfbbox(GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, self::prependAbsolutePath($strCfg['fontFile']), $strCfg['str']);
1052  $x += $wordInf[2] - $wordInf[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceAfter'];
1053  $y += $wordInf[5] - $wordInf[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceAfter'];
1054  } else {
1055  debug('cannot read file: ' . $fontFile, \TYPO3\CMS\Core\Imaging\GraphicalFunctions::class . '::ImageTTFTextWrapper()');
1056  }
1057  }
1058  }
1059 
1069  public function splitString($string, $splitRendering, $fontSize, $fontFile)
1070  {
1071  // Initialize by setting the whole string and default configuration as the first entry.
1072  $result = [];
1073  $result[] = [
1074  'str' => $string,
1075  'fontSize' => $fontSize,
1076  'fontFile' => $fontFile
1077  ];
1078  // Traverse the split-rendering configuration:
1079  // Splitting will create more entries in $result with individual configurations.
1080  if (is_array($splitRendering)) {
1081  $sKeyArray = \TYPO3\CMS\Core\TypoScript\TemplateService::sortedKeyList($splitRendering);
1082  // Traverse configured options:
1083  foreach ($sKeyArray as $key) {
1084  $cfg = $splitRendering[$key . '.'];
1085  // Process each type of split rendering keyword:
1086  switch ((string)$splitRendering[$key]) {
1087  case 'highlightWord':
1088  if ((string)$cfg['value'] !== '') {
1089  $newResult = [];
1090  // Traverse the current parts of the result array:
1091  foreach ($result as $part) {
1092  // Explode the string value by the word value to highlight:
1093  $explodedParts = explode($cfg['value'], $part['str']);
1094  foreach ($explodedParts as $c => $expValue) {
1095  if ((string)$expValue !== '') {
1096  $newResult[] = array_merge($part, ['str' => $expValue]);
1097  }
1098  if ($c + 1 < count($explodedParts)) {
1099  $newResult[] = [
1100  'str' => $cfg['value'],
1101  'fontSize' => $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1102  'fontFile' => $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1103  'color' => $cfg['color'],
1104  'xSpaceBefore' => $cfg['xSpaceBefore'],
1105  'xSpaceAfter' => $cfg['xSpaceAfter'],
1106  'ySpaceBefore' => $cfg['ySpaceBefore'],
1107  'ySpaceAfter' => $cfg['ySpaceAfter']
1108  ];
1109  }
1110  }
1111  }
1112  // Set the new result as result array:
1113  if (!empty($newResult)) {
1114  $result = $newResult;
1115  }
1116  }
1117  break;
1118  case 'charRange':
1119  if ((string)$cfg['value'] !== '') {
1120  // Initialize range:
1121  $ranges = GeneralUtility::trimExplode(',', $cfg['value'], true);
1122  foreach ($ranges as $i => $rangeDef) {
1123  $ranges[$i] = GeneralUtility::intExplode('-', $ranges[$i]);
1124  if (!isset($ranges[$i][1])) {
1125  $ranges[$i][1] = $ranges[$i][0];
1126  }
1127  }
1128  $newResult = [];
1129  // Traverse the current parts of the result array:
1130  foreach ($result as $part) {
1131  // Initialize:
1132  $currentState = -1;
1133  $bankAccum = '';
1134  // Explode the string value by the word value to highlight:
1135  $utf8Chars = $this->singleChars($part['str']);
1136  foreach ($utf8Chars as $utfChar) {
1137  // Find number and evaluate position:
1138  $uNumber = (int)$this->csConvObj->utf8CharToUnumber($utfChar);
1139  $inRange = 0;
1140  foreach ($ranges as $rangeDef) {
1141  if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
1142  $inRange = 1;
1143  break;
1144  }
1145  }
1146  if ($currentState == -1) {
1147  $currentState = $inRange;
1148  }
1149  // Initialize first char
1150  // Switch bank:
1151  if ($inRange != $currentState && $uNumber !== 9 && $uNumber !== 10 && $uNumber !== 13 && $uNumber !== 32) {
1152  // Set result:
1153  if ($bankAccum !== '') {
1154  $newResult[] = [
1155  'str' => $bankAccum,
1156  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1157  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1158  'color' => $currentState ? $cfg['color'] : '',
1159  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1160  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1161  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1162  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1163  ];
1164  }
1165  // Initialize new settings:
1166  $currentState = $inRange;
1167  $bankAccum = '';
1168  }
1169  // Add char to bank:
1170  $bankAccum .= $utfChar;
1171  }
1172  // Set result for FINAL part:
1173  if ($bankAccum !== '') {
1174  $newResult[] = [
1175  'str' => $bankAccum,
1176  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1177  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1178  'color' => $currentState ? $cfg['color'] : '',
1179  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1180  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1181  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1182  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1183  ];
1184  }
1185  }
1186  // Set the new result as result array:
1187  if (!empty($newResult)) {
1188  $result = $newResult;
1189  }
1190  }
1191  break;
1192  }
1193  }
1194  }
1195  return $result;
1196  }
1197 
1207  public function calcWordSpacing($conf, $scaleFactor = 1)
1208  {
1209  $spacing = (int)$conf['spacing'];
1210  $wordSpacing = (int)$conf['wordSpacing'];
1211  $wordSpacing = $wordSpacing ?: $spacing * 2;
1212  $spacing *= $scaleFactor;
1213  $wordSpacing *= $scaleFactor;
1214  return [$spacing, $wordSpacing];
1215  }
1216 
1224  public function getTextScalFactor($conf)
1225  {
1226  if (!$conf['niceText']) {
1227  $sF = 1;
1228  } else {
1229  // NICETEXT::
1230  $sF = MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
1231  }
1232  return $sF;
1233  }
1234 
1251  protected function renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF = 1)
1252  {
1253  if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
1254  $phrase = '';
1255  $currentWidth = 0;
1256  $breakWidth = $conf['breakWidth'];
1257  $breakSpace = $this->getBreakSpace($conf);
1258  $wordPairs = $this->getWordPairsForLineBreak($string);
1259  // Iterate through all word pairs:
1260  foreach ($wordPairs as $index => $wordPair) {
1261  $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
1262  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
1263  $currentWidth += $wordWidth;
1264  $phrase .= $wordPair;
1265  } else {
1266  // Render the current phrase that is below breakWidth:
1267  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1268  // Calculate the news height offset:
1269  $y += $breakSpace;
1270  // Restart the phrase:
1271  $currentWidth = $wordWidth;
1272  $phrase = $wordPair;
1273  }
1274  }
1275  // Render the remaining phrase:
1276  if ($currentWidth) {
1277  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1278  }
1279  } else {
1280  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
1281  }
1282  }
1283 
1290  protected function getWordPairsForLineBreak($string)
1291  {
1292  $wordPairs = [];
1293  $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
1294  $wordsCount = count($wordsArray);
1295  for ($index = 0; $index < $wordsCount; $index += 2) {
1296  $wordPairs[] = $wordsArray[$index] . $wordsArray[$index + 1];
1297  }
1298  return $wordPairs;
1299  }
1300 
1308  protected function getRenderedTextWidth($text, $conf)
1309  {
1310  $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $this->recodeString($text), $conf['splitRendering.']);
1311  if ($conf['angle'] < 0) {
1312  $pixelWidth = abs($bounds[4] - $bounds[0]);
1313  } elseif ($conf['angle'] > 0) {
1314  $pixelWidth = abs($bounds[2] - $bounds[6]);
1315  } else {
1316  $pixelWidth = abs($bounds[4] - $bounds[6]);
1317  }
1318  return $pixelWidth;
1319  }
1320 
1328  protected function getBreakSpace($conf, array $boundingBox = null)
1329  {
1330  if (!isset($boundingBox)) {
1331  $boundingBox = $this->calcBBox($conf);
1332  $boundingBox = $boundingBox[2];
1333  }
1334  if (isset($conf['breakSpace']) && $conf['breakSpace']) {
1335  $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
1336  } else {
1337  $breakSpace = $boundingBox['lineHeight'];
1338  }
1339  return $breakSpace;
1340  }
1341 
1342  /*********************************************
1343  *
1344  * Other GIFBUILDER objects related to TEXT
1345  *
1346  *********************************************/
1357  public function makeOutline(&$im, $conf, $workArea, $txtConf)
1358  {
1359  $thickness = (int)$conf['thickness'];
1360  if ($thickness) {
1361  $txtConf['fontColor'] = $conf['color'];
1362  $outLineDist = MathUtility::forceIntegerInRange($thickness, 1, 2);
1363  for ($b = 1; $b <= $outLineDist; $b++) {
1364  if ($b == 1) {
1365  $it = 8;
1366  } else {
1367  $it = 16;
1368  }
1369  $outL = $this->circleOffset($b, $it);
1370  for ($a = 0; $a < $it; $a++) {
1371  $this->makeText($im, $txtConf, $this->applyOffset($workArea, $outL[$a]));
1372  }
1373  }
1374  }
1375  }
1376 
1387  public function circleOffset($distance, $iterations)
1388  {
1389  $res = [];
1390  if ($distance && $iterations) {
1391  for ($a = 0; $a < $iterations; $a++) {
1392  $yOff = round(sin((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1393  if ($yOff) {
1394  $yOff = (int)(ceil(abs(($yOff / 100))) * ($yOff / abs($yOff)));
1395  }
1396  $xOff = round(cos((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1397  if ($xOff) {
1398  $xOff = (int)(ceil(abs(($xOff / 100))) * ($xOff / abs($xOff)));
1399  }
1400  $res[$a] = [$xOff, $yOff];
1401  }
1402  }
1403  return $res;
1404  }
1405 
1416  public function makeEmboss(&$im, $conf, $workArea, $txtConf)
1417  {
1418  $conf['color'] = $conf['highColor'];
1419  $this->makeShadow($im, $conf, $workArea, $txtConf);
1420  $newOffset = GeneralUtility::intExplode(',', $conf['offset']);
1421  $newOffset[0] *= -1;
1422  $newOffset[1] *= -1;
1423  $conf['offset'] = implode(',', $newOffset);
1424  $conf['color'] = $conf['lowColor'];
1425  $this->makeShadow($im, $conf, $workArea, $txtConf);
1426  }
1427 
1439  public function makeShadow(&$im, $conf, $workArea, $txtConf)
1440  {
1441  $workArea = $this->applyOffset($workArea, GeneralUtility::intExplode(',', $conf['offset']));
1442  $blurRate = MathUtility::forceIntegerInRange((int)$conf['blur'], 0, 99);
1443  // No effects if ImageMagick ver. 5+
1444  if (!$blurRate || $this->NO_IM_EFFECTS) {
1445  $txtConf['fontColor'] = $conf['color'];
1446  $this->makeText($im, $txtConf, $workArea);
1447  } else {
1448  $w = imagesx($im);
1449  $h = imagesy($im);
1450  // Area around the blur used for cropping something
1451  $blurBorder = 3;
1452  $tmpStr = $this->randomName();
1453  $fileMenu = $tmpStr . '_menu.' . $this->gifExtension;
1454  $fileColor = $tmpStr . '_color.' . $this->gifExtension;
1455  $fileMask = $tmpStr . '_mask.' . $this->gifExtension;
1456  // BlurColor Image laves
1457  $blurColImg = imagecreatetruecolor($w, $h);
1458  $bcols = $this->convertColor($conf['color']);
1459  $Bcolor = imagecolorallocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
1460  imagefilledrectangle($blurColImg, 0, 0, $w, $h, $Bcolor);
1461  $this->ImageWrite($blurColImg, $fileColor);
1462  imagedestroy($blurColImg);
1463  // The mask is made: BlurTextImage
1464  $blurTextImg = imagecreatetruecolor($w + $blurBorder * 2, $h + $blurBorder * 2);
1465  // Black background
1466  $Bcolor = imagecolorallocate($blurTextImg, 0, 0, 0);
1467  imagefilledrectangle($blurTextImg, 0, 0, $w + $blurBorder * 2, $h + $blurBorder * 2, $Bcolor);
1468  $txtConf['fontColor'] = 'white';
1469  $blurBordArr = [$blurBorder, $blurBorder];
1470  $this->makeText($blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
1471  // Dump to temporary file
1472  $this->ImageWrite($blurTextImg, $fileMask);
1473  // Destroy
1474  imagedestroy($blurTextImg);
1475  $command = '';
1476  if ($this->V5_EFFECTS) {
1477  $command .= $this->v5_blur($blurRate + 1);
1478  } else {
1479  // Blurring of the mask
1480  // How many blur-commands that is executed. Min = 1;
1481  $times = ceil($blurRate / 10);
1482  // Here I boost the blur-rate so that it is 100 already at 25. The rest is done by up to 99 iterations of the blur-command.
1483  $newBlurRate = $blurRate * 4;
1484  $newBlurRate = MathUtility::forceIntegerInRange($newBlurRate, 1, 99);
1485  // Building blur-command
1486  for ($a = 0; $a < $times; $a++) {
1487  $command .= ' -blur ' . $blurRate;
1488  }
1489  }
1490  $this->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
1491  // The mask is loaded again
1492  $blurTextImg_tmp = $this->imageCreateFromFile($fileMask);
1493  // If nothing went wrong we continue with the blurred mask
1494  if ($blurTextImg_tmp) {
1495  // Cropping the border from the mask
1496  $blurTextImg = imagecreatetruecolor($w, $h);
1497  $this->imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, $w, $h, $w, $h);
1498  // Destroy the temporary mask
1499  imagedestroy($blurTextImg_tmp);
1500  // Adjust the mask
1501  $intensity = 40;
1502  if ($conf['intensity']) {
1503  $intensity = MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
1504  }
1505  $intensity = ceil(255 - $intensity / 100 * 255);
1506  $this->inputLevels($blurTextImg, 0, $intensity);
1507  $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 0, 100);
1508  if ($opacity && $opacity < 100) {
1509  $high = ceil(255 * $opacity / 100);
1510  // Reducing levels as the opacity demands
1511  $this->outputLevels($blurTextImg, 0, $high);
1512  }
1513  // Dump the mask again
1514  $this->ImageWrite($blurTextImg, $fileMask);
1515  // Destroy the mask
1516  imagedestroy($blurTextImg);
1517  // The pictures are combined
1518  // The main pictures is saved temporarily
1519  $this->ImageWrite($im, $fileMenu);
1520  $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
1521  // The main image is loaded again...
1522  $backIm = $this->imageCreateFromFile($fileMenu);
1523  // ... and if nothing went wrong we load it onto the old one.
1524  if ($backIm) {
1525  if (!$this->saveAlphaLayer) {
1526  imagecolortransparent($backIm, -1);
1527  }
1528  $im = $backIm;
1529  }
1530  }
1531  // Deleting temporary files;
1532  if (!$this->dontUnlinkTempFiles) {
1533  unlink($fileMenu);
1534  unlink($fileColor);
1535  unlink($fileMask);
1536  }
1537  }
1538  }
1539 
1540  /****************************
1541  *
1542  * Other GIFBUILDER objects
1543  *
1544  ****************************/
1554  public function makeBox(&$im, $conf, $workArea)
1555  {
1556  $cords = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1557  $conf['offset'] = $cords[0] . ',' . $cords[1];
1558  $cords = $this->objPosition($conf, $workArea, [$cords[2], $cords[3]]);
1559  $cols = $this->convertColor($conf['color']);
1560  $opacity = 0;
1561  if (isset($conf['opacity'])) {
1562  // conversion:
1563  // PHP 0 = opaque, 127 = transparent
1564  // TYPO3 100 = opaque, 0 = transparent
1565  $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 1, 100, 1);
1566  $opacity = abs($opacity - 100);
1567  $opacity = round(127 * $opacity / 100);
1568  }
1569  $tmpColor = imagecolorallocatealpha($im, $cols[0], $cols[1], $cols[2], $opacity);
1570  imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1571  }
1572 
1594  public function makeEllipse(&$im, array $conf, array $workArea)
1595  {
1596  $ellipseConfiguration = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1597  // Ellipse offset inside workArea (x/y)
1598  $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1599  // @see objPosition
1600  $imageCoordinates = $this->objPosition($conf, $workArea, [$ellipseConfiguration[2], $ellipseConfiguration[3]]);
1601  $color = $this->convertColor($conf['color']);
1602  $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1603  imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1604  }
1605 
1615  public function makeEffect(&$im, $conf)
1616  {
1617  $commands = $this->IMparams($conf['value']);
1618  if ($commands) {
1619  $this->applyImageMagickToPHPGif($im, $commands);
1620  }
1621  }
1622 
1631  public function IMparams($setup)
1632  {
1633  if (!trim($setup)) {
1634  return '';
1635  }
1636  $effects = explode('|', $setup);
1637  $commands = '';
1638  foreach ($effects as $val) {
1639  $pairs = explode('=', $val, 2);
1640  $value = trim($pairs[1]);
1641  $effect = strtolower(trim($pairs[0]));
1642  switch ($effect) {
1643  case 'gamma':
1644  $commands .= ' -gamma ' . floatval($value);
1645  break;
1646  case 'blur':
1647  if (!$this->NO_IM_EFFECTS) {
1648  if ($this->V5_EFFECTS) {
1649  $commands .= $this->v5_blur($value);
1650  } else {
1651  $commands .= ' -blur ' . MathUtility::forceIntegerInRange($value, 1, 99);
1652  }
1653  }
1654  break;
1655  case 'sharpen':
1656  if (!$this->NO_IM_EFFECTS) {
1657  if ($this->V5_EFFECTS) {
1658  $commands .= $this->v5_sharpen($value);
1659  } else {
1660  $commands .= ' -sharpen ' . MathUtility::forceIntegerInRange($value, 1, 99);
1661  }
1662  }
1663  break;
1664  case 'rotate':
1665  $commands .= ' -rotate ' . MathUtility::forceIntegerInRange($value, 0, 360);
1666  break;
1667  case 'solarize':
1668  $commands .= ' -solarize ' . MathUtility::forceIntegerInRange($value, 0, 99);
1669  break;
1670  case 'swirl':
1671  $commands .= ' -swirl ' . MathUtility::forceIntegerInRange($value, 0, 1000);
1672  break;
1673  case 'wave':
1674  $params = GeneralUtility::intExplode(',', $value);
1675  $commands .= ' -wave ' . MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . MathUtility::forceIntegerInRange($params[1], 0, 99);
1676  break;
1677  case 'charcoal':
1678  $commands .= ' -charcoal ' . MathUtility::forceIntegerInRange($value, 0, 100);
1679  break;
1680  case 'gray':
1681  $commands .= ' -colorspace GRAY';
1682  break;
1683  case 'edge':
1684  $commands .= ' -edge ' . MathUtility::forceIntegerInRange($value, 0, 99);
1685  break;
1686  case 'emboss':
1687  $commands .= ' -emboss';
1688  break;
1689  case 'flip':
1690  $commands .= ' -flip';
1691  break;
1692  case 'flop':
1693  $commands .= ' -flop';
1694  break;
1695  case 'colors':
1696  $commands .= ' -colors ' . MathUtility::forceIntegerInRange($value, 2, 255);
1697  break;
1698  case 'shear':
1699  $commands .= ' -shear ' . MathUtility::forceIntegerInRange($value, -90, 90);
1700  break;
1701  case 'invert':
1702  $commands .= ' -negate';
1703  break;
1704  }
1705  }
1706  return $commands;
1707  }
1708 
1717  public function adjust(&$im, $conf)
1718  {
1719  $setup = $conf['value'];
1720  if (!trim($setup)) {
1721  return '';
1722  }
1723  $effects = explode('|', $setup);
1724  foreach ($effects as $val) {
1725  $pairs = explode('=', $val, 2);
1726  $value = trim($pairs[1]);
1727  $effect = strtolower(trim($pairs[0]));
1728  switch ($effect) {
1729  case 'inputlevels':
1730  // low,high
1731  $params = GeneralUtility::intExplode(',', $value);
1732  $this->inputLevels($im, $params[0], $params[1]);
1733  break;
1734  case 'outputlevels':
1735  $params = GeneralUtility::intExplode(',', $value);
1736  $this->outputLevels($im, $params[0], $params[1]);
1737  break;
1738  case 'autolevels':
1739  $this->autoLevels($im);
1740  break;
1741  }
1742  }
1743  }
1744 
1753  public function crop(&$im, $conf)
1754  {
1755  // Clears workArea to total image
1756  $this->setWorkArea('');
1757  $cords = GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1758  $conf['offset'] = $cords[0] . ',' . $cords[1];
1759  $cords = $this->objPosition($conf, $this->workArea, [$cords[2], $cords[3]]);
1760  $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1761  $cols = $this->convertColor($conf['backColor'] ? $conf['backColor'] : $this->setup['backColor']);
1762  $Bcolor = imagecolorallocate($newIm, $cols[0], $cols[1], $cols[2]);
1763  imagefilledrectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1764  $newConf = [];
1765  $workArea = [0, 0, $cords[2], $cords[3]];
1766  if ($cords[0] < 0) {
1767  $workArea[0] = abs($cords[0]);
1768  } else {
1769  $newConf['offset'] = -$cords[0];
1770  }
1771  if ($cords[1] < 0) {
1772  $workArea[1] = abs($cords[1]);
1773  } else {
1774  $newConf['offset'] .= ',' . -$cords[1];
1775  }
1776  $this->copyGifOntoGif($newIm, $im, $newConf, $workArea);
1777  $im = $newIm;
1778  $this->w = imagesx($im);
1779  $this->h = imagesy($im);
1780  // Clears workArea to total image
1781  $this->setWorkArea('');
1782  }
1783 
1792  public function scale(&$im, $conf)
1793  {
1794  if ($conf['width'] || $conf['height'] || $conf['params']) {
1795  $tmpStr = $this->randomName();
1796  $theFile = $tmpStr . '.' . $this->gifExtension;
1797  $this->ImageWrite($im, $theFile);
1798  $theNewFile = $this->imageMagickConvert($theFile, $this->gifExtension, $conf['width'], $conf['height'], $conf['params']);
1799  $tmpImg = $this->imageCreateFromFile($theNewFile[3]);
1800  if ($tmpImg) {
1801  imagedestroy($im);
1802  $im = $tmpImg;
1803  $this->w = imagesx($im);
1804  $this->h = imagesy($im);
1805  // Clears workArea to total image
1806  $this->setWorkArea('');
1807  }
1808  if (!$this->dontUnlinkTempFiles) {
1809  unlink($theFile);
1810  if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1811  unlink($theNewFile[3]);
1812  }
1813  }
1814  }
1815  }
1816 
1826  public function setWorkArea($workArea)
1827  {
1828  $this->workArea = GeneralUtility::intExplode(',', $workArea);
1829  $this->workArea = $this->applyOffset($this->workArea, $this->OFFSET);
1830  if (!$this->workArea[2]) {
1831  $this->workArea[2] = $this->w;
1832  }
1833  if (!$this->workArea[3]) {
1834  $this->workArea[3] = $this->h;
1835  }
1836  }
1837 
1838  /*************************
1839  *
1840  * Adjustment functions
1841  *
1842  ************************/
1849  public function autolevels(&$im)
1850  {
1851  $totalCols = imagecolorstotal($im);
1852  $min = 255;
1853  $max = 0;
1854  for ($c = 0; $c < $totalCols; $c++) {
1855  $cols = imagecolorsforindex($im, $c);
1856  $grayArr[] = round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
1857  }
1858  $min = min($grayArr);
1859  $max = max($grayArr);
1860  $delta = $max - $min;
1861  if ($delta) {
1862  for ($c = 0; $c < $totalCols; $c++) {
1863  $cols = imagecolorsforindex($im, $c);
1864  $cols['red'] = floor(($cols['red'] - $min) / $delta * 255);
1865  $cols['green'] = floor(($cols['green'] - $min) / $delta * 255);
1866  $cols['blue'] = floor(($cols['blue'] - $min) / $delta * 255);
1867  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1868  }
1869  }
1870  }
1871 
1881  public function outputLevels(&$im, $low, $high, $swap = '')
1882  {
1883  if ($low < $high) {
1884  $low = MathUtility::forceIntegerInRange($low, 0, 255);
1885  $high = MathUtility::forceIntegerInRange($high, 0, 255);
1886  if ($swap) {
1887  $temp = $low;
1888  $low = 255 - $high;
1889  $high = 255 - $temp;
1890  }
1891  $delta = $high - $low;
1892  $totalCols = imagecolorstotal($im);
1893  for ($c = 0; $c < $totalCols; $c++) {
1894  $cols = imagecolorsforindex($im, $c);
1895  $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
1896  $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
1897  $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
1898  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1899  }
1900  }
1901  }
1902 
1911  public function inputLevels(&$im, $low, $high)
1912  {
1913  if ($low < $high) {
1914  $low = MathUtility::forceIntegerInRange($low, 0, 255);
1915  $high = MathUtility::forceIntegerInRange($high, 0, 255);
1916  $delta = $high - $low;
1917  $totalCols = imagecolorstotal($im);
1918  for ($c = 0; $c < $totalCols; $c++) {
1919  $cols = imagecolorsforindex($im, $c);
1920  $cols['red'] = MathUtility::forceIntegerInRange(($cols['red'] - $low) / $delta * 255, 0, 255);
1921  $cols['green'] = MathUtility::forceIntegerInRange(($cols['green'] - $low) / $delta * 255, 0, 255);
1922  $cols['blue'] = MathUtility::forceIntegerInRange(($cols['blue'] - $low) / $delta * 255, 0, 255);
1923  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1924  }
1925  }
1926  }
1927 
1935  public function IMreduceColors($file, $cols)
1936  {
1937  $fI = GeneralUtility::split_fileref($file);
1938  $ext = strtolower($fI['fileext']);
1939  $result = $this->randomName() . '.' . $ext;
1940  if (($reduce = MathUtility::forceIntegerInRange($cols, 0, $ext == 'gif' ? 256 : $this->truecolorColors, 0)) > 0) {
1941  $params = ' -colors ' . $reduce;
1942  if ($reduce <= 256) {
1943  $params .= ' -type Palette';
1944  }
1945  if ($ext == 'png' && $reduce <= 256) {
1946  $prefix = 'png8:';
1947  }
1948  $this->imageMagickExec($file, $prefix . $result, $params);
1949  if ($result) {
1950  return $result;
1951  }
1952  }
1953  return '';
1954  }
1955 
1956  /*********************************
1957  *
1958  * GIFBUILDER Helper functions
1959  *
1960  *********************************/
1969  public function prependAbsolutePath($fontFile)
1970  {
1971  $absPath = defined('PATH_typo3') ? dirname(PATH_thisScript) . '/' : PATH_site;
1972  $fontFile = GeneralUtility::isAbsPath($fontFile) ? $fontFile : GeneralUtility::resolveBackPath($absPath . $fontFile);
1973  return $fontFile;
1974  }
1975 
1984  public function v5_sharpen($factor)
1985  {
1986  $factor = MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
1987  $sharpenArr = explode(',', ',' . $this->im5fx_sharpenSteps);
1988  $sharpenF = trim($sharpenArr[$factor]);
1989  if ($sharpenF) {
1990  $cmd = ' -sharpen ' . $sharpenF;
1991  return $cmd;
1992  }
1993  }
1994 
2003  public function v5_blur($factor)
2004  {
2005  $factor = MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
2006  $blurArr = explode(',', ',' . $this->im5fx_blurSteps);
2007  $blurF = trim($blurArr[$factor]);
2008  if ($blurF) {
2009  $cmd = ' -blur ' . $blurF;
2010  return $cmd;
2011  }
2012  }
2013 
2020  public function randomName()
2021  {
2022  $this->createTempSubDir('temp/');
2023  return $this->tempPath . 'temp/' . md5(uniqid('', true));
2024  }
2025 
2034  public function applyOffset($cords, $OFFSET)
2035  {
2036  $cords[0] = (int)$cords[0] + (int)$OFFSET[0];
2037  $cords[1] = (int)$cords[1] + (int)$OFFSET[1];
2038  return $cords;
2039  }
2040 
2048  public function convertColor($string)
2049  {
2050  $col = [];
2051  $cParts = explode(':', $string, 2);
2052  // Finding the RGB definitions of the color:
2053  $string = $cParts[0];
2054  if (strstr($string, '#')) {
2055  $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string);
2056  $col[] = hexdec(substr($string, 0, 2));
2057  $col[] = hexdec(substr($string, 2, 2));
2058  $col[] = hexdec(substr($string, 4, 2));
2059  } elseif (strstr($string, ',')) {
2060  $string = preg_replace('/[^,0-9]*/', '', $string);
2061  $strArr = explode(',', $string);
2062  $col[] = (int)$strArr[0];
2063  $col[] = (int)$strArr[1];
2064  $col[] = (int)$strArr[2];
2065  } else {
2066  $string = strtolower(trim($string));
2067  if ($this->colMap[$string]) {
2068  $col = $this->colMap[$string];
2069  } else {
2070  $col = [0, 0, 0];
2071  }
2072  }
2073  // ... and possibly recalculating the value
2074  if (trim($cParts[1])) {
2075  $cParts[1] = trim($cParts[1]);
2076  if ($cParts[1][0] === '*') {
2077  $val = floatval(substr($cParts[1], 1));
2078  $col[0] = MathUtility::forceIntegerInRange($col[0] * $val, 0, 255);
2079  $col[1] = MathUtility::forceIntegerInRange($col[1] * $val, 0, 255);
2080  $col[2] = MathUtility::forceIntegerInRange($col[2] * $val, 0, 255);
2081  } else {
2082  $val = (int)$cParts[1];
2083  $col[0] = MathUtility::forceIntegerInRange($col[0] + $val, 0, 255);
2084  $col[1] = MathUtility::forceIntegerInRange($col[1] + $val, 0, 255);
2085  $col[2] = MathUtility::forceIntegerInRange($col[2] + $val, 0, 255);
2086  }
2087  }
2088  return $col;
2089  }
2090 
2098  public function recodeString($string)
2099  {
2100  // Recode string to UTF-8 from $this->nativeCharset:
2101  if ($this->nativeCharset && $this->nativeCharset != 'utf-8') {
2102  // Convert to UTF-8
2103  $string = $this->csConvObj->utf8_encode($string, $this->nativeCharset);
2104  }
2105  return $string;
2106  }
2107 
2116  public function singleChars($theText, $returnUnicodeNumber = false)
2117  {
2118  if ($this->nativeCharset) {
2119  // Get an array of separated UTF-8 chars
2120  return $this->csConvObj->utf8_to_numberarray($theText, 1, $returnUnicodeNumber ? 0 : 1);
2121  } else {
2122  $output = [];
2123  $c = strlen($theText);
2124  for ($a = 0; $a < $c; $a++) {
2125  $output[] = substr($theText, $a, 1);
2126  }
2127  return $output;
2128  }
2129  }
2130 
2141  public function objPosition($conf, $workArea, $BB)
2142  {
2143  // offset, align, valign, workarea
2144  $result = [];
2145  $result[2] = $BB[0];
2146  $result[3] = $BB[1];
2147  $w = $workArea[2];
2148  $h = $workArea[3];
2149  $align = explode(',', $conf['align']);
2150  $align[0] = strtolower(substr(trim($align[0]), 0, 1));
2151  $align[1] = strtolower(substr(trim($align[1]), 0, 1));
2152  switch ($align[0]) {
2153  case 'r':
2154  $result[0] = $w - $result[2];
2155  break;
2156  case 'c':
2157  $result[0] = round(($w - $result[2]) / 2);
2158  break;
2159  default:
2160  $result[0] = 0;
2161  }
2162  switch ($align[1]) {
2163  case 'b':
2164  // y pos
2165  $result[1] = $h - $result[3];
2166  break;
2167  case 'c':
2168  $result[1] = round(($h - $result[3]) / 2);
2169  break;
2170  default:
2171  $result[1] = 0;
2172  }
2173  $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
2174  $result = $this->applyOffset($result, $workArea);
2175  return $result;
2176  }
2177 
2178  /***********************************
2179  *
2180  * Scaling, Dimensions of images
2181  *
2182  ***********************************/
2197  public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $params = '', $frame = '', $options = [], $mustCreate = false)
2198  {
2199  if ($this->NO_IMAGE_MAGICK) {
2200  // Returning file info right away
2201  return $this->getImageDimensions($imagefile);
2202  }
2203  if ($info = $this->getImageDimensions($imagefile)) {
2204  $newExt = strtolower(trim($newExt));
2205  // If no extension is given the original extension is used
2206  if (!$newExt) {
2207  $newExt = $info[2];
2208  }
2209  if ($newExt == 'web') {
2210  if (GeneralUtility::inList($this->webImageExt, $info[2])) {
2211  $newExt = $info[2];
2212  } else {
2213  $newExt = $this->gif_or_jpg($info[2], $info[0], $info[1]);
2214  if (!$params) {
2215  $params = $this->cmds[$newExt];
2216  }
2217  }
2218  }
2219  if (GeneralUtility::inList($this->imageFileExt, $newExt)) {
2220  if (strstr($w . $h, 'm')) {
2221  $max = 1;
2222  } else {
2223  $max = 0;
2224  }
2225  $data = $this->getImageScale($info, $w, $h, $options);
2226  $w = $data['origW'];
2227  $h = $data['origH'];
2228  // If no conversion should be performed
2229  // this flag is TRUE if the width / height does NOT dictate
2230  // the image to be scaled!! (that is if no width / height is
2231  // given or if the destination w/h matches the original image
2232  // dimensions or if the option to not scale the image is set)
2233  $noScale = !$w && !$h || $data[0] == $info[0] && $data[1] == $info[1] || !empty($options['noScale']);
2234  if ($noScale && !$data['crs'] && !$params && !$frame && $newExt == $info[2] && !$mustCreate) {
2235  // Set the new width and height before returning,
2236  // if the noScale option is set
2237  if (!empty($options['noScale'])) {
2238  $info[0] = $data[0];
2239  $info[1] = $data[1];
2240  }
2241  $info[3] = $imagefile;
2242  return $info;
2243  }
2244  $info[0] = $data[0];
2245  $info[1] = $data[1];
2246  $frame = $this->noFramePrepended ? '' : (int)$frame;
2247  if (!$params) {
2248  $params = $this->cmds[$newExt];
2249  }
2250  // Cropscaling:
2251  if ($data['crs']) {
2252  if (!$data['origW']) {
2253  $data['origW'] = $data[0];
2254  }
2255  if (!$data['origH']) {
2256  $data['origH'] = $data[1];
2257  }
2258  $offsetX = (int)(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
2259  $offsetY = (int)(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
2260  $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . '! ';
2261  }
2262  $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
2263  $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
2264  if ($this->alternativeOutputKey) {
2265  $theOutputName = GeneralUtility::shortMD5($command . $cropscale . basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
2266  } else {
2267  $theOutputName = GeneralUtility::shortMD5($command . $cropscale . $imagefile . filemtime($imagefile) . '[' . $frame . ']');
2268  }
2269  if ($this->imageMagickConvert_forceFileNameBody) {
2271  $this->imageMagickConvert_forceFileNameBody = '';
2272  }
2273  // Making the temporary filename:
2274  $this->createTempSubDir('pics/');
2275  $output = $this->absPrefix . $this->tempPath . 'pics/' . $this->filenamePrefix . $theOutputName . '.' . $newExt;
2276  if ($this->dontCheckForExistingTempFile || !file_exists($output)) {
2277  $this->imageMagickExec($imagefile, $output, $command, $frame);
2278  }
2279  if (file_exists($output)) {
2280  $info[3] = $output;
2281  $info[2] = $newExt;
2282  // params might change some image data!
2283  if ($params) {
2284  $info = $this->getImageDimensions($info[3]);
2285  }
2286  if ($info[2] == $this->gifExtension && !$this->dontCompress) {
2287  // Compress with IM (lzw) or GD (rle) (Workaround for the absence of lzw-compression in GD)
2288  self::gifCompress($info[3], '');
2289  }
2290  return $info;
2291  }
2292  }
2293  }
2294  }
2295 
2303  public function getImageDimensions($imageFile)
2304  {
2305  preg_match('/([^\\.]*)$/', $imageFile, $reg);
2306  if (file_exists($imageFile) && GeneralUtility::inList($this->imageFileExt, strtolower($reg[0]))) {
2307  if ($returnArr = $this->getCachedImageDimensions($imageFile)) {
2308  return $returnArr;
2309  } else {
2310  if ($temp = @getimagesize($imageFile)) {
2311  $returnArr = [$temp[0], $temp[1], strtolower($reg[0]), $imageFile];
2312  } else {
2313  $returnArr = $this->imageMagickIdentify($imageFile);
2314  }
2315  if ($returnArr) {
2316  $this->cacheImageDimensions($returnArr);
2317  return $returnArr;
2318  }
2319  }
2320  }
2321  return null;
2322  }
2323 
2331  public function cacheImageDimensions(array $identifyResult)
2332  {
2333  $filePath = $identifyResult[3];
2334  $statusHash = $this->generateStatusHashForImageFile($filePath);
2335  $identifier = $this->generateCacheKeyForImageFile($filePath);
2336 
2338  $cache = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_imagesizes');
2339  $imageDimensions = [
2340  'hash' => $statusHash,
2341  'imagewidth' => $identifyResult[0],
2342  'imageheight' => $identifyResult[1],
2343  ];
2344  $cache->set($identifier, $imageDimensions);
2345 
2346  return true;
2347  }
2348 
2357  public function getCachedImageDimensions($filePath)
2358  {
2359  $statusHash = $this->generateStatusHashForImageFile($filePath);
2360  $identifier = $this->generateCacheKeyForImageFile($filePath);
2362  $cache = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_imagesizes');
2363  $cachedImageDimensions = $cache->get($identifier);
2364  if (!isset($cachedImageDimensions['hash'])) {
2365  return false;
2366  }
2367 
2368  if ($cachedImageDimensions['hash'] !== $statusHash) {
2369  // The file has changed. Delete the cache entry.
2370  $cache->remove($identifier);
2371  $result = false;
2372  } else {
2373  preg_match('/([^\\.]*)$/', $filePath, $imageExtension);
2374  $result = [
2375  (int)$cachedImageDimensions['imagewidth'],
2376  (int)$cachedImageDimensions['imageheight'],
2377  strtolower($imageExtension[0]),
2378  $filePath
2379  ];
2380  }
2381 
2382  return $result;
2383  }
2384 
2394  protected function generateCacheKeyForImageFile($filePath)
2395  {
2396  return sha1($filePath);
2397  }
2398 
2406  protected function generateStatusHashForImageFile($filePath)
2407  {
2408  $fileStatus = stat($filePath);
2409 
2410  return sha1($fileStatus['mtime'] . $fileStatus['size']);
2411  }
2412 
2424  public function getImageScale($info, $w, $h, $options)
2425  {
2426  if (strstr($w . $h, 'm')) {
2427  $max = 1;
2428  } else {
2429  $max = 0;
2430  }
2431  if (strstr($w . $h, 'c')) {
2432  $out['cropH'] = (int)substr(strstr($w, 'c'), 1);
2433  $out['cropV'] = (int)substr(strstr($h, 'c'), 1);
2434  $crs = true;
2435  } else {
2436  $crs = false;
2437  }
2438  $out['crs'] = $crs;
2439  $w = (int)$w;
2440  $h = (int)$h;
2441  // If there are max-values...
2442  if (!empty($options['maxW'])) {
2443  // If width is given...
2444  if ($w) {
2445  if ($w > $options['maxW']) {
2446  $w = $options['maxW'];
2447  // Height should follow
2448  $max = 1;
2449  }
2450  } else {
2451  if ($info[0] > $options['maxW']) {
2452  $w = $options['maxW'];
2453  // Height should follow
2454  $max = 1;
2455  }
2456  }
2457  }
2458  if (!empty($options['maxH'])) {
2459  // If height is given...
2460  if ($h) {
2461  if ($h > $options['maxH']) {
2462  $h = $options['maxH'];
2463  // Height should follow
2464  $max = 1;
2465  }
2466  } else {
2467  // Changed [0] to [1] 290801
2468  if ($info[1] > $options['maxH']) {
2469  $h = $options['maxH'];
2470  // Height should follow
2471  $max = 1;
2472  }
2473  }
2474  }
2475  $out['origW'] = $w;
2476  $out['origH'] = $h;
2477  $out['max'] = $max;
2478  if (!$this->mayScaleUp) {
2479  if ($w > $info[0]) {
2480  $w = $info[0];
2481  }
2482  if ($h > $info[1]) {
2483  $h = $info[1];
2484  }
2485  }
2486  // If scaling should be performed
2487  if ($w || $h) {
2488  if ($w && !$h) {
2489  $info[1] = ceil($info[1] * ($w / $info[0]));
2490  $info[0] = $w;
2491  }
2492  if (!$w && $h) {
2493  $info[0] = ceil($info[0] * ($h / $info[1]));
2494  $info[1] = $h;
2495  }
2496  if ($w && $h) {
2497  if ($max) {
2498  $ratio = $info[0] / $info[1];
2499  if ($h * $ratio > $w) {
2500  $h = round($w / $ratio);
2501  } else {
2502  $w = round($h * $ratio);
2503  }
2504  }
2505  if ($crs) {
2506  $ratio = $info[0] / $info[1];
2507  if ($h * $ratio < $w) {
2508  $h = round($w / $ratio);
2509  } else {
2510  $w = round($h * $ratio);
2511  }
2512  }
2513  $info[0] = $w;
2514  $info[1] = $h;
2515  }
2516  }
2517  $out[0] = $info[0];
2518  $out[1] = $info[1];
2519  // Set minimum-measures!
2520  if (isset($options['minW']) && $out[0] < $options['minW']) {
2521  if (($max || $crs) && $out[0]) {
2522  $out[1] = round($out[1] * $options['minW'] / $out[0]);
2523  }
2524  $out[0] = $options['minW'];
2525  }
2526  if (isset($options['minH']) && $out[1] < $options['minH']) {
2527  if (($max || $crs) && $out[1]) {
2528  $out[0] = round($out[0] * $options['minH'] / $out[1]);
2529  }
2530  $out[1] = $options['minH'];
2531  }
2532  return $out;
2533  }
2534 
2535  /***********************************
2536  *
2537  * ImageMagick API functions
2538  *
2539  ***********************************/
2547  public function imageMagickIdentify($imagefile)
2548  {
2549  if (!$this->NO_IMAGE_MAGICK) {
2550  $frame = $this->noFramePrepended ? '' : '[0]';
2551  $cmd = GeneralUtility::imageMagickCommand('identify', CommandUtility::escapeShellArgument($imagefile) . $frame);
2552  $returnVal = [];
2554  $splitstring = array_pop($returnVal);
2555  $this->IM_commands[] = ['identify', $cmd, $splitstring];
2556  if ($splitstring) {
2557  preg_match('/([^\\.]*)$/', $imagefile, $reg);
2558  $splitinfo = explode(' ', $splitstring);
2559  foreach ($splitinfo as $key => $val) {
2560  $temp = '';
2561  if ($val) {
2562  $temp = explode('x', $val);
2563  }
2564  if ((int)$temp[0] && (int)$temp[1]) {
2565  $dim = $temp;
2566  break;
2567  }
2568  }
2569  if ($dim[0] && $dim[1]) {
2570  return [$dim[0], $dim[1], strtolower($reg[0]), $imagefile];
2571  }
2572  }
2573  }
2574  }
2575 
2586  public function imageMagickExec($input, $output, $params, $frame = 0)
2587  {
2588  if (!$this->NO_IMAGE_MAGICK) {
2589  // Unless noFramePrepended is set in the Install Tool, a frame number is added to
2590  // select a specific page of the image (by default this will be the first page)
2591  if (!$this->noFramePrepended) {
2592  $frame = '[' . (int)$frame . ']';
2593  } else {
2594  $frame = '';
2595  }
2597  $this->IM_commands[] = [$output, $cmd];
2599  // Change the permissions of the file
2601  return $ret;
2602  }
2603  }
2604 
2616  public function combineExec($input, $overlay, $mask, $output, $handleNegation = false)
2617  {
2618  if (!$this->NO_IMAGE_MAGICK) {
2619  $params = '-colorspace GRAY +matte';
2620  $theMask = $this->randomName() . '.' . $this->gifExtension;
2621  $this->imageMagickExec($mask, $theMask, $params);
2622  $cmd = GeneralUtility::imageMagickCommand('combine', '-compose over +matte ' . CommandUtility::escapeShellArgument($input) . ' ' . CommandUtility::escapeShellArgument($overlay) . ' ' . CommandUtility::escapeShellArgument($theMask) . ' ' . CommandUtility::escapeShellArgument($output));
2623  // +matte = no alpha layer in output
2624  $this->IM_commands[] = [$output, $cmd];
2626  // Change the permissions of the file
2628  if (is_file($theMask)) {
2629  @unlink($theMask);
2630  }
2631  return $ret;
2632  }
2633  }
2634 
2653  public static function gifCompress($theFile, $type)
2654  {
2655  $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
2656  if (!$gfxConf['gif_compress'] || strtolower(substr($theFile, -4, 4)) !== '.gif') {
2657  return '';
2658  }
2659 
2660  if (($type === 'IM' || !$type) && $gfxConf['im'] && $gfxConf['im_path_lzw']) {
2661  // Use temporary file to prevent problems with read and write lock on same file on network file systems
2662  $temporaryName = dirname($theFile) . '/' . md5(uniqid('', true)) . '.gif';
2663  // Rename could fail, if a simultaneous thread is currently working on the same thing
2664  if (@rename($theFile, $temporaryName)) {
2665  $cmd = GeneralUtility::imageMagickCommand('convert', '"' . $temporaryName . '" "' . $theFile . '"', $gfxConf['im_path_lzw']);
2666  CommandUtility::exec($cmd);
2667  unlink($temporaryName);
2668  }
2669  $returnCode = 'IM';
2670  if (@is_file($theFile)) {
2672  }
2673  } elseif (($type === 'GD' || !$type) && $gfxConf['gdlib'] && !$gfxConf['gdlib_png']) {
2674  $tempImage = imagecreatefromgif($theFile);
2675  imagegif($tempImage, $theFile);
2676  imagedestroy($tempImage);
2677  $returnCode = 'GD';
2678  if (@is_file($theFile)) {
2680  }
2681  } else {
2682  $returnCode = '';
2683  }
2684 
2685  return $returnCode;
2686  }
2687 
2696  public static function pngToGifByImagemagick($theFile)
2697  {
2699  return $theFile;
2700  }
2701 
2710  public static function readPngGif($theFile, $output_png = false)
2711  {
2712  if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['im'] || !@is_file($theFile)) {
2713  return null;
2714  }
2715 
2716  $ext = strtolower(substr($theFile, -4, 4));
2717  if ((string)$ext == '.png' && $output_png || (string)$ext == '.gif' && !$output_png) {
2718  return $theFile;
2719  }
2720 
2721  if (!@is_dir(PATH_site . 'typo3temp/GraphicalResources/')) {
2722  GeneralUtility::mkdir(PATH_site . 'typo3temp/GraphicalResources/');
2723  }
2724  $newFile = PATH_site . 'typo3temp/GraphicalResources/' . md5($theFile . '|' . filemtime($theFile)) . ($output_png ? '.png' : '.gif');
2726  'convert', '"' . $theFile . '" "' . $newFile . '"', $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path']
2727  );
2728  CommandUtility::exec($cmd);
2729  if (@is_file($newFile)) {
2731  return $newFile;
2732  }
2733  return null;
2734  }
2735 
2736  /***********************************
2737  *
2738  * Various IO functions
2739  *
2740  ***********************************/
2747  public function checkFile($file)
2748  {
2749  if (@is_file($file)) {
2750  return $file;
2751  } else {
2752  return '';
2753  }
2754  }
2755 
2762  public function createTempSubDir($dirName)
2763  {
2764  // Checking if the this->tempPath is already prefixed with PATH_site and if not, prefix it with that constant.
2765  if (GeneralUtility::isFirstPartOfStr($this->tempPath, PATH_site)) {
2766  $tmpPath = $this->tempPath;
2767  } else {
2768  $tmpPath = PATH_site . $this->tempPath;
2769  }
2770  // Making the temporary filename:
2771  if (!@is_dir(($tmpPath . $dirName))) {
2772  return GeneralUtility::mkdir($tmpPath . $dirName);
2773  }
2774  }
2775 
2783  public function applyImageMagickToPHPGif(&$im, $command)
2784  {
2785  $tmpStr = $this->randomName();
2786  $theFile = $tmpStr . '.' . $this->gifExtension;
2787  $this->ImageWrite($im, $theFile);
2788  $this->imageMagickExec($theFile, $theFile, $command);
2789  $tmpImg = $this->imageCreateFromFile($theFile);
2790  if ($tmpImg) {
2791  imagedestroy($im);
2792  $im = $tmpImg;
2793  $this->w = imagesx($im);
2794  $this->h = imagesy($im);
2795  }
2796  if (!$this->dontUnlinkTempFiles) {
2797  unlink($theFile);
2798  }
2799  }
2800 
2810  public function gif_or_jpg($type, $w, $h)
2811  {
2812  if ($type == 'ai' || $w * $h < $this->pixelLimitGif) {
2813  return $this->gifExtension;
2814  } else {
2815  return 'jpg';
2816  }
2817  }
2818 
2828  public function output($file)
2829  {
2830  if ($file) {
2831  $reg = [];
2832  preg_match('/([^\\.]*)$/', $file, $reg);
2833  $ext = strtolower($reg[0]);
2834  switch ($ext) {
2835  case 'gif':
2836 
2837  case 'png':
2838  if ($this->ImageWrite($this->im, $file)) {
2839  // ImageMagick operations
2840  if ($this->setup['reduceColors'] || !$this->png_truecolor) {
2841  $reduced = $this->IMreduceColors($file, MathUtility::forceIntegerInRange($this->setup['reduceColors'], 256, $this->truecolorColors, 256));
2842  if ($reduced) {
2843  @copy($reduced, $file);
2844  @unlink($reduced);
2845  }
2846  }
2847  // Compress with IM! (adds extra compression, LZW from ImageMagick)
2848  // (Workaround for the absence of lzw-compression in GD)
2849  self::gifCompress($file, 'IM');
2850  }
2851  break;
2852  case 'jpg':
2853 
2854  case 'jpeg':
2855  // Use the default
2856  $quality = 0;
2857  if ($this->setup['quality']) {
2858  $quality = MathUtility::forceIntegerInRange($this->setup['quality'], 10, 100);
2859  }
2860  if ($this->ImageWrite($this->im, $file, $quality)) {
2861  }
2862  break;
2863  }
2864  }
2865  return $file;
2866  }
2867 
2874  public function destroy()
2875  {
2876  imagedestroy($this->im);
2877  }
2878 
2885  public function imgTag($imgInfo)
2886  {
2887  return '<img src="' . $imgInfo[3] . '" width="' . $imgInfo[0] . '" height="' . $imgInfo[1] . '" border="0" alt="" />';
2888  }
2889 
2899  public function ImageWrite($destImg, $theImage, $quality = 0)
2900  {
2901  imageinterlace($destImg, 0);
2902  $ext = strtolower(substr($theImage, strrpos($theImage, '.') + 1));
2903  $result = false;
2904  switch ($ext) {
2905  case 'jpg':
2906 
2907  case 'jpeg':
2908  if (function_exists('imageJpeg')) {
2909  if ($quality == 0) {
2910  $quality = $this->jpegQuality;
2911  }
2912  $result = imagejpeg($destImg, $theImage, $quality);
2913  }
2914  break;
2915  case 'gif':
2916  if (function_exists('imageGif')) {
2917  imagetruecolortopalette($destImg, true, 256);
2918  $result = imagegif($destImg, $theImage);
2919  }
2920  break;
2921  case 'png':
2922  if (function_exists('imagePng')) {
2923  $result = imagepng($destImg, $theImage);
2924  }
2925  break;
2926  }
2927  if ($result) {
2928  GeneralUtility::fixPermissions($theImage);
2929  }
2930  return $result;
2931  }
2932 
2940  public function imageCreateFromFile($sourceImg)
2941  {
2942  $imgInf = pathinfo($sourceImg);
2943  $ext = strtolower($imgInf['extension']);
2944  switch ($ext) {
2945  case 'gif':
2946  if (function_exists('imagecreatefromgif')) {
2947  return imagecreatefromgif($sourceImg);
2948  }
2949  break;
2950  case 'png':
2951  if (function_exists('imagecreatefrompng')) {
2952  $imageHandle = imagecreatefrompng($sourceImg);
2953  if ($this->saveAlphaLayer) {
2954  imagesavealpha($imageHandle, true);
2955  }
2956  return $imageHandle;
2957  }
2958  break;
2959  case 'jpg':
2960 
2961  case 'jpeg':
2962  if (function_exists('imagecreatefromjpeg')) {
2963  return imagecreatefromjpeg($sourceImg);
2964  }
2965  break;
2966  }
2967  // If non of the above:
2968  $i = @getimagesize($sourceImg);
2969  $im = imagecreatetruecolor($i[0], $i[1]);
2970  $Bcolor = imagecolorallocate($im, 128, 128, 128);
2971  imagefilledrectangle($im, 0, 0, $i[0], $i[1], $Bcolor);
2972  return $im;
2973  }
2974 
2981  public function hexColor($col)
2982  {
2983  $r = dechex($col[0]);
2984  if (strlen($r) < 2) {
2985  $r = '0' . $r;
2986  }
2987  $g = dechex($col[1]);
2988  if (strlen($g) < 2) {
2989  $g = '0' . $g;
2990  }
2991  $b = dechex($col[2]);
2992  if (strlen($b) < 2) {
2993  $b = '0' . $b;
2994  }
2995  return '#' . $r . $g . $b;
2996  }
2997 
3006  public function unifyColors(&$img, $colArr, $closest = false)
3007  {
3008  $retCol = -1;
3009  if (is_array($colArr) && !empty($colArr) && function_exists('imagepng') && function_exists('imagecreatefrompng')) {
3010  $firstCol = array_shift($colArr);
3011  $firstColArr = $this->convertColor($firstCol);
3012  if (count($colArr) > 1) {
3013  $origName = ($preName = $this->randomName() . '.png');
3014  $postName = $this->randomName() . '.png';
3015  $this->imageWrite($img, $preName);
3016  $firstCol = $this->hexColor($firstColArr);
3017  foreach ($colArr as $transparentColor) {
3018  $transparentColor = $this->convertColor($transparentColor);
3019  $transparentColor = $this->hexColor($transparentColor);
3020  $cmd = '-fill "' . $firstCol . '" -opaque "' . $transparentColor . '"';
3021  $this->imageMagickExec($preName, $postName, $cmd);
3022  $preName = $postName;
3023  }
3024  $this->imageMagickExec($postName, $origName, '');
3025  if (@is_file($origName)) {
3026  $tmpImg = $this->imageCreateFromFile($origName);
3027  }
3028  } else {
3029  $tmpImg = $img;
3030  }
3031  if ($tmpImg) {
3032  $img = $tmpImg;
3033  if ($closest) {
3034  $retCol = imagecolorclosest($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
3035  } else {
3036  $retCol = imagecolorexact($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
3037  }
3038  }
3039  // Unlink files from process
3040  if (!$this->dontUnlinkTempFiles) {
3041  if ($origName) {
3042  @unlink($origName);
3043  }
3044  if ($postName) {
3045  @unlink($postName);
3046  }
3047  }
3048  }
3049  return $retCol;
3050  }
3051 
3064  public function getTemporaryImageWithText($filename, $textline1, $textline2, $textline3)
3065  {
3066  if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'])) {
3067  throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
3068  }
3069  // Creates the basis for the error image
3070  $basePath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('core') . 'Resources/Public/Images/';
3071  if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
3072  $im = imagecreatefrompng($basePath . 'NotFound.png');
3073  } else {
3074  $im = imagecreatefromgif($basePath . 'NotFound.gif');
3075  }
3076  // Sets background color and print color.
3077  $white = imagecolorallocate($im, 255, 255, 255);
3078  $black = imagecolorallocate($im, 0, 0, 0);
3079  // Prints the text strings with the build-in font functions of GD
3080  $x = 0;
3081  $font = 0;
3082  if ($textline1) {
3083  imagefilledrectangle($im, $x, 9, 56, 16, $white);
3084  imagestring($im, $font, $x, 9, $textline1, $black);
3085  }
3086  if ($textline2) {
3087  imagefilledrectangle($im, $x, 19, 56, 26, $white);
3088  imagestring($im, $font, $x, 19, $textline2, $black);
3089  }
3090  if ($textline3) {
3091  imagefilledrectangle($im, $x, 29, 56, 36, $white);
3092  imagestring($im, $font, $x, 29, substr($textline3, -14), $black);
3093  }
3094  // Outputting the image stream and exit
3095  if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
3096  imagepng($im, $filename);
3097  } else {
3098  imagegif($im, $filename);
3099  }
3100  }
3101 }
static imageMagickCommand($command, $parameters, $path='')
copyGifOntoGif(&$im, $cpImg, $conf, $workArea)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
unifyColors(&$img, $colArr, $closest=false)
makeEmboss(&$im, $conf, $workArea, $txtConf)
imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight)
SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF=1)
debug($variable='', $name=' *variable *', $line=' *line *', $file=' *file *', $recursiveDepth=3, $debugLevel='E_DEBUG')
static isFirstPartOfStr($str, $partStr)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
makeEllipse(&$im, array $conf, array $workArea)
getBreakSpace($conf, array $boundingBox=null)
imageMagickExec($input, $output, $params, $frame=0)
static sortedKeyList($setupArr, $acceptOnlyProperties=false)
static exec($command, &$output=null, &$returnValue=0)
combineExec($input, $overlay, $mask, $output, $handleNegation=false)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF=1)
static split_fileref($fileNameWithPath)
makeShadow(&$im, $conf, $workArea, $txtConf)
makeOutline(&$im, $conf, $workArea, $txtConf)
static fixPermissions($path, $recursive=false)
ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF=1)
getTemporaryImageWithText($filename, $textline1, $textline2, $textline3)
singleChars($theText, $returnUnicodeNumber=false)
static readPngGif($theFile, $output_png=false)
renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF=1)
ImageWrite($destImg, $theImage, $quality=0)
imageMagickConvert($imagefile, $newExt='', $w='', $h='', $params='', $frame='', $options=[], $mustCreate=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
splitString($string, $splitRendering, $fontSize, $fontFile)