updated libraries
This commit is contained in:
parent
d5d5d000b3
commit
3b7e68065a
102 changed files with 3020 additions and 1624 deletions
|
|
@ -57,7 +57,7 @@ uint8_t get_brightness();
|
|||
void *pSmartMatrix = NULL;
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
|
||||
CFastLED FastLED; // global constructor allowed in this case.
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@
|
|||
/// * 1 digit for the major version
|
||||
/// * 3 digits for the minor version
|
||||
/// * 3 digits for the patch version
|
||||
#define FASTLED_VERSION 3009020
|
||||
#define FASTLED_VERSION 301001
|
||||
#ifndef FASTLED_INTERNAL
|
||||
# ifdef FASTLED_SHOW_VERSION
|
||||
# ifdef FASTLED_HAS_PRAGMA_MESSAGE
|
||||
# pragma message "FastLED version 3.009.020"
|
||||
# pragma message "FastLED version 3.010.001"
|
||||
# else
|
||||
# warning FastLED version 3.009.020 (Not really a warning, just telling you here.)
|
||||
# warning FastLED version 3.010.001 (Not really a warning, just telling you here.)
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
|
@ -968,3 +968,6 @@ using namespace fl;
|
|||
void loop() { FASTLED_WARN("hijacked the loop"); real_loop(); } \
|
||||
void real_loop()
|
||||
#endif
|
||||
|
||||
|
||||
#include "fl/sketch_macros.h"
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
#include "FastLED.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
#include "fl/bilinear_expansion.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/downscale.h"
|
||||
#include "lib8tion/math8.h"
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ void CRGB::upscale(const CRGB *src, const fl::XYMap &srcXY, CRGB *dst,
|
|||
"Upscaling only works with a src matrix that is rectangular");
|
||||
uint16_t w = srcXY.getWidth();
|
||||
uint16_t h = srcXY.getHeight();
|
||||
fl::bilinearExpand(src, dst, w, h, dstXY);
|
||||
fl::upscale(src, dst, w, h, dstXY);
|
||||
}
|
||||
|
||||
CRGB &CRGB::nscale8(uint8_t scaledown) {
|
||||
|
|
|
|||
|
|
@ -11,4 +11,159 @@ void reverse(Iterator first, Iterator last) {
|
|||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
Iterator max_element(Iterator first, Iterator last) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator max_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (*max_iter < *first) {
|
||||
max_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return max_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator max_element(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator max_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (comp(*max_iter, *first)) {
|
||||
max_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return max_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
Iterator min_element(Iterator first, Iterator last) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator min_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (*first < *min_iter) {
|
||||
min_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return min_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator min_element(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator min_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (comp(*first, *min_iter)) {
|
||||
min_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return min_iter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename Iterator1, typename Iterator2>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2) {
|
||||
while (first1 != last1) {
|
||||
if (*first1 != *first2) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryPredicate pred) {
|
||||
while (first1 != last1) {
|
||||
if (!pred(*first1, *first2)) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) {
|
||||
while (first1 != last1 && first2 != last2) {
|
||||
if (*first1 != *first2) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return first1 == last1 && first2 == last2;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, BinaryPredicate pred) {
|
||||
while (first1 != last1 && first2 != last2) {
|
||||
if (!pred(*first1, *first2)) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return first1 == last1 && first2 == last2;
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2>
|
||||
bool equal_container(const Container1& c1, const Container2& c2) {
|
||||
size_t size1 = c1.size();
|
||||
size_t size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end());
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2, typename BinaryPredicate>
|
||||
bool equal_container(const Container1& c1, const Container2& c2, BinaryPredicate pred) {
|
||||
size_t size1 = c1.size();
|
||||
size_t size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end(), pred);
|
||||
}
|
||||
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
void fill(Iterator first, Iterator last, const T& value) {
|
||||
while (first != last) {
|
||||
*first = value;
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ void swap(array<T, N> &lhs,
|
|||
TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
memset(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#elif __has_include(<cstdlib>)
|
||||
#include <cstdlib>
|
||||
#include <cstdlib> // ok include
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
memset(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/deprecated.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
|
@ -19,47 +21,62 @@ namespace fl {
|
|||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
|
||||
void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
fl::XYMap xyMap);
|
||||
fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscaleArbitrary from upscale.h");
|
||||
|
||||
/// @brief Performs bilinear interpolation for upscaling an image.
|
||||
/// @param output The output grid to write into the interpolated values.
|
||||
/// @param input The input grid to read from.
|
||||
/// @param inputWidth The width of the input grid.
|
||||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
uint8_t inputHeight, fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscalePowerOf2 from upscale.h");
|
||||
|
||||
//
|
||||
inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap) {
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
const bool wontFit =
|
||||
(outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight());
|
||||
// if the input dimensions are not a power of 2 then we can't use the
|
||||
// optimized version.
|
||||
if (wontFit || (inputWidth & (inputWidth - 1)) ||
|
||||
(inputHeight & (inputHeight - 1))) {
|
||||
bilinearExpandArbitrary(input, output, inputWidth, inputHeight, xyMap);
|
||||
} else {
|
||||
bilinearExpandPowerOf2(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
}
|
||||
void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscale from upscale.h");
|
||||
|
||||
// These are here for testing purposes and are slow. Their primary use
|
||||
// is to test against the fixed integer version above.
|
||||
void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
uint8_t inputHeight, fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscaleFloat from upscale.h");
|
||||
|
||||
void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
fl::XYMap xyMap);
|
||||
fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscaleArbitraryFloat from upscale.h");
|
||||
|
||||
uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy);
|
||||
uint8_t v11, float dx, float dy)
|
||||
FASTLED_DEPRECATED("use upscaleFloat from upscale.h");
|
||||
|
||||
////////////////// Inline definitions for backward compatibility ///////////////////////
|
||||
|
||||
inline void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
fl::XYMap xyMap) {
|
||||
upscaleArbitrary(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline void bilinearExpandPowerOf2(const CRGB *input, CRGB *output,
|
||||
uint8_t inputWidth, uint8_t inputHeight,
|
||||
fl::XYMap xyMap) {
|
||||
upscalePowerOf2(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap) {
|
||||
upscale(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth,
|
||||
uint16_t inputHeight,
|
||||
fl::XYMap xyMap) {
|
||||
upscaleArbitraryFloat(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy) {
|
||||
return upscaleFloat(v00, v10, v01, v11, dx, dy);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@
|
|||
#include "fl/leds.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
template<typename T>
|
||||
class Grid;
|
||||
|
||||
// Memory safe clear function for CRGB arrays.
|
||||
template <int N> inline void clear(CRGB (&arr)[N]) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
|
|
@ -18,4 +23,17 @@ inline void clear(LedsXY<W, H> &leds) {
|
|||
leds.fill(CRGB::Black);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void clear(Grid<T> &grid) {
|
||||
grid.clear();
|
||||
}
|
||||
|
||||
// Default, when you don't know what do then call clear.
|
||||
template<typename Container>
|
||||
inline void clear(Container &container) {
|
||||
container.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -19,3 +19,11 @@
|
|||
#define FL_DISABLE_WARNING_POP
|
||||
#define FL_DISABLE_WARNING(warning)
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__clang__)
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS /* nothing */
|
||||
#endif
|
||||
|
|
@ -1,138 +1,142 @@
|
|||
#include "fl/corkscrew.h"
|
||||
#include "fl/algorithm.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/splat.h"
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
|
||||
#define TWO_PI (PI * 2.0)
|
||||
|
||||
namespace fl {
|
||||
|
||||
void generateMap(const Corkscrew::Input &input, CorkscrewOutput &output);
|
||||
void generateState(const Corkscrew::Input &input, CorkscrewState *output);
|
||||
|
||||
void generateMap(const Corkscrew::Input &input, CorkscrewOutput &output) {
|
||||
// Calculate circumference per turn from height and total angle
|
||||
float circumferencePerTurn = input.totalHeight * TWO_PI / input.totalAngle;
|
||||
void generateState(const Corkscrew::Input &input, CorkscrewState *output) {
|
||||
|
||||
// Calculate vertical segments based on number of turns
|
||||
// For a single turn (2π), we want exactly 1 vertical segment
|
||||
// For two turns (4π), we want exactly 2 vertical segments
|
||||
uint16_t verticalSegments = round(input.totalAngle / TWO_PI);
|
||||
// uint16_t verticalSegments = ceil(input.totalTurns);
|
||||
|
||||
// Calculate width based on LED density per turn
|
||||
float ledsPerTurn = static_cast<float>(input.numLeds) / verticalSegments;
|
||||
// float ledsPerTurn = static_cast<float>(input.numLeds) / verticalSegments;
|
||||
|
||||
// Determine cylindrical dimensions
|
||||
output.height = verticalSegments;
|
||||
output.width = ceil(ledsPerTurn);
|
||||
|
||||
output.mapping.clear();
|
||||
output->mapping.clear();
|
||||
output->width = 0; // we will change this below.
|
||||
output->height = 0;
|
||||
|
||||
// If numLeds is specified, use that for mapping size instead of grid
|
||||
if (input.numLeds > 0) {
|
||||
output.mapping.reserve(input.numLeds);
|
||||
output->mapping.reserve(input.numLeds);
|
||||
// Generate LED mapping based on numLeds
|
||||
// Note that width_step should be 1.0f/float(input.numLeds) so last led in a
|
||||
// turn does not wrap around.
|
||||
const float width_step =
|
||||
1.0f / float(input.numLeds); // Corkscrew reaches max width on last led.
|
||||
const float height_step =
|
||||
1.0f /
|
||||
float(input.numLeds - 1); // Corkscrew reaches max height on last led.
|
||||
// const float led_width_factor = circumferencePerTurn / TWO_PI;
|
||||
const float length_per_turn = input.numLeds / input.totalTurns;
|
||||
|
||||
// Generate LED mapping based on numLeds
|
||||
for (uint16_t i = 0; i < input.numLeds; ++i) {
|
||||
// Calculate position along the corkscrew (0.0 to 1.0)
|
||||
float position = static_cast<float>(i) / (input.numLeds - 1);
|
||||
for (uint16_t i = 0; i < input.numLeds; ++i) {
|
||||
// Calculate position along the corkscrew (0.0 to 1.0)
|
||||
const float i_f = static_cast<float>(i);
|
||||
const float alpha_width = i_f * width_step;
|
||||
const float alpha_height = i_f * height_step;
|
||||
const float width_before_mod = alpha_width * input.totalLength;
|
||||
const float height = alpha_height * input.totalHeight;
|
||||
const float width = fmodf(width_before_mod, length_per_turn);
|
||||
output->mapping.push_back({width, height});
|
||||
}
|
||||
|
||||
// Calculate angle and height
|
||||
float angle = position * input.totalAngle;
|
||||
float height = position * verticalSegments;
|
||||
|
||||
// Calculate circumference position
|
||||
float circumference = fmodf(angle * circumferencePerTurn / TWO_PI,
|
||||
circumferencePerTurn);
|
||||
|
||||
// Store the mapping
|
||||
output.mapping.push_back({circumference, height});
|
||||
}
|
||||
} else {
|
||||
// Original grid-based mapping
|
||||
output.mapping.reserve(output.width * output.height);
|
||||
|
||||
// Corrected super sampling step size
|
||||
float thetaStep = 0.5f / output.width;
|
||||
float hStep = 0.5f / output.height;
|
||||
|
||||
// Precompute angle per segment
|
||||
float anglePerSegment = input.totalAngle / verticalSegments;
|
||||
|
||||
// Loop over cylindrical pixels
|
||||
for (uint16_t h = 0; h < output.height; ++h) {
|
||||
float segmentOffset = input.offsetCircumference * h;
|
||||
for (uint16_t w = 0; w < output.width; ++w) {
|
||||
vec2f sample = {0, 0};
|
||||
// 2x2 supersampling
|
||||
for (uint8_t ssH = 0; ssH < 2; ++ssH) {
|
||||
for (uint8_t ssW = 0; ssW < 2; ++ssW) {
|
||||
float theta =
|
||||
(w + 0.5f + ssW * thetaStep) / output.width;
|
||||
float height = (h + 0.5f + ssH * hStep) / output.height;
|
||||
|
||||
// Corkscrew projection (θ,h)
|
||||
float corkscrewTheta =
|
||||
theta * TWO_PI + anglePerSegment * h;
|
||||
float corkscrewH = height * verticalSegments;
|
||||
|
||||
// Apply circumference offset
|
||||
float corkscrewCircumference = fmodf(
|
||||
corkscrewTheta * circumferencePerTurn / TWO_PI +
|
||||
segmentOffset,
|
||||
circumferencePerTurn);
|
||||
|
||||
// Accumulate samples
|
||||
sample.x += corkscrewCircumference;
|
||||
sample.y += corkscrewH;
|
||||
}
|
||||
}
|
||||
|
||||
// Average the supersampled points
|
||||
sample.x *= 0.25f;
|
||||
sample.y *= 0.25f;
|
||||
|
||||
output.mapping.push_back(sample);
|
||||
}
|
||||
if (!output->mapping.empty()) {
|
||||
float max_width = 0.0f;
|
||||
float max_height = 0.0f;
|
||||
for (const auto &point : output->mapping) {
|
||||
max_width = MAX(max_width, point.x);
|
||||
max_height = MAX(max_height, point.y);
|
||||
}
|
||||
output->width = static_cast<uint16_t>(ceilf(max_width)) + 1;
|
||||
output->height = static_cast<uint16_t>(ceilf(max_height)) + 1;
|
||||
}
|
||||
|
||||
// Apply inversion if requested
|
||||
if (input.invert) {
|
||||
fl::reverse(output.mapping.begin(), output.mapping.end());
|
||||
fl::reverse(output->mapping.begin(), output->mapping.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Corkscrew::Corkscrew(const Corkscrew::Input &input) : mInput(input) {
|
||||
fl::generateMap(mInput, mOutput);
|
||||
fl::generateState(mInput, &mState);
|
||||
}
|
||||
|
||||
vec2f Corkscrew::at(uint16_t i) const {
|
||||
if (i >= mOutput.mapping.size()) {
|
||||
vec2f Corkscrew::at_exact(uint16_t i) const {
|
||||
if (i >= mState.mapping.size()) {
|
||||
// Handle out-of-bounds access, possibly by returning a default value
|
||||
return vec2f(0, 0);
|
||||
}
|
||||
// Convert the float position to integer
|
||||
const vec2f &position = mOutput.mapping[i];
|
||||
const vec2f &position = mState.mapping[i];
|
||||
return position;
|
||||
}
|
||||
|
||||
Tile2x2_u8 Corkscrew::at_splat(uint16_t i) const {
|
||||
if (i >= mOutput.mapping.size()) {
|
||||
|
||||
Tile2x2_u8 Corkscrew::at_splat_extrapolate(float i) const {
|
||||
// To finish this, we need to handle wrap around.
|
||||
// To accomplish this we need a different data structure than the the
|
||||
// Tile2x2_u8.
|
||||
// 1. It will be called CorkscrewTile2x2_u8.
|
||||
// 2. The four alpha values will each contain the index the LED is at,
|
||||
// uint16_t.
|
||||
// 3. There will be no origin, each pixel in the tile will contain a
|
||||
// uint16_t origin. This is not supposed to be a storage format, but a
|
||||
// convenient pre-computed value for rendering.
|
||||
if (i >= mState.mapping.size()) {
|
||||
// Handle out-of-bounds access, possibly by returning a default
|
||||
// Tile2x2_u8
|
||||
FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat: "
|
||||
<< i << " size: " << mState.mapping.size());
|
||||
return Tile2x2_u8();
|
||||
}
|
||||
// Use the splat function to convert the vec2f to a Tile2x2_u8
|
||||
return splat(mOutput.mapping[i]);
|
||||
float i_floor = floorf(i);
|
||||
float i_ceil = ceilf(i);
|
||||
if (ALMOST_EQUAL_FLOAT(i_floor, i_ceil)) {
|
||||
// If the index is the same, just return the splat of that index
|
||||
return splat(mState.mapping[static_cast<uint16_t>(i_floor)]);
|
||||
} else {
|
||||
// Interpolate between the two points and return the splat of the result
|
||||
vec2f pos1 = mState.mapping[static_cast<uint16_t>(i_floor)];
|
||||
vec2f pos2 = mState.mapping[static_cast<uint16_t>(i_ceil)];
|
||||
|
||||
if (pos2.x < pos1.x) {
|
||||
// If the next point is on the other side of the cylinder, we need
|
||||
// to wrap it around and bring it back into the positive direction so we can construct a Tile2x2_u8 wrap with it.
|
||||
pos2.x += mState.width;
|
||||
}
|
||||
|
||||
vec2f interpolated_pos =
|
||||
pos1 * (1.0f - (i - i_floor)) + pos2 * (i - i_floor);
|
||||
return splat(interpolated_pos);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Corkscrew::size() const { return mOutput.mapping.size(); }
|
||||
size_t Corkscrew::size() const { return mState.mapping.size(); }
|
||||
|
||||
CorkscrewOutput Corkscrew::generateMap(const Input &input) {
|
||||
CorkscrewOutput output;
|
||||
fl::generateMap(input, output);
|
||||
Corkscrew::State Corkscrew::generateState(const Corkscrew::Input &input) {
|
||||
CorkscrewState output;
|
||||
fl::generateState(input, &output);
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
|
||||
Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const {
|
||||
// This is a splatted pixel, but wrapped around the cylinder.
|
||||
// This is useful for rendering the corkscrew in a cylindrical way.
|
||||
Tile2x2_u8 tile = at_splat_extrapolate(i);
|
||||
return Tile2x2_u8_wrap(tile, mState.width, mState.height);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -4,15 +4,15 @@
|
|||
* @file corkscrew.h
|
||||
* @brief Corkscrew projection utilities
|
||||
*
|
||||
* Corkscrew projection maps from Corkscrew (θ, h) to Cylindrical cartesian (w,
|
||||
* h) space, where w = one turn of the Corkscrew. The corkscrew at (0,0) will
|
||||
* map to (0,0) in cylindrical space.
|
||||
* You want to draw on a rectangular surface, and have it map to a GOD DAMN
|
||||
* CORKSCREW! Well guess what, this is the file for you.
|
||||
*
|
||||
* Corkscrew projection maps from Corkscrew angle height, (θ, h) to Cylindrical
|
||||
* cartesian (w, h) space, where w = one turn of the Corkscrew. The corkscrew at
|
||||
* (0) will map to the first index in the cylinder map at (0, 0). The last value
|
||||
* is probly not at the max pixel value at (width - 1, height - 1), but could
|
||||
* be.
|
||||
*
|
||||
* The projection:
|
||||
* - Super samples cylindrical space?
|
||||
* - θ is normalized to [0, 1] or mapped to [0, W-1] for grid projection
|
||||
* - Uses 2x2 super sampling for better visual quality
|
||||
* - Works with XYPathRenderer's "Splat Rendering" for sub-pixel rendering
|
||||
*
|
||||
* Inputs:
|
||||
* - Total Height of the Corkscrew in centimeters
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
#include "fl/allocator.h"
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
|
|
@ -46,41 +47,43 @@ namespace fl {
|
|||
* @return The resulting cylindrical mapping.
|
||||
*/
|
||||
struct CorkscrewInput {
|
||||
float totalLength = 100; // Total length of the corkscrew in centimeters,
|
||||
// set to dense 144 strips.
|
||||
float totalHeight = 23.25; // Total height of the corkscrew in centimeters
|
||||
// for 144 densly wrapped up over 19 turns
|
||||
float totalAngle = 19.f * 2 * PI; // Default to 19 turns
|
||||
float offsetCircumference = 0; // Optional offset for gap accounting
|
||||
uint16_t numLeds = 144; // Default to dense 144 leds.
|
||||
bool invert = false; // If true, reverse the mapping order
|
||||
float totalTurns = 19.f; // Default to 19 turns
|
||||
float offsetCircumference = 0; // Optional offset for gap accounting
|
||||
uint16_t numLeds = 144; // Default to dense 144 leds.
|
||||
bool invert = false; // If true, reverse the mapping order
|
||||
CorkscrewInput() = default;
|
||||
CorkscrewInput(float height, float total_angle, float offset = 0,
|
||||
uint16_t leds = 144, bool invertMapping = false)
|
||||
: totalHeight(height), totalAngle(total_angle),
|
||||
offsetCircumference(offset), numLeds(leds), invert(invertMapping) {}
|
||||
CorkscrewInput(float total_length, float height, float total_turns,
|
||||
uint16_t leds, float offset = 0,
|
||||
bool invertMapping = false)
|
||||
: totalLength(total_length), totalHeight(height),
|
||||
totalTurns(total_turns), offsetCircumference(offset), numLeds(leds),
|
||||
invert(invertMapping) {}
|
||||
};
|
||||
|
||||
struct CorkscrewOutput {
|
||||
struct CorkscrewState {
|
||||
uint16_t width = 0; // Width of cylindrical map (circumference of one turn)
|
||||
uint16_t height = 0; // Height of cylindrical map (total vertical segments)
|
||||
fl::vector<fl::vec2f, fl::allocator_psram<fl::vec2f>>
|
||||
mapping; // Full precision mapping from corkscrew to cylindrical
|
||||
CorkscrewOutput() = default;
|
||||
CorkscrewState() = default;
|
||||
|
||||
class iterator {
|
||||
public:
|
||||
public:
|
||||
using value_type = vec2f;
|
||||
using difference_type = int32_t;
|
||||
using pointer = vec2f*;
|
||||
using reference = vec2f&;
|
||||
using pointer = vec2f *;
|
||||
using reference = vec2f &;
|
||||
|
||||
iterator(CorkscrewOutput* owner, size_t position)
|
||||
iterator(CorkscrewState *owner, size_t position)
|
||||
: owner_(owner), position_(position) {}
|
||||
|
||||
vec2f& operator*() const {
|
||||
return owner_->mapping[position_];
|
||||
}
|
||||
vec2f &operator*() const { return owner_->mapping[position_]; }
|
||||
|
||||
iterator& operator++() {
|
||||
iterator &operator++() {
|
||||
++position_;
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -91,67 +94,82 @@ struct CorkscrewOutput {
|
|||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& other) const {
|
||||
iterator &operator--() {
|
||||
--position_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) {
|
||||
iterator temp = *this;
|
||||
--position_;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const {
|
||||
return position_ == other.position_;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& other) const {
|
||||
bool operator!=(const iterator &other) const {
|
||||
return position_ != other.position_;
|
||||
}
|
||||
|
||||
private:
|
||||
CorkscrewOutput* owner_;
|
||||
difference_type operator-(const iterator &other) const {
|
||||
return static_cast<difference_type>(position_) -
|
||||
static_cast<difference_type>(other.position_);
|
||||
}
|
||||
|
||||
private:
|
||||
CorkscrewState *owner_;
|
||||
size_t position_;
|
||||
};
|
||||
|
||||
iterator begin() {
|
||||
return iterator(this, 0);
|
||||
}
|
||||
iterator begin() { return iterator(this, 0); }
|
||||
|
||||
iterator end() {
|
||||
return iterator(this, mapping.size());
|
||||
}
|
||||
fl::Tile2x2_u8 at(int16_t x, int16_t y) const;
|
||||
iterator end() { return iterator(this, mapping.size()); }
|
||||
};
|
||||
|
||||
// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering
|
||||
// a densly wrapped LED corkscrew.
|
||||
class Corkscrew {
|
||||
public:
|
||||
using Input = CorkscrewInput;
|
||||
using Output = CorkscrewOutput;
|
||||
using iterator = CorkscrewOutput::iterator;
|
||||
using State = CorkscrewState;
|
||||
using iterator = CorkscrewState::iterator;
|
||||
|
||||
Corkscrew(const Input &input);
|
||||
Corkscrew(const Corkscrew &) = default;
|
||||
Corkscrew(Corkscrew &&) = default;
|
||||
|
||||
vec2f at_exact(uint16_t i) const;
|
||||
|
||||
// This is the future api.
|
||||
Tile2x2_u8_wrap at_wrap(float i) const;
|
||||
|
||||
vec2f at(uint16_t i) const;
|
||||
// This is a splatted pixel. This is will look way better than
|
||||
// using at(), because it uses 2x2 neighboor sampling.
|
||||
Tile2x2_u8 at_splat(uint16_t i) const;
|
||||
size_t size() const;
|
||||
|
||||
iterator begin() {
|
||||
return mOutput.begin();
|
||||
}
|
||||
iterator begin() { return mState.begin(); }
|
||||
|
||||
iterator end() {
|
||||
return mOutput.end();
|
||||
}
|
||||
iterator end() { return mState.end(); }
|
||||
|
||||
/// For testing
|
||||
|
||||
static CorkscrewOutput generateMap(const Input &input);
|
||||
static State generateState(const Input &input);
|
||||
|
||||
Output& access() {
|
||||
return mOutput;
|
||||
}
|
||||
State &access() { return mState; }
|
||||
|
||||
const Output& access() const {
|
||||
return mOutput;
|
||||
}
|
||||
const State &access() const { return mState; }
|
||||
|
||||
int16_t cylinder_width() const { return mState.width; }
|
||||
int16_t cylinder_height() const { return mState.height; }
|
||||
|
||||
private:
|
||||
Input mInput; // The input parameters defining the corkscrew
|
||||
CorkscrewOutput mOutput; // The resulting cylindrical mapping
|
||||
// For internal use. Splats the pixel on the surface which
|
||||
// extends past the width. This extended Tile2x2 is designed
|
||||
// to be wrapped around with a Tile2x2_u8_wrap.
|
||||
Tile2x2_u8 at_splat_extrapolate(float i) const;
|
||||
|
||||
Input mInput; // The input parameters defining the corkscrew
|
||||
State mState; // The resulting cylindrical mapping
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -13,19 +13,15 @@ template <typename T> class Grid {
|
|||
Grid(uint32_t width, uint32_t height) { reset(width, height); }
|
||||
|
||||
void reset(uint32_t width, uint32_t height) {
|
||||
clear();
|
||||
if (width != mWidth || height != mHeight) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
// Only re-allocate if the size is now bigger.
|
||||
mData.reserve(width * height);
|
||||
// Fill with default objects.
|
||||
while (mData.size() < width * height) {
|
||||
mData.push_back(T());
|
||||
}
|
||||
mSlice = fl::MatrixSlice<T>(mData.data(), width, height, 0, 0,
|
||||
width - 1, height - 1);
|
||||
mData.resize(width * height);
|
||||
|
||||
}
|
||||
clear();
|
||||
mSlice = fl::MatrixSlice<T>(mData.data(), width, height, 0, 0,
|
||||
width, height);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
|
|
@ -60,6 +56,11 @@ template <typename T> class Grid {
|
|||
uint32_t width() const { return mWidth; }
|
||||
uint32_t height() const { return mHeight; }
|
||||
|
||||
T* data() { return mData.data(); }
|
||||
const T* data() const { return mData.data(); }
|
||||
|
||||
size_t size() const { return mData.size(); }
|
||||
|
||||
private:
|
||||
static T &NullValue() {
|
||||
static T gNull;
|
||||
|
|
|
|||
44
libraries/FastLED/src/fl/range_access.h
Normal file
44
libraries/FastLED/src/fl/range_access.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// fl::begin for arrays
|
||||
template <typename T, size_t N>
|
||||
constexpr T* begin(T (&array)[N]) noexcept {
|
||||
return array;
|
||||
}
|
||||
|
||||
// fl::end for arrays
|
||||
template <typename T, size_t N>
|
||||
constexpr T* end(T (&array)[N]) noexcept {
|
||||
return array + N;
|
||||
}
|
||||
|
||||
// fl::begin for containers with begin() member function
|
||||
template <typename Container>
|
||||
constexpr auto begin(Container& c) -> decltype(c.begin()) {
|
||||
return c.begin();
|
||||
}
|
||||
|
||||
// fl::begin for const containers with begin() member function
|
||||
template <typename Container>
|
||||
constexpr auto begin(const Container& c) -> decltype(c.begin()) {
|
||||
return c.begin();
|
||||
}
|
||||
|
||||
// fl::end for containers with end() member function
|
||||
template <typename Container>
|
||||
constexpr auto end(Container& c) -> decltype(c.end()) {
|
||||
return c.end();
|
||||
}
|
||||
|
||||
// fl::end for const containers with end() member function
|
||||
template <typename Container>
|
||||
constexpr auto end(const Container& c) -> decltype(c.end()) {
|
||||
return c.end();
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
|
|
@ -9,6 +9,8 @@ namespace fl {
|
|||
template <typename T, int N = 0> class Singleton {
|
||||
public:
|
||||
static T &instance() {
|
||||
// We love function level singletons!! They don't get construction until first call.
|
||||
// And they seem to have locks on them in most compilers. So yay.
|
||||
static T instance;
|
||||
return instance;
|
||||
}
|
||||
|
|
|
|||
7
libraries/FastLED/src/fl/sketch_macros.h
Normal file
7
libraries/FastLED/src/fl/sketch_macros.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#if defined(__AVR__) || defined(ARDUINO_TEENSYLC) || defined(ARDUINO_TEENSY30) || defined(ARDUINO_TEENSY31)|| defined(STM32F1) || defined(ARDUINO_ARCH_RENESAS_UNO) || defined(ESP8266)
|
||||
#define SKETCH_HAS_LOTS_OF_MEMORY 0
|
||||
#else
|
||||
#define SKETCH_HAS_LOTS_OF_MEMORY 1
|
||||
#endif
|
||||
|
|
@ -1,16 +1,27 @@
|
|||
#include "fl/tile2x2.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/draw_visitor.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/raster.h"
|
||||
#include "fl/raster_sparse.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
static vec2i16 wrap(const vec2i16 &v, const vec2i16 &size) {
|
||||
// Wrap the vector v around the size
|
||||
return vec2i16(v.x % size.x, v.y % size.y);
|
||||
}
|
||||
|
||||
static vec2i16 wrap_x(const vec2i16 &v, const uint16_t width) {
|
||||
// Wrap the x component of the vector v around the size
|
||||
return vec2i16(v.x % width, v.y);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Tile2x2_u8::Rasterize(const Slice<const Tile2x2_u8> &tiles,
|
||||
XYRasterU8Sparse *out_raster) {
|
||||
out_raster->rasterize(tiles);
|
||||
|
|
@ -34,4 +45,66 @@ void Tile2x2_u8::scale(uint8_t scale) {
|
|||
}
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width) {
|
||||
const vec2i16 origin = from.origin();
|
||||
at(0, 0) = {wrap_x(vec2i16(origin.x, origin.y), width), from.at(0, 0)};
|
||||
at(0, 1) = {wrap_x(vec2i16(origin.x, origin.y + 1), width), from.at(0, 1)};
|
||||
at(1, 0) = {wrap_x(vec2i16(origin.x + 1, origin.y), width), from.at(1, 0)};
|
||||
at(1, 1) = {wrap_x(vec2i16(origin.x + 1, origin.y + 1), width),
|
||||
from.at(1, 1)};
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap::Data &Tile2x2_u8_wrap::at(uint16_t x, uint16_t y) {
|
||||
// Wrap around the edges
|
||||
x = (x + 2) % 2;
|
||||
y = (y + 2) % 2;
|
||||
return tile[y][x];
|
||||
}
|
||||
|
||||
const Tile2x2_u8_wrap::Data &Tile2x2_u8_wrap::at(uint16_t x, uint16_t y) const {
|
||||
// Wrap around the edges
|
||||
x = (x + 2) % 2;
|
||||
y = (y + 2) % 2;
|
||||
return tile[y][x];
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width,
|
||||
uint16_t height) {
|
||||
const vec2i16 origin = from.origin();
|
||||
at(0, 0) = {wrap(vec2i16(origin.x, origin.y), vec2i16(width, height)),
|
||||
from.at(0, 0)};
|
||||
at(0, 1) = {wrap(vec2i16(origin.x, origin.y + 1), vec2i16(width, height)),
|
||||
from.at(0, 1)};
|
||||
at(1, 0) = {wrap(vec2i16(origin.x + 1, origin.y), vec2i16(width, height)),
|
||||
from.at(1, 0)};
|
||||
at(1,
|
||||
1) = {wrap(vec2i16(origin.x + 1, origin.y + 1), vec2i16(width, height)),
|
||||
from.at(1, 1)};
|
||||
}
|
||||
|
||||
uint8_t Tile2x2_u8::maxValue() const {
|
||||
uint8_t max = 0;
|
||||
max = MAX(max, at(0, 0));
|
||||
max = MAX(max, at(0, 1));
|
||||
max = MAX(max, at(1, 0));
|
||||
max = MAX(max, at(1, 1));
|
||||
return max;
|
||||
}
|
||||
|
||||
Tile2x2_u8 Tile2x2_u8::MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b) {
|
||||
Tile2x2_u8 result;
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
for (int y = 0; y < 2; ++y) {
|
||||
result.at(x, y) = MAX(a.at(x, y), b.at(x, y));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
rect<int16_t> Tile2x2_u8::bounds() const {
|
||||
vec2<int16_t> min = mOrigin;
|
||||
vec2<int16_t> max = mOrigin + vec2<int16_t>(2, 2);
|
||||
return rect<int16_t>(min, max);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/slice.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
|
|
@ -31,6 +32,8 @@ class Tile2x2_u8 {
|
|||
|
||||
void scale(uint8_t scale);
|
||||
|
||||
void setOrigin(int16_t x, int16_t y) { mOrigin = vec2<int16_t>(x, y); }
|
||||
|
||||
uint8_t &operator()(int x, int y) { return at(x, y); }
|
||||
uint8_t &at(int x, int y) { return mTile[y][x]; }
|
||||
const uint8_t &at(int x, int y) const { return mTile[y][x]; }
|
||||
|
|
@ -40,33 +43,19 @@ class Tile2x2_u8 {
|
|||
uint8_t &lower_right() { return at(1, 0); }
|
||||
uint8_t &upper_right() { return at(1, 1); }
|
||||
|
||||
uint8_t maxValue() const {
|
||||
uint8_t max = 0;
|
||||
max = MAX(max, at(0, 0));
|
||||
max = MAX(max, at(0, 1));
|
||||
max = MAX(max, at(1, 0));
|
||||
max = MAX(max, at(1, 1));
|
||||
return max;
|
||||
}
|
||||
const uint8_t &lower_left() const { return at(0, 0); }
|
||||
const uint8_t &upper_left() const { return at(0, 1); }
|
||||
const uint8_t &lower_right() const { return at(1, 0); }
|
||||
const uint8_t &upper_right() const { return at(1, 1); }
|
||||
|
||||
static Tile2x2_u8 Max(const Tile2x2_u8 &a, const Tile2x2_u8 &b) {
|
||||
Tile2x2_u8 result;
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
for (int y = 0; y < 2; ++y) {
|
||||
result.at(x, y) = MAX(a.at(x, y), b.at(x, y));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
uint8_t maxValue() const;
|
||||
|
||||
static Tile2x2_u8 MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b);
|
||||
|
||||
vec2<int16_t> origin() const { return mOrigin; }
|
||||
|
||||
/// bounds => [begin_x, end_x) (where end_x is exclusive)
|
||||
rect<int16_t> bounds() const {
|
||||
vec2<int16_t> min = mOrigin;
|
||||
vec2<int16_t> max = mOrigin + vec2<int16_t>(2, 2);
|
||||
return rect<int16_t>(min, max);
|
||||
}
|
||||
rect<int16_t> bounds() const;
|
||||
|
||||
// Draws the subpixel tile to the led array.
|
||||
void draw(const CRGB &color, const XYMap &xymap, CRGB *out) const;
|
||||
|
|
@ -96,4 +85,24 @@ class Tile2x2_u8 {
|
|||
vec2<int16_t> mOrigin;
|
||||
};
|
||||
|
||||
class Tile2x2_u8_wrap {
|
||||
// This is a class that is like a Tile2x2_u8 but wraps around the edges.
|
||||
// This is useful for cylinder mapping where the x-coordinate wraps around
|
||||
// the width of the cylinder and the y-coordinate wraps around the height.
|
||||
// This converts a tile2x2 to a wrapped x,y version.
|
||||
public:
|
||||
using Data = fl::pair<vec2i16, uint8_t>; // absolute position, alpha
|
||||
|
||||
Tile2x2_u8_wrap() = default;
|
||||
Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width);
|
||||
Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width, uint16_t height);
|
||||
|
||||
// Returns the absolute position and the alpha.
|
||||
Data &at(uint16_t x, uint16_t y);
|
||||
const Data &at(uint16_t x, uint16_t y) const;
|
||||
|
||||
private:
|
||||
Data tile[2][2] = {}; // zero filled.
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bilinear_expansion.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
|
@ -17,9 +17,8 @@ uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11,
|
|||
uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, uint8_t dx, uint8_t dy);
|
||||
|
||||
void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
XYMap xyMap) {
|
||||
void upscaleArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, XYMap xyMap) {
|
||||
uint16_t n = xyMap.getTotal();
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
|
|
@ -82,8 +81,8 @@ uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11,
|
|||
return result;
|
||||
}
|
||||
|
||||
void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
void upscalePowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
uint8_t width = xyMap.getWidth();
|
||||
uint8_t height = xyMap.getHeight();
|
||||
if (width != xyMap.getWidth() || height != xyMap.getHeight()) {
|
||||
|
|
@ -158,7 +157,7 @@ uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01,
|
|||
}
|
||||
|
||||
// Floating-point version of bilinear interpolation
|
||||
uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t upscaleFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy) {
|
||||
float dx_inv = 1.0f - dx;
|
||||
float dy_inv = 1.0f - dy;
|
||||
|
|
@ -179,9 +178,8 @@ uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
|||
}
|
||||
|
||||
// Floating-point version for arbitrary grid sizes
|
||||
void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
XYMap xyMap) {
|
||||
void upscaleArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, XYMap xyMap) {
|
||||
uint16_t n = xyMap.getTotal();
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
|
|
@ -214,11 +212,11 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
|||
|
||||
CRGB result;
|
||||
result.r =
|
||||
bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
result.g =
|
||||
bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
result.b =
|
||||
bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
|
||||
uint16_t idx = xyMap.mapToIndex(x, y);
|
||||
if (idx < n) {
|
||||
|
|
@ -229,8 +227,8 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
|||
}
|
||||
|
||||
// Floating-point version for power-of-two grid sizes
|
||||
void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
void upscaleFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
uint8_t outputWidth = xyMap.getWidth();
|
||||
uint8_t outputHeight = xyMap.getHeight();
|
||||
if (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()) {
|
||||
|
|
@ -267,11 +265,11 @@ void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
|||
|
||||
CRGB result;
|
||||
result.r =
|
||||
bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
result.g =
|
||||
bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
result.b =
|
||||
bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
|
||||
uint16_t idx = xyMap.mapToIndex(x, y);
|
||||
if (idx < n) {
|
||||
63
libraries/FastLED/src/fl/upscale.h
Normal file
63
libraries/FastLED/src/fl/upscale.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/// @file bilinear_expansion.h
|
||||
/// @brief Demonstrates how to mix noise generation with color palettes on a
|
||||
/// 2D LED matrix
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// @brief Performs bilinear interpolation for upscaling an image.
|
||||
/// @param output The output grid to write into the interpolated values.
|
||||
/// @param input The input grid to read from.
|
||||
/// @param inputWidth The width of the input grid.
|
||||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
void upscaleArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
/// @brief Performs bilinear interpolation for upscaling an image.
|
||||
/// @param output The output grid to write into the interpolated values.
|
||||
/// @param input The input grid to read from.
|
||||
/// @param inputWidth The width of the input grid.
|
||||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
void upscalePowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
//
|
||||
inline void upscale(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap) {
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
const bool wontFit =
|
||||
(outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight());
|
||||
// if the input dimensions are not a power of 2 then we can't use the
|
||||
// optimized version.
|
||||
if (wontFit || (inputWidth & (inputWidth - 1)) ||
|
||||
(inputHeight & (inputHeight - 1))) {
|
||||
upscaleArbitrary(input, output, inputWidth, inputHeight, xyMap);
|
||||
} else {
|
||||
upscalePowerOf2(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
}
|
||||
|
||||
// These are here for testing purposes and are slow. Their primary use
|
||||
// is to test against the fixed integer version above.
|
||||
void upscaleFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
void upscaleArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
uint8_t upscaleFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy);
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -237,7 +237,7 @@ XYPathPtr XYPath::NewCatmullRomPath(uint16_t width, uint16_t height,
|
|||
}
|
||||
|
||||
XYPathPtr XYPath::NewCustomPath(const fl::function<vec2f(float)> &f,
|
||||
const rect<int> &drawbounds,
|
||||
const rect<int16_t> &drawbounds,
|
||||
const TransformFloat &transform,
|
||||
const char *name) {
|
||||
|
||||
|
|
@ -250,7 +250,7 @@ XYPathPtr XYPath::NewCustomPath(const fl::function<vec2f(float)> &f,
|
|||
if (!transform.is_identity()) {
|
||||
out->setTransform(transform);
|
||||
}
|
||||
rect<int> bounds;
|
||||
rect<int16_t> bounds;
|
||||
if (path->hasDrawBounds(&bounds)) {
|
||||
if (!bounds.mMin.is_zero()) {
|
||||
// Set the bounds to the path's bounds
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
#include "fl/transform.h"
|
||||
#include "fl/xypath_impls.h"
|
||||
|
||||
#include "fl/avr_disallowed.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class Gradient;
|
||||
|
|
@ -37,7 +35,6 @@ namespace xypath_detail {
|
|||
fl::Str unique_missing_name(const char *prefix = "XYCustomPath: ");
|
||||
} // namespace xypath_detail
|
||||
|
||||
AVR_DISALLOWED
|
||||
class XYPath : public Referent {
|
||||
public:
|
||||
/////////////////////////////////////////////
|
||||
|
|
@ -57,7 +54,7 @@ class XYPath : public Referent {
|
|||
// Custom path using just a function.
|
||||
static XYPathPtr
|
||||
NewCustomPath(const fl::function<vec2f(float)> &path,
|
||||
const rect<int> &drawbounds = rect<int>(),
|
||||
const rect<int16_t> &drawbounds = rect<int16_t>(),
|
||||
const TransformFloat &transform = TransformFloat(),
|
||||
const char *name = nullptr);
|
||||
|
||||
|
|
@ -148,10 +145,10 @@ class XYPathFunction : public XYPathGenerator {
|
|||
const Str name() const override { return mName; }
|
||||
void setName(const Str &name) { mName = name; }
|
||||
|
||||
fl::rect<int> drawBounds() const { return mDrawBounds; }
|
||||
void setDrawBounds(const fl::rect<int> &bounds) { mDrawBounds = bounds; }
|
||||
fl::rect<int16_t> drawBounds() const { return mDrawBounds; }
|
||||
void setDrawBounds(const fl::rect<int16_t> &bounds) { mDrawBounds = bounds; }
|
||||
|
||||
bool hasDrawBounds(fl::rect<int> *bounds) override {
|
||||
bool hasDrawBounds(fl::rect<int16_t> *bounds) override {
|
||||
if (bounds) {
|
||||
*bounds = mDrawBounds;
|
||||
}
|
||||
|
|
@ -161,7 +158,7 @@ class XYPathFunction : public XYPathGenerator {
|
|||
private:
|
||||
fl::function<vec2f(float)> mFunction;
|
||||
fl::Str mName = "XYPathFunction Unnamed";
|
||||
fl::rect<int> mDrawBounds;
|
||||
fl::rect<int16_t> mDrawBounds;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class XYPathGenerator : public Referent {
|
|||
virtual const Str name() const = 0;
|
||||
virtual vec2f compute(float alpha) = 0;
|
||||
// No writes when returning false.
|
||||
virtual bool hasDrawBounds(rect<int> *bounds) {
|
||||
virtual bool hasDrawBounds(rect<int16_t> *bounds) {
|
||||
FASTLED_UNUSED(bounds);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
#include "fl/scoped_ptr.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fx/fx2d.h"
|
||||
#include "eorder.h"
|
||||
#include "pixel_controller.h" // For RGB_BYTE_0, RGB_BYTE_1, RGB_BYTE_2
|
||||
|
||||
#define ANIMARTRIX_INTERNAL
|
||||
#include "animartrix_detail.hpp"
|
||||
|
|
@ -93,6 +95,8 @@ class Animartrix : public Fx2d {
|
|||
int fxGet() const { return static_cast<int>(current_animation); }
|
||||
Str fxName() const override { return "Animartrix:"; }
|
||||
void fxNext(int fx = 1) { fxSet(fxGet() + fx); }
|
||||
void setColorOrder(EOrder order) { color_order = order; }
|
||||
EOrder getColorOrder() const { return color_order; }
|
||||
|
||||
private:
|
||||
friend void AnimartrixLoop(Animartrix &self, uint32_t now);
|
||||
|
|
@ -102,6 +106,7 @@ class Animartrix : public Fx2d {
|
|||
fl::scoped_ptr<FastLEDANIMartRIX> impl;
|
||||
CRGB *leds = nullptr; // Only set during draw, then unset back to nullptr.
|
||||
AnimartrixAnim current_animation = RGB_BLOBS5;
|
||||
EOrder color_order = RGB;
|
||||
};
|
||||
|
||||
void AnimartrixLoop(Animartrix &self, uint32_t now);
|
||||
|
|
@ -263,6 +268,17 @@ const char *Animartrix::getAnimationName(AnimartrixAnim animation) {
|
|||
void Animartrix::draw(DrawContext ctx) {
|
||||
this->leds = ctx.leds;
|
||||
AnimartrixLoop(*this, ctx.now);
|
||||
if (color_order != RGB) {
|
||||
for (int i = 0; i < mXyMap.getTotal(); ++i) {
|
||||
CRGB &pixel = ctx.leds[i];
|
||||
const uint8_t b0_index = RGB_BYTE0(color_order);
|
||||
const uint8_t b1_index = RGB_BYTE1(color_order);
|
||||
const uint8_t b2_index = RGB_BYTE2(color_order);
|
||||
pixel = CRGB(pixel.raw[b0_index], pixel.raw[b1_index],
|
||||
pixel.raw[b2_index]);
|
||||
}
|
||||
|
||||
}
|
||||
this->leds = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "FastLED.h"
|
||||
#include "fl/bilinear_expansion.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fx/fx2d.h"
|
||||
|
|
@ -64,13 +64,13 @@ void ScaleUp::draw(DrawContext context) {
|
|||
void ScaleUp::expand(const CRGB *input, CRGB *output, uint16_t width,
|
||||
uint16_t height, XYMap mXyMap) {
|
||||
#if FASTLED_SCALE_UP == FASTLED_SCALE_UP_ALWAYS_POWER_OF_2
|
||||
bilinearExpandPowerOf2(input, output, width, height, mXyMap);
|
||||
fl::upscalePowerOf2(input, output, static_cast<uint8_t>(width), static_cast<uint8_t>(height), mXyMap);
|
||||
#elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_HIGH_PRECISION
|
||||
bilinearExpandArbitrary(input, output, width, height, mXyMap);
|
||||
fl::upscaleArbitrary(input, output, width, height, mXyMap);
|
||||
#elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_DECIDE_AT_RUNTIME
|
||||
bilinearExpand(input, output, width, height, mXyMap);
|
||||
fl::upscale(input, output, width, height, mXyMap);
|
||||
#elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_FORCE_FLOATING_POINT
|
||||
bilinearExpandFloat(input, output, width, height, mXyMap);
|
||||
fl::upscaleFloat(input, output, static_cast<uint8_t>(width), static_cast<uint8_t>(height), mXyMap);
|
||||
#else
|
||||
#error "Invalid FASTLED_SCALE_UP"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "fl/bilinear_expansion.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/xymap.h"
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ typedef fl::FixedVector<int, 16> PinList16;
|
|||
|
||||
typedef uint8_t Pin;
|
||||
|
||||
bool gPsramInited = false;
|
||||
|
||||
|
||||
|
||||
// Maps multiple pins and CRGB strips to a single I2S_Esp32 object.
|
||||
|
|
@ -180,6 +182,13 @@ class Driver: public InternalI2SDriver {
|
|||
};
|
||||
|
||||
InternalI2SDriver* InternalI2SDriver::create() {
|
||||
if (!gPsramInited) {
|
||||
gPsramInited = true;
|
||||
bool ok = psramInit();
|
||||
if (!ok) {
|
||||
log_e("PSRAM initialization failed, I2S driver may crash.");
|
||||
}
|
||||
}
|
||||
return new Driver();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,20 @@
|
|||
#include "fl/vector.h"
|
||||
#include "eorder.h"
|
||||
|
||||
#ifndef FASTLED_INTERNAL
|
||||
// We need to do a check for the esp-idf version because very specific versions of the
|
||||
// esp-idf arduino core are broken.
|
||||
#include "platforms/esp/esp_version.h"
|
||||
// Broken in 3.0.2 (esp-idf 5.1.0)
|
||||
// Broken in 3.0.4 (esp-idf 5.1.0)
|
||||
// Broken in 3.0.7 (esp-idf 5.1.0)
|
||||
// Broken in 3.1.0 (esp-idf 5.3.2)
|
||||
#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 1, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0)
|
||||
#error "I2S driver is known to not be compatible with ESP-IDF 5.1.0, upgrade to ESP-IDF 5.4.0 in Arduino core esp32 3.2.0+, see https://github.com/FastLED/FastLED/issues/1903"
|
||||
#elif ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 3, 2)
|
||||
#error "I2S driver is known to not be compatible with ESP-IDF 5.3.2, upgrade to ESP-IDF 5.4.0 in Arduino core esp32 3.2.0+, see https://github.com/FastLED/FastLED/issues/1903"
|
||||
#endif
|
||||
#endif // FASTLED_INTERNAL
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ public:
|
|||
// GPIO 20-22, 24-26 used by default for SPI flash.
|
||||
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(24) | _FL_BIT(25) | _FL_BIT(26) | _FL_BIT(28) | _FL_BIT(29) | _FL_BIT(30))
|
||||
|
||||
#elif CONFIG_IDF_TARGET_ESP32P4
|
||||
// 55 GPIO pins. ESPIDF defines all pins as valid.
|
||||
// NOTE: GPIO 24 & 25 commonly used for USB and may cause flashes when uploading.
|
||||
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(24) | _FL_BIT(25))
|
||||
|
||||
#elif CONFIG_IDF_TARGET_ESP32H2
|
||||
// 22 GPIO pins. ESPIDF defines all pins as valid.
|
||||
// ESP32-H2 datasheet not yet available, when it is, mask the pins commonly used by SPI flash.
|
||||
|
|
@ -131,6 +136,7 @@ public:
|
|||
#define FASTLED_UNUSABLE_PIN_MASK (0ULL)
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,11 +25,7 @@ private:
|
|||
|
||||
static RmtController5::DmaMode DefaultDmaMode()
|
||||
{
|
||||
#ifdef FASTLED_RMT_USE_DMA
|
||||
return RmtController5::DMA_ENABLED;
|
||||
#else
|
||||
return RmtController5::DMA_AUTO;
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#if !defined(PROGMEM)
|
||||
#define PROGMEM
|
||||
#define FL_PROGMEM
|
||||
#endif
|
||||
|
||||
#if !defined(FL_PROGMEM)
|
||||
#define FL_PROGMEM PROGMEM
|
||||
#endif
|
||||
|
||||
#define FL_PGM_READ_BYTE_NEAR(x) (*((const uint8_t *)(x)))
|
||||
#define FL_PGM_READ_WORD_NEAR(x) (*((const uint16_t *)(x)))
|
||||
#define FL_PGM_READ_DWORD_NEAR(x) (*((const uint32_t *)(x)))
|
||||
#define FL_ALIGN_PROGMEM
|
||||
#define FL_ALIGN_PROGMEM
|
||||
|
||||
#define FL_PROGMEM_USES_NULL 1
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
|
||||
static const auto start_time = std::chrono::system_clock::now();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class ActiveStripData : public fl::EngineEvents::Listener {
|
|||
updateScreenMap(id, screenmap);
|
||||
}
|
||||
|
||||
const bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
|
||||
private:
|
||||
friend class fl::Singleton<ActiveStripData>;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class ActiveStripData2 : public fl::EngineEvents::Listener {
|
|||
updateScreenMap(id, screenmap);
|
||||
}
|
||||
|
||||
const bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
|
||||
private:
|
||||
friend class fl::Singleton<ActiveStripData2>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue