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}