Plan 9 from Bell Labs’s /usr/web/sources/contrib/quanstro/src/tiff/tiff.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <bio.h>
#include <tiff.h>
#include <thread.h>	//!

int rflag = 1;
int dflag = 1;

static char *tagtab[] = {
[Tsubfiletype-0xfe]	"Tsubfiletype",
[Twidth-0xfe]		"Twidth",
[Tlength-0xfe]		"Tlength",
[Tbitspersample-0xfe]	"Tbitspersample",
[Tcompression-0xfe]	"Tcompression",
[Tphotometric-0xfe]	"Tphotometric",
[Tthresholding-0xfe]	"Tthresholding",
[Tcellwidth-0xfe]		"Tcellwidth",
[Tcelllength-0xfe]		"Tcelllength",
[Thwmodel-0xfe]		"Thwmodel",
[Tfillorder-0xfe]		"Tfillorder",
[Timagedesc-0xfe]		"Timagedesc",

[Tstripoffsets-0xfe]	"Tstripoffsets",
[Torientation-0xfe]	"Torientation",
[Tsamplespp-0xfe]	"Tsamplespp",
[Trowsperstrip-0xfe]	"Trowsperstrip",
[Tstripbytecounts-0xfe]	"Tstripbytecounts",

[Txresolution-0xfe]	"Txresolution",
[Tyresolution-0xfe]	"Tyresolution",
[Tplanarconf-0xfe]	"Tplanarconf",
[Tresolutionunit-0xfe]	"Tresolutionunit",
[Tpageno-0xfe]		"Tpageno",
[Tsoftware-0xfe]		"Tsoftware",
[Tdatetime-0xfe]		"Tdatetime",
[Tartist-0xfe]		"Tartist",
[Tcomputer-0xfe]		"Tcomputer",
[Tpredictor-0xfe]		"Tpredictor",
[Tcolormap-0xfe]		"Tcolormap",
[Textrasamples-0xfe]	"Textrasamples",
};

char*
tagname(IFDtype t)
{
	int i;

	i = t-0xfe;
	if(i<0 || i>=Tend)
		return "unknown";
	return tagtab[i];
}

typedef struct {
	IFDtype	t;
	char	*name;
	int	size;
	uint	fmtstride;
	char	*fmt;
	char	desc;
} IFDinfo;

IFDinfo  IFDtab[] = {
	{0,	"b0rked",	0,	0,"b0rked",	0,},
	{Ibyte,	"byte",	1,	1, "%02uhhx",	'c',},
	{Iascii,	"ascii",	1,	~0,"%.*s",	'a'},
	{Ishort,	"short",	2,	1, "%uhd ",	's'},	
	{Ilong,	"long",	4,	1, "%uld ",	'l'},
	{Irat,	"rat",	8,	2, "[%uld.%uld]",	'r'},
	{Isbyte,	"sbyte",	1,	1, "%hhd",	'B'},
	{Iundef,	"undef",	1,	~0, "%.*s",	'U'},
	{Isshort,	"sshort",	2,	1, "%hd ",	'S'},
	{Islong,	"slong",	4,	1, "%ld ",	'L'},
	{Israt,	"srat",	8,	1, "[%ld.%ld]",	'R'},
	{Ifloat,	"float",	4,	1, "%f ",		'f'},
	{Idbl,	"dbl",	8,	1, "%d ",		'd'},
	{0,	"b0rked",	0,	0, 0,		},
};

int
typesize(IFDtype t)
{
	return IFDtab[t].size;
}

char*
typename(IFDtype t)
{
	return IFDtab[t].name;
}

long
tiffshort(uchar *p, End o)
{
	switch(o){
	case BE:
		return p[1] | p[0]<<8;
	case LE:
		return p[0] | p[1]<<8;
	}
	assert(0);
	return -1;
}

long
Btiffshort(Biobuf *b, End o)
{
	uchar p[2];
	
	Bread(b, p, sizeof p);
	return tiffshort(p, o);
}

long
Btiffshorts(Biobuf *b, short *p, int n, End o)
{
	while(n--)
		*p++ = Btiffshort(b, o);
	return 0;
}

long
tifflong(uchar *p, End o)
{
	switch(o){
	case BE:
		return p[3] | p[2]<<8 | p[1]<<16 | p[0]<<24;
	case LE:
		return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24;
	}
	assert(0);
	return -1;
}

long
Btifflong(Biobuf *b, End o)
{
	uchar p[4];
	
	Bread(b, p, sizeof p);
	return tifflong(p, o);
}

long
Btifflongs(Biobuf *b, long *p, int n, End o)
{
	while(n--)
		*p++ = Btifflong(b, o);
	return 0;
}

