GraphicalFunctions::imageMagickConvert()   F
last analyzed

Complexity

Conditions 33
Paths > 20000

Size

Total Lines 100
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 33
eloc 63
nc 76906
nop 8
dl 0
loc 100
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
16
namespace TYPO3\CMS\Core\Imaging;
17
18
use TYPO3\CMS\Core\Cache\CacheManager;
19
use TYPO3\CMS\Core\Charset\CharsetConverter;
20
use TYPO3\CMS\Core\Core\Environment;
21
use TYPO3\CMS\Core\Type\File\ImageInfo;
22
use TYPO3\CMS\Core\Utility\ArrayUtility;
23
use TYPO3\CMS\Core\Utility\CommandUtility;
24
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Core\Utility\MathUtility;
27
use TYPO3\CMS\Core\Utility\PathUtility;
28
use TYPO3\CMS\Core\Utility\StringUtility;
29
30
/**
31
 * Standard graphical functions
32
 *
33
 * Class contains a bunch of cool functions for manipulating graphics with GDlib/Freetype and ImageMagick.
34
 * VERY OFTEN used with gifbuilder that extends this class and provides a TypoScript API to using these functions
35
 */
36
class GraphicalFunctions
37
{
38
    /**
39
     * If set, the frame pointer is appended to the filenames.
40
     *
41
     * @var bool
42
     */
43
    public $addFrameSelection = true;
44
45
    /**
46
     * This should be changed to 'png' if you want this class to read/make PNG-files instead!
47
     *
48
     * @var string
49
     */
50
    public $gifExtension = 'gif';
51
52
    /**
53
     * File formats supported by gdlib. This variable get's filled in "init" method
54
     *
55
     * @var array
56
     */
57
    protected $gdlibExtensions = [];
58
59
    /**
60
     * defines the RGB colorspace to use
61
     *
62
     * @var string
63
     */
64
    protected $colorspace = 'RGB';
65
66
    /**
67
     * colorspace names allowed
68
     *
69
     * @var array
70
     */
71
    protected $allowedColorSpaceNames = [
72
        'CMY',
73
        'CMYK',
74
        'Gray',
75
        'HCL',
76
        'HSB',
77
        'HSL',
78
        'HWB',
79
        'Lab',
80
        'LCH',
81
        'LMS',
82
        'Log',
83
        'Luv',
84
        'OHTA',
85
        'Rec601Luma',
86
        'Rec601YCbCr',
87
        'Rec709Luma',
88
        'Rec709YCbCr',
89
        'RGB',
90
        'sRGB',
91
        'Transparent',
92
        'XYZ',
93
        'YCbCr',
94
        'YCC',
95
        'YIQ',
96
        'YCbCr',
97
        'YUV'
98
    ];
99
100
    /**
101
     * 16777216 Colors is the maximum value for PNG, JPEG truecolor images (24-bit, 8-bit / Channel)
102
     *
103
     * @var int
104
     */
105
    public $truecolorColors = 16777215;
106
107
    /**
108
     * Allowed file extensions perceived as images by TYPO3.
109
     * List should be set to 'gif,png,jpeg,jpg' if IM is not available.
110
     *
111
     * @var array
112
     */
113
    protected $imageFileExt = ['gif', 'jpg', 'jpeg', 'png', 'tif', 'bmp', 'tga', 'pcx', 'ai', 'pdf', 'webp'];
114
115
    /**
116
     * Web image extensions (can be shown by a webbrowser)
117
     *
118
     * @var array
119
     */
120
    protected $webImageExt = ['gif', 'jpg', 'jpeg', 'png'];
121
122
    /**
123
     * Enable ImageMagick effects, disabled by default as IM5+ effects slow down the image generation
124
     *
125
     * @var bool
126
     */
127
    protected $processorEffectsEnabled = false;
128
129
    /**
130
     * @var array
131
     */
132
    public $cmds = [
133
        'jpg' => '',
134
        'jpeg' => '',
135
        'gif' => '',
136
        'png' => ''
137
    ];
138
139
    /**
140
     * Whether ImageMagick/GraphicsMagick is enabled or not
141
     * @var bool
142
     */
143
    protected $processorEnabled;
144
145
    /**
146
     * @var bool
147
     */
148
    protected $mayScaleUp = true;
149
150
    /**
151
     * Filename prefix for images scaled in imageMagickConvert()
152
     *
153
     * @var string
154
     */
155
    public $filenamePrefix = '';
156
157
    /**
158
     * Forcing the output filename of imageMagickConvert() to this value. However after calling imageMagickConvert() it will be set blank again.
159
     *
160
     * @var string
161
     */
162
    public $imageMagickConvert_forceFileNameBody = '';
163
164
    /**
165
     * This flag should always be FALSE. If set TRUE, imageMagickConvert will always write a new file to the tempdir! Used for debugging.
166
     *
167
     * @var bool
168
     */
169
    public $dontCheckForExistingTempFile = false;
170
171
    /**
172
     * Prevents imageMagickConvert() from compressing the gif-files with self::gifCompress()
173
     *
174
     * @var bool
175
     */
176
    public $dontCompress = false;
177
178
    /**
179
     * For debugging only.
180
     * Filenames will not be based on mtime and only filename (not path) will be used.
181
     * This key is also included in the hash of the filename...
182
     *
183
     * @var string
184
     */
185
    public $alternativeOutputKey = '';
186
187
    /**
188
     * All ImageMagick commands executed is stored in this array for tracking. Used by the Install Tools Image section
189
     *
190
     * @var array
191
     */
192
    public $IM_commands = [];
193
194
    /**
195
     * @var array
196
     */
197
    public $workArea = [];
198
199
    /**
200
     * Preserve the alpha transparency layer of read PNG images
201
     *
202
     * @var bool
203
     */
204
    protected $saveAlphaLayer = false;
205
206
    /**
207
     * ImageMagick scaling command; "-auto-orient -geometry" or "-auto-orient -sample". Used in makeText() and imageMagickConvert()
208
     *
209
     * @var string
210
     */
211
    public $scalecmd = '-auto-orient -geometry';
212
213
    /**
214
     * Used by v5_blur() to simulate 10 continuous steps of blurring
215
     *
216
     * @var string
217
     */
218
    protected $im5fx_blurSteps = '1x2,2x2,3x2,4x3,5x3,5x4,6x4,7x5,8x5,9x5';
219
220
    /**
221
     * Used by v5_sharpen() to simulate 10 continuous steps of sharpening.
222
     *
223
     * @var string
224
     */
225
    protected $im5fx_sharpenSteps = '1x2,2x2,3x2,2x3,3x3,4x3,3x4,4x4,4x5,5x5';
226
227
    /**
228
     * This is the limit for the number of pixels in an image before it will be rendered as JPG instead of GIF/PNG
229
     *
230
     * @var int
231
     */
232
    protected $pixelLimitGif = 10000;
233
234
    /**
235
     * Array mapping HTML color names to RGB values.
236
     *
237
     * @var array
238
     */
239
    protected $colMap = [
240
        'aqua' => [0, 255, 255],
241
        'black' => [0, 0, 0],
242
        'blue' => [0, 0, 255],
243
        'fuchsia' => [255, 0, 255],
244
        'gray' => [128, 128, 128],
245
        'green' => [0, 128, 0],
246
        'lime' => [0, 255, 0],
247
        'maroon' => [128, 0, 0],
248
        'navy' => [0, 0, 128],
249
        'olive' => [128, 128, 0],
250
        'purple' => [128, 0, 128],
251
        'red' => [255, 0, 0],
252
        'silver' => [192, 192, 192],
253
        'teal' => [0, 128, 128],
254
        'yellow' => [255, 255, 0],
255
        'white' => [255, 255, 255]
256
    ];
257
258
    /**
259
     * Charset conversion object:
260
     *
261
     * @var CharsetConverter
262
     */
263
    protected $csConvObj;
264
265
    /**
266
     * @var int
267
     */
268
    protected $jpegQuality = 85;
269
270
    /**
271
     * @var string
272
     */
273
    public $map = '';
274
275
    /**
276
     * This holds the operational setup.
277
     * Basically this is a TypoScript array with properties.
278
     *
279
     * @var array
280
     */
281
    public $setup = [];
282
283
    /**
284
     * @var int
285
     */
286
    public $w = 0;
287
288
    /**
289
     * @var int
290
     */
291
    public $h = 0;
292
293
    /**
294
     * @var array
295
     */
296
    protected $OFFSET;
297
298
    /**
299
     * @var resource|\GdImage
300
     */
301
    protected $im;
302
303
    /**
304
     * Reads configuration information from $GLOBALS['TYPO3_CONF_VARS']['GFX']
305
     * and sets some values in internal variables.
306
     */
307
    public function __construct()
308
    {
309
        $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
310
        if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
311
            $this->gdlibExtensions[] = 'jpg';
312
            $this->gdlibExtensions[] = 'jpeg';
313
        }
314
        if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
315
            $this->gdlibExtensions[] = 'png';
316
        }
317
        if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
318
            $this->gdlibExtensions[] = 'gif';
319
        }
320
321
        if ($gfxConf['processor_colorspace'] && in_array($gfxConf['processor_colorspace'], $this->allowedColorSpaceNames, true)) {
322
            $this->colorspace = $gfxConf['processor_colorspace'];
323
        }
324
325
        $this->processorEnabled = (bool)$gfxConf['processor_enabled'];
326
        // Setting default JPG parameters:
327
        $this->jpegQuality = MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, 85);
328
        $this->addFrameSelection = (bool)$gfxConf['processor_allowFrameSelection'];
329
        if ($gfxConf['gdlib_png']) {
330
            $this->gifExtension = 'png';
331
        }
332
        $this->imageFileExt = GeneralUtility::trimExplode(',', $gfxConf['imagefile_ext']);
333
334
        // Boolean. This is necessary if using ImageMagick 5+.
335
        // Effects in Imagemagick 5+ tends to render very slowly!!
336
        // - therefore must be disabled in order not to perform sharpen, blurring and such.
337
        $this->cmds['jpg'] = $this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . $this->jpegQuality;
338
339
        // ... but if 'processor_effects' is set, enable effects
340
        if ($gfxConf['processor_effects']) {
341
            $this->processorEffectsEnabled = true;
342
            $this->cmds['jpg'] .= $this->v5_sharpen(10);
343
            $this->cmds['jpeg'] .= $this->v5_sharpen(10);
344
        }
345
        // Secures that images are not scaled up.
346
        $this->mayScaleUp = (bool)$gfxConf['processor_allowUpscaling'];
347
        $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
348
    }
349
350
    /*************************************************
351
     *
352
     * Layering images / "IMAGE" GIFBUILDER object
353
     *
354
     *************************************************/
355
    /**
356
     * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is TRUE.
357
     * It reads the two images defined by $conf['file'] and $conf['mask'] and copies the $conf['file'] onto the input image pointer image using the $conf['mask'] as a grayscale mask
358
     * The operation involves ImageMagick for combining.
359
     *
360
     * @param resource $im GDlib image pointer
361
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
362
     * @param array $workArea The current working area coordinates.
363
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
364
     */
365
    public function maskImageOntoImage(&$im, $conf, $workArea)
366
    {
367
        if ($conf['file'] && $conf['mask']) {
368
            $imgInf = pathinfo($conf['file']);
369
            $imgExt = strtolower($imgInf['extension']);
370
            if (!in_array($imgExt, $this->gdlibExtensions, true)) {
371
                $BBimage = $this->imageMagickConvert($conf['file'], $this->gifExtension);
372
            } else {
373
                $BBimage = $this->getImageDimensions($conf['file']);
374
            }
375
            $maskInf = pathinfo($conf['mask']);
376
            $maskExt = strtolower($maskInf['extension']);
377
            if (!in_array($maskExt, $this->gdlibExtensions, true)) {
378
                $BBmask = $this->imageMagickConvert($conf['mask'], $this->gifExtension);
379
            } else {
380
                $BBmask = $this->getImageDimensions($conf['mask']);
381
            }
382
            if ($BBimage && $BBmask) {
383
                $w = imagesx($im);
384
                $h = imagesy($im);
385
                $tmpStr = $this->randomName();
386
                $theImage = $tmpStr . '_img.' . $this->gifExtension;
387
                $theDest = $tmpStr . '_dest.' . $this->gifExtension;
388
                $theMask = $tmpStr . '_mask.' . $this->gifExtension;
389
                // Prepare overlay image
390
                $cpImg = $this->imageCreateFromFile($BBimage[3]);
391
                $destImg = imagecreatetruecolor($w, $h);
392
                // Preserve alpha transparency
393
                if ($this->saveAlphaLayer) {
394
                    imagesavealpha($destImg, true);
395
                    $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
396
                    imagefill($destImg, 0, 0, $Bcolor);
397
                } else {
398
                    $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
399
                    imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
400
                }
401
                $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
0 ignored issues
show
Bug introduced by
It seems like $destImg can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...tions::copyGifOntoGif() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

401
                $this->copyGifOntoGif(/** @scrutinizer ignore-type */ $destImg, $cpImg, $conf, $workArea);
Loading history...
402
                $this->ImageWrite($destImg, $theImage);
403
                imagedestroy($cpImg);
404
                imagedestroy($destImg);
405
                // Prepare mask image
406
                $cpImg = $this->imageCreateFromFile($BBmask[3]);
407
                $destImg = imagecreatetruecolor($w, $h);
408
                if ($this->saveAlphaLayer) {
409
                    imagesavealpha($destImg, true);
410
                    $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
411
                    imagefill($destImg, 0, 0, $Bcolor);
412
                } else {
413
                    $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
414
                    imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
415
                }
416
                $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
417
                $this->ImageWrite($destImg, $theMask);
418
                imagedestroy($cpImg);
419
                imagedestroy($destImg);
420
                // Mask the images
421
                $this->ImageWrite($im, $theDest);
422
                // Let combineExec handle maskNegation
423
                $this->combineExec($theDest, $theImage, $theMask, $theDest);
424
                // The main image is loaded again...
425
                $backIm = $this->imageCreateFromFile($theDest);
426
                // ... and if nothing went wrong we load it onto the old one.
427
                if ($backIm) {
0 ignored issues
show
introduced by
$backIm is of type resource, thus it always evaluated to false.
Loading history...
428
                    if (!$this->saveAlphaLayer) {
429
                        imagecolortransparent($backIm, -1);
430
                    }
431
                    $im = $backIm;
432
                }
433
                // Unlink files from process
434
                unlink($theDest);
435
                unlink($theImage);
436
                unlink($theMask);
437
            }
438
        }
439
    }
440
441
    /**
442
     * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is FALSE (using only $conf['file'])
443
     *
444
     * @param resource $im GDlib image pointer
445
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
446
     * @param array $workArea The current working area coordinates.
447
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
448
     * @see maskImageOntoImage()
449
     */
450
    public function copyImageOntoImage(&$im, $conf, $workArea)
451
    {
452
        if ($conf['file']) {
453
            if (!in_array($conf['BBOX'][2], $this->gdlibExtensions, true)) {
454
                $conf['BBOX'] = $this->imageMagickConvert($conf['BBOX'][3], $this->gifExtension);
455
                $conf['file'] = $conf['BBOX'][3];
456
            }
457
            $cpImg = $this->imageCreateFromFile($conf['file']);
458
            $this->copyGifOntoGif($im, $cpImg, $conf, $workArea);
459
            imagedestroy($cpImg);
460
        }
461
    }
