September 23, 2016
For everyone suffering from the lack of monospaced fonts and pixel-based displays in Space Engineers here I share my simple, yet effective solution: A simple helper class in C#, which I wrote yesterday night to solve this particular problem. As a bonus this also enabled using the LCD Screens for arbitrary pixel-precise output, even with animations, as you will see later.
Have fun and feel free to improve it!
The class can be easily embedded in your programmable blocks and allows simulating pixel-precise displays on the LCD and Text Panels. The class also ships with a beautiful set of retro-style compile time mono-spaced fonts.
First admire a few screenshot to get a first impression:
The class exposes the following public methods:
With a timer block this approach can be used for beautiful animations too!
The solution is based on emulating pixels using very small fonts ~0.2 on regular LCD Panels. I use ‘[‘ and ‘.’ here, since they both have the same widths and work quite well to emulate on/off pixels.
Usage of the class is easy and straigth forward from your Main function:
// construct a new Screen class instace providing
// the label of your panel and dimensions.
Screen s = new Screen("Your_LCD_1",215,59,this);
// put text on specified pixel coordinates
s.put_text(10,10," Welcome to the Machine ");
// refresh the LCD Panel
s.update();
And this is the C# class behind it. You will have to copy it into your programmable blocks. (Let me know if there is a more elegant way?)
Sidenote: If you wonder how I generated this numbers inside the letters array, you can read it in this seperate post about bitmap fonts generation.
class Screen
{
public int width;
public int height;
IMyTextPanel panel;
char mark_pixel='[';
char mark_empty='.';
static MyGridProgram programm;
string output;
public Screen(string panel_name, int _width, int _height, MyGridProgram programRef)
{
width=_width;
height=_height;
programm=programRef;
panel=programm.GridTerminalSystem.GetBlockWithName(panel_name) as IMyTextPanel;
output=new string(' ',0);
for(int y=0;y<height;y++)
{
output+=new string(mark_empty,width);
output+='\n';
}
}
public void pixelOn(int x,int y)
{
StringBuilder sb = new StringBuilder(output);
sb[y*(width+1)+x]=mark_pixel;
output = sb.ToString();
}
public void draw_rect(int x, int y, int width, int height)
{
for(int i=x;i<x+width;i++)
{
pixelOn(i,y);
pixelOn(i,y+height-1);
}
for(int i=y;i<y+height;i++)
{
pixelOn(x,i);
pixelOn(width+x-1,i);
}
}
public void put_text(int x, int y, string str)
{
int linefeed=0;
for(int i=0;i<str.Length;i++)
{
if(str[i]=='\n')
{
y+=7;
linefeed=0;
continue;
}
linefeed+=7;
put_letter(x+linefeed,y,str[i]);
}
}
public void put_text_inv(int x, int y, string str)
{
int linefeed=0;
for(int i=0;i<str.Length;i++)
{
if(str[i]=='\n')
{
y+=7;
linefeed=0;
continue;
}
linefeed+=7;
put_letter_inv(x+linefeed,y,str[i]);
}
}
public void put_letter(int x, int y, int ascii_code)
{
int letter=letters[ascii_code-0x20];
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
if(0<(letter & (int)Math.Pow(2,24-(i+j*5)) ))
pixelOn(x+i,y+j);
}
public void put_letter_inv(int x, int y, int ascii_code)
{
int letter=letters[ascii_code-0x20];
draw_rect(x-1,y-1,7,7); // outline
for(int i=0;i<5;i++)
for(int j=0;j<5;j++) if(0>=(letter & (int)Math.Pow(2,24-(i+j*5)) ))
pixelOn(x+i,y+j);
}
public void update()
{
panel.WritePublicText(output, false);
}
// monospace fonts starting from 0x20 (space)
static readonly int[] letters =
{
0,4329476,10813440,11512810,16398526,17895697,6632015,4325376,2232450,8523912,
22483413,4357252,68,31744,4,1118480,15390382,4608142,15239320,31504446,
1841462,33060926,33062463,32540808,33095231,33094719,131200,131208,2236546,
1016800,8521864,32051204,15392270,33095217,32045630,33047071,32032318,
33061407,33062416,33050175,18415153,14815374,14748236,20673235,17318431,18732593,18667121,
15255086,32045584,15259213,32045779,33299071,32641156,18400814,18393412,18405233,18157905,18157700,
32575775,14950670,17043521,14747726,4539392,31,6389760,33095217,32045630,
33047071,32032318,33061407,33062416,33050175,18415153,14815374,14748236,
20673235,17318431,18732593,18667121,15255086,32045584,15259213,32045779,33299071,
32641156,18400814,18393412,18405233,18157905,18157700,32575775,2240642,4329604
8525960,14016,
};
};
static int cnt=0; // programm counter
public void Main(string argument)
{
// increase programm counter
cnt+=1;
if (cnt>100) cnt= 0;
// init our screen class: new 438x59 screen
Screen s = new Screen("Miguel_LCD_1",(int)(438*1.5),(int)(59*1.5),this);
// draw some rectangles
s.draw_rect(0,0,s.width,s.height);
s.draw_rect(2,2,s.width-4,s.height-4);
//draw more rectangles (stress testing only)
// for(int i=4; i<35; i+=2)
// {
// s.draw_rect(i,i,s.width-2*i,s.height-2*i);
// }
// write counter value
s.put_text(8,5,"counter: "+cnt);
// inverted text
s.put_text_inv(8,12,"SOME INVERTED TEXT --- ");
// paint a sinus wave
for(int i=0;i<s.width;i++) { if(i>200)s.pixelOn(i,(int)(s.height/2+s.height/3*Math.Sin((i+cnt)/25.0)));
}
// paint individual letters along a sinus
string txt=new string(' ',0);
txt="! software fools rules the waves!";
for(int i=0;i<txt.Length;i++) { s.put_letter(30+i*7,(int)(s.height/2+s.height/5*Math.Sin((30+i*6+cnt)/25.0)),txt[i]); } s.update(); // other screens Screen s2 = new Screen("Miguel_LCD_2",329,89,this); Screen s3 = new Screen("Miguel_LCD_3",215,59,this); s3.put_text_inv(10,10," Welcome to the Machine "); s3.put_text(10,17,"-= Miguel's 5x5 Fonts =-\n The quick brown fox \n jumps over the lazy dog\n0123456789><=;'~^\n"+
" %#@!~?{}[]-+*&`.,:'");
s3.draw_rect(0,0,s3.width,s3.height);
s3.draw_rect(2,2,s3.width-4,s3.height-4);
s2.put_text(10,1,
"Something......[|||||||||| ] 10%\n"+
"Foo............[||| ] 33%\n"+
"Bar............[||| ] 99%\n"+
"Something else.[||||||||||I ] 05%\n"
);
s2.put_text_inv(10,35,
"Something......[|||||||||| ] 10%\n"+
"Foo............[||| ] 33%\n"+
"Bar............[||| ] 99%\n"+
"Something else.[||||||||||I ] 05%\n"
);
s2.put_text(10,65,
"copyright by softwarefools.com"
);
s2.update();
s3.update();
}
JR Raynal
December 13, 2017 at 9:29 am
Hi, I was playing with your script, and found it super slow… which is odd given the performances of other scripts out there. Turns out by switching the output from a string to a StringBuilder, you don’t need to copy them around as much. I assume this is the fix because I experienced a serious speed increase with that!
Here is the rewritten snippet:
StringBuilder output;
public Screen(string panel_name, int _width, int _height, MyGridProgram programRef)
{
width = _width;
height = _height;
programm = programRef;
panel = programm.GridTerminalSystem.GetBlockWithName(panel_name) as IMyTextPanel;
output = new StringBuilder();
for (int y = 0; y < height; y++)
{
output.Append(mark_empty, width);
output.Append('\n');
}
}
public void pixelOn(int x, int y)
{
output[y * (width + 1) + x] = mark_pixel;
}
cleanup and optimize code!!, move source code to git repo, use some kind of syntax highlihter on this post. allow other font-sets with other sizes and let the user adjust spacing, publish python font converter in another post.