/*------------------------------------------------------------------------- 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), _bytesPerPixel(0), _bottomToTop(true) { } ~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; size_t result; result = _file.read((uint8_t*)(&bmpHeader), sizeof(bmpHeader)); if (result != sizeof(bmpHeader) || bmpHeader.FileId != c_BitmapFileId || bmpHeader.FileSize != _file.size()) { goto error; } result = _file.read((uint8_t*)(&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) { 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; }; template void Render(NeoBufferContext destBuffer, T_SHADER& shader, 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 (static_cast(xSrc) < _width) { if (readPixel(&color)) { color = shader.Apply(indexPixel, color); xSrc++; } } T_COLOR_FEATURE::applyPixelColor(destBuffer.Pixels, indexPixel, color); } } } void Blt(NeoBufferContext destBuffer, uint16_t indexPixel, int16_t xSrc, int16_t ySrc, int16_t wSrc) { NeoShaderNop shaderNop; Render>(destBuffer, shaderNop, indexPixel, xSrc, ySrc, wSrc); }; template void Render(NeoBufferContext destBuffer, T_SHADER& shader, 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++) { uint16_t indexDest = layoutMap(xDest + x, yDest + y); if (static_cast(xFile) < _width) { if (readPixel(&color)) { color = shader.Apply(indexDest, color); xFile++; } } if (indexDest < destPixelCount) { T_COLOR_FEATURE::applyPixelColor(destBuffer.Pixels, indexDest, 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) { NeoShaderNop shaderNop; Render>(destBuffer, shaderNop, xDest, yDest, xSrc, ySrc, wSrc, hSrc, layoutMap); }; 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) const { if (x < 0) { x = 0; } else if (static_cast(x) >= _width) { x = _width - 1; } return x; }; int16_t constrainY(int16_t y) const { if (y < 0) { y = 0; } else if (static_cast(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; }; };