Skip to content

Commit 41f31b8

Browse files
committed
POV Display usermod
this usermod adds a new effect called "POV Image". To get it to work: - read the README :) - upload a bmp image to the ESP filesystem using "/edit" url. - select "POV Image" effect. - set the filename (ie: "/myimage.bmp") as segment name. - rotate the segment at approximately 20 RPM. - enjoy the show!
1 parent 7285efe commit 41f31b8

File tree

7 files changed

+322
-76
lines changed

7 files changed

+322
-76
lines changed

usermods/pov_display/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
##POV Display usermod
2+
3+
this usermod adds a new effect called "POV Image".
4+
5+
To get it working:
6+
- resize your image, the height should be same number of pixels as your led strip.
7+
- rotate your image 90 degrees clockwise (height is now width...)
8+
- upload a bmp image to the ESP filesystem using "/edit" url.
9+
- select "POV Image" effect.
10+
- set the segment name with the absolute path of the image (ie: "/myimage.bmp").
11+
- rotate the segment at approximately 20 RPM.
12+
- enjoy the show!

usermods/pov_display/bmpimage.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#include "bmpimage.h"
2+
#define BUF_SIZE 64000
3+
4+
byte _buffer[BUF_SIZE];
5+
6+
uint16_t read16(File &f) {
7+
uint16_t result;
8+
f.read((uint8_t *)&result,2);
9+
return result;
10+
}
11+
12+
uint32_t read32(File &f) {
13+
uint32_t result;
14+
f.read((uint8_t *)&result,4);
15+
return result;
16+
}
17+
18+
bool BMPimage::init(const char * fn) {
19+
File bmpFile;
20+
int bmpDepth;
21+
//first, check if filename exists
22+
if (!WLED_FS.exists(fn)) {
23+
return false;
24+
}
25+
26+
bmpFile = WLED_FS.open(fn);
27+
if (!bmpFile) {
28+
_valid=false;
29+
return false;
30+
}
31+
32+
//so, the file exists and is opened
33+
// Parse BMP header
34+
uint16_t header = read16(bmpFile);
35+
if(header != 0x4D42) { // BMP signature
36+
_valid=false;
37+
bmpFile.close();
38+
return false;
39+
}
40+
41+
//read and ingnore file size
42+
read32(bmpFile);
43+
(void)read32(bmpFile); // Read & ignore creator bytes
44+
_imageOffset = read32(bmpFile); // Start of image data
45+
// Read DIB header
46+
read32(bmpFile);
47+
_width = read32(bmpFile);
48+
_height = read32(bmpFile);
49+
if(read16(bmpFile) != 1) { // # planes -- must be '1'
50+
_valid=false;
51+
bmpFile.close();
52+
return false;
53+
}
54+
bmpDepth = read16(bmpFile); // bits per pixel
55+
if((bmpDepth != 24) || (read32(bmpFile) != 0)) { // 0 = uncompressed {
56+
_width=0;
57+
_valid=false;
58+
bmpFile.close();
59+
return false;
60+
}
61+
// If _height is negative, image is in top-down order.
62+
// This is not canon but has been observed in the wild.
63+
if(_height < 0) {
64+
_height = -_height;
65+
}
66+
//now, we have successfully got all the basics
67+
// BMP rows are padded (if needed) to 4-byte boundary
68+
_rowSize = (_width * 3 + 3) & ~3;
69+
//check image size - if it is too large, it will be unusable
70+
if (_rowSize*_height>BUF_SIZE) {
71+
_valid=false;
72+
bmpFile.close();
73+
return false;
74+
}
75+
76+
bmpFile.close();
77+
strcpy(filename,fn);
78+
return true;
79+
}
80+
81+
void BMPimage::clear(){
82+
strcpy(filename, "");
83+
_width=0;
84+
_height=0;
85+
_rowSize=0;
86+
_imageOffset=0;
87+
_loaded=false;
88+
_valid=false;
89+
}
90+
91+
bool BMPimage::load(){
92+
uint16_t size= _rowSize * _height;
93+
if (BUF_SIZE < size ){
94+
return false;
95+
}
96+
97+
File bmpFile = WLED_FS.open(filename);
98+
if (!bmpFile) {
99+
return false;
100+
}
101+
bmpFile.seek(_imageOffset);
102+
bmpFile.read(_buffer, size);
103+
bmpFile.close();
104+
_loaded = true;
105+
return true;
106+
}
107+
108+
byte* BMPimage::line(uint16_t n){
109+
if (_loaded) {
110+
return (_buffer+n*_rowSize);
111+
} else {
112+
return NULL;
113+
}
114+
}
115+
116+
uint32_t BMPimage::pixelColor(uint16_t x, uint16_t y){
117+
uint32_t pos;
118+
byte b,g,r; //colors
119+
if (! _loaded) {
120+
return 0;
121+
}
122+
if ( (x>=_width) || (y>=_height) ) {
123+
return 0;
124+
}
125+
pos=y*_rowSize + 3*x;
126+
//get colors. Note that in BMP files, they go in BGR order
127+
b= _buffer[pos++];
128+
g= _buffer[pos++];
129+
r= _buffer[pos];
130+
return (r<<16|g<<8|b);
131+
}

