854f2c0ae366799a6615a88c45156d3d398c7817
[litoprism.git] / src / main.rs
1 use std::convert::From;
2 use std::env;
3 use std::io::Cursor;
4 use std::fmt::Display;
5 use std::fmt;
6 use std::borrow::BorrowMut;
7 use std::sync::{Arc, Mutex};
8 use log::LevelFilter;
9 use num_traits::Zero;
10 use num_traits::cast::AsPrimitive;
11 use percent_encoding::percent_decode_str;
12 use actix_web::{middleware::Logger, get, web, App, HttpServer, HttpRequest, HttpResponse, Responder, ResponseError, Result};
13 use actix_web::body::BoxBody;
14 use actix_web::http::StatusCode;
15 use image::{ImageBuffer, ColorType, Rgb, RgbImage, write_buffer_with_format};
16 use image::ImageOutputFormat::Png;
17 use image::imageops::{FilterType, overlay, resize};
18 use image::error::{LimitError, LimitErrorKind};
19 use actix_web::cookie::time::OffsetDateTime;
20
21
22 const TIMEAVATAR_SIZE_U32: u32 = 6;
23 const TIMEAVATAR_SIZE_I64: i64 = TIMEAVATAR_SIZE_U32 as i64;
24
25 macro_rules! response_image {
26 ($cursor:expr) => {
27 HttpResponse::build(StatusCode::OK)
28 .content_type("image/png")
29 .body($cursor.into_inner())
30 }
31 }
32
33 #[derive(Debug)]
34 struct ImageError(image::ImageError);
35
36 impl ImageError {
37 fn dimension_error() -> ImageError {
38 ImageError(image::ImageError::Limits(LimitError::from_kind(LimitErrorKind::DimensionError)))
39 }
40 }
41
42 impl Display for ImageError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "{}", &self.0)
45 }
46 }
47
48 impl ResponseError for ImageError {
49 fn status_code(&self) -> StatusCode {
50 StatusCode::INTERNAL_SERVER_ERROR
51 }
52
53 fn error_response(&self) -> HttpResponse<BoxBody> {
54 HttpResponse::InternalServerError().body(format!("{}\n", &self))
55 }
56 }
57
58 #[derive(Debug)]
59 enum CanvasError {
60 NotExists,
61 DimensionMismatch((u32, u32)),
62 ImageError(ImageError)
63 }
64
65 impl Display for CanvasError {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 write!(f, "Canvas Error: see error_response() for more details")
68 }
69 }
70
71 impl ResponseError for CanvasError {
72 fn status_code(&self) -> StatusCode {
73 match &self {
74 CanvasError::NotExists => StatusCode::NOT_FOUND,
75 CanvasError::DimensionMismatch(_) => StatusCode::BAD_REQUEST,
76 CanvasError::ImageError(e) => e.status_code()
77 }
78 }
79
80 fn error_response(&self) -> HttpResponse<BoxBody> {
81 match &self {
82 CanvasError::NotExists => HttpResponse::NotFound().body("Canvas does not exist."),
83 CanvasError::DimensionMismatch(dim) =>
84 HttpResponse::BadRequest().body(format!("Supplied dimensions don't match the dimensions of existing canvas {:?}.", dim)),
85 CanvasError::ImageError(e) => e.error_response()
86 }
87 }
88 }
89
90 impl From<ImageError> for CanvasError {
91 fn from(e: ImageError) -> CanvasError {
92 CanvasError::ImageError(e)
93 }
94 }
95
96 struct ToRgbIter<'a, T> where T: AsPrimitive<u8> {
97 iter: &'a mut dyn Iterator<Item = T>
98 }
99
100 impl<'a, T: AsPrimitive<u8>> ToRgbIter<'a, T> {
101 fn new(iter: &'a mut (dyn Iterator<Item = T>)) -> ToRgbIter<T> {
102 ToRgbIter { iter }
103 }
104 }
105
106 impl<T: AsPrimitive<u8> + Zero> Iterator for ToRgbIter<'_, T> {
107 type Item = Rgb<u8>;
108 fn next(&mut self) -> Option<Rgb<u8>> {
109 if let Some(r) = self.iter.next() {
110 let g = self.iter.next().unwrap_or_else(T::zero);
111 let b = self.iter.next().unwrap_or_else(T::zero);
112 Some(Rgb([r.as_(), g.as_(), b.as_()]))
113 }
114 else {
115 None
116 }
117 }
118 }
119
120 fn to_imageresult<T> (result: Result<T, image::ImageError>) -> Result<T, ImageError> {
121 match result {
122 Ok(v) => Ok(v),
123 Err(e) => Err(ImageError(e))
124 }
125 }
126
127 fn to_canvasresult<T: std::fmt::Debug> (result: Result<T, ImageError>) -> Result<T, CanvasError> {
128 match result {
129 Ok(v) => Ok(v),
130 Err(e) => Err(CanvasError::ImageError(e))
131 }
132 }
133
134 #[derive(Debug)]
135 struct Canvas {
136 pen: usize,
137 img: RgbImage
138 }
139
140 impl Canvas {
141 fn new(width: u32, height: u32) -> Canvas {
142 Canvas { pen: 0, img: ImageBuffer::new(width, height) }
143 }
144
145 fn from_image(img: RgbImage) -> Canvas {
146 Canvas { pen: 0, img }
147 }
148
149 /*fn replace(&self, img: RgbImage, pen: usize) -> Canvas {
150 self.img = img;
151 self.pen = pen;
152 self
153 }*/
154
155 fn advance_pen(mut self, offset: usize) -> Canvas {
156 //Canvas { pen: self.pen + offset, img: self.img }
157 self.pen += offset;
158 self.pen %= self.img.width() as usize * self.img.height() as usize;
159 self
160 }
161 }
162
163 struct Canvas0(Option<Canvas>);
164 struct Canvas1(Option<Canvas>);
165 struct Canvas2(Option<Canvas>);
166
167 #[get("/hello/{name}")]
168 async fn greet(name: web::Path<String>, req: HttpRequest) -> impl Responder {
169 println!("{:?}", req);
170 format!("Hello {name}!\n{:?}", req.headers().get("user-agent"))
171 }
172
173 fn rgb_encode_to_canvas(mut canvas: Canvas, data: &mut dyn Iterator<Item = u8>) -> Result<Canvas, ImageError> {
174 let mut pixels = canvas.img.pixels_mut().skip(canvas.pen);
175 let mut counter = 0;
176
177 for sp in ToRgbIter::new(data) {
178 let mut dp = match pixels.next() {
179 Some(pixel) => pixel,
180 None => {
181 pixels = canvas.img.pixels_mut().skip(0);
182 pixels.next().ok_or_else(ImageError::dimension_error)?
183 }
184 };
185 dp.0 = sp.0;
186 counter += 1;
187 }
188
189 Ok(canvas.advance_pen(counter))
190 }
191
192 fn rgb_encode(img: RgbImage, data: &mut dyn Iterator<Item = u8>) -> Result<RgbImage, ImageError> {
193 Ok(rgb_encode_to_canvas(Canvas::from_image(img), data)?.img)
194 }
195
196 fn apply_to_canvas<'a, F>(f: F, canvas_option: &'a mut Option<Canvas>, dimensions: Option<(u32, u32)>, data: &str) -> Result<&'a mut Canvas, CanvasError>
197 where F: Fn(Canvas, &mut dyn Iterator<Item = u8>) -> Result<Canvas, CanvasError>
198 {
199 let canvas = match canvas_option.take() {
200 Some(canvas) => match dimensions {
201 Some(dimensions) => {
202 if dimensions == canvas.img.dimensions() {
203 Ok(canvas)
204 }
205 else {
206 Err(CanvasError::DimensionMismatch(canvas_option.insert(canvas).img.dimensions()))
207 }
208 },
209 None =>
210 Ok(canvas)
211 },
212 None => match dimensions {
213 Some((dim_x, dim_y)) =>
214 Ok(Canvas::new(dim_x, dim_y)),
215 None =>
216 Err(CanvasError::NotExists)
217 }
218 }?;
219
220 Ok(canvas_option.insert(f(canvas, percent_decode_str(data).into_iter().borrow_mut())?))
221 }
222
223 fn make_png(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>) -> Result<Cursor<Vec<u8>>, ImageError> {
224 // Image must not be larger than 1 megapixel
225 if dim_x * dim_y * scale > 1048576 {
226 return Err(ImageError::dimension_error())
227 }
228
229 let img: RgbImage = rgb_encode(ImageBuffer::new(dim_x, dim_y), data)?;
230 let cursor = image_to_cursor(img, scale)?;
231 Ok(cursor)
232 }
233
234 fn image_to_cursor(img: RgbImage, scale: u32) -> Result<Cursor<Vec<u8>>, ImageError> {
235 let (dim_x, dim_y) = img.dimensions();
236 let (dim_x, dim_y) = (dim_x * scale, dim_y * scale);
237
238 // Image must not be larger than 1 megapixel
239 if dim_x * dim_y > 1048576 {
240 return Err(ImageError::dimension_error())
241 }
242
243 let mut cursor = Cursor::new(Vec::new());
244 let img = resize(&img, dim_x, dim_y, FilterType::Nearest);
245 to_imageresult(write_buffer_with_format(&mut cursor, &img, dim_x, dim_y, ColorType::Rgb8, Png))?;
246 Ok(cursor)
247 }
248
249
250 #[get("/gen/0/{data}")]
251 async fn img_gen0(req: HttpRequest) -> Result<impl Responder> {
252 let data = req.uri().path().split('/').nth(3).unwrap();
253 let cursor = make_png(32, 32, 16, percent_decode_str(data).into_iter().borrow_mut())?;
254 Ok(response_image!(cursor))
255 }
256
257 #[get("/gen/1/{dim_x}/{dim_y}/{scale}/{data}")]
258 async fn img_gen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
259 let (dim_x, dim_y, scale) = path.into_inner();
260 let data = req.uri().path().split('/').nth(6).unwrap();
261 let cursor = make_png(dim_x, dim_y, scale, percent_decode_str(data).into_iter().borrow_mut())?;
262 Ok(response_image!(cursor))
263 }
264
265 #[get("/gen/2/{dim_x}/{dim_y}/{scale}")]
266 async fn img_gen2(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
267 let (dim_x, dim_y, scale) = path.into_inner();
268 let data = match req.headers().get("user-agent") {
269 Some(header) => header.to_str().unwrap_or(""),
270 _ => ""
271 };
272 let cursor = make_png(dim_x, dim_y, scale, &mut data.bytes())?;
273 Ok(response_image!(cursor))
274 }
275
276 #[get("/gen/3/{scale}")]
277 async fn img_gen3(req: HttpRequest, path: web::Path<u32>) -> Result<impl Responder> {
278 let scale = path.into_inner();
279 let data = match req.headers().get("user-agent") {
280 Some(header) => header.to_str().unwrap_or(""),
281 _ => ""
282 };
283 let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
284 let mut resimg: RgbImage = ImageBuffer::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32);
285 let time = OffsetDateTime::now_utc().time();
286 let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
287 overlay(&mut resimg, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
288 let cursor = image_to_cursor(resimg, scale)?;
289 Ok(response_image!(cursor))
290 }
291
292 #[get("/pgen/0/{data}")]
293 async fn img_pgen0(req: HttpRequest, canvas0: web::Data<Arc<Mutex<Canvas0>>>) -> Result<impl Responder> {
294 let data = req.uri().path().split('/').nth(3).unwrap();
295 let scale = 16;
296 let img: RgbImage = ({
297 let canvas_option = &mut *canvas0.lock().unwrap();
298 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, None, data)?.img.clone())
299 } as Result<RgbImage, CanvasError>)?;
300 let cursor = image_to_cursor(img, scale)?;
301 Ok(response_image!(cursor))
302 }
303
304 #[get("/pgen/1/{dim_x}/{dim_y}/{scale}/{data}")]
305 async fn img_pgen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>, canvas1: web::Data<Arc<Mutex<Canvas1>>>) -> Result<impl Responder> {
306 let (dim_x, dim_y, scale) = path.into_inner();
307 let data = req.uri().path().split('/').nth(6).unwrap();
308 let img: RgbImage = ({
309 let canvas_option = &mut *canvas1.lock().unwrap();
310 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, Some((dim_x, dim_y)), data)?.img.clone())
311 } as Result<RgbImage, CanvasError>)?;
312 let cursor = image_to_cursor(img, scale)?;
313 Ok(response_image!(cursor))
314 }
315
316 #[get("/pgen/2/{dim_x}/{dim_y}/{scale}")]
317 async fn img_pgen2(req: HttpRequest, path: web::Path<(u32, u32, u32)>, canvas1: web::Data<Arc<Mutex<Canvas1>>>) -> Result<impl Responder> {
318 let (dim_x, dim_y, scale) = path.into_inner();
319 let data = match req.headers().get("user-agent") {
320 Some(header) => header.to_str().unwrap_or(""),
321 _ => ""
322 };
323 let img: RgbImage = ({
324 let canvas_option = &mut *canvas1.lock().unwrap();
325 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, Some((dim_x, dim_y)), data)?.img.clone())
326 } as Result<RgbImage, CanvasError>)?;
327 let cursor = image_to_cursor(img, scale)?;
328 Ok(response_image!(cursor))
329 }
330
331 #[get("/pgen/3/{scale}")]
332 async fn img_pgen3(req: HttpRequest, path: web::Path<u32>, canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
333 let scale = path.into_inner();
334 let data = match req.headers().get("user-agent") {
335 Some(header) => header.to_str().unwrap_or(""),
336 _ => ""
337 };
338 let time = OffsetDateTime::now_utc().time();
339 let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
340 let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
341 let resimg: RgbImage = ({
342 let canvas_option = &mut *canvas2.lock().unwrap();
343 let mut canvas = match canvas_option.0.take() {
344 Some(canvas) => canvas,
345 None => Canvas::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32)
346 };
347 overlay(&mut canvas.img, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
348 Ok(canvas_option.0.insert(canvas).img.clone())
349 } as Result<RgbImage, CanvasError>)?;
350 let cursor = image_to_cursor(resimg, scale)?;
351 Ok(response_image!(cursor))
352 }
353
354 #[get("/pdrop/{canvas_id}")]
355 async fn pdrop(canvas_id: web::Path<u8>,
356 canvas0: web::Data<Arc<Mutex<Canvas0>>>,
357 canvas1: web::Data<Arc<Mutex<Canvas1>>>,
358 canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
359
360 let canvas_id = canvas_id.into_inner();
361 match canvas_id {
362 0 => canvas0.lock().unwrap().0.take(),
363 1 => canvas1.lock().unwrap().0.take(),
364 2 => canvas2.lock().unwrap().0.take(),
365 _ => None
366 }.ok_or(CanvasError::NotExists)?;
367 Ok(HttpResponse::build(StatusCode::OK)
368 .body(format!("Canvas {} dropped.", canvas_id)))
369 }
370
371
372 #[actix_web::main] // or #[tokio::main]
373 async fn main() -> std::io::Result<()> {
374 env_logger::builder()
375 .filter_module("actix_web", LevelFilter::Info)
376 .parse_default_env()
377 .init();
378
379 let listenaddress = env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_string());
380 let canvas0 = Arc::new(Mutex::new(Canvas0(Some(Canvas::new(32, 32)))));
381 let canvas1 = Arc::new(Mutex::new(Canvas1(None)));
382 let canvas2 = Arc::new(Mutex::new(Canvas2(None)));
383
384 HttpServer::new(move || {
385 App::new()
386 .wrap(Logger::default())
387 .service(greet)
388 .service(img_gen0)
389 .service(img_gen1)
390 .service(img_gen2)
391 .service(img_gen3)
392 .service(img_pgen0)
393 .service(img_pgen1)
394 .service(img_pgen2)
395 .service(img_pgen3)
396 .service(pdrop)
397 .app_data(web::Data::new(canvas0.clone()))
398 .app_data(web::Data::new(canvas1.clone()))
399 .app_data(web::Data::new(canvas2.clone()))
400 })
401 .bind(listenaddress)?
402 .run()
403 .await
404 }