|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
namespace Del; |
|
6
|
|
|
|
|
7
|
|
|
use Del\Image\Exception\NotFoundException; |
|
8
|
|
|
use Del\Image\Exception\NothingLoadedException; |
|
9
|
|
|
use Del\Image\Strategy\GifStrategy; |
|
10
|
|
|
use Del\Image\Strategy\ImageTypeStrategyInterface; |
|
11
|
|
|
use Del\Image\Strategy\JpegStrategy; |
|
12
|
|
|
use Del\Image\Strategy\PngStrategy; |
|
13
|
|
|
use Del\Image\Strategy\WebPStrategy; |
|
14
|
|
|
use GdImage; |
|
15
|
|
|
|
|
16
|
|
|
class Image |
|
17
|
|
|
{ |
|
18
|
|
|
private GdImage $image; |
|
19
|
|
|
private ?string $fileName = null; |
|
20
|
|
|
private ?ImageTypeStrategyInterface $strategy = null; |
|
21
|
|
|
|
|
22
|
|
|
private array $strategies = [ |
|
23
|
|
|
IMAGETYPE_JPEG => JpegStrategy::class, |
|
24
|
|
|
IMAGETYPE_GIF => GifStrategy::class, |
|
25
|
|
|
IMAGETYPE_PNG => PngStrategy::class, |
|
26
|
|
|
IMAGETYPE_WEBP => WebPStrategy::class, |
|
27
|
|
|
]; |
|
28
|
|
|
|
|
29
|
28 |
|
public function __construct(?string $filename = null) |
|
30
|
|
|
{ |
|
31
|
28 |
|
if ($filename !== null) { |
|
32
|
3 |
|
$this->fileName = $filename; |
|
33
|
3 |
|
$this->load($filename); |
|
34
|
|
|
} |
|
35
|
|
|
} |
|
36
|
|
|
|
|
37
|
|
|
/** @throws NotFoundException */ |
|
38
|
27 |
|
private function checkFileExists(string $path): void |
|
39
|
|
|
{ |
|
40
|
27 |
|
if (!\file_exists($path)) { |
|
41
|
1 |
|
throw new NotFoundException("$path does not exist"); |
|
42
|
|
|
} |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
/** @throws NotFoundException */ |
|
46
|
27 |
|
public function load(string $filename): void |
|
47
|
|
|
{ |
|
48
|
27 |
|
$this->checkFileExists($filename); |
|
49
|
26 |
|
$index = \getimagesize($filename)[2]; |
|
50
|
26 |
|
$this->strategy = new $this->strategies[$index](); |
|
51
|
26 |
|
$this->image = $this->strategy->create($filename); |
|
|
|
|
|
|
52
|
|
|
} |
|
53
|
|
|
|
|
54
|
1 |
|
public function setImageStrategy(ImageTypeStrategyInterface $imageTypeStrategy): void |
|
55
|
|
|
{ |
|
56
|
1 |
|
$this->strategy = $imageTypeStrategy; |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
4 |
|
public function save(?string $filename = null, ?int $permissions = null, int $compression = 100): void |
|
60
|
|
|
{ |
|
61
|
4 |
|
$filename = ($filename) ?: $this->fileName; |
|
62
|
4 |
|
$this->strategy->save($this->image, $filename, $compression); |
|
|
|
|
|
|
63
|
4 |
|
$this->setPermissions($filename, $permissions); |
|
|
|
|
|
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
4 |
|
private function setPermissions(string $filename, ?int $permissions = null): void |
|
67
|
|
|
{ |
|
68
|
4 |
|
if ($permissions !== null) { |
|
69
|
2 |
|
\chmod($filename, $permissions); |
|
70
|
|
|
} |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
20 |
|
public function output(bool $return = false): ?string |
|
74
|
|
|
{ |
|
75
|
20 |
|
$contents = null; |
|
76
|
|
|
|
|
77
|
20 |
|
if ($return) { |
|
78
|
20 |
|
\ob_start(); |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
20 |
|
$this->renderImage(); |
|
82
|
|
|
|
|
83
|
20 |
|
if ($return) { |
|
84
|
20 |
|
$contents = \ob_get_clean(); |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
20 |
|
return $contents; |
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
/** @throws NothingLoadedException */ |
|
91
|
1 |
|
public function outputBase64Src(): string |
|
92
|
|
|
{ |
|
93
|
1 |
|
return 'data:' . $this->getHeader() . ';base64,' . \base64_encode( $this->output(true) ); |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
20 |
|
private function renderImage(): void |
|
97
|
|
|
{ |
|
98
|
20 |
|
$this->strategy->render($this->image); |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
11 |
|
public function getWidth(): int |
|
102
|
|
|
{ |
|
103
|
11 |
|
return \imagesx($this->image); |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
11 |
|
public function getHeight(): int |
|
107
|
|
|
{ |
|
108
|
11 |
|
return \imagesy($this->image); |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
3 |
|
public function resizeToHeight(int $height): void |
|
112
|
|
|
{ |
|
113
|
3 |
|
$ratio = $height / $this->getHeight(); |
|
114
|
3 |
|
$width = (int) ($this->getWidth() * $ratio); |
|
115
|
3 |
|
$this->resize($width, $height); |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
4 |
|
public function resizeToWidth(int $width): void |
|
119
|
|
|
{ |
|
120
|
4 |
|
$ratio = $width / $this->getWidth(); |
|
121
|
4 |
|
$height = (int) ($this->getHeight() * $ratio); |
|
122
|
4 |
|
$this->resize($width, $height); |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
1 |
|
public function scale(int $scale): void |
|
126
|
|
|
{ |
|
127
|
1 |
|
$width = (int) ($this->getWidth() * $scale / 100); |
|
128
|
1 |
|
$height = (int) ($this->getHeight() * $scale / 100); |
|
129
|
1 |
|
$this->resize($width, $height); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
4 |
|
public function resizeAndCrop(int $width, int $height): void |
|
133
|
|
|
{ |
|
134
|
4 |
|
$targetRatio = $width / $height; |
|
135
|
4 |
|
$actualRatio = $this->getWidth() / $this->getHeight(); |
|
136
|
|
|
|
|
137
|
4 |
|
if ($targetRatio === $actualRatio) { |
|
138
|
|
|
// Scale to size |
|
139
|
1 |
|
$this->resize($width, $height); |
|
140
|
3 |
|
} elseif ($targetRatio > $actualRatio) { |
|
141
|
|
|
// Resize to width, crop extra height |
|
142
|
1 |
|
$this->resizeToWidth($width); |
|
143
|
1 |
|
$this->crop($width, $height); |
|
144
|
|
|
} else { |
|
145
|
|
|
// Resize to height, crop additional width |
|
146
|
2 |
|
$this->resizeToHeight($height); |
|
147
|
2 |
|
$this->crop($width, $height); |
|
148
|
|
|
} |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
9 |
|
public function resize(int $width, int $height): void |
|
152
|
|
|
{ |
|
153
|
9 |
|
$newImage = \imagecreatetruecolor($width, $height); |
|
154
|
|
|
|
|
155
|
9 |
|
$this->strategy->handleTransparency($newImage, $this->image); |
|
|
|
|
|
|
156
|
|
|
|
|
157
|
|
|
// Now resample the image |
|
158
|
9 |
|
\imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight()); |
|
159
|
|
|
|
|
160
|
|
|
// And allocate to $this |
|
161
|
9 |
|
$this->image = $newImage; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** $trim can be either left or center */ |
|
165
|
4 |
|
public function crop(int $width, int $height, string $trim = 'center'): void |
|
166
|
|
|
{ |
|
167
|
4 |
|
$offsetX = 0; |
|
168
|
4 |
|
$offsetY = 0; |
|
169
|
4 |
|
$currentWidth = $this->getWidth(); |
|
170
|
4 |
|
$currentHeight = $this->getHeight(); |
|
171
|
|
|
|
|
172
|
4 |
|
if ($trim !== 'left') { |
|
173
|
4 |
|
$offsetX = $this->getOffsetX($currentWidth, $width, $trim); |
|
174
|
4 |
|
$offsetY = $this->getOffsetY($currentHeight, $height, $trim); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
4 |
|
$newImage = \imagecreatetruecolor($width, $height); |
|
178
|
4 |
|
\imagecopyresampled($newImage, $this->image, 0, 0, $offsetX, $offsetY, $width, $height, $width, $height); |
|
179
|
4 |
|
$this->image = $newImage; |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
4 |
|
private function getOffsetX(int $currentWidth, int $width, string $trim): int |
|
183
|
|
|
{ |
|
184
|
4 |
|
$offsetX = 0; |
|
185
|
|
|
|
|
186
|
4 |
|
if ($currentWidth > $width) { |
|
187
|
3 |
|
$diff = $currentWidth - $width; |
|
188
|
3 |
|
$offsetX = ($trim === 'center') ? $diff / 2 : $diff; //full diff for trim right |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
4 |
|
return (int) $offsetX; |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
4 |
|
private function getOffsetY(int $currentHeight, int $height, string $trim): int |
|
195
|
|
|
{ |
|
196
|
4 |
|
$offsetY = 0; |
|
197
|
|
|
|
|
198
|
4 |
|
if ($currentHeight > $height) { |
|
199
|
2 |
|
$diff = $currentHeight - $height; |
|
200
|
2 |
|
$offsetY = ($trim === 'center') ? $diff / 2 : $diff; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
4 |
|
return (int) $offsetY; |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
/** @throws NothingLoadedException */ |
|
207
|
6 |
|
public function getHeader(): string |
|
208
|
|
|
{ |
|
209
|
6 |
|
if (!$this->strategy) { |
|
210
|
1 |
|
throw new NothingLoadedException(); |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
5 |
|
return $this->strategy->getContentType(); |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
8 |
|
public function destroy(): void |
|
217
|
|
|
{ |
|
218
|
8 |
|
\imagedestroy($this->image); |
|
219
|
|
|
} |
|
220
|
|
|
} |
|
221
|
|
|
|
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.