纯 C 重采样 DLL
一个小型 DLL,提供两个函数来重采样基于 GDI 的位图

引言
当我第一次看到 Libor Tinka 关于重采样出色的文章 Image Resizing - Outperform GDI+ 时,我对自己说:“这确实是非常好的东西,我确实需要一个类似的工具来处理基于 GDI 的位图。”
库
Resample.dll 是一个小型动态链接库,它导出了两个重采样函数。其设计旨在模仿 Win32 GDI API 的接口和行为。
函数场景很简单:假设我们有一个 GDI HBITMAP
(例如通过 LoadImage
从文件加载),大小为 w
x h
,我们需要将其调整到 wNew
x hNew
,而图像质量损失不大。我们可以通过简单调用库提供的函数 CreateResampledBitmap
来完成上述任务,如下所示:
hBmpNew = CreateResampledBitmap(hdc, hBmp, wNew, hNew, STOCK_FILTER_LANCZOS8);
请注意(例如,与 GDI CreateCompatibleBitmap
一样),获取的句柄在使用完毕后必须通过 GDI DeleteObject
进行删除。另一个导出的函数是 CreateUserFilterResampledBitmap
,它允许调用者提供一个自定义过滤器函数指针。
致谢
本文基于 Libor Tinka 的文章:Image Resizing - Outperform GDI+。我只是将他的 C# 代码移植到了纯 C 代码(稍稍修改了算法),并将所有内容打包到一个 DLL 中。所有内置过滤器都是 Libor 在原文章中使用的过滤器,可以作为参考。
背景
使用此代码需要对 Win32 GDI API 有 general 的理解。要修改库的内部实现,则需要对 GDI 位图和 C 语言有更深入的理解。使用自定义过滤器时,熟悉回调函数可能会有所帮助。


