| 
 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-bit surface = 2 colours
 
  - 2-bit surface = 4 colours
 
  - 4-bit surface = 16 colours
 
  - 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. 
                                       |