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