everytab/pipeline/05_bundle_gen/convert.go

82 lines
2 KiB
Go

package main
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"image"
"image/png"
_ "image/gif"
_ "image/jpeg"
_ "github.com/biessek/golang-ico"
_ "golang.org/x/image/webp"
)
// convertIconToBase64PNG downloads an icon from S3, converts it to PNG, and returns base64-encoded data.
func convertIconToBase64PNG(ctx context.Context, s3Key string, iconsBucket string) (encoded string, width, height int, err error) {
data, err := s3Download(ctx, iconsBucket, s3Key)
if err != nil {
return "", 0, 0, fmt.Errorf("s3 download: %w", err)
}
// Check for SVG (can't decode to raster without external deps)
if isSVG(data) {
return "", 0, 0, fmt.Errorf("svg not supported")
}
// image.Decode handles PNG, GIF, JPEG, WebP, BMP, and ICO (via registered decoders)
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return "", 0, 0, fmt.Errorf("decode: %w", err)
}
// Downscale icons >128px to 32x32 to keep bundle sizes reasonable
bounds := img.Bounds()
w, h := bounds.Dx(), bounds.Dy()
if w > 128 || h > 128 {
img = resizeNearestNeighbor(img, 32, 32)
w, h = 32, 32
}
// Re-encode as PNG
var pngBuf bytes.Buffer
if err := png.Encode(&pngBuf, img); err != nil {
return "", 0, 0, fmt.Errorf("png encode: %w", err)
}
encoded = base64.StdEncoding.EncodeToString(pngBuf.Bytes())
return encoded, w, h, nil
}
// resizeNearestNeighbor does a simple nearest-neighbor resize.
func resizeNearestNeighbor(src image.Image, dstW, dstH int) image.Image {
srcBounds := src.Bounds()
srcW := srcBounds.Dx()
srcH := srcBounds.Dy()
dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
for y := 0; y < dstH; y++ {
srcY := srcBounds.Min.Y + y*srcH/dstH
for x := 0; x < dstW; x++ {
srcX := srcBounds.Min.X + x*srcW/dstW
dst.Set(x, y, src.At(srcX, srcY))
}
}
return dst
}
func isSVG(data []byte) bool {
if len(data) < 5 {
return false
}
header := data[:min(256, len(data))]
return bytes.Contains(header, []byte("<svg")) || bytes.Contains(header, []byte("<?xml"))
}
func min(a, b int) int {
if a < b {
return a
}
return b
}