1eab1b99c1
- Rename all QINIU_* config/code/docs to OSS_* to match actual Alibaba Cloud OSS - Refactor upload module from internal/common/qiniu to internal/common/upload - Add backend proxy upload endpoint (POST /api/admin/marketing/upload) to avoid CORS - Auto-generate compressed thumbnail (800px, JPEG 80%) on admin image upload - Add dev-login endpoint (POST /api/v1/auth/dev-login) for H5 debugging - Add imageutil package for server-side image resizing Made-with: Cursor
73 lines
1.7 KiB
Go
73 lines
1.7 KiB
Go
package imageutil
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"io"
|
|
"strings"
|
|
|
|
"golang.org/x/image/draw"
|
|
)
|
|
|
|
type ResizeOptions struct {
|
|
MaxWidth int
|
|
Quality int // JPEG quality 1-100
|
|
}
|
|
|
|
func DefaultResizeOptions() ResizeOptions {
|
|
return ResizeOptions{MaxWidth: 800, Quality: 80}
|
|
}
|
|
|
|
// GenerateThumbnail reads image data, resizes if wider than MaxWidth, and
|
|
// re-encodes as JPEG. Returns the thumbnail bytes and whether the image
|
|
// was actually resized (false if already small enough but still re-encoded).
|
|
func GenerateThumbnail(data []byte, contentType string, opts ResizeOptions) ([]byte, error) {
|
|
if opts.MaxWidth <= 0 {
|
|
opts.MaxWidth = 800
|
|
}
|
|
if opts.Quality <= 0 || opts.Quality > 100 {
|
|
opts.Quality = 80
|
|
}
|
|
|
|
src, err := decodeImage(bytes.NewReader(data), contentType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode image: %w", err)
|
|
}
|
|
|
|
bounds := src.Bounds()
|
|
srcW := bounds.Dx()
|
|
srcH := bounds.Dy()
|
|
|
|
dstW := srcW
|
|
dstH := srcH
|
|
if srcW > opts.MaxWidth {
|
|
dstW = opts.MaxWidth
|
|
dstH = srcH * opts.MaxWidth / srcW
|
|
}
|
|
|
|
dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
|
draw.BiLinear.Scale(dst, dst.Bounds(), src, bounds, draw.Over, nil)
|
|
|
|
var buf bytes.Buffer
|
|
if err := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: opts.Quality}); err != nil {
|
|
return nil, fmt.Errorf("encode jpeg: %w", err)
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func decodeImage(r io.Reader, contentType string) (image.Image, error) {
|
|
ct := strings.ToLower(contentType)
|
|
switch {
|
|
case strings.Contains(ct, "png"):
|
|
return png.Decode(r)
|
|
case strings.Contains(ct, "jpeg"), strings.Contains(ct, "jpg"):
|
|
return jpeg.Decode(r)
|
|
default:
|
|
img, _, err := image.Decode(r)
|
|
return img, err
|
|
}
|
|
}
|