GP-TRICKS Logo

Scalar's GP-TRICKS Homepage

Site MapTricks SectionHow 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


This article is © 1998-2008 by M. G. Ricken and was exclusively written for GP-Tricks.
Copyright © 1998-2008 by M.G.Ricken        E-Mail: Scalar@psynet.net     |     mgricken@gptricks.de