Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion include/crgbw.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@
//
//---------------------------------------------------------------------------

#include <cstdint>
#include "globals.h"

#include <algorithm>
#include <cstdint>

struct CRGBW
{
Expand Down
146 changes: 77 additions & 69 deletions include/effects/matrix/PatternAnimatedGIF.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

#include "effects.h"
#include "GifDecoder.h"
#include "globals.h"
#include "hub75gfx.h"
#include "ledstripeffect.h"
#include "systemcontainer.h"
Expand Down Expand Up @@ -106,69 +107,68 @@ struct GIFInfo : public EmbeddedFile
{}
};

static const std::map<GIFIdentifier, const GIFInfo> AnimatedGIFs =
{
// Banana has 8 frames. Most music is around 120BPM, so we need to play each frame for 1/15th of a second to somewhat align with a typical beat
{ GIFIdentifier::OnAir, GIFInfo(onair_start, onair_end, 64, 32, 14 ) }, // 8 KB
{ GIFIdentifier::Banana, GIFInfo(banana_start, banana_end, 32, 32, 10 ) }, // 4 KB
{ GIFIdentifier::Nyancat, GIFInfo(nyancat_start, nyancat_end, 64, 32, 18 ) }, // 20 KB
{ GIFIdentifier::Pacman, GIFInfo(pacman_start, pacman_end, 64, 12, 20 ) }, // 36 KB
{ GIFIdentifier::Atomic, GIFInfo(atomic_start, atomic_end, 32, 32, 60 ) }, // 21 KB
{ GIFIdentifier::ColorSphere, GIFInfo(colorsphere_start, colorsphere_end, 32, 32, 16 ) }, // 52 KB
{ GIFIdentifier::ThreeRings, GIFInfo(threerings_start, threerings_end, 64, 32, 24 ) }, // 9 KB
{ GIFIdentifier::Tesseract, GIFInfo(tesseract_start, tesseract_end, 40, 32, 40 ) }, // 24 KB
{ GIFIdentifier::Firelog, GIFInfo(firelog_start, firelog_end, 64, 32, 16 ) }, // 24 KB
};

// The decoder needs us to track some state, but there's only one instance of the decoder, and
// we can't pass it a pointer to our state because the callback doesn't allow you to pass any
// context, and you can't use a lambda that captures the this pointer because that can't be
// converted to a callback function pointer. So we have to use a global.

struct
{
int _offsetX = 0;
int _offsetY = 0;
uint8_t _fps = 24;
CRGB _bkColor = CRGB::Black;
// Scaling parameters for best-fit rendering
float _scaleX = 1.0f;
float _scaleY = 1.0f;
uint16_t _srcWidth = 0;
uint16_t _srcHeight = 0;
uint16_t _dstWidth = 0;
uint16_t _dstHeight = 0;
}
g_gifDecoderState;

// We dynamically allocate the GIF decoder because it's pretty big and we don't want to waste the base
// ram on it. This way it, and the GIFs it decodes, can live in PSRAM.

const std::unique_ptr<GifDecoder<MATRIX_WIDTH, MATRIX_HEIGHT, 16, true>> g_ptrGIFDecoder = std::make_unique<GifDecoder<MATRIX_WIDTH, MATRIX_HEIGHT, 16, true>>();

// PatternAnimatedGIF
//
// Draws a cycling animated GIF on the LED matrix. Use GifDecoder to do the heavy lifting behind the scenes.

