Skip to main content

fx/
hist.rs

1// hist -- histogram helpers of the effect kernels (thread safe, explicit buffers)  [src/fx/fx_hist.s]
2//
3//   hist_rgba(a, hist) -> count | ERR_CANCEL    u32[4][256] (B, G, R, A) of the ROI pixels with
4//        alpha != 0 and (with a selection) coverage != 0; polls the cancel flag once per row
5//   hist_percentile(h, total, fraction) -> j    smallest j with h[0] + .. + h[j] > total *
6//        fraction (f64 compare); 0 when there is none (paint.net HistogramRgb.GetPercentile)
7//   hist_mean(h) -> f64                         f32(sum j h[j]) / f32(sum h[j]) (paint.net GetMean)
8//   lochist(a, dst, rad, f) -> 0 | ERR_CANCEL   paint.net LocalHistogramEffect.RenderRect: for
9//        every ROI pixel the histograms (LocHist) of the source pixels (x+u, y+v) inside the
10//        image with u^2 + v^2 <= ((2 rad + 1)^2 + 2) / 4; dst = f(hist, area, srcpixel).
11//        Histograms start at the ROI's left edge of every row and slide to the right (with
12//        paint.net's edge loops), so the result does not depend on the ROI split.
13
14use alloc::vec;
15
16use alloc::vec::Vec;
17
18use crate::{px, put, Dst, FxArgs, ERR_CANCEL};
19
20pub const FX_LOCHIST_MAX: i32 = 1024;
21
22/// histograms B, G, R, A and their block sums (block k = bins 16k .. 16k + 15)
23#[derive(Clone)]
24pub struct LocHist {
25    pub h: [[u32; 256]; 4],
26    pub blk: [[u32; 16]; 4],
27}
28
29impl LocHist {
30    #[inline(always)]
31    fn add(&mut self, p: u32) {
32        for c in 0..4 {
33            let v = (p >> (8 * c) & 255) as usize;
34            self.h[c][v] += 1;
35            self.blk[c][v >> 4] += 1;
36        }
37    }
38    #[inline(always)]
39    fn sub(&mut self, p: u32) {
40        for c in 0..4 {
41            let v = (p >> (8 * c) & 255) as usize;
42            self.h[c][v] -= 1;
43            self.blk[c][v >> 4] -= 1;
44        }
45    }
46}
47
48/// histograms of the ROI (alpha > 0, selection > 0) -> pixels counted | ERR_CANCEL  [fx_hist_rgba]
49pub fn hist_rgba(a: &FxArgs, hist: &mut [[u32; 256]; 4]) -> i64 {
50    *hist = [[0; 256]; 4];
51    let mut count = 0i64;
52    let mut sbuf = [0u8; 256];
53    for y in a.y0..a.y1 {
54        if a.cancelled() {
55            return ERR_CANCEL as i64;
56        }
57        let row = a.src_row(y);
58        let mut x = a.x0;
59        while a.x1 - x > 0 {
60            let n = (a.x1 - x).min(256);
61            let sel = match a.sel {
62                Some(m) => Some(m.row(x, y, n, &mut sbuf).0),
63                None => None,
64            };
65            for i in 0..n as usize {
66                if let Some(s) = sel {
67                    if s[i] == 0 {
68                        continue;
69                    }
70                }
71                let p = px(row, x as usize + i);
72                if p < 0x0100_0000 {
73                    continue; // alpha 0
74                }
75                hist[0][(p & 255) as usize] += 1;
76                hist[1][(p >> 8 & 255) as usize] += 1;
77                hist[2][(p >> 16 & 255) as usize] += 1;
78                hist[3][(p >> 24) as usize] += 1;
79                count += 1;
80            }
81            x += n;
82        }
83    }
84    count
85}
86
87/// smallest j with h[0] + .. + h[j] > total * fraction, else 0  [fx_hist_percentile]
88pub fn hist_percentile(h: &[u32; 256], total: i64, fraction: f64) -> u32 {
89    let target = total as f64 * fraction;
90    let mut integral = 0i64;
91    for (j, &c) in h.iter().enumerate() {
92        integral += c as i64;
93        if integral as f64 > target {
94            return j as u32;
95        }
96    }
97    0
98}
99
100/// f32(sum j h[j]) / f32(sum h[j]) as f64, 0 when empty  [fx_hist_mean]
101pub fn hist_mean(h: &[u32; 256]) -> f64 {
102    let (mut s, mut n) = (0i64, 0i64);
103    for (j, &c) in h.iter().enumerate() {
104        n += c as i64;
105        s += c as i64 * j as i64;
106    }
107    if n == 0 {
108        return 0.0;
109    }
110    (s as f32 / n as f32) as f64
111}
112
113/// paint.net LocalHistogramEffect.RenderRect over the ROI -> 0 | ERR_CANCEL  [fx_lochist]
114pub fn lochist(a: &FxArgs, dst: &mut Dst, rad: i32, mut f: impl FnMut(&LocHist, u32, u32) -> u32) -> i32 {
115    let rad = rad.clamp(0, FX_LOCHIST_MAX);
116    let cutoff = ((2 * rad + 1) * (2 * rad + 1) + 2) >> 2;
117    let (w, h) = (a.src.w, a.src.h);
118    let stride = a.src.stride as usize;
119    let sp = &a.src.px;
120    let at = |x: i32, y: i32| -> u32 { px(&sp[y as usize * stride..], x as usize) };
121    // leadingEdgeX[v] = largest u <= rad with u^2 + v^2 <= cutoff
122    let mut edge = vec![0i32; rad as usize + 1];
123    for v in 0..=rad {
124        let mut e = 0;
125        for u in 0..=rad {
126            if u * u + v * v <= cutoff {
127                e = u;
128            }
129        }
130        edge[v as usize] = e;
131    }
132    let mut hb = alloc::boxed::Box::new(LocHist { h: [[0; 256]; 4], blk: [[0; 16]; 4] });
133    // interior columns (rad <= x <= w - 2 - rad): every window row loses its trailing pixel and
134    // gains its leading one; (trailing, leading) byte offsets of the rows less x * 4.  The counts
135    // do not depend on the order of the updates; an equal pair changes nothing.
136    let mut offs: Vec<(usize, usize)> = Vec::with_capacity(2 * rad as usize + 1);
137    let rd = |o: usize| -> u32 {
138        let q: [u8; 4] = sp[o..o + 4].try_into().unwrap();
139        u32::from_le_bytes(q)
140    };
141    for y in a.y0..a.y1 {
142        if a.x1 <= a.x0 {
143            break;
144        }
145        if a.cancelled() {
146            return ERR_CANCEL;
147        }
148        let hb = &mut *hb;
149        hb.h = [[0; 256]; 4];
150        hb.blk = [[0; 16]; 4];
151        let mut area = 0u32;
152        let top = -rad.min(y);
153        let bottom = (h - 1 - y).min(rad);
154        let x0 = a.x0;
155        let mut left = -rad.min(x0);
156        let mut right = (w - 1 - x0).min(rad);
157        for v in top..=bottom {
158            for u in left..=right {
159                if u * u + v * v <= cutoff {
160                    hb.add(at(x0 + u, y + v));
161                    area += 1;
162                }
163            }
164        }
165        offs.clear();
166        for v in top..=bottom {
167            let e = edge[v.unsigned_abs() as usize] as usize;
168            let ro = (y + v) as usize * stride;
169            offs.push((ro.wrapping_sub(e * 4), ro + (e + 1) * 4));
170        }
171        let drow = dst.row(y);
172        for x in a.x0..a.x1 {
173            put(drow, (x - a.x0) as usize, f(hb, area, at(x, y)));
174            if x >= rad && x <= w - 2 - rad {
175                let b = x as usize * 4;
176                for &(ot, ol) in &offs {
177                    let (pt, pl) = (rd(ot.wrapping_add(b)), rd(ol + b));
178                    if pt != pl {
179                        hb.sub(pt);
180                        hb.add(pl);
181                    }
182                }
183                continue;
184            }
185            left = -rad.min(x);
186            right = (w - 1 - x).min(rad + 1);
187            // trailing edge, top half
188            let mut v = -1;
189            while v >= top {
190                if -edge[-v as usize] >= left {
191                    break;
192                }
193                v -= 1;
194            }
195            while v >= top {
196                hb.sub(at(x - edge[-v as usize], y + v));
197                area -= 1;
198                v -= 1;
199            }
200            // leading edge, top half
201            let mut v = -1;
202            while v >= top {
203                if edge[-v as usize] + 1 <= right {
204                    break;
205                }
206                v -= 1;
207            }
208            while v >= top {
209                hb.add(at(x + edge[-v as usize] + 1, y + v));
210                area += 1;
211                v -= 1;
212            }
213            // trailing edge, bottom half
214            let mut v = 0;
215            while v <= bottom {
216                if -edge[v as usize] >= left {
217                    break;
218                }
219                v += 1;
220            }
221            while v <= bottom {
222                hb.sub(at(x - edge[v as usize], y + v));
223                area -= 1;
224                v += 1;
225            }
226            // leading edge, bottom half
227            let mut v = 0;
228            while v <= bottom {
229                if edge[v as usize] + 1 <= right {
230                    break;
231                }
232                v += 1;
233            }
234            while v <= bottom {
235                hb.add(at(x + edge[v as usize] + 1, y + v));
236                area += 1;
237                v += 1;
238            }
239        }
240    }
241    0
242}