int
ifdvalread(Biobuf *b, IFD* i, End o)
{
	int s;

	s = typesize(i->type);
	i->cp = mallocz((s*i->n|15)+1, 1);
	if(!i->cp)
		sysfatal("malloc");
	if(Bseek(b, i->offset, 0) == -1)
		return -1;
	switch(s){
	case 1:
		Bread(b, i->cp, i->n);
		break;
	case 2:
		Btiffshorts(b, i->sp, i->n, o);
		break;
	case 4:
		Btifflongs(b, i->lp, i->n, o);
		break;
	case 8:
		Btifflongs(b, i->lp, i->n*2, o);
		break;
	}
	return 0;
}

int
ifdvalcp(IFD* i, End o)
{
	int s, n;
	uchar *p;

	i->cp = mallocz(16, 1);
	if(!i->cp)
		sysfatal("malloc");
	p = i->roffset;
	switch(s = typesize(i->type)){
	case 1:
		memcpy(i->cp, p, 4);
		break;
	case 2:
		for(n = 0; s; n++, s -= 2)
			i->sp[n] = tiffshort(p+2*n, o);
	case 4:
	case 8:
		for(n = 0; s; n++, s -= 4)
			i->lp[n] = tifflong(p+4*n, o);
	}
//	i->offset = 0;
	return 0;
}

int
readifd(Biobuf *b, vlong offset, Tiff* t)
{
	IFD *i, *e;
	long n;

	if(Bseek(b, offset, 0) == -1)
		return -1;
	t->n = t->alloc = Btiffshort(b, t->order);
	t->ifds = mallocz(t->alloc * sizeof *t->ifds, 1);
	if(t->ifds == 0)
		return -1;

	for(i = t->ifds, e = i+t->n; i<e; i++){
		i->tag = Btiffshort(b, t->order);
		i->type = Btiffshort(b, t->order);
		i->n = Btifflong(b, t->order);
		Bread(b, i->roffset, 4);		// avoid double-endian conversion
		i->offset = tifflong(i->roffset, t->order);
	}

	// here's where we should check for other IFDs.  we want a null next ptr.
	if((n = Btifflong(b, t->order)) != 0)
		fprint(2, "another IFD after this one @%uld -- ignored.\n", n);

	for(i = t->ifds, e = i+t->n; i<e; i++)
		if(typesize(i->type)*i->n > 4)
			ifdvalread(b, i, t->order);
		else
			ifdvalcp(i, t->order);
	return 0;
}

void
tifffree(Tiff *t)
{
	IFD *i, *e;

	e = t->ifds + t->n;
	for(i = t->ifds; i<e; i++)
		free(i->sp);
	free(t->ifds);
	free(t->rawimg);
	free(t);
}

void
ifdprint(Tiff *t)
{
	IFD *i, *e;
	char *tag, *tab, *fmt, buf[9];
	int j, l, stride, tsize;

	for(i = t->ifds, e = i+t->n; i < e; i++){
		tag = tagname(i->tag);
		if(!tag){
			snprint(buf, sizeof buf, "%x", i->tag);
			tag = buf;
		}
		l = strlen(tag)+2;
		if(l<=13)
			tab = "\t\t";
		else
			tab = "\t";
		fprint(2, "%03x [%s]%s%c/%uld", i->tag, tag, tab, IFDtab[i->type].desc, i->n);
		if(typesize(i->type)>4 || i->n>1)
			fprint(2, "@%-8uld\t", i->offset);
		else
			fprint(2, "\t\t");
		
		switch(i->tag){
		case Tcompression:
			if(tiffzstr(i->sp[0]) == 0)
				break;
			fprint(2, "%s\n", tiffzstr(i->sp[0]));
			continue;
		}

		tsize = IFDtab[i->type].size;
		fmt = IFDtab[i->type].fmt;
		stride = IFDtab[i->type].fmtstride;
		for(j = 0; j < i->n ; j += stride)
			switch(tsize){
			case 1:
				if(stride == 1)
					; //fprint(2, fmt, i->cp[j]);
				else
					fprint(2, fmt, i->n, i->cp);
				break;
			case 2:
				fprint(2, fmt, i->sp[j]);
				break;
			case 4:
				fprint(2, fmt, i->lp[j]);
				break;
			case 8:
				//fprint(2, fmt, i->lp[j], i->lp[j+1]);
				fprint(2, "[%g]", (double)i->lp[j] / (double)i->lp[j+1]);
				break;
			}
		fprint(2, "\n");
	}
}

IFD*
lookifdptr(Tiff *t, Itype tag)
{
	IFD *i, *e;

	for(i = t->ifds, e = i+t->n; i<e; i++)
		if(i->tag == tag)
			return i;
	return 0;
}

