#ifdef __GNUC__
#  pragma implementation
#endif

#include <string.h>
#include <math.h>
#include <zlib.h>
#include <strstream.h>
#include <stdlib.h>
#include "png.H"

unsigned long Tchunk::crc_table[256];
bool Tchunk::crc_table_computed = false;

inline int min(int x, int y) {return (x < y) ? x : y;}

Tchunk::Tchunk()
	: len(0), data(0)
{
	int i; for (i=0; i<5; i++) type[i] = 0;
	if (!crc_table_computed) make_crc_table();
}

Tchunk::Tchunk(const char *typestring, int chunklen)
	: len(chunklen), data(0)
{
	strncpy(type,typestring,4);
	type[4] = '\0';
	if (len > 0) data = new unsigned char [len];
	if (!crc_table_computed) make_crc_table();
}

ostream& operator<<(ostream& o, const Tchunk& c)
{
	o << "'" << c.type << "', len=" << c.len;
	return o;
}

unsigned long Tchunk::read_uint4(istream& in)
{
	unsigned char ch1,ch2,ch3,ch4;
	in.get(ch1);
	in.get(ch2);
	in.get(ch3);
	in.get(ch4);
	return (ch1 << 24) | (ch2 << 16) | (ch3 << 8) | ch4;
}

bool Tchunk::write_uint4(ostream& out, unsigned long x) const
{
	unsigned char ch1,ch2,ch3,ch4;
	ch1 = (x >> 24) & 0xFF;
	ch2 = (x >> 16) & 0xFF;
	ch3 = (x >> 8) & 0xFF;
	ch4 = x & 0xFF;
	out.put(ch1);
	out.put(ch2);
	out.put(ch3);
	out.put(ch4);
	return out.good();
}

void Tchunk::make_crc_table()
{
	unsigned long c;
	int n,k;
	for (n=0; n<256; n++) {
		c = (unsigned long)n;
		for (k=0; k<8; k++) {
			if (c & 1)
				c = 0xedb88320UL ^ (c >> 1);
			else
				c = (c >> 1);
		}
		crc_table[n] = c;
	}
	crc_table_computed = true;
}

unsigned long Tchunk::update_crc(unsigned long crc, unsigned char *buf, int len)
{
	unsigned long c = crc;
	int n;
	for (n=0; n<len; n++) {
		c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
	}
	return c;
}

unsigned long Tchunk::crc(unsigned char *buf, unsigned char *typecode, int len)
{
	unsigned long c = 0xffffffffUL;
	c = update_crc(c,typecode,4);
	if (buf) c = update_crc(c,buf,len);
	return c ^ 0xffffffffUL;
}

bool Tchunk::read(istream& in)
{
	len = read_uint4(in);
	if (in.eof()) return false;
	int i;
	char ch;
	for (i=0; i<4; i++) {in.get(ch); type[i] = ch;}
	if (data) {delete [] data; data = 0;}
	if (len > 0) {
		data = new unsigned char [len];
		in.read(data,len);
	}
	const unsigned long c = read_uint4(in);
	const unsigned long c_calc = crc(data,(unsigned char *)type,len);
	if (c_calc != c) cerr << "*** crc error, crcinfile=" << hex << c << ", crc=" << hex << c_calc << "\n";
	return true;
}

bool Tchunk::write(ostream& out) const
{
	if (!write_uint4(out,len)) return false;
	int i;
	char ch;
	for (i=0; i<4; i++) {ch = type[i]; if (!out.put(ch)) return false;}
	if (len > 0) {
		out.write(data,len);
		if (!out.good()) return false;
	}
	const unsigned long crcvalue = crc(data,(unsigned char *)type,len);
	if (!write_uint4(out,crcvalue)) return false;
	return true;
}

static bool expectchunk(istream& in, const char *type, Tchunk& c, bool ignore_palette=false)
{
	while (1) {
		if (!c.read(in)) return false;
		if (!strcmp(c.typestr(),type)) {
			return true;
		} else if (c.typestr()[0] & 32) {
			cerr << "expectchunk " << type << ": passed ancillary chunk " << c.typestr() << "\n";
		} else if (ignore_palette && !strcmp(c.typestr(),"PLTE")) {
			cerr << "expectchunk " << type << ": passed ancillary palette chunk\n";
		}
	}
	return false;
}

