
/*
 * Written 1998 Jens Ch. Restemeier <jchrr@hrz.uni-bielefeld.de>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * This code can be used to read and write FLI movies. It is currently
 * only used for the GIMP fli plug-in, but it can be used for other
 * programs, too.
 */

#include "config.h"

#include <string.h>
#include <stdio.h>

#include "fli.h"

/*
 * To avoid endian-problems I wrote these functions:
 */
static unsigned char fli_read_char(FILE *f)
{
	unsigned char b;
	fread(&b,1,1,f);
	return b;
}

static unsigned short fli_read_short(FILE *f)
{
	unsigned char b[2];
	fread(&b,1,2,f);
	return (unsigned short)(b[1]<<8) | b[0];
}

static unsigned long fli_read_long(FILE *f)
{
	unsigned char b[4];
	fread(&b,1,4,f);
	return (unsigned long)(b[3]<<24) | (b[2]<<16) | (b[1]<<8) | b[0];
}

static void fli_write_char(FILE *f, unsigned char b)
{
	fwrite(&b,1,1,f);
}

static void fli_write_short(FILE *f, unsigned short w)
{
	unsigned char b[2];
	b[0]=w&255;
	b[1]=(w>>8)&255;
	fwrite(&b,1,2,f);
}

static void fli_write_long(FILE *f, unsigned long l)
{
	unsigned char b[4];
	b[0]=l&255;
	b[1]=(l>>8)&255;
	b[2]=(l>>16)&255;
	b[3]=(l>>24)&255;
	fwrite(&b,1,4,f);
}

void fli_read_header(FILE *f, s_fli_header *fli_header)
{
	fli_header->filesize=fli_read_long(f);	/* 0 */
	fli_header->magic=fli_read_short(f);	/* 4 */
	fli_header->frames=fli_read_short(f);	/* 6 */
	fli_header->width=fli_read_short(f);	/* 8 */
	fli_header->height=fli_read_short(f);	/* 10 */
	fli_header->depth=fli_read_short(f);	/* 12 */
	fli_header->flags=fli_read_short(f);	/* 14 */
	if (fli_header->magic == HEADER_FLI) {
		/* FLI saves speed in 1/70s */
		fli_header->speed=fli_read_short(f)*14;		/* 16 */
	} else {
		if (fli_header->magic == HEADER_FLC) {
			/* FLC saves speed in 1/1000s */
			fli_header->speed=fli_read_long(f);	/* 16 */
		} else {
			fprintf(stderr, "error: magic number is wrong !\n");
			fli_header->magic = NO_HEADER;
		}
	}

	if (fli_header->width == 0)
	  fli_header->width = 320;

	if (fli_header->height == 0)
	  fli_header->height = 200;
}

void fli_write_header(FILE *f, s_fli_header *fli_header)
{
	fli_header->filesize=ftell(f);
	fseek(f, 0, SEEK_SET);
	fli_write_long(f, fli_header->filesize);	/* 0 */
	fli_write_short(f, fli_header->magic);	/* 4 */
	fli_write_short(f, fli_header->frames);	/* 6 */
	fli_write_short(f, fli_header->width);	/* 8 */
	fli_write_short(f, fli_header->height);	/* 10 */
	fli_write_short(f, fli_header->depth);	/* 12 */
	fli_write_short(f, fli_header->flags);	/* 14 */
	if (fli_header->magic == HEADER_FLI) {
		/* FLI saves speed in 1/70s */
		fli_write_short(f, fli_header->speed / 14);	/* 16 */
	} else {
		if (fli_header->magic == HEADER_FLC) {
			/* FLC saves speed in 1/1000s */
			fli_write_long(f, fli_header->speed);	/* 16 */
			fseek(f, 80, SEEK_SET);
			fli_write_long(f, fli_header->oframe1);	/* 80 */
			fli_write_long(f, fli_header->oframe2);	/* 84 */
		} else {
			fprintf(stderr, "error: magic number in header is wrong !\n");
		}
	}
}

