Contributor: TOMMY ANDERSEN

{
Hi, This is an easy made screensaver, viewing FLI files from
Autodesk animator, it's not optimized for reading FLC files, since
that would be a larger project, which i dont have enough spare-time
for now!

The "Treatframe" and "Getclock" routine was taken from Eirik Pedersens
fli player, found in snipet: "misc". I had to change Treatframe a litle
just to handle the palette. Use at your own risk.

There's not much documentation, but if there's so much you don't understand,
send me a mail, and i'll try to answer it as soon as possible!



Tommy Andersen
email: tommy.andersen@dialogue.telemax.no
snail: Tommy Andersen
       Andebuveien 11
       3170  SEM
       Norway
}


Program Fliplay;

Uses
  Forms,
  Unit1 in 'UNIT1.PAS' {Form1};

{$R *.RES}

Begin
   { Prevent multiple instances }
   IF HPrevinst <> 0 Then Exit;

   Application.CreateForm(TForm1, Form1);
   Application.Run;
End.

{ -------------  Cut out and save as UNIT1.PAS ----------------- }

Unit Unit1;

Interface

Uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs;

Const
  CLOCK_HZ              = 4608;                   { Frequency of clock }
  MONItoR_HZ            = 70;                     { Frequency of monitor }
  CLOCK_SCALE           = CLOCK_HZ div MONItoR_HZ;
  CDATA                 = $040;                   { Port number of timer 0 }
  CMODE                 = $043;                   { Port number of timers control Word }
  Scale_FLI             = False;                  { Set this to true if saver shall use whole screen }

