package media import ( "bytes" "context" "image" _ "image/gif" "image/jpeg" _ "image/png" "golang.org/x/image/draw" "golang.org/x/image/webp" ) const maxUploadSize = 25 << 20 // 25 MiB // IsImage reports whether the content type is a decodable image format. func IsImage(contentType string) bool { switch contentType { case "image/jpeg", "image/png", "image/gif", "image/webp": return true default: return false } } // Generate returns a JPEG thumbnail of at most maxDim on its larger side. // It returns nil for non-images or when the source is already small enough. func Generate(ctx context.Context, r *bytes.Reader, contentType string, maxDim int) ([]byte, error) { if !IsImage(contentType) { return nil, nil } if err := ctx.Err(); err != nil { return nil, err } src, err := decodeImage(r, contentType) if err != nil { return nil, err } bounds := src.Bounds() w, h := bounds.Dx(), bounds.Dy() if w <= maxDim && h <= maxDim { return nil, nil } scale := float64(maxDim) / float64(max(w, h)) nw, nh := int(float64(w)*scale), int(float64(h)*scale) if nw < 1 { nw = 1 } if nh < 1 { nh = 1 } dst := image.NewRGBA(image.Rect(0, 0, nw, nh)) draw.BiLinear.Scale(dst, dst.Bounds(), src, bounds, draw.Over, nil) if err := ctx.Err(); err != nil { return nil, err } var buf bytes.Buffer if err := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: 80}); err != nil { return nil, err } return buf.Bytes(), nil } func decodeImage(r *bytes.Reader, contentType string) (image.Image, error) { if contentType == "image/webp" { return webp.Decode(r) } img, _, err := image.Decode(r) return img, err } func max(a, b int) int { if a > b { return a } return b }