Passed
Branch master (6c65a4)
by Christian
27:15 queued 11:09
created

GraphicalFunctions::splitString()   D

Complexity

Conditions 49
Paths 2

Size

Total Lines 127
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 49
eloc 80
nc 2
nop 4
dl 0
loc 127
rs 4.1818
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
namespace TYPO3\CMS\Core\Imaging;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Core\Cache\CacheManager;
18
use TYPO3\CMS\Core\Charset\CharsetConverter;
19
use TYPO3\CMS\Core\Utility\ArrayUtility;
20
use TYPO3\CMS\Core\Utility\CommandUtility;
21
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
22
use TYPO3\CMS\Core\Utility\GeneralUtility;
23
use TYPO3\CMS\Core\Utility\MathUtility;
24
25
/**
26
 * Standard graphical functions
27
 *
28
 * Class contains a bunch of cool functions for manipulating graphics with GDlib/Freetype and ImageMagick.
29
 * VERY OFTEN used with gifbuilder that extends this class and provides a TypoScript API to using these functions
30
 */
31
class GraphicalFunctions
32
{
33
    /**
34
     * If set, the frame pointer is appended to the filenames.
35
     *
36
     * @var bool
37
     */
38
    public $addFrameSelection = true;
39
40
    /**
41
     * This should be changed to 'png' if you want this class to read/make PNG-files instead!
42
     *
43
     * @var string
44
     */
45
    public $gifExtension = 'gif';
46
47
    /**
48
     * File formats supported by gdlib. This variable get's filled in "init" method
49
     *
50
     * @var array
51
     */
52
    protected $gdlibExtensions = [];
53
54
    /**
55
     * defines the RGB colorspace to use
56
     *
57
     * @var string
58
     */
59
    protected $colorspace = 'RGB';
60
61
    /**
62
     * colorspace names allowed
63
     *
64
     * @var array
65
     */
66
    protected $allowedColorSpaceNames = [
67
        'CMY',
68
        'CMYK',
69
        'Gray',
70
        'HCL',
71
        'HSB',
72
        'HSL',
73
        'HWB',
74
        'Lab',
75
        'LCH',
76
        'LMS',
77
        'Log',
78
        'Luv',
79
        'OHTA',
80
        'Rec601Luma',
81
        'Rec601YCbCr',
82
        'Rec709Luma',
83
        'Rec709YCbCr',
84
        'RGB',
85
        'sRGB',
86
        'Transparent',
87
        'XYZ',
88
        'YCbCr',
89
        'YCC',
90
        'YIQ',
91
        'YCbCr',
92
        'YUV'
93
    ];
94
95
    /**
96
     * 16777216 Colors is the maximum value for PNG, JPEG truecolor images (24-bit, 8-bit / Channel)
97
     *
98
     * @var int
99
     */
100
    public $truecolorColors = 16777215;
101
102
    /**
103
     * Allowed file extensions perceived as images by TYPO3.
104
     * List should be set to 'gif,png,jpeg,jpg' if IM is not available.
105
     *
106
     * @var array
107
     */
108
    protected $imageFileExt = ['gif', 'jpg', 'jpeg', 'png', 'tif', 'bmp', 'tga', 'pcx', 'ai', 'pdf'];
109
110
    /**
111
     * Web image extensions (can be shown by a webbrowser)
112
     *
113
     * @var array
114
     */
115
    protected $webImageExt = ['gif', 'jpg', 'jpeg', 'png'];
116
117
    /**
118
     * Enable ImageMagick effects, disabled by default as IM5+ effects slow down the image generation
119
     *
120
     * @var bool
121
     */
122
    protected $processorEffectsEnabled = false;
123
124
    /**
125
     * @var array
126
     */
127
    public $cmds = [
128
        'jpg' => '',
129
        'jpeg' => '',
130
        'gif' => '',
131
        'png' => ''
132
    ];
133
134
    /**
135
     * @var bool
136
     */
137
    protected $NO_IMAGE_MAGICK = false;
138
139
    /**
140
     * @var bool
141
     */
142
    protected $mayScaleUp = true;
143
144
    /**
145
     * Filename prefix for images scaled in imageMagickConvert()
146
     *
147
     * @var string
148
     */
149
    public $filenamePrefix = '';
150
151
    /**
152
     * Forcing the output filename of imageMagickConvert() to this value. However after calling imageMagickConvert() it will be set blank again.
153
     *
154
     * @var string
155
     */
156
    public $imageMagickConvert_forceFileNameBody = '';
157
158
    /**
159
     * This flag should always be FALSE. If set TRUE, imageMagickConvert will always write a new file to the tempdir! Used for debugging.
160
     *
161
     * @var bool
162
     */
163
    public $dontCheckForExistingTempFile = false;
164
165
    /**
166
     * Prevents imageMagickConvert() from compressing the gif-files with self::gifCompress()
167
     *
168
     * @var bool
169
     */
170
    public $dontCompress = false;
171
172
    /**
173
     * For debugging only.
174
     * Filenames will not be based on mtime and only filename (not path) will be used.
175
     * This key is also included in the hash of the filename...
176
     *
177
     * @var string
178
     */
179
    public $alternativeOutputKey = '';
180
181
    /**
182
     * All ImageMagick commands executed is stored in this array for tracking. Used by the Install Tools Image section
183
     *
184
     * @var array
185
     */
186
    public $IM_commands = [];
187
188
    /**
189
     * @var array
190
     */
191
    public $workArea = [];
192
193
    /**
194
     * Preserve the alpha transparency layer of read PNG images
195
     *
196
     * @var bool
197
     */
198
    protected $saveAlphaLayer = false;
199
200
    /**
201
     * ImageMagick scaling command; "-geometry" or "-sample". Used in makeText() and imageMagickConvert()
202
     *
203
     * @var string
204
     */
205
    public $scalecmd = '-geometry';
206
207
    /**
208
     * Used by v5_blur() to simulate 10 continuous steps of blurring
209
     *
210
     * @var string
211
     */
212
    protected $im5fx_blurSteps = '1x2,2x2,3x2,4x3,5x3,5x4,6x4,7x5,8x5,9x5';
213
214
    /**
215
     * Used by v5_sharpen() to simulate 10 continuous steps of sharpening.
216
     *
217
     * @var string
218
     */
219
    protected $im5fx_sharpenSteps = '1x2,2x2,3x2,2x3,3x3,4x3,3x4,4x4,4x5,5x5';
220
221
    /**
222
     * This is the limit for the number of pixels in an image before it will be rendered as JPG instead of GIF/PNG
223
     *
224
     * @var int
225
     */
226
    protected $pixelLimitGif = 10000;
227
228
    /**
229
     * Array mapping HTML color names to RGB values.
230
     *
231
     * @var array
232
     */
233
    protected $colMap = [
234
        'aqua' => [0, 255, 255],
235
        'black' => [0, 0, 0],
236
        'blue' => [0, 0, 255],
237
        'fuchsia' => [255, 0, 255],
238
        'gray' => [128, 128, 128],
239
        'green' => [0, 128, 0],
240
        'lime' => [0, 255, 0],
241
        'maroon' => [128, 0, 0],
242
        'navy' => [0, 0, 128],
243
        'olive' => [128, 128, 0],
244
        'purple' => [128, 0, 128],
245
        'red' => [255, 0, 0],
246
        'silver' => [192, 192, 192],
247
        'teal' => [0, 128, 128],
248
        'yellow' => [255, 255, 0],
249
        'white' => [255, 255, 255]
250
    ];
251
252
    /**
253
     * Charset conversion object:
254
     *
255
     * @var CharsetConverter
256
     */
257
    protected $csConvObj;
258
259
    /**
260
     * @var int
261
     */
262
    protected $jpegQuality = 85;
263
264
    /**
265
     * @var string
266
     */
267
    public $map = '';
268
269
    /**
270
     * This holds the operational setup.
271
     * Basically this is a TypoScript array with properties.
272
     *
273
     * @var array
274
     */
275
    public $setup = [];
276
277
    /**
278
     * @var int
279
     */
280
    public $w = 0;
281
282
    /**
283
     * @var int
284
     */
285
    public $h = 0;
286
287
    /**
288
     * @var array
289
     */
290
    protected $OFFSET;
291
292
    /**
293
     * @var resource
294
     */
295
    protected $im;
296
297
    /**
298
     * Init function. Must always call this when using the class.
299
     * This function will read the configuration information from $GLOBALS['TYPO3_CONF_VARS']['GFX'] can set some values in internal variables.
300
     */
301
    public function init()
302
    {
303
        $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
304
        if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
305
            $this->gdlibExtensions[] = 'jpg';
306
            $this->gdlibExtensions[] = 'jpeg';
307
        }
308
        if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
309
            $this->gdlibExtensions[] = 'png';
310
        }
311
        if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
312
            $this->gdlibExtensions[] = 'gif';
313
        }
314
315
        if ($gfxConf['processor_colorspace'] && in_array($gfxConf['processor_colorspace'], $this->allowedColorSpaceNames, true)) {
316
            $this->colorspace = $gfxConf['processor_colorspace'];
317
        }
318
319
        if (!$gfxConf['processor_enabled']) {
320
            $this->NO_IMAGE_MAGICK = true;
321
        }
322
        // Setting default JPG parameters:
323
        $this->jpegQuality = MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, 85);
324
        $this->addFrameSelection = (bool)$gfxConf['processor_allowFrameSelection'];
325
        if ($gfxConf['gdlib_png']) {
326
            $this->gifExtension = 'png';
327
        }
328
        $this->imageFileExt = GeneralUtility::trimExplode(',', $gfxConf['imagefile_ext']);
329
330
        // Boolean. This is necessary if using ImageMagick 5+.
331
        // Effects in Imagemagick 5+ tends to render very slowly!!
332
        // - therefore must be disabled in order not to perform sharpen, blurring and such.
333
        $this->cmds['jpg'] = $this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . $this->jpegQuality;
334
335
        // ... but if 'processor_effects' is set, enable effects
336
        if ($gfxConf['processor_effects']) {
337
            $this->processorEffectsEnabled = true;
338
            $this->cmds['jpg'] .= $this->v5_sharpen(10);
339
            $this->cmds['jpeg'] .= $this->v5_sharpen(10);
340
        }
341
        // Secures that images are not scaled up.
342
        $this->mayScaleUp = (bool)$gfxConf['processor_allowUpscaling'];
343
        $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
344
    }
345
346
    /*************************************************
347
     *
348
     * Layering images / "IMAGE" GIFBUILDER object
349
     *
350
     *************************************************/
351
    /**
352
     * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is TRUE.
353
     * 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
354
     * The operation involves ImageMagick for combining.
355
     *
356
     * @param resource $im GDlib image pointer
357
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
358
     * @param array $workArea The current working area coordinates.
359
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
360
     */
361
    public function maskImageOntoImage(&$im, $conf, $workArea)
362
    {
363
        if ($conf['file'] && $conf['mask']) {
364
            $imgInf = pathinfo($conf['file']);
365
            $imgExt = strtolower($imgInf['extension']);
366
            if (!in_array($imgExt, $this->gdlibExtensions, true)) {
367
                $BBimage = $this->imageMagickConvert($conf['file'], $this->gifExtension);
368
            } else {
369
                $BBimage = $this->getImageDimensions($conf['file']);
370
            }
371
            $maskInf = pathinfo($conf['mask']);
372
            $maskExt = strtolower($maskInf['extension']);
373
            if (!in_array($maskExt, $this->gdlibExtensions, true)) {
374
                $BBmask = $this->imageMagickConvert($conf['mask'], $this->gifExtension);
375
            } else {
376
                $BBmask = $this->getImageDimensions($conf['mask']);
377
            }
378
            if ($BBimage && $BBmask) {
379
                $w = imagesx($im);
380
                $h = imagesy($im);
381
                $tmpStr = $this->randomName();
382
                $theImage = $tmpStr . '_img.' . $this->gifExtension;
383
                $theDest = $tmpStr . '_dest.' . $this->gifExtension;
384
                $theMask = $tmpStr . '_mask.' . $this->gifExtension;
385
                // Prepare overlay image
386
                $cpImg = $this->imageCreateFromFile($BBimage[3]);
387
                $destImg = imagecreatetruecolor($w, $h);
388
                // Preserve alpha transparency
389
                if ($this->saveAlphaLayer) {
390
                    imagesavealpha($destImg, true);
391
                    $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
392
                    imagefill($destImg, 0, 0, $Bcolor);
393
                } else {
394
                    $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
395
                    imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
396
                }
397
                $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
398
                $this->ImageWrite($destImg, $theImage);
399
                imagedestroy($cpImg);
400
                imagedestroy($destImg);
401
                // Prepare mask image
402
                $cpImg = $this->imageCreateFromFile($BBmask[3]);
403
                $destImg = imagecreatetruecolor($w, $h);
404
                if ($this->saveAlphaLayer) {
405
                    imagesavealpha($destImg, true);
406
                    $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
407
                    imagefill($destImg, 0, 0, $Bcolor);
408
                } else {
409
                    $Bcolor = imagecolorallocate($destImg, 0, 0, 0);
410
                    imagefilledrectangle($destImg, 0, 0, $w, $h, $Bcolor);
411
                }
412
                $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
413
                $this->ImageWrite($destImg, $theMask);
414
                imagedestroy($cpImg);
415
                imagedestroy($destImg);
416
                // Mask the images
417
                $this->ImageWrite($im, $theDest);
418
                // Let combineExec handle maskNegation
419
                $this->combineExec($theDest, $theImage, $theMask, $theDest);
420
                // The main image is loaded again...
421
                $backIm = $this->imageCreateFromFile($theDest);
422
                // ... and if nothing went wrong we load it onto the old one.
423
                if ($backIm) {
424
                    if (!$this->saveAlphaLayer) {
425
                        imagecolortransparent($backIm, -1);
426
                    }
427
                    $im = $backIm;
428
                }
429
                // Unlink files from process
430
                unlink($theDest);
431
                unlink($theImage);
432
                unlink($theMask);
433
            }
434
        }
435
    }
436
437
    /**
438
     * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is FALSE (using only $conf['file'])
439
     *
440
     * @param resource $im GDlib image pointer
441
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
442
     * @param array $workArea The current working area coordinates.
443
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), maskImageOntoImage()
444
     */
445
    public function copyImageOntoImage(&$im, $conf, $workArea)
446
    {
447
        if ($conf['file']) {
448
            if (!in_array($conf['BBOX'][2], $this->gdlibExtensions, true)) {
449
                $conf['BBOX'] = $this->imageMagickConvert($conf['BBOX'][3], $this->gifExtension);
450
                $conf['file'] = $conf['BBOX'][3];
451
            }
452
            $cpImg = $this->imageCreateFromFile($conf['file']);
453
            $this->copyGifOntoGif($im, $cpImg, $conf, $workArea);
454
            imagedestroy($cpImg);
455
        }
456
    }
457
458
    /**
459
     * Copies two GDlib image pointers onto each other, using TypoScript configuration from $conf and the input $workArea definition.
460
     *
461
     * @param resource $im GDlib image pointer, destination (bottom image)
462
     * @param resource $cpImg GDlib image pointer, source (top image)
463
     * @param array $conf TypoScript array with the properties for the IMAGE GIFBUILDER object. Only used for the "tile" property value.
464
     * @param array $workArea Work area
465
     * @access private
466
     */