462
463
    /**
464
     * Copies two GDlib image pointers onto each other, using TypoScript configuration from $conf and the input $workArea definition.
465
     *
466
     * @param resource $im GDlib image pointer, destination (bottom image)
467
     * @param resource $cpImg GDlib image pointer, source (top image)
468
     * @param array $conf TypoScript array with the properties for the IMAGE GIFBUILDER object. Only used for the "tile" property value.
469
     * @param array $workArea Work area
470
     * @internal
471
     */
472
    public function copyGifOntoGif(&$im, $cpImg, $conf, $workArea)
473
    {
474
        $cpW = imagesx($cpImg);
475
        $cpH = imagesy($cpImg);
476
        $tile = GeneralUtility::intExplode(',', $conf['tile']);
477
        $tile[0] = MathUtility::forceIntegerInRange($tile[0], 1, 20);
478
        $tile[1] = MathUtility::forceIntegerInRange($tile[1], 1, 20);
479
        $cpOff = $this->objPosition($conf, $workArea, [$cpW * $tile[0], $cpH * $tile[1]]);
480
        for ($xt = 0; $xt < $tile[0]; $xt++) {
481
            $Xstart = $cpOff[0] + $cpW * $xt;
482
            // If this image is inside of the workArea, then go on
483
            if ($Xstart + $cpW > $workArea[0]) {
484
                // X:
485
                if ($Xstart < $workArea[0]) {
486
                    $cpImgCutX = $workArea[0] - $Xstart;
487
                    $Xstart = $workArea[0];
488
                } else {
489
                    $cpImgCutX = 0;
490
                }
491
                $w = $cpW - $cpImgCutX;
492
                if ($Xstart > $workArea[0] + $workArea[2] - $w) {
493
                    $w = $workArea[0] + $workArea[2] - $Xstart;
494
                }
495
                // If this image is inside of the workArea, then go on
496
                if ($Xstart < $workArea[0] + $workArea[2]) {
497
                    // Y:
498
                    for ($yt = 0; $yt < $tile[1]; $yt++) {
499
                        $Ystart = $cpOff[1] + $cpH * $yt;
500
                        // If this image is inside of the workArea, then go on
501
                        if ($Ystart + $cpH > $workArea[1]) {
502
                            if ($Ystart < $workArea[1]) {
503
                                $cpImgCutY = $workArea[1] - $Ystart;
504
                                $Ystart = $workArea[1];
505
                            } else {
506
                                $cpImgCutY = 0;
507
                            }
508
                            $h = $cpH - $cpImgCutY;
509
                            if ($Ystart > $workArea[1] + $workArea[3] - $h) {
510
                                $h = $workArea[1] + $workArea[3] - $Ystart;
511
                            }
512
                            // If this image is inside of the workArea, then go on
513
                            if ($Ystart < $workArea[1] + $workArea[3]) {
514
                                $this->imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, $w, $h, $w, $h);
515
                            }
516
                        }
517
                    }
518
                }
519
            }
520
        }
521
    }
522
523
    /**
524
     * Alternative function for using the similar PHP function imagecopyresized(). Used for GD2 only.
525
     *
526
     * OK, the reason for this stupid fix is the following story:
527
     * GD1.x was capable of copying two images together and combining their palettes! GD2 is apparently not.
528
     * With GD2 only the palette of the dest-image is used which mostly results in totally black images when trying to
529
     * copy a color-ful image onto the destination.
530
     * The GD2-fix is to
531
     * 1) Create a blank TRUE-COLOR image
532
     * 2) Copy the destination image onto that one
533
     * 3) Then do the actual operation; Copying the source (top image) onto that
534
     * 4) ... and return the result pointer.
535
     * 5) Reduce colors (if we do not, the result may become strange!)
536
     * It works, but the resulting images is now a true-color PNG which may be very large.
537
     * So, why not use 'imagetruecolortopalette ($im, TRUE, 256)' - well because it does NOT WORK! So simple is that.
538
     *
539
     * @param resource $dstImg Destination image
540
     * @param resource $srcImg Source image
541
     * @param int $dstX Destination x-coordinate
542
     * @param int $dstY Destination y-coordinate
543
     * @param int $srcX Source x-coordinate
544
     * @param int $srcY Source y-coordinate
545
     * @param int $dstWidth Destination width
546
     * @param int $dstHeight Destination height
547
     * @param int $srcWidth Source width
548
     * @param int $srcHeight Source height
549
     * @internal
550
     */
551
    public function imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight)
552
    {
553
        if (!$this->saveAlphaLayer) {
554
            // Make true color image
555
            $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
556
            // Copy the source image onto that
557
            imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
558
            // Then copy the source image onto that (the actual operation!)
559
            imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
560
            // Set the destination image
561
            $dstImg = $tmpImg;
562
        } else {
563
            imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
564
        }
565
    }
566
567
    /********************************
568
     *
569
     * Text / "TEXT" GIFBUILDER object
570
     *
571
     ********************************/
572
    /**
573
     * Implements the "TEXT" GIFBUILDER object
574
     *
575
     * @param resource $im GDlib image pointer
576
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
577
     * @param array $workArea The current working area coordinates.
578
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
579
     */
580
    public function makeText(&$im, $conf, $workArea)
581
    {
582
        // Spacing
583
        [$spacing, $wordSpacing] = $this->calcWordSpacing($conf);
584
        // Position
585
        $txtPos = $this->txtPosition($conf, $workArea, $conf['BBOX']);
586
        $theText = $conf['text'] ?? '';
587
        if (($conf['imgMap'] ?? false) && is_array($conf['imgMap.'])) {
588
            $this->addToMap($this->calcTextCordsForMap($conf['BBOX'][2], $txtPos, $conf['imgMap.']), $conf['imgMap.']);
589
        }
590
        if (!($conf['hideButCreateMap'] ?? false)) {
591
            // Font Color:
592
            $cols = $this->convertColor($conf['fontColor']);
593
            // NiceText is calculated
594
            if (!($conf['niceText'] ?? false)) {
595
                $Fcolor = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
596
                // antiAliasing is setup:
597
                $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
598
                for ($a = 0; $a < $conf['iterations']; $a++) {
599
                    // If any kind of spacing applies, we use this function:
600
                    if ($spacing || $wordSpacing) {
601
                        $this->SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
602
                    } else {
603
                        $this->renderTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'] ?? [], $conf);
604
                    }
605
                }
606
            } else {
607
                // NICETEXT::
608
                // options anti_aliased and iterations is NOT available when doing this!!
609
                $w = imagesx($im);
610
                $h = imagesy($im);
611
                $tmpStr = $this->randomName();
612
                $fileMenu = $tmpStr . '_menuNT.' . $this->gifExtension;
613
                $fileColor = $tmpStr . '_colorNT.' . $this->gifExtension;
614
                $fileMask = $tmpStr . '_maskNT.' . $this->gifExtension;
615
                // Scalefactor
616
                $sF = MathUtility::forceIntegerInRange(($conf['niceText.']['scaleFactor'] ?? 2), 2, 5);
617
                $newW = (int)ceil($sF * imagesx($im));
618
                $newH = (int)ceil($sF * imagesy($im));
619
                // Make mask
620
                $maskImg = imagecreatetruecolor($newW, $newH);
621
                $Bcolor = imagecolorallocate($maskImg, 255, 255, 255);
622
                imagefilledrectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
623
                $Fcolor = imagecolorallocate($maskImg, 0, 0, 0);
624
                // If any kind of spacing applies, we use this function:
625
                if ($spacing || $wordSpacing) {
626
                    $this->SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
0 ignored issues
show
Bug introduced by
It seems like $maskImg can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...s::SpacedImageTTFText() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

626
                    $this->SpacedImageTTFText(/** @scrutinizer ignore-type */ $maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
Loading history...
627
                } else {
628
                    $this->renderTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'] ?? [], $conf, $sF);
0 ignored issues
show
Bug introduced by
It seems like $maskImg can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...ctions::renderTTFText() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

628
                    $this->renderTTFText(/** @scrutinizer ignore-type */ $maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'] ?? [], $conf, $sF);
Loading history...
629
                }
630
                $this->ImageWrite($maskImg, $fileMask);
631
                imagedestroy($maskImg);
632
                // Downscales the mask
633
                if (!$this->processorEffectsEnabled) {
634
                    $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '! -negate');
635
                } else {
636
                    $command = trim(($conf['niceText.']['before'] ?? '') . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . ($conf['niceText.']['after'] ?? '') . ' -negate');
637
                    if (isset($conf['niceText.']['sharpen'])) {
638
                        $command .= $this->v5_sharpen($conf['niceText.']['sharpen']);
639
                    }
640
                }
641
                $this->imageMagickExec($fileMask, $fileMask, $command);
642
                // Make the color-file
643
                $colorImg = imagecreatetruecolor($w, $h);
644
                $Ccolor = imagecolorallocate($colorImg, $cols[0], $cols[1], $cols[2]);
645
                imagefilledrectangle($colorImg, 0, 0, $w, $h, $Ccolor);
646
                $this->ImageWrite($colorImg, $fileColor);
0 ignored issues
show
Bug introduced by
It seems like $colorImg can also be of type GdImage; however, parameter $destImg of TYPO3\CMS\Core\Imaging\G...Functions::ImageWrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

646
                $this->ImageWrite(/** @scrutinizer ignore-type */ $colorImg, $fileColor);
Loading history...
647
                imagedestroy($colorImg);
648
                // The mask is applied
649
                // The main pictures is saved temporarily
650
                $this->ImageWrite($im, $fileMenu);
651
                $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
652
                // The main image is loaded again...
653
                $backIm = $this->imageCreateFromFile($fileMenu);
654
                // ... and if nothing went wrong we load it onto the old one.
655
                if ($backIm) {
0 ignored issues
show
introduced by
$backIm is of type resource, thus it always evaluated to false.
Loading history...
656
                    if (!$this->saveAlphaLayer) {
657
                        imagecolortransparent($backIm, -1);
658
                    }
659
                    $im = $backIm;
660
                }
661
                // Deleting temporary files;
662
                unlink($fileMenu);
663
                unlink($fileColor);
664
                unlink($fileMask);
665
            }
666
        }
667
    }
668
669
    /**
670
     * Calculates text position for printing the text onto the image based on configuration like alignment and workarea.
671
     *
672
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
673
     * @param array $workArea Work area definition
674
     * @param array $BB Bounding box information, was set in \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
675
     * @return array [0]=x, [1]=y, [2]=w, [3]=h
676
     * @internal
677
     * @see makeText()
678
     */
679
    public function txtPosition($conf, $workArea, $BB)
680
    {
681
        $angle = (int)$conf['angle'] / 180 * M_PI;
682
        $conf['angle'] = 0;
683
        $straightBB = $this->calcBBox($conf);
684
        // offset, align, valign, workarea
685
        // [0]=x, [1]=y, [2]=w, [3]=h
686
        $result = [];
687
        $result[2] = $BB[0];
688
        $result[3] = $BB[1];
689
        $w = $workArea[2];
690
        $alignment = $conf['align'] ?? '';
691
        switch ($alignment) {
692
            case 'right':
693
694
            case 'center':
695
                $factor = abs(cos($angle));
696
                $sign = cos($angle) < 0 ? -1 : 1;
697
                $len1 = $sign * $factor * $straightBB[0];
698
                $len2 = $sign * $BB[0];
699
                $result[0] = $w - ceil($len2 * $factor + (1 - $factor) * $len1);
700
                $factor = abs(sin($angle));
701
                $sign = sin($angle) < 0 ? -1 : 1;
702
                $len1 = $sign * $factor * $straightBB[0];
703
                $len2 = $sign * $BB[1];
704
                $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
705
                break;
706
        }
707
        switch ($alignment) {
708
            case 'right':
709
                break;
710
            case 'center':
711
                $result[0] = round($result[0] / 2);
712
                $result[1] = round($result[1] / 2);
713
                break;
714
            default:
715
                $result[0] = 0;
716
                $result[1] = 0;
717
        }
718
        $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
719
        $result = $this->applyOffset($result, $workArea);
720
        return $result;
721
    }
722
723
    /**
724
     * Calculates bounding box information for the TEXT GIFBUILDER object.
725
     *
726
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
727
     * @return array Array with three keys [0]/[1] being x/y and [2] being the bounding box array
728
     * @internal
729
     * @see txtPosition()
730
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
731
     */
732
    public function calcBBox($conf)
733
    {
734
        $sF = $this->getTextScalFactor($conf);
735
        [$spacing, $wordSpacing] = $this->calcWordSpacing($conf, $sF);
736
        $theText = $conf['text'];
737
        $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $theText, ($conf['splitRendering.'] ?? []), $sF);
738
        $theBBoxInfo = $charInf;
739
        if ($conf['angle']) {
740
            $xArr = [$charInf[0], $charInf[2], $charInf[4], $charInf[6]];
741
            $yArr = [$charInf[1], $charInf[3], $charInf[5], $charInf[7]];
742
            $x = max($xArr) - min($xArr);
743
            $y = max($yArr) - min($yArr);
744
        } else {
745
            $x = $charInf[2] - $charInf[0];
746
            $y = $charInf[1] - $charInf[7];
747
        }
748
        // Set original lineHeight (used by line breaks):
749
        $theBBoxInfo['lineHeight'] = $y;
750
        if (!empty($conf['lineHeight'])) {
751
            $theBBoxInfo['lineHeight'] = (int)$conf['lineHeight'];
752
        }
753
754
        // If any kind of spacing applys, we use this function:
755
        if ($spacing || $wordSpacing) {
756
            $x = 0;
757
            if (!$spacing && $wordSpacing) {
758
                $bits = explode(' ', $theText);
759
                foreach ($bits as $word) {
760
                    $word .= ' ';
761
                    $wordInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
762
                    $wordW = $wordInf[2] - $wordInf[0];
763
                    $x += $wordW + $wordSpacing;
764
                }
765
            } else {
766
                $utf8Chars = $this->csConvObj->utf8_to_numberarray($theText);
767
                // For each UTF-8 char, do:
768
                foreach ($utf8Chars as $char) {
769
                    $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
770
                    $charW = $charInf[2] - $charInf[0];
771
                    $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
772
                }
773
            }
774
        } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
775
            $maxWidth = 0;
776
            $currentWidth = 0;
777
            $breakWidth = $conf['breakWidth'];
778
            $breakSpace = $this->getBreakSpace($conf, $theBBoxInfo);
779
            $wordPairs = $this->getWordPairsForLineBreak($conf['text']);
780
            // Iterate through all word pairs:
781
            foreach ($wordPairs as $index => $wordPair) {
782
                $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
783
                if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
784
                    $currentWidth += $wordWidth;
785
                } else {
786
                    $maxWidth = max($maxWidth, $currentWidth);
787
                    $y += $breakSpace;
788
                    // Restart:
789
                    $currentWidth = $wordWidth;
790
                }
791
            }
792
            $x = max($maxWidth, $currentWidth) * $sF;
793
        }
794
        if ($sF > 1) {
795
            $x = ceil($x / $sF);
796
            $y = ceil($y / $sF);
797
            if (is_array($theBBoxInfo)) {
0 ignored issues
show
introduced by
The condition is_array($theBBoxInfo) is always true.
Loading history...
798
                foreach ($theBBoxInfo as &$value) {
799
                    $value = ceil($value / $sF);
800
                }
801
                unset($value);
802
            }
803
        }
