Skip to content
Snippets Groups Projects
CMedit.C 12.95 KiB
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory.h>
#include <string.h>


#include <ctype.h>
#undef isspace

#include "CMedit.H"
#include "colorpatch.H"

#ifdef __APPLE__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif

CMedit :: CMedit(int x, int y, int w, int h, const char *label)
		: Fl_Gl_Window(x,y,w,h,label) {
  init();
  end();
}

int CMedit::fload( FILE *inf ) {
  char line[256];
  char *cp, *sp;
  int count = -1;
  int i, ix, ox, nix, prevox;
  float rgba[4], hsba[4], phsba[4];
  int lno;
  static enum CMfield flds[4] = { HUE, SAT, BRIGHT, ALPHA };
  int f;
  char tc[2];

  lno = 0;
  this->postscale_ = 1;
  this->postexpon_ = 1;
  ox = 0;
  while(fgets(line, sizeof(line), inf) != NULL) {
    lno++;
    for(cp = line; *cp && isspace(*cp); cp++)
	;
    if(*cp == '\0' || *cp == '\n')
	continue;

    sp = strstr(cp, "#Ascale");
    if(sp != NULL) {
	if(0==strncmp(sp, "#Ascale", 7)) {
	    *sp = '\0';
	    sp += 7;
	    if(*sp == '=' || *sp == ':')
		sp++;
	    sscanf(sp, "%f%f", &this->postscale_, &this->postexpon_);
	    cp = sp;	/* don't record this one as a comment */
	}
    }

    if(*cp == '#') {
	if(ncomments >= maxcomments) {
	    maxcomments *= 2;
	    comments = (char **)realloc( comments, maxcomments * sizeof(char *) );
	}
	comments[ncomments++] = strdup( line );
	continue;
    }

    /* ``nnn:'' entries set the colormap pointer ... */
    if(sscanf(line, "%d%1[:]", &nix, tc) == 2) {
	ix = nix;
	cp = strchr(line, ':') + 1;
	while(*cp && isspace(*cp)) cp++;
	if(*cp == '\0' || *cp == '\n' || *cp == '#')
	    continue;
    }

    if(count == -1) {
	if(!sscanf(line, "%d", &count) || count < 1) {
	    fprintf(stderr, "Not a .cmap file?  Doesn't begin with a number.\n");
	    return 0;
	}
	cment( count );
	ix = 0, prevox = 0;
	continue;
    } else {

	if(ix >= count)
	    break;
	    
	rgba[3] = 1;
	if(sscanf(cp, "%f%f%f%f", &rgba[0],&rgba[1],&rgba[2],&rgba[3]) < 3) {
	    fprintf(stderr, "Couldn't read colormap line %d (cmap entry %d of 0..%d)\n",
		lno, ix, count-1);
	    return 0;
	}
	rgb2hsb( rgba[0],rgba[1],rgba[2], &hsba[0],&hsba[1],&hsba[2] );
	hsba[3] = rgba[3];
	if(ox == 0)
	    memcpy(phsba, hsba, sizeof(phsba));
	else
	    phsba[0] = huenear(phsba[0], hsba[0]);
	ox = (cment_-1) * ix / count + 1;
	for(f = 0; f < 4; f++) {
	    dragrange( prevox, ox, flds[f], phsba[f], hsba[f], 1.0 );
	    phsba[f] = hsba[f];
	}
	prevox = ox;
	ix++;
    }

  }
  if(count <= 0) {
    fprintf(stderr, "Empty colormap file?\n");
    return 0;
  }
  if(ix < count)
    fprintf(stderr, "Only got %d colormap entries, expected %d\n", ix, count);

  for(f = 0; f < 4; f++)
    dragrange( prevox, cment_, flds[f], phsba[f], phsba[f], 1.0 );

  if(postscale_ != 1 || postexpon_ != 1) {
      for(i = 0; i < count; i++)
	alpha[i] = (alpha[i] > 0) ? pow(alpha[i] / postscale_, 1 / postexpon_) : 0;
  }

  return ix > 0;
}

int CMedit::fsave( FILE *outf ) {
  float r,g,b;
  int i, ok = 1;

  fprintf(outf, "%d\n", cment());
  for(i = 0; i < cment_; i++) {
    hsb2rgb( vh[i], vs[i], vb[i], &r, &g, &b );
    if(fprintf(outf, "%f %f %f %f\n", r, g, b, Aout( alpha[i] )) <= 0)
	ok = 0;
  }
  if(postscale_ != 1 || postexpon_ != 1)
    fprintf(outf, "#Ascale %g %g\n", postscale_, postexpon_);
  if(ncomments > 0)
    fputs("\n", outf);
  for(i = 0; i < ncomments; i++)
    fputs(comments[i], outf);
  return ok;
}
    
