‪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;
23 use TYPO3\CMS\Core\Imaging\ImageResource;
28 use TYPO3\CMS\Core\Type\File\ImageInfo;
37 
58 {
64  protected array ‪$combinedTextStrings = [];
65 
71  protected array ‪$combinedFileNames = [];
72 
76  protected array ‪$data = [];
77  protected array ‪$objBB = [];
78 
82  protected array ‪$charRangeMap = [];
83 
87  protected array ‪$XY = [];
89 
93  protected array ‪$workArea = [];
94 
98  protected array ‪$defaultWorkArea = [];
99 
103  protected bool ‪$saveAlphaLayer = false;
104 
110  protected array ‪$colMap = [
111  'aqua' => [0, 255, 255],
112  'black' => [0, 0, 0],
113  'blue' => [0, 0, 255],
114  'fuchsia' => [255, 0, 255],
115  'gray' => [128, 128, 128],
116  'green' => [0, 128, 0],
117  'lime' => [0, 255, 0],
118  'maroon' => [128, 0, 0],
119  'navy' => [0, 0, 128],
120  'olive' => [128, 128, 0],
121  'purple' => [128, 0, 128],
122  'red' => [255, 0, 0],
123  'silver' => [192, 192, 192],
124  'teal' => [0, 128, 128],
125  'yellow' => [255, 255, 0],
126  'white' => [255, 255, 255],
127  ];
128 
135  public array ‪$setup = [];
136 
140  protected int ‪$w = 0;
144  protected int ‪$h = 0;
145 
149  protected array ‪$offset;
150 
156  protected array ‪$gdlibExtensions = [];
157 
159  protected GraphicalFunctions ‪$imageService;
160 
164  protected bool ‪$processorEffectsEnabled = false;
165 
169  protected int ‪$jpegQuality = 85;
173  protected int ‪$webpQuality = 85;
174 
175  public function ‪__construct()
176  {
177  $gfxConf = ‪$GLOBALS['TYPO3_CONF_VARS']['GFX'];
178  if ($gfxConf['processor_effects'] ?? false) {
179  $this->processorEffectsEnabled = true;
180  }
181  $this->jpegQuality = ‪MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, $this->jpegQuality);
182  if (isset($gfxConf['webp_quality'])) {
183  // see IMG_WEBP_LOSSLESS // https://www.php.net/manual/en/image.constants.php
184  if ($gfxConf['webp_quality'] === 'lossless') {
185  $this->webpQuality = 101;
186  } else {
187  $this->webpQuality = ‪MathUtility::forceIntegerInRange($gfxConf['webp_quality'], 10, 101, $this->webpQuality);
188  }
189  }
190  if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
191  $this->gdlibExtensions[] = 'jpg';
192  $this->gdlibExtensions[] = 'jpeg';
193  }
194  if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
195  $this->gdlibExtensions[] = 'png';
196  }
197  if (function_exists('imagecreatefromwebp') && function_exists('imagewebp')) {
198  $this->gdlibExtensions[] = 'webp';
199  }
200  if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
201  $this->gdlibExtensions[] = 'gif';
202  }
203  $this->imageService = GeneralUtility::makeInstance(GraphicalFunctions::class);
204  $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
205  }
206 
215  public function ‪start(array $conf, array ‪$data): void
216  {
217  if (!class_exists(\GdImage::class)) {
218  return;
219  }
220  $this->setup = $conf;
221  $this->data = ‪$data;
222  $this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
223  $this->cObj->start($this->data);
224  // Initializing Char Range Map
225  $this->charRangeMap = [];
226  foreach ($conf['charRangeMap.'] ?? [] as $cRMcfgkey => $cRMcfg) {
227  if (is_array($cRMcfg)) {
228  $cRMkey = $conf['charRangeMap.'][substr($cRMcfgkey, 0, -1)];
229  $this->charRangeMap[$cRMkey] = [];
230  $this->charRangeMap[$cRMkey]['charMapConfig'] = $cRMcfg['charMapConfig.'] ?? [];
231  $this->charRangeMap[$cRMkey]['cfgKey'] = substr($cRMcfgkey, 0, -1);
232  $this->charRangeMap[$cRMkey]['multiplicator'] = (float)$cRMcfg['fontSizeMultiplicator'];
233  $this->charRangeMap[$cRMkey]['pixelSpace'] = (int)$cRMcfg['pixelSpaceFontSizeRef'];
234  }
235  }
236  // Getting sorted list of TypoScript keys from setup.
237  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
238  // Setting the background color, passing it through stdWrap
239  $this->setup['backColor'] = $this->cObj->stdWrapValue('backColor', $this->setup, 'white');
240  $this->setup['transparentColor_array'] = explode('|', trim((string)$this->cObj->stdWrapValue('transparentColor', $this->setup)));
241  $this->setup['transparentBackground'] = $this->cObj->stdWrapValue('transparentBackground', $this->setup);
242  // Set default dimensions
243  $this->setup['XY'] = $this->cObj->stdWrapValue('XY', $this->setup);
244  if (!$this->setup['XY']) {
245  $this->setup['XY'] = '120,50';
246  }
247  // Checking TEXT and IMAGE objects for files. If any errors the objects are cleared.
248  // The Bounding Box for the objects is stored in an array
249  foreach ($sKeyArray as $index => $theKey) {
250  if (!($theValue = $this->setup[$theKey] ?? false)) {
251  continue;
252  }
253  if ((int)$theKey && ($conf = $this->setup[$theKey . '.'] ?? [])) {
254  // Swipes through TEXT and IMAGE-objects
255  switch ($theValue) {
256  case 'TEXT':
257  if ($this->setup[$theKey . '.'] = $this->‪checkTextObj($conf)) {
258  // Adjust font width if max size is set:
259  $maxWidth = $this->cObj->stdWrapValue('maxWidth', $this->setup[$theKey . '.'] ?? []);
260  if ($maxWidth) {
261  $this->setup[$theKey . '.']['fontSize'] = $this->‪fontResize($this->setup[$theKey . '.']);
262  }
263  // Calculate bounding box:
264  $txtInfo = $this->‪calcBBox($this->setup[$theKey . '.']);
265  $this->setup[$theKey . '.']['BBOX'] = $txtInfo;
266  $this->objBB[$theKey] = $txtInfo;
267  }
268  break;
269  case 'IMAGE':
270  $imageResource = $this->‪getResource($conf['file'] ?? '', $conf['file.'] ?? []);
271  if ($imageResource !== null) {
272  $this->combinedFileNames[] = preg_replace('/\\.[[:alnum:]]+$/', '', ‪PathUtility::basename($imageResource->getFullPath()));
273  if ($imageResource->getProcessedFile() instanceof ‪ProcessedFile) {
274  // Use processed file, if a FAL file has been processed by GIFBUILDER (e.g. scaled/cropped)
275  $this->setup[$theKey . '.']['file'] = $imageResource->getProcessedFile()->getForLocalProcessing(false);
276  } elseif ($imageResource->getOriginalFile() instanceof ‪File) {
277  // Use FAL file with getForLocalProcessing to circumvent problems with umlauts, if it is a FAL file (origFile not set)
278  $this->setup[$theKey . '.']['file'] = $imageResource->getOriginalFile()->getForLocalProcessing(false);
279  } else {
280  // 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)
281  $this->setup[$theKey . '.']['file'] = $imageResource->getFullPath();
282  }
283 
284  // only pass necessary parts of ImageResource further down, to not incorporate facts as
285  // CropScaleMask runs in this request, that may not occur in subsequent calls and change
286  // the md5 of the generated file name
287  $this->setup[$theKey . '.']['BBOX'] = $imageResource->getLegacyImageResourceInformation();
288  $this->objBB[$theKey] = $imageResource->getLegacyImageResourceInformation();
289  if ($conf['mask'] ?? false) {
290  $maskResource = $this->‪getResource($conf['mask'], $conf['mask.'] ?? []);
291  if ($maskResource !== null) {
292  // the same selection criteria as regarding fileInfo above apply here
293  if ($maskResource->getProcessedFile() instanceof ‪ProcessedFile) {
294  $this->setup[$theKey . '.']['mask'] = $maskResource->getProcessedFile()->getForLocalProcessing(false);
295  } elseif ($maskResource->getOriginalFile() instanceof ‪File) {
296  $this->setup[$theKey . '.']['mask'] = $maskResource->getOriginalFile()->getForLocalProcessing(false);
297  } else {
298  $this->setup[$theKey . '.']['mask'] = $maskResource->getFullPath();
299  }
300  } else {
301  $this->setup[$theKey . '.']['mask'] = '';
302  }
303  }
304  } else {
305  unset($this->setup[$theKey . '.']);
306  }
307  break;
308  }
309  // Checks if disabled is set
310  if (($conf['if.'] ?? false) && !$this->cObj->checkIf($conf['if.'])) {
311  unset($sKeyArray[$index]);
312  unset($this->setup[$theKey]);
313  unset($this->setup[$theKey . '.']);
314  unset($this->objBB[$theKey]);
315  }
316  }
317  }
318  // Calculate offsets on elements
319  $this->setup['XY'] = $this->‪calcOffset($this->setup['XY']);
320  $this->setup['offset'] = (string)$this->cObj->stdWrapValue('offset', $this->setup);
321  $this->setup['offset'] = $this->‪calcOffset($this->setup['offset']);
322  $this->setup['workArea'] = (string)$this->cObj->stdWrapValue('workArea', $this->setup);
323  $this->setup['workArea'] = $this->‪calcOffset($this->setup['workArea']);
324  foreach ($sKeyArray as $theKey) {
325  if (!($theValue = $this->setup[$theKey] ?? false)) {
326  continue;
327  }
328  if ((int)$theKey && ($this->setup[$theKey . '.'] ?? false)) {
329  switch ($theValue) {
330  case 'TEXT':
331 
332  case 'IMAGE':
333  if (isset($this->setup[$theKey . '.']['offset.'])) {
334  $this->setup[$theKey . '.']['offset'] = $this->cObj->stdWrapValue('offset', $this->setup[$theKey . '.']);
335  unset($this->setup[$theKey . '.']['offset.']);
336  }
337  if ($this->setup[$theKey . '.']['offset'] ?? false) {
338  $this->setup[$theKey . '.']['offset'] = $this->‪calcOffset($this->setup[$theKey . '.']['offset']);
339  }
340  break;
341  case 'BOX':
342 
343  case 'ELLIPSE':
344  if (isset($this->setup[$theKey . '.']['dimensions.'])) {
345  $this->setup[$theKey . '.']['dimensions'] = $this->cObj->stdWrapValue('dimensions', $this->setup[$theKey . '.']);
346  unset($this->setup[$theKey . '.']['dimensions.']);
347  }
348  if ($this->setup[$theKey . '.']['dimensions'] ?? false) {
349  $this->setup[$theKey . '.']['dimensions'] = $this->‪calcOffset($this->setup[$theKey . '.']['dimensions']);
350  }
351  break;
352  case 'WORKAREA':
353  if (isset($this->setup[$theKey . '.']['set.'])) {
354  $this->setup[$theKey . '.']['set'] = $this->cObj->stdWrapValue('set', $this->setup[$theKey . '.']);
355  unset($this->setup[$theKey . '.']['set.']);
356  }
357  if ($this->setup[$theKey . '.']['set'] ?? false) {
358  $this->setup[$theKey . '.']['set'] = $this->‪calcOffset($this->setup[$theKey . '.']['set']);
359  }
360  break;
361  case 'CROP':
362  if (isset($this->setup[$theKey . '.']['crop.'])) {
363  $this->setup[$theKey . '.']['crop'] = $this->cObj->stdWrapValue('crop', $this->setup[$theKey . '.']);
364  unset($this->setup[$theKey . '.']['crop.']);
365  }
366  if ($this->setup[$theKey . '.']['crop'] ?? false) {
367  $this->setup[$theKey . '.']['crop'] = $this->‪calcOffset($this->setup[$theKey . '.']['crop']);
368  }
369  break;
370  case 'SCALE':
371  if (isset($this->setup[$theKey . '.']['width.'])) {
372  $this->setup[$theKey . '.']['width'] = $this->cObj->stdWrapValue('width', $this->setup[$theKey . '.']);
373  unset($this->setup[$theKey . '.']['width.']);
374  }
375  if ($this->setup[$theKey . '.']['width'] ?? false) {
376  $this->setup[$theKey . '.']['width'] = $this->‪calcOffset($this->setup[$theKey . '.']['width']);
377  }
378  if (isset($this->setup[$theKey . '.']['height.'])) {
379  $this->setup[$theKey . '.']['height'] = $this->cObj->stdWrapValue('height', $this->setup[$theKey . '.']);
380  unset($this->setup[$theKey . '.']['height.']);
381  }
382  if ($this->setup[$theKey . '.']['height'] ?? false) {
383  $this->setup[$theKey . '.']['height'] = $this->‪calcOffset($this->setup[$theKey . '.']['height']);
384  }
385  break;
386  }
387  }
388  }
389  // Get trivial data
390  ‪$XY = ‪GeneralUtility::intExplode(',', $this->setup['XY']);
391  $maxWidth = (int)$this->cObj->stdWrapValue('maxWidth', $this->setup);
392  $maxHeight = (int)$this->cObj->stdWrapValue('maxHeight', $this->setup);
393  ‪$XY[0] = ‪MathUtility::forceIntegerInRange(‪$XY[0], 1, $maxWidth ?: 2000);
394  ‪$XY[1] = ‪MathUtility::forceIntegerInRange(‪$XY[1], 1, $maxHeight ?: 2000);
395  $this->XY = ‪$XY;
396  $this->w = ‪$XY[0];
397  $this->h = ‪$XY[1];
398  $this->offset = ‪GeneralUtility::intExplode(',', $this->setup['offset']);
399  // this sets the workArea
400  $this->‪setWorkArea($this->setup['workArea']);
401  // this sets the default to the current
402  $this->defaultWorkArea = ‪$this->workArea;
403  }
404 
415  public function ‪gifBuild(): ?ImageResource
416  {
417  if (!$this->setup || !class_exists(\GdImage::class)) {
418  return null;
419  }
420 
421  $fullFileName = ‪Environment::getPublicPath() . '/typo3temp/assets/images/' . $this->‪fileName();
422  if (!file_exists($fullFileName)) {
423  // Create temporary directory if not done
424  ‪GeneralUtility::mkdir_deep(dirname($fullFileName));
425  // Create file
426  $gdImage = $this->‪make();
427  $this->‪output($gdImage, $fullFileName);
428  imagedestroy($gdImage);
429  }
430 
431  $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $fullFileName);
432  if ($imageInfo->getWidth() > 0) {
433  return ImageResource::createFromImageInfo($imageInfo);
434  }
435 
436  return null;
437  }
438 
446  protected function ‪output(\GdImage $gdImage, string $file): void
447  {
448  if ($file === '') {
449  return;
450  }
451 
452  $reg = [];
453  preg_match('/([^\\.]*)$/', $file, $reg);
454  $ext = strtolower($reg[0]);
455  switch ($ext) {
456  case 'gif':
457  case 'png':
458  $this->‪ImageWrite($gdImage, $file);
459  break;
460  case 'jpg':
461  case 'jpeg':
462  // Use the default
463  $quality = isset($this->setup['quality']) ? ‪MathUtility::forceIntegerInRange((int)$this->setup['quality'], 10, 100) : 0;
464  $this->‪ImageWrite($gdImage, $file, $quality);
465  break;
466  case 'webp':
467  // Quality can also be set to IMG_WEBP_LOSSLESS = 101
468  $quality = isset($this->setup['quality']) ? ‪MathUtility::forceIntegerInRange((int)$this->setup['quality'], 10, 101) : 0;
469  $this->‪ImageWrite($gdImage, $file, $quality);
470  break;
471  }
472  }
473 
486  protected function ‪make(): \GdImage
487  {
488  // Get trivial data
490  // Reset internal properties
491  $this->saveAlphaLayer = false;
492  // Gif-start
493  $im = imagecreatetruecolor(‪$XY[0], ‪$XY[1]);
494  if (!$im instanceof \GdImage) {
495  throw new \RuntimeException('imagecreatetruecolor returned false', 1598350445);
496  }
497  $this->w = ‪$XY[0];
498  $this->h = ‪$XY[1];
499  // Transparent layer as background if set and requirements are met
500  if (($this->setup['backColor'] ?? '') === 'transparent' && (empty($this->setup['format']) || $this->setup['format'] === 'png')) {
501  // Set transparency properties
502  imagesavealpha($im, true);
503  // Fill with a transparent background
504  $transparentColor = imagecolorallocatealpha($im, 0, 0, 0, 127);
505  imagefill($im, 0, 0, $transparentColor);
506  // Set internal properties to keep the transparency over the rendering process
507  $this->saveAlphaLayer = true;
508  // Force PNG in case no format is set
509  $this->setup['format'] = 'png';
510  $BGcols = [];
511  } else {
512  // Fill the background with the given color
513  $BGcols = $this->‪convertColor($this->setup['backColor']);
514  $Bcolor = imagecolorallocate($im, $BGcols[0], $BGcols[1], $BGcols[2]);
515  imagefilledrectangle($im, 0, 0, ‪$XY[0], ‪$XY[1], $Bcolor);
516  }
517  // Traverse the GIFBUILDER objects and render each one:
518  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
519  foreach ($sKeyArray as $theKey) {
520  $theValue = $this->setup[$theKey];
521  if ((int)$theKey && ($conf = $this->setup[$theKey . '.'] ?? [])) {
522  // apply stdWrap to all properties, except for TEXT objects
523  // all properties of the TEXT sub-object have already been stdWrap-ped
524  // before in ->checkTextObj()
525  if ($theValue !== 'TEXT') {
526  $isStdWrapped = [];
527  foreach ($conf as $key => $value) {
528  $parameter = rtrim($key, '.');
529  if (!($isStdWrapped[$parameter] ?? false) && isset($conf[$parameter . '.'])) {
530  $conf[$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
531  $isStdWrapped[$parameter] = 1;
532  }
533  }
534  }
535 
536  switch ($theValue) {
537  case 'IMAGE':
538  if ($conf['mask'] ?? false) {
539  $this->‪maskImageOntoImage($im, $conf, $this->workArea);
540  } else {
541  $this->‪copyImageOntoImage($im, $conf, $this->workArea);
542  }
543  break;
544  case 'TEXT':
545  if (!($conf['hide'] ?? false)) {
546  if (is_array($conf['shadow.'] ?? null)) {
547  $isStdWrapped = [];
548  foreach ($conf['shadow.'] as $key => $value) {
549  $parameter = rtrim($key, '.');
550  if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
551  $conf['shadow.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
552  $isStdWrapped[$parameter] = 1;
553  }
554  }
555  $this->‪makeShadow($im, $conf['shadow.'], $this->workArea, $conf);
556  }
557  if (is_array($conf['emboss.'] ?? null)) {
558  $isStdWrapped = [];
559  foreach ($conf['emboss.'] as $key => $value) {
560  $parameter = rtrim($key, '.');
561  if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
562  $conf['emboss.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
563  $isStdWrapped[$parameter] = 1;
564  }
565  }
566  $this->‪makeEmboss($im, $conf['emboss.'], $this->workArea, $conf);
567  }
568  if (is_array($conf['outline.'] ?? null)) {
569  $isStdWrapped = [];
570  foreach ($conf['outline.'] as $key => $value) {
571  $parameter = rtrim($key, '.');
572  if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
573  $conf['outline.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
574  $isStdWrapped[$parameter] = 1;
575  }
576  }
577  $this->‪makeOutline($im, $conf['outline.'], $this->workArea, $conf);
578  }
579  $this->‪makeText($im, $conf, $this->workArea);
580  }
581  break;
582  case 'OUTLINE':
583  if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->‪checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
584  $this->‪makeOutline($im, $conf, $this->workArea, $txtConf);
585  }
586  break;
587  case 'EMBOSS':
588  if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->‪checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
589  $this->‪makeEmboss($im, $conf, $this->workArea, $txtConf);
590  }
591  break;
592  case 'SHADOW':
593  if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->‪checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
594  $this->‪makeShadow($im, $conf, $this->workArea, $txtConf);
595  }
596  break;
597  case 'BOX':
598  $this->‪makeBox($im, $conf, $this->workArea);
599  break;
600  case 'EFFECT':
601  $this->‪makeEffect($im, $conf);
602  break;
603  case 'ADJUST':
604  $this->‪adjust($im, $conf);
605  break;
606  case 'CROP':
607  $this->‪crop($im, $conf);
608  break;
609  case 'SCALE':
610  $this->‪scale($im, $conf);
611  break;
612  case 'WORKAREA':
613  if ($conf['set']) {
614  // this sets the workArea
615  $this->‪setWorkArea($conf['set']);
616  }
617  if (isset($conf['clear'])) {
618  // This sets the current to the default;
619  $this->workArea = ‪$this->defaultWorkArea;
620  }
621  break;
622  case 'ELLIPSE':
623  $this->‪makeEllipse($im, $conf, $this->workArea);
624  break;
625  }
626  }
627  }
628  // Preserve alpha transparency
629  if (!$this->saveAlphaLayer) {
630  if ($this->setup['transparentBackground']) {
631  // Auto transparent background is set
632  $Bcolor = imagecolorclosest($im, $BGcols[0], $BGcols[1], $BGcols[2]);
633  imagecolortransparent($im, $Bcolor);
634  } elseif (is_array($this->setup['transparentColor_array'])) {
635  // Multiple transparent colors are set. This is done via the trick that all transparent colors get
636  // converted to one color and then this one gets set as transparent as png/gif can just have one
637  // transparent color.
638  $Tcolor = $this->‪unifyColors($im, $this->setup['transparentColor_array'], (bool)($this->setup['transparentColor.']['closest'] ?? false));
639  if ($Tcolor >= 0) {
640  imagecolortransparent($im, $Tcolor);
641  }
642  }
643  }
644  return $im;
645  }
646 
657  protected function ‪maskImageOntoImage(\GdImage &$im, array $conf, array ‪$workArea): void
658  {
659  if ($conf['file'] && $conf['mask']) {
660  $imgInf = pathinfo($conf['file']);
661  $imgExt = strtolower($imgInf['extension']);
662  if (!in_array($imgExt, $this->gdlibExtensions, true)) {
663  $BBimage = $this->imageService->convert($conf['file'], 'png');
664  } else {
665  $BBimage = $this->imageService->getImageDimensions($conf['file'], true);
666  }
667  $maskInf = pathinfo($conf['mask']);
668  $maskExt = strtolower($maskInf['extension']);
669  if (!in_array($maskExt, $this->gdlibExtensions, true)) {
670  $BBmask = $this->imageService->convert($conf['mask'], 'png');
671  } else {
672  $BBmask = $this->imageService->getImageDimensions($conf['mask'], true);
673  }
674  if ($BBimage && $BBmask) {
675  ‪$w = imagesx($im);
676  ‪$h = imagesy($im);
677  $tmpStr = $this->imageService->randomName();
678  $theImage = $tmpStr . '_img.png';
679  $theDest = $tmpStr . '_dest.png';
680  $theMask = $tmpStr . '_mask.png';
681  // Prepare overlay image
682  $cpImg = $this->‪imageCreateFromFile($BBimage->getRealPath());
683  $destImg = imagecreatetruecolor(‪$w, ‪$h);
684  // Preserve alpha transparency
685  if ($this->saveAlphaLayer) {
686  imagesavealpha($destImg, true);
687  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
688  imagefill($destImg, 0, 0, $Bcolor);
689  } else {
690  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
691  imagefilledrectangle($destImg, 0, 0, ‪$w, ‪$h, $Bcolor);
692  }
693  $this->‪copyGifOntoGif($destImg, $cpImg, $conf, ‪$workArea);
694  $this->‪ImageWrite($destImg, $theImage);
695  imagedestroy($cpImg);
696  imagedestroy($destImg);
697  // Prepare mask image
698  $cpImg = $this->‪imageCreateFromFile($BBmask->getRealPath());
699  $destImg = imagecreatetruecolor(‪$w, ‪$h);
700  if ($this->saveAlphaLayer) {
701  imagesavealpha($destImg, true);
702  $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
703  imagefill($destImg, 0, 0, $Bcolor);
704  } else {
705  $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
706  imagefilledrectangle($destImg, 0, 0, ‪$w, ‪$h, $Bcolor);
707  }
708  $this->‪copyGifOntoGif($destImg, $cpImg, $conf, ‪$workArea);
709  $this->‪ImageWrite($destImg, $theMask);
710  imagedestroy($cpImg);
711  imagedestroy($destImg);
712  // Mask the images
713  $this->‪ImageWrite($im, $theDest);
714  // Let combineExec handle maskNegation
715  $this->imageService->combineExec($theDest, $theImage, $theMask, $theDest);
716  // The main image is loaded again...
717  $backIm = $this->‪imageCreateFromFile($theDest);
718  // ... and if nothing went wrong we load it onto the old one.
719  if ($backIm) {
720  if (!$this->saveAlphaLayer) {
721  imagecolortransparent($backIm, -1);
722  }
723  $im = $backIm;
724  }
725  // Unlink files from process
726  unlink($theDest);
727  unlink($theImage);
728  unlink($theMask);
729  }
730  }
731  }
732 
742  protected function ‪copyImageOntoImage(\GdImage &$im, array $conf, array ‪$workArea): void
743  {
744  if ($conf['file']) {
745  if (!in_array($conf['BBOX'][2], $this->gdlibExtensions, true)) {
746  $conf['BBOX'] = $this->imageService->convert($conf['BBOX']->getRealPath(), 'png');
747  $conf['file'] = $conf['BBOX'] ? $conf['BBOX']->getRealPath() : null;
748  }
749  $cpImg = $this->‪imageCreateFromFile($conf['file']);
750  $this->‪copyGifOntoGif($im, $cpImg, $conf, ‪$workArea);
751  imagedestroy($cpImg);
752  }
753  }
754 
764  public function ‪makeText(\GdImage &$im, array $conf, array ‪$workArea): void
765  {
766  // Spacing
767  [$spacing, $wordSpacing] = $this->‪calcWordSpacing($conf);
768  // Position
769  $txtPos = $this->‪txtPosition($conf, ‪$workArea, $conf['BBOX']);
770  $theText = $conf['text'] ?? '';
771  // Font Color:
772  $cols = $this->‪convertColor($conf['fontColor']);
773  // NiceText is calculated
774  if (!($conf['niceText'] ?? false)) {
775  $Fcolor = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
776  // antiAliasing is setup:
777  $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
778  for ($a = 0; $a < $conf['iterations']; $a++) {
779  // If any kind of spacing applies, we use this function:
780  if ($spacing || $wordSpacing) {
781  $this->‪SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
782  } else {
783  $this->‪renderTTFText($im, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'] ?? [], $conf);
784  }
785  }
786  } else {
787  // NICETEXT::
788  // options anti_aliased and iterations is NOT available when doing this!!
789  ‪$w = imagesx($im);
790  ‪$h = imagesy($im);
791  $tmpStr = $this->imageService->randomName();
792  $fileMenu = $tmpStr . '_menuNT.png';
793  $fileColor = $tmpStr . '_colorNT.png';
794  $fileMask = $tmpStr . '_maskNT.png';
795  // Scalefactor
796  $sF = ‪MathUtility::forceIntegerInRange(($conf['niceText.']['scaleFactor'] ?? 2), 2, 5);
797  $newW = (int)ceil($sF * imagesx($im));
798  $newH = (int)ceil($sF * imagesy($im));
799  // Make mask
800  $maskImg = imagecreatetruecolor($newW, $newH);
801  $Bcolor = imagecolorallocate($maskImg, 255, 255, 255);
802  imagefilledrectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
803  $Fcolor = imagecolorallocate($maskImg, 0, 0, 0);
804  // If any kind of spacing applies, we use this function:
805  if ($spacing || $wordSpacing) {
806  $this->‪SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
807  } else {
808  $this->‪renderTTFText($maskImg, $conf['fontSize'], $conf['angle'] ?? 0, $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'] ?? [], $conf, $sF);
809  }
810  $this->‪ImageWrite($maskImg, $fileMask);
811  imagedestroy($maskImg);
812  // Downscales the mask
813  if (!$this->processorEffectsEnabled) {
814  $command = trim($this->imageService->scalecmd . ' ' . ‪$w . 'x' . ‪$h . '! -negate');
815  } else {
816  $command = trim(($conf['niceText.']['before'] ?? '') . ' ' . $this->imageService->scalecmd . ' ' . ‪$w . 'x' . ‪$h . '! ' . ($conf['niceText.']['after'] ?? '') . ' -negate');
817  if (isset($conf['niceText.']['sharpen'])) {
818  $command .= $this->imageService->v5_sharpen($conf['niceText.']['sharpen']);
819  }
820  }
821  $this->imageService->imageMagickExec($fileMask, $fileMask, $command);
822  // Make the color-file
823  $colorImg = imagecreatetruecolor(‪$w, ‪$h);
824  $Ccolor = imagecolorallocate($colorImg, $cols[0], $cols[1], $cols[2]);
825  imagefilledrectangle($colorImg, 0, 0, ‪$w, ‪$h, $Ccolor);
826  $this->‪ImageWrite($colorImg, $fileColor);
827  imagedestroy($colorImg);
828  // The mask is applied
829  // The main pictures is saved temporarily
830  $this->‪ImageWrite($im, $fileMenu);
831  $this->imageService->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
832  // The main image is loaded again...
833  $backIm = $this->‪imageCreateFromFile($fileMenu);
834  // ... and if nothing went wrong we load it onto the old one.
835  if ($backIm) {
836  if (!$this->saveAlphaLayer) {
837  imagecolortransparent($backIm, -1);
838  }
839  $im = $backIm;
840  }
841  // Deleting temporary files;
842  unlink($fileMenu);
843  unlink($fileColor);
844  unlink($fileMask);
845  }
846  }
847 
858  protected function ‪makeOutline(\GdImage &$im, array $conf, array ‪$workArea, array $txtConf): void
859  {
860  $thickness = (int)$conf['thickness'];
861  if ($thickness) {
862  $txtConf['fontColor'] = $conf['color'];
863  $outLineDist = ‪MathUtility::forceIntegerInRange($thickness, 1, 2);
864  for ($b = 1; $b <= $outLineDist; $b++) {
865  if ($b == 1) {
866  $it = 8;
867  } else {
868  $it = 16;
869  }
870  $outL = $this->‪circleOffset($b, $it);
871  for ($a = 0; $a < $it; $a++) {
872  $this->‪makeText($im, $txtConf, $this->‪applyOffset($workArea, $outL[$a]));
873  }
874  }
875  }
876  }
877 
888  protected function ‪makeEmboss(\GdImage &$im, array $conf, array ‪$workArea, array $txtConf): void
889  {
890  $conf['color'] = $conf['highColor'];
891  $this->‪makeShadow($im, $conf, ‪$workArea, $txtConf);
892  $newOffset = ‪GeneralUtility::intExplode(',', (string)($conf['offset'] ?? ''));
893  $newOffset[0] *= -1;
894  $newOffset[1] *= -1;
895  $conf['offset'] = implode(',', $newOffset);
896  $conf['color'] = $conf['lowColor'];
897  $this->‪makeShadow($im, $conf, ‪$workArea, $txtConf);
898  }
899 
913  public function ‪makeShadow(\GdImage &$im, array $conf, array ‪$workArea, array $txtConf): void
914  {
915  ‪$workArea = $this->‪applyOffset($workArea, ‪GeneralUtility::intExplode(',', (string)($conf['offset'])));
916  $blurRate = ‪MathUtility::forceIntegerInRange((int)$conf['blur'], 0, 99);
917  // No effects if ImageMagick ver. 5+
918  if (!$blurRate || !$this->processorEffectsEnabled) {
919  $txtConf['fontColor'] = $conf['color'];
920  $this->‪makeText($im, $txtConf, ‪$workArea);
921  } else {
922  ‪$w = imagesx($im);
923  ‪$h = imagesy($im);
924  // Area around the blur used for cropping something
925  $blurBorder = 3;
926  $tmpStr = $this->imageService->randomName();
927  $fileMenu = $tmpStr . '_menu.png';
928  $fileColor = $tmpStr . '_color.png';
929  $fileMask = $tmpStr . '_mask.png';
930  // BlurColor Image laves
931  $blurColImg = imagecreatetruecolor(‪$w, ‪$h);
932  $bcols = $this->‪convertColor($conf['color']);
933  $Bcolor = imagecolorallocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
934  imagefilledrectangle($blurColImg, 0, 0, ‪$w, ‪$h, $Bcolor);
935  $this->‪ImageWrite($blurColImg, $fileColor);
936  imagedestroy($blurColImg);
937  // The mask is made: BlurTextImage
938  $blurTextImg = imagecreatetruecolor(‪$w + $blurBorder * 2, ‪$h + $blurBorder * 2);
939  // Black background
940  $Bcolor = imagecolorallocate($blurTextImg, 0, 0, 0);
941  imagefilledrectangle($blurTextImg, 0, 0, ‪$w + $blurBorder * 2, ‪$h + $blurBorder * 2, $Bcolor);
942  $txtConf['fontColor'] = 'white';
943  $blurBordArr = [$blurBorder, $blurBorder];
944  $this->‪makeText($blurTextImg, $txtConf, $this->‪applyOffset($workArea, $blurBordArr));
945  // Dump to temporary file
946  $this->‪ImageWrite($blurTextImg, $fileMask);
947  // Destroy
948  imagedestroy($blurTextImg);
949  $command = $this->imageService->v5_blur($blurRate + 1);
950  $this->imageService->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
951  // The mask is loaded again
952  $blurTextImg_tmp = $this->‪imageCreateFromFile($fileMask);
953  // If nothing went wrong we continue with the blurred mask
954  if ($blurTextImg_tmp) {
955  // Cropping the border from the mask
956  $blurTextImg = imagecreatetruecolor(‪$w, ‪$h);
957  $this->‪imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, ‪$w, ‪$h, ‪$w, ‪$h);
958  // Destroy the temporary mask
959  imagedestroy($blurTextImg_tmp);
960  // Adjust the mask
961  $intensity = 40;
962  if ($conf['intensity'] ?? false) {
963  $intensity = ‪MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
964  }
965  $intensity = (int)ceil(255 - $intensity / 100 * 255);
966  $this->‪inputLevels($blurTextImg, 0, $intensity);
967  $opacity = ‪MathUtility::forceIntegerInRange((int)$conf['opacity'], 0, 100);
968  if ($opacity && $opacity < 100) {
969  $high = (int)ceil(255 * $opacity / 100);
970  // Reducing levels as the opacity demands
971  $this->‪outputLevels($blurTextImg, 0, $high);
972  }
973  // Dump the mask again
974  $this->‪ImageWrite($blurTextImg, $fileMask);
975  // Destroy the mask
976  imagedestroy($blurTextImg);
977  // The pictures are combined
978  // The main pictures is saved temporarily
979  $this->‪ImageWrite($im, $fileMenu);
980  $this->imageService->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
981  // The main image is loaded again...
982  $backIm = $this->‪imageCreateFromFile($fileMenu);
983  // ... and if nothing went wrong we load it onto the old one.
984  if ($backIm) {
985  if (!$this->saveAlphaLayer) {
986  imagecolortransparent($backIm, -1);
987  }
988  $im = $backIm;
989  }
990  }
991  // Deleting temporary files;
992  unlink($fileMenu);
993  unlink($fileColor);
994  unlink($fileMask);
995  }
996  }
997 
1007  public function ‪makeBox(\GdImage &$im, array $conf, array ‪$workArea): void
1008  {
1009  $cords = ‪GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1010  $conf['offset'] = $cords[0] . ',' . $cords[1];
1011  $cords = $this->‪objPosition($conf, ‪$workArea, [$cords[2], $cords[3]]);
1012  $cols = $this->‪convertColor($conf['color'] ?? '');
1013  $opacity = 0;
1014  if (isset($conf['opacity'])) {
1015  // conversion:
1016  // PHP 0 = opaque, 127 = transparent
1017  // TYPO3 100 = opaque, 0 = transparent
1018  $opacity = ‪MathUtility::forceIntegerInRange((int)$conf['opacity'], 1, 100, 1);
1019  $opacity = (int)abs($opacity - 100);
1020  $opacity = (int)round(127 * $opacity / 100);
1021  }
1022  $tmpColor = imagecolorallocatealpha($im, $cols[0], $cols[1], $cols[2], $opacity);
1023  imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1024  }
1025 
1046  public function ‪makeEllipse(\GdImage &$im, array $conf, array ‪$workArea): void
1047  {
1048  $ellipseConfiguration = ‪GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1049  // Ellipse offset inside workArea (x/y)
1050  $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1051  // @see objPosition
1052  $imageCoordinates = $this->‪objPosition($conf, ‪$workArea, [$ellipseConfiguration[2], $ellipseConfiguration[3]]);
1053  $color = $this->‪convertColor($conf['color'] ?? '');
1054  $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1055  imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1056  }
1057 
1067  protected function ‪makeEffect(\GdImage &$im, array $conf): void
1068  {
1069  $commands = $this->‪IMparams($conf['value']);
1070  if ($commands) {
1071  $this->‪applyImageMagickToPHPGif($im, $commands);
1072  }
1073  }
1074 
1085  protected function ‪adjust(\GdImage &$im, array $conf): void
1086  {
1087  ‪$setup = $conf['value'];
1088  if (!trim(‪$setup)) {
1089  return;
1090  }
1091  $effects = explode('|', ‪$setup);
1092  foreach ($effects as $val) {
1093  $pairs = explode('=', $val, 2);
1094  $value = trim($pairs[1]);
1095  $effect = strtolower(trim($pairs[0]));
1096  switch ($effect) {
1097  case 'inputlevels':
1098  // low,high
1099  $params = ‪GeneralUtility::intExplode(',', $value);
1100  $this->‪inputLevels($im, $params[0], $params[1]);
1101  break;
1102  case 'outputlevels':
1103  $params = ‪GeneralUtility::intExplode(',', $value);
1104  $this->‪outputLevels($im, $params[0], $params[1]);
1105  break;
1106  case 'autolevels':
1107  $this->‪autolevels($im);
1108  break;
1109  }
1110  }
1111  }
1112 
1120  protected function ‪crop(\GdImage &$im, array $conf): void
1121  {
1122  // Clears workArea to total image
1123  $this->‪setWorkArea('');
1124  $cords = ‪GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1125  $conf['offset'] = $cords[0] . ',' . $cords[1];
1126  $cords = $this->‪objPosition($conf, $this->workArea, [$cords[2], $cords[3]]);
1127  $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1128  $cols = $this->‪convertColor(!empty($conf['backColor']) ? $conf['backColor'] : $this->setup['backColor']);
1129  $Bcolor = imagecolorallocate($newIm, $cols[0], $cols[1], $cols[2]);
1130  imagefilledrectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1131  $newConf = [];
1132  ‪$workArea = [0, 0, $cords[2], $cords[3]];
1133  if ($cords[0] < 0) {
1134  ‪$workArea[0] = abs($cords[0]);
1135  } else {
1136  $newConf['offset'] = -$cords[0];
1137  }
1138  if ($cords[1] < 0) {
1139  ‪$workArea[1] = abs($cords[1]);
1140  } else {
1141  $newConf['offset'] .= ',' . -$cords[1];
1142  }
1143  $this->‪copyGifOntoGif($newIm, $im, $newConf, ‪$workArea);
1144  $im = $newIm;
1145  $this->w = imagesx($im);
1146  $this->h = imagesy($im);
1147  // Clears workArea to total image
1148  $this->‪setWorkArea('');
1149  }
1150 
1158  protected function ‪scale(\GdImage &$im, array $conf): void
1159  {
1160  // @todo: not covered with tests
1161  if (isset($conf['width']) || isset($conf['height']) || isset($conf['params'])) {
1162  $tmpStr = $this->imageService->randomName();
1163  $theFile = $tmpStr . '.png';
1164  $this->‪ImageWrite($im, $theFile);
1165  $theNewFile = $this->imageService->resize($theFile, 'png', $conf['width'] ?? '', $conf['height'] ?? '', $conf['params'] ?? '');
1166  if ($theNewFile->isFile()) {
1167  $tmpImg = $this->‪imageCreateFromFile($theNewFile->getRealPath());
1168  if ($tmpImg) {
1169  imagedestroy($im);
1170  $im = $tmpImg;
1171  $this->w = imagesx($im);
1172  $this->h = imagesy($im);
1173  // Clears workArea to total image
1174  $this->‪setWorkArea('');
1175  }
1176  unlink($theFile);
1177  if ($theNewFile->getRealPath() !== $theFile) {
1178  unlink($theNewFile->getRealPath());
1179  }
1180  }
1181  }
1182  }
1183 
1192  protected function ‪setWorkArea(string ‪$workArea): void
1193  {
1194  $this->workArea = ‪GeneralUtility::intExplode(',', ‪$workArea);
1195  $this->workArea = $this->‪applyOffset($this->workArea, $this->offset);
1196  if (!($this->workArea[2] ?? false)) {
1197  $this->workArea[2] = ‪$this->w;
1198  }
1199  if (!($this->workArea[3] ?? false)) {
1200  $this->workArea[3] = ‪$this->h;
1201  }
1202  }
1203 
1204  /*********************************************
1205  *
1206  * Various helper functions
1207  *
1208  ********************************************/
1219  protected function ‪checkTextObj(array $conf): ?array
1220  {
1221  ‪$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
1222  ‪$cObj->‪start($this->data);
1223  $isStdWrapped = [];
1224  foreach ($conf as $key => $value) {
1225  $parameter = rtrim($key, '.');
1226  if (!($isStdWrapped[$parameter] ?? false) && isset($conf[$parameter . '.'])) {
1227  $conf[$parameter] = ‪$cObj->‪stdWrapValue($parameter, $conf);
1228  $isStdWrapped[$parameter] = 1;
1229  }
1230  }
1231 
1232  if (!is_null($conf['fontFile'] ?? null)) {
1233  $conf['fontFile'] = $this->‪checkFile($conf['fontFile']);
1234  }
1235  if (!($conf['fontFile'] ?? false)) {
1236  $conf['fontFile'] = $this->‪checkFile('EXT:core/Resources/Private/Font/nimbus.ttf');
1237  }
1238  if (!($conf['iterations'] ?? false)) {
1239  $conf['iterations'] = 1;
1240  }
1241  if (!($conf['fontSize'] ?? false)) {
1242  $conf['fontSize'] = 12;
1243  }
1244  // If any kind of spacing applies, we cannot use angles!!
1245  if (($conf['spacing'] ?? false) || ($conf['wordSpacing'] ?? false)) {
1246  $conf['angle'] = 0;
1247  }
1248  if (!isset($conf['antiAlias'])) {
1249  $conf['antiAlias'] = 1;
1250  }
1251  $conf['fontColor'] = trim($conf['fontColor'] ?? '');
1252  // Strip HTML
1253  if (!($conf['doNotStripHTML'] ?? false)) {
1254  $conf['text'] = strip_tags($conf['text'] ?? '');
1255  }
1256  $this->combinedTextStrings[] = strip_tags($conf['text'] ?? '');
1257  // Max length = 100 if automatic line breaks are not defined:
1258  if (!isset($conf['breakWidth']) || !$conf['breakWidth']) {
1259  $tlen = (int)($conf['textMaxLength'] ?? 0) ?: 100;
1260  $conf['text'] = mb_substr($conf['text'], 0, $tlen, 'utf-8');
1261  }
1262  if ((string)$conf['text'] != '') {
1263  // Char range map thingie:
1264  $fontBaseName = ‪PathUtility::basename($conf['fontFile']);
1265  if (is_array($this->charRangeMap[$fontBaseName] ?? null)) {
1266  // Initialize splitRendering array:
1267  if (!is_array($conf['splitRendering.'])) {
1268  $conf['splitRendering.'] = [];
1269  }
1270  $cfgK = $this->charRangeMap[$fontBaseName]['cfgKey'];
1271  // Do not impose settings if a splitRendering object already exists:
1272  if (!isset($conf['splitRendering.'][$cfgK])) {
1273  // Set configuration:
1274  $conf['splitRendering.'][$cfgK] = 'charRange';
1275  $conf['splitRendering.'][$cfgK . '.'] = $this->charRangeMap[$fontBaseName]['charMapConfig'];
1276  // Multiplicator of fontsize:
1277  if ($this->charRangeMap[$fontBaseName]['multiplicator']) {
1278  $conf['splitRendering.'][$cfgK . '.']['fontSize'] = round($conf['fontSize'] * $this->charRangeMap[$fontBaseName]['multiplicator']);
1279  }
1280  // Multiplicator of pixelSpace:
1281  if ($this->charRangeMap[$fontBaseName]['pixelSpace']) {
1282  $travKeys = ['xSpaceBefore', 'xSpaceAfter', 'ySpaceBefore', 'ySpaceAfter'];
1283  foreach ($travKeys as $pxKey) {
1284  if (isset($conf['splitRendering.'][$cfgK . '.'][$pxKey])) {
1285  $conf['splitRendering.'][$cfgK . '.'][$pxKey] = round($conf['splitRendering.'][$cfgK . '.'][$pxKey] * ($conf['fontSize'] / $this->charRangeMap[$fontBaseName]['pixelSpace']));
1286  }
1287  }
1288  }
1289  }
1290  }
1291  if (is_array($conf['splitRendering.'] ?? null)) {
1292  foreach ($conf['splitRendering.'] as $key => $value) {
1293  if (is_array($conf['splitRendering.'][$key])) {
1294  if (isset($conf['splitRendering.'][$key]['fontFile'])) {
1295  $conf['splitRendering.'][$key]['fontFile'] = $this->‪checkFile($conf['splitRendering.'][$key]['fontFile']);
1296  }
1297  }
1298  }
1299  }
1300  return $conf;
1301  }
1302  return null;
1303  }
1304 
1316  public function ‪calcOffset(string $string): string
1317  {
1318  $value = [];
1319  $numbers = ‪GeneralUtility::trimExplode(',', $this->‪calculateFunctions($string));
1320  foreach ($numbers as $key => $val) {
1321  if ((string)$val == (string)(int)$val) {
1322  $value[$key] = (int)$val;
1323  } else {
1324  $value[$key] = $this->‪calculateValue($val);
1325  }
1326  }
1327  $string = implode(',', $value);
1328  return $string;
1329  }
1330 
1339  protected function ‪getResource(string|‪File $file, array $fileArray): ?ImageResource
1340  {
1341  $context = GeneralUtility::makeInstance(Context::class);
1342  $deferProcessing = !$context->hasAspect('fileProcessing') || $context->getPropertyFromAspect('fileProcessing', 'deferProcessing');
1343  $context->setAspect('fileProcessing', new ‪FileProcessingAspect(false));
1344  try {
1345  if (!in_array($fileArray['ext'] ?? '', $this->imageService->getImageFileExt(), true)) {
1346  $fileArray['ext'] = 'png';
1347  }
1348  ‪$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
1349  ‪$cObj->‪start($this->data);
1350  return ‪$cObj->‪getImgResource($file, $fileArray);
1351  } finally {
1352  $context->setAspect('fileProcessing', new ‪FileProcessingAspect($deferProcessing));
1353  }
1354  }
1355 
1362  protected function ‪checkFile(string $file): ?string
1363  {
1364  try {
1365  return GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($file, true);
1366  } catch (‪Exception $e) {
1367  return null;
1368  }
1369  }
1370 
1377  protected function ‪fileName(): string
1378  {
1379  $basicFileFunctions = GeneralUtility::makeInstance(BasicFileUtility::class);
1380  $filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
1381  $filePrefix = $basicFileFunctions->cleanFileName(ltrim($filePrefix, '.'));
1382 
1383  // shorten prefix to avoid overly long file names
1384  $filePrefix = substr($filePrefix, 0, 100);
1385 
1386  $configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
1387 
1388  // we use ConfigurationService::serialize here to use as much of $this->setup as possible,
1389  // but preventing inclusion of objects that could cause problems with json_encode
1390  $hashInputForFileName = [
1391  $configurationService->serialize($this->setup),
1392  $filePrefix,
1393  ‪$this->XY,
1394  ‪$this->w,
1395  ‪$this->h,
1401  ];
1402  return $filePrefix . '_' . md5((string)json_encode($hashInputForFileName)) . '.' . $this->‪extension();
1403  }
1404 
1408  protected function ‪extension(): string
1409  {
1410  return match (strtolower($this->setup['format'] ?? '')) {
1411  'jpg', 'jpeg' => 'jpg',
1412  'gif' => 'gif',
1413  'webp' => 'webp',
1414  default => 'png',
1415  };
1416  }
1417 
1425  protected function ‪calculateValue(string $string): int
1426  {
1427  $calculatedValue = 0;
1428  $parts = GeneralUtility::splitCalc($string, '+-*/%');
1429  foreach ($parts as $part) {
1430  $theVal = $part[1];
1431  $sign = $part[0];
1432  if (((string)(int)$theVal) == ((string)$theVal)) {
1433  $theVal = (int)$theVal;
1434  } elseif ('[' . substr($theVal, 1, -1) . ']' == $theVal) {
1435  $objParts = explode('.', substr($theVal, 1, -1));
1436  $theVal = 0;
1437  if (isset($this->objBB[$objParts[0]], $objParts[1])) {
1438  if ($objParts[1] === 'w' && isset($this->objBB[$objParts[0]][0])) {
1439  $theVal = $this->objBB[$objParts[0]][0];
1440  } elseif ($objParts[1] === 'h' && isset($this->objBB[$objParts[0]][1])) {
1441  $theVal = $this->objBB[$objParts[0]][1];
1442  } elseif ($objParts[1] === 'lineHeight' && isset($this->objBB[$objParts[0]][2]['lineHeight'])) {
1443  $theVal = $this->objBB[$objParts[0]][2]['lineHeight'];
1444  }
1445  $theVal = (int)$theVal;
1446  }
1447  } elseif ((float)$theVal) {
1448  $theVal = (float)$theVal;
1449  } else {
1450  $theVal = 0;
1451  }
1452  if ($sign === '-') {
1453  $calculatedValue -= $theVal;
1454  } elseif ($sign === '+') {
1455  $calculatedValue += $theVal;
1456  } elseif ($sign === '/' && $theVal) {
1457  $calculatedValue /= $theVal;
1458  } elseif ($sign === '*') {
1459  $calculatedValue *= $theVal;
1460  } elseif ($sign === '%' && $theVal) {
1461  $calculatedValue %= $theVal;
1462  }
1463  }
1464  return (int)round($calculatedValue);
1465  }
1466 
1474  protected function ‪calculateFunctions(string $string): string
1475  {
1476  if (preg_match_all('#max\\(([^)]+)\\)#', $string, $matches)) {
1477  foreach ($matches[1] as $index => $maxExpression) {
1478  $string = str_replace($matches[0][$index], (string)$this->‪calculateMaximum($maxExpression), $string);
1479  }
1480  }
1481  return $string;
1482  }
1483 
1490  protected function ‪calculateMaximum(string $value): int
1491  {
1492  $parts = ‪GeneralUtility::trimExplode(',', $this->‪calcOffset($value), true);
1493  return $parts !== [] ? (int)max($parts) : 0;
1494  }
1495 
1508  protected function ‪objPosition(array $conf, array ‪$workArea, array $BB): array
1509  {
1510  // offset, align, valign, workarea
1511  $result = [];
1512  $result[2] = $BB[0];
1513  $result[3] = $BB[1];
1514  ‪$w = ‪$workArea[2];
1515  ‪$h = ‪$workArea[3];
1516  $align = explode(',', $conf['align'] ?? ',');
1517  $align[0] = strtolower(substr(trim($align[0]), 0, 1));
1518  $align[1] = strtolower(substr(trim($align[1]), 0, 1));
1519  switch ($align[0]) {
1520  case 'r':
1521  $result[0] = ‪$w - $result[2];
1522  break;
1523  case 'c':
1524  $result[0] = round((‪$w - $result[2]) / 2);
1525  break;
1526  default:
1527  $result[0] = 0;
1528  }
1529  switch ($align[1]) {
1530  case 'b':
1531  // y pos
1532  $result[1] = ‪$h - $result[3];
1533  break;
1534  case 'c':
1535  $result[1] = round((‪$h - $result[3]) / 2);
1536  break;
1537  default:
1538  $result[1] = 0;
1539  }
1540  $result = $this->‪applyOffset($result, ‪GeneralUtility::intExplode(',', (string)($conf['offset'] ?? '')));
1541  $result = $this->‪applyOffset($result, ‪$workArea);
1542  return $result;
1543  }
1544 
1553  protected function ‪applyOffset(array $cords, array ‪$offset): array
1554  {
1555  $cords[0] = (int)$cords[0] + (int)‪$offset[0];
1556  $cords[1] = (int)($cords[1] ?? 0) + (int)(‪$offset[1] ?? 0);
1557  return $cords;
1558  }
1559 
1568  protected function ‪copyGifOntoGif(\GdImage &$im, \GdImage &$cpImg, array $conf, array ‪$workArea): void
1569  {
1570  $cpW = imagesx($cpImg);
1571  $cpH = imagesy($cpImg);
1572  $tile = ‪GeneralUtility::intExplode(',', (string)($conf['tile'] ?? ''));
1573  $tile[0] = ‪MathUtility::forceIntegerInRange($tile[0], 1, 20);
1574  $tile[1] = ‪MathUtility::forceIntegerInRange($tile[1] ?? 0, 1, 20);
1575  $cpOff = $this->‪objPosition($conf, ‪$workArea, [$cpW * $tile[0], $cpH * $tile[1]]);
1576  for ($xt = 0; $xt < $tile[0]; $xt++) {
1577  $Xstart = $cpOff[0] + $cpW * $xt;
1578  // If this image is inside of the workArea, then go on
1579  if ($Xstart + $cpW > ‪$workArea[0]) {
1580  // X:
1581  if ($Xstart < ‪$workArea[0]) {
1582  $cpImgCutX = ‪$workArea[0] - $Xstart;
1583  $Xstart = ‪$workArea[0];
1584  } else {
1585  $cpImgCutX = 0;
1586  }
1587  ‪$w = $cpW - $cpImgCutX;
1588  if ($Xstart > ‪$workArea[0] + ‪$workArea[2] - ‪$w) {
1589  ‪$w = ‪$workArea[0] + ‪$workArea[2] - $Xstart;
1590  }
1591  // If this image is inside of the workArea, then go on
1592  if ($Xstart < ‪$workArea[0] + ‪$workArea[2]) {
1593  // Y:
1594  for ($yt = 0; $yt < $tile[1]; $yt++) {
1595  $Ystart = $cpOff[1] + $cpH * $yt;
1596  // If this image is inside of the workArea, then go on
1597  if ($Ystart + $cpH > ‪$workArea[1]) {
1598  if ($Ystart < ‪$workArea[1]) {
1599  $cpImgCutY = ‪$workArea[1] - $Ystart;
1600  $Ystart = ‪$workArea[1];
1601  } else {
1602  $cpImgCutY = 0;
1603  }
1604  ‪$h = $cpH - $cpImgCutY;
1605  if ($Ystart > ‪$workArea[1] + ‪$workArea[3] - ‪$h) {
1606  ‪$h = ‪$workArea[1] + ‪$workArea[3] - $Ystart;
1607  }
1608  // If this image is inside of the workArea, then go on
1609  if ($Ystart < ‪$workArea[1] + ‪$workArea[3]) {
1610  $this->‪imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, ‪$w, ‪$h, ‪$w, ‪$h);
1611  }
1612  }
1613  }
1614  }
1615  }
1616  }
1617  }
1618 
1646  protected function ‪imagecopyresized(\GdImage &$dstImg, \GdImage &$srcImg, int $dstX, int $dstY, int $srcX, int $srcY, int $dstWidth, int $dstHeight, int $srcWidth, int $srcHeight): void
1647  {
1648  if (!$this->saveAlphaLayer) {
1649  // Make true color image
1650  $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
1651  // Copy the source image onto that
1652  ‪imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
1653  // Then copy the source image onto that (the actual operation!)
1654  ‪imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
1655  // Set the destination image
1656  $dstImg = $tmpImg;
1657  } else {
1658  ‪imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
1659  }
1660  }
1661 
1669  protected function ‪circleOffset(int $distance, int $iterations): array
1670  {
1671  $res = [];
1672  if ($distance && $iterations) {
1673  for ($a = 0; $a < $iterations; $a++) {
1674  $yOff = round(sin(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1675  if ($yOff) {
1676  $yOff = (int)(ceil(abs($yOff / 100)) * ($yOff / abs($yOff)));
1677  }
1678  $xOff = round(cos(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1679  if ($xOff) {
1680  $xOff = (int)(ceil(abs($xOff / 100)) * ($xOff / abs($xOff)));
1681  }
1682  $res[$a] = [$xOff, $yOff];
1683  }
1684  }
1685  return $res;
1686  }
1687 
1695  protected function ‪IMparams(string ‪$setup): string
1696  {
1697  if (!trim(‪$setup)) {
1698  return '';
1699  }
1700  $effects = explode('|', ‪$setup);
1701  $commands = '';
1702  foreach ($effects as $val) {
1703  $pairs = explode('=', $val, 2);
1704  $value = trim($pairs[1] ?? '');
1705  $effect = strtolower(trim($pairs[0]));
1706  switch ($effect) {
1707  case 'gamma':
1708  $commands .= ' -gamma ' . (float)$value;
1709  break;
1710  case 'blur':
1711  if ($this->processorEffectsEnabled) {
1712  $commands .= $this->imageService->v5_blur((int)$value);
1713  }
1714  break;
1715  case 'sharpen':
1716  if ($this->processorEffectsEnabled) {
1717  $commands .= $this->imageService->v5_sharpen((int)$value);
1718  }
1719  break;
1720  case 'rotate':
1721  $commands .= ' -rotate ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 360);
1722  break;
1723  case 'solarize':
1724  $commands .= ' -solarize ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 99);
1725  break;
1726  case 'swirl':
1727  $commands .= ' -swirl ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 1000);
1728  break;
1729  case 'wave':
1730  $params = ‪GeneralUtility::intExplode(',', $value);
1731  $commands .= ' -wave ' . ‪MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . ‪MathUtility::forceIntegerInRange($params[1], 0, 99);
1732  break;
1733  case 'charcoal':
1734  $commands .= ' -charcoal ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 100);
1735  break;
1736  case 'gray':
1737  $commands .= ' -colorspace GRAY';
1738  break;
1739  case 'edge':
1740  $commands .= ' -edge ' . ‪MathUtility::forceIntegerInRange((int)$value, 0, 99);
1741  break;
1742  case 'emboss':
1743  $commands .= ' -emboss';
1744  break;
1745  case 'flip':
1746  $commands .= ' -flip';
1747  break;
1748  case 'flop':
1749  $commands .= ' -flop';
1750  break;
1751  case 'colors':
1752  $commands .= ' -colors ' . ‪MathUtility::forceIntegerInRange((int)$value, 2, 255);
1753  break;
1754  case 'shear':
1755  $commands .= ' -shear ' . ‪MathUtility::forceIntegerInRange((int)$value, -90, 90);
1756  break;
1757  case 'invert':
1758  $commands .= ' -negate';
1759  break;
1760  }
1761  }
1762  return $commands;
1763  }
1764 
1771  protected function ‪hexColor(array $color): string
1772  {
1773  $r = dechex($color[0]);
1774  if (strlen($r) < 2) {
1775  $r = '0' . $r;
1776  }
1777  $g = dechex($color[1]);
1778  if (strlen($g) < 2) {
1779  $g = '0' . $g;
1780  }
1781  $b = dechex($color[2]);
1782  if (strlen($b) < 2) {
1783  $b = '0' . $b;
1784  }
1785  return '#' . $r . $g . $b;
1786  }
1787 
1795  protected function ‪unifyColors(\GdImage &$img, array $colArr, bool $closest): int
1796  {
1797  $retCol = -1;
1798  if ($colArr !== [] && function_exists('imagepng') && function_exists('imagecreatefrompng')) {
1799  $firstCol = array_shift($colArr);
1800  $firstColArr = $this->‪convertColor($firstCol);
1801  $origName = $preName = $this->imageService->randomName() . '.png';
1802  $postName = $this->imageService->randomName() . '.png';
1803  $tmpImg = null;
1804  if (count($colArr) > 1) {
1805  $this->‪ImageWrite($img, $preName);
1806  $firstCol = $this->‪hexColor($firstColArr);
1807  foreach ($colArr as $transparentColor) {
1808  $transparentColor = $this->‪convertColor($transparentColor);
1809  $transparentColor = $this->‪hexColor($transparentColor);
1810  $cmd = '-fill "' . $firstCol . '" -opaque "' . $transparentColor . '"';
1811  $this->imageService->imageMagickExec($preName, $postName, $cmd);
1812  $preName = $postName;
1813  }
1814  $this->imageService->imageMagickExec($postName, $origName, '');
1815  if (@is_file($origName)) {
1816  $tmpImg = $this->‪imageCreateFromFile($origName);
1817  }
1818  } else {
1819  $tmpImg = $img;
1820  }
1821  if ($tmpImg) {
1822  $img = $tmpImg;
1823  if ($closest) {
1824  $retCol = imagecolorclosest($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
1825  } else {
1826  $retCol = imagecolorexact($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
1827  }
1828  }
1829  // Unlink files from process
1830  if ($origName) {
1831  @unlink($origName);
1832  }
1833  if ($postName) {
1834  @unlink($postName);
1835  }
1836  }
1837  return $retCol;
1838  }
1839 
1847  protected function ‪convertColor(string $string): array
1848  {
1849  $col = [];
1850  $cParts = explode(':', $string, 2);
1851  // Finding the RGB definitions of the color:
1852  $string = $cParts[0];
1853  if (str_contains($string, '#')) {
1854  $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string) ?? '';
1855  $col[] = hexdec(substr($string, 0, 2));
1856  $col[] = hexdec(substr($string, 2, 2));
1857  $col[] = hexdec(substr($string, 4, 2));
1858  } elseif (str_contains($string, ',')) {
1859  $string = preg_replace('/[^,0-9]*/', '', $string) ?? '';
1860  $strArr = explode(',', $string);
1861  $col[] = (int)$strArr[0];
1862  $col[] = (int)$strArr[1];
1863  $col[] = (int)$strArr[2];
1864  } else {
1865  $string = strtolower(trim($string));
1866  if ($this->colMap[$string] ?? false) {
1867  $col = $this->colMap[$string];
1868  } else {
1869  $col = [0, 0, 0];
1870  }
1871  }
1872  // ... and possibly recalculating the value
1873  if (trim($cParts[1] ?? '')) {
1874  $cParts[1] = trim($cParts[1]);
1875  if ($cParts[1][0] === '*') {
1876  $val = (float)substr($cParts[1], 1);
1877  $col[0] = ‪MathUtility::forceIntegerInRange((int)($col[0] * $val), 0, 255);
1878  $col[1] = ‪MathUtility::forceIntegerInRange((int)($col[1] * $val), 0, 255);
1879  $col[2] = ‪MathUtility::forceIntegerInRange((int)($col[2] * $val), 0, 255);
1880  } else {
1881  $val = (int)$cParts[1];
1882  $col[0] = ‪MathUtility::forceIntegerInRange((int)($col[0] + $val), 0, 255);
1883  $col[1] = ‪MathUtility::forceIntegerInRange((int)($col[1] + $val), 0, 255);
1884  $col[2] = ‪MathUtility::forceIntegerInRange((int)($col[2] + $val), 0, 255);
1885  }
1886  }
1887  return $col;
1888  }
1889 
1899  protected function ‪txtPosition(array $conf, array ‪$workArea, array $BB): array
1900  {
1901  $angle = (int)($conf['angle'] ?? 0) / 180 * M_PI;
1902  $conf['angle'] = 0;
1903  $straightBB = $this->‪calcBBox($conf);
1904  // offset, align, valign, workarea
1905  // [0]=x, [1]=y, [2]=w, [3]=h
1906  $result = [];
1907  $result[2] = $BB[0];
1908  $result[3] = $BB[1];
1909  ‪$w = ‪$workArea[2];
1910  $alignment = $conf['align'] ?? '';
1911  switch ($alignment) {
1912  case 'right':
1913 
1914  case 'center':
1915  $factor = abs(cos($angle));
1916  $sign = cos($angle) < 0 ? -1 : 1;
1917  $len1 = $sign * $factor * $straightBB[0];
1918  $len2 = $sign * $BB[0];
1919  $result[0] = ‪$w - ceil($len2 * $factor + (1 - $factor) * $len1);
1920  $factor = abs(sin($angle));
1921  $sign = sin($angle) < 0 ? -1 : 1;
1922  $len1 = $sign * $factor * $straightBB[0];
1923  $len2 = $sign * $BB[1];
1924  $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
1925  break;
1926  }
1927  switch ($alignment) {
1928  case 'right':
1929  break;
1930  case 'center':
1931  $result[0] = round($result[0] / 2);
1932  $result[1] = round($result[1] / 2);
1933  break;
1934  default:
1935  $result[0] = 0;
1936  $result[1] = 0;
1937  }
1938  $result = $this->‪applyOffset($result, ‪GeneralUtility::intExplode(',', (string)($conf['offset'] ?? '')));
1939  $result = $this->‪applyOffset($result, ‪$workArea);
1940  return $result;
1941  }
1942 
1952  public function ‪calcBBox(array $conf): array
1953  {
1954  $sF = $this->‪getTextScalFactor($conf);
1955  [$spacing, $wordSpacing] = $this->‪calcWordSpacing($conf, $sF);
1956  $theText = $conf['text'];
1957  $charInf = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'] ?? 0, $conf['fontFile'], $theText, ($conf['splitRendering.'] ?? []), $sF);
1958  $theBBoxInfo = $charInf;
1959  if ($conf['angle'] ?? false) {
1960  $xArr = [$charInf[0], $charInf[2], $charInf[4], $charInf[6]];
1961  $yArr = [$charInf[1], $charInf[3], $charInf[5], $charInf[7]];
1962  $x = max($xArr) - min($xArr);
1963  $y = max($yArr) - min($yArr);
1964  } else {
1965  $x = $charInf[2] - $charInf[0];
1966  $y = $charInf[1] - $charInf[7];
1967  }
1968  // Set original lineHeight (used by line breaks):
1969  $theBBoxInfo['lineHeight'] = $y;
1970  if (!empty($conf['lineHeight'])) {
1971  $theBBoxInfo['lineHeight'] = (int)$conf['lineHeight'];
1972  }
1973 
1974  if ($spacing) {
1975  $x = 0;
1976  $utf8Chars = $this->csConvObj->utf8_to_numberarray($theText);
1977  // For each UTF-8 char, do:
1978  foreach ($utf8Chars as $char) {
1979  $charInf = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
1980  $charW = $charInf[2] - $charInf[0];
1981  $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
1982  }
1983  } elseif ($wordSpacing) {
1984  $x = 0;
1985  $bits = explode(' ', $theText);
1986  foreach ($bits as $word) {
1987  $word .= ' ';
1988  $wordInf = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
1989  $wordW = $wordInf[2] - $wordInf[0];
1990  $x += $wordW + $wordSpacing;
1991  }
1992  } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->‪getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
1993  $maxWidth = 0;
1994  $currentWidth = 0;
1995  $breakWidth = $conf['breakWidth'];
1996  $breakSpace = $this->‪getBreakSpace($conf, $theBBoxInfo);
1997  $wordPairs = $this->‪getWordPairsForLineBreak($conf['text']);
1998  // Iterate through all word pairs:
1999  foreach ($wordPairs as $index => $wordPair) {
2000  $wordWidth = $this->‪getRenderedTextWidth($wordPair, $conf);
2001  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
2002  $currentWidth += $wordWidth;
2003  } else {
2004  $maxWidth = max($maxWidth, $currentWidth);
2005  $y += $breakSpace;
2006  // Restart:
2007  $currentWidth = $wordWidth;
2008  }
2009  }
2010  $x = max($maxWidth, $currentWidth) * $sF;
2011  }
2012  if ($sF > 1) {
2013  $x = ceil($x / $sF);
2014  $y = ceil($y / $sF);
2015  if (is_array($theBBoxInfo)) {
2016  foreach ($theBBoxInfo as &$value) {
2017  $value = ceil($value / $sF);
2018  }
2019  unset($value);
2020  }
2021  }
2022  return [$x, $y, $theBBoxInfo];
2023  }
2024 
2043  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
2044  {
2045  $spacing *= $sF;
2046  $wordSpacing *= $sF;
2047  if (!$spacing && $wordSpacing) {
2048  $bits = explode(' ', $text);
2049  foreach ($bits as $word) {
2050  $word .= ' ';
2051  $wordInf = $this->‪ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
2052  $wordW = $wordInf[2] - $wordInf[0];
2053  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
2054  $x += $wordW + $wordSpacing;
2055  }
2056  } else {
2057  $utf8Chars = $this->csConvObj->utf8_to_numberarray($text);
2058  // For each UTF-8 char, do:
2059  foreach ($utf8Chars as $char) {
2060  $charInf = $this->‪ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
2061  $charW = $charInf[2] - $charInf[0];
2062  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
2063  $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
2064  }
2065  }
2066  }
2067 
2075  protected function ‪fontResize(array $conf): int
2076  {
2077  // 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!!!!
2078  $maxWidth = (int)$conf['maxWidth'];
2079  [$spacing, $wordSpacing] = $this->‪calcWordSpacing($conf);
2080  if ($maxWidth) {
2081  // If any kind of spacing applys, we use this function:
2082  if ($spacing || $wordSpacing) {
2083  return $conf['fontSize'];
2084  }
2085  do {
2086  // Determine bounding box.
2087  $bounds = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $conf['text'], $conf['splitRendering.']);
2088  if ($conf['angle'] < 0) {
2089  $pixelWidth = abs($bounds[4] - $bounds[0]);
2090  } elseif ($conf['angle'] > 0) {
2091  $pixelWidth = abs($bounds[2] - $bounds[6]);
2092  } else {
2093  $pixelWidth = abs($bounds[4] - $bounds[6]);
2094  }
2095  // Size is fine, exit:
2096  if ($pixelWidth <= $maxWidth) {
2097  break;
2098  }
2099  $conf['fontSize']--;
2100  } while ($conf['fontSize'] > 1);
2101  }
2102  return $conf['fontSize'];
2103  }
2104 
2116  protected function ‪ImageTTFBBoxWrapper(int $fontSize, int $angle, string $fontFile, string $string, array $splitRendering, int $sF = 1): array
2117  {
2118  // Initialize:
2119  $offsetInfo = [];
2120  $stringParts = $this->‪splitString($string, $splitRendering, $fontSize, $fontFile);
2121  // Traverse string parts:
2122  foreach ($stringParts as $strCfg) {
2123  $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
2124  if (is_readable($fontFile)) {
2125  // Calculate Bounding Box for part.
2126  $calc = imagettfbbox($this->‪compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
2127  // Calculate offsets:
2128  if (empty($offsetInfo)) {
2129  // First run, just copy over.
2130  $offsetInfo = $calc;
2131  } else {
2132  $offsetInfo[2] += $calc[2] - $calc[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
2133  $offsetInfo[3] += $calc[3] - $calc[1] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
2134  $offsetInfo[4] += $calc[4] - $calc[6] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
2135  $offsetInfo[5] += $calc[5] - $calc[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
2136  }
2137  } else {
2138  ‪debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFBBoxWrapper()');
2139  }
2140  }
2141  return $offsetInfo;
2142  }
2143 
2158  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
2159  {
2160  // Initialize:
2161  $stringParts = $this->‪splitString($string, $splitRendering, $fontSize, $fontFile);
2162  $x = (int)ceil($sF * $x);
2163  $y = (int)ceil($sF * $y);
2164  // Traverse string parts:
2165  foreach ($stringParts as $i => $strCfg) {
2166  // Initialize:
2167  $colorIndex = $color;
2168  // Set custom color if any (only when niceText is off):
2169  if (($strCfg['color'] ?? false) && $sF == 1) {
2170  $cols = $this->‪convertColor($strCfg['color']);
2171  $colorIndex = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
2172  $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
2173  }
2174  // Setting xSpaceBefore
2175  if ($i) {
2176  $x += (int)$strCfg['xSpaceBefore'];
2177  $y -= (int)$strCfg['ySpaceBefore'];
2178  }
2179  $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
2180  if (is_readable($fontFile)) {
2181  // Render part:
2182  imagettftext($im, $this->‪compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
2183  // Calculate offset to apply:
2184  $wordInf = imagettfbbox($this->‪compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, GeneralUtility::getFileAbsFileName($strCfg['fontFile']), $strCfg['str']);
2185  $x += $wordInf[2] - $wordInf[0] + (int)($splitRendering['compX'] ?? 0) + (int)($strCfg['xSpaceAfter'] ?? 0);
2186  $y += $wordInf[5] - $wordInf[7] - (int)($splitRendering['compY'] ?? 0) - (int)($strCfg['ySpaceAfter'] ?? 0);
2187  } else {
2188  ‪debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFTextWrapper()');
2189  }
2190  }
2191  }
2192 
2202  protected function ‪splitString(string $string, array $splitRendering, int $fontSize, string $fontFile): array
2203  {
2204  // Initialize by setting the whole string and default configuration as the first entry.
2205  $result = [];
2206  $result[] = [
2207  'str' => $string,
2208  'fontSize' => $fontSize,
2209  'fontFile' => $fontFile,
2210  ];
2211  // Traverse the split-rendering configuration:
2212  // Splitting will create more entries in $result with individual configurations.
2213  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($splitRendering);
2214  // Traverse configured options:
2215  foreach ($sKeyArray as $key) {
2216  $cfg = $splitRendering[$key . '.'];
2217  // Process each type of split rendering keyword:
2218  switch ((string)$splitRendering[$key]) {
2219  case 'highlightWord':
2220  if ((string)$cfg['value'] !== '') {
2221  $newResult = [];
2222  // Traverse the current parts of the result array:
2223  foreach ($result as $part) {
2224  // Explode the string value by the word value to highlight:
2225  $explodedParts = explode($cfg['value'], $part['str']);
2226  foreach ($explodedParts as $c => $expValue) {
2227  if ((string)$expValue !== '') {
2228  $newResult[] = array_merge($part, ['str' => $expValue]);
2229  }
2230  if ($c + 1 < count($explodedParts)) {
2231  $newResult[] = [
2232  'str' => $cfg['value'],
2233  'fontSize' => $cfg['fontSize'] ?: $part['fontSize'],
2234  'fontFile' => $cfg['fontFile'] ?: $part['fontFile'],
2235  'color' => $cfg['color'],
2236  'xSpaceBefore' => $cfg['xSpaceBefore'],
2237  'xSpaceAfter' => $cfg['xSpaceAfter'],
2238  'ySpaceBefore' => $cfg['ySpaceBefore'],
2239  'ySpaceAfter' => $cfg['ySpaceAfter'],
2240  ];
2241  }
2242  }
2243  }
2244  // Set the new result as result array:
2245  if (!empty($newResult)) {
2246  $result = $newResult;
2247  }
2248  }
2249  break;
2250  case 'charRange':
2251  if ((string)$cfg['value'] !== '') {
2252  // Initialize range:
2253  $ranges = ‪GeneralUtility::trimExplode(',', $cfg['value'], true);
2254  foreach ($ranges as $i => $rangeDef) {
2255  $ranges[$i] = ‪GeneralUtility::intExplode('-', $rangeDef);
2256  if (!isset($ranges[$i][1])) {
2257  $ranges[$i][1] = $ranges[$i][0];
2258  }
2259  }
2260  $newResult = [];
2261  // Traverse the current parts of the result array:
2262  foreach ($result as $part) {
2263  // Initialize:
2264  $currentState = -1;
2265  $bankAccum = '';
2266  // Explode the string value by the word value to highlight:
2267  $utf8Chars = $this->csConvObj->utf8_to_numberarray($part['str']);
2268  foreach ($utf8Chars as $utfChar) {
2269  // Find number and evaluate position:
2270  $uNumber = (int)$this->csConvObj->utf8CharToUnumber($utfChar);
2271  $inRange = 0;
2272  foreach ($ranges as $rangeDef) {
2273  if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
2274  $inRange = 1;
2275  break;
2276  }
2277  }
2278  if ($currentState == -1) {
2279  $currentState = $inRange;
2280  }
2281  // Initialize first char
2282  // Switch bank:
2283  if ($inRange != $currentState && $uNumber !== 9 && $uNumber !== 10 && $uNumber !== 13 && $uNumber !== 32) {
2284  // Set result:
2285  if ($bankAccum !== '') {
2286  $newResult[] = [
2287  'str' => $bankAccum,
2288  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
2289  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
2290  'color' => $currentState ? $cfg['color'] : '',
2291  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
2292  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
2293  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
2294  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : '',
2295  ];
2296  }
2297  // Initialize new settings:
2298  $currentState = $inRange;
2299  $bankAccum = '';
2300  }
2301  // Add char to bank:
2302  $bankAccum .= $utfChar;
2303  }
2304  // Set result for FINAL part:
2305  if ($bankAccum !== '') {
2306  $newResult[] = [
2307  'str' => $bankAccum,
2308  'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
2309  'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
2310  'color' => $currentState ? $cfg['color'] : '',
2311  'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
2312  'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
2313  'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
2314  'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : '',
2315  ];
2316  }
2317  }
2318  // Set the new result as result array:
2319  if (!empty($newResult)) {
2320  $result = $newResult;
2321  }
2322  }
2323  break;
2324  }
2325  }
2326  return $result;
2327  }
2328 
2337  protected function ‪calcWordSpacing(array $conf, int $scaleFactor = 1): array
2338  {
2339  $spacing = (int)($conf['spacing'] ?? 0);
2340  $wordSpacing = (int)($conf['wordSpacing'] ?? 0);
2341  $wordSpacing = $wordSpacing ?: $spacing * 2;
2342  $spacing *= $scaleFactor;
2343  $wordSpacing *= $scaleFactor;
2344  return [$spacing, $wordSpacing];
2345  }
2346 
2353  protected function ‪getTextScalFactor(array $conf): int
2354  {
2355  if (!($conf['niceText'] ?? false)) {
2356  $sF = 1;
2357  } else {
2358  $sF = ‪MathUtility::forceIntegerInRange(($conf['niceText.']['scaleFactor'] ?? 2), 2, 5);
2359  }
2360  return $sF;
2361  }
2362 
2378  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
2379  {
2380  if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->‪getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
2381  $phrase = '';
2382  $currentWidth = 0;
2383  $breakWidth = $conf['breakWidth'];
2384  $breakSpace = $this->‪getBreakSpace($conf);
2385  $wordPairs = $this->‪getWordPairsForLineBreak($string);
2386  // Iterate through all word pairs:
2387  foreach ($wordPairs as $index => $wordPair) {
2388  $wordWidth = $this->‪getRenderedTextWidth($wordPair, $conf);
2389  if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
2390  $currentWidth += $wordWidth;
2391  $phrase .= $wordPair;
2392  } else {
2393  // Render the current phrase that is below breakWidth:
2394  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
2395  // Calculate the news height offset:
2396  $y += $breakSpace;
2397  // Restart the phrase:
2398  $currentWidth = $wordWidth;
2399  $phrase = $wordPair;
2400  }
2401  }
2402  // Render the remaining phrase:
2403  if ($currentWidth) {
2404  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
2405  }
2406  } else {
2407  $this->‪ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
2408  }
2409  }
2410 
2414  protected function ‪getWordPairsForLineBreak(string $string): array
2415  {
2416  $wordPairs = [];
2417  $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
2418  $wordsArray = is_array($wordsArray) ? $wordsArray : [];
2419  $wordsCount = count($wordsArray);
2420  for ($index = 0; $index < $wordsCount; $index += 2) {
2421  $wordPairs[] = $wordsArray[$index] . ($wordsArray[$index + 1] ?? '');
2422  }
2423  return $wordPairs;
2424  }
2425 
2429  protected function ‪getRenderedTextWidth(string $text, array $conf): int
2430  {
2431  $bounds = $this->‪ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $text, $conf['splitRendering.']);
2432  if ($conf['angle'] < 0) {
2433  $pixelWidth = abs($bounds[4] - $bounds[0]);
2434  } elseif ($conf['angle'] > 0) {
2435  $pixelWidth = abs($bounds[2] - $bounds[6]);
2436  } else {
2437  $pixelWidth = abs($bounds[4] - $bounds[6]);
2438  }
2439  return (int)$pixelWidth;
2440  }
2441 
2449  protected function ‪getBreakSpace(array $conf, array $boundingBox = []): int
2450  {
2451  if ($boundingBox === []) {
2452  $boundingBox = $this->‪calcBBox($conf);
2453  $boundingBox = $boundingBox[2];
2454  }
2455  if (isset($conf['breakSpace']) && $conf['breakSpace']) {
2456  $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
2457  } else {
2458  $breakSpace = $boundingBox['lineHeight'];
2459  }
2460  return (int)$breakSpace;
2461  }
2462 
2470  protected function ‪compensateFontSizeiBasedOnFreetypeDpi(float $fontSize): float
2471  {
2472  return $fontSize / 96.0 * 72;
2473  }
2474 
2475  /*************************
2476  *
2477  * Adjustment functions
2478  *
2479  ************************/
2485  protected function ‪autolevels(\GdImage &$im): void
2486  {
2487  $totalCols = imagecolorstotal($im);
2488  $grayArr = [];
2489  for ($c = 0; $c < $totalCols; $c++) {
2490  $cols = imagecolorsforindex($im, $c);
2491  $grayArr[] = (int)round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
2492  }
2493  $min = min($grayArr);
2494  $max = max($grayArr);
2495  $delta = $max - $min;
2496  if ($delta) {
2497  for ($c = 0; $c < $totalCols; $c++) {
2498  $cols = imagecolorsforindex($im, $c);
2499  $cols['red'] = (int)floor(($cols['red'] - $min) / $delta * 255);
2500  $cols['green'] = (int)floor(($cols['green'] - $min) / $delta * 255);
2501  $cols['blue'] = (int)floor(($cols['blue'] - $min) / $delta * 255);
2502  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
2503  }
2504  }
2505  }
2506 
2514  protected function ‪outputLevels(\GdImage &$im, int $low, int $high): void
2515  {
2516  if ($low < $high) {
2517  $low = ‪MathUtility::forceIntegerInRange($low, 0, 255);
2518  $high = ‪MathUtility::forceIntegerInRange($high, 0, 255);
2519  $delta = $high - $low;
2520  $totalCols = imagecolorstotal($im);
2521  for ($c = 0; $c < $totalCols; $c++) {
2522  $cols = imagecolorsforindex($im, $c);
2523  $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
2524  $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
2525  $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
2526  imagecolorset($im, $c, (int)$cols['red'], (int)$cols['green'], (int)$cols['blue']);
2527  }
2528  }
2529  }
2530 
2538  protected function ‪inputLevels(\GdImage &$im, int $low, int $high): void
2539  {
2540  if ($low < $high) {
2541  $low = ‪MathUtility::forceIntegerInRange($low, 0, 255);
2542  $high = ‪MathUtility::forceIntegerInRange($high, 0, 255);
2543  $delta = $high - $low;
2544  $totalCols = imagecolorstotal($im);
2545  for ($c = 0; $c < $totalCols; $c++) {
2546  $cols = imagecolorsforindex($im, $c);
2547  $cols['red'] = ‪MathUtility::forceIntegerInRange((int)(($cols['red'] - $low) / $delta * 255), 0, 255);
2548  $cols['green'] = ‪MathUtility::forceIntegerInRange((int)(($cols['green'] - $low) / $delta * 255), 0, 255);
2549  $cols['blue'] = ‪MathUtility::forceIntegerInRange((int)(($cols['blue'] - $low) / $delta * 255), 0, 255);
2550  imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
2551  }
2552  }
2553  }
2554 
2562  protected function ‪applyImageMagickToPHPGif(\GdImage &$im, string $command): void
2563  {
2564  $tmpStr = $this->imageService->randomName();
2565  $theFile = $tmpStr . '.png';
2566  $this->‪ImageWrite($im, $theFile);
2567  $this->imageService->imageMagickExec($theFile, $theFile, $command);
2568  $tmpImg = $this->‪imageCreateFromFile($theFile);
2569  if ($tmpImg) {
2570  imagedestroy($im);
2571  $im = $tmpImg;
2572  $this->w = imagesx($im);
2573  $this->h = imagesy($im);
2574  }
2575  unlink($theFile);
2576  }
2577 
2589  public function ‪ImageWrite(\GdImage &$destImg, string $theImage, int $quality = 0): bool
2590  {
2591  imageinterlace($destImg, false);
2592  $ext = strtolower(substr($theImage, (int)strrpos($theImage, '.') + 1));
2593  $result = false;
2594  switch ($ext) {
2595  case 'jpg':
2596  case 'jpeg':
2597  if (function_exists('imagejpeg')) {
2598  $result = imagejpeg($destImg, $theImage, ($quality ?: $this->jpegQuality));
2599  }
2600  break;
2601  case 'webp':
2602  if (function_exists('imagewebp')) {
2603  $result = imagewebp($destImg, $theImage, ($quality ?: $this->webpQuality));
2604  }
2605  break;
2606  case 'gif':
2607  if (function_exists('imagegif')) {
2608  imagetruecolortopalette($destImg, true, 256);
2609  $result = imagegif($destImg, $theImage);
2610  }
2611  break;
2612  case 'png':
2613  if (function_exists('imagepng')) {
2614  $result = imagepng($destImg, $theImage);
2615  }
2616  break;
2617  }
2618  if ($result) {
2620  }
2621  return $result;
2622  }
2623 
2631  public function ‪imageCreateFromFile(string $sourceImg): \GdImage
2632  {
2633  $imgInf = pathinfo($sourceImg);
2634  $ext = strtolower($imgInf['extension']);
2635  switch ($ext) {
2636  case 'gif':
2637  if (function_exists('imagecreatefromgif')) {
2638  return imagecreatefromgif($sourceImg);
2639  }
2640  break;
2641  case 'png':
2642  if (function_exists('imagecreatefrompng')) {
2643  $imageHandle = imagecreatefrompng($sourceImg);
2644  if ($this->saveAlphaLayer) {
2645  imagesavealpha($imageHandle, true);
2646  }
2647  return $imageHandle;
2648  }
2649  break;
2650  case 'jpg':
2651  case 'jpeg':
2652  if (function_exists('imagecreatefromjpeg')) {
2653  return imagecreatefromjpeg($sourceImg);
2654  }
2655  break;
2656  case 'webp':
2657  if (function_exists('imagecreatefromwebp')) {
2658  return imagecreatefromwebp($sourceImg);
2659  }
2660  break;
2661  }
2662  // If none of the above:
2663  $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $sourceImg);
2664  $im = imagecreatetruecolor($imageInfo->getWidth(), $imageInfo->getHeight());
2665  $Bcolor = imagecolorallocate($im, 128, 128, 128);
2666  imagefilledrectangle($im, 0, 0, $imageInfo->getWidth(), $imageInfo->getHeight(), $Bcolor);
2667  return $im;
2668  }
2669 
2682  public function ‪getTemporaryImageWithText(string $filename, string $textline1, string $textline2 = '', string $textline3 = ''): void
2683  {
2684  if (!class_exists(\GdImage::class)) {
2685  throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
2686  }
2687  // Creates the basis for the error image
2688  $basePath = ‪ExtensionManagementUtility::extPath('core') . 'Resources/Public/Images/';
2689  $im = imagecreatefrompng($basePath . 'NotFound.png');
2690  // Sets background color and print color.
2691  $white = imagecolorallocate($im, 255, 255, 255);
2692  $black = imagecolorallocate($im, 0, 0, 0);
2693  // Prints the text strings with the build-in font functions of GD
2694  $x = 0;
2695  $font = 0;
2696  if ($textline1) {
2697  imagefilledrectangle($im, $x, 9, 56, 16, $white);
2698  imagestring($im, $font, $x, 9, $textline1, $black);
2699  }
2700  if ($textline2) {
2701  imagefilledrectangle($im, $x, 19, 56, 26, $white);
2702  imagestring($im, $font, $x, 19, $textline2, $black);
2703  }
2704  if ($textline3) {
2705  imagefilledrectangle($im, $x, 29, 56, 36, $white);
2706  imagestring($im, $font, $x, 29, substr($textline3, -14), $black);
2707  }
2708  // Outputting the image stream and exit
2709  imagepng($im, $filename);
2710  }
2711 
2715  public function ‪getGraphicalFunctions(): GraphicalFunctions
2716  {
2717  return ‪$this->imageService;
2718  }
2719 }
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\ImageWrite
‪bool ImageWrite(\GdImage &$destImg, string $theImage, int $quality=0)
Definition: GifBuilder.php:2589
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeBox
‪makeBox(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:1007
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\checkTextObj
‪array null checkTextObj(array $conf)
Definition: GifBuilder.php:1219
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calcWordSpacing
‪array calcWordSpacing(array $conf, int $scaleFactor=1)
Definition: GifBuilder.php:2337
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getTemporaryImageWithText
‪getTemporaryImageWithText(string $filename, string $textline1, string $textline2='', string $textline3='')
Definition: GifBuilder.php:2682
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\copyImageOntoImage
‪copyImageOntoImage(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:742
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calculateFunctions
‪string calculateFunctions(string $string)
Definition: GifBuilder.php:1474
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getRenderedTextWidth
‪getRenderedTextWidth(string $text, array $conf)
Definition: GifBuilder.php:2429
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\splitString
‪array splitString(string $string, array $splitRendering, int $fontSize, string $fontFile)
Definition: GifBuilder.php:2202
‪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:436
‪debug
‪debug(mixed $variable='', ?string $title=null)
Definition: GlobalDebugFunctions.php:21
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$saveAlphaLayer
‪bool $saveAlphaLayer
Definition: GifBuilder.php:103
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\adjust
‪adjust(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1085
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$imageService
‪GraphicalFunctions $imageService
Definition: GifBuilder.php:159
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\__construct
‪__construct()
Definition: GifBuilder.php:175
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\maskImageOntoImage
‪maskImageOntoImage(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:657
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeText
‪makeText(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:764
‪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:1508
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$w
‪int $w
Definition: GifBuilder.php:140
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\make
‪make()
Definition: GifBuilder.php:486
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\crop
‪crop(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1120
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$webpQuality
‪int $webpQuality
Definition: GifBuilder.php:173
‪TYPO3\CMS\Core\Charset\CharsetConverter
Definition: CharsetConverter.php:54
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getBreakSpace
‪int getBreakSpace(array $conf, array $boundingBox=[])
Definition: GifBuilder.php:2449
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\hexColor
‪string hexColor(array $color)
Definition: GifBuilder.php:1771
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep(string $directory)
Definition: GeneralUtility.php:1654
‪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:2075
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$XY
‪array $XY
Definition: GifBuilder.php:87
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$setup
‪array $setup
Definition: GifBuilder.php:135
‪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:2378
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer\stdWrapValue
‪string int bool null stdWrapValue($key, array $config, $defaultValue='')
Definition: ContentObjectRenderer.php:1139
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$h
‪int $h
Definition: GifBuilder.php:144
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:32
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\start
‪start(array $conf, array $data)
Definition: GifBuilder.php:215
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\txtPosition
‪array txtPosition(array $conf, array $workArea, array $BB)
Definition: GifBuilder.php:1899
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calculateMaximum
‪int calculateMaximum(string $value)
Definition: GifBuilder.php:1490
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeShadow
‪makeShadow(\GdImage &$im, array $conf, array $workArea, array $txtConf)
Definition: GifBuilder.php:913
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\inputLevels
‪inputLevels(\GdImage &$im, int $low, int $high)
Definition: GifBuilder.php:2538
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\IMparams
‪string IMparams(string $setup)
Definition: GifBuilder.php:1695
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$csConvObj
‪CharsetConverter $csConvObj
Definition: GifBuilder.php:158
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static extPath(string $key, string $script='')
Definition: ExtensionManagementUtility.php:82
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\compensateFontSizeiBasedOnFreetypeDpi
‪float compensateFontSizeiBasedOnFreetypeDpi(float $fontSize)
Definition: GifBuilder.php:2470
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calcBBox
‪array calcBBox(array $conf)
Definition: GifBuilder.php:1952
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\copyGifOntoGif
‪copyGifOntoGif(\GdImage &$im, \GdImage &$cpImg, array $conf, array $workArea)
Definition: GifBuilder.php:1568
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$processorEffectsEnabled
‪bool $processorEffectsEnabled
Definition: GifBuilder.php:164
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\gifBuild
‪ImageResource null gifBuild()
Definition: GifBuilder.php:415
‪TYPO3\CMS\Frontend\Imaging
Definition: GifBuilder.php:16
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeEllipse
‪makeEllipse(\GdImage &$im, array $conf, array $workArea)
Definition: GifBuilder.php:1046
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$jpegQuality
‪int $jpegQuality
Definition: GifBuilder.php:169
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:26
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\applyImageMagickToPHPGif
‪applyImageMagickToPHPGif(\GdImage &$im, string $command)
Definition: GifBuilder.php:2562
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\convertColor
‪array convertColor(string $string)
Definition: GifBuilder.php:1847
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\fileName
‪fileName()
Definition: GifBuilder.php:1377
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\output
‪output(\GdImage $gdImage, string $file)
Definition: GifBuilder.php:446
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$defaultWorkArea
‪array $defaultWorkArea
Definition: GifBuilder.php:98
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$offset
‪array $offset
Definition: GifBuilder.php:149
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\unifyColors
‪int unifyColors(\GdImage &$img, array $colArr, bool $closest)
Definition: GifBuilder.php:1795
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getResource
‪ImageResource null getResource(string|File $file, array $fileArray)
Definition: GifBuilder.php:1339
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$colMap
‪array $colMap
Definition: GifBuilder.php:110
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeOutline
‪makeOutline(\GdImage &$im, array $conf, array $workArea, array $txtConf)
Definition: GifBuilder.php:858
‪TYPO3\CMS\Frontend\Imaging\GifBuilder
Definition: GifBuilder.php:58
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixPermissions
‪static bool fixPermissions(string $path, bool $recursive=false)
Definition: GeneralUtility.php:1496
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$data
‪array $data
Definition: GifBuilder.php:76
‪TYPO3\CMS\Core\Resource\ProcessedFile
Definition: ProcessedFile.php:47
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\checkFile
‪string null checkFile(string $file)
Definition: GifBuilder.php:1362
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$objBB
‪array $objBB
Definition: GifBuilder.php:77
‪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:888
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\autolevels
‪autolevels(\GdImage &$im)
Definition: GifBuilder.php:2485
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getGraphicalFunctions
‪getGraphicalFunctions()
Definition: GifBuilder.php:2715
‪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:2043
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$workArea
‪array $workArea
Definition: GifBuilder.php:93
‪TYPO3\CMS\Core\Resource\Service\ConfigurationService
Definition: ConfigurationService.php:34
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\makeEffect
‪makeEffect(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1067
‪$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:156
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\outputLevels
‪outputLevels(\GdImage &$im, int $low, int $high)
Definition: GifBuilder.php:2514
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getTextScalFactor
‪int getTextScalFactor(array $conf)
Definition: GifBuilder.php:2353
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$cObj
‪ContentObjectRenderer $cObj
Definition: GifBuilder.php:88
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\applyOffset
‪array applyOffset(array $cords, array $offset)
Definition: GifBuilder.php:1553
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\setWorkArea
‪setWorkArea(string $workArea)
Definition: GifBuilder.php:1192
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
Definition: ContentObjectRenderer.php:102
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\scale
‪scale(\GdImage &$im, array $conf)
Definition: GifBuilder.php:1158
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$combinedTextStrings
‪array $combinedTextStrings
Definition: GifBuilder.php:64
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$charRangeMap
‪array $charRangeMap
Definition: GifBuilder.php:82
‪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:1646
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\extension
‪extension()
Definition: GifBuilder.php:1408
‪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:2631
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\$combinedFileNames
‪array $combinedFileNames
Definition: GifBuilder.php:71
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calculateValue
‪int calculateValue(string $string)
Definition: GifBuilder.php:1425
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\ImageTTFBBoxWrapper
‪array ImageTTFBBoxWrapper(int $fontSize, int $angle, string $fontFile, string $string, array $splitRendering, int $sF=1)
Definition: GifBuilder.php:2116
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\getWordPairsForLineBreak
‪getWordPairsForLineBreak(string $string)
Definition: GifBuilder.php:2414
‪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:2158
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer\getImgResource
‪ImageResource null getImgResource($file, $fileArray)
Definition: ContentObjectRenderer.php:3542
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static list< int > intExplode(string $delimiter, string $string, bool $removeEmptyValues=false)
Definition: GeneralUtility.php:756
‪TYPO3\CMS\Core\Resource\Exception
Definition: AbstractFileOperationException.php:16
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\calcOffset
‪string calcOffset(string $string)
Definition: GifBuilder.php:1316
‪TYPO3\CMS\Frontend\Imaging\GifBuilder\circleOffset
‪circleOffset(int $distance, int $iterations)
Definition: GifBuilder.php:1669
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822