shithub: rott

ref: a7ed30404bc98c13ac89721ab54e1d702d4e0b39
dir: /src/fli_main.c/

View raw version
/*
Copyright (C) 1994-1995 Apogee Software, Ltd.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
/* readflic.c - This module contains the routines to read and decompress
 * a flic.  They assume Intel byte ordering,  but otherwise should be
 * fairly portable.  They call machine specific stuff in pcclone.c.
 * This file starts with the low level decompression routines - first
 * for colors and then for pixels.  Then it goes to the higher level
 * exported flic_xxxx routines as prototyped in readflic.h.
 *
 * Copyright (c) 1992 Jim Kent.  This file may be freely used, modified,
 * copied and distributed.  This file was first published as part of
 * an article for Dr. Dobb's Journal March 1993 issue.
 */

#include "cin_glob.h"
#include <errno.h>
#include <string.h>
#include <io.h>
#include "fli_type.h"
#include "fli_util.h"
#include "fli_def.h"
#include "fli_main.h"



typedef void ColorOut(Screen *s, int start, Color  *colors, int count);
/* This is the type of output parameter to our decode_color below.
 * Not coincedently screen_put_color is of this type. */

static void decode_color(Uchar  *data
                         , Flic *flic, Screen *s, ColorOut *output)
/* Decode color map.  Put results into output.  The two color
 * compressions are identical except for whether the RGB values
 * are 0-63 or 0-255.  Passing in an output that does the appropriate
 * shifting on the way to the real pallete lets us use the same
 * code for both COLOR_64 and COLOR_256 compression.
 */
{
    int start = 0;
    Uchar  *cbuf = (Uchar  *)data;
    Short  *wp = (Short  *)cbuf;
    Short ops;
    int count;

    ops = *wp;
    cbuf += sizeof(*wp);
    while (--ops >= 0)
    {
        start += *cbuf++;
        if ((count = *cbuf++) == 0)
            count = 256;
        (*output)(s, start, (Color  *)cbuf, count);
        cbuf += 3*count;
        start += count;
    }
}

static void decode_color_256(Uchar  *data, Flic *flic, Screen *s)
/* Decode COLOR_256 chunk. */
{
    decode_color(data, flic, s, screen_put_colors);
}

static void decode_color_64(Uchar  *data, Flic *flic, Screen *s)
/* Decode COLOR_64 chunk. */
{
    decode_color(data, flic, s, screen_put_colors_64);
}


static void decode_byte_run(Uchar  *data, Flic *flic, Screen *s)
/* Byte-run-length decompression. */
{
    int x,y;
    int width = flic->head.width;
    int height = flic->head.height;
    Char psize;
    Char  *cpt = data;
    int end;

    y = flic->yoff;
    end = flic->xoff + width;
    while (--height >= 0)
    {
        x = flic->xoff;
        cpt += 1;	/* skip over obsolete opcount byte */
        psize = 0;
        while ((x+=psize) < end)
        {
            psize = *cpt++;
            if (psize >= 0)
            {
                screen_repeat_one(s, x, y, *cpt++, psize);
            }
            else
            {
                psize = -psize;
                screen_copy_seg(s, x, y, (Pixel  *)cpt, psize);
                cpt += psize;
            }
        }
        y++;
    }
}

static void decode_delta_fli(Uchar  *data, Flic *flic, Screen *s)
/* Fli style delta decompression. */
{
    int xorg = flic->xoff;
    int yorg = flic->yoff;
    Short  *wpt = (Short  *)data;
    Uchar  *cpt = (Uchar  *)(wpt + 2);
    int x,y;
    Short lines;
    Uchar opcount;
    Char psize;

    y = yorg + *wpt++;
    lines = *wpt;
    while (--lines >= 0)
    {
        x = xorg;
        opcount = *cpt++;
        while (opcount > 0)
        {
            x += *cpt++;
            psize = *cpt++;
            if (psize < 0)
            {
                psize = -psize;
                screen_repeat_one(s, x, y, *cpt++, psize);
                x += psize;
                opcount-=1;
            }
            else
            {
                screen_copy_seg(s, x, y, (Pixel  *)cpt, psize);
                cpt += psize;
                x += psize;
                opcount -= 1;
            }
        }
        y++;
    }
}


