Eyecandy: Turn your computer into an expensive lava lamp.

Ooze

Initial Release Date
July 6, 1989
Color Palette
8/18b
Max Resolution
320x200
License Status
Public Domain
Codebase
C
Platform(s)
MS-DOS
Author(s)
Jeff Clough

Description

Ooze performs color cycling on a input image. If no image is specified it will generate a plasma fractal, and color cycle that.

This program is sort of a lava-lamp for your computer.

Ooze is based on a similar, earlier, plasma fractal program, Plasma, written in Turbo Pascal by Bret Bulvey. Ooze clean things up in a number of ways, including: correcting flickering, expanding the palette entries used, allowing saving and loading of patterns, and enabling the reversal of the color cycling direction.

Though a few non-plasma sample images are provided with the program, the file format it uses seems to be non-standard, making it hard to produce custom images. One can save out the plasma fractals generated by Ooze though, by renaming the ooze.ooz generated after each run. Of the sample images provided, the most interesting is probably, corners.ooz. This at first seemed to be sort sort of alternate Sirpinski's carpet, but Jeff thinks it was simply a bug with the subdivsion logic of this plasma fractal generator that he preserved.

Video

Screenshots

ScreenshotScreenshotScreenshotScreenshot

Downloads

Documentation

Ooze.c
/* This program is sort of a lava-lamp for your computer. It requires
   VGA equipment or anything else that is compatible with mode 13h of
   the VGA (320x200x256 mode). To run the program, execute the command

     ooze

   The first time this is run, it will generate a random, amorphous
   image on the screen and will store this image into the file ooze.ooz.
   The image on the screen will then begin to "ooze" all over the
   screen. To change the direction of oozing, press a key (like the
   space bar). To stop the program and return to DOS, press the Escape
   key.
 
   The next time ooze is run, it will read the file ooze.ooz and
   proceede directly to ooze the image stored there. Ooze.ooz is mearly
   the default filename. Any other filename may be specified on the
   command line. If, for instance, the command
 
     ooze image1.ooz

   is issued, the file image1.ooz will be read from disk (if it is
   present) and the image from this file will be oozed. If image1.ooze
   is not found, it will be created in the same manner as ooze.ooz was
   created above. But the image created will be different for every new
   ooze image file created. You could build a whole library of differnet
   ooze files!

   ---------------------- For Programmers Only -------------------------

   This program was translated to Turbo C from Turbo Pascal. The
   original TP code was written and copyrighted (1988) by Bret Bulvey
   [71330,3567] and can be found in various places as PLASMA.ARC (or
   .ZIP depending on where you get it). The translation to TC was done
   by me (Jeff Clough [71330,2227]) and includes improvements and
   enhancements over the TP code. These include the elimination of
   flicker, the use of secondary as well as primary colors in the color
   palette, the use of 252 colors instead of only the 191 colors used in
   PLASMA, the ability to reverse the direction in which the colors move
   on the screen while the program is executing, and being able to
   specify the name of the image file to use on the command line. Also,
   as a side-effect of the process by which flicker is avoided, OOZE
   should run at the same speed on all VGA equipment regardless of the
   speed of the computer. The only unique part of the original code
   presented here (aside from a few variable and type names) is the
   algorithm for generating the amorphous images. The algorithm has been
   modified (simplified, actually) so that it includes no floating
   point. The original algorithm employed the use of Manhattan distances
   to determine how far apart two pixels were. The code presented here
   employes its own square root function (using integer bisection) and
   uses this to take advantage of a little trick Pathagorus demonstrated
   a while back. It is hoped that this new method for computing
   distances will produce more rounded edges in the generated images.
   Also, there was provision in the original code for adjusting the
   "roughness of the image" at compile time. This has been removed
   (since it involved a floating point operation).

   To eliminate the flicker, it was necessary to implement parts of the
   program in assembly language. The code that follows is, therefore, a
   hybrid of Turbo C and Assembly. Turbo C 2.0 and TASM were used to
   compile/assemble this program. To recompile, do

     tcc -B -mc ooze

   This will cause TC to "compile" to assembly source code and then
   invoke TASM on that code. The -mc switch may optionally be changed to
   -ml or -mh for the large or huge memory models, but may not be
   changed to -mt, -ms, or -mm because the tiny, small, and medium
   memory models all use near data segments. The far data segments used
   by the other memory models are necessary because this program uses
   the fread and fwrite functions to read from and write to the video
   memory directly.

   If the identifier "dotest" is defined during compilation, a test
   pattern will be generated instead of the more usual amorphous images
   when the program is run. This test code was used during debugging to
   ensure that all colors were being represented on the screen. If you
   change any part of this source code, you may want to use this test
   pattern to verify that your changes meet your expectations of them.
   To tell the compiler to generate the test code, include the -Ddotest
   command line switch when you compile as follows:

     tcc -B -mc -Ddotest ooze

   When ooze.exe is run, the test pattern will be generated and stored
   into whatever file is passed to it on the command line or to ooze.ooz
   by default. Don't forget to recompile without the -Ddotest switch so
   that the program will once again generate amorphous images.
*/

