Hello, world!++

ezgif-7-c100a670976f

Alright, this post has been a long time coming and as I keep getting hung up on going into how I control the text over WebSockets I’ve decided to just brain dump how I’m handling text parsing in ol’ stringless PBJS.

Credit for the font itself and character decoding goes to this hackaday.io post by PK (not that PK)

This code represents the 4th, maybe 5th attempt as I was unable to find a good way to render the text that was outside the color rendering, resulting in a display that wasn’t very dynamic.

In this iteration the text rendering is reduced to a single function call that takes an [X,Y] and returns true or false if the pixel should be lit. Two variables startx, starty represent where the text should start being drawn. The code has a bit of cruft left over in it (brain dump!) but should be pretty easy to follow and I’d be happy to answer any questions. Beyond the obvious cleanup I think there are some neat simple features to add (vertical next) and I’ll hopefully get to the next part of controlling the text over websockets soon. Once that is done I’ll start my relentless campaign of bugging @wizard until we get library includes, then some really cool things could be possible.

w = 30;

var alpha_len = 192;
var alpha = array(alpha_len);
alpha[0] = 0x00;
alpha[1] = 0x00;   /*SPACE*/
alpha[2] = 0x49;
alpha[3] = 0x08;   /*'!'*/
alpha[4] = 0xb4;
alpha[5] = 0x00;   /*'"'*/
alpha[6] = 0xbe;
alpha[7] = 0xf6;   /*'#'*/
alpha[8] = 0x7b;
alpha[9] = 0x7a;   /*'$'*/
alpha[10] = 0xa5;
alpha[11] = 0x94;   /*'%'*/
alpha[12] = 0x55;
alpha[13] = 0xb8;   /*'&'*/
alpha[14] = 0x48;
alpha[15] = 0x00;   /*''*/
alpha[16] = 0x29;
alpha[17] = 0x44;   /*'('*/
alpha[18] = 0x44;
alpha[19] = 0x2a;   /*')'*/
alpha[20] = 0x15;
alpha[21] = 0xa0;   /*'*'*/
alpha[22] = 0x0b;
alpha[23] = 0x42;   /*'+'*/
alpha[24] = 0x00;
alpha[25] = 0x51;   /*','*/
alpha[26] = 0x03;
alpha[27] = 0x02;   /*'-'*/
alpha[28] = 0x00;
alpha[29] = 0x08;   /*'.'*/
alpha[30] = 0x25;
alpha[31] = 0x90;   /*'/'*/
alpha[32] = 0x76;
alpha[33] = 0xba;   /*'0'*/
alpha[34] = 0x59;
alpha[35] = 0x5c;   /*'1'*/
alpha[36] = 0xc5;
alpha[37] = 0x9e;   /*'2'*/
alpha[38] = 0xc5;
alpha[39] = 0x38;   /*'3'*/
alpha[40] = 0x92;
alpha[41] = 0xe6;   /*'4'*/
alpha[42] = 0xf3;
alpha[43] = 0x3a;   /*'5'*/
alpha[44] = 0x73;
alpha[45] = 0xba;   /*'6'*/
alpha[46] = 0xe5;
alpha[47] = 0x90;   /*'7'*/
alpha[48] = 0x77;
alpha[49] = 0xba;   /*'8'*/
alpha[50] = 0x77;
alpha[51] = 0x3a;   /*'9'*/
alpha[52] = 0x08;
alpha[53] = 0x40;   /*':'*/
alpha[54] = 0x08;
alpha[55] = 0x50;   /*';'*/
alpha[56] = 0x2a;
alpha[57] = 0x44;   /*'<'*/
alpha[58] = 0x1c;
alpha[59] = 0xe0;   /*'='*/
alpha[60] = 0x88;
alpha[61] = 0x52;   /*'>'*/
alpha[62] = 0xe5;
alpha[63] = 0x08;   /*'?'*/
alpha[64] = 0x56;
alpha[65] = 0x8e;   /*'@'*/
alpha[66] = 0x77;
alpha[67] = 0xb6;   /*'A'*/
alpha[68] = 0x77;
alpha[69] = 0xb8;   /*'B'*/
alpha[70] = 0x72;
alpha[71] = 0x8c;   /*'C'*/
alpha[72] = 0xd6;
alpha[73] = 0xba;   /*'D'*/
alpha[74] = 0x73;
alpha[75] = 0x9e;   /*'E'*/
alpha[76] = 0x73;
alpha[77] = 0x92;   /*'F'*/
alpha[78] = 0x72;
alpha[79] = 0xae;   /*'G'*/
alpha[80] = 0xb7;
alpha[81] = 0xb6;   /*'H'*/
alpha[82] = 0xe9;
alpha[83] = 0x5c;   /*'I'*/
alpha[84] = 0x64;
alpha[85] = 0xaa;   /*'J'*/
alpha[86] = 0xb7;
alpha[87] = 0xb4;   /*'K'*/
alpha[88] = 0x92;
alpha[89] = 0x9c;   /*'L'*/
alpha[90] = 0xbe;
alpha[91] = 0xb6;   /*'M'*/
alpha[92] = 0xd6;
alpha[93] = 0xb6;   /*'N'*/
alpha[94] = 0x56;
alpha[95] = 0xaa;   /*'O'*/
alpha[96] = 0xd7;
alpha[97] = 0x92;   /*'P'*/
alpha[98] = 0x76;
alpha[99] = 0xee;   /*'Q'*/
alpha[100] = 0x77;
alpha[101] = 0xb4;   /*'R'*/
alpha[102] = 0x71;
alpha[103] = 0x38;   /*'S'*/
alpha[104] = 0xe9;
alpha[105] = 0x48;   /*'T'*/
alpha[106] = 0xb6;
alpha[107] = 0xae;   /*'U'*/
alpha[108] = 0xb6;
alpha[109] = 0xaa;   /*'V'*/
alpha[110] = 0xb6;
alpha[111] = 0xf6;   /*'W'*/
alpha[112] = 0xb5;
alpha[113] = 0xb4;   /*'X'*/
alpha[114] = 0xb5;
alpha[115] = 0x48;   /*'Y'*/
alpha[116] = 0xe5;
alpha[117] = 0x9c;   /*'Z'*/
alpha[118] = 0x69;
alpha[119] = 0x4c;   /*'['*/
alpha[120] = 0x91;
alpha[121] = 0x24;   /*'\'*/
alpha[122] = 0x64;
alpha[123] = 0x2e;   /*']'*/
alpha[124] = 0x54;
alpha[125] = 0x00;   /*'^'*/
alpha[126] = 0x00;
alpha[127] = 0x1c;   /*'_'*/
alpha[128] = 0x44;
alpha[129] = 0x00;   /*'backtick'*/
alpha[130] = 0x0e;
alpha[131] = 0xae;   /*'a'*/
alpha[132] = 0x9a;
alpha[133] = 0xba;   /*'b'*/
alpha[134] = 0x0e;
alpha[135] = 0x8c;   /*'c'*/
alpha[136] = 0x2e;
alpha[137] = 0xae;   /*'d'*/
alpha[138] = 0x0e;
alpha[139] = 0xce;   /*'e'*/
alpha[140] = 0x56;
alpha[141] = 0xd0;   /*'f'*/
alpha[142] = 0x55;
alpha[143] = 0x3B;   /*'g'*/
alpha[144] = 0x93;
alpha[145] = 0xb4;   /*'h'*/
alpha[146] = 0x41;
alpha[147] = 0x44;   /*'i'*/
alpha[148] = 0x41;
alpha[149] = 0x51;   /*'j'*/
alpha[150] = 0x97;
alpha[151] = 0xb4;   /*'k'*/
alpha[152] = 0x49;
alpha[153] = 0x44;   /*'l'*/
alpha[154] = 0x17;
alpha[155] = 0xb6;   /*'m'*/
alpha[156] = 0x1a;
alpha[157] = 0xb6;   /*'n'*/
alpha[158] = 0x0a;
alpha[159] = 0xaa;   /*'o'*/
alpha[160] = 0xd6;
alpha[161] = 0xd3;   /*'p'*/
alpha[162] = 0x76;
alpha[163] = 0x67;   /*'q'*/
alpha[164] = 0x17;
alpha[165] = 0x90;   /*'r'*/
alpha[166] = 0x0f;
alpha[167] = 0x38;   /*'s'*/
alpha[168] = 0x9a;
alpha[169] = 0x8c;   /*'t'*/
alpha[170] = 0x16;
alpha[171] = 0xae;   /*'u'*/
alpha[172] = 0x16;
alpha[173] = 0xba;   /*'v'*/
alpha[174] = 0x16;
alpha[175] = 0xf6;   /*'w'*/
alpha[176] = 0x15;
alpha[177] = 0xb4;   /*'x'*/
alpha[178] = 0xb5;
alpha[179] = 0x2b;   /*'y'*/
alpha[180] = 0x1c;
alpha[181] = 0x5e;   /*'z'*/
alpha[182] = 0x6b;
alpha[183] = 0x4c;   /*'{'*/
alpha[184] = 0x49;
alpha[185] = 0x48;   /*'|'*/
alpha[186] = 0xc9;
alpha[187] = 0x5a;   /*'}'*/
alpha[188] = 0x54;
alpha[189] = 0x00;   /*'~'*/
alpha[190] = 0x56;
alpha[191] = 0xe2;    /*''*/

