/* Terminality - a portable terminal handling library * Copyright (C) 1998-2002, Emil Mikulic. * This is LGPL - look at COPYING.LIB */ /* Project: Terminality * File: precalc.c * Author: Emil Mikulic * Description: ANSI-artifier precalc table generator */ #include "precalc.h" #ifdef _WIN32 #define inline __inline #endif const char precalc_rcsid[] = "$Id: precalc.c,v 1.10 2002/07/26 02:12:13 darkmoon Exp $"; /* -CP437- * * I grew up on DOS so I developed an attachment to codepage 437. * This code works fine for me on Linux and FreeBSD consoles. If you're * using some form of telnet/SSH client, set your encoding to CP437 and * use a font that has the right characters in the right places * (like the standard font "Terminal" under Windows) */ typedef struct pc_rgbtriplet { unsigned char r,g,b; } pc_rgbtriplet; typedef struct shadechar { color fgcol, bgcol; /* foreground and background colors */ chtype chr; /* character */ int diff; /* difference from requested color */ } shadechar; #define DOS_COLORS /* use dos-box colors */ #define FADE_TO_BLACK /* fade colorbox after calculating it */ /* RGB values for each of the 16 colors - obtained from a screenshot of a windowed dos-box under Win98. Feel free to send me different values, but tell me how you got them */ #ifdef DOS_COLORS const pc_rgbtriplet pc_rgbval[16] = { { 0, 0, 0}, /* 0 - black */ { 0, 0,168}, /* 1 - blue */ { 0,168, 0}, /* 2 - green */ { 0,168,168}, /* 3 - cyan */ {168, 0, 0}, /* 4 - red */ {168, 0,168}, /* 5 - magenta */ {168, 84, 0}, /* 6 - brown */ {168,168,168}, /* 7 - light gray */ { 84, 84, 84}, /* 8 - dark gray */ { 84, 84,252}, /* 9 - light blue */ { 84,252, 84}, /* 10 - light green */ { 84,252,252}, /* 11 - light cyan */ {252, 84, 84}, /* 12 - light red */ {252, 84,252}, /* 13 - light magenta */ {252,252, 84}, /* 14 - yellow */ {252,252,252} /* 15 - white */ }; #else const pc_rgbtriplet pc_rgbval[16] = { { 0, 0, 0}, /* 0 - black */ { 0, 0,128}, /* 1 - blue */ { 0,128, 0}, /* 2 - green */ { 0,128,128}, /* 3 - cyan */ {128, 0, 0}, /* 4 - red */ {128, 0,128}, /* 5 - magenta */ {128, 0, 0}, /* 6 - brown */ {128,128,128}, /* 7 - light gray */ { 64, 64, 64}, /* 8 - dark gray */ { 0, 0,255}, /* 9 - light blue */ { 0,255, 0}, /* 10 - light green */ { 0,255,255}, /* 11 - light cyan */ {255, 0, 0}, /* 12 - light red */ {255, 0,255}, /* 13 - light magenta */ {255,255, 0}, /* 14 - yellow */ {255,255,255} /* 15 - white */ }; #endif #ifndef abs #define abs(x) ((x) >= 0 ? (x) : -(x)) #endif /* percentages of block characters - full dots out of 64 */ const int pc_blockdots[5] = { 0, /* 32 */ 16, /* 176 */ 32, /* 177 */ 48, /* 178 */ 64 /* 219 */ }; /* chars which correspond to the given block value */ const unsigned char pc_chblock[5] = { 32, 176, 177, 178, 219 }; /* Block characters in Codepage 437 */ #define CHAR_BARTOP 223 #define CHAR_BARBOTTOM 220 #define CHAR_BARLEFT 221 #define CHAR_BARRIGHT 222 #define CHAR_BLOCK1 176 #define CHAR_BLOCK2 177 #define CHAR_BLOCK3 178 #define CHAR_BLOCK4 219 #define sqr(x) ((x)*(x)) #define limit(x,a,b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x))) superchar pc_monochrome[32], pc_monochrome_shape[16][16][16][16], pc_color[16][16][16]; /* Write superchar */ void putsc(superchar sc) { color fg = (color)(sc.attr & 15), bg = (color)((sc.attr >> 4) & 15); setcolor( fg, bg ); writech( (fg != bg) ? _c(sc.chr) : ' ' ); } superchar get_monochrome(unsigned char c) { return pc_monochrome[c >> 3]; } superchar get_monochrome_shape(unsigned char p1, unsigned char p2, unsigned char p3, unsigned char p4) { return pc_monochrome_shape[p1 >> 4][p2 >> 4][p3 >> 4][p4 >> 4]; } superchar get_rgb_block(unsigned char r, unsigned char g, unsigned char b) { return pc_color[r >> 4][g >> 4][b >> 4]; } /* Get brightness of given shade */ inline int pc_shadebrightness(color shade) { switch (shade) { #ifdef DOS_COLORS case Black: return 0; case DarkGray: return 84; case LightGray: return 168; case White: return 252; #else case Black: return 0; case DarkGray: return 64; case LightGray: return 128; case White: return 255; #endif default: printw("Passed bad shade (%d) to pc_shadebrightness()", shade); raise(SIGTERM); return -1; } } /* Better of two shadechars */ inline shadechar pc_best(shadechar a, shadechar b) { int acon, bcon; /* If blocks have same diff then pick less contrasting one */ if (a.diff == b.diff) { acon = abs( pc_shadebrightness(a.fgcol) - pc_shadebrightness(a.bgcol) ); bcon = abs( pc_shadebrightness(b.fgcol) - pc_shadebrightness(b.bgcol) ); if (acon < bcon) return a; else return b; } /* Return the more accurate one */ if (a.diff < b.diff) return a; else return b; } /* Make shadechar from gray shade on gray shade and block value */ inline shadechar pc_makegrayongrayblock(int c, color fg, color bg, chtype block) { int fgbright, bgbright, brightness = 0; shadechar sc; fgbright = pc_shadebrightness(fg); bgbright = pc_shadebrightness(bg); if (bg > LightGray) { printw("Passed illegal bgcolor (%d) to \ pc_makegrayongrayblock()\n", bg); raise(SIGTERM); } /* Change using block */ switch (block) { case ' ': brightness = bgbright; break; case CHAR_BLOCK1: brightness = (fgbright / 4) + ((bgbright * 3) / 4); break; case CHAR_BLOCK2: brightness = (fgbright + bgbright) / 2; break; case CHAR_BLOCK3: brightness = ((fgbright * 3) / 4) + (bgbright / 4); break; case CHAR_BLOCK4: brightness = fgbright; break; default: printw("Passed non-block char %d to \ makegrayongrayblock()\n", block); raise(SIGTERM); } /* Construct shadechar */ sc.fgcol = fg; sc.bgcol = bg; sc.chr = block; sc.diff = abs(c - brightness); return sc; } /* Return best block for gray on gray shadechar */ inline shadechar pc_grayongrayblock(int c, color fg, color bg) { shadechar sc; sc = pc_makegrayongrayblock(c, fg, bg, ' '); sc = pc_best(sc, pc_makegrayongrayblock(c, fg, bg, CHAR_BLOCK1) ); sc = pc_best(sc, pc_makegrayongrayblock(c, fg, bg, CHAR_BLOCK2) ); sc = pc_best(sc, pc_makegrayongrayblock(c, fg, bg, CHAR_BLOCK3) ); sc = pc_best(sc, pc_makegrayongrayblock(c, fg, bg, CHAR_BLOCK4) ); /* Return it */ return sc; } /* Return best monochrome shadechar */ inline shadechar pc_monochromeblock(int c) { shadechar sc; sc = pc_grayongrayblock(c, DarkGray, Black); sc = pc_best(sc, pc_grayongrayblock(c, LightGray, Black) ); /* sc = pc_best(sc, pc_grayongrayblock(c, White, Black) ); sc = pc_best(sc, pc_grayongrayblock(c, Black, LightGray) );*/ sc = pc_best(sc, pc_grayongrayblock(c, DarkGray, LightGray) ); sc = pc_best(sc, pc_grayongrayblock(c, White, LightGray) ); /* Return it */ return sc; } /* Pack shadechar into superchar */ inline superchar pc_shadetosuper(shadechar a) { superchar sc; sc.attr = (a.fgcol & 15) | ( (a.bgcol & 15) << 4 ); sc.chr = (unsigned char)(a.chr); return sc; } /* Closest foreground shade (color) for a given brightness */ inline color pc_shapefgshade(int bright) { color c, tmp; int diff, newdiff; /* Find closest shade */ c = Black; diff = bright; #define shade_try(this) tmp = this; \ newdiff = abs(bright - pc_shadebrightness(tmp)); \ if (newdiff < diff) \ { \ c = tmp; \ diff = newdiff; \ } shade_try(DarkGray); shade_try(LightGray); shade_try(White); #undef shade_try return c; } /* Closest background shade (color) for a given brightness */ inline color pc_shapebgshade(int bright) { /* Find closest shade */ if ( abs(bright - pc_shadebrightness(LightGray)) < bright ) return LightGray; else return Black; } inline shadechar pc_makeshapeblock(int fg, int bg, chtype chr, int avg) { shadechar sc; /* Initialise shadechar */ sc.fgcol = pc_shapefgshade(fg); sc.bgcol = pc_shapebgshade(bg); sc.chr = chr; return sc; } /* Get best shape shadechar from 4 pixel values */ inline shadechar pc_shapeblock(int pUL, int pUR, int pLL, int pLR, int avg) { shadechar sc, scfull, sctop, scbottom, scleft, scright; int top, bottom, left, right; int difftop, diffbottom, diffleft, diffright, difffull; int brightfull = 0, mindiff; top = (pUL + pUR) >> 1; bottom = (pLL + pLR) >> 1; left = (pUL + pLL) >> 1; right = (pUR + pLR) >> 1; /* Get all possible blocks */ sctop = pc_makeshapeblock(top, bottom, CHAR_BARTOP, avg); scbottom = pc_makeshapeblock(bottom, top, CHAR_BARBOTTOM, avg); scleft = pc_makeshapeblock(left, right, CHAR_BARLEFT, avg); scright = pc_makeshapeblock(right, left, CHAR_BARRIGHT, avg); scfull = pc_monochromeblock(avg); /* Get brightness of full block */ switch (scfull.chr) { case ' ': brightfull = pc_shadebrightness(scfull.bgcol); break; case CHAR_BLOCK1: brightfull = (pc_shadebrightness(scfull.fgcol) / 4) + ((pc_shadebrightness(scfull.bgcol) * 3) / 4); break; case CHAR_BLOCK2: brightfull = (pc_shadebrightness(scfull.fgcol) + pc_shadebrightness(scfull.bgcol)) / 2; break; case CHAR_BLOCK3: brightfull = (pc_shadebrightness(scfull.bgcol) / 4) + ((pc_shadebrightness(scfull.fgcol) * 3) / 4); break; case CHAR_BLOCK4: brightfull = pc_shadebrightness(scfull.fgcol); break; default: printw("Tried to mix bad character in shapeblock()\n"); raise(SIGTERM); } /* Get differences */ difftop = sqr(pUL - pc_shadebrightness(sctop.fgcol)) + sqr(pUR - pc_shadebrightness(sctop.fgcol)) + sqr(pLL - pc_shadebrightness(sctop.bgcol)) + sqr(pLR - pc_shadebrightness(sctop.bgcol)); diffbottom = sqr(pUL - pc_shadebrightness(scbottom.bgcol)) + sqr(pUR - pc_shadebrightness(scbottom.bgcol)) + sqr(pLL - pc_shadebrightness(scbottom.fgcol)) + sqr(pLR - pc_shadebrightness(scbottom.fgcol)); diffleft = sqr(pUL - pc_shadebrightness(scleft.fgcol)) + sqr(pLL - pc_shadebrightness(scleft.fgcol)) + sqr(pUR - pc_shadebrightness(scleft.bgcol)) + sqr(pLR - pc_shadebrightness(scleft.bgcol)); diffright = sqr(pUL - pc_shadebrightness(scright.bgcol)) + sqr(pLL - pc_shadebrightness(scright.bgcol)) + sqr(pUR - pc_shadebrightness(scright.fgcol)) + sqr(pLR - pc_shadebrightness(scright.fgcol)); difffull = sqr(pUL - brightfull) + sqr(pLL - brightfull) + sqr(pUR - brightfull) + sqr(pLR - brightfull); /* Find best block */ mindiff = difffull; sc = scfull; if (difftop < mindiff) { mindiff = difftop; sc = sctop; } if (diffbottom < mindiff) { mindiff = diffbottom; sc = scbottom; } if (diffleft < mindiff) { mindiff = diffleft; sc = scleft; } if (diffright < mindiff) { mindiff = diffright; sc = scright; } /* Return it */ return sc; } inline pc_rgbtriplet pc_make_rgbtriplet(const int block, const int fg, const int bg) { int mix; pc_rgbtriplet f, b, out; f = pc_rgbval[fg]; b = pc_rgbval[bg]; mix = pc_blockdots[block]; out.r = (unsigned char)(((int)(f.r)*mix + (int)(b.r)*(64-mix))/64); out.g = (unsigned char)(((int)(f.g)*mix + (int)(b.g)*(64-mix))/64); out.b = (unsigned char)(((int)(f.b)*mix + (int)(b.b)*(64-mix))/64); return out; } inline superchar pc_bestrgbmatch(const int rr, const int gg, const int bb) { superchar sc; pc_rgbtriplet tr; int i,j,k, bestdist, dist; int r,g,b; r = limit(rr,0,255); g = limit(gg,0,255); b = limit(bb,0,255); bestdist = -1; for (i=0; i<16; i++) for (j=0; j<8; j++) for (k=0; k<5; k++) /* nice list of breakouts so we're not testing redundant values */ if ( ((k==0) && (i==0) && (j==0)) || ((k>0) && (k<4) && (i != j)) || ((k==4) && (j==0)) ){ tr = pc_make_rgbtriplet(k, i, j); dist = sqr(tr.r - r) + sqr(tr.g - g) + sqr(tr.b - b); /* weighting - fudge undesirable combinations */ if (k == 3) dist *= 2; if (i == 0) dist *= 2; if ((j == 0) && (i > 8) && (k != 4)) dist *= 2; if ((j == 0) && (i > 8) && (k == 1)) dist *= 2; if ((j == 4) && (i >= 14) && (k == 1)) dist *= 2; if ((i == 15) && (j != 7) && (j != 11) && (j != 13) && (j != 14)) dist *= 2; /* compare distance to last best match */ if ((dist < bestdist) || (bestdist == -1)) { bestdist = dist; sc.attr = (i & 15) | ( (j & 15) << 4 ); sc.chr = (unsigned char)(pc_chblock[k]); } } return sc; } /* Load precalcs from file */ int pc_loadprecalc(char *fn) { FILE *fp; int i; /* Open file */ fp = fopen(fn, "rb"); if (!fp) { printw("Cannot load precalcs from %s\n", fn); return 0; } #define loader(dest, size, amount) \ i = fread(dest, size, amount, fp); \ if (i != amount) { printw("Error loading precalc tables.\n"); \ return 0; } /* Read data */ loader(pc_monochrome, sizeof(superchar), 32); loader(pc_monochrome_shape, sizeof(superchar), 16*16*16*16); loader(pc_color, sizeof(superchar), 16*16*16); #undef loader /* Success */ printw("Loaded tables.\n"); /* Clean up */ fclose(fp); return 1; } /* Save precalcs to file */ void pc_saveprecalc(char *fn) { FILE *fp; /* Open file */ fp = fopen(fn, "wb"); if (!fp) { printw("Cannot open %s for writing.\n", fn); return; } /* Read data */ fwrite(pc_monochrome, sizeof(superchar), 32, fp); fwrite(pc_monochrome_shape, sizeof(superchar), 16*16*16*16, fp); fwrite(pc_color, sizeof(superchar), 16*16*16, fp); fclose(fp); } /* Precalculate lookup tables */ void pc_doprecalc(void) { int i, j, k, p1, p2, p3, p4; int avg; /* Precalc all simple tables */ printw("simple..."); for (i=0; i<32; i++) pc_monochrome[i] = pc_shadetosuper( pc_monochromeblock(i << 3) ); update(); printw("shapes..."); /* Precalc the shape table */ for (p1=0; p1<16; p1++) { printw("%X", p1); update(); for (p2=0; p2<16; p2++) for (p3=0; p3<16; p3++) for (p4=0; p4<16; p4++) { /* Get average color for matching */ avg = (p1 + p2 + p3 + p4) >> 2; #define mult16(a) (int)((double)(a) * (255.0/15.0)) /* Get the shadechar */ pc_monochrome_shape[p1][p2][p3][p4] = pc_shadetosuper( pc_shapeblock( mult16(p1), mult16(p2), mult16(p3), mult16(p4), mult16(avg) ) ); #undef mult16 } } /* precalc the color table */ printw("\n\n"); textcolor(2); printw("- -- "); textcolor(10); printw("--- "); textcolor(15); printw("calculating RGB lookup table "); textcolor(10); printw("--- "); textcolor(2); printw("-- -"); printw("\n"); for (k=0; k<16; k++) { gotoxy(1,6); for (j=0; j<16; j++) { setcolor(7,0); printw(" "); for (i=0; i<16; i++) { superchar sc; sc = pc_bestrgbmatch(i*17, j*17, k*17); pc_color[i][j][k] = sc; putsc(sc); putsc(sc); } printw("\n"); } update(); #ifdef FADE_TO_BLACK delay(10); #endif } #ifdef FADE_TO_BLACK /* fade to black (yay!) */ for (k=0; k<16; k++) { gotoxy(1,6); for (j=0; j<16; j++) { setcolor(7,0); printw(" "); for (i=0; i<16; i++) { superchar sc; sc = pc_color [limit(i-k,0,15)] [limit(j-k,0,15)] [limit(15-k,0,15)]; putsc(sc); putsc(sc); } printw("\n"); } update(); delay(50); } gotoxy(1,7); normvideo(); #else normvideo(); clrscr(); printw("Done.\n\n"); #endif } /* Precalculate */ void precalc(void) { if (!pc_loadprecalc("precalc.dat")) { pc_doprecalc(); pc_saveprecalc("precalc.dat"); } }