/* pbm.c:
 *
 * portable bit map (pbm) format images
 *
 * jim frost 09.27.89
 *
 * patched by W. David Higgins (wdh@mkt.csd.harris.com) to support
 * raw-format PBM files.
 *
 * patched by Ian MacPhedran (macphed@dvinci.usask.ca) to support
 * PGM and PPM files (03-July-1990)
 */

#include "xli.h"
#include "imagetypes.h"
#include "pbm.h"

static int IntTable[256];
static unsigned int Initialized = 0;

#define NOTINT  -1
#define COMMENT -2
#define SPACE   -3
#define NEWLINE -4

#define BADREAD    0		/* read error */
#define NOTPBM     1		/* not a pbm file */
#define PBMNORMAL  2		/* pbm normal type file */
#define PBMCOMPACT 3		/* pbm compacty type file */
#define PBMRAWBITS 4		/* pbm raw bits type file */
#define PGMNORMAL  5		/* pgm normal type file */
#define PGMRAWBITS 6		/* pgm raw bytes type file */
#define PPMNORMAL  7		/* ppm normal type file */
#define PPMRAWBITS 8		/* ppm raw bytes type file */

static void initializeTable(void)
{
	unsigned int a;

	for (a = 0; a < 256; a++)
		IntTable[a] = NOTINT;
	IntTable['#'] = COMMENT;
	IntTable['\n'] = NEWLINE;
	IntTable['\r'] = IntTable['\t'] = IntTable[' '] = SPACE;
	IntTable['0'] = 0;
	IntTable['1'] = 1;
	IntTable['2'] = 2;
	IntTable['3'] = 3;
	IntTable['4'] = 4;
	IntTable['5'] = 5;
	IntTable['6'] = 6;
	IntTable['7'] = 7;
	IntTable['8'] = 8;
	IntTable['9'] = 9;
	Initialized = 1;
}

static int pbmReadChar(ZFILE * zf)
{
	int c;

	if ((c = zgetc(zf)) == EOF) {
		zclose(zf);
		return (-1);
	}
	if (IntTable[c] == COMMENT)
		do {
			if ((c = zgetc(zf)) == EOF)
				return (-1);
		} while (IntTable[c] != NEWLINE);
	return (c);
}

static int pbmReadInt(ZFILE * zf)
{
	int c, value;

	for (;;) {
		c = pbmReadChar(zf);
		if (c < 0)
			return (-1);
		if (IntTable[c] >= 0)
			break;
	};

	value = IntTable[c];
	for (;;) {
		c = pbmReadChar(zf);
		if (c < 0)
			return (-1);
		if (IntTable[c] < 0)
			return (value);
		value = (value * 10) + IntTable[c];
	}
}

static int pbmReadRawInt(ZFILE * zf, int depth)
{
	int i, src, src2;

	src = 0;
	for (i = 0; i < depth; i += 8) {
		if ((src2 = zgetc(zf)) == EOF)
			return EOF;
		src = (src << 8) | src2;
	}

	return src;
}

static int isPBM(ZFILE * zf, char *name,
	unsigned int *width, unsigned int *height, unsigned int *maxval,
	unsigned int verbose)
{
	byte buf[4];

	if (!Initialized)
		initializeTable();

	if (zread(zf, buf, 2) != 2)
		return (NOTPBM);

	if (memToVal((byte *) buf, 2) == memToVal((byte *) "P1", 2)) {
		if (((*width = pbmReadInt(zf)) < 0) ||
				((*height = pbmReadInt(zf)) < 0))
			return (NOTPBM);

		*maxval = 1;
		if (verbose) {
			printf("%s is a %dx%d PBM image\n",
				name, *width, *height);
		}
		return (PBMNORMAL);
	}

	if (memToVal((byte *) buf, 2) == memToVal((byte *) "P4", 2)) {
		if (((*width = pbmReadInt(zf)) < 0) ||
				((*height = pbmReadInt(zf)) < 0))
			return (NOTPBM);

		*maxval = 1;
		if (verbose)
			printf("%s is a %dx%d RawBits PBM image\n",
				name, *width, *height);
		return (PBMRAWBITS);
	}

	if (memToVal(buf, 2) == 0x2a17) {
		if (zread(zf, buf, 4) != 4)
			return (NOTPBM);

		*width = memToVal((byte *) buf, 2);
		*height = memToVal((byte *) (buf + 2), 2);
		*maxval = 1;
		if (verbose) {
			printf("%s is a %dx%d Compact PBM image\n",
				name, *width, *height);
		}
		return (PBMCOMPACT);
	}

	if (memToVal(buf, 2) == memToVal((byte *) "P2", 2)) {
		if (((*width = pbmReadInt(zf)) < 0)
				|| ((*height = pbmReadInt(zf)) < 0))
			return (NOTPBM);

		*maxval = pbmReadInt(zf);
		if (verbose) {
			printf("%s is a %dx%d PGM image with %d levels\n",
				name, *width, *height, (*maxval + 1));
		}
		return (PGMNORMAL);
	}

	if (memToVal(buf, 2) == memToVal((byte *) "P5", 2)) {
		if (((*width = pbmReadInt(zf)) < 0)
				|| ((*height = pbmReadInt(zf)) < 0))
			return (NOTPBM);

		*maxval = pbmReadInt(zf);
		if (verbose)
			printf("%s is a %dx%d Raw PGM image with %d levels\n",
				name, *width, *height, (*maxval + 1));
		return (PGMRAWBITS);
	}

	if (memToVal(buf, 2) == memToVal((byte *) "P3", 2)) {
		if (((*width = pbmReadInt(zf)) < 0) ||
				((*height = pbmReadInt(zf)) < 0))
			return (NOTPBM);

		*maxval = pbmReadInt(zf);
		if (verbose) {
			printf("%s is a %dx%d PPM image with %d levels\n",
				name, *width, *height, (*maxval + 1));
		}
		return (PPMNORMAL);
	}

	if (memToVal(buf, 2) == memToVal((byte *) "P6", 2)) {
		if (((*width = pbmReadInt(zf)) < 0) ||
				((*height = pbmReadInt(zf)) < 0))
			return (NOTPBM);

		*maxval = pbmReadInt(zf);
		if (verbose) {
			printf("%s is a %dx%d Raw PPM image with %d levels\n",
				name, *width, *height, (*maxval + 1));
		}
		return (PPMRAWBITS);
	}
	return (NOTPBM);
}

