// originally from:
// https://github.com/sutaburosu/scintillating_heatshrink/
#include <Arduino.h>
#include <FastLED.h>
#include <stdint.h>
#include <avr/pgmspace.h>
#include "heatshrink_config.h"
#include "heatshrink_decoder.h"
enum XY_matrix_config { SERPENTINE = 1, ROWMAJOR = 2, FLIPMAJOR = 4, FLIPMINOR = 8 };
// MAX_MILLIWATTS can only be changed at compile-time. Use 0 to disable limit.
// Brightness can be changed at runtime via serial with 'b' and 'B'
#define MAX_MILLIWATTS 0
#define BRIGHTNESS 255
#define LED_PIN 2
#define COLOR_ORDER GRB
#define CHIPSET WS2812B
#define kMatrixWidth 16
#define kMatrixHeight 16
#define XY_MATRIX (ROWMAJOR)
#define NUM_LEDS ((kMatrixWidth) * (kMatrixHeight))
#define SERIAL_UI 1 // if 1, can be controlled via keypresses in PuTTY
#define FADEIN_FRAMES 32 // how long to reach full brightness when powered on
#define MS_GOAL 20 // to try maintain 1000 / 20ms == 50 frames per second
#define GIF2H_MAX_PALETTE_ENTRIES 140 // RAM is always tight on ATmega328...
#define GIF2H_NUM_PIXELS NUM_LEDS // all images must be the same size as the matrix
CRGB leds[NUM_LEDS];
CRGBPalette16 currentPalette;
uint16_t XY(uint8_t x, uint8_t y) {
uint8_t major, minor, sz_major, sz_minor;
if (x >= kMatrixWidth || y >= kMatrixHeight)
return NUM_LEDS;
if (XY_MATRIX & ROWMAJOR)
major = x, minor = y, sz_major = kMatrixWidth, sz_minor = kMatrixHeight;
else
major = y, minor = x, sz_major = kMatrixHeight, sz_minor = kMatrixWidth;
if (XY_MATRIX & FLIPMINOR)
minor = sz_minor - 1 - minor;
if ((XY_MATRIX & FLIPMAJOR) ^ (minor & 1 && (XY_MATRIX & SERPENTINE)))
major = sz_major - 1 - major;
return (uint16_t) minor * sz_major + major;
}
#if FASTLED_USE_PROGMEM == 1
#define FL_PGM_READ_PTR_NEAR(x) (pgm_read_ptr_near(x))
#else
#if !defined(FL_PGM_READ_PTR_NEAR)
#define FL_PGM_READ_PTR_NEAR(addr) ({typeof(addr) _addr = (addr); *(void * const *)(_addr); })
#endif
#if !defined(memcpy_P)
#define memcpy_P(dest, src, n) memcpy(dest, src, n)
#endif
#endif
// Stuff from FastLED pull requests and gists that will hopefully be merged one day
// Blend one CRGB color toward another CRGB color by a given amount.
// Blending is linear, and done in the RGB color space.
// These functions modify 'cur' in place.
// https://gist.github.com/kriegsman/d0a5ed3c8f38c64adcb4837dafb6e690
CRGB fadeTowardColour(CRGB& cur, const CRGB& target, uint8_t amount) {
nblendU8TowardU8(cur.red, target.red, amount);
nblendU8TowardU8(cur.green, target.green, amount);
nblendU8TowardU8(cur.blue, target.blue, amount);
return cur;
}
CRGB fadeTowardColour_video(CRGB& cur, const CRGB& target, uint8_t amount) {
nblendU8TowardU8_video(cur.red, target.red, amount);
nblendU8TowardU8_video(cur.green, target.green, amount);
nblendU8TowardU8_video(cur.blue, target.blue, amount);
return cur;
}
// Helper function that blends one uint8_t toward another by a given amount
void nblendU8TowardU8(uint8_t& cur, const uint8_t target, uint8_t amount) {
if (cur == target) return;
if (cur < target ) {
uint8_t delta = target - cur;
delta = scale8(delta, amount);
cur += delta;
} else {
uint8_t delta = cur - target;
delta = scale8(delta, amount);
cur -= delta;
}
}
void nblendU8TowardU8_video(uint8_t& cur, const uint8_t target, uint8_t amount) {
if (cur == target) return;
if (cur < target ) {
uint8_t delta = target - cur;
delta = scale8_video(delta, amount);
cur += delta;
} else {
uint8_t delta = cur - target;
delta = scale8_video(delta, amount);
cur -= delta;
}
}
// FastLED uses 4-bit interpolation. 8-bit looks far less janky.
// https://github.com/FastLED/FastLED/pull/202
CRGB ColorFromPaletteExtended(const CRGBPalette16& pal, uint16_t index, uint8_t brightness, TBlendType blendType) {
// Extract the four most significant bits of the index as a palette index.
uint8_t index_4bit = (index >> 12);
// Calculate the 8-bit offset from the palette index.
uint8_t offset = (uint8_t)(index >> 4);
// Get the palette entry from the 4-bit index
const CRGB* entry = &(pal[0]) + index_4bit;
uint8_t red1 = entry->red;
uint8_t green1 = entry->green;
uint8_t blue1 = entry->blue;
uint8_t blend = offset && (blendType != NOBLEND);
if (blend) {
if (index_4bit == 15) {
entry = &(pal[0]);
} else {
entry++;
}
// Calculate the scaling factor and scaled values for the lower palette value.
uint8_t f1 = 255 - offset;
red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
// Calculate the scaled values for the neighbouring palette value.
uint8_t red2 = entry->red;
uint8_t green2 = entry->green;
uint8_t blue2 = entry->blue;
red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
cleanup_R1();
// These sums can't overflow, so no qadd8 needed.
red1 += red2;
green1 += green2;
blue1 += blue2;
}
if (brightness != 255) {
// nscale8x3_video(red1, green1, blue1, brightness);
nscale8x3(red1, green1, blue1, brightness);
}
return CRGB(red1, green1, blue1);
}
CRGB ColorFromPaletteExtended(const CRGBPalette32& pal, uint16_t index, uint8_t brightness, TBlendType blendType) {
// Extract the five most significant bits of the index as a palette index.
uint8_t index_5bit = (index >> 11);
// Calculate the 8-bit offset from the palette index.
uint8_t offset = (uint8_t)(index >> 3);
// Get the palette entry from the 5-bit index
const CRGB* entry = &(pal[0]) + index_5bit;
uint8_t red1 = entry->red;
uint8_t green1 = entry->green;
uint8_t blue1 = entry->blue;
uint8_t blend = offset && (blendType != NOBLEND);
if (blend) {
if (index_5bit == 31) {
entry = &(pal[0]);
} else {
entry++;
}
// Calculate the scaling factor and scaled values for the lower palette value.
uint8_t f1 = 255 - offset;
red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
// Calculate the scaled values for the neighbouring palette value.
uint8_t red2 = entry->red;
uint8_t green2 = entry->green;
uint8_t blue2 = entry->blue;
red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
cleanup_R1();
// These sums can't overflow, so no qadd8 needed.
red1 += red2;
green1 += green2;
blue1 += blue2;
}
if (brightness != 255) {
// nscale8x3_video(red1, green1, blue1, brightness);
nscale8x3(red1, green1, blue1, brightness);
}
return CRGB(red1, green1, blue1);
}
typedef struct HSprite {
uint16_t datasize;
uint16_t frames;
uint16_t duration;
uint8_t flags;
uint8_t palette_entries;
CRGB palette[];
uint8_t hs_data[];
} HSprite;
#include "gifs.h"
FL_PROGMEM extern const HSprite *const hsprite_list[] = {
(HSprite*) &HSpr_35disk,
(HSprite*) &HSpr_load,
(HSprite*) &HSpr_vn,
(HSprite*) &HSpr_unionflag16,
(HSprite*) &HSpr_eu,
(HSprite*) &HSpr_ghost,
(HSprite*) &HSpr_scheisse,
(HSprite*) &HSpr_devil_nod,
(HSprite*) &HSpr_kputrummis,
(HSprite*) &HSpr_rjb,
(HSprite*) &HSpr_owl,
(HSprite*) &HSpr_twylogo,
(HSprite*) &HSpr_gear,
(HSprite*) &HSpr_16x16_oric,
(HSprite*) &HSpr_zx_k_ref,
(HSprite*) &HSpr_invader,
(HSprite*) &HSpr_c64,
(HSprite*) &HSpr_amigaroll_trs,
(HSprite*) &HSpr_joystick,
(HSprite*) &HSpr_slime,
(HSprite*) &HSpr_DigDug16x16,
(HSprite*) &HSpr_octorokblue,
(HSprite*) &HSpr_ptititi,
(HSprite*) &HSpr_burgertime,
(HSprite*) &HSpr_pouet_avatar_poi_charly_walk2,
(HSprite*) &HSpr_rockhell,
(HSprite*) &HSpr_pouet_avatar_mario,
(HSprite*) &HSpr_kirby_run,
(HSprite*) &HSpr_bub,
(HSprite*) &HSpr_sirlord,
(HSprite*) &HSpr_plane,
(HSprite*) &HSpr_fire,
(HSprite*) &HSpr_bird16,
(HSprite*) &HSpr_mspacman,
(HSprite*) &HSpr_ghost3,
(HSprite*) &HSpr_batman2,
(HSprite*) &HSpr_lemming,
(HSprite*) &HSpr_mwk,
(HSprite*) &HSpr_gwain2,
(HSprite*) &HSpr_scoopexrulez,
(HSprite*) &HSpr_8bit_avatar,
};
#define HSPRITES_N (sizeof(hsprite_list) / sizeof(hsprite_list[0]))
typedef struct {
uint8_t xw, yw, xd, yd;
int16_t xdd, ydd, bcd;
} RainbowSmoothiePreset;
typedef struct {
uint32_t frame = 0;
uint32_t millis = 0;
uint8_t effect = 2;
uint8_t palette_n = 1;
uint8_t alpha_fade = 64;
uint8_t brightness = BRIGHTNESS;
uint8_t rs_preset_n = 0;
TBlendType currentBlending = LINEARBLEND;
uint8_t extendedmixing = 1;
RainbowSmoothiePreset rs;
} Config;
Config cfg;
// /*__attribute__ ((section(".noinit")))*/ uint8_t * nv_preset;
void setup() {
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setCorrection(UncorrectedColor);
FastLED.setTemperature(UncorrectedTemperature);
FastLED.setDither(DISABLE_DITHER);
// FastLED.setMaxRefreshRate(400);
if (MAX_MILLIWATTS > 0) FastLED.setMaxPowerInMilliWatts(MAX_MILLIWATTS);
if (SERIAL_UI == 1) {
Serial.begin(250000);
halp();
}
pinMode(LED_BUILTIN, OUTPUT);
inc_palette(0);
inc_rs_preset(0);
}
#define MAX_EFFECTS 3
void loop() {
uint8_t effect = cfg.effect + 1;
if (effect & 1) rainbow_smoothie();
if (effect & 2) scintillating_heatshrink();
if (SERIAL_UI == 1) poll_serial();
if (cfg.frame <= FADEIN_FRAMES)
FastLED.setBrightness(scale8(cfg.brightness, (cfg.frame * 255) / FADEIN_FRAMES));
// cap the frame rate and indicate idle time via the built-in LED
cfg.frame++;
uint32_t frame_time = millis() - cfg.millis; // wraparound safe
int32_t pause = MS_GOAL - frame_time;
// if (pause < 0 && SERIAL_UI == 1) { Serial.print(-pause); Serial.println("ms late"); }
digitalWrite(LED_BUILTIN, HIGH);
if (pause > 0) delay(pause);
digitalWrite(LED_BUILTIN, LOW);
cfg.millis = millis();
FastLED.show();
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
void poll_serial() {
if (Serial.available() <= 0)
return;
int input = Serial.read();
switch (input) {
case 'e':
cfg.effect = addmod8(cfg.effect, 1, MAX_EFFECTS);
break;
case 'f':
cfg.alpha_fade = qsub8(cfg.alpha_fade, 1);
break;
case 'F':
cfg.alpha_fade = qadd8(cfg.alpha_fade, 1);
break;
case '{':
cfg.currentBlending = LINEARBLEND;
break;
case '}':
cfg.currentBlending = NOBLEND;
break;
case '[':
cfg.extendedmixing = 1;
break;
case ']':
cfg.extendedmixing = 0;
break;
case 'p':
inc_palette(1);
break;
case '~':
inc_rs_preset(1);
break;
case 'B':
cfg.brightness = qadd8(cfg.brightness, 1);
FastLED.setBrightness(cfg.brightness);
break;
case 'b':
cfg.brightness = qsub8(cfg.brightness, 1);
FastLED.setBrightness(cfg.brightness);
break;
case 'd':
FastLED.setDither(DISABLE_DITHER);
break;
case 'D':
FastLED.setDither(BINARY_DITHER);
break;
case ',':
cfg.rs.bcd -= 64;
break;
case '<':
cfg.rs.bcd -= 256;
break;
case '.':
cfg.rs.bcd += 64;
break;
case '>':
cfg.rs.bcd += 256;
break;
case '#':
randomise_rainbowsmoothie();
break;
case 13:
print_params();
break;
case 10:
break;
default:
Serial.println(F("?"));
}
}
void halp() {
Serial.println(F(" #\trandomise rainbow smoothie\r\n"
" ~\tchoose next preset for rainbow smoothie\r\n"
" <enter>\tprint parameters\r\n"
" e\tswitch to next Effect\r\n"
" p\tswitch to next palette\r\n"
" {}\tturn extended palette blending on/off\r\n"
" []\tturn linear palette blending on/off\r\n"
" ,.<>\tchange colour cycling rate\r\n"
" bB\tbrightness down/up\r\n"
" dD\tbinary dither off/on\r\n"
" fF\tdecrease/increase fade out rate\r\n"));
}
void print_params() {
Serial.println(F("bright\tfade\txw\tyw\txd\tyd\txdd\tydd\tbcd\tFPS"));
Serial.print(cfg.brightness); Serial.print("\t");
Serial.print(cfg.alpha_fade); Serial.print("\t");
Serial.print(cfg.rs.xw); Serial.print("\t");
Serial.print(cfg.rs.yw); Serial.print("\t");
Serial.print(cfg.rs.xd); Serial.print("\t");
Serial.print(cfg.rs.yd); Serial.print("\t");
Serial.print(cfg.rs.xdd); Serial.print("\t");
Serial.print(cfg.rs.ydd); Serial.print("\t");
Serial.print(cfg.rs.bcd); Serial.print("\t");
Serial.println(FastLED.getFPS());
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// FL_PROGMEM extern const TProgmemPalette16 myRedWhiteBluePalette_p = {
// CRGB::Red, CRGB::Gray, CRGB::Blue, CRGB::Black,
// CRGB::Red, CRGB::Gray, CRGB::Blue, CRGB::Black,
// CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
// CRGB::Blue, CRGB::Blue, CRGB::Black, CRGB::Black
// };
// FL_PROGMEM extern const TProgmemPalette16 myBlackWhitePalette_p = {
// CRGB::Black, CRGB::Black, CRGB::White, CRGB::Grey,
// CRGB::Black, CRGB::Black, CRGB::White, CRGB::Grey,
// CRGB::Black, CRGB::Black, CRGB::White, CRGB::Grey,
// CRGB::Black, CRGB::Black, CRGB::White, CRGB::Grey,
// };
FL_PROGMEM extern const TProgmemRGBPalette16 RainbowStripeColors2_p = {
0xFF0000, 0x7F0000, 0xAB5500, 0x552A00,
0xABAB00, 0x555500, 0x00FF00, 0x007F00,
0x00AB55, 0x00552A, 0x0000FF, 0x00007F,
0x5500AB, 0x2A0055, 0xAB0055, 0x55002A
};
// DEFINE_GRADIENT_PALETTE(froth616_gp) {
// // http://soliton.vm.bytemark.co.uk/pub/cpt-city/fractint/base/tn/froth616.png.index.html
// // Size: 116 bytes of program space.
// 0, 247, 0,247,
// 17, 75, 0, 79,
// 33, 247, 0, 0,
// 51, 75, 0, 0,
// 68, 247,248, 0,
// 84, 75, 91, 0,
// 102, 0,248, 0,
// 119, 0, 91, 0,
// 135, 0, 0,247,
// 153, 0, 0, 79,
// 170, 0,248,247,
// 186, 0, 91, 79,
// 204, 0, 0, 0,
// 237, 0, 0, 0,
// 255, 247,248,247};
FL_PROGMEM extern const TProgmemRGBPalette16 *const palettes16[] = {
&RainbowColors_p, &RainbowStripeColors_p, &RainbowStripeColors2_p,
// &HeatColors_p, &CloudColors_p, &LavaColors_p, &OceanColors_p,
// &ForestColors_p, &PartyColors_p,
// &myRedWhiteBluePalette_p, &myBlackWhitePalette_p
};
#define RGB16PALETTES (sizeof(palettes16) / sizeof(palettes16[0]))
FL_PROGMEM extern const TProgmemRGBGradientPalettePtr palettesGrad[] = {
// froth616_gp,
};
#define RGBGRADPALETTES (sizeof(palettesGrad) / sizeof(TProgmemRGBGradientPalettePtr))
void inc_palette(uint8_t n) {
uint8_t total_palettes = RGB16PALETTES + RGBGRADPALETTES;
cfg.palette_n = n = addmod8(cfg.palette_n, n, total_palettes);
if (n < RGBGRADPALETTES) {
currentPalette = (TProgmemRGBGradientPalettePtr) FL_PGM_READ_PTR_NEAR(&palettesGrad[n]);
return;
}
n -= RGBGRADPALETTES;
if (n < RGB16PALETTES) {
currentPalette = *(TProgmemRGBPalette16 *) FL_PGM_READ_PTR_NEAR(&palettes16[n]);
return;
}
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
FL_PROGMEM const RainbowSmoothiePreset rs_presets[] = {
//xw yw xd yd xdd ydd bcd
{ 4, 3, 80, 208, 15327, -3010, 27000},
// { 4, 4, 14, 14, 6514, -16076, 16384},
{ 1, 1, 34, 86, -7661, 30835, 9600},
// { 7, 12, 105, 46, 5563, 5313,-10240},
// {19, 19, 200, 237, 20142, 14656, -2857},
// {20, 6, 116, 33, -8683, -10095, -4633},
// { 4, 3, 16, 48, 15347, 6636, 3331},
// { 5, 10, 2, 44, -7661, 30835, -2414},
// { 3, 3, 64, 231, 8691, 7618, 3795},
// { 7, 5, 36, 111, 25249, 4738, -1283},
// { 4, 8, 255, 255, -32767, -32690, -6234},
// { 2, 5, 173, 103, -18586, 14361, 3788},
};
#define MAX_RS_PRESETS (sizeof(rs_presets) / sizeof(RainbowSmoothiePreset))
void inc_rs_preset(uint8_t n) {
cfg.rs_preset_n = addmod8(cfg.rs_preset_n, n, MAX_RS_PRESETS);
uint8_t* src = (uint8_t *) &rs_presets[cfg.rs_preset_n];
uint8_t* dst = (uint8_t *) &cfg.rs;
memcpy_P(dst, src, sizeof(cfg.rs));
}
void rainbow_smoothie() {
uint32_t startHue = cfg.frame * cfg.rs.bcd;
uint32_t yHueDelta = ((uint32_t)sin16(cfg.frame * cfg.rs.xd) * cfg.rs.xw);
uint32_t xHueDelta = ((uint32_t)cos16(cfg.frame * cfg.rs.yd) * cfg.rs.yw);
uint32_t lineStartHue = startHue - (kMatrixHeight / 2) * yHueDelta;
uint32_t yd2 = cfg.rs.ydd * 2048;
uint32_t xd2 = cfg.rs.xdd * 2048;
for (byte y = 0; y < kMatrixHeight; y++) {
lineStartHue += yHueDelta;
yHueDelta += yd2;
uint32_t pixelHue = lineStartHue - (kMatrixWidth / 2) * xHueDelta;
uint32_t xhd = xHueDelta;
for (byte x = 0; x < kMatrixWidth; x++) {
pixelHue += xhd;
xhd += xd2;
if (cfg.extendedmixing == 1) {
leds[XY(x, y)] = ColorFromPaletteExtended(currentPalette, pixelHue >> 7, 255, cfg.currentBlending);
} else {
leds[XY(x, y)] = ColorFromPalette(currentPalette, pixelHue >> 15, 255, cfg.currentBlending);
}
}
}
}
void randomise_rainbowsmoothie() {
random16_add_entropy(random());
cfg.rs.bcd = random16(16383) - 8192;
cfg.rs.xd = random8();
cfg.rs.yd = random8();
cfg.rs.xdd = random16() - 32768;
cfg.rs.ydd = random16() - 32768;
cfg.rs.xw = random8(24);
cfg.rs.yw = random8(24);
}
struct hstatus {
uint32_t last_millis;
heatshrink_decoder hsd;
size_t heatsunk;
uint16_t frame;
uint8_t hs_spr_n = -1;
uint8_t loops;
CRGB palette[GIF2H_MAX_PALETTE_ENTRIES];
} hstatus;
// HStatus hstatus;
void heatshrunk_sprite_reset(struct hstatus * status, uint8_t hs_spr_n) {
// decompress the palette and leave the decompressor ready to emit pixels
status->hs_spr_n = hs_spr_n;
status->heatsunk = 0;
status->frame = 0;
HSprite *spr_ptr = (HSprite *) FL_PGM_READ_PTR_NEAR(&hsprite_list[hs_spr_n]);
// uint16_t datasize = FL_PGM_READ_WORD_NEAR(&spr_ptr->datasize);
uint8_t palette_entries = FL_PGM_READ_BYTE_NEAR(&spr_ptr->palette_entries);
// set heatshrink's window buffer position, so the pixels end up aligned with the start of the buffer
heatshrink_decoder_reset(&status->hsd);
status->hsd.head_index -= palette_entries * sizeof(CRGB);
uint8_t *dst = (uint8_t *) &status->palette;
uint16_t remaining = palette_entries * sizeof(CRGB);
size_t count = 0;
while (remaining) {
heatshrink_decoder_sinkP(&status->hsd, &spr_ptr->hs_data[status->heatsunk],
HEATSHRINK_STATIC_INPUT_BUFFER_SIZE, &count);
status->heatsunk += count;
HSD_poll_res pres;
do {
pres = heatshrink_decoder_poll(&status->hsd, dst, remaining, &count);
dst += count;
remaining -= count;
} while ((pres == HSDR_POLL_MORE) && remaining);
}
}
void heatshrunk_sprite_prepare(struct hstatus * status, uint8_t hs_spr_n) {
HSprite *spr_ptr = (HSprite *) FL_PGM_READ_PTR_NEAR(&hsprite_list[hs_spr_n]);
uint16_t datasize = FL_PGM_READ_WORD_NEAR(&spr_ptr->datasize);
uint16_t frames = FL_PGM_READ_WORD_NEAR(&spr_ptr->frames);
uint16_t duration = FL_PGM_READ_WORD_NEAR(&spr_ptr->duration);
// uint8_t flags = FL_PGM_READ_BYTE_NEAR(&spr_ptr->flags);
if (hs_spr_n != status->hs_spr_n) {
// a different sprite has been selected; force a reset
status->frame = frames;
status->loops = 0;
duration = 0;
}
// time to decompress the next frame?
if ((cfg.millis - status->last_millis) < duration) return;
if (frames == 1 && status->loops != 0) return;
// need to loop/reset first?
if (status->frame >= frames) {
heatshrunk_sprite_reset(status, hs_spr_n);
status->loops += 1;
}
status->frame++;
status->last_millis = cfg.millis;
size_t count = 0;
uint16_t remaining = GIF2H_NUM_PIXELS;
while (remaining) {
if (status->heatsunk < datasize) {
heatshrink_decoder_sinkP(&status->hsd, &spr_ptr->hs_data[status->heatsunk],
datasize - status->heatsunk, &count);
status->heatsunk += count;
}
HSD_poll_res pres;
do {
pres = heatshrink_decoder_poll(&status->hsd, 0, remaining, &count);
remaining -= count;
} while ((pres == HSDR_POLL_MORE) && remaining);
if (0 && (status->heatsunk >= datasize)) {
heatshrink_decoder_finish(&status->hsd); // not strictly needed when using static buffers
}
}
}
void heatshrunk_sprite_plot(int8_t xstart, int8_t ystart, struct hstatus * status, const uint8_t fade_speed) {
CRGB *pal = (CRGB *) status->palette;
uint8_t *pixels = status->hsd.buffers + HEATSHRINK_STATIC_INPUT_BUFFER_SIZE;
// plot each sprite pixel modulo the size of the matrix, i.e. wrap the image
for (uint8_t y = 0; y < kMatrixHeight; y++) {
for (uint8_t x = 0; x < kMatrixWidth; x++) {
uint16_t led_index = XY(addmod8(x, xstart, kMatrixWidth),
addmod8(y, ystart, kMatrixHeight));
uint8_t palette_index = *pixels++;
if (palette_index > 0 && cfg.effect != 1) {
// non-0 palette entry; copy the palette entry to the LED
leds[led_index] = pal[palette_index];
} else {
// palette entry 0 is the mask; fade this LED to the mask colour (black usually)
fadeTowardColour(leds[led_index], pal[palette_index], fade_speed);
}
}
}
}
void scintillating_heatshrink() {
static uint8_t spr_n = 0;
static uint32_t last_spr_change = 0;
if ((hstatus.loops > 0) && (cfg.millis - last_spr_change > 2500)) {
last_spr_change = cfg.millis;
spr_n = addmod8(spr_n, 1, HSPRITES_N);
}
heatshrunk_sprite_prepare(&hstatus, spr_n);
heatshrunk_sprite_plot(0 /*- (cos8(cfg.frame & 0x7f) >> 3)*/, 0, &hstatus, cfg.alpha_fade);
}