#include "low_level_render.h" #include #include #include #include "../fonts/5x5_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" #include "../fonts/Commo-Monospaced_font.h" #include "../fonts/7linedigital_font.h" #include "../fonts/BMSPA_font.h" #include "../fonts/HUNTER_font.h" #include "../fonts/Raumsond_font.h" #include "../fonts/bubblesstandard_font.h" #include "../fonts/formplex12_font.h" #include "../fonts/homespun_font.h" #include "../fonts/Minimum_1_font.h" #include "../fonts/m38_font.h" #include "../fonts/pzim3x5_font.h" #include "../fonts/renew_font.h" #include "../fonts/tama_mini02_font.h" // Font object definitions Font font_5x5_obj(reinterpret_cast(font_5x5), 96, 6, 8); Font font_7linedigital_obj(reinterpret_cast(font_7linedigital), 96, 4, 8); Font font_acme_5_outlines_obj(reinterpret_cast(font_acme_5_outlines), 96, 6, 8); Font font_aztech_obj(reinterpret_cast(font_aztech), 96, 6, 8); Font font_BMplain_obj(reinterpret_cast(font_BMplain), 96, 6, 8); Font font_BMSPA_obj(reinterpret_cast(font_BMSPA), 96, 8, 8); Font font_Blokus_obj(reinterpret_cast(font_Blokus), 96, 6, 8); Font font_bubblesstandard_obj(reinterpret_cast(font_bubblesstandard), 96, 7, 8); Font font_Commo_Monospaced_obj(reinterpret_cast(font_Commo_Monospaced), 96, 8, 8); Font font_crackers_obj(reinterpret_cast(font_crackers), 96, 6, 8); Font font_formplex12_obj(reinterpret_cast(font_formplex12), 96, 8, 8); Font font_haiku_obj(reinterpret_cast(font_haiku), 96, 6, 8); Font font_HISKYF21_obj(reinterpret_cast(font_HISKYF21), 96, 6, 8); Font font_homespun_obj(reinterpret_cast(font_homespun), 96, 7, 8); Font font_HUNTER_obj(reinterpret_cast(font_HUNTER), 96, 8, 8); Font font_m38_obj(reinterpret_cast(font_m38), 96, 8, 8); Font font_Minimum_obj(reinterpret_cast(font_Minimum), 96, 6, 8); Font font_Minimum_1_obj(reinterpret_cast(font_Minimum_1), 96, 7, 8); Font font_pzim3x5_obj(reinterpret_cast(font_pzim3x5), 96, 3, 8); Font font_Raumsond_obj(reinterpret_cast(font_Raumsond), 96, 5, 8); Font font_renew_obj(reinterpret_cast(font_renew), 96, 7, 8); Font font_sloth_obj(reinterpret_cast(font_sloth), 96, 6, 8); Font font_SUPERDIG_obj(reinterpret_cast(font_SUPERDIG), 96, 6, 8); Font font_tama_mini02_obj(reinterpret_cast(font_tama_mini02), 96, 5, 8); Font font_zxpix_obj(reinterpret_cast(font_zxpix), 96, 6, 8); LowLevelRenderer::LowLevelRenderer(uint8_t* buffer, int width, int height) : bit_buffer(buffer), V_WIDTH(width), V_HEIGHT(height), current_font(&font_5x5_obj), clipping_enabled(false), clip_x(0), clip_y(0), clip_width(width), clip_height(height), text_color(true) {} void LowLevelRenderer::set_font(const Font* font) { current_font = font; } void LowLevelRenderer::set_text_color(bool color) { text_color = color; } // 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 width) { 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) { // Draw a vertical line for the specified width for (int w = -(width / 2); w <= (width / 2); w++) { set_pixel(x0 + w, y0, on); } for (int w = -(width / 2); w <= (width / 2); w++) { set_pixel(x0, y0 + w, on); } 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, int line_width) { // Draw top line draw_line(x, y, x + width - 1, y, on, line_width); // Draw bottom line draw_line(x, y + height - 1, x + width - 1, y + height - 1, on, line_width); // Draw left line draw_line(x, y, x, y + height - 1, on, line_width); // Draw right line draw_line(x + width - 1, y, x + width - 1, y + height - 1, on, line_width); } void LowLevelRenderer::draw_filled_rectangle(int x, int y, int width, int height, bool on, int line_width) { for (int i = 0; i < height; i++) { draw_line(x, y + i, x + width - 1, y + i, on, line_width); } } void LowLevelRenderer::draw_rounded_rectangle(int x, int y, int width, int height, int radius, bool on, bool filled) { // 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; if (!filled) { // --- Outline Logic --- if (width > 2 * radius) { draw_line(x + radius, y, x + width - radius - 1, y, on); draw_line(x + radius, y + height - 1, x + width - radius - 1, y + height - 1, on); } if (height > 2 * radius) { draw_line(x, y + radius, x, y + height - radius - 1, on); draw_line(x + width - 1, y + radius, x + width - 1, y + height - radius - 1, on); } if (radius > 0) { draw_corner_arc(x + radius, y + radius, radius, 2, on); draw_corner_arc(x + width - radius - 1, y + radius, radius, 1, on); draw_corner_arc(x + radius, y + height - radius - 1, radius, 3, on); draw_corner_arc(x + width - radius - 1, y + height - radius - 1, radius, 0, on); } } else { // --- Filling Logic --- // 1. Fill the central rectangular body (excluding the top and bottom radius areas) draw_filled_rectangle(x, y + radius, width, height - 2 * radius, on, 1); // 2. Fill the top and bottom sections with horizontal lines of varying widths for (int i = 0; i < radius; i++) { // Calculate horizontal offset using Pythagorean theorem: offset = r - sqrt(r^2 - (r-i)^2) int offset = radius - (int)sqrt((double)radius * radius - (double)(radius - i) * (radius - i)); // Top radius row draw_line(x + offset, y + i, x + width - offset - 1, y + i, on); // Bottom radius row int bottom_y = y + height - radius + i; // Mirroring the offset logic for the bottom int b_offset = radius - (int)sqrt((double)radius * radius - (double)(i + 1) * (i + 1)); draw_line(x + b_offset, bottom_y, x + width - b_offset - 1, bottom_y, on); } } } 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>& 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>& 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) { int byteWidth = (width + 7) / 8; // Bitmaps are typically padded to the next full byte for each row for (int py = 0; py < height; ++py) { for (int px = 0; px < width; ++px) { int byte_index = py * byteWidth + (px / 8); int bit_offset = 7 - (px % 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, text_color); } } } } 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); } } } } int LowLevelRenderer::draw_char_vcol(int x, int y, char c) { if (!current_font) { fprintf(stderr, "[draw_char_vcol] current_font is null!\n"); return 0; } // The font table starts at space (ASCII 32) if (c < 32 || c > 127) { fprintf(stderr, "[draw_char_vcol] char out of range: %d\n", (int)c); return 0; } int font_idx = c - 32; if (font_idx < 0 || font_idx >= current_font->get_num_chars()) { fprintf(stderr, "[draw_char_vcol] font_idx out of range: %d\n", font_idx); return 0; } const unsigned char* char_data = current_font->get_char_data(font_idx); if (!char_data) { fprintf(stderr, "[draw_char_vcol] char_data is null for idx %d\n", font_idx); return 0; } int bytes_per_char = current_font->get_bytes_per_char(); int char_height = current_font->get_char_height(); // Find the actual width by skipping trailing empty columns int actual_width = 0; for (int col = bytes_per_char - 1; col >= 0; col--) { if (char_data[col] != 0 && c != ' ') { actual_width = col + 1; break; } } // Draw only up to the actual width for (int col = 0; col < actual_width; col++) { unsigned char column_byte = char_data[col]; for (int row = 0; row < char_height; row++) { // Check if the bit for this row is set if (column_byte & (1 << row)) { set_pixel(x + col, y + row, text_color); } } } return actual_width; } void LowLevelRenderer::draw_string(int x, int y, const std::string &text, int spacing) { if (!current_font) return; int current_x = x; for (size_t i = 0; i < text.length(); i++) { int char_width = draw_char_vcol(current_x, y, text[i]); current_x += char_width + spacing; } } int LowLevelRenderer::draw_char_scaled(int x, int y, char c, int scale) { if (!current_font) return 0; if (c < 32 || c > 127) return 0; if (scale < 1) scale = 1; // Safety check int font_idx = c - 32; const unsigned char* char_data = current_font->get_char_data(font_idx); if (!char_data) return 0; int bytes_per_char = current_font->get_bytes_per_char(); int char_height = current_font->get_char_height(); // Find the actual width by skipping trailing empty columns int actual_width = 0; for (int col = bytes_per_char - 1; col >= 0; col--) { if (char_data[col] != 0) { actual_width = col + 1; break; } } // Draw only up to the actual width, scaled for (int col = 0; col < actual_width; col++) { unsigned char column_byte = char_data[col]; for (int row = 0; row < char_height; 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, text_color); } } } } } return actual_width * scale; } int LowLevelRenderer::draw_string_scaled(int x, int y, const char* text, int scale, int spacing) { if (!current_font) return 0; int current_x = x; int i = 0; while(text[i] != '\0') { int char_width = draw_char_scaled(current_x, y, text[i], scale); current_x += char_width + (spacing * scale); i++; } return current_x; } int LowLevelRenderer::get_char_width_scaled(char c, int scale) { if (!current_font) return 0; if (c < 32 || c > 127) return 0; if (scale < 1) scale = 1; int font_idx = c - 32; const unsigned char *char_data = current_font->get_char_data(font_idx); if (!char_data) return 0; int bytes_per_char = current_font->get_bytes_per_char(); // Find the actual width by skipping trailing empty columns int actual_width = 0; for (int col = bytes_per_char - 1; col >= 0; col--) { if (char_data[col] != 0) { actual_width = col + 1; break; } } return actual_width * scale; } int LowLevelRenderer::get_string_width_scaled(const char *text, int scale, int spacing) { if (!current_font) return 0; int width = 0; int i = 0; while (text[i] != '\0') { int char_width = get_char_width_scaled(text[i], scale); // Add spacing only if it's not the last character, but logic usually adds // spacing after each char In drawn_string_scaled: current_x += char_width + // (spacing * scale); So width accumulates char_width + spacing*scale. // However, the last character shouldn't really have spacing if we want // exact bounding box, but let's match draw_string_scaled behavior which // effectively advances cursor. Wait, draw_string_scaled returns // `current_x`. If x=0, current_x ends up at sum(char_width + // spacing*scale). width += char_width + (spacing * scale); i++; } // Correction: draw_string_scaled includes spacing after the last character. // If we want exact pixel width of the visible text, we might want to subtract // the last spacing. But for UI alignment, usually cursor advancement is fine. // Let's stick to returning what draw_string_scaled would add to x. return width; }