mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
153 lines
3.3 KiB
Go
153 lines
3.3 KiB
Go
// Package main provides a utility to generate PNG and ICO favicons from SVG source files.
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/srwiley/oksvg"
|
|
"github.com/srwiley/rasterx"
|
|
)
|
|
|
|
func main() {
|
|
mediaDir := "pkg/service/handlers/web/img"
|
|
files := []string{"favicon-braille", "favicon-morse"}
|
|
|
|
for _, name := range files {
|
|
svgPath := filepath.Join(mediaDir, name+".svg")
|
|
pngPath := filepath.Join(mediaDir, name+".png")
|
|
icoPath := filepath.Join(mediaDir, name+".ico")
|
|
|
|
fmt.Printf("Processing %s...\n", name)
|
|
|
|
// 1. Render SVG to PNG
|
|
img, err := renderSVG(svgPath, 32, 32)
|
|
if err != nil {
|
|
log.Fatalf("Failed to render %s: %v", svgPath, err)
|
|
}
|
|
|
|
f, err := os.Create(pngPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create %s: %v", pngPath, err)
|
|
}
|
|
|
|
if err := png.Encode(f, img); err != nil {
|
|
f.Close()
|
|
log.Fatalf("Failed to encode PNG %s: %v", pngPath, err)
|
|
}
|
|
|
|
f.Close()
|
|
fmt.Printf("Created %s\n", pngPath)
|
|
|
|
// 2. Create ICO (containing multiple sizes)
|
|
sizes := []int{16, 32, 48}
|
|
|
|
var images []image.Image
|
|
|
|
for _, s := range sizes {
|
|
m, err := renderSVG(svgPath, s, s)
|
|
if err != nil {
|
|
log.Fatalf("Failed to render %s at size %d: %v", svgPath, s, err)
|
|
}
|
|
|
|
images = append(images, m)
|
|
}
|
|
|
|
if err := writeICO(icoPath, images); err != nil {
|
|
log.Fatalf("Failed to write ICO %s: %v", icoPath, err)
|
|
}
|
|
|
|
fmt.Printf("Created %s\n", icoPath)
|
|
}
|
|
}
|
|
|
|
func renderSVG(path string, w, h int) (image.Image, error) {
|
|
in, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer in.Close()
|
|
|
|
icon, err := oksvg.ReadIconStream(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
icon.SetTarget(0, 0, float64(w), float64(h))
|
|
rgba := image.NewRGBA(image.Rect(0, 0, w, h))
|
|
gv := rasterx.NewScannerGV(w, h, rgba, rgba.Bounds())
|
|
dasher := rasterx.NewDasher(w, h, gv)
|
|
icon.Draw(dasher, 1.0)
|
|
|
|
return rgba, nil
|
|
}
|
|
|
|
// Simple ICO encoder that wraps PNGs
|
|
func writeICO(path string, images []image.Image) error {
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
bw := bufio.NewWriter(f)
|
|
defer bw.Flush()
|
|
|
|
// ICONDIR header
|
|
// Reserved (2), Type (2), Count (2)
|
|
binary.Write(bw, binary.LittleEndian, uint16(0))
|
|
binary.Write(bw, binary.LittleEndian, uint16(1)) // 1 = ICO
|
|
binary.Write(bw, binary.LittleEndian, uint16(len(images)))
|
|
|
|
var pngData [][]byte
|
|
|
|
for _, img := range images {
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err != nil {
|
|
return err
|
|
}
|
|
|
|
pngData = append(pngData, buf.Bytes())
|
|
}
|
|
|
|
offset := uint32(6 + len(images)*16)
|
|
for i, img := range images {
|
|
b := img.Bounds()
|
|
|
|
width := uint8(b.Dx())
|
|
if b.Dx() >= 256 {
|
|
width = 0
|
|
}
|
|
|
|
height := uint8(b.Dy())
|
|
if b.Dy() >= 256 {
|
|
height = 0
|
|
}
|
|
|
|
// ICONDIRENTRY
|
|
bw.WriteByte(width)
|
|
bw.WriteByte(height)
|
|
bw.WriteByte(0) // Color count
|
|
bw.WriteByte(0) // Reserved
|
|
binary.Write(bw, binary.LittleEndian, uint16(1)) // Planes (1)
|
|
binary.Write(bw, binary.LittleEndian, uint16(32)) // Bits per pixel (32)
|
|
binary.Write(bw, binary.LittleEndian, uint32(len(pngData[i])))
|
|
binary.Write(bw, binary.LittleEndian, offset)
|
|
|
|
offset += uint32(len(pngData[i]))
|
|
}
|
|
|
|
for _, data := range pngData {
|
|
bw.Write(data)
|
|
}
|
|
|
|
return nil
|
|
}
|