icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

gdi.go (2611B)


      1 //go:build windows
      2 
      3 package provider
      4 
      5 import (
      6 	"fmt"
      7 	"image"
      8 	"unsafe"
      9 
     10 	"golang.org/x/sys/windows"
     11 )
     12 
     13 var (
     14 	gdi32                = windows.NewLazySystemDLL("gdi32.dll")
     15 	procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
     16 	procDeleteObject     = gdi32.NewProc("DeleteObject")
     17 )
     18 
     19 const (
     20 	biRGB         = 0
     21 	dibRGBColors  = 0
     22 	bitsPerPixel  = 32
     23 	bytesPerPixel = bitsPerPixel / 8
     24 )
     25 
     26 // bitmapInfoHeader mirrors BITMAPINFOHEADER from wingdi.h.
     27 type bitmapInfoHeader struct {
     28 	Size          uint32
     29 	Width         int32
     30 	Height        int32
     31 	Planes        uint16
     32 	BitCount      uint16
     33 	Compression   uint32
     34 	SizeImage     uint32
     35 	XPelsPerMeter int32
     36 	YPelsPerMeter int32
     37 	ClrUsed       uint32
     38 	ClrImportant  uint32
     39 }
     40 
     41 // bitmapInfo mirrors BITMAPINFO; the colour table is unused at 32bpp.
     42 type bitmapInfo struct {
     43 	Header bitmapInfoHeader
     44 	Colors [1]uint32
     45 }
     46 
     47 // CreateDIB copies img into a new top-down 32bpp BGRA device-independent
     48 // bitmap. Go's [image.RGBA] is already alpha-premultiplied, which is exactly
     49 // what the shell expects for WTSAT_ARGB thumbnails; only the channel order
     50 // changes. The caller owns the returned handle.
     51 func CreateDIB(img *image.RGBA) (windows.Handle, error) {
     52 	w, h := img.Rect.Dx(), img.Rect.Dy()
     53 	if w <= 0 || h <= 0 {
     54 		return 0, fmt.Errorf("empty image %v", img.Rect)
     55 	}
     56 	bmi := bitmapInfo{Header: bitmapInfoHeader{
     57 		Size:        uint32(unsafe.Sizeof(bitmapInfoHeader{})),
     58 		Width:       int32(w),
     59 		Height:      -int32(h), // Negative height selects a top-down bitmap.
     60 		Planes:      1,
     61 		BitCount:    bitsPerPixel,
     62 		Compression: biRGB,
     63 	}}
     64 	var bits unsafe.Pointer
     65 	r, _, err := procCreateDIBSection.Call(
     66 		0, // No device context is needed for DIB_RGB_COLORS.
     67 		uintptr(unsafe.Pointer(&bmi)),
     68 		dibRGBColors,
     69 		uintptr(unsafe.Pointer(&bits)),
     70 		0, // No file mapping.
     71 		0,
     72 	)
     73 	if r == 0 || bits == nil {
     74 		return 0, fmt.Errorf("CreateDIBSection: %w", err)
     75 	}
     76 	// A 32bpp stride is always DWORD aligned, so rows are packed.
     77 	stride := w * bytesPerPixel
     78 	dst := unsafe.Slice((*byte)(bits), stride*h)
     79 	for y := 0; y < h; y++ {
     80 		src := img.Pix[y*img.Stride : y*img.Stride+stride]
     81 		row := dst[y*stride : (y+1)*stride]
     82 		for x := 0; x < stride; x += bytesPerPixel {
     83 			row[x+0] = src[x+2] // B
     84 			row[x+1] = src[x+1] // G
     85 			row[x+2] = src[x+0] // R
     86 			row[x+3] = src[x+3] // A
     87 		}
     88 	}
     89 	return windows.Handle(r), nil
     90 }
     91 
     92 // DeleteObject frees a GDI object such as the bitmap returned by [CreateDIB].
     93 func DeleteObject(h windows.Handle) error {
     94 	r, _, err := procDeleteObject.Call(uintptr(h))
     95 	if r == 0 {
     96 		return fmt.Errorf("DeleteObject: %w", err)
     97 	}
     98 	return nil
     99 }