Skip to main content

fx/
lib.rs

1// fx -- the effect and adjustment kernels: the FXDESC registry, prepare / render on ROIs from
2// worker threads, deterministic per pixel                       [src/fx/*.s, inc/fx.inc]
3//
4// The contract (fx.inc).  An application (PaintCraft, efftest) runs an effect by
5//   1. copying the source layer (FMT_ARGB, straight alpha) -> FxArgs::src,
6//   2. calling desc.prepare once when it has one (global statistics, LUTs, noise tables):
7//      Ok(Some(aux)) / Ok(None) / Err(ERR_*) -- ERR_CANCEL when the job was cancelled,
8//   3. calling desc.render for many rectangles (ROIs) -- from several worker threads at once
9//      -- until the ROIs cover the area to change; every call gets the aux of step 2,
10//   4. blending the destination back through the selection mask itself
11//      (gfx::straight::lerp_mask_row).
12// Rules: render writes the destination only inside its ROI (Dst holds exactly those pixels)
13// and may read the whole source; the result of a pixel depends only on (source, params, aux)
14// -- never on the ROI split or the thread count (bit-identical); render polls the job's
15// cancel flag once per row (FxArgs::cancelled) and returns ERR_CANCEL at once.  Pixels are
16// straight ARGB (0xAARRGGBB = bytes B, G, R, A).  Random effects use hash32(x, y, seed,
17// stream), never a sequential generator.  FXF_BANDS: render wants full-width row bands (the
18// runner splits by rows only); FXF_WHOLE: one call over the whole image.
19//
20// API (the asm records as Rust types):
21//   FxDesc   FXDESC: id (FX_*), flags (FXF_*), name (ASCII key), prepare / render (fn
22//            pointers; FXD_FREE is the Drop of the aux), props (FXPROP records), category
23//            (FXC_*), user (free for the registering code, e.g. a plugin index)
24//   FxProp   FXPROP: typ (FP_*), dec, slot, flags (FPF_*), name, min, max, def (FP_COLOR:
25//            the f64 bits are the ARGB colour -- color()), def_y (FP_PAN: the y default),
26//            choices (FP_CHOICE keys)
27//   Params   the parameter block: FX_NSLOTS u64 slots (i64 for FP_INT / BOOL / CHOICE / SEED,
28//            f64 bits for FP_DEC / ANGLE / PAN, ARGB in the low half for FP_COLOR, raw bytes
29//            for FP_BLOB); defaults(desc, &mut params) fills it
30//   FxArgs   FXARGS without the destination: src, ROI x0 y0 x1 y1, params, aux, job (a
31//            Cancel flag: the worker pool's JobCtl, an AtomicBool / AtomicU32), sel (the
32//            selection TMask: statistics may be limited to it)
33//   Dst      the destination pixels of one render call: one &mut [u8] per ROI row with the
34//            pixels x0..x1 -- of_surface (one call owns the whole rectangle), tiles (disjoint
35//            rectangles of one surface for parallel calls; the row slices of all of them live
36//            in one caller-owned Vec), strided (rows of a tile buffer), or new (a row list)
37//   Aux      Box<dyn Any + Send + Sync>: the result of prepare, read by render (aux::<T>())
38//   registry use_all() registers the built-in groups (menu order), register(group) adds a
39//            group (&'static [FxDesc], e.g. plugin effects), count(), get(i), find(name),
40//            find_id(id), defaults(desc, params)
41//   hash32(x, y, seed, stream)   deterministic per-pixel random bits (fx_common.s)
42//   rows(a, dst, f)              fx_rows: f(dst_row, src_row, x0, y) for every ROI row
43//
44// Modules: lut (fx_lut.s), hist (fx_hist.s), adj (fx_adj.s), color (fx_color.s), stylize
45// (fx_stylize.s), conv (fx_conv.s), lochist (fx_lochist.s), blur (fx_blur.s), artistic
46// (fx_artistic.s), noise (fx_noise.s), photo (fx_photo.s), object (fx_object.s), warp
47// (fx_warp.s), perlin (fx_perlin.s), distort (fx_distort.s), render (fx_render.s).
48#![no_std]
49#![forbid(unsafe_code)]
50
51extern crate alloc;
52
53pub mod adj;
54pub mod artistic;
55pub mod enhance;
56pub mod blur;
57pub mod color;
58pub mod conv;
59pub mod distort;
60pub mod hist;
61pub mod lochist;
62pub mod lut;
63pub mod noise;
64pub mod object;
65pub mod perlin;
66pub mod photo;
67pub mod render;
68pub mod stylize;
69pub mod warp;
70
71use alloc::boxed::Box;
72use alloc::vec::Vec;
73use core::any::Any;
74
75pub use gfx::tmask::TMask;
76pub use gfx::Surface;
77pub use plat::JobCtl;
78use rt::sync::SpinLock;
79
80// ---------------------------------------------------------------- constants (fx.inc)
81
82pub const FX_NSLOTS: usize = 32; // parameter slots per effect
83
84pub const FXF_ADJ: u32 = 1; // adjustment (Korrekturen) rather than effect
85pub const FXF_PREPARE: u32 = 2; // has a prepare step (statistics over the source / selection)
86pub const FXF_SEED: u32 = 4; // uses a random seed (has an FP_SEED property)
87pub const FXF_BANDS: u32 = 8; // render wants full-width row bands
88pub const FXF_WHOLE: u32 = 16; // render must get the whole image in one call
89pub const FXF_NO_DIALOG: u32 = 32; // applied immediately (e.g. Invert Colors, Sepia)
90pub const FXF_SLOW: u32 = 64; // hint: show progress
91
92pub const FXC_ADJUST: u32 = 0;
93pub const FXC_ARTISTIC: u32 = 1;
94pub const FXC_BLUR: u32 = 2;
95pub const FXC_COLOR: u32 = 3;
96pub const FXC_DISTORT: u32 = 4;
97pub const FXC_NOISE: u32 = 5;
98pub const FXC_OBJECT: u32 = 6;
99pub const FXC_PHOTO: u32 = 7;
100pub const FXC_RENDER: u32 = 8;
101pub const FXC_STYLIZE: u32 = 9;
102
103pub const FP_INT: u8 = 1; // slot: i64
104pub const FP_DEC: u8 = 2; // slot: f64
105pub const FP_BOOL: u8 = 3; // slot: i64 0/1
106pub const FP_CHOICE: u8 = 4; // slot: i64 index
107pub const FP_ANGLE: u8 = 5; // slot: f64 degrees
108pub const FP_PAN: u8 = 6; // slots: f64 x, f64 y (normalised -1..1, 0 = centre)
109pub const FP_COLOR: u8 = 7; // slot: u32 straight ARGB in the low half
110pub const FP_SEED: u8 = 8; // slot: i64 seed
111pub const FP_BLOB: u8 = 9; // slots slot .. slot + max - 1 hold raw bytes (default all 0)
112pub const FPF_LOG: u8 = 1; // slider maps logarithmically
113
114pub const FX_AUTO_LEVEL: u32 = 1; // Korrekturen
115pub const FX_BLACK_WHITE: u32 = 2;
116pub const FX_BRIGHT_CONTRAST: u32 = 3;
117pub const FX_CURVES: u32 = 4;
118pub const FX_EXPOSURE: u32 = 5;
119pub const FX_HIGHLIGHTS_SHADOWS: u32 = 6;
120pub const FX_HUE_SATURATION: u32 = 7;
121pub const FX_INVERT_ALPHA: u32 = 8;
122pub const FX_INVERT_COLORS: u32 = 9;
123pub const FX_LEVELS: u32 = 10;
124pub const FX_POSTERIZE: u32 = 11;
125pub const FX_SEPIA: u32 = 12;
126pub const FX_TEMP_TINT: u32 = 13;
127pub const FX_INK_SKETCH: u32 = 20; // Künstlerisch
128pub const FX_OIL_PAINTING: u32 = 21;
129pub const FX_PENCIL_SKETCH: u32 = 22;
130pub const FX_BOKEH_BLUR: u32 = 30; // Weichzeichnen
131pub const FX_FRAGMENT_BLUR: u32 = 31;
132pub const FX_GAUSSIAN_BLUR: u32 = 32;
133pub const FX_MEDIAN: u32 = 33;
134pub const FX_MOTION_BLUR: u32 = 34;
135pub const FX_RADIAL_BLUR: u32 = 35;
136pub const FX_SKETCH_BLUR: u32 = 36;
137pub const FX_SQUARE_BLUR: u32 = 37;
138pub const FX_SURFACE_BLUR: u32 = 38;
139pub const FX_UNFOCUS: u32 = 39;
140pub const FX_ZOOM_BLUR: u32 = 40;
141pub const FX_QUANTIZE: u32 = 50; // Farbe
142pub const FX_BULGE: u32 = 60; // Verzerren
143pub const FX_CRYSTALIZE: u32 = 61;
144pub const FX_DENTS: u32 = 62;
145pub const FX_FROSTED_GLASS: u32 = 63;
146pub const FX_MORPHOLOGY: u32 = 64;
147pub const FX_PIXELATE: u32 = 65;
148pub const FX_POLAR_INVERSION: u32 = 66;
149pub const FX_TILE_REFLECTION: u32 = 67;
150pub const FX_TWIST: u32 = 68;
151pub const FX_ADD_NOISE: u32 = 70; // Rauschen
152pub const FX_REDUCE_NOISE: u32 = 71;
153pub const FX_DROP_SHADOW: u32 = 80; // Objekt
154pub const FX_GLOW: u32 = 90; // Foto
155pub const FX_RED_EYE: u32 = 91;
156pub const FX_SHARPEN: u32 = 92;
157pub const FX_SOFTEN_PORTRAIT: u32 = 93;
158pub const FX_STRAIGHTEN: u32 = 94;
159pub const FX_VIGNETTE: u32 = 95;
160pub const FX_CLOUDS: u32 = 100; // Rendern
161pub const FX_JULIA: u32 = 101;
162pub const FX_MANDELBROT: u32 = 102;
163pub const FX_TURBULENCE: u32 = 103;
164pub const FX_EDGE_DETECT: u32 = 110; // Stilisieren
165pub const FX_EMBOSS: u32 = 111;
166pub const FX_OUTLINE: u32 = 112;
167pub const FX_RELIEF: u32 = 113;
168
169pub const ERR_NOMEM: i32 = -8;
170pub const ERR_DIMS: i32 = -12;
171pub const ERR_CANCEL: i32 = -14;
172pub const ERR_LIMIT: i32 = -15;
173
174const FXG_MAX: usize = 16; // registered group tables
175
176// ---------------------------------------------------------------- records
177
178/// the result of prepare (read-only in render; FXD_FREE = its Drop)
179pub type Aux = Box<dyn Any + Send + Sync>;
180/// FXD_PREPARE: Ok(Some(aux)) / Ok(None) / Err(ERR_*)
181pub type PrepareFn = fn(&FxArgs) -> Result<Option<Aux>, i32>;
182/// FXD_RENDER: 0 / ERR_CANCEL / ERR_*
183pub type RenderFn = fn(&FxArgs, &mut Dst) -> i32;
184
185/// an effect (FXDESC)
186pub struct FxDesc {
187    pub id: u32,
188    pub flags: u32,
189    pub name: &'static str,
190    pub prepare: Option<PrepareFn>,
191    pub render: RenderFn,
192    pub props: &'static [FxProp],
193    pub category: u32,
194    /// free for the code that registers the group (0 for the built-in effects)
195    pub user: usize,
196}
197
198/// a property (FXPROP); slot i of the parameter block belongs to property i, except FP_PAN
199/// (2 slots) and FP_BLOB (max slots)
200pub struct FxProp {
201    pub typ: u8,
202    pub dec: u8,
203    pub slot: u8,
204    pub flags: u8,
205    pub name: &'static str,
206    pub min: f64,
207    pub max: f64,
208    /// the default (FP_COLOR: the bits of this f64 are the ARGB colour)
209    pub def: f64,
210    /// FP_PAN: the y default (FXP_CHOICES of the asm record)
211    pub def_y: f64,
212    pub choices: &'static [&'static str],
213}
214
215impl FxProp {
216    /// an FP_INT / DEC / BOOL / ANGLE / SEED / BLOB property (FXPROP)
217    pub const fn new(typ: u8, dec: u8, slot: u8, flags: u8, name: &'static str, min: f64, max: f64, def: f64) -> FxProp {
218        FxProp { typ, dec, slot, flags, name, min, max, def, def_y: 0.0, choices: &[] }
219    }
220    /// an FP_CHOICE property
221    pub const fn choice(slot: u8, name: &'static str, max: f64, def: f64, choices: &'static [&'static str]) -> FxProp {
222        FxProp { typ: FP_CHOICE, dec: 0, slot, flags: 0, name, min: 0.0, max, def, def_y: 0.0, choices }
223    }
224    /// an FP_COLOR property (FXPROPC: the default's bits are the colour)
225    pub const fn color(slot: u8, name: &'static str, argb: u32) -> FxProp {
226        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: &[] }
227    }
228    /// an FP_PAN property (default x, y)
229    pub const fn pan(slot: u8, name: &'static str, dec: u8, min: f64, max: f64, x: f64, y: f64) -> FxProp {
230        FxProp { typ: FP_PAN, dec, slot, flags: 0, name, min, max, def: x, def_y: y, choices: &[] }
231    }
232    /// the default colour of an FP_COLOR property
233    pub fn def_color(&self) -> u32 {
234        self.def.to_bits() as u32
235    }
236}
237
238/// the parameter block (FX_NSLOTS 8-byte slots)
239#[derive(Clone, Copy)]
240pub struct Params(pub [u64; FX_NSLOTS]);
241
242impl Default for Params {
243    fn default() -> Self {
244        Params([0; FX_NSLOTS])
245    }
246}
247
248impl Params {
249    /// slot as i64
250    #[inline]
251    pub fn i(&self, slot: usize) -> i64 {
252        self.0[slot] as i64
253    }
254    /// slot as f64
255    #[inline]
256    pub fn f(&self, slot: usize) -> f64 {
257        f64::from_bits(self.0[slot])
258    }
259    /// the i64 slot clamped to lo..=hi  [prm_int]
260    #[inline]
261    pub fn int(&self, slot: usize, lo: i32, hi: i32) -> i32 {
262        self.i(slot).max(lo as i64).min(hi as i64) as i32
263    }
264    /// the f64 slot clamped to lo..=hi, NaN -> lo  [prm_f64]
265    #[inline]
266    pub fn dbl(&self, slot: usize, lo: f64, hi: f64) -> f64 {
267        let v = self.f(slot);
268        let v = if v.is_nan() { lo } else { v };
269        rt::fx::minsd(rt::fx::maxsd(v, lo), hi)
270    }
271    /// byte i of the block counted from slot `slot` (FP_BLOB)
272    #[inline]
273    pub fn byte(&self, slot: usize, i: usize) -> u8 {
274        let k = slot * 8 + i;
275        (self.0[k / 8] >> ((k % 8) * 8)) as u8
276    }
277    pub fn set_i(&mut self, slot: usize, v: i64) {
278        self.0[slot] = v as u64;
279    }
280    pub fn set_f(&mut self, slot: usize, v: f64) {
281        self.0[slot] = v.to_bits();
282    }
283    pub fn set_byte(&mut self, slot: usize, i: usize, v: u8) {
284        let k = slot * 8 + i;
285        let sh = (k % 8) * 8;
286        self.0[k / 8] = self.0[k / 8] & !(0xFFu64 << sh) | (v as u64) << sh;
287    }
288}
289
290/// a cancel flag render / prepare poll (JOB_CANCEL of FXA_JOB): the pool's JobCtl, or any
291/// flag of the caller's
292pub trait Cancel {
293    fn cancelled(&self) -> bool;
294}
295
296impl Cancel for JobCtl {
297    #[inline]
298    fn cancelled(&self) -> bool {
299        JobCtl::cancelled(self)
300    }
301}
302
303impl Cancel for core::sync::atomic::AtomicBool {
304    #[inline]
305    fn cancelled(&self) -> bool {
306        self.load(core::sync::atomic::Ordering::Relaxed)
307    }
308}
309
310impl Cancel for core::sync::atomic::AtomicU32 {
311    #[inline]
312    fn cancelled(&self) -> bool {
313        self.load(core::sync::atomic::Ordering::Relaxed) != 0
314    }
315}
316
317/// the arguments of prepare / render (FXARGS without the destination)
318#[derive(Clone, Copy)]
319pub struct FxArgs<'a> {
320    pub src: &'a Surface,
321    pub x0: i32,
322    pub y0: i32,
323    pub x1: i32,
324    pub y1: i32,
325    pub params: &'a Params,
326    pub aux: Option<&'a (dyn Any + Send + Sync)>,
327    pub job: Option<&'a (dyn Cancel + Sync)>,
328    pub sel: Option<&'a TMask>,
329}
330
331impl<'a> FxArgs<'a> {
332    /// arguments for the ROI (x0, y0) - (x1, y1) without aux, job and selection
333    pub fn new(src: &'a Surface, roi: [i32; 4], params: &'a Params) -> FxArgs<'a> {
334        FxArgs { src, x0: roi[0], y0: roi[1], x1: roi[2], y1: roi[3], params, aux: None, job: None, sel: None }
335    }
336    /// the job's cancel flag is set (JOB_CANCEL)
337    #[inline]
338    pub fn cancelled(&self) -> bool {
339        match self.job {
340            Some(j) => j.cancelled(),
341            None => false,
342        }
343    }
344    /// the aux of prepare as T
345    #[inline]
346    pub fn aux<T: 'static>(&self) -> Option<&'a T> {
347        self.aux?.downcast_ref::<T>()
348    }
349    /// source row y (w * 4 bytes)
350    #[inline]
351    pub fn src_row(&self, y: i32) -> &'a [u8] {
352        row(self.src, y)
353    }
354}
355
356/// row y of a surface (w * 4 bytes)
357#[inline]
358pub fn row(sf: &Surface, y: i32) -> &[u8] {
359    let o = y as usize * sf.stride as usize;
360    &sf.px[o..o + sf.w as usize * 4]
361}
362
363/// pixel i of a row
364#[inline(always)]
365pub fn px(b: &[u8], i: usize) -> u32 {
366    let q: [u8; 4] = b[4 * i..4 * i + 4].try_into().unwrap();
367    u32::from_le_bytes(q)
368}
369
370/// set pixel i of a row
371#[inline(always)]
372pub fn put(b: &mut [u8], i: usize, v: u32) {
373    b[4 * i..4 * i + 4].copy_from_slice(&v.to_le_bytes());
374}
375
376/// the destination pixels of one render call: row k holds the pixels x0..x1 of row y0 + k
377pub struct Dst<'a> {
378    rows: Rows<'a>,
379    pub x0: i32,
380    pub y0: i32,
381    pub x1: i32,
382    pub y1: i32,
383}
384
385enum Rows<'a> {
386    /// one slice per row
387    List(Vec<&'a mut [u8]>),
388    /// one slice per row, in a table shared by several destinations (tiles)
389    Table(&'a mut [&'a mut [u8]]),
390    /// row k at k * stride of one buffer
391    Strided(&'a mut [u8], usize),
392}
393
394impl<'a> Dst<'a> {
395    /// a destination from row slices (each (x1 - x0) * 4 bytes, y1 - y0 of them)
396    pub fn new(roi: [i32; 4], rows: Vec<&'a mut [u8]>) -> Dst<'a> {
397        Dst { rows: Rows::List(rows), x0: roi[0], y0: roi[1], x1: roi[2], y1: roi[3] }
398    }
399
400    /// a destination in one buffer: row y0 + k at k * stride (e.g. a tile buffer with stride
401    /// (x1 - x0) * 4)
402    pub fn strided(roi: [i32; 4], buf: &'a mut [u8], stride: usize) -> Dst<'a> {
403        Dst { rows: Rows::Strided(buf, stride), x0: roi[0], y0: roi[1], x1: roi[2], y1: roi[3] }
404    }
405
406    /// the rectangle of a surface (clamped to it) as one destination
407    pub fn of_surface(sf: &'a mut Surface, roi: [i32; 4]) -> Dst<'a> {
408        let (w, h) = (sf.w, sf.h);
409        let x0 = roi[0].clamp(0, w);
410        let y0 = roi[1].clamp(0, h);
411        let (x1, y1) = (roi[2].clamp(x0, w), roi[3].clamp(y0, h));
412        let stride = sf.stride as usize;
413        if x1 <= x0 || y1 <= y0 {
414            return Dst { rows: Rows::List(Vec::new()), x0, y0, x1, y1 };
415        }
416        let start = y0 as usize * stride + x0 as usize * 4;
417        let end = (y1 - 1) as usize * stride + x1 as usize * 4;
418        Dst { rows: Rows::Strided(&mut sf.px[start..end], stride), x0, y0, x1, y1 }
419    }
420
421    /// the pixels x0..x1 of row y (y0 <= y < y1)
422    #[inline]
423    pub fn row(&mut self, y: i32) -> &mut [u8] {
424        let k = (y - self.y0) as usize;
425        match &mut self.rows {
426            Rows::List(v) => v[k],
427            Rows::Table(t) => t[k],
428            Rows::Strided(b, st) => {
429                let n = (self.x1 - self.x0) as usize * 4;
430                &mut b[k * *st..k * *st + n]
431            }
432        }
433    }
434}
435
436/// disjoint rectangles of a surface as destinations (clamped to the surface; empty ones are
437/// empty destinations); panics when two rectangles overlap.  `store` holds the row slices of
438/// all destinations (one allocation, cleared first; pass a new or reused empty Vec):
439///     let mut store = Vec::new();
440///     let dsts = fx::tiles(&mut surface, &rects, &mut store);
441pub fn tiles<'a>(sf: &'a mut Surface, rects: &[[i32; 4]], store: &'a mut Vec<&'a mut [u8]>) -> Vec<Dst<'a>> {
442    let (w, h) = (sf.w, sf.h);
443    let stride = sf.stride as usize;
444    let cl: Vec<[i32; 4]> = rects
445        .iter()
446        .map(|r| {
447            let x0 = r[0].clamp(0, w);
448            let y0 = r[1].clamp(0, h);
449            [x0, y0, r[2].clamp(x0, w), r[3].clamp(y0, h)]
450        })
451        .collect();
452    // an empty rectangle has no rows
453    let nrows = |r: &[i32; 4]| if r[2] > r[0] && r[3] > r[1] { (r[3] - r[1]) as usize } else { 0 };
454    // the rows of rectangle i at store[base[i]..]
455    let mut base = Vec::with_capacity(cl.len() + 1);
456    let mut total = 0usize;
457    for r in &cl {
458        base.push(total);
459        total += nrows(r);
460    }
461    base.push(total);
462    store.clear();
463    store.resize_with(total, Default::default);
464    // the non-empty rectangles by first row; the active ones of a row are kept sorted by x
465    let mut order: Vec<usize> = (0..cl.len()).filter(|&i| nrows(&cl[i]) > 0).collect();
466    if !order.is_empty() && stride != 0 {
467        order.sort_by_key(|&i| (cl[i][1], cl[i][0]));
468        let ymin = cl[order[0]][1];
469        let ymax = order.iter().map(|&i| cl[i][3]).max().unwrap_or(0);
470        let mut active: Vec<usize> = Vec::new();
471        let mut next = 0usize;
472        let lines = sf.px.chunks_mut(stride).take(ymax as usize).skip(ymin as usize);
473        for (k, line) in lines.enumerate() {
474            let y = ymin + k as i32;
475            active.retain(|&i| y < cl[i][3]);
476            while next < order.len() && cl[order[next]][1] == y {
477                let i = order[next];
478                let at = active.partition_point(|&j| cl[j][0] < cl[i][0]);
479                active.insert(at, i);
480                next += 1;
481            }
482            let (mut rest, _) = line.split_at_mut(w as usize * 4);
483            let mut at = 0i32; // column of rest[0]
484            for &i in &active {
485                let (x0, x1) = (cl[i][0], cl[i][2]);
486                assert!(x0 >= at, "fx::tiles: overlapping rectangles");
487                let tmp = core::mem::take(&mut rest);
488                let (_, tail) = tmp.split_at_mut((x0 - at) as usize * 4);
489                let (mine, tail) = tail.split_at_mut((x1 - x0) as usize * 4);
490                store[base[i] + (y - cl[i][1]) as usize] = mine;
491                rest = tail;
492                at = x1;
493            }
494        }
495    }
496    let mut rest: &'a mut [&'a mut [u8]] = &mut store[..];
497    let mut out: Vec<Dst<'a>> = Vec::with_capacity(cl.len());
498    for (i, r) in cl.iter().enumerate() {
499        let (mine, tail) = core::mem::take(&mut rest).split_at_mut(base[i + 1] - base[i]);
500        rest = tail;
501        out.push(Dst { rows: Rows::Table(mine), x0: r[0], y0: r[1], x1: r[2], y1: r[3] });
502    }
503    out
504}
505
506/// fx_rows: f(dst_row, src_row, x0, y) for every row of the ROI (rows = the pixels x0..x1);
507/// polls the cancel flag before every row  -> 0 | ERR_CANCEL
508#[inline]
509pub fn rows(a: &FxArgs, dst: &mut Dst, mut f: impl FnMut(&mut [u8], &[u8], i32, i32)) -> i32 {
510    if a.x1 - a.x0 <= 0 {
511        return 0;
512    }
513    let (b0, b1) = (a.x0 as usize * 4, a.x1 as usize * 4);
514    for y in a.y0..a.y1 {
515        if a.cancelled() {
516            return ERR_CANCEL;
517        }
518        let s = &a.src_row(y)[b0..b1];
519        f(dst.row(y), s, a.x0, y);
520    }
521    0
522}
523
524// ---------------------------------------------------------------- registry (fx_common.s)
525
526static REG: SpinLock<Vec<&'static [FxDesc]>> = SpinLock::new(Vec::new());
527
528/// add a group of effects; a group already registered is ignored -> 0 | ERR_LIMIT
529pub fn register(group: &'static [FxDesc]) -> i32 {
530    let mut r = REG.lock();
531    if r.iter().any(|g| core::ptr::eq(*g, group)) {
532        return 0;
533    }
534    if r.len() >= FXG_MAX {
535        return ERR_LIMIT;
536    }
537    r.push(group);
538    0
539}
540
541/// register every built-in group in menu order (adjustments first)  [fx_use_all]
542pub fn use_all() {
543    for g in [
544        &adj::FXTAB as &'static [FxDesc],
545        &artistic::FXTAB,
546        &blur::FXTAB,
547        &color::FXTAB,
548        &distort::FXTAB,
549        &noise::FXTAB,
550        &object::FXTAB,
551        &photo::FXTAB,
552        &render::FXTAB,
553        &stylize::FXTAB,
554        &enhance::FXTAB,
555    ] {
556        register(g);
557    }
558}
559
560/// the number of registered effects
561pub fn count() -> usize {
562    REG.lock().iter().map(|g| g.len()).sum()
563}
564
565/// the i-th registered effect (group order, then table order)
566pub fn get(i: usize) -> Option<&'static FxDesc> {
567    let r = REG.lock();
568    let mut i = i;
569    for g in r.iter() {
570        if i < g.len() {
571            return Some(&g[i]);
572        }
573        i -= g.len();
574    }
575    None
576}
577
578/// the effect with this ASCII key
579pub fn find(name: &str) -> Option<&'static FxDesc> {
580    (0..).map_while(get).find(|d| d.name == name)
581}
582
583/// the effect with this FX_* id
584pub fn find_id(id: u32) -> Option<&'static FxDesc> {
585    (0..).map_while(get).find(|d| d.id == id)
586}
587
588/// every property's default into its slot(s); slots without a property are 0
589pub fn defaults(desc: &FxDesc, p: &mut Params) {
590    p.0 = [0; FX_NSLOTS];
591    for pr in desc.props {
592        let s = pr.slot as usize;
593        if s >= FX_NSLOTS {
594            continue;
595        }
596        match pr.typ {
597            FP_DEC | FP_ANGLE => p.0[s] = pr.def.to_bits(),
598            FP_PAN => {
599                p.0[s] = pr.def.to_bits();
600                if s + 1 < FX_NSLOTS {
601                    p.0[s + 1] = pr.def_y.to_bits();
602                }
603            }
604            FP_COLOR => p.0[s] = pr.def.to_bits() & 0xFFFF_FFFF,
605            _ => p.0[s] = rt::fx::cvttd64(pr.def) as u64,
606        }
607    }
608}
609
610/// deterministic per-pixel random bits: a murmur3-style finaliser over the inputs  [fx_hash32]
611#[inline]
612pub fn hash32(x: u32, y: u32, seed: u32, stream: u32) -> u32 {
613    let mut h = x.wrapping_mul(0x8DA6B343)
614        ^ y.wrapping_mul(0xD8163841)
615        ^ seed.wrapping_mul(0xCB1AB31F)
616        ^ stream.wrapping_mul(0x165667B1);
617    h ^= h >> 16;
618    h = h.wrapping_mul(0x85EBCA6B);
619    h ^= h >> 13;
620    h = h.wrapping_mul(0xC2B2AE35);
621    h ^ (h >> 16)
622}