Skip to main content

agg_rust/
image_accessors.rs

1//! Image pixel access with boundary handling modes.
2//!
3//! Port of `agg_image_accessors.h` — provides pixel access to image buffers
4//! with different boundary handling: clip (background color), no-clip,
5//! clone (clamp to edge), and wrap (tiling).
6//!
7//! Also includes 6 wrap mode structs for coordinate transformation.
8
9use crate::rendering_buffer::RowAccessor;
10
11// ============================================================================
12// ImageSource trait
13// ============================================================================
14
15/// Trait for image pixel sources used by span image filters.
16///
17/// All image accessor types implement this trait, providing a common
18/// interface for the span generators to read pixels.
19pub trait ImageSource {
20    /// Begin reading a span at (x, y) with `len` pixels. Returns first pixel.
21    fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8];
22
23    /// Advance to the next pixel in the x direction.
24    fn next_x(&mut self) -> &[u8];
25
26    /// Advance to the next row (y+1), resetting x to the span start.
27    fn next_y(&mut self) -> &[u8];
28}
29
30impl<const PIX_WIDTH: usize> ImageSource for ImageAccessorClip<'_, PIX_WIDTH> {
31    fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8] {
32        self.span(x, y, len)
33    }
34    fn next_x(&mut self) -> &[u8] {
35        self.next_x()
36    }
37    fn next_y(&mut self) -> &[u8] {
38        self.next_y()
39    }
40}
41
42impl<const PIX_WIDTH: usize> ImageSource for ImageAccessorNoClip<'_, PIX_WIDTH> {
43    fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8] {
44        self.span(x, y, len)
45    }
46    fn next_x(&mut self) -> &[u8] {
47        self.next_x()
48    }
49    fn next_y(&mut self) -> &[u8] {
50        self.next_y()
51    }
52}
53
54impl<const PIX_WIDTH: usize> ImageSource for ImageAccessorClone<'_, PIX_WIDTH> {
55    fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8] {
56        self.span(x, y, len)
57    }
58    fn next_x(&mut self) -> &[u8] {
59        self.next_x()
60    }
61    fn next_y(&mut self) -> &[u8] {
62        self.next_y()
63    }
64}
65
66impl<const PIX_WIDTH: usize, WX: WrapMode, WY: WrapMode> ImageSource
67    for ImageAccessorWrap<'_, PIX_WIDTH, WX, WY>
68{
69    fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8] {
70        self.span(x, y, len)
71    }
72    fn next_x(&mut self) -> &[u8] {
73        self.next_x()
74    }
75    fn next_y(&mut self) -> &[u8] {
76        self.next_y()
77    }
78}
79
80// ============================================================================
81// WrapMode trait
82// ============================================================================
83
84/// Coordinate wrapping mode for tiled image access.
85pub trait WrapMode {
86    /// Create a wrap mode for the given image dimension.
87    fn new(size: u32) -> Self;
88
89    /// Map a coordinate to wrapped output, storing internal state.
90    fn func(&mut self, v: i32) -> u32;
91
92    /// Increment the internal state, returning the next wrapped coordinate.
93    fn inc(&mut self) -> u32;
94}
95
96// ============================================================================
97// WrapModeRepeat — modulo wrapping
98// ============================================================================
99
100/// Repeat (modulo) wrapping for tiling.
101///
102/// Port of C++ `wrap_mode_repeat`.
103pub struct WrapModeRepeat {
104    size: u32,
105    add: u32,
106    value: u32,
107}
108
109impl WrapMode for WrapModeRepeat {
110    fn new(size: u32) -> Self {
111        Self {
112            size,
113            add: size.wrapping_mul(0x3FFF_FFFF / size),
114            value: 0,
115        }
116    }
117
118    #[inline]
119    fn func(&mut self, v: i32) -> u32 {
120        self.value = (v as u32).wrapping_add(self.add) % self.size;
121        self.value
122    }
123
124    #[inline]
125    fn inc(&mut self) -> u32 {
126        self.value += 1;
127        if self.value >= self.size {
128            self.value = 0;
129        }
130        self.value
131    }
132}
133
134// ============================================================================
135// WrapModeRepeatPow2 — fast bitwise repeat for power-of-2 sizes
136// ============================================================================
137
138/// Power-of-2 repeat wrapping using bitwise AND.
139///
140/// Port of C++ `wrap_mode_repeat_pow2`.
141pub struct WrapModeRepeatPow2 {
142    mask: u32,
143    value: u32,
144}
145
146impl WrapMode for WrapModeRepeatPow2 {
147    fn new(size: u32) -> Self {
148        let mut mask = 1u32;
149        while mask < size {
150            mask = (mask << 1) | 1;
151        }
152        mask >>= 1;
153        Self { mask, value: 0 }
154    }
155
156    #[inline]
157    fn func(&mut self, v: i32) -> u32 {
158        self.value = v as u32 & self.mask;
159        self.value
160    }
161
162    #[inline]
163    fn inc(&mut self) -> u32 {
164        self.value += 1;
165        if self.value > self.mask {
166            self.value = 0;
167        }
168        self.value
169    }
170}
171
172// ============================================================================
173// WrapModeRepeatAutoPow2 — auto-detect pow2 vs modulo
174// ============================================================================
175
176/// Auto-detecting repeat: uses bitwise AND for power-of-2 sizes, modulo otherwise.
177///
178/// Port of C++ `wrap_mode_repeat_auto_pow2`.
179pub struct WrapModeRepeatAutoPow2 {
180    size: u32,
181    add: u32,
182    mask: u32,
183    value: u32,
184}
185
186impl WrapMode for WrapModeRepeatAutoPow2 {
187    fn new(size: u32) -> Self {
188        let mask = if size & (size - 1) == 0 { size - 1 } else { 0 };
189        Self {
190            size,
191            add: size.wrapping_mul(0x3FFF_FFFF / size),
192            mask,
193            value: 0,
194        }
195    }
196
197    #[inline]
198    fn func(&mut self, v: i32) -> u32 {
199        if self.mask != 0 {
200            self.value = v as u32 & self.mask;
201        } else {
202            self.value = (v as u32).wrapping_add(self.add) % self.size;
203        }
204        self.value
205    }
206
207    #[inline]
208    fn inc(&mut self) -> u32 {
209        self.value += 1;
210        if self.value >= self.size {
211            self.value = 0;
212        }
213        self.value
214    }
215}
216
217// ============================================================================
218// WrapModeReflect — mirror reflection
219// ============================================================================
220
221/// Reflect (mirror) wrapping for tiling.
222///
223/// Port of C++ `wrap_mode_reflect`.
224pub struct WrapModeReflect {
225    size: u32,
226    size2: u32,
227    add: u32,
228    value: u32,
229}
230
231impl WrapMode for WrapModeReflect {
232    fn new(size: u32) -> Self {
233        let size2 = size * 2;
234        Self {
235            size,
236            size2,
237            add: size2.wrapping_mul(0x3FFF_FFFF / size2),
238            value: 0,
239        }
240    }
241
242    #[inline]
243    fn func(&mut self, v: i32) -> u32 {
244        self.value = (v as u32).wrapping_add(self.add) % self.size2;
245        if self.value >= self.size {
246            self.size2 - self.value - 1
247        } else {
248            self.value
249        }
250    }
251
252    #[inline]
253    fn inc(&mut self) -> u32 {
254        self.value += 1;
255        if self.value >= self.size2 {
256            self.value = 0;
257        }
258        if self.value >= self.size {
259            self.size2 - self.value - 1
260        } else {
261            self.value
262        }
263    }
264}
265
266// ============================================================================
267// WrapModeReflectPow2 — fast power-of-2 reflection
268// ============================================================================
269
270/// Power-of-2 reflect wrapping using bitwise operations.
271///
272/// Port of C++ `wrap_mode_reflect_pow2`.
273pub struct WrapModeReflectPow2 {
274    size: u32,
275    mask: u32,
276    value: u32,
277}
278
279impl WrapMode for WrapModeReflectPow2 {
280    fn new(size: u32) -> Self {
281        let mut mask = 1u32;
282        let mut sz = 1u32;
283        while mask < size {
284            mask = (mask << 1) | 1;
285            sz <<= 1;
286        }
287        Self {
288            size: sz,
289            mask,
290            value: 0,
291        }
292    }
293
294    #[inline]
295    fn func(&mut self, v: i32) -> u32 {
296        self.value = v as u32 & self.mask;
297        if self.value >= self.size {
298            self.mask - self.value
299        } else {
300            self.value
301        }
302    }
303
304    #[inline]
305    fn inc(&mut self) -> u32 {
306        self.value += 1;
307        self.value &= self.mask;
308        if self.value >= self.size {
309            self.mask - self.value
310        } else {
311            self.value
312        }
313    }
314}
315
316// ============================================================================
317// WrapModeReflectAutoPow2 — auto-detect pow2 vs general reflection
318// ============================================================================
319
320/// Auto-detecting reflect: uses bitwise for power-of-2 sizes, modulo otherwise.
321///
322/// Port of C++ `wrap_mode_reflect_auto_pow2`.
323pub struct WrapModeReflectAutoPow2 {
324    size: u32,
325    size2: u32,
326    add: u32,
327    mask: u32,
328    value: u32,
329}
330
331impl WrapMode for WrapModeReflectAutoPow2 {
332    fn new(size: u32) -> Self {
333        let size2 = size * 2;
334        let mask = if size2 & (size2 - 1) == 0 {
335            size2 - 1
336        } else {
337            0
338        };
339        Self {
340            size,
341            size2,
342            add: size2.wrapping_mul(0x3FFF_FFFF / size2),
343            mask,
344            value: 0,
345        }
346    }
347
348    #[inline]
349    fn func(&mut self, v: i32) -> u32 {
350        self.value = if self.mask != 0 {
351            v as u32 & self.mask
352        } else {
353            (v as u32).wrapping_add(self.add) % self.size2
354        };
355        if self.value >= self.size {
356            self.size2 - self.value - 1
357        } else {
358            self.value
359        }
360    }
361
362    #[inline]
363    fn inc(&mut self) -> u32 {
364        self.value += 1;
365        if self.value >= self.size2 {
366            self.value = 0;
367        }
368        if self.value >= self.size {
369            self.size2 - self.value - 1
370        } else {
371            self.value
372        }
373    }
374}
375
376// ============================================================================
377// ImageAccessorClip — returns background color for out-of-bounds
378// ============================================================================
379
380/// Image accessor with clipping: returns a background color for out-of-bounds pixels.
381///
382/// Port of C++ `image_accessor_clip<PixFmt>`.
383pub struct ImageAccessorClip<'a, const PIX_WIDTH: usize> {
384    rbuf: &'a RowAccessor,
385    bk_buf: [u8; 8], // background color buffer (max 8 bytes per pixel)
386    x: i32,
387    x0: i32,
388    y: i32,
389    fast_path: bool,
390    pix_off: usize,
391}
392
393impl<'a, const PIX_WIDTH: usize> ImageAccessorClip<'a, PIX_WIDTH> {
394    pub fn new(rbuf: &'a RowAccessor, bk_color: &[u8]) -> Self {
395        let mut bk_buf = [0u8; 8];
396        let len = bk_color.len().min(8);
397        bk_buf[..len].copy_from_slice(&bk_color[..len]);
398        Self {
399            rbuf,
400            bk_buf,
401            x: 0,
402            x0: 0,
403            y: 0,
404            fast_path: false,
405            pix_off: 0,
406        }
407    }
408
409    pub fn set_background(&mut self, bk_color: &[u8]) {
410        let len = bk_color.len().min(8);
411        self.bk_buf[..len].copy_from_slice(&bk_color[..len]);
412    }
413
414    fn pixel(&self) -> &[u8] {
415        if self.y >= 0
416            && self.y < self.rbuf.height() as i32
417            && self.x >= 0
418            && self.x < self.rbuf.width() as i32
419        {
420            let row = self.rbuf.row_slice(self.y as u32);
421            let off = self.x as usize * PIX_WIDTH;
422            &row[off..off + PIX_WIDTH]
423        } else {
424            &self.bk_buf[..PIX_WIDTH]
425        }
426    }
427
428    pub fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8] {
429        self.x = x;
430        self.x0 = x;
431        self.y = y;
432        if y >= 0
433            && y < self.rbuf.height() as i32
434            && x >= 0
435            && (x + len as i32) <= self.rbuf.width() as i32
436        {
437            self.fast_path = true;
438            self.pix_off = x as usize * PIX_WIDTH;
439            let row = self.rbuf.row_slice(y as u32);
440            &row[self.pix_off..self.pix_off + PIX_WIDTH]
441        } else {
442            self.fast_path = false;
443            self.pixel()
444        }
445    }
446
447    pub fn next_x(&mut self) -> &[u8] {
448        if self.fast_path {
449            self.pix_off += PIX_WIDTH;
450            let row = self.rbuf.row_slice(self.y as u32);
451            &row[self.pix_off..self.pix_off + PIX_WIDTH]
452        } else {
453            self.x += 1;
454            self.pixel()
455        }
456    }
457
458    pub fn next_y(&mut self) -> &[u8] {
459        self.y += 1;
460        self.x = self.x0;
461        if self.fast_path && self.y >= 0 && self.y < self.rbuf.height() as i32 {
462            self.pix_off = self.x as usize * PIX_WIDTH;
463            let row = self.rbuf.row_slice(self.y as u32);
464            &row[self.pix_off..self.pix_off + PIX_WIDTH]
465        } else {
466            self.fast_path = false;
467            self.pixel()
468        }
469    }
470}
471
472// ============================================================================
473// ImageAccessorNoClip — unchecked access
474// ============================================================================
475
476/// Image accessor without bounds checking — fastest, assumes all coordinates are valid.
477///
478/// Port of C++ `image_accessor_no_clip<PixFmt>`.
479pub struct ImageAccessorNoClip<'a, const PIX_WIDTH: usize> {
480    rbuf: &'a RowAccessor,
481    x: i32,
482    y: i32,
483    pix_off: usize,
484}
485
486impl<'a, const PIX_WIDTH: usize> ImageAccessorNoClip<'a, PIX_WIDTH> {
487    pub fn new(rbuf: &'a RowAccessor) -> Self {
488        Self {
489            rbuf,
490            x: 0,
491            y: 0,
492            pix_off: 0,
493        }
494    }
495
496    pub fn span(&mut self, x: i32, y: i32, _len: u32) -> &[u8] {
497        self.x = x;
498        self.y = y;
499        self.pix_off = x as usize * PIX_WIDTH;
500        let row = self.rbuf.row_slice(y as u32);
501        &row[self.pix_off..self.pix_off + PIX_WIDTH]
502    }
503
504    pub fn next_x(&mut self) -> &[u8] {
505        self.pix_off += PIX_WIDTH;
506        let row = self.rbuf.row_slice(self.y as u32);
507        &row[self.pix_off..self.pix_off + PIX_WIDTH]
508    }
509
510    pub fn next_y(&mut self) -> &[u8] {
511        self.y += 1;
512        self.pix_off = self.x as usize * PIX_WIDTH;
513        let row = self.rbuf.row_slice(self.y as u32);
514        &row[self.pix_off..self.pix_off + PIX_WIDTH]
515    }
516}
517
518// ============================================================================
519// ImageAccessorClone — clamp to edge pixels
520// ============================================================================
521
522/// Image accessor with clamping: out-of-bounds coordinates snap to edge pixels.
523///
524/// Port of C++ `image_accessor_clone<PixFmt>`.
525pub struct ImageAccessorClone<'a, const PIX_WIDTH: usize> {
526    rbuf: &'a RowAccessor,
527    x: i32,
528    x0: i32,
529    y: i32,
530    /// Cached pointer to the current pixel when on the in-bounds fast path,
531    /// or null when a coordinate is clamped (mirrors C++ `m_pix_ptr`). This
532    /// lets `next_x` advance by a raw pointer add instead of reconstructing a
533    /// full-row slice with a bounds-checked subslice on every pixel.
534    pix_ptr: *const u8,
535}
536
537impl<'a, const PIX_WIDTH: usize> ImageAccessorClone<'a, PIX_WIDTH> {
538    pub fn new(rbuf: &'a RowAccessor) -> Self {
539        Self {
540            rbuf,
541            x: 0,
542            x0: 0,
543            y: 0,
544            pix_ptr: std::ptr::null(),
545        }
546    }
547
548    fn pixel(&self) -> &[u8] {
549        let cx = self.x.max(0).min(self.rbuf.width() as i32 - 1);
550        let cy = self.y.max(0).min(self.rbuf.height() as i32 - 1);
551        let row = self.rbuf.row_slice(cy as u32);
552        let off = cx as usize * PIX_WIDTH;
553        &row[off..off + PIX_WIDTH]
554    }
555
556    /// Build a `PIX_WIDTH`-length slice from the cached pixel pointer.
557    ///
558    /// # Safety
559    /// `self.pix_ptr` must be non-null and point at `PIX_WIDTH` valid,
560    /// initialized bytes within the attached buffer.
561    #[inline]
562    unsafe fn pix_slice(&self) -> &[u8] {
563        std::slice::from_raw_parts(self.pix_ptr, PIX_WIDTH)
564    }
565
566    pub fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8] {
567        self.x = x;
568        self.x0 = x;
569        self.y = y;
570        if y >= 0
571            && y < self.rbuf.height() as i32
572            && x >= 0
573            && (x + len as i32) <= self.rbuf.width() as i32
574        {
575            // SAFETY: y is in [0, height); x*PIX_WIDTH is a valid byte offset
576            // within the row because x + len <= width and len >= 1.
577            unsafe {
578                self.pix_ptr = self.rbuf.row_ptr(y).add(x as usize * PIX_WIDTH);
579                self.pix_slice()
580            }
581        } else {
582            self.pix_ptr = std::ptr::null();
583            self.pixel()
584        }
585    }
586
587    pub fn next_x(&mut self) -> &[u8] {
588        if !self.pix_ptr.is_null() {
589            // SAFETY: span() validated `x + len <= width`, so advancing the
590            // cached pointer stays within the row for the requested span.
591            unsafe {
592                self.pix_ptr = self.pix_ptr.add(PIX_WIDTH);
593                self.pix_slice()
594            }
595        } else {
596            self.x += 1;
597            self.pixel()
598        }
599    }
600
601    pub fn next_y(&mut self) -> &[u8] {
602        self.y += 1;
603        self.x = self.x0;
604        if !self.pix_ptr.is_null() && self.y >= 0 && self.y < self.rbuf.height() as i32 {
605            // SAFETY: y is in [0, height) and x0 was validated in-bounds by span().
606            unsafe {
607                self.pix_ptr = self.rbuf.row_ptr(self.y).add(self.x as usize * PIX_WIDTH);
608                self.pix_slice()
609            }
610        } else {
611            self.pix_ptr = std::ptr::null();
612            self.pixel()
613        }
614    }
615}
616
617// ============================================================================
618// ImageAccessorWrap — tiling modes
619// ============================================================================
620
621/// Image accessor with tiling: wraps coordinates using WrapMode policies.
622///
623/// Port of C++ `image_accessor_wrap<PixFmt, WrapX, WrapY>`.
624pub struct ImageAccessorWrap<'a, const PIX_WIDTH: usize, WX: WrapMode, WY: WrapMode> {
625    rbuf: &'a RowAccessor,
626    x: i32,
627    wrap_x: WX,
628    wrap_y: WY,
629    row_y: u32,
630}
631
632impl<'a, const PIX_WIDTH: usize, WX: WrapMode, WY: WrapMode>
633    ImageAccessorWrap<'a, PIX_WIDTH, WX, WY>
634{
635    pub fn new(rbuf: &'a RowAccessor) -> Self {
636        Self {
637            rbuf,
638            x: 0,
639            wrap_x: WX::new(rbuf.width()),
640            wrap_y: WY::new(rbuf.height()),
641            row_y: 0,
642        }
643    }
644
645    pub fn span(&mut self, x: i32, y: i32, _len: u32) -> &[u8] {
646        self.x = x;
647        self.row_y = self.wrap_y.func(y);
648        let wx = self.wrap_x.func(x) as usize * PIX_WIDTH;
649        let row = self.rbuf.row_slice(self.row_y);
650        &row[wx..wx + PIX_WIDTH]
651    }
652
653    pub fn next_x(&mut self) -> &[u8] {
654        let wx = self.wrap_x.inc() as usize * PIX_WIDTH;
655        let row = self.rbuf.row_slice(self.row_y);
656        &row[wx..wx + PIX_WIDTH]
657    }
658
659    pub fn next_y(&mut self) -> &[u8] {
660        self.row_y = self.wrap_y.inc();
661        let wx = self.wrap_x.func(self.x) as usize * PIX_WIDTH;
662        let row = self.rbuf.row_slice(self.row_y);
663        &row[wx..wx + PIX_WIDTH]
664    }
665}
666
667// ============================================================================
668// Tests
669// ============================================================================
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674
675    fn make_rgba_buffer(width: u32, height: u32, data: &mut Vec<u8>) -> RowAccessor {
676        let stride = width as usize * 4;
677        data.resize(stride * height as usize, 0);
678        unsafe { RowAccessor::new_with_buf(data.as_mut_ptr(), width, height, stride as i32) }
679    }
680
681    fn set_rgba_pixel(data: &mut [u8], width: u32, x: u32, y: u32, rgba: [u8; 4]) {
682        let off = (y * width * 4 + x * 4) as usize;
683        data[off..off + 4].copy_from_slice(&rgba);
684    }
685
686    // -- WrapMode tests --
687
688    #[test]
689    fn test_wrap_repeat() {
690        let mut w = WrapModeRepeat::new(4);
691        assert_eq!(w.func(0), 0);
692        assert_eq!(w.func(3), 3);
693        assert_eq!(w.func(4), 0);
694        assert_eq!(w.func(5), 1);
695        assert_eq!(w.func(-1), 3);
696    }
697
698    #[test]
699    fn test_wrap_repeat_inc() {
700        let mut w = WrapModeRepeat::new(3);
701        w.func(0);
702        assert_eq!(w.inc(), 1);
703        assert_eq!(w.inc(), 2);
704        assert_eq!(w.inc(), 0); // wraps
705    }
706
707    #[test]
708    fn test_wrap_repeat_pow2() {
709        let mut w = WrapModeRepeatPow2::new(4); // mask=3
710        assert_eq!(w.func(0), 0);
711        assert_eq!(w.func(3), 3);
712        assert_eq!(w.func(4), 0);
713        assert_eq!(w.func(7), 3);
714    }
715
716    #[test]
717    fn test_wrap_reflect() {
718        let mut w = WrapModeReflect::new(4); // size=4, size2=8
719        assert_eq!(w.func(0), 0);
720        assert_eq!(w.func(3), 3);
721        assert_eq!(w.func(4), 3); // reflected
722        assert_eq!(w.func(7), 0); // reflected
723    }
724
725    #[test]
726    fn test_wrap_reflect_inc() {
727        let mut w = WrapModeReflect::new(3); // size=3, size2=6
728        w.func(0);
729        assert_eq!(w.inc(), 1);
730        assert_eq!(w.inc(), 2);
731        assert_eq!(w.inc(), 2); // reflected: 6-3-1=2
732        assert_eq!(w.inc(), 1); // 6-4-1=1
733        assert_eq!(w.inc(), 0); // 6-5-1=0
734        assert_eq!(w.inc(), 0); // wraps to 0
735    }
736
737    #[test]
738    fn test_wrap_repeat_auto_pow2() {
739        // Power-of-2 size: should use mask
740        let mut w = WrapModeRepeatAutoPow2::new(8);
741        assert_eq!(w.func(8), 0);
742        assert_eq!(w.func(9), 1);
743
744        // Non-power-of-2: should use modulo
745        let mut w2 = WrapModeRepeatAutoPow2::new(5);
746        assert_eq!(w2.func(5), 0);
747        assert_eq!(w2.func(7), 2);
748    }
749
750    #[test]
751    fn test_wrap_reflect_pow2() {
752        let mut w = WrapModeReflectPow2::new(4);
753        assert_eq!(w.func(0), 0);
754        assert_eq!(w.func(3), 3);
755        assert_eq!(w.func(4), 3); // reflected
756        assert_eq!(w.func(7), 0); // reflected
757    }
758
759    // -- ImageAccessorClip tests --
760
761    #[test]
762    fn test_clip_in_bounds() {
763        let mut data = Vec::new();
764        let rbuf = make_rgba_buffer(4, 4, &mut data);
765        set_rgba_pixel(&mut data, 4, 1, 1, [10, 20, 30, 40]);
766
767        let mut acc = ImageAccessorClip::<4>::new(&rbuf, &[0, 0, 0, 0]);
768        let pix = acc.span(1, 1, 1);
769        assert_eq!(&pix[..4], &[10, 20, 30, 40]);
770    }
771
772    #[test]
773    fn test_clip_out_of_bounds() {
774        let mut data = Vec::new();
775        let rbuf = make_rgba_buffer(4, 4, &mut data);
776
777        let mut acc = ImageAccessorClip::<4>::new(&rbuf, &[99, 88, 77, 66]);
778        let pix = acc.span(-1, 0, 1);
779        assert_eq!(&pix[..4], &[99, 88, 77, 66]);
780    }
781
782    #[test]
783    fn test_clip_span_fast_path() {
784        let mut data = Vec::new();
785        let rbuf = make_rgba_buffer(4, 4, &mut data);
786        set_rgba_pixel(&mut data, 4, 0, 0, [1, 2, 3, 4]);
787        set_rgba_pixel(&mut data, 4, 1, 0, [5, 6, 7, 8]);
788
789        let mut acc = ImageAccessorClip::<4>::new(&rbuf, &[0, 0, 0, 0]);
790        let pix = acc.span(0, 0, 2);
791        assert_eq!(&pix[..4], &[1, 2, 3, 4]);
792        let pix = acc.next_x();
793        assert_eq!(&pix[..4], &[5, 6, 7, 8]);
794    }
795
796    // -- ImageAccessorNoClip tests --
797
798    #[test]
799    fn test_no_clip_span() {
800        let mut data = Vec::new();
801        let rbuf = make_rgba_buffer(4, 4, &mut data);
802        set_rgba_pixel(&mut data, 4, 2, 1, [11, 22, 33, 44]);
803
804        let mut acc = ImageAccessorNoClip::<4>::new(&rbuf);
805        let pix = acc.span(2, 1, 1);
806        assert_eq!(&pix[..4], &[11, 22, 33, 44]);
807    }
808
809    #[test]
810    fn test_no_clip_next_x() {
811        let mut data = Vec::new();
812        let rbuf = make_rgba_buffer(4, 4, &mut data);
813        set_rgba_pixel(&mut data, 4, 0, 0, [10, 0, 0, 0]);
814        set_rgba_pixel(&mut data, 4, 1, 0, [20, 0, 0, 0]);
815        set_rgba_pixel(&mut data, 4, 2, 0, [30, 0, 0, 0]);
816
817        let mut acc = ImageAccessorNoClip::<4>::new(&rbuf);
818        acc.span(0, 0, 3);
819        let p1 = acc.next_x();
820        assert_eq!(p1[0], 20);
821        let p2 = acc.next_x();
822        assert_eq!(p2[0], 30);
823    }
824
825    // -- ImageAccessorClone tests --
826
827    #[test]
828    fn test_clone_in_bounds() {
829        let mut data = Vec::new();
830        let rbuf = make_rgba_buffer(4, 4, &mut data);
831        set_rgba_pixel(&mut data, 4, 1, 1, [50, 60, 70, 80]);
832
833        let mut acc = ImageAccessorClone::<4>::new(&rbuf);
834        let pix = acc.span(1, 1, 1);
835        assert_eq!(&pix[..4], &[50, 60, 70, 80]);
836    }
837
838    #[test]
839    fn test_clone_clamps_negative() {
840        let mut data = Vec::new();
841        let rbuf = make_rgba_buffer(4, 4, &mut data);
842        set_rgba_pixel(&mut data, 4, 0, 0, [10, 20, 30, 40]);
843
844        let mut acc = ImageAccessorClone::<4>::new(&rbuf);
845        let pix = acc.span(-5, -3, 1);
846        // Should clamp to (0, 0)
847        assert_eq!(&pix[..4], &[10, 20, 30, 40]);
848    }
849
850    #[test]
851    fn test_clone_clamps_overflow() {
852        let mut data = Vec::new();
853        let rbuf = make_rgba_buffer(4, 4, &mut data);
854        set_rgba_pixel(&mut data, 4, 3, 3, [99, 88, 77, 66]);
855
856        let mut acc = ImageAccessorClone::<4>::new(&rbuf);
857        let pix = acc.span(100, 100, 1);
858        // Should clamp to (3, 3)
859        assert_eq!(&pix[..4], &[99, 88, 77, 66]);
860    }
861
862    #[test]
863    fn test_clone_fast_path_border_walk() {
864        // Exercise the cached-pointer fast path (span/next_x/next_y) at the
865        // exact right/bottom border and off the edge, asserting each fetch
866        // matches the clamping `pixel()` path. The other clone tests only use
867        // len=1 and never advance along the fast path, so this guards the
868        // unsafe raw-pointer arithmetic that replaced the per-call row slice.
869        let mut data = Vec::new();
870        let rbuf = make_rgba_buffer(4, 4, &mut data);
871        for y in 0..4u32 {
872            for x in 0..4u32 {
873                set_rgba_pixel(&mut data, 4, x, y, [x as u8, y as u8, (x * 4 + y) as u8, 255]);
874            }
875        }
876
877        // Oracle: fetch the clamped pixel at (x, y) via the production slow
878        // path. A huge `len` fails the fast-path bounds check, forcing span()
879        // into the clamping `pixel()` branch, so this reuses the real clamping
880        // logic rather than duplicating it.
881        let clamp_pixel = |x: i32, y: i32| -> [u8; 4] {
882            let mut oracle = ImageAccessorClone::<4>::new(&rbuf);
883            let p = oracle.span(x, y, 1_000_000);
884            [p[0], p[1], p[2], p[3]]
885        };
886
887        // Fast-path span flush against the right border: x + len == width
888        // (2 + 2 == 4) and y == height - 1 (3). span() takes the fast path.
889        let mut acc = ImageAccessorClone::<4>::new(&rbuf);
890        let p = acc.span(2, 3, 2);
891        assert_eq!(&p[..4], &clamp_pixel(2, 3), "span at right/bottom border");
892
893        // Fast-path next_x pointer advance to the last valid column (3, 3).
894        let p = acc.next_x();
895        assert_eq!(&p[..4], &clamp_pixel(3, 3), "next_x fast-path advance");
896
897        // next_y steps off the bottom edge (y -> 4): must drop off the fast
898        // path and clamp, returning x0 = 2 clamped to (2, 3).
899        let p = acc.next_y();
900        assert_eq!(&p[..4], &clamp_pixel(2, 4), "next_y off bottom edge clamps");
901
902        // next_x after leaving the fast path advances logical x and clamps
903        // (3, 4) -> (3, 3).
904        let p = acc.next_x();
905        assert_eq!(&p[..4], &clamp_pixel(3, 4), "next_x after clamp fallback");
906
907        // Separate fully in-bounds walk exercising several consecutive
908        // fast-path next_x pointer advances across a row.
909        let mut acc2 = ImageAccessorClone::<4>::new(&rbuf);
910        let p = acc2.span(0, 1, 4);
911        assert_eq!(&p[..4], &clamp_pixel(0, 1));
912        for x in 1..4 {
913            let p = acc2.next_x();
914            assert_eq!(&p[..4], &clamp_pixel(x, 1), "in-bounds fast-path next_x");
915        }
916    }
917
918    // -- ImageAccessorWrap tests --
919
920    #[test]
921    fn test_wrap_repeat_access() {
922        let mut data = Vec::new();
923        let rbuf = make_rgba_buffer(2, 2, &mut data);
924        set_rgba_pixel(&mut data, 2, 0, 0, [10, 0, 0, 0]);
925        set_rgba_pixel(&mut data, 2, 1, 0, [20, 0, 0, 0]);
926        set_rgba_pixel(&mut data, 2, 0, 1, [30, 0, 0, 0]);
927        set_rgba_pixel(&mut data, 2, 1, 1, [40, 0, 0, 0]);
928
929        let mut acc = ImageAccessorWrap::<4, WrapModeRepeat, WrapModeRepeat>::new(&rbuf);
930        // x=2, y=0 should wrap to x=0, y=0
931        let pix = acc.span(2, 0, 1);
932        assert_eq!(pix[0], 10);
933        // next_x → x=3 wraps to x=1
934        let pix = acc.next_x();
935        assert_eq!(pix[0], 20);
936    }
937
938    #[test]
939    fn test_wrap_next_y() {
940        let mut data = Vec::new();
941        let rbuf = make_rgba_buffer(2, 2, &mut data);
942        set_rgba_pixel(&mut data, 2, 0, 0, [10, 0, 0, 0]);
943        set_rgba_pixel(&mut data, 2, 0, 1, [30, 0, 0, 0]);
944
945        let mut acc = ImageAccessorWrap::<4, WrapModeRepeat, WrapModeRepeat>::new(&rbuf);
946        acc.span(0, 0, 1);
947        let pix = acc.next_y();
948        assert_eq!(pix[0], 30);
949    }
950}