long
ifdidx(IFD *i, int n)
{
	if(i)
	switch(typesize(i->type)){
	case 2:
		return i->sp[n];
	case 4:
		return i->lp[n];
	}
	sysfatal("bad type");
	return -1;
}

long
lookifd(Tiff *t, Itype tag)
{
	IFD *i;

	i = lookifdptr(t, tag);
	if(i && i->n == 1)
		return ifdidx(i, 0);
	return -1;
}

long
tiffimglen(Tiff *t)
{
	ulong x, y, bpp, i;
	IFD* cdepth;

	x = lookifd(t, Twidth);
	y = lookifd(t, Tlength);
	cdepth = lookifdptr(t, Tbitspersample);
	if(!cdepth)
		return -1;
	for(bpp = 0, i = 0; i < cdepth->n; i++)
		bpp += cdepth->sp[i];
	debug("x=%uld y=%uld bpp=%uld\n", x, y, bpp);
	if(x<1 || y<1 || bpp<1)
		return -1;
	t->bpp = bpp;
	return x*y*bpp>>3;
}

static long
sz(Tiff *t, long l, long i)
{
	int b, r, c, bpl;
	IFD *z;

	z = lookifdptr(t, Tstripoffsets);
	r = lookifd(t, Trowsperstrip);
	c = lookifd(t, Tlength);
	if(i == z->n-1)
		r = c - (z->n-1)*r;	// truncated final strip
	b = (int)((vlong)l*(vlong)r/(vlong)c);
	bpl = t->bpp*lookifd(t, Twidth);

	debug("b = %d; l = %ld, r=%d, c=%d\n", b, l, r, c);
	// rows must be an even number of bytes.
	// cheat if bpl == 0 on Z8
	if((bpl%8) == 0)
		return b;
	for(i = 1; i <= r; i++)
		if((bpl*i)%8)
			b++;
	return b;
}

int
tiffimg(Biobuf *b, Tiff *t)
{
	IFD *stripp, *stripsz;
	long l, i, n, r;

	l = tiffimglen(t);
	stripp = lookifdptr(t, Tstripoffsets);
	stripsz = lookifdptr(t, Tstripbytecounts);
	if(l == -1 || !stripp || !stripsz){
		debug("tiffimg fails(l=%ld)\n", l);
		return -1;
	}

	t->rawimge = t->rawimg = malloc(l+10);
	if(!t->rawimg)
		return -1;

	for(i = 0, n = 0; i < stripp->n; i++){
		if(Bseek(b, ifdidx(stripp, i), 0) < 0)
			return -1;
		// ifdidx(stripsz, i) is the *compressed* size
		r = tiffunz(t, lookifd(t, Tcompression), b, sz(t, l, i));
		debug("tiffunz → %ld; %ld\n", r, n);
		if(r < 0)
			return -1;
		t->rawimge += r;
		n+= r;
	}

	return 0;
}

int
tiffsetrgb(Tiff *t, int bpc)
{
	IFD *i;
	int j;

	i = lookifdptr(t, Tphotometric);
	if(!i)
		return -1;
	i->sp[0] = Prgb;

	i = lookifdptr(t, Tbitspersample);
	if(!i)
		return -1;
	i->sp = realloc(i->sp, 3*sizeof *i->sp);
	for(j = 0; j < 3; j++)
		i->sp[j] = bpc;
	i = lookifdptr(t, Tsamplespp);
	if(!i)
		return -1;
	i->sp[0] = 3;
	t->bpp = bpc*3;
	return 0;
}

int
tiffcmap(Tiff *t)
{
	uchar m[3*256], *map, *p, *q, *e;
	long i, l;
	ushort *s;
	IFD *d;

	if(lookifd(t, Tphotometric) != Prgbcmap)
		return 0;
	
	d = lookifdptr(t, Tcolormap);
	if(!d)
		return -1;
	s = (ushort*)d->sp;

	for(i = 0; i < 3*256; i++) {
		m[i] = s[i]>>8;
		if(s[i]&256 >= 128)
			m[i]++;
	}

	l = tiffimglen(t);
	map = malloc(3*l);
	if(!map)
		return -1;

//	for(i = 0; i < 256; i++)
//		if(m[i] || m[i+256] || m[i+512])
//			fprint(2, "%02x %02x %02x\n", m[i], m[i+256], m[i+512]);

	q = t->rawimg;
	e = t->rawimge;
	for(p = map; q < e; q++){
		*p++ = m[*q+0*256];
		*p++ = m[*q+1*256];
		*p++ = m[*q+2*256];
	}
	free(t->rawimg);
	t->rawimg = map;
	t->rawimge = map+3*l;
	return tiffsetrgb(t, 8);
}

