Skip to main content

polyscope_structures/floating/
color_image.rs

1//! Floating color image quantity.
2
3use glam::{Vec3, Vec4};
4use polyscope_core::quantity::{Quantity, QuantityKind};
5
6use super::ImageOrigin;
7
8/// A floating color image quantity (not attached to any structure).
9///
10/// Displays a 2D grid of RGB colors directly.
11pub struct FloatingColorImage {
12    name: String,
13    width: u32,
14    height: u32,
15    colors: Vec<Vec4>, // RGBA per pixel
16    origin: ImageOrigin,
17    enabled: bool,
18}
19
20impl FloatingColorImage {
21    /// Creates a new floating color image.
22    pub fn new(name: impl Into<String>, width: u32, height: u32, colors: Vec<Vec3>) -> Self {
23        let colors = colors.into_iter().map(|c| c.extend(1.0)).collect();
24        Self {
25            name: name.into(),
26            width,
27            height,
28            colors,
29            origin: ImageOrigin::default(),
30            enabled: true,
31        }
32    }
33
34    /// Returns the image width.
35    #[must_use]
36    pub fn width(&self) -> u32 {
37        self.width
38    }
39
40    /// Returns the image height.
41    #[must_use]
42    pub fn height(&self) -> u32 {
43        self.height
44    }
45
46    /// Returns the pixel colors.
47    #[must_use]
48    pub fn colors(&self) -> &[Vec4] {
49        &self.colors
50    }
51
52    /// Gets the image origin.
53    #[must_use]
54    pub fn origin(&self) -> ImageOrigin {
55        self.origin
56    }
57
58    /// Sets the image origin.
59    pub fn set_origin(&mut self, origin: ImageOrigin) -> &mut Self {
60        self.origin = origin;
61        self
62    }
63
64    /// Returns the pixel color at (x, y), accounting for image origin.
65    #[must_use]
66    pub fn pixel(&self, x: u32, y: u32) -> Vec4 {
67        let row = match self.origin {
68            ImageOrigin::UpperLeft => y,
69            ImageOrigin::LowerLeft => self.height - 1 - y,
70        };
71        self.colors[(row * self.width + x) as usize]
72    }
73}
74
75impl Quantity for FloatingColorImage {
76    fn as_any(&self) -> &dyn std::any::Any {
77        self
78    }
79    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
80        self
81    }
82    fn name(&self) -> &str {
83        &self.name
84    }
85    #[allow(clippy::unnecessary_literal_bound)]
86    fn structure_name(&self) -> &str {
87        "" // No parent structure
88    }
89    fn kind(&self) -> QuantityKind {
90        QuantityKind::Color
91    }
92    fn is_enabled(&self) -> bool {
93        self.enabled
94    }
95    fn set_enabled(&mut self, enabled: bool) {
96        self.enabled = enabled;
97    }
98    fn build_ui(&mut self, _ui: &dyn std::any::Any) {}
99    fn refresh(&mut self) {}
100    fn data_size(&self) -> usize {
101        self.colors.len()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_color_image_creation() {
111        let colors = vec![
112            Vec3::new(1.0, 0.0, 0.0),
113            Vec3::new(0.0, 1.0, 0.0),
114            Vec3::new(0.0, 0.0, 1.0),
115            Vec3::new(1.0, 1.0, 1.0),
116        ];
117        let img = FloatingColorImage::new("test", 2, 2, colors);
118
119        assert_eq!(img.name(), "test");
120        assert_eq!(img.width(), 2);
121        assert_eq!(img.height(), 2);
122        assert_eq!(img.data_size(), 4);
123        assert_eq!(img.kind(), QuantityKind::Color);
124    }
125
126    #[test]
127    fn test_color_image_pixel_access() {
128        let colors = vec![
129            Vec3::new(1.0, 0.0, 0.0), // (0,0) top-left red
130            Vec3::new(0.0, 1.0, 0.0), // (1,0) top-right green
131            Vec3::new(0.0, 0.0, 1.0), // (0,1) bottom-left blue
132            Vec3::new(1.0, 1.0, 1.0), // (1,1) bottom-right white
133        ];
134        let img = FloatingColorImage::new("test", 2, 2, colors);
135
136        assert_eq!(img.pixel(0, 0), Vec4::new(1.0, 0.0, 0.0, 1.0));
137        assert_eq!(img.pixel(1, 0), Vec4::new(0.0, 1.0, 0.0, 1.0));
138        assert_eq!(img.pixel(0, 1), Vec4::new(0.0, 0.0, 1.0, 1.0));
139        assert_eq!(img.pixel(1, 1), Vec4::new(1.0, 1.0, 1.0, 1.0));
140    }
141
142    #[test]
143    fn test_color_image_lower_left_origin() {
144        let colors = vec![
145            Vec3::new(1.0, 0.0, 0.0),
146            Vec3::new(0.0, 1.0, 0.0),
147            Vec3::new(0.0, 0.0, 1.0),
148            Vec3::new(1.0, 1.0, 1.0),
149        ];
150        let mut img = FloatingColorImage::new("test", 2, 2, colors);
151        img.set_origin(ImageOrigin::LowerLeft);
152
153        // LowerLeft: y=0 maps to bottom row (index 2,3)
154        assert_eq!(img.pixel(0, 0), Vec4::new(0.0, 0.0, 1.0, 1.0));
155        assert_eq!(img.pixel(1, 0), Vec4::new(1.0, 1.0, 1.0, 1.0));
156        assert_eq!(img.pixel(0, 1), Vec4::new(1.0, 0.0, 0.0, 1.0));
157        assert_eq!(img.pixel(1, 1), Vec4::new(0.0, 1.0, 0.0, 1.0));
158    }
159}