pimoroni-pico/libraries/pico_graphics/pico_graphics.cpp

335 lines
9.7 KiB
C++

#include "pico_graphics.hpp"
namespace pimoroni {
PicoGraphics::PicoGraphics(uint16_t width, uint16_t height, uint16_t *frame_buffer)
: frame_buffer(frame_buffer), bounds(0, 0, width, height), clip(0, 0, width, height) {
set_font(&font6);
};
void PicoGraphics::set_font(const Font *font){
this->font = font;
}
void PicoGraphics::set_pen(uint8_t r, uint8_t g, uint8_t b) {
pen = create_pen(r, g, b);
}
void PicoGraphics::set_pen(Pen p) {
pen = p;
}
void PicoGraphics::set_clip(const Rect &r) {
clip = bounds.intersection(r);
}
void PicoGraphics::remove_clip() {
clip = bounds;
}
Pen* PicoGraphics::ptr(const Rect &r) {
return frame_buffer + r.x + r.y * bounds.w;
}
Pen* PicoGraphics::ptr(const Point &p) {
return frame_buffer + p.x + p.y * bounds.w;
}
Pen* PicoGraphics::ptr(int32_t x, int32_t y) {
return frame_buffer + x + y * bounds.w;
}
void PicoGraphics::clear() {
rectangle(clip);
}
void PicoGraphics::pixel(const Point &p) {
if(!clip.contains(p)) return;
*ptr(p) = pen;
}
void PicoGraphics::pixel_span(const Point &p, int32_t l) {
// check if span in bounds
if( p.x + l < clip.x || p.x >= clip.x + clip.w ||
p.y < clip.y || p.y >= clip.y + clip.h) return;
// clamp span horizontally
Point clipped = p;
if(clipped.x < clip.x) {l += clipped.x - clip.x; clipped.x = clip.x;}
if(clipped.x + l >= clip.x + clip.w) {l = clip.x + clip.w - clipped.x;}
Pen *dest = ptr(clipped);
while(l--) {
*dest++ = pen;
}
}
void PicoGraphics::rectangle(const Rect &r) {
// clip and/or discard depending on rectangle visibility
Rect clipped = r.intersection(clip);
if(clipped.empty()) return;
Pen *dest = ptr(clipped);
while(clipped.h--) {
// draw span of pixels for this row
for(int32_t i = 0; i < clipped.w; i++) {
*dest++ = pen;
}
// move to next scanline
dest += bounds.w - clipped.w;
}
}
void PicoGraphics::circle(const Point &p, int32_t radius) {
// circle in screen bounds?
Rect bounds = Rect(p.x - radius, p.y - radius, radius * 2, radius * 2);
if(!bounds.intersects(clip)) return;
int ox = radius, oy = 0, err = -radius;
while (ox >= oy)
{
int last_oy = oy;
err += oy; oy++; err += oy;
pixel_span(Point(p.x - ox, p.y + last_oy), ox * 2 + 1);
if (last_oy != 0) {
pixel_span(Point(p.x - ox, p.y - last_oy), ox * 2 + 1);
}
if(err >= 0 && ox != last_oy) {
pixel_span(Point(p.x - last_oy, p.y + ox), last_oy * 2 + 1);
if (ox != 0) {
pixel_span(Point(p.x - last_oy, p.y - ox), last_oy * 2 + 1);
}
err -= ox; ox--; err -= ox;
}
}
}
void PicoGraphics::character(const char c, const Point &p, uint8_t scale) {
uint8_t char_index = c - 32;
Rect char_bounds(p.x, p.y, font->widths[char_index] * scale, font->height * scale);
if(!clip.intersects(char_bounds)) return;
const uint8_t *d = &font->data[char_index * font->max_width];
for(uint8_t cx = 0; cx < font->widths[char_index]; cx++) {
for(uint8_t cy = 0; cy < font->height; cy++) {
if((1U << cy) & *d) {
rectangle(Rect(p.x + (cx * scale), p.y + (cy * scale), scale, scale));
}
}
d++;
}
}
void PicoGraphics::text(const std::string &t, const Point &p, int32_t wrap, uint8_t scale) {
uint32_t co = 0, lo = 0; // character and line (if wrapping) offset
size_t i = 0;
while(i < t.length()) {
// find length of current word
size_t next_space = t.find(' ', i + 1);
if(next_space == std::string::npos) {
next_space = t.length();
}
uint16_t word_width = 0;
for(size_t j = i; j < next_space; j++) {
word_width += font->widths[t[j] - 32] * scale;
}
// if this word would exceed the wrap limit then
// move to the next line
if(co != 0 && co + word_width > (uint32_t)wrap) {
co = 0;
lo += (font->height + 1) * scale;
}
// draw word
for(size_t j = i; j < next_space; j++) {
character(t[j], Point(p.x + co, p.y + lo), scale);
co += font->widths[t[j] - 32] * scale;
}
// move character offset to end of word and add a space
co += font->widths[0] * scale;
i = next_space + 1;
}
}
int32_t orient2d(Point p1, Point p2, Point p3) {
return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
}
bool is_top_left(const Point &p1, const Point &p2) {
return (p1.y == p2.y && p1.x > p2.x) || (p1.y < p2.y);
}
void PicoGraphics::triangle(Point p1, Point p2, Point p3) {
Rect triangle_bounds(
Point(std::min(p1.x, std::min(p2.x, p3.x)), std::min(p1.y, std::min(p2.y, p3.y))),
Point(std::max(p1.x, std::max(p2.x, p3.x)), std::max(p1.y, std::max(p2.y, p3.y))));
// clip extremes to frame buffer size
triangle_bounds = clip.intersection(triangle_bounds);
// if triangle completely out of bounds then don't bother!
if (triangle_bounds.empty()) {
return;
}
// fix "winding" of vertices if needed
int32_t winding = orient2d(p1, p2, p3);
if (winding < 0) {
Point t;
t = p1; p1 = p3; p3 = t;
}
// bias ensures no overdraw between neighbouring triangles
int8_t bias0 = is_top_left(p2, p3) ? 0 : -1;
int8_t bias1 = is_top_left(p3, p1) ? 0 : -1;
int8_t bias2 = is_top_left(p1, p2) ? 0 : -1;
int32_t a01 = p1.y - p2.y;
int32_t b01 = p2.x - p1.x;
int32_t a12 = p2.y - p3.y;
int32_t b12 = p3.x - p2.x;
int32_t a20 = p3.y - p1.y;
int32_t b20 = p1.x - p3.x;
Point tl(triangle_bounds.x, triangle_bounds.y);
int32_t w0row = orient2d(p2, p3, tl) + bias0;
int32_t w1row = orient2d(p3, p1, tl) + bias1;
int32_t w2row = orient2d(p1, p2, tl) + bias2;
for (int32_t y = 0; y < triangle_bounds.h; y++) {
int32_t w0 = w0row;
int32_t w1 = w1row;
int32_t w2 = w2row;
Pen *dest = ptr(triangle_bounds.x, triangle_bounds.y + y);
for (int32_t x = 0; x < triangle_bounds.w; x++) {
if ((w0 | w1 | w2) >= 0) {
*dest = pen;
}
dest++;
w0 += a12;
w1 += a20;
w2 += a01;
}
w0row += b12;
w1row += b20;
w2row += b01;
}
}
void PicoGraphics::polygon(const std::vector<Point> &points) {
static int32_t nodes[64]; // maximum allowed number of nodes per scanline for polygon rendering
int32_t miny = points[0].y, maxy = points[0].y;
for (uint16_t i = 1; i < points.size(); i++) {
miny = std::min(miny, points[i].y);
maxy = std::max(maxy, points[i].y);
}
// for each scanline within the polygon bounds (clipped to clip rect)
Point p;
for (p.y = std::max(clip.y, miny); p.y <= std::min(clip.y + clip.h, maxy); p.y++) {
uint8_t n = 0;
for (uint16_t i = 0; i < points.size(); i++) {
uint16_t j = (i + 1) % points.size();
int32_t sy = points[i].y;
int32_t ey = points[j].y;
int32_t fy = p.y;
if ((sy < fy && ey >= fy) || (ey < fy && sy >= fy)) {
int32_t sx = points[i].x;
int32_t ex = points[j].x;
int32_t px = int32_t(sx + float(fy - sy) / float(ey - sy) * float(ex - sx));
nodes[n++] = px < clip.x ? clip.x : (px >= clip.x + clip.w ? clip.x + clip.w - 1 : px);// clamp(int32_t(sx + float(fy - sy) / float(ey - sy) * float(ex - sx)), clip.x, clip.x + clip.w);
}
}
uint16_t i = 0;
while (i < n - 1) {
if (nodes[i] > nodes[i + 1]) {
int32_t s = nodes[i]; nodes[i] = nodes[i + 1]; nodes[i + 1] = s;
if (i) i--;
}
else {
i++;
}
}
for (uint16_t i = 0; i < n; i += 2) {
pixel_span(Point(nodes[i], p.y), nodes[i + 1] - nodes[i] + 1);
}
}
}
void PicoGraphics::line(Point p1, Point p2) {
// fast horizontal line
if(p1.y == p2.y) {
int32_t start = std::max(clip.x, std::min(p1.x, p2.x));
int32_t end = std::min(clip.x + clip.w, std::max(p1.x, p2.x));
pixel_span(Point(start, p1.y), end - start);
return;
}
// fast vertical line
if(p1.x == p2.x) {
int32_t start = std::max(clip.y, std::min(p1.y, p2.y));
int32_t length = std::min(clip.y + clip.h, std::max(p1.y, p2.y)) - start;
Pen *dest = ptr(p1.x, start);
while(length--) {
*dest = pen;
dest += bounds.w;
}
return;
}
// general purpose line
// lines are either "shallow" or "steep" based on whether the x delta
// is greater than the y delta
int32_t dx = p2.x - p1.x;
int32_t dy = p2.y - p1.y;
bool shallow = std::abs(dx) > std::abs(dy);
if(shallow) {
// shallow version
int32_t s = std::abs(dx); // number of steps
int32_t sx = dx < 0 ? -1 : 1; // x step value
int32_t sy = (dy << 16) / s; // y step value in fixed 16:16
int32_t x = p1.x;
int32_t y = p1.y << 16;
while(s--) {
pixel(Point(x, y >> 16));
y += sy;
x += sx;
}
}else{
// steep version
int32_t s = std::abs(dy); // number of steps
int32_t sy = dy < 0 ? -1 : 1; // y step value
int32_t sx = (dx << 16) / s; // x step value in fixed 16:16
int32_t y = p1.y;
int32_t x = p1.x << 16;
while(s--) {
pixel(Point(x >> 16, y));
y += sy;
x += sx;
}
}
}
}