Files
jiang13-forum/services/image_webp.go
freefire 9fe299a45f refactor: Gitea 式目录改组,移除本分支 SPA 与杂项产物
将 model/service/handler/middleware 迁至 models/services/routers/api/modules/auth,并删除 frontend、embed_static、scripts 及误入库缓存/二进制。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 03:54:56 +08:00

153 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"bytes"
"errors"
"fmt"
"image"
"image/gif"
"io"
"mime/multipart"
"path/filepath"
"strings"
"github.com/KarpelesLab/gowebp"
// 注册解码器
_ "image/jpeg"
_ "image/png"
_ "golang.org/x/image/webp"
)
// 图片展示方案(上传时同时保留原图与 WebP按此决定返回给前端的 URL
const (
ImageDeliveryWebP = "webp" // 使用 WebP默认省流量
ImageDeliveryOriginal = "original" // 使用原图
)
const (
// UploadWebPQuality 上传衍生 WebP 有损质量0100
UploadWebPQuality float32 = 82
// UploadWebPMethod 编码档位3 速度与体积较均衡
UploadWebPMethod = 3
// ThumbWebPQuality 帖子预览图质量
ThumbWebPQuality float32 = 80
)
// preparedUpload 原图 + 可选 WebP 衍生
type preparedUpload struct {
OrigExt string // 含点,如 .jpg
OrigContentType string
OrigData []byte
WebPData []byte // 空表示无衍生(动图 GIF或原图已是 WebP
}
// prepareUploadImage 始终保留原图字节;静态图额外生成 WebP 衍生
func prepareUploadImage(file *multipart.FileHeader) (*preparedUpload, error) {
ext := strings.ToLower(filepath.Ext(file.Filename))
if !allowedImageExt[ext] {
return nil, errors.New("仅支持 jpg/png/gif/webp 格式")
}
if ext == ".jpeg" {
ext = ".jpg"
}
src, err := file.Open()
if err != nil {
return nil, err
}
defer src.Close()
raw, err := io.ReadAll(src)
if err != nil {
return nil, err
}
if len(raw) == 0 {
return nil, errors.New("空图片文件")
}
out := &preparedUpload{
OrigExt: ext,
OrigContentType: imageContentType(ext),
OrigData: raw,
}
// 动图 GIF只保留原文件
if ext == ".gif" && gifFrameCount(raw) > 1 {
return out, nil
}
// 上传已是 WebP原图即 WebP不再重复衍生
if ext == ".webp" {
return out, nil
}
img, _, err := image.Decode(bytes.NewReader(raw))
if err != nil {
return nil, fmt.Errorf("解码图片失败: %w", err)
}
webpBytes, err := encodeWebPBytes(img, UploadWebPQuality, UploadWebPMethod)
if err != nil {
return nil, fmt.Errorf("转换 WebP 失败: %w", err)
}
out.WebPData = webpBytes
return out, nil
}
func gifFrameCount(raw []byte) int {
g, err := gif.DecodeAll(bytes.NewReader(raw))
if err != nil || g == nil {
return 0
}
return len(g.Image)
}
func encodeWebPBytes(img image.Image, quality float32, method int) ([]byte, error) {
var buf bytes.Buffer
if err := gowebp.Encode(&buf, img, &gowebp.Options{
Lossy: true,
Quality: quality,
Method: method,
}); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func normalizeImageDelivery(raw string) string {
if strings.ToLower(strings.TrimSpace(raw)) == ImageDeliveryOriginal {
return ImageDeliveryOriginal
}
return ImageDeliveryWebP
}
func imageContentType(ext string) string {
switch strings.ToLower(ext) {
case ".jpg", ".jpeg":
return "image/jpeg"
case ".png":
return "image/png"
case ".gif":
return "image/gif"
case ".webp":
return "image/webp"
default:
return "application/octet-stream"
}
}
// siblingUploadExts 同主文件名可能存在的伴生扩展名(删除时一并清理)
func siblingUploadExts(ext string) []string {
ext = strings.ToLower(ext)
all := []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
out := make([]string, 0, len(all))
for _, e := range all {
if e == ext || (ext == ".jpg" && e == ".jpeg") || (ext == ".jpeg" && e == ".jpg") {
continue
}
out = append(out, e)
}
return out
}