Skip to main content

fx/
conv.rs

1// conv -- shared convolution helpers of the fx kernels                     [src/fx/fx_conv.s]
2//
3//   exp(x) / ln(x) / pow(x, y)       f64 e^x (Cody-Waite + degree 12 Taylor), ln (2 atanh), x^y
4//   roi(a) -> Option<[i32; 4]>       the ROI clamped to the image (fxc_roi), None when empty
5//   copy_roi(a, dst) -> 0 | ERR_CANCEL      destination ROI = source ROI
6//   pint(a, slot, lo, hi) / pdbl(a, slot)   clamped i64 / f64 parameters (pdbl: NaN / inf -> 0,
7//                                    clamped to +-1e6)
8//   load_row(w16, src, y, x0, x1, mode)     working pixels (4 x i16 premultiplied, full 16320)
9//                                    of (x0 .. x1-1, y); outside: edge pixels or 0 (FXL_ZERO);
10//                                    FXL_GAMMA: linear light
11//   var12(r), Kern::gauss(var12), Kern::boxk(h)   separable kernels (exact FIR for sigma <= 6,
12//                                    else a cascade of 3 boxes)
13//   sep_run(sep, io) -> 0 | ERR_*    separable convolution of a ROI in strips: H pass of the
14//                                    rows (clamped / loaded), V pass, io.emit per row
15//   disc_run(disc, io) -> 0 | ERR_*  sums over the paint.net disc of every ROI pixel (prefix
16//                                    sums per row, i32 or f32 accumulation)
17//   disc_edges(r) -> (E, area)       half widths of the disc rows
18//   bac_init(b, c) -> (tab, mode), bac_row, desat_row   paint.net 3 Brightness / Contrast
19//   blurbac(a, dst, r, b, c, f)      Gaussian blur then Brightness / Contrast of the ROI;
20//                                    f(dst, y, x0, row) finishes each output row
21//
22// Exactness: every sum is an integer (running box sums, FIR dot products), so the result of
23// a pixel never depends on the ROI split; box means are rounded through f32
24// (rint(f32(S) * f32(1 / w))), FIR sums as (S + 8192) >> 14 saturated to i16.
25
26use alloc::vec;
27use alloc::vec::Vec;
28
29use gfx::{Surface, FMT_ARGB};
30use img_core::resample::{from_work, to_work};
31use rt::fx::{cvtd64, cvttd, maxsd, minsd};
32use simd::{Kernel, Simd, Sse2};
33
34use crate::lut::luma;
35use crate::*;
36
37pub const FXL_GAMMA: u32 = 1; // linear light (RS_GAMMA)
38pub const FXL_ZERO: u32 = 2; // outside the image = transparent (default: clamp to the edge)
39pub const KT_COPY: u32 = 0;
40pub const KT_FIR: u32 = 1;
41pub const KT_BOX: u32 = 2;
42pub const KN_MAXTAPS: usize = 255;
43pub const KN_MAXBOX: usize = 8;
44const FIR_MAXVAR12: i32 = 432; // 12 sigma^2 of the widest exact FIR (sigma = 6)
45const SEP_MAXBYTES: u64 = 16_777_216; // target size of one strip buffer
46pub const DSF_F32: u32 = 1; // disc: accumulate the per-row sums in f32
47
48// ---------------------------------------------------------------- maths
49
50static EXPC: [f64; 13] = [
51    2.08767569878680989792e-09,
52    2.50521083854417187751e-08,
53    2.75573192239858906526e-07,
54    2.75573192239858906526e-06,
55    2.48015873015873015873e-05,
56    1.98412698412698412698e-04,
57    1.38888888888888888889e-03,
58    8.33333333333333333333e-03,
59    4.16666666666666666667e-02,
60    1.66666666666666666667e-01,
61    0.5,
62    1.0,
63    1.0,
64];
65static LNC: [f64; 11] = [
66    0.0952380952380952380952,
67    0.105263157894736842105,
68    0.117647058823529411765,
69    0.133333333333333333333,
70    0.153846153846153846154,
71    0.181818181818181818182,
72    0.222222222222222222222,
73    0.285714285714285714286,
74    0.4,
75    0.666666666666666666667,
76    2.0,
77];
78const LN2HI: f64 = f64::from_bits(0x3FE6_2E42_FEE0_0000);
79const LN2LO: f64 = f64::from_bits(0x3DEA_39EF_3579_3C76);
80
81/// e^x: k = rint(x / ln 2), r = x - k ln2 (Cody-Waite), degree 12 Taylor  [fxc_exp]
82pub fn exp(x: f64) -> f64 {
83    let x = maxsd(minsd(x, 709.0), -708.0);
84    let k = cvtd64(x * 1.4426950408889634074);
85    let kd = k as f64;
86    let mut r = x - kd * LN2HI;
87    r -= kd * LN2LO;
88    let mut p = EXPC[0];
89    for c in &EXPC[1..] {
90        p = p * r + *c;
91    }
92    p * f64::from_bits(((k + 1023) as u64) << 52)
93}
94
95/// ln x: x = m 2^e, ln m = 2 atanh((m - 1) / (m + 1)); x <= 0 or denormal -> -745  [fxc_ln]
96pub fn ln(x: f64) -> f64 {
97    if !(x > 0.0) {
98        return -745.0;
99    }
100    let b = x.to_bits();
101    let ex = (b >> 52) as i32 & 0x7FF;
102    if ex == 0 {
103        return -745.0;
104    }
105    let mut e = ex - 1023;
106    let mut m = b & 0x000F_FFFF_FFFF_FFFF | 0x3FF0_0000_0000_0000;
107    if m >= 0x3FF6_A09E_667F_3BCD {
108        m -= 0x0010_0000_0000_0000;
109        e += 1;
110    }
111    let m = f64::from_bits(m);
112    let s = (m - 1.0) / (m + 1.0);
113    let z = s * s;
114    let mut p = LNC[0];
115    for c in &LNC[1..] {
116        p = p * z + *c;
117    }
118    let lnm = p * s;
119    let ed = e as f64;
120    ed * LN2HI + (lnm + ed * LN2LO)
121}
122
123/// x^y = exp(y ln x) for x > 0, else 0  [fxc_pow]
124pub fn pow(x: f64, y: f64) -> f64 {
125    if !(x > 0.0) {
126        return 0.0;
127    }
128    exp(ln(x) * y)
129}
130
131// ---------------------------------------------------------------- ROI / parameters
132
133/// the ROI clamped to the image (x0, y0 >= 0, x1 <= w, y1 <= h); None when empty  [fxc_roi]
134pub fn roi(a: &FxArgs) -> Option<[i32; 4]> {
135    let r = [a.x0.max(0), a.y0.max(0), a.x1.min(a.src.w), a.y1.min(a.src.h)];
136    if r[0] < r[2] && r[1] < r[3] { Some(r) } else { None }
137}
138
139/// destination ROI = source ROI  [fxc_copy_roi]
140pub fn copy_roi(a: &FxArgs, dst: &mut Dst) -> i32 {
141    let Some(r) = roi(a) else {
142        return 0;
143    };
144    for y in r[1]..r[3] {
145        if a.cancelled() {
146            return ERR_CANCEL;
147        }
148        let s = &a.src_row(y)[r[0] as usize * 4..r[2] as usize * 4];
149        dst_px(dst, y, r[0], r[2] - r[0]).copy_from_slice(s);
150    }
151    0
152}
153
154/// the destination pixels x0 .. x0 + n of row y
155#[inline]
156pub fn dst_px<'d>(dst: &'d mut Dst, y: i32, x0: i32, n: i32) -> &'d mut [u8] {
157    let o = (x0 - dst.x0) as usize * 4;
158    &mut dst.row(y)[o..o + n as usize * 4]
159}
160
161/// i64 parameter slot clamped to [lo, hi]  [fxc_pint]
162#[inline]
163pub fn pint(a: &FxArgs, slot: usize, lo: i32, hi: i32) -> i32 {
164    a.params.int(slot & (FX_NSLOTS - 1), lo, hi)
165}
166
167/// f64 parameter slot; NaN / infinite -> 0, clamped to -1e6 .. 1e6  [fxc_pdbl]
168#[inline]
169pub fn pdbl(a: &FxArgs, slot: usize) -> f64 {
170    let v = a.params.f(slot & (FX_NSLOTS - 1));
171    if (v.to_bits() >> 52) & 0x7FF == 0x7FF {
172        return 0.0;
173    }
174    minsd(maxsd(v, -1000000.0), 1000000.0)
175}
176
177// ---------------------------------------------------------------- row loader
178
179/// w16 = working pixels of (x0 .. x1-1, y); outside columns / rows: the edge pixel (clamp)
180/// or 0 (mode & FXL_ZERO); mode & FXL_GAMMA: linear light  [fxc_load_row]
181pub fn load_row(dst: &mut [i16], src: &Surface, y: i32, x0: i32, x1: i32, mode: u32) {
182    let n = x1 - x0;
183    if n <= 0 {
184        return;
185    }
186    let mut y = y;
187    if y >= src.h || y < 0 {
188        if mode & FXL_ZERO != 0 {
189            dst[..n as usize * 4].fill(0);
190            return;
191        }
192        y = if y < 0 { 0 } else { src.h - 1 };
193    }
194    let row = row(src, y);
195    let g = mode & FXL_GAMMA;
196    let nl = (-x0).clamp(0, n);
197    let nr = (x1 - src.w).clamp(0, n);
198    let nm = n - nl - nr;
199    if nm > 0 {
200        let c = (x0 + nl) as usize * 4;
201        to_work(&mut dst[nl as usize * 4..], &row[c..], nm as usize, FMT_ARGB, g);
202    }
203    if nl > 0 {
204        let v: [i16; 4] = if mode & FXL_ZERO != 0 {
205            [0; 4]
206        } else if nm > 0 {
207            dst[nl as usize * 4..nl as usize * 4 + 4].try_into().unwrap()
208        } else {
209            to_work(&mut dst[..4], row, 1, FMT_ARGB, g);
210            dst[..4].try_into().unwrap()
211        };
212        for p in dst[..nl as usize * 4].chunks_exact_mut(4) {
213            p.copy_from_slice(&v);
214        }
215    }
216    if nr > 0 {
217        let f = (nl + nm) as usize * 4;
218        let v: [i16; 4] = if mode & FXL_ZERO != 0 {
219            [0; 4]
220        } else if nm > 0 {
221            dst[f - 4..f].try_into().unwrap()
222        } else {
223            to_work(&mut dst[f..f + 4], &row[(src.w as usize - 1) * 4..], 1, FMT_ARGB, g);
224            dst[f..f + 4].try_into().unwrap()
225        };
226        for p in dst[f..f + nr as usize * 4].chunks_exact_mut(4) {
227            p.copy_from_slice(&v);
228        }
229    }
230}
231
232// ---------------------------------------------------------------- kernels
233
234/// paint.net-like blur radius -> 12 sigma^2 = 2 r (r + 2) (r <= 0 -> 0)  [fxc_var12]
235#[inline]
236pub fn var12(r: i32) -> i32 {
237    if r <= 0 { 0 } else { (r + 2).wrapping_mul(r) * 2 }
238}
239
240/// a separable 1-D kernel (KERN)
241#[derive(Clone)]
242pub struct Kern {
243    pub typ: u32,
244    pub halo: i32,
245    pub n: i32,           // FIR taps (odd) / number of boxes
246    pub w: [i16; 256],    // FIR weights (+ a 0 pad tap)
247    pub h: [i32; KN_MAXBOX], // box half widths
248}
249
250impl Kern {
251    /// the identity
252    pub fn copy() -> Kern {
253        Kern { typ: KT_COPY, halo: 0, n: 0, w: [0; 256], h: [0; KN_MAXBOX] }
254    }
255
256    /// one box of width 2h + 1 (h <= 0: identity)  [fxc_kern_box]
257    pub fn boxk(h: i32) -> Kern {
258        let mut k = Kern::copy();
259        if h > 0 {
260            k.typ = KT_BOX;
261            k.halo = h;
262            k.n = 1;
263            k.h[0] = h;
264        }
265        k
266    }
267
268    /// Gaussian with 12 sigma^2 = var12 (<= 0: identity)  [fxc_kern_gauss]
269    ///   FIR (var12 <= 432): h = ceil(sqrt(0.75 var12)) (1..18), g_i = exp(-6 i^2 / var12),
270    ///   sum = g_0 + 2 g_1 + ..., q_i = trunc(g_i (16384 / sum) + 0.5), the centre absorbs
271    ///   16384 - (q_0 + 2 sum q_i).  Boxes: wl = largest odd w with 3 w^2 <= var12 + 3,
272    ///   m = (2 num + den) / (2 den) (num = 3 wl^2 + 12 wl + 9 - var12, den = 4 wl + 4,
273    ///   m <= 3): m boxes of width wl, then 3 - m of wl + 2.
274    pub fn gauss(var12: i32) -> Kern {
275        let mut k = Kern::copy();
276        if var12 <= 0 {
277            return k;
278        }
279        if var12 <= FIR_MAXVAR12 {
280            let x = rt::fx::sqrtd(var12 as f64 * 0.75);
281            let mut h = cvttd(x);
282            if x > h as f64 {
283                h += 1;
284            }
285            let h = h.clamp(1, 18);
286            let c = -6.0 / var12 as f64;
287            let mut g = [0f64; 19];
288            for (i, gv) in g.iter_mut().enumerate().take(h as usize + 1) {
289                *gv = exp(((i * i) as i32) as f64 * c);
290            }
291            let mut sum = g[0];
292            for &gv in g.iter().take(h as usize + 1).skip(1) {
293                sum += gv + gv;
294            }
295            let inv = 16384.0 / sum;
296            let hu = h as usize;
297            let mut total = 0i32;
298            for (i, &gv) in g.iter().enumerate().take(hu + 1) {
299                let q = cvttd(gv * inv + 0.5);
300                k.w[hu + i] = q as i16;
301                k.w[hu - i] = q as i16;
302                total += q;
303                if i != 0 {
304                    total += q;
305                }
306            }
307            k.w[hu] = k.w[hu].wrapping_add((16384 - total) as i16);
308            k.w[2 * hu + 1] = 0;
309            k.typ = KT_FIR;
310            k.halo = h;
311            k.n = 2 * h + 1;
312            return k;
313        }
314        let mut wl = 1i32;
315        while (wl + 2) * (wl + 2) * 3 <= var12 + 3 {
316            wl += 2;
317        }
318        let num = wl * wl * 3 + wl * 12 + 9 - var12;
319        let den = wl * 4 + 4;
320        let m = (((2 * num + den) as u32) / ((2 * den) as u32)).min(3) as i32;
321        let mut halo = 0;
322        for b in 0..3 {
323            let w = if b < m { wl } else { wl + 2 };
324            k.h[b as usize] = w >> 1;
325            halo += w >> 1;
326        }
327        k.typ = KT_BOX;
328        k.halo = halo;
329        k.n = 3;
330        k
331    }
332
333    /// FIR weights as used: taps 0 .. 2 * npairs - 1 (the pad tap is 0)
334    #[inline]
335    fn fir_taps(&self) -> usize {
336        let n = (self.n as usize).min(KN_MAXTAPS);
337        n.div_ceil(2) * 2
338    }
339}
340
341// ---------------------------------------------------------------- row kernels
342
343#[inline(always)]
344fn sat16(v: i32) -> i16 {
345    v.clamp(-32768, 32767) as i16
346}
347
348/// the pmaddwd weight pair (w[2p], w[2p+1]) as one u32
349#[inline(always)]
350fn pair_w(w: &[i16], p: usize) -> u32 {
351    w[2 * p] as u16 as u32 | (w[2 * p + 1] as u16 as u32) << 16
352}
353
354/// the two pixels of a register interleaved channel by channel (b b' g g' r r' a a' per lane)
355#[inline(always)]
356fn pair<S: Simd>(s: S, v: S::V) -> S::V {
357    s.shuf16::<0xD8>(s.shuf32::<0xD8>(v))
358}
359
360/// one output pixel of fir_h (also the tail of the kernel)
361#[inline(always)]
362fn fir_h_px(dst: &mut [i16], src: &[i16], i: usize, w: &[i16]) {
363    let mut acc = [0i32; 4];
364    for (j, &wj) in w.iter().enumerate() {
365        if wj == 0 {
366            continue;
367        }
368        let p = &src[(i + j) * 4..(i + j) * 4 + 4];
369        for c in 0..4 {
370            acc[c] = acc[c].wrapping_add(p[c] as i32 * wj as i32);
371        }
372    }
373    for c in 0..4 {
374        dst[i * 4 + c] = sat16(acc[c].wrapping_add(8192) >> 14);
375    }
376}
377
378// fxc_fir_h: pmaddwd over pixel pairs (the asm's pair rows built on the fly), 2 (SSE2) / 4
379// (AVX2) output pixels per step
380struct FirHK<'a> {
381    dst: &'a mut [i16],
382    src: &'a [i16],
383    n: usize,
384    w: &'a [i16],
385}
386
387impl Kernel for FirHK<'_> {
388    type Out = ();
389    #[inline(always)]
390    fn run<S: Simd>(self, s: S) {
391        let FirHK { dst, src, n, w } = self;
392        let np = w.len() / 2;
393        let step = S::BYTES / 8;
394        let r = s.splat32(8192);
395        let mut i = 0;
396        while i + step <= n && (i + step + 2 * np) * 4 <= src.len() {
397            let (mut lo, mut hi) = (s.zero(), s.zero());
398            for p in 0..np {
399                let wv = s.splat32(pair_w(w, p));
400                let a = pair(s, s.load_i16(&src[(i + 2 * p) * 4..]));
401                let b = pair(s, s.load_i16(&src[(i + 1 + 2 * p) * 4..]));
402                lo = s.add32(lo, s.madd16(a, wv));
403                hi = s.add32(hi, s.madd16(b, wv));
404            }
405            let lo = s.sra32::<14>(s.add32(lo, r));
406            let hi = s.sra32::<14>(s.add32(hi, r));
407            s.store_i16(&mut dst[i * 4..], s.packs32(lo, hi));
408            i += step;
409        }
410        while i < n {
411            fir_h_px(dst, src, i, w);
412            i += 1;
413        }
414    }
415}
416
417/// dst[i] = sat((sum_j src[i + j] w[j] + 8192) >> 14), i < n (4 channels per pixel)  [fxc_fir_h]
418fn fir_h(dst: &mut [i16], src: &[i16], n: usize, w: &[i16]) {
419    simd::dispatch(FirHK { dst, src, n, w });
420}
421
422type V = <Sse2 as Simd>::V;
423
424/// rint(f32(v) * inv) of four i32 lanes (cvtdq2ps, mulps, cvtps2dq)
425#[inline(always)]
426fn box_mean(v: V, inv: V) -> V {
427    let x = Sse2;
428    x.cvtr_f32_i32(x.mulf(x.cvt_i32_f32(v), inv))
429}
430
431/// two results as 8 i16 (2 pixels), or the first pixel only
432#[inline(always)]
433fn store2(dst: &mut [i16], a: V, b: V, two: bool) {
434    let x = Sse2;
435    if two {
436        x.store_i16(dst, x.packs32(a, b));
437    } else {
438        let mut t = [0i16; 8];
439        x.store_i16(&mut t, x.packs32(a, a));
440        dst[..4].copy_from_slice(&t[..4]);
441    }
442}
443
444/// dst[i] = rint(f32(sum src[i .. i+2h]) * f32(1 / (2h+1))), i < n (running sums of 4 i32
445/// lanes, one pixel per register as the asm); src holds 4 slack values  [fxc_box_h]
446fn box_h(dst: &mut [i16], src: &[i16], n: usize, h: i32) {
447    if n == 0 {
448        return;
449    }
450    let x = Sse2;
451    let z = x.zero();
452    let w = (2 * h + 1) as usize;
453    let inv = x.splatf(1.0f32 / w as i32 as f32);
454    let px = |i: usize| x.unpacklo16(x.load_i16(&src[i * 4..]), z);
455    let mut sum = z;
456    for k in 0..w {
457        sum = x.add32(sum, px(k));
458    }
459    let mut i = 0;
460    while i < n {
461        let v0 = box_mean(sum, inv);
462        if i + 1 == n {
463            store2(&mut dst[i * 4..], v0, v0, false);
464            break;
465        }
466        sum = x.sub32(x.add32(sum, px(i + w)), px(i));
467        let v1 = box_mean(sum, inv);
468        store2(&mut dst[i * 4..], v0, v1, true);
469        if i + 2 < n {
470            sum = x.sub32(x.add32(sum, px(i + 1 + w)), px(i + 1));
471        }
472        i += 2;
473    }
474}
475
476// fxc_fir_v: pmaddwd over row pairs, 2 (SSE2) / 4 (AVX2) pixels per step
477struct FirVK<'a> {
478    out: &'a mut [i16],
479    src: &'a [i16],
480    rows: &'a [usize],
481    n: usize,
482    tw: usize,
483    w: &'a [i16],
484}
485
486impl Kernel for FirVK<'_> {
487    type Out = ();
488    #[inline(always)]
489    fn run<S: Simd>(self, s: S) {
490        let FirVK { out, src, rows, n, tw, w } = self;
491        let np = w.len() / 2;
492        let step = S::BYTES / 8;
493        let r = s.splat32(8192);
494        for k in 0..n {
495            let o = &mut out[k * tw * 4..(k + 1) * tw * 4];
496            let mut x = 0;
497            while x + step <= tw {
498                let (mut lo, mut hi) = (s.zero(), s.zero());
499                for p in 0..np {
500                    let wv = s.splat32(pair_w(w, p));
501                    let a = s.load_i16(&src[rows[k + 2 * p] + x * 4..]);
502                    let b = s.load_i16(&src[rows[k + 2 * p + 1] + x * 4..]);
503                    lo = s.add32(lo, s.madd16(s.unpacklo16(a, b), wv));
504                    hi = s.add32(hi, s.madd16(s.unpackhi16(a, b), wv));
505                }
506                let lo = s.sra32::<14>(s.add32(lo, r));
507                let hi = s.sra32::<14>(s.add32(hi, r));
508                s.store_i16(&mut o[x * 4..], s.packs32(lo, hi));
509                x += step;
510            }
511            for i in x * 4..tw * 4 {
512                let mut a = 0i32;
513                for (j, &wj) in w.iter().enumerate() {
514                    if wj != 0 {
515                        a = a.wrapping_add(src[rows[k + j] + i] as i32 * wj as i32);
516                    }
517                }
518                o[i] = sat16(a.wrapping_add(8192) >> 14);
519            }
520        }
521    }
522}
523
524/// output row k = sat((sum_j w_j rows[k + j] + 8192) >> 14); row j at src[rows[j]..]
525/// [fxc_fir_v]
526fn fir_v(out: &mut [i16], src: &[i16], rows: &[usize], n: usize, tw: usize, w: &[i16]) {
527    simd::dispatch(FirVK { out, src, rows, n, tw, w });
528}
529
530/// output row k = rint(f32(sum rows[k .. k+2h]) * f32(1 / (2h+1))); row j at src[rows[j]..];
531/// acc: tw * 4 scratch values  [fxc_box_v]
532fn box_v(out: &mut [i16], src: &[i16], rows: &[usize], n: usize, tw: usize, h: i32, acc: &mut [i32]) {
533    let x = Sse2;
534    let w = (2 * h + 1) as usize;
535    let inv = x.splatf(1.0f32 / w as i32 as f32);
536    let acc = &mut acc[..tw * 4];
537    acc.fill(0);
538    for &r in rows.iter().take(w) {
539        for (a, &p) in acc.iter_mut().zip(src[r..r + tw * 4].iter()) {
540            *a = a.wrapping_add(p as u16 as i32);
541        }
542    }
543    for k in 0..n {
544        let o = &mut out[k * tw * 4..(k + 1) * tw * 4];
545        let mut i = 0;
546        while i < tw {
547            let a = box_mean(x.load_i32(&acc[i * 4..]), inv);
548            if i + 1 < tw {
549                let b = box_mean(x.load_i32(&acc[(i + 1) * 4..]), inv);
550                store2(&mut o[i * 4..], a, b, true);
551            } else {
552                store2(&mut o[i * 4..], a, a, false);
553            }
554            i += 2;
555        }
556        if k + 1 < n {
557            let (lv, en) = (&src[rows[k]..rows[k] + tw * 4], &src[rows[k + w]..rows[k + w] + tw * 4]);
558            for ((a, &e), &l) in acc.iter_mut().zip(en.iter()).zip(lv.iter()) {
559                *a = a.wrapping_add(e as u16 as i32).wrapping_sub(l as u16 as i32);
560            }
561        }
562    }
563}
564
565// ---------------------------------------------------------------- separable run
566
567/// a separable run (SEP): source, job, kernels, the output ROI, loader mode
568pub struct Sep<'a> {
569    pub src: &'a Surface,
570    pub job: Option<&'a (dyn Cancel + Sync)>,
571    pub kh: &'a Kern,
572    pub kv: Option<&'a Kern>,
573    pub x0: i32,
574    pub y0: i32,
575    pub x1: i32,
576    pub y1: i32,
577    pub mode: u32,
578    /// io.load replaces fxc_load_row (rows are then not clamped to the image)
579    pub custom_load: bool,
580}
581
582impl<'a> Sep<'a> {
583    /// the standard run of an effect: source / job / ROI of a (None when the ROI is empty)
584    /// [fxc_sep_init]
585    pub fn new(a: &FxArgs<'a>, k: &'a Kern) -> Option<Sep<'a>> {
586        let r = roi(a)?;
587        Some(Sep { src: a.src, job: a.job, kh: k, kv: None, x0: r[0], y0: r[1], x1: r[2], y1: r[3], mode: 0, custom_load: false })
588    }
589    #[inline]
590    fn cancelled(&self) -> bool {
591        self.job.is_some_and(|j| j.cancelled())
592    }
593}
594
595/// the row source and sink of sep_run (SP_LOAD / SP_EMIT)
596pub trait SepIo {
597    /// a custom loader (when Sep::custom_load): w16 of (x0 .. x1-1, y)
598    fn load(&mut self, _sep: &Sep, _w16: &mut [i16], _y: i32, _x0: i32, _x1: i32) {}
599    /// output row y (columns sep.x0 .. sep.x1-1) as working pixels
600    fn emit(&mut self, sep: &Sep, y: i32, row: &[i16]);
601}
602
603/// the standard emitter: working row -> straight ARGB in the destination  [fxc_emit_argb]
604pub struct EmitArgb<'d, 'e>(pub &'d mut Dst<'e>);
605
606impl SepIo for EmitArgb<'_, '_> {
607    fn emit(&mut self, sep: &Sep, y: i32, row: &[i16]) {
608        let n = sep.x1 - sep.x0;
609        from_work(dst_px(self.0, y, sep.x0, n), row, n as usize, FMT_ARGB, sep.mode & FXL_GAMMA);
610    }
611}
612
613/// separable convolution of a ROI -> 0 | ERR_CANCEL  [fxc_sep_run]
614/// Strips of S rows: H pass for the rows sy0 - HY .. sy1 + HY (clamped rows computed once),
615/// then the V pass over a row table, then io.emit per row.
616pub fn sep_run(sep: &Sep, io: &mut impl SepIo) -> i32 {
617    let kh = sep.kh;
618    let kv = sep.kv.unwrap_or(kh);
619    let (hx, hy) = (kh.halo, kv.halo);
620    let tw = sep.x1 - sep.x0;
621    if tw <= 0 || sep.y1 <= sep.y0 {
622        return 0;
623    }
624    let l = (tw + 2 * hx) as usize;
625    let tw = tw as usize;
626    let rb = tw as u64 * 8;
627    let mut s = 256i32;
628    while s > 16 && (s + 2 * hy) as u64 * rb > SEP_MAXBYTES {
629        s >>= 1;
630    }
631    let nrmax = (s + 2 * hy) as usize;
632    let imgh = sep.src.h;
633    let clamp = !sep.custom_load && sep.mode & FXL_ZERO == 0;
634    // one block for the buffers (as the asm: one allocation per run)
635    let vbox = kv.typ == KT_BOX;
636    let (l4, big) = ((l + 4) * 4, nrmax * tw * 4);
637    let nb = if vbox { big } else { 0 };
638    let mut buf = vec![0i16; 3 * l4 + big + 2 * nb + s as usize * tw * 4];
639    let (inb, rest) = buf.split_at_mut(l4);
640    let (ha, rest) = rest.split_at_mut(l4);
641    let (hb, rest) = rest.split_at_mut(l4);
642    let (t, rest) = rest.split_at_mut(big);
643    let (b1, rest) = rest.split_at_mut(nb);
644    let (b2, out) = rest.split_at_mut(nb);
645    // row tables: pt (offsets of the T rows of a strip, + 2 slack) and the plain rows of B1 / B2
646    let mut tabs = vec![0usize; 2 * nrmax + 2];
647    let (pt, ident) = tabs.split_at_mut(nrmax + 2);
648    for (k, v) in ident.iter_mut().enumerate() {
649        *v = k * tw * 4;
650    }
651    let mut acc = vec![0i32; if vbox { tw * 4 } else { 0 }];
652    let wh: &[i16] = &kh.w[..kh.fir_taps()];
653    let wv: &[i16] = &kv.w[..kv.fir_taps()];
654    let mut sy0 = sep.y0;
655    while sy0 < sep.y1 {
656        let sy1 = (sy0 + s).min(sep.y1);
657        let ra = sy0 - hy;
658        let rbnd = sy1 + hy;
659        let (ta, tb) = if clamp { (ra.max(0), rbnd.min(imgh)) } else { (ra, rbnd) };
660        // ---- H pass: rows ta .. tb-1 -> t
661        for (k, y) in (ta..tb).enumerate() {
662            if sep.cancelled() {
663                return ERR_CANCEL;
664            }
665            let (x0, x1) = (sep.x0 - hx, sep.x1 + hx);
666            if sep.custom_load {
667                io.load(sep, inb, y, x0, x1);
668            } else {
669                load_row(inb, sep.src, y, x0, x1, sep.mode);
670            }
671            let trow = &mut t[k * tw * 4..(k + 1) * tw * 4];
672            match kh.typ {
673                KT_FIR => fir_h(trow, inb, tw, wh),
674                KT_BOX => {
675                    let mut len = l;
676                    let nb = (kh.n as usize).min(KN_MAXBOX);
677                    for b in 0..nb {
678                        let h = kh.h[b];
679                        let nlen = len - 2 * h as usize;
680                        let last = b + 1 >= kh.n as usize;
681                        // source: inb (box 0), ha (after an even box), hb (after an odd box)
682                        match (b, last) {
683                            (0, true) => box_h(trow, inb, nlen, h),
684                            (0, false) => box_h(ha, inb, nlen, h),
685                            (_, true) if b % 2 == 1 => box_h(trow, ha, nlen, h),
686                            (_, true) => box_h(trow, hb, nlen, h),
687                            (_, false) if b % 2 == 1 => box_h(hb, ha, nlen, h),
688                            (_, false) => box_h(ha, hb, nlen, h),
689                        }
690                        len = nlen;
691                    }
692                }
693                _ => trow.copy_from_slice(&inb[..tw * 4]),
694            }
695        }
696        // ---- row table: PT[k] = t row of clamp(ra + k, ta, tb - 1)
697        let nv = (rbnd - ra) as usize;
698        for (k, v) in pt[..nv].iter_mut().enumerate() {
699            let y = (ra + k as i32).clamp(ta, tb - 1) - ta;
700            *v = y as usize * tw * 4;
701        }
702        pt[nv] = pt[nv - 1];
703        pt[nv + 1] = pt[nv - 1];
704        let ptr: &[usize] = &pt[..nv + 2];
705        let nrows = (sy1 - sy0) as usize;
706        // ---- V pass
707        let res: Option<&[i16]> = match kv.typ {
708            KT_FIR => {
709                fir_v(out, t, ptr, nrows, tw, wv);
710                Some(&*out)
711            }
712            KT_BOX => {
713                // box b reads the T rows (b = 0), B1 (odd b) or B2 (even b > 0) and writes OUT
714                // (the last box), B1 (even b) or B2 (odd b)
715                let nb = (kv.n as usize).min(KN_MAXBOX);
716                let mut nin = nv;
717                for b in 0..nb {
718                    let h = kv.h[b];
719                    let nprev = nin;
720                    nin -= 2 * h as usize;
721                    let last = b + 1 >= kv.n as usize;
722                    if b == 0 {
723                        box_v(if last { &mut *out } else { &mut *b1 }, t, ptr, nin, tw, h, &mut acc);
724                    } else if b % 2 == 1 {
725                        box_v(if last { &mut *out } else { &mut *b2 }, b1, &ident[..nprev], nin, tw, h, &mut acc);
726                    } else {
727                        box_v(if last { &mut *out } else { &mut *b1 }, b2, &ident[..nprev], nin, tw, h, &mut acc);
728                    }
729                }
730                Some(&*out)
731            }
732            _ => None,
733        };
734        // ---- emit
735        for k in 0..nrows {
736            if sep.cancelled() {
737                return ERR_CANCEL;
738            }
739            let row: &[i16] = match res {
740                Some(o) => &o[k * tw * 4..(k + 1) * tw * 4],
741                None => &t[ptr[k]..ptr[k] + tw * 4],
742            };
743            io.emit(sep, sy0 + k as i32, row);
744        }
745        sy0 = sy1;
746    }
747    0
748}
749
750// ---------------------------------------------------------------- disc run
751
752/// half widths of the disc rows E[0..r] (paint.net LocalHistogramEffect: largest u with
753/// u^2 + v^2 <= ((2r+1)^2 + 2) / 4) and the area  [fxc_disc_edges]
754pub fn disc_edges(r: i32) -> (Vec<i32>, i32) {
755    let cutoff = ((2 * r + 1) * (2 * r + 1) + 2) >> 2;
756    let mut e = vec![0i32; r.max(0) as usize + 1];
757    let mut area = 0;
758    for v in 0..=r.max(0) {
759        let mut u = 0;
760        while u < r && (u + 1) * (u + 1) + v * v <= cutoff {
761            u += 1;
762        }
763        e[v as usize] = u;
764        area += if v == 0 { 2 * u + 1 } else { 2 * (2 * u + 1) };
765    }
766    (e, area)
767}
768
769/// a disc run (DSC)
770pub struct Disc<'a> {
771    pub src: &'a Surface,
772    pub job: Option<&'a (dyn Cancel + Sync)>,
773    pub r: i32,
774    pub flags: u32,
775    pub x0: i32,
776    pub y0: i32,
777    pub x1: i32,
778    pub y1: i32,
779    pub mode: u32,
780    /// pixels in the disc (set by disc_run)
781    pub area: i32,
782    /// io.prep replaces the default row preparation
783    pub custom_prep: bool,
784}
785
786impl<'a> Disc<'a> {
787    /// the standard run of an effect (None when the ROI is empty)  [fxc_disc_init]
788    pub fn new(a: &FxArgs<'a>, r: i32) -> Option<Disc<'a>> {
789        let rc = roi(a)?;
790        Some(Disc { src: a.src, job: a.job, r, flags: 0, x0: rc[0], y0: rc[1], x1: rc[2], y1: rc[3], mode: 0, area: 0, custom_prep: false })
791    }
792    #[inline]
793    fn cancelled(&self) -> bool {
794        self.job.is_some_and(|j| j.cancelled())
795    }
796}
797
798/// the summed row of a disc run: i32 x 4 per pixel, or f32 x 4 with DSF_F32
799#[non_exhaustive]
800pub enum DiscRow<'r> {
801    I(&'r mut [i32]),
802    F(&'r mut [f32]),
803}
804
805/// the row source and sink of disc_run (DS_PREP / DS_EMIT)
806pub trait DiscIo {
807    /// custom preparation (Disc::custom_prep): i32 x 4 values of (x0 .. x1-1, y)
808    fn prep(&mut self, _d: &Disc, _v: &mut [i32], _y: i32, _x0: i32, _x1: i32) {}
809    fn emit(&mut self, d: &Disc, y: i32, row: DiscRow);
810}
811
812/// the standard emitter (i32 sums): W16 = rint(f32(sum) * f32(1 / area)), then straight ARGB
813/// [fxc_disc_emit_argb]
814pub struct DiscArgb<'d, 'e>(pub &'d mut Dst<'e>);
815
816impl DiscIo for DiscArgb<'_, '_> {
817    fn emit(&mut self, d: &Disc, y: i32, row: DiscRow) {
818        let n = d.x1 - d.x0;
819        if n <= 0 {
820            return;
821        }
822        let inv = Sse2.splatf(1.0f32 / d.area as f32);
823        let DiscRow::I(r) = row else {
824            return;
825        };
826        // chunks of DE_CHUNK pixels through a stack buffer
827        let mut w = [0i16; DE_CHUNK * 4];
828        let mut x = 0usize;
829        for c in r[..n as usize * 4].chunks(DE_CHUNK * 4) {
830            let m = c.len() / 4;
831            for (o, q) in w.chunks_exact_mut(8).zip(c.chunks(8)) {
832                let sv = Sse2;
833                let a = sv.cvtr_f32_i32(sv.mulf(sv.cvt_i32_f32(sv.load_i32(&q[..4])), inv));
834                let b = if q.len() == 8 { sv.cvtr_f32_i32(sv.mulf(sv.cvt_i32_f32(sv.load_i32(&q[4..])), inv)) } else { a };
835                sv.store_i16(o, sv.packs32(a, b));
836            }
837            from_work(dst_px(self.0, y, d.x0 + x as i32, m as i32), &w[..m * 4], m, FMT_ARGB, d.mode & FXL_GAMMA);
838            x += m;
839        }
840    }
841}
842
843/// pixels per from_work call of the disc emitters
844pub(crate) const DE_CHUNK: usize = 256;
845
846/// disc sums of a ROI -> 0 | ERR_CANCEL  [fxc_disc_run]
847/// Per source row a prefix sum P (i32, wrapping) makes each disc row one difference
848/// P[x + e + 1] - P[x - e]; rows are clamped to the image; the rows are summed in i32 or (DSF_F32)
849/// in f32 in the order v = -r .. r.
850pub fn disc_run(d: &mut Disc, io: &mut impl DiscIo) -> i32 {
851    let r = d.r.clamp(0, 1000);
852    let tw = d.x1 - d.x0;
853    if tw <= 0 || d.y1 <= d.y0 {
854        return 0;
855    }
856    let l = (tw + 2 * r) as usize;
857    let tw = tw as usize;
858    let prb = (l as u64 + 1) * 16;
859    let imgh = d.src.h;
860    let mut s = 128i32;
861    while s > 8 && (s + 2 * r) as u64 * prb > SEP_MAXBYTES {
862        s >>= 1;
863    }
864    let nrmax = (s + 2 * r) as usize;
865    let (e, area) = disc_edges(r);
866    d.area = area;
867    let pw = (l + 1) * 4; // prefix row length (i32)
868    // the i32 buffers in one block (v: custom preparation only; acc_i: i32 sums only)
869    let f32m = d.flags & DSF_F32 != 0;
870    let nv = if d.custom_prep { (l + 4) * 4 } else { 0 };
871    let na = if f32m { 0 } else { tw * 4 };
872    let mut ibuf = vec![0i32; nv + nrmax * pw + na];
873    let (v, rest) = ibuf.split_at_mut(nv);
874    let (p, acc_i) = rest.split_at_mut(nrmax * pw);
875    let mut w16 = vec![0i16; if d.custom_prep { 0 } else { (l + 4) * 4 }];
876    let mut acc_f = vec![0f32; if f32m { tw * 4 } else { 0 }];
877    let mut sy0 = d.y0;
878    while sy0 < d.y1 {
879        let sy1 = (sy0 + s).min(d.y1);
880        let ra = sy0 - r;
881        let ta = ra.max(0);
882        let tb = (sy1 + r).min(imgh);
883        // prefix rows ta .. tb-1
884        for (k, y) in (ta..tb).enumerate() {
885            if d.cancelled() {
886                return ERR_CANCEL;
887            }
888            let (x0, x1) = (d.x0 - r, d.x1 + r);
889            let pr = &mut p[k * pw..(k + 1) * pw];
890            pr[..4].fill(0);
891            let x = Sse2;
892            let mut sum = x.zero();
893            if d.custom_prep {
894                io.prep(d, v, y, x0, x1);
895                for (o, q) in pr[4..].chunks_exact_mut(4).zip(v[..l * 4].chunks_exact(4)) {
896                    sum = x.add32(sum, x.load_i32(q));
897                    x.store_i32(o, sum);
898                }
899            } else {
900                load_row(&mut w16, d.src, y, x0, x1, d.mode);
901                // w16 holds 4 values of slack: an 8-value load per pixel stays inside
902                let z = x.zero();
903                for (i, o) in pr[4..].chunks_exact_mut(4).enumerate() {
904                    sum = x.add32(sum, x.unpacklo16(x.load_i16(&w16[i * 4..]), z));
905                    x.store_i32(o, sum);
906                }
907            }
908        }
909        let nrows = (sy1 - sy0) as usize;
910        for kk in 0..nrows {
911            if d.cancelled() {
912                return ERR_CANCEL;
913            }
914            acc_i.fill(0);
915            acc_f.fill(0.0);
916            for j in 0..=(2 * r) as usize {
917                let vv = j as i32 - r;
918                let ev = e[vv.unsigned_abs() as usize] as usize;
919                let y = (ra + (kk + j) as i32).clamp(ta, tb - 1) - ta;
920                let pr = &p[y as usize * pw..(y as usize + 1) * pw];
921                let ho = (r as usize + ev + 1) * 4;
922                let lo = (r as usize - ev) * 4;
923                let (hi, lo) = (&pr[ho..ho + tw * 4], &pr[lo..lo + tw * 4]);
924                if f32m {
925                    for ((a, &h), &l) in acc_f.iter_mut().zip(hi).zip(lo) {
926                        *a += h.wrapping_sub(l) as f32;
927                    }
928                } else {
929                    for ((a, &h), &l) in acc_i.iter_mut().zip(hi).zip(lo) {
930                        *a = a.wrapping_add(h.wrapping_sub(l));
931                    }
932                }
933            }
934            let row = if f32m { DiscRow::F(&mut acc_f) } else { DiscRow::I(&mut *acc_i) };
935            io.emit(d, sy0 + kk as i32, row);
936        }
937        sy0 = sy1;
938    }
939    0
940}
941
942// ---------------------------------------------------------------- brightness / contrast
943
944/// paint.net 3 BrightnessAndContrast as used inside Glow / Soften Portrait / Pencil / Ink:
945/// (tab, mode) -- mode 0 (div = 100) / 1: shift tables, mode 2 (contrast 100): threshold
946/// [fxc_bac_init]
947pub fn bac_init(b: i32, c: i32) -> ([i32; 256], u32) {
948    let (mul, div) = if c < 0 {
949        (c + 100, 100)
950    } else if c == 0 {
951        (1, 1)
952    } else {
953        (100, 100 - c)
954    };
955    let mut tab = [0i32; 256];
956    if div == 0 {
957        for (i, t) in tab.iter_mut().enumerate() {
958            *t = if i as i32 + b < 128 { 0 } else { 255 };
959        }
960        return (tab, 2);
961    }
962    let mode = if div == 100 { 0 } else { 1 };
963    for (i, t) in tab.iter_mut().enumerate() {
964        let i = i as i32;
965        let mut v = i - 127;
966        if mode != 0 {
967            v += b;
968        }
969        let mut r = v.wrapping_mul(mul) / div + 127 - i;
970        if mode == 0 {
971            r += b;
972        }
973        *t = r;
974    }
975    (tab, mode)
976}
977
978/// apply bac_init's table to a straight ARGB row (dst may be src: pass the same slice twice
979/// through bac_row_inplace)  [fxc_bac_row]
980pub fn bac_row_inplace(row: &mut [u8], tab: &[i32; 256], mode: u32) {
981    for i in 0..row.len() / 4 {
982        let p = px(row, i);
983        let t = tab[luma(p) as usize];
984        let o = if mode == 2 {
985            (t as u32).wrapping_mul(0x010101) | p & 0xFF00_0000
986        } else {
987            let mut o = p & 0xFF00_0000;
988            for sh in [0, 8, 16] {
989                let v = ((p >> sh & 255) as i32 + t).clamp(0, 255) as u32;
990                o |= v << sh;
991            }
992            o
993        };
994        put(row, i, o);
995    }
996}
997
998/// B = G = R = intensity, alpha kept  [fxc_desat_row]
999pub fn desat_row(dst: &mut [u8], src: &[u8]) {
1000    for i in 0..src.len() / 4 {
1001        let p = px(src, i);
1002        put(dst, i, luma(p) * 0x010101 | p & 0xFF00_0000);
1003    }
1004}
1005
1006/// desat_row in place
1007pub fn desat_inplace(row: &mut [u8]) {
1008    for i in 0..row.len() / 4 {
1009        let p = px(row, i);
1010        put(row, i, luma(p) * 0x010101 | p & 0xFF00_0000);
1011    }
1012}
1013
1014struct BlurBac<'d, 'e, 'f, F: FnMut(&mut Dst, i32, i32, &mut [u8])> {
1015    dst: &'d mut Dst<'e>,
1016    tab: [i32; 256],
1017    mode: u32,
1018    tmp: Vec<u8>,
1019    f: &'f mut F,
1020}
1021
1022impl<F: FnMut(&mut Dst, i32, i32, &mut [u8])> SepIo for BlurBac<'_, '_, '_, F> {
1023    fn emit(&mut self, sep: &Sep, y: i32, row: &[i16]) {
1024        let n = (sep.x1 - sep.x0) as usize;
1025        from_work(&mut self.tmp[..n * 4], row, n, FMT_ARGB, 0);
1026        bac_row_inplace(&mut self.tmp[..n * 4], &self.tab, self.mode);
1027        (self.f)(self.dst, y, sep.x0, &mut self.tmp[..n * 4]);
1028    }
1029}
1030
1031/// the paint.net 3 chain "Gaussian blur (radius r) of the source, then Brightness /
1032/// Contrast" for the ROI; f(dst, y, x0, row) receives the straight ARGB result of each row
1033/// (columns x0 ..) and writes the destination itself -> 0 | ERR_*  [fxc_blurbac]
1034pub fn blurbac(a: &FxArgs, dst: &mut Dst, r: i32, b: i32, c: i32, mut f: impl FnMut(&mut Dst, i32, i32, &mut [u8])) -> i32 {
1035    let (tab, mode) = bac_init(b, c);
1036    let k = Kern::gauss(var12(r));
1037    let Some(sep) = Sep::new(a, &k) else {
1038        return 0;
1039    };
1040    let n = (sep.x1 - sep.x0) as usize;
1041    let mut io = BlurBac { dst, tab, mode, tmp: vec![0u8; n * 4 + 64], f: &mut f };
1042    sep_run(&sep, &mut io)
1043}
1044
1045/// working values of one straight pixel  (rs_to_work, no gamma)
1046#[inline]
1047pub fn w16_of(p: u32) -> [i16; 4] {
1048    let mut w = [0i16; 4];
1049    to_work(&mut w, &p.to_le_bytes(), 1, FMT_ARGB, 0);
1050    w
1051}