Add Team A.R.G. Arglib functionality

- Version changed to 2.1.0
- Added sprite, button and collision functions from ArduboyExtra
- Added drawCompressed() function from Arglib
- Updated LICENSE.txt
- Removed CONTRIBUTORS.md (now covered by LICENSE.txt)
This commit is contained in:
Scott Allen 2016-10-19 20:23:34 -04:00
parent f9dc4cb0e5
commit f763263d2b
10 changed files with 871 additions and 52 deletions

View File

@ -1,17 +0,0 @@
# Individual Contributors
- Kevin "Arduboy" Bates (@Arduboy)
- arduboychris (@arduboychris)
- Ross (@rogosher)
- Andrew (@ace-dent)
- Josh Goebel (@yyyc514)
- Scott Allen (@MLXXXp)
# Included code from other open source projects
- Original SSD1306 library
https://github.com/adafruit/Adafruit_SSD1306
BSD License
Copyright (c) 2012, Adafruit Industries

View File

@ -1,14 +1,23 @@
Software License Agreement (BSD License)
Software License Agreements
-------------------------------------------------------------------------------
Licensed under the BSD 3-clause license:
Arduboy2 library:
Copyright (c) 2016, Scott Allen
All rights reserved.
The Arduboy 2 library was forked from the Arduboy library:
https://github.com/Arduboy/Arduboy
Copyright (c) 2016, Kevin "Arduboy" Bates
Copyright (c) 2016, Chris Martinez
Copyright (c) 2016, Josh Goebel
Copyright (c) 2016, Scott Allen
All rights reserved.
Please see CONTRIBUTORS.md for license information and copyright
notices for any libraries we built on or redistribute.
which is in turn partially based on the Adafruit_SSD1306 library
https://github.com/adafruit/Adafruit_SSD1306
Copyright (c) 2012, Adafruit Industries
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
@ -31,3 +40,90 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Licensed under the 2-clause BSD license:
Portions of the Arduboy library, and thus the Arduboy2 library, based on
the Adafruit-GFX library:
https://github.com/adafruit/Adafruit-GFX-Library
Copyright (c) 2012 Adafruit Industries
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Licensed under the MIT license:
Code from ArduboyExtra:
https://github.com/yyyc514/ArduboyExtra
Copyright (c) 2015 Josh Goebel
Code for drawing compressed bitmaps:
https://github.com/TEAMarg/drawCompressed
Copyright (c) 2016 TEAM a.r.g.
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.
-------------------------------------------------------------------------------
Licensed under the GNU LGPL license:
https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
ArduBreakout example sketch:
Original work:
Copyright (c) 2011 Sebastian Goscik
All rights reserved.
Modified work:
Copyright (c) 2016 Scott Allen
All rights reserved.
Buttons and HelloWorld example sketches:
Copyright (c) 2015 David Martinez
All rights reserved.
This work 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 2.1 of the License, or (at your option) any later version.
This library 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.
===============================================================================

View File

