/* sgamma.c  Stuart Levy, Geometry Center, University of Minnesota  1995
 * 			  and NCSA, University of Illinois          2001	
 *
 * For Irix, uses IrisGL:
 *	cc -o sgamma  sgamma.c  -lgl -lm
 *
 * For XFree86 (>= 4.1), uses XF86VidMode extension:
 *	cc -o sgamma  sgamma.c  -L/usr/X11R6/lib -lX11 -lXxf86vm -lXext -lm
 */


#ifdef sgi
#  include <gl/gl.h>
#else
#  include <X11/Xlib.h>
#  include <X11/extensions/xf86vmode.h>
#endif

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <memory.h>
#include <alloca.h>

char Usage[] = "\
Usage: %s [-m colormap.ppm] [-b brighten] [-B prebrighten] [[-g] gamma]\n\
Uses hardware gamma mapping to adjust the brightness and/or gamma\n\
of the display.  With no arguments, resets brightness and gamma to their\n\
defaults.\n\
  ``brightness'' multiplies all displayed screen values by the given factor;\n\
	may be a single number or 3 comma-separated ones for independent R,G,B\n\
	brightening.  Default is ``-b 1'' a.k.a. ``-b 1,1,1''.\n\
  ``gamma'' sets the exponent of a power-law mapping of pixel values to	\n\
	screen values, and may also be either one or three comma-separated\n\
	numbers.  Larger values brighten faint things, lightening\n\
	shadows, and making colors less saturated.  The default is the\n"
#ifdef sgi
"	systemwide value as set by gamma(1), normally ``-g 1.7''.\n"
#else
"	systemwide value as set by gamma(1) or xgamma(1), normally ``-g 1.7''.\n"
#endif
"	Typically about -g 2.4 gives linear relation from pixel value to brightness.\n"

#ifdef sgi
"\n\
Unlike the SGI program /usr/sbin/gamma, this program\n\
  - does not require special privileges (while only root can run ``gamma'' on Irix)\n\
  - makes only a non-permanent setting, which reverts to the system default\n\
    at next X reset, typically at logout.\n"
#endif
;

#ifdef sgi
# define FULLSCALE  255
#else
# define FULLSCALE  65535
#endif

