diff --git a/README.md b/README.md index 1480771..1c9db8c 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,42 @@ To generate documentation for all packages including dependencies: cargo doc --open ``` +## CLI Arguments + +Represents command line arguments the CLI application + +### Client arguments + +```bash +Usage: client [OPTIONS] + +Options: + + --hostname Optional: The hostname of the client. Default: "localhost" [default: localhost] + -P, --port Optional: The port number to connect on. Default: 8787 [default: 8787] + -N, --name Optional: The name of the worker. Default: "worker" [default: worker] + -v, --verbose Optional: Add a flag to enable/disable logging. Default: false + -d, --debug Optional: Add a flag to enable/disable debug mode. Default: false + -o, --open Optional: Add a flag to enable/disable opening the browser. Default: false + -s, --save Optional: Add a flag to save the image to a file. Default: false + -h, --help Print help (see more with '--help') +``` + +### Server arguments + +```bash +Usage: server [OPTIONS] + +Options: + --hostname The hostname of the server. Default: "localhost" [default: localhost] + -P, --port The port number the server listens on. Default: 8787 [default: 8787] + -v, --verbose Optional: Add a flag to enable/disable logging. Default: false + -d, --debug Optional: Add a flag to enable/disable debug mode. Default: false + --width Optional: Add a flag to edit the width and height of the window. Default: 1200 [default: 1200] + --height Optional: Add a flag to edit the width and height of the window. Default: 1200 [default: 1200] + -h, --help Print help (see more with '--help') +``` + ## Contributing Contributions are welcome. Please follow standard contribution guidelines for pull requests. diff --git a/cli/src/operation.rs b/cli/src/operation.rs index 3ba93a8..5afc54d 100644 --- a/cli/src/operation.rs +++ b/cli/src/operation.rs @@ -8,6 +8,8 @@ mod operation_tests { port: 8787, verbose: false, debug: false, + width: 1, + height: 1, } } @@ -36,6 +38,8 @@ mod operation_tests { port: args.port.clone(), verbose: args.verbose.clone(), debug: args.debug.clone(), + width: 1, + height: 1, }; let address = format!("{}:{}", server_args.hostname, server_args.port); diff --git a/cli/src/parser.rs b/cli/src/parser.rs index 7b2c434..2e7c40a 100644 --- a/cli/src/parser.rs +++ b/cli/src/parser.rs @@ -19,7 +19,7 @@ pub use clap::Parser; pub struct CliClientArgs { /// Optional: The hostname of the client. /// Default: "localhost" - #[clap(short = 'H', long = "hostname", default_value = "localhost")] + #[clap(long = "hostname", default_value = "localhost")] pub hostname: String, /// Optional: The port number to connect on. @@ -61,6 +61,10 @@ pub struct CliClientArgs { /// ## Fields /// - `hostname`: A string specifying the hostname. Defaults to "localhost". /// - `port`: A 16-bit unsigned integer specifying the port number. Defaults to 8787. +/// - `verbose`: A boolean flag to enable/disable logging. Defaults to false. +/// - `debug`: A boolean flag to enable/disable debug mode. Defaults to false. +/// - `width`: A 16-bit unsigned integer specifying the width of the window. Defaults to 1200. +/// - `height`: A 16-bit unsigned integer specifying the height of the window. Defaults to 1200. /// /// ## Example /// Command line usage for the server might be: @@ -71,7 +75,7 @@ pub struct CliClientArgs { pub struct CliServerArgs { /// The hostname of the server. /// Default: "localhost" - #[clap(short = 'H', long = "hostname", default_value = "localhost")] + #[clap(long = "hostname", default_value = "localhost")] pub hostname: String, /// The port number the server listens on. @@ -88,6 +92,16 @@ pub struct CliServerArgs { /// Default: false #[clap(short = 'd', long = "debug", default_value = "false")] pub debug: bool, + + /// Optional: Add a flag to edit the width and height of the window. + /// Default: 1200 + #[clap(long = "width", default_value = "1200")] + pub width: u16, + + /// Optional: Add a flag to edit the width and height of the window. + /// Default: 1200 + #[clap(long = "height", default_value = "1200")] + pub height: u16, } /// An enumeration representing the possible types of command line arguments. diff --git a/client/src/fractal_generation.rs b/client/src/fractal_generation.rs index 8c17fb5..c4cd7c8 100644 --- a/client/src/fractal_generation.rs +++ b/client/src/fractal_generation.rs @@ -2,7 +2,6 @@ use complex::complex_operations::ComplexOperations; use complex::fractal_operations::FractalOperations; use image::{ImageBuffer, Rgb}; use log::info; -use shared::types::color::{HSL, RGB}; use shared::types::complex::Complex; use shared::types::error::FractalError; use shared::types::fractal_descriptor::FractalType::{ @@ -10,6 +9,7 @@ use shared::types::fractal_descriptor::FractalType::{ }; use shared::types::messages::FragmentTask; use shared::types::pixel_intensity::PixelIntensity; +use shared::utils::colors_utils::color; /// Generates an image of a Fractal Type based on the provided fragment task. /// @@ -41,10 +41,7 @@ pub fn generate_fractal_set( let scale_y = (range.max.y - range.min.y) / resolution.ny as f64; let mut img = ImageBuffer::new(resolution.nx.into(), resolution.ny.into()); - let mut pixel_data_vec = Vec::new(); - - // crée une matrice de pixel_intensity let mut pixel_matrice_intensity = Vec::new(); info!("Generating fractal set..."); @@ -55,11 +52,8 @@ pub fn generate_fractal_set( let pixel_intensity = descriptor.compute_pixel_intensity(&complex_point, fragment_task.max_iteration); - if pixel_intensity.count != 1.0 { - *pixel = Rgb(color(pixel_intensity)); - } else { - *pixel = Rgb([0, 0, 0]); - } + + *pixel = Rgb(color(pixel_intensity)); pixel_matrice_intensity.push(pixel_intensity); pixel_data_vec.push(pixel[0]); @@ -70,57 +64,6 @@ pub fn generate_fractal_set( Ok((img, pixel_data_vec, pixel_matrice_intensity)) } -///Generates a color based on the provided pixel intensity. -/// # Arguments -/// * `pixel_intensity`: A `PixelIntensity` containing the number of iterations and the norm of the complex point. -/// -/// # Returns -/// Returns an array containing the RGB values of the color. -/// -fn color(pixel_intensity: PixelIntensity) -> [u8; 3] { - let hsl = HSL { - h: pixel_intensity.count * 360.0, - s: 0.5 + 0.5 * (pixel_intensity.zn * 3.14).cos(), - l: 0.5, - }; - - let color = hsl_to_rgb(hsl); - - [color.r, color.g, color.b] -} - -/// Convert a color from HSL to RGB -/// # Arguments -/// * `hsl`: A `HSL` containing the HSL values of the color (Hue, Saturation, Lightness) -/// -/// # Returns -/// Returns a tuple containing the RGB values of the color -/// -/// # Details -/// This function is based on the algorithm found at https://www.rapidtables.com/convert/color/hsl-to-rgb.html -/// -fn hsl_to_rgb(hsl: HSL) -> RGB { - let c = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s; - let h_prime = hsl.h / 60.0; - let x = c * (1.0 - (h_prime % 2.0 - 1.0).abs()); - let m = hsl.l - c / 2.0; - - let (r_temp, g_temp, b_temp) = match h_prime.floor() as u8 { - 0 => (c, x, 0.0), - 1 => (x, c, 0.0), - 2 => (0.0, c, x), - 3 => (0.0, x, c), - 4 => (x, 0.0, c), - _ => (c, 0.0, x), - }; - - RGB { - r: ((r_temp + m) * 255.0) as u8, - g: ((g_temp + m) * 255.0) as u8, - b: ((b_temp + m) * 255.0) as u8, - } -} - #[cfg(test)] mod julia_descriptor_tests { use complex::complex_operations::ComplexOperations; @@ -135,7 +78,6 @@ mod julia_descriptor_tests { use shared::types::range::Range; use shared::types::resolution::Resolution; use shared::types::u8data::U8Data; - use shared::utils::type_of::type_of; use super::*; @@ -284,24 +226,4 @@ mod julia_descriptor_tests { assert_eq!(img.dimensions(), (800, 600)); } } - - #[test] - fn test_color() { - let pixel_intensity = PixelIntensity { - zn: 0.5, - count: 0.5, - }; - - let result = color(pixel_intensity); - - let test0 = type_of(result[0]); - let test1 = type_of(result[1]); - let test2 = type_of(result[2]); - - assert!(test0.eq("u8")); - assert!(test1.eq("u8")); - assert!(test2.eq("u8")); - - assert_eq!(result, [63, 191, 191]); - } } diff --git a/client/src/networking.rs b/client/src/networking.rs index a88a291..ab145b0 100644 --- a/client/src/networking.rs +++ b/client/src/networking.rs @@ -133,7 +133,8 @@ pub fn process_fragment_task( ) -> Result { let dir_path_buf = get_dir_path_buf()?; - let img_path = get_file_path("julia", dir_path_buf, get_extension_str(FileExtension::PNG))?; + let img_path: String = + get_file_path("julia", dir_path_buf, get_extension_str(FileExtension::PNG))?; let (img, pixel_data_bytes, pixel_intensity_matrice) = generate_fractal_set(task.clone())?; debug!("Pixel data bytes: {:?}", pixel_data_bytes); diff --git a/server/src/image.rs b/server/src/image.rs deleted file mode 100644 index 8b13789..0000000 --- a/server/src/image.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/server/src/lib.rs b/server/src/lib.rs index d57c969..4b61f09 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,4 +1,3 @@ -pub mod image; pub mod messages; pub mod services; pub use serde::{Deserialize, Serialize}; diff --git a/server/src/main.rs b/server/src/main.rs index 1ad3474..6f1f05d 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,5 @@ +use std::env; + use cli::parser::{CliServerArgs, Parser}; use log::{error, info}; use server::services::server_runner::run_server; @@ -6,6 +8,8 @@ use shared::types::error::FractalError; fn main() -> Result<(), FractalError> { let cli_args: CliServerArgs = CliServerArgs::parse(); shared::logger::init_logger(cli_args.verbose, cli_args.debug)?; + env::set_var("RESOLUTION_WIDTH", cli_args.width.to_string()); + env::set_var("RESOLUTION_HEIGHT", cli_args.height.to_string()); let address = format!("{}:{}", cli_args.hostname, cli_args.port); match run_server(address.as_str()) { diff --git a/server/src/messages/fragment_maker.rs b/server/src/messages/fragment_maker.rs index 494b445..063fa2d 100644 --- a/server/src/messages/fragment_maker.rs +++ b/server/src/messages/fragment_maker.rs @@ -1,37 +1,72 @@ +use std::error::Error; + use shared::types::fractal_descriptor::FractalType::IteratedSinZ; use shared::types::{ complex::Complex, fractal_descriptor::{FractalDescriptor, IteratedSinZDescriptor}, - messages::{FragmentRequest, FragmentResult, FragmentTask}, + messages::{FragmentResult, FragmentTask}, point::Point, range::Range, resolution::Resolution, u8data::U8Data, }; +use shared::utils::env_utils::get_env_var_as_u16; + +pub fn create_tasks() -> Result, Box> { + let width = get_env_var_as_u16("RESOLUTION_WIDTH")?; + let height = get_env_var_as_u16("RESOLUTION_HEIGHT")?; + + let range = generate_range( + Range::new(Point::new(-3.0, -3.0), Point::new(3.0, 3.0)), + 2.0, + ); + let mut tasks = vec![]; -pub fn create_task_for_request(_request: FragmentRequest) -> FragmentTask { - FragmentTask { - id: U8Data { - offset: 0, - count: 16, - }, - fractal: FractalDescriptor { - fractal_type: IteratedSinZ(IteratedSinZDescriptor { - c: Complex { re: 0.2, im: 1.0 }, - }), - }, - max_iteration: 64, - resolution: Resolution { nx: 1080, ny: 1920 }, - range: Range { - min: Point { - x: -2.0, - y: -3.55556, + for r in range { + let task = FragmentTask { + id: U8Data { + offset: 0, + count: 16, + }, + fractal: FractalDescriptor { + fractal_type: IteratedSinZ(IteratedSinZDescriptor { + c: Complex { re: 0.2, im: 1.0 }, + }), + }, + max_iteration: 64, + resolution: Resolution { + nx: width, + ny: height, }, - max: Point { x: 2.0, y: 3.55556 }, - }, + range: r, + }; + + tasks.push(task); } + + Ok(tasks) } -pub fn process_result(_result: FragmentResult) { - todo!() +pub fn process_result(_result: FragmentResult) {} + +pub fn generate_range(full_image: Range, step: f64) -> Vec { + let mut ranges = Vec::new(); + let y_step = step; + let x_step = step; + + let mut y = full_image.min.y; + while y < full_image.max.y { + let mut x = full_image.min.x; + while x < full_image.max.x { + let min = Point::new(x, y); + let max = Point::new( + (x + x_step).min(full_image.max.x), + (y + y_step).min(full_image.max.y), + ); + ranges.push(Range::new(min, max)); + x += x_step; + } + y += y_step; + } + ranges } diff --git a/server/src/messages/handler.rs b/server/src/messages/handler.rs index 915bd5c..8068cbf 100644 --- a/server/src/messages/handler.rs +++ b/server/src/messages/handler.rs @@ -1,16 +1,22 @@ +use log::{debug, error, info}; +use shared::{ + types::{ + error::FractalError, filesystem::FileExtension, messages::Message, + pixel_intensity::PixelIntensity, + }, + utils::{ + filesystem::{get_dir_path_buf, get_extension_str, get_file_path}, + fragment_task_impl::FragmentTaskOperation, + image::image_from_pixel_intensity, + }, +}; use std::{ io::{self, Read}, net::TcpStream, }; -use log::{debug, error, info}; -use shared::{types::messages::Message, utils::fragment_task_impl::FragmentTaskOperation}; - -use super::{ - fragment_maker::{create_task_for_request, process_result}, - serialization::deserialize_message, -}; -use crate::services; +use super::serialization::deserialize_message; +use crate::{messages::fragment_maker::create_tasks, services}; /// Handles a client TCP stream. /// @@ -55,7 +61,7 @@ pub fn handle_client(mut stream: TcpStream) -> io::Result<()> { let json_size = u32::from_be_bytes(json_size_buffer) as usize; debug!("JSON size: {}", json_size); - let mut buffer = vec![0; total_size]; + let mut buffer = vec![0; json_size]; stream.read_exact(&mut buffer)?; let json_str = std::str::from_utf8(&buffer[0..json_size]).map_err(|e| { @@ -72,14 +78,31 @@ pub fn handle_client(mut stream: TcpStream) -> io::Result<()> { } }; + let mut pixel_intensity = vec![]; + if total_size - json_size > 0 { + let mut buffer = vec![0; total_size - json_size]; + stream.read_exact(&mut buffer)?; + debug!("Received data: {:?}", buffer); + pixel_intensity = PixelIntensity::vec_data_to_pixel_intensity_matrix(buffer); + } + debug!("Received data: {}", data_str); let message_result = deserialize_message(data_str); debug!("Deserialized data {:?}", message_result); match message_result { - Ok(Message::FragmentRequest(request)) => { - let task = create_task_for_request(request); + Ok(Message::FragmentRequest(_request)) => { + let task = match create_tasks() { + Ok(task) => task[0].clone(), + Err(e) => { + error!("Error creating task: {:?}", e); + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8")); + } + }; + + debug!("Task created: {:?}", task.clone()); + let serialized_task = match task.serialize() { Ok(serialized_task) => serialized_task, Err(e) => { @@ -101,8 +124,47 @@ pub fn handle_client(mut stream: TcpStream) -> io::Result<()> { Ok(Message::FragmentTask(_task)) => { todo!() } - Ok(Message::FragmentResult(result)) => { - process_result(result); + Ok(Message::FragmentResult(_result)) => { + //process_result(result); + + let img = match image_from_pixel_intensity(pixel_intensity) { + Ok(img) => img, + Err(e) => { + error!("Error creating image from pixel intensity: {:?}", e); + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8")); + } + }; + + let dir_path_buf = match get_dir_path_buf() { + Ok(dir_path_buf) => dir_path_buf, + Err(e) => { + error!("Error getting directory path: {:?}", e); + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8")); + } + }; + + let img_path: String = match get_file_path( + "test-23_02_23", + dir_path_buf, + get_extension_str(FileExtension::PNG), + ) { + Ok(img_path) => img_path, + Err(e) => { + error!("Error getting file path: {:?}", e); + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8")); + } + }; + + match img.save(img_path.clone()).map_err(FractalError::Image) { + Ok(_) => { + info!("Image saved successfully"); + debug!("Image path {}", img_path); + } + Err(e) => { + error!("Error saving image: {:?}", e); + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8")); + } + } } Err(e) => { error!("Error deserializing request: {:?}", e); diff --git a/server/src/messages/serialization.rs b/server/src/messages/serialization.rs index 7e745e3..c2a624a 100644 --- a/server/src/messages/serialization.rs +++ b/server/src/messages/serialization.rs @@ -2,8 +2,11 @@ use log::debug; use serde::de::Error as SerdeError; use shared::{ - types::messages::{FragmentRequest, Message}, - utils::fragment_request_impl::FragmentRequestOperation, + types::messages::{FragmentRequest, FragmentResult, Message}, + utils::{ + fragment_request_impl::FragmentRequestOperation, + fragment_result_impl::FragmentResultOperation, + }, }; /// Deserializes a JSON string into a `Message` enum variant. @@ -28,11 +31,9 @@ pub fn deserialize_message(response: &str) -> serde_json::Result { debug!("Deserializing FragmentRequest"); FragmentRequest::deserialize(response).map(Message::FragmentRequest) } - Some(key) if key == "FragmentTask" => { - todo!() - } Some(key) if key == "FragmentResult" => { - todo!() + debug!("Deserializing FragmentResult"); + FragmentResult::deserialize(response).map(Message::FragmentResult) } _ => Err(serde_json::Error::custom( "No recognizable message type found", diff --git a/server/src/services/reader.rs b/server/src/services/reader.rs index 277a676..e1ca84a 100644 --- a/server/src/services/reader.rs +++ b/server/src/services/reader.rs @@ -1,7 +1,6 @@ use std::{ io::{self, Read}, net::TcpStream, - time::Duration, }; use log::debug; @@ -72,6 +71,5 @@ pub fn read_message(stream: &mut TcpStream) -> io::Result<(String, Vec)> { } pub fn get_response(stream: &mut TcpStream) -> io::Result<(String, Vec)> { - stream.set_read_timeout(Some(Duration::new(5, 0)))?; Ok(read_message(stream)?) } diff --git a/server/src/services/write.rs b/server/src/services/write.rs index 440d408..643b8aa 100644 --- a/server/src/services/write.rs +++ b/server/src/services/write.rs @@ -74,14 +74,14 @@ pub fn write(stream: &mut TcpStream, message: &str) -> io::Result<()> { pub fn write_img(stream: &mut TcpStream, message: &str, img_data: Vec) -> io::Result<()> { let mut stream_clone = stream.try_clone()?; - let message_bytes = prepare_message(message); + let json_bytes = prepare_message(message); let json_size = (message.len() as u32).to_be_bytes(); let message_length = message.len() + img_data.len(); let total_size = (message_length as u32).to_be_bytes(); stream_clone.write_all(&total_size)?; stream_clone.write_all(&json_size)?; - stream_clone.write_all(&message_bytes)?; + stream_clone.write_all(&json_bytes)?; for byte in img_data { stream_clone.write_all(&[byte])?; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index b85f628..f8a8221 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" rand = "0.8.5" serde = { version = "1.0.193", features = ["derive"] } serde_json ="1.0.108" -env_logger = "0.10.1" +env_logger = "0.11.2" log = "0.4.20" image = { version = "0.24.7", features = [] } diff --git a/shared/src/types/point.rs b/shared/src/types/point.rs index d463c5c..33ec759 100644 --- a/shared/src/types/point.rs +++ b/shared/src/types/point.rs @@ -10,3 +10,19 @@ pub struct Point { pub x: f64, pub y: f64, } + +impl Point { + /// Creates a new `Point` with the specified x and y coordinates. + /// + /// # Arguments + /// + /// * `x` - A `f64` representing the x-coordinate of the point. + /// * `y` - A `f64` representing the y-coordinate of the point. + /// + /// # Returns + /// + /// A new `Point` with the specified x and y coordinates. + pub fn new(x: f64, y: f64) -> Point { + Point { x, y } + } +} diff --git a/shared/src/types/range.rs b/shared/src/types/range.rs index 3c0465b..8786ea1 100644 --- a/shared/src/types/range.rs +++ b/shared/src/types/range.rs @@ -12,3 +12,19 @@ pub struct Range { pub min: Point, pub max: Point, } + +impl Range { + /// Creates a new `Range` with the specified minimum and maximum points. + /// + /// # Arguments + /// + /// * `min` - A `Point` defining the minimum (bottom-left) corner of the range. + /// * `max` - A `Point` defining the maximum (top-right) corner of the range. + /// + /// # Returns + /// + /// A new `Range` with the specified minimum and maximum points. + pub fn new(min: Point, max: Point) -> Self { + Range { min, max } + } +} diff --git a/shared/src/types/resolution.rs b/shared/src/types/resolution.rs index 7ebbab5..6de05cb 100644 --- a/shared/src/types/resolution.rs +++ b/shared/src/types/resolution.rs @@ -10,3 +10,19 @@ pub struct Resolution { pub nx: u16, pub ny: u16, } + +impl Resolution { + /// Creates a new `Resolution` with the specified number of pixels along the x and y axes. + /// + /// # Arguments + /// + /// * `nx` - A `u16` representing the number of pixels along the x-axis (width). + /// * `ny` - A `u16` representing the number of pixels along the y-axis (height). + /// + /// # Returns + /// + /// A new `Resolution` with the specified number of pixels along the x and y axes. + pub fn new(nx: u16, ny: u16) -> Resolution { + Resolution { nx, ny } + } +} diff --git a/shared/src/utils/colors_utils.rs b/shared/src/utils/colors_utils.rs new file mode 100644 index 0000000..80694ed --- /dev/null +++ b/shared/src/utils/colors_utils.rs @@ -0,0 +1,79 @@ +use crate::types::{ + color::{HSL, RGB}, + pixel_intensity::PixelIntensity, +}; + +///Generates a color based on the provided pixel intensity. +/// # Arguments +/// * `pixel_intensity`: A `PixelIntensity` containing the number of iterations and the norm of the complex point. +/// +/// # Returns +/// Returns an array containing the RGB values of the color. +/// +pub fn color(pixel_intensity: PixelIntensity) -> [u8; 3] { + if pixel_intensity.count == 1.0 { + return [0, 0, 0]; + } + + let hsl = HSL { + h: pixel_intensity.count * 360.0, + s: 0.5 + 0.5 * (pixel_intensity.zn * 3.14).cos(), + l: 0.5, + }; + + let color = hsl_to_rgb(hsl); + + [color.r, color.g, color.b] +} + +/// Convert a color from HSL to RGB +/// # Arguments +/// * `hsl`: A `HSL` containing the HSL values of the color (Hue, Saturation, Lightness) +/// +/// # Returns +/// Returns a tuple containing the RGB values of the color +/// +/// # Details +/// This function is based on the algorithm found at https://www.rapidtables.com/convert/color/hsl-to-rgb.html +/// +pub fn hsl_to_rgb(hsl: HSL) -> RGB { + let c = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s; + let h_prime = hsl.h / 60.0; + let x = c * (1.0 - (h_prime % 2.0 - 1.0).abs()); + let m = hsl.l - c / 2.0; + + let (r_temp, g_temp, b_temp) = match h_prime.floor() as u8 { + 0 => (c, x, 0.0), + 1 => (x, c, 0.0), + 2 => (0.0, c, x), + 3 => (0.0, x, c), + 4 => (x, 0.0, c), + _ => (c, 0.0, x), + }; + + RGB { + r: ((r_temp + m) * 255.0) as u8, + g: ((g_temp + m) * 255.0) as u8, + b: ((b_temp + m) * 255.0) as u8, + } +} + +#[test] +fn test_color() { + let pixel_intensity = PixelIntensity { + zn: 0.5, + count: 0.5, + }; + + let result = color(pixel_intensity); + + let test0 = crate::utils::type_of::type_of(result[0]); + let test1 = crate::utils::type_of::type_of(result[1]); + let test2 = crate::utils::type_of::type_of(result[2]); + + assert!(test0.eq("u8")); + assert!(test1.eq("u8")); + assert!(test2.eq("u8")); + + assert_eq!(result, [63, 191, 191]); +} diff --git a/shared/src/utils/env_utils.rs b/shared/src/utils/env_utils.rs new file mode 100644 index 0000000..8e09ef6 --- /dev/null +++ b/shared/src/utils/env_utils.rs @@ -0,0 +1,15 @@ +use std::env; + +/// Tente de récupérer une variable d'environnement et de la convertir en `u16`. +/// Retourne `Ok` avec la valeur convertie ou `Err` avec un message d'erreur. +pub fn get_env_var_as_u16(var_name: &str) -> Result { + match env::var(var_name) { + Ok(value) => value.parse::().map_err(|e| { + format!( + "Erreur lors de la conversion de la variable d'environnement {} en u16: {}", + var_name, e + ) + }), + Err(_) => Err(format!("Variable d'environnement {} non définie", var_name)), + } +} diff --git a/shared/src/utils/image.rs b/shared/src/utils/image.rs new file mode 100644 index 0000000..5182c2d --- /dev/null +++ b/shared/src/utils/image.rs @@ -0,0 +1,22 @@ +use image::{ImageBuffer, Rgb}; + +use crate::types::{pixel_intensity::PixelIntensity, resolution::Resolution}; + +use super::{colors_utils::color, env_utils::get_env_var_as_u16}; + +pub fn image_from_pixel_intensity( + pixel_intensity: Vec, +) -> Result, Vec>, Box> { + let width = get_env_var_as_u16("RESOLUTION_WIDTH")?; + let height = get_env_var_as_u16("RESOLUTION_HEIGHT")?; + + let resolution = Resolution::new(width, height); + let mut img = ImageBuffer::new(resolution.nx.into(), resolution.ny.into()); + for (i, pixel_intensity) in pixel_intensity.iter().enumerate() { + let x = (i as u32) % resolution.nx as u32; + let y = (i as u32) / resolution.nx as u32; + img.put_pixel(x, y, Rgb(color(*pixel_intensity))); + } + + Ok(img) +} diff --git a/shared/src/utils/mod.rs b/shared/src/utils/mod.rs index 3aab99c..65f11e3 100644 --- a/shared/src/utils/mod.rs +++ b/shared/src/utils/mod.rs @@ -1,5 +1,9 @@ +pub mod colors_utils; +pub mod env_utils; pub mod filesystem; pub mod fragment_request_impl; pub mod fragment_result_impl; pub mod fragment_task_impl; +pub mod image; +pub mod pixel_intensity_impl; pub mod type_of; diff --git a/shared/src/utils/pixel_data_impl.rs b/shared/src/utils/pixel_data_impl.rs new file mode 100644 index 0000000..a4a12ac --- /dev/null +++ b/shared/src/utils/pixel_data_impl.rs @@ -0,0 +1,15 @@ +impl PixelData { + /// Creates a new `PixelData` with the specified resolution and pixel data. + /// + /// # Arguments + /// + /// * `resolution` - A `Resolution` representing the resolution of the pixel data. + /// * `data` - A `Vec` representing the pixel data. + /// + /// # Returns + /// + /// A new `PixelData` with the specified resolution and pixel data. + pub fn new(resolution: Resolution, data: Vec) -> PixelData { + PixelData { resolution, data } + } +} diff --git a/shared/src/utils/pixel_intensity_impl.rs b/shared/src/utils/pixel_intensity_impl.rs new file mode 100644 index 0000000..d9df72a --- /dev/null +++ b/shared/src/utils/pixel_intensity_impl.rs @@ -0,0 +1,62 @@ +use crate::types::pixel_intensity::PixelIntensity; + +impl PixelIntensity { + /// Creates a new `PixelIntensity` with the specified resolution and pixel data. + /// + /// # Arguments + /// - `zn`: A `f32` representing the magnitude at the end of the iteration process. + /// This is typically used to determine the color or intensity of a pixel in fractal images. + /// - `count`: A `f32` reflecting the number of iterations performed, normalized by the maximum number of iterations. + /// This value is often used to apply color gradients based on the iteration depth. + /// A new `PixelIntensity` with the specified resolution and pixel data. + pub fn new(zn: f32, count: f32) -> PixelIntensity { + PixelIntensity { zn, count } + } + + pub fn vec_data_to_pixel_intensity_matrix(vec_data: Vec) -> Vec { + let mut pixel_intensity_matrix = Vec::new(); + let mut i = 0; + + // Assurez-vous que i + la taille de zn + la taille de count n'est pas hors limite + while i + 8 + 4 <= vec_data.len() { + // Extraire zn + let zn_bytes = + <[u8; 4]>::try_from(&vec_data[i..i + 4]).expect("slice with incorrect length"); + let zn = f32::from_be_bytes(zn_bytes); + i += 4; + + // Extraire count + let count_bytes = + <[u8; 4]>::try_from(&vec_data[i..i + 4]).expect("slice with incorrect length"); + let count = f32::from_be_bytes(count_bytes); + i += 4; + + // Ajouter à la matrice + pixel_intensity_matrix.push(PixelIntensity { zn, count }); + } + + pixel_intensity_matrix + } +} + +#[cfg(test)] +mod pixel_intensity_tests { + use super::*; + + #[test] + fn should_convert_vector_to_pixel_intensity() { + let vec_data: Vec = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let result = PixelIntensity::vec_data_to_pixel_intensity_matrix(vec_data); + assert_eq!(result.len(), 1); + assert_eq!(result[0].zn, 0.0); + assert_eq!(result[0].count, 0.0); + } + + #[test] + fn should_handle_vector_with_incomplete_zn_bytes() { + let vec_data: Vec = vec![0, 0, 0, 0, 0, 0, 0]; + + let result = PixelIntensity::vec_data_to_pixel_intensity_matrix(vec_data); + assert_eq!(result.len(), 0); + } +}