TinyGPU
Loading...
Searching...
No Matches
SurfaceBase.h
Go to the documentation of this file.
1#pragma once
2#include <stddef.h>
3#include <stdint.h>
4
5#include <algorithm>
6#include <vector>
7
8#include "TinyGPU/Font/IFont.h"
9#include "TinyGPU/Surface/ISurface.h"
10#include "TinyGPU/Font/LinePrinter.h"
11
12namespace tinygpu {
13
14/**
15 * @brief Base class for 2D surfaces with drawing and text rendering support.
16 *
17 * SurfaceBase provides common logic for managing surface dimensions, font
18 * handling, and basic geometry operations. It is intended to be subclassed by
19 * concrete surface implementations that provide pixel storage and access (e.g.,
20 * Surface, SurfaceMonochrome).
21 *
22 * @tparam PixelT The pixel color type (e.g., RGB565, bool, etc.)
23 */
24template <typename PixelT>
25class SurfaceBase : public ISurface<PixelT> {
26 public:
27 /// Default constructor.
28 SurfaceBase() = default;
29
30 /// Construct with width, height, and font.
31 SurfaceBase(size_t width, size_t height, IFont<PixelT>& font) : font_(&font) {
32 setFont(font);
33 width_ = width;
34 height_ = height;
35 }
36
37 /// Initializes the surface by resizing it to the current dimensions.
38 bool begin() {
39 return resize(width_, height_);
40 }
41
42 /// Clears the surface and releases any allocated resources.
43 void end() {
44 resizeBuffer(0, 0);
45 }
46
47 /// Virtual destructor.
48 ~SurfaceBase() override = default;
49
50 /// Abstract method: set a pixel at (x, y) to the specified color.
51 void setPixel(size_t x, size_t y, PixelT color) override = 0;
52
53 /// Abstract method: get the pixel color at (x, y).
54 PixelT getPixel(size_t x, size_t y) const override = 0;
55
56 /// Set the font for text rendering.
57 void setFont(IFont<PixelT>& font) {
58 linePrinter_.setFont(font);
59 font_ = &font;
60 }
61
62 /// Get the current font (mutable).
63 IFont<PixelT>& font() override { return *font_; }
64
65 /// Get the current font (const).
66 const IFont<PixelT>& font() const { return *font_; }
67
68 /// Resize the surface to new dimensions.
69 bool resize(size_t newWidth, size_t newHeight) override {
70 width_ = newWidth;
71 height_ = newHeight;
72 return resizeBuffer(newWidth, newHeight);
73 }
74
75 /// Get the width of the surface.
76 size_t width() const override { return width_; }
77
78 /// Get the height of the surface.
79 size_t height() const override { return height_; }
80
81 // Pixel access (to be implemented by derived classes)
82 virtual bool resizeBuffer(size_t, size_t) = 0;
83
84 // Drawing/geometry logic (now generic, uses setPixel/getPixel)
85 void clear(PixelT color = PixelT()) override {
86 for (size_t y = 0; y < height_; ++y) {
87 for (size_t x = 0; x < width_; ++x) {
88 setPixel(x, y, color);
89 }
90 }
91 }
92
93 /// Scroll the surface content by (dx, dy) pixels.
94 void scroll(int dx, int dy) {
95 if (dx == 0 && dy == 0) return;
96 Vector<PixelT> newBuffer(width_ * height_);
97 for (size_t y = 0; y < height_; ++y) {
98 for (size_t x = 0; x < width_; ++x) {
99 int srcX = (int)x + dx;
100 int srcY = (int)y + dy;
101 PixelT color = (srcX >= 0 && srcX < (int)width_ && srcY >= 0 &&
102 srcY < (int)height_)
103 ? getPixel(srcX, srcY)
104 : PixelT();
105 newBuffer[y * width_ + x] = color;
106 }
107 }
108 // Copy new buffer back to the surface
109 for (size_t i = 0; i < newBuffer.size(); ++i) {
110 setPixel(i % width_, i / width_, newBuffer[i]);
111 }
112 }
113
114 /// Draw a line from (x0, y0) to (x1, y1) with the given color.
115 void drawLine(size_t x0, size_t y0, size_t x1, size_t y1,
116 PixelT color) override {
117 int dx = std::abs((int)x1 - (int)x0), sx = x0 < x1 ? 1 : -1;
118 int dy = -std::abs((int)y1 - (int)y0), sy = y0 < y1 ? 1 : -1;
119 int err = dx + dy, e2;
120 while (true) {
121 setPixelClipped(x0, y0, color);
122 if (x0 == x1 && y0 == y1) break;
123 e2 = 2 * err;
124 if (e2 >= dy) {
125 err += dy;
126 x0 += sx;
127 }
128 if (e2 <= dx) {
129 err += dx;
130 y0 += sy;
131 }
132 }
133 }
134
135 /// Draw a rectangle outline at (x, y) with width w, height h, and color.
136 void drawRect(size_t x, size_t y, size_t w, size_t h, PixelT color) override {
137 if (w == 0 || h == 0) return;
138 size_t x1 = x + w - 1, y1 = y + h - 1;
139 drawLine(x, y, x1, y, color);
140 drawLine(x, y1, x1, y1, color);
141 drawLine(x, y, x, y1, color);
142 drawLine(x1, y, x1, y1, color);
143 }
144
145 /// Fill a rectangle at (x, y) with width w, height h, and color -
146 /// clipped both to the surface's own bounds and to the active clip
147 /// rect (see pushClipRect()), since every text glyph cell (Font5x7/
148 /// BitmapFont) and word-wrapped line (LinePrinter) is drawn as a
149 /// fillRect() call - clipping it here is what makes drawText() respect
150 /// the clip too, with no separate handling needed.
151 void fillRect(size_t x, size_t y, size_t w, size_t h, PixelT color) override {
152 if (w == 0 || h == 0) return;
153 size_t endX = std::min(x + w, width_);
154 size_t endY = std::min(y + h, height_);
155 if (!clipStack_.empty()) {
156 const ClipRect& clip = clipStack_.back();
157 x = std::max(x, clip.x);
158 y = std::max(y, clip.y);
159 endX = std::min(endX, clip.x + clip.w);
160 endY = std::min(endY, clip.y + clip.h);
161 }
162 for (size_t yy = y; yy < endY; ++yy) {
163 for (size_t xx = x; xx < endX; ++xx) {
164 setPixel(xx, yy, color);
165 }
166 }
167 }
168
169 /// Draw a circle outline centered at (x, y) with radius r and color.
170 void drawCircle(size_t x, size_t y, size_t r, PixelT color) override {
171 int offsetX = r, offsetY = 0, decision = 1 - (int)offsetX;
172 while (offsetX >= offsetY) {
173 setPixelClipped(x + offsetX, y + offsetY, color);
174 setPixelClipped(x + offsetY, y + offsetX, color);
175 setPixelClipped(x - offsetY, y + offsetX, color);
176 setPixelClipped(x - offsetX, y + offsetY, color);
177 setPixelClipped(x - offsetX, y - offsetY, color);
178 setPixelClipped(x - offsetY, y - offsetX, color);
179 setPixelClipped(x + offsetY, y - offsetX, color);
180 setPixelClipped(x + offsetX, y - offsetY, color);
181 ++offsetY;
182 if (decision <= 0)
183 decision += 2 * offsetY + 1;
184 else {
185 --offsetX;
186 decision += 2 * (offsetY - offsetX) + 1;
187 }
188 }
189 }
190
191 /// Fill a circle centered at (x, y) with radius r and color.
192 void fillCircle(size_t x, size_t y, size_t r, PixelT color) override {
193 int offsetX = r, offsetY = 0, decision = 1 - (int)offsetX;
194 while (offsetX >= offsetY) {
195 drawHorizontalLineClipped(x - offsetX, x + offsetX, y + offsetY, color);
196 drawHorizontalLineClipped(x - offsetX, x + offsetX, y - offsetY, color);
197 drawHorizontalLineClipped(x - offsetY, x + offsetY, y + offsetX, color);
198 drawHorizontalLineClipped(x - offsetY, x + offsetY, y - offsetX, color);
199 ++offsetY;
200 if (decision <= 0)
201 decision += 2 * offsetY + 1;
202 else {
203 --offsetX;
204 decision += 2 * (offsetY - offsetX) + 1;
205 }
206 }
207 }
208
209 /// Draw a sprite at (x, y), skipping pixels matching invisibleColor.
210 void drawSprite(size_t x, size_t y, const ISurface<PixelT>& sprite,
211 PixelT invisibleColor = PixelT()) override {
212 for (size_t spriteY = 0; spriteY < sprite.height(); ++spriteY) {
213 for (size_t spriteX = 0; spriteX < sprite.width(); ++spriteX) {
214 PixelT color = sprite.getPixel(spriteX, spriteY);
215 if (color != invisibleColor) {
216 setPixelClipped(x + spriteX, y + spriteY, color);
217 }
218 }
219 }
220 }
221
222 /// Clear the region covered by a sprite at (x, y) to clearColor.
223 void clearSprite(size_t x, size_t y, ISurface<PixelT>& sprite,
224 PixelT clearColor = PixelT()) override {
225 for (size_t spriteY = 0; spriteY < sprite.height(); ++spriteY) {
226 for (size_t spriteX = 0; spriteX < sprite.width(); ++spriteX) {
227 setPixelClipped(x + spriteX, y + spriteY, clearColor);
228 }
229 }
230 }
231
232 /// Copy the framebuffer region at (x, y) into the sprite.
233 void copySprite(size_t x, size_t y, const ISurface<PixelT>& sprite) override {
234 for (size_t spriteY = 0; spriteY < sprite.height(); ++spriteY) {
235 for (size_t spriteX = 0; spriteX < sprite.width(); ++spriteX) {
236 size_t framebufferX = x + spriteX;
237 size_t framebufferY = y + spriteY;
238 PixelT color = (framebufferX < width_ && framebufferY < height_)
239 ? getPixel(framebufferX, framebufferY)
240 : PixelT();
241 const_cast<ISurface<PixelT>&>(sprite).setPixel(spriteX, spriteY, color);
242 }
243 }
244 }
245
246 /// Draw text at (x, y) with foreground/background color and formatting
247 /// options.
248 void drawText(int16_t x, int16_t y, const char* text, PixelT foreground,
249 PixelT background = PixelT(), bool opaque = false,
250 uint8_t scale = 1, uint8_t spacing = 1,
251 uint8_t lineSpacing = 1) override {
252 font_->drawText(*this, x, y, text, foreground, background, opaque, scale,
253 spacing, lineSpacing);
254 }
255
256 /// Get the line printer for this surface.
258 linePrinter_.setFont(*font_);
259 linePrinter_.setTarget(*this);
260 return linePrinter_;
261 }
262
263 /// Returns true if (x, y) is within the surface bounds.
264 bool isInBounds(size_t x, size_t y) const {
265 return x < width_ && y < height_;
266 }
267 /// Set a pixel only if (x, y) is in bounds and not clipped away (see
268 /// pushClipRect()).
269 void setPixelClipped(size_t x, size_t y, PixelT color) {
270 if (isInBounds(x, y) && isClipVisible(x, y)) setPixel(x, y, color);
271 }
272 /// Draw a horizontal line from x0 to x1 at y, clipped to bounds and to
273 /// the active clip rect (see pushClipRect()).
274 void drawHorizontalLineClipped(int x0, int x1, int y, PixelT color) {
275 if (y < 0 || (size_t)y >= height_) return;
276 int startX = std::max(0, std::min(x0, x1));
277 int endX = std::min((int)width_ - 1, std::max(x0, x1));
278 if (!clipStack_.empty()) {
279 const ClipRect& clip = clipStack_.back();
280 if ((size_t)y < clip.y || (size_t)y >= clip.y + clip.h) return;
281 startX = std::max(startX, static_cast<int>(clip.x));
282 endX = std::min(endX, static_cast<int>(clip.x + clip.w) - 1);
283 }
284 if (startX > endX) return;
285 for (int x = startX; x <= endX; ++x) setPixel(x, y, color);
286 }
287
288 bool contains(size_t x, size_t y) override {
289 return isInBounds(x, y) && isClipVisible(x, y);
290 }
291
292 /// See ISurface::pushClipRect() - intersects (x, y, w, h) with whatever
293 /// clip is already active, so nested containers each narrow further
294 /// rather than one replacing another's restriction.
295 void pushClipRect(size_t x, size_t y, size_t w, size_t h) override {
296 ClipRect rect{x, y, w, h};
297 if (!clipStack_.empty()) rect = intersect(rect, clipStack_.back());
298 clipStack_.push_back(rect);
299 }
300
301 /// See ISurface::popClipRect().
302 void popClipRect() override {
303 if (!clipStack_.empty()) clipStack_.pop_back();
304 }
305
306 /// See ISurface::isClipVisible().
307 bool isClipVisible(size_t x, size_t y) const override {
308 if (clipStack_.empty()) return true;
309 const ClipRect& clip = clipStack_.back();
310 return x >= clip.x && y >= clip.y && x < clip.x + clip.w && y < clip.y + clip.h;
311 }
312
313 protected:
316 IFont<PixelT>* font_ = nullptr;
318
319 private:
320 /// One entry of the clip stack - see pushClipRect().
321 struct ClipRect {
322 size_t x = 0;
323 size_t y = 0;
324 size_t w = 0;
325 size_t h = 0;
326 };
327
328 static ClipRect intersect(const ClipRect& a, const ClipRect& b) {
329 const size_t x0 = std::max(a.x, b.x);
330 const size_t y0 = std::max(a.y, b.y);
331 const size_t x1 = std::min(a.x + a.w, b.x + b.w);
332 const size_t y1 = std::min(a.y + a.h, b.y + b.h);
333 if (x1 <= x0 || y1 <= y0) return ClipRect{x0, y0, 0, 0};
334 return ClipRect{x0, y0, x1 - x0, y1 - y0};
335 }
336
337 std::vector<ClipRect> clipStack_;
338};
339
340} // namespace tinygpu
Font rendering interface for TinyGPU-compatible framebuffers.
Definition: IFont.h:19
Helper for printing wrapped lines of text onto a TinyGPU target.
Definition: LinePrinter.h:17
Base class for 2D surfaces with drawing and text rendering support.
Definition: SurfaceBase.h:25
void pushClipRect(size_t x, size_t y, size_t w, size_t h) override
Definition: SurfaceBase.h:295
void drawLine(size_t x0, size_t y0, size_t x1, size_t y1, PixelT color) override
Draw a line from (x0, y0) to (x1, y1) with the given color.
Definition: SurfaceBase.h:115
void fillRect(size_t x, size_t y, size_t w, size_t h, PixelT color) override
Definition: SurfaceBase.h:151
void setPixelClipped(size_t x, size_t y, PixelT color)
Definition: SurfaceBase.h:269
void clearSprite(size_t x, size_t y, ISurface< PixelT > &sprite, PixelT clearColor=PixelT()) override
Clear the region covered by a sprite at (x, y) to clearColor.
Definition: SurfaceBase.h:223
PixelT getPixel(size_t x, size_t y) const override=0
Abstract method: get the pixel color at (x, y).
IFont< PixelT > * font_
Definition: SurfaceBase.h:316
void drawSprite(size_t x, size_t y, const ISurface< PixelT > &sprite, PixelT invisibleColor=PixelT()) override
Draw a sprite at (x, y), skipping pixels matching invisibleColor.
Definition: SurfaceBase.h:210
void drawCircle(size_t x, size_t y, size_t r, PixelT color) override
Draw a circle outline centered at (x, y) with radius r and color.
Definition: SurfaceBase.h:170
void setFont(IFont< PixelT > &font)
Set the font for text rendering.
Definition: SurfaceBase.h:57
bool contains(size_t x, size_t y) override
Checks if the given coordinates are within the surface bounds.
Definition: SurfaceBase.h:288
size_t width_
Definition: SurfaceBase.h:314
IFont< PixelT > & font() override
Get the current font (mutable).
Definition: SurfaceBase.h:63
bool begin()
Initializes the surface by resizing it to the current dimensions.
Definition: SurfaceBase.h:38
size_t height_
Definition: SurfaceBase.h:315
const IFont< PixelT > & font() const
Get the current font (const).
Definition: SurfaceBase.h:66
void popClipRect() override
See ISurface::popClipRect().
Definition: SurfaceBase.h:302
void drawRect(size_t x, size_t y, size_t w, size_t h, PixelT color) override
Draw a rectangle outline at (x, y) with width w, height h, and color.
Definition: SurfaceBase.h:136
size_t height() const override
Get the height of the surface.
Definition: SurfaceBase.h:79
SurfaceBase(size_t width, size_t height, IFont< PixelT > &font)
Construct with width, height, and font.
Definition: SurfaceBase.h:31
bool isClipVisible(size_t x, size_t y) const override
See ISurface::isClipVisible().
Definition: SurfaceBase.h:307
LinePrinter< PixelT > & linePrinter()
Get the line printer for this surface.
Definition: SurfaceBase.h:257
void clear(PixelT color=PixelT()) override
Clears the framebuffer with a single color.
Definition: SurfaceBase.h:85
void drawText(int16_t x, int16_t y, const char *text, PixelT foreground, PixelT background=PixelT(), bool opaque=false, uint8_t scale=1, uint8_t spacing=1, uint8_t lineSpacing=1) override
Definition: SurfaceBase.h:248
void setPixel(size_t x, size_t y, PixelT color) override=0
Abstract method: set a pixel at (x, y) to the specified color.
void fillCircle(size_t x, size_t y, size_t r, PixelT color) override
Fill a circle centered at (x, y) with radius r and color.
Definition: SurfaceBase.h:192
void scroll(int dx, int dy)
Scroll the surface content by (dx, dy) pixels.
Definition: SurfaceBase.h:94
void end()
Clears the surface and releases any allocated resources.
Definition: SurfaceBase.h:43
virtual bool resizeBuffer(size_t, size_t)=0
SurfaceBase()=default
Default constructor.
size_t width() const override
Get the width of the surface.
Definition: SurfaceBase.h:76
LinePrinter< PixelT > linePrinter_
Definition: SurfaceBase.h:317
~SurfaceBase() override=default
Virtual destructor.
bool resize(size_t newWidth, size_t newHeight) override
Resize the surface to new dimensions.
Definition: SurfaceBase.h:69
void copySprite(size_t x, size_t y, const ISurface< PixelT > &sprite) override
Copy the framebuffer region at (x, y) into the sprite.
Definition: SurfaceBase.h:233
void drawHorizontalLineClipped(int x0, int x1, int y, PixelT color)
Definition: SurfaceBase.h:274
bool isInBounds(size_t x, size_t y) const
Returns true if (x, y) is within the surface bounds.
Definition: SurfaceBase.h:264
Definition: DSIBusESP32.h:19