How to fade the palette
Abe's Demoschool: Part II
Today I'll cover how to fade the palette in and out, how to display text in graphic mode, how to use precalculated lists and how to use a virtual screen.
I told you how to set the palette in part 1 of the demoschool, to fade the palette is similar to setting the palette repeaditly with slightly different values. A common trick is to set all the colors of the palette to black, then draw something to the screen and finally fade the palette in. That way the whole picture will fade in simultaneously no matter how much is on the screen.
Like you remember, the palette is set through ports 3c8h and 3c9h. The index of the color that shall be set first is written to port 3c8h. Then the red, green and blue values are written to port 3c9h. After 3 bytes have been written to port 3c9h the color index is automatically increased. To set the entire palette to black, first write 0 to port 3c8h. Then write 0 to port 3c9h 256*3 times.
Here's an example in C:
void blackpal(void)
{
int i;
outp(0x3c8,0);
for(i=0;i<256*3;i++) outp(0x3c9,0);
}
To fade the palette in, the palette is send as parameter to the function that will fade the palette, let's call it fade_in. That function should also have a local array that will hold the temporary values of the palette during the fade. First set all colors in the local palette to 0. Then enter a loop that will set the local palette, increase the values of the local palette until they have reached their "right" values. That will be maximum 64 times since the red green and blue values can be only 63 or less. Before setting the palette you should call wtsync, that waits for the vertical retrace, to avoid snow due to bad syncronisation with the electron beam.
Here's a fade_in funtion in C:
//will fade in the palette palett
void fade_in(unsigned char*palett)
{
int i,j;
unsigned char pal[256*3]; //local array
for(i=0;i<256*3;i++) pal[i]=0; //set entire pal to 0
for(j=0;j<64;j++) //fade in 64 steps
{
outp(0x3c8,0); //Every step begins with setting color 0
wtsync(); //call wtsync to avoid snow
wtsync(); //call it another time to get a slower fade
//You could skip the second call to wtsync for a faster fade
for(i=0;i<256*3;i++) //loop through all bytes in the palette
{
outp(0x3c9,pal[i]); //send a byte to 3c9h
if(pal[i]<palett[i]) pal[i]++; //compare with final value
} //if it's less increase it
}
}
This function fades from black to the palette. Another variant is to fade from the current palette to the palette that will be set. In that case set pal to the current palette with getpal instead of black.
And add in the inner loop:
if(pal[i]<palett[i]) pal[i]++;
if(pal[i]>palett[i]) pal[i]--;
In this case the color will go towards palette[i] even if it's greater.
The getpal function is in the source code.
Another fade would be to fade from the current palette to black and from the current palette to white, this should be real simple if you understand the functions above.
To write text to the screen in graphic mode is not as simple as in textmode. You could use printf but please don't, it's so slooow.
Instead make an array where you store the characters you intend to use. The easiest way is to let one byte represent one pixel. If one character is 8*8 pixels, that makes 8*8 = 64 bytes per character in the array.
In the source I've made a global array chars64 with the ascii-chars 32 ... 90.
That is Space to Z.
The question is how to get hold of the patterns of the characters. You could draw them yourself in a paint program and store them on disk or you could read them from the BIOS dataarea where all the patterns are stored.
This time I'll read'em from BIOS. The problem is that every character is stored with 1 bit per pixel, that is 8 bytes per character and we want them 64 bytes per character.
Ex : Letter A in the BIOS dataarea
Byte BinÑrt Decimal
BYTE 0: 0 0 0 0 0 0 0 0 = 0
BYTE 1: 0 0 0 1 1 0 0 0 = 24
BYTE 2: 0 0 1 0 0 1 0 0 = 36
BYTE 3: 0 1 0 0 0 0 1 0 = 66
BYTE 4: 0 1 1 1 1 1 1 0 = 126
BYTE 5: 0 1 0 0 0 0 1 0 = 66
BYTE 6: 0 1 0 0 0 0 1 0 = 66
BYTE 7: 0 0 0 0 0 0 0 0 = 0
A 0 means the background color and a 1 means the foreground color. For every bit in the pattern we want a byte. That's no problem, just shift the bits one by one and if the shifted bit was a 0 set the respective byte in chars64 to 0 else set it to the foreground color.
The foreground color can be any of 256 possible colors. That means that the pixels in the character does not have to be in the same color. I've chosen to make the top row of every character bright and then I subtract 3 for every row making the letters darker and darker for every row.
Data for BIOS chartable begins at address F000:FA6E
Every character is 8 bytes and they are in the same order as they appear in the ascii-table. The letter A has acii-value 65.
The pattern for A is therefore at address F000:(FA6E + 65*8).
This should be enough to understand the function getbioschars in the source.
To Display the characters I've made two functions. Showchar draws one single character to the screen (the character should be defined in chars64 or else only crap will be drawn to the screen). Show char draws all pixels even the background color. Putcharonflag draws a character in the array flag. It doesn't draw the pixel if it's 0 that makes the characters drawn with putcharonflag transparent.
Lookup-tables are one of the most used and also most effective optimizing tricks in demo and game programming that require real-time prestanda.
The idea is to calculate as much as possible in the initialization phase (beginning) of the program and save the results in arrays (lookup-tables). That way the program don't have to make the calculations in the main-loop of the program, only lookup the results in the lookup-tables (that's where the name comes from).
The classical example of a lookup table is a sinus-table like I used in the example program. The program draws a flag on the screen and then moves it in sine-waves in both x- and y-direction. All movements of the flag are precalculated and put in the array sinlist. If the amplitude of the waves is 20 pixels up and down, then the calculation of the sinlist is done like this:
for(i=0;i<xxx;i++) sinlist[i]=sin(K*i)*20;
This will make sinlist[i] vary between -20 and +20.
K and xxx are constants, xxx is the length of the list and K determines the length of the waves (period of the wave).
At the same time as the flag moves in waves I draw two texts on the screen and the texts also moves in waves (movements come from the same sinlist).
Even the color of the flag changes in waves all from the same sinlist.
This way it looks like there's some major math going on but in reality it's only one precalculated sin-table that does all the work.
The constants are #defined at the top of the code and it's easy to change them and see what happens.
This program also uses a virtual screen. A virtual screen is an array in memory that can be drawn to without anything being spotted on the visible screen. When the whole frame is ready-drawn just "flip" the virtual screen over to the visible screen and it all appears at once.
In this program the virtual screen is smaller than the visible screen but most times they have the same size. Before flipping it's a good idea to call wtsync, to avoid snow and flickering.
Some graphic modes have more than one page, that means that it's possible to draw to a invisible page and when you're ready make that page the visible page. This is the same thing as using a virtual screen but you don't have to allocate any memory for it and the flipping is faster. Multiple pages also make scrolling a lot easier, but mode 13h doesn't normally support multiple pages. Although it is possible to screw with the port's of the graphic card to make it support multiple pages. This is known as unchained mode 13h or Mode X (resolution of real mode X is actually 320*240).
I'll cover these modes later (maybe part 5 or 6).
Demo2.c contains the source code for the flag and text. onlyflag.c is shorter (only the flag) and perhaps easier to understand.
This part have been perhaps too complex for the beginner so if you don't understand everything dont be sad, next part will be easier. It will cover sprites and linking pure assembly routines to C.
PS: The program is still slow even though it uses precalculated tables. It would be faster with 386-specific instructions but my compiler won't allow it. If the assembly routines are written separately and linked with the c-program you can use 386-specific instructions. I'll cover that next time. DS
s-mail:
Albert Veli
spisringsg. 9
724 76 VÑsterÜs
mail: dat94avi@idt.mdh.se
DEMO2.C
/************************************************************************/
/* */
/* Sourcecode for part 2 in Abe's demoschool */
/* */
/* */
/* This program draws a flag on the screen and moves it using a */
/* precalculated sinus-table. */
/* */
/* The program is pretty messy in main with lots of inline */
/* assembly but the functions that are called from main */
/* should be understandable. */
/* */
/* The theory behind it all is found in demo2.txt */
/* Compile demo2.c whith: */
/* */
/* bcc -1 demo2.c Borland c */
/* tcc -1 demo2.c Turbo c */
/* */
/* -1 means that 286-instructions is allowed. */
/* If you use another compiler remember to allow 286-instructions. */
/* */
/* 1996-01-04 VéSTERèS */
/* Albert Veli */
/* mail: dat94avi@idt.mdh.se */
/* */
/* */
/* PS If the flag suffers from severe slowness, try */
/* decreasing the width & height of the flag DS */
/* */
/************************************************************************/
#include<conio.h>
#include<math.h>
//Try to change the first 5 #define values and see what happens
#define WIDTH 180 //Change to acceptable size (less if slow computer)
#define HEIGHT 100 //The height should be about half of the width
#define FASE 2 //change between 1..6
#define WAVEX 2 // 1..4
#define WAVEY 3 // 1..4
#define PI 3.14159 // Don't change these
#define KX FASE*2*PI/WIDTH
#define BLUE 127-20
#define GULT 63-20
#define VITT 255-20
#define RED 191-20
char chars64[64*60]; //SPACE . . Z
//ascii: 32 . . 90 = 59 characters
//Reads ascii 32-90 from BIOS dataarea to the global array chars64
//The patterns are stored at F000:FA6Eh in BIOS-dataarea
//The chars are given color col at the top row and the color is
//then decreased by 3 every row making the chars darker downwards
void getbioschars(char col)
{
char color,number;
asm push ds //push changed registers to the stack
asm push di
asm push si
asm push ds
asm pop es //es = ds = segment for chars64
asm lea di,chars64 //es:di point to chars64
asm mov ax,0F000h //BIOS charset begins at f000:fa6eh
asm mov ds,ax
asm mov si,0fa6eh
asm mov ax,32
asm shl ax,3 //ax=offset SPACE (32) each char 8 bytes in BIOS
asm add si,ax //ds:si points at space
asm mov number,59 //read 59 characters to chars64
asm xor ah,ah
charsloop:
asm mov dx,8 //8 rows in a character
asm mov bl,col
asm mov color,bl
charloop:
asm lodsb //read one row to ax
asm sub color,3 //change color avery row
asm mov bl,al //save the color in bl
asm mov cx,8 //8 bytes in a row
rowloop:
asm mov al,color
asm shl bl,1
asm jc CARRYSET
asm xor al,al
CARRYSET:
asm stosb
asm loop rowloop
asm dec dx
asm jnz charloop
asm dec number
asm jnz charsloop
asm pop si //pop registers off the stack
asm pop di
asm pop ds
}
//erases one character by drawing color 0 in a 8*8 rectangle at (x,y)
void erasechar(int x,int y)
{
asm mov ax,0a000h
asm mov es,ax
asm mov bx,y
asm mov di,bx //di = y
asm xchg bh,bl //bx = 256*y
asm shl di,6 //di = 64*y
asm add di,bx //di = (256 + 64)*y = 320*y
asm add di,x //di = 320*y + x
asm xor ax,ax
asm mov dx,8
suddach1:
asm mov cx,4
asm rep stosw
asm add di,(320-8)
asm dec dx
asm jnz suddach1
}
//draws the character shar (hmm) at (x,y) on the screen
void showchar(int x,int y,char shar)
{
asm mov ax,0a000h //ax = segment to screen in mode 13h
asm mov es,ax //move seg addr to es
asm mov bx,y //bx = y
asm mov di,bx //di = y
asm xchg bh,bl //bx = 256*y
asm shl di,6 //di = 64*y
asm add di,bx //di = (256 + 64)*y = 320*y
asm add di,x //di = 320*y + x
asm lea si,chars64
asm mov al,shar
asm xor ah,ah
asm sub ax,32 //chars64 begins with space (ascii 32)
asm shl ax,6 //each character 64 bytes
asm add si,ax
asm mov dx,8
showch1:
asm mov cx,4
asm rep movsw
asm add di,(320-8)
asm dec dx
asm jnz showch1
}
//shows the string string at (x,y)
void showstring(int x,int y,char far*string)
{
asm push ds
asm lds si,string
showstr1:
asm lodsb
asm cmp al,0
asm jz clarkent
showchar(x,y,_AX);
asm add x,8
asm jmp showstr1
clarkent:
asm pop ds
}
//Writes the letter shar at (x,y) on the array flag
//The width and height of flag are defined at the top of the code
//The parameter flag is the offset to the array flag relative to ds
//This is called a DDR solution (East germany . . . when all else fails)
void putcharonflag(int x,int y,int flag,char shar)
{
asm push ds
asm pop es
asm mov di,flag //es:di=flag
asm mov ax,y
asm mov bx,WIDTH
asm mul bx
asm add ax,x
asm add di,ax
asm lea si,chars64
asm mov al,shar
asm xor ah,ah
asm sub ax,32 //chars64 begins with space (ascii 32)
asm shl ax,6 //each character 64 bytes
asm add si,ax
asm mov dx,8
showch2:
asm mov cx,8
putchloop:
asm lodsb
asm cmp al,0
asm jz dontput
asm mov [es:di],al
dontput:
asm inc di
asm loop putchloop
asm add di,(WIDTH-8)
asm dec dx
asm jnz showch2
}
//Waits for the electron beam to finish current screen (vertical retrace)
void wtsync(void)
{
asm mov dx,3DAh
WaitVR1:
asm in al,dx
asm test al,8
asm jne WaitVR1
WaitVR2:
asm in al,dx
asm test al,8
asm je WaitVR2
}
//makes the palette pal the active palette
void setpal(unsigned char*pal)
{
int i;
outp(0x3c8,0);
for(i=0;i<256*3;i++)
outp(0x3c9,pal[i]);
}
//sets all colors to black
void blackpal(void)
{
int i;
outp(0x3c8,0);
for(i=0;i<256*3;i++) outp(0x3c9,0);
}
//fades the palette in from black
void fade_in(unsigned char*palett)
{
int i,j;
unsigned char pal[256*3];
for(i=0;i<256*3;i++) pal[i]=0;
for(j=0;j<64;j++)
{
outp(0x3c8,0);
wtsync();
wtsync();
for(i=0;i<256*3;i++)
{
outp(0x3c9,pal[i]);
if(pal[i]<palett[i]) pal[i]++;
}
}
}
//fades the palette out to black
void fade_to_black(char*pal)
{
int i,j;
for(j=0;j<64;j++)
{
outp(0x3c8,0);
wtsync();
for(i=0;i<256*3;i++)
{
outp(0x3c9,pal[i]);
if(pal[i]>0) pal[i]--;
}
}
} // (fade to black is btw a great song by Metallica)
//fades the palette to white
//both fade_to_black and fade_to_white destroys the array pal
//if you want to save pal just make a local copy of it in the function
//and leave the original alone
void fade_to_white(char*pal)
{
int i,j;
for(j=0;j<64;j++)
{
outp(0x3c8,0);
if(i%2==0)wtsync(); //let the fade to white be pretty fast
//only wait everyother frame
for(i=0;i<256*3;i++)
{
outp(0x3c9,pal[i]);
if(pal[i]<63) pal[i]++;
}
}
}
//calculates a palette and puts it in the array pal
void coolpal(char*pal)
{
int i,r=0,g=0,b=0,col=0;
for(i=0;i<64;i++) // BLACK - YELLOW
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(r<63)r++;
if(g<63)g++;
}
r=0;
g=0;
for(i=0;i<64;i++) // BLACK - BLUE
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(b<63)b++;
}
b=0;
for(i=0;i<64;i++) // BLACK - RED
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(r<63)r++;
}
r=0;
for(i=0;i<64;i++) // BLACK - WHITE
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(r<63)r++;
if(g<63)g++;
if(b<63)b++;
}
setpal(pal); // activate the palette pal
}
//puts a pixel at (x,y) with color col
void putpixel(int x, int y, char col)
{
asm mov ax,0a000h
asm mov es,ax
asm mov bx,y //bx = y
asm mov di,bx //di = y
asm xchg bh,bl //bx = 256*y
asm shl di,6 //di = 64*y
asm add di,bx //di = (256 + 64)*y = 320*y
asm add di,x //di = 320*y + x
asm mov al, col //now es:di points where the pixel shall be drawn
asm mov [es:di],al //draw the pixel
}
//Draws the flag on the screen, very slow but it doesn't matter
//because it will only be done once in the beginning of the program
//(px,py) is the upper left corner of the screen where the flag will be drawn
void drawflag(char*flag,int px,int py)
{
int x,y;
for(x=0;x<WIDTH;x++)
{
for(y=0;y<HEIGHT;y++) putpixel(px+x,py+y,flag[WIDTH*y+x]);
}
}
//sets a graphic mode, the mode is sent as parameter to the function
void setmode(int mode)
{
asm mov ax,mode
asm int 10h
}
//creates a tricolor, like the french flag
void trikolor(char*flag)
{
int x,y;
for(x=0;x<WIDTH;x++)
{
for(y=0;y<HEIGHT/3;y++) flag[WIDTH*y+x]=BLUE;
}
for(x=0;x<WIDTH;x++)
{
for(y=HEIGHT/3;y<HEIGHT;y++) flag[WIDTH*y+x]=VITT;
}
for(x=0;x<WIDTH;x++)
{
for(y=2*HEIGHT/3;y<HEIGHT;y++) flag[WIDTH*y+x]=RED;
}
}
//Creates a swedish flag (Blue with yellow cross)
//To change to finnish (White with Blue cross) or some other cross flag
//you only have to change the colors
//(svenskflag means swedish flag in swedish)
void svenskflag(char*flag)
{
int x,y;
for(x=0;x<WIDTH/4+WIDTH/40;x++)
{
for(y=0;y<HEIGHT;y++) flag[WIDTH*y+x]=BLUE;
}
for(x=WIDTH/4+WIDTH/40;x<WIDTH/3+WIDTH/15;x++)
{
for(y=0;y<HEIGHT;y++) flag[WIDTH*y+x]=GULT;
}
for(x=WIDTH/3+WIDTH/15;x<WIDTH;x++)
{
for(y=0;y<HEIGHT;y++) flag[WIDTH*y+x]=BLUE;
}
for(x=0;x<WIDTH;x++)
{
for(y=HEIGHT/2-HEIGHT/9;y<HEIGHT/2+HEIGHT/9;y++) flag[WIDTH*y+x]=GULT;
}
}
/*
All three-colored flags like the french and all cross flags like the swedish
is very easy to create with small changes.
It will be more difficult to create an English or American flag because of
their geometries. But it is possible since none of them has more than four
colors and this program copes with ANY picture that consists of four colors
or less, the problem is to define the pattern of the flag. It could be done
in a separate file. . .
The palette is divided into four parts (0-63) (64-127) (128-191) (192-255).
(Watch the defines at the top of this code).
Each part has their own run from black to the color itself.
The color varies +20 to -20 colors, that means that
color 1 should be 63 - 20 = 43
color 2 should be 127 - 20 = 107
color 3 should be 191 - 20 = 171
color 4 should be 255 - 20 = 235
Then color 1 will vary between 23 and 63. This leaves colors 0-22 unused
in each interval.
But with all our knowledge we could easy use them too but I don't think it's
urgent. For now we have enough colors.
*/
/*
Main is pretty badly written, I know that I should have divided it
up into several smaller functions but I didn't because It's workin fine
right now and I'm very, very sleepy (yaaawwn).
*/
void main(void)
{
int x,y,px,py,py1,foo,bar,doo,index,textx,texty,text2x,text2y,adrflag;
int sinlist[4*WIDTH];
char pal[256*3];
char flag[(WIDTH)*HEIGHT];
char virt[(WIDTH+40)*(HEIGHT+40)];
char text[30]="ABE'S DEMOSCHOOL PART 2"; //this text will be written
char text2[30]="FLY THE FLAG"; //this too
asm lea ax,flag //this isn't really necessary but I couldn't
asm mov adrflag,ax //make it compile any other way
//the offset to the array flag is saved in the int adrflag
getbioschars(255-20); //get the patterns for the characters, let the top
//row of the characters have color 255-20
//The color will vary 20 remember?
for(x=0;text[x]!=0;x++);
x++; //x = length of the string text
textx=((320-(x<<3))>>1); //calculate a "good" position in x-direction for the first text
texty=24; //y-position of the first text
for(x=0;text2[x]!=0;x++);
x++; //x = lenght of the second text
text2x=15+((320-(x<<4))>>1); //x-pos for text2 (this formula is not perfect)
text2y=190; //y-pos for text2
setmode(0x0013); //get into mode 13h
//calculate the sinus values and save them in the array sinlist
//this way we don't have to calculate any sines in the main loop
for(x=0;x<WIDTH;x++)
{
sinlist[x]=0; //let the flag be still at first
sinlist[WIDTH+x]=sin(KX*x)*20;//then let it vary in sine waves
sinlist[2*WIDTH+x]=sinlist[WIDTH+x];
sinlist[3*WIDTH+x]=sinlist[WIDTH+x];
}
coolpal(pal); //setup a palette with smooth colorruns
svenskflag(flag); //draw a swedish flag
//trikolor(flag); //or a french flag, you choose
//My c-compiler didn't want me to send the pointer to the array flag
//so I sent the offset (adrflag) instead.
//this is a very ugly solution (DDR!?) but it works
putcharonflag(10,46,adrflag,'H'); //Put some text on the flag
putcharonflag(20,46,adrflag,'E'); //what could be more appropriate
putcharonflag(30,46,adrflag,'L'); //than the classic "HELLO WORLD"
putcharonflag(40,46,adrflag,'L'); //from Ritchie's and Kernighans book
putcharonflag(50,46,adrflag,'O'); //The C Programming Language
putcharonflag(70,46,adrflag,'W');
putcharonflag(80,46,adrflag,'O');
putcharonflag(90,46,adrflag,'R');
putcharonflag(100,46,adrflag,'L');
putcharonflag(110,46,adrflag,'D');
blackpal(); //set all colors to black
drawflag(flag,160-WIDTH/2,70); //draw the flag the first time
fade_in(pal); //fade it in (Ooooh)
//You can comment out the blackpal and fade_in rows if you get tired of
//waiting for the fades everytime you run the program
index=0; //index to the sinlist
//by changing the index you get the flag moving
//This is the main loop it contains some inline assembly
//If you want you could cut out the inline assembly stuff and put
//it in separate functions.
do
{
//first erase the whole virt array
asm{ push ds
pop es //es = ds = datasegment
lea di,virt
mov cx,((WIDTH+40)*(HEIGHT+40))/2
xor ax,ax
rep stosw
lea si,flag //point ds:si to the array flag
mov doo,si //doo is the offset to the array flag
lea di,virt //es:di points to virt
add di,(WIDTH+40)*20+20 //start at 20,20 in virt (leave room for movement)
mov bar,di //bar = the offset to virt
} //foo and bar are temporary variables that points to the arrays
//flag and virt
//it's common to call temporary variables foo, bar and doo
//(already on the ZX-Spectrum time they did so)
//Copy the data from virt over to flag, at the same time move the vertical
//rows of the flag foo pixels in the y-direction.
//foo, py and py1 are calculated from the sinlist.
//foo depends on index and the x-position during the loop
//changing index makes the flag move from frame to frame
//at the same time I change the color also depending on the index and x-position
//making the color change with the waves in the x-direction
//which enhances the feeling of waveiness
for(x=0;x<WIDTH;x++)
{
py=sinlist[index+x]; //py=value to add to the color
py1=sinlist[index+x+WIDTH/(2*FASE)]; //make the y-waves move in fase with the colorwaves
foo=(WIDTH+40)*(py1>>WAVEY); //foo = the change in the y-direction
_DI=bar+x+foo; //di = the position in virt
_SI=doo+x; //si = the position in flag
asm mov cx,HEIGHT //cx = the number of pixels to move
asm mov bx,py //bx = the change in color
ape:
asm lodsb
asm add ax,bx //ax = color + change in color
asm stosb
asm add si,(WIDTH-1)
asm add di,(WIDTH+40-1)
asm loop ape
}
//Now the flag and color is in waves in the y-direction
//Now we'll make it wavey in the x-direction too
//That's done completely in virt
//When it's done virt is flipped over to the visible screen
asm lea si,virt //let es:di & ds:si point to virt
asm add si,20
asm push ds
asm pop es
asm mov di,si
for(y=0;y<HEIGHT+40;y++) //loop trough all rows
{
px=sinlist[index+y]>>WAVEX; //px = number of pixels to move the row
//px can be positive or negative
if(px<0) //if px is negative
{
asm cld //move from left to right
asm add di,px //px is negative so make si go first
asm mov cx,(WIDTH+10)/2 //and di follow
asm rep movsw
asm add si,30
asm mov di,si
}
if(px>0) //if px is positive move from right to left
{
asm std
asm add si,WIDTH //si first and di follow
asm mov di,si //the pixels will be copied from ds:si
asm add di,px //to es:di so si must be first or the
asm mov cx,(WIDTH+10)/2 //flag will be destroyed
asm rep movsw //move one complete row two pixels at a time
asm add si,WIDTH+50 //move to next row
asm mov di,si
asm cld
}
if(px==0)
{
asm add si,WIDTH+40 //if px is 0 dont move anything
asm mov di,si //just go on to the next row
}
}
index++; //changing index makes the flag move
if(index==2*WIDTH) index=WIDTH; //if index is at the end of the sinlist
//move it to the beginning of the waves
//in the sinlist
//the waves begin at sinlist[WIDTH]
//the absolute beginning of sinlist
//was all 0 remember?
wtsync(); //syncronize with the electronbeam before flipping
//virt over to the visible screen
for(x=0;text[x]!=0;x++) //erase the old text at the old position
//at the new position
{
erasechar(textx+(x<<3),texty+sinlist[index+x]);
showchar(textx+(x<<3),texty+sinlist[index+x+1],text[x]);
}
//the same thing with text2, did you notice text moves insinewaves in
//the y-direction and text2 moves in sinewaves in the x-direction?
//Do you see why?
for(x=0;text2[x]!=0;x++)
{
erasechar(text2x+(x<<4)+(sinlist[index+x]),text2y);
showchar(text2x+(x<<4)+(sinlist[index+x+1]),text2y,text2[x]);
}
//Flip the contents of virt over to the visible screen
//di = upper left corner on the screen where the flag will be drawn
asm{ mov ax,0a000h
mov es,ax
mov di,(320-WIDTH-40)/2+320*50 //This formula is supposed to work
//on different sizes of the flag
//you could also write a constant value directly to di here
lea si,virt
mov dx,HEIGHT+40
}
ldsf: //don't you like the names of my labels?
asm{
mov cx,(WIDTH+40)/2
rep movsw
add di,320-WIDTH-40
dec dx
jnz ldsf
}
}while(!kbhit()); //continue until someone hits the keybored
fade_to_white(pal); //fade the screen up to white
fade_to_black(pal); //the fade it out to black
setmode(3); //finally get back into textmode
}
ONLYFLAG.C
/************************************************************************/
/* */
/* Sourcecode for part 2 in Abe's demoschool */
/* (Only the flag, no text) */
/* */
/* This program draws a flag on the screen and moves it using a */
/* precalculated sinus-table. */
/* */
/* The program is pretty messy in main with lots of inline */
/* assembly. */
/* */
/* The theory behind it all is found in demo2.txt */
/* Compile onlyflag.c whith: */
/* */
/* bcc -1 onlyflag.c Borland c */
/* tcc -1 onlyflag.c Turbo c */
/* */
/* -1 means that 286-instructions is allowed. */
/* If you use another compiler remember to allow 286-instructions. */
/* */
/* 1996-01-04 VéSTERèS */
/* Albert Veli */
/* mail: dat94avi@idt.mdh.se */
/* */
/* */
/* PS If the flag suffers from severe slowness, try */
/* decreasing the width & height of the flag DS */
/* */
/************************************************************************/
#include<conio.h>
#include<math.h>
//Try to change the first 5 #define values and see what happens
#define WIDTH 180 //Change to acceptable size (less if slow computer)
#define HEIGHT 100 //The height should be about half of the width
#define FASE 2 //change between 1..6
#define WAVEX 2 // 1..4
#define WAVEY 3 // 1..4
#define PI 3.14159 // Don't change these
#define KX FASE*2*PI/WIDTH
#define BLUE 127-20
#define GULT 63-20
#define VITT 255-20
#define RED 191-20
//Waits for the electron beam to finish current screen (vertical retrace)
void wtsync(void)
{
asm mov dx,3DAh
WaitVR1:
asm in al,dx
asm test al,8
asm jne WaitVR1
WaitVR2:
asm in al,dx
asm test al,8
asm je WaitVR2
}
//makes the palette pal the active palette
void setpal(unsigned char*pal)
{
int i;
outp(0x3c8,0);
for(i=0;i<256*3;i++)
outp(0x3c9,pal[i]);
}
//sets all colors to black
void blackpal(void)
{
int i;
outp(0x3c8,0);
for(i=0;i<256*3;i++) outp(0x3c9,0);
}
//fades the palette in from black
void fade_in(unsigned char*palett)
{
int i,j;
unsigned char pal[256*3];
for(i=0;i<256*3;i++) pal[i]=0;
for(j=0;j<64;j++)
{
outp(0x3c8,0);
wtsync();
wtsync();
for(i=0;i<256*3;i++)
{
outp(0x3c9,pal[i]);
if(pal[i]<palett[i]) pal[i]++;
}
}
}
//fades the palette out to black
void fade_to_black(char*pal)
{
int i,j;
for(j=0;j<64;j++)
{
outp(0x3c8,0);
wtsync();
for(i=0;i<256*3;i++)
{
outp(0x3c9,pal[i]);
if(pal[i]>0) pal[i]--;
}
}
} // (fade to black is btw a great song by Metallica)
//fades the palette to white
//both fade_to_black and fade_to_white destroys the array pal
//if you want to save pal just make a local copy of it in the function
//and leave the original alone
void fade_to_white(char*pal)
{
int i,j;
for(j=0;j<64;j++)
{
outp(0x3c8,0);
if(i%2==0)wtsync(); //let the fade to white be pretty fast
//only wait everyother frame
for(i=0;i<256*3;i++)
{
outp(0x3c9,pal[i]);
if(pal[i]<63) pal[i]++;
}
}
}
//calculates a palette and puts it in the array pal
void coolpal(char*pal)
{
int i,r=0,g=0,b=0,col=0;
for(i=0;i<64;i++) // BLACK - YELLOW
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(r<63)r++;
if(g<63)g++;
}
r=0;
g=0;
for(i=0;i<64;i++) // BLACK - BLUE
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(b<63)b++;
}
b=0;
for(i=0;i<64;i++) // BLACK - RED
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(r<63)r++;
}
r=0;
for(i=0;i<64;i++) // BLACK - WHITE
{
pal[col++]=r;
pal[col++]=g;
pal[col++]=b;
if(r<63)r++;
if(g<63)g++;
if(b<63)b++;
}
setpal(pal); // activate the palette pal
}
//puts a pixel at (x,y) with color col
void putpixel(int x, int y, char col)
{
asm mov ax,0a000h
asm mov es,ax
asm mov bx,y //bx = y
asm mov di,bx //di = y
asm xchg bh,bl //bx = 256*y
asm shl di,6 //di = 64*y
asm add di,bx //di = (256 + 64)*y = 320*y
asm add di,x //di = 320*y + x
asm mov al, col //now es:di points where the pixel shall be drawn
asm mov [es:di],al //draw the pixel
}
//Draws the flag on the screen, very slow but it doesn't matter
//because it will only be done once in the beginning of the program
//(px,py) is the upper left corner of the screen where the flag will be drawn
void drawflag(char*flag,int px,int py)
{
int x,y;
for(x=0;x<WIDTH;x++)
{
for(y=0;y<HEIGHT;y++) putpixel(px+x,py+y,flag[WIDTH*y+x]);
}
}
//sets a graphic mode, the mode is sent as parameter to the function
void setmode(int mode)
{
asm mov ax,mode
asm int 10h
}
//creates a tricolor, like the french flag
void trikolor(char*flag)
{
int x,y;
for(x=0;x<WIDTH;x++)
{
for(y=0;y<HEIGHT/3;y++) flag[WIDTH*y+x]=BLUE;
}
for(x=0;x<WIDTH;x++)
{
for(y=HEIGHT/3;y<HEIGHT;y++) flag[WIDTH*y+x]=VITT;
}
for(x=0;x<WIDTH;x++)
{
for(y=2*HEIGHT/3;y<HEIGHT;y++) flag[WIDTH*y+x]=RED;
}
}
//Creates a swedish flag (Blue with yellow cross)
//To change to finnish (White with Blue cross) or some other cross flag
//you only have to change the colors
//(svenskflag means swedish flag in swedish)
void svenskflag(char*flag)
{
int x,y;
for(x=0;x<WIDTH/4+WIDTH/40;x++)
{
for(y=0;y<HEIGHT;y++) flag[WIDTH*y+x]=BLUE;
}
for(x=WIDTH/4+WIDTH/40;x<WIDTH/3+WIDTH/15;x++)
{
for(y=0;y<HEIGHT;y++) flag[WIDTH*y+x]=GULT;
}
for(x=WIDTH/3+WIDTH/15;x<WIDTH;x++)
{
for(y=0;y<HEIGHT;y++) flag[WIDTH*y+x]=BLUE;
}
for(x=0;x<WIDTH;x++)
{
for(y=HEIGHT/2-HEIGHT/9;y<HEIGHT/2+HEIGHT/9;y++) flag[WIDTH*y+x]=GULT;
}
}
/*
All three-colored flags like the french and all cross flags like the swedish
is very easy to create with small changes.
It will be more difficult to create an English or American flag because of
their geometries. But it is possible since none of them has more than four
colors and this program copes with ANY picture that consists of four colors
or less, the problem is to define the pattern of the flag. It could be done
in a separate file. . .
The palette is divided into four parts (0-63) (64-127) (128-191) (192-255).
(Watch the defines at the top of this code).
Each part has their own run from black to the color itself.
The color varies +20 to -20 colors, that means that
color 1 should be 63 - 20 = 43
color 2 should be 127 - 20 = 107
color 3 should be 191 - 20 = 171
color 4 should be 255 - 20 = 235
Then color 1 will vary between 23 and 63. This leaves colors 0-22 unused
in each interval.
But with all our knowledge we could easy use them too but I don't think it's
urgent. For now we have enough colors.
*/
/*
Main is pretty badly written, I know that I should have divided it
up into several smaller functions but I didn't because It's workin fine
right now and I'm very, very sleepy (yaaawwn).
*/
void main(void)
{
int x,y,px,py,py1,foo,bar,doo,index,adrflag;
int sinlist[4*WIDTH];
char pal[256*3];
char flag[(WIDTH)*HEIGHT];
char virt[(WIDTH+40)*(HEIGHT+40)];
asm lea ax,flag //this isn't really necessary but I couldn't
asm mov adrflag,ax //make it compile any other way
//the offset to the array flag is saved in the int adrflag
setmode(0x0013); //get into mode 13h
//calculate the sinus values and save them in the array sinlist
//this way we don't have to calculate any sines in the main loop
for(x=0;x<WIDTH;x++)
{
sinlist[x]=0; //let the flag be still at first
sinlist[WIDTH+x]=sin(KX*x)*20;//then let it vary in sine waves
sinlist[2*WIDTH+x]=sinlist[WIDTH+x];
sinlist[3*WIDTH+x]=sinlist[WIDTH+x];
}
coolpal(pal); //setup a palette with smooth colorruns
svenskflag(flag); //draw a swedish flag
//trikolor(flag); //or a french flag, you choose
blackpal(); //set all colors to black
drawflag(flag,(320-WIDTH)/2,(200-HEIGHT)/2);//draw the flag the first time
fade_in(pal); //fade it in (Ooooh)
//You can comment out the blackpal and fade_in rows if you get tired of
//waiting for the fades everytime you run the program
index=0; //index to the sinlist
//by changing the index you get the flag moving
//This is the main loop it contains some inline assembly
//If you want you could cut out the inline assembly stuff and put
//it in separate functions.
do
{
//first erase the whole virt array
asm{ push ds
pop es //es = ds = datasegment
lea di,virt
mov cx,((WIDTH+40)*(HEIGHT+40))/2
xor ax,ax
rep stosw
lea si,flag //point ds:si to the array flag
mov doo,si //doo is the offset to the array flag
lea di,virt //es:di points to virt
add di,(WIDTH+40)*20+20 //start at 20,20 in virt (leave room for movement)
mov bar,di //bar = the offset to virt
} //foo and bar are temporary variables that points to the arrays
//flag and virt
//it's common to call temporary variables foo, bar and doo
//(already on the ZX-Spectrum time they did so)
//Copy the data from virt over to flag, at the same time move the vertical
//rows of the flag foo pixels in the y-direction.
//foo, py and py1 are calculated from the sinlist.
//foo depends on index and the x-position during the loop
//changing index makes the flag move from frame to frame
//at the same time I change the color also depending on the index and x-position
//making the color change with the waves in the x-direction
//which enhances the feeling of waveiness
for(x=0;x<WIDTH;x++)
{
py=sinlist[index+x]; //py=value to add to the color
py1=sinlist[index+x+WIDTH/(2*FASE)]; //make the y-waves move in fase with the colorwaves
foo=(WIDTH+40)*(py1>>WAVEY); //foo = the change in the y-direction
_DI=bar+x+foo; //di = the position in virt
_SI=doo+x; //si = the position in flag
asm mov cx,HEIGHT //cx = the number of pixels to move
asm mov bx,py //bx = the change in color
ape:
asm lodsb
asm add ax,bx //ax = color + change in color
asm stosb
asm add si,(WIDTH-1)
asm add di,(WIDTH+40-1)
asm loop ape
}
//Now the flag and color is in waves in the y-direction
//Now we'll make it wavey in the x-direction too
//That's done completely in virt
//When it's done virt is flipped over to the visible screen
asm lea si,virt //let es:di & ds:si point to virt
asm add si,20
asm push ds
asm pop es
asm mov di,si
for(y=0;y<HEIGHT+40;y++) //loop trough all rows
{
px=sinlist[index+y]>>WAVEX; //px = number of pixels to move the row
//px can be positive or negative
if(px<0) //if px is negative
{
asm cld //move from left to right
asm add di,px //px is negative so make si go first
asm mov cx,(WIDTH+10)/2 //and di follow
asm rep movsw
asm add si,30
asm mov di,si
}
if(px>0) //if px is positive move from right to left
{
asm std
asm add si,WIDTH //si first and di follow
asm mov di,si //the pixels will be copied from ds:si
asm add di,px //to es:di so si must be first or the
asm mov cx,(WIDTH+10)/2 //flag will be destroyed
asm rep movsw //move one complete row two pixels at a time
asm add si,WIDTH+50 //move to next row
asm mov di,si
asm cld
}
if(px==0)
{
asm add si,WIDTH+40 //if px is 0 dont move anything
asm mov di,si //just go on to the next row
}
}
index++; //changing index makes the flag move
if(index==2*WIDTH) index=WIDTH; //if index is at the end of the sinlist
//move it to the beginning of the waves
//in the sinlist
//the waves begin at sinlist[WIDTH]
//the absolute beginning of sinlist
//was all 0 remember?
wtsync(); //syncronize with the electronbeam before flipping
//virt over to the visible screen
//Flip the contents of virt over to the visible screen
//di = upper left corner on the screen where the flag will be drawn
asm{ mov ax,0a000h
mov es,ax
mov di,(320-WIDTH-40)/2+160*(200-HEIGHT-40)
//This formula is supposed to work
//on different sizes of the flag
//you could also write a constant value directly to di here
lea si,virt
mov dx,HEIGHT+40
}
ldsf: //don't you like the names of my labels?
asm{
mov cx,(WIDTH+40)/2
rep movsw
add di,320-WIDTH-40
dec dx
jnz ldsf
}
}while(!kbhit()); //continue until someone hits the keybored
fade_to_white(pal); //fade the screen up to white
fade_to_black(pal); //the fade it out to black
setmode(3); //finally get back into textmode
}