467
    public function copyGifOntoGif(&$im, $cpImg, $conf, $workArea)
468
    {
469
        $cpW = imagesx($cpImg);
470
        $cpH = imagesy($cpImg);
471
        $tile = GeneralUtility::intExplode(',', $conf['tile']);
472
        $tile[0] = MathUtility::forceIntegerInRange($tile[0], 1, 20);
473
        $tile[1] = MathUtility::forceIntegerInRange($tile[1], 1, 20);
474
        $cpOff = $this->objPosition($conf, $workArea, [$cpW * $tile[0], $cpH * $tile[1]]);
475
        for ($xt = 0; $xt < $tile[0]; $xt++) {
476
            $Xstart = $cpOff[0] + $cpW * $xt;
477
            // If this image is inside of the workArea, then go on
478
            if ($Xstart + $cpW > $workArea[0]) {
479
                // X:
480
                if ($Xstart < $workArea[0]) {
481
                    $cpImgCutX = $workArea[0] - $Xstart;
482
                    $Xstart = $workArea[0];
483
                } else {
484
                    $cpImgCutX = 0;
485
                }
486
                $w = $cpW - $cpImgCutX;
487
                if ($Xstart > $workArea[0] + $workArea[2] - $w) {
488
                    $w = $workArea[0] + $workArea[2] - $Xstart;
489
                }
490
                // If this image is inside of the workArea, then go on
491
                if ($Xstart < $workArea[0] + $workArea[2]) {
492
                    // Y:
493
                    for ($yt = 0; $yt < $tile[1]; $yt++) {
494
                        $Ystart = $cpOff[1] + $cpH * $yt;
495
                        // If this image is inside of the workArea, then go on
496
                        if ($Ystart + $cpH > $workArea[1]) {
497
                            if ($Ystart < $workArea[1]) {
498
                                $cpImgCutY = $workArea[1] - $Ystart;
499
                                $Ystart = $workArea[1];
500
                            } else {
501
                                $cpImgCutY = 0;
502
                            }
503
                            $h = $cpH - $cpImgCutY;
504
                            if ($Ystart > $workArea[1] + $workArea[3] - $h) {
505
                                $h = $workArea[1] + $workArea[3] - $Ystart;
506
                            }
507
                            // If this image is inside of the workArea, then go on
508
                            if ($Ystart < $workArea[1] + $workArea[3]) {
509
                                $this->imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, $w, $h, $w, $h);
510
                            }
511
                        }
512
                    }
513
                }
514
            }
515
        }
516
    }
517
518
    /**
519
     * Alternative function for using the similar PHP function imagecopyresized(). Used for GD2 only.
520
     *
521
     * OK, the reason for this stupid fix is the following story:
522
     * GD1.x was capable of copying two images together and combining their palettes! GD2 is apparently not.
523
     * With GD2 only the palette of the dest-image is used which mostly results in totally black images when trying to
524
     * copy a color-ful image onto the destination.
525
     * The GD2-fix is to
526
     * 1) Create a blank TRUE-COLOR image
527
     * 2) Copy the destination image onto that one
528
     * 3) Then do the actual operation; Copying the source (top image) onto that
529
     * 4) ... and return the result pointer.
530
     * 5) Reduce colors (if we do not, the result may become strange!)
531
     * It works, but the resulting images is now a true-color PNG which may be very large.
532
     * So, why not use 'imagetruecolortopalette ($im, TRUE, 256)' - well because it does NOT WORK! So simple is that.
533
     *
534
     * @param resource $dstImg Destination image
535
     * @param resource $srcImg Source image
536
     * @param int $dstX Destination x-coordinate
537
     * @param int $dstY Destination y-coordinate
538
     * @param int $srcX Source x-coordinate
539
     * @param int $srcY Source y-coordinate
540
     * @param int $dstWidth Destination width
541
     * @param int $dstHeight Destination height
542
     * @param int $srcWidth Source width
543
     * @param int $srcHeight Source height
544
     * @access private
545
     */
546
    public function imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight)
547
    {
548
        if (!$this->saveAlphaLayer) {
549
            // Make true color image
550
            $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
551
            // Copy the source image onto that
552
            imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
553
            // Then copy the source image onto that (the actual operation!)
554
            imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
555
            // Set the destination image
556
            $dstImg = $tmpImg;
557
        } else {
558
            imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
559
        }
560
    }
561
562
    /********************************
563
     *
564
     * Text / "TEXT" GIFBUILDER object
565
     *
566
     ********************************/
567
    /**
568
     * Implements the "TEXT" GIFBUILDER object
569
     *
570
     * @param resource $im GDlib image pointer
571
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
572
     * @param array $workArea The current working area coordinates.
573
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
574
     */
575
    public function makeText(&$im, $conf, $workArea)
576
    {
577
        // Spacing
578
        list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
579
        // Position
580
        $txtPos = $this->txtPosition($conf, $workArea, $conf['BBOX']);
581
        $theText = $conf['text'];
582
        if ($conf['imgMap'] && is_array($conf['imgMap.'])) {
583
            $this->addToMap($this->calcTextCordsForMap($conf['BBOX'][2], $txtPos, $conf['imgMap.']), $conf['imgMap.']);
584
        }
585
        if (!$conf['hideButCreateMap']) {
586
            // Font Color:
587
            $cols = $this->convertColor($conf['fontColor']);
588
            // NiceText is calculated
589
            if (!$conf['niceText']) {
590
                $Fcolor = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
591
                // antiAliasing is setup:
592
                $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
593
                for ($a = 0; $a < $conf['iterations']; $a++) {
594
                    // If any kind of spacing applys, we use this function:
595
                    if ($spacing || $wordSpacing) {
596
                        $this->SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
597
                    } else {
598
                        $this->renderTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf);
599
                    }
600
                }
601
            } else {
602
                // NICETEXT::
603
                // options anti_aliased and iterations is NOT available when doing this!!
604
                $w = imagesx($im);
605
                $h = imagesy($im);
606
                $tmpStr = $this->randomName();
607
                $fileMenu = $tmpStr . '_menuNT.' . $this->gifExtension;
608
                $fileColor = $tmpStr . '_colorNT.' . $this->gifExtension;
609
                $fileMask = $tmpStr . '_maskNT.' . $this->gifExtension;
610
                // Scalefactor
611
                $sF = MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
612
                $newW = ceil($sF * imagesx($im));
613
                $newH = ceil($sF * imagesy($im));
614
                // Make mask
615
                $maskImg = imagecreatetruecolor($newW, $newH);
0 ignored issues
show
Bug introduced by
$newH of type double is incompatible with the type integer expected by parameter $height of imagecreatetruecolor(). ( Ignorable by Annotation )

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

615
                $maskImg = imagecreatetruecolor($newW, /** @scrutinizer ignore-type */ $newH);
Loading history...
Bug introduced by
$newW of type double is incompatible with the type integer expected by parameter $width of imagecreatetruecolor(). ( Ignorable by Annotation )

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

615
                $maskImg = imagecreatetruecolor(/** @scrutinizer ignore-type */ $newW, $newH);
Loading history...
616
                $Bcolor = imagecolorallocate($maskImg, 255, 255, 255);
617
                imagefilledrectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
0 ignored issues
show
Bug introduced by
$newW of type double is incompatible with the type integer expected by parameter $x2 of imagefilledrectangle(). ( Ignorable by Annotation )

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

617
                imagefilledrectangle($maskImg, 0, 0, /** @scrutinizer ignore-type */ $newW, $newH, $Bcolor);
Loading history...
Bug introduced by
$newH of type double is incompatible with the type integer expected by parameter $y2 of imagefilledrectangle(). ( Ignorable by Annotation )

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

617
                imagefilledrectangle($maskImg, 0, 0, $newW, /** @scrutinizer ignore-type */ $newH, $Bcolor);
Loading history...
618
                $Fcolor = imagecolorallocate($maskImg, 0, 0, 0);
619
                // If any kind of spacing applies, we use this function:
620
                if ($spacing || $wordSpacing) {
621
                    $this->SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, GeneralUtility::getFileAbsFileName($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
622
                } else {
623
                    $this->renderTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf, $sF);
624
                }
625
                $this->ImageWrite($maskImg, $fileMask);
626
                imagedestroy($maskImg);
627
                // Downscales the mask
628
                if (!$this->processorEffectsEnabled) {
629
                    $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '! -negate');
630
                } else {
631
                    $command = trim($conf['niceText.']['before'] . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . $conf['niceText.']['after'] . ' -negate');
632
                    if ($conf['niceText.']['sharpen']) {
633
                        $command .= $this->v5_sharpen($conf['niceText.']['sharpen']);
634
                    }
635
                }
636
                $this->imageMagickExec($fileMask, $fileMask, $command);
637
                // Make the color-file
638
                $colorImg = imagecreatetruecolor($w, $h);
639
                $Ccolor = imagecolorallocate($colorImg, $cols[0], $cols[1], $cols[2]);
640
                imagefilledrectangle($colorImg, 0, 0, $w, $h, $Ccolor);
641
                $this->ImageWrite($colorImg, $fileColor);
642
                imagedestroy($colorImg);
643
                // The mask is applied
644
                // The main pictures is saved temporarily
645
                $this->ImageWrite($im, $fileMenu);
646
                $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
647
                // The main image is loaded again...
648
                $backIm = $this->imageCreateFromFile($fileMenu);
649
                // ... and if nothing went wrong we load it onto the old one.
650
                if ($backIm) {
651
                    if (!$this->saveAlphaLayer) {
652
                        imagecolortransparent($backIm, -1);
653
                    }
654
                    $im = $backIm;
655
                }
656
                // Deleting temporary files;
657
                unlink($fileMenu);
658
                unlink($fileColor);
659
                unlink($fileMask);
660
            }
661
        }
662
    }
663
664
    /**
665
     * Calculates text position for printing the text onto the image based on configuration like alignment and workarea.
666
     *
667
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
668
     * @param array $workArea Work area definition
669
     * @param array $BB Bounding box information, was set in \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
670
     * @return array [0]=x, [1]=y, [2]=w, [3]=h
671
     * @access private
672
     * @see makeText()
673
     */
674
    public function txtPosition($conf, $workArea, $BB)
675
    {
676
        $angle = (int)$conf['angle'] / 180 * pi();
677
        $conf['angle'] = 0;
678
        $straightBB = $this->calcBBox($conf);
679
        // offset, align, valign, workarea
680
        // [0]=x, [1]=y, [2]=w, [3]=h
681
        $result = [];
682
        $result[2] = $BB[0];
683
        $result[3] = $BB[1];
684
        $w = $workArea[2];
685
        switch ($conf['align']) {
686
            case 'right':
687
688
            case 'center':
689
                $factor = abs(cos($angle));
690
                $sign = cos($angle) < 0 ? -1 : 1;
691
                $len1 = $sign * $factor * $straightBB[0];
692
                $len2 = $sign * $BB[0];
693
                $result[0] = $w - ceil(($len2 * $factor + (1 - $factor) * $len1));
694
                $factor = abs(sin($angle));
695
                $sign = sin($angle) < 0 ? -1 : 1;
696
                $len1 = $sign * $factor * $straightBB[0];
697
                $len2 = $sign * $BB[1];
698
                $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
699
                break;
700
        }
701
        switch ($conf['align']) {
702
            case 'right':
703
                break;
704
            case 'center':
705
                $result[0] = round($result[0] / 2);
706
                $result[1] = round($result[1] / 2);
707
                break;
708
            default:
709
                $result[0] = 0;
710
                $result[1] = 0;
711
        }
712
        $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
713
        $result = $this->applyOffset($result, $workArea);
714
        return $result;
715
    }
716
717
    /**
718
     * Calculates bounding box information for the TEXT GIFBUILDER object.
719
     *
720
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
721
     * @return array Array with three keys [0]/[1] being x/y and [2] being the bounding box array
722
     * @access private
723
     * @see txtPosition(), \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
724
     */
725
    public function calcBBox($conf)
726
    {
727
        $sF = $this->getTextScalFactor($conf);
728
        list($spacing, $wordSpacing) = $this->calcWordSpacing($conf, $sF);
729
        $theText = $conf['text'];
730
        $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $theText, $conf['splitRendering.'], $sF);
731
        $theBBoxInfo = $charInf;
732
        if ($conf['angle']) {
733
            $xArr = [$charInf[0], $charInf[2], $charInf[4], $charInf[6]];
734
            $yArr = [$charInf[1], $charInf[3], $charInf[5], $charInf[7]];
735
            $x = max($xArr) - min($xArr);
736
            $y = max($yArr) - min($yArr);
737
        } else {
738
            $x = $charInf[2] - $charInf[0];
739
            $y = $charInf[1] - $charInf[7];
740
        }
741
        // Set original lineHeight (used by line breaks):
742
        $theBBoxInfo['lineHeight'] = $y;
743
        // If any kind of spacing applys, we use this function:
744
        if ($spacing || $wordSpacing) {
745
            $x = 0;
746
            if (!$spacing && $wordSpacing) {
747
                $bits = explode(' ', $theText);
748
                foreach ($bits as $word) {
749
                    $word .= ' ';
750
                    $wordInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
751
                    $wordW = $wordInf[2] - $wordInf[0];
752
                    $x += $wordW + $wordSpacing;
753
                }
754
            } else {
755
                $utf8Chars = $this->csConvObj->utf8_to_numberarray($theText);
756
                // For each UTF-8 char, do:
757
                foreach ($utf8Chars as $char) {
758
                    $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
759
                    $charW = $charInf[2] - $charInf[0];
760
                    $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
761
                }
762
            }
763
        } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
764
            $maxWidth = 0;
765
            $currentWidth = 0;
766
            $breakWidth = $conf['breakWidth'];
767
            $breakSpace = $this->getBreakSpace($conf, $theBBoxInfo);
768
            $wordPairs = $this->getWordPairsForLineBreak($conf['text']);
769
            // Iterate through all word pairs:
770
            foreach ($wordPairs as $index => $wordPair) {
771
                $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
772
                if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
773
                    $currentWidth += $wordWidth;
774
                } else {
775
                    $maxWidth = max($maxWidth, $currentWidth);
776
                    $y += $breakSpace;
777
                    // Restart:
778
                    $currentWidth = $wordWidth;
779
                }
780
            }
781
            $x = max($maxWidth, $currentWidth) * $sF;
782
        }
783
        if ($sF > 1) {
784
            $x = ceil($x / $sF);
785
            $y = ceil($y / $sF);
786
            if (is_array($theBBoxInfo)) {
787
                foreach ($theBBoxInfo as &$value) {
788
                    $value = ceil($value / $sF);
789
                }
790
                unset($value);
791
            }
792
        }
793
        return [$x, $y, $theBBoxInfo];
794
    }
795
796
    /**
797
     * Adds an <area> tag to the internal variable $this->map which is used to accumulate the content for an ImageMap
798
     *
799
     * @param array $cords Coordinates for a polygon image map as created by ->calcTextCordsForMap()
800
     * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
801
     * @access private
802
     * @see makeText(), calcTextCordsForMap()
803
     */
