Release NaraMotion v0.26.0: Structure of Arrays (SoA), Path Arena Allocator, Uniform Grid Indexing, Dual-Buffer Rendering & Centroid Spring Pop-in
b516e00 verified Download c_src/naramotion_fx.cpp from ArifiIslam/NaraMotion: direct link, hf CLI and curl.
- Browser
- Download file 29.1 kB
-
https://huggingface.co/ArifiIslam/NaraMotion/resolve/main/c_src/naramotion_fx.cpp
- Command line
-
hf download hf://ArifiIslam/NaraMotion/c_src/naramotion_fx.cpp
-
curl -L -o naramotion_fx.cpp https://huggingface.co/ArifiIslam/NaraMotion/resolve/main/c_src/naramotion_fx.cpp
29.1 kB
| // 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<std::thread> 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<std::thread> 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<uint32_t> 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<uint32_t> 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<std::thread> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint64_t> 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" | |