diff --git a/cmd/package.go b/cmd/package.go index 48fb38dfa..e9624a3ca 100644 --- a/cmd/package.go +++ b/cmd/package.go @@ -1,9 +1,12 @@ package cmd import ( + "bufio" "bytes" + "encoding/binary" "fmt" "image" + "image/png" "io/ioutil" "os" "path" @@ -13,6 +16,8 @@ import ( "text/template" "time" + "golang.org/x/image/draw" + "github.com/jackmordaunt/icns" ) @@ -55,6 +60,87 @@ func newPlistData(title, exe, packageID, version, author string) *plistData { } } +type windowsIconHeader struct { + _ uint16 + imageType uint16 + imageCount uint16 + width uint8 + height uint8 + colours uint8 + _ uint8 + planes uint16 + bpp uint16 + size uint32 + offset uint32 +} + +func generateWindowsIcon(pngFilename string, iconfile string) error { + header := &windowsIconHeader{ + imageType: 1, + imageCount: 1, + bpp: 32, + planes: 1, + offset: 22, + width: 255, + height: 255, + } + + // Load png + pngfile, err := os.Open(pngFilename) + if err != nil { + return err + } + defer pngfile.Close() + + // Decode to internal image + pngdata, err := png.Decode(pngfile) + if err != nil { + return err + } + + // Scale to 256*256 + rect := image.Rect(0, 0, 255, 255) + rawdata := image.NewRGBA(rect) + scale := draw.ApproxBiLinear + scale.Scale(rawdata, rect, pngdata, pngdata.Bounds(), draw.Over, nil) + + // Convert back to PNG + icondata := new(bytes.Buffer) + writer := bufio.NewWriter(icondata) + err = png.Encode(writer, rawdata) + if err != nil { + return err + } + err = writer.Flush() + if err != nil { + return err + } + + // Save size of PNG data + header.size = uint32(len(icondata.Bytes())) + + // Open icon file for writing + outfilename := filepath.Join(filepath.Dir(pngFilename), iconfile) + outfile, err := os.Create(outfilename) + if err != nil { + return err + } + defer outfile.Close() + + // Write out the header + err = binary.Write(outfile, binary.LittleEndian, header) + if err != nil { + return err + } + + // Write out the image data + _, err = outfile.Write(icondata.Bytes()) + if err != nil { + return err + } + return nil +} + func defaultString(val string, defaultVal string) string { if val != "" { return val @@ -177,14 +263,16 @@ func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error { outputDir := b.fs.Cwd() basename := strings.TrimSuffix(po.BinaryName, ".exe") - // Copy icon - tgtIconFile := filepath.Join(outputDir, basename+".ico") - if !b.fs.FileExists(tgtIconFile) { - srcIconfile := filepath.Join(b.getPackageFileBaseDir(), "wails.ico") - err := b.fs.CopyFile(srcIconfile, tgtIconFile) - if err != nil { - return err - } + // Copy default icon if needed + icon, err := b.copyIcon() + if err != nil { + return err + } + + // Generate icon from PNG + err = generateWindowsIcon(icon, po.BinaryName+".ico") + if err != nil { + return err } // Copy manifest @@ -243,7 +331,7 @@ func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error { return nil } -func (b *PackageHelper) copyIcon(resourceDir string) (string, error) { +func (b *PackageHelper) copyIcon() (string, error) { // TODO: Read this from project.json const appIconFilename = "appicon.png" @@ -268,7 +356,7 @@ func (b *PackageHelper) copyIcon(resourceDir string) (string, error) { func (b *PackageHelper) packageIconOSX(resourceDir string) error { - srcIcon, err := b.copyIcon(resourceDir) + srcIcon, err := b.copyIcon() if err != nil { return err } diff --git a/go.mod b/go.mod index 5f62b2058..d6b34de22 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/stretchr/testify v1.3.0 // indirect github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 // indirect + golang.org/x/image v0.0.0-20200430140353-33d19683fad8 golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 // indirect golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 golang.org/x/text v0.3.0 diff --git a/go.sum b/go.sum index 24fc4f54c..6b2c18543 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 h1:6M3SDHlHHDCx2PcQw3S4KsR170vGqDhJDOmpVd4Hjak= golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=