[BUGFIX] Eliminate gremlins in image cropper
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Imaging / ImageManipulation / Area.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Imaging\ImageManipulation;
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
18 use TYPO3\CMS\Core\Resource\FileInterface;
19
20 class Area
21 {
22 /**
23 * @var float
24 */
25 protected $x;
26
27 /**
28 * @var float
29 */
30 protected $y;
31
32 /**
33 * @var float
34 */
35 protected $width;
36
37 /**
38 * @var float
39 */
40 protected $height;
41
42 /**
43 * @param float $x
44 * @param float $y
45 * @param float $width
46 * @param float $height
47 */
48 public function __construct(float $x, float $y, float $width, float $height)
49 {
50 $this->x = $x;
51 $this->y = $y;
52 $this->width = $width;
53 $this->height = $height;
54 }
55
56 /**
57 * @param array $config
58 * @return Area
59 * @throws InvalidConfigurationException
60 */
61 public static function createFromConfiguration(array $config): Area
62 {
63 try {
64 return new self(
65 $config['x'],
66 $config['y'],
67 $config['width'],
68 $config['height']
69 );
70 } catch (\Throwable $throwable) {
71 throw new InvalidConfigurationException(sprintf('Invalid type for area property given: %s', $throwable->getMessage()), 1485279226, $throwable);
72 }
73 }
74
75 /**
76 * @param array $config
77 * @return Area[]
78 * @throws InvalidConfigurationException
79 */
80 public static function createMultipleFromConfiguration(array $config): array
81 {
82 $areas = [];
83 foreach ($config as $areaConfig) {
84 $areas[] = self::createFromConfiguration($areaConfig);
85 }
86 return $areas;
87 }
88
89 /**
90 * @return Area
91 */
92 public static function createEmpty()
93 {
94 return new self(0.0, 0.0, 1.0, 1.0);
95 }
96
97 /**
98 * @return array
99 * @internal
100 */
101 public function asArray(): array
102 {
103 return [
104 'x' => $this->x,
105 'y' => $this->y,
106 'width' => $this->width,
107 'height' => $this->height,
108 ];
109 }
110
111 /**
112 * @param FileInterface $file
113 * @return Area
114 */
115 public function makeAbsoluteBasedOnFile(FileInterface $file)
116 {
117 return new self(
118 $this->x * $file->getProperty('width'),
119 $this->y * $file->getProperty('height'),
120 $this->width * $file->getProperty('width'),
121 $this->height * $file->getProperty('height')
122 );
123 }
124
125 /**
126 * @param FileInterface $file
127 * @return Area
128 */
129 public function makeRelativeBasedOnFile(FileInterface $file)
130 {
131 return new self(
132 $this->x / $file->getProperty('width'),
133 $this->y / $file->getProperty('height'),
134 $this->width / $file->getProperty('width'),
135 $this->height / $file->getProperty('height')
136 );
137 }
138
139 /**
140 * @param Ratio $ratio
141 * @return Area
142 */
143 public function applyRatioRestriction(Ratio $ratio): Area
144 {
145 if ($ratio->isFree()) {
146 return $this;
147 }
148 $expectedRatio = $ratio->getRatioValue();
149 $newArea = clone $this;
150 if ($newArea->height * $expectedRatio > $newArea->width) {
151 $newArea->height = $newArea->width / $expectedRatio;
152 $newArea->y += ($this->height - $newArea->height) / 2;
153 } else {
154 $newArea->width = $newArea->height * $expectedRatio;
155 $newArea->x += ($this->width - $newArea->width) / 2;
156 }
157 return $newArea;
158 }
159
160 /**
161 * @return bool
162 */
163 public function isEmpty()
164 {
165 return $this->x === 0.0 && $this->y === 0.0 && $this->width === 1.0 && $this->height === 1.0;
166 }
167
168 /**
169 * @return string
170 */
171 public function __toString()
172 {
173 if ($this->isEmpty()) {
174 return '';
175 } else {
176 return json_encode($this->asArray());
177 }
178 }
179 }