139 lines
2.8 KiB
Go
139 lines
2.8 KiB
Go
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
|
|
}
|