static bool Uncompress(unsigned char *out, int& outlen, const char *in, int inlen)
// uncompresses in[0..inlen-1] into out[0..outlen-1]
// in png applications, you know outlen already beforehand from IHDR info
{
	uLongf destLen = outlen;
	const int zret = uncompress((Bytef *)out,&destLen,(const Bytef *)in,inlen);
	outlen = destLen;
	return (zret == Z_OK);
}

static bool Compress(unsigned char *out, int& outlen, const unsigned char *in, int inlen)
{
	uLongf destLen = outlen;
	const int level = Z_DEFAULT_COMPRESSION;	// Z_DEFAULT_COMPRESSION, or number 0..9 (0 gives no compression)
	const int zret = compress2((Bytef *)out, &destLen, (const Bytef *)in, inlen, level);
	outlen = destLen;
	if (zret == Z_MEM_ERROR) {
		cerr << "*** Out of memory in Compress\n";
	} else if (zret == Z_BUF_ERROR) {
		cerr << "*** Output buffer too short in Compress\n";
	} else if (zret == Z_STREAM_ERROR) {
		cerr << "*** Invalid compression level " << level << " in Compress\n";
	}
	return (zret == Z_OK);
}

typedef short int paethint;

inline paethint PaethPredictor(paethint a, paethint b, paethint c)
{
	// a=left, b=above, c=upper left
	const paethint p = a + b - c;		// initial estimate
	const paethint pa = abs(p-a);		// distances to a,b,c
	const paethint pb = abs(p-b);
	const paethint pc = abs(p-c);
	// return nearest of a,b,c
	// breaking ties in order a,b,c
	if (pa <= pb && pa <= pc)
		return a;
	else if (pb <= pc)
		return b;
	else
		return c;
}

static void DecodeFilter(const int filtertype, unsigned char u[], int N, int bpp, const unsigned char *prev)
// filtertype is one of 0,1,2,3,4 (None,Sub,Up,Average,Paeth),
// u is scanline to be decoded, N is number of bytes on scanline (NOTE not number of pixels!),
// bpp is bytes per pixel, prev is previous scanline.
// If prev is null pointer, assume all zeros on previous scanline
{
	int i;
	if (filtertype == 0) {
		// None
		;
	} else if (filtertype == 1) {
		// Sub
		for (i=bpp; i<N; i++) u[i]+= u[i-bpp];
	} else if (filtertype == 2) {
		// Up
		if (prev) {
			for (i=0; i<N; i++) u[i]+= prev[i];
		}
	} else if (filtertype == 3) {
		// Average
		if (prev) {
			for (i=0; i<min(bpp,N); i++) u[i]+= (prev[i] >> 1);
			for (i=bpp; i<N; i++) u[i]+= ((int(u[i-bpp]) + int(prev[i])) >> 1);
		} else {
			for (i=bpp; i<N; i++) u[i]+= (u[i-bpp] >> 1);
		}
	} else if (filtertype == 4) {
		// Paeth
		if (prev) {
			const int max_i = min(bpp,N);
			for (i=0; i<max_i; i++) u[i]+= PaethPredictor(0,prev[i],0);
			for (i=bpp; i<N; i++) u[i]+= PaethPredictor(u[i-bpp],prev[i],prev[i-bpp]);
		} else {
			for (i=bpp; i<N; i++) u[i]+= PaethPredictor(u[i-bpp],0,0);
		}
	}
}

static void EncodeFilter(const int filtertype, unsigned char out[], int N, int bpp,
						 const unsigned char *u, const unsigned char *prev)
// filtertype is one of 0,1,2,3,4 (None,Sub,Up,Average,Paeth),
// out is filtered scanline, N is number of bytes on scanline (NOTE not number of pixels!),
// bpp is bytes per pixel, u is scanline to be encoded (input),
// prev is previous scanline.
// If prev is null pointer, assume all zeros on previous scanline
{
	int i;
	if (filtertype == 0) {
		// None
		for (i=0; i<N; i++) out[i] = u[i];
	} else if (filtertype == 1) {
		// Sub
		const int max_i = min(bpp,N);
		for (i=0; i<max_i; i++) out[i] = u[i];
		for (i=bpp; i<N; i++) out[i] = u[i] - u[i-bpp];
	} else if (filtertype == 2) {
		// Up
		if (prev) {
			for (i=0; i<N; i++) out[i] = u[i] - prev[i];
		} else {
			for (i=0; i<N; i++) out[i] = u[i];
		}
	} else if (filtertype == 3) {
		// Average
		if (prev) {
			const int max_i = min(bpp,N);
			for (i=0; i<max_i; i++) out[i] = u[i] - (prev[i] >> 1);
			for (i=bpp; i<N; i++) out[i] = u[i] - ((int(u[i-bpp]) + int(prev[i])) >> 1);
		} else {
			for (i=bpp; i<N; i++) out[i] = u[i] - (u[i-bpp] >> 1);
		}
	} else if (filtertype == 4) {
		// Paeth
		if (prev) {
			const int max_i = min(bpp,N);
			for (i=0; i<max_i; i++) out[i] = u[i] - PaethPredictor(0,prev[i],0);
			for (i=bpp; i<N; i++) out[i] = u[i] - PaethPredictor(u[i-bpp],prev[i],prev[i-bpp]);
		} else {
			for (i=bpp; i<N; i++) out[i] = u[i] - PaethPredictor(u[i-bpp],0,0);
		}
	}
}

