#![no_std]
#![forbid(unsafe_code)]
extern crate alloc;
pub mod adj;
pub mod artistic;
pub mod enhance;
pub mod blur;
pub mod color;
pub mod conv;
pub mod distort;
pub mod hist;
pub mod lochist;
pub mod lut;
pub mod noise;
pub mod object;
pub mod perlin;
pub mod photo;
pub mod render;
pub mod stylize;
pub mod warp;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::any::Any;
pub use gfx::tmask::TMask;
pub use gfx::Surface;
pub use plat::JobCtl;
use rt::sync::SpinLock;
pub const FX_NSLOTS: usize = 32;
pub const FXF_ADJ: u32 = 1; pub const FXF_PREPARE: u32 = 2; pub const FXF_SEED: u32 = 4; pub const FXF_BANDS: u32 = 8; pub const FXF_WHOLE: u32 = 16; pub const FXF_NO_DIALOG: u32 = 32; pub const FXF_SLOW: u32 = 64;
pub const FXC_ADJUST: u32 = 0;
pub const FXC_ARTISTIC: u32 = 1;
pub const FXC_BLUR: u32 = 2;
pub const FXC_COLOR: u32 = 3;
pub const FXC_DISTORT: u32 = 4;
pub const FXC_NOISE: u32 = 5;
pub const FXC_OBJECT: u32 = 6;
pub const FXC_PHOTO: u32 = 7;
pub const FXC_RENDER: u32 = 8;
pub const FXC_STYLIZE: u32 = 9;
pub const FP_INT: u8 = 1; pub const FP_DEC: u8 = 2; pub const FP_BOOL: u8 = 3; pub const FP_CHOICE: u8 = 4; pub const FP_ANGLE: u8 = 5; pub const FP_PAN: u8 = 6; pub const FP_COLOR: u8 = 7; pub const FP_SEED: u8 = 8; pub const FP_BLOB: u8 = 9; pub const FPF_LOG: u8 = 1;
pub const FX_AUTO_LEVEL: u32 = 1; pub const FX_BLACK_WHITE: u32 = 2;
pub const FX_BRIGHT_CONTRAST: u32 = 3;
pub const FX_CURVES: u32 = 4;
pub const FX_EXPOSURE: u32 = 5;
pub const FX_HIGHLIGHTS_SHADOWS: u32 = 6;
pub const FX_HUE_SATURATION: u32 = 7;
pub const FX_INVERT_ALPHA: u32 = 8;
pub const FX_INVERT_COLORS: u32 = 9;
pub const FX_LEVELS: u32 = 10;
pub const FX_POSTERIZE: u32 = 11;
pub const FX_SEPIA: u32 = 12;
pub const FX_TEMP_TINT: u32 = 13;
pub const FX_INK_SKETCH: u32 = 20; pub const FX_OIL_PAINTING: u32 = 21;
pub const FX_PENCIL_SKETCH: u32 = 22;
pub const FX_BOKEH_BLUR: u32 = 30; pub const FX_FRAGMENT_BLUR: u32 = 31;
pub const FX_GAUSSIAN_BLUR: u32 = 32;
pub const FX_MEDIAN: u32 = 33;
pub const FX_MOTION_BLUR: u32 = 34;
pub const FX_RADIAL_BLUR: u32 = 35;
pub const FX_SKETCH_BLUR: u32 = 36;
pub const FX_SQUARE_BLUR: u32 = 37;
pub const FX_SURFACE_BLUR: u32 = 38;
pub const FX_UNFOCUS: u32 = 39;
pub const FX_ZOOM_BLUR: u32 = 40;
pub const FX_QUANTIZE: u32 = 50; pub const FX_BULGE: u32 = 60; pub const FX_CRYSTALIZE: u32 = 61;
pub const FX_DENTS: u32 = 62;
pub const FX_FROSTED_GLASS: u32 = 63;
pub const FX_MORPHOLOGY: u32 = 64;
pub const FX_PIXELATE: u32 = 65;
pub const FX_POLAR_INVERSION: u32 = 66;
pub const FX_TILE_REFLECTION: u32 = 67;
pub const FX_TWIST: u32 = 68;
pub const FX_ADD_NOISE: u32 = 70; pub const FX_REDUCE_NOISE: u32 = 71;
pub const FX_DROP_SHADOW: u32 = 80; pub const FX_GLOW: u32 = 90; pub const FX_RED_EYE: u32 = 91;
pub const FX_SHARPEN: u32 = 92;
pub const FX_SOFTEN_PORTRAIT: u32 = 93;
pub const FX_STRAIGHTEN: u32 = 94;
pub const FX_VIGNETTE: u32 = 95;
pub const FX_CLOUDS: u32 = 100; pub const FX_JULIA: u32 = 101;
pub const FX_MANDELBROT: u32 = 102;
pub const FX_TURBULENCE: u32 = 103;
pub const FX_EDGE_DETECT: u32 = 110; pub const FX_EMBOSS: u32 = 111;
pub const FX_OUTLINE: u32 = 112;
pub const FX_RELIEF: u32 = 113;
pub const ERR_NOMEM: i32 = -8;
pub const ERR_DIMS: i32 = -12;
pub const ERR_CANCEL: i32 = -14;
pub const ERR_LIMIT: i32 = -15;
const FXG_MAX: usize = 16;
pub type Aux = Box<dyn Any + Send + Sync>;
pub type PrepareFn = fn(&FxArgs) -> Result<Option<Aux>, i32>;
pub type RenderFn = fn(&FxArgs, &mut Dst) -> i32;
pub struct FxDesc {
pub id: u32,
pub flags: u32,
pub name: &'static str,
pub prepare: Option<PrepareFn>,
pub render: RenderFn,
pub props: &'static [FxProp],
pub category: u32,
pub user: usize,
}
pub struct FxProp {
pub typ: u8,
pub dec: u8,
pub slot: u8,
pub flags: u8,
pub name: &'static str,
pub min: f64,
pub max: f64,
pub def: f64,
pub def_y: f64,
pub choices: &'static [&'static str],
}
impl FxProp {
pub const fn new(typ: u8, dec: u8, slot: u8, flags: u8, name: &'static str, min: f64, max: f64, def: f64) -> FxProp {
FxProp { typ, dec, slot, flags, name, min, max, def, def_y: 0.0, choices: &[] }
}
pub const fn choice(slot: u8, name: &'static str, max: f64, def: f64, choices: &'static [&'static str]) -> FxProp {
FxProp { typ: FP_CHOICE, dec: 0, slot, flags: 0, name, min: 0.0, max, def, def_y: 0.0, choices }
}
pub const fn color(slot: u8, name: &'static str, argb: u32) -> FxProp {
FxProp { typ: FP_COLOR, dec: 0, slot, flags: 0, name, min: 0.0, max: 0.0, def: f64::from_bits(argb as u64), def_y: 0.0, choices: &[] }
}
pub const fn pan(slot: u8, name: &'static str, dec: u8, min: f64, max: f64, x: f64, y: f64) -> FxProp {
FxProp { typ: FP_PAN, dec, slot, flags: 0, name, min, max, def: x, def_y: y, choices: &[] }
}
pub fn def_color(&self) -> u32 {
self.def.to_bits() as u32
}
}
#[derive(Clone, Copy)]
pub struct Params(pub [u64; FX_NSLOTS]);
impl Default for Params {
fn default() -> Self {
Params([0; FX_NSLOTS])
}
}
impl Params {
#[inline]
pub fn i(&self, slot: usize) -> i64 {
self.0[slot] as i64
}
#[inline]
pub fn f(&self, slot: usize) -> f64 {
f64::from_bits(self.0[slot])
}
#[inline]
pub fn int(&self, slot: usize, lo: i32, hi: i32) -> i32 {
self.i(slot).max(lo as i64).min(hi as i64) as i32
}
#[inline]
pub fn dbl(&self, slot: usize, lo: f64, hi: f64) -> f64 {
let v = self.f(slot);
let v = if v.is_nan() { lo } else { v };
rt::fx::minsd(rt::fx::maxsd(v, lo), hi)
}
#[inline]
pub fn byte(&self, slot: usize, i: usize) -> u8 {
let k = slot * 8 + i;
(self.0[k / 8] >> ((k % 8) * 8)) as u8
}
pub fn set_i(&mut self, slot: usize, v: i64) {
self.0[slot] = v as u64;
}
pub fn set_f(&mut self, slot: usize, v: f64) {
self.0[slot] = v.to_bits();
}
pub fn set_byte(&mut self, slot: usize, i: usize, v: u8) {
let k = slot * 8 + i;
let sh = (k % 8) * 8;
self.0[k / 8] = self.0[k / 8] & !(0xFFu64 << sh) | (v as u64) << sh;
}
}
pub trait Cancel {
fn cancelled(&self) -> bool;
}
impl Cancel for JobCtl {
#[inline]
fn cancelled(&self) -> bool {
JobCtl::cancelled(self)
}
}
impl Cancel for core::sync::atomic::AtomicBool {
#[inline]
fn cancelled(&self) -> bool {
self.load(core::sync::atomic::Ordering::Relaxed)
}
}
impl Cancel for core::sync::atomic::AtomicU32 {
#[inline]
fn cancelled(&self) -> bool {
self.load(core::sync::atomic::Ordering::Relaxed) != 0
}
}
#[derive(Clone, Copy)]
pub struct FxArgs<'a> {
pub src: &'a Surface,
pub x0: i32,
pub y0: i32,
pub x1: i32,
pub y1: i32,
pub params: &'a Params,
pub aux: Option<&'a (dyn Any + Send + Sync)>,
pub job: Option<&'a (dyn Cancel + Sync)>,
pub sel: Option<&'a TMask>,
}
impl<'a> FxArgs<'a> {
pub fn new(src: &'a Surface, roi: [i32; 4], params: &'a Params) -> FxArgs<'a> {
FxArgs { src, x0: roi[0], y0: roi[1], x1: roi[2], y1: roi[3], params, aux: None, job: None, sel: None }
}
#[inline]
pub fn cancelled(&self) -> bool {
match self.job {
Some(j) => j.cancelled(),
None => false,
}
}
#[inline]
pub fn aux<T: 'static>(&self) -> Option<&'a T> {
self.aux?.downcast_ref::<T>()
}
#[inline]
pub fn src_row(&self, y: i32) -> &'a [u8] {
row(self.src, y)
}
}
#[inline]
pub fn row(sf: &Surface, y: i32) -> &[u8] {
let o = y as usize * sf.stride as usize;
&sf.px[o..o + sf.w as usize * 4]
}
#[inline(always)]
pub fn px(b: &[u8], i: usize) -> u32 {
let q: [u8; 4] = b[4 * i..4 * i + 4].try_into().unwrap();
u32::from_le_bytes(q)
}
#[inline(always)]
pub fn put(b: &mut [u8], i: usize, v: u32) {
b[4 * i..4 * i + 4].copy_from_slice(&v.to_le_bytes());
}
pub struct Dst<'a> {
rows: Rows<'a>,
pub x0: i32,
pub y0: i32,
pub x1: i32,
pub y1: i32,
}
enum Rows<'a> {
List(Vec<&'a mut [u8]>),
Table(&'a mut [&'a mut [u8]]),
Strided(&'a mut [u8], usize),
}
impl<'a> Dst<'a> {
pub fn new(roi: [i32; 4], rows: Vec<&'a mut [u8]>) -> Dst<'a> {
Dst { rows: Rows::List(rows), x0: roi[0], y0: roi[1], x1: roi[2], y1: roi[3] }
}
pub fn strided(roi: [i32; 4], buf: &'a mut [u8], stride: usize) -> Dst<'a> {
Dst { rows: Rows::Strided(buf, stride), x0: roi[0], y0: roi[1], x1: roi[2], y1: roi[3] }
}
pub fn of_surface(sf: &'a mut Surface, roi: [i32; 4]) -> Dst<'a> {
let (w, h) = (sf.w, sf.h);
let x0 = roi[0].clamp(0, w);
let y0 = roi[1].clamp(0, h);
let (x1, y1) = (roi[2].clamp(x0, w), roi[3].clamp(y0, h));
let stride = sf.stride as usize;
if x1 <= x0 || y1 <= y0 {
return Dst { rows: Rows::List(Vec::new()), x0, y0, x1, y1 };
}
let start = y0 as usize * stride + x0 as usize * 4;
let end = (y1 - 1) as usize * stride + x1 as usize * 4;
Dst { rows: Rows::Strided(&mut sf.px[start..end], stride), x0, y0, x1, y1 }
}
#[inline]
pub fn row(&mut self, y: i32) -> &mut [u8] {
let k = (y - self.y0) as usize;
match &mut self.rows {
Rows::List(v) => v[k],
Rows::Table(t) => t[k],
Rows::Strided(b, st) => {
let n = (self.x1 - self.x0) as usize * 4;
&mut b[k * *st..k * *st + n]
}
}
}
}
pub fn tiles<'a>(sf: &'a mut Surface, rects: &[[i32; 4]], store: &'a mut Vec<&'a mut [u8]>) -> Vec<Dst<'a>> {
let (w, h) = (sf.w, sf.h);
let stride = sf.stride as usize;
let cl: Vec<[i32; 4]> = rects
.iter()
.map(|r| {
let x0 = r[0].clamp(0, w);
let y0 = r[1].clamp(0, h);
[x0, y0, r[2].clamp(x0, w), r[3].clamp(y0, h)]
})
.collect();
let nrows = |r: &[i32; 4]| if r[2] > r[0] && r[3] > r[1] { (r[3] - r[1]) as usize } else { 0 };
let mut base = Vec::with_capacity(cl.len() + 1);
let mut total = 0usize;
for r in &cl {
base.push(total);
total += nrows(r);
}
base.push(total);
store.clear();
store.resize_with(total, Default::default);
let mut order: Vec<usize> = (0..cl.len()).filter(|&i| nrows(&cl[i]) > 0).collect();
if !order.is_empty() && stride != 0 {
order.sort_by_key(|&i| (cl[i][1], cl[i][0]));
let ymin = cl[order[0]][1];
let ymax = order.iter().map(|&i| cl[i][3]).max().unwrap_or(0);
let mut active: Vec<usize> = Vec::new();
let mut next = 0usize;
let lines = sf.px.chunks_mut(stride).take(ymax as usize).skip(ymin as usize);
for (k, line) in lines.enumerate() {
let y = ymin + k as i32;
active.retain(|&i| y < cl[i][3]);
while next < order.len() && cl[order[next]][1] == y {
let i = order[next];
let at = active.partition_point(|&j| cl[j][0] < cl[i][0]);
active.insert(at, i);
next += 1;
}
let (mut rest, _) = line.split_at_mut(w as usize * 4);
let mut at = 0i32; for &i in &active {
let (x0, x1) = (cl[i][0], cl[i][2]);
assert!(x0 >= at, "fx::tiles: overlapping rectangles");
let tmp = core::mem::take(&mut rest);
let (_, tail) = tmp.split_at_mut((x0 - at) as usize * 4);
let (mine, tail) = tail.split_at_mut((x1 - x0) as usize * 4);
store[base[i] + (y - cl[i][1]) as usize] = mine;
rest = tail;
at = x1;
}
}
}
let mut rest: &'a mut [&'a mut [u8]] = &mut store[..];
let mut out: Vec<Dst<'a>> = Vec::with_capacity(cl.len());
for (i, r) in cl.iter().enumerate() {
let (mine, tail) = core::mem::take(&mut rest).split_at_mut(base[i + 1] - base[i]);
rest = tail;
out.push(Dst { rows: Rows::Table(mine), x0: r[0], y0: r[1], x1: r[2], y1: r[3] });
}
out
}
#[inline]
pub fn rows(a: &FxArgs, dst: &mut Dst, mut f: impl FnMut(&mut [u8], &[u8], i32, i32)) -> i32 {
if a.x1 - a.x0 <= 0 {
return 0;
}
let (b0, b1) = (a.x0 as usize * 4, a.x1 as usize * 4);
for y in a.y0..a.y1 {
if a.cancelled() {
return ERR_CANCEL;
}
let s = &a.src_row(y)[b0..b1];
f(dst.row(y), s, a.x0, y);
}
0
}
static REG: SpinLock<Vec<&'static [FxDesc]>> = SpinLock::new(Vec::new());
pub fn register(group: &'static [FxDesc]) -> i32 {
let mut r = REG.lock();
if r.iter().any(|g| core::ptr::eq(*g, group)) {
return 0;
}
if r.len() >= FXG_MAX {
return ERR_LIMIT;
}
r.push(group);
0
}
pub fn use_all() {
for g in [
&adj::FXTAB as &'static [FxDesc],
&artistic::FXTAB,
&blur::FXTAB,
&color::FXTAB,
&distort::FXTAB,
&noise::FXTAB,
&object::FXTAB,
&photo::FXTAB,
&render::FXTAB,
&stylize::FXTAB,
&enhance::FXTAB,
] {
register(g);
}
}
pub fn count() -> usize {
REG.lock().iter().map(|g| g.len()).sum()
}
pub fn get(i: usize) -> Option<&'static FxDesc> {
let r = REG.lock();
let mut i = i;
for g in r.iter() {
if i < g.len() {
return Some(&g[i]);
}
i -= g.len();
}
None
}
pub fn find(name: &str) -> Option<&'static FxDesc> {
(0..).map_while(get).find(|d| d.name == name)
}
pub fn find_id(id: u32) -> Option<&'static FxDesc> {
(0..).map_while(get).find(|d| d.id == id)
}
pub fn defaults(desc: &FxDesc, p: &mut Params) {
p.0 = [0; FX_NSLOTS];
for pr in desc.props {
let s = pr.slot as usize;
if s >= FX_NSLOTS {
continue;
}
match pr.typ {
FP_DEC | FP_ANGLE => p.0[s] = pr.def.to_bits(),
FP_PAN => {
p.0[s] = pr.def.to_bits();
if s + 1 < FX_NSLOTS {
p.0[s + 1] = pr.def_y.to_bits();
}
}
FP_COLOR => p.0[s] = pr.def.to_bits() & 0xFFFF_FFFF,
_ => p.0[s] = rt::fx::cvttd64(pr.def) as u64,
}
}
}
#[inline]
pub fn hash32(x: u32, y: u32, seed: u32, stream: u32) -> u32 {
let mut h = x.wrapping_mul(0x8DA6B343)
^ y.wrapping_mul(0xD8163841)
^ seed.wrapping_mul(0xCB1AB31F)
^ stream.wrapping_mul(0x165667B1);
h ^= h >> 16;
h = h.wrapping_mul(0x85EBCA6B);
h ^= h >> 13;
h = h.wrapping_mul(0xC2B2AE35);
h ^ (h >> 16)
}