#ifndef lint
static const char RCSid[] = "$Id: bmpfile.c,v 2.20 2019/01/23 19:13:55 greg Exp $";
#endif
/*
 *  Windows and OS/2 BMP file support
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bmpfile.h"

#ifdef getc_unlocked		/* avoid horrendous overhead of flockfile */
#undef getc
#undef putc
#define getc    getc_unlocked
#define putc    putc_unlocked
#endif

/* Get corresponding error message */
const char *
BMPerrorMessage(int ec)
{
	switch (ec) {
	case BIR_OK:
		return "No error";
	case BIR_EOF:
		return "End of BMP image";
	case BIR_TRUNCATED:
		return "Truncated BMP image";
	case BIR_UNSUPPORTED:
		return "Unsupported BMP feature";
	case BIR_RLERROR:
		return "BMP runlength encoding error";
	case BIR_SEEKERR:
		return "BMP seek error";
	}
	return "Unknown BMP error";
}

/* Check that header is sensible */
static int
BMPheaderOK(const BMPHeader *hdr)
{
	if (!hdr)
		return 0;
	if ((hdr->width <= 0) | (hdr->height <= 0))
		return 0;
	switch (hdr->bpp) {		/* check compression */
	case 1:
	case 24:
		if (hdr->compr != BI_UNCOMPR)
			return 0;
		break;
	case 16:
	case 32:
		if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_BITFIELDS))
			return 0;
		break;
	case 4:
		if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE4))
			return 0;
		break;
	case 8:
		if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE8))
			return 0;
		break;
	default:
		return 0;
	}
	if (hdr->compr == BI_BITFIELDS && (BMPbitField(hdr)[0] &
				BMPbitField(hdr)[1] & BMPbitField(hdr)[2]))
		return 0;
	if (hdr->bpp > 8) {
		if (hdr->nColors != 0)
			return 0;
	} else {
		if ((hdr->nColors < 0) | (hdr->nColors > 1<<hdr->bpp))
			return 0;
		if ((hdr->impColors < 0) | (hdr->impColors > hdr->nColors))
			return 0;
	}
	return 1;
}

					/* compute uncompressed scan size */
#define getScanSiz(h)   ( ((((h)->bpp*(h)->width+7) >>3) + 3) & ~03 )

					/* get next byte from reader */
#define rdbyte(c,br)    ((br)->fpos += (c=(*(br)->cget)((br)->c_data))!=EOF, c)

/* Read n bytes */
static int
rdbytes(char *bp, uint32 n, BMPReader *br)
{
	int     c;

	while (n--) {
		if (rdbyte(c, br) == EOF)
			return BIR_TRUNCATED;
		*bp++ = c;
	}
	return BIR_OK;
}

/* Read 32-bit integer in littlendian order */
static int32
rdint32(BMPReader *br)
{
	int32   i;
	int     c;

	i = rdbyte(c, br);
	i |= rdbyte(c, br) << 8;
	i |= rdbyte(c, br) << 16;
	i |= rdbyte(c, br) << 24;
	return i;			/* -1 on EOF */
}

/* Read 16-bit unsigned integer in littlendian order */
static int
rduint16(BMPReader *br)
{
	int     i;
	int     c;
	
	i = rdbyte(c, br);
	i |= rdbyte(c, br) << 8;
	return i;			/* -1 on EOF */
}

/* Seek on reader or return 0 (BIR_OK) on success */
static int
rdseek(uint32 pos, BMPReader *br)
{
	if (pos == br->fpos)
		return BIR_OK;
	if (br->seek == NULL || (*br->seek)(pos, br->c_data) != 0)
		return BIR_SEEKERR;
	br->fpos = pos;
	return BIR_OK;
}