#define  YMAX  (1.0)
#define  YMIN  (-.25)
#define  YBAR0  (-.01)
#define  YBAR1  (-.06)

#define  XMIN  (-cment_ / 16.f)
#define  XMAX  cment_

int CMedit::wx2x( int wx ) {
  return (int) (XMIN + wx * (XMAX-XMIN) / w());
}

float CMedit::wy2y( int wy ) {
  return (YMAX - wy * (YMAX-YMIN) / h());
}

void CMedit::draw() {
  int i;
  float v;
  int coarse = (w() >= 2*cment());

  if(!valid() || damage()/* == FL_DAMAGE_ALL*/) {
    /* Assume reshaped */
    valid(1);
    remin = 0;  remax = cment()-1;
    glViewport( 0, 0, w(), h() );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho( XMIN, XMAX,  YMIN, 1,  -1, 1 );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
  }

  if(remax-remin < cment()-1) {
    glEnable( GL_SCISSOR_TEST );
    glScissor( remin*w()/cment(), (remax+1)*w()/cment(), 0, h() );
  } else {
    glDisable( GL_SCISSOR_TEST );
  }

  glClearColor( 0,0,0,0 );
  glClear( GL_COLOR_BUFFER_BIT );

  glDisable( GL_DEPTH_TEST );
  glDisable( GL_LIGHTING );
  glDisable( GL_TEXTURE_2D );
  glDisable( GL_COLOR_MATERIAL );

  glBegin( GL_QUADS );
  glColor3f( 0,0,0 );
  glVertex2f( remin, -.05 );
  glVertex2f( remax+1, -.05 );
  glColor3f( 1,1,1 );
  glVertex2f( remax+1, YMIN );
  glVertex2f( remin, YMIN );
  glEnd();

  glLineWidth( 1 );
  glDisable( GL_BLEND );
  if(hsbmode) {
    glColor3f( 1,1,0 ); /* Hue: yellow */
    float midhue = y2hue(.5);
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, v = hue2y( huenear( vh[i], midhue ) ) );
	if(coarse) glVertex2f( i+1, v );
    }
    glEnd();

    glColor3f( .3,1,.25 );  /* Saturation: teal */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, vs[i] );
	if(coarse) glVertex2f( i+1, vs[i] );
    }
    glEnd();

    glColor3f( .5,.2,1 );  /* Brightness: purple */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, vb[i] );
	if(coarse) glVertex2f( i+1, vb[i] );
    }
    glEnd();
  } else {

    float r[CMENTMAX], g[CMENTMAX], b[CMENTMAX];

    for(i = remin; i <= remax; i++)
	hsb2rgb( vh[i], vs[i], vb[i], &r[i], &g[i], &b[i] );

    glColor3f( 1,0,0 ); /* red */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, r[i] );
	if(coarse) glVertex2f( i+1, r[i] );
    }
    glEnd();

    glColor3f( 0,1,0 );  /* green */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, g[i] );
	if(coarse) glVertex2f( i+1, g[i] );
    }
    glEnd();

    glColor3f( 0,0,1 );  /* blue */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, b[i] );
	if(coarse) glVertex2f( i+1, b[i] );
    }
    glEnd();
  }   

  glColor3f( .7,.7,.7 );  /* alpha: gray */
  glBegin( GL_LINE_STRIP );
  for(i = remin; i <= remax-coarse; i++) {
    glVertex2f( i, alpha[i] );
    if(coarse) glVertex2f( i+1, alpha[i] );
  }
  glEnd();

  glDisable( GL_BLEND );
  glShadeModel( GL_SMOOTH );
  glBegin( GL_QUAD_STRIP );
  for(i = remin; i <= remax; i++) {
    float rgb[3];
    hsb2rgb( vh[i], vs[i], vb[i], &rgb[0],&rgb[1],&rgb[2] );
    glColor3fv( rgb );
    glVertex2f( i, YBAR0 );
    glVertex2f( i, YBAR1 );
    if(coarse) {
	glVertex2f( i+1, YBAR0 );
	glVertex2f( i+1, YBAR1 );
    }
  }
  glEnd();

  glEnable( GL_BLEND );
  glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
  glShadeModel( GL_SMOOTH );
  glBegin( GL_QUAD_STRIP );
  for(i = remin; i <= remax; i++) {
    float rgba[4];
    hsb2rgb( vh[i], vs[i], vb[i], &rgba[0],&rgba[1],&rgba[2] );
    rgba[3] = alpha[i];
    glColor4fv( rgba );
    glVertex2f( i, YBAR1 );
    glVertex2f( i, YMIN );
    if(coarse) {
	glVertex2f( i+1, YBAR1 );
	glVertex2f( i+1, YMIN );
    }
  }
  glEnd();
  glDisable( GL_BLEND );

  if(remin <= 0) {
    glBegin( GL_QUAD_STRIP );
    for(i = 0; i < 128; i++) {
	float rgb[3];
	float y = i / 127.;
	hsb2rgb( y2hue(y), 1, 1, &rgb[0],&rgb[1],&rgb[2] );
	glColor3fv( rgb );
	glVertex2f( XMIN, y );
	glVertex2f( XMIN/4, y );
    }
    glEnd();
  }

  glFinish();

  /* draw (I hope) any children lying on top of us */
  if(children()) Fl_Gl_Window::draw();
}


