#include <stdio.h> #include <stdlib.h> #include <math.h> #include <memory.h> #include <string.h> #include <ctype.h> #undef isspace #include "CMedit.H" 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; int count = -1; int 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; while(fgets(line, sizeof(line), inf) != NULL) { lno++; for(cp = line; *cp && isspace(*cp); cp++) ; if(*cp == '\0' || *cp == '\n') continue; 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 ); return ix > 0; } int CMedit::fsave( FILE *outf ) { float r,g,b; int i, ok = 1; for(i = 0; i < ncomments; i++) fputs(comments[i], outf); if(ncomments > 0) fputs("\n", outf); 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, alpha[i]) <= 0) ok = 0; } 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 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(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 { if(hsbmode) { if(Fl::event_state(FL_BUTTON1)) dragfield = HUE; else if(Fl::event_state(FL_BUTTON2)) dragfield = SAT; else dragfield = BRIGHT; } else { if(Fl::event_state(FL_BUTTON1)) dragfield = RED; else if(Fl::event_state(FL_BUTTON2)) dragfield = GREEN; else dragfield = BLUE; } } 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: report( x ); return 1; case FL_MOVE: 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::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; redraw(); } void CMedit::reportto( void (*func)(int x, float h,float s,float b,float a, float red,float green,float blue) ) { reportfunc = func; } void CMedit::report( int x ) { float r,g,b; if(x < 0 || x > cment()-1 || reportfunc == NULL) return; hsb2rgb( vh[x], vs[x], vb[x], &r,&g,&b ); (*reportfunc)( x, vh[x], vs[x], vb[x], alpha[x], r,g,b ); }