![]() |
|
Published 1998-08-01 Printer-friendly version
Do you or your end users ever get tired of using the ColorDialog? Give them a color well instead, and dive into the Windows API for your own benefit, as well. Did someone say well?
This project delves into some of the uses of the Windows API to provide an easy-to-use color control. Some call this a well. Here it is called a pool!
The project is a simple color pool that could be used on a toolbar or as a control wherever preferred. The example provides all that's needed to build a color pool or to provide a means to select a color from an image using the commonly accepted "eye-dropper" cursor. All bitmaps and cursors used in the example are provided within the source download.

The screen captured above shows the color pool, selection cursor, sample box and readouts of the component colors (red, green and blue) displayed on the toolbar. This pool is created from three controls (bitmap, region and panel) although only bitmap and region are required. The panel is used solely to provide a 3D effect.
The pool is also sensitive to the color depth of the video in use so it can be used on 16, 256 and higher color depths using the same code but three different pool images.
Listed here are the Windows APIs used in this project. (Prototypes provided are for 16-bit only.)
MODULE('')
GetDC(UNSIGNED),UNSIGNED,PASCAL,NAME('GetDC')
ReleaseDC(UNSIGNED,UNSIGNED),SIGNED,PASCAL,NAME('ReleaseDC'),PROC
SelectObject(UNSIGNED,UNSIGNED),UNSIGNED,PASCAL,NAME('SelectObject'),PROC
GetStockObject(SIGNED),UNSIGNED,PASCAL,NAME('GetStockObject')
DeleteObject(UNSIGNED),BOOL,PASCAL,NAME('DeleteObject'),PROC
CreateSolidBrush(ULONG),UNSIGNED,PASCAL,NAME('CreateSolidBrush')
Rectangle(UNSIGNED,SIGNED,SIGNED,SIGNED,SIGNED),BOOL,PASCAL,NAME('Rectangle'),PROC
GetCursorPos(LONG),PASCAL,NAME('GetCursorPos')
GetPixel(UNSIGNED,UNSIGNED,UNSIGNED),ULONG,PASCAL,NAME('GetPixel')
GetWindowRect(UNSIGNED,LONG),PASCAL,PROC,NAME('GetWindowRect')
GetDeviceCaps(UNSIGNED,SIGNED),SIGNED,PASCAL,NAME('GetDeviceCaps')
END
The MODULE('') section encompasses the Windows API prototypes that provide the color pool its functionality. Notice that some of these prototypes use the PROC argument so they can be called as procedures as well as functions.
GetDC: Returns the handle of a device context(DC)
ReleaseDC: Releases the device context returned by GetDC
A device context is a Windows data structure that contains information about the attributes of a device such as a display or a printer.
To retrieve information about a window or to draw to a window requires its Device Context. To get the DC of a window you pass GetDC the handle to that window. For use in this project a NULL is passed to GetDC to return a DC for the display screen. The handle returned by GetDC can be used with any API call that requires a DC such as those used in this project.
Once completed with a device context remember to release it using ReleaseDC. Failure to do so will result in memory loss and eventual application failure. Kind of like a pool with a leak... eventually it runs out of water!
The following API functions are used to draw to the window.
SelectObject: Selects an object, such as a bitmap or brush, into a device context(DC)
GetStockObject: Retrieves a handle to a predefined stock pen, brush or font
DeleteObject: Deletes a created object when no longer needed
CreateSolidBrush: Creates a brush object of the specified color which can then be selected
Rectangle: Draws a rectangle with the current pen filled with the current brush
To "do something" within a DC, the objects required must be selected into that DC before they can be used. Outlines use pens. Fills use brushes. Text uses fonts. You get the idea.
SelectObject provides a means to select the brushes, pens, etc. needed to perform other API functions within a device context. The objects selected determine the final effect produced by the functions that use them.
All device contexts have common stock objects such as black & white pens, several different brushes like black, white, gray, dark gray and several different fonts, among others. GetStockObject is used to select from these common objects.
If a non-stock object is needed, such as a specific fill color (a brush) or specific outline color (a pen), there are other API calls that can be used to create these objects. This project uses a specific brush to fill the sample BOX() by using CreateSolidBrush.
Whenever an object is created, remember that it must be deleted using DeleteObject to recover the memory used. Also remember never to delete an object until it has been selected back out of the DC in which it was used.
When SelectObject is used to select an object into a device context, it returns the handle of the current object. The original object's handle should be retained so that object can be selected back into the DC before it is released. If the DC is not reset to its original state; i.e., all original objects selected back into the DC, odd behavior (and memory loss from any created objects) will result.
Some simple steps to remember when using a DC:
GetDC - Rope off the area and determine surface type: stucco, plaster, wood, etc
-create objects- - Rent scaffolding
SelectObject - (IN) Gather brushes and paints and set up scaffolding
SelectObject - (OUT) Clean brushes, close paint cans and disassemble scaffolding
DeleteObject - Return scaffolding
ReleaseDC - Take the rope down, step back and admire your work
In addition to the DC and drawing functions the following are used to retrieve information about the window and cursor.
GetCursorPos: Returns the current cursor position in a POINT structure
GetPixel: Returns the composite color attributes for a given pixel by DC and x, y
GetWindowRect: Fills a RECT structure with screen coordinates for a window or control
GetDeviceCaps: Retrieves information about a specific device and its capabilities
GetCursorPos fills a POINT structure with the current position of the cursor in pixels from the top left corner of the display. Don't confuse this with the results of MouseX() and MouseY(). More often than not these will be VERY different measurements.
A POINT structure holds x and y coordinates...
POINT GROUP
x SIGNED ! X in pixels
y SIGNED ! Y in pixels
END
GetPixel is a simple API call that returns the composite color of the indicated pixel. This is the most important API call used within this project. The LONG that is returned by this call is a composite of the three primary colors; red, green and blue, of the selected pixel. Each component may have a value of 0 to 255 determining the saturation of that color. If all three components are 0 the color is black. If all three are 255 the color is white. This results in a maximum of 16,777,216 (256*256*256) unique colors.
GetWindowRect fills a RECT structure with the bounding coordinates of the window specified by the passed handle. The shape of the "window" referenced by the handle passed to GetWindowRect is of no importance. In other words, if the handle to a Clarion Ellipse() is passed the coordinates of a rectangle which fits the outside of the ellipse will be returned. (some restrictions apply, such as the width of the outline used in the control referenced)
A RECT structure is used with API calls to define the bounding (outside) coordinates of a rectangle...
RECT GROUP
LeftX SIGNED ! Upper Left X in pixels
TopY SIGNED ! Upper Left Y in pixels
RightX SIGNED ! Lower Right X in pixels
BottomY SIGNED ! Lower Right Y in pixels
END
GetDeviceCaps has far too many uses to list here. Suffice it to say that GetDeviceCaps can be of much use within any Windows application depending on the needs. In this project it is used to determine the color depth of the display, ie: the number of colors supported. This is further used in the selection of an appropriate bitmap for the color pool.
This example is a simple window with a toolbar and a handful of controls. The region behind PoolImage is used to determine mouse movement and, when accepted, to fill the larger BOX() control with the selected color.
The Windows API GetDeviceCaps is used in this procedure to determine the color depth of the display.
When mouse movement is detected within the PoolRegion the cursor position is read and passed to Get_Color. Get_Color reads the current pixel properties and returns the composite color value that is then compared to the previous color. If the colors are different then a call is made to Color_Rect. This procedure creates a solid brush in the selected color and then draws a rectangle filled with this color.
When this region is ACCEPTED, the last color selected by mouse movement is used to fill the larger BOX() control.
Couldn't get much simpler, could it? Now don't make up your mind until you read the source that follows. You should agree that there isn't that much involved.
ColorPool PROCEDURE
NULL EQUATE(0) !An equate for NULL parameters
PLANES EQUATE(14) !An equate for GetDeviceCaps API
BITSPIXEL EQUATE(12) !An equate for GetDeviceCaps API
hDC UNSIGNED !Handle to a Device Context
sbHnd UNSIGNED !Handle to the sample BOX()
MaxColors ULONG !Maximum color depth of display
NewColor ULONG !New color selected in pool
LastColor ULONG !Previous color selected
ColorRef GROUP,PRE(),OVER(LastColor) !Group to dissect color components
vRed BYTE ! RED color component
vGreen BYTE ! GREEN color component
vBlue BYTE ! BLUE color component
END
POINT GROUP !POINT structure group (current)
x SIGNED
y SIGNED
END
LastPOINT GROUP !POINT structure group (previous)
x SIGNED
y SIGNED
END
Window WINDOW('Color Pool'),AT(,,244,120)|
,FONT('MS Sans Serif',8,,FONT:regular)|
,CENTER,SYSTEM,GRAY,DOUBLE
TOOLBAR,AT(0,0,244,34)
BUTTON('Close'),AT(2,2,41,30),USE(?CloseButton)
STRING('Red'),AT(112,2),USE(?rString)
STRING('Green'),AT(112,12),USE(?gString)
STRING('Blue'),AT(112,22),USE(?bString)
STRING(@n3),AT(142,2),USE(vRed),TRN
STRING(@n3),AT(142,12),USE(vGreen),TRN
STRING(@n3),AT(142,22),USE(vBlue),TRN
REGION,AT(162,2,65,29),USE(?PoolRegion),IMM,CURSOR('POOL.CUR')
IMAGE('POOL16.BMP'),AT(161,1,67,31),USE(?PoolImage)
PANEL,AT(161,1,67,31),USE(?PoolBevel),BEVEL(-1)
BOX,AT(230,1,13,31),USE(?SampleBox),COLOR(00H),FILL(08000H)
END
BOX,AT(43,15,157,57),USE(?Shadow),FILL(00H)
BOX,AT(41,13,157,57),USE(?BigSample),FILL(0FFFFFFH)
STRING(@N_8),AT(104,74),USE(NewColor),CENTER
END
CODE
LastColor = COLOR:White !Initialize color for sample BOX()
NewColor = 0 !Initialize value for comparison
OPEN(Window)
!
! Calculate maximum colors supported and select the appropriate pool bitmap
!
hDC = GetDC(NULL) !Get device context for screen
MaxColors = 2^(GetDeviceCaps(hDC,PLANES) * GetDeviceCaps(hDC,BITSPIXEL))
IF MaxColors > 256 !More than 256 colors supported
?PoolImage{PROP:Text} = '~Pool16.bmp' ! then use the 16-bit bitmap
ELSIF MaxColors > 16 !256 colors supported
0{PROP:Palette} = 165 ! set palette to image colors
?PoolImage{PROP:Text} = '~Pool8.bmp' ! and use the 8-bit bitmap
ELSE !16 colors supported
?PoolImage{PROP:Text} = '~Pool4.bmp' ! then use the 4-bit bitmap
END
ReleaseDC(NULL,hDC) !Release the DC selected by GetDC
sbHnd = ?SampleBox{PROP:Handle} !Get a handle to the sample BOX()
ACCEPT
CASE EVENT()
OF EVENT:OpenWindow
Color_Rect(sbHnd,LastColor) !Initialize sample color rectangle
OF EVENT:Accepted
CASE FIELD()
OF ?CloseButton
POST(EVENT:CloseWindow) !Close window and exit ACCEPT LOOP
OF ?PoolRegion
?BigSample{PROP:Fill} = NewColor !Display newly selected color
END
OF EVENT:MouseMove !Mouse movement within pool REGION()
IF FIELD() = ?PoolRegion
GetCursorPos(ADDRESS(POINT)) !Get cursor position
IF LastPOINT <> POINT !If cursor position has changed
LastPOINT = POINT ! remember the new position
NewColor = Get_Color(POINT.x,POINT.y) ! and read the pixel color...
IF NewColor <> LastColor AND NewColor > -1 !If the color is different
LastColor = NewColor ! remember the new color
Color_Rect(sbHnd,LastColor) ! and update the sample BOX()
END
END
END
END
END
CLOSE(Window)
RETURN
Get_Color is a CW function used to retrieve the color components of an individual screen pixel. This is the heart of the color pool and provides easy color selection using bitmaps as opposed to seriously verbose math and logic... something most of us prefer to stay away from!
This function is passed the X and Y coordinates of the cursor. These values are used to perform the Windows GetPixel API and return the composite color in a LONG.
Using the customary "eye-dropper" cursor this function could be used to return the color for any use desired simply by reading the cursor position.
Get_Color FUNCTION(SIGNED AtX, SIGNED AtY) NULL EQUATE(0) !An equate for NULL parameters hDC UNSIGNED !Handle to a Device Context NewColor LONG !Function return variable CODE hDC = GetDC(NULL) !Get device context for screen NewColor = GetPixel(hDC,AtX,AtY) !Read the color of the pixel ReleaseDC(NULL,hDC) !Release the DC selected by GetDC RETURN(NewColor) !Return the composite color
Color_Rect is a CW procedure that uses the Windows API to fill a rectangular area with a selected color. The Windows RECTANGLE function is used to reduce the flashing which occurs when using PROP:Fill in a BOX() control. Using these API calls provides a flash free alternative for rectangles that are not overlapped.
This procedure is passed the handle of the sample BOX() and the composite color to be used as fill. By default this procedure uses a black pen for the box outline.
Color_Rect PROCEDURE(UNSIGNED SmplHndl, ULONG ThisColor) NULLEQUATE(0) !An equate for NULL parameters BLACK_PENEQUATE(7) !An equate for a black pen hDCUNSIGNED !Handle to a Device Context hPen UNSIGNED !Handle to a pen object hBr UNSIGNED !Handle to a brush object hPenOLD UNSIGNED !Handle to original DC pen hBrOLD UNSIGNED !Handle to original DC brush RECT GROUP !RECT structure group LeftX SIGNED TopY SIGNED RightX SIGNED BottomY SIGNED END CODE hDC = GetDC(NULL) !Get device context for screen hPen = GetStockObject(BLACK_PEN) !Select the stock black pen object hBr = CreateSolidBrush(ThisColor) !Create a solid color brush hPenOLD = SelectObject(hDC,hPen) !Select the black pen into the DC hBrOLD = SelectObject(hDC,hBr) !Select the solid brush into the DC GetWindowRect(SmplHndl,ADDRESS(RECT)) !Load the RECT of the sample BOX() Rectangle(hDC,RECT.LeftX,RECT.TopY,RECT.RightX,RECT.BottomY) !DRAW A NEW RECTANGLE SelectObject(hDC,hBrOLD) !Select the old brush into the DC SelectObject(hDC,hPenOLD) !Select the old pen into the DC DeleteObject(hBr) !Delete the brush object ReleaseDC(NULL,hDC) !Release the DC selected by GetDC RETURN
Since a black pen and a solid brush have been selected into the device context the call to RECTANGLE will draw a box to the dimensions provided in the RECT structure with a black outline of 1 pixel, filled with the color passed in "ThisColor".
hPenOLD and hBrOLD are used to retain the original pen and brush within the device context. Any time a new pen, brush, font, bitmap or other object is selected into a device context the original object should be selected back into the device context before releasing the DC. Failure to do so can have some rather odd results, to say the least.
Any objects created should be deleted once they have been selected out of the device context to release the memory they used.
Once all objects have been selected out, and all created objects have been deleted, the device context must be released.
That's it. Two rather simple procedures that provide some special functionality and can add a touch of class to your applications if you have the need for color selection.
With some effort you could easily produce a LIB, LIB/DLL or CLASS and template to include this tool as a group of controls in future applications. The beauty of CW, reusable code!
The example source includes the CLW and PRJ files as well as these images for your use.

Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239