#include #include #include #include #include //! 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; itag = 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; itype)*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; isp); 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; itag == 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); }