feat(core): add ascii and ppm frame renderers

This commit is contained in:
dualfroz committed 2026-08-23 14:45:48 +02:00
1 parent bd40840d67
commit c5280986f6
2 files changed
+158

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
using System.Text;
namespace Chip8.Core;
public static class AsciiScreen
{
public static string render(bool[] display, char on = '#', char off = ' ')
{
var builder = new StringBuilder((Machine.displayWidth + 1) * Machine.displayHeight);
for (int row = 0; row < Machine.displayHeight; row++)
{
for (int col = 0; col < Machine.displayWidth; col++)
{
builder.Append(display[row * Machine.displayWidth + col] ? on : off);
}
builder.Append('\n');
}
return builder.ToString();
}
public static string renderFramed(bool[] display, char on = '#', char off = ' ')
{
var builder = new StringBuilder();
string border = new string('-', Machine.displayWidth);
builder.Append('+').Append(border).Append("+\n");
for (int row = 0; row < Machine.displayHeight; row++)
{
builder.Append('|');
for (int col = 0; col < Machine.displayWidth; col++)
{
builder.Append(display[row * Machine.displayWidth + col] ? on : off);
}
builder.Append("|\n");
}
builder.Append('+').Append(border).Append("+\n");
return builder.ToString();
}
public static int litPixelCount(bool[] display)
{
int lit = 0;
foreach (bool pixel in display)
{
if (pixel)
{
lit++;
}
}
return lit;
}
public static string render(Video video, string palette = " #*@")
{
var builder = new StringBuilder((video.width + 1) * video.height);
for (int row = 0; row < video.height; row++)
{
for (int col = 0; col < video.width; col++)
{
builder.Append(palette[video.colorAt(col, row)]);
}
builder.Append('\n');
}
return builder.ToString();
}
public static int litPixelCount(Video video)
{
int lit = 0;
for (int row = 0; row < video.height; row++)
{
for (int col = 0; col < video.width; col++)
{
if (video.colorAt(col, row) != 0)
{
lit++;
}
}
}
return lit;
}
}
+71
View File
@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Text;
namespace Chip8.Core.Export;
public static class PpmImage
{
public static byte[] fromMonochrome(bool[] pixels, int width, int height, Palette palette, int scale = 1)
{
if (pixels.Length < width * height)
{
throw new ArgumentException("pixel buffer is smaller than " + width + "x" + height, nameof(pixels));
}
return build(width, height, scale, palette, (x, y) => pixels[y * width + x] ? 1 : 0);
}
public static byte[] fromVideo(Video video, Palette palette, int scale = 1) =>
build(video.width, video.height, scale, palette, video.colorAt);
public static byte[] fromMachine(Machine machine, Palette palette, int scale = 1)
{
if (machine.platform == Platform.Chip8)
{
return fromMonochrome(machine.display, Machine.displayWidth, Machine.displayHeight, palette, scale);
}
return fromVideo(machine.video, palette, scale);
}
public static void writeFile(string path, byte[] ppm) => File.WriteAllBytes(path, ppm);
static byte[] build(int width, int height, int scale, Palette palette, Func<int, int, int> colorAt)
{
if (scale < 1)
{
throw new ArgumentOutOfRangeException(nameof(scale), "scale must be at least 1");
}
int outWidth = width * scale;
int outHeight = height * scale;
byte[] header = Encoding.ASCII.GetBytes("P6\n" + outWidth + " " + outHeight + "\n255\n");
var output = new byte[header.Length + outWidth * outHeight * 3];
Array.Copy(header, output, header.Length);
int cursor = header.Length;
for (int y = 0; y < height; y++)
{
int rowStart = cursor;
for (int x = 0; x < width; x++)
{
Rgb color = palette.resolve(colorAt(x, y));
for (int sx = 0; sx < scale; sx++)
{
output[cursor++] = color.r;
output[cursor++] = color.g;
output[cursor++] = color.b;
}
}
int rowBytes = outWidth * 3;
for (int sy = 1; sy < scale; sy++)
{
Array.Copy(output, rowStart, output, cursor, rowBytes);
cursor += rowBytes;
}
}
return output;
}
}