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