class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>
{
private:
struct DecoderState
{
int _offsetX = 0;
int _offsetY = 0;
uint8_t _fps = 24;
CRGB _bkColor = CRGB::Black;
// Scaling parameters for best-fit rendering
float _scaleX = 1.0f;
float _scaleY = 1.0f;
uint16_t _srcWidth = 0;
uint16_t _srcHeight = 0;
uint16_t _dstWidth = 0;
uint16_t _dstHeight = 0;
} _decoderState;

// We dynamically allocate the GIF decoder because it's pretty big and we don't want to waste the base
// ram on it. This way it, and the GIFs it decodes, can live in PSRAM.
std::unique_ptr<GifDecoder<MATRIX_WIDTH, MATRIX_HEIGHT, 16, true>> _ptrGIFDecoder;

const std::map<GIFIdentifier, const GIFInfo> _animatedGIFs =
{
// Banana has 8 frames. Most music is around 120BPM, so we need to play each frame for 1/15th of a second to somewhat align with a typical beat
{ GIFIdentifier::OnAir, GIFInfo(onair_start, onair_end, 64, 32, 14 ) }, // 8 KB
{ GIFIdentifier::Banana, GIFInfo(banana_start, banana_end, 32, 32, 10 ) }, // 4 KB
{ GIFIdentifier::Nyancat, GIFInfo(nyancat_start, nyancat_end, 64, 32, 18 ) }, // 20 KB
{ GIFIdentifier::Pacman, GIFInfo(pacman_start, pacman_end, 64, 12, 20 ) }, // 36 KB
{ GIFIdentifier::Atomic, GIFInfo(atomic_start, atomic_end, 32, 32, 60 ) }, // 21 KB
{ GIFIdentifier::ColorSphere, GIFInfo(colorsphere_start, colorsphere_end, 32, 32, 16 ) }, // 52 KB
{ GIFIdentifier::ThreeRings, GIFInfo(threerings_start, threerings_end, 64, 32, 24 ) }, // 9 KB
{ GIFIdentifier::Tesseract, GIFInfo(tesseract_start, tesseract_end, 40, 32, 40 ) }, // 24 KB
{ GIFIdentifier::Firelog, GIFInfo(firelog_start, firelog_end, 64, 32, 16 ) }, // 24 KB
};

GIFIdentifier _gifIndex = GIFIdentifier::INVALID;
CRGB _bkColor = BLACK16;
bool _preClear = false;
bool _gifReadyToDraw = false;

// GIF decoder callbacks. These are static because the decoder doesn't allow you to pass any context, so they
// have to be global. We use the global g_gifDecoderState to track state. The GifDecoder code calls back to
// these callbacks to do the actual work of plotting them on the LED matrix.
// have to be global. We use a static helper to track the current effect instance.
static PatternAnimatedGIF*& ActiveInstance()
{
static PatternAnimatedGIF* pActiveInstance = nullptr;
return pActiveInstance;
}

// screenClearCallback - clears the screen with the color given to the constructor

static void screenClearCallback(void)
{
auto& g = g_ptrSystem->GetEffectManager().g();
g.Clear(g_gifDecoderState._bkColor);
if (ActiveInstance())
{
auto& g = g_ptrSystem->GetEffectManager().g();
g.Clear(ActiveInstance()->_decoderState._bkColor);
}
}

// We decide when to update the screen, so this is a no-op
Expand All @@ -184,11 +184,14 @@ class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>

static void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue)
{
if (!ActiveInstance())
return;

auto& g = g_ptrSystem->GetEffectManager().g(0);

// Apply scaling transformation
int16_t scaledX = (int16_t)(x * g_gifDecoderState._scaleX) + g_gifDecoderState._offsetX;
int16_t scaledY = (int16_t)(y * g_gifDecoderState._scaleY) + g_gifDecoderState._offsetY;
int16_t scaledX = (int16_t)(x * ActiveInstance()->_decoderState._scaleX) + ActiveInstance()->_decoderState._offsetX;
int16_t scaledY = (int16_t)(y * ActiveInstance()->_decoderState._scaleY) + ActiveInstance()->_decoderState._offsetY;

if (false == g.isValidPixel(scaledX, scaledY))
{
Expand Down Expand Up @@ -216,14 +219,14 @@ class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>
// For slower animations that run at a lower framerate, we double the framerate by discarding every other frame,
// which allows us to draw the VU meter and so on at a useable rate even though the animation doesn't paint every time.

static bool FrameDoubling()
bool FrameDoubling() const
{
return g_gifDecoderState._fps <= 15;
return _decoderState._fps <= 15;
}

size_t DesiredFramesPerSecond() const override
{
return FrameDoubling() ? g_gifDecoderState._fps * 2 : g_gifDecoderState._fps;
return FrameDoubling() ? _decoderState._fps * 2 : _decoderState._fps;
}

public:
Expand All @@ -234,6 +237,7 @@ class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>
_gifIndex(gifIndex),
_bkColor(bkColor)
{
_ptrGIFDecoder = std::make_unique<GifDecoder<MATRIX_WIDTH, MATRIX_HEIGHT, 16, true>>();
}

PatternAnimatedGIF(const JsonObjectConst& jsonObject)
Expand All @@ -242,6 +246,7 @@ class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>
_gifIndex((GIFIdentifier)jsonObject[PTY_GIFINDEX].as<std::underlying_type_t<GIFIdentifier>>()),
_bkColor(jsonObject[PTY_BKCOLOR])
{
_ptrGIFDecoder = std::make_unique<GifDecoder<MATRIX_WIDTH, MATRIX_HEIGHT, 16, true>>();
}

bool SerializeToJSON(JsonObject& jsonObject) override
Expand All @@ -264,11 +269,11 @@ class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>

// Open the GIF and start decoding

auto gif = AnimatedGIFs.find(_gifIndex);
if (gif == AnimatedGIFs.end())
auto gif = _animatedGIFs.find(_gifIndex);
if (gif == _animatedGIFs.end())
throw std::runtime_error(str_sprintf("Unable to locate GIF by index %d in the map.", (int) _gifIndex).c_str());

// Set up the gifDecoderState with all of the context that it will need to decode and
// Set up the _decoderState with all of the context that it will need to decode and
// draw the GIF, since the static callbacks will have no other context to work with.

// Calculate best-fit scaling if the GIF is larger than the matrix
Expand Down Expand Up @@ -301,25 +306,25 @@ class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>
debugI("GIF scaling: %dx%d -> %dx%d (scale %.2f,%.2f) offset (%d,%d)",
(int)gifWidth, (int)gifHeight, (int)dstWidth, (int)dstHeight, scaleX, scaleY, (int)offsetX, (int)offsetY);

g_gifDecoderState._offsetX = offsetX;
g_gifDecoderState._offsetY = offsetY;
g_gifDecoderState._fps = gif->second._fps;
g_gifDecoderState._bkColor = _bkColor;
g_gifDecoderState._scaleX = scaleX;
g_gifDecoderState._scaleY = scaleY;
g_gifDecoderState._srcWidth = gifWidth;
g_gifDecoderState._srcHeight = gifHeight;
g_gifDecoderState._dstWidth = dstWidth;
g_gifDecoderState._dstHeight = dstHeight;
_decoderState._offsetX = offsetX;
_decoderState._offsetY = offsetY;
_decoderState._fps = gif->second._fps;
_decoderState._bkColor = _bkColor;
_decoderState._scaleX = scaleX;
_decoderState._scaleY = scaleY;
_decoderState._srcWidth = gifWidth;
_decoderState._srcHeight = gifHeight;
_decoderState._dstWidth = dstWidth;
_decoderState._dstHeight = dstHeight;

// Set the GIF decoder callbacks to our static functions

g_ptrGIFDecoder->setScreenClearCallback( screenClearCallback );
g_ptrGIFDecoder->setUpdateScreenCallback( updateScreenCallback );
g_ptrGIFDecoder->setDrawPixelCallback( drawPixelCallback );
g_ptrGIFDecoder->setDrawLineCallback( drawLineCallback );
_ptrGIFDecoder->setScreenClearCallback( screenClearCallback );
_ptrGIFDecoder->setUpdateScreenCallback( updateScreenCallback );
_ptrGIFDecoder->setDrawPixelCallback( drawPixelCallback );
_ptrGIFDecoder->setDrawLineCallback( drawLineCallback );

_gifReadyToDraw = (ERROR_NONE == g_ptrGIFDecoder->startDecoding((uint8_t *) gif->second.contents, gif->second.length));
_gifReadyToDraw = (ERROR_NONE == _ptrGIFDecoder->startDecoding((uint8_t *) gif->second.contents, gif->second.length));
if (!_gifReadyToDraw)
debugW("Failed to start decoding GIF");
}
Expand All @@ -345,8 +350,11 @@ class PatternAnimatedGIF : public EffectWithId<PatternAnimatedGIF>
g().Clear(_bkColor);

if (_gifReadyToDraw)
g_ptrGIFDecoder->decodeFrame(false);

{
ActiveInstance() = this;
_ptrGIFDecoder->decodeFrame(false);
ActiveInstance() = nullptr;
}
}
};

