Introduction
When I first saw Libor Tinka's great article on resampling Image Resizing - Outperform GDI+, I said to myself: "very good stuff indeed, I really need a similar tool for GDI based bitmaps."
The Library
Resample.dll is a small dynamic library that exports two resampling functions. The design aims to resemble the Win32 GDI API interface and behaviour.
The function scenario is simple: Suppose we have a GDI HBITMAP
(for instance loaded from a file via LoadImage
), say hBmp
, whose size is w
x h
and we need to resize it to wNew
x hNew
without a big loss in image quality. We can accomplish the above task with just a call to the library provided function CreateResampledBitmap
in the following way:
hBmpNew = CreateResampledBitmap(hdc, hBmp, wNew, hNew, STOCK_FILTER_LANCZOS8);
Note that (as with, for instance, GDI CreateCompatibleBitmap
), the obtained handle must be deleted (via GDI DeleteObject
) when it is no longer needed. The other exported function is CreateUserFilterResampledBitmap
that allows the caller to provide a custom filter function pointer.
Acknowledgements
This article is based on Libor Tinka's one: Image Resizing - Outperform GDI+. I just ported his C# code to a pure C one (changing the algorithm a bit) and packaged it all inside a DLL. All stock filters are the ones used in the original article by Libor, that ultimately can be used as reference.
Background
A general understanding of Win32 GDI API is required to use the code. A slightly deeper understanding of GDI bitmaps and C language is needed in order to hack the library internals. To use custom filters, a familiarity with callback functions may help.
Image downsampling example.
The same image upsampled.
Library Reference
Since the library contains only two functions, I can give reference information in the classical GDI documentation style.
Resampling With Stock Filters
CreateResampledBitmap
The CreateResampledBitmap
function creates a resampled bitmap compatible with the device that is associated with the specified device context. The resampling filter is chosen between available stock filters.
HBITMAP CreateResampledBitmap(
HDC hdc,
HBITMAP hBmpSource,
DWORD dwWidth,
DWORD dwHeight,
DWORD dwFilter
);
Parameters
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
Return Values
If the function succeeds, the return value is the handle of the resampled bitmap. If the function fails, the return value is NULL
. To get extended error information, call GetLastError
.
Remarks
dwWidth
and dwHeight
are clipped to the range 1-4096
dwFilter
is wrapped around the available stock filter range (i.e. dwFilter=STOCK_FILTER_TRIANGLE+1
becomes dwFilter=STOCK_FILTER_BELL
).
Resampling With Custom Filters
CreateUserFilterResampledBitmap
The CreateUserFilterResampledBitmap
function creates a resampled bitmap compatible with the device that is associated with the specified device context. The resampling filter is provided by the caller.
HBITMAP CreateUserFilterResampledBitmap(
HDC hdc,
HBITMAP hBmpSource,
DWORD dwWidth,
DWORD dwHeight,
double (*pCustomFilter)(double),
double dRadius
);
Parameters
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.
Return Values
If the function succeeds, the return value is the handle of the resampled bitmap. If the function fails, the return value is NULL
. To get extended error information, call GetLastError
.
Remarks
dwWidth
and dwHeight
are clipped to the range 1-4096
Radius should be the proper filter radius for the pCustomFilter
function
- Legal range for
Radius
is 0.0-16.0.
Using the Code
Project Setup
In order to use Resample.dll functions, the application must:
- Include Resample.h header file
- Link with Resample.lib file
This of course implies that Visual Studio Environment must be able to find both header and library file paths.
Using the library is quite straightforward, the following code snippet loads a bitmap from test.bmp file and resamples it using the BOX
stock filter:
...
hBmp = (HBITMAP) LoadImage( hInstance, _T("test.bmp"),
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (! hBmp ) return FALSE;
hBmpResampled = CreateResampledBitmap(hdc, hBmp, 1024, 768, STOCK_FILTER_BOX);
...
The following one shows instead the use of a custom filter:
...
const double dRad = 3.0;
...
double myFilter( double x)
{
if ( x < 0.0 ) x = -x;
if (x < dRad) return (dRad * dRad - 2 * dRad * x + x * x);
return 0.0;
}
...
hBmp = (HBITMAP) LoadImage( hInstance, _T("test.bmp"),
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (! hBmp ) return FALSE;
hBmpResampled = CreateUserFilterResampledBitmap(hdc, hBmp, 1024, 768, myFilter, dRad);
...
The created HBITMAP
can be used (as any other valid bitmap handle) later on, inside the application WM_PAINT
message handler as follows:
...
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);
}
...
The Test Application
I have included a test application project, namely ResampleTestApp
. It allows the user to load a bitmap, then the loaded image is shown (using its original dimensions) inside the main window while its resampled twin is painted in a child window. The user can select filter type and scale (zoom) of the resampled bitmap. The application (standard C++ Windows app, no MFC), though very basic and rough, allows to try all filters on different images. The child window title bar shows some information about the occurred resampling:
Namely:
- Name of the filter used
- Effective size of the resampled image
- Resampling effective scale
- Elapsed time
The resampling effective scale may differ significantly with the requested one due to the constraints on the dimension range of the resampled bitmap (1-4096
).
Be aware that the application naively creates a resampled bitmap with chosen scale, e.g. if you ask it a 4x
of the original 1024
x 768
bitmap, it calls the resampling function even if the window itself is far smaller than 4096
x 3072
(on painting the image is centered and clipped) this can be a very time consuming task (especially with high quality filters like the Lanczos ones).
Points of Interest
I have modified the original Libor algorithm to:
- Reduce memory allocation/deallocation calls (memory is allocated in a big chunk)
- Avoid unnecessary memory transfer
The resulting function is a bit faster (trade-off: code is less clean...) than the original Libor one (BTW there is, of course, the 100% pure unmanaged code impact...). By design, the resampling happens on all of the RGBQUAD
components. It is also worth noting that resampling intermediate results are held by unsigned char
instead of unsigned short
(used by Libor) this may degrade the quality but, as far as I can perceive, there is no significant effect.
History
- 19th December, 2007: First release