float CMedit::drag( int x, enum CMfield field, float y, float lerp ) {
  float ny, oy, *vp;
  float rgb[3];
  int usergb = 0;
  if(x < 0 || x >= cment())
    return 0;
  if(locked && (x < lockmin || x > lockmax))
    return 0;
  switch(field) {
  case HUE: vp = &vh[x]; break;
  case SAT: vp = &vs[x]; break;
  case BRIGHT: vp = &vb[x]; break;
  case ALPHA: vp = &alpha[x]; break;
  default:
	hsb2rgb( vh[x],vs[x],vb[x], &rgb[0],&rgb[1],&rgb[2] );
	vp = &rgb[ field - RED ];
	usergb = 1;
  }
  if(field == HUE) {
    oy = hue2y(*vp);
    y = hue2y( huenear( y2hue(y), *vp ) );
    ny = (1-lerp) * oy + lerp * y;
    *vp = y2hue(ny);
  } else {
    oy = *vp;
    ny = (1-lerp) * oy + lerp * y;
    if(field != ALPHA) {
	if(ny < 0) ny = 0;
	else if(ny > 1) ny = 1;
    }
    *vp = ny;
    if(usergb)
	rgb2hsb( rgb[0],rgb[1],rgb[2], &vh[x],&vs[x],&vb[x] );
  }
  return ny;
}

void CMedit::dragrange( int x0, int x1, enum CMfield field, float y0, float y1, float lerp ) {
  int x;
  float dy;
  if(x0 == x1  || x0 < 0 && x1 < 0  ||  x0 >= cment() && x1 >= cment())
    return;

  dy = (y1 - y0) / (x1 - x0);
  if(x0 < x1)
    for(x = x0; x < x1; x++)
	drag( x, field, y0 + dy*(x-x0), lerp );
  else
    for(x = x0; x > x1; x--)
	drag( x, field, y0 + dy*(x-x0), lerp );
}


int CMedit::handle(int ev) {
  int x = wx2x( Fl::event_x() );
  float y = wy2y( Fl::event_y() );
  int xmin, xmax;
 
  switch(ev) {

  case FL_SHORTCUT:
    if(Fl::event_key() == 'u') {
	undo();
	return 1;
    }
    return 0;

  case FL_PUSH:
    dragfrom = x, dragval = y, dragamount = 0;
    draghue = ( (x - XMIN) * (x - (XMIN/4)) < 0 );	/* If dragging on hue strip */

    if(Fl::event_state(FL_SHIFT)) {
	dragfield = ALPHA;
    } else {
	// Which button?  Take account of alt/meta modifiers too.
	
	int btn = 1;

	if(Fl::event_state(FL_BUTTON2 | FL_ALT))
	    btn = 2;
	else if(Fl::event_state(FL_BUTTON3 | FL_META))
	    btn = 3;

	static CMfield btn2field[2][3] = {
	    { RED, GREEN, BLUE },
	    { HUE, SAT, BRIGHT }
	};
	dragfield = btn2field[ hsbmode ][ btn-1 ];
    }
    if(Fl::event_key('l')) {
	/*...*/
    } else if(Fl::event_key('r')) {
	/*...*/
    } else {
	snapshot();
    }
    /* Fall into ... */
    
  case FL_DRAG:
  case FL_RELEASE:

#ifdef NOTYET
    if(draghue) {
	float h0 = y2hue( y );
	hueshift += (y - dragval) / huezoom;
	huezoom *= (x - dragfrom)
    } else { ... }
#endif
    dragrange( dragfrom, x, dragfield, dragval, y,
		    Fl::event_state(FL_CTRL) ? 1.0 : lerpval );
    xmin = (dragfrom<x) ? dragfrom : x;
    xmax = dragfrom+x-xmin;
    if(damage() == 0) {
	remin = xmin, remax = xmax;
    } else {
	if(remin > xmin) remin = xmin;
	if(remax < xmax) remax = xmax;
    }
    damage(1);
    if(dragfrom != x)
	dragamount = 1;
    if(ev == FL_RELEASE && dragamount == 0)
	drag( x, dragfield, y, Fl::event_state(FL_CTRL) ? 1.0 : lerpval );

    dragfrom = x, dragval = y;
    report( x );
    return 1;

  case FL_ENTER:
  case FL_LEAVE:
    dragfrom = x;
    report( x );
    return 1;

  case FL_MOVE:
    dragfrom = x;
    report( x );
    return 1;
    
  }
  return 0;
}

