LocalCropScaleMaskHelper.php 11.6 KB
Newer Older
1
<?php
2

3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
7
8
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
9
 *
10
11
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
14
 * The TYPO3 project - inspiring people to share!
 */
15

16
17
namespace TYPO3\CMS\Core\Resource\Processing;

18
use TYPO3\CMS\Core\Core\Environment;
19
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
20
use TYPO3\CMS\Core\Resource;
21
22
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\ProcessedFile;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
Frans Saris's avatar
Frans Saris committed
24
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
25
26
27
28

/**
 * Helper class to locally perform a crop/scale/mask task with the TYPO3 image processing classes.
 */
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class LocalCropScaleMaskHelper
{
    /**
     * This method actually does the processing of files locally
     *
     * Takes the original file (for remote storages this will be fetched from the remote server),
     * does the IM magic on the local server by creating a temporary typo3temp/ file,
     * copies the typo3temp/ file to the processing folder of the target storage and
     * removes the typo3temp/ file.
     *
     * The returned array has the following structure:
     *   width => 100
     *   height => 200
     *   filePath => /some/path
     *
     * If filePath isn't set but width and height are the original file is used as ProcessedFile
     * with the returned width and height. This is for example useful for SVG images.
     *
     * @param TaskInterface $task
48
     * @return array|null
49
50
     */
    public function process(TaskInterface $task)
51
52
53
54
55
56
57
58
59
60
61
62
63
    {
        return $this->processWithLocalFile($task, $task->getSourceFile()->getForLocalProcessing(false));
    }

    /**
     * Does the heavy lifting prescribed in processTask()
     * except that the processing can be performed on any given local image
     *
     * @param TaskInterface $task
     * @param string $originalFileName
     * @return array|null
     */
    public function processWithLocalFile(TaskInterface $task, string $originalFileName): ?array
64
    {
65
66
67
68
        if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'])) {
            return null;
        }

69
70
71
72
73
74
75
76
77
78
79
80
81
82
        $result = null;
        $targetFile = $task->getTargetFile();

        $gifBuilder = GeneralUtility::makeInstance(GifBuilder::class);

        $configuration = $targetFile->getProcessingConfiguration();
        $configuration['additionalParameters'] = $this->modifyImageMagickStripProfileParameters($configuration['additionalParameters'], $configuration);

        if (empty($configuration['fileExtension'])) {
            $configuration['fileExtension'] = $task->getTargetFileExtension();
        }

        $options = $this->getConfigurationForImageCropScaleMask($targetFile, $gifBuilder);
        // Normal situation (no masking)
83
        if (empty($configuration['maskImages'])) {
84
85
86
            // the result info is an array with 0=width,1=height,2=extension,3=filename
            $result = $gifBuilder->imageMagickConvert(
                $originalFileName,
87
88
89
90
91
                $configuration['fileExtension'] ?? null,
                $configuration['width'] ?? null,
                $configuration['height'] ?? null,
                $configuration['additionalParameters'] ?? null,
                $configuration['frame'] ?? null,
92
93
                $options
            );
94
95
        } else {
            $targetFileName = $this->getFilenameForImageCropScaleMask($task);
96
            $temporaryFileName = Environment::getPublicPath() . '/typo3temp/' . $targetFileName;
97
98
            $maskImage = $configuration['maskImages']['maskImage'];
            $maskBackgroundImage = $configuration['maskImages']['backgroundImage'];
99
            if ($maskImage instanceof FileInterface && $maskBackgroundImage instanceof FileInterface) {
100
                $temporaryExtension = 'png';
101
                if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_allowTemporaryMasksAsPng']) {
102
103
104
105
106
107
                    // If ImageMagick version 5+
                    $temporaryExtension = $gifBuilder->gifExtension;
                }
                $tempFileInfo = $gifBuilder->imageMagickConvert(
                    $originalFileName,
                    $temporaryExtension,
108
109
110
111
                    $configuration['width'] ?? null,
                    $configuration['height'] ?? null,
                    $configuration['additionalParameters'] ?? null,
                    $configuration['frame'] ?? null,
112
113
114
                    $options
                );
                if (is_array($tempFileInfo)) {
115
                    $maskBottomImage = $configuration['maskImages']['maskBottomImage'] ?? null;
116
                    if ($maskBottomImage instanceof FileInterface) {
117
                        $maskBottomImageMask = $configuration['maskImages']['maskBottomImageMask'] ?? null;
118
119
120
121
122
                    } else {
                        $maskBottomImageMask = null;
                    }

                    //	Scaling:	****
123
                    $tempScale = [];
124
125
126
127
128
129
130
131
132
133
                    $command = '-geometry ' . $tempFileInfo[0] . 'x' . $tempFileInfo[1] . '!';
                    $command = $this->modifyImageMagickStripProfileParameters($command, $configuration);
                    $tmpStr = $gifBuilder->randomName();
                    //	m_mask
                    $tempScale['m_mask'] = $tmpStr . '_mask.' . $temporaryExtension;
                    $gifBuilder->imageMagickExec($maskImage->getForLocalProcessing(true), $tempScale['m_mask'], $command);
                    //	m_bgImg
                    $tempScale['m_bgImg'] = $tmpStr . '_bgImg.miff';
                    $gifBuilder->imageMagickExec($maskBackgroundImage->getForLocalProcessing(), $tempScale['m_bgImg'], $command);
                    //	m_bottomImg / m_bottomImg_mask
134
                    if ($maskBottomImage instanceof FileInterface && $maskBottomImageMask instanceof FileInterface) {
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
                        $tempScale['m_bottomImg'] = $tmpStr . '_bottomImg.' . $temporaryExtension;
                        $gifBuilder->imageMagickExec($maskBottomImage->getForLocalProcessing(), $tempScale['m_bottomImg'], $command);
                        $tempScale['m_bottomImg_mask'] = ($tmpStr . '_bottomImg_mask.') . $temporaryExtension;
                        $gifBuilder->imageMagickExec($maskBottomImageMask->getForLocalProcessing(), $tempScale['m_bottomImg_mask'], $command);
                        // BEGIN combining:
                        // The image onto the background
                        $gifBuilder->combineExec($tempScale['m_bgImg'], $tempScale['m_bottomImg'], $tempScale['m_bottomImg_mask'], $tempScale['m_bgImg']);
                    }
                    // The image onto the background
                    $gifBuilder->combineExec($tempScale['m_bgImg'], $tempFileInfo[3], $tempScale['m_mask'], $temporaryFileName);
                    $tempFileInfo[3] = $temporaryFileName;
                    // Unlink the temp-images...
                    foreach ($tempScale as $tempFile) {
                        if (@is_file($tempFile)) {
                            unlink($tempFile);
                        }
                    }
                }
                $result = $tempFileInfo;
            }
        }

        // check if the processing really generated a new file (scaled and/or cropped)
        if ($result !== null) {
159
            if ($result[3] !== $originalFileName) {
160
                $result = [
161
162
163
                    'width' => $result[0],
                    'height' => $result[1],
                    'filePath' => $result[3],
164
                ];
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
            } else {
                // No file was generated
                $result = null;
            }
        }

        return $result;
    }

    /**
     * @param Resource\ProcessedFile $processedFile
     * @param \TYPO3\CMS\Frontend\Imaging\GifBuilder $gifBuilder
     *
     * @return array
     */
