Add env_logger
[litoprism.git] / src / main.rs
1 use std::io::Cursor;
2 use std::fmt::Display;
3 use std::fmt;
4 use std::borrow::BorrowMut;
5 use num_traits::Zero;
6 use num_traits::cast::AsPrimitive;
7 use percent_encoding::percent_decode_str;
8 use actix_web::{get, web, App, HttpServer, HttpRequest, HttpResponse, Responder, ResponseError, Result};
9 use actix_web::body::BoxBody;
10 use actix_web::http::StatusCode;
11 use image::{ImageBuffer, ColorType, Rgb, RgbImage, write_buffer_with_format};
12 use image::ImageOutputFormat::Png;
13 use image::imageops::{FilterType, resize};
14 use image::error::{LimitError, LimitErrorKind};
15
16
17 #[derive(Debug)]
18 struct ImageError(image::ImageError);
19
20 impl ImageError {
21 fn dimension_error() -> ImageError {
22 ImageError(image::ImageError::Limits(LimitError::from_kind(LimitErrorKind::DimensionError)))
23 }
24 }
25
26 impl Display for ImageError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "{}", &self.0)
29 }
30 }
31
32 impl ResponseError for ImageError {
33 fn status_code(&self) -> StatusCode {
34 StatusCode::INTERNAL_SERVER_ERROR
35 }
36
37 fn error_response(&self) -> HttpResponse<BoxBody> {
38 HttpResponse::InternalServerError().body(format!("{}\n", &self))
39 }
40 }
41
42 struct ToRgbIter<'a, T> where T: AsPrimitive<u8> {
43 iter: Box<&'a mut dyn Iterator<Item = T>>
44 }
45
46 impl<'a, T: AsPrimitive<u8>> ToRgbIter<'a, T> {
47 fn new(data: &'a mut (dyn Iterator<Item = T>)) -> ToRgbIter<T> {
48 ToRgbIter { iter: Box::new(data) }
49 }
50 }
51
52 impl<T: AsPrimitive<u8> + Zero> Iterator for ToRgbIter<'_, T> {
53 type Item = Rgb<u8>;
54 fn next(&mut self) -> Option<Rgb<u8>> {
55 if let Some(r) = self.iter.next() {
56 let g = self.iter.next().unwrap_or(T::zero());
57 let b = self.iter.next().unwrap_or(T::zero());
58 Some(Rgb([r.as_(), g.as_(), b.as_()]))
59 }
60 else {
61 None
62 }
63 }
64 }
65
66 fn to_imageresult<T> (result: Result<T, image::ImageError>) -> Result<T, ImageError> {
67 match result {
68 Ok(v) => Ok(v),
69 Err(e) => Err(ImageError(e))
70 }
71 }
72
73 #[get("/hello/{name}")]
74 async fn greet(name: web::Path<String>, req: HttpRequest) -> impl Responder {
75 println!("{:?}", req);
76 format!("Hello {name}!")
77 }
78
79 fn make_png(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>) -> Result<Cursor<Vec<u8>>, ImageError> {
80 // Image must not be larger than 1 megapixel
81 if dim_x * dim_y * scale > 1048576 {
82 return Err(ImageError::dimension_error())
83 }
84
85 let mut img: RgbImage = ImageBuffer::new(dim_x, dim_y);
86 let mut pixels = img.pixels_mut();
87
88 for sp in ToRgbIter::new(data) {
89 let mut dp = pixels.next().ok_or(ImageError::dimension_error())?;
90 println!("{:?}", sp);
91 dp.0 = sp.0;
92 }
93
94 let tdim_x = dim_x * scale;
95 let tdim_y = dim_y * scale;
96 let img = resize(&img, tdim_x, tdim_y, FilterType::Nearest);
97 let mut cursor = Cursor::new(Vec::new());
98 to_imageresult(write_buffer_with_format(&mut cursor, &img, tdim_x, tdim_y, ColorType::Rgb8, Png))?;
99 Ok(cursor)
100 }
101
102 #[get("/gen/0/{data}")]
103 async fn img_gen0(req: HttpRequest) -> Result<impl Responder> {
104 let data = req.uri().path().split("/").skip(3).next().unwrap();
105 let cursor = make_png(32, 32, 16, percent_decode_str(&data).into_iter().borrow_mut())?;
106 Ok(HttpResponse::build(StatusCode::OK)
107 .content_type("image/png")
108 .body(cursor.into_inner()))
109 }
110
111 #[get("/gen/1/{dim_x}/{dim_y}/{scale}/{data}")]
112 async fn img_gen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
113 let (dim_x, dim_y, scale) = path.into_inner();
114 let data = req.uri().path().split("/").skip(6).next().unwrap();
115 let cursor = make_png(dim_x, dim_y, scale, percent_decode_str(&data).into_iter().borrow_mut())?;
116 Ok(HttpResponse::build(StatusCode::OK)
117 .content_type("image/png")
118 .body(cursor.into_inner()))
119 }
120
121 #[actix_web::main] // or #[tokio::main]
122 async fn main() -> std::io::Result<()> {
123 env_logger::init();
124 HttpServer::new(|| {
125 App::new()
126 .service(greet)
127 .service(img_gen0)
128 .service(img_gen1)
129 })
130 .bind(("127.0.0.1", 8080))?
131 .run()
132 .await
133 }