ref: 516fc276f112baf0bd4508596598aed49929bde9
dir: /src/fli_main.c/
/* Copyright (C) 1994-1995 Apogee Software, Ltd. Copyright (C) 2002-2015 icculus.org, GNU/Linux port Copyright (C) 2017-2018 Steven LeVesque 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. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ /* 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)); } }