/* Open BMP stream for reading and get first scanline */
BMPReader *
BMPopenReader(int (*cget)(void *), int (*seek)(uint32, void *), void *c_data)
{
	BMPReader       *br;
	uint32		bmPos, hdrSiz, palSiz;
	int		magic[2];		/* check magic number */

	if (cget == NULL)
		return NULL;
	magic[0] = (*cget)(c_data);
	if (magic[0] != 'B')
		return NULL;
	magic[1] = (*cget)(c_data);
	if (magic[1] != 'M' && magic[1] != 'A')
		return NULL;
	br = (BMPReader *)calloc(1, sizeof(BMPReader));
	if (br == NULL)
		return NULL;
	br->cget = cget;
	br->seek = seek;
	br->c_data = c_data;
	br->hdr = (BMPHeader *)malloc(sizeof(BMPHeader));
	if (br->hdr == NULL)
		goto err;
	br->fpos = 2;
					/* read & verify file header */
	(void)rdint32(br);			/* file size */
	(void)rdint32(br);			/* reserved word */
	bmPos = rdint32(br);			/* offset to bitmap */
	hdrSiz = 2 + 3*4 + rdint32(br);		/* header size */
	if (hdrSiz < 2 + 6*4 + 2*2 + 6*4)
		goto err;
	br->hdr->width = rdint32(br);		/* bitmap width */
	br->hdr->height = rdint32(br);		/* bitmap height */
	if (((br->hdr->width <= 0) | (br->hdr->height == 0)))
		goto err;
	if ((br->hdr->yIsDown = br->hdr->height < 0))
		br->hdr->height = -br->hdr->height;
	if (rduint16(br) != 1)			/* number of planes */
		goto err;
	br->hdr->bpp = rduint16(br);		/* bits per pixel */
	br->hdr->compr = rdint32(br);		/* compression mode */
	(void)rdint32(br);			/* bitmap size */
	br->hdr->hRes = rdint32(br);		/* horizontal resolution */
	br->hdr->vRes = rdint32(br);		/* vertical resolution */
	br->hdr->nColors = rdint32(br);		/* # colors used */
	if (!br->hdr->nColors && br->hdr->bpp <= 8)
		br->hdr->nColors = 1<<br->hdr->bpp;
	br->hdr->impColors = rdint32(br);       /* # important colors */
	if (br->hdr->impColors < 0)
		goto err;			/* catch premature EOF */
	if (!BMPheaderOK(br->hdr))
		goto err;
	palSiz = sizeof(RGBquad)*br->hdr->nColors;
	if (br->hdr->impColors <= 0)
		br->hdr->impColors = br->hdr->nColors;
						/* extend header */
	if (bmPos < hdrSiz + palSiz)
		goto err;
	br->hdr->infoSiz = bmPos - (hdrSiz + palSiz);
	if (br->hdr->nColors > 0 || br->hdr->infoSiz > 0) {
		br->hdr = (BMPHeader *)realloc((void *)br->hdr,
					sizeof(BMPHeader) +
					palSiz + br->hdr->infoSiz);
		if (br->hdr == NULL)
			goto err;
	}
						/* read colors or fields */
	if (br->hdr->compr == BI_BITFIELDS) {
		BMPbitField(br->hdr)[0] = (uint32)rdint32(br);
		BMPbitField(br->hdr)[1] = (uint32)rdint32(br);
		BMPbitField(br->hdr)[2] = (uint32)rdint32(br);
	} else if (rdbytes((char *)br->hdr->palette, palSiz, br) != BIR_OK)
		goto err;
						/* read add'l information */
	if (rdbytes(BMPinfo(br->hdr), br->hdr->infoSiz, br) != BIR_OK)
		goto err;
						/* read first scanline */
	br->scanline = (uint8 *)calloc(getScanSiz(br->hdr), sizeof(uint8));
	if (br->scanline == NULL)
		goto err;
	br->yscan = -1;
	if (seek != NULL && ((br->hdr->compr == BI_RLE8) |
					(br->hdr->compr == BI_RLE4))) {
		BMPReader       *newbr = (BMPReader *)realloc((void *)br,
						sizeof(BMPReader) +
						sizeof(br->scanpos[0]) *
							br->hdr->height);
		if (newbr == NULL)
			goto err;
		br = newbr;
		memset((void *)(br->scanpos + 1), 0,
				sizeof(br->scanpos[0])*br->hdr->height);
	}
	br->scanpos[0] = br->fpos;
	if (BMPreadScanline(br) == BIR_OK)
		return br;
err:
	if (br->hdr != NULL)
		free((void *)br->hdr);
	if (br->scanline != NULL)
		free((void *)br->scanline);
	free((void *)br);
	return NULL;
}

