Passed
Push — master ( 73c813...93ac43 )
by
unknown
22:03
created

GraphicalFunctions::setImageFileExt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
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'];
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
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);
0 ignored issues
show
Bug introduced by
It seems like $destImg can also be of type false; however, parameter $image of imagesavealpha() 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

394
                    imagesavealpha(/** @scrutinizer ignore-type */ $destImg, true);
Loading history...
395
                    $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
0 ignored issues
show
Bug introduced by
It seems like $destImg can also be of type false; however, parameter $image of imagecolorallocatealpha() 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

395
                    $Bcolor = imagecolorallocatealpha(/** @scrutinizer ignore-type */ $destImg, 0, 0, 0, 127);
Loading history...
396
                    imagefill($destImg, 0, 0, $Bcolor);
0 ignored issues
show
Bug introduced by
It seems like $destImg can also be of type false; however, parameter $image of imagefill() 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

396
                    imagefill(/** @scrutinizer ignore-type */ $destImg, 0, 0, $Bcolor);
Loading history...
397
                } else {
398
                    $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
0 ignored issues
show
Bug introduced by
It seems like $destImg can also be of type false; however, parameter $image of imagecolorallocate() 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

398
                    $Bcolor = imagecolorallocate(/** @scrutinizer ignore-type */ $destImg, 0, 0, 0);
Loading history...
399
                    imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
0 ignored issues
show
Bug introduced by
It seems like $destImg can also be of type false; however, parameter $image of imagefilledrectangle() 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

399
                    imagefilledrectangle(/** @scrutinizer ignore-type */ $destImg, 0, 0, $w, $h, $Bcolor);
Loading history...
400
                }
401
                $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
0 ignored issues
show
Bug introduced by
It seems like $destImg can also be of type false; 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));
0 ignored issues
show
Bug introduced by
It seems like $tmpImg can also be of type false; however, parameter $dst_image of imagecopyresized() 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

557
            imagecopyresized(/** @scrutinizer ignore-type */ $tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
Loading history...
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'] && is_array($conf['imgMap.'])) {
588
            $this->addToMap($this->calcTextCordsForMap($conf['BBOX'][2], $txtPos, $conf['imgMap.']), $conf['imgMap.']);
589
        }
590
        if (!$conf['hideButCreateMap']) {
591
            // Font Color:
592
            $cols = $this->convertColor($conf['fontColor']);
593
            // NiceText is calculated
594
            if (!$conf['niceText']) {
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, 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);
0 ignored issues
show
Bug introduced by
It seems like $maskImg can also be of type false; however, parameter $image of imagecolorallocate() 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

621
                $Bcolor = imagecolorallocate(/** @scrutinizer ignore-type */ $maskImg, 255, 255, 255);
Loading history...
622
                imagefilledrectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
0 ignored issues
show
Bug introduced by
It seems like $maskImg can also be of type false; however, parameter $image of imagefilledrectangle() 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

622
                imagefilledrectangle(/** @scrutinizer ignore-type */ $maskImg, 0, 0, $newW, $newH, $Bcolor);
Loading history...
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 false; 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 false; 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 ($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 false; 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);
0 ignored issues
show
Bug introduced by
It seems like $colorImg can also be of type false; however, parameter $image of imagedestroy() 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

647
                imagedestroy(/** @scrutinizer ignore-type */ $colorImg);
Loading history...
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
        switch ($conf['align']) {
691
            case 'right':
692
693
            case 'center':
694
                $factor = abs(cos($angle));
695
                $sign = cos($angle) < 0 ? -1 : 1;
696
                $len1 = $sign * $factor * $straightBB[0];
697
                $len2 = $sign * $BB[0];
698
                $result[0] = $w - ceil($len2 * $factor + (1 - $factor) * $len1);
699
                $factor = abs(sin($angle));
700
                $sign = sin($angle) < 0 ? -1 : 1;
701
                $len1 = $sign * $factor * $straightBB[0];
702
                $len2 = $sign * $BB[1];
703
                $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
704
                break;
705
        }
706
        switch ($conf['align']) {
707
            case 'right':
708
                break;
709
            case 'center':
710
                $result[0] = round($result[0] / 2);
711
                $result[1] = round($result[1] / 2);
712
                break;
713
            default:
714
                $result[0] = 0;
715
                $result[1] = 0;
716
        }
717
        $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
718
        $result = $this->applyOffset($result, $workArea);
719
        return $result;
720
    }
721
722
    /**
723
     * Calculates bounding box information for the TEXT GIFBUILDER object.
724
     *
725
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
726
     * @return array Array with three keys [0]/[1] being x/y and [2] being the bounding box array
727
     * @internal
728
     * @see txtPosition()
729
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
730
     */
731
    public function calcBBox($conf)
732
    {
733
        $sF = $this->getTextScalFactor($conf);
734
        [$spacing, $wordSpacing] = $this->calcWordSpacing($conf, $sF);
735
        $theText = $conf['text'];
736
        $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $theText, $conf['splitRendering.'], $sF);
737
        $theBBoxInfo = $charInf;
738
        if ($conf['angle']) {
739
            $xArr = [$charInf[0], $charInf[2], $charInf[4], $charInf[6]];
740
            $yArr = [$charInf[1], $charInf[3], $charInf[5], $charInf[7]];
741
            $x = max($xArr) - min($xArr);
742
            $y = max($yArr) - min($yArr);
743
        } else {
744
            $x = $charInf[2] - $charInf[0];
745
            $y = $charInf[1] - $charInf[7];
746
        }
747
        // Set original lineHeight (used by line breaks):
748
        $theBBoxInfo['lineHeight'] = $y;
749
        if (!empty($conf['lineHeight'])) {
750
            $theBBoxInfo['lineHeight'] = (int)$conf['lineHeight'];
751
        }
752
753
        // If any kind of spacing applys, we use this function:
754
        if ($spacing || $wordSpacing) {
755
            $x = 0;
756
            if (!$spacing && $wordSpacing) {
757
                $bits = explode(' ', $theText);
758
                foreach ($bits as $word) {
759
                    $word .= ' ';
760
                    $wordInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
761
                    $wordW = $wordInf[2] - $wordInf[0];
762
                    $x += $wordW + $wordSpacing;
763
                }
764
            } else {
765
                $utf8Chars = $this->csConvObj->utf8_to_numberarray($theText);
766
                // For each UTF-8 char, do:
767
                foreach ($utf8Chars as $char) {
768
                    $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
769
                    $charW = $charInf[2] - $charInf[0];
770
                    $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
771
                }
772
            }
773
        } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
774
            $maxWidth = 0;
775
            $currentWidth = 0;
776
            $breakWidth = $conf['breakWidth'];
777
            $breakSpace = $this->getBreakSpace($conf, $theBBoxInfo);
778
            $wordPairs = $this->getWordPairsForLineBreak($conf['text']);
779
            // Iterate through all word pairs:
780
            foreach ($wordPairs as $index => $wordPair) {
781
                $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
782
                if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
783
                    $currentWidth += $wordWidth;
784
                } else {
785
                    $maxWidth = max($maxWidth, $currentWidth);
786
                    $y += $breakSpace;
787
                    // Restart:
788
                    $currentWidth = $wordWidth;
789
                }
790
            }
791
            $x = max($maxWidth, $currentWidth) * $sF;
792
        }
793
        if ($sF > 1) {
794
            $x = ceil($x / $sF);
795
            $y = ceil($y / $sF);
796
            if (is_array($theBBoxInfo)) {
0 ignored issues
show
introduced by
The condition is_array($theBBoxInfo) is always true.
Loading history...
797
                foreach ($theBBoxInfo as &$value) {
798
                    $value = ceil($value / $sF);
799
                }
800
                unset($value);
801
            }
802
        }
803
        return [$x, $y, $theBBoxInfo];
804
    }
805
806
    /**
807
     * Adds an <area> tag to the internal variable $this->map which is used to accumulate the content for an ImageMap
808
     *
809
     * @param array $cords Coordinates for a polygon image map as created by ->calcTextCordsForMap()
810
     * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
811
     * @internal
812
     * @see makeText()
813
     * @see calcTextCordsForMap()
814
     */
815
    public function addToMap($cords, $conf)
816
    {
817
        $this->map .= '<area shape="poly" coords="' . implode(',', $cords) . '"'
818
            . ' href="' . htmlspecialchars($conf['url']) . '"'
819
            . ($conf['target'] ? ' target="' . htmlspecialchars($conf['target']) . '"' : '')
820
            . ((string)$conf['titleText'] !== '' ? ' title="' . htmlspecialchars($conf['titleText']) . '"' : '')
821
            . ' alt="' . htmlspecialchars($conf['altText']) . '" />';
822
    }
823
824
    /**
825
     * Calculating the coordinates for a TEXT string on an image map. Used in an <area> tag
826
     *
827
     * @param array $cords Coordinates (from BBOX array)
828
     * @param array $offset Offset array
829
     * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
830
     * @return array
831
     * @internal
832
     * @see makeText()
833
     * @see calcTextCordsForMap()
834
     */
835
    public function calcTextCordsForMap($cords, $offset, $conf)
836
    {
837
        $newCords = [];
838
        $pars = GeneralUtility::intExplode(',', $conf['explode'] . ',');
839
        $newCords[0] = $cords[0] + $offset[0] - $pars[0];
840
        $newCords[1] = $cords[1] + $offset[1] + $pars[1];
841
        $newCords[2] = $cords[2] + $offset[0] + $pars[0];
842
        $newCords[3] = $cords[3] + $offset[1] + $pars[1];
843
        $newCords[4] = $cords[4] + $offset[0] + $pars[0];
844
        $newCords[5] = $cords[5] + $offset[1] - $pars[1];
845
        $newCords[6] = $cords[6] + $offset[0] - $pars[0];
846
        $newCords[7] = $cords[7] + $offset[1] - $pars[1];
847
        return $newCords;
848
    }
849
850
    /**
851
     * Printing text onto an image like the PHP function imageTTFText does but in addition it offers options for spacing of letters and words.
852
     * Spacing is done by printing one char at a time and this means that the spacing is rather uneven and probably not very nice.
853
     * See
854
     *
855
     * @param resource $im (See argument for PHP function imageTTFtext())
856
     * @param int $fontSize (See argument for PHP function imageTTFtext())
857
     * @param int $angle (See argument for PHP function imageTTFtext())
858
     * @param int $x (See argument for PHP function imageTTFtext())
859
     * @param int $y (See argument for PHP function imageTTFtext())
860
     * @param int $Fcolor (See argument for PHP function imageTTFtext())
861
     * @param string $fontFile (See argument for PHP function imageTTFtext())
862
     * @param string $text (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
863
     * @param int $spacing The spacing of letters in pixels
864
     * @param int $wordSpacing The spacing of words in pixels
865
     * @param array $splitRenderingConf Array
866
     * @param int $sF Scale factor
867
     * @internal
868
     */
869
    public function SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF = 1)
