initial commit

This commit is contained in:
2026-02-12 00:45:31 -08:00
commit 5f168f370b
3024 changed files with 804889 additions and 0 deletions

View File

@@ -0,0 +1,368 @@
/// @file bilinear_expansion.cpp
/// @brief Demonstrates how to mix noise generation with color palettes on a
/// 2D LED matrix
#include "fl/stdint.h"
#include "crgb.h"
#include "fl/namespace.h"
#include "fl/upscale.h"
#include "fl/xymap.h"
namespace fl {
u8 bilinearInterpolate(u8 v00, u8 v10, u8 v01, u8 v11,
u16 dx, u16 dy);
u8 bilinearInterpolatePowerOf2(u8 v00, u8 v10, u8 v01,
u8 v11, u8 dx, u8 dy);
void upscaleRectangular(const CRGB *input, CRGB *output, u16 inputWidth,
u16 inputHeight, u16 outputWidth, u16 outputHeight) {
const u16 scale_factor = 256; // Using 8 bits for the fractional part
for (u16 y = 0; y < outputHeight; y++) {
for (u16 x = 0; x < outputWidth; x++) {
// Calculate the corresponding position in the input grid
u32 fx = ((u32)x * (inputWidth - 1) * scale_factor) /
(outputWidth - 1);
u32 fy = ((u32)y * (inputHeight - 1) * scale_factor) /
(outputHeight - 1);
u16 ix = fx / scale_factor; // Integer part of x
u16 iy = fy / scale_factor; // Integer part of y
u16 dx = fx % scale_factor; // Fractional part of x
u16 dy = fy % scale_factor; // Fractional part of y
u16 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
u16 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
// Direct array access - no XY mapping overhead
u16 i00 = iy * inputWidth + ix;
u16 i10 = iy * inputWidth + ix1;
u16 i01 = iy1 * inputWidth + ix;
u16 i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r = bilinearInterpolate(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g = bilinearInterpolate(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b = bilinearInterpolate(c00.b, c10.b, c01.b, c11.b, dx, dy);
// Direct array access - no XY mapping overhead
u16 idx = y * outputWidth + x;
output[idx] = result;
}
}
}
void upscaleRectangularPowerOf2(const CRGB *input, CRGB *output, u8 inputWidth,
u8 inputHeight, u8 outputWidth, u8 outputHeight) {
for (u8 y = 0; y < outputHeight; y++) {
for (u8 x = 0; x < outputWidth; x++) {
// Use 8-bit fixed-point arithmetic with 8 fractional bits
// (scale factor of 256)
u16 fx = ((u16)x * (inputWidth - 1) * 256) / (outputWidth - 1);
u16 fy = ((u16)y * (inputHeight - 1) * 256) / (outputHeight - 1);
u8 ix = fx >> 8; // Integer part
u8 iy = fy >> 8;
u8 dx = fx & 0xFF; // Fractional part
u8 dy = fy & 0xFF;
u8 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
u8 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
// Direct array access - no XY mapping overhead
u16 i00 = iy * inputWidth + ix;
u16 i10 = iy * inputWidth + ix1;
u16 i01 = iy1 * inputWidth + ix;
u16 i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r =
bilinearInterpolatePowerOf2(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g =
bilinearInterpolatePowerOf2(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b =
bilinearInterpolatePowerOf2(c00.b, c10.b, c01.b, c11.b, dx, dy);
// Direct array access - no XY mapping overhead
u16 idx = y * outputWidth + x;
output[idx] = result;
}
}
}
void upscaleArbitrary(const CRGB *input, CRGB *output, u16 inputWidth,
u16 inputHeight, const XYMap& xyMap) {
u16 n = xyMap.getTotal();
u16 outputWidth = xyMap.getWidth();
u16 outputHeight = xyMap.getHeight();
const u16 scale_factor = 256; // Using 8 bits for the fractional part
for (u16 y = 0; y < outputHeight; y++) {
for (u16 x = 0; x < outputWidth; x++) {
// Calculate the corresponding position in the input grid
u32 fx = ((u32)x * (inputWidth - 1) * scale_factor) /
(outputWidth - 1);
u32 fy = ((u32)y * (inputHeight - 1) * scale_factor) /
(outputHeight - 1);
u16 ix = fx / scale_factor; // Integer part of x
u16 iy = fy / scale_factor; // Integer part of y
u16 dx = fx % scale_factor; // Fractional part of x
u16 dy = fy % scale_factor; // Fractional part of y
u16 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
u16 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
u16 i00 = iy * inputWidth + ix;
u16 i10 = iy * inputWidth + ix1;
u16 i01 = iy1 * inputWidth + ix;
u16 i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r = bilinearInterpolate(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g = bilinearInterpolate(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b = bilinearInterpolate(c00.b, c10.b, c01.b, c11.b, dx, dy);
u16 idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
u8 bilinearInterpolate(u8 v00, u8 v10, u8 v01, u8 v11,
u16 dx, u16 dy) {
u16 dx_inv = 256 - dx;
u16 dy_inv = 256 - dy;
u32 w00 = (u32)dx_inv * dy_inv;
u32 w10 = (u32)dx * dy_inv;
u32 w01 = (u32)dx_inv * dy;
u32 w11 = (u32)dx * dy;
u32 sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
// Normalize the result by dividing by 65536 (shift right by 16 bits),
// with rounding
u8 result = (u8)((sum + 32768) >> 16);
return result;
}
void upscalePowerOf2(const CRGB *input, CRGB *output, u8 inputWidth,
u8 inputHeight, const XYMap& xyMap) {
u8 width = xyMap.getWidth();
u8 height = xyMap.getHeight();
if (width != xyMap.getWidth() || height != xyMap.getHeight()) {
// xyMap has width and height that do not fit in an u16.
return;
}
u16 n = xyMap.getTotal();
for (u8 y = 0; y < height; y++) {
for (u8 x = 0; x < width; x++) {
// Use 8-bit fixed-point arithmetic with 8 fractional bits
// (scale factor of 256)
u16 fx = ((u16)x * (inputWidth - 1) * 256) / (width - 1);
u16 fy =
((u16)y * (inputHeight - 1) * 256) / (height - 1);
u8 ix = fx >> 8; // Integer part
u8 iy = fy >> 8;
u8 dx = fx & 0xFF; // Fractional part
u8 dy = fy & 0xFF;
u8 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
u8 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
u16 i00 = iy * inputWidth + ix;
u16 i10 = iy * inputWidth + ix1;
u16 i01 = iy1 * inputWidth + ix;
u16 i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r =
bilinearInterpolatePowerOf2(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g =
bilinearInterpolatePowerOf2(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b =
bilinearInterpolatePowerOf2(c00.b, c10.b, c01.b, c11.b, dx, dy);
u16 idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
u8 bilinearInterpolatePowerOf2(u8 v00, u8 v10, u8 v01,
u8 v11, u8 dx, u8 dy) {
u16 dx_inv = 256 - dx; // 0 to 256
u16 dy_inv = 256 - dy; // 0 to 256
// Scale down weights to fit into u16
u16 w00 = (dx_inv * dy_inv) >> 8; // Max value 256
u16 w10 = (dx * dy_inv) >> 8;
u16 w01 = (dx_inv * dy) >> 8;
u16 w11 = (dx * dy) >> 8;
// Sum of weights should be approximately 256
u16 weight_sum = w00 + w10 + w01 + w11;
// Compute the weighted sum of pixel values
u16 sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
// Normalize the result
u8 result = (sum + (weight_sum >> 1)) / weight_sum;
return result;
}
// Floating-point version of bilinear interpolation
u8 upscaleFloat(u8 v00, u8 v10, u8 v01,
u8 v11, float dx, float dy) {
float dx_inv = 1.0f - dx;
float dy_inv = 1.0f - dy;
// Calculate the weights for each corner
float w00 = dx_inv * dy_inv;
float w10 = dx * dy_inv;
float w01 = dx_inv * dy;
float w11 = dx * dy;
// Compute the weighted sum
float sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
// Clamp the result to [0, 255] and round
u8 result = static_cast<u8>(sum + 0.5f);
return result;
}
// Floating-point version for arbitrary grid sizes
void upscaleArbitraryFloat(const CRGB *input, CRGB *output, u16 inputWidth,
u16 inputHeight, const XYMap& xyMap) {
u16 n = xyMap.getTotal();
u16 outputWidth = xyMap.getWidth();
u16 outputHeight = xyMap.getHeight();
for (u16 y = 0; y < outputHeight; y++) {
for (u16 x = 0; x < outputWidth; x++) {
// Map output pixel to input grid position
float fx =
static_cast<float>(x) * (inputWidth - 1) / (outputWidth - 1);
float fy =
static_cast<float>(y) * (inputHeight - 1) / (outputHeight - 1);
u16 ix = static_cast<u16>(fx);
u16 iy = static_cast<u16>(fy);
float dx = fx - ix;
float dy = fy - iy;
u16 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
u16 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
u16 i00 = iy * inputWidth + ix;
u16 i10 = iy * inputWidth + ix1;
u16 i01 = iy1 * inputWidth + ix;
u16 i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r =
upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g =
upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b =
upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
u16 idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
// Floating-point version for power-of-two grid sizes
void upscaleFloat(const CRGB *input, CRGB *output, u8 inputWidth,
u8 inputHeight, const XYMap& xyMap) {
u8 outputWidth = xyMap.getWidth();
u8 outputHeight = xyMap.getHeight();
if (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()) {
// xyMap has width and height that do not fit in a u8.
return;
}
u16 n = xyMap.getTotal();
for (u8 y = 0; y < outputHeight; y++) {
for (u8 x = 0; x < outputWidth; x++) {
// Map output pixel to input grid position
float fx =
static_cast<float>(x) * (inputWidth - 1) / (outputWidth - 1);
float fy =
static_cast<float>(y) * (inputHeight - 1) / (outputHeight - 1);
u8 ix = static_cast<u8>(fx);
u8 iy = static_cast<u8>(fy);
float dx = fx - ix;
float dy = fy - iy;
u8 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
u8 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
u16 i00 = iy * inputWidth + ix;
u16 i10 = iy * inputWidth + ix1;
u16 i01 = iy1 * inputWidth + ix;
u16 i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r =
upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g =
upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b =
upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
u16 idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
} // namespace fl