static void decode_delta_flc(Uchar  *data, Flic *flic, Screen *s)
/* Flc-style delta decompression.  The data is word oriented though
* a lot of the control info (how  to skip, how many words to
 * copy) are byte oriented still to save space. */
{
    int xorg = flic->xoff;
    int yorg = flic->yoff;
    int width = flic->head.width;
    int x,y;
    Short lp_count;
    Short opcount;
    int psize;
    union {
        Short  *w;
        Uchar  *ub;
        Char  *b;
        Pixels2  *p2;
    } wpt;
    int lastx;


    lastx = xorg + width - 1;
    wpt.ub = data;
    lp_count = *wpt.w++;
    y = yorg;
    goto LPACK;

SKIPLINES:	/* Advance over some lines. */
    y -= opcount;

LPACK:		/* do next line */
    if ((opcount = *wpt.w++) >= 0)
        goto DO_SS2OPS;
    if( ((Ushort)opcount) & 0x4000) /* skip lines */
        goto SKIPLINES;
    screen_put_dot(s,(Uchar)opcount,lastx,y); /* put dot at eol with low byte */
    if((opcount = *wpt.w++) == 0)
    {
        ++y;
        if (--lp_count > 0)
            goto LPACK;
        goto OUT;
    }
DO_SS2OPS:
    x = xorg;

PPACK:				/* do next packet */
    x += *wpt.ub++;
    psize = *wpt.b++;
    if ((psize += psize) >= 0)
    {
        screen_copy_seg(s, x, y, (Pixel  *)wpt.ub, psize);
        x += psize;
        wpt.ub += psize;
        if (--opcount != 0)
            goto PPACK;
        ++y;
        if (--lp_count > 0)
            goto LPACK;
    }
    else
    {
        psize = -psize;
        screen_repeat_two(s, x, y, *wpt.p2++, psize>>1);
        x += psize;
        if (--opcount != 0)
            goto PPACK;
        ++y;
        if (--lp_count > 0)
            goto LPACK;
    }
OUT:
    return;
}

static void decode_black(Uchar  *data, Flic *flic, Screen *s)
/* Decode a BLACK chunk.  Set frame to solid color 0 one
 * line at a time. */
{
    Pixels2 black;
    int i;
    int height = flic->head.height;
    int width = flic->head.width;
    int x = flic->xoff;
    int y = flic->yoff;

    black.pixels[0] = black.pixels[1] = 0;
    for (i=0; i<height; ++i)
    {
        screen_repeat_two(s, x, y+i, black, width/2);
        if (width & 1)	/* if odd set last pixel */
            screen_put_dot(s, x+width-1, y+i, 0);
    }
}

static void decode_literal(Uchar  *data, Flic *flic, Screen *s)
/* Decode a LITERAL chunk.  Just copy data to screen one line at
 * a time. */
{
    int i;
    int height = flic->head.height;
    int width = flic->head.width;
    int x = flic->xoff;
    int y = flic->yoff;

    for (i=0; i<height; ++i)
    {
        screen_copy_seg(s, x, y+i, (Pixel  *)data, width);
        data += width;
    }
}

ErrCode SetupFlicAccess (Flic * flic)
{
    if (flic->usefile==TRUE)
    {
        return file_open_to_read(&flic->handle, flic->name);
    }
    else
    {
        flic->flicoffset=0;
        return Success;
    }
}

ErrCode CopyNextFlicBlock (Flic * flic, MemPtr buf, Ulong size)
{
    ErrCode err;

    if (flic->usefile==TRUE)
    {
        err = file_read_big_block(flic->handle, buf, size);
        return err;
    }
    else
    {
        memcpy(buf, flic->flicbuffer+flic->flicoffset, size);
        flic->flicoffset+=size;
        return Success;
    }
}

