diff --git a/app/Enums/Watermark/ImageOption.php b/app/Enums/Watermark/ImageOption.php index 11c8c574..f6e5b38a 100644 --- a/app/Enums/Watermark/ImageOption.php +++ b/app/Enums/Watermark/ImageOption.php @@ -4,5 +4,27 @@ namespace App\Enums\Watermark; final class ImageOption { + /** @var string 水印图片 */ + const Image = 'image'; + /** @var string 水印位置 */ + const Position = 'position'; + + /** @var string 水印图片宽 */ + const Width = 'width'; + + /** @var string 水印图片高 */ + const Height = 'height'; + + /** @var string 旋转角度 */ + const Rotate = 'rotate'; + + /** @var string 不透明度 */ + const Opacity = 'opacity'; + + /** @var string X轴偏移量 */ + const X = 'x'; + + /** @var string Y轴偏移量 */ + const Y = 'y'; } diff --git a/app/Service/ImageService.php b/app/Service/ImageService.php index 2bb473dd..8e843fb9 100644 --- a/app/Service/ImageService.php +++ b/app/Service/ImageService.php @@ -11,6 +11,7 @@ use App\Enums\StrategyKey; use App\Enums\UserConfigKey; use App\Enums\UserStatus; use App\Enums\Watermark\FontOption; +use App\Enums\Watermark\ImageOption; use App\Exceptions\UploadException; use App\Models\Group; use App\Models\Image; @@ -45,7 +46,7 @@ class ImageService { $file = $request->file('file'); - if (is_null($user) && ! Utils::config(ConfigKey::IsAllowGuestUpload, true)) { + if (is_null($user) && !Utils::config(ConfigKey::IsAllowGuestUpload, true)) { throw new UploadException('管理员关闭了游客上传'); } @@ -60,8 +61,8 @@ class ImageService 'configs' => collect([LocalOption::Root => config('filesystems.disks.uploads.root')]), ]); - if (! is_null($user)) { - if (Utils::config(ConfigKey::IsUserNeedVerify) && ! $user->email_verified_at) { + if (!is_null($user)) { + if (Utils::config(ConfigKey::IsUserNeedVerify) && !$user->email_verified_at) { throw new UploadException('账户未验证'); } @@ -99,7 +100,7 @@ class ImageService } } - if (! in_array($file->extension(), $configs->get(GroupConfigKey::AcceptedFileSuffixes))) { + if (!in_array($file->extension(), $configs->get(GroupConfigKey::AcceptedFileSuffixes))) { throw new UploadException('不支持的文件类型'); } @@ -119,7 +120,7 @@ class ImageService $sql = collect(array_keys($array))->transform(function ($range) use ($carbon, $format) { return "count(if(`created_at` between '{$carbon->parse("-1 {$range}")->format($format)}' and '{$carbon->format($format)}', 1, null)) as {$range}"; })->implode(', '); - $statistics = Image::query()->selectRaw($sql)->when(! is_null($user), function (Builder $builder) use ($user) { + $statistics = Image::query()->selectRaw($sql)->when(!is_null($user), function (Builder $builder) use ($user) { $builder->where('user_id', $user->id); }, function (Builder $builder) use ($request) { $builder->where('uploaded_ip', $request->ip())->whereNull('user_id'); @@ -171,12 +172,14 @@ class ImageService } DB::transaction(function () use ($image, $user, $filesystem, $existing) { - if (! $image->save()) { + if (!$image->save()) { // 删除文件 - if (is_null($existing)) $filesystem->delete($image->pathname); + if (is_null($existing)) { + $filesystem->delete($image->pathname); + } throw new UploadException('图片保存失败'); } - if (! is_null($user)) { + if (!is_null($user)) { $user->increment('image_num'); } }, 3); @@ -200,8 +203,8 @@ class ImageService /** * 合成验证码 * - * @param mixed $image - * @param Collection $configs + * @param mixed $image + * @param Collection $configs * @return \Intervention\Image\Image */ public function stickWatermark(mixed $image, Collection $configs): \Intervention\Image\Image @@ -211,50 +214,65 @@ class ImageService $image = InterventionImage::make($image); $position = $options->get(FontOption::Position, 'top-right'); + $offsetX = (int) $options->get(FontOption::X, 10); + $offsetY = (int) $options->get(FontOption::Y, 10); if ($driver === 'font') { $text = $options->get(FontOption::Text, Utils::config(ConfigKey::SiteName)); $font = new Font(urldecode($text)); $font->valign('top') - ->file($options->get(FontOption::Font)) - ->size($options->get(FontOption::Size, 50)) - ->angle($options->get(FontOption::Angle, 0)) + ->file(storage_path('app/public/test.ttf')) + ->size((int) $options->get(FontOption::Size, 50)) + ->angle((int) $options->get(FontOption::Angle, 0)) ->color($options->get(FontOption::Color, '000000')); // 十六进制 or rgba $box = $font->getBoxSize(); $font->text($text); $manager = new ImageManager(); $canvas = $manager->canvas($box['width'], $box['height']); - - // 原图宽高 - $imageWidth = $image->width(); - $imageHeight = $image->height(); - - // 水印画布宽高 - $canvasWidth = $canvas->width(); - $canvasHeight = $canvas->height(); - - $offsetX = $options->get(FontOption::X, 0); - $offsetY = $options->get(FontOption::Y, 0); - - if ($position === 'tiled') { - // 平铺水印 - for ($x = 0; $x < $imageWidth; $x++) { - for ($y = 0; $y < $imageHeight; $y++) { - $image->text($text, $x, $y, fn (Font &$f) => $f = $font); - $y += $canvasHeight + $offsetY; - } - $x += $canvasWidth + $offsetX; - } - } else { - $font->applyToImage($canvas); - $image->insert($manager->make($canvas), $position, $offsetX, $offsetY); - } + $font->applyToImage($canvas); + $watermark = $manager->make($canvas); } if ($driver === 'image') { - // TODO + $watermark = InterventionImage::make($options->get(ImageOption::Image)); + $opacity = (int) $options->get(ImageOption::Opacity, 0); + $width = $options->get(ImageOption::Width, 0); + $height = $options->get(ImageOption::Height, 0); + + if ($opacity && $opacity != 100) { + $watermark->opacity((int) min($opacity, 100)); + } + + if ($width + $height > 0) { + $watermark->resize($width ?: null, $height ?: null, function ($constraint) { + $constraint->aspectRatio(); + $constraint->upsize(); + }); + } } + // 原图宽高 + $imageWidth = $image->width(); + $imageHeight = $image->height(); + + // 水印画布宽高 + $watermarkWidth = $watermark->width(); + $watermarkHeight = $watermark->height(); + + if ($position === 'tiled') { + // 平铺水印 + for ($x = 0; $x < $imageWidth; $x++) { + for ($y = 0; $y < $imageHeight; $y++) { + $image->insert($watermark, '', $x, $y); + $y += $watermarkHeight + $offsetY; + } + $x += $watermarkWidth + $offsetX; + } + } else { + $image->insert($watermark, $position, $offsetX, $offsetY); + } + + // TODO 可读性差,需要改进 return $image; } @@ -265,10 +283,10 @@ class ImageService '{y}' => date('y'), '{m}' => date('m'), '{d}' => date('d'), - 'timestamp' => time(), + '{timestamp}' => time(), '{uniqid}' => uniqid(), - '{md5}' => md5(microtime() . Str::random()), - '{md5-16}' => substr(md5(microtime() . Str::random()), 0, 16), + '{md5}' => md5(microtime().Str::random()), + '{md5-16}' => substr(md5(microtime().Str::random()), 0, 16), '{str-random-16}' => Str::random(), '{str-random-10}' => Str::random(10), '{filename}' => rtrim($file->getClientOriginalName(), '.'.$file->extension()), diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index cf82fb56..4cbdfba2 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -6,6 +6,7 @@ use App\Enums\ConfigKey; use App\Enums\GroupConfigKey; use App\Enums\Mail\SmtpOption; use App\Enums\Watermark\FontOption; +use App\Enums\Watermark\ImageOption; use App\Models\Group; use Illuminate\Database\Seeder; use Illuminate\Support\Carbon; @@ -54,7 +55,7 @@ class DatabaseSeeder extends Seeder 'drivers' => [ 'font' => [ FontOption::Text => 'Lsky Pro', - FontOption::Position => 'top-right', + FontOption::Position => 'bottom-right', FontOption::Angle => 0, FontOption::Size => 50, FontOption::Font => '', @@ -63,7 +64,14 @@ class DatabaseSeeder extends Seeder FontOption::Y => 10, ], 'image' => [ - // TODO + ImageOption::Image => '', + ImageOption::Position => 'bottom-right', + ImageOption::Opacity => 100, + ImageOption::Rotate => 0, + ImageOption::Width => 0, + ImageOption::Height => 0, + ImageOption::X => 10, + ImageOption::Y => 10, ] ], ],