export var strlen = 13;
export var helloworld = array(300)
helloworld[0] = 80
helloworld[1] = 74
helloworld[2] = 88
helloworld[3] = 88
helloworld[4] = 94
helloworld[5] = 24
helloworld[6] = 0
helloworld[7] = 110
helloworld[8] = 94
helloworld[9] = 100
helloworld[10] = 88
helloworld[11] = 72
helloworld[12] = 2

function indexOf(arr, val) {
  for(var i = 0; i < alpha_len; i++)
    if(arr[i] == val) return i;
    
  return -1;
}

// used to hold current charecter being rendered
var chararr = array(2)
function getcharbytes(c) {
  // grab the two bytes that represent this char
  chararr[0] = alpha[c];
  chararr[1] = alpha[c + 1];
  
  return chararr;
}

export var color = 1;

export var startx = 1;
export var starty = 2;

var pWidth = 3;
var cWidth = pWidth + 1;

export var byte;
export var cIdx;
export var c;
export var line;
export var cPos;
var t1;
var z;

var timeout = 0;
export var scrolldir = 0;

export function beforeRender(t) {
  timeout += t;
  t1 = time(.05)*PI2
  z = 1+ wave(time(.2))*5

  // advance the text every 100ms
  // startx +/- 1 depending on present direction
  if(timeout > 100) {
    if(!scrolldir)
      startx -= 1;
    else
      startx += 1;
    timeout = 0;
  }
  
  // moving <<<<<<<
  if(scrolldir && startx == 3) {
    scrolldir = !scrolldir;
  } else if(!scrolldir && abs(startx) >= (strlen + 9)) {
    scrolldir = !scrolldir;
  } else {
    
  }
}

