-
Notifications
You must be signed in to change notification settings - Fork 71
/
DMDFrame.cpp
361 lines (322 loc) · 10.8 KB
/
DMDFrame.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#include "DMD2.h"
/*
DMDFrame class implementation.
Non-hardware specific functions for updating the framebuffer
Copyright (C) 2014 Freetronics, Inc. (info <at> freetronics <dot> com)
Updated by Angus Gratton, based on DMD by Marc Alexander.
---
This program is free software: you can redistribute it and/or modify it under the terms
of the version 3 GNU General Public License as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program.
If not, see <http://www.gnu.org/licenses/>.
*/
DMDFrame::DMDFrame(byte pixelsWide, byte pixelsHigh)
:
width(pixelsWide),
height(pixelsHigh),
font(0)
{
row_width_bytes = (pixelsWide + 7)/8; // on full panels pixelsWide is a multiple of 8, but for sub-regions may not be
height_in_panels = (pixelsHigh + PANEL_HEIGHT-1) / PANEL_HEIGHT;
bitmap = (uint8_t *)malloc(bitmap_bytes());
memset((void *)bitmap, 0xFF, bitmap_bytes());
}
DMDFrame::DMDFrame(const DMDFrame &source) :
width(source.width),
height(source.height),
row_width_bytes(source.row_width_bytes),
height_in_panels(source.height_in_panels),
font(source.font)
{
bitmap = (uint8_t *)malloc(bitmap_bytes());
memcpy((void *)bitmap, (void *)source.bitmap, bitmap_bytes());
}
DMDFrame::~DMDFrame()
{
free((void *)bitmap);
}
void DMDFrame::swapBuffers(DMDFrame &other)
{
#ifdef __AVR__
// AVR can't write pointers atomically, so need to disable interrupts
char oldSREG = SREG;
cli();
#endif
volatile uint8_t *temp = other.bitmap;
other.bitmap = this->bitmap;
this->bitmap = temp;
#ifdef __AVR__
SREG = oldSREG;
#endif
}
// Set a single LED on or off. Remember that the pixel array is inverted (bit set = LED off)
void DMDFrame::setPixel(unsigned int x, unsigned int y, DMDGraphicsMode mode)
{
if(x >= width || y >= height)
return;
int byte_idx = pixelToBitmapIndex(x,y);
uint8_t bit = pixelToBitmask(x);
switch(mode) {
case GRAPHICS_ON:
bitmap[byte_idx] &= ~bit; // and with the inverse of the bit - so
break;
case GRAPHICS_OFF:
bitmap[byte_idx] |= bit; // set bit (which turns it off)
break;
case GRAPHICS_OR:
bitmap[byte_idx] = ~(~bitmap[byte_idx] | bit);
break;
case GRAPHICS_NOR:
bitmap[byte_idx] = (~bitmap[byte_idx] | bit);
break;
case GRAPHICS_XOR:
bitmap[byte_idx] ^= bit;
break;
case GRAPHICS_INVERSE:
case GRAPHICS_NOOP:
break;
}
}
bool DMDFrame::getPixel(unsigned int x, unsigned int y)
{
if(x >= width || y >= height)
return false;
int byte_idx = pixelToBitmapIndex(x,y);
uint8_t bit = pixelToBitmask(x);
bool res = !(bitmap[byte_idx] & bit);
return res;
}
void DMDFrame::debugPixelLine(unsigned int y, char *buf) { // buf must be large enough (2x pixels+EOL+nul), or we'll overrun
char *currentPixel = buf;
for(int x=0;x < width;x++) {
bool set = getPixel(x,y);
if(set) {
*currentPixel='[';
currentPixel++;
*currentPixel=']';
} else {
*currentPixel='_';
currentPixel++;
*currentPixel='_';
}
currentPixel++;
}
*currentPixel ='\n';
currentPixel++;
*currentPixel = 0; // nul terminator
}
void DMDFrame::movePixels(unsigned int from_x, unsigned int from_y,
unsigned int to_x, unsigned int to_y,
unsigned int width, unsigned int height)
{
// NB: This implementation is actually a copy-erase so
// it uses more RAM than a real move implementation would
// do (however bypasses issues around overlapping regions.)
if(from_x >= this->width || from_y >= this->height
|| to_x >= this->width || to_y >= this->height)
return;
DMDFrame to_move = this->subFrame(from_x, from_y, width, height);
this->drawFilledBox(from_x,from_y,from_x+width-1,from_y+height-1,GRAPHICS_OFF);
this->copyFrame(to_move, to_x, to_y);
}
// Set the entire screen
void DMDFrame::fillScreen(bool on)
{
memset((void *)bitmap, on ? 0 : 0xFF, bitmap_bytes());
}
void DMDFrame::drawLine(int x1, int y1, int x2, int y2, DMDGraphicsMode mode)
{
int dy = y2 - y1;
int dx = x2 - x1;
int stepx, stepy;
if (dy < 0) {
dy = -dy;
stepy = -1;
} else {
stepy = 1;
}
if (dx < 0) {
dx = -dx;
stepx = -1;
} else {
stepx = 1;
}
dy = dy * 2;
dx = dx * 2;
setPixel(x1, y1, mode);
if (dx > dy) {
int fraction = dy - (dx / 2); // same as 2*dy - dx
while (x1 != x2) {
if (fraction >= 0) {
y1 += stepy;
fraction -= dx; // same as fraction -= 2*dx
}
x1 += stepx;
fraction += dy; // same as fraction -= 2*dy
setPixel(x1, y1, mode);
}
} else {
int fraction = dx - (dy / 2);
while (y1 != y2) {
if (fraction >= 0) {
x1 += stepx;
fraction -= dy;
}
y1 += stepy;
fraction += dx;
setPixel(x1, y1, mode);
}
}
}
void DMDFrame::drawCircle(unsigned int xCenter, unsigned int yCenter, int radius, DMDGraphicsMode mode)
{
// Bresenham's circle drawing algorithm
int x = -radius;
int y = 0;
int error = 2-2*radius;
while(x < 0) {
setPixel(xCenter-x, yCenter+y, mode);
setPixel(xCenter-y, yCenter-x, mode);
setPixel(xCenter+x, yCenter-y, mode);
setPixel(xCenter+y, yCenter+x, mode);
radius = error;
if (radius <= y) error += ++y*2+1;
if (radius > x || error > y) error += ++x*2+1;
}
}
void DMDFrame::drawBox(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, DMDGraphicsMode mode)
{
drawLine(x1, y1, x2, y1, mode);
drawLine(x2, y1, x2, y2, mode);
drawLine(x2, y2, x1, y2, mode);
drawLine(x1, y2, x1, y1, mode);
}
void DMDFrame::drawFilledBox(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, DMDGraphicsMode mode)
{
for (unsigned int b = x1; b <= x2; b++) {
drawLine(b, y1, b, y2, mode);
}
}
void DMDFrame::scrollY(int scrollBy) {
if(abs(scrollBy) >= height) { // scrolling over the whole display
// scrolling will erase everything
drawFilledBox(0, 0, width-1, height-1, GRAPHICS_OFF);
}
else if(scrollBy < 0) { // Scroll up
movePixels(0, -scrollBy, 0, 0, width, height + scrollBy);
drawFilledBox(0, height+scrollBy, width, height, GRAPHICS_OFF);
}
else if(scrollBy > 0) { // Scroll down
movePixels(0, 0, 0, scrollBy, width, height - scrollBy);
drawFilledBox(0, 0, width, scrollBy, GRAPHICS_OFF);
}
}
void DMDFrame::scrollX(int scrollBy) {
if(abs(scrollBy) >= width) { // scrolling over the whole display!
// scrolling will erase everything
drawFilledBox(0, 0, width-1, height-1, GRAPHICS_OFF);
}
else if(scrollBy < 0) { // Scroll left
movePixels(-scrollBy, 0, 0, 0, width + scrollBy, height);
drawFilledBox(width+scrollBy, 0, width, height, GRAPHICS_OFF);
}
else { // Scroll right
movePixels(0, 0, scrollBy, 0, width - scrollBy, height);
drawFilledBox(0, 0, scrollBy, height, GRAPHICS_OFF);
}
}
void DMDFrame::marqueeScrollX(int scrollBy) {
// Scrolling is basically the same as normal scrolling, but we save/restore the overlapping
// area in between to create the marquee effect
scrollBy = scrollBy % width;
if(scrollBy < 0) { // Scroll left
DMDFrame frame = subFrame(0, 0, -scrollBy, height); // save leftmost
movePixels(-scrollBy, 0, 0, 0, width + scrollBy, height); // move
copyFrame(frame, width+scrollBy, 0); // drop back at right edge
} else { // Scroll right
DMDFrame frame = subFrame(width-scrollBy, 0, scrollBy, height); // save rightmost
movePixels(0, 0, scrollBy, 0, width - scrollBy, height); // move
copyFrame(frame, 0, 0); // drop back at left edge
}
}
void DMDFrame::marqueeScrollY(int scrollBy) {
scrollBy = scrollBy % height;
if(scrollBy < 0) { // Scroll up
DMDFrame frame = subFrame(0, 0, width, -scrollBy); // save topmost
movePixels(0, -scrollBy, 0, 0, width, height+scrollBy); // move
copyFrame(frame, 0, height+scrollBy); // drop back at bottom edge
} else { // Scroll down
DMDFrame frame = subFrame(0, height-scrollBy, width, scrollBy); // save bottommost
movePixels(0, 0, 0, scrollBy, width, height-scrollBy); // move
copyFrame(frame, 0, 0); // drop back at top edge
}
}
DMDFrame DMDFrame::subFrame(unsigned int left, unsigned int top, unsigned int width, unsigned int height)
{
DMDFrame result(width, height);
if((left % 8) == 0 && (width % 8) == 0) {
// Copying from/to byte boundaries, can do simple/efficient copies
for(unsigned int to_y = 0; to_y < height; to_y++) {
unsigned int from_y = top + to_y;
unsigned int from_end = pixelToBitmapIndex(left+width,from_y);
unsigned int to_byte = result.pixelToBitmapIndex(0,to_y);
for(unsigned int from_byte = pixelToBitmapIndex(left,from_y); from_byte < from_end; from_byte++) {
result.bitmap[to_byte++] = this->bitmap[from_byte];
}
}
}
else {
// Copying not from a byte boundary. Slow pixel-by-pixel for now.
for(unsigned int to_y = 0; to_y < height; to_y++) {
for(unsigned int to_x = 0; to_x < width; to_x++) {
bool val = this->getPixel(to_x+left,to_y+top);
result.setPixel(to_x,to_y,val ? GRAPHICS_ON : GRAPHICS_OFF);
}
}
}
return result;
}
void DMDFrame::copyFrame(DMDFrame &from, unsigned int left, unsigned int top)
{
if((left % 8) == 0 && (from.width % 8) == 0) {
// Copying rows on byte boundaries, can do simple/efficient copies
unsigned int to_bottom = top + from.height;
if(to_bottom > this->height)
to_bottom = this->height;
unsigned int to_right = left + from.width;
if(to_right > this->width)
to_right = this->width;
unsigned int from_y = 0;
for(unsigned int to_y = top; to_y < to_bottom; to_y++) {
unsigned int to_end = pixelToBitmapIndex(to_right, to_y);
unsigned int from_byte = from.pixelToBitmapIndex(0, from_y);
for(unsigned int to_byte = pixelToBitmapIndex(left,to_y); to_byte < to_end; to_byte++) {
this->bitmap[to_byte] = from.bitmap[from_byte++];
}
from_y++;
}
}
else {
// Copying not to a byte boundary. Slow pixel-by-pixel for now.
for(unsigned int from_y = 0; from_y < from.height; from_y++) {
for(unsigned int from_x = 0; from_x < from.width; from_x++) {
bool val = from.getPixel(from_x,from_y);
this->setPixel(from_x + left, from_y + top, val ? GRAPHICS_ON : GRAPHICS_OFF);
}
}
}
}
/* Lookup table for DMD pixel locations, marginally faster than bitshifting */
const PROGMEM uint8_t DMD_Pixel_Lut[] = {
0x80, //0, bit 7
0x40, //1, bit 6
0x20, //2. bit 5
0x10, //3, bit 4
0x08, //4, bit 3
0x04, //5, bit 2
0x02, //6, bit 1
0x01 //7, bit 0
};