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