如何将HBITMAP转换为image.Image?Go调用Windows API遇问题
HBITMAP转image.Image失败问题修复
问题描述
需要从指定Windows应用程序窗口捕获图像并转换为Go的image.Image类型,目前通过API获取到HBITMAP后,调用GetDIBits始终无法获取到像素数据(lpPixels为nil),两次调用返回值均为1。
原始代码
rc := w32.GetClientRect(hwnd) if rc != nil { // create HDCScreen := w32.GetWindowDC(hwnd) hdc := w32.CreateCompatibleDC(HDCScreen) hbmp := w32.CreateCompatibleBitmap(HDCScreen, int(rc.Right)-int(rc.Left), int(rc.Bottom)-int(rc.Top)) w32.SelectObject(hdc, w32.HGDIOBJ(hbmp)) // Print to memory hdc w32.PrintWindow(hwnd, hdc, 0x00000002) // ------------------------------------------------ var bmpInfo *w32.BITMAPINFO = &w32.BITMAPINFO{} bmpInfo.BmiHeader.BiSize = uint32(unsafe.Sizeof(bmpInfo.BmiHeader)) firstDIBits := w32.GetDIBits(HDCScreen, hbmp, 0, 0, nil, bmpInfo, w32.DIB_RGB_COLORS) fmt.Printf("firstDIBits: %v\n", firstDIBits) var lpPixels *[]byte bmpInfo.BmiHeader.BiBitCount = 32 bmpInfo.BmiHeader.BiCompression = w32.BI_RGB bmpInfo.BmiHeader.BiHeight = int32(math.Abs(float64(bmpInfo.BmiHeader.BiHeight))) bmpInfo.BmiHeader.BiCompression = w32.BI_RGB secondDIBits := w32.GetDIBits(hdc, hbmp, 0, uint(bmpInfo.BmiHeader.BiHeight), unsafe.Pointer(lpPixels), bmpInfo, w32.DIB_RGB_COLORS) fmt.Printf("secondDIBits: %v\n", secondDIBits) fmt.Printf("lpPixels: %v\n", lpPixels) // ------------------------------------------------ // copy to clipBoard w32.OpenClipboard(0) w32.EmptyClipboard() w32.SetClipboardData(w32.CF_BITMAP, w32.HANDLE(hbmp)) w32.CloseClipboard() // release w32.DeleteDC(hdc) w32.DeleteObject(w32.HGDIOBJ(hbmp)) w32.ReleaseDC(0, HDCScreen) }
问题根源
- 未分配像素内存:仅声明了
lpPixels *[]byte但未实际分配内存,GetDIBits无法将数据写入空指针。 - DC参数错误:第二次调用
GetDIBits使用了内存DC(hdc),而非屏幕DC(HDCScreen),导致无法正确读取位图数据。 - 资源管理不严谨:未恢复
SelectObject替换的原始位图,存在GDI资源泄漏风险。
修复后的代码
import ( "fmt" "image" "image/color" "math" "unsafe" "github.com/AllenDang/w32" ) func captureWindowToImage(hwnd w32.HWND) (image.Image, error) { rc := w32.GetClientRect(hwnd) if rc == nil { return nil, fmt.Errorf("failed to get client rect") } width := int(rc.Right) - int(rc.Left) height := int(rc.Bottom) - int(rc.Top) // 获取窗口DC并确保释放 hdcscreen := w32.GetWindowDC(hwnd) defer w32.ReleaseDC(0, hdcscreen) // 创建兼容DC并确保释放 hdc := w32.CreateCompatibleDC(hdcscreen) defer w32.DeleteDC(hdc) // 创建兼容位图并确保释放 hbmp := w32.CreateCompatibleBitmap(hdcscreen, width, height) defer w32.DeleteObject(w32.HGDIOBJ(hbmp)) // 保存原始位图,后续恢复 oldBmp := w32.SelectObject(hdc, w32.HGDIOBJ(hbmp)) defer w32.SelectObject(hdc, oldBmp) // 将窗口内容绘制到内存DC if !w32.PrintWindow(hwnd, hdc, 0x00000002) { return nil, fmt.Errorf("print window failed") } // 初始化BITMAPINFO,获取位图基础信息 var bmpInfo w32.BITMAPINFO bmpInfo.BmiHeader.BiSize = uint32(unsafe.Sizeof(bmpInfo.BmiHeader)) firstDIBits := w32.GetDIBits(hdcscreen, hbmp, 0, 0, nil, &bmpInfo, w32.DIB_RGB_COLORS) if firstDIBits == 0 { return nil, fmt.Errorf("get dib info failed") } // 设置目标格式:32位BGRA,无压缩 bmpInfo.BmiHeader.BiBitCount = 32 bmpInfo.BmiHeader.BiCompression = w32.BI_RGB // 计算行字节数(必须是4的倍数) stride := (width*int(bmpInfo.BmiHeader.BiBitCount) + 31) / 32 * 4 // 计算总像素数据大小 pixelSize := stride * height // 分配内存存储像素数据 lpPixels := make([]byte, pixelSize) // 获取像素数据 secondDIBits := w32.GetDIBits(hdcscreen, hbmp, 0, uint(bmpInfo.BmiHeader.BiHeight), unsafe.Pointer(&lpPixels[0]), &bmpInfo, w32.DIB_RGB_COLORS) if secondDIBits == 0 { return nil, fmt.Errorf("get dib bits failed") } // 将BGRA格式转换为Go的RGBA格式 img := image.NewRGBA(image.Rect(0, 0, width, height)) for y := 0; y < height; y++ { for x := 0; x < width; x++ { idx := y*stride + x*4 b := lpPixels[idx] g := lpPixels[idx+1] r := lpPixels[idx+2] a := lpPixels[idx+3] img.SetRGBA(x, y, color.RGBA{r, g, b, a}) } } // 可选:复制到剪贴板 w32.OpenClipboard(0) w32.EmptyClipboard() w32.SetClipboardData(w32.CF_BITMAP, w32.HANDLE(hbmp)) w32.CloseClipboard() return img, nil }
关键修复点说明
- 内存分配:根据位图尺寸和位深计算所需内存,用
make()分配lpPixels数组,并传递其首地址给GetDIBits。 - DC修正:第二次
GetDIBits使用屏幕DC(hdcscreen),确保能正确读取位图数据。 - 资源安全:用
defer语句自动释放GDI资源,同时恢复SelectObject替换的原始位图,避免资源泄漏。 - 格式转换:Windows DIB默认采用BGRA像素顺序,手动转换为Go
image.RGBA所需的RGBA顺序,确保图像颜色正确。
内容的提问来源于stack exchange,提问作者EtherDev
相关产品推荐
相关产品推荐

