+use std::f32::consts::PI;
+use std::convert::From;
+use std::env;
use std::io::Cursor;
use std::fmt::Display;
use std::fmt;
use std::borrow::BorrowMut;
use std::sync::{Arc, Mutex};
+use log::{debug, LevelFilter};
use num_traits::Zero;
use num_traits::cast::AsPrimitive;
use percent_encoding::percent_decode_str;
-use actix_web::{get, web, App, HttpServer, HttpRequest, HttpResponse, Responder, ResponseError, Result};
+use actix_web::{middleware::Logger, get, web, App, HttpServer, HttpRequest, HttpResponse, Responder, ResponseError, Result};
use actix_web::body::BoxBody;
use actix_web::http::StatusCode;
+use actix_web::cookie::time::OffsetDateTime;
use image::{ImageBuffer, ColorType, Rgb, RgbImage, write_buffer_with_format};
use image::ImageOutputFormat::Png;
-use image::imageops::{FilterType, resize};
+use image::imageops::{FilterType, overlay, resize};
use image::error::{LimitError, LimitErrorKind};
+const TIMEAVATAR_SIZE_U32: u32 = 6;
+const TIMEAVATAR_SIZE_I64: i64 = TIMEAVATAR_SIZE_U32 as i64;
+
+macro_rules! response_image {
+ ($cursor:expr) => {
+ HttpResponse::build(StatusCode::OK)
+ .content_type("image/png")
+ .body($cursor.into_inner())
+ }
+}
+
+macro_rules! extract_data {
+ ($req:expr,$n:expr) => {
+ $req.uri().path().split('/').nth($n).unwrap()
+ }
+}
+
+macro_rules! extract_header {
+ ($req:expr,$header:expr) => {
+ match $req.headers().get($header) {
+ Some(header) => header.to_str().unwrap_or(""),
+ _ => ""
+ }
+ }
+}
+
#[derive(Debug)]
struct ImageError(image::ImageError);
}
}
+impl From<image::ImageError> for ImageError {
+ fn from(e: image::ImageError) -> Self {
+ Self(e)
+ }
+}
+
+#[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>>
+ iter: &'a mut dyn Iterator<Item = T>
}
impl<'a, T: AsPrimitive<u8>> ToRgbIter<'a, T> {
- fn new(data: &'a mut (dyn Iterator<Item = T>)) -> ToRgbIter<T> {
- ToRgbIter { iter: Box::new(data) }
+ fn new(iter: &'a mut (dyn Iterator<Item = T>)) -> ToRgbIter<T> {
+ ToRgbIter { iter }
}
}
type Item = Rgb<u8>;
fn next(&mut self) -> Option<Rgb<u8>> {
if let Some(r) = self.iter.next() {
- let g = self.iter.next().unwrap_or(T::zero());
- let b = self.iter.next().unwrap_or(T::zero());
+ let g = self.iter.next().unwrap_or_else(T::zero);
+ let b = self.iter.next().unwrap_or_else(T::zero);
Some(Rgb([r.as_(), g.as_(), b.as_()]))
}
else {
}
}
-fn to_imageresult<T> (result: Result<T, image::ImageError>) -> Result<T, ImageError> {
- match result {
- Ok(v) => Ok(v),
- Err(e) => Err(ImageError(e))
- }
-}
-
+#[derive(Debug)]
struct Canvas {
pen: usize,
img: RgbImage
Canvas { pen: 0, img: ImageBuffer::new(width, height) }
}
- fn from_image(img: RgbImage) -> Canvas {
+ /*fn from_image(img: RgbImage) -> Canvas {
Canvas { pen: 0, img }
}
- /*fn replace(&self, img: RgbImage, pen: usize) -> Canvas {
+ 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>);
+struct Canvas2(Option<Canvas>);
+
#[get("/hello/{name}")]
async fn greet(name: web::Path<String>, req: HttpRequest) -> impl Responder {
println!("{:?}", req);
- format!("Hello {name}!")
+ format!("Hello {name}!\n{:?}", req.headers().get("user-agent"))
}
-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);
+fn rgb_encode_with_pen(img: &mut RgbImage, pen: usize, data: &mut dyn Iterator<Item = u8>) -> Result<usize, ImageError> {
+ let mut pixels = img.pixels_mut().skip(pen);
let mut counter = 0;
for sp in ToRgbIter::new(data) {
- let mut dp = pixels.next().ok_or(ImageError::dimension_error())?;
- println!("{:?}", sp);
+ let mut dp = match pixels.next() {
+ Some(pixel) => pixel,
+ None => {
+ pixels = img.pixels_mut().skip(0);
+ pixels.next().ok_or_else(ImageError::dimension_error)?
+ }
+ };
dp.0 = sp.0;
counter += 1;
}
+ Ok(counter)
+}
+
+fn rgb_encode_to_canvas(mut canvas: Canvas, data: &mut dyn Iterator<Item = u8>) -> Result<Canvas, CanvasError> {
+ let counter = rgb_encode_with_pen(&mut canvas.img, canvas.pen, data)?;
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 rgb_encode(mut img: RgbImage, data: &mut dyn Iterator<Item = u8>) -> Result<RgbImage, ImageError> {
+ rgb_encode_with_pen(&mut img, 0, data)?;
+ Ok(img)
}
-fn make_png(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>) -> Result<Cursor<Vec<u8>>, ImageError> {
+fn sine_encode(mut img: RgbImage, wave_offset: (u32, u32), amplitude: f32, freq_base: f32, run: u32, wave_color: Rgb<u8>,
+ data: &mut dyn Iterator<Item = u8>) -> Result<RgbImage, ImageError>
+{
+ let run: f32 = run as f32;
+ let (wave_offset_x, wave_offset_y) = wave_offset;
+ let (wave_offset_x, wave_offset_y) = (wave_offset_x as f32, wave_offset_y as f32);
+ let signal = &mut data.peekable();
+ let mut x: f32 = wave_offset_x as f32;
+ let max_x: f32 = (img.width() - 1) as f32;
+ let max_y: f32 = img.height() as f32;
+ while signal.peek().is_some() && (x < max_x) {
+ let px = x;
+ let s = signal.next().unwrap_or(0);
+ while ((x - px) < run) && (x < max_x) {
+ let y = if s != 0 {
+ (2.0 * PI * freq_base * x * s as f32).sin() * amplitude + wave_offset_y
+ }
+ else { wave_offset_y };
+ x += 0.001;
+ if (y > 0.0) && (y < max_y) {
+ img.put_pixel(x as u32, y as u32, wave_color);
+ }
+ }
+ }
+ Ok(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<F>(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>, encode: F) -> Result<Cursor<Vec<u8>>, ImageError>
+ where F: Fn(RgbImage, &mut dyn Iterator<Item = u8>) -> Result<RgbImage, ImageError>
+{
// Image must not be larger than 1 megapixel
if dim_x * dim_y * scale > 1048576 {
return Err(ImageError::dimension_error())
}
- let mut img: RgbImage = rgb_encode(ImageBuffer::new(dim_x, dim_y), data)?;
+ let img: RgbImage = 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);
+ 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();
- let cursor = make_png(32, 32, 16, percent_decode_str(&data).into_iter().borrow_mut())?;
- println!("{:?}", &cursor);
- Ok(HttpResponse::build(StatusCode::OK)
- .content_type("image/png")
- .body(cursor.into_inner()))
+ let data = extract_data!(req, 3);
+ let cursor = make_png(32, 32, 16, percent_decode_str(data).into_iter().borrow_mut(), rgb_encode)?;
+ Ok(response_image!(cursor))
}
#[get("/gen/1/{dim_x}/{dim_y}/{scale}/{data}")]
async fn img_gen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
let (dim_x, dim_y, scale) = path.into_inner();
- let data = req.uri().path().split("/").skip(6).next().unwrap();
- let cursor = make_png(dim_x, dim_y, scale, percent_decode_str(&data).into_iter().borrow_mut())?;
- Ok(HttpResponse::build(StatusCode::OK)
- .content_type("image/png")
- .body(cursor.into_inner()))
+ let data = extract_data!(req, 6);
+ let cursor = make_png(dim_x, dim_y, scale, percent_decode_str(data).into_iter().borrow_mut(), rgb_encode)?;
+ Ok(response_image!(cursor))
+}
+
+#[get("/gen/2/{dim_x}/{dim_y}/{scale}")]
+async fn img_gen2(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
+ let (dim_x, dim_y, scale) = path.into_inner();
+ let data = extract_header!(req, "user-agent");
+ let cursor = make_png(dim_x, dim_y, scale, &mut data.bytes(), rgb_encode)?;
+ Ok(response_image!(cursor))
+}
+
+#[get("/gen/3/{scale}")]
+async fn img_gen3(req: HttpRequest, path: web::Path<u32>) -> Result<impl Responder> {
+ let scale = path.into_inner();
+ let data = extract_header!(req, "user-agent");
+ let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
+ let mut resimg: RgbImage = ImageBuffer::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32);
+ let time = OffsetDateTime::now_utc().time();
+ let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
+ overlay(&mut resimg, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
+ let cursor = image_to_cursor(resimg, scale)?;
+ Ok(response_image!(cursor))
+}
+
+#[get("/gen/4/{dim_x}/{dim_y}/{scale}/{wave_offset_x}/{wave_offset_y}/{amplitude}/{freq}/{run}/{wave_color}/{data}")]
+#[allow(clippy::type_complexity)]
+async fn img_gen4(req: HttpRequest, path: web::Path<(u32, u32, u32, u32, u32, f32, f32, u32)>) -> Result<impl Responder> {
+ let (dim_x, dim_y, scale, wave_offset_x, wave_offset_y, amplitude, freq_base, run) = path.into_inner();
+ let freq_base = freq_base / 1000.0;
+ let wave_color = extract_data!(req, 11);
+ debug!("Wave color input: {:?}", wave_color);
+ let wave_color = ToRgbIter::new(&mut percent_decode_str(wave_color)).next().unwrap_or(Rgb([255, 255, 255]));
+ debug!("Decoded color: {:?}", wave_color);
+ let signal = &mut percent_decode_str(extract_data!(req, 12));
+ let cursor = make_png(dim_x, dim_y, scale, signal,
+ |img, signal| sine_encode(img, (wave_offset_x, wave_offset_y), amplitude, freq_base, run, wave_color, signal))?;
+ Ok(response_image!(cursor))
}
#[get("/pgen/0/{data}")]
-async fn img_pgen0(req: HttpRequest, canvas0: web::Data<Arc<Mutex<Option<Canvas>>>>) -> Result<impl Responder> {
- let data = req.uri().path().split("/").skip(3).next().unwrap();
- let mut cursor = Cursor::new(Vec::new());
- let tdim_x = 32;
- let tdim_y = 32;
- {
- let mut canvas_option = &mut *canvas0.lock().unwrap();
- let canvas = canvas_option.take();
- let canvas = rgb_encode_to_canvas(canvas.expect("Canvas doesn't exist!"), percent_decode_str(&data).into_iter().borrow_mut())?;
- to_imageresult(write_buffer_with_format(&mut cursor, &canvas.img, tdim_x, tdim_y, ColorType::Rgb8, Png))?;
- canvas_option.insert(canvas);
- }
+async fn img_pgen0(req: HttpRequest, canvas0: web::Data<Arc<Mutex<Canvas0>>>) -> Result<impl Responder> {
+ let data = extract_data!(req, 3);
+ let scale = 16;
+ let img: RgbImage = ({
+ let canvas_option = &mut *canvas0.lock().unwrap();
+ Ok(apply_to_canvas( |c, d| 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(response_image!(cursor))
+}
+
+#[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 = extract_data!(req, 6);
+ let img: RgbImage = ({
+ let canvas_option = &mut *canvas1.lock().unwrap();
+ Ok(apply_to_canvas( |c, d| 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(response_image!(cursor))
+}
+
+#[get("/pgen/2/{dim_x}/{dim_y}/{scale}")]
+async fn img_pgen2(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 = extract_header!(req, "user-agent");
+ let img: RgbImage = ({
+ let canvas_option = &mut *canvas1.lock().unwrap();
+ Ok(apply_to_canvas( |c, d| 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(response_image!(cursor))
+}
+
+#[get("/pgen/3/{scale}")]
+async fn img_pgen3(req: HttpRequest, path: web::Path<u32>, canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
+ let scale = path.into_inner();
+ let data = extract_header!(req, "user-agent");
+ let time = OffsetDateTime::now_utc().time();
+ let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
+ let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
+ let resimg: RgbImage = ({
+ let canvas_option = &mut *canvas2.lock().unwrap();
+ let mut canvas = match canvas_option.0.take() {
+ Some(canvas) => canvas,
+ None => Canvas::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32)
+ };
+ overlay(&mut canvas.img, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
+ Ok(canvas_option.0.insert(canvas).img.clone())
+ } as Result<RgbImage, CanvasError>)?;
+ let cursor = image_to_cursor(resimg, scale)?;
+ Ok(response_image!(cursor))
+}
+
+#[get("/pdrop/{canvas_id}")]
+async fn pdrop(canvas_id: web::Path<u8>,
+ canvas0: web::Data<Arc<Mutex<Canvas0>>>,
+ canvas1: web::Data<Arc<Mutex<Canvas1>>>,
+ canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
+
+ let canvas_id = canvas_id.into_inner();
+ match canvas_id {
+ 0 => canvas0.lock().unwrap().0.take(),
+ 1 => canvas1.lock().unwrap().0.take(),
+ 2 => canvas2.lock().unwrap().0.take(),
+ _ => None
+ }.ok_or(CanvasError::NotExists)?;
Ok(HttpResponse::build(StatusCode::OK)
- .content_type("image/png")
- .body(cursor.into_inner()))
+ .body(format!("Canvas {} dropped.", canvas_id)))
}
+
#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
- env_logger::init();
- let canvas0 = Arc::new(Mutex::new(Some(Canvas::new(32, 32))));
+ env_logger::builder()
+ .filter_module("actix_web", LevelFilter::Info)
+ .parse_default_env()
+ .init();
+
+ let listenaddress = env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_string());
+ let canvas0 = Arc::new(Mutex::new(Canvas0(Some(Canvas::new(32, 32)))));
+ let canvas1 = Arc::new(Mutex::new(Canvas1(None)));
+ let canvas2 = Arc::new(Mutex::new(Canvas2(None)));
+
HttpServer::new(move || {
App::new()
+ .wrap(Logger::default())
.service(greet)
.service(img_gen0)
.service(img_gen1)
+ .service(img_gen2)
+ .service(img_gen3)
+ .service(img_gen4)
.service(img_pgen0)
+ .service(img_pgen1)
+ .service(img_pgen2)
+ .service(img_pgen3)
+ .service(pdrop)
.app_data(web::Data::new(canvas0.clone()))
+ .app_data(web::Data::new(canvas1.clone()))
+ .app_data(web::Data::new(canvas2.clone()))
})
- .bind(("127.0.0.1", 8080))?
+ .bind(listenaddress)?
.run()
.await
}