int
screengamma(float gamma[3], float max[3], float prescale[3], float rewhite[3],
					char *wholemap, int nwholemap )
{
    static float one[3] = { 1,1,1 };
    static float mygamma[3];
    unsigned short *ramp[3];
    float *framp;
    int i, c;
    int nramp;

#ifdef sgi

    foreground();
    noport();
    winopen("");
    if(gamma == NULL) {
	FILE *gamfile = fopen("/etc/config/system.glGammaVal", "r");

	mygamma[0] = 1.7;	/* SGI default */
	if(gamfile)
	    fscanf(gamfile, "%f", &mygamma[0]);
	mygamma[1] = mygamma[2] = mygamma[0];
	gamma = &mygamma[0];
    }
    nramp = 256;

#else  /* XFree86 */

    Display *dpy;
    int scrno;
    dpy = XOpenDisplay(NULL);
    if(dpy == NULL) {
	fprintf(stderr, "Can't open DISPLAY\n");
	return 1;
    }
    scrno = XScreenNumberOfScreen( DefaultScreenOfDisplay( dpy ) );
    if(! XF86VidModeGetGammaRampSize( dpy, scrno, &nramp )) {
	fprintf(stderr, "Can't XF86VidModeGetGammaRampSize(): is this an XFree86 >=4.1 display?\n");
	return 1;
    }

    if(gamma == NULL) {
	XF86VidModeGamma sysgamma;
	if(!XF86VidModeGetGamma( dpy, scrno, &sysgamma )) {
	    fprintf(stderr, "Can't XF86VidModeGetGamma()? Assuming 1.0\n");
	    sysgamma.red = sysgamma.green = sysgamma.blue = 1;
	}
	mygamma[0] = sysgamma.red;
	mygamma[1] = sysgamma.green;
	mygamma[2] = sysgamma.blue;
	gamma = &mygamma[0];
    }
#endif /* XFree86 */

    framp = (float *)alloca( nramp * sizeof(float) );
    for(i = 0; i < 3; i++)
	ramp[i] = (unsigned short *)alloca( nramp * sizeof(unsigned short) );

    if(max == NULL) max = one;
    printf(gamma[1] == gamma[0] && gamma[2] == gamma[0]
	? "Setting gamma to %g" : "Setting gamma: red %g green %g blue %g",
	gamma[0], gamma[1], gamma[2]);
    if(max[0] != 1 || max[1] != 1 || max[2] != 1) {
	printf(max[0] == max[1] && max[0] == max[2]
	 ? ", max RGB %g" : ", max R %g G %g B %g",
	 max[0], max[1], max[2]);
    }

    if(wholemap && nwholemap != nramp) {
	fprintf(stderr, "Wrong gamma ramp file size: need %dx1 PPM, not %dx1\n",
		nramp, nwholemap);
	return 1;
    }

    for(c = 0; c < 3; c++) {
	float invgamma = 1./gamma[c];
	float dramp = 1./(nramp-1);
	int fullscale;

	for(i = 0; i < nramp; i++)
	    framp[i] = pow(prescale[c]*i*dramp, invgamma);

	fullscale = FULLSCALE * max[c];
	for(i = 0; i < nramp; i++) {
	    float v = fullscale<0 ? (FULLSCALE + fullscale*(framp[i]))
				  : fullscale * framp[i];
	    if(v < 0) v = 0;
	    else if(v > FULLSCALE) v = FULLSCALE;
	    if(wholemap) {
		v = (v * (nramp-1)) / FULLSCALE;	/* now 0<=v<nramp */
		ramp[c][i] = wholemap[ 3*(int)(v + .5) + c ]
			    * (FULLSCALE/255);
	    } else {
		ramp[c][i] = (int)v;
	    }
	}
    }
    if(rewhite) {
	/* Hack to allow damping down the awful white that some programs use --
	 * just replace the topmost colortable slot.
	 */
	ramp[0][nramp-1] = FULLSCALE*rewhite[0];
	ramp[1][nramp-1] = FULLSCALE*rewhite[1];
	ramp[2][nramp-1] = FULLSCALE*rewhite[2];
    }

#ifdef sgi
    gammaramp((short *)ramp[0], (short *)ramp[1], (short *)ramp[2]);
#else
    if(! XF86VidModeSetGammaRamp( dpy, scrno, nramp, ramp[0],ramp[1],ramp[2] )) {
	fprintf(stderr, "sgamma: Can't XF86VidModeSetGammaRamp()?\n");
	return 1;
    }
#endif

    return 0;
}

int getint( FILE *f ) {
    int c, v;
    while(isspace(c = getc(f)) || c == '#') {
	if(c == '#') {
	    while((c = getc(f)) != EOF && c != '\n')
		;
	}
    }
    if(c == EOF)
	return -1;
    if(isdigit(c)) {
	v = c - '0';
	while(isdigit(c = getc(f))) {
	    v = v*10 + c - '0';
	}
	if(c != EOF)
	    ungetc(c, f);
	return v;
    }
    return -1;
}

int nx, ny;
int quiet;

unsigned char *loadppm( char *fname, int *nxp, int *nyp ) {
    FILE *f = fopen(fname, "rb");
    char line[512];
    int c, maxval;
    unsigned char *rgb;

    if(f == NULL) {
	fprintf(stderr, "%s: cannot open input\n", fname);
	exit(1);
    }
    c = getc(f);
    if(c != 'P' || getc(f) != '6') {
	fprintf(stderr, "%s: not a binary PPM image file\n", fname);
	exit(1);
    }
    nx = getint(f);
    ny = getint(f);
    maxval = getint(f);
    if(maxval != 255) {
	fprintf(stderr, "%s: not a binary PPM image file\n", fname);
	exit(1);
    }
    while((c = getc(f)) != EOF && c != '\n')
	;
    rgb = (unsigned char *)malloc( nx*ny*3 );
    if(fread( rgb, nx*ny*3, 1, f ) <= 0) {
	fprintf(stderr, "%s: couldn't read all expected %d*%d*3 image bytes!\n",
		fname, nx, ny);
	return NULL;
    }
    if(nxp) *nxp = nx;
    if(nyp) *nyp = ny;
    return rgb;
}

