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