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