Laravel 13 图片处理完全指南

图片处理是现代 Web 应用的重要功能。本文将深入探讨 Laravel 13 中图片处理的各种技巧和最佳实践。

安装配置

安装 Intervention Image

1
composer require intervention/image

配置

1
2
3
4
// config/image.php
return [
'driver' => env('IMAGE_DRIVER', 'gd'),
];

基础图片操作

创建图片实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Intervention\Image\Facades\Image;

// 从文件创建
$img = Image::make('public/image.jpg');

// 从上传文件创建
$img = Image::make($request->file('image'));

// 从二进制数据创建
$img = Image::make(file_get_contents('image.jpg'));

// 从 Base64 创建
$img = Image::make('data:image/png;base64,...');

// 创建空白图片
$img = Image::canvas(800, 600, '#ffffff');

调整大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 调整尺寸
$img->resize(300, 200);

// 只指定宽度,高度自动
$img->resize(300, null);

// 只指定高度,宽度自动
$img->resize(null, 200);

// 保持宽高比
$img->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
});

// 防止放大
$img->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});

裁剪

1
2
3
4
5
6
7
8
9
10
11
12
13
// 居中裁剪
$img->crop(200, 200);

// 指定位置裁剪
$img->crop(200, 200, 100, 50);

// 智能裁剪
$img->fit(300, 200);

// 智能裁剪并居中
$img->fit(300, 200, function ($constraint) {
$constraint->upsize();
});

图片滤镜

调整亮度对比度

1
2
3
4
5
6
7
8
// 调整亮度 (-100 到 100)
$img->brightness(30);

// 调整对比度 (-100 到 100)
$img->contrast(50);

// 调整伽马值
$img->gamma(1.6);

颜色调整

1
2
3
4
5
6
7
8
9
10
11
// 转换为灰度
$img->greyscale();

// 反转颜色
$img->invert();

// 调整颜色
$img->colorize(100, 0, 0); // 增加红色

// 限制颜色数量
$img->limitColors(255, '#ffffff');

模糊与锐化

1
2
3
4
5
6
7
8
9
// 模糊
$img->blur();
$img->blur(15); // 更强的模糊

// 锐化
$img->sharpen(15);

// 像素化
$img->pixelate(12);

滤镜效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 应用预设滤镜
$img->filter(new Intervention\Image\Filters\DemoFilter);

// 自定义滤镜
class VintageFilter implements Intervention\Image\Filters\FilterInterface
{
public function applyFilter(Intervention\Image\Image $image)
{
return $image
->brightness(-10)
->contrast(10)
->colorize(20, 0, -20);
}
}

$img->filter(new VintageFilter);

添加水印

文字水印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$img->text('Watermark', 10, 10, function ($font) {
$font->size(24);
$font->color('#ffffff');
$font->align('left');
$font->valign('top');
$font->angle(45);
$font->file(public_path('fonts/arial.ttf'));
});

// 居中文字
$img->text('Center', $img->width() / 2, $img->height() / 2, function ($font) {
$font->size(40);
$font->color('#ffffff');
$font->align('center');
$font->valign('middle');
});

图片水印

1
2
3
4
5
6
7
8
9
10
11
$watermark = Image::make('watermark.png');

$img->insert($watermark, 'bottom-right', 10, 10);

$img->insert($watermark, 'center');

$img->insert($watermark, 'top-left', 0, 0);

// 半透明水印
$watermark->opacity(50);
$img->insert($watermark, 'bottom-right', 10, 10);

图片绘制

绘制形状

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 绘制线条
$img->line(10, 10, 100, 100, function ($draw) {
$draw->color('#ff0000');
$draw->width(3);
});

// 绘制矩形
$img->rectangle(10, 10, 100, 100, function ($draw) {
$draw->background('#0000ff');
$draw->border(2, '#ff0000');
});

// 绘制圆形
$img->circle(50, 50, 50, function ($draw) {
$draw->background('#00ff00');
$draw->border(2, '#000000');
});

// 绘制椭圆
$img->ellipse(80, 40, 50, 50, function ($draw) {
$draw->background('#ffff00');
});

填充

1
2
3
4
5
6
7
8
// 纯色填充
$img->fill('#ff0000');

// 渐变填充
$img->fill('linear:(#ff0000)-(#0000ff)');

// 图案填充
$img->fill(Image::make('pattern.png'));

图片编码

输出格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 编码为 JPEG
$img->encode('jpg', 80);

// 编码为 PNG
$img->encode('png');

// 编码为 WebP
$img->encode('webp', 90);

// 编码为 GIF
$img->encode('gif');

// 编码为 Base64
$base64 = (string) $img->encode('data-url');

保存图片

1
2
3
4
5
6
7
8
9
// 保存文件
$img->save('path/to/image.jpg');

// 指定质量
$img->save('path/to/image.jpg', 80);

// 保存为不同格式
$img->save('path/to/image.png');
$img->save('path/to/image.webp');

图片上传服务

完整上传服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php

namespace App\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

