Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace show image crate #55

Merged
merged 23 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ http = "0.2.9"
http-body = "0.4.5"
hyper = "0.14.27"
image = "0.24.3"
imageproc = "0.23.0"
iref = "^2.0.3"
lazy_static = "1.4.0"
log = "^0.4"
Expand All @@ -56,10 +57,10 @@ parking_lot = "0.12.1"
prost = "0.12"
prost-types = "0.12"
regex = " 1.9.3"
sdl2 = "0.35.2"
serde = "1.0.160"
serde_derive = "1.0.163"
serde_json = "^1.0"
show-image = "0.13.1"
strum = "0.25"
strum_macros = "0.25.1"
tokio = "1.29.1"
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- [Install gcc](#install-gcc)
- [Install Rust](#install-rust)
- [Install Protobuf Compiler](#install-protobuf-compiler)
- [Install fontconfig-dev library](#install-fontconfig-dev-library)
- [Install SDL2 library](#install-sdl2-library")
- [Install MQTT Broker](#install-mqtt-broker)
- [Cloning the Repo](#cloning-the-repo)
- [Developer Notes](#developer-notes)
Expand Down Expand Up @@ -67,12 +67,12 @@ You will need to install the Protobuf Compiler. This can be done by executing:
sudo apt install -y protobuf-compiler
```

### <a name="install-fontconfig-dev-library">Install fontconfig-dev library</a>
### <a name="install-sdl2-library">Install SDL2 library</a>

You will need to install the fontconfig-dev library. This can be done by executing:
You will need to install the libsdl2-2.0-0 library. This can be done by executing:

```shell
sudo apt install -y libfontconfig-dev
sudo apt install libsdl2-2.0-0
```

### <a name="install-mqtt-broker">Install MQTT Broker</a>
Expand Down
5 changes: 3 additions & 2 deletions core/invehicle-digital-twin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ yaml-rust = { workspace = true, optional = true }
tonic-build = { workspace = true }

[features]
default = ["managed_subscribe", "yaml"]
managed_subscribe = ["dep:managed_subscribe"]
default = ["yaml"]
# default = ["managed_subscribe", "yaml"]
# managed_subscribe = ["dep:managed_subscribe"]
yaml = ["yaml-rust"]
2 changes: 2 additions & 0 deletions samples/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ license = "MIT"

[dependencies]
config = { workspace = true }
image = { workspace = true }
log = { workspace = true }
samples-protobuf-data-access = { path = "../protobuf_data_access" }
sdl2 = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_derive = { workspace = true }
tokio = { workspace = true }
Expand Down
103 changes: 103 additions & 0 deletions samples/common/src/image_rendering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// SPDX-License-Identifier: MIT

use image::{imageops::FilterType, DynamicImage};
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::rect::Rect;
use sdl2::render::WindowCanvas;
use sdl2::surface::Surface;
use sdl2::Sdl;

// This module is based on SDL2 (Simple DirectMedia Layer). It complements the sdl2 crate by providing
// methods that make it easier to render images.

/// Create a canvas with an enclosing window.
///
/// # Arguments
/// * `sdl_context` - The SDL context.
/// * `window_title` - The window's title.
/// * `window_width` - The window's width.
/// * `window_height` - The window's height.
pub fn create_canvas(
sdl_context: &mut Sdl,
window_title: &str,
window_width: u32,
window_height: u32,
) -> Result<WindowCanvas, String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for rust libraries it's generally not recommended to use strings for the error type. this makes it hard to do anything but log the error. I think other places in Ibeji are using Status (e.g. here) so ideally we should use that here too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used String to be consistent with the sdl2 crate's methods, which use String.

Status comes from the tonic crate and is currently only used in conjunction with gRPC-related calls.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used String to be consistent with the sdl2 crate's methods, which use String.

Converting errors can be done with map_err. It's also worth mentioning that the library owner would prefer to use a proper error type but hasn't made the change due to concerns with breaking changes: Rust-SDL2/rust-sdl2#1053

Status comes from the tonic crate and is currently only used in conjunction with gRPC-related calls.

Then Status wouldn't be the right thing to use, we should find something else. Consider defining your own error type similar to what Freyja does, or you could use anyhow (not recommended for libraries but easy to use and better than String) or thiserror, or even reuse std::io::Error

let video_subsystem = sdl_context.video()?;

let window = video_subsystem
.window(window_title, window_width, window_height)
.position_centered()
.allow_highdpi()
.build()
.map_err(|err| format!("{}", err))?;

let mut canvas = window.into_canvas().build().map_err(|err| format!("{}", err))?;

// Set the background color to black.
canvas.set_draw_color(Color::RGB(0, 0, 0));

Ok(canvas)
}

/// Resize an image to fit inside a canvas.
///
/// # Arguments
/// * `image` - The image that needs to be resized.
/// * `canvas` - The canvas that it needs to fit in.
pub fn resize_image_to_fit_in_canvas(
image: DynamicImage,
canvas: &WindowCanvas,
) -> Result<DynamicImage, String> {
let (window_width, window_height): (u32, u32) = canvas.output_size()?;

let width_scale = window_width as f32 / image.width() as f32;
let height_scale = window_height as f32 / image.height() as f32;
let scale: f32 = height_scale.min(width_scale);

let resized_image_width = (scale * image.width() as f32) as u32;
let resized_image_height = (scale * image.height() as f32) as u32;

Ok(image.resize(resized_image_width, resized_image_height, FilterType::Triangle))
}

/// Render an image to a canvas.
///
/// # Arguments
/// * `image` - The image that we want to render.
/// * `canvas` - The canvas that will render the image.
pub fn render_image_to_canvas(
image: &DynamicImage,
canvas: &mut WindowCanvas,
) -> Result<(), String> {
// Prepare the image for copying it to a surface.
let rgb_image = image.to_rgb8();
let mut image_buffer = rgb_image.into_raw();

let image_width = image.width();
let image_height = image.height();
// The pitch is the width of the texture times the size of a single pixel in bytes.
// Since we are using 24 bit pixels (RGB24), we need to mutiple the width by 3.
let image_pitch: u32 = image_width * 3;

let surface = Surface::from_data(
&mut image_buffer,
image_width,
image_height,
image_pitch,
PixelFormatEnum::RGB24,
)
.map_err(|err| format!("{}", err))?;

let texture_creator = canvas.texture_creator();
let texture = texture_creator.create_texture_from_surface(surface).unwrap();
jorchiu marked this conversation as resolved.
Show resolved Hide resolved

// Render the image.
canvas.clear();
canvas.copy(&texture, None, Rect::new(0, 0, image_width, image_height))?;
canvas.present();

Ok(())
}
1 change: 1 addition & 0 deletions samples/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

pub mod constants;
pub mod consumer_config;
pub mod image_rendering;
pub mod provider_config;
pub mod utils;
3 changes: 2 additions & 1 deletion samples/streaming/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ config = { workspace = true }
digital-twin-model = { path = "../../digital-twin-model" }
env_logger = { workspace = true }
image = { workspace = true }
imageproc = { workspace = true, features = ["display-window"] }
log = { workspace = true }
paho-mqtt = { workspace = true }
parking_lot = { workspace = true }
prost = { workspace = true }
samples-common = { path = "../common" }
samples-protobuf-data-access = { path = "../protobuf_data_access" }
sdl2 = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_derive = { workspace = true }
serde_json = { workspace = true }
show-image = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal", "sync"] }
tokio-stream = { workspace = true }
tonic = { workspace = true }
Expand Down
42 changes: 21 additions & 21 deletions samples/streaming/consumer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ mod streaming_consumer_config;

use digital_twin_model::sdv_v1 as sdv;
use env_logger::{Builder, Target};

use image::io::Reader as ImageReader;
use image::{DynamicImage, io::Reader as ImageReader};
use log::{info, LevelFilter, warn};
use samples_common::constants::{digital_twin_operation, digital_twin_protocol};
use samples_common::image_rendering::{create_canvas, render_image_to_canvas, resize_image_to_fit_in_canvas};
use samples_common::utils::{
discover_digital_twin_provider_using_ibeji, retrieve_invehicle_digital_twin_uri,
};
use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::StreamRequest;
use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_client::DigitalTwinProviderClient;
use show_image::{ImageView, ImageInfo, create_window, WindowProxy};
use std::error::Error;
use std::io::Cursor;
use tokio_stream::StreamExt;
Expand All @@ -26,13 +25,15 @@ use tonic::transport::Channel;
/// # Arguments
/// * `client` - The client connection to the service that will transfer the stream.
ashbeitz marked this conversation as resolved.
Show resolved Hide resolved
/// * `number_of_images` - The number of images that we will stream.
/// * `window` - The window where the streamed images will be shown.
async fn stream_images(
client: &mut DigitalTwinProviderClient<Channel>,
entity_id: &str,
number_of_images: usize,
window: &mut WindowProxy,
) -> Result<(), Box<dyn Error>> {
let mut sdl_context = sdl2::init()?;

let mut canvas = create_canvas(&mut sdl_context, "Streamed Image", 800, 500)?;
jorchiu marked this conversation as resolved.
Show resolved Hide resolved

let stream =
client.stream(StreamRequest { entity_id: entity_id.to_string() }).await?.into_inner();

Expand All @@ -46,11 +47,20 @@ async fn stream_images(
}
let media_content = opt_media.unwrap().media_content;
jorchiu marked this conversation as resolved.
Show resolved Hide resolved
let image_reader = ImageReader::new(Cursor::new(media_content)).with_guessed_format()?;
let image = image_reader.decode()?;
let image_data = image.as_bytes().to_vec();
let image_view =
ImageView::new(ImageInfo::rgb8(image.width(), image.height()), &image_data);
window.set_image("some file", image_view)?;
let image: DynamicImage = image_reader.decode()?;

let resized_image = match resize_image_to_fit_in_canvas(image, &canvas) {
Ok(value) => value,
Err(err) => {
warn!("Failed to resize the image due to: {err}");
// Skip this image.
continue;
}
};

if let Err(err) = render_image_to_canvas(&resized_image, &mut canvas) {
warn!("Failed to render the image due to: {err}");
}
}

// The stream is dropped when we exit the function and the disconnect info is sent to the server.
Expand All @@ -59,7 +69,6 @@ async fn stream_images(
}

#[tokio::main]
#[show_image::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Setup logging.
Builder::new().filter(None, LevelFilter::Info).target(Target::Stdout).init();
Expand Down Expand Up @@ -87,17 +96,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
let provider_uri = provider_endpoint_info.uri;
info!("The provider URI for the Cabin Camera Feed property's provider is {provider_uri}");

// Create a window with default options and display the image.
let mut window = create_window("image", Default::default())?;

let mut client = DigitalTwinProviderClient::connect(provider_uri.clone()).await.unwrap();
stream_images(
&mut client,
sdv::camera::feed::ID,
settings.number_of_images.into(),
&mut window,
)
.await?;
stream_images(&mut client, sdv::camera::feed::ID, settings.number_of_images.into()).await?;

info!("The Consumer has completed.");

Expand Down
Loading