1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Core\Imaging;
19
20use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
21use TYPO3\CMS\Core\Resource\ProcessedFile;
22use TYPO3\CMS\Core\Resource\Processing\LocalPreviewHelper;
23use TYPO3\CMS\Core\Resource\Processing\TaskInterface;
24use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26/**
27 * Representing an image dimension (width and height)
28 * and calculating the dimension from a source with a given processing instruction
29 */
30class ImageDimension
31{
32    /**
33     * @var int
34     */
35    private $width;
36
37    /**
38     * @var int
39     */
40    private $height;
41
42    public function __construct(int $width, int $height)
43    {
44        $this->width = $width;
45        $this->height = $height;
46    }
47
48    public function getWidth(): int
49    {
50        return $this->width;
51    }
52
53    public function getHeight(): int
54    {
55        return $this->height;
56    }
57
58    public static function fromProcessingTask(TaskInterface $task): self
59    {
60        $config = self::getConfigurationForImageCropScaleMask($task);
61        $processedFile = $task->getTargetFile();
62        $isCropped = false;
63        if (($config['crop'] ?? null) instanceof Area) {
64            $isCropped = true;
65            $imageDimension = new self(
66                (int)round($config['crop']->getWidth()),
67                (int)round($config['crop']->getHeight())
68            );
69        } else {
70            $imageDimension = new self(
71                (int)$processedFile->getOriginalFile()->getProperty('width'),
72                (int)$processedFile->getOriginalFile()->getProperty('height')
73            );
74        }
75        if ($imageDimension->width <=0 || $imageDimension->height <=0) {
76            throw new \BadMethodCallException('Width and height of the image must be greater than zero', 1597310560);
77        }
78        $result = GeneralUtility::makeInstance(GraphicalFunctions::class)->getImageScale(
79            [
80                $imageDimension->width,
81                $imageDimension->height,
82                $processedFile->getExtension()
83            ],
84            $config['width'] ?? null,
85            $config['height'] ?? null,
86            $config
87        );
88        $imageWidth = $geometryWidth = (int)$result[0];
89        $imageHeight = $geometryHeight = (int)$result[1];
90        $isCropScaled = $result['crs'];
91
92        if ($isCropScaled) {
93            $cropWidth = (int)$result['origW'];
94            $cropHeight = (int)$result['origH'];
95            // If the image is crop scaled, use the dimension of the crop
96            // unless crop area exceeds the dimension of the scaled image
97            if ($cropWidth <= $geometryWidth && $cropHeight <= $geometryHeight) {
98                $imageWidth = $cropWidth;
99                $imageHeight = $cropHeight;
100            }
101            if (!$isCropped && $task->getTargetFileExtension() === 'svg') {
102                // Keep aspect ratio of SVG files, when crop-scaling is requested
103                // but no crop is applied
104                if ($geometryWidth > $geometryHeight) {
105                    $imageHeight = (int)round($imageWidth * $geometryHeight / $geometryWidth);
106                } else {
107                    $imageWidth = (int)round($imageHeight * $geometryWidth / $geometryHeight);
108                }
109            }
110        }
111        $imageDimension->width = $imageWidth;
112        $imageDimension->height = $imageHeight;
113
114        return $imageDimension;
115    }
116
117    private static function getConfigurationForImageCropScaleMask(TaskInterface $task): array
118    {
119        $configuration = $task->getConfiguration();
120
121        if ($task->getTargetFile()->getTaskIdentifier() === ProcessedFile::CONTEXT_IMAGEPREVIEW) {
122            $configuration = LocalPreviewHelper::preProcessConfiguration($configuration);
123            $configuration['maxWidth'] = $configuration['width'];
124            unset($configuration['width']);
125            $configuration['maxHeight'] = $configuration['height'];
126            unset($configuration['height']);
127        }
128
129        $options = $configuration;
130        if ($configuration['maxWidth'] ?? null) {
131            $options['maxW'] = $configuration['maxWidth'];
132        }
133        if ($configuration['maxHeight'] ?? null) {
134            $options['maxH'] = $configuration['maxHeight'];
135        }
136        if ($configuration['minWidth'] ?? null) {
137            $options['minW'] = $configuration['minWidth'];
138        }
139        if ($configuration['minHeight'] ?? null) {
140            $options['minH'] = $configuration['minHeight'];
141        }
142        if ($configuration['crop'] ?? null) {
143            $options['crop'] = $configuration['crop'];
144            if (is_string($configuration['crop'])) {
145                // check if it is a json object
146                $cropData = json_decode($configuration['crop']);
147                if ($cropData) {
148                    $options['crop'] = new Area($cropData->x, $cropData->y, $cropData->width, $cropData->height);
149                } else {
150                    [$offsetLeft, $offsetTop, $newWidth, $newHeight] = explode(',', $configuration['crop'], 4);
151                    $options['crop'] = new Area((float)$offsetLeft, (float)$offsetTop, (float)$newWidth, (float)$newHeight);
152                }
153                if ($options['crop']->isEmpty()) {
154                    unset($options['crop']);
155                }
156            }
157        }
158        if ($configuration['noScale'] ?? null) {
159            $options['noScale'] = $configuration['noScale'];
160        }
161
162        return $options;
163    }
164}
165