@artilingo/artiframe-cli 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core-stubs/bin/SystemMethod.php +600 -521
- package/core-stubs/bin/ViewMethod.php +591 -393
- package/core-stubs/docs/de.html +853 -1172
- package/core-stubs/docs/en.html +585 -904
- package/core-stubs/docs/es.html +934 -1253
- package/core-stubs/docs/fr.html +913 -1232
- package/core-stubs/docs/tr.html +4212 -5021
- package/package.json +1 -1
- package/src/App.php +38 -0
- package/src/Commands/AddCommand.php +1 -1
- package/src/Commands/AuthCommand.php +219 -0
- package/src/Commands/IssuesCommand.php +174 -0
- package/src/Commands/ListCommand.php +2 -1
- package/src/Commands/MakeApiCommand.php +1 -1
- package/src/Commands/MakeClassCommand.php +1 -1
- package/src/Commands/MakeViewCommand.php +1 -1
- package/src/Commands/NewProjectCommand.php +37 -25
- package/src/Commands/RemoveCommand.php +21 -10
- package/src/Commands/ServeCommand.php +29 -9
- package/src/Commands/SuggestCommand.php +143 -0
- package/src/Commands/TableCommand.php +2 -1
- package/src/Commands/UpgradeCommand.php +209 -0
- package/src/Commands/VersionCommand.php +2 -1
- package/src/Lang/de.php +9 -1
- package/src/Lang/en.php +8 -0
- package/src/Lang/es.php +9 -1
- package/src/Lang/fr.php +8 -0
- package/src/Lang/tr.php +10 -2
- package/src/Services/Safeguard.php +25 -0
- package/stubs/service/image.stub +334 -334
- package/stubs/service/iyzico.stub +637 -637
- package/stubs/service/jwt.stub +312 -312
- package/stubs/service/phpmailer.stub +143 -143
- package/stubs/service/pusher.stub +232 -232
- package/stubs/service/qrcode.stub +169 -169
- package/stubs/service/redis.stub +361 -361
- package/stubs/service/s3.stub +377 -377
- package/stubs/service/sentry.stub +76 -106
- package/stubs/service/stripe.stub +526 -526
- package/stubs/service/twilio.stub +361 -361
- package/core-stubs/app/R2Manager.php +0 -130
package/stubs/service/image.stub
CHANGED
|
@@ -1,334 +1,334 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace Service;
|
|
4
|
-
|
|
5
|
-
use Intervention\Image\ImageManager;
|
|
6
|
-
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
|
|
7
|
-
use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
|
|
8
|
-
use Intervention\Image\Interfaces\ImageInterface;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* ImageProcessor — Wrapper for Intervention Image v3.
|
|
12
|
-
*
|
|
13
|
-
* Provides simple methods for common image operations such as
|
|
14
|
-
* resizing, cropping, watermarking, format conversion, and compression.
|
|
15
|
-
*
|
|
16
|
-
* Required ENV variables:
|
|
17
|
-
* IMAGE_DRIVER — "gd" or "imagick" (default: gd)
|
|
18
|
-
* IMAGE_QUALITY — Default JPEG/WebP quality 1-100 (default: 80)
|
|
19
|
-
*
|
|
20
|
-
* @package Service
|
|
21
|
-
*/
|
|
22
|
-
class ImageProcessor
|
|
23
|
-
{
|
|
24
|
-
/** @var ImageManager Intervention Image manager instance */
|
|
25
|
-
private ImageManager $manager;
|
|
26
|
-
|
|
27
|
-
/** @var int Default output quality (1-100) */
|
|
28
|
-
private int $quality;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Initialize the image processor.
|
|
32
|
-
*
|
|
33
|
-
* Reads IMAGE_DRIVER and IMAGE_QUALITY from $_ENV.
|
|
34
|
-
* Falls back to GD driver and quality 80 when not specified.
|
|
35
|
-
*/
|
|
36
|
-
public function __construct()
|
|
37
|
-
{
|
|
38
|
-
$driver = strtolower($_ENV['IMAGE_DRIVER'] ?? 'gd');
|
|
39
|
-
|
|
40
|
-
$this->manager = match ($driver) {
|
|
41
|
-
'imagick' => new ImageManager(new ImagickDriver()),
|
|
42
|
-
default => new ImageManager(new GdDriver()),
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
$this->quality = (int) ($_ENV['IMAGE_QUALITY'] ?? 80);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------
|
|
49
|
-
// Resize
|
|
50
|
-
// ---------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Resize an image.
|
|
54
|
-
*
|
|
55
|
-
* When $height is null the aspect ratio is preserved automatically.
|
|
56
|
-
* When both dimensions are given the image is resized exactly.
|
|
57
|
-
*
|
|
58
|
-
* @param string $inputPath Absolute path to source image.
|
|
59
|
-
* @param string $outputPath Absolute path for the resized image.
|
|
60
|
-
* @param int $width Target width in pixels.
|
|
61
|
-
* @param int|null $height Target height in pixels (null = auto).
|
|
62
|
-
* @return bool True on success.
|
|
63
|
-
*/
|
|
64
|
-
public function resize(string $inputPath, string $outputPath, int $width, int $height = null): bool
|
|
65
|
-
{
|
|
66
|
-
try {
|
|
67
|
-
$image = $this->manager->read($inputPath);
|
|
68
|
-
|
|
69
|
-
if ($height === null) {
|
|
70
|
-
// Scale down proportionally
|
|
71
|
-
$image->scaleDown(width: $width);
|
|
72
|
-
} else {
|
|
73
|
-
$image->resize($width, $height);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
$image->save($outputPath, quality: $this->quality);
|
|
77
|
-
|
|
78
|
-
return true;
|
|
79
|
-
} catch (\Throwable $e) {
|
|
80
|
-
error_log('[ImageProcessor::resize] ' . $e->getMessage());
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ---------------------------------------------------------------
|
|
86
|
-
// Crop
|
|
87
|
-
// ---------------------------------------------------------------
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Crop a rectangular region from an image.
|
|
91
|
-
*
|
|
92
|
-
* @param string $inputPath Absolute path to source image.
|
|
93
|
-
* @param string $outputPath Absolute path for the cropped image.
|
|
94
|
-
* @param int $width Crop width in pixels.
|
|
95
|
-
* @param int $height Crop height in pixels.
|
|
96
|
-
* @param int $x X-offset from the top-left corner.
|
|
97
|
-
* @param int $y Y-offset from the top-left corner.
|
|
98
|
-
* @return bool True on success.
|
|
99
|
-
*/
|
|
100
|
-
public function crop(string $inputPath, string $outputPath, int $width, int $height, int $x = 0, int $y = 0): bool
|
|
101
|
-
{
|
|
102
|
-
try {
|
|
103
|
-
$image = $this->manager->read($inputPath);
|
|
104
|
-
$image->crop($width, $height, $x, $y);
|
|
105
|
-
$image->save($outputPath, quality: $this->quality);
|
|
106
|
-
|
|
107
|
-
return true;
|
|
108
|
-
} catch (\Throwable $e) {
|
|
109
|
-
error_log('[ImageProcessor::crop] ' . $e->getMessage());
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ---------------------------------------------------------------
|
|
115
|
-
// Thumbnail
|
|
116
|
-
// ---------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Create a square thumbnail by cover-cropping to the given size.
|
|
120
|
-
*
|
|
121
|
-
* The image is resized so the shortest side matches $size,
|
|
122
|
-
* then center-cropped to produce a perfect square.
|
|
123
|
-
*
|
|
124
|
-
* @param string $inputPath Absolute path to source image.
|
|
125
|
-
* @param string $outputPath Absolute path for the thumbnail.
|
|
126
|
-
* @param int $size Square dimension in pixels (default 200).
|
|
127
|
-
* @return bool True on success.
|
|
128
|
-
*/
|
|
129
|
-
public function thumbnail(string $inputPath, string $outputPath, int $size = 200): bool
|
|
130
|
-
{
|
|
131
|
-
try {
|
|
132
|
-
$image = $this->manager->read($inputPath);
|
|
133
|
-
$image->cover($size, $size);
|
|
134
|
-
$image->save($outputPath, quality: $this->quality);
|
|
135
|
-
|
|
136
|
-
return true;
|
|
137
|
-
} catch (\Throwable $e) {
|
|
138
|
-
error_log('[ImageProcessor::thumbnail] ' . $e->getMessage());
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ---------------------------------------------------------------
|
|
144
|
-
// Watermark
|
|
145
|
-
// ---------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Overlay a watermark image onto the source image.
|
|
149
|
-
*
|
|
150
|
-
* @param string $inputPath Absolute path to source image.
|
|
151
|
-
* @param string $outputPath Absolute path for the watermarked image.
|
|
152
|
-
* @param string $watermarkPath Absolute path to the watermark image (PNG recommended).
|
|
153
|
-
* @param string $position Placement position: top-left, top, top-right,
|
|
154
|
-
* left, center, right, bottom-left, bottom, bottom-right.
|
|
155
|
-
* @param int $opacity Watermark opacity 0-100 (default 50).
|
|
156
|
-
* @return bool True on success.
|
|
157
|
-
*/
|
|
158
|
-
public function watermark(
|
|
159
|
-
string $inputPath,
|
|
160
|
-
string $outputPath,
|
|
161
|
-
string $watermarkPath,
|
|
162
|
-
string $position = 'bottom-right',
|
|
163
|
-
int $opacity = 50
|
|
164
|
-
): bool {
|
|
165
|
-
try {
|
|
166
|
-
$image = $this->manager->read($inputPath);
|
|
167
|
-
$watermark = $this->manager->read($watermarkPath);
|
|
168
|
-
|
|
169
|
-
// Map human-readable position to Intervention's place() arguments
|
|
170
|
-
$positionMap = [
|
|
171
|
-
'top-left' => 'top-left',
|
|
172
|
-
'top' => 'top',
|
|
173
|
-
'top-right' => 'top-right',
|
|
174
|
-
'left' => 'left',
|
|
175
|
-
'center' => 'center',
|
|
176
|
-
'right' => 'right',
|
|
177
|
-
'bottom-left' => 'bottom-left',
|
|
178
|
-
'bottom' => 'bottom',
|
|
179
|
-
'bottom-right' => 'bottom-right',
|
|
180
|
-
];
|
|
181
|
-
|
|
182
|
-
$pos = $positionMap[$position] ?? 'bottom-right';
|
|
183
|
-
|
|
184
|
-
$image->place($watermark, $pos, opacity: $opacity);
|
|
185
|
-
$image->save($outputPath, quality: $this->quality);
|
|
186
|
-
|
|
187
|
-
return true;
|
|
188
|
-
} catch (\Throwable $e) {
|
|
189
|
-
error_log('[ImageProcessor::watermark] ' . $e->getMessage());
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ---------------------------------------------------------------
|
|
195
|
-
// Format Conversion — WebP
|
|
196
|
-
// ---------------------------------------------------------------
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Convert an image to WebP format.
|
|
200
|
-
*
|
|
201
|
-
* When $outputPath is null the file is saved alongside the original
|
|
202
|
-
* with a .webp extension.
|
|
203
|
-
*
|
|
204
|
-
* @param string $inputPath Absolute path to source image.
|
|
205
|
-
* @param string|null $outputPath Destination path (null = auto).
|
|
206
|
-
* @param int|null $quality WebP quality 1-100 (null = env default).
|
|
207
|
-
* @return bool True on success.
|
|
208
|
-
*/
|
|
209
|
-
public function toWebp(string $inputPath, string $outputPath = null, int $quality = null): bool
|
|
210
|
-
{
|
|
211
|
-
try {
|
|
212
|
-
$quality = $quality ?? $this->quality;
|
|
213
|
-
$outputPath = $outputPath ?? preg_replace('/\.[^.]+$/', '.webp', $inputPath);
|
|
214
|
-
|
|
215
|
-
$image = $this->manager->read($inputPath);
|
|
216
|
-
$image->toWebp($quality)->save($outputPath);
|
|
217
|
-
|
|
218
|
-
return true;
|
|
219
|
-
} catch (\Throwable $e) {
|
|
220
|
-
error_log('[ImageProcessor::toWebp] ' . $e->getMessage());
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ---------------------------------------------------------------
|
|
226
|
-
// Compress
|
|
227
|
-
// ---------------------------------------------------------------
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Re-compress an image to reduce file size.
|
|
231
|
-
*
|
|
232
|
-
* When $outputPath is null the original file is overwritten.
|
|
233
|
-
*
|
|
234
|
-
* @param string $inputPath Absolute path to source image.
|
|
235
|
-
* @param string|null $outputPath Destination path (null = overwrite original).
|
|
236
|
-
* @param int|null $quality Quality 1-100 (null = env default).
|
|
237
|
-
* @return bool True on success.
|
|
238
|
-
*/
|
|
239
|
-
public function compress(string $inputPath, string $outputPath = null, int $quality = null): bool
|
|
240
|
-
{
|
|
241
|
-
try {
|
|
242
|
-
$quality = $quality ?? $this->quality;
|
|
243
|
-
$outputPath = $outputPath ?? $inputPath;
|
|
244
|
-
|
|
245
|
-
$image = $this->manager->read($inputPath);
|
|
246
|
-
$image->save($outputPath, quality: $quality);
|
|
247
|
-
|
|
248
|
-
return true;
|
|
249
|
-
} catch (\Throwable $e) {
|
|
250
|
-
error_log('[ImageProcessor::compress] ' . $e->getMessage());
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// ---------------------------------------------------------------
|
|
256
|
-
// Info
|
|
257
|
-
// ---------------------------------------------------------------
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Retrieve basic information about an image file.
|
|
261
|
-
*
|
|
262
|
-
* @param string $path Absolute path to the image.
|
|
263
|
-
* @return array{width: int, height: int, mime: string, size: int}
|
|
264
|
-
* Associative array with image metadata.
|
|
265
|
-
* Returns an array with an 'error' key on failure.
|
|
266
|
-
*/
|
|
267
|
-
public function info(string $path): array
|
|
268
|
-
{
|
|
269
|
-
try {
|
|
270
|
-
$image = $this->manager->read($path);
|
|
271
|
-
|
|
272
|
-
return [
|
|
273
|
-
'width' => $image->width(),
|
|
274
|
-
'height' => $image->height(),
|
|
275
|
-
'mime' => mime_content_type($path),
|
|
276
|
-
'size' => filesize($path),
|
|
277
|
-
];
|
|
278
|
-
} catch (\Throwable $e) {
|
|
279
|
-
error_log('[ImageProcessor::info] ' . $e->getMessage());
|
|
280
|
-
return ['error' => $e->getMessage()];
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ---------------------------------------------------------------
|
|
285
|
-
// Rotate
|
|
286
|
-
// ---------------------------------------------------------------
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Rotate an image by a given angle (counter-clockwise).
|
|
290
|
-
*
|
|
291
|
-
* @param string $inputPath Absolute path to source image.
|
|
292
|
-
* @param string $outputPath Absolute path for the rotated image.
|
|
293
|
-
* @param float $angle Rotation angle in degrees.
|
|
294
|
-
* @return bool True on success.
|
|
295
|
-
*/
|
|
296
|
-
public function rotate(string $inputPath, string $outputPath, float $angle): bool
|
|
297
|
-
{
|
|
298
|
-
try {
|
|
299
|
-
$image = $this->manager->read($inputPath);
|
|
300
|
-
$image->rotate($angle);
|
|
301
|
-
$image->save($outputPath, quality: $this->quality);
|
|
302
|
-
|
|
303
|
-
return true;
|
|
304
|
-
} catch (\Throwable $e) {
|
|
305
|
-
error_log('[ImageProcessor::rotate] ' . $e->getMessage());
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// ---------------------------------------------------------------
|
|
311
|
-
// Greyscale
|
|
312
|
-
// ---------------------------------------------------------------
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Convert an image to greyscale.
|
|
316
|
-
*
|
|
317
|
-
* @param string $inputPath Absolute path to source image.
|
|
318
|
-
* @param string $outputPath Absolute path for the greyscale image.
|
|
319
|
-
* @return bool True on success.
|
|
320
|
-
*/
|
|
321
|
-
public function greyscale(string $inputPath, string $outputPath): bool
|
|
322
|
-
{
|
|
323
|
-
try {
|
|
324
|
-
$image = $this->manager->read($inputPath);
|
|
325
|
-
$image->greyscale();
|
|
326
|
-
$image->save($outputPath, quality: $this->quality);
|
|
327
|
-
|
|
328
|
-
return true;
|
|
329
|
-
} catch (\Throwable $e) {
|
|
330
|
-
error_log('[ImageProcessor::greyscale] ' . $e->getMessage());
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Src\Service;
|
|
4
|
+
|
|
5
|
+
use Intervention\Image\ImageManager;
|
|
6
|
+
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
|
|
7
|
+
use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
|
|
8
|
+
use Intervention\Image\Interfaces\ImageInterface;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ImageProcessor — Wrapper for Intervention Image v3.
|
|
12
|
+
*
|
|
13
|
+
* Provides simple methods for common image operations such as
|
|
14
|
+
* resizing, cropping, watermarking, format conversion, and compression.
|
|
15
|
+
*
|
|
16
|
+
* Required ENV variables:
|
|
17
|
+
* IMAGE_DRIVER — "gd" or "imagick" (default: gd)
|
|
18
|
+
* IMAGE_QUALITY — Default JPEG/WebP quality 1-100 (default: 80)
|
|
19
|
+
*
|
|
20
|
+
* @package Service
|
|
21
|
+
*/
|
|
22
|
+
class ImageProcessor
|
|
23
|
+
{
|
|
24
|
+
/** @var ImageManager Intervention Image manager instance */
|
|
25
|
+
private ImageManager $manager;
|
|
26
|
+
|
|
27
|
+
/** @var int Default output quality (1-100) */
|
|
28
|
+
private int $quality;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the image processor.
|
|
32
|
+
*
|
|
33
|
+
* Reads IMAGE_DRIVER and IMAGE_QUALITY from $_ENV.
|
|
34
|
+
* Falls back to GD driver and quality 80 when not specified.
|
|
35
|
+
*/
|
|
36
|
+
public function __construct()
|
|
37
|
+
{
|
|
38
|
+
$driver = strtolower($_ENV['IMAGE_DRIVER'] ?? 'gd');
|
|
39
|
+
|
|
40
|
+
$this->manager = match ($driver) {
|
|
41
|
+
'imagick' => new ImageManager(new ImagickDriver()),
|
|
42
|
+
default => new ImageManager(new GdDriver()),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
$this->quality = (int) ($_ENV['IMAGE_QUALITY'] ?? 80);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------
|
|
49
|
+
// Resize
|
|
50
|
+
// ---------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resize an image.
|
|
54
|
+
*
|
|
55
|
+
* When $height is null the aspect ratio is preserved automatically.
|
|
56
|
+
* When both dimensions are given the image is resized exactly.
|
|
57
|
+
*
|
|
58
|
+
* @param string $inputPath Absolute path to source image.
|
|
59
|
+
* @param string $outputPath Absolute path for the resized image.
|
|
60
|
+
* @param int $width Target width in pixels.
|
|
61
|
+
* @param int|null $height Target height in pixels (null = auto).
|
|
62
|
+
* @return bool True on success.
|
|
63
|
+
*/
|
|
64
|
+
public function resize(string $inputPath, string $outputPath, int $width, int $height = null): bool
|
|
65
|
+
{
|
|
66
|
+
try {
|
|
67
|
+
$image = $this->manager->read($inputPath);
|
|
68
|
+
|
|
69
|
+
if ($height === null) {
|
|
70
|
+
// Scale down proportionally
|
|
71
|
+
$image->scaleDown(width: $width);
|
|
72
|
+
} else {
|
|
73
|
+
$image->resize($width, $height);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
$image->save($outputPath, quality: $this->quality);
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
} catch (\Throwable $e) {
|
|
80
|
+
error_log('[ImageProcessor::resize] ' . $e->getMessage());
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------
|
|
86
|
+
// Crop
|
|
87
|
+
// ---------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Crop a rectangular region from an image.
|
|
91
|
+
*
|
|
92
|
+
* @param string $inputPath Absolute path to source image.
|
|
93
|
+
* @param string $outputPath Absolute path for the cropped image.
|
|
94
|
+
* @param int $width Crop width in pixels.
|
|
95
|
+
* @param int $height Crop height in pixels.
|
|
96
|
+
* @param int $x X-offset from the top-left corner.
|
|
97
|
+
* @param int $y Y-offset from the top-left corner.
|
|
98
|
+
* @return bool True on success.
|
|
99
|
+
*/
|
|
100
|
+
public function crop(string $inputPath, string $outputPath, int $width, int $height, int $x = 0, int $y = 0): bool
|
|
101
|
+
{
|
|
102
|
+
try {
|
|
103
|
+
$image = $this->manager->read($inputPath);
|
|
104
|
+
$image->crop($width, $height, $x, $y);
|
|
105
|
+
$image->save($outputPath, quality: $this->quality);
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
} catch (\Throwable $e) {
|
|
109
|
+
error_log('[ImageProcessor::crop] ' . $e->getMessage());
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------
|
|
115
|
+
// Thumbnail
|
|
116
|
+
// ---------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create a square thumbnail by cover-cropping to the given size.
|
|
120
|
+
*
|
|
121
|
+
* The image is resized so the shortest side matches $size,
|
|
122
|
+
* then center-cropped to produce a perfect square.
|
|
123
|
+
*
|
|
124
|
+
* @param string $inputPath Absolute path to source image.
|
|
125
|
+
* @param string $outputPath Absolute path for the thumbnail.
|
|
126
|
+
* @param int $size Square dimension in pixels (default 200).
|
|
127
|
+
* @return bool True on success.
|
|
128
|
+
*/
|
|
129
|
+
public function thumbnail(string $inputPath, string $outputPath, int $size = 200): bool
|
|
130
|
+
{
|
|
131
|
+
try {
|
|
132
|
+
$image = $this->manager->read($inputPath);
|
|
133
|
+
$image->cover($size, $size);
|
|
134
|
+
$image->save($outputPath, quality: $this->quality);
|
|
135
|
+
|
|
136
|
+
return true;
|
|
137
|
+
} catch (\Throwable $e) {
|
|
138
|
+
error_log('[ImageProcessor::thumbnail] ' . $e->getMessage());
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------
|
|
144
|
+
// Watermark
|
|
145
|
+
// ---------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Overlay a watermark image onto the source image.
|
|
149
|
+
*
|
|
150
|
+
* @param string $inputPath Absolute path to source image.
|
|
151
|
+
* @param string $outputPath Absolute path for the watermarked image.
|
|
152
|
+
* @param string $watermarkPath Absolute path to the watermark image (PNG recommended).
|
|
153
|
+
* @param string $position Placement position: top-left, top, top-right,
|
|
154
|
+
* left, center, right, bottom-left, bottom, bottom-right.
|
|
155
|
+
* @param int $opacity Watermark opacity 0-100 (default 50).
|
|
156
|
+
* @return bool True on success.
|
|
157
|
+
*/
|
|
158
|
+
public function watermark(
|
|
159
|
+
string $inputPath,
|
|
160
|
+
string $outputPath,
|
|
161
|
+
string $watermarkPath,
|
|
162
|
+
string $position = 'bottom-right',
|
|
163
|
+
int $opacity = 50
|
|
164
|
+
): bool {
|
|
165
|
+
try {
|
|
166
|
+
$image = $this->manager->read($inputPath);
|
|
167
|
+
$watermark = $this->manager->read($watermarkPath);
|
|
168
|
+
|
|
169
|
+
// Map human-readable position to Intervention's place() arguments
|
|
170
|
+
$positionMap = [
|
|
171
|
+
'top-left' => 'top-left',
|
|
172
|
+
'top' => 'top',
|
|
173
|
+
'top-right' => 'top-right',
|
|
174
|
+
'left' => 'left',
|
|
175
|
+
'center' => 'center',
|
|
176
|
+
'right' => 'right',
|
|
177
|
+
'bottom-left' => 'bottom-left',
|
|
178
|
+
'bottom' => 'bottom',
|
|
179
|
+
'bottom-right' => 'bottom-right',
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
$pos = $positionMap[$position] ?? 'bottom-right';
|
|
183
|
+
|
|
184
|
+
$image->place($watermark, $pos, opacity: $opacity);
|
|
185
|
+
$image->save($outputPath, quality: $this->quality);
|
|
186
|
+
|
|
187
|
+
return true;
|
|
188
|
+
} catch (\Throwable $e) {
|
|
189
|
+
error_log('[ImageProcessor::watermark] ' . $e->getMessage());
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------
|
|
195
|
+
// Format Conversion — WebP
|
|
196
|
+
// ---------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Convert an image to WebP format.
|
|
200
|
+
*
|
|
201
|
+
* When $outputPath is null the file is saved alongside the original
|
|
202
|
+
* with a .webp extension.
|
|
203
|
+
*
|
|
204
|
+
* @param string $inputPath Absolute path to source image.
|
|
205
|
+
* @param string|null $outputPath Destination path (null = auto).
|
|
206
|
+
* @param int|null $quality WebP quality 1-100 (null = env default).
|
|
207
|
+
* @return bool True on success.
|
|
208
|
+
*/
|
|
209
|
+
public function toWebp(string $inputPath, string $outputPath = null, int $quality = null): bool
|
|
210
|
+
{
|
|
211
|
+
try {
|
|
212
|
+
$quality = $quality ?? $this->quality;
|
|
213
|
+
$outputPath = $outputPath ?? preg_replace('/\.[^.]+$/', '.webp', $inputPath);
|
|
214
|
+
|
|
215
|
+
$image = $this->manager->read($inputPath);
|
|
216
|
+
$image->toWebp($quality)->save($outputPath);
|
|
217
|
+
|
|
218
|
+
return true;
|
|
219
|
+
} catch (\Throwable $e) {
|
|
220
|
+
error_log('[ImageProcessor::toWebp] ' . $e->getMessage());
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------
|
|
226
|
+
// Compress
|
|
227
|
+
// ---------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Re-compress an image to reduce file size.
|
|
231
|
+
*
|
|
232
|
+
* When $outputPath is null the original file is overwritten.
|
|
233
|
+
*
|
|
234
|
+
* @param string $inputPath Absolute path to source image.
|
|
235
|
+
* @param string|null $outputPath Destination path (null = overwrite original).
|
|
236
|
+
* @param int|null $quality Quality 1-100 (null = env default).
|
|
237
|
+
* @return bool True on success.
|
|
238
|
+
*/
|
|
239
|
+
public function compress(string $inputPath, string $outputPath = null, int $quality = null): bool
|
|
240
|
+
{
|
|
241
|
+
try {
|
|
242
|
+
$quality = $quality ?? $this->quality;
|
|
243
|
+
$outputPath = $outputPath ?? $inputPath;
|
|
244
|
+
|
|
245
|
+
$image = $this->manager->read($inputPath);
|
|
246
|
+
$image->save($outputPath, quality: $quality);
|
|
247
|
+
|
|
248
|
+
return true;
|
|
249
|
+
} catch (\Throwable $e) {
|
|
250
|
+
error_log('[ImageProcessor::compress] ' . $e->getMessage());
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------
|
|
256
|
+
// Info
|
|
257
|
+
// ---------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Retrieve basic information about an image file.
|
|
261
|
+
*
|
|
262
|
+
* @param string $path Absolute path to the image.
|
|
263
|
+
* @return array{width: int, height: int, mime: string, size: int}
|
|
264
|
+
* Associative array with image metadata.
|
|
265
|
+
* Returns an array with an 'error' key on failure.
|
|
266
|
+
*/
|
|
267
|
+
public function info(string $path): array
|
|
268
|
+
{
|
|
269
|
+
try {
|
|
270
|
+
$image = $this->manager->read($path);
|
|
271
|
+
|
|
272
|
+
return [
|
|
273
|
+
'width' => $image->width(),
|
|
274
|
+
'height' => $image->height(),
|
|
275
|
+
'mime' => mime_content_type($path),
|
|
276
|
+
'size' => filesize($path),
|
|
277
|
+
];
|
|
278
|
+
} catch (\Throwable $e) {
|
|
279
|
+
error_log('[ImageProcessor::info] ' . $e->getMessage());
|
|
280
|
+
return ['error' => $e->getMessage()];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------
|
|
285
|
+
// Rotate
|
|
286
|
+
// ---------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Rotate an image by a given angle (counter-clockwise).
|
|
290
|
+
*
|
|
291
|
+
* @param string $inputPath Absolute path to source image.
|
|
292
|
+
* @param string $outputPath Absolute path for the rotated image.
|
|
293
|
+
* @param float $angle Rotation angle in degrees.
|
|
294
|
+
* @return bool True on success.
|
|
295
|
+
*/
|
|
296
|
+
public function rotate(string $inputPath, string $outputPath, float $angle): bool
|
|
297
|
+
{
|
|
298
|
+
try {
|
|
299
|
+
$image = $this->manager->read($inputPath);
|
|
300
|
+
$image->rotate($angle);
|
|
301
|
+
$image->save($outputPath, quality: $this->quality);
|
|
302
|
+
|
|
303
|
+
return true;
|
|
304
|
+
} catch (\Throwable $e) {
|
|
305
|
+
error_log('[ImageProcessor::rotate] ' . $e->getMessage());
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---------------------------------------------------------------
|
|
311
|
+
// Greyscale
|
|
312
|
+
// ---------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Convert an image to greyscale.
|
|
316
|
+
*
|
|
317
|
+
* @param string $inputPath Absolute path to source image.
|
|
318
|
+
* @param string $outputPath Absolute path for the greyscale image.
|
|
319
|
+
* @return bool True on success.
|
|
320
|
+
*/
|
|
321
|
+
public function greyscale(string $inputPath, string $outputPath): bool
|
|
322
|
+
{
|
|
323
|
+
try {
|
|
324
|
+
$image = $this->manager->read($inputPath);
|
|
325
|
+
$image->greyscale();
|
|
326
|
+
$image->save($outputPath, quality: $this->quality);
|
|
327
|
+
|
|
328
|
+
return true;
|
|
329
|
+
} catch (\Throwable $e) {
|
|
330
|
+
error_log('[ImageProcessor::greyscale] ' . $e->getMessage());
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|