added icon downloader

This commit is contained in:
Joe Lothan 2026-05-17 22:09:03 -04:00
parent 8b5693b5c6
commit 5a2e37ae06
10 changed files with 829 additions and 68 deletions

View file

@ -0,0 +1,139 @@
package main
import (
"bytes"
"encoding/binary"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
_ "golang.org/x/image/webp"
)
// detectImageType checks magic bytes to determine the actual image format.
// Returns empty string if not a recognized image format.
func detectImageType(data []byte) string {
if len(data) < 4 {
return ""
}
// PNG: 89 50 4E 47
if data[0] == 0x89 && data[1] == 'P' && data[2] == 'N' && data[3] == 'G' {
return "image/png"
}
// GIF: GIF87a or GIF89a
if data[0] == 'G' && data[1] == 'I' && data[2] == 'F' {
return "image/gif"
}
// JPEG: FF D8 FF
if data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF {
return "image/jpeg"
}
// ICO: 00 00 01 00
if data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01 && data[3] == 0x00 {
return "image/x-icon"
}
// BMP: BM
if data[0] == 'B' && data[1] == 'M' {
return "image/bmp"
}
// WebP: RIFF....WEBP
if len(data) >= 12 && string(data[0:4]) == "RIFF" && string(data[8:12]) == "WEBP" {
return "image/webp"
}
// SVG: look for <?xml or <svg in first 256 bytes
if len(data) > 5 {
header := string(data[:min(256, len(data))])
if bytes.Contains([]byte(header), []byte("<svg")) || bytes.Contains([]byte(header), []byte("<?xml")) {
return "image/svg+xml"
}
}
return ""
}
// getImageDimensions reads image dimensions from the data.
// Returns (0, 0) for SVG or if dimensions can't be determined.
func getImageDimensions(data []byte, contentType string) (int, int) {
switch contentType {
case "image/svg+xml":
return 0, 0
case "image/x-icon":
return getICODimensions(data)
default:
// Use Go's image.DecodeConfig for standard formats
cfg, _, err := image.DecodeConfig(bytes.NewReader(data))
if err != nil {
return 0, 0
}
return cfg.Width, cfg.Height
}
}
// getICODimensions reads the ICO directory to find the largest image ≤64x64.
// ICO format: 6-byte header + 16-byte directory entries.
func getICODimensions(data []byte) (int, int) {
if len(data) < 6 {
return 0, 0
}
numImages := int(binary.LittleEndian.Uint16(data[4:6]))
if numImages == 0 || len(data) < 6+numImages*16 {
return 0, 0
}
bestW, bestH := 0, 0
for i := 0; i < numImages; i++ {
offset := 6 + i*16
w := int(data[offset])
h := int(data[offset+1])
// ICO uses 0 to mean 256
if w == 0 {
w = 256
}
if h == 0 {
h = 256
}
// Pick the largest that's ≤64x64
if w <= 64 && h <= 64 && w*h > bestW*bestH {
bestW = w
bestH = h
}
}
// If nothing ≤64, just report the largest
if bestW == 0 {
for i := 0; i < numImages; i++ {
offset := 6 + i*16
w := int(data[offset])
h := int(data[offset+1])
if w == 0 {
w = 256
}
if h == 0 {
h = 256
}
if w*h > bestW*bestH {
bestW = w
bestH = h
}
}
}
return bestW, bestH
}
func min(a, b int) int {
if a < b {
return a
}
return b
}