@ -36,6 +36,11 @@ Main differences between Arduboy2 and Arduboy V1.1 are:
- The *beginNoLogo()* function is not included. This function could be used in Arduboy V1.1 in place of *begin()* to suppress the displaying of the ARDUBOY logo and thus free up the code that it required. Instead, Arduboy2 allows a sketch to call *boot()* and then add in any extra features that *begin()* provides by calling their functions directly after *boot()*, if desired.
- The *ArduboyCore* and *ArduboyAudio* base classes, previously only available to, and used to derive, the *Arduboy* class, have been made publicly available for the benefit of developers who may wish to use them as the base of an entirely new library. This change doesn't affect the existing API.
As of version 2.1.0 functionality from the [Team A.R.G.](http://www.team-arg.org/) *Arglib* library has been added:
- The sprite drawing functions, collision detection functions, and button handling functions that Team A.R.G. incorporated from the [ArduboyExtra](https://github.com/yyyc514/ArduboyExtra) project. The *poll()* function was renamed *pollButtons()* for clarity.
- The *drawCompressed()* function, which allows compressed bitmaps to be drawn. Saving bitmaps in compressed form may reduce overall sketch size.
## Start up features
The *begin()* function, used to initialize the library, includes features that are intended to be available to all sketches using the library (unless the sketch developer has chosen to disable one or more of them to free up some code space):

View File

@ -7,7 +7,7 @@
"type": "git",
"url": "https://github.com/MLXXXp/Arduboy2.git"
},
"version": "2.0.5",
"version": "2.1.0",
"exclude": "extras",
"frameworks": "arduino",
"platforms": "atmelavr"

View File

@ -1,5 +1,5 @@
name=Arduboy2
version=2.0.5
version=2.1.0
author=Chris J. Martinez, Kevin Bates, Josh Goebel, Scott Allen, Ross O. Shoger
maintainer=Scott Allen saydisp-git@yahoo.ca
sentence=An alternative library for use with the Arduboy game system.

View File

@ -10,12 +10,13 @@ uint8_t Arduboy2Base::sBuffer[];
Arduboy2Base::Arduboy2Base()
{
currentButtonState = 0;
previousButtonState = 0;
// frame management
setFrameRate(60);
frameCount = 0;
nextFrameStart = 0;
post_render = false;
// init not necessary, will be reset after first use
// lastFrameStart
// lastFrameDurationMs
@ -644,7 +645,7 @@ void Arduboy2Base::drawBitmap
(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h,
uint8_t color)
{
// no need to dar at all of we're offscreen
// no need to draw at all if we're offscreen
if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1)
return;
@ -664,14 +665,20 @@ void Arduboy2Base::drawBitmap
if (iCol + x > (WIDTH-1)) break;
if (iCol + x >= 0) {
if (bRow >= 0) {
if (color == WHITE) this->sBuffer[ (bRow*WIDTH) + x + iCol ] |= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset;
else if (color == BLACK) 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 (color == WHITE)
sBuffer[(bRow*WIDTH) + x + iCol] |= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset;
else if (color == BLACK)
sBuffer[(bRow*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) << yOffset);
else
sBuffer[(bRow*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset;
}
if (yOffset && bRow<(HEIGHT/8)-1 && bRow > -2) {
if (color == WHITE) this->sBuffer[ ((bRow+1)*WIDTH) + x + iCol ] |= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset);
else if (color == BLACK) 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);
if (color == WHITE)
sBuffer[((bRow+1)*WIDTH) + x + iCol] |= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset);
else if (color == BLACK)
sBuffer[((bRow+1)*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset));
else
sBuffer[((bRow+1)*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset);
}
}
}
@ -697,10 +704,141 @@ void Arduboy2Base::drawSlowXYBitmap
}
}
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 Arduboy2Base::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 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)
sBuffer[(bRow * WIDTH) + sx + iCol] |= byte << yOffset;
else
sBuffer[(bRow * WIDTH) + sx + iCol] &= ~(byte << yOffset);
}
if (yOffset && bRow < (HEIGHT / 8) - 1 && bRow > -2)
{
if (color)
sBuffer[((bRow + 1)*WIDTH) + sx + iCol] |= byte >> (8 - yOffset);
else
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
}
}
void Arduboy2Base::display()
{
this->paintScreen(sBuffer);
paintScreen(sBuffer);
}
unsigned char* Arduboy2Base::getBuffer()
@ -718,13 +856,48 @@ bool Arduboy2Base::notPressed(uint8_t buttons)
return (buttonsState() & buttons) == 0;
}
void Arduboy2Base::pollButtons()
{
previousButtonState = currentButtonState;
currentButtonState = buttonsState();
}
bool Arduboy2Base::justPressed(uint8_t button)
{
return (!(previousButtonState & button) && (currentButtonState & button));
}
bool Arduboy2Base::justReleased(uint8_t button)
{
return ((previousButtonState & button) && !(currentButtonState & button));
}
bool Arduboy2Base::collide(Point point, Rect rect)
{
return ((point.x >= rect.x) && (point.x < rect.x + rect.width) &&
(point.y >= rect.y) && (point.y < rect.y + rect.height));
}
bool Arduboy2Base::collide(Rect rect1, Rect rect2)
{
return !(rect2.x >= rect1.x + rect1.width ||
rect2.x + rect2.width <= rect1.x ||
rect2.y >= rect1.y + rect1.height ||
rect2.y + rect2.height <= rect1.y);
}
void Arduboy2Base::swap(int16_t& a, int16_t& b)
{
int temp = a;
int16_t temp = a;
a = b;
b = temp;
}
//====================================
//========== class Arduboy2 ==========
//====================================
Arduboy2::Arduboy2()
{
cursor_x = 0;
@ -735,11 +908,6 @@ Arduboy2::Arduboy2()
textWrap = 0;
}
//====================================
//========== class Arduboy2 ==========
//====================================
size_t Arduboy2::write(uint8_t c)
{
if (c == '\n')

View File

@ -1,7 +1,9 @@
#ifndef ARDUBOY2_H
#define ARDUBOY2_H
#include <Arduino.h>
#include "ArduboyCore.h"
#include "Sprites.h"
#include <Print.h>
#include <limits.h>
@ -9,7 +11,7 @@
// For a version number in the form of x.y.z the value of the define will be
// ((x * 10000) + (y * 100) + (z)) as a decimal number.
// So, it will read as xxxyyzz, with no leading zeros on x.
#define ARDUBOY_LIB_VER 20005
#define ARDUBOY_LIB_VER 20100
// EEPROM settings
#define EEPROM_VERSION 0
@ -35,6 +37,22 @@
#define ADC_TEMP (_BV(REFS0) | _BV(REFS1) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0))
/// Rectangle for collision functions
struct Rect
{
int16_t x;
int16_t y;
uint8_t width;
uint8_t height;
};
/// Point for collision functions
struct Point
{
int16_t x;
int16_t y;
};
//==================================
//========== Arduboy2Base ==========
//==================================
@ -46,18 +64,6 @@ public:
ArduboyAudio audio;
/// Returns true if the button mask passed in is pressed.
/**
* if (pressed(LEFT_BUTTON + A_BUTTON))
*/
bool pressed(uint8_t buttons);
/// Returns true if the button mask passed in not pressed.
/**
* if (notPressed(LEFT_BUTTON))
*/
bool notPressed(uint8_t buttons);
/// Initialize hardware, boot logo, boot utilities, etc.
/**
* To free up some code space for use by the sketch, you can use "boot()"
@ -176,6 +182,16 @@ public:
*/
void drawSlowXYBitmap(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h, uint8_t color = WHITE);
/// Draw a compressed bitmap
/**
* Draws a bitmap starting at the given coordinates that has been compressed
* using an algorthm implemented by Team A.R.G.
* For more information see:
* https://github.com/TEAMarg/drawCompressed
* https://github.com/TEAMarg/Cabi
*/
void drawCompressed(int16_t sx, int16_t sy, const uint8_t *bitmap, uint8_t color = WHITE);
/// Get a pointer to the display buffer.
unsigned char* getBuffer();
@ -214,6 +230,61 @@ public:
/// useful for getting raw approximate voltage values
uint16_t rawADC(uint8_t adc_bits);
/// Test if the specified buttons are pressed
/**
* Returns "true" if all the buttons in the provided mask are currently pressed.
* (Can be a single button)
* E.g. `if (pressed(LEFT_BUTTON + A_BUTTON))`
*/
bool pressed(uint8_t buttons);
/// Test if the specified buttons are not pressed
/**
* Returns "true" if all the buttons in the provided mask are currently released.
* (Can be a single button)
* E.g. `if (notPressed(LEFT_BUTTON))`
*/
bool notPressed(uint8_t buttons);
/// Poll the hardware buttons and track state over time.
/**
* Should be called either in the main `loop()` or as part of the
* frame system at the start of rendering a frame.
*
* The `justPressed()` and `justReleased()` functions rely on this function.
*/
void pollButtons();
/// Check if a button has just been pressed
/**
* Will return "true" if the given button was pressed between the latest
* call to `pollButtons()` and previous call to `pollButtons()`.
* If the button has been held down over multiple polls this function will
* return "false". This function should only be used to test a single button.
*/
bool justPressed(uint8_t button);
/// Check if a button has just been released
/**
* Will return "true" if the given button was released between the latest
* call to `pollButtons()` and previous call to `pollButtons()`.
* If the button has been released over multiple polls this function will
* return "false". This function should only be used to test a single button.
*/
bool justReleased(uint8_t button);
/// Test if a point is within a rectangle
/**
* Returns "true" if the point is within the rectangle.
*/
bool collide(Point point, Rect rect);
/// Test if a rectangle is intersecting with another rectangle.
/**
* Returns "true" if the first rectangle is intersecting the second.
*/
bool collide(Rect rect1, Rect rect2);
protected:
// helper function for sound enable/disable system control
void sysCtrlSound(uint8_t buttons, uint8_t led, uint8_t eeVal);
@ -221,6 +292,10 @@ protected:
// Screen buffer
static uint8_t sBuffer[(HEIGHT*WIDTH)/8];
// For button handling
uint8_t currentButtonState;
uint8_t previousButtonState;
// For frame funcions
uint16_t frameCount;
uint8_t eachFrameMillis;

View File

@ -1,6 +1,7 @@
#ifndef ARDUBOY_CORE_H
#define ARDUBOY_CORE_H
#include <Arduino.h>
#include <avr/power.h>
#include <SPI.h>
#include <avr/sleep.h>

353
src/Sprites.cpp Normal file
View File

@ -0,0 +1,353 @@
#include "Sprites.h"
Sprites::Sprites(uint8_t* buffer)
{
sBuffer = buffer;
}
void Sprites::drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap,
const uint8_t *mask, uint8_t frame, uint8_t mask_frame)
{
draw(x, y, bitmap, frame, mask, mask_frame, SPRITE_MASKED);
}
void Sprites::drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_OVERWRITE);
}
void Sprites::drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK_ERASE);
}
void Sprites::drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK);
}
void Sprites::drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_PLUS_MASK);
}
//common functions
void Sprites::draw(int16_t x, int16_t y,
const uint8_t *bitmap, uint8_t frame,
const uint8_t *mask, uint8_t sprite_frame,
uint8_t drawMode)
{
unsigned int frame_offset;
if (bitmap == NULL)
return;
uint8_t width = pgm_read_byte(bitmap);
uint8_t height = pgm_read_byte(++bitmap);
bitmap++;
if (frame > 0 || sprite_frame > 0) {
frame_offset = (width * ( height / 8 + ( height % 8 == 0 ? 0 : 1)));
// sprite plus mask uses twice as much space for each frame
if (drawMode == SPRITE_PLUS_MASK) {
frame_offset *= 2;
} else if (mask != NULL) {
mask += sprite_frame * frame_offset;
}
bitmap += frame * frame_offset;
}
// if we're detecting the draw mode then base it on whether a mask
// was passed as a separate object
if (drawMode == SPRITE_AUTO_MODE) {
drawMode = mask == NULL ? SPRITE_UNMASKED : SPRITE_MASKED;
}
drawBitmap(x, y, bitmap, mask, width, height, drawMode);
}
void Sprites::drawBitmap(int16_t x, int16_t y,
const uint8_t *bitmap, const uint8_t *mask,
int8_t w, int8_t h, uint8_t draw_mode)
{
// no need to draw at all of we're offscreen
if (x + w <= 0 || x > WIDTH - 1 || y + h <= 0 || y > HEIGHT - 1)
return;
if (bitmap == NULL)
return;
// xOffset technically doesn't need to be 16 bit but the math operations
// are measurably faster if it is
uint16_t xOffset, ofs;
int8_t yOffset = abs(y) % 8;
int8_t sRow = y / 8;
uint8_t loop_h, start_h, rendered_width;
if (y < 0 && yOffset > 0) {
sRow--;
yOffset = 8 - yOffset;
}
// if the left side of the render is offscreen skip those loops
if (x < 0) {
xOffset = abs(x);
} else {
xOffset = 0;
}
// if the right side of the render is offscreen skip those loops
if (x + w > WIDTH - 1) {
rendered_width = ((WIDTH - x) - xOffset);
} else {
rendered_width = (w - xOffset);
}
// if the top side of the render is offscreen skip those loops
if (sRow < -1) {
start_h = abs(sRow) - 1;
} else {
start_h = 0;
}
loop_h = h / 8 + (h % 8 > 0 ? 1 : 0); // divide, then round up
// if (sRow + loop_h - 1 > (HEIGHT/8)-1)
if (sRow + loop_h > (HEIGHT / 8)) {
loop_h = (HEIGHT / 8) - sRow;
}
// prepare variables for loops later so we can compare with 0
// instead of comparing two variables
loop_h -= start_h;
sRow += start_h;
ofs = (sRow * WIDTH) + x + xOffset;
uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset;
uint8_t *mask_ofs;
if (mask != 0)
mask_ofs = (uint8_t *)mask + (start_h * w) + xOffset;
uint8_t data;
uint8_t mul_amt = 1 << yOffset;
uint16_t mask_data;
uint16_t bitmap_data;
switch (draw_mode) {
case SPRITE_UNMASKED:
// we only want to mask the 8 bits of our own sprite, so we can
// calculate the mask before the start of the loop
mask_data = ~(0xFF * mul_amt);
// really if yOffset = 0 you have a faster case here that could be
// optimized
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
data = sBuffer[ofs];
data &= (uint8_t)(mask_data);
data |= (uint8_t)(bitmap_data);
sBuffer[ofs] = data;
}
if (yOffset != 0 && sRow < 7) {
data = sBuffer[ofs + WIDTH];
data &= (*((unsigned char *) (&mask_data) + 1));
data |= (*((unsigned char *) (&bitmap_data) + 1));
sBuffer[ofs + WIDTH] = data;
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
case SPRITE_IS_MASK:
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
sBuffer[ofs] |= (uint8_t)(bitmap_data);
}
if (yOffset != 0 && sRow < 7) {
sBuffer[ofs + WIDTH] |= (*((unsigned char *) (&bitmap_data) + 1));
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
case SPRITE_IS_MASK_ERASE:
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
sBuffer[ofs] &= ~(uint8_t)(bitmap_data);
}
if (yOffset != 0 && sRow < 7) {
sBuffer[ofs + WIDTH] &= ~(*((unsigned char *) (&bitmap_data) + 1));
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
case SPRITE_MASKED:
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
// NOTE: you might think in the yOffset==0 case that this results
// in more effort, but in all my testing the compiler was forcing
// 16-bit math to happen here anyways, so this isn't actually
// compiling to more code than it otherwise would. If the offset
// is 0 the high part of the word will just never be used.
// load data and bit shift
// mask needs to be bit flipped
mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt);
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
data = sBuffer[ofs];
data &= (uint8_t)(mask_data);
data |= (uint8_t)(bitmap_data);
sBuffer[ofs] = data;
}
if (yOffset != 0 && sRow < 7) {
data = sBuffer[ofs + WIDTH];
data &= (*((unsigned char *) (&mask_data) + 1));
data |= (*((unsigned char *) (&bitmap_data) + 1));
sBuffer[ofs + WIDTH] = data;
}
ofs++;
mask_ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
mask_ofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
case SPRITE_PLUS_MASK:
// *2 because we use double the bits (mask + bitmap)
bofs = (uint8_t *)(bitmap + ((start_h * w) + xOffset) * 2);
uint8_t xi = rendered_width; // used for x loop below
uint8_t yi = loop_h; // used for y loop below
asm volatile(
"push r28\n" // save Y
"push r29\n"
"mov r28, %A[buffer_page2_ofs]\n" // Y = buffer page 2 offset
"mov r29, %B[buffer_page2_ofs]\n"
"loop_y:\n"
"loop_x:\n"
// load bitmap and mask data
"lpm %A[bitmap_data], Z+\n"
"lpm %A[mask_data], Z+\n"
// shift mask and buffer data
"tst %[yOffset]\n"
"breq skip_shifting\n"
"mul %A[bitmap_data], %[mul_amt]\n"
"mov %A[bitmap_data], r0\n"
"mov %B[bitmap_data], r1\n"
"mul %A[mask_data], %[mul_amt]\n"
"mov %A[mask_data], r0\n"
// "mov %B[mask_data], r1\n"
// SECOND PAGE
// if yOffset != 0 && sRow < 7
"cpi %[sRow], 7\n"
"brge end_second_page\n"
// then
"ld %[data], Y\n"
// "com %B[mask_data]\n" // invert high byte of mask
"com r1\n"
"and %[data], r1\n" // %B[mask_data]
"or %[data], %B[bitmap_data]\n"
// update buffer, increment
"st Y+, %[data]\n"
"end_second_page:\n"
"skip_shifting:\n"
// FIRST PAGE
"ld %[data], %a[buffer_ofs]\n"
// if sRow >= 0
"tst %[sRow]\n"
"brmi end_first_page\n"
// then
"com %A[mask_data]\n"
"and %[data], %A[mask_data]\n"
"or %[data], %A[bitmap_data]\n"
"end_first_page:\n"
// update buffer, increment
"st %a[buffer_ofs]+, %[data]\n"
// "x_loop_next:\n"
"dec %[xi]\n"
"brne loop_x\n"
// increment y
"next_loop_y:\n"
"dec %[yi]\n"
"breq finished\n"
"mov %[xi], %[x_count]\n" // reset x counter
// sRow++;
"inc %[sRow]\n"
"clr __zero_reg__\n"
// sprite_ofs += (w - rendered_width) * 2;
"add %A[sprite_ofs], %A[sprite_ofs_jump]\n"
"adc %B[sprite_ofs], __zero_reg__\n"
// buffer_ofs += WIDTH - rendered_width;
"add %A[buffer_ofs], %A[buffer_ofs_jump]\n"
"adc %B[buffer_ofs], __zero_reg__\n"
// buffer_ofs_page_2 += WIDTH - rendered_width;
"add r28, %A[buffer_ofs_jump]\n"
"adc r29, __zero_reg__\n"
"rjmp loop_y\n"
"finished:\n"
// put the Y register back in place
"pop r29\n"
"pop r28\n"
"clr __zero_reg__\n" // just in case
: [xi] "+&r" (xi),
[yi] "+&r" (yi),
[sRow] "+&a" (sRow), // CPI requires an upper register
[data] "+&r" (data),
[mask_data] "+&r" (mask_data),
[bitmap_data] "+&r" (bitmap_data)
:
[x_count] "r" (rendered_width),
[y_count] "r" (loop_h),
[sprite_ofs] "z" (bofs),
[buffer_ofs] "x" (sBuffer+ofs),
[buffer_page2_ofs] "r" (sBuffer+ofs+WIDTH), // Y pointer
[buffer_ofs_jump] "r" (WIDTH-rendered_width),
[sprite_ofs_jump] "r" ((w-rendered_width)*2),
[yOffset] "r" (yOffset),
[mul_amt] "r" (mul_amt)
:
);
break;
}
}

