5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 02:20:47 +08:00
wails/v3/pkg/w32/icon.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)
}