75 lines
2 KiB
Go
75 lines
2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
_ "image/gif"
|
|
_ "image/jpeg"
|
|
|
|
_ "github.com/biessek/golang-ico"
|
|
_ "golang.org/x/image/bmp"
|
|
_ "golang.org/x/image/webp"
|
|
)
|
|
|
|
// convertIconToBase64PNG reads an icon from disk, converts it to PNG, and returns base64-encoded data.
|
|
func convertIconToBase64PNG(hash string, iconsDir string) (encoded string, width, height int, err error) {
|
|
data, err := readIconFromDisk(iconsDir, hash)
|
|
if err != nil {
|
|
return "", 0, 0, fmt.Errorf("read icon: %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"))
|
|
}
|