void fli_read_frame(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *old_cmap, unsigned char *framebuf, unsigned char *cmap)
{
	s_fli_frame fli_frame;
	unsigned long framepos;
	int c;
	framepos=ftell(f);

	fli_frame.size=fli_read_long(f);
	fli_frame.magic=fli_read_short(f);
	fli_frame.chunks=fli_read_short(f);

	if (fli_frame.magic == FRAME) {
		fseek(f, framepos+16, SEEK_SET);
		for (c=0;c<fli_frame.chunks;c++) {
			s_fli_chunk chunk;
			unsigned long chunkpos;
			chunkpos = ftell(f);
			chunk.size=fli_read_long(f);
			chunk.magic=fli_read_short(f);
			switch (chunk.magic) {
				case FLI_COLOR:   fli_read_color(f, fli_header, old_cmap, cmap); break;
				case FLI_COLOR_2: fli_read_color_2(f, fli_header, old_cmap, cmap); break;
				case FLI_BLACK:	  fli_read_black(f, fli_header, framebuf); break;
				case FLI_BRUN:    fli_read_brun(f, fli_header, framebuf); break;
				case FLI_COPY:    fli_read_copy(f, fli_header, framebuf); break;
				case FLI_LC:      fli_read_lc(f, fli_header, old_framebuf, framebuf); break;
				case FLI_LC_2:    fli_read_lc_2(f, fli_header, old_framebuf, framebuf); break;
				case FLI_MINI:	  /* unused, skip */ break;
				default: /* unknown, skip */ break;
			}
			if (chunk.size & 1) chunk.size++;
			fseek(f,chunkpos+chunk.size,SEEK_SET);
		}
	} /* else: unknown, skip */
	fseek(f, framepos+fli_frame.size, SEEK_SET);
}

