diff --git a/examples/NeoPixelBitmap/NeoPixelBitmap.ino b/examples/NeoPixelBitmap/NeoPixelBitmap.ino new file mode 100644 index 00000000..39c2f258 --- /dev/null +++ b/examples/NeoPixelBitmap/NeoPixelBitmap.ino @@ -0,0 +1,96 @@ +// NeoPixelBuffer +// This example will animate pixels using a bitmap stored on a SD card +// +// +// This will demonstrate the use of the NeoBitmapFile object +// NOTE: The images provided in the example directory should be copied to +// the root of the SD card so the below code will find it. +// NOTE: This sample and the included images were built for a 144 pixel strip so +// running this with a smaller string may not look as interesting. Try providing +// your own 24 bit bitmap for better results. + +#include +#include +#include +#include + +const int chipSelect = D8; // make sure to set this to your SD carder reader CS + +//typedef NeoGrbFeature MyPixelColorFeature; +typedef NeoGrbwFeature MyPixelColorFeature; + +const uint16_t PixelCount = 144; // the sample images are meant for 144 pixels +const uint16_t PixelPin = 2; +const uint16_t AnimCount = 1; // we only need one + +NeoPixelBus strip(PixelCount, PixelPin); +NeoPixelAnimator animations(AnimCount); // NeoPixel animation management object + +// our NeoBitmapFile will use the same color feature as NeoPixelBus and +// we want it to use the SD File object +NeoBitmapFile image; + +uint16_t animState; + +void LoopAnimUpdate(const AnimationParam& param) +{ + // wait for this animation to complete, + // we are using it as a timer of sorts + if (param.state == AnimationState_Completed) + { + // done, time to restart this position tracking animation/timer + animations.RestartAnimation(param.index); + + // draw the complete row at animState to the complete strip + image.Blt(strip, 0, 0, animState, image.Width()); + animState = (animState + 1) % image.Height(); // increment and wrap + } +} + +void setup() { + Serial.begin(115200); + while (!Serial); // wait for serial attach + + strip.Begin(); + strip.Show(); + + Serial.print("Initializing SD card..."); + + // see if the card is present and can be initialized: + if (!SD.begin(chipSelect)) + { + Serial.println("Card failed, or not present"); + // don't do anything more: + return; + } + Serial.println("card initialized."); + + // open the file + File bitmapFile = SD.open("strings.bmp"); + if (!bitmapFile) + { + Serial.println("File open fail, or not present"); + // don't do anything more: + return; + } + + // initialize the image with the file + if (!image.Begin(bitmapFile)) + { + Serial.println("File format fail, not a supported bitmap"); + // don't do anything more: + return; + } + + animState = 0; + // we use the index 0 animation to time how often we rotate all the pixels + animations.StartAnimation(0, 30, LoopAnimUpdate); +} + +void loop() { + // this is all that is needed to keep it running + // and avoiding using delay() is always a good thing for + // any timing related routines + animations.UpdateAnimations(); + strip.Show(); +} \ No newline at end of file diff --git a/examples/NeoPixelBitmap/Strings.bmp b/examples/NeoPixelBitmap/Strings.bmp new file mode 100644 index 00000000..0ee3be6f Binary files /dev/null and b/examples/NeoPixelBitmap/Strings.bmp differ diff --git a/examples/NeoPixelBitmap/StringsW.bmp b/examples/NeoPixelBitmap/StringsW.bmp new file mode 100644 index 00000000..8e45e137 Binary files /dev/null and b/examples/NeoPixelBitmap/StringsW.bmp differ diff --git a/keywords.txt b/keywords.txt index e7653a18..b1cd71e3 100644 --- a/keywords.txt +++ b/keywords.txt @@ -65,6 +65,7 @@ NeoBufferMethod KEYWORD1 NeoBufferProgmemMethod KEYWORD1 NeoBuffer KEYWORD1 NeoVerticalSpriteSheet KEYWORD1 +NeoBitmapFile KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) diff --git a/library.json b/library.json index cbc1dea9..03ddd3d9 100644 --- a/library.json +++ b/library.json @@ -7,7 +7,7 @@ "type": "git", "url": "https://github.com/Makuna/NeoPixelBus" }, - "version": "2.1.1", + "version": "2.1.2", "frameworks": "arduino", "platforms": "*" } diff --git a/library.properties b/library.properties index d3f252ee..d5d6c2d0 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=NeoPixelBus by Makuna -version=2.1.1 +version=2.1.2 author=Michael C. Miller (makuna@live.com) maintainer=Michael C. Miller (makuna@live.com) sentence=A library that makes controlling NeoPixels (WS2811, WS2812 & SK6812) easy. diff --git a/src/NeoPixelBus.h b/src/NeoPixelBus.h index 859fc00d..13d00b3a 100644 --- a/src/NeoPixelBus.h +++ b/src/NeoPixelBus.h @@ -35,6 +35,8 @@ License along with NeoPixel. If not, see #include "internal/HtmlColor.h" #include "internal/RgbwColor.h" +#include "internal/NeoColorFeatures.h" + #include "internal/Layouts.h" #include "internal/NeoTopology.h" #include "internal/NeoTiles.h" @@ -44,12 +46,11 @@ License along with NeoPixel. If not, see #include "internal/NeoBufferMethods.h" #include "internal/NeoBuffer.h" #include "internal/NeoSpriteSheet.h" +#include "internal/NeoBitmapFile.h" #include "internal/NeoEase.h" #include "internal/NeoGamma.h" -#include "internal/NeoColorFeatures.h" - #if defined(ARDUINO_ARCH_ESP8266) #include "internal/NeoEsp8266DmaMethod.h" #include "internal/NeoEsp8266UartMethod.h" diff --git a/src/internal/NeoBitmapFile.h b/src/internal/NeoBitmapFile.h new file mode 100644 index 00000000..7a1fa5e8 --- /dev/null +++ b/src/internal/NeoBitmapFile.h @@ -0,0 +1,351 @@ +/*------------------------------------------------------------------------- +NeoPixel library + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ +#pragma once + +const uint16_t c_BitmapFileId = 0x4d42; // "BM" + +#pragma pack(push, 2) +struct BitmapFileHeader +{ + uint16_t FileId; // only c_BitmapFileId is supported + uint32_t FileSize; + uint16_t Reserved0; + uint16_t Reserved1; + uint32_t PixelAddress; +}; + +struct BitmapInfoHeader +{ + uint32_t Size; + int32_t Width; + int32_t Height; + uint16_t Planes; // only support 1 + uint16_t BitsPerPixel; // only support 24 and 32 + uint32_t Compression; // only support BI_Rgb + uint32_t RawDateSize; // can be zero + int32_t XPpm; + int32_t YPpm; + uint32_t PaletteLength; + uint32_t ImportantColorCount; +}; +#pragma pack(pop) + +enum BmpCompression +{ + BI_Rgb, + BI_Rle8, + BI_Rle4, + BI_Bitfields, + BI_Jpeg, + BI_Png, + BI_AlphaBitfields, + BI_Cmyk = 11, + BI_CmykRle8, + BI_CmykRle4 +}; + +template class NeoBitmapFile +{ +public: + NeoBitmapFile() : + _fileAddressPixels(0), + _width(0), + _height(0), + _sizeRow(0), + _bottomToTop(true), + _bytesPerPixel(0) + { + } + + + ~NeoBitmapFile() + { + _file.close(); + } + + bool Begin(T_FILE_METHOD file) + { + if (_file) + { + _file.close(); + } + + if (!file || !file.seek(0)) + { + goto error; + } + + _file = file; + + BitmapFileHeader bmpHeader; + BitmapInfoHeader bmpInfoHeader; + int result; + + result = _file.read(&bmpHeader, sizeof(bmpHeader)); + + if (result != sizeof(bmpHeader) || + bmpHeader.FileId != c_BitmapFileId || + bmpHeader.FileSize != _file.size()) + { + goto error; + } + + result = _file.read(&bmpInfoHeader, sizeof(bmpInfoHeader)); + + if (result != sizeof(bmpInfoHeader) || + result != bmpInfoHeader.Size || + 1 != bmpInfoHeader.Planes || + BI_Rgb != bmpInfoHeader.Compression) + { + goto error; + } + + if (!(24 == bmpInfoHeader.BitsPerPixel || + 32 == bmpInfoHeader.BitsPerPixel)) + { + goto error; + } + + // save the interesting information + _width = abs(bmpInfoHeader.Width); + _height = abs(bmpInfoHeader.Height); + _fileAddressPixels = bmpHeader.PixelAddress; + // negative height means rows are top to bottom + _bottomToTop = (bmpInfoHeader.Height > 0); + // rows are 32 bit aligned so they may have padding on each row + _sizeRow = (bmpInfoHeader.BitsPerPixel * _width + 31) / 32 * 4; + _bytesPerPixel = bmpInfoHeader.BitsPerPixel / 8; + + return true; + + error: + _fileAddressPixels = 0; + _width = 0; + _height = 0; + _sizeRow = 0; + _bytesPerPixel = 0; + + _file.close(); + return false; + }; + + size_t PixelSize() const + { + return T_COLOR_FEATURE::PixelSize; + }; + + uint16_t PixelCount() const + { + return _width * _height; + }; + + uint16_t Width() const + { + return _width; + }; + + uint16_t Height() const + { + return _height; + }; + + typename T_COLOR_FEATURE::ColorObject GetPixelColor(int16_t x, int16_t y) const + { + if (x < 0 || x >= _width || y < 0 || y >= _height) + { + // Pixel # is out of bounds, this will get converted to a + // color object type initialized to 0 (black) + return 0; + } + + typename T_COLOR_FEATURE::ColorObject color; + if (!seek(x, y) || !readPixel(&color)) + { + return 0; + } + + return color; + }; + + void Blt(NeoBufferContext destBuffer, + uint16_t indexPixel, + int16_t xSrc, + int16_t ySrc, + int16_t wSrc) + { + const uint16_t destPixelCount = destBuffer.PixelCount(); + typename T_COLOR_FEATURE::ColorObject color(0); + xSrc = constrainX(xSrc); + ySrc = constrainY(ySrc); + + if (seek(xSrc, ySrc)) + { + for (int16_t x = 0; x < wSrc && indexPixel < destPixelCount; x++, indexPixel++) + { + if (xSrc < _width) + { + if (readPixel(&color)) + { + xSrc++; + } + } + + T_COLOR_FEATURE::applyPixelColor(destBuffer.Pixels, indexPixel, color); + } + } + } + + void Blt(NeoBufferContext destBuffer, + int16_t xDest, + int16_t yDest, + int16_t xSrc, + int16_t ySrc, + int16_t wSrc, + int16_t hSrc, + LayoutMapCallback layoutMap) + { + const uint16_t destPixelCount = destBuffer.PixelCount(); + typename T_COLOR_FEATURE::ColorObject color(0); + + for (int16_t y = 0; y < hSrc; y++) + { + int16_t xFile = constrainX(xSrc); + int16_t yFile = constrainY(ySrc + y); + + if (seek(xFile, yFile)) + { + for (int16_t x = 0; x < wSrc; x++) + { + if (xFile < _width) + { + if (readPixel(&color)) + { + xFile++; + } + } + + uint16_t indexDest = layoutMap(xDest + x, yDest + y); + + if (indexDest < destPixelCount) + { + T_COLOR_FEATURE::applyPixelColor(destBuffer.Pixels, indexDest, color); + } + } + } + } + }; + + +private: + T_FILE_METHOD _file; + uint32_t _fileAddressPixels; + uint16_t _width; + uint16_t _height; + uint32_t _sizeRow; + uint8_t _bytesPerPixel; + bool _bottomToTop; + + int16_t constrainX(int16_t x) + { + if (x < 0) + { + x = 0; + } + else if (x >= _width) + { + x = _width - 1; + } + return x; + }; + + int16_t constrainY(int16_t y) + { + if (y < 0) + { + y = 0; + } + else if (y >= _height) + { + y = _height - 1; + } + return y; + }; + + bool seek(int16_t x, int16_t y) + { + if (_bottomToTop) + { + y = (_height - 1) - y; + } + + uint32_t pos = y * _sizeRow + x * _bytesPerPixel; + pos += _fileAddressPixels; + + return _file.seek(pos); + }; + + bool readPixel(RgbColor* color) + { + uint8_t bgr[4]; + int result; + + result = _file.read(bgr, _bytesPerPixel); + + if (result != _bytesPerPixel) + { + *color = 0; + return false; + } + + color->B = bgr[0]; + color->G = bgr[1]; + color->R = bgr[2]; + + return true; + }; + + bool readPixel(RgbwColor* color) + { + uint8_t bgr[4]; + int result; + + bgr[3] = 0; // init white channel as read maybe only 3 bytes + result = _file.read(bgr, _bytesPerPixel); + + if (result != _bytesPerPixel) + { + *color = 0; + return false; + } + + color->B = bgr[0]; + color->G = bgr[1]; + color->R = bgr[2]; + color->W = bgr[3]; + + return true; + }; +}; \ No newline at end of file diff --git a/src/internal/NeoBuffer.h b/src/internal/NeoBuffer.h index 691a9b8b..86176c9b 100644 --- a/src/internal/NeoBuffer.h +++ b/src/internal/NeoBuffer.h @@ -55,7 +55,7 @@ template class NeoBuffer return _method.Height(); }; - void SetPixelColor(uint16_t indexSprite, + void SetPixelColor( int16_t x, int16_t y, typename T_BUFFER_METHOD::ColorObject color) @@ -63,7 +63,7 @@ template class NeoBuffer _method.SetPixelColor(pixelIndex(x, y), color); }; - typename T_BUFFER_METHOD::ColorObject GetPixelColor(uint16_t indexSprite, + typename T_BUFFER_METHOD::ColorObject GetPixelColor( int16_t x, int16_t y) const { diff --git a/src/internal/NeoBufferMethods.h b/src/internal/NeoBufferMethods.h index 0c1b2057..4bd531ff 100644 --- a/src/internal/NeoBufferMethods.h +++ b/src/internal/NeoBufferMethods.h @@ -55,6 +55,11 @@ template class NeoBufferMethod } } + ~NeoBufferMethod() + { + free(_pixels); + } + operator NeoBufferContext() { return NeoBufferContext(Pixels(), PixelsSize());