NaraMotion / c_src /blend2d_fx.cpp
ArifiIslam's picture
Release NaraMotion v0.26.0: Structure of Arrays (SoA), Path Arena Allocator, Uniform Grid Indexing, Dual-Buffer Rendering & Centroid Spring Pop-in
b516e00 verified
Raw History Blame Contribute Delete
29.1 kB
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#include <thread>
#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<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"