main(int argc, char *argv[]) {
    float gamma[3];
    float max[3];
    float prescale[3];
    float white[3];
    int gotgamma = 0;
    int setwhite = 0;
    int k, status;
    char *prog = argv[0];
    char *wholemap = NULL;
    int nwholemap;
    char *wholemapname;

    gamma[0] = gamma[1] = gamma[2] = 1.7;
    max[0] = max[1] = max[2] = 1;
    prescale[0] = prescale[1] = prescale[2] = 1;

    while(argc > 2 && argv[1][0] == '-') {
        if(argv[1][1] == 'b') {
	    k = sscanf(argv[2], "%f%*c%f%*c%f", &max[0], &max[1], &max[2]);
	    if(k < 2) max[1] = max[0];
	    if(k < 3) max[2] = max[1];
	    argc -= 2, argv += 2;

	} else if(argv[1][1] == 'B') {
	    k = sscanf(argv[2], "%f%*c%f%*c%f",
		&prescale[0], &prescale[1], &prescale[2]);
	    if(k < 2) prescale[1] = prescale[0];
	    if(k < 3) prescale[2] = prescale[1];
	    argc -= 2, argv += 2;

	} else if(argv[1][1] == 'w') {
	    k = sscanf(argv[2], "%f%*c%f%*c%f", &white[0], &white[1], &white[2]);
	    if(k < 2) white[1] = white[0];
	    if(k < 3) white[2] = white[1];
	    setwhite = 1;
	    argc -= 2, argv += 2;

	} else if(argv[1][1] == 'g') {
	    switch( sscanf(argv[2], "%f%*c%f%*c%f", &gamma[0], &gamma[1], &gamma[2]) ) {
	    case 0: case -1:
		fprintf(stderr, Usage, prog);
		exit(1);
	    case 1:  gamma[1] = gamma[0];
	    case 2:  gamma[2] = gamma[1];
	    }
	    gotgamma = 1;
	    argc -= 2, argv += 2;

	} else if(argv[1][1] == 'm') {
	    int nx, ny;
	    wholemap = loadppm( argv[2], &nx, &ny );
	    if(wholemap == NULL)
		exit(1);
	    nwholemap = (nx == 1) ? ny : nx;
	    wholemapname = argv[2];
	    argc -= 2, argv += 2;

	} else if(argv[1][1] == 'q') {
	    quiet = 1;
	    argc--, argv++;

	} else {
	    break;
	}
    }

    if(argc > 1) {
	switch( sscanf(argv[1], "%f%*c%f%*c%f", &gamma[0], &gamma[1], &gamma[2]) ) {
	case 0: case -1:
	    fprintf(stderr, Usage, prog);
	    exit(1);
	case 1:  gamma[1] = gamma[0];	/* ... and fall into ... */
	case 2:  gamma[2] = gamma[1];
	}
	if(argc > 2) gamma[1] = gamma[2] = atof(argv[2]);
	if(argc > 3) gamma[2] = atof(argv[3]);
	gotgamma = 1;
    }

    status = screengamma( gotgamma ? gamma : NULL,
			  max, prescale,
			  setwhite ? white : NULL,
			  wholemap, nwholemap );
    if(status == 0) {
	if(wholemap) {
	    printf(" mapped through %s\n", wholemapname);
	} else {
	    printf("\n");
	}
    }
    exit(status);
}

