Skip to main content

gfx/
tmask.rs

1// tmask -- sparse tiled A8 masks                                          [src/gfx/tmask.s]
2//
3//   TMask::new(w, h) -> Option          1 <= w, h <= 65535, every pixel 0
4//   clear(value)                        every pixel = value (0 / 255 need no memory)
5//   get(x, y) -> u8                     coverage of one pixel (0 outside the mask)
6//   span(x, y, maxn) -> (Span, n)       run of n (1..maxn) pixels from (x, y): Empty (all 0),
7//                                       Full (all 255) or Bytes inside one tile (n <= 64);
8//                                       sentinel runs span tiles; outside -> Empty
9//   row(x, y, n, buf) -> (&[u8], bool)  the n bytes of (x .. x+n-1, y): inside a tile when
10//                                       possible, else buf is filled (true = buf returned)
11//   row_w(x, y) -> Option<&mut [u8]>    writable bytes from (x, y) to the end of its tile row
12//                                       (at most w - x); materialises the tile
13//   tile_w(tx, ty) -> Option<&mut [u8]> writable 64 x 64 tile;   tile_set(tx, ty, full)
14//   fill_rect(x0, y0, x1, y1, value)    (x1, y1 exclusive, clipped)
15//   combine(src, op) / combine_block(tx, ty, Block, op) / copy(src) / clone()
16//   invert(), threshold(level), bbox() -> Option<[i32; 4]>, optimize(), count_tiles()
17//   load_a8(src, stride), store_a8(dst, stride)
18//   raster_path(path, ras, rule, op), raster_polygon(path, ras, pts, rule, op)
19//   trace(cb) -> i32                    outlines of the pixels >= 128 as closed polylines in
20//                                       pixel-edge coordinates, cb(points) -> 0 = go on
21//
22// Tiles are 64 x 64 bytes (row stride 64).  A slot holds Empty, Full or a tile; tiles come
23// from a pool (count_tiles reports the ones in use, as the assembly does).  Pixels of edge
24// tiles outside w x h are ignored everywhere.  Ops return 0 / ERR_* like the assembly.
25
26use alloc::boxed::Box;
27use alloc::vec;
28use alloc::vec::Vec;
29
30use super::path::Path;
31use super::raster::Raster;
32use super::{div255, ERR_FAIL};
33use rt::fx::{cvtt, maxss, minss};
34use simd::{Simd, Sse2};
35
36pub const TM_TILE: usize = 64;
37const TM_SHIFT: u32 = 6;
38const TB: usize = 4096; // bytes per tile
39pub const TM_MAX_SIDE: u32 = 65535;
40pub const TM_REPLACE: u32 = 0; // src
41pub const TM_ADD: u32 = 1; // max(dst, src)
42pub const TM_SUB: u32 = 2; // round(dst * (255 - src) / 255)
43pub const TM_INTERSECT: u32 = 3; // round(dst * src / 255)
44pub const TM_XOR: u32 = 4; // |dst - src|
45pub const TM_OP_COUNT: u32 = 5;
46pub const TM_TRACE_LEVEL: u8 = 128;
47const CHUNK_TILES: u32 = 64; // raster_path: tiles per rasterized chunk
48
49#[derive(Clone, Copy, PartialEq, Eq)]
50enum Slot {
51    Empty,
52    Full,
53    Tile(u32),
54}
55
56/// a run of tm_span
57pub enum Span<'a> {
58    Empty,
59    Full,
60    Bytes(&'a [u8]),
61}
62
63/// a 64 x 64 source block of combine_block
64#[derive(Clone, Copy)]
65pub enum Block<'a> {
66    Empty,
67    Full,
68    Data(&'a [u8], usize), // bytes, row stride (64 readable bytes in each of 64 rows)
69}
70
71// block operations
72#[derive(Clone, Copy, PartialEq, Eq)]
73enum Bo {
74    Max,
75    Sub,
76    Mul,
77    AbsDiff,
78    Inv,
79}
80
81pub struct TMask {
82    pub w: u32,
83    pub h: u32,
84    tw: u32,
85    th: u32,
86    slots: Vec<Slot>,
87    pool: Vec<Box<[u8; TB]>>,
88    free: Vec<u32>,
89    ntiles: u32,
90}
91
92fn bo_byte(op: Bo, d: u8, s: u8) -> u8 {
93    match op {
94        Bo::Max => d.max(s),
95        Bo::Sub => div255(d as u32 * (255 - s as u32)) as u8,
96        Bo::Mul => div255(d as u32 * s as u32) as u8,
97        Bo::AbsDiff => d.abs_diff(s),
98        Bo::Inv => 255 - d,
99    }
100}
101
102// blk_class: 0 all zero / 1 all 255 / 2 mixed, looking at vw bytes of vh rows only
103fn blk_class(src: &[u8], stride: usize, vw: usize, vh: usize) -> u32 {
104    let (mut any, mut all) = (false, true);
105    for r in 0..vh {
106        for &b in &src[r * stride..r * stride + vw] {
107            any |= b != 0;
108            all &= b == 255;
109        }
110    }
111    if !any {
112        0
113    } else if all {
114        1
115    } else {
116        2
117    }
118}
119
120// dst = f(dst, src) for the 64 rows of a tile, src rows of 64 bytes at stride
121#[inline(always)]
122fn rows_op(tile: &mut [u8; TB], src: &[u8], stride: usize, f: impl Fn(u8, u8) -> u8) {
123    for (r, row) in tile.as_chunks_mut::<64>().0.iter_mut().enumerate() {
124        let s: &[u8; 64] = src[r * stride..r * stride + 64].try_into().expect("64 bytes");
125        for (d, &v) in row.iter_mut().zip(s) {
126            *d = f(*d, v);
127        }
128    }
129}
130
131// bit k = byte k of the 64-byte row has its top bit set (>= 128); perf-raster: pmovmskb as
132// blk_rowbits does
133#[inline]
134fn rowbits_top(row: &[u8; 64]) -> u64 {
135    let x = Sse2;
136    let mut bits = 0u64;
137    for k in 0..4 {
138        bits |= (x.movemask8(x.load(&row[k * 16..k * 16 + 16])) as u64) << (16 * k);
139    }
140    bits
141}
142
143// bit k = byte k of the 64-byte row is not 0 (blk_rownz)
144#[inline]
145fn rowbits_nz(row: &[u8; 64]) -> u64 {
146    let x = Sse2;
147    let mut bits = 0u64;
148    for k in 0..4 {
149        bits |= (x.movemask8(x.cmpeq8(x.load(&row[k * 16..k * 16 + 16]), x.zero())) as u64) << (16 * k);
150    }
151    !bits
152}
153
154fn vmask64(vw: u32) -> u64 {
155    if vw >= 64 { u64::MAX } else { (1u64 << vw) - 1 }
156}
157
158// f2i_clamp: trunc(clamp(x, -1, 70000)) (NaN -> -1)
159fn f2i_clamp(x: f32) -> i32 {
160    cvtt(minss(maxss(x, -1.0), 70000.0))
161}
162
163impl TMask {
164    /// a mask of w x h pixels, every pixel 0 (None: bad size)  [tm_alloc]
165    pub fn new(w: u32, h: u32) -> Option<TMask> {
166        if !(1..=TM_MAX_SIDE).contains(&w) || !(1..=TM_MAX_SIDE).contains(&h) {
167            return None;
168        }
169        let tw = (w + 63) >> TM_SHIFT;
170        let th = (h + 63) >> TM_SHIFT;
171        Some(TMask { w, h, tw, th, slots: vec![Slot::Empty; (tw * th) as usize], pool: Vec::new(), free: Vec::new(), ntiles: 0 })
172    }
173
174    pub fn tiles_x(&self) -> u32 {
175        self.tw
176    }
177
178    pub fn tiles_y(&self) -> u32 {
179        self.th
180    }
181
182    // ---- tiles and slots
183
184    fn si(&self, tx: u32, ty: u32) -> usize {
185        (ty * self.tw + tx) as usize
186    }
187
188    // in-range width / height of a tile (1..64)
189    fn tile_dims(&self, tx: u32, ty: u32) -> (usize, usize) {
190        let vw = (self.w - (tx << TM_SHIFT)).min(64);
191        let vh = (self.h - (ty << TM_SHIFT)).min(64);
192        (vw as usize, vh as usize)
193    }
194
195    fn tile_alloc(&mut self) -> u32 {
196        self.ntiles += 1;
197        if let Some(t) = self.free.pop() {
198            return t;
199        }
200        self.pool.push(Box::new([0u8; TB]));
201        self.pool.len() as u32 - 1
202    }
203
204    // a sentinel into a slot, releasing its tile
205    fn slot_put(&mut self, i: usize, v: Slot) {
206        if let Slot::Tile(t) = self.slots[i] {
207            self.free.push(t);
208            self.ntiles -= 1;
209        }
210        self.slots[i] = v;
211    }
212
213    // the slot's tile, materialised if it was a sentinel (fill: with the sentinel's value)
214    fn slot_tile(&mut self, i: usize, fill: bool) -> u32 {
215        match self.slots[i] {
216            Slot::Tile(t) => t,
217            s => {
218                let t = self.tile_alloc();
219                self.slots[i] = Slot::Tile(t);
220                if fill {
221                    self.pool[t as usize].fill(if s == Slot::Full { 255 } else { 0 });
222                }
223                t
224            }
225        }
226    }
227
228    // a uniform tile becomes a sentinel
229    fn slot_norm(&mut self, i: usize, vw: usize, vh: usize) {
230        if let Slot::Tile(t) = self.slots[i] {
231            match blk_class(&self.pool[t as usize][..], 64, vw, vh) {
232                0 => self.slot_put(i, Slot::Empty),
233                1 => self.slot_put(i, Slot::Full),
234                _ => {}
235            }
236        }
237    }
238
239    fn drop_tiles(&mut self) {
240        self.pool.clear();
241        self.free.clear();
242        self.ntiles = 0;
243    }
244
245    fn blk_op(&mut self, t: u32, src: Block, op: Bo) {
246        let tile = &mut self.pool[t as usize];
247        match src {
248            // perf-raster: 64-byte rows, one loop per op (the compiler vectorizes them)
249            Block::Data(s, stride) => match op {
250                Bo::Max => rows_op(tile, s, stride, |d, s| d.max(s)),
251                Bo::Sub => rows_op(tile, s, stride, |d, s| div255(d as u32 * (255 - s as u32)) as u8),
252                Bo::Mul => rows_op(tile, s, stride, |d, s| div255(d as u32 * s as u32) as u8),
253                Bo::AbsDiff => rows_op(tile, s, stride, |d, s| d.abs_diff(s)),
254                Bo::Inv => rows_op(tile, s, stride, |d, _| 255 - d),
255            },
256            _ => {
257                // (Inv only: the source is the tile itself)
258                for b in tile.iter_mut() {
259                    *b = bo_byte(op, *b, *b);
260                }
261            }
262        }
263    }
264
265    // ---- public API
266
267    /// every pixel = value  [tm_clear]
268    pub fn clear(&mut self, value: u8) -> i32 {
269        self.drop_tiles();
270        let s = if value == 255 { Slot::Full } else { Slot::Empty };
271        self.slots.iter_mut().for_each(|x| *x = s);
272        if value != 0 && value != 255 {
273            for i in 0..self.slots.len() {
274                let t = self.slot_tile(i, false);
275                self.pool[t as usize].fill(value);
276            }
277        }
278        0
279    }
280
281    /// coverage of one pixel (0 outside)  [tm_get]
282    pub fn get(&self, x: i32, y: i32) -> u8 {
283        if x as u32 >= self.w || y as u32 >= self.h {
284            return 0;
285        }
286        let (x, y) = (x as u32, y as u32);
287        match self.slots[self.si(x >> TM_SHIFT, y >> TM_SHIFT)] {
288            Slot::Empty => 0,
289            Slot::Full => 255,
290            Slot::Tile(t) => self.pool[t as usize][((y & 63) << TM_SHIFT | (x & 63)) as usize],
291        }
292    }
293
294    /// a run of n (1..maxn) pixels from (x, y)  [tm_span]
295    pub fn span(&self, x: i32, y: i32, maxn: i32) -> (Span<'_>, i32) {
296        if maxn <= 0 {
297            return (Span::Empty, 0);
298        }
299        if y as u32 >= self.h {
300            return (Span::Empty, maxn); // row outside (also y < 0)
301        }
302        if x < 0 {
303            return (Span::Empty, (-(x as i64)).min(maxn as i64) as i32);
304        }
305        if x as u32 >= self.w {
306            return (Span::Empty, maxn);
307        }
308        let maxn = maxn.min(self.w as i32 - x);
309        let (xu, yu) = (x as u32, y as u32);
310        let mut i = self.si(xu >> TM_SHIFT, yu >> TM_SHIFT);
311        let s = self.slots[i];
312        let mut left = 64 - (xu & 63) as i32;
313        match s {
314            Slot::Tile(t) => {
315                let o = ((yu & 63) << TM_SHIFT | (xu & 63)) as usize;
316                let n = left.min(maxn);
317                (Span::Bytes(&self.pool[t as usize][o..o + n as usize]), n)
318            }
319            _ => {
320                while left < maxn {
321                    i += 1;
322                    if self.slots[i] != s {
323                        break;
324                    }
325                    left += 64;
326                }
327                (if s == Slot::Full { Span::Full } else { Span::Empty }, left.min(maxn))
328            }
329        }
330    }
331
332    /// the n bytes of (x .. x+n-1, y): (bytes, true when they are buf)  [tm_row]
333    pub fn row<'a>(&'a self, x: i32, y: i32, n: i32, buf: &'a mut [u8]) -> (&'a [u8], bool) {
334        if n <= 0 {
335            return (&buf[..0], true);
336        }
337        if let (Span::Bytes(b), k) = self.span(x, y, n) {
338            if k == n {
339                return (b, false); // one tile holds the whole run
340            }
341        }
342        let (mut x, mut left, mut o) = (x, n, 0usize);
343        while left > 0 {
344            let (sp, k) = self.span(x, y, left);
345            let k = k as usize;
346            match sp {
347                Span::Empty => buf[o..o + k].fill(0),
348                Span::Full => buf[o..o + k].fill(255),
349                Span::Bytes(b) => buf[o..o + k].copy_from_slice(b),
350            }
351            o += k;
352            x += k as i32;
353            left -= k as i32;
354        }
355        (&buf[..n as usize], true)
356    }
357
358    /// writable bytes from (x, y) to the end of its tile row (at most w - x)  [tm_row_w]
359    pub fn row_w(&mut self, x: i32, y: i32) -> Option<&mut [u8]> {
360        if x as u32 >= self.w || y as u32 >= self.h {
361            return None;
362        }
363        let (xu, yu) = (x as u32, y as u32);
364        let i = self.si(xu >> TM_SHIFT, yu >> TM_SHIFT);
365        let t = self.slot_tile(i, true);
366        let o = ((yu & 63) << TM_SHIFT | (xu & 63)) as usize;
367        let n = ((64 - (xu & 63)).min(self.w - xu)) as usize;
368        Some(&mut self.pool[t as usize][o..o + n])
369    }
370
371    /// writable 64 x 64 tile (materialised with its content)  [tm_tile_w]
372    pub fn tile_w(&mut self, tx: u32, ty: u32) -> Option<&mut [u8]> {
373        if tx >= self.tw || ty >= self.th {
374            return None;
375        }
376        let i = self.si(tx, ty);
377        let t = self.slot_tile(i, true);
378        Some(&mut self.pool[t as usize][..])
379    }
380
381    /// tile (tx, ty) := all 0 / all 255  [tm_tile_set]
382    pub fn tile_set(&mut self, tx: u32, ty: u32, full: bool) {
383        if tx >= self.tw || ty >= self.th {
384            return;
385        }
386        let i = self.si(tx, ty);
387        self.slot_put(i, if full { Slot::Full } else { Slot::Empty });
388    }
389
390    /// set the pixels of the rectangle (x1, y1 exclusive, clipped) to value; whole tiles of
391    /// 0 / 255 become sentinels  [tm_fill_rect]
392    pub fn fill_rect(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, value: u8) -> i32 {
393        let x0 = x0.max(0);
394        let y0 = y0.max(0);
395        let x1 = x1.min(self.w as i32);
396        let y1 = y1.min(self.h as i32);
397        if x1 <= x0 || y1 <= y0 {
398            return 0;
399        }
400        let kind = match value {
401            0 => Some(Slot::Empty),
402            255 => Some(Slot::Full),
403            _ => None,
404        };
405        for ty in (y0 as u32 >> TM_SHIFT)..=((y1 - 1) as u32 >> TM_SHIFT) {
406            for tx in (x0 as u32 >> TM_SHIFT)..=((x1 - 1) as u32 >> TM_SHIFT) {
407                let (vw, vh) = self.tile_dims(tx, ty);
408                let (tx0, ty0) = ((tx << TM_SHIFT) as i32, (ty << TM_SHIFT) as i32);
409                let (tx1, ty1) = (tx0 + vw as i32, ty0 + vh as i32);
410                let (rx0, rx1, ry0, ry1) = (x0.max(tx0), x1.min(tx1), y0.max(ty0), y1.min(ty1));
411                let i = self.si(tx, ty);
412                if let Some(k) = kind {
413                    if rx0 == tx0 && rx1 == tx1 && ry0 == ty0 && ry1 == ty1 {
414                        self.slot_put(i, k);
415                        continue;
416                    }
417                    if self.slots[i] == k {
418                        continue; // already uniform with this value
419                    }
420                }
421                let t = self.slot_tile(i, true) as usize;
422                for y in ry0..ry1 {
423                    let o = ((y & 63) << TM_SHIFT) as usize + (rx0 & 63) as usize;
424                    self.pool[t][o..o + (rx1 - rx0) as usize].fill(value);
425                }
426            }
427        }
428        0
429    }
430
431    /// combine a 64 x 64 block into tile (tx, ty)  [tm_combine_block]
432    pub fn combine_block(&mut self, tx: u32, ty: u32, src: Block, op: u32) -> i32 {
433        if tx >= self.tw || ty >= self.th {
434            return 0;
435        }
436        let (vw, vh) = self.tile_dims(tx, ty);
437        let i = self.si(tx, ty);
438        let mut src = src;
439        if let Block::Data(b, stride) = src {
440            match blk_class(b, stride, vw, vh) {
441                0 => src = Block::Empty, // uniform source -> sentinel
442                1 => src = Block::Full,
443                _ => {}
444            }
445        }
446        let d = self.slots[i];
447        enum Act {
448            Nothing,
449            Set(Slot),
450            Copy,
451            InvCopy,
452            Op(Bo),
453            InvTile,
454        }
455        let act = match op {
456            TM_REPLACE => match src {
457                Block::Empty => Act::Set(Slot::Empty),
458                Block::Full => Act::Set(Slot::Full),
459                _ => Act::Copy,
460            },
461            TM_ADD => match (src, d) {
462                (Block::Empty, _) => Act::Nothing,
463                (Block::Full, _) => Act::Set(Slot::Full),
464                (_, Slot::Empty) => Act::Copy,
465                (_, Slot::Full) => Act::Nothing,
466                _ => Act::Op(Bo::Max),
467            },
468            TM_SUB => match (src, d) {
469                (Block::Empty, _) => Act::Nothing,
470                (Block::Full, _) => Act::Set(Slot::Empty),
471                (_, Slot::Empty) => Act::Nothing,
472                (_, Slot::Full) => Act::InvCopy,
473                _ => Act::Op(Bo::Sub),
474            },
475            TM_INTERSECT => match (src, d) {
476                (Block::Empty, _) => Act::Set(Slot::Empty),
477                (Block::Full, _) => Act::Nothing,
478                (_, Slot::Empty) => Act::Nothing,
479                (_, Slot::Full) => Act::Copy,
480                _ => Act::Op(Bo::Mul),
481            },
482            TM_XOR => match (src, d) {
483                (Block::Empty, _) => Act::Nothing,
484                (Block::Full, Slot::Empty) => Act::Set(Slot::Full),
485                (Block::Full, Slot::Full) => Act::Set(Slot::Empty),
486                (Block::Full, _) => Act::InvTile,
487                (_, Slot::Empty) => Act::Copy,
488                (_, Slot::Full) => Act::InvCopy,
489                _ => Act::Op(Bo::AbsDiff),
490            },
491            _ => return ERR_FAIL,
492        };
493        match act {
494            Act::Nothing => {}
495            Act::Set(s) => self.slot_put(i, s),
496            Act::Copy | Act::InvCopy => {
497                let t = self.slot_tile(i, false) as usize;
498                if let Block::Data(b, stride) = src {
499                    let tile = &mut self.pool[t];
500                    // perf-raster: rows of 64 bytes (blk_copy) instead of a byte loop
501                    if matches!(act, Act::InvCopy) {
502                        rows_op(tile, b, stride, |_, v| 255 - v);
503                    } else {
504                        for (r, row) in tile.as_chunks_mut::<64>().0.iter_mut().enumerate() {
505                            row.copy_from_slice(&b[r * stride..r * stride + 64]);
506                        }
507                    }
508                }
509            }
510            Act::InvTile => {
511                if let Slot::Tile(t) = d {
512                    self.blk_op(t, Block::Empty, Bo::Inv);
513                }
514            }
515            Act::Op(bo) => {
516                if let Slot::Tile(t) = d {
517                    self.blk_op(t, src, bo);
518                    self.slot_norm(i, vw, vh);
519                }
520            }
521        }
522        0
523    }
524
525    /// dst = dst OP src per pixel (same size)  [tm_combine]
526    pub fn combine(&mut self, src: &TMask, op: u32) -> i32 {
527        if self.w != src.w || self.h != src.h {
528            return ERR_FAIL;
529        }
530        for ty in 0..self.th {
531            for tx in 0..self.tw {
532                let b = match src.slots[src.si(tx, ty)] {
533                    Slot::Empty => Block::Empty,
534                    Slot::Full => Block::Full,
535                    Slot::Tile(t) => Block::Data(&src.pool[t as usize][..], 64),
536                };
537                let r = self.combine_block(tx, ty, b, op);
538                if r != 0 {
539                    return r;
540                }
541            }
542        }
543        0
544    }
545
546    /// dst = dst OP dst (tm_combine with the same mask as both operands)
547    pub fn combine_self(&mut self, op: u32) -> i32 {
548        let copy = self.clone();
549        self.combine(&copy, op)
550    }
551
552    /// dst = src (same size)  [tm_copy]
553    pub fn copy(&mut self, src: &TMask) -> i32 {
554        self.combine(src, TM_REPLACE)
555    }
556
557    /// v = 255 - v  [tm_invert]
558    pub fn invert(&mut self) {
559        for i in 0..self.slots.len() {
560            match self.slots[i] {
561                Slot::Empty => self.slots[i] = Slot::Full,
562                Slot::Full => self.slots[i] = Slot::Empty,
563                Slot::Tile(t) => self.blk_op(t, Block::Empty, Bo::Inv),
564            }
565        }
566    }
567
568    /// v = v >= level ? 255 : 0 (level clamped to 1..255)  [tm_threshold]
569    pub fn threshold(&mut self, level: i32) {
570        let level = level.clamp(1, 255) as u8;
571        for ty in 0..self.th {
572            for tx in 0..self.tw {
573                let i = self.si(tx, ty);
574                if let Slot::Tile(t) = self.slots[i] {
575                    for b in self.pool[t as usize].iter_mut() {
576                        *b = if *b >= level { 255 } else { 0 };
577                    }
578                    let (vw, vh) = self.tile_dims(tx, ty);
579                    self.slot_norm(i, vw, vh);
580                }
581            }
582        }
583    }
584
585    /// every uniform tile becomes a sentinel  [tm_optimize]
586    pub fn optimize(&mut self) {
587        for ty in 0..self.th {
588            for tx in 0..self.tw {
589                let (vw, vh) = self.tile_dims(tx, ty);
590                let i = self.si(tx, ty);
591                self.slot_norm(i, vw, vh);
592            }
593        }
594    }
595
596    /// (tiles held in memory, slots that are all 255)  [tm_count_tiles]
597    pub fn count_tiles(&self) -> (u32, u32) {
598        (self.ntiles, self.slots.iter().filter(|s| **s == Slot::Full).count() as u32)
599    }
600
601    /// x0, y0, x1, y1 (exclusive) of the pixels != 0, None when the mask is empty  [tm_bbox]
602    pub fn bbox(&self) -> Option<[i32; 4]> {
603        let mut b = [i32::MAX, i32::MAX, -1, -1]; // inclusive
604        let mut add = |x0: i32, y0: i32, x1: i32, y1: i32| {
605            b[0] = b[0].min(x0);
606            b[1] = b[1].min(y0);
607            b[2] = b[2].max(x1);
608            b[3] = b[3].max(y1);
609        };
610        for ty in 0..self.th {
611            for tx in 0..self.tw {
612                let (vw, vh) = self.tile_dims(tx, ty);
613                let (x0, y0) = ((tx << TM_SHIFT) as i32, (ty << TM_SHIFT) as i32);
614                match self.slots[self.si(tx, ty)] {
615                    Slot::Empty => {}
616                    Slot::Full => add(x0, y0, x0 + vw as i32 - 1, y0 + vh as i32 - 1),
617                    Slot::Tile(t) => {
618                        let m = vmask64(vw as u32);
619                        for r in 0..vh {
620                            let row: &[u8; 64] = self.pool[t as usize][r * 64..r * 64 + 64].try_into().expect("64 bytes");
621                            let bits = rowbits_nz(row) & m;
622                            if bits != 0 {
623                                let (lo, hi) = (bits.trailing_zeros() as i32, 63 - bits.leading_zeros() as i32);
624                                add(x0 + lo, y0 + r as i32, x0 + hi, y0 + r as i32);
625                            }
626                        }
627                    }
628                }
629            }
630        }
631        if b[2] < 0 { None } else { Some([b[0], b[1], b[2] + 1, b[3] + 1]) }
632    }
633
634    /// the mask becomes a copy of the dense w x h A8 image at src (row stride `stride`)  [tm_load_a8]
635    pub fn load_a8(&mut self, src: &[u8], stride: usize) -> i32 {
636        let mut tmp = vec![0u8; TB];
637        for ty in 0..self.th {
638            for tx in 0..self.tw {
639                let (vw, vh) = self.tile_dims(tx, ty);
640                let o = (ty as usize * 64) * stride + tx as usize * 64;
641                let r = if vw == 64 && vh == 64 {
642                    self.combine_block(tx, ty, Block::Data(&src[o..], stride), TM_REPLACE)
643                } else {
644                    // edge tile: the in-range part into the temporary block
645                    for row in 0..vh {
646                        tmp[row * 64..row * 64 + vw].copy_from_slice(&src[o + row * stride..o + row * stride + vw]);
647                    }
648                    self.combine_block(tx, ty, Block::Data(&tmp, 64), TM_REPLACE)
649                };
650                if r != 0 {
651                    return r;
652                }
653            }
654        }
655        0
656    }
657
658    /// write the mask as a dense w x h A8 image (row stride `stride`)  [tm_store_a8]
659    pub fn store_a8(&self, dst: &mut [u8], stride: usize) {
660        for ty in 0..self.th {
661            for tx in 0..self.tw {
662                let (vw, vh) = self.tile_dims(tx, ty);
663                let o = (ty as usize * 64) * stride + tx as usize * 64;
664                let s = self.slots[self.si(tx, ty)];
665                for row in 0..vh {
666                    let d = &mut dst[o + row * stride..o + row * stride + vw];
667                    match s {
668                        Slot::Empty => d.fill(0),
669                        Slot::Full => d.fill(255),
670                        Slot::Tile(t) => d.copy_from_slice(&self.pool[t as usize][row * 64..row * 64 + vw]),
671                    }
672                }
673            }
674        }
675    }
676
677    /// rasterize the path (subpaths closed implicitly, rule RULE_*) with exact anti-aliased
678    /// coverage and combine it into the mask with op (TM_*)  [tm_raster_path]
679    pub fn raster_path(&mut self, path: &Path, ras: &mut Raster, rule: u32, op: u32) -> i32 {
680        if op >= TM_OP_COUNT {
681            return ERR_FAIL;
682        }
683        path.fill_lines(ras);
684        let mut op = op;
685        if op == TM_REPLACE {
686            self.clear(0);
687            op = TM_ADD;
688        }
689        let range = if ras.has_lines() {
690            let lb = ras.lbox();
691            let tx0 = (f2i_clamp(lb[0]).max(0) as u32) >> TM_SHIFT;
692            let ty0 = (f2i_clamp(lb[1]).max(0) as u32) >> TM_SHIFT;
693            let ex = f2i_clamp(lb[2]) + 2 + 63; // ceil + the spill pixel, rounded up to tiles
694            let ey = f2i_clamp(lb[3]) + 2 + 63;
695            if ex < 0 || ey < 0 {
696                None
697            } else {
698                let tx1 = ((ex as u32) >> TM_SHIFT).min(self.tw);
699                let ty1 = ((ey as u32) >> TM_SHIFT).min(self.th);
700                if tx1 <= tx0 || ty1 <= ty0 { None } else { Some((tx0, tx1, ty0, ty1)) }
701            }
702        } else {
703            None
704        };
705        let Some((tx0, tx1, ty0, ty1)) = range else {
706            // nothing covered: only intersect changes the mask (replace was cleared above)
707            if op == TM_INTERSECT {
708                self.clear(0);
709            }
710            return 0;
711        };
712        if op == TM_INTERSECT {
713            for ty in 0..self.th {
714                for tx in 0..self.tw {
715                    if ty < ty0 || ty >= ty1 || tx < tx0 || tx >= tx1 {
716                        self.tile_set(tx, ty, false);
717                    }
718                }
719            }
720        }
721        let chunk = (tx1 - tx0).min(CHUNK_TILES);
722        let rowb = (chunk * 64) as usize;
723        let mut buf = vec![0u8; rowb * 64];
724        for ty in ty0..ty1 {
725            let mut cx = tx0;
726            while cx < tx1 {
727                let cols = (tx1 - cx).min(chunk);
728                let (bx, by) = ((cx * 64) as i32, (ty * 64) as i32);
729                let clip = [bx, by, (bx + (cols * 64) as i32).min(self.w as i32), (by + 64).min(self.h as i32)];
730                // (the assembly: ras_fill with k_a8_over at alpha 255 into the zeroed buffer;
731                // every row is emitted once, so that is the coverage itself -- perf-raster:
732                // a copy, and the written row parts are remembered: tile columns outside
733                // all of them are empty blocks (what blk_class finds there), and only they
734                // are zeroed again)
735                let mut rows = [(0usize, 0usize); 64]; // (offset, bytes) per buffer row
736                let (mut lo, mut hi) = (usize::MAX, 0usize);
737                ras.fill_rows(clip, rule, &mut |y, x, cov| {
738                    let (r, o) = ((y - by) as usize, (x - bx) as usize);
739                    buf[r * rowb + o..r * rowb + o + cov.len()].copy_from_slice(cov);
740                    rows[r] = (o, cov.len());
741                    lo = lo.min(o);
742                    hi = hi.max(o + cov.len());
743                });
744                for col in 0..cols {
745                    let c0 = (col * 64) as usize;
746                    let src = if c0 + 64 <= lo || c0 >= hi { Block::Empty } else { Block::Data(&buf[c0..], rowb) };
747                    let r = self.combine_block(cx + col, ty, src, op);
748                    if r != 0 {
749                        return r;
750                    }
751                }
752                for (r, &(o, n)) in rows.iter().enumerate() {
753                    buf[r * rowb + o..r * rowb + o + n].fill(0);
754                }
755                cx += chunk;
756            }
757        }
758        0
759    }
760
761    /// raster_path of one closed polygon given as points in mask pixels (replaces the path)
762    pub fn raster_polygon(&mut self, path: &mut Path, ras: &mut Raster, pts: &[(f32, f32)], rule: u32, op: u32) -> i32 {
763        path.reset();
764        if let Some(&(x, y)) = pts.first() {
765            path.move_f(x, y);
766            for &(x, y) in &pts[1..] {
767                path.line_f(x, y);
768            }
769        }
770        self.raster_path(path, ras, rule, op)
771    }
772
773    // ---- outline tracing
774
775    // bit i = pixel (tx*64 + i, y) is inside (>= 128); 0 outside the mask
776    fn tr_rowbits(&self, tx: u32, y: i32) -> u64 {
777        if y as u32 >= self.h {
778            return 0;
779        }
780        let m = vmask64((self.w - (tx << TM_SHIFT)).min(64));
781        match self.slots[self.si(tx, y as u32 >> TM_SHIFT)] {
782            Slot::Empty => 0,
783            Slot::Full => m,
784            Slot::Tile(t) => {
785                let o = ((y as u32 & 63) << TM_SHIFT) as usize;
786                // (>= TM_TRACE_LEVEL = 128: the top bit)
787                rowbits_top(self.pool[t as usize][o..o + 64].try_into().expect("64 bytes")) & m
788            }
789        }
790    }
791
792    /// outlines of the pixels with coverage >= 128 along pixel edges: cb(points) for every
793    /// closed contour (corners in pixel-edge coordinates, implicitly closed, inside on the
794    /// right); returns 0 or the first nonzero cb result  [tm_trace]
795    pub fn trace(&self, cb: &mut dyn FnMut(&[(i32, i32)]) -> i32) -> i32 {
796        let mut tr = Tracer { tm: self, vis: vec![None; (self.th + 1) as usize], pts: Vec::new(), cb };
797        let tw = self.tw as usize;
798        for ty in 0..self.th {
799            // perf-raster: the slots of this tile row and the row above as slices
800            let row = &self.slots[ty as usize * tw..(ty as usize + 1) * tw];
801            let above_row = if ty == 0 { &[][..] } else { &self.slots[(ty as usize - 1) * tw..ty as usize * tw] };
802            for tx in 0..self.tw {
803                let s = row[tx as usize];
804                let above = above_row.get(tx as usize).copied().unwrap_or(Slot::Empty);
805                let y0 = (ty << TM_SHIFT) as i32;
806                let last = if (y0 + 64) < self.h as i32 { y0 + 63 } else { self.h as i32 };
807                let r = if let Slot::Tile(_) = s {
808                    let mut r = 0;
809                    for y in y0..=last {
810                        r = tr.scan(tx, y);
811                        if r != 0 {
812                            break;
813                        }
814                    }
815                    r
816                } else {
817                    // uniform tile: only its top row (if the tile above differs) and the bottom
818                    let mut r = 0;
819                    if s != above {
820                        r = tr.scan(tx, y0);
821                    }
822                    if r == 0 && last == self.h as i32 && s == Slot::Full {
823                        r = tr.scan(tx, last);
824                    }
825                    r
826                };
827                if r != 0 {
828                    return r;
829                }
830            }
831        }
832        0
833    }
834}
835
836impl Clone for TMask {
837    fn clone(&self) -> TMask {
838        let mut m = TMask::new(self.w, self.h).expect("valid size");
839        m.copy(self);
840        m
841    }
842}
843
844// lattice walk: pixel ahead-right / ahead-left of point (px, py) for direction d
845// (0 = +x, 1 = +y, 2 = -x, 3 = -y)
846const ARX: [i32; 4] = [0, -1, -1, 0];
847const ARY: [i32; 4] = [0, 0, -1, -1];
848const ALX: [i32; 4] = [0, 0, -1, -1];
849const ALY: [i32; 4] = [-1, 0, 0, -1];
850
851struct Tracer<'a> {
852    tm: &'a TMask,
853    // visited horizontal edges per (tile column, 64 lattice rows), allocated on first use;
854    // perf-raster: one list of tile columns per lattice tile row (th + 1 of them), so a huge
855    // sparse mask needs no table of tw * (th + 1) entries
856    vis: Vec<Option<Box<[Option<Box<[u64; 64]>>]>>>,
857    pts: Vec<(i32, i32)>,
858    cb: &'a mut dyn FnMut(&[(i32, i32)]) -> i32,
859}
860
861impl Tracer<'_> {
862    fn word(&mut self, tx: u32, y: i32) -> &mut u64 {
863        let tw = self.tm.tw as usize;
864        let cols = self.vis[(y as u32 >> TM_SHIFT) as usize].get_or_insert_with(|| vec![None; tw].into_boxed_slice());
865        let blk = cols[tx as usize].get_or_insert_with(|| Box::new([0u64; 64]));
866        &mut blk[(y & 63) as usize]
867    }
868
869    fn mark(&mut self, x: i32, y: i32) {
870        *self.word(x as u32 >> TM_SHIFT, y) |= 1u64 << (x & 63);
871    }
872
873    // trace the contour through the horizontal boundary edge (x .. x+1, y) and report it;
874    // below: pixel (x, y) is inside (walk right along its top), else (x, y-1) (walk left)
875    fn follow(&mut self, x: i32, y: i32, below: bool) -> i32 {
876        let (mut p0x, p0y, d0) = (x, y, if below { 0 } else { 2 });
877        if !below {
878            p0x += 1;
879        }
880        self.pts.clear();
881        self.pts.push((p0x, p0y));
882        let (mut px, mut py, mut d) = (p0x, p0y, d0);
883        loop {
884            match d {
885                0 => {
886                    self.mark(px, py);
887                    px += 1;
888                }
889                1 => py += 1,
890                2 => {
891                    self.mark(px - 1, py);
892                    px -= 1;
893                }
894                _ => py -= 1,
895            }
896            let ar = self.tm.get(px + ARX[d], py + ARY[d]);
897            let al = self.tm.get(px + ALX[d], py + ALY[d]);
898            let nd = if ar < TM_TRACE_LEVEL {
899                (d + 1) & 3 // turn right
900            } else if al < TM_TRACE_LEVEL {
901                d // straight on
902            } else {
903                (d + 3) & 3 // turn left
904            };
905            if px == p0x && py == p0y && nd == d0 {
906                // arrived straight: the start is no corner
907                let pts = if d == d0 { &self.pts[1..] } else { &self.pts[..] };
908                return (self.cb)(pts);
909            }
910            if nd != d {
911                self.pts.push((px, py));
912            }
913            d = nd;
914        }
915    }
916
917    // trace every contour through an unvisited boundary edge of lattice row y in column tx
918    fn scan(&mut self, tx: u32, y: i32) -> i32 {
919        let above = self.tm.tr_rowbits(tx, y - 1);
920        let below = self.tm.tr_rowbits(tx, y);
921        let edges = above ^ below;
922        if edges == 0 {
923            return 0;
924        }
925        loop {
926            let open = !*self.word(tx, y) & edges;
927            if open == 0 {
928                return 0;
929            }
930            let bit = open.trailing_zeros() as i32;
931            let r = self.follow((tx << TM_SHIFT) as i32 + bit, y, below >> bit & 1 != 0);
932            if r != 0 {
933                return r;
934            }
935        }
936    }
937}