Update OLED api to use buffer class
This commit is contained in:
parent
8115ae1412
commit
10e9176d13
@ -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
|
||||
)
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
120
include/picoled/buffer.h
Normal 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
59
include/picoled/gfx/2D.h
Normal 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
63
src/2D.cpp
Normal 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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
0
src/buffer.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user