Cài đặt và cấu hình
1) Cài package Intervention Image v3
composer require intervention/image
2) Chọn driver Imagick (khuyến nghị)
Cài Imagick extension cho PHP (tùy môi trường):
# Ubuntu
sudo apt-get install -y php-imagick
# Kiểm tra
php -m | grep imagick
Cấu hình driver trong code (v3 có API mới, không cần publish config):
use Intervention\\Image\\ImageManager;
use Intervention\\Image\\Drivers\\Imagick\\Driver;
$manager = new ImageManager(new Driver());
Tài liệu: Intervention Image – Docs.
ImageCompressionService (Service Pattern)
Service sau gói gọn các trường hợp: từ path, URL, base64, trả về base64, và resize + nén.
<?php
namespace App\\Services;
use Intervention\\Image\\ImageManager;
use Intervention\\Image\\Drivers\\Imagick\\Driver;
use Illuminate\\Support\\Facades\\Log;
class ImageCompressionService
{
protected ImageManager $manager;
public function __construct()
{
$this->manager = new ImageManager(new Driver());
}
/**
* Nén ảnh từ đường dẫn file về WebP
*/
public function compressFromPath(string $inputPath, string $outputPath, int $quality = 80): bool
{
try {
$image = $this->manager->read($inputPath);
// Tối ưu: strip metadata
$image->orientate();
$image->toWebp($quality)->save($outputPath);
return true;
} catch (\\Throwable $e) {
Log::error('Image compression failed: '.$e->getMessage());
return false;
}
}
/**
* Nén ảnh từ URL về WebP
*/
public function compressFromUrl(string $url, string $outputPath, int $quality = 80): bool
{
try {
$image = $this->manager->read($url);
$image->orientate();
$image->toWebp($quality)->save($outputPath);
return true;
} catch (\\Throwable $e) {
Log::error('Image compression from URL failed: '.$e->getMessage());
return false;
}
}
/**
* Nén ảnh từ base64 về WebP
*/
public function compressFromBase64(string $base64String, string $outputPath, int $quality = 80): bool
{
try {
$image = $this->manager->read($base64String);
$image->orientate();
$image->toWebp($quality)->save($outputPath);
return true;
} catch (\\Throwable $e) {
Log::error('Image compression from base64 failed: '.$e->getMessage());
return false;
}
}
/**
* Nén ảnh và trả về data URI (base64)
*/
public function compressToBase64(string $inputPath, int $quality = 80): ?string
{
try {
$image = $this->manager->read($inputPath);
$image->orientate();
return $image->toWebp($quality)->toDataUri();
} catch (\\Throwable $e) {
Log::error('Image compression to base64 failed: '.$e->getMessage());
return null;
}
}
/**
* Resize theo max width/height rồi nén WebP
*/
public function resizeAndCompress(
string $inputPath,
string $outputPath,
int $maxWidth,
int $maxHeight,
int $quality = 80
): bool {
try {
$image = $this->manager->read($inputPath);
// scaleDown giữ tỉ lệ, không vượt quá khung
$image->scaleDown($maxWidth, $maxHeight)
->orientate()
->toWebp($quality)
->save($outputPath);
return true;
} catch (\\Throwable $e) {
Log::error('Image resize and compression failed: '.$e->getMessage());
return false;
}
}
}
Gist tham khảo: Service Compress Img Laravel
Tài liệu: Intervention Image – Docs
Lưu file đúng chuẩn (Storage)
Lưu vào
storage/app/public/images/...
để tiện public.Tạo symlink nếu chưa có:
php artisan storage:link
Trả URL public:
asset('storage/images/xyz.webp')
.
Ví dụ Controller + Request
// app/Http/Requests/StoreImageRequest.php
namespace App\\Http\\Requests;
use Illuminate\\Foundation\\Http\\FormRequest;
class StoreImageRequest extends FormRequest
{
public function rules(): array
{
return [
'image' => ['required','image','mimes:jpeg,png,jpg,webp','max:5120'], // 5MB
'max_width' => ['nullable','integer','min:16','max:7680'],
'max_height' => ['nullable','integer','min:16','max:4320'],
'quality' => ['nullable','integer','min:1','max:100'],
];
}
}
// app/Http/Controllers/ImageController.php
namespace App\\Http\\Controllers;
use App\\Http\\Requests\\StoreImageRequest;
use App\\Services\\ImageCompressionService;
use Illuminate\\Support\\Str;
class ImageController extends Controller
{
public function store(StoreImageRequest $request, ImageCompressionService $svc)
{
$file = $request->file('image');
$name = Str::uuid().'.webp';
$path = storage_path('app/public/images/'.$name);
$maxW = (int)($request->input('max_width', 1920));
$maxH = (int)($request->input('max_height', 1080));
$quality = (int)($request->input('quality', 80));
$file->move(storage_path('app/tmp'), $tmp = storage_path('app/tmp/'.Str::uuid().'.tmp'));
$ok = $svc->resizeAndCompress($tmp, $path, $maxW, $maxH, $quality);
@unlink($tmp);
abort_unless($ok, 500, 'Compress failed');
return response()->json([
'url' => asset('storage/images/'.$name),
'width' => $maxW,
'height' => $maxH,
'quality' => $quality,
]);
}
}
Route:
Route::post('/images', [ImageController::class, 'store'])->name('[images.store](<http://images.store>)');
Tối ưu thêm cho sản xuất
Resize trước rồi mới nén để giảm kích thước đáng kể.
Chọn chất lượng 70–85 cho WebP là hợp lý; ảnh hero có thể 85–90.
Strip metadata (EXIF) để giảm dung lượng, đã minh họa bằng
orientate()
và xuất lại.Tạo nhiều kích thước (responsive images) và dùng
srcset/sizes
.Lazy‑load: thêm
loading="lazy"
cho ảnh dưới màn hình đầu tiên.Hàng loạt ảnh: đẩy xử lý vào queue để không chặn request.
Bảo mật: chỉ cho phép mimes ảnh hợp lệ, kiểm tra kích thước, từ chối file lạ.
Hiển thị ảnh (Blade)
<picture>
<source srcset=" asset('storage/images/post-1920.webp') " type="image/webp">
<img
src=" asset('storage/images/post-1920.webp') "
alt="Ảnh minh họa nén WebP trong Laravel"
width="1920" height="1080"
loading="lazy" decoding="async">
</picture>
FAQ
WebP có luôn nhỏ hơn JPEG/PNG không?
Đa phần là có, nhất là với ảnh chụp. Với đồ họa phẳng, SVG/PNG tối ưu vẫn có thể nhỏ hơn.
Không có Imagick thì sao?
Có thể dùng driver GD, nhưng chất lượng WebP và hiệu năng thường kém hơn Imagick.
Có cần giữ bản gốc?
Nên. Lưu bản gốc (original) để có thể re‑encode khi đổi chất lượng/kích thước.
Làm sao tránh ghi đè tên?
Đặt tên file bằng uuid()
hoặc hash nội dung.
Tài liệu tham khảo
Intervention Image – Docs: Intervention Image
Gist demo Service: Service Compress Img Laravel
Laravel Storage: Laravel Docs – Filesystem