#ifndef lint static const char RCSid[] = "$Id: tonemap.c,v 3.18 2005/01/07 22:05:30 greg Exp $"; #endif /* * Tone mapping functions. * See tonemap.h for detailed function descriptions. * Added von Kries white-balance calculations 10/01 (GW). * * Externals declared in tonemap.h */ #include "copyright.h" #include #include #include "tmprivat.h" #include "tmerrmsg.h" #define exp10(x) exp(M_LN10*(x)) /* our list of conversion packages */ struct tmPackage *tmPkg[TM_MAXPKG]; int tmNumPkgs = 0; /* number of registered packages */ TMstruct * tmInit( /* initialize new tone mapping */ int flags, RGBPRIMP monpri, double gamval ) { COLORMAT cmat; TMstruct *tmnew; int i; /* allocate structure */ tmnew = (TMstruct *)malloc(sizeof(TMstruct)); if (tmnew == NULL) return(NULL); tmnew->flags = flags & ~TM_F_UNIMPL; if (tmnew->flags & TM_F_BW) tmnew->flags &= ~TM_F_MESOPIC; /* set monitor transform */ if (monpri == NULL || monpri == stdprims || tmnew->flags & TM_F_BW) { tmnew->monpri = stdprims; tmnew->clf[RED] = rgb2xyzmat[1][0]; tmnew->clf[GRN] = rgb2xyzmat[1][1]; tmnew->clf[BLU] = rgb2xyzmat[1][2]; } else { comprgb2xyzWBmat(cmat, tmnew->monpri=monpri); tmnew->clf[RED] = cmat[1][0]; tmnew->clf[GRN] = cmat[1][1]; tmnew->clf[BLU] = cmat[1][2]; } /* set gamma value */ if (gamval < MINGAM) tmnew->mongam = DEFGAM; else tmnew->mongam = gamval; /* set color divisors */ for (i = 0; i < 3; i++) tmnew->cdiv[i] = 256.*pow(tmnew->clf[i], 1./tmnew->mongam); /* set input transform */ tmnew->inppri = tmnew->monpri; tmnew->cmat[0][0] = tmnew->cmat[1][1] = tmnew->cmat[2][2] = tmnew->inpsf = WHTEFFICACY; tmnew->cmat[0][1] = tmnew->cmat[0][2] = tmnew->cmat[1][0] = tmnew->cmat[1][2] = tmnew->cmat[2][0] = tmnew->cmat[2][1] = 0.; tmnew->inpdat = NULL; tmnew->hbrmin = 10; tmnew->hbrmax = -10; tmnew->histo = NULL; tmnew->mbrmin = 10; tmnew->mbrmax = -10; tmnew->lumap = NULL; /* zero private data */ for (i = TM_MAXPKG; i--; ) tmnew->pd[i] = NULL; tmnew->lastError = TM_E_OK; tmnew->lastFunc = "NoErr"; /* return new TMstruct */ return(tmnew); } int tmSetSpace( /* set input color space for conversions */ TMstruct *tms, RGBPRIMP pri, double sf, MEM_PTR dat ) { static const char funcName[] = "tmSetSpace"; int i, j; /* error check */ if (tms == NULL) returnErr(TM_E_TMINVAL); if (sf <= 1e-12) returnErr(TM_E_ILLEGAL); /* check if no change */ if (pri == tms->inppri && FEQ(sf, tms->inpsf) && dat == tms->inpdat) returnOK; tms->inppri = pri; /* let's set it */ tms->inpsf = sf; tms->inpdat = dat; if (tms->flags & TM_F_BW) { /* color doesn't matter */ tms->monpri = tms->inppri; /* eliminate xform */ if (tms->inppri == TM_XYZPRIM) { tms->clf[CIEX] = tms->clf[CIEZ] = 0.; tms->clf[CIEY] = 1.; } else { comprgb2xyzWBmat(tms->cmat, tms->monpri); tms->clf[RED] = tms->cmat[1][0]; tms->clf[GRN] = tms->cmat[1][1]; tms->clf[BLU] = tms->cmat[1][2]; } tms->cmat[0][0] = tms->cmat[1][1] = tms->cmat[2][2] = tms->inpsf; tms->cmat[0][1] = tms->cmat[0][2] = tms->cmat[1][0] = tms->cmat[1][2] = tms->cmat[2][0] = tms->cmat[2][1] = 0.; } else if (tms->inppri == TM_XYZPRIM) /* input is XYZ */ compxyz2rgbWBmat(tms->cmat, tms->monpri); else { /* input is RGB */ if (tms->inppri != tms->monpri && PRIMEQ(tms->inppri, tms->monpri)) tms->inppri = tms->monpri; /* no xform */ comprgb2rgbWBmat(tms->cmat, tms->inppri, tms->monpri); } for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) tms->cmat[i][j] *= tms->inpsf; /* set color divisors */ for (i = 0; i < 3; i++) if (tms->clf[i] > .001) tms->cdiv[i] = 256.*pow(tms->clf[i], 1./tms->mongam); else tms->cdiv[i] = 1; /* notify packages */ for (i = tmNumPkgs; i--; ) if (tms->pd[i] != NULL && tmPkg[i]->NewSpace != NULL) (*tmPkg[i]->NewSpace)(tms); returnOK; } void tmClearHisto( /* clear current histogram */ TMstruct *tms ) { if (tms == NULL || tms->histo == NULL) return; free((MEM_PTR)tms->histo); tms->histo = NULL; } int tmCvColors( /* convert float colors */ TMstruct *tms, TMbright *ls, BYTE *cs, COLOR *scan, int len ) { static const char funcName[] = "tmCvColors"; static COLOR csmall = {.5*MINLUM, .5*MINLUM, .5*MINLUM}; COLOR cmon; double lum, slum; double d; int i; if (tms == NULL) returnErr(TM_E_TMINVAL); if ((ls == NULL) | (scan == NULL) | (len < 0)) returnErr(TM_E_ILLEGAL); for (i = len; i--; ) { if (tmNeedMatrix(tms)) { /* get monitor RGB */ colortrans(cmon, tms->cmat, scan[i]); } else { cmon[RED] = tms->inpsf*scan[i][RED]; cmon[GRN] = tms->inpsf*scan[i][GRN]; cmon[BLU] = tms->inpsf*scan[i][BLU]; } /* world luminance */ lum = tms->clf[RED]*cmon[RED] + tms->clf[GRN]*cmon[GRN] + tms->clf[BLU]*cmon[BLU] ; /* check range */ if (clipgamut(cmon, lum, CGAMUT_LOWER, csmall, cwhite)) lum = tms->clf[RED]*cmon[RED] + tms->clf[GRN]*cmon[GRN] + tms->clf[BLU]*cmon[BLU] ; if (lum < MINLUM) { ls[i] = MINBRT-1; /* bogus value */ lum = MINLUM; } else { d = TM_BRTSCALE*log(lum); /* encode it */ ls[i] = d>0. ? (int)(d+.5) : (int)(d-.5); } if (cs == TM_NOCHROM) /* no color? */ continue; if (tms->flags & TM_F_MESOPIC && lum < LMESUPPER) { slum = scotlum(cmon); /* mesopic adj. */ if (lum < LMESLOWER) cmon[RED] = cmon[GRN] = cmon[BLU] = slum; else { d = (lum - LMESLOWER)/(LMESUPPER - LMESLOWER); if (tms->flags & TM_F_BW) cmon[RED] = cmon[GRN] = cmon[BLU] = d*lum; else scalecolor(cmon, d); d = (1.-d)*slum; cmon[RED] += d; cmon[GRN] += d; cmon[BLU] += d; } } else if (tms->flags & TM_F_BW) { cmon[RED] = cmon[GRN] = cmon[BLU] = lum; } d = tms->clf[RED]*cmon[RED]/lum; cs[3*i ] = d>=.999 ? 255 : (int)(256.*pow(d, 1./tms->mongam)); d = tms->clf[GRN]*cmon[GRN]/lum; cs[3*i+1] = d>=.999 ? 255 : (int)(256.*pow(d, 1./tms->mongam)); d = tms->clf[BLU]*cmon[BLU]/lum; cs[3*i+2] = d>=.999 ? 255 : (int)(256.*pow(d, 1./tms->mongam)); } returnOK; } int tmCvGrays( /* convert float gray values */ TMstruct *tms, TMbright *ls, float *scan, int len ) { static const char funcName[] = "tmCvGrays"; double d; int i; if (tms == NULL) returnErr(TM_E_TMINVAL); if ((ls == NULL) | (scan == NULL) | (len < 0)) returnErr(TM_E_ILLEGAL); for (i = len; i--; ) if (scan[i] <= TM_NOLUM) { ls[i] = TM_NOBRT; /* bogus value */ } else { d = TM_BRTSCALE*log(scan[i]); /* encode it */ ls[i] = d>0. ? (int)(d+.5) : (int)(d-.5); } returnOK; } int tmAddHisto( /* add values to histogram */ TMstruct *tms, TMbright *ls, int len, int wt ) { static const char funcName[] = "tmAddHisto"; int oldorig=0, oldlen, horig, hlen; int i, j; if (tms == NULL) returnErr(TM_E_TMINVAL); if (len < 0) returnErr(TM_E_ILLEGAL); if (len == 0) returnOK; /* first, grow limits */ if (tms->histo == NULL) { for (i = len; i-- && ls[i] < MINBRT; ) ; if (i < 0) returnOK; tms->hbrmin = tms->hbrmax = ls[i]; oldlen = 0; } else { oldorig = (tms->hbrmin-MINBRT)/HISTEP; oldlen = (tms->hbrmax-MINBRT)/HISTEP + 1 - oldorig; } for (i = len; i--; ) { if ((j = ls[i]) < MINBRT) continue; if (j < tms->hbrmin) tms->hbrmin = j; else if (j > tms->hbrmax) tms->hbrmax = j; } horig = (tms->hbrmin-MINBRT)/HISTEP; hlen = (tms->hbrmax-MINBRT)/HISTEP + 1 - horig; if (hlen > oldlen) { /* (re)allocate histogram */ int *newhist = (int *)calloc(hlen, sizeof(int)); if (newhist == NULL) returnErr(TM_E_NOMEM); if (oldlen) { /* copy and free old */ for (i = oldlen, j = i+oldorig-horig; i; ) newhist[--j] = tms->histo[--i]; free((MEM_PTR)tms->histo); } tms->histo = newhist; } if (wt == 0) returnOK; for (i = len; i--; ) /* add in new counts */ if (ls[i] >= MINBRT) tms->histo[ (ls[i]-MINBRT)/HISTEP - horig ] += wt; returnOK; } static double htcontrs( /* human threshold contrast sensitivity, dL(La) */ double La ) { double l10La, l10dL; /* formula taken from Ferwerda et al. [SG96] */ if (La < 1.148e-4) return(1.38e-3); l10La = log10(La); if (l10La < -1.44) /* rod response regime */ l10dL = pow(.405*l10La + 1.6, 2.18) - 2.86; else if (l10La < -.0184) l10dL = l10La - .395; else if (l10La < 1.9) /* cone response regime */ l10dL = pow(.249*l10La + .65, 2.7) - .72; else l10dL = l10La - 1.255; return(exp10(l10dL)); } static int tmNewMap( /* allocate new tone-mapping array */ TMstruct *tms ) { if (tms->lumap != NULL && (tms->mbrmax - tms->mbrmin) != (tms->hbrmax - tms->hbrmin)) { free((MEM_PTR)tms->lumap); tms->lumap = NULL; } tms->mbrmin = tms->hbrmin; tms->mbrmax = tms->hbrmax; if (tms->mbrmin > tms->mbrmax) return 0; if (tms->lumap == NULL) tms->lumap = (unsigned short *)malloc(sizeof(unsigned short)* (tms->mbrmax-tms->mbrmin+1)); return(tms->lumap != NULL); } int tmFixedMapping( /* compute fixed, linear tone-mapping */ TMstruct *tms, double expmult, double gamval ) { static const char funcName[] = "tmFixedMapping"; double d; int i; if (!tmNewMap(tms)) returnErr(TM_E_NOMEM); if (expmult <= .0) expmult = 1.; if (gamval < MINGAM) gamval = tms->mongam; d = log(expmult/tms->inpsf); for (i = tms->mbrmax-tms->mbrmin+1; i--; ) tms->lumap[i] = 256. * exp( ( d + (tms->mbrmin+i)*(1./TM_BRTSCALE) ) / gamval ); returnOK; } int tmComputeMapping( /* compute histogram tone-mapping */ TMstruct *tms, double gamval, double Lddyn, double Ldmax ) { static const char funcName[] = "tmComputeMapping"; int *histo; float *cumf; int brt0, histlen, threshold, ceiling, trimmings; double logLddyn, Ldmin, Ldavg, Lwavg, Tr, Lw, Ld; int32 histot; double sum; double d; int i, j; if (tms == NULL || tms->histo == NULL) returnErr(TM_E_TMINVAL); /* check arguments */ if (Lddyn < MINLDDYN) Lddyn = DEFLDDYN; if (Ldmax < MINLDMAX) Ldmax = DEFLDMAX; if (gamval < MINGAM) gamval = tms->mongam; /* compute handy values */ Ldmin = Ldmax/Lddyn; logLddyn = log(Lddyn); Ldavg = sqrt(Ldmax*Ldmin); i = (tms->hbrmin-MINBRT)/HISTEP; brt0 = MINBRT + HISTEP/2 + i*HISTEP; histlen = (tms->hbrmax-MINBRT)/HISTEP + 1 - i; /* histogram total and mean */ histot = 0; sum = 0; j = brt0 + histlen*HISTEP; for (i = histlen; i--; ) { histot += tms->histo[i]; sum += (j -= HISTEP) * tms->histo[i]; } threshold = histot*0.005 + .5; if (threshold < 4) returnErr(TM_E_TMFAIL); Lwavg = tmLuminance( (double)sum / histot ); /* allocate space for mapping */ if (!tmNewMap(tms)) returnErr(TM_E_NOMEM); /* use linear tone mapping? */ if (tms->flags & TM_F_LINEAR) goto linearmap; /* clamp histogram */ histo = (int *)malloc(histlen*sizeof(int)); cumf = (float *)malloc((histlen+2)*sizeof(float)); if ((histo == NULL) | (cumf == NULL)) returnErr(TM_E_NOMEM); cumf[histlen+1] = 1.; /* guard for assignment code */ for (i = histlen; i--; ) /* make malleable copy */ histo[i] = tms->histo[i]; do { /* iterate to solution */ sum = 0; /* cumulative probability */ for (i = 0; i < histlen; i++) { cumf[i] = (double)sum/histot; sum += histo[i]; } cumf[histlen] = 1.; Tr = histot * (double)(tms->hbrmax - tms->hbrmin) / ((double)histlen*TM_BRTSCALE) / logLddyn; ceiling = Tr + 1.; trimmings = 0; /* clip to envelope */ for (i = histlen; i--; ) { if (tms->flags & TM_F_HCONTR) { Lw = tmLuminance(brt0 + i*HISTEP); Ld = Ldmin * exp( logLddyn * .5*(cumf[i]+cumf[i+1]) ); ceiling = Tr * (htcontrs(Ld) * Lw) / (htcontrs(Lw) * Ld) + 1.; } if (histo[i] > ceiling) { trimmings += histo[i] - ceiling; histo[i] = ceiling; } } /* check if we're out of data */ if ((histot -= trimmings) <= threshold) { free((MEM_PTR)histo); free((MEM_PTR)cumf); goto linearmap; } } while (trimmings > threshold); /* assign tone-mapping */ for (i = tms->mbrmax-tms->mbrmin+1; i--; ) { j = d = (double)i/(tms->mbrmax-tms->mbrmin)*histlen; d -= (double)j; Ld = Ldmin*exp(logLddyn*((1.-d)*cumf[j]+d*cumf[j+1])); d = (Ld - Ldmin)/(Ldmax - Ldmin); tms->lumap[i] = 256.*pow(d, 1./gamval); } free((MEM_PTR)histo); /* clean up and return */ free((MEM_PTR)cumf); returnOK; linearmap: /* linear tone-mapping */ if (tms->flags & TM_F_HCONTR) d = htcontrs(Ldavg) / htcontrs(Lwavg); else d = Ldavg / Lwavg; return(tmFixedMapping(tms, tms->inpsf*d/Ldmax, gamval)); } int tmMapPixels( /* apply tone-mapping to pixel(s) */ TMstruct *tms, BYTE *ps, TMbright *ls, BYTE *cs, int len ) { static const char funcName[] = "tmMapPixels"; int32 li, pv; if (tms == NULL || tms->lumap == NULL) returnErr(TM_E_TMINVAL); if ((ps == NULL) | (ls == NULL) | (len < 0)) returnErr(TM_E_ILLEGAL); while (len--) { if ((li = *ls++) < tms->mbrmin) { li = 0; } else { if (li > tms->mbrmax) li = tms->mbrmax; li = tms->lumap[li - tms->mbrmin]; } if (cs == TM_NOCHROM) *ps++ = li>255 ? 255 : li; else { pv = *cs++ * li / tms->cdiv[RED]; *ps++ = pv>255 ? 255 : pv; pv = *cs++ * li / tms->cdiv[GRN]; *ps++ = pv>255 ? 255 : pv; pv = *cs++ * li / tms->cdiv[BLU]; *ps++ = pv>255 ? 255 : pv; } } returnOK; } TMstruct * tmDup( /* duplicate top tone mapping */ TMstruct *tms ) { int len; int i; TMstruct *tmnew; if (tms == NULL) /* anything to duplicate? */ return(NULL); tmnew = (TMstruct *)malloc(sizeof(TMstruct)); if (tmnew == NULL) return(NULL); *tmnew = *tms; /* copy everything */ if (tmnew->histo != NULL) { /* duplicate histogram */ len = (tmnew->hbrmax-MINBRT)/HISTEP + 1 - (tmnew->hbrmin-MINBRT)/HISTEP; tmnew->histo = (int *)malloc(len*sizeof(int)); if (tmnew->histo != NULL) for (i = len; i--; ) tmnew->histo[i] = tms->histo[i]; } if (tmnew->lumap != NULL) { /* duplicate luminance mapping */ len = tmnew->mbrmax-tmnew->mbrmin+1; tmnew->lumap = (unsigned short *)malloc( len*sizeof(unsigned short) ); if (tmnew->lumap != NULL) for (i = len; i--; ) tmnew->lumap[i] = tms->lumap[i]; } /* clear package data */ for (i = tmNumPkgs; i--; ) tmnew->pd[i] = NULL; /* return copy */ return(tmnew); } void tmDone(tms) /* done with tone mapping -- destroy it */ TMstruct *tms; { int i; /* NULL arg. is equiv. to tms */ if (tms == NULL) return; /* free tables */ if (tms->histo != NULL) free((MEM_PTR)tms->histo); if (tms->lumap != NULL) free((MEM_PTR)tms->lumap); /* free private data */ for (i = tmNumPkgs; i--; ) if (tms->pd[i] != NULL) (*tmPkg[i]->Free)(tms->pd[i]); free((MEM_PTR)tms); /* free basic structure */ } /******************** Shared but Private library routines *********************/ BYTE tmMesofact[BMESUPPER-BMESLOWER]; void tmMkMesofact() /* build mesopic lookup factor table */ { int i; if (tmMesofact[BMESUPPER-BMESLOWER-1]) return; for (i = BMESLOWER; i < BMESUPPER; i++) tmMesofact[i-BMESLOWER] = 256. * (tmLuminance(i) - LMESLOWER) / (LMESUPPER - LMESLOWER); } int tmErrorReturn( /* error return (with message) */ const char *func, TMstruct *tms, int err ) { if (tms != NULL) { tms->lastFunc = func; tms->lastError = err; if (tms->flags & TM_F_NOSTDERR) return(err); } fputs(func, stderr); fputs(": ", stderr); fputs(tmErrorMessage[err], stderr); fputs("!\n", stderr); return(err); }