void fli_write_frame(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *old_cmap, unsigned char *framebuf, unsigned char *cmap, unsigned short codec_mask)
{
	s_fli_frame fli_frame;
	unsigned long framepos, frameend;
	framepos=ftell(f);
	fseek(f, framepos+16, SEEK_SET);

	switch (fli_header->frames) {
		case 0: fli_header->oframe1=framepos; break;
		case 1: fli_header->oframe2=framepos; break;
	}

	fli_frame.size=0;
	fli_frame.magic=FRAME;
	fli_frame.chunks=0;

	/*
	 * create color chunk
	 */
	if (fli_header->magic == HEADER_FLI) {
		if (fli_write_color(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
	} else {
		if (fli_header->magic == HEADER_FLC) {
			if (fli_write_color_2(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
		} else {
			fprintf(stderr, "error: magic number in header is wrong !\n");
		}
	}

#if 0
	if (codec_mask & W_COLOR) {
		if (fli_write_color(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
	}
	if (codec_mask & W_COLOR_2) {
		if (fli_write_color_2(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
	}
#endif
	/* create bitmap chunk */
	if (old_framebuf==NULL) {
		fli_write_brun(f, fli_header, framebuf);
	} else {
		fli_write_lc(f, fli_header, old_framebuf, framebuf);
	}
	fli_frame.chunks++;

	frameend=ftell(f);
	fli_frame.size=frameend-framepos;
	fseek(f, framepos, SEEK_SET);
	fli_write_long(f, fli_frame.size);
	fli_write_short(f, fli_frame.magic);
	fli_write_short(f, fli_frame.chunks);
	fseek(f, frameend, SEEK_SET);
	fli_header->frames++;
}

/*
 * palette chunks from the classical Autodesk Animator.
 */
void fli_read_color(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned short num_packets, cnt_packets, col_pos;
	col_pos=0;
	num_packets=fli_read_short(f);
	for (cnt_packets=num_packets; cnt_packets>0; cnt_packets--) {
		unsigned short skip_col, num_col, col_cnt;
		skip_col=fli_read_char(f);
		num_col=fli_read_char(f);
		if (num_col==0) {
			for (col_pos=0; col_pos<768; col_pos++) {
				cmap[col_pos]=fli_read_char(f)<<2;
			}
			return;
		}
		for (col_cnt=skip_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
		}
		for (col_cnt=num_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos++]=fli_read_char(f)<<2;
			cmap[col_pos++]=fli_read_char(f)<<2;
			cmap[col_pos++]=fli_read_char(f)<<2;
		}
	}
}

int fli_write_color(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned long chunkpos;
	unsigned short num_packets;
	s_fli_chunk chunk;
	chunkpos=ftell(f);
	fseek(f, chunkpos+8, SEEK_SET);
	num_packets=0;
	if (old_cmap==NULL) {
		unsigned short col_pos;
		num_packets=1;
		fli_write_char(f, 0); /* skip no color */
		fli_write_char(f, 0); /* 256 color */
		for (col_pos=0; col_pos<768; col_pos++) {
			fli_write_char(f, cmap[col_pos]>>2);
		}
	} else {
		unsigned short cnt_skip, cnt_col, col_pos, col_start;
		col_pos=0;
		do {
			cnt_skip=0;
			while ((col_pos<256) && (old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2])) {
				cnt_skip++; col_pos++;
			}
			col_start=col_pos*3;
			cnt_col=0;
			while ((col_pos<256) && !((old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2]))) {
				cnt_col++; col_pos++;
			}
			if (cnt_col>0) {
				num_packets++;
				fli_write_char(f, cnt_skip & 255);
				fli_write_char(f, cnt_col & 255);
				while (cnt_col>0) {
					fli_write_char(f, cmap[col_start++]>>2);
					fli_write_char(f, cmap[col_start++]>>2);
					fli_write_char(f, cmap[col_start++]>>2);
					cnt_col--;
				}
			}
		} while (col_pos<256);
	}

	if (num_packets>0) {
		chunk.size=ftell(f)-chunkpos;
		chunk.magic=FLI_COLOR;

		fseek(f, chunkpos, SEEK_SET);
		fli_write_long(f, chunk.size);
		fli_write_short(f, chunk.magic);
		fli_write_short(f, num_packets);

		if (chunk.size & 1) chunk.size++;
		fseek(f,chunkpos+chunk.size,SEEK_SET);
		return 1;
	}
	fseek(f,chunkpos, SEEK_SET);
	return 0;
}

/*
 * palette chunks from Autodesk Animator pro
 */
void fli_read_color_2(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned short num_packets, cnt_packets, col_pos;
	num_packets=fli_read_short(f);
	col_pos=0;
	for (cnt_packets=num_packets; cnt_packets>0; cnt_packets--) {
		unsigned short skip_col, num_col, col_cnt;
		skip_col=fli_read_char(f);
		num_col=fli_read_char(f);
		if (num_col == 0) {
			for (col_pos=0; col_pos<768; col_pos++) {
				cmap[col_pos]=fli_read_char(f);
			}
			return;
		}
		for (col_cnt=skip_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
		}
		for (col_cnt=num_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos++]=fli_read_char(f);
			cmap[col_pos++]=fli_read_char(f);
			cmap[col_pos++]=fli_read_char(f);
		}
	}
}

int fli_write_color_2(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned long chunkpos;
	unsigned short num_packets;
	s_fli_chunk chunk;
	chunkpos=ftell(f);
	fseek(f, chunkpos+8, SEEK_SET);
	num_packets=0;
	if (old_cmap==NULL) {
		unsigned short col_pos;
		num_packets=1;
		fli_write_char(f, 0); /* skip no color */
		fli_write_char(f, 0); /* 256 color */
		for (col_pos=0; col_pos<768; col_pos++) {
			fli_write_char(f, cmap[col_pos]);
		}
	} else {
		unsigned short cnt_skip, cnt_col, col_pos, col_start;
		col_pos=0;
		do {
			cnt_skip=0;
			while ((col_pos<256) && (old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2])) {
				cnt_skip++; col_pos++;
			}
			col_start=col_pos*3;
			cnt_col=0;
			while ((col_pos<256) && !((old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2]))) {
				cnt_col++; col_pos++;
			}
			if (cnt_col>0) {
				num_packets++;
				fli_write_char(f, cnt_skip);
				fli_write_char(f, cnt_col);
				while (cnt_col>0) {
					fli_write_char(f, cmap[col_start++]);
					fli_write_char(f, cmap[col_start++]);
					fli_write_char(f, cmap[col_start++]);
					cnt_col--;
				}
			}
		} while (col_pos<256);
	}

	if (num_packets>0) {
		chunk.size=ftell(f)-chunkpos;
		chunk.magic=FLI_COLOR_2;

		fseek(f, chunkpos, SEEK_SET);
		fli_write_long(f, chunk.size);
		fli_write_short(f, chunk.magic);
		fli_write_short(f, num_packets);

		if (chunk.size & 1) chunk.size++;
		fseek(f,chunkpos+chunk.size,SEEK_SET);
		return 1;
	}
	fseek(f,chunkpos, SEEK_SET);
	return 0;
}

/*
 * completely black frame
 */
void fli_read_black(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	memset(framebuf, 0, fli_header->width * fli_header->height);
}

void fli_write_black(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	s_fli_chunk chunk;

	chunk.size=6;
	chunk.magic=FLI_BLACK;

	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);
}

/*
 * Uncompressed frame
 */
void fli_read_copy(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	fread(framebuf, fli_header->width, fli_header->height, f);
}

void fli_write_copy(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{

	unsigned long chunkpos;
	s_fli_chunk chunk;
	chunkpos=ftell(f);
	fseek(f, chunkpos+6, SEEK_SET);
	fwrite(framebuf, fli_header->width, fli_header->height, f);
	chunk.size=ftell(f)-chunkpos;
	chunk.magic=FLI_COPY;

	fseek(f, chunkpos, SEEK_SET);
	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);

	if (chunk.size & 1) chunk.size++;
	fseek(f,chunkpos+chunk.size,SEEK_SET);
}

/*
 * This is a RLE algorithm, used for the first image of an animation
 */
void fli_read_brun(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	unsigned short yc;
	unsigned char *pos;
	for (yc=0; yc < fli_header->height; yc++) {
		unsigned short xc, pc, pcnt;
		pc=fli_read_char(f);
		xc=0;
		pos=framebuf+(fli_header->width * yc);
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps;
			ps=fli_read_char(f);
			if (ps & 0x80) {
				unsigned short len;
				for (len=-(signed char)ps; len>0; len--) {
					pos[xc++]=fli_read_char(f);
				}
			} else {
				unsigned char val;
				val=fli_read_char(f);
				memset(&(pos[xc]), val, ps);
				xc+=ps;
			}
		}
	}
}