804
        return [$x, $y, $theBBoxInfo];
805
    }
806
807
    /**
808
     * Adds an <area> tag to the internal variable $this->map which is used to accumulate the content for an ImageMap
809
     *
810
     * @param array $cords Coordinates for a polygon image map as created by ->calcTextCordsForMap()
811
     * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
812
     * @internal
813
     * @see makeText()
814
     * @see calcTextCordsForMap()
815
     */
816
    public function addToMap($cords, $conf)
817
    {
818
        $this->map .= '<area shape="poly" coords="' . implode(',', $cords) . '"'
819
            . ' href="' . htmlspecialchars($conf['url']) . '"'
820
            . ($conf['target'] ? ' target="' . htmlspecialchars($conf['target']) . '"' : '')
821
            . ((string)$conf['titleText'] !== '' ? ' title="' . htmlspecialchars($conf['titleText']) . '"' : '')
822
            . ' alt="' . htmlspecialchars($conf['altText']) . '" />';
823
    }
824
825
    /**
826
     * Calculating the coordinates for a TEXT string on an image map. Used in an <area> tag
827
     *
828
     * @param array $cords Coordinates (from BBOX array)
829
     * @param array $offset Offset array
830
     * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
831
     * @return array
832
     * @internal
833
     * @see makeText()
834
     * @see calcTextCordsForMap()
835
     */
836
    public function calcTextCordsForMap($cords, $offset, $conf)
837
    {
838
        $newCords = [];
839
        $pars = GeneralUtility::intExplode(',', $conf['explode'] . ',');
840
        $newCords[0] = $cords[0] + $offset[0] - $pars[0];
841
        $newCords[1] = $cords[1] + $offset[1] + $pars[1];
842
        $newCords[2] = $cords[2] + $offset[0] + $pars[0];
843
        $newCords[3] = $cords[3] + $offset[1] + $pars[1];
844
        $newCords[4] = $cords[4] + $offset[0] + $pars[0];
845
        $newCords[5] = $cords[5] + $offset[1] - $pars[1];
846
        $newCords[6] = $cords[6] + $offset[0] - $pars[0];
847
        $newCords[7] = $cords[7] + $offset[1] - $pars[1];
848
        return $newCords;
849
    }
850
851
    /**
852
     * Printing text onto an image like the PHP function imageTTFText does but in addition it offers options for spacing of letters and words.
853
     * Spacing is done by printing one char at a time and this means that the spacing is rather uneven and probably not very nice.
854
     * See
855
     *
856
     * @param resource $im (See argument for PHP function imageTTFtext())
857
     * @param int $fontSize (See argument for PHP function imageTTFtext())
858
     * @param int $angle (See argument for PHP function imageTTFtext())
859
     * @param int $x (See argument for PHP function imageTTFtext())
860
     * @param int $y (See argument for PHP function imageTTFtext())
861
     * @param int $Fcolor (See argument for PHP function imageTTFtext())
862
     * @param string $fontFile (See argument for PHP function imageTTFtext())
863
     * @param string $text (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
864
     * @param int $spacing The spacing of letters in pixels
865
     * @param int $wordSpacing The spacing of words in pixels
866
     * @param array $splitRenderingConf Array
867
     * @param int $sF Scale factor
868
     * @internal
869
     */
870
    public function SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF = 1)
871
    {
872
        $spacing *= $sF;
873
        $wordSpacing *= $sF;
874
        if (!$spacing && $wordSpacing) {
875
            $bits = explode(' ', $text);
876
            foreach ($bits as $word) {
877
                $word .= ' ';
878
                $wordInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
879
                $wordW = $wordInf[2] - $wordInf[0];
880
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
881
                $x += $wordW + $wordSpacing;
882
            }
883
        } else {
884
            $utf8Chars = $this->csConvObj->utf8_to_numberarray($text);
885
            // For each UTF-8 char, do:
886
            foreach ($utf8Chars as $char) {
887
                $charInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
888
                $charW = $charInf[2] - $charInf[0];
889
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
890
                $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
891
            }
892
        }
893
    }
894
895
    /**
896
     * Function that finds the right fontsize that will render the textstring within a certain width
897
     *
898
     * @param array $conf The TypoScript properties of the TEXT GIFBUILDER object
899
     * @return int The new fontSize
900
     * @internal
901
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
902
     */
903
    public function fontResize($conf)
904
    {
905
        // 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!!!!
906
        $maxWidth = (int)$conf['maxWidth'];
907
        [$spacing, $wordSpacing] = $this->calcWordSpacing($conf);
908
        if ($maxWidth) {
909
            // If any kind of spacing applys, we use this function:
910
            if ($spacing || $wordSpacing) {
911
                return $conf['fontSize'];
912
            }
913
            do {
914
                // Determine bounding box.
915
                $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $conf['text'], $conf['splitRendering.']);
916
                if ($conf['angle'] < 0) {
917
                    $pixelWidth = abs($bounds[4] - $bounds[0]);
918
                } elseif ($conf['angle'] > 0) {
919
                    $pixelWidth = abs($bounds[2] - $bounds[6]);
920
                } else {
921
                    $pixelWidth = abs($bounds[4] - $bounds[6]);
922
                }
923
                // Size is fine, exit:
924
                if ($pixelWidth <= $maxWidth) {
925
                    break;
926
                }
927
                $conf['fontSize']--;
928
            } while ($conf['fontSize'] > 1);
929
        }
930
        return $conf['fontSize'];
931
    }
932
933
    /**
934
     * Wrapper for ImageTTFBBox
935
     *
936
     * @param int $fontSize (See argument for PHP function ImageTTFBBox())
937
     * @param int $angle (See argument for PHP function ImageTTFBBox())
938
     * @param string $fontFile (See argument for PHP function ImageTTFBBox())
939
     * @param string $string (See argument for PHP function ImageTTFBBox())
940
     * @param array $splitRendering Split-rendering configuration
941
     * @param int $sF Scale factor
942
     * @return array Information array.
943
     */
944
    public function ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF = 1)
945
    {
946
        // Initialize:
947
        $offsetInfo = [];
948
        $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
949
        // Traverse string parts:
950
        foreach ($stringParts as $strCfg) {
951
            $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
952
            if (is_readable($fontFile)) {
953
                // Calculate Bounding Box for part.
954
                $calc = imagettfbbox($this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
955
                // Calculate offsets:
956
                if (empty($offsetInfo)) {
957
                    // First run, just copy over.
958
                    $offsetInfo = $calc;
959
                } else {
960
                    $offsetInfo[2] += $calc[2] - $calc[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
961
                    $offsetInfo[3] += $calc[3] - $calc[1] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
962
                    $offsetInfo[4] += $calc[4] - $calc[6] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
963
                    $offsetInfo[5] += $calc[5] - $calc[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
964
                }
965
            } else {
966
                debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFBBoxWrapper()');
967
            }
968
        }
969
        return $offsetInfo;
970
    }
971
972
    /**
973
     * Wrapper for ImageTTFText
974
     *
975
     * @param resource $im (See argument for PHP function imageTTFtext())
976
     * @param int $fontSize (See argument for PHP function imageTTFtext())
977
     * @param int $angle (See argument for PHP function imageTTFtext())
978
     * @param int $x (See argument for PHP function imageTTFtext())
979
     * @param int $y (See argument for PHP function imageTTFtext())
980
     * @param int $color (See argument for PHP function imageTTFtext())
981
     * @param string $fontFile (See argument for PHP function imageTTFtext())
982
     * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
983
     * @param array $splitRendering Split-rendering configuration
984
     * @param int $sF Scale factor
985
     */
986
    public function ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF = 1)
987
    {
988
        // Initialize:
989
        $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
990
        $x = (int)ceil($sF * $x);
991
        $y = (int)ceil($sF * $y);
992
        // Traverse string parts:
993
        foreach ($stringParts as $i => $strCfg) {
994
            // Initialize:
995
            $colorIndex = $color;
996
            // Set custom color if any (only when niceText is off):
997
            if (($strCfg['color'] ?? false) && $sF == 1) {
998
                $cols = $this->convertColor($strCfg['color']);
999
                $colorIndex = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
1000
                $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
1001
            }
1002
            // Setting xSpaceBefore
1003
            if ($i) {
1004
                $x += (int)$strCfg['xSpaceBefore'];
1005
                $y -= (int)$strCfg['ySpaceBefore'];
1006
            }
1007
            $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
1008
            if (is_readable($fontFile)) {
1009
                // Render part:
1010
                imagettftext($im, $this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
1011
                // Calculate offset to apply:
1012
                $wordInf = imagettfbbox($this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, GeneralUtility::getFileAbsFileName($strCfg['fontFile']), $strCfg['str']);
1013
                $x += $wordInf[2] - $wordInf[0] + (int)($splitRendering['compX'] ?? 0) + (int)($strCfg['xSpaceAfter'] ?? 0);
1014
                $y += $wordInf[5] - $wordInf[7] - (int)($splitRendering['compY'] ?? 0) - (int)($strCfg['ySpaceAfter'] ?? 0);
1015
            } else {
1016
                debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFTextWrapper()');
1017
            }
1018
        }
1019
    }
1020
1021
    /**
1022
     * Splitting a string for ImageTTFBBox up into an array where each part has its own configuration options.
1023
     *
1024
     * @param string $string UTF-8 string
1025
     * @param array $splitRendering Split-rendering configuration from GIFBUILDER TEXT object.
1026
     * @param int $fontSize Current fontsize
1027
     * @param string $fontFile Current font file
1028
     * @return array Array with input string splitted according to configuration
1029
     */
1030
    public function splitString($string, $splitRendering, $fontSize, $fontFile)
1031
    {
1032
        // Initialize by setting the whole string and default configuration as the first entry.
1033
        $result = [];
1034
        $result[] = [
1035
            'str' => $string,
1036
            'fontSize' => $fontSize,
1037
            'fontFile' => $fontFile
1038
        ];
1039
        // Traverse the split-rendering configuration:
1040
        // Splitting will create more entries in $result with individual configurations.
1041
        if (is_array($splitRendering)) {
0 ignored issues
show
introduced by
The condition is_array($splitRendering) is always true.
Loading history...
1042
            $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($splitRendering);
1043
            // Traverse configured options:
1044
            foreach ($sKeyArray as $key) {
1045
                $cfg = $splitRendering[$key . '.'];
1046
                // Process each type of split rendering keyword:
1047
                switch ((string)$splitRendering[$key]) {
1048
                    case 'highlightWord':
1049
                        if ((string)$cfg['value'] !== '') {
1050
                            $newResult = [];
1051
                            // Traverse the current parts of the result array:
1052
                            foreach ($result as $part) {
1053
                                // Explode the string value by the word value to highlight:
1054
                                $explodedParts = explode($cfg['value'], $part['str']);
1055
                                foreach ($explodedParts as $c => $expValue) {
1056
                                    if ((string)$expValue !== '') {
1057
                                        $newResult[] = array_merge($part, ['str' => $expValue]);
1058
                                    }
1059
                                    if ($c + 1 < count($explodedParts)) {
1060
                                        $newResult[] = [
1061
                                            'str' => $cfg['value'],
1062
                                            'fontSize' => $cfg['fontSize'] ?: $part['fontSize'],
1063
                                            'fontFile' => $cfg['fontFile'] ?: $part['fontFile'],
1064
                                            'color' => $cfg['color'],
1065
                                            'xSpaceBefore' => $cfg['xSpaceBefore'],
1066
                                            'xSpaceAfter' => $cfg['xSpaceAfter'],
1067
                                            'ySpaceBefore' => $cfg['ySpaceBefore'],
1068
                                            'ySpaceAfter' => $cfg['ySpaceAfter']
1069
                                        ];
1070
                                    }
1071
                                }
1072
                            }
1073
                            // Set the new result as result array:
1074
                            if (!empty($newResult)) {
1075
                                $result = $newResult;
1076
                            }
1077
                        }
1078
                        break;
1079
                    case 'charRange':
1080
                        if ((string)$cfg['value'] !== '') {
1081
                            // Initialize range:
1082
                            $ranges = GeneralUtility::trimExplode(',', $cfg['value'], true);
1083
                            foreach ($ranges as $i => $rangeDef) {
1084
                                $ranges[$i] = GeneralUtility::intExplode('-', (string)$ranges[$i]);
1085
                                if (!isset($ranges[$i][1])) {
1086
                                    $ranges[$i][1] = $ranges[$i][0];
1087
                                }
1088
                            }
1089
                            $newResult = [];
1090
                            // Traverse the current parts of the result array:
1091
                            foreach ($result as $part) {
1092
                                // Initialize:
1093
                                $currentState = -1;
1094
                                $bankAccum = '';
1095
                                // Explode the string value by the word value to highlight:
1096
                                $utf8Chars = $this->csConvObj->utf8_to_numberarray($part['str']);
1097
                                foreach ($utf8Chars as $utfChar) {
1098
                                    // Find number and evaluate position:
1099
                                    $uNumber = (int)$this->csConvObj->utf8CharToUnumber($utfChar);
1100
                                    $inRange = 0;
1101
                                    foreach ($ranges as $rangeDef) {
1102
                                        if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
1103
                                            $inRange = 1;
1104
                                            break;
1105
                                        }
1106
                                    }
1107
                                    if ($currentState == -1) {
1108
                                        $currentState = $inRange;
1109
                                    }
1110
                                    // Initialize first char
1111
                                    // Switch bank:
1112
                                    if ($inRange != $currentState && $uNumber !== 9 && $uNumber !== 10 && $uNumber !== 13 && $uNumber !== 32) {
1113
                                        // Set result:
1114
                                        if ($bankAccum !== '') {
1115
                                            $newResult[] = [
1116
                                                'str' => $bankAccum,
1117
                                                'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1118
                                                'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1119
                                                'color' => $currentState ? $cfg['color'] : '',
1120
                                                'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1121
                                                'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1122
                                                'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1123
                                                'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1124
                                            ];
1125
                                        }
1126
                                        // Initialize new settings:
1127
                                        $currentState = $inRange;
1128
                                        $bankAccum = '';
1129
                                    }
1130
                                    // Add char to bank:
1131
                                    $bankAccum .= $utfChar;
1132
                                }
1133
                                // Set result for FINAL part:
1134
                                if ($bankAccum !== '') {
1135
                                    $newResult[] = [
1136
                                        'str' => $bankAccum,
1137
                                        'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1138
                                        'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1139
                                        'color' => $currentState ? $cfg['color'] : '',
1140
                                        'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1141
                                        'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1142
                                        'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1143
                                        'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1144
                                    ];
1145
                                }
1146
                            }
1147
                            // Set the new result as result array:
1148
                            if (!empty($newResult)) {
1149
                                $result = $newResult;
1150
                            }
1151
                        }
1152
                        break;
1153
                }
1154
            }
1155
        }
1156
        return $result;
1157
    }
1158
1159
    /**
1160
     * Calculates the spacing and wordSpacing values
1161
     *
1162
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1163
     * @param int $scaleFactor TypoScript value from eg $conf['niceText.']['scaleFactor']
1164
     * @return array Array with two keys [0]/[1] being array($spacing,$wordSpacing)
1165
     * @internal
1166
     * @see calcBBox()
1167
     */
1168
    public function calcWordSpacing($conf, $scaleFactor = 1)
1169
    {
1170
        $spacing = (int)($conf['spacing'] ?? 0);
1171
        $wordSpacing = (int)($conf['wordSpacing'] ?? 0);
1172
        $wordSpacing = $wordSpacing ?: $spacing * 2;
1173
        $spacing *= $scaleFactor;
1174
        $wordSpacing *= $scaleFactor;
1175
        return [$spacing, $wordSpacing];
1176
    }
1177
1178
    /**
1179
     * Calculates and returns the niceText.scaleFactor
1180
     *
1181
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1182
     * @return int TypoScript value from eg $conf['niceText.']['scaleFactor']
1183
     * @internal
1184
     */
1185
    public function getTextScalFactor($conf)
1186
    {
1187
        if (!($conf['niceText'] ?? false)) {
1188
            $sF = 1;
1189
        } else {
1190
            // NICETEXT::
1191
            $sF = MathUtility::forceIntegerInRange(($conf['niceText.']['scaleFactor'] ?? 2), 2, 5);
1192
        }
1193
        return $sF;
1194
    }
1195
1196
    /**
1197
     * @param array $imageFileExt
1198
     * @internal Only used for ext:install, not part of TYPO3 Core API.
1199
     */
1200
    public function setImageFileExt(array $imageFileExt): void
1201
    {
1202
        $this->imageFileExt = $imageFileExt;
1203
    }
1204
1205
    /**
1206
     * Renders a regular text and takes care of a possible line break automatically.
1207
     *
1208
     * @param resource $im (See argument for PHP function imageTTFtext())
1209
     * @param int $fontSize (See argument for PHP function imageTTFtext())
1210
     * @param int $angle (See argument for PHP function imageTTFtext())
1211
     * @param int $x (See argument for PHP function imageTTFtext())
1212
     * @param int $y (See argument for PHP function imageTTFtext())
1213
     * @param int $color (See argument for PHP function imageTTFtext())
1214
     * @param string $fontFile (See argument for PHP function imageTTFtext())
1215
     * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
1216
     * @param array $splitRendering Split-rendering configuration
1217
     * @param array $conf The configuration
1218
     * @param int $sF Scale factor
1219
     */
1220
    protected function renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF = 1)
1221
    {
1222
        if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
1223
            $phrase = '';
1224
            $currentWidth = 0;
1225
            $breakWidth = $conf['breakWidth'];
1226
            $breakSpace = $this->getBreakSpace($conf);
1227
            $wordPairs = $this->getWordPairsForLineBreak($string);
1228
            // Iterate through all word pairs:
1229
            foreach ($wordPairs as $index => $wordPair) {
1230
                $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
1231
                if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
1232
                    $currentWidth += $wordWidth;
1233
                    $phrase .= $wordPair;
1234
                } else {
1235
                    // Render the current phrase that is below breakWidth:
1236
                    $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1237
                    // Calculate the news height offset:
1238
                    $y += $breakSpace;
1239
                    // Restart the phrase:
1240
                    $currentWidth = $wordWidth;
1241
                    $phrase = $wordPair;
1242
                }
1243
            }
1244
            // Render the remaining phrase:
1245
            if ($currentWidth) {
1246
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1247
            }
1248
        } else {
1249
            $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
1250
        }