/* Determine if image is grayscale */
int
BMPisGrayscale(const BMPHeader *hdr)
{
	const RGBquad   *rgbp;
	int		n;

	if (hdr == NULL)
		return -1;
	if (hdr->bpp > 8)		/* assume they had a reason for it */
		return 0;
	for (rgbp = hdr->palette, n = hdr->impColors; n-- > 0; rgbp++)
		if ((rgbp->r != rgbp->g) | (rgbp->g != rgbp->b))
			return 0;
	return 1;			/* all colors neutral in map */
}

/* Read and decode next BMP scanline */
int
BMPreadScanline(BMPReader *br)
{
	int     n;
	int8    *sp;

	if (br->yscan + 1 >= br->hdr->height)
		return BIR_EOF;
	br->yscan++;			/* prepare to read */
	n = getScanSiz(br->hdr);	/* reading uncompressed data? */
	if (br->hdr->compr == BI_UNCOMPR || br->hdr->compr == BI_BITFIELDS)
		return rdbytes((char *)br->scanline, n, br);
	/*
	 * RLE4/RLE8 Decoding
	 *
	 * Certain aspects of this scheme are completely insane, so
	 * we don't support them.  Fortunately, they rarely appear.
	 * One is the mid-file EOD (0x0001) and another is the ill-conceived
	 * "delta" (0x0002), which is like a "goto" statement for bitmaps.
	 * Whoever thought this up should be wrestled to the ground and told
	 * why it's impossible to support such a scheme in any reasonable way.
	 * Also, RLE4 mode allows runs to stop halfway through a byte,
	 * which is likewise uncodeable, so we don't even try.
	 * Finally, the scanline break is ambiguous -- we assume here that
	 * it is required at the end of each scanline, though I haven't
	 * found anywhere this is written.  Otherwise, we would read to
	 * the end of the scanline, assuming the next bit of data belongs
	 * the following scan.  If a break follows the last pixel, as it
	 * seems to in the files I've tested out of Photoshop, you end up
	 * painting every other line black.  Also, I assume any skipped
	 * pixels are painted with color 0, which is often black.  Nowhere
	 * is it specified what we should assume for missing pixels.  This
	 * is undoubtedly the most brain-dead format I've ever encountered.
	 */
	sp = (int8 *)br->scanline;
	n = br->hdr->width;
	if (br->hdr->compr == BI_RLE4)
		n = (n + 1) >> 1;
	while (n > 0) {
		int     skipOdd, len, val;

		if (rdbyte(len, br) == EOF)
			return BIR_TRUNCATED;
		if (len > 0) {		/* got a run */
			if (br->hdr->compr == BI_RLE4) {
				if (len & 1)
					return BIR_UNSUPPORTED;
				len >>= 1;
			}
			if (len > n)
				return BIR_RLERROR;
			if (rdbyte(val, br) == EOF)
				return BIR_TRUNCATED;
			n -= len;
			while (len--)
				*sp++ = val;
			continue;
		}
					/* check for escape */
		switch (rdbyte(len, br)) {
		case EOF:
			return BIR_TRUNCATED;
		case 0:			/* end of line */
			while (n--)
				*sp++ = 0;
			/* leaves n == -1 as flag for test after loop */
			continue;
		case 1:			/* end of bitmap */
		case 2:			/* delta */
			return BIR_UNSUPPORTED;
		}
					/* absolute mode */
		if (br->hdr->compr == BI_RLE4) {
			if (len & 1)
				return BIR_UNSUPPORTED;
			len >>= 1;
		}
		skipOdd = len & 1;
		if (len > n)
			return BIR_RLERROR;
		n -= len;
		while (len--) {
			if (rdbyte(val, br) == EOF)
				return BIR_TRUNCATED;
			*sp++ = val;
		}
		if (skipOdd && rdbyte(val, br) == EOF)
			return BIR_TRUNCATED;
	}
					/* verify break at end of line */
	if (!n && (rdbyte(n, br) != 0 || (rdbyte(n, br) != 0 &&
				(n != 1 || br->yscan != br->hdr->height-1))))
		return BIR_RLERROR;
	if (br->seek != NULL)		/* record next scanline position */
		br->scanpos[br->yscan + 1] = br->fpos;
	return BIR_OK;
}

