FabGL
ESP32 Display Controller and Graphics Library
displaycontroller.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 "displaycontroller.h"
29
30#include <string.h>
31#include <limits.h>
32#include <math.h>
33
34#include "freertos/task.h"
35
36#include "fabutils.h"
37#include "images/cursors.h"
38
39
40#pragma GCC optimize ("O2")
41
42
43namespace fabgl {
44
45
46
47
48// Array to convert Color enum to RGB888 struct
49const RGB888 COLOR2RGB888[16] = {
50 { 0, 0, 0 }, // Black
51 { 128, 0, 0 }, // Red
52 { 0, 128, 0 }, // Green
53 { 128, 128, 0 }, // Yellow
54 { 0, 0, 128 }, // Blue
55 { 128, 0, 128 }, // Magenta
56 { 0, 128, 128 }, // Cyan
57 { 128, 128, 128 }, // White
58 { 64, 64, 64 }, // BrightBlack
59 { 255, 0, 0 }, // BrightRed
60 { 0, 255, 0 }, // BrightGreen
61 { 255, 255, 0 }, // BrightYellow
62 { 0, 0, 255 }, // BrightBlue
63 { 255, 0, 255 }, // BrightMagenta
64 { 0, 255, 255 }, // BrightCyan
65 { 255, 255, 255 }, // BrightWhite
66};
67
68
69
72// RGB222 implementation
73
74bool RGB222::lowBitOnly = false;
75
76
77// 0 .. 63 => 0
78// 64 .. 127 => 1
79// 128 .. 191 => 2
80// 192 .. 255 => 3
81RGB222::RGB222(RGB888 const & value)
82{
83 if (lowBitOnly) {
84 R = value.R ? 3 : 0;
85 G = value.G ? 3 : 0;
86 B = value.B ? 3 : 0;
87 } else {
88 R = value.R >> 6;
89 G = value.G >> 6;
90 B = value.B >> 6;
91 }
92}
93
94
95
98// RGB888 implementation
99
100
101RGB888::RGB888(Color color)
102{
103 *this = COLOR2RGB888[(int)color];
104}
105
106
107
110// RGB888toPackedRGB222()
111
112uint8_t RGB888toPackedRGB222(RGB888 const & rgb)
113{
114 // 64 colors
115 static const int CONVR64[4] = { 0 << 0, // 00XXXXXX (0..63)
116 1 << 0, // 01XXXXXX (64..127)
117 2 << 0, // 10XXXXXX (128..191)
118 3 << 0, }; // 11XXXXXX (192..255)
119 static const int CONVG64[4] = { 0 << 2, // 00XXXXXX (0..63)
120 1 << 2, // 01XXXXXX (64..127)
121 2 << 2, // 10XXXXXX (128..191)
122 3 << 2, }; // 11XXXXXX (192..255)
123 static const int CONVB64[4] = { 0 << 4, // 00XXXXXX (0..63)
124 1 << 4, // 01XXXXXX (64..127)
125 2 << 4, // 10XXXXXX (128..191)
126 3 << 4, }; // 11XXXXXX (192..255)
127 // 8 colors
128 static const int CONVR8[4] = { 0 << 0, // 00XXXXXX (0..63)
129 3 << 0, // 01XXXXXX (64..127)
130 3 << 0, // 10XXXXXX (128..191)
131 3 << 0, }; // 11XXXXXX (192..255)
132 static const int CONVG8[4] = { 0 << 2, // 00XXXXXX (0..63)
133 3 << 2, // 01XXXXXX (64..127)
134 3 << 2, // 10XXXXXX (128..191)
135 3 << 2, }; // 11XXXXXX (192..255)
136 static const int CONVB8[4] = { 0 << 4, // 00XXXXXX (0..63)
137 3 << 4, // 01XXXXXX (64..127)
138 3 << 4, // 10XXXXXX (128..191)
139 3 << 4, }; // 11XXXXXX (192..255)
140
141 if (RGB222::lowBitOnly)
142 return (CONVR8[rgb.R >> 6]) | (CONVG8[rgb.G >> 6]) | (CONVB8[rgb.B >> 6]);
143 else
144 return (CONVR64[rgb.R >> 6]) | (CONVG64[rgb.G >> 6]) | (CONVB64[rgb.B >> 6]);
145}
146
147
148
151// Sprite implementation
152
153
154Sprite::Sprite()
155{
156 x = 0;
157 y = 0;
158 currentFrame = 0;
159 frames = nullptr;
160 framesCount = 0;
161 savedBackgroundWidth = 0;
162 savedBackgroundHeight = 0;
163 savedBackground = nullptr; // allocated or reallocated when bitmaps are added
164 savedX = 0;
165 savedY = 0;
166 collisionDetectorObject = nullptr;
167 visible = true;
168 isStatic = false;
169 allowDraw = true;
170}
171
172
173Sprite::~Sprite()
174{
175 free(frames);
176 free(savedBackground);
177}
178
179
180void Sprite::clearBitmaps()
181{
182 free(frames);
183 frames = nullptr;
184 framesCount = 0;
185}
186
187
188Sprite * Sprite::addBitmap(Bitmap * bitmap)
189{
190 ++framesCount;
191 frames = (Bitmap**) realloc(frames, sizeof(Bitmap*) * framesCount);
192 frames[framesCount - 1] = bitmap;
193 return this;
194}
195
196
197Sprite * Sprite::addBitmap(Bitmap * bitmap[], int count)
198{
199 frames = (Bitmap**) realloc(frames, sizeof(Bitmap*) * (framesCount + count));
200 for (int i = 0; i < count; ++i)
201 frames[framesCount + i] = bitmap[i];
202 framesCount += count;
203 return this;
204}
205
206
207Sprite * Sprite::moveBy(int offsetX, int offsetY)
208{
209 x += offsetX;
210 y += offsetY;
211 return this;
212}
213
214
215Sprite * Sprite::moveBy(int offsetX, int offsetY, int wrapAroundWidth, int wrapAroundHeight)
216{
217 x += offsetX;
218 y += offsetY;
219 if (x > wrapAroundWidth)
220 x = - (int) getWidth();
221 if (x < - (int) getWidth())
222 x = wrapAroundWidth;
223 if (y > wrapAroundHeight)
224 y = - (int) getHeight();
225 if (y < - (int) getHeight())
226 y = wrapAroundHeight;
227 return this;
228}
229
230
231Sprite * Sprite::moveTo(int x, int y)
232{
233 this->x = x;
234 this->y = y;
235 return this;
236}
237
238
239
242// Bitmap implementation
243
244
245
246Bitmap::Bitmap(int width_, int height_, void const * data_, PixelFormat format_, RGB888 foregroundColor_, bool copy)
247 : width(width_),
248 height(height_),
249 format(format_),
250 foregroundColor(foregroundColor_),
251 data((uint8_t*)data_),
252 dataAllocated(false)
253{
254 if (copy) {
255 allocate();
256 copyFrom(data_);
257 }
258}
259
260
261Bitmap::Bitmap(int width_, int height_, void const * data_, PixelFormat format_, bool copy)
262 : Bitmap(width_, height_, data_, format_, RGB888(255, 255, 255), copy)
263{
264}
265
266
267void Bitmap::allocate()
268{
269 if (dataAllocated) {
270 free((void*)data);
271 data = nullptr;
272 }
273 dataAllocated = true;
274 switch (format) {
275 case PixelFormat::Undefined:
276 case PixelFormat::Native:
277 break;
278 case PixelFormat::Mask:
279 data = (uint8_t*) heap_caps_malloc((width + 7) / 8 * height, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
280 break;
281 case PixelFormat::RGBA2222:
282 data = (uint8_t*) heap_caps_malloc(width * height, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
283 break;
284 case PixelFormat::RGBA8888:
285 data = (uint8_t*) heap_caps_malloc(width * height * 4, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
286 break;
287 }
288}
289
290
291// data must have the same pixel format
292void Bitmap::copyFrom(void const * srcData)
293{
294 switch (format) {
295 case PixelFormat::Undefined:
296 case PixelFormat::Native:
297 break;
298 case PixelFormat::Mask:
299 memcpy(data, srcData, (width + 7) / 8 * height);
300 break;
301 case PixelFormat::RGBA2222:
302 memcpy(data, srcData, width * height);
303 break;
304 case PixelFormat::RGBA8888:
305 memcpy(data, srcData, width * height * 4);
306 break;
307 }
308}
309
310
311void Bitmap::setPixel(int x, int y, int value)
312{
313 int rowlen = (width + 7) / 8;
314 uint8_t * rowptr = data + y * rowlen;
315 if (value)
316 rowptr[x >> 3] |= 0x80 >> (x & 7);
317 else
318 rowptr[x >> 3] &= ~(0x80 >> (x & 7));
319}
320
321
322void Bitmap::setPixel(int x, int y, RGBA2222 value)
323{
324 ((RGBA2222*)data)[y * width + x] = value;
325}
326
327
328void Bitmap::setPixel(int x, int y, RGBA8888 value)
329{
330 ((RGBA8888*)data)[y * width + x] = value;
331}
332
333
334int Bitmap::getAlpha(int x, int y)
335{
336 int r = 0;
337 switch (format) {
338 case PixelFormat::Undefined:
339 break;
340 case PixelFormat::Native:
341 r = 0xff;
342 break;
343 case PixelFormat::Mask:
344 {
345 int rowlen = (width + 7) / 8;
346 uint8_t * rowptr = data + y * rowlen;
347 r = (rowptr[x >> 3] >> (7 - (x & 7))) & 1;
348 break;
349 }
350 case PixelFormat::RGBA2222:
351 r = ((RGBA2222*)data)[y * height + x].A;
352 break;
353 case PixelFormat::RGBA8888:
354 r = ((RGBA8888*)data)[y * height + x].A;
355 break;
356 }
357 return r;
358}
359
360
361Bitmap::~Bitmap()
362{
363 if (dataAllocated)
364 heap_caps_free((void*)data);
365}
366
367
368
371// BitmappedDisplayController implementation
372
373
374int BitmappedDisplayController::queueSize = FABGLIB_DEFAULT_DISPLAYCONTROLLER_QUEUE_SIZE;
375
376
377BitmappedDisplayController::BitmappedDisplayController()
378 : m_primDynMemPool(FABGLIB_PRIMITIVES_DYNBUFFERS_SIZE)
379{
380 m_execQueue = nullptr;
381 m_backgroundPrimitiveExecutionEnabled = true;
382 m_sprites = nullptr;
383 m_spritesCount = 0;
384 m_doubleBuffered = false;
385 m_mouseCursor.visible = false;
386 m_backgroundPrimitiveTimeoutEnabled = true;
387 m_spritesHidden = true;
388}
389
390
391BitmappedDisplayController::~BitmappedDisplayController()
392{
393 vQueueDelete(m_execQueue);
394}
395
396
397void BitmappedDisplayController::setDoubleBuffered(bool value)
398{
399 m_doubleBuffered = value;
400 if (m_execQueue)
401 vQueueDelete(m_execQueue);
402 // on double buffering a queue of single element is enough and necessary (see addPrimitive() for details)
403 m_execQueue = xQueueCreate(value ? 1 : BitmappedDisplayController::queueSize, sizeof(Primitive));
404}
405
406
407void IRAM_ATTR BitmappedDisplayController::resetPaintState()
408{
409 m_paintState.penColor = RGB888(255, 255, 255);
410 m_paintState.brushColor = RGB888(0, 0, 0);
411 m_paintState.position = Point(0, 0);
412 m_paintState.glyphOptions.value = 0; // all options: 0
413 m_paintState.paintOptions = PaintOptions();
414 m_paintState.scrollingRegion = Rect(0, 0, getViewPortWidth() - 1, getViewPortHeight() - 1);
415 m_paintState.origin = Point(0, 0);
416 m_paintState.clippingRect = Rect(0, 0, getViewPortWidth() - 1, getViewPortHeight() - 1);
417 m_paintState.absClippingRect = m_paintState.clippingRect;
418 m_paintState.penWidth = 1;
419 m_paintState.lineEnds = LineEnds::None;
420}
421
422
423void BitmappedDisplayController::addPrimitive(Primitive & primitive)
424{
425 if ((m_backgroundPrimitiveExecutionEnabled && m_doubleBuffered == false) || primitive.cmd == PrimitiveCmd::SwapBuffers) {
426 primitiveReplaceDynamicBuffers(primitive);
427 xQueueSendToBack(m_execQueue, &primitive, portMAX_DELAY);
428
429 if (m_doubleBuffered) {
430 // wait notufy from PrimitiveCmd::SwapBuffers executor
431 ulTaskNotifyTake(true, portMAX_DELAY);
432 }
433
434 } else {
435 Rect updateRect = Rect(SHRT_MAX, SHRT_MAX, SHRT_MIN, SHRT_MIN);
436 execPrimitive(primitive, updateRect, false);
437 showSprites(updateRect);
438 }
439}
440
441
442// some primitives require additional buffers (like drawPath and fillPath).
443// this function copies primitive data into an allocated buffer (using LightMemoryPool allocator) that
444// will be released inside primitive drawing code.
445void BitmappedDisplayController::primitiveReplaceDynamicBuffers(Primitive & primitive)
446{
447 switch (primitive.cmd) {
448 case PrimitiveCmd::DrawPath:
449 case PrimitiveCmd::FillPath:
450 {
451 int sz = primitive.path.pointsCount * sizeof(Point);
453 void * newbuf = nullptr;
454 // wait until we have enough free space
455 while ((newbuf = m_primDynMemPool.alloc(sz)) == nullptr)
456 taskYIELD();
457 memcpy(newbuf, primitive.path.points, sz);
458 primitive.path.points = (Point*)newbuf;
459 primitive.path.freePoints = true;
460 }
461 break;
462 }
463
464 default:
465 break;
466 }
467}
468
469
470// call this only inside an ISR
471bool IRAM_ATTR BitmappedDisplayController::getPrimitiveISR(Primitive * primitive)
472{
473 return xQueueReceiveFromISR(m_execQueue, primitive, nullptr);
474}
475
476
477bool BitmappedDisplayController::getPrimitive(Primitive * primitive, int timeOutMS)
478{
479 return xQueueReceive(m_execQueue, primitive, msToTicks(timeOutMS));
480}
481
482
483// cannot be called inside an ISR
484void BitmappedDisplayController::waitForPrimitives()
485{
486 Primitive p;
487 xQueuePeek(m_execQueue, &p, portMAX_DELAY);
488}
489
490
491void BitmappedDisplayController::primitivesExecutionWait()
492{
493 if (m_backgroundPrimitiveExecutionEnabled) {
494 while (uxQueueMessagesWaiting(m_execQueue) > 0)
495 ;
496 }
497}
498
499
500// When false primitives are executed immediately, otherwise they are added to the primitive queue
501// When set to false the queue is emptied executing all pending primitives
502// Cannot be nested
503void BitmappedDisplayController::enableBackgroundPrimitiveExecution(bool value)
504{
505 if (value != m_backgroundPrimitiveExecutionEnabled) {
506 if (value) {
507 resumeBackgroundPrimitiveExecution();
508 } else {
509 suspendBackgroundPrimitiveExecution();
510 processPrimitives();
511 }
512 m_backgroundPrimitiveExecutionEnabled = value;
513 }
514}
515
516
517// Use for fast queue processing. Warning, may generate flickering because don't care of vertical sync
518// Do not call inside ISR
519void IRAM_ATTR BitmappedDisplayController::processPrimitives()
520{
521 suspendBackgroundPrimitiveExecution();
522 Rect updateRect = Rect(SHRT_MAX, SHRT_MAX, SHRT_MIN, SHRT_MIN);
523 Primitive prim;
524 while (xQueueReceive(m_execQueue, &prim, 0) == pdTRUE)
525 execPrimitive(prim, updateRect, false);
526 showSprites(updateRect);
527 resumeBackgroundPrimitiveExecution();
528 Primitive p(PrimitiveCmd::Refresh, updateRect);
529 addPrimitive(p);
530}
531
532
533void BitmappedDisplayController::setSprites(Sprite * sprites, int count, int spriteSize)
534{
535 processPrimitives();
536 primitivesExecutionWait();
537 m_sprites = sprites;
538 m_spriteSize = spriteSize;
539 m_spritesCount = count;
540
541 // allocates background buffer
542 if (!isDoubleBuffered()) {
543 uint8_t * spritePtr = (uint8_t*)m_sprites;
544 for (int i = 0; i < m_spritesCount; ++i, spritePtr += m_spriteSize) {
545 Sprite * sprite = (Sprite*) spritePtr;
546 int reqBackBufferSize = 0;
547 for (int i = 0; i < sprite->framesCount; ++i)
548 reqBackBufferSize = tmax(reqBackBufferSize, sprite->frames[i]->width * getBitmapSavePixelSize() * sprite->frames[i]->height);
549 if (reqBackBufferSize > 0)
550 sprite->savedBackground = (uint8_t*) realloc(sprite->savedBackground, reqBackBufferSize);
551 }
552 }
553}
554
555
556Sprite * IRAM_ATTR BitmappedDisplayController::getSprite(int index)
557{
558 return (Sprite*) ((uint8_t*)m_sprites + index * m_spriteSize);
559}
560
561
562void BitmappedDisplayController::refreshSprites()
563{
564 Primitive p(PrimitiveCmd::RefreshSprites);
565 addPrimitive(p);
566}
567
568
569void IRAM_ATTR BitmappedDisplayController::hideSprites(Rect & updateRect)
570{
571 if (!m_spritesHidden) {
572 m_spritesHidden = true;
573
574 // normal sprites
575 if (spritesCount() > 0 && !isDoubleBuffered()) {
576 // restore saved backgrounds
577 for (int i = spritesCount() - 1; i >= 0; --i) {
578 Sprite * sprite = getSprite(i);
579 if (sprite->allowDraw && sprite->savedBackgroundWidth > 0) {
580 int savedX = sprite->savedX;
581 int savedY = sprite->savedY;
582 int savedWidth = sprite->savedBackgroundWidth;
583 int savedHeight = sprite->savedBackgroundHeight;
584 Bitmap bitmap(savedWidth, savedHeight, sprite->savedBackground, PixelFormat::Native);
585 absDrawBitmap(savedX, savedY, &bitmap, nullptr, true);
586 updateRect = updateRect.merge(Rect(savedX, savedY, savedX + savedWidth - 1, savedY + savedHeight - 1));
587 sprite->savedBackgroundWidth = sprite->savedBackgroundHeight = 0;
588 }
589 }
590 }
591
592 // mouse cursor sprite
593 Sprite * mouseSprite = mouseCursor();
594 if (mouseSprite->savedBackgroundWidth > 0) {
595 int savedX = mouseSprite->savedX;
596 int savedY = mouseSprite->savedY;
597 int savedWidth = mouseSprite->savedBackgroundWidth;
598 int savedHeight = mouseSprite->savedBackgroundHeight;
599 Bitmap bitmap(savedWidth, savedHeight, mouseSprite->savedBackground, PixelFormat::Native);
600 absDrawBitmap(savedX, savedY, &bitmap, nullptr, true);
601 updateRect = updateRect.merge(Rect(savedX, savedY, savedX + savedWidth - 1, savedY + savedHeight - 1));
602 mouseSprite->savedBackgroundWidth = mouseSprite->savedBackgroundHeight = 0;
603 }
604
605 }
606}
607
608
609void IRAM_ATTR BitmappedDisplayController::showSprites(Rect & updateRect)
610{
611 if (m_spritesHidden) {
612 m_spritesHidden = false;
613
614 // normal sprites
615 // save backgrounds and draw sprites
616 for (int i = 0; i < spritesCount(); ++i) {
617 Sprite * sprite = getSprite(i);
618 if (sprite->visible && sprite->allowDraw && sprite->getFrame()) {
619 // save sprite X and Y so other threads can change them without interferring
620 int spriteX = sprite->x;
621 int spriteY = sprite->y;
622 Bitmap const * bitmap = sprite->getFrame();
623 int bitmapWidth = bitmap->width;
624 int bitmapHeight = bitmap->height;
625 absDrawBitmap(spriteX, spriteY, bitmap, sprite->savedBackground, true);
626 sprite->savedX = spriteX;
627 sprite->savedY = spriteY;
628 sprite->savedBackgroundWidth = bitmapWidth;
629 sprite->savedBackgroundHeight = bitmapHeight;
630 if (sprite->isStatic)
631 sprite->allowDraw = false;
632 updateRect = updateRect.merge(Rect(spriteX, spriteY, spriteX + bitmapWidth - 1, spriteY + bitmapHeight - 1));
633 }
634 }
635
636 // mouse cursor sprite
637 // save backgrounds and draw mouse cursor
638 Sprite * mouseSprite = mouseCursor();
639 if (mouseSprite->visible && mouseSprite->getFrame()) {
640 // save sprite X and Y so other threads can change them without interferring
641 int spriteX = mouseSprite->x;
642 int spriteY = mouseSprite->y;
643 Bitmap const * bitmap = mouseSprite->getFrame();
644 int bitmapWidth = bitmap->width;
645 int bitmapHeight = bitmap->height;
646 absDrawBitmap(spriteX, spriteY, bitmap, mouseSprite->savedBackground, true);
647 mouseSprite->savedX = spriteX;
648 mouseSprite->savedY = spriteY;
649 mouseSprite->savedBackgroundWidth = bitmapWidth;
650 mouseSprite->savedBackgroundHeight = bitmapHeight;
651 updateRect = updateRect.merge(Rect(spriteX, spriteY, spriteX + bitmapWidth - 1, spriteY + bitmapHeight - 1));
652 }
653
654 }
655}
656
657
658// cursor = nullptr -> disable mouse
659void BitmappedDisplayController::setMouseCursor(Cursor * cursor)
660{
661 if (cursor == nullptr || &cursor->bitmap != m_mouseCursor.getFrame()) {
662 m_mouseCursor.visible = false;
663 m_mouseCursor.clearBitmaps();
664
665 refreshSprites();
666 processPrimitives();
667 primitivesExecutionWait();
668
669 if (cursor) {
670 m_mouseCursor.moveBy(+m_mouseHotspotX, +m_mouseHotspotY);
671 m_mouseHotspotX = cursor->hotspotX;
672 m_mouseHotspotY = cursor->hotspotY;
673 m_mouseCursor.addBitmap(&cursor->bitmap);
674 m_mouseCursor.visible = true;
675 m_mouseCursor.moveBy(-m_mouseHotspotX, -m_mouseHotspotY);
676 if (!isDoubleBuffered())
677 m_mouseCursor.savedBackground = (uint8_t*) realloc(m_mouseCursor.savedBackground, cursor->bitmap.width * getBitmapSavePixelSize() * cursor->bitmap.height);
678 }
679 refreshSprites();
680 }
681}
682
683
684void BitmappedDisplayController::setMouseCursor(CursorName cursorName)
685{
686 setMouseCursor(&CURSORS[(int)cursorName]);
687}
688
689
690void BitmappedDisplayController::setMouseCursorPos(int X, int Y)
691{
692 m_mouseCursor.moveTo(X - m_mouseHotspotX, Y - m_mouseHotspotY);
693 refreshSprites();
694}
695
696
697void IRAM_ATTR BitmappedDisplayController::execPrimitive(Primitive const & prim, Rect & updateRect, bool insideISR)
698{
699 switch (prim.cmd) {
700 case PrimitiveCmd::Flush:
701 break;
702 case PrimitiveCmd::Refresh:
703 updateRect = updateRect.merge(prim.rect);
704 break;
705 case PrimitiveCmd::Reset:
706 resetPaintState();
707 break;
708 case PrimitiveCmd::SetPenColor:
709 paintState().penColor = prim.color;
710 break;
711 case PrimitiveCmd::SetBrushColor:
712 paintState().brushColor = prim.color;
713 break;
714 case PrimitiveCmd::SetPixel:
715 setPixelAt( (PixelDesc) { prim.position, getActualPenColor() }, updateRect );
716 break;
717 case PrimitiveCmd::SetPixelAt:
718 setPixelAt(prim.pixelDesc, updateRect);
719 break;
720 case PrimitiveCmd::MoveTo:
721 paintState().position = Point(prim.position.X + paintState().origin.X, prim.position.Y + paintState().origin.Y);
722 break;
723 case PrimitiveCmd::LineTo:
724 lineTo(prim.position, updateRect);
725 break;
726 case PrimitiveCmd::FillRect:
727 fillRect(prim.rect, getActualBrushColor(), updateRect);
728 break;
729 case PrimitiveCmd::DrawRect:
730 drawRect(prim.rect, updateRect);
731 break;
732 case PrimitiveCmd::FillEllipse:
733 fillEllipse(paintState().position.X, paintState().position.Y, prim.size, getActualBrushColor(), updateRect);
734 break;
735 case PrimitiveCmd::DrawEllipse:
736 drawEllipse(prim.size, updateRect);
737 break;
738 case PrimitiveCmd::Clear:
739 updateRect = updateRect.merge(Rect(0, 0, getViewPortWidth() - 1, getViewPortHeight() - 1));
740 clear(updateRect);
741 break;
742 case PrimitiveCmd::VScroll:
743 updateRect = updateRect.merge(Rect(paintState().scrollingRegion.X1, paintState().scrollingRegion.Y1, paintState().scrollingRegion.X2, paintState().scrollingRegion.Y2));
744 VScroll(prim.ivalue, updateRect);
745 break;
746 case PrimitiveCmd::HScroll:
747 updateRect = updateRect.merge(Rect(paintState().scrollingRegion.X1, paintState().scrollingRegion.Y1, paintState().scrollingRegion.X2, paintState().scrollingRegion.Y2));
748 HScroll(prim.ivalue, updateRect);
749 break;
750 case PrimitiveCmd::DrawGlyph:
751 drawGlyph(prim.glyph, paintState().glyphOptions, paintState().penColor, paintState().brushColor, updateRect);
752 break;
753 case PrimitiveCmd::SetGlyphOptions:
754 paintState().glyphOptions = prim.glyphOptions;
755 break;
756 case PrimitiveCmd::SetPaintOptions:
757 paintState().paintOptions = prim.paintOptions;
758 break;
759 case PrimitiveCmd::InvertRect:
760 invertRect(prim.rect, updateRect);
761 break;
762 case PrimitiveCmd::CopyRect:
763 copyRect(prim.rect, updateRect);
764 break;
765 case PrimitiveCmd::SetScrollingRegion:
766 paintState().scrollingRegion = prim.rect;
767 break;
768 case PrimitiveCmd::SwapFGBG:
769 swapFGBG(prim.rect, updateRect);
770 break;
771 case PrimitiveCmd::RenderGlyphsBuffer:
772 renderGlyphsBuffer(prim.glyphsBufferRenderInfo, updateRect);
773 break;
774 case PrimitiveCmd::DrawBitmap:
775 drawBitmap(prim.bitmapDrawingInfo, updateRect);
776 break;
777 case PrimitiveCmd::RefreshSprites:
778 hideSprites(updateRect);
779 showSprites(updateRect);
780 break;
781 case PrimitiveCmd::SwapBuffers:
782 swapBuffers();
783 updateRect = updateRect.merge(Rect(0, 0, getViewPortWidth() - 1, getViewPortHeight() - 1));
784 if (insideISR) {
785 vTaskNotifyGiveFromISR(prim.notifyTask, nullptr);
786 } else {
787 xTaskNotifyGive(prim.notifyTask);
788 }
789 break;
790 case PrimitiveCmd::DrawPath:
791 drawPath(prim.path, updateRect);
792 break;
793 case PrimitiveCmd::FillPath:
794 fillPath(prim.path, getActualBrushColor(), updateRect);
795 break;
796 case PrimitiveCmd::SetOrigin:
797 paintState().origin = prim.position;
798 updateAbsoluteClippingRect();
799 break;
800 case PrimitiveCmd::SetClippingRect:
801 paintState().clippingRect = prim.rect;
802 updateAbsoluteClippingRect();
803 break;
804 case PrimitiveCmd::SetPenWidth:
805 paintState().penWidth = imax(1, prim.ivalue);
806 break;
807 case PrimitiveCmd::SetLineEnds:
808 paintState().lineEnds = prim.lineEnds;
809 break;
810 }
811}
812
813
814RGB888 IRAM_ATTR BitmappedDisplayController::getActualBrushColor()
815{
816 return paintState().paintOptions.swapFGBG ? paintState().penColor : paintState().brushColor;
817}
818
819
820RGB888 IRAM_ATTR BitmappedDisplayController::getActualPenColor()
821{
822 return paintState().paintOptions.swapFGBG ? paintState().brushColor : paintState().penColor;
823}
824
825
826void IRAM_ATTR BitmappedDisplayController::lineTo(Point const & position, Rect & updateRect)
827{
828 RGB888 color = getActualPenColor();
829
830 int origX = paintState().origin.X;
831 int origY = paintState().origin.Y;
832 int x1 = paintState().position.X;
833 int y1 = paintState().position.Y;
834 int x2 = position.X + origX;
835 int y2 = position.Y + origY;
836
837 int hw = paintState().penWidth / 2;
838 updateRect = updateRect.merge(Rect(imin(x1, x2) - hw, imin(y1, y2) - hw, imax(x1, x2) + hw, imax(y1, y2) + hw));
839 hideSprites(updateRect);
840 absDrawLine(x1, y1, x2, y2, color);
841
842 paintState().position = Point(x2, y2);
843}
844
845
846void IRAM_ATTR BitmappedDisplayController::updateAbsoluteClippingRect()
847{
848 int X1 = iclamp(paintState().origin.X + paintState().clippingRect.X1, 0, getViewPortWidth() - 1);
849 int Y1 = iclamp(paintState().origin.Y + paintState().clippingRect.Y1, 0, getViewPortHeight() - 1);
850 int X2 = iclamp(paintState().origin.X + paintState().clippingRect.X2, 0, getViewPortWidth() - 1);
851 int Y2 = iclamp(paintState().origin.Y + paintState().clippingRect.Y2, 0, getViewPortHeight() - 1);
852 paintState().absClippingRect = Rect(X1, Y1, X2, Y2);
853}
854
855
856void IRAM_ATTR BitmappedDisplayController::drawRect(Rect const & rect, Rect & updateRect)
857{
858 int x1 = (rect.X1 < rect.X2 ? rect.X1 : rect.X2) + paintState().origin.X;
859 int y1 = (rect.Y1 < rect.Y2 ? rect.Y1 : rect.Y2) + paintState().origin.Y;
860 int x2 = (rect.X1 < rect.X2 ? rect.X2 : rect.X1) + paintState().origin.X;
861 int y2 = (rect.Y1 < rect.Y2 ? rect.Y2 : rect.Y1) + paintState().origin.Y;
862
863 int hw = paintState().penWidth / 2;
864 updateRect = updateRect.merge(Rect(x1 - hw, y1 - hw, x2 + hw, y2 + hw));
865 hideSprites(updateRect);
866 RGB888 color = getActualPenColor();
867
868 absDrawLine(x1 + 1, y1, x2, y1, color);
869 absDrawLine(x2, y1 + 1, x2, y2, color);
870 absDrawLine(x2 - 1, y2, x1, y2, color);
871 absDrawLine(x1, y2 - 1, x1, y1, color);
872}
873
874
875void IRAM_ATTR BitmappedDisplayController::fillRect(Rect const & rect, RGB888 const & color, Rect & updateRect)
876{
877 int x1 = (rect.X1 < rect.X2 ? rect.X1 : rect.X2) + paintState().origin.X;
878 int y1 = (rect.Y1 < rect.Y2 ? rect.Y1 : rect.Y2) + paintState().origin.Y;
879 int x2 = (rect.X1 < rect.X2 ? rect.X2 : rect.X1) + paintState().origin.X;
880 int y2 = (rect.Y1 < rect.Y2 ? rect.Y2 : rect.Y1) + paintState().origin.Y;
881
882 const int clipX1 = paintState().absClippingRect.X1;
883 const int clipY1 = paintState().absClippingRect.Y1;
884 const int clipX2 = paintState().absClippingRect.X2;
885 const int clipY2 = paintState().absClippingRect.Y2;
886
887 if (x1 > clipX2 || x2 < clipX1 || y1 > clipY2 || y2 < clipY1)
888 return;
889
890 x1 = iclamp(x1, clipX1, clipX2);
891 y1 = iclamp(y1, clipY1, clipY2);
892 x2 = iclamp(x2, clipX1, clipX2);
893 y2 = iclamp(y2, clipY1, clipY2);
894
895 updateRect = updateRect.merge(Rect(x1, y1, x2, y2));
896 hideSprites(updateRect);
897
898 for (int y = y1; y <= y2; ++y)
899 rawFillRow(y, x1, x2, color);
900}
901
902
903// McIlroy's algorithm
904void IRAM_ATTR BitmappedDisplayController::fillEllipse(int centerX, int centerY, Size const & size, RGB888 const & color, Rect & updateRect)
905{
906 const int clipX1 = paintState().absClippingRect.X1;
907 const int clipY1 = paintState().absClippingRect.Y1;
908 const int clipX2 = paintState().absClippingRect.X2;
909 const int clipY2 = paintState().absClippingRect.Y2;
910
911 const int halfWidth = size.width / 2;
912 const int halfHeight = size.height / 2;
913
914 updateRect = updateRect.merge(Rect(centerX - halfWidth, centerY - halfHeight, centerX + halfWidth, centerY + halfHeight));
915 hideSprites(updateRect);
916
917 const int a2 = halfWidth * halfWidth;
918 const int b2 = halfHeight * halfHeight;
919 const int crit1 = -(a2 / 4 + halfWidth % 2 + b2);
920 const int crit2 = -(b2 / 4 + halfHeight % 2 + a2);
921 const int crit3 = -(b2 / 4 + halfHeight % 2);
922 const int d2xt = 2 * b2;
923 const int d2yt = 2 * a2;
924 int x = 0; // travels from 0 up to halfWidth
925 int y = halfHeight; // travels from halfHeight down to 0
926 int width = 1;
927 int t = -a2 * y;
928 int dxt = 2 * b2 * x;
929 int dyt = -2 * a2 * y;
930
931 while (y >= 0 && x <= halfWidth) {
932 if (t + b2 * x <= crit1 || t + a2 * y <= crit3) {
933 x++;
934 dxt += d2xt;
935 t += dxt;
936 width += 2;
937 } else {
938 int col1 = centerX - x;
939 int col2 = centerX - x + width - 1;
940 if (col1 <= clipX2 && col2 >= clipX1) {
941 col1 = iclamp(col1, clipX1, clipX2);
942 col2 = iclamp(col2, clipX1, clipX2);
943 int row1 = centerY - y;
944 int row2 = centerY + y;
945 if (row1 >= clipY1 && row1 <= clipY2)
946 rawFillRow(row1, col1, col2, color);
947 if (y != 0 && row2 >= clipY1 && row2 <= clipY2)
948 rawFillRow(row2, col1, col2, color);
949 }
950 if (t - a2 * y <= crit2) {
951 x++;
952 dxt += d2xt;
953 t += dxt;
954 width += 2;
955 }
956 y--;
957 dyt += d2yt;
958 t += dyt;
959 }
960 }
961 // one line horizontal ellipse case
962 if (halfHeight == 0 && centerY >= clipY1 && centerY <= clipY2)
963 rawFillRow(centerY, iclamp(centerX - halfWidth, clipX1, clipX2), iclamp(centerX - halfWidth + 2 * halfWidth + 1, clipX1, clipX2), color);
964}
965
966
967void IRAM_ATTR BitmappedDisplayController::renderGlyphsBuffer(GlyphsBufferRenderInfo const & glyphsBufferRenderInfo, Rect & updateRect)
968{
969 int itemX = glyphsBufferRenderInfo.itemX;
970 int itemY = glyphsBufferRenderInfo.itemY;
971
972 int glyphsWidth = glyphsBufferRenderInfo.glyphsBuffer->glyphsWidth;
973 int glyphsHeight = glyphsBufferRenderInfo.glyphsBuffer->glyphsHeight;
974
975 uint32_t const * mapItem = glyphsBufferRenderInfo.glyphsBuffer->map + itemX + itemY * glyphsBufferRenderInfo.glyphsBuffer->columns;
976
977 GlyphOptions glyphOptions = glyphMapItem_getOptions(mapItem);
978 auto fgColor = glyphMapItem_getFGColor(mapItem);
979 auto bgColor = glyphMapItem_getBGColor(mapItem);
980
981 Glyph glyph;
982 glyph.X = (int16_t) (itemX * glyphsWidth * (glyphOptions.doubleWidth ? 2 : 1));
983 glyph.Y = (int16_t) (itemY * glyphsHeight);
984 glyph.width = glyphsWidth;
985 glyph.height = glyphsHeight;
986 glyph.data = glyphsBufferRenderInfo.glyphsBuffer->glyphsData + glyphMapItem_getIndex(mapItem) * glyphsHeight * ((glyphsWidth + 7) / 8);;
987
988 drawGlyph(glyph, glyphOptions, fgColor, bgColor, updateRect);
989}
990
991
992void IRAM_ATTR BitmappedDisplayController::drawPath(Path const & path, Rect & updateRect)
993{
994 RGB888 color = getActualPenColor();
995
996 const int clipX1 = paintState().absClippingRect.X1;
997 const int clipY1 = paintState().absClippingRect.Y1;
998 const int clipX2 = paintState().absClippingRect.X2;
999 const int clipY2 = paintState().absClippingRect.Y2;
1000
1001 int origX = paintState().origin.X;
1002 int origY = paintState().origin.Y;
1003
1004 int minX = clipX1;
1005 int maxX = clipX2 + 1;
1006 int minY = INT_MAX;
1007 int maxY = 0;
1008 for (int i = 0; i < path.pointsCount; ++i) {
1009 int py = path.points[i].Y + origY;
1010 if (py < minY)
1011 minY = py;
1012 if (py > maxY)
1013 maxY = py;
1014 }
1015 minY = tmax(clipY1, minY);
1016 maxY = tmin(clipY2, maxY);
1017
1018 int hw = paintState().penWidth / 2;
1019 updateRect = updateRect.merge(Rect(minX - hw, minY - hw, maxX + hw, maxY + hw));
1020 hideSprites(updateRect);
1021
1022 int i = 0;
1023 for (; i < path.pointsCount - 1; ++i) {
1024 const int x1 = path.points[i].X + origX;
1025 const int y1 = path.points[i].Y + origY;
1026 const int x2 = path.points[i + 1].X + origX;
1027 const int y2 = path.points[i + 1].Y + origY;
1028 absDrawLine(x1, y1, x2, y2, color);
1029 }
1030 const int x1 = path.points[i].X + origX;
1031 const int y1 = path.points[i].Y + origY;
1032 const int x2 = path.points[0].X + origX;
1033 const int y2 = path.points[0].Y + origY;
1034 absDrawLine(x1, y1, x2, y2, color);
1035
1036 if (path.freePoints)
1037 m_primDynMemPool.free((void*)path.points);
1038}
1039
1040
1041void IRAM_ATTR BitmappedDisplayController::fillPath(Path const & path, RGB888 const & color, Rect & updateRect)
1042{
1043 const int clipX1 = paintState().absClippingRect.X1;
1044 const int clipY1 = paintState().absClippingRect.Y1;
1045 const int clipX2 = paintState().absClippingRect.X2;
1046 const int clipY2 = paintState().absClippingRect.Y2;
1047
1048 const int origX = paintState().origin.X;
1049 const int origY = paintState().origin.Y;
1050
1051 int minX = clipX1;
1052 int maxX = clipX2 + 1;
1053 int minY = INT_MAX;
1054 int maxY = 0;
1055 for (int i = 0; i < path.pointsCount; ++i) {
1056 int py = path.points[i].Y + origY;
1057 if (py < minY)
1058 minY = py;
1059 if (py > maxY)
1060 maxY = py;
1061 }
1062 minY = tmax(clipY1, minY);
1063 maxY = tmin(clipY2, maxY);
1064
1065 updateRect = updateRect.merge(Rect(minX, minY, maxX, maxY));
1066 hideSprites(updateRect);
1067
1068 int16_t nodeX[path.pointsCount];
1069
1070 for (int pixelY = minY; pixelY <= maxY; ++pixelY) {
1071
1072 int nodes = 0;
1073 int j = path.pointsCount - 1;
1074 for (int i = 0; i < path.pointsCount; ++i) {
1075 int piy = path.points[i].Y + origY;
1076 int pjy = path.points[j].Y + origY;
1077 if ((piy < pixelY && pjy >= pixelY) || (pjy < pixelY && piy >= pixelY)) {
1078 int pjx = path.points[j].X + origX;
1079 int pix = path.points[i].X + origX;
1080 int a = (pixelY - piy) * (pjx - pix);
1081 int b = (pjy - piy);
1082 nodeX[nodes++] = pix + a / b + (((a < 0) ^ (b > 0)) && (a % b));
1083 }
1084 j = i;
1085 }
1086
1087 int i = 0;
1088 while (i < nodes - 1) {
1089 if (nodeX[i] > nodeX[i + 1]) {
1090 tswap(nodeX[i], nodeX[i + 1]);
1091 if (i)
1092 --i;
1093 } else
1094 ++i;
1095 }
1096
1097 for (int i = 0; i < nodes; i += 2) {
1098 if (nodeX[i] >= maxX)
1099 break;
1100 if (nodeX[i + 1] > minX) {
1101 if (nodeX[i] < minX)
1102 nodeX[i] = minX;
1103 if (nodeX[i + 1] > maxX)
1104 nodeX[i + 1] = maxX;
1105 rawFillRow(pixelY, nodeX[i], nodeX[i + 1] - 1, color);
1106 }
1107 }
1108 }
1109
1110 if (path.freePoints)
1111 m_primDynMemPool.free((void*)path.points);
1112}
1113
1114
1115void IRAM_ATTR BitmappedDisplayController::absDrawThickLine(int X1, int Y1, int X2, int Y2, int penWidth, RGB888 const & color)
1116{
1117 // just to "de-absolutize"
1118 const int origX = paintState().origin.X;
1119 const int origY = paintState().origin.Y;
1120 X1 -= origX;
1121 Y1 -= origY;
1122 X2 -= origX;
1123 Y2 -= origY;
1124
1125 Point pts[4];
1126
1127 const double angle = atan2(Y2 - Y1, X2 - X1);
1128 const double pw = (double)penWidth / 2.0;
1129 const int ofs1 = lround(pw * cos(angle + M_PI_2));
1130 const int ofs2 = lround(pw * sin(angle + M_PI_2));
1131 const int ofs3 = lround(pw * cos(angle - M_PI_2));
1132 const int ofs4 = lround(pw * sin(angle - M_PI_2));
1133 pts[0].X = X1 + ofs1;
1134 pts[0].Y = Y1 + ofs2;
1135 pts[1].X = X1 + ofs3;
1136 pts[1].Y = Y1 + ofs4;
1137 pts[2].X = X2 + ofs3;
1138 pts[2].Y = Y2 + ofs4;
1139 pts[3].X = X2 + ofs1;
1140 pts[3].Y = Y2 + ofs2;
1141
1142 Rect updateRect;
1143 Path path = { pts, 4, false };
1144 fillPath(path, color, updateRect);
1145
1146 switch (paintState().lineEnds) {
1147 case LineEnds::Circle:
1148 if ((penWidth & 1) == 0)
1149 --penWidth;
1150 fillEllipse(X1, Y1, Size(penWidth, penWidth), color, updateRect);
1151 fillEllipse(X2, Y2, Size(penWidth, penWidth), color, updateRect);
1152 break;
1153 default:
1154 break;
1155 }
1156}
1157
1158
1159void IRAM_ATTR BitmappedDisplayController::drawBitmap(BitmapDrawingInfo const & bitmapDrawingInfo, Rect & updateRect)
1160{
1161 int x = bitmapDrawingInfo.X + paintState().origin.X;
1162 int y = bitmapDrawingInfo.Y + paintState().origin.Y;
1163 updateRect = updateRect.merge(Rect(x, y, x + bitmapDrawingInfo.bitmap->width - 1, y + bitmapDrawingInfo.bitmap->height - 1));
1164 hideSprites(updateRect);
1165 absDrawBitmap(x, y, bitmapDrawingInfo.bitmap, nullptr, false);
1166}
1167
1168
1169void IRAM_ATTR BitmappedDisplayController::absDrawBitmap(int destX, int destY, Bitmap const * bitmap, void * saveBackground, bool ignoreClippingRect)
1170{
1171 const int clipX1 = ignoreClippingRect ? 0 : paintState().absClippingRect.X1;
1172 const int clipY1 = ignoreClippingRect ? 0 : paintState().absClippingRect.Y1;
1173 const int clipX2 = ignoreClippingRect ? getViewPortWidth() - 1 : paintState().absClippingRect.X2;
1174 const int clipY2 = ignoreClippingRect ? getViewPortHeight() - 1 : paintState().absClippingRect.Y2;
1175
1176 if (destX > clipX2 || destY > clipY2)
1177 return;
1178
1179 int width = bitmap->width;
1180 int height = bitmap->height;
1181
1182 int X1 = 0;
1183 int XCount = width;
1184
1185 if (destX < clipX1) {
1186 X1 = clipX1 - destX;
1187 destX = clipX1;
1188 }
1189 if (X1 >= width)
1190 return;
1191
1192 if (destX + XCount > clipX2 + 1)
1193 XCount = clipX2 + 1 - destX;
1194 if (X1 + XCount > width)
1195 XCount = width - X1;
1196
1197 int Y1 = 0;
1198 int YCount = height;
1199
1200 if (destY < clipY1) {
1201 Y1 = clipY1 - destY;
1202 destY = clipY1;
1203 }
1204 if (Y1 >= height)
1205 return;
1206
1207 if (destY + YCount > clipY2 + 1)
1208 YCount = clipY2 + 1 - destY;
1209 if (Y1 + YCount > height)
1210 YCount = height - Y1;
1211
1212 switch (bitmap->format) {
1213
1214 case PixelFormat::Undefined:
1215 break;
1216
1217 case PixelFormat::Native:
1218 rawDrawBitmap_Native(destX, destY, bitmap, X1, Y1, XCount, YCount);
1219 break;
1220
1221 case PixelFormat::Mask:
1222 rawDrawBitmap_Mask(destX, destY, bitmap, saveBackground, X1, Y1, XCount, YCount);
1223 break;
1224
1225 case PixelFormat::RGBA2222:
1226 rawDrawBitmap_RGBA2222(destX, destY, bitmap, saveBackground, X1, Y1, XCount, YCount);
1227 break;
1228
1229 case PixelFormat::RGBA8888:
1230 rawDrawBitmap_RGBA8888(destX, destY, bitmap, saveBackground, X1, Y1, XCount, YCount);
1231 break;
1232
1233 }
1234
1235}
1236
1237
1238
1239} // end of namespace
uint8_t width
uint8_t const * data
int16_t X
uint8_t swapFGBG
int16_t Y
uint8_t height
This file contains fabgl::BitmappedDisplayController definition.
#define FABGLIB_PRIMITIVES_DYNBUFFERS_SIZE
Definition: fabglconf.h:62
#define FABGLIB_DEFAULT_DISPLAYCONTROLLER_QUEUE_SIZE
Definition: fabglconf.h:53
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.
Color
This enum defines named colors.
CursorName
This enum defines a set of predefined mouse cursors.
PixelFormat
This enum defines a pixel format.
Represents an image.
Defines a cursor.
Represents a rectangle.
Definition: fabutils.h:248
Represents a sprite.