2018-04-04 17:37:11 -04:00
|
|
|
import paper from '@scratch/paper';
|
|
|
|
import {getRaster} from '../layer';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tool for drawing with the bitmap brush.
|
|
|
|
*/
|
|
|
|
class BrushTool extends paper.Tool {
|
|
|
|
/**
|
|
|
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
|
|
|
*/
|
|
|
|
constructor (onUpdateSvg) {
|
|
|
|
super();
|
|
|
|
this.onUpdateSvg = onUpdateSvg;
|
|
|
|
|
|
|
|
// We have to set these functions instead of just declaring them because
|
|
|
|
// paper.js tools hook up the listeners in the setter functions.
|
|
|
|
this.onMouseDown = this.handleMouseDown;
|
|
|
|
this.onMouseDrag = this.handleMouseDrag;
|
|
|
|
this.onMouseUp = this.handleMouseUp;
|
|
|
|
|
|
|
|
this.colorState = null;
|
|
|
|
this.active = false;
|
2018-04-04 23:54:41 -04:00
|
|
|
this.lastPoint = null;
|
2018-04-05 17:20:42 -04:00
|
|
|
// For performance, make sure this is an integer
|
|
|
|
this.size = 10;
|
2018-04-04 17:37:11 -04:00
|
|
|
}
|
2018-04-04 23:54:41 -04:00
|
|
|
setColor (color) {
|
2018-04-05 17:20:42 -04:00
|
|
|
this.color = color;
|
2018-04-04 23:54:41 -04:00
|
|
|
}
|
2018-04-05 17:20:42 -04:00
|
|
|
line (point1, point2, callback){
|
|
|
|
// Bresenham line algorithm
|
2018-04-04 23:54:41 -04:00
|
|
|
// Fast Math.floor
|
|
|
|
let x1 = ~~point1.x;
|
2018-04-05 17:20:42 -04:00
|
|
|
const x2 = ~~point2.x;
|
2018-04-04 23:54:41 -04:00
|
|
|
let y1 = ~~point1.y;
|
2018-04-05 17:20:42 -04:00
|
|
|
const y2 = ~~point2.y;
|
2018-04-04 23:54:41 -04:00
|
|
|
|
|
|
|
const dx = Math.abs(x2 - x1);
|
|
|
|
const dy = Math.abs(y2 - y1);
|
|
|
|
const sx = (x1 < x2) ? 1 : -1;
|
|
|
|
const sy = (y1 < y2) ? 1 : -1;
|
|
|
|
let err = dx - dy;
|
|
|
|
|
2018-04-05 01:10:36 -04:00
|
|
|
callback(x1, y1);
|
2018-04-04 23:54:41 -04:00
|
|
|
while (x1 !== x2 || y1 !== y2) {
|
2018-04-05 17:20:42 -04:00
|
|
|
const e2 = err * 2;
|
|
|
|
if (e2 > -dy) {
|
2018-04-04 23:54:41 -04:00
|
|
|
err -= dy; x1 += sx;
|
|
|
|
}
|
|
|
|
if (e2 < dx) {
|
|
|
|
err += dx; y1 += sy;
|
|
|
|
}
|
2018-04-05 01:10:36 -04:00
|
|
|
callback(x1, y1);
|
2018-04-04 23:54:41 -04:00
|
|
|
}
|
|
|
|
}
|
2018-04-05 17:20:42 -04:00
|
|
|
fillEllipse (centerX, centerY, radiusX, radiusY, context) {
|
|
|
|
// Bresenham ellipse algorithm
|
|
|
|
centerX = ~~centerX;
|
|
|
|
centerY = ~~centerY;
|
|
|
|
radiusX = ~~radiusX;
|
|
|
|
radiusY = ~~radiusY;
|
|
|
|
const twoRadXSquared = 2 * radiusX * radiusX;
|
|
|
|
const twoRadYSquared = 2 * radiusY * radiusY;
|
|
|
|
let x = radiusX;
|
|
|
|
let y = 0;
|
|
|
|
let dx = radiusY * radiusY * (1 - (radiusX << 1));
|
|
|
|
let dy = radiusX * radiusX;
|
|
|
|
let error = 0;
|
|
|
|
let stoppingX = twoRadYSquared * radiusX;
|
|
|
|
let stoppingY = 0;
|
|
|
|
|
|
|
|
while (stoppingX >= stoppingY) {
|
|
|
|
context.fillRect(centerX - x, centerY - y, x << 1, y << 1);
|
|
|
|
y++;
|
|
|
|
stoppingY += twoRadXSquared;
|
|
|
|
error += dy;
|
|
|
|
dy += twoRadXSquared;
|
|
|
|
if ((error << 1) + dx > 0) {
|
|
|
|
x--;
|
|
|
|
stoppingX -= twoRadYSquared;
|
|
|
|
error += dx;
|
|
|
|
dx += twoRadYSquared;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
x = 0;
|
|
|
|
y = radiusY;
|
|
|
|
dx = radiusY * radiusY;
|
|
|
|
dy = radiusX * radiusX * (1 - (radiusY << 1));
|
|
|
|
error = 0;
|
|
|
|
stoppingX = 0;
|
|
|
|
stoppingY = twoRadXSquared * radiusY;
|
|
|
|
while (stoppingX <= stoppingY) {
|
|
|
|
context.fillRect(centerX - x, centerY - y, x * 2, y * 2);
|
|
|
|
x++;
|
|
|
|
stoppingX += twoRadYSquared;
|
|
|
|
error += dx;
|
|
|
|
dx += twoRadYSquared;
|
|
|
|
if ((error << 1) + dy > 0) {
|
|
|
|
y--;
|
|
|
|
stoppingY -= twoRadXSquared;
|
|
|
|
error += dy;
|
|
|
|
dy += twoRadXSquared;
|
2018-04-05 01:10:36 -04:00
|
|
|
}
|
2018-04-05 17:20:42 -04:00
|
|
|
|
2018-04-05 01:10:36 -04:00
|
|
|
}
|
2018-04-04 17:37:11 -04:00
|
|
|
}
|
2018-04-05 17:20:42 -04:00
|
|
|
// Draw a brush mark at the given point
|
|
|
|
draw (x, y) {
|
|
|
|
getRaster().drawImage(this.tmpCanvas, new paper.Point(x - ~~(this.size / 2), y - ~~(this.size / 2)));
|
|
|
|
}
|
2018-04-04 17:37:11 -04:00
|
|
|
handleMouseDown (event) {
|
|
|
|
if (event.event.button > 0) return; // only first mouse button
|
|
|
|
this.active = true;
|
2018-04-05 17:20:42 -04:00
|
|
|
|
|
|
|
this.tmpCanvas = document.createElement('canvas');
|
|
|
|
this.tmpCanvas.width = this.size;
|
|
|
|
this.tmpCanvas.height = this.size;
|
|
|
|
const context = this.tmpCanvas.getContext('2d');
|
|
|
|
context.imageSmoothingEnabled = false;
|
|
|
|
context.fillStyle = this.color;
|
|
|
|
// Small squares for pixel artists
|
|
|
|
if (this.size <= 4) {
|
|
|
|
context.fillRect(0, 0, this.size, this.size);
|
|
|
|
} else {
|
|
|
|
this.fillEllipse(this.size / 2, this.size / 2, this.size / 2, this.size / 2, context);
|
|
|
|
}
|
|
|
|
|
2018-04-05 01:10:36 -04:00
|
|
|
this.draw(event.point, event.point);
|
2018-04-04 23:54:41 -04:00
|
|
|
this.lastPoint = event.point;
|
2018-04-04 17:37:11 -04:00
|
|
|
}
|
|
|
|
handleMouseDrag (event) {
|
|
|
|
if (event.event.button > 0 || !this.active) return; // only first mouse button
|
|
|
|
|
|
|
|
if (this.isBoundingBoxMode) {
|
|
|
|
this.boundingBoxTool.onMouseDrag(event);
|
|
|
|
return;
|
|
|
|
}
|
2018-04-05 17:20:42 -04:00
|
|
|
this.line(this.lastPoint, event.point, this.draw.bind(this));
|
2018-04-04 23:54:41 -04:00
|
|
|
this.lastPoint = event.point;
|
2018-04-04 17:37:11 -04:00
|
|
|
}
|
|
|
|
handleMouseUp (event) {
|
|
|
|
if (event.event.button > 0 || !this.active) return; // only first mouse button
|
2018-04-05 17:20:42 -04:00
|
|
|
|
|
|
|
this.line(this.lastPoint, event.point, this.draw.bind(this));
|
|
|
|
|
|
|
|
this.tmpCanvas = null;
|
2018-04-04 23:54:41 -04:00
|
|
|
this.lastPoint = null;
|
2018-04-04 17:37:11 -04:00
|
|
|
this.active = false;
|
|
|
|
}
|
|
|
|
deactivateTool () {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default BrushTool;
|