diff --git a/Layer.cpp b/Layer.cpp new file mode 100644 index 0000000..aa8dc1c --- /dev/null +++ b/Layer.cpp @@ -0,0 +1,338 @@ +/** + * Experimental layer class to do play with pixel in an off-screen buffer before painting to the DMA + * + * Requires FastLED + * + * Faptastic 2020 + **/ + +#include "Layer.h" + +// For adafruit +void Layer::drawPixel(int16_t x, int16_t y, uint16_t color) { + + // 565 color conversion + uint8_t r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6; + uint8_t g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6; + uint8_t b = (((color & 0x1F) * 527) + 23) >> 6; + + drawPixel(x, y, CRGB(r,g,b)); +} + +void Layer::drawPixel(int16_t x, int16_t y, int r, int g, int b) { + drawPixel(x, y, CRGB(r,g,b)); +} + +void Layer::drawPixel(int16_t x, int16_t y, CRGB color) { + + if( x >= LAYER_WIDTH || x < 0) return; // 0; + if( y >= LAYER_HEIGHT || y < 0) return; // 0; + + pixels->data[y][x] = color; + //pixel[XY(x,y)] = color; +} + +void Layer::dim(byte value) { + + // nscale8 max value is 255, or it'll flip back to 0 + // (documentation is wrong when it says x/256), it's actually x/255 + for (int y = 0; y < LAYER_HEIGHT; y++) { + for (int x = 0; x < LAYER_WIDTH; x++) { + pixels->data[y][x].nscale8(value); + }} +// for (int i = 0; i < NUM_PIXELS; i++) pixel[i].nscale8(value); + +} + +void Layer::clear() { + + memset(pixels, BLACK_BACKGROUND_PIXEL_COLOUR, sizeof(layerPixels) ); +} + +void Layer::display() { + + CRGB _pixel = 0 ; + for (int y = 0; y < LAYER_HEIGHT; y++) { + for (int x = 0; x < LAYER_WIDTH; x++) + { + //_pixel = pixel[XY(x, y)]; + _pixel = pixels->data[y][x]; + + matrix->drawPixelRGB888( x, y, _pixel.r, _pixel.g, _pixel.b); + + /* + if ( !transparency_enabled ){ + matrix->drawPixelRGB888( x, y, _pixel.r, _pixel.g, _pixel.b); + } else { + if (_pixel != transparency_colour) { + matrix->drawPixelRGB888( x, y, _pixel.r, _pixel.g, _pixel.b); + } + } + */ + } // end loop to copy fast led to the dma matrix + } + +} // display + +void Layer::overridePixelColor(int r, int g, int b) { + CRGB _pixel = 0 ; + for (int y = 0; y < LAYER_HEIGHT; y++) { + for (int x = 0; x < LAYER_WIDTH; x++) + { + //_pixel = pixel[XY(x, y)]; + _pixel = pixels->data[y][x]; + + if (_pixel != transparency_colour) { + matrix->drawPixelRGB888( x, y, _pixel.r, _pixel.g, _pixel.b); + } + + } // end loop to copy fast led to the dma matrix + } +} + + +// default value is in definition +void Layer::drawCentreText(const char *buf, textPosition textPos, const GFXfont *f, CRGB color, int yadjust) +{ + int16_t x1, y1; + uint16_t w, h; + + setTextWrap(false); + + if (f) { // Font struct pointer passed in? + setFont((GFXfont *)f); + } else { // NULL passed. Current font struct defined? + setFont(); // use default + } + + // getTextBounds isn't correct for variable width fonts + getTextBounds(buf, 0, 0, &x1, &y1, &w, &h); //calc width of new string + + //Serial.printf("The width of the text is %d pixels, the height is %d pixels.\n", w,h); + + /* + + From: https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts + + For example, whereas the cursor position when printing with the classic font identified + the top-left corner of the character cell, with new fonts the cursor position indicates the baseline — + the bottom-most row — of subsequent text. Characters may vary in size and width, and don’t + necessarily begin at the exact cursor column (as in below, this character starts one pixel + left of the cursor, but others may be on or to the right of it). + */ + + if (!f) { + if (textPos == TOP) { + setCursor((LAYER_WIDTH - w) / 2, 0); // top + } else if (textPos == BOTTOM) { + setCursor((LAYER_WIDTH - w) / 2, LAYER_HEIGHT - h); + } else { // middle + setCursor((LAYER_WIDTH - w) / 2, (LAYER_HEIGHT - h) / 2); // top + } + } + else // custom font + /* As we can't reliable know what is the actual FIRST and last 'lit' pixel, we need to check what was printed to the layer.*/ + { + int wstart = 0; + + if (w > 42) wstart = (LAYER_WIDTH - w) / 2; + else wstart = (LAYER_WIDTH - w) / 2; + + if (textPos == TOP) { + setCursor(wstart, h+yadjust); // top + } else if (textPos == BOTTOM) { + setCursor(wstart+1, (LAYER_HEIGHT-1)+yadjust); + } else { // middle + setCursor( wstart, ((LAYER_HEIGHT/2) + (h/2)) + yadjust); + } + + //Serial.printf("Layer: x1: %d, y1: %d, w: %d, h: %d.\n", x1, y1, w, h); + } + + // setCursor(0,16); + setTextColor(this->color565(color.r, color.g, color.b)); // Need to confirm from FastLed CRGB to adafruit 565 + print(buf); + +} // end drawCentreText + + + // Move the contents of the screen left (-ve) or right (+ve) + void Layer::moveX(int offset) + { + if(offset > 0) { // move right + // Sprintln("Moving right"); + + for(int x = LAYER_WIDTH - 1; x >= 0; x--){ // 63 to 0 + for(int y = 0; y < LAYER_HEIGHT; y++){ // 0 to 31 + if (x - offset >= 0) + { + // Serial.printf("setting y %d x %d to y %d x %d\n", y, x, y, x-offset); + pixels->data[y][x] = pixels->data[y][x-offset]; + } + else { + pixels->data[y][x] = BLACK_BACKGROUND_PIXEL_COLOUR; + } + } + } + } else { // move left + + // Sprintln("Moving Left"); + for(int x = 0; x <=LAYER_WIDTH - 1; x++){ + for(int y = 0; y < LAYER_HEIGHT; y++){ + if ( x > (LAYER_WIDTH-1)+offset ) + { + pixels->data[y][x] = BLACK_BACKGROUND_PIXEL_COLOUR; + //Serial.println("eh?"); + } + else + { + pixels->data[y][x] = pixels->data[y][x-offset]; + // Serial.println("eh?"); + } + } + } + } + } + + // Find the leftmost and rightmost pixels across all rows. Centre the contents of the layer. + void Layer::autoCenterX() + { + int leftmost_x = 0, rightmost_x = 0, adjusted_leftmost_x = 0; + + // Find leftmost + for(int x = 0; x < LAYER_WIDTH; x++) { + for(int y = 0; y < LAYER_HEIGHT; y++) { + if (pixels->data[y][x] != BLACK_BACKGROUND_PIXEL_COLOUR) + { + leftmost_x = x; + //Serial.printf("Left most x pixel is %d\n", leftmost_x); + goto rightmost; + } + } + } + + rightmost: + for(int x = LAYER_WIDTH-1; x >= 0; x--) { + for(int y = 0; y < LAYER_HEIGHT; y++) { + if (pixels->data[y][x] != BLACK_BACKGROUND_PIXEL_COLOUR) + { + rightmost_x = x+1; + //Serial.printf("Right most x pixel is %d\n", rightmost_x); + goto centreit; + } + } + } + + centreit: + adjusted_leftmost_x = ( LAYER_WIDTH - (rightmost_x - leftmost_x))/2; + //Serial.printf("Adjusted: %d, Moving x coords by %d pixels.\n", adjusted_leftmost_x, adjusted_leftmost_x-leftmost_x); + moveX(adjusted_leftmost_x-leftmost_x); + } // end autoCentreX + + void Layer::moveY(int delta) + { + + } + + +Layer::~Layer(void) +{ + free(pixels); +} + + + + +/* Merge FastLED layers into a super layer and display. Definition */ +namespace LayerCompositor +{ + /* + * Display the foreground pixels if they're not the background/transparent color. + * If not, then fill with whatever is in the background. + * + * writeToBg = write the result back to the _bgLayer, and not directly to the output device! + * -> no need to do a subsequent bgLayer.display() otherwise. + */ + void Stack(RGB64x32MatrixPanel_I2S_DMA &disp, const Layer &_bgLayer, const Layer &_fgLayer, bool writeBackToBg) + { + for (int y = 0; y < LAYER_HEIGHT; y++) { + for (int x = 0; x < LAYER_WIDTH; x++) + { + //https://www.educative.io/edpresso/how-to-resolve-the-expression-must-have-class-type-error-in-cpp + if (_fgLayer.pixels->data[y][x] == _fgLayer.transparency_colour) // foreground is transparent, show the _bgLayer colors + { + if (writeBackToBg) // write the foreground to the background layer... perhaps so we can do stuff later with the _fgLayer. + _bgLayer.pixels->data[y][x] = _bgLayer.pixels->data[y][x]; + else + disp.drawPixelRGB888(x,y, _bgLayer.pixels->data[y][x].r, _bgLayer.pixels->data[y][x].g, _bgLayer.pixels->data[y][x].b ); + + } // if the foreground is NOT transparent, then print whatever is the bg + else + { + if (writeBackToBg) // write the foreground to the background layer... perhaps so we can do stuff later with the _fgLayer. + _bgLayer.pixels->data[y][x] = _fgLayer.pixels->data[y][x]; + else + disp.drawPixelRGB888(x,y, _fgLayer.pixels->data[y][x].r, _fgLayer.pixels->data[y][x].g, _fgLayer.pixels->data[y][x].b ); + } + + } // end x loop + } // end y loop + } // end stack + + + /* + * Where the foreground pixels are not the background/transparent color, populate with + * whatever is in the background. + */ + void Siloette(RGB64x32MatrixPanel_I2S_DMA &disp, const Layer &_bgLayer, const Layer &_fgLayer) + { + //const Layer *bg = &_bgLayer; + //const Layer *fg = &_fgLayer; + + for (int y = 0; y < LAYER_HEIGHT; y++) { + for (int x = 0; x < LAYER_WIDTH; x++) + { + //https://www.educative.io/edpresso/how-to-resolve-the-expression-must-have-class-type-error-in-cpp + if (_fgLayer.pixels->data[y][x] != _fgLayer.transparency_colour) + { + disp.drawPixelRGB888(x,y, _bgLayer.pixels->data[y][x].r, _bgLayer.pixels->data[y][x].g, _bgLayer.pixels->data[y][x].b ); + } // if the foreground is transparent, then print whatever is the bg + else + { + disp.drawPixelRGB888(x,y, 0,0,0); + } + + } // end x loop + } // end y loop + } // end stack + + + + + + void Blend(RGB64x32MatrixPanel_I2S_DMA &disp, const Layer &_bgLayer, const Layer &_fgLayer, uint8_t ratio) + { + CRGB _pixel = 0 ; + + for (int y = 0; y < LAYER_HEIGHT; y++) + { + for (int x = 0; x < LAYER_WIDTH; x++) + { + /* + // set the blend ratio for the video cross fade + // (set ratio to 127 for a constant 50% / 50% blend) + uint8_t ratio = beatsin8(5); + */ + + _pixel = blend(_bgLayer.pixels->data[y][x], _fgLayer.pixels->data[y][x], ratio); + + // https://gist.github.com/StefanPetrick/0c0d54d0f35ea9cca983 + disp.drawPixelRGB888(x,y, _pixel.r, _pixel.g, _pixel.b ); + + } // end x loop + } // end y loop + + + + } // end blend +} diff --git a/Layer.h b/Layer.h new file mode 100644 index 0000000..ed08fa0 --- /dev/null +++ b/Layer.h @@ -0,0 +1,104 @@ +/** + * Experimental layer class to do play with pixel in an off-screen buffer before painting to the DMA + * + * Requires FastLED + * + * Faptastic 2020 + **/ + + +#ifndef DISPLAY_MATRIX_LAYER +#define DISPLAY_MATRIX_LAYER + +#include +#include +#include + +#define HALF_WHITE_COLOUR 0x8410 +#define BLACK_BACKGROUND_PIXEL_COLOUR CRGB(0,0,0) + +#define LAYER_WIDTH 64 +#define LAYER_HEIGHT 32 + +enum textPosition { TOP, MIDDLE, BOTTOM }; + + +/* To help with direct pixel referencing by width and height */ +struct layerPixels { + CRGB data[LAYER_HEIGHT][LAYER_WIDTH]; +}; + +class Layer : public GFX // use GFX Root for now +{ + public: + + // Static allocation of memory for layer + //CRGB pixels[LAYER_WIDTH][LAYER_HEIGHT] = {{0}}; + + Layer(RGB64x32MatrixPanel_I2S_DMA &disp) : GFX (LAYER_WIDTH, LAYER_HEIGHT) { + matrix = &disp; + } + + inline void init() + { + // https://stackoverflow.com/questions/5914422/proper-way-to-initialize-c-structs + pixels = new layerPixels(); + + //pixels = (layerPixels *) malloc(sizeof(layerPixels)); + // pixel = (CRGB *) &pixels[0]; + //Serial.printf("Allocated %d bytes of memory for standard CRGB (24bit) layer.\r\n", NUM_PIXELS*sizeof(CRGB)); + Serial.printf("Allocated %d bytes of memory for layerPixels.\r\n", sizeof(layerPixels)); + + } // end Layer + + void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation + void drawPixel(int16_t x, int16_t y, int r, int g, int b); // Layer implementation + void drawPixel(int16_t x, int16_t y, CRGB color); // Layer implementation + + // Font Stuff + //https://forum.arduino.cc/index.php?topic=642749.0 + void drawCentreText(const char *buf, textPosition textPos = BOTTOM, const GFXfont *f = NULL, CRGB color = 0x8410, int yadjust = 0); // 128,128,128 RGB @ bottom row by default + + + void dim(byte value); + void clear(); + void display(); // flush to display / LED matrix + + // override the color of all pixels that aren't the transparent color + void overridePixelColor(int r, int g, int b); + + inline uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + + inline void setTransparency(bool t) { transparency_enabled = t; } + + // Effects + void moveX(int delta); + void autoCenterX(); + void moveY(int delta); + + // For layer composition - accessed publically + CRGB transparency_colour = BLACK_BACKGROUND_PIXEL_COLOUR; + bool transparency_enabled = true; + layerPixels *pixels; + //CRGB *pixel; + + + // Release Memory + ~Layer(void); + + private: + RGB64x32MatrixPanel_I2S_DMA *matrix = NULL; +}; + + +/* Merge FastLED layers into a super layer and display. */ +namespace LayerCompositor +{ + void Stack(RGB64x32MatrixPanel_I2S_DMA &disp, const Layer &_bgLayer, const Layer &_fgLayer, bool writeToBgLayer = false); + void Siloette(RGB64x32MatrixPanel_I2S_DMA &disp, const Layer &_bgLayer, const Layer &_fgLayer); + void Blend(RGB64x32MatrixPanel_I2S_DMA &disp, const Layer &_bgLayer, const Layer &_fgLayer, uint8_t ratio); +} + +#endif