+use std::convert::From;
use std::io::Cursor;
use std::fmt::Display;
use std::fmt;
use std::borrow::BorrowMut;
+use std::sync::{Arc, Mutex};
use num_traits::Zero;
use num_traits::cast::AsPrimitive;
use percent_encoding::percent_decode_str;
#[derive(Debug)]
struct ImageError(image::ImageError);
+impl ImageError {
+ fn dimension_error() -> ImageError {
+ ImageError(image::ImageError::Limits(LimitError::from_kind(LimitErrorKind::DimensionError)))
+ }
+}
+
impl Display for ImageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
+#[derive(Debug)]
+enum CanvasError {
+ NotExists,
+ DimensionMismatch((u32, u32)),
+ ImageError(ImageError)
+}
+
+impl Display for CanvasError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Canvas Error: see error_response() for more details")
+ }
+}
+
+impl ResponseError for CanvasError {
+ fn status_code(&self) -> StatusCode {
+ match &self {
+ CanvasError::NotExists => StatusCode::NOT_FOUND,
+ CanvasError::DimensionMismatch(_) => StatusCode::BAD_REQUEST,
+ CanvasError::ImageError(e) => e.status_code()
+ }
+ }
+
+ fn error_response(&self) -> HttpResponse<BoxBody> {
+ match &self {
+ CanvasError::NotExists => HttpResponse::NotFound().body("Canvas does not exist."),
+ CanvasError::DimensionMismatch(dim) =>
+ HttpResponse::BadRequest().body(format!("Supplied dimensions don't match the dimensions of existing canvas {:?}.", dim)),
+ CanvasError::ImageError(e) => e.error_response()
+ }
+ }
+}
+
+impl From<ImageError> for CanvasError {
+ fn from(e: ImageError) -> CanvasError {
+ CanvasError::ImageError(e)
+ }
+}
+
struct ToRgbIter<'a, T> where T: AsPrimitive<u8> {
iter: Box<&'a mut dyn Iterator<Item = T>>
}
}
}
+fn to_canvasresult<T: std::fmt::Debug> (result: Result<T, ImageError>) -> Result<T, CanvasError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(CanvasError::ImageError(e))
+ }
+}
+
+#[derive(Debug)]
+struct Canvas {
+ pen: usize,
+ img: RgbImage
+}
+
+impl Canvas {
+ fn new(width: u32, height: u32) -> Canvas {
+ Canvas { pen: 0, img: ImageBuffer::new(width, height) }
+ }
+
+ fn from_image(img: RgbImage) -> Canvas {
+ Canvas { pen: 0, img }
+ }
+
+ /*fn replace(&self, img: RgbImage, pen: usize) -> Canvas {
+ self.img = img;
+ self.pen = pen;
+ self
+ }*/
+
+ fn advance_pen(mut self, offset: usize) -> Canvas {
+ //Canvas { pen: self.pen + offset, img: self.img }
+ self.pen += offset;
+ self.pen %= self.img.width() as usize * self.img.height() as usize;
+ self
+ }
+}
+
+struct Canvas0(Option<Canvas>);
+struct Canvas1(Option<Canvas>);
+
#[get("/hello/{name}")]
async fn greet(name: web::Path<String>, req: HttpRequest) -> impl Responder {
println!("{:?}", req);
format!("Hello {name}!")
}
-fn make_png(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>) -> Result<Cursor<Vec<u8>>, ImageError> {
- let mut img: RgbImage = ImageBuffer::new(dim_x, dim_y);
- let mut pixels = img.pixels_mut();
+fn rgb_encode_to_canvas(mut canvas: Canvas, data: &mut dyn Iterator<Item = u8>) -> Result<Canvas, ImageError> {
+ let mut pixels = canvas.img.pixels_mut().skip(canvas.pen);
+ let mut counter = 0;
for sp in ToRgbIter::new(data) {
- let mut dp = pixels.next().ok_or(ImageError(image::ImageError::Limits(LimitError::from_kind(LimitErrorKind::DimensionError))))?;
- println!("{:?}", sp);
+ let mut dp = match pixels.next() {
+ Some(pixel) => pixel,
+ None => {
+ pixels = canvas.img.pixels_mut().skip(0);
+ pixels.next().ok_or(ImageError::dimension_error())?
+ }
+ };
dp.0 = sp.0;
+ counter += 1;
+ }
+
+ Ok(canvas.advance_pen(counter))
+}
+
+fn rgb_encode(img: RgbImage, data: &mut dyn Iterator<Item = u8>) -> Result<RgbImage, ImageError> {
+ Ok(rgb_encode_to_canvas(Canvas::from_image(img), data)?.img)
+}
+
+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>
+ where F: Fn(Canvas, &mut dyn Iterator<Item = u8>) -> Result<Canvas, CanvasError>
+{
+ let canvas = match canvas_option.take() {
+ Some(canvas) => match dimensions {
+ Some(dimensions) => {
+ if dimensions == canvas.img.dimensions() {
+ Ok(canvas)
+ }
+ else {
+ Err(CanvasError::DimensionMismatch(canvas_option.insert(canvas).img.dimensions()))
+ }
+ },
+ None =>
+ Ok(canvas)
+ },
+ None => match dimensions {
+ Some((dim_x, dim_y)) =>
+ Ok(Canvas::new(dim_x, dim_y)),
+ None =>
+ Err(CanvasError::NotExists)
+ }
+ }?;
+
+ Ok(canvas_option.insert(f(canvas, percent_decode_str(&data).into_iter().borrow_mut())?))
+}
+
+fn make_png(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>) -> Result<Cursor<Vec<u8>>, ImageError> {
+ // Image must not be larger than 1 megapixel
+ if dim_x * dim_y * scale > 1048576 {
+ return Err(ImageError::dimension_error())
+ }
+
+ let img: RgbImage = rgb_encode(ImageBuffer::new(dim_x, dim_y), data)?;
+ let cursor = image_to_cursor(img, scale)?;
+ Ok(cursor)
+}
+
+fn image_to_cursor(img: RgbImage, scale: u32) -> Result<Cursor<Vec<u8>>, ImageError> {
+ let (dim_x, dim_y) = img.dimensions();
+ let (dim_x, dim_y) = (dim_x * scale, dim_y * scale);
+
+ // Image must not be larger than 1 megapixel
+ if dim_x * dim_y > 1048576 {
+ return Err(ImageError::dimension_error())
}
- let tdim_x = dim_x * scale;
- let tdim_y = dim_y * scale;
- let img = resize(&img, tdim_x, tdim_y, FilterType::Nearest);
let mut cursor = Cursor::new(Vec::new());
- to_imageresult(write_buffer_with_format(&mut cursor, &img, tdim_x, tdim_y, ColorType::Rgb8, Png))?;
+ let img = resize(&img, dim_x, dim_y, FilterType::Nearest);
+ to_imageresult(write_buffer_with_format(&mut cursor, &img, dim_x, dim_y, ColorType::Rgb8, Png))?;
Ok(cursor)
}
+
#[get("/gen/0/{data}")]
async fn img_gen0(req: HttpRequest) -> Result<impl Responder> {
let data = req.uri().path().split("/").skip(3).next().unwrap();
.body(cursor.into_inner()))
}
+#[get("/pgen/0/{data}")]
+async fn img_pgen0(req: HttpRequest, canvas0: web::Data<Arc<Mutex<Canvas0>>>) -> Result<impl Responder> {
+ let data = req.uri().path().split("/").skip(3).next().unwrap();
+ let scale = 16;
+ let img: RgbImage = ({
+ let canvas_option = &mut *canvas0.lock().unwrap();
+ Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, None, data)?.img.clone())
+ } as Result<RgbImage, CanvasError>)?;
+ let cursor = image_to_cursor(img, scale)?;
+ Ok(HttpResponse::build(StatusCode::OK)
+ .content_type("image/png")
+ .body(cursor.into_inner()))
+}
+
+#[get("/pgen/1/{dim_x}/{dim_y}/{scale}/{data}")]
+async fn img_pgen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>, canvas1: web::Data<Arc<Mutex<Canvas1>>>) -> Result<impl Responder> {
+ let (dim_x, dim_y, scale) = path.into_inner();
+ let data = req.uri().path().split("/").skip(6).next().unwrap();
+ let img: RgbImage = ({
+ let canvas_option = &mut *canvas1.lock().unwrap();
+ 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())
+ } as Result<RgbImage, CanvasError>)?;
+ let cursor = image_to_cursor(img, scale)?;
+ Ok(HttpResponse::build(StatusCode::OK)
+ .content_type("image/png")
+ .body(cursor.into_inner()))
+}
+
+
#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
- HttpServer::new(|| {
+ env_logger::init();
+ let canvas0 = Arc::new(Mutex::new(Canvas0(Some(Canvas::new(32, 32)))));
+ let canvas1 = Arc::new(Mutex::new(Canvas1(None)));
+ HttpServer::new(move || {
App::new()
.service(greet)
.service(img_gen0)
.service(img_gen1)
+ .service(img_pgen0)
+ .service(img_pgen1)
+ .app_data(web::Data::new(canvas0.clone()))
+ .app_data(web::Data::new(canvas1.clone()))
})
.bind(("127.0.0.1", 8080))?
.run()