TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
24 
32 {
38  public $addFrameSelection = true;
39 
45  public $gifExtension = 'gif';
46 
52  public $gdlibExtensions = '';
53 
59  public $png_truecolor = false;
60 
66  protected $colorspace = 'RGB';
67 
74  'CMY',
75  'CMYK',
76  'Gray',
77  'HCL',
78  'HSB',
79  'HSL',
80  'HWB',
81  'Lab',
82  'LCH',
83  'LMS',
84  'Log',
85  'Luv',
86  'OHTA',
87  'Rec601Luma',
88  'Rec601YCbCr',
89  'Rec709Luma',
90  'Rec709YCbCr',
91  'RGB',
92  'sRGB',
93  'Transparent',
94  'XYZ',
95  'YCbCr',
96  'YCC',
97  'YIQ',
98  'YCbCr',
99  'YUV'
100  ];
101 
107  public $truecolorColors = 16777215;
108 
115  public $imageFileExt = 'gif,jpg,jpeg,png,tif,bmp,tga,pcx,ai,pdf';
116 
122  public $webImageExt = 'gif,jpg,jpeg,png';
123 
127  public $NO_IM_EFFECTS = '';
128 
132  public $cmds = [
133  'jpg' => '',
134  'jpeg' => '',
135  'gif' => '',
136  'png' => '-colors 64'
137  ];
138 
142  public $NO_IMAGE_MAGICK = '';
143 
147  public $V5_EFFECTS = 0;
148 
152  public $mayScaleUp = 1;
153 
159  public $filenamePrefix = '';
160 
167 
174 
180  public $dontCompress = 0;
181 
188 
197 
203  public $IM_commands = [];
204 
208  public $workArea = [];
209 
215  protected $saveAlphaLayer = false;
216 
222  public $tempPath = 'typo3temp/';
223 
229  public $absPrefix = '';
230 
236  public $scalecmd = '-geometry';
237 
243  public $im5fx_blurSteps = '1x2,2x2,3x2,4x3,5x3,5x4,6x4,7x5,8x5,9x5';
244 
250  public $im5fx_sharpenSteps = '1x2,2x2,3x2,2x3,3x3,4x3,3x4,4x4,4x5,5x5';
251 
257  public $pixelLimitGif = 10000;
258 
264  public $colMap = [
265  'aqua' => [0, 255, 255],
266  'black' => [0, 0, 0],
267  'blue' => [0, 0, 255],
268  'fuchsia' => [255, 0, 255],
269  'gray' => [128, 128, 128],
270  'green' => [0, 128, 0],
271  'lime' => [0, 255, 0],
272  'maroon' => [128, 0, 0],
273  'navy' => [0, 0, 128],
274  'olive' => [128, 128, 0],
275  'purple' => [128, 0, 128],
276  'red' => [255, 0, 0],
277  'silver' => [192, 192, 192],
278  'teal' => [0, 128, 128],
279  'yellow' => [255, 255, 0],
280  'white' => [255, 255, 255]
281  ];
282 
288  public $csConvObj;
289 
293  public $jpegQuality = 75;
294 
298  public $map = '';
299 
306  public $setup = [];
307 
311  public $w = 0;
312 
316  public $h = 0;
317 
321  public $OFFSET;
322 
326  protected $im;
327 
334  public function init()
335  {
336  $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
337  if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
338  $this->gdlibExtensions .= ',jpg,jpeg';
339  }
340  if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
341  $this->gdlibExtensions .= ',png';
342  }
343  if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
344  $this->gdlibExtensions .= ',gif';
345  }
346  if ($gfxConf['png_truecolor']) {
347  $this->png_truecolor = true;
348  }
349 
350  if ($gfxConf['processor_colorspace'] && in_array($gfxConf['processor_colorspace'], $this->allowedColorSpaceNames, true)) {
351  $this->colorspace = $gfxConf['processor_colorspace'];
352  }
353 
354  if (!$gfxConf['processor_enabled']) {
355  $this->NO_IMAGE_MAGICK = 1;
356  }
357  // When GIFBUILDER gets used in truecolor mode
358  // No colors parameter if we generate truecolor images.
359  if ($this->png_truecolor) {
360  $this->cmds['png'] = '';
361  }
362  // Setting default JPG parameters:
363  $this->jpegQuality = MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, 75);
364  $this->addFrameSelection = (bool)$gfxConf['processor_allowFrameSelection'];
365  if ($gfxConf['gdlib_png']) {
366  $this->gifExtension = 'png';
367  }
368  $this->imageFileExt = $gfxConf['imagefile_ext'];
369 
370  // Boolean. This is necessary if using ImageMagick 5+.
371  // Effects in Imagemagick 5+ tends to render very slowly!!
372  // - therefore must be disabled in order not to perform sharpen, blurring and such.
373  $this->NO_IM_EFFECTS = 1;
374  $this->cmds['jpg'] = $this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . $this->jpegQuality;
375 
376  // ... but if 'processor_effects' is set, enable effects
377  if ($gfxConf['processor_effects']) {
378  $this->NO_IM_EFFECTS = 0;
379  $this->V5_EFFECTS = 1;
380  if ($gfxConf['processor_effects'] > 0) {
381  $this->cmds['jpg'] = $this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . (int)$gfxConf['jpg_quality'] . $this->v5_sharpen(10);
382  }
383  }
384  // Secures that images are not scaled up.
385  if (!$gfxConf['processor_allowUpscaling']) {
386  $this->mayScaleUp = 0;
387  }
388  $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
389  }
390 
391  /*************************************************
392  *
393  * Layering images / "IMAGE" GIFBUILDER object
394  *
395  *************************************************/
407  public function maskImageOntoImage(&$im, $conf, $workArea)
408  {
409  if ($conf['file'] && $conf['mask']) {
410  $imgInf = pathinfo($conf['file']);
411  $imgExt = strtolower($imgInf['extension']);
412  if (!GeneralUtility::inList($this->gdlibExtensions, $imgExt)) {
413  $BBimage = $this->imageMagickConvert($conf['file'], $this->gifExtension);
414  } else {
415  $BBimage = $this->getImageDimensions($conf['file']);
416  }
417  $maskInf = pathinfo($conf['mask']);
418  $maskExt = strtolower($maskInf['extension']);
419  if (!GeneralUtility::inList($this->gdlibExtensions, $maskExt)) {
420  $BBmask = $this->imageMagickConvert($conf['mask'], $this->gifExtension);
421  } else {
422  $BBmask = $this->getImageDimensions($conf['mask']);
423  }
424  if ($BBimage && $BBmask) {
425  $w = imagesx($im);
426  $h = imagesy($im);
427  $tmpStr = $this->randomName();
428  $theImage = $tmpStr . '_img.' . $this->gifExtension;
429  $theDest = $tmpStr . '_dest.' . $this->gifExtension;
430  $theMask = $tmpStr . '_mask.' . $this->gifExtension;
431  // Prepare overlay image
432  $cpImg = $this->imageCreateFromFile($BBimage[3]);
433  $destImg = imagecreatetruecolor($w, $h);
434  // Preserve alpha transparency
435  if ($this->saveAlphaLayer) {
436  imagesavealpha($destImg, true);
437  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
438  imagefill($destImg, 0, 0, $Bcolor);
439  } else {
440  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
441  imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
442  }
443  $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
444  $this->ImageWrite($destImg, $theImage);
445  imagedestroy($cpImg);
446  imagedestroy($destImg);
447  // Prepare mask image
448  $cpImg = $this->imageCreateFromFile($BBmask[3]);
449  $destImg = imagecreatetruecolor($w, $h);
450  if ($this->saveAlphaLayer) {
451  imagesavealpha($destImg, true);
452  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
453  imagefill($destImg, 0, 0, $Bcolor);
454  } else {
455  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
456  imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
457  }
458  $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
459  $this->ImageWrite($destImg, $theMask);
460  imagedestroy($cpImg);
461  imagedestroy($destImg);
462  // Mask the images
463  $this->ImageWrite($im, $theDest);
464  // Let combineExec handle maskNegation
465  $this->combineExec($theDest, $theImage, $theMask, $theDest);
466  // The main image is loaded again...
467  $backIm = $this->imageCreateFromFile($theDest);
468  // ... and if nothing went wrong we load it onto the old one.
469  if ($backIm) {
470  if (!$this->saveAlphaLayer) {
471  imagecolortransparent($backIm, -1);
472  }
473  $im = $backIm;
474  }
475  // Unlink files from process
476  if (!$this->dontUnlinkTempFiles) {
477  unlink($theDest);
478  unlink($theImage);
479  unlink($theMask);
480  }
481  }
482  }
483  }
484 
494  public function copyImageOntoImage(&$im, $conf, $workArea)
495  {
496  if ($conf['file']) {
497  if (!GeneralUtility::inList($this->gdlibExtensions, $conf['BBOX'][2])) {
498  $conf['BBOX'] = $this->imageMagickConvert($conf['BBOX'][3], $this->gifExtension);
499  $conf['file'] = $conf['BBOX'][3];
500  }
501  $cpImg = $this->imageCreateFromFile($conf['file']);
502  $this->copyGifOntoGif($im, $cpImg, $conf, $workArea);
503  imagedestroy($cpImg);
504  }
505  }
506 
517  public function copyGifOntoGif(&$im, $cpImg, $conf, $workArea)
518  {
519  $cpW = imagesx($cpImg);
520  $cpH = imagesy($cpImg);
521  $tile = GeneralUtility::intExplode(',', $conf['tile']);
522  $tile[0] = MathUtility::forceIntegerInRange($tile[0], 1, 20);
523  $tile[1] = MathUtility::forceIntegerInRange($tile[1], 1, 20);
524  $cpOff = $this->objPosition($conf, $workArea, [$cpW * $tile[0], $cpH * $tile[1]]);
525  for ($xt = 0; $xt < $tile[0]; $xt++) {
526  $Xstart = $cpOff[0] + $cpW * $xt;
527  // If this image is inside of the workArea, then go on
528  if ($Xstart + $cpW > $workArea[0]) {
529  // X:
530  if ($Xstart < $workArea[0]) {
531  $cpImgCutX = $workArea[0] - $Xstart;
532  $Xstart = $workArea[0];
533  } else {
534  $cpImgCutX = 0;
535  }
536  $w = $cpW - $cpImgCutX;
537  if ($Xstart > $workArea[0] + $workArea[2] - $w) {
538  $w = $workArea[0] + $workArea[2] - $Xstart;
539  }
540  // If this image is inside of the workArea, then go on
541  if ($Xstart < $workArea[0] + $workArea[2]) {
542  // Y:
543  for ($yt = 0; $yt < $tile[1]; $yt++) {
544  $Ystart = $cpOff[1] + $cpH * $yt;
545  // If this image is inside of the workArea, then go on
546  if ($Ystart + $cpH > $workArea[1]) {
547  if ($Ystart < $workArea[1]) {
548  $cpImgCutY = $workArea[1] - $Ystart;
549  $Ystart = $workArea[1];
550  } else {
551  $cpImgCutY = 0;
552  }
553  $h = $cpH - $cpImgCutY;
554  if ($Ystart > $workArea[1] + $workArea[3] - $h) {
555  $h = $workArea[1] + $workArea[3] - $Ystart;
556  }
557  // If this image is inside of the workArea, then go on
558  if ($Ystart < $workArea[1] + $workArea[3]) {
559  $this->imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, $w, $h, $w, $h);
560  }
561  }
562  }
563  }
564  }
565  }
566  }
567 
597  public function imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight)
598  {
599  if (!$this->saveAlphaLayer) {
600  // Make true color image
601  $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
602  // Copy the source image onto that
603  imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
604  // Then copy the source image onto that (the actual operation!)
605  imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
606  // Set the destination image
607  $dstImg = $tmpImg;
608  } else {
609  imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
610  }
611  }
612 
613  /********************************
614  *
615  * Text / "TEXT" GIFBUILDER object
616  *
617  ********************************/
627  public function makeText(&$im, $conf, $workArea)
628  {
629  // Spacing
630  list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
631  // Position
632  $txtPos = $this->txtPosition($conf, $workArea, $conf['BBOX']);
633  $theText = $conf['text'];
634  if ($conf['imgMap'] && is_array($conf['imgMap.'])) {
635  $this->addToMap($this->calcTextCordsForMap($conf['BBOX'][2], $txtPos, $conf['imgMap.']), $conf['imgMap.']);
636  }
637  if (!$conf['hideButCreateMap']) {
638  // Font Color:
639  $cols = $this->convertColor($conf['fontColor']);
640  // NiceText is calculated
641  if (!$conf['niceText']) {
642  $Fcolor = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
643  // antiAliasing is setup:
644  $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
645  for ($a = 0; $a < $conf['iterations']; $a++) {
646  // If any kind of spacing applys, we use this function:
647  if ($spacing || $wordSpacing) {
648  $this->SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
649  } else {
650  $this->renderTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf);
651  }
652  }
653  } else {
654  // NICETEXT::
655  // options anti_aliased and iterations is NOT available when doing this!!
656  $w = imagesx($im);
657  $h = imagesy($im);
658  $tmpStr = $this->randomName();
659  $fileMenu = $tmpStr . '_menuNT.' . $this->gifExtension;
660  $fileColor = $tmpStr . '_colorNT.' . $this->gifExtension;
661  $fileMask = $tmpStr . '_maskNT.' . $this->gifExtension;
662  // Scalefactor
663  $sF = MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
664  $newW = ceil($sF * imagesx($im));
665  $newH = ceil($sF * imagesy($im));
666  // Make mask
667  $maskImg = imagecreatetruecolor($newW, $newH);
668  $Bcolor = imagecolorallocate($maskImg, 255, 255, 255);
669  imagefilledrectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
670  $Fcolor = imagecolorallocate($maskImg, 0, 0, 0);
671  // If any kind of spacing applies, we use this function:
672  if ($spacing || $wordSpacing) {
673  $this->SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
674  } else {
675  $this->renderTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf, $sF);
676  }
677  $this->ImageWrite($maskImg, $fileMask);
678  imagedestroy($maskImg);
679  // Downscales the mask
680  if ($this->NO_IM_EFFECTS) {
681  $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '! -negate');
682  } else {
683  $command = trim($conf['niceText.']['before'] . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . $conf['niceText.']['after'] . ' -negate');
684  if ($conf['niceText.']['sharpen']) {
685  if ($this->V5_EFFECTS) {
686  $command .= $this->v5_sharpen($conf['niceText.']['sharpen']);
687  } else {
688  $command .= ' -sharpen ' . MathUtility::forceIntegerInRange($conf['niceText.']['sharpen'], 1, 99);
689  }
690  }
691  }
692  $this->imageMagickExec($fileMask, $fileMask, $command);
693  // Make the color-file
694  $colorImg = imagecreatetruecolor($w, $h);
695  $Ccolor = imagecolorallocate($colorImg, $cols[0], $cols[1], $cols[2]);
696  imagefilledrectangle($colorImg, 0, 0, $w, $h, $Ccolor);
697  $this->ImageWrite($colorImg, $fileColor);
698  imagedestroy($colorImg);
699  // The mask is applied
700  // The main pictures is saved temporarily
701  $this->ImageWrite($im, $fileMenu);
702  $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
703  // The main image is loaded again...
704  $backIm = $this->imageCreateFromFile($fileMenu);
705  // ... and if nothing went wrong we load it onto the old one.
706  if ($backIm) {
707  if (!$this->saveAlphaLayer) {
708  imagecolortransparent($backIm, -1);
709  }
710  $im = $backIm;
711  }
712  // Deleting temporary files;
713  if (!$this->dontUnlinkTempFiles) {
714  unlink($fileMenu);
715  unlink($fileColor);
716  unlink($fileMask);
717  }
718  }
719  }
720  }
721 
732  public function txtPosition($conf, $workArea, $BB)
733  {
734  $angle = (int)$conf['angle'] / 180 * pi();
735  $conf['angle'] = 0;
736  $straightBB = $this->calcBBox($conf);
737  // offset, align, valign, workarea
738  // [0]=x, [1]=y, [2]=w, [3]=h
739  $result = [];
740  $result[2] = $BB[0];
741  $result[3] = $BB[1];
742  $w = $workArea[2];
743  switch ($conf['align']) {
744  case 'right':
745 
746  case 'center':
747  $factor = abs(cos($angle));
748  $sign = cos($angle) < 0 ? -1 : 1;
749  $len1 = $sign * $factor * $straightBB[0];
750  $len2 = $sign * $BB[0];
751  $result[0] = $w - ceil(($len2 * $factor + (1 - $factor) * $len1));
752  $factor = abs(sin($angle));
753  $sign = sin($angle) < 0 ? -1 : 1;
754  $len1 = $sign * $factor * $straightBB[0];
755  $len2 = $sign * $BB[1];
756  $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
757  break;
758  }
759  switch ($conf['align']) {
760  case 'right':
761  break;
762  case 'center':
763  $result[0] = round($result[0] / 2);
764  $result[1] = round($result[1] / 2);
765  break;
766  default:
767  $result[0] = 0;
768  $result[1] = 0;
769  }
770  $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
771  $result = $this->applyOffset($result, $workArea);
772  return $result;
773  }
774 
783  public function calcBBox($conf)
784  {
785  $sF = $this->getTextScalFactor($conf);
786  list($spacing, $wordSpacing) = $this->calcWordSpacing($conf, $sF);
787  $theText = $conf['text'];
788  $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $theText, $conf['splitRendering.'], $sF);
789  $theBBoxInfo = $charInf;
790  if ($conf['angle']) {
791  $xArr = [$charInf[0], $charInf[2], $charInf[4], $charInf[6]];
792  $yArr = [$charInf[1], $charInf[3], $charInf[5], $charInf[7]];
793  $x = max($xArr) - min($xArr);
794  $y = max($yArr) - min($yArr);
795  } else {
796  $x = $charInf[2] - $charInf[0];
797  $y = $charInf[1] - $charInf[7];
798  }
799  // Set original lineHeight (used by line breaks):
800  $theBBoxInfo['lineHeight'] = $y;
801  // If any kind of spacing applys, we use this function:
802  if ($spacing || $wordSpacing) {
803  $x = 0;
804  if (!$spacing && $wordSpacing) {
805  $bits = explode(' ', $theText);
806  foreach ($bits as $word) {
807  $word .= ' ';
808  $wordInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
809  $wordW = $wordInf[2] - $wordInf[0];
810  $x += $wordW + $wordSpacing;
811  }
812  } else {
813  $utf8Chars = $this->csConvObj->utf8_to_numberarray($theText);
814  // For each UTF-8 char, do:
815  foreach ($utf8Chars as $char) {
816  $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
817  $charW = $charInf[2] - $charInf[0];
818  $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
819  }
820  }
821  } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
822  $maxWidth = 0;
823  $currentWidth = 0;
824  $breakWidth = $conf['breakWidth'];
825  $breakSpace = $this->getBreakSpace($conf, $theBBoxInfo);
826  $wordPairs = $this->getWordPairsForLineBreak($conf['text']);
827  // Iterate through all word pairs:
828  foreach ($wordPairs as $index => $wordPair) {
829  $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
830  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
831  $currentWidth += $wordWidth;
832  } else {
833  $maxWidth = max($maxWidth, $currentWidth);
834  $y += $breakSpace;
835  // Restart:
836  $currentWidth = $wordWidth;
837  }
838  }
839  $x = max($maxWidth, $currentWidth) * $sF;
840  }
841  if ($sF > 1) {
842  $x = ceil($x / $sF);
843  $y = ceil($y / $sF);
844  if (is_array($theBBoxInfo)) {
845  foreach ($theBBoxInfo as &$value) {
846  $value = ceil($value / $sF);
847  }
848  unset($value);
849  }
850  }
851  return [$x, $y, $theBBoxInfo];
852  }
853 
863  public function addToMap($cords, $conf)
864  {
865  $this->map .= '<area' . ' shape="poly"' . ' coords="' . implode(',', $cords) . '"'
866  . ' href="' . htmlspecialchars($conf['url']) . '"'
867  . ($conf['target'] ? ' target="' . htmlspecialchars($conf['target']) . '"' : '')
868  . ((string)$conf['titleText'] !== '' ? ' title="' . htmlspecialchars($conf['titleText']) . '"' : '')
869  . ' alt="' . htmlspecialchars($conf['altText']) . '" />';
870  }
871 
882  public function calcTextCordsForMap($cords, $offset, $conf)
883  {
884  $pars = GeneralUtility::intExplode(',', $conf['explode'] . ',');
885  $newCords[0] = $cords[0] + $offset[0] - $pars[0];
886  $newCords[1] = $cords[1] + $offset[1] + $pars[1];
887  $newCords[2] = $cords[2] + $offset[0] + $pars[0];
888  $newCords[3] = $cords[3] + $offset[1] + $pars[1];
889  $newCords[4] = $cords[4] + $offset[0] + $pars[0];
890  $newCords[5] = $cords[5] + $offset[1] - $pars[1];
891  $newCords[6] = $cords[6] + $offset[0] - $pars[0];
892  $newCords[7] = $cords[7] + $offset[1] - $pars[1];
893  return $newCords;
894  }
895 
916  public function SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF = 1)
917  {
918  $spacing *= $sF;
919  $wordSpacing *= $sF;
920  if (!$spacing && $wordSpacing) {
921  $bits = explode(' ', $text);
922  foreach ($bits as $word) {
923  $word .= ' ';
924  $wordInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
925  $wordW = $wordInf[2] - $wordInf[0];
926  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
927  $x += $wordW + $wordSpacing;
928  }
929  } else {
930  $utf8Chars = $this->csConvObj->utf8_to_numberarray($text);
931  // For each UTF-8 char, do:
932  foreach ($utf8Chars as $char) {
933  $charInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
934  $charW = $charInf[2] - $charInf[0];
935  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
936  $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
937  }
938  }
939  }
940 
949  public function fontResize($conf)
950  {
951  // 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!!!!
952  $maxWidth = (int)$conf['maxWidth'];
953  list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
954  if ($maxWidth) {
955  // If any kind of spacing applys, we use this function:
956  if ($spacing || $wordSpacing) {
957  return $conf['fontSize'];
958  } else {
959  do {
960  // Determine bounding box.
961  $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $conf['text'], $conf['splitRendering.']);
962  if ($conf['angle'] < 0) {
963  $pixelWidth = abs($bounds[4] - $bounds[0]);
964  } elseif ($conf['angle'] > 0) {
965  $pixelWidth = abs($bounds[2] - $bounds[6]);
966  } else {
967  $pixelWidth = abs($bounds[4] - $bounds[6]);
968  }
969  // Size is fine, exit:
970  if ($pixelWidth <= $maxWidth) {
971  break;
972  } else {
973  $conf['fontSize']--;
974  }
975  } while ($conf['fontSize'] > 1);
976  }
977  }
978  return $conf['fontSize'];
979  }
980 
992  public function ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF = 1)
993  {
994  // Initialize:
995  $offsetInfo = [];
996  $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
997  // Traverse string parts:
998  foreach ($stringParts as $strCfg) {
999  $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
1000  if (is_readable($fontFile)) {
1008  $try = 0;
1009  do {
1010  $calc = imagettfbbox(GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
1011  } while ($calc[2] < 0 && $try++ < 10);
1012  // Calculate offsets:
1013  if (empty($offsetInfo)) {
1014  // First run, just copy over.
1015  $offsetInfo = $calc;
1016  } else {
1017  $offsetInfo[2] += $calc[2] - $calc[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
1018  $offsetInfo[3] += $calc[3] - $calc[1] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
1019  $offsetInfo[4] += $calc[4] - $calc[6] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
1020  $offsetInfo[5] += $calc[5] - $calc[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
1021  }
1022  } else {
1023  debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFBBoxWrapper()');
1024  }
1025  }
1026  return $offsetInfo;
1027  }
1028 
1044  public function ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF = 1)
1045  {
1046  // Initialize:
1047  $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
1048  $x = ceil($sF * $x);
1049  $y = ceil($sF * $y);
1050  // Traverse string parts:
1051  foreach ($stringParts as $i => $strCfg) {
1052  // Initialize:
1053  $colorIndex = $color;
1054  // Set custom color if any (only when niceText is off):
1055  if ($strCfg['color'] && $sF == 1) {
1056  $cols = $this->convertColor($strCfg['color']);
1057  $colorIndex = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
1058  $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
1059  }
1060  // Setting xSpaceBefore
1061  if ($i) {
1062  $x += (int)$strCfg['xSpaceBefore'];
1063  $y -= (int)$strCfg['ySpaceBefore'];
1064  }
1065  $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
1066  if (is_readable($fontFile)) {
1067  // Render part:
1068  imagettftext($im, GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
1069  // Calculate offset to apply:
1070  $wordInf = imagettfbbox(GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, GeneralUtility::getFileAbsFileName($strCfg['fontFile']), $strCfg['str']);
1071  $x += $wordInf[2] - $wordInf[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceAfter'];
1072  $y += $wordInf[5] - $wordInf[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceAfter'];
1073  } else {
1074  debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFTextWrapper()');
1075  }
1076  }
1077  }
1078 
1088  public function splitString($string, $splitRendering, $fontSize, $fontFile)
1089  {
1090  // Initialize by setting the whole string and default configuration as the first entry.
1091  $result = [];
1092  $result[] = [
1093  'str' => $string,
1094  'fontSize' => $fontSize,
1095  'fontFile' => $fontFile
1096  ];
1097  // Traverse the split-rendering configuration:
1098  // Splitting will create more entries in $result with individual configurations.
1099  if (is_array($splitRendering)) {
1100  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($splitRendering);
1101  // Traverse configured options:
1102  foreach ($sKeyArray as $key) {
1103  $cfg = $splitRendering[$key . '.'];
1104  // Process each type of split rendering keyword:
1105  switch ((string)$splitRendering[$key]) {
1106  case 'highlightWord':
1107  if ((string)$cfg['value'] !== '') {
1108  $newResult = [];
1109  // Traverse the current parts of the result array:
1110  foreach ($result as $part) {
1111  // Explode the string value by the word value to highlight:
1112  $explodedParts = explode($cfg['value'], $part['str']);
1113  foreach ($explodedParts as $c => $expValue) {
1114  if ((string)$expValue !== '') {
1115  $newResult[] = array_merge($part, ['str' => $expValue]);
1116  }
1117  if ($c + 1 < count($explodedParts)) {
1118  $newResult[] = [
1119  'str' => $cfg['value'],
1120  'fontSize' => $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1121  'fontFile' => $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1122  'color' => $cfg['color'],
1123  'xSpaceBefore' => $cfg['xSpaceBefore'],
1124  'xSpaceAfter' => $cfg['xSpaceAfter'],
1125  'ySpaceBefore' => $cfg['ySpaceBefore'],
1126  'ySpaceAfter' => $cfg['ySpaceAfter']
1127  ];
1128  }
1129  }
1130  }
1131  // Set the new result as result array:
1132  if (!empty($newResult)) {
1133  $result = $newResult;
1134  }
1135  }
1136  break;
1137  case 'charRange':
1138  if ((string)$cfg['value'] !== '') {
1139  // Initialize range:
1140  $ranges = GeneralUtility::trimExplode(',', $cfg['value'], true);
1141  foreach ($ranges as $i => $rangeDef) {
1142  $ranges[$i] = GeneralUtility::intExplode('-', $ranges[$i]);
1143  if (!isset($ranges[$i][1])) {
1144  $ranges[$i][1] = $ranges[$i][0];
1145  }
1146  }
1147  $newResult = [];
1148  // Traverse the current parts of the result array:
1149  foreach ($result as $part) {
1150  // Initialize:
1151  $currentState = -1;
1152  $bankAccum = '';
1153  // Explode the string value by the word value to highlight:
1154  $utf8Chars = $this->csConvObj->utf8_to_numberarray($part['str']);
1155  foreach ($utf8Chars as $utfChar) {
1156  // Find number and evaluate position:
1157  $uNumber = (int)$this->csConvObj->utf8CharToUnumber($utfChar);
1158  $inRange = 0;
1159  foreach ($ranges as $rangeDef) {
1160  if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
1161  $inRange = 1;
1162  break;
1163  }
1164  }
1165  if ($currentState == -1) {
1166  $currentState = $inRange;
1167  }
1168  // Initialize first char
1169  // Switch bank:
1170  if ($inRange != $currentState && $uNumber !== 9 && $uNumber !== 10 && $uNumber !== 13 && $uNumber !== 32) {
1171  // Set result:
1172  if ($bankAccum !== '') {
1173  $newResult[] = [
1174  'str' => $bankAccum,
1175  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1176  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1177  'color' => $currentState ? $cfg['color'] : '',
1178  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1179  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1180  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1181  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1182  ];
1183  }
1184  // Initialize new settings:
1185  $currentState = $inRange;
1186  $bankAccum = '';
1187  }
1188  // Add char to bank:
1189  $bankAccum .= $utfChar;
1190  }
1191  // Set result for FINAL part:
1192  if ($bankAccum !== '') {
1193  $newResult[] = [
1194  'str' => $bankAccum,
1195  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1196  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1197  'color' => $currentState ? $cfg['color'] : '',
1198  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1199  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1200  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1201  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1202  ];
1203  }
1204  }
1205  // Set the new result as result array:
1206  if (!empty($newResult)) {
1207  $result = $newResult;
1208  }
1209  }
1210  break;
1211  }
1212  }
1213  }
1214  return $result;
1215  }
1216 
1226  public function calcWordSpacing($conf, $scaleFactor = 1)
1227  {
1228  $spacing = (int)$conf['spacing'];
1229  $wordSpacing = (int)$conf['wordSpacing'];
1230  $wordSpacing = $wordSpacing ?: $spacing * 2;
1231  $spacing *= $scaleFactor;
1232  $wordSpacing *= $scaleFactor;
1233  return [$spacing, $wordSpacing];
1234  }
1235 
1243  public function getTextScalFactor($conf)
1244  {
1245  if (!$conf['niceText']) {
1246  $sF = 1;
1247  } else {
1248  // NICETEXT::
1249  $sF = MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
1250  }
1251  return $sF;
1252  }
1253 
1270  protected function renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF = 1)
1271  {
1272  if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
1273  $phrase = '';
1274  $currentWidth = 0;
1275  $breakWidth = $conf['breakWidth'];
1276  $breakSpace = $this->getBreakSpace($conf);
1277  $wordPairs = $this->getWordPairsForLineBreak($string);
1278  // Iterate through all word pairs:
1279  foreach ($wordPairs as $index => $wordPair) {
1280  $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
1281  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
1282  $currentWidth += $wordWidth;
1283  $phrase .= $wordPair;
1284  } else {
1285  // Render the current phrase that is below breakWidth:
1286  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1287  // Calculate the news height offset:
1288  $y += $breakSpace;
1289  // Restart the phrase:
1290  $currentWidth = $wordWidth;
1291  $phrase = $wordPair;
1292  }
1293  }
1294  // Render the remaining phrase:
1295  if ($currentWidth) {
1296  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1297  }
1298  } else {
1299  $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
1300  }
1301  }
1302 
1309  protected function getWordPairsForLineBreak($string)
1310  {
1311  $wordPairs = [];
1312  $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
1313  $wordsCount = count($wordsArray);
1314  for ($index = 0; $index < $wordsCount; $index += 2) {
1315  $wordPairs[] = $wordsArray[$index] . $wordsArray[$index + 1];
1316  }
1317  return $wordPairs;
1318  }
1319 
1327  protected function getRenderedTextWidth($text, $conf)
1328  {
1329  $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $text, $conf['splitRendering.']);
1330  if ($conf['angle'] < 0) {
1331  $pixelWidth = abs($bounds[4] - $bounds[0]);
1332  } elseif ($conf['angle'] > 0) {
1333  $pixelWidth = abs($bounds[2] - $bounds[6]);
1334  } else {
1335  $pixelWidth = abs($bounds[4] - $bounds[6]);
1336  }
1337  return $pixelWidth;
1338  }
1339 
1347  protected function getBreakSpace($conf, array $boundingBox = null)
1348  {
1349  if (!isset($boundingBox)) {
1350  $boundingBox = $this->calcBBox($conf);
1351  $boundingBox = $boundingBox[2];
1352  }
1353  if (isset($conf['breakSpace']) && $conf['breakSpace']) {
1354  $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
1355  } else {
1356  $breakSpace = $boundingBox['lineHeight'];
1357  }
1358  return $breakSpace;
1359  }
1360 
1361  /*********************************************
1362  *
1363  * Other GIFBUILDER objects related to TEXT
1364  *
1365  *********************************************/
1376  public function makeOutline(&$im, $conf, $workArea, $txtConf)
1377  {
1378  $thickness = (int)$conf['thickness'];
1379  if ($thickness) {
1380  $txtConf['fontColor'] = $conf['color'];
1381  $outLineDist = MathUtility::forceIntegerInRange($thickness, 1, 2);
1382  for ($b = 1; $b <= $outLineDist; $b++) {
1383  if ($b == 1) {
1384  $it = 8;
1385  } else {
1386  $it = 16;
1387  }
1388  $outL = $this->circleOffset($b, $it);
1389  for ($a = 0; $a < $it; $a++) {
1390  $this->makeText($im, $txtConf, $this->applyOffset($workArea, $outL[$a]));
1391  }
1392  }
1393  }
1394  }
1395 
1406  public function circleOffset($distance, $iterations)
1407  {
1408  $res = [];
1409  if ($distance && $iterations) {
1410  for ($a = 0; $a < $iterations; $a++) {
1411  $yOff = round(sin((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1412  if ($yOff) {
1413  $yOff = (int)(ceil(abs(($yOff / 100))) * ($yOff / abs($yOff)));
1414  }
1415  $xOff = round(cos((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1416  if ($xOff) {
1417  $xOff = (int)(ceil(abs(($xOff / 100))) * ($xOff / abs($xOff)));
1418  }
1419  $res[$a] = [$xOff, $yOff];
1420  }
1421  }
1422  return $res;
1423  }
1424 
1435  public function makeEmboss(&$im, $conf, $workArea, $txtConf)
1436  {
1437  $conf['color'] = $conf['highColor'];
1438  $this->makeShadow($im, $conf, $workArea, $txtConf);
1439  $newOffset = GeneralUtility::intExplode(',', $conf['offset']);
1440  $newOffset[0] *= -1;
1441  $newOffset[1] *= -1;
1442  $conf['offset'] = implode(',', $newOffset);
1443  $conf['color'] = $conf['lowColor'];
1444  $this->makeShadow($im, $conf, $workArea, $txtConf);
1445  }
1446 
1458  public function makeShadow(&$im, $conf, $workArea, $txtConf)
1459  {
1460  $workArea = $this->applyOffset($workArea, GeneralUtility::intExplode(',', $conf['offset']));
1461  $blurRate = MathUtility::forceIntegerInRange((int)$conf['blur'], 0, 99);
1462  // No effects if ImageMagick ver. 5+
1463  if (!$blurRate || $this->NO_IM_EFFECTS) {
1464  $txtConf['fontColor'] = $conf['color'];
1465  $this->makeText($im, $txtConf, $workArea);
1466  } else {
1467  $w = imagesx($im);
1468  $h = imagesy($im);
1469  // Area around the blur used for cropping something
1470  $blurBorder = 3;
1471  $tmpStr = $this->randomName();
1472  $fileMenu = $tmpStr . '_menu.' . $this->gifExtension;
1473  $fileColor = $tmpStr . '_color.' . $this->gifExtension;
1474  $fileMask = $tmpStr . '_mask.' . $this->gifExtension;
1475  // BlurColor Image laves
1476  $blurColImg = imagecreatetruecolor($w, $h);
1477  $bcols = $this->convertColor($conf['color']);
1478  $Bcolor = imagecolorallocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
1479  imagefilledrectangle($blurColImg, 0, 0, $w, $h, $Bcolor);
1480  $this->ImageWrite($blurColImg, $fileColor);
1481  imagedestroy($blurColImg);
1482  // The mask is made: BlurTextImage
1483  $blurTextImg = imagecreatetruecolor($w + $blurBorder * 2, $h + $blurBorder * 2);
1484  // Black background
1485  $Bcolor = imagecolorallocate($blurTextImg, 0, 0, 0);
1486  imagefilledrectangle($blurTextImg, 0, 0, $w + $blurBorder * 2, $h + $blurBorder * 2, $Bcolor);
1487  $txtConf['fontColor'] = 'white';
1488  $blurBordArr = [$blurBorder, $blurBorder];
1489  $this->makeText($blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
1490  // Dump to temporary file
1491  $this->ImageWrite($blurTextImg, $fileMask);
1492  // Destroy
1493  imagedestroy($blurTextImg);
1494  $command = '';
1495  if ($this->V5_EFFECTS) {
1496  $command .= $this->v5_blur($blurRate + 1);
1497  } else {
1498  // Blurring of the mask
1499  // How many blur-commands that is executed. Min = 1;
1500  $times = ceil($blurRate / 10);
1501  // Building blur-command
1502  for ($a = 0; $a < $times; $a++) {
1503  $command .= ' -blur ' . $blurRate;
1504  }
1505  }
1506  $this->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
1507  // The mask is loaded again
1508  $blurTextImg_tmp = $this->imageCreateFromFile($fileMask);
1509  // If nothing went wrong we continue with the blurred mask
1510  if ($blurTextImg_tmp) {
1511  // Cropping the border from the mask
1512  $blurTextImg = imagecreatetruecolor($w, $h);
1513  $this->imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, $w, $h, $w, $h);
1514  // Destroy the temporary mask
1515  imagedestroy($blurTextImg_tmp);
1516  // Adjust the mask
1517  $intensity = 40;
1518  if ($conf['intensity']) {
1519  $intensity = MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
1520  }
1521  $intensity = ceil(255 - $intensity / 100 * 255);
1522  $this->inputLevels($blurTextImg, 0, $intensity);
1523  $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 0, 100);
1524  if ($opacity && $opacity < 100) {
1525  $high = ceil(255 * $opacity / 100);
1526  // Reducing levels as the opacity demands
1527  $this->outputLevels($blurTextImg, 0, $high);
1528  }
1529  // Dump the mask again
1530  $this->ImageWrite($blurTextImg, $fileMask);
1531  // Destroy the mask
1532  imagedestroy($blurTextImg);
1533  // The pictures are combined
1534  // The main pictures is saved temporarily
1535  $this->ImageWrite($im, $fileMenu);
1536  $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
1537  // The main image is loaded again...
1538  $backIm = $this->imageCreateFromFile($fileMenu);
1539  // ... and if nothing went wrong we load it onto the old one.
1540  if ($backIm) {
1541  if (!$this->saveAlphaLayer) {
1542  imagecolortransparent($backIm, -1);
1543  }
1544  $im = $backIm;
1545  }
1546  }
1547  // Deleting temporary files;
1548  if (!$this->dontUnlinkTempFiles) {
1549  unlink($fileMenu);
1550  unlink($fileColor);
1551  unlink($fileMask);
1552  }
1553  }
1554  }
1555 
1556  /****************************
1557  *
1558  * Other GIFBUILDER objects
1559  *
1560  ****************************/
1570  public function makeBox(&$im, $conf, $workArea)
1571  {
1572  $cords = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1573  $conf['offset'] = $cords[0] . ',' . $cords[1];
1574  $cords = $this->objPosition($conf, $workArea, [$cords[2], $cords[3]]);
1575  $cols = $this->convertColor($conf['color']);
1576  $opacity = 0;
1577  if (isset($conf['opacity'])) {
1578  // conversion:
1579  // PHP 0 = opaque, 127 = transparent
1580  // TYPO3 100 = opaque, 0 = transparent
1581  $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 1, 100, 1);
1582  $opacity = abs($opacity - 100);
1583  $opacity = round(127 * $opacity / 100);
1584  }
1585  $tmpColor = imagecolorallocatealpha($im, $cols[0], $cols[1], $cols[2], $opacity);
1586  imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1587  }
1588 
1610  public function makeEllipse(&$im, array $conf, array $workArea)
1611  {
1612  $ellipseConfiguration = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1613  // Ellipse offset inside workArea (x/y)
1614  $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1615  // @see objPosition
1616  $imageCoordinates = $this->objPosition($conf, $workArea, [$ellipseConfiguration[2], $ellipseConfiguration[3]]);
1617  $color = $this->convertColor($conf['color']);
1618  $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1619  imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1620  }
1621 
1631  public function makeEffect(&$im, $conf)
1632  {
1633  $commands = $this->IMparams($conf['value']);
1634  if ($commands) {
1635  $this->applyImageMagickToPHPGif($im, $commands);
1636  }
1637  }
1638 
1647  public function IMparams($setup)
1648  {
1649  if (!trim($setup)) {
1650  return '';
1651  }
1652  $effects = explode('|', $setup);
1653  $commands = '';
1654  foreach ($effects as $val) {
1655  $pairs = explode('=', $val, 2);
1656  $value = trim($pairs[1]);
1657  $effect = strtolower(trim($pairs[0]));
1658  switch ($effect) {
1659  case 'gamma':
1660  $commands .= ' -gamma ' . (float)$value;
1661  break;
1662  case 'blur':
1663  if (!$this->NO_IM_EFFECTS) {
1664  if ($this->V5_EFFECTS) {
1665  $commands .= $this->v5_blur($value);
1666  } else {
1667  $commands .= ' -blur ' . MathUtility::forceIntegerInRange($value, 1, 99);
1668  }
1669  }
1670  break;
1671  case 'sharpen':
1672  if (!$this->NO_IM_EFFECTS) {
1673  if ($this->V5_EFFECTS) {
1674  $commands .= $this->v5_sharpen($value);
1675  } else {
1676  $commands .= ' -sharpen ' . MathUtility::forceIntegerInRange($value, 1, 99);
1677  }
1678  }
1679  break;
1680  case 'rotate':
1681  $commands .= ' -rotate ' . MathUtility::forceIntegerInRange($value, 0, 360);
1682  break;
1683  case 'solarize':
1684  $commands .= ' -solarize ' . MathUtility::forceIntegerInRange($value, 0, 99);
1685  break;
1686  case 'swirl':
1687  $commands .= ' -swirl ' . MathUtility::forceIntegerInRange($value, 0, 1000);
1688  break;
1689  case 'wave':
1690  $params = GeneralUtility::intExplode(',', $value);
1691  $commands .= ' -wave ' . MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . MathUtility::forceIntegerInRange($params[1], 0, 99);
1692  break;
1693  case 'charcoal':
1694  $commands .= ' -charcoal ' . MathUtility::forceIntegerInRange($value, 0, 100);
1695  break;
1696  case 'gray':
1697  $commands .= ' -colorspace GRAY';
1698  break;
1699  case 'edge':
1700  $commands .= ' -edge ' . MathUtility::forceIntegerInRange($value, 0, 99);
1701  break;
1702  case 'emboss':
1703  $commands .= ' -emboss';
1704  break;
1705  case 'flip':
1706  $commands .= ' -flip';
1707  break;
1708  case 'flop':
1709  $commands .= ' -flop';
1710  break;
1711  case 'colors':
1712  $commands .= ' -colors ' . MathUtility::forceIntegerInRange($value, 2, 255);
1713  break;
1714  case 'shear':
1715  $commands .= ' -shear ' . MathUtility::forceIntegerInRange($value, -90, 90);
1716  break;
1717  case 'invert':
1718  $commands .= ' -negate';
1719  break;
1720  }
1721  }
1722  return $commands;
1723  }
1724 
1733  public function adjust(&$im, $conf)
1734  {
1735  $setup = $conf['value'];
1736  if (!trim($setup)) {
1737  return;
1738  }
1739  $effects = explode('|', $setup);
1740  foreach ($effects as $val) {
1741  $pairs = explode('=', $val, 2);
1742  $value = trim($pairs[1]);
1743  $effect = strtolower(trim($pairs[0]));
1744  switch ($effect) {
1745  case 'inputlevels':
1746  // low,high
1747  $params = GeneralUtility::intExplode(',', $value);
1748  $this->inputLevels($im, $params[0], $params[1]);
1749  break;
1750  case 'outputlevels':
1751  $params = GeneralUtility::intExplode(',', $value);
1752  $this->outputLevels($im, $params[0], $params[1]);
1753  break;
1754  case 'autolevels':
1755  $this->autolevels($im);
1756  break;
1757  }
1758  }
1759  }
1760 
1769  public function crop(&$im, $conf)
1770  {
1771  // Clears workArea to total image
1772  $this->setWorkArea('');
1773  $cords = GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1774  $conf['offset'] = $cords[0] . ',' . $cords[1];
1775  $cords = $this->objPosition($conf, $this->workArea, [$cords[2], $cords[3]]);
1776  $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1777  $cols = $this->convertColor($conf['backColor'] ? $conf['backColor'] : $this->setup['backColor']);
1778  $Bcolor = imagecolorallocate($newIm, $cols[0], $cols[1], $cols[2]);
1779  imagefilledrectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1780  $newConf = [];
1781  $workArea = [0, 0, $cords[2], $cords[3]];
1782  if ($cords[0] < 0) {
1783  $workArea[0] = abs($cords[0]);
1784  } else {
1785  $newConf['offset'] = -$cords[0];
1786  }
1787  if ($cords[1] < 0) {
1788  $workArea[1] = abs($cords[1]);
1789  } else {
1790  $newConf['offset'] .= ',' . -$cords[1];
1791  }
1792  $this->copyGifOntoGif($newIm, $im, $newConf, $workArea);
1793  $im = $newIm;
1794  $this->w = imagesx($im);
1795  $this->h = imagesy($im);
1796  // Clears workArea to total image
1797  $this->setWorkArea('');
1798  }
1799 
1808  public function scale(&$im, $conf)
1809  {
1810  if ($conf['width'] || $conf['height'] || $conf['params']) {
1811  $tmpStr = $this->randomName();
1812  $theFile = $tmpStr . '.' . $this->gifExtension;
1813  $this->ImageWrite($im, $theFile);
1814  $theNewFile = $this->imageMagickConvert($theFile, $this->gifExtension, $conf['width'], $conf['height'], $conf['params']);
1815  $tmpImg = $this->imageCreateFromFile($theNewFile[3]);
1816  if ($tmpImg) {
1817  imagedestroy($im);
1818  $im = $tmpImg;
1819  $this->w = imagesx($im);
1820  $this->h = imagesy($im);
1821  // Clears workArea to total image
1822  $this->setWorkArea('');
1823  }
1824  if (!$this->dontUnlinkTempFiles) {
1825  unlink($theFile);
1826  if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1827  unlink($theNewFile[3]);
1828  }
1829  }
1830  }
1831  }
1832 
1842  public function setWorkArea($workArea)
1843  {
1844  $this->workArea = GeneralUtility::intExplode(',', $workArea);
1845  $this->workArea = $this->applyOffset($this->workArea, $this->OFFSET);
1846  if (!$this->workArea[2]) {
1847  $this->workArea[2] = $this->w;
1848  }
1849  if (!$this->workArea[3]) {
1850  $this->workArea[3] = $this->h;
1851  }
1852  }
1853 
1854  /*************************
1855  *
1856  * Adjustment functions
1857  *
1858  ************************/
1865  public function autolevels(&$im)
1866  {
1867  $totalCols = imagecolorstotal($im);
1868  $grayArr = [];
1869  for ($c = 0; $c < $totalCols; $c++) {
1870  $cols = imagecolorsforindex($im, $c);
1871  $grayArr[] = round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
1872  }
1873  $min = min($grayArr);
1874  $max = max($grayArr);
1875  $delta = $max - $min;
1876  if ($delta) {
1877  for ($c = 0; $c < $totalCols; $c++) {
1878  $cols = imagecolorsforindex($im, $c);
1879  $cols['red'] = floor(($cols['red'] - $min) / $delta * 255);
1880  $cols['green'] = floor(($cols['green'] - $min) / $delta * 255);
1881  $cols['blue'] = floor(($cols['blue'] - $min) / $delta * 255);
1882  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1883  }
1884  }
1885  }
1886 
1896  public function outputLevels(&$im, $low, $high, $swap = false)
1897  {
1898  if ($low < $high) {
1899  $low = MathUtility::forceIntegerInRange($low, 0, 255);
1900  $high = MathUtility::forceIntegerInRange($high, 0, 255);
1901  if ($swap) {
1902  $temp = $low;
1903  $low = 255 - $high;
1904  $high = 255 - $temp;
1905  }
1906  $delta = $high - $low;
1907  $totalCols = imagecolorstotal($im);
1908  for ($c = 0; $c < $totalCols; $c++) {
1909  $cols = imagecolorsforindex($im, $c);
1910  $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
1911  $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
1912  $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
1913  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1914  }
1915  }
1916  }
1917 
1926  public function inputLevels(&$im, $low, $high)
1927  {
1928  if ($low < $high) {
1929  $low = MathUtility::forceIntegerInRange($low, 0, 255);
1930  $high = MathUtility::forceIntegerInRange($high, 0, 255);
1931  $delta = $high - $low;
1932  $totalCols = imagecolorstotal($im);
1933  for ($c = 0; $c < $totalCols; $c++) {
1934  $cols = imagecolorsforindex($im, $c);
1935  $cols['red'] = MathUtility::forceIntegerInRange(($cols['red'] - $low) / $delta * 255, 0, 255);
1936  $cols['green'] = MathUtility::forceIntegerInRange(($cols['green'] - $low) / $delta * 255, 0, 255);
1937  $cols['blue'] = MathUtility::forceIntegerInRange(($cols['blue'] - $low) / $delta * 255, 0, 255);
1938  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1939  }
1940  }
1941  }
1942 
1950  public function IMreduceColors($file, $cols)
1951  {
1952  $fI = GeneralUtility::split_fileref($file);
1953  $ext = strtolower($fI['fileext']);
1954  $result = $this->randomName() . '.' . $ext;
1955  $reduce = MathUtility::forceIntegerInRange($cols, 0, $ext === 'gif' ? 256 : $this->truecolorColors, 0);
1956  if ($reduce > 0) {
1957  $params = ' -colors ' . $reduce;
1958  if ($reduce <= 256) {
1959  $params .= ' -type Palette';
1960  }
1961  $prefix = $ext === 'png' && $reduce <= 256 ? 'png8:' : '';
1962  $this->imageMagickExec($file, $prefix . $result, $params);
1963  if ($result) {
1964  return $result;
1965  }
1966  }
1967  return '';
1968  }
1969 
1970  /*********************************
1971  *
1972  * GIFBUILDER Helper functions
1973  *
1974  *********************************/
1983  public function prependAbsolutePath($fontFile)
1984  {
1986  return GeneralUtility::isAbsPath($fontFile) ? $fontFile : PATH_site . $fontFile;
1987  }
1988 
1997  public function v5_sharpen($factor)
1998  {
1999  $factor = MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
2000  $sharpenArr = explode(',', ',' . $this->im5fx_sharpenSteps);
2001  $sharpenF = trim($sharpenArr[$factor]);
2002  if ($sharpenF) {
2003  return ' -sharpen ' . $sharpenF;
2004  }
2005  return '';
2006  }
2007 
2016  public function v5_blur($factor)
2017  {
2018  $factor = MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
2019  $blurArr = explode(',', ',' . $this->im5fx_blurSteps);
2020  $blurF = trim($blurArr[$factor]);
2021  if ($blurF) {
2022  return ' -blur ' . $blurF;
2023  }
2024  return '';
2025  }
2026 
2033  public function randomName()
2034  {
2035  $this->createTempSubDir('var/transient/');
2036  return $this->tempPath . 'var/transient/' . md5(uniqid('', true));
2037  }
2038 
2047  public function applyOffset($cords, $OFFSET)
2048  {
2049  $cords[0] = (int)$cords[0] + (int)$OFFSET[0];
2050  $cords[1] = (int)$cords[1] + (int)$OFFSET[1];
2051  return $cords;
2052  }
2053 
2061  public function convertColor($string)
2062  {
2063  $col = [];
2064  $cParts = explode(':', $string, 2);
2065  // Finding the RGB definitions of the color:
2066  $string = $cParts[0];
2067  if (strstr($string, '#')) {
2068  $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string);
2069  $col[] = hexdec(substr($string, 0, 2));
2070  $col[] = hexdec(substr($string, 2, 2));
2071  $col[] = hexdec(substr($string, 4, 2));
2072  } elseif (strstr($string, ',')) {
2073  $string = preg_replace('/[^,0-9]*/', '', $string);
2074  $strArr = explode(',', $string);
2075  $col[] = (int)$strArr[0];
2076  $col[] = (int)$strArr[1];
2077  $col[] = (int)$strArr[2];
2078  } else {
2079  $string = strtolower(trim($string));
2080  if ($this->colMap[$string]) {
2081  $col = $this->colMap[$string];
2082  } else {
2083  $col = [0, 0, 0];
2084  }
2085  }
2086  // ... and possibly recalculating the value
2087  if (trim($cParts[1])) {
2088  $cParts[1] = trim($cParts[1]);
2089  if ($cParts[1][0] === '*') {
2090  $val = (float)substr($cParts[1], 1);
2091  $col[0] = MathUtility::forceIntegerInRange($col[0] * $val, 0, 255);
2092  $col[1] = MathUtility::forceIntegerInRange($col[1] * $val, 0, 255);
2093  $col[2] = MathUtility::forceIntegerInRange($col[2] * $val, 0, 255);
2094  } else {
2095  $val = (int)$cParts[1];
2096  $col[0] = MathUtility::forceIntegerInRange($col[0] + $val, 0, 255);
2097  $col[1] = MathUtility::forceIntegerInRange($col[1] + $val, 0, 255);
2098  $col[2] = MathUtility::forceIntegerInRange($col[2] + $val, 0, 255);
2099  }
2100  }
2101  return $col;
2102  }
2103 
2114  public function objPosition($conf, $workArea, $BB)
2115  {
2116  // offset, align, valign, workarea
2117  $result = [];
2118  $result[2] = $BB[0];
2119  $result[3] = $BB[1];
2120  $w = $workArea[2];
2121  $h = $workArea[3];
2122  $align = explode(',', $conf['align']);
2123  $align[0] = strtolower(substr(trim($align[0]), 0, 1));
2124  $align[1] = strtolower(substr(trim($align[1]), 0, 1));
2125  switch ($align[0]) {
2126  case 'r':
2127  $result[0] = $w - $result[2];
2128  break;
2129  case 'c':
2130  $result[0] = round(($w - $result[2]) / 2);
2131  break;
2132  default:
2133  $result[0] = 0;
2134  }
2135  switch ($align[1]) {
2136  case 'b':
2137  // y pos
2138  $result[1] = $h - $result[3];
2139  break;
2140  case 'c':
2141  $result[1] = round(($h - $result[3]) / 2);
2142  break;
2143  default:
2144  $result[1] = 0;
2145  }
2146  $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
2147  $result = $this->applyOffset($result, $workArea);
2148  return $result;
2149  }
2150 
2151  /***********************************
2152  *
2153  * Scaling, Dimensions of images
2154  *
2155  ***********************************/
2170  public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $params = '', $frame = '', $options = [], $mustCreate = false)
2171  {
2172  if ($this->NO_IMAGE_MAGICK) {
2173  // Returning file info right away
2174  return $this->getImageDimensions($imagefile);
2175  }
2176  $info = $this->getImageDimensions($imagefile);
2177  if (!$info) {
2178  return null;
2179  }
2180 
2181  $newExt = strtolower(trim($newExt));
2182  // If no extension is given the original extension is used
2183  if (!$newExt) {
2184  $newExt = $info[2];
2185  }
2186  if ($newExt === 'web') {
2187  if (GeneralUtility::inList($this->webImageExt, $info[2])) {
2188  $newExt = $info[2];
2189  } else {
2190  $newExt = $this->gif_or_jpg($info[2], $info[0], $info[1]);
2191  if (!$params) {
2192  $params = $this->cmds[$newExt];
2193  }
2194  }
2195  }
2196  if (!GeneralUtility::inList($this->imageFileExt, $newExt)) {
2197  return null;
2198  }
2199 
2200  $data = $this->getImageScale($info, $w, $h, $options);
2201  $w = $data['origW'];
2202  $h = $data['origH'];
2203  // If no conversion should be performed
2204  // this flag is TRUE if the width / height does NOT dictate
2205  // the image to be scaled!! (that is if no width / height is
2206  // given or if the destination w/h matches the original image
2207  // dimensions or if the option to not scale the image is set)
2208  $noScale = !$w && !$h || $data[0] == $info[0] && $data[1] == $info[1] || !empty($options['noScale']);
2209  if ($noScale && !$data['crs'] && !$params && !$frame && $newExt == $info[2] && !$mustCreate) {
2210  // Set the new width and height before returning,
2211  // if the noScale option is set
2212  if (!empty($options['noScale'])) {
2213  $info[0] = $data[0];
2214  $info[1] = $data[1];
2215  }
2216  $info[3] = $imagefile;
2217  return $info;
2218  }
2219  $info[0] = $data[0];
2220  $info[1] = $data[1];
2221  $frame = $this->addFrameSelection ? (int)$frame : '';
2222  if (!$params) {
2223  $params = $this->cmds[$newExt];
2224  }
2225  // Cropscaling:
2226  if ($data['crs']) {
2227  if (!$data['origW']) {
2228  $data['origW'] = $data[0];
2229  }
2230  if (!$data['origH']) {
2231  $data['origH'] = $data[1];
2232  }
2233  $offsetX = (int)(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
2234  $offsetY = (int)(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
2235  $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . '! ';
2236  }
2237  $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
2238  $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
2239  if ($this->alternativeOutputKey) {
2240  $theOutputName = GeneralUtility::shortMD5($command . $cropscale . basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
2241  } else {
2242  $theOutputName = GeneralUtility::shortMD5($command . $cropscale . $imagefile . filemtime($imagefile) . '[' . $frame . ']');
2243  }
2244  if ($this->imageMagickConvert_forceFileNameBody) {
2246  $this->imageMagickConvert_forceFileNameBody = '';
2247  }
2248  // Making the temporary filename:
2249  $this->createTempSubDir('assets/images/');
2250  $output = $this->absPrefix . $this->tempPath . 'assets/images/' . $this->filenamePrefix . $theOutputName . '.' . $newExt;
2251  if ($this->dontCheckForExistingTempFile || !file_exists($output)) {
2252  $this->imageMagickExec($imagefile, $output, $command, $frame);
2253  }
2254  if (file_exists($output)) {
2255  $info[3] = $output;
2256  $info[2] = $newExt;
2257  // params might change some image data!
2258  if ($params) {
2259  $info = $this->getImageDimensions($info[3]);
2260  }
2261  if ($info[2] == $this->gifExtension && !$this->dontCompress) {
2262  // Compress with IM (lzw) or GD (rle) (Workaround for the absence of lzw-compression in GD)
2263  self::gifCompress($info[3], '');
2264  }
2265  return $info;
2266  }
2267  return null;
2268  }
2269 
2277  public function getImageDimensions($imageFile)
2278  {
2279  preg_match('/([^\\.]*)$/', $imageFile, $reg);
2280  if (file_exists($imageFile) && GeneralUtility::inList($this->imageFileExt, strtolower($reg[0]))) {
2281  if ($returnArr = $this->getCachedImageDimensions($imageFile)) {
2282  return $returnArr;
2283  } else {
2284  if ($temp = @getimagesize($imageFile)) {
2285  $returnArr = [$temp[0], $temp[1], strtolower($reg[0]), $imageFile];
2286  } else {
2287  $returnArr = $this->imageMagickIdentify($imageFile);
2288  }
2289  if ($returnArr) {
2290  $this->cacheImageDimensions($returnArr);
2291  return $returnArr;
2292  }
2293  }
2294  }
2295  return null;
2296  }
2297 
2305  public function cacheImageDimensions(array $identifyResult)
2306  {
2307  $filePath = $identifyResult[3];
2308  $statusHash = $this->generateStatusHashForImageFile($filePath);
2309  $identifier = $this->generateCacheKeyForImageFile($filePath);
2310 
2312  $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_imagesizes');
2313  $imageDimensions = [
2314  'hash' => $statusHash,
2315  'imagewidth' => $identifyResult[0],
2316  'imageheight' => $identifyResult[1],
2317  ];
2318  $cache->set($identifier, $imageDimensions);
2319 
2320  return true;
2321  }
2322 
2331  public function getCachedImageDimensions($filePath)
2332  {
2333  $statusHash = $this->generateStatusHashForImageFile($filePath);
2334  $identifier = $this->generateCacheKeyForImageFile($filePath);
2336  $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_imagesizes');
2337  $cachedImageDimensions = $cache->get($identifier);
2338  if (!isset($cachedImageDimensions['hash'])) {
2339  return false;
2340  }
2341 
2342  if ($cachedImageDimensions['hash'] !== $statusHash) {
2343  // The file has changed. Delete the cache entry.
2344  $cache->remove($identifier);
2345  $result = false;
2346  } else {
2347  preg_match('/([^\\.]*)$/', $filePath, $imageExtension);
2348  $result = [
2349  (int)$cachedImageDimensions['imagewidth'],
2350  (int)$cachedImageDimensions['imageheight'],
2351  strtolower($imageExtension[0]),
2352  $filePath
2353  ];
2354  }
2355 
2356  return $result;
2357  }
2358 
2368  protected function generateCacheKeyForImageFile($filePath)
2369  {
2370  return sha1($filePath);
2371  }
2372 
2380  protected function generateStatusHashForImageFile($filePath)
2381  {
2382  $fileStatus = stat($filePath);
2383 
2384  return sha1($fileStatus['mtime'] . $fileStatus['size']);
2385  }
2386 
2398  public function getImageScale($info, $w, $h, $options)
2399  {
2400  if (strstr($w . $h, 'm')) {
2401  $max = 1;
2402  } else {
2403  $max = 0;
2404  }
2405  if (strstr($w . $h, 'c')) {
2406  $out['cropH'] = (int)substr(strstr($w, 'c'), 1);
2407  $out['cropV'] = (int)substr(strstr($h, 'c'), 1);
2408  $crs = true;
2409  } else {
2410  $crs = false;
2411  }
2412  $out['crs'] = $crs;
2413  $w = (int)$w;
2414  $h = (int)$h;
2415  // If there are max-values...
2416  if (!empty($options['maxW'])) {
2417  // If width is given...
2418  if ($w) {
2419  if ($w > $options['maxW']) {
2420  $w = $options['maxW'];
2421  // Height should follow
2422  $max = 1;
2423  }
2424  } else {
2425  if ($info[0] > $options['maxW']) {
2426  $w = $options['maxW'];
2427  // Height should follow
2428  $max = 1;
2429  }
2430  }
2431  }
2432  if (!empty($options['maxH'])) {
2433  // If height is given...
2434  if ($h) {
2435  if ($h > $options['maxH']) {
2436  $h = $options['maxH'];
2437  // Height should follow
2438  $max = 1;
2439  }
2440  } else {
2441  // Changed [0] to [1] 290801
2442  if ($info[1] > $options['maxH']) {
2443  $h = $options['maxH'];
2444  // Height should follow
2445  $max = 1;
2446  }
2447  }
2448  }
2449  $out['origW'] = $w;
2450  $out['origH'] = $h;
2451  $out['max'] = $max;
2452  if (!$this->mayScaleUp) {
2453  if ($w > $info[0]) {
2454  $w = $info[0];
2455  }
2456  if ($h > $info[1]) {
2457  $h = $info[1];
2458  }
2459  }
2460  // If scaling should be performed
2461  if ($w || $h) {
2462  if ($w && !$h) {
2463  $info[1] = ceil($info[1] * ($w / $info[0]));
2464  $info[0] = $w;
2465  }
2466  if (!$w && $h) {
2467  $info[0] = ceil($info[0] * ($h / $info[1]));
2468  $info[1] = $h;
2469  }
2470  if ($w && $h) {
2471  if ($max) {
2472  $ratio = $info[0] / $info[1];
2473  if ($h * $ratio > $w) {
2474  $h = round($w / $ratio);
2475  } else {
2476  $w = round($h * $ratio);
2477  }
2478  }
2479  if ($crs) {
2480  $ratio = $info[0] / $info[1];
2481  if ($h * $ratio < $w) {
2482  $h = round($w / $ratio);
2483  } else {
2484  $w = round($h * $ratio);
2485  }
2486  }
2487  $info[0] = $w;
2488  $info[1] = $h;
2489  }
2490  }
2491  $out[0] = $info[0];
2492  $out[1] = $info[1];
2493  // Set minimum-measures!
2494  if (isset($options['minW']) && $out[0] < $options['minW']) {
2495  if (($max || $crs) && $out[0]) {
2496  $out[1] = round($out[1] * $options['minW'] / $out[0]);
2497  }
2498  $out[0] = $options['minW'];
2499  }
2500  if (isset($options['minH']) && $out[1] < $options['minH']) {
2501  if (($max || $crs) && $out[1]) {
2502  $out[0] = round($out[0] * $options['minH'] / $out[1]);
2503  }
2504  $out[1] = $options['minH'];
2505  }
2506  return $out;
2507  }
2508 
2509  /***********************************
2510  *
2511  * ImageMagick API functions
2512  *
2513  ***********************************/
2520  public function imageMagickIdentify($imagefile)
2521  {
2522  if ($this->NO_IMAGE_MAGICK) {
2523  return null;
2524  }
2525 
2526  $frame = $this->addFrameSelection ? '[0]' : '';
2527  $cmd = CommandUtility::imageMagickCommand('identify', CommandUtility::escapeShellArgument($imagefile) . $frame);
2528  $returnVal = [];
2529  CommandUtility::exec($cmd, $returnVal);
2530  $splitstring = array_pop($returnVal);
2531  $this->IM_commands[] = ['identify', $cmd, $splitstring];
2532  if ($splitstring) {
2533  preg_match('/([^\\.]*)$/', $imagefile, $reg);
2534  $splitinfo = explode(' ', $splitstring);
2535  $dim = false;
2536  foreach ($splitinfo as $key => $val) {
2537  $temp = '';
2538  if ($val) {
2539  $temp = explode('x', $val);
2540  }
2541  if ((int)$temp[0] && (int)$temp[1]) {
2542  $dim = $temp;
2543  break;
2544  }
2545  }
2546  if (!empty($dim[0]) && !empty($dim[1])) {
2547  return [$dim[0], $dim[1], strtolower($reg[0]), $imagefile];
2548  }
2549  }
2550  return null;
2551  }
2552 
2563  public function imageMagickExec($input, $output, $params, $frame = 0)
2564  {
2565  if ($this->NO_IMAGE_MAGICK) {
2566  return '';
2567  }
2568  // If addFrameSelection is set in the Install Tool, a frame number is added to
2569  // select a specific page of the image (by default this will be the first page)
2570  $frame = $this->addFrameSelection ? '[' . (int)$frame . ']' : '';
2571  $cmd = CommandUtility::imageMagickCommand('convert', $params . ' ' . CommandUtility::escapeShellArgument($input . $frame) . ' ' . CommandUtility::escapeShellArgument($output));
2572  $this->IM_commands[] = [$output, $cmd];
2573  $ret = CommandUtility::exec($cmd);
2574  // Change the permissions of the file
2576  return $ret;
2577  }
2578 
2589  public function combineExec($input, $overlay, $mask, $output)
2590  {
2591  if ($this->NO_IMAGE_MAGICK) {
2592  return '';
2593  }
2594  $theMask = $this->randomName() . '.' . $this->gifExtension;
2595  // +matte = no alpha layer in output
2596  $this->imageMagickExec($mask, $theMask, '-colorspace GRAY +matte');
2597 
2598  $parameters = '-compose over +matte '
2599  . CommandUtility::escapeShellArgument($input) . ' '
2600  . CommandUtility::escapeShellArgument($overlay) . ' '
2601  . CommandUtility::escapeShellArgument($theMask) . ' '
2603  $cmd = CommandUtility::imageMagickCommand('combine', $parameters);
2604  $this->IM_commands[] = [$output, $cmd];
2605  $ret = CommandUtility::exec($cmd);
2606  // Change the permissions of the file
2608  if (is_file($theMask)) {
2609  @unlink($theMask);
2610  }
2611  return $ret;
2612  }
2613 
2632  public static function gifCompress($theFile, $type)
2633  {
2634  $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
2635  if (!$gfxConf['gif_compress'] || strtolower(substr($theFile, -4, 4)) !== '.gif') {
2636  return '';
2637  }
2638 
2639  if (($type === 'IM' || !$type) && $gfxConf['processor_enabled'] && $gfxConf['processor_path_lzw']) {
2640  // Use temporary file to prevent problems with read and write lock on same file on network file systems
2641  $temporaryName = dirname($theFile) . '/' . md5(uniqid('', true)) . '.gif';
2642  // Rename could fail, if a simultaneous thread is currently working on the same thing
2643  if (@rename($theFile, $temporaryName)) {
2644  $cmd = CommandUtility::imageMagickCommand('convert', '"' . $temporaryName . '" "' . $theFile . '"', $gfxConf['processor_path_lzw']);
2645  CommandUtility::exec($cmd);
2646  unlink($temporaryName);
2647  }
2648  $returnCode = 'IM';
2649  if (@is_file($theFile)) {
2651  }
2652  } elseif (($type === 'GD' || !$type) && $gfxConf['gdlib'] && !$gfxConf['gdlib_png']) {
2653  $tempImage = imagecreatefromgif($theFile);
2654  imagegif($tempImage, $theFile);
2655  imagedestroy($tempImage);
2656  $returnCode = 'GD';
2657  if (@is_file($theFile)) {
2659  }
2660  } else {
2661  $returnCode = '';
2662  }
2663 
2664  return $returnCode;
2665  }
2666 
2675  public static function readPngGif($theFile, $output_png = false)
2676  {
2677  if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] || !@is_file($theFile)) {
2678  return null;
2679  }
2680 
2681  $ext = strtolower(substr($theFile, -4, 4));
2682  if ((string)$ext == '.png' && $output_png || (string)$ext == '.gif' && !$output_png) {
2683  return $theFile;
2684  }
2685 
2686  if (!@is_dir(PATH_site . 'typo3temp/assets/images/')) {
2687  GeneralUtility::mkdir_deep(PATH_site . 'typo3temp/assets/images/');
2688  }
2689  $newFile = PATH_site . 'typo3temp/assets/images/' . md5($theFile . '|' . filemtime($theFile)) . ($output_png ? '.png' : '.gif');
2691  'convert', '"' . $theFile . '" "' . $newFile . '"', $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path']
2692  );
2693  CommandUtility::exec($cmd);
2694  if (@is_file($newFile)) {
2696  return $newFile;
2697  }
2698  return null;
2699  }
2700 
2701  /***********************************
2702  *
2703  * Various IO functions
2704  *
2705  ***********************************/
2706 
2713  public function createTempSubDir($dirName)
2714  {
2715  // Checking if the this->tempPath is already prefixed with PATH_site and if not, prefix it with that constant.
2716  if (GeneralUtility::isFirstPartOfStr($this->tempPath, PATH_site)) {
2717  $tmpPath = $this->tempPath;
2718  } else {
2719  $tmpPath = PATH_site . $this->tempPath;
2720  }
2721  // Making the temporary filename:
2722  if (!@is_dir($tmpPath . $dirName)) {
2723  GeneralUtility::mkdir_deep($tmpPath . $dirName);
2724  return @is_dir($tmpPath . $dirName);
2725  }
2726  return false;
2727  }
2728 
2736  public function applyImageMagickToPHPGif(&$im, $command)
2737  {
2738  $tmpStr = $this->randomName();
2739  $theFile = $tmpStr . '.' . $this->gifExtension;
2740  $this->ImageWrite($im, $theFile);
2741  $this->imageMagickExec($theFile, $theFile, $command);
2742  $tmpImg = $this->imageCreateFromFile($theFile);
2743  if ($tmpImg) {
2744  imagedestroy($im);
2745  $im = $tmpImg;
2746  $this->w = imagesx($im);
2747  $this->h = imagesy($im);
2748  }
2749  if (!$this->dontUnlinkTempFiles) {
2750  unlink($theFile);
2751  }
2752  }
2753 
2763  public function gif_or_jpg($type, $w, $h)
2764  {
2765  if ($type == 'ai' || $w * $h < $this->pixelLimitGif) {
2766  return $this->gifExtension;
2767  } else {
2768  return 'jpg';
2769  }
2770  }
2771 
2781  public function output($file)
2782  {
2783  if ($file) {
2784  $reg = [];
2785  preg_match('/([^\\.]*)$/', $file, $reg);
2786  $ext = strtolower($reg[0]);
2787  switch ($ext) {
2788  case 'gif':
2789 
2790  case 'png':
2791  if ($this->ImageWrite($this->im, $file)) {
2792  // ImageMagick operations
2793  if ($this->setup['reduceColors'] || !$this->png_truecolor) {
2794  $reduced = $this->IMreduceColors($file, MathUtility::forceIntegerInRange($this->setup['reduceColors'], 256, $this->truecolorColors, 256));
2795  if ($reduced) {
2796  @copy($reduced, $file);
2797  @unlink($reduced);
2798  }
2799  }
2800  // Compress with IM! (adds extra compression, LZW from ImageMagick)
2801  // (Workaround for the absence of lzw-compression in GD)
2802  self::gifCompress($file, 'IM');
2803  }
2804  break;
2805  case 'jpg':
2806 
2807  case 'jpeg':
2808  // Use the default
2809  $quality = 0;
2810  if ($this->setup['quality']) {
2811  $quality = MathUtility::forceIntegerInRange($this->setup['quality'], 10, 100);
2812  }
2813  if ($this->ImageWrite($this->im, $file, $quality)) {
2814  }
2815  break;
2816  }
2817  }
2818  return $file;
2819  }
2820 
2827  public function destroy()
2828  {
2829  imagedestroy($this->im);
2830  }
2831 
2838  public function imgTag($imgInfo)
2839  {
2840  return '<img src="' . $imgInfo[3] . '" width="' . $imgInfo[0] . '" height="' . $imgInfo[1] . '" border="0" alt="" />';
2841  }
2842 
2852  public function ImageWrite($destImg, $theImage, $quality = 0)
2853  {
2854  imageinterlace($destImg, 0);
2855  $ext = strtolower(substr($theImage, strrpos($theImage, '.') + 1));
2856  $result = false;
2857  switch ($ext) {
2858  case 'jpg':
2859 
2860  case 'jpeg':
2861  if (function_exists('imagejpeg')) {
2862  if ($quality == 0) {
2863  $quality = $this->jpegQuality;
2864  }
2865  $result = imagejpeg($destImg, $theImage, $quality);
2866  }
2867  break;
2868  case 'gif':
2869  if (function_exists('imagegif')) {
2870  imagetruecolortopalette($destImg, true, 256);
2871  $result = imagegif($destImg, $theImage);
2872  }
2873  break;
2874  case 'png':
2875  if (function_exists('imagepng')) {
2876  $result = imagepng($destImg, $theImage);
2877  }
2878  break;
2879  }
2880  if ($result) {
2881  GeneralUtility::fixPermissions($theImage);
2882  }
2883  return $result;
2884  }
2885 
2893  public function imageCreateFromFile($sourceImg)
2894  {
2895  $imgInf = pathinfo($sourceImg);
2896  $ext = strtolower($imgInf['extension']);
2897  switch ($ext) {
2898  case 'gif':
2899  if (function_exists('imagecreatefromgif')) {
2900  return imagecreatefromgif($sourceImg);
2901  }
2902  break;
2903  case 'png':
2904  if (function_exists('imagecreatefrompng')) {
2905  $imageHandle = imagecreatefrompng($sourceImg);
2906  if ($this->saveAlphaLayer) {
2907  imagesavealpha($imageHandle, true);
2908  }
2909  return $imageHandle;
2910  }
2911  break;
2912  case 'jpg':
2913 
2914  case 'jpeg':
2915  if (function_exists('imagecreatefromjpeg')) {
2916  return imagecreatefromjpeg($sourceImg);
2917  }
2918  break;
2919  }
2920  // If non of the above:
2921  $i = @getimagesize($sourceImg);
2922  $im = imagecreatetruecolor($i[0], $i[1]);
2923  $Bcolor = imagecolorallocate($im, 128, 128, 128);
2924  imagefilledrectangle($im, 0, 0, $i[0], $i[1], $Bcolor);
2925  return $im;
2926  }
2927 
2934  public function hexColor($color)
2935  {
2936  $r = dechex($color[0]);
2937  if (strlen($r) < 2) {
2938  $r = '0' . $r;
2939  }
2940  $g = dechex($color[1]);
2941  if (strlen($g) < 2) {
2942  $g = '0' . $g;
2943  }
2944  $b = dechex($color[2]);
2945  if (strlen($b) < 2) {
2946  $b = '0' . $b;
2947  }
2948  return '#' . $r . $g . $b;
2949  }
2950 
2959  public function unifyColors(&$img, $colArr, $closest = false)
2960  {
2961  $retCol = -1;
2962  if (is_array($colArr) && !empty($colArr) && function_exists('imagepng') && function_exists('imagecreatefrompng')) {
2963  $firstCol = array_shift($colArr);
2964  $firstColArr = $this->convertColor($firstCol);
2965  $origName = $preName = $this->randomName() . '.png';
2966  $postName = $this->randomName() . '.png';
2967  $tmpImg = null;
2968  if (count($colArr) > 1) {
2969  $this->ImageWrite($img, $preName);
2970  $firstCol = $this->hexColor($firstColArr);
2971  foreach ($colArr as $transparentColor) {
2972  $transparentColor = $this->convertColor($transparentColor);
2973  $transparentColor = $this->hexColor($transparentColor);
2974  $cmd = '-fill "' . $firstCol . '" -opaque "' . $transparentColor . '"';
2975  $this->imageMagickExec($preName, $postName, $cmd);
2976  $preName = $postName;
2977  }
2978  $this->imageMagickExec($postName, $origName, '');
2979  if (@is_file($origName)) {
2980  $tmpImg = $this->imageCreateFromFile($origName);
2981  }
2982  } else {
2983  $tmpImg = $img;
2984  }
2985  if ($tmpImg) {
2986  $img = $tmpImg;
2987  if ($closest) {
2988  $retCol = imagecolorclosest($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2989  } else {
2990  $retCol = imagecolorexact($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2991  }
2992  }
2993  // Unlink files from process
2994  if (!$this->dontUnlinkTempFiles) {
2995  if ($origName) {
2996  @unlink($origName);
2997  }
2998  if ($postName) {
2999  @unlink($postName);
3000  }
3001  }
3002  }
3003  return $retCol;
3004  }
3005 
3018  public function getTemporaryImageWithText($filename, $textline1, $textline2, $textline3)
3019  {
3020  if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'])) {
3021  throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
3022  }
3023  // Creates the basis for the error image
3024  $basePath = ExtensionManagementUtility::extPath('core') . 'Resources/Public/Images/';
3025  if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
3026  $im = imagecreatefrompng($basePath . 'NotFound.png');
3027  } else {
3028  $im = imagecreatefromgif($basePath . 'NotFound.gif');
3029  }
3030  // Sets background color and print color.
3031  $white = imagecolorallocate($im, 255, 255, 255);
3032  $black = imagecolorallocate($im, 0, 0, 0);
3033  // Prints the text strings with the build-in font functions of GD
3034  $x = 0;
3035  $font = 0;
3036  if ($textline1) {
3037  imagefilledrectangle($im, $x, 9, 56, 16, $white);
3038  imagestring($im, $font, $x, 9, $textline1, $black);
3039  }
3040  if ($textline2) {
3041  imagefilledrectangle($im, $x, 19, 56, 26, $white);
3042  imagestring($im, $font, $x, 19, $textline2, $black);
3043  }
3044  if ($textline3) {
3045  imagefilledrectangle($im, $x, 29, 56, 36, $white);
3046  imagestring($im, $font, $x, 29, substr($textline3, -14), $black);
3047  }
3048  // Outputting the image stream and exit
3049  if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
3050  imagepng($im, $filename);
3051  } else {
3052  imagegif($im, $filename);
3053  }
3054  }
3055 }
ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF=1)
static isFirstPartOfStr($str, $partStr)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
splitString($string, $splitRendering, $fontSize, $fontFile)
getBreakSpace($conf, array $boundingBox=null)
static mkdir_deep($directory, $deepDirectory= '')
static split_fileref($fileNameWithPath)
makeEmboss(&$im, $conf, $workArea, $txtConf)
makeEllipse(&$im, array $conf, array $workArea)
ImageWrite($destImg, $theImage, $quality=0)
makeShadow(&$im, $conf, $workArea, $txtConf)
copyGifOntoGif(&$im, $cpImg, $conf, $workArea)
combineExec($input, $overlay, $mask, $output)
renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF=1)
imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight)
static imageMagickCommand($command, $parameters, $path= '')
static fixPermissions($path, $recursive=false)
makeOutline(&$im, $conf, $workArea, $txtConf)
ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF=1)
static readPngGif($theFile, $output_png=false)
SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF=1)
static exec($command, &$output=null, &$returnValue=0)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
getTemporaryImageWithText($filename, $textline1, $textline2, $textline3)
static makeInstance($className,...$constructorArguments)
static filterAndSortByNumericKeys($setupArr, $acceptAnyKeys=false)
imageMagickExec($input, $output, $params, $frame=0)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
debug($variable= '', $name= '*variable *', $line= '*line *', $file= '*file *', $recursiveDepth=3, $debugLevel=E_DEBUG)
static getFileAbsFileName($filename, $_=null, $_2=null)
imageMagickConvert($imagefile, $newExt= '', $w= '', $h= '', $params= '', $frame= '', $options=[], $mustCreate=false)
outputLevels(&$im, $low, $high, $swap=false)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
unifyColors(&$img, $colArr, $closest=false)