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 }