Compare commits
3 Commits
257c858f98
...
5e0fe7c6d9
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e0fe7c6d9 | |||
| 55475d10ec | |||
| 713d2c0cfd |
@@ -7,7 +7,7 @@ CXXFLAGS = -std=c++11 -Wall
|
|||||||
|
|
||||||
# Paths for Homebrew on macOS (Silicon/M1/M2/M3)
|
# Paths for Homebrew on macOS (Silicon/M1/M2/M3)
|
||||||
ifeq ($(OS), Darwin)
|
ifeq ($(OS), Darwin)
|
||||||
SFML_DIR = $(shell brew --prefix sfml)
|
SFML_DIR = $(shell brew --prefix sfml@2)
|
||||||
INCLUDES = -I$(SFML_DIR)/include
|
INCLUDES = -I$(SFML_DIR)/include
|
||||||
LIBS = -L$(SFML_DIR)/lib -lsfml-graphics -lsfml-window -lsfml-system
|
LIBS = -L$(SFML_DIR)/lib -lsfml-graphics -lsfml-window -lsfml-system
|
||||||
else
|
else
|
||||||
@@ -18,7 +18,7 @@ endif
|
|||||||
|
|
||||||
# Target
|
# Target
|
||||||
TARGET = app
|
TARGET = app
|
||||||
SRC = main.cpp
|
SRC = main.cpp low_level_render.cpp
|
||||||
|
|
||||||
$(TARGET): $(SRC)
|
$(TARGET): $(SRC)
|
||||||
$(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET) $(INCLUDES) $(LIBS)
|
$(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET) $(INCLUDES) $(LIBS)
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_5x5[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x06,0x00,0x06,0x00,0x00,0x00}, // "
|
{0x06,0x00,0x06,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][4] = {
|
const unsigned char font_7linedigital[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00}, //
|
||||||
{0x36,0x00,0x00,0x00}, // !
|
{0x36,0x00,0x00,0x00}, // !
|
||||||
{0x06,0x00,0x00,0x06}, // "
|
{0x06,0x00,0x00,0x06}, // "
|
||||||
|
|||||||
+1
-1
@@ -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,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x00,0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x00,0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // "
|
{0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_BMplain[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x2e,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x2e,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x00,0x03,0x00,0x00,0x00}, // "
|
{0x03,0x00,0x03,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
+1
-1
@@ -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,0x00,0x00,0x00,0x00}, //
|
||||||
{0x00,0x00,0x5e,0x00,0x00,0x00}, // !
|
{0x00,0x00,0x5e,0x00,0x00,0x00}, // !
|
||||||
{0x00,0x0e,0x00,0x0e,0x00,0x00}, // "
|
{0x00,0x0e,0x00,0x0e,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -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}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5f,0x5c,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x5f,0x5c,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x07,0x00,0x03,0x07,0x00,0x00,0x00}, // "
|
{0x03,0x07,0x00,0x03,0x07,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_HISKYF21[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x0c,0x00,0x0c,0x00,0x00,0x00}, // "
|
{0x0c,0x00,0x0c,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
+1
-1
@@ -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}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5f,0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x5f,0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x07,0x07,0x00,0x07,0x07,0x00,0x00,0x00}, // "
|
{0x07,0x07,0x00,0x07,0x07,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -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}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x7f,0x51,0x7f,0x00,0x00,0x00,0x00}, // !
|
{0x7f,0x51,0x7f,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x0f,0x09,0x0f,0x09,0x0f,0x00,0x00}, // "
|
{0x0f,0x09,0x0f,0x09,0x0f,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_Minimum[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x2e,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x2e,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x06,0x00,0x06,0x00,0x00,0x00}, // "
|
{0x06,0x00,0x06,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][5] = {
|
const unsigned char font_Raumsond[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5c,0x00,0x00,0x00,0x00}, // !
|
{0x5c,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x0c,0x00,0x0c,0x00,0x00}, // "
|
{0x0c,0x00,0x0c,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_SUPERDIG[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x58,0x5c,0x00,0x00,0x00,0x00}, // !
|
{0x58,0x5c,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x00,0x01,0x00,0x00,0x01,0x00}, // "
|
{0x00,0x01,0x00,0x00,0x01,0x00}, // "
|
||||||
|
|||||||
@@ -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}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x7f,0x51,0x7f,0x00,0x00,0x00}, // !
|
{0x7f,0x51,0x7f,0x00,0x00,0x00}, // !
|
||||||
{0x0f,0x09,0x0f,0x09,0x0f,0x00}, // "
|
{0x0f,0x09,0x0f,0x09,0x0f,0x00}, // "
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_aztech[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x00,0x2e,0x00,0x00,0x00,0x00}, // !
|
{0x00,0x2e,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x00,0x02,0x00,0x02,0x00,0x00}, // "
|
{0x00,0x02,0x00,0x02,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][7] = {
|
const unsigned char font_bubblesstandard[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0xbf,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
{0xbf,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // "
|
{0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_crackers[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5e,0x06,0x06,0x00,0x00,0x00}, // !
|
{0x5e,0x06,0x06,0x00,0x00,0x00}, // !
|
||||||
{0x1e,0x00,0x00,0x1e,0x00,0x00}, // "
|
{0x1e,0x00,0x00,0x1e,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -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,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x00,0x2f,0x2f,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x00,0x2f,0x2f,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x03,0x00,0x03,0x03,0x00,0x00,0x00}, // "
|
{0x03,0x03,0x00,0x03,0x03,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_haiku[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x00,0x5e,0x00,0x00,0x00,0x00}, // !
|
{0x00,0x5e,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x00,0x0e,0x0e,0x00,0x00,0x00}, // "
|
{0x00,0x0e,0x0e,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][7] = {
|
const unsigned char font_homespun[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x5f,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // "
|
{0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
+1
-1
@@ -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}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0xdf,0xdf,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
{0xdf,0xdf,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x07,0x07,0x00,0x07,0x07,0x00,0x00,0x00}, // "
|
{0x07,0x07,0x00,0x07,0x07,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][3] = {
|
const unsigned char font_pzim3x5[96][6] = {
|
||||||
{0x00,0x00,0x00}, //
|
{0x00,0x00,0x00}, //
|
||||||
{0x00,0x2e,0x00}, // !
|
{0x00,0x2e,0x00}, // !
|
||||||
{0x06,0x00,0x06}, // "
|
{0x06,0x00,0x06}, // "
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][7] = {
|
const unsigned char font_renew[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5e,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x5e,0x00,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x0c,0x00,0x0c,0x00,0x00,0x00,0x00}, // "
|
{0x0c,0x00,0x0c,0x00,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_sloth[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x00,0x03,0x00,0x00,0x00}, // "
|
{0x03,0x00,0x03,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][5] = {
|
const unsigned char font_tama_mini02[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x2f,0x00,0x00,0x00,0x00}, // !
|
{0x2f,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x00,0x03,0x00,0x00}, // "
|
{0x03,0x00,0x03,0x00,0x00}, // "
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const unsigned char font[96][6] = {
|
const unsigned char font_zxpix[96][6] = {
|
||||||
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
{0x2f,0x00,0x00,0x00,0x00,0x00}, // !
|
{0x2f,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
{0x03,0x00,0x03,0x00,0x00,0x00}, // "
|
{0x03,0x00,0x03,0x00,0x00,0x00}, // "
|
||||||
|
|||||||
@@ -0,0 +1,540 @@
|
|||||||
|
#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_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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// 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 <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
bool clipping_enabled;
|
||||||
|
int clip_x, clip_y, clip_width, clip_height;
|
||||||
|
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);
|
||||||
|
bool is_point_in_clip_rect(int x, int y);
|
||||||
|
|
||||||
|
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<std::pair<int, int>>& points, bool on);
|
||||||
|
void draw_filled_polygon(const std::vector<std::pair<int, int>>& points, bool on);
|
||||||
|
void draw_arc(int center_x, int center_y, int radius, int start_angle, int end_angle, bool on);
|
||||||
|
|
||||||
|
// Clipping functions
|
||||||
|
void set_clip_rect(int x, int y, int width, int height);
|
||||||
|
void reset_clip_rect();
|
||||||
|
bool is_clipping_enabled() const;
|
||||||
|
|
||||||
|
// Buffer operations
|
||||||
|
void invert_buffer();
|
||||||
|
void clear_buffer();
|
||||||
|
|
||||||
|
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
|
||||||
@@ -2,172 +2,163 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "./fonts/acme_5_outlines_font.h"
|
#include <unistd.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include "low_level_render.h"
|
||||||
|
|
||||||
const int V_WIDTH = 400;
|
const int V_WIDTH = 400;
|
||||||
const int V_HEIGHT = 300;
|
const int V_HEIGHT = 300;
|
||||||
uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8];
|
uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8];
|
||||||
|
|
||||||
// --- 1-BIT DRAWING PRIMITIVES ---
|
char command_buffer[256];
|
||||||
|
int command_buffer_index = 0;
|
||||||
|
|
||||||
void set_pixel(int x, int y, bool on) {
|
int main()
|
||||||
if (x < 0 || x >= V_WIDTH || y < 0 || y >= V_HEIGHT) return;
|
{
|
||||||
int bit_pos = y * V_WIDTH + x;
|
// Save original terminal settings
|
||||||
if (on) bit_buffer[bit_pos / 8] |= (1 << (7 - (bit_pos % 8)));
|
struct termios old_tio, new_tio;
|
||||||
else bit_buffer[bit_pos / 8] &= ~(1 << (7 - (bit_pos % 8)));
|
tcgetattr(STDIN_FILENO, &old_tio);
|
||||||
}
|
new_tio = old_tio;
|
||||||
|
new_tio.c_lflag &= (~ICANON & ~ECHO); // Disable canonical mode and echo
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
|
||||||
|
|
||||||
void draw_line(int x0, int y0, int x1, int y1, bool on) {
|
sf::RenderWindow window(sf::VideoMode(400, 300), "0.5Hz Update Emulator");
|
||||||
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 std::string& text, int scale, int spacing = 1) {
|
|
||||||
for (size_t i = 0; i < text.length(); i++) {
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
sf::RenderWindow window(sf::VideoMode(800, 600), "0.5Hz Update Emulator");
|
|
||||||
|
|
||||||
std::vector<sf::Uint8> display_pixels(V_WIDTH * V_HEIGHT * 4);
|
std::vector<sf::Uint8> display_pixels(V_WIDTH * V_HEIGHT * 4);
|
||||||
sf::Texture texture;
|
sf::Texture texture;
|
||||||
texture.create(V_WIDTH, V_HEIGHT);
|
texture.create(V_WIDTH, V_HEIGHT);
|
||||||
texture.setSmooth(false); // Keeps the 1-bit pixels sharp and blocky
|
texture.setSmooth(false); // Keeps the 1-bit pixels sharp and blocky
|
||||||
sf::Sprite sprite(texture);
|
sf::Sprite sprite(texture);
|
||||||
sprite.setScale(2.f, 2.f);
|
sprite.setScale(1.f, 1.f);
|
||||||
|
|
||||||
|
LowLevelRenderer renderer(bit_buffer, V_WIDTH, V_HEIGHT);
|
||||||
|
|
||||||
sf::Clock clock;
|
sf::Clock clock;
|
||||||
bool toggle = false;
|
bool toggle = false;
|
||||||
|
bool inverted = false;
|
||||||
|
|
||||||
while (window.isOpen()) {
|
while (window.isOpen())
|
||||||
|
{
|
||||||
sf::Event event;
|
sf::Event event;
|
||||||
while (window.pollEvent(event)) {
|
while (window.pollEvent(event))
|
||||||
if (event.type == sf::Event::Closed) window.close();
|
{
|
||||||
|
if (event.type == sf::Event::Closed)
|
||||||
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
while (window.pollEvent(event)) {
|
// Check for console input with timeout
|
||||||
if (event.type == sf::Event::Closed)
|
fd_set readfds;
|
||||||
window.close();
|
FD_ZERO(&readfds);
|
||||||
|
FD_SET(STDIN_FILENO, &readfds);
|
||||||
// Check for a single key press
|
struct timeval tv;
|
||||||
if (event.type == sf::Event::KeyPressed) {
|
tv.tv_sec = 0;
|
||||||
if (event.key.code == sf::Keyboard::Space) {
|
tv.tv_usec = 10000; // 10ms timeout
|
||||||
// This happens only ONCE when you hit Space
|
int retval = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
|
||||||
toggle = !toggle;
|
if (retval > 0)
|
||||||
|
{
|
||||||
|
char c = getchar();
|
||||||
|
//printf("Key Pressed: %c\n", c);
|
||||||
|
if (c == '\n' || c == '\r')
|
||||||
|
{
|
||||||
|
printf("Command entered: %s\n", command_buffer);
|
||||||
|
command_buffer_index = 0;
|
||||||
|
command_buffer[0] = '\0';
|
||||||
|
}
|
||||||
|
else if (c == '\b' || c == 127) // Backspace
|
||||||
|
{
|
||||||
|
if (command_buffer_index > 0)
|
||||||
|
{
|
||||||
|
command_buffer_index--;
|
||||||
|
command_buffer[command_buffer_index] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c >= 32 && c <= 126) // Printable characters
|
||||||
|
{
|
||||||
|
if (command_buffer_index < sizeof(command_buffer) - 1)
|
||||||
|
{
|
||||||
|
command_buffer[command_buffer_index++] = c;
|
||||||
|
command_buffer[command_buffer_index] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- TIMING LOGIC: Update twice per second (500ms) ---
|
// --- TIMING LOGIC: Update twice per second (500ms) ---
|
||||||
if (clock.getElapsedTime().asMilliseconds() >= 500) {
|
if (clock.getElapsedTime().asMilliseconds() >= 500)
|
||||||
|
{
|
||||||
clock.restart();
|
clock.restart();
|
||||||
toggle = !toggle; // Change something every half second
|
toggle = !toggle; // Change something every half second
|
||||||
|
|
||||||
|
// Invert display every 4 seconds (8 cycles)
|
||||||
|
static int cycle_count = 0;
|
||||||
|
cycle_count++;
|
||||||
|
if (cycle_count % 8 == 0) {
|
||||||
|
renderer.invert_buffer();
|
||||||
|
inverted = !inverted;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear buffer
|
// Clear buffer
|
||||||
for (int i = 0; i < sizeof(bit_buffer); i++) bit_buffer[i] = 0;
|
for (int i = 0; i < sizeof(bit_buffer); i++)
|
||||||
|
bit_buffer[i] = 0;
|
||||||
|
|
||||||
// Draw content
|
// Showcase drawing functions
|
||||||
//draw_line(10, 10, 390, 290, true);
|
// Rectangles
|
||||||
draw_string_scaled(10, 10, "Hello World!", 3);
|
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<std::pair<int, int>> hex_points = {
|
||||||
|
{250, 130}, {280, 145}, {280, 175}, {250, 190}, {220, 175}, {220, 145}
|
||||||
|
};
|
||||||
|
renderer.draw_polygon(hex_points, true);
|
||||||
|
|
||||||
|
std::vector<std::pair<int, int>> 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);
|
||||||
|
|
||||||
|
// Demonstrate clipping - draw a circle that gets clipped
|
||||||
|
renderer.set_clip_rect(300, 200, 80, 60); // Small rectangle in bottom-right
|
||||||
|
renderer.draw_filled_circle(340, 230, 40, true); // Circle that extends outside clip rect
|
||||||
|
renderer.reset_clip_rect(); // Reset to full screen
|
||||||
|
|
||||||
|
// Text with different fonts
|
||||||
|
renderer.set_font(&font_acme_5_outlines);
|
||||||
|
renderer.draw_string_scaled(10, 10, inverted ? "INVERTED MODE" : "Drawing Demo", 2);
|
||||||
|
|
||||||
|
renderer.set_font(&font_5x5);
|
||||||
|
renderer.draw_string_scaled(10, 270, command_buffer, 1);
|
||||||
|
|
||||||
// Bridge: 1-bit to RGBA
|
// Bridge: 1-bit to RGBA
|
||||||
for (int i = 0; i < V_WIDTH * V_HEIGHT; ++i) {
|
for (int i = 0; i < V_WIDTH * V_HEIGHT; ++i)
|
||||||
|
{
|
||||||
bool is_on = (bit_buffer[i / 8] >> (7 - (i % 8))) & 1;
|
bool is_on = (bit_buffer[i / 8] >> (7 - (i % 8))) & 1;
|
||||||
int base = i * 4;
|
int base = i * 4;
|
||||||
sf::Uint8 color = is_on ? 0xFF : 0x00;
|
sf::Uint8 color = is_on ? 0xFF : 0x00;
|
||||||
display_pixels[base] = display_pixels[base+1] = display_pixels[base+2] = color;
|
display_pixels[base] = display_pixels[base + 1] = display_pixels[base + 2] = color;
|
||||||
display_pixels[base + 3] = 255;
|
display_pixels[base + 3] = 255;
|
||||||
}
|
}
|
||||||
texture.update(display_pixels.data());
|
texture.update(display_pixels.data());
|
||||||
@@ -176,6 +167,10 @@ int main() {
|
|||||||
window.clear();
|
window.clear();
|
||||||
window.draw(sprite);
|
window.draw(sprite);
|
||||||
window.display();
|
window.display();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore original terminal settings
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user