Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

DirectDraw: Palettes
By: Jack Hoxley
Written: May 2000
Download: DD_Pal.zip (95kb)


Palettes are rarely used now, due to the rapid advances in hardware most computers support the higher colour depths; however, palettes can be very useful every-now-and-then. Palettes cant be used on surfaces with 16bit, 24bit and 32bit colour depths, they can be used as shown:

  1. 1-bit surface = 2 colours
  2. 2-bit surface = 4 colours
  3. 4-bit surface = 16 colours
  4. 8-bit surface = 256 colours

I have only ever had the need to use 8-bit display modes, anything less looks extremely poor in comparison to other programs. As most computers support 16-bit colour (or 24/32-bit) you may ask yourself: "Do I really need to use palettes?" - using higher bit depths will look much better and will have little speed sacrifice.

One thing that palettes allow you to do easily is modify the colours on screen. This can be very powerful when used properly. Changing palettes is very simple and requires very small amounts of processing power it can be done often; this leads to a technique called palette animation. You can create some impressive fading effects using this method, you can keep changing the values in the palette - fading them in or out - and the result will be mirrored onto the screen.

Palettes are basically big arrays of numbers. A palette has a fixed number of entries, for example, an 8-bit palette has 256 entries (for 256 colours). You set the value of each of these entries - eg, you can set entry number 125 to be RGB(0,0,0) - any RGB triplet.

The Code

Copy and paste the following code into a visual basic form for a working program. The comments will explain it all....

Option Explicit

Dim binit As Boolean


'These values should be easy now
'if not, go see the Fullscreen tutorial

Dim dx As New DirectX7
Dim dd As DirectDraw7
Dim Mainsurf As DirectDrawSurface7
Dim primary As DirectDrawSurface7
Dim backbuffer As DirectDrawSurface7
Dim ddsd1 As DDSURFACEDESC2
Dim ddsd2 As DDSURFACEDESC2
Dim ddsd3 As DDSURFACEDESC2
Dim brunning As Boolean
Dim CurModeActiveStatus As Boolean
Dim bRestore As Boolean


'Palette Variables
'An 8bit palette has 256 entries, making an
'array of 255 (0 is the first element) allows us
'to represent the entire palette.

Dim Pal(255) As PALETTEENTRY
'This is the palette object, it is created by Directdraw based
'on the entries in the above array.

Dim DDPalette As DirectDrawPalette
'These are the offset variables - explained in the init procedure
Dim Ro As Integer, Go As Integer, Bo As Integer


Sub Init()
On Local Error GoTo errOut:

'This creates a directdraw object
Set dd = dx.DirectDrawCreate("")
'This must be called before the next two lines.
'Otherwise (for some reason) the form does not maximise itself.
'This becomes a problem when the user clicks the mouse, if the mouse
'isn't over the form then it will attempt to bring whatever window
'is behind it to the front - which conflicts with DD, it won't crash, but
'you'll get some weird arftifacts appearing - parts of windows will appear
'on the screen in 8-bit palettised form. Only if the form is maximised can
'we be garaunteed to catch every mouse event.

Me.Show

'You should know these two lines by now.....
Call dd.SetCooperativeLevel(Me.hWnd, DDSCL_FULLSCREEN Or DDSCL_ALLOWMODEX Or DDSCL_EXCLUSIVE) Call dd.SetDisplayMode(640, 480, 8, 0, DDSDM_DEFAULT)


'Create the primarybuffer. Note the special flag DDPF_PALETTEINDEXED8
'This is so that we can apply an 8-bit palette to this surface. the same
'flag is used when creating the bitmap surface. If you use 1,2 or 4 bit
'palettes change this flag to be DDPF_PALETTEINDEXED*1*2*4 (whichever
'fits).

ddsd1.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT
ddsd1.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX
ddsd1.ddpfPixelFormat.lFlags = DDPF_PALETTEINDEXED8
ddsd1.lBackBufferCount = 1
Set primary = dd.CreateSurface(ddsd1)
'The palette is not applied to the primary surface just yet. This is because
'it has not yet been created - see notes below.


Dim caps As DDSCAPS2
caps.lCaps = DDSCAPS_BACKBUFFER
Set backbuffer = primary.GetAttachedSurface(caps)
backbuffer.GetSurfaceDesc ddsd3

'We must set up the palettes before the surfaces. This is because
'of the way i wrote the program - not a directdraw convention
'In the initsurfaces procedure it applies the palette to the surface
'if the palette hasn't been created yet it will give an error, so we
'must create the palettes first. SetupPalette
'The only rules you must adhere to when using palettes and applying
'them to surfaces are:
'1. Surfaces must be created before palettes can be applied to them
'2. Palettes must be created before they can be attached to a surface

