1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Rvdlee\ZfImageResizer\Service; |
4
|
|
|
|
5
|
|
|
use Exception; |
6
|
|
|
use Rvdlee\ZfImageResizer\Exception\InvalidArgumentException; |
7
|
|
|
use Rvdlee\ZfImageResizer\Interfaces\ImageResizerInterface; |
8
|
|
|
use Rvdlee\ZfImageResizer\Model\Image; |
9
|
|
|
use Zend\Log\LoggerInterface; |
10
|
|
|
|
11
|
|
|
class ImageResizerService |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* @var ImageResizerInterface |
15
|
|
|
*/ |
16
|
|
|
protected $adapter; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var LoggerInterface |
20
|
|
|
*/ |
21
|
|
|
protected $logger; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @param ImageResizerInterface $adapter |
25
|
|
|
* |
26
|
|
|
* @throws InvalidArgumentException |
27
|
|
|
*/ |
28
|
|
|
public function __construct(ImageResizerInterface $adapter, LoggerInterface $logger) |
29
|
|
|
{ |
30
|
|
|
$this->setLogger($logger); |
31
|
|
|
$this->getLogger()->info('Constucting ImageResizerService'); |
32
|
|
|
$this->setAdapter($adapter); |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @param string $imagePath |
37
|
|
|
* @param int $newWidth |
38
|
|
|
* @param int $newHeight |
39
|
|
|
* @param bool $returnBase64 |
40
|
|
|
* @return string |
41
|
|
|
* @throws InvalidArgumentException |
42
|
|
|
*/ |
43
|
|
|
public function resizeImage(string $imagePath, int $newWidth = 0, int $newHeight = 0, bool $returnBase64 = false): string |
44
|
|
|
{ |
45
|
|
|
/** @var Image $image */ |
46
|
|
|
$image = new Image($imagePath, $newWidth, $newHeight); |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* If the image already exists, return the path and notify via logger |
50
|
|
|
*/ |
51
|
|
|
if (file_exists($image->getTargetPath())) { |
52
|
|
|
$this->getLogger()->info('Image already exists, not resizing...'); |
53
|
|
|
return $image->getTargetPath(); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
try { |
57
|
|
|
if ($this->getAdapter()->canHandle($imagePath)) { |
58
|
|
|
/** @var string $command */ |
59
|
|
|
$command = $this->getAdapter()->resizeCommand($image, $returnBase64); |
60
|
|
|
|
61
|
|
|
$this->getLogger()->info(sprintf('Handling %s via %s', $imagePath, get_class($this->getAdapter()))); |
62
|
|
|
$output = shell_exec(sprintf('%s 2>&1', $command)); |
63
|
|
|
$this->getLogger()->info($output); |
64
|
|
|
} |
65
|
|
|
} catch (Exception $exception) { |
66
|
|
|
$this->getLogger()->err( |
67
|
|
|
sprintf('Exception when handling %s to via %s', $imagePath, get_class($this->getAdapter())) |
68
|
|
|
); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
return $image->getTargetPath(); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @param string $imagePath |
76
|
|
|
* @param int $cropWidth |
77
|
|
|
* @param int $cropHeight |
78
|
|
|
* @param string $mode |
79
|
|
|
* @param int $x |
80
|
|
|
* @param int $y |
81
|
|
|
* @return string |
82
|
|
|
* @throws InvalidArgumentException |
83
|
|
|
*/ |
84
|
|
|
public function cropImage(string $imagePath, int $cropWidth, int $cropHeight, string $mode = Image::MANUAL_CROP, int $x = 0, int $y = 0): string |
85
|
|
|
{ |
86
|
|
|
/** @var Image $image */ |
87
|
|
|
$image = new Image($imagePath, $cropWidth, $cropHeight, $mode, $x, $y); |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* If the image already exists, return the path and notify via logger |
91
|
|
|
*/ |
92
|
|
|
if (file_exists($image->getTargetPath())) { |
93
|
|
|
$this->getLogger()->info('Image already exists, not resizing...'); |
94
|
|
|
return $image->getTargetPath(); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
try { |
98
|
|
|
if ($this->getAdapter()->canHandle($imagePath)) { |
99
|
|
|
/** @var string $command */ |
100
|
|
|
$command = $this->getAdapter()->cropCommand($image, $mode, $x, $y); |
101
|
|
|
|
102
|
|
|
$this->getLogger()->info(sprintf('Handling %s via %s', $imagePath, get_class($this->getAdapter()))); |
103
|
|
|
$output = shell_exec(sprintf('%s 2>&1', $command)); |
104
|
|
|
$this->getLogger()->info($output); |
105
|
|
|
} |
106
|
|
|
} catch (Exception $exception) { |
107
|
|
|
$this->getLogger()->err( |
108
|
|
|
sprintf('Exception when handling %s to via %s', $imagePath, get_class($this->getAdapter())) |
109
|
|
|
); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
return $image->getTargetPath(); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @param string $imagePath |
117
|
|
|
* @param int $cropWidth |
118
|
|
|
* @param int $cropHeight |
119
|
|
|
* @param int $finalWidth |
120
|
|
|
* @param int $finalHeight |
121
|
|
|
* @param string $mode |
122
|
|
|
* @param int $x |
123
|
|
|
* @param int $y |
124
|
|
|
* @return string |
125
|
|
|
* @throws InvalidArgumentException |
126
|
|
|
*/ |
127
|
|
|
public function cropAndResize(string $imagePath, int $cropWidth, int $cropHeight, int $finalWidth, int $finalHeight, string $mode = Image::MANUAL_CROP, int $x = 0, int $y = 0): string |
128
|
|
|
{ |
129
|
|
|
// If final width has not been set, set it to the cropWidth as a fallback |
130
|
|
|
if ($finalWidth === 0) { |
131
|
|
|
$finalWidth = $cropWidth; |
132
|
|
|
} |
133
|
|
|
// If final width has not been set, set it to the cropHeight as a fallback |
134
|
|
|
if ($finalWidth === 0) { |
135
|
|
|
$finalWidth = $cropWidth; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
// First crop the image at the highest possible resolution for best effect |
139
|
|
|
$resized = $resizedCropped = $this->cropImage($imagePath, $cropWidth, $cropHeight, $mode, $x, $y); |
140
|
|
|
|
141
|
|
|
// Resize if the final height or width differs from the cropped width and height |
142
|
|
|
if ($cropWidth !== $finalWidth || $cropHeight !== $finalHeight) { |
143
|
|
|
// Resize that image to the desired final width and height |
144
|
|
|
$resizedCropped = $this->resizeImage($resized, $finalWidth, $finalHeight); |
145
|
|
|
|
146
|
|
|
// Delete the resized image, we dont need it anymore |
147
|
|
|
unlink($resized); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
// Return the cropped and resized image |
151
|
|
|
return $resizedCropped; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* @param string $filepath |
156
|
|
|
* @param int $width |
157
|
|
|
* @param int $height |
158
|
|
|
* @return string |
159
|
|
|
* @throws \Gumlet\ImageResizeException |
160
|
|
|
*/ |
161
|
|
|
public function scaleAndCropThumbnail(string $filepath, int $width, int $height): string |
162
|
|
|
{ |
163
|
|
|
[$orginalWidth, $originalHeight] = getimagesize($filepath); |
164
|
|
|
$ratio = ($orginalWidth > $originalHeight ? $orginalWidth : $originalHeight) / ($orginalWidth < $originalHeight ? $orginalWidth : $originalHeight); |
165
|
|
|
|
166
|
|
|
if ($orginalWidth > $originalHeight) { |
167
|
|
|
$targetWidth = round($height * $ratio); |
168
|
|
|
$scaledImage = $this->scaleThumbnail($filepath, (int) $targetWidth, $height); |
169
|
|
|
} else { |
170
|
|
|
$targetHeight = round($width * $ratio); |
171
|
|
|
$scaledImage = $this->scaleThumbnail($filepath, $width, (int) $targetHeight); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
$thumbpath = $this->generateThumbFilepath($filepath); |
|
|
|
|
175
|
|
|
|
176
|
|
|
// Crop the scaled image for best result |
177
|
|
|
$croppedImage = $this->cropImage($scaledImage, $width, $height, Image::CENTERED_CROP); |
178
|
|
|
|
179
|
|
|
// Delete the scaled image, this only served for improved thumb quality |
180
|
|
|
unlink($scaledImage); |
181
|
|
|
|
182
|
|
|
return $croppedImage; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Scale by ratio, width or height is optional but one needs to be provided, it will calculate the |
187
|
|
|
* ratio by either one of these. |
188
|
|
|
* |
189
|
|
|
* @param string $filepath |
190
|
|
|
* @param int $targetWidth |
191
|
|
|
* @param int $targetHeight |
192
|
|
|
* @return string |
193
|
|
|
* @throws \Gumlet\ImageResizeException |
194
|
|
|
*/ |
195
|
|
|
public function scaleThumbnail(string $filepath, int $targetWidth = 0, int $targetHeight = 0) |
196
|
|
|
{ |
197
|
|
|
if (!file_exists($filepath) && is_file($filepath)) { |
198
|
|
|
throw new InvalidArgumentException('File does not exist!'); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
if ($targetHeight >! 0 || $targetWidth >! 0) { |
202
|
|
|
throw new InvalidArgumentException('targetWidth or targetHeight needs to be defined.'); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
return $this->resizeImage($filepath, $targetWidth, $targetHeight); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
public function generateThumbFilepath(string $filepath): string |
209
|
|
|
{ |
210
|
|
|
// Extract the basename out of the path |
211
|
|
|
$filename = basename($filepath); |
212
|
|
|
// Get the filepath |
213
|
|
|
$explodedFilepath = explode(DIRECTORY_SEPARATOR, $filepath); |
214
|
|
|
// Reconstruct the base file path |
215
|
|
|
$baseFilepath = implode(DIRECTORY_SEPARATOR, array_slice($explodedFilepath, 0, count($explodedFilepath)-1)); |
216
|
|
|
// Explode the filename |
217
|
|
|
$explodedFilename = explode('.', $filename); |
218
|
|
|
// Generate new name |
219
|
|
|
$thumbname = sprintf( |
220
|
|
|
'%s.%s', |
221
|
|
|
bin2hex(random_bytes(64)), |
222
|
|
|
end($explodedFilename) |
223
|
|
|
); |
224
|
|
|
|
225
|
|
|
return $baseFilepath . DIRECTORY_SEPARATOR . $thumbname; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @return ImageResizerInterface |
230
|
|
|
*/ |
231
|
|
|
public function getAdapter(): ImageResizerInterface |
232
|
|
|
{ |
233
|
|
|
return $this->adapter; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* @param ImageResizerInterface $adapter |
238
|
|
|
* @return ImageResizerService |
239
|
|
|
*/ |
240
|
|
|
public function setAdapter(ImageResizerInterface $adapter): ImageResizerService |
241
|
|
|
{ |
242
|
|
|
$this->adapter = $adapter; |
243
|
|
|
return $this; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @return LoggerInterface |
248
|
|
|
*/ |
249
|
|
|
public function getLogger(): LoggerInterface |
250
|
|
|
{ |
251
|
|
|
return $this->logger; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @param LoggerInterface $logger |
256
|
|
|
* |
257
|
|
|
* @return ImageResizerService |
258
|
|
|
*/ |
259
|
|
|
public function setLogger(LoggerInterface $logger): ImageResizerService |
260
|
|
|
{ |
261
|
|
|
$this->logger = $logger; |
262
|
|
|
|
263
|
|
|
return $this; |
264
|
|
|
} |
265
|
|
|
} |