diff --git a/examples/GraphicsLayer/GraphicsLayer.ino b/examples/GraphicsLayer/GraphicsLayer.ino new file mode 100644 index 0000000..e8a16d9 --- /dev/null +++ b/examples/GraphicsLayer/GraphicsLayer.ino @@ -0,0 +1,109 @@ +/* + * Portions of this code are adapted from Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from LedEffects Plasma by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Plasma.cpp?at=default + * Copyright (c) 2013 Robert Atkins + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +//#define USE_CUSTOM_PINS // uncomment to use custom pins, then provide below + +#define A_PIN 26 +#define B_PIN 4 +#define C_PIN 27 +#define D_PIN 2 +#define E_PIN 21 + +#define R1_PIN 5 +#define R2_PIN 19 +#define G1_PIN 17 +#define G2_PIN 16 +#define B1_PIN 18 +#define B2_PIN 25 + +#define CLK_PIN 14 +#define LAT_PIN 15 +#define OE_PIN 13 + + +#include +MatrixPanel_I2S_DMA dma_display; + +#include + +int time_counter = 0; +int cycles = 0; + + +CRGBPalette16 currentPalette; +CRGB currentColor; + + +CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) { + return ColorFromPalette(currentPalette, index, brightness, blendType); +} + +void setup() { + + Serial.begin(115200); + + Serial.println("*****************************************************"); + Serial.println(" HELLO !"); + Serial.println("*****************************************************"); + +#ifdef USE_CUSTOM_PINS + dma_display.begin(R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN ); // setup the LED matrix +#else + dma_display.begin(); +#endif + + // fill the screen with 'black' + dma_display.fillScreen(dma_display.color444(0, 0, 0)); + + // Set current FastLED palette + currentPalette = RainbowColors_p; + +} + +void loop() { + + for (int x = 0; x < dma_display.width(); x++) { + for (int y = 0; y < dma_display.height(); y++) { + int16_t v = 0; + uint8_t wibble = sin8(time_counter); + v += sin16(x * wibble * 3 + time_counter); + v += cos16(y * (128 - wibble) + time_counter); + v += sin16(y * x * cos8(-time_counter) / 8); + + currentColor = ColorFromPalette(currentPalette, (v >> 8) + 127); //, brightness, currentBlendType); + dma_display.drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b); + + } + } + + time_counter += 1; + cycles++; + + if (cycles >= 2048) { + time_counter = 0; + cycles = 0; + } + +} // end loop \ No newline at end of file diff --git a/examples/GraphicsLayer/GraphicsLayer.jpg b/examples/GraphicsLayer/GraphicsLayer.jpg new file mode 100644 index 0000000..70dd6da Binary files /dev/null and b/examples/GraphicsLayer/GraphicsLayer.jpg differ diff --git a/examples/GraphicsLayer/Layer.cpp b/examples/GraphicsLayer/Layer.cpp new file mode 100644 index 0000000..29ea97f --- /dev/null +++ b/examples/GraphicsLayer/Layer.cpp @@ -0,0 +1,344 @@ +/** + * 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; +} + +/** + * Dim all the pixels in the display. + */ +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); + }} +} + +void Layer::clear() { + + memset(pixels, BLACK_BACKGROUND_PIXEL_COLOUR, sizeof(layerPixels) ); +} + +/** + * Send the layer to the display device. + */ +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?"); + } + } + } + } + } + +/** + * Centre the contents of the layer based on the leftmost and rightmost pixels. + * Useful if you want to make sure text / graphics IS in the centre of the display. + */ + 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) + { + // Not implemented + } + + +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/examples/GraphicsLayer/Layer.h b/examples/GraphicsLayer/Layer.h new file mode 100644 index 0000000..69a5886 --- /dev/null +++ b/examples/GraphicsLayer/Layer.h @@ -0,0 +1,122 @@ +/** + * 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 + + +/* Use GFX_Root (https://github.com/mrfaptastic/GFX_Root) instead of + * Adafruit_GFX library. No real benefit unless you don't want Bus_IO & Wire.h library dependencies. + */ +#define USE_GFX_ROOT 1 + + +#ifdef USE_GFX_ROOT + #include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root +#else + #include "Adafruit_GFX.h" // Adafruit class with all the other stuff +#endif + +#include +#include + +/* + * Set the width and height of the layer buffers. This should match exactly that of your output display, or virtual display. + */ +#define LAYER_WIDTH 64 +#define LAYER_HEIGHT 32 + + +#define HALF_WHITE_COLOUR 0x8410 +#define BLACK_BACKGROUND_PIXEL_COLOUR CRGB(0,0,0) + +enum textPosition { TOP, MIDDLE, BOTTOM }; + + +/* To help with direct pixel referencing by width and height */ +struct layerPixels { + CRGB data[LAYER_HEIGHT][LAYER_WIDTH]; +}; + +#ifdef USE_GFX_ROOT +class Layer : public GFX { +#else +class Layer : public Adafruit_GFX { +#endif +// 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; + + // 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 diff --git a/examples/GraphicsLayer/README.md b/examples/GraphicsLayer/README.md new file mode 100644 index 0000000..562b151 --- /dev/null +++ b/examples/GraphicsLayer/README.md @@ -0,0 +1,5 @@ +# Layer Class + +Example of using additional pixel buffers / layers based on the FastLed CRGB data type, doing stuff with the pixels, merging the layers prior to sending to the DMA display library for output. + +![It's better in real life](GraphicsLayer.jpg) diff --git a/keywords.txt b/keywords.txt index d07f9b5..b6ccefa 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,5 +1,6 @@ RGB64x32MatrixPanel_I2S_DMA KEYWORD1 MatrixPanel_I2S_DMA KEYWORD1 +Layer KEYWORD1 fillScreen KEYWORD2 clearScreen KEYWORD2 fillScreenRGB888 KEYWORD2