1251
    }
1252
1253
    /**
1254
     * Gets the word pairs used for automatic line breaks.
1255
     *
1256
     * @param string $string
1257
     * @return array
1258
     */
1259
    protected function getWordPairsForLineBreak($string)
1260
    {
1261
        $wordPairs = [];
1262
        $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
1263
        $wordsArray = is_array($wordsArray) ? $wordsArray : [];
0 ignored issues
show
introduced by
The condition is_array($wordsArray) is always true.
Loading history...
1264
        $wordsCount = count($wordsArray);
1265
        for ($index = 0; $index < $wordsCount; $index += 2) {
1266
            $wordPairs[] = $wordsArray[$index] . $wordsArray[$index + 1];
1267
        }
1268
        return $wordPairs;
1269
    }
1270
1271
    /**
1272
     * Gets the rendered text width
1273
     *
1274
     * @param string $text
1275
     * @param array $conf
1276
     * @return int
1277
     */
1278
    protected function getRenderedTextWidth($text, $conf)
1279
    {
1280
        $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $text, $conf['splitRendering.']);
1281
        if ($conf['angle'] < 0) {
1282
            $pixelWidth = abs($bounds[4] - $bounds[0]);
1283
        } elseif ($conf['angle'] > 0) {
1284
            $pixelWidth = abs($bounds[2] - $bounds[6]);
1285
        } else {
1286
            $pixelWidth = abs($bounds[4] - $bounds[6]);
1287
        }
1288
        return $pixelWidth;
1289
    }
1290
1291
    /**
1292
     * Gets the break space for each new line.
1293
     *
1294
     * @param array $conf TypoScript configuration for the currently rendered object
1295
     * @param array $boundingBox The bounding box the the currently rendered object
1296
     * @return int The break space
1297
     */
1298
    protected function getBreakSpace($conf, array $boundingBox = null)
1299
    {
1300
        if (!isset($boundingBox)) {
1301
            $boundingBox = $this->calcBBox($conf);
1302
            $boundingBox = $boundingBox[2];
1303
        }
1304
        if (isset($conf['breakSpace']) && $conf['breakSpace']) {
1305
            $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
1306
        } else {
1307
            $breakSpace = $boundingBox['lineHeight'];
1308
        }
1309
        return $breakSpace;
1310
    }
1311
1312
    /*********************************************
1313
     *
1314
     * Other GIFBUILDER objects related to TEXT
1315
     *
1316
     *********************************************/
1317
    /**
1318
     * Implements the "OUTLINE" GIFBUILDER object / property for the TEXT object
1319
     *
1320
     * @param resource $im GDlib image pointer
1321
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1322
     * @param array $workArea The current working area coordinates.
1323
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1324
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1325
     * @see makeText()
1326
     */
1327
    public function makeOutline(&$im, $conf, $workArea, $txtConf)
1328
    {
1329
        $thickness = (int)$conf['thickness'];
1330
        if ($thickness) {
1331
            $txtConf['fontColor'] = $conf['color'];
1332
            $outLineDist = MathUtility::forceIntegerInRange($thickness, 1, 2);
1333
            for ($b = 1; $b <= $outLineDist; $b++) {
1334
                if ($b == 1) {
1335
                    $it = 8;
1336
                } else {
1337
                    $it = 16;
1338
                }
1339
                $outL = $this->circleOffset($b, $it);
1340
                for ($a = 0; $a < $it; $a++) {
1341
                    $this->makeText($im, $txtConf, $this->applyOffset($workArea, $outL[$a]));
1342
                }
1343
            }
1344
        }
1345
    }
1346
1347
    /**
1348
     * Creates some offset values in an array used to simulate a circularly applied outline around TEXT
1349
     *
1350
     * access private
1351
     *
1352
     * @param int $distance Distance
1353
     * @param int $iterations Iterations.
1354
     * @return array
1355
     * @see makeOutline()
1356
     */
1357
    public function circleOffset($distance, $iterations)
