FastLED 3.9.15
Loading...
Searching...
No Matches
upscale.cpp.hpp
Go to the documentation of this file.
1
4
5#include "fl/stl/stdint.h"
6
7#include "crgb.h"
8#include "fl/gfx/upscale.h"
9#include "fl/math/xymap.h"
10
11namespace fl {
12
13u8 bilinearInterpolate(u8 v00, u8 v10, u8 v01, u8 v11,
14 u16 dx, u16 dy);
15
17 u8 v11, u8 dx, u8 dy);
18
19void upscaleRectangular(const CRGB *input, CRGB *output, u16 inputWidth,
20 u16 inputHeight, u16 outputWidth, u16 outputHeight) {
21 const u16 scale_factor = 256; // Using 8 bits for the fractional part
22
23 for (u16 y = 0; y < outputHeight; y++) {
24 for (u16 x = 0; x < outputWidth; x++) {
25 // Calculate the corresponding position in the input grid
26 u32 fx = ((u32)x * (inputWidth - 1) * scale_factor) /
27 (outputWidth - 1);
28 u32 fy = ((u32)y * (inputHeight - 1) * scale_factor) /
29 (outputHeight - 1);
30
31 u16 ix = fx / scale_factor; // Integer part of x
32 u16 iy = fy / scale_factor; // Integer part of y
33 u16 dx = fx % scale_factor; // Fractional part of x
34 u16 dy = fy % scale_factor; // Fractional part of y
35
36 u16 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
37 u16 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
38
39 // Direct array access - no XY mapping overhead
40 u16 i00 = iy * inputWidth + ix;
41 u16 i10 = iy * inputWidth + ix1;
42 u16 i01 = iy1 * inputWidth + ix;
43 u16 i11 = iy1 * inputWidth + ix1;
44
45 CRGB c00 = input[i00];
46 CRGB c10 = input[i10];
47 CRGB c01 = input[i01];
48 CRGB c11 = input[i11];
49
51 result.r = bilinearInterpolate(c00.r, c10.r, c01.r, c11.r, dx, dy);
52 result.g = bilinearInterpolate(c00.g, c10.g, c01.g, c11.g, dx, dy);
53 result.b = bilinearInterpolate(c00.b, c10.b, c01.b, c11.b, dx, dy);
54
55 // Direct array access - no XY mapping overhead
56 u16 idx = y * outputWidth + x;
57 output[idx] = result;
58 }
59 }
60}
61
62void upscaleRectangularPowerOf2(const CRGB *input, CRGB *output, u8 inputWidth,
63 u8 inputHeight, u8 outputWidth, u8 outputHeight) {
64 for (u8 y = 0; y < outputHeight; y++) {
65 for (u8 x = 0; x < outputWidth; x++) {
66 // Use 8-bit fixed-point arithmetic with 8 fractional bits
67 // (scale factor of 256)
68 u16 fx = ((u16)x * (inputWidth - 1) * 256) / (outputWidth - 1);
69 u16 fy = ((u16)y * (inputHeight - 1) * 256) / (outputHeight - 1);
70
71 u8 ix = fx >> 8; // Integer part
72 u8 iy = fy >> 8;
73 u8 dx = fx & 0xFF; // Fractional part
74 u8 dy = fy & 0xFF;
75
76 u8 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
77 u8 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
78
79 // Direct array access - no XY mapping overhead
80 u16 i00 = iy * inputWidth + ix;
81 u16 i10 = iy * inputWidth + ix1;
82 u16 i01 = iy1 * inputWidth + ix;
83 u16 i11 = iy1 * inputWidth + ix1;
84
85 CRGB c00 = input[i00];
86 CRGB c10 = input[i10];
87 CRGB c01 = input[i01];
88 CRGB c11 = input[i11];
89
91 result.r =
92 bilinearInterpolatePowerOf2(c00.r, c10.r, c01.r, c11.r, dx, dy);
93 result.g =
94 bilinearInterpolatePowerOf2(c00.g, c10.g, c01.g, c11.g, dx, dy);
95 result.b =
96 bilinearInterpolatePowerOf2(c00.b, c10.b, c01.b, c11.b, dx, dy);
97
98 // Direct array access - no XY mapping overhead
99 u16 idx = y * outputWidth + x;
100 output[idx] = result;
101 }
102 }
103}
104
105void upscaleArbitrary(const CRGB *input, CRGB *output, u16 inputWidth,
106 u16 inputHeight, const XYMap& xyMap) {
107 u16 n = xyMap.getTotal();
108 u16 outputWidth = xyMap.getWidth();
109 u16 outputHeight = xyMap.getHeight();
110 const u16 scale_factor = 256; // Using 8 bits for the fractional part
111
112 for (u16 y = 0; y < outputHeight; y++) {
113 for (u16 x = 0; x < outputWidth; x++) {
114 // Calculate the corresponding position in the input grid
115 u32 fx = ((u32)x * (inputWidth - 1) * scale_factor) /
116 (outputWidth - 1);
117 u32 fy = ((u32)y * (inputHeight - 1) * scale_factor) /
118 (outputHeight - 1);
119
120 u16 ix = fx / scale_factor; // Integer part of x
121 u16 iy = fy / scale_factor; // Integer part of y
122 u16 dx = fx % scale_factor; // Fractional part of x
123 u16 dy = fy % scale_factor; // Fractional part of y
124
125 u16 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
126 u16 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
127
128 u16 i00 = iy * inputWidth + ix;
129 u16 i10 = iy * inputWidth + ix1;
130 u16 i01 = iy1 * inputWidth + ix;
131 u16 i11 = iy1 * inputWidth + ix1;
132
133 CRGB c00 = input[i00];
134 CRGB c10 = input[i10];
135 CRGB c01 = input[i01];
136 CRGB c11 = input[i11];
137
138 CRGB result;
139 result.r = bilinearInterpolate(c00.r, c10.r, c01.r, c11.r, dx, dy);
140 result.g = bilinearInterpolate(c00.g, c10.g, c01.g, c11.g, dx, dy);
141 result.b = bilinearInterpolate(c00.b, c10.b, c01.b, c11.b, dx, dy);
142
143 u16 idx = xyMap.mapToIndex(x, y);
144 if (idx < n) {
145 output[idx] = result;
146 }
147 }
148 }
149}
150u8 bilinearInterpolate(u8 v00, u8 v10, u8 v01, u8 v11,
151 u16 dx, u16 dy) {
152 u16 dx_inv = 256 - dx;
153 u16 dy_inv = 256 - dy;
154
155 u32 w00 = (u32)dx_inv * dy_inv;
156 u32 w10 = (u32)dx * dy_inv;
157 u32 w01 = (u32)dx_inv * dy;
158 u32 w11 = (u32)dx * dy;
159
160 u32 sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
161
162 // Normalize the result by dividing by 65536 (shift right by 16 bits),
163 // with rounding
164 u8 result = (u8)((sum + 32768) >> 16);
165
166 return result;
167}
168
169void upscalePowerOf2(const CRGB *input, CRGB *output, u8 inputWidth,
170 u8 inputHeight, const XYMap& xyMap) {
171 u8 width = xyMap.getWidth();
172 u8 height = xyMap.getHeight();
173 if (width != xyMap.getWidth() || height != xyMap.getHeight()) {
174 // xyMap has width and height that do not fit in an u16.
175 return;
176 }
177 u16 n = xyMap.getTotal();
178
179 for (u8 y = 0; y < height; y++) {
180 for (u8 x = 0; x < width; x++) {
181 // Use 8-bit fixed-point arithmetic with 8 fractional bits
182 // (scale factor of 256)
183 u16 fx = ((u16)x * (inputWidth - 1) * 256) / (width - 1);
184 u16 fy =
185 ((u16)y * (inputHeight - 1) * 256) / (height - 1);
186
187 u8 ix = fx >> 8; // Integer part
188 u8 iy = fy >> 8;
189 u8 dx = fx & 0xFF; // Fractional part
190 u8 dy = fy & 0xFF;
191
192 u8 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
193 u8 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
194
195 u16 i00 = iy * inputWidth + ix;
196 u16 i10 = iy * inputWidth + ix1;
197 u16 i01 = iy1 * inputWidth + ix;
198 u16 i11 = iy1 * inputWidth + ix1;
199
200 CRGB c00 = input[i00];
201 CRGB c10 = input[i10];
202 CRGB c01 = input[i01];
203 CRGB c11 = input[i11];
204
205 CRGB result;
206 result.r =
207 bilinearInterpolatePowerOf2(c00.r, c10.r, c01.r, c11.r, dx, dy);
208 result.g =
209 bilinearInterpolatePowerOf2(c00.g, c10.g, c01.g, c11.g, dx, dy);
210 result.b =
211 bilinearInterpolatePowerOf2(c00.b, c10.b, c01.b, c11.b, dx, dy);
212
213 u16 idx = xyMap.mapToIndex(x, y);
214 if (idx < n) {
215 output[idx] = result;
216 }
217 }
218 }
219}
220
222 u8 v11, u8 dx, u8 dy) {
223 u16 dx_inv = 256 - dx; // 0 to 256
224 u16 dy_inv = 256 - dy; // 0 to 256
225
226 // Scale down weights to fit into u16
227 u16 w00 = (dx_inv * dy_inv) >> 8; // Max value 256
228 u16 w10 = (dx * dy_inv) >> 8;
229 u16 w01 = (dx_inv * dy) >> 8;
230 u16 w11 = (dx * dy) >> 8;
231
232 // Sum of weights should be approximately 256
233 u16 weight_sum = w00 + w10 + w01 + w11;
234
235 // Compute the weighted sum of pixel values
236 u16 sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
237
238 // Normalize the result
239 u8 result = (sum + (weight_sum >> 1)) / weight_sum;
240
241 return result;
242}
243
244// Floating-point version of bilinear interpolation
245u8 upscaleFloat(u8 v00, u8 v10, u8 v01,
246 u8 v11, float dx, float dy) {
247 float dx_inv = 1.0f - dx;
248 float dy_inv = 1.0f - dy;
249
250 // Calculate the weights for each corner
251 float w00 = dx_inv * dy_inv;
252 float w10 = dx * dy_inv;
253 float w01 = dx_inv * dy;
254 float w11 = dx * dy;
255
256 // Compute the weighted sum
257 float sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
258
259 // Clamp the result to [0, 255] and round
260 u8 result = static_cast<u8>(sum + 0.5f);
261
262 return result;
263}
264
265// Floating-point version for arbitrary grid sizes
266void upscaleArbitraryFloat(const CRGB *input, CRGB *output, u16 inputWidth,
267 u16 inputHeight, const XYMap& xyMap) {
268 u16 n = xyMap.getTotal();
269 u16 outputWidth = xyMap.getWidth();
270 u16 outputHeight = xyMap.getHeight();
271
272 for (u16 y = 0; y < outputHeight; y++) {
273 for (u16 x = 0; x < outputWidth; x++) {
274 // Map output pixel to input grid position
275 float fx =
276 static_cast<float>(x) * (inputWidth - 1) / (outputWidth - 1);
277 float fy =
278 static_cast<float>(y) * (inputHeight - 1) / (outputHeight - 1);
279
280 u16 ix = static_cast<u16>(fx);
281 u16 iy = static_cast<u16>(fy);
282 float dx = fx - ix;
283 float dy = fy - iy;
284
285 u16 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
286 u16 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
287
288 u16 i00 = iy * inputWidth + ix;
289 u16 i10 = iy * inputWidth + ix1;
290 u16 i01 = iy1 * inputWidth + ix;
291 u16 i11 = iy1 * inputWidth + ix1;
292
293 CRGB c00 = input[i00];
294 CRGB c10 = input[i10];
295 CRGB c01 = input[i01];
296 CRGB c11 = input[i11];
297
298 CRGB result;
299 result.r =
300 upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
301 result.g =
302 upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
303 result.b =
304 upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
305
306 u16 idx = xyMap.mapToIndex(x, y);
307 if (idx < n) {
308 output[idx] = result;
309 }
310 }
311 }
312}
313
314// Floating-point version for power-of-two grid sizes
315void upscaleFloat(const CRGB *input, CRGB *output, u8 inputWidth,
316 u8 inputHeight, const XYMap& xyMap) {
317 u8 outputWidth = xyMap.getWidth();
318 u8 outputHeight = xyMap.getHeight();
319 if (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()) {
320 // xyMap has width and height that do not fit in a u8.
321 return;
322 }
323 u16 n = xyMap.getTotal();
324
325 for (u8 y = 0; y < outputHeight; y++) {
326 for (u8 x = 0; x < outputWidth; x++) {
327 // Map output pixel to input grid position
328 float fx =
329 static_cast<float>(x) * (inputWidth - 1) / (outputWidth - 1);
330 float fy =
331 static_cast<float>(y) * (inputHeight - 1) / (outputHeight - 1);
332
333 u8 ix = static_cast<u8>(fx);
334 u8 iy = static_cast<u8>(fy);
335 float dx = fx - ix;
336 float dy = fy - iy;
337
338 u8 ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
339 u8 iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
340
341 u16 i00 = iy * inputWidth + ix;
342 u16 i10 = iy * inputWidth + ix1;
343 u16 i01 = iy1 * inputWidth + ix;
344 u16 i11 = iy1 * inputWidth + ix1;
345
346 CRGB c00 = input[i00];
347 CRGB c10 = input[i10];
348 CRGB c01 = input[i01];
349 CRGB c11 = input[i11];
350
351 CRGB result;
352 result.r =
353 upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
354 result.g =
355 upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
356 result.b =
357 upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
358
359 u16 idx = xyMap.mapToIndex(x, y);
360 if (idx < n) {
361 output[idx] = result;
362 }
363 }
364 }
365}
366
367} // namespace fl
fl::XYMap xyMap
Defines the 8-bit red, green, and blue (RGB) pixel type in the fl namespace.
unsigned char u8
Definition stdint.h:131
void upscaleRectangular(const CRGB *input, CRGB *output, u16 inputWidth, u16 inputHeight, u16 outputWidth, u16 outputHeight)
Optimized upscale for rectangular/line-by-line XY maps.
void upscaleRectangularPowerOf2(const CRGB *input, CRGB *output, u8 inputWidth, u8 inputHeight, u8 outputWidth, u8 outputHeight)
Optimized upscale for rectangular/line-by-line XY maps (power-of-2 version).
u8 u8 height
Definition blur.h:186
u8 bilinearInterpolatePowerOf2(u8 v00, u8 v10, u8 v01, u8 v11, u8 dx, u8 dy)
void upscaleArbitraryFloat(const CRGB *input, CRGB *output, u16 inputWidth, u16 inputHeight, const XYMap &xyMap)
void upscaleArbitrary(const CRGB *input, CRGB *output, u16 inputWidth, u16 inputHeight, const XYMap &xyMap)
Performs bilinear interpolation for upscaling an image.
u8 width
Definition blur.h:186
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
u8 bilinearInterpolate(u8 v00, u8 v10, u8 v01, u8 v11, u16 dx, u16 dy)
u8 upscaleFloat(u8 v00, u8 v10, u8 v01, u8 v11, float dx, float dy)
void upscalePowerOf2(const CRGB *input, CRGB *output, u8 inputWidth, u8 inputHeight, const XYMap &xyMap)
Performs bilinear interpolation for upscaling an image.
Base definition for an LED controller.
Definition crgb.hpp:179
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38