function getPixelState(x, y) {
  // TODO check upper bounds of X
  if(x < startx || y < starty) return 0;
  
  cIdx = floor((x - startx) / cWidth);
  
  if(cIdx >= strlen) return 0;
  
  c = getcharbytes(helloworld[cIdx]);
  var py = y - starty;
  var cx = x + 1 - startx;
  
  if(c[1] & 1 == 1) py -= 1;
  
  // fetch the line for this pixel
  if(py == 0)
    byte = c[0] >> 4;
  else if(py == 1)
    byte = c[0] >> 1;
  else if(py == 2)
    byte = (c[0] & 0x03) << 2 | (c[1] & 0x02);
  else if(py == 3)
    byte = c[1] >> 4;
  else if(py == 4)
    byte = c[1] >> 1;
  else return 0;
  
  line = byte & 0x0e;
  
  // return if bit is set
  return line & 1 << (4 - cx % 4);
}

export function render(index) {
  y = floor(index / w)
  x = index % w
  x = (y % 2 == 1 ? x : w-1-x)
  
  if(getPixelState(x,y)) {
    hsv(sin(x/w*z + t1),1,1)
  } else {
    hsv(0,0,0);
  }
}
2 Likes

it should be noted, the terrible GIF quality is video/conversion artifacts and it’s crisp in person.

1 Like

Excellent. Just damn excellent. Thank you for doing this.

Ironically I’d looked at that same font but hadn’t found that hackaday code, and would have reinvented their wheel, as I wanted it really tiny and compact like this.

1 Like

Sweeeeet!

OK I had a bit of fun with this and the new APIs…

w = 8; //scale to this many font-pixels wide 
h = 8; //and high

//if you don't have a 2D pixel map and your matrix alternates direction,
//then set this to true
var zigZagMatrix = true

