在 Windows 10 上保存位图并启用 GDI 缩放
Saving Bitmaps on Windows 10 with GDI scaling active
我有一个带工具栏的 MFC 应用程序(使用 CMFCToolbar)。我使用来自文件和资源的位图即时创建工具栏位图。 DIB 有不同的颜色格式。
- 所以我创建了一个与屏幕 DC 兼容的空位图工具栏图像。
- 然后我打开所有位图并将内容 blit 到工具栏位图(GDI 为我进行色彩空间转换和拉伸)。
- 然后我将位图保存到一个 24 位的 DIB 文件中。
- 然后我创建工具栏对象并加载图像。
这已经工作了很长时间并且现在正在工作,除了一种情况:
最近我们必须为 Windows 10 1703 及更高版本启用 GDI 缩放。
在具有高分辨率显示和 200% 缩放比例(如 Surface)的系统上,会出现以下效果:
所有工具栏图标都变形了。
我也找到了原因:
保存合成图像时,我只得到图像的左上四分之一。
与没有 GDI 缩放的正常分辨率显示相比,位图的宽度和高度没有变化(比如 1024x15)。但是该位图仅包含左上角四分之一的像素(参见下面的示例)。
所以我假设设备上下文告诉 Windows 大约 200% 缩放。从源到目标的 blitting 图像会自动放大,但位图的尺寸不会改变。
如何保存未缩放的位图?
-或-
如何正确保存缩放后的位图?从哪里得到丢失的像素?从哪里获得合适的尺寸? (HBITMAP 仅指未缩放的尺寸)。
示例:
无 GDI 缩放,正确:
200% 缩放比例,相同尺寸,但只有正确图像的左上四分之一:
总结及解决方案:
假设我们创建了一个与屏幕格式 (DDB) 兼容的内存位图:
CBitmap toolBitmap;
toolBitmap.CreateCompatibleBitmap (pDC, 1000, 20);
稍后我们将一些东西写入内存位图中(这里无关紧要)。现在我们要保存位图(作为 DIB 写入文件)。
虽然我们知道尺寸(此处:1000x20)我们不应该使用它们。因为在 Window 10 并且进程激活了 GDI 缩放并使用了带缩放的高分辨率显示 - 尺寸可能在内部发生了变化。因此位图不再是 1000x20。
这个失败了:
BITMAP bmHdr;
toolBitmap.GetObject(sizeof(BITMAP), &bmHdr);
位图 header 包含原始尺寸 (1000x20)。 使用它们保存到文件会导致图像不完整。只存储左上部分。
这个有效 - 我们可以检索缩放尺寸:
BITMAPINFO bi = {};
bi.dwSize = sizeof(bi);
int result = GetDIBits(pDC->GetSafeHdc(), (HBITMAP)toolBitmap.GetSafeHandle(), 0, 0, NULL, &bi, DIB_RGB_COLORS);
现在我们可以开始新的维度了。
我最终使用了 GDI+ 函数,它还保存了完整的(缩放的)位图:
Gdiplus::Bitmap bm((HBITMAP)toolBitmap.GetSafeHandle(), NULL);
Gdiplus::Status status = bm.Save(pwszFileName, &clsidEncoder, NULL);
我认为有大量旧的 MFC 和 GDI 代码无法在 Windows 10 上激活 GDI 缩放时正常工作。
我有一个带工具栏的 MFC 应用程序(使用 CMFCToolbar)。我使用来自文件和资源的位图即时创建工具栏位图。 DIB 有不同的颜色格式。
- 所以我创建了一个与屏幕 DC 兼容的空位图工具栏图像。
- 然后我打开所有位图并将内容 blit 到工具栏位图(GDI 为我进行色彩空间转换和拉伸)。
- 然后我将位图保存到一个 24 位的 DIB 文件中。
- 然后我创建工具栏对象并加载图像。
这已经工作了很长时间并且现在正在工作,除了一种情况:
最近我们必须为 Windows 10 1703 及更高版本启用 GDI 缩放。 在具有高分辨率显示和 200% 缩放比例(如 Surface)的系统上,会出现以下效果:
所有工具栏图标都变形了。
我也找到了原因:
保存合成图像时,我只得到图像的左上四分之一。 与没有 GDI 缩放的正常分辨率显示相比,位图的宽度和高度没有变化(比如 1024x15)。但是该位图仅包含左上角四分之一的像素(参见下面的示例)。
所以我假设设备上下文告诉 Windows 大约 200% 缩放。从源到目标的 blitting 图像会自动放大,但位图的尺寸不会改变。
如何保存未缩放的位图?
-或-
如何正确保存缩放后的位图?从哪里得到丢失的像素?从哪里获得合适的尺寸? (HBITMAP 仅指未缩放的尺寸)。
示例:
无 GDI 缩放,正确:
200% 缩放比例,相同尺寸,但只有正确图像的左上四分之一:
总结及解决方案:
假设我们创建了一个与屏幕格式 (DDB) 兼容的内存位图:
CBitmap toolBitmap;
toolBitmap.CreateCompatibleBitmap (pDC, 1000, 20);
稍后我们将一些东西写入内存位图中(这里无关紧要)。现在我们要保存位图(作为 DIB 写入文件)。
虽然我们知道尺寸(此处:1000x20)我们不应该使用它们。因为在 Window 10 并且进程激活了 GDI 缩放并使用了带缩放的高分辨率显示 - 尺寸可能在内部发生了变化。因此位图不再是 1000x20。
这个失败了:
BITMAP bmHdr;
toolBitmap.GetObject(sizeof(BITMAP), &bmHdr);
位图 header 包含原始尺寸 (1000x20)。 使用它们保存到文件会导致图像不完整。只存储左上部分。
这个有效 - 我们可以检索缩放尺寸:
BITMAPINFO bi = {};
bi.dwSize = sizeof(bi);
int result = GetDIBits(pDC->GetSafeHdc(), (HBITMAP)toolBitmap.GetSafeHandle(), 0, 0, NULL, &bi, DIB_RGB_COLORS);
现在我们可以开始新的维度了。
我最终使用了 GDI+ 函数,它还保存了完整的(缩放的)位图:
Gdiplus::Bitmap bm((HBITMAP)toolBitmap.GetSafeHandle(), NULL);
Gdiplus::Status status = bm.Save(pwszFileName, &clsidEncoder, NULL);
我认为有大量旧的 MFC 和 GDI 代码无法在 Windows 10 上激活 GDI 缩放时正常工作。