1358
    {
1359
        $res = [];
1360
        if ($distance && $iterations) {
1361
            for ($a = 0; $a < $iterations; $a++) {
1362
                $yOff = round(sin(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1363
                if ($yOff) {
1364
                    $yOff = (int)(ceil(abs($yOff / 100)) * ($yOff / abs($yOff)));
1365
                }
1366
                $xOff = round(cos(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1367
                if ($xOff) {
1368
                    $xOff = (int)(ceil(abs($xOff / 100)) * ($xOff / abs($xOff)));
1369
                }
1370
                $res[$a] = [$xOff, $yOff];
1371
            }
1372
        }
1373
        return $res;
1374
    }
1375
1376
    /**
1377
     * Implements the "EMBOSS" GIFBUILDER object / property for the TEXT object
1378
     *
1379
     * @param resource $im GDlib image pointer
1380
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1381
     * @param array $workArea The current working area coordinates.
1382
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1383
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1384
     * @see makeShadow()
1385
     */
1386
    public function makeEmboss(&$im, $conf, $workArea, $txtConf)
1387
    {
1388
        $conf['color'] = $conf['highColor'];
1389
        $this->makeShadow($im, $conf, $workArea, $txtConf);
1390
        $newOffset = GeneralUtility::intExplode(',', $conf['offset']);
1391
        $newOffset[0] *= -1;
1392
        $newOffset[1] *= -1;
1393
        $conf['offset'] = implode(',', $newOffset);
1394
        $conf['color'] = $conf['lowColor'];
1395
        $this->makeShadow($im, $conf, $workArea, $txtConf);
1396
    }
1397
1398
    /**
1399
     * Implements the "SHADOW" GIFBUILDER object / property for the TEXT object
1400
     * The operation involves ImageMagick for combining.
1401
     *
1402
     * @param resource $im GDlib image pointer
1403
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1404
     * @param array $workArea The current working area coordinates.
1405
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1406
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1407
     * @see makeText()
1408
     * @see makeEmboss()
1409
     */
1410
    public function makeShadow(&$im, $conf, $workArea, $txtConf)
1411
    {
1412
        $workArea = $this->applyOffset($workArea, GeneralUtility::intExplode(',', $conf['offset']));
1413
        $blurRate = MathUtility::forceIntegerInRange((int)$conf['blur'], 0, 99);
1414
        // No effects if ImageMagick ver. 5+
1415
        if (!$blurRate || !$this->processorEffectsEnabled) {
1416
            $txtConf['fontColor'] = $conf['color'];
1417
            $this->makeText($im, $txtConf, $workArea);
1418
        } else {
1419
            $w = imagesx($im);
1420
            $h = imagesy($im);
1421
            // Area around the blur used for cropping something
1422
            $blurBorder = 3;
1423
            $tmpStr = $this->randomName();
1424
            $fileMenu = $tmpStr . '_menu.' . $this->gifExtension;
1425
            $fileColor = $tmpStr . '_color.' . $this->gifExtension;
1426
            $fileMask = $tmpStr . '_mask.' . $this->gifExtension;
1427
            // BlurColor Image laves
1428
            $blurColImg = imagecreatetruecolor($w, $h);
1429
            $bcols = $this->convertColor($conf['color']);
1430
            $Bcolor = imagecolorallocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
1431
            imagefilledrectangle($blurColImg, 0, 0, $w, $h, $Bcolor);
1432
            $this->ImageWrite($blurColImg, $fileColor);
0 ignored issues
show
Bug introduced by
It seems like $blurColImg can also be of type GdImage; however, parameter $destImg of TYPO3\CMS\Core\Imaging\G...Functions::ImageWrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1432
            $this->ImageWrite(/** @scrutinizer ignore-type */ $blurColImg, $fileColor);
Loading history...
1433
            imagedestroy($blurColImg);
1434
            // The mask is made: BlurTextImage
1435
            $blurTextImg = imagecreatetruecolor($w + $blurBorder * 2, $h + $blurBorder * 2);
1436
            // Black background
1437
            $Bcolor = imagecolorallocate($blurTextImg, 0, 0, 0);
1438
            imagefilledrectangle($blurTextImg, 0, 0, $w + $blurBorder * 2, $h + $blurBorder * 2, $Bcolor);
1439
            $txtConf['fontColor'] = 'white';
1440
            $blurBordArr = [$blurBorder, $blurBorder];
1441
            $this->makeText($blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
0 ignored issues
show
Bug introduced by
It seems like $blurTextImg can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...alFunctions::makeText() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1441
            $this->makeText(/** @scrutinizer ignore-type */ $blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
Loading history...
1442
            // Dump to temporary file
1443
            $this->ImageWrite($blurTextImg, $fileMask);
1444
            // Destroy
1445
            imagedestroy($blurTextImg);
1446
            $command = $this->v5_blur($blurRate + 1);
1447
            $this->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
1448
            // The mask is loaded again
1449
            $blurTextImg_tmp = $this->imageCreateFromFile($fileMask);
1450
            // If nothing went wrong we continue with the blurred mask
1451
            if ($blurTextImg_tmp) {
0 ignored issues
show
introduced by
$blurTextImg_tmp is of type resource, thus it always evaluated to false.
Loading history...
1452
                // Cropping the border from the mask
1453
                $blurTextImg = imagecreatetruecolor($w, $h);
1454
                $this->imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, $w, $h, $w, $h);
1455
                // Destroy the temporary mask
1456
                imagedestroy($blurTextImg_tmp);
1457
                // Adjust the mask
1458
                $intensity = 40;
1459
                if ($conf['intensity'] ?? false) {
1460
                    $intensity = MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
1461
                }
1462
                $intensity = (int)ceil(255 - $intensity / 100 * 255);
1463
                $this->inputLevels($blurTextImg, 0, $intensity);
1464
                $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 0, 100);
1465
                if ($opacity && $opacity < 100) {
1466
                    $high = (int)ceil(255 * $opacity / 100);
1467
                    // Reducing levels as the opacity demands
1468
                    $this->outputLevels($blurTextImg, 0, $high);
1469
                }
1470
                // Dump the mask again
1471
                $this->ImageWrite($blurTextImg, $fileMask);
1472
                // Destroy the mask
1473
                imagedestroy($blurTextImg);
1474
                // The pictures are combined
1475
                // The main pictures is saved temporarily
1476
                $this->ImageWrite($im, $fileMenu);
1477
                $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
1478
                // The main image is loaded again...
1479
                $backIm = $this->imageCreateFromFile($fileMenu);
1480
                // ... and if nothing went wrong we load it onto the old one.
1481
                if ($backIm) {
1482
                    if (!$this->saveAlphaLayer) {
1483
                        imagecolortransparent($backIm, -1);
1484
                    }
1485
                    $im = $backIm;
1486
                }
1487
            }
1488
            // Deleting temporary files;
1489
            unlink($fileMenu);
1490
            unlink($fileColor);
1491
            unlink($fileMask);
1492
        }
1493
    }
1494
1495
    /****************************
1496
     *
1497
     * Other GIFBUILDER objects
1498
     *
1499
     ****************************/
1500
    /**
1501
     * Implements the "BOX" GIFBUILDER object
1502
     *
1503
     * @param resource $im GDlib image pointer
1504
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1505
     * @param array $workArea The current working area coordinates.
1506
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1507
     */
1508
    public function makeBox(&$im, $conf, $workArea)
1509
    {
1510
        $cords = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1511
        $conf['offset'] = $cords[0] . ',' . $cords[1];
1512
        $cords = $this->objPosition($conf, $workArea, [$cords[2], $cords[3]]);
1513
        $cols = $this->convertColor($conf['color']);
1514
        $opacity = 0;
1515
        if (isset($conf['opacity'])) {
1516
            // conversion:
1517
            // PHP 0 = opaque, 127 = transparent
1518
            // TYPO3 100 = opaque, 0 = transparent
1519
            $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 1, 100, 1);
1520
            $opacity = (int)abs($opacity - 100);
1521
            $opacity = (int)round(127 * $opacity / 100);
1522
        }
1523
        $tmpColor = imagecolorallocatealpha($im, $cols[0], $cols[1], $cols[2], $opacity);
1524
        imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1525
    }
1526
1527
    /**
1528
     * Implements the "Ellipse" GIFBUILDER object
1529
     * Example Typoscript:
1530
     * file  =  GIFBUILDER
1531
     * file  {
1532
     * XY  =  200,200
1533
     * format  =  jpg
1534
     * quality  =  100
1535
     * 10  =  ELLIPSE
1536
     * 10.dimensions  =  100,100,50,50
1537
     * 10.color  =  red
1538
     *
1539
     * $workArea = X,Y
1540
     * $conf['dimensions'] = offset x, offset y, width of ellipse, height of ellipse
1541
     *
1542
     * @param resource $im GDlib image pointer
1543
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1544
     * @param array $workArea The current working area coordinates.
1545
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1546
     */
1547
    public function makeEllipse(&$im, array $conf, array $workArea)
1548
    {
1549
        $ellipseConfiguration = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1550
        // Ellipse offset inside workArea (x/y)
1551
        $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1552
        // @see objPosition
1553
        $imageCoordinates = $this->objPosition($conf, $workArea, [$ellipseConfiguration[2], $ellipseConfiguration[3]]);
1554
        $color = $this->convertColor($conf['color']);
1555
        $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1556
        imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1557
    }
1558
1559
    /**
1560
     * Implements the "EFFECT" GIFBUILDER object
1561
     * The operation involves ImageMagick for applying effects
1562
     *
1563
     * @param resource $im GDlib image pointer
1564
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1565
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1566
     * @see applyImageMagickToPHPGif()
1567
     */
1568
    public function makeEffect(&$im, $conf)
1569
    {
1570
        $commands = $this->IMparams($conf['value']);
1571
        if ($commands) {
1572
            $this->applyImageMagickToPHPGif($im, $commands);
1573
        }
1574
    }
1575
1576
    /**
1577
     * Creating ImageMagick parameters from TypoScript property
1578
     *
1579
     * @param string $setup A string with effect keywords=value pairs separated by "|
1580
     * @return string ImageMagick prepared parameters.
1581
     * @internal
1582
     * @see makeEffect()
1583
     */
1584
    public function IMparams($setup)
1585
    {
1586
        if (!trim($setup)) {
1587
            return '';
1588
        }
1589
        $effects = explode('|', $setup);
1590
        $commands = '';
1591
        foreach ($effects as $val) {
1592
            $pairs = explode('=', $val, 2);
1593
            $value = trim($pairs[1]);
1594
            $effect = strtolower(trim($pairs[0]));
1595
            switch ($effect) {
1596
                case 'gamma':
1597
                    $commands .= ' -gamma ' . (float)$value;
1598
                    break;
1599
                case 'blur':
1600
                    if ($this->processorEffectsEnabled) {
1601
                        $commands .= $this->v5_blur((int)$value);
1602
                    }
1603
                    break;
1604
                case 'sharpen':
1605
                    if ($this->processorEffectsEnabled) {
1606
                        $commands .= $this->v5_sharpen((int)$value);
1607
                    }
1608
                    break;
1609
                case 'rotate':
1610
                    $commands .= ' -rotate ' . MathUtility::forceIntegerInRange((int)$value, 0, 360);
1611
                    break;
1612
                case 'solarize':
1613
                    $commands .= ' -solarize ' . MathUtility::forceIntegerInRange((int)$value, 0, 99);
1614
                    break;
1615
                case 'swirl':
1616
                    $commands .= ' -swirl ' . MathUtility::forceIntegerInRange((int)$value, 0, 1000);
1617
                    break;
1618
                case 'wave':
1619
                    $params = GeneralUtility::intExplode(',', $value);
1620
                    $commands .= ' -wave ' . MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . MathUtility::forceIntegerInRange($params[1], 0, 99);
1621
                    break;
1622
                case 'charcoal':
1623
                    $commands .= ' -charcoal ' . MathUtility::forceIntegerInRange((int)$value, 0, 100);
1624
                    break;
1625
                case 'gray':
1626
                    $commands .= ' -colorspace GRAY';
1627
                    break;
1628
                case 'edge':
1629
                    $commands .= ' -edge ' . MathUtility::forceIntegerInRange((int)$value, 0, 99);
1630
                    break;
1631
                case 'emboss':
1632
                    $commands .= ' -emboss';
1633
                    break;
1634
                case 'flip':
1635
                    $commands .= ' -flip';
1636
                    break;
1637
                case 'flop':
1638
                    $commands .= ' -flop';
1639
                    break;
1640
                case 'colors':
1641
                    $commands .= ' -colors ' . MathUtility::forceIntegerInRange((int)$value, 2, 255);
1642
                    break;
1643
                case 'shear':
1644
                    $commands .= ' -shear ' . MathUtility::forceIntegerInRange((int)$value, -90, 90);
1645
                    break;
1646
                case 'invert':
1647
                    $commands .= ' -negate';
1648
                    break;
1649
            }
1650
        }
1651
        return $commands;
1652
    }
1653
1654
    /**
1655
     * Implements the "ADJUST" GIFBUILDER object
1656
     *
1657
     * @param resource $im GDlib image pointer
1658
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1659
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1660
     * @see autoLevels()
1661
     * @see outputLevels()
1662
     * @see inputLevels()
1663
     */
1664
    public function adjust(&$im, $conf)
1665
    {
1666
        $setup = $conf['value'];
1667
        if (!trim($setup)) {
1668
            return;
1669
        }
1670
        $effects = explode('|', $setup);
1671
        foreach ($effects as $val) {
1672
            $pairs = explode('=', $val, 2);
1673
            $value = trim($pairs[1]);
1674
            $effect = strtolower(trim($pairs[0]));
1675
            switch ($effect) {
1676
                case 'inputlevels':
1677
                    // low,high
1678
                    $params = GeneralUtility::intExplode(',', $value);
1679
                    $this->inputLevels($im, $params[0], $params[1]);
1680
                    break;
1681
                case 'outputlevels':
1682
                    $params = GeneralUtility::intExplode(',', $value);
1683
                    $this->outputLevels($im, $params[0], $params[1]);
1684
                    break;
1685
                case 'autolevels':
1686
                    $this->autolevels($im);
1687
                    break;
1688
            }
1689
        }
1690
    }
1691
1692
    /**
1693
     * Implements the "CROP" GIFBUILDER object
1694
     *
1695
     * @param resource $im GDlib image pointer
1696
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1697
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1698
     */
1699
    public function crop(&$im, $conf)
1700
    {
1701
        // Clears workArea to total image
1702
        $this->setWorkArea('');
1703
        $cords = GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1704
        $conf['offset'] = $cords[0] . ',' . $cords[1];
1705
        $cords = $this->objPosition($conf, $this->workArea, [$cords[2], $cords[3]]);
1706
        $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1707
        $cols = $this->convertColor($conf['backColor'] ?: $this->setup['backColor']);
1708
        $Bcolor = imagecolorallocate($newIm, $cols[0], $cols[1], $cols[2]);
1709
        imagefilledrectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1710
        $newConf = [];
1711
        $workArea = [0, 0, $cords[2], $cords[3]];
1712
        if ($cords[0] < 0) {
1713
            $workArea[0] = abs($cords[0]);
1714
        } else {
1715
            $newConf['offset'] = -$cords[0];
1716
        }
1717
        if ($cords[1] < 0) {
1718
            $workArea[1] = abs($cords[1]);
1719
        } else {
1720
            $newConf['offset'] .= ',' . -$cords[1];
1721
        }
1722
        $this->copyGifOntoGif($newIm, $im, $newConf, $workArea);
0 ignored issues
show
Bug introduced by
It seems like $newIm can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...tions::copyGifOntoGif() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1722
        $this->copyGifOntoGif(/** @scrutinizer ignore-type */ $newIm, $im, $newConf, $workArea);
Loading history...
1723
        $im = $newIm;
1724
        $this->w = imagesx($im);
1725
        $this->h = imagesy($im);
1726
        // Clears workArea to total image
1727
        $this->setWorkArea('');
1728
    }
1729
1730
    /**
1731
     * Implements the "SCALE" GIFBUILDER object
1732
     *
1733
     * @param resource $im GDlib image pointer
1734
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1735
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1736
     */
1737
    public function scale(&$im, $conf)
1738
    {
1739
        if ($conf['width'] || $conf['height'] || $conf['params']) {
1740
            $tmpStr = $this->randomName();
1741
            $theFile = $tmpStr . '.' . $this->gifExtension;
1742
            $this->ImageWrite($im, $theFile);
1743
            $theNewFile = $this->imageMagickConvert($theFile, $this->gifExtension, $conf['width'], $conf['height'], $conf['params']);
1744
            $tmpImg = $this->imageCreateFromFile($theNewFile[3]);
1745
            if ($tmpImg) {
0 ignored issues
show
introduced by
$tmpImg is of type resource, thus it always evaluated to false.
Loading history...
1746
                imagedestroy($im);
1747
                $im = $tmpImg;
1748
                $this->w = imagesx($im);
1749
                $this->h = imagesy($im);
1750
                // Clears workArea to total image
1751
                $this->setWorkArea('');
1752
            }
1753
            unlink($theFile);
1754
            if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1755
                unlink($theNewFile[3]);
1756
            }
1757
        }
1758
    }
1759
1760
    /**
1761
     * Implements the "WORKAREA" GIFBUILDER object when setting it
1762
     * Setting internal working area boundaries (->workArea)
1763
     *
1764
     * @param string $workArea Working area dimensions, comma separated
1765
     * @internal
1766
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1767
     */
1768
    public function setWorkArea($workArea)
1769
    {
1770
        $this->workArea = GeneralUtility::intExplode(',', $workArea);
1771
        $this->workArea = $this->applyOffset($this->workArea, $this->OFFSET);
1772
        if (!$this->workArea[2]) {
1773
            $this->workArea[2] = $this->w;
1774
        }
1775
        if (!$this->workArea[3]) {
1776
            $this->workArea[3] = $this->h;
1777
        }
1778
    }
1779
1780
    /*************************
1781
     *
1782
     * Adjustment functions
1783
     *
1784
     ************************/
1785
    /**
1786
     * Apply auto-levels to input image pointer
1787
     *
1788
     * @param resource $im GDlib Image Pointer
1789
     */
1790
    public function autolevels(&$im)
1791
    {
1792
        $totalCols = imagecolorstotal($im);
1793
        $grayArr = [];
1794
        for ($c = 0; $c < $totalCols; $c++) {
1795
            $cols = imagecolorsforindex($im, $c);
1796
            $grayArr[] = round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
1797
        }
1798
        $min = min($grayArr);
1799
        $max = max($grayArr);
1800
        $delta = $max - $min;
1801
        if ($delta) {
1802
            for ($c = 0; $c < $totalCols; $c++) {
1803
                $cols = imagecolorsforindex($im, $c);
1804
                $cols['red'] = floor(($cols['red'] - $min) / $delta * 255);
1805
                $cols['green'] = floor(($cols['green'] - $min) / $delta * 255);
1806
                $cols['blue'] = floor(($cols['blue'] - $min) / $delta * 255);
1807
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
0 ignored issues
show
Bug introduced by
$cols['green'] of type double is incompatible with the type integer expected by parameter $green of imagecolorset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1807
                imagecolorset($im, $c, $cols['red'], /** @scrutinizer ignore-type */ $cols['green'], $cols['blue']);
Loading history...
Bug introduced by
$cols['blue'] of type double is incompatible with the type integer expected by parameter $blue of imagecolorset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1807
                imagecolorset($im, $c, $cols['red'], $cols['green'], /** @scrutinizer ignore-type */ $cols['blue']);
Loading history...
Bug introduced by
$cols['red'] of type double is incompatible with the type integer expected by parameter $red of imagecolorset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1807
                imagecolorset($im, $c, /** @scrutinizer ignore-type */ $cols['red'], $cols['green'], $cols['blue']);
Loading history...
1808
            }
1809
        }
1810
    }
1811
1812
    /**
1813
     * Apply output levels to input image pointer (decreasing contrast)
1814
     *
1815
     * @param resource $im GDlib Image Pointer
1816
     * @param int $low The "low" value (close to 0)
1817
     * @param int $high The "high" value (close to 255)
1818
     * @param bool $swap If swap, then low and high are swapped. (Useful for negated masks...)
1819
     */
1820
    public function outputLevels(&$im, $low, $high, $swap = false)
1821
    {
1822
        if ($low < $high) {
1823
            $low = MathUtility::forceIntegerInRange($low, 0, 255);
1824
            $high = MathUtility::forceIntegerInRange($high, 0, 255);
1825
            if ($swap) {
1826
                $temp = $low;
1827
                $low = 255 - $high;
1828
                $high = 255 - $temp;
1829
            }
1830
            $delta = $high - $low;
1831
            $totalCols = imagecolorstotal($im);
1832
            for ($c = 0; $c < $totalCols; $c++) {
1833
                $cols = imagecolorsforindex($im, $c);
1834
                $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
1835
                $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
1836
                $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
1837
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
0 ignored issues
show
Bug introduced by
$cols['blue'] of type double is incompatible with the type integer expected by parameter $blue of imagecolorset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1837
                imagecolorset($im, $c, $cols['red'], $cols['green'], /** @scrutinizer ignore-type */ $cols['blue']);
Loading history...
Bug introduced by
$cols['red'] of type double is incompatible with the type integer expected by parameter $red of imagecolorset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1837
                imagecolorset($im, $c, /** @scrutinizer ignore-type */ $cols['red'], $cols['green'], $cols['blue']);
Loading history...
Bug introduced by
$cols['green'] of type double is incompatible with the type integer expected by parameter $green of imagecolorset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1837
                imagecolorset($im, $c, $cols['red'], /** @scrutinizer ignore-type */ $cols['green'], $cols['blue']);
Loading history...
1838
            }
1839
        }
1840
    }
1841
1842
    /**
1843
     * Apply input levels to input image pointer (increasing contrast)
1844
     *
1845
     * @param resource $im GDlib Image Pointer
1846
     * @param int $low The "low" value (close to 0)
1847
     * @param int $high The "high" value (close to 255)
1848
     */
1849
    public function inputLevels(&$im, $low, $high)
1850
    {
1851
        if ($low < $high) {
1852
            $low = MathUtility::forceIntegerInRange($low, 0, 255);
1853
            $high = MathUtility::forceIntegerInRange($high, 0, 255);
1854
            $delta = $high - $low;
1855
            $totalCols = imagecolorstotal($im);
1856
            for ($c = 0; $c < $totalCols; $c++) {
1857
                $cols = imagecolorsforindex($im, $c);
1858
                $cols['red'] = MathUtility::forceIntegerInRange((int)(($cols['red'] - $low) / $delta * 255), 0, 255);
1859
                $cols['green'] = MathUtility::forceIntegerInRange((int)(($cols['green'] - $low) / $delta * 255), 0, 255);
1860
                $cols['blue'] = MathUtility::forceIntegerInRange((int)(($cols['blue'] - $low) / $delta * 255), 0, 255);
1861
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1862
            }
1863
        }
1864
    }
1865
1866
    /**
1867
     * Reduce colors in image using IM and create a palette based image if possible (<=256 colors)
1868
     *
1869
     * @param string $file Image file to reduce
1870
     * @param int $cols Number of colors to reduce the image to.
1871
     * @return string Reduced file
1872
     */
1873
    public function IMreduceColors($file, $cols)
1874
    {
1875
        $fI = GeneralUtility::split_fileref($file);
1876
        $ext = strtolower($fI['fileext']);
1877
        $result = $this->randomName() . '.' . $ext;
1878
        $reduce = MathUtility::forceIntegerInRange($cols, 0, $ext === 'gif' ? 256 : $this->truecolorColors, 0);
1879
        if ($reduce > 0) {
1880
            $params = ' -colors ' . $reduce;
1881
            if ($reduce <= 256) {
1882
                $params .= ' -type Palette';
1883
            }
1884
            $prefix = $ext === 'png' && $reduce <= 256 ? 'png8:' : '';
1885
            $this->imageMagickExec($file, $prefix . $result, $params);
1886
            if ($result) {
1887
                return $result;
1888
            }
1889
        }
1890
        return '';
1891
    }
1892
1893
    /*********************************
1894
     *
1895
     * GIFBUILDER Helper functions
1896
     *
1897
     *********************************/
1898
    /**
1899
     * Returns the IM command for sharpening with ImageMagick 5
1900
     * Uses $this->im5fx_sharpenSteps for translation of the factor to an actual command.
1901
     *
1902
     * @param int $factor The sharpening factor, 0-100 (effectively in 10 steps)
1903
     * @return string The sharpening command, eg. " -sharpen 3x4
1904
     * @see makeText()
1905
     * @see IMparams()
1906
     * @see v5_blur()
1907
     */
1908
    public function v5_sharpen($factor)
1909
    {
1910
        $factor = MathUtility::forceIntegerInRange((int)ceil($factor / 10), 0, 10);
1911
        $sharpenArr = explode(',', ',' . $this->im5fx_sharpenSteps);
1912
        $sharpenF = trim($sharpenArr[$factor]);
1913
        if ($sharpenF) {
1914
            return ' -sharpen ' . $sharpenF;
1915
        }
1916
        return '';
1917
    }
1918
1919
    /**
1920
     * Returns the IM command for blurring with ImageMagick 5.
1921
     * Uses $this->im5fx_blurSteps for translation of the factor to an actual command.
1922
     *
1923
     * @param int $factor The blurring factor, 0-100 (effectively in 10 steps)
1924
     * @return string The blurring command, eg. " -blur 3x4
1925
     * @see makeText()
1926
     * @see IMparams()
1927
     * @see v5_sharpen()
1928
     */
1929
    public function v5_blur($factor)
1930
    {
1931
        $factor = MathUtility::forceIntegerInRange((int)ceil($factor / 10), 0, 10);
1932
        $blurArr = explode(',', ',' . $this->im5fx_blurSteps);
1933
        $blurF = trim($blurArr[$factor]);
1934
        if ($blurF) {
1935
            return ' -blur ' . $blurF;
1936
        }
1937
        return '';
1938
    }
1939
1940
    /**
1941
     * Returns a random filename prefixed with "temp_" and then 32 char md5 hash (without extension).
1942
     * Used by functions in this class to create truly temporary files for the on-the-fly processing. These files will most likely be deleted right away.
1943
     *
1944
     * @return string
1945
     */
1946
    public function randomName()
1947
    {
1948
        GeneralUtility::mkdir_deep(Environment::getVarPath() . '/transient/');
1949
        return Environment::getVarPath() . '/transient/' . md5(StringUtility::getUniqueId());
1950
    }
1951
1952
    /**
1953
     * Applies offset value to coordinated in $cords.
1954
     * Basically the value of key 0/1 of $OFFSET is added to keys 0/1 of $cords
1955
     *
1956
     * @param array $cords Integer coordinates in key 0/1
1957
     * @param array $OFFSET Offset values in key 0/1
1958
     * @return array Modified $cords array
1959
     */
1960
    public function applyOffset($cords, $OFFSET)
1961
    {
1962
        $cords[0] = (int)$cords[0] + (int)$OFFSET[0];
1963
        $cords[1] = (int)$cords[1] + (int)$OFFSET[1];
1964
        return $cords;
1965
    }
1966
1967
    /**
1968
     * Converts a "HTML-color" TypoScript datatype to RGB-values.
1969
     * Default is 0,0,0
1970
     *
1971
     * @param string $string "HTML-color" data type string, eg. 'red', '#ffeedd' or '255,0,255'. You can also add a modifying operator afterwards. There are two options: "255,0,255 : 20" - will add 20 to values, result is "255,20,255". Or "255,0,255 : *1.23" which will multiply all RGB values with 1.23
1972
     * @return array RGB values in key 0/1/2 of the array
1973
     */
1974
    public function convertColor($string)
1975
    {
1976
        $col = [];
1977
        $cParts = explode(':', $string, 2);
1978
        // Finding the RGB definitions of the color:
1979
        $string = $cParts[0];
1980
        if (strpos($string, '#') !== false) {
1981
            $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string) ?? '';
1982
            $col[] = hexdec(substr($string, 0, 2));
1983
            $col[] = hexdec(substr($string, 2, 2));
1984
            $col[] = hexdec(substr($string, 4, 2));
1985
        } elseif (strpos($string, ',') !== false) {
1986
            $string = preg_replace('/[^,0-9]*/', '', $string) ?? '';
1987
            $strArr = explode(',', $string);
1988
            $col[] = (int)$strArr[0];
1989
            $col[] = (int)$strArr[1];
1990
            $col[] = (int)$strArr[2];
1991
        } else {
1992
            $string = strtolower(trim($string));
1993
            if ($this->colMap[$string]) {
1994
                $col = $this->colMap[$string];
1995
            } else {
1996
                $col = [0, 0, 0];
1997
            }
1998
        }
1999
        // ... and possibly recalculating the value
2000
        if (trim($cParts[1] ?? '')) {
2001
            $cParts[1] = trim($cParts[1]);
2002
            if ($cParts[1][0] === '*') {
2003
                $val = (float)substr($cParts[1], 1);
2004
                $col[0] = MathUtility::forceIntegerInRange((int)($col[0] * $val), 0, 255);
2005
                $col[1] = MathUtility::forceIntegerInRange((int)($col[1] * $val), 0, 255);
2006
                $col[2] = MathUtility::forceIntegerInRange((int)($col[2] * $val), 0, 255);
2007
            } else {
2008
                $val = (int)$cParts[1];
2009
                $col[0] = MathUtility::forceIntegerInRange((int)($col[0] + $val), 0, 255);
2010
                $col[1] = MathUtility::forceIntegerInRange((int)($col[1] + $val), 0, 255);
2011
                $col[2] = MathUtility::forceIntegerInRange((int)($col[2] + $val), 0, 255);
2012
            }
2013
        }
2014
        return $col;
2015
    }
2016
2017
    /**
2018
     * Create an array with object position/boundaries based on input TypoScript configuration (such as the "align" property is used), the work area definition and $BB array
2019
     *
2020
     * @param array $conf TypoScript configuration for a GIFBUILDER object
2021
     * @param array $workArea Workarea definition
2022
     * @param array $BB BB (Bounding box) array. Not just used for TEXT objects but also for others
2023
     * @return array [0]=x, [1]=y, [2]=w, [3]=h
2024
     * @internal
2025
     * @see copyGifOntoGif()
2026
     * @see makeBox()
2027
     * @see crop()
2028
     */
2029
    public function objPosition($conf, $workArea, $BB)
2030
    {
2031
        // offset, align, valign, workarea
2032
        $result = [];
2033
        $result[2] = $BB[0];
2034
        $result[3] = $BB[1];
2035
        $w = $workArea[2];
2036
        $h = $workArea[3];
2037
        $align = explode(',', $conf['align'] ?? ',');
2038
        $align[0] = strtolower(substr(trim($align[0]), 0, 1));
2039
        $align[1] = strtolower(substr(trim($align[1]), 0, 1));
2040
        switch ($align[0]) {
2041
            case 'r':
2042
                $result[0] = $w - $result[2];
2043
                break;
2044
            case 'c':
2045
                $result[0] = round(($w - $result[2]) / 2);
2046
                break;
2047
            default:
2048
                $result[0] = 0;
2049
        }
2050
        switch ($align[1]) {
2051
            case 'b':
2052
                // y pos
2053
                $result[1] = $h - $result[3];
2054
                break;
2055
            case 'c':
2056
                $result[1] = round(($h - $result[3]) / 2);
2057
                break;
2058
            default:
2059
                $result[1] = 0;
2060
        }
2061
        $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
2062
        $result = $this->applyOffset($result, $workArea);
2063
        return $result;
2064
    }
2065
2066
    /***********************************
2067
     *
2068
     * Scaling, Dimensions of images
2069
     *
2070
     ***********************************/
2071
    /**
2072
     * Converts $imagefile to another file in temp-dir of type $newExt (extension).
2073
     *
2074
     * @param string $imagefile The image filepath
2075
     * @param string $newExt New extension, eg. "gif", "png", "jpg", "tif". If $newExt is NOT set, the new imagefile will be of the original format. If newExt = 'WEB' then one of the web-formats is applied.
2076
     * @param string $w Width. $w / $h is optional. If only one is given the image is scaled proportionally. If an 'm' exists in the $w or $h and if both are present the $w and $h is regarded as the Maximum w/h and the proportions will be kept
2077
     * @param string $h Height. See $w
2078
     * @param string $params Additional ImageMagick parameters.
2079
     * @param string $frame Refers to which frame-number to select in the image. '' or 0 will select the first frame, 1 will select the next and so on...
2080
     * @param array $options An array with options passed to getImageScale (see this function).
2081
     * @param bool $mustCreate If set, then another image than the input imagefile MUST be returned. Otherwise you can risk that the input image is good enough regarding measures etc and is of course not rendered to a new, temporary file in typo3temp/. But this option will force it to.
2082
     * @return array|null [0]/[1] is w/h, [2] is file extension and [3] is the filename.
2083
     * @see getImageScale()
2084
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
2085
     * @see maskImageOntoImage()
2086
     * @see copyImageOntoImage()
2087
     * @see scale()
2088
     */
2089
    public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $params = '', $frame = '', $options = [], $mustCreate = false)
