fix: downscale reference image before CF AI img2img to avoid 502 payload limit
CF Workers AI has a ~4MB JSON body limit. Large cover images base64-encoded can easily exceed this, causing Cloudflare to return a 502 Bad Gateway. Added resizeRefImage() which scales the reference down so its longest side ≤ 768px (nearest-neighbour, re-encoded as JPEG) before passing it to the GenerateImageFromReference call. Images already within the limit pass through unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,8 +29,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
"image/jpeg"
|
||||||
_ "image/jpeg" // register JPEG decoder
|
_ "image/jpeg" // register JPEG decoder
|
||||||
_ "image/png" // register PNG decoder
|
"image/png"
|
||||||
|
_ "image/png" // register PNG decoder
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -189,6 +192,11 @@ func (c *imageGenHTTPClient) GenerateImage(ctx context.Context, req ImageRequest
|
|||||||
return c.callImageAPI(ctx, req.Model, body)
|
return c.callImageAPI(ctx, req.Model, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refImageMaxDim is the maximum dimension (width or height) for reference images
|
||||||
|
// sent to Cloudflare Workers AI. CF's JSON body limit is ~4 MB; a 768px JPEG
|
||||||
|
// stays well under that while preserving enough detail for img2img guidance.
|
||||||
|
const refImageMaxDim = 768
|
||||||
|
|
||||||
// GenerateImageFromReference generates an image from a text prompt + reference image.
|
// GenerateImageFromReference generates an image from a text prompt + reference image.
|
||||||
func (c *imageGenHTTPClient) GenerateImageFromReference(ctx context.Context, req ImageRequest, refImage []byte) ([]byte, error) {
|
func (c *imageGenHTTPClient) GenerateImageFromReference(ctx context.Context, req ImageRequest, refImage []byte) ([]byte, error) {
|
||||||
if len(refImage) == 0 {
|
if len(refImage) == 0 {
|
||||||
@@ -196,6 +204,10 @@ func (c *imageGenHTTPClient) GenerateImageFromReference(ctx context.Context, req
|
|||||||
}
|
}
|
||||||
req = applyImageDefaults(req)
|
req = applyImageDefaults(req)
|
||||||
|
|
||||||
|
// Shrink the reference image if it exceeds the safe payload size.
|
||||||
|
// This avoids CF's 4 MB JSON body limit and reduces latency.
|
||||||
|
refImage = resizeRefImage(refImage, refImageMaxDim)
|
||||||
|
|
||||||
var body map[string]any
|
var body map[string]any
|
||||||
if req.Model == ImageModelSD15Img2Img {
|
if req.Model == ImageModelSD15Img2Img {
|
||||||
pixels, err := decodeImageToRGBA(refImage)
|
pixels, err := decodeImageToRGBA(refImage)
|
||||||
@@ -286,6 +298,60 @@ func applyImageDefaults(req ImageRequest) ImageRequest {
|
|||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resizeRefImage down-scales an image so that its longest side is at most maxDim
|
||||||
|
// pixels, then re-encodes it as JPEG (quality 85). If the image is already small
|
||||||
|
// enough, or if decoding fails, the original bytes are returned unchanged.
|
||||||
|
// This keeps the JSON payload well under Cloudflare Workers AI's 4 MB body limit.
|
||||||
|
func resizeRefImage(data []byte, maxDim int) []byte {
|
||||||
|
src, format, err := image.Decode(bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
b := src.Bounds()
|
||||||
|
w, h := b.Dx(), b.Dy()
|
||||||
|
|
||||||
|
longest := w
|
||||||
|
if h > longest {
|
||||||
|
longest = h
|
||||||
|
}
|
||||||
|
if longest <= maxDim {
|
||||||
|
return data // already fits
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute target dimensions preserving aspect ratio.
|
||||||
|
scale := float64(maxDim) / float64(longest)
|
||||||
|
newW := int(float64(w)*scale + 0.5)
|
||||||
|
newH := int(float64(h)*scale + 0.5)
|
||||||
|
if newW < 1 {
|
||||||
|
newW = 1
|
||||||
|
}
|
||||||
|
if newH < 1 {
|
||||||
|
newH = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nearest-neighbour downsample (no extra deps, sufficient for reference guidance).
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, newW, newH))
|
||||||
|
for y := 0; y < newH; y++ {
|
||||||
|
for x := 0; x < newW; x++ {
|
||||||
|
srcX := b.Min.X + int(float64(x)/scale)
|
||||||
|
srcY := b.Min.Y + int(float64(y)/scale)
|
||||||
|
draw.Draw(dst, image.Rect(x, y, x+1, y+1), src, image.Pt(srcX, srcY), draw.Src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if format == "jpeg" {
|
||||||
|
if encErr := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: 85}); encErr != nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if encErr := png.Encode(&buf, dst); encErr != nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
// decodeImageToRGBA decodes PNG/JPEG bytes to a flat []uint8 RGBA pixel array
|
// decodeImageToRGBA decodes PNG/JPEG bytes to a flat []uint8 RGBA pixel array
|
||||||
// required by the stable-diffusion-v1-5-img2img model.
|
// required by the stable-diffusion-v1-5-img2img model.
|
||||||
func decodeImageToRGBA(data []byte) ([]uint8, error) {
|
func decodeImageToRGBA(data []byte) ([]uint8, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user