Feat: cleanup and favicon
This commit is contained in:
parent
e12bec0b13
commit
3d32fa6bd8
5 changed files with 197 additions and 141 deletions
153
src/main.rs
153
src/main.rs
|
@ -1,13 +1,19 @@
|
||||||
use axum::{extract::Query, response::Html, routing::get, Router};
|
use axum::Router;
|
||||||
use cairo::{Context, Format, ImageSurface};
|
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
mod polygon;
|
||||||
|
mod routes;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SharedState {}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// build our application with a route
|
let state = SharedState {};
|
||||||
let app = Router::new().route("/", get(handler));
|
let app = Router::new()
|
||||||
|
.nest("/logo", routes::logo::routes())
|
||||||
|
.nest("/favicon.ico", routes::favicon::routes())
|
||||||
|
.with_state(state);
|
||||||
|
|
||||||
// run it
|
// run it
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
@ -17,138 +23,3 @@ async fn main() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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)| {
|
|
||||||
let r = r as u16;
|
|
||||||
let g = g as u16;
|
|
||||||
let b = b as u16;
|
|
||||||
if dark_mode && (r + g + b) < 100 {
|
|
||||||
return false;
|
|
||||||
} else if !dark_mode && (r + g + b) > 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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
115
src/polygon.rs
Normal file
115
src/polygon.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use cairo::Context;
|
||||||
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
|
|
||||||
|
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)| {
|
||||||
|
let r = r as u16;
|
||||||
|
let g = g as u16;
|
||||||
|
let b = b as u16;
|
||||||
|
if dark_mode && (r + g + b) < 100 {
|
||||||
|
return false;
|
||||||
|
} else if !dark_mode && (r + g + b) > 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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_segmented_polygon(
|
||||||
|
center: (f64, f64),
|
||||||
|
side_length: f64,
|
||||||
|
num_sides: i32,
|
||||||
|
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, num_sides, 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_polygon_of_polygons(
|
||||||
|
center: (f64, f64),
|
||||||
|
side_length: f64,
|
||||||
|
num_sides: i32,
|
||||||
|
context: &Context,
|
||||||
|
dark_mode: bool,
|
||||||
|
) {
|
||||||
|
let corners = calculate_polygon_corners(center, num_sides, side_length, 0.0);
|
||||||
|
for corner in corners {
|
||||||
|
draw_segmented_polygon(corner, side_length * 0.4, num_sides, &context, dark_mode);
|
||||||
|
}
|
||||||
|
}
|
26
src/routes/favicon.rs
Normal file
26
src/routes/favicon.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use axum::{routing::get, Router};
|
||||||
|
use cairo::{Context, Format, ImageSurface};
|
||||||
|
|
||||||
|
use crate::{polygon, SharedState};
|
||||||
|
|
||||||
|
async fn handler() -> impl axum::response::IntoResponse {
|
||||||
|
let surface = ImageSurface::create(Format::ARgb32, 100, 100).unwrap();
|
||||||
|
let context = Context::new(&surface).unwrap();
|
||||||
|
|
||||||
|
context.set_source_rgba(0.0, 0.0, 0.0, 0.0);
|
||||||
|
context.fill().unwrap();
|
||||||
|
|
||||||
|
polygon::draw_polygon_of_polygons((50.0, 50.0), 65.0, 6, &context, false);
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Router<SharedState> {
|
||||||
|
Router::new().route("/", get(handler))
|
||||||
|
}
|
42
src/routes/logo.rs
Normal file
42
src/routes/logo.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use axum::{extract::Query, routing::get, Router};
|
||||||
|
use cairo::{Context, Format, ImageSurface};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{polygon, SharedState};
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
polygon::draw_polygon_of_polygons((200.0, 200.0), 200.0, 6, &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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Router<SharedState> {
|
||||||
|
Router::new().route("/", get(handler))
|
||||||
|
}
|
2
src/routes/mod.rs
Normal file
2
src/routes/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod favicon;
|
||||||
|
pub mod logo;
|
Loading…
Reference in a new issue