void    SetFlicOffset (Flic * flic, Ulong offset )
{
    if (flic->usefile==TRUE)
    {
        lseek(flic->handle,offset,SEEK_SET);
    }
    else
    {
        flic->flicoffset = offset;
    }
}


ErrCode flic_open(Flic *flic, char *name, MemPtr buf, Boolean usefile)
/* Open flic file.  Read header and verify it's a flic.
 * Seek to first frame. */
{
    ErrCode err;

    ClearStruct(flic);		/* Start at a known state. */
    flic->usefile=usefile;  /* use file or buffer */
    flic->name = name;      /* Save name for future use. */
    flic->flicbuffer=buf;   /* save address of flicbuffer */

    if ((err = SetupFlicAccess (flic)) >= Success)
    {
        if ((err = CopyNextFlicBlock (flic, (Uchar *)&flic->head, sizeof(flic->head)))
                >= Success)
        {
            if (flic->head.type == FLC_TYPE)
            {
                /* Seek frame 1. */
                SetFlicOffset (flic, flic->head.oframe1 );
                return Success;
            }
            if (flic->head.type == FLI_TYPE)
            {
                /* Do some conversion work here. */
                flic->head.oframe1 = sizeof(flic->head);
                flic->head.speed = flic->head.speed * 1000L / 70L;
                return Success;
            }
            else
            {
                err = ErrBadFlic;
            }
        }
    }
    flic_close(flic);    /* Close down and scrub partially opened flic. */
    return err;
}


void flic_close(Flic *flic)
/* Close flic file and scrub flic. */
{
    if (flic->usefile==TRUE)
    {
        close(flic->handle);
    }
    ClearStruct(flic);		/* Discourage use after close. */
}

static ErrCode decode_frame(Flic *flic
                            , FrameHead *frame, Uchar  *data, Screen *s)
/* Decode a frame that is in memory already into screen.
 * Here we just loop through each chunk calling appropriate
 * chunk decoder.
 */
{
    int i;
    ChunkHead  *chunk;

    for (i=0; i<frame->chunks; ++i)
    {
        chunk = (ChunkHead  *)data;
        data += chunk->size;
        switch (chunk->type)
        {
        case COLOR_256:
            decode_color_256((Uchar  *)(chunk+1), flic, s);
            break;
        case DELTA_FLC:
            decode_delta_flc((Uchar  *)(chunk+1), flic, s);
            break;
        case COLOR_64:
            decode_color_64((Uchar  *)(chunk+1), flic, s);
            break;
        case DELTA_FLI:
            decode_delta_fli((Uchar  *)(chunk+1), flic, s);
            break;
        case BLACK:
            decode_black((Uchar  *)(chunk+1), flic, s);
            break;
        case BYTE_RUN:
            decode_byte_run((Uchar  *)(chunk+1), flic, s);
            break;
        case LITERAL:
            decode_literal((Uchar  *)(chunk+1), flic, s);
            break;
        default:
            break;
        }
    }
    return Success;
}

ErrCode flic_next_frame(Flic *flic, Screen *screen)
/* Advance to next frame of flic. */
{
    FrameHead head;
    ErrCode err;
    MemPtr bb;
    long size;

    if ((err = CopyNextFlicBlock (flic, (Uchar *)&head, sizeof(head))) >= Success)
    {
        if (head.type == FRAME_TYPE)
        {
            size = head.size - sizeof(head);	/* Don't include head. */
            if (size > 0)
            {
                if ((err = big_alloc(&bb, size)) >= Success)
                {
                    if ((err = CopyNextFlicBlock (flic, bb, size)) >= Success)
                    {
                        err = decode_frame(flic, &head, bb, screen);
                    }
                    big_free(&bb);
                }
            }
        }
        else
        {
            err = ErrBadFrame;
        }
    }
    return err;
}


static Ulong calc_end_time(Ulong millis)
/* Little helper subroutine to find out when to start on next
 * frame. */
{
    return (GetCinematicTime() + ( (millis * CLOCKSPEED) / 4000l) );
}