804
    public function addToMap($cords, $conf)
805
    {
806
        $this->map .= '<area' . ' shape="poly"' . ' coords="' . implode(',', $cords) . '"'
807
            . ' href="' . htmlspecialchars($conf['url']) . '"'
808
            . ($conf['target'] ? ' target="' . htmlspecialchars($conf['target']) . '"' : '')
809
            . ((string)$conf['titleText'] !== '' ? ' title="' . htmlspecialchars($conf['titleText']) . '"' : '')
810
            . ' alt="' . htmlspecialchars($conf['altText']) . '" />';
811
    }
812
813
    /**
814
     * Calculating the coordinates for a TEXT string on an image map. Used in an <area> tag
815
     *
816
     * @param array $cords Coordinates (from BBOX array)
817
     * @param array $offset Offset array
818
     * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
819
     * @return array
820
     * @access private
821
     * @see makeText(), calcTextCordsForMap()
822
     */
823
    public function calcTextCordsForMap($cords, $offset, $conf)
824
    {
825
        $pars = GeneralUtility::intExplode(',', $conf['explode'] . ',');
826
        $newCords[0] = $cords[0] + $offset[0] - $pars[0];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$newCords was never initialized. Although not strictly required by PHP, it is generally a good practice to add $newCords = array(); before regardless.
Loading history...
827
        $newCords[1] = $cords[1] + $offset[1] + $pars[1];
828
        $newCords[2] = $cords[2] + $offset[0] + $pars[0];
829
        $newCords[3] = $cords[3] + $offset[1] + $pars[1];
830
        $newCords[4] = $cords[4] + $offset[0] + $pars[0];
831
        $newCords[5] = $cords[5] + $offset[1] - $pars[1];
832
        $newCords[6] = $cords[6] + $offset[0] - $pars[0];
833
        $newCords[7] = $cords[7] + $offset[1] - $pars[1];
834
        return $newCords;
835
    }
836
837
    /**
838
     * Printing text onto an image like the PHP function imageTTFText does but in addition it offers options for spacing of letters and words.
839
     * Spacing is done by printing one char at a time and this means that the spacing is rather uneven and probably not very nice.
840
     * See
841
     *
842
     * @param resource $im (See argument for PHP function imageTTFtext())
843
     * @param int $fontSize (See argument for PHP function imageTTFtext())
844
     * @param int $angle (See argument for PHP function imageTTFtext())
845
     * @param int $x (See argument for PHP function imageTTFtext())
846
     * @param int $y (See argument for PHP function imageTTFtext())
847
     * @param int $Fcolor (See argument for PHP function imageTTFtext())
848
     * @param string $fontFile (See argument for PHP function imageTTFtext())
849
     * @param string $text (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
850
     * @param int $spacing The spacing of letters in pixels
851
     * @param int $wordSpacing The spacing of words in pixels
852
     * @param array $splitRenderingConf Array
853
     * @param int $sF Scale factor
854
     * @access private
855
     */
856
    public function SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF = 1)
857
    {
858
        $spacing *= $sF;
859
        $wordSpacing *= $sF;
860
        if (!$spacing && $wordSpacing) {
861
            $bits = explode(' ', $text);
862
            foreach ($bits as $word) {
863
                $word .= ' ';
864
                $wordInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
865
                $wordW = $wordInf[2] - $wordInf[0];
866
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
867
                $x += $wordW + $wordSpacing;
868
            }
869
        } else {
870
            $utf8Chars = $this->csConvObj->utf8_to_numberarray($text);
871
            // For each UTF-8 char, do:
872
            foreach ($utf8Chars as $char) {
873
                $charInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
874
                $charW = $charInf[2] - $charInf[0];
875
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
876
                $x += $charW + ($char === ' ' ? $wordSpacing : $spacing);
877
            }
878
        }
879
    }
880
881
    /**
882
     * Function that finds the right fontsize that will render the textstring within a certain width
883
     *
884
     * @param array $conf The TypoScript properties of the TEXT GIFBUILDER object
885
     * @return int The new fontSize
886
     * @access private
887
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
888
     */
889
    public function fontResize($conf)
890
    {
891
        // 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!!!!
892
        $maxWidth = (int)$conf['maxWidth'];
893
        list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
894
        if ($maxWidth) {
895
            // If any kind of spacing applys, we use this function:
896
            if ($spacing || $wordSpacing) {
897
                return $conf['fontSize'];
898
            }
899
            do {
900
                // Determine bounding box.
901
                $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $conf['text'], $conf['splitRendering.']);
902
                if ($conf['angle'] < 0) {
903
                    $pixelWidth = abs($bounds[4] - $bounds[0]);
904
                } elseif ($conf['angle'] > 0) {
905
                    $pixelWidth = abs($bounds[2] - $bounds[6]);
906
                } else {
907
                    $pixelWidth = abs($bounds[4] - $bounds[6]);
908
                }
909
                // Size is fine, exit:
910
                if ($pixelWidth <= $maxWidth) {
911
                    break;
912
                }
913
                $conf['fontSize']--;
914
            } while ($conf['fontSize'] > 1);
915
        }
916
        return $conf['fontSize'];
917
    }
918
919
    /**
920
     * Wrapper for ImageTTFBBox
921
     *
922
     * @param int $fontSize (See argument for PHP function ImageTTFBBox())
923
     * @param int $angle (See argument for PHP function ImageTTFBBox())
924
     * @param string $fontFile (See argument for PHP function ImageTTFBBox())
925
     * @param string $string (See argument for PHP function ImageTTFBBox())
926
     * @param array $splitRendering Split-rendering configuration
927
     * @param int $sF Scale factor
928
     * @return array Information array.
929
     */
930
    public function ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF = 1)
931
    {
932
        // Initialize:
933
        $offsetInfo = [];
934
        $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
935
        // Traverse string parts:
936
        foreach ($stringParts as $strCfg) {
937
            $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
938
            if (is_readable($fontFile)) {
939
                /**
940
                 * Calculate Bounding Box for part.
941
                 * Due to a PHP bug, we must retry if $calc[2] is negative.
942
                 *
943
                 * @see https://bugs.php.net/bug.php?id=51315
944
                 * @see https://bugs.php.net/bug.php?id=22513
945
                 */
946
                $try = 0;
947
                do {
948
                    $calc = imagettfbbox($this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
949
                } while ($calc[2] < 0 && $try++ < 10);
950
                // Calculate offsets:
951
                if (empty($offsetInfo)) {
952
                    // First run, just copy over.
953
                    $offsetInfo = $calc;
954
                } else {
955
                    $offsetInfo[2] += $calc[2] - $calc[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
956
                    $offsetInfo[3] += $calc[3] - $calc[1] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
957
                    $offsetInfo[4] += $calc[4] - $calc[6] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceBefore'] + (int)$strCfg['xSpaceAfter'];
958
                    $offsetInfo[5] += $calc[5] - $calc[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceBefore'] - (int)$strCfg['ySpaceAfter'];
959
                }
960
            } else {
961
                debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFBBoxWrapper()');
962
            }
963
        }
964
        return $offsetInfo;
965
    }
966
967
    /**
968
     * Wrapper for ImageTTFText
969
     *
970
     * @param resource $im (See argument for PHP function imageTTFtext())
971
     * @param int $fontSize (See argument for PHP function imageTTFtext())
972
     * @param int $angle (See argument for PHP function imageTTFtext())
973
     * @param int $x (See argument for PHP function imageTTFtext())
974
     * @param int $y (See argument for PHP function imageTTFtext())
975
     * @param int $color (See argument for PHP function imageTTFtext())
976
     * @param string $fontFile (See argument for PHP function imageTTFtext())
977
     * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
978
     * @param array $splitRendering Split-rendering configuration
979
     * @param int $sF Scale factor
980
     */
981
    public function ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF = 1)
982
    {
983
        // Initialize:
984
        $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
985
        $x = ceil($sF * $x);
986
        $y = ceil($sF * $y);
987
        // Traverse string parts:
988
        foreach ($stringParts as $i => $strCfg) {
989
            // Initialize:
990
            $colorIndex = $color;
991
            // Set custom color if any (only when niceText is off):
992
            if ($strCfg['color'] && $sF == 1) {
993
                $cols = $this->convertColor($strCfg['color']);
994
                $colorIndex = imagecolorallocate($im, $cols[0], $cols[1], $cols[2]);
995
                $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
996
            }
997
            // Setting xSpaceBefore
998
            if ($i) {
999
                $x += (int)$strCfg['xSpaceBefore'];
1000
                $y -= (int)$strCfg['ySpaceBefore'];
1001
            }
1002
            $fontFile = GeneralUtility::getFileAbsFileName($strCfg['fontFile']);
1003
            if (is_readable($fontFile)) {
1004
                // Render part:
1005
                imagettftext($im, $this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
0 ignored issues
show
Bug introduced by
It seems like $x can also be of type double; however, parameter $x of imagettftext() does only seem to accept integer, 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

1005
                imagettftext($im, $this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, /** @scrutinizer ignore-type */ $x, $y, $colorIndex, $fontFile, $strCfg['str']);
Loading history...
Bug introduced by
It seems like $y can also be of type double; however, parameter $y of imagettftext() does only seem to accept integer, 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

1005
                imagettftext($im, $this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, $x, /** @scrutinizer ignore-type */ $y, $colorIndex, $fontFile, $strCfg['str']);
Loading history...
1006
                // Calculate offset to apply:
1007
                $wordInf = imagettfbbox($this->compensateFontSizeiBasedOnFreetypeDpi($sF * $strCfg['fontSize']), $angle, GeneralUtility::getFileAbsFileName($strCfg['fontFile']), $strCfg['str']);
1008
                $x += $wordInf[2] - $wordInf[0] + (int)$splitRendering['compX'] + (int)$strCfg['xSpaceAfter'];
1009
                $y += $wordInf[5] - $wordInf[7] - (int)$splitRendering['compY'] - (int)$strCfg['ySpaceAfter'];
1010
            } else {
1011
                debug('cannot read file: ' . $fontFile, self::class . '::ImageTTFTextWrapper()');
1012
            }
1013
        }
1014
    }
1015
1016
    /**
1017
     * Splitting a string for ImageTTFBBox up into an array where each part has its own configuration options.
1018
     *
1019
     * @param string $string UTF-8 string
1020
     * @param array $splitRendering Split-rendering configuration from GIFBUILDER TEXT object.
1021
     * @param int $fontSize Current fontsize
1022
     * @param string $fontFile Current font file
1023
     * @return array Array with input string splitted according to configuration
1024
     */
1025
    public function splitString($string, $splitRendering, $fontSize, $fontFile)
1026
    {
1027
        // Initialize by setting the whole string and default configuration as the first entry.
1028
        $result = [];
1029
        $result[] = [
1030
            'str' => $string,
1031
            'fontSize' => $fontSize,
1032
            'fontFile' => $fontFile
1033
        ];
1034
        // Traverse the split-rendering configuration:
1035
        // Splitting will create more entries in $result with individual configurations.
1036
        if (is_array($splitRendering)) {
1037
            $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($splitRendering);
1038
            // Traverse configured options:
1039
            foreach ($sKeyArray as $key) {
1040
                $cfg = $splitRendering[$key . '.'];
1041
                // Process each type of split rendering keyword:
1042
                switch ((string)$splitRendering[$key]) {
1043
                    case 'highlightWord':
1044
                        if ((string)$cfg['value'] !== '') {
1045
                            $newResult = [];
1046
                            // Traverse the current parts of the result array:
1047
                            foreach ($result as $part) {
1048
                                // Explode the string value by the word value to highlight:
1049
                                $explodedParts = explode($cfg['value'], $part['str']);
1050
                                foreach ($explodedParts as $c => $expValue) {
1051
                                    if ((string)$expValue !== '') {
1052
                                        $newResult[] = array_merge($part, ['str' => $expValue]);
1053
                                    }
1054
                                    if ($c + 1 < count($explodedParts)) {
1055
                                        $newResult[] = [
1056
                                            'str' => $cfg['value'],
1057
                                            'fontSize' => $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1058
                                            'fontFile' => $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1059
                                            'color' => $cfg['color'],
1060
                                            'xSpaceBefore' => $cfg['xSpaceBefore'],
1061
                                            'xSpaceAfter' => $cfg['xSpaceAfter'],
1062
                                            'ySpaceBefore' => $cfg['ySpaceBefore'],
1063
                                            'ySpaceAfter' => $cfg['ySpaceAfter']
1064
                                        ];
1065
                                    }
1066
                                }
1067
                            }
1068
                            // Set the new result as result array:
1069
                            if (!empty($newResult)) {
1070
                                $result = $newResult;
1071
                            }
1072
                        }
1073
                        break;
1074
                    case 'charRange':
1075
                        if ((string)$cfg['value'] !== '') {
1076
                            // Initialize range:
1077
                            $ranges = GeneralUtility::trimExplode(',', $cfg['value'], true);
1078
                            foreach ($ranges as $i => $rangeDef) {
1079
                                $ranges[$i] = GeneralUtility::intExplode('-', $ranges[$i]);
1080
                                if (!isset($ranges[$i][1])) {
1081
                                    $ranges[$i][1] = $ranges[$i][0];
1082
                                }
1083
                            }
1084
                            $newResult = [];
1085
                            // Traverse the current parts of the result array:
1086
                            foreach ($result as $part) {
1087
                                // Initialize:
1088
                                $currentState = -1;
1089
                                $bankAccum = '';
1090
                                // Explode the string value by the word value to highlight:
1091
                                $utf8Chars = $this->csConvObj->utf8_to_numberarray($part['str']);
1092
                                foreach ($utf8Chars as $utfChar) {
1093
                                    // Find number and evaluate position:
1094
                                    $uNumber = (int)$this->csConvObj->utf8CharToUnumber($utfChar);
1095
                                    $inRange = 0;
1096
                                    foreach ($ranges as $rangeDef) {
1097
                                        if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
1098
                                            $inRange = 1;
1099
                                            break;
1100
                                        }
1101
                                    }
1102
                                    if ($currentState == -1) {
1103
                                        $currentState = $inRange;
1104
                                    }
1105
                                    // Initialize first char
1106
                                    // Switch bank:
1107
                                    if ($inRange != $currentState && $uNumber !== 9 && $uNumber !== 10 && $uNumber !== 13 && $uNumber !== 32) {
1108
                                        // Set result:
1109
                                        if ($bankAccum !== '') {
1110
                                            $newResult[] = [
1111
                                                'str' => $bankAccum,
1112
                                                'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1113
                                                'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1114
                                                'color' => $currentState ? $cfg['color'] : '',
1115
                                                'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1116
                                                'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1117
                                                'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1118
                                                'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1119
                                            ];
1120
                                        }
1121
                                        // Initialize new settings:
1122
                                        $currentState = $inRange;
1123
                                        $bankAccum = '';
1124
                                    }
1125
                                    // Add char to bank:
1126
                                    $bankAccum .= $utfChar;
1127
                                }
1128
                                // Set result for FINAL part:
1129
                                if ($bankAccum !== '') {
1130
                                    $newResult[] = [
1131
                                        'str' => $bankAccum,
1132
                                        'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1133
                                        'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1134
                                        'color' => $currentState ? $cfg['color'] : '',
1135
                                        'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1136
                                        'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1137
                                        'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1138
                                        'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1139
                                    ];
1140
                                }
1141
                            }
1142
                            // Set the new result as result array:
1143
                            if (!empty($newResult)) {
1144
                                $result = $newResult;
1145
                            }
1146
                        }
1147
                        break;
1148
                }
1149
            }
1150
        }
1151
        return $result;
1152
    }
1153
1154
    /**
1155
     * Calculates the spacing and wordSpacing values
1156
     *
1157
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1158
     * @param int $scaleFactor TypoScript value from eg $conf['niceText.']['scaleFactor']
1159
     * @return array Array with two keys [0]/[1] being array($spacing,$wordSpacing)
1160
     * @access private
1161
     * @see calcBBox()
1162
     */
1163
    public function calcWordSpacing($conf, $scaleFactor = 1)
1164
    {
1165
        $spacing = (int)$conf['spacing'];
1166
        $wordSpacing = (int)$conf['wordSpacing'];
1167
        $wordSpacing = $wordSpacing ?: $spacing * 2;
1168
        $spacing *= $scaleFactor;
1169
        $wordSpacing *= $scaleFactor;
1170
        return [$spacing, $wordSpacing];
1171
    }
1172
1173
    /**
1174
     * Calculates and returns the niceText.scaleFactor
1175
     *
1176
     * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1177
     * @return int TypoScript value from eg $conf['niceText.']['scaleFactor']
1178
     * @access private
1179
     */
1180
    public function getTextScalFactor($conf)
1181
    {
1182
        if (!$conf['niceText']) {
1183
            $sF = 1;
1184
        } else {
1185
            // NICETEXT::
1186
            $sF = MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
1187
        }
1188
        return $sF;
1189
    }
1190
1191
    /**
1192
     * Renders a regular text and takes care of a possible line break automatically.
1193
     *
1194
     * @param resource $im (See argument for PHP function imageTTFtext())
1195
     * @param int $fontSize (See argument for PHP function imageTTFtext())
1196
     * @param int $angle (See argument for PHP function imageTTFtext())
1197
     * @param int $x (See argument for PHP function imageTTFtext())
1198
     * @param int $y (See argument for PHP function imageTTFtext())
1199
     * @param int $color (See argument for PHP function imageTTFtext())
1200
     * @param string $fontFile (See argument for PHP function imageTTFtext())
1201
     * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
1202
     * @param array $splitRendering Split-rendering configuration
1203
     * @param array $conf The configuration
1204
     * @param int $sF Scale factor
1205
     */
1206
    protected function renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF = 1)
1207
    {
1208
        if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
1209
            $phrase = '';
1210
            $currentWidth = 0;
1211
            $breakWidth = $conf['breakWidth'];
1212
            $breakSpace = $this->getBreakSpace($conf);
1213
            $wordPairs = $this->getWordPairsForLineBreak($string);
1214
            // Iterate through all word pairs:
1215
            foreach ($wordPairs as $index => $wordPair) {
1216
                $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
1217
                if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
1218
                    $currentWidth += $wordWidth;
1219
                    $phrase .= $wordPair;
1220
                } else {
1221
                    // Render the current phrase that is below breakWidth:
1222
                    $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1223
                    // Calculate the news height offset:
1224
                    $y += $breakSpace;
1225
                    // Restart the phrase:
1226
                    $currentWidth = $wordWidth;
1227
                    $phrase = $wordPair;
1228
                }
1229
            }
1230
            // Render the remaining phrase:
1231
            if ($currentWidth) {
1232
                $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1233
            }
1234
        } else {
1235
            $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
1236
        }
