mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-04 02:20:47 +08:00
256 lines
5.7 KiB
Go
256 lines
5.7 KiB
Go
//go:build windows
|
|
|
|
package w32
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/png"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, version uint32, cxDesired int, cyDesired int, flags uint) (uintptr, error) {
|
|
icon := 0
|
|
if isIcon {
|
|
icon = 1
|
|
}
|
|
r, _, err := procCreateIconFromResourceEx.Call(
|
|
presbits,
|
|
uintptr(dwResSize),
|
|
uintptr(icon),
|
|
uintptr(version),
|
|
uintptr(cxDesired),
|
|
uintptr(cyDesired),
|
|
uintptr(flags),
|
|
)
|
|
|
|
if r == 0 {
|
|
return 0, err
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func isPNG(fileData []byte) bool {
|
|
if len(fileData) < 4 {
|
|
return false
|
|
}
|
|
return string(fileData[:4]) == "\x89PNG"
|
|
}
|
|
|
|
func isICO(fileData []byte) bool {
|
|
if len(fileData) < 4 {
|
|
return false
|
|
}
|
|
return string(fileData[:4]) == "\x00\x00\x01\x00"
|
|
}
|
|
|
|
// CreateSmallHIconFromImage creates a HICON from a PNG or ICO file
|
|
func CreateSmallHIconFromImage(fileData []byte) (HICON, error) {
|
|
if len(fileData) < 8 {
|
|
return 0, fmt.Errorf("invalid file format")
|
|
}
|
|
|
|
if !isPNG(fileData) && !isICO(fileData) {
|
|
return 0, fmt.Errorf("unsupported file format")
|
|
}
|
|
iconWidth := GetSystemMetrics(SM_CXSMICON)
|
|
iconHeight := GetSystemMetrics(SM_CYSMICON)
|
|
icon, err := CreateIconFromResourceEx(
|
|
uintptr(unsafe.Pointer(&fileData[0])),
|
|
uint32(len(fileData)),
|
|
true,
|
|
0x00030000,
|
|
iconWidth,
|
|
iconHeight,
|
|
LR_DEFAULTSIZE)
|
|
return HICON(icon), err
|
|
}
|
|
|
|
// CreateLargeHIconFromImage creates a HICON from a PNG or ICO file
|
|
func CreateLargeHIconFromImage(fileData []byte) (HICON, error) {
|
|
if len(fileData) < 8 {
|
|
return 0, fmt.Errorf("invalid file format")
|
|
}
|
|
|
|
if !isPNG(fileData) && !isICO(fileData) {
|
|
return 0, fmt.Errorf("unsupported file format")
|
|
}
|
|
iconWidth := GetSystemMetrics(SM_CXICON)
|
|
iconHeight := GetSystemMetrics(SM_CXICON)
|
|
icon, err := CreateIconFromResourceEx(
|
|
uintptr(unsafe.Pointer(&fileData[0])),
|
|
uint32(len(fileData)),
|
|
true,
|
|
0x00030000,
|
|
iconWidth,
|
|
iconHeight,
|
|
LR_DEFAULTSIZE)
|
|
return HICON(icon), err
|
|
}
|
|
|
|
type ICONINFO struct {
|
|
FIcon int32
|
|
XHotspot int32
|
|
YHotspot int32
|
|
HbmMask syscall.Handle
|
|
HbmColor syscall.Handle
|
|
}
|
|
|
|
func SaveHIconAsPNG(hIcon HICON, filePath string) error {
|
|
// Load necessary DLLs
|
|
user32 := syscall.NewLazyDLL("user32.dll")
|
|
gdi32 := syscall.NewLazyDLL("gdi32.dll")
|
|
|
|
// Get procedures
|
|
getIconInfo := user32.NewProc("GetIconInfo")
|
|
getObject := gdi32.NewProc("GetObjectW")
|
|
createCompatibleDC := gdi32.NewProc("CreateCompatibleDC")
|
|
selectObject := gdi32.NewProc("SelectObject")
|
|
getDIBits := gdi32.NewProc("GetDIBits")
|
|
deleteObject := gdi32.NewProc("DeleteObject")
|
|
deleteDC := gdi32.NewProc("DeleteDC")
|
|
|
|
// Get icon info
|
|
var iconInfo ICONINFO
|
|
ret, _, err := getIconInfo.Call(
|
|
uintptr(hIcon),
|
|
uintptr(unsafe.Pointer(&iconInfo)),
|
|
)
|
|
if ret == 0 {
|
|
return err
|
|
}
|
|
defer deleteObject.Call(uintptr(iconInfo.HbmMask))
|
|
defer deleteObject.Call(uintptr(iconInfo.HbmColor))
|
|
|
|
// Get bitmap info
|
|
var bmp BITMAP
|
|
ret, _, err = getObject.Call(
|
|
uintptr(iconInfo.HbmColor),
|
|
unsafe.Sizeof(bmp),
|
|
uintptr(unsafe.Pointer(&bmp)),
|
|
)
|
|
if ret == 0 {
|
|
return err
|
|
}
|
|
|
|
// Create DC
|
|
hdc, _, _ := createCompatibleDC.Call(0)
|
|
if hdc == 0 {
|
|
return syscall.EINVAL
|
|
}
|
|
defer deleteDC.Call(hdc)
|
|
|
|
// Select bitmap into DC
|
|
oldBitmap, _, _ := selectObject.Call(hdc, uintptr(iconInfo.HbmColor))
|
|
defer selectObject.Call(hdc, oldBitmap)
|
|
|
|
// Prepare bitmap info header
|
|
var bi BITMAPINFO
|
|
bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader))
|
|
bi.BmiHeader.BiWidth = bmp.BmWidth
|
|
bi.BmiHeader.BiHeight = bmp.BmHeight
|
|
bi.BmiHeader.BiPlanes = 1
|
|
bi.BmiHeader.BiBitCount = 32
|
|
bi.BmiHeader.BiCompression = BI_RGB
|
|
|
|
// Allocate memory for bitmap bits
|
|
width, height := int(bmp.BmWidth), int(bmp.BmHeight)
|
|
bufferSize := width * height * 4
|
|
bits := make([]byte, bufferSize)
|
|
|
|
// Get bitmap bits
|
|
ret, _, err = getDIBits.Call(
|
|
hdc,
|
|
uintptr(iconInfo.HbmColor),
|
|
0,
|
|
uintptr(bmp.BmHeight),
|
|
uintptr(unsafe.Pointer(&bits[0])),
|
|
uintptr(unsafe.Pointer(&bi)),
|
|
DIB_RGB_COLORS,
|
|
)
|
|
if ret == 0 {
|
|
return err
|
|
}
|
|
|
|
// Create Go image
|
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
|
|
// Convert DIB to RGBA
|
|
for y := 0; y < height; y++ {
|
|
for x := 0; x < width; x++ {
|
|
// DIB is bottom-up, so we need to invert Y
|
|
dibIndex := ((height-1-y)*width + x) * 4
|
|
|
|
// BGRA to RGBA
|
|
b := bits[dibIndex]
|
|
g := bits[dibIndex+1]
|
|
r := bits[dibIndex+2]
|
|
a := bits[dibIndex+3]
|
|
|
|
// Set pixel in the image
|
|
img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a})
|
|
}
|
|
}
|
|
|
|
// Create output file
|
|
outFile, err := os.Create(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer outFile.Close()
|
|
|
|
// Encode and save the image
|
|
return png.Encode(outFile, img)
|
|
}
|
|
|
|
func SetWindowIcon(hwnd HWND, icon HICON) {
|
|
SendMessage(hwnd, WM_SETICON, ICON_SMALL, uintptr(icon))
|
|
}
|
|
|
|
func pngToImage(data []byte) (*image.RGBA, error) {
|
|
img, err := png.Decode(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bounds := img.Bounds()
|
|
rgba := image.NewRGBA(bounds)
|
|
draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
|
|
return rgba, nil
|
|
}
|
|
|
|
func SetMenuIcons(parentMenu HMENU, itemID int, unchecked []byte, checked []byte) error {
|
|
if unchecked == nil {
|
|
return fmt.Errorf("invalid unchecked bitmap")
|
|
}
|
|
var err error
|
|
var uncheckedIcon, checkedIcon HBITMAP
|
|
var uncheckedImage, checkedImage *image.RGBA
|
|
uncheckedImage, err = pngToImage(unchecked)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
uncheckedIcon, err = CreateHBITMAPFromImage(uncheckedImage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if checked != nil {
|
|
checkedImage, err = pngToImage(checked)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
checkedIcon, err = CreateHBITMAPFromImage(checkedImage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
checkedIcon = uncheckedIcon
|
|
}
|
|
return SetMenuItemBitmaps(parentMenu, uint32(itemID), MF_BYCOMMAND, checkedIcon, uncheckedIcon)
|
|
}
|