2019-10-04 15:24:57 +00:00
|
|
|
// vim: set ts=2 sw=2 et tw=80:
|
|
|
|
class App {
|
2019-10-06 18:22:53 +00:00
|
|
|
static get BRUSHES() {
|
|
|
|
return {
|
|
|
|
"PenBrush": new PenBrush(),
|
|
|
|
"DiscBrush": new DiscBrush(),
|
|
|
|
"StarBrush": new StarBrush(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-10-04 15:24:57 +00:00
|
|
|
constructor(conf) {
|
|
|
|
if (!(typeof conf === 'object' && conf)) {
|
|
|
|
throw new Error('Argument conf different from specification');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.canvas = document.getElementById(conf.canvas);
|
|
|
|
if (!this.canvas || this.canvas.tagName !== 'CANVAS') {
|
|
|
|
throw new Error(`canvas is not a canvas`);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.ctx = this.canvas.getContext('2d');
|
|
|
|
this.favourites = document.querySelector('div#favourites');
|
|
|
|
|
|
|
|
if (typeof conf.buttons === 'object' && conf.buttons) {
|
|
|
|
this.buttons = {}
|
2019-10-06 18:22:53 +00:00
|
|
|
for (const b of ['clear', 'undo', 'camera'])
|
2019-10-04 15:24:57 +00:00
|
|
|
this.buttons[b] = document.getElementById(conf.buttons[b]);
|
|
|
|
|
2019-10-08 07:14:27 +00:00
|
|
|
if (this.buttons.clear) {
|
2019-10-06 18:22:53 +00:00
|
|
|
this.buttons.clear.addEventListener('click', () => {
|
|
|
|
this.erase();
|
2019-10-07 09:48:24 +00:00
|
|
|
this.background = null;
|
2019-10-06 18:22:53 +00:00
|
|
|
history.clear();
|
|
|
|
});
|
2019-10-08 07:14:27 +00:00
|
|
|
}
|
2019-10-06 18:22:53 +00:00
|
|
|
|
2019-10-08 07:14:27 +00:00
|
|
|
if (this.buttons.undo) {
|
|
|
|
this.buttons.undo.addEventListener('click', () => {
|
|
|
|
history.pop();
|
|
|
|
this.redrawAll();
|
|
|
|
});
|
|
|
|
}
|
2019-10-04 15:24:57 +00:00
|
|
|
|
2019-10-08 07:14:27 +00:00
|
|
|
if (this.buttons.camera) {
|
2019-10-04 15:24:57 +00:00
|
|
|
this.buttons.camera.addEventListener('click', () => {
|
|
|
|
const base64 = this.canvas.toDataURL();
|
|
|
|
const img = document.createElement('img');
|
|
|
|
img.src = base64;
|
|
|
|
const span = document.createElement('span');
|
|
|
|
span.contentEditable = true;
|
2019-10-07 10:19:01 +00:00
|
|
|
span.innerText = new Date().toString();
|
2019-10-04 15:24:57 +00:00
|
|
|
this.favourites.appendChild(img);
|
|
|
|
this.favourites.appendChild(span);
|
|
|
|
});
|
2019-10-08 07:14:27 +00:00
|
|
|
}
|
2019-10-07 09:48:24 +00:00
|
|
|
|
|
|
|
const player = document.createElement('video');
|
|
|
|
player.addEventListener('loadeddata', () => {
|
2019-10-10 08:02:30 +00:00
|
|
|
player.play();
|
|
|
|
setTimeout(() => {
|
|
|
|
const imgCanvas = document.createElement('canvas');
|
|
|
|
imgCanvas.width = this.canvas.width;
|
|
|
|
imgCanvas.height = this.canvas.height;
|
|
|
|
const imgCtx = imgCanvas.getContext('2d');
|
|
|
|
imgCtx.drawImage(player, 0, 0, canvas.width, canvas.height);
|
|
|
|
const imgData = imgCtx.getImageData(0, 0, imgCanvas.width,
|
|
|
|
imgCanvas.height);
|
|
|
|
|
|
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
|
|
const pixel = (data[i] + 2 * data[i+1] + data[i+2]) / 4;
|
|
|
|
data[i] = data[i+1] = data[i+2] = pixel;
|
|
|
|
}
|
|
|
|
imgCtx.putImageData(imgData, 0, 0);
|
|
|
|
|
|
|
|
const img = document.createElement('img');
|
|
|
|
img.src = imgCanvas.toDataURL();
|
|
|
|
img.addEventListener('load', () => {
|
|
|
|
this.background = img;
|
|
|
|
this.redrawAll();
|
|
|
|
});
|
2019-10-07 09:48:24 +00:00
|
|
|
|
2019-10-10 08:02:30 +00:00
|
|
|
player.srcObject.getVideoTracks().forEach(track => track.stop());
|
|
|
|
}, 100);
|
2019-10-07 09:48:24 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const button = document.createElement('button');
|
|
|
|
button.type = 'button';
|
|
|
|
button.innerHTML = 'Photo';
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
navigator.mediaDevices.getUserMedia({ video: true })
|
|
|
|
.then((stream) => {
|
|
|
|
player.srcObject = stream;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById('left-toolbar').appendChild(button);
|
2019-10-04 15:24:57 +00:00
|
|
|
}
|
|
|
|
|
2019-10-06 18:22:53 +00:00
|
|
|
this.ctx.lineWidth = 1;
|
|
|
|
this.strokeStyle = this.constructor.defaultStrokeStyle;
|
|
|
|
this.brush = "PenBrush";
|
2019-10-04 15:24:57 +00:00
|
|
|
|
2019-10-06 18:22:53 +00:00
|
|
|
const brushToolbar = document.querySelector('#brush-toolbar');
|
2019-10-07 10:17:28 +00:00
|
|
|
if (brushToolbar) {
|
|
|
|
for (const name in App.BRUSHES) {
|
|
|
|
const b = document.createElement('button');
|
|
|
|
b.innerText = name;
|
|
|
|
b.addEventListener('click', () => this.brush = name);
|
|
|
|
brushToolbar.appendChild(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
const label = document.createElement('label');
|
|
|
|
label.setAttribute('for', 'color');
|
|
|
|
const color = document.createElement('input');
|
|
|
|
color.type = 'color';
|
|
|
|
color.name = 'color';
|
|
|
|
color.value = this.constructor.defaultStrokeStyle;
|
|
|
|
color.addEventListener('change', () => this.strokeStyle = color.value);
|
|
|
|
brushToolbar.appendChild(label);
|
|
|
|
brushToolbar.appendChild(color);
|
2019-10-06 18:22:53 +00:00
|
|
|
}
|
2019-10-04 15:24:57 +00:00
|
|
|
|
2019-10-08 07:14:27 +00:00
|
|
|
const toMouse = (e, func) => {
|
|
|
|
if (e && e.touches && e.touches[0]) {
|
|
|
|
return func.bind(this)({
|
|
|
|
offsetX: e.touches[0].pageX - this.canvas.offsetLeft,
|
|
|
|
offsetY: e.touches[0].pageY - this.canvas.offsetTop
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.canvas.addEventListener('touchstart', e => toMouse(e, this.startPath));
|
2019-10-06 18:22:53 +00:00
|
|
|
this.canvas.addEventListener('mousedown', this.startPath.bind(this));
|
2019-10-08 07:14:27 +00:00
|
|
|
this.canvas.addEventListener('touchmove', e => toMouse(e, this.draw));
|
2019-10-06 18:22:53 +00:00
|
|
|
this.canvas.addEventListener('mousemove', this.draw.bind(this));
|
2019-10-08 07:14:27 +00:00
|
|
|
this.canvas.addEventListener('touchcancel', e => toMouse(e, this.endPath));
|
2019-10-06 18:22:53 +00:00
|
|
|
this.canvas.addEventListener('mouseup', this.endPath.bind(this));
|
|
|
|
this.canvas.addEventListener('mouseout', this.endPath.bind(this));
|
2019-10-04 15:24:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static get defaultStrokeStyle() {
|
|
|
|
return 'black';
|
|
|
|
}
|
|
|
|
|
|
|
|
get strokeStyle() {
|
2019-10-07 10:17:28 +00:00
|
|
|
if (this.ctx.strokeStyle == '#000000') {
|
|
|
|
return 'black';
|
|
|
|
}
|
2019-10-04 15:24:57 +00:00
|
|
|
return this.ctx.strokeStyle;
|
|
|
|
}
|
|
|
|
|
|
|
|
set strokeStyle(style) {
|
|
|
|
if (typeof style !== 'string') {
|
|
|
|
throw new Error('style is not a string');
|
|
|
|
}
|
|
|
|
this.ctx.strokeStyle = style;
|
|
|
|
}
|
|
|
|
|
2019-10-06 18:22:53 +00:00
|
|
|
get brush() {
|
|
|
|
return this._brush.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
set brush(brushName) {
|
|
|
|
this._brush = App.BRUSHES[brushName];
|
|
|
|
}
|
|
|
|
|
|
|
|
erase() {
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
}
|
|
|
|
|
|
|
|
startPath(e, record = true) {
|
2019-10-04 15:24:57 +00:00
|
|
|
this.ctx.beginPath();
|
|
|
|
this.ctx.moveTo(e.offsetX, e.offsetY);
|
2019-10-06 18:22:53 +00:00
|
|
|
|
|
|
|
if (record) {
|
|
|
|
history.initializeNewPath();
|
2019-10-07 09:48:24 +00:00
|
|
|
history.push(new Stroke(this.brush, this.strokeStyle,
|
|
|
|
e.offsetX, e.offsetY));
|
2019-10-06 18:22:53 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 07:14:27 +00:00
|
|
|
if (e instanceof MouseEvent) {
|
|
|
|
this.mousedown = true;
|
|
|
|
}
|
2019-10-06 18:22:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
draw(e, beginNew = true, record = true) {
|
2019-10-08 07:14:27 +00:00
|
|
|
if (this.mousedown || !(e instanceof MouseEvent)) {
|
2019-10-06 18:22:53 +00:00
|
|
|
this._brush.draw(this.ctx, this.strokeStyle, e.offsetX, e.offsetY);
|
|
|
|
|
|
|
|
if (record) {
|
2019-10-07 09:48:24 +00:00
|
|
|
history.push(new Stroke(this.brush, this.strokeStyle,
|
|
|
|
e.offsetX, e.offsetY));
|
2019-10-06 18:22:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (beginNew) {
|
|
|
|
this.ctx.beginPath();
|
|
|
|
this.ctx.moveTo(e.offsetX, e.offsetY);
|
2019-10-08 07:14:27 +00:00
|
|
|
} else if (e instanceof MouseEvent) {
|
2019-10-06 18:22:53 +00:00
|
|
|
this.mousedown = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
endPath(e, record = true) {
|
|
|
|
this.draw(e, false, record);
|
|
|
|
}
|
|
|
|
|
|
|
|
drawPath(path) {
|
|
|
|
const last = path.length - 1;
|
|
|
|
const lastBrush = this.brush;
|
2019-10-07 09:48:24 +00:00
|
|
|
const lastStyle = this.strokeStyle;
|
2019-10-06 18:22:53 +00:00
|
|
|
for (let i = 0; i <= last; i++) {
|
|
|
|
this.brush = path[i].brushName;
|
2019-10-07 09:48:24 +00:00
|
|
|
this.strokeStyle = path[i].strokeStyle;
|
2019-10-06 18:22:53 +00:00
|
|
|
switch(i) {
|
|
|
|
case 0: this.startPath(path[i], false); break;
|
|
|
|
case last: this.endPath(path[i], false); break;
|
|
|
|
default: this.draw(path[i], true, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.brush = lastBrush;
|
2019-10-07 09:48:24 +00:00
|
|
|
this.strokeStyle = lastStyle;
|
2019-10-04 15:24:57 +00:00
|
|
|
}
|
|
|
|
|
2019-10-07 09:48:24 +00:00
|
|
|
redrawAll() {
|
|
|
|
this.erase();
|
|
|
|
if (this.background) {
|
|
|
|
this.ctx.drawImage(this.background, 0, 0, this.canvas.width,
|
|
|
|
this.canvas.height);
|
|
|
|
}
|
|
|
|
for (const path of history.paths) {
|
|
|
|
this.drawPath(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|