1237
    }
1238
1239
    /**
1240
     * Gets the word pairs used for automatic line breaks.
1241
     *
1242
     * @param string $string
1243
     * @return array
1244
     */
1245
    protected function getWordPairsForLineBreak($string)
1246
    {
1247
        $wordPairs = [];
1248
        $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
1249
        $wordsCount = count($wordsArray);
0 ignored issues
show
Bug introduced by
It seems like $wordsArray can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, 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

1249
        $wordsCount = count(/** @scrutinizer ignore-type */ $wordsArray);
Loading history...
1250
        for ($index = 0; $index < $wordsCount; $index += 2) {
1251
            $wordPairs[] = $wordsArray[$index] . $wordsArray[$index + 1];
1252
        }
1253
        return $wordPairs;
1254
    }
1255
1256
    /**
1257
     * Gets the rendered text width
1258
     *
1259
     * @param string $text
1260
     * @param array $conf
1261
     * @return int
1262
     */
1263
    protected function getRenderedTextWidth($text, $conf)
1264
    {
1265
        $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $text, $conf['splitRendering.']);
1266
        if ($conf['angle'] < 0) {
1267
            $pixelWidth = abs($bounds[4] - $bounds[0]);
1268
        } elseif ($conf['angle'] > 0) {
1269
            $pixelWidth = abs($bounds[2] - $bounds[6]);
1270
        } else {
1271
            $pixelWidth = abs($bounds[4] - $bounds[6]);
1272
        }
1273
        return $pixelWidth;
1274
    }
1275
1276
    /**
1277
     * Gets the break space for each new line.
1278
     *
1279
     * @param array $conf TypoScript configuration for the currently rendered object
1280
     * @param array $boundingBox The bounding box the the currently rendered object
1281
     * @return int The break space
1282
     */
1283
    protected function getBreakSpace($conf, array $boundingBox = null)
1284
    {
1285
        if (!isset($boundingBox)) {
1286
            $boundingBox = $this->calcBBox($conf);
1287
            $boundingBox = $boundingBox[2];
1288
        }
1289
        if (isset($conf['breakSpace']) && $conf['breakSpace']) {
1290
            $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
1291
        } else {
1292
            $breakSpace = $boundingBox['lineHeight'];
1293
        }
1294
        return $breakSpace;
1295
    }
1296
1297
    /*********************************************
1298
     *
1299
     * Other GIFBUILDER objects related to TEXT
1300
     *
1301
     *********************************************/
1302
    /**
1303
     * Implements the "OUTLINE" GIFBUILDER object / property for the TEXT object
1304
     *
1305
     * @param resource $im GDlib image pointer
1306
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1307
     * @param array $workArea The current working area coordinates.
1308
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1309
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeText()
1310
     */
1311
    public function makeOutline(&$im, $conf, $workArea, $txtConf)
1312
    {
1313
        $thickness = (int)$conf['thickness'];
1314
        if ($thickness) {
1315
            $txtConf['fontColor'] = $conf['color'];
1316
            $outLineDist = MathUtility::forceIntegerInRange($thickness, 1, 2);
1317
            for ($b = 1; $b <= $outLineDist; $b++) {
1318
                if ($b == 1) {
1319
                    $it = 8;
1320
                } else {
1321
                    $it = 16;
1322
                }
1323
                $outL = $this->circleOffset($b, $it);
1324
                for ($a = 0; $a < $it; $a++) {
1325
                    $this->makeText($im, $txtConf, $this->applyOffset($workArea, $outL[$a]));
1326
                }
1327
            }
1328
        }
1329
    }
1330
1331
    /**
1332
     * Creates some offset values in an array used to simulate a circularly applied outline around TEXT
1333
     *
1334
     * access private
1335
     *
1336
     * @param int $distance Distance
1337
     * @param int $iterations Iterations.
1338
     * @return array
1339
     * @see makeOutline()
1340
     */
1341
    public function circleOffset($distance, $iterations)