870
    {
871
        $spacing *= $sF;
872
        $wordSpacing *= $sF;
873
        if (!$spacing && $wordSpacing) {
874
            $bits = explode(' ', $text);
875
            foreach ($bits as $word) {
876
                $word .= ' ';
877
                $wordInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
878
                $wordW = $wordInf[2] - $wordInf[0];
879
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
880
                $x += $wordW + $wordSpacing;
881
            }
882
        } else {
883
            $utf8Chars = $this->csConvObj->utf8_to_numberarray($text);
884
            // For each UTF-8 char, do:
885
            foreach ($utf8Chars as $char) {
886
                $charInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
887
                $charW = $charInf[2] - $charInf[0];
888
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
889
                $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
890
            }
891
        }
892
    }
893
894
    /**
895
     * Function that finds the right fontsize that will render the textstring within a certain width
896
     *
897
     * @param array $conf The TypoScript properties of the TEXT GIFBUILDER object
898
     * @return int The new fontSize
899
     * @internal
900
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
901
     */
902
    public function fontResize($conf)
903
    {
904
        // 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!!!!
905
        $maxWidth = (int)$conf['maxWidth'];
906
        [$spacing, $wordSpacing] = $this->calcWordSpacing($conf);
907
        if ($maxWidth) {
908
            // If any kind of spacing applys, we use this function:
909
            if ($spacing || $wordSpacing) {
910
                return $conf['fontSize'];
911
            }
912
            do {
913
                // Determine bounding box.
914
                $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $conf['text'], $conf['splitRendering.']);
915
                if ($conf['angle'] < 0) {
916
                    $pixelWidth = abs($bounds[4] - $bounds[0]);
917
                } elseif ($conf['angle'] > 0) {
918
                    $pixelWidth = abs($bounds[2] - $bounds[6]);
919
                } else {
920
                    $pixelWidth = abs($bounds[4] - $bounds[6]);
921
                }
922
                // Size is fine, exit:
923
                if ($pixelWidth <= $maxWidth) {
924
                    break;
925
                }
926
                $conf['fontSize']--;
927
            } while ($conf['fontSize'] > 1);
928
        }
929
        return $conf['fontSize'];
930
    }
931
932
    /**
933
     * Wrapper for ImageTTFBBox
934
     *
935
     * @param int $fontSize (See argument for PHP function ImageTTFBBox())
936
     * @param int $angle (See argument for PHP function ImageTTFBBox())
937
     * @param string $fontFile (See argument for PHP function ImageTTFBBox())
938
     * @param string $string (See argument for PHP function ImageTTFBBox())
939
     * @param array $splitRendering Split-rendering configuration
940
     * @param int $sF Scale factor
941
     * @return array Information array.
942
     */
943
    public function ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF = 1)
944
    {
945
        // Initialize:
946
        $offsetInfo = [];
947
        $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
948
        // Traverse string parts:
949
        foreach ($stringParts as $strCfg) {
950
            $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
951
            if (is_readable($fontFile)) {
952
                // Calculate Bounding Box for part.
953
                $calc = imagettfbbox($this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
954
                // Calculate offsets:
955
                if (empty($offsetInfo)) {
956
                    // First run, just copy over.
957
                    $offsetInfo = $calc;
958
                } else {
959
                    $offsetInfo[2] += $calc[2] - $calc[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
960
                    $offsetInfo[3] += $calc[3] - $calc[1] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
961
                    $offsetInfo[4] += $calc[4] - $calc[6] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
962
                    $offsetInfo[5] += $calc[5] - $calc[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
963
                }
964
            } else {
965
                debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFBBoxWrapper()');
966
            }
967
        }
968
        return $offsetInfo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $offsetInfo could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
969
    }
970
971
    /**
972
     * Wrapper for ImageTTFText
973
     *
974
     * @param resource $im (See argument for PHP function imageTTFtext())
975
     * @param int $fontSize (See argument for PHP function imageTTFtext())
976
     * @param int $angle (See argument for PHP function imageTTFtext())
977
     * @param int $x (See argument for PHP function imageTTFtext())
978
     * @param int $y (See argument for PHP function imageTTFtext())
979
     * @param int $color (See argument for PHP function imageTTFtext())
980
     * @param string $fontFile (See argument for PHP function imageTTFtext())
981
     * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
982
     * @param array $splitRendering Split-rendering configuration
983
     * @param int $sF Scale factor
984
     */
985
    public function ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF = 1)
986
    {
987
        // Initialize:
988
        $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
989
        $x = (int)ceil($sF * $x);
990
        $y = (int)ceil($sF * $y);
991
        // Traverse string parts:
992
        foreach ($stringParts as $i => $strCfg) {
993
            // Initialize:
994
            $colorIndex = $color;
995
            // Set custom color if any (only when niceText is off):
996
            if ($strCfg['color'] && $sF == 1) {
997
                $cols = $this->convertColor($strCfg['color']);
998
                $colorIndex = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
999
                $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
1000
            }
1001
            // Setting xSpaceBefore
1002
            if ($i) {
1003
                $x += (int)$strCfg['xSpaceBefore'];
1004
                $y -= (int)$strCfg['ySpaceBefore'];
1005
            }
1006
            $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
1007
            if (is_readable($fontFile)) {
1008
                // Render part:
1009
                imagettftext($im, $this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
1010
                // Calculate offset to apply:
1011
                $wordInf = imagettfbbox($this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, GeneralUtility::getFileAbsFileName($strCfg['fontFile']), $strCfg['str']);
1012
                $x += $wordInf[2] - $wordInf[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceAfter'];
1013
                $y += $wordInf[5] - $wordInf[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceAfter'];
1014
            } else {
1015
                debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFTextWrapper()');
1016
            }
1017
        }
1018
    }
1019
1020
    /**
1021
     * Splitting a string for ImageTTFBBox up into an array where each part has its own configuration options.
1022
     *
1023
     * @param string $string UTF-8 string
1024
     * @param array $splitRendering Split-rendering configuration from GIFBUILDER TEXT object.
1025
     * @param int $fontSize Current fontsize
1026
     * @param string $fontFile Current font file
1027
     * @return array Array with input string splitted according to configuration
1028
     */
1029
    public function splitString($string, $splitRendering, $fontSize, $fontFile)
1030
    {
1031
        // Initialize by setting the whole string and default configuration as the first entry.
1032
        $result = [];
1033
        $result[] = [
1034
            'str' => $string,
1035
            'fontSize' => $fontSize,
1036
            'fontFile' => $fontFile
1037
        ];
1038
        // Traverse the split-rendering configuration:
1039
        // Splitting will create more entries in $result with individual configurations.
1040
        if (is_array($splitRendering)) {
0 ignored issues
show
introduced by
The condition is_array($splitRendering) is always true.
Loading history...
1041
            $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($splitRendering);
1042
            // Traverse configured options:
1043
            foreach ($sKeyArray as $key) {
1044
                $cfg = $splitRendering[$key . '.'];
1045
                // Process each type of split rendering keyword:
1046
                switch ((string)$splitRendering[$key]) {
1047
                    case 'highlightWord':
1048
                        if ((string)$cfg['value'] !== '') {
1049
                            $newResult = [];
1050
                            // Traverse the current parts of the result array:
1051
                            foreach ($result as $part) {
1052
                                // Explode the string value by the word value to highlight:
1053
                                $explodedParts = explode($cfg['value'], $part['str']);
1054
                                foreach ($explodedParts as $c => $expValue) {
1055
                                    if ((string)$expValue !== '') {
1056
                                        $newResult[] = array_merge($part, ['str' => $expValue]);
1057
                                    }
1058
                                    if ($c + 1 < count($explodedParts)) {
1059
                                        $newResult[] = [
1060
                                            'str' => $cfg['value'],
1061
                                            'fontSize' => $cfg['fontSize'] ?: $part['fontSize'],
1062
                                            'fontFile' => $cfg['fontFile'] ?: $part['fontFile'],
1063
                                            'color' => $cfg['color'],
1064
                                            'xSpaceBefore' => $cfg['xSpaceBefore'],
1065
                                            'xSpaceAfter' => $cfg['xSpaceAfter'],
1066
                                            'ySpaceBefore' => $cfg['ySpaceBefore'],
1067
                                            'ySpaceAfter' => $cfg['ySpaceAfter']
1068
                                        ];
1069
                                    }
1070
                                }
1071
                            }
1072
                            // Set the new result as result array:
1073
                            if (!empty($newResult)) {
1074
                                $result = $newResult;
1075
                            }
1076
                        }
1077
                        break;
1078
                    case 'charRange':
1079
                        if ((string)$cfg['value'] !== '') {
1080
                            // Initialize range:
1081
                            $ranges = GeneralUtility::trimExplode(',', $cfg['value'], true);
1082
                            foreach ($ranges as $i => $rangeDef) {
1083
                                $ranges[$i] = GeneralUtility::intExplode('-', (string)$ranges[$i]);
1084
                                if (!isset($ranges[$i][1])) {
1085
                                    $ranges[$i][1] = $ranges[$i][0];
1086
                                }
1087
                            }
1088
                            $newResult = [];
1089
                            // Traverse the current parts of the result array:
1090
                            foreach ($result as $part) {
1091
                                // Initialize:
1092
                                $currentState = -1;
1093
                                $bankAccum = '';
1094
                                // Explode the string value by the word value to highlight:
1095
                                $utf8Chars = $this->csConvObj->utf8_to_numberarray($part['str']);
1096
                                foreach ($utf8Chars as $utfChar) {
1097
                                    // Find number and evaluate position:
1098
                                    $uNumber = (int)$this->csConvObj->utf8CharToUnumber($utfChar);
1099
                                    $inRange = 0;
1100
                                    foreach ($ranges as $rangeDef) {
1101
                                        if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
1102
                                            $inRange = 1;
1103
                                            break;
1104
                                        }
1105
                                    }
1106
                                    if ($currentState == -1) {
1107
                                        $currentState = $inRange;
1108
                                    }
1109
                                    // Initialize first char
1110
                                    // Switch bank:
1111
                                    if ($inRange != $currentState && $uNumber !== 9 && $uNumber !== 10 && $uNumber !== 13 && $uNumber !== 32) {
1112
                                        // Set result:
1113
                                        if ($bankAccum !== '') {
1114
                                            $newResult[] = [
1115
                                                'str' => $bankAccum,
1116
                                                'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1117
                                                'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1118
                                                'color' => $currentState ? $cfg['color'] : '',
1119
                                                'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1120
                                                'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1121
                                                'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1122
                                                'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1123
                                            ];
1124
                                        }
1125
                                        // Initialize new settings:
1126
                                        $currentState = $inRange;
1127
                                        $bankAccum = '';
1128
                                    }
1129
                                    // Add char to bank:
1130
                                    $bankAccum .= $utfChar;
1131
                                }
1132
                                // Set result for FINAL part:
1133
                                if ($bankAccum !== '') {
1134
                                    $newResult[] = [
1135
                                        'str' => $bankAccum,
1136
                                        'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1137
                                        'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1138
                                        'color' => $currentState ? $cfg['color'] : '',
1139
                                        'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1140
                                        'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1141
                                        'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1142
                                        'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1143
                                    ];
1144
                                }
1145
                            }
1146
                            // Set the new result as result array:
1147
                            if (!empty($newResult)) {
1148
                                $result = $newResult;
1149
                            }
1150
                        }
1151
                        break;
1152
                }
1153
            }
1154
        }
1155
        return $result;
1156
    }
1157
1158
    /**
1159
     * Calculates the spacing and wordSpacing values
1160
     *
1161
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1162
     * @param int $scaleFactor TypoScript value from eg $conf['niceText.']['scaleFactor']
1163
     * @return array Array with two keys [0]/[1] being array($spacing,$wordSpacing)
1164
     * @internal
1165
     * @see calcBBox()
1166
     */
1167
    public function calcWordSpacing($conf, $scaleFactor = 1)
1168
    {
1169
        $spacing = (int)$conf['spacing'];
1170
        $wordSpacing = (int)$conf['wordSpacing'];
1171
        $wordSpacing = $wordSpacing ?: $spacing * 2;
1172
        $spacing *= $scaleFactor;
1173
        $wordSpacing *= $scaleFactor;
1174
        return [$spacing, $wordSpacing];
1175
    }
1176
1177
    /**
1178
     * Calculates and returns the niceText.scaleFactor
1179
     *
1180
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1181
     * @return int TypoScript value from eg $conf['niceText.']['scaleFactor']
1182
     * @internal
1183
     */
1184
    public function getTextScalFactor($conf)
1185
    {
1186
        if (!$conf['niceText']) {
1187
            $sF = 1;
1188
        } else {
1189
            // NICETEXT::
1190
            $sF = MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
1191
        }
1192
        return $sF;
1193
    }
1194
1195
    /**
1196
     * @param array $imageFileExt
1197
     * @internal Only used for ext:install, not part of TYPO3 Core API.
1198
     */
1199
    public function setImageFileExt(array $imageFileExt): void
1200
    {
1201
        $this->imageFileExt = $imageFileExt;
1202
    }
1203
1204
    /**
1205
     * Renders a regular text and takes care of a possible line break automatically.
1206
     *
1207
     * @param resource $im (See argument for PHP function imageTTFtext())
1208
     * @param int $fontSize (See argument for PHP function imageTTFtext())
1209
     * @param int $angle (See argument for PHP function imageTTFtext())
1210
     * @param int $x (See argument for PHP function imageTTFtext())
1211
     * @param int $y (See argument for PHP function imageTTFtext())
1212
     * @param int $color (See argument for PHP function imageTTFtext())
1213
     * @param string $fontFile (See argument for PHP function imageTTFtext())
1214
     * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
1215
     * @param array $splitRendering Split-rendering configuration
1216
     * @param array $conf The configuration
1217
     * @param int $sF Scale factor
1218
     */
1219
    protected function renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF = 1)
1220
    {
1221
        if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
1222
            $phrase = '';
1223
            $currentWidth = 0;
1224
            $breakWidth = $conf['breakWidth'];
1225
            $breakSpace = $this->getBreakSpace($conf);
1226
            $wordPairs = $this->getWordPairsForLineBreak($string);
1227
            // Iterate through all word pairs:
1228
            foreach ($wordPairs as $index => $wordPair) {
1229
                $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
1230
                if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
1231
                    $currentWidth += $wordWidth;
1232
                    $phrase .= $wordPair;
1233
                } else {
1234
                    // Render the current phrase that is below breakWidth:
1235
                    $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1236
                    // Calculate the news height offset:
1237
                    $y += $breakSpace;
1238
                    // Restart the phrase:
1239
                    $currentWidth = $wordWidth;
1240
                    $phrase = $wordPair;
1241
                }
1242
            }
1243
            // Render the remaining phrase:
1244
            if ($currentWidth) {
1245
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1246
            }
1247
        } else {
1248
            $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
1249
        }
1250
    }
1251
1252
    /**
1253
     * Gets the word pairs used for automatic line breaks.
1254
     *
1255
     * @param string $string
1256
     * @return array
1257
     */
1258
    protected function getWordPairsForLineBreak($string)
1259
    {
1260
        $wordPairs = [];
1261
        $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
1262
        $wordsArray = is_array($wordsArray) ? $wordsArray : [];
1263
        $wordsCount = count($wordsArray);
1264
        for ($index = 0; $index < $wordsCount; $index += 2) {
1265
            $wordPairs[] = $wordsArray[$index] . $wordsArray[$index + 1];
1266
        }
1267
        return $wordPairs;
1268
    }
1269
1270
    /**
1271
     * Gets the rendered text width
1272
     *
1273
     * @param string $text
1274
     * @param array $conf
1275
     * @return int
1276
     */
1277
    protected function getRenderedTextWidth($text, $conf)
1278
    {
1279
        $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $text, $conf['splitRendering.']);
1280
        if ($conf['angle'] < 0) {
1281
            $pixelWidth = abs($bounds[4] - $bounds[0]);
1282
        } elseif ($conf['angle'] > 0) {
1283
            $pixelWidth = abs($bounds[2] - $bounds[6]);
1284
        } else {
1285
            $pixelWidth = abs($bounds[4] - $bounds[6]);
1286
        }
1287
        return $pixelWidth;
1288
    }
