FastLED 3.10.8
Loading...
Searching...
No Matches
downscale.cpp.hpp
Go to the documentation of this file.
1
2// BETA - NOT TESTED!!!
3// VIBE CODED WITH AI
4
5#include "fl/gfx/downscale.h"
7#include "fl/stl/int.h"
8
9#include "crgb.h"
10#include "fl/stl/assert.h"
11#include "fl/math/math.h"
12#include "fl/math/xymap.h"
13#include "fl/stl/noexcept.h"
14
16FL_DISABLE_WARNING(shift-count-overflow)
17
18namespace fl {
19
20void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight,
21 CRGB *dst) FL_NO_EXCEPT {
22 fl::u16 dstWidth = srcWidth / 2;
23 fl::u16 dstHeight = srcHeight / 2;
24
25 for (fl::u16 y = 0; y < dstHeight; ++y) {
26 for (fl::u16 x = 0; x < dstWidth; ++x) {
27 // Map to top-left of the 2x2 block in source
28 fl::u16 srcX = x * 2;
29 fl::u16 srcY = y * 2;
30
31 // Fetch 2x2 block
32 const CRGB &p00 = src[(srcY)*srcWidth + (srcX)];
33 const CRGB &p10 = src[(srcY)*srcWidth + (srcX + 1)];
34 const CRGB &p01 = src[(srcY + 1) * srcWidth + (srcX)];
35 const CRGB &p11 = src[(srcY + 1) * srcWidth + (srcX + 1)];
36
37 // Average each color channel
38 fl::u16 r =
39 (p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding
40 fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4;
41 fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4;
42
43 // Store result
44 dst[y * dstWidth + x] = CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b);
45 }
46 }
47}
48
49void downscaleHalf(const CRGB *src, const XYMap &srcXY, CRGB *dst,
50 const XYMap &dstXY) FL_NO_EXCEPT {
51 fl::u16 dstWidth = dstXY.getWidth();
52 fl::u16 dstHeight = dstXY.getHeight();
53
54 FASTLED_ASSERT(srcXY.getWidth() == dstXY.getWidth() * 2,
55 "Source width must be double the destination width");
56 FASTLED_ASSERT(srcXY.getHeight() == dstXY.getHeight() * 2,
57 "Source height must be double the destination height");
58
59 for (fl::u16 y = 0; y < dstHeight; ++y) {
60 for (fl::u16 x = 0; x < dstWidth; ++x) {
61 // Map to top-left of the 2x2 block in source
62 fl::u16 srcX = x * 2;
63 fl::u16 srcY = y * 2;
64
65 // Fetch 2x2 block
66 const CRGB &p00 = src[srcXY.mapToIndex(srcX, srcY)];
67 const CRGB &p10 = src[srcXY.mapToIndex(srcX + 1, srcY)];
68 const CRGB &p01 = src[srcXY.mapToIndex(srcX, srcY + 1)];
69 const CRGB &p11 = src[srcXY.mapToIndex(srcX + 1, srcY + 1)];
70
71 // Average each color channel
72 fl::u16 r =
73 (p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding
74 fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4;
75 fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4;
76
77 // Store result
78 dst[dstXY.mapToIndex(x, y)] =
79 CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b);
80 }
81 }
82}
83
84void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst,
85 const XYMap &dstXY) FL_NO_EXCEPT {
86 const fl::u16 srcWidth = srcXY.getWidth();
87 const fl::u16 srcHeight = srcXY.getHeight();
88 const fl::u16 dstWidth = dstXY.getWidth();
89 const fl::u16 dstHeight = dstXY.getHeight();
90
91 const fl::u32 FP_ONE = 256; // Q8.8 fixed-point multiplier
92
93 FASTLED_ASSERT(dstWidth <= srcWidth,
94 "Destination width must be <= source width");
95 FASTLED_ASSERT(dstHeight <= srcHeight,
96 "Destination height must be <= source height");
97
98 for (fl::u16 dy = 0; dy < dstHeight; ++dy) {
99 // Fractional boundaries in Q8.8
100 fl::u32 dstY0 = (dy * srcHeight * FP_ONE) / dstHeight;
101 fl::u32 dstY1 = ((dy + 1) * srcHeight * FP_ONE) / dstHeight;
102
103 for (fl::u16 dx = 0; dx < dstWidth; ++dx) {
104 fl::u32 dstX0 = (dx * srcWidth * FP_ONE) / dstWidth;
105 fl::u32 dstX1 = ((dx + 1) * srcWidth * FP_ONE) / dstWidth;
106
107 fl::u64 rSum = 0, gSum = 0, bSum = 0;
108 fl::u32 totalWeight = 0;
109
110 // Find covered source pixels
111 fl::u16 srcY_start = dstY0 / FP_ONE;
112 fl::u16 srcY_end = (dstY1 + FP_ONE - 1) / FP_ONE; // ceil
113
114 fl::u16 srcX_start = dstX0 / FP_ONE;
115 fl::u16 srcX_end = (dstX1 + FP_ONE - 1) / FP_ONE; // ceil
116
117 for (fl::u16 sy = srcY_start; sy < srcY_end; ++sy) {
118 // Calculate vertical overlap in Q8.8
119 fl::u32 sy0 = sy * FP_ONE;
120 fl::u32 sy1 = (sy + 1) * FP_ONE;
121 fl::u32 y_overlap = fl::min(dstY1, sy1) - fl::max(dstY0, sy0);
122 if (y_overlap == 0)
123 continue;
124
125 for (fl::u16 sx = srcX_start; sx < srcX_end; ++sx) {
126 fl::u32 sx0 = sx * FP_ONE;
127 fl::u32 sx1 = (sx + 1) * FP_ONE;
128 fl::u32 x_overlap = fl::min(dstX1, sx1) - fl::max(dstX0, sx0);
129 if (x_overlap == 0)
130 continue;
131
132 fl::u32 weight = (x_overlap * y_overlap + (FP_ONE >> 1)) >>
133 8; // Q8.8 * Q8.8 → Q16.16 → Q8.8
134
135 const CRGB &p = src[srcXY.mapToIndex(sx, sy)];
136 rSum += p.r * weight;
137 gSum += p.g * weight;
138 bSum += p.b * weight;
139 totalWeight += weight;
140 }
141 }
142
143 // Final division, rounding
144 fl::u8 r =
145 totalWeight ? (rSum + (totalWeight >> 1)) / totalWeight : 0;
146 fl::u8 g =
147 totalWeight ? (gSum + (totalWeight >> 1)) / totalWeight : 0;
148 fl::u8 b =
149 totalWeight ? (bSum + (totalWeight >> 1)) / totalWeight : 0;
150
151 dst[dstXY.mapToIndex(dx, dy)] = CRGB(r, g, b);
152 }
153 }
154}
155
156void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst,
157 const XYMap &dstXY) FL_NO_EXCEPT {
158 fl::u16 srcWidth = srcXY.getWidth();
159 fl::u16 srcHeight = srcXY.getHeight();
160 fl::u16 dstWidth = dstXY.getWidth();
161 fl::u16 dstHeight = dstXY.getHeight();
162
163 FASTLED_ASSERT(dstWidth <= srcWidth,
164 "Destination width must be <= source width");
165 FASTLED_ASSERT(dstHeight <= srcHeight,
166 "Destination height must be <= source height");
167 const bool destination_is_half_of_source =
168 (dstWidth * 2 == srcWidth) && (dstHeight * 2 == srcHeight);
169 // Attempt to use the downscaleHalf function if the destination is half the
170 // size of the source.
171 if (destination_is_half_of_source) {
172 const bool both_rectangles = (srcXY.getType() == XYMap::kLineByLine) &&
173 (dstXY.getType() == XYMap::kLineByLine);
174 if (both_rectangles) {
175 // If both source and destination are rectangular, we can use the
176 // optimized version
177 downscaleHalf(src, srcWidth, srcHeight, dst);
178 } else {
179 // Otherwise, we need to use the mapped version
180 downscaleHalf(src, srcXY, dst, dstXY);
181 }
182 return;
183 }
184
185 downscaleArbitrary(src, srcXY, dst, dstXY);
186}
187
188} // namespace fl
189
int y
Definition simple.h:93
int x
Definition simple.h:92
@ kLineByLine
Definition xymap.h:43
#define FL_DISABLE_WARNING(warning)
#define FL_DISABLE_WARNING_PUSH
#define FL_DISABLE_WARNING_POP
Defines the 8-bit red, green, and blue (RGB) pixel type in the fl namespace.
fl::CRGB CRGB
Definition crgb.h:25
unsigned char u8
Definition stdint.h:131
constexpr common_type_t< T, U > max(T a, U b) FL_NO_EXCEPT
Definition math.h:75
fl::CRGB CRGB
Definition video.h:18
void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight, CRGB *dst) FL_NO_EXCEPT
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NO_EXCEPT
Definition math.h:71
static constexpr i32 FP_ONE
InputGamut g
Definition rgbw.h:125
void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst, const XYMap &dstXY) FL_NO_EXCEPT
void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst, const XYMap &dstXY) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NO_EXCEPT