int pbmIdent(char *fullname, char *name)
{
	ZFILE *zf;
	unsigned int width, height, maxval, ret;

	if (!(zf = zopen(fullname)))
		return (0);

	ret = isPBM(zf, name, &width, &height, &maxval, (unsigned int) 1);
	zclose(zf);

	return (ret != NOTPBM);
}


Image *pbmLoad(char *fullname, ImageOptions * image_ops, boolean verbose)
{
	ZFILE *zf;
	char *name = image_ops->name;
	Image *image = 0;
	int pbm_type;
	unsigned int x, y;
	unsigned int width, height, maxval, fmaxval, depth, fdepth;
	unsigned int linelen;
	byte srcmask, destmask;
	byte *destptr = 0, *destline;
	int src = -1, size;
	unsigned int numbytes, numread;

	if (!(zf = zopen(fullname)))
		return (NULL);

	pbm_type = isPBM(zf, name, &width, &height, &maxval, verbose);
	if (pbm_type == NOTPBM) {
		zclose(zf);
		return (NULL);
	}
	znocache(zf);

	switch (pbm_type) {
	case PBMNORMAL:
		image = newBitImage(width, height);
		image->title = dupString(name);
		linelen = (width / 8) + (width % 8 ? 1 : 0);
		destline = image->data;
		for (y = 0; y < height; y++) {
			destptr = destline;
			destmask = 0x80;
			for (x = 0; x < width; x++) {
				do {
					if ((src = pbmReadChar(zf)) < 0) {
						fprintf(stderr, "pbmLoad: %s - Short image\n", name);
						zclose(zf);
						return (image);
					}
					if (IntTable[src] == NOTINT) {
						fprintf(stderr, "pbmLoad: %s - Bad image data\n", name);
						zclose(zf);
						return (image);
					}
				} while (IntTable[src] < 0);

				switch (IntTable[src]) {
				case 1:
					*destptr |= destmask;
				case 0:
					if (!(destmask >>= 1)) {
						destmask = 0x80;
						destptr++;
					}
					break;
				default:
					fprintf(stderr, "pbmLoad: %s - Bad image data\n", name);
					zclose(zf);
					return (image);
				}
			}
			destline += linelen;
		}
		break;

	case PBMRAWBITS:
		image = newBitImage(width, height);
		image->title = dupString(name);
		destline = image->data;
		linelen = (width + 7) / 8;
		numbytes = linelen * height;
		srcmask = 0;	/* force initial read */
		numread = 0;
		for (y = 0; y < height; y++) {
			destptr = destline;
			destmask = 0x80;
			if (srcmask != 0x80) {
				srcmask = 0x80;
				if ((numread < numbytes) && ((src = zgetc(zf)) == EOF)) {
					fprintf(stderr, "pbmLoad: %s - Short image\n", name);
					zclose(zf);
					return (image);
				}
				numread++;
			}
			for (x = 0; x < width; x++) {
				if (src & srcmask)
					*destptr |= destmask;
				if (!(destmask >>= 1)) {
					destmask = 0x80;
					destptr++;
				}
				if (!(srcmask >>= 1)) {
					srcmask = 0x80;
					if ((numread < numbytes) && ((src = zgetc(zf)) == EOF)) {
						fprintf(stderr, "pbmLoad: %s - Short image\n", name);
						zclose(zf);
						return (image);
					}
					numread++;
				}
			}
			destline += linelen;
		}
		break;

	case PBMCOMPACT:
		image = newBitImage(width, height);
		image->title = dupString(name);
		destline = image->data;
		linelen = (width / 8) + (width % 8 ? 1 : 0);
		srcmask = 0x80;
		destmask = 0x80;
		if ((src = zgetc(zf)) == EOF) {
			fprintf(stderr, "pbmLoad: %s - Short image\n", name);
			zclose(zf);
			return (image);
		}
		numread = 1;
		numbytes = width * height;
		numbytes = (numbytes / 8) + (numbytes % 8 ? 1 : 0);
		for (y = 0; y < height; y++) {
			destptr = destline;
			destmask = 0x80;
			for (x = 0; x < width; x++) {
				if (src & srcmask)
					*destptr |= destmask;
				if (!(destmask >>= 1)) {
					destmask = 0x80;
					destptr++;
				}
				if (!(srcmask >>= 1)) {
					srcmask = 0x80;
					if ((numread < numbytes) && ((src = zgetc(zf)) == EOF)) {
						fprintf(stderr, "pbmLoad: %s - Short image\n", name);
						zclose(zf);
						return (image);
					}
					numread++;
				}
			}
			destline += linelen;
		}
		break;

	case PGMRAWBITS:
		fmaxval = maxval;
		if (maxval > 0xff)
			maxval = 0xff;
		depth = colorsToDepth(maxval + 1);
		image = newRGBImage(width, height, depth);
		/* use simple ramp for grey scale */
		for (y = 0; y <= maxval; y++) {
			*(image->rgb.red + y) = PM_SCALE(y, maxval, 0xffff);
			*(image->rgb.green + y) = PM_SCALE(y, maxval, 0xffff);
			*(image->rgb.blue + y) = PM_SCALE(y, maxval, 0xffff);
		}
		image->rgb.used = maxval + 1;
		image->gamma = 1.0;	/* overide xli IRGB guess */
		image->title = dupString(name);
		size = height * width;

		if (fmaxval <= 0xff) {
			/* read in the image in a chunk */
			if (zread(zf, image->data, size) != size) {
				fprintf(stderr, "pbmLoad: %s - Short image\n", name);
				zclose(zf);
				return (image);
			}
		} else {
			fdepth = colorsToDepth(fmaxval + 1);
			for (y = 0; y < size; y++) {
				if ((src = pbmReadRawInt(zf, fdepth)) == EOF) {
					fprintf(stderr, "pbmLoad: %s - Short image\n", name);
					zclose(zf);
					return (image);
				}
				*(destptr++) = PM_SCALE(src, fmaxval, 0xff);
			}
		}
		break;

	case PGMNORMAL:
		fmaxval = maxval;
		if (maxval > 0xff)
			maxval = 0xff;
		depth = colorsToDepth(maxval + 1);
		image = newRGBImage(width, height, depth);
		for (y = 0; y <= maxval; y++) {
			/* As in sunraster.c, use simple ramp for grey scale */
			*(image->rgb.red + y) = PM_SCALE(y, maxval, 0xffff);
			*(image->rgb.green + y) = PM_SCALE(y, maxval, 0xffff);
			*(image->rgb.blue + y) = PM_SCALE(y, maxval, 0xffff);
		}
		image->rgb.used = maxval + 1;
		image->gamma = 1.0;	/* overide xli IRGB guess */
		image->title = dupString(name);
		destptr = image->data;
		size = height * width;
		for (y = 0; y < size; y++) {
			if ((src = pbmReadInt(zf)) < 0) {
				fprintf(stderr, "pbmLoad: %s - Short image\n", name);
				zclose(zf);
				return (image);
			}
			*(destptr++) = PM_SCALE(src, fmaxval, 0xff);
		}
		break;

	case PPMRAWBITS:
		/* this is nice because the bit format is exactly what we want
		 * except for scaling.
		 */

		image = newTrueImage(width, height);
		image->title = dupString(name);
		size = height * width * 3;
		if (maxval <= 0xff) {
			if (zread(zf, image->data, size) != size) {
				fprintf(stderr, "pbmLoad: %s - Short image\n",
					name);
				zclose(zf);
				return (image);
			}
			if (maxval != 0xff) {
				destptr = image->data;
				for (y = 0; y < size; y++) {
					*destptr = PM_SCALE(*destptr, maxval, 0xff);
					destptr++;
				}
			}
		} else {
			fdepth = colorsToDepth(maxval + 1);
			for (y = 0; y < size; y++) {
				if ((src = pbmReadRawInt(zf, fdepth)) == EOF) {
					fprintf(stderr, "pbmLoad: %s - Short image\n", name);
					zclose(zf);
					return (image);
				}
				*(destptr++) = PM_SCALE(src, maxval, 0xff);
			}
		}
		break;
	case PPMNORMAL:
		image = newTrueImage(width, height);
		image->title = dupString(name);
		size = height * width * 3;
		destptr = image->data;
		for (y = 0; y < size; y++) {
			if ((src = pbmReadInt(zf)) == EOF) {
				fprintf(stderr, "pbmLoad: %s - Short image\n",
					name);
				zclose(zf);
				return (image);
			}
			*(destptr++) = PM_SCALE(src, maxval, 0xff);
		}
		break;
	}
	read_trail_opt(image_ops, zf, image, verbose);
	zclose(zf);
	return (image);
}