var alpha = [
	0x00, 0x00,   /*SPACE*/
	0x49, 0x08,   /*'!'*/
	0xb4, 0x00,   /*'"'*/
	0xbe, 0xf6,   /*'#'*/
	0x7b, 0x7a,   /*'$'*/
	0xa5, 0x94,   /*'%'*/
	0x55, 0xb8,   /*'&'*/
	0x48, 0x00,   /*''*/
	0x29, 0x44,   /*'('*/
	0x44, 0x2a,   /*')'*/
	0x15, 0xa0,   /*'*'*/
	0x0b, 0x42,   /*'+'*/
	0x00, 0x51,   /*','*/
	0x03, 0x02,   /*'-'*/
	0x00, 0x08,   /*'.'*/
	0x25, 0x90,   /*'/'*/
	0x76, 0xba,   /*'0'*/
	0x59, 0x5c,   /*'1'*/
	0xc5, 0x9e,   /*'2'*/
	0xc5, 0x38,   /*'3'*/
	0x92, 0xe6,   /*'4'*/
	0xf3, 0x3a,   /*'5'*/
	0x73, 0xba,   /*'6'*/
	0xe5, 0x90,   /*'7'*/
	0x77, 0xba,   /*'8'*/
	0x77, 0x3a,   /*'9'*/
	0x08, 0x40,   /*':'*/
	0x08, 0x50,   /*';'*/
	0x2a, 0x44,   /*'<'*/
	0x1c, 0xe0,   /*'='*/
	0x88, 0x52,   /*'>'*/
	0xe5, 0x08,   /*'?'*/
	0x56, 0x8e,   /*'@'*/
	0x77, 0xb6,   /*'A'*/
	0x77, 0xb8,   /*'B'*/
	0x72, 0x8c,   /*'C'*/
	0xd6, 0xba,   /*'D'*/
	0x73, 0x9e,   /*'E'*/
	0x73, 0x92,   /*'F'*/
	0x72, 0xae,   /*'G'*/
	0xb7, 0xb6,   /*'H'*/
	0xe9, 0x5c,   /*'I'*/
	0x64, 0xaa,   /*'J'*/
	0xb7, 0xb4,   /*'K'*/
	0x92, 0x9c,   /*'L'*/
	0xbe, 0xb6,   /*'M'*/
	0xd6, 0xb6,   /*'N'*/
	0x56, 0xaa,   /*'O'*/
	0xd7, 0x92,   /*'P'*/
	0x76, 0xee,   /*'Q'*/
	0x77, 0xb4,   /*'R'*/
	0x71, 0x38,   /*'S'*/
	0xe9, 0x48,   /*'T'*/
	0xb6, 0xae,   /*'U'*/
	0xb6, 0xaa,   /*'V'*/
	0xb6, 0xf6,   /*'W'*/
	0xb5, 0xb4,   /*'X'*/
	0xb5, 0x48,   /*'Y'*/
	0xe5, 0x9c,   /*'Z'*/
	0x69, 0x4c,   /*'['*/
	0x91, 0x24,   /*'\'*/
	0x64, 0x2e,   /*']'*/
	0x54, 0x00,   /*'^'*/
	0x00, 0x1c,   /*'_'*/
	0x44, 0x00,   /*'backtick'*/
	0x0e, 0xae,   /*'a'*/
	0x9a, 0xba,   /*'b'*/
	0x0e, 0x8c,   /*'c'*/
	0x2e, 0xae,   /*'d'*/
	0x0e, 0xce,   /*'e'*/
	0x56, 0xd0,   /*'f'*/
	0x55, 0x3B,   /*'g'*/
	0x93, 0xb4,   /*'h'*/
	0x41, 0x44,   /*'i'*/
	0x41, 0x51,   /*'j'*/
	0x97, 0xb4,   /*'k'*/
	0x49, 0x44,   /*'l'*/
	0x17, 0xb6,   /*'m'*/
	0x1a, 0xb6,   /*'n'*/
	0x0a, 0xaa,   /*'o'*/
	0xd6, 0xd3,   /*'p'*/
	0x76, 0x67,   /*'q'*/
	0x17, 0x90,   /*'r'*/
	0x0f, 0x38,   /*'s'*/
	0x9a, 0x8c,   /*'t'*/
	0x16, 0xae,   /*'u'*/
	0x16, 0xba,   /*'v'*/
	0x16, 0xf6,   /*'w'*/
	0x15, 0xb4,   /*'x'*/
	0xb5, 0x2b,   /*'y'*/
	0x1c, 0x5e,   /*'z'*/
	0x6b, 0x4c,   /*'{'*/
	0x49, 0x48,   /*'|'*/
	0xc9, 0x5a,   /*'}'*/
	0x54, 0x00,   /*'~'*/
	0x56, 0xe2,    /*''*/
]
var alpha_len = alpha.length

