// +---------------------------------------------------------------------- declare (strict_types = 1); namespace addon\articles\service; /** * 图片服务(WebP 副本生成) * * 策略:**不覆盖原图**,只在同目录生成 `原文件名.webp` 副本; * 浏览器 Accept 含 image/webp 时才使用副本,否则回落原图。 * 依赖 GD(imagewebp);不支持时自动回落,不影响页面。 */ class ImageService { /** WebP 输出质量 */ const QUALITY = 80; /** * 取 WebP 版本地址(无副本且 GD 可用时现场生成) * * @param string $src 站内相对路径(如 /upload/2026/01/a.jpg) * @return string WebP 地址;不可用返回空串(调用方应回落到原图) */ public static function webpUrl(string $src): string { // 只处理站内相对路径,跳过站外 URL、data:、已是 webp 的图 if ($src === '' || strpos($src, '/') !== 0) { return ''; } if (preg_match('#^data:#i', $src) || preg_match('#\.webp($|\?)#i', $src)) { return ''; } $root = rtrim((string) public_path(), DIRECTORY_SEPARATOR); $srcFile = $root . $src; $dst = $src . '.webp'; $dstFile = $root . $dst; // 副本已存在直接用 if (is_file($dstFile)) { return $dst; } if (!is_file($srcFile) || !function_exists('imagewebp')) { return ''; } return self::convert($srcFile, $dstFile) ? $dst : ''; } /** * 转换一张图为 WebP */ private static function convert(string $srcFile, string $dstFile): bool { $info = @getimagesize($srcFile); if (!is_array($info) || empty($info['mime'])) { return false; } $im = null; switch ($info['mime']) { case 'image/jpeg': $im = @imagecreatefromjpeg($srcFile); break; case 'image/png': $im = @imagecreatefrompng($srcFile); if ($im) { imagepalettetotruecolor($im); imagealphablending($im, true); imagesavealpha($im, true); } break; case 'image/gif': $im = @imagecreatefromgif($srcFile); break; case 'image/webp': $im = @imagecreatefromwebp($srcFile); break; default: return false; } if (!$im) { return false; } $dir = dirname($dstFile); if (!is_dir($dir)) { @mkdir($dir, 0755, true); } $ok = @imagewebp($im, $dstFile, self::QUALITY); imagedestroy($im); return $ok && is_file($dstFile); } }