库参考
由于该库只包含两个函数,我可以用经典的 GDI 文档风格提供参考信息。
使用内置过滤器进行重采样
CreateResampledBitmap
CreateResampledBitmap
函数创建一个与指定设备上下文关联的设备的兼容的重采样位图。重采样过滤器将从可用的内置过滤器中选择。
HBITMAP CreateResampledBitmap(
HDC hdc, // handle to DC
HBITMAP hBmpSource, // handle to original bitmap
DWORD dwWidth, // width of the resampled bitmap, in pixels
DWORD dwHeight, // height of the resampled bitmap, in pixels
DWORD dwFilter // index of the stock resampling filter used
);
参数
hdc
[in] Handle to a device context
hBmpSource
[in] Handle to the original bitmap
dwWidth
[in] Specifies the resampled bitmap width, in pixels
dwHeight
[in] Specifies the resampled bitmap height, in pixels
dwFilter
[in] Specifies the index of the stock resampling filter
Can be one of the following values
STOCK_FILTER_BELL
STOCK_FILTER_BOX
STOCK_FILTER_CATMULLROM
STOCK_FILTER_COSINE
STOCK_FILTER_CUBICCONVOLUTION
STOCK_FILTER_CUBICSPLINE
STOCK_FILTER_HERMITE
STOCK_FILTER_LANCZOS3
STOCK_FILTER_LANCZOS8
STOCK_FILTER_MITCHELL
STOCK_FILTER_QUADRATIC
STOCK_FILTER_QUADRATICBSPLINE
STOCK_FILTER_TRIANGLE
返回值
如果函数成功,返回值是重采样位图的句柄。如果函数失败,返回值是 NULL
。要获取扩展的错误信息,请调用 GetLastError
。
备注
dwWidth
和dwHeight
将被裁剪到1-4096
的范围。dwFilter
将围绕可用的内置过滤器范围进行循环(即dwFilter=STOCK_FILTER_TRIANGLE+1
将变为dwFilter=STOCK_FILTER_BELL
)。
使用自定义过滤器进行重采样
CreateUserFilterResampledBitmap
CreateUserFilterResampledBitmap
函数创建一个与指定设备上下文关联的设备的兼容的重采样位图。重采样过滤器由调用者提供。
HBITMAP CreateUserFilterResampledBitmap(
HDC hdc, // handle to DC
HBITMAP hBmpSource, // handle to original bitmap
DWORD dwWidth, // width of the resampled bitmap, in pixels
DWORD dwHeight, // height of the resampled bitmap, in pixels
double (*pCustomFilter)(double), // custom filter function pointer
double dRadius // custom filter radius
);
参数
hdc
[in] Handle to a device context
hBmpSource
[in] Handle to the original bitmap
dwWidth
[in] Specifies the resampled bitmap width, in pixels
dwHeight
[in] Specifies the resampled bitmap height, in pixels
pCustomFilter
[in] Specifies the pointer to the custom filter function.
dRadius
[in] Radius of the custom filter.
返回值
如果函数成功,返回值是重采样位图的句柄。如果函数失败,返回值是 NULL
。要获取扩展的错误信息,请调用 GetLastError
。
备注
dwWidth
和dwHeight
将被裁剪到1-4096
的范围。Radius 应该是
pCustomFilter
函数的合适过滤器半径。Radius
的合法范围是 0.0-16.0。
Using the Code
项目设置
为了使用 Resample.dll 函数,应用程序必须
- 包含 Resample.h 头文件。
- 链接 Resample.lib 文件。
这当然意味着 Visual Studio 环境必须能够找到头文件和库文件的路径。
使用该库非常直接,以下代码片段从 test.bmp 文件加载一个位图,并使用 BOX
内置过滤器对其进行重采样。
...
// load the original bitmap
hBmp = (HBITMAP) LoadImage( hInstance, _T("test.bmp"),
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (! hBmp ) return FALSE;
// create the 1024x768 resampled bitmap with stock filter
hBmpResampled = CreateResampledBitmap(hdc, hBmp, 1024, 768, STOCK_FILTER_BOX);
...
以下代码段则演示了自定义过滤器的使用。
...
// user-filter radius
const double dRad = 3.0;
...
// user-filter function
double myFilter( double x)
{
if ( x < 0.0 ) x = -x;
if (x < dRad) return (dRad * dRad - 2 * dRad * x + x * x);
return 0.0;
}
...
// load the original bitmap
hBmp = (HBITMAP) LoadImage( hInstance, _T("test.bmp"),
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (! hBmp ) return FALSE;
// create the 1024x768 resampled bitmap with user filter
hBmpResampled = CreateUserFilterResampledBitmap(hdc, hBmp, 1024, 768, myFilter, dRad);
...
创建的 HBITMAP
可以在应用程序的 WM_PAINT
消息处理程序中像任何其他有效的位图句柄一样使用,如下所示:
// WM_PAINT message handler
...
// hdc is the painting device context
HDC hMemdc = CreateCompatibleDC(hdc);
if (hMemDC)
{
HBITMAP hBmpOld = (HBITMAP) SelectObject(hMemdc, hBmpResampled);
BitBlt(hdc, 0, 0, 1024, 768, hMemdc, 0, 0, SRCCOPY);
SelectObject(hMemdc, hBmpOld);
DeleteDC(hMemDC);
}
...
测试应用程序
我包含了一个名为 ResampleTestApp
的测试应用程序项目。它允许用户加载位图,然后加载的图像(使用其原始尺寸)显示在主窗口中,而其重采样后的副本则绘制在一个子窗口中。用户可以选择过滤器类型和重采样位图的缩放比例。该应用程序(标准的 C++ Windows 应用,无 MFC),虽然非常基础且粗糙,但允许在不同图像上尝试所有过滤器。子窗口的标题栏显示有关发生的重采样的一些信息。

即
- 使用的过滤器名称。
- 重采样图像的有效尺寸。
- 重采样的有效比例。
- 经过的时间。
由于重采样位图尺寸范围(1-4096
)的限制,重采样有效比例可能与请求的比例有显著差异。
请注意,该应用程序会简单地创建一个具有所选比例的重采样位图,例如,如果您请求将原始 1024
x 768
位图放大 4x
,即使窗口本身远小于 4096
x 3072
,它也会调用重采样函数(在绘制图像时,图像会被居中并裁剪)。这可能是一项非常耗时的任务(尤其是使用 Lanczos 等高质量过滤器时)。
关注点
我对 Libor 的原始算法进行了修改,以
- 减少内存分配/释放调用(内存分配在一个大块中)。
- 避免不必要的内存传输。
生成的函数比 Libor 的原始函数稍快(权衡:代码不太整洁……)。(顺便说一句,这当然有 100% 纯非托管代码的影响……)。根据设计,重采样会作用于所有的 RGBQUAD
分量。还值得注意的是,重采样的中间结果由 unsigned char
保存,而不是 unsigned short
(Libor 使用的),这可能会降低质量,但据我所知,没有明显的影响。
历史
- 2007 年 12 月 19 日:首次发布