'Create the single surface
InitSurfaces

'Set the default values.
'These values just shift an entry towards one end
'of the spectrum. They are an Offset.
'If the offset is 0 then it generates a normal black-white
'greyscale palette, if the offset changes it will enhance the
'amount of colour in that channel. ie. if Ro is positive it will tint the palette
'red. If one colour goes negative it is the same as enhancing the other
'channels, ie. if Ro=-10 and Go=0; Bo=0 it is the same colour as
'Ro=0, Go=10,Bo=10
'The easiest way to understand this effect is to play with the program
'use the controls to enhance different colours and see what the different
'results are.

Ro = 0: Go = 0: Bo = 0

binit = True
brunning = True
Do While brunning
'This is a simple logic tree, it stops the values
'from going out of range. There is more checking in the
'ModifyPalette procedure. The values must be allowed
'to go all the way to -255, otherwise we can't
'achieve true black.

If Ro >= 255 Then Ro = -255
If Go >= 255 Then Go = -255
If Bo >= 255 Then Bo = -255

If Ro <= -255 Then Ro = 255
If Go <= -255 Then Go = 255
If Bo <= -255 Then Bo = 255
'This is actually a call to modify the palette.
'you could manually set the palette offsets by
'replacing Ro,Go and Bo.....

ModifyPalette Ro, Go, Bo
blt
DoEvents
Loop

errOut:
EndIt
End Sub


Sub InitSurfaces()
Set Mainsurf = Nothing 'clear any existing data

'load the bitmap into a surface - backdrop.bmp
ddsd2.lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH
ddsd2.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
ddsd2.ddpfPixelFormat.lFlags = DDPF_PALETTEINDEXED8
ddsd2.lWidth = 640
ddsd2.lHeight = 480

'To demonstrate the effects there are 3 different pictures: 0,1 and 2
'This just chooses a random picture. tip: 1 and 2 look good. :-)

Randomize
Set Mainsurf = dd.CreateSurfaceFromFile(App.Path & "\backdrop" & Int(Rnd * 2) & ".bmp", ddsd2)

'Apply the palette to the surface. You can only do this AFTER the
'surface has been created - if you try to do this before then it will generate
'an error

Mainsurf.SetPalette DDPalette
'DDPalette is the single, standard palette
'that we are going to use.
'Note, the palette used for the Mainsurf is always going to be a greyscale
'bitmap - only the palette for the primary buffer gets changed/tinted.

End Sub


Sub blt()
On Local Error GoTo errOut
If binit = False Then Exit Sub 'Skip this code if we haven't finished
'initialising directdraw. This should never be the case though, the code
'is designed to only start blitting after the initialisation process. If this line
'stops the code then it is because there was an error setting up directdraw


'DirectDrawReturnVALue
Dim ddrval As Long

Dim rBack As RECT


' this will keep us from trying to blt in case we lose the surfaces (alt-tab)
bRestore = False
Do Until ExModeActive
DoEvents
bRestore = True
Loop

' if we lost and got back the surfaces, then restore them
DoEvents
If bRestore Then
bRestore = False
dd.RestoreAllSurfaces
InitSurfaces
End If

'Define the area we want to blit to as the whole screen
rBack.Bottom = ddsd3.lHeight
rBack.Right = ddsd3.lWidth

'A very simple call. Note: Palettes are not mentioned here. DDraw
'automatically implements the use of them and modifies the
'colours - you don't need to do this.

ddrval = backbuffer.BltFast(0, 0, Mainsurf, rBack, DDBLTFAST_WAIT)

'flip the back buffer to the screen
primary.Flip Nothing, DDFLIP_WAIT
errOut:
End Sub


Sub EndIt()
'This shuts DD down, very quick, very simple.
Call dd.RestoreDisplayMode
Call dd.SetCooperativeLevel(Me.hWnd, DDSCL_NORMAL)
End End


Sub Private Sub Form_Click()
'Can also press [escape] to end the program
EndIt
End Sub


Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
'This part is fairly simple, depending on which button is pressed
'it modifies that colour
'I used Form_Keydown because it allows you to hold down the button
'Form_KeyUp requires you to keep tapping the button

Select Case KeyCode
Case vbKeyPageUp
Bo = Bo + 1
Case vbKeyPageDown
Bo = Bo - 1
Case vbKeyEnd
Go = Go - 1
Case vbKeyHome
Go = Go + 1
Case vbKeyInsert
Ro = Ro + 1
Case vbKeyDelete
Ro = Ro - 1
Case vbKeyUp
Ro = Ro + 1: Go = Go + 1: Bo = Bo + 1
Case vbKeyDown
Ro = Ro - 1: Go = Go - 1: Bo = Bo - 1
Case vbKeyReturn
Ro = 0: Go = 0: Bo = 0
Case vbKeyEscape
'This is the only different one - just ends the program
EndIt
End Select
End Sub


