diff --git a/CMakeLists.txt b/CMakeLists.txt index ebdeb2a..29a9e9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,13 @@ cmake_minimum_required(VERSION 3.13) add_library(picoled include/picoled.h + include/picoled/buffer.h + include/picoled/gfx/2D.h include/picoled/SSD1306.h src/picoled.cpp + src/buffer.cpp + src/2D.cpp src/SSD1306.cpp ) diff --git a/include/picoled.h b/include/picoled.h index d3ed0e5..baf539e 100644 --- a/include/picoled.h +++ b/include/picoled.h @@ -1,33 +1,52 @@ #ifndef PICOLED_H #define PICOLED_H -#include +#include "picoled/buffer.h" + #include namespace picoled { +// rgb565 class pixel { - friend class oled; public: pixel() : pixel(0) {} - pixel(uint8_t value) : - value(value) + pixel(uint8_t grayscale) : + pixel(grayscale, grayscale, grayscale) {} + pixel(uint8_t r, uint8_t g, uint8_t b) : + r(r), + g(g), + b(b) + {} + + uint8_t red() const { return r; } + uint8_t green() const { return g; } + uint8_t blue() const { return b; } + + uint8_t grayscale() const + { + uint16_t unscaled = red() + green() + blue(); + return unscaled / 3; + } + private: - uint8_t value; + uint8_t r; + uint8_t g; + uint8_t b; }; class oled { public: oled(size_t width, size_t height); - virtual ~oled(); + virtual ~oled() = default; oled(const oled&) = delete; oled(oled&&) = delete; @@ -35,26 +54,27 @@ public: oled& operator=(const oled&) = delete; oled& operator=(oled&&) = delete; - pixel& operator()(int x, int y); - const pixel& operator()(int x, int y) const; - void clear(); - size_t get_width() const { return width; } - size_t get_height() const { return height; } + size_t get_width() const { return buffer_a.get_width(); } + size_t get_height() const { return buffer_a.get_height(); } - void update() const; + void write(int x, int y, const pixel& p) { active_buffer().write(x, y, p); } + + buffer& active_buffer() { return active ? buffer_b : buffer_a; } + + void update(); + void swap_buffers(); protected: - virtual void update_impl() const = 0; + virtual void update_impl() = 0; - bool is_on(int x, int y) const; - - size_t width; - size_t height; + buffer& inactive_buffer() { return active ? buffer_a : buffer_b; } private: - pixel* pixels; + bool active; + buffer buffer_a; + buffer buffer_b; }; } diff --git a/include/picoled/SSD1306.h b/include/picoled/SSD1306.h index d991ba6..fa25d08 100644 --- a/include/picoled/SSD1306.h +++ b/include/picoled/SSD1306.h @@ -17,7 +17,7 @@ public: virtual ~ssd1306(); protected: - void update_impl() const override; + void update_impl() override; private: void command(uint8_t cmd) const; diff --git a/include/picoled/buffer.h b/include/picoled/buffer.h new file mode 100644 index 0000000..4ad04ea --- /dev/null +++ b/include/picoled/buffer.h @@ -0,0 +1,120 @@ +#ifndef PICOLED_BUFFER_H +#define PICOLED_BUFFER_H + +#include + +namespace picoled +{ + +template +class buffer +{ +public: + // behavior when out of bounds + enum class ReadMode + { + Default, // use default constructor for T + Wrap, // use modular arithmetic to put back in bounds + Clamp // pick from the corresponding edge + }; + + buffer(size_t width, size_t height) : + width(width), + height(height), + data(new T[width * height]) + {} + ~buffer() + { + delete[] data; + } + + buffer(const buffer&) = delete; + buffer(buffer&&) = delete; + + buffer& operator=(const buffer&) = delete; + buffer& operator=(buffer&&) = delete; + + size_t get_width() const { return width; } + size_t get_height() const { return height; } + + void write(int x, int y, const T& pixel) + { + if (x < 0 || x >= width || y < 0 || y >= height) { return; } + + data[x + y * width] = pixel; + } + + T read(int x, int y, ReadMode mode = ReadMode::Default) + { + switch (mode) + { + case ReadMode::Default: + return read_default(x, y); + + case ReadMode::Wrap: + return read_wrap(x, y); + + case ReadMode::Clamp: + return read_clamp(x, y); + } + + return T(); + } + + void clear() + { + for (int i = 0; i < width * height; i++) + { + data[i] = T(); + } + } + +private: + size_t width; + size_t height; + T* data; + + T read_default(int x, int y) + { + if (x < 0 || x >= width || y < 0 || y >= height) { return T(); } + return data[x + y * width]; + } + + T read_wrap(int x, int y) + { + if (x < 0) + { + x = width - ((-x) % width); + } + else if (x >= width) + { + x = x % width; + } + + if (y < 0) + { + y = height - ((-y) % height); + } + else if (y >= height) + { + y = y % height; + } + + return data[x + y * width]; + } + + T read_clamp(int x, int y) + { + if (x < 0) { x = 0; } + else if (x >= width) { x = width - 1; } + + if (y < 0) { y = 0; } + else if (y >= height) { y = height - 1; } + + return data[x + y * width]; + } +}; + +} + +#endif//PICOLED_BUFFER_H diff --git a/include/picoled/gfx/2D.h b/include/picoled/gfx/2D.h new file mode 100644 index 0000000..fddf2dd --- /dev/null +++ b/include/picoled/gfx/2D.h @@ -0,0 +1,59 @@ +#ifndef PICOLED_GFX_2D_H +#define PICOLED_GFX_2D_H + +#include "picoled.h" + +namespace picoled::gfx +{ + +class vec2 +{ +public: + float x; + float y; + + vec2 operator+(const vec2& other) const { return { x + other.x, y + other.y }; } + vec2 operator-(const vec2& other) const { return { x - other.x, y - other.y }; } + vec2 operator*(const vec2& other) const { return { x * other.x, y * other.y }; } + vec2 operator/(const vec2& other) const { return { x / other.x, y / other.y }; } + + float operator%(const vec2& other) const { return x * other.x + y * other.y; } + float operator^(const vec2& other) const { return x * other.y - y * other.x; } +}; + +class triangle +{ +public: + vec2 a; + vec2 b; + vec2 c; + + triangle fix_winding() const; + bool inside(const vec2& p) const; + + vec2 min() const; + vec2 max() const; +}; + +class gfx2d +{ +public: + explicit gfx2d(oled& screen) : + screen(&screen) + {} + + gfx2d(const gfx2d&) = delete; + gfx2d(gfx2d&&) = delete; + + gfx2d& operator=(const gfx2d&) = delete; + gfx2d& operator=(gfx2d&&) = delete; + + void draw_triangle(triangle tri, pixel p = 0xFF, bool ignore_winding = true); + +private: + oled* screen; +}; + +} + +#endif//PICOLED_GFX_2D_H diff --git a/src/2D.cpp b/src/2D.cpp new file mode 100644 index 0000000..345ba0e --- /dev/null +++ b/src/2D.cpp @@ -0,0 +1,63 @@ +#include "picoled/gfx/2D.h" + +#include + +namespace picoled::gfx +{ + +triangle triangle::fix_winding() const +{ + if (((b - a) ^ (c - a)) < 0) + { + return { a, c, b }; + } + else + { + return { a, b, c }; + } +} + +bool triangle::inside(const vec2& p) const +{ + const float abp = (b - a) ^ (p - a); + const float bcp = (c - b) ^ (p - b); + const float cap = (a - c) ^ (p - c); + + return abp >= 0 && bcp >= 0 && cap >= 0; +} + +vec2 triangle::min() const +{ + return + { + fminf(fminf(a.x, b.x), c.x), + fminf(fminf(a.y, b.y), c.y) + }; +} + +vec2 triangle::max() const +{ + return + { + fmaxf(fmaxf(a.x, b.x), c.x), + fmaxf(fmaxf(a.y, b.y), c.y) + }; +} + +void gfx2d::draw_triangle(triangle tri, pixel p, bool ignore_winding) +{ + if (ignore_winding) { tri = tri.fix_winding(); } + + const vec2 min = tri.min(); + const vec2 max = tri.max(); + + for (float i = min.x; i <= max.x; i += 0.75f) + { + for (float j = min.y; j <= max.y; j += 0.75f) + { + if (tri.inside({ i, j })) { screen->write(i, j, p); } + } + } +} + +} diff --git a/src/SSD1306.cpp b/src/SSD1306.cpp index 0c26e8d..566dada 100644 --- a/src/SSD1306.cpp +++ b/src/SSD1306.cpp @@ -1,5 +1,6 @@ #include "picoled/SSD1306.h" +#include #include // https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf @@ -77,18 +78,18 @@ ssd1306::~ssd1306() delete[] buffer; } -void ssd1306::update_impl() const +void ssd1306::update_impl() { // clear display to zero - memset(&buffer[1], 0, width * height * sizeof(*buffer)); + memset(&buffer[1], 0, get_width() * get_height() * sizeof(*buffer)); - for (int x = 0; x < width; x++) + for (int x = 0; x < get_width(); x++) { - for (int y = 0; y < height; y++) + for (int y = 0; y < get_height(); y++) { - if (!is_on(x, y)) { continue; } + if (inactive_buffer().read(x, y).grayscale() <= rand() % 0xFF) { continue; } - const int index = (width - x - 1) + (y / 8) * width; + const int index = (get_width() - x - 1) + (y / 8) * get_width(); const uint8_t bits = 1 << (y & 7); // add one to leave room for 0x40 data indicator at beginning of array @@ -97,8 +98,8 @@ void ssd1306::update_impl() const } command(PAGE_ADDRESS); command(0); command(7); - command(COLUMN_ADDRESS); command(0); command(width - 1); - i2c_write_blocking(i2c, address, buffer, width * height / 8 + 1, false); + command(COLUMN_ADDRESS); command(0); command(get_width() - 1); + i2c_write_blocking(i2c, address, buffer, get_width() * get_height() / 8 + 1, false); } void ssd1306::command(uint8_t cmd) const diff --git a/src/buffer.cpp b/src/buffer.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/picoled.cpp b/src/picoled.cpp index c5cfe9f..8e99ef2 100644 --- a/src/picoled.cpp +++ b/src/picoled.cpp @@ -1,57 +1,27 @@ #include "picoled.h" -#include - -namespace -{ - -picoled::pixel DUMMY_PIXEL; - -} - namespace picoled { oled::oled(size_t width, size_t height) : - pixels(new pixel[width * height]), - width(width), - height(height) -{} + buffer_a(width, height), + buffer_b(width, height), + active(false) +{ } -oled::~oled() -{ - delete[] pixels; -} - - -pixel& oled::operator()(int x, int y) -{ - if (x < 0 || x >= width || y < 0 || y >= height) { return DUMMY_PIXEL; } - return pixels[x + y * width]; -} - -const pixel& oled::operator()(int x, int y) const -{ - if (x < 0 || x >= width || y < 0 || y >= height) { return DUMMY_PIXEL; } - return pixels[x + y * width]; -} - -void oled::update() const +void oled::update() { update_impl(); } -bool oled::is_on(int x, int y) const +void oled::swap_buffers() { - return (*this)(x, y).value > rand() % 0xFF; + active = !active; } void oled::clear() { - for (int i = 0; i < width * height; i++) - { - pixels[i] = false; - } + active_buffer().clear(); } }