| #include <u.h> | 
 | #include <libc.h> | 
 | #include <bio.h> | 
 | #include <draw.h> | 
 | #include "imagefile.h" | 
 |  | 
 | #include "rgbv.h" | 
 | #include "ycbcr.h" | 
 |  | 
 | #define	CLAMPOFF 128 | 
 |  | 
 | static	int	clamp[CLAMPOFF+256+CLAMPOFF]; | 
 | static	int	inited; | 
 |  | 
 | void* | 
 | _remaperror(char *fmt, ...) | 
 | { | 
 | 	va_list arg; | 
 | 	char buf[256]; | 
 |  | 
 | 	va_start(arg, fmt); | 
 | 	vseprint(buf, buf+sizeof buf, fmt, arg); | 
 | 	va_end(arg); | 
 |  | 
 | 	werrstr(buf); | 
 | 	return nil; | 
 | } | 
 |  | 
 | Rawimage* | 
 | torgbv(Rawimage *i, int errdiff) | 
 | { | 
 | 	int j, k, rgb, x, y, er, eg, eb, col, t; | 
 | 	int r, g, b, r1, g1, b1; | 
 | 	int *ered, *egrn, *eblu, *rp, *gp, *bp; | 
 | 	uint *map3; | 
 | 	uchar *closest; | 
 | 	Rawimage *im; | 
 | 	int dx, dy; | 
 | 	char err[ERRMAX]; | 
 | 	uchar *cmap, *cm, *in, *out, *inp, *outp, cmap1[3*256], map[256], *rpic, *bpic, *gpic; | 
 |  | 
 | 	err[0] = '\0'; | 
 | 	errstr(err, sizeof err);	/* throw it away */ | 
 | 	im = malloc(sizeof(Rawimage)); | 
 | 	if(im == nil) | 
 | 		return nil; | 
 | 	memset(im, 0, sizeof(Rawimage)); | 
 | 	im->chans[0] = malloc(i->chanlen); | 
 | 	if(im->chans[0] == nil){ | 
 | 		free(im); | 
 | 		return nil; | 
 | 	} | 
 | 	im->r = i->r; | 
 | 	im->nchans = 1; | 
 | 	im->chandesc = CRGBV; | 
 | 	im->chanlen = i->chanlen; | 
 |  | 
 | 	dx = i->r.max.x-i->r.min.x; | 
 | 	dy = i->r.max.y-i->r.min.y; | 
 | 	cmap = i->cmap; | 
 |  | 
 | 	if(inited == 0){ | 
 | 		inited = 1; | 
 | 		for(j=0; j<CLAMPOFF; j++) | 
 | 			clamp[j] = 0; | 
 | 		for(j=0; j<256; j++) | 
 | 			clamp[CLAMPOFF+j] = (j>>4); | 
 | 		for(j=0; j<CLAMPOFF; j++) | 
 | 			clamp[CLAMPOFF+256+j] = (255>>4); | 
 | 	} | 
 |  | 
 | 	in = i->chans[0]; | 
 | 	inp = in; | 
 | 	out = im->chans[0]; | 
 | 	outp = out; | 
 |  | 
 | 	ered = malloc((dx+1)*sizeof(int)); | 
 | 	egrn = malloc((dx+1)*sizeof(int)); | 
 | 	eblu = malloc((dx+1)*sizeof(int)); | 
 | 	if(ered==nil || egrn==nil || eblu==nil){ | 
 | 		free(im->chans[0]); | 
 | 		free(im); | 
 | 		free(ered); | 
 | 		free(egrn); | 
 | 		free(eblu); | 
 | 		return _remaperror("remap: malloc failed: %r"); | 
 | 	} | 
 | 	memset(ered, 0, (dx+1)*sizeof(int)); | 
 | 	memset(egrn, 0, (dx+1)*sizeof(int)); | 
 | 	memset(eblu, 0, (dx+1)*sizeof(int)); | 
 |  | 
 | 	switch(i->chandesc){ | 
 | 	default: | 
 | 		return _remaperror("remap: can't recognize channel type %d", i->chandesc); | 
 | 	case CRGB1: | 
 | 		if(cmap == nil) | 
 | 			return _remaperror("remap: image has no color map"); | 
 | 		if(i->nchans != 1) | 
 | 			return _remaperror("remap: can't handle nchans %d", i->nchans); | 
 | 		for(j=1; j<=8; j++) | 
 | 			if(i->cmaplen == 3*(1<<j)) | 
 | 				break; | 
 | 		if(j > 8) | 
 | 			return _remaperror("remap: can't do colormap size 3*%d", i->cmaplen/3); | 
 | 		if(i->cmaplen != 3*256){ | 
 | 			/* to avoid a range check in inner loop below, make a full-size cmap */ | 
 | 			memmove(cmap1, cmap, i->cmaplen); | 
 | 			cmap = cmap1; | 
 | 		} | 
 | 		if(errdiff == 0){ | 
 | 			k = 0; | 
 | 			for(j=0; j<256; j++){ | 
 | 				r = cmap[k]>>4; | 
 | 				g = cmap[k+1]>>4; | 
 | 				b = cmap[k+2]>>4; | 
 | 				k += 3; | 
 | 				map[j] = closestrgb[b+16*(g+16*r)]; | 
 | 			} | 
 | 			for(j=0; j<i->chanlen; j++) | 
 | 				out[j] = map[in[j]]; | 
 | 		}else{ | 
 | 			/* modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 */ | 
 | 			for(y=0; y<dy; y++){ | 
 | 				er = 0; | 
 | 				eg = 0; | 
 | 				eb = 0; | 
 | 				rp = ered; | 
 | 				gp = egrn; | 
 | 				bp = eblu; | 
 | 				for(x=0; x<dx; x++){ | 
 | 					cm = &cmap[3 * *inp++]; | 
 | 					r = cm[0] +*rp; | 
 | 					g = cm[1] +*gp; | 
 | 					b = cm[2] +*bp; | 
 |  | 
 | 					/* sanity checks are new */ | 
 | 					if(r >= 256+CLAMPOFF) | 
 | 						r = 0; | 
 | 					if(g >= 256+CLAMPOFF) | 
 | 						g = 0; | 
 | 					if(b >= 256+CLAMPOFF) | 
 | 						b = 0; | 
 | 					r1 = clamp[r+CLAMPOFF]; | 
 | 					g1 = clamp[g+CLAMPOFF]; | 
 | 					b1 = clamp[b+CLAMPOFF]; | 
 | 					if(r1 >= 16 || g1 >= 16 || b1 >= 16) | 
 | 						col = 0; | 
 | 					else | 
 | 						col = closestrgb[b1+16*(g1+16*r1)]; | 
 | 					*outp++ = col; | 
 |  | 
 | 					rgb = rgbmap[col]; | 
 | 					r -= (rgb>>16) & 0xFF; | 
 | 					t = (3*r)>>4; | 
 | 					*rp++ = t+er; | 
 | 					*rp += t; | 
 | 					er = r-3*t; | 
 |  | 
 | 					g -= (rgb>>8) & 0xFF; | 
 | 					t = (3*g)>>4; | 
 | 					*gp++ = t+eg; | 
 | 					*gp += t; | 
 | 					eg = g-3*t; | 
 |  | 
 | 					b -= rgb & 0xFF; | 
 | 					t = (3*b)>>4; | 
 | 					*bp++ = t+eb; | 
 | 					*bp += t; | 
 | 					eb = b-3*t; | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case CYCbCr: | 
 | 		closest = closestycbcr; | 
 | 		map3 = ycbcrmap; | 
 | 		goto Threecolor; | 
 |  | 
 | 	case CRGB: | 
 | 		closest = closestrgb; | 
 | 		map3 = rgbmap; | 
 |  | 
 | 	Threecolor: | 
 | 		if(i->nchans != 3) | 
 | 			return _remaperror("remap: RGB image has %d channels", i->nchans); | 
 | 		rpic = i->chans[0]; | 
 | 		gpic = i->chans[1]; | 
 | 		bpic = i->chans[2]; | 
 | 		if(errdiff == 0){ | 
 | 			for(j=0; j<i->chanlen; j++){ | 
 | 				r = rpic[j]>>4; | 
 | 				g = gpic[j]>>4; | 
 | 				b = bpic[j]>>4; | 
 | 				out[j] = closest[b+16*(g+16*r)]; | 
 | 			} | 
 | 		}else{ | 
 | 			/* modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 */ | 
 | 			for(y=0; y<dy; y++){ | 
 | 				er = 0; | 
 | 				eg = 0; | 
 | 				eb = 0; | 
 | 				rp = ered; | 
 | 				gp = egrn; | 
 | 				bp = eblu; | 
 | 				for(x=0; x<dx; x++){ | 
 | 					r = *rpic++ + *rp; | 
 | 					g = *gpic++ + *gp; | 
 | 					b = *bpic++ + *bp; | 
 | 					/* | 
 | 					 * Errors can be uncorrectable if converting from YCbCr, | 
 | 					 * since we can't guarantee that an extremal value of one of | 
 | 					 * the components selects a color with an extremal value. | 
 | 					 * If we don't, the errors accumulate without bound.  This | 
 | 					 * doesn't happen in RGB because the closest table can guarantee | 
 | 					 * a color on the edge of the gamut, producing a zero error in | 
 | 					 * that component.  For the rotation YCbCr space, there may be | 
 | 					 * no color that can guarantee zero error at the edge. | 
 | 					 * Therefore we must clamp explicitly rather than by assuming | 
 | 					 * an upper error bound of CLAMPOFF.  The performance difference | 
 | 					 * is miniscule anyway. | 
 | 					 */ | 
 | 					if(r < 0) | 
 | 						r = 0; | 
 | 					else if(r > 255) | 
 | 						r = 255; | 
 | 					if(g < 0) | 
 | 						g = 0; | 
 | 					else if(g > 255) | 
 | 						g = 255; | 
 | 					if(b < 0) | 
 | 						b = 0; | 
 | 					else if(b > 255) | 
 | 						b = 255; | 
 | 					r1 = r>>4; | 
 | 					g1 = g>>4; | 
 | 					b1 = b>>4; | 
 | 					col = closest[b1+16*(g1+16*r1)]; | 
 | 					*outp++ = col; | 
 |  | 
 | 					rgb = map3[col]; | 
 | 					r -= (rgb>>16) & 0xFF; | 
 | 					t = (3*r)>>4; | 
 | 					*rp++ = t+er; | 
 | 					*rp += t; | 
 | 					er = r-3*t; | 
 |  | 
 | 					g -= (rgb>>8) & 0xFF; | 
 | 					t = (3*g)>>4; | 
 | 					*gp++ = t+eg; | 
 | 					*gp += t; | 
 | 					eg = g-3*t; | 
 |  | 
 | 					b -= rgb & 0xFF; | 
 | 					t = (3*b)>>4; | 
 | 					*bp++ = t+eb; | 
 | 					*bp += t; | 
 | 					eb = b-3*t; | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case CY: | 
 | 		if(i->nchans != 1) | 
 | 			return _remaperror("remap: Y image has %d chans", i->nchans); | 
 | 		rpic = i->chans[0]; | 
 | 		if(errdiff == 0){ | 
 | 			for(j=0; j<i->chanlen; j++){ | 
 | 				r = rpic[j]>>4; | 
 | 				*outp++ = closestrgb[r+16*(r+16*r)]; | 
 | 			} | 
 | 		}else{ | 
 | 			/* modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 */ | 
 | 			for(y=0; y<dy; y++){ | 
 | 				er = 0; | 
 | 				rp = ered; | 
 | 				for(x=0; x<dx; x++){ | 
 | 					r = *inp++ + *rp; | 
 | 					r1 = clamp[r+CLAMPOFF]; | 
 | 					col = closestrgb[r1+16*(r1+16*r1)]; | 
 | 					*outp++ = col; | 
 |  | 
 | 					rgb = rgbmap[col]; | 
 | 					r -= (rgb>>16) & 0xFF; | 
 | 					t = (3*r)>>4; | 
 | 					*rp++ = t+er; | 
 | 					*rp += t; | 
 | 					er = r-3*t; | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 		break; | 
 | 	} | 
 | 	free(ered); | 
 | 	free(egrn); | 
 | 	free(eblu); | 
 | 	return im; | 
 | } |