2021-01-16 12:26:03 +00:00
|
|
|
#include "pico_graphics.hpp"
|
|
|
|
|
|
|
|
namespace pimoroni {
|
2021-02-23 17:07:03 +00:00
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::set_pen(uint c) {};
|
|
|
|
void PicoGraphics::set_pen(uint8_t r, uint8_t g, uint8_t b) {};
|
2022-06-08 16:37:09 +01:00
|
|
|
int PicoGraphics::update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {return -1;};
|
|
|
|
int PicoGraphics::reset_pen(uint8_t i) {return -1;};
|
2022-06-07 16:37:06 +01:00
|
|
|
int PicoGraphics::create_pen(uint8_t r, uint8_t g, uint8_t b) {return -1;};
|
2022-06-08 16:37:09 +01:00
|
|
|
void PicoGraphics::set_pixel(const Point &p) {};
|
2022-06-10 00:58:54 +01:00
|
|
|
void PicoGraphics::set_pixel_dither(const Point &p, const RGB &c) {};
|
2022-06-10 10:00:35 +01:00
|
|
|
void PicoGraphics::set_pixel_dither(const Point &p, const RGB565 &c) {};
|
2022-06-09 20:21:46 +01:00
|
|
|
void PicoGraphics::scanline_convert(PenType type, conversion_callback_func callback) {};
|
2022-06-14 13:20:27 +01:00
|
|
|
void PicoGraphics::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) {};
|
2022-06-07 16:37:06 +01:00
|
|
|
|
|
|
|
void PicoGraphics::set_dimensions(int width, int height) {
|
2022-06-08 13:23:53 +01:00
|
|
|
bounds = clip = {0, 0, width, height};
|
|
|
|
}
|
|
|
|
|
|
|
|
void PicoGraphics::set_framebuffer(void *frame_buffer) {
|
|
|
|
this->frame_buffer = frame_buffer;
|
2022-06-07 16:37:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void PicoGraphics::set_font(const bitmap::font_t *font){
|
2022-06-08 16:37:09 +01:00
|
|
|
this->bitmap_font = font;
|
|
|
|
this->hershey_font = nullptr;
|
2021-01-16 12:26:03 +00:00
|
|
|
}
|
|
|
|
|
2022-06-08 16:37:09 +01:00
|
|
|
void PicoGraphics::set_font(const hershey::font_t *font){
|
|
|
|
this->bitmap_font = nullptr;
|
|
|
|
this->hershey_font = font;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PicoGraphics::set_font(std::string name){
|
|
|
|
if (name == "bitmap6") {
|
|
|
|
set_font(&font6);
|
|
|
|
} else if (name == "bitmap8") {
|
|
|
|
set_font(&font8);
|
|
|
|
} else if (name == "bitmap14_outline") {
|
|
|
|
set_font(&font14_outline);
|
|
|
|
} else {
|
|
|
|
// check that font exists and assign it
|
|
|
|
if(hershey::fonts.find(name) != hershey::fonts.end()) {
|
|
|
|
set_font(hershey::fonts[name]);
|
|
|
|
}
|
2022-06-07 20:19:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::set_clip(const Rect &r) {
|
2021-01-23 23:41:09 +00:00
|
|
|
clip = bounds.intersection(r);
|
2021-01-18 07:58:19 +00:00
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::remove_clip() {
|
2021-01-18 07:58:19 +00:00
|
|
|
clip = bounds;
|
|
|
|
}
|
|
|
|
|
2022-06-10 00:58:54 +01:00
|
|
|
void PicoGraphics::get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array<uint8_t, 16> &candidates) {
|
|
|
|
RGB error;
|
|
|
|
for(size_t i = 0; i < candidates.size(); i++) {
|
|
|
|
candidates[i] = (col + error).closest(palette, len);
|
|
|
|
error += (col - palette[candidates[i]]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort by a rough approximation of luminance, this ensures that neighbouring
|
|
|
|
// pixels in the dither matrix are at extreme opposites of luminence
|
|
|
|
// giving a more balanced output
|
|
|
|
std::sort(candidates.begin(), candidates.end(), [palette](int a, int b) {
|
|
|
|
return palette[a].luminance() > palette[b].luminance();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::clear() {
|
2021-01-18 07:58:19 +00:00
|
|
|
rectangle(clip);
|
|
|
|
}
|
2021-01-16 12:26:03 +00:00
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::pixel(const Point &p) {
|
2021-01-18 07:58:19 +00:00
|
|
|
if(!clip.contains(p)) return;
|
2022-06-08 16:37:09 +01:00
|
|
|
set_pixel(p);
|
2021-01-16 12:26:03 +00:00
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::pixel_span(const Point &p, int32_t l) {
|
2021-01-18 07:58:19 +00:00
|
|
|
// 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;
|
2021-01-16 12:26:03 +00:00
|
|
|
|
2021-01-18 07:58:19 +00:00
|
|
|
// clamp span horizontally
|
2021-01-23 23:41:09 +00:00
|
|
|
Point clipped = p;
|
2021-01-18 07:58:19 +00:00
|
|
|
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;}
|
2021-01-16 12:26:03 +00:00
|
|
|
|
2022-06-06 17:54:15 +01:00
|
|
|
Point dest(clipped.x, clipped.y);
|
2021-01-16 12:26:03 +00:00
|
|
|
while(l--) {
|
2022-06-08 16:37:09 +01:00
|
|
|
set_pixel(dest);
|
2022-06-06 17:54:15 +01:00
|
|
|
dest.x++;
|
2021-01-16 12:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::rectangle(const Rect &r) {
|
2021-01-16 12:26:03 +00:00
|
|
|
// clip and/or discard depending on rectangle visibility
|
2021-01-23 23:41:09 +00:00
|
|
|
Rect clipped = r.intersection(clip);
|
2021-01-18 07:58:19 +00:00
|
|
|
|
|
|
|
if(clipped.empty()) return;
|
|
|
|
|
2022-06-06 17:54:15 +01:00
|
|
|
Point dest(clipped.x, clipped.y);
|
2021-01-18 07:58:19 +00:00
|
|
|
while(clipped.h--) {
|
|
|
|
// draw span of pixels for this row
|
2022-06-06 17:54:15 +01:00
|
|
|
pixel_span(dest, clipped.w);
|
2021-01-18 07:58:19 +00:00
|
|
|
// move to next scanline
|
2022-06-06 17:54:15 +01:00
|
|
|
dest.y++;
|
2021-01-16 12:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::circle(const Point &p, int32_t radius) {
|
2021-01-16 12:26:03 +00:00
|
|
|
// circle in screen bounds?
|
2021-01-23 23:41:09 +00:00
|
|
|
Rect bounds = Rect(p.x - radius, p.y - radius, radius * 2, radius * 2);
|
2021-01-18 07:58:19 +00:00
|
|
|
if(!bounds.intersects(clip)) return;
|
2021-01-16 12:26:03 +00:00
|
|
|
|
|
|
|
int ox = radius, oy = 0, err = -radius;
|
|
|
|
while (ox >= oy)
|
|
|
|
{
|
|
|
|
int last_oy = oy;
|
|
|
|
|
|
|
|
err += oy; oy++; err += oy;
|
|
|
|
|
2021-01-23 23:41:09 +00:00
|
|
|
pixel_span(Point(p.x - ox, p.y + last_oy), ox * 2 + 1);
|
2021-01-16 12:26:03 +00:00
|
|
|
if (last_oy != 0) {
|
2021-01-23 23:41:09 +00:00
|
|
|
pixel_span(Point(p.x - ox, p.y - last_oy), ox * 2 + 1);
|
2021-01-16 12:26:03 +00:00
|
|
|
}
|
|
|
|
|
2021-01-18 07:58:19 +00:00
|
|
|
if(err >= 0 && ox != last_oy) {
|
2021-01-23 23:41:09 +00:00
|
|
|
pixel_span(Point(p.x - last_oy, p.y + ox), last_oy * 2 + 1);
|
2021-01-18 07:58:19 +00:00
|
|
|
if (ox != 0) {
|
2021-01-23 23:41:09 +00:00
|
|
|
pixel_span(Point(p.x - last_oy, p.y - ox), last_oy * 2 + 1);
|
2021-01-16 12:26:03 +00:00
|
|
|
}
|
2021-01-18 07:58:19 +00:00
|
|
|
|
|
|
|
err -= ox; ox--; err -= ox;
|
2021-01-16 12:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-08 16:37:09 +01:00
|
|
|
void PicoGraphics::character(const char c, const Point &p, float s, float a) {
|
|
|
|
if (bitmap_font) {
|
|
|
|
bitmap::character(bitmap_font, [this](int32_t x, int32_t y, int32_t w, int32_t h) {
|
|
|
|
rectangle(Rect(x, y, w, h));
|
|
|
|
}, c, p.x, p.y, std::max(1.0f, s));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hershey_font) {
|
|
|
|
hershey::glyph(hershey_font, [this](int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
|
|
|
|
line(Point(x1, y1), Point(x2, y2));
|
|
|
|
}, c, p.x, p.y, s, a);
|
|
|
|
return;
|
|
|
|
}
|
2021-01-17 07:41:25 +00:00
|
|
|
}
|
|
|
|
|
2022-06-08 16:37:09 +01:00
|
|
|
void PicoGraphics::text(const std::string &t, const Point &p, int32_t wrap, float s, float a, uint8_t letter_spacing) {
|
|
|
|
if (bitmap_font) {
|
|
|
|
bitmap::text(bitmap_font, [this](int32_t x, int32_t y, int32_t w, int32_t h) {
|
|
|
|
rectangle(Rect(x, y, w, h));
|
|
|
|
}, t, p.x, p.y, wrap, std::max(1.0f, s), letter_spacing);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hershey_font) {
|
|
|
|
hershey::text(hershey_font, [this](int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
|
|
|
|
line(Point(x1, y1), Point(x2, y2));
|
|
|
|
}, t, p.x, p.y, s, a);
|
|
|
|
return;
|
|
|
|
}
|
2021-01-17 07:41:25 +00:00
|
|
|
}
|
2021-01-16 12:26:03 +00:00
|
|
|
|
2022-06-08 16:37:09 +01:00
|
|
|
int32_t PicoGraphics::measure_text(const std::string &t, float s, uint8_t letter_spacing) {
|
|
|
|
if (bitmap_font) return bitmap::measure_text(bitmap_font, t, std::max(1.0f, s), letter_spacing);
|
|
|
|
if (hershey_font) return hershey::measure_text(hershey_font, t, s);
|
|
|
|
return 0;
|
2022-05-26 11:51:15 +01:00
|
|
|
}
|
|
|
|
|
2021-01-23 23:41:09 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::triangle(Point p1, Point p2, Point p3) {
|
2021-01-23 23:41:09 +00:00
|
|
|
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;
|
|
|
|
|
2021-03-22 20:43:23 +00:00
|
|
|
for (int32_t y = 0; y < triangle_bounds.h; y++) {
|
2021-01-23 23:41:09 +00:00
|
|
|
int32_t w0 = w0row;
|
|
|
|
int32_t w1 = w1row;
|
|
|
|
int32_t w2 = w2row;
|
|
|
|
|
2022-06-06 17:54:15 +01:00
|
|
|
Point dest = Point(triangle_bounds.x, triangle_bounds.y + y);
|
2021-03-22 20:43:23 +00:00
|
|
|
for (int32_t x = 0; x < triangle_bounds.w; x++) {
|
2021-01-23 23:41:09 +00:00
|
|
|
if ((w0 | w1 | w2) >= 0) {
|
2022-06-08 16:37:09 +01:00
|
|
|
set_pixel(dest);
|
2021-01-23 23:41:09 +00:00
|
|
|
}
|
|
|
|
|
2022-06-06 17:54:15 +01:00
|
|
|
dest.x++;
|
2021-01-23 23:41:09 +00:00
|
|
|
|
|
|
|
w0 += a12;
|
|
|
|
w1 += a20;
|
|
|
|
w2 += a01;
|
|
|
|
}
|
|
|
|
|
|
|
|
w0row += b12;
|
|
|
|
w1row += b20;
|
|
|
|
w2row += b01;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::polygon(const std::vector<Point> &points) {
|
2021-01-23 23:41:09 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void PicoGraphics::line(Point p1, Point p2) {
|
2021-01-23 23:41:09 +00:00
|
|
|
// fast horizontal line
|
|
|
|
if(p1.y == p2.y) {
|
2022-06-08 16:37:09 +01:00
|
|
|
p1 = p1.clamp(clip);
|
|
|
|
p2 = p2.clamp(clip);
|
|
|
|
int32_t start = std::min(p1.x, p2.x);
|
|
|
|
int32_t end = std::max(p1.x, p2.x);
|
2021-01-23 23:41:09 +00:00
|
|
|
pixel_span(Point(start, p1.y), end - start);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fast vertical line
|
|
|
|
if(p1.x == p2.x) {
|
2022-06-08 16:37:09 +01:00
|
|
|
p1 = p1.clamp(clip);
|
|
|
|
p2 = p2.clamp(clip);
|
|
|
|
int32_t start = std::min(p1.y, p2.y);
|
|
|
|
int32_t length = std::max(p1.y, p2.y) - start;
|
2022-06-06 17:54:15 +01:00
|
|
|
Point dest(p1.x, start);
|
2021-01-23 23:41:09 +00:00
|
|
|
while(length--) {
|
2022-06-08 16:37:09 +01:00
|
|
|
set_pixel(dest);
|
2022-06-06 17:54:15 +01:00
|
|
|
dest.y++;
|
2021-01-23 23:41:09 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-08 16:37:09 +01:00
|
|
|
|
2021-01-23 23:41:09 +00:00
|
|
|
// 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--) {
|
2022-06-08 16:37:09 +01:00
|
|
|
Point p(x, y >> 16);
|
|
|
|
if(clip.contains(p)) set_pixel(p);
|
2021-01-23 23:41:09 +00:00
|
|
|
y += sy;
|
|
|
|
x += sx;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
// steep version
|
|
|
|
int32_t s = std::abs(dy); // number of steps
|
2021-02-11 23:16:47 +00:00
|
|
|
int32_t sy = dy < 0 ? -1 : 1; // y step value
|
|
|
|
int32_t sx = (dx << 16) / s; // x step value in fixed 16:16
|
2021-01-23 23:41:09 +00:00
|
|
|
int32_t y = p1.y;
|
|
|
|
int32_t x = p1.x << 16;
|
|
|
|
while(s--) {
|
2022-06-08 16:37:09 +01:00
|
|
|
Point p(x >> 16, y);
|
|
|
|
if(clip.contains(p)) set_pixel(p);
|
2021-01-23 23:41:09 +00:00
|
|
|
y += sy;
|
|
|
|
x += sx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-11 23:16:47 +00:00
|
|
|
}
|