1289
1290
    /**
1291
     * Gets the break space for each new line.
1292
     *
1293
     * @param array $conf TypoScript configuration for the currently rendered object
1294
     * @param array $boundingBox The bounding box the the currently rendered object
1295
     * @return int The break space
1296
     */
1297
    protected function getBreakSpace($conf, array $boundingBox = null)
1298
    {
1299
        if (!isset($boundingBox)) {
1300
            $boundingBox = $this->calcBBox($conf);
1301
            $boundingBox = $boundingBox[2];
1302
        }
1303
        if (isset($conf['breakSpace']) && $conf['breakSpace']) {
1304
            $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
1305
        } else {
1306
            $breakSpace = $boundingBox['lineHeight'];
1307
        }
1308
        return $breakSpace;
1309
    }
1310
1311
    /*********************************************
1312
     *
1313
     * Other GIFBUILDER objects related to TEXT
1314
     *
1315
     *********************************************/
1316
    /**
1317
     * Implements the "OUTLINE" GIFBUILDER object / property for the TEXT object
1318
     *
1319
     * @param resource $im GDlib image pointer
1320
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1321
     * @param array $workArea The current working area coordinates.
1322
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1323
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1324
     * @see makeText()
1325
     */
1326
    public function makeOutline(&$im, $conf, $workArea, $txtConf)
1327
    {
1328
        $thickness = (int)$conf['thickness'];
1329
        if ($thickness) {
1330
            $txtConf['fontColor'] = $conf['color'];
1331
            $outLineDist = MathUtility::forceIntegerInRange($thickness, 1, 2);
1332
            for ($b = 1; $b <= $outLineDist; $b++) {
1333
                if ($b == 1) {
1334
                    $it = 8;
1335
                } else {
1336
                    $it = 16;
1337
                }
1338
                $outL = $this->circleOffset($b, $it);
1339
                for ($a = 0; $a < $it; $a++) {
1340
                    $this->makeText($im, $txtConf, $this->applyOffset($workArea, $outL[$a]));
1341
                }
1342
            }
1343
        }
1344
    }
1345
1346
    /**
1347
     * Creates some offset values in an array used to simulate a circularly applied outline around TEXT
1348
     *
1349
     * access private
1350
     *
1351
     * @param int $distance Distance
1352
     * @param int $iterations Iterations.
1353
     * @return array
1354
     * @see makeOutline()
1355
     */
1356
    public function circleOffset($distance, $iterations)
