TTY Quake uses AALib, which is from 1997. There's a a demo called "bb" to show off its features, as well as Text::AAlib perl bindings.
I have no idea how many Terminals support full RGB color, but it seems more and more do these days. I was able to double the vertical resolution by printing a Unicode Upper Half Block character and setting both it's foreground and background color.
The escape codes are quite bulky, though. To increase FPS, i remember the last colors used and only send color changes when needed. Nevertheless, screens with lots of color changes result in a much lower FPS that ones with a uniform background and only a few, big colored areas. To make a colored "doublepixel", this is essentially how it looks:
my $halfblock = chr(9600);
utf8::encode($halfblock); # Needs to be UTF8-encoded
...
print "\e[38;2;255;255;255m"; # White foreground
print "\e[48;2;0;0;0m"; # Black background
print $halfblock;
...
In reality, i internally hold an array of pixels (basically, my graphics memory), then render the whole screen into a big string, THEN output the string all at once. This avoids flickering the cursor all over the screen and increases the framerate as well.
sub render($self) {
my $lastfgcolor = '';
my $lastbgcolor = '';
my $out = '' . $self->{home};
my ($r, $g, $b); # Color vars
for(my $y = 0; $y < $self->{rows}; $y+=2) {
for(my $x = 0; $x < $self->{cols}; $x++) {
# Foreground color
($r,$g,$b) = @{$self->{img}->[$x + ($y * $self->{cols})]};
my $newfgcolor = "\e[38;2;" . join(';', $r, $g, $b) . "m";
if($newfgcolor ne $lastfgcolor) {
$lastfgcolor = $newfgcolor;
$out .= $newfgcolor;
}
# Background color
my $lowy = $y + 1;
if($lowy == $self->{rows}) {
# End of image. need a black half-line
($r, $g, $b) = (0, 0, 0);
} else {
($r,$g,$b) = @{$self->{img}->[$x + ($lowy * $self->{co
+ls})]};
}
my $newbgcolor = "\e[48;2;" . join(';', $r, $g, $b) . "m";
if($newbgcolor ne $lastbgcolor) {
$lastbgcolor = $newbgcolor;
$out .= $newbgcolor;
}
$out .= $halfblock;
#print utf8::encode("\N{FULL BLOCK}");
}
($r, $g, $b) = (0, 0, 0);
$lastfgcolor = "\e[38;2;" . join(';', 255, 255, 255) . "m";
$lastbgcolor = "\e[48;2;" . join(';', 0, 0, 0) . "m";
$out .= $lastfgcolor . $lastbgcolor . "\n";
}
my $now = time;
my $fps = 0;
if($self->{lastrender}) {
$fps = (int((1 / ($now - $self->{lastrender})) * 100)/100);
}
$self->{lastrender} = $now;
# Status line
my ($pre, $post) = split/\./o, $fps;
if(!defined($post)) {
$post = '00';
}
while(length($pre) < 4) {
$pre = ' ' . $pre;
}
while(length($post) < 2) {
$post .= '0';
}
$fps = 'FPS: ' . $pre . '.' . $post;
$fps .= ' ' x (18 - length($fps));
$out .= $fps;
$out .= $self->{statustext};
if($self->{info} ne '') {
$out .= $self->{info};
$out .= ' ' x (220 - length($self->{info}));
} elsif($self->{progress} == -1) {
$out .= ' ' x 220;
} elsif($self->{info} ne '') {
$out .= $self->{info};
$out .= ' ' x (220 - length($self->{info}));
} else {
$out .= $fullblock;
my $full = int(218 * $self->{progress});
$out .= $darkshade x $full;
$out .= $lightshade x (218-$full);
$out .= $fullblock;
}
print $out;
my $key = ReadKey(-1);
if(defined($key)) {
$key = ord($key);
#croak("\n\n\n ** $key **\n");
if($key == 32) {
return 1;
} elsif($key == 27 || $key == 113) {
return -1;
}
}
return 0;
}
My code is far from optimized, because i keep tinkering with it and optimization would make that much harder. If i reorganize how the data is layed out in memory, i could (potentially) rewrite the output function in C for better performance. But i'm currently not sure if one of the bottlenecks isn't simply the STDOUT output pipe to the Terminal. But potentially, with optimized code on a modern computer (mine is 12 years old), you could get 100s of frames per second. (Edit: Flattened image structure, pushed to mercurial. Still have to look into doing something with Inline::C)
There's also the possibility to go pure black&white, but increase the resolution to 4 pixels per character with the clever use of a combination of Unicode block characters. This would work quite well for line graphics and pencil sketches.
Another possible optimization would be to basically diff the current and previous image and only update the areas that have changed by jumping the cursor directly to that area. And example where that would be quite effective is the scroller in my demo. Frame-by-Frame, only about roughly 30% on average (probably less) of the screen lines change, and even those might have long stretches of unchanged characters. Basically, what you would do is to paint an interframe instead of a keyframe, to borrow from mpeg terminology. (Edit: Implemented the line-by-line buffering and pushed it to mercurial. It gives a somewhat higher framerate when not much is changing on screen. The "partial rows" update doesn't seem worth the hassle at the moment)
Additional links:
|