‪TYPO3CMS  ‪main
GifBuilder.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
22 use TYPO3\CMS\Core\Imaging\GraphicalFunctions;
27 use TYPO3\CMS\Core\Type\File\ImageInfo;
36 
57 {
63  protected array ‪$combinedTextStrings = [];
64 
70  protected array ‪$combinedFileNames = [];
71 
75  protected array ‪$data = [];
76  protected array ‪$objBB = [];
77 
81  protected array ‪$charRangeMap = [];
82 
86  protected array ‪$XY = [];
88 
92  protected array ‪$workArea = [];
93 
97  protected array ‪$defaultWorkArea = [];
98 
102  protected bool ‪$saveAlphaLayer = false;
103 
109  protected array ‪$colMap = [
110  'aqua' => [0, 255, 255],
111  'black' => [0, 0, 0],
112  'blue' => [0, 0, 255],
113  'fuchsia' => [255, 0, 255],
114  'gray' => [128, 128, 128],
115  'green' => [0, 128, 0],
116  'lime' => [0, 255, 0],
117  'maroon' => [128, 0, 0],
118  'navy' => [0, 0, 128],
119  'olive' => [128, 128, 0],
120  'purple' => [128, 0, 128],
121  'red' => [255, 0, 0],
122  'silver' => [192, 192, 192],
123  'teal' => [0, 128, 128],
124  'yellow' => [255, 255, 0],
125  'white' => [255, 255, 255],
126  ];
127 
134  public array ‪$setup = [];
135 
139  protected int ‪$w = 0;
143  protected int ‪$h = 0;
144 
148  protected array ‪$offset;
149 
155  protected array ‪$gdlibExtensions = [];
156 
158  protected GraphicalFunctions ‪$imageService;
159 
163  protected bool ‪$processorEffectsEnabled = false;
164 
168  protected int ‪$jpegQuality = 85;
172  protected int ‪$webpQuality = 85;
173 
174  public function ‪__construct()
175  {
176  $gfxConf = ‪$GLOBALS['TYPO3_CONF_VARS']['GFX'];
177  if ($gfxConf['processor_effects'] ?? false) {
178  $this->processorEffectsEnabled = true;
179  }
180  $this->jpegQuality = ‪MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, $this->jpegQuality);
181  if (isset($gfxConf['webp_quality'])) {
182  // see IMG_WEBP_LOSSLESS // https://www.php.net/manual/en/image.constants.php
183  if ($gfxConf['webp_quality'] === 'lossless') {
184  $this->webpQuality = 101;
185  } else {
186  $this->webpQuality = ‪MathUtility::forceIntegerInRange($gfxConf['webp_quality'], 10, 101, $this->webpQuality);
187  }
188  }
189  if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
190  $this->gdlibExtensions[] = 'jpg';
191  $this->gdlibExtensions[] = 'jpeg';
192  }
193  if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
194  $this->gdlibExtensions[] = 'png';
195  }
196  if (function_exists('imagecreatefromwebp') && function_exists('imagewebp')) {
197  $this->gdlibExtensions[] = 'webp';
198  }
199  if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
200  $this->gdlibExtensions[] = 'gif';
201  }
202  $this->imageService = GeneralUtility::makeInstance(GraphicalFunctions::class);
203  $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
204  }
205 
214  public function ‪start($conf, ‪$data)
215  {
216  if (!is_array($conf) || !class_exists(\GdImage::class)) {
217  return;
218  }
219  $this->setup = $conf;
220  $this->data = ‪$data;
221  $this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
222  $this->cObj->start($this->data);
223  // Hook preprocess gifbuilder conf
224  // Added by Julle for 3.8.0
225  //
226  // Lets you pre-process the gifbuilder configuration. for
227  // example you can split a string up into lines and render each
228  // line as TEXT obj, see extension julle_gifbconf
229  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'] ?? [] as $_funcRef) {
230  $_params = ‪$this->setup;
231  $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
232  $this->setup = GeneralUtility::callUserFunction($_funcRef, $_params, $ref);
233  }
234  // Initializing Char Range Map
235  $this->charRangeMap = [];
236  foreach (($conf['charRangeMap.'] ?? []) as $cRMcfgkey => $cRMcfg) {
237  if (is_array($cRMcfg)) {
238  $cRMkey = $conf['charRangeMap.'][substr($cRMcfgkey, 0, -1)];
239  $this->charRangeMap[$cRMkey] = [];
240  $this->charRangeMap[$cRMkey]['charMapConfig'] = $cRMcfg['charMapConfig.'] ?? [];
241  $this->charRangeMap[$cRMkey]['cfgKey'] = substr($cRMcfgkey, 0, -1);
242  $this->charRangeMap[$cRMkey]['multiplicator'] = (float)$cRMcfg['fontSizeMultiplicator'];
243  $this->charRangeMap[$cRMkey]['pixelSpace'] = (int)$cRMcfg['pixelSpaceFontSizeRef'];
244  }
245  }
246  // Getting sorted list of TypoScript keys from setup.
247  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
248  // Setting the background color, passing it through stdWrap
249  $this->setup['backColor'] = $this->cObj->stdWrapValue('backColor', $this->setup, 'white');
250  $this->setup['transparentColor_array'] = explode('|', trim((string)$this->cObj->stdWrapValue('transparentColor', $this->setup)));
251  $this->setup['transparentBackground'] = $this->cObj->stdWrapValue('transparentBackground', $this->setup);
252  // Set default dimensions
253  $this->setup['XY'] = $this->cObj->stdWrapValue('XY', $this->setup);
254  if (!$this->setup['XY']) {
255  $this->setup['XY'] = '120,50';
256  }
257  // Checking TEXT and IMAGE objects for files. If any errors the objects are cleared.
258  // The Bounding Box for the objects is stored in an array
259  foreach ($sKeyArray as $theKey) {
260  $theValue = $this->setup[$theKey];
261  if ((int)$theKey && ($conf = $this->setup[$theKey . '.'] ?? [])) {
262  // Swipes through TEXT and IMAGE-objects
263  switch ($theValue) {
264  case 'TEXT':
265  if ($this->setup[$theKey . '.'] = $this->‪checkTextObj($conf)) {
266  // Adjust font width if max size is set:
267  $maxWidth = $this->cObj->stdWrapValue('maxWidth', $this->setup[$theKey . '.'] ?? []);
268  if ($maxWidth) {
269  $this->setup[$theKey . '.']['fontSize'] = $this->‪fontResize($this->setup[$theKey . '.']);
270  }
271  // Calculate bounding box:
272  $txtInfo = $this->‪calcBBox($this->setup[$theKey . '.']);
273  $this->setup[$theKey . '.']['BBOX'] = $txtInfo;
274  $this->objBB[$theKey] = $txtInfo;
275  }
276  break;
277  case 'IMAGE':
278  $fileInfo = $this->‪getResource($conf['file'] ?? '', $conf['file.'] ?? []);
279  if ($fileInfo) {
280  $this->combinedFileNames[] = preg_replace('/\\.[[:alnum:]]+$/', '', ‪PathUtility::basename($fileInfo[3]));
281  if (($fileInfo['processedFile'] ?? null) instanceof ‪ProcessedFile) {
282  // Use processed file, if a FAL file has been processed by GIFBUILDER (e.g. scaled/cropped)
283  $this->setup[$theKey . '.']['file'] = $fileInfo['processedFile']->getForLocalProcessing(false);
284  } elseif (!isset($fileInfo['origFile']) && ($fileInfo['originalFile'] ?? null) instanceof ‪File) {
285  // Use FAL file with getForLocalProcessing to circumvent problems with umlauts, if it is a FAL file (origFile not set)
286  $originalFile = $fileInfo['originalFile'];
287  $this->setup[$theKey . '.']['file'] = $originalFile->getForLocalProcessing(false);
288  } else {
289  // Use normal path from fileInfo if it is a non-FAL file (even non-FAL files have originalFile set, but only non-FAL files have origFile set)
290  $this->setup[$theKey . '.']['file'] = $fileInfo[3];
291  }
292 
293  // only pass necessary parts of fileInfo further down, to not incorporate facts as
294  // CropScaleMask runs in this request, that may not occur in subsequent calls and change
295  // the md5 of the generated file name
296  $essentialFileInfo = $fileInfo;
297  unset($essentialFileInfo['originalFile'], $essentialFileInfo['processedFile']);
298 
299  $this->setup[$theKey . '.']['BBOX'] = $essentialFileInfo;
300  $this->objBB[$theKey] = $essentialFileInfo;
301  if ($conf['mask'] ?? false) {
302  $maskInfo = $this->‪getResource($conf['mask'], $conf['mask.'] ?? []);
303  if ($maskInfo) {
304  // the same selection criteria as regarding fileInfo above apply here
305  if (($maskInfo['processedFile'] ?? null) instanceof ‪ProcessedFile) {
306  $this->setup[$theKey . '.']['mask'] = $maskInfo['processedFile']->getForLocalProcessing(false);
307  } elseif (!isset($maskInfo['origFile']) && $maskInfo['originalFile'] instanceof ‪File) {
308  $originalFile = $maskInfo['originalFile'];
309  $this->setup[$theKey . '.']['mask'] = $originalFile->getForLocalProcessing(false);
310  } else {
311  $this->setup[$theKey . '.']['mask'] = $maskInfo[3];
312  }
313  } else {
314  $this->setup[$theKey . '.']['mask'] = '';
315  }
316  }
317  } else {
318  unset($this->setup[$theKey . '.']);
319  }
320  break;
321  }
322  // Checks if disabled is set
323  if ($conf['if.'] ?? false) {
324  ‪$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
325  ‪$cObj->‪start($this->data);
326  if (!‪$cObj->‪checkIf($conf['if.'])) {
327  unset($this->setup[$theKey]);
328  unset($this->setup[$theKey . '.']);
329  unset($this->objBB[$theKey]);
330  }
331  }
332  }
333  }
334  // Calculate offsets on elements
335  $this->setup['XY'] = $this->‪calcOffset($this->setup['XY']);
336  $this->setup['offset'] = (string)$this->cObj->stdWrapValue('offset', $this->setup);
337  $this->setup['offset'] = $this->‪calcOffset($this->setup['offset']);
338  $this->setup['workArea'] = (string)$this->cObj->stdWrapValue('workArea', $this->setup);
339  $this->setup['workArea'] = $this->‪calcOffset($this->setup['workArea']);
340  foreach ($sKeyArray as $theKey) {
341  $theValue = $this->setup[$theKey];
342  if ((int)$theKey && ($this->setup[$theKey . '.'] ?? false)) {
343  switch ($theValue) {
344  case 'TEXT':
345 
346  case 'IMAGE':
347  if (isset($this->setup[$theKey . '.']['offset.'])) {
348  $this->setup[$theKey . '.']['offset'] = $this->cObj->stdWrapValue('offset', $this->setup[$theKey . '.']);
349  unset($this->setup[$theKey . '.']['offset.']);
350  }
351  if ($this->setup[$theKey . '.']['offset'] ?? false) {
352  $this->setup[$theKey . '.']['offset'] = $this->‪calcOffset($this->setup[$theKey . '.']['offset']);
353  }
354  break;
355  case 'BOX':
356 
357  case 'ELLIPSE':
358  if (isset($this->setup[$theKey . '.']['dimensions.'])) {
359  $this->setup[$theKey . '.']['dimensions'] = $this->cObj->stdWrapValue('dimensions', $this->setup[$theKey . '.']);
360  unset($this->setup[$theKey . '.']['dimensions.']);
361  }
362  if ($this->setup[$theKey . '.']['dimensions'] ?? false) {
363  $this->setup[$theKey . '.']['dimensions'] = $this->‪calcOffset($this->setup[$theKey . '.']['dimensions']);
364  }
365  break;
366  case 'WORKAREA':
367  if (isset($this->setup[$theKey . '.']['set.'])) {
368  $this->setup[$theKey . '.']['set'] = $this->cObj->stdWrapValue('set', $this->setup[$theKey . '.']);
369  unset($this->setup[$theKey . '.']['set.']);
370  }
371  if ($this->setup[$theKey . '.']['set'] ?? false) {
372  $this->setup[$theKey . '.']['set'] = $this->‪calcOffset($this->setup[$theKey . '.']['set']);
373  }
374  break;
375  case 'CROP':
376  if (isset($this->setup[$theKey . '.']['crop.'])) {
377  $this->setup[$theKey . '.']['crop'] = $this->cObj->stdWrapValue('crop', $this->setup[$theKey . '.']);
378  unset($this->setup[$theKey . '.']['crop.']);
379  }
380  if ($this->setup[$theKey . '.']['crop'] ?? false) {
381  $this->setup[$theKey . '.']['crop'] = $this->‪calcOffset($this->setup[$theKey . '.']['crop']);
382  }
383  break;
384  case 'SCALE':
385  if (isset($this->setup[$theKey . '.']['width.'])) {
386  $this->setup[$theKey . '.']['width'] = $this->cObj->stdWrapValue('width', $this->setup[$theKey . '.']);
387  unset($this->setup[$theKey . '.']['width.']);
388  }
389  if ($this->setup[$theKey . '.']['width'] ?? false) {
390  $this->setup[$theKey . '.']['width'] = $this->‪calcOffset($this->setup[$theKey . '.']['width']);
391  }
392  if (isset($this->setup[$theKey . '.']['height.'])) {
393  $this->setup[$theKey . '.']['height'] = $this->cObj->stdWrapValue('height', $this->setup[$theKey . '.']);
394  unset($this->setup[$theKey . '.']['height.']);
395  }
396  if ($this->setup[$theKey . '.']['height'] ?? false) {
397  $this->setup[$theKey . '.']['height'] = $this->‪calcOffset($this->setup[$theKey . '.']['height']);
398  }
399  break;
400  }
401  }
402  }
403  // Get trivial data
404  ‪$XY = GeneralUtility::intExplode(',', $this->setup['XY']);
405  $maxWidth = (int)$this->cObj->stdWrapValue('maxWidth', $this->setup);
406  $maxHeight = (int)$this->cObj->stdWrapValue('maxHeight', $this->setup);
407  ‪$XY[0] = ‪MathUtility::forceIntegerInRange(‪$XY[0], 1, $maxWidth ?: 2000);
408  ‪$XY[1] = ‪MathUtility::forceIntegerInRange(‪$XY[1], 1, $maxHeight ?: 2000);
409  $this->XY = ‪$XY;
410  $this->w = ‪$XY[0];
411  $this->h = ‪$XY[1];
412  $this->offset = GeneralUtility::intExplode(',', $this->setup['offset']);
413  // this sets the workArea
414  $this->‪setWorkArea($this->setup['workArea']);
415  // this sets the default to the current
416  $this->defaultWorkArea = ‪$this->workArea;
417  }
418 
428  public function ‪gifBuild()
429  {
430  if (!$this->setup || !class_exists(\GdImage::class)) {
431  return '';
432  }
433 
434  // Relative to Environment::getPublicPath()
435  $gifFileName = $this->‪fileName();
436  $relativeFileName = 'typo3temp/assets/images/' . $gifFileName;
437  $fullFileName = ‪Environment::getPublicPath() . '/' . $relativeFileName;
438 
439  if (!file_exists($fullFileName)) {
440  // Create temporary directory if not done
441  ‪GeneralUtility::mkdir_deep(dirname($fullFileName));
442  // Create file
443  $gdImage = $this->‪make();
444  $this->‪output($gdImage, $fullFileName);
445  imagedestroy($gdImage);
446  }
447  return $relativeFileName;
448  }
449 
457  protected function ‪output(\GdImage $gdImage, string $file): void
458  {
459  if ($file === '') {
460  return;
461  }
462 
463  $reg = [];
464  preg_match('/([^\\.]*)$/', $file, $reg);
465  $ext = strtolower($reg[0]);
466  switch ($ext) {
467  case 'gif':
468  case 'png':
469  $this->‪ImageWrite($gdImage, $file);
470  break;
471  case 'jpg':
472  case 'jpeg':
473  // Use the default
474  $quality = isset($this->setup['quality']) ? ‪MathUtility::forceIntegerInRange((int)$this->setup['quality'], 10, 100) : 0;
475  $this->‪ImageWrite($gdImage, $file, $quality);
476  break;
477  case 'webp':
478  // Quality can also be set to IMG_WEBP_LOSSLESS = 101
479  $quality = isset($this->setup['quality']) ? ‪MathUtility::forceIntegerInRange((int)$this->setup['quality'], 10, 101) : 0;
480  $this->‪ImageWrite($gdImage, $file, $quality);
481  break;
482  }
483  }
484 
497  protected function ‪make(): \GdImage
498  {
499  // Get trivial data
501  // Reset internal properties
502  $this->saveAlphaLayer = false;
503  // Gif-start
504  $im = imagecreatetruecolor(‪$XY[0], ‪$XY[1]);
505  if (!$im instanceof \GdImage) {
506  throw new \RuntimeException('imagecreatetruecolor returned false', 1598350445);
507  }
508  $this->w = ‪$XY[0];
509  $this->h = ‪$XY[1];
510  // Transparent layer as background if set and requirements are met
511  if (($this->setup['backColor'] ?? '') === 'transparent' && (empty($this->setup['format']) || $this->setup['format'] === 'png')) {
512  // Set transparency properties
513  imagesavealpha($im, true);
514  // Fill with a transparent background
515  $transparentColor = imagecolorallocatealpha($im, 0, 0, 0, 127);
516  imagefill($im, 0, 0, $transparentColor);
517  // Set internal properties to keep the transparency over the rendering process
518  $this->saveAlphaLayer = true;
519  // Force PNG in case no format is set
520  $this->setup['format'] = 'png';
521  $BGcols = [];
522  } else {
523  // Fill the background with the given color
524  $BGcols = $this->‪convertColor($this->setup['backColor']);
525  $Bcolor = imagecolorallocate($im, $BGcols[0], $BGcols[1], $BGcols[2]);
526  imagefilledrectangle($im, 0, 0, ‪$XY[0], ‪$XY[1], $Bcolor);
527  }
528  // Traverse the GIFBUILDER objects and render each one:
529  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
530  foreach ($sKeyArray as $theKey) {
531  $theValue = $this->setup[$theKey];
532  if ((int)$theKey && ($conf = $this->setup[$theKey . '.'] ?? [])) {
533  // apply stdWrap to all properties, except for TEXT objects
534  // all properties of the TEXT sub-object have already been stdWrap-ped
535  // before in ->checkTextObj()
536  if ($theValue !== 'TEXT') {
537  $isStdWrapped = [];
538  foreach ($conf as $key => $value) {
539  $parameter = rtrim($key, '.');
540  if (!($isStdWrapped[$parameter] ?? false) && isset($conf[$parameter . '.'])) {
541  $conf[$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
542  $isStdWrapped[$parameter] = 1;
543  }
544  }
545  }
546 
547  switch ($theValue) {
548  case 'IMAGE':
549  if ($conf['mask'] ?? false) {
550  $this->‪maskImageOntoImage($im, $conf, $this->workArea);
551  } else {
552  $this->‪copyImageOntoImage($im, $conf, $this->workArea);
553  }
554  break;
555  case 'TEXT':
556  if (!($conf['hide'] ?? false)) {
557  if (is_array($conf['shadow.'] ?? null)) {
558  $isStdWrapped = [];
559  foreach ($conf['shadow.'] as $key => $value) {
560  $parameter = rtrim($key, '.');
561  if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
562  $conf['shadow.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
563  $isStdWrapped[$parameter] = 1;
564  }
565  }
566  $this->‪makeShadow($im, $conf['shadow.'], $this->workArea, $conf);
567  }
568  if (is_array($conf['emboss.'] ?? null)) {
569  $isStdWrapped = [];
570  foreach ($conf['emboss.'] as $key => $value) {
571  $parameter = rtrim($key, '.');
572  if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
573  $conf['emboss.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
574  $isStdWrapped[$parameter] = 1;
575  }
576  }
577  $this->‪makeEmboss($im, $conf['emboss.'], $this->workArea, $conf);
578  }
579  if (is_array($conf['outline.'] ?? null)) {
580  $isStdWrapped = [];
581  foreach ($conf['outline.'] as $key => $value) {
582  $parameter = rtrim($key, '.');
583  if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
584  $conf['outline.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
585  $isStdWrapped[$parameter] = 1;
586  }
587  }
588  $this->‪makeOutline($im, $conf['outline.'], $this->workArea, $conf);
589  }
590  $this->‪makeText($im, $conf, $this->workArea);
591  }
592  break;
593  case 'OUTLINE':
594  if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->‪checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
595  $this->‪makeOutline($im, $conf, $this->workArea, $txtConf);
596  }
597  break;
598  case 'EMBOSS':
599  if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->‪checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
600  $this->‪makeEmboss($im, $conf, $this->workArea, $txtConf);
601  }
602  break;
603  case 'SHADOW':
604  if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->‪checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
605  $this->‪makeShadow($im, $conf, $this->workArea, $txtConf);
606  }
607  break;
608  case 'BOX':
609  $this->‪makeBox($im, $conf, $this->workArea);
610  break;
611  case 'EFFECT':
612  $this->‪makeEffect($im, $conf);
613  break;
614  case 'ADJUST':
615  $this->‪adjust($im, $conf);
616  break;
617  case 'CROP':
618  $this->‪crop($im, $conf);
619  break;
620  case 'SCALE':
621  $this->‪scale($im, $conf);
622  break;
623  case 'WORKAREA':
624  if ($conf['set']) {
625  // this sets the workArea
626  $this->‪setWorkArea($conf['set']);
627  }
628  if (isset($conf['clear'])) {
629  // This sets the current to the default;
630  $this->workArea = ‪$this->defaultWorkArea;
631  }
632  break;
633  case 'ELLIPSE':
634  $this->‪makeEllipse($im, $conf, $this->workArea);
635  break;
636  }
637  }
638  }
639  // Preserve alpha transparency
640  if (!$this->saveAlphaLayer) {
641  if ($this->setup['transparentBackground']) {
642  // Auto transparent background is set
643  $Bcolor = imagecolorclosest($im, $BGcols[0], $BGcols[1], $BGcols[2]);
644  imagecolortransparent($im, $Bcolor);
645  } elseif (is_array($this->setup['transparentColor_array'])) {
646  // Multiple transparent colors are set. This is done via the trick that all transparent colors get
647  // converted to one color and then this one gets set as transparent as png/gif can just have one
648  // transparent color.
649  $Tcolor = $this->‪unifyColors($im, $this->setup['transparentColor_array'], (bool)($this->setup['transparentColor.']['closest'] ?? false));
650  if ($Tcolor >= 0) {
651  imagecolortransparent($im, $Tcolor);
652  }
653  }
654  }
655  return $im;
656  }
657 
668  protected function ‪maskImageOntoImage(\GdImage &$im, array $conf, array ‪$workArea): void
669  {
670  if ($conf['file'] && $conf['mask']) {
671  $imgInf = pathinfo($conf['file']);
672  $imgExt = strtolower($imgInf['extension']);
673  if (!in_array($imgExt, $this->gdlibExtensions, true)) {
674  $BBimage = $this->imageService->imageMagickConvert($conf['file'], 'png');
675  } else {
676  $BBimage = $this->imageService->getImageDimensions($conf['file']);
677  }
678  $maskInf = pathinfo($conf['mask']);
679  $maskExt = strtolower($maskInf['extension']);
680  if (!in_array($maskExt, $this->gdlibExtensions, true)) {
681  $BBmask = $this->imageService->imageMagickConvert($conf['mask'], 'png');
682  } else {
683  $BBmask = $this->imageService->getImageDimensions($conf['mask']);
684  }
685  if ($BBimage && $BBmask) {
686  ‪$w = imagesx($im);
687  ‪$h = imagesy($im);
688  $tmpStr = $this->imageService->randomName();
689  $theImage = $tmpStr . '_img.png';
690  $theDest = $tmpStr . '_dest.png';
691  $theMask = $tmpStr . '_mask.png';
692  // Prepare overlay image
693  $cpImg = $this->‪imageCreateFromFile($BBimage[3]);
694  $destImg = imagecreatetruecolor(‪$w, ‪$h);
695  // Preserve alpha transparency
696  if ($this->saveAlphaLayer) {
697  imagesavealpha($destImg, true);
698  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
699  imagefill($destImg, 0, 0, $Bcolor);
700  } else {
701  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
702  imagefilledrectangle($destImg, 0, 0, ‪$w, ‪$h, $Bcolor);
703  }
704  $this->‪copyGifOntoGif($destImg, $cpImg, $conf, ‪$workArea);
705  $this->‪ImageWrite($destImg, $theImage);
706  imagedestroy($cpImg);
707  imagedestroy($destImg);
708  // Prepare mask image
709  $cpImg = $this->‪imageCreateFromFile($BBmask[3]);
710  $destImg = imagecreatetruecolor(‪$w, ‪$h);
711  if ($this->saveAlphaLayer) {
712  imagesavealpha($destImg, true);
713  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
714  imagefill($destImg, 0, 0, $Bcolor);
715  } else {
716  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
717  imagefilledrectangle($destImg, 0, 0, ‪$w, ‪$h, $Bcolor);
718  }
719  $this->‪copyGifOntoGif($destImg, $cpImg, $conf, ‪$workArea);
720  $this->‪ImageWrite($destImg, $theMask);
721  imagedestroy($cpImg);
722  imagedestroy($destImg);
723  // Mask the images
724  $this->‪ImageWrite($im, $theDest);
725  // Let combineExec handle maskNegation
726  $this->imageService->combineExec($theDest, $theImage, $theMask, $theDest);
727  // The main image is loaded again...
728  $backIm = $this->‪imageCreateFromFile($theDest);
729  // ... and if nothing went wrong we load it onto the old one.
730  if ($backIm) {
731  if (!$this->saveAlphaLayer) {
732  imagecolortransparent($backIm, -1);
733  }
734  $im = $backIm;
735  }
736  // Unlink files from process
737  unlink($theDest);
738  unlink($theImage);
739  unlink($theMask);
740  }
741  }
742  }
743 
753  protected function ‪copyImageOntoImage(\GdImage &$im, array $conf, array ‪$workArea): void
754  {
755  if ($conf['file']) {
756  if (!in_array($conf['BBOX'][2], $this->gdlibExtensions, true)) {
757  $conf['BBOX'] = $this->imageService->imageMagickConvert($conf['BBOX'][3], 'png');
758  $conf['file'] = $conf['BBOX'][3];
759  }
760  $cpImg = $this->‪imageCreateFromFile($conf['file']);
761  $this->‪copyGifOntoGif($im, $cpImg, $conf, ‪$workArea);
762  imagedestroy($cpImg);
763  }
764  }
765 
775  public function ‪makeText(\GdImage &$im, array $conf, array ‪$workArea): void
776  {
777  // Spacing
778  [$spacing, $wordSpacing] = $this->‪calcWordSpacing($conf);
779  // Position
780  $txtPos = $this->‪txtPosition($conf, ‪$workArea, $conf['BBOX']);
781  $theText = $conf['text'] ?? '';
782  // Font Color:
783  $cols = $this->‪convertColor($conf['fontColor']);
784  // NiceText is calculated
785  if (!($conf['niceText'] ?? false)) {
786  $Fcolor = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
787  // antiAliasing is setup:
788  $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
789  for ($a = 0; $a < $conf['iterations']; $a++) {
790  // If any kind of spacing applies, we use this function:
791  if ($spacing || $wordSpacing) {
792  $this->‪SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
793  } else {
794  $this->‪renderTTFText($im, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'] ?? [], $conf);
795  }
796  }
797  } else {
798  // NICETEXT::
799  // options anti_aliased and iterations is NOT available when doing this!!
800  ‪$w = imagesx($im);
801  ‪$h = imagesy($im);
802  $tmpStr = $this->imageService->randomName();
803  $fileMenu = $tmpStr . '_menuNT.png';
804  $fileColor = $tmpStr . '_colorNT.png';
805  $fileMask = $tmpStr . '_maskNT.png';
806  // Scalefactor
807  $sF = ‪MathUtility::forceIntegerInRange(($conf['niceText.']['scaleFactor'] ?? 2), 2, 5);
808  $newW = (int)ceil($sF * imagesx($im));
809  $newH = (int)ceil($sF * imagesy($im));
810  // Make mask
811  $maskImg = imagecreatetruecolor($newW, $newH);
812  $Bcolor = imagecolorallocate($maskImg, 255, 255, 255);
813  imagefilledrectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
814  $Fcolor = imagecolorallocate($maskImg, 0, 0, 0);
815  // If any kind of spacing applies, we use this function:
816  if ($spacing || $wordSpacing) {
817  $this->‪SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
818  } else {
819  $this->‪renderTTFText($maskImg, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'] ?? [], $conf, $sF);
820  }
821  $this->‪ImageWrite($maskImg, $fileMask);
822  imagedestroy($maskImg);
823  // Downscales the mask
824  if (!$this->processorEffectsEnabled) {
825  $command = trim($this->imageService->scalecmd . ' ' . ‪$w . 'x' . ‪$h . '! -negate');
826  } else {
827  $command = trim(($conf['niceText.']['before'] ?? '') . ' ' . $this->imageService->scalecmd . ' ' . ‪$w . 'x' . ‪$h . '! ' . ($conf['niceText.']['after'] ?? '') . ' -negate');
828  if (isset($conf['niceText.']['sharpen'])) {
829  $command .= $this->imageService->v5_sharpen($conf['niceText.']['sharpen']);
830  }
831  }
832  $this->imageService->imageMagickExec($fileMask, $fileMask, $command);
833  // Make the color-file
834  $colorImg = imagecreatetruecolor(‪$w, ‪$h);
835  $Ccolor = imagecolorallocate($colorImg, $cols[0], $cols[1], $cols[2]);
836  imagefilledrectangle($colorImg, 0, 0, ‪$w, ‪$h, $Ccolor);
837  $this->‪ImageWrite($colorImg, $fileColor);
838  imagedestroy($colorImg);
839  // The mask is applied
840  // The main pictures is saved temporarily
841  $this->‪ImageWrite($im, $fileMenu);
842  $this->imageService->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
843  // The main image is loaded again...
844  $backIm = $this->‪imageCreateFromFile($fileMenu);
845  // ... and if nothing went wrong we load it onto the old one.
846  if ($backIm) {
847  if (!$this->saveAlphaLayer) {
848  imagecolortransparent($backIm, -1);
849  }
850  $im = $backIm;
851  }
852  // Deleting temporary files;
853  unlink($fileMenu);
854  unlink($fileColor);
855  unlink($fileMask);
856  }
857  }
858 
869  protected function ‪makeOutline(\GdImage &$im, array $conf, array ‪$workArea, array $txtConf): void
870  {
871  $thickness = (int)$conf['thickness'];
872  if ($thickness) {
873  $txtConf['fontColor'] = $conf['color'];
874  $outLineDist = ‪MathUtility::forceIntegerInRange($thickness, 1, 2);
875  for ($b = 1; $b <= $outLineDist; $b++) {
876  if ($b == 1) {
877  $it = 8;
878  } else {
879  $it = 16;
880  }
881  $outL = $this->‪circleOffset($b, $it);
882  for ($a = 0; $a < $it; $a++) {
883  $this->‪makeText($im, $txtConf, $this->‪applyOffset($workArea, $outL[$a]));
884  }
885  }
886  }
887  }
888 
899  protected function ‪makeEmboss(\GdImage &$im, array $conf, array ‪$workArea, array $txtConf): void
900  {
901  $conf['color'] = $conf['highColor'];
902  $this->‪makeShadow($im, $conf, ‪$workArea, $txtConf);
903  $newOffset = GeneralUtility::intExplode(',', (string)($conf['offset'] ?? ''));
904  $newOffset[0] *= -1;
905  $newOffset[1] *= -1;
906  $conf['offset'] = implode(',', $newOffset);
907  $conf['color'] = $conf['lowColor'];
908  $this->‪makeShadow($im, $conf, ‪$workArea, $txtConf);
909  }
910 
924  public function ‪makeShadow(\GdImage &$im, array $conf, array ‪$workArea, array $txtConf): void
925  {
926  ‪$workArea = $this->‪applyOffset($workArea, GeneralUtility::intExplode(',', (string)($conf['offset'])));
927  $blurRate = ‪MathUtility::forceIntegerInRange((int)$conf['blur'], 0, 99);
928  // No effects if ImageMagick ver. 5+
929  if (!$blurRate || !$this->processorEffectsEnabled) {
930  $txtConf['fontColor'] = $conf['color'];
931  $this->‪makeText($im, $txtConf, ‪$workArea);
932  } else {
933  ‪$w = imagesx($im);
934  ‪$h = imagesy($im);
935  // Area around the blur used for cropping something
936  $blurBorder = 3;
937  $tmpStr = $this->imageService->randomName();
938  $fileMenu = $tmpStr . '_menu.png';
939  $fileColor = $tmpStr . '_color.png';
940  $fileMask = $tmpStr . '_mask.png';
941  // BlurColor Image laves
942  $blurColImg = imagecreatetruecolor(‪$w, ‪$h);
943  $bcols = $this->‪convertColor($conf['color']);
944  $Bcolor = imagecolorallocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
945  imagefilledrectangle($blurColImg, 0, 0, ‪$w, ‪$h, $Bcolor);
946  $this->‪ImageWrite($blurColImg, $fileColor);
947  imagedestroy($blurColImg);
948  // The mask is made: BlurTextImage
949  $blurTextImg = imagecreatetruecolor(‪$w + $blurBorder * 2, ‪$h + $blurBorder * 2);
950  // Black background
951  $Bcolor = imagecolorallocate($blurTextImg, 0, 0, 0);
952  imagefilledrectangle($blurTextImg, 0, 0, ‪$w + $blurBorder * 2, ‪$h + $blurBorder * 2, $Bcolor);
953  $txtConf['fontColor'] = 'white';
954  $blurBordArr = [$blurBorder, $blurBorder];
955  $this->‪makeText($blurTextImg, $txtConf, $this->‪applyOffset($workArea, $blurBordArr));
956  // Dump to temporary file
957  $this->‪ImageWrite($blurTextImg, $fileMask);
958  // Destroy
959  imagedestroy($blurTextImg);
960  $command = $this->imageService->v5_blur($blurRate + 1);
961  $this->imageService->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
962  // The mask is loaded again
963  $blurTextImg_tmp = $this->‪imageCreateFromFile($fileMask);
964  // If nothing went wrong we continue with the blurred mask
965  if ($blurTextImg_tmp) {
966  // Cropping the border from the mask
967  $blurTextImg = imagecreatetruecolor(‪$w, ‪$h);
968  $this->‪imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, ‪$w, ‪$h, ‪$w, ‪$h);
969  // Destroy the temporary mask
970  imagedestroy($blurTextImg_tmp);
971  // Adjust the mask
972  $intensity = 40;
973  if ($conf['intensity'] ?? false) {
974  $intensity = ‪MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
975  }
976  $intensity = (int)ceil(255 - $intensity / 100 * 255);
977  $this->‪inputLevels($blurTextImg, 0, $intensity);
978  $opacity = ‪MathUtility::forceIntegerInRange((int)$conf['opacity'], 0, 100);
979  if ($opacity && $opacity < 100) {
980  $high = (int)ceil(255 * $opacity / 100);
981  // Reducing levels as the opacity demands
982  $this->‪outputLevels($blurTextImg, 0, $high);
983  }
984  // Dump the mask again
985  $this->‪ImageWrite($blurTextImg, $fileMask);
986  // Destroy the mask
987  imagedestroy($blurTextImg);
988  // The pictures are combined
989  // The main pictures is saved temporarily
990  $this->‪ImageWrite($im, $fileMenu);
991  $this->imageService->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
992  // The main image is loaded again...
993  $backIm = $this->‪imageCreateFromFile($fileMenu);
994  // ... and if nothing went wrong we load it onto the old one.
995  if ($backIm) {
996  if (!$this->saveAlphaLayer) {
997  imagecolortransparent($backIm, -1);
998  }
999  $im = $backIm;
1000  }
1001  }
1002  // Deleting temporary files;
1003  unlink($fileMenu);
1004  unlink($fileColor);
1005  unlink($fileMask);
1006  }
1007  }
1008 
1018  public function ‪makeBox(\GdImage &$im, array $conf, array ‪$workArea): void
1019  {
1020  $cords = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1021  $conf['offset'] = $cords[0] . ',' . $cords[1];
1022  $cords = $this->‪objPosition($conf, ‪$workArea, [$cords[2], $cords[3]]);
1023  $cols = $this->‪convertColor($conf['color'] ?? '');
1024  $opacity = 0;
1025  if (isset($conf['opacity'])) {
1026  // conversion:
1027  // PHP 0 = opaque, 127 = transparent
1028  // TYPO3 100 = opaque, 0 = transparent
1029  $opacity = ‪MathUtility::forceIntegerInRange((int)$conf['opacity'], 1, 100, 1);
1030  $opacity = (int)abs($opacity - 100);
1031  $opacity = (int)round(127 * $opacity / 100);
1032  }
1033  $tmpColor = imagecolorallocatealpha($im, $cols[0], $cols[1], $cols[2], $opacity);
1034  imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1035  }
1036 
1057  public function ‪makeEllipse(\GdImage &$im, array $conf, array ‪$workArea): void
1058  {
1059  $ellipseConfiguration = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1060  // Ellipse offset inside workArea (x/y)
1061  $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1062  // @see objPosition
1063  $imageCoordinates = $this->‪objPosition($conf, ‪$workArea, [$ellipseConfiguration[2], $ellipseConfiguration[3]]);
1064  $color = $this->‪convertColor($conf['color'] ?? '');
1065  $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1066  imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1067  }
1068 
1078  protected function ‪makeEffect(\GdImage &$im, array $conf): void
1079  {
1080  $commands = $this->‪IMparams($conf['value']);
1081  if ($commands) {
1082  $this->‪applyImageMagickToPHPGif($im, $commands);
1083  }
1084  }
1085 
1096  protected function ‪adjust(\GdImage &$im, array $conf): void
1097  {
1098  ‪$setup = $conf['value'];
1099  if (!trim(‪$setup)) {
1100  return;
1101  }
1102  $effects = explode('|', ‪$setup);
1103  foreach ($effects as $val) {
1104  $pairs = explode('=', $val, 2);
1105  $value = trim($pairs[1]);
1106  $effect = strtolower(trim($pairs[0]));
1107  switch ($effect) {
1108  case 'inputlevels':
1109  // low,high
1110  $params = GeneralUtility::intExplode(',', $value);
1111  $this->‪inputLevels($im, $params[0], $params[1]);
1112  break;
1113  case 'outputlevels':
1114  $params = GeneralUtility::intExplode(',', $value);
1115  $this->‪outputLevels($im, $params[0], $params[1]);
1116  break;
1117  case 'autolevels':
1118  $this->‪autolevels($im);
1119  break;
1120  }
1121  }
1122  }
1123 
1131  protected function ‪crop(\GdImage &$im, array $conf): void
1132  {
1133  // Clears workArea to total image
1134  $this->‪setWorkArea('');
1135  $cords = GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1136  $conf['offset'] = $cords[0] . ',' . $cords[1];
1137  $cords = $this->‪objPosition($conf, $this->workArea, [$cords[2], $cords[3]]);
1138  $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1139  $cols = $this->‪convertColor(!empty($conf['backColor']) ? $conf['backColor'] : $this->setup['backColor']);
1140  $Bcolor = imagecolorallocate($newIm, $cols[0], $cols[1], $cols[2]);
1141  imagefilledrectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1142  $newConf = [];
1143  ‪$workArea = [0, 0, $cords[2], $cords[3]];
1144  if ($cords[0] < 0) {
1145  ‪$workArea[0] = abs($cords[0]);
1146  } else {
1147  $newConf['offset'] = -$cords[0];
1148  }
1149  if ($cords[1] < 0) {
1150  ‪$workArea[1] = abs($cords[1]);
1151  } else {
1152  $newConf['offset'] .= ',' . -$cords[1];
1153  }
1154  $this->‪copyGifOntoGif($newIm, $im, $newConf, ‪$workArea);
1155  $im = $newIm;
1156  $this->w = imagesx($im);
1157  $this->h = imagesy($im);
1158  // Clears workArea to total image
1159  $this->‪setWorkArea('');
1160  }
1161 
1169  protected function ‪scale(\GdImage &$im, array $conf): void
1170  {
1171  if ($conf['width'] || $conf['height'] || $conf['params']) {
1172  $tmpStr = $this->imageService->randomName();
1173  $theFile = $tmpStr . '.png';
1174  $this->‪ImageWrite($im, $theFile);
1175  $theNewFile = $this->imageService->imageMagickConvert($theFile, 'png', $conf['width'] ?? '', $conf['height'] ?? '', $conf['params'] ?? '');
1176  $tmpImg = $this->‪imageCreateFromFile($theNewFile[3]);
1177  if ($tmpImg) {
1178  imagedestroy($im);
1179  $im = $tmpImg;
1180  $this->w = imagesx($im);
1181  $this->h = imagesy($im);
1182  // Clears workArea to total image
1183  $this->‪setWorkArea('');
1184  }
1185  unlink($theFile);
1186  if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1187  unlink($theNewFile[3]);
1188  }
1189  }
1190  }
1191 
1200  protected function ‪setWorkArea(string ‪$workArea): void
1201  {
1202  $this->workArea = GeneralUtility::intExplode(',', ‪$workArea);
1203  $this->workArea = $this->‪applyOffset($this->workArea, $this->offset);
1204  if (!($this->workArea[2] ?? false)) {
1205  $this->workArea[2] = ‪$this->w;
1206  }
1207  if (!($this->workArea[3] ?? false)) {
1208  $this->workArea[3] = ‪$this->h;
1209  }
1210  }
1211 
1212  /*********************************************
1213  *
1214  * Various helper functions
1215  *
1216  ********************************************/
1227  protected function ‪checkTextObj(array $conf): ?array
1228  {
1229  ‪$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
1230  ‪$cObj->‪start($this->data);
1231  $isStdWrapped = [];
1232  foreach ($conf as $key => $value) {
1233  $parameter = rtrim($key, '.');
1234  if (!($isStdWrapped[$parameter] ?? false) && isset($conf[$parameter . '.'])) {
1235  $conf[$parameter] = ‪$cObj->‪stdWrapValue($parameter, $conf);
1236  $isStdWrapped[$parameter] = 1;
1237  }
1238  }
1239 
1240  if (!is_null($conf['fontFile'] ?? null)) {
1241  $conf['fontFile'] = $this->‪checkFile($conf['fontFile']);
1242  }
1243  if (!($conf['fontFile'] ?? false)) {
1244  $conf['fontFile'] = $this->‪checkFile('EXT:core/Resources/Private/Font/nimbus.ttf');
1245  }
1246  if (!($conf['iterations'] ?? false)) {
1247  $conf['iterations'] = 1;
1248  }
1249  if (!($conf['fontSize'] ?? false)) {
1250  $conf['fontSize'] = 12;
1251  }
1252  // If any kind of spacing applies, we cannot use angles!!
1253  if (($conf['spacing'] ?? false) || ($conf['wordSpacing'] ?? false)) {
1254  $conf['angle'] = 0;
1255  }
1256  if (!isset($conf['antiAlias'])) {
1257  $conf['antiAlias'] = 1;
1258  }
1259  $conf['fontColor'] = trim($conf['fontColor'] ?? '');
1260  // Strip HTML
1261  if (!($conf['doNotStripHTML'] ?? false)) {
1262  $conf['text'] = strip_tags($conf['text'] ?? '');
1263  }
1264  $this->combinedTextStrings[] = strip_tags($conf['text'] ?? '');
1265  // Max length = 100 if automatic line breaks are not defined:
1266  if (!isset($conf['breakWidth']) || !$conf['breakWidth']) {
1267  $tlen = (int)($conf['textMaxLength'] ?? 0) ?: 100;
1268  $conf['text'] = mb_substr($conf['text'], 0, $tlen, 'utf-8');
1269  }
1270  if ((string)$conf['text'] != '') {
1271  // Char range map thingie:
1272  $fontBaseName = ‪PathUtility::basename($conf['fontFile']);
1273  if (is_array($this->charRangeMap[$fontBaseName] ?? null)) {
1274  // Initialize splitRendering array:
1275  if (!is_array($conf['splitRendering.'])) {
1276  $conf['splitRendering.'] = [];
1277  }
1278  $cfgK = $this->charRangeMap[$fontBaseName]['cfgKey'];
1279  // Do not impose settings if a splitRendering object already exists:
1280  if (!isset($conf['splitRendering.'][$cfgK])) {
1281  // Set configuration:
1282  $conf['splitRendering.'][$cfgK] = 'charRange';
1283  $conf['splitRendering.'][$cfgK . '.'] = $this->charRangeMap[$fontBaseName]['charMapConfig'];
1284  // Multiplicator of fontsize:
1285  if ($this->charRangeMap[$fontBaseName]['multiplicator']) {
1286  $conf['splitRendering.'][$cfgK . '.']['fontSize'] = round($conf['fontSize'] * $this->charRangeMap[$fontBaseName]['multiplicator']);
1287  }
1288  // Multiplicator of pixelSpace:
1289  if ($this->charRangeMap[$fontBaseName]['pixelSpace']) {
1290  $travKeys = ['xSpaceBefore', 'xSpaceAfter', 'ySpaceBefore', 'ySpaceAfter'];
1291  foreach ($travKeys as $pxKey) {
1292  if (isset($conf['splitRendering.'][$cfgK . '.'][$pxKey])) {
1293  $conf['splitRendering.'][$cfgK . '.'][$pxKey] = round($conf['splitRendering.'][$cfgK . '.'][$pxKey] * ($conf['fontSize'] / $this->charRangeMap[$fontBaseName]['pixelSpace']));
1294  }
1295  }
1296  }
1297  }
1298  }
1299  if (is_array($conf['splitRendering.'] ?? null)) {
1300  foreach ($conf['splitRendering.'] as $key => $value) {
1301  if (is_array($conf['splitRendering.'][$key])) {
1302  if (isset($conf['splitRendering.'][$key]['fontFile'])) {
1303  $conf['splitRendering.'][$key]['fontFile'] = $this->‪checkFile($conf['splitRendering.'][$key]['fontFile']);
1304  }
1305  }
1306  }
1307  }
1308  return $conf;
1309  }
1310  return null;
1311  }
1312 
1324  public function ‪calcOffset(string $string): string
1325  {
1326  $value = [];
1327  $numbers = GeneralUtility::trimExplode(',', $this->‪calculateFunctions($string));
1328  foreach ($numbers as $key => $val) {
1329  if ((string)$val == (string)(int)$val) {
1330  $value[$key] = (int)$val;
1331  } else {
1332  $value[$key] = $this->‪calculateValue($val);
1333  }
1334  }
1335  $string = implode(',', $value);
1336  return $string;
1337  }
1338 
1347  protected function ‪getResource(string|‪File $file, array $fileArray): ?array
1348  {
1349  $context = GeneralUtility::makeInstance(Context::class);
1350  $deferProcessing = !$context->hasAspect('fileProcessing') || $context->getPropertyFromAspect('fileProcessing', 'deferProcessing');
1351  $context->setAspect('fileProcessing', new ‪FileProcessingAspect(false));
1352  try {
1353  if (!in_array($fileArray['ext'] ?? '', $this->imageService->getImageFileExt(), true)) {
1354  $fileArray['ext'] = 'png';
1355  }
1356  ‪$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
1357  ‪$cObj->‪start($this->data);
1358  return ‪$cObj->‪getImgResource($file, $fileArray);
1359  } finally {
1360  $context->setAspect('fileProcessing', new ‪FileProcessingAspect($deferProcessing));
1361  }
1362  }
1363 
1370  protected function ‪checkFile(string $file): ?string
1371  {
1372  try {
1373  return GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($file, true);
1374  } catch (‪Exception $e) {
1375  return null;
1376  }
1377  }
1378 
1385  protected function ‪fileName(): string
1386  {
1387  $basicFileFunctions = GeneralUtility::makeInstance(BasicFileUtility::class);
1388  $filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
1389  $filePrefix = $basicFileFunctions->cleanFileName(ltrim($filePrefix, '.'));
1390 
1391  // shorten prefix to avoid overly long file names
1392  $filePrefix = substr($filePrefix, 0, 100);
1393 
1394  $configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
1395 
1396  // we use ConfigurationService::serialize here to use as much of $this->setup as possible,
1397  // but preventing inclusion of objects that could cause problems with json_encode
1398  $hashInputForFileName = [
1399  $configurationService->serialize($this->setup),
1400  $filePrefix,
1401  ‪$this->XY,
1402  ‪$this->w,
1403  ‪$this->h,
1409  ];
1410  return $filePrefix . '_' . md5((string)json_encode($hashInputForFileName)) . '.' . $this->‪extension();
1411  }
1412 
1416  protected function ‪extension(): string
1417  {
1418  return match (strtolower($this->setup['format'] ?? '')) {
1419  'jpg', 'jpeg' => 'jpg',
1420  'gif' => 'gif',
1421  'webp' => 'webp',
1422  default => 'png',
1423  };
1424  }
1425 
1433  protected function ‪calculateValue(string $string): int
1434  {
1435  $calculatedValue = 0;
1436  $parts = GeneralUtility::splitCalc($string, '+-*/%');
1437  foreach ($parts as $part) {
1438  $theVal = $part[1];
1439  $sign = $part[0];
1440  if (((string)(int)$theVal) == ((string)$theVal)) {
1441  $theVal = (int)$theVal;
1442  } elseif ('[' . substr($theVal, 1, -1) . ']' == $theVal) {
1443  $objParts = explode('.', substr($theVal, 1, -1));
1444  $theVal = 0;
1445  if (isset($this->objBB[$objParts[0]], $objParts[1])) {
1446  if ($objParts[1] === 'w' && isset($this->objBB[$objParts[0]][0])) {
1447  $theVal = $this->objBB[$objParts[0]][0];
1448  } elseif ($objParts[1] === 'h' && isset($this->objBB[$objParts[0]][1])) {
1449  $theVal = $this->objBB[$objParts[0]][1];
1450  } elseif ($objParts[1] === 'lineHeight' && isset($this->objBB[$objParts[0]][2]['lineHeight'])) {
1451  $theVal = $this->objBB[$objParts[0]][2]['lineHeight'];
1452  }
1453  $theVal = (int)$theVal;
1454  }
1455  } elseif ((float)$theVal) {
1456  $theVal = (float)$theVal;
1457  } else {
1458  $theVal = 0;
1459  }
1460  if ($sign === '-') {
1461  $calculatedValue -= $theVal;
1462  } elseif ($sign === '+') {
1463  $calculatedValue += $theVal;
1464  } elseif ($sign === '/' && $theVal) {
1465  $calculatedValue /= $theVal;
1466  } elseif ($sign === '*') {
1467  $calculatedValue *= $theVal;
1468  } elseif ($sign === '%' && $theVal) {
1469  $calculatedValue %= $theVal;
1470  }
1471  }
1472  return (int)round($calculatedValue);
1473  }
1474 
1482  protected function ‪calculateFunctions(string $string): string
1483  {
1484  if (preg_match_all('#max\\(([^)]+)\\)#', $string, $matches)) {
1485  foreach ($matches[1] as $index => $maxExpression) {
1486  $string = str_replace($matches[0][$index], (string)$this->‪calculateMaximum($maxExpression), $string);
1487  }
1488  }
1489  return $string;
1490  }
1491 
1498  protected function ‪calculateMaximum(string $value): int
1499  {
1500  $parts = GeneralUtility::trimExplode(',', $this->‪calcOffset($value), true);
1501  return $parts !== [] ? (int)max($parts) : 0;
1502  }
1503 
1516  protected function ‪objPosition(array $conf, array ‪$workArea, array $BB): array
1517  {
1518  // offset, align, valign, workarea
1519  $result = [];
1520  $result[2] = $BB[0];
1521  $result[3] = $BB[1];
1522  ‪$w = ‪$workArea[2];
1523  ‪$h = ‪$workArea[3];
1524  $align = explode(',', $conf['align'] ?? ',');
1525  $align[0] = strtolower(substr(trim($align[0]), 0, 1));
1526  $align[1] = strtolower(substr(trim($align[1]), 0, 1));
1527  switch ($align[0]) {
1528  case 'r':
1529  $result[0] = ‪$w - $result[2];
1530  break;
1531  case 'c':
1532  $result[0] = round((‪$w - $result[2]) / 2);
1533  break;
1534  default:
1535  $result[0] = 0;
1536  }
1537  switch ($align[1]) {
1538  case 'b':
1539  // y pos
1540  $result[1] = ‪$h - $result[3];
1541  break;
1542  case 'c':
1543  $result[1] = round((‪$h - $result[3]) / 2);
1544  break;
1545  default:
1546  $result[1] = 0;
1547  }
1548  $result = $this->‪applyOffset($result, GeneralUtility::intExplode(',', (string)($conf['offset'] ?? '')));
1549  $result = $this->‪applyOffset($result, ‪$workArea);
1550  return $result;
1551  }
1552 
1561  protected function ‪applyOffset(array $cords, array ‪$offset): array
1562  {
1563  $cords[0] = (int)$cords[0] + (int)‪$offset[0];
1564  $cords[1] = (int)($cords[1] ?? 0) + (int)(‪$offset[1] ?? 0);
1565  return $cords;
1566  }
1567 
1576  protected function ‪copyGifOntoGif(\GdImage &$im, \GdImage &$cpImg, array $conf, array ‪$workArea): void
1577  {
1578  $cpW = imagesx($cpImg);
1579  $cpH = imagesy($cpImg);
1580  $tile = GeneralUtility::intExplode(',', (string)($conf['tile'] ?? ''));
1581  $tile[0] = ‪MathUtility::forceIntegerInRange($tile[0], 1, 20);
1582  $tile[1] = ‪MathUtility::forceIntegerInRange($tile[1] ?? 0, 1, 20);
1583  $cpOff = $this->‪objPosition($conf, ‪$workArea, [$cpW * $tile[0], $cpH * $tile[1]]);
1584  for ($xt = 0; $xt < $tile[0]; $xt++) {
1585  $Xstart = $cpOff[0] + $cpW * $xt;
1586  // If this image is inside of the workArea, then go on
1587  if ($Xstart + $cpW > ‪$workArea[0]) {
1588  // X:
1589  if ($Xstart < ‪$workArea[0]) {
1590  $cpImgCutX = ‪$workArea[0] - $Xstart;
1591  $Xstart = ‪$workArea[0];
1592  } else {
1593  $cpImgCutX = 0;
1594  }
1595  ‪$w = $cpW - $cpImgCutX;
1596  if ($Xstart > ‪$workArea[0] + ‪$workArea[2] - ‪$w) {
1597  ‪$w = ‪$workArea[0] + ‪$workArea[2] - $Xstart;
1598  }
1599  // If this image is inside of the workArea, then go on
1600  if ($Xstart < ‪$workArea[0] + ‪$workArea[2]) {
1601  // Y:
1602  for ($yt = 0; $yt < $tile[1]; $yt++) {
1603  $Ystart = $cpOff[1] + $cpH * $yt;
1604  // If this image is inside of the workArea, then go on
1605  if ($Ystart + $cpH > ‪$workArea[1]) {
1606  if ($Ystart < ‪$workArea[1]) {
1607  $cpImgCutY = ‪$workArea[1] - $Ystart;
1608  $Ystart = ‪$workArea[1];
1609  } else {
1610  $cpImgCutY = 0;
1611  }
1612  ‪$h = $cpH - $cpImgCutY;
1613  if ($Ystart > ‪$workArea[1] + ‪$workArea[3] - ‪$h) {
1614  ‪$h = ‪$workArea[1] + ‪$workArea[3] - $Ystart;
1615  }
1616  // If this image is inside of the workArea, then go on
1617  if ($Ystart < ‪$workArea[1] + ‪$workArea[3]) {
1618  $this->‪imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, ‪$w, ‪$h, ‪$w, ‪$h);
1619  }
1620  }
1621  }
1622  }
1623  }
1624  }
1625  }
1626 
1654  protected function ‪imagecopyresized(\GdImage &$dstImg, \GdImage &$srcImg, int $dstX, int $dstY, int $srcX, int $srcY, int $dstWidth, int $dstHeight, int $srcWidth, int $srcHeight): void
1655  {
1656  if (!$this->saveAlphaLayer) {
1657  // Make true color image
1658  $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
1659  // Copy the source image onto that
1660  ‪imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
1661  // Then copy the source image onto that (the actual operation!)
1662  ‪imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
1663  // Set the destination image
1664  $dstImg = $tmpImg;
1665  } else {
1666  ‪imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
1667  }
1668  }
1669 
1677  protected function ‪circleOffset(int $distance, int $iterations): array
1678  {
1679  $res = [];
1680  if ($distance && $iterations) {
1681  for ($a = 0; $a < $iterations; $a++) {
1682  $yOff = round(sin(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1683  if ($yOff) {
1684  $yOff = (int)(ceil(abs($yOff / 100)) * ($yOff / abs($yOff)));
1685  }
1686  $xOff = round(cos(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1687  if ($xOff) {
1688  $xOff = (int)(ceil(abs($xOff / 100)) * ($xOff / abs($xOff)));
1689  }
1690  $res[$a] = [$xOff, $yOff];
1691  }
1692  }
1693  return $res;
1694  }
1695 
1703  protected function ‪IMparams(string ‪$setup): string
1704  {
1705  if (!trim(‪$setup)) {
1706  return '';
1707  }
1708  $effects = explode('|', ‪$setup);
1709  $commands = '';
1710  foreach ($effects as $val) {
1711  $pairs = explode('=', $val, 2);
1712  $value = trim($pairs[1] ?? '');
1713  $effect = strtolower(trim($pairs[0]));
1714  switch ($effect) {
1715  case 'gamma':
1716  $commands .= ' -gamma ' . (float)$value;
1717  break;
1718  case 'blur':
1719  if ($this->processorEffectsEnabled) {
1720  $commands .= $this->imageService->v5_blur((int)$value);
1721  }
1722  break;
1723  case 'sharpen':
1724  if ($this->processorEffectsEnabled) {
1725  $commands .= $this->imageService->v5_sharpen((int)$value);
1726  }
1727  break;
1728  case 'rotate':
1729  $commands .= ' -rotate ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 360);
1730  break;
1731  case 'solarize':
1732  $commands .= ' -solarize ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 99);
1733  break;
1734  case 'swirl':
1735  $commands .= ' -swirl ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 1000);
1736  break;
1737  case 'wave':
1738  $params = GeneralUtility::intExplode(',', $value);
1739  $commands .= ' -wave ' . ‪MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . ‪MathUtility::forceIntegerInRange($params[1], 0, 99);
1740  break;
1741  case 'charcoal':
1742  $commands .= ' -charcoal ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 100);
1743  break;
1744  case 'gray':
1745  $commands .= ' -colorspace GRAY';
1746  break;
1747  case 'edge':
1748  $commands .= ' -edge ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 99);
1749  break;
1750  case 'emboss':
1751  $commands .= ' -emboss';
1752  break;
1753  case 'flip':
1754  $commands .= ' -flip';
1755  break;
1756  case 'flop':
1757  $commands .= ' -flop';
1758  break;
1759  case 'colors':
1760  $commands .= ' -colors ' . ‪MathUtility::forceIntegerInRange((int)$value, 2, 255);
1761  break;
1762  case 'shear':
1763  $commands .= ' -shear ' . ‪MathUtility::forceIntegerInRange((int)$value, -90, 90);
1764  break;
1765  case 'invert':
1766  $commands .= ' -negate';
1767  break;
1768  }
1769  }
1770  return $commands;
1771  }
1772 
1779  protected function ‪hexColor(array $color): string
1780  {
1781  $r = dechex($color[0]);
1782  if (strlen($r) < 2) {
1783  $r = '0' . $r;
1784  }
1785  $g = dechex($color[1]);
1786  if (strlen($g) < 2) {
1787  $g = '0' . $g;
1788  }
1789  $b = dechex($color[2]);
1790  if (strlen($b) < 2) {
1791  $b = '0' . $b;
1792  }
1793  return '#' . $r . $g . $b;
1794  }
1795 
1803  protected function ‪unifyColors(\GdImage &$img, array $colArr, bool $closest): int
1804  {
1805  $retCol = -1;
1806  if ($colArr !== [] && function_exists('imagepng') && function_exists('imagecreatefrompng')) {
1807  $firstCol = array_shift($colArr);
1808  $firstColArr = $this->‪convertColor($firstCol);
1809  $origName = $preName = $this->imageService->randomName() . '.png';
1810  $postName = $this->imageService->randomName() . '.png';
1811  $tmpImg = null;
1812  if (count($colArr) > 1) {
1813  $this->‪ImageWrite($img, $preName);
1814  $firstCol = $this->‪hexColor($firstColArr);
1815  foreach ($colArr as $transparentColor) {
1816  $transparentColor = $this->‪convertColor($transparentColor);
1817  $transparentColor = $this->‪hexColor($transparentColor);
1818  $cmd = '-fill "' . $firstCol . '" -opaque "' . $transparentColor . '"';
1819  $this->imageService->imageMagickExec($preName, $postName, $cmd);
1820  $preName = $postName;
1821  }
1822  $this->imageService->imageMagickExec($postName, $origName, '');
1823  if (@is_file($origName)) {
1824  $tmpImg = $this->‪imageCreateFromFile($origName);
1825  }
1826  } else {
1827  $tmpImg = $img;
1828  }
1829  if ($tmpImg) {
1830  $img = $tmpImg;
1831  if ($closest) {
1832  $retCol = imagecolorclosest($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
1833  } else {
1834  $retCol = imagecolorexact($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
1835  }
1836  }
1837  // Unlink files from process
1838  if ($origName) {
1839  @unlink($origName);
1840  }
1841  if ($postName) {
1842  @unlink($postName);
1843  }
1844  }
1845  return $retCol;
1846  }
1847 
1855  protected function ‪convertColor(string $string): array
1856  {
1857  $col = [];
1858  $cParts = explode(':', $string, 2);
1859  // Finding the RGB definitions of the color:
1860  $string = $cParts[0];
1861  if (str_contains($string, '#')) {
1862  $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string) ?? '';
1863  $col[] = hexdec(substr($string, 0, 2));
1864  $col[] = hexdec(substr($string, 2, 2));
1865  $col[] = hexdec(substr($string, 4, 2));
1866  } elseif (str_contains($string, ',')) {
1867  $string = preg_replace('/[^,0-9]*/', '', $string) ?? '';
1868  $strArr = explode(',', $string);
1869  $col[] = (int)$strArr[0];
1870  $col[] = (int)$strArr[1];
1871  $col[] = (int)$strArr[2];
1872  } else {
1873  $string = strtolower(trim($string));
1874  if ($this->colMap[$string] ?? false) {
1875  $col = $this->colMap[$string];
1876  } else {
1877  $col = [0, 0, 0];
1878  }
1879  }
1880  // ... and possibly recalculating the value
1881  if (trim($cParts[1] ?? '')) {
1882  $cParts[1] = trim($cParts[1]);
1883  if ($cParts[1][0] === '*') {
1884  $val = (float)substr($cParts[1], 1);
1885  $col[0] = ‪MathUtility::forceIntegerInRange((int)($col[0] * $val), 0, 255);
1886  $col[1] = ‪MathUtility::forceIntegerInRange((int)($col[1] * $val), 0, 255);
1887  $col[2] = ‪MathUtility::forceIntegerInRange((int)($col[2] * $val), 0, 255);
1888  } else {
1889  $val = (int)$cParts[1];
1890  $col[0] = ‪MathUtility::forceIntegerInRange((int)($col[0] + $val), 0, 255);
1891  $col[1] = ‪MathUtility::forceIntegerInRange((int)($col[1] + $val), 0, 255);
1892  $col[2] = ‪MathUtility::forceIntegerInRange((int)($col[2] + $val), 0, 255);
1893  }
1894  }
1895  return $col;
1896  }
1897 
1907  protected function ‪txtPosition(array $conf, array ‪$workArea, array $BB): array
1908  {
1909  $angle = (int)($conf['angle'] ?? 0) / 180 * M_PI;
1910  $conf['angle'] = 0;
1911  $straightBB = $this->‪calcBBox($conf);
1912  // offset, align, valign, workarea
1913  // [0]=x, [1]=y, [2]=w, [3]=h
1914  $result = [];
1915  $result[2] = $BB[0];
1916  $result[3] = $BB[1];
1917  ‪$w = ‪$workArea[2];
1918  $alignment = $conf['align'] ?? '';
1919  switch ($alignment) {
1920  case 'right':
1921 
1922  case 'center':
1923  $factor = abs(cos($angle));
1924  $sign = cos($angle) < 0 ? -1 : 1;
1925  $len1 = $sign * $factor * $straightBB[0];
1926  $len2 = $sign * $BB[0];
1927  $result[0] = ‪$w - ceil($len2 * $factor + (1 - $factor) * $len1);
1928  $factor = abs(sin($angle));
1929  $sign = sin($angle) < 0 ? -1 : 1;
1930  $len1 = $sign * $factor * $straightBB[0];
1931  $len2 = $sign * $BB[1];
1932  $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
1933  break;
1934  }
1935  switch ($alignment) {
1936  case 'right':
1937  break;
1938  case 'center':
1939  $result[0] = round($result[0] / 2);
1940  $result[1] = round($result[1] / 2);
1941  break;
1942  default:
1943  $result[0] = 0;
1944  $result[1] = 0;
1945  }
1946  $result = $this->‪applyOffset($result, GeneralUtility::intExplode(',', (string)($conf['offset'] ?? '')));
1947  $result = $this->‪applyOffset($result, ‪$workArea);
1948  return $result;
1949  }
1950 
1960  public function ‪calcBBox(array $conf): array
1961  {
1962  $sF = $this->‪getTextScalFactor($conf);
1963  [$spacing, $wordSpacing] = $this->‪calcWordSpacing($conf, $sF);
1964  $theText = $conf['text'];
1965  $charInf = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'] ?? 0, $conf['fontFile'], $theText, ($conf['splitRendering.'] ?? []), $sF);
1966  $theBBoxInfo = $charInf;
1967  if ($conf['angle'] ?? false) {
1968  $xArr = [$charInf[0], $charInf[2], $charInf[4], $charInf[6]];
1969  $yArr = [$charInf[1], $charInf[3], $charInf[5], $charInf[7]];
1970  $x = max($xArr) - min($xArr);
1971  $y = max($yArr) - min($yArr);
1972  } else {
1973  $x = $charInf[2] - $charInf[0];
1974  $y = $charInf[1] - $charInf[7];
1975  }
1976  // Set original lineHeight (used by line breaks):
1977  $theBBoxInfo['lineHeight'] = $y;
1978  if (!empty($conf['lineHeight'])) {
1979  $theBBoxInfo['lineHeight'] = (int)$conf['lineHeight'];
1980  }
1981 
1982  if ($spacing) {
1983  $x = 0;
1984  $utf8Chars = $this->csConvObj->utf8_to_numberarray($theText);
1985  // For each UTF-8 char, do:
1986  foreach ($utf8Chars as $char) {
1987  $charInf = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
1988  $charW = $charInf[2] - $charInf[0];
1989  $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
1990  }
1991  } elseif ($wordSpacing) {
1992  $x = 0;
1993  $bits = explode(' ', $theText);
1994  foreach ($bits as $word) {
1995  $word .= ' ';
1996  $wordInf = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
1997  $wordW = $wordInf[2] - $wordInf[0];
1998  $x += $wordW + $wordSpacing;
1999  }
2000  } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->‪getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
2001  $maxWidth = 0;
2002  $currentWidth = 0;
2003  $breakWidth = $conf['breakWidth'];
2004  $breakSpace = $this->‪getBreakSpace($conf, $theBBoxInfo);
2005  $wordPairs = $this->‪getWordPairsForLineBreak($conf['text']);
2006  // Iterate through all word pairs:
2007  foreach ($wordPairs as $index => $wordPair) {
2008  $wordWidth = $this->‪getRenderedTextWidth($wordPair, $conf);
2009  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
2010  $currentWidth += $wordWidth;
2011  } else {
2012  $maxWidth = max($maxWidth, $currentWidth);
2013  $y += $breakSpace;
2014  // Restart:
2015  $currentWidth = $wordWidth;
2016  }
2017  }
2018  $x = max($maxWidth, $currentWidth) * $sF;
2019  }
2020  if ($sF > 1) {
2021  $x = ceil($x / $sF);
2022  $y = ceil($y / $sF);
2023  if (is_array($theBBoxInfo)) {
2024  foreach ($theBBoxInfo as &$value) {
2025  $value = ceil($value / $sF);
2026  }
2027  unset($value);
2028  }
2029  }
2030  return [$x, $y, $theBBoxInfo];
2031  }
2032 
2051  protected function ‪SpacedImageTTFText(\GdImage &$im, int $fontSize, int $angle, int $x, int $y, int $Fcolor, string $fontFile, string $text, int $spacing, int $wordSpacing, array $splitRenderingConf, int $sF = 1): void
2052  {
2053  $spacing *= $sF;
2054  $wordSpacing *= $sF;
2055  if (!$spacing && $wordSpacing) {
2056  $bits = explode(' ', $text);
2057  foreach ($bits as $word) {
2058  $word .= ' ';
2059  $wordInf = $this->‪ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
2060  $wordW = $wordInf[2] - $wordInf[0];
2061  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
2062  $x += $wordW + $wordSpacing;
2063  }
2064  } else {
2065  $utf8Chars = $this->csConvObj->utf8_to_numberarray($text);
2066  // For each UTF-8 char, do:
2067  foreach ($utf8Chars as $char) {
2068  $charInf = $this->‪ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
2069  $charW = $charInf[2] - $charInf[0];
2070  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
2071  $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
2072  }
2073  }
2074  }
2075 
2083  protected function ‪fontResize(array $conf): int
2084  {
2085  // 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!!!!
2086  $maxWidth = (int)$conf['maxWidth'];
2087  [$spacing, $wordSpacing] = $this->‪calcWordSpacing($conf);
2088  if ($maxWidth) {
2089  // If any kind of spacing applys, we use this function:
2090  if ($spacing || $wordSpacing) {
2091  return $conf['fontSize'];
2092  }
2093  do {
2094  // Determine bounding box.
2095  $bounds = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $conf['text'], $conf['splitRendering.']);
2096  if ($conf['angle'] < 0) {
2097  $pixelWidth = abs($bounds[4] - $bounds[0]);
2098  } elseif ($conf['angle'] > 0) {
2099  $pixelWidth = abs($bounds[2] - $bounds[6]);
2100  } else {
2101  $pixelWidth = abs($bounds[4] - $bounds[6]);
2102  }
2103  // Size is fine, exit:
2104  if ($pixelWidth <= $maxWidth) {
2105  break;
2106  }
2107  $conf['fontSize']--;
2108  } while ($conf['fontSize'] > 1);
2109  }
2110  return $conf['fontSize'];
2111  }
2112 
2124  protected function ‪ImageTTFBBoxWrapper(int $fontSize, int $angle, string $fontFile, string $string, array $splitRendering, int $sF = 1): array
2125  {
2126  // Initialize:
2127  $offsetInfo = [];
2128  $stringParts = $this->‪splitString($string, $splitRendering, $fontSize, $fontFile);
2129  // Traverse string parts:
2130  foreach ($stringParts as $strCfg) {
2131  $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
2132  if (is_readable($fontFile)) {
2133  // Calculate Bounding Box for part.
2134  $calc = imagettfbbox($this->‪compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
2135  // Calculate offsets:
2136  if (empty($offsetInfo)) {
2137  // First run, just copy over.
2138  $offsetInfo = $calc;
2139  } else {
2140  $offsetInfo[2] += $calc[2] - $calc[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
2141  $offsetInfo[3] += $calc[3] - $calc[1] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
2142  $offsetInfo[4] += $calc[4] - $calc[6] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
2143  $offsetInfo[5] += $calc[5] - $calc[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
2144  }
2145  } else {
2146  ‪debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFBBoxWrapper()');
2147  }
2148  }
2149  return $offsetInfo;
2150  }
2151 
2166  protected function ‪ImageTTFTextWrapper(\GdImage &$im, int $fontSize, int $angle, int $x, int $y, int $color, string $fontFile, string $string, array $splitRendering, int $sF = 1): void
2167  {
2168  // Initialize:
2169  $stringParts = $this->‪splitString($string, $splitRendering, $fontSize, $fontFile);
2170  $x = (int)ceil($sF * $x);
2171  $y = (int)ceil($sF * $y);
2172  // Traverse string parts:
2173  foreach ($stringParts as $i => $strCfg) {
2174  // Initialize:
2175  $colorIndex = $color;
2176  // Set custom color if any (only when niceText is off):
2177  if (($strCfg['color'] ?? false) && $sF == 1) {
2178  $cols = $this->‪convertColor($strCfg['color']);
2179  $colorIndex = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
2180  $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
2181  }
2182  // Setting xSpaceBefore
2183  if ($i) {
2184  $x += (int)$strCfg['xSpaceBefore'];
2185  $y -= (int)$strCfg['ySpaceBefore'];
2186  }
2187  $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
2188  if (is_readable($fontFile)) {
2189  // Render part:
2190  imagettftext($im, $this->‪compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
2191  // Calculate offset to apply:
2192  $wordInf = imagettfbbox($this->‪compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, GeneralUtility::getFileAbsFileName($strCfg['fontFile']), $strCfg['str']);
2193  $x += $wordInf[2] - $wordInf[0] + (int)($splitRendering['compX'] ?? 0) + (int)($strCfg['xSpaceAfter'] ?? 0);
2194  $y += $wordInf[5] - $wordInf[7] - (int)($splitRendering['compY'] ?? 0) - (int)($strCfg['ySpaceAfter'] ?? 0);
2195  } else {
2196  ‪debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFTextWrapper()');
2197  }
2198  }
2199  }
2200 
2210  protected function ‪splitString(string $string, array $splitRendering, int $fontSize, string $fontFile): array
2211  {
2212  // Initialize by setting the whole string and default configuration as the first entry.
2213  $result = [];
2214  $result[] = [
2215  'str' => $string,
2216  'fontSize' => $fontSize,
2217  'fontFile' => $fontFile,
2218  ];
2219  // Traverse the split-rendering configuration:
2220  // Splitting will create more entries in $result with individual configurations.
2221  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($splitRendering);
2222  // Traverse configured options:
2223  foreach ($sKeyArray as $key) {
2224  $cfg = $splitRendering[$key . '.'];
2225  // Process each type of split rendering keyword:
2226  switch ((string)$splitRendering[$key]) {
2227  case 'highlightWord':
2228  if ((string)$cfg['value'] !== '') {
2229  $newResult = [];
2230  // Traverse the current parts of the result array:
2231  foreach ($result as $part) {
2232  // Explode the string value by the word value to highlight:
2233  $explodedParts = explode($cfg['value'], $part['str']);
2234  foreach ($explodedParts as $c => $expValue) {
2235  if ((string)$expValue !== '') {
2236  $newResult[] = array_merge($part, ['str' => $expValue]);
2237  }
2238  if ($c + 1 < count($explodedParts)) {
2239  $newResult[] = [
2240  'str' => $cfg['value'],
2241  'fontSize' => $cfg['fontSize'] ?: $part['fontSize'],
2242  'fontFile' => $cfg['fontFile'] ?: $part['fontFile'],
2243  'color' => $cfg['color'],
2244  'xSpaceBefore' => $cfg['xSpaceBefore'],
2245  'xSpaceAfter' => $cfg['xSpaceAfter'],
2246  'ySpaceBefore' => $cfg['ySpaceBefore'],
2247  'ySpaceAfter' => $cfg['ySpaceAfter'],
2248  ];
2249  }
2250  }
2251  }
2252  // Set the new result as result array:
2253  if (!empty($newResult)) {
2254  $result = $newResult;
2255  }
2256  }
2257  break;
2258  case 'charRange':
2259  if ((string)$cfg['value'] !== '') {
2260  // Initialize range:
2261  $ranges = GeneralUtility::trimExplode(',', $cfg['value'], true);
2262  foreach ($ranges as $i => $rangeDef) {
2263  $ranges[$i] = GeneralUtility::intExplode('-', $rangeDef);
2264  if (!isset($ranges[$i][1])) {
2265  $ranges[$i][1] = $ranges[$i][0];
2266  }
2267  }
2268  $newResult = [];
2269  // Traverse the current parts of the result array:
2270  foreach ($result as $part) {
2271  // Initialize:
2272  $currentState = -1;
2273  $bankAccum = '';
2274  // Explode the string value by the word value to highlight:
2275  $utf8Chars = $this->csConvObj->utf8_to_numberarray($part['str']);
2276  foreach ($utf8Chars as $utfChar) {
2277  // Find number and evaluate position:
2278  $uNumber = (int)$this->csConvObj->utf8CharToUnumber($utfChar);
2279  $inRange = 0;
2280  foreach ($ranges as $rangeDef) {
2281  if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
2282  $inRange = 1;
2283  break;
2284  }
2285  }
2286  if ($currentState == -1) {
2287  $currentState = $inRange;
2288  }
2289  // Initialize first char
2290  // Switch bank:
2291  if ($inRange != $currentState && $uNumber !== 9 && $uNumber !== 10 && $uNumber !== 13 && $uNumber !== 32) {
2292  // Set result:
2293  if ($bankAccum !== '') {
2294  $newResult[] = [
2295  'str' => $bankAccum,
2296  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
2297  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
2298  'color' => $currentState ? $cfg['color'] : '',
2299  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
2300  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
2301  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
2302  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : '',
2303  ];
2304  }
2305  // Initialize new settings:
2306  $currentState = $inRange;
2307  $bankAccum = '';
2308  }
2309  // Add char to bank:
2310  $bankAccum .= $utfChar;
2311  }
2312  // Set result for FINAL part:
2313  if ($bankAccum !== '') {
2314  $newResult[] = [
2315  'str' => $bankAccum,
2316  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
2317  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
2318  'color' => $currentState ? $cfg['color'] : '',
2319  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
2320  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
2321  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
2322  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : '',
2323  ];
2324  }
2325  }
2326  // Set the new result as result array:
2327  if (!empty($newResult)) {
2328  $result = $newResult;
2329  }
2330  }
2331  break;
2332  }
2333  }
2334  return $result;
2335  }
2336 
2345  protected function ‪calcWordSpacing(array $conf, int $scaleFactor = 1): array
2346  {
2347  $spacing = (int)($conf['spacing'] ?? 0);
2348  $wordSpacing = (int)($conf['wordSpacing'] ?? 0);
2349  $wordSpacing = $wordSpacing ?: $spacing * 2;
2350  $spacing *= $scaleFactor;
2351  $wordSpacing *= $scaleFactor;
2352  return [$spacing, $wordSpacing];
2353  }
2354 
2361  protected function ‪getTextScalFactor(array $conf): int
2362  {
2363  if (!($conf['niceText'] ?? false)) {
2364  $sF = 1;
2365  } else {
2366  $sF = ‪MathUtility::forceIntegerInRange(($conf['niceText.']['scaleFactor'] ?? 2), 2, 5);
2367  }
2368  return $sF;
2369  }
2370 
2386  protected function ‪renderTTFText(\GdImage &$im, int $fontSize, int $angle, int $x, int $y, int $color, string $fontFile, string $string, array $splitRendering, array $conf, int $sF = 1): void
2387  {
2388  if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->‪getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
2389  $phrase = '';
2390  $currentWidth = 0;
2391  $breakWidth = $conf['breakWidth'];
2392  $breakSpace = $this->‪getBreakSpace($conf);
2393  $wordPairs = $this->‪getWordPairsForLineBreak($string);
2394  // Iterate through all word pairs:
2395  foreach ($wordPairs as $index => $wordPair) {
2396  $wordWidth = $this->‪getRenderedTextWidth($wordPair, $conf);
2397  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
2398  $currentWidth += $wordWidth;
2399  $phrase .= $wordPair;
2400  } else {
2401  // Render the current phrase that is below breakWidth:
2402  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
2403  // Calculate the news height offset:
2404  $y += $breakSpace;
2405  // Restart the phrase:
2406  $currentWidth = $wordWidth;
2407  $phrase = $wordPair;
2408  }
2409  }
2410  // Render the remaining phrase:
2411  if ($currentWidth) {
2412  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
2413  }
2414  } else {
2415  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
2416  }
2417  }
2418 
2422  protected function ‪getWordPairsForLineBreak(string $string): array
2423  {
2424  $wordPairs = [];
2425  $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
2426  $wordsArray = is_array($wordsArray) ? $wordsArray : [];
2427  $wordsCount = count($wordsArray);
2428  for ($index = 0; $index < $wordsCount; $index += 2) {
2429  $wordPairs[] = $wordsArray[$index] . ($wordsArray[$index + 1] ?? '');
2430  }
2431  return $wordPairs;
2432  }
2433 
2437  protected function ‪getRenderedTextWidth(string $text, array $conf): int
2438  {
2439  $bounds = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $text, $conf['splitRendering.']);
2440  if ($conf['angle'] < 0) {
2441  $pixelWidth = abs($bounds[4] - $bounds[0]);
2442  } elseif ($conf['angle'] > 0) {
2443  $pixelWidth = abs($bounds[2] - $bounds[6]);
2444  } else {
2445  $pixelWidth = abs($bounds[4] - $bounds[6]);
2446  }
2447  return (int)$pixelWidth;
2448  }
2449 
2457  protected function ‪getBreakSpace(array $conf, array $boundingBox = []): int
2458  {
2459  if ($boundingBox === []) {
2460  $boundingBox = $this->‪calcBBox($conf);
2461  $boundingBox = $boundingBox[2];
2462  }
2463  if (isset($conf['breakSpace']) && $conf['breakSpace']) {
2464  $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
2465  } else {
2466  $breakSpace = $boundingBox['lineHeight'];
2467  }
2468  return (int)$breakSpace;
2469  }
2470 
2478  protected function ‪compensateFontSizeiBasedOnFreetypeDpi(float $fontSize): float
2479  {
2480  return $fontSize / 96.0 * 72;
2481  }
2482 
2483  /*************************
2484  *
2485  * Adjustment functions
2486  *
2487  ************************/
2493  protected function ‪autolevels(\GdImage &$im): void
2494  {
2495  $totalCols = imagecolorstotal($im);
2496  $grayArr = [];
2497  for ($c = 0; $c < $totalCols; $c++) {
2498  $cols = imagecolorsforindex($im, $c);
2499  $grayArr[] = (int)round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
2500  }
2501  $min = min($grayArr);
2502  $max = max($grayArr);
2503  $delta = $max - $min;
2504  if ($delta) {
2505  for ($c = 0; $c < $totalCols; $c++) {
2506  $cols = imagecolorsforindex($im, $c);
2507  $cols['red'] = (int)floor(($cols['red'] - $min) / $delta * 255);
2508  $cols['green'] = (int)floor(($cols['green'] - $min) / $delta * 255);
2509  $cols['blue'] = (int)floor(($cols['blue'] - $min) / $delta * 255);
2510  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
2511  }
2512  }
2513  }
2514 
2522  protected function ‪outputLevels(\GdImage &$im, int $low, int $high): void
2523  {
2524  if ($low < $high) {
2525  $low = ‪MathUtility::forceIntegerInRange($low, 0, 255);
2526  $high = ‪MathUtility::forceIntegerInRange($high, 0, 255);
2527  $delta = $high - $low;
2528  $totalCols = imagecolorstotal($im);
2529  for ($c = 0; $c < $totalCols; $c++) {
2530  $cols = imagecolorsforindex($im, $c);
2531  $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
2532  $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
2533  $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
2534  imagecolorset($im, $c, (int)$cols['red'], (int)$cols['green'], (int)$cols['blue']);
2535  }
2536  }
2537  }
2538 
2546  protected function ‪inputLevels(\GdImage &$im, int $low, int $high): void
2547  {
2548  if ($low < $high) {
2549  $low = ‪MathUtility::forceIntegerInRange($low, 0, 255);
2550  $high = ‪MathUtility::forceIntegerInRange($high, 0, 255);
2551  $delta = $high - $low;
2552  $totalCols = imagecolorstotal($im);
2553  for ($c = 0; $c < $totalCols; $c++) {
2554  $cols = imagecolorsforindex($im, $c);
2555  $cols['red'] = ‪MathUtility::forceIntegerInRange((int)(($cols['red'] - $low) / $delta * 255), 0, 255);
2556  $cols['green'] = ‪MathUtility::forceIntegerInRange((int)(($cols['green'] - $low) / $delta * 255), 0, 255);
2557  $cols['blue'] = ‪MathUtility::forceIntegerInRange((int)(($cols['blue'] - $low) / $delta * 255), 0, 255);
2558  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
2559  }
2560  }
2561  }
2562 
2570  protected function ‪applyImageMagickToPHPGif(\GdImage &$im, string $command): void
2571  {
2572  $tmpStr = $this->imageService->randomName();
2573  $theFile = $tmpStr . '.png';
2574  $this->‪ImageWrite($im, $theFile);
2575  $this->imageService->imageMagickExec($theFile, $theFile, $command);
2576  $tmpImg = $this->‪imageCreateFromFile($theFile);
2577  if ($tmpImg) {
2578  imagedestroy($im);
2579  $im = $tmpImg;
2580  $this->w = imagesx($im);
2581  $this->h = imagesy($im);
2582  }
2583  unlink($theFile);
2584  }
2585 
2597  public function ‪ImageWrite(\GdImage &$destImg, $theImage, $quality = 0)
2598  {
2599  imageinterlace($destImg, false);
2600  $ext = strtolower(substr($theImage, (int)strrpos($theImage, '.') + 1));
2601  $result = false;
2602  switch ($ext) {
2603  case 'jpg':
2604  case 'jpeg':
2605  if (function_exists('imagejpeg')) {
2606  $result = imagejpeg($destImg, $theImage, ($quality ?: $this->jpegQuality));
2607  }
2608  break;
2609  case 'webp':
2610  if (function_exists('imagewebp')) {
2611  $result = imagewebp($destImg, $theImage, ($quality ?: $this->webpQuality));
2612  }
2613  break;
2614  case 'gif':
2615  if (function_exists('imagegif')) {
2616  imagetruecolortopalette($destImg, true, 256);
2617  $result = imagegif($destImg, $theImage);
2618  }
2619  break;
2620  case 'png':
2621  if (function_exists('imagepng')) {
2622  $result = imagepng($destImg, $theImage);
2623  }
2624  break;
2625  }
2626  if ($result) {
2628  }
2629  return $result;
2630  }
2631 
2639  public function ‪imageCreateFromFile(string $sourceImg)
2640  {
2641  $imgInf = pathinfo($sourceImg);
2642  $ext = strtolower($imgInf['extension']);
2643  switch ($ext) {
2644  case 'gif':
2645  if (function_exists('imagecreatefromgif')) {
2646  return imagecreatefromgif($sourceImg);
2647  }
2648  break;
2649  case 'png':
2650  if (function_exists('imagecreatefrompng')) {
2651  $imageHandle = imagecreatefrompng($sourceImg);
2652  if ($this->saveAlphaLayer) {
2653  imagesavealpha($imageHandle, true);
2654  }
2655  return $imageHandle;
2656  }
2657  break;
2658  case 'jpg':
2659  case 'jpeg':
2660  if (function_exists('imagecreatefromjpeg')) {
2661  return imagecreatefromjpeg($sourceImg);
2662  }
2663  break;
2664  case 'webp':
2665  if (function_exists('imagecreatefromwebp')) {
2666  return imagecreatefromwebp($sourceImg);
2667  }
2668  break;
2669  }
2670  // If none of the above:
2671  $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $sourceImg);
2672  $im = imagecreatetruecolor($imageInfo->getWidth(), $imageInfo->getHeight());
2673  $Bcolor = imagecolorallocate($im, 128, 128, 128);
2674  imagefilledrectangle($im, 0, 0, $imageInfo->getWidth(), $imageInfo->getHeight(), $Bcolor);
2675  return $im;
2676  }
2677 
2690  public function ‪getTemporaryImageWithText(string $filename, string $textline1, string $textline2 = '', string $textline3 = ''): void
2691  {
2692  if (!class_exists(\GdImage::class)) {
2693  throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
2694  }
2695  // Creates the basis for the error image
2696  $basePath = ‪ExtensionManagementUtility::extPath('core') . 'Resources/Public/Images/';
2697  $im = imagecreatefrompng($basePath . 'NotFound.png');
2698  // Sets background color and print color.
2699  $white = imagecolorallocate($im, 255, 255, 255);
2700  $black = imagecolorallocate($im, 0, 0, 0);
2701  // Prints the text strings with the build-in font functions of GD
2702  $x = 0;
2703  $font = 0;
2704  if ($textline1) {
2705  imagefilledrectangle($im, $x, 9, 56, 16, $white);
2706  imagestring($im, $font, $x, 9, $textline1, $black);
2707  }
2708  if ($textline2) {
2709  imagefilledrectangle($im, $x, 19, 56, 26, $white);
2710  imagestring($im, $font, $x, 19, $textline2, $black);
2711  }
2712  if ($textline3) {
2713  imagefilledrectangle($im, $x, 29, 56, 36, $white);
2714  imagestring($im, $font, $x, 29, substr($textline3, -14), $black);
2715  }
2716  // Outputting the image stream and exit
2717  imagepng($im, $filename);
2718  }
2719 
2723  public function ‪getGraphicalFunctions(): GraphicalFunctions
2724  {
2725  return ‪$this->imageService;
2726  }
2727 }
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeBox
‪makeBox(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:1018
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\checkTextObj
‪array null checkTextObj(array $conf)
Definition: GifBuilder.php:1227
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calcWordSpacing
‪array calcWordSpacing(array $conf, int $scaleFactor=1)
Definition: GifBuilder.php:2345
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getTemporaryImageWithText
‪getTemporaryImageWithText(string $filename, string $textline1, string $textline2='', string $textline3='')
Definition: GifBuilder.php:2690
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\copyImageOntoImage
‪copyImageOntoImage(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:753
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calculateFunctions
‪string calculateFunctions(string $string)
Definition: GifBuilder.php:1482
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getRenderedTextWidth
‪getRenderedTextWidth(string $text, array $conf)
Definition: GifBuilder.php:2437
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\splitString
‪array splitString(string $string, array $splitRendering, int $fontSize, string $fontFile)
Definition: GifBuilder.php:2210
‪TYPO3\CMS\Core\Context\FileProcessingAspect
Definition: FileProcessingAspect.php:29
‪TYPO3\CMS\Core\Utility\File\BasicFileUtility
Definition: BasicFileUtility.php:37
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer\start
‪start($data, $table='')
Definition: ContentObjectRenderer.php:441
‪debug
‪debug(mixed $variable='', ?string $title=null)
Definition: GlobalDebugFunctions.php:21
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$saveAlphaLayer
‪bool $saveAlphaLayer
Definition: GifBuilder.php:102
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\adjust
‪adjust(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1096
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$imageService
‪GraphicalFunctions $imageService
Definition: GifBuilder.php:158
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\__construct
‪__construct()
Definition: GifBuilder.php:174
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\maskImageOntoImage
‪maskImageOntoImage(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:668
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeText
‪makeText(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:775
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Frontend\Resource\FilePathSanitizer
Definition: FilePathSanitizer.php:39
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\objPosition
‪array objPosition(array $conf, array $workArea, array $BB)
Definition: GifBuilder.php:1516
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$w
‪int $w
Definition: GifBuilder.php:139
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\make
‪make()
Definition: GifBuilder.php:497
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\crop
‪crop(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1131
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$webpQuality
‪int $webpQuality
Definition: GifBuilder.php:172
‪TYPO3\CMS\Core\Charset\CharsetConverter
Definition: CharsetConverter.php:54
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getBreakSpace
‪int getBreakSpace(array $conf, array $boundingBox=[])
Definition: GifBuilder.php:2457
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\hexColor
‪string hexColor(array $color)
Definition: GifBuilder.php:1779
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep(string $directory)
Definition: GeneralUtility.php:1631
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static basename(string $path)
Definition: PathUtility.php:219
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\fontResize
‪int fontResize(array $conf)
Definition: GifBuilder.php:2083
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$XY
‪array $XY
Definition: GifBuilder.php:86
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$setup
‪array $setup
Definition: GifBuilder.php:134
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer\checkIf
‪checkIf($conf)
Definition: ContentObjectRenderer.php:2551
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\renderTTFText
‪renderTTFText(\GdImage &$im, int $fontSize, int $angle, int $x, int $y, int $color, string $fontFile, string $string, array $splitRendering, array $conf, int $sF=1)
Definition: GifBuilder.php:2386
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer\stdWrapValue
‪string int bool null stdWrapValue($key, array $config, $defaultValue='')
Definition: ContentObjectRenderer.php:1181
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$h
‪int $h
Definition: GifBuilder.php:143
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:31
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\txtPosition
‪array txtPosition(array $conf, array $workArea, array $BB)
Definition: GifBuilder.php:1907
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calculateMaximum
‪int calculateMaximum(string $value)
Definition: GifBuilder.php:1498
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeShadow
‪makeShadow(\GdImage &$im, array $conf, array $workArea, array $txtConf)
Definition: GifBuilder.php:924
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\inputLevels
‪inputLevels(\GdImage &$im, int $low, int $high)
Definition: GifBuilder.php:2546
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\IMparams
‪string IMparams(string $setup)
Definition: GifBuilder.php:1703
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$csConvObj
‪CharsetConverter $csConvObj
Definition: GifBuilder.php:157
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static extPath(string $key, string $script='')
Definition: ExtensionManagementUtility.php:81
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\compensateFontSizeiBasedOnFreetypeDpi
‪float compensateFontSizeiBasedOnFreetypeDpi(float $fontSize)
Definition: GifBuilder.php:2478
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calcBBox
‪array calcBBox(array $conf)
Definition: GifBuilder.php:1960
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\copyGifOntoGif
‪copyGifOntoGif(\GdImage &$im, \GdImage &$cpImg, array $conf, array $workArea)
Definition: GifBuilder.php:1576
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$processorEffectsEnabled
‪bool $processorEffectsEnabled
Definition: GifBuilder.php:163
‪TYPO3\CMS\Frontend\Imaging
Definition: GifBuilder.php:16
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeEllipse
‪makeEllipse(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:1057
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$jpegQuality
‪int $jpegQuality
Definition: GifBuilder.php:168
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:26
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\applyImageMagickToPHPGif
‪applyImageMagickToPHPGif(\GdImage &$im, string $command)
Definition: GifBuilder.php:2570
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\convertColor
‪array convertColor(string $string)
Definition: GifBuilder.php:1855
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\fileName
‪fileName()
Definition: GifBuilder.php:1385
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\output
‪output(\GdImage $gdImage, string $file)
Definition: GifBuilder.php:457
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$defaultWorkArea
‪array $defaultWorkArea
Definition: GifBuilder.php:97
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$offset
‪array $offset
Definition: GifBuilder.php:148
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\unifyColors
‪int unifyColors(\GdImage &$img, array $colArr, bool $closest)
Definition: GifBuilder.php:1803
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$colMap
‪array $colMap
Definition: GifBuilder.php:109
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeOutline
‪makeOutline(\GdImage &$im, array $conf, array $workArea, array $txtConf)
Definition: GifBuilder.php:869
‪TYPO3\CMS\Frontend\Imaging\GifBuilder
Definition: GifBuilder.php:57
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixPermissions
‪static bool fixPermissions(string $path, bool $recursive=false)
Definition: GeneralUtility.php:1473
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$data
‪array $data
Definition: GifBuilder.php:75
‪TYPO3\CMS\Core\Resource\ProcessedFile
Definition: ProcessedFile.php:47
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\checkFile
‪string null checkFile(string $file)
Definition: GifBuilder.php:1370
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$objBB
‪array $objBB
Definition: GifBuilder.php:76
‪TYPO3\CMS\Core\Resource\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeEmboss
‪makeEmboss(\GdImage &$im, array $conf, array $workArea, array $txtConf)
Definition: GifBuilder.php:899
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\autolevels
‪autolevels(\GdImage &$im)
Definition: GifBuilder.php:2493
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getGraphicalFunctions
‪getGraphicalFunctions()
Definition: GifBuilder.php:2723
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\SpacedImageTTFText
‪SpacedImageTTFText(\GdImage &$im, int $fontSize, int $angle, int $x, int $y, int $Fcolor, string $fontFile, string $text, int $spacing, int $wordSpacing, array $splitRenderingConf, int $sF=1)
Definition: GifBuilder.php:2051
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$workArea
‪array $workArea
Definition: GifBuilder.php:92
‪TYPO3\CMS\Core\Resource\Service\ConfigurationService
Definition: ConfigurationService.php:34
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\start
‪start($conf, $data)
Definition: GifBuilder.php:214
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeEffect
‪makeEffect(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1078
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$gdlibExtensions
‪array $gdlibExtensions
Definition: GifBuilder.php:155
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\outputLevels
‪outputLevels(\GdImage &$im, int $low, int $high)
Definition: GifBuilder.php:2522
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getTextScalFactor
‪int getTextScalFactor(array $conf)
Definition: GifBuilder.php:2361
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$cObj
‪ContentObjectRenderer $cObj
Definition: GifBuilder.php:87
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\applyOffset
‪array applyOffset(array $cords, array $offset)
Definition: GifBuilder.php:1561
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\setWorkArea
‪setWorkArea(string $workArea)
Definition: GifBuilder.php:1200
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
Definition: ContentObjectRenderer.php:89
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\scale
‪scale(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1169
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$combinedTextStrings
‪array $combinedTextStrings
Definition: GifBuilder.php:63
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\gifBuild
‪string gifBuild()
Definition: GifBuilder.php:428
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer\getImgResource
‪array null getImgResource($file, $fileArray)
Definition: ContentObjectRenderer.php:3650
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$charRangeMap
‪array $charRangeMap
Definition: GifBuilder.php:81
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\imagecopyresized
‪imagecopyresized(\GdImage &$dstImg, \GdImage &$srcImg, int $dstX, int $dstY, int $srcX, int $srcY, int $dstWidth, int $dstHeight, int $srcWidth, int $srcHeight)
Definition: GifBuilder.php:1654
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\extension
‪extension()
Definition: GifBuilder.php:1416
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange(mixed $theInt, int $min, int $max=2000000000, int $defaultValue=0)
Definition: MathUtility.php:34
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\imageCreateFromFile
‪GdImage imageCreateFromFile(string $sourceImg)
Definition: GifBuilder.php:2639
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$combinedFileNames
‪array $combinedFileNames
Definition: GifBuilder.php:70
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calculateValue
‪int calculateValue(string $string)
Definition: GifBuilder.php:1433
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\ImageTTFBBoxWrapper
‪array ImageTTFBBoxWrapper(int $fontSize, int $angle, string $fontFile, string $string, array $splitRendering, int $sF=1)
Definition: GifBuilder.php:2124
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getWordPairsForLineBreak
‪getWordPairsForLineBreak(string $string)
Definition: GifBuilder.php:2422
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\ImageTTFTextWrapper
‪ImageTTFTextWrapper(\GdImage &$im, int $fontSize, int $angle, int $x, int $y, int $color, string $fontFile, string $string, array $splitRendering, int $sF=1)
Definition: GifBuilder.php:2166
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\ImageWrite
‪bool ImageWrite(\GdImage &$destImg, $theImage, $quality=0)
Definition: GifBuilder.php:2597
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getResource
‪array null getResource(string|File $file, array $fileArray)
Definition: GifBuilder.php:1347
‪TYPO3\CMS\Core\Resource\Exception
Definition: AbstractFileOperationException.php:16
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calcOffset
‪string calcOffset(string $string)
Definition: GifBuilder.php:1324
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\circleOffset
‪circleOffset(int $distance, int $iterations)
Definition: GifBuilder.php:1677