Save path into image & fetch latest image
This commit is contained in:
parent
6c908a572f
commit
dd09207066
3 changed files with 156 additions and 26 deletions
103
server.py
103
server.py
|
@ -1,11 +1,15 @@
|
|||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import logging
|
||||
import coloredlogs
|
||||
import argparse
|
||||
import json
|
||||
from urllib.parse import urlparse
|
||||
import uuid
|
||||
|
||||
import coloredlogs
|
||||
import glob
|
||||
|
||||
|
||||
logger = logging.getLogger("drawing")
|
||||
|
@ -23,11 +27,38 @@ argParser.add_argument(
|
|||
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):
|
||||
def set_extra_headers(self, path):
|
||||
"""For subclass to add extra headers to the response"""
|
||||
if path[-5:] == '.html':
|
||||
self.set_header("Access-Control-Allow-Origin", "*")
|
||||
if path[-4:] == '.svg':
|
||||
self.set_header("Content-Type", "image/svg+xml")
|
||||
|
||||
class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||
CORS_ORIGINS = ['localhost', '.mturk.com']
|
||||
|
@ -43,20 +74,36 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
|||
def open(self, p = None):
|
||||
self.__class__.connections.add(self)
|
||||
logger.info("New client connected")
|
||||
self.write_message("hello!")
|
||||
self.strokes = []
|
||||
# self.write_message("hello!")
|
||||
|
||||
# the client sent the message
|
||||
def on_message(self, message):
|
||||
logger.debug(f"recieve: {message}")
|
||||
try:
|
||||
msg = json.loads(message)
|
||||
# TODO: sanitize input: min/max, limit strokes
|
||||
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':
|
||||
logger.info(f'up: {msg}')
|
||||
point = [msg['direction'][0],msg['direction'][1], 1]
|
||||
self.strokes.append(point)
|
||||
|
||||
elif msg['action'] == 'submit':
|
||||
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':
|
||||
# not used, implicit in move?
|
||||
pass
|
||||
|
@ -72,17 +119,54 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
|||
def on_close(self):
|
||||
self.__class__.rmConnection(self)
|
||||
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
|
||||
def rmConnection(cls, client):
|
||||
if client not in cls.connections:
|
||||
return
|
||||
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__":
|
||||
args = argParser.parse_args()
|
||||
print(logger.level)
|
||||
|
||||
coloredlogs.install(
|
||||
level=logging.DEBUG if args.verbose else logging.INFO,
|
||||
)
|
||||
|
@ -98,6 +182,7 @@ if __name__ == "__main__":
|
|||
|
||||
application = tornado.web.Application([
|
||||
(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,
|
||||
{"path": 'www', "default_filename": 'index.html'}),
|
||||
], debug=True)
|
||||
|
|
0
www/generated/.gitignore
vendored
Normal file
0
www/generated/.gitignore
vendored
Normal file
|
@ -4,10 +4,14 @@
|
|||
<meta charset="utf-8">
|
||||
<title>MT Request: draw over the image</title>
|
||||
<style media="screen">
|
||||
svg{
|
||||
height:600px;
|
||||
width:600px;
|
||||
border:solid 1px;
|
||||
#sample, svg{
|
||||
position:absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
path {
|
||||
|
@ -15,21 +19,45 @@
|
|||
stroke: red;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
body.submitted path{
|
||||
stroke:darkgray;
|
||||
}
|
||||
|
||||
body.submitted .buttons{
|
||||
display:none;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
height: 600px;
|
||||
width: 600px;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='interface'>
|
||||
<img src="" id='sample'>
|
||||
<div id='wrapper'>
|
||||
<img src="/latest.svg" id='sample'>
|
||||
<svg id="canvas">
|
||||
<path d="" id="stroke">
|
||||
<path d="" id="stroke" />
|
||||
</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>
|
||||
<script type="text/javascript">
|
||||
let url = window.location.origin.replace('http', 'ws') +'/ws';
|
||||
let svgEl = document.getElementById("canvas");
|
||||
let strokeEl = document.getElementById('stroke');
|
||||
let submitEl = document.getElementById('submit');
|
||||
let resetEl = document.getElementById('reset');
|
||||
let messageEl = document.getElementById('message');
|
||||
|
||||
let strokes = [];
|
||||
let isDrawing = false;
|
||||
let draw = function(e) {
|
||||
|
@ -54,6 +82,10 @@
|
|||
isDrawing = false;
|
||||
|
||||
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){
|
||||
// mark point as last of stroke
|
||||
|
@ -61,6 +93,7 @@
|
|||
}
|
||||
socket.send(JSON.stringify({
|
||||
'action': 'up',
|
||||
'direction': [x, y]
|
||||
}));
|
||||
|
||||
};
|
||||
|
@ -89,25 +122,37 @@
|
|||
}
|
||||
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);
|
||||
|
||||
socket.addEventListener('message', function(e){
|
||||
console.log('receive', e.data);
|
||||
if(e.data == 'submitted') {
|
||||
// TODO close the interface
|
||||
let msg = JSON.parse(e.data);
|
||||
console.log('receive', msg);
|
||||
if(msg['action'] == 'submitted') {
|
||||
document.body.classList.add('submitted');
|
||||
messageEl.innerHTML = msg['msg'];
|
||||
svgEl.removeEventListener('mousedown', startDrawing);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('mouseup', penup);
|
||||
svgEl.addEventListener('mousedown', function(e){
|
||||
isDrawing = true;
|
||||
|
||||
// start drawing
|
||||
document.body.addEventListener('mousemove', draw);
|
||||
|
||||
});
|
||||
svgEl.addEventListener('mousedown', startDrawing);
|
||||
resetEl.addEventListener('click', reset);
|
||||
submitEl.addEventListener('click', function(e){
|
||||
if(!strokes.length){
|
||||
alert('please draw before submitting');
|
||||
|
|
Loading…
Reference in a new issue