2090
    {
2091
        if (!$this->processorEnabled) {
2092
            // Returning file info right away
2093
            return $this->getImageDimensions($imagefile);
2094
        }
2095
        $info = $this->getImageDimensions($imagefile);
2096
        if (!$info) {
2097
            return null;
2098
        }
2099
2100
        $newExt = strtolower(trim($newExt));
2101
        // If no extension is given the original extension is used
2102
        if (!$newExt) {
2103
            $newExt = $info[2];
2104
        }
2105
        if ($newExt === 'web') {
2106
            if (in_array($info[2], $this->webImageExt, true)) {
2107
                $newExt = $info[2];
2108
            } else {
2109
                $newExt = $this->gif_or_jpg($info[2], $info[0], $info[1]);
2110
                if (!$params) {
2111
                    $params = $this->cmds[$newExt];
2112
                }
2113
            }
2114
        }
2115
        if (!in_array($newExt, $this->imageFileExt, true)) {
2116
            return null;
2117
        }
2118
2119
        $data = $this->getImageScale($info, $w, $h, $options);
2120
        $w = $data['origW'];
2121
        $h = $data['origH'];
2122
        // If no conversion should be performed
2123
        // this flag is TRUE if the width / height does NOT dictate
2124
        // the image to be scaled!! (that is if no width / height is
2125
        // given or if the destination w/h matches the original image
2126
        // dimensions or if the option to not scale the image is set)
2127
        $noScale = !$w && !$h || $data[0] == $info[0] && $data[1] == $info[1] || !empty($options['noScale']);
2128
        if ($noScale && !$data['crs'] && !$params && !$frame && $newExt == $info[2] && !$mustCreate) {
2129
            // Set the new width and height before returning,
2130
            // if the noScale option is set
2131
            if (!empty($options['noScale'])) {
2132
                $info[0] = $data[0];
2133
                $info[1] = $data[1];
2134
            }
2135
            $info[3] = $imagefile;
2136
            return $info;
2137
        }
2138
        $info[0] = $data[0];
2139
        $info[1] = $data[1];
2140
        $frame = $this->addFrameSelection ? (int)$frame : 0;
2141
        if (!$params) {
2142
            $params = $this->cmds[$newExt] ?? '';
2143
        }
2144
        // Cropscaling:
2145
        if ($data['crs']) {
2146
            if (!$data['origW']) {
2147
                $data['origW'] = $data[0];
2148
            }
2149
            if (!$data['origH']) {
2150
                $data['origH'] = $data[1];
2151
            }
2152
            $offsetX = (int)(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
2153
            $offsetY = (int)(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
2154
            $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . '! +repage';
2155
        }
2156
        $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
2157
        // re-apply colorspace-setting for the resulting image so colors don't appear to dark (sRGB instead of RGB)
2158
        $command .= ' -colorspace ' . $this->colorspace;
2159
        $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
2160
        if ($this->alternativeOutputKey) {
2161
            $theOutputName = GeneralUtility::shortMD5($command . $cropscale . PathUtility::basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
2162
        } else {
2163
            $theOutputName = GeneralUtility::shortMD5($command . $cropscale . $imagefile . filemtime($imagefile) . '[' . $frame . ']');
2164
        }
2165
        if ($this->imageMagickConvert_forceFileNameBody) {
2166
            $theOutputName = $this->imageMagickConvert_forceFileNameBody;
2167
            $this->imageMagickConvert_forceFileNameBody = '';
2168
        }
2169
        // Making the temporary filename
2170
        GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
2171
        $output = Environment::getPublicPath() . '/typo3temp/assets/images/' . $this->filenamePrefix . $theOutputName . '.' . $newExt;
2172
        if ($this->dontCheckForExistingTempFile || !file_exists($output)) {
2173
            $this->imageMagickExec($imagefile, $output, $command, $frame);
2174
        }
2175
        if (file_exists($output)) {
2176
            $info[3] = $output;
2177
            $info[2] = $newExt;
2178
            // params might change some image data!
2179
            if ($params) {
2180
                $info = $this->getImageDimensions($info[3]);
2181
            }
2182
            if ($info[2] == $this->gifExtension && !$this->dontCompress) {
2183
                // Compress with IM (lzw) or GD (rle)  (Workaround for the absence of lzw-compression in GD)
2184
                self::gifCompress($info[3], '');
2185
            }
2186
            return $info;
2187
        }
2188
        return null;
2189
    }
2190
2191
    /**
2192
     * Gets the input image dimensions.
2193
     *
2194
     * @param string $imageFile The image filepath
2195
     * @return array|null Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2196
     * @see imageMagickConvert()
2197
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
2198
     */
2199
    public function getImageDimensions($imageFile)
2200
    {
2201
        $returnArr = null;
2202
        preg_match('/([^\\.]*)$/', $imageFile, $reg);
2203
        if (file_exists($imageFile) && in_array(strtolower($reg[0]), $this->imageFileExt, true)) {
2204
            $returnArr = $this->getCachedImageDimensions($imageFile);
2205
            if (!$returnArr) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $returnArr of type array<integer,integer|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$returnArr is a non-empty array, thus ! $returnArr is always false.
Loading history...
2206
                $imageInfoObject = GeneralUtility::makeInstance(ImageInfo::class, $imageFile);
2207
                if ($imageInfoObject->getWidth()) {
2208
                    $returnArr = [
2209
                        $imageInfoObject->getWidth(),
2210
                        $imageInfoObject->getHeight(),
2211
                        strtolower($reg[0]),
2212
                        $imageFile
2213
                    ];
2214
                    $this->cacheImageDimensions($returnArr);
2215
                }
2216
            }
2217
        }
2218
        return $returnArr;
2219
    }
2220
2221
    /**
2222
     * Caches the result of the getImageDimensions function into the database. Does not check if the file exists.
2223
     *
2224
     * @param array $identifyResult Result of the getImageDimensions function
2225
     *
2226
     * @return bool always TRUE
2227
     */
2228
    public function cacheImageDimensions(array $identifyResult)
2229
    {
2230
        $filePath = $identifyResult[3];
2231
        $statusHash = $this->generateStatusHashForImageFile($filePath);
2232
        $identifier = $this->generateCacheKeyForImageFile($filePath);
2233
2234
        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
2235
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('imagesizes');
2236
        $imageDimensions = [
2237
            'hash'        => $statusHash,
2238
            'imagewidth'  => $identifyResult[0],
2239
            'imageheight' => $identifyResult[1],
2240
        ];
2241
        $cache->set($identifier, $imageDimensions);
2242
2243
        return true;
2244
    }
2245
2246
    /**
2247
     * Fetches the cached image dimensions from the cache. Does not check if the image file exists.
2248
     *
2249
     * @param string $filePath Image file path, relative to public web path
2250
     *
2251
     * @return array|bool an array where [0]/[1] is w/h, [2] is extension and [3] is the file name,
2252
     *                    or FALSE for a cache miss
2253
     */
2254
    public function getCachedImageDimensions($filePath)
2255
    {
2256
        $statusHash = $this->generateStatusHashForImageFile($filePath);
2257
        $identifier = $this->generateCacheKeyForImageFile($filePath);
2258
        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
2259
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('imagesizes');
2260
        $cachedImageDimensions = $cache->get($identifier);
2261
        if (!isset($cachedImageDimensions['hash'])) {
2262
            return false;
2263
        }
2264
2265
        if ($cachedImageDimensions['hash'] !== $statusHash) {
2266
            // The file has changed. Delete the cache entry.
2267
            $cache->remove($identifier);
2268
            $result = false;
2269
        } else {
2270
            preg_match('/([^\\.]*)$/', $filePath, $imageExtension);
2271
            $result = [
2272
                (int)$cachedImageDimensions['imagewidth'],
2273
                (int)$cachedImageDimensions['imageheight'],
2274
                strtolower($imageExtension[0]),
2275
                $filePath
2276
            ];
2277
        }
2278
2279
        return $result;
2280
    }
2281
2282
    /**
2283
     * Creates the key for the image dimensions cache for an image file.
2284
     *
2285
     * This method does not check if the image file actually exists.
2286
     *
2287
     * @param string $filePath Image file path, relative to public web path
2288
     *
2289
     * @return string the hash key (an SHA1 hash), will not be empty
2290
     */
2291
    protected function generateCacheKeyForImageFile($filePath)
2292
    {
2293
        return sha1($filePath);
2294
    }
2295
2296
    /**
2297
     * Creates the status hash to check whether a file has been changed.
2298
     *
2299
     * @param string $filePath Image file path, relative to public web path
2300
     *
2301
     * @return string the status hash (an SHA1 hash)
2302
     */
2303
    protected function generateStatusHashForImageFile($filePath)
2304
    {
2305
        $fileStatus = stat($filePath);
2306
2307
        return sha1($fileStatus['mtime'] . $fileStatus['size']);
2308
    }
2309
2310
    /**
2311
     * Get numbers for scaling the image based on input
2312
     *
2313
     * @param array $info Current image information: Width, Height etc.
2314
     * @param string $w "required" width
2315
     * @param string $h "required" height
2316
     * @param array $options Options: Keys are like "maxW", "maxH", "minW", "minH
2317
     * @return array
2318
     * @internal
2319
     * @see imageMagickConvert()
2320
     */
2321
    public function getImageScale($info, $w, $h, $options)
2322
    {
2323
        $out = [];
2324
        $max = strpos($w . $h, 'm') !== false ? 1 : 0;
2325
        if (strpos($w . $h, 'c') !== false) {
2326
            $out['cropH'] = (int)substr((string)strstr($w, 'c'), 1);
2327
            $out['cropV'] = (int)substr((string)strstr($h, 'c'), 1);
2328
            $crs = true;
2329
        } else {
2330
            $crs = false;
2331
        }
2332
        $out['crs'] = $crs;
2333
        $w = (int)$w;
2334
        $h = (int)$h;
2335
        // If there are max-values...
2336
        if (!empty($options['maxW'])) {
2337
            // If width is given...
2338
            if ($w) {
2339
                if ($w > $options['maxW']) {
2340
                    $w = $options['maxW'];
2341
                    // Height should follow
2342
                    $max = 1;
2343
                }
2344
            } else {
2345
                if ($info[0] > $options['maxW']) {
2346
                    $w = $options['maxW'];
2347
                    // Height should follow
2348
                    $max = 1;
2349
                }
2350
            }
2351
        }
2352
        if (!empty($options['maxH'])) {
2353
            // If height is given...
2354
            if ($h) {
2355
                if ($h > $options['maxH']) {
2356
                    $h = $options['maxH'];
2357
                    // Height should follow
2358
                    $max = 1;
2359
                }
2360
            } else {
2361
                // Changed [0] to [1] 290801
2362
                if ($info[1] > $options['maxH']) {
2363
                    $h = $options['maxH'];
2364
                    // Height should follow
2365
                    $max = 1;
2366
                }
2367
            }
2368
        }
2369
        $out['origW'] = $w;
2370
        $out['origH'] = $h;
2371
        $out['max'] = $max;
2372
        if (!$this->mayScaleUp) {
2373
            if ($w > $info[0]) {
2374
                $w = $info[0];
2375
            }
2376
            if ($h > $info[1]) {
2377
                $h = $info[1];
2378
            }
2379
        }
2380
        // If scaling should be performed. Check that input "info" array will not cause division-by-zero
2381
        if (($w || $h) && $info[0] && $info[1]) {
2382
            if ($w && !$h) {
2383
                $info[1] = ceil($info[1] * ($w / $info[0]));
2384
                $info[0] = $w;
2385
            }
2386
            if (!$w && $h) {
2387
                $info[0] = ceil($info[0] * ($h / $info[1]));
2388
                $info[1] = $h;
2389
            }
2390
            if ($w && $h) {
2391
                if ($max) {
2392
                    $ratio = $info[0] / $info[1];
2393
                    if ($h * $ratio > $w) {
2394
                        $h = round($w / $ratio);
2395
                    } else {
2396
                        $w = round($h * $ratio);
2397
                    }
2398
                }
2399
                if ($crs) {
2400
                    $ratio = $info[0] / $info[1];
2401
                    if ($h * $ratio < $w) {
2402
                        $h = round($w / $ratio);
2403
                    } else {
2404
                        $w = round($h * $ratio);
2405
                    }
2406
                }
2407
                $info[0] = $w;
2408
                $info[1] = $h;
2409
            }
2410
        }
2411
        $out[0] = $info[0];
2412
        $out[1] = $info[1];
2413
        // Set minimum-measures!
2414
        if (isset($options['minW']) && $out[0] < $options['minW']) {
2415
            if (($max || $crs) && $out[0]) {
2416
                $out[1] = round($out[1] * $options['minW'] / $out[0]);
2417
            }
2418
            $out[0] = $options['minW'];
2419
        }
2420
        if (isset($options['minH']) && $out[1] < $options['minH']) {
2421
            if (($max || $crs) && $out[1]) {
2422
                $out[0] = round($out[0] * $options['minH'] / $out[1]);
2423
            }
2424
            $out[1] = $options['minH'];
2425
        }
2426
        return $out;
2427
    }
2428
2429
    /***********************************
2430
     *
2431
     * ImageMagick API functions
2432
     *
2433
     ***********************************/
2434
    /**
2435
     * Call the identify command
2436
     *
2437
     * @param string $imagefile The relative to public web path image filepath
2438
     * @return array|null Returns an array where [0]/[1] is w/h, [2] is extension, [3] is the filename and [4] the real image type identified by ImageMagick.
2439
     */
2440
    public function imageMagickIdentify($imagefile)
2441
    {
2442
        if (!$this->processorEnabled) {
2443
            return null;
2444
        }
2445
2446
        $result = $this->executeIdentifyCommandForImageFile($imagefile);
2447
        if ($result) {
2448
            [$width, $height, $fileExtension, $fileType] = explode(' ', $result);
2449
            if ((int)$width && (int)$height) {
2450
                return [$width, $height, strtolower($fileExtension), $imagefile, strtolower($fileType)];
2451
            }
2452
        }
2453
        return null;
2454
    }
2455
2456
    /**
2457
     * Internal function to execute an IM command fetching information on an image
2458
     *
2459
     * @param string $imageFile the absolute path to the image
2460
     * @return string|null the raw result of the identify command.
2461
     */
2462
    protected function executeIdentifyCommandForImageFile(string $imageFile): ?string
2463
    {
2464
        $frame = $this->addFrameSelection ? 0 : null;
2465
        $cmd = CommandUtility::imageMagickCommand(
2466
            'identify',
2467
            '-format "%w %h %e %m" ' . ImageMagickFile::fromFilePath($imageFile, $frame)
2468
        );
2469
        $returnVal = [];
2470
        CommandUtility::exec($cmd, $returnVal);
2471
        $result = array_pop($returnVal);
2472
        $this->IM_commands[] = ['identify', $cmd, $result];
2473
        return $result;
2474
    }
2475
2476
    /**
2477
     * Executes an ImageMagick "convert" on two filenames, $input and $output using $params before them.
2478
     * Can be used for many things, mostly scaling and effects.
2479
     *
2480
     * @param string $input The relative to public web path image filepath, input file (read from)
2481
     * @param string $output The relative to public web path image filepath, output filename (written to)
2482
     * @param string $params ImageMagick parameters
2483
     * @param int $frame Optional, refers to which frame-number to select in the image. '' or 0
2484
     * @return string The result of a call to PHP function "exec()
2485
     */
2486
    public function imageMagickExec($input, $output, $params, $frame = 0)
2487
    {
2488
        if (!$this->processorEnabled) {
2489
            return '';
2490
        }
2491
        // If addFrameSelection is set in the Install Tool, a frame number is added to
2492
        // select a specific page of the image (by default this will be the first page)
2493
        $frame = $this->addFrameSelection ? (int)$frame : null;
2494
        $cmd = CommandUtility::imageMagickCommand(
2495
            'convert',
2496
            $params
2497
                . ' ' . ImageMagickFile::fromFilePath($input, $frame)
2498
                . ' ' . CommandUtility::escapeShellArgument($output)
2499
        );
2500
        $this->IM_commands[] = [$output, $cmd];
2501
        $ret = CommandUtility::exec($cmd);
2502
        // Change the permissions of the file
2503
        GeneralUtility::fixPermissions($output);
2504
        return $ret;
2505
    }
2506
2507
    /**
2508
     * Executes an ImageMagick "combine" (or composite in newer times) on four filenames - $input, $overlay and $mask as input files and $output as the output filename (written to)
2509
     * Can be used for many things, mostly scaling and effects.
2510
     *
2511
     * @param string $input The relative to public web path image filepath, bottom file
2512
     * @param string $overlay The relative to public web path image filepath, overlay file (top)
2513
     * @param string $mask The relative to public web path image filepath, the mask file (grayscale)
2514
     * @param string $output The relative to public web path image filepath, output filename (written to)
2515
     * @return string
2516
     */
2517
    public function combineExec($input, $overlay, $mask, $output)
2518
    {
2519
        if (!$this->processorEnabled) {
2520
            return '';
2521
        }
2522
        $theMask = $this->randomName() . '.' . $this->gifExtension;
2523
        // +matte = no alpha layer in output
2524
        $this->imageMagickExec($mask, $theMask, '-colorspace GRAY +matte');
2525
2526
        $parameters = '-compose over'
2527
            . ' -quality ' . $this->jpegQuality
2528
            . ' +matte '
2529
            . ImageMagickFile::fromFilePath($input) . ' '
2530
            . ImageMagickFile::fromFilePath($overlay) . ' '
2531
            . ImageMagickFile::fromFilePath($theMask) . ' '
2532
            . CommandUtility::escapeShellArgument($output);
2533
        $cmd = CommandUtility::imageMagickCommand('combine', $parameters);
2534
        $this->IM_commands[] = [$output, $cmd];
2535
        $ret = CommandUtility::exec($cmd);
2536
        // Change the permissions of the file
2537
        GeneralUtility::fixPermissions($output);
2538
        if (is_file($theMask)) {
2539
            @unlink($theMask);
2540
        }
2541
        return $ret;
2542
    }
2543
2544
    /**
2545
     * Compressing a GIF file if not already LZW compressed.
2546
     * This function is a workaround for the fact that ImageMagick and/or GD does not compress GIF-files to their minimum size (that is RLE or no compression used)
2547
     *
2548
     * The function takes a file-reference, $theFile, and saves it again through GD or ImageMagick in order to compress the file
2549
     * GIF:
2550
     * If $type is not set, the compression is done with ImageMagick (provided that $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path_lzw'] is pointing to the path of a lzw-enabled version of 'convert') else with GD (should be RLE-enabled!)
2551
     * If $type is set to either 'IM' or 'GD' the compression is done with ImageMagick and GD respectively
2552
     * PNG:
2553
     * No changes.
2554
     *
2555
     * $theFile is expected to be a valid GIF-file!
2556
     * The function returns a code for the operation.
2557
     *
2558
     * @param string $theFile Filepath
2559
     * @param string $type See description of function
2560
     * @return string Returns "GD" if GD was used, otherwise "IM" if ImageMagick was used. If nothing done at all, it returns empty string.
2561
     */
2562
    public static function gifCompress($theFile, $type)
2563
    {
2564
        $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
2565
        if (!$gfxConf['gif_compress'] || strtolower(substr($theFile, -4, 4)) !== '.gif') {
2566
            return '';
2567
        }
2568
2569
        if (($type === 'IM' || !$type) && $gfxConf['processor_enabled'] && $gfxConf['processor_path_lzw']) {
2570
            // Use temporary file to prevent problems with read and write lock on same file on network file systems
2571
            $temporaryName = PathUtility::dirname($theFile) . '/' . md5(StringUtility::getUniqueId()) . '.gif';
2572
            // Rename could fail, if a simultaneous thread is currently working on the same thing
2573
            if (@rename($theFile, $temporaryName)) {
2574
                $cmd = CommandUtility::imageMagickCommand(
2575
                    'convert',
2576
                    ImageMagickFile::fromFilePath($temporaryName) . ' ' . CommandUtility::escapeShellArgument($theFile),
2577
                    $gfxConf['processor_path_lzw']
2578
                );
2579
                CommandUtility::exec($cmd);
2580
                unlink($temporaryName);
2581
            }
2582
            $returnCode = 'IM';
2583
            if (@is_file($theFile)) {
2584
                GeneralUtility::fixPermissions($theFile);
2585
            }
2586
        } elseif (($type === 'GD' || !$type) && $gfxConf['gdlib'] && !$gfxConf['gdlib_png']) {
2587
            $tempImage = imagecreatefromgif($theFile);
2588
            imagegif($tempImage, $theFile);
2589
            imagedestroy($tempImage);
2590
            $returnCode = 'GD';
2591
            if (@is_file($theFile)) {
2592
                GeneralUtility::fixPermissions($theFile);
2593
            }
2594
        } else {
2595
            $returnCode = '';
2596
        }
2597
2598
        return $returnCode;
2599
    }
2600
2601
    /**
2602
     * Returns filename of the png/gif version of the input file (which can be png or gif).
2603
     * If input file type does not match the wanted output type a conversion is made and temp-filename returned.
2604
     *
2605
     * @param string $theFile Filepath of image file
2606
     * @param bool $output_png If TRUE, then input file is converted to PNG, otherwise to GIF
2607
     * @return string|null If the new image file exists, its filepath is returned
2608
     */
2609
    public static function readPngGif($theFile, $output_png = false)
2610
    {
2611
        if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] || !@is_file($theFile)) {
2612
            return null;
2613
        }
2614
2615
        $ext = strtolower(substr($theFile, -4, 4));
2616
        if ((string)$ext === '.png' && $output_png || (string)$ext === '.gif' && !$output_png) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ((string)$ext === '.png'...'.gif' && ! $output_png, Probably Intended Meaning: (string)$ext === '.png' ....gif' && ! $output_png)
Loading history...
2617
            return $theFile;
2618
        }
2619
2620
        if (!@is_dir(Environment::getPublicPath() . '/typo3temp/assets/images/')) {
2621
            GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
2622
        }
2623
        $newFile = Environment::getPublicPath() . '/typo3temp/assets/images/' . md5($theFile . '|' . filemtime($theFile)) . ($output_png ? '.png' : '.gif');
2624
        $cmd = CommandUtility::imageMagickCommand(
2625
            'convert',
2626
            ImageMagickFile::fromFilePath($theFile)
2627
                . ' ' . CommandUtility::escapeShellArgument($newFile),
2628
            $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path']
2629
        );
2630
        CommandUtility::exec($cmd);
2631
        if (@is_file($newFile)) {
2632
            GeneralUtility::fixPermissions($newFile);
2633
            return $newFile;
2634
        }
2635
        return null;
2636
    }
