You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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)
}

问题根源

  1. 未分配像素内存:仅声明了lpPixels *[]byte但未实际分配内存,GetDIBits无法将数据写入空指针。
  2. DC参数错误:第二次调用GetDIBits使用了内存DC(hdc),而非屏幕DC(HDCScreen),导致无法正确读取位图数据。
  3. 资源管理不严谨:未恢复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像素顺序,手动转换为Goimage.RGBA所需的RGBA顺序,确保图像颜色正确。

内容的提问来源于stack exchange,提问作者EtherDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 12:50:25