[CLEANUP] The correct case must be used for standard PHP types in phpdoc
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Processing / LocalCropScaleMaskHelper.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource\Processing;
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\Resource;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Frontend\Imaging\GifBuilder;
20
21 /**
22 * Helper class to locally perform a crop/scale/mask task with the TYPO3 image processing classes.
23 */
24 class LocalCropScaleMaskHelper
25 {
26 /**
27 * @var LocalImageProcessor
28 */
29 protected $processor;
30
31 /**
32 * @param LocalImageProcessor $processor
33 */
34 public function __construct(LocalImageProcessor $processor)
35 {
36 $this->processor = $processor;
37 }
38
39 /**
40 * This method actually does the processing of files locally
41 *
42 * Takes the original file (for remote storages this will be fetched from the remote server),
43 * does the IM magic on the local server by creating a temporary typo3temp/ file,
44 * copies the typo3temp/ file to the processing folder of the target storage and
45 * removes the typo3temp/ file.
46 *
47 * The returned array has the following structure:
48 * width => 100
49 * height => 200
50 * filePath => /some/path
51 *
52 * If filePath isn't set but width and height are the original file is used as ProcessedFile
53 * with the returned width and height. This is for example useful for SVG images.
54 *
55 * @param TaskInterface $task
56 * @return array|null
57 */
58 public function process(TaskInterface $task)
59 {
60 $result = null;
61 $targetFile = $task->getTargetFile();
62 $sourceFile = $task->getSourceFile();
63
64 $originalFileName = $sourceFile->getForLocalProcessing(false);
65 /** @var $gifBuilder GifBuilder */
66 $gifBuilder = GeneralUtility::makeInstance(GifBuilder::class);
67 $gifBuilder->init();
68 $gifBuilder->absPrefix = PATH_site;
69
70 $configuration = $targetFile->getProcessingConfiguration();
71 $configuration['additionalParameters'] = $this->modifyImageMagickStripProfileParameters($configuration['additionalParameters'], $configuration);
72
73 if (empty($configuration['fileExtension'])) {
74 $configuration['fileExtension'] = $task->getTargetFileExtension();
75 }
76
77 $options = $this->getConfigurationForImageCropScaleMask($targetFile, $gifBuilder);
78
79 $croppedImage = null;
80 if (!empty($configuration['crop'])) {
81
82 // check if it is a json object
83 $cropData = json_decode($configuration['crop']);
84 if ($cropData) {
85 $crop = implode(',', [(int)$cropData->x, (int)$cropData->y, (int)$cropData->width, (int)$cropData->height]);
86 } else {
87 $crop = $configuration['crop'];
88 }
89
90 list($offsetLeft, $offsetTop, $newWidth, $newHeight) = explode(',', $crop, 4);
91
92 $backupPrefix = $gifBuilder->filenamePrefix;
93 $gifBuilder->filenamePrefix = 'crop_';
94
95 // the result info is an array with 0=width,1=height,2=extension,3=filename
96 $result = $gifBuilder->imageMagickConvert(
97 $originalFileName,
98 $configuration['fileExtension'],
99 '',
100 '',
101 sprintf('-crop %dx%d+%d+%d', $newWidth, $newHeight, $offsetLeft, $offsetTop),
102 '',
103 ['noScale' => true],
104 true
105 );
106 $gifBuilder->filenamePrefix = $backupPrefix;
107
108 if ($result !== null) {
109 $originalFileName = $croppedImage = $result[3];
110 }
111 }
112
113 // Normal situation (no masking)
114 if (!(is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'])) {
115
116 // SVG
117 if ($croppedImage === null && $sourceFile->getExtension() === 'svg') {
118 $newDimensions = $this->getNewSvgDimensions($sourceFile, $configuration, $options, $gifBuilder);
119 $result = [
120 0 => $newDimensions['width'],
121 1 => $newDimensions['height'],
122 3 => '' // no file = use original
123 ];
124 } else {
125 // all other images
126 // the result info is an array with 0=width,1=height,2=extension,3=filename
127 $result = $gifBuilder->imageMagickConvert(
128 $originalFileName,
129 $configuration['fileExtension'],
130 $configuration['width'],
131 $configuration['height'],
132 $configuration['additionalParameters'],
133 $configuration['frame'],
134 $options
135 );
136 }
137 } else {
138 $targetFileName = $this->getFilenameForImageCropScaleMask($task);
139 $temporaryFileName = PATH_site . 'typo3temp/' . $targetFileName;
140 $maskImage = $configuration['maskImages']['maskImage'];
141 $maskBackgroundImage = $configuration['maskImages']['backgroundImage'];
142 if ($maskImage instanceof Resource\FileInterface && $maskBackgroundImage instanceof Resource\FileInterface) {
143 $temporaryExtension = 'png';
144 if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_allowTemporaryMasksAsPng']) {
145 // If ImageMagick version 5+
146 $temporaryExtension = $gifBuilder->gifExtension;
147 }
148 $tempFileInfo = $gifBuilder->imageMagickConvert(
149 $originalFileName,
150 $temporaryExtension,
151 $configuration['width'],
152 $configuration['height'],
153 $configuration['additionalParameters'],
154 $configuration['frame'],
155 $options
156 );
157 if (is_array($tempFileInfo)) {
158 $maskBottomImage = $configuration['maskImages']['maskBottomImage'];
159 if ($maskBottomImage instanceof Resource\FileInterface) {
160 $maskBottomImageMask = $configuration['maskImages']['maskBottomImageMask'];
161 } else {
162 $maskBottomImageMask = null;
163 }
164
165 // Scaling: ****
166 $tempScale = [];
167 $command = '-geometry ' . $tempFileInfo[0] . 'x' . $tempFileInfo[1] . '!';
168 $command = $this->modifyImageMagickStripProfileParameters($command, $configuration);
169 $tmpStr = $gifBuilder->randomName();
170 // m_mask
171 $tempScale['m_mask'] = $tmpStr . '_mask.' . $temporaryExtension;
172 $gifBuilder->imageMagickExec($maskImage->getForLocalProcessing(true), $tempScale['m_mask'], $command);
173 // m_bgImg
174 $tempScale['m_bgImg'] = $tmpStr . '_bgImg.miff';
175 $gifBuilder->imageMagickExec($maskBackgroundImage->getForLocalProcessing(), $tempScale['m_bgImg'], $command);
176 // m_bottomImg / m_bottomImg_mask
177 if ($maskBottomImage instanceof Resource\FileInterface && $maskBottomImageMask instanceof Resource\FileInterface) {
178 $tempScale['m_bottomImg'] = $tmpStr . '_bottomImg.' . $temporaryExtension;
179 $gifBuilder->imageMagickExec($maskBottomImage->getForLocalProcessing(), $tempScale['m_bottomImg'], $command);
180 $tempScale['m_bottomImg_mask'] = ($tmpStr . '_bottomImg_mask.') . $temporaryExtension;
181 $gifBuilder->imageMagickExec($maskBottomImageMask->getForLocalProcessing(), $tempScale['m_bottomImg_mask'], $command);
182 // BEGIN combining:
183 // The image onto the background
184 $gifBuilder->combineExec($tempScale['m_bgImg'], $tempScale['m_bottomImg'], $tempScale['m_bottomImg_mask'], $tempScale['m_bgImg']);
185 }
186 // The image onto the background
187 $gifBuilder->combineExec($tempScale['m_bgImg'], $tempFileInfo[3], $tempScale['m_mask'], $temporaryFileName);
188 $tempFileInfo[3] = $temporaryFileName;
189 // Unlink the temp-images...
190 foreach ($tempScale as $tempFile) {
191 if (@is_file($tempFile)) {
192 unlink($tempFile);
193 }
194 }
195 }
196 $result = $tempFileInfo;
197 }
198 }
199
200 // check if the processing really generated a new file (scaled and/or cropped)
201 if ($result !== null) {
202 if ($result[3] !== $originalFileName || $originalFileName === $croppedImage) {
203 $result = [
204 'width' => $result[0],
205 'height' => $result[1],
206 'filePath' => $result[3],
207 ];
208 } else {
209 // No file was generated
210 $result = null;
211 }
212 }
213
214 // Cleanup temp file if it isn't used as result
215 if ($croppedImage && ($result === null || $croppedImage !== $result['filePath'])) {
216 GeneralUtility::unlink_tempfile($croppedImage);
217 }
218
219 return $result;
220 }
221
222 /**
223 * Calculate new dimensions for SVG image
224 * No cropping, if cropped info present image is scaled down
225 *
226 * @param Resource\FileInterface $file
227 * @param array $configuration
228 * @param array $options
229 * @param GifBuilder $gifBuilder
230 * @return array width,height
231 */
232 protected function getNewSvgDimensions($file, array $configuration, array $options, GifBuilder $gifBuilder)
233 {
234 $info = [$file->getProperty('width'), $file->getProperty('height')];
235 $data = $gifBuilder->getImageScale($info, $configuration['width'], $configuration['height'], $options);
236
237 // Turn cropScaling into scaling
238 if ($data['crs']) {
239 if (!$data['origW']) {
240 $data['origW'] = $data[0];
241 }
242 if (!$data['origH']) {
243 $data['origH'] = $data[1];
244 }
245 if ($data[0] > $data['origW']) {
246 $data[1] = (int)(($data['origW'] * $data[1]) / $data[0]);
247 $data[0] = $data['origW'];
248 } else {
249 $data[0] = (int)(($data['origH'] * $data[0]) / $data[1]);
250 $data[1] = $data['origH'];
251 }
252 }
253
254 return [
255 'width' => $data[0],
256 'height' => $data[1]
257 ];
258 }
259
260 /**
261 * @param Resource\ProcessedFile $processedFile
262 * @param \TYPO3\CMS\Frontend\Imaging\GifBuilder $gifBuilder
263 *
264 * @return array
265 */
266 protected function getConfigurationForImageCropScaleMask(Resource\ProcessedFile $processedFile, \TYPO3\CMS\Frontend\Imaging\GifBuilder $gifBuilder)
267 {
268 $configuration = $processedFile->getProcessingConfiguration();
269
270 if ($configuration['useSample']) {
271 $gifBuilder->scalecmd = '-sample';
272 }
273 $options = [];
274 if ($configuration['maxWidth']) {
275 $options['maxW'] = $configuration['maxWidth'];
276 }
277 if ($configuration['maxHeight']) {
278 $options['maxH'] = $configuration['maxHeight'];
279 }
280 if ($configuration['minWidth']) {
281 $options['minW'] = $configuration['minWidth'];
282 }
283 if ($configuration['minHeight']) {
284 $options['minH'] = $configuration['minHeight'];
285 }
286
287 $options['noScale'] = $configuration['noScale'];
288
289 return $options;
290 }
291
292 /**
293 * Returns the filename for a cropped/scaled/masked file.
294 *
295 * @param TaskInterface $task
296 * @return string
297 */
298 protected function getFilenameForImageCropScaleMask(TaskInterface $task)
299 {
300 $configuration = $task->getTargetFile()->getProcessingConfiguration();
301 $targetFileExtension = $task->getSourceFile()->getExtension();
302 $processedFileExtension = $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'] ? 'png' : 'gif';
303 if (is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] && $task->getSourceFile()->getExtension() != $processedFileExtension) {
304 $targetFileExtension = 'jpg';
305 } elseif ($configuration['fileExtension']) {
306 $targetFileExtension = $configuration['fileExtension'];
307 }
308
309 return $task->getTargetFile()->generateProcessedFileNameWithoutExtension() . '.' . ltrim(trim($targetFileExtension), '.');
310 }
311
312 /**
313 * Modifies the parameters for ImageMagick for stripping of profile information.
314 *
315 * @param string $parameters The parameters to be modified (if required)
316 * @param array $configuration The TypoScript configuration of [IMAGE].file
317 * @return string
318 */
319 protected function modifyImageMagickStripProfileParameters($parameters, array $configuration)
320 {
321 // Strips profile information of image to save some space:
322 if (isset($configuration['stripProfile'])) {
323 if (
324 $configuration['stripProfile']
325 && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] !== ''
326 ) {
327 $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripProfileCommand'] . $parameters;
328 } else {
329 $parameters .= '###SkipStripProfile###';
330 }
331 }
332 return $parameters;
333 }
334 }