commit f934849576c059e8ef61c59b93472e87dda2e73d Author: Bradley Bickford Date: Mon Jul 1 19:18:45 2024 -0400 G.U.L.L.S. rebuilt to use PlatformIO diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..f641408 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +

Grand Unified LED Logic Software (G.U.L.L.S.)

+ +*I need to write something here* + +##### LED Hardware Development Status + +| Hardware Support | Written? | Tested? | +| ---------------- | ------------------ | ------------------ | +| FastLED Strip | :heavy_check_mark: | | +| FastLED Matrix | :heavy_check_mark: | | +| SmartMatrix | :heavy_check_mark: | :heavy_check_mark: | + +##### Animations Development Status (Some will require both 1D and 2D implementations) +| Animation Name | New? | Written? | Tested? | +| --------------------- | ------------------ | ------------------ | ------------------ | +| AlternateMatrix | | :heavy_check_mark: | :heavy_check_mark: | +| AlternateStrip | | :heavy_check_mark: | :heavy_check_mark: | +| CollisionMatrix | | :heavy_check_mark: | | +| CollisionStrip | | :heavy_check_mark: | | +| CycleLightMatrix | | :heavy_check_mark: | | +| CycleLightStrip | | :heavy_check_mark: | | +| FireworksMatrix | | :heavy_check_mark: | | +| FluidColorMatrix | | :heavy_check_mark: | | +| ColorRandomizerMatrix | | :heavy_check_mark: | | +| ColorRandomizerStrip | | :heavy_check_mark: | | +| PlasmaMatrix | | :heavy_check_mark: | | +| RicochetMatrix | | :heavy_check_mark: | | +| TextMatrix | :heavy_check_mark: | | | +| TripCyclingMatrix | | | | +| TripCyclingStrip | | | | +| GIFMatrix | :heavy_check_mark: | | | +| FlameMatrix | :heavy_check_mark: | | | +| RainfallMatrix | :heavy_check_mark: | | | +| Conway'sMatrix | :heavy_check_mark: | | | +| CycloneMatrix | :heavy_check_mark: | :heavy_check_mark: | | + + +##### Notes / Extra To Do's: + +- CycloneMatrix (specifically CycloneHelper, a component of this animation) is liable to have a ton of problems as it had to be mostly rewritten from the original Java version because dynamically expanding arrays aren't available to make the job easy (or at the very least, implementing dynamically expanding arrays is a lot of work and adds an amount of memory overhead I'm not comfortable with). More than just surface level "does the animation display properly" testing needs to be done to make sure nothings going to get all corrupted and nasty. \ No newline at end of file diff --git a/images/terrible logo.png b/images/terrible logo.png new file mode 100644 index 0000000..fe10649 Binary files /dev/null and b/images/terrible logo.png differ diff --git a/include/AlternateMatrix.h b/include/AlternateMatrix.h new file mode 100644 index 0000000..1b0d442 --- /dev/null +++ b/include/AlternateMatrix.h @@ -0,0 +1,46 @@ +#ifndef ALTERNATEMATRIX_H +#define ALTERNATEMATRIX_H + +#include "LEDHAL2D.h" +#include "MatrixAnimation.h" + +enum AlternateType { + HORIZONTAL_ALTERNATE, + VERTICAL_ALTERNATE +}; + +class AlternateMatrix : public MatrixAnimation { + public: + AlternateMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, CRGB* _colors, + uint8_t _numColors, AlternateType _type) : MatrixAnimation(_matrix, _refName, _updateTime), + numColors(_numColors), colors(new CRGB[_numColors]), alternateInt(0), type(_type) { + + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + virtual ~AlternateMatrix() { delete colors; } + + void initialize() { + alternateInt = 0; + resetTimer(); + } + + void execute(); + + AlternateType getType() { + return type; + } + + private: + uint16_t + alternateInt; + CRGB* + colors; + uint8_t + numColors; + AlternateType + type; +}; + +#endif \ No newline at end of file diff --git a/include/AlternateStrip.h b/include/AlternateStrip.h new file mode 100644 index 0000000..18a4115 --- /dev/null +++ b/include/AlternateStrip.h @@ -0,0 +1,34 @@ +#ifndef ALTERNATESTRIP_H +#define ALTERNATESTRIP_H + +#include "LEDHAL.h" +#include "StripAnimation.h" + +class AlternateStrip : public StripAnimation { + public: + AlternateStrip(LEDHAL* _strip, char* _refName, long _updateTime, CRGB* _colors, + uint8_t _numColors) : StripAnimation(_strip, _refName, _updateTime), + numColors(_numColors), colors(new CRGB[_numColors]), alternateInt(0) { + + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + virtual ~AlternateStrip() { delete colors; } + + void initialize() { + alternateInt = 0; + resetTimer(); + } + + void execute(); + private: + uint16_t + alternateInt; + CRGB* + colors; + uint8_t + numColors; +}; + +#endif \ No newline at end of file diff --git a/include/AnimationBase.h b/include/AnimationBase.h new file mode 100644 index 0000000..49cb1bd --- /dev/null +++ b/include/AnimationBase.h @@ -0,0 +1,75 @@ +#ifndef ANIMATIONBASE_H +#define ANIMATIONBASE_H + +#include "elapsedMillis.h" + +class AnimationBase { +public: + AnimationBase(char* _refName, long _updateTime) + : refName(_refName), updateTime(_updateTime), + enabled(false), initialized(false), timer(0) {} + virtual ~AnimationBase() { + delete refName; + } + + virtual void initialize() {} + virtual void execute() {} + virtual bool isFinished() { + return false; + } + + void update() { + if (enabled) { + if (timer >= updateTime) { + if (!initialized) { + initialize(); + initialized = true; + } + + if (!isFinished()) { + execute(); + } + + resetTimer(); + } + } + } + + void enable() { + enabled = true; + } + + void disable() { + enabled = false; + } + + void reinitialize() { + initialized = false; + } + + bool isEnabled() { + return enabled; + } + + char* getReferenceName() { + return refName; + } + +protected: + void resetTimer() { + timer = 0; + } + +private: + char* + refName; + long + updateTime; + bool + enabled, + initialized; + elapsedMillis + timer; +}; + +#endif \ No newline at end of file diff --git a/include/CLEDControllerPhysicalMatrix.h b/include/CLEDControllerPhysicalMatrix.h new file mode 100644 index 0000000..c9f3f29 --- /dev/null +++ b/include/CLEDControllerPhysicalMatrix.h @@ -0,0 +1,73 @@ +#ifndef CLEDCONTROLLERPHYSICALMATRIX_H +#define CLEDCONTROLLERPHYSICALMATRIX_H + +#include "LEDHAL2D.h" + +enum ArrangementType { + HORIZONTALSCAN, + HORIZONTALSERPENTINE, + COLUMNSCAN, + COLUMNSERPENTINE +}; + +class CLEDControllerPhysicalMatrix : public LEDHAL2D { + public: + CLEDControllerPhysicalMatrix(CLEDController* _controller, char* ledName, ArrangementType _arrangement, + int16_t width, int16_t height): LEDHAL2D(width, height, ledName, true), controller(_controller), arrangement(_arrangement) {} + virtual ~CLEDControllerPhysicalMatrix() {} + + void drawPixel(int16_t x, int16_t y, CRGB color) { + controller->leds()[XY(x, y)] = color; + } + + void setColor(int16_t pixel, CRGB color) { + controller->leds()[pixel] = color; + } + + CRGB getColor(int16_t pixel) { + return controller->leds()[pixel]; + } + + CRGB getColor(int16_t x, int16_t y) { + return getColor(XY(x, y)); + } + + int16_t XY(int16_t x, int16_t y) { + if(arrangement == ArrangementType::HORIZONTALSCAN) { + return getWidth() * y + x; + } else if(arrangement == ArrangementType::COLUMNSCAN) { + return getHeight() * x + y; + } else if(arrangement == ArrangementType::HORIZONTALSERPENTINE) { + if(y & 0x1) { + return y * getWidth() + (getWidth() - 1 - x); + } else { + return y * getWidth() + x; + } + } else if(arrangement == ArrangementType::COLUMNSERPENTINE) { + if(x & 0x1) { + return x * getHeight() + (getHeight() - 1 - y); + } else { + return x * getHeight() + y; + } + } else { + return 0; //How did you get here? + } + } + + uint16_t getNumLEDs() { + return getWidth() * getHeight(); + } + + protected: + void updateLEDs() { + controller->showLeds(); + } + + private: + CLEDController* + controller; + ArrangementType + arrangement; +}; + +#endif \ No newline at end of file diff --git a/include/CLEDControllerPhysicalStrip.h b/include/CLEDControllerPhysicalStrip.h new file mode 100644 index 0000000..2427c7e --- /dev/null +++ b/include/CLEDControllerPhysicalStrip.h @@ -0,0 +1,24 @@ +#ifndef CLEDCONTROLLERPHYSICALSTRIP_H +#define CLEDCONTROLLERPHYSICALSTRIP_H + +#include "LEDHAL.h" + +class CLEDControllerPhysicalStrip : public LEDHAL { + public: + CLEDControllerPhysicalStrip(CLEDController* _controller, char* _ledName) : + LEDHAL(_ledName, true), controller(_controller) {} + + uint16_t getNumLEDs() { return controller->size(); } + CRGB getColor(int16_t pixel) { return controller->leds()[pixel]; } + void setColor(int16_t pixel, CRGB color) { controller->leds()[pixel] = color; } + + protected: + void updateLEDs() { controller->showLeds(); } + + + private: + CLEDController* + controller; +}; + +#endif \ No newline at end of file diff --git a/include/CollisionMatrix.h b/include/CollisionMatrix.h new file mode 100644 index 0000000..15e2e30 --- /dev/null +++ b/include/CollisionMatrix.h @@ -0,0 +1,39 @@ +#ifndef COLLISIONMATRIX_H +#define COLLISIONMATRIX_H + +#include "MatrixAnimation.h" + +enum CollisionType { + HORIZONTAL_COLLISION, + VERTICAL_COLLISION +}; + +class CollisionMatrix : public MatrixAnimation { + public: + CollisionMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, CRGB _color1, + CRGB _color2, CollisionType _type) : MatrixAnimation(_matrix, _refName, _updateTime), + color1(_color1), color2(_color2), type(_type), collisionInt(0) {} + virtual ~CollisionMatrix() {} + + void initialize() { + collisionInt = 0; + resetTimer(); + } + + void execute(); + + CollisionType getType() { + return type; + } + + private: + uint16_t + collisionInt; + CRGB + color1, + color2; + CollisionType + type; +}; + +#endif \ No newline at end of file diff --git a/include/CollisionStrip.h b/include/CollisionStrip.h new file mode 100644 index 0000000..d45991a --- /dev/null +++ b/include/CollisionStrip.h @@ -0,0 +1,28 @@ +#ifndef COLLISIONSTRIP_H +#define COLLISIONSTRIP_H + +#include "StripAnimation.h" +#include "LEDHAL.h" + +class CollisionStrip : public StripAnimation { + public: + CollisionStrip(LEDHAL* _strip, char* _refName, long _updateTime, CRGB _color1, + CRGB _color2) : StripAnimation(_strip, _refName, _updateTime), color1(_color1), + color2(_color2), collisionInt(0) {} + virtual ~CollisionStrip() {} + + void initialize() { + collisionInt = 0; + resetTimer(); + } + + void execute(); + private: + uint16_t + collisionInt; + CRGB + color1, + color2; +}; + +#endif \ No newline at end of file diff --git a/include/ColorRandomizerMatrix.h b/include/ColorRandomizerMatrix.h new file mode 100644 index 0000000..b7a416c --- /dev/null +++ b/include/ColorRandomizerMatrix.h @@ -0,0 +1,68 @@ +#ifndef COLORRANDOMIZERMATRIX_H +#define COLORRANDOMIZERMATRIX_H + +#include "MatrixAnimation.h" +#include "LEDHAL2D.h" + +#define DEFAULT_MATRIX_FADER 64 + +enum ColorRandomizerType { + HORIZONTAL_COLORRANDOMIZER, + VERTICAL_COLORRANDOMIZER +}; + +class ColorRandomizerMatrix : public MatrixAnimation { + public: + ColorRandomizerMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, bool _fade, + uint8_t _numColors, CRGB* _colors, ColorRandomizerType _type) : + MatrixAnimation(_matrix, _refName, _updateTime), numColors(_numColors), + colors(new CRGB[_numColors]), fade(_fade), colorRandomizerInt(0), + scale(DEFAULT_MATRIX_FADER) { + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + + virtual ~ColorRandomizerMatrix() { delete colors; delete fadingValues; } + + void initialize() { + colorRandomizerInt = 0; + isFading = false; + + if(fadingValues != nullptr) { + delete fadingValues; + } + + if(type == ColorRandomizerType::HORIZONTAL_COLORRANDOMIZER) { + fadingValues = new CRGB[matrix->getHeight()]; + } else { + fadingValues = new CRGB[matrix->getWidth()]; + } + } + + void execute(); + + void setFade(bool _fade) { fade = _fade; initialize(); } + bool getFade() { return fade; } + void setFadeScale(uint8_t _scale) { scale = _scale; initialize(); } + uint8_t getFadeScale() { return scale; } + void setType(ColorRandomizerType _type) { type = _type; initialize(); } + ColorRandomizerType getType() { return type; } + + private: + CRGB + *colors, + *fadingValues; + uint16_t + colorRandomizerInt; + uint8_t + scale, + numColors; + bool + isFading, + fade; + ColorRandomizerType + type; +}; + +#endif \ No newline at end of file diff --git a/include/ColorRandomizerStrip.h b/include/ColorRandomizerStrip.h new file mode 100644 index 0000000..0bdc720 --- /dev/null +++ b/include/ColorRandomizerStrip.h @@ -0,0 +1,50 @@ +#ifndef COLORRANDOMIZERSTRIP_H +#define COLORRANDOMIZERSTRIP_H + +#include "StripAnimation.h" +#include "LEDHAL.h" + +#define DEFAULT_STRIP_FADER 64 + +class ColorRandomizerStrip : public StripAnimation { + public: + ColorRandomizerStrip(LEDHAL* _strip, char* _refName, long _updateTime, bool _fade, + uint8_t _numColors, CRGB* _colors) : StripAnimation(_strip, _refName, _updateTime), + numColors(_numColors), colors(new CRGB[_numColors]), + fadingValues(new CRGB[_numColors]), fade(_fade), colorRandomizerInt(0), + scale(DEFAULT_STRIP_FADER) { + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + + virtual ~ColorRandomizerStrip() { delete colors; delete fadingValues; } + + void initialize() { + colorRandomizerInt = 0; + isFading = false; + resetTimer(); + } + + void execute(); + + void setFade(bool _fade) { fade = _fade; initialize(); } + bool getFade() { return fade; } + void setFadeScale(uint8_t _scale) { scale = _scale; initialize(); } + uint8_t getFadeScale() { return scale; } + + private: + CRGB + *colors, + *fadingValues; + uint16_t + colorRandomizerInt; + uint8_t + scale, + numColors; + bool + isFading, + fade; +}; + +#endif \ No newline at end of file diff --git a/include/CycleLightMatrix.h b/include/CycleLightMatrix.h new file mode 100644 index 0000000..0ec0b76 --- /dev/null +++ b/include/CycleLightMatrix.h @@ -0,0 +1,47 @@ +#ifndef CYCLELIGHTMATRIX_H +#define CYCLELIGHTMATRIX_H + +#include "MatrixAnimation.h" + +enum CycleLightType { + HORIZONTAL_CYCLELIGHT, + VERTICAL_CYCLELIGHT +}; + +class CycleLightMatrix : public MatrixAnimation { + public: + CycleLightMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, CRGB* _colors, + uint8_t _numColors, CycleLightType _type) : MatrixAnimation(_matrix, _refName, + _updateTime), numColors(_numColors), colors(new CRGB[_numColors]), + cycleLightInt(0), cycleLightColor(0), type(_type) { + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + virtual ~CycleLightMatrix() { delete colors; } + + void initialize() { + cycleLightInt = 0; + cycleLightColor = 0; + resetTimer(); + } + + void execute(); + + CycleLightType getType() { + return type; + } + + private: + uint16_t + cycleLightInt, + cycleLightColor; + CRGB* + colors; + uint8_t + numColors; + CycleLightType + type; +}; + +#endif \ No newline at end of file diff --git a/include/CycleLightStrip.h b/include/CycleLightStrip.h new file mode 100644 index 0000000..25b578f --- /dev/null +++ b/include/CycleLightStrip.h @@ -0,0 +1,38 @@ +#ifndef CYCLELIGHTSTRIP_H +#define CYCLELIGHTSTRIP_H + +#include "StripAnimation.h" +#include "LEDHAL.h" + +class CycleLightStrip : public StripAnimation { + public: + CycleLightStrip(LEDHAL* _strip, char* _refName, long _updateTime, uint8_t _numColors, + CRGB* _colors) : StripAnimation(_strip, _refName, _updateTime), + numColors(_numColors), colors(new CRGB[_numColors]), cycleLightInt(0), + cycleLightColor(0) { + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + + virtual ~CycleLightStrip() { delete colors; } + + void initialize() { + cycleLightInt = 0; + cycleLightColor = 0; + resetTimer(); + } + + void execute(); + + private: + uint16_t + cycleLightInt, + cycleLightColor; + CRGB* + colors; + uint8_t + numColors; +}; + +#endif \ No newline at end of file diff --git a/include/CycloneHelper.h b/include/CycloneHelper.h new file mode 100644 index 0000000..211dcbf --- /dev/null +++ b/include/CycloneHelper.h @@ -0,0 +1,60 @@ +#ifndef CYCLONEHELPER_H +#define CYCLONEHELPER_H + +#include "Arduino.h" +#include "Point2D.h" +#include "LEDHAL2D.h" + +class CycloneHelper { + public: + CycloneHelper(CRGB _color, uint16_t _originX, uint16_t _originY, + uint8_t _radius, uint8_t _spinDirection) : color(_color), originX(_originX), + originY(_originY), radius(_radius), spinDirection(_spinDirection), + currentDrawAngle(0) { + + numberOfTails = (uint8_t) round(PI * _radius * 2); + + tailColors = new CRGB[numberOfTails]; + tailPoints = new Point2D[numberOfTails]; + + float redScaler = ((float) color.red) / numberOfTails; + float greenScaler = ((float) color.green) / numberOfTails; + float blueScaler = ((float) color.blue) / numberOfTails; + + for(uint8_t i = 0; i < numberOfTails; i++) { + tailColors[i] = CRGB( + min(round(redScaler * (i + 1)), color.red), + min(round(greenScaler * (i + 1)), color.green), + min(round(blueScaler * (i + 1)), color.blue) + ); + } + + nextPoint = Point2D( + _originX + (uint16_t) round(radius * cosf(currentDrawAngle)), + _originY + (uint16_t) round(radius * sinf(currentDrawAngle)) + ); + } + + void updateAndRedraw(LEDHAL2D* matrix); + private: + CRGB + color; + CRGB* + tailColors; + Point2D + nextPoint; + Point2D* + tailPoints; + int8_t + spinDirection; + uint8_t + radius, + numberOfTails; + uint16_t + originX, + originY; + int16_t + currentDrawAngle; +}; + +#endif \ No newline at end of file diff --git a/include/CycloneMatrix.h b/include/CycloneMatrix.h new file mode 100644 index 0000000..5174c40 --- /dev/null +++ b/include/CycloneMatrix.h @@ -0,0 +1,63 @@ +#ifndef CYCLONEMATRIX_H +#define CYCLONEMATRIX_H + +#include "LEDHAL2D.h" +#include "CycloneHelper.h" +#include "MatrixAnimation.h" + +class CycloneMatrix : public MatrixAnimation { + public: + CycloneMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, uint16_t originX, + uint16_t originY, uint8_t _startRadius, uint8_t _endRadius, uint8_t numColors, + CRGB* _colors) : MatrixAnimation(_matrix, _refName, _updateTime), + startRadius(_startRadius), endRadius(_endRadius) { + + colors = new CRGB[numColors]; + + for(uint8_t i = 0; i < numColors; i++) { + colors[i] = _colors[i]; + } + + helpers = new CycloneHelper*[_endRadius - _startRadius]; + + // The colors are currently being passed in such a way that I believe + // there being taken into the CycloneHelper through value, rather than ByRef + // i.e. wasting memory + for(uint8_t i = 0; i < _endRadius - _startRadius; i++) { + helpers[i] = new CycloneHelper( + colors[random(0, numColors)], + originX, originY, + _startRadius + i, + random(0, 100000) % 2 == 0 ? 1 : -1 + ); + } + } + + virtual ~CycloneMatrix() { delete helpers; delete colors; } + + // I don't know that there is a nice, memory friendly way to + // reset this yet... + void initialize() { + resetTimer(); + } + + void execute() { + matrix->clearLEDs(); + + for(uint8_t i = 0; i < endRadius - startRadius; i++) { + helpers[i]->updateAndRedraw(matrix); + } + + matrix->requestShow(); + } + private: + uint8_t + startRadius, + endRadius; + CRGB* + colors; + CycloneHelper** + helpers; +}; + +#endif \ No newline at end of file diff --git a/include/DecodingBitStream.h b/include/DecodingBitStream.h new file mode 100644 index 0000000..f98f4ca --- /dev/null +++ b/include/DecodingBitStream.h @@ -0,0 +1,37 @@ +#ifndef DECODINGBITSTREAM_H +#define DECODINGBITSTREAM_H + +#include "SD.h" + +class DecodingBitStream +{ + public: + DecodingBitStream(File* _stream, uint8_t _bytesAvailable, uint16_t _numberOfBitsInCode) : + stream(_stream), bytesAvailable(_bytesAvailable), numberOfBitsInCode(_numberOfBitsInCode), + bitBuffer(0), bitsAvailable(0) {} + ~DecodingBitStream() {} + + void setNumberOfBitsInCode(uint16_t _numberOfBitsInCode) { numberOfBitsInCode = _numberOfBitsInCode; } + void setBytesAvailable(uint8_t _bytesAvailable) { bytesAvailable = _bytesAvailable; } + + uint16_t getCode(); + uint16_t getNumberOfBitsInCode() { return numberOfBitsInCode; } + + bool dataRemainingInBlock() { return bytesAvailable > 0 || bitsAvailable > 0; } + + static uint16_t getNumberOfBitsToRepresentValue(uint16_t value); + + private: + void + verifyEnoughBits(void); + uint16_t + bitBuffer; + uint8_t + bytesAvailable, + bitsAvailable, + numberOfBitsInCode; + File* + stream; +}; + +#endif diff --git a/include/FireworksMatrix.h b/include/FireworksMatrix.h new file mode 100644 index 0000000..32ce525 --- /dev/null +++ b/include/FireworksMatrix.h @@ -0,0 +1,42 @@ +#ifndef FIREWORKSMATRIX_H +#define FIREWORKSMATRIX_H + +#include "MatrixAnimation.h" + +typedef struct _firework_t { + int16_t xPos; + int16_t yPos; + int16_t yMaxHeight; + uint16_t currentRadius; + uint16_t radius; + CRGB color; + bool isActive; +} Firework_t; + +class FireworksMatrix : public MatrixAnimation { + public: + FireworksMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, CRGB* _colors, + uint8_t _numColors, uint8_t _maxRadius, uint8_t _maxFireworks) : + MatrixAnimation(_matrix, _refName, _updateTime), colors(new CRGB[_numColors]), + numColors(_numColors), maxRadius(_maxRadius), maxFireworks(_maxFireworks) { + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + virtual ~FireworksMatrix() { delete[] colors; delete[] fireworks; } + + void initialize(); + void execute(); + + private: + uint8_t + maxFireworks, + numColors, + maxRadius; + CRGB* + colors; + Firework_t* + fireworks; +}; + +#endif \ No newline at end of file diff --git a/include/FluidColorMatrix.h b/include/FluidColorMatrix.h new file mode 100644 index 0000000..0b6a13a --- /dev/null +++ b/include/FluidColorMatrix.h @@ -0,0 +1,41 @@ +#ifndef FLUIDCOLORMATRIX_H +#define FLUIDCOLORMATRIX_H + +#include "MatrixAnimation.h" + +enum FluidColorType { + PIXEL_BY_PIXEL_FLUIDCOLOR, + HORIZONTAL_FLUIDCOLOR, + VERTICAL_FLUIDCOLOR +}; + +enum FluidColorResolution { + FULL_FLUIDCOLOR, + HALF_FLUIDCOLOR, + QUATER_FLUIDCOLOR, + EIGHTH_FLUIDCOLOR +}; + +class FluidColorMatrix : public MatrixAnimation { + public: + FluidColorMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, + FluidColorType _type, FluidColorResolution _resolution) : MatrixAnimation(_matrix, + _refName, _updateTime), type(_type), resolution(_resolution), colorShifter(0) {} + virtual ~FluidColorMatrix() { delete[] colors; } + + void initialize(); + void execute(); + private: + uint16_t + colorShifter; + CRGB* + colors; + uint8_t + numColors; + FluidColorType + type; + FluidColorResolution + resolution; +}; + +#endif \ No newline at end of file diff --git a/include/GIFMatrix.h b/include/GIFMatrix.h new file mode 100644 index 0000000..3e146a8 --- /dev/null +++ b/include/GIFMatrix.h @@ -0,0 +1,89 @@ +#ifndef GIFMATRIX_H +#define GIFMATRIX_H + +#include "SD.h" +#include "MatrixAnimation.h" + +class GIFMatrix : public MatrixAnimation { + public: + GIFMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, File* _dataFile) : + MatrixAnimation(_matrix, _refName, _updateTime), dataFile(_dataFile) { + STANDARDHEADER[0] = 0x47; + STANDARDHEADER[1] = 0x49; + STANDARDHEADER[2] = 0x46; + STANDARDHEADER[3] = 0x38; + STANDARDHEADER[4] = 0x39; + STANDARDHEADER[5] = 0x61; + + if(!headerOK()) { + // I can't throw exceptions, need to do something here... + } + + decodeLogicalDescriptor(); + + preImageDataBytes = 6 + 7 + (globalColorTableExists ? globalColorTableSize * 3 : 0); + + lastCode = _dataFile->read(); + } + + void initialize() { + // Reinitialization needs to be tested, this may not work correctly + dataFile->seek(preImageDataBytes); + resetTimer(); + } + + void execute(); + bool headerOK(); + void decodeLogicalDescriptor(); + void decodeGlobalColorTable(); + void decodeGraphicControlExtension(); + void decodeApplicationExtension(); + void decodeImageDescriptor(); + void buildCodeTable(); + void drawForArray(CRGB* colorTable, uint16_t* indexes, uint16_t startingXPosition, uint16_t imageWidth); + uint16_t readWord(); + + private: + File* + dataFile; + uint16_t + preImageDataBytes, + currentXPosition, + currentYPosition, + gifCanvasWidth, + gitCanvasHeight, + currentImageLeft, + currentImageTop, + currentImageWidth, + currentImageHeight, + currentCCCode, + currentEOICode; + uint16_t** + currentCodeTable; + long + delayTime; + uint8_t + lastCode, + originalColorResolution, + globalColorTableSize, + globalColorTableBackgroundColorIndex, + disposalMethodValue, + userInputFlag, + transparentColorFlag, + transparentColorIndex, + localColorTableSize, + currentMinimumCodeSize; + bool + globalColorTableExists, + globalColorTableSorted, + localColorTableExists, + interlaceFlag, + localColorTableSorted; + CRGB* + globalColorTable, + localColorTable; + byte + STANDARDHEADER[6]; +}; + +#endif \ No newline at end of file diff --git a/include/GULLS_GFX.h b/include/GULLS_GFX.h new file mode 100644 index 0000000..aa47474 --- /dev/null +++ b/include/GULLS_GFX.h @@ -0,0 +1,312 @@ +#ifndef _GULLS_GFX_H +#define _GULLS_GFX_H + +#if ARDUINO >= 100 +#include "Arduino.h" +#include "Print.h" +#else +#include "WProgram.h" +#endif +#include "gfxfont.h" + +#include "FastLED.h" + +#include +#include + +/// A generic graphics superclass that can handle all sorts of drawing. At a +/// minimum you can subclass and provide drawPixel(). At a maximum you can do a +/// ton of overriding to optimize. Used for any/all Adafruit displays! +class GULLS_GFX : public Print { + +public: + GULLS_GFX(int16_t w, int16_t h); // Constructor + + /**********************************************************************/ + /*! + @brief Draw to the screen/framebuffer/etc. + Must be overridden in subclass. + @param x X coordinate in pixels + @param y Y coordinate in pixels + @param color 16-bit pixel color. + */ + /**********************************************************************/ + virtual void drawPixel(int16_t x, int16_t y, CRGB color) = 0; + + // TRANSACTION API / CORE DRAW API + // These MAY be overridden by the subclass to provide device-specific + // optimized code. Otherwise 'generic' versions are used. + virtual void startWrite(void); + virtual void writePixel(int16_t x, int16_t y, CRGB color); + virtual void writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h, + CRGB color); + virtual void writeFastVLine(int16_t x, int16_t y, int16_t h, CRGB color); + virtual void writeFastHLine(int16_t x, int16_t y, int16_t w, CRGB color); + virtual void writeLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + CRGB color); + virtual void endWrite(void); + + // CONTROL API + // These MAY be overridden by the subclass to provide device-specific + // optimized code. Otherwise 'generic' versions are used. + virtual void setRotation(uint8_t r); + virtual void invertDisplay(bool i); + + // BASIC DRAW API + // These MAY be overridden by the subclass to provide device-specific + // optimized code. Otherwise 'generic' versions are used. + + // It's good to implement those, even if using transaction API + virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, CRGB color); + virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, CRGB color); + virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, + CRGB color); + virtual void fillScreen(CRGB color); + // Optional and probably not necessary to change + virtual void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + CRGB color); + virtual void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, + CRGB color); + + // These exist only with GULLS_GFX (no subclass overrides) + void drawCircle(int16_t x0, int16_t y0, int16_t r, CRGB color); + void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, + CRGB color); + void fillCircle(int16_t x0, int16_t y0, int16_t r, CRGB color); + void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, + int16_t delta, CRGB color); + void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, + int16_t y2, CRGB color); + void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, + int16_t y2, CRGB color); + void drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, + int16_t radius, CRGB color); + void fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, + int16_t radius, CRGB color); + void drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, + int16_t h, CRGB color); + void drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, + int16_t h, CRGB color, CRGB bg); + void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, + CRGB color); + void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, + CRGB color, CRGB bg); + void drawXBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, + int16_t h, CRGB color); + void drawGrayscaleBitmap(int16_t x, int16_t y, const uint8_t bitmap[], + int16_t w, int16_t h); + void drawGrayscaleBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, + int16_t h); + void drawGrayscaleBitmap(int16_t x, int16_t y, const uint8_t bitmap[], + const uint8_t mask[], int16_t w, int16_t h); + void drawGrayscaleBitmap(int16_t x, int16_t y, uint8_t *bitmap, uint8_t *mask, + int16_t w, int16_t h); + void drawRGBBitmap(int16_t x, int16_t y, const CRGB bitmap[], int16_t w, + int16_t h); + void drawRGBBitmap(int16_t x, int16_t y, CRGB *bitmap, int16_t w, + int16_t h); + void drawRGBBitmap(int16_t x, int16_t y, const CRGB bitmap[], + const uint8_t mask[], int16_t w, int16_t h); + void drawRGBBitmap(int16_t x, int16_t y, CRGB *bitmap, uint8_t *mask, + int16_t w, int16_t h); + void drawChar(int16_t x, int16_t y, unsigned char c, CRGB color, + CRGB bg, uint8_t size); + void drawChar(int16_t x, int16_t y, unsigned char c, CRGB color, + CRGB bg, uint8_t size_x, uint8_t size_y); + void getTextBounds(const char *string, int16_t x, int16_t y, int16_t *x1, + int16_t *y1, uint16_t *w, uint16_t *h); + void getTextBounds(const __FlashStringHelper *s, int16_t x, int16_t y, + int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h); + void getTextBounds(const String &str, int16_t x, int16_t y, int16_t *x1, + int16_t *y1, uint16_t *w, uint16_t *h); + void setTextSize(uint8_t s); + void setTextSize(uint8_t sx, uint8_t sy); + void setFont(const GFXfont *f = NULL); + + /**********************************************************************/ + /*! + @brief Set text cursor location + @param x X coordinate in pixels + @param y Y coordinate in pixels + */ + /**********************************************************************/ + void setCursor(int16_t x, int16_t y) { + cursor_x = x; + cursor_y = y; + } + + /**********************************************************************/ + /*! + @brief Set text font color with transparant background + @param c 16-bit 5-6-5 Color to draw text with + @note For 'transparent' background, background and foreground + are set to same color rather than using a separate flag. + */ + /**********************************************************************/ + void setTextColor(CRGB c) { textcolor = textbgcolor = c; } + + /**********************************************************************/ + /*! + @brief Set text font color with custom background color + @param c 16-bit 5-6-5 Color to draw text with + @param bg 16-bit 5-6-5 Color to draw background/fill with + */ + /**********************************************************************/ + void setTextColor(CRGB c, CRGB bg) { + textcolor = c; + textbgcolor = bg; + } + + /**********************************************************************/ + /*! + @brief Set whether text that is too long for the screen width should + automatically wrap around to the next line (else clip right). + @param w true for wrapping, false for clipping + */ + /**********************************************************************/ + void setTextWrap(bool w) { wrap = w; } + + /**********************************************************************/ + /*! + @brief Enable (or disable) Code Page 437-compatible charset. + There was an error in glcdfont.c for the longest time -- one + character (#176, the 'light shade' block) was missing -- this + threw off the index of every character that followed it. + But a TON of code has been written with the erroneous + character indices. By default, the library uses the original + 'wrong' behavior and old sketches will still work. Pass + 'true' to this function to use correct CP437 character values + in your code. + @param x true = enable (new behavior), false = disable (old behavior) + */ + /**********************************************************************/ + void cp437(bool x = true) { _cp437 = x; } + + using Print::write; +#if ARDUINO >= 100 + virtual size_t write(uint8_t); +#else + virtual void write(uint8_t); +#endif + + /************************************************************************/ + /*! + @brief Get width of the display, accounting for current rotation + @returns Width in pixels + */ + /************************************************************************/ + int16_t width(void) const { return _width; }; + + /************************************************************************/ + /*! + @brief Get height of the display, accounting for current rotation + @returns Height in pixels + */ + /************************************************************************/ + int16_t height(void) const { return _height; } + + /************************************************************************/ + /*! + @brief Get rotation setting for display + @returns 0 thru 3 corresponding to 4 cardinal rotations + */ + /************************************************************************/ + uint8_t getRotation(void) const { return rotation; } + + // get current cursor position (get rotation safe maximum values, + // using: width() for x, height() for y) + /************************************************************************/ + /*! + @brief Get text cursor X location + @returns X coordinate in pixels + */ + /************************************************************************/ + int16_t getCursorX(void) const { return cursor_x; } + + /************************************************************************/ + /*! + @brief Get text cursor Y location + @returns Y coordinate in pixels + */ + /************************************************************************/ + int16_t getCursorY(void) const { return cursor_y; }; + +protected: + void charBounds(unsigned char c, int16_t *x, int16_t *y, int16_t *minx, + int16_t *miny, int16_t *maxx, int16_t *maxy); + int16_t WIDTH; ///< This is the 'raw' display width - never changes + int16_t HEIGHT; ///< This is the 'raw' display height - never changes + int16_t _width; ///< Display width as modified by current rotation + int16_t _height; ///< Display height as modified by current rotation + int16_t cursor_x; ///< x location to start print()ing text + int16_t cursor_y; ///< y location to start print()ing text + CRGB textcolor; ///< 16-bit background color for print() + CRGB textbgcolor; ///< 16-bit text color for print() + uint8_t textsize_x; ///< Desired magnification in X-axis of text to print() + uint8_t textsize_y; ///< Desired magnification in Y-axis of text to print() + uint8_t rotation; ///< Display rotation (0 thru 3) + bool wrap; ///< If set, 'wrap' text at right edge of display + bool _cp437; ///< If set, use correct CP437 charset (default is off) + GFXfont *gfxFont; ///< Pointer to special font +}; + +/// A simple drawn button UI element +class GULLS_GFX_Button { + +public: + GULLS_GFX_Button(void); + // "Classic" initButton() uses center & size + void initButton(GULLS_GFX *gfx, int16_t x, int16_t y, uint16_t w, + uint16_t h, CRGB outline, CRGB fill, + CRGB textcolor, char *label, uint8_t textsize); + void initButton(GULLS_GFX *gfx, int16_t x, int16_t y, uint16_t w, + uint16_t h, CRGB outline, CRGB fill, + CRGB textcolor, char *label, uint8_t textsize_x, + uint8_t textsize_y); + // New/alt initButton() uses upper-left corner & size + void initButtonUL(GULLS_GFX *gfx, int16_t x1, int16_t y1, uint16_t w, + uint16_t h, CRGB outline, CRGB fill, + CRGB textcolor, char *label, uint8_t textsize); + void initButtonUL(GULLS_GFX *gfx, int16_t x1, int16_t y1, uint16_t w, + uint16_t h, CRGB outline, CRGB fill, + CRGB textcolor, char *label, uint8_t textsize_x, + uint8_t textsize_y); + void drawButton(bool inverted = false); + bool contains(int16_t x, int16_t y); + + /**********************************************************************/ + /*! + @brief Sets button state, should be done by some touch function + @param p True for pressed, false for not. + */ + /**********************************************************************/ + void press(bool p) { + laststate = currstate; + currstate = p; + } + + bool justPressed(); + bool justReleased(); + + /**********************************************************************/ + /*! + @brief Query whether the button is currently pressed + @returns True if pressed + */ + /**********************************************************************/ + bool isPressed(void) { return currstate; }; + +private: + GULLS_GFX *_gfx; + int16_t _x1, _y1; // Coordinates of top-left corner + uint16_t _w, _h; + uint8_t _textsize_x; + uint8_t _textsize_y; + CRGB _outlinecolor, _fillcolor, _textcolor; + char _label[10]; + + bool currstate, laststate; +}; + + +#endif // _GULLS_GFX_H \ No newline at end of file diff --git a/include/LEDHAL.h b/include/LEDHAL.h new file mode 100644 index 0000000..6f8393b --- /dev/null +++ b/include/LEDHAL.h @@ -0,0 +1,50 @@ +#ifndef LEDHAL_H +#define LEDHAL_H + +#include "FastLED.h" + +class LEDHAL { + public: + LEDHAL(char* _ledName, bool _isPhysical) : + ledName(_ledName), isPhysical(_isPhysical), showRequested(false) {} + virtual ~LEDHAL() { delete ledName; } + + virtual char* getLEDName() { return ledName; } + + virtual uint16_t getNumLEDs() = 0; + + virtual bool getIsPhysical() { return isPhysical; } + + virtual CRGB getColor(int16_t pixel) = 0; + + virtual void setColor(int16_t pixel, CRGB color) = 0; + + virtual void requestShow() { showRequested = true; } + + //TODO UnifiedColor should have constants + virtual void clearLEDs() { + for(uint16_t i = 0; i < getNumLEDs(); i++) { + setColor(i, CRGB(0, 0, 0)); + } + }; + + void show() { + if(showRequested) { + updateLEDs(); + + showRequested = false; + } + } + + protected: + virtual void updateLEDs() {}; + + private: + char* + ledName; + bool + isPhysical, + showRequested; +}; + +#endif \ No newline at end of file diff --git a/include/LEDHAL2D.h b/include/LEDHAL2D.h new file mode 100644 index 0000000..6b94e85 --- /dev/null +++ b/include/LEDHAL2D.h @@ -0,0 +1,42 @@ +#ifndef LEDHAL2D_H +#define LEDHAL2D_H + +#include "LEDHAL.h" + +#include "GULLS_GFX.h" + +class LEDHAL2D : public LEDHAL, public GULLS_GFX { + public: + LEDHAL2D(int16_t width, int16_t height, char* ledName, bool isPhysical) : + LEDHAL(ledName, isPhysical), GULLS_GFX(width, height), _width(width), _height(height) {} + + virtual uint16_t getNumLEDs() = 0; + virtual CRGB getColor(int16_t pixel) = 0; + virtual void drawPixel(int16_t x, int16_t y, CRGB color) = 0; + virtual CRGB getColor(int16_t x, int16_t y) = 0; + + virtual int16_t getWidth() { + return _width; + } + + virtual int16_t getHeight() { + return _height; + } + + virtual void clearLEDs() { + CRGB noColor(0, 0, 0); + + for(int16_t x = 0; x < getWidth(); x++) { + for(int16_t y = 0; y < getHeight(); y++) { + drawPixel(x, y, noColor); + } + } + } + + private: + int16_t + _width, + _height; //TODO Really need to be getting these values from GFX +}; + +#endif \ No newline at end of file diff --git a/include/LogicalMatrix.h b/include/LogicalMatrix.h new file mode 100644 index 0000000..273e948 --- /dev/null +++ b/include/LogicalMatrix.h @@ -0,0 +1,71 @@ +#ifndef LOGICALMATRIX_H +#define LOGICALMATRIX_H + +#include "LEDHAL2D.h" +#include "FastLED.h" + +class LogicalMatrix : public LEDHAL2D { + public: + LogicalMatrix(char* _ledName, LEDHAL2D* _parent, int16_t _startX, int16_t _startY, + int16_t _stopX, int16_t _stopY) : LEDHAL2D(_stopX - _startX, _stopY - _startY, + _ledName, false), startX(_startX), stopX(_stopX), startY(_startY), stopY(_stopY), + parent(_parent) {} + virtual ~LogicalMatrix() {} + + void drawPixel(int16_t x, int16_t y, CRGB color) { + parent->drawPixel(startX + x, startY + y, color); + } + + void setColor(int16_t pixel, CRGB color) { + //This is a computationally expensive cheap trick that I'm not + //entirely convinced is necessary, it is essentially the reverse + //of pixelPos = getHeight() * y + x + //Integer division by getHeight returns y from the above + //Modulous (remainder) by getHeight returns x from the above + int16_t intDiv = pixel / getHeight(); //yPos + int16_t intMod = pixel % getHeight(); //xPos + + parent->setColor((startX + intMod) * (startY + intDiv), color); + } + + CRGB getColor(int16_t pixel) { + //This is a computationally expensive cheap trick that I'm not + //entirely convinced is necessary, it is essentially the reverse + //of pixelPos = getHeight() * y + x + //Integer division by getHeight returns y from the above + //Modulous (remainder) by getHeight returns x from the above + int16_t intDiv = pixel / getHeight(); //yPos + int16_t intMod = pixel % getHeight(); //xPos + + return parent->getColor((startX + intMod) * (startY + intDiv)); + } + + CRGB getColor(int16_t x, int16_t y) { + return parent->getColor(startX + x, startY + y); + } + + uint16_t getNumLEDs() { + return getWidth() * getHeight(); + } + + void requestShow() { + parent->requestShow(); + } + + protected: + void updateLEDs() { + //This does nothing, because show should never be called on + //a logical strip + } + + private: + LEDHAL2D* + parent; + int16_t + startX, + stopX, + startY, + stopY; +}; + +#endif \ No newline at end of file diff --git a/include/LogicalStrip.h b/include/LogicalStrip.h new file mode 100644 index 0000000..a41e21f --- /dev/null +++ b/include/LogicalStrip.h @@ -0,0 +1,42 @@ +#ifndef LOGICALSTRIP_H +#define LOGICALSTRIP_H + +#include "LEDHAL.h" +#include "FastLED.h" + +class LogicalStrip : public LEDHAL { + public: + LogicalStrip(char* _ledName, LEDHAL* _parent, int16_t _startPixel, + uint16_t _stopPixel) : LEDHAL(_ledName, false), parent(_parent), + startPixel(_startPixel), stopPixel(_stopPixel) {} + virtual ~LogicalStrip() {} + + uint16_t getNumLEDs() { return stopPixel - startPixel; } + + CRGB getColor(int16_t pixel) { + return parent->getColor(startPixel + pixel); + } + + void setColor(uint16_t pixel, CRGB color) { + parent->setColor(startPixel + pixel, color); + } + + void requestShow() { + parent->requestShow(); + } + + protected: + void updateLEDs() { + //This does nothing, because show should never be called on + //a logical strip + } + + private: + LEDHAL* + parent; + int16_t + startPixel, + stopPixel; +}; + +#endif \ No newline at end of file diff --git a/include/MatrixAnimation.h b/include/MatrixAnimation.h new file mode 100644 index 0000000..87aed8a --- /dev/null +++ b/include/MatrixAnimation.h @@ -0,0 +1,19 @@ +#ifndef MATRIXANIMATION_H +#define MATRIXANIMATION_H + +#include "AnimationBase.h" +#include "LEDHAL2D.h" + +class MatrixAnimation : public AnimationBase { + public: + MatrixAnimation(LEDHAL2D* _matrix, char* _refName, long _updateTime) : + AnimationBase(_refName, _updateTime), matrix(_matrix) {} + virtual ~MatrixAnimation() {} + + LEDHAL2D* getMatrix() { return matrix; } + protected: + LEDHAL2D* + matrix; +}; + +#endif \ No newline at end of file diff --git a/include/PlasmaMatrix.h b/include/PlasmaMatrix.h new file mode 100644 index 0000000..7027891 --- /dev/null +++ b/include/PlasmaMatrix.h @@ -0,0 +1,53 @@ +#ifndef PLASMAMATRIX_H +#define PLASMAMATRIX_H + +#include "MatrixAnimation.h" +#include "LEDHAL2D.h" + +class PlasmaMatrix : public MatrixAnimation { + public: + PlasmaMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime) : + MatrixAnimation(_matrix, _refName, _updateTime), paletteShift(0), palette(new CRGB[256]) { + plasma = new uint8_t*[matrix->getWidth()]; + + for(uint16_t i = 0; i < matrix->getWidth(); i++) { + plasma[i] = new uint8_t[matrix->getHeight()]; + } + + for(uint16_t x = 0; x < matrix->getWidth(); x++) { + for(uint16_t y = 0; y < matrix->getHeight(); y++) { + plasma[x][y] = (uint8_t) ((128.0 + (128 * sinf(x / 8.0)) + 128 + (128.0 * sinf(y / 8.0))) / 2); + } + } + + for(uint16_t i = 0; i < 256; i++) { + palette[i] = CRGB(CHSV(i, 255, 255)); + } + } + + virtual ~PlasmaMatrix() { + delete[] palette; + + for(uint16_t i = 0; i < matrix->getWidth(); i++) { + delete[] plasma[i]; + } + + delete[] plasma; + } + + void initialize() { + paletteShift = 0; + } + + void execute(); + + private: + uint16_t + paletteShift; + CRGB* + palette; + uint8_t** + plasma; +}; + +#endif \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/RicochetHelper.h b/include/RicochetHelper.h new file mode 100644 index 0000000..838fdf9 --- /dev/null +++ b/include/RicochetHelper.h @@ -0,0 +1,36 @@ +#ifndef RICOCHETHELPER_H +#define RICOCHETHELPER_H + +#include "Arduino.h" +#include "FastLED.h" + +class RicochetHelper { + public: + RicochetHelper() {} + RicochetHelper(int16_t _startPosX, int16_t _startPosY, int16_t _width, int16_t _height, + CRGB _color) : currentX(_startPosX), currentY(_startPosY), width(_width), + height(_height), xDir(random(0, 101) > 51 ? -1 : 1), yDir(random(0, 100) > 51 ? -1 : 1) {} + + int16_t getCurrentXPos() { return currentX; } + int16_t getCurrentYPos() { return currentY; } + + CRGB getCurrentColor() { return color; } + + void setCurrentColor(uint16_t _color) { color = _color; } + + void updatePositions(); + + private: + int16_t + currentX, + currentY, + width, + height; + int8_t + xDir, + yDir; + CRGB + color; +}; + +#endif \ No newline at end of file diff --git a/include/RicochetMatrix.h b/include/RicochetMatrix.h new file mode 100644 index 0000000..6b1fa99 --- /dev/null +++ b/include/RicochetMatrix.h @@ -0,0 +1,32 @@ +#ifndef RICOCHETMATRIX_H +#define RICOCHETMATRIX_H + +#include "MatrixAnimation.h" +#include "RicochetHelper.h" + +class RicochetMatrix : public MatrixAnimation { + public: + RicochetMatrix(LEDHAL2D* _matrix, char* _refName, long _updateTime, uint16_t _numBalls, + CRGB* _colors, uint8_t _numColors) : MatrixAnimation(_matrix, _refName, _updateTime), + numBalls(_numBalls), colors(new CRGB[_numColors]), numColors(_numColors), + balls(new RicochetHelper*[_numBalls]) { + for(uint8_t i = 0; i < _numColors; i++) { + colors[i] = _colors[i]; + } + } + virtual ~RicochetMatrix() { delete[] colors; delete[] balls; }; + + void initialize(); + void execute(); + private: + uint16_t + numBalls; + CRGB* + colors; + uint8_t + numColors; + RicochetHelper** + balls; +}; + +#endif \ No newline at end of file diff --git a/include/SmartMatrixPhysicalMatrix.h b/include/SmartMatrixPhysicalMatrix.h new file mode 100644 index 0000000..5f02a2d --- /dev/null +++ b/include/SmartMatrixPhysicalMatrix.h @@ -0,0 +1,46 @@ +#ifndef SMARTMATRIXPHYSICALMATRIX_H +#define SMARTMATRIXPHYSICALMATRIX_H + +#include "SmartMatrix.h" +#include "LEDHAL2D.h" + +class SmartMatrixPhysicalMatrix : public LEDHAL2D { + public: + SmartMatrixPhysicalMatrix(SMLayerBackground* _layer, char* ledName, int16_t width, int16_t height): + LEDHAL2D(width, height, ledName, true), layer(_layer) {} + virtual ~SmartMatrixPhysicalMatrix() {} + + void drawPixel(int16_t x, int16_t y, CRGB color) { + layer->drawPixel(x, y, color); + } + + void setColor(int16_t pixel, CRGB color) { + layer->drawPixel(pixel % getHeight(), (int)(pixel / getHeight()), color); + } + + CRGB getColor(int16_t x, int16_t y) { + rgb24 value = layer->readPixel(x, y); + + return CRGB(value.red, value.green, value.blue); + } + + CRGB getColor(int16_t pixel) { + rgb24 value = layer->readPixel(pixel % getHeight(), (int)(pixel / getHeight())); + + return CRGB(value.red, value.green, value.blue); + } + + uint16_t getNumLEDs() { + return getWidth() * getHeight(); + } + + protected: + void updateLEDs() { + layer->swapBuffers(); + } + + private: + SMLayerBackground* layer; +}; + +#endif \ No newline at end of file diff --git a/include/StripAnimation.h b/include/StripAnimation.h new file mode 100644 index 0000000..f226d03 --- /dev/null +++ b/include/StripAnimation.h @@ -0,0 +1,19 @@ +#ifndef STRIPANIMATION_H +#define STRIPANIMATION_H + +#include "AnimationBase.h" +#include "LEDHAL.h" + +class StripAnimation : public AnimationBase { + public: + StripAnimation(LEDHAL* _strip, char* _refName, long _updateTime) : + AnimationBase(_refName, _updateTime), strip(_strip) {} + virtual ~StripAnimation() {} + + LEDHAL* getStrip() { return strip; } + protected: + LEDHAL* + strip; +}; + +#endif \ No newline at end of file diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..2593a33 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..c797682 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,24 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = teensy36 + +[common] + +[env:teensy36] +platform = teensy +framework = arduino +board = teensy36 +lib_deps = + fastled/FastLED@^3.7.0 + pixelmatix/SmartMatrix@^4.0.3 + pfeerick/elapsedMillis@^1.0.6 + adafruit/Adafruit GFX Library@^1.11.9 diff --git a/src/AlternateMatrix.cpp b/src/AlternateMatrix.cpp new file mode 100644 index 0000000..5569735 --- /dev/null +++ b/src/AlternateMatrix.cpp @@ -0,0 +1,18 @@ +#include "AlternateMatrix.h" + +void AlternateMatrix::execute() { + if(type == AlternateType::HORIZONTAL_ALTERNATE) { + for(int16_t i = 0; i < matrix->getHeight(); i++) { + matrix->drawFastHLine(0, i, matrix->getWidth(), + colors[(i + alternateInt) % numColors]); + } + } else if(type == AlternateType::VERTICAL_ALTERNATE) { + for(int16_t i = 0; i < matrix->getWidth(); i++) { + matrix->drawFastVLine(i, 0, matrix->getHeight(), + colors[(i + alternateInt) % numColors]); + } + } + + alternateInt++; + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/AlternateStrip.cpp b/src/AlternateStrip.cpp new file mode 100644 index 0000000..257868b --- /dev/null +++ b/src/AlternateStrip.cpp @@ -0,0 +1,10 @@ +#include "AlternateStrip.h" + +void AlternateStrip::execute() { + for(int16_t i = 0; i < strip->getNumLEDs(); i++) { + strip->setColor(i, colors[(i + alternateInt) % numColors]); + } + + alternateInt++; + strip->requestShow(); +} \ No newline at end of file diff --git a/src/CollisionMatrix.cpp b/src/CollisionMatrix.cpp new file mode 100644 index 0000000..ab057c6 --- /dev/null +++ b/src/CollisionMatrix.cpp @@ -0,0 +1,23 @@ +#include "CollisionMatrix.h" + +void CollisionMatrix::execute() { + if(collisionInt == 0) { + matrix->clearLEDs(); + } + + if(type == CollisionType::HORIZONTAL_COLLISION) { + matrix->drawFastHLine(0, collisionInt, matrix->getWidth(), color1); + matrix->drawFastHLine(0, matrix->getHeight() - collisionInt - 1, matrix->getWidth(), color2); + } else if(type == CollisionType::VERTICAL_COLLISION) { + matrix->drawFastVLine(collisionInt, 0, matrix->getHeight(), color1); + matrix->drawFastVLine(matrix->getWidth() - 1 - collisionInt, 0, matrix->getHeight(), color2); + } + + collisionInt++; + + if(collisionInt >= (type == CollisionType::HORIZONTAL_COLLISION ? matrix->getHeight() : matrix->getWidth())) { + collisionInt = 0; + } + + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/CollisionStrip.cpp b/src/CollisionStrip.cpp new file mode 100644 index 0000000..4f7fa29 --- /dev/null +++ b/src/CollisionStrip.cpp @@ -0,0 +1,18 @@ +#include "CollisionStrip.h" + +void CollisionStrip::execute() { + if(collisionInt == 0) { + strip->clearLEDs(); + } + + strip->setColor(collisionInt, color1); + strip->setColor(strip->getNumLEDs() - collisionInt - 1, color2); + + collisionInt++; + + if(collisionInt >= strip->getNumLEDs()) { + collisionInt = 0; + } + + strip->requestShow(); +} \ No newline at end of file diff --git a/src/ColorRandomizerMatrix.cpp b/src/ColorRandomizerMatrix.cpp new file mode 100644 index 0000000..abbdb20 --- /dev/null +++ b/src/ColorRandomizerMatrix.cpp @@ -0,0 +1,54 @@ +#include "ColorRandomizerMatrix.h" + +void ColorRandomizerMatrix::execute() { + if(fade && isFading) { + CRGB color; + + if(colorRandomizerInt == 0) { + for(uint16_t i = 0; i < type == ColorRandomizerType::HORIZONTAL_COLORRANDOMIZER ? matrix->getHeight() : matrix->getWidth(); i++) { + if(type == ColorRandomizerType::HORIZONTAL_COLORRANDOMIZER) { + color = matrix->getColor(0, i); + } else { + color = matrix->getColor(i, 0); + } + + fadingValues[i].red = (ceil((float)color.red/scale)); + fadingValues[i].green = (ceil((float)color.green/scale)); + fadingValues[i].blue = (ceil((float)color.blue/scale)); + } + + colorRandomizerInt++; + } else if(colorRandomizerInt > 0 && colorRandomizerInt < scale + 1) { + for(uint16_t i = 0; i < type == ColorRandomizerType::HORIZONTAL_COLORRANDOMIZER ? matrix->getHeight() : matrix->getWidth(); i++) { + if(type == ColorRandomizerType::HORIZONTAL_COLORRANDOMIZER) { + matrix->drawFastHLine(0, i, matrix->getWidth(), matrix->getColor(0, i) - fadingValues[i]); + } else { + matrix->drawFastVLine(i, 0, matrix->getHeight(), matrix->getColor(i, 0) - fadingValues[i]); + } + } + + colorRandomizerInt++; + } else { + colorRandomizerInt = 0; + isFading = false; + } + } else { + uint8_t rand; + + for(uint16_t i = 0; i < type == ColorRandomizerType::HORIZONTAL_COLORRANDOMIZER ? matrix->getHeight() : matrix->getWidth(); i++) { + rand = random(0, numColors); + + if(type == ColorRandomizerType::HORIZONTAL_COLORRANDOMIZER) { + matrix->drawFastHLine(0, i, matrix->getWidth(), colors[rand]); + } else { + matrix->drawFastVLine(i, 0, matrix->getHeight(), colors[rand]); + } + } + + if(fade) { + isFading = true; + } + } + + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/ColorRandomizerStrip.cpp b/src/ColorRandomizerStrip.cpp new file mode 100644 index 0000000..cb98d42 --- /dev/null +++ b/src/ColorRandomizerStrip.cpp @@ -0,0 +1,42 @@ +#include "ColorRandomizerStrip.h" + +void ColorRandomizerStrip::execute() { + if(fade && isFading) { + CRGB color; + + if(colorRandomizerInt == 0) { + for(uint16_t i = 0; i < strip->getNumLEDs(); i++) { + color = strip->getColor(i); + + fadingValues[i].red = (ceil((float)color.red/scale)); + fadingValues[i].green = (ceil((float)color.green/scale)); + fadingValues[i].blue = (ceil((float)color.blue/scale)); + } + + colorRandomizerInt++; + } else if(colorRandomizerInt > 0 && colorRandomizerInt < scale + 1) { + for(uint16_t i = 0; i < strip->getNumLEDs(); i++) { + strip->setColor(i, strip->getColor(i) - fadingValues[i]); + } + + colorRandomizerInt++; + } else { + colorRandomizerInt = 0; + isFading = false; + } + } else { + uint8_t rand; + + for(uint16_t i = 0; i < strip->getNumLEDs(); i++) { + rand = random(0, numColors); + + strip->setColor(i, colors[rand]); + } + + if(fade) { + isFading = true; + } + } + + strip->requestShow(); +} \ No newline at end of file diff --git a/src/CycleLightMatrix.cpp b/src/CycleLightMatrix.cpp new file mode 100644 index 0000000..8c7a66a --- /dev/null +++ b/src/CycleLightMatrix.cpp @@ -0,0 +1,24 @@ +#include "CycleLightMatrix.h" + +void CycleLightMatrix::execute() { + if(cycleLightInt == 0) { + matrix->clearLEDs(); + } + + if(type == CycleLightType::HORIZONTAL_CYCLELIGHT) { + matrix->drawFastHLine(0, cycleLightInt, matrix->getWidth(), + colors[cycleLightColor % numColors]); + } else if(type == CycleLightType::VERTICAL_CYCLELIGHT) { + matrix->drawFastVLine(cycleLightInt, 0, matrix->getHeight(), + colors[cycleLightColor % numColors]); + } + + cycleLightInt++; + + if(cycleLightInt >= (type == CycleLightType::HORIZONTAL_CYCLELIGHT ? matrix->getHeight() : matrix->getWidth())) { + cycleLightInt = 0; + cycleLightColor++; + } + + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/CycleLightStrip.cpp b/src/CycleLightStrip.cpp new file mode 100644 index 0000000..871c177 --- /dev/null +++ b/src/CycleLightStrip.cpp @@ -0,0 +1,18 @@ +#include "CycleLightStrip.h" + +void CycleLightStrip::execute() { + if(cycleLightInt == 0) { + strip->clearLEDs(); + } + + strip->setColor(cycleLightInt, colors[cycleLightColor % numColors]); + + cycleLightInt++; + + if(cycleLightInt >= strip->getNumLEDs()) { + cycleLightInt = 0; + cycleLightColor++; + } + + strip->requestShow(); +} \ No newline at end of file diff --git a/src/CycloneHelper.cpp b/src/CycloneHelper.cpp new file mode 100644 index 0000000..9494245 --- /dev/null +++ b/src/CycloneHelper.cpp @@ -0,0 +1,42 @@ +#include "CycloneHelper.h" + +void CycloneHelper::updateAndRedraw(LEDHAL2D* matrix) { + matrix->drawPixel(nextPoint.getX(), nextPoint.getY(), color); + + // This will create an ungodly number of stack allocations and destroys + for(uint8_t i = 0; i < numberOfTails; i++) { + if(!tailPoints[i].isOrigin()) { + matrix->drawPixel(tailPoints[i].getX(), tailPoints[i].getY(), tailColors[numberOfTails - i - 1]); + } + } + + Point2D tempPoint = nextPoint; + + // This is risky, if the logic is wrong, this is infinite + // Find the next position on the circle that actually is different + do { + currentDrawAngle += spinDirection; + + if(currentDrawAngle > 359) { + currentDrawAngle = 0; + } else if(currentDrawAngle < 0) { + currentDrawAngle = 359; + } + + tempPoint = Point2D( + originX + (uint16_t) round(radius * cosf(currentDrawAngle)), + originY + (uint16_t) round(radius * sinf(currentDrawAngle)) + ); + } while(tempPoint == nextPoint); + + //Slide all tailpoints back one + for(uint8_t i = numberOfTails - 1; i >= 1; i--) { + tailPoints[i] = tailPoints[i - 1]; + } + + // Point we just drew becomes the first tail point + tailPoints[0] = nextPoint; + + // Point we just discovered becomes the next point to draw + nextPoint = tempPoint; +} \ No newline at end of file diff --git a/src/DecodingBitStream.cpp b/src/DecodingBitStream.cpp new file mode 100644 index 0000000..4723296 --- /dev/null +++ b/src/DecodingBitStream.cpp @@ -0,0 +1,37 @@ +#include "DecodingBitStream.h" + +uint16_t DecodingBitStream::getCode() +{ + uint16_t returnCode; + + verifyEnoughBits(); + + returnCode = bitBuffer & (int)(pow(2, numberOfBitsInCode) - 1); + bitBuffer >>= numberOfBitsInCode; + bitsAvailable -= numberOfBitsInCode; + + return returnCode; +} + +void DecodingBitStream::verifyEnoughBits() +{ + while(bytesAvailable != 0 && (bitsAvailable <= 8 || bitsAvailable <= numberOfBitsInCode)) + { + bitBuffer |= stream->read() << bitsAvailable; + bitsAvailable += 8; + bytesAvailable--; + } +} + +uint16_t DecodingBitStream::getNumberOfBitsToRepresentValue(uint16_t value) +{ + uint16_t count = 0; + + while(value > 0) + { + value >>= 1; + count++; + } + + return count; +} diff --git a/src/FireworksMatrix.cpp b/src/FireworksMatrix.cpp new file mode 100644 index 0000000..9b33bc0 --- /dev/null +++ b/src/FireworksMatrix.cpp @@ -0,0 +1,75 @@ +#include "FireworksMatrix.h" + +void FireworksMatrix::initialize() { + for(uint8_t i = 0; i < maxFireworks; i++) { + fireworks[i].isActive = false; + } + + uint8_t third = maxFireworks / 6; + + for(uint8_t i = 0; i < third; i++) { + randomSeed(random(0, 100000000)); + + fireworks[i].radius = random(1, maxRadius + 1); + fireworks[i].currentRadius = 1; + fireworks[i].xPos = random(fireworks[i].radius, matrix->getWidth() - fireworks[i].radius); + fireworks[i].yPos = matrix->getHeight(); + fireworks[i].yMaxHeight = random(fireworks[i].radius, matrix->getHeight() - fireworks[i].radius); + fireworks[i].color = colors[random(0, numColors)]; + fireworks[i].isActive = true; + } + + resetTimer(); +} + +void FireworksMatrix::execute() { + matrix->clearLEDs(); + + uint8_t totalInactive = 0; + + for(uint8_t i = 0; i < maxFireworks; i++) { + if(!fireworks[i].isActive) { + totalInactive++; + } + } + + for(uint8_t i = 0; i < totalInactive; i++) { + randomSeed(random(0, 100000000)); + + if(random(0, 1000) > 500) { + for(uint8_t j = 0; j < maxFireworks; j++) { + if(!fireworks[j].isActive) { + fireworks[j].radius = random(1, maxRadius + 1); + fireworks[j].currentRadius = 1; + fireworks[j].xPos = random(fireworks[j].radius, matrix->getWidth() - fireworks[j].radius); + fireworks[j].yPos = matrix->getHeight(); + fireworks[j].yMaxHeight = random(fireworks[j].radius, matrix->getHeight() - fireworks[j].radius); + fireworks[j].color = colors[random(0, numColors)]; + fireworks[j].isActive = true; + break; + } + } + } + } + + for(uint8_t i = 0; i < maxFireworks; i++) { + if(fireworks[i].isActive) { + if(fireworks[i].yMaxHeight == fireworks[i].yPos && + fireworks[i].currentRadius == fireworks[i].radius) { + + fireworks[i].isActive = false; + } else if(fireworks[i].yMaxHeight == fireworks[i].yPos && + fireworks[i].currentRadius < fireworks[i].radius) { + + matrix->drawCircle(fireworks[i].xPos, fireworks[i].yPos, fireworks[i].currentRadius, + fireworks[i].color); + fireworks[i].currentRadius++; + } else if(fireworks[i].yPos > fireworks[i].yMaxHeight) { + matrix->drawPixel(fireworks[i].xPos, fireworks[i].yPos, fireworks[i].color); + fireworks[i].yPos--; + } + } + } + + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/FluidColorMatrix.cpp b/src/FluidColorMatrix.cpp new file mode 100644 index 0000000..ef8f70c --- /dev/null +++ b/src/FluidColorMatrix.cpp @@ -0,0 +1,59 @@ +#include "FluidColorMatrix.h" + +void FluidColorMatrix::initialize() { + uint8_t step = 0; + switch(resolution) { + case FluidColorResolution::FULL_FLUIDCOLOR: + numColors = 255; + step = 1; + break; + case FluidColorResolution::HALF_FLUIDCOLOR: + numColors = 128; + step = 2; + break; + case FluidColorResolution::QUATER_FLUIDCOLOR: + numColors = 64; + step = 4; + break; + case FluidColorResolution::EIGHTH_FLUIDCOLOR: + default: + numColors = 32; + step = 8; + break; + } + + colors = new CRGB[numColors]; + + for(uint16_t i = 0; i < numColors; i+=step) { + colors[i/step] = CRGB(CHSV(i, 255, 255)); + } +} + +void FluidColorMatrix::execute() { + switch(type) { + case FluidColorType::PIXEL_BY_PIXEL_FLUIDCOLOR: + for(int16_t x = 0; x < matrix->getWidth(); x++) { + for(int16_t y = 0; y < matrix->getHeight(); y++) { + matrix->drawPixel(x, y, + colors[((matrix->getWidth() * y + x) + colorShifter) % numColors]); + } + } + break; + case FluidColorType::HORIZONTAL_FLUIDCOLOR: + for(int16_t y = 0; y < matrix->getHeight(); y++) { + matrix->drawFastHLine(0, y, matrix->getWidth(), + colors[(y + colorShifter) % numColors]); + } + break; + case FluidColorType::VERTICAL_FLUIDCOLOR: + default: + for(int16_t x = 0; x < matrix->getWidth(); x++) { + matrix->drawFastVLine(x, 0, matrix->getHeight(), + colors[(x + colorShifter) % numColors]); + } + break; + } + + colorShifter++; + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/GIFMatrix.cpp b/src/GIFMatrix.cpp new file mode 100644 index 0000000..a2193f8 --- /dev/null +++ b/src/GIFMatrix.cpp @@ -0,0 +1,36 @@ +#include "GIFMatrix.h" + +void GIFMatrix::buildCodeTable() { + // I am not confident this is going to work, it's a lot of memory cleanup, + // memory that is dynamic, and I'm afraid it's going to cause destructive memory + // corruption + if(currentCodeTable != nullptr) { + for(uint16_t i = 0; i < sizeof(currentCodeTable)/sizeof(uint16_t*); i++) { + delete currentCodeTable[i]; + } + + delete currentCodeTable; + } + + +} + +void GIFMatrix::drawForArray(CRGB* colorTable, uint16_t* indexes, uint16_t startingXPosition, uint16_t imageWidth) { + for(uint16_t i = 0; i < sizeof(indexes)/sizeof(uint16_t); i++) { + matrix->drawPixel(currentXPosition, currentYPosition, colorTable[indexes[i]]); + + currentXPosition++; + + if(currentXPosition >= (imageWidth + startingXPosition)) { + currentXPosition = startingXPosition; + currentYPosition++; + } + } +} + +uint16_t GIFMatrix::readWord() { + uint16_t lsb = dataFile->read(); + uint16_t msb = dataFile->read(); + + return (msb << 8) | lsb; +} \ No newline at end of file diff --git a/src/GULLS_GFX.cpp b/src/GULLS_GFX.cpp new file mode 100644 index 0000000..c79ce4b --- /dev/null +++ b/src/GULLS_GFX.cpp @@ -0,0 +1,1730 @@ +/* +This is the core graphics library for all our displays, providing a common +set of graphics primitives (points, lines, circles, etc.). It needs to be +paired with a hardware-specific library for each display device we carry +(to handle the lower-level functions). + +Adafruit invests time and resources providing this open source code, please +support Adafruit & open-source hardware by purchasing products from Adafruit! + +Copyright (c) 2013 Adafruit Industries. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + */ + +#include "GULLS_GFX.h" +#include "glcdfont.c" +#ifdef __AVR__ +#include +#elif defined(ESP8266) || defined(ESP32) +#include +#endif + +// Many (but maybe not all) non-AVR board installs define macros +// for compatibility with existing PROGMEM-reading AVR code. +// Do our own checks and defines here for good measure... + +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#endif +#ifndef pgm_read_word +#define pgm_read_word(addr) (*(const unsigned short *)(addr)) +#endif +#ifndef pgm_read_dword +#define pgm_read_dword(addr) (*(const unsigned long *)(addr)) +#endif + +// Pointers are a peculiar case...typically 16-bit on AVR boards, +// 32 bits elsewhere. Try to accommodate both... + +#if !defined(__INT_MAX__) || (__INT_MAX__ > 0xFFFF) +#define pgm_read_pointer(addr) ((void *)pgm_read_dword(addr)) +#else +#define pgm_read_pointer(addr) ((void *)pgm_read_word(addr)) +#endif + +inline GFXglyph *pgm_read_glyph_ptr(const GFXfont *gfxFont, uint8_t c) { +#ifdef __AVR__ + return &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); +#else + // expression in __AVR__ section may generate "dereferencing type-punned + // pointer will break strict-aliasing rules" warning In fact, on other + // platforms (such as STM32) there is no need to do this pointer magic as + // program memory may be read in a usual way So expression may be simplified + return gfxFont->glyph + c; +#endif //__AVR__ +} + +inline uint8_t *pgm_read_bitmap_ptr(const GFXfont *gfxFont) { +#ifdef __AVR__ + return (uint8_t *)pgm_read_pointer(&gfxFont->bitmap); +#else + // expression in __AVR__ section generates "dereferencing type-punned pointer + // will break strict-aliasing rules" warning In fact, on other platforms (such + // as STM32) there is no need to do this pointer magic as program memory may + // be read in a usual way So expression may be simplified + return gfxFont->bitmap; +#endif //__AVR__ +} + +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef _swap_int16_t +#define _swap_int16_t(a, b) \ + { \ + int16_t t = a; \ + a = b; \ + b = t; \ + } +#endif + +/**************************************************************************/ +/*! + @brief Instatiate a GFX context for graphics! Can only be done by a + superclass + @param w Display width, in pixels + @param h Display height, in pixels +*/ +/**************************************************************************/ +GULLS_GFX::GULLS_GFX(int16_t w, int16_t h) : WIDTH(w), HEIGHT(h) { + _width = WIDTH; + _height = HEIGHT; + rotation = 0; + cursor_y = cursor_x = 0; + textsize_x = textsize_y = 1; + textcolor = textbgcolor = 0xFFFF; + wrap = true; + _cp437 = false; + gfxFont = NULL; +} + +/**************************************************************************/ +/*! + @brief Write a line. Bresenham's algorithm - thx wikpedia + @param x0 Start point x coordinate + @param y0 Start point y coordinate + @param x1 End point x coordinate + @param y1 End point y coordinate + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::writeLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + CRGB color) { +#if defined(ESP8266) + yield(); +#endif + int16_t steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + _swap_int16_t(x0, y0); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int16_t err = dx / 2; + int16_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0 <= x1; x0++) { + if (steep) { + writePixel(y0, x0, color); + } else { + writePixel(x0, y0, color); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +/**************************************************************************/ +/*! + @brief Start a display-writing routine, overwrite in subclasses. +*/ +/**************************************************************************/ +void GULLS_GFX::startWrite() {} + +/**************************************************************************/ +/*! + @brief Write a pixel, overwrite in subclasses if startWrite is defined! + @param x x coordinate + @param y y coordinate + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::writePixel(int16_t x, int16_t y, CRGB color) { + drawPixel(x, y, color); +} + +/**************************************************************************/ +/*! + @brief Write a perfectly vertical line, overwrite in subclasses if + startWrite is defined! + @param x Top-most x coordinate + @param y Top-most y coordinate + @param h Height in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::writeFastVLine(int16_t x, int16_t y, int16_t h, + CRGB color) { + // Overwrite in subclasses if startWrite is defined! + // Can be just writeLine(x, y, x, y+h-1, color); + // or writeFillRect(x, y, 1, h, color); + drawFastVLine(x, y, h, color); +} + +/**************************************************************************/ +/*! + @brief Write a perfectly horizontal line, overwrite in subclasses if + startWrite is defined! + @param x Left-most x coordinate + @param y Left-most y coordinate + @param w Width in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::writeFastHLine(int16_t x, int16_t y, int16_t w, + CRGB color) { + // Overwrite in subclasses if startWrite is defined! + // Example: writeLine(x, y, x+w-1, y, color); + // or writeFillRect(x, y, w, 1, color); + drawFastHLine(x, y, w, color); +} + +/**************************************************************************/ +/*! + @brief Write a rectangle completely with one color, overwrite in + subclasses if startWrite is defined! + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h, + CRGB color) { + // Overwrite in subclasses if desired! + fillRect(x, y, w, h, color); +} + +/**************************************************************************/ +/*! + @brief End a display-writing routine, overwrite in subclasses if + startWrite is defined! +*/ +/**************************************************************************/ +void GULLS_GFX::endWrite() {} + +/**************************************************************************/ +/*! + @brief Draw a perfectly vertical line (this is often optimized in a + subclass!) + @param x Top-most x coordinate + @param y Top-most y coordinate + @param h Height in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::drawFastVLine(int16_t x, int16_t y, int16_t h, + CRGB color) { + startWrite(); + writeLine(x, y, x, y + h - 1, color); + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a perfectly horizontal line (this is often optimized in a + subclass!) + @param x Left-most x coordinate + @param y Left-most y coordinate + @param w Width in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::drawFastHLine(int16_t x, int16_t y, int16_t w, + CRGB color) { + startWrite(); + writeLine(x, y, x + w - 1, y, color); + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Fill a rectangle completely with one color. Update in subclasses if + desired! + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, + CRGB color) { + startWrite(); + for (int16_t i = x; i < x + w; i++) { + writeFastVLine(i, y, h, color); + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Fill the screen completely with one color. Update in subclasses if + desired! + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::fillScreen(CRGB color) { + fillRect(0, 0, _width, _height, color); +} + +/**************************************************************************/ +/*! + @brief Draw a line + @param x0 Start point x coordinate + @param y0 Start point y coordinate + @param x1 End point x coordinate + @param y1 End point y coordinate + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + CRGB color) { + // Update in subclasses if desired! + if (x0 == x1) { + if (y0 > y1) + _swap_int16_t(y0, y1); + drawFastVLine(x0, y0, y1 - y0 + 1, color); + } else if (y0 == y1) { + if (x0 > x1) + _swap_int16_t(x0, x1); + drawFastHLine(x0, y0, x1 - x0 + 1, color); + } else { + startWrite(); + writeLine(x0, y0, x1, y1, color); + endWrite(); + } +} + +/**************************************************************************/ +/*! + @brief Draw a circle outline + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawCircle(int16_t x0, int16_t y0, int16_t r, + CRGB color) { +#if defined(ESP8266) + yield(); +#endif + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + startWrite(); + writePixel(x0, y0 + r, color); + writePixel(x0, y0 - r, color); + writePixel(x0 + r, y0, color); + writePixel(x0 - r, y0, color); + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + writePixel(x0 + x, y0 + y, color); + writePixel(x0 - x, y0 + y, color); + writePixel(x0 + x, y0 - y, color); + writePixel(x0 - x, y0 - y, color); + writePixel(x0 + y, y0 + x, color); + writePixel(x0 - y, y0 + x, color); + writePixel(x0 + y, y0 - x, color); + writePixel(x0 - y, y0 - x, color); + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Quarter-circle drawer, used to do circles and roundrects + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param cornername Mask bit #1 or bit #2 to indicate which quarters of + the circle we're doing + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawCircleHelper(int16_t x0, int16_t y0, int16_t r, + uint8_t cornername, CRGB color) { + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + if (cornername & 0x4) { + writePixel(x0 + x, y0 + y, color); + writePixel(x0 + y, y0 + x, color); + } + if (cornername & 0x2) { + writePixel(x0 + x, y0 - y, color); + writePixel(x0 + y, y0 - x, color); + } + if (cornername & 0x8) { + writePixel(x0 - y, y0 + x, color); + writePixel(x0 - x, y0 + y, color); + } + if (cornername & 0x1) { + writePixel(x0 - y, y0 - x, color); + writePixel(x0 - x, y0 - y, color); + } + } +} + +/**************************************************************************/ +/*! + @brief Draw a circle with filled color + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::fillCircle(int16_t x0, int16_t y0, int16_t r, + CRGB color) { + startWrite(); + writeFastVLine(x0, y0 - r, 2 * r + 1, color); + fillCircleHelper(x0, y0, r, 3, 0, color); + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Quarter-circle drawer with fill, used for circles and roundrects + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param corners Mask bits indicating which quarters we're doing + @param delta Offset from center-point, used for round-rects + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GULLS_GFX::fillCircleHelper(int16_t x0, int16_t y0, int16_t r, + uint8_t corners, int16_t delta, + CRGB color) { + + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + int16_t px = x; + int16_t py = y; + + delta++; // Avoid some +1's in the loop + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + // These checks avoid double-drawing certain lines, important + // for the SSD1306 library which has an INVERT drawing mode. + if (x < (y + 1)) { + if (corners & 1) + writeFastVLine(x0 + x, y0 - y, 2 * y + delta, color); + if (corners & 2) + writeFastVLine(x0 - x, y0 - y, 2 * y + delta, color); + } + if (y != py) { + if (corners & 1) + writeFastVLine(x0 + py, y0 - px, 2 * px + delta, color); + if (corners & 2) + writeFastVLine(x0 - py, y0 - px, 2 * px + delta, color); + py = y; + } + px = x; + } +} + +/**************************************************************************/ +/*! + @brief Draw a rectangle with no fill color + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawRect(int16_t x, int16_t y, int16_t w, int16_t h, + CRGB color) { + startWrite(); + writeFastHLine(x, y, w, color); + writeFastHLine(x, y + h - 1, w, color); + writeFastVLine(x, y, h, color); + writeFastVLine(x + w - 1, y, h, color); + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a rounded rectangle with no fill color + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param r Radius of corner rounding + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, + int16_t r, CRGB color) { + int16_t max_radius = ((w < h) ? w : h) / 2; // 1/2 minor axis + if (r > max_radius) + r = max_radius; + // smarter version + startWrite(); + writeFastHLine(x + r, y, w - 2 * r, color); // Top + writeFastHLine(x + r, y + h - 1, w - 2 * r, color); // Bottom + writeFastVLine(x, y + r, h - 2 * r, color); // Left + writeFastVLine(x + w - 1, y + r, h - 2 * r, color); // Right + // draw four corners + drawCircleHelper(x + r, y + r, r, 1, color); + drawCircleHelper(x + w - r - 1, y + r, r, 2, color); + drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color); + drawCircleHelper(x + r, y + h - r - 1, r, 8, color); + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a rounded rectangle with fill color + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param r Radius of corner rounding + @param color 16-bit 5-6-5 Color to draw/fill with +*/ +/**************************************************************************/ +void GULLS_GFX::fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, + int16_t r, CRGB color) { + int16_t max_radius = ((w < h) ? w : h) / 2; // 1/2 minor axis + if (r > max_radius) + r = max_radius; + // smarter version + startWrite(); + writeFillRect(x + r, y, w - 2 * r, h, color); + // draw four corners + fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color); + fillCircleHelper(x + r, y + r, r, 2, h - 2 * r - 1, color); + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a triangle with no fill color + @param x0 Vertex #0 x coordinate + @param y0 Vertex #0 y coordinate + @param x1 Vertex #1 x coordinate + @param y1 Vertex #1 y coordinate + @param x2 Vertex #2 x coordinate + @param y2 Vertex #2 y coordinate + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + int16_t x2, int16_t y2, CRGB color) { + drawLine(x0, y0, x1, y1, color); + drawLine(x1, y1, x2, y2, color); + drawLine(x2, y2, x0, y0, color); +} + +/**************************************************************************/ +/*! + @brief Draw a triangle with color-fill + @param x0 Vertex #0 x coordinate + @param y0 Vertex #0 y coordinate + @param x1 Vertex #1 x coordinate + @param y1 Vertex #1 y coordinate + @param x2 Vertex #2 x coordinate + @param y2 Vertex #2 y coordinate + @param color 16-bit 5-6-5 Color to fill/draw with +*/ +/**************************************************************************/ +void GULLS_GFX::fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + int16_t x2, int16_t y2, CRGB color) { + + int16_t a, b, y, last; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + _swap_int16_t(y0, y1); + _swap_int16_t(x0, x1); + } + if (y1 > y2) { + _swap_int16_t(y2, y1); + _swap_int16_t(x2, x1); + } + if (y0 > y1) { + _swap_int16_t(y0, y1); + _swap_int16_t(x0, x1); + } + + startWrite(); + if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if (x1 < a) + a = x1; + else if (x1 > b) + b = x1; + if (x2 < a) + a = x2; + else if (x2 > b) + b = x2; + writeFastHLine(a, y0, b - a + 1, color); + endWrite(); + return; + } + + int16_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, + dx12 = x2 - x1, dy12 = y2 - y1; + int32_t sa = 0, sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) + last = y1; // Include y1 scanline + else + last = y1 - 1; // Skip it + + for (y = y0; y <= last; y++) { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int16_t(a, b); + writeFastHLine(a, y, b - a + 1, color); + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + sa = (int32_t)dx12 * (y - y1); + sb = (int32_t)dx02 * (y - y0); + for (; y <= y2; y++) { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + /* longhand: + a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int16_t(a, b); + writeFastHLine(a, y, b - a + 1, color); + } + endWrite(); +} + +// BITMAP / XBITMAP / GRAYSCALE / RGB BITMAP FUNCTIONS --------------------- + +/**************************************************************************/ +/*! + @brief Draw a PROGMEM-resident 1-bit image at the specified (x,y) + position, using the specified foreground color (unset bits are transparent). + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with monochrome bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], + int16_t w, int16_t h, CRGB color) { + + int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte + uint8_t b = 0; + + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = pgm_read_byte(&bitmap[j * byteWidth + i / 8]); + if (b & 0x80) + writePixel(x + i, y, color); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a PROGMEM-resident 1-bit image at the specified (x,y) + position, using the specified foreground (for set bits) and background (unset + bits) colors. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with monochrome bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels + @param color 16-bit 5-6-5 Color to draw pixels with + @param bg 16-bit 5-6-5 Color to draw background with +*/ +/**************************************************************************/ +void GULLS_GFX::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], + int16_t w, int16_t h, CRGB color, + CRGB bg) { + + int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte + uint8_t b = 0; + + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = pgm_read_byte(&bitmap[j * byteWidth + i / 8]); + writePixel(x + i, y, (b & 0x80) ? color : bg); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a RAM-resident 1-bit image at the specified (x,y) position, + using the specified foreground color (unset bits are transparent). + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with monochrome bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GULLS_GFX::drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, + int16_t h, CRGB color) { + + int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte + uint8_t b = 0; + + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = bitmap[j * byteWidth + i / 8]; + if (b & 0x80) + writePixel(x + i, y, color); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a RAM-resident 1-bit image at the specified (x,y) position, + using the specified foreground (for set bits) and background (unset bits) + colors. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with monochrome bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels + @param color 16-bit 5-6-5 Color to draw pixels with + @param bg 16-bit 5-6-5 Color to draw background with +*/ +/**************************************************************************/ +void GULLS_GFX::drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, + int16_t h, CRGB color, CRGB bg) { + + int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte + uint8_t b = 0; + + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = bitmap[j * byteWidth + i / 8]; + writePixel(x + i, y, (b & 0x80) ? color : bg); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw PROGMEM-resident XBitMap Files (*.xbm), exported from GIMP. + Usage: Export from GIMP to *.xbm, rename *.xbm to *.c and open in editor. + C Array can be directly used with this function. + There is no RAM-resident version of this function; if generating bitmaps + in RAM, use the format defined by drawBitmap() and call that instead. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with monochrome bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels + @param color 16-bit 5-6-5 Color to draw pixels with +*/ +/**************************************************************************/ +void GULLS_GFX::drawXBitmap(int16_t x, int16_t y, const uint8_t bitmap[], + int16_t w, int16_t h, CRGB color) { + + int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte + uint8_t b = 0; + + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b >>= 1; + else + b = pgm_read_byte(&bitmap[j * byteWidth + i / 8]); + // Nearly identical to drawBitmap(), only the bit order + // is reversed here (left-to-right = LSB to MSB): + if (b & 0x01) + writePixel(x + i, y, color); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a PROGMEM-resident 8-bit image (grayscale) at the specified + (x,y) pos. Specifically for 8-bit display devices such as IS31FL3731; no + color reduction/expansion is performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with grayscale bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawGrayscaleBitmap(int16_t x, int16_t y, + const uint8_t bitmap[], int16_t w, + int16_t h) { + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + writePixel(x + i, y, (uint8_t)pgm_read_byte(&bitmap[j * w + i])); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a RAM-resident 8-bit image (grayscale) at the specified (x,y) + pos. Specifically for 8-bit display devices such as IS31FL3731; no color + reduction/expansion is performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with grayscale bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawGrayscaleBitmap(int16_t x, int16_t y, uint8_t *bitmap, + int16_t w, int16_t h) { + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + writePixel(x + i, y, bitmap[j * w + i]); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a PROGMEM-resident 8-bit image (grayscale) with a 1-bit mask + (set bits = opaque, unset bits = clear) at the specified (x,y) position. + BOTH buffers (grayscale and mask) must be PROGMEM-resident. + Specifically for 8-bit display devices such as IS31FL3731; no color + reduction/expansion is performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with grayscale bitmap + @param mask byte array with mask bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawGrayscaleBitmap(int16_t x, int16_t y, + const uint8_t bitmap[], + const uint8_t mask[], int16_t w, + int16_t h) { + int16_t bw = (w + 7) / 8; // Bitmask scanline pad = whole byte + uint8_t b = 0; + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = pgm_read_byte(&mask[j * bw + i / 8]); + if (b & 0x80) { + writePixel(x + i, y, (uint8_t)pgm_read_byte(&bitmap[j * w + i])); + } + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a RAM-resident 8-bit image (grayscale) with a 1-bit mask + (set bits = opaque, unset bits = clear) at the specified (x,y) position. + BOTH buffers (grayscale and mask) must be RAM-residentt, no mix-and-match + Specifically for 8-bit display devices such as IS31FL3731; no color + reduction/expansion is performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with grayscale bitmap + @param mask byte array with mask bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawGrayscaleBitmap(int16_t x, int16_t y, uint8_t *bitmap, + uint8_t *mask, int16_t w, int16_t h) { + int16_t bw = (w + 7) / 8; // Bitmask scanline pad = whole byte + uint8_t b = 0; + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = mask[j * bw + i / 8]; + if (b & 0x80) { + writePixel(x + i, y, bitmap[j * w + i]); + } + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a PROGMEM-resident 16-bit image (RGB 5/6/5) at the specified + (x,y) position. For 16-bit display devices; no color reduction performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with 16-bit color bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawRGBBitmap(int16_t x, int16_t y, const CRGB bitmap[], + int16_t w, int16_t h) { + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + writePixel(x + i, y, pgm_read_word(&bitmap[j * w + i])); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a RAM-resident 16-bit image (RGB 5/6/5) at the specified (x,y) + position. For 16-bit display devices; no color reduction performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with 16-bit color bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawRGBBitmap(int16_t x, int16_t y, CRGB *bitmap, + int16_t w, int16_t h) { + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + writePixel(x + i, y, bitmap[j * w + i]); + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a PROGMEM-resident 16-bit image (RGB 5/6/5) with a 1-bit mask + (set bits = opaque, unset bits = clear) at the specified (x,y) position. BOTH + buffers (color and mask) must be PROGMEM-resident. For 16-bit display + devices; no color reduction performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with 16-bit color bitmap + @param mask byte array with monochrome mask bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawRGBBitmap(int16_t x, int16_t y, const CRGB bitmap[], + const uint8_t mask[], int16_t w, int16_t h) { + int16_t bw = (w + 7) / 8; // Bitmask scanline pad = whole byte + uint8_t b = 0; + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = pgm_read_byte(&mask[j * bw + i / 8]); + if (b & 0x80) { + writePixel(x + i, y, pgm_read_word(&bitmap[j * w + i])); + } + } + } + endWrite(); +} + +/**************************************************************************/ +/*! + @brief Draw a RAM-resident 16-bit image (RGB 5/6/5) with a 1-bit mask (set + bits = opaque, unset bits = clear) at the specified (x,y) position. BOTH + buffers (color and mask) must be RAM-resident. For 16-bit display devices; no + color reduction performed. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with 16-bit color bitmap + @param mask byte array with monochrome mask bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels +*/ +/**************************************************************************/ +void GULLS_GFX::drawRGBBitmap(int16_t x, int16_t y, CRGB *bitmap, + uint8_t *mask, int16_t w, int16_t h) { + int16_t bw = (w + 7) / 8; // Bitmask scanline pad = whole byte + uint8_t b = 0; + startWrite(); + for (int16_t j = 0; j < h; j++, y++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + b <<= 1; + else + b = mask[j * bw + i / 8]; + if (b & 0x80) { + writePixel(x + i, y, bitmap[j * w + i]); + } + } + } + endWrite(); +} + +// TEXT- AND CHARACTER-HANDLING FUNCTIONS ---------------------------------- + +// Draw a character +/**************************************************************************/ +/*! + @brief Draw a single character + @param x Bottom left corner x coordinate + @param y Bottom left corner y coordinate + @param c The 8-bit font-indexed character (likely ascii) + @param color 16-bit 5-6-5 Color to draw chraracter with + @param bg 16-bit 5-6-5 Color to fill background with (if same as color, + no background) + @param size Font magnification level, 1 is 'original' size +*/ +/**************************************************************************/ +void GULLS_GFX::drawChar(int16_t x, int16_t y, unsigned char c, + CRGB color, CRGB bg, uint8_t size) { + drawChar(x, y, c, color, bg, size, size); +} + +// Draw a character +/**************************************************************************/ +/*! + @brief Draw a single character + @param x Bottom left corner x coordinate + @param y Bottom left corner y coordinate + @param c The 8-bit font-indexed character (likely ascii) + @param color 16-bit 5-6-5 Color to draw chraracter with + @param bg 16-bit 5-6-5 Color to fill background with (if same as color, + no background) + @param size_x Font magnification level in X-axis, 1 is 'original' size + @param size_y Font magnification level in Y-axis, 1 is 'original' size +*/ +/**************************************************************************/ +void GULLS_GFX::drawChar(int16_t x, int16_t y, unsigned char c, + CRGB color, CRGB bg, uint8_t size_x, + uint8_t size_y) { + + if (!gfxFont) { // 'Classic' built-in font + + if ((x >= _width) || // Clip right + (y >= _height) || // Clip bottom + ((x + 6 * size_x - 1) < 0) || // Clip left + ((y + 8 * size_y - 1) < 0)) // Clip top + return; + + if (!_cp437 && (c >= 176)) + c++; // Handle 'classic' charset behavior + + startWrite(); + for (int8_t i = 0; i < 5; i++) { // Char bitmap = 5 columns + uint8_t line = pgm_read_byte(&font[c * 5 + i]); + for (int8_t j = 0; j < 8; j++, line >>= 1) { + if (line & 1) { + if (size_x == 1 && size_y == 1) + writePixel(x + i, y + j, color); + else + writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y, + color); + } else if (bg != color) { + if (size_x == 1 && size_y == 1) + writePixel(x + i, y + j, bg); + else + writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y, bg); + } + } + } + if (bg != color) { // If opaque, draw vertical line for last column + if (size_x == 1 && size_y == 1) + writeFastVLine(x + 5, y, 8, bg); + else + writeFillRect(x + 5 * size_x, y, size_x, 8 * size_y, bg); + } + endWrite(); + + } else { // Custom font + + // Character is assumed previously filtered by write() to eliminate + // newlines, returns, non-printable characters, etc. Calling + // drawChar() directly with 'bad' characters of font may cause mayhem! + + c -= (uint8_t)pgm_read_byte(&gfxFont->first); + GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c); + uint8_t *bitmap = pgm_read_bitmap_ptr(gfxFont); + + uint16_t bo = pgm_read_word(&glyph->bitmapOffset); + uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height); + int8_t xo = pgm_read_byte(&glyph->xOffset), + yo = pgm_read_byte(&glyph->yOffset); + uint8_t xx, yy, bits = 0, bit = 0; + int16_t xo16 = 0, yo16 = 0; + + if (size_x > 1 || size_y > 1) { + xo16 = xo; + yo16 = yo; + } + + // Todo: Add character clipping here + + // NOTE: THERE IS NO 'BACKGROUND' COLOR OPTION ON CUSTOM FONTS. + // THIS IS ON PURPOSE AND BY DESIGN. The background color feature + // has typically been used with the 'classic' font to overwrite old + // screen contents with new data. This ONLY works because the + // characters are a uniform size; it's not a sensible thing to do with + // proportionally-spaced fonts with glyphs of varying sizes (and that + // may overlap). To replace previously-drawn text when using a custom + // font, use the getTextBounds() function to determine the smallest + // rectangle encompassing a string, erase the area with fillRect(), + // then draw new text. This WILL infortunately 'blink' the text, but + // is unavoidable. Drawing 'background' pixels will NOT fix this, + // only creates a new set of problems. Have an idea to work around + // this (a canvas object type for MCUs that can afford the RAM and + // displays supporting setAddrWindow() and pushColors()), but haven't + // implemented this yet. + + startWrite(); + for (yy = 0; yy < h; yy++) { + for (xx = 0; xx < w; xx++) { + if (!(bit++ & 7)) { + bits = pgm_read_byte(&bitmap[bo++]); + } + if (bits & 0x80) { + if (size_x == 1 && size_y == 1) { + writePixel(x + xo + xx, y + yo + yy, color); + } else { + writeFillRect(x + (xo16 + xx) * size_x, y + (yo16 + yy) * size_y, + size_x, size_y, color); + } + } + bits <<= 1; + } + } + endWrite(); + + } // End classic vs custom font +} +/**************************************************************************/ +/*! + @brief Print one byte/character of data, used to support print() + @param c The 8-bit ascii character to write +*/ +/**************************************************************************/ +size_t GULLS_GFX::write(uint8_t c) { + if (!gfxFont) { // 'Classic' built-in font + + if (c == '\n') { // Newline? + cursor_x = 0; // Reset x to zero, + cursor_y += textsize_y * 8; // advance y one line + } else if (c != '\r') { // Ignore carriage returns + if (wrap && ((cursor_x + textsize_x * 6) > _width)) { // Off right? + cursor_x = 0; // Reset x to zero, + cursor_y += textsize_y * 8; // advance y one line + } + drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize_x, + textsize_y); + cursor_x += textsize_x * 6; // Advance x one char + } + + } else { // Custom font + + if (c == '\n') { + cursor_x = 0; + cursor_y += + (int16_t)textsize_y * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } else if (c != '\r') { + uint8_t first = pgm_read_byte(&gfxFont->first); + if ((c >= first) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last))) { + GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c - first); + uint8_t w = pgm_read_byte(&glyph->width), + h = pgm_read_byte(&glyph->height); + if ((w > 0) && (h > 0)) { // Is there an associated bitmap? + int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); // sic + if (wrap && ((cursor_x + textsize_x * (xo + w)) > _width)) { + cursor_x = 0; + cursor_y += (int16_t)textsize_y * + (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } + drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize_x, + textsize_y); + } + cursor_x += + (uint8_t)pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize_x; + } + } + } + return 1; +} + +/**************************************************************************/ +/*! + @brief Set text 'magnification' size. Each increase in s makes 1 pixel + that much bigger. + @param s Desired text size. 1 is default 6x8, 2 is 12x16, 3 is 18x24, etc +*/ +/**************************************************************************/ +void GULLS_GFX::setTextSize(uint8_t s) { setTextSize(s, s); } + +/**************************************************************************/ +/*! + @brief Set text 'magnification' size. Each increase in s makes 1 pixel + that much bigger. + @param s_x Desired text width magnification level in X-axis. 1 is default + @param s_y Desired text width magnification level in Y-axis. 1 is default +*/ +/**************************************************************************/ +void GULLS_GFX::setTextSize(uint8_t s_x, uint8_t s_y) { + textsize_x = (s_x > 0) ? s_x : 1; + textsize_y = (s_y > 0) ? s_y : 1; +} + +/**************************************************************************/ +/*! + @brief Set rotation setting for display + @param x 0 thru 3 corresponding to 4 cardinal rotations +*/ +/**************************************************************************/ +void GULLS_GFX::setRotation(uint8_t x) { + rotation = (x & 3); + switch (rotation) { + case 0: + case 2: + _width = WIDTH; + _height = HEIGHT; + break; + case 1: + case 3: + _width = HEIGHT; + _height = WIDTH; + break; + } +} + +/**************************************************************************/ +/*! + @brief Set the font to display when print()ing, either custom or default + @param f The GFXfont object, if NULL use built in 6x8 font +*/ +/**************************************************************************/ +void GULLS_GFX::setFont(const GFXfont *f) { + if (f) { // Font struct pointer passed in? + if (!gfxFont) { // And no current font struct? + // Switching from classic to new font behavior. + // Move cursor pos down 6 pixels so it's on baseline. + cursor_y += 6; + } + } else if (gfxFont) { // NULL passed. Current font struct defined? + // Switching from new to classic font behavior. + // Move cursor pos up 6 pixels so it's at top-left of char. + cursor_y -= 6; + } + gfxFont = (GFXfont *)f; +} + +/**************************************************************************/ +/*! + @brief Helper to determine size of a character with current font/size. + Broke this out as it's used by both the PROGMEM- and RAM-resident + getTextBounds() functions. + @param c The ASCII character in question + @param x Pointer to x location of character. Value is modified by + this function to advance to next character. + @param y Pointer to y location of character. Value is modified by + this function to advance to next character. + @param minx Pointer to minimum X coordinate, passed in to AND returned + by this function -- this is used to incrementally build a + bounding rectangle for a string. + @param miny Pointer to minimum Y coord, passed in AND returned. + @param maxx Pointer to maximum X coord, passed in AND returned. + @param maxy Pointer to maximum Y coord, passed in AND returned. +*/ +/**************************************************************************/ +void GULLS_GFX::charBounds(unsigned char c, int16_t *x, int16_t *y, + int16_t *minx, int16_t *miny, int16_t *maxx, + int16_t *maxy) { + + if (gfxFont) { + + if (c == '\n') { // Newline? + *x = 0; // Reset x to zero, advance y by one line + *y += textsize_y * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } else if (c != '\r') { // Not a carriage return; is normal char + uint8_t first = pgm_read_byte(&gfxFont->first), + last = pgm_read_byte(&gfxFont->last); + if ((c >= first) && (c <= last)) { // Char present in this font? + GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c - first); + uint8_t gw = pgm_read_byte(&glyph->width), + gh = pgm_read_byte(&glyph->height), + xa = pgm_read_byte(&glyph->xAdvance); + int8_t xo = pgm_read_byte(&glyph->xOffset), + yo = pgm_read_byte(&glyph->yOffset); + if (wrap && ((*x + (((int16_t)xo + gw) * textsize_x)) > _width)) { + *x = 0; // Reset x to zero, advance y by one line + *y += textsize_y * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } + int16_t tsx = (int16_t)textsize_x, tsy = (int16_t)textsize_y, + x1 = *x + xo * tsx, y1 = *y + yo * tsy, x2 = x1 + gw * tsx - 1, + y2 = y1 + gh * tsy - 1; + if (x1 < *minx) + *minx = x1; + if (y1 < *miny) + *miny = y1; + if (x2 > *maxx) + *maxx = x2; + if (y2 > *maxy) + *maxy = y2; + *x += xa * tsx; + } + } + + } else { // Default font + + if (c == '\n') { // Newline? + *x = 0; // Reset x to zero, + *y += textsize_y * 8; // advance y one line + // min/max x/y unchaged -- that waits for next 'normal' character + } else if (c != '\r') { // Normal char; ignore carriage returns + if (wrap && ((*x + textsize_x * 6) > _width)) { // Off right? + *x = 0; // Reset x to zero, + *y += textsize_y * 8; // advance y one line + } + int x2 = *x + textsize_x * 6 - 1, // Lower-right pixel of char + y2 = *y + textsize_y * 8 - 1; + if (x2 > *maxx) + *maxx = x2; // Track max x, y + if (y2 > *maxy) + *maxy = y2; + if (*x < *minx) + *minx = *x; // Track min x, y + if (*y < *miny) + *miny = *y; + *x += textsize_x * 6; // Advance x one char + } + } +} + +/**************************************************************************/ +/*! + @brief Helper to determine size of a string with current font/size. + Pass string and a cursor position, returns UL corner and W,H. + @param str The ASCII string to measure + @param x The current cursor X + @param y The current cursor Y + @param x1 The boundary X coordinate, returned by function + @param y1 The boundary Y coordinate, returned by function + @param w The boundary width, returned by function + @param h The boundary height, returned by function +*/ +/**************************************************************************/ +void GULLS_GFX::getTextBounds(const char *str, int16_t x, int16_t y, + int16_t *x1, int16_t *y1, uint16_t *w, + uint16_t *h) { + + uint8_t c; // Current character + int16_t minx = 0x7FFF, miny = 0x7FFF, maxx = -1, maxy = -1; // Bound rect + // Bound rect is intentionally initialized inverted, so 1st char sets it + + *x1 = x; // Initial position is value passed in + *y1 = y; + *w = *h = 0; // Initial size is zero + + while ((c = *str++)) { + // charBounds() modifies x/y to advance for each character, + // and min/max x/y are updated to incrementally build bounding rect. + charBounds(c, &x, &y, &minx, &miny, &maxx, &maxy); + } + + if (maxx >= minx) { // If legit string bounds were found... + *x1 = minx; // Update x1 to least X coord, + *w = maxx - minx + 1; // And w to bound rect width + } + if (maxy >= miny) { // Same for height + *y1 = miny; + *h = maxy - miny + 1; + } +} + +/**************************************************************************/ +/*! + @brief Helper to determine size of a string with current font/size. Pass + string and a cursor position, returns UL corner and W,H. + @param str The ascii string to measure (as an arduino String() class) + @param x The current cursor X + @param y The current cursor Y + @param x1 The boundary X coordinate, set by function + @param y1 The boundary Y coordinate, set by function + @param w The boundary width, set by function + @param h The boundary height, set by function +*/ +/**************************************************************************/ +void GULLS_GFX::getTextBounds(const String &str, int16_t x, int16_t y, + int16_t *x1, int16_t *y1, uint16_t *w, + uint16_t *h) { + if (str.length() != 0) { + getTextBounds(const_cast(str.c_str()), x, y, x1, y1, w, h); + } +} + +/**************************************************************************/ +/*! + @brief Helper to determine size of a PROGMEM string with current + font/size. Pass string and a cursor position, returns UL corner and W,H. + @param str The flash-memory ascii string to measure + @param x The current cursor X + @param y The current cursor Y + @param x1 The boundary X coordinate, set by function + @param y1 The boundary Y coordinate, set by function + @param w The boundary width, set by function + @param h The boundary height, set by function +*/ +/**************************************************************************/ +void GULLS_GFX::getTextBounds(const __FlashStringHelper *str, int16_t x, + int16_t y, int16_t *x1, int16_t *y1, + uint16_t *w, uint16_t *h) { + uint8_t *s = (uint8_t *)str, c; + + *x1 = x; + *y1 = y; + *w = *h = 0; + + int16_t minx = _width, miny = _height, maxx = -1, maxy = -1; + + while ((c = pgm_read_byte(s++))) + charBounds(c, &x, &y, &minx, &miny, &maxx, &maxy); + + if (maxx >= minx) { + *x1 = minx; + *w = maxx - minx + 1; + } + if (maxy >= miny) { + *y1 = miny; + *h = maxy - miny + 1; + } +} + +/**************************************************************************/ +/*! + @brief Invert the display (ideally using built-in hardware command) + @param i True if you want to invert, false to make 'normal' +*/ +/**************************************************************************/ +void GULLS_GFX::invertDisplay(bool i) { + // Do nothing, must be subclassed if supported by hardware + (void)i; // disable -Wunused-parameter warning +} + +/***************************************************************************/ + +/**************************************************************************/ +/*! + @brief Create a simple drawn button UI element +*/ +/**************************************************************************/ +GULLS_GFX_Button::GULLS_GFX_Button(void) { _gfx = 0; } + +/**************************************************************************/ +/*! + @brief Initialize button with our desired color/size/settings + @param gfx Pointer to our display so we can draw to it! + @param x The X coordinate of the center of the button + @param y The Y coordinate of the center of the button + @param w Width of the buttton + @param h Height of the buttton + @param outline Color of the outline (16-bit 5-6-5 standard) + @param fill Color of the button fill (16-bit 5-6-5 standard) + @param textcolor Color of the button label (16-bit 5-6-5 standard) + @param label Ascii string of the text inside the button + @param textsize The font magnification of the label text +*/ +/**************************************************************************/ +// Classic initButton() function: pass center & size +void GULLS_GFX_Button::initButton(GULLS_GFX *gfx, int16_t x, int16_t y, + uint16_t w, uint16_t h, CRGB outline, + CRGB fill, CRGB textcolor, + char *label, uint8_t textsize) { + // Tweak arguments and pass to the newer initButtonUL() function... + initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, textcolor, + label, textsize); +} + +/**************************************************************************/ +/*! + @brief Initialize button with our desired color/size/settings + @param gfx Pointer to our display so we can draw to it! + @param x The X coordinate of the center of the button + @param y The Y coordinate of the center of the button + @param w Width of the buttton + @param h Height of the buttton + @param outline Color of the outline (16-bit 5-6-5 standard) + @param fill Color of the button fill (16-bit 5-6-5 standard) + @param textcolor Color of the button label (16-bit 5-6-5 standard) + @param label Ascii string of the text inside the button + @param textsize_x The font magnification in X-axis of the label text + @param textsize_y The font magnification in Y-axis of the label text +*/ +/**************************************************************************/ +// Classic initButton() function: pass center & size +void GULLS_GFX_Button::initButton(GULLS_GFX *gfx, int16_t x, int16_t y, + uint16_t w, uint16_t h, CRGB outline, + CRGB fill, CRGB textcolor, + char *label, uint8_t textsize_x, + uint8_t textsize_y) { + // Tweak arguments and pass to the newer initButtonUL() function... + initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, textcolor, + label, textsize_x, textsize_y); +} + +/**************************************************************************/ +/*! + @brief Initialize button with our desired color/size/settings, with + upper-left coordinates + @param gfx Pointer to our display so we can draw to it! + @param x1 The X coordinate of the Upper-Left corner of the button + @param y1 The Y coordinate of the Upper-Left corner of the button + @param w Width of the buttton + @param h Height of the buttton + @param outline Color of the outline (16-bit 5-6-5 standard) + @param fill Color of the button fill (16-bit 5-6-5 standard) + @param textcolor Color of the button label (16-bit 5-6-5 standard) + @param label Ascii string of the text inside the button + @param textsize The font magnification of the label text +*/ +/**************************************************************************/ +void GULLS_GFX_Button::initButtonUL(GULLS_GFX *gfx, int16_t x1, + int16_t y1, uint16_t w, uint16_t h, + CRGB outline, CRGB fill, + CRGB textcolor, char *label, + uint8_t textsize) { + initButtonUL(gfx, x1, y1, w, h, outline, fill, textcolor, label, textsize, + textsize); +} + +/**************************************************************************/ +/*! + @brief Initialize button with our desired color/size/settings, with + upper-left coordinates + @param gfx Pointer to our display so we can draw to it! + @param x1 The X coordinate of the Upper-Left corner of the button + @param y1 The Y coordinate of the Upper-Left corner of the button + @param w Width of the buttton + @param h Height of the buttton + @param outline Color of the outline (16-bit 5-6-5 standard) + @param fill Color of the button fill (16-bit 5-6-5 standard) + @param textcolor Color of the button label (16-bit 5-6-5 standard) + @param label Ascii string of the text inside the button + @param textsize_x The font magnification in X-axis of the label text + @param textsize_y The font magnification in Y-axis of the label text +*/ +/**************************************************************************/ +void GULLS_GFX_Button::initButtonUL(GULLS_GFX *gfx, int16_t x1, + int16_t y1, uint16_t w, uint16_t h, + CRGB outline, CRGB fill, + CRGB textcolor, char *label, + uint8_t textsize_x, uint8_t textsize_y) { + _x1 = x1; + _y1 = y1; + _w = w; + _h = h; + _outlinecolor = outline; + _fillcolor = fill; + _textcolor = textcolor; + _textsize_x = textsize_x; + _textsize_y = textsize_y; + _gfx = gfx; + strncpy(_label, label, 9); + _label[9] = 0; // strncpy does not place a null at the end. + // When 'label' is >9 characters, _label is not terminated. +} + +/**************************************************************************/ +/*! + @brief Draw the button on the screen + @param inverted Whether to draw with fill/text swapped to indicate + 'pressed' +*/ +/**************************************************************************/ +void GULLS_GFX_Button::drawButton(bool inverted) { + CRGB fill, outline, text; + + if (!inverted) { + fill = _fillcolor; + outline = _outlinecolor; + text = _textcolor; + } else { + fill = _textcolor; + outline = _outlinecolor; + text = _fillcolor; + } + + uint8_t r = min(_w, _h) / 4; // Corner radius + _gfx->fillRoundRect(_x1, _y1, _w, _h, r, fill); + _gfx->drawRoundRect(_x1, _y1, _w, _h, r, outline); + + _gfx->setCursor(_x1 + (_w / 2) - (strlen(_label) * 3 * _textsize_x), + _y1 + (_h / 2) - (4 * _textsize_y)); + _gfx->setTextColor(text); + _gfx->setTextSize(_textsize_x, _textsize_y); + _gfx->print(_label); +} + +/**************************************************************************/ +/*! + @brief Helper to let us know if a coordinate is within the bounds of the + button + @param x The X coordinate to check + @param y The Y coordinate to check + @returns True if within button graphics outline +*/ +/**************************************************************************/ +bool GULLS_GFX_Button::contains(int16_t x, int16_t y) { + return ((x >= _x1) && (x < (int16_t)(_x1 + _w)) && (y >= _y1) && + (y < (int16_t)(_y1 + _h))); +} + +/**************************************************************************/ +/*! + @brief Query whether the button was pressed since we last checked state + @returns True if was not-pressed before, now is. +*/ +/**************************************************************************/ +bool GULLS_GFX_Button::justPressed() { return (currstate && !laststate); } + +/**************************************************************************/ +/*! + @brief Query whether the button was released since we last checked state + @returns True if was pressed before, now is not. +*/ +/**************************************************************************/ +bool GULLS_GFX_Button::justReleased() { return (!currstate && laststate); } + +// ------------------------------------------------------------------------- + diff --git a/src/PlasmaMatrix.cpp b/src/PlasmaMatrix.cpp new file mode 100644 index 0000000..989e527 --- /dev/null +++ b/src/PlasmaMatrix.cpp @@ -0,0 +1,13 @@ +#include "PlasmaMatrix.h" + +void PlasmaMatrix::execute() { + for(uint16_t x = 0; x < matrix->getWidth(); x++) { + for(uint16_t y = 0; y < matrix->getHeight(); y++) { + uint8_t color = (plasma[x][y] + paletteShift) % 256; + + matrix->drawPixel(x, y, palette[color]); + } + } + + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/Point2D.h b/src/Point2D.h new file mode 100644 index 0000000..e986e75 --- /dev/null +++ b/src/Point2D.h @@ -0,0 +1,33 @@ +#ifndef POINT2D_H +#define POINT2D_H + +#include "Arduino.h" + +class Point2D { + public: + Point2D() : x(0), y(0) {} + Point2D(uint16_t _x, uint16_t _y) : x(_x), y(_y) {} + + virtual ~Point2D() {} + + void setX(uint16_t _x) { x = _x; } + void setY(uint16_t _y) { y = _y; } + uint16_t getX() { return x; } + uint16_t getY() { return y; } + bool isOrigin() { return x == 0 && y == 0; } + + bool operator==(const Point2D& other) { + return this->x == other.x && this->y == other.y; + } + + bool operator!=(const Point2D other) { + return this->x != other.x || this->y != other.y; + } + + private: + uint16_t + x, + y; +}; + +#endif \ No newline at end of file diff --git a/src/RicochetHelper.cpp b/src/RicochetHelper.cpp new file mode 100644 index 0000000..be0da3e --- /dev/null +++ b/src/RicochetHelper.cpp @@ -0,0 +1,14 @@ +#include "RicochetHelper.h" + +void RicochetHelper::updatePositions() { + currentX += xDir; + currentY += yDir; + + if(currentX >= width || currentX < 0) { + xDir *= -1; + } + + if(currentY >= height || currentY < 0) { + yDir *= -1; + } +} \ No newline at end of file diff --git a/src/RicochetMatrix.cpp b/src/RicochetMatrix.cpp new file mode 100644 index 0000000..c8a8297 --- /dev/null +++ b/src/RicochetMatrix.cpp @@ -0,0 +1,26 @@ +#include "RicochetMatrix.h" + +void RicochetMatrix::initialize() { + for(uint16_t i = 0; i < numBalls; i++) { + if(balls[i] != NULL) { + delete balls[i]; + } + + randomSeed(random(0, 10000000)); + balls[i] = new RicochetHelper(random(0, matrix->getWidth()), random(0, matrix->getHeight()), + matrix->getWidth(), matrix->getHeight(), colors[random(0, numColors)]); + } +} + +void RicochetMatrix::execute() { + matrix->clearLEDs(); + + for(uint16_t i = 0; i < numBalls; i++) { + balls[i]->updatePositions(); + + matrix->drawPixel(balls[i]->getCurrentXPos(), balls[i]->getCurrentYPos(), + balls[i]->getCurrentColor()); + } + + matrix->requestShow(); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..00c54e8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,59 @@ +#include + +#include "MatrixHardware_Teensy3_ShieldV1toV3.h" +#include "SmartMatrix.h" + +#include "FastLED.h" +#include "LEDHAL.h" +#include "LEDHAL2D.h" +#include "CLEDControllerPhysicalStrip.h" +#include "CLEDControllerPhysicalMatrix.h" +#include "SmartMatrixPhysicalMatrix.h" + +#define NUMLEDS 24 + +#define COLOR_DEPTH 24 // known working: 24, 48 - If the sketch uses type `rgb24` directly, COLOR_DEPTH must be 24 + +const uint8_t kMatrixWidth = 96; // known working: 32, 64, 96, 128 +const uint8_t kMatrixHeight = 64; // known working: 16, 32, 48, 64 +const uint8_t kRefreshDepth = 36; // known working: 24, 36, 48 +const uint8_t kDmaBufferRows = 4; // known working: 2-4, use 2 to save memory, more to keep from dropping frames and automatically lowering refresh rate +const uint8_t kPanelType = SMARTMATRIX_HUB75_32ROW_MOD16SCAN; // use SMARTMATRIX_HUB75_16ROW_MOD8SCAN for common 16x32 panels +const uint8_t kMatrixOptions = SMARTMATRIX_OPTIONS_BOTTOM_TO_TOP_STACKING; // see http://docs.pixelmatix.com/SmartMatrix for options +const uint8_t kBackgroundLayerOptions = (SM_BACKGROUND_OPTIONS_NONE); +const uint8_t kScrollingLayerOptions = (SM_SCROLLING_OPTIONS_NONE); + +SMARTMATRIX_ALLOCATE_BUFFERS(matrix, kMatrixWidth, kMatrixHeight, kRefreshDepth, kDmaBufferRows, kPanelType, kMatrixOptions); +SMARTMATRIX_ALLOCATE_BACKGROUND_LAYER(backgroundLayer, kMatrixWidth, kMatrixHeight, COLOR_DEPTH, kBackgroundLayerOptions); + + +CRGB leds[NUMLEDS]; +CRGB leds2[NUMLEDS]; +CRGB leds3[NUMLEDS]; + +CRGB someColor(255, 255, 255); + +LEDHAL* hal; +LEDHAL2D* hal2D; +LEDHAL2D* smHAL; + +rgb24 canRGB24Translate(rgb24 color) { + color.red = 0; + + return color; +} + +void setup() { + CLEDController* controller = &FastLED.addLeds(leds, NUMLEDS); + CLEDController* controller2d = &FastLED.addLeds(leds2, NUMLEDS); + + hal = new CLEDControllerPhysicalStrip(controller, "Test Strip"); + hal2D = new CLEDControllerPhysicalMatrix(controller2d, "Test Matrix", ArrangementType::COLUMNSERPENTINE, 6, 4); + smHAL = new SmartMatrixPhysicalMatrix(&backgroundLayer, "Test SM Matrix", kMatrixWidth, kMatrixHeight); + + canRGB24Translate(someColor); +} + +void loop() { + // put your main code here, to run repeatedly: +} diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html