Compare commits
6 commits
master
...
pb-thinkin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c8cac018e | ||
|
|
4016b1f805 | ||
|
|
ca867284d1 | ||
|
|
27826c9e79 | ||
|
|
63b1a38356 | ||
|
|
6744d14d7c |
2 changed files with 711 additions and 0 deletions
644
Adafruit_ImageReader_ThinkInk.cpp
Normal file
644
Adafruit_ImageReader_ThinkInk.cpp
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
#include "Adafruit_ImageReader_ThinkInk.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
#define BUFPIXELS 24 ///< 24 * 5 = 120 bytes
|
||||
#else
|
||||
#define BUFPIXELS 200 ///< 200 * 5 = 1000 bytes
|
||||
#endif
|
||||
|
||||
// KEEP THIS VALUE CURRENT WITH THE LARGEST MAJOR AXIS AMONG ALL PANELS IN
|
||||
// Adafruit_EPD (currently 400 px from the ThinkInk_420_* panels).
|
||||
#define EPD_AXIS_MAX 400
|
||||
// These are error accumulation buffers used for diffusion dithering:
|
||||
static int8_t vRed[EPD_AXIS_MAX], vGreen[EPD_AXIS_MAX], vBlue[EPD_AXIS_MAX];
|
||||
static int8_t hRed, hGreen, hBlue;
|
||||
|
||||
// Palettes are weird but are needed for error diffusion dithering, and the
|
||||
// tricolor palette is used in the quantize() function. Values here are the
|
||||
// perceived RGB565 values corresponding to each ink color (derived from
|
||||
// the Photoshop palettes used in dither guide).
|
||||
static const uint8_t
|
||||
palette_mono[][4] = {{47 >> 3, 36 >> 2, 41 >> 3, EPD_BLACK},
|
||||
{242 >> 3, 244 >> 2, 239 >> 3, EPD_WHITE}},
|
||||
palette_tricolor[][4] = {{47 >> 3, 36 >> 2, 41 >> 3, EPD_BLACK},
|
||||
{242 >> 3, 244 >> 2, 239 >> 3, EPD_WHITE},
|
||||
{215 >> 3, 38 >> 2, 39 >> 3, EPD_RED}},
|
||||
palette_grayscale4[][4] = {{47 >> 3, 36 >> 2, 41 >> 3, EPD_BLACK},
|
||||
{112 >> 3, 105 >> 2, 107 >> 3, EPD_DARK},
|
||||
{177 >> 3, 175 >> 2, 173 >> 3, EPD_LIGHT},
|
||||
{242 >> 3, 244 >> 2, 239 >> 3, EPD_WHITE}};
|
||||
|
||||
/*!
|
||||
@brief Quantize RGB565 color to one of the fixed EPD colod indices,
|
||||
whatever's a closest match for the EPD display type.
|
||||
@param rgb RGB565 input color.
|
||||
@param mode One of the thinkinkmode_t types enumerated in
|
||||
Adafruit_EPD library (e.g. THINKINK_MONO).
|
||||
@return Color index into palette appropriate for mode. This is the
|
||||
palette index, NOT the RGB value. The calling function may
|
||||
or may not want to look up the RGB color on its own (e.g.
|
||||
when doing error diffusion).
|
||||
*/
|
||||
static uint8_t quantize(uint16_t rgb, thinkinkmode_t mode) {
|
||||
uint8_t r = rgb >> 11;
|
||||
uint8_t g = (rgb >> 5) & 0x3F;
|
||||
uint8_t b = rgb & 0x1F;
|
||||
if (mode == THINKINK_MONO) {
|
||||
// RGB-to-gray weightings here are fixed-point equivalents to
|
||||
// R=0.2989, G=0.587, B=0.114, factoring in that green is a 6-bit
|
||||
// value covering a larger range compared to red and blue's 5 bits.
|
||||
return (uint8_t)((r * 631 + g * 611 + b * 241) >> 15); // 0 or 1
|
||||
} else if (mode == THINKINK_GRAYSCALE4) {
|
||||
// Same deal re: RGB-to-gray weightings
|
||||
return (uint8_t)((r * 631 + g * 611 + b * 241) >> 14); // 0 to 3
|
||||
} else { // THINKINK_TRICOLOR
|
||||
// For the moment, doing a brute-force compare against each color
|
||||
// in the tricolor palette, returning the index of the closest match
|
||||
// (using distance in linear RGB space for comparison).
|
||||
uint8_t i, closest_index = 0;
|
||||
uint32_t closest_dist = 0xFFFFFFFF;
|
||||
for (i = 0; i < sizeof palette_tricolor / sizeof palette_tricolor[0]; i++) {
|
||||
int8_t dr = (r - palette_tricolor[i][0]) * 2; // Red dist
|
||||
int8_t dg = g - palette_tricolor[i][1]; // Green dist
|
||||
int8_t db = (b - palette_tricolor[i][2]) * 2; // Blue dist
|
||||
uint32_t dist = dr * dr + dg * dg + db * db; // Dist^2
|
||||
if (dist < closest_dist) { // No sqrt needed, because relative compare
|
||||
closest_dist = dist;
|
||||
closest_index = i;
|
||||
}
|
||||
}
|
||||
return closest_index;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Reset error accumulation buffers prior to diffusion dithering
|
||||
(before reading BMP or rendering via draw()).
|
||||
@param rgb If true, display has some colors (not mono or gray).
|
||||
@return None (void).
|
||||
*/
|
||||
static void dither_reset(bool rgb) {
|
||||
memset(vRed, 0, sizeof vRed);
|
||||
hRed = 0;
|
||||
if (rgb) {
|
||||
memset(vGreen, 0, sizeof vGreen);
|
||||
memset(vBlue, 0, sizeof vBlue);
|
||||
hGreen = hBlue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Draw span of pixels from source buffer to EPD display, applying
|
||||
quantization and dithering as requested. Clipping is already
|
||||
handled in calling function; coordinates can safely be assumed
|
||||
fully in-image at this point. This ONLY does a horizontal span,
|
||||
not a 2D rect. Input data will ALWAYS be 16-bit RGB565 at this
|
||||
point (bitmaps have been expanded).
|
||||
@param src Source data; array of pixels in 16-bit RGB565 format.
|
||||
@param epd Screen to draw to (any Adafruit_EPD-derived class).
|
||||
|
||||
@param x Horizontal offset in pixels; left edge = 0,
|
||||
positive = right. Screen rotation setting is observed.
|
||||
@param y Vertical offset in pixels; top edge = 0, positive = down.
|
||||
@param width Width of span to draw, in pixels.
|
||||
@param mode One of the thinkinkmode_t types enumerated in
|
||||
Adafruit_EPD library (e.g. THINKINK_MONO).
|
||||
@param dither One of the dither_t values enumerated in header -
|
||||
DITHER_NONE, DITHER_ORDERED or DITHER_DIFFUSION.
|
||||
@return None (void).
|
||||
*/
|
||||
static void span(uint16_t *src, Adafruit_EPD *epd, int16_t x, int16_t y,
|
||||
int16_t width, thinkinkmode_t mode, dither_t dither) {
|
||||
uint8_t *palette;
|
||||
if (mode == THINKINK_MONO) {
|
||||
palette = (uint8_t *)palette_mono;
|
||||
} else if (mode == THINKINK_GRAYSCALE4) {
|
||||
palette = (uint8_t *)palette_grayscale4;
|
||||
} else {
|
||||
palette = (uint8_t *)palette_tricolor;
|
||||
}
|
||||
if (dither == DITHER_NONE) {
|
||||
while (width--) {
|
||||
epd->drawPixel(x++, y, palette[quantize(*src++, mode) * 4 + 3]);
|
||||
}
|
||||
} else if (dither == DITHER_PATTERN) {
|
||||
// 4x4 ordered dither goes here
|
||||
} else { // DITHER_DIFFUSION
|
||||
while (width--) {
|
||||
uint16_t rgb = *src++;
|
||||
uint8_t r = rgb >> 11;
|
||||
uint8_t g = (rgb >> 5) & 0x3F;
|
||||
uint8_t b = rgb & 0x1F;
|
||||
if (mode == THINKINK_TRICOLOR) {
|
||||
} else {
|
||||
// See notes in quantize() about the math here
|
||||
uint16_t gray = r * 631 + g * 611 + b * 241; // 0-65535ish
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Draw an image in RAM to an Adafruit ThinkInk 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.
|
||||
@param mode One of the thinkinkmode_t types enumerated in
|
||||
Adafruit_EPD library (e.g. THINKINK_MONO).
|
||||
@param dither One of the dither_t values enumerated in header -
|
||||
DITHER_NONE, DITHER_ORDERED or DITHER_DIFFUSION.
|
||||
@return None (void).
|
||||
*/
|
||||
void Adafruit_Image_ThinkInk::draw(Adafruit_EPD &epd, int16_t x, int16_t y,
|
||||
thinkinkmode_t mode, dither_t dither) {
|
||||
if ((x >= epd.width()) || (y >= epd.height())) {
|
||||
return; // Reject off right/bottom
|
||||
}
|
||||
|
||||
if (format == IMAGE_1) {
|
||||
int x2 = x + canvas.canvas1->width() - 1;
|
||||
int y2 = y + canvas.canvas1->height() - 1;
|
||||
if ((x2 < 0) || (y2 < 0)) {
|
||||
return; // Reject off left/top
|
||||
}
|
||||
// Vertical clipping is achieved with an offset into the canvas buffer.
|
||||
uint8_t *buffer = canvas.canvas1->getBuffer();
|
||||
if (y < 0) { // Top clip
|
||||
buffer -= y * ((canvas.canvas1->width() + 7) / 8);
|
||||
y = 0;
|
||||
}
|
||||
if (y2 >= epd.height()) { // Bottom clip
|
||||
y2 = epd.height() - 1;
|
||||
}
|
||||
// Horizontal clipping is peculiar by comparison...because source data
|
||||
// is bit packed, need to keep track of the initial byte offset (at the
|
||||
// start of each row) and bit index of the first pixel...
|
||||
uint16_t initial_offset;
|
||||
uint8_t initial_bit;
|
||||
if (x < 0) { // Left clip
|
||||
initial_offset = -x / 8;
|
||||
initial_bit = 7 - (-x & 7); // 7 to 0
|
||||
x = 0;
|
||||
} else {
|
||||
initial_offset = initial_bit = 0;
|
||||
}
|
||||
if (x2 >= epd.width()) { // Right clip
|
||||
x2 = epd.width() - 1;
|
||||
}
|
||||
|
||||
epd.startWrite();
|
||||
if (dither == DITHER_DIFFUSION) {
|
||||
dither_reset((mode == THINKINK_TRICOLOR));
|
||||
}
|
||||
|
||||
uint16_t epdbuf[BUFPIXELS]; // Temp space for buffering EPD data
|
||||
uint16_t destidx = 0;
|
||||
for (; y <= y2; y++) { // For each row...
|
||||
uint16_t offset = initial_offset;
|
||||
uint8_t bit = initial_bit;
|
||||
int span_x = x;
|
||||
for (int16_t col = x; col <= x2; col++) {
|
||||
epdbuf[destidx++] = palette[(buffer[offset] >> bit) & 1];
|
||||
if (bit) {
|
||||
bit--;
|
||||
} else {
|
||||
bit = 7;
|
||||
offset++;
|
||||
}
|
||||
// If last pixel of row, or if epdbuf is full...
|
||||
if ((col >= x2) || (destidx >= BUFPIXELS)) {
|
||||
// Pass epdbuf data to span-drawing function...
|
||||
span(epdbuf, &epd, span_x, y, destidx, mode, dither);
|
||||
span_x += destidx; // Next span starts here
|
||||
destidx = 0; // Reset epdbuf
|
||||
}
|
||||
}
|
||||
buffer += (canvas.canvas1->width() + 7) / 8; // Offset to next row
|
||||
}
|
||||
} else if (format == IMAGE_8) {
|
||||
// BMP reader doesn't currently handle palettized images
|
||||
} else if (format == IMAGE_16) {
|
||||
int x2 = x + canvas.canvas16->width() - 1;
|
||||
int y2 = y + canvas.canvas16->height() - 1;
|
||||
if ((x2 < 0) || (y2 < 0)) {
|
||||
return; // Reject off left/top
|
||||
}
|
||||
uint16_t *buffer = canvas.canvas16->getBuffer();
|
||||
if (y < 0) { // Top clip
|
||||
buffer -= y * canvas.canvas16->width(); // Offset to first scanline
|
||||
y = 0;
|
||||
}
|
||||
if (y2 >= epd.height()) { // Bottom clip
|
||||
y2 = epd.height() - 1;
|
||||
}
|
||||
if (x < 0) { // Left clip
|
||||
buffer += x; // Offset to first column
|
||||
x = 0;
|
||||
}
|
||||
if (x2 >= epd.width()) { // Right clip
|
||||
x2 = epd.width() - 1;
|
||||
}
|
||||
|
||||
epd.startWrite();
|
||||
if (dither == DITHER_DIFFUSION) {
|
||||
dither_reset((mode == THINKINK_TRICOLOR));
|
||||
}
|
||||
|
||||
int16_t width = x2 - x + 1;
|
||||
for (; y <= y2; y++) { // For each row...
|
||||
// Call span function, passing pointer into RGB565 image
|
||||
span(buffer, &epd, x, y, width, mode, dither);
|
||||
buffer += canvas.canvas16->width(); // Offset to next scanline
|
||||
}
|
||||
} // end IMAGE_16
|
||||
epd.endWrite();
|
||||
}
|
||||
|
||||
// ADAFRUIT_IMAGEREADER_THINKINK 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_ThinkInk::Adafruit_ImageReader_ThinkInk(FatFileSystem &fs)
|
||||
: Adafruit_ImageReader(fs) {}
|
||||
|
||||
/*!
|
||||
@brief Loads BMP image file from SD card directly to Adafruit_ThinkInk
|
||||
display.
|
||||
@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 mode One of the thinkinkmode_t types enumerated in
|
||||
Adafruit_EPD library (e.g. THINKINK_MONO).
|
||||
@param dither One of the dither_t values enumerated in header -
|
||||
DITHER_NONE, DITHER_ORDERED or DITHER_DIFFUSION.
|
||||
@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_ThinkInk::drawBMP(
|
||||
char *filename, Adafruit_EPD &epd, int16_t x, int16_t y,
|
||||
thinkinkmode_t mode, dither_t dither, 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, mode, dither, 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, or NULL otherwise.
|
||||
@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_ThinkInk object, if
|
||||
loading to RAM (or NULL if loading to screen).
|
||||
@param mode One of the thinkinkmode_t types enumerated in
|
||||
Adafruit_EPD library (e.g. THINKINK_MONO).
|
||||
@param dither One of the dither_t values enumerated in header -
|
||||
DITHER_NONE, DITHER_ORDERED or DITHER_DIFFUSION
|
||||
(IGNORED if loading image to canvas).
|
||||
@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_ThinkInk::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_ThinkInk *img, // NULL if load-to-screen
|
||||
thinkinkmode_t mode, dither_t dither,
|
||||
boolean transact) { // SD & EPD sharing bus, use transactions
|
||||
|
||||
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, epd_row;
|
||||
#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;
|
||||
|
||||
// CURRENTLY ONLY 24-BIT BGR AND 1-BIT BMPS ARE SUPPORTED.
|
||||
// 8-bit grayscale, 8- or 4-bit paletted, etc. are NOT HANDLED.
|
||||
|
||||
if ((depth == 24) || (depth == 1)) {
|
||||
|
||||
if (img) {
|
||||
// Loading to RAM -- allocate GFX canvas
|
||||
status = IMAGE_ERR_MALLOC; // Assume won't fit to start
|
||||
// Future: handle other depths.
|
||||
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();
|
||||
}
|
||||
}
|
||||
} // else loading to screen -- 'dest' pointer was passed in
|
||||
|
||||
if (dest || dest1) { // Supported format, alloc OK, etc.
|
||||
status = IMAGE_SUCCESS;
|
||||
|
||||
if ((loadWidth > 0) && (loadHeight > 0)) { // Clip top/left
|
||||
if (epd) { // If loading to display...
|
||||
epd->startWrite(); // Start SPI (regardless of transact)
|
||||
epd_col = x;
|
||||
epd_row = y;
|
||||
} else {
|
||||
// Future: handle other depths.
|
||||
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 color table, quantied to RGB565. This is NOT
|
||||
// converted to EPD color indices yet -- that's done
|
||||
// during draw operation, as there may yet be
|
||||
// dithering operations to perform.
|
||||
for (uint16_t c = 0; c < colors; c++) {
|
||||
b = file.read();
|
||||
g = file.read();
|
||||
r = file.read();
|
||||
(void)file.read(); // Ignore 4th byte
|
||||
quantized[c] =
|
||||
((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
}
|
||||
|
||||
if (dither == DITHER_DIFFUSION) {
|
||||
dither_reset((mode == THINKINK_TRICOLOR));
|
||||
}
|
||||
|
||||
for (row = 0; row < loadHeight; row++) {
|
||||
|
||||
// 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) { // If loading 1-bit image to RAM
|
||||
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
|
||||
}
|
||||
|
||||
int span_x = epd_col;
|
||||
|
||||
for (col = 0; col < loadWidth; col++) {
|
||||
|
||||
// EACH PIXEL ON SCANLINE --------------------------------
|
||||
|
||||
if (srcidx >= sizeof sdbuf) { // Time to load more data?
|
||||
if (epd && transact) {
|
||||
epd->endWrite(); // End display SPI transaction
|
||||
}
|
||||
#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 (epd && transact) {
|
||||
epd->startWrite(); // Start next display SPI transact
|
||||
}
|
||||
srcidx = 0; // Reset bmp buf index
|
||||
}
|
||||
|
||||
if (depth == 24) {
|
||||
// Convert RGB pixel to 565 format, save in dest.
|
||||
// Makes no difference if going to screen or canvas.
|
||||
b = sdbuf[srcidx++];
|
||||
g = sdbuf[srcidx++];
|
||||
r = sdbuf[srcidx++];
|
||||
dest[destidx++] =
|
||||
((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
} else { // 1-bit
|
||||
// Extract bit (color index) from BMP...
|
||||
uint8_t n = (sdbuf[srcidx] >> bitIn) & 1;
|
||||
if (!bitIn) {
|
||||
srcidx++;
|
||||
bitIn = 7;
|
||||
} else {
|
||||
bitIn--;
|
||||
}
|
||||
if (epd) { // Loading 1-bit image to display...
|
||||
// RGB565 color from palette goes in EPD dest buffer...
|
||||
dest[destidx++] = quantized[n];
|
||||
// Even though source image is 1-bit, and display might
|
||||
// be 1-bit, it's not always the case that the source
|
||||
// image is black & white (could be any two RGB colors).
|
||||
// Hence the expansion to RGB565. The span-rendering
|
||||
// function then quantizes and/or dithers this to what
|
||||
// the display can best handle. In some cases that's
|
||||
// fantastically bloaty (converting 1-bit to 16-bit RGB
|
||||
// and then back to 1-bit later in the span function),
|
||||
// but the alternative is a TON of special case code.
|
||||
// Since the EPD is slow to update anyway, we'll just
|
||||
// accept it, the code isn't really the bottleneck.
|
||||
} else { // Loading 1-bit image to RAM...
|
||||
// Store bit in canvas1 buffer
|
||||
if (n)
|
||||
dest1[destidx] |= bitOut;
|
||||
else
|
||||
dest1[destidx] &= ~bitOut;
|
||||
bitOut >>= 1;
|
||||
if (!bitOut) {
|
||||
bitOut = 0x80;
|
||||
destidx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If loading to display, and either we're on the last pixel
|
||||
// of the current row, OR if the dest buffer is full...
|
||||
if (epd &&
|
||||
((col == (loadWidth - 1)) || (destidx >= BUFPIXELS))) {
|
||||
// Issue a span of pixels to the display...
|
||||
span(dest, epd, span_x, epd_row + row, destidx, mode,
|
||||
dither);
|
||||
span_x += destidx; // Next span will start here
|
||||
destidx = 0; // Reset dest buffer counter
|
||||
}
|
||||
} // end pixel (column) loop -------------------------------
|
||||
} // end scanline (row) loop -------------------------------
|
||||
|
||||
if (quantized) { // Was an RGB565 palette allocated?
|
||||
if (epd) // If image was loaded to display,
|
||||
free(quantized); // palette is 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;
|
||||
}
|
||||
67
Adafruit_ImageReader_ThinkInk.h
Normal file
67
Adafruit_ImageReader_ThinkInk.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*!
|
||||
* @file Adafruit_ImageReader_ThinkInk.h
|
||||
*
|
||||
* This is part of Adafruit's ImageReader library for Arduino, designed to
|
||||
* work with Adafruit_GFX plus a display device-specific library.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* Written by Melissa LeBlanc-Williams for Adafruit Industries.
|
||||
*
|
||||
* BSD license, all text here must be included in any redistribution.
|
||||
*/
|
||||
#ifndef __ADAFRUIT_IMAGE_READER_THINKINK_H__
|
||||
#define __ADAFRUIT_IMAGE_READER_THINKINK_H__
|
||||
|
||||
#include "Adafruit_ImageReader.h"
|
||||
#include "Adafruit_ThinkInk.h"
|
||||
|
||||
typedef enum {
|
||||
DITHER_NONE,
|
||||
DITHER_PATTERN,
|
||||
DITHER_DIFFUSION,
|
||||
} dither_t;
|
||||
|
||||
/*!
|
||||
@brief Data bundle returned with an image loaded to RAM. Used by
|
||||
ImageReader.loadBMP() and Image.draw(), not ImageReader.drawBMP().
|
||||
*/
|
||||
class Adafruit_Image_ThinkInk : public Adafruit_Image {
|
||||
public:
|
||||
void draw(Adafruit_EPD &epd, int16_t x, int16_t y, thinkinkmode_t mode,
|
||||
dither_t dither = DITHER_NONE);
|
||||
|
||||
protected:
|
||||
friend class Adafruit_ImageReader_ThinkInk; ///< Loading occurs here
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief An optional adjunct to Adafruit_EPD that reads bitmap or RGB
|
||||
BMP images (maybe others in the future) from a flash filesystem
|
||||
(SD card or SPI/QSPI flash). It's purposefully been made an
|
||||
entirely separate class (rather than part of SPITFT or GFX
|
||||
classes) so that Arduino code that uses GFX or SPITFT *without*
|
||||
image loading does not need to incur the RAM overhead and
|
||||
additional dependencies of the Adafruit_SPIFlash library by
|
||||
its mere inclusion. The syntaxes can therefore be a bit
|
||||
bizarre (passing display object as an argument), see examples
|
||||
for use.
|
||||
*/
|
||||
class Adafruit_ImageReader_ThinkInk : public Adafruit_ImageReader {
|
||||
public:
|
||||
Adafruit_ImageReader_ThinkInk(FatFileSystem &fs);
|
||||
ImageReturnCode drawBMP(char *filename, Adafruit_EPD &epd, int16_t x,
|
||||
int16_t y, thinkinkmode_t mode,
|
||||
dither_t dither = DITHER_NONE,
|
||||
boolean transact = true);
|
||||
|
||||
private:
|
||||
ImageReturnCode coreBMP(char *filename, Adafruit_EPD *epd, uint16_t *dest,
|
||||
int16_t x, int16_t y, Adafruit_Image_ThinkInk *img,
|
||||
thinkinkmode_t mode, dither_t dither,
|
||||
boolean transact);
|
||||
};
|
||||
|
||||
#endif // __ADAFRUIT_IMAGE_READER_THINKINK_H__
|
||||
Loading…
Reference in a new issue