FabGL
ESP32 Display Controller and Graphics Library
vgacontroller.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27
28#include <alloca.h>
29#include <stdarg.h>
30#include <math.h>
31#include <string.h>
32
33#include "freertos/FreeRTOS.h"
34#include "freertos/task.h"
35
36#include "soc/i2s_struct.h"
37#include "soc/i2s_reg.h"
38#include "driver/periph_ctrl.h"
39#include "soc/rtc.h"
40#include "esp_spi_flash.h"
41
42#include "fabutils.h"
43#include "vgacontroller.h"
45
46
47
48
49#pragma GCC optimize ("O2")
50
51
52namespace fabgl {
53
54
55
56
57
58/*************************************************************************************/
59/* VGAController definitions */
60
61
62VGAController * VGAController::s_instance = nullptr;
63
64
65VGAController::VGAController()
66{
67 s_instance = this;
68}
69
70
71void VGAController::init()
72{
73 VGABaseController::init();
74
75 m_doubleBufferOverDMA = true;
76}
77
78
80{
81 VGABaseController::suspendBackgroundPrimitiveExecution();
82 if (m_primitiveProcessingSuspended == 1) {
83 I2S1.int_clr.val = 0xFFFFFFFF;
84 I2S1.int_ena.out_eof = 0;
85 }
86}
87
88
90{
91 VGABaseController::resumeBackgroundPrimitiveExecution();
92 if (m_primitiveProcessingSuspended == 0) {
93 if (m_isr_handle == nullptr)
94 esp_intr_alloc(ETS_I2S1_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, VSyncInterrupt, this, &m_isr_handle);
95 I2S1.int_clr.val = 0xFFFFFFFF;
96 I2S1.int_ena.out_eof = 1;
97 }
98}
99
100
101void VGAController::allocateViewPort()
102{
103 VGABaseController::allocateViewPort(MALLOC_CAP_DMA, m_viewPortWidth);
104}
105
106
107void VGAController::setResolution(VGATimings const& timings, int viewPortWidth, int viewPortHeight, bool doubleBuffered)
108{
109 VGABaseController::setResolution(timings, viewPortWidth, viewPortHeight, doubleBuffered);
110
111 // fill view port
112 for (int i = 0; i < m_viewPortHeight; ++i)
113 fill(m_viewPort[i], 0, m_viewPortWidth, 0, 0, 0, false, false);
114
115 // number of microseconds usable in VSynch ISR
116 m_maxVSyncISRTime = ceil(1000000.0 / m_timings.frequency * m_timings.scanCount * m_HLineSize * (m_timings.VSyncPulse + m_timings.VBackPorch + m_timings.VFrontPorch + m_viewPortRow));
117
118 startGPIOStream();
120}
121
122
123void VGAController::onSetupDMABuffer(lldesc_t volatile * buffer, bool isStartOfVertFrontPorch, int scan, bool isVisible, int visibleRow)
124{
125 // generate interrupt at the beginning of vertical front porch
126 if (isStartOfVertFrontPorch)
127 buffer->eof = 1;
128}
129
130
131void IRAM_ATTR VGAController::VSyncInterrupt(void * arg)
132{
133 if (I2S1.int_st.out_eof) {
134 auto VGACtrl = (VGAController*)arg;
135 int64_t startTime = VGACtrl->backgroundPrimitiveTimeoutEnabled() ? esp_timer_get_time() : 0;
136 Rect updateRect = Rect(SHRT_MAX, SHRT_MAX, SHRT_MIN, SHRT_MIN);
137 do {
138 Primitive prim;
139 if (VGACtrl->getPrimitiveISR(&prim) == false)
140 break;
141
142 VGACtrl->execPrimitive(prim, updateRect, true);
143
144 if (VGACtrl->m_primitiveProcessingSuspended)
145 break;
146
147 } while (!VGACtrl->backgroundPrimitiveTimeoutEnabled() || (startTime + VGACtrl->m_maxVSyncISRTime > esp_timer_get_time()));
148 VGACtrl->showSprites(updateRect);
149 }
150 I2S1.int_clr.val = I2S1.int_st.val;
151}
152
153
154void IRAM_ATTR VGAController::setPixelAt(PixelDesc const & pixelDesc, Rect & updateRect)
155{
156 genericSetPixelAt(pixelDesc, updateRect,
157 [&] (RGB888 const & color) { return preparePixel(color); },
158 [&] (int X, int Y, uint8_t pattern) { VGA_PIXEL(X, Y) = pattern; }
159 );
160}
161
162
163// coordinates are absolute values (not relative to origin)
164// line clipped on current absolute clipping rectangle
165void IRAM_ATTR VGAController::absDrawLine(int X1, int Y1, int X2, int Y2, RGB888 color)
166{
167 genericAbsDrawLine(X1, Y1, X2, Y2, color,
168 [&] (RGB888 const & color) { return preparePixel(color); },
169 [&] (int Y, int X1, int X2, uint8_t pattern) { rawFillRow(Y, X1, X2, pattern); },
170 [&] (int Y, int X1, int X2) { rawInvertRow(Y, X1, X2); },
171 [&] (int X, int Y, uint8_t pattern) { VGA_PIXEL(X, Y) = pattern; },
172 [&] (int X, int Y) { VGA_INVERT_PIXEL(X, Y); }
173 );
174}
175
176
177// parameters not checked
178void IRAM_ATTR VGAController::rawFillRow(int y, int x1, int x2, RGB888 color)
179{
180 rawFillRow(y, x1, x2, preparePixel(color));
181}
182
183
184// parameters not checked
185void IRAM_ATTR VGAController::rawFillRow(int y, int x1, int x2, uint8_t pattern)
186{
187 auto row = m_viewPort[y];
188 // fill first bytes before full 32 bits word
189 int x = x1;
190 for (; x <= x2 && (x & 3) != 0; ++x) {
191 VGA_PIXELINROW(row, x) = pattern;
192 }
193 // fill whole 32 bits words (don't care about VGA_PIXELINROW adjusted alignment)
194 if (x <= x2) {
195 int sz = (x2 & ~3) - x;
196 memset((void*)(row + x), pattern, sz);
197 x += sz;
198 }
199 // fill last unaligned bytes
200 for (; x <= x2; ++x) {
201 VGA_PIXELINROW(row, x) = pattern;
202 }
203}
204
205
206// parameters not checked
207void IRAM_ATTR VGAController::rawInvertRow(int y, int x1, int x2)
208{
209 auto row = m_viewPort[y];
210 for (int x = x1; x <= x2; ++x) {
211 uint8_t * px = (uint8_t*) &VGA_PIXELINROW(row, x);
212 *px = m_HVSync | ~(*px);
213 }
214}
215
216
217// swaps all pixels inside the range x1...x2 of yA and yB
218// parameters not checked
219void IRAM_ATTR VGAController::swapRows(int yA, int yB, int x1, int x2)
220{
221 uint8_t * rowA = (uint8_t*) m_viewPort[yA];
222 uint8_t * rowB = (uint8_t*) m_viewPort[yB];
223 // swap first bytes before full 32 bits word
224 int x = x1;
225 for (; x <= x2 && (x & 3) != 0; ++x)
226 tswap(VGA_PIXELINROW(rowA, x), VGA_PIXELINROW(rowB, x));
227 // swap whole 32 bits words (don't care about VGA_PIXELINROW adjusted alignment)
228 uint32_t * a = (uint32_t*)(rowA + x);
229 uint32_t * b = (uint32_t*)(rowB + x);
230 for (int right = (x2 & ~3); x < right; x += 4)
231 tswap(*a++, *b++);
232 // swap last unaligned bytes
233 for (x = (x2 & ~3); x <= x2; ++x)
234 tswap(VGA_PIXELINROW(rowA, x), VGA_PIXELINROW(rowB, x));
235}
236
237
238void IRAM_ATTR VGAController::drawEllipse(Size const & size, Rect & updateRect)
239{
240 genericDrawEllipse(size, updateRect,
241 [&] (RGB888 const & color) { return preparePixel(color); },
242 [&] (int X, int Y, uint8_t pattern) { VGA_PIXEL(X, Y) = pattern; }
243 );
244}
245
246
247void IRAM_ATTR VGAController::clear(Rect & updateRect)
248{
249 hideSprites(updateRect);
250 uint8_t pattern = preparePixel(getActualBrushColor());
251 for (int y = 0; y < m_viewPortHeight; ++y)
252 memset((uint8_t*) m_viewPort[y], pattern, m_viewPortWidth);
253}
254
255
256// scroll < 0 -> scroll UP
257// scroll > 0 -> scroll DOWN
258// Speciying horizontal scrolling region slow-down scrolling!
259void IRAM_ATTR VGAController::VScroll(int scroll, Rect & updateRect)
260{
261 genericVScroll(scroll, updateRect,
262 [&] (int yA, int yB, int x1, int x2) { swapRows(yA, yB, x1, x2); }, // swapRowsCopying
263 [&] (int yA, int yB) { tswap(m_viewPort[yA], m_viewPort[yB]); }, // swapRowsPointers
264 [&] (int y, int x1, int x2, RGB888 pattern) { rawFillRow(y, x1, x2, pattern); } // rawFillRow
265 );
266
267 if (scroll != 0) {
268 // reassign DMA pointers
269 int viewPortBuffersPerLine = 0;
270 int linePos = 1;
271 switch (m_timings.HStartingBlock) {
273 // FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
274 viewPortBuffersPerLine = (m_viewPortCol + m_viewPortWidth) < m_timings.HVisibleArea ? 3 : 2;
275 break;
277 // SYNC -> BACKPORCH -> VISIBLEAREA -> FRONTPORCH
278 viewPortBuffersPerLine = 3;
279 break;
281 // BACKPORCH -> VISIBLEAREA -> FRONTPORCH -> SYNC
282 viewPortBuffersPerLine = 3;
283 break;
285 // VISIBLEAREA -> FRONTPORCH -> SYNC -> BACKPORCH
286 viewPortBuffersPerLine = m_viewPortCol > 0 ? 3 : 2;
287 linePos = m_viewPortCol > 0 ? 1 : 0;
288 break;
289 }
290 const int Y1 = paintState().scrollingRegion.Y1;
291 const int Y2 = paintState().scrollingRegion.Y2;
292 for (int i = Y1, idx = Y1 * m_timings.scanCount; i <= Y2; ++i)
293 for (int scan = 0; scan < m_timings.scanCount; ++scan, ++idx)
294 setDMABufferView(m_viewPortRow * m_timings.scanCount + idx * viewPortBuffersPerLine + linePos, i, scan, m_viewPort, false);
295 }
296}
297
298
299// Scrolling by 1, 2, 3 and 4 pixels is optimized. Also scrolling multiples of 4 (8, 16, 24...) is optimized.
300// Scrolling by other values requires up to three steps (scopose scrolling by 1, 2, 3 or 4): for example scrolling by 5 is scomposed to 4 and 1, scrolling
301// by 6 is 4 + 2, etc.
302// Horizontal scrolling region start and size (X2-X1+1) must be aligned to 32 bits, otherwise the unoptimized (very slow) version is used.
303void IRAM_ATTR VGAController::HScroll(int scroll, Rect & updateRect)
304{
305 hideSprites(updateRect);
306 uint8_t pattern8 = preparePixel(getActualBrushColor());
307 uint16_t pattern16 = pattern8 << 8 | pattern8;
308 uint32_t pattern32 = pattern16 << 16 | pattern16;
309
310 int Y1 = paintState().scrollingRegion.Y1;
311 int Y2 = paintState().scrollingRegion.Y2;
312 int X1 = paintState().scrollingRegion.X1;
313 int X2 = paintState().scrollingRegion.X2;
314
315 int width = X2 - X1 + 1;
316 int width32 = width >> 2;
317 bool HScrolllingRegionAligned = ((X1 & 3) == 0 && (width & 3) == 0);
318
319 if (scroll < 0) {
320 // scroll left
321 for (int y = Y1; y <= Y2; ++y) {
322 if (HScrolllingRegionAligned) {
323 // aligned horizontal scrolling region, fast version
324 uint8_t * row = (uint8_t*) (m_viewPort[y] + X1);
325 for (int s = -scroll; s > 0;) {
326 if (s >= 4) {
327 // scroll left 4, 8 12, etc.. pixels moving 32 bit words
328 uint8_t * w = row;
329 int sz = (s & ~3) >> 2;
330 for (int i = 0; i < width32 - sz; ++i, w += 4)
331 *((uint32_t*)w) = *((uint32_t*)w + sz);
332 for (int i = tmax(0, width32 - sz); i < width32; ++i, w += 4)
333 *((uint32_t*)w) = pattern32;
334 s -= (s & ~3);
335 } else if ((s & 3) == 3) {
336 // scroll left 3 pixels swapping 8 bit words
337 uint8_t * b = row;
338 for (int i = 1; i < width32; ++i, b += 4) {
339 b[2] = b[1];
340 b[1] = b[4];
341 b[0] = b[7];
342 b[3] = b[6];
343 }
344 b[2] = b[1];
345 b[1] = b[0] = b[3] = pattern8;
346 s -= 3;
347 } else if (s & 2) {
348 // scroll left 2 pixels swapping 16 bit words
349 uint16_t * w = (uint16_t*) row;
350 for (int i = 1; i < width32; ++i, w += 2) {
351 w[1] = w[0];
352 w[0] = w[3];
353 }
354 w[1] = w[0];
355 w[0] = pattern16;
356 s -= 2;
357 } else if (s & 1) {
358 // scroll left 1 pixel by rotating 32 bit words
359 uint8_t * w = row;
360 for (int i = 1; i < width32; ++i, w += 4) {
361 *((uint32_t*)w) = *((uint32_t*)w) >> 8 | *((uint32_t*)w) << 24;
362 w[1] = w[6];
363 }
364 *((uint32_t*)w) = *((uint32_t*)w) >> 8 | *((uint32_t*)w) << 24;
365 w[1] = pattern8;
366 --s;
367 }
368 }
369 } else {
370 // unaligned horizontal scrolling region, fallback to slow version
371 uint8_t * row = (uint8_t*) m_viewPort[y];
372 for (int x = X1; x <= X2 + scroll; ++x)
373 VGA_PIXELINROW(row, x) = VGA_PIXELINROW(row, x - scroll);
374 // fill right area with brush color
375 for (int x = X2 + 1 + scroll; x <= X2; ++x)
376 VGA_PIXELINROW(row, x) = pattern8;
377 }
378 }
379 } else if (scroll > 0) {
380 // scroll right
381 for (int y = Y1; y <= Y2; ++y) {
382 if (HScrolllingRegionAligned) {
383 // aligned horizontal scrolling region, fast version
384 uint8_t * row = (uint8_t*) (m_viewPort[y] + X1);
385 for (int s = scroll; s > 0;) {
386 if (s >= 4) {
387 // scroll right 4, 8 12, etc.. pixels moving 32 bit words
388 int sz = (s & ~3) >> 2;
389 uint8_t * w = row + width - 4;
390 for (int i = 0; i < width32 - sz; ++i, w -= 4)
391 *((uint32_t*)w) = *((uint32_t*)w - sz);
392 for (int i = tmax(0, width32 - sz); i < width32; ++i, w -= 4)
393 *((uint32_t*)w) = pattern32;
394 s -= (s & ~3);
395 } else if ((s & 3) == 3) {
396 // scroll right 3 pixels swapping 8 bit words
397 uint8_t * b = row + width - 4;
398 for (int i = 1; i < width32; ++i, b -= 4) {
399 b[0] = b[-3];
400 b[1] = b[2];
401 b[2] = b[-1];
402 b[3] = b[-4];
403 }
404 b[1] = b[2];
405 b[0] = b[2] = b[3] = pattern8;
406 s -= 3;
407 } else if (s & 2) {
408 // scroll right 2 pixels swapping 16 bit words
409 uint16_t * w = (uint16_t*) (row + width - 4);
410 for (int i = 1; i < width32; ++i, w -= 2) {
411 w[0] = w[1];
412 w[1] = w[-2];
413 }
414 w[0] = w[1];
415 w[1] = pattern16;
416 s -= 2;
417 } else if (s & 1) {
418 // scroll right 1 pixel by rotating 32 bit words
419 uint8_t * w = row + width - 4;
420 for (int i = 1; i < width32; ++i, w -= 4) {
421 *((uint32_t*)w) = *((uint32_t*)w) << 8 | *((uint32_t*)w) >> 24;
422 w[2] = w[-3];
423 }
424 *((uint32_t*)w) = *((uint32_t*)w) << 8 | *((uint32_t*)w) >> 24;
425 w[2] = pattern8;
426 --s;
427 }
428 }
429 } else {
430 // unaligned horizontal scrolling region, fallback to slow version
431 uint8_t * row = (uint8_t*) m_viewPort[y];
432 for (int x = X2 - scroll; x >= X1; --x)
433 VGA_PIXELINROW(row, x + scroll) = VGA_PIXELINROW(row, x);
434 // fill left area with brush color
435 for (int x = X1; x < X1 + scroll; ++x)
436 VGA_PIXELINROW(row, x) = pattern8;
437 }
438 }
439
440 }
441}
442
443
444void IRAM_ATTR VGAController::drawGlyph(Glyph const & glyph, GlyphOptions glyphOptions, RGB888 penColor, RGB888 brushColor, Rect & updateRect)
445{
446 genericDrawGlyph(glyph, glyphOptions, penColor, brushColor, updateRect,
447 [&] (RGB888 const & color) { return preparePixel(color); },
448 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
449 [&] (uint8_t * row, int x, uint8_t pattern) { VGA_PIXELINROW(row, x) = pattern; }
450 );
451}
452
453
454void IRAM_ATTR VGAController::invertRect(Rect const & rect, Rect & updateRect)
455{
456 genericInvertRect(rect, updateRect,
457 [&] (int Y, int X1, int X2) { rawInvertRow(Y, X1, X2); }
458 );
459}
460
461
462void IRAM_ATTR VGAController::swapFGBG(Rect const & rect, Rect & updateRect)
463{
464 genericSwapFGBG(rect, updateRect,
465 [&] (RGB888 const & color) { return preparePixel(color); },
466 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
467 [&] (uint8_t * row, int x) { return VGA_PIXELINROW(row, x); },
468 [&] (uint8_t * row, int x, uint8_t pattern) { VGA_PIXELINROW(row, x) = pattern; }
469 );
470}
471
472
473// Slow operation!
474// supports overlapping of source and dest rectangles
475void IRAM_ATTR VGAController::copyRect(Rect const & source, Rect & updateRect)
476{
477 genericCopyRect(source, updateRect,
478 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
479 [&] (uint8_t * row, int x) { return VGA_PIXELINROW(row, x); },
480 [&] (uint8_t * row, int x, uint8_t pattern) { VGA_PIXELINROW(row, x) = pattern; }
481 );
482}
483
484
485// no bounds check is done!
486void VGAController::readScreen(Rect const & rect, RGB888 * destBuf)
487{
488 for (int y = rect.Y1; y <= rect.Y2; ++y) {
489 uint8_t * row = (uint8_t*) m_viewPort[y];
490 for (int x = rect.X1; x <= rect.X2; ++x, ++destBuf) {
491 uint8_t rawpix = VGA_PIXELINROW(row, x);
492 *destBuf = RGB888((rawpix & 3) * 85, ((rawpix >> 2) & 3) * 85, ((rawpix >> 4) & 3) * 85);
493 }
494 }
495}
496
497
498// no bounds check is done!
499void VGAController::readScreen(Rect const & rect, RGB222 * destBuf)
500{
501 uint8_t * dbuf = (uint8_t*) destBuf;
502 for (int y = rect.Y1; y <= rect.Y2; ++y) {
503 uint8_t * row = (uint8_t*) m_viewPort[y];
504 for (int x = rect.X1; x <= rect.X2; ++x, ++dbuf)
505 *dbuf = VGA_PIXELINROW(row, x) & ~VGA_SYNC_MASK;
506 }
507}
508
509
510// no bounds check is done!
511void VGAController::writeScreen(Rect const & rect, RGB222 * srcBuf)
512{
513 uint8_t * sbuf = (uint8_t*) srcBuf;
514 for (int y = rect.Y1; y <= rect.Y2; ++y) {
515 uint8_t * row = (uint8_t*) m_viewPort[y];
516 for (int x = rect.X1; x <= rect.X2; ++x, ++sbuf)
517 VGA_PIXELINROW(row, x) = *sbuf | m_HVSync;
518 }
519}
520
521
522void IRAM_ATTR VGAController::rawDrawBitmap_Native(int destX, int destY, Bitmap const * bitmap, int X1, int Y1, int XCount, int YCount)
523{
524 genericRawDrawBitmap_Native(destX, destY, (uint8_t*) bitmap->data, bitmap->width, X1, Y1, XCount, YCount,
525 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
526 [&] (uint8_t * row, int x, uint8_t src) { VGA_PIXELINROW(row, x) = m_HVSync | src; } // rawSetPixelInRow
527 );
528}
529
530
531void IRAM_ATTR VGAController::rawDrawBitmap_Mask(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
532{
533 auto foregroundPattern = preparePixel(bitmap->foregroundColor);
534 genericRawDrawBitmap_Mask(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
535 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
536 [&] (uint8_t * row, int x) { return VGA_PIXELINROW(row, x); }, // rawGetPixelInRow
537 [&] (uint8_t * row, int x) { VGA_PIXELINROW(row, x) = foregroundPattern; } // rawSetPixelInRow
538 );
539}
540
541
542void IRAM_ATTR VGAController::rawDrawBitmap_RGBA2222(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
543{
544 genericRawDrawBitmap_RGBA2222(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
545 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
546 [&] (uint8_t * row, int x) { return VGA_PIXELINROW(row, x); }, // rawGetPixelInRow
547 [&] (uint8_t * row, int x, uint8_t src) { VGA_PIXELINROW(row, x) = m_HVSync | (src & 0x3f); } // rawSetPixelInRow
548 );
549}
550
551
552void IRAM_ATTR VGAController::rawDrawBitmap_RGBA8888(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
553{
554 genericRawDrawBitmap_RGBA8888(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
555 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
556 [&] (uint8_t * row, int x) { return VGA_PIXELINROW(row, x); }, // rawGetPixelInRow
557 [&] (uint8_t * row, int x, RGBA8888 const & src) { VGA_PIXELINROW(row, x) = m_HVSync | (src.R >> 6) | (src.G >> 6 << 2) | (src.B >> 6 << 4); } // rawSetPixelInRow
558 );
559}
560
561
562
563} // end of namespace
void suspendBackgroundPrimitiveExecution()
Suspends drawings.
void resumeBackgroundPrimitiveExecution()
Resumes drawings after suspendBackgroundPrimitiveExecution().
void readScreen(Rect const &rect, RGB222 *destBuf)
Reads pixels inside the specified rectangle.
void writeScreen(Rect const &rect, RGB222 *srcBuf)
Writes pixels inside the specified rectangle.
uint8_t width
int16_t X
int16_t Y
int16_t X1
Definition: fabutils.h:0
int16_t Y2
Definition: fabutils.h:3
int16_t X2
Definition: fabutils.h:2
int16_t Y1
Definition: fabutils.h:1
This file contains some utility classes and functions.
Represents an image.
Represents a 6 bit RGB color.
Represents a rectangle.
Definition: fabutils.h:248
This file contains fabgl::GPIOStream definition.
This file contains fabgl::VGAController definition.