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