diff --git a/.gitignore b/.gitignore index 3ca43ae..193d30e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,8 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0bbbc5c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "logo-generator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +png = "0.11.0" +cairo-rs = { version = "0.17.0", features = ["png"] } +axum = "0.6.10" +tokio = { version = "1.0", features = ["full"] } +base64 = "0.21.0" +rand = "0.8.5" +serde = { version = "1.0.152", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..030d31d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,151 @@ +use axum::{extract::Query, response::Html, routing::get, Router}; +use cairo::{Context, Format, ImageSurface}; +use rand::{rngs::ThreadRng, Rng}; +use serde::Deserialize; +use std::net::SocketAddr; + +#[tokio::main] +async fn main() { + // build our application with a route + let app = Router::new().route("/", get(handler)); + + // run it + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + println!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +fn calculate_polygon_corners( + center: (f64, f64), + num_sides: i32, + side_length: f64, + rotation_angle_degrees: f64, +) -> Vec<(f64, f64)> { + // no idea how this code works, it was written by ChatGPT + let mut corners = Vec::new(); + let rotation_angle = rotation_angle_degrees.to_radians(); + let angle = std::f64::consts::PI / (num_sides as f64); + let radius = side_length / (2.0 * angle.cos()); + + for i in 0..num_sides { + let vertex_angle = angle * (2.0 * i as f64 + 1.0 - num_sides as f64) + rotation_angle; + let x = center.0 + radius * vertex_angle.sin(); + let y = center.1 + radius * vertex_angle.cos(); + corners.push((x, y)); + } + + corners +} + +fn set_color_i8((r, g, b): (u8, u8, u8), context: &Context) { + context.set_source_rgb(r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0); +} + +fn generate_color( + rng: &mut ThreadRng, + dark_mode: bool, + unlike: Option<(u8, u8, u8)>, +) -> (u8, u8, u8) { + let criteria = |(r, g, b): (u8, u8, u8)| { + if dark_mode && (r as u16 + g as u16 + b as u16) < 100 { + return false; + } else if !dark_mode && (r as u16 + g as u16 + b as u16) > 600 { + return false; + } + + if let Some((r1, g1, b1)) = unlike { + if (r.abs_diff(r1) as u16 + g.abs_diff(g1) as u16 + b.abs_diff(b1) as u16) < 200 { + return false; + } + } + + return true; + }; + + let mut color = rng.gen(); + + while !criteria(color) { + color = rng.gen(); + } + + color +} + +fn draw_segmented_polygon( + center: (f64, f64), + side_length: f64, + context: &Context, + dark_mode: bool, +) { + let mut rng = rand::thread_rng(); + let c1 = generate_color(&mut rng, dark_mode, None); + set_color_i8(c1, context); + context.new_path(); + + let corners = calculate_polygon_corners(center, 6, side_length, 30.0); + let corner1: u8 = rng.gen_range(0..6); + let corner2: u8 = (corner1 + rng.gen_range(2..5)) % 6; + + let from_corner = std::cmp::min(corner1, corner2); + let to_corner = std::cmp::max(corner1, corner2); + + // draw one side + for i in from_corner..(to_corner + 1) { + let (x, y) = corners[i as usize]; + context.line_to(x, y); + } + + context.close_path(); + context.fill().unwrap(); + + let c2 = generate_color(&mut rng, dark_mode, Some(c1)); + set_color_i8(c2, context); + context.new_path(); + // draw other side + for i in to_corner..(from_corner + 1 + 6) { + let (x, y) = corners[(i % 6) as usize]; + context.line_to(x, y); + } + + context.close_path(); + context.fill().unwrap(); +} + +fn default_as_false() -> bool { + false +} + +#[derive(Deserialize)] +struct ImageProperties { + #[serde(default = "default_as_false")] + dark_mode: bool, +} + +async fn handler(Query(properties): Query) -> impl axum::response::IntoResponse { + let surface = ImageSurface::create(Format::ARgb32, 400, 400).unwrap(); + let context = Context::new(&surface).unwrap(); + + if properties.dark_mode { + context.set_source_rgb(0.0, 0.0, 0.0); + } else { + context.set_source_rgb(1.0, 1.0, 1.0); + } + + context.paint().unwrap(); + + let corners = calculate_polygon_corners((200.0, 200.0), 6, 200.0, 0.0); + for corner in corners { + draw_segmented_polygon(corner, 80.0, &context, properties.dark_mode); + } + + let mut data: Vec = Vec::new(); + surface.write_to_png(&mut data).unwrap(); + + ( + axum::response::AppendHeaders([(axum::http::header::CONTENT_TYPE, "image/png")]), + data, + ) +}