void fli_write_brun(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	unsigned long chunkpos;
	s_fli_chunk chunk;
	unsigned short yc;
	unsigned char *linebuf;

	chunkpos=ftell(f);
	fseek(f, chunkpos+6, SEEK_SET);

	for (yc=0; yc < fli_header->height; yc++) {
		unsigned short xc, t1, pc, tc;
		unsigned long linepos, lineend, bc;
		linepos=ftell(f); bc=0;
		fseek(f, 1, SEEK_CUR);
		linebuf=framebuf + (yc*fli_header->width);
		xc=0; tc=0; t1=0;
		while (xc < fli_header->width) {
			pc=1;
			while ((pc<120) && ((xc+pc)<fli_header->width) && (linebuf[xc+pc] == linebuf[xc])) {
				pc++;
			}
			if (pc>2) {
				if (tc>0) {
					bc++;
					fli_write_char(f, (tc-1)^0xFF);
					fwrite(linebuf+t1, 1, tc, f);
					tc=0;
				}
				bc++;
				fli_write_char(f, pc);
				fli_write_char(f, linebuf[xc]);
				t1=xc+pc;
			} else {
				tc+=pc;
				if (tc>120) {
					bc++;
					fli_write_char(f, (tc-1)^0xFF);
					fwrite(linebuf+t1, 1, tc, f);
					tc=0;
					t1=xc+pc;
				}
			}
			xc+=pc;
		}
		if (tc>0) {
			bc++;
			fli_write_char(f, (tc-1)^0xFF);
			fwrite(linebuf+t1, 1, tc, f);
			tc=0;
		}
		lineend=ftell(f);
		fseek(f, linepos, SEEK_SET);
		fli_write_char(f, bc);
		fseek(f, lineend, SEEK_SET);
	}

	chunk.size=ftell(f)-chunkpos;
	chunk.magic=FLI_BRUN;

	fseek(f, chunkpos, SEEK_SET);
	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);

	if (chunk.size & 1) chunk.size++;
	fseek(f,chunkpos+chunk.size,SEEK_SET);
}

/*
 * This is the delta-compression method from the classic Autodesk
 * Animator.  It's basically the RLE method from above, but it
 * supports skipping unchanged lines at the beginning and end of an
 * image, and unchanged pixels in a line. This chunk is used in FLI
 * files.
 */
void fli_read_lc(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *framebuf)
{
	unsigned short yc, firstline, numline;
	unsigned char *pos;
	memcpy(framebuf, old_framebuf, fli_header->width * fli_header->height);
	firstline = fli_read_short(f);
	numline = fli_read_short(f);
	for (yc=0; yc < numline; yc++) {
		unsigned short xc, pc, pcnt;
		pc=fli_read_char(f);
		xc=0;
		pos=framebuf+(fli_header->width * (firstline+yc));
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps,skip;
			skip=fli_read_char(f);
			ps=fli_read_char(f);
			xc+=skip;
			if (ps & 0x80) {
				unsigned char val;
				ps=-(signed char)ps;
				val=fli_read_char(f);
				memset(&(pos[xc]), val, ps);
				xc+=ps;
			} else {
				fread(&(pos[xc]), ps, 1, f);
				xc+=ps;
			}
		}
	}
}