bool pngread(istream& in, Timage& image)
{
	Tchunk ihdr;
	if (!ihdr.read(in) || strcmp(ihdr.typestr(),"IHDR") || ihdr.length() != 13) {
		if (strcmp(ihdr.typestr(),"MEND"))
			cout << "pngread failed, ihdr.length() = " << ihdr.length() << ", str=" << ihdr.typestr() << "\n";
		return false;
	}
	const int w = ihdr.uint4ref(0);
	const int h = ihdr.uint4ref(4);
//	cout << "pngread: w=" << w << ", h=" << h << "\n";
	if (w == 0 || h == 0) return false;
	const int bitdepth = ihdr.dataref(8);
	if (bitdepth != 1 && bitdepth != 2 && bitdepth != 4 && bitdepth != 8 && bitdepth != 16) return false;
	const int colortype = ihdr.dataref(9);
	if (colortype != 0 && colortype != 2 && colortype != 3 && colortype != 4 && colortype != 6) return false;
	int bufflen=0,bytesperline=0;
	bool musthavepalette=false;
	bool mayhavepalette=false;
	int bytesperpixel = 0;
	if (colortype == 0) {
		// each pixel a grayscale sample, possible bitdepths 1,2,4,8,16
		bytesperpixel = int(ceil(bitdepth/8.0));
		bytesperline = int(ceil((bitdepth*w)/8.0));
	} else if (colortype == 2) {
		// each pixel an rgb triple, possible bitdepths 8,16
		bytesperpixel = 3*(bitdepth/8);
		bytesperline = 3*((bitdepth*w)/8);
		mayhavepalette = true;
	} else if (colortype == 3) {
		// each pixel a palette index, possible bitdepths 1,2,4,8
		musthavepalette = true;
		bytesperpixel = int(ceil(bitdepth/8.0));
		bytesperline = int(ceil((bitdepth*w)/8.0));
	} else if (colortype == 4) {
		// each pixel a grayscale and alpha sample, possible bitdepths 8,16
		bytesperpixel = 2*(bitdepth/8);
		bytesperline = 2*((bitdepth*w)/8);
	} else if (colortype == 6) {
		// each pixel rgba quadruple, possible bitdepths 8,16
		bytesperpixel = 4*(bitdepth/8);
		bytesperline = 4*((bitdepth*w)/8);
		mayhavepalette = true;
	}
	bufflen = bytesperline*h;
	bufflen+= h;		// all scanlines contain filter type byte
	const int compressionmethod = ihdr.dataref(10);
	if (compressionmethod != 0) return false;
	const int filtermethod = ihdr.dataref(11);
	if (filtermethod != 0) return false;
	const int interlacemethod = ihdr.dataref(12);
	if (interlacemethod != 0 && interlacemethod != 1) return false;
	if (interlacemethod == 1) {cerr << "interlaced images not supported\n"; return false;}
	image.alloc(w,h);
	unsigned char pal[256][3];
	int pal_len = 0;
	memset(pal,0,3*256);
	if (musthavepalette) {
		Tchunk plte;
		if (!expectchunk(in,"PLTE",plte)) return false;
		if (plte.length() % 3 != 0) return false;
		pal_len = plte.length()/3;
		if (pal_len < 1 || pal_len > 256) return false;
		memcpy(&pal[0][0],plte.data_addr(),plte.length());
	}
//	cout << "width=" << w << ", height=" << h << ", colortype=" << colortype << ", bitdepth=" << bitdepth
//		 << ", interlacemethod=" << interlacemethod << "\n";
	unsigned char *buff = new unsigned char [bufflen];
	Tchunk idat;
	if (!expectchunk(in,"IDAT",idat, true)) return false;
	strstream S;
	int Slen = 0;
	S.write(idat.data_addr(),idat.length());
	Slen+= idat.length();
//	cout << "Read IDAT of length " << idat.length() << "\n";
	while (1) {
		if (!idat.read(in)) return false;
		if (!strcmp(idat.typestr(),"IDAT")) {
			S.write(idat.data_addr(),idat.length());
			Slen+= idat.length();
//			cout << "Read IDAT of length " << idat.length() << "\n";
		} else
			break;
	}
	if (strcmp(idat.typestr(),"IEND")) {
		if (!expectchunk(in,"IEND",idat)) return false;
	}
	S << ends;
	char *charptr = S.str();
	if (!Uncompress(buff,bufflen,charptr,Slen)) {
		cerr << "uncompress failed\n";
		return false;
	}
	delete [] charptr;
	// now buff contains the uncompressed raw image data with each scanline preceded by filter info byte
	unsigned char *filterinfos = new unsigned char [h];
	int i,j;
	const int W = bytesperline + 1;
	const int FullyOpaque = 255;
//	cout << "bytesperline = " << bytesperline << "\n";
	for (i=0; i<h; i++) {
		filterinfos[i] = buff[W*i];
//		if (filterinfos[i] != 0) cerr << "warning: filterinfo=" << int(filterinfos[i]) << "\n";
		DecodeFilter(filterinfos[i],&buff[1+i*W],bytesperline,bytesperpixel, (i==0) ? 0 : &buff[1+(i-1)*W]);
		if (colortype == 0) {
			// each pixel a grayscale sample, possible bitdepths 1,2,4,8,16
			if (bitdepth == 8) {
				for (j=0; j<w; j++) {
					const int u = buff[1+i*W+j];
					image.set_red(i,j,u);
					image.set_green(i,j,u);
					image.set_blue(i,j,u);
					image.set_alpha(i,j,FullyOpaque);
				}
			} else {
				cerr << "unsupported colortype=0, bitdepth=" << bitdepth << "\n";
				return false;
			}
		} else if (colortype == 2) {
			// each pixel an rgb triple, possible bitdepths 8,16
			if (bitdepth == 8) {
				const int iW1 = i*W + 1;
				for (j=0; j<w; j++) {
					const int p = iW1+3*j;
					image.set_rgba(i,j,
								   (buff[p] << RED_SHIFT) | (buff[p+1] << GREEN_SHIFT)
								   | (buff[p+2] << BLUE_SHIFT) | FullyOpaque << ALPHA_SHIFT);
					/*
					image.set_red(i,j,buff[1+i*W+3*j]);
					image.set_green(i,j,buff[1+i*W+3*j+1]);
					image.set_blue(i,j,buff[1+i*W+3*j+2]);
					image.set_alpha(i,j,FullyOpaque);
					*/
				}
			} else {
				cerr << "unsupported colortype=2, bitdepth=" << bitdepth << "\n";
				return false;
			}
		} else if (colortype == 3) {
			// each pixel a palette index, possible bitdepths 1,2,4,8
			if (bitdepth == 8) {
				for (j=0; j<w; j++) {
					const int k = buff[1+i*W+j];
					if (k < 0 || k >= pal_len) continue;
					image.set_red(i,j,pal[k][0]);
					image.set_green(i,j,pal[k][1]);
					image.set_blue(i,j,pal[k][2]);
				}
			} else if (bitdepth == 4) {
				for (j=0; j<w; j++) {
					const int j1 = j/2;
					int k;
					if (j & 1) {
						k = buff[1+i*W+j1] & 0x0f;
					} else {
						k = (buff[1+i*W+j1] & 0xf0) >> 4;
					}
					if (k < 0 || k >= pal_len) continue;
					image.set_red(i,j,pal[k][0]);
					image.set_green(i,j,pal[k][1]);
					image.set_blue(i,j,pal[k][2]);
				}
			} else {
				// this shouldn't happen, but check anyway
				cerr << "unsupported colortype=3, bitdepth=" << bitdepth << "\n";
				return false;
			}
		} else if (colortype == 4) {
			// each pixel a grayscale and alpha sample, possible bitdepths 8,16
			if (bitdepth == 8) {
				for (j=0; j<w; j++) {
					image.set_red(i,j,buff[1+i*W+j]);
					image.set_alpha(i,j,buff[1+i*W+j+1]);
				}
			} else {
				cerr << "unsupported colortype=4, bitdepth=" << bitdepth << "\n";
			}
		} else if (colortype == 6) {
			// each pixel rgba quadruple, possible bitdepths 8,16
			if (bitdepth == 8) {
				for (j=0; j<w; j++) {
					image.set_red(i,j,buff[1+i*W+4*j]);
					image.set_green(i,j,buff[1+i*W+4*j+1]);
					image.set_blue(i,j,buff[1+i*W+4*j+2]);
					image.set_alpha(i,j,buff[1+i*W+4*j+3]);
				}
			} else {
				cerr << "unsupported colortype=6, bitdepth=" << bitdepth << "\n";
			}
		}
	}
	delete [] filterinfos;
	delete [] buff;
	return true;
}

