#include #include #include #include #include "exif.h" static jmp_buf Failed; static Img Imgs[32]; // FIXME: should really be realloc'ed on demand static int Verbose = 0; static void fail(char *fmt, ...) { va_list arg; va_start(arg, fmt); vfprint(2, fmt, arg); va_end(arg); longjmp(Failed, 1); } /********************************************/ int rint(Img *ip, int n) { int i, val, x; val = 0; for(i = 0; i < n; i++){ if((x = Bgetc(ip->bp)) == -1) fail("%s unexpected EOF - %r\n", ip->file); if(ip->intel) val |= x << i*8; else val |= x << (n-i-1)*8; } return val; } void rmem(Img *ip, void *buf, int n) { memset(buf, 0, n); if(Bread(ip->bp, buf, n) != n) fail("%s unexpected EOF - %r\n", ip->file); } /********************************************/ char * val2name(Exif *ex, int val) { Namval *nv; if(ex->nv == nil) return nil; for(nv = ex->nv; nv->name; nv++) if(nv->val == val) return nv->name; return nil; } void tag_distance(Img *ip, int base, Exif *, int, int, int val) { double a, b; Bseek(ip->bp, base+val, 0); a = rint(ip, 4); b = rint(ip, 4); fmtprint(&ip->mfmt, "%g m", a/b); } void tag_lens(Img *ip, int base, Exif *, int, int, int val) { double a, b; Bseek(ip->bp, base+val, 0); a = rint(ip, 4); b = rint(ip, 4); fmtprint(&ip->mfmt, "%g mm\n", a/b); } void tag_apex(Img *ip, int base, Exif *ep, int, int, int val) { double a, b; double k = 30.0/32.0; Bseek(ip->bp, base+val, 0); a = rint(ip, 4); b = rint(ip, 4); switch(ep->tag){ case 0x9202: // aperture case 0x9204: // exposure bias case 0x9205: // max aperture fmtprint(&ip->mfmt, "F%-5.1f\n", pow(2.0, (a/b)/2.0) + 0.05); break; case 0x9203: // brightness if((uint)a == ~0u) fmtprint(&ip->mfmt, "unknown\n"); else fmtprint(&ip->mfmt, "%+.1f\n", pow(2.0, a/b)*k + 0.05); break; case 0x9201: // shutter speed fmtprint(&ip->mfmt, "1/%.1f\n", pow(2.0, a/b) + 0.05); break; } } void tag_version(Img *ip, int, Exif *, int, int, int val) { fmtprint(&ip->mfmt, "V%c%c.%c%c\n", val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff); } void tag_shutter(Img *ip, int base, Exif *, int, int, int val) { double a, b; Bseek(ip->bp, base+val, 0); a = rint(ip, 4); b = rint(ip, 4); fmtprint(&ip->mfmt, "1/%g\n", b/a); } void tag_comment(Img *ip, int base, Exif *, int, int num, int val) { int c; fmtprint(&ip->mfmt, "'"); Bseek(ip->bp, base+val, 0); while(num--){ if((c = Bgetc(ip->bp)) == -1) fail("%s unexpected EOF - %r\n", ip->file); if(isprint(c)) fmtprint(&ip->mfmt, "%c", c); } fmtprint(&ip->mfmt, "'\n"); } void tag_other(Img *ip, int base, Exif *ep, int fmt, int num, int val) { char *str; double a, b; int c; switch(fmt){ case 1: if((str = val2name(ep, (uchar)val)) != nil) fmtprint(&ip->mfmt, "%s ", str); else fmtprint(&ip->mfmt, "%ud ", (uchar)val); break; case 2: Bseek(ip->bp, base+val, 0); while(num--){ if((c = Bgetc(ip->bp)) == -1) fail("%s unexpected EOF - %r\n", ip->file); if(isprint(c)) fmtprint(&ip->mfmt, "%c", c); } fmtprint(&ip->mfmt, " "); break; case 3: // ushort if((str = val2name(ep, (ushort)val)) != nil) fmtprint(&ip->mfmt, "%s ", str); else fmtprint(&ip->mfmt, "%ud ", (ushort)val); break; case 4: // uint if((str = val2name(ep, (uint)val)) != nil) fmtprint(&ip->mfmt, "%s ", str); else fmtprint(&ip->mfmt, "%ud ", (uint)val); break; case 5: // unsigned fraction Bseek(ip->bp, base+val, 0); a = (unsigned)rint(ip, 4); b = (unsigned)rint(ip, 4); fmtprint(&ip->mfmt, "%g ", a/b); break; case 9: // signed int if((str = val2name(ep, val)) != nil) fmtprint(&ip->mfmt, "%s ", str); else fmtprint(&ip->mfmt, "%d ", val); break; case 10: // signed fraction Bseek(ip->bp, base+val, 0); a = rint(ip, 4); b = rint(ip, 4); fmtprint(&ip->mfmt, "%g ", a/b); break; case 7: // unknown if((str = val2name(ep, val)) != nil){ fmtprint(&ip->mfmt, "%s ", str); break; } /* FALLTHRU */ default: // undefined fmtprint(&ip->mfmt, "[tag=0x%04x fmt=%d, num=%d val=%d] ", ep->tag, fmt, num, val); break; } fmtprint(&ip->mfmt, "\n"); } /****************************************************/ void decode(Img *ip, int base, int tag, int fmt, int num, int val) { Exif *ep; for(ep = Table; ep->name; ep++) if(ep->tag == tag) break; if(! ep->useful && !Verbose) return; if(ep->name == nil) fmtprint(&ip->mfmt, "[tag=0x%04x]: ", tag); else fmtprint(&ip->mfmt, "%s: ", ep->name); if(ep->name && ep->func) (*ep->func)(ip, base, ep, fmt, num, val); else tag_other(ip, base, ep, fmt, num, val); } void ifd(Img *ip, uvlong base, int off, int ifdnum) { uvlong was; int n, i, tag, fmt, num, val, next; Bseek(ip->bp, base+off, 0); n = rint(ip, 2); // slots in IFD for(i = 0; i < n; i++){ tag = rint(ip, 2); // tag fmt = rint(ip, 2); // data format num = rint(ip, 4); // num items val = rint(ip, 4); // value / pointer if(fmt < 0 || fmt > 20) fail("%s silly number format, lost in file?\n", ip->file); was = Bseek(ip->bp, 0, 1); switch(tag){ case EXIF: // offset to EXIF idf ifd(ip, base, val, ifdnum); break; case EX_toff: ip->toff = base+val; break; case EX_tlen: ip->tlen = val; break; } if(ip->mode == 'm') decode(ip, base, tag, fmt, num, val); Bseek(ip->bp, was, 0); } next = rint(ip, 4); if(next) ifd(ip, base, next, ifdnum+1); Bseek(ip->bp, base+off, 0); } void app1(Img *ip, int) { int end, off; uvlong base; char exif[] ="Exif\0"; char buf[sizeof(exif)]; rmem(ip, buf, sizeof(exif)); if(memcmp(buf, exif, sizeof(exif)) != 0) return; base = Bseek(ip->bp, 0, 1); end = rint(ip, 2); switch(end){ case 0x4949: ip->intel = 1; break; case 0x4d4d: ip->intel = 0; break; default: fail("%s 0x%04x bad endian flag\n", ip->file, end); } rint(ip, 2); // 42, a magic number it appears off = rint(ip, 2); // offset to IFD if(off == 0) // some files are broken off = 8; ifd(ip, base, off, 0); } void app0(Img *ip, int len) { char buf[64]; char jfif[] ="JFIF"; char jfxx[] ="JFXX"; int code, xth, yth; rmem(ip, buf, sizeof(jfif)); if(memcmp(buf, jfif, sizeof(jfif)) != 0) return; len -= sizeof(jfif); rint(ip, 1); // major version rint(ip, 1); // minor version rint(ip, 1); // units, zero -> xden/yden == aspect ratio rint(ip, 2); // xdensity/aspect rint(ip, 2); // ydensity/aspect xth = rint(ip, 1); // x size fo thumbnail yth = rint(ip, 1); // ysize of thumbnail len -= 9; if(xth == 0 || yth == 0) return; rmem(ip, buf, sizeof(jfxx)); if(memcmp(buf, jfxx, sizeof(jfxx)) != 0) return; len -= sizeof(jfxx); code = rint(ip, 1); len -= 1; if(code != JFXX_jpeg) return; ip->toff = Bseek(ip->bp, 1, 0); ip->tlen = len; } void sof(Img *ip, int len) { USED(len); rint(ip, 1); rint(ip, 2); rint(ip, 2); rint(ip, 1); } int parse(Img *ip) { int tag, len, n; if(setjmp(Failed) != 0) return -1; while(1){ do{ if((tag = Bgetc(ip->bp)) == -1) fail("%s unexpected EOF - %r", ip->file); if(tag != 0xff) continue; do{ if((tag = Bgetc(ip->bp)) == -1) fail("%s unexpected EOF - %r", ip->file); }while(tag == 0xff); }while(tag == 0); switch(tag){ case SOI: case EOI: case TEM: case RST+0: case RST+1: case RST+2: case RST+3: case RST+4: case RST+5: case RST+6: case RST+7: continue; // stand alone tags default: break; // tags with length and data } if((n = Bgetc(ip->bp)) == -1) fail("%s unexpected EOF - %r", ip->file); len = (n << 8) & 0xff00; if((n = Bgetc(ip->bp)) == -1) fail("%s unexpected EOF - %r", ip->file); len |= n & 0xff; len -= 2; // length includes the length field switch(tag){ case APP+0: // jpeg - image spec app0(ip, len); break; case APP+1: // exif - image spec, thumbnamil spec & data app1(ip, len); break; case SOF: // jpeg - start of frame sof(ip, len); break; case SOS: // jpeg - compressed data followed by EOI and EOF return 0; default: break; } } } int jpgopen(char *phys, int) { int rc, fd; char *virt; if((virt = strrchr(phys, '/')) == nil){ werrstr("jpg: %s - path too short, no virtual file name", phys); return -1; } *virt++ = 0; for(fd = 0; fd < nelem(Imgs); fd++) if(Imgs[fd].bp == nil) break; if(Imgs[fd].bp){ werrstr("jpg: no free img descriptors"); return -1; } if((Imgs[fd].bp = Bopen(phys, OREAD)) == nil) return -1; if((Imgs[fd].file = strdup(phys)) == nil) return -1; if(strcmp(virt, "metadata") == 0){ fmtstrinit(&Imgs[fd].mfmt); Imgs[fd].mode = 'm'; rc = parse(&Imgs[fd]); Imgs[fd].mdata = fmtstrflush(&Imgs[fd].mfmt); Imgs[fd].mlen = strlen(Imgs[fd].mdata); } else if(strcmp(virt, "fullsize.jpg") == 0){ Imgs[fd].mode = 'f'; Imgs[fd].mdata = nil; rc = parse(&Imgs[fd]); } else if(strcmp(virt, "thumbnail.jpg") == 0){ Imgs[fd].mode = 't'; Imgs[fd].mdata = nil; Imgs[fd].tlen = 0; rc = parse(&Imgs[fd]); if(Imgs[fd].tlen == 0) // no thumbnail available (should resize on the fly). Imgs[fd].mode = 'f'; } else{ werrstr("%s - not found\n", virt); return -1; } if(rc == -1){ Bterm(Imgs[fd].bp); Imgs[fd].bp = nil; free(Imgs[fd].file); free(Imgs[fd].mdata); return -1; } return fd; } long jpgpread(int fd, void *buf, long len, vlong off) { if(Imgs[fd].mode == 't'){ if(off < 0LL || off >= Imgs[fd].tlen) return 0; if(Bseek(Imgs[fd].bp, Imgs[fd].toff+off, 0) != Imgs[fd].toff+off) return -1; if((len+off) > Imgs[fd].tlen) len = Imgs[fd].tlen - off; return Bread(Imgs[fd].bp, buf, len); } if(Imgs[fd].mode == 'f'){ if(Bseek(Imgs[fd].bp, off, 0) != off) return -1; return Bread(Imgs[fd].bp, buf, len); } if(Imgs[fd].mode == 'm'){ if(off > Imgs[fd].mlen) return 0; if(len + off > Imgs[fd].mlen) len = Imgs[fd].mlen - off; memmove(buf, Imgs[fd].mdata + off, len); return len; } return -1; } int jpgclose(int fd) { Bterm(Imgs[fd].bp); Imgs[fd].bp = nil; free(Imgs[fd].file); free(Imgs[fd].mdata); return 0; }