
Scalar's GP-TRICKS Homepage
| Site Map | Tricks Section | How To Do a Shadebob |
|---|
|
|
How To Do a Shadebob
A shadebob is simply a brush of any shape that moves across the screen
on a pre-defined path, all the while increasing the intensity levels of
the primary colors. The standard shadebob increases all three of them
equally, thus leaving a trace that is initially black, but soon changes
to gray and later white.
The brush is a bitmap whose pixels define the intensity of change at the
specific location. A pixel equal to zero does no change and is therefore
transparent. A common shape for a shadebob is a simple circle with a
radial gradient from 0 on the outside to, say, 20 on the inside. When
you paint with this brush, the color on the inside of the streak changes
more quickly than on the outside, giving the drawing a smooth look.
If you apply the brush to one location several times, the changes
accumulate, increasing the color's brightness more and more: the classic
"gray" shadebob, for example, will eventually end up as white. The
data structure for storing a bitmap like this is really simple -- a two-
dimensional array like the TIntensityBitmap array in Listing 1.
Once you have your brush bitmap, either stored on disk and loaded later,
or calculated at run-time, you need to define the path the brush will
travel. It is easiest to simply store the x and y position of the location
where to use the brush next. A structure for storing this might look like
the TPosition record and its associated array structure:
| Listing 1 |
| Borland Pascal 7.0 |
type TIntensityBitmap = array[1..21,1..21] of byte
TPosition = record
x,y : word;
end;
TPosArray = array[1..16000] of TPosition
|
There are, of course, several ways to create the data for the motion of
the shadebob. One is to use mathematical functions to generate it, the
sine/cosine functions, for example. The method I will use here is to
write an editor for the shadebob: You move the mouse along the desired
path, and the editor writes the mouse positions to a file. This file
is then read by the shadebob player and played at a certain speed.
Normally, you will not want to save any timing information inside the
data file since the shadebob's motion should be smooth and continuous.
On the other hand, if you can achieve a neat effect with it, go for it!
The function to record the shadebob's path is really simple: It only
executes a repeat-until loop until a key is pressed; then it saves the
position data. Inside the loop, the program always compares the new
mouse position to the position where the last brush was applied to, and
only if they are different, the brush is applied to the new location.
If this were not done, the brush would be drawn at the same spot multiple
times over only a short timespan, thus making it bright very quickly.
The player function is even simpler: It just loads the data and cycles
through the positions at full speed.
This example uses a primitive brush shape (rectangular) and a simple
grayscale palette. You might achieve a better effect using a colored
palette (like black-red-black-blue-black, or so) and an image as a brush.
You could even try to let it "spray" text onto the screen. The possibilities are endless.
Listing 2 features a shadebob recorder and player program. If you want
to record a shadebob path, pass the name of the file that should store the
data as first parameter, and supply a second dummy parameter (just plain anything).
Now the recorder will load and you can use the mouse to paint the path. Press
a keyboard key to save and exit.
To play a file that you have recorded this way, only specify the file name
(no dummy parameter). Press a key to exit the player.
| Listing 2 |
| Borland Pascal 7.0 |
program ShadeBobs;
uses Crt,Dos;
type TIntensityBitmap = array[1..21,1..21] of byte;
TPosition = record x,y:word; end;
TPosArray = array[1..16000] of TPosition;
var regs : Registers;
brush : TIntensityBitmap;
posarray : ^TPosArray;
arraypos,arraymax : word;
posfile : file;
newx,newy : integer;
oldx,oldy : integer;
(*---- VGA and mouse routines ----*)
procedure SetCol(nr,r,g,b:byte); { set RGB values of color "nr" }
begin
port[$3C8]:=nr; port[$3C9]:=r; port[$3C9]:=g; port[$3C9]:=b;
end;
function GetPix(x,y:word) : byte; { get color of pixel at x;y }
begin
if (x>=320) or (y>=200) then exit;
GetPix:=Mem[SegA000:y*320+x];
end;
procedure SetPix(x,y:word; c:byte); { set color of pixel at x;y }
begin
if (x>=320) or (y>=200) then exit;
Mem[SegA000:y*320+x]:=c;
end;
procedure InitVGA; { initialize VGA }
begin
asm
mov ax,0013h
int 10h { set mode 13h: 320x200/256 }
end;
end;
procedure InitMouse; { initialize mouse }
begin
regs.ax:=$0000; intr($33,regs); { Initialize }
regs.ax:=$0002; intr($33,regs); { Hide mouse cursor }
regs.cx:=320; regs.dx:=100;
regs.ax:=$0004; intr($33,regs); { Center mouse }
end;
function MouseX : word; { get X position of mouse }
begin
regs.ax:=$0003; intr($33,regs); MouseX:=regs.cx shr 1;
end;
function MouseY : word; { get Y position of mouse }
begin
regs.ax:=$0003; intr($33,regs); MouseY:=regs.dx;
end;
(*--------------------------------*)
(*------ Shadebob routines -------*)
procedure InitBrush; { initialize brush }
var a,b,c : byte;
begin
{ make a rectangular gradient with 23 in the middle and }
{ 1 on the outside }
for c:=0 to 10 do
for b:=1+c to 21-c do
for a:=1+c to 21-c do brush[a,b]:=1+c*2;
brush[11,11]:=23;
end;
procedure GeneratePal; { generate smooth palette }
var a,b : byte;
begin
{ make a grayscale palette from 0;0;0 to 63;63;63 }
for a:=0 to 255 do
begin
b:=round(a/255*63);
SetCol(a,b,b,b);
end;
end;
procedure DrawShadeBob(xp,yp:word); { draw shadebob at xp;yp }
var a,b,c : word;
begin
{ cycle through all the pixels of the shadebob }
for b:=1 to 21 do
for a:=1 to 21 do
begin
{ get old color of the pixel }
c:=GetPix(xp+a-11,yp+b-11);
{ add the brush intensity; do not let it go above 255 }
c:=c+brush[a,b]; if c>255 then c:=255;
{ draw pixel with new color }
SetPix(xp+a-11,yp+b-11,c);
end;
end;
(*--------------------------------*)
(*--------- Main program ---------*)
procedure Init; { initialize }
begin
if ParamCount=0 then halt;
InitVGA; InitBrush; InitMouse;
ClrScr; GeneratePal;
New(posarray); arraypos:=0;
end;
procedure EditShadebob; { edit shadebob path }
begin
oldx:=MouseX; oldy:=MouseY;
repeat { repeat until keypressed... }
newx:=MouseX; newy:=MouseY; { get new mouse position }
{ was the mouse moved? }
if (newx<>oldx) or (newy<>oldy) then
begin
inc(arraypos); { next position in array }
{ if full, stop editing }
if arraypos>High(TPosArray) then break;
{ store mouse position in array }
posarray^[arraypos].x:=newx; posarray^[arraypos].y:=newy;
{ draw shadebob }
DrawShadeBob(newx,newy);
{ store mouse position as old one }
oldx:=newx; oldy:=newy;
end;
until keypressed;
{ save to file }
Assign(posfile,ParamStr(1)); Rewrite(posfile,1);
BlockWrite(posfile,arraypos,SizeOf(arraypos));
BlockWrite(posfile,posarray^,arraypos*SizeOf(TPosition));
Close(posfile);
{ free memory }
Dispose(posarray);
end;
procedure PlayShadebob; { play shadebob }
begin
{ load from file }
Assign(posfile,ParamStr(1)); Reset(posfile,1);
BlockRead(posfile,arraymax,SizeOf(arraymax));
BlockRead(posfile,posarray^,arraymax*SizeOf(TPosition));
Close(posfile);
repeat { repeat until keypressed... }
inc(arraypos); { next position in array }
{ repeat animation without end }
if arraypos=arraymax then arraypos:=1;
{ draw shadebob at saved position }
DrawShadeBob(posarray^[arraypos].x,posarray^[arraypos].y);
until keypressed;
readkey;
{ free memory }
Dispose(posarray);
end;
begin
Init;
if ParamCount>1 then EditShadebob else PlayShadebob;
TextMode(CO80);
end.
|
Back to Top of Page
|