/* Read a specific scanline */
int
BMPseekScanline(int y, BMPReader *br)
{
	int     rv;
					/* check arguments */
	if (br == NULL)
		return BIR_EOF;
	if (y < 0)
		return BIR_SEEKERR;
	if (y >= br->hdr->height)
		return BIR_EOF;
					/* already read? */
	if (y == br->yscan)
		return BIR_OK;
					/* shall we seek? */
	if (y != br->yscan + 1 && br->seek != NULL) {
		int     yseek;
		uint32  seekp;
		if (br->hdr->compr == BI_UNCOMPR ||
					br->hdr->compr == BI_BITFIELDS) {
			yseek = y;
			seekp = br->scanpos[0] + y*getScanSiz(br->hdr);
		} else {
			yseek = br->yscan + 1;
			while (yseek < y && br->scanpos[yseek+1] != 0)
				++yseek;
			if (y < yseek && br->scanpos[yseek=y] == 0)
				return BIR_SEEKERR;
			seekp = br->scanpos[yseek];
		}
		if ((rv = rdseek(seekp, br)) != BIR_OK)
			return rv;
		br->yscan = yseek - 1;
	} else if (y < br->yscan)       /* else we can't back up */
		return BIR_SEEKERR;
					/* read until we get there */
	while (br->yscan < y)
		if ((rv = BMPreadScanline(br)) != BIR_OK)
			return rv;
	return BIR_OK;
}

/* Get ith pixel from last scanline */
RGBquad
BMPdecodePixel(int i, const BMPReader *br)
{
	static const uint32     std16mask[3] = {0x7c00, 0x3e0, 0x1f};
	static const RGBquad    black = {0, 0, 0, 0};
	const uint32		*mask;
	const uint8		*pp;
	uint32			pval, v;
	RGBquad			cval;
	
	if (((br == NULL) | (i < 0)) || i >= br->hdr->width)
		return black;

	cval.padding = 0;

	switch (br->hdr->bpp) {
	case 24:
		pp = br->scanline + 3*i;
		cval.b = *pp++;
		cval.g = *pp++;
		cval.r = *pp;
		return cval;
	case 32:
		if (br->hdr->compr == BI_UNCOMPR)
			return ((RGBquad *)br->scanline)[i];
						/* convert bit fields */
		pp = br->scanline + 4*i;
		pval = *pp++;
		pval |= *pp++ << 8;
		pval |= *pp++ << 16;
		pval |= *pp << 24;
		mask = BMPbitField(br->hdr);
		v = pval & mask[0];
		while (v & ~0xff) v >>= 8;
		cval.r = v;
		v = pval & mask[1];
		while (v & ~0xff) v >>= 8;
		cval.g = v;
		v = pval & mask[2];
		while (v & ~0xff) v >>= 8;
		cval.b = v;
		return cval;
	case 8:
		return br->hdr->palette[br->scanline[i]];
	case 1:
		return br->hdr->palette[br->scanline[i>>3]>>(7-(i&7)) & 1];
	case 4:
		return br->hdr->palette[br->scanline[i>>1]>>(i&1?4:0) & 0xf];
	case 16:
		pp = br->scanline + 2*i;
		pval = *pp++;
		pval |= *pp++ << 8;
		mask = std16mask;
		if (br->hdr->compr == BI_BITFIELDS)
			mask = BMPbitField(br->hdr);
		cval.r = ((pval & mask[0]) << 8) / (mask[0] + 1);
		cval.g = ((pval & mask[1]) << 8) / (mask[1] + 1);
		cval.b = ((pval & mask[2]) << 8) / (mask[2] + 1);
		return cval;
	}
	return black;				/* should never happen */
}

