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-2021 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 
43 namespace fabgl {
44 
45 
46 
47 
48 // Array to convert Color enum to RGB888 struct
49 const 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 
74 bool RGB222::lowBitOnly = false;
75 
76 
77 // 0 .. 63 => 0
78 // 64 .. 127 => 1
79 // 128 .. 191 => 2
80 // 192 .. 255 => 3
81 RGB222::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 
101 RGB888::RGB888(Color color)
102 {
103  *this = COLOR2RGB888[(int)color];
104 }
105 
106 
107 
110 // RGB888toPackedRGB222()
111 
112 uint8_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 
154 Sprite::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 
173 Sprite::~Sprite()
174 {
175  free(frames);
176  free(savedBackground);
177 }
178 
179 
180 void Sprite::clearBitmaps()
181 {
182  free(frames);
183  frames = nullptr;
184  framesCount = 0;
185 }
186 
187 
188 Sprite * 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 
197 Sprite * 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 
207 Sprite * Sprite::moveBy(int offsetX, int offsetY)
208 {
209  x += offsetX;
210  y += offsetY;
211  return this;
212 }
213 
214 
215 Sprite * 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 
231 Sprite * 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 
246 Bitmap::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 
261 Bitmap::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 
267 void Bitmap::allocate()
268 {
269  if (dataAllocated) {
270  free((void*)data);
271  data = nullptr;
272  }
273  dataAllocated = true;
274  switch (format) {
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;
282  data = (uint8_t*) heap_caps_malloc(width * height, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
283  break;
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
292 void Bitmap::copyFrom(void const * srcData)
293 {
294  switch (format) {
296  case PixelFormat::Native:
297  break;
298  case PixelFormat::Mask:
299  memcpy(data, srcData, (width + 7) / 8 * height);
300  break;
302  memcpy(data, srcData, width * height);
303  break;
305  memcpy(data, srcData, width * height * 4);
306  break;
307  }
308 }
309 
310 
311 void 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 
322 void Bitmap::setPixel(int x, int y, RGBA2222 value)
323 {
324  ((RGBA2222*)data)[y * width + x] = value;
325 }
326 
327 
328 void Bitmap::setPixel(int x, int y, RGBA8888 value)
329 {
330  ((RGBA8888*)data)[y * width + x] = value;
331 }
332 
333 
334 int Bitmap::getAlpha(int x, int y)
335 {
336  int r = 0;
337  switch (format) {
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  }
351  r = ((RGBA2222*)data)[y * height + x].A;
352  break;
354  r = ((RGBA8888*)data)[y * height + x].A;
355  break;
356  }
357  return r;
358 }
359 
360 
361 Bitmap::~Bitmap()
362 {
363  if (dataAllocated)
364  heap_caps_free((void*)data);
365 }
366 
367 
368 
371 // BitmappedDisplayController implementation
372 
373 
375 
376 
377 BitmappedDisplayController::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 
391 BitmappedDisplayController::~BitmappedDisplayController()
392 {
393  vQueueDelete(m_execQueue);
394 }
395 
396 
397 void 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 
407 void 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 
423 void 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.
445 void 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
471 bool IRAM_ATTR BitmappedDisplayController::getPrimitiveISR(Primitive * primitive)
472 {
473  return xQueueReceiveFromISR(m_execQueue, primitive, nullptr);
474 }
475 
476 
477 bool 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
484 void BitmappedDisplayController::waitForPrimitives()
485 {
486  Primitive p;
487  xQueuePeek(m_execQueue, &p, portMAX_DELAY);
488 }
489 
490 
491 void 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
504 {
505  if (value != m_backgroundPrimitiveExecutionEnabled) {
506  if (value) {
508  } else {
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
520 {
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);
528  Primitive p(PrimitiveCmd::Refresh, updateRect);
529  addPrimitive(p);
530 }
531 
532 
533 void BitmappedDisplayController::setSprites(Sprite * sprites, int count, int spriteSize)
534 {
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 
556 Sprite * IRAM_ATTR BitmappedDisplayController::getSprite(int index)
557 {
558  return (Sprite*) ((uint8_t*)m_sprites + index * m_spriteSize);
559 }
560 
561 
563 {
564  Primitive p(PrimitiveCmd::RefreshSprites);
565  addPrimitive(p);
566 }
567 
568 
569 void 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 
609 void 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
660 {
661  if (cursor == nullptr || &cursor->bitmap != m_mouseCursor.getFrame()) {
662  m_mouseCursor.visible = false;
663  m_mouseCursor.clearBitmaps();
664 
665  refreshSprites();
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 
685 {
686  setMouseCursor(&CURSORS[(int)cursorName]);
687 }
688 
689 
691 {
692  m_mouseCursor.moveTo(X - m_mouseHotspotX, Y - m_mouseHotspotY);
693  refreshSprites();
694 }
695 
696 
697 void 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  break;
789  case PrimitiveCmd::DrawPath:
790  drawPath(prim.path, updateRect);
791  break;
792  case PrimitiveCmd::FillPath:
793  fillPath(prim.path, getActualBrushColor(), updateRect);
794  break;
795  case PrimitiveCmd::SetOrigin:
796  paintState().origin = prim.position;
797  updateAbsoluteClippingRect();
798  break;
799  case PrimitiveCmd::SetClippingRect:
800  paintState().clippingRect = prim.rect;
801  updateAbsoluteClippingRect();
802  break;
803  case PrimitiveCmd::SetPenWidth:
804  paintState().penWidth = imax(1, prim.ivalue);
805  break;
806  case PrimitiveCmd::SetLineEnds:
807  paintState().lineEnds = prim.lineEnds;
808  break;
809  }
810 }
811 
812 
813 RGB888 IRAM_ATTR BitmappedDisplayController::getActualBrushColor()
814 {
815  return paintState().paintOptions.swapFGBG ? paintState().penColor : paintState().brushColor;
816 }
817 
818 
819 RGB888 IRAM_ATTR BitmappedDisplayController::getActualPenColor()
820 {
821  return paintState().paintOptions.swapFGBG ? paintState().brushColor : paintState().penColor;
822 }
823 
824 
825 void IRAM_ATTR BitmappedDisplayController::lineTo(Point const & position, Rect & updateRect)
826 {
827  RGB888 color = getActualPenColor();
828 
829  int origX = paintState().origin.X;
830  int origY = paintState().origin.Y;
831  int x1 = paintState().position.X;
832  int y1 = paintState().position.Y;
833  int x2 = position.X + origX;
834  int y2 = position.Y + origY;
835 
836  int hw = paintState().penWidth / 2;
837  updateRect = updateRect.merge(Rect(imin(x1, x2) - hw, imin(y1, y2) - hw, imax(x1, x2) + hw, imax(y1, y2) + hw));
838  hideSprites(updateRect);
839  absDrawLine(x1, y1, x2, y2, color);
840 
841  paintState().position = Point(x2, y2);
842 }
843 
844 
845 void IRAM_ATTR BitmappedDisplayController::updateAbsoluteClippingRect()
846 {
847  int X1 = iclamp(paintState().origin.X + paintState().clippingRect.X1, 0, getViewPortWidth() - 1);
848  int Y1 = iclamp(paintState().origin.Y + paintState().clippingRect.Y1, 0, getViewPortHeight() - 1);
849  int X2 = iclamp(paintState().origin.X + paintState().clippingRect.X2, 0, getViewPortWidth() - 1);
850  int Y2 = iclamp(paintState().origin.Y + paintState().clippingRect.Y2, 0, getViewPortHeight() - 1);
851  paintState().absClippingRect = Rect(X1, Y1, X2, Y2);
852 }
853 
854 
855 void IRAM_ATTR BitmappedDisplayController::drawRect(Rect const & rect, Rect & updateRect)
856 {
857  int x1 = (rect.X1 < rect.X2 ? rect.X1 : rect.X2) + paintState().origin.X;
858  int y1 = (rect.Y1 < rect.Y2 ? rect.Y1 : rect.Y2) + paintState().origin.Y;
859  int x2 = (rect.X1 < rect.X2 ? rect.X2 : rect.X1) + paintState().origin.X;
860  int y2 = (rect.Y1 < rect.Y2 ? rect.Y2 : rect.Y1) + paintState().origin.Y;
861 
862  int hw = paintState().penWidth / 2;
863  updateRect = updateRect.merge(Rect(x1 - hw, y1 - hw, x2 + hw, y2 + hw));
864  hideSprites(updateRect);
865  RGB888 color = getActualPenColor();
866 
867  absDrawLine(x1 + 1, y1, x2, y1, color);
868  absDrawLine(x2, y1 + 1, x2, y2, color);
869  absDrawLine(x2 - 1, y2, x1, y2, color);
870  absDrawLine(x1, y2 - 1, x1, y1, color);
871 }
872 
873 
874 void IRAM_ATTR BitmappedDisplayController::fillRect(Rect const & rect, RGB888 const & color, Rect & updateRect)
875 {
876  int x1 = (rect.X1 < rect.X2 ? rect.X1 : rect.X2) + paintState().origin.X;
877  int y1 = (rect.Y1 < rect.Y2 ? rect.Y1 : rect.Y2) + paintState().origin.Y;
878  int x2 = (rect.X1 < rect.X2 ? rect.X2 : rect.X1) + paintState().origin.X;
879  int y2 = (rect.Y1 < rect.Y2 ? rect.Y2 : rect.Y1) + paintState().origin.Y;
880 
881  const int clipX1 = paintState().absClippingRect.X1;
882  const int clipY1 = paintState().absClippingRect.Y1;
883  const int clipX2 = paintState().absClippingRect.X2;
884  const int clipY2 = paintState().absClippingRect.Y2;
885 
886  if (x1 > clipX2 || x2 < clipX1 || y1 > clipY2 || y2 < clipY1)
887  return;
888 
889  x1 = iclamp(x1, clipX1, clipX2);
890  y1 = iclamp(y1, clipY1, clipY2);
891  x2 = iclamp(x2, clipX1, clipX2);
892  y2 = iclamp(y2, clipY1, clipY2);
893 
894  updateRect = updateRect.merge(Rect(x1, y1, x2, y2));
895  hideSprites(updateRect);
896 
897  for (int y = y1; y <= y2; ++y)
898  rawFillRow(y, x1, x2, color);
899 }
900 
901 
902 // McIlroy's algorithm
903 void IRAM_ATTR BitmappedDisplayController::fillEllipse(int centerX, int centerY, Size const & size, RGB888 const & color, Rect & updateRect)
904 {
905  const int clipX1 = paintState().absClippingRect.X1;
906  const int clipY1 = paintState().absClippingRect.Y1;
907  const int clipX2 = paintState().absClippingRect.X2;
908  const int clipY2 = paintState().absClippingRect.Y2;
909 
910  const int halfWidth = size.width / 2;
911  const int halfHeight = size.height / 2;
912 
913  updateRect = updateRect.merge(Rect(centerX - halfWidth, centerY - halfHeight, centerX + halfWidth, centerY + halfHeight));
914  hideSprites(updateRect);
915 
916  const int a2 = halfWidth * halfWidth;
917  const int b2 = halfHeight * halfHeight;
918  const int crit1 = -(a2 / 4 + halfWidth % 2 + b2);
919  const int crit2 = -(b2 / 4 + halfHeight % 2 + a2);
920  const int crit3 = -(b2 / 4 + halfHeight % 2);
921  const int d2xt = 2 * b2;
922  const int d2yt = 2 * a2;
923  int x = 0; // travels from 0 up to halfWidth
924  int y = halfHeight; // travels from halfHeight down to 0
925  int width = 1;
926  int t = -a2 * y;
927  int dxt = 2 * b2 * x;
928  int dyt = -2 * a2 * y;
929 
930  while (y >= 0 && x <= halfWidth) {
931  if (t + b2 * x <= crit1 || t + a2 * y <= crit3) {
932  x++;
933  dxt += d2xt;
934  t += dxt;
935  width += 2;
936  } else {
937  int col1 = centerX - x;
938  int col2 = centerX - x + width - 1;
939  if (col1 <= clipX2 && col2 >= clipX1) {
940  col1 = iclamp(col1, clipX1, clipX2);
941  col2 = iclamp(col2, clipX1, clipX2);
942  int row1 = centerY - y;
943  int row2 = centerY + y;
944  if (row1 >= clipY1 && row1 <= clipY2)
945  rawFillRow(row1, col1, col2, color);
946  if (y != 0 && row2 >= clipY1 && row2 <= clipY2)
947  rawFillRow(row2, col1, col2, color);
948  }
949  if (t - a2 * y <= crit2) {
950  x++;
951  dxt += d2xt;
952  t += dxt;
953  width += 2;
954  }
955  y--;
956  dyt += d2yt;
957  t += dyt;
958  }
959  }
960  // one line horizontal ellipse case
961  if (halfHeight == 0 && centerY >= clipY1 && centerY <= clipY2)
962  rawFillRow(centerY, iclamp(centerX - halfWidth, clipX1, clipX2), iclamp(centerX - halfWidth + 2 * halfWidth + 1, clipX1, clipX2), color);
963 }
964 
965 
966 void IRAM_ATTR BitmappedDisplayController::renderGlyphsBuffer(GlyphsBufferRenderInfo const & glyphsBufferRenderInfo, Rect & updateRect)
967 {
968  int itemX = glyphsBufferRenderInfo.itemX;
969  int itemY = glyphsBufferRenderInfo.itemY;
970 
971  int glyphsWidth = glyphsBufferRenderInfo.glyphsBuffer->glyphsWidth;
972  int glyphsHeight = glyphsBufferRenderInfo.glyphsBuffer->glyphsHeight;
973 
974  uint32_t const * mapItem = glyphsBufferRenderInfo.glyphsBuffer->map + itemX + itemY * glyphsBufferRenderInfo.glyphsBuffer->columns;
975 
976  GlyphOptions glyphOptions = glyphMapItem_getOptions(mapItem);
977  auto fgColor = glyphMapItem_getFGColor(mapItem);
978  auto bgColor = glyphMapItem_getBGColor(mapItem);
979 
980  Glyph glyph;
981  glyph.X = (int16_t) (itemX * glyphsWidth * (glyphOptions.doubleWidth ? 2 : 1));
982  glyph.Y = (int16_t) (itemY * glyphsHeight);
983  glyph.width = glyphsWidth;
984  glyph.height = glyphsHeight;
985  glyph.data = glyphsBufferRenderInfo.glyphsBuffer->glyphsData + glyphMapItem_getIndex(mapItem) * glyphsHeight * ((glyphsWidth + 7) / 8);;
986 
987  drawGlyph(glyph, glyphOptions, fgColor, bgColor, updateRect);
988 }
989 
990 
991 void IRAM_ATTR BitmappedDisplayController::drawPath(Path const & path, Rect & updateRect)
992 {
993  RGB888 color = getActualPenColor();
994 
995  const int clipX1 = paintState().absClippingRect.X1;
996  const int clipY1 = paintState().absClippingRect.Y1;
997  const int clipX2 = paintState().absClippingRect.X2;
998  const int clipY2 = paintState().absClippingRect.Y2;
999 
1000  int origX = paintState().origin.X;
1001  int origY = paintState().origin.Y;
1002 
1003  int minX = clipX1;
1004  int maxX = clipX2 + 1;
1005  int minY = INT_MAX;
1006  int maxY = 0;
1007  for (int i = 0; i < path.pointsCount; ++i) {
1008  int py = path.points[i].Y + origY;
1009  if (py < minY)
1010  minY = py;
1011  if (py > maxY)
1012  maxY = py;
1013  }
1014  minY = tmax(clipY1, minY);
1015  maxY = tmin(clipY2, maxY);
1016 
1017  int hw = paintState().penWidth / 2;
1018  updateRect = updateRect.merge(Rect(minX - hw, minY - hw, maxX + hw, maxY + hw));
1019  hideSprites(updateRect);
1020 
1021  int i = 0;
1022  for (; i < path.pointsCount - 1; ++i) {
1023  const int x1 = path.points[i].X + origX;
1024  const int y1 = path.points[i].Y + origY;
1025  const int x2 = path.points[i + 1].X + origX;
1026  const int y2 = path.points[i + 1].Y + origY;
1027  absDrawLine(x1, y1, x2, y2, color);
1028  }
1029  const int x1 = path.points[i].X + origX;
1030  const int y1 = path.points[i].Y + origY;
1031  const int x2 = path.points[0].X + origX;
1032  const int y2 = path.points[0].Y + origY;
1033  absDrawLine(x1, y1, x2, y2, color);
1034 
1035  if (path.freePoints)
1036  m_primDynMemPool.free((void*)path.points);
1037 }
1038 
1039 
1040 void IRAM_ATTR BitmappedDisplayController::fillPath(Path const & path, RGB888 const & color, Rect & updateRect)
1041 {
1042  const int clipX1 = paintState().absClippingRect.X1;
1043  const int clipY1 = paintState().absClippingRect.Y1;
1044  const int clipX2 = paintState().absClippingRect.X2;
1045  const int clipY2 = paintState().absClippingRect.Y2;
1046 
1047  const int origX = paintState().origin.X;
1048  const int origY = paintState().origin.Y;
1049 
1050  int minX = clipX1;
1051  int maxX = clipX2 + 1;
1052  int minY = INT_MAX;
1053  int maxY = 0;
1054  for (int i = 0; i < path.pointsCount; ++i) {
1055  int py = path.points[i].Y + origY;
1056  if (py < minY)
1057  minY = py;
1058  if (py > maxY)
1059  maxY = py;
1060  }
1061  minY = tmax(clipY1, minY);
1062  maxY = tmin(clipY2, maxY);
1063 
1064  updateRect = updateRect.merge(Rect(minX, minY, maxX, maxY));
1065  hideSprites(updateRect);
1066 
1067  int16_t nodeX[path.pointsCount];
1068 
1069  for (int pixelY = minY; pixelY <= maxY; ++pixelY) {
1070 
1071  int nodes = 0;
1072  int j = path.pointsCount - 1;
1073  for (int i = 0; i < path.pointsCount; ++i) {
1074  int piy = path.points[i].Y + origY;
1075  int pjy = path.points[j].Y + origY;
1076  if ((piy < pixelY && pjy >= pixelY) || (pjy < pixelY && piy >= pixelY)) {
1077  int pjx = path.points[j].X + origX;
1078  int pix = path.points[i].X + origX;
1079  int a = (pixelY - piy) * (pjx - pix);
1080  int b = (pjy - piy);
1081  nodeX[nodes++] = pix + a / b + (((a < 0) ^ (b > 0)) && (a % b));
1082  }
1083  j = i;
1084  }
1085 
1086  int i = 0;
1087  while (i < nodes - 1) {
1088  if (nodeX[i] > nodeX[i + 1]) {
1089  tswap(nodeX[i], nodeX[i + 1]);
1090  if (i)
1091  --i;
1092  } else
1093  ++i;
1094  }
1095 
1096  for (int i = 0; i < nodes; i += 2) {
1097  if (nodeX[i] >= maxX)
1098  break;
1099  if (nodeX[i + 1] > minX) {
1100  if (nodeX[i] < minX)
1101  nodeX[i] = minX;
1102  if (nodeX[i + 1] > maxX)
1103  nodeX[i + 1] = maxX;
1104  rawFillRow(pixelY, nodeX[i], nodeX[i + 1] - 1, color);
1105  }
1106  }
1107  }
1108 
1109  if (path.freePoints)
1110  m_primDynMemPool.free((void*)path.points);
1111 }
1112 
1113 
1114 void IRAM_ATTR BitmappedDisplayController::absDrawThickLine(int X1, int Y1, int X2, int Y2, int penWidth, RGB888 const & color)
1115 {
1116  // just to "de-absolutize"
1117  const int origX = paintState().origin.X;
1118  const int origY = paintState().origin.Y;
1119  X1 -= origX;
1120  Y1 -= origY;
1121  X2 -= origX;
1122  Y2 -= origY;
1123 
1124  Point pts[4];
1125 
1126  const double angle = atan2(Y2 - Y1, X2 - X1);
1127  const double pw = (double)penWidth / 2.0;
1128  const int ofs1 = lround(pw * cos(angle + M_PI_2));
1129  const int ofs2 = lround(pw * sin(angle + M_PI_2));
1130  const int ofs3 = lround(pw * cos(angle - M_PI_2));
1131  const int ofs4 = lround(pw * sin(angle - M_PI_2));
1132  pts[0].X = X1 + ofs1;
1133  pts[0].Y = Y1 + ofs2;
1134  pts[1].X = X1 + ofs3;
1135  pts[1].Y = Y1 + ofs4;
1136  pts[2].X = X2 + ofs3;
1137  pts[2].Y = Y2 + ofs4;
1138  pts[3].X = X2 + ofs1;
1139  pts[3].Y = Y2 + ofs2;
1140 
1141  Rect updateRect;
1142  Path path = { pts, 4, false };
1143  fillPath(path, color, updateRect);
1144 
1145  switch (paintState().lineEnds) {
1146  case LineEnds::Circle:
1147  if ((penWidth & 1) == 0)
1148  --penWidth;
1149  fillEllipse(X1, Y1, Size(penWidth, penWidth), color, updateRect);
1150  fillEllipse(X2, Y2, Size(penWidth, penWidth), color, updateRect);
1151  break;
1152  default:
1153  break;
1154  }
1155 }
1156 
1157 
1158 void IRAM_ATTR BitmappedDisplayController::drawBitmap(BitmapDrawingInfo const & bitmapDrawingInfo, Rect & updateRect)
1159 {
1160  int x = bitmapDrawingInfo.X + paintState().origin.X;
1161  int y = bitmapDrawingInfo.Y + paintState().origin.Y;
1162  updateRect = updateRect.merge(Rect(x, y, x + bitmapDrawingInfo.bitmap->width - 1, y + bitmapDrawingInfo.bitmap->height - 1));
1163  hideSprites(updateRect);
1164  absDrawBitmap(x, y, bitmapDrawingInfo.bitmap, nullptr, false);
1165 }
1166 
1167 
1168 void IRAM_ATTR BitmappedDisplayController::absDrawBitmap(int destX, int destY, Bitmap const * bitmap, void * saveBackground, bool ignoreClippingRect)
1169 {
1170  const int clipX1 = ignoreClippingRect ? 0 : paintState().absClippingRect.X1;
1171  const int clipY1 = ignoreClippingRect ? 0 : paintState().absClippingRect.Y1;
1172  const int clipX2 = ignoreClippingRect ? getViewPortWidth() - 1 : paintState().absClippingRect.X2;
1173  const int clipY2 = ignoreClippingRect ? getViewPortHeight() - 1 : paintState().absClippingRect.Y2;
1174 
1175  if (destX > clipX2 || destY > clipY2)
1176  return;
1177 
1178  int width = bitmap->width;
1179  int height = bitmap->height;
1180 
1181  int X1 = 0;
1182  int XCount = width;
1183 
1184  if (destX < clipX1) {
1185  X1 = clipX1 - destX;
1186  destX = clipX1;
1187  }
1188  if (X1 >= width)
1189  return;
1190 
1191  if (destX + XCount > clipX2 + 1)
1192  XCount = clipX2 + 1 - destX;
1193  if (X1 + XCount > width)
1194  XCount = width - X1;
1195 
1196  int Y1 = 0;
1197  int YCount = height;
1198 
1199  if (destY < clipY1) {
1200  Y1 = clipY1 - destY;
1201  destY = clipY1;
1202  }
1203  if (Y1 >= height)
1204  return;
1205 
1206  if (destY + YCount > clipY2 + 1)
1207  YCount = clipY2 + 1 - destY;
1208  if (Y1 + YCount > height)
1209  YCount = height - Y1;
1210 
1211  switch (bitmap->format) {
1212 
1214  break;
1215 
1216  case PixelFormat::Native:
1217  rawDrawBitmap_Native(destX, destY, bitmap, X1, Y1, XCount, YCount);
1218  break;
1219 
1220  case PixelFormat::Mask:
1221  rawDrawBitmap_Mask(destX, destY, bitmap, saveBackground, X1, Y1, XCount, YCount);
1222  break;
1223 
1224  case PixelFormat::RGBA2222:
1225  rawDrawBitmap_RGBA2222(destX, destY, bitmap, saveBackground, X1, Y1, XCount, YCount);
1226  break;
1227 
1228  case PixelFormat::RGBA8888:
1229  rawDrawBitmap_RGBA8888(destX, destY, bitmap, saveBackground, X1, Y1, XCount, YCount);
1230  break;
1231 
1232  }
1233 
1234 }
1235 
1236 
1237 
1238 } // end of namespace
int16_t X2
Definition: fabutils.h:180
Defines a cursor.
Represents a sprite.
void setSprites(T *sprites, int count)
Sets the list of active sprites.
virtual void resumeBackgroundPrimitiveExecution()=0
Resumes drawings after suspendBackgroundPrimitiveExecution().
int16_t Y2
Definition: fabutils.h:181
int16_t Y1
Definition: fabutils.h:179
int16_t Y
This file contains fabgl::BitmappedDisplayController definition.
uint8_t const * data
Color
This enum defines named colors.
int16_t X1
Definition: fabutils.h:178
virtual int getViewPortWidth()=0
Determines horizontal size of the viewport.
virtual int getViewPortHeight()=0
Determines vertical size of the viewport.
PixelFormat
This enum defines a pixel format.
Represents an image.
void refreshSprites()
Forces the sprites to be updated.
This file contains some utility classes and functions.
Definition: canvas.cpp:36
void enableBackgroundPrimitiveExecution(bool value)
Enables or disables drawings inside vertical retracing time.
Represents a rectangle.
Definition: fabutils.h:226
CursorName
This enum defines a set of predefined mouse cursors.
void setMouseCursor(Cursor *cursor)
Sets mouse cursor and make it visible.
static int queueSize
Size of display controller primitives queue.
int16_t X
virtual void suspendBackgroundPrimitiveExecution()=0
Suspends drawings.
void setMouseCursorPos(int X, int Y)
Sets mouse cursor position.
void processPrimitives()
Draws immediately all primitives in the queue.
bool isDoubleBuffered()
Determines whether BitmappedDisplayController is on double buffered mode.
PixelFormat format
#define FABGLIB_PRIMITIVES_DYNBUFFERS_SIZE
Definition: fabglconf.h:62
uint8_t height
#define FABGLIB_DEFAULT_DISPLAYCONTROLLER_QUEUE_SIZE
Definition: fabglconf.h:53
uint8_t width