Save path into image & fetch latest image

This commit is contained in:
Ruben van de Ven 2019-09-11 21:00:06 +02:00
parent 6c908a572f
commit dd09207066
3 changed files with 156 additions and 26 deletions

103
server.py
View file

@ -1,11 +1,15 @@
import argparse
import json
import logging
import os
import tornado.ioloop import tornado.ioloop
import tornado.web import tornado.web
import tornado.websocket import tornado.websocket
import logging
import coloredlogs
import argparse
import json
from urllib.parse import urlparse from urllib.parse import urlparse
import uuid
import coloredlogs
import glob
logger = logging.getLogger("drawing") logger = logging.getLogger("drawing")
@ -23,11 +27,38 @@ argParser.add_argument(
action="store_true", action="store_true",
) )
generated_image_dir = os.path.join('www','generated')
def strokes2D(strokes):
# strokes to a d attribute for a path
d = "";
last_stroke = None;
cmd = "";
for stroke in strokes:
if not last_stroke:
d += f"M{stroke[0]},{stroke[1]} "
cmd = 'M'
else:
if last_stroke[2] == 1:
d += " m"
cmd = 'm'
elif cmd != 'l':
d+=' l '
cmd = 'l'
rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]];
d += f"{rel_stroke[0]},{rel_stroke[1]} "
last_stroke = stroke;
return d;
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler): class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path): def set_extra_headers(self, path):
"""For subclass to add extra headers to the response""" """For subclass to add extra headers to the response"""
if path[-5:] == '.html': if path[-5:] == '.html':
self.set_header("Access-Control-Allow-Origin", "*") self.set_header("Access-Control-Allow-Origin", "*")
if path[-4:] == '.svg':
self.set_header("Content-Type", "image/svg+xml")
class WebSocketHandler(tornado.websocket.WebSocketHandler): class WebSocketHandler(tornado.websocket.WebSocketHandler):
CORS_ORIGINS = ['localhost', '.mturk.com'] CORS_ORIGINS = ['localhost', '.mturk.com']
@ -43,20 +74,36 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self, p = None): def open(self, p = None):
self.__class__.connections.add(self) self.__class__.connections.add(self)
logger.info("New client connected") logger.info("New client connected")
self.write_message("hello!") self.strokes = []
# self.write_message("hello!")
# the client sent the message # the client sent the message
def on_message(self, message): def on_message(self, message):
logger.debug(f"recieve: {message}") logger.debug(f"recieve: {message}")
try: try:
msg = json.loads(message) msg = json.loads(message)
# TODO: sanitize input: min/max, limit strokes
if msg['action'] == 'move': if msg['action'] == 'move':
pass # TODO: min/max input
point = [float(msg['direction'][0]),float(msg['direction'][1]), 0]
self.strokes.append(point)
elif msg['action'] == 'up': elif msg['action'] == 'up':
logger.info(f'up: {msg}') logger.info(f'up: {msg}')
point = [msg['direction'][0],msg['direction'][1], 1]
self.strokes.append(point)
elif msg['action'] == 'submit': elif msg['action'] == 'submit':
logger.info(f'up: {msg}') logger.info(f'up: {msg}')
self.write_message(json.dumps('submitted')) id = self.submit_strokes()
if not id:
self.write_message(json.dumps('error'))
return
self.write_message(json.dumps({
'action': 'submitted',
'msg': f"Submission ok, please refer to your submission as: {id}"
}))
elif msg['action'] == 'down': elif msg['action'] == 'down':
# not used, implicit in move? # not used, implicit in move?
pass pass
@ -72,17 +119,54 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
def on_close(self): def on_close(self):
self.__class__.rmConnection(self) self.__class__.rmConnection(self)
logger.info("Client disconnected") logger.info("Client disconnected")
def submit_strokes(self):
if len(self.strokes) < 1:
return False
d = strokes2D(self.strokes)
svg = f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 600 600"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
>
<path d="{d}" style="stroke:black;stroke-width:2;fill:none;" />
</svg>
"""
id = uuid.uuid4().hex
filename = os.path.join(generated_image_dir , id+'.svg')
with open(filename, 'w') as fp:
logger.info(f"Wrote {filename}")
fp.write(svg)
return id
@classmethod @classmethod
def rmConnection(cls, client): def rmConnection(cls, client):
if client not in cls.connections: if client not in cls.connections:
return return
cls.connections.remove(client) cls.connections.remove(client)
class LatestImageHandler(tornado.web.RequestHandler):
def get(self):
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
self.set_header("Content-Type", "image/svg+xml")
list_of_files = glob.glob(os.path.join(generated_image_dir,'*.svg'))
latest_file = max(list_of_files, key=os.path.getctime)
with open(latest_file, 'r') as fp:
self.write(fp.read())
if __name__ == "__main__": if __name__ == "__main__":
args = argParser.parse_args() args = argParser.parse_args()
print(logger.level)
coloredlogs.install( coloredlogs.install(
level=logging.DEBUG if args.verbose else logging.INFO, level=logging.DEBUG if args.verbose else logging.INFO,
) )
@ -98,6 +182,7 @@ if __name__ == "__main__":
application = tornado.web.Application([ application = tornado.web.Application([
(r"/ws(.*)", WebSocketHandler), (r"/ws(.*)", WebSocketHandler),
(r"/latest.svg", LatestImageHandler), # TODO: have js request the right image, based on a 'start' button. This way we can trace the history of a drawing
(r"/(.*)", StaticFileWithHeaderHandler, (r"/(.*)", StaticFileWithHeaderHandler,
{"path": 'www', "default_filename": 'index.html'}), {"path": 'www', "default_filename": 'index.html'}),
], debug=True) ], debug=True)

0
www/generated/.gitignore vendored Normal file
View file

View file

@ -4,10 +4,14 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>MT Request: draw over the image</title> <title>MT Request: draw over the image</title>
<style media="screen"> <style media="screen">
svg{ #sample, svg{
height:600px; position:absolute;
width:600px; top: 0;
border:solid 1px; left: 0;
bottom: 0;
right:0;
width:100%;
height:100%;
} }
path { path {
@ -15,21 +19,45 @@
stroke: red; stroke: red;
stroke-width: 2px; stroke-width: 2px;
} }
body.submitted path{
stroke:darkgray;
}
body.submitted .buttons{
display:none;
}
#wrapper {
height: 600px;
width: 600px;
position: relative;
}
</style> </style>
</head> </head>
<body> <body>
<div id='interface'> <div id='interface'>
<img src="" id='sample'> <div id='wrapper'>
<img src="/latest.svg" id='sample'>
<svg id="canvas"> <svg id="canvas">
<path d="" id="stroke"> <path d="" id="stroke" />
</svg> </svg>
<button id='submit'>Submit</button> </div>
<div class='buttons'>
<button id='submit'>Submit</button>
<button id='reset'>Reset</button>
</div>
<div id='message'></div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
let url = window.location.origin.replace('http', 'ws') +'/ws'; let url = window.location.origin.replace('http', 'ws') +'/ws';
let svgEl = document.getElementById("canvas"); let svgEl = document.getElementById("canvas");
let strokeEl = document.getElementById('stroke'); let strokeEl = document.getElementById('stroke');
let submitEl = document.getElementById('submit'); let submitEl = document.getElementById('submit');
let resetEl = document.getElementById('reset');
let messageEl = document.getElementById('message');
let strokes = []; let strokes = [];
let isDrawing = false; let isDrawing = false;
let draw = function(e) { let draw = function(e) {
@ -54,6 +82,10 @@
isDrawing = false; isDrawing = false;
document.body.removeEventListener('mousemove', draw); document.body.removeEventListener('mousemove', draw);
let pos = svgEl.getBoundingClientRect()
let x = e.x - pos['left'];
let y = e.y - pos['top'];
if(strokes.length > 0){ if(strokes.length > 0){
// mark point as last of stroke // mark point as last of stroke
@ -61,6 +93,7 @@
} }
socket.send(JSON.stringify({ socket.send(JSON.stringify({
'action': 'up', 'action': 'up',
'direction': [x, y]
})); }));
}; };
@ -89,25 +122,37 @@
} }
return d; return d;
} }
let startDrawing = function(e){
isDrawing = true;
// start drawing
document.body.addEventListener('mousemove', draw);
};
let reset = function() {
strokes = [];
strokeEl.setAttribute('d', "");
socket.send(JSON.stringify({
'action': 'reset',
}));
}
let socket = new WebSocket(url); let socket = new WebSocket(url);
socket.addEventListener('message', function(e){ socket.addEventListener('message', function(e){
console.log('receive', e.data); let msg = JSON.parse(e.data);
if(e.data == 'submitted') { console.log('receive', msg);
// TODO close the interface if(msg['action'] == 'submitted') {
document.body.classList.add('submitted');
messageEl.innerHTML = msg['msg'];
svgEl.removeEventListener('mousedown', startDrawing);
} }
}); });
document.body.addEventListener('mouseup', penup); document.body.addEventListener('mouseup', penup);
svgEl.addEventListener('mousedown', function(e){ svgEl.addEventListener('mousedown', startDrawing);
isDrawing = true; resetEl.addEventListener('click', reset);
// start drawing
document.body.addEventListener('mousemove', draw);
});
submitEl.addEventListener('click', function(e){ submitEl.addEventListener('click', function(e){
if(!strokes.length){ if(!strokes.length){
alert('please draw before submitting'); alert('please draw before submitting');