float CMedit::huenear( float hue, float hueref ) {
  float h = hue;
  if(h - hueref > .5f)
    do { h -= 1.0f; } while (h - hueref > .5);
  else
    while(h - hueref < -.5f) h += 1.0f;
  return h;
}

float CMedit::hue2y( float hue ) {
  /* Find closest multiple of given hue to reference y-position y0 */
  return (hue - hueshift) * huezoom;
}

float CMedit::y2hue( float y ) {
  return y / huezoom + hueshift;
}

static float sample( float *a, int ents, float at, int smooth ) {
  if(at <= 0 || ents <= 1) return a[0];
  if(at >= 1) return a[ents-1];
  float eat = at * ents;
  int iat = (int)eat;
  return smooth ? a[iat]*(1 - (eat-iat)) + a[iat+1]*(eat-iat)
		: a[iat];
}

void CMedit::postscale( float s ) {
  postscale_ = s;
}
void CMedit::postexpon( float e ) {
  postexpon_ = e;
}

void CMedit::cment( int newcment ) {
  if(newcment < 1) return;
  if(newcment > CMENTMAX) {
    fprintf(stderr, "Oops, can't ask for more than %d colormap entries -- using %d\n",
	CMENTMAX,CMENTMAX);
    newcment = CMENTMAX;
  }
  if(cment_ == newcment)
    return;

  /* Resample */

  snapshot();

  int smooth = 0; // (cment_ < newcment);
  for(int o = 0; o < newcment; o++) {
    float at = newcment > 1 ? (float)o / (newcment-1) : 0;
    vh[o] = sample( &snap[0][0], cment_, at, smooth );
    vs[o] = sample( &snap[1][0], cment_, at, smooth );
    vb[o] = sample( &snap[2][0], cment_, at, smooth );
    alpha[o] = sample( &snap[3][0], cment_, at, smooth );
  }
  cment_ = newcment;
  remin = 0;
  remax = cment_ - 1;
  lockmin = 0;
  lockmax = cment_ - 1;

  if(cmentcb_ != 0)
    (*cmentcb_)( this );
  report( dragfrom );

  redraw();
}

void CMedit::getrgba( int index, float rgba[4] ) const
{
  if(index < 0 || index >= cment()-1) {
    rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0;
  } else {
    hsb2rgb( vh[index], vs[index], vb[index], &rgba[0],&rgba[1],&rgba[2] );
    rgba[3] = alpha[index];
  }
}

void CMedit::gethsba( int index, float hsba[4] ) const
{
  if(index < 0 || index >= cment()-1) {
    hsba[0] = hsba[1] = hsba[2] = hsba[3] = 0;
  } else {
    hsba[0] = vh[index];
    hsba[1] = vs[index];
    hsba[2] = vb[index];
    hsba[3] = alpha[index];
  }
}
  

void CMedit::reportto( void (*func)( CMedit *cm, int index ) ) {
  reportfunc = func;
}

void CMedit::report( int index )
{
  if(index < 0 || index >= cment() || reportfunc == NULL) 
    return;
  lastx_ = index;

  (*reportfunc)( this, index );
}

void CMedit::report()
{
  CMedit::report( lastx_ );
}

float CMedit::Aout( float Ain ) const
{
  return Ain > 0 ? postscale_ * pow( Ain, postexpon_ ) : 0;
}

void colorpatch::draw() {
    glClearColor( vr,vg,vb,va );
    glClear( GL_COLOR_BUFFER_BIT );
}