diff --git a/anthropic.go b/anthropic.go index faaa164..f2b6a66 100644 --- a/anthropic.go +++ b/anthropic.go @@ -90,7 +90,7 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest { // Check if image size exceeds 5MiB (5242880 bytes) if len(raw) >= 5242880 { - compressed, err := utils.CompressImage(img.Base64, 5*1024*1024) + compressed, mime, err := utils.CompressImage(img.Base64, 5*1024*1024) // just replace the image with the compressed one if err != nil { @@ -98,6 +98,7 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest { } img.Base64 = compressed + img.ContentType = mime } m.Content = append(m.Content, anth.NewImageMessageContent( diff --git a/utils/compress_image.go b/utils/compress_image.go index b42b14c..a64b13f 100644 --- a/utils/compress_image.go +++ b/utils/compress_image.go @@ -7,7 +7,6 @@ import ( "image" "image/gif" "image/jpeg" - "image/png" "net/http" "golang.org/x/image/draw" @@ -15,18 +14,21 @@ import ( // CompressImage takes a base‑64‑encoded image (JPEG, PNG or GIF) and returns // a base‑64‑encoded version that is at most maxLength in size, or an error. -func CompressImage(b64 string, maxLength int) (string, error) { +func CompressImage(b64 string, maxLength int) (string, string, error) { raw, err := base64.StdEncoding.DecodeString(b64) if err != nil { - return "", fmt.Errorf("base64 decode: %w", err) - } - if len(raw) <= maxLength { - return b64, nil // small enough already + return "", "", fmt.Errorf("base64 decode: %w", err) } - switch mime := http.DetectContentType(raw); mime { + mime := http.DetectContentType(raw) + if len(raw) <= maxLength { + return b64, mime, nil // small enough already + } + + switch mime { case "image/gif": return compressGIF(raw, maxLength) + default: // jpeg, png, webp, etc. → treat as raster return compressRaster(raw, maxLength) } @@ -34,20 +36,20 @@ func CompressImage(b64 string, maxLength int) (string, error) { // ---------- Raster path (jpeg / png / single‑frame gif) ---------- -func compressRaster(src []byte, maxLength int) (string, error) { +func compressRaster(src []byte, maxLength int) (string, string, error) { img, _, err := image.Decode(bytes.NewReader(src)) if err != nil { - return "", fmt.Errorf("decode raster: %w", err) + return "", "", fmt.Errorf("decode raster: %w", err) } quality := 95 for { var buf bytes.Buffer if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}); err != nil { - return "", fmt.Errorf("jpeg encode: %w", err) + return "", "", fmt.Errorf("jpeg encode: %w", err) } if buf.Len() <= maxLength { - return base64.StdEncoding.EncodeToString(buf.Bytes()), nil + return base64.StdEncoding.EncodeToString(buf.Bytes()), "image/jpeg", nil } if quality > 20 { @@ -58,7 +60,7 @@ func compressRaster(src []byte, maxLength int) (string, error) { // down‑scale 80% b := img.Bounds() if b.Dx() < 100 || b.Dy() < 100 { - return "", fmt.Errorf("cannot compress below %.02fMiB without destroying image", float64(maxLength)/1048576.0) + return "", "", fmt.Errorf("cannot compress below %.02fMiB without destroying image", float64(maxLength)/1048576.0) } dst := image.NewRGBA(image.Rect(0, 0, int(float64(b.Dx())*0.8), int(float64(b.Dy())*0.8))) draw.ApproxBiLinear.Scale(dst, dst.Bounds(), img, b, draw.Over, nil) @@ -69,25 +71,25 @@ func compressRaster(src []byte, maxLength int) (string, error) { // ---------- Animated GIF path ---------- -func compressGIF(src []byte, maxLength int) (string, error) { +func compressGIF(src []byte, maxLength int) (string, string, error) { g, err := gif.DecodeAll(bytes.NewReader(src)) if err != nil { - return "", fmt.Errorf("gif decode: %w", err) + return "", "", fmt.Errorf("gif decode: %w", err) } for { var buf bytes.Buffer if err := gif.EncodeAll(&buf, g); err != nil { - return "", fmt.Errorf("gif encode: %w", err) + return "", "", fmt.Errorf("gif encode: %w", err) } if buf.Len() <= maxLength { - return base64.StdEncoding.EncodeToString(buf.Bytes()), nil + return base64.StdEncoding.EncodeToString(buf.Bytes()), "image/gif", nil } // down‑scale every frame by 80% w, h := g.Config.Width, g.Config.Height if w < 100 || h < 100 { - return "", fmt.Errorf("cannot compress animated GIF below 5 MiB without excessive quality loss") + return "", "", fmt.Errorf("cannot compress animated GIF below 5 MiB without excessive quality loss") } nw, nh := int(float64(w)*0.8), int(float64(h)*0.8) @@ -110,20 +112,3 @@ func compressGIF(src []byte, maxLength int) (string, error) { // loop back and test size again … } } - -// ---------- Helpers for precise MIME decodes (optional) ---------- - -func decode(r *bytes.Reader, mime string) (image.Image, error) { - switch mime { - case "image/jpeg": - return jpeg.Decode(r) - case "image/png": - return png.Decode(r) - case "image/gif": - // for single‑frame GIFs only – call DecodeAll in caller if animated - return gif.Decode(r) - default: - i, _, err := image.Decode(r) - return i, err // let the stdlib guess the format - } -}