During the last weekend I wrote a Palette laboratory program (works both with PB/CC and PB/DLL). It helps to test all the possible strategies in implementing a palette driven program, when the screen is 256 colors. In the past I had some discussions on this topic (mostly with Semen) - The result was there are two main methods to work with palattes:
The code I wrote uses some Windows messages (the quotation marks identify Win32Api.hlp extracts):
My code also uses some API calls:
My laboratory program must be used on a 256 colors screen. It shows a grid with 128 rectangles, painted each with a different color. The colors are 128 values of red, green, blue or gray, depending on a listbox selection. If you run two (or more) instances of the program, each showing different colors, of course only the instance which has the focus will show all the correct colors. The other instances will show some incorrect colors - they will be highlighted via a cyan cross.
There are three checkbox groups: the first enables the specific Windows messages process; the second enables the API functions to be called inside each message processing; the third enables the APIs to be called inside the WM_PAINT message.
Two buttons reset all the checkboxes to the needed values for the two methods I mentioned: Full Messages Defaults (my method) and Simple Paint Defaults (Semen's method).
To fully test the methods, you must try (at least) the following:
You will see both the methods are ok. The following is an abstract of my method:
while the following is an abstract of the Semen's method:
If you look at it, the two methods are very similar. All what is needed is to select a palette into a device context, and to realize it. The difference between the two methods is on when to realize the palette. My method realizes it only when needed (I mean, when Windows sends its messages), while the Semen's method realizes it on every WM_PAINT message. Well, I think both methods work well; but I think my method is a little bit faster.
To conclude, I'll recall my unsolved quetions - they are unsolved even after a deep testing (I hope someone will post a reply):
What follows is the complete laboratory program code. I'd like to get any comment, and to know if are there other methods I couldn't find. As Lance wrote some time ago, there are not too much palette gurus around...
------------------
[This message has been edited by Aldo Cavini (edited September 16, 2001).]
- To realize the palette inside the Windows specific messages (my own method)
- To realize the palette inside the WM_PAINT message (Semen's method - hope I haven't misunderstood his thought)
The code I wrote uses some Windows messages (the quotation marks identify Win32Api.hlp extracts):
- WM_QUERYNEWPALETTE - "This message informs a window that it is about to receive the keyboard focus, giving the window the opportunity to realize its logical palette when it receives the focus"
- WM_PALETTEISCHANGING - I couldn't understand this message usage, so I didn't include it into the laboratory
- WM_PALETTECHANGED - "This message is sent to all top-level and overlapped windows after the window with the keyboard focus has realized its logical palette, thereby changing the system palette"
- WM_DISPLAYCHANGE - "This message is sent to all windows when the display resolution has changed" (also it is sent when the bit-per-pixel has changed)
- USER_palettechange - This message is posted by the program itself when it changes the palette colors
- WM_PAINT - Well, we all know how this message works
My code also uses some API calls:
- CreatePalette - "This function creates a logical color palette".
- SelectPalette - "This function selects the specified logical palette into a device context". It requires the bForceBackground parameter, which can be %true or %false. I didn't understand which case requires the %true value.
- UnrealizeObject - "This function resets a logical palette". This function seems to be ininfluent to the program, but sometimes its use make the program behaves bad (see later).
- RealizePalette - "This function maps palette entries from the current logical palette to the system palette"
- RedrawWindow - "This function updates the specified rectangle or region in a window's client area"
My laboratory program must be used on a 256 colors screen. It shows a grid with 128 rectangles, painted each with a different color. The colors are 128 values of red, green, blue or gray, depending on a listbox selection. If you run two (or more) instances of the program, each showing different colors, of course only the instance which has the focus will show all the correct colors. The other instances will show some incorrect colors - they will be highlighted via a cyan cross.
There are three checkbox groups: the first enables the specific Windows messages process; the second enables the API functions to be called inside each message processing; the third enables the APIs to be called inside the WM_PAINT message.
Two buttons reset all the checkboxes to the needed values for the two methods I mentioned: Full Messages Defaults (my method) and Simple Paint Defaults (Semen's method).
To fully test the methods, you must try (at least) the following:
- Run more than one instance of the program, showing different colors, and show another palette driver window (i.e. MsPaint).
- Change the screen resolution (8 bit to 24, 24 bit to 8). This test demonstrates why it is needed to process the WM_DISPLAYCHANGE message on my method.
- Maximize a window (i.e. explorer), and put all the laboratory program windows on the top; then minimize the explorer window: the laboratory windows will be hidden and painted again on the same time. You will see the program sometimes behaves wrong if the UnrealizeObject call is enabled (bad colors on the non-foreground windows).
You will see both the methods are ok. The following is an abstract of my method:
Code:
CASE %WM_QUERYNEWPALETTE, %WM_PALETTECHANGED, %WM_DISPLAYCHANGE, %USER_palettechange hDc = GetWindowDc( hWnd ) IF ( GetDeviceCaps( hDc, %RASTERCAPS ) AND %RC_PALETTE ) <> 0 THEN IF wMsg = %WM_PALETTECHANGED AND hWnd = wParam THEN EXIT SELECT ' avoid endless loop END IF SelectPalette hDc, hPalette, %false RealizePalette hDc IF wMsg = %WM_QUERYNEWPALETTE THEN FUNCTION = %true ' the window realized its palette END IF RedrawWindow hWnd, BYVAL %null, BYVAL %null, %RDW_INVALIDATE OR %RDW_ALLCHILDREN END IF ReleaseDc hWnd, hDc CASE %WM_PAINT hDc = BeginPaint( hWnd, ps ) IF ( GetDeviceCaps( hDc, %RASTERCAPS ) AND %RC_PALETTE ) <> 0 THEN SelectPalette hDc, hPalette, %false ... END IF
Code:
CASE %WM_QUERYNEWPALETTE, %WM_PALETTECHANGED, %USER_palettechange RedrawWindow hWnd, BYVAL %null, BYVAL %null, %RDW_INVALIDATE OR %RDW_ALLCHILDREN CASE %WM_PAINT hDc = BeginPaint( hWnd, ps ) IF ( GetDeviceCaps( hDc, %RASTERCAPS ) AND %RC_PALETTE ) <> 0 THEN SelectPalette hDc, hPalette, %false RealizePalette hDc ... END IF
To conclude, I'll recall my unsolved quetions - they are unsolved even after a deep testing (I hope someone will post a reply):
- The use of the WM_PALATTEISCHANGING message
- The use of the UnrealizeObject function
- The use of the bForceBackground parameter inside the SelectPalette function.
What follows is the complete laboratory program code. I'd like to get any comment, and to know if are there other methods I couldn't find. As Lance wrote some time ago, there are not too much palette gurus around...

Code:
#COMPILE EXE #REGISTER NONE #INCLUDE "win32api.inc" %USER_palettechange = %WM_USER + 1 GLOBAL flags AS STRING $defaults1 = "1111......10011.....1000......" $defaults2 = "1101......00001.....1001......" ' ----------------------------------------------- create palette 128 colors ------------- FUNCTION Palette( BYVAL n AS LONG ) AS LONG ' 0 to 127: returns the color ' -1 : returns the palette handler ' -2 to -5: create a red/green/blue/gray palette STATIC s AS STRING ' returns the palette handler STATIC h AS LONG LOCAL i AS LONG IF n = -1 THEN FUNCTION = h ELSEIF n < -1 THEN IF h <> 0 THEN DeleteObject h END IF s = MKI$( 768 ) + MKI$( 128 ) FOR i = 0 TO 127 SELECT CASE n CASE -5 s = s + CHR$( i * 2 + 1, 0, 0, 0 ) CASE -4 s = s + CHR$( 0, i * 2 + 1, 0, 0 ) CASE -3 s = s + CHR$( 0, 0, i * 2 + 1, 0 ) CASE -2 s = s + CHR$( i * 2 + 1, i * 2 + 1, i * 2 + 1, 0 ) END SELECT NEXT i h = CreatePalette( BYVAL STRPTR( s ) ) FUNCTION = h ELSEIF h <> 0 THEN FUNCTION = CVL( MID$( s, n * 4 + 5, 4 ) ) END IF END FUNCTION ' ----------------------------------------------- colors window callback function ------- FUNCTION CallbackColors( BYVAL hWnd AS LONG, _ BYVAL wMsg AS LONG, _ BYVAL wParam AS LONG, _ BYVAL lParam AS LONG) AS LONG LOCAL hDc AS LONG LOCAL ps AS PAINTSTRUCT LOCAL x AS LONG LOCAL y AS LONG LOCAL z AS LONG LOCAL hPen AS LONG LOCAL hBr AS LONG SELECT CASE wMsg CASE %WM_ERASEBKGND FUNCTION = %true EXIT FUNCTION CASE %WM_PAINT hDc = BeginPaint( hWnd, ps ) IF ( GetDeviceCaps( hDc, %RASTERCAPS ) AND %RC_PALETTE ) <> 0 THEN IF MID$( flags, 21, 1 ) = "1" THEN SelectPalette hDc, Palette( -1 ), %false ELSEIF MID$( flags, 22, 1 ) = "1" THEN SelectPalette hDc, Palette( -1 ), %true END IF IF MID$( flags, 23, 1 ) = "1" THEN UnrealizeObject Palette( -1 ) END IF IF MID$( flags, 24, 1 ) = "1" THEN RealizePalette hDc END IF END IF hPen = SelectObject( hDc, GetStockObject( %BLACK_PEN ) ) FOR y = 0 TO 15 FOR x = 0 TO 7 hBr = SelectObject( hDc, CreateSolidBrush( Palette( y * 8 + x ) OR &H02000000 ) ) Rectangle hDc, x * 16, y * 16, x * 16 + 16, y * 16 + 16 DeleteObject SelectObject( hDc, hBr ) NEXT x NEXT y SelectObject hDc, CreatePen( %PS_SOLID, 0, RGB( 0, 255, 255 ) ) FOR y = 0 TO 15 FOR x = 0 TO 7 IF Palette( y * 8 + x ) <> GetNearestColor( hDc, Palette( y * 8 + x ) OR &H02000000 ) THEN MoveToEx hDc, x * 16 + 1, y * 16 + 1, BYVAL %null LineTo hDc, x * 16 + 14, y * 16 + 14 MoveToEx hDc, x * 16 + 14, y * 16 + 1, BYVAL %null LineTo hDc, x * 16 + 1, y * 16 + 14 END IF NEXT x NEXT y DeleteObject SelectObject( hDc, hPen ) EndPaint hWnd, ps END SELECT FUNCTION = DefWindowProc( hWnd, wMsg, wParam, lParam ) END FUNCTION ' ----------------------------------------------- callback function --------------------- FUNCTION CallBackFunction( BYVAL hWnd AS LONG, _ BYVAL wMsg AS LONG, _ BYVAL wParam AS LONG, _ BYVAL lParam AS LONG) AS LONG LOCAL h AS LONG LOCAL hDc AS LONG LOCAL cs AS CREATESTRUCT PTR LOCAL s AS STRING LOCAL i AS LONG SELECT CASE wMsg CASE %WM_CREATE cs = lParam Palette -5 flags = $defaults1 ' colors window h = CreateWindowEx( 0, "colors", "", %WS_CHILD OR %WS_VISIBLE, _ 20, 20, 128, 256, hWnd, 0, @cs.hInstance, BYVAL %null ) ' palette selection h = CreateWindowEx( 0, "listbox", "RED", %WS_CHILD OR %WS_VISIBLE OR %WS_BORDER OR %LBS_NOTIFY, _ 360, 10, 150, 70, hWnd, 101, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 FOR i = 1 TO 4 s = PARSE$( "RED,GREEN,BLUE,GRAY", i ) SendMessage h, %LB_ADDSTRING, 0, STRPTR( s ) NEXT i SendMessage h, %LB_SETCURSEL, 0, 0 ' refresh and default buttons h = CreateWindowEx( 0, "Button", "Refresh", _ %WS_CHILD OR %WS_VISIBLE OR %BS_PUSHBUTTON, _ 360, 80, 150, 25, hWnd, 102, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 h = CreateWindowEx( 0, "Button", "Full Messages Defaults", _ %WS_CHILD OR %WS_VISIBLE OR %BS_PUSHBUTTON, _ 170, 260, 150, 25, hWnd, 103, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 h = CreateWindowEx( 0, "Button", "Simple Paint Defaults", _ %WS_CHILD OR %WS_VISIBLE OR %BS_PUSHBUTTON, _ 360, 260, 150, 25, hWnd, 104, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 ' palette messages processing h = CreateWindowEx( 0, "static", "Enabled Windows Messages :", _ %WS_CHILD OR %WS_VISIBLE, _ 170, 10, 160, 25, hWnd, %null, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 s = "WM_QUERYNEWPALETTE,WM_PALETTECHANGED,WM_DISPLAYCHANGE,USER_palettechange" FOR i = 1 TO 4 h = CreateWindowEx( 0, "button", PARSE$( s, i ), %WS_CHILD OR %WS_VISIBLE OR %BS_CHECKBOX, _ 170, 10 + i * 20, 160, 25, hWnd, 200 + i, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 IF MID$( flags, i, 1 ) = "1" THEN SendMessage h, %BM_SETCHECK, %BST_CHECKED, 0 END IF NEXT i h = CreateWindowEx( 0, "static", "", %WS_CHILD OR %WS_VISIBLE OR %SS_GRAYFRAME, _ 170, 120, 340, 1, hWnd, %null, @cs.hInstance, BYVAL %null ) ' actions on messages s = "SelectPalette( false ),SelectPalette( true ),UnrealizeObject,RealizePalette,RedrawWindow" h = CreateWindowEx( 0, "static", "Actions on Windows Messages :", _ %WS_CHILD OR %WS_VISIBLE, _ 170, 130, 160, 25, hWnd, %null, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 FOR i = 1 TO 5 h = CreateWindowEx( 0, "button", PARSE$( s, i ), %WS_CHILD OR %WS_VISIBLE OR %BS_CHECKBOX, _ 170, 130 + i * 20, 160, 25, hWnd, 210 + i, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 IF MID$( flags, i + 10, 1 ) = "1" THEN SendMessage h, %BM_SETCHECK, %BST_CHECKED, 0 END IF NEXT i ' actions on PAINT h = CreateWindowEx( 0, "static", "Actions on PAINT :", _ %WS_CHILD OR %WS_VISIBLE, _ 360, 130, 200, 25, hWnd, %null, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 FOR i = 1 TO 4 h = CreateWindowEx( 0, "button", PARSE$( s, i ), %WS_CHILD OR %WS_VISIBLE OR %BS_CHECKBOX, _ 360, 130 + i * 20, 200, 25, hWnd, 220 + i, @cs.hInstance, BYVAL %null ) SendMessage h, %WM_SETFONT, GetStockObject( %ANSI_VAR_FONT ), 0 IF MID$( flags, i + 20, 1 ) = "1" THEN SendMessage h, %BM_SETCHECK, %BST_CHECKED, 0 END IF NEXT i CASE %WM_QUERYNEWPALETTE, %WM_PALETTECHANGED, %WM_DISPLAYCHANGE, %USER_palettechange IF wMsg = %WM_PALETTECHANGED AND hWnd = wParam THEN EXIT SELECT ' avoid endless loop END IF IF wMsg = %WM_QUERYNEWPALETTE AND MID$( flags, 1, 1 ) = "1" OR _ wMsg = %WM_PALETTECHANGED AND MID$( flags, 2, 1 ) = "1" OR _ wMsg = %WM_DISPLAYCHANGE AND MID$( flags, 3, 1 ) = "1" OR _ wMsg = %USER_palettechange AND MID$( flags, 4, 1 ) = "1" THEN hDc = GetWindowDc( hWnd ) IF ( GetDeviceCaps( hDc, %RASTERCAPS ) AND %RC_PALETTE ) <> 0 THEN IF MID$( flags, 11, 1 ) = "1" THEN SelectPalette hDc, Palette( -1 ), %false ELSEIF MID$( flags, 12, 1 ) = "1" THEN SelectPalette hDc, Palette( -1 ), %true END IF IF MID$( flags, 13, 1 ) = "1" THEN UnrealizeObject Palette( -1 ) END IF IF MID$( flags, 14, 1 ) = "1" THEN RealizePalette hDc IF wMsg = %WM_QUERYNEWPALETTE THEN FUNCTION = %true END IF END IF END IF ReleaseDc hWnd, hDc IF MID$( flags, 15, 1 ) = "1" THEN RedrawWindow hWnd, BYVAL %null, BYVAL %null, %RDW_INVALIDATE OR %RDW_ALLCHILDREN END IF END IF CASE %WM_COMMAND SELECT CASE HIWRD( wParam ) CASE %LBN_SELCHANGE IF LOWRD( wParam ) = 101 THEN Palette SendMessage( lParam, %LB_GETCURSEL, 0, 0 ) - 5 SendMessage hWnd, %USER_palettechange, 0, 0 END IF CASE %BN_CLICKED i = LOWRD( wParam ) IF i = 102 THEN InvalidateRect hWnd, BYVAL %null, %null ELSEIF i = 103 OR i = 104 THEN IF i = 103 THEN flags = $defaults1 ELSE flags = $defaults2 END IF FOR i = 1 TO 30 IF MID$( flags, i, 1 ) = "1" THEN SendMessage GetDlgItem( hWnd, i + 200 ), %BM_SETCHECK, %BST_CHECKED, 0 ELSEIF MID$( flags, i, 1 ) = "0" THEN SendMessage GetDlgItem( hWnd, i + 200 ), %BM_SETCHECK, %BST_UNCHECKED, 0 END IF NEXT i ELSEIF i >= 201 AND i <= 230 THEN IF MID$( flags, i - 200, 1 ) = "1" THEN MID$( flags, i - 200, 1 ) = "0" SendMessage lParam, %BM_SETCHECK, %BST_UNCHECKED, 0 ELSE MID$( flags, i - 200, 1 ) = "1" SendMessage lParam, %BM_SETCHECK, %BST_CHECKED, 0 IF i = 211 AND MID$( flags, 12, 1 ) = "1" THEN MID$( flags, 12, 1 ) = "0" SendMessage GetDlgItem( hWnd, 212 ), %BM_SETCHECK, %BST_UNCHECKED, 0 ELSEIF i = 212 AND MID$( flags, 11, 1 ) = "1" THEN MID$( flags, 11, 1 ) = "0" SendMessage GetDlgItem( hWnd, 211 ), %BM_SETCHECK, %BST_UNCHECKED, 0 ELSEIF i = 221 AND MID$( flags, 22, 1 ) = "1" THEN MID$( flags, 22, 1 ) = "0" SendMessage GetDlgItem( hWnd, 222 ), %BM_SETCHECK, %BST_UNCHECKED, 0 ELSEIF i = 222 AND MID$( flags, 21, 1 ) = "1" THEN MID$( flags, 21, 1 ) = "0" SendMessage GetDlgItem( hWnd, 221 ), %BM_SETCHECK, %BST_UNCHECKED, 0 END IF END IF END IF END SELECT CASE %WM_DESTROY DeleteObject Palette( -1 ) PostQuitMessage 0 END SELECT FUNCTION = DefWindowProc( hWnd, wMsg, wParam, lParam ) END FUNCTION ' ----------------------------------------------- main function ------------------------- FUNCTION WINMAIN( BYVAL hCurInstance AS LONG, _ BYVAL hPrevInstance AS LONG, _ lpszCmdLine AS ASCIIZ PTR, _ BYVAL nCmdShow AS LONG ) AS LONG LOCAL Msg AS tagMsg LOCAL hWnd AS LONG LOCAL ascii AS ASCIIZ * 64 LOCAL WndCl AS WndClassEx LOCAL MainWindow AS LONG ascii = "Palette Test" ' creates main window class WndCl.cbSize = SIZEOF( WndCl ) WndCl.style = 0 WndCl.lpfnWndProc = CODEPTR( CallbackFunction ) WndCl.cbClsExtra = 0 WndCl.cbWndExtra = 4 WndCl.hInstance = hCurInstance WndCl.hIcon = LoadIcon( hCurInstance, BYVAL %IDI_APPLICATION ) WndCl.hCursor = LoadCursor( %null, BYVAL %IDC_ARROW ) WndCl.hbrBackground = GetStockObject( %LTGRAY_BRUSH ) WndCl.lpszMenuName = %null WndCl.lpszClassName = VARPTR( ascii ) WndCl.hIconSm = %null RegisterClassEx WndCl ascii = "colors" ' creates colors window class WndCl.lpfnWndProc = CODEPTR( CallbackColors ) WndCl.hbrBackground = GetStockObject( %NULL_BRUSH ) WndCl.lpszClassName = VARPTR( ascii ) RegisterClassEx WndCl ' creates the main window MainWindow = CreateWindowEx( _ 0, _ "Palette Test", _ "Palette Test", _ %WS_OVERLAPPEDWINDOW OR %WS_CLIPCHILDREN, _ %CW_USEDEFAULT, %CW_USEDEFAULT, _ 540, 330, _ %HWND_DESKTOP, _ BYVAL %null, _ BYVAL hCurInstance, _ BYVAL %null ) ShowWindow MainWindow, %SW_SHOW WHILE GetMessage( Msg, %null, 0, 0 ) ' messages loop TranslateMessage Msg DispatchMessage Msg WEND FUNCTION = 0 END FUNCTION
[This message has been edited by Aldo Cavini (edited September 16, 2001).]
Comment