#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
#error COMPILE WITH LARGE DATA MODEL.
#endif

#include <bios.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define VERSION "1.1"

typedef struct
{unsigned char r,g,b;
} colortype;

colortype p[256];

int direction=0; /* controls the direction of palette rotation */

void adjust(int x1,int y1,int x,int y,int x2,int y2);
int getpixel(int x,int y);
void putpixel(int x,int y,int c);
void rotatepalette(colortype p[]);
void setvgapalette(void *buffer,int first,int length);
void subdivide(int x1,int y1,int x2,int y2);
unsigned sqrt(long n);
void test(void);
void usage(void);

int main(int argc,char *argv[])
{FILE *file;
 char filename[129];
 union REGS reg;
 unsigned char r,g,b;
 int i,       /* holds the number of the current palette register */
     key,     /* holds the scancode of the last key pressed       */
     newooze; /* TRUE if a new .OOZ file is to be created         */

 newooze=0;
 if (argc>2)
  {usage();
   return 1;
  }
 if (argc>1)
  {if (stricmp(argv[1],"?")==0 ||
       stricmp(argv[1],"-H")==0 ||
       stricmp(argv[1],"/H")==0)
    {usage();
     return 1;
    }
   strcpy(filename,argv[1]);
  }
 else
   strcpy(filename,"ooze.ooz");
 file=fopen(filename,"rb");
 if (!file)
  {file=fopen(filename,"w+b");
   newooze=1;
  }
 if (!file)
  {printf("%s cannot be found or created.\n\n",filename);
   usage();
   return 2;
  }

 /* initialize palette register array */
 r=53;
 g=11;
 b=53;
 for(i=1;i<=42;i++) /* from magenta to red */
  {p[i].r=r;
   p[i].g=g;
   p[i].b=b--;
  }
 for(;i<=84;i++) /* from red to yellow */
  {p[i].r=r;
   p[i].g=++g;
   p[i].b=b;
  }
 for(;i<=126;i++) /* from yellow to green */
  {p[i].r=r--;
   p[i].g=g;
   p[i].b=b;
  }
 for(;i<=168;i++) /* from green to cyan */
  {p[i].r=r;
   p[i].g=g;
   p[i].b=++b;
  }
 for(;i<=210;i++) /* from cyan to blue */
  {p[i].r=r;
   p[i].g=g--;
   p[i].b=b;
  }
 for(;i<=252;i++) /* from blue to magenta */
  {p[i].r=++r;
   p[i].g=g;
   p[i].b=b;
  }

 /* put VGA into mode 0x13 (320x200x256) */
 reg.x.ax=0x13;
 int86(0x10,&reg,&reg);

 setvgapalette(p,0,128);
 setvgapalette(&p[128],128,128);

 if (newooze)
  {
#if defined(dotest)
   test();
#else
   srand(peek(0x40,0x6c));
   putpixel(0,0,random(252)+1);
   putpixel(319,0,random(252)+1);
   putpixel(319,199,random(252)+1);
   putpixel(0,199,random(252)+1);
   subdivide(0,0,319,199);
#endif
   fwrite(MK_FP(0xa000,0),1,0xfa00,file);
  }
 else
   fread(MK_FP(0xa000,0),1,0xfa00,file);
 fclose(file);

 /* rotate the palette until the [Esc] key is pressed */
 do
  {rotatepalette(p);
   if (bioskey(1))
    {key=bioskey(0)>>8;
     direction=1-direction;
    }
  }
 while(key!=1);

 /* put VGA into mode 3 (color text mode (80x25)) */
 reg.x.ax=3;
 int86(0x10,&reg,&reg);

 return 0;
} /* end of int main(argc,argv[]) */

void adjust(int x1,int y1,int x,int y,int x2,int y2)
{int c,d;
 long horz,vert;
 if (getpixel(x,y))
   return;
 horz=(long)(x2-x1);
 vert=(long)(y2-y1);
 d=sqrt(horz*horz+vert*vert);
 if (random(2))
   c=((getpixel(x1,y1)+getpixel(x2,y2))/2-random(d)) % 252;
 else
   c=((getpixel(x1,y1)+getpixel(x2,y2))/2+random(d)) % 252;
 if (c<0)
   c=-c;
 else
   if (!c)
     c=1;
 putpixel(x,y,abs(c));
} /* end of adjust(x1,y1,x,y,x2,y2) */

int getpixel(int x,int y)
{return(peekb(0xa000,320*y+x) & 0xff);
} /* end of int getpixel(x,y) */

void putpixel(int x,int y,int c)
{pokeb(0xa000,320*y+x,c);
} /* end of putpixel(x,y,c) */

void rotatepalette(colortype p[])
{colortype temp;
 if (direction)
  {memmove(&temp,p+252,sizeof(colortype));
   memmove(p+2,p+1,251*sizeof(colortype));
   memmove(p+1,&temp,sizeof(colortype));
  }
 else
  {memmove(&temp,p+1,sizeof(colortype));
   memmove(p+1,p+2,251*sizeof(colortype));
   memmove(p+252,&temp,sizeof(colortype));
  }
 setvgapalette(p,0,128);
 setvgapalette(p+128,128,128);
} /* end of rotatepalette(p) */

void setvgapalette(void *buffer,int first,int length)
{asm cli        /* Disable interrupts. */
 asm mov dx,3dah
setvgapalette1: /* Wait for vertical retrace to end. */
 asm in al,dx
 asm test al,8
 asm jnz setvgapalette1
setvgapalette2: /* Wait for vertical retrace to start. */
 asm in al,dx
 asm test al,8
 asm jz setvgapalette2
 /* Set the VGA palette registers. */
 asm push ds
 _DS=FP_SEG(buffer);
 _SI=FP_OFF(buffer);
 asm mov dx,3c8h   /* port address of DAC address register.                   */
 asm mov ax,first  /* this is the number of first DAC register to update.     */
 asm out dx,al     /* start with this DAC.                                    */
 asm inc dx        /* 3c9h is the port address where the RGB info is written. */
 asm mov cx,length /* this is the number of DAC registers to update.          */
setdacloop:
 asm mov al,[si]
 asm out dx,al
 asm inc si
 asm mov al,[si]
 asm out dx,al
 asm inc si
 asm mov al,[si]
 asm out dx,al
 asm inc si
 asm loop setdacloop
 asm pop ds
 asm sti        /* Enable interrupts. */
} /* end of setvgapalette(buffer,first,length) */

void subdivide(int x1,int y1,int x2,int y2)
{int c,x,y;
 if (bioskey(1))
   return;
 if (x2-x1<2 && y2-y1<2)
   return;
 x=(x1+x2)/2;
 y=(y1+y2)/2;
 adjust(x1,y1,x,y1,x2,y1);
 adjust(x2,y1,x2,y,x2,y2);
 adjust(x1,y2,x,y2,x2,y2);
 adjust(x1,y1,x1,y,x1,y2);
 if (getpixel(x,y)==0)
  {c=(getpixel(x1,y1)+getpixel(x2,y1)+getpixel(x2,y2)+getpixel(x1,y2))/4;
   if (c<1)
     c=1;
   else
     if (c>252)
       c=252;
   putpixel(x,y,c);
  }
 subdivide(x1,y1,x,y);
 subdivide(x,y1,x2,y);
 subdivide(x,y,x2,y2);
 subdivide(x1,y,x,y2);
} /* end of subdivide(x1,y1,x2,y2) */

/* Use bisection to find the root of y=x*x-n.
   Return the value of x.                     */
unsigned sqrt(long n)
{long xl,x,xh,y;
 if (n<0)
   n=-n;
 xl=0L;      /* (xl,xh) is the range of y=x*x-n */
 xh=46340L;
 while(xl<xh-1L)
  {x=(xl+xh)/2;
   y=x*x;
   if (y<n)
     xl=x;
   else
     xh=x;
  }
 if (n-xl*xl < xh*xh-n)
   return((int)xl);
 else
   return((int)xh);
} /* end of unsigned sqrt(n) */

#if defined(dotest)

void test(void)
{int x,y,c;
 long h,v;
 for(x=0;x<320;x++)
   for(y=0;y<200;y++)
    {h=(long)(160-x);
     v=(long)(199-y);
     c=sqrt(h*h+v*v) % 252 + 1;
     putpixel(x,y,c);
    }
} /* end of test() */

#endif

void usage(void)
{printf("OOZE version %s compiled %s\n\n"
        "usage: OOZE [filename]\n"
        "where filename is the name of a .OOZ file.\n\n"
        "If the file does not exist, it will be created.\n"
        "If it already exists, the image in it will be oozed.\n"
        "If the filename parameter is not given, a default\n"
        "filename of OOZE.OOZ is assumed.\n",VERSION,__DATE__);
} /* end of usage() */
Output
OOZE version 1.1 compiled Jul 06 1989

usage: OOZE [filename]
where filename is the name of a .OOZ file.

If the file does not exist, it will be created.
If it already exists, the image in it will be oozed.
If the filename parameter is not given, a default
filename of OOZE.OOZ is assumed.
Plasma.pas
{ Turbo Pascal 4.0 source code }
{$I-}
program plasma;

  uses
    Crt,Dos;

  const
    F = 2.0; { the "roughness" of the image }

  type
    ColorValue = record Rvalue,Gvalue,Bvalue: byte; end;
    PaletteType = array [0..255] of ColorValue;

  var
    ch: char;
    i: integer;
    p: PaletteType;
    image: file;
    ok: boolean;

  procedure SetVGApalette(var tp: PaletteType);
    var regs: Registers;
  begin { procedure SetVGApalette }
    with regs do
      begin
        AX:=$1012;
        BX:=0; { first register to set }
        CX:=256; { number of registers to set }
        ES:=Seg(tp); DX:=Ofs(tp);
      end;
    Intr($10,regs);
  end; { procedure SetVGApalette }

  procedure PutPixel(x,y: integer; c: byte);
  begin { procedure PutPixel }
    mem[$A000:word(320*y+x)]:=c;
  end; { procedure PutPixel }

  function GetPixel(x,y: integer): byte;
  begin { function GetPixel }
    GetPixel:=mem[$A000:word(320*y+x)];
  end; { function GetPixel }

  procedure adjust(xa,ya,x,y,xb,yb: integer);
    var
      d: integer;
      v: real;
  begin { procedure adjust }
    if GetPixel(x,y)<>0 then exit;
    d:=Abs(xa-xb)+Abs(ya-yb);
    v:=(GetPixel(xa,ya)+GetPixel(xb,yb))/2+(random-0.5)*d*F;
    if v<1 then v:=1;
    if v>=193 then v:=192;
    PutPixel(x,y,Trunc(v));
  end; { procedure adjust }

  procedure subDivide(x1,y1,x2,y2: integer);
    var
      x,y: integer;
      v: real;
  begin { procedure subDivide }
    if KeyPressed then exit;
    if (x2-x1<2) and (y2-y1<2) then exit;

    x:=(x1+x2) div 2;
    y:=(y1+y2) div 2;

    adjust(x1,y1,x,y1,x2,y1);
    adjust(x2,y1,x2,y,x2,y2);
    adjust(x1,y2,x,y2,x2,y2);
    adjust(x1,y1,x1,y,x1,y2);

    if GetPixel(x,y)=0 then
      begin
        v:=(GetPixel(x1,y1)+GetPixel(x2,y1)+GetPixel(x2,y2)+GetPixel(x1,y2))/4;
        PutPixel(x,y,Trunc(v));
      end;

    subDivide(x1,y1,x,y);
    subDivide(x,y1,x2,y);
    subDivide(x,y,x2,y2);
    subDivide(x1,y,x,y2);
  end; { procedure subDivide }

  procedure rotatePalette(var p: PaletteType; n1,n2,d: integer);
    var
      q: PaletteType;
  begin { procedure rotatePalette }
    q:=p;
    for i:=n1 to n2 do
      p[i]:=q[n1+(i+d) mod (n2-n1+1)];
    SetVGApalette(p);
  end; { procedure rotatePalette }

begin
  Inline($B8/$13/0/$CD/$10); { select video mode 13h (320x200 with 256 colors) }

  with p[0] do               { set background palette entry to grey }
    begin
      Rvalue:=32;
      Gvalue:=32;
      Bvalue:=32;
    end;

  for i:=0 to 63 do { create the color wheel }
    begin
      with p[i+1] do begin Rvalue:=i; Gvalue:=63-i; Bvalue:=0; end;
      with p[i+65] do begin Rvalue:=63-i; Gvalue:=0; Bvalue:=i; end;
      with p[i+129] do begin Rvalue:=0; Gvalue:=i; Bvalue:=63-i; end;
    end;

  SetVGApalette(p);

  Assign(image,'PLASMA.IMG');
  Reset(image,1);
  ok:=(ioResult=0);

  if not ok or (ParamCount<>0) then { create a new image }
    begin
      Randomize;

      PutPixel(0,0,1+Random(192));
      PutPixel(319,0,1+Random(192));
      PutPixel(319,199,1+Random(192));
      PutPixel(0,199,1+Random(192));

      subDivide(0,0,319,199);

      Rewrite(image,1);
      BlockWrite(image,mem[$A000:0],$FA00);
    end
  else { use the previous image }
    BlockRead(image,mem[$A000:0],$FA00);

  Close(image);

  repeat
    rotatePalette(p,1,192,+1);
  until KeyPressed;

  ch:=ReadKey; if ch=#0 then ch:=ReadKey;

  TextMode(LastMode);
end.

External References

Discussion