int
tiffphoto(Tiff *t)
{
	ulong *u, *e;
	IFD *i;

	if((i = lookifdptr(t, Tphotometric)) == 0)
		return -1;

	switch(i->sp[0]){
	case Pblackzero:
	case Prgb:
	case Prgba:
		return 0;
	case Pwhitezero:
		e = (ulong*)t->rawimge;
		for(u = (ulong*)t->rawimg; u < e; u++)
			*u = ~*u;
		i->sp[0] = Pblackzero;
		return 0;
	case Prgbcmap:
		return tiffcmap(t);
	case Pcmyk:
	case PYCbCr:
	case PCIELab:
	default:
		return -1;
	}
}

static void
dumprawstrip(Tiff *t, Biobuf *b, int x)
{
	int o, c, i;

	o = ifdidx(lookifdptr(t, Tstripoffsets), x);
	c = ifdidx(lookifdptr(t, Tstripbytecounts), x);
	if(Bseek(b, o, 0) == -1)
		return;
	fprint(2, "%d\n", c);
	for(i = 0; i < c; i++){
		fprint(2, "%02x ", Bgetc(b));
		if((i+1)%16 == 0)
			fprint(2, "\n");
	}
}

Tiff*
tiffhdr(Biobuf *b)
{
	Tiff *t;
	char buf[3];

	t = mallocz(sizeof *t, 1);
	if(!t)
		return 0;

	buf[2] = 0;
	if(Bread(b, buf, 2) != 2)
		goto cleanup;

	if(strcmp(buf, "MM") == 0)
		t->order = BE;
	else if (strcmp(buf, "II") == 0)
		t->order = LE;
	else
		goto cleanup;

	if(42 != Btiffshort(b, t->order))
		goto cleanup;
	t->ifd0 = Btifflong(b, t->order);

	if(readifd(b, t->ifd0, t) == -1)
		goto cleanup;
	return t;
cleanup:
	tifffree(t);
	return 0;

}

Tiff*
readtiff(Biobuf *b)
{
	Tiff *t;
	int f;

	t = tiffhdr(b);
	if(!t)
		return 0;
	// dumprawstrip(t, b, 1); exits("");
	f = lookifd(t, Tfillorder);			// check for backwards bytes.
	if(f < 0 || f != 1)
	if(tiffimg(b, t) != -1)
	if(tiffexpand(t) != -1)
	if(tiffphoto(t) != -1)
		return t;
	tifffree(t);
	return 0;
}

void
tiffinfo(Tiff *t)
{
	debug("t.order = %s\n", t->order == BE ? "BE" : "LE");
	debug("t.ifd0 = %uld\n", t->ifd0);
	ifdprint(t);
}

void
tiffprint(Biobuf *b)
{
	Tiff *t;

	if(t = tiffhdr(b)){
		tiffinfo(t);
		tifffree(t);
	}
}

void
tiffdisplay(Biobuf *b)
{
	Tiff *t;

	t = readtiff(b);
	if(!t){
		fprint(2, "can't read tiff\n");
		return;
	}
	if(lookifd(t, Tsamplespp) == 3 && lookifd(t, Tphotometric) == Prgb)
		dirty(t);
	else if(lookifd(t, Tsamplespp) == 1 && lookifd(t, Tphotometric) == Pblackzero)
		dirty(t);
	tifffree(t);
}

void
tiffwrite(Biobuf *b)
{
	Tiff *t;
	char buf[20];

	t = readtiff(b);
	if(!t){
		fprint(2, "can't read tiff\n");
		return;
	}
	print("%11s %11d %11d %11ld %11ld ", tiffchantostr(t, buf),
		0, 0, lookifd(t, Twidth), lookifd(t, Tlength));
	if(write(1, t->rawimg, t->rawimge-t->rawimg) != t->rawimge-t->rawimg)
		fprint(2, "tiff: write error %r\n");
	tifffree(t);
}

int mainstacksize = 2*64*1024;
void
threadmain(int argc, char **argv)
{
	Biobuf *b;
	int r;
	void (*f)(Biobuf*);

	f = tiffdisplay;

	ARGBEGIN{
	case 'r':
		rflag ^= 1;
		break;
	case 'd':
		f = tiffdisplay;
		break;
	case 'p':
		f = tiffprint;
		break;
	case '9':
		f = tiffwrite;
		break;
	default:
		fprint(2, "usage: tiff -[rdp] [files]\n");
		threadexitsall("usage");
	}ARGEND;

	if(!*argv){
		*argv = "/fd/0";
		argv[1] = 0;
	}

	for(r = 0; *argv; argv++){
		b = Bopen(*argv, OREAD);
		if(!b){
			fprint(2, "%r\n");
			r = -1;
			continue;
		}
		f(b);
		Bterm(b);
	}
	threadexitsall(r == -1 ? "bad tiff" : 0);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.