Louis Mayencourt
/
NRFBOY
MBED NRF51 Arduboy port
Diff: abstractarduboy.cpp
- Revision:
- 1:c53e766082b4
- Child:
- 2:e3ef9f476913
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/abstractarduboy.cpp Fri Jan 06 22:10:20 2017 +0000 @@ -0,0 +1,674 @@ +#include "abstractarduboy.h" + +#define _BV(b) (1UL << (b)) +#define min(a,b) (((a)<(b))?(a):(b)) +#define max(a,b) (((a)>(b))?(a):(b)) +#define abs(a) (((a) < 0) ? -(a) : (a)) +#define pgm_read_byte(a) 1 + +AbstractArduboy::AbstractArduboy() +{ + frameRate = 60; + frameCount = 0; + eachFrameMillis = 1000 / 60; + lastFrameStart = 0; + nextFrameStart = 0; + post_render = false; + lastFrameDurationMs = 0; + + currentButtonState = 0; + previousButtonState = 0; + + textcolor = WHITE; + textbgcolor = BLACK; + textsize = 1; +} + +//////////////////////////////////////////////// +// default virtual function +//////////////////////////////////////////////// +void AbstractArduboy::idle() +{} + +void AbstractArduboy::saveMuchPower() +{} + +//////////////////////////////////////////////// +// frame management +//////////////////////////////////////////////// +void AbstractArduboy::setFrameRate(uint8_t rate) +{ + frameRate = rate; + eachFrameMillis = 1000 / rate; +} + +bool AbstractArduboy::everyXFrames(uint8_t frames) +{ + return frameCount % frames == 0; +} + +bool AbstractArduboy::nextFrame() +{ + long now = getTime(); + uint8_t remaining; + + // post render + if (post_render) { + lastFrameDurationMs = now - lastFrameStart; + frameCount++; + post_render = false; + } + + // if it's not time for the next frame yet + if (now < nextFrameStart) { + remaining = nextFrameStart - now; + // if we have more than 1ms to spare, lets sleep + // we should be woken up by timer0 every 1ms, so this should be ok + if (remaining > 1) + idle(); + return false; + } + + // pre-render + + // technically next frame should be last frame + each frame but if we're + // running a slow render we would constnatly be behind the clock + // keep an eye on this and see how it works. If it works well the + // lastFrameStart variable could be eliminated completely + nextFrameStart = now + eachFrameMillis; + lastFrameStart = now; + post_render = true; + return post_render; +} + +//////////////////////////////////////////////// +// info +//////////////////////////////////////////////// +// returns the load on the CPU as a percentage +// this is based on how much of the time your app is spends rendering +// frames. This number can be higher than 100 if your app is rendering +// really slowly. +int AbstractArduboy::cpuLoad() +{ + return lastFrameDurationMs * 100 / eachFrameMillis; +} + + +//////////////////////////////////////////////// +// buttons +//////////////////////////////////////////////// + +void AbstractArduboy::poll() +{ + previousButtonState = currentButtonState; + currentButtonState = getInput(); +} + +// returns true if the button mask passed in is pressed +// +// if (pressed(LEFT_BUTTON + A_BUTTON)) +bool AbstractArduboy::pressed(uint8_t buttons) +{ + uint8_t button_state = getInput(); + return (button_state & buttons) == buttons; +} + +// returns true if the button mask passed in not pressed +// +// if (not_pressed(LEFT_BUTTON)) +bool AbstractArduboy::notPressed(uint8_t buttons) +{ + uint8_t button_state = getInput(); + return (button_state & buttons) == 0; +} + +// returns true if a button has just been pressed +// if the button has been held down for multiple frames this will return +// false. You should only use this to poll a single button. +bool AbstractArduboy::justPressed(uint8_t button) +{ + uint8_t button_state = getInput(); + return (!(previousButtonState & button) && (currentButtonState & button)); +} + +//////////////////////////////////////////////// +// graphics +//////////////////////////////////////////////// + +void AbstractArduboy::blank() +{ + fillScreen(0); +} + +void AbstractArduboy::clearDisplay() +{ + fillScreen(0); +} + +void AbstractArduboy::drawPixel(int x, int y, uint8_t color) +{ +#ifdef PIXEL_SAFE_MODE + if (x < 0 || x > (WIDTH - 1) || y < 0 || y > (HEIGHT - 1)) + { + return; + } +#endif + + uint8_t row = (uint8_t)y / 8; + if (color) + { + sBuffer[(row * WIDTH) + (uint8_t)x] |= _BV((uint8_t)y % 8); + } + else + { + sBuffer[(row * WIDTH) + (uint8_t)x] &= ~ _BV((uint8_t)y % 8); + } +} + +uint8_t AbstractArduboy::getPixel(uint8_t x, uint8_t y) +{ + uint8_t row = y / 8; + uint8_t bit_position = y % 8; + return (sBuffer[(row * WIDTH) + x] & _BV(bit_position)) >> bit_position; +} + +void AbstractArduboy::drawCircle(int16_t x0, int16_t y0, int16_t r, uint8_t color) +{ + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + drawPixel(x0, y0 + r, color); + drawPixel(x0, y0 - r, color); + drawPixel(x0 + r, y0, color); + drawPixel(x0 - r, y0, color); + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + + x++; + ddF_x += 2; + f += ddF_x; + + drawPixel(x0 + x, y0 + y, color); + drawPixel(x0 - x, y0 + y, color); + drawPixel(x0 + x, y0 - y, color); + drawPixel(x0 - x, y0 - y, color); + drawPixel(x0 + y, y0 + x, color); + drawPixel(x0 - y, y0 + x, color); + drawPixel(x0 + y, y0 - x, color); + drawPixel(x0 - y, y0 - x, color); + } +} + +void AbstractArduboy::drawCircleHelper +(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, uint8_t color) +{ + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + + x++; + ddF_x += 2; + f += ddF_x; + + if (cornername & 0x4) + { + drawPixel(x0 + x, y0 + y, color); + drawPixel(x0 + y, y0 + x, color); + } + if (cornername & 0x2) + { + drawPixel(x0 + x, y0 - y, color); + drawPixel(x0 + y, y0 - x, color); + } + if (cornername & 0x8) + { + drawPixel(x0 - y, y0 + x, color); + drawPixel(x0 - x, y0 + y, color); + } + if (cornername & 0x1) + { + drawPixel(x0 - y, y0 - x, color); + drawPixel(x0 - x, y0 - y, color); + } + } +} + +void AbstractArduboy::fillCircle(int16_t x0, int16_t y0, int16_t r, uint8_t color) +{ + drawFastVLine(x0, y0 - r, 2 * r + 1, color); + fillCircleHelper(x0, y0, r, 3, 0, color); +} + +void AbstractArduboy::fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, uint8_t color) +{ + // used to do circles and roundrects! + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + + x++; + ddF_x += 2; + f += ddF_x; + + if (cornername & 0x1) + { + drawFastVLine(x0 + x, y0 - y, 2 * y + 1 + delta, color); + drawFastVLine(x0 + y, y0 - x, 2 * x + 1 + delta, color); + } + + if (cornername & 0x2) + { + drawFastVLine(x0 - x, y0 - y, 2 * y + 1 + delta, color); + drawFastVLine(x0 - y, y0 - x, 2 * x + 1 + delta, color); + } + } +} + +void AbstractArduboy::drawLine +(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color) +{ + // bresenham's algorithm - thx wikpedia + bool steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + swap(x0, y0); + swap(x1, y1); + } + + if (x0 > x1) { + swap(x0, x1); + swap(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int16_t err = dx / 2; + int8_t ystep; + + if (y0 < y1) + { + ystep = 1; + } + else + { + ystep = -1; + } + + for (; x0 <= x1; x0++) + { + if (steep) + { + drawPixel(y0, x0, color); + } + else + { + drawPixel(x0, y0, color); + } + + err -= dy; + if (err < 0) + { + y0 += ystep; + err += dx; + } + } +} + +void AbstractArduboy::drawRect +(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color) +{ + drawFastHLine(x, y, w, color); + drawFastHLine(x, y + h - 1, w, color); + drawFastVLine(x, y, h, color); + drawFastVLine(x + w - 1, y, h, color); +} + +void AbstractArduboy::drawFastVLine +(int16_t x, int16_t y, int16_t h, uint8_t color) +{ + int end = y + h; + for (int a = max(0, y); a < min(end, HEIGHT); a++) + { + drawPixel(x, a, color); + } +} + +void AbstractArduboy::drawFastHLine +(int16_t x, int16_t y, int16_t w, uint8_t color) +{ + int end = x + w; + for (int a = max(0, x); a < min(end, WIDTH); a++) + { + drawPixel(a, y, color); + } +} + +void AbstractArduboy::fillRect +(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color) +{ + // stupidest version - update in subclasses if desired! + for (int16_t i = x; i < x + w; i++) + { + drawFastVLine(i, y, h, color); + } +} + +void AbstractArduboy::fillScreen(uint8_t color) +{ + // C version : + if(color != 0) color = 0xFF; //change any nonzero argument to b11111111 and insert into screen array. + for(int16_t i=0; i<1024; i++) { sBuffer[i] = color; } //sBuffer = (128*64) = 8192/8 = 1024 bytes. +} + +void AbstractArduboy::drawRoundRect +(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint8_t color) +{ + // smarter version + drawFastHLine(x + r, y, w - 2 * r, color); // Top + drawFastHLine(x + r, y + h - 1, w - 2 * r, color); // Bottom + drawFastVLine(x, y + r, h - 2 * r, color); // Left + drawFastVLine(x + w - 1, y + r, h - 2 * r, color); // Right + // draw four corners + drawCircleHelper(x + r, y + r, r, 1, color); + drawCircleHelper(x + w - r - 1, y + r, r, 2, color); + drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color); + drawCircleHelper(x + r, y + h - r - 1, r, 8, color); +} + +void AbstractArduboy::fillRoundRect +(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint8_t color) +{ + // smarter version + fillRect(x + r, y, w - 2 * r, h, color); + + // draw four corners + fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color); + fillCircleHelper(x + r, y + r, r, 2, h - 2 * r - 1, color); +} + +void AbstractArduboy::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint8_t color) { + // no need to dar at all of we're offscreen + if (x + w < 0 || x > WIDTH - 1 || y + h < 0 || y > HEIGHT - 1) + return; + + int yOffset = abs(y) % 8; + int sRow = y / 8; + if (y < 0) { + sRow--; + yOffset = 8 - yOffset; + } + int rows = h / 8; + if (h % 8 != 0) rows++; + for (int a = 0; a < rows; a++) { + int bRow = sRow + a; + if (bRow > (HEIGHT / 8) - 1) break; + if (bRow > -2) { + for (int iCol = 0; iCol < w; iCol++) { + if (iCol + x > (WIDTH - 1)) break; + if (iCol + x >= 0) { + if (bRow >= 0) { + if (color) this->sBuffer[ (bRow * WIDTH) + x + iCol ] |= pgm_read_byte(bitmap + (a * w) + iCol) << yOffset; + else this->sBuffer[ (bRow * WIDTH) + x + iCol ] &= ~(pgm_read_byte(bitmap + (a * w) + iCol) << yOffset); + } + if (yOffset && bRow < (HEIGHT / 8) - 1 && bRow > -2) { + if (color) this->sBuffer[ ((bRow + 1)*WIDTH) + x + iCol ] |= pgm_read_byte(bitmap + (a * w) + iCol) >> (8 - yOffset); + else this->sBuffer[ ((bRow + 1)*WIDTH) + x + iCol ] &= ~(pgm_read_byte(bitmap + (a * w) + iCol) >> (8 - yOffset)); + } + } + } + } + } +} + + + +typedef struct CSESSION { + int byte; + int bit; + const uint8_t *src; + int src_pos; +} CSESSION; +static CSESSION cs; + +static int getval(int bits) +{ + int val = 0; + int i; + for (i = 0; i < bits; i++) + { + if (cs.bit == 0x100) + { + cs.bit = 0x1; + cs.byte = pgm_read_byte(&cs.src[cs.src_pos]); + cs.src_pos ++; + } + if (cs.byte & cs.bit) + val += (1 << i); + cs.bit <<= 1; + } + return val; +} + +void AbstractArduboy::drawCompressed(int16_t sx, int16_t sy, const uint8_t *bitmap, uint8_t color) +{ + int bl, len; + int col; + int i; + int a, iCol; + int x, y; + int byte = 0; + int bit = 0; + int w, h; + + // set up decompress state + + cs.src = bitmap; + cs.bit = 0x100; + cs.byte = 0; + cs.src_pos = 0; + + // read header + + w = getval(8) + 1; + h = getval(8) + 1; + + col = getval(1); // starting colour + + // no need to draw at all if we're offscreen + if (sx + w < 0 || sx > WIDTH - 1 || sy + h < 0 || sy > HEIGHT - 1) + return; + + // sy = sy - (frame*h); + + int yOffset = abs(sy) % 8; + int sRow = sy / 8; + if (sy < 0) { + sRow--; + yOffset = 8 - yOffset; + } + int rows = h / 8; + if (h % 8 != 0) rows++; + + a = 0; // +(frame*rows); + iCol = 0; + + byte = 0; bit = 1; + while (a < rows) // + (frame*rows)) + { + bl = 1; + while (!getval(1)) + bl += 2; + + len = getval(bl) + 1; // span length + + // draw the span + + + for (i = 0; i < len; i++) + { + if (col) + byte |= bit; + bit <<= 1; + + if (bit == 0x100) // reached end of byte + { + // draw + + int bRow = sRow + a; + + //if (byte) // possible optimisation + if (bRow <= (HEIGHT / 8) - 1) + if (bRow > -2) + if (iCol + sx <= (WIDTH - 1)) + if (iCol + sx >= 0) { + + if (bRow >= 0) + { + if (color) + this->sBuffer[ (bRow * WIDTH) + sx + iCol] |= byte << yOffset; + else + this->sBuffer[ (bRow * WIDTH) + sx + iCol] &= ~(byte << yOffset); + } + if (yOffset && bRow < (HEIGHT / 8) - 1 && bRow > -2) + { + if (color) + this->sBuffer[((bRow + 1)*WIDTH) + sx + iCol] |= byte >> (8 - yOffset); + else + this->sBuffer[((bRow + 1)*WIDTH) + sx + iCol] &= ~(byte >> (8 - yOffset)); + } + + } + + // iterate + iCol ++; + if (iCol >= w) + { + iCol = 0; + a ++; + } + + // reset byte + byte = 0; bit = 1; + } + } + + col = 1 - col; // toggle colour for next span + } +} + +uint8_t AbstractArduboy::writeChar(uint8_t c) +{ + if (c == '\n') + { + cursor_y += textsize*8; + cursor_x = 0; + } + else if (c == '\r') + cursor_x = 0; + else + { + drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize); + cursor_x += textsize*6; + if (wrap && (cursor_x > (WIDTH - textsize*6))) + { + cursor_y += textsize*8; + cursor_x = 0; + } + } + return 1; +} + +// draw a character +void AbstractArduboy::drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size) +{ + if( + (x >= WIDTH) || // Clip right + (y >= HEIGHT) || // Clip bottom + ((x + 5 * size - 1) < 0) || // Clip left + ((y + 8 * size - 1) < 0) // Clip top + ) + return; + + for (int8_t i=0; i<6; i++ ) + { + uint8_t line = 0; + + if (i == 5) + line = 0x0; + else + line = font[(c*5)+i]; + + for (int8_t j = 0; j<8; j++) + { + if (line & 0x1) + { +#if defined(GFX_WANT_ABSTRACTS) || defined(GFX_SIZEABLE_TEXT) + if (size == 1) // default size + drawPixel(x+i, y+j, color); + else // big size + fillRect(x+(i*size), y+(j*size), size, size, color); +#else + drawPixel(x+i, y+j, color); +#endif + } + else if (bg != color) + { +#if defined(GFX_WANT_ABSTRACTS) || defined(GFX_SIZEABLE_TEXT) + if (size == 1) // default size + drawPixel(x+i, y+j, bg); + else // big size + fillRect(x+i*size, y+j*size, size, size, bg); +#else + drawPixel(x+i, y+j, bg); +#endif + } + line >>= 1; + } + } +} + +void AbstractArduboy::swap(int16_t &a, int16_t &b) +{ + int16_t t = a; + + a = b; + b = t; +} + +void AbstractArduboy::display() +{ + this->drawScreen(sBuffer); +}