From 8345197a83b7839726f4bf4d17c331a96bec8d19 Mon Sep 17 00:00:00 2001 From: Jonathan Williamson Date: Mon, 18 Jan 2021 07:58:19 +0000 Subject: [PATCH] add support for clipping rectangles to graphics libary, add point and rect types --- examples/pico_display/demo.cpp | 8 +- examples/pico_explorer/demo.cpp | 57 ++++++----- libraries/pico_graphics/CMakeLists.txt | 2 +- libraries/pico_graphics/pico_graphics.cpp | 116 +++++++++++++--------- libraries/pico_graphics/pico_graphics.hpp | 60 +++++++++-- libraries/pico_graphics/types.cpp | 60 +++++++++++ 6 files changed, 218 insertions(+), 85 deletions(-) create mode 100644 libraries/pico_graphics/types.cpp diff --git a/examples/pico_display/demo.cpp b/examples/pico_display/demo.cpp index 8ef199bf..2a2659c8 100644 --- a/examples/pico_display/demo.cpp +++ b/examples/pico_display/demo.cpp @@ -109,18 +109,18 @@ int main() { uint32_t i = 0; while(true) { pico_display.set_pen(120, 40, 60); - pico_display.rectangle(0, 0, 240, 135); + pico_display.clear(); for(auto &shape : shapes) { shape.x += shape.dx; shape.y += shape.dy; if(shape.x < 0) shape.dx *= -1; - if(shape.x >= pico_display.width) shape.dx *= -1; + if(shape.x >= pico_display.bounds.w) shape.dx *= -1; if(shape.y < 0) shape.dy *= -1; - if(shape.y >= pico_display.height) shape.dy *= -1; + if(shape.y >= pico_display.bounds.h) shape.dy *= -1; pico_display.set_pen(shape.pen); - pico_display.circle(shape.x, shape.y, shape.r); + pico_display.circle(point(shape.x, shape.y), shape.r); } float led_step = fmod(i / 20.0f, M_PI * 2.0f); diff --git a/examples/pico_explorer/demo.cpp b/examples/pico_explorer/demo.cpp index 0663de6c..61243408 100644 --- a/examples/pico_explorer/demo.cpp +++ b/examples/pico_explorer/demo.cpp @@ -86,57 +86,51 @@ int main() { uint32_t i = 0; while(true) { pico_explorer.set_pen(120, 40, 60); - pico_explorer.rectangle(0, 0, pico_explorer.width, pico_explorer.height); + pico_explorer.clear(); for(auto &shape : shapes) { shape.x += shape.dx; shape.y += shape.dy; if(shape.x < 0) shape.dx *= -1; - if(shape.x >= pico_explorer.width) shape.dx *= -1; + if(shape.x >= pico_explorer.bounds.w) shape.dx *= -1; if(shape.y < 0) shape.dy *= -1; - if(shape.y >= pico_explorer.height) shape.dy *= -1; + if(shape.y >= pico_explorer.bounds.h) shape.dy *= -1; pico_explorer.set_pen(shape.pen); - pico_explorer.circle(shape.x, shape.y, shape.r); + pico_explorer.circle(point(shape.x, shape.y), shape.r); } - pico_explorer.set_pen(255, 255, 255); - pico_explorer.text("This is a test of some text data that should wrap nicely onto multiple lines which is dead useful like.", 10, 10, 180); - float rv = pico_explorer.get_adc(pico_explorer.ADC0); pico_explorer.set_pen(255, 255, 255); - pico_explorer.circle(rv * 140 + 50, 110, 20); + pico_explorer.circle(point(rv * 140 + 50, 110), 20); pico_explorer.set_pen(rv * 255, 0, 0); - pico_explorer.circle(rv * 140 + 50, 110, 15); + pico_explorer.circle(point(rv * 140 + 50, 110), 15); float gv = pico_explorer.get_adc(pico_explorer.ADC1); pico_explorer.set_pen(255, 255, 255); - pico_explorer.circle(gv * 140 + 50, 160, 20); + pico_explorer.circle(point(gv * 140 + 50, 160), 20); pico_explorer.set_pen(0, gv * 255, 0); - pico_explorer.circle(gv * 140 + 50, 160, 15); + pico_explorer.circle(point(gv * 140 + 50, 160), 15); float bv = pico_explorer.get_adc(pico_explorer.ADC2); pico_explorer.set_pen(255, 255, 255); - pico_explorer.circle(bv * 140 + 50, 210, 20); + pico_explorer.circle(point(bv * 140 + 50, 210), 20); pico_explorer.set_pen(0, 0, bv * 255); - pico_explorer.circle(bv * 140 + 50, 210, 15); + pico_explorer.circle(point(bv * 140 + 50, 210), 15); pico_explorer.set_motor(pico_explorer.MOTOR1, pico_explorer.FORWARD, bv); pico_explorer.set_motor(pico_explorer.MOTOR2, pico_explorer.FORWARD, rv); pico_explorer.set_tone(100 + (bv * 1000), rv); + float tyoff = cos(i / 20.0f) * 50.0f - 50.0f; + rect text_box(10, 10, 150, 150); + pico_explorer.set_pen(55, 65, 75); + pico_explorer.rectangle(text_box); + text_box.deflate(10); + pico_explorer.set_clip(text_box); pico_explorer.set_pen(255, 255, 255); - pico_explorer.text("x: " + std::to_string(msa301.get_axis(msa301.X, 16)), 10, 190, 100); - pico_explorer.text("y: " + std::to_string(msa301.get_axis(msa301.Y, 16)), 10, 205, 100); - pico_explorer.text("z: " + std::to_string(msa301.get_axis(msa301.Z, 16)), 10, 220, 100); - - uint16_t xpos = (msa301.get_axis(msa301.X, 16) * 120) + 120; - uint16_t ypos = (msa301.get_axis(msa301.Z, 16) * 120) + 120; - pico_explorer.set_pen(255, 255, 255); - pico_explorer.circle(xpos, ypos, 20); - pico_explorer.set_pen(255, 0, 255); - pico_explorer.circle(xpos, ypos, 15); + pico_explorer.text("This is a test of some text data that should wrap nicely onto multiple lines which is dead useful like.", point(text_box.x, text_box.y + tyoff), 100); float xoff = sin(i / 20.0f) * 50.0f; xoff += 120 - (81 / 2); @@ -151,10 +145,25 @@ int main() { uint8_t b = *src++; pico_explorer.set_pen(r, g, b); - pico_explorer.pixel(x + xoff, 68 - y + yoff); + pico_explorer.pixel(point(x + xoff, 68 - y + yoff)); } } + pico_explorer.remove_clip(); + + pico_explorer.set_pen(255, 255, 255); + pico_explorer.text("x: " + std::to_string(int(msa301.get_axis(msa301.X, 16) * 100)), point(10, 190), 100); + pico_explorer.text("y: " + std::to_string(int(msa301.get_axis(msa301.Y, 16) * 100)), point(10, 205), 100); + pico_explorer.text("z: " + std::to_string(int(msa301.get_axis(msa301.Z, 16) * 100)), point(10, 220), 100); + + uint16_t xpos = (msa301.get_axis(msa301.X, 16) * 120) + 120; + uint16_t ypos = (msa301.get_axis(msa301.Z, 16) * 120) + 120; + pico_explorer.set_pen(255, 255, 255); + pico_explorer.circle(point(xpos, ypos), 20); + pico_explorer.set_pen(255, 0, 255); + pico_explorer.circle(point(xpos, ypos), 15); + + /* if(pico_display.is_pressed(pico_display.A)) { pico_display.rectangle(0, 0, 18, 18); diff --git a/libraries/pico_graphics/CMakeLists.txt b/libraries/pico_graphics/CMakeLists.txt index 972e4cb1..da981b52 100644 --- a/libraries/pico_graphics/CMakeLists.txt +++ b/libraries/pico_graphics/CMakeLists.txt @@ -1 +1 @@ -add_library(pico_graphics font_data.cpp pico_graphics.cpp) \ No newline at end of file +add_library(pico_graphics types.cpp font_data.cpp pico_graphics.cpp) \ No newline at end of file diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index 4b31acb2..6e2a8465 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -6,11 +6,11 @@ extern uint8_t character_widths[96]; namespace pimoroni { void PicoGraphics::set_pen(uint8_t r, uint8_t g, uint8_t b) { - this->pen = create_pen(r, g, b); + pen = create_pen(r, g, b); } - void PicoGraphics::set_pen(uint16_t pen) { - this->pen = pen; + void PicoGraphics::set_pen(uint16_t p) { + pen = p; } uint16_t PicoGraphics::create_pen(uint8_t r, uint8_t g, uint8_t b) { @@ -22,49 +22,73 @@ namespace pimoroni { return ((p & 0xff00) >> 8) | ((p & 0xff) << 8); } + void PicoGraphics::set_clip(const rect &r) { + clip = r; + } + + void PicoGraphics::remove_clip() { + clip = bounds; + } + + uint16_t* PicoGraphics::ptr(const rect &r) { + return frame_buffer + r.x + r.y * bounds.w; + } + + uint16_t* PicoGraphics::ptr(const point &p) { + return frame_buffer + p.x + p.y * bounds.w; + } + uint16_t* PicoGraphics::ptr(int32_t x, int32_t y) { - return this->frame_buffer + x + y * this->width; + return frame_buffer + x + y * bounds.w; } - void PicoGraphics::pixel(int32_t x, int32_t y) { - if(x < 0 || y < 0 || x >= this->width || y >= this->height) { return; } - - *ptr(x, y) = pen; + void PicoGraphics::clear() { + rectangle(clip); } - void PicoGraphics::pixel_span(int32_t x, int32_t y, int32_t l) { - if(x + l < 0 || y < 0 || x >= this->width || y >= this->height) { return; } + void PicoGraphics::pixel(const point &p) { + if(!clip.contains(p)) return; + *ptr(p) = pen; + } - if(x < 0) {l += x; x = 0;} - if(x + l >= this->width) {l = this->width - x;} + 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; - uint16_t *p = ptr(x, y); + // 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;} + + uint16_t *dest = ptr(clipped); while(l--) { - *p++ = pen; + *dest++ = pen; } } - void PicoGraphics::rectangle(int32_t x, int32_t y, int32_t width, int32_t height) { + void PicoGraphics::rectangle(const rect &r) { // clip and/or discard depending on rectangle visibility - if(x >= this->width || y >= this->height) { return; } - if(x < 0) { width += x; x = 0; } - if(y < 0) { height += y; y = 0; } - if(width <= 0 || height <= 0) { return; } - if(x + width >= this->width) { width = this->width - x; } - if(y + height >= this->height) { height = this->height - y; } + rect clipped = r.intersection(clip); - uint16_t *p = ptr(x, y); - while(height--) { - for(uint32_t i = 0; i < width; i++) { - *p++ = this->pen; + if(clipped.empty()) return; + + uint16_t *dest = ptr(clipped); + while(clipped.h--) { + // draw span of pixels for this row + for(uint32_t i = 0; i < clipped.w; i++) { + *dest++ = pen; } - p += this->width - width; // move to next scanline + + // move to next scanline + dest += bounds.w - clipped.w; } } - void PicoGraphics::circle(int32_t x, int32_t y, int32_t radius) { + void PicoGraphics::circle(const point &p, int32_t radius) { // circle in screen bounds? - if(x + radius < 0 || y + radius < 0 || x - radius >= this->width || y - radius >= this->height) { return; } + 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) @@ -73,31 +97,33 @@ namespace pimoroni { err += oy; oy++; err += oy; - pixel_span(x - ox, y + last_oy, ox * 2 + 1); + pixel_span(point(p.x - ox, p.y + last_oy), ox * 2 + 1); if (last_oy != 0) { - pixel_span(x - ox, y - last_oy, ox * 2 + 1); + pixel_span(point(p.x - ox, p.y - last_oy), ox * 2 + 1); } - if (err >= 0) { - if (ox != last_oy) { - pixel_span(x - last_oy, y + ox, last_oy * 2 + 1); - if (ox != 0) { - pixel_span(x - last_oy, y - ox, last_oy * 2 + 1); - } - - err -= ox; ox--; err -= ox; + 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, int32_t x, int32_t y, uint8_t scale) { + 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, character_widths[char_index] * scale, 6 * scale); + + if(!clip.intersects(char_bounds)) return; + const uint8_t *d = &font_data[char_index][0]; for(uint8_t cx = 0; cx < character_widths[char_index]; cx++) { for(uint8_t cy = 0; cy < 6; cy++) { if((1U << cy) & *d) { - rectangle(x + (cx * scale), y + (cy * scale), scale, scale); + rectangle(rect(p.x + (cx * scale), p.y + (cy * scale), scale, scale)); } } @@ -105,7 +131,7 @@ namespace pimoroni { } } - void PicoGraphics::text(const std::string &t, int32_t x, int32_t y, int32_t wrap) { + void PicoGraphics::text(const std::string &t, const point &p, int32_t wrap) { uint32_t co = 0, lo = 0; // character and line (if wrapping) offset uint8_t scale = 2; @@ -119,21 +145,21 @@ namespace pimoroni { next_space = t.length(); } - uint16_t word_length = 0; + uint16_t word_width = 0; for(size_t j = i; j < next_space; j++) { - word_length += character_widths[t[j] - 32]; + word_width += character_widths[t[j] - 32] * scale; } // if this word would exceed the wrap limit then // move to the next line - if(co + word_length > wrap) { + if(co != 0 && co + word_width > wrap) { co = 0; lo += 7 * scale; } // draw word for(size_t j = i; j < next_space; j++) { - character(t[j], x + co, y + lo, scale); + character(t[j], point(p.x + co, p.y + lo), scale); co += character_widths[t[j] - 32] * scale; } diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 41d0becc..2aef657e 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -1,35 +1,73 @@ #pragma once #include -#include +#include // a tiny little graphics library for our Pico products // supports only 16-bit (565) RGB framebuffers namespace pimoroni { + struct rect; + + struct point { + int32_t x = 0, y = 0; + + point() = default; + point(int32_t x, int32_t y) : x(x), y(y) {} + + inline point& operator-= (const point &a) { x -= a.x; y -= a.y; return *this; } + inline point& operator+= (const point &a) { x += a.x; y += a.y; return *this; } + + point clamp(const rect &r) const; + }; + + struct rect { + int32_t x = 0, y = 0, w = 0, h = 0; + + rect() = default; + rect(int32_t x, int32_t y, int32_t w, int32_t h) : x(x), y(y), w(w), h(h) {} + + bool empty() const; + bool contains(const point &p) const; + bool contains(const rect &p) const; + bool intersects(const rect &r) const; + rect intersection(const rect &r) const; + + void inflate(int32_t v); + void deflate(int32_t v); + }; + class PicoGraphics { public: - uint16_t width; - uint16_t height; + rect bounds; + rect clip; + uint16_t *frame_buffer; - uint16_t pen; + + uint16_t pen; public: PicoGraphics(uint16_t width, uint16_t height, uint16_t *frame_buffer) - : frame_buffer(frame_buffer), width(width), height(height) {} + : frame_buffer(frame_buffer), bounds(0, 0, width, height), clip(0, 0, width, height) {} void set_pen(uint8_t r, uint8_t g, uint8_t b); void set_pen(uint16_t p); uint16_t create_pen(uint8_t r, uint8_t g, uint8_t b); + void set_clip(const rect &r); + void remove_clip(); + + uint16_t* ptr(const point &p); + uint16_t* ptr(const rect &r); uint16_t* ptr(int32_t x, int32_t y); - void pixel(int32_t x, int32_t y); - void pixel_span(int32_t x, int32_t y, int32_t l); - void rectangle(int32_t x, int32_t y, int32_t w, int32_t h); - void circle(int32_t x, int32_t y, int32_t r); - void text(const std::string &t, int32_t x, int32_t y, int32_t wrap); - void character(const char c, int32_t x, int32_t y, uint8_t scale); + void clear(); + void pixel(const point &p); + void pixel_span(const point &p, int32_t l); + void rectangle(const rect &r); + void circle(const point &p, int32_t r); + void text(const std::string &t, const point &p, int32_t wrap); + void character(const char c, const point &p, uint8_t scale); //void polygon(std::vector); }; diff --git a/libraries/pico_graphics/types.cpp b/libraries/pico_graphics/types.cpp new file mode 100644 index 00000000..838f5556 --- /dev/null +++ b/libraries/pico_graphics/types.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "pico_graphics.hpp" + +namespace pimoroni { + + point point::clamp(const rect &r) const { + return point( + std::min(std::max(x, r.x), r.x + r.w), + std::min(std::max(y, r.y), r.y + r.h) + ); + } + + point operator- (point lhs, const point &rhs) { + lhs -= rhs; + return lhs; + } + + point operator- (const point &rhs) { + return point(-rhs.x, -rhs.y); + } + + point operator+ (point lhs, const point &rhs) { + lhs += rhs; + return lhs; + } + + bool rect::empty() const { + return w <= 0 || h <= 0; + } + + bool rect::contains(const point &p) const { + return p.x >= x && p.y >= y && p.x < x + w && p.y < y + h; + } + + bool rect::contains(const rect &p) const { + return p.x >= x && p.y >= y && p.x + p.w < x + w && p.y + p.h < y + h; + } + + bool rect::intersects(const rect &r) const { + return !(x > r.x + r.w || x + w < r.x || y > r.y + r.h || y + h < r.y); + } + + rect rect::intersection(const rect &r) const { + return rect(std::max(x, r.x), + std::max(y, r.y), + std::min(x + w, r.x + r.w) - std::max(x, r.x), + std::min(y + h, r.y + r.h) - std::max(y, r.y)); + } + + void rect::inflate(int32_t v) { + x -= v; y -= v; w += v * 2; h += v * 2; + } + + void rect::deflate(int32_t v) { + x += v; y += v; w -= v * 2; h -= v * 2; + } + +} \ No newline at end of file