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    fast_path: bool,
531    pix_off: usize,
532}
533
534impl<'a, const PIX_WIDTH: usize> ImageAccessorClone<'a, PIX_WIDTH> {
535    pub fn new(rbuf: &'a RowAccessor) -> Self {
536        Self {
537            rbuf,
538            x: 0,
539            x0: 0,
540            y: 0,
541            fast_path: false,
542            pix_off: 0,
543        }
544    }
545
546    fn pixel(&self) -> &[u8] {
547        let cx = self.x.max(0).min(self.rbuf.width() as i32 - 1);
548        let cy = self.y.max(0).min(self.rbuf.height() as i32 - 1);
549        let row = self.rbuf.row_slice(cy as u32);
550        let off = cx as usize * PIX_WIDTH;
551        &row[off..off + PIX_WIDTH]
552    }
553
554    pub fn span(&mut self, x: i32, y: i32, len: u32) -> &[u8] {
555        self.x = x;
556        self.x0 = x;
557        self.y = y;
558        if y >= 0
559            && y < self.rbuf.height() as i32
560            && x >= 0
561            && (x + len as i32) <= self.rbuf.width() as i32
562        {
563            self.fast_path = true;
564            self.pix_off = x as usize * PIX_WIDTH;
565            let row = self.rbuf.row_slice(y as u32);
566            &row[self.pix_off..self.pix_off + PIX_WIDTH]
567        } else {
568            self.fast_path = false;
569            self.pixel()
570        }
571    }
572
573    pub fn next_x(&mut self) -> &[u8] {
574        if self.fast_path {
575            self.pix_off += PIX_WIDTH;
576            let row = self.rbuf.row_slice(self.y as u32);
577            &row[self.pix_off..self.pix_off + PIX_WIDTH]
578        } else {
579            self.x += 1;
580            self.pixel()
581        }
582    }
583
584    pub fn next_y(&mut self) -> &[u8] {
585        self.y += 1;
586        self.x = self.x0;
587        if self.fast_path && self.y >= 0 && self.y < self.rbuf.height() as i32 {
588            self.pix_off = self.x as usize * PIX_WIDTH;
589            let row = self.rbuf.row_slice(self.y as u32);
590            &row[self.pix_off..self.pix_off + PIX_WIDTH]
591        } else {
592            self.fast_path = false;
593            self.pixel()
594        }
595    }
596}
597
598// ============================================================================
599// ImageAccessorWrap — tiling modes
600// ============================================================================
601
602/// Image accessor with tiling: wraps coordinates using WrapMode policies.
603///
604/// Port of C++ `image_accessor_wrap<PixFmt, WrapX, WrapY>`.
605pub struct ImageAccessorWrap<'a, const PIX_WIDTH: usize, WX: WrapMode, WY: WrapMode> {
606    rbuf: &'a RowAccessor,
607    x: i32,
608    wrap_x: WX,
609    wrap_y: WY,
610    row_y: u32,
611}
612
613impl<'a, const PIX_WIDTH: usize, WX: WrapMode, WY: WrapMode>
614    ImageAccessorWrap<'a, PIX_WIDTH, WX, WY>
615{
616    pub fn new(rbuf: &'a RowAccessor) -> Self {
617        Self {
618            rbuf,
619            x: 0,
620            wrap_x: WX::new(rbuf.width()),
621            wrap_y: WY::new(rbuf.height()),
622            row_y: 0,
623        }
624    }
625
626    pub fn span(&mut self, x: i32, y: i32, _len: u32) -> &[u8] {
627        self.x = x;
628        self.row_y = self.wrap_y.func(y);
629        let wx = self.wrap_x.func(x) as usize * PIX_WIDTH;
630        let row = self.rbuf.row_slice(self.row_y);
631        &row[wx..wx + PIX_WIDTH]
632    }
633
634    pub fn next_x(&mut self) -> &[u8] {
635        let wx = self.wrap_x.inc() as usize * PIX_WIDTH;
636        let row = self.rbuf.row_slice(self.row_y);
637        &row[wx..wx + PIX_WIDTH]
638    }
639
640    pub fn next_y(&mut self) -> &[u8] {
641        self.row_y = self.wrap_y.inc();
642        let wx = self.wrap_x.func(self.x) as usize * PIX_WIDTH;
643        let row = self.rbuf.row_slice(self.row_y);
644        &row[wx..wx + PIX_WIDTH]
645    }
646}
647
648// ============================================================================
649// Tests
650// ============================================================================
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655
656    fn make_rgba_buffer(width: u32, height: u32, data: &mut Vec<u8>) -> RowAccessor {
657        let stride = width as usize * 4;
658        data.resize(stride * height as usize, 0);
659        unsafe { RowAccessor::new_with_buf(data.as_mut_ptr(), width, height, stride as i32) }
660    }
661
662    fn set_rgba_pixel(data: &mut [u8], width: u32, x: u32, y: u32, rgba: [u8; 4]) {
663        let off = (y * width * 4 + x * 4) as usize;
664        data[off..off + 4].copy_from_slice(&rgba);
665    }
666
667    // -- WrapMode tests --
668
669    #[test]
670    fn test_wrap_repeat() {
671        let mut w = WrapModeRepeat::new(4);
672        assert_eq!(w.func(0), 0);
673        assert_eq!(w.func(3), 3);
674        assert_eq!(w.func(4), 0);
675        assert_eq!(w.func(5), 1);
676        assert_eq!(w.func(-1), 3);
677    }
678
679    #[test]
680    fn test_wrap_repeat_inc() {
681        let mut w = WrapModeRepeat::new(3);
682        w.func(0);
683        assert_eq!(w.inc(), 1);
684        assert_eq!(w.inc(), 2);
685        assert_eq!(w.inc(), 0); // wraps
686    }
687
688    #[test]
689    fn test_wrap_repeat_pow2() {
690        let mut w = WrapModeRepeatPow2::new(4); // mask=3
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(7), 3);
695    }
696
697    #[test]
698    fn test_wrap_reflect() {
699        let mut w = WrapModeReflect::new(4); // size=4, size2=8
700        assert_eq!(w.func(0), 0);
701        assert_eq!(w.func(3), 3);
702        assert_eq!(w.func(4), 3); // reflected
703        assert_eq!(w.func(7), 0); // reflected
704    }
705
706    #[test]
707    fn test_wrap_reflect_inc() {
708        let mut w = WrapModeReflect::new(3); // size=3, size2=6
709        w.func(0);
710        assert_eq!(w.inc(), 1);
711        assert_eq!(w.inc(), 2);
712        assert_eq!(w.inc(), 2); // reflected: 6-3-1=2
713        assert_eq!(w.inc(), 1); // 6-4-1=1
714        assert_eq!(w.inc(), 0); // 6-5-1=0
715        assert_eq!(w.inc(), 0); // wraps to 0
716    }
717
718    #[test]
719    fn test_wrap_repeat_auto_pow2() {
720        // Power-of-2 size: should use mask
721        let mut w = WrapModeRepeatAutoPow2::new(8);
722        assert_eq!(w.func(8), 0);
723        assert_eq!(w.func(9), 1);
724
725        // Non-power-of-2: should use modulo
726        let mut w2 = WrapModeRepeatAutoPow2::new(5);
727        assert_eq!(w2.func(5), 0);
728        assert_eq!(w2.func(7), 2);
729    }
730
731    #[test]
732    fn test_wrap_reflect_pow2() {
733        let mut w = WrapModeReflectPow2::new(4);
734        assert_eq!(w.func(0), 0);
735        assert_eq!(w.func(3), 3);
736        assert_eq!(w.func(4), 3); // reflected
737        assert_eq!(w.func(7), 0); // reflected
738    }
739
740    // -- ImageAccessorClip tests --
741
742    #[test]
743    fn test_clip_in_bounds() {
744        let mut data = Vec::new();
745        let rbuf = make_rgba_buffer(4, 4, &mut data);
746        set_rgba_pixel(&mut data, 4, 1, 1, [10, 20, 30, 40]);
747
748        let mut acc = ImageAccessorClip::<4>::new(&rbuf, &[0, 0, 0, 0]);
749        let pix = acc.span(1, 1, 1);
750        assert_eq!(&pix[..4], &[10, 20, 30, 40]);
751    }
752
753    #[test]
754    fn test_clip_out_of_bounds() {
755        let mut data = Vec::new();
756        let rbuf = make_rgba_buffer(4, 4, &mut data);
757
758        let mut acc = ImageAccessorClip::<4>::new(&rbuf, &[99, 88, 77, 66]);
759        let pix = acc.span(-1, 0, 1);
760        assert_eq!(&pix[..4], &[99, 88, 77, 66]);
761    }
762
763    #[test]
764    fn test_clip_span_fast_path() {
765        let mut data = Vec::new();
766        let rbuf = make_rgba_buffer(4, 4, &mut data);
767        set_rgba_pixel(&mut data, 4, 0, 0, [1, 2, 3, 4]);
768        set_rgba_pixel(&mut data, 4, 1, 0, [5, 6, 7, 8]);
769
770        let mut acc = ImageAccessorClip::<4>::new(&rbuf, &[0, 0, 0, 0]);
771        let pix = acc.span(0, 0, 2);
772        assert_eq!(&pix[..4], &[1, 2, 3, 4]);
773        let pix = acc.next_x();
774        assert_eq!(&pix[..4], &[5, 6, 7, 8]);
775    }
776
777    // -- ImageAccessorNoClip tests --
778
779    #[test]
780    fn test_no_clip_span() {
781        let mut data = Vec::new();
782        let rbuf = make_rgba_buffer(4, 4, &mut data);
783        set_rgba_pixel(&mut data, 4, 2, 1, [11, 22, 33, 44]);
784
785        let mut acc = ImageAccessorNoClip::<4>::new(&rbuf);
786        let pix = acc.span(2, 1, 1);
787        assert_eq!(&pix[..4], &[11, 22, 33, 44]);
788    }
789
790    #[test]
791    fn test_no_clip_next_x() {
792        let mut data = Vec::new();
793        let rbuf = make_rgba_buffer(4, 4, &mut data);
794        set_rgba_pixel(&mut data, 4, 0, 0, [10, 0, 0, 0]);
795        set_rgba_pixel(&mut data, 4, 1, 0, [20, 0, 0, 0]);
796        set_rgba_pixel(&mut data, 4, 2, 0, [30, 0, 0, 0]);
797
798        let mut acc = ImageAccessorNoClip::<4>::new(&rbuf);
799        acc.span(0, 0, 3);
800        let p1 = acc.next_x();
801        assert_eq!(p1[0], 20);
802        let p2 = acc.next_x();
803        assert_eq!(p2[0], 30);
804    }
805
806    // -- ImageAccessorClone tests --
807
808    #[test]
809    fn test_clone_in_bounds() {
810        let mut data = Vec::new();
811        let rbuf = make_rgba_buffer(4, 4, &mut data);
812        set_rgba_pixel(&mut data, 4, 1, 1, [50, 60, 70, 80]);
813
814        let mut acc = ImageAccessorClone::<4>::new(&rbuf);
815        let pix = acc.span(1, 1, 1);
816        assert_eq!(&pix[..4], &[50, 60, 70, 80]);
817    }
818
819    #[test]
820    fn test_clone_clamps_negative() {
821        let mut data = Vec::new();
822        let rbuf = make_rgba_buffer(4, 4, &mut data);
823        set_rgba_pixel(&mut data, 4, 0, 0, [10, 20, 30, 40]);
824
825        let mut acc = ImageAccessorClone::<4>::new(&rbuf);
826        let pix = acc.span(-5, -3, 1);
827        // Should clamp to (0, 0)
828        assert_eq!(&pix[..4], &[10, 20, 30, 40]);
829    }
830
831    #[test]
832    fn test_clone_clamps_overflow() {
833        let mut data = Vec::new();
834        let rbuf = make_rgba_buffer(4, 4, &mut data);
835        set_rgba_pixel(&mut data, 4, 3, 3, [99, 88, 77, 66]);
836
837        let mut acc = ImageAccessorClone::<4>::new(&rbuf);
838        let pix = acc.span(100, 100, 1);
839        // Should clamp to (3, 3)
840        assert_eq!(&pix[..4], &[99, 88, 77, 66]);
841    }
842
843    // -- ImageAccessorWrap tests --
844
845    #[test]
846    fn test_wrap_repeat_access() {
847        let mut data = Vec::new();
848        let rbuf = make_rgba_buffer(2, 2, &mut data);
849        set_rgba_pixel(&mut data, 2, 0, 0, [10, 0, 0, 0]);
850        set_rgba_pixel(&mut data, 2, 1, 0, [20, 0, 0, 0]);
851        set_rgba_pixel(&mut data, 2, 0, 1, [30, 0, 0, 0]);
852        set_rgba_pixel(&mut data, 2, 1, 1, [40, 0, 0, 0]);
853
854        let mut acc = ImageAccessorWrap::<4, WrapModeRepeat, WrapModeRepeat>::new(&rbuf);
855        // x=2, y=0 should wrap to x=0, y=0
856        let pix = acc.span(2, 0, 1);
857        assert_eq!(pix[0], 10);
858        // next_x → x=3 wraps to x=1
859        let pix = acc.next_x();
860        assert_eq!(pix[0], 20);
861    }
862
863    #[test]
864    fn test_wrap_next_y() {
865        let mut data = Vec::new();
866        let rbuf = make_rgba_buffer(2, 2, &mut data);
867        set_rgba_pixel(&mut data, 2, 0, 0, [10, 0, 0, 0]);
868        set_rgba_pixel(&mut data, 2, 0, 1, [30, 0, 0, 0]);
869
870        let mut acc = ImageAccessorWrap::<4, WrapModeRepeat, WrapModeRepeat>::new(&rbuf);
871        acc.span(0, 0, 1);
872        let pix = acc.next_y();
873        assert_eq!(pix[0], 30);
874    }
875}