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.