class ImageService
{
protected array $sizes = [
'thumbnail' => [150, 150],
'small' => [300, 300],
'medium' => [600, 600],
'large' => [1200, 1200],
];

protected array $watermarks = [
'large' => 'watermarks/large.png',
'medium' => 'watermarks/medium.png',
];

public function upload(UploadedFile $file, string $directory = 'images'): array
{
$hash = md5_file($file->path());
$extension = 'webp';
$filename = "{$hash}.{$extension}";

$paths = [];

// 原图
$original = Image::make($file);
$originalPath = "{$directory}/original/{$filename}";
Storage::put($originalPath, $original->encode('webp', 100));
$paths['original'] = $originalPath;

// 生成各种尺寸
foreach ($this->sizes as $name => [$width, $height]) {
$img = clone $original;

$img->fit($width, $height, function ($constraint) {
$constraint->upsize();
});

if (isset($this->watermarks[$name])) {
$this->addWatermark($img, $this->watermarks[$name]);
}

$path = "{$directory}/{$name}/{$filename}";
Storage::put($path, $img->encode('webp', 85));
$paths[$name] = $path;
}

return [
'paths' => $paths,
'filename' => $filename,
'width' => $original->width(),
'height' => $original->height(),
];
}

protected function addWatermark($img, string $watermarkPath): void
{
if (Storage::exists($watermarkPath)) {
$watermark = Image::make(Storage::path($watermarkPath));
$watermark->opacity(30);
$img->insert($watermark, 'bottom-right', 10, 10);
}
}

public function delete(string $directory, string $filename): void
{
Storage::delete("{$directory}/original/{$filename}");

foreach (array_keys($this->sizes) as $name) {
Storage::delete("{$directory}/{$name}/{$filename}");
}
}
}

图片优化

压缩优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php

namespace App\Services;

use Intervention\Image\Facades\Image;

class ImageOptimizer
{
public function optimize(string $path, array $options = []): void
{
$img = Image::make($path);

// 移除元数据
$img->orientate();

// 调整尺寸
$maxWidth = $options['max_width'] ?? 1920;
$maxHeight = $options['max_height'] ?? 1080;

if ($img->width() > $maxWidth || $img->height() > $maxHeight) {
$img->resize($maxWidth, $maxHeight, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}

// 限制颜色
if ($options['limit_colors'] ?? true) {
$img->limitColors(255);
}

// 保存
$quality = $options['quality'] ?? 85;
$format = $options['format'] ?? pathinfo($path, PATHINFO_EXTENSION);

$img->save($path, $quality, $format);
}

public function batchOptimize(string $directory, array $options = []): array
{
$results = [];
$files = Storage::allFiles($directory);

foreach ($files as $file) {
if ($this->isImage($file)) {
$path = Storage::path($file);
$originalSize = filesize($path);

$this->optimize($path, $options);

$newSize = filesize($path);
$saved = $originalSize - $newSize;

$results[] = [
'file' => $file,
'original_size' => $originalSize,
'new_size' => $newSize,
'saved' => $saved,
'saved_percent' => round(($saved / $originalSize) * 100, 2),
];
}
}

return $results;
}

protected function isImage(string $file): bool
{
$extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
return in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), $extensions);
}
}

图片验证

自定义验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Intervention\Image\Facades\Image;

class ValidImage implements ValidationRule
{
protected int $minWidth;
protected int $minHeight;
protected int $maxWidth;
protected int $maxHeight;

public function __construct(
int $minWidth = 0,
int $minHeight = 0,
int $maxWidth = 10000,
int $maxHeight = 10000
) {
$this->minWidth = $minWidth;
$this->minHeight = $minHeight;
$this->maxWidth = $maxWidth;
$this->maxHeight = $maxHeight;
}

public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$img = Image::make($value);

if ($img->width() < $this->minWidth) {
$fail("图片宽度至少需要 {$this->minWidth} 像素");
}

if ($img->height() < $this->minHeight) {
$fail("图片高度至少需要 {$this->minHeight} 像素");
}

if ($img->width() > $this->maxWidth) {
$fail("图片宽度不能超过 {$this->maxWidth} 像素");
}

if ($img->height() > $this->maxHeight) {
$fail("图片高度不能超过 {$this->maxHeight} 像素");
}
} catch (\Exception $e) {
$fail('无效的图片文件');
}
}
}

图片处理队列

异步处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

namespace App\Jobs;

use App\Models\Image;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

class ProcessImage implements ShouldQueue
{
use Queueable;

protected array $sizes = [
'thumbnail' => [150, 150],
'medium' => [600, 600],
'large' => [1200, 1200],
];

public function __construct(
protected Image $imageModel
) {}

public function handle(): void
{
$originalPath = $this->imageModel->original_path;
$directory = dirname($originalPath);
$filename = basename($originalPath);

$img = Image::make(Storage::path($originalPath));

$paths = [];

foreach ($this->sizes as $name => [$width, $height]) {
$resized = clone $img;

$resized->fit($width, $height, function ($constraint) {
$constraint->upsize();
});

$path = "{$directory}/{$name}/{$filename}";
Storage::put($path, $resized->encode('webp', 85));
$paths[$name] = $path;
}

$this->imageModel->update([
'paths' => $paths,
'width' => $img->width(),
'height' => $img->height(),
'processed_at' => now(),
]);
}
}

总结

Laravel 13 的图片处理提供了:

  • 强大的图片编辑功能
  • 灵活的尺寸调整
  • 丰富的滤镜效果
  • 水印添加支持
  • 多格式编码输出
  • 批量处理优化
  • 异步队列处理

掌握图片处理技巧可以构建功能丰富的媒体应用。