1357
    {
1358
        $res = [];
1359
        if ($distance && $iterations) {
1360
            for ($a = 0; $a < $iterations; $a++) {
1361
                $yOff = round(sin(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1362
                if ($yOff) {
1363
                    $yOff = (int)(ceil(abs($yOff / 100)) * ($yOff / abs($yOff)));
1364
                }
1365
                $xOff = round(cos(2 * M_PI / $iterations * ($a + 1)) * 100 * $distance);
1366
                if ($xOff) {
1367
                    $xOff = (int)(ceil(abs($xOff / 100)) * ($xOff / abs($xOff)));
1368
                }
1369
                $res[$a] = [$xOff, $yOff];
1370
            }
1371
        }
1372
        return $res;
1373
    }
1374
1375
    /**
1376
     * Implements the "EMBOSS" GIFBUILDER object / property for the TEXT object
1377
     *
1378
     * @param resource $im GDlib image pointer
1379
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1380
     * @param array $workArea The current working area coordinates.
1381
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1382
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1383
     * @see makeShadow()
1384
     */
1385
    public function makeEmboss(&$im, $conf, $workArea, $txtConf)
1386
    {
1387
        $conf['color'] = $conf['highColor'];
1388
        $this->makeShadow($im, $conf, $workArea, $txtConf);
1389
        $newOffset = GeneralUtility::intExplode(',', $conf['offset']);
1390
        $newOffset[0] *= -1;
1391
        $newOffset[1] *= -1;
1392
        $conf['offset'] = implode(',', $newOffset);
1393
        $conf['color'] = $conf['lowColor'];
1394
        $this->makeShadow($im, $conf, $workArea, $txtConf);
1395
    }
1396
1397
    /**
1398
     * Implements the "SHADOW" GIFBUILDER object / property for the TEXT object
1399
     * The operation involves ImageMagick for combining.
1400
     *
1401
     * @param resource $im GDlib image pointer
1402
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1403
     * @param array $workArea The current working area coordinates.
1404
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1405
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1406
     * @see makeText()
1407
     * @see makeEmboss()
1408
     */
1409
    public function makeShadow(&$im, $conf, $workArea, $txtConf)
1410
    {
1411
        $workArea = $this->applyOffset($workArea, GeneralUtility::intExplode(',', $conf['offset']));
1412
        $blurRate = MathUtility::forceIntegerInRange((int)$conf['blur'], 0, 99);
1413
        // No effects if ImageMagick ver. 5+
1414
        if (!$blurRate || !$this->processorEffectsEnabled) {
1415
            $txtConf['fontColor'] = $conf['color'];
1416
            $this->makeText($im, $txtConf, $workArea);
1417
        } else {
1418
            $w = imagesx($im);
1419
            $h = imagesy($im);
1420
            // Area around the blur used for cropping something
1421
            $blurBorder = 3;
1422
            $tmpStr = $this->randomName();
1423
            $fileMenu = $tmpStr . '_menu.' . $this->gifExtension;
1424
            $fileColor = $tmpStr . '_color.' . $this->gifExtension;
1425
            $fileMask = $tmpStr . '_mask.' . $this->gifExtension;
1426
            // BlurColor Image laves
1427
            $blurColImg = imagecreatetruecolor($w, $h);
1428
            $bcols = $this->convertColor($conf['color']);
1429
            $Bcolor = imagecolorallocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
0 ignored issues
show
Bug introduced by
It seems like $blurColImg can also be of type false; however, parameter $image of imagecolorallocate() 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

1429
            $Bcolor = imagecolorallocate(/** @scrutinizer ignore-type */ $blurColImg, $bcols[0], $bcols[1], $bcols[2]);
Loading history...
1430
            imagefilledrectangle($blurColImg, 0, 0, $w, $h, $Bcolor);
0 ignored issues
show
Bug introduced by
It seems like $blurColImg can also be of type false; however, parameter $image of imagefilledrectangle() 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

1430
            imagefilledrectangle(/** @scrutinizer ignore-type */ $blurColImg, 0, 0, $w, $h, $Bcolor);
Loading history...
1431
            $this->ImageWrite($blurColImg, $fileColor);
0 ignored issues
show
Bug introduced by
It seems like $blurColImg can also be of type false; 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

1431
            $this->ImageWrite(/** @scrutinizer ignore-type */ $blurColImg, $fileColor);
Loading history...
1432
            imagedestroy($blurColImg);
0 ignored issues
show
Bug introduced by
It seems like $blurColImg can also be of type false; however, parameter $image of imagedestroy() 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
            imagedestroy(/** @scrutinizer ignore-type */ $blurColImg);
Loading history...
1433
            // The mask is made: BlurTextImage
1434
            $blurTextImg = imagecreatetruecolor($w + $blurBorder * 2, $h + $blurBorder * 2);
1435
            // Black background
1436
            $Bcolor = imagecolorallocate($blurTextImg, 0, 0, 0);
1437
            imagefilledrectangle($blurTextImg, 0, 0, $w + $blurBorder * 2, $h + $blurBorder * 2, $Bcolor);
1438
            $txtConf['fontColor'] = 'white';
1439
            $blurBordArr = [$blurBorder, $blurBorder];
1440
            $this->makeText($blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
0 ignored issues
show
Bug introduced by
It seems like $blurTextImg can also be of type false; 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

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

1707
        $Bcolor = imagecolorallocate(/** @scrutinizer ignore-type */ $newIm, $cols[0], $cols[1], $cols[2]);
Loading history...
1708
        imagefilledrectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
0 ignored issues
show
Bug introduced by
It seems like $newIm can also be of type false; however, parameter $image of imagefilledrectangle() 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

1708
        imagefilledrectangle(/** @scrutinizer ignore-type */ $newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
Loading history...
1709
        $newConf = [];
1710
        $workArea = [0, 0, $cords[2], $cords[3]];
1711
        if ($cords[0] < 0) {
1712
            $workArea[0] = abs($cords[0]);
1713
        } else {
1714
            $newConf['offset'] = -$cords[0];
1715
        }
1716
        if ($cords[1] < 0) {
1717
            $workArea[1] = abs($cords[1]);
1718
        } else {
1719
            $newConf['offset'] .= ',' . -$cords[1];
1720
        }
1721
        $this->copyGifOntoGif($newIm, $im, $newConf, $workArea);
0 ignored issues
show
Bug introduced by
It seems like $newIm can also be of type false; 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

1721
        $this->copyGifOntoGif(/** @scrutinizer ignore-type */ $newIm, $im, $newConf, $workArea);
Loading history...
1722
        $im = $newIm;
1723
        $this->w = imagesx($im);
1724
        $this->h = imagesy($im);
1725
        // Clears workArea to total image
1726
        $this->setWorkArea('');
1727
    }
1728
1729
    /**
1730
     * Implements the "SCALE" GIFBUILDER object
1731
     *
1732
     * @param resource $im GDlib image pointer
1733
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1734
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1735
     */
1736
    public function scale(&$im, $conf)
1737
    {
1738
        if ($conf['width'] || $conf['height'] || $conf['params']) {
1739
            $tmpStr = $this->randomName();
1740
            $theFile = $tmpStr . '.' . $this->gifExtension;
1741
            $this->ImageWrite($im, $theFile);
1742
            $theNewFile = $this->imageMagickConvert($theFile, $this->gifExtension, $conf['width'], $conf['height'], $conf['params']);
1743
            $tmpImg = $this->imageCreateFromFile($theNewFile[3]);
1744
            if ($tmpImg) {
0 ignored issues
show
introduced by
$tmpImg is of type resource, thus it always evaluated to false.
Loading history...
1745
                imagedestroy($im);
1746
                $im = $tmpImg;
1747
                $this->w = imagesx($im);
1748
                $this->h = imagesy($im);
1749
                // Clears workArea to total image
1750
                $this->setWorkArea('');
1751
            }
1752
            unlink($theFile);
1753
            if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1754
                unlink($theNewFile[3]);
1755
            }
1756
        }
1757
    }
1758
1759
    /**
1760
     * Implements the "WORKAREA" GIFBUILDER object when setting it
1761
     * Setting internal working area boundaries (->workArea)
1762
     *
1763
     * @param string $workArea Working area dimensions, comma separated
1764
     * @internal
1765
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1766
     */
1767
    public function setWorkArea($workArea)
1768
    {
1769
        $this->workArea = GeneralUtility::intExplode(',', $workArea);
1770
        $this->workArea = $this->applyOffset($this->workArea, $this->OFFSET);
1771
        if (!$this->workArea[2]) {
1772
            $this->workArea[2] = $this->w;
1773
        }
1774
        if (!$this->workArea[3]) {
1775
            $this->workArea[3] = $this->h;
1776
        }
1777
    }
1778
1779
    /*************************
1780
     *
1781
     * Adjustment functions
1782
     *
1783
     ************************/
1784
    /**
1785
     * Apply auto-levels to input image pointer
1786
     *
1787
     * @param resource $im GDlib Image Pointer
1788
     */
1789
    public function autolevels(&$im)
1790
    {
1791
        $totalCols = imagecolorstotal($im);
1792
        $grayArr = [];
1793
        for ($c = 0; $c < $totalCols; $c++) {
1794
            $cols = imagecolorsforindex($im, $c);
1795
            $grayArr[] = round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
1796
        }
1797
        $min = min($grayArr);
1798
        $max = max($grayArr);
1799
        $delta = $max - $min;
1800
        if ($delta) {
1801
            for ($c = 0; $c < $totalCols; $c++) {
1802
                $cols = imagecolorsforindex($im, $c);
1803
                $cols['red'] = floor(($cols['red'] - $min) / $delta * 255);
1804
                $cols['green'] = floor(($cols['green'] - $min) / $delta * 255);
1805
                $cols['blue'] = floor(($cols['blue'] - $min) / $delta * 255);
1806
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1807
            }
1808
        }
1809
    }
1810
1811
    /**
1812
     * Apply output levels to input image pointer (decreasing contrast)
1813
     *
1814
     * @param resource $im GDlib Image Pointer
1815
     * @param int $low The "low" value (close to 0)
1816
     * @param int $high The "high" value (close to 255)
1817
     * @param bool $swap If swap, then low and high are swapped. (Useful for negated masks...)
1818
     */
1819
    public function outputLevels(&$im, $low, $high, $swap = false)
1820
    {
1821
        if ($low < $high) {
1822
            $low = MathUtility::forceIntegerInRange($low, 0, 255);
1823
            $high = MathUtility::forceIntegerInRange($high, 0, 255);
1824
            if ($swap) {
1825
                $temp = $low;
1826
                $low = 255 - $high;
1827
                $high = 255 - $temp;
1828
            }
1829
            $delta = $high - $low;
1830
            $totalCols = imagecolorstotal($im);
1831
            for ($c = 0; $c < $totalCols; $c++) {
1832
                $cols = imagecolorsforindex($im, $c);
1833
                $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
1834
                $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
1835
                $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
1836
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1837
            }
1838
        }
1839
    }
1840
1841
    /**
1842
     * Apply input levels to input image pointer (increasing contrast)
1843
     *
1844
     * @param resource $im GDlib Image Pointer
1845
     * @param int $low The "low" value (close to 0)
1846
     * @param int $high The "high" value (close to 255)
1847
     */
1848
    public function inputLevels(&$im, $low, $high)
1849
    {
1850
        if ($low < $high) {
1851
            $low = MathUtility::forceIntegerInRange($low, 0, 255);
1852
            $high = MathUtility::forceIntegerInRange($high, 0, 255);
1853
            $delta = $high - $low;
1854
            $totalCols = imagecolorstotal($im);
1855
            for ($c = 0; $c < $totalCols; $c++) {
1856
                $cols = imagecolorsforindex($im, $c);
1857
                $cols['red'] = MathUtility::forceIntegerInRange((int)(($cols['red'] - $low) / $delta * 255), 0, 255);
1858
                $cols['green'] = MathUtility::forceIntegerInRange((int)(($cols['green'] - $low) / $delta * 255), 0, 255);
1859
                $cols['blue'] = MathUtility::forceIntegerInRange((int)(($cols['blue'] - $low) / $delta * 255), 0, 255);
1860
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1861
            }
1862
        }
1863
    }
1864
1865
    /**
1866
     * Reduce colors in image using IM and create a palette based image if possible (<=256 colors)
1867
     *
1868
     * @param string $file Image file to reduce
1869
     * @param int $cols Number of colors to reduce the image to.
1870
     * @return string Reduced file
1871
     */
1872
    public function IMreduceColors($file, $cols)
1873
    {
1874
        $fI = GeneralUtility::split_fileref($file);
1875
        $ext = strtolower($fI['fileext']);
1876
        $result = $this->randomName() . '.' . $ext;
1877
        $reduce = MathUtility::forceIntegerInRange($cols, 0, $ext === 'gif' ? 256 : $this->truecolorColors, 0);
1878
        if ($reduce > 0) {
1879
            $params = ' -colors ' . $reduce;
1880
            if ($reduce <= 256) {
1881
                $params .= ' -type Palette';
1882
            }
1883
            $prefix = $ext === 'png' && $reduce <= 256 ? 'png8:' : '';
1884
            $this->imageMagickExec($file, $prefix . $result, $params);
1885
            if ($result) {
1886
                return $result;
1887
            }
1888
        }
1889
        return '';
1890
    }
1891
1892
    /*********************************
1893
     *
1894
     * GIFBUILDER Helper functions
1895
     *
1896
     *********************************/
1897
    /**
1898
     * Returns the IM command for sharpening with ImageMagick 5
1899
     * Uses $this->im5fx_sharpenSteps for translation of the factor to an actual command.
1900
     *
1901
     * @param int $factor The sharpening factor, 0-100 (effectively in 10 steps)
1902
     * @return string The sharpening command, eg. " -sharpen 3x4
1903
     * @see makeText()
1904
     * @see IMparams()
1905
     * @see v5_blur()
1906
     */
1907
    public function v5_sharpen($factor)
1908
    {
1909
        $factor = MathUtility::forceIntegerInRange((int)ceil($factor / 10), 0, 10);
1910
        $sharpenArr = explode(',', ',' . $this->im5fx_sharpenSteps);
1911
        $sharpenF = trim($sharpenArr[$factor]);
1912
        if ($sharpenF) {
1913
            return ' -sharpen ' . $sharpenF;
1914
        }
1915
        return '';
1916
    }
1917
1918
    /**
1919
     * Returns the IM command for blurring with ImageMagick 5.
1920
     * Uses $this->im5fx_blurSteps for translation of the factor to an actual command.
1921
     *
1922
     * @param int $factor The blurring factor, 0-100 (effectively in 10 steps)
1923
     * @return string The blurring command, eg. " -blur 3x4
1924
     * @see makeText()
1925
     * @see IMparams()
1926
     * @see v5_sharpen()
1927
     */
1928
    public function v5_blur($factor)
1929
    {
1930
        $factor = MathUtility::forceIntegerInRange((int)ceil($factor / 10), 0, 10);
1931
        $blurArr = explode(',', ',' . $this->im5fx_blurSteps);
1932
        $blurF = trim($blurArr[$factor]);
1933
        if ($blurF) {
1934
            return ' -blur ' . $blurF;
1935
        }
1936
        return '';
1937
    }
1938
1939
    /**
1940
     * Returns a random filename prefixed with "temp_" and then 32 char md5 hash (without extension).
1941
     * 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.
1942
     *
1943
     * @return string
1944
     */
1945
    public function randomName()
1946
    {
1947
        GeneralUtility::mkdir_deep(Environment::getVarPath() . '/transient/');
1948
        return Environment::getVarPath() . '/transient/' . md5(StringUtility::getUniqueId());
1949
    }
1950
1951
    /**
1952
     * Applies offset value to coordinated in $cords.
1953
     * Basically the value of key 0/1 of $OFFSET is added to keys 0/1 of $cords
1954
     *
1955
     * @param array $cords Integer coordinates in key 0/1
1956
     * @param array $OFFSET Offset values in key 0/1
1957
     * @return array Modified $cords array
1958
     */
1959
    public function applyOffset($cords, $OFFSET)
1960
    {
1961
        $cords[0] = (int)$cords[0] + (int)$OFFSET[0];
1962
        $cords[1] = (int)$cords[1] + (int)$OFFSET[1];
1963
        return $cords;
1964
    }
1965
1966
    /**
1967
     * Converts a "HTML-color" TypoScript datatype to RGB-values.
1968
     * Default is 0,0,0
1969
     *
1970
     * @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
1971
     * @return array RGB values in key 0/1/2 of the array
1972
     */
1973
    public function convertColor($string)
1974
    {
1975
        $col = [];
1976
        $cParts = explode(':', $string, 2);
1977
        // Finding the RGB definitions of the color:
1978
        $string = $cParts[0];
1979
        if (strpos($string, '#') !== false) {
1980
            $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string) ?? '';
1981
            $col[] = hexdec(substr($string, 0, 2));
1982
            $col[] = hexdec(substr($string, 2, 2));
1983
            $col[] = hexdec(substr($string, 4, 2));
1984
        } elseif (strpos($string, ',') !== false) {
1985
            $string = preg_replace('/[^,0-9]*/', '', $string) ?? '';
1986
            $strArr = explode(',', $string);
1987
            $col[] = (int)$strArr[0];
1988
            $col[] = (int)$strArr[1];
1989
            $col[] = (int)$strArr[2];
1990
        } else {
1991
            $string = strtolower(trim($string));
1992
            if ($this->colMap[$string]) {
1993
                $col = $this->colMap[$string];
1994
            } else {
1995
                $col = [0, 0, 0];
1996
            }
1997
        }
1998
        // ... and possibly recalculating the value
1999
        if (trim($cParts[1])) {
2000
            $cParts[1] = trim($cParts[1]);
2001
            if ($cParts[1][0] === '*') {
2002
                $val = (float)substr($cParts[1], 1);
2003
                $col[0] = MathUtility::forceIntegerInRange((int)($col[0] * $val), 0, 255);
2004
                $col[1] = MathUtility::forceIntegerInRange((int)($col[1] * $val), 0, 255);
2005
                $col[2] = MathUtility::forceIntegerInRange((int)($col[2] * $val), 0, 255);
2006
            } else {
2007
                $val = (int)$cParts[1];
2008
                $col[0] = MathUtility::forceIntegerInRange((int)($col[0] + $val), 0, 255);
2009
                $col[1] = MathUtility::forceIntegerInRange((int)($col[1] + $val), 0, 255);
2010
                $col[2] = MathUtility::forceIntegerInRange((int)($col[2] + $val), 0, 255);
2011
            }
2012
        }
2013
        return $col;
2014
    }
2015
2016
    /**
2017
     * 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
2018
     *
2019
     * @param array $conf TypoScript configuration for a GIFBUILDER object
2020
     * @param array $workArea Workarea definition
2021
     * @param array $BB BB (Bounding box) array. Not just used for TEXT objects but also for others
2022
     * @return array [0]=x, [1]=y, [2]=w, [3]=h
2023
     * @internal
2024
     * @see copyGifOntoGif()
2025
     * @see makeBox()
2026
     * @see crop()
2027
     */
2028
    public function objPosition($conf, $workArea, $BB)
2029
    {
2030
        // offset, align, valign, workarea
2031
        $result = [];
2032
        $result[2] = $BB[0];
2033
        $result[3] = $BB[1];
2034
        $w = $workArea[2];
2035
        $h = $workArea[3];
2036
        $align = explode(',', $conf['align']);
2037
        $align[0] = strtolower(substr(trim($align[0]), 0, 1));
2038
        $align[1] = strtolower(substr(trim($align[1]), 0, 1));
2039
        switch ($align[0]) {
2040
            case 'r':
2041
                $result[0] = $w - $result[2];
2042
                break;
2043
            case 'c':
2044
                $result[0] = round(($w - $result[2]) / 2);
2045
                break;
2046
            default:
2047
                $result[0] = 0;
2048
        }
2049
        switch ($align[1]) {
2050
            case 'b':
2051
                // y pos
2052
                $result[1] = $h - $result[3];
2053
                break;
2054
            case 'c':
2055
                $result[1] = round(($h - $result[3]) / 2);
2056
                break;
2057
            default:
2058
                $result[1] = 0;
2059
        }
2060
        $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
2061
        $result = $this->applyOffset($result, $workArea);
2062
        return $result;
2063
    }
2064
2065
    /***********************************
2066
     *
2067
     * Scaling, Dimensions of images
2068
     *
2069
     ***********************************/
2070
    /**
2071
     * Converts $imagefile to another file in temp-dir of type $newExt (extension).
2072
     *
2073
     * @param string $imagefile The image filepath
2074
     * @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.
2075
     * @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
2076
     * @param string $h Height. See $w
2077
     * @param string $params Additional ImageMagick parameters.
2078
     * @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...
2079
     * @param array $options An array with options passed to getImageScale (see this function).
2080
     * @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.
2081
     * @return array|null [0]/[1] is w/h, [2] is file extension and [3] is the filename.
2082
     * @see getImageScale()
2083
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
2084
     * @see maskImageOntoImage()
2085
     * @see copyImageOntoImage()
2086
     * @see scale()
2087
     */
2088
    public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $params = '', $frame = '', $options = [], $mustCreate = false)
2089
    {
2090
        if (!$this->processorEnabled) {
2091
            // Returning file info right away
2092
            return $this->getImageDimensions($imagefile);
2093
        }
2094
        $info = $this->getImageDimensions($imagefile);
2095
        if (!$info) {
2096
            return null;
2097
        }
2098
2099
        $newExt = strtolower(trim($newExt));
2100
        // If no extension is given the original extension is used
2101
        if (!$newExt) {
2102
            $newExt = $info[2];
2103
        }
2104
        if ($newExt === 'web') {
2105
            if (in_array($info[2], $this->webImageExt, true)) {
2106
                $newExt = $info[2];
2107
            } else {
2108
                $newExt = $this->gif_or_jpg($info[2], $info[0], $info[1]);
2109
                if (!$params) {
2110
                    $params = $this->cmds[$newExt];
2111
                }
2112
            }
2113
        }
2114
        if (!in_array($newExt, $this->imageFileExt, true)) {
2115
            return null;
2116
        }
2117
2118
        $data = $this->getImageScale($info, $w, $h, $options);
2119
        $w = $data['origW'];
2120
        $h = $data['origH'];
2121
        // If no conversion should be performed
2122
        // this flag is TRUE if the width / height does NOT dictate
2123
        // the image to be scaled!! (that is if no width / height is
2124
        // given or if the destination w/h matches the original image
2125
        // dimensions or if the option to not scale the image is set)
2126
        $noScale = !$w && !$h || $data[0] == $info[0] && $data[1] == $info[1] || !empty($options['noScale']);
2127
        if ($noScale && !$data['crs'] && !$params && !$frame && $newExt == $info[2] && !$mustCreate) {
2128
            // Set the new width and height before returning,
2129
            // if the noScale option is set
2130
            if (!empty($options['noScale'])) {
2131
                $info[0] = $data[0];
2132
                $info[1] = $data[1];
2133
            }
2134
            $info[3] = $imagefile;
2135
            return $info;
2136
        }
2137
        $info[0] = $data[0];
2138
        $info[1] = $data[1];
2139
        $frame = $this->addFrameSelection ? (int)$frame : 0;
2140
        if (!$params) {
2141
            $params = $this->cmds[$newExt];
2142
        }
2143
        // Cropscaling:
2144
        if ($data['crs']) {
2145
            if (!$data['origW']) {
2146
                $data['origW'] = $data[0];
2147
            }
2148
            if (!$data['origH']) {
2149
                $data['origH'] = $data[1];
2150
            }
2151
            $offsetX = (int)(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
2152
            $offsetY = (int)(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
2153
            $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . '! +repage';
2154
        }
2155
        $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
2156
        // re-apply colorspace-setting for the resulting image so colors don't appear to dark (sRGB instead of RGB)
2157
        $command .= ' -colorspace ' . $this->colorspace;
2158
        $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
2159
        if ($this->alternativeOutputKey) {
2160
            $theOutputName = GeneralUtility::shortMD5($command . $cropscale . PathUtility::basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
2161
        } else {
2162
            $theOutputName = GeneralUtility::shortMD5($command . $cropscale . $imagefile . filemtime($imagefile) . '[' . $frame . ']');
2163
        }
2164
        if ($this->imageMagickConvert_forceFileNameBody) {
2165
            $theOutputName = $this->imageMagickConvert_forceFileNameBody;
2166
            $this->imageMagickConvert_forceFileNameBody = '';
2167
        }
2168
        // Making the temporary filename
2169
        GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
2170
        $output = Environment::getPublicPath() . '/typo3temp/assets/images/' . $this->filenamePrefix . $theOutputName . '.' . $newExt;
2171
        if ($this->dontCheckForExistingTempFile || !file_exists($output)) {
2172
            $this->imageMagickExec($imagefile, $output, $command, $frame);
2173
        }
2174
        if (file_exists($output)) {
2175
            $info[3] = $output;
2176
            $info[2] = $newExt;
2177
            // params might change some image data!
2178
            if ($params) {
2179
                $info = $this->getImageDimensions($info[3]);
2180
            }
2181
            if ($info[2] == $this->gifExtension && !$this->dontCompress) {
2182
                // Compress with IM (lzw) or GD (rle)  (Workaround for the absence of lzw-compression in GD)
2183
                self::gifCompress($info[3], '');
2184
            }
2185
            return $info;
2186
        }
2187
        return null;
2188
    }
2189
2190
    /**
2191
     * Gets the input image dimensions.
2192
     *
2193
     * @param string $imageFile The image filepath
2194
     * @return array|null Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2195
     * @see imageMagickConvert()
2196
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
2197
     */
2198
    public function getImageDimensions($imageFile)
2199
    {
2200
        $returnArr = null;
2201
        preg_match('/([^\\.]*)$/', $imageFile, $reg);
2202
        if (file_exists($imageFile) && in_array(strtolower($reg[0]), $this->imageFileExt, true)) {
2203
            $returnArr = $this->getCachedImageDimensions($imageFile);
2204
            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...
2205
                $imageInfoObject = GeneralUtility::makeInstance(ImageInfo::class, $imageFile);
2206
                if ($imageInfoObject->getWidth()) {
2207
                    $returnArr = [
2208
                        $imageInfoObject->getWidth(),
2209
                        $imageInfoObject->getHeight(),
2210
                        strtolower($reg[0]),
2211
                        $imageFile
2212
                    ];
2213
                    $this->cacheImageDimensions($returnArr);
2214
                }
2215
            }
2216
        }
2217
        return $returnArr;
2218
    }
2219
2220
    /**
2221
     * Caches the result of the getImageDimensions function into the database. Does not check if the file exists.
2222
     *
2223
     * @param array $identifyResult Result of the getImageDimensions function
2224
     *
2225
     * @return bool always TRUE
2226
     */
2227
    public function cacheImageDimensions(array $identifyResult)
2228
    {
2229
        $filePath = $identifyResult[3];
2230
        $statusHash = $this->generateStatusHashForImageFile($filePath);
2231
        $identifier = $this->generateCacheKeyForImageFile($filePath);
2232
2233
        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
2234
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('imagesizes');
2235
        $imageDimensions = [
2236
            'hash'        => $statusHash,
2237
            'imagewidth'  => $identifyResult[0],
2238
            'imageheight' => $identifyResult[1],
2239
        ];
2240
        $cache->set($identifier, $imageDimensions);
2241
2242
        return true;
2243
    }
2244
2245
    /**
2246
     * Fetches the cached image dimensions from the cache. Does not check if the image file exists.
2247
     *
2248
     * @param string $filePath Image file path, relative to public web path
2249
     *
2250
     * @return array|bool an array where [0]/[1] is w/h, [2] is extension and [3] is the file name,
2251
     *                    or FALSE for a cache miss
2252
     */
2253
    public function getCachedImageDimensions($filePath)
2254
    {
2255
        $statusHash = $this->generateStatusHashForImageFile($filePath);
2256
        $identifier = $this->generateCacheKeyForImageFile($filePath);
2257
        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
2258
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('imagesizes');
2259
        $cachedImageDimensions = $cache->get($identifier);
2260
        if (!isset($cachedImageDimensions['hash'])) {
2261
            return false;
2262
        }
2263
2264
        if ($cachedImageDimensions['hash'] !== $statusHash) {
2265
            // The file has changed. Delete the cache entry.
2266
            $cache->remove($identifier);
2267
            $result = false;
2268
        } else {
2269
            preg_match('/([^\\.]*)$/', $filePath, $imageExtension);
2270
            $result = [
2271
                (int)$cachedImageDimensions['imagewidth'],
2272
                (int)$cachedImageDimensions['imageheight'],
2273
                strtolower($imageExtension[0]),
2274
                $filePath
2275
            ];
2276
        }
2277
2278
        return $result;
2279
    }
2280
2281
    /**
2282
     * Creates the key for the image dimensions cache for an image file.
2283
     *
2284
     * This method does not check if the image file actually exists.
2285
     *
2286
     * @param string $filePath Image file path, relative to public web path
2287
     *
2288
     * @return string the hash key (an SHA1 hash), will not be empty
2289
     */
2290
    protected function generateCacheKeyForImageFile($filePath)
2291
    {
2292
        return sha1($filePath);
2293
    }
2294
2295
    /**
2296
     * Creates the status hash to check whether a file has been changed.
2297
     *
2298
     * @param string $filePath Image file path, relative to public web path
2299
     *
2300
     * @return string the status hash (an SHA1 hash)
2301
     */
2302
    protected function generateStatusHashForImageFile($filePath)
2303
    {
2304
        $fileStatus = stat($filePath);
2305
2306
        return sha1($fileStatus['mtime'] . $fileStatus['size']);
2307
    }
2308
2309
    /**
2310
     * Get numbers for scaling the image based on input
2311
     *
2312
     * @param array $info Current image information: Width, Height etc.
2313
     * @param string $w "required" width
2314
     * @param string $h "required" height
2315
     * @param array $options Options: Keys are like "maxW", "maxH", "minW", "minH
2316
     * @return array
2317
     * @internal
2318
     * @see imageMagickConvert()
2319
     */
2320
    public function getImageScale($info, $w, $h, $options)
2321
    {
2322
        $out = [];
2323
        $max = strpos($w . $h, 'm') !== false ? 1 : 0;
2324
        if (strpos($w . $h, 'c') !== false) {
2325
            $out['cropH'] = (int)substr((string)strstr($w, 'c'), 1);
2326
            $out['cropV'] = (int)substr((string)strstr($h, 'c'), 1);
2327
            $crs = true;
2328
        } else {
2329
            $crs = false;
2330
        }
2331
        $out['crs'] = $crs;
2332
        $w = (int)$w;
2333
        $h = (int)$h;
2334
        // If there are max-values...
2335
        if (!empty($options['maxW'])) {
2336
            // If width is given...
2337
            if ($w) {
2338
                if ($w > $options['maxW']) {
2339
                    $w = $options['maxW'];
2340
                    // Height should follow
2341
                    $max = 1;
2342
                }
2343
            } else {
2344
                if ($info[0] > $options['maxW']) {
2345
                    $w = $options['maxW'];
2346
                    // Height should follow
2347
                    $max = 1;
2348
                }
2349
            }
2350
        }
2351
        if (!empty($options['maxH'])) {
2352
            // If height is given...
2353
            if ($h) {
2354
                if ($h > $options['maxH']) {
2355
                    $h = $options['maxH'];
2356
                    // Height should follow
2357
                    $max = 1;
2358
                }
2359
            } else {
2360
                // Changed [0] to [1] 290801
2361
                if ($info[1] > $options['maxH']) {
2362
                    $h = $options['maxH'];
2363
                    // Height should follow
2364
                    $max = 1;
2365
                }
2366
            }
2367
        }
2368
        $out['origW'] = $w;
2369
        $out['origH'] = $h;
2370
        $out['max'] = $max;
2371
        if (!$this->mayScaleUp) {
2372
            if ($w > $info[0]) {
2373
                $w = $info[0];
2374
            }
2375
            if ($h > $info[1]) {
2376
                $h = $info[1];
2377
            }
2378
        }
2379
        // If scaling should be performed. Check that input "info" array will not cause division-by-zero
2380
        if (($w || $h) && $info[0] && $info[1]) {
2381
            if ($w && !$h) {
2382
                $info[1] = ceil($info[1] * ($w / $info[0]));
2383
                $info[0] = $w;
2384
            }
2385
            if (!$w && $h) {
2386
                $info[0] = ceil($info[0] * ($h / $info[1]));
2387
                $info[1] = $h;
2388
            }
2389
            if ($w && $h) {
2390
                if ($max) {
2391
                    $ratio = $info[0] / $info[1];
2392
                    if ($h * $ratio > $w) {
2393
                        $h = round($w / $ratio);
2394
                    } else {
2395
                        $w = round($h * $ratio);
2396
                    }
2397
                }
2398
                if ($crs) {
2399
                    $ratio = $info[0] / $info[1];
2400
                    if ($h * $ratio < $w) {
2401
                        $h = round($w / $ratio);
2402
                    } else {
2403
                        $w = round($h * $ratio);
2404
                    }
2405
                }
2406
                $info[0] = $w;
2407
                $info[1] = $h;
2408
            }
2409
        }
2410
        $out[0] = $info[0];
2411
        $out[1] = $info[1];
2412
        // Set minimum-measures!
2413
        if (isset($options['minW']) && $out[0] < $options['minW']) {
2414
            if (($max || $crs) && $out[0]) {
2415
                $out[1] = round($out[1] * $options['minW'] / $out[0]);
2416
            }
2417
            $out[0] = $options['minW'];
2418
        }
2419
        if (isset($options['minH']) && $out[1] < $options['minH']) {
2420
            if (($max || $crs) && $out[1]) {
2421
                $out[0] = round($out[0] * $options['minH'] / $out[1]);
2422
            }
2423
            $out[1] = $options['minH'];
2424
        }
2425
        return $out;
2426
    }
2427
2428
    /***********************************
2429
     *
2430
     * ImageMagick API functions
2431
     *
2432
     ***********************************/
2433
    /**
2434
     * Call the identify command
2435
     *
2436
     * @param string $imagefile The relative to public web path image filepath
2437
     * @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.
2438
     */
2439
    public function imageMagickIdentify($imagefile)
2440
    {
2441
        if (!$this->processorEnabled) {
2442
            return null;
2443
        }
2444
2445
        $result = $this->executeIdentifyCommandForImageFile($imagefile);
2446
        if ($result) {
2447
            [$width, $height, $fileExtension, $fileType] = explode(' ', $result);
2448
            if ((int)$width && (int)$height) {
2449
                return [$width, $height, strtolower($fileExtension), $imagefile, strtolower($fileType)];
2450
            }
2451
        }
2452
        return null;
2453
    }
2454
2455
    /**
2456
     * Internal function to execute an IM command fetching information on an image
2457
     *
2458
     * @param string $imageFile the absolute path to the image
2459
     * @return string|null the raw result of the identify command.
2460
     */
2461
    protected function executeIdentifyCommandForImageFile(string $imageFile): ?string
2462
    {
2463
        $frame = $this->addFrameSelection ? 0 : null;
2464
        $cmd = CommandUtility::imageMagickCommand(
2465
            'identify',
2466
            '-format "%w %h %e %m" ' . ImageMagickFile::fromFilePath($imageFile, $frame)
2467
        );
2468
        $returnVal = [];
2469
        CommandUtility::exec($cmd, $returnVal);
2470
        $result = array_pop($returnVal);
2471
        $this->IM_commands[] = ['identify', $cmd, $result];
2472
        return $result;
2473
    }
2474
2475
    /**
2476
     * Executes an ImageMagick "convert" on two filenames, $input and $output using $params before them.
2477
     * Can be used for many things, mostly scaling and effects.
2478
     *
2479
     * @param string $input The relative to public web path image filepath, input file (read from)
2480
     * @param string $output The relative to public web path image filepath, output filename (written to)
2481
     * @param string $params ImageMagick parameters
2482
     * @param int $frame Optional, refers to which frame-number to select in the image. '' or 0
2483
     * @return string The result of a call to PHP function "exec()
2484
     */
2485
    public function imageMagickExec($input, $output, $params, $frame = 0)
2486
    {
2487
        if (!$this->processorEnabled) {
2488
            return '';
2489
        }
2490
        // If addFrameSelection is set in the Install Tool, a frame number is added to
2491
        // select a specific page of the image (by default this will be the first page)
2492
        $frame = $this->addFrameSelection ? (int)$frame : null;
2493
        $cmd = CommandUtility::imageMagickCommand(
2494
            'convert',
2495
            $params
2496
                . ' ' . ImageMagickFile::fromFilePath($input, $frame)
2497
                . ' ' . CommandUtility::escapeShellArgument($output)
2498
        );
2499
        $this->IM_commands[] = [$output, $cmd];
2500
        $ret = CommandUtility::exec($cmd);
2501
        // Change the permissions of the file
2502
        GeneralUtility::fixPermissions($output);
2503
        return $ret;
2504
    }
2505
2506
    /**
2507
     * 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)
2508
     * Can be used for many things, mostly scaling and effects.
2509
     *
2510
     * @param string $input The relative to public web path image filepath, bottom file
2511
     * @param string $overlay The relative to public web path image filepath, overlay file (top)
2512
     * @param string $mask The relative to public web path image filepath, the mask file (grayscale)
2513
     * @param string $output The relative to public web path image filepath, output filename (written to)
2514
     * @return string
2515
     */
2516
    public function combineExec($input, $overlay, $mask, $output)
2517
    {
2518
        if (!$this->processorEnabled) {
2519
            return '';
2520
        }
2521
        $theMask = $this->randomName() . '.' . $this->gifExtension;
2522
        // +matte = no alpha layer in output
2523
        $this->imageMagickExec($mask, $theMask, '-colorspace GRAY +matte');
2524
2525
        $parameters = '-compose over'
2526
            . ' -quality ' . $this->jpegQuality
2527
            . ' +matte '
2528
            . ImageMagickFile::fromFilePath($input) . ' '
2529
            . ImageMagickFile::fromFilePath($overlay) . ' '
2530
            . ImageMagickFile::fromFilePath($theMask) . ' '
2531
            . CommandUtility::escapeShellArgument($output);
2532
        $cmd = CommandUtility::imageMagickCommand('combine', $parameters);
2533
        $this->IM_commands[] = [$output, $cmd];
2534
        $ret = CommandUtility::exec($cmd);
2535
        // Change the permissions of the file
2536
        GeneralUtility::fixPermissions($output);
2537
        if (is_file($theMask)) {
2538
            @unlink($theMask);
2539
        }
2540
        return $ret;
2541
    }
2542
2543
    /**
2544
     * Compressing a GIF file if not already LZW compressed.
2545
     * 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)
2546
     *
2547
     * The function takes a file-reference, $theFile, and saves it again through GD or ImageMagick in order to compress the file
2548
     * GIF:
2549
     * 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!)
2550
     * If $type is set to either 'IM' or 'GD' the compression is done with ImageMagick and GD respectively
2551
     * PNG:
2552
     * No changes.
2553
     *
2554
     * $theFile is expected to be a valid GIF-file!
2555
     * The function returns a code for the operation.
2556
     *
2557
     * @param string $theFile Filepath
2558
     * @param string $type See description of function
2559
     * @return string Returns "GD" if GD was used, otherwise "IM" if ImageMagick was used. If nothing done at all, it returns empty string.
2560
     */
2561
    public static function gifCompress($theFile, $type)
2562
    {
2563
        $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
2564
        if (!$gfxConf['gif_compress'] || strtolower(substr($theFile, -4, 4)) !== '.gif') {
2565
            return '';
2566
        }
2567
2568
        if (($type === 'IM' || !$type) && $gfxConf['processor_enabled'] && $gfxConf['processor_path_lzw']) {
2569
            // Use temporary file to prevent problems with read and write lock on same file on network file systems
2570
            $temporaryName = PathUtility::dirname($theFile) . '/' . md5(StringUtility::getUniqueId()) . '.gif';
2571
            // Rename could fail, if a simultaneous thread is currently working on the same thing
2572
            if (@rename($theFile, $temporaryName)) {
2573
                $cmd = CommandUtility::imageMagickCommand(
2574
                    'convert',
2575
                    ImageMagickFile::fromFilePath($temporaryName) . ' ' . CommandUtility::escapeShellArgument($theFile),
2576
                    $gfxConf['processor_path_lzw']
2577
                );
2578
                CommandUtility::exec($cmd);
2579
                unlink($temporaryName);
2580
            }
2581
            $returnCode = 'IM';
2582
            if (@is_file($theFile)) {
2583
                GeneralUtility::fixPermissions($theFile);
2584
            }
2585
        } elseif (($type === 'GD' || !$type) && $gfxConf['gdlib'] && !$gfxConf['gdlib_png']) {
2586
            $tempImage = imagecreatefromgif($theFile);
2587
            imagegif($tempImage, $theFile);
0 ignored issues
show
Bug introduced by
It seems like $tempImage can also be of type false; however, parameter $image of imagegif() 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

2587
            imagegif(/** @scrutinizer ignore-type */ $tempImage, $theFile);
Loading history...
2588
            imagedestroy($tempImage);
0 ignored issues
show
Bug introduced by
It seems like $tempImage can also be of type false; however, parameter $image of imagedestroy() 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

2588
            imagedestroy(/** @scrutinizer ignore-type */ $tempImage);
Loading history...
2589
            $returnCode = 'GD';
2590
            if (@is_file($theFile)) {
2591
                GeneralUtility::fixPermissions($theFile);
2592
            }
2593
        } else {
2594
            $returnCode = '';
2595
        }
2596
2597
        return $returnCode;
2598
    }
2599
2600
    /**
2601
     * Returns filename of the png/gif version of the input file (which can be png or gif).
2602
     * If input file type does not match the wanted output type a conversion is made and temp-filename returned.
2603
     *
2604
     * @param string $theFile Filepath of image file
2605
     * @param bool $output_png If TRUE, then input file is converted to PNG, otherwise to GIF
2606
     * @return string|null If the new image file exists, its filepath is returned
2607
     */
2608
    public static function readPngGif($theFile, $output_png = false)
2609
    {
2610
        if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] || !@is_file($theFile)) {
2611
            return null;
2612
        }
2613
2614
        $ext = strtolower(substr($theFile, -4, 4));
2615
        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...
2616
            return $theFile;
2617
        }
2618
2619
        if (!@is_dir(Environment::getPublicPath() . '/typo3temp/assets/images/')) {
2620
            GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
2621
        }
2622
        $newFile = Environment::getPublicPath() . '/typo3temp/assets/images/' . md5($theFile . '|' . filemtime($theFile)) . ($output_png ? '.png' : '.gif');
2623
        $cmd = CommandUtility::imageMagickCommand(
2624
            'convert',
2625
            ImageMagickFile::fromFilePath($theFile)
2626
                . ' ' . CommandUtility::escapeShellArgument($newFile),
2627
            $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path']
2628
        );
2629
        CommandUtility::exec($cmd);
2630
        if (@is_file($newFile)) {
2631
            GeneralUtility::fixPermissions($newFile);
2632
            return $newFile;
2633
        }
2634
        return null;
2635
    }
2636
2637
    /***********************************
2638
     *
2639
     * Various IO functions
2640
     *
2641
     ***********************************/
2642
2643
    /**
2644
     * 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.
2645
     *
2646
     * @param resource $im The image pointer (reference)
2647
     * @param string $command The ImageMagick parameters. Like effects, scaling etc.
2648
     */
2649
    public function applyImageMagickToPHPGif(&$im, $command)
2650
    {
2651
        $tmpStr = $this->randomName();
2652
        $theFile = $tmpStr . '.' . $this->gifExtension;
2653
        $this->ImageWrite($im, $theFile);
2654
        $this->imageMagickExec($theFile, $theFile, $command);
2655
        $tmpImg = $this->imageCreateFromFile($theFile);
2656
        if ($tmpImg) {
0 ignored issues
show
introduced by
$tmpImg is of type resource, thus it always evaluated to false.
Loading history...
2657
            imagedestroy($im);
2658
            $im = $tmpImg;
2659
            $this->w = imagesx($im);
2660
            $this->h = imagesy($im);
2661
        }
2662
        unlink($theFile);
2663
    }
2664
2665
    /**
2666
     * 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.
2667
     * For example: If the number of pixels exceeds $this->pixelLimitGif (normally 10000) then it will be a "jpg" string in return.
2668
     *
2669
     * @param string $type The file extension, lowercase.
2670
     * @param int $w The width of the output image.
2671
     * @param int $h The height of the output image.
2672
     * @return string The filename, either "jpg" or "gif"/"png" (whatever $this->gifExtension is set to.)
2673
     */
2674
    public function gif_or_jpg($type, $w, $h)
2675
    {
2676
        if ($type === 'ai' || $w * $h < $this->pixelLimitGif) {
2677
            return $this->gifExtension;
2678
        }
2679
        return 'jpg';
2680
    }
2681
2682
    /**
2683
     * Writing the internal image pointer, $this->im, to file based on the extension of the input filename
2684
     * Used in GIFBUILDER
2685
     * Uses $this->setup['reduceColors'] for gif/png images and $this->setup['quality'] for jpg images to reduce size/quality if needed.
2686
     *
2687
     * @param string $file The filename to write to.
2688
     * @return string Returns input filename
2689
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::gifBuild()
2690
     */
2691
    public function output($file)
2692
    {
2693
        if ($file) {
2694
            $reg = [];
2695
            preg_match('/([^\\.]*)$/', $file, $reg);
2696
            $ext = strtolower($reg[0]);
2697
            switch ($ext) {
2698
                case 'gif':
2699
                case 'png':
2700
                    if ($this->ImageWrite($this->im, $file)) {
2701
                        // ImageMagick operations
2702
                        if ($this->setup['reduceColors']) {
2703
                            $reduced = $this->IMreduceColors($file, MathUtility::forceIntegerInRange($this->setup['reduceColors'], 256, $this->truecolorColors, 256));
2704
                            if ($reduced) {
2705
                                @copy($reduced, $file);
2706
                                @unlink($reduced);
2707
                            }
2708
                        }
2709
                        // Compress with IM! (adds extra compression, LZW from ImageMagick)
2710
                        // (Workaround for the absence of lzw-compression in GD)
2711
                        self::gifCompress($file, 'IM');
2712
                    }
2713
                    break;
2714
                case 'jpg':
2715
                case 'jpeg':
2716
                    // Use the default
2717
                    $quality = 0;
2718
                    if ($this->setup['quality']) {
2719
                        $quality = MathUtility::forceIntegerInRange($this->setup['quality'], 10, 100);
2720
                    }
2721
                    $this->ImageWrite($this->im, $file, $quality);
2722
                    break;
2723
            }
2724
        }
2725
        return $file;
2726
    }
2727
2728
    /**
2729
     * Destroy internal image pointer, $this->im
2730
     *
2731
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::gifBuild()
2732
     */
2733
    public function destroy()
2734
    {
2735
        imagedestroy($this->im);
2736
    }
2737
2738
    /**
2739
     * Returns Image Tag for input image information array.
2740
     *
2741
     * @param array $imgInfo Image information array, key 0/1 is width/height and key 3 is the src value
2742
     * @return string Image tag for the input image information array.
2743
     */
2744
    public function imgTag($imgInfo)
2745
    {
2746
        return '<img src="' . $imgInfo[3] . '" width="' . $imgInfo[0] . '" height="' . $imgInfo[1] . '" border="0" alt="" />';
2747
    }
2748
2749
    /**
2750
     * Writes the input GDlib image pointer to file
2751
     *
2752
     * @param resource $destImg The GDlib image resource pointer
2753
     * @param string $theImage The filename to write to
2754
     * @param int $quality The image quality (for JPEGs)
2755
     * @return bool The output of either imageGif, imagePng or imageJpeg based on the filename to write
2756
     * @see maskImageOntoImage()
2757
     * @see scale()
2758
     * @see output()
2759
     */
2760
    public function ImageWrite($destImg, $theImage, $quality = 0)
2761
    {
2762
        imageinterlace($destImg, 0);
2763
        $ext = strtolower(substr($theImage, (int)strrpos($theImage, '.') + 1));
2764
        $result = false;
2765
        switch ($ext) {
2766
            case 'jpg':
2767
            case 'jpeg':
2768
                if (function_exists('imagejpeg')) {
2769
                    if ($quality === 0) {
2770
                        $quality = $this->jpegQuality;
2771
                    }
2772
                    $result = imagejpeg($destImg, $theImage, $quality);
2773
                }
2774
                break;
2775
            case 'gif':
2776
                if (function_exists('imagegif')) {
2777
                    imagetruecolortopalette($destImg, true, 256);
2778
                    $result = imagegif($destImg, $theImage);
2779
                }
2780
                break;
2781
            case 'png':
2782
                if (function_exists('imagepng')) {
2783
                    $result = imagepng($destImg, $theImage);
2784
                }
2785
                break;
2786
        }
2787
        if ($result) {
2788
            GeneralUtility::fixPermissions($theImage);
2789
        }
2790
        return $result;
2791
    }
2792
2793
    /**
2794
     * Creates a new GDlib image resource based on the input image filename.
2795
     * 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.
2796
     *
2797
     * @param string $sourceImg Image filename
2798
     * @return resource Image Resource pointer
2799
     */
2800
    public function imageCreateFromFile($sourceImg)
2801
    {
2802
        $imgInf = pathinfo($sourceImg);
2803
        $ext = strtolower($imgInf['extension']);
2804
        switch ($ext) {
2805
            case 'gif':
2806
                if (function_exists('imagecreatefromgif')) {
2807
                    return imagecreatefromgif($sourceImg);
0 ignored issues
show
Bug Best Practice introduced by
The expression return imagecreatefromgif($sourceImg) could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2808
                }
2809
                break;
2810
            case 'png':
2811
                if (function_exists('imagecreatefrompng')) {
2812
                    $imageHandle = imagecreatefrompng($sourceImg);
2813
                    if ($this->saveAlphaLayer) {
2814
                        imagesavealpha($imageHandle, true);
0 ignored issues
show
Bug introduced by
It seems like $imageHandle can also be of type false; however, parameter $image of imagesavealpha() 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

2814
                        imagesavealpha(/** @scrutinizer ignore-type */ $imageHandle, true);
Loading history...
2815
                    }
2816
                    return $imageHandle;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $imageHandle could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2817
                }
2818
                break;
2819
            case 'jpg':
2820
            case 'jpeg':
2821
                if (function_exists('imagecreatefromjpeg')) {
2822
                    return imagecreatefromjpeg($sourceImg);
0 ignored issues
show
Bug Best Practice introduced by
The expression return imagecreatefromjpeg($sourceImg) could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2823
                }
2824
                break;
2825
        }
2826
        // If non of the above:
2827
        $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $sourceImg);
2828
        $im = imagecreatetruecolor($imageInfo->getWidth(), $imageInfo->getHeight());
2829
        $Bcolor = imagecolorallocate($im, 128, 128, 128);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type false; however, parameter $image of imagecolorallocate() 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

2829
        $Bcolor = imagecolorallocate(/** @scrutinizer ignore-type */ $im, 128, 128, 128);
Loading history...
2830
        imagefilledrectangle($im, 0, 0, $imageInfo->getWidth(), $imageInfo->getHeight(), $Bcolor);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type false; however, parameter $image of imagefilledrectangle() 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

2830
        imagefilledrectangle(/** @scrutinizer ignore-type */ $im, 0, 0, $imageInfo->getWidth(), $imageInfo->getHeight(), $Bcolor);
Loading history...
2831
        return $im;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $im could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2832
    }