1342
    {
1343
        $res = [];
1344
        if ($distance && $iterations) {
1345
            for ($a = 0; $a < $iterations; $a++) {
1346
                $yOff = round(sin((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1347
                if ($yOff) {
1348
                    $yOff = (int)(ceil(abs(($yOff / 100))) * ($yOff / abs($yOff)));
1349
                }
1350
                $xOff = round(cos((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1351
                if ($xOff) {
1352
                    $xOff = (int)(ceil(abs(($xOff / 100))) * ($xOff / abs($xOff)));
1353
                }
1354
                $res[$a] = [$xOff, $yOff];
1355
            }
1356
        }
1357
        return $res;
1358
    }
1359
1360
    /**
1361
     * Implements the "EMBOSS" GIFBUILDER object / property for the TEXT object
1362
     *
1363
     * @param resource $im GDlib image pointer
1364
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1365
     * @param array $workArea The current working area coordinates.
1366
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1367
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeShadow()
1368
     */
1369
    public function makeEmboss(&$im, $conf, $workArea, $txtConf)
1370
    {
1371
        $conf['color'] = $conf['highColor'];
1372
        $this->makeShadow($im, $conf, $workArea, $txtConf);
1373
        $newOffset = GeneralUtility::intExplode(',', $conf['offset']);
1374
        $newOffset[0] *= -1;
1375
        $newOffset[1] *= -1;
1376
        $conf['offset'] = implode(',', $newOffset);
1377
        $conf['color'] = $conf['lowColor'];
1378
        $this->makeShadow($im, $conf, $workArea, $txtConf);
1379
    }
1380
1381
    /**
1382
     * Implements the "SHADOW" GIFBUILDER object / property for the TEXT object
1383
     * The operation involves ImageMagick for combining.
1384
     *
1385
     * @param resource $im GDlib image pointer
1386
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1387
     * @param array $workArea The current working area coordinates.
1388
     * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1389
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeText(), makeEmboss()
1390
     */
1391
    public function makeShadow(&$im, $conf, $workArea, $txtConf)
1392
    {
1393
        $workArea = $this->applyOffset($workArea, GeneralUtility::intExplode(',', $conf['offset']));
1394
        $blurRate = MathUtility::forceIntegerInRange((int)$conf['blur'], 0, 99);
1395
        // No effects if ImageMagick ver. 5+
1396
        if (!$blurRate || !$this->processorEffectsEnabled) {
1397
            $txtConf['fontColor'] = $conf['color'];
1398
            $this->makeText($im, $txtConf, $workArea);
1399
        } else {
1400
            $w = imagesx($im);
1401
            $h = imagesy($im);
1402
            // Area around the blur used for cropping something
1403
            $blurBorder = 3;
1404
            $tmpStr = $this->randomName();
1405
            $fileMenu = $tmpStr . '_menu.' . $this->gifExtension;
1406
            $fileColor = $tmpStr . '_color.' . $this->gifExtension;
1407
            $fileMask = $tmpStr . '_mask.' . $this->gifExtension;
1408
            // BlurColor Image laves
1409
            $blurColImg = imagecreatetruecolor($w, $h);
1410
            $bcols = $this->convertColor($conf['color']);
1411
            $Bcolor = imagecolorallocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
1412
            imagefilledrectangle($blurColImg, 0, 0, $w, $h, $Bcolor);
1413
            $this->ImageWrite($blurColImg, $fileColor);
1414
            imagedestroy($blurColImg);
1415
            // The mask is made: BlurTextImage
1416
            $blurTextImg = imagecreatetruecolor($w + $blurBorder * 2, $h + $blurBorder * 2);
1417
            // Black background
1418
            $Bcolor = imagecolorallocate($blurTextImg, 0, 0, 0);
1419
            imagefilledrectangle($blurTextImg, 0, 0, $w + $blurBorder * 2, $h + $blurBorder * 2, $Bcolor);
1420
            $txtConf['fontColor'] = 'white';
1421
            $blurBordArr = [$blurBorder, $blurBorder];
1422
            $this->makeText($blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
1423
            // Dump to temporary file
1424
            $this->ImageWrite($blurTextImg, $fileMask);
1425
            // Destroy
1426
            imagedestroy($blurTextImg);
1427
            $command = $this->v5_blur($blurRate + 1);
1428
            $this->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
1429
            // The mask is loaded again
1430
            $blurTextImg_tmp = $this->imageCreateFromFile($fileMask);
1431
            // If nothing went wrong we continue with the blurred mask
1432
            if ($blurTextImg_tmp) {
1433
                // Cropping the border from the mask
1434
                $blurTextImg = imagecreatetruecolor($w, $h);
1435
                $this->imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, $w, $h, $w, $h);
1436
                // Destroy the temporary mask
1437
                imagedestroy($blurTextImg_tmp);
1438
                // Adjust the mask
1439
                $intensity = 40;
1440
                if ($conf['intensity']) {
1441
                    $intensity = MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
1442
                }
1443
                $intensity = ceil(255 - $intensity / 100 * 255);
1444
                $this->inputLevels($blurTextImg, 0, $intensity);
1445
                $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 0, 100);
1446
                if ($opacity && $opacity < 100) {
1447
                    $high = ceil(255 * $opacity / 100);
1448
                    // Reducing levels as the opacity demands
1449
                    $this->outputLevels($blurTextImg, 0, $high);
1450
                }
1451
                // Dump the mask again
1452
                $this->ImageWrite($blurTextImg, $fileMask);
1453
                // Destroy the mask
1454
                imagedestroy($blurTextImg);
1455
                // The pictures are combined
1456
                // The main pictures is saved temporarily
1457
                $this->ImageWrite($im, $fileMenu);
1458
                $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
1459
                // The main image is loaded again...
1460
                $backIm = $this->imageCreateFromFile($fileMenu);
1461
                // ... and if nothing went wrong we load it onto the old one.
1462
                if ($backIm) {
1463
                    if (!$this->saveAlphaLayer) {
1464
                        imagecolortransparent($backIm, -1);
1465
                    }
1466
                    $im = $backIm;
1467
                }
1468
            }
1469
            // Deleting temporary files;
1470
            unlink($fileMenu);
1471
            unlink($fileColor);
1472
            unlink($fileMask);
1473
        }
1474
    }
1475
1476
    /****************************
1477
     *
1478
     * Other GIFBUILDER objects
1479
     *
1480
     ****************************/
1481
    /**
1482
     * Implements the "BOX" GIFBUILDER object
1483
     *
1484
     * @param resource $im GDlib image pointer
1485
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1486
     * @param array $workArea The current working area coordinates.
1487
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1488
     */
1489
    public function makeBox(&$im, $conf, $workArea)
1490
    {
1491
        $cords = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1492
        $conf['offset'] = $cords[0] . ',' . $cords[1];
1493
        $cords = $this->objPosition($conf, $workArea, [$cords[2], $cords[3]]);
1494
        $cols = $this->convertColor($conf['color']);
1495
        $opacity = 0;
1496
        if (isset($conf['opacity'])) {
1497
            // conversion:
1498
            // PHP 0 = opaque, 127 = transparent
1499
            // TYPO3 100 = opaque, 0 = transparent
1500
            $opacity = MathUtility::forceIntegerInRange((int)$conf['opacity'], 1, 100, 1);
1501
            $opacity = abs($opacity - 100);
1502
            $opacity = round(127 * $opacity / 100);
1503
        }
1504
        $tmpColor = imagecolorallocatealpha($im, $cols[0], $cols[1], $cols[2], $opacity);
0 ignored issues
show
Bug introduced by
It seems like $opacity can also be of type double; however, parameter $alpha of imagecolorallocatealpha() does only seem to accept integer, 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

1504
        $tmpColor = imagecolorallocatealpha($im, $cols[0], $cols[1], $cols[2], /** @scrutinizer ignore-type */ $opacity);
Loading history...
1505
        imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1506
    }
1507
1508
    /**
1509
     * Implements the "Ellipse" GIFBUILDER object
1510
     * Example Typoscript:
1511
     * file  =  GIFBUILDER
1512
     * file  {
1513
     * XY  =  200,200
1514
     * format  =  jpg
1515
     * quality  =  100
1516
     * 10  =  ELLIPSE
1517
     * 10.dimensions  =  100,100,50,50
1518
     * 10.color  =  red
1519
     *
1520
     * $workArea = X,Y
1521
     * $conf['dimensions'] = offset x, offset y, width of ellipse, height of ellipse
1522
     *
1523
     * @param resource $im GDlib image pointer
1524
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1525
     * @param array $workArea The current working area coordinates.
1526
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1527
     */
1528
    public function makeEllipse(&$im, array $conf, array $workArea)
1529
    {
1530
        $ellipseConfiguration = GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1531
        // Ellipse offset inside workArea (x/y)
1532
        $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1533
        // @see objPosition
1534
        $imageCoordinates = $this->objPosition($conf, $workArea, [$ellipseConfiguration[2], $ellipseConfiguration[3]]);
1535
        $color = $this->convertColor($conf['color']);
1536
        $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1537
        imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1538
    }
1539
1540
    /**
1541
     * Implements the "EFFECT" GIFBUILDER object
1542
     * The operation involves ImageMagick for applying effects
1543
     *
1544
     * @param resource $im GDlib image pointer
1545
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1546
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), applyImageMagickToPHPGif()
1547
     */
1548
    public function makeEffect(&$im, $conf)
1549
    {
1550
        $commands = $this->IMparams($conf['value']);
1551
        if ($commands) {
1552
            $this->applyImageMagickToPHPGif($im, $commands);
1553
        }
1554
    }
1555
1556
    /**
1557
     * Creating ImageMagick parameters from TypoScript property
1558
     *
1559
     * @param string $setup A string with effect keywords=value pairs separated by "|
1560
     * @return string ImageMagick prepared parameters.
1561
     * @access private
1562
     * @see makeEffect()
1563
     */
1564
    public function IMparams($setup)
1565
    {
1566
        if (!trim($setup)) {
1567
            return '';
1568
        }
1569
        $effects = explode('|', $setup);
1570
        $commands = '';
1571
        foreach ($effects as $val) {
1572
            $pairs = explode('=', $val, 2);
1573
            $value = trim($pairs[1]);
1574
            $effect = strtolower(trim($pairs[0]));
1575
            switch ($effect) {
1576
                case 'gamma':
1577
                    $commands .= ' -gamma ' . (float)$value;
1578
                    break;
1579
                case 'blur':
1580
                    if ($this->processorEffectsEnabled) {
1581
                        $commands .= $this->v5_blur($value);
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type integer expected by parameter $factor of TYPO3\CMS\Core\Imaging\G...calFunctions::v5_blur(). ( Ignorable by Annotation )

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

1581
                        $commands .= $this->v5_blur(/** @scrutinizer ignore-type */ $value);
Loading history...
1582
                    }
1583
                    break;
1584
                case 'sharpen':
1585
                    if ($this->processorEffectsEnabled) {
1586
                        $commands .= $this->v5_sharpen($value);
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type integer expected by parameter $factor of TYPO3\CMS\Core\Imaging\G...Functions::v5_sharpen(). ( Ignorable by Annotation )

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

1586
                        $commands .= $this->v5_sharpen(/** @scrutinizer ignore-type */ $value);
Loading history...
1587
                    }
1588
                    break;
1589
                case 'rotate':
1590
                    $commands .= ' -rotate ' . MathUtility::forceIntegerInRange($value, 0, 360);
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type integer expected by parameter $theInt of TYPO3\CMS\Core\Utility\M...::forceIntegerInRange(). ( Ignorable by Annotation )

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

1590
                    $commands .= ' -rotate ' . MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ $value, 0, 360);
Loading history...
1591
                    break;
1592
                case 'solarize':
1593
                    $commands .= ' -solarize ' . MathUtility::forceIntegerInRange($value, 0, 99);
1594
                    break;
1595
                case 'swirl':
1596
                    $commands .= ' -swirl ' . MathUtility::forceIntegerInRange($value, 0, 1000);
1597
                    break;
1598
                case 'wave':
1599
                    $params = GeneralUtility::intExplode(',', $value);
1600
                    $commands .= ' -wave ' . MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . MathUtility::forceIntegerInRange($params[1], 0, 99);
1601
                    break;
1602
                case 'charcoal':
1603
                    $commands .= ' -charcoal ' . MathUtility::forceIntegerInRange($value, 0, 100);
1604
                    break;
1605
                case 'gray':
1606
                    $commands .= ' -colorspace GRAY';
1607
                    break;
1608
                case 'edge':
1609
                    $commands .= ' -edge ' . MathUtility::forceIntegerInRange($value, 0, 99);
1610
                    break;
1611
                case 'emboss':
1612
                    $commands .= ' -emboss';
1613
                    break;
1614
                case 'flip':
1615
                    $commands .= ' -flip';
1616
                    break;
1617
                case 'flop':
1618
                    $commands .= ' -flop';
1619
                    break;
1620
                case 'colors':
1621
                    $commands .= ' -colors ' . MathUtility::forceIntegerInRange($value, 2, 255);
1622
                    break;
1623
                case 'shear':
1624
                    $commands .= ' -shear ' . MathUtility::forceIntegerInRange($value, -90, 90);
1625
                    break;
1626
                case 'invert':
1627
                    $commands .= ' -negate';
1628
                    break;
1629
            }
1630
        }
1631
        return $commands;
1632
    }
1633
1634
    /**
1635
     * Implements the "ADJUST" GIFBUILDER object
1636
     *
1637
     * @param resource $im GDlib image pointer
1638
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1639
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), autoLevels(), outputLevels(), inputLevels()
1640
     */
1641
    public function adjust(&$im, $conf)
1642
    {
1643
        $setup = $conf['value'];
1644
        if (!trim($setup)) {
1645
            return;
1646
        }
1647
        $effects = explode('|', $setup);
1648
        foreach ($effects as $val) {
1649
            $pairs = explode('=', $val, 2);
1650
            $value = trim($pairs[1]);
1651
            $effect = strtolower(trim($pairs[0]));
1652
            switch ($effect) {
1653
                case 'inputlevels':
1654
                    // low,high
1655
                    $params = GeneralUtility::intExplode(',', $value);
1656
                    $this->inputLevels($im, $params[0], $params[1]);
1657
                    break;
1658
                case 'outputlevels':
1659
                    $params = GeneralUtility::intExplode(',', $value);
1660
                    $this->outputLevels($im, $params[0], $params[1]);
1661
                    break;
1662
                case 'autolevels':
1663
                    $this->autolevels($im);
1664
                    break;
1665
            }
1666
        }
1667
    }
1668
1669
    /**
1670
     * Implements the "CROP" GIFBUILDER object
1671
     *
1672
     * @param resource $im GDlib image pointer
1673
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1674
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1675
     */
1676
    public function crop(&$im, $conf)
1677
    {
1678
        // Clears workArea to total image
1679
        $this->setWorkArea('');
1680
        $cords = GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1681
        $conf['offset'] = $cords[0] . ',' . $cords[1];
1682
        $cords = $this->objPosition($conf, $this->workArea, [$cords[2], $cords[3]]);
1683
        $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1684
        $cols = $this->convertColor($conf['backColor'] ?: $this->setup['backColor']);
1685
        $Bcolor = imagecolorallocate($newIm, $cols[0], $cols[1], $cols[2]);
1686
        imagefilledrectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1687
        $newConf = [];
1688
        $workArea = [0, 0, $cords[2], $cords[3]];
1689
        if ($cords[0] < 0) {
1690
            $workArea[0] = abs($cords[0]);
1691
        } else {
1692
            $newConf['offset'] = -$cords[0];
1693
        }
1694
        if ($cords[1] < 0) {
1695
            $workArea[1] = abs($cords[1]);
1696
        } else {
1697
            $newConf['offset'] .= ',' . -$cords[1];
1698
        }
1699
        $this->copyGifOntoGif($newIm, $im, $newConf, $workArea);
1700
        $im = $newIm;
1701
        $this->w = imagesx($im);
1702
        $this->h = imagesy($im);
1703
        // Clears workArea to total image
1704
        $this->setWorkArea('');
1705
    }
1706
1707
    /**
1708
     * Implements the "SCALE" GIFBUILDER object
1709
     *
1710
     * @param resource $im GDlib image pointer
1711
     * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1712
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1713
     */
1714
    public function scale(&$im, $conf)
1715
    {
1716
        if ($conf['width'] || $conf['height'] || $conf['params']) {
1717
            $tmpStr = $this->randomName();
1718
            $theFile = $tmpStr . '.' . $this->gifExtension;
1719
            $this->ImageWrite($im, $theFile);
1720
            $theNewFile = $this->imageMagickConvert($theFile, $this->gifExtension, $conf['width'], $conf['height'], $conf['params']);
1721
            $tmpImg = $this->imageCreateFromFile($theNewFile[3]);
1722
            if ($tmpImg) {
1723
                imagedestroy($im);
1724
                $im = $tmpImg;
1725
                $this->w = imagesx($im);
1726
                $this->h = imagesy($im);
1727
                // Clears workArea to total image
1728
                $this->setWorkArea('');
1729
            }
1730
            unlink($theFile);
1731
            if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1732
                unlink($theNewFile[3]);
1733
            }
1734
        }
1735
    }
1736
1737
    /**
1738
     * Implements the "WORKAREA" GIFBUILDER object when setting it
1739
     * Setting internal working area boundaries (->workArea)
1740
     *
1741
     * @param string $workArea Working area dimensions, comma separated
1742
     * @access private
1743
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1744
     */
1745
    public function setWorkArea($workArea)
1746
    {
1747
        $this->workArea = GeneralUtility::intExplode(',', $workArea);
1748
        $this->workArea = $this->applyOffset($this->workArea, $this->OFFSET);
1749
        if (!$this->workArea[2]) {
1750
            $this->workArea[2] = $this->w;
1751
        }
1752
        if (!$this->workArea[3]) {
1753
            $this->workArea[3] = $this->h;
1754
        }
1755
    }
1756
1757
    /*************************
1758
     *
1759
     * Adjustment functions
1760
     *
1761
     ************************/
1762
    /**
1763
     * Apply auto-levels to input image pointer
1764
     *
1765
     * @param resource $im GDlib Image Pointer
1766
     */
1767
    public function autolevels(&$im)
1768
    {
1769
        $totalCols = imagecolorstotal($im);
1770
        $grayArr = [];
1771
        for ($c = 0; $c < $totalCols; $c++) {
1772
            $cols = imagecolorsforindex($im, $c);
1773
            $grayArr[] = round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
1774
        }
1775
        $min = min($grayArr);
1776
        $max = max($grayArr);
1777
        $delta = $max - $min;
1778
        if ($delta) {
1779
            for ($c = 0; $c < $totalCols; $c++) {
1780
                $cols = imagecolorsforindex($im, $c);
1781
                $cols['red'] = floor(($cols['red'] - $min) / $delta * 255);
1782
                $cols['green'] = floor(($cols['green'] - $min) / $delta * 255);
1783
                $cols['blue'] = floor(($cols['blue'] - $min) / $delta * 255);
1784
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
0 ignored issues
show
Bug introduced by
$cols['red'] of type double is incompatible with the type integer expected by parameter $red of imagecolorset(). ( Ignorable by Annotation )

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

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

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

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

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

1784
                imagecolorset($im, $c, $cols['red'], /** @scrutinizer ignore-type */ $cols['green'], $cols['blue']);
Loading history...
1785
            }
1786
        }
1787
    }
1788
1789
    /**
1790
     * Apply output levels to input image pointer (decreasing contrast)
1791
     *
1792
     * @param resource $im GDlib Image Pointer
1793
     * @param int $low The "low" value (close to 0)
1794
     * @param int $high The "high" value (close to 255)
1795
     * @param bool $swap If swap, then low and high are swapped. (Useful for negated masks...)
1796
     */
1797
    public function outputLevels(&$im, $low, $high, $swap = false)
1798
    {
1799
        if ($low < $high) {
1800
            $low = MathUtility::forceIntegerInRange($low, 0, 255);
1801
            $high = MathUtility::forceIntegerInRange($high, 0, 255);
1802
            if ($swap) {
1803
                $temp = $low;
1804
                $low = 255 - $high;
1805
                $high = 255 - $temp;
1806
            }
1807
            $delta = $high - $low;
1808
            $totalCols = imagecolorstotal($im);
1809
            for ($c = 0; $c < $totalCols; $c++) {
1810
                $cols = imagecolorsforindex($im, $c);
1811
                $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
1812
                $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
1813
                $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
1814
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
0 ignored issues
show
Bug introduced by
$cols['blue'] of type double is incompatible with the type integer expected by parameter $blue of imagecolorset(). ( Ignorable by Annotation )

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

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

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

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

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

1814
                imagecolorset($im, $c, /** @scrutinizer ignore-type */ $cols['red'], $cols['green'], $cols['blue']);
Loading history...
1815
            }
1816
        }
1817
    }
1818
1819
    /**
1820
     * Apply input levels to input image pointer (increasing contrast)
1821
     *
1822
     * @param resource $im GDlib Image Pointer
1823
     * @param int $low The "low" value (close to 0)
1824
     * @param int $high The "high" value (close to 255)
1825
     */
1826
    public function inputLevels(&$im, $low, $high)
1827
    {
1828
        if ($low < $high) {
1829
            $low = MathUtility::forceIntegerInRange($low, 0, 255);
1830
            $high = MathUtility::forceIntegerInRange($high, 0, 255);
1831
            $delta = $high - $low;
1832
            $totalCols = imagecolorstotal($im);
1833
            for ($c = 0; $c < $totalCols; $c++) {
1834
                $cols = imagecolorsforindex($im, $c);
1835
                $cols['red'] = MathUtility::forceIntegerInRange(($cols['red'] - $low) / $delta * 255, 0, 255);
1836
                $cols['green'] = MathUtility::forceIntegerInRange(($cols['green'] - $low) / $delta * 255, 0, 255);
1837
                $cols['blue'] = MathUtility::forceIntegerInRange(($cols['blue'] - $low) / $delta * 255, 0, 255);
1838
                imagecolorset($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1839
            }
1840
        }
1841
    }
1842
1843
    /**
1844
     * Reduce colors in image using IM and create a palette based image if possible (<=256 colors)
1845
     *
1846
     * @param string $file Image file to reduce
1847
     * @param int $cols Number of colors to reduce the image to.
1848
     * @return string Reduced file
1849
     */
1850
    public function IMreduceColors($file, $cols)
1851
    {
1852
        $fI = GeneralUtility::split_fileref($file);
1853
        $ext = strtolower($fI['fileext']);
1854
        $result = $this->randomName() . '.' . $ext;
1855
        $reduce = MathUtility::forceIntegerInRange($cols, 0, $ext === 'gif' ? 256 : $this->truecolorColors, 0);
1856
        if ($reduce > 0) {
1857
            $params = ' -colors ' . $reduce;
1858
            if ($reduce <= 256) {
1859
                $params .= ' -type Palette';
1860
            }
1861
            $prefix = $ext === 'png' && $reduce <= 256 ? 'png8:' : '';
1862
            $this->imageMagickExec($file, $prefix . $result, $params);
1863
            if ($result) {
1864
                return $result;
1865
            }
1866
        }
1867
        return '';
1868
    }
1869
1870
    /*********************************
1871
     *
1872
     * GIFBUILDER Helper functions
1873
     *
1874
     *********************************/
1875
    /**
1876
     * Returns the IM command for sharpening with ImageMagick 5
1877
     * Uses $this->im5fx_sharpenSteps for translation of the factor to an actual command.
1878
     *
1879
     * @param int $factor The sharpening factor, 0-100 (effectively in 10 steps)
1880
     * @return string The sharpening command, eg. " -sharpen 3x4
1881
     * @see makeText(), IMparams(), v5_blur()
1882
     */
1883
    public function v5_sharpen($factor)
1884
    {
1885
        $factor = MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
0 ignored issues
show
Bug introduced by
ceil($factor / 10) of type double is incompatible with the type integer expected by parameter $theInt of TYPO3\CMS\Core\Utility\M...::forceIntegerInRange(). ( Ignorable by Annotation )

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

1885
        $factor = MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ ceil($factor / 10), 0, 10);
Loading history...
1886
        $sharpenArr = explode(',', ',' . $this->im5fx_sharpenSteps);
1887
        $sharpenF = trim($sharpenArr[$factor]);
1888
        if ($sharpenF) {
1889
            return ' -sharpen ' . $sharpenF;
1890
        }
1891
        return '';
1892
    }
1893
1894
    /**
1895
     * Returns the IM command for blurring with ImageMagick 5.
1896
     * Uses $this->im5fx_blurSteps for translation of the factor to an actual command.
1897
     *
1898
     * @param int $factor The blurring factor, 0-100 (effectively in 10 steps)
1899
     * @return string The blurring command, eg. " -blur 3x4
1900
     * @see makeText(), IMparams(), v5_sharpen()
1901
     */
1902
    public function v5_blur($factor)
1903
    {
1904
        $factor = MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
0 ignored issues
show
Bug introduced by
ceil($factor / 10) of type double is incompatible with the type integer expected by parameter $theInt of TYPO3\CMS\Core\Utility\M...::forceIntegerInRange(). ( Ignorable by Annotation )

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

1904
        $factor = MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ ceil($factor / 10), 0, 10);
Loading history...
1905
        $blurArr = explode(',', ',' . $this->im5fx_blurSteps);
1906
        $blurF = trim($blurArr[$factor]);
1907
        if ($blurF) {
1908
            return ' -blur ' . $blurF;
1909
        }
1910
        return '';
1911
    }
1912
1913
    /**
1914
     * Returns a random filename prefixed with "temp_" and then 32 char md5 hash (without extension).
1915
     * 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.
1916
     *
1917
     * @return string
1918
     */
1919
    public function randomName()
1920
    {
1921
        GeneralUtility::mkdir_deep(PATH_site . 'typo3temp/var/transient/');
1922
        return PATH_site . 'typo3temp/var/transient/' . md5(uniqid('', true));
1923
    }
1924
1925
    /**
1926
     * Applies offset value to coordinated in $cords.
1927
     * Basically the value of key 0/1 of $OFFSET is added to keys 0/1 of $cords
1928
     *
1929
     * @param array $cords Integer coordinates in key 0/1
1930
     * @param array $OFFSET Offset values in key 0/1
1931
     * @return array Modified $cords array
1932
     */
1933
    public function applyOffset($cords, $OFFSET)
1934
    {
1935
        $cords[0] = (int)$cords[0] + (int)$OFFSET[0];
1936
        $cords[1] = (int)$cords[1] + (int)$OFFSET[1];
1937
        return $cords;
1938
    }
1939
1940
    /**
1941
     * Converts a "HTML-color" TypoScript datatype to RGB-values.
1942
     * Default is 0,0,0
1943
     *
1944
     * @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
1945
     * @return array RGB values in key 0/1/2 of the array
1946
     */
1947
    public function convertColor($string)
1948
    {
1949
        $col = [];
1950
        $cParts = explode(':', $string, 2);
1951
        // Finding the RGB definitions of the color:
1952
        $string = $cParts[0];
1953
        if (strstr($string, '#')) {
1954
            $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string);
1955
            $col[] = hexdec(substr($string, 0, 2));
1956
            $col[] = hexdec(substr($string, 2, 2));
1957
            $col[] = hexdec(substr($string, 4, 2));
1958
        } elseif (strstr($string, ',')) {
1959
            $string = preg_replace('/[^,0-9]*/', '', $string);
1960
            $strArr = explode(',', $string);
1961
            $col[] = (int)$strArr[0];
1962
            $col[] = (int)$strArr[1];
1963
            $col[] = (int)$strArr[2];
1964
        } else {
1965
            $string = strtolower(trim($string));
1966
            if ($this->colMap[$string]) {
1967
                $col = $this->colMap[$string];
1968
            } else {
1969
                $col = [0, 0, 0];
1970
            }
1971
        }
1972
        // ... and possibly recalculating the value
1973
        if (trim($cParts[1])) {
1974
            $cParts[1] = trim($cParts[1]);
1975
            if ($cParts[1][0] === '*') {
1976
                $val = (float)substr($cParts[1], 1);
1977
                $col[0] = MathUtility::forceIntegerInRange($col[0] * $val, 0, 255);
0 ignored issues
show
Bug introduced by
$col[0] * $val of type double is incompatible with the type integer expected by parameter $theInt of TYPO3\CMS\Core\Utility\M...::forceIntegerInRange(). ( Ignorable by Annotation )

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

1977
                $col[0] = MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ $col[0] * $val, 0, 255);
Loading history...
1978
                $col[1] = MathUtility::forceIntegerInRange($col[1] * $val, 0, 255);
1979
                $col[2] = MathUtility::forceIntegerInRange($col[2] * $val, 0, 255);
1980
            } else {
1981
                $val = (int)$cParts[1];
1982
                $col[0] = MathUtility::forceIntegerInRange($col[0] + $val, 0, 255);
1983
                $col[1] = MathUtility::forceIntegerInRange($col[1] + $val, 0, 255);
1984
                $col[2] = MathUtility::forceIntegerInRange($col[2] + $val, 0, 255);
1985
            }
1986
        }
1987
        return $col;
1988
    }