bool pngwrite(ostream& out, const Timage& image)
{
	// write IHDR chunk
	Tchunk ihdr("IHDR",13);
	const int w = image.width();
	const int h = image.height();
	ihdr.uint4set(0,w);
	ihdr.uint4set(4,h);
	const int bitdepth = 8;
	const int colortype = 2;		// colortype==2: each pixel RGB triple
	ihdr.dataset(8,bitdepth);
	ihdr.dataset(9,colortype);
	ihdr.dataset(10,0);		// compressionmethod must always be zero
	ihdr.dataset(11,0);		// filtermethod must always be zero
	ihdr.dataset(12,0);		// interlacemethod==0: no interlace
	if (!ihdr.write(out)) return false;
	// header chunk written
	const int bytesperpixel = 3*(bitdepth/8);
	const int bytesperline = 3*((bitdepth*w)/8);
	const int inputbufflen = (bytesperline+1)*h;
	int outputbufflen = int(ceil(inputbufflen*1.002)) + 14;
	// prepare filtered data in inputbuff
	unsigned char *inputbuff = new unsigned char [inputbufflen];
	int i,j;
	const int W = bytesperline + 1;
	unsigned char *scanline = new unsigned char [7*bytesperline];
	unsigned char *prev_scanline = scanline + bytesperline;
	unsigned char *filtered[5];
	int f;
	int prev_best_filter = 0;
	for (f=0; f<5; f++) filtered[f] = scanline + (2+f)*bytesperline;
	memset(prev_scanline,0,bytesperline);
	for (i=0; i<h; i++) {
		// each pixel an rgb triple, possible bitdepths 8,16
		const int iW1 = i*W + 1;
		for (j=0; j<w; j++) {
			const int p = 3*j;
			scanline[p] = image.red(i,j);
			scanline[p+1] = image.green(i,j);
			scanline[p+2] = image.blue(i,j);
		}
		for (f=0; f<5; f++) {
			EncodeFilter(f,filtered[f],bytesperline,bytesperpixel,scanline,prev_scanline);
		}
		unsigned int smallest_abssum = (1 << 30);
		int best_filter = 0;
		for (f=0; f<5; f++) {
			unsigned int M1 = 0, M2 = 0;
			for (j=0; j<bytesperline; j++) {
				M1+= abs((signed char)filtered[f][j]);
			}
			for (j=1; j<bytesperline; j++) {
				M2+= abs((signed char)filtered[f][j] - (signed char)filtered[f][j-1]);
			}
			unsigned int M = (M1 < M2) ? M1 : M2;
//			if (f == 2 && prev_best_filter == 2) M = (unsigned int)(M*0.1);
			if (M < smallest_abssum) {smallest_abssum = M; best_filter = f;}
		}
//		cout << " " << best_filter << "/" << smallest_abssum;
		memcpy(&inputbuff[W*i+1],filtered[best_filter],bytesperline);
		inputbuff[W*i] = best_filter;
		memcpy(prev_scanline,scanline,bytesperline);
		prev_best_filter = best_filter;
	}
	delete [] scanline;
	// inputbuff ready
	// compress inputbuff to outputbuff
	unsigned char *outputbuff = new unsigned char [outputbufflen];
	if (!Compress(outputbuff,outputbufflen, inputbuff,inputbufflen)) {
		cerr << "compress failed\n";
		delete [] outputbuff;
		delete [] inputbuff;
		return false;
	}
    // write IDAT chunk
	Tchunk idat("IDAT",outputbufflen);
	for (i=0; i<outputbufflen; i++) idat.dataset(i,outputbuff[i]);
	delete [] inputbuff;
	if (!idat.write(out)) {
		delete [] outputbuff;
		return false;
	}
	delete [] outputbuff;
	// write IEND chunk
	Tchunk iend("IEND",0);
	if (!iend.write(out)) return false;
	return true;
}