/* Free BMP reader resources */
void
BMPfreeReader(BMPReader *br)
{
	if (br == NULL)
		return;
	free((void *)br->hdr);
	free((void *)br->scanline);
	free((void *)br);
}

/* stdio getc() callback */
int
stdio_getc(void *p)
{
	if (!p)
		return EOF;
	return getc((FILE *)p);
}

/* stdio putc() callback */
void
stdio_putc(int c, void *p)
{
	if (p)
		putc(c, (FILE *)p);
}

/* stdio fseek() callback */
int
stdio_fseek(uint32 pos, void *p)
{
	if (!p)
		return -1;
	return fseek((FILE *)p, (long)pos, 0);
}

/* Allocate uncompressed (24-bit) RGB header */
BMPHeader *
BMPtruecolorHeader(int xr, int yr, int infolen)
{
	BMPHeader       *hdr;
	
	if (xr <= 0 || yr <= 0 || infolen < 0)
		return NULL;
	hdr = (BMPHeader *)malloc(sizeof(BMPHeader) - sizeof(hdr->palette) +
					infolen);
	if (hdr == NULL)
		return NULL;
	hdr->width = xr;
	hdr->height = yr;
	hdr->yIsDown = 0;			/* default to upwards order */
	hdr->bpp = 24;
	hdr->compr = BI_UNCOMPR;
	hdr->hRes = hdr->vRes = 2835;		/* default to 72 ppi */
	hdr->nColors = hdr->impColors = 0;
	hdr->infoSiz = infolen;
	return hdr;
}

/* Allocate color-mapped header (defaults to minimal grayscale) */
BMPHeader *
BMPmappedHeader(int xr, int yr, int infolen, int ncolors)
{
	int		n;
	BMPHeader       *hdr;
	
	if (xr <= 0 || yr <= 0 || infolen < 0 || ncolors < 2)
		return NULL;
	if (ncolors <= 2)
		n = 1;
	else if (ncolors <= 16)
		n = 4;
	else if (ncolors <= 256)
		n = 8;
	else
		return NULL;
	hdr = (BMPHeader *)malloc(sizeof(BMPHeader) +
					sizeof(RGBquad)*((size_t)1<<n) -
					sizeof(hdr->palette) +
					infolen);
	if (hdr == NULL)
		return NULL;
	hdr->width = xr;
	hdr->height = yr;
	hdr->yIsDown = 0;			/* default to upwards order */
	hdr->bpp = n;
	hdr->compr = BI_UNCOMPR;		/* compression needs seek */
	hdr->hRes = hdr->vRes = 2835;		/* default to 72 ppi */
	hdr->nColors = ncolors;
	hdr->impColors = 0;			/* says all colors important */
	hdr->infoSiz = infolen;
	memset((void *)hdr->palette, 0, sizeof(RGBquad)*((size_t)1<<n) + infolen);
	for (n = ncolors; n--; )
		hdr->palette[n].r = hdr->palette[n].g =
			hdr->palette[n].b = n*255/(ncolors-1);
	return hdr;
}

					/* put byte to writer */
#define wrbyte(c,bw)    ( (*(bw)->cput)(c,(bw)->c_data), \
				++(bw)->fpos > (bw)->flen ? \
					((bw)->flen = (bw)->fpos) : \
					(bw)->fpos )