Private Sub Form_Load()
Dim msg As String
'This just lists the controls for you.
msg = "Controls:" & vbCr
msg = msg & "INSERT = Increase amount of red" & vbCr
msg = msg & "DELETE = Decrease amount of red" & vbCr
msg = msg & " " & vbCr
msg = msg & "HOME = Increase amount of green" & vbCr
msg = msg & "END = Decrease amount of green" & vbCr
msg = msg & " " & vbCr
msg = msg & "PAGE UP = Increase amount of blue" & vbCr
msg = msg & "PAGE DOWN = Decrease the amount of blue" & vbCr
msg = msg & " " & vbCr
msg = msg & "UP ARROW = Increase all colours" & vbCr
msg = msg & "DOWN ARROW = Decrease all colours" & vbCr
msg = msg & " " & vbCr
msg = msg & "ENTER = Reset all values" & vbCr
msg = msg & "ESCAPE / Left Mouse Button = Exit Program" & vbCr
msg = msg & " " & vbCr
'A little bit of advertising ;-)

msg = msg & "If you like this example, visit me at:" & vbCr
msg = msg & "http://www.dx4vb.da.ru" & vbCr
MsgBox msg, vbInformation, "Info"
Init
End Sub


Private Sub Form_Paint()
'if windows tells VB that you need to redraw
'the window then translate it into a
'directdraw call.....

blt
End Sub


Function ExModeActive() As Boolean
'This checks that we're in the correct mode
Dim TestCoopRes As Long
TestCoopRes = dd.TestCooperativeLevel
If (TestCoopRes = DD_OK) Then
ExModeActive = True
Else
ExModeActive = False
End If
End Function


Sub SetupPalette()
'This goes through each entry in the pal() array and creates a default colour
'If you skipped this part and just created a palette it would be completely
'black - as by default all the entries in the array will be 0

Dim I As Integer
For I = 0 To 255
Pal(I).red = I
Pal(I).green = I
Pal(I).blue = I
Next I

'The is where you actually create the palette. The flags specify it as
'being an 8bit palette; the ALLOW256 flag is important. Without it, directdraw
'reserves entries 0 and 255 in the palette for itself - effectively allowing you
'only 254 colours. if we use this flag it allows us to use the first & last entries.

Set DDPalette = dd.CreatePalette(DDPCAPS_8BIT Or DDPCAPS_ALLOW256, Pal())
'Apply the palette to the surface - this represents the screen.
primary.SetPalette DDPalette
'If you have slightly different palettes on the primary surface and the bitmpa
'surface, when you copy the bitmap to the primary surface it will translate it
'into the primary surface's palette.

End Sub


Sub ModifyPalette(redOffset As Integer, greenOffset As Integer, blueOffset As Integer)
'This modifies the palette depending on the offset.
'This is almost identical to creating the original palette
'the offsets add (or subtract) an amount from the overall value.


Dim Temp As Integer
Dim T As Integer

For T = 0 To 255
Temp = T + redOffset
'We must check that the value is within the correct range
'before setting the value. If the value is out of bounds
'we correct it.

If Temp >= 255 Then Temp = 255
If Temp <= 0 Then Temp = 0
Pal(T).red = Temp Temp =

T + greenOffset If Temp >= 255 Then Temp = 255
If Temp <= 0 Then Temp = 0
Pal(T).green = Temp

Temp = T + blueOffset
If Temp >= 255 Then Temp = 255
If Temp <= 0 Then Temp = 0
Pal(T).blue = Temp

Next T
'This creates the palette again.
Set DDPalette = dd.CreatePalette(DDPCAPS_8BIT Or DDPCAPS_ALLOW256, Pal())
'and applies it to the screen.
primary.SetPalette DDPalette
End Sub

 

Finished. This article only discusses the use of 256 colour palettes; 2,4 and 16 colour palettes are just as easy, but they are very rarely used any more. Should you want a different number of colours to those listed above (2,4,16,256) then you need to create the palette one-size up fromt he number you want. for example, you only need 32 colours - define a 256 colour palette and only use the first 32 entries; same goes for if you wanted a 12 colour palette - define a 16 colour palette and only use 12 entries.

Palette animation can be an incredibly powerful effect - with little or no speed loss. Although palette animation is not automated in this example it can be simulated - hold down the up/down arrows and you will see the image fade in or fade out. Palette animation is as simple as writing a maths formula, you need to write code that changes the palette based on certain guidelines and rules.

You can download the complete, working, project from the download page, or you can download it from the top of the page.

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster