Compare commits

..

2 Commits

Author SHA1 Message Date
14c8280fc0 Add SSD1306 display driver
All checks were successful
continuous-integration/drone/push Build is passing
2021-05-15 21:36:19 +03:00
0d601f0fa1 Add Utility/pixelbuffers 2021-05-15 21:36:12 +03:00
5 changed files with 928 additions and 1 deletions

View File

@ -0,0 +1,255 @@
/*
* peripherals_ssd1036.hpp
*
* Created on: May 14, 2021
* Author: erki
*/
#ifndef SKULLC_PERIPHERALS_SSD1036_DISPLAY_HPP_
#define SKULLC_PERIPHERALS_SSD1036_DISPLAY_HPP_
#include <array>
#include <cstdint>
namespace Peripherals
{
struct SSD1306WriteAdapter
{
template<typename G, typename S>
struct Spi
{
using gpio = G;
using registers_handle = S;
gpio dc;
gpio cs;
registers_handle registers;
Spi() = delete;
Spi(const gpio& dc, const gpio& cs, const registers_handle& registers)
: dc(dc), cs(cs), registers(registers)
{}
/**
* Writes a command byte.
*
* When writing data into the command registers, the DC/RS pin must be set LOW.
*
* @param data The command byte to write.
*/
void writeCommand(std::uint8_t data)
{
dc.Set(false);
cs.Set(false);
registers.Transmit(&data, 1);
cs.Set(true);
}
void writeCommand(std::uint8_t command, std::uint8_t data)
{
dc.Set(false);
cs.Set(false);
registers.Transmit(&command, 1);
registers.Transmit(&data, 1);
cs.Set(true);
}
void writeData(std::uint8_t* data, const std::uint32_t len)
{
dc.Set(true);
cs.Set(false);
registers.Transmit(data, len);
cs.Set(true);
}
};
template<typename S>
struct I2c
{
// TODO: implement
};
};
/**
* A generalized version of the SSD1306 display. Works with both I2C and SPI interfaces.
*
* The data is held in a compressed manner, where each bit of a byte represents a single pixel.
* These bits are arrenged into a byte per column.
*
* Datasheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
*/
template<typename G, typename A, typename HAL, std::size_t W, std::size_t H>
class SSD1306Display
{
public:
using gpio = G;
using write_adapter = A;
using hal = HAL;
constexpr static std::size_t width = W;
constexpr static std::size_t height = H;
constexpr static std::size_t rows = height / 8;
SSD1306Display() = delete;
SSD1306Display(const gpio& reset, const write_adapter& registers)
: reset_(reset), registers_(registers)
{}
void setup()
{
hal::Delay(1);
reset_.Set(false);
hal::Delay(5);
reset_.Set(true);
hal::Delay(5);
// reference impl: https://gitlab.com/TTYRobotiklubi/micromouse/mousetrap2/-/blob/master/Src/Peripherals/lcd.c
//Set the display to sleep mode for the rest of the init.
registers_.writeCommand(Regs_::DISPLAY_OFF_YES_SLEEP);
//Set the clock speed, nominal ~105FPS
//Low nibble is divide ratio
//High level is oscillator frequency. Should be about 177 Hz.
registers_.writeCommand(Regs_::CLOCK_DIVIDE_PREFIX, 0x80);
//Set the multiplex ratio to 1/32
//Default is 0x3F (1/64 Duty), we need 0x1F (1/32 Duty)
registers_.writeCommand(Regs_::MULTIPLEX_RATIO_PREFIX, 0x1F);
//Set the display offset to 0 (default)
registers_.writeCommand(Regs_::DISPLAY_OFFSET_PREFIX, 0x00);
//Set the display RAM display start line to 0 (default)
//Bits 0-5 can be set to 0-63 with a bitwise or
registers_.writeCommand(Regs_::DISPLAY_START_LINE);
//Enable DC/DC converter, 7.5v
registers_.writeCommand(Regs_::DCDC_CONFIG_PRESET, Regs_::DCDC_CONFIG_7p5v);
//Map the columns correctly for our OLED glass layout.
registers_.writeCommand(Regs_::SEG0_IS_COL_0);
//Set COM output scan correctly for our OLED glass layout.
registers_.writeCommand(Regs_::SCAN_DIR_UP);
//Set COM pins correctly for our OLED glass layout
registers_.writeCommand(Regs_::COM_CONFIG_PREFIX, Regs_::COM_CONFIG_SEQUENTIAL_LEFT);
// Contrast.
registers_.writeCommand(Regs_::CONTRAST_PREFIX, 0xBF);
// Set precharge (low nibble) / discharge (high nibble) timing.
// precharge = 1 clock
// discharge = 15 clocks
registers_.writeCommand(Regs_::PRECHARGE_PERIOD_PREFIX, 0xF1);
// Set VCOM deselect level.
registers_.writeCommand(Regs_::VCOMH_DESELECT_PREFIX, Regs_::VCOMH_DESELECT_0p83xVCC);
// Set display to normal.
registers_.writeCommand(Regs_::ENTIRE_DISPLAY_NORMAL);
// Uninvert the display.
registers_.writeCommand(Regs_::INVERSION_NORMAL);
// Unset sleep mode.
registers_.writeCommand(Regs_::DISPLAY_ON_NO_SLEEP);
}
void blank(const bool black)
{
screen_buffer_.fill(black ? 0xFF : 0x00);
}
void drawBuffer()
{
for (std::uint32_t row = 0; row < rows; row++)
{
registers_.writeCommand(0xB0 + row);
registers_.writeCommand(0x00);
registers_.writeCommand(0x10);
registers_.writeData(&screen_buffer_[width * row], width);
}
}
template<typename PB>
void copyPixelBuffer(const PB& buffer)
{
static_assert(PB::width == width, "Pixel buffer's width doesn't match.");
static_assert(PB::height == height, "Pixel buffer's height doesn't match.");
static_assert(std::is_same<typename PB::value, std::uint8_t>::value, "Pixel buffer's value type does not match.");
for (std::uint32_t row = 0; row < rows; row++)
{
for (std::uint32_t col = 0; col < width; col++)
{
std::uint8_t& byte = screen_buffer_[width * row + col];
byte = 0;
for (std::uint32_t bit = 0; bit < 8; bit++)
{
if (buffer.at(col, 8 * row + bit) != 0)
{
byte |= (1 << bit);
}
}
}
}
}
private:
gpio reset_;
write_adapter registers_;
std::array<std::uint8_t, width * rows> screen_buffer_;
struct Regs_
{
static constexpr std::uint8_t DCDC_CONFIG_PRESET = 0x8D;
static constexpr std::uint8_t DCDC_CONFIG_7p5v = 0x14;
static constexpr std::uint8_t DCDC_CONFIG_6p0v = 0x15;
static constexpr std::uint8_t DCDC_CONFIG_8p5v = 0x94;
static constexpr std::uint8_t DCDC_CONFIG_9p0v = 0x95;
static constexpr std::uint8_t DISPLAY_OFF_YES_SLEEP = 0xAE;
static constexpr std::uint8_t DISPLAY_ON_NO_SLEEP = 0xAF;
static constexpr std::uint8_t CLOCK_DIVIDE_PREFIX = 0xD5;
static constexpr std::uint8_t MULTIPLEX_RATIO_PREFIX = 0xA8;
static constexpr std::uint8_t DISPLAY_OFFSET_PREFIX = 0xD3;
static constexpr std::uint8_t DISPLAY_START_LINE = 0x40;
static constexpr std::uint8_t SEG0_IS_COL_0 = 0xA0;
static constexpr std::uint8_t SEG0_IS_COL_127 = 0xA1;
static constexpr std::uint8_t SCAN_DIR_UP = 0xC0;
static constexpr std::uint8_t SCAN_DIR_DOWN = 0xC8;
static constexpr std::uint8_t COM_CONFIG_PREFIX = 0xDA;
static constexpr std::uint8_t COM_CONFIG_SEQUENTIAL_LEFT = 0x02;
static constexpr std::uint8_t COM_CONFIG_ALTERNATE_LEFT = 0x12;
static constexpr std::uint8_t COM_CONFIG_SEQUENTIAL_RIGHT = 0x22;
static constexpr std::uint8_t COM_CONFIG_ALTERNATE_RIGHT = 0x32;
static constexpr std::uint8_t CONTRAST_PREFIX = 0x81;
static constexpr std::uint8_t PRECHARGE_PERIOD_PREFIX = 0xD9;
static constexpr std::uint8_t VCOMH_DESELECT_PREFIX = 0xDB;
static constexpr std::uint8_t VCOMH_DESELECT_0p65xVCC = 0x00;
static constexpr std::uint8_t VCOMH_DESELECT_0p71xVCC = 0x10;
static constexpr std::uint8_t VCOMH_DESELECT_0p77xVCC = 0x20;
static constexpr std::uint8_t VCOMH_DESELECT_0p83xVCC = 0x30;
static constexpr std::uint8_t ENTIRE_DISPLAY_FORCE_ON = 0xA5;
static constexpr std::uint8_t ENTIRE_DISPLAY_NORMAL = 0xA4;
static constexpr std::uint8_t INVERSION_NORMAL = 0xA6;
static constexpr std::uint8_t INVERSION_INVERTED = 0xA7;
};
};
}// namespace Peripherals
#endif /* SKULLC_PERIPHERALS_SSD1036_DISPLAY_HPP_ */