/* Write out a string of bytes */
static void
wrbytes(char *bp, uint32 n, BMPWriter *bw)
{
	while (n--)
		wrbyte(*bp++, bw);
}

/* Write 32-bit integer in littlendian order */
static void
wrint32(int32 i, BMPWriter *bw)
{
	wrbyte(i& 0xff, bw);
	wrbyte(i>>8 & 0xff, bw);
	wrbyte(i>>16 & 0xff, bw);
	wrbyte(i>>24 & 0xff, bw);
}

/* Write 16-bit unsigned integer in littlendian order */
static void
wruint16(uint16 ui, BMPWriter *bw)
{
	wrbyte(ui & 0xff, bw);
	wrbyte(ui>>8 & 0xff, bw);
}

/* Seek to the specified file position, returning 0 (BIR_OK) on success */
static int
wrseek(uint32 pos, BMPWriter *bw)
{
	if (pos == bw->fpos)
		return BIR_OK;
	if (bw->seek == NULL)
		return BIR_SEEKERR;
	if ((*bw->seek)(pos, bw->c_data) != 0)
		return BIR_SEEKERR;
	bw->fpos = pos;
	if (pos > bw->flen)
		bw->flen = pos;
	return BIR_OK;
}

/* Open BMP stream for writing */
BMPWriter *
BMPopenWriter(void (*cput)(int, void *), int (*seek)(uint32, void *),
			void *c_data, BMPHeader *hdr)
{
	BMPWriter       *bw;
	uint32		hdrSiz, palSiz, scanSiz, bmSiz;
						/* check arguments */
	if (cput == NULL)
		return NULL;
	if (!BMPheaderOK(hdr))
		return NULL;
	if ((hdr->bpp == 16) | (hdr->compr == BI_RLE4))
		return NULL;			/* unsupported */
/* no seek means we may have the wrong file length, but most app's don't care
	if (seek == NULL && ((hdr->compr == BI_RLE8) | (hdr->compr == BI_RLE4)))
		return NULL;
*/
						/* compute sizes */
	hdrSiz = 2 + 6*4 + 2*2 + 6*4;
	if (hdr->compr == BI_BITFIELDS)
		hdrSiz += sizeof(uint32)*3;
	palSiz = sizeof(RGBquad)*hdr->nColors;
	scanSiz = getScanSiz(hdr);
	bmSiz = hdr->height*scanSiz;		/* wrong if compressed */
						/* initialize writer */
	bw = (BMPWriter *)malloc(sizeof(BMPWriter));
	if (bw == NULL)
		return NULL;
	bw->hdr = hdr;
	bw->yscan = 0;
	bw->scanline = (uint8 *)calloc(scanSiz, sizeof(uint8));
	if (bw->scanline == NULL) {
		free((void *)bw);
		return NULL;
	}
	bw->fbmp = hdrSiz + palSiz + hdr->infoSiz;
	bw->fpos = bw->flen = 0;
	bw->cput = cput;
	bw->seek = seek;
	bw->c_data = c_data;
						/* write out header */
	wrbyte('B', bw); wrbyte('M', bw);       /* magic number */
	wrint32(bw->fbmp + bmSiz, bw);		/* file size */
	wrint32(0, bw);				/* reserved word */
	wrint32(bw->fbmp, bw);			/* offset to bitmap */
	wrint32(hdrSiz - bw->fpos, bw);		/* info header size */
	wrint32(hdr->width, bw);		/* bitmap width */
	if (hdr->yIsDown)			/* bitmap height */
		wrint32(-hdr->height, bw);
	else
		wrint32(hdr->height, bw);
	wruint16(1, bw);			/* number of planes */
	wruint16(hdr->bpp, bw);			/* bits per pixel */
	wrint32(hdr->compr, bw);		/* compression mode */
	wrint32(bmSiz, bw);			/* bitmap size */
	wrint32(hdr->hRes, bw);			/* horizontal resolution */
	wrint32(hdr->vRes, bw);			/* vertical resolution */
	wrint32(hdr->nColors, bw);		/* # colors used */
	wrint32(hdr->impColors, bw);		/* # important colors */
						/* write out color palette */
	wrbytes((char *)hdr->palette, palSiz, bw);
						/* write add'l information */
	wrbytes(BMPinfo(hdr), hdr->infoSiz, bw);
#ifndef NDEBUG
	if (bw->fpos != bw->fbmp) {
		fputs("Coding error 1 in BMPopenWriter\n", stderr);
		exit(1);
	}
#endif
	return bw;
}

/* Find position of next run of 5 or more identical bytes, or 255 if none */
static int
findNextRun(const int8 *bp, int len)
{
	int     pos, cnt;
						/* look for run */
	for (pos = 0; (len > 0) & (pos < 255); pos++, bp++, len--) {
		if (len < 5)			/* no hope left? */
			continue;
		cnt = 1;			/* else let's try it */
		while (bp[cnt] == bp[0])
			if (++cnt >= 5)
				return pos;     /* long enough */
	}
	return pos;				/* didn't find any */
}
				
/* Write the current scanline */
int
BMPwriteScanline(BMPWriter *bw)
{
	const int8      *sp;
	int		n;

	if (bw->yscan >= bw->hdr->height)
		return BIR_EOF;
						/* writing uncompressed? */
	if (bw->hdr->compr == BI_UNCOMPR || bw->hdr->compr == BI_BITFIELDS) {
		uint32  scanSiz = getScanSiz(bw->hdr);
		uint32  slpos = bw->fbmp + bw->yscan*scanSiz;
		if (wrseek(slpos, bw) != BIR_OK)
			return BIR_SEEKERR;
		wrbytes((char *)bw->scanline, scanSiz, bw);
		bw->yscan++;
		return BIR_OK;
	}
	/*
	 * RLE8 Encoding
	 *
	 * See the notes in BMPreadScanline() on this encoding.  Needless
	 * to say, we avoid the nuttier aspects of this specification.
	 * We also assume that every scanline ends in a line break
	 * (0x0000) except for the last, which ends in a bitmap break
	 * (0x0001).  We don't support RLE4 at all; it's too awkward.
	 */
	sp = (const int8 *)bw->scanline;
	n = bw->hdr->width;
	while (n > 0) {
		int     cnt, val;
		cnt = findNextRun(sp, n);       /* 0-255 < n */
		if (cnt >= 3) {			/* output absolute */
			int     skipOdd = cnt & 1;
			wrbyte(0, bw);
			wrbyte(cnt, bw);
			n -= cnt;
			while (cnt--)
				wrbyte(*sp++, bw);
			if (skipOdd)
				wrbyte(0, bw);
		}
		if (n <= 0)			/* was that it? */
			break;
		val = *sp++;			/* output run */
		for (cnt = 1; --n && cnt < 255; cnt++, sp++)
			if (*sp != val)
				break;
		wrbyte(cnt, bw);
		wrbyte(val, bw);
	}
	bw->yscan++;				/* write line break or EOD */
	if (bw->yscan == bw->hdr->height) {
		wrbyte(0, bw); wrbyte(1, bw);   /* end of bitmap marker */
		if (wrseek(2, bw) != BIR_OK)
			return BIR_OK;		/* no one may care */
		wrint32(bw->flen, bw);		/* correct file length */
		if (wrseek(34, bw) != BIR_OK)
			return BIR_OK;
		wrint32(bw->flen-bw->fbmp, bw); /* correct bitmap length */
	} else {
		wrbyte(0, bw); wrbyte(0, bw);   /* end of line marker */
	}
	return BIR_OK;
}

/* Free BMP writer resources */
void
BMPfreeWriter(BMPWriter *bw)
{
	if (bw == NULL)
		return;
	free((void *)bw->hdr);
	free((void *)bw->scanline);
	free((void *)bw);
}
