diff --git a/Makefile b/Makefile index 21d05ee..a67c571 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ endif # Target TARGET = app -SRC = main.cpp +SRC = main.cpp low_level_render.cpp $(TARGET): $(SRC) $(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET) $(INCLUDES) $(LIBS) diff --git a/app b/app index 5ae3212..9862d75 100755 Binary files a/app and b/app differ diff --git a/fonts/5x5_font.h b/fonts/5x5_font.h index 1fd14eb..f46da8f 100644 --- a/fonts/5x5_font.h +++ b/fonts/5x5_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_5x5[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5c,0x00,0x00,0x00,0x00,0x00}, // ! {0x06,0x00,0x06,0x00,0x00,0x00}, // " diff --git a/fonts/7linedigital_font.h b/fonts/7linedigital_font.h index 12bde5e..055c045 100644 --- a/fonts/7linedigital_font.h +++ b/fonts/7linedigital_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][4] = { +const unsigned char font_7linedigital[96][6] = { {0x00,0x00,0x00,0x00}, // {0x36,0x00,0x00,0x00}, // ! {0x06,0x00,0x00,0x06}, // " diff --git a/fonts/BMSPA_font.h b/fonts/BMSPA_font.h index 9188a04..845d34b 100644 --- a/fonts/BMSPA_font.h +++ b/fonts/BMSPA_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][8] = { +const unsigned char font_BMSPA[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0x00,0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // ! {0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // " diff --git a/fonts/BMplain_font.h b/fonts/BMplain_font.h index cd667da..858f0ef 100644 --- a/fonts/BMplain_font.h +++ b/fonts/BMplain_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_BMplain[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x2e,0x00,0x00,0x00,0x00,0x00}, // ! {0x03,0x00,0x03,0x00,0x00,0x00}, // " diff --git a/fonts/Blokus_font.h b/fonts/Blokus_font.h index 7db641c..0dfad30 100644 --- a/fonts/Blokus_font.h +++ b/fonts/Blokus_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_Blokus[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x00,0x00,0x5e,0x00,0x00,0x00}, // ! {0x00,0x0e,0x00,0x0e,0x00,0x00}, // " diff --git a/fonts/Commo-Monospaced_font.h b/fonts/Commo-Monospaced_font.h index 82f8e27..03622a3 100644 --- a/fonts/Commo-Monospaced_font.h +++ b/fonts/Commo-Monospaced_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][8] = { +const unsigned char font_Commo-Monospaced[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5f,0x5c,0x00,0x00,0x00,0x00,0x00,0x00}, // ! {0x03,0x07,0x00,0x03,0x07,0x00,0x00,0x00}, // " diff --git a/fonts/HISKYF21_font.h b/fonts/HISKYF21_font.h index ce32003..60c3c4d 100644 --- a/fonts/HISKYF21_font.h +++ b/fonts/HISKYF21_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_HISKYF21[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5c,0x00,0x00,0x00,0x00,0x00}, // ! {0x0c,0x00,0x0c,0x00,0x00,0x00}, // " diff --git a/fonts/HUNTER_font.h b/fonts/HUNTER_font.h index f7014ad..66015cd 100644 --- a/fonts/HUNTER_font.h +++ b/fonts/HUNTER_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][8] = { +const unsigned char font_HUNTER[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5f,0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // ! {0x07,0x07,0x00,0x07,0x07,0x00,0x00,0x00}, // " diff --git a/fonts/Minimum+1_font.h b/fonts/Minimum+1_font.h index 3c1a628..5a4a474 100644 --- a/fonts/Minimum+1_font.h +++ b/fonts/Minimum+1_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][7] = { +const unsigned char font_Minimum+1[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0x7f,0x51,0x7f,0x00,0x00,0x00,0x00}, // ! {0x0f,0x09,0x0f,0x09,0x0f,0x00,0x00}, // " diff --git a/fonts/Minimum_font.h b/fonts/Minimum_font.h index 5ddd91d..5e6f02a 100644 --- a/fonts/Minimum_font.h +++ b/fonts/Minimum_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_Minimum[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x2e,0x00,0x00,0x00,0x00,0x00}, // ! {0x06,0x00,0x06,0x00,0x00,0x00}, // " diff --git a/fonts/Raumsond_font.h b/fonts/Raumsond_font.h index 39143b0..681cc9b 100644 --- a/fonts/Raumsond_font.h +++ b/fonts/Raumsond_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][5] = { +const unsigned char font_Raumsond[96][6] = { {0x00,0x00,0x00,0x00,0x00}, // {0x5c,0x00,0x00,0x00,0x00}, // ! {0x0c,0x00,0x0c,0x00,0x00}, // " diff --git a/fonts/SUPERDIG_font.h b/fonts/SUPERDIG_font.h index 5777b45..fdb8593 100644 --- a/fonts/SUPERDIG_font.h +++ b/fonts/SUPERDIG_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_SUPERDIG[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x58,0x5c,0x00,0x00,0x00,0x00}, // ! {0x00,0x01,0x00,0x00,0x01,0x00}, // " diff --git a/fonts/acme_5_outlines_font.h b/fonts/acme_5_outlines_font.h index c696ae0..44f2477 100644 --- a/fonts/acme_5_outlines_font.h +++ b/fonts/acme_5_outlines_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_acme_5_outlines[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x7f,0x51,0x7f,0x00,0x00,0x00}, // ! {0x0f,0x09,0x0f,0x09,0x0f,0x00}, // " diff --git a/fonts/aztech_font.h b/fonts/aztech_font.h index 1de7169..1588496 100644 --- a/fonts/aztech_font.h +++ b/fonts/aztech_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_aztech[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x00,0x2e,0x00,0x00,0x00,0x00}, // ! {0x00,0x02,0x00,0x02,0x00,0x00}, // " diff --git a/fonts/bubblesstandard_font.h b/fonts/bubblesstandard_font.h index 3ec9418..b2215e4 100644 --- a/fonts/bubblesstandard_font.h +++ b/fonts/bubblesstandard_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][7] = { +const unsigned char font_bubblesstandard[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0xbf,0x00,0x00,0x00,0x00,0x00,0x00}, // ! {0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // " diff --git a/fonts/crackers_font.h b/fonts/crackers_font.h index 8a5ce5c..d76d851 100644 --- a/fonts/crackers_font.h +++ b/fonts/crackers_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_crackers[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5e,0x06,0x06,0x00,0x00,0x00}, // ! {0x1e,0x00,0x00,0x1e,0x00,0x00}, // " diff --git a/fonts/formplex12_font.h b/fonts/formplex12_font.h index 0315979..26cf79b 100644 --- a/fonts/formplex12_font.h +++ b/fonts/formplex12_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][8] = { +const unsigned char font_formplex12[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0x00,0x2f,0x2f,0x00,0x00,0x00,0x00,0x00}, // ! {0x03,0x03,0x00,0x03,0x03,0x00,0x00,0x00}, // " diff --git a/fonts/haiku_font.h b/fonts/haiku_font.h index 3554d9c..f408927 100644 --- a/fonts/haiku_font.h +++ b/fonts/haiku_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_haiku[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x00,0x5e,0x00,0x00,0x00,0x00}, // ! {0x00,0x0e,0x0e,0x00,0x00,0x00}, // " diff --git a/fonts/homespun_font.h b/fonts/homespun_font.h index 4e14e2f..5f8727c 100644 --- a/fonts/homespun_font.h +++ b/fonts/homespun_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][7] = { +const unsigned char font_homespun[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // ! {0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // " diff --git a/fonts/m38_font.h b/fonts/m38_font.h index ac72aef..4d88789 100644 --- a/fonts/m38_font.h +++ b/fonts/m38_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][8] = { +const unsigned char font_m38[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0xdf,0xdf,0x00,0x00,0x00,0x00,0x00,0x00}, // ! {0x07,0x07,0x00,0x07,0x07,0x00,0x00,0x00}, // " diff --git a/fonts/pzim3x5_font.h b/fonts/pzim3x5_font.h index 45cc8b4..9a4c205 100644 --- a/fonts/pzim3x5_font.h +++ b/fonts/pzim3x5_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][3] = { +const unsigned char font_pzim3x5[96][6] = { {0x00,0x00,0x00}, // {0x00,0x2e,0x00}, // ! {0x06,0x00,0x06}, // " diff --git a/fonts/renew_font.h b/fonts/renew_font.h index 980680f..934819c 100644 --- a/fonts/renew_font.h +++ b/fonts/renew_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][7] = { +const unsigned char font_renew[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5e,0x00,0x00,0x00,0x00,0x00,0x00}, // ! {0x0c,0x00,0x0c,0x00,0x00,0x00,0x00}, // " diff --git a/fonts/sloth_font.h b/fonts/sloth_font.h index a730ca2..7f3c7d3 100644 --- a/fonts/sloth_font.h +++ b/fonts/sloth_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_sloth[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x5c,0x00,0x00,0x00,0x00,0x00}, // ! {0x03,0x00,0x03,0x00,0x00,0x00}, // " diff --git a/fonts/tama_mini02_font.h b/fonts/tama_mini02_font.h index ff31e63..e9b7c57 100644 --- a/fonts/tama_mini02_font.h +++ b/fonts/tama_mini02_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][5] = { +const unsigned char font_tama_mini02[96][6] = { {0x00,0x00,0x00,0x00,0x00}, // {0x2f,0x00,0x00,0x00,0x00}, // ! {0x03,0x00,0x03,0x00,0x00}, // " diff --git a/fonts/zxpix_font.h b/fonts/zxpix_font.h index 0188b0e..e744ad7 100644 --- a/fonts/zxpix_font.h +++ b/fonts/zxpix_font.h @@ -1,4 +1,4 @@ -const unsigned char font[96][6] = { +const unsigned char font_zxpix[96][6] = { {0x00,0x00,0x00,0x00,0x00,0x00}, // {0x2f,0x00,0x00,0x00,0x00,0x00}, // ! {0x03,0x00,0x03,0x00,0x00,0x00}, // " diff --git a/low_level_render.cpp b/low_level_render.cpp new file mode 100644 index 0000000..8797470 --- /dev/null +++ b/low_level_render.cpp @@ -0,0 +1,492 @@ +#include "low_level_render.h" +#include +#include +#include +#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) {} + +void LowLevelRenderer::set_font(const unsigned char (*font)[96][6]) { + current_font = font; +} + +void LowLevelRenderer::set_pixel(int x, int y, bool on) +{ + if (x < 0 || x >= V_WIDTH || y < 0 || y >= V_HEIGHT) + 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>& 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_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++; + } +} diff --git a/low_level_render.h b/low_level_render.h new file mode 100644 index 0000000..6cb19ab --- /dev/null +++ b/low_level_render.h @@ -0,0 +1,67 @@ +// class that handles low-level rendering operations, such as drawing pixels and shapes to the display. +// This class is framework-agnostic and focuses solely on manipulating a 1-bit per pixel buffer. +// Constructor Args: +// uint8_t* buffer: Pointer to the bit buffer +// int width: Display width in pixels +// int height: Display height in pixels + +#ifndef LOW_LEVEL_RENDER_H +#define LOW_LEVEL_RENDER_H + +#include +#include +#include + +// Font extern declarations +extern const unsigned char font_5x5[96][6]; +extern const unsigned char font_7linedigital[96][6]; +extern const unsigned char font_BMplain[96][6]; +extern const unsigned char font_Blokus[96][6]; +extern const unsigned char font_HISKYF21[96][6]; +extern const unsigned char font_Minimum[96][6]; +extern const unsigned char font_SUPERDIG[96][6]; +extern const unsigned char font_acme_5_outlines[96][6]; +extern const unsigned char font_aztech[96][6]; +extern const unsigned char font_crackers[96][6]; +extern const unsigned char font_haiku[96][6]; +extern const unsigned char font_sloth[96][6]; +extern const unsigned char font_zxpix[96][6]; + +class LowLevelRenderer { +private: + uint8_t* bit_buffer; + int V_WIDTH; + int V_HEIGHT; + const unsigned char (*current_font)[96][6]; + void draw_corner_arc(int center_x, int center_y, int radius, int quadrant, bool on); + void fill_bottom_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); + void fill_top_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); + +public: + LowLevelRenderer(uint8_t* buffer, int width, int height); + + // Font management + void set_font(const unsigned char (*font)[96][6]); + + // --- 1-BIT DRAWING PRIMITIVES --- + void set_pixel(int x, int y, bool on); + void draw_line(int x0, int y0, int x1, int y1, bool on); + void draw_rectangle(int x, int y, int width, int height, bool on); + void draw_filled_rectangle(int x, int y, int width, int height, bool on); + void draw_rounded_rectangle(int x, int y, int width, int height, int radius, bool on); + void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); + void draw_filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); + void draw_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on); + void draw_filled_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on); + void draw_polygon(const std::vector>& points, bool on); + void draw_filled_polygon(const std::vector>& points, bool on); + void draw_arc(int center_x, int center_y, int radius, int start_angle, int end_angle, bool on); + void draw_circle(int x, int y, int radius, bool on); + void draw_filled_circle(int x, int y, int radius, bool on); + void draw_char_vcol(int x, int y, char c); + void draw_string(int x, int y, const std::string &text, int spacing = 1); + void draw_char_scaled(int x, int y, char c, int scale); + void draw_string_scaled(int x, int y, const char* text, int scale, int spacing = 1); +}; + +#endif // LOW_LEVEL_RENDER_H \ No newline at end of file diff --git a/main.cpp b/main.cpp index 3ff510f..7b4e961 100644 --- a/main.cpp +++ b/main.cpp @@ -5,159 +5,12 @@ #include #include #include -#include "./fonts/acme_5_outlines_font.h" +#include "low_level_render.h" const int V_WIDTH = 400; const int V_HEIGHT = 300; uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8]; -// --- 1-BIT DRAWING PRIMITIVES --- - -void set_pixel(int x, int y, bool on) -{ - if (x < 0 || x >= V_WIDTH || y < 0 || y >= V_HEIGHT) - 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 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; - } - } -} - -// Minimal 8x8 Bitmap Font (Example for 'A' and 'B') -void draw_char(int x, int y, char c) -{ - static const uint8_t font[2][8] = { - {0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00}, // A - {0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00} // B - }; - int idx = (c == 'A') ? 0 : 1; - for (int row = 0; row < 8; row++) - { - for (int col = 0; col < 8; col++) - { - if (font[idx][row] & (1 << (7 - col))) - set_pixel(x + col, y + row, true); - } - } -} - -/** - * Draws a single character using the 6x8 vertical column format - * @param x Start x position - * @param y Start y position - * @param c The character to draw - */ -void 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 = 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); - } - } - } -} - -/** - * Draws a full string of text - * @param spacing pixels between characters (usually 1 or 2) - */ -void draw_string(int x, int y, const std::string &text, int spacing = 1) -{ - for (size_t i = 0; i < text.length(); i++) - { - draw_char_vcol(x + (i * (6 + spacing)), y, text[i]); - } -} - -/** - * Draws a scaled character using the 6x8 vertical column format - * @param scale Integer multiplier (1 = 1x, 2 = 2x, etc.) - */ -void 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 = 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); - } - } - } - } - } -} - -/** - * Draws a scaled string - */ -void draw_string_scaled(int x, int y, const char* text, int scale, int spacing = 1) -{ - 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++; - } -} - char command_buffer[256]; int command_buffer_index = 0; @@ -179,11 +32,11 @@ int main() sf::Sprite sprite(texture); sprite.setScale(1.f, 1.f); + LowLevelRenderer renderer(bit_buffer, V_WIDTH, V_HEIGHT); + sf::Clock clock; bool toggle = false; - int counter = 0; - while (window.isOpen()) { sf::Event event; @@ -227,32 +80,11 @@ int main() command_buffer[command_buffer_index] = '\0'; } } - - // Update display immediately after input - // Clear buffer - for (int i = 0; i < sizeof(bit_buffer); i++) - bit_buffer[i] = 0; - - // Draw content - draw_string_scaled(10, 10, "Hello World!", 3); - draw_string_scaled(10, 270, command_buffer, 1); - - // Bridge: 1-bit to RGBA - for (int i = 0; i < V_WIDTH * V_HEIGHT; ++i) - { - bool is_on = (bit_buffer[i / 8] >> (7 - (i % 8))) & 1; - int base = i * 4; - sf::Uint8 color = is_on ? 0xFF : 0x00; - display_pixels[base] = display_pixels[base + 1] = display_pixels[base + 2] = color; - display_pixels[base + 3] = 255; - } - texture.update(display_pixels.data()); } // --- TIMING LOGIC: Update twice per second (500ms) --- - if (clock.getElapsedTime().asMilliseconds() >= 1000) + if (clock.getElapsedTime().asMilliseconds() >= 500) { - //printf("Updating display %d...\n", counter++); clock.restart(); toggle = !toggle; // Change something every half second @@ -260,11 +92,51 @@ int main() for (int i = 0; i < sizeof(bit_buffer); i++) bit_buffer[i] = 0; - // Draw content - // draw_line(10, 10, 390, 290, true); - draw_string_scaled(10, 10, "Hello World!", 3); - - draw_string_scaled(10, 270, command_buffer, 1); + // Showcase drawing functions + // Rectangles + renderer.draw_rectangle(10, 50, 80, 60, true); + renderer.draw_filled_rectangle(110, 50, 80, 60, true); + renderer.draw_rounded_rectangle(210, 50, 80, 60, 10, true); + + // Triangles + renderer.draw_triangle(20, 130, 80, 130, 50, 180, true); + renderer.draw_filled_triangle(120, 130, 180, 130, 150, 180, true); + + // Polygons + std::vector> hex_points = { + {250, 130}, {280, 145}, {280, 175}, {250, 190}, {220, 175}, {220, 145} + }; + renderer.draw_polygon(hex_points, true); + + std::vector> star_points = { + {320, 140}, {325, 155}, {340, 155}, {330, 170}, {335, 185}, + {320, 175}, {305, 185}, {310, 170}, {300, 155}, {315, 155} + }; + renderer.draw_filled_polygon(star_points, true); + + // Arcs + renderer.draw_arc(60, 270, 25, 45, 315, true); // Pac-man shape + renderer.draw_arc(160, 270, 25, 0, 180, true); // Semicircle + + // Ellipses + renderer.draw_ellipse(60, 220, 30, 20, true); + renderer.draw_filled_ellipse(160, 220, 30, 20, true); + + // Circles + renderer.draw_circle(60, 160, 25, true); + renderer.draw_filled_circle(160, 160, 25, true); + renderer.draw_circle(260, 160, 15, true); // Smaller circle + + // Lines + renderer.draw_line(10, 200, 90, 250, true); + renderer.draw_line(110, 200, 190, 250, true); + + // Text with different fonts + renderer.set_font(&font_acme_5_outlines); + renderer.draw_string_scaled(10, 10, "Drawing Demo", 2); + + renderer.set_font(&font_5x5); + renderer.draw_string_scaled(10, 270, command_buffer, 1); // Bridge: 1-bit to RGBA for (int i = 0; i < V_WIDTH * V_HEIGHT; ++i)