Plan 9 from Bell Labs’s /usr/web/sources/contrib/steve/root/sys/src/cmd/exifsrv/jpgparse.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 <ctype.h>
#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;
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].