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