1use fovea::image::ImageView;
28use fovea::pixel::{
29 Bgr8, Bgra8, Mono, Mono8, Mono16, Mono32, Mono64, MonoF32, MonoF64, Rgb8, RgbF32, Rgba8,
30 RgbaF32, Srgb8, SrgbMono8, SrgbMonoA8, Srgba8,
31};
32use fovea::transform::{ConvertPixel, SrgbGamma};
33
34use crate::pixel::DisplayPixel;
35
36pub trait DisplayStrategy<P: Copy> {
55 fn to_display(&self, pixel: &P) -> Srgba8;
57}
58
59pub struct Identity;
109
110impl DisplayStrategy<Srgba8> for Identity {
111 #[inline]
112 fn to_display(&self, pixel: &Srgba8) -> Srgba8 {
113 *pixel
114 }
115}
116
117impl DisplayStrategy<Srgb8> for Identity {
118 #[inline]
119 fn to_display(&self, pixel: &Srgb8) -> Srgba8 {
120 Srgba8::new(pixel.r.0, pixel.g.0, pixel.b.0, 255)
121 }
122}
123
124impl DisplayStrategy<SrgbMono8> for Identity {
125 #[inline]
126 fn to_display(&self, pixel: &SrgbMono8) -> Srgba8 {
127 let v = pixel.0.0;
128 Srgba8::new(v, v, v, 255)
129 }
130}
131
132impl DisplayStrategy<SrgbMonoA8> for Identity {
133 #[inline]
134 fn to_display(&self, pixel: &SrgbMonoA8) -> Srgba8 {
135 let v = pixel.v.0;
136 Srgba8::new(v, v, v, pixel.a.0)
137 }
138}
139
140pub struct LinearToDisplay;
171
172impl DisplayStrategy<Rgb8> for LinearToDisplay {
173 #[inline]
174 fn to_display(&self, pixel: &Rgb8) -> Srgba8 {
175 let linear = RgbF32::new(
176 pixel.r.0 as f32 / 255.0,
177 pixel.g.0 as f32 / 255.0,
178 pixel.b.0 as f32 / 255.0,
179 );
180 let srgb: Srgb8 = SrgbGamma.convert(&linear);
181 Srgba8::new(srgb.r.0, srgb.g.0, srgb.b.0, 255)
182 }
183}
184
185impl DisplayStrategy<Rgba8> for LinearToDisplay {
186 #[inline]
187 fn to_display(&self, pixel: &Rgba8) -> Srgba8 {
188 let linear = RgbaF32::new(
189 pixel.r.0 as f32 / 255.0,
190 pixel.g.0 as f32 / 255.0,
191 pixel.b.0 as f32 / 255.0,
192 pixel.a.0 as f32 / 255.0,
193 );
194 SrgbGamma.convert(&linear)
195 }
196}
197
198impl DisplayStrategy<RgbF32> for LinearToDisplay {
199 #[inline]
200 fn to_display(&self, pixel: &RgbF32) -> Srgba8 {
201 let srgb: Srgb8 = SrgbGamma.convert(pixel);
202 Srgba8::new(srgb.r.0, srgb.g.0, srgb.b.0, 255)
203 }
204}
205
206impl DisplayStrategy<RgbaF32> for LinearToDisplay {
207 #[inline]
208 fn to_display(&self, pixel: &RgbaF32) -> Srgba8 {
209 SrgbGamma.convert(pixel)
210 }
211}
212
213impl DisplayStrategy<Bgr8> for LinearToDisplay {
214 #[inline]
215 fn to_display(&self, pixel: &Bgr8) -> Srgba8 {
216 let linear = RgbF32::new(
217 pixel.r.0 as f32 / 255.0,
218 pixel.g.0 as f32 / 255.0,
219 pixel.b.0 as f32 / 255.0,
220 );
221 let srgb: Srgb8 = SrgbGamma.convert(&linear);
222 Srgba8::new(srgb.r.0, srgb.g.0, srgb.b.0, 255)
223 }
224}
225
226impl DisplayStrategy<Bgra8> for LinearToDisplay {
227 #[inline]
228 fn to_display(&self, pixel: &Bgra8) -> Srgba8 {
229 let linear = RgbaF32::new(
230 pixel.r.0 as f32 / 255.0,
231 pixel.g.0 as f32 / 255.0,
232 pixel.b.0 as f32 / 255.0,
233 pixel.a.0 as f32 / 255.0,
234 );
235 SrgbGamma.convert(&linear)
236 }
237}
238
239impl DisplayStrategy<Mono8> for LinearToDisplay {
240 #[inline]
241 fn to_display(&self, pixel: &Mono8) -> Srgba8 {
242 let linear = MonoF32::new(pixel.value() as f32 / 255.0);
243 let srgb: SrgbMono8 = SrgbGamma.convert(&linear);
244 let v = srgb.0.0;
245 Srgba8::new(v, v, v, 255)
246 }
247}
248
249impl DisplayStrategy<f32> for LinearToDisplay {
250 #[inline]
251 fn to_display(&self, pixel: &f32) -> Srgba8 {
252 let clamped = MonoF32::new(pixel.clamp(0.0, 1.0));
253 let srgb: SrgbMono8 = SrgbGamma.convert(&clamped);
254 let v = srgb.0.0;
255 Srgba8::new(v, v, v, 255)
256 }
257}
258
259impl DisplayStrategy<MonoF32> for LinearToDisplay {
260 #[inline]
261 fn to_display(&self, pixel: &MonoF32) -> Srgba8 {
262 <Self as DisplayStrategy<f32>>::to_display(self, &pixel.0)
263 }
264}
265
266impl DisplayStrategy<MonoF64> for LinearToDisplay {
267 #[inline]
268 fn to_display(&self, pixel: &MonoF64) -> Srgba8 {
269 let clamped = MonoF32::new(pixel.0.clamp(0.0, 1.0) as f32);
270 let srgb: SrgbMono8 = SrgbGamma.convert(&clamped);
271 let v = srgb.0.0;
272 Srgba8::new(v, v, v, 255)
273 }
274}
275
276#[derive(Clone, Copy)]
285struct RangeMap {
286 min: f64,
287 scale: f64, }
289
290impl RangeMap {
291 fn new(min: f64, max: f64) -> Self {
295 let range = max - min;
296 let scale = if range.abs() < f64::EPSILON {
297 0.0
298 } else {
299 1.0 / range
300 };
301 RangeMap { min, scale }
302 }
303
304 #[inline]
310 fn map_to_srgba8(&self, value: f64) -> Srgba8 {
311 if self.scale == 0.0 {
313 return Srgba8::new(128, 128, 128, 255);
314 }
315
316 let t = MonoF32::new(((value - self.min) * self.scale).clamp(0.0, 1.0) as f32);
317 let srgb: SrgbMono8 = SrgbGamma.convert(&t);
318 let v = srgb.0.0;
319 Srgba8::new(v, v, v, 255)
320 }
321}
322
323#[derive(Clone, Copy)]
355pub struct AutoContrast {
356 range: RangeMap,
357}
358
359impl AutoContrast {
360 #[must_use]
365 pub fn new(min: f64, max: f64) -> Self {
366 AutoContrast {
367 range: RangeMap::new(min, max),
368 }
369 }
370
371 pub fn scan<V>(image: &V) -> Self
400 where
401 V: ImageView,
402 V::Pixel: Copy + Into<f64>,
403 {
404 Self::scan_with(image, |p| (*p).into())
405 }
406
407 pub fn scan_with<V, F>(image: &V, to_scalar: F) -> Self
429 where
430 V: ImageView,
431 V::Pixel: Copy,
432 F: Fn(&V::Pixel) -> f64,
433 {
434 let w = image.width();
435 let h = image.height();
436
437 if w == 0 || h == 0 {
438 return AutoContrast::new(0.0, 0.0);
439 }
440
441 let first = to_scalar(&image.pixel_at(0, 0));
442 let mut min = first;
443 let mut max = first;
444
445 for y in 0..h {
446 for x in 0..w {
447 let v = to_scalar(&image.pixel_at(x, y));
448 if v < min {
449 min = v;
450 }
451 if v > max {
452 max = v;
453 }
454 }
455 }
456
457 AutoContrast::new(min, max)
458 }
459}
460
461#[inline(always)]
465fn mono8_to_f64(pixel: &Mono8) -> f64 {
466 pixel.value() as f64
467}
468
469#[inline(always)]
471fn mono16_to_f64(pixel: &Mono16) -> f64 {
472 pixel.value() as f64
473}
474
475#[inline(always)]
477fn mono32_to_f64(pixel: &Mono32) -> f64 {
478 pixel.value() as f64
479}
480
481#[inline(always)]
483fn mono64_to_f64(pixel: &Mono64) -> f64 {
484 pixel.value() as f64
485}
486
487impl DisplayStrategy<Mono8> for AutoContrast {
488 #[inline]
489 fn to_display(&self, pixel: &Mono8) -> Srgba8 {
490 self.range.map_to_srgba8(mono8_to_f64(pixel))
491 }
492}
493
494impl DisplayStrategy<Mono16> for AutoContrast {
495 #[inline]
496 fn to_display(&self, pixel: &Mono16) -> Srgba8 {
497 self.range.map_to_srgba8(mono16_to_f64(pixel))
498 }
499}
500
501impl DisplayStrategy<Mono32> for AutoContrast {
502 #[inline]
503 fn to_display(&self, pixel: &Mono32) -> Srgba8 {
504 self.range.map_to_srgba8(mono32_to_f64(pixel))
505 }
506}
507
508impl DisplayStrategy<Mono64> for AutoContrast {
509 #[inline]
510 fn to_display(&self, pixel: &Mono64) -> Srgba8 {
511 self.range.map_to_srgba8(mono64_to_f64(pixel))
512 }
513}
514
515impl DisplayStrategy<f32> for AutoContrast {
516 #[inline]
517 fn to_display(&self, pixel: &f32) -> Srgba8 {
518 self.range.map_to_srgba8(*pixel as f64)
519 }
520}
521
522impl DisplayStrategy<f64> for AutoContrast {
523 #[inline]
524 fn to_display(&self, pixel: &f64) -> Srgba8 {
525 self.range.map_to_srgba8(*pixel)
526 }
527}
528
529impl DisplayStrategy<MonoF32> for AutoContrast {
530 #[inline]
531 fn to_display(&self, pixel: &MonoF32) -> Srgba8 {
532 self.range.map_to_srgba8(pixel.0 as f64)
533 }
534}
535
536impl DisplayStrategy<MonoF64> for AutoContrast {
537 #[inline]
538 fn to_display(&self, pixel: &MonoF64) -> Srgba8 {
539 self.range.map_to_srgba8(pixel.0)
540 }
541}
542
543impl DisplayStrategy<u8> for AutoContrast {
544 #[inline]
545 fn to_display(&self, pixel: &u8) -> Srgba8 {
546 self.range.map_to_srgba8(*pixel as f64)
547 }
548}
549
550impl DisplayStrategy<u16> for AutoContrast {
551 #[inline]
552 fn to_display(&self, pixel: &u16) -> Srgba8 {
553 self.range.map_to_srgba8(*pixel as f64)
554 }
555}
556
557impl<const BITS: usize> DisplayStrategy<Mono<BITS>> for AutoContrast {
558 #[inline]
559 fn to_display(&self, pixel: &Mono<BITS>) -> Srgba8 {
560 self.range.map_to_srgba8(pixel.value() as f64)
561 }
562}
563
564#[derive(Clone, Copy)]
594pub struct FixedRange {
595 range: RangeMap,
596}
597
598impl FixedRange {
599 #[must_use]
604 pub fn new(min: f64, max: f64) -> Self {
605 FixedRange {
606 range: RangeMap::new(min, max),
607 }
608 }
609}
610
611impl DisplayStrategy<Mono8> for FixedRange {
614 #[inline]
615 fn to_display(&self, pixel: &Mono8) -> Srgba8 {
616 self.range.map_to_srgba8(mono8_to_f64(pixel))
617 }
618}
619
620impl DisplayStrategy<Mono16> for FixedRange {
621 #[inline]
622 fn to_display(&self, pixel: &Mono16) -> Srgba8 {
623 self.range.map_to_srgba8(mono16_to_f64(pixel))
624 }
625}
626
627impl DisplayStrategy<Mono32> for FixedRange {
628 #[inline]
629 fn to_display(&self, pixel: &Mono32) -> Srgba8 {
630 self.range.map_to_srgba8(mono32_to_f64(pixel))
631 }
632}
633
634impl DisplayStrategy<Mono64> for FixedRange {
635 #[inline]
636 fn to_display(&self, pixel: &Mono64) -> Srgba8 {
637 self.range.map_to_srgba8(mono64_to_f64(pixel))
638 }
639}
640
641impl DisplayStrategy<f32> for FixedRange {
642 #[inline]
643 fn to_display(&self, pixel: &f32) -> Srgba8 {
644 self.range.map_to_srgba8(*pixel as f64)
645 }
646}
647
648impl DisplayStrategy<f64> for FixedRange {
649 #[inline]
650 fn to_display(&self, pixel: &f64) -> Srgba8 {
651 self.range.map_to_srgba8(*pixel)
652 }
653}
654
655impl DisplayStrategy<u8> for FixedRange {
656 #[inline]
657 fn to_display(&self, pixel: &u8) -> Srgba8 {
658 self.range.map_to_srgba8(*pixel as f64)
659 }
660}
661
662impl DisplayStrategy<u16> for FixedRange {
663 #[inline]
664 fn to_display(&self, pixel: &u16) -> Srgba8 {
665 self.range.map_to_srgba8(*pixel as f64)
666 }
667}
668
669impl<const BITS: usize> DisplayStrategy<Mono<BITS>> for FixedRange {
670 #[inline]
671 fn to_display(&self, pixel: &Mono<BITS>) -> Srgba8 {
672 self.range.map_to_srgba8(pixel.value() as f64)
673 }
674}
675
676pub(crate) struct Framebuffer {
688 pub width: u32,
689 pub height: u32,
690 pub data: Vec<u32>,
691}
692
693impl Framebuffer {
694 pub(crate) fn from_image<V, S>(image: &V, strategy: S) -> Self
701 where
702 V: ImageView,
703 V::Pixel: Copy,
704 S: DisplayStrategy<V::Pixel>,
705 {
706 let w = image.width();
707 let h = image.height();
708 let len = w * h;
709 let mut data = Vec::with_capacity(len);
710
711 for y in 0..h {
712 for x in 0..w {
713 let pixel = image.pixel_at(x, y);
714 let display = strategy.to_display(&pixel);
715 data.push(display.to_framebuffer_u32());
716 }
717 }
718
719 Framebuffer {
720 width: w as u32,
721 height: h as u32,
722 data,
723 }
724 }
725
726 #[allow(dead_code)]
732 pub(crate) fn from_raw(width: u32, height: u32, data: Vec<u32>) -> Self {
733 assert_eq!(
734 data.len(),
735 (width as usize) * (height as usize),
736 "Framebuffer::from_raw: data length ({}) does not match dimensions ({}×{}={})",
737 data.len(),
738 width,
739 height,
740 (width as usize) * (height as usize),
741 );
742 Framebuffer {
743 width,
744 height,
745 data,
746 }
747 }
748}
749
750#[cfg(test)]
755mod tests {
756 use super::*;
757 use fovea::image::{Image, ImageViewMut, SubView};
758 use fovea::pixel::*;
759
760 #[test]
763 fn identity_srgba8_passthrough() {
764 let px = Srgba8::new(42, 100, 200, 128);
765 assert_eq!(Identity.to_display(&px), px);
766 }
767
768 #[test]
769 fn identity_srgba8_all_values_preserved() {
770 for r in [0u8, 1, 127, 128, 254, 255] {
771 for g in [0u8, 128, 255] {
772 for b in [0u8, 128, 255] {
773 for a in [0u8, 128, 255] {
774 let px = Srgba8::new(r, g, b, a);
775 assert_eq!(Identity.to_display(&px), px);
776 }
777 }
778 }
779 }
780 }
781
782 #[test]
783 fn identity_srgb8_adds_alpha() {
784 let px = Srgb8::new(128, 64, 200);
785 assert_eq!(Identity.to_display(&px), Srgba8::new(128, 64, 200, 255));
786 }
787
788 #[test]
789 fn identity_srgb8_black() {
790 let px = Srgb8::new(0, 0, 0);
791 assert_eq!(Identity.to_display(&px), Srgba8::new(0, 0, 0, 255));
792 }
793
794 #[test]
795 fn identity_srgb8_white() {
796 let px = Srgb8::new(255, 255, 255);
797 assert_eq!(Identity.to_display(&px), Srgba8::new(255, 255, 255, 255));
798 }
799
800 #[test]
801 fn identity_srgb_mono8_broadcast() {
802 let px = SrgbMono8::new(128);
803 assert_eq!(Identity.to_display(&px), Srgba8::new(128, 128, 128, 255));
804 }
805
806 #[test]
807 fn identity_srgb_mono8_black() {
808 let px = SrgbMono8::new(0);
809 assert_eq!(Identity.to_display(&px), Srgba8::new(0, 0, 0, 255));
810 }
811
812 #[test]
813 fn identity_srgb_mono8_white() {
814 let px = SrgbMono8::new(255);
815 assert_eq!(Identity.to_display(&px), Srgba8::new(255, 255, 255, 255));
816 }
817
818 #[test]
819 fn identity_srgb_mono_a8_broadcast_with_alpha() {
820 let px = SrgbMonoA8::new(128, 64);
821 assert_eq!(Identity.to_display(&px), Srgba8::new(128, 128, 128, 64));
822 }
823
824 #[test]
825 fn identity_srgb_mono_a8_black_transparent() {
826 let px = SrgbMonoA8::new(0, 0);
827 assert_eq!(Identity.to_display(&px), Srgba8::new(0, 0, 0, 0));
828 }
829
830 #[test]
831 fn identity_srgb_mono_a8_white_opaque() {
832 let px = SrgbMonoA8::new(255, 255);
833 assert_eq!(Identity.to_display(&px), Srgba8::new(255, 255, 255, 255));
834 }
835
836 #[test]
839 fn linear_rgb8_black() {
840 let px = Rgb8::new(0, 0, 0);
841 assert_eq!(LinearToDisplay.to_display(&px), Srgba8::new(0, 0, 0, 255));
842 }
843
844 #[test]
845 fn linear_rgb8_white() {
846 let px = Rgb8::new(255, 255, 255);
847 assert_eq!(
848 LinearToDisplay.to_display(&px),
849 Srgba8::new(255, 255, 255, 255)
850 );
851 }
852
853 #[test]
854 fn linear_rgbf32_mid_gray() {
855 let px = RgbF32::new(0.5, 0.5, 0.5);
857 let result = LinearToDisplay.to_display(&px);
858 assert_eq!(result.r.0, 188);
860 assert_eq!(result.g.0, 188);
861 assert_eq!(result.b.0, 188);
862 assert_eq!(result.a.0, 255);
863 }
864
865 #[test]
866 fn linear_mono8_correct_srgb_gray() {
867 let px = Mono8::new(128);
869 let result = LinearToDisplay.to_display(&px);
870 assert_eq!(result.r.0, result.g.0);
871 assert_eq!(result.g.0, result.b.0);
872 assert_eq!(result.a.0, 255);
873 assert!(result.r.0 >= 187 && result.r.0 <= 189);
875 }
876
877 #[test]
878 fn linear_rgba_f32_alpha_preserved() {
879 let px = RgbaF32::new(0.5, 0.5, 0.5, 0.5);
881 let result = LinearToDisplay.to_display(&px);
882 assert_eq!(result.a.0, 128);
884 assert_eq!(result.r.0, 188);
886 }
887
888 #[test]
889 fn linear_bgr8_channel_order() {
890 let px = Bgr8::new(0, 0, 255); let result = LinearToDisplay.to_display(&px);
893 assert_eq!(result.r.0, 255); assert_eq!(result.g.0, 0);
895 assert_eq!(result.b.0, 0);
896 }
897
898 #[test]
899 fn linear_bgra8_channel_order_with_alpha() {
900 let px = Bgra8::new(0, 0, 255, 128); let result = LinearToDisplay.to_display(&px);
902 assert_eq!(result.r.0, 255);
903 assert_eq!(result.g.0, 0);
904 assert_eq!(result.b.0, 0);
905 assert_eq!(result.a.0, 128);
907 }
908
909 #[test]
910 fn linear_f32_clamps_below_zero() {
911 let px: f32 = -0.5;
912 let result = LinearToDisplay.to_display(&px);
913 assert_eq!(result, Srgba8::new(0, 0, 0, 255));
914 }
915
916 #[test]
917 fn linear_f32_clamps_above_one() {
918 let px: f32 = 1.5;
919 let result = LinearToDisplay.to_display(&px);
920 assert_eq!(result, Srgba8::new(255, 255, 255, 255));
921 }
922
923 #[test]
926 fn range_map_zero_to_one_black() {
927 let rm = RangeMap::new(0.0, 1.0);
928 assert_eq!(rm.map_to_srgba8(0.0), Srgba8::new(0, 0, 0, 255));
929 }
930
931 #[test]
932 fn range_map_zero_to_one_white() {
933 let rm = RangeMap::new(0.0, 1.0);
934 assert_eq!(rm.map_to_srgba8(1.0), Srgba8::new(255, 255, 255, 255));
935 }
936
937 #[test]
938 fn range_map_zero_to_one_mid_gray() {
939 let rm = RangeMap::new(0.0, 1.0);
940 let result = rm.map_to_srgba8(0.5);
941 assert_eq!(result.r.0, 188);
943 assert_eq!(result.g.0, 188);
944 assert_eq!(result.b.0, 188);
945 assert_eq!(result.a.0, 255);
946 }
947
948 #[test]
949 fn range_map_custom_range_black() {
950 let rm = RangeMap::new(100.0, 200.0);
951 assert_eq!(rm.map_to_srgba8(100.0), Srgba8::new(0, 0, 0, 255));
952 }
953
954 #[test]
955 fn range_map_custom_range_white() {
956 let rm = RangeMap::new(100.0, 200.0);
957 assert_eq!(rm.map_to_srgba8(200.0), Srgba8::new(255, 255, 255, 255));
958 }
959
960 #[test]
961 fn range_map_degenerate_mid_gray() {
962 let rm = RangeMap::new(5.0, 5.0);
963 assert_eq!(rm.map_to_srgba8(5.0), Srgba8::new(128, 128, 128, 255));
964 }
965
966 #[test]
967 fn range_map_clamp_below() {
968 let rm = RangeMap::new(100.0, 200.0);
969 assert_eq!(rm.map_to_srgba8(50.0), Srgba8::new(0, 0, 0, 255));
970 }
971
972 #[test]
973 fn range_map_clamp_above() {
974 let rm = RangeMap::new(100.0, 200.0);
975 assert_eq!(rm.map_to_srgba8(300.0), Srgba8::new(255, 255, 255, 255));
976 }
977
978 #[test]
981 fn auto_contrast_mono16_full_range() {
982 let ac = AutoContrast::new(0.0, 65535.0);
983 assert_eq!(ac.to_display(&Mono16::new(0)), Srgba8::new(0, 0, 0, 255));
984 assert_eq!(
985 ac.to_display(&Mono16::new(65535)),
986 Srgba8::new(255, 255, 255, 255)
987 );
988 }
989
990 #[test]
991 fn auto_contrast_custom_range() {
992 let ac = AutoContrast::new(100.0, 200.0);
993 assert_eq!(ac.to_display(&Mono16::new(100)), Srgba8::new(0, 0, 0, 255));
994 assert_eq!(
995 ac.to_display(&Mono16::new(200)),
996 Srgba8::new(255, 255, 255, 255)
997 );
998 }
999
1000 #[test]
1001 fn auto_contrast_f32_mid_gray() {
1002 let ac = AutoContrast::new(0.0, 1.0);
1003 let result = ac.to_display(&0.5f32);
1004 assert_eq!(result.r.0, 188);
1005 }
1006
1007 #[test]
1008 fn auto_contrast_scan_f32() {
1009 let mut img = Image::<MonoF32>::fill(4, 4, MonoF32::new(0.5));
1010 *img.get_mut(0, 0).unwrap() = MonoF32::new(0.0);
1011 *img.get_mut(3, 3).unwrap() = MonoF32::new(1.0);
1012
1013 let ac = AutoContrast::scan(&img);
1014 assert_eq!(ac.to_display(&MonoF32::new(0.0)), Srgba8::new(0, 0, 0, 255));
1015 assert_eq!(
1016 ac.to_display(&MonoF32::new(1.0)),
1017 Srgba8::new(255, 255, 255, 255)
1018 );
1019 }
1020
1021 #[test]
1022 fn auto_contrast_scan_constant_image_degenerate() {
1023 let img = Image::<MonoF32>::fill(4, 4, MonoF32::new(0.5));
1024 let ac = AutoContrast::scan(&img);
1025 assert_eq!(
1027 ac.to_display(&MonoF32::new(0.5)),
1028 Srgba8::new(128, 128, 128, 255)
1029 );
1030 }
1031
1032 #[test]
1033 fn auto_contrast_scan_single_pixel_degenerate() {
1034 let img = Image::<MonoF64>::fill(1, 1, MonoF64::new(42.0));
1035 let ac = AutoContrast::scan(&img);
1036 assert_eq!(
1037 ac.to_display(&MonoF64::new(42.0)),
1038 Srgba8::new(128, 128, 128, 255)
1039 );
1040 }
1041
1042 #[test]
1043 fn auto_contrast_scan_empty_image() {
1044 let img = Image::<MonoF32>::fill(0, 0, MonoF32::new(0.0));
1045 let ac = AutoContrast::scan(&img);
1046 assert_eq!(
1048 ac.to_display(&MonoF32::new(0.0)),
1049 Srgba8::new(128, 128, 128, 255)
1050 );
1051 }
1052
1053 #[test]
1054 fn auto_contrast_scan_with_mono16() {
1055 let mut img = Image::<Mono16>::fill(4, 4, Mono16::new(100));
1056 *img.get_mut(0, 0).unwrap() = Mono16::new(50);
1057 *img.get_mut(3, 3).unwrap() = Mono16::new(200);
1058
1059 let ac = AutoContrast::scan_with(&img, mono16_to_f64);
1060 assert_eq!(ac.to_display(&Mono16::new(50)), Srgba8::new(0, 0, 0, 255));
1061 assert_eq!(
1062 ac.to_display(&Mono16::new(200)),
1063 Srgba8::new(255, 255, 255, 255)
1064 );
1065 }
1066
1067 #[test]
1068 fn auto_contrast_mono32() {
1069 let ac = AutoContrast::new(0.0, u32::MAX as f64);
1070 assert_eq!(ac.to_display(&Mono32::new(0)), Srgba8::new(0, 0, 0, 255));
1071 assert_eq!(
1072 ac.to_display(&Mono32::new(u32::MAX)),
1073 Srgba8::new(255, 255, 255, 255)
1074 );
1075 }
1076
1077 #[test]
1078 fn auto_contrast_mono64() {
1079 let ac = AutoContrast::new(0.0, u64::MAX as f64);
1080 assert_eq!(ac.to_display(&Mono64::new(0)), Srgba8::new(0, 0, 0, 255));
1081 assert_eq!(
1083 ac.to_display(&Mono64::new(u64::MAX)),
1084 Srgba8::new(255, 255, 255, 255)
1085 );
1086 }
1087
1088 #[test]
1089 fn auto_contrast_u8() {
1090 let ac = AutoContrast::new(0.0, 255.0);
1091 assert_eq!(ac.to_display(&0u8), Srgba8::new(0, 0, 0, 255));
1092 assert_eq!(ac.to_display(&255u8), Srgba8::new(255, 255, 255, 255));
1093 }
1094
1095 #[test]
1096 fn auto_contrast_u16() {
1097 let ac = AutoContrast::new(0.0, 65535.0);
1098 assert_eq!(ac.to_display(&0u16), Srgba8::new(0, 0, 0, 255));
1099 assert_eq!(ac.to_display(&65535u16), Srgba8::new(255, 255, 255, 255));
1100 }
1101
1102 #[test]
1103 fn auto_contrast_f64() {
1104 let ac = AutoContrast::new(0.0, 1.0);
1105 assert_eq!(ac.to_display(&0.0f64), Srgba8::new(0, 0, 0, 255));
1106 assert_eq!(ac.to_display(&1.0f64), Srgba8::new(255, 255, 255, 255));
1107 }
1108
1109 #[test]
1110 fn auto_contrast_mono10() {
1111 let ac = AutoContrast::new(0.0, 1023.0);
1112 let px = Mono10::new(0);
1113 assert_eq!(ac.to_display(&px), Srgba8::new(0, 0, 0, 255));
1114 let px = Mono10::new(1023);
1115 assert_eq!(ac.to_display(&px), Srgba8::new(255, 255, 255, 255));
1116 }
1117
1118 #[test]
1119 fn auto_contrast_mono12() {
1120 let ac = AutoContrast::new(0.0, 4095.0);
1121 let px = Mono12::new(0);
1122 assert_eq!(ac.to_display(&px), Srgba8::new(0, 0, 0, 255));
1123 let px = Mono12::new(4095);
1124 assert_eq!(ac.to_display(&px), Srgba8::new(255, 255, 255, 255));
1125 }
1126
1127 #[test]
1128 fn auto_contrast_mono14() {
1129 let ac = AutoContrast::new(0.0, 16383.0);
1130 let px = Mono14::new(0);
1131 assert_eq!(ac.to_display(&px), Srgba8::new(0, 0, 0, 255));
1132 let px = Mono14::new(16383);
1133 assert_eq!(ac.to_display(&px), Srgba8::new(255, 255, 255, 255));
1134 }
1135
1136 #[test]
1139 fn fixed_range_mono16_boundaries() {
1140 let fr = FixedRange::new(100.0, 200.0);
1141 assert_eq!(fr.to_display(&Mono16::new(100)), Srgba8::new(0, 0, 0, 255));
1142 assert_eq!(
1143 fr.to_display(&Mono16::new(200)),
1144 Srgba8::new(255, 255, 255, 255)
1145 );
1146 }
1147
1148 #[test]
1149 fn fixed_range_clamping_below() {
1150 let fr = FixedRange::new(100.0, 200.0);
1151 assert_eq!(fr.to_display(&Mono16::new(0)), Srgba8::new(0, 0, 0, 255));
1152 }
1153
1154 #[test]
1155 fn fixed_range_clamping_above() {
1156 let fr = FixedRange::new(100.0, 200.0);
1157 assert_eq!(
1158 fr.to_display(&Mono16::new(65535)),
1159 Srgba8::new(255, 255, 255, 255)
1160 );
1161 }
1162
1163 #[test]
1164 fn fixed_range_degenerate() {
1165 let fr = FixedRange::new(42.0, 42.0);
1166 assert_eq!(
1167 fr.to_display(&Mono16::new(42)),
1168 Srgba8::new(128, 128, 128, 255)
1169 );
1170 }
1171
1172 #[test]
1173 fn fixed_range_f32() {
1174 let fr = FixedRange::new(0.0, 1.0);
1175 assert_eq!(fr.to_display(&0.0f32), Srgba8::new(0, 0, 0, 255));
1176 assert_eq!(fr.to_display(&1.0f32), Srgba8::new(255, 255, 255, 255));
1177 }
1178
1179 #[test]
1180 fn fixed_range_f64() {
1181 let fr = FixedRange::new(-1.0, 1.0);
1182 assert_eq!(fr.to_display(&-1.0f64), Srgba8::new(0, 0, 0, 255));
1183 assert_eq!(fr.to_display(&1.0f64), Srgba8::new(255, 255, 255, 255));
1184 }
1185
1186 #[test]
1187 fn fixed_range_u8() {
1188 let fr = FixedRange::new(0.0, 255.0);
1189 assert_eq!(fr.to_display(&0u8), Srgba8::new(0, 0, 0, 255));
1190 assert_eq!(fr.to_display(&255u8), Srgba8::new(255, 255, 255, 255));
1191 }
1192
1193 #[test]
1194 fn fixed_range_u16() {
1195 let fr = FixedRange::new(0.0, 65535.0);
1196 assert_eq!(fr.to_display(&0u16), Srgba8::new(0, 0, 0, 255));
1197 assert_eq!(fr.to_display(&65535u16), Srgba8::new(255, 255, 255, 255));
1198 }
1199
1200 #[test]
1201 fn fixed_range_mono10() {
1202 let fr = FixedRange::new(0.0, 1023.0);
1203 assert_eq!(fr.to_display(&Mono10::new(0)), Srgba8::new(0, 0, 0, 255));
1204 assert_eq!(
1205 fr.to_display(&Mono10::new(1023)),
1206 Srgba8::new(255, 255, 255, 255)
1207 );
1208 }
1209
1210 #[test]
1213 fn framebuffer_from_image_2x2_srgba8() {
1214 let mut img = Image::fill(2, 2, Srgba8::new(0, 0, 0, 255));
1215 *img.get_mut(0, 0).unwrap() = Srgba8::new(255, 0, 0, 255);
1216 *img.get_mut(1, 0).unwrap() = Srgba8::new(0, 255, 0, 255);
1217 *img.get_mut(0, 1).unwrap() = Srgba8::new(0, 0, 255, 255);
1218 *img.get_mut(1, 1).unwrap() = Srgba8::new(255, 255, 255, 255);
1219
1220 let fb = Framebuffer::from_image(&img, Identity);
1221 assert_eq!(fb.width, 2);
1222 assert_eq!(fb.height, 2);
1223 assert_eq!(fb.data.len(), 4);
1224 assert_eq!(fb.data[0], 0x00FF0000); assert_eq!(fb.data[1], 0x0000FF00); assert_eq!(fb.data[2], 0x000000FF); assert_eq!(fb.data[3], 0x00FFFFFF); }
1229
1230 #[test]
1231 fn framebuffer_from_image_zero_size() {
1232 let img = Image::<Srgba8>::fill(0, 0, Srgba8::new(0, 0, 0, 0));
1233 let fb = Framebuffer::from_image(&img, Identity);
1234 assert_eq!(fb.width, 0);
1235 assert_eq!(fb.height, 0);
1236 assert!(fb.data.is_empty());
1237 }
1238
1239 #[test]
1240 fn framebuffer_from_image_1x1() {
1241 let img = Image::fill(1, 1, Srgba8::new(0xAA, 0xBB, 0xCC, 0xFF));
1242 let fb = Framebuffer::from_image(&img, Identity);
1243 assert_eq!(fb.width, 1);
1244 assert_eq!(fb.height, 1);
1245 assert_eq!(fb.data.len(), 1);
1246 assert_eq!(fb.data[0], 0x00AABBCC);
1247 }
1248
1249 #[test]
1250 fn framebuffer_from_image_with_strategy() {
1251 let img = Image::fill(2, 1, Mono8::new(0));
1253 let fb = Framebuffer::from_image(&img, LinearToDisplay);
1254 assert_eq!(fb.width, 2);
1255 assert_eq!(fb.height, 1);
1256 assert_eq!(fb.data.len(), 2);
1257 assert_eq!(fb.data[0], 0x00000000);
1259 assert_eq!(fb.data[1], 0x00000000);
1260 }
1261
1262 #[test]
1263 fn framebuffer_from_image_roi() {
1264 let mut img = Image::fill(4, 4, Srgba8::new(0, 0, 0, 255));
1266 *img.get_mut(1, 1).unwrap() = Srgba8::new(255, 0, 0, 255);
1267 *img.get_mut(2, 1).unwrap() = Srgba8::new(0, 255, 0, 255);
1268 *img.get_mut(1, 2).unwrap() = Srgba8::new(0, 0, 255, 255);
1269 *img.get_mut(2, 2).unwrap() = Srgba8::new(255, 255, 255, 255);
1270
1271 let roi = img
1272 .roi(fovea::Rectangle::new((1usize, 1usize), (2usize, 2usize)))
1273 .unwrap();
1274 let fb = Framebuffer::from_image(&roi, Identity);
1275 assert_eq!(fb.width, 2);
1276 assert_eq!(fb.height, 2);
1277 assert_eq!(fb.data[0], 0x00FF0000); assert_eq!(fb.data[1], 0x0000FF00); assert_eq!(fb.data[2], 0x000000FF); assert_eq!(fb.data[3], 0x00FFFFFF); }
1282
1283 #[test]
1284 fn framebuffer_from_raw_valid() {
1285 let fb = Framebuffer::from_raw(2, 2, vec![0, 1, 2, 3]);
1286 assert_eq!(fb.width, 2);
1287 assert_eq!(fb.height, 2);
1288 assert_eq!(fb.data, vec![0, 1, 2, 3]);
1289 }
1290
1291 #[test]
1292 fn framebuffer_from_raw_empty() {
1293 let fb = Framebuffer::from_raw(0, 0, vec![]);
1294 assert_eq!(fb.width, 0);
1295 assert_eq!(fb.height, 0);
1296 assert!(fb.data.is_empty());
1297 }
1298
1299 #[test]
1300 #[should_panic(expected = "does not match dimensions")]
1301 fn framebuffer_from_raw_wrong_size() {
1302 let _ = Framebuffer::from_raw(2, 2, vec![0, 1, 2]);
1303 }
1304}