bool pngwriteheader(ostream& out)
{
	out.put((unsigned char)137);
	out.put((unsigned char)80);
	out.put((unsigned char)78);
	out.put((unsigned char)71);
	out.put((unsigned char)13);
	out.put((unsigned char)10);
	out.put((unsigned char)26);
	out.put((unsigned char)10);
	return out.good();
}

bool mngwriteheader(ostream& out)
{
	out.put((unsigned char)138);
	out.put((unsigned char)77);
	out.put((unsigned char)78);
	out.put((unsigned char)71);
	out.put((unsigned char)13);
	out.put((unsigned char)10);
	out.put((unsigned char)26);
	out.put((unsigned char)10);
	return out.good();
}

bool pngcheckheader(istream& in)
// reads 8 bytes from in, returns true if they match PNG header, false otherwise
{
	unsigned char chs[8];
	int i;
	for (i=0; i<8; i++) in.get(chs[i]);
	if (!in.good()) return false;
	if (chs[0] != 137 || chs[1] != 80 || chs[2] != 78 || chs[3] != 71 ||
		chs[4] != 13 || chs[5] != 10 || chs[6] != 26 || chs[7] != 10) return false;
	return true;
}

bool mngcheckheader(istream& in)
// reads 8 bytes from in, returns true if they match MNG header, false otherwise
{
	unsigned char chs[8];
	int i;
	for (i=0; i<8; i++) in.get(chs[i]);
	if (!in.good()) return false;
	if (chs[0] != 138 || chs[1] != 77 || chs[2] != 78 || chs[3] != 71 ||
		chs[4] != 13 || chs[5] != 10 || chs[6] != 26 || chs[7] != 10) return false;
	return true;
}