180
    protected function getConfigurationForImageCropScaleMask(ProcessedFile $processedFile, GifBuilder $gifBuilder)
181
182
183
    {
        $configuration = $processedFile->getProcessingConfiguration();

184
        if ($configuration['useSample'] ?? null) {
185
186
            $gifBuilder->scalecmd = '-sample';
        }
187
        $options = [];
188
        if ($configuration['maxWidth'] ?? null) {
189
190
            $options['maxW'] = $configuration['maxWidth'];
        }
191
        if ($configuration['maxHeight'] ?? null) {
192
193
            $options['maxH'] = $configuration['maxHeight'];
        }
194
        if ($configuration['minWidth'] ?? null) {
195
196
            $options['minW'] = $configuration['minWidth'];
        }
197
        if ($configuration['minHeight'] ?? null) {
198
199
            $options['minH'] = $configuration['minHeight'];
        }
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
        if ($configuration['crop'] ?? null) {
            $options['crop'] = $configuration['crop'];
            // This normalisation is required to preserve backwards compatibility
            // In the future the whole processing configuration should be a plain array or json serializable object for straightforward serialisation
            // The crop configuration currently can be a json string, string with comma separated values or an Area object
            if (is_string($configuration['crop'])) {
                // check if it is a json object
                $cropData = json_decode($configuration['crop']);
                if ($cropData) {
                    $options['crop'] = new Area($cropData->x, $cropData->y, $cropData->width, $cropData->height);
                } else {
                    [$offsetLeft, $offsetTop, $newWidth, $newHeight] = explode(',', $configuration['crop'], 4);
                    $options['crop'] = new Area($offsetLeft, $offsetTop, $newWidth, $newHeight);
                }
            }
        }
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232

        $options['noScale'] = $configuration['noScale'];

        return $options;
    }

    /**
     * Returns the filename for a cropped/scaled/masked file.
     *
     * @param TaskInterface $task
     * @return string
     */
    protected function getFilenameForImageCropScaleMask(TaskInterface $task)
    {
        $configuration = $task->getTargetFile()->getProcessingConfiguration();
        $targetFileExtension = $task->getSourceFile()->getExtension();
        $processedFileExtension = $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'] ? 'png' : 'gif';
233
        if (is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] && $task->getSourceFile()->getExtension() != $processedFileExtension) {
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
            $targetFileExtension = 'jpg';
        } elseif ($configuration['fileExtension']) {
            $targetFileExtension = $configuration['fileExtension'];
        }

        return $task->getTargetFile()->generateProcessedFileNameWithoutExtension() . '.' . ltrim(trim($targetFileExtension), '.');
    }

    /**
     * Modifies the parameters for ImageMagick for stripping of profile information.
     *
     * @param string $parameters The parameters to be modified (if required)
     * @param array $configuration The TypoScript configuration of [IMAGE].file
     * @return string
     */
    protected function modifyImageMagickStripProfileParameters($parameters, array $configuration)
    {
        // Strips profile information of image to save some space:
        if (isset($configuration['stripProfile'])) {
253
254
255
256
            if (
                $configuration['stripProfile']
                && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] !== ''
            ) {
257
                $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] . $parameters;
258
259
260
261
262
263
            } else {
                $parameters .= '###SkipStripProfile###';
            }
        }
        return $parameters;
    }
264
}