2637
2638
    /***********************************
2639
     *
2640
     * Various IO functions
2641
     *
2642
     ***********************************/
2643
2644
    /**
2645
     * Applies an ImageMagick parameter to a GDlib image pointer resource by writing the resource to file, performing an IM operation upon it and reading back the result into the ImagePointer.
2646
     *
2647
     * @param resource $im The image pointer (reference)
2648
     * @param string $command The ImageMagick parameters. Like effects, scaling etc.
2649
     */
2650
    public function applyImageMagickToPHPGif(&$im, $command)
2651
    {
2652
        $tmpStr = $this->randomName();
2653
        $theFile = $tmpStr . '.' . $this->gifExtension;
2654
        $this->ImageWrite($im, $theFile);
2655
        $this->imageMagickExec($theFile, $theFile, $command);
2656
        $tmpImg = $this->imageCreateFromFile($theFile);
2657
        if ($tmpImg) {
0 ignored issues
show
introduced by
$tmpImg is of type resource, thus it always evaluated to false.
Loading history...
2658
            imagedestroy($im);
2659
            $im = $tmpImg;
2660
            $this->w = imagesx($im);
2661
            $this->h = imagesy($im);
2662
        }
2663
        unlink($theFile);
2664
    }
2665
2666
    /**
2667
     * Returns an image extension for an output image based on the number of pixels of the output and the file extension of the original file.
2668
     * For example: If the number of pixels exceeds $this->pixelLimitGif (normally 10000) then it will be a "jpg" string in return.
2669
     *
2670
     * @param string $type The file extension, lowercase.
2671
     * @param int $w The width of the output image.
2672
     * @param int $h The height of the output image.
2673
     * @return string The filename, either "jpg" or "gif"/"png" (whatever $this->gifExtension is set to.)
2674
     */
