Author: Pieter Philippaerts
2.
Simple Graphical function
In
part 2, we’re going to discuss an easy graphical function that converts a picture to a DuoTone version of
that picture. The function itself isn’t too hard to understand, but the algorithm explains some very
useful methods to pass handles of objects to your VC++ procedure.
Before we’re going to write the algorithm in C, I’ll first explain how it works.
Take a look at the pseudo-code version of the function:
DuoTone
function {
Create a temporary memory bitmap;
Paint it with the color specified by
the user (=parameter);
‘And’ the source picture over the
temporary memory bitmap;
Copy the result to the destination
Device Context;
}
To
explain the algorithm, let us assume that we specified the color Red (RGB(255,0,0)) as DuoColor. This way,
the memory bitmap will be completely filled with red pixels.
If we then ‘And’ the source picture with the red memory bitmap, every pixel will be converted to its
red component.
For instance, if we ‘And’ a pixel with the color &HD34F19 (RGB(25, 79, 211)) with the color red
(&H0000FF), the result will be &H000019, or RGB(25, 0, 0).
We
first declare the function:
int _stdcall DuoTone(HBITMAP hSrcBitmap, COLORREF DuoColor, HDC hDestDC, int
destx, int desty) {
|
The function follows the standard calling convention, since this is required for
Visual Basic.
It returns an ‘int’ variable that corresponds to a ‘Long’ variable in Visual Basic.
The function has five parameters: two ints that are converted to Longs in Visual Basic, two handles and a
COLORREF. All handles are converted to Longs in Visual Basic and because a COLORREF is a 32-bit value, it
is also converted to a Long.
The VB declaration looks like this:
Declare Function DuoTone Lib “MyDll.dll” (
_
Byval hSrcBitmap as Long,
Byval DuoColor as
Long, _
Byval hDestDC as Long, Byval destx as Long, _
Byval desty as Long)
as Long |
Do
note that all the parameters are passed by value. We only have to pass a parameter by reference if the VC++
declaration of that variable is defined as a pointer (i.e. int *myValue).
int retval = 1;
BITMAP bminfo;
GetObject(hSrcBitmap, sizeof(bminfo),
&bminfo);
|
In
this code, we declared a variable that contains the return value of the function. If something fails within
the function, this variable will be set to 0.
We also declare a BITMAP structure and fill it with the information of the source bitmap. The ampersand
(‘&’) before ‘bminfo’ means that we pass a pointer to bminfo instead of copying the
entire structure on the stack.
HDC hSrcDC = CreateCompatibleDC(hDestDC);
HGDIOBJ oldsrc = SelectObject
(hSrcDC, hSrcBitmap);
|
We
now create a new device context, compatible with the destination device context, and we associate it with
the source bitmap so we can access the bitmap trough our device context.
if (oldsrc != 0) {
BITMAPINFOHEADER bi = {
sizeof(BITMAPINFOHEADER),
// biSize
bminfo.bmWidth,
// biWidth;
bminfo.bmHeight,
// biHeight;
1,
// biPlanes;
24,
// biBitCount
BI_RGB,
// biCompression;
0,
// biSizeImage;
0,
// biXPelsPerMeter;
0,
// biYPelsPerMeter;
0,
// biClrUsed;
0
// biClrImportant;
};
HBITMAP hTempBitmap =
CreateDIBSection(hDestDC,
(BITMAPINFO *)&bi, DIB_RGB_COLORS,
NULL, NULL, 0);
HDC hTempDC = CreateCompatibleDC(hDestDC);
HGDIOBJ oldtemp =
SelectObject(hTempDC,
hTempBitmap);
|
After
checking whether the device context was successfully associated with the source bitmap, we create a
temporary 24-bit memory bitmap, with the same size as the source bitmap. We then create a device context
and associate that new device context with our memory bitmap.
if (oldtemp
!= 0) {
RECT r = {0, 0, bminfo.bmWidth,
bminfo.bmHeight};
HBRUSH dBrush = CreateSolidBrush(DuoColor); |
If the association was completed successfully, we define a rectangle structure and
initialize it with the width and height of the source bitmap. We also create a new solid brush with the
color specified by the caller.
if
(dBrush != 0 &&
FillRect(hTempDC, &r,
dBrush) != 0) {
if (!BitBlt(hTempDC, 0, 0,
bminfo.bmWidth,
bminfo.bmHeight, hSrcDC, 0, 0, SRCAND)
|| !BitBlt(hDestDC,
destx, desty,
bminfo.bmWidth, bminfo.bmHeight,
hTempDC, 0,
0, SRCCOPY))
retval = 0;
|
We check for successful creation of the brush and whether the temporary memory bitmap
was filled with the DuoColor, and if both calls are completed correctly, we start blitting the source
bitmap to our DuoColor memory bitmap, and then copy the result to the destination device context. If an
error occurs, we set retval to 0.
} else
{
retval
= 0;
}
DeleteObject(dBrush);
} else
{
retval = 0;
}
DeleteObject(SelectObject(hTempDC,
oldtemp));
DeleteDC(hTempDC);
} else {
retval = 0;
}
SelectObject(hSrcDC, oldsrc);
DeleteDC(hSrcDC);
return retval;
} |
The last few lines are clean up code. If an error occurs they make sure retval is set
to 0, and they make sure that all the memory is released before returning to the caller.
If
you now paste all the parts of the C source code into your VC++ DLL, you’ll have a working DuoTone
function. However, you don’t need a DLL to implement such a function in Visual Basic. You could easily
write the same function in Visual Basic, and you would hardly notice a difference in speed, since the Win32
API functions are doing the work in this function.
That’s why we’ll see in the next part how to program processor intensive functions, which use pointers
as parameters.
You
can download the VC++ source code and the VB example project over
here.
Part
1 - Part 2 - Part 3
- Part 4 isn't available
yet
|