export var strlen = 13;
export var helloworld = array(300)
helloworld[0] = 80
helloworld[1] = 74
helloworld[2] = 88
helloworld[3] = 88
helloworld[4] = 94
helloworld[5] = 24
helloworld[6] = 0
helloworld[7] = 110
helloworld[8] = 94
helloworld[9] = 100
helloworld[10] = 88
helloworld[11] = 72
helloworld[12] = 2

function indexOf(arr, val) {
  for(var i = 0; i < alpha_len; i++)
    if(arr[i] == val) return i;
    
  return -1;
}

// used to hold current charecter being rendered
var chararr = array(2)
function getcharbytes(c) {
  // grab the two bytes that represent this char
  chararr[0] = alpha[c];
  chararr[1] = alpha[c + 1];
  
  return chararr;
}

export var color = 1;

export var startx = 1;
export var starty = 2;

var pWidth = 3;
var cWidth = pWidth + 1;

export var byte;
export var cIdx;
export var c;
export var line;
export var cPos;
var t1;
var z;

var timeout = 0;
export var scrolldir = 0;

export function beforeRender(t) {
  timeout += t;
  t1 = time(.05)*PI2
  z = 1+ wave(time(.2))*5

  // advance the text every 100ms
  // startx +/- 1 depending on present direction
  if(timeout > 100) {
    if(!scrolldir)
      startx -= 1;
    else
      startx += 1;
    timeout = 0;
  }
  
  // moving <<<<<<<
  if(scrolldir && startx == 3) {
    scrolldir = !scrolldir;
  } else if(!scrolldir && abs(startx) >= (strlen + 9)) {
    scrolldir = !scrolldir;
  } else {
    
  }
  
  startx = 0 //hack to turn off the pixel level movement since we will use transform api
  resetTransform()
  translate(-.5, -.5) //center coordinate system
  rotate(sin(time(.045) * PI2) * .1) //silly cartoon rotation
  sx = 0.5 + wave(time(.02))
  sy = 0.5 + wave(time(.03))
  scale(sx, sy) //wobbly stretchy scaling
  //zoom back and forth
  translate(wave(time(.13))*strlen/2 * 8 / w, .5)
  
}

function getPixelState(x, y) {
  // TODO check upper bounds of X
  if(x < startx || y < starty) return 0;
  
  cIdx = floor((x - startx) / cWidth);
  
  if(cIdx >= strlen) return 0;
  
  c = getcharbytes(helloworld[cIdx]);
  var py = y - starty;
  var cx = x + 1 - startx;
  
  if(c[1] & 1 == 1) py -= 1;
  
  // fetch the line for this pixel
  if(py == 0)
    byte = c[0] >> 4;
  else if(py == 1)
    byte = c[0] >> 1;
  else if(py == 2)
    byte = (c[0] & 0x03) << 2 | (c[1] & 0x02);
  else if(py == 3)
    byte = c[1] >> 4;
  else if(py == 4)
    byte = c[1] >> 1;
  else return 0;
  
  line = byte & 0x0e;
  
  // return if bit is set
  return line & 1 << (4 - cx % 4);
}

export function render(index) {
  y = floor(index / w)
  x = index % w
  x = (y % 2 == 1 ? x : w-1-x)
  
  if(getPixelState(x,y)) {
    hsv(sin(x/w*z + t1),1,1)
  } else {
    hsv(0,0,0);
  }
}

export function render2D(index, x, y) {
  //scale the pixel map as if it was w*h pixels
  y = floor(y * h)
  x = floor(x * w)

  if(getPixelState(x,y)) {
    hsv(sin(x/w*z + t1),1,1)
  } else {
    hsv(0,0,0);
  }
}

Oh wow!!! Like the pumpkin in the Orange colors post, this gives me such a Super Mario Browsers castle vibes I LOVE IT!

1 Like