usermods/pov_display/bmpimage.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#ifndef _BMPIMAGE_H
2+
#define _BMPIMAGE_H
3+
#include "Arduino.h"
4+
#include "wled.h"
5+
6+
/*
7+
* This class describes a bitmap image. Each object refers to a bmp file on
8+
* filesystem fatfs.
9+
* To initialize, call init(), passign to it name of a bitmap file
10+
* at the root of fatfs filesystem:
11+
*
12+
* BMPimage myImage;
13+
* myImage.init("logo.bmp");
14+
*
15+
* For performance reasons, before actually usign the image, you need to load
16+
* it from filesystem to RAM:
17+
* myImage.load();
18+
* All load() operations use the same reserved buffer in RAM, so you can only
19+
* have one file loaded at a time. Before loading a new file, always unload the
20+
* previous one:
21+
* myImage.unload();
22+
*/
23+
24+
class BMPimage {
25+
public:
26+
int height() {return _height; }
27+
int width() {return _width; }
28+
int rowSize() {return _rowSize;}
29+
bool isLoaded() {return _loaded; }
30+
bool load();
31+
void unload() {_loaded=false; }
32+
byte * line(uint16_t n);
33+
uint32_t pixelColor(uint16_t x,uint16_t y);
34+
bool init(const char* fn);
35+
void clear();
36+
char * getFilename() {return filename;};
37+
38+
private:
39+
char filename[WLED_MAX_SEGNAME_LEN+1]="";
40+
int _width=0;
41+
int _height=0;
42+
int _rowSize=0;
43+
int _imageOffset=0;
44+
bool _loaded=false;
45+
bool _valid=false;
46+
};
47+
48+
extern byte _buffer[];
49+
50+
#endif

usermods/pov_display/library.json.disabled renamed to usermods/pov_display/library.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@
22
"name:": "pov_display",
33
"build": { "libArchive": false},
44
"dependencies": {
5-
"bitbank2/PNGdec":"^1.0.3"
65
}
76
}

usermods/pov_display/pov.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "pov.h"
2+
3+
POV::POV() {}
4+
5+
void POV::showLine(byte * line, uint16_t size){
6+
uint16_t i,pos;
7+
uint8_t r,g,b;
8+
for (i=0; i<SEGLEN; i++) {
9+
if (i<size) {
10+
pos=3*i;
11+
//using bgr order
12+
b=line[pos++];
13+
g=line[pos++];
14+
r=line[pos];
15+
SEGMENT.setPixelColor(i, CRGB(r,g,b));
16+
} else {
17+
SEGMENT.setPixelColor(i, CRGB::Black);
18+
}
19+
}
20+
strip.show();
21+
lastLineUpdate=micros();
22+
}
23+
24+
bool POV::loadImage(char * filename){
25+
if(!image.init(filename)) return false;
26+
if(!image.load()) return false;
27+
currentLine=0;
28+
return true;
29+
}
30+
31+
int16_t POV::showNextLine(){
32+
if (!image.isLoaded()) return 0;
33+
//move to next line
34+
showLine(image.line(currentLine), image.width());
35+
currentLine++;
36+
if (currentLine == image.height()) {currentLine=0;}
37+
return currentLine;
38+
}

usermods/pov_display/pov.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#ifndef _POV_H
2+
#define _POV_H
3+
#include "bmpimage.h"
4+
5+
6+
class POV {
7+
public:
8+
POV();
9+
10+
/* Shows one line. line should be pointer to array which holds pixel colors
11+
* (3 bytes per pixel, in BGR order). Note: 3, not 4!!!
12+
* size should be size of array (number of pixels, not number of bytes)
13+
*/
14+
void showLine(byte * line, uint16_t size);
15+
16+
/* Reads from file an image and making it current image */
17+
bool loadImage(char * filename);
18+
19+
/* Show next line of active image
20+
Retunrs the index of next line to be shown (not yet shown!)
21+
If it retunrs 0, it means we have completed showing the image and
22+
next call will start again
23+
*/
24+
int16_t showNextLine();
25+
26+
//time since strip was last updated, in micro sec
27+
uint32_t timeSinceUpdate() {return (micros()-lastLineUpdate);}
28+
29+
30+
BMPimage * currentImage() {return &image;}
31+
32+
char * getFilename() {return image.getFilename();}
33+
34+
private:
35+
BMPimage image;
36+
int16_t currentLine=0; //next line to be shown
37+
uint32_t lastLineUpdate=0; //time in microseconds
38+
};
39+
40+
41+
42+
#endif

0 commit comments

Comments
 (0)