145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
|
|
//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)
|
||
|
|
}
|