diff --git a/.vscode/settings.json b/.vscode/settings.json
index d53e8ef..518e87c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -76,6 +76,7 @@
"polyline",
"pyplot",
"rarrow",
+ "rgba",
"roundtooth",
"rstride",
"savefig",
diff --git a/examples/README.md b/examples/README.md
index 03eea59..a11494b 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -95,6 +95,8 @@ Some output of integration tests are shown below.
## Image
[test_image.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_image.rs)
+[test_image_with_rgb.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_image_with_rgb.rs)
+[test_image_with_rgba.rs](https://github.com/cpmech/plotpy/tree/main/tests/test_image_with_rgba.rs)

diff --git a/figures/integ_image_with_rgb.svg b/figures/integ_image_with_rgb.svg
new file mode 100644
index 0000000..804cd07
--- /dev/null
+++ b/figures/integ_image_with_rgb.svg
@@ -0,0 +1,401 @@
+
+
+
diff --git a/figures/integ_image_with_rgba.svg b/figures/integ_image_with_rgba.svg
new file mode 100644
index 0000000..21874b4
--- /dev/null
+++ b/figures/integ_image_with_rgba.svg
@@ -0,0 +1,401 @@
+
+
+
diff --git a/src/conversions.rs b/src/conversions.rs
index c8923ff..500c081 100644
--- a/src/conversions.rs
+++ b/src/conversions.rs
@@ -56,6 +56,26 @@ where
write!(buf, "]\n").unwrap();
}
+/// Generates a 3-deep nested Python list
+pub(crate) fn generate_nested_list_3(buf: &mut String, name: &str, data: &Vec>>)
+where
+ T: std::fmt::Display + Num,
+{
+ write!(buf, "{}=[", name).unwrap();
+ for row in data.into_iter() {
+ write!(buf, "[").unwrap();
+ for val in row.into_iter() {
+ write!(buf, "[",).unwrap();
+ for v in val.into_iter() {
+ write!(buf, "{},", v).unwrap();
+ }
+ write!(buf, "],").unwrap();
+ }
+ write!(buf, "],").unwrap();
+ }
+ write!(buf, "]\n").unwrap();
+}
+
/// Converts a matrix to a 2D NumPy array
pub(crate) fn matrix_to_array<'a, T, U>(buf: &mut String, name: &str, matrix: &'a T)
where
@@ -78,7 +98,7 @@ where
#[cfg(test)]
mod tests {
- use super::{generate_list, generate_list_quoted, generate_nested_list, matrix_to_array, vector_to_array};
+ use super::*;
#[test]
fn generate_list_works() {
@@ -139,6 +159,17 @@ mod tests {
assert_eq!(buf, "a=[[1,2,3,],[4,5,],[6,7,8,9,],]\n");
}
+ #[test]
+ fn generate_nested_list_3_works() {
+ let mut buf = String::new();
+ let a = vec![
+ vec![vec![1.0, 0.0, 0.0, 1.0], vec![0.0, 1.0, 0.0, 1.0]], // Row 0: Red, Green
+ vec![vec![0.0, 0.0, 1.0, 1.0], vec![1.0, 1.0, 1.0, 0.5]], // Row 1: Blue, White (semi-transparent)
+ ];
+ generate_nested_list_3(&mut buf, "a", &a);
+ assert_eq!(buf, "a=[[[1,0,0,1,],[0,1,0,1,],],[[0,0,1,1,],[1,1,1,0.5,],],]\n");
+ }
+
#[test]
fn matrix_to_array_works() {
let mut buf = String::new();
diff --git a/src/image.rs b/src/image.rs
index 4e82385..b3d91f5 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -1,4 +1,4 @@
-use super::{matrix_to_array, AsMatrix, GraphMaker};
+use super::{generate_nested_list_3, matrix_to_array, AsMatrix, GraphMaker};
use num_traits::Num;
use std::fmt::Write;
@@ -55,6 +55,12 @@ impl Image {
}
/// (imshow) Displays data as an image
+ ///
+ /// # Arguments
+ ///
+ /// * `data` - 2D matrix-like data structure
+ ///
+ /// See
pub fn draw<'a, T, U>(&mut self, data: &'a T)
where
T: AsMatrix<'a, U>,
@@ -65,6 +71,23 @@ impl Image {
write!(&mut self.buffer, "plt.imshow(data{})\n", &opt).unwrap();
}
+ /// (imshow) Displays data as an image with RGB or RGB(A) values
+ ///
+ /// # Arguments
+ ///
+ /// * `data` - 3D vector with shape (height, width, 3) for RGB or (height, width, 4) for RGBA
+ /// The inner-most vector contains the color channels.
+ ///
+ /// See
+ pub fn draw_rgb_or_rgba(&mut self, data: &Vec>>)
+ where
+ T: std::fmt::Display + Num,
+ {
+ generate_nested_list_3(&mut self.buffer, "data", data);
+ let opt = self.options();
+ write!(&mut self.buffer, "plt.imshow(data{})\n", &opt).unwrap();
+ }
+
/// Sets the colormap index
///
/// Options:
diff --git a/tests/test_image.rs b/tests/test_image.rs
index 3b303c6..b5ea5b1 100644
--- a/tests/test_image.rs
+++ b/tests/test_image.rs
@@ -37,3 +37,93 @@ fn test_image_1() -> Result<(), StrError> {
assert!(c > 420 && c < 500);
Ok(())
}
+
+#[test]
+fn test_image_with_rgb() -> Result<(), StrError> {
+ let data = vec![
+ // --- Row 0 ---
+ vec![
+ vec![1.0, 0.0, 0.0], // Pixel 0,0: Red
+ vec![0.0, 1.0, 0.0], // Pixel 0,1: Green
+ vec![0.0, 0.0, 1.0], // Pixel 0,2: Blue
+ ],
+ // --- Row 1 ---
+ vec![
+ vec![1.0, 1.0, 0.0], // Pixel 1,0: Yellow
+ vec![1.0, 0.0, 1.0], // Pixel 1,1: Magenta
+ vec![0.0, 1.0, 1.0], // Pixel 1,2: Cyan
+ ],
+ // --- Row 2 ---
+ vec![
+ vec![0.5, 0.5, 0.5], // Pixel 2,0: Gray
+ vec![1.0, 1.0, 1.0], // Pixel 2,1: White
+ vec![0.0, 0.0, 0.0], // Pixel 2,2: Black
+ ],
+ ];
+
+ // image plot and options
+ let mut img = Image::new();
+ img.set_colormap_name("terrain")
+ .set_extra("alpha=0.8")
+ .draw_rgb_or_rgba(&data);
+
+ let mut plot = Plot::new();
+ plot.add(&img);
+
+ // save figure
+ let path = Path::new(OUT_DIR).join("integ_image_with_rgb.svg");
+ plot.set_show_errors(true).save(&path)?;
+
+ // check number of lines
+ let file = File::open(path).map_err(|_| "cannot open file")?;
+ let buffered = BufReader::new(file);
+ let lines_iter = buffered.lines();
+ let c = lines_iter.count();
+ assert!(c > 400 && c < 430);
+ Ok(())
+}
+
+#[test]
+fn test_image_with_rgba() -> Result<(), StrError> {
+ let data = vec![
+ // --- Row 0 ---
+ vec![
+ vec![1.0, 0.0, 0.0, 0.5], // Pixel 0,0: Red
+ vec![0.0, 1.0, 0.0, 0.5], // Pixel 0,1: Green
+ vec![0.0, 0.0, 1.0, 0.5], // Pixel 0,2: Blue
+ ],
+ // --- Row 1 ---
+ vec![
+ vec![1.0, 1.0, 0.0, 0.8], // Pixel 1,0: Yellow
+ vec![1.0, 0.0, 1.0, 0.8], // Pixel 1,1: Magenta
+ vec![0.0, 1.0, 1.0, 0.8], // Pixel 1,2: Cyan
+ ],
+ // --- Row 2 ---
+ vec![
+ vec![0.5, 0.5, 0.5, 0.2], // Pixel 2,0: Gray
+ vec![1.0, 1.0, 1.0, 0.2], // Pixel 2,1: White
+ vec![0.0, 0.0, 0.0, 0.2], // Pixel 2,2: Black
+ ],
+ ];
+
+ // image plot and options
+ let mut img = Image::new();
+ img.set_colormap_name("terrain")
+ .set_extra("alpha=0.8")
+ .draw_rgb_or_rgba(&data);
+
+ let mut plot = Plot::new();
+ plot.add(&img);
+
+ // save figure
+ let path = Path::new(OUT_DIR).join("integ_image_with_rgba.svg");
+ plot.set_show_errors(true).save(&path)?;
+
+ // check number of lines
+ let file = File::open(path).map_err(|_| "cannot open file")?;
+ let buffered = BufReader::new(file);
+ let lines_iter = buffered.lines();
+ let c = lines_iter.count();
+ assert!(c > 400 && c < 430);
+ Ok(())
+}