Compare commits
2 commits
75e5691824
...
ce6c45e285
Author | SHA1 | Date | |
---|---|---|---|
ce6c45e285 | |||
7c928f3249 |
10 changed files with 84 additions and 65 deletions
BIN
images/Makerlab.png
Normal file
BIN
images/Makerlab.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
12
src/color.rs
12
src/color.rs
|
@ -60,6 +60,18 @@ where
|
||||||
Ok(res.unwrap())
|
Ok(res.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
pub fn from_hex(hex: &str) -> Self {
|
||||||
|
serde_json::from_str(&format!("\"{hex}\"")).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Color {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Color::from_hex(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_color() {
|
fn test_serialize_color() {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#![feature(const_trait_impl)]
|
||||||
|
#![feature(const_convert)]
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
use cairo::Context;
|
use cairo::Context;
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
static COLORS: [(&str, &str); 3] = [
|
||||||
|
("#F4A263", "#E87052"),
|
||||||
|
("#E0DA48", "#969A1D"),
|
||||||
|
("#309E8F", "#2F3F52"),
|
||||||
|
];
|
||||||
|
|
||||||
fn calculate_polygon_corners(
|
fn calculate_polygon_corners(
|
||||||
center: (f64, f64),
|
center: (f64, f64),
|
||||||
num_sides: i32,
|
num_sides: i32,
|
||||||
side_length: f64,
|
radius: f64,
|
||||||
rotation_angle_degrees: f64,
|
rotation_angle_degrees: f64,
|
||||||
) -> Vec<(f64, f64)> {
|
) -> Vec<(f64, f64)> {
|
||||||
// no idea how this code works, it was written by ChatGPT
|
// no idea how this code works, it was written by ChatGPT
|
||||||
let mut corners = Vec::new();
|
let mut corners = Vec::new();
|
||||||
let rotation_angle = rotation_angle_degrees.to_radians();
|
let rotation_angle = rotation_angle_degrees.to_radians();
|
||||||
let angle = std::f64::consts::PI / (num_sides as f64);
|
let angle = std::f64::consts::PI / (num_sides as f64);
|
||||||
let radius = side_length / (2.0 * angle.cos());
|
|
||||||
|
|
||||||
for i in 0..num_sides {
|
for i in 0..num_sides {
|
||||||
let vertex_angle = angle * (2.0 * i as f64 + 1.0 - num_sides as f64) + rotation_angle;
|
let vertex_angle = angle * (2.0 * i as f64 + 1.0 - num_sides as f64) + rotation_angle;
|
||||||
|
@ -23,41 +30,18 @@ fn calculate_polygon_corners(
|
||||||
corners
|
corners
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_color_i8((r, g, b): (u8, u8, u8), context: &Context) {
|
fn set_color(color: Color, context: &Context) {
|
||||||
context.set_source_rgb(r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0);
|
context.set_source_rgb(
|
||||||
|
color.r as f64 / 255.0,
|
||||||
|
color.g as f64 / 255.0,
|
||||||
|
color.b as f64 / 255.0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_color(
|
fn generate_colors(rng: &mut ThreadRng) -> (Color, Color) {
|
||||||
rng: &mut ThreadRng,
|
let color_num = rng.gen_range(0..COLORS.len());
|
||||||
dark_mode: bool,
|
let (c1, c2) = COLORS[color_num];
|
||||||
unlike: Option<(u8, u8, u8)>,
|
(Color::from_hex(c1), Color::from_hex(c2))
|
||||||
) -> (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) < 300 {
|
|
||||||
return false;
|
|
||||||
} else if !dark_mode && (r + g + b) > 500 {
|
|
||||||
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_polygon(
|
pub fn draw_polygon(
|
||||||
|
@ -65,11 +49,10 @@ pub fn draw_polygon(
|
||||||
side_length: f64,
|
side_length: f64,
|
||||||
num_sides: i32,
|
num_sides: i32,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
dark_mode: bool,
|
|
||||||
) -> Result<(), cairo::Error> {
|
) -> Result<(), cairo::Error> {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let c1 = generate_color(&mut rng, dark_mode, None);
|
let (c1, _) = generate_colors(&mut rng);
|
||||||
set_color_i8(c1, context);
|
set_color(c1, context);
|
||||||
context.new_path();
|
context.new_path();
|
||||||
|
|
||||||
let corners = calculate_polygon_corners(center, num_sides, side_length, 30.0);
|
let corners = calculate_polygon_corners(center, num_sides, side_length, 30.0);
|
||||||
|
@ -89,11 +72,10 @@ pub fn draw_segmented_polygon(
|
||||||
side_length: f64,
|
side_length: f64,
|
||||||
num_sides: i32,
|
num_sides: i32,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
dark_mode: bool,
|
|
||||||
) -> Result<(), cairo::Error> {
|
) -> Result<(), cairo::Error> {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let c1 = generate_color(&mut rng, dark_mode, None);
|
let (c1, c2) = generate_colors(&mut rng);
|
||||||
set_color_i8(c1, context);
|
set_color(c1, context);
|
||||||
context.new_path();
|
context.new_path();
|
||||||
|
|
||||||
let corners = calculate_polygon_corners(center, num_sides, side_length, 30.0);
|
let corners = calculate_polygon_corners(center, num_sides, side_length, 30.0);
|
||||||
|
@ -112,8 +94,7 @@ pub fn draw_segmented_polygon(
|
||||||
context.close_path();
|
context.close_path();
|
||||||
context.fill()?;
|
context.fill()?;
|
||||||
|
|
||||||
let c2 = generate_color(&mut rng, dark_mode, Some(c1));
|
set_color(c2, context);
|
||||||
set_color_i8(c2, context);
|
|
||||||
context.new_path();
|
context.new_path();
|
||||||
// draw other side
|
// draw other side
|
||||||
for i in to_corner..(from_corner + 1 + 6) {
|
for i in to_corner..(from_corner + 1 + 6) {
|
||||||
|
@ -129,28 +110,27 @@ pub fn draw_segmented_polygon(
|
||||||
|
|
||||||
pub fn draw_polygon_of_segmented_polygons(
|
pub fn draw_polygon_of_segmented_polygons(
|
||||||
center: (f64, f64),
|
center: (f64, f64),
|
||||||
side_length: f64,
|
radius: f64,
|
||||||
|
inner_radius: f64,
|
||||||
num_sides: i32,
|
num_sides: i32,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
dark_mode: bool,
|
|
||||||
) -> Result<(), cairo::Error> {
|
) -> Result<(), cairo::Error> {
|
||||||
let corners = calculate_polygon_corners(center, num_sides, side_length, 0.0);
|
let corners = calculate_polygon_corners(center, num_sides, radius, 0.0);
|
||||||
for corner in corners {
|
for corner in corners {
|
||||||
draw_segmented_polygon(corner, side_length * 0.4, num_sides, &context, dark_mode)?;
|
draw_segmented_polygon(corner, inner_radius, num_sides, &context)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_polygon_of_polygons(
|
pub fn draw_polygon_of_polygons(
|
||||||
center: (f64, f64),
|
center: (f64, f64),
|
||||||
side_length: f64,
|
radius: f64,
|
||||||
num_sides: i32,
|
num_sides: i32,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
dark_mode: bool,
|
|
||||||
) -> Result<(), cairo::Error> {
|
) -> Result<(), cairo::Error> {
|
||||||
let corners = calculate_polygon_corners(center, num_sides, side_length, 0.0);
|
let corners = calculate_polygon_corners(center, num_sides, radius, 0.0);
|
||||||
for corner in corners {
|
for corner in corners {
|
||||||
draw_polygon(corner, side_length * 0.4, num_sides, &context, dark_mode)?;
|
draw_polygon(corner, radius * 0.4, num_sides, &context)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ async fn handler() -> impl axum::response::IntoResponse {
|
||||||
context.set_source_rgba(0.0, 0.0, 0.0, 0.0);
|
context.set_source_rgba(0.0, 0.0, 0.0, 0.0);
|
||||||
context.fill().unwrap();
|
context.fill().unwrap();
|
||||||
|
|
||||||
polygon::draw_polygon_of_polygons((50.0, 50.0), 65.0, 6, &context, false).unwrap();
|
polygon::draw_polygon_of_polygons((50.0, 50.0), 65.0, 6, &context).unwrap();
|
||||||
|
|
||||||
let mut data: Vec<u8> = Vec::new();
|
let mut data: Vec<u8> = Vec::new();
|
||||||
surface.write_to_png(&mut data).unwrap();
|
surface.write_to_png(&mut data).unwrap();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use axum::{extract::Query, routing::get, Router};
|
use axum::{extract::Query, routing::get, Router};
|
||||||
use cairo::{Context, Format, ImageSurface};
|
use cairo::{Context, Format, ImageSurface};
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
use serde::{de, Deserialize};
|
use serde::{de, Deserialize};
|
||||||
|
|
||||||
use crate::{color::Color, polygon, SharedState};
|
use crate::{color::Color, polygon, SharedState};
|
||||||
|
@ -29,11 +30,33 @@ struct ImageProperties {
|
||||||
#[serde(default = "default_as_false")]
|
#[serde(default = "default_as_false")]
|
||||||
#[serde(deserialize_with = "deserialize_bool")]
|
#[serde(deserialize_with = "deserialize_bool")]
|
||||||
dark_mode: bool,
|
dark_mode: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(deserialize_with = "deserialize_bool")]
|
||||||
|
text: bool,
|
||||||
background_color: Option<Color>,
|
background_color: Option<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "images"]
|
||||||
|
struct ImageFiles;
|
||||||
|
|
||||||
|
fn get_surface_and_logo_coordiates(properties: &ImageProperties) -> (ImageSurface, (f64, f64)) {
|
||||||
|
if properties.text {
|
||||||
|
let image = ImageFiles::get("Makerlab.png").unwrap();
|
||||||
|
(
|
||||||
|
ImageSurface::create_from_png(&mut image.data.as_ref()).unwrap(),
|
||||||
|
(604.0, 432.0),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
ImageSurface::create(Format::ARgb32, 400, 400).unwrap(),
|
||||||
|
(200.0, 200.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn handler(Query(properties): Query<ImageProperties>) -> impl axum::response::IntoResponse {
|
async fn handler(Query(properties): Query<ImageProperties>) -> impl axum::response::IntoResponse {
|
||||||
let surface = ImageSurface::create(Format::ARgb32, 400, 400).unwrap();
|
let (surface, logo_coordinates) = get_surface_and_logo_coordiates(&properties);
|
||||||
let context = Context::new(&surface).unwrap();
|
let context = Context::new(&surface).unwrap();
|
||||||
|
|
||||||
if let Some(c) = properties.background_color {
|
if let Some(c) = properties.background_color {
|
||||||
|
@ -44,14 +67,8 @@ async fn handler(Query(properties): Query<ImageProperties>) -> impl axum::respon
|
||||||
|
|
||||||
context.paint().unwrap();
|
context.paint().unwrap();
|
||||||
|
|
||||||
polygon::draw_polygon_of_segmented_polygons(
|
polygon::draw_polygon_of_segmented_polygons(logo_coordinates, 148.0, 67.0, 6, &context)
|
||||||
(200.0, 200.0),
|
.unwrap();
|
||||||
200.0,
|
|
||||||
6,
|
|
||||||
&context,
|
|
||||||
properties.dark_mode,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut data: Vec<u8> = Vec::new();
|
let mut data: Vec<u8> = Vec::new();
|
||||||
surface.write_to_png(&mut data).unwrap();
|
surface.write_to_png(&mut data).unwrap();
|
||||||
|
|
|
@ -9,7 +9,7 @@ use rust_embed::{EmbeddedFile, RustEmbed};
|
||||||
use crate::SharedState;
|
use crate::SharedState;
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "static"]
|
#[folder = "web"]
|
||||||
struct StaticFiles;
|
struct StaticFiles;
|
||||||
|
|
||||||
async fn static_files(uri: Uri) -> impl IntoResponse {
|
async fn static_files(uri: Uri) -> impl IntoResponse {
|
||||||
|
|
|
@ -26,8 +26,11 @@
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><img src="logo" style="height: 50px" class="light-only" /> <img src="logo?dark_mode=true"
|
<li>
|
||||||
style="height: 50px" class="dark-only" /> <strong>MakerLab Murnau Logo generator</strong></li>
|
<img src="logo?dark_mode=false&text=false" style="height: 50px" class="light-only" />
|
||||||
|
<img src="logo?dark_mode=true&text=false" style="height: 50px" class="dark-only" />
|
||||||
|
<strong>MakerLab Murnau Logo generator</strong>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://makerlab-murnau.de">MakerLab Website</a></li>
|
<li><a href="https://makerlab-murnau.de">MakerLab Website</a></li>
|
||||||
|
@ -48,6 +51,10 @@
|
||||||
</label>
|
</label>
|
||||||
<input type="color" id="logo_background_color" name="background_color" value="">
|
<input type="color" id="logo_background_color" name="background_color" value="">
|
||||||
</div>
|
</div>
|
||||||
|
<label for="logo_text">
|
||||||
|
<input type="checkbox" id="logo_text" name="text" role="switch">
|
||||||
|
Add text
|
||||||
|
</label>
|
||||||
<label for="logo_dark_mode">
|
<label for="logo_dark_mode">
|
||||||
<input type="checkbox" id="logo_dark_mode" name="dark_mode" role="switch">
|
<input type="checkbox" id="logo_dark_mode" name="dark_mode" role="switch">
|
||||||
Dark mode
|
Dark mode
|
0
static/pico.min.css → web/pico.min.css
vendored
0
static/pico.min.css → web/pico.min.css
vendored
Loading…
Reference in a new issue