Expand Down
60 changes: 27 additions & 33 deletions include/effects/matrix/PatternWeather.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

#include "effects.h"
#include "array_utils.h"
#include "globals.h"
#include "systemcontainer.h"
#include "TJpg_Decoder.h"
#include "types.h"
Expand Down Expand Up @@ -100,39 +101,32 @@ extern const uint8_t thunderstorm_night_end[] asm("_binary_assets_bmp_thun

static constexpr auto pszDaysOfWeek = to_array( { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" } );

// This lookup table is tiny and constructed during global initialization, before the system has
// completed its normal startup path. Keep it on the regular heap so boot does not depend on PSRAM.

static std::map<const String, EmbeddedFile> weatherIcons =
{
{ "01d", EmbeddedFile(clearsky_start, clearsky_end) },
{ "02d", EmbeddedFile(fewclouds_start, fewclouds_end) },
{ "03d", EmbeddedFile(scatteredclouds_start, scatteredclouds_end) },
{ "04d", EmbeddedFile(brokenclouds_start, brokenclouds_end) },
{ "09d", EmbeddedFile(showerrain_start, showerrain_end) },
{ "10d", EmbeddedFile(rain_start, rain_end) },
{ "11d", EmbeddedFile(thunderstorm_start, thunderstorm_end) },
{ "13d", EmbeddedFile(snow_start, snow_end) },
{ "50d", EmbeddedFile(mist_start, mist_end) },
{ "01n", EmbeddedFile(clearsky_night_start, clearsky_night_end) },
{ "02n", EmbeddedFile(fewclouds_night_start, fewclouds_night_end) },
{ "03n", EmbeddedFile(scatteredclouds_night_start, scatteredclouds_night_end) },
{ "04n", EmbeddedFile(brokenclouds_night_start, brokenclouds_night_end) },
{ "09n", EmbeddedFile(showerrain_night_start, showerrain_night_end) },
{ "10n", EmbeddedFile(rain_night_start, rain_night_end) },
{ "11n", EmbeddedFile(thunderstorm_night_start, thunderstorm_night_end) },
{ "13n", EmbeddedFile(snow_night_start, snow_night_end) },
{ "50n", EmbeddedFile(mist_night_start, mist_night_end) }
};

/**
* @brief This class implements the Weather Data effect
*
*/
class PatternWeather : public EffectWithId<PatternWeather>
{
private:

const std::map<const String, EmbeddedFile> _weatherIcons =
{
{ "01d", EmbeddedFile(clearsky_start, clearsky_end) },
{ "02d", EmbeddedFile(fewclouds_start, fewclouds_end) },
{ "03d", EmbeddedFile(scatteredclouds_start, scatteredclouds_end) },
{ "04d", EmbeddedFile(brokenclouds_start, brokenclouds_end) },
{ "09d", EmbeddedFile(showerrain_start, showerrain_end) },
{ "10d", EmbeddedFile(rain_start, rain_end) },
{ "11d", EmbeddedFile(thunderstorm_start, thunderstorm_end) },
{ "13d", EmbeddedFile(snow_start, snow_end) },
{ "50d", EmbeddedFile(mist_start, mist_end) },
{ "01n", EmbeddedFile(clearsky_night_start, clearsky_night_end) },
{ "02n", EmbeddedFile(fewclouds_night_start, fewclouds_night_end) },
{ "03n", EmbeddedFile(scatteredclouds_night_start, scatteredclouds_night_end) },
{ "04n", EmbeddedFile(brokenclouds_night_start, brokenclouds_night_end) },
{ "09n", EmbeddedFile(showerrain_night_start, showerrain_night_end) },
{ "10n", EmbeddedFile(rain_night_start, rain_night_end) },
{ "11n", EmbeddedFile(thunderstorm_night_start, thunderstorm_night_end) },
{ "13n", EmbeddedFile(snow_night_start, snow_night_end) },
{ "50n", EmbeddedFile(mist_night_start, mist_night_end) }
};

String strLocationName = "";
String strLocation = "";
String strCountryCode = "";
Expand Down Expand Up @@ -513,16 +507,16 @@ class PatternWeather : public EffectWithId<PatternWeather>
}

// Draw the graphics
auto iconEntry = weatherIcons.find(iconToday);
if (iconEntry != weatherIcons.end())
auto iconEntry = _weatherIcons.find(iconToday);
if (iconEntry != _weatherIcons.end())
{
auto icon = iconEntry->second;
if (JDR_OK != TJpgDec.drawJpg(0, 10, icon.contents, icon.length)) // Draw the image
debugW("Could not display icon %s", iconToday.c_str());
}

iconEntry = weatherIcons.find(iconTomorrow);
if (iconEntry != weatherIcons.end())
iconEntry = _weatherIcons.find(iconTomorrow);
if (iconEntry != _weatherIcons.end())
{
auto icon = iconEntry->second;
if (JDR_OK != TJpgDec.drawJpg(xHalf+1, 10, icon.contents, icon.length)) // Draw the image
Expand Down
2 changes: 1 addition & 1 deletion include/effects/strip/fireeffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ class SmoothFireEffect : public EffectWithId<SmoothFireEffect>
LEDStripEffect::Init(gfx);
// Large effect-state buffer; the PSRAM-default policy in main.cpp
// routes this through PSRAM automatically once it crosses the
// configured threshold, so no explicit *_psram helper is needed.
// configured threshold.
_Temperatures = std::make_unique<float[]>(_cLEDs);
if (!_Temperatures)
{
Expand Down
2 changes: 1 addition & 1 deletion include/gfxbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
#include <mutex>

#include "Adafruit_GFX.h"
#include "pixeltypes.h"
#include "crgbw.h"
#include "pixeltypes.h"

// Calculates a weight for anti-aliasing in Wu's algorithm.
constexpr static inline uint8_t WU_WEIGHT(uint8_t a, uint8_t b)
Expand Down
Loading