Update OLED api to use buffer class

This commit is contained in:
shylie 2025-06-08 09:32:35 -04:00
parent 8115ae1412
commit 10e9176d13
9 changed files with 302 additions and 65 deletions

View File

@ -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
)

View File

@ -1,33 +1,52 @@
#ifndef PICOLED_H
#define PICOLED_H
#include <cstddef>
#include "picoled/buffer.h"
#include <cstdint>
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<pixel>& 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<pixel>& inactive_buffer() { return active ? buffer_a : buffer_b; }
private:
pixel* pixels;
bool active;
buffer<pixel> buffer_a;
buffer<pixel> buffer_b;
};
}

View File

@ -17,7 +17,7 @@ public:
virtual ~ssd1306();
protected:
void update_impl() const override;
void update_impl() override;
private:
void command(uint8_t cmd) const;

120
include/picoled/buffer.h Normal file
View File

@ -0,0 +1,120 @@
#ifndef PICOLED_BUFFER_H
#define PICOLED_BUFFER_H
#include <cstddef>
namespace picoled
{
template <typename T>
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

59
include/picoled/gfx/2D.h Normal file
View File

@ -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

63
src/2D.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "picoled/gfx/2D.h"
#include <cmath>
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); }
}
}
}
}

View File

@ -1,5 +1,6 @@
#include "picoled/SSD1306.h"
#include <cstdlib>
#include <cstring>
// 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

0
src/buffer.cpp Normal file
View File

View File

@ -1,57 +1,27 @@
#include "picoled.h"
#include <cstdlib>
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();
}
}