2833
2834
    /**
2835
     * Returns the HEX color value for an RGB color array
2836
     *
2837
     * @param array $color RGB color array
2838
     * @return string HEX color value
2839
     */
2840
    public function hexColor($color)
2841
    {
2842
        $r = dechex($color[0]);
2843
        if (strlen($r) < 2) {
2844
            $r = '0' . $r;
2845
        }
2846
        $g = dechex($color[1]);
2847
        if (strlen($g) < 2) {
2848
            $g = '0' . $g;
2849
        }
2850
        $b = dechex($color[2]);
2851
        if (strlen($b) < 2) {
2852
            $b = '0' . $b;
2853
        }
2854
        return '#' . $r . $g . $b;
2855
    }
2856
2857
    /**
2858
     * Unifies all colors given in the colArr color array to the first color in the array.
2859
     *
2860
     * @param resource $img Image resource
2861
     * @param array $colArr Array containing RGB color arrays
2862
     * @param bool $closest
2863
     * @return int The index of the unified color
2864
     */
2865
    public function unifyColors(&$img, $colArr, $closest = false)
2866
    {
2867
        $retCol = -1;
2868
        if (is_array($colArr) && !empty($colArr) && function_exists('imagepng') && function_exists('imagecreatefrompng')) {
2869
            $firstCol = array_shift($colArr);
2870
            $firstColArr = $this->convertColor($firstCol);
2871
            $origName = $preName = $this->randomName() . '.png';
2872
            $postName = $this->randomName() . '.png';
2873
            $tmpImg = null;
2874
            if (count($colArr) > 1) {
2875
                $this->ImageWrite($img, $preName);
2876
                $firstCol = $this->hexColor($firstColArr);
2877
                foreach ($colArr as $transparentColor) {
2878
                    $transparentColor = $this->convertColor($transparentColor);
2879
                    $transparentColor = $this->hexColor($transparentColor);
2880
                    $cmd = '-fill "' . $firstCol . '" -opaque "' . $transparentColor . '"';
2881
                    $this->imageMagickExec($preName, $postName, $cmd);
2882
                    $preName = $postName;
2883
                }
2884
                $this->imageMagickExec($postName, $origName, '');
2885
                if (@is_file($origName)) {
2886
                    $tmpImg = $this->imageCreateFromFile($origName);
2887
                }
2888
            } else {
2889
                $tmpImg = $img;
2890
            }
2891
            if ($tmpImg) {
0 ignored issues
show
introduced by
$tmpImg is of type null|resource, thus it always evaluated to false.
Loading history...
2892
                $img = $tmpImg;
2893
                if ($closest) {
2894
                    $retCol = imagecolorclosest($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2895
                } else {
2896
                    $retCol = imagecolorexact($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2897
                }
2898
            }
2899
            // Unlink files from process
2900
            if ($origName) {
2901
                @unlink($origName);
2902
            }
2903
            if ($postName) {
2904
                @unlink($postName);
2905
            }
2906
        }
2907
        return $retCol;
2908
    }
2909
2910
    /**
2911
     * Creates error image based on gfx/notfound_thumb.png
2912
     * Requires GD lib enabled, otherwise it will exit with the three
2913
     * textstrings outputted as text. Outputs the image stream to browser and exits!
2914
     *
2915
     * @param string $filename Name of the file
2916
     * @param string $textline1 Text line 1
2917
     * @param string $textline2 Text line 2
2918
     * @param string $textline3 Text line 3
2919
     * @throws \RuntimeException
2920
     */
2921
    public function getTemporaryImageWithText($filename, $textline1, $textline2, $textline3)
2922
    {
2923
        if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'])) {
2924
            throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
2925
        }
2926
        // Creates the basis for the error image
2927
        $basePath = ExtensionManagementUtility::extPath('core') . 'Resources/Public/Images/';
2928
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
2929
            $im = imagecreatefrompng($basePath . 'NotFound.png');
2930
        } else {
2931
            $im = imagecreatefromgif($basePath . 'NotFound.gif');
2932
        }
2933
        // Sets background color and print color.
2934
        $white = imagecolorallocate($im, 255, 255, 255);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type false; however, parameter $image of imagecolorallocate() 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

2934
        $white = imagecolorallocate(/** @scrutinizer ignore-type */ $im, 255, 255, 255);
Loading history...
2935
        $black = imagecolorallocate($im, 0, 0, 0);
2936
        // Prints the text strings with the build-in font functions of GD
2937
        $x = 0;
2938
        $font = 0;
2939
        if ($textline1) {
2940
            imagefilledrectangle($im, $x, 9, 56, 16, $white);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type false; however, parameter $image of imagefilledrectangle() 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

2940
            imagefilledrectangle(/** @scrutinizer ignore-type */ $im, $x, 9, 56, 16, $white);
Loading history...
2941
            imagestring($im, $font, $x, 9, $textline1, $black);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type false; however, parameter $image of imagestring() 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

2941
            imagestring(/** @scrutinizer ignore-type */ $im, $font, $x, 9, $textline1, $black);
Loading history...
2942
        }
2943
        if ($textline2) {
2944
            imagefilledrectangle($im, $x, 19, 56, 26, $white);
2945
            imagestring($im, $font, $x, 19, $textline2, $black);
2946
        }
2947
        if ($textline3) {
2948
            imagefilledrectangle($im, $x, 29, 56, 36, $white);
2949
            imagestring($im, $font, $x, 29, substr($textline3, -14), $black);
2950
        }
2951
        // Outputting the image stream and exit
2952
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
2953
            imagepng($im, $filename);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type false; however, parameter $image of imagepng() 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

2953
            imagepng(/** @scrutinizer ignore-type */ $im, $filename);
Loading history...
2954
        } else {
2955
            imagegif($im, $filename);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type false; however, parameter $image of imagegif() 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

2955
            imagegif(/** @scrutinizer ignore-type */ $im, $filename);
Loading history...
2956
        }
2957
    }
2958
2959
    /**
2960
     * Function to compensate for DPI resolution.
2961
     * FreeType 2 always has 96 dpi, so it is hard-coded at this place.
2962
     *
2963
     * @param float $fontSize font size for freetype function call
2964
     * @return float compensated font size based on 96 dpi
2965
     */
2966
    protected function compensateFontSizeiBasedOnFreetypeDpi($fontSize)
2967
    {
2968
        return $fontSize / 96.0 * 72;
2969
    }
2970
}
2971