Type
  Big_Buffer_Type = Array[0..65534] of Byte;
  FliHeaderType = Record
                     Size          : Longint;
                     Magic         : Word;
                     Frames        : Word;
                     Width         : Word;
                     Height        : Word;
                     Bitsperpixel  : Word;
                     Flags         : Integer;
                     Speed         : Integer;
                     Nexthead      : Longint;
                     Framesintable : Longint;
                     hfile         : Integer;
                     hframe1offset : Longint;
                     Strokes       : Longint;
                     Session       : Longint;
                     Reserved      : Array [1..88] of Byte;
                  End;
  FrameHeaderType = Record
                       Size   : LongInt;
                       Magic  : Word; { $F1FA }
                       Chunks : Word;
                       Expand : Array[1..8] of Byte;
                    End;


  TForm1 = Class(TForm)
    OpenDialog1: TOpenDialog;
    Procedure FormCreate(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  Private
    { Private declarations }
  Public
    { Public declarations }
    Start_Screensaver        : Boolean;
    MouseMovement            : Byte;
    Fli_Filename             : String;
    Screensaver_Ini_filename : String;
    ScreenBitmap             : TBitmap;


    Flifilestream            : TMemoryStream;
    FliScreenstream          : TMemoryStream;
    Screen_Buffer            : ^Big_Buffer_Type;
    File_Buffer              : ^Big_Buffer_Type;
    FLI_Header               : FLIHeaderType;
    FLI_FrameHeader          : FrameHeaderType;
    FLI_Speed                : Longint;
    FLI_Nexttime             : Longint;
    Fli_FrameNr              : Word;
    FLI_SecondPosition       : Longint;


    Procedure Get_INI_Filename;
    Procedure Read_INI_Settings;
    Procedure Write_INI_Settings;

    Procedure Create_Bitmap;
    Procedure Show_Next_Frame;
    Procedure Load_FLI_File;
    Procedure Kill_FLI_Screensaver;
  End;

Var
  Form1: TForm1;

Implementation

{$R *.DFM}

Uses Inifiles;


Function GetClock:LongInt; Assembler; {Taken from the FLILIB source}
{ this routine returns a clock With occassional spikes where time
  will look like its running backwards 1/18th of a second.  The resolution
  of the clock is 1/(18*256) = 1/4608 second.  66 ticks of this clock
  are supposed to be equal to a monitor 1/70 second tick.}
Asm
  mov  ah,0                                         { get tick count from Dos and use For hi 3 Bytes }
  int  01ah                                         { lo order count in DX, hi order in CX }
  mov  ah,dl
  mov  dl,dh
  mov  dh,cl

  mov  al,0                                         { read lo Byte straight from timer chip }
  out  CMODE,al                                         { latch count }
  mov  al,1
  out  CMODE,al                                         { set up to read count }
  in   al,CDATA                                         { read in lo Byte (and discard) }
  in   al,CDATA                                         { hi Byte into al }
  neg  al                                         { make it so counting up instead of down }
End;

Procedure TForm1.Get_INI_Filename;
Var
  Buffer : Array[0..255] of Char;
  Size   : Word;

Begin
   Size := GetSystemDirectory(Buffer, 256);
   IF Size <> 0 Then
   Begin
      Screensaver_Ini_filename := StrPas(Buffer);
      Screensaver_Ini_filename[0] := Chr(Size);
   End
    Else Screensaver_Ini_filename := 'C:\';



   { Make sure filename got the last expected slash }
   IF Screensaver_Ini_filename[Length(Screensaver_Ini_filename)] <> '\' Then
      Screensaver_Ini_filename := Screensaver_Ini_filename + '\';


   Screensaver_Ini_filename := Screensaver_Ini_filename + 'FLIPLAY.INI';
End;

Procedure TForm1.Write_INI_Settings;
Var
  Inifile : TInifile;

Begin
   Inifile := TInifile.Create(Screensaver_Ini_filename);
   Inifile.WriteString('FLI-Screensaver', 'Filename', Fli_Filename);
   Inifile.Free;
End;

Procedure TForm1.Read_INI_Settings;
Var
  Inifile : TInifile;

Begin
   Inifile := TInifile.Create(Screensaver_Ini_filename);
   Fli_Filename := Inifile.ReadString('FLI-Screensaver', 'Filename', '');
   Inifile.Free;
End;

Procedure TForm1.Load_FLI_File;
Var
  Temp : Word;

Begin
   Fli_FrameNr := 0;
   FliFileStream.Clear;

   IF FileExists(Fli_Filename) Then
   Begin
      Try
        FliFileStream.LoadFromFile(Fli_Filename);
      Except
        FliFileStream.Clear;
      End;

      IF (FliFileStream.Size > 128) Then
      Begin
         FliFileStream.Seek(0, 0);
         Temp := FliFileStream.Read(Fli_Header, 128);

         IF (Temp = 128) and (Fli_Header.Magic = $AF11) Then
         Begin
            { Ok }
            FLI_Speed := Fli_Header.Speed;
            FLI_Speed := FLI_Speed*CLOCK_SCALE;
            FLI_NextTime := 0;
         End
          Else FliFileStream.Clear;

      End;
   End;
End;

Procedure TForm1.Create_Bitmap;
Type
  BitmapHeader = Record
                    ID    : Word;
                    FSize : LongInt;
                    Ver   : LongInt;
                    Image : LongInt;
                    Misc  : LongInt;
                    Width : LongInt;
                    Height: LongInt;
                    Num   : Word;
                    Bits  : Word;
                    Comp  : LongInt;
                    ISize : LongInt;
                    XRes  : LongInt;
                    YRes  : LongInt;
                    PSize : LongInt;
                    Res   : LongInt;
                 End;

Var
  BmpHeader : BitmapHeader;
  T, myByte : Byte;
  MSize     : LongInt;


Begin
   FLIScreenStream.Clear;


   MSize := 64000;
   MSize := MSize + 1024;
   MSize := MSize + 54;

   BmpHeader.ID          := 19778;
   BmpHeader.FSize       := MSize;
   BmpHeader.Ver         := 0;
   BmpHeader.Image       := 54 + (256*4);
   BmpHeader.Misc        := 40;
   BmpHeader.Width       := 320;
   BmpHeader.Height      := 200;
   BmpHeader.Num         := 1;
   BmpHeader.Bits        := 8;
   BmpHeader.Comp        := bi_RGB;
   BmpHeader.ISize       := BmpHeader.FSize - BmpHeader.Image;
   BmpHeader.XRes        := 0;
   BmpHeader.YRes        := 0;
   BmpHeader.Res         := 0;

   FLIScreenStream.Write(BmpHeader.ID, 2);
   FLIScreenStream.Write(BmpHeader.FSize, 4);
   FLIScreenStream.Write(BmpHeader.Ver, 4);
   FLIScreenStream.Write(BmpHeader.Image, 4);
   FLIScreenStream.Write(BmpHeader.Misc, 4);
   FLIScreenStream.Write(BmpHeader.Width, 4);
   FLIScreenStream.Write(BmpHeader.Height, 4);
   FLIScreenStream.Write(BmpHeader.Num, 2);
   FLIScreenStream.Write(BmpHeader.Bits, 2);
   FLIScreenStream.Write(BmpHeader.Comp, 4);
   FLIScreenStream.Write(BmpHeader.ISize, 4);
   FLIScreenStream.Write(BmpHeader.XRes, 4);
   FLIScreenStream.Write(BmpHeader.YRes, 4);
   FLIScreenStream.Write(BmpHeader.Res, 4);


   FLIScreenStream.Seek(54, 0);
   { Create palette }
   For T := 0 To 255 do
   Begin
      { Blue }
      myByte := T;
      FLIScreenStream.Write(myByte, 1);

      { Green }
      FLIScreenStream.Write(myByte, 1);

      { Red }
      FLIScreenStream.Write(myByte, 1);

      myByte := 0;
      FLIScreenStream.Write(myByte, 1);
   End;


   FillChar(Screen_Buffer^, 64000, 0);
   FLIScreenStream.Write(Screen_Buffer^, 64000);
End;

Procedure TForm1.Kill_FLI_Screensaver;
Begin
   Freemem(Screen_Buffer, 64000);
   Freemem(File_Buffer, 65535);
   Flifilestream.Free;
   FliScreenstream.Free;
   ScreenBitmap.Free;
   Halt(0);
End;

Procedure TForm1.FormCreate(Sender: TObject);
Var
  Param : String;
  S     : String;

Begin
   Flifilestream    := TMemoryStream.Create;
   FliScreenstream  := TMemoryStream.Create;
   ScreenBitmap     := TBitmap.Create;


   Param := Uppercase( Paramstr(1) );
   Caption := 'FLI screensaver, made by Tommy Andersen!';
   Application.Title := Caption;


   Getmem(Screen_Buffer, 64000);
   Getmem(File_Buffer, 65535);


   Get_INI_Filename;
   Read_INI_Settings;


   { Config screensaver? }
   IF Param = '/C' Then
   Begin
      { Yes }
      Start_Screensaver := False;
      Windowstate := wsMinimized;


      S     := '';
      Param := FLI_Filename;
      While Pos('\', Param) > 0 do
      Begin
         S := S + Copy(Param, 1, Pos('\', Param));
         Delete(Param, 1, Pos('\', Param));
      End;
      Opendialog1.Initialdir := S;
      Opendialog1.Filename := Param;


      Opendialog1.Filter := 'FLI files|*.FLI|All files|*.*';
      IF Opendialog1.Execute Then
      Begin
         FLI_Filename := Opendialog1.Filename;
         Write_INI_Settings;
      End;


      Kill_FLI_Screensaver;
   End
    Else
     Begin
        { No! Start screensaver! }
        Create_Bitmap;
        Load_FLI_File;


        Start_Screensaver := True;
        Windowstate       := wsMaximized;
{
        Formstyle         := fsStayOnTop;
}
        Borderstyle       := bsNone;
        Color             := clBlack;
        MouseMovement     := 0;
     End;

End;

Procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y : Integer);
Begin
   IF MouseMovement > 2 Then Kill_FLI_Screensaver;
   Inc(MouseMovement);
End;

Procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
Begin
   Kill_FLI_Screensaver;
End;

Procedure TForm1.Show_Next_Frame;
Type
  Paltype = Array[0..767] of Byte;

Var
  Temp    : Word;
  Nextpos : Longint;
  Palette : ^Paltype;
  Paladdr : Word;

Procedure TreatFrame(Var Buffer, ScreenBuffer, Palette; Chunks:Word); Assembler;
{ this is the 'workhorse' routine that takes a frame and put it on the screen }
{ chunk by chunk }
Label
  Color_Loop, Copy_Bytes, Copy_Bytes2, Exit, Fli_Black, Fli_Brun, Fli_Color,
  Fli_Copy, Fli_Lc, Fli_Loop, Jump_Over, Line_Loop, Line_Loop2, Next_Line,
  Next_Line2, Pack_Loop, Pack_Loop2, C_Loop;

Asm
  Cli

  push ds
  lds  si,Buffer                                 { let DS:SI point at the frame to be drawn }

Fli_Loop:                                        { main loop that goes through all the chunks in a frame }
  cmp  Chunks,0                                  { are there any more chunks to draw? }
  je   Exit
  dec  Chunks                                    { decrement Chunks For the chunk to process now }

  mov  ax,[Word ptr ds:si+4]                     { let AX have the ChunkType }
  add  si,6                                      { skip the ChunkHeader }

  cmp  ax,0Bh                                    { is it a FLI_COLor chunk? }
  je   Fli_Color
  cmp  ax,0Ch                                    { is it a FLI_LC chunk? }
  je   Fli_Lc
  cmp  ax,0Dh                                    { is it a FLI_BLACK chunk? }
  je   Fli_Black
  cmp  ax,0Fh                                    { is it a FLI_BRUN chunk? }
  je   Fli_Brun
  cmp  ax,10h                                    { is it a FLI_COPY chunk? }
  je   Fli_Copy
  jmp  Fli_Loop                                  { This command should not be necessary since the Program should make one - }
                                                 { - of the other jumps }

Fli_Color:
  mov  bx,[Word ptr ds:si]                       { number of packets in this chunk (allways 1?) }
  add  si,2                                      { skip the NumberofPackets }
  mov  al,0                                      { start at color 0 }
  xor  cx,cx                                     { reset CX }

Color_Loop:
  or   bx,bx                                     { set flags }
  jz   Fli_Loop                                  { Exit if no more packages }
  dec  bx                                        { decrement NumberofPackages For the package to process now }

  mov  cl,[Byte ptr ds:si+0]                     { first Byte in packet tells how many colors to skip }
  add  al,cl                                     { add the skiped colors to the start to get the new start }

  mov  cl,[Byte ptr ds:si+1]                     { next Byte in packet tells how many colors to change }
  or   cl,cl                                     { set the flags }
  jnz  Jump_Over                                 { if NumberstoChange=0 then NumberstoChange=256 }
  inc  ch                                        { CH=1 and CL=0 => CX=256 }
Jump_Over:
  add  al,cl                                     { update the color to start at }
  mov  di,cx                                     { since each color is made of 3 Bytes (Red, Green & Blue) we have to - }
  shl  cx,1                                      { - multiply CX (the data counter) With 3 }
  add  cx,di                                     { - CX = old_CX shl 1 + old_CX   (the fastest way to multiply With 3) }
  add  si,2                                      { skip the NumberstoSkip and NumberstoChange Bytes }


  { Find start position }
  Les  di, Palette
  Mov  CL, AL
@LLL:
  Cmp  CL, 0
  Je   C_Loop
  Dec  CL
  Add  di, 3
  Jmp  @LLL

C_Loop:
  Cmp  CX, 0
  Je   Color_Loop
  Dec  CX
  Mov  AL, [Byte ptr DS:SI]
  Add  AL, AL
  Add  AL, AL
  Mov  [Byte ptr ES:DI], AL
  Inc  SI
  Inc  DI
  Jmp  C_Loop


Fli_Lc:
  Les  di, ScreenBuffer

  mov  di,[Word ptr ds:si+0]                     { put LinestoSkip into DI - }
  mov  ax,di                                     { - to get the offset address to this line we have to multiply With 320 - }
  shl  ax,8                                      { - DI = old_DI shl 8 + old_DI shl 6 - }
  shl  di,6                                      { - it is the same as DI = old_DI*256 + old_DI*64 = old_DI*320 - }
  add  di,ax                                     { - but this way is faster than a plain mul }
  mov  bx,[Word ptr ds:si+2]                     { put LinestoChange into BX }
  add  si,4                                      { skip the LinestoSkip and LinestoChange Words }
  xor  cx,cx                                     { reset cx }

Line_Loop:
  or   bx,bx                                     { set flags }
  jz  Fli_Loop                                   { Exit if no more lines to change }
  dec  bx

  mov  dl,[Byte ptr ds:si]                       { put PacketsInLine into DL }
  inc  si                                        { skip the PacketsInLine Byte }
  push di                                        { save the offset address of this line }

Pack_Loop:
  or   dl,dl                                     { set flags }
  jz   Next_Line                                 { Exit if no more packets in this line }
  dec  dl
  mov  cl,[Byte ptr ds:si+0]                     { put BytestoSkip into CL }
  add  di,cx                                     { update the offset address }
  mov  cl,[Byte ptr ds:si+1]                     { put BytesofDatatoCome into CL }
  or   cl,cl                                     { set flags }
  jns  Copy_Bytes                                { no SIGN means that CL number of data is to come - }
                                                 { - else the next data should be put -CL number of times }
  mov  al,[Byte ptr ds:si+2]                     { put the Byte to be Repeated into AL }
  add  si,3                                      { skip the packet }
  neg  cl                                        { Repeat -CL times }
  rep  stosb
  jmp  Pack_Loop                                 { finish With this packet }

Copy_Bytes:
  add  si,2                                      { skip the two count Bytes at the start of the packet }
  rep  movsb
  jmp  Pack_Loop                                 { finish With this packet }

Next_Line:
  pop  di                                        { restore the old offset address of the current line }
  add  di,320                                    { offset address to the next line }
  jmp  Line_Loop


Fli_Black:
  Les  di, ScreenBuffer

  xor  di,di
  mov  cx,32000                                  { number of Words in a screen }
  xor  ax,ax                                     { color 0 is to be put on the screen }
  rep  stosw
  jmp  Fli_Loop                                  { jump back to main loop }


Fli_Brun:
  Les  di, ScreenBuffer

  xor  di,di
  mov  bx,200                                    { numbers of lines in a screen }
  xor  cx,cx

Line_Loop2:
  mov  dl,[Byte ptr ds:si]                       { put PacketsInLine into DL }
  inc  si                                        { skip the PacketsInLine Byte }
  push di                                        { save the offset address of this line }

Pack_Loop2:
  or   dl,dl                                     { set flags }
  jz   Next_Line2                                { Exit if no more packets in this line }
  dec  dl
  mov  cl,[Byte ptr ds:si]                       { put BytesofDatatoCome into CL }
  or   cl,cl                                     { set flags }
  js   Copy_Bytes2                               { SIGN meens that CL number of data is to come - }
                                                 { - else the next data should be put -CL number of times }
  mov  al,[Byte ptr ds:si+1]                     { put the Byte to be Repeated into AL }
  add  si,2                                      { skip the packet }
  rep  stosb
  jmp  Pack_Loop2                                { finish With this packet }

Copy_Bytes2:
  inc  si                                        { skip the count Byte at the start of the packet }
  neg  cl                                        { Repeat -CL times }
  rep  movsb
  jmp  Pack_Loop2                                { finish With this packet }

Next_Line2:
  pop  di                                        { restore the old offset address of the current line }
  add  di,320                                    { offset address to the next line }
  dec  bx                                        { any more lines to draw? }
  jnz  Line_Loop2
  jmp  Fli_Loop                                  { jump back to main loop }


Fli_Copy:
  Les  di, ScreenBuffer

  xor  di,di
  mov  cx,32000                                  { number of Words in a screen }
  rep  movsw
  jmp  Fli_Loop                                  { jump back to main loop }


Exit:
  mov  ax, 0
  mov  es, ax
  pop  ds

  Sti
end;

Procedure ReadPalette;
Var
  T, Zero : Byte;

Begin
   FLIScreenstream.Seek(54, 0);
   For T := 0 to 255 do
   Begin
      FLIScreenStream.Read(Palette^[T*3+2], 1); { Blue }
      FLIScreenStream.Read(Palette^[T*3+1], 1); { Green }
      FLIScreenStream.Read(Palette^[T*3], 1); { Red }
      FLIScreenStream.Read(Zero, 1);         { Zero }
   End;
End;

Procedure WritePalette;
Var
  T, Zero : Byte;

Begin
   Zero := 0;

   FLIScreenStream.Seek(54, 0);
   For T := 0 to 255 do
   Begin
      FLIScreenStream.Write(Palette^[T*3+2], 1); { Blue }
      FLIScreenStream.Write(Palette^[T*3+1], 1); { Green }
      FLIScreenStream.Write(Palette^[T*3], 1); { Red }
      FLIScreenStream.Write(Zero, 1);         { Zero }
   End;
End;

Procedure Write_To_Screen;
Var
  Y : Word;

Begin
   FLIScreenStream.Seek(1078, 0);

   For Y := 199 downto 0 do
   Begin
      FLIScreenStream.Write(Screen_Buffer^[Y*320], 320);
   End;
End;


Begin
   IF GetClock < FLI_Nexttime Then Exit;


   IF (FliFileStream.Size > 128) Then
   Begin
      FillChar(FLI_FrameHeader, 16, 0);
      FliFileStream.Read(FLI_FrameHeader.Size, 4);
      FliFileStream.Read(FLI_FrameHeader.Magic, 2);
      FliFileStream.Read(FLI_FrameHeader.Chunks, 2);
      FliFileStream.Read(FLI_FrameHeader.Expand, 8);

      IF (FLI_FrameHeader.Magic = $F1FA) Then
      Begin
         FLI_FrameHeader.Size := FLI_FrameHeader.Size - 16;
         FliFileStream.Read(File_Buffer^, FLI_FrameHeader.Size);

         Getmem(Palette, 768);
         Paladdr := Seg(Palette^);
         ReadPalette;
         TreatFrame(File_Buffer^, Screen_Buffer^, Palette^, FLI_FrameHeader.Chunks);
         WritePalette;
         Freemem(Palette, 768);

         Write_To_Screen;


         IF FLI_FrameNr = 0 Then
         Begin
            FLI_SecondPosition := FliFileStream.Position;
         End;


         Inc(Fli_FrameNr);
         IF Fli_FrameNr > FLI_Header.Frames Then
         Begin
            FliFileStream.Seek(FLI_SecondPosition, 0);
            Fli_FrameNr := 1;
         End;


         FLI_NextTime := GetClock + FLI_Speed;
      End;
   End;
End;

Procedure TForm1.FormPaint(Sender: TObject);
Begin
   IF not Start_Screensaver Then Exit;
   Start_Screensaver := False;

   While True do
   Begin
      Show_Next_Frame;


      FLIScreenStream.Seek(0, 0);
      Try
        ScreenBitmap.LoadFromStream(FLIScreenStream);
      Except
      End;

      IF not Scale_FLI Then Canvas.Draw((Screen.Width div 2) - 160, (Screen.Height div 2) - 100, ScreenBitmap)
         Else Canvas.StretchDraw(ClientRect, ScreenBitmap);

      Application.ProcessMessages;
   End;
End;

End.

{ -------------  Cut out and save as UNIT1.DFM ----------------- }

object Form1: TForm1
  Left = 216
  Top = 168
  Width = 435
  Height = 300
  Caption = 'Form1'
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = 'System'
  Font.Style = []
  PixelsPerInch = 96
  OnCreate = FormCreate
  OnKeyDown = FormKeyDown
  OnMouseMove = FormMouseMove
  OnPaint = FormPaint
  TextHeight = 16
  object OpenDialog1: TOpenDialog
    Left = 4
    Top = 4
  end
end