Files
emulatedisplay/low_level_render.cpp
2026-01-13 21:47:03 -05:00

559 lines
16 KiB
C++

#include "low_level_render.h"
#include <cmath>
#include <algorithm>
#include <vector>
#include "./fonts/5x5_font.h"
#include "./fonts/7linedigital_font.h"
#include "./fonts/BMplain_font.h"
#include "./fonts/Blokus_font.h"
#include "./fonts/HISKYF21_font.h"
#include "./fonts/Minimum_font.h"
#include "./fonts/SUPERDIG_font.h"
#include "./fonts/acme_5_outlines_font.h"
#include "./fonts/aztech_font.h"
#include "./fonts/crackers_font.h"
#include "./fonts/haiku_font.h"
#include "./fonts/sloth_font.h"
#include "./fonts/zxpix_font.h"
LowLevelRenderer::LowLevelRenderer(uint8_t* buffer, int width, int height)
: bit_buffer(buffer), V_WIDTH(width), V_HEIGHT(height), current_font(&font_acme_5_outlines),
clipping_enabled(false), clip_x(0), clip_y(0), clip_width(width), clip_height(height) {}
void LowLevelRenderer::set_font(const unsigned char (*font)[96][6]) {
current_font = font;
}
// Clipping functions
void LowLevelRenderer::set_clip_rect(int x, int y, int width, int height) {
clip_x = x;
clip_y = y;
clip_width = width;
clip_height = height;
clipping_enabled = true;
}
void LowLevelRenderer::reset_clip_rect() {
clipping_enabled = false;
clip_x = 0;
clip_y = 0;
clip_width = V_WIDTH;
clip_height = V_HEIGHT;
}
bool LowLevelRenderer::is_clipping_enabled() const {
return clipping_enabled;
}
bool LowLevelRenderer::is_point_in_clip_rect(int x, int y) {
if (!clipping_enabled) return true;
return (x >= clip_x && x < clip_x + clip_width &&
y >= clip_y && y < clip_y + clip_height);
}
// Buffer operations
void LowLevelRenderer::invert_buffer() {
int buffer_size = (V_WIDTH * V_HEIGHT + 7) / 8; // Round up for bit buffer size
for (int i = 0; i < buffer_size; ++i) {
bit_buffer[i] = ~bit_buffer[i]; // Bitwise NOT to invert all bits
}
}
void LowLevelRenderer::clear_buffer() {
int buffer_size = (V_WIDTH * V_HEIGHT + 7) / 8;
for (int i = 0; i < buffer_size; ++i) {
bit_buffer[i] = 0;
}
}
void LowLevelRenderer::set_pixel(int x, int y, bool on)
{
if (x < 0 || x >= V_WIDTH || y < 0 || y >= V_HEIGHT)
return;
// Check clipping
if (!is_point_in_clip_rect(x, y))
return;
int bit_pos = y * V_WIDTH + x;
if (on)
bit_buffer[bit_pos / 8] |= (1 << (7 - (bit_pos % 8)));
else
bit_buffer[bit_pos / 8] &= ~(1 << (7 - (bit_pos % 8)));
}
void LowLevelRenderer::draw_line(int x0, int y0, int x1, int y1, bool on)
{
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2;
while (true)
{
set_pixel(x0, y0, on);
if (x0 == x1 && y0 == y1)
break;
e2 = 2 * err;
if (e2 >= dy)
{
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
}
}
void LowLevelRenderer::draw_rectangle(int x, int y, int width, int height, bool on)
{
// Draw top line
draw_line(x, y, x + width - 1, y, on);
// Draw bottom line
draw_line(x, y + height - 1, x + width - 1, y + height - 1, on);
// Draw left line
draw_line(x, y, x, y + height - 1, on);
// Draw right line
draw_line(x + width - 1, y, x + width - 1, y + height - 1, on);
}
void LowLevelRenderer::draw_filled_rectangle(int x, int y, int width, int height, bool on)
{
for (int i = 0; i < height; i++)
{
draw_line(x, y + i, x + width - 1, y + i, on);
}
}
void LowLevelRenderer::draw_rounded_rectangle(int x, int y, int width, int height, int radius, bool on)
{
// Ensure radius doesn't exceed half the smaller dimension
int max_radius = std::min(width, height) / 2;
if (radius > max_radius) radius = max_radius;
if (radius < 0) radius = 0;
// Draw straight parts
// Top line (from x+radius to x+width-radius-1)
if (width > 2 * radius)
draw_line(x + radius, y, x + width - radius - 1, y, on);
// Bottom line
if (width > 2 * radius)
draw_line(x + radius, y + height - 1, x + width - radius - 1, y + height - 1, on);
// Left line (from y+radius to y+height-radius-1)
if (height > 2 * radius)
draw_line(x, y + radius, x, y + height - radius - 1, on);
// Right line
if (height > 2 * radius)
draw_line(x + width - 1, y + radius, x + width - 1, y + height - radius - 1, on);
// Draw corner arcs
if (radius > 0)
{
// Top-left corner
draw_corner_arc(x + radius, y + radius, radius, 2, on); // quadrant 2
// Top-right corner
draw_corner_arc(x + width - radius - 1, y + radius, radius, 1, on); // quadrant 1
// Bottom-left corner
draw_corner_arc(x + radius, y + height - radius - 1, radius, 3, on); // quadrant 3
// Bottom-right corner
draw_corner_arc(x + width - radius - 1, y + height - radius - 1, radius, 0, on); // quadrant 0
}
}
void LowLevelRenderer::draw_corner_arc(int center_x, int center_y, int radius, int quadrant, bool on)
{
int x = radius;
int y = 0;
int err = 0;
while (x >= y)
{
// Depending on quadrant, set pixels in the appropriate octants
switch (quadrant)
{
case 0: // Bottom-right
set_pixel(center_x + x, center_y + y, on);
set_pixel(center_x + y, center_y + x, on);
break;
case 1: // Top-right
set_pixel(center_x + x, center_y - y, on);
set_pixel(center_x + y, center_y - x, on);
break;
case 2: // Top-left
set_pixel(center_x - x, center_y - y, on);
set_pixel(center_x - y, center_y - x, on);
break;
case 3: // Bottom-left
set_pixel(center_x - x, center_y + y, on);
set_pixel(center_x - y, center_y + x, on);
break;
}
if (err <= 0)
{
y += 1;
err += 2 * y + 1;
}
if (err > 0)
{
x -= 1;
err -= 2 * x + 1;
}
}
}
void LowLevelRenderer::draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
draw_line(x1, y1, x2, y2, on);
draw_line(x2, y2, x3, y3, on);
draw_line(x3, y3, x1, y1, on);
}
void LowLevelRenderer::draw_filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
// Sort points by y-coordinate
if (y1 > y2) { std::swap(x1, x2); std::swap(y1, y2); }
if (y1 > y3) { std::swap(x1, x3); std::swap(y1, y3); }
if (y2 > y3) { std::swap(x2, x3); std::swap(y2, y3); }
// Flat bottom triangle
if (y2 == y3) {
fill_bottom_flat_triangle(x1, y1, x2, y2, x3, y3, on);
}
// Flat top triangle
else if (y1 == y2) {
fill_top_flat_triangle(x1, y1, x2, y2, x3, y3, on);
}
// General triangle - split into flat bottom and flat top
else {
int x4 = x1 + ((y2 - y1) * (x3 - x1)) / (y3 - y1);
int y4 = y2;
fill_bottom_flat_triangle(x1, y1, x2, y2, x4, y4, on);
fill_top_flat_triangle(x2, y2, x4, y4, x3, y3, on);
}
}
void LowLevelRenderer::fill_bottom_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
float invslope1 = (float)(x2 - x1) / (y2 - y1);
float invslope2 = (float)(x3 - x1) / (y3 - y1);
float curx1 = x1;
float curx2 = x1;
for (int scanlineY = y1; scanlineY <= y2; scanlineY++) {
draw_line((int)curx1, scanlineY, (int)curx2, scanlineY, on);
curx1 += invslope1;
curx2 += invslope2;
}
}
void LowLevelRenderer::fill_top_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
float invslope1 = (float)(x3 - x1) / (y3 - y1);
float invslope2 = (float)(x3 - x2) / (y3 - y2);
float curx1 = x3;
float curx2 = x3;
for (int scanlineY = y3; scanlineY > y1; scanlineY--) {
draw_line((int)curx1, scanlineY, (int)curx2, scanlineY, on);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
void LowLevelRenderer::draw_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on)
{
int x = 0;
int y = radius_y;
// Decision parameter for region 1
long long a2 = radius_x * radius_x;
long long b2 = radius_y * radius_y;
long long fa2 = 4 * a2, fb2 = 4 * b2;
long long sigma = 2 * b2 + a2 * (1 - 2 * radius_y);
// Region 1
while (b2 * x <= a2 * y) {
set_pixel(center_x + x, center_y + y, on);
set_pixel(center_x - x, center_y + y, on);
set_pixel(center_x + x, center_y - y, on);
set_pixel(center_x - x, center_y - y, on);
if (sigma >= 0) {
sigma += fa2 * (1 - y);
y--;
}
sigma += b2 * ((4 * x) + 6);
x++;
}
// Region 2
x = radius_x;
y = 0;
sigma = 2 * a2 + b2 * (1 - 2 * radius_x);
while (a2 * y <= b2 * x) {
set_pixel(center_x + x, center_y + y, on);
set_pixel(center_x - x, center_y + y, on);
set_pixel(center_x + x, center_y - y, on);
set_pixel(center_x - x, center_y - y, on);
if (sigma >= 0) {
sigma += fb2 * (1 - x);
x--;
}
sigma += a2 * ((4 * y) + 6);
y++;
}
}
void LowLevelRenderer::draw_filled_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on)
{
int hh = radius_y * radius_y;
int ww = radius_x * radius_x;
int hhww = hh * ww;
int x0 = radius_x;
int dx = 0;
// Do the horizontal diameter
draw_line(center_x - radius_x, center_y, center_x + radius_x, center_y, on);
// Now do both halves at the same time, away from the diameter
for (int y = 1; y <= radius_y; y++) {
int x1 = x0 - (dx - 1); // Try slopes of dx - 1 or more
for ( ; x1 > 0; x1--) {
if (x1*x1*hh + y*y*ww <= hhww)
break;
}
dx = x0 - x1; // Current approximation of the slope
x0 = x1;
draw_line(center_x - x0, center_y - y, center_x + x0, center_y - y, on);
draw_line(center_x - x0, center_y + y, center_x + x0, center_y + y, on);
}
}
void LowLevelRenderer::draw_polygon(const std::vector<std::pair<int, int>>& points, bool on)
{
if (points.size() < 3) return;
for (size_t i = 0; i < points.size(); ++i) {
size_t next = (i + 1) % points.size();
draw_line(points[i].first, points[i].second,
points[next].first, points[next].second, on);
}
}
void LowLevelRenderer::draw_filled_polygon(const std::vector<std::pair<int, int>>& points, bool on)
{
if (points.size() < 3) return;
// Simple triangulation: fan from first vertex
// This works for convex polygons
for (size_t i = 1; i < points.size() - 1; ++i) {
draw_filled_triangle(points[0].first, points[0].second,
points[i].first, points[i].second,
points[i+1].first, points[i+1].second, on);
}
}
void LowLevelRenderer::draw_arc(int center_x, int center_y, int radius, int start_angle, int end_angle, bool on)
{
// Normalize angles to 0-360 range
start_angle = start_angle % 360;
end_angle = end_angle % 360;
if (start_angle < 0) start_angle += 360;
if (end_angle < 0) end_angle += 360;
// Handle wrap-around
if (start_angle > end_angle) {
draw_arc(center_x, center_y, radius, start_angle, 360, on);
draw_arc(center_x, center_y, radius, 0, end_angle, on);
return;
}
int x = radius;
int y = 0;
int err = 0;
// Convert angles to radians for comparison
double start_rad = start_angle * M_PI / 180.0;
double end_rad = end_angle * M_PI / 180.0;
while (x >= y) {
// Check each octant point against angle range
double angles[8] = {
atan2(y, x), // 0-45 deg
atan2(x, y), // 45-90 deg
atan2(x, -y), // 90-135 deg
atan2(y, -x), // 135-180 deg
atan2(-y, -x), // 180-225 deg
atan2(-x, -y), // 225-270 deg
atan2(-x, y), // 270-315 deg
atan2(-y, x) // 315-360 deg
};
int dx[8] = {x, y, -y, -x, -x, -y, y, x};
int dy[8] = {y, x, x, y, -y, -x, -x, -y};
for (int i = 0; i < 8; ++i) {
double angle = angles[i];
if (angle < 0) angle += 2 * M_PI;
if (angle >= start_rad && angle <= end_rad) {
set_pixel(center_x + dx[i], center_y + dy[i], on);
}
}
if (err <= 0) {
y += 1;
err += 2 * y + 1;
}
if (err > 0) {
x -= 1;
err -= 2 * x + 1;
}
}
}
void LowLevelRenderer::draw_bitmap(const unsigned char* bitmap, int x, int y, int width, int height, bool invert)
{
for (int py = 0; py < height; ++py) {
for (int px = 0; px < width; ++px) {
int bit_index = py * width + px;
int byte_index = bit_index / 8;
int bit_offset = 7 - (bit_index % 8); // MSB first
bool pixel_on = (bitmap[byte_index] & (1 << bit_offset)) != 0;
if (invert) {
pixel_on = !pixel_on;
}
if (pixel_on) {
set_pixel(x + px, y + py, true);
}
}
}
}
void LowLevelRenderer::draw_circle(int x, int y, int radius, bool on)
{
int x_pos = radius;
int y_pos = 0;
int err = 0;
while (x_pos >= y_pos)
{
set_pixel(x + x_pos, y + y_pos, on);
set_pixel(x + y_pos, y + x_pos, on);
set_pixel(x - y_pos, y + x_pos, on);
set_pixel(x - x_pos, y + y_pos, on);
set_pixel(x - x_pos, y - y_pos, on);
set_pixel(x - y_pos, y - x_pos, on);
set_pixel(x + y_pos, y - x_pos, on);
set_pixel(x + x_pos, y - y_pos, on);
if (err <= 0)
{
y_pos += 1;
err += 2 * y_pos + 1;
}
if (err > 0)
{
x_pos -= 1;
err -= 2 * x_pos + 1;
}
}
}
void LowLevelRenderer::draw_filled_circle(int x, int y, int radius, bool on)
{
int radius_squared = radius * radius;
for (int dy = -radius; dy <= radius; dy++)
{
for (int dx = -radius; dx <= radius; dx++)
{
if (dx * dx + dy * dy <= radius_squared)
{
set_pixel(x + dx, y + dy, on);
}
}
}
}
void LowLevelRenderer::draw_char_vcol(int x, int y, char c)
{
// The font table starts at space (ASCII 32)
if (c < 32 || c > 127)
return;
int font_idx = c - 32;
for (int col = 0; col < 6; col++)
{
unsigned char column_byte = (*current_font)[font_idx][col];
for (int row = 0; row < 8; row++)
{
// Check if the bit for this row is set
// Most of these 1-bit fonts use bit 0 as the top pixel
if (column_byte & (1 << row))
{
set_pixel(x + col, y + row, true);
}
}
}
}
void LowLevelRenderer::draw_string(int x, int y, const std::string &text, int spacing)
{
for (size_t i = 0; i < text.length(); i++)
{
draw_char_vcol(x + (i * (6 + spacing)), y, text[i]);
}
}
void LowLevelRenderer::draw_char_scaled(int x, int y, char c, int scale)
{
if (c < 32 || c > 127)
return;
if (scale < 1)
scale = 1; // Safety check
int font_idx = c - 32;
for (int col = 0; col < 6; col++)
{
unsigned char column_byte = (*current_font)[font_idx][col];
for (int row = 0; row < 8; row++)
{
if (column_byte & (1 << row))
{
// Draw a square of size [scale x scale]
for (int sy = 0; sy < scale; sy++)
{
for (int sx = 0; sx < scale; sx++)
{
set_pixel(x + (col * scale) + sx,
y + (row * scale) + sy,
true);
}
}
}
}
}
}
void LowLevelRenderer::draw_string_scaled(int x, int y, const char* text, int scale, int spacing)
{
int i = 0;
while(text[i] != '\0')
{
// We multiply the character width (6) and spacing by the scale
int next_x = x + (i * (6 + spacing) * scale);
draw_char_scaled(next_x, y, text[i], scale);
i++;
}
}