package app import ( "bytes" "errors" "fmt" "image" "image/png" "io" "io/ioutil" "log" "net/http" "os" "os/exec" "path/filepath" ) // BestIcon selects the best PNG icon image. func BestIcon(i Insights) (image.Image, error) { // Ensure all icons are loaded. for _, icon := range i.Icons { if icon.Data == nil { img, err := ParseWebPNG(icon.URL) if err == nil { icon.Data = img } } } // Get the biggest icon. var biggest image.Image var maxSize int for _, icon := range i.Icons { w := icon.Width() if w > maxSize && icon.Data != nil { biggest = icon.Data maxSize = w } } if biggest == nil { return nil, errors.New("no suitable icon available") } return biggest, nil } // ParsePNG parses a PNG image. func ParsePNG(r io.Reader) (image.Image, error) { img, err := png.Decode(r) return img, err } // ParseWebPNG parses a PNG image by HTTP URL. func ParseWebPNG(url string) (image.Image, error) { resp, err := http.Get(AddScheme(url)) if err != nil { return nil, err } defer resp.Body.Close() return ParsePNG(resp.Body) } // IcoToPNG converts a .ico file into PNG images. func IcoToPNG(r io.Reader) ([]image.Image, error) { var result = []image.Image{} bin, _ := ioutil.ReadAll(r) // Check if the image is already a PNG format. if string(bin[1:4]) == "PNG" { fmt.Printf("IcoToPNG: ico is already a png format!") buf := bytes.NewBuffer(bin) img, err := png.Decode(buf) if err != nil { return result, err } return []image.Image{img}, nil } // Ensure we have icotool available. if !HasICOTool() { return result, errors.New("icotool not available") } // Create a temp directory to extract this icon. dir, err := ioutil.TempDir("", "pwa-launcher") if err != nil { return result, fmt.Errorf("TempDir: %s", err) } log.Printf("Temp dir to extract .ico file: %s", dir) defer os.RemoveAll(dir) // Write the .ico binary to the source file. icoFile := filepath.Join(dir, "favicon.ico") fh, err := os.Create(icoFile) if err != nil { return result, fmt.Errorf("Write %s: %s", icoFile, err) } fh.Write(bin) fh.Close() // Run the commands. cmd := exec.Command( "icotool", "-x", "-o", dir, icoFile, ) stdout, err := cmd.CombinedOutput() fmt.Println(stdout) // Glom the PNG images. files, _ := filepath.Glob(filepath.Join(dir, "*.png")) for _, file := range files { fh, err := os.Open(file) if err != nil { continue } if img, err := ParsePNG(fh); err == nil { result = append(result, img) } fh.Close() } return result, nil } // HasICOTool checks if the icotool binary is available. func HasICOTool() bool { _, err := exec.LookPath("icotool") if err != nil { fmt.Printf( "********\n" + "WARNING: command `icotool` was not found on your system.\n" + "Extracting PNG images from .ico files will not be supported.\n" + "To remedy this, install icoutils, e.g. `sudo apt install icoutils`\n" + "********\n", ) } return true }