package imaging import ( "bytes" "image" "image/color" "image/draw" "image/jpeg" _ "image/png" ) const ( bgThreshold = 10 padding = 5 maxDim = 300 ) // CropToContent auto-crops src to the bounding box of non-background pixels, // adds padding, scales to fit within maxDim×maxDim, and re-encodes as JPEG. // Returns the original bytes unchanged if decoding fails. func CropToContent(src []byte) ([]byte, error) { img, _, err := image.Decode(bytes.NewReader(src)) if err != nil { return src, nil } bounds := img.Bounds() if bounds.Dx() == 0 || bounds.Dy() == 0 { return src, nil } bgColor := img.At(bounds.Min.X, bounds.Min.Y) minX, minY, maxX, maxY := findContentBounds(img, bgColor, bounds) if minX > maxX || minY > maxY { return src, nil } minX = max(bounds.Min.X, minX-padding) minY = max(bounds.Min.Y, minY-padding) maxX = min(bounds.Max.X-1, maxX+padding) maxY = min(bounds.Max.Y-1, maxY+padding) cropRect := image.Rect(minX, minY, maxX+1, maxY+1) cropped := cropImage(img, cropRect) scaled := scaleDown(cropped) var buf bytes.Buffer if err := jpeg.Encode(&buf, scaled, &jpeg.Options{Quality: 85}); err != nil { return src, nil } return buf.Bytes(), nil } func findContentBounds(img image.Image, bg color.Color, bounds image.Rectangle) (minX, minY, maxX, maxY int) { minX = bounds.Max.X minY = bounds.Max.Y maxX = bounds.Min.X - 1 maxY = bounds.Min.Y - 1 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { if channelDiff(img.At(x, y), bg) > bgThreshold { if x < minX { minX = x } if y < minY { minY = y } if x > maxX { maxX = x } if y > maxY { maxY = y } } } } return } func channelDiff(c1, c2 color.Color) int { r1, g1, b1, _ := c1.RGBA() r2, g2, b2, _ := c2.RGBA() dr := abs(int(r1>>8) - int(r2>>8)) dg := abs(int(g1>>8) - int(g2>>8)) db := abs(int(b1>>8) - int(b2>>8)) return max(dr, max(dg, db)) } func abs(x int) int { if x < 0 { return -x } return x } type subImager interface { SubImage(r image.Rectangle) image.Image } func cropImage(img image.Image, rect image.Rectangle) image.Image { if si, ok := img.(subImager); ok { return si.SubImage(rect) } dst := image.NewRGBA(image.Rect(0, 0, rect.Dx(), rect.Dy())) draw.Draw(dst, dst.Bounds(), img, rect.Min, draw.Src) return dst } func scaleDown(img image.Image) image.Image { bounds := img.Bounds() w, h := bounds.Dx(), bounds.Dy() if w <= maxDim && h <= maxDim { return img } scaleW := float64(maxDim) / float64(w) scaleH := float64(maxDim) / float64(h) scale := scaleW if scaleH < scale { scale = scaleH } newW := max(1, int(float64(w)*scale)) newH := max(1, int(float64(h)*scale)) dst := image.NewRGBA(image.Rect(0, 0, newW, newH)) for y := 0; y < newH; y++ { for x := 0; x < newW; x++ { srcX := bounds.Min.X + int(float64(x)/scale) srcY := bounds.Min.Y + int(float64(y)/scale) dst.Set(x, y, img.At(srcX, srcY)) } } return dst }