Feat: inital poc
This commit is contained in:
parent
ee12e841f8
commit
f2b3e73212
3 changed files with 171 additions and 0 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -14,3 +14,8 @@ Cargo.lock
|
|||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
@ -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"] }
|
151
src/main.rs
Normal file
151
src/main.rs
Normal file
|
@ -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<ImageProperties>) -> 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<u8> = Vec::new();
|
||||
surface.write_to_png(&mut data).unwrap();
|
||||
|
||||
(
|
||||
axum::response::AppendHeaders([(axum::http::header::CONTENT_TYPE, "image/png")]),
|
||||
data,
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue