╖ Demo coding - Color cycling ╖

© 1998 by IceDragon / MiB



Introduction

The aim of this tutorial is to show newbies how they can achieve simple color cycling effects in assembly. It assumes that you are familiar with the basics of ASM on the PC (if not, you should read Cruehead's fine tutorial on ASM first).



What is color cycling ?

When you draw a picture on the screen you use a defined set of colors, called the palette. Of course you can change the values of the colors defined in the standard palette, to be able to use your own set of colors. If you rotate the value of colors continuosly, you do 'color-cycling'... Now you might ask, "What the hell can i use that for ???". Okay, apart from showing off with it ("Hah, you lamer can't even do a simple color-cycle !" :-P) you can give the impression of "movement" in a picture. Or you can let something glow (eg. cycling from dark colors to light colors). Amiga-freaks should be familiar with it anyway (the Amiga had color-cycling implemented in one of its graphic-chips). In this tutorial, i'll give you an example of a color cycle which looks like something is being moved.




VGA, the palette and other things....


Video Modes

First thing you should know is how to get into a proper graphic mode. For this tutorial we will be messing around in Mode 13h (this is 320x200 with 256 colors) coz it's very simple to work with it. To get into Mode 13h, we have to call the BIOS Interrupt 10h (which is used for Video/Display routines). Switching Video-modes is done via function 00 of Int 10h:



Interrupt 10h
function 00 - Set Video Mode
needs Video mode in AL
	AH = 00
	AL = 00  40x25 B/W text (CGA,EGA,MCGA,VGA)
	   = 01  40x25 16 color text (CGA,EGA,MCGA,VGA)
	   = 02  80x25 16 shades of gray text (CGA,EGA,MCGA,VGA)
	   = 03  80x25 16 color text (CGA,EGA,MCGA,VGA)
	   = 04  320x200 4 color graphics (CGA,EGA,MCGA,VGA)
	   = 05  320x200 4 color graphics (CGA,EGA,MCGA,VGA)
	   = 06  640x200 B/W graphics (CGA,EGA,MCGA,VGA)
	   = 07  80x25 Monochrome text (MDA,HERC,EGA,VGA)
	   = 08  160x200 16 color graphics (PCjr)
	   = 09  320x200 16 color graphics (PCjr)
	   = 0A  640x200 4 color graphics (PCjr)
	   = 0B  Reserved (EGA BIOS function 11)
	   = 0C  Reserved (EGA BIOS function 11)
	   = 0D  320x200 16 color graphics (EGA,VGA)
	   = 0E  640x200 16 color graphics (EGA,VGA)
	   = 0F  640x350 Monochrome graphics (EGA,VGA)
	   = 10  640x350 16 color graphics (EGA or VGA with 128K)
		 640x350 4 color graphics (64K EGA)
	   = 11  640x480 B/W graphics (MCGA,VGA)
	   = 12  640x480 16 color graphics (VGA)
	   = 13  320x200 256 color graphics (MCGA,VGA)


	- if AL bit 7=1, prevents EGA,MCGA & VGA from clearing display

You call Int 10h to set mode 13h with the following code:
        mov     ah, 00h         ; Set video mode 
        mov     al, 13h         ; Use mode 13h   
        int     10h             ; ... let's go !
The Palette

In Mode 13h, we have 256 colors to play with. That gives us in hexadecimal the colour numbers from 00h to FFh. Each color is represented through 3 byte sized values: the Red, Green and Blue components (RGB). The RGB values range from 0 to 63 decimal or 0h to 40h in hexadecimal. So the color Black would have 0,0,0 as RGB values and White would be 63,63,63 .


Now we need a method to change the colors of the standard VGA palette. This is done with the VGA registers. Here is a description of the VGA registers concerning the palette:

 VGA-Register 03C7h    Sets the palette in read mode

 VGA-Register 03C8h    Sets the palette in write mode

 VGA-Register 03C9h    Read or write 3 RGB values, after every third write the
                   color you are setting is incremented by one.

"How can i access these strange registers ?" you might yell... Well, you use the ASM commands IN and OUT. IN and OUT are used to send/receive data from one of the 65536 possible hardware ports of the PC (like the serial port for instance). Here is the syntax for both commands:

 IN ACCUMULATOR, PORT 

 Description:
 Reads some data from PORT into ACCUMULATOR
 where ACCUMULATOR can be AL (byte), AX (word) or EAX (double-word) depending on the size of the data
 you want to read and PORT can either be an immediate value from 0 to 0FFh or DX.

 e.g.: in    ax, 0A0h  ;read word from port 0A0h into ax

 or

       mov   dx, 03C7h  ;put the VGA-PaletteRead Register in dx
       mov   al, 1      ;put the color number to read in al
       out   dx, al     ;set palette in READ mode for color 1
       mov   dx, 03C9h  ;put the VGA-PaletteData Register in dx
       in    al, dx     ;get Red value of color 1 in al
       

 OUT PORT, ACCUMULATOR 
                          
 Description:
 Sends some data from ACCUMULATOR to PORT
 where ACCUMULATOR can be AL (byte), AX (word) or EAX (double-word) depending on the size of the data
 you want to send and PORT can either be an immediate value from 0 to 0FFh or DX.

 e.g.: out   0A0h, ax  ;send word in ax to port 0A0h

 or

       mov   dx, 03C8h ;put the VGA-PaletteWrite Register in dx
       mov   al, 3     ;put the color number to write in al
       out   dx, al    ;Set palette in WRITE mode for color 3
       mov   dx, 03C9h ;put the VGA-PaletteData Register in dx
       mov   al, 63    ;use 63 as new Red value for color 3 in al
       out   dx, al    ;send it ... 

Now we have the 'tools' to change colors in the palette. A nice feature if you want read or write several colors in a row is the automatic color increasing after every 3rd read/write when you use the VGA-PaletteData Register. You can use it like this:
       mov   dx, 03C8h ;put the VGA-PaletteWrite Register in dx
       mov   al, 3     ;put the color number to write in al
       out   dx, al    ;Set palette in WRITE mode for color 3
       mov   dx, 03C9h ;put the VGA-PaletteData Register in dx
       mov   al, 63    ;use 63 as new Red value for color 3 in al
LoopCol:
       out   dx, al    ;send it ... 
       out   dx, al
       out   dx, al
       dec   al
       cmp   al, 0
       jnz   LoopCol

This code starts with setting the palette in WRITE mode for color number 3 (we want to change the colors) and then writes three times the value 63 to the VGA-PaletteData register. After that, the value in al is decreased by one and the new value is sent again 3 times to the PaletteData Register, then again decreased, then again sent 3 times and so and so on ... until finally al is ZERO. What does that do ? After the value 63 is sent 3 times to the VGA register, the VGA automatically increases the color number you are about to change by one. So the next time the value 62 from al is sent, color number 4 is changed accordingly. This goes on until al reaches zero. As a result you have changed colors 3 to 67 in shades from white to black.


Arrays

Ha ha, i bet you wondered about the "other things" in the topic line. Well, other things are Arrays in this case ;-)
What the heck is an array ? And what do i need it for ?
Don't panic, i'll tell you (almost) everything 'bout arrays... An Array is a group of data items which all have the same type. For example a book is an array of pages (since each page is generally the same type and contains string data :-)). Usually, you declare a single variable in ASM like this:

Name      size   initial-value


MyVar     DB     0
This declares the variable MyVar with the size of one byte (with the statement DB) and the initial value of zero. If you want MyVar to have no specific value from the start on, you can declare it uninitialized with the "?" operator:
                                
Name      size   initial-value


MyVar     DB     ?
Okay, now how can i declare an array ? Quite simple. You just use the number of elements along with the "dup" operator:
Name      size   No.  dup   initial-value


MyArray   DB     10   dup   ?
This declares a byte-sized array which has 10 uninitialized elements (or you could say MyArray is made up of 10 bytes uninitialized data).
"Well, these array-thingys look quite flashy. But what can i use them for ???"
Chill, man. I'll show you.
First thing, your data segment will look far more superior containing arrays in the first case. Imagine the following data segment:
.DATA

Bored1    DB   0
Bored2    DW   0

Whazzup   DB   ?

Badjoke   DB   0
You will agree with me that this must be the most boring data segment you have ever seen in a ASM source code. But now to a data segment which contains an array:
.DATA

;**** BEGIN OF NAUGHTY DATA DECLARATIONS ****

Naughty   DB  11

;**** HOORAY - ARRAY ! ****

Hooray    DB   128  dup  0   ; HEY ! IT'S AN ARRAY !

Cheers    DW   7

Drunk     DB   ?
Isn't it wonderful ? Ain't it pretty ? Yes, arrays can do wonderful things for us all...
Okay, okay, enough of that bloody jokes (you can stop throwing stones at me now) - Show must go on:
Thumb rule is that ff you have data which belongs together (and is the same type/size) you should store it in an array. Why that ? Because it is easier to reference and easier to process in code than declaring each element seperately. For instance, if you want to make a virtual screen (that is a copy of the VGA-screen in memory) it would sure be quite a mess to declare each byte for every pixel seperately. Instead you just declare an array of 64000 bytes (320x200) and that's it.
.DATA    ;beginning of data segment...

;*** Virtual Screen ***

VScreen    DB 64000 dup 0    

Referencing one of the elements of an array is also very easy. There are 2 methods of referencing, direct and indirect. Lets say you have a BYTE-sized array of 20 elements which is called TestArray and you want to get the ninth element of it into register cl. The definition of the array is:
TestArray DB   20   dup 0
Then the code for moving the contents of element 9 into cl will look like:
mov  cl, [TestArray + 8]   ;direct reference of element 9 
or for an indirect reference:
mov  ax, 8
mov  cl, [TestArray + ax]   ;indirect reference of element 9 
Note that every array starts with element 0 so the ninth element is number 8 !!! Now referencing an array which has a size other than byte is a bit more difficult. You have to multiply the index with the size divided through bytes in order to get the right reference. Lets take a WORD-sized array this time:
TestArray DW   20   dup 0
Then the code for moving the contents of element 9 into cx (WORD-sized) will look like:
mov  ax, 8                  ;ninth element
mul  2                      ;multiplied with (size/bytes): a word consists of 2 bytes
mov  cx, [TestArray + ax]   ;indirect reference of element 9 
I hope you get the point. Of course it is possible to declare Arrays in other sizes than byte or word, but since this is NOT a tutorial on DATA STRUCTURES IN ASM i will skip this now.






Ingredients


Let's sum up again what we need for the color cycling routine:

Okay, we already covered most of it. So i hope you are ready for a source code example:






Example 1 - Cycle.asm


This example will display a string on the screen and then cycles the color of the text through shades of yellow. It is structured like this:

  1. Set graphic mode 13h (320x200)
  2. Print string in the middle of the screen
  3. Wait for a Vertical Retrace (flicker-free access to the VGA)
  4. Cycle the Text-Color through the array ColList
  5. Check for Keypress - if user hit any key then exit
  6. Back to number 3
Looks easy, doesn't it ? Okay, now i will explain some of the source code in detail.

The data-segment should look familiar. First, we have...
CyclePtr   DW 0                             
CyclePtr is obviously a WORD-sized variable (DW) with the initial value of zero. CyclePtr is used to store the current position in the array ColList.
Text1      DB 'PALETTE-CYCLE DEMO PART I',0
Text1 holds the string that should be printed on the screen.
Credits    DB 0Dh, 0Ah,'This was the Palette-Cycle Demo',0Dh,0Ah
etc...
Credits holds the string that should be printed on exit.
ColList    DB    0 , 0 , 0 , 1 , 1 , 0 , 2 , 2 , 0 , 3 , 3 , 0 , 4 , 4 , 1 , 5 , 5 , 1 , 6 , 6 
etc...
This is the array that holds the color values which are going to be cycled. It ends with 3 times 0FFh as a marker for the end of the color table.

Let's go on with an overview of the code-segment:

First comes the definition of the WaitVRetrace macro. This code checks the VGA-Register 03DAh if a vertical retrace is in progress. It loops until a retrace and then gives control back to the program.
Then we have the procedure CyclePal. This is where all the stuff for the actual cycling of the color is done.
After that comes the procedure WriteChar which writes the string Text1 on the graphic screen.
Next the main program begins at the label START (look at table above for the structure).


Now, here is the full source:

    .MODEL SMALL
    .STACK 200h
    .DATA


CyclePtr   DW 0                             

Text1      DB 'PALETTE-CYCLE DEMO PART I',0
Credits    DB 0Dh, 0Ah,'This was the Palette-Cycle Demo',0Dh,0Ah
           DB          '            Part I             ',0Dh,0Ah
           DB          '  done by Icedragon from MiB   ',0Dh,0Ah
           DB          '          1998/12/16           ',0Dh,0Ah           
           DB          '                               ',0Dh,0Ah           
           DB          '     visit our homepage at     ',0Dh,0Ah           
           DB          '  http:\\www.mib99.cjb.net     ',0Dh,0Ah
           DB          '                               ',0Dh,0Ah,0           
; *** This is the sample palette for the cycled colors.    ***
; *** The 3 0FFh bytes at the end are put in to make       ***
; *** sure that the index gets the proper end value (0FFh) ***
; *** when it is moved 3 bytes forward.                    ***

ColList    DB    0 , 0 , 0 , 1 , 1 , 0 , 2 , 2 , 0 , 3 , 3 , 0 , 4 , 4 , 1 , 5 , 5 , 1 , 6 , 6 
           DB    1 , 7 , 7 , 2 , 8 , 8 , 2 , 9 , 9 , 2 , 10 , 10 , 3 , 11 , 11 , 3 , 12 , 12 , 3 , 13 
           DB    13 , 4 , 14 , 14 , 4 , 15 , 15 , 4 , 16 , 16 , 5 , 17 , 17 , 5 , 18 , 18 , 5 , 19 , 19 , 6 
           DB    20 , 20 , 6 , 21 , 21 , 6 , 22 , 22 , 7 , 23 , 23 , 7 , 24 , 24 , 7 , 25 , 25 , 8 , 26 , 26 
           DB    8 , 27 , 27 , 8 , 28 , 28 , 9 , 29 , 29 , 9 , 30 , 30 , 9 , 31 , 31 , 10 , 32 , 32 , 10 , 33 
           DB    33 , 10 , 34 , 34 , 11 , 35 , 35 , 11 , 36 , 36 , 11 , 37 , 37 , 12 , 38 , 38 , 12 , 39 , 39 , 12 
           DB    40 , 40 , 13 , 41 , 41 , 13 , 42 , 42 , 13 , 43 , 43 , 14 , 44 , 44 , 14 , 45 , 45 , 14 , 46 , 46 
           DB    15 , 47 , 47 , 15 , 48 , 48 , 15 , 49 , 49 , 16 , 50 , 50 , 16 , 51 , 51 , 16 , 52 , 52 , 17 , 53 
           DB    53 , 17 , 54 , 54 , 17 , 55 , 55 , 18 , 56 , 56 , 18 , 57 , 57 , 18 , 58 , 58 , 19 , 59 , 59 , 19 
           DB    60 , 60 , 19 , 61 , 61 , 20 , 62 , 62 , 20 , 63 , 63 , 21 , 62 , 62 , 20 , 61 , 61 , 20 , 60 , 60 
           DB    20 , 59 , 59 , 19 , 58 , 58 , 19 , 57 , 57 , 19 , 56 , 56 , 18 , 55 , 55 , 18 , 54 , 54 , 18 , 53 
           DB    53 , 17 , 52 , 52 , 17 , 51 , 51 , 17 , 50 , 50 , 16 , 49 , 49 , 16 , 48 , 48 , 16 , 47 , 47 , 15 
           DB    46 , 46 , 15 , 45 , 45 , 15 , 44 , 44 , 14 , 43 , 43 , 14 , 42 , 42 , 14 , 41 , 41 , 13 , 40 , 40 
           DB    13 , 39 , 39 , 13 , 38 , 38 , 12 , 37 , 37 , 12 , 36 , 36 , 12 , 35 , 35 , 11 , 34 , 34 , 11 , 33 
           DB    33 , 11 , 32 , 32 , 10 , 31 , 31 , 10 , 30 , 30 , 10 , 29 , 29 , 9 , 28 , 28 , 9 , 27 , 27 , 9 
           DB    26 , 26 , 8 , 25 , 25 , 8 , 24 , 24 , 8 , 23 , 23 , 7 , 22 , 22 , 7 , 21 , 21 , 7 , 20 , 20 
           DB    6 , 19 , 19 , 6 , 18 , 18 , 6 , 17 , 17 , 5 , 16 , 16 , 5 , 15 , 15 , 5 , 14 , 14 , 4 , 13 
           DB    13 , 4 , 12 , 12 , 4 , 11 , 11 , 3 , 10 , 10 , 3 , 9 , 9 , 3 , 8 , 8 , 2 , 7 , 7 , 2 
           DB    6 , 6 , 2 , 5 , 5 , 1 , 4 , 4 , 1 , 3 , 3 , 1 , 2 , 2 , 0 , 1 , 1 , 0 , 0 , 0 
           DB    0 , 0FFh, 0FFh, 0FFh

    .CODE
    .386

;*** This is a macro to wait for the start of a vertical retrace ***
;*** During a vertical retrace you have flicker-free access to   ***
;*** the VGA.                                                    ***
;*** It first checks if a Retrace is ALREADY in progress,        ***
;*** coz in this case we have to wait for the NEXT one to start  ***
;*** (this way you can be sure that you ALWAYS get the full time ***
;*** for the flicker-free access)                                ***


WaitVRetrace    MACRO
                LOCAL   VRetrace,NoVRetrace
                push    ax                   ;save contents of ax + dx
                push    dx
                mov     dx,03dah             ;check the
VRetrace:       in      al,dx                ;VGA-Register 03DAh
                test    al,00001000b         ;if we are in the middle of a Vertical Retrace...
                jnz     VRetrace             ;if yes, then skip this one and 
NoVRetrace:     in      al,dx                ;wait for the beginning of a new one...
                test    al,00001000b         
                jz      NoVRetrace
                pop     dx                   ;restore ax + dx
                pop     ax                   
                ENDM





;******* This routine cycles the first color in the palette thru the array ColList

CyclePal PROC

        push    ax              ;save regs
        push    bx
        push    cx
        push    dx
        xor     ax, ax          ;clear regs
        xor     bx, bx 
        mov      si, CyclePtr   ;load cycle pointer
        lodsb                   ;get color value
        cmp      al, 0FFh       ;end of palette table ?
        jnz      NotEnd
        mov      CyclePtr, OFFSET ColList ;yes, so reset pointer
        mov      si, CyclePtr   ;and load it again
        lodsb                   ;and get the color value

NotEnd: 
        mov     cl, al
        mov     al, 1h          ; Put the number of the color to cycle in AL
        mov     dx, 03C8h       ; Put the DAC write register in DX

        out     dx, al          ; Send color to port DX
        inc     dx              ; Now use port 03C9h
        mov     al, cl
NextCol:
        inc     bx              ; counter for loop
        out     dx, al          ; Send Red value to port DX
        lodsb                   ; get next color value
        out     dx, al          ; Send Green value to port DX
        lodsb                   ; get next color value
        out     dx, al          ; Send Blue value to port DX
        lodsb                   ; get next color value
        cmp      al, 0FFh       ; is pointer at the end of the palette ?
        jnz      NotEnd2        ; if not, go on...
        mov      si, OFFSET ColList  ; at end, so reset to start of the palette table
        lodsb                   ;get value from table
NotEnd2:
        add     CyclePtr, 3     ;move pointer in Palette ahead to next color
        pop     dx              ;then restore regs...
        pop     cx
        pop     bx
        pop     ax
        ret                     ;...and return !

CyclePal ENDP     

WriteChar       PROC
        mov     bp, OFFSET Text1   ;point bp to string Text1
LoopWrite:
        mov     al, ds:[bp]        ;load next char from string into al
        mov     ah, 0Eh            ;function 0Eh WriteCharTTY (Write Character in Teletype Mode)
        mov     bh, 0h             ;must be zero for graphics mode
        mov     bl, 1h             ;foreground color 1
        int     10h                ;call BIOS interrupt 10h function WriteCharTTY
        inc     bp                 ;point bp to next char
        cmp     al,0               ;string end ?
        jnz     LoopWrite          ;if not then do next char....
        ret                        ;... otherwise return !
WriteChar       ENDP


START:
        
        mov     ax, @data
        mov     ds, ax
        mov     CyclePtr, OFFSET ColList  ;initialize the pointer for the ColList array
;Init 300x200
        mov     ah, 00h         ; Set video mode 
        mov     al, 13h         ; Use mode 13h   
        int     10h             ; ... let's go !
;Set Cursor to middle of screen
        mov     ah, 02h         ;SetCursor function
        mov     bh, 0h          ;page number (0 for graphics)
        mov     dh, 0ah         ;row
        mov     dl, 08h         ;column
        int     10h             ;call function SetCursor
        

        cli                     ; clear the interrupt flag

        call    WriteChar       ;print string on screen
MainLoop:
        WaitVRetrace            ;wait for a vertical retrace
        WaitVRetrace
        call    CyclePal        ;then cycle the palette...


;Keypress ?        
        mov     ah, 01h                       ; Check for keypress
        int     16h                           ; Is a key waiting in the buffer?
        jz      MainLoop                      ; No, keep on going

        mov     ah, 00h                       ; yes, so get the key
        int     16h
        sti                                   ; set the interrupt flag again

TextMode:
;Back to text mode
        mov     ah, 00h
        mov     al, 03h
        int     10h
        mov     si, OFFSET Credits            ;print Credits...
WriteTxt:
        WaitVRetrace
        WaitVRetrace
        WaitVRetrace
        WaitVRetrace
        WaitVRetrace
        lodsb
        mov     ah, 0eh
        xor     bx, bx
        int     10h
        cmp     al, 0 
        jnz     WriteTxt
   
;Exit to DOS
ExitDos:
        mov     ax, 4c00h   
        int     21h


END START




Final words

Have some fun with the example code, modify it - rip it - whatever... Main thing is that you got the point of how to do a color cycle in the first place. I will actually add a second code example very soon, so check our homepage if interested. Of course there will also be tutorials about other demo-effects in assembly on the MiB-page (like scrolltext and other nifty FX you always wanted to implement in that patcher of yours...). If you have any questions/suggestions concerning the code/tutorial or my sense of humour then mail me at icedragon@thevortex.com.