1 use std
::f32::consts
::PI
;
2 use std
::convert
::From
;
7 use std
::borrow
::BorrowMut
;
8 use std
::sync
::{Arc
, Mutex
};
9 use log
::{debug
, LevelFilter
};
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
};
23 const TIMEAVATAR_SIZE_U32
: u32 = 6;
24 const TIMEAVATAR_SIZE_I64
: i64 = TIMEAVATAR_SIZE_U32
as i64;
26 macro_rules
! response_image
{
28 HttpResponse
::build(StatusCode
::OK
)
29 .content_type("image/png")
30 .body($cursor
.into
_inner
())
34 macro_rules
! extract_data
{
35 ($req
:expr
,$n
:expr
) => {
36 $req
.ur
i().path().split('
/'
).nth($n
).unwrap
()
40 macro_rules
! extract_color
{
41 ($req
:expr
,$n
:expr
) => {
43 let data_raw
= extract_data
!($req
, $n
);
44 let mut data_decoded
= percent_decode_str(data_raw
);
45 let mut rgb
= ToRgbIter
::new(&mut data_decoded
);
46 let color
= rgb
.next().ok_or(ColorError(data_raw
.to_owned()))?
;
48 Some(_
) => Err(ColorError(data_raw
.to_owned())),
55 macro_rules
! extract_header
{
56 ($req
:expr
,$header
:expr
) => {
57 match $req
.headers().get($header
) {
58 Some(header
) => header
.to_str().unwrap
_or
(""),
65 struct ColorError(String
);
67 impl Display
for ColorError
{
68 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
69 write
!(f
, "Invalid color specification: {}", &self.0)
73 impl ResponseError
for ColorError
{
74 fn status_code(&self) -> StatusCode
{
75 StatusCode
::INTERNAL_SERVER_ERROR
78 fn error_response(&self) -> HttpResponse
<BoxBody
> {
79 HttpResponse
::InternalServerError().body(format
!("{}\n", &self))
84 struct ImageError(image
::ImageError
);
87 fn dimension_error() -> ImageError
{
88 ImageError(image
::ImageError
::Limits(LimitError
::from_kind(LimitErrorKind
::DimensionError
)))
92 impl Display
for ImageError
{
93 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
94 write
!(f
, "{}", &self.0)
98 impl ResponseError
for ImageError
{
99 fn status_code(&self) -> StatusCode
{
100 StatusCode
::INTERNAL_SERVER_ERROR
103 fn error_response(&self) -> HttpResponse
<BoxBody
> {
104 HttpResponse
::InternalServerError().body(format
!("{}\n", &self))
108 impl From
<image
::ImageError
> for ImageError
{
109 fn from(e
: image
::ImageError
) -> Self {
117 DimensionMismatch((u32, u32)),
118 ImageError(ImageError
)
121 impl Display
for CanvasError
{
122 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
123 write
!(f
, "Canvas Error: see error_response() for more details")
127 impl ResponseError
for CanvasError
{
128 fn status_code(&self) -> StatusCode
{
130 CanvasError
::NotExists
=> StatusCode
::NOT_FOUND
,
131 CanvasError
::DimensionMismatch(_
) => StatusCode
::BAD_REQUEST
,
132 CanvasError
::ImageError(e
) => e
.status_code()
136 fn error_response(&self) -> HttpResponse
<BoxBody
> {
138 CanvasError
::NotExists
=> HttpResponse
::NotFound().body("Canvas does not exist."),
139 CanvasError
::DimensionMismatch(dim
) =>
140 HttpResponse
::BadRequest().body(format
!("Supplied dimensions don't match the dimensions of existing canvas {:?}.", dim
)),
141 CanvasError
::ImageError(e
) => e
.error_response()
146 impl From
<ImageError
> for CanvasError
{
147 fn from(e
: ImageError
) -> CanvasError
{
148 CanvasError
::ImageError(e
)
152 struct ToRgbIter
<'a
, T
> where T
: AsPrimitive
<u8> {
153 iter
: &'a
mut dyn Iterator
<Item
= T
>
156 impl<'a
, T
: AsPrimitive
<u8>> ToRgbIter
<'a
, T
> {
157 fn new(iter
: &'a
mut (dyn Iterator
<Item
= T
>)) -> ToRgbIter
<T
> {
162 impl<T
: AsPrimitive
<u8> + Zero
> Iterator
for ToRgbIter
<'_
, T
> {
164 fn next(&mut self) -> Option
<Rgb
<u8>> {
165 if let Some(r
) = self.iter
.next() {
166 let g
= self.iter
.next().unwrap
_or
_else
(T
::zero
);
167 let b
= self.iter
.next().unwrap
_or
_else
(T
::zero
);
168 Some(Rgb([r
.as_(), g
.as_(), b
.as_()]))
183 fn new(width
: u32, height
: u32) -> Canvas
{
184 Canvas
{ pen
: 0, img
: ImageBuffer
::new(width
, height
) }
187 /*fn from_image(img: RgbImage) -> Canvas {
188 Canvas { pen: 0, img }
191 fn replace(&self, img: RgbImage, pen: usize) -> Canvas {
197 fn advance_pen(mut self, offset
: usize) -> Canvas
{
198 //Canvas { pen: self.pen + offset, img: self.img }
200 self.pen
%= self.img
.width() as usize * self.img
.height() as usize;
205 struct Canvas0(Option
<Canvas
>);
206 struct Canvas1(Option
<Canvas
>);
207 struct Canvas2(Option
<Canvas
>);
209 #[get("/hello/{name}")]
210 async
fn greet(name
: web
::Path
<String
>, req
: HttpRequest
) -> impl Responder
{
211 println
!("{:?}", req
);
212 format
!("Hello {name}!\n{:?}", req
.headers().get("user-agent"))
215 fn rgb_encode_with_pen(img
: &mut RgbImage
, pen
: usize, data
: &mut dyn Iterator
<Item
= u8>) -> Result
<usize, ImageError
> {
216 let mut pixels
= img
.pixels_mut().skip(pen
);
219 for sp
in ToRgbIter
::new(data
) {
220 let mut dp
= match pixels
.next() {
221 Some(pixel
) => pixel
,
223 pixels
= img
.pixels_mut().skip(0);
224 pixels
.next().ok_or_else(ImageError
::dimension_error
)?
234 fn rgb_encode_to_canvas(mut canvas
: Canvas
, data
: &mut dyn Iterator
<Item
= u8>) -> Result
<Canvas
, CanvasError
> {
235 let counter
= rgb_encode_with_pen(&mut canvas
.img
, canvas
.pen
, data
)?
;
236 Ok(canvas
.advance_pen(counter
))
239 fn rgb_encode(mut img
: RgbImage
, data
: &mut dyn Iterator
<Item
= u8>) -> Result
<RgbImage
, ImageError
> {
240 rgb_encode_with_pen(&mut img
, 0, data
)?
;
244 fn sine_encode(mut img
: RgbImage
, wave_offset
: (u32, u32), amplitude
: f32, freq_base
: f32, run
: u32, wave_color
: Rgb
<u8>,
245 data
: &mut dyn Iterator
<Item
= u8>) -> Result
<RgbImage
, ImageError
>
247 let run
: f32 = run
as f32;
248 let (wave_offset_x
, wave_offset_y
) = wave_offset
;
249 let (wave_offset_x
, wave_offset_y
) = (wave_offset_x
as f32, wave_offset_y
as f32);
250 let signal
= &mut data
.peekable();
251 let mut x
: f32 = wave_offset_x
as f32;
252 let max_x
: f32 = (img
.width() - 1) as f32;
253 let max_y
: f32 = img
.height() as f32;
254 while signal
.peek().is
_some
() && (x
< max_x
) {
256 let s
= signal
.next().unwrap
_or
(0);
257 while ((x
- px
) < run
) && (x
< max_x
) {
259 (2.0 * PI
* freq_base
* x
* s
as f32).sin() * amplitude
+ wave_offset_y
261 else { wave_offset_y
};
263 if (y
> 0.0) && (y
< max_y
) {
264 img
.put_pixel(x
as u32, y
as u32, wave_color
);
271 fn offset_encode(mut img
: RgbImage
, offset
: (u32, u32), run
: u32, color
: Rgb
<u8>, data
: &mut dyn Iterator
<Item
= u8>) -> Result
<RgbImage
, ImageError
> {
272 let (offset_x
, offset_y
) = offset
;
273 let mut x
= offset_x
;
274 let mut data
= data
.peekable();
275 while (x
< img
.width()) && (data
.peek().is
_some
()) {
276 let v
= data
.next().unwrap
_or
(0);
277 let y
= offset_y
+ v
as u32;
278 let max_x
= if (x
+ run
) < img
.width() { x
+ run
} else { img
.width() };
280 if y
< img
.height() {
281 img
.put_pixel(x
, offset_y
+ v
as u32, color
);
289 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
>
290 where F
: Fn(Canvas
, &mut dyn Iterator
<Item
= u8>) -> Result
<Canvas
, CanvasError
>
292 let canvas
= match canvas_option
.take() {
293 Some(canvas
) => match dimensions
{
294 Some(dimensions
) => {
295 if dimensions
== canvas
.img
.dimensions() {
299 Err(CanvasError
::DimensionMismatch(canvas_option
.insert
(canvas
).img
.dimensions()))
305 None
=> match dimensions
{
306 Some((dim_x
, dim_y
)) =>
307 Ok(Canvas
::new(dim_x
, dim_y
)),
309 Err(CanvasError
::NotExists
)
313 Ok(canvas_option
.insert
(f(canvas
, percent_decode_str(data
).into
_iter
().borrow_mut())?
))
316 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
>
317 where F
: Fn(RgbImage
, &mut dyn Iterator
<Item
= u8>) -> Result
<RgbImage
, ImageError
>
319 // Image must not be larger than 1 megapixel
320 if dim_x
* dim_y
* scale
> 1048576 {
321 return Err(ImageError
::dimension_error())
324 let img
: RgbImage
= encode(ImageBuffer
::new(dim_x
, dim_y
), data
)?
;
325 let cursor
= image_to_cursor(img
, scale
)?
;
329 fn image_to_cursor(img
: RgbImage
, scale
: u32) -> Result
<Cursor
<Vec
<u8>>, ImageError
> {
330 let (dim_x
, dim_y
) = img
.dimensions();
331 let (dim_x
, dim_y
) = (dim_x
* scale
, dim_y
* scale
);
333 // Image must not be larger than 1 megapixel
334 if dim_x
* dim_y
> 1048576 {
335 return Err(ImageError
::dimension_error())
338 let mut cursor
= Cursor
::new(Vec
::new());
339 let img
= resize(&img
, dim_x
, dim_y
, FilterType
::Nearest
);
340 write_buffer_with_format(&mut cursor
, &img
, dim_x
, dim_y
, ColorType
::Rgb8
, Png
)?
;
345 #[get("/gen/0/{data}")]
346 async
fn img_gen0(req
: HttpRequest
) -> Result
<impl Responder
> {
347 let data
= extract_data
!(req
, 3);
348 let cursor
= make_png(32, 32, 16, percent_decode_str(data
).into
_iter
().borrow_mut(), rgb_encode
)?
;
349 Ok(response_image
!(cursor
))
352 #[get("/gen/1/{dim_x}/{dim_y}/{scale}/{data}")]
353 async
fn img_gen1(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32)>) -> Result
<impl Responder
> {
354 let (dim_x
, dim_y
, scale
) = path
.into
_inner
();
355 let data
= extract_data
!(req
, 6);
356 let cursor
= make_png(dim_x
, dim_y
, scale
, percent_decode_str(data
).into
_iter
().borrow_mut(), rgb_encode
)?
;
357 Ok(response_image
!(cursor
))
360 #[get("/gen/2/{dim_x}/{dim_y}/{scale}")]
361 async
fn img_gen2(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32)>) -> Result
<impl Responder
> {
362 let (dim_x
, dim_y
, scale
) = path
.into
_inner
();
363 let data
= extract_header
!(req
, "user-agent");
364 let cursor
= make_png(dim_x
, dim_y
, scale
, &mut data
.bytes(), rgb_encode
)?
;
365 Ok(response_image
!(cursor
))
368 #[get("/gen/3/{scale}")]
369 async
fn img_gen3(req
: HttpRequest
, path
: web
::Path
<u32>) -> Result
<impl Responder
> {
370 let scale
= path
.into
_inner
();
371 let data
= extract_header
!(req
, "user-agent");
372 let rgbimg
: RgbImage
= rgb_encode(ImageBuffer
::new(TIMEAVATAR_SIZE_U32
, TIMEAVATAR_SIZE_U32
), &mut data
.bytes())?
;
373 let mut resimg
: RgbImage
= ImageBuffer
::new(60 * TIMEAVATAR_SIZE_U32
, 24 * TIMEAVATAR_SIZE_U32
);
374 let time
= OffsetDateTime
::now_utc().time();
375 let (hour
, minute
): (i64, i64) = (time
.hour().into
(), time
.minute().into
());
376 overlay(&mut resimg
, &rgbimg
, minute
* TIMEAVATAR_SIZE_I64
, hour
* TIMEAVATAR_SIZE_I64
);
377 let cursor
= image_to_cursor(resimg
, scale
)?
;
378 Ok(response_image
!(cursor
))
381 #[get("/gen/4/{dim_x}/{dim_y}/{scale}/{wave_offset_x}/{wave_offset_y}/{amplitude}/{freq}/{run}/{wave_color}/{data}")]
382 #[allow(clippy::type_complexity)]
383 async
fn img_gen4(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32, u32, u32, f32, f32, u32)>) -> Result
<impl Responder
> {
384 let (dim_x
, dim_y
, scale
, wave_offset_x
, wave_offset_y
, amplitude
, freq_base
, run
) = path
.into
_inner
();
385 let freq_base
= freq_base
/ 1000.0;
386 let wave_color
= extract_color
!(req
, 11)?
;
387 let signal
= &mut percent_decode_str(extract_data
!(req
, 12));
388 let cursor
= make_png(dim_x
, dim_y
, scale
, signal
,
389 |img
, signal
| sine_encode(img
, (wave_offset_x
, wave_offset_y
), amplitude
, freq_base
, run
, wave_color
, signal
))?
;
390 Ok(response_image
!(cursor
))
393 #[get("/gen/5/{dim_x}/{dim_y}/{scale}/{offset_x}/{offset_y}/{run}/{color}/{data}")]
394 async
fn img_gen5(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32, u32, u32, u32)>) -> Result
<impl Responder
> {
395 let (dim_x
, dim_y
, scale
, offset_x
, offset_y
, run
) = path
.into
_inner
();
396 let color
= extract_color
!(req
, 9)?
;
397 let data
= &mut percent_decode_str(extract_data
!(req
, 10));
398 let cursor
= make_png(dim_x
, dim_y
, scale
, data
,
399 |img
, data
| offset_encode(img
, (offset_x
, offset_y
), run
, color
, data
))?
;
400 Ok(response_image
!(cursor
))
403 #[get("/pgen/0/{data}")]
404 async
fn img_pgen0(req
: HttpRequest
, canvas0
: web
::Data
<Arc
<Mutex
<Canvas0
>>>) -> Result
<impl Responder
> {
405 let data
= extract_data
!(req
, 3);
407 let img
: RgbImage
= ({
408 let canvas_option
= &mut *canvas0
.lock().unwrap
();
409 Ok(apply_to_canvas( |c
, d
| rgb_encode_to_canvas(c
, d
), &mut canvas_option
.0, None
, data
)?
.img
.clone())
410 } as Result
<RgbImage
, CanvasError
>)?
;
411 let cursor
= image_to_cursor(img
, scale
)?
;
412 Ok(response_image
!(cursor
))
415 #[get("/pgen/1/{dim_x}/{dim_y}/{scale}/{data}")]
416 async
fn img_pgen1(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32)>, canvas1
: web
::Data
<Arc
<Mutex
<Canvas1
>>>) -> Result
<impl Responder
> {
417 let (dim_x
, dim_y
, scale
) = path
.into
_inner
();
418 let data
= extract_data
!(req
, 6);
419 let img
: RgbImage
= ({
420 let canvas_option
= &mut *canvas1
.lock().unwrap
();
421 Ok(apply_to_canvas( |c
, d
| rgb_encode_to_canvas(c
, d
), &mut canvas_option
.0, Some((dim_x
, dim_y
)), data
)?
.img
.clone())
422 } as Result
<RgbImage
, CanvasError
>)?
;
423 let cursor
= image_to_cursor(img
, scale
)?
;
424 Ok(response_image
!(cursor
))
427 #[get("/pgen/2/{dim_x}/{dim_y}/{scale}")]
428 async
fn img_pgen2(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32)>, canvas1
: web
::Data
<Arc
<Mutex
<Canvas1
>>>) -> Result
<impl Responder
> {
429 let (dim_x
, dim_y
, scale
) = path
.into
_inner
();
430 let data
= extract_header
!(req
, "user-agent");
431 let img
: RgbImage
= ({
432 let canvas_option
= &mut *canvas1
.lock().unwrap
();
433 Ok(apply_to_canvas( |c
, d
| rgb_encode_to_canvas(c
, d
), &mut canvas_option
.0, Some((dim_x
, dim_y
)), data
)?
.img
.clone())
434 } as Result
<RgbImage
, CanvasError
>)?
;
435 let cursor
= image_to_cursor(img
, scale
)?
;
436 Ok(response_image
!(cursor
))
439 #[get("/pgen/3/{scale}")]
440 async
fn img_pgen3(req
: HttpRequest
, path
: web
::Path
<u32>, canvas2
: web
::Data
<Arc
<Mutex
<Canvas2
>>>) -> Result
<impl Responder
> {
441 let scale
= path
.into
_inner
();
442 let data
= extract_header
!(req
, "user-agent");
443 let time
= OffsetDateTime
::now_utc().time();
444 let (hour
, minute
): (i64, i64) = (time
.hour().into
(), time
.minute().into
());
445 let rgbimg
: RgbImage
= rgb_encode(ImageBuffer
::new(TIMEAVATAR_SIZE_U32
, TIMEAVATAR_SIZE_U32
), &mut data
.bytes())?
;
446 let resimg
: RgbImage
= ({
447 let canvas_option
= &mut *canvas2
.lock().unwrap
();
448 let mut canvas
= match canvas_option
.0.take() {
449 Some(canvas
) => canvas
,
450 None
=> Canvas
::new(60 * TIMEAVATAR_SIZE_U32
, 24 * TIMEAVATAR_SIZE_U32
)
452 overlay(&mut canvas
.img
, &rgbimg
, minute
* TIMEAVATAR_SIZE_I64
, hour
* TIMEAVATAR_SIZE_I64
);
453 Ok(canvas_option
.0.insert
(canvas
).img
.clone())
454 } as Result
<RgbImage
, CanvasError
>)?
;
455 let cursor
= image_to_cursor(resimg
, scale
)?
;
456 Ok(response_image
!(cursor
))
459 #[get("/pgen/4/{dim_x}/{dim_y}/{scale}/{wave_offset_x}/{wave_offset_y}/{amplitude}/{freq}/{run}/{wave_color}/{data}")]
460 #[allow(clippy::type_complexity)]
461 async
fn img_pgen4(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32, u32, u32, f32, f32, u32)>,
462 canvas1
: web
::Data
<Arc
<Mutex
<Canvas1
>>>) -> Result
<impl Responder
>
464 let (dim_x
, dim_y
, scale
, wave_offset_x
, wave_offset_y
, amplitude
, freq_base
, run
) = path
.into
_inner
();
465 let freq_base
= freq_base
/ 1000.0;
466 let wave_color
= extract_color
!(req
, 11)?
;
467 let data
= extract_data
!(req
, 12);
468 let img
: RgbImage
= ({
469 let canvas_option
= &mut *canvas1
.lock().unwrap
();
471 |mut canvas
, signal
| {
472 canvas
.img
= sine_encode(canvas
.img
, (wave_offset_x
, wave_offset_y
), amplitude
, freq_base
, run
, wave_color
, signal
)?
;
475 &mut canvas_option
.0, Some((dim_x
, dim_y
)), data
)?
.img
.clone())
476 } as Result
<RgbImage
, CanvasError
>)?
;
477 let cursor
= image_to_cursor(img
, scale
)?
;
478 Ok(response_image
!(cursor
))
481 #[get("/pgen/5/{dim_x}/{dim_y}/{scale}/{offset_x}/{offset_y}/{run}/{color}/{data}")]
482 async
fn img_pgen5(req
: HttpRequest
, path
: web
::Path
<(u32, u32, u32, u32, u32, u32)>,
483 canvas1
: web
::Data
<Arc
<Mutex
<Canvas1
>>>) -> Result
<impl Responder
>
485 let (dim_x
, dim_y
, scale
, offset_x
, offset_y
, run
) = path
.into
_inner
();
486 let color
= extract_color
!(req
, 9)?
;
487 let data
= extract_data
!(req
, 10);
488 let img
: RgbImage
= ({
489 let canvas_option
= &mut *canvas1
.lock().unwrap
();
492 canvas
.img
= offset_encode(canvas
.img
, (offset_x
, offset_y
), run
, color
, data
)?
;
495 &mut canvas_option
.0, Some((dim_x
, dim_y
)), data
)?
.img
.clone())
496 } as Result
<RgbImage
, CanvasError
>)?
;
497 let cursor
= image_to_cursor(img
, scale
)?
;
498 Ok(response_image
!(cursor
))
501 #[get("/pdrop/{canvas_id}")]
502 async
fn pdrop(canvas_id
: web
::Path
<u8>,
503 canvas0
: web
::Data
<Arc
<Mutex
<Canvas0
>>>,
504 canvas1
: web
::Data
<Arc
<Mutex
<Canvas1
>>>,
505 canvas2
: web
::Data
<Arc
<Mutex
<Canvas2
>>>) -> Result
<impl Responder
> {
507 let canvas_id
= canvas_id
.into
_inner
();
509 0 => canvas0
.lock().unwrap
().0.take(),
510 1 => canvas1
.lock().unwrap
().0.take(),
511 2 => canvas2
.lock().unwrap
().0.take(),
513 }.ok_or(CanvasError
::NotExists
)?
;
514 Ok(HttpResponse
::build(StatusCode
::OK
)
515 .body(format
!("Canvas {} dropped.", canvas_id
)))
519 #[actix_web::main] // or #[tokio::main]
520 async
fn main() -> std
::io
::Result
<()> {
521 env_logger
::builder()
522 .filter
_mod
ule
("actix_web", LevelFilter
::Info
)
526 let listenaddress
= env
::args().nth(1).unwrap
_or
_else
(|| "127.0.0.1:8080".to_string());
527 let canvas0
= Arc
::new(Mutex
::new(Canvas0(Some(Canvas
::new(32, 32)))));
528 let canvas1
= Arc
::new(Mutex
::new(Canvas1(None
)));
529 let canvas2
= Arc
::new(Mutex
::new(Canvas2(None
)));
531 HttpServer
::new(move || {
533 .wrap(Logger
::default())
548 .app_data(web
::Data
::new(canvas0
.clone()))
549 .app_data(web
::Data
::new(canvas1
.clone()))
550 .app_data(web
::Data
::new(canvas2
.clone()))
552 .bind(listenaddress
)?