perf: 文章图片 WebP 转换改为二分查找质量,目标 ≤ 原文件大小
- 不再仅试 quality 80,改为二分查找 (50~80) 找最高质量 ≤ 原文件大小 - 新增 encodeWebPBinary 辅助函数:先试 80,太大则二分降到 50 - 降到 50 仍超限时回退原格式,避免产生马赛克级质量 - 与头像的激进二分(最低 quality 1)区分,文章图片保底 quality 50
This commit is contained in:
@ -416,22 +416,19 @@ func (ctrl *PostController) UploadImage(c *gin.Context) {
|
|||||||
isGIF := ext == ".gif"
|
isGIF := ext == ".gif"
|
||||||
|
|
||||||
// 强制解码验证文件是否为有效图片(防伪扩展名、图片投毒),验证失败直接拒绝
|
// 强制解码验证文件是否为有效图片(防伪扩展名、图片投毒),验证失败直接拒绝
|
||||||
// JPEG/PNG:解码后顺便做 WebP 压缩
|
// JPEG/PNG:解码后做 WebP 压缩(二分查找质量,目标 ≤ 原文件大小)
|
||||||
if isJPEGPNG {
|
if isJPEGPNG {
|
||||||
img, _, err := image.Decode(bytes.NewReader(data))
|
img, _, err := image.Decode(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Error(c, http.StatusBadRequest, "图片格式无效,请上传有效的图片文件")
|
common.Error(c, http.StatusBadRequest, "图片格式无效,请上传有效的图片文件")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// WebP 压缩存储(保留原尺寸,quality 80)
|
webpData := encodeWebPBinary(img, len(data))
|
||||||
var buf bytes.Buffer
|
if webpData != nil && len(webpData) < len(data) {
|
||||||
if err := webp.Encode(&buf, img, &webp.Options{Quality: 80}); err == nil {
|
filename := fmt.Sprintf("%d_%d.webp", uid, ts)
|
||||||
if buf.Len() > 0 && buf.Len() < len(data) {
|
savePath = filepath.Join(storageDir, filename)
|
||||||
filename := fmt.Sprintf("%d_%d.webp", uid, ts)
|
if err := os.WriteFile(savePath, webpData, 0644); err == nil {
|
||||||
savePath = filepath.Join(storageDir, filename)
|
url = "/uploads/posts/" + filename
|
||||||
if err := os.WriteFile(savePath, buf.Bytes(), 0644); err == nil {
|
|
||||||
url = "/uploads/posts/" + filename
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,7 +449,7 @@ func (ctrl *PostController) UploadImage(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 原格式存储(WebP / GIF / 转换后反而更大)
|
// 原格式存储(GIF / 已有 WebP / 二分压缩仍超原文件大小)
|
||||||
if savePath == "" {
|
if savePath == "" {
|
||||||
filename := fmt.Sprintf("%d_%d%s", uid, ts, ext)
|
filename := fmt.Sprintf("%d_%d%s", uid, ts, ext)
|
||||||
savePath = filepath.Join(storageDir, filename)
|
savePath = filepath.Join(storageDir, filename)
|
||||||
@ -474,3 +471,33 @@ func (ctrl *PostController) applyShortcode(post *model.Post) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeWebPBinary 二分查找 WebP 质量,找 ≤ targetBytes 的最高质量(quality 50~80)
|
||||||
|
// 返回 nil 表示最低质量仍超限,应由调用方回退原格式
|
||||||
|
func encodeWebPBinary(img image.Image, targetBytes int) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := webp.Encode(&buf, img, &webp.Options{Quality: 80}); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if buf.Len() <= targetBytes {
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
low, high := 50, 80
|
||||||
|
var best []byte
|
||||||
|
for low <= high {
|
||||||
|
mid := (low + high) / 2
|
||||||
|
buf.Reset()
|
||||||
|
if err := webp.Encode(&buf, img, &webp.Options{Quality: float32(mid)}); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if buf.Len() <= targetBytes {
|
||||||
|
best = make([]byte, buf.Len())
|
||||||
|
copy(best, buf.Bytes())
|
||||||
|
low = mid + 1
|
||||||
|
} else {
|
||||||
|
high = mid - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user