void fli_write_lc(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *framebuf)
{
	unsigned long chunkpos;
	s_fli_chunk chunk;
	unsigned short yc, firstline, numline, lastline;
	unsigned char *linebuf, *old_linebuf;

	chunkpos=ftell(f);
	fseek(f, chunkpos+6, SEEK_SET);

	/* first check, how many lines are unchanged at the beginning */
	firstline=0;
	while ((memcmp(old_framebuf+(firstline*fli_header->width), framebuf+(firstline*fli_header->width), fli_header->width)==0) && (firstline<fli_header->height)) firstline++;

	/* then check from the end, how many lines are unchanged */
	if (firstline<fli_header->height) {
		lastline=fli_header->height-1;
		while ((memcmp(old_framebuf+(lastline*fli_header->width), framebuf+(lastline*fli_header->width), fli_header->width)==0) && (lastline>firstline)) lastline--;
		numline=(lastline-firstline)+1;
	} else {
		numline=0;
	}
	if (numline==0) firstline=0;

	fli_write_short(f, firstline);
	fli_write_short(f, numline);

	for (yc=0; yc < numline; yc++) {
		unsigned short xc, sc, cc, tc;
		unsigned long linepos, lineend, bc;
		linepos=ftell(f); bc=0;
		fseek(f, 1, SEEK_CUR);

		linebuf=framebuf + ((firstline+yc)*fli_header->width);
		old_linebuf=old_framebuf + ((firstline+yc)*fli_header->width);
		xc=0;
		while (xc < fli_header->width) {
			sc=0;
			while ((linebuf[xc]==old_linebuf[xc]) && (xc<fli_header->width) && (sc<255)) {
				xc++; sc++;
			}
			fli_write_char(f, sc);
			cc=1;
			while ((linebuf[xc]==linebuf[xc+cc]) && ((xc+cc)<fli_header->width) && (cc<120)) {
				cc++;
			}
			if (cc>2) {
				bc++;
				fli_write_char(f, (cc-1)^0xFF);
				fli_write_char(f, linebuf[xc]);
				xc+=cc;
			} else {
				tc=0;
				do {
					sc=0;
					while ((linebuf[tc+xc+sc]==old_linebuf[tc+xc+sc]) && ((tc+xc+sc)<fli_header->width) && (sc<5)) {
						sc++;
					}
					cc=1;
					while ((linebuf[tc+xc]==linebuf[tc+xc+cc]) && ((tc+xc+cc)<fli_header->width) && (cc<10)) {
						cc++;
					}
					tc++;
				} while ((tc<120) && (cc<9) && (sc<4) && ((xc+tc)<fli_header->width));
				bc++;
				fli_write_char(f, tc);
				fwrite(linebuf+xc, tc, 1, f);
				xc+=tc;
			}
		}
		lineend=ftell(f);
		fseek(f, linepos, SEEK_SET);
		fli_write_char(f, bc);
		fseek(f, lineend, SEEK_SET);
	}

	chunk.size=ftell(f)-chunkpos;
	chunk.magic=FLI_LC;

	fseek(f, chunkpos, SEEK_SET);
	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);

	if (chunk.size & 1) chunk.size++;
	fseek(f,chunkpos+chunk.size,SEEK_SET);
}


/*
 * This is an enhanced version of the old delta-compression used by
 * the autodesk animator pro. It's word-oriented, and supports
 * skipping larger parts of the image. This chunk is used in FLC
 * files.
 */
void fli_read_lc_2(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *framebuf)
{
	unsigned short yc, lc, numline;
	unsigned char *pos;
	memcpy(framebuf, old_framebuf, fli_header->width * fli_header->height);
	yc=0;
	numline = fli_read_short(f);
	for (lc=0; lc < numline; lc++) {
		unsigned short xc, pc, pcnt, lpf, lpn;
		pc=fli_read_short(f);
		lpf=0; lpn=0;
		while (pc & 0x8000) {
			if (pc & 0x4000) {
				yc+=-(signed short)pc;
			} else {
				lpf=1;lpn=pc&0xFF;
			}
			pc=fli_read_short(f);
		}
		xc=0;
		pos=framebuf+(fli_header->width * yc);
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps,skip;
			skip=fli_read_char(f);
			ps=fli_read_char(f);
			xc+=skip;
			if (ps & 0x80) {
				unsigned char v1,v2;
				ps=-(signed char)ps;
				v1=fli_read_char(f);
				v2=fli_read_char(f);
				while (ps>0) {
					pos[xc++]=v1;
					pos[xc++]=v2;
					ps--;
				}
			} else {
				fread(&(pos[xc]), ps, 2, f);
				xc+=ps << 1;
			}
		}
		if (lpf) pos[xc]=lpn;
		yc++;
	}
}
