| 81 | } |
| 82 | |
| 83 | void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst, |
| 84 | const XYMap &dstXY) { |
| 85 | const fl::u16 srcWidth = srcXY.getWidth(); |
| 86 | const fl::u16 srcHeight = srcXY.getHeight(); |
| 87 | const fl::u16 dstWidth = dstXY.getWidth(); |
| 88 | const fl::u16 dstHeight = dstXY.getHeight(); |
| 89 | |
| 90 | const fl::u32 FP_ONE = 256; // Q8.8 fixed-point multiplier |
| 91 | |
| 92 | FASTLED_ASSERT(dstWidth <= srcWidth, |
| 93 | "Destination width must be <= source width"); |
| 94 | FASTLED_ASSERT(dstHeight <= srcHeight, |
| 95 | "Destination height must be <= source height"); |
| 96 | |
| 97 | for (fl::u16 dy = 0; dy < dstHeight; ++dy) { |
| 98 | // Fractional boundaries in Q8.8 |
| 99 | fl::u32 dstY0 = (dy * srcHeight * FP_ONE) / dstHeight; |
| 100 | fl::u32 dstY1 = ((dy + 1) * srcHeight * FP_ONE) / dstHeight; |
| 101 | |
| 102 | for (fl::u16 dx = 0; dx < dstWidth; ++dx) { |
| 103 | fl::u32 dstX0 = (dx * srcWidth * FP_ONE) / dstWidth; |
| 104 | fl::u32 dstX1 = ((dx + 1) * srcWidth * FP_ONE) / dstWidth; |
| 105 | |
| 106 | fl::u64 rSum = 0, gSum = 0, bSum = 0; |
| 107 | fl::u32 totalWeight = 0; |
| 108 | |
| 109 | // Find covered source pixels |
| 110 | fl::u16 srcY_start = dstY0 / FP_ONE; |
| 111 | fl::u16 srcY_end = (dstY1 + FP_ONE - 1) / FP_ONE; // ceil |
| 112 | |
| 113 | fl::u16 srcX_start = dstX0 / FP_ONE; |
| 114 | fl::u16 srcX_end = (dstX1 + FP_ONE - 1) / FP_ONE; // ceil |
| 115 | |
| 116 | for (fl::u16 sy = srcY_start; sy < srcY_end; ++sy) { |
| 117 | // Calculate vertical overlap in Q8.8 |
| 118 | fl::u32 sy0 = sy * FP_ONE; |
| 119 | fl::u32 sy1 = (sy + 1) * FP_ONE; |
| 120 | fl::u32 y_overlap = fl::min(dstY1, sy1) - fl::max(dstY0, sy0); |
| 121 | if (y_overlap == 0) |
| 122 | continue; |
| 123 | |
| 124 | for (fl::u16 sx = srcX_start; sx < srcX_end; ++sx) { |
| 125 | fl::u32 sx0 = sx * FP_ONE; |
| 126 | fl::u32 sx1 = (sx + 1) * FP_ONE; |
| 127 | fl::u32 x_overlap = fl::min(dstX1, sx1) - fl::max(dstX0, sx0); |
| 128 | if (x_overlap == 0) |
| 129 | continue; |
| 130 | |
| 131 | fl::u32 weight = (x_overlap * y_overlap + (FP_ONE >> 1)) >> |
| 132 | 8; // Q8.8 * Q8.8 → Q16.16 → Q8.8 |
| 133 | |
| 134 | const CRGB &p = src[srcXY.mapToIndex(sx, sy)]; |
| 135 | rSum += p.r * weight; |
| 136 | gSum += p.g * weight; |
| 137 | bSum += p.b * weight; |
| 138 | totalWeight += weight; |
| 139 | } |
| 140 | } |