138
src/Sprites.h Normal file
View File

@ -0,0 +1,138 @@
#ifndef Sprites_h
#define Sprites_h
#include "Arduboy2.h"
#define SPRITE_MASKED 1
#define SPRITE_UNMASKED 2
#define SPRITE_OVERWRITE 2
#define SPRITE_PLUS_MASK 3
#define SPRITE_IS_MASK 250
#define SPRITE_IS_MASK_ERASE 251
#define SPRITE_AUTO_MODE 255
class Sprites
{
public:
/// The class constructor.
/// `buffer` is a pointer to the screen buffer where the draw functions
/// will write to.
///
Sprites(uint8_t* buffer);
/// drawExternalMask() uses a separate mask to mask image (MASKED)
///
/// image mask before after
///
/// ..... .OOO. ..... .....
/// ..O.. OOOOO ..... ..O..
/// OO.OO OO.OO ..... OO.OO
/// ..O.. OOOOO ..... ..O..
/// ..... .OOO. ..... .....
///
/// image mask before after
///
/// ..... .OOO. OOOOO O...O
/// ..O.. OOOOO OOOOO ..O..
/// OO.OO OOOOO OOOOO OO.OO
/// ..O.. OOOOO OOOOO ..O..
/// ..... .OOO. OOOOO O...O
///
void drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap,
const uint8_t *mask, uint8_t frame, uint8_t mask_frame);
/// drawPlusMask() has the same behavior as `drawExternalMask()` except
/// the data is arranged in byte tuples interposing the mask right along
/// with the image data (SPRITE_PLUS_MASK)
///
/// typical image data (8 bytes):
/// [I][I][I][I][I][I][I][I]
///
/// interposed image/mask data (8 byes):
/// [I][M][I][M][I][M][I][M]
///
/// The byte order does not change, just for every image byte you mix
/// in it's matching mask byte. Softare tools make easy work of this.
///
/// See: https://github.com/yyyc514/img2ard
///
void drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
/// drawOverwrite() replaces the existing content completely (UNMASKED)
///
/// image before after
///
/// ..... ..... .....
/// ..O.. ..... ..O..
/// OO.OO ..... OO.OO
/// ..O.. ..... ..O..
/// ..... ..... .....
///
/// image before after
///
/// ..... OOOOO .....
/// ..O.. OOOOO ..O..
/// OO.OO OOOOO OO.OO
/// ..O.. OOOOO ..O..
/// ..... OOOOO .....
///
void drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
/// drawErase() removes the lit pixels in the image from the display
/// (SPRITE_IS_MASK_ERASE)
///
/// image before after
///
/// ..... ..... .....
/// ..O.. ..... .....
/// OO.OO ..... .....
/// ..O.. ..... .....
/// ..... ..... .....
///
/// image before after
///
/// ..... OOOOO OOOOO
/// ..O.. OOOOO OO.OO
/// OO.OO OOOOO ..O..
/// ..O.. OOOOO OO.OO
/// ..... OOOOO OOOOO
///
void drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
/// drawSelfMasked() only draws lit pixels, black pixels in
/// your image are treated as "transparent" (SPRITE_IS_MASK)
///
/// image before after
///
/// ..... ..... .....
/// ..O.. ..... ..O..
/// OO.OO ..... OO.OO
/// ..O.. ..... ..O..
/// ..... ..... .....
///
/// image before after
///
/// ..... OOOOO OOOOO (no change because all pixels were
/// ..O.. OOOOO OOOOO already white)
/// OO.OO OOOOO OOOOO
/// ..O.. OOOOO OOOOO
/// ..... OOOOO OOOOO
///
void drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
/// Master function. Needs to be abstracted into separate function for
/// every render type
void draw(int16_t x, int16_t y,
const uint8_t *bitmap, uint8_t frame,
const uint8_t *mask, uint8_t sprite_frame,
uint8_t drawMode);
void drawBitmap(int16_t x, int16_t y,
const uint8_t *bitmap, const uint8_t *mask,
int8_t w, int8_t h, uint8_t draw_mode);
private:
unsigned char *sBuffer;
};
#endif