diff --git a/OLEDDisplay.cpp b/OLEDDisplay.cpp index 3a949906e7196135c855fb44a9af923f33e416db..5fc15dbec766c85728e0859b1e73bc3f63726a6f 100644 --- a/OLEDDisplay.cpp +++ b/OLEDDisplay.cpp @@ -32,14 +32,14 @@ bool OLEDDisplay::init() { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); return false; } - this->buffer = (uint8_t*) malloc(sizeof(uint8_t) * DISPLAY_BUFFER_SIZE); + this->buffer = (uint8_t*) malloc(sizeof(uint8_t) * displayBufferSize); if(!this->buffer) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); return false; } #ifdef OLEDDISPLAY_DOUBLE_BUFFER - this->buffer_back = (uint8_t*) malloc(sizeof(uint8_t) * DISPLAY_BUFFER_SIZE); + this->buffer_back = (uint8_t*) malloc(sizeof(uint8_t) * displayBufferSize); if(!this->buffer_back) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); free(this->buffer); @@ -63,7 +63,7 @@ void OLEDDisplay::end() { void OLEDDisplay::resetDisplay(void) { clear(); #ifdef OLEDDISPLAY_DOUBLE_BUFFER - memset(buffer_back, 1, DISPLAY_BUFFER_SIZE); + memset(buffer_back, 1, displayBufferSize); #endif display(); } @@ -72,12 +72,17 @@ void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { this->color = color; } +OLEDDISPLAY_COLOR OLEDDisplay::getColor() { + return this->color; +} + void OLEDDisplay::setPixel(int16_t x, int16_t y) { - if (x >= 0 && x < 128 && y >= 0 && y < 64) { + if (x >= 0 && x < displayWidth && y >= 0 && y < displayHeight) { + switch (color) { - case WHITE: buffer[x + (y / 8) * DISPLAY_WIDTH] |= (1 << (y & 7)); break; - case BLACK: buffer[x + (y / 8) * DISPLAY_WIDTH] &= ~(1 << (y & 7)); break; - case INVERSE: buffer[x + (y / 8) * DISPLAY_WIDTH] ^= (1 << (y & 7)); break; + case WHITE: buffer[x + (y / 8) * displayWidth] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * displayWidth] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * displayWidth] ^= (1 << (y & 7)); break; } } } @@ -222,21 +227,21 @@ void OLEDDisplay::fillCircle(int16_t x0, int16_t y0, int16_t radius) { } void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { - if (y < 0 || y >= DISPLAY_HEIGHT) { return; } + if (y < 0 || y >= displayHeight) { return; } if (x < 0) { length += x; x = 0; } - if ( (x + length) > DISPLAY_WIDTH) { - length = (DISPLAY_WIDTH - x); + if ( (x + length) > displayWidth) { + length = (displayWidth - x); } if (length <= 0) { return; } uint8_t * bufferPtr = buffer; - bufferPtr += (y >> 3) * DISPLAY_WIDTH; + bufferPtr += (y >> 3) * displayWidth; bufferPtr += x; uint8_t drawBit = 1 << (y & 7); @@ -255,15 +260,15 @@ void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { } void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { - if (x < 0 || x >= DISPLAY_WIDTH) return; + if (x < 0 || x > displayWidth) return; if (y < 0) { length += y; y = 0; } - if ( (y + length) > DISPLAY_HEIGHT) { - length = (DISPLAY_HEIGHT - y); + if ( (y + length) > displayHeight) { + length = (displayHeight - y); } if (length <= 0) return; @@ -273,7 +278,7 @@ void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { uint8_t drawBit; uint8_t *bufferPtr = buffer; - bufferPtr += (y >> 3) * DISPLAY_WIDTH; + bufferPtr += (y >> 3) * displayWidth; bufferPtr += x; if (yOffset) { @@ -293,7 +298,7 @@ void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { if (length < yOffset) return; length -= yOffset; - bufferPtr += DISPLAY_WIDTH; + bufferPtr += displayWidth; } if (length >= 8) { @@ -303,14 +308,14 @@ void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { drawBit = (color == WHITE) ? 0xFF : 0x00; do { *bufferPtr = drawBit; - bufferPtr += DISPLAY_WIDTH; + bufferPtr += displayWidth; length -= 8; } while (length >= 8); break; case INVERSE: do { *bufferPtr = ~(*bufferPtr); - bufferPtr += DISPLAY_WIDTH; + bufferPtr += displayWidth; length -= 8; } while (length >= 8); break; @@ -347,11 +352,11 @@ void OLEDDisplay::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16 fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); } -void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *image) { +void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) { drawInternal(xMove, yMove, width, height, image, 0, 0); } -void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *xbm) { +void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { int16_t widthInXbm = (width + 7) / 8; uint8_t data = 0; @@ -389,12 +394,12 @@ void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, u xMove -= textWidth; break; case TEXT_ALIGN_LEFT: - break; + break; } // Don't draw anything if it is not on the screen. - if (xMove + textWidth < 0 || xMove > DISPLAY_WIDTH ) {return;} - if (yMove + textHeight < 0 || yMove > DISPLAY_HEIGHT) {return;} + if (xMove + textWidth < 0 || xMove > displayWidth ) {return;} + if (yMove + textHeight < 0 || yMove > displayHeight) {return;} for (uint16_t j = 0; j < textLength; j++) { int16_t xPos = xMove + cursorX; @@ -527,7 +532,7 @@ void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { this->textAlignment = textAlignment; } -void OLEDDisplay::setFont(const char *fontData) { +void OLEDDisplay::setFont(const uint8_t *fontData) { this->fontData = fontData; } @@ -552,13 +557,23 @@ void OLEDDisplay::setContrast(char contrast) { sendCommand(contrast); } +void OLEDDisplay::resetOrientation() { + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); //Reset screen rotation or mirroring +} + void OLEDDisplay::flipScreenVertically() { sendCommand(SEGREMAP | 0x01); sendCommand(COMSCANDEC); //Rotate screen 180 Deg } +void OLEDDisplay::mirrorScreen() { + sendCommand(SEGREMAP); + sendCommand(COMSCANDEC); //Mirror screen +} + void OLEDDisplay::clear(void) { - memset(buffer, 0, DISPLAY_BUFFER_SIZE); + memset(buffer, 0, displayBufferSize); } void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { @@ -593,6 +608,14 @@ void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { } } +uint16_t OLEDDisplay::getWidth(void) { + return displayWidth; +} + +uint16_t OLEDDisplay::getHeight(void) { + return displayHeight; +} + bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ if (logBuffer != NULL) free(logBuffer); uint16_t size = lines * chars; @@ -614,12 +637,17 @@ size_t OLEDDisplay::write(uint8_t c) { // Don't waste space on \r\n line endings, dropping \r if (c == 13) return 1; + // convert UTF-8 character to font table index + c = (this->fontTableLookupFunction)(c); + // drop unknown character + if (c == 0) return 1; + bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines; bool bufferNotFull = this->logBufferFilled < this->logBufferSize; // Can we write to the buffer? if (bufferNotFull && maxLineNotReached) { - this->logBuffer[logBufferFilled] = utf8ascii(c); + this->logBuffer[logBufferFilled] = c; this->logBufferFilled++; // Keep track of lines written if (c == 10) this->logBufferLine++; @@ -672,7 +700,8 @@ void OLEDDisplay::sendInitCommands(void) { sendCommand(SETDISPLAYCLOCKDIV); sendCommand(0xF0); // Increase speed of the display max ~96Hz sendCommand(SETMULTIPLEX); - sendCommand(0x3F); + //sendCommand(0x3F); + sendCommand(displayHeight - 1); sendCommand(SETDISPLAYOFFSET); sendCommand(0x00); sendCommand(SETSTARTLINE); @@ -683,9 +712,21 @@ void OLEDDisplay::sendInitCommands(void) { sendCommand(SEGREMAP); sendCommand(COMSCANINC); sendCommand(SETCOMPINS); - sendCommand(0x12); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0x12); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x02); + } + sendCommand(SETCONTRAST); - sendCommand(0xCF); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0xCF); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x8F); + } + sendCommand(SETPRECHARGE); sendCommand(0xF1); sendCommand(DISPLAYALLON_RESUME); @@ -694,10 +735,10 @@ void OLEDDisplay::sendInitCommands(void) { sendCommand(DISPLAYON); } -void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *data, uint16_t offset, uint16_t bytesInData) { +void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) { if (width < 0 || height < 0) return; - if (yMove + height < 0 || yMove > DISPLAY_HEIGHT) return; - if (xMove + width < 0 || xMove > DISPLAY_WIDTH) return; + if (yMove + height < 0 || yMove > displayHeight) return; + if (xMove + width < 0 || xMove > displayWidth) return; uint8_t rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0) int8_t yOffset = yMove & 7; @@ -719,12 +760,12 @@ void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t widt byte currentByte = pgm_read_byte(data + offset + i); int16_t xPos = xMove + (i / rasterHeight); - int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * DISPLAY_WIDTH; + int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * displayWidth; int16_t dataPos = xPos + yPos; - if (dataPos >= 0 && dataPos < DISPLAY_BUFFER_SIZE && - xPos >= 0 && xPos < DISPLAY_WIDTH ) { + if (dataPos >= 0 && dataPos < displayBufferSize && + xPos >= 0 && xPos < displayWidth ) { if (yOffset >= 0) { switch (this->color) { @@ -732,11 +773,11 @@ void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t widt case BLACK: buffer[dataPos] &= ~(currentByte << yOffset); break; case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break; } - if (dataPos < (DISPLAY_BUFFER_SIZE - DISPLAY_WIDTH)) { + if (dataPos < (displayBufferSize - displayWidth)) { switch (this->color) { - case WHITE: buffer[dataPos + DISPLAY_WIDTH] |= currentByte >> (8 - yOffset); break; - case BLACK: buffer[dataPos + DISPLAY_WIDTH] &= ~(currentByte >> (8 - yOffset)); break; - case INVERSE: buffer[dataPos + DISPLAY_WIDTH] ^= currentByte >> (8 - yOffset); break; + case WHITE: buffer[dataPos + displayWidth] |= currentByte >> (8 - yOffset); break; + case BLACK: buffer[dataPos + displayWidth] &= ~(currentByte >> (8 - yOffset)); break; + case INVERSE: buffer[dataPos + displayWidth] ^= currentByte >> (8 - yOffset); break; } } } else { @@ -761,27 +802,6 @@ void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t widt } } -// Code form http://playground.arduino.cc/Main/Utf8ascii -uint8_t OLEDDisplay::utf8ascii(byte ascii) { - static uint8_t LASTCHAR; - - if ( ascii < 128 ) { // Standard ASCII-set 0..0x7F handling - LASTCHAR = 0; - return ascii; - } - - uint8_t last = LASTCHAR; // get last char - LASTCHAR = ascii; - - switch (last) { // conversion depnding on first UTF8-character - case 0xC2: return (ascii); break; - case 0xC3: return (ascii | 0xC0); break; - case 0x82: if (ascii == 0xAC) return (0x80); // special case Euro-symbol - } - - return 0; // otherwise: return zero, if character has to be ignored -} - // You need to free the char! char* OLEDDisplay::utf8ascii(String str) { uint16_t k = 0; @@ -798,7 +818,7 @@ char* OLEDDisplay::utf8ascii(String str) { length--; for (uint16_t i=0; i < length; i++) { - char c = utf8ascii(s[i]); + char c = (this->fontTableLookupFunction)(s[i]); if (c!=0) { s[k++]=c; } @@ -809,3 +829,7 @@ char* OLEDDisplay::utf8ascii(String str) { // This will leak 's' be sure to free it in the calling function. return s; } + +void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { + this->fontTableLookupFunction = function; +} \ No newline at end of file diff --git a/OLEDDisplay.h b/OLEDDisplay.h index f072b1aa438510f260ecc914bb13824ea5e617bf..83c966c8809bbf127275a08270c6b12780d480c3 100644 --- a/OLEDDisplay.h +++ b/OLEDDisplay.h @@ -42,12 +42,6 @@ #define OLEDDISPLAY_DOUBLE_BUFFER #endif - -// Display settings -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_BUFFER_SIZE 1024 - // Header Values #define JUMPTABLE_BYTES 4 @@ -108,8 +102,18 @@ enum OLEDDISPLAY_TEXT_ALIGNMENT { }; +enum OLEDDISPLAY_GEOMETRY { + GEOMETRY_128_64 = 0, + GEOMETRY_128_32 = 1 +}; + +typedef byte (*FontTableLookupFunction)(const byte ch); + + class OLEDDisplay : public Print { public: + virtual ~OLEDDisplay() {} + // Initialize the display bool init(); @@ -123,6 +127,9 @@ class OLEDDisplay : public Print { // Sets the color of all pixel operations void setColor(OLEDDISPLAY_COLOR color); + // Returns the current color. + OLEDDISPLAY_COLOR getColor(); + // Draw a pixel at given position void setPixel(int16_t x, int16_t y); @@ -147,18 +154,18 @@ class OLEDDisplay : public Print { // Draw a line horizontally void drawHorizontalLine(int16_t x, int16_t y, int16_t length); - // Draw a lin vertically + // Draw a line vertically void drawVerticalLine(int16_t x, int16_t y, int16_t length); - // Draws a rounded progress bar with the outer dimensions given by width and height. Progress is + // Draws a rounded progress bar with the outer dimensions given by width and height. Progress is // a unsigned byte value between 0 and 100 void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); // Draw a bitmap in the internal image format - void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const char *image); + void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image); // Draw a XBM - void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const char *xbm); + void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm); /* Text functions */ @@ -184,7 +191,10 @@ class OLEDDisplay : public Print { // Sets the current font. Available default fonts // ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24 - void setFont(const char *fontData); + void setFont(const uint8_t *fontData); + + // Set the function that will convert utf-8 to font table index + void setFontTableLookupFunction(FontTableLookupFunction function); /* Display functions */ @@ -203,9 +213,15 @@ class OLEDDisplay : public Print { // Set display contrast void setContrast(char contrast); + // Reset display rotation or mirroring + void resetOrientation(); + // Turn the display upside down void flipScreenVertically(); + // Mirror the display (to be used in a mirror or as a projector) + void mirrorScreen(); + // Write the buffer to the display memory virtual void display(void) = 0; @@ -222,7 +238,11 @@ class OLEDDisplay : public Print { // Draw the log buffer at position (x, y) void drawLogBuffer(uint16_t x, uint16_t y); - // Implementent needed function to be compatible with Print class + // Get screen geometry + uint16_t getWidth(void); + uint16_t getHeight(void); + + // Implement needed function to be compatible with Print class size_t write(uint8_t c); size_t write(const char* s); @@ -234,10 +254,16 @@ class OLEDDisplay : public Print { protected: + OLEDDISPLAY_GEOMETRY geometry = GEOMETRY_128_64; + + uint16_t displayWidth = 128; + uint16_t displayHeight = 64; + uint16_t displayBufferSize = 1024; + OLEDDISPLAY_TEXT_ALIGNMENT textAlignment = TEXT_ALIGN_LEFT; OLEDDISPLAY_COLOR color = WHITE; - const char *fontData = ArialMT_Plain_10; + const uint8_t *fontData = ArialMT_Plain_10; // State values for logBuffer uint16_t logBufferSize = 0; @@ -256,13 +282,33 @@ class OLEDDisplay : public Print { void sendInitCommands(); // converts utf8 characters to extended ascii - static char* utf8ascii(String s); - static byte utf8ascii(byte ascii); + char* utf8ascii(String s); - void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline)); + void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline)); void drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth); + // UTF-8 to font table index converter + // Code form http://playground.arduino.cc/Main/Utf8ascii + FontTableLookupFunction fontTableLookupFunction = [](const byte ch) { + static uint8_t LASTCHAR; + + if (ch < 128) { // Standard ASCII-set 0..0x7F handling + LASTCHAR = 0; + return ch; + } + + uint8_t last = LASTCHAR; // get last char + LASTCHAR = ch; + + switch (last) { // conversion depnding on first UTF8-character + case 0xC2: return (uint8_t) ch; break; + case 0xC3: return (uint8_t) (ch | 0xC0); break; + case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol + } + + return (uint8_t) 0; // otherwise: return zero, if character has to be ignored + }; }; #endif diff --git a/OLEDDisplayFonts.h b/OLEDDisplayFonts.h index 6dd21ef60b9919ca2dd24935340f3a5f518735a8..3544edb80d1721237febea01c3f6389ee6e2ad60 100644 --- a/OLEDDisplayFonts.h +++ b/OLEDDisplayFonts.h @@ -1,7 +1,7 @@ #ifndef OLEDDISPLAYFONTS_h #define OLEDDISPLAYFONTS_h -const char ArialMT_Plain_10[] PROGMEM = { +const uint8_t ArialMT_Plain_10[] PROGMEM = { 0x0A, // Width: 10 0x0D, // Height: 13 0x20, // First Char: 32 @@ -425,7 +425,7 @@ const char ArialMT_Plain_10[] PROGMEM = { 0x20,0x00,0xC8,0x09,0x00,0x06,0xC8,0x01,0x20 // 255 }; -const char ArialMT_Plain_16[] PROGMEM = { +const uint8_t ArialMT_Plain_16[] PROGMEM = { 0x10, // Width: 16 0x13, // Height: 19 0x20, // First Char: 32 @@ -848,7 +848,7 @@ const char ArialMT_Plain_16[] PROGMEM = { 0x00,0x00,0x00,0xF8,0xFF,0x03,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 254 0xC0,0x01,0x00,0x00,0x06,0x02,0x10,0x38,0x02,0x00,0xE0,0x01,0x10,0x38,0x00,0x00,0x07,0x00,0xC0 // 255 }; -const char ArialMT_Plain_24[] PROGMEM = { +const uint8_t ArialMT_Plain_24[] PROGMEM = { 0x18, // Width: 24 0x1C, // Height: 28 0x20, // First Char: 32 @@ -1271,4 +1271,4 @@ const char ArialMT_Plain_24[] PROGMEM = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x00,0x1C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x03, // 254 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0xC0,0xF0,0x01,0x06,0xC0,0x80,0x0F,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xFC,0x00,0xC0,0xC0,0x1F,0x00,0xC0,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06 // 255 }; -#endif +#endif diff --git a/OLEDDisplayUi.cpp b/OLEDDisplayUi.cpp index b3fdfdb820cb396fa70fb1f6243786a8242e39e5..b29b8b55799ab6cb9ffcce7d216e5a3ebe8f1d0c 100644 --- a/OLEDDisplayUi.cpp +++ b/OLEDDisplayUi.cpp @@ -90,10 +90,10 @@ void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) { void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) { this->indicatorDirection = dir; } -void OLEDDisplayUi::setActiveSymbol(const char* symbol) { +void OLEDDisplayUi::setActiveSymbol(const uint8_t* symbol) { this->activeSymbol = symbol; } -void OLEDDisplayUi::setInactiveSymbol(const char* symbol) { +void OLEDDisplayUi::setInactiveSymbol(const uint8_t* symbol) { this->inactiveSymbol = symbol; } @@ -254,28 +254,29 @@ void OLEDDisplayUi::drawFrame(){ int16_t x = 0, y = 0, x1 = 0, y1 = 0; switch(this->frameAnimationDirection){ case SLIDE_LEFT: - x = -128 * progress; + x = -(this->display->getWidth()) * progress; y = 0; - x1 = x + 128; + x1 = x + this->display->getWidth(); y1 = 0; break; case SLIDE_RIGHT: - x = 128 * progress; + x = this->display->getWidth() * progress; y = 0; - x1 = x - 128; + x1 = x - this->display->getWidth(); y1 = 0; break; case SLIDE_UP: x = 0; - y = -64 * progress; + y = -(this->display->getHeight()) * progress; x1 = 0; - y1 = y + 64; + y1 = y + (this->display->getHeight()); break; case SLIDE_DOWN: + default: x = 0; - y = 64 * progress; + y = (this->display->getHeight()) * progress; x1 = 0; - y1 = y - 64; + y1 = y - (this->display->getHeight()); break; } @@ -345,6 +346,7 @@ void OLEDDisplayUi::drawIndicator() { posOfHighlightFrame = frameToHighlight; break; case RIGHT_LEFT: + default: posOfHighlightFrame = this->frameCount - frameToHighlight; break; } @@ -360,27 +362,36 @@ void OLEDDisplayUi::drawIndicator() { break; } - uint16_t frameStartPos = (12 * frameCount / 2); - const char *image; - uint16_t x = 0, y = 0; + //Space between indicators - reduce for small screen sizes + uint16_t indicatorSpacing = 12; + if (this->display->getHeight() < 64 && (this->indicatorPosition == RIGHT || this->indicatorPosition == LEFT)) { + indicatorSpacing = 6; + } + + uint16_t frameStartPos = (indicatorSpacing * frameCount / 2); + const uint8_t *image; + + uint16_t x,y; + for (byte i = 0; i < this->frameCount; i++) { switch (this->indicatorPosition){ case TOP: y = 0 - (8 * indicatorFadeProgress); - x = 64 - frameStartPos + 12 * i; + x = (this->display->getWidth() / 2) - frameStartPos + indicatorSpacing * i; break; case BOTTOM: - y = 56 + (8 * indicatorFadeProgress); - x = 64 - frameStartPos + 12 * i; + y = (this->display->getHeight() - 8) + (8 * indicatorFadeProgress); + x = (this->display->getWidth() / 2) - frameStartPos + indicatorSpacing * i; break; case RIGHT: - x = 120 + (8 * indicatorFadeProgress); - y = 32 - frameStartPos + 2 + 12 * i; + x = (this->display->getWidth() - 8) + (8 * indicatorFadeProgress); + y = (this->display->getHeight() / 2) - frameStartPos + indicatorSpacing * i; break; case LEFT: + default: x = 0 - (8 * indicatorFadeProgress); - y = 32 - frameStartPos + 2 + 12 * i; + y = (this->display->getHeight() / 2) - frameStartPos + indicatorSpacing * i; break; } diff --git a/OLEDDisplayUi.h b/OLEDDisplayUi.h index 35a1e99bcf449c3d2f5f4405f223949005fce497..a5f6c90083de0b8281fca8787eff19446ebde65b 100644 --- a/OLEDDisplayUi.h +++ b/OLEDDisplayUi.h @@ -61,11 +61,11 @@ enum FrameState { }; -const char ANIMATION_activeSymbol[] PROGMEM = { +const uint8_t ANIMATION_activeSymbol[] PROGMEM = { 0x00, 0x18, 0x3c, 0x7e, 0x7e, 0x3c, 0x18, 0x00 }; -const char ANIMATION_inactiveSymbol[] PROGMEM = { +const uint8_t ANIMATION_inactiveSymbol[] PROGMEM = { 0x00, 0x0, 0x0, 0x18, 0x18, 0x0, 0x0, 0x00 }; @@ -106,8 +106,8 @@ class OLEDDisplayUi { IndicatorPosition indicatorPosition = BOTTOM; IndicatorDirection indicatorDirection = LEFT_RIGHT; - const char* activeSymbol = ANIMATION_activeSymbol; - const char* inactiveSymbol = ANIMATION_inactiveSymbol; + const uint8_t* activeSymbol = ANIMATION_activeSymbol; + const uint8_t* inactiveSymbol = ANIMATION_inactiveSymbol; bool shouldDrawIndicators = true; @@ -240,12 +240,12 @@ class OLEDDisplayUi { /** * Set the symbol to indicate an active frame in the indicator bar. */ - void setActiveSymbol(const char* symbol); + void setActiveSymbol(const uint8_t* symbol); /** * Set the symbol to indicate an inactive frame in the indicator bar. */ - void setInactiveSymbol(const char* symbol); + void setInactiveSymbol(const uint8_t* symbol); // Frame settings diff --git a/README.md b/README.md index 01c134c3506041cdd547a76b78b673f1d2659b49..28c11b55ac8c2c5e4ea8e5d58051802ebae25473 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -esp8266-oled-ssd1306 [](https://travis-ci.org/squix78/esp8266-oled-ssd1306) -============ +[](https://travis-ci.org/ThingPulse/esp8266-oled-ssd1306) + +# ESP8266 OLED SSD1306 > We just released version 3.0.0. Please have a look at our [upgrade guide](UPGRADE-3.0.md) @@ -19,7 +20,7 @@ The init sequence for the SSD1306 was inspired by Adafruit's library for the sam ## Usage -Check out the examples folder for a few comprehensive demonstrations how to use the library. Also check out the ESP8266 Weather Station library (https://github.com/squix78/esp8266-weather-station) which uses the OLED library to display beautiful weather information. +Check out the examples folder for a few comprehensive demonstrations how to use the library. Also check out the [ESP8266 Weather Station](https://github.com/ThingPulse/esp8266-weather-station) library which uses the OLED library to display beautiful weather information. ## Upgrade @@ -141,6 +142,9 @@ void setContrast(char contrast); // Turn the display upside down void flipScreenVertically(); + +// Draw the screen mirrored +void mirrorScreen(); ``` ## Pixel drawing diff --git a/SH1106Brzo.h b/SH1106Brzo.h index 385630b3267a7191fe0c81a21fe34d4b5de0a2e4..a747cb68ebe2994cc1ad2a13eedf58335db765ba 100644 --- a/SH1106Brzo.h +++ b/SH1106Brzo.h @@ -44,7 +44,18 @@ class SH1106Brzo : public OLEDDisplay { uint8_t _scl; public: - SH1106Brzo(uint8_t _address, uint8_t _sda, uint8_t _scl) { + SH1106Brzo(OLEDDISPLAY_GEOMETRY g, uint8_t _address, uint8_t _sda, uint8_t _scl) { + this->geometry = g; + if (g == GEOMETRY_128_64) { + this->displayWidth = 128; + this->displayHeight = 64; + this->displayBufferSize = 1024; + } else if (g == GEOMETRY_128_32) { + this->displayWidth = 128; + this->displayHeight = 32; + this->displayBufferSize = 512; + } + this->_address = _address; this->_sda = _sda; this->_scl = _scl; @@ -66,9 +77,9 @@ class SH1106Brzo : public OLEDDisplay { // Calculate the Y bounding box of changes // and copy buffer[pos] to buffer_back[pos]; - for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { - for (x = 0; x < DISPLAY_WIDTH; x++) { - uint16_t pos = x + y * DISPLAY_WIDTH; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; if (buffer[pos] != buffer_back[pos]) { minBoundY = _min(minBoundY, y); maxBoundY = _max(maxBoundY, y); @@ -101,7 +112,7 @@ class SH1106Brzo : public OLEDDisplay { sendCommand(minBoundXp2L); for (x = minBoundX; x <= maxBoundX; x++) { k++; - sendBuffer[k] = buffer[x + y * DISPLAY_WIDTH]; + sendBuffer[k] = buffer[x + y * displayWidth]; if (k == 16) { brzo_i2c_write(sendBuffer, 17, true); k = 0; diff --git a/SH1106Spi.h b/SH1106Spi.h index 0a64e27568ff9fc1270e13ec0eec221ac9337a67..d81d90d38a2a4668ecca1329256d9f0bf22df788 100644 --- a/SH1106Spi.h +++ b/SH1106Spi.h @@ -38,7 +38,18 @@ class SH1106Spi : public OLEDDisplay { public: - SH1106Spi(uint8_t _rst, uint8_t _dc) { + SH1106Spi(OLEDDISPLAY_GEOMETRY g, uint8_t _rst, uint8_t _dc) { + this->geometry = g; + if (g == GEOMETRY_128_64) { + this->displayWidth = 128; + this->displayHeight = 64; + this->displayBufferSize = 1024; + } else if (g == GEOMETRY_128_32) { + this->displayWidth = 128; + this->displayHeight = 32; + this->displayBufferSize = 512; + } + this->_rst = _rst; this->_dc = _dc; } @@ -71,9 +82,9 @@ class SH1106Spi : public OLEDDisplay { // Calculate the Y bounding box of changes // and copy buffer[pos] to buffer_back[pos]; - for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { - for (x = 0; x < DISPLAY_WIDTH; x++) { - uint16_t pos = x + y * DISPLAY_WIDTH; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; if (buffer[pos] != buffer_back[pos]) { minBoundY = _min(minBoundY, y); maxBoundY = _max(maxBoundY, y); @@ -100,18 +111,18 @@ class SH1106Spi : public OLEDDisplay { sendCommand(minBoundXp2L); digitalWrite(_dc, HIGH); // data mode for (x = minBoundX; x <= maxBoundX; x++) { - SPI.transfer(buffer[x + y * DISPLAY_WIDTH]); + SPI.transfer(buffer[x + y * displayWidth]); } yield(); } #else - for (uint8_t y=0; y<DISPLAY_HEIGHT/8; y++) { + for (uint8_t y=0; y<displayHeight/8; y++) { sendCommand(0xB0 + y); sendCommand(0x02); sendCommand(0x10); digitalWrite(_dc, HIGH); // data mode - for( uint8_t x=0; x < DISPLAY_WIDTH; x++) { - SPI.transfer(buffer[x + y * DISPLAY_WIDTH]); + for( uint8_t x=0; x < displayWidth; x++) { + SPI.transfer(buffer[x + y * displayWidth]); } yield(); } diff --git a/SH1106Wire.h b/SH1106Wire.h index f0784b9423051a4e91bf2e237b38347a2f6b6ede..3205ac06ad09ecdff37c63d4564c7a2022786e09 100644 --- a/SH1106Wire.h +++ b/SH1106Wire.h @@ -44,7 +44,18 @@ class SH1106Wire : public OLEDDisplay { uint8_t _scl; public: - SH1106Wire(uint8_t _address, uint8_t _sda, uint8_t _scl) { + SH1106Wire(OLEDDISPLAY_GEOMETRY g, uint8_t _address, uint8_t _sda, uint8_t _scl) { + this->geometry = g; + if (g == GEOMETRY_128_64) { + this->displayWidth = 128; + this->displayHeight = 64; + this->displayBufferSize = 1024; + } else if (g == GEOMETRY_128_32) { + this->displayWidth = 128; + this->displayHeight = 32; + this->displayBufferSize = 512; + } + this->_address = _address; this->_sda = _sda; this->_scl = _scl; @@ -70,9 +81,9 @@ class SH1106Wire : public OLEDDisplay { // Calculate the Y bounding box of changes // and copy buffer[pos] to buffer_back[pos]; - for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { - for (x = 0; x < DISPLAY_WIDTH; x++) { - uint16_t pos = x + y * DISPLAY_WIDTH; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; if (buffer[pos] != buffer_back[pos]) { minBoundY = _min(minBoundY, y); maxBoundY = _max(maxBoundY, y); @@ -103,7 +114,7 @@ class SH1106Wire : public OLEDDisplay { Wire.beginTransmission(_address); Wire.write(0x40); } - Wire.write(buffer[x + y * DISPLAY_WIDTH]); + Wire.write(buffer[x + y * displayWidth]); k++; if (k == 16) { Wire.endTransmission(); diff --git a/SSD1306Brzo.h b/SSD1306Brzo.h index 3b99d82d2a196dbc0836b8e4ad13d7fa46b60813..a9a2aa0cccb0079b727a312c3be97171d88d7e84 100644 --- a/SSD1306Brzo.h +++ b/SSD1306Brzo.h @@ -44,7 +44,18 @@ class SSD1306Brzo : public OLEDDisplay { uint8_t _scl; public: - SSD1306Brzo(uint8_t _address, uint8_t _sda, uint8_t _scl) { + SSD1306Brzo(OLEDDISPLAY_GEOMETRY g, uint8_t _address, uint8_t _sda, uint8_t _scl) { + this->geometry = g; + if (g == GEOMETRY_128_64) { + this->displayWidth = 128; + this->displayHeight = 64; + this->displayBufferSize = 1024; + } else if (g == GEOMETRY_128_32) { + this->displayWidth = 128; + this->displayHeight = 32; + this->displayBufferSize = 512; + } + this->_address = _address; this->_sda = _sda; this->_scl = _scl; @@ -67,9 +78,9 @@ class SSD1306Brzo : public OLEDDisplay { // Calculate the Y bounding box of changes // and copy buffer[pos] to buffer_back[pos]; - for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { - for (x = 0; x < DISPLAY_WIDTH; x++) { - uint16_t pos = x + y * DISPLAY_WIDTH; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; if (buffer[pos] != buffer_back[pos]) { minBoundY = _min(minBoundY, y); maxBoundY = _max(maxBoundY, y); @@ -101,7 +112,7 @@ class SSD1306Brzo : public OLEDDisplay { for (y = minBoundY; y <= maxBoundY; y++) { for (x = minBoundX; x <= maxBoundX; x++) { k++; - sendBuffer[k] = buffer[x + y * DISPLAY_WIDTH]; + sendBuffer[k] = buffer[x + y * displayWidth]; if (k == 16) { brzo_i2c_write(sendBuffer, 17, true); k = 0; @@ -119,12 +130,17 @@ class SSD1306Brzo : public OLEDDisplay { sendCommand(PAGEADDR); sendCommand(0x0); - sendCommand(0x7); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0x7); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x3); + } uint8_t sendBuffer[17]; sendBuffer[0] = 0x40; brzo_i2c_start_transaction(this->_address, BRZO_I2C_SPEED); - for (uint16_t i=0; i<DISPLAY_BUFFER_SIZE; i++) { + for (uint16_t i=0; i<displayBufferSize; i++) { for (uint8_t x=1; x<17; x++) { sendBuffer[x] = buffer[i]; i++; diff --git a/SSD1306Spi.h b/SSD1306Spi.h index 21794587ac612cb951706546dadb2d2bb0c23593..9a41a0a264dbb1bca4a01d4dcf6fb28eb6e28fe4 100644 --- a/SSD1306Spi.h +++ b/SSD1306Spi.h @@ -44,7 +44,18 @@ class SSD1306Spi : public OLEDDisplay { uint8_t _cs; public: - SSD1306Spi(uint8_t _rst, uint8_t _dc, uint8_t _cs) { + SSD1306Spi(OLEDDISPLAY_GEOMETRY g, uint8_t _rst, uint8_t _dc, uint8_t _cs) { + this->geometry = g; + if (g == GEOMETRY_128_64) { + this->displayWidth = 128; + this->displayHeight = 64; + this->displayBufferSize = 1024; + } else if (g == GEOMETRY_128_32) { + this->displayWidth = 128; + this->displayHeight = 32; + this->displayBufferSize = 512; + } + this->_rst = _rst; this->_dc = _dc; this->_cs = _cs; @@ -79,9 +90,9 @@ class SSD1306Spi : public OLEDDisplay { // Calculate the Y bounding box of changes // and copy buffer[pos] to buffer_back[pos]; - for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { - for (x = 0; x < DISPLAY_WIDTH; x++) { - uint16_t pos = x + y * DISPLAY_WIDTH; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; if (buffer[pos] != buffer_back[pos]) { minBoundY = _min(minBoundY, y); maxBoundY = _max(maxBoundY, y); @@ -111,7 +122,7 @@ class SSD1306Spi : public OLEDDisplay { digitalWrite(_cs, LOW); for (y = minBoundY; y <= maxBoundY; y++) { for (x = minBoundX; x <= maxBoundX; x++) { - SPI.transfer(buffer[x + y * DISPLAY_WIDTH]); + SPI.transfer(buffer[x + y * displayWidth]); } yield(); } @@ -124,12 +135,17 @@ class SSD1306Spi : public OLEDDisplay { sendCommand(PAGEADDR); sendCommand(0x0); - sendCommand(0x7); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0x7); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x3); + } digitalWrite(_cs, HIGH); digitalWrite(_dc, HIGH); // data mode digitalWrite(_cs, LOW); - for (uint16_t i=0; i<DISPLAY_BUFFER_SIZE; i++) { + for (uint16_t i=0; i<displayBufferSize; i++) { SPI.transfer(buffer[i]); yield(); } diff --git a/SSD1306Wire.h b/SSD1306Wire.h index d558da844e2dc7d1e9749031e708d7a7e1fb5d39..2c92327e00e0379e6675609e211c8ca52952c870 100644 --- a/SSD1306Wire.h +++ b/SSD1306Wire.h @@ -38,7 +38,18 @@ class SSD1306Wire : public OLEDDisplay { uint8_t _scl; public: - SSD1306Wire(uint8_t _address, uint8_t _sda, uint8_t _scl) { + SSD1306Wire(OLEDDISPLAY_GEOMETRY g, uint8_t _address, uint8_t _sda, uint8_t _scl) { + this->geometry = g; + if (g == GEOMETRY_128_64) { + this->displayWidth = 128; + this->displayHeight = 64; + this->displayBufferSize = 1024; + } else if (g == GEOMETRY_128_32) { + this->displayWidth = 128; + this->displayHeight = 32; + this->displayBufferSize = 512; + } + this->_address = _address; this->_sda = _sda; this->_scl = _scl; @@ -63,9 +74,9 @@ class SSD1306Wire : public OLEDDisplay { // Calculate the Y bounding box of changes // and copy buffer[pos] to buffer_back[pos]; - for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { - for (x = 0; x < DISPLAY_WIDTH; x++) { - uint16_t pos = x + y * DISPLAY_WIDTH; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; if (buffer[pos] != buffer_back[pos]) { minBoundY = _min(minBoundY, y); maxBoundY = _max(maxBoundY, y); @@ -97,7 +108,7 @@ class SSD1306Wire : public OLEDDisplay { Wire.beginTransmission(_address); Wire.write(0x40); } - Wire.write(buffer[x + y * DISPLAY_WIDTH]); + Wire.write(buffer[x + y * displayWidth]); k++; if (k == 16) { Wire.endTransmission(); @@ -118,9 +129,14 @@ class SSD1306Wire : public OLEDDisplay { sendCommand(PAGEADDR); sendCommand(0x0); - sendCommand(0x7); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0x7); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x3); + } - for (uint16_t i=0; i < DISPLAY_BUFFER_SIZE; i++) { + for (uint16_t i=0; i < displayBufferSize; i++) { Wire.beginTransmission(this->_address); Wire.write(0x40); for (uint8_t x = 0; x < 16; x++) { diff --git a/examples/SSD1306ClockDemo/SSD1306ClockDemo.ino b/examples/SSD1306ClockDemo/SSD1306ClockDemo.ino index e9db7d645a610f8c42d857ba20a774b2fe9cd89e..1f1a8c968575c8c5b9ee7e2a8e6aa6afe7dca68b 100644 --- a/examples/SSD1306ClockDemo/SSD1306ClockDemo.ino +++ b/examples/SSD1306ClockDemo/SSD1306ClockDemo.ino @@ -66,7 +66,7 @@ // SH1106Brzo display(0x3c, D3, D5); // Initialize the OLED display using Wire library -SSD1306 display(0x3c, D3, D5); +SSD1306 display(GEOMETRY_128_64, 0x3c, D3, D5); // SH1106 display(0x3c, D3, D5); OLEDDisplayUi ui ( &display ); diff --git a/examples/SSD1306ClockDemo/images.h b/examples/SSD1306ClockDemo/images.h index a220a27c2b6637946f5df26f500f0eb88998a2c8..1889188fd489e49ef1a6dd8ce6a7ffaa4f956be0 100644 --- a/examples/SSD1306ClockDemo/images.h +++ b/examples/SSD1306ClockDemo/images.h @@ -1,4 +1,4 @@ -const char activeSymbol[] PROGMEM = { +const unsigned char activeSymbol[] PROGMEM = { B00000000, B00000000, B00011000, @@ -9,7 +9,7 @@ const char activeSymbol[] PROGMEM = { B00011000 }; -const char inactiveSymbol[] PROGMEM = { +const unsigned char inactiveSymbol[] PROGMEM = { B00000000, B00000000, B00000000, diff --git a/examples/SSD1306DrawingDemo/SSD1306DrawingDemo.ino b/examples/SSD1306DrawingDemo/SSD1306DrawingDemo.ino index cf37fb0412002492388ed3d71fbb1ec58866c233..3f6e0a4f1c90ee597abf6faf213980e29a088368 100644 --- a/examples/SSD1306DrawingDemo/SSD1306DrawingDemo.ino +++ b/examples/SSD1306DrawingDemo/SSD1306DrawingDemo.ino @@ -58,56 +58,56 @@ // SH1106Brzo display(0x3c, D3, D5); // Initialize the OLED display using Wire library - SSD1306 display(0x3c, D3, D5); + SSD1306 display(GEOMETRY_128_64, 0x3c, D3, D5); // SH1106 display(0x3c, D3, D5); // Adapted from Adafruit_SSD1306 void drawLines() { - for (int16_t i=0; i<DISPLAY_WIDTH; i+=4) { - display.drawLine(0, 0, i, DISPLAY_HEIGHT-1); + for (int16_t i=0; i<display.getWidth(); i+=4) { + display.drawLine(0, 0, i, display.getHeight()-1); display.display(); delay(10); } - for (int16_t i=0; i<DISPLAY_HEIGHT; i+=4) { - display.drawLine(0, 0, DISPLAY_WIDTH-1, i); + for (int16_t i=0; i<display.getHeight(); i+=4) { + display.drawLine(0, 0, display.getWidth()-1, i); display.display(); delay(10); } delay(250); display.clear(); - for (int16_t i=0; i<DISPLAY_WIDTH; i+=4) { - display.drawLine(0, DISPLAY_HEIGHT-1, i, 0); + for (int16_t i=0; i<display.getWidth(); i+=4) { + display.drawLine(0, display.getHeight()-1, i, 0); display.display(); delay(10); } - for (int16_t i=DISPLAY_HEIGHT-1; i>=0; i-=4) { - display.drawLine(0, DISPLAY_HEIGHT-1, DISPLAY_WIDTH-1, i); + for (int16_t i=display.getHeight()-1; i>=0; i-=4) { + display.drawLine(0, display.getHeight()-1, display.getWidth()-1, i); display.display(); delay(10); } delay(250); display.clear(); - for (int16_t i=DISPLAY_WIDTH-1; i>=0; i-=4) { - display.drawLine(DISPLAY_WIDTH-1, DISPLAY_HEIGHT-1, i, 0); + for (int16_t i=display.getWidth()-1; i>=0; i-=4) { + display.drawLine(display.getWidth()-1, display.getHeight()-1, i, 0); display.display(); delay(10); } - for (int16_t i=DISPLAY_HEIGHT-1; i>=0; i-=4) { - display.drawLine(DISPLAY_WIDTH-1, DISPLAY_HEIGHT-1, 0, i); + for (int16_t i=display.getHeight()-1; i>=0; i-=4) { + display.drawLine(display.getWidth()-1, display.getHeight()-1, 0, i); display.display(); delay(10); } delay(250); display.clear(); - for (int16_t i=0; i<DISPLAY_HEIGHT; i+=4) { - display.drawLine(DISPLAY_WIDTH-1, 0, 0, i); + for (int16_t i=0; i<display.getHeight(); i+=4) { + display.drawLine(display.getWidth()-1, 0, 0, i); display.display(); delay(10); } - for (int16_t i=0; i<DISPLAY_WIDTH; i+=4) { - display.drawLine(DISPLAY_WIDTH-1, 0, i, DISPLAY_HEIGHT-1); + for (int16_t i=0; i<display.getWidth(); i+=4) { + display.drawLine(display.getWidth()-1, 0, i, display.getHeight()-1); display.display(); delay(10); } @@ -116,8 +116,8 @@ void drawLines() { // Adapted from Adafruit_SSD1306 void drawRect(void) { - for (int16_t i=0; i<DISPLAY_HEIGHT/2; i+=2) { - display.drawRect(i, i, DISPLAY_WIDTH-2*i, DISPLAY_HEIGHT-2*i); + for (int16_t i=0; i<display.getHeight()/2; i+=2) { + display.drawRect(i, i, display.getWidth()-2*i, display.getHeight()-2*i); display.display(); delay(10); } @@ -126,9 +126,9 @@ void drawRect(void) { // Adapted from Adafruit_SSD1306 void fillRect(void) { uint8_t color = 1; - for (int16_t i=0; i<DISPLAY_HEIGHT/2; i+=3) { + for (int16_t i=0; i<display.getHeight()/2; i+=3) { display.setColor((color % 2 == 0) ? BLACK : WHITE); // alternate colors - display.fillRect(i, i, DISPLAY_WIDTH - i*2, DISPLAY_HEIGHT - i*2); + display.fillRect(i, i, display.getWidth() - i*2, display.getHeight() - i*2); display.display(); delay(10); color++; @@ -139,8 +139,8 @@ void fillRect(void) { // Adapted from Adafruit_SSD1306 void drawCircle(void) { - for (int16_t i=0; i<DISPLAY_HEIGHT; i+=2) { - display.drawCircle(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, i); + for (int16_t i=0; i<display.getHeight(); i+=2) { + display.drawCircle(display.getWidth()/2, display.getHeight()/2, i); display.display(); delay(10); } @@ -153,16 +153,16 @@ void drawCircle(void) { // ------|----- // 0100 | 1000 // - display.drawCircleQuads(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, DISPLAY_HEIGHT/4, 0b00000001); + display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00000001); display.display(); delay(200); - display.drawCircleQuads(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, DISPLAY_HEIGHT/4, 0b00000011); + display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00000011); display.display(); delay(200); - display.drawCircleQuads(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, DISPLAY_HEIGHT/4, 0b00000111); + display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00000111); display.display(); delay(200); - display.drawCircleQuads(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, DISPLAY_HEIGHT/4, 0b00001111); + display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00001111); display.display(); } diff --git a/examples/SSD1306OTADemo/SSD1306OTADemo.ino b/examples/SSD1306OTADemo/SSD1306OTADemo.ino index 6460ff9e4a308dffa396d0db473c6708e0c14f34..856917a958d22b8fc5f2ee1e8a5286b40a8fe95e 100644 --- a/examples/SSD1306OTADemo/SSD1306OTADemo.ino +++ b/examples/SSD1306OTADemo/SSD1306OTADemo.ino @@ -69,7 +69,7 @@ // SH1106Brzo display(0x3c, D3, D5); // Initialize the OLED display using Wire library -SSD1306 display(0x3c, D3, D5); +SSD1306 display(GEOMETRY_128_64, 0x3c, D3, D5); // SH1106 display(0x3c, D3, D5); @@ -90,7 +90,7 @@ void setup() { display.clear(); display.setFont(ArialMT_Plain_10); display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); - display.drawString(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2 - 10, "OTA Update"); + display.drawString(display.getWidth()/2, display.getHeight()/2 - 10, "OTA Update"); display.display(); }); @@ -103,14 +103,14 @@ void setup() { display.clear(); display.setFont(ArialMT_Plain_10); display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); - display.drawString(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, "Restart"); + display.drawString(display.getWidth()/2, display.getHeight()/2, "Restart"); display.display(); }); // Align text vertical/horizontal center display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); display.setFont(ArialMT_Plain_10); - display.drawString(DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2, "Ready for OTA:\n" + WiFi.localIP().toString()); + display.drawString(display.getWidth()/2, display.getHeight()/2, "Ready for OTA:\n" + WiFi.localIP().toString()); display.display(); } diff --git a/examples/SSD1306SimpleDemo/SSD1306SimpleDemo.ino b/examples/SSD1306SimpleDemo/SSD1306SimpleDemo.ino index ed7401915861f89d9a1f4dd6f697bc82b44f3069..12ad6b111c57830117422b76bc9e28d86141cebd 100644 --- a/examples/SSD1306SimpleDemo/SSD1306SimpleDemo.ino +++ b/examples/SSD1306SimpleDemo/SSD1306SimpleDemo.ino @@ -58,7 +58,7 @@ // SH1106Brzo display(0x3c, D3, D5); // Initialize the OLED display using Wire library -SSD1306 display(0x3c, D3, D5); +SSD1306 display(GEOMETRY_128_64, 0x3c, D3, D5); // SH1106 display(0x3c, D3, D5); diff --git a/examples/SSD1306SimpleDemo/images.h b/examples/SSD1306SimpleDemo/images.h index 9daf8c1a25b9d006fc67f49c6868563d13c7ae77..50417990fd0d3d0854b4385afac1334d45223214 100644 --- a/examples/SSD1306SimpleDemo/images.h +++ b/examples/SSD1306SimpleDemo/images.h @@ -1,6 +1,6 @@ #define WiFi_Logo_width 60 #define WiFi_Logo_height 36 -const char WiFi_Logo_bits[] PROGMEM = { +const uint8_t WiFi_Logo_bits[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, diff --git a/examples/SSD1306UiDemo/SSD1306UiDemo.ino b/examples/SSD1306UiDemo/SSD1306UiDemo.ino index 31357569ff3849b6c1098165aa96f9880129327f..f19617af86301391786b15bbc80b23080556d865 100644 --- a/examples/SSD1306UiDemo/SSD1306UiDemo.ino +++ b/examples/SSD1306UiDemo/SSD1306UiDemo.ino @@ -64,7 +64,7 @@ // SH1106Brzo display(0x3c, D3, D5); // Initialize the OLED display using Wire library -SSD1306 display(0x3c, D3, D5); +SSD1306 display(GEOMETRY_128_64, 0x3c, D3, D5); // SH1106 display(0x3c, D3, D5); OLEDDisplayUi ui ( &display ); diff --git a/examples/SSD1306UiDemo/images.h b/examples/SSD1306UiDemo/images.h index 8b876a369e6f2609700798da9da47431e5e0db21..2489d1e5de658663f1bf16b456b18743193cf1c0 100644 --- a/examples/SSD1306UiDemo/images.h +++ b/examples/SSD1306UiDemo/images.h @@ -1,6 +1,6 @@ #define WiFi_Logo_width 60 #define WiFi_Logo_height 36 -const char WiFi_Logo_bits[] PROGMEM = { +const uint8_t WiFi_Logo_bits[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, @@ -27,7 +27,7 @@ const char WiFi_Logo_bits[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -const char activeSymbol[] PROGMEM = { +const uint8_t activeSymbol[] PROGMEM = { B00000000, B00000000, B00011000, @@ -38,7 +38,7 @@ const char activeSymbol[] PROGMEM = { B00011000 }; -const char inactiveSymbol[] PROGMEM = { +const uint8_t inactiveSymbol[] PROGMEM = { B00000000, B00000000, B00000000, diff --git a/resources/glyphEditor.html b/resources/glyphEditor.html new file mode 100644 index 0000000000000000000000000000000000000000..f253a807b0cd971b2493b7469fe1a2a50c737be5 --- /dev/null +++ b/resources/glyphEditor.html @@ -0,0 +1,633 @@ +<!--The MIT License (MIT) + +Copyright (c) 2017 by Xavier Grosjean + +Based on work +Copyright (c) 2016 by Daniel Eichhorn +Copyright (c) 2016 by Fabrice Weinberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--> +<!-- + Standalone page to draw icons in matrix and generates the map definition with the format required by + library for OLED SD1306 screen: https://github.com/squix78/esp8266-oled-ssd1306 + 100% quick and dirty vanilla ECS6, no framework or library was injured for this project. + +--> +<html> + <head> + <meta charset="utf-8" /> + <style> + #form, #chars table { + user-select: none; + -moz-user-select: none; + margin-top: 5px; + } + #chars table, tr, td, th { + border-collapse: collapse; + } + #chars td, th { + border: 1px solid #CCC; + width: 12px; + height: 15px; + } + #chars th[action] { + cursor:pointer; + } + #chars th[action="up"], #chars th[action="down"], #chars th[action="left"], #chars th[action="right"] { + font-size: 10px; + } + #chars td.on { + background-color: #00F; + } + + #addChar, #generate { + display: none; + } + body.started #addChar, body.started #generate { + display: inline; + } + body.started #create { + display: none; + } + #page { + position:relative; + } + #page>div { + position: relative; + float: left; + } + + #output { + margin-left: 20px; + margin-top: 12px; + font-size:12px; + user-select: all; + -moz-user-select: all; + max-width:60%; + } + + #header { + margin-top: 10px; + } + #inputText { + width: 100%; + height:120px; + } + + </style> + </head> + <body> + <div id="form"> + <table width="100%"> + <tr> + <td>Font array name:</td><td><input placeholder="Font array name" type="text" id="name" value="My_Font"/></td> + <td width="75%" rowspan="5"> + <textarea id="inputText" placeholder="Or paste a char array font definition here"> +const char My_Font[] PROGMEM = { + 0x0A, // Width: 10 + 0x0D, // Height: 13 + 0x01, // First char: 1 + 0x02, // Number of chars: 2 + // Jump Table: + 0x00, 0x00, 0x13, 0x0A, // 1 + 0x00, 0x13, 0x14, 0x0A, // 2 + // Font Data: + 0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0xC0, // 1 + 0x00, 0x0C, 0x80, 0x0B, 0x70, 0x08, 0x0C, 0x08, 0xF3, 0x0A, 0xF3, 0x0A, 0x0C, 0x08, 0x70, 0x08, 0x80, 0x0B, 0x00, 0x0C, // 2 +}; + </textarea></td> + </tr> + <tr> + <td>First char code:</td><td><input placeholder="First char code" type="number" id="code" value="1" min="1"/></td> + </tr> + <tr> + <td>Width:</td><td><input placeholder="Font width" type="number" id="width" value="10"/></td> + </tr> + <tr> + <td>Height:</td><td><input placeholder="Font height" type="number" id="height" value="13" max="64"/></td> + </tr> + <tr> + <td colspan="3"> </td> <!--slightly improves layout--> + </tr> + <tr> + <td colspan="2"><button id="create">Create</button> <button id="generate">Generate</button> <button id="savetoFile">Save To File</button> </td> + <td><input type="file" id="fileinput" /> <button id="parse">Parse</button></td> + </tr> + </table> + <br/> + </div> + <div id="page"> + <div id="charxContainer" ondragstart="return false;"> + <div id="chars"></div><br/> + <button id="addChar">Add a character</button> + </div> + <div id="output"> + <div class="output" id="header"> </div> + <div class="output" id="jump"> </div> + <div class="output" id="data"> </div> + </div> + </div> + <script> + (function() { + let font; + + class Font { + constructor() { + this.height = parseInt(document.getElementById('height').value); + this.width = parseInt(document.getElementById('width').value); + this.currentCharCode = parseInt(document.getElementById('code').value); + this.fontContainer = document.getElementById('chars'); + this.bytesForHeight = (1 + ((this.height - 1) >> 3)); // number of bytes needed for this font height + document.body.className = "started"; + document.getElementById('height').disabled = true; + document.getElementById('width').disabled = true; + } + + // Should we have a Char and a Pixel class ? not sure it's worth it. + // Let's use the DOM to store everything for now + + static updateCaption(element, code) { + element.textContent = `Char #${code}`; + } + + // Add a pixel matrix to draw a new character + // jumpaData not used for now: some day, use the last byte for optimisation + addChar(jumpData, charData) { + let charContainer = this.fontContainer.appendChild(document.createElement("table")); + charContainer.setAttribute("code", this.currentCharCode); + let caption = charContainer.appendChild(document.createElement("caption")); + Font.updateCaption(caption, this.currentCharCode); + let header = charContainer.appendChild(document.createElement("tr")); + header.innerHTML = '<th title="Delete this char" action="delete">✗</th>' + + '<th title="Add a char above" action="add">+</th>' + + '<th title="Shift pixels to the left" action="left">←</th>' + + '<th title="Shift pixels down" action="down">↓</th>' + + '<th title="Shift pixels up" action="up">↑</th>' + + '<th title="Shift pixels to the right" action="right">→</th>' + + '<th title="Copy from another character" action="copy">©</th>' + + '<th title="Reset all pixels" action="clean">∅</th>'; + // If data is provided, decode it to pixel initialization friendly structure + let pixelInit = []; + if (charData && charData.length) { + // charData byte count needs to be a multiple of bytesForHeight. End bytes with value 0 may have been trimmed + let missingBytes = charData.length % this.bytesForHeight; + for(let b = 0; b < missingBytes ; b++) { + charData.push(0); + } + while(charData.length) { + let row = charData.splice(0, this.bytesForHeight).reverse(); + let pixelRow = []; + for (let b = 0; b < row.length; b++) { + let mask = 0x80; + let byte = row[b]; + for (let bit = 0; bit < 8; bit++) { + if (byte & mask) { + pixelRow.push(1); + } else { + pixelRow.push(0); + } + mask = mask >> 1; + } + } + pixelRow.splice(0, pixelRow.length - this.height); + //Font.output('data', pixelRow); + pixelInit.push(pixelRow.reverse()); + } + } + + for(let r = 0; r < this.height; r++) { + let rowContainer = charContainer.appendChild(document.createElement("tr")); + for(let c = 0; c < this.width; c++) { + let pixContainer = rowContainer.appendChild(document.createElement("td")); + pixContainer.setAttribute('action', 'toggle'); + // If there is some init data, set the pixel accordingly + if (pixelInit.length && pixelInit[c]) { + if (pixelInit[c][r]) { + pixContainer.className = 'on'; + } + } + } + } + this.currentCharCode++; + return charContainer; + } + + static togglePixel(pixel) { + pixel.className = pixel.className === 'on' ? '': 'on'; + } + + // Return anInt as hex string + static toHexString(aByte) { + let zero = aByte < 16?'0':''; + return `0x${zero}${aByte.toString(16).toUpperCase()}` + } + + // Return least significant byte as hex string + static getLsB(anInt) { + return Font.toHexString(anInt & 0xFF); + } + // Return most significant byte as hex string + static getMsB(anInt) { + return Font.toHexString(anInt>>>8); + } + + static output(targetId, msg) { + let output = document.getElementById(targetId); + let line = output.appendChild(document.createElement('div')); + line.textContent = msg; + } + static emptyChars() { + document.getElementById('chars').textContent = ''; + } + static emptyOutput() { + document.getElementById('header').textContent = ''; + document.getElementById('jump').textContent = ''; + document.getElementById('data').textContent = ''; + } + + saveFile() { + let filename = document.getElementById('name').value.replace(/[^a-zA-Z0-9_$]/g, '_') + ".h"; + let data = document.getElementById("output").innerText; + if(data.length < 10) return; + let blobObject = new Blob([data], {type:'text/plain'}); + + if(window.navigator.msSaveBlob) { + window.navigator.msSaveBlob(blobObject, filename); + } else { + let a = document.createElement("a"); + a.setAttribute("href", URL.createObjectURL(blobObject)); + a.setAttribute("download", filename); + if (document.createEvent) { + let event = document.createEvent('MouseEvents'); + event.initEvent('click', true, true); + a.dispatchEvent(event); + } else { + a.click(); + } + } + } + + // Read from the <td> css class the pixels that need to be on + // generates the jump table and font data + generate() { + Font.emptyOutput(); + let chars = this.fontContainer.getElementsByTagName('table'); + let firstCharCode = parseInt(document.getElementById('code').value); + let name = document.getElementById('name').value.replace(/[^a-zA-Z0-9_$]/g, '_'); + + let bits2add = this.bytesForHeight*8 - this.height; // number of missing bits to fill leftmost byte + let charCount = chars.length; + let charAddr = 0; + // Comments are used when parsing back a generated font + Font.output('jump', ' // Jump Table:'); + Font.output('data', ' // Font Data:'); + // Browse each character + for(let ch = 0; ch < charCount; ch++) { + // Fix renumbering in case first char code was modified + Font.updateCaption(chars[ch].getElementsByTagName('caption')[0], ch + firstCharCode); + let charBytes = []; + let charCode = ch + firstCharCode; + let rows = chars[ch].getElementsByTagName('tr'); + let notZero = false; + // Browse each column + for(let col = 0; col < this.width ; col++) { + let bits = ""; // using string because js uses 32b ints when performing bit operations + // Browse each row starting from the bottom one, going up, and accumulate pixels in + // a string: this rotates the glyph + // Beware, row 0 is table headers. + for(let r = rows.length-1; r >=1 ; r--) { + let pixelState = rows[r].children[col].className; + bits += (pixelState === 'on' ? '1': '0'); + } + // Need to complete missing bits to have a sizeof byte multiple number of bits + for(let b = 0; b < bits2add; b++) { + bits = '0' + bits; + } + // Font.output('data', ` // ${bits}`); // Debugging help: rotated bitmap + + // read bytes from the end + for(let b = bits.length - 1; b >= 7; b -= 8) { + //Font.output('data', ` // ${bits.substr(b-7, 8)}`); // Debugging help: rotated bitmap + let byte = parseInt(bits.substr(b-7, 8), 2); + if (byte !== 0) { + notZero = true; + } + charBytes.push(Font.toHexString(byte)); + } + } + // Remove bytes with value 0 at the end of the array. + while(parseInt(charBytes[charBytes.length-1]) === 0 && charBytes.length !== 1) { + charBytes.pop(); + } + + if (notZero) { + Font.output('data', ` ${charBytes.join(', ')}, // ${charCode}`); + // TODO: last param width is not the best value. Need to compute the actual occupied width + Font.output('jump', ` ${Font.getMsB(charAddr)}, ${Font.getLsB(charAddr)}, ${Font.toHexString(charBytes.length)}, ${Font.toHexString(this.width)}, // ${charCode} `); + charAddr += charBytes.length; + } else { + Font.output('jump', ` 0xFF, 0xFF, 0x00, ${Font.toHexString(this.width)}, // ${charCode} `); + } + } + Font.output('data', '};'); + + Font.output('header', "// Font generated or edited with the glyphEditor"); + Font.output('header', `const char ${name}[] PROGMEM = {`); + // Comments are used when parsing back a generated font + Font.output('header', ` ${Font.toHexString(this.width)}, // Width: ${this.width}`); + Font.output('header', ` ${Font.toHexString(this.height)}, // Height: ${this.height}`); + Font.output('header', ` ${Font.toHexString(firstCharCode)}, // First char: ${firstCharCode}`); + Font.output('header', ` ${Font.toHexString(charCount)}, // Number of chars: ${charCount}`); + } + } + + document.getElementById('fileinput').addEventListener('change', function(e) { + let f = e.target.files[0]; + if (f) { + let r = new FileReader(); + r.onload = function(e) { + let contents = e.target.result; + alert( "Got the file.\n" + +"name: " + f.name + "\n" + +"type: " + f.type + "\n" + +"size: " + f.size + " bytes\n" + +"starts with: " + contents.substr(0, contents.indexOf("\n")) + ); + document.getElementById("inputText").value = contents; + }; + r.readAsText(f); + } else { + alert("Failed to load file"); + } + }); + + document.getElementById('savetoFile').addEventListener('click', function() { + font.saveFile(); + }); + + document.getElementById('generate').addEventListener('click', function() { + font.generate(); + }); + document.getElementById('addChar').addEventListener('click', function() { + font.addChar(); + }); + document.getElementById('inputText').addEventListener('click', function(e) { + let target = e.target; + target.select(); + }); + document.getElementById('chars').addEventListener('mousedown', function(e) { + if (e.button !== 0) return; + let target = e.target; + let action = target.getAttribute('action') || ''; + if (action === '') return; + let result, code, nextContainer, previousContainer, pixels ; + let currentContainer = target.parentNode.parentNode; + switch(action) { + case 'add': + code = currentContainer.getAttribute('code'); + nextContainer = font.addChar(); + currentContainer.parentNode.insertBefore(nextContainer, currentContainer); + do { + nextContainer.setAttribute('code', code); + Font.updateCaption(nextContainer.getElementsByTagName('caption')[0], code); + code ++; + } while (nextContainer = nextContainer.nextSibling); + break; + + case 'delete': + result = confirm("Delete this character ?"); + if (!result) return; + code = currentContainer.getAttribute('code'); + nextContainer = currentContainer; + while (nextContainer = nextContainer.nextSibling) { + nextContainer.setAttribute('code', code); + Font.updateCaption(nextContainer.getElementsByTagName('caption')[0], code); + code ++; + } + currentContainer.parentNode.removeChild(currentContainer); + break; + + // Shift pixels to the left + case 'left': + pixels = currentContainer.getElementsByTagName('td'); + for(p = 0; p < pixels.length; p++) { + if((p + 1) % font.width) { + pixels[p].className = pixels[p + 1].className; + } else { + pixels[p].className = ''; + } + } + break; + + // Shift pixels to the right + case 'right': + pixels = currentContainer.getElementsByTagName('td'); + for(p = pixels.length-1; p >=0 ; p--) { + if(p % font.width) { + pixels[p].className = pixels[p - 1].className; + } else { + pixels[p].className = ''; + } + } + break; + + // Shift pixels down + case 'down': + pixels = currentContainer.getElementsByTagName('td'); + for(p = pixels.length-1; p >=0 ; p--) { + if(p >= font.width) { + pixels[p].className = pixels[p - font.width].className; + } else { + pixels[p].className = ''; + } + } break; + + // Shift pixels up + case 'up': + pixels = currentContainer.getElementsByTagName('td'); + for(p = 0; p < pixels.length; p++) { + if(p < font.width*(font.height -1)) { + pixels[p].className = pixels[p + font.width].className; + } else { + pixels[p].className = ''; + } + } + break; + + case 'toggle': + Font.togglePixel(target); + break; + + case 'clean': + result = confirm("Delete the pixels ?"); + if (!result) return; + pixels = currentContainer.getElementsByTagName('td'); + for (let p = 0; p < pixels.length; p++) { + pixels[p].className = ''; + } + break; + + case 'copy': + let charNumber = parseInt(prompt("Source char #: ")); + let chars = font.fontContainer.getElementsByTagName('table'); + let tableOffset = charNumber - parseInt(document.getElementById('code').value); + let srcPixels = chars[tableOffset].getElementsByTagName('td'); + let targetPixels = currentContainer.getElementsByTagName('td'); + for(let i=0; i < srcPixels.length; i++) { + // First tds are in the th row, for editing actions. Skip them + if (targetPixels[i].parentNode.localName === 'th') continue; // In case we give them css at some point... + targetPixels[i].className = srcPixels[i].className; + } + break; + default: + // no. + } + + }); + document.getElementById('chars').addEventListener('mouseover', function(e) { + let target = e.target; + let action = target.getAttribute('action'); + if (action !== 'toggle' || e.buttons !== 1) return; + Font.togglePixel(target); + }); + document.getElementById('chars').addEventListener('dragstart', function() { + return false; + }); + + document.getElementById('create').addEventListener('click', function() { + font = new Font(); + font.addChar(); + }); + + // parse a char array declaration for an existing font. + // parsing heavily relies on comments. + document.getElementById('parse').addEventListener('click', function() { + if (document.getElementById('chars').childElementCount) { + let result = confirm("Confirm you want to overwrite the existing grids ?"); + if (!result) return; + } + let lines = document.getElementById('inputText').value.split('\n'); + let name = ''; + let height = 0; + let width = 0; + let firstCharCode = 0; + let jumpTable = []; + let charData = []; + let readingJumpTable = false; + let readingData = false; + + for(let l = 0 ; l < lines.length; l++) { + // TODO ? keep C compilation directives to copy them (not lowercased :)) to newly generated char array + let line = lines[l].trim(); + //alert(line); + let fields; + + // Declaration line: grab the name + if (line.indexOf('PROGMEM') > 0) { + fields = line.split(' '); + name = fields[2].replace(/[\[\]]/g, ''); + continue; + } + line = line.toLowerCase(); + // Todo: could rely on line order... + // width line: grab the width + if (line.indexOf('width') > 0) { + fields = line.split(','); + width = fields[0]; + continue; + } + // height line: grab the height + if (line.indexOf('height') > 0) { + fields = line.split(','); + height = fields[0]; + continue; + } + // first char code line: grab the first char code + if (line.indexOf('first char') > 0) { + fields = line.split(','); + firstCharCode = fields[0]; + continue; + } + // End of array declaration + // TODO warn if more lines: next fonts are ignored + if (line.indexOf('};') === 0) { + break; + } + + if (readingJumpTable || readingData) { + if (line.indexOf('#') !== 0 && line.length !== 0 && line.indexOf('//') !== 0) { + line = line.replace(/\/\/.*/, ''); // get rid of end of line comments + fields = line.split(','); + let newEntry = []; + for(let f=0; f < fields.length; f++) { + let value = parseInt(fields[f]); + if (isNaN(value)) continue; + if (readingData) { + charData.push(value); + } + if (readingJumpTable) { + newEntry.push(value); + } + } + if (readingJumpTable) { + jumpTable.push(newEntry); + } + } + } + + // Begining of data + if (line.indexOf('font data') > 0) { + readingData = true; + readingJumpTable = false; + } + // Begining of jump table + if (line.indexOf('jump table') > 0) { + readingJumpTable = true; + } + } + if (!name || !height || !width || !firstCharCode) { + alert("Does not look like a parsable font. Try again."); + return; + } + + Font.emptyChars(); + Font.emptyOutput(); + document.getElementById('name').value = name; + document.getElementById('height').value = parseInt(height); + document.getElementById('width').value = parseInt(width); + document.getElementById('code').value = parseInt(firstCharCode); + font = new Font(); + for(let c = 0 ; c < jumpTable.length; c++) { + let jumpEntry = jumpTable[c]; + let charEntry = []; + // displayable character + if (jumpEntry[0] !== 255 || jumpEntry[1] !== 255) { + charEntry = charData.splice(0, jumpEntry[2]); + } + font.addChar(jumpEntry, charEntry); + } + document.body.className = "started"; + }); + + })(); + </script> + </body> +</html> diff --git a/resources/glyphEditor.png b/resources/glyphEditor.png new file mode 100644 index 0000000000000000000000000000000000000000..800efc56f4bd50ba347eab57f3205ec8d0c0dd31 Binary files /dev/null and b/resources/glyphEditor.png differ