LoadImage()返回位图句柄后,使用GetObject()获取位图大小有时会返回不正确的结果。

3
我们发现在 Windows XP 下,使用位图作为按钮背景的自绘制按钮会偶尔出现显示位图错误的问题。如果同一窗口中包含多个使用相同位图文件作为按钮背景的按钮,则大多数按钮将正确显示,但某些情况下可能会有一个或两个按钮将显示缩小后的位图背景。
如果退出应用程序然后重新启动它,您可能会看到相同的按钮图标显示不正确的行为,但可能与之前的按钮不同。这种图标显示不正确的行为并不总是出现。有时会出现,有时则不会。由于我们只需加载一次按钮图标,因此一旦按钮显示不正确,它将始终显示不正确。
通过调试器,我们最终发现,在调用 GetObject() 函数时,返回的位图大小数据有时会不正确。例如,在某个情况下,位图大小是 75x75 像素,而 GetObject() 返回的大小却是 13x13。由于此大小是位图绘制的一部分,因此显示的背景变成了按钮窗口上的小装饰品。
实际源代码如下。
if (!hBitmapFocus) {
    CString iconPath;
    iconPath.Format(ICON_FILES_DIR_FORMAT, m_Icon);
    hBitmapFocus = (HBITMAP)LoadImage(NULL, iconPath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
}
if (hBitmapFocus) {
    BITMAP   bitmap;
    int iNoBytes = GetObject(hBitmapFocus, sizeof(BITMAP), &bitmap);
    if (iNoBytes < 1) {
        char xBuff[128];
        sprintf (xBuff, "GetObject() failed. GetLastError = %d", GetLastError ());
        NHPOS_ASSERT_TEXT((iNoBytes > 0), xBuff);
    }
    cxSource = bitmap.bmWidth;
    cySource = bitmap.bmHeight;
    //Bitmaps cannot be drawn directly to the screen so a 
    //compatible memory DC is created to draw to, then the image is 
    //transfered to the screen
    CDC hdcMem;
    hdcMem.CreateCompatibleDC(pDC);

    HGDIOBJ  hpOldObject = hdcMem.SelectObject(hBitmapFocus);

    int xPos;
    int yPos;

    //The Horizontal and Vertical Alignment
    //For Images
    //Are set in the Layout Manager
    //the proper attribute will have to be checked against
    //for now the Image is centered on the button

    //Horizontal Alignment
    if(btnAttributes.horIconAlignment == IconAlignmentHLeft){//Image to left
        xPos = 2;
    }else if(btnAttributes.horIconAlignment == IconAlignmentHRight){//Image to right
       xPos = myRect.right - cxSource - 5;
    }else {//Horizontal center
       xPos = ((myRect.right - cxSource) / 2) - 1;
    }

    //Vertical Alignment
    if(btnAttributes.vertIconAlignment == IconAlignmentVTop){//Image to top
        yPos = 2;
    }else if(btnAttributes.vertIconAlignment == IconAlignmentVBottom){//Image to bottom
        yPos = myRect.bottom - cySource - 5;
    }else{//Vertical Center
        yPos = ((myRect.bottom - cySource) / 2) - 1;
    }

    pDC->BitBlt(xPos, yPos, cxSource, cySource, &hdcMem, 0, 0, SRCCOPY);

    hdcMem.SelectObject(hpOldObject);
}

使用调试器,我们可以看到iconPath字符串是正确的,并且位图已加载,因为hBitmapFocus不为NULL。接下来,我们可以看到调用GetObject(),返回iNoBytes的值等于24。对于那些正确显示的按钮,bitmap.bmWidthbitmap.bmHeight中的值是正确的,但对于那些不正确的按钮,这些值要小得多,导致在绘制位图时尺寸不正确。
该变量在类头文件中定义。
HBITMAP hBitmapFocus;

作为研究的一部分,我找到了这个栈溢出问题,GetObject返回奇怪的大小,我想知道这里是否存在某种对齐问题。
在调用GetObject()时,bitmap变量是否需要处于某种对齐边界上?尽管我们对一些数据使用紧凑格式,但我们正在使用pragma指令来仅指定包含需要在一个字节边界上打包的特定结构体的代码的特定部分,这些结构体在包含文件中。

你说“可能会有一个或两个按钮显示缩小尺寸的位图背景”。BitBlt在绘制时不会缩放图像-传递较小的大小将剪切图像而不是缩放它。你是否真的意味着图像被剪裁,还是确实看到完整的图像缩小了?如果是后者,则问题在于实际加载的图像而不是渲染。 - Jonathan Potter
@JonathanPotter 使用的位图通常是某种颜色,因此很难确定。然而,问题似乎出在GetObject()返回错误数据而不是BitBlt()问题上。我预期的是,图像被剪切了,因为当将大小传递给BitBlt()时,由于GetObject()报告的大小不正确,大小计算不正确。根据我看到的位图变量的内存地址,我怀疑这不是对齐问题。下一个按钮经常使用相同的地址,并且数据将对其正确。 - Richard Chambers
我进行了更改,以捕获额外的信息,并且不再看到问题。 <叹气> - Richard Chambers
总是很棒当这种事情发生 :) - Jonathan Potter
尽管采用了新的程序,问题仍然出现了。似乎Windows API存在问题,导致GetObject()函数返回错误的位图尺寸。<叹气> <叹气> - Richard Chambers
显示剩余2条评论
1个回答

0
请阅读这篇Microsoft KB文章,了解如何加载带调色板信息的位图。它还有一个很好的示例。
另外注意:我没有在你的代码中看到任何地方调用::DeleteObject(hBitmapFocus)。这非常重要,因为你很快就会用完GDI对象。
使用Windows任务管理器查看你的程序是否耗尽了GDI资源总是一个好主意。只需将“GDI对象”列添加到任务管理器中,查看对象数量是否不断增加,而是保持在预期范围内,类似于其他程序。

谢谢提供的链接,我会看一下。但是我仍然好奇为什么位图数据通常是正确的,但有时却是错误的。提供的代码实际上只是显示位图的代码的一部分,并且作为显示的一部分,会检查是否有图标要放到按钮上。一旦创建了位图,它就会保留,直到按钮被销毁。如果与按钮关联了位图,则按钮对象的析构函数将执行DeleteObject操作。并非所有按钮都会有位图。 - Richard Chambers
我查看了提供的链接,不确定它是否提供了关于我遇到的问题的任何信息。该示例似乎正在执行我没有执行的操作。我的问题是,使用GetObject()检索的位图大小信息是不正确的。然而,进一步思考后,我将更改为在LoadImage()之后立即使用GetObject()来检查大小。 - Richard Chambers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接