View File

@ -16,7 +16,8 @@ add_executable(tests
parser.cpp parser.cpp
button.cpp button.cpp
rand.cpp rand.cpp
fixedpoint.cpp) fixedpoint.cpp
pixelbuffer.cpp)
target_link_libraries(tests target_link_libraries(tests
PUBLIC PUBLIC

250
Tests/pixelbuffer.cpp Normal file
View File

@ -0,0 +1,250 @@
//
// Created by erki on 13.05.21.
//
#include "utility_fonts_5x7.hpp"
#include "utility_pixelbuffer.hpp"
#include <catch2/catch.hpp>
#define TEST_WIDGET "[utility],[pixelbuffer]"
using PixelBuffer = Utility::PixelBuffer<10, 15>;
using compare_array = std::array<std::uint8_t, 10 * 15>;
using Font = Utility::Font5x7;
TEST_CASE("Pixelbuffer fill fills the array with data.", TEST_WIDGET)
{
SECTION("Fill works as expected")
{
PixelBuffer pb;
pb.fill(100);
for (const auto& px : pb)
{
REQUIRE(px == 100);
}
}
SECTION("Default pixelbuffer is zero-filled.")
{
PixelBuffer pb;
for (const auto& px : pb)
{
REQUIRE(px == 0);
}
}
SECTION("Filled constructor fills the array.")
{
PixelBuffer pb(200);
for (const auto& px : pb)
{
REQUIRE(px == 200);
}
}
}
TEST_CASE("Coordinate access retreives the appropriate pixels.", TEST_WIDGET)
{
PixelBuffer pb(200);
const uint8_t* value_ptr = &pb.at(5, 2);
const uint8_t* aritm_ptr = &(*(pb.cbegin() + (5 + 2 * 10)));
REQUIRE(value_ptr == aritm_ptr);
const uint8_t* value_ptr_c = &pb.at({5, 2});
REQUIRE(value_ptr_c == aritm_ptr);
}
TEST_CASE("Pixelbuffer draws rectangle correctly.", TEST_WIDGET)
{
PixelBuffer pb;
pb.rectangle({0, 0}, {5, 10}, 10);
// clang-format off
compare_array etalon = {
10, 10, 10, 10, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 0, 0, 0, 10, 0, 0, 0, 0, 0,
10, 10, 10, 10, 10, 0, 0, 0, 0,
0
};
// clang-format on
REQUIRE(pb.data() == etalon);
}
TEST_CASE("Pixelbuffer draws line correctly.", TEST_WIDGET)
{
PixelBuffer pb;
SECTION("Straight line on X axis is correct.")
{
pb.line({0, 0}, {3, 0}, 10);
compare_array etalon = {
10, 10, 10};
REQUIRE(pb.data() == etalon);
}
SECTION("Straight line on Y axis is correct.")
{
pb.line({0, 0}, {0, 3}, 10);
// clang-format off
compare_array etalon = {
10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10
};
// clang-format on
REQUIRE(pb.data() == etalon);
}
SECTION("Straight line on X axis is correct with reversed coordinates.")
{
pb.line({3, 0}, {0, 0}, 10);
compare_array etalon = {
10, 10, 10};
REQUIRE(pb.data() == etalon);
}
SECTION("Straight line on Y axis is correct with reversed coordinates.")
{
pb.line({0, 3}, {0, 0}, 10);
// clang-format off
compare_array etalon = {
10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10
};
// clang-format on
REQUIRE(pb.data() == etalon);
}
SECTION("Diagonal line is correct.")
{
pb.line({0, 0}, {4, 4}, 10);
// clang-format off
compare_array etalon = {
10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 10, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 10, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
// clang-format on
REQUIRE(pb.data() == etalon);
}
SECTION("Diagonal line is correct if points are reversed.")
{
pb.line({4, 4}, {0, 0}, 10);
// clang-format off
compare_array etalon = {
10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 10, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 10, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
// clang-format on
REQUIRE(pb.data() == etalon);
}
}
TEST_CASE("Pixelbuffer draws circle properly.", TEST_WIDGET)
{
PixelBuffer pb;
pb.circle({5, 5}, 2, 10);
// clang-format off
compare_array etalon = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 10, 10, 10, 0, 0, 0,
0, 0, 0, 10, 0, 0, 0, 10, 0, 0,
0, 0, 0, 10, 0, 0, 0, 10, 0, 0,
0, 0, 0, 10, 0, 0, 0, 10, 0, 0,
0, 0, 0, 0, 10, 10, 10, 0, 0, 0
};
// clang-format on
REQUIRE(pb.data() == etalon);
}
TEST_CASE("Pixelbuffer outputs fonts correctly.", TEST_WIDGET)
{
PixelBuffer pb;
pb.text<Font>("!", {0, 0}, 0xFF);
// clang-format off
compare_array etalon = {
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
// clang-format on
REQUIRE(pb.data() == etalon);
}
TEST_CASE("Pixelbuffer cuts off fonts when required.", TEST_WIDGET)
{
PixelBuffer pb;
pb.text<Font>("!", {0, 13}, 0xFF);
// clang-format off
compare_array etalon = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0, 0, 0, 0, 0, 0, 0
};
// clang-format on
REQUIRE(pb.data() == etalon);
}

View File

@ -0,0 +1,176 @@
/*
* utility_fonts_5x7.hpp
*
* Created on: May 15, 2021
* Author: erki
*/
#ifndef SKULLC_UTILITY_FONTS_5X7_HPP_
#define SKULLC_UTILITY_FONTS_5X7_HPP_
#include <array>
#include <cstdint>
#include <tuple>
namespace Utility
{
struct Font5x7
{
constexpr static std::size_t width = 5;
constexpr static std::size_t height = 7;
constexpr static std::size_t bytes_per_column = 1;
// standard ascii 5x7 font
// defines ascii characters 0x00-0x7F (0-127)
// LSByte is the first column, MSByte is last column.
// The LSbit of a byte is the top row.
static constexpr std::array<std::uint8_t, 128 * 5> data = {
0x00, 0x00, 0x00, 0x00, 0x00,// 0x00 (nul)
0x3E, 0x5B, 0x4F, 0x5B, 0x3E,// 0x01 (soh)
0x3E, 0x6B, 0x4F, 0x6B, 0x3E,// 0x02 (stx)
0x1C, 0x3E, 0x7C, 0x3E, 0x1C,// 0x03 (etx)
0x18, 0x3C, 0x7E, 0x3C, 0x18,// 0x04 (eot)
0x1C, 0x57, 0x7D, 0x57, 0x1C,// 0x05 (enq)
0x1C, 0x5E, 0x7F, 0x5E, 0x1C,// 0x06 (ack)
0x00, 0x18, 0x3C, 0x18, 0x00,// 0x07 (bel)
0xFF, 0xE7, 0xC3, 0xE7, 0xFF,// 0x08 (bs)
0x00, 0x18, 0x24, 0x18, 0x00,// 0x09 (tab)
0xFF, 0xE7, 0xDB, 0xE7, 0xFF,// 0x0A (lf)
0x30, 0x48, 0x3A, 0x06, 0x0E,// 0x0B (vt)
0x26, 0x29, 0x79, 0x29, 0x26,// 0x0C (np)
0x40, 0x7F, 0x05, 0x05, 0x07,// 0x0D (cr)
0x40, 0x7F, 0x05, 0x25, 0x3F,// 0x0E (so)
0x5A, 0x3C, 0xE7, 0x3C, 0x5A,// 0x0F (si)
0x7F, 0x3E, 0x1C, 0x1C, 0x08,// 0x10 (dle)
0x08, 0x1C, 0x1C, 0x3E, 0x7F,// 0x11 (dc1)
0x14, 0x22, 0x7F, 0x22, 0x14,// 0x12 (dc2)
0x5F, 0x5F, 0x00, 0x5F, 0x5F,// 0x13 (dc3)
0x06, 0x09, 0x7F, 0x01, 0x7F,// 0x14 (dc4)
0x00, 0x66, 0x89, 0x95, 0x6A,// 0x15 (nak)
0x60, 0x60, 0x60, 0x60, 0x60,// 0x16 (syn)
0x94, 0xA2, 0xFF, 0xA2, 0x94,// 0x17 (etb)
0x08, 0x04, 0x7E, 0x04, 0x08,// 0x18 (can)
0x10, 0x20, 0x7E, 0x20, 0x10,// 0x19 (em)
0x08, 0x08, 0x2A, 0x1C, 0x08,// 0x1A (eof)
0x08, 0x1C, 0x2A, 0x08, 0x08,// 0x1B (esc)
0x1E, 0x10, 0x10, 0x10, 0x10,// 0x1C (fs)
0x0C, 0x1E, 0x0C, 0x1E, 0x0C,// 0x1D (gs)
0x30, 0x38, 0x3E, 0x38, 0x30,// 0x1E (rs)
0x06, 0x0E, 0x3E, 0x0E, 0x06,// 0x1F (us)
0x00, 0x00, 0x00, 0x00, 0x00,// 0x20
0x00, 0x00, 0x5F, 0x00, 0x00,// 0x21 !
0x00, 0x07, 0x00, 0x07, 0x00,// 0x22 "
0x14, 0x7F, 0x14, 0x7F, 0x14,// 0x23 #
0x24, 0x2A, 0x7F, 0x2A, 0x12,// 0x24 $
0x23, 0x13, 0x08, 0x64, 0x62,// 0x25 %
0x36, 0x49, 0x56, 0x20, 0x50,// 0x26 &
0x00, 0x08, 0x07, 0x03, 0x00,// 0x27 '
0x00, 0x1C, 0x22, 0x41, 0x00,// 0x28 (
0x00, 0x41, 0x22, 0x1C, 0x00,// 0x29 )
0x2A, 0x1C, 0x7F, 0x1C, 0x2A,// 0x2A *
0x08, 0x08, 0x3E, 0x08, 0x08,// 0x2B +
0x00, 0x80, 0x70, 0x30, 0x00,// 0x2C ,
0x08, 0x08, 0x08, 0x08, 0x08,// 0x2D -
0x00, 0x00, 0x60, 0x60, 0x00,// 0x2E .
0x20, 0x10, 0x08, 0x04, 0x02,// 0x2F /
0x3E, 0x51, 0x49, 0x45, 0x3E,// 0x30 0
0x00, 0x42, 0x7F, 0x40, 0x00,// 0x31 1
0x72, 0x49, 0x49, 0x49, 0x46,// 0x32 2
0x21, 0x41, 0x49, 0x4D, 0x33,// 0x33 3
0x18, 0x14, 0x12, 0x7F, 0x10,// 0x34 4
0x27, 0x45, 0x45, 0x45, 0x39,// 0x35 5
0x3C, 0x4A, 0x49, 0x49, 0x31,// 0x36 6
0x41, 0x21, 0x11, 0x09, 0x07,// 0x37 7
0x36, 0x49, 0x49, 0x49, 0x36,// 0x38 8
0x46, 0x49, 0x49, 0x29, 0x1E,// 0x39 9
0x00, 0x00, 0x14, 0x00, 0x00,// 0x3A :
0x00, 0x40, 0x34, 0x00, 0x00,// 0x3B ;
0x00, 0x08, 0x14, 0x22, 0x41,// 0x3C <
0x14, 0x14, 0x14, 0x14, 0x14,// 0x3D =
0x00, 0x41, 0x22, 0x14, 0x08,// 0x3E >
0x02, 0x01, 0x59, 0x09, 0x06,// 0x3F ?
0x3E, 0x41, 0x5D, 0x59, 0x4E,// 0x40 @
0x7C, 0x12, 0x11, 0x12, 0x7C,// 0x41 A
0x7F, 0x49, 0x49, 0x49, 0x36,// 0x42 B
0x3E, 0x41, 0x41, 0x41, 0x22,// 0x43 C
0x7F, 0x41, 0x41, 0x41, 0x3E,// 0x44 D
0x7F, 0x49, 0x49, 0x49, 0x41,// 0x45 E
0x7F, 0x09, 0x09, 0x09, 0x01,// 0x46 F
0x3E, 0x41, 0x41, 0x51, 0x73,// 0x47 G
0x7F, 0x08, 0x08, 0x08, 0x7F,// 0x48 H
0x00, 0x41, 0x7F, 0x41, 0x00,// 0x49 I
0x20, 0x40, 0x41, 0x3F, 0x01,// 0x4A J
0x7F, 0x08, 0x14, 0x22, 0x41,// 0x4B K
0x7F, 0x40, 0x40, 0x40, 0x40,// 0x4C L
0x7F, 0x02, 0x1C, 0x02, 0x7F,// 0x4D M
0x7F, 0x04, 0x08, 0x10, 0x7F,// 0x4E N
0x3E, 0x41, 0x41, 0x41, 0x3E,// 0x4F O
0x7F, 0x09, 0x09, 0x09, 0x06,// 0x50 P
0x3E, 0x41, 0x51, 0x21, 0x5E,// 0x51 Q
0x7F, 0x09, 0x19, 0x29, 0x46,// 0x52 R
0x26, 0x49, 0x49, 0x49, 0x32,// 0x53 S
0x03, 0x01, 0x7F, 0x01, 0x03,// 0x54 T
0x3F, 0x40, 0x40, 0x40, 0x3F,// 0x55 U
0x1F, 0x20, 0x40, 0x20, 0x1F,// 0x56 V
0x3F, 0x40, 0x38, 0x40, 0x3F,// 0x57 W
0x63, 0x14, 0x08, 0x14, 0x63,// 0x58 X
0x03, 0x04, 0x78, 0x04, 0x03,// 0x59 Y
0x61, 0x59, 0x49, 0x4D, 0x43,// 0x5A Z
0x00, 0x7F, 0x41, 0x41, 0x41,// 0x5B [
0x02, 0x04, 0x08, 0x10, 0x20,// 0x5C backslash
0x00, 0x41, 0x41, 0x41, 0x7F,// 0x5D ]
0x04, 0x02, 0x01, 0x02, 0x04,// 0x5E ^
0x40, 0x40, 0x40, 0x40, 0x40,// 0x5F _
0x00, 0x03, 0x07, 0x08, 0x00,// 0x60 `
0x20, 0x54, 0x54, 0x78, 0x40,// 0x61 a
0x7F, 0x28, 0x44, 0x44, 0x38,// 0x62 b
0x38, 0x44, 0x44, 0x44, 0x28,// 0x63 c
0x38, 0x44, 0x44, 0x28, 0x7F,// 0x64 d
0x38, 0x54, 0x54, 0x54, 0x18,// 0x65 e
0x00, 0x08, 0x7E, 0x09, 0x02,// 0x66 f
0x18, 0xA4, 0xA4, 0x9C, 0x78,// 0x67 g
0x7F, 0x08, 0x04, 0x04, 0x78,// 0x68 h
0x00, 0x44, 0x7D, 0x40, 0x00,// 0x69 i
0x20, 0x40, 0x40, 0x3D, 0x00,// 0x6A j
0x7F, 0x10, 0x28, 0x44, 0x00,// 0x6B k
0x00, 0x41, 0x7F, 0x40, 0x00,// 0x6C l
0x7C, 0x04, 0x78, 0x04, 0x78,// 0x6D m
0x7C, 0x08, 0x04, 0x04, 0x78,// 0x6E n
0x38, 0x44, 0x44, 0x44, 0x38,// 0x6F o
0xFC, 0x18, 0x24, 0x24, 0x18,// 0x70 p
0x18, 0x24, 0x24, 0x18, 0xFC,// 0x71 q
0x7C, 0x08, 0x04, 0x04, 0x08,// 0x72 r
0x48, 0x54, 0x54, 0x54, 0x24,// 0x73 s
0x04, 0x04, 0x3F, 0x44, 0x24,// 0x74 t
0x3C, 0x40, 0x40, 0x20, 0x7C,// 0x75 u
0x1C, 0x20, 0x40, 0x20, 0x1C,// 0x76 v
0x3C, 0x40, 0x30, 0x40, 0x3C,// 0x77 w
0x44, 0x28, 0x10, 0x28, 0x44,// 0x78 x
0x4C, 0x90, 0x90, 0x90, 0x7C,// 0x79 y
0x44, 0x64, 0x54, 0x4C, 0x44,// 0x7A z
0x00, 0x08, 0x36, 0x41, 0x00,// 0x7B {
0x00, 0x00, 0x77, 0x00, 0x00,// 0x7C |
0x00, 0x41, 0x36, 0x08, 0x00,// 0x7D }
0x02, 0x01, 0x02, 0x04, 0x02,// 0x7E ~
0x3C, 0x26, 0x23, 0x26, 0x3C,// 0x7F
};
using const_iterator = decltype(data)::const_iterator;
static constexpr std::uint32_t bytesPerChar()
{
return sizeof(data) / 128;
}
static constexpr std::pair<const_iterator, const_iterator> getCharacterBytes(const char& c)
{
const_iterator it = data.cbegin() + ((c % 128) * 5);
return {it, it + 5};
}
};
}// namespace Utility
#endif /* SKULLC_UTILITY_FONTS_5X7_HPP_ */

View File

@ -0,0 +1,245 @@
//
// Created by erki on 13.05.21.
//
#ifndef SKULLC_UTILITY_PIXELBUFFER_HPP
#define SKULLC_UTILITY_PIXELBUFFER_HPP
#include <array>
#include <cmath>
#include <cstdint>
#include <string_view>
namespace Utility
{
template<std::size_t W, std::size_t H>
class PixelBuffer
{
public:
static constexpr std::size_t width = W;
static constexpr std::size_t height = H;
using value = std::uint8_t;
using container = std::array<value, width * height>;
using iterator = typename container::iterator;
using const_iterator = typename container::const_iterator;
using coordinates = std::pair<std::size_t, std::size_t>;
PixelBuffer()
: data_({0})
{}
explicit PixelBuffer(const value& fill_value)
: data_()
{
fill(fill_value);
}
constexpr value& at(const std::size_t& x, const std::size_t& y)
{
return data_[x + ((y) *width)];
}
constexpr const value& at(const std::size_t& x, const std::size_t& y) const
{
return data_[x + ((y) *width)];
}
constexpr value& at(const coordinates& c)
{
return at(x_(c), y_(c));
}
constexpr const value& at(const coordinates& c) const
{
return at(x_(c), y_(c));
}
const container& data() const
{
return data_;
}
const value* ptr() const
{
return data_.data();
}
iterator begin()
{
return data_.begin();
}
iterator end()
{
return data_.end();
}
const_iterator cbegin() const
{
return data_.cbegin();
}
const_iterator cend() const
{
return data_.cend();
}
void fill(const value& color)
{
data_.fill(color);
}
/**
* Draws the text in the font provided onto the pixel buffer.
*
* Currently only supports fonts which are 1 byte high at most.
*
* @todo: make the function work with multi-byte fonts.
*
* @tparam Font The font object to be used. Must have the following members:
* * height, indicating how many pixels tall the font is.
* * width, indicating how many pixels wide the font is.
* * getCharacterBytes(char) that returns start and stop iterators for a given character.
*/
template<typename Font>
void text(const std::string_view& text, const coordinates& start, const value& color, const value& background = 0x00)
{
std::uint32_t x = x_(start);
const std::uint32_t y = y_(start);
for (const char& c : text)
{
const auto [c_start, c_stop] = Font::getCharacterBytes(c);
for (auto it = c_start; it != c_stop; it++, x++)
{
const std::uint8_t& column_byte = *it;
if (x >= width)
return;
for (std::uint32_t i = 0; i < Font::height && i + y < height; i++)
{
at(x, y + i) = column_byte & (1 << i) ? color : background;
}
}
x++;
}
}
void rectangle(const coordinates& top_left, const coordinates& size, const value& color)
{
for (auto i = x_(top_left); i < (x_(top_left) + x_(size)); i++)
{
at(i, y_(top_left)) = color;
at(i, y_(top_left) + y_(size) - 1) = color;
}
for (auto i = y_(top_left); i < (y_(top_left) + y_(size)); i++)
{
at(x_(top_left), i) = color;
at(x_(top_left) + x_(size) - 1, i) = color;
}
}
void line(const coordinates& p1, const coordinates& p2, const value& color)
{
const coordinates start = {std::min(x_(p1), x_(p2)), std::min(y_(p1), y_(p2))};
const coordinates stop = {std::max(x_(p1), x_(p2)), std::max(y_(p1), y_(p2))};
const auto dx = std::int32_t(x_(stop)) - std::int32_t(x_(start));
const auto dy = std::int32_t(y_(stop)) - std::int32_t(y_(start));
auto x = std::int32_t(x_(start));
auto y = std::int32_t(y_(start));
// We shortcut the direct lines, as Bresenham's algorithm cannot handle them.
if (dx == 0 && dy == 0)
{
at(start) = color;
} else if (dx == 0)
{
for (; y < y_(stop); y++)
at({x, y}) = color;
} else if (dy == 0)
{
for (; x < x_(stop); x++)
at({x, y}) = color;
} else
{
// Bresenham's algorithm.
std::int32_t p = 2 * dy - dx;
while (x < x_(stop))
{
at({x, y}) = color;
if (p >= 0)
{
y++;
p = p + 2 * dy - 2 * dx;
} else
{
p = p + 2 * dy;
}
x++;
}
}
}
void circle(const coordinates& center, const std::size_t radius, const value& color)
{
auto draw_pixels = [this, color, center](const std::size_t& x, const std::size_t& y) {
at(x_(center) + x, y_(center) + y) = color;
at(x_(center) - x, y_(center) + y) = color;
at(x_(center) + x, y_(center) - y) = color;
at(x_(center) - x, y_(center) - y) = color;
at(x_(center) + y, y_(center) + x) = color;
at(x_(center) - y, y_(center) + x) = color;
at(x_(center) + y, y_(center) - x) = color;
at(x_(center) - y, y_(center) - x) = color;
};
auto x = std::int32_t(0);
auto y = std::int32_t(radius);
auto d = 3 - 2 * std::int32_t(radius);
draw_pixels(x, y);
while (y >= x)
{
x++;
if (d > 0)
{
y--;
d = d + 4 * (x - y) + 10;
} else
{
d = d + 4 * x + 6;
}
draw_pixels(x, y);
}
}
private:
container data_;
constexpr const std::size_t& x_(const coordinates& c) const
{
return c.first;
}
constexpr const std::size_t& y_(const coordinates& c) const
{
return c.second;
}
};
}// namespace Utility
#endif//SKULLC_UTILITY_PIXELBUFFER_HPP