1989
1990
    /**
1991
     * 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
1992
     *
1993
     * @param array $conf TypoScript configuration for a GIFBUILDER object
1994
     * @param array $workArea Workarea definition
1995
     * @param array $BB BB (Bounding box) array. Not just used for TEXT objects but also for others
1996
     * @return array [0]=x, [1]=y, [2]=w, [3]=h
1997
     * @access private
1998
     * @see copyGifOntoGif(), makeBox(), crop()
1999
     */
2000
    public function objPosition($conf, $workArea, $BB)
2001
    {
2002
        // offset, align, valign, workarea
2003
        $result = [];
2004
        $result[2] = $BB[0];
2005
        $result[3] = $BB[1];
2006
        $w = $workArea[2];
2007
        $h = $workArea[3];
2008
        $align = explode(',', $conf['align']);
2009
        $align[0] = strtolower(substr(trim($align[0]), 0, 1));
2010
        $align[1] = strtolower(substr(trim($align[1]), 0, 1));
2011
        switch ($align[0]) {
2012
            case 'r':
2013
                $result[0] = $w - $result[2];
2014
                break;
2015
            case 'c':
2016
                $result[0] = round(($w - $result[2]) / 2);
2017
                break;
2018
            default:
2019
                $result[0] = 0;
2020
        }
2021
        switch ($align[1]) {
2022
            case 'b':
2023
                // y pos
2024
                $result[1] = $h - $result[3];
2025
                break;
2026
            case 'c':
2027
                $result[1] = round(($h - $result[3]) / 2);
2028
                break;
2029
            default:
2030
                $result[1] = 0;
2031
        }
2032
        $result = $this->applyOffset($result, GeneralUtility::intExplode(',', $conf['offset']));
2033
        $result = $this->applyOffset($result, $workArea);
2034
        return $result;
2035
    }
2036
2037
    /***********************************
2038
     *
2039
     * Scaling, Dimensions of images
2040
     *
2041
     ***********************************/
2042
    /**
2043
     * Converts $imagefile to another file in temp-dir of type $newExt (extension).
2044
     *
2045
     * @param string $imagefile The image filepath
2046
     * @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.
2047
     * @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
2048
     * @param string $h Height. See $w
2049
     * @param string $params Additional ImageMagick parameters.
2050
     * @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...
2051
     * @param array $options An array with options passed to getImageScale (see this function).
2052
     * @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 messures etc and is of course not rendered to a new, temporary file in typo3temp/. But this option will force it to.
2053
     * @return array|null [0]/[1] is w/h, [2] is file extension and [3] is the filename.
2054
     * @see getImageScale(), typo3/show_item.php, fileList_ext::renderImage(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource(), SC_tslib_showpic::show(), maskImageOntoImage(), copyImageOntoImage(), scale()
2055
     */
2056
    public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $params = '', $frame = '', $options = [], $mustCreate = false)
