Skip to main content

rasterrocket_render/
bitmap.rs

1//! Pixel buffers and the anti-aliasing scratch buffer.
2//!
3//! [`Bitmap<P>`] replaces `SplashBitmap` with a clean, always-top-down,
4//! typed-generic alternative. Key differences from the C++ original:
5//! - `rowSize` is always positive (no bottom-up layout).
6//! - Typed row access via [`bytemuck::cast_slice`] — zero-copy, no mode switch.
7//! - Alpha is an `Option<Vec<u8>>` separate plane (matching C++ layout).
8//!
9//! [`AaBuf`] is the 1-bit supersampled scratch buffer used by the AA fill
10//! path. It is 4 rows × (`bitmap_width` × 4) columns, MSB-first packed.
11
12use std::marker::PhantomData;
13
14use crate::types::{AA_SIZE, Pixel};
15
16// ── internal helper ───────────────────────────────────────────────────────────
17
18/// Convert a `u32` to `usize`.
19///
20/// On every platform where Rust runs, `usize` is at least 32 bits wide, so
21/// this conversion is always exact. The compile-time `const` assertion below
22/// enforces that invariant; any platform that violates it will fail to
23/// compile rather than producing silently truncated values.
24#[inline]
25const fn u32_to_usize(v: u32) -> usize {
26    const {
27        assert!(
28            u32::BITS <= usize::BITS,
29            "platform has a narrower usize than u32"
30        );
31    }
32    v as usize
33}
34
35// ── Bitmap ────────────────────────────────────────────────────────────────────
36
37/// A typed, top-down pixel buffer.
38///
39/// `P` determines the in-memory layout. The row stride is `width * P::BYTES`
40/// rounded up to the next multiple of `row_pad` (default 4).
41///
42/// The optional alpha plane is always `width * height` bytes, one byte per pixel,
43/// top-down. It is stored separately from the colour data (matching `SplashBitmap`).
44pub struct Bitmap<P: Pixel> {
45    /// Width of the bitmap in pixels.
46    pub width: u32,
47    /// Height of the bitmap in pixels.
48    pub height: u32,
49    /// Byte distance between the start of consecutive rows; always `≥ width * P::BYTES`
50    /// and a multiple of `row_pad`.
51    pub stride: usize,
52    data: Vec<u8>,
53    alpha: Option<Vec<u8>>,
54    _marker: PhantomData<P>,
55}
56
57impl<P: Pixel> Bitmap<P> {
58    /// Allocate a new zeroed bitmap.
59    ///
60    /// `row_pad` pads each row to a multiple of that many bytes (pass 1 for no padding).
61    /// `with_alpha` allocates a separate alpha plane initialised to 0.
62    ///
63    /// # Panics
64    ///
65    /// Panics if `row_pad` is 0.
66    #[must_use]
67    pub fn new(width: u32, height: u32, row_pad: usize, with_alpha: bool) -> Self {
68        assert!(row_pad >= 1, "row_pad must be ≥ 1");
69        let raw_stride = u32_to_usize(width) * P::BYTES;
70        let stride = if row_pad <= 1 {
71            raw_stride
72        } else {
73            raw_stride.div_ceil(row_pad) * row_pad
74        };
75        let data = vec![0u8; stride * u32_to_usize(height)];
76        let alpha = if with_alpha {
77            Some(vec![0u8; u32_to_usize(width) * u32_to_usize(height)])
78        } else {
79            None
80        };
81        Self {
82            width,
83            height,
84            stride,
85            data,
86            alpha,
87            _marker: PhantomData,
88        }
89    }
90
91    // ── Row access ────────────────────────────────────────────────────────────
92
93    /// Typed read-only access to row `y`.
94    ///
95    /// Returns a slice of exactly `width` pixels.
96    ///
97    /// # Panics
98    ///
99    /// Panics if `y >= height`.
100    #[must_use]
101    pub fn row(&self, y: u32) -> &[P] {
102        let bytes = self.row_bytes(y);
103        bytemuck::cast_slice(&bytes[..u32_to_usize(self.width) * P::BYTES])
104    }
105
106    /// Typed mutable access to row `y`.
107    ///
108    /// # Panics
109    ///
110    /// Panics if `y >= height`.
111    pub fn row_mut(&mut self, y: u32) -> &mut [P] {
112        let w = u32_to_usize(self.width) * P::BYTES;
113        let bytes = self.row_bytes_mut(y);
114        bytemuck::cast_slice_mut(&mut bytes[..w])
115    }
116
117    /// Shared bounds check used by every row accessor.
118    #[inline]
119    fn assert_row_in_bounds(&self, y: u32) {
120        assert!(
121            y < self.height,
122            "row index {y} is out of bounds for bitmap height {}",
123            self.height
124        );
125    }
126
127    /// Raw byte read-only access to the full stride of row `y` (including padding).
128    ///
129    /// # Panics
130    ///
131    /// Panics if `y >= height`.
132    #[must_use]
133    pub fn row_bytes(&self, y: u32) -> &[u8] {
134        self.assert_row_in_bounds(y);
135        let off = u32_to_usize(y) * self.stride;
136        &self.data[off..off + self.stride]
137    }
138
139    /// Raw byte mutable access to the full stride of row `y`.
140    ///
141    /// # Panics
142    ///
143    /// Panics if `y >= height`.
144    pub fn row_bytes_mut(&mut self, y: u32) -> &mut [u8] {
145        self.assert_row_in_bounds(y);
146        let off = u32_to_usize(y) * self.stride;
147        &mut self.data[off..off + self.stride]
148    }
149
150    // ── Alpha access ──────────────────────────────────────────────────────────
151
152    /// Alpha plane row `y`, if the alpha plane was allocated.
153    ///
154    /// Returns `None` if this bitmap was created without an alpha plane.
155    ///
156    /// # Panics
157    ///
158    /// Panics if `y >= height`.
159    #[must_use]
160    pub fn alpha_row(&self, y: u32) -> Option<&[u8]> {
161        self.assert_row_in_bounds(y);
162        self.alpha.as_ref().map(|a| {
163            let w = u32_to_usize(self.width);
164            let off = u32_to_usize(y) * w;
165            &a[off..off + w]
166        })
167    }
168
169    /// Returns `true` if this bitmap has a separate alpha plane.
170    #[must_use]
171    pub const fn has_alpha(&self) -> bool {
172        self.alpha.is_some()
173    }
174
175    /// Read-only access to the full alpha plane (`width × height` bytes).
176    ///
177    /// Returns `None` if this bitmap was created without an alpha plane.
178    #[must_use]
179    pub fn alpha_plane(&self) -> Option<&[u8]> {
180        self.alpha.as_deref()
181    }
182
183    /// Mutable access to the full alpha plane (`width × height` bytes).
184    ///
185    /// Returns `None` if this bitmap was created without an alpha plane.
186    pub fn alpha_plane_mut(&mut self) -> Option<&mut [u8]> {
187        self.alpha.as_deref_mut()
188    }
189
190    /// Simultaneous mutable access to the pixel row and the alpha row.
191    ///
192    /// Returns `(pixel_row_bytes, Some(alpha_row))` or `(pixel_row_bytes, None)`.
193    /// Provided because the borrow checker rejects holding two mutable
194    /// references into the same `&mut self` (pixel data and alpha plane) via
195    /// separate accessor calls.
196    ///
197    /// # Panics
198    ///
199    /// Panics if `y >= height`.
200    pub fn row_and_alpha_mut(&mut self, y: u32) -> (&mut [u8], Option<&mut [u8]>) {
201        self.assert_row_in_bounds(y);
202        let stride = self.stride;
203        let w = u32_to_usize(self.width);
204        let off = u32_to_usize(y) * stride;
205        let pixels = &mut self.data[off..off + stride];
206        let alpha = self.alpha.as_mut().map(|a| {
207            let aoff = u32_to_usize(y) * w;
208            &mut a[aoff..aoff + w]
209        });
210        (pixels, alpha)
211    }
212
213    // ── Bulk operations ───────────────────────────────────────────────────────
214
215    /// Fill the entire bitmap with a solid colour and an alpha value.
216    ///
217    /// `alpha` is ignored if the bitmap has no alpha plane.
218    pub fn clear(&mut self, color: P, alpha: u8) {
219        let pixel_bytes: &[u8] = bytemuck::bytes_of(&color);
220        if P::BYTES == 1 {
221            // Optimise single-byte pixels: memset the entire data buffer.
222            self.data.fill(pixel_bytes[0]);
223        } else {
224            debug_assert!(
225                P::BYTES > 0,
226                "clear: P::BYTES must be non-zero for chunked fill"
227            );
228            // Write one pixel-sized chunk per pixel in each row, then zero the
229            // stride padding. Using `chunks_exact_mut` avoids manual index arithmetic.
230            let w = u32_to_usize(self.width);
231            let n = w * P::BYTES;
232            let stride = self.stride;
233            for row in self.data.chunks_exact_mut(stride) {
234                for dst in row[..n].chunks_exact_mut(P::BYTES) {
235                    dst.copy_from_slice(pixel_bytes);
236                }
237                row[n..].fill(0);
238            }
239        }
240        if let Some(ref mut a) = self.alpha {
241            a.fill(alpha);
242        }
243    }
244
245    /// Total byte size of the colour data buffer.
246    #[must_use]
247    pub const fn data_len(&self) -> usize {
248        self.data.len()
249    }
250
251    /// Access the full colour data buffer as a flat byte slice.
252    #[must_use]
253    pub fn data(&self) -> &[u8] {
254        &self.data
255    }
256
257    /// Mutable access to the full colour data buffer as a flat byte slice.
258    pub fn data_mut(&mut self) -> &mut [u8] {
259        &mut self.data
260    }
261}
262
263// ── BitmapBand ────────────────────────────────────────────────────────────────
264
265/// A mutable view into a horizontal band of a [`Bitmap`].
266///
267/// Created by [`Bitmap::bands_mut`].  Provides the same row-access interface
268/// as `Bitmap<P>` but covers only the rows `[y_start, y_start + height)`.
269///
270/// Row indices passed to [`BitmapBand::row_and_alpha_mut`] and
271/// [`BitmapBand::row_bytes_mut`] are **absolute** (i.e. in the same coordinate
272/// space as the parent bitmap), not band-local.
273pub struct BitmapBand<'bmp, P: Pixel> {
274    /// Pixel width of the band (same as the parent bitmap width).
275    pub width: u32,
276    /// Number of rows in this band.
277    pub height: u32,
278    /// Absolute y-coordinate of the first row in the full bitmap.
279    pub y_start: u32,
280    /// Byte distance between the start of consecutive rows.
281    pub stride: usize,
282    data: &'bmp mut [u8],
283    alpha: Option<&'bmp mut [u8]>,
284    _marker: PhantomData<P>,
285}
286
287impl<P: Pixel> BitmapBand<'_, P> {
288    /// Simultaneous mutable access to the pixel row and the alpha row.
289    ///
290    /// `y` is the **absolute** row index (must satisfy `y_start ≤ y < y_start + height`).
291    ///
292    /// Returns `(pixel_row_bytes, Some(alpha_row))` or `(pixel_row_bytes, None)`.
293    ///
294    /// # Panics
295    ///
296    /// Panics if `y` is outside `[y_start, y_start + height)`.
297    pub fn row_and_alpha_mut(&mut self, y: u32) -> (&mut [u8], Option<&mut [u8]>) {
298        let y_local = self.local_y(y);
299        let stride = self.stride;
300        let w = u32_to_usize(self.width);
301        let off = y_local * stride;
302        let pixels = &mut self.data[off..off + stride];
303        let alpha = self.alpha.as_mut().map(|a| {
304            let aoff = y_local * w;
305            &mut a[aoff..aoff + w]
306        });
307        (pixels, alpha)
308    }
309
310    /// Raw byte mutable access to the full stride of row `y`.
311    ///
312    /// `y` is the **absolute** row index (must satisfy `y_start ≤ y < y_start + height`).
313    ///
314    /// # Panics
315    ///
316    /// Panics if `y` is outside `[y_start, y_start + height)`.
317    pub fn row_bytes_mut(&mut self, y: u32) -> &mut [u8] {
318        let y_local = self.local_y(y);
319        let stride = self.stride;
320        let off = y_local * stride;
321        &mut self.data[off..off + stride]
322    }
323
324    /// Convert an absolute row index to a band-local index, panicking if out of range.
325    #[inline]
326    fn local_y(&self, y: u32) -> usize {
327        assert!(
328            y >= self.y_start && y < self.y_start + self.height,
329            "row index {y} is out of range for band [y_start={}, height={}]",
330            self.y_start,
331            self.height,
332        );
333        u32_to_usize(y - self.y_start)
334    }
335}
336
337impl<P: Pixel> Bitmap<P> {
338    /// Split the bitmap into `n_bands` horizontal bands of approximately equal height,
339    /// returning a `Vec<BitmapBand<'_, P>>`.
340    ///
341    /// The bands cover the full height of the bitmap with no gaps and no overlaps.
342    /// Each band borrows a disjoint slice of the underlying pixel data and alpha plane,
343    /// making it safe to render bands in parallel.
344    ///
345    /// If `n_bands == 0` or `n_bands > height`, the number of bands is clamped so that
346    /// each band contains at least one row.
347    ///
348    /// # Panics
349    ///
350    /// Panics if `n_bands` is 0.
351    pub fn bands_mut(&mut self, n_bands: usize) -> Vec<BitmapBand<'_, P>> {
352        assert!(n_bands >= 1, "n_bands must be ≥ 1");
353        let h = u32_to_usize(self.height);
354        // Clamp so each band has at least 1 row.
355        let n = n_bands.min(h.max(1));
356
357        let width = self.width;
358        let stride = self.stride;
359
360        // Split the pixel data into per-band slices using split_at_mut.
361        // We build a Vec of mutable slices without unsafe by iterating with split_at_mut.
362        let mut remaining_data: &mut [u8] = &mut self.data;
363        let mut remaining_alpha: Option<&mut [u8]> = self.alpha.as_deref_mut();
364
365        let mut bands = Vec::with_capacity(n);
366
367        for band_idx in 0..n {
368            // Distribute rows as evenly as possible: first `h % n` bands get one extra row.
369            let rows_before = (h / n) * band_idx + band_idx.min(h % n);
370            let rows_in_band = h / n + usize::from(band_idx < h % n);
371            let _ = rows_before; // used only for y_start calculation via cumulative split
372
373            let data_bytes = rows_in_band * stride;
374            let alpha_bytes = rows_in_band * u32_to_usize(width);
375
376            let (band_data, rest_data) = remaining_data.split_at_mut(data_bytes);
377            remaining_data = rest_data;
378
379            let (band_alpha_opt, rest_alpha_opt) = remaining_alpha.map_or((None, None), |a| {
380                let (ba, ra) = a.split_at_mut(alpha_bytes);
381                (Some(ba), Some(ra))
382            });
383            remaining_alpha = rest_alpha_opt;
384
385            // y_start is the cumulative row count of all previous bands.
386            // Recompute from the band index.
387            let y_start = (h / n) * band_idx + band_idx.min(h % n);
388
389            bands.push(BitmapBand {
390                width,
391                height: u32::try_from(rows_in_band).expect("rows_in_band fits in u32"),
392                y_start: u32::try_from(y_start).expect("y_start fits in u32"),
393                stride,
394                data: band_data,
395                alpha: band_alpha_opt,
396                _marker: PhantomData,
397            });
398        }
399
400        bands
401    }
402}
403
404// ── Type-erased bitmap ────────────────────────────────────────────────────────
405
406/// A mode-erased bitmap, used when the transparency group stack must store
407/// bitmaps of varying pixel types without monomorphizing the entire stack.
408///
409/// Phase 2 will add rendering methods; Phase 1 only needs storage + dimensions.
410pub enum AnyBitmap {
411    /// 24-bit RGB, 8 bits per channel.
412    Rgb8(Bitmap<crate::types::Rgb8>),
413    /// 32-bit RGBA, 8 bits per channel (alpha stored in a separate plane).
414    Rgba8(Bitmap<crate::types::Rgba8>),
415    /// 8-bit grayscale.
416    Gray8(Bitmap<crate::types::Gray8>),
417    /// 32-bit CMYK, 8 bits per channel.
418    Cmyk8(Bitmap<crate::types::Cmyk8>),
419    /// Up to 8 spot/device-N channels, 8 bits each.
420    DeviceN8(Bitmap<crate::types::DeviceN8>),
421}
422
423impl AnyBitmap {
424    /// Width of the contained bitmap in pixels.
425    #[must_use]
426    pub const fn width(&self) -> u32 {
427        match self {
428            Self::Rgb8(b) => b.width,
429            Self::Rgba8(b) => b.width,
430            Self::Gray8(b) => b.width,
431            Self::Cmyk8(b) => b.width,
432            Self::DeviceN8(b) => b.width,
433        }
434    }
435
436    /// Height of the contained bitmap in pixels.
437    #[must_use]
438    pub const fn height(&self) -> u32 {
439        match self {
440            Self::Rgb8(b) => b.height,
441            Self::Rgba8(b) => b.height,
442            Self::Gray8(b) => b.height,
443            Self::Cmyk8(b) => b.height,
444            Self::DeviceN8(b) => b.height,
445        }
446    }
447
448    /// The pixel mode (colour space + bit depth) of the contained bitmap.
449    #[must_use]
450    pub const fn mode(&self) -> crate::types::PixelMode {
451        match self {
452            Self::Rgb8(_) => crate::types::PixelMode::Rgb8,
453            Self::Rgba8(_) => crate::types::PixelMode::Xbgr8,
454            Self::Gray8(_) => crate::types::PixelMode::Mono8,
455            Self::Cmyk8(_) => crate::types::PixelMode::Cmyk8,
456            Self::DeviceN8(_) => crate::types::PixelMode::DeviceN8,
457        }
458    }
459}
460
461// ── AaBuf ─────────────────────────────────────────────────────────────────────
462
463/// Anti-aliasing scratch buffer: 4 rows × (`bitmap_width` × 4) columns, 1 bit/pixel.
464///
465/// Bit packing is MSB-first (matching `SplashBitmap` `Mono1` layout and the
466/// `renderAALine` / `clipAALine` code in `SplashXPathScanner.cc`):
467/// - Bit 7 of byte 0 is pixel 0 of the row.
468/// - Left-edge mask for pixel x: `0xff >> (x & 7)`.
469/// - Right-edge mask for pixel x1 (exclusive): `(0xff00u16 >> (x1 & 7)) as u8`.
470///
471/// `height` is always [`AA_SIZE`] (4). `width` is `bitmap_width * AA_SIZE`.
472pub struct AaBuf {
473    /// Width in pixels (= `bitmap_width` × `AA_SIZE`).
474    pub width: usize,
475    /// Always [`AA_SIZE`] (4).
476    pub height: usize,
477    data: Vec<u8>, // row-major; rows are (width+7)/8 bytes each
478}
479
480/// `AA_SIZE` converted to `usize`. Computed once as a compile-time constant.
481///
482/// `AA_SIZE` is `i32 = 4`, which is always non-negative, so the conversion
483/// can never fail; the `expect` ensures any future change to a negative value
484/// is caught immediately.
485const AA_SIZE_USIZE: usize = {
486    // i32 → usize: can only fail if AA_SIZE is negative.
487    assert!(AA_SIZE >= 0, "AA_SIZE must be non-negative");
488    AA_SIZE as usize
489};
490
491impl AaBuf {
492    /// Create a new zeroed AA buffer for a bitmap of the given pixel width.
493    #[must_use]
494    pub fn new(bitmap_width: usize) -> Self {
495        let width = bitmap_width * AA_SIZE_USIZE;
496        let height = AA_SIZE_USIZE;
497        let row_bytes = width.div_ceil(8);
498        Self {
499            width,
500            height,
501            data: vec![0u8; row_bytes * height],
502        }
503    }
504
505    /// Bytes per row (= `width.div_ceil(8)`).
506    #[inline]
507    #[must_use]
508    pub const fn row_bytes(&self) -> usize {
509        self.width.div_ceil(8)
510    }
511
512    /// Shared bounds check used by every row accessor.
513    #[inline]
514    fn assert_row_in_bounds(&self, row: usize) {
515        assert!(
516            row < self.height,
517            "row index {row} is out of bounds for AaBuf height {}",
518            self.height
519        );
520    }
521
522    /// Clear all bits to 0.
523    #[inline]
524    pub fn clear(&mut self) {
525        self.data.fill(0);
526    }
527
528    /// Set pixels `[x0, x1)` to 1 in the given row.
529    ///
530    /// `x0` and `x1` are clamped to `[0, width]`. No-op if `x0 ≥ x1`.
531    ///
532    /// # Panics
533    ///
534    /// Panics if `row >= height`.
535    pub fn set_span(&mut self, row: usize, x0: usize, x1: usize) {
536        self.assert_row_in_bounds(row);
537        let x0 = x0.min(self.width);
538        let x1 = x1.min(self.width);
539        if x0 >= x1 {
540            return;
541        }
542        let rb = self.row_bytes();
543        let base = row * rb;
544        let b0 = x0 >> 3;
545        let b1 = (x1 - 1) >> 3;
546        if b0 == b1 {
547            // Span is entirely within one byte.
548            // Left mask: top x0%8 bits clear, rest set.
549            // Right mask: top x1%8 bits set (or all bits if byte-aligned).
550            let left_mask = 0xff_u8 >> (x0 & 7);
551            let right_shift = x1 & 7;
552            let right_mask = if right_shift == 0 {
553                0xff_u8
554            } else {
555                !(0xff_u8 >> right_shift)
556            };
557            self.data[base + b0] |= left_mask & right_mask;
558        } else {
559            // Left partial byte.
560            self.data[base + b0] |= 0xff_u8 >> (x0 & 7);
561            // Full bytes in the middle.
562            for b in (b0 + 1)..b1 {
563                self.data[base + b] = 0xff;
564            }
565            // Right partial byte: set top (x1 % 8) bits; 0xff if x1 is byte-aligned.
566            let right_shift = x1 & 7;
567            let right_mask = if right_shift == 0 {
568                0xff_u8
569            } else {
570                !(0xff_u8 >> right_shift)
571            };
572            self.data[base + b1] |= right_mask;
573        }
574    }
575
576    /// Read one raw byte from the given `row` at byte index `byte_idx`.
577    ///
578    /// # Panics
579    ///
580    /// Panics if `row >= height` or `byte_idx >= row_bytes()`.
581    #[inline]
582    #[must_use]
583    pub fn get_byte(&self, row: usize, byte_idx: usize) -> u8 {
584        self.assert_row_in_bounds(row);
585        let rb = self.row_bytes();
586        assert!(
587            byte_idx < rb,
588            "byte_idx {byte_idx} is out of bounds for row_bytes {rb}"
589        );
590        self.data[row * rb + byte_idx]
591    }
592
593    /// Read-only access to a full row as a byte slice.
594    ///
595    /// # Panics
596    ///
597    /// Panics if `row >= height`.
598    #[must_use]
599    pub fn row_slice(&self, row: usize) -> &[u8] {
600        self.assert_row_in_bounds(row);
601        let rb = self.row_bytes();
602        &self.data[row * rb..(row + 1) * rb]
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609    use crate::types::Rgb8;
610
611    #[test]
612    fn bitmap_stride_alignment() {
613        let bm = Bitmap::<Rgb8>::new(7, 3, 4, false);
614        // 7 * 3 = 21 bytes, next multiple of 4 = 24
615        assert_eq!(bm.stride, 24);
616    }
617
618    #[test]
619    fn bitmap_row_len() {
620        let bm = Bitmap::<Rgb8>::new(5, 2, 1, false);
621        assert_eq!(bm.row(0).len(), 5);
622        assert_eq!(bm.row(1).len(), 5);
623    }
624
625    #[test]
626    fn bitmap_clear() {
627        let mut bm = Bitmap::<Rgb8>::new(4, 2, 1, true);
628        bm.clear(
629            Rgb8 {
630                r: 255,
631                g: 0,
632                b: 128,
633            },
634            200,
635        );
636        for y in 0..2u32 {
637            for px in bm.row(y) {
638                assert_eq!(
639                    *px,
640                    Rgb8 {
641                        r: 255,
642                        g: 0,
643                        b: 128
644                    }
645                );
646            }
647            for &a in bm.alpha_row(y).unwrap() {
648                assert_eq!(a, 200);
649            }
650        }
651    }
652
653    #[test]
654    fn aabuf_set_span_single_byte() {
655        let mut buf = AaBuf::new(4); // width = 16, row_bytes = 2
656        buf.set_span(0, 2, 6);
657        // pixels 2..6 → bits 2,3,4,5 → byte 0: 0b00111100 = 0x3c
658        assert_eq!(buf.get_byte(0, 0), 0x3c);
659        assert_eq!(buf.get_byte(0, 1), 0x00);
660    }
661
662    #[test]
663    fn aabuf_set_span_cross_byte() {
664        let mut buf = AaBuf::new(4); // width = 16
665        buf.set_span(0, 6, 10);
666        // pixels 6..10: byte0 bits 6,7 = 0x03; byte1 bits 0,1 = 0xc0
667        assert_eq!(buf.get_byte(0, 0), 0x03);
668        assert_eq!(buf.get_byte(0, 1), 0xc0);
669    }
670
671    #[test]
672    fn aabuf_clear() {
673        let mut buf = AaBuf::new(4);
674        buf.set_span(0, 0, 16);
675        buf.clear();
676        for i in 0..buf.row_bytes() {
677            assert_eq!(buf.get_byte(0, i), 0);
678        }
679    }
680}