#include #include #include #include #include #include #include #define PI 3.14159265358979323846 #ifdef _WIN32 #define BL_FX_EXPORT __declspec(dllexport) #else #define BL_FX_EXPORT __attribute__((visibility("default"))) #endif // Pixel Helpers (PRGB32: A, R, G, B in memory) static inline uint8_t _clamp_u8(double v) { return (uint8_t)std::max(0.0, std::min(255.0, v)); } static inline uint32_t _pack_rgba(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; } // ACES Filmic Tone Mapping Curve static inline double _aces_curve(double x) { double a = 2.51; double b = 0.03; double c = 2.43; double d = 0.59; double e = 0.14; return std::max(0.0, std::min(1.0, (x * (a * x + b)) / (x * (c * x + d) + e))); } extern "C" { // ============================================================================ // 1. Fast Separable Gaussian / Box Blur Engine // ============================================================================ static void _box_blur_h(const uint32_t* src, uint32_t* dst, int w, int h, int stride_px, int radius) { if (radius <= 0) { std::memcpy(dst, src, h * stride_px * sizeof(uint32_t)); return; } double iarr = 1.0 / (radius + radius + 1); unsigned int n_threads = std::min(8u, std::max(1u, std::thread::hardware_concurrency())); if (h < 128) n_threads = 1; auto worker = [=](int y_start, int y_end) { for (int i = y_start; i < y_end; i++) { int ti = i * stride_px; int li = ti; int ri = ti + radius; uint32_t fv = src[ti]; uint32_t lv = src[ti + w - 1]; int fv_a = (fv >> 24) & 0xFF, fv_r = (fv >> 16) & 0xFF, fv_g = (fv >> 8) & 0xFF, fv_b = fv & 0xFF; int lv_a = (lv >> 24) & 0xFF, lv_r = (lv >> 16) & 0xFF, lv_g = (lv >> 8) & 0xFF, lv_b = lv & 0xFF; int val_a = (radius + 1) * fv_a; int val_r = (radius + 1) * fv_r; int val_g = (radius + 1) * fv_g; int val_b = (radius + 1) * fv_b; for (int j = 0; j < radius; j++) { uint32_t p = src[ti + j]; val_a += (p >> 24) & 0xFF; val_r += (p >> 16) & 0xFF; val_g += (p >> 8) & 0xFF; val_b += p & 0xFF; } for (int j = 0; j <= radius; j++) { uint32_t p = src[ri++]; val_a += ((p >> 24) & 0xFF) - fv_a; val_r += ((p >> 16) & 0xFF) - fv_r; val_g += ((p >> 8) & 0xFF) - fv_g; val_b += (p & 0xFF) - fv_b; dst[ti++] = _pack_rgba((uint8_t)(val_a * iarr), (uint8_t)(val_r * iarr), (uint8_t)(val_g * iarr), (uint8_t)(val_b * iarr)); } for (int j = radius + 1; j < w - radius; j++) { uint32_t p_in = src[ri++]; uint32_t p_out = src[li++]; val_a += ((p_in >> 24) & 0xFF) - ((p_out >> 24) & 0xFF); val_r += ((p_in >> 16) & 0xFF) - ((p_out >> 16) & 0xFF); val_g += ((p_in >> 8) & 0xFF) - ((p_out >> 8) & 0xFF); val_b += (p_in & 0xFF) - (p_out & 0xFF); dst[ti++] = _pack_rgba((uint8_t)(val_a * iarr), (uint8_t)(val_r * iarr), (uint8_t)(val_g * iarr), (uint8_t)(val_b * iarr)); } for (int j = w - radius; j < w; j++) { uint32_t p_out = src[li++]; val_a += lv_a - ((p_out >> 24) & 0xFF); val_r += lv_r - ((p_out >> 16) & 0xFF); val_g += lv_g - ((p_out >> 8) & 0xFF); val_b += lv_b - (p_out & 0xFF); dst[ti++] = _pack_rgba((uint8_t)(val_a * iarr), (uint8_t)(val_r * iarr), (uint8_t)(val_g * iarr), (uint8_t)(val_b * iarr)); } } }; if (n_threads <= 1) { worker(0, h); } else { std::vector threads; int chunk = (h + n_threads - 1) / n_threads; for (unsigned int t = 0; t < n_threads; t++) { int y_start = t * chunk; int y_end = std::min(h, y_start + chunk); if (y_start < y_end) { threads.emplace_back(worker, y_start, y_end); } } for (auto& th : threads) th.join(); } } static void _box_blur_v(const uint32_t* src, uint32_t* dst, int w, int h, int stride_px, int radius) { if (radius <= 0) { std::memcpy(dst, src, h * stride_px * sizeof(uint32_t)); return; } double iarr = 1.0 / (radius + radius + 1); unsigned int n_threads = std::min(8u, std::max(1u, std::thread::hardware_concurrency())); if (w < 128) n_threads = 1; auto worker = [=](int x_start, int x_end) { for (int i = x_start; i < x_end; i++) { int ti = i; int li = ti; int ri = ti + radius * stride_px; uint32_t fv = src[ti]; uint32_t lv = src[ti + (h - 1) * stride_px]; int fv_a = (fv >> 24) & 0xFF, fv_r = (fv >> 16) & 0xFF, fv_g = (fv >> 8) & 0xFF, fv_b = fv & 0xFF; int lv_a = (lv >> 24) & 0xFF, lv_r = (lv >> 16) & 0xFF, lv_g = (lv >> 8) & 0xFF, lv_b = lv & 0xFF; int val_a = (radius + 1) * fv_a; int val_r = (radius + 1) * fv_r; int val_g = (radius + 1) * fv_g; int val_b = (radius + 1) * fv_b; for (int j = 0; j < radius; j++) { uint32_t p = src[ti + j * stride_px]; val_a += (p >> 24) & 0xFF; val_r += (p >> 16) & 0xFF; val_g += (p >> 8) & 0xFF; val_b += p & 0xFF; } for (int j = 0; j <= radius; j++) { uint32_t p = src[ri]; ri += stride_px; val_a += ((p >> 24) & 0xFF) - fv_a; val_r += ((p >> 16) & 0xFF) - fv_r; val_g += ((p >> 8) & 0xFF) - fv_g; val_b += (p & 0xFF) - fv_b; dst[ti] = _pack_rgba((uint8_t)(val_a * iarr), (uint8_t)(val_r * iarr), (uint8_t)(val_g * iarr), (uint8_t)(val_b * iarr)); ti += stride_px; } for (int j = radius + 1; j < h - radius; j++) { uint32_t p_in = src[ri]; uint32_t p_out = src[li]; ri += stride_px; li += stride_px; val_a += ((p_in >> 24) & 0xFF) - ((p_out >> 24) & 0xFF); val_r += ((p_in >> 16) & 0xFF) - ((p_out >> 16) & 0xFF); val_g += ((p_in >> 8) & 0xFF) - ((p_out >> 8) & 0xFF); val_b += (p_in & 0xFF) - (p_out & 0xFF); dst[ti] = _pack_rgba((uint8_t)(val_a * iarr), (uint8_t)(val_r * iarr), (uint8_t)(val_g * iarr), (uint8_t)(val_b * iarr)); ti += stride_px; } for (int j = h - radius; j < h; j++) { uint32_t p_out = src[li]; li += stride_px; val_a += lv_a - ((p_out >> 24) & 0xFF); val_r += lv_r - ((p_out >> 16) & 0xFF); val_g += lv_g - ((p_out >> 8) & 0xFF); val_b += lv_b - (p_out & 0xFF); dst[ti] = _pack_rgba((uint8_t)(val_a * iarr), (uint8_t)(val_r * iarr), (uint8_t)(val_g * iarr), (uint8_t)(val_b * iarr)); ti += stride_px; } } }; if (n_threads <= 1) { worker(0, w); } else { std::vector threads; int chunk = (w + n_threads - 1) / n_threads; for (unsigned int t = 0; t < n_threads; t++) { int x_start = t * chunk; int x_end = std::min(w, x_start + chunk); if (x_start < x_end) { threads.emplace_back(worker, x_start, x_end); } } for (auto& th : threads) th.join(); } } BL_FX_EXPORT int bl_fx_gaussian_blur(uint32_t* pixels, int w, int h, int stride_bytes, double sigma) { if (!pixels || w <= 0 || h <= 0 || sigma <= 0.0) return 0; int stride_px = stride_bytes / 4; double w_ideal = std::sqrt((12.0 * sigma * sigma / 3.0) + 1.0); int wl = (int)std::floor(w_ideal); if (wl % 2 == 0) wl--; int wu = wl + 2; double m_ideal = (12.0 * sigma * sigma - 3.0 * wl * wl - 12.0 * wl - 9.0) / (-4.0 * wl - 4.0); int m = (int)std::round(m_ideal); int r[3]; for (int i = 0; i < 3; i++) { r[i] = ((i < m ? wl : wu) - 1) / 2; } std::vector temp(h * stride_px); for (int pass = 0; pass < 3; pass++) { int radius = r[pass]; if (radius <= 0) continue; _box_blur_h(pixels, temp.data(), w, h, stride_px, radius); _box_blur_v(temp.data(), pixels, w, h, stride_px, radius); } return 0; } // ============================================================================ // 2. Ultra-Fast Multi-Scale Downsampled Pyramid Bloom (4K Real-Time) // ============================================================================ BL_FX_EXPORT int bl_fx_bloom( uint32_t* pixels, int w, int h, int stride_bytes, double threshold, double intensity, double radius, uint32_t tint_color ) { if (!pixels || w <= 0 || h <= 0) return -1; int stride_px = stride_bytes / 4; // Determine optimal hierarchical downsample factor int ds = 1; if (w >= 2560 || h >= 1440) ds = 4; // 4K UHD: 4x downsample (960x540) else if (w >= 1280 || h >= 720) ds = 2; // 1080p: 2x downsample (960x540) int dw = w / ds; int dh = h / ds; if (dw < 4 || dh < 4) ds = 1; double tint_r = ((tint_color >> 16) & 0xFF) / 255.0; double tint_g = ((tint_color >> 8) & 0xFF) / 255.0; double tint_b = (tint_color & 0xFF) / 255.0; int thresh_val = (int)(threshold * 255.0); if (ds > 1) { // Direct bright-pass extraction into tiny downsampled buffer (< 2MB) std::vector down_buf(dw * dh, 0); for (int dy = 0; dy < dh; dy++) { int sy = dy * ds; int row_idx = sy * stride_px; int down_row = dy * dw; for (int dx = 0; dx < dw; dx++) { int sx = dx * ds; uint32_t p = pixels[row_idx + sx]; int r = (p >> 16) & 0xFF; int g = (p >> 8) & 0xFF; int b = p & 0xFF; int luma = (r * 299 + g * 587 + b * 114) / 1000; if (luma > thresh_val) { double excess = (double)(luma - thresh_val) / std::max(1.0, 255.0 - thresh_val); uint8_t br_r = _clamp_u8(r * excess * tint_r); uint8_t br_g = _clamp_u8(g * excess * tint_g); uint8_t br_b = _clamp_u8(b * excess * tint_b); down_buf[down_row + dx] = _pack_rgba(255, br_r, br_g, br_b); } } } // Separable blur runs in < 1 millisecond on downsampled buffer double scaled_rad = std::max(1.0, radius / (double)ds); bl_fx_gaussian_blur(down_buf.data(), dw, dh, dw * 4, scaled_rad); // Multi-threaded soft screen-additive blend directly onto 4K target unsigned int n_threads = std::min(8u, std::max(1u, std::thread::hardware_concurrency())); auto comp_worker = [=, &down_buf](int y_start, int y_end) { for (int y = y_start; y < y_end; y++) { int dy = std::min(dh - 1, y / ds); int row_offset = y * stride_px; int down_row = dy * dw; for (int x = 0; x < w; x++) { int dx = std::min(dw - 1, x / ds); uint32_t bp = down_buf[down_row + dx]; if (bp == 0) continue; uint32_t orig = pixels[row_offset + x]; uint8_t oa = (orig >> 24) & 0xFF; double or_f = ((orig >> 16) & 0xFF) / 255.0; double og_f = ((orig >> 8) & 0xFF) / 255.0; double ob_f = (orig & 0xFF) / 255.0; double bpr_f = (((bp >> 16) & 0xFF) / 255.0) * intensity; double bpg_f = (((bp >> 8) & 0xFF) / 255.0) * intensity; double bpb_f = ((bp & 0xFF) / 255.0) * intensity; double out_r = 1.0 - (1.0 - or_f) * (1.0 - std::min(0.99, bpr_f)); double out_g = 1.0 - (1.0 - og_f) * (1.0 - std::min(0.99, bpg_f)); double out_b = 1.0 - (1.0 - ob_f) * (1.0 - std::min(0.99, bpb_f)); pixels[row_offset + x] = _pack_rgba(oa, _clamp_u8(out_r * 255.0), _clamp_u8(out_g * 255.0), _clamp_u8(out_b * 255.0)); } } }; if (n_threads <= 1) { comp_worker(0, h); } else { std::vector threads; int chunk = (h + n_threads - 1) / n_threads; for (unsigned int t = 0; t < n_threads; t++) { int y_start = t * chunk; int y_end = std::min(h, y_start + chunk); if (y_start < y_end) threads.emplace_back(comp_worker, y_start, y_end); } for (auto& th : threads) th.join(); } return 0; } // Small image path size_t total = h * stride_px; std::vector bright_pass(total, 0); for (size_t i = 0; i < total; i++) { uint32_t p = pixels[i]; int a = (p >> 24) & 0xFF; int r = (p >> 16) & 0xFF; int g = (p >> 8) & 0xFF; int b = p & 0xFF; int luma = (r * 299 + g * 587 + b * 114) / 1000; if (luma > thresh_val) { double excess = (double)(luma - thresh_val) / std::max(1.0, 255.0 - thresh_val); uint8_t br_r = _clamp_u8(r * excess * tint_r); uint8_t br_g = _clamp_u8(g * excess * tint_g); uint8_t br_b = _clamp_u8(b * excess * tint_b); bright_pass[i] = _pack_rgba(a, br_r, br_g, br_b); } } bl_fx_gaussian_blur(bright_pass.data(), w, h, stride_bytes, radius); for (size_t i = 0; i < total; i++) { uint32_t p = pixels[i]; uint32_t bp = bright_pass[i]; if (bp == 0) continue; int a = (p >> 24) & 0xFF; double or_f = ((p >> 16) & 0xFF) / 255.0; double og_f = ((p >> 8) & 0xFF) / 255.0; double ob_f = (p & 0xFF) / 255.0; double bpr_f = (((bp >> 16) & 0xFF) / 255.0) * intensity; double bpg_f = (((bp >> 8) & 0xFF) / 255.0) * intensity; double bpb_f = ((bp & 0xFF) / 255.0) * intensity; double out_r = 1.0 - (1.0 - or_f) * (1.0 - std::min(0.99, bpr_f)); double out_g = 1.0 - (1.0 - og_f) * (1.0 - std::min(0.99, bpg_f)); double out_b = 1.0 - (1.0 - ob_f) * (1.0 - std::min(0.99, bpb_f)); pixels[i] = _pack_rgba(a, _clamp_u8(out_r * 255.0), _clamp_u8(out_g * 255.0), _clamp_u8(out_b * 255.0)); } return 0; } // ============================================================================ // 3. ACES Filmic Tone Mapping (Anti-Burnout / True HDR Color Mapping) // ============================================================================ BL_FX_EXPORT int bl_fx_tone_map_aces(uint32_t* pixels, int w, int h, int stride_bytes, double exposure) { if (!pixels || w <= 0 || h <= 0) return -1; int stride_px = stride_bytes / 4; size_t total = h * stride_px; for (size_t i = 0; i < total; i++) { uint32_t p = pixels[i]; uint8_t a = (p >> 24) & 0xFF; double r = (((p >> 16) & 0xFF) / 255.0) * exposure; double g = (((p >> 8) & 0xFF) / 255.0) * exposure; double b = ((p & 0xFF) / 255.0) * exposure; uint8_t out_r = _clamp_u8(_aces_curve(r) * 255.0); uint8_t out_g = _clamp_u8(_aces_curve(g) * 255.0); uint8_t out_b = _clamp_u8(_aces_curve(b) * 255.0); pixels[i] = _pack_rgba(a, out_r, out_g, out_b); } return 0; } // ============================================================================ // 4. Chromatic Aberration & Lens Fringing // ============================================================================ BL_FX_EXPORT int bl_fx_chromatic_aberration( uint32_t* pixels, int w, int h, int stride_bytes, double shift_x, double shift_y, double angle_rad ) { if (!pixels || w <= 0 || h <= 0) return -1; int stride_px = stride_bytes / 4; std::vector src_copy(pixels, pixels + h * stride_px); double dx = std::cos(angle_rad) * shift_x; double dy = std::sin(angle_rad) * shift_y; int offset_rx = (int)std::round(dx); int offset_ry = (int)std::round(dy); int offset_bx = (int)std::round(-dx); int offset_by = (int)std::round(-dy); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { uint32_t orig = src_copy[y * stride_px + x]; uint8_t a = (orig >> 24) & 0xFF; uint8_t g = (orig >> 8) & 0xFF; int rx = std::max(0, std::min(w - 1, x + offset_rx)); int ry = std::max(0, std::min(h - 1, y + offset_ry)); uint8_t r = (src_copy[ry * stride_px + rx] >> 16) & 0xFF; int bx = std::max(0, std::min(w - 1, x + offset_bx)); int by = std::max(0, std::min(h - 1, y + offset_by)); uint8_t b = src_copy[by * stride_px + bx] & 0xFF; pixels[y * stride_px + x] = _pack_rgba(a, r, g, b); } } return 0; } // ============================================================================ // 5. Cinematic Vignette (Radial Optical Falloff) // ============================================================================ BL_FX_EXPORT int bl_fx_vignette( uint32_t* pixels, int w, int h, int stride_bytes, double intensity, double radius, double smoothness, uint32_t vignette_color ) { if (!pixels || w <= 0 || h <= 0) return -1; int stride_px = stride_bytes / 4; double cx = w * 0.5; double cy = h * 0.5; double max_dist = std::hypot(cx, cy); uint8_t vig_r = (vignette_color >> 16) & 0xFF; uint8_t vig_g = (vignette_color >> 8) & 0xFF; uint8_t vig_b = vignette_color & 0xFF; for (int y = 0; y < h; y++) { double dy = y - cy; for (int x = 0; x < w; x++) { double dx = x - cx; double dist = std::hypot(dx, dy) / max_dist; double edge0 = radius; double edge1 = radius + smoothness; double t = std::max(0.0, std::min(1.0, (dist - edge0) / (edge1 - edge0 > 0 ? edge1 - edge0 : 0.001))); double vig_factor = t * t * (3.0 - 2.0 * t) * intensity; uint32_t p = pixels[y * stride_px + x]; uint8_t a = (p >> 24) & 0xFF; uint8_t r = (p >> 16) & 0xFF; uint8_t g = (p >> 8) & 0xFF; uint8_t b = p & 0xFF; uint8_t out_r = _clamp_u8(r * (1.0 - vig_factor) + vig_r * vig_factor); uint8_t out_g = _clamp_u8(g * (1.0 - vig_factor) + vig_g * vig_factor); uint8_t out_b = _clamp_u8(b * (1.0 - vig_factor) + vig_b * vig_factor); pixels[y * stride_px + x] = _pack_rgba(a, out_r, out_g, out_b); } } return 0; } // ============================================================================ // 6. Film Grain (Analog Cinematic Noise) // ============================================================================ static inline uint32_t _xorshift32(uint32_t* state) { uint32_t x = *state; x ^= x << 13; x ^= x >> 17; x ^= x << 5; *state = x; return x; } BL_FX_EXPORT int bl_fx_film_grain( uint32_t* pixels, int w, int h, int stride_bytes, double intensity, uint32_t seed ) { if (!pixels || w <= 0 || h <= 0) return -1; int stride_px = stride_bytes / 4; size_t total = h * stride_px; uint32_t state = (seed != 0) ? seed : 123456789; double scale = intensity * 255.0; for (size_t i = 0; i < total; i++) { uint32_t p = pixels[i]; uint8_t a = (p >> 24) & 0xFF; int r = (p >> 16) & 0xFF; int g = (p >> 8) & 0xFF; int b = p & 0xFF; double noise = (((_xorshift32(&state) & 0xFFFF) / 65535.0) * 2.0 - 1.0) * scale; int nr = _clamp_u8(r + noise); int ng = _clamp_u8(g + noise); int nb = _clamp_u8(b + noise); pixels[i] = _pack_rgba(a, (uint8_t)nr, (uint8_t)ng, (uint8_t)nb); } return 0; } // ============================================================================ // 7. Color Adjustments (Brightness, Contrast, Saturation, Gamma) // ============================================================================ BL_FX_EXPORT int bl_fx_color_grade( uint32_t* pixels, int w, int h, int stride_bytes, double brightness, double contrast, double saturation, double gamma ) { if (!pixels || w <= 0 || h <= 0) return -1; int stride_px = stride_bytes / 4; size_t total = h * stride_px; uint8_t lut[256]; double inv_gamma = (gamma > 0.0) ? (1.0 / gamma) : 1.0; for (int i = 0; i < 256; i++) { double v = i / 255.0; v = (v - 0.5) * contrast + 0.5 + brightness; v = std::max(0.0, std::min(1.0, v)); if (gamma != 1.0) { v = std::pow(v, inv_gamma); } lut[i] = _clamp_u8(v * 255.0); } for (size_t i = 0; i < total; i++) { uint32_t p = pixels[i]; uint8_t a = (p >> 24) & 0xFF; uint8_t r = lut[(p >> 16) & 0xFF]; uint8_t g = lut[(p >> 8) & 0xFF]; uint8_t b = lut[p & 0xFF]; if (saturation != 1.0) { double gray = 0.299 * r + 0.587 * g + 0.114 * b; r = _clamp_u8(gray + (r - gray) * saturation); g = _clamp_u8(gray + (g - gray) * saturation); b = _clamp_u8(gray + (b - gray) * saturation); } pixels[i] = _pack_rgba(a, r, g, b); } return 0; } // ============================================================================ // 8. Cyberpunk Scanlines & Glitch Displacement // ============================================================================ BL_FX_EXPORT int bl_fx_scanlines( uint32_t* pixels, int w, int h, int stride_bytes, int line_spacing, double opacity ) { if (!pixels || w <= 0 || h <= 0 || line_spacing <= 0) return 0; int stride_px = stride_bytes / 4; double factor = 1.0 - std::max(0.0, std::min(1.0, opacity)); for (int y = 0; y < h; y++) { if (y % line_spacing == 0) { for (int x = 0; x < w; x++) { uint32_t p = pixels[y * stride_px + x]; uint8_t a = (p >> 24) & 0xFF; uint8_t r = (uint8_t)(((p >> 16) & 0xFF) * factor); uint8_t g = (uint8_t)(((p >> 8) & 0xFF) * factor); uint8_t b = (uint8_t)((p & 0xFF) * factor); pixels[y * stride_px + x] = _pack_rgba(a, r, g, b); } } } return 0; } BL_FX_EXPORT int bl_fx_glitch_displacement( uint32_t* pixels, int w, int h, int stride_bytes, int num_slices, int max_displacement, uint32_t seed ) { if (!pixels || w <= 0 || h <= 0 || num_slices <= 0) return 0; int stride_px = stride_bytes / 4; std::vector copy(pixels, pixels + h * stride_px); uint32_t state = (seed != 0) ? seed : 987654321; for (int s = 0; s < num_slices; s++) { state = state * 1664525u + 1013904223u; int slice_y = (state % h); state = state * 1664525u + 1013904223u; int slice_h = 5 + (state % 25); state = state * 1664525u + 1013904223u; int disp = ((int)(state % (max_displacement * 2))) - max_displacement; int end_y = std::min(h, slice_y + slice_h); for (int y = slice_y; y < end_y; y++) { for (int x = 0; x < w; x++) { int src_x = std::max(0, std::min(w - 1, x + disp)); pixels[y * stride_px + x] = copy[y * stride_px + src_x]; } } } return 0; } // ============================================================================ // 9. Blurred Drop Shadow Generator // ============================================================================ BL_FX_EXPORT int bl_fx_drop_shadow( const uint32_t* src_pixels, uint32_t* dst_pixels, int w, int h, int stride_bytes, double dx, double dy, double blur_radius, uint32_t shadow_color ) { if (!src_pixels || !dst_pixels || w <= 0 || h <= 0) return -1; int stride_px = stride_bytes / 4; size_t total = h * stride_px; uint8_t sh_a = (shadow_color >> 24) & 0xFF; uint8_t sh_r = (shadow_color >> 16) & 0xFF; uint8_t sh_g = (shadow_color >> 8) & 0xFF; uint8_t sh_b = shadow_color & 0xFF; std::vector shadow_buf(total, 0); int off_x = (int)std::round(dx); int off_y = (int)std::round(dy); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { int src_x = x - off_x; int src_y = y - off_y; if (src_x >= 0 && src_x < w && src_y >= 0 && src_y < h) { uint32_t sp = src_pixels[src_y * stride_px + src_x]; uint8_t src_a = (sp >> 24) & 0xFF; uint8_t out_a = (uint8_t)((src_a * sh_a) / 255); shadow_buf[y * stride_px + x] = _pack_rgba(out_a, sh_r, sh_g, sh_b); } } } if (blur_radius > 0.0) { bl_fx_gaussian_blur(shadow_buf.data(), w, h, stride_bytes, blur_radius); } for (size_t i = 0; i < total; i++) { uint32_t sp = src_pixels[i]; uint32_t shp = shadow_buf[i]; uint8_t sa = (sp >> 24) & 0xFF; uint8_t sr = (sp >> 16) & 0xFF; uint8_t sg = (sp >> 8) & 0xFF; uint8_t sb = sp & 0xFF; uint8_t da = (shp >> 24) & 0xFF; uint8_t dr = (shp >> 16) & 0xFF; uint8_t dg = (shp >> 8) & 0xFF; uint8_t db = shp & 0xFF; double inv_sa = 1.0 - (sa / 255.0); uint8_t out_a = _clamp_u8(sa + da * inv_sa); uint8_t out_r = _clamp_u8(sr + dr * inv_sa); uint8_t out_g = _clamp_u8(sg + dg * inv_sa); uint8_t out_b = _clamp_u8(sb + db * inv_sa); dst_pixels[i] = _pack_rgba(out_a, out_r, out_g, out_b); } return 0; } // ============================================================================ // 10. Integral Images & Fast Constant-Time O(1) Area-Sum Filtering // ============================================================================ BL_FX_EXPORT int bl_fx_integral_image( const uint32_t* src, uint64_t* sum_r, uint64_t* sum_g, uint64_t* sum_b, int w, int h, int stride_px ) { if (!src || !sum_r || !sum_g || !sum_b || w <= 0 || h <= 0) return -1; int int_w = w + 1; std::memset(sum_r, 0, (w + 1) * sizeof(uint64_t)); std::memset(sum_g, 0, (w + 1) * sizeof(uint64_t)); std::memset(sum_b, 0, (w + 1) * sizeof(uint64_t)); for (int y = 0; y < h; y++) { uint64_t row_r = 0, row_g = 0, row_b = 0; int prev_y_offset = y * int_w; int curr_y_offset = (y + 1) * int_w; sum_r[curr_y_offset] = 0; sum_g[curr_y_offset] = 0; sum_b[curr_y_offset] = 0; for (int x = 0; x < w; x++) { uint32_t p = src[y * stride_px + x]; row_r += (p >> 16) & 0xFF; row_g += (p >> 8) & 0xFF; row_b += p & 0xFF; sum_r[curr_y_offset + x + 1] = sum_r[prev_y_offset + x + 1] + row_r; sum_g[curr_y_offset + x + 1] = sum_g[prev_y_offset + x + 1] + row_g; sum_b[curr_y_offset + x + 1] = sum_b[prev_y_offset + x + 1] + row_b; } } return 0; } BL_FX_EXPORT int bl_fx_box_blur_integral( uint32_t* pixels, int w, int h, int stride_bytes, int radius ) { if (!pixels || w <= 0 || h <= 0 || radius <= 0) return 0; int stride_px = stride_bytes / 4; int int_w = w + 1; size_t int_size = (w + 1) * (h + 1); std::vector sum_r(int_size), sum_g(int_size), sum_b(int_size); bl_fx_integral_image(pixels, sum_r.data(), sum_g.data(), sum_b.data(), w, h, stride_px); for (int y = 0; y < h; y++) { int y0 = std::max(0, y - radius); int y1 = std::min(h, y + radius + 1); for (int x = 0; x < w; x++) { int x0 = std::max(0, x - radius); int x1 = std::min(w, x + radius + 1); int area = (x1 - x0) * (y1 - y0); if (area <= 0) continue; uint64_t r_sum = sum_r[y1 * int_w + x1] - sum_r[y0 * int_w + x1] - sum_r[y1 * int_w + x0] + sum_r[y0 * int_w + x0]; uint64_t g_sum = sum_g[y1 * int_w + x1] - sum_g[y0 * int_w + x1] - sum_g[y1 * int_w + x0] + sum_g[y0 * int_w + x0]; uint64_t b_sum = sum_b[y1 * int_w + x1] - sum_b[y0 * int_w + x1] - sum_b[y1 * int_w + x0] + sum_b[y0 * int_w + x0]; uint32_t orig = pixels[y * stride_px + x]; uint8_t a = (orig >> 24) & 0xFF; pixels[y * stride_px + x] = _pack_rgba(a, (uint8_t)(r_sum / area), (uint8_t)(g_sum / area), (uint8_t)(b_sum / area)); } } return 0; } } // extern "C"