static ErrCode wait_til(Ulong end_time, Machine *machine)
/* This waits until key is hit or end_time arrives.
 * Return Success if timed out,  ErrCancel if key hit.
 * Insures keyboard will be polled at least once.
 */
{
    do
    {
        if (CinematicAbort()!=0)
            return ErrCancel;
    }
    while (GetCinematicTime() < end_time);
    return Success;
}

ErrCode flic_play_once(Flic *flic, Machine *machine)
/* Play a flic through once. */
{
    ErrCode err;
    int i;
    Ulong end_time;

    for (i=0; i<flic->head.frames; ++i)
    {
        end_time = calc_end_time(flic->head.speed);
        if ((err = flic_next_frame(flic, &machine->screen)) < Success)
            break;
        if ((err = wait_til(end_time, machine)) < Success)
            break;
    }
    return err;
}

static ErrCode fill_in_frame2(Flic *flic)
/* This figures out where the second frame of the flic is
 * (useful for playing in a loop).  */
{
    FrameHead head;
    ErrCode err;

    SetFlicOffset (flic, flic->head.oframe1 );
    if ((err = CopyNextFlicBlock (flic, (MemPtr)&head, sizeof(head))) < Success)
        return err;
    flic->head.oframe2 = flic->head.oframe1 + head.size;
    return Success;
}

ErrCode flic_play_loop(Flic *flic, Machine *machine)
/* Play a flic until key is pressed. */
{
    int i;
    Ulong end_time;
    ErrCode err;

    if (flic->head.oframe2 == 0)
    {
        fill_in_frame2(flic);
    }
    /* Seek to first frame. */
    SetFlicOffset (flic, flic->head.oframe1 );
    /* Save time to move on. */
    end_time = calc_end_time(flic->head.speed);
    /* Display first frame. */
    if ((err = flic_next_frame(flic, &machine->screen)) < Success)
        return err;
    for (;;)
    {
        /* Seek to second frame */
        SetFlicOffset (flic, flic->head.oframe2 );
        /* Loop from 2nd frame thru ring frame*/
        for (i=0; i<flic->head.frames; ++i)
        {
            if (wait_til(end_time, machine) < Success)
                return Success;		/* Time out is a success here. */
            if ((err = flic_next_frame(flic, &machine->screen)) < Success)
                return err;
            end_time = calc_end_time(flic->head.speed);
        }
    }
}


static char *err_strings[] =
{
    "Unspecified error",
    "Not enough memory",
    "Not a flic file",
    "Bad frame in flic",
    NULL,
    NULL,
    "Couldn't open display",
    "Couldn't open keyboard",
    "User canceled action",
};

char *flic_err_string(ErrCode err)
/* Return a string that describes an error. */
{
    if (err >= Success)
        return "Success";		/* Shouldn't happen really... */
    if (err == ErrOpen || err == ErrRead)
        return strerror(errno);	/* Get Disk IO error from DOS. */
    err = -err;
    err -= 1;
    if (err > ArrayEls(err_strings))
        return "Unknown error";
    return err_strings[err];
}


static void center_flic(Flic *flic, Screen *s)
/* Set flic.xoff and flic.yoff so flic plays centered rather
 * than in upper left corner of display. */
{
    flic->xoff = (screen_width(s) - (signed)flic->head.width)/2;
    flic->yoff = (screen_height(s) - (signed)flic->head.height)/2;
}

void PlayFlic ( char * name, unsigned char * buffer, int usefile, int loop)
{
    ErrCode err;
    Flic flic;
    Machine machine;

    if ((err = machine_open(&machine)) >= Success)
    {
        if ((err = flic_open(&flic, name, buffer, usefile)) >= Success)
        {
            center_flic(&flic, &machine.screen);
            if (loop==0)
            {
                err = flic_play_once(&flic, &machine);
            }
            else
            {
                err = flic_play_loop(&flic, &machine);
            }
            flic_close(&flic);
        }
        machine_close(&machine);
    }
    if (err < Success && err != ErrCancel)
    {
        Error("Play Flic had troubles with %s.\n%s.\n",name, flic_err_string(err));
    }
}