2675
    public function gif_or_jpg($type, $w, $h)
2676
    {
2677
        if ($type === 'ai' || $w * $h < $this->pixelLimitGif) {
2678
            return $this->gifExtension;
2679
        }
2680
        return 'jpg';
2681
    }
2682
2683
    /**
2684
     * Writing the internal image pointer, $this->im, to file based on the extension of the input filename
2685
     * Used in GIFBUILDER
2686
     * Uses $this->setup['reduceColors'] for gif/png images and $this->setup['quality'] for jpg images to reduce size/quality if needed.
2687
     *
2688
     * @param string $file The filename to write to.
2689
     * @return string Returns input filename
2690
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::gifBuild()
2691
     */
2692
    public function output($file)
2693
    {
2694
        if ($file) {
2695
            $reg = [];
2696
            preg_match('/([^\\.]*)$/', $file, $reg);
2697
            $ext = strtolower($reg[0]);
2698
            switch ($ext) {
2699
                case 'gif':
2700
                case 'png':
2701
                    if ($this->ImageWrite($this->im, $file)) {
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $destImg of TYPO3\CMS\Core\Imaging\G...Functions::ImageWrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2701
                    if ($this->ImageWrite(/** @scrutinizer ignore-type */ $this->im, $file)) {
Loading history...
2702
                        // ImageMagick operations
2703
                        if ($this->setup['reduceColors']) {
2704
                            $reduced = $this->IMreduceColors($file, MathUtility::forceIntegerInRange($this->setup['reduceColors'], 256, $this->truecolorColors, 256));
2705
                            if ($reduced) {
2706
                                @copy($reduced, $file);
2707
                                @unlink($reduced);
2708
                            }
2709
                        }
2710
                        // Compress with IM! (adds extra compression, LZW from ImageMagick)
2711
                        // (Workaround for the absence of lzw-compression in GD)
2712
                        self::gifCompress($file, 'IM');
2713
                    }
2714
                    break;
2715
                case 'jpg':
2716
                case 'jpeg':
2717
                    // Use the default
2718
                    $quality = 0;
2719
                    if ($this->setup['quality']) {
2720
                        $quality = MathUtility::forceIntegerInRange($this->setup['quality'], 10, 100);
2721
                    }
2722
                    $this->ImageWrite($this->im, $file, $quality);
2723
                    break;
2724
            }
2725
        }
2726
        return $file;
2727
    }
2728
2729
    /**
2730
     * Destroy internal image pointer, $this->im
2731
     *
2732
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::gifBuild()
2733
     */
2734
    public function destroy()
2735
    {
2736
        imagedestroy($this->im);
2737
    }
2738
2739
    /**
2740
     * Returns Image Tag for input image information array.
2741
     *
2742
     * @param array $imgInfo Image information array, key 0/1 is width/height and key 3 is the src value
2743
     * @return string Image tag for the input image information array.
2744
     */
2745
    public function imgTag($imgInfo)
2746
    {
2747
        return '<img src="' . $imgInfo[3] . '" width="' . $imgInfo[0] . '" height="' . $imgInfo[1] . '" border="0" alt="" />';
2748
    }
2749
2750
    /**
2751
     * Writes the input GDlib image pointer to file
2752
     *
2753
     * @param resource $destImg The GDlib image resource pointer
2754
     * @param string $theImage The filename to write to
2755
     * @param int $quality The image quality (for JPEGs)
2756
     * @return bool The output of either imageGif, imagePng or imageJpeg based on the filename to write
2757
     * @see maskImageOntoImage()
2758
     * @see scale()
2759
     * @see output()
2760
     */
2761
    public function ImageWrite($destImg, $theImage, $quality = 0)
2762
    {
2763
        imageinterlace($destImg, 0);
2764
        $ext = strtolower(substr($theImage, (int)strrpos($theImage, '.') + 1));
2765
        $result = false;
2766
        switch ($ext) {
2767
            case 'jpg':
2768
            case 'jpeg':
2769
                if (function_exists('imagejpeg')) {
2770
                    if ($quality === 0) {
2771
                        $quality = $this->jpegQuality;
2772
                    }
2773
                    $result = imagejpeg($destImg, $theImage, $quality);
2774
                }
2775
                break;
2776
            case 'gif':
2777
                if (function_exists('imagegif')) {
2778
                    imagetruecolortopalette($destImg, true, 256);
2779
                    $result = imagegif($destImg, $theImage);
2780
                }
2781
                break;
2782
            case 'png':
2783
                if (function_exists('imagepng')) {
2784
                    $result = imagepng($destImg, $theImage);
2785
                }
2786
                break;
2787
        }
2788
        if ($result) {
2789
            GeneralUtility::fixPermissions($theImage);
2790
        }
2791
        return $result;
2792
    }
2793
2794
    /**
2795
     * Creates a new GDlib image resource based on the input image filename.
2796
     * If it fails creating an image from the input file a blank gray image with the dimensions of the input image will be created instead.
2797
     *
2798
     * @param string $sourceImg Image filename
2799
     * @return resource Image Resource pointer
2800
     */
2801
    public function imageCreateFromFile($sourceImg)
2802
    {
2803
        $imgInf = pathinfo($sourceImg);
2804
        $ext = strtolower($imgInf['extension']);
2805
        switch ($ext) {
2806
            case 'gif':
2807
                if (function_exists('imagecreatefromgif')) {
2808
                    return imagecreatefromgif($sourceImg);
0 ignored issues
show
Bug Best Practice introduced by
The expression return imagecreatefromgif($sourceImg) also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
2809
                }
2810
                break;
2811
            case 'png':
2812
                if (function_exists('imagecreatefrompng')) {
2813
                    $imageHandle = imagecreatefrompng($sourceImg);
2814
                    if ($this->saveAlphaLayer) {
2815
                        imagesavealpha($imageHandle, true);
2816
                    }
2817
                    return $imageHandle;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $imageHandle also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
2818
                }
2819
                break;
2820
            case 'jpg':
2821
            case 'jpeg':
2822
                if (function_exists('imagecreatefromjpeg')) {
2823
                    return imagecreatefromjpeg($sourceImg);
0 ignored issues
show
Bug Best Practice introduced by
The expression return imagecreatefromjpeg($sourceImg) also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
2824
                }
2825
                break;
2826
        }
2827
        // If non of the above:
2828
        $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $sourceImg);
2829
        $im = imagecreatetruecolor($imageInfo->getWidth(), $imageInfo->getHeight());
2830
        $Bcolor = imagecolorallocate($im, 128, 128, 128);
2831
        imagefilledrectangle($im, 0, 0, $imageInfo->getWidth(), $imageInfo->getHeight(), $Bcolor);
2832
        return $im;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $im also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
2833
    }
2834
2835
    /**
2836
     * Returns the HEX color value for an RGB color array
2837
     *
2838
     * @param array $color RGB color array
2839
     * @return string HEX color value
2840
     */
2841
    public function hexColor($color)
2842
    {
2843
        $r = dechex($color[0]);
2844
        if (strlen($r) < 2) {
2845
            $r = '0' . $r;
2846
        }
2847
        $g = dechex($color[1]);
2848
        if (strlen($g) < 2) {
2849
            $g = '0' . $g;
2850
        }
2851
        $b = dechex($color[2]);
2852
        if (strlen($b) < 2) {
2853
            $b = '0' . $b;
2854
        }
2855
        return '#' . $r . $g . $b;
2856
    }
2857
2858
    /**
2859
     * Unifies all colors given in the colArr color array to the first color in the array.
2860
     *
2861
     * @param resource $img Image resource
2862
     * @param array $colArr Array containing RGB color arrays
2863
     * @param bool $closest
2864
     * @return int The index of the unified color
2865
     */
2866
    public function unifyColors(&$img, $colArr, $closest = false)
2867
    {
2868
        $retCol = -1;
2869
        if (is_array($colArr) && !empty($colArr) && function_exists('imagepng') && function_exists('imagecreatefrompng')) {
2870
            $firstCol = array_shift($colArr);
2871
            $firstColArr = $this->convertColor($firstCol);
2872
            $origName = $preName = $this->randomName() . '.png';
2873
            $postName = $this->randomName() . '.png';
2874
            $tmpImg = null;
2875
            if (count($colArr) > 1) {
2876
                $this->ImageWrite($img, $preName);
2877
                $firstCol = $this->hexColor($firstColArr);
2878
                foreach ($colArr as $transparentColor) {
2879
                    $transparentColor = $this->convertColor($transparentColor);
2880
                    $transparentColor = $this->hexColor($transparentColor);
2881
                    $cmd = '-fill "' . $firstCol . '" -opaque "' . $transparentColor . '"';
2882
                    $this->imageMagickExec($preName, $postName, $cmd);
2883
                    $preName = $postName;
2884
                }
2885
                $this->imageMagickExec($postName, $origName, '');
2886
                if (@is_file($origName)) {
2887
                    $tmpImg = $this->imageCreateFromFile($origName);
2888
                }
2889
            } else {
2890
                $tmpImg = $img;
2891
            }
2892
            if ($tmpImg) {
0 ignored issues
show
introduced by
$tmpImg is of type null|resource, thus it always evaluated to false.
Loading history...
2893
                $img = $tmpImg;
2894
                if ($closest) {
2895
                    $retCol = imagecolorclosest($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2896
                } else {
2897
                    $retCol = imagecolorexact($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2898
                }
2899
            }
2900
            // Unlink files from process
2901
            if ($origName) {
2902
                @unlink($origName);
2903
            }
2904
            if ($postName) {
2905
                @unlink($postName);
2906
            }
2907
        }
2908
        return $retCol;
2909
    }
2910
2911
    /**
2912
     * Creates error image based on gfx/notfound_thumb.png
2913
     * Requires GD lib enabled, otherwise it will exit with the three
2914
     * textstrings outputted as text. Outputs the image stream to browser and exits!
2915
     *
2916
     * @param string $filename Name of the file
2917
     * @param string $textline1 Text line 1
2918
     * @param string $textline2 Text line 2
2919
     * @param string $textline3 Text line 3
2920
     * @throws \RuntimeException
2921
     */
2922
    public function getTemporaryImageWithText($filename, $textline1, $textline2, $textline3)
2923
    {
2924
        if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'])) {
2925
            throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
2926
        }
2927
        // Creates the basis for the error image
2928
        $basePath = ExtensionManagementUtility::extPath('core') . 'Resources/Public/Images/';
2929
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
2930
            $im = imagecreatefrompng($basePath . 'NotFound.png');
2931
        } else {
2932
            $im = imagecreatefromgif($basePath . 'NotFound.gif');
2933
        }
2934
        // Sets background color and print color.
2935
        $white = imagecolorallocate($im, 255, 255, 255);
2936
        $black = imagecolorallocate($im, 0, 0, 0);
2937
        // Prints the text strings with the build-in font functions of GD
2938
        $x = 0;
2939
        $font = 0;
2940
        if ($textline1) {
2941
            imagefilledrectangle($im, $x, 9, 56, 16, $white);
2942
            imagestring($im, $font, $x, 9, $textline1, $black);
2943
        }
2944
        if ($textline2) {
2945
            imagefilledrectangle($im, $x, 19, 56, 26, $white);
2946
            imagestring($im, $font, $x, 19, $textline2, $black);
2947
        }
2948
        if ($textline3) {
2949
            imagefilledrectangle($im, $x, 29, 56, 36, $white);
2950
            imagestring($im, $font, $x, 29, substr($textline3, -14), $black);
2951
        }
2952
        // Outputting the image stream and exit
2953
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
2954
            imagepng($im, $filename);
2955
        } else {
2956
            imagegif($im, $filename);
2957
        }
2958
    }
2959
2960
    /**
2961
     * Function to compensate for DPI resolution.
2962
     * FreeType 2 always has 96 dpi, so it is hard-coded at this place.
2963
     *
2964
     * @param float $fontSize font size for freetype function call
2965
     * @return float compensated font size based on 96 dpi
2966
     */
2967
    protected function compensateFontSizeiBasedOnFreetypeDpi($fontSize)
2968
    {
2969
        return $fontSize / 96.0 * 72;
2970
    }
2971
}
2972