bool mngreadfirst(istream& in, Timage& image, int& w, int& h)
{
	Tchunk mhdr;
	if (!mhdr.read(in) || strcmp(mhdr.typestr(),"MHDR") || mhdr.length() != 28) return false;
	w = mhdr.uint4ref(0);
	h = mhdr.uint4ref(4);
//	cout << "mngreadfirst: w=" << w << ", h=" << h << "\n";
	if (w == 0 || h == 0) return false;
	const unsigned int tickspersecond = mhdr.uint4ref(8);
	const unsigned int nominal_layercount = mhdr.uint4ref(12);
	const unsigned int nominal_framecount = mhdr.uint4ref(16);
	const unsigned int nominal_playtime = mhdr.uint4ref(20);
	const unsigned int simplicityprofile = mhdr.uint4ref(24);
	while (1) {
		Tchunk c;
		const streampos i = in.tellg();
		c.read(in);
		// exit loop if chunk c is not one of BACK,bGRD,bkGD
		if (strcmp(c.typestr(),"BACK") && strcmp(c.typestr(),"bGRD") && strcmp(c.typestr(),"bKGD")) {
			in.seekg(i);
			break;
		}
	}
	return pngread(in,image);
}

bool mngwritefirst(ostream& out, const Timage& image)
{
	Tchunk mhdr("MHDR",28);
//	cout << "mngwritefirst: width=" << image.width() << ", height=" << image.height() << "\n";
	mhdr.uint4set(0,image.width());
	mhdr.uint4set(4,image.height());
	mhdr.uint4set(8,1);					// ticks per second (1)
	mhdr.uint4set(12,0);				// nominal layer count (0=unspecified)
	mhdr.uint4set(16,0);				// nominal frame count (0=unspecified)
	mhdr.uint4set(20,0);				// nominal play time (0=unspecified)
	mhdr.uint4set(24,1);				// simplicity profile
	if (!mhdr.write(out)) return false;
	return pngwrite(out,image);
}

bool mngreadnext(istream& in, int w, int h, Timage& image)
{
	return pngread(in,image);
}

bool mngwritenext(ostream& out, const Timage& image)
{
	return pngwrite(out,image);
}

bool mngwriteendmark(ostream& out)
{
	Tchunk mend("MEND",0);
	return mend.write(out);
}