2057
    {
2058
        if ($this->NO_IMAGE_MAGICK) {
2059
            // Returning file info right away
2060
            return $this->getImageDimensions($imagefile);
2061
        }
2062
        $info = $this->getImageDimensions($imagefile);
2063
        if (!$info) {
2064
            return null;
2065
        }
2066
2067
        $newExt = strtolower(trim($newExt));
2068
        // If no extension is given the original extension is used
2069
        if (!$newExt) {
2070
            $newExt = $info[2];
2071
        }
2072
        if ($newExt === 'web') {
2073
            if (in_array($info[2], $this->webImageExt, true)) {
2074
                $newExt = $info[2];
2075
            } else {
2076
                $newExt = $this->gif_or_jpg($info[2], $info[0], $info[1]);
2077
                if (!$params) {
2078
                    $params = $this->cmds[$newExt];
2079
                }
2080
            }
2081
        }
2082
        if (!in_array($newExt, $this->imageFileExt, true)) {
2083
            return null;
2084
        }
2085
2086
        $data = $this->getImageScale($info, $w, $h, $options);
0 ignored issues
show
Bug introduced by
$w of type string is incompatible with the type integer expected by parameter $w of TYPO3\CMS\Core\Imaging\G...ctions::getImageScale(). ( Ignorable by Annotation )

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

2086
        $data = $this->getImageScale($info, /** @scrutinizer ignore-type */ $w, $h, $options);
Loading history...
Bug introduced by
$h of type string is incompatible with the type integer expected by parameter $h of TYPO3\CMS\Core\Imaging\G...ctions::getImageScale(). ( Ignorable by Annotation )

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

2086
        $data = $this->getImageScale($info, $w, /** @scrutinizer ignore-type */ $h, $options);
Loading history...
2087
        $w = $data['origW'];
2088
        $h = $data['origH'];
2089
        // If no conversion should be performed
2090
        // this flag is TRUE if the width / height does NOT dictate
2091
        // the image to be scaled!! (that is if no width / height is
2092
        // given or if the destination w/h matches the original image
2093
        // dimensions or if the option to not scale the image is set)
2094
        $noScale = !$w && !$h || $data[0] == $info[0] && $data[1] == $info[1] || !empty($options['noScale']);
2095
        if ($noScale && !$data['crs'] && !$params && !$frame && $newExt == $info[2] && !$mustCreate) {
2096
            // Set the new width and height before returning,
2097
            // if the noScale option is set
2098
            if (!empty($options['noScale'])) {
2099
                $info[0] = $data[0];
2100
                $info[1] = $data[1];
2101
            }
2102
            $info[3] = $imagefile;
2103
            return $info;
2104
        }
2105
        $info[0] = $data[0];
2106
        $info[1] = $data[1];
2107
        $frame = $this->addFrameSelection ? (int)$frame : '';
2108
        if (!$params) {
2109
            $params = $this->cmds[$newExt];
2110
        }
2111
        // Cropscaling:
2112
        if ($data['crs']) {
2113
            if (!$data['origW']) {
2114
                $data['origW'] = $data[0];
2115
            }
2116
            if (!$data['origH']) {
2117
                $data['origH'] = $data[1];
2118
            }
2119
            $offsetX = (int)(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
2120
            $offsetY = (int)(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
2121
            $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . '! ';
2122
        }
2123
        $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
2124
        $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
2125
        if ($this->alternativeOutputKey) {
2126
            $theOutputName = GeneralUtility::shortMD5($command . $cropscale . basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
2127
        } else {
2128
            $theOutputName = GeneralUtility::shortMD5($command . $cropscale . $imagefile . filemtime($imagefile) . '[' . $frame . ']');
2129
        }
2130
        if ($this->imageMagickConvert_forceFileNameBody) {
2131
            $theOutputName = $this->imageMagickConvert_forceFileNameBody;
2132
            $this->imageMagickConvert_forceFileNameBody = '';
2133
        }
2134
        // Making the temporary filename:
2135
        GeneralUtility::mkdir_deep(PATH_site . 'typo3temp/assets/images/');
2136
        $output = PATH_site . 'typo3temp/assets/images/' . $this->filenamePrefix . $theOutputName . '.' . $newExt;
2137
        if ($this->dontCheckForExistingTempFile || !file_exists($output)) {
2138
            $this->imageMagickExec($imagefile, $output, $command, $frame);
0 ignored issues
show
Bug introduced by
It seems like $frame can also be of type string; however, parameter $frame of TYPO3\CMS\Core\Imaging\G...ions::imageMagickExec() does only seem to accept integer, 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

2138
            $this->imageMagickExec($imagefile, $output, $command, /** @scrutinizer ignore-type */ $frame);
Loading history...
2139
        }
2140
        if (file_exists($output)) {
2141
            $info[3] = $output;
2142
            $info[2] = $newExt;
2143
            // params might change some image data!
2144
            if ($params) {
2145
                $info = $this->getImageDimensions($info[3]);
2146
            }
2147
            if ($info[2] == $this->gifExtension && !$this->dontCompress) {
2148
                // Compress with IM (lzw) or GD (rle)  (Workaround for the absence of lzw-compression in GD)
2149
                self::gifCompress($info[3], '');
2150
            }
2151
            return $info;
2152
        }
2153
        return null;
2154
    }
2155
2156
    /**
2157
     * Gets the input image dimensions.
2158
     *
2159
     * @param string $imageFile The image filepath
2160
     * @return array|null Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2161
     * @see imageMagickConvert(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
2162
     */
2163
    public function getImageDimensions($imageFile)
2164
    {
2165
        preg_match('/([^\\.]*)$/', $imageFile, $reg);
2166
        if (file_exists($imageFile) && in_array(strtolower($reg[0]), $this->imageFileExt, true)) {
2167
            if ($returnArr = $this->getCachedImageDimensions($imageFile)) {
2168
                return $returnArr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $returnArr also could return the type true which is incompatible with the documented return type null|array.
Loading history...
2169
            }
2170
            if ($temp = @getimagesize($imageFile)) {
2171
                $returnArr = [$temp[0], $temp[1], strtolower($reg[0]), $imageFile];
2172
            } else {
2173
                $returnArr = $this->imageMagickIdentify($imageFile);
2174
            }
2175
            if ($returnArr) {
2176
                $this->cacheImageDimensions($returnArr);
2177
                return $returnArr;
2178
            }
2179
        }
2180
        return null;
2181
    }
2182
2183
    /**
2184
     * Caches the result of the getImageDimensions function into the database. Does not check if the file exists.
2185
     *
2186
     * @param array $identifyResult Result of the getImageDimensions function
2187
     *
2188
     * @return bool always TRUE
2189
     */
2190
    public function cacheImageDimensions(array $identifyResult)
2191
    {
2192
        $filePath = $identifyResult[3];
2193
        $statusHash = $this->generateStatusHashForImageFile($filePath);
2194
        $identifier = $this->generateCacheKeyForImageFile($filePath);
2195
2196
        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
2197
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_imagesizes');
2198
        $imageDimensions = [
2199
            'hash'        => $statusHash,
2200
            'imagewidth'  => $identifyResult[0],
2201
            'imageheight' => $identifyResult[1],
2202
        ];
2203
        $cache->set($identifier, $imageDimensions);
2204
2205
        return true;
2206
    }
2207
2208
    /**
2209
     * Fetches the cached image dimensions from the cache. Does not check if the image file exists.
2210
     *
2211
     * @param string $filePath Image file path, relative to PATH_site
2212
     *
2213
     * @return array|bool an array where [0]/[1] is w/h, [2] is extension and [3] is the file name,
2214
     *                    or FALSE for a cache miss
2215
     */
2216
    public function getCachedImageDimensions($filePath)
2217
    {
2218
        $statusHash = $this->generateStatusHashForImageFile($filePath);
2219
        $identifier = $this->generateCacheKeyForImageFile($filePath);
2220
        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
2221
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_imagesizes');
2222
        $cachedImageDimensions = $cache->get($identifier);
2223
        if (!isset($cachedImageDimensions['hash'])) {
2224
            return false;
2225
        }
2226
2227
        if ($cachedImageDimensions['hash'] !== $statusHash) {
2228
            // The file has changed. Delete the cache entry.
2229
            $cache->remove($identifier);
2230
            $result = false;
2231
        } else {
2232
            preg_match('/([^\\.]*)$/', $filePath, $imageExtension);
2233
            $result = [
2234
                (int)$cachedImageDimensions['imagewidth'],
2235
                (int)$cachedImageDimensions['imageheight'],
2236
                strtolower($imageExtension[0]),
2237
                $filePath
2238
            ];
2239
        }
2240
2241
        return $result;
2242
    }
2243
2244
    /**
2245
     * Creates the key for the image dimensions cache for an image file.
2246
     *
2247
     * This method does not check if the image file actually exists.
2248
     *
2249
     * @param string $filePath Image file path, relative to PATH_site
2250
     *
2251
     * @return string the hash key (an SHA1 hash), will not be empty
2252
     */
2253
    protected function generateCacheKeyForImageFile($filePath)
2254
    {
2255
        return sha1($filePath);
2256
    }
2257
2258
    /**
2259
     * Creates the status hash to check whether a file has been changed.
2260
     *
2261
     * @param string $filePath Image file path, relative to PATH_site
2262
     *
2263
     * @return string the status hash (an SHA1 hash)
2264
     */
2265
    protected function generateStatusHashForImageFile($filePath)
2266
    {
2267
        $fileStatus = stat($filePath);
2268
2269
        return sha1($fileStatus['mtime'] . $fileStatus['size']);
2270
    }
2271
2272
    /**
2273
     * Get numbers for scaling the image based on input
2274
     *
2275
     * @param array $info Current image information: Width, Height etc.
2276
     * @param int $w "required" width
2277
     * @param int $h "required" height
2278
     * @param array $options Options: Keys are like "maxW", "maxH", "minW", "minH
2279
     * @return array
2280
     * @access private
2281
     * @see imageMagickConvert()
2282
     */
2283
    public function getImageScale($info, $w, $h, $options)
2284
    {
2285
        if (strstr($w . $h, 'm')) {
2286
            $max = 1;
2287
        } else {
2288
            $max = 0;
2289
        }
2290
        if (strstr($w . $h, 'c')) {
2291
            $out['cropH'] = (int)substr(strstr($w, 'c'), 1);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$out was never initialized. Although not strictly required by PHP, it is generally a good practice to add $out = array(); before regardless.
Loading history...
2292
            $out['cropV'] = (int)substr(strstr($h, 'c'), 1);
2293
            $crs = true;
2294
        } else {
2295
            $crs = false;
2296
        }
2297
        $out['crs'] = $crs;
2298
        $w = (int)$w;
2299
        $h = (int)$h;
2300
        // If there are max-values...
2301
        if (!empty($options['maxW'])) {
2302
            // If width is given...
2303
            if ($w) {
2304
                if ($w > $options['maxW']) {
2305
                    $w = $options['maxW'];
2306
                    // Height should follow
2307
                    $max = 1;
2308
                }
2309
            } else {
2310
                if ($info[0] > $options['maxW']) {
2311
                    $w = $options['maxW'];
2312
                    // Height should follow
2313
                    $max = 1;
2314
                }
2315
            }
2316
        }
2317
        if (!empty($options['maxH'])) {
2318
            // If height is given...
2319
            if ($h) {
2320
                if ($h > $options['maxH']) {
2321
                    $h = $options['maxH'];
2322
                    // Height should follow
2323
                    $max = 1;
2324
                }
2325
            } else {
2326
                // Changed [0] to [1] 290801
2327
                if ($info[1] > $options['maxH']) {
2328
                    $h = $options['maxH'];
2329
                    // Height should follow
2330
                    $max = 1;
2331
                }
2332
            }
2333
        }
2334
        $out['origW'] = $w;
2335
        $out['origH'] = $h;
2336
        $out['max'] = $max;
2337
        if (!$this->mayScaleUp) {
2338
            if ($w > $info[0]) {
2339
                $w = $info[0];
2340
            }
2341
            if ($h > $info[1]) {
2342
                $h = $info[1];
2343
            }
2344
        }
2345
        // If scaling should be performed
2346
        if ($w || $h) {
2347
            if ($w && !$h) {
2348
                $info[1] = ceil($info[1] * ($w / $info[0]));
2349
                $info[0] = $w;
2350
            }
2351
            if (!$w && $h) {
2352
                $info[0] = ceil($info[0] * ($h / $info[1]));
2353
                $info[1] = $h;
2354
            }
2355
            if ($w && $h) {
2356
                if ($max) {
2357
                    $ratio = $info[0] / $info[1];
2358
                    if ($h * $ratio > $w) {
2359
                        $h = round($w / $ratio);
2360
                    } else {
2361
                        $w = round($h * $ratio);
2362
                    }
2363
                }
2364
                if ($crs) {
2365
                    $ratio = $info[0] / $info[1];
2366
                    if ($h * $ratio < $w) {
2367
                        $h = round($w / $ratio);
2368
                    } else {
2369
                        $w = round($h * $ratio);
2370
                    }
2371
                }
2372
                $info[0] = $w;
2373
                $info[1] = $h;
2374
            }
2375
        }
2376
        $out[0] = $info[0];
2377
        $out[1] = $info[1];
2378
        // Set minimum-measures!
2379
        if (isset($options['minW']) && $out[0] < $options['minW']) {
2380
            if (($max || $crs) && $out[0]) {
2381
                $out[1] = round($out[1] * $options['minW'] / $out[0]);
2382
            }
2383
            $out[0] = $options['minW'];
2384
        }
2385
        if (isset($options['minH']) && $out[1] < $options['minH']) {
2386
            if (($max || $crs) && $out[1]) {
2387
                $out[0] = round($out[0] * $options['minH'] / $out[1]);
2388
            }
2389
            $out[1] = $options['minH'];
2390
        }
2391
        return $out;
2392
    }
2393
2394
    /***********************************
2395
     *
2396
     * ImageMagick API functions
2397
     *
2398
     ***********************************/
2399
    /**
2400
     * Call the identify command
2401
     *
2402
     * @param string $imagefile The relative (to PATH_site) image filepath
2403
     * @return array|null Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2404
     */
2405
    public function imageMagickIdentify($imagefile)
2406
    {
2407
        if ($this->NO_IMAGE_MAGICK) {
2408
            return null;
2409
        }
2410
2411
        $frame = $this->addFrameSelection ? '[0]' : '';
2412
        $cmd = CommandUtility::imageMagickCommand('identify', CommandUtility::escapeShellArgument($imagefile) . $frame);
2413
        $returnVal = [];
2414
        CommandUtility::exec($cmd, $returnVal);
2415
        $splitstring = array_pop($returnVal);
2416
        $this->IM_commands[] = ['identify', $cmd, $splitstring];
2417
        if ($splitstring) {
2418
            preg_match('/([^\\.]*)$/', $imagefile, $reg);
2419
            $splitinfo = explode(' ', $splitstring);
2420
            $dim = false;
2421
            foreach ($splitinfo as $key => $val) {
2422
                $temp = '';
2423
                if ($val) {
2424
                    $temp = explode('x', $val);
2425
                }
2426
                if ((int)$temp[0] && (int)$temp[1]) {
2427
                    $dim = $temp;
2428
                    break;
2429
                }
2430
            }
2431
            if (!empty($dim[0]) && !empty($dim[1])) {
2432
                return [$dim[0], $dim[1], strtolower($reg[0]), $imagefile];
2433
            }
2434
        }
2435
        return null;
2436
    }
2437
2438
    /**
2439
     * Executes an ImageMagick "convert" on two filenames, $input and $output using $params before them.
2440
     * Can be used for many things, mostly scaling and effects.
2441
     *
2442
     * @param string $input The relative (to PATH_site) image filepath, input file (read from)
2443
     * @param string $output The relative (to PATH_site) image filepath, output filename (written to)
2444
     * @param string $params ImageMagick parameters
2445
     * @param int $frame Optional, refers to which frame-number to select in the image. '' or 0
2446
     * @return string The result of a call to PHP function "exec()
2447
     */
2448
    public function imageMagickExec($input, $output, $params, $frame = 0)
2449
    {
2450
        if ($this->NO_IMAGE_MAGICK) {
2451
            return '';
2452
        }
2453
        // If addFrameSelection is set in the Install Tool, a frame number is added to
2454
        // select a specific page of the image (by default this will be the first page)
2455
        $frame  = $this->addFrameSelection ? '[' . (int)$frame . ']' : '';
2456
        $cmd = CommandUtility::imageMagickCommand('convert', $params . ' ' . CommandUtility::escapeShellArgument($input . $frame) . ' ' . CommandUtility::escapeShellArgument($output));
2457
        $this->IM_commands[] = [$output, $cmd];
2458
        $ret = CommandUtility::exec($cmd);
2459
        // Change the permissions of the file
2460
        GeneralUtility::fixPermissions($output);
2461
        return $ret;
2462
    }
2463
2464
    /**
2465
     * 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)
2466
     * Can be used for many things, mostly scaling and effects.
2467
     *
2468
     * @param string $input The relative (to PATH_site) image filepath, bottom file
2469
     * @param string $overlay The relative (to PATH_site) image filepath, overlay file (top)
2470
     * @param string $mask The relative (to PATH_site) image filepath, the mask file (grayscale)
2471
     * @param string $output The relative (to PATH_site) image filepath, output filename (written to)
2472
     * @return string
2473
     */
2474
    public function combineExec($input, $overlay, $mask, $output)
2475
    {
2476
        if ($this->NO_IMAGE_MAGICK) {
2477
            return '';
2478
        }
2479
        $theMask = $this->randomName() . '.' . $this->gifExtension;
2480
        // +matte = no alpha layer in output
2481
        $this->imageMagickExec($mask, $theMask, '-colorspace GRAY +matte');
2482
2483
        $parameters = '-compose over +matte '
2484
                      . CommandUtility::escapeShellArgument($input) . ' '
2485
                      . CommandUtility::escapeShellArgument($overlay) . ' '
2486
                      . CommandUtility::escapeShellArgument($theMask) . ' '
2487
                      . CommandUtility::escapeShellArgument($output);
2488
        $cmd = CommandUtility::imageMagickCommand('combine', $parameters);
2489
        $this->IM_commands[] = [$output, $cmd];
2490
        $ret = CommandUtility::exec($cmd);
2491
        // Change the permissions of the file
2492
        GeneralUtility::fixPermissions($output);
2493
        if (is_file($theMask)) {
2494
            @unlink($theMask);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

2494
            /** @scrutinizer ignore-unhandled */ @unlink($theMask);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2495
        }
2496
        return $ret;
2497
    }
2498
2499
    /**
2500
     * Compressing a GIF file if not already LZW compressed.
2501
     * This function is a workaround for the fact that ImageMagick and/or GD does not compress GIF-files to their minimun size (that is RLE or no compression used)
2502
     *
2503
     * The function takes a file-reference, $theFile, and saves it again through GD or ImageMagick in order to compress the file
2504
     * GIF:
2505
     * 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!)
2506
     * If $type is set to either 'IM' or 'GD' the compression is done with ImageMagick and GD respectively
2507
     * PNG:
2508
     * No changes.
2509
     *
2510
     * $theFile is expected to be a valid GIF-file!
2511
     * The function returns a code for the operation.
2512
     *
2513
     * @param string $theFile Filepath
2514
     * @param string $type See description of function
2515
     * @return string Returns "GD" if GD was used, otherwise "IM" if ImageMagick was used. If nothing done at all, it returns empty string.
2516
     */
2517
    public static function gifCompress($theFile, $type)
2518
    {
2519
        $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
2520
        if (!$gfxConf['gif_compress'] || strtolower(substr($theFile, -4, 4)) !== '.gif') {
2521
            return '';
2522
        }
2523
2524
        if (($type === 'IM' || !$type) && $gfxConf['processor_enabled'] && $gfxConf['processor_path_lzw']) {
2525
            // Use temporary file to prevent problems with read and write lock on same file on network file systems
2526
            $temporaryName = dirname($theFile) . '/' . md5(uniqid('', true)) . '.gif';
2527
            // Rename could fail, if a simultaneous thread is currently working on the same thing
2528
            if (@rename($theFile, $temporaryName)) {
2529
                $cmd = CommandUtility::imageMagickCommand('convert', '"' . $temporaryName . '" "' . $theFile . '"', $gfxConf['processor_path_lzw']);
2530
                CommandUtility::exec($cmd);
2531
                unlink($temporaryName);
2532
            }
2533
            $returnCode = 'IM';
2534
            if (@is_file($theFile)) {
2535
                GeneralUtility::fixPermissions($theFile);
2536
            }
2537
        } elseif (($type === 'GD' || !$type) && $gfxConf['gdlib'] && !$gfxConf['gdlib_png']) {
2538
            $tempImage = imagecreatefromgif($theFile);
2539
            imagegif($tempImage, $theFile);
2540
            imagedestroy($tempImage);
2541
            $returnCode = 'GD';
2542
            if (@is_file($theFile)) {
2543
                GeneralUtility::fixPermissions($theFile);
2544
            }
2545
        } else {
2546
            $returnCode = '';
2547
        }
2548
2549
        return $returnCode;
2550
    }
2551
2552
    /**
2553
     * Returns filename of the png/gif version of the input file (which can be png or gif).
2554
     * If input file type does not match the wanted output type a conversion is made and temp-filename returned.
2555
     *
2556
     * @param string $theFile Filepath of image file
2557
     * @param bool $output_png If TRUE, then input file is converted to PNG, otherwise to GIF
2558
     * @return string|null If the new image file exists, its filepath is returned
2559
     */
2560
    public static function readPngGif($theFile, $output_png = false)
2561
    {
2562
        if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] || !@is_file($theFile)) {
2563
            return null;
2564
        }
2565
2566
        $ext = strtolower(substr($theFile, -4, 4));
2567
        if ((string)$ext === '.png' && $output_png || (string)$ext === '.gif' && !$output_png) {
2568
            return $theFile;
2569
        }
2570
2571
        if (!@is_dir(PATH_site . 'typo3temp/assets/images/')) {
2572
            GeneralUtility::mkdir_deep(PATH_site . 'typo3temp/assets/images/');
2573
        }
2574
        $newFile = PATH_site . 'typo3temp/assets/images/' . md5($theFile . '|' . filemtime($theFile)) . ($output_png ? '.png' : '.gif');
2575
        $cmd = CommandUtility::imageMagickCommand(
2576
            'convert',
2577
            '"' . $theFile . '" "' . $newFile . '"',
2578
            $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path']
2579
        );
2580
        CommandUtility::exec($cmd);
2581
        if (@is_file($newFile)) {
2582
            GeneralUtility::fixPermissions($newFile);
2583
            return $newFile;
2584
        }
2585
        return null;
2586
    }
2587
2588
    /***********************************
2589
     *
2590
     * Various IO functions
2591
     *
2592
     ***********************************/
2593
2594
    /**
2595
     * 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.
2596
     *
2597
     * @param resource $im The image pointer (reference)
2598
     * @param string $command The ImageMagick parameters. Like effects, scaling etc.
2599
     */
2600
    public function applyImageMagickToPHPGif(&$im, $command)
2601
    {
2602
        $tmpStr = $this->randomName();
2603
        $theFile = $tmpStr . '.' . $this->gifExtension;
2604
        $this->ImageWrite($im, $theFile);
2605
        $this->imageMagickExec($theFile, $theFile, $command);
2606
        $tmpImg = $this->imageCreateFromFile($theFile);
2607
        if ($tmpImg) {
2608
            imagedestroy($im);
2609
            $im = $tmpImg;
2610
            $this->w = imagesx($im);
2611
            $this->h = imagesy($im);
2612
        }
2613
        unlink($theFile);
2614
    }
2615
2616
    /**
2617
     * 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.
2618
     * For example: If the number of pixels exceeds $this->pixelLimitGif (normally 10000) then it will be a "jpg" string in return.
2619
     *
2620
     * @param string $type The file extension, lowercase.
2621
     * @param int $w The width of the output image.
2622
     * @param int $h The height of the output image.
2623
     * @return string The filename, either "jpg" or "gif"/"png" (whatever $this->gifExtension is set to.)
2624
     */
2625
    public function gif_or_jpg($type, $w, $h)
2626
    {
2627
        if ($type === 'ai' || $w * $h < $this->pixelLimitGif) {
2628
            return $this->gifExtension;
2629
        }
2630
        return 'jpg';
2631
    }
2632
2633
    /**
2634
     * Writing the internal image pointer, $this->im, to file based on the extension of the input filename
2635
     * Used in GIFBUILDER
2636
     * Uses $this->setup['reduceColors'] for gif/png images and $this->setup['quality'] for jpg images to reduce size/quality if needed.
2637
     *
2638
     * @param string $file The filename to write to.
2639
     * @return string Returns input filename
2640
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::gifBuild()
2641
     */
2642
    public function output($file)
2643
    {
2644
        if ($file) {
2645
            $reg = [];
2646
            preg_match('/([^\\.]*)$/', $file, $reg);
2647
            $ext = strtolower($reg[0]);
2648
            switch ($ext) {
2649
                case 'gif':
2650
                case 'png':
2651
                    if ($this->ImageWrite($this->im, $file)) {
2652
                        // ImageMagick operations
2653
                        if ($this->setup['reduceColors']) {
2654
                            $reduced = $this->IMreduceColors($file, MathUtility::forceIntegerInRange($this->setup['reduceColors'], 256, $this->truecolorColors, 256));
2655
                            if ($reduced) {
2656
                                @copy($reduced, $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

2656
                                /** @scrutinizer ignore-unhandled */ @copy($reduced, $file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2657
                                @unlink($reduced);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

2657
                                /** @scrutinizer ignore-unhandled */ @unlink($reduced);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2658
                            }
2659
                        }
2660
                        // Compress with IM! (adds extra compression, LZW from ImageMagick)
2661
                        // (Workaround for the absence of lzw-compression in GD)
2662
                        self::gifCompress($file, 'IM');
2663
                    }
2664
                    break;
2665
                case 'jpg':
2666
                case 'jpeg':
2667
                    // Use the default
2668
                    $quality = 0;
2669
                    if ($this->setup['quality']) {
2670
                        $quality = MathUtility::forceIntegerInRange($this->setup['quality'], 10, 100);
2671
                    }
2672
                    $this->ImageWrite($this->im, $file, $quality);
2673
                    break;
2674
            }
2675
        }
2676
        return $file;
2677
    }
2678
2679
    /**
2680
     * Destroy internal image pointer, $this->im
2681
     *
2682
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::gifBuild()
2683
     */
2684
    public function destroy()
2685
    {
2686
        imagedestroy($this->im);
2687
    }
2688
2689
    /**
2690
     * Returns Image Tag for input image information array.
2691
     *
2692
     * @param array $imgInfo Image information array, key 0/1 is width/height and key 3 is the src value
2693
     * @return string Image tag for the input image information array.
2694
     */
2695
    public function imgTag($imgInfo)
2696
    {
2697
        return '<img src="' . $imgInfo[3] . '" width="' . $imgInfo[0] . '" height="' . $imgInfo[1] . '" border="0" alt="" />';
2698
    }
2699
2700
    /**
2701
     * Writes the input GDlib image pointer to file
2702
     *
2703
     * @param resource $destImg The GDlib image resource pointer
2704
     * @param string $theImage The filename to write to
2705
     * @param int $quality The image quality (for JPEGs)
2706
     * @return bool The output of either imageGif, imagePng or imageJpeg based on the filename to write
2707
     * @see maskImageOntoImage(), scale(), output()
2708
     */
2709
    public function ImageWrite($destImg, $theImage, $quality = 0)
2710
    {
2711
        imageinterlace($destImg, 0);
2712
        $ext = strtolower(substr($theImage, strrpos($theImage, '.') + 1));
2713
        $result = false;
2714
        switch ($ext) {
2715
            case 'jpg':
2716
            case 'jpeg':
2717
                if (function_exists('imagejpeg')) {
2718
                    if ($quality === 0) {
2719
                        $quality = $this->jpegQuality;
2720
                    }
2721
                    $result = imagejpeg($destImg, $theImage, $quality);
2722
                }
2723
                break;
2724
            case 'gif':
2725
                if (function_exists('imagegif')) {
2726
                    imagetruecolortopalette($destImg, true, 256);
2727
                    $result = imagegif($destImg, $theImage);
2728
                }
2729
                break;
2730
            case 'png':
2731
                if (function_exists('imagepng')) {
2732
                    $result = imagepng($destImg, $theImage);
2733
                }
2734
                break;
2735
        }
2736
        if ($result) {
2737
            GeneralUtility::fixPermissions($theImage);
2738
        }
2739
        return $result;
2740
    }
2741
2742
    /**
2743
     * Creates a new GDlib image resource based on the input image filename.
2744
     * 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.
2745
     *
2746
     * @param string $sourceImg Image filename
2747
     * @return resource Image Resource pointer
2748
     */
2749
    public function imageCreateFromFile($sourceImg)
2750
    {
2751
        $imgInf = pathinfo($sourceImg);
2752
        $ext = strtolower($imgInf['extension']);
2753
        switch ($ext) {
2754
            case 'gif':
2755
                if (function_exists('imagecreatefromgif')) {
2756
                    return imagecreatefromgif($sourceImg);
2757
                }
2758
                break;
2759
            case 'png':
2760
                if (function_exists('imagecreatefrompng')) {
2761
                    $imageHandle = imagecreatefrompng($sourceImg);
2762
                    if ($this->saveAlphaLayer) {
2763
                        imagesavealpha($imageHandle, true);
2764
                    }
2765
                    return $imageHandle;
2766
                }
2767
                break;
2768
            case 'jpg':
2769
            case 'jpeg':
2770
                if (function_exists('imagecreatefromjpeg')) {
2771
                    return imagecreatefromjpeg($sourceImg);
2772
                }
2773
                break;
2774
        }
2775
        // If non of the above:
2776
        $i = @getimagesize($sourceImg);
2777
        $im = imagecreatetruecolor($i[0], $i[1]);
2778
        $Bcolor = imagecolorallocate($im, 128, 128, 128);
2779
        imagefilledrectangle($im, 0, 0, $i[0], $i[1], $Bcolor);
2780
        return $im;
2781
    }
2782
2783
    /**
2784
     * Returns the HEX color value for an RGB color array
2785
     *
2786
     * @param array $color RGB color array
2787
     * @return string HEX color value
2788
     */
2789
    public function hexColor($color)
2790
    {
2791
        $r = dechex($color[0]);
2792
        if (strlen($r) < 2) {
2793
            $r = '0' . $r;
2794
        }
2795
        $g = dechex($color[1]);
2796
        if (strlen($g) < 2) {
2797
            $g = '0' . $g;
2798
        }
2799
        $b = dechex($color[2]);
2800
        if (strlen($b) < 2) {
2801
            $b = '0' . $b;
2802
        }
2803
        return '#' . $r . $g . $b;
2804
    }
2805
2806
    /**
2807
     * Unifies all colors given in the colArr color array to the first color in the array.
2808
     *
2809
     * @param resource $img Image resource
2810
     * @param array $colArr Array containing RGB color arrays
2811
     * @param bool $closest
2812
     * @return int The index of the unified color
2813
     */
2814
    public function unifyColors(&$img, $colArr, $closest = false)
2815
    {
2816
        $retCol = -1;
2817
        if (is_array($colArr) && !empty($colArr) && function_exists('imagepng') && function_exists('imagecreatefrompng')) {
2818
            $firstCol = array_shift($colArr);
2819
            $firstColArr = $this->convertColor($firstCol);
2820
            $origName = $preName = $this->randomName() . '.png';
2821
            $postName = $this->randomName() . '.png';
2822
            $tmpImg = null;
2823
            if (count($colArr) > 1) {
2824
                $this->ImageWrite($img, $preName);
2825
                $firstCol = $this->hexColor($firstColArr);
2826
                foreach ($colArr as $transparentColor) {
2827
                    $transparentColor = $this->convertColor($transparentColor);
2828
                    $transparentColor = $this->hexColor($transparentColor);
2829
                    $cmd = '-fill "' . $firstCol . '" -opaque "' . $transparentColor . '"';
2830
                    $this->imageMagickExec($preName, $postName, $cmd);
2831
                    $preName = $postName;
2832
                }
2833
                $this->imageMagickExec($postName, $origName, '');
2834
                if (@is_file($origName)) {
2835
                    $tmpImg = $this->imageCreateFromFile($origName);
2836
                }
2837
            } else {
2838
                $tmpImg = $img;
2839
            }
2840
            if ($tmpImg) {
2841
                $img = $tmpImg;
2842
                if ($closest) {
2843
                    $retCol = imagecolorclosest($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2844
                } else {
2845
                    $retCol = imagecolorexact($img, $firstColArr[0], $firstColArr[1], $firstColArr[2]);
2846
                }
2847
            }
2848
            // Unlink files from process
2849
            if ($origName) {
2850
                @unlink($origName);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

2850
                /** @scrutinizer ignore-unhandled */ @unlink($origName);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2851
            }
2852
            if ($postName) {
2853
                @unlink($postName);
2854
            }
2855
        }
2856
        return $retCol;
2857
    }
2858
2859
    /**
2860
     * Creates error image based on gfx/notfound_thumb.png
2861
     * Requires GD lib enabled, otherwise it will exit with the three
2862
     * textstrings outputted as text. Outputs the image stream to browser and exits!
2863
     *
2864
     * @param string $filename Name of the file
2865
     * @param string $textline1 Text line 1
2866
     * @param string $textline2 Text line 2
2867
     * @param string $textline3 Text line 3
2868
     * @throws \RuntimeException
2869
     */
2870
    public function getTemporaryImageWithText($filename, $textline1, $textline2, $textline3)
2871
    {
2872
        if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'])) {
2873
            throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
2874
        }
2875
        // Creates the basis for the error image
2876
        $basePath = ExtensionManagementUtility::extPath('core') . 'Resources/Public/Images/';
2877
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
2878
            $im = imagecreatefrompng($basePath . 'NotFound.png');
2879
        } else {
2880
            $im = imagecreatefromgif($basePath . 'NotFound.gif');
2881
        }
2882
        // Sets background color and print color.
2883
        $white = imagecolorallocate($im, 255, 255, 255);
2884
        $black = imagecolorallocate($im, 0, 0, 0);
2885
        // Prints the text strings with the build-in font functions of GD
2886
        $x = 0;
2887
        $font = 0;
2888
        if ($textline1) {
2889
            imagefilledrectangle($im, $x, 9, 56, 16, $white);
2890
            imagestring($im, $font, $x, 9, $textline1, $black);
2891
        }
2892
        if ($textline2) {
2893
            imagefilledrectangle($im, $x, 19, 56, 26, $white);
2894
            imagestring($im, $font, $x, 19, $textline2, $black);
2895
        }
2896
        if ($textline3) {
2897
            imagefilledrectangle($im, $x, 29, 56, 36, $white);
2898
            imagestring($im, $font, $x, 29, substr($textline3, -14), $black);
2899
        }
2900
        // Outputting the image stream and exit
2901
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'])) {
2902
            imagepng($im, $filename);
2903
        } else {
2904
            imagegif($im, $filename);
2905
        }
2906
    }
2907
2908
    /**
2909
     * Function to compensate for DPI resolution.
2910
     * FreeType 2 always has 96 dpi, so it is hard-coded at this place.
2911
     *
2912
     * @param float $fontSize font size for freetype function call
2913
     * @return float compensated font size based on 96 dpi
2914
     */
2915
    protected function compensateFontSizeiBasedOnFreetypeDpi($fontSize)
2916
    {
2917
        return $fontSize / 96.0 * 72;
2918
    }
2919
}
2920