package media import ( "bytes" "fmt" "image" _ "image/gif" _ "image/jpeg" _ "image/png" "io" "github.com/chai2010/webp" "github.com/disintegration/imaging" ) const ( DefaultMaxEdge = 1920 DefaultThumbEdge = 400 DefaultQuality = 80 ) type ProcessOptions struct { MaxEdge int ThumbEdge int WebPQuality float32 } type ProcessedImage struct { Main []byte MainType string // image/webp MainExt string // .webp Thumb []byte ThumbType string ThumbExt string Width int Height int ThumbWidth int ThumbHeight int } // ProcessImage 解码 → 等比缩放 → WebP 主图 + 缩略图 func ProcessImage(r io.Reader, opt ProcessOptions) (*ProcessedImage, error) { if opt.MaxEdge <= 0 { opt.MaxEdge = DefaultMaxEdge } if opt.ThumbEdge <= 0 { opt.ThumbEdge = DefaultThumbEdge } if opt.WebPQuality <= 0 || opt.WebPQuality > 100 { opt.WebPQuality = DefaultQuality } src, _, err := image.Decode(r) if err != nil { return nil, fmt.Errorf("无法解码图片: %w", err) } mainImg := fitWithin(src, opt.MaxEdge) thumbImg := fitWithin(src, opt.ThumbEdge) mainBuf := &bytes.Buffer{} if err := webp.Encode(mainBuf, mainImg, &webp.Options{Quality: opt.WebPQuality}); err != nil { return nil, fmt.Errorf("webp 编码失败: %w", err) } thumbBuf := &bytes.Buffer{} if err := webp.Encode(thumbBuf, thumbImg, &webp.Options{Quality: opt.WebPQuality}); err != nil { return nil, fmt.Errorf("缩略图编码失败: %w", err) } bMain := mainImg.Bounds() bThumb := thumbImg.Bounds() return &ProcessedImage{ Main: mainBuf.Bytes(), MainType: "image/webp", MainExt: ".webp", Thumb: thumbBuf.Bytes(), ThumbType: "image/webp", ThumbExt: ".webp", Width: bMain.Dx(), Height: bMain.Dy(), ThumbWidth: bThumb.Dx(), ThumbHeight: bThumb.Dy(), }, nil } func fitWithin(img image.Image, maxEdge int) image.Image { b := img.Bounds() w, h := b.Dx(), b.Dy() if w <= maxEdge && h <= maxEdge { return img } if w >= h { return imaging.Resize(img, maxEdge, 0, imaging.Lanczos) } return imaging.Resize(img, 0, maxEdge, imaging.Lanczos) } // AllowedUploadMIME 上传阶段允许的源格式 func AllowedUploadMIME(contentType string) bool { switch contentType { case "image/jpeg", "image/png", "image/gif", "image/webp": return true default: return false } }