-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathAdafruit_ImageReader_EPD.cpp
More file actions
527 lines (494 loc) · 20.6 KB
/
Adafruit_ImageReader_EPD.cpp
File metadata and controls
527 lines (494 loc) · 20.6 KB
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
#include "Adafruit_ImageReader_EPD.h"
#ifdef __AVR__
#define BUFPIXELS 24 ///< 24 * 5 = 120 bytes
#else
#define BUFPIXELS 200 ///< 200 * 5 = 1000 bytes
#endif
/*!
@brief Maps RGB color values to EPD display colors based on display mode.
@param r
Red component of the color (0-255).
@param g
Green component of the color (0-255).
@param b
Blue component of the color (0-255).
@param mode
The display mode (THINKINK_MONO, THINKINK_TRICOLOR,
THINKINK_GRAYSCALE4, THINKINK_QUADCOLOR, etc.) that
determines the available colors and mapping strategy.
@return EPD color constant (EPD_BLACK, EPD_WHITE, EPD_RED, EPD_YELLOW,
EPD_DARK, EPD_LIGHT) appropriate for the display mode.
@note Color mapping thresholds:
- Monochrome: Simple average threshold at 128
- Tricolor: Black < 0x60, Red >= 0x80 (red only), White otherwise
- Grayscale: Black < 0x40, Dark < 0x80, Light < 0xC0, White >= 0xC0
- Quadcolor: Black < 0x60, White >= 0xE0 (all channels),
Yellow >= 0xC0 red + >= 0x80 green, Red >= 0xC0 red + < 0x80
green
*/
uint8_t Adafruit_ImageReader_EPD::mapColorForDisplay(uint8_t r, uint8_t g,
uint8_t b,
thinkinkmode_t mode) {
switch (mode) {
case THINKINK_MONO:
case THINKINK_MONO_PARTIAL:
if ((r + g + b) / 3 < 128) {
return EPD_BLACK;
} else {
return EPD_WHITE;
}
case THINKINK_TRICOLOR:
if ((r < 0x60) && (g < 0x60) && (b < 0x60)) {
return EPD_BLACK;
} else if ((r >= 0x80) && (g < 0x80) && (b < 0x80)) {
return EPD_RED;
} else {
return EPD_WHITE;
}
case THINKINK_GRAYSCALE4: {
uint8_t gray = (r + g + b) / 3;
if (gray < 0x40) {
return EPD_BLACK;
} else if (gray < 0x80) {
return EPD_DARK;
} else if (gray < 0xC0) {
return EPD_LIGHT;
} else {
return EPD_WHITE;
}
}
case THINKINK_QUADCOLOR:
if ((r < 0x60) && (g < 0x60) && (b < 0x60)) {
return EPD_BLACK;
} else if ((r >= 0xE0) && (g >= 0xE0) && (b >= 0xE0)) {
return EPD_WHITE;
} else if ((r >= 0xC0) && (g >= 0x80) && (b < 0x40)) {
return EPD_YELLOW;
} else if ((r >= 0xC0) && (g < 0x80) && (b < 0x40)) {
return EPD_RED;
} else {
return EPD_WHITE;
}
default:
if ((r < 0x60) && (g < 0x60) && (b < 0x60)) {
return EPD_BLACK;
} else if ((r >= 0x80) && (g < 0x80) && (b < 0x80)) {
return EPD_RED;
} else {
return EPD_WHITE;
}
}
}
/*!
@brief Draw image to an Adafruit ePaper-type display.
@param epd
Screen to draw to (any Adafruit_EPD-derived class).
@param x
Horizontal offset in pixels; left edge = 0, positive = right.
Value is signed, image will be clipped if all or part is off
the screen edges. Screen rotation setting is observed.
@param y
Vertical offset in pixels; top edge = 0, positive = down.
@return None (void).
*/
void Adafruit_Image_EPD::draw(Adafruit_EPD &epd, int16_t x, int16_t y) {
int16_t col = x, row = y;
if (format == IMAGE_1) {
uint8_t *buffer = canvas.canvas1->getBuffer();
uint8_t i, c;
while (row < y + canvas.canvas1->height()) {
for (i = 0; i < 8; i++) {
if ((*buffer & (0x80 >> i)) > 0) {
c = EPD_BLACK; // try to infer black
} else {
c = EPD_WHITE;
}
epd.writePixel(col, row, c);
col++;
}
if (col >= x + canvas.canvas1->width()) {
col = x;
row++;
}
buffer++;
};
} else if (format == IMAGE_8) {
} else if (format == IMAGE_16) {
uint16_t *buffer = canvas.canvas16->getBuffer();
thinkinkmode_t displayMode = epd.getMode();
while (row < y + canvas.canvas16->height()) {
// RGB in 565 format
uint8_t r = (*buffer & 0xf800) >> 8;
uint8_t g = (*buffer & 0x07e0) >> 3;
uint8_t b = (*buffer & 0x001f) << 3;
uint8_t c =
Adafruit_ImageReader_EPD::mapColorForDisplay(r, g, b, displayMode);
epd.writePixel(col, row, c);
col++;
if (col == x + canvas.canvas16->width()) {
col = x;
row++;
}
buffer++;
};
}
}
// ADAFRUIT_IMAGEREADER_EPD CLASS **********************************************
// Loads images from SD card to screen or RAM.
/*!
@brief Constructor.
@return Adafruit_ImageReader object.
@param fs
FAT filesystem associated with this Adafruit_ImageReader
instance. Any images to load will come from this filesystem;
if multiple filesystems are required, each will require its
own Adafruit_ImageReader object. The filesystem does NOT need
to be initialized yet when passed in here (since this will
often be in pre-setup() declaration, but DOES need initializing
before any of the image loading or size functions are called!
*/
Adafruit_ImageReader_EPD::Adafruit_ImageReader_EPD(FatVolume &fs)
: Adafruit_ImageReader(fs) {}
/*!
@brief Loads BMP image file from SD card directly to Adafruit_EPD screen.
@param filename
Name of BMP image file to load.
@param epd
Screen to draw to (any Adafruit_EPD-derived class).
@param x
Horizontal offset in pixels; left edge = 0, positive = right.
Value is signed, image will be clipped if all or part is off
the screen edges. Screen rotation setting is observed.
@param y
Vertical offset in pixels; top edge = 0, positive = down.
@param transact
Pass 'true' if TFT and SD are on the same SPI bus, in which
case SPI transactions are necessary. If separate peripherals,
can pass 'false'.
@return One of the ImageReturnCode values (IMAGE_SUCCESS on successful
completion, other values on failure).
*/
ImageReturnCode Adafruit_ImageReader_EPD::drawBMP(char *filename,
Adafruit_EPD &epd, int16_t x,
int16_t y, boolean transact) {
uint16_t epdbuf[BUFPIXELS]; // Temp space for buffering EPD data
// Call core BMP-reading function, passing address to EPD object,
// EPD working buffer, and X & Y position of top-left corner (image
// will be cropped on load if necessary). Image pointer is NULL when
// reading to EPD, and transact argument is passed through.
return coreBMP(filename, &epd, epdbuf, x, y, NULL, transact);
}
/*!
@brief BMP-reading function common both to the draw function (to EPD)
and load function (to canvas object in RAM). BMP code has been
centralized here so if/when more BMP format variants are added
in the future, it doesn't need to be implemented, debugged and
kept in sync in two places.
@param filename
Name of BMP image file to load.
@param epd
Screen to draw to (any Adafruit_EPD-derived class). if loading to
screen, else NULL.
@param dest
Working buffer for loading 16-bit TFT pixel data, if loading to
screen, else NULL.
@param x
Horizontal offset in pixels (if loading to screen).
@param y
Vertical offset in pixels (if loading to screen).
@param img
Pointer to Adafruit_Image_EPD object, if loading to RAM (or NULL
if loading to screen).
@param transact
Use SPI transactions; 'true' is needed only if loading to screen
and it's on the same SPI bus as the SD card. Other situations
can use 'false'.
@return One of the ImageReturnCode values (IMAGE_SUCCESS on successful
completion, other values on failure).
*/
ImageReturnCode Adafruit_ImageReader_EPD::coreBMP(
char *filename, // SD file to load
Adafruit_EPD *epd, // Pointer to TFT object, or NULL if to image
uint16_t *dest, // EPD working buffer, or NULL if to canvas
int16_t x, // Position if loading to EPD (else ignored)
int16_t y,
Adafruit_Image_EPD *img, // NULL if load-to-screen
boolean transact) { // SD & EPD sharing bus, use transactions
thinkinkmode_t displayMode = epd ? epd->getMode() : THINKINK_TRICOLOR;
ImageReturnCode status = IMAGE_ERR_FORMAT; // IMAGE_SUCCESS on valid file
uint32_t offset; // Start of image data in file
uint32_t headerSize; // Indicates BMP version
int bmpWidth, bmpHeight; // BMP width & height in pixels
uint8_t planes; // BMP planes
uint8_t depth; // BMP bit depth
uint32_t compression = 0; // BMP compression mode
uint32_t colors = 0; // Number of colors in palette
uint16_t *quantized = NULL; // EPD Color palette
uint32_t rowSize; // >bmpWidth if scanline padding
uint8_t sdbuf[3 * BUFPIXELS]; // BMP read buf (R+G+B/pixel)
int16_t epd_col = 0, epd_row = 0;
#if ((3 * BUFPIXELS) <= 255)
uint8_t srcidx = sizeof sdbuf; // Current position in sdbuf
#else
uint16_t srcidx = sizeof sdbuf;
#endif
uint32_t destidx = 0;
uint8_t *dest1 = NULL; // Dest ptr for 1-bit BMPs to img
boolean flip = true; // BMP is stored bottom-to-top
uint32_t bmpPos = 0; // Next pixel position in file
int loadWidth, loadHeight, // Region being loaded (clipped)
loadX, loadY; // "
int row, col; // Current pixel pos.
uint8_t r, g, b, color; // Current pixel color
uint8_t bitIn = 0; // Bit number for 1-bit data in
uint8_t bitOut = 0; // Column mask for 1-bit data out
// If an Adafruit_Image object is passed and currently contains anything,
// free its contents as it's about to be overwritten with new stuff.
if (img)
img->dealloc();
// If BMP is being drawn off the right or bottom edge of the screen,
// nothing to do here. NOT an error, just a trivial clip operation.
if (epd && ((x >= epd->width()) || (y >= epd->height())))
return IMAGE_SUCCESS;
// Open requested file on SD card
if (!(file = filesys->open(filename, FILE_READ))) {
return IMAGE_ERR_FILE_NOT_FOUND;
}
// Parse BMP header. 0x4D42 (ASCII 'BM') is the Windows BMP signature.
// There are other values possible in a .BMP file but these are super
// esoteric (e.g. OS/2 struct bitmap array) and NOT supported here!
if (readLE16() == 0x4D42) { // BMP signature
(void)readLE32(); // Read & ignore file size
(void)readLE32(); // Read & ignore creator bytes
offset = readLE32(); // Start of image data
// Read DIB header
headerSize = readLE32();
bmpWidth = readLE32();
bmpHeight = readLE32();
// If bmpHeight is negative, image is in top-down order.
// This is not canon but has been observed in the wild.
if (bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
planes = readLE16();
depth = readLE16(); // Bits per pixel
// Compression mode is present in later BMP versions (default = none)
if (headerSize > 12) {
compression = readLE32();
(void)readLE32(); // Raw bitmap data size; ignore
(void)readLE32(); // Horizontal resolution, ignore
(void)readLE32(); // Vertical resolution, ignore
colors = readLE32(); // Number of colors in palette, or 0 for 2^depth
(void)readLE32(); // Number of colors used (ignore)
// File position should now be at start of palette (if present)
}
if (!colors)
colors = 1 << depth;
loadWidth = bmpWidth;
loadHeight = bmpHeight;
loadX = 0;
loadY = 0;
if (epd) {
// Crop area to be loaded (if destination is EPD)
if (x < 0) {
loadX = -x;
loadWidth += x;
x = 0;
}
if (y < 0) {
loadY = -y;
loadHeight += y;
y = 0;
}
if ((x + loadWidth) > epd->width())
loadWidth = epd->width() - x;
if ((y + loadHeight) > epd->height())
loadHeight = epd->height() - y;
}
if ((planes == 1) && (compression == 0)) { // Only uncompressed is handled
// BMP rows are padded (if needed) to 4-byte boundary
rowSize = ((depth * bmpWidth + 31) / 32) * 4;
if ((depth == 24) || (depth == 1)) { // BGR or 1-bit bitmap format
if (img) {
// Loading to RAM -- allocate GFX 16-bit canvas type
status = IMAGE_ERR_MALLOC; // Assume won't fit to start
if (depth == 24) {
if ((img->canvas.canvas16 = new GFXcanvas16(bmpWidth, bmpHeight))) {
dest = img->canvas.canvas16->getBuffer();
}
} else {
if ((img->canvas.canvas1 = new GFXcanvas1(bmpWidth, bmpHeight))) {
dest1 = img->canvas.canvas1->getBuffer();
}
}
// Future: handle other depths.
}
if (dest || dest1) { // Supported format, alloc OK, etc.
status = IMAGE_SUCCESS;
if ((loadWidth > 0) && (loadHeight > 0)) { // Clip top/left
if (epd) {
epd->startWrite(); // Start SPI (regardless of transact)
epd_col = x;
epd_row = y;
} else {
if (depth == 1) {
img->format = IMAGE_1; // Is a GFX 1-bit canvas type
} else {
img->format = IMAGE_16; // Is a GFX 16-bit canvas type
}
}
if ((depth >= 16) ||
(quantized = (uint16_t *)malloc(colors * sizeof(uint16_t)))) {
if (depth < 16) {
// Load and quantize color table
thinkinkmode_t displayMode =
epd ? epd->getMode() : THINKINK_TRICOLOR;
for (uint16_t c = 0; c < colors; c++) {
b = file.read();
g = file.read();
r = file.read();
(void)file.read(); // Ignore 4th byte
color = mapColorForDisplay(r, g, b, displayMode);
quantized[c] = color;
}
}
for (row = 0; row < loadHeight; row++) { // For each scanline...
yield(); // Keep ESP8266 happy
// Seek to start of scan line. It might seem labor-intensive
// to be doing this on every line, but this method covers a
// lot of gritty details like cropping, flip and scanline
// padding. Also, the seek only takes place if the file
// position actually needs to change (avoids a lot of cluster
// math in SD library).
if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
bmpPos = offset + (bmpHeight - 1 - (row + loadY)) * rowSize;
else // Bitmap is stored top-to-bottom
bmpPos = offset + (row + loadY) * rowSize;
if (depth == 24) {
bmpPos += loadX * 3;
} else {
bmpPos += loadX / 8;
bitIn = 7 - (loadX & 7);
bitOut = 0x80;
if (img) {
destidx = ((bmpWidth + 7) / 8) * row;
}
}
if (file.position() != bmpPos) { // Need seek?
if (transact) {
epd->endWrite(); // End EPD SPI transaction
}
file.seek(bmpPos); // Seek = SD transaction
srcidx = sizeof sdbuf; // Force buffer reload
}
for (col = 0; col < loadWidth; col++) { // For each pixel...
if (srcidx >= sizeof sdbuf) { // Time to load more?
if (epd) { // Drawing to TFT?
if (transact) {
epd->endWrite(); // End EPD SPI transact
}
#if defined(ARDUINO_NRF52_ADAFRUIT)
// NRF52840 seems to have trouble reading more than 512
// bytes across certain boundaries. Workaround for now
// is to break the read into smaller chunks...
int32_t bytesToGo = sizeof sdbuf, bytesRead = 0,
bytesThisPass;
while (bytesToGo > 0) {
bytesThisPass = min(bytesToGo, 512);
file.read(&sdbuf[bytesRead], bytesThisPass);
bytesRead += bytesThisPass;
bytesToGo -= bytesThisPass;
}
#else
file.read(sdbuf, sizeof sdbuf); // Load from SD
#endif
if (transact)
epd->startWrite(); // Start EPD SPI transact
if (destidx) { // If buffered EPD data
// Non-blocking writes (DMA) have been temporarily
// disabled until this can be rewritten with two
// alternating 'dest' buffers (else the nonblocking
// data out is overwritten in the dest[] write below).
uint16_t index = 0;
while (index < destidx && epd_row < y + loadHeight) {
epd->writePixel(epd_col, epd_row, dest[index]);
epd_col++;
if (epd_col == x + loadWidth) {
epd_col = x;
epd_row++;
}
index++;
};
destidx = 0; // and reset dest index
}
} else { // Canvas is simpler,
file.read(sdbuf, sizeof sdbuf); // just load sdbuf
} // (destidx never resets)
srcidx = 0; // Reset bmp buf index
}
if (depth == 24) {
// Convert each pixel from BMP to 565 format, save in dest
b = sdbuf[srcidx++];
g = sdbuf[srcidx++];
r = sdbuf[srcidx++];
color = mapColorForDisplay(r, g, b, displayMode);
dest[destidx++] = color;
} else {
// Extract 1-bit color index
uint8_t n = (sdbuf[srcidx] >> bitIn) & 1;
if (!bitIn) {
srcidx++;
bitIn = 7;
} else {
bitIn--;
}
if (epd) {
// Look up in palette, store in epd dest buf
dest[destidx++] = quantized[n];
} else {
// Store bit in canvas1 buffer (ignore palette)
if (n)
dest1[destidx] |= bitOut;
else
dest1[destidx] &= ~bitOut;
bitOut >>= 1;
if (!bitOut) {
bitOut = 0x80;
destidx++;
}
}
}
} // end pixel loop
if (epd) { // Drawing to TFT?
if (destidx) { // Any remainders?
uint16_t index = 0;
while (index < destidx && epd_row < y + loadHeight) {
epd->writePixel(epd_col, epd_row, dest[index]);
epd_col++;
if (epd_col == x + loadWidth) {
epd_col = x;
epd_row++;
}
index++;
};
destidx = 0; // and reset dest index
}
epd->endWrite(); // End TFT (regardless of transact)
}
} // end scanline loop
if (quantized) {
if (epd)
free(quantized); // Palette no longer needed
else
img->palette = quantized; // Keep palette with img
}
} // end depth>24 or quantized malloc OK
} // end top/left clip
} // end malloc check
} // end depth check
} // end planes/compression check
} // end signature
file.close();
return status;
}