webtools/cmds/android-icons/main.go

145 lines
3.5 KiB
Go
Raw Permalink Normal View History

2026-03-12 11:52:25 +00:00
//go:build js
package main
import (
"archive/zip"
"bytes"
"encoding/base64"
"fmt"
"image"
"image/png"
"strings"
"syscall/js"
"golang.org/x/image/draw"
)
type density struct {
dir string
size int
}
var densities = []density{
{"mipmap-mdpi", 48},
{"mipmap-hdpi", 72},
{"mipmap-xhdpi", 96},
{"mipmap-xxhdpi", 144},
{"mipmap-xxxhdpi", 192},
}
func main() {
js.Global().Set("prepareZip", js.FuncOf(prepareZip))
<-make(chan struct{})
}
func prepareZip(this js.Value, args []js.Value) interface{} {
// args[0] is an array of {name: string, data: string (base64 PNG data)}
files := args[0]
length := files.Length()
if length == 0 {
js.Global().Call("alert", "No files selected.")
return nil
}
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
for i := 0; i < length; i++ {
file := files.Index(i)
name := file.Get("name").String()
dataURL := file.Get("data").String()
// Strip data URL prefix (data:image/png;base64,)
commaIdx := strings.Index(dataURL, ",")
if commaIdx < 0 {
setStatus(fmt.Sprintf("Invalid data for %s", name))
return nil
}
b64Data := dataURL[commaIdx+1:]
imgData, err := base64.StdEncoding.DecodeString(b64Data)
if err != nil {
setStatus(fmt.Sprintf("Failed to decode %s: %v", name, err))
return nil
}
src, err := png.Decode(bytes.NewReader(imgData))
if err != nil {
setStatus(fmt.Sprintf("Failed to decode PNG %s: %v", name, err))
return nil
}
// Strip extension from name for the base name
baseName := name
if strings.HasSuffix(strings.ToLower(baseName), ".png") {
baseName = baseName[:len(baseName)-4]
}
for _, d := range densities {
resized := resizeImage(src, d.size)
var pngBuf bytes.Buffer
if err := png.Encode(&pngBuf, resized); err != nil {
setStatus(fmt.Sprintf("Failed to encode %s/%s.png: %v", d.dir, baseName, err))
return nil
}
path := fmt.Sprintf("%s/%s.png", d.dir, baseName)
w, err := zw.Create(path)
if err != nil {
setStatus(fmt.Sprintf("Failed to create zip entry %s: %v", path, err))
return nil
}
if _, err := w.Write(pngBuf.Bytes()); err != nil {
setStatus(fmt.Sprintf("Failed to write zip entry %s: %v", path, err))
return nil
}
}
}
if err := zw.Close(); err != nil {
setStatus(fmt.Sprintf("Failed to finalize zip: %v", err))
return nil
}
// Convert zip bytes to a JS Uint8Array and trigger download
zipBytes := buf.Bytes()
jsArray := js.Global().Get("Uint8Array").New(len(zipBytes))
js.CopyBytesToJS(jsArray, zipBytes)
// Create Blob and download
blobParts := js.Global().Get("Array").New()
blobParts.Call("push", jsArray)
blobOpts := js.Global().Get("Object").New()
blobOpts.Set("type", "application/zip")
blob := js.Global().Get("Blob").New(blobParts, blobOpts)
url := js.Global().Get("URL").Call("createObjectURL", blob)
anchor := js.Global().Get("document").Call("createElement", "a")
anchor.Set("href", url)
anchor.Set("download", "android-icons.zip")
js.Global().Get("document").Get("body").Call("appendChild", anchor)
anchor.Call("click")
anchor.Call("remove")
js.Global().Get("URL").Call("revokeObjectURL", url)
setStatus(fmt.Sprintf("Done! Prepared icons for %d image(s).", length))
return nil
}
func resizeImage(src image.Image, size int) image.Image {
dst := image.NewNRGBA(image.Rect(0, 0, size, size))
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
return dst
}
func setStatus(msg string) {
querySelector("#status").Set("innerText", msg)
}
func querySelector(query string) js.Value {
return js.Global().Get("document").Call("querySelector", query)
}