turtlestitch/vectorPaint.js

2320 wiersze
96 KiB
JavaScript
Czysty Zwykły widok Historia

/*
vectorPaint.js
a vector paint editor for Snap!
inspired by the Snap bitmap paint editor and the Scratch paint editor.
written by ****
Copyright (C) 2015 by ****
This file is part of Snap!.
Snap! is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
prerequisites:
--------------
needs paint.js, blocks.js, gui.js, threads.js, objects.js and morphic.js
toc
---
the following list shows the order in which all constructors are
defined. Use this list to locate code in this document:
VectorShape
VectorRectangle
VectorLine
VectorBrush
VectorEllipse
VectorClosedBrushPath
VectorPolygon
VectorPaintEditorMorph
VectorPaintCanvasMorph
VectorCostume;
credits
-------
*/
/*global Point, Object, Rectangle, VectorCostume, Costume,
ToggleButtonMorph, SymbolMorph, AlignmentMorph, Morph,
PaintColorPickerMorph, Color, SliderMorph, InputFieldMorph,
ToggleMorph, TextMorph, Image, VectorPaintEditorMorph, newCanvas */
// Declarations
var VectorShape;
var VectorRectangle;
var VectorLine;
var VectorBrush;
var VectorEllipse;
var VectorClosedBrushPath;
var VectorPolygon;
var VectorPaintEditorMorph;
var VectorPaintCanvasMorph;
var VectorCostume;
/* New symbolsMorph in Vector editor */
SymbolMorph.prototype.names.push('selection');
SymbolMorph.prototype.names.push('polygon');
SymbolMorph.prototype.names.push('closedBrushPath');
SymbolMorph.prototype.names.push('duplicate');
SymbolMorph.prototype.originalSymbolCanvasColored = SymbolMorph.prototype.symbolCanvasColored;
SymbolMorph.prototype.symbolCanvasColored = function (aColor) {
var canvas = newCanvas(new Point(this.symbolWidth(), this.size));
switch (this.name) {
case 'selection':
return this.drawSymbolSelection(canvas, aColor);
case 'polygon':
return this.drawSymbolOctagonOutline(canvas, aColor);
case 'closedBrushPath':
return this.drawSymbolClosedBrushPath(canvas, aColor);
case 'duplicate':
return this.drawSymbolDuplicate(canvas, aColor);
}
return this.originalSymbolCanvasColored(aColor);
}
SymbolMorph.prototype.drawSymbolSelection = function (canvas, color) {
// answer a canvas showing a filled arrow and a dashed rectangle
var ctx = canvas.getContext('2d'),
w = canvas.width;
h = canvas.height;
ctx.save();
ctx.setLineDash([3]);
this.drawSymbolRectangle(canvas, color);
ctx.restore();
ctx.save();
ctx.fillStyle = color.toString();
ctx.translate(0.7*w, 0.4*h);
ctx.scale(0.5,0.5);
ctx.rotate(radians(135));
this.drawSymbolArrowDownOutline(canvas, color);
ctx.fill();
ctx.restore();
return canvas;
};
SymbolMorph.prototype.drawSymbolOctagonOutline = function (canvas, color) {
// answer a canvas showing an octagon
var ctx = canvas.getContext('2d'),
side = canvas.width,
vert = (side - (side * 0.383)) / 2;
ctx.fillStyle = color.toString();
ctx.beginPath();
ctx.moveTo(vert, 0);
ctx.lineTo(side - vert, 0);
ctx.lineTo(side, vert);
ctx.lineTo(side, side - vert);
ctx.lineTo(side - vert, side);
ctx.lineTo(vert, side);
ctx.lineTo(0, side - vert);
ctx.lineTo(0, vert);
ctx.closePath();
ctx.stroke();
return canvas;
};
SymbolMorph.prototype.drawSymbolClosedBrushPath = function (canvas, color) {
// answer a canvas showing an cloud
var ctx = canvas.getContext('2d');
ctx.save();
this.drawSymbolCloudOutline(canvas, color);
ctx.restore();
return canvas;
};
SymbolMorph.prototype.drawSymbolDuplicate = function (canvas, color) {
// answer a canvas showing a rubber stamping
var ctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height;
ctx.save();
ctx.beginPath();
ctx.arc(w / 2, h / 8, w / 6, radians(0), radians(360), false);
ctx.fillRect((w / 2)-ctx.lineWidth, h / 5, w / 8, h*0.5);
ctx.fillRect(w/8, h/2, w*0.8, h / 4);
ctx.fillRect(w/4, 0.75*h, w*0.55, h / 6);
ctx.fill();
ctx.closePath;
ctx.restore();
return canvas;
};
// VectorShape
VectorShape.prototype = new Object();
VectorShape.prototype.constructor = VectorShape;
VectorShape.uber = Object.prototype;
function VectorShape(borderWidth, borderColor, threshold) {
this.init(borderWidth, borderColor, threshold);
}
VectorShape.prototype.init = function(borderWidth, borderColor, threshold) {
this.borderWidth = borderWidth; // get from editor
this.borderColor = borderColor; // get from editor
this.threshold = threshold === undefined ? 10: threshold;
this.image = newCanvas();
}
VectorShape.prototype.toString = function () {
return 'a ' +
(this.constructor.name ||
this.constructor.toString().split(' ')[1].split('(')[0])
}
VectorShape.prototype.copy = function (newshape) {
var shape = newshape || new VectorShape(this.borderWidth, this.borderColor);
shape.image.width = this.image.width;
shape.image.height = this.image.height;
shape.threshold = this.threshold;
shape.image.getContext("2d").drawImage(this.image,0,0);
return shape;
}
VectorShape.prototype.drawBoundingBox = function(context, origin, destination) {
var bounds = this.getBounds();
/* Drawing corners */
context.lineWidth = 1;
context.strokeStyle = "grey";
context.setLineDash([6]);
context.beginPath();
context.strokeRect(bounds.left, bounds.top, Math.abs(bounds.left-bounds.right), Math.abs(bounds.top-bounds.bottom));
/* Drawing corners */
context.fillStyle = "white";
context.strokeStyle = "black";
context.setLineDash([]);
/* upper-left corner */
context.beginPath();
context.arc(bounds.left,bounds.top,4,0,2*Math.PI);
context.closePath();
context.stroke();
context.fill();
/* upper-right corner */
context.beginPath();
context.arc(bounds.right,bounds.top,4,0,2*Math.PI);
context.closePath();
context.stroke();
context.fill();
/* bottom-left corner */
context.beginPath();
context.arc(bounds.left,bounds.bottom,4,0,2*Math.PI);
context.closePath();
context.stroke();
context.fill();
/* bottom-right corner */
context.beginPath();
context.arc(bounds.right,bounds.bottom,4,0,2*Math.PI);
context.closePath();
context.stroke();
context.fill();
}
VectorShape.prototype.isEndPointInBoundingBox = function(leftTop, leftBottom, rightTop, rightBottom, aPoint) {
var threshold = 0,
radius = 4;
circle = new VectorEllipse(null, null, null, leftTop, null, radius, radius, threshold);
if(circle.containsPoint(aPoint)) return 'leftTop';
circle = new VectorEllipse(null, null, null, leftBottom, null, radius, radius, threshold);
if(circle.containsPoint(aPoint)) return 'leftBottom';
circle = new VectorEllipse(null, null, null, rightTop, null, radius, radius, threshold);
if(circle.containsPoint(aPoint)) return 'rightTop';
circle = new VectorEllipse(null, null, null, rightBottom, null, radius, radius, threshold);
if(circle.containsPoint(aPoint)) return 'rightBottom';
return false;
}
// VectorRectangle
VectorRectangle.prototype = new VectorShape();
VectorRectangle.prototype.constructor = VectorRectangle;
VectorRectangle.uber = VectorShape.prototype;
function VectorRectangle(borderWidth, borderColor, fillColor, origin, destination, threshold) {
VectorRectangle.uber.init.call(this, borderWidth, borderColor, threshold);
this.init(fillColor, origin, destination);
}
VectorRectangle.prototype.init = function(fillColor, origin, destination) {
this.origin = origin;
this.destination = destination;
this.fillColor = fillColor;
}
VectorRectangle.prototype.copy = function () {
var newRectangle = new VectorRectangle(
this.borderWidth,
this.borderColor,
this.fillColor,
this.origin,
this.destination
);
return VectorRectangle.uber.copy.call(this, newRectangle);
}
VectorRectangle.prototype.toString = function () {
return VectorRectangle.uber.toString.call(this) + ' from: ' + this.origin.toString() + ' to: ' + this.destination.toString();
}
VectorRectangle.prototype.containsPoint = function(aPoint) {
var rect = new Rectangle(
Math.min(this.origin.x, this.destination.x) - this.threshold,
Math.min(this.origin.y, this.destination.y) - this.threshold,
Math.max(this.origin.x, this.destination.x) + this.threshold,
Math.max(this.origin.y, this.destination.y) + this.threshold);
if (!rect.containsPoint(aPoint)) { return false };
return true;
}
VectorRectangle.prototype.containsPointEdge = function(aPoint) {
var smallest = new VectorRectangle(null, null, null,
new Point(Math.min(this.origin.x, this.destination.x) + this.threshold, Math.min(this.origin.y, this.destination.y) + this.threshold),
new Point(Math.max(this.origin.x, this.destination.x) - this.threshold, Math.max(this.origin.y, this.destination.y) - this.threshold), 0),
biggest = new VectorRectangle(null, null, null,
new Point(Math.min(this.origin.x, this.destination.x) - this.threshold, Math.min(this.origin.y, this.destination.y) - this.threshold),
new Point(Math.max(this.origin.x, this.destination.x) + this.threshold, Math.max(this.origin.y, this.destination.y) + this.threshold), 0);
if(!smallest.containsPoint(aPoint) && biggest.containsPoint(aPoint)) return true;
else false;
}
VectorRectangle.prototype.isFound = function(selectionBox) {
if ((selectionBox.origin.x === selectionBox.destination.x
&& selectionBox.origin.y === selectionBox.destination.y
&& this.containsPoint(selectionBox.origin))
|| (selectionBox.containsPoint(this.origin)
&& selectionBox.containsPoint(this.destination))) return true;
return false;
}
VectorRectangle.prototype.getBounds = function() {
return {
left: Math.min(this.origin.x, this.destination.x) - (this.borderWidth / 2),
top: Math.min(this.origin.y, this.destination.y) - (this.borderWidth / 2),
right: Math.max(this.origin.x, this.destination.x) + (this.borderWidth / 2),
bottom: Math.max(this.origin.y, this.destination.y) + (this.borderWidth / 2)
};
}
VectorRectangle.prototype.isInBoundingBox = function(aPoint) {
var bounds = this.getBounds(),
result = this.isEndPointInBoundingBox(new Point(bounds.left, bounds.top),
new Point(bounds.left, bounds.bottom),
new Point(bounds.right, bounds.top),
new Point(bounds.right, bounds.bottom),
aPoint);
if(!result) return this.containsPoint(aPoint);
return result;
}
VectorRectangle.prototype.exportAsSVG = function() {
var borderColor, fillColor, height = Math.abs(this.origin.y-this.destination.y),
width = Math.abs(this.origin.x - this.destination.x),
x = Math.min(this.origin.x, this.destination.x),
y = Math.min(this.origin.y, this.destination.y);
borderColor = this.borderColor != 'transparent' ? 'stroke="' + this.borderColor + '"' : 'stroke="none"';
fillColor = this.fillColor != 'transparent'? ' fill="' + this.fillColor + '"': ' fill="none"';
return '<rect height="' + height + '" width="' + width + '" x="' + x + '" y="' + y
+ '" stroke-width="' + this.borderWidth + '" ' + borderColor + fillColor + '/>';
}
// VectorLine
VectorLine.prototype = new VectorShape();
VectorLine.prototype.constructor = VectorLine;
VectorLine.uber = VectorShape.prototype;
function VectorLine(borderWidth, borderColor, fillColor, origin, destination, threshold) {
VectorLine.uber.init.call(this, borderWidth, borderColor, threshold);
this.init(fillColor, origin, destination);
}
VectorLine.prototype.init = function(fillColor, origin, destination) {
this.origin = origin;
this.destination = destination;
}
VectorLine.prototype.copy = function () {
var newLine = new VectorLine(
this.borderWidth,
this.borderColor,
this.fillColor,
this.origin,
this.destination
);
return VectorLine.uber.copy.call(this, newLine);
}
VectorLine.prototype.toString = function () {
return VectorLine.uber.toString.call(this) + ' from: ' + this.origin.toString() + ' to: ' + this.destination.toString();
}
VectorLine.prototype.containsPoint = function(aPoint) {
var cross,
rect = new Rectangle(
Math.min(this.origin.x, this.destination.x)-this.threshold,
Math.min(this.origin.y, this.destination.y)-this.threshold,
Math.max(this.origin.x, this.destination.x)+this.threshold,
Math.max(this.origin.y, this.destination.y)+this.threshold);
if (!rect.containsPoint(aPoint)) { return false };
cross = (aPoint.x - this.origin.x) * (this.destination.y - this.origin.y) - (aPoint.y - this.origin.y) * (this.destination.x - this.origin.x); // cross product
if (Math.abs(cross) > 1000) {return false}; // 1000 is a threshold
return true;
}
VectorLine.prototype.isFound = function(selectionBox) {
if ((selectionBox.origin.x === selectionBox.destination.x
&& selectionBox.origin.y === selectionBox.destination.y
&& this.containsPoint(selectionBox.origin))
|| (selectionBox.containsPoint(this.origin)
&& selectionBox.containsPoint(this.destination))) return true;
return false;
}
VectorLine.prototype.getBounds = function() {
return {left: Math.min(this.origin.x, this.destination.x),
top: Math.min(this.origin.y, this.destination.y),
right: Math.max(this.origin.x, this.destination.x),
bottom: Math.max(this.origin.y, this.destination.y)
};
}
VectorLine.prototype.isInBoundingBox = function(aPoint) {
var bounds = this.getBounds(),
result = this.isEndPointInBoundingBox(new Point(bounds.left, bounds.top),
new Point(bounds.left, bounds.bottom),
new Point(bounds.right, bounds.top),
new Point(bounds.right, bounds.bottom),
aPoint);
if(!result) return this.containsPoint(aPoint);
return result;
}
VectorLine.prototype.exportAsSVG = function() {
var borderColor = this.borderColor != 'transparent'? ' stroke="' + this.borderColor + '"': ' stroke="none"';
return '<line x1="' + this.origin.x + '" y1="' + this.origin.y + '" x2="' + this.destination.x
+ '" y2="' + this.destination.y + '" stroke-width="' + this.borderWidth + '" ' + borderColor + '/>';
}
// VectorBrush
VectorBrush.prototype = new VectorShape();
VectorBrush.prototype.constructor = VectorBrush;
VectorBrush.uber = VectorShape.prototype;
function VectorBrush(borderWidth, borderColor, fillColor, origin, destination, threshold) {
VectorBrush.uber.init.call(this, borderWidth, borderColor, threshold);
this.init(fillColor, origin, destination);
}
VectorBrush.prototype.init = function(fillColor, origin, destination) {
this.origin = origin.slice();
}
VectorBrush.prototype.copy = function () {
var newBrush = new VectorBrush(
this.borderWidth,
this.borderColor,
this.fillColor,
this.origin,
this.destination
);
return VectorBrush.uber.copy.call(this, newBrush);
}
VectorBrush.prototype.toString = function () {
var coordinates = "";
coordinates += this.origin.length + this.origin.length-1;
for (i = 0; i < this.origin.length; ++i) {
coordinates += "[" + this.origin[i][0].toString() + "@" + this.origin[i][1].toString() + "]";
if (this.origin.length != (this.origin.length - 1)) coordinates += ", ";
}
return VectorBrush.uber.toString.call(this) + coordinates;
}
VectorBrush.prototype.containsPoint = function(aPoint) {
var line;
for (i = 0; i < this.origin.length - 1; ++i) {
line = new VectorLine(null, null, null, new Point(this.origin[i][0], this.origin[i][1]), new Point(this.origin[i+1][0], this.origin[i+1][1])); // [0] = x, [1] = y
if (line.containsPoint(aPoint)) return true;
}
return false;
}
VectorBrush.prototype.isFound = function(selectionBox) {
var bounds = this.getBounds();
if ((selectionBox.origin.x === selectionBox.destination.x
&& selectionBox.origin.y === selectionBox.destination.y
&& this.containsPoint(selectionBox.origin))
|| (selectionBox.containsPoint(new Point(bounds.left, bounds.top))
&& selectionBox.containsPoint(new Point(bounds.right, bounds.bottom)))) return true;
return false;
}
VectorBrush.prototype.getBounds = function() {
var rightBottom,
leftTop = this.origin.reduce(function(previous, current) {
var left, top;
left = (previous[0] > current[0] ? current[0] : previous[0]);
top = (previous[1] > current[1] ? current[1] : previous[1]);
return [left , top]}
);
rightBottom = this.origin.reduce(function(previous, current) {
var right, bottom;
right = (previous[0] < current[0] ? current[0] : previous[0]);
bottom = (previous[1] < current[1] ? current[1] : previous[1]);
return [right , bottom]}
);
return { left: leftTop[0]-(this.borderWidth/2),
top: leftTop[1]-(this.borderWidth/2),
right: rightBottom[0]+(this.borderWidth/2),
bottom: rightBottom[1]+(this.borderWidth/2) };
}
VectorBrush.prototype.isInBoundingBox = function(aPoint) {
var bounds = this.getBounds(),
result = this.isEndPointInBoundingBox(new Point(bounds.left, bounds.top),
new Point(bounds.left, bounds.bottom),
new Point(bounds.right, bounds.top),
new Point(bounds.right, bounds.bottom),
aPoint);
if(!result) return this.containsPoint(aPoint);
return result;
}
VectorBrush.prototype.exportAsSVG = function() {
var borderColor,
path = "M " + this.origin[0][0] + " " + this.origin[0][1];
this.origin.forEach(function(each) {
path = path + " L " + each[0] + " " + each[1]; //[0] = x & [1] = y
});
borderColor = this.borderColor != 'transparent'? ' stroke="' + this.borderColor + '"': ' stroke="none"';
return '<path d="' + path + '" stroke-width="' + this.borderWidth + '" ' + borderColor + ' fill="none" />';
}
// VectorEllipse
VectorEllipse.prototype = new VectorShape();
VectorEllipse.prototype.constructor = VectorEllipse;
VectorEllipse.uber = VectorShape.prototype;
function VectorEllipse(borderWidth, borderColor, fillColor, origin, destination, hRadius, vRadius, threshold) {
VectorEllipse.uber.init.call(this, borderWidth, borderColor, threshold);
this.init(fillColor, origin, destination, hRadius, vRadius);
}
VectorEllipse.prototype.init = function(fillColor, origin, destination, hRadius, vRadius) {
this.fillColor = fillColor;
this.origin = origin;
this.destination = destination;
this.hRadius = hRadius;
this.vRadius = vRadius;
}
VectorEllipse.prototype.copy = function () {
var newEllipse = new VectorEllipse(
this.borderWidth,
this.borderColor,
this.fillColor,
this.origin,
this.destination,
this.hRadius,
this.vRadius
);
return VectorEllipse.uber.copy.call(this, newEllipse);
}
VectorEllipse.prototype.toString = function () {
return VectorEllipse.uber.toString.call(this) + ' center: ' + this.origin.toString() + ' radius: (' + this.hRadius.toString() + ',' + this.vRadius.toString() + ')';
}
VectorEllipse.prototype.containsPoint = function(aPoint) {
return (Math.pow(aPoint.x-this.origin.x,2)/Math.pow(this.hRadius+this.threshold,2) + Math.pow(aPoint.y-this.origin.y,2)/Math.pow(this.vRadius+this.threshold,2)) < 1 ? true: false;
}
VectorEllipse.prototype.containsPointEdge = function(aPoint) {
smallest = new VectorEllipse(null, null, null, this.origin, null, this.hRadius-this.threshold, this.vRadius-this.threshold, 0);
biggest = new VectorEllipse(null, null, null, this.origin, null, this.hRadius+this.threshold, this.vRadius+this.threshold, 0);
if(!smallest.containsPoint(aPoint) && biggest.containsPoint(aPoint)) return true;
else false;
}
VectorEllipse.prototype.isFound = function(selectionBox) {
var bounds = this.getBounds();
if ((selectionBox.origin.x === selectionBox.destination.x
&& selectionBox.origin.y === selectionBox.destination.y
&& this.containsPoint(selectionBox.origin))
|| (selectionBox.containsPoint(new Point(bounds.left, bounds.top))
&& selectionBox.containsPoint(new Point(bounds.left, bounds.bottom))
&& selectionBox.containsPoint(new Point(bounds.right, bounds.top))
&& selectionBox.containsPoint(new Point(bounds.right, bounds.bottom)))) return true;
return false;
}
VectorEllipse.prototype.isInBoundingBox = function(aPoint) {
var bounds = this.getBounds(),
result = this.isEndPointInBoundingBox(new Point(bounds.left, bounds.top),
new Point(bounds.left, bounds.bottom),
new Point(bounds.right, bounds.top),
new Point(bounds.right, bounds.bottom),
aPoint);
if(!result) return this.containsPoint(aPoint);
return result;
}
VectorEllipse.prototype.getBounds = function() {
return {left: this.origin.x-this.hRadius-(this.borderWidth/2),
top: this.origin.y-this.vRadius-(this.borderWidth/2),
right: this.origin.x+this.hRadius+(this.borderWidth/2),
bottom: this.origin.y+this.vRadius+(this.borderWidth/2)
};
}
VectorEllipse.prototype.exportAsSVG = function() {
var borderColor = this.borderColor != 'transparent'? ' stroke="' + this.borderColor + '"': ' stroke="none"',
fillColor = this.fillColor != 'transparent'? ' fill="' + this.fillColor + '"': ' fill="none"';
return '<ellipse cx="' + this.origin.x + '" cy="' + this.origin.y + '" rx="' + this.hRadius
+ '" ry="' + this.vRadius + '" stroke-width="' + this.borderWidth + '" ' + borderColor
+ fillColor + '/>';
}
// VectorClosedBrushPath
VectorClosedBrushPath.prototype = new VectorShape();
VectorClosedBrushPath.prototype.constructor = VectorClosedBrushPath;
VectorClosedBrushPath.uber = VectorShape.prototype;
function VectorClosedBrushPath(borderWidth, borderColor, fillColor, origin, destination, threshold) {
VectorClosedBrushPath.uber.init.call(this, borderWidth, borderColor, threshold);
this.init(origin, fillColor);
}
VectorClosedBrushPath.prototype.init = function(origin, fillColor) {
this.origin = origin.slice();
this.fillColor = fillColor;
}
VectorClosedBrushPath.prototype.copy = function () {
var newBrush = new VectorClosedBrushPath(
this.borderWidth,
this.borderColor,
this.fillColor,
this.origin,
this.destination
);
return VectorClosedBrushPath.uber.copy.call(this, newBrush);
}
VectorClosedBrushPath.prototype.toString = function () {
var coordinates = "";
coordinates += this.origin.length + this.origin.length-1;
for (i = 0; i < this.origin.length; ++i) {
coordinates += "[" + this.origin[i][0].toString() + "@" + this.origin[i][1].toString() + "]";
if (this.origin.length != (this.origin.length - 1)) coordinates += ", ";
}
return VectorClosedBrushPath.uber.toString.call(this) + coordinates;
}
VectorClosedBrushPath.prototype.getBounds = function() {
var leftTop = this.origin.reduce(function(previous, current) {
var left, top;
left = (previous[0] > current[0] ? current[0] : previous[0]);
top = (previous[1] > current[1] ? current[1] : previous[1]);
return [left , top]}
);
var rightBottom = this.origin.reduce(function(previous, current) {
var right, bottom;
right = (previous[0] < current[0] ? current[0] : previous[0]);
bottom = (previous[1] < current[1] ? current[1] : previous[1]);
return [right , bottom]}
);
return { left: leftTop[0]-(this.borderWidth/2), top: leftTop[1]-(this.borderWidth/2), right: rightBottom[0]+(this.borderWidth/2), bottom: rightBottom[1]+(this.borderWidth/2) };
}
VectorClosedBrushPath.prototype.containsPoint = function(aPoint) {
var lineAorigin, lineAdest,
lineBorigin, lineBdest,
line, countX = 0;
/* Point in polygon evaluation (inside) */
function CCW(p1, p2, p3) {
return (p3.y - p1.y) * (p2.x - p1.x) > (p2.y - p1.y) * (p3.x - p1.x);
}
lineBorigin = aPoint;
lineBdest = new Point(0, aPoint.y);
for (i = 0; i < this.origin.length -1; ++i) {
lineAorigin = new Point(this.origin[i][0], this.origin[i][1]);
lineAdest = new Point(this.origin[i+1][0], this.origin[i+1][1]);
if((CCW(lineAorigin, lineBorigin, lineBdest) != CCW(lineAdest, lineBorigin, lineBdest)) && (CCW(lineAorigin, lineAdest, lineBorigin) != CCW(lineAorigin, lineAdest, lineBdest))) ++countX;
}
lineAorigin = new Point(this.origin[0][0], this.origin[0][1]);
lineAdest = new Point(this.origin[this.origin.length-1][0], this.origin[this.origin.length-1][1]);
if((CCW(lineAorigin, lineBorigin, lineBdest) != CCW(lineAdest, lineBorigin, lineBdest)) && (CCW(lineAorigin, lineAdest, lineBorigin) != CCW(lineAorigin, lineAdest, lineBdest)) ) ++countX;
if(countX % 2 !== 0) return true;
/* Detect borders */
for (i = 0; i < this.origin.length - 1; ++i) {
line = new VectorLine(null, null, null, new Point(this.origin[i][0], this.origin[i][1]), new Point(this.origin[i+1][0], this.origin[i+1][1]), 0);
if (line.containsPoint(aPoint)) return true;
}
/* closepath evaluation */
line = new VectorLine(null, null, null, new Point(this.origin[0][0], this.origin[0][1]), new Point(this.origin[this.origin.length-1][0], this.origin[this.origin.length-1][1]), 0);
if (line.containsPoint(aPoint)) return true;
return false;
}
VectorClosedBrushPath.prototype.containsPointEdge = function(aPoint) {
var brush = [],
smallest,
biggest,
i,
bounds,
tmp,
resizeRatioX,
resizeRatioY;
bounds = this.getBounds();
resizeRatioX = (bounds.right-bounds.left+(2*this.threshold))/(bounds.right-bounds.left);
resizeRatioY = (bounds.bottom-bounds.top+(2*this.threshold))/(bounds.bottom-bounds.top);
for (i = 0; i < this.origin.length; ++i) {
tmp = new Point(this.origin[i][0]-bounds.left, this.origin[i][1]-bounds.top);
brush.push([(tmp.x*resizeRatioX)+bounds.left, (tmp.y*resizeRatioY)+bounds.top]);
brush[i][0] -= this.threshold;
brush[i][1] -= this.threshold;
}
biggest = new VectorClosedBrushPath(null, null, null, brush, null, 0);
if(!biggest.containsPoint(aPoint)) return false;
brush = [];
resizeRatioX = (bounds.right-bounds.left-(2*this.threshold))/(bounds.right-bounds.left);
resizeRatioY = (bounds.bottom-bounds.top-(2*this.threshold))/(bounds.bottom-bounds.top);
for (i = 0; i < this.origin.length; ++i) {
tmp = new Point(this.origin[i][0]-bounds.left, this.origin[i][1]-bounds.top);
brush.push([(tmp.x*resizeRatioX)+bounds.left, (tmp.y*resizeRatioY)+bounds.top]);
brush[i][0] += this.threshold;
brush[i][1] += this.threshold;
}
smallest = new VectorClosedBrushPath(null, null, null, brush, null, 0);
if(smallest.containsPoint(aPoint)) return false;
return true;
}
VectorClosedBrushPath.prototype.isFound = function(selectionBox) {
var bounds = this.getBounds();
if ((selectionBox.origin.x === selectionBox.destination.x
&& selectionBox.origin.y === selectionBox.destination.y
&& this.containsPoint(selectionBox.origin))
|| (selectionBox.containsPoint(new Point(bounds.left, bounds.top))
&& selectionBox.containsPoint(new Point(bounds.right, bounds.bottom)))) return true;
return false;
}
VectorClosedBrushPath.prototype.isInBoundingBox = function(aPoint) {
var bounds = this.getBounds(),
result = this.isEndPointInBoundingBox(new Point(bounds.left, bounds.top),
new Point(bounds.left, bounds.bottom),
new Point(bounds.right, bounds.top),
new Point(bounds.right, bounds.bottom),
aPoint);
if(!result) return this.containsPoint(aPoint);
return result;
}
VectorClosedBrushPath.prototype.exportAsSVG = function() {
var fillColor = this.fillColor != 'transparent'? ' fill="' + this.fillColor + '"': ' fill="none"',
borderColor = this.borderColor != 'transparent'? ' stroke="' + this.borderColor + '"': ' stroke="none"',
path = "M " + this.origin[0][0] + " " + this.origin[0][1];
this.origin.forEach(function(each) {
path = path + " L " + each[0] + " " + each[1]; //[0] = x & [1] = y
});
return '<path d="' + path + ' Z" stroke-width="' + this.borderWidth + '" ' + borderColor + fillColor + ' />';
}
// VectorPolygon
VectorPolygon.prototype = new VectorShape();
VectorPolygon.prototype.constructor = VectorPolygon;
VectorPolygon.uber = VectorShape.prototype;
function VectorPolygon(borderWidth, borderColor, fillColor, origin, destination, threshold) {
VectorPolygon.uber.init.call(this, borderWidth, borderColor, threshold);
this.init(fillColor, origin, destination);
}
VectorPolygon.prototype.init = function(fillColor, origin, destination) {
this.origin = origin
this.fillColor = fillColor;
}
VectorPolygon.prototype.copy = function () {
var newPolygon = new VectorPolygon(
this.borderWidth,
this.borderColor,
this.fillColor,
this.origin,
this.destination
);
return VectorPolygon.uber.copy.call(this, newPolygon);
}
VectorPolygon.prototype.toString = function () {
var coordinates = "";
coordinates += this.origin.length + this.origin.length-1;
for (i = 0; i < this.origin.length; ++i) {
coordinates += "[" + this.origin[i][0] + "@" + this.origin[i][0] + "]";
if (this.origin.length != (this.origin.length - 1)) coordinates += ", ";
}
return VectorPolygon.uber.toString.call(this) + coordinates;
}
VectorPolygon.prototype.containsPoint = function(aPoint) {
var lineAorigin, lineAdest,
lineBorigin, lineBdest,
line, countX = 0;
/* Point in polygon evaluation (inside) */
function CCW(p1, p2, p3) {
return (p3.y - p1.y) * (p2.x - p1.x) > (p2.y - p1.y) * (p3.x - p1.x);
}
lineBorigin = aPoint;
lineBdest = new Point(0, aPoint.y);
for (i = 0; i < this.origin.length -1; ++i) {
lineAorigin = new Point(this.origin[i][0], this.origin[i][1]);
lineAdest = new Point(this.origin[i+1][0], this.origin[i+1][1]);
if((CCW(lineAorigin, lineBorigin, lineBdest) != CCW(lineAdest, lineBorigin, lineBdest)) && (CCW(lineAorigin, lineAdest, lineBorigin) != CCW(lineAorigin, lineAdest, lineBdest))) ++countX;
}
lineAorigin = new Point(this.origin[0][0], this.origin[0][1]);
lineAdest = new Point(this.origin[this.origin.length-1][0], this.origin[this.origin.length-1][1]);
if((CCW(lineAorigin, lineBorigin, lineBdest) != CCW(lineAdest, lineBorigin, lineBdest)) && (CCW(lineAorigin, lineAdest, lineBorigin) != CCW(lineAorigin, lineAdest, lineBdest))) ++countX;
if(countX % 2 !== 0) return true;
/* Detect borders */
for (i = 0; i < this.origin.length - 1; ++i) {
line = new VectorLine(null, null, null, new Point(this.origin[i][0], this.origin[i][1]), new Point(this.origin[i+1][0], this.origin[i+1][1]), 0);
if (line.containsPoint(aPoint)) return true;
}
/* closepath evaluation */
line = new VectorLine(null, null, null, new Point(this.origin[0][0], this.origin[0][1]), new Point(this.origin[this.origin.length-1][0], this.origin[this.origin.length-1][1]), 0);
if (line.containsPoint(aPoint)) return true;
return false;
}
VectorPolygon.prototype.containsPointEdge = function(aPoint) {
var brush = [],
smallest,
biggest,
i,
bounds,
tmp,
resizeRatioX,
resizeRatioY;
bounds = this.getBounds();
resizeRatioX = (bounds.right-bounds.left+(2*this.threshold))/(bounds.right-bounds.left);
resizeRatioY = (bounds.bottom-bounds.top+(2*this.threshold))/(bounds.bottom-bounds.top);
for (i = 0; i < this.origin.length; ++i) {
tmp = new Point(this.origin[i][0]-bounds.left, this.origin[i][1]-bounds.top);
brush.push([(tmp.x*resizeRatioX)+bounds.left, (tmp.y*resizeRatioY)+bounds.top]);
brush[i][0] -= this.threshold;
brush[i][1] -= this.threshold;
}
biggest = new VectorClosedBrushPath(null, null, null, brush, null, 0);
if(!biggest.containsPoint(aPoint)) return false;
brush = [];
resizeRatioX = (bounds.right-bounds.left-(2*this.threshold))/(bounds.right-bounds.left);
resizeRatioY = (bounds.bottom-bounds.top-(2*this.threshold))/(bounds.bottom-bounds.top);
for (i = 0; i < this.origin.length; ++i) {
tmp = new Point(this.origin[i][0]-bounds.left, this.origin[i][1]-bounds.top);
brush.push([(tmp.x*resizeRatioX)+bounds.left, (tmp.y*resizeRatioY)+bounds.top]);
brush[i][0] += this.threshold;
brush[i][1] += this.threshold;
}
smallest = new VectorClosedBrushPath(null, null, null, brush, null, 0);
if(smallest.containsPoint(aPoint)) return false;
return true;
}
VectorPolygon.prototype.isFound = function(selectionBox) {
var bounds = this.getBounds();
if ((selectionBox.origin.x === selectionBox.destination.x
&& selectionBox.origin.y === selectionBox.destination.y
&& this.containsPoint(selectionBox.origin))
|| (selectionBox.containsPoint(new Point(bounds.left, bounds.top))
&& selectionBox.containsPoint(new Point(bounds.right, bounds.bottom)))) return true;
return false;
}
VectorPolygon.prototype.getBounds = function() {
var leftTop = this.origin.reduce(function(previous, current) {
var left, top;
left = (previous[0] > current[0] ? current[0] : previous[0]);
top = (previous[1] > current[1] ? current[1] : previous[1]);
return [left , top]}
),
rightBottom = this.origin.reduce(function(previous, current) {
var right, bottom;
right = (previous[0] < current[0] ? current[0] : previous[0]);
bottom = (previous[1] < current[1] ? current[1] : previous[1]);
return [right , bottom]}
);
return { left: leftTop[0]-(this.borderWidth/2), top: leftTop[1]-(this.borderWidth/2), right: rightBottom[0]+(this.borderWidth/2), bottom: rightBottom[1]+(this.borderWidth/2)};
}
VectorPolygon.prototype.isInBoundingBox = function(aPoint) {
var bounds = this.getBounds(),
result = this.isEndPointInBoundingBox(new Point(bounds.left, bounds.top),
new Point(bounds.left, bounds.bottom),
new Point(bounds.right, bounds.top),
new Point(bounds.right, bounds.bottom),
aPoint);
if(!result) return this.containsPoint(aPoint);
return result;
}
VectorPolygon.prototype.exportAsSVG = function() {
var fillColor = this.fillColor != 'transparent'? ' fill="' + this.fillColor + '"': ' fill="none"',
borderColor = this.borderColor != 'transparent'? ' stroke="' + this.borderColor + '"': ' stroke="none"',
path = "M " + this.origin[0][0] + " " + this.origin[0][1];
this.origin.forEach(function(each) {
path = path + " L " + each[0] + " " + each[1]; //[0] = x & [1] = y
});
return '<path d="' + path + ' Z" stroke-width="' + this.borderWidth + '"' + borderColor + fillColor + ' />';
}
// Decorator Pattern
// =================
// Modificar comportament de funcions sense sobreescriure-les
PaintEditorMorph.prototype.originalOpenIn = PaintEditorMorph.prototype.openIn;
PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback, anIDE) {
this.originalOpenIn(world, oldim, oldrc, callback);
this.ide = anIDE;
}
PaintEditorMorph.prototype.originalBuildEdits = PaintEditorMorph.prototype.buildEdits;
PaintEditorMorph.prototype.buildEdits = function () {
var myself = this;
this.originalBuildEdits();
this.edits.add(this.pushButton(
'Vector',
function () {
this.object = new VectorCostume();
this.object.edit(
this.world(),
myself.ide,
true,
myself.oncancel,
function() { myself.ide.currentSprite.changed() }
);
}
));
this.edits.fixLayout();
};
/////////// VectorPaintEditorMorph //////////////////////////
VectorPaintEditorMorph.prototype = new PaintEditorMorph();
VectorPaintEditorMorph.prototype.constructor = VectorPaintEditorMorph;
VectorPaintEditorMorph.uber = PaintEditorMorph.prototype;
function VectorPaintEditorMorph() {
this.init();
}
VectorPaintEditorMorph.prototype.init = function () {
var myself = this;
// additional properties:
this.paper = null; // paint canvas
this.vectorObjects = []; // collection of VectorShapes
this.vectorObjectsSelected = []; // collection of VectorShapes
this.vectorObjectsToDuplicate = []; // collection of VectorShapes to duplicate
this.currentObject = null; // object being currently painted / edited
// initialize inherited properties:
VectorPaintEditorMorph.uber.init.call(this);
// override inherited properties:
this.labelString = "Vector Paint Editor";
this.createLabel();
this.fixLayout();
};
VectorPaintEditorMorph.prototype.buildEdits = function () {
var crosshairs,
myself = this;
this.edits.add(this.pushButton(
"clear", function () { myself.paper.clearCanvas()}));
this.edits.add(this.pushButton(
'Bitmap',
function () {
var can = newCanvas(StageMorph.prototype.dimensions);
this.object = new Costume();
myself.vectorObjects.forEach(function(each) {
can.getContext("2d").drawImage(each.image, 0, 0);
});
this.object.rotationCenter = this.paper.rotationCenter.copy();
this.object.contents = can;
this.object.edit(
this.world(),
myself.ide,
false,
myself.oncancel
);
this.destroy();
}
));
crosshairs = new ToggleButtonMorph(
null,
this,
function () { // action
myself.paper.currentTool = 'crosshairs';
myself.paper.toolChanged('crosshairs');
myself.refreshToolButtons();
},
new SymbolMorph('crosshairs', 14),
function () {return myself.paper.currentTool === 'crosshairs'; }
);
crosshairs.drawNew();
crosshairs.fixLayout();
crosshairs.refresh();
this.edits.add(crosshairs);
this.edits.fixLayout();
}
VectorPaintEditorMorph.prototype.buildLayersBox = function () {
var mctx = this.paper.mask.getContext("2d");
this.scaleBox.add(this.pushButton(
"Top",
this.jumpTop = function() {
var index;
for(z = this.vectorObjectsSelected.length-1; z >= 0; --z) {
index = this.vectorObjects.indexOf(this.vectorObjectsSelected[z]);
mctx.save();
this.vectorObjects[index].drawBoundingBox(mctx);
this.paper.changed();
mctx.restore();
this.vectorObjects.splice(index,1);
this.vectorObjects.push(this.vectorObjectsSelected[z]);
}
this.paper.drawNew();
}
));
this.scaleBox.add(this.pushButton(
"Bottom", this.jumpBottom
= function() {
var index,
z;
for(z = 0; z < this.vectorObjectsSelected.length; ++z) {
index = this.vectorObjects.indexOf(this.vectorObjectsSelected[z]);
mctx.save();
this.vectorObjects[index].drawBoundingBox(mctx);
this.paper.changed();
mctx.restore();
this.vectorObjects.splice(index,1);
this.vectorObjects.unshift(this.vectorObjectsSelected[z]);
}
this.paper.drawNew();
}
));
this.scaleBox.add(this.pushButton(
"Up", this.jumpUp = function() {
var index,
tmp,
z,
lastIndexChanged = this.vectorObjects.length;
for(z = 0; z < this.vectorObjectsSelected.length; ++z) {
index = this.vectorObjects.indexOf(this.vectorObjectsSelected[z]);
mctx.save();
this.vectorObjects[index].drawBoundingBox(mctx);
this.paper.changed();
mctx.restore();
if(lastIndexChanged-index > 1) {
tmp = this.vectorObjects[index];
this.vectorObjects[index] = this.vectorObjects[index+1];
this.vectorObjects[index+1] = tmp;
lastIndexChanged = index;
}
else lastIndexChanged = index;
}
this.paper.drawNew();
}
));
this.scaleBox.add(this.pushButton(
"Down", this.jumpDown = function() {
var index,
tmp,
z,
lastIndexChanged = -1;
for(z = this.vectorObjectsSelected.length-1; z >= 0; --z) {
index = this.vectorObjects.indexOf(this.vectorObjectsSelected[z]);
mctx.save();
this.vectorObjects[index].drawBoundingBox(mctx);
this.paper.changed();
mctx.restore();
if(index-lastIndexChanged > 1) {
tmp = this.vectorObjects[index];
this.vectorObjects[index] = this.vectorObjects[index-1];
this.vectorObjects[index-1] = tmp;
lastIndexChanged = index;
}
else lastIndexChanged = index;
}
this.paper.drawNew();
}
));
this.scaleBox.fixLayout();
}
VectorPaintEditorMorph.prototype.buildScaleBox = VectorPaintEditorMorph.prototype.buildLayersBox;
VectorPaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback, anIDE, oldvecObj) {
/* copy oldVectorObjects */
var myself = this,
vecObj = [];
VectorPaintEditorMorph.uber.openIn.call(this, world, oldim, oldrc, callback);
oldvecObj.forEach(function(each) {
vecObj.push(each.copy());
});
this.vectorObjects = vecObj;
this.ide = anIDE;
this.processKeyUp = function () {
this.shift = false;
this.ctrl = false;
this.propertiesControls.constrain.refresh();
};
this.processKeyDown = function () {
/* Shift key */
if(!this.shift) this.shift = this.world().currentKey === 16;
/* Ctrl */
if(!this.ctrl) this.ctrl = this.world().currentKey === 17;
switch (this.world().currentKey) {
/* Del key */
case 46:
this.delete = function() {
for(z = 0; z < this.vectorObjectsSelected.length; ++z) {
var index = this.vectorObjects.indexOf(this.vectorObjectsSelected[z]);
this.vectorObjects.splice(index,1);
}
this.drawNew(true);
this.changed();
}
this.delete();
break;
/* Page Up key */
case 33:
this.jumpUp();
break;
/* Page Down key */
case 34:
this.jumpDown();
break;
/* Home key */
case 36:
this.jumpTop();
break;
/* End key */
case 35:
this.jumpBottom();
case 86:
/* Ctrl + V */
var pos, hand = world.hand;
pos = hand.position();
pos = pos.subtract(this.paper.bounds.origin);
function insidePaper(pos) {
return (pos.x >= 0 && pos.y >= 0 && pos.x <= myself.paper.bounds.width() && pos.y <= myself.paper.bounds.height());
}
if(this.ctrl && this.vectorObjectsToDuplicate.length && insidePaper(pos)) {
myself.paper.currentTool = 'duplicate';
myself.paper.toolChanged('duplicate');
myself.refreshToolButtons();
this.paper.duplicateShape(pos);
this.paper.mouseClickLeft();
};
break;
case 67:
/* Ctrl + C */
var vecObjDup = [];
if(this.ctrl && this.vectorObjectsSelected.length) {
this.vectorObjectsSelected.forEach(function(each) {
vecObjDup.push(each.copy());
});
this.vectorObjectsToDuplicate = vecObjDup;
}
break;
default:
nop();
}
this.propertiesControls.constrain.refresh();
};
this.drawNew();
}
VectorPaintEditorMorph.prototype.buildContents = function() {
VectorPaintEditorMorph.uber.buildContents.call(this);
var myself = this;
this.paper.destroy();
this.paper = new VectorPaintCanvasMorph(myself.shift);
this.paper.setExtent(StageMorph.prototype.dimensions);
this.body.add(this.paper);
this.refreshToolButtons();
this.fixLayout();
this.drawNew();
}
VectorPaintEditorMorph.prototype.buildToolbox = function () {
//VectorPaintEditorMorph.uber.buildToolbox.call(this);
//this.tools.destroy();
this.tools = null;
var tools = {
selection:
"Selection tool",
brush:
"Paintbrush tool\n(free draw)",
line:
"Line tool\n(shift: vertical/horizontal)",
rectangle:
"Rectangle\n(shift: square)",
circle:
"Ellipse\n(shift: circle)",
duplicate:
"Duplicate a shapespe",
paintbucket:
"Fill a border (shift: fill a shape)",
pipette:
"Pipette tool\n(pick a color anywhere)",
polygon:
"Polygon \n(pick a color anywhere)",
closedBrushPath:
"Paintbrush closed \n(free draw)"
},
myself = this,
left = this.toolbox.left(),
top = this.toolbox.top(),
padding = 2,
inset = 5,
x = 0,
y = 0;
Object.keys(tools).forEach(function (tool) {
var btn = myself.toolButton(tool, tools[tool]);
btn.setPosition(new Point(
left + x,
top + y
));
x += btn.width() + padding;
if (tool === "circle") { /* the tool mark the newline */
x = 0;
y += btn.height() + padding;
myself.paper.drawcrosshair();
}
myself.toolbox[tool] = btn;
myself.toolbox.add(btn);
});
this.toolbox.bounds = this.toolbox.fullBounds().expandBy(inset * 2);
this.toolbox.drawNew();
};
VectorPaintEditorMorph.prototype.populatePropertiesMenu = function () {
var c = this.controls,
myself = this,
pc = this.propertiesControls,
alpen = new AlignmentMorph("row", this.padding);
alignColor = new AlignmentMorph("row", this.padding);
pc.primaryColorViewer = new Morph();
pc.primaryColorViewer.setExtent(new Point(85, 15)); // 40 = height primary & brush size
pc.primaryColorViewer.color = new Color(0, 0, 0);
pc.secondaryColorViewer = new Morph();
pc.secondaryColorViewer.setExtent(new Point(85, 15)); // 20 = height secondaryColor box
pc.secondaryColorViewer.color = new Color(0, 0, 0);
pc.colorpicker = new PaintColorPickerMorph(
new Point(180, 100),
function (color, whichColor) {
whichColor = whichColor || myself.paper.isShiftPressed()? 'secondaryColor' : 'primaryColor';
var ni = newCanvas(pc[whichColor + 'Viewer'].extent()), // equals secondaryColorViewer or primaryColorViewer
ctx = ni.getContext("2d"),
i,
j;
myself.paper.settings[whichColor.toLowerCase()] = color;
if (color === "transparent") {
for (i = 0; i < 180; i += 5) {
for (j = 0; j < 15; j += 5) {
ctx.fillStyle =
((j + i) / 5) % 2 === 0 ?
"rgba(0, 0, 0, 0.2)" :
"rgba(0, 0, 0, 0.5)";
ctx.fillRect(i, j, 5, 5);
}
}
} else {
ctx.fillStyle = color.toString();
ctx.fillRect(0, 0, 180, 15);
};
//Brush size
ctx.strokeStyle = "black";
ctx.lineWidth = Math.min(myself.paper.settings.linewidth, 20);
ctx.beginPath();
ctx.lineCap = "round";
ctx.moveTo(20, 30);
ctx.lineTo(160, 30);
ctx.stroke();
pc[whichColor + 'Viewer'].image = ni;
pc[whichColor + 'Viewer'].changed();
}
);
pc.colorpicker.action(new Color(0, 0, 0));
pc.colorpicker.action("transparent", 'secondaryColor'); // inizialize secondarycolor pc
pc.penSizeSlider = new SliderMorph(0, 20, 5, 5);
pc.penSizeSlider.orientation = "horizontal";
pc.penSizeSlider.setHeight(15);
pc.penSizeSlider.setWidth(150);
pc.penSizeSlider.action = function (num) {
if (pc.penSizeField) {
pc.penSizeField.setContents(num);
}
myself.paper.settings.linewidth = num;
pc.colorpicker.action(myself.paper.settings.primarycolor);
};
pc.penSizeField = new InputFieldMorph("3", true, null, false);
pc.penSizeField.contents().minWidth = 20;
pc.penSizeField.setWidth(25);
pc.penSizeField.accept = function () {
var val = parseFloat(pc.penSizeField.getValue());
pc.penSizeSlider.value = val;
pc.penSizeSlider.drawNew();
pc.penSizeSlider.updateValue();
this.setContents(val);
myself.paper.settings.linewidth = val;
this.world().keyboardReceiver = myself;
pc.colorpicker.action(myself.paper.settings.primarycolor);
};
alpen.add(pc.penSizeSlider);
alpen.add(pc.penSizeField);
alpen.color = myself.color;
alpen.fixLayout();
pc.penSizeField.drawNew();
pc.constrain = new ToggleMorph(
"checkbox",
this,
function () {myself.shift = !myself.shift; },
"Constrain proportions of shapes?\n(you can also hold shift)",
function () {return myself.shift; }
);
alignColor.add(pc.primaryColorViewer);
alignColor.add(pc.secondaryColorViewer);
alignColor.fixLayout();
c.add(pc.colorpicker);
c.add(new TextMorph(localize("Border color Fill color")));
c.add(alignColor);
c.add(new TextMorph(localize("Brush size")));
c.add(alpen);
c.add(pc.constrain);
};
VectorPaintEditorMorph.prototype.getSVG = function () {
var srcSVG = "";
this.vectorObjects.forEach(function(each) {
srcSVG = srcSVG + each.exportAsSVG();
});
return srcSVG;
};
VectorPaintEditorMorph.prototype.getBoundsVectorObjects = function (vecObj) {
var bounds = [];
vecObj.forEach(function(each) {
bounds.push(each.getBounds());
});
bounds = bounds.reduce(function(previous, current) {
return {left: (previous.left > current.left ? current.left : previous.left),
top: (previous.top > current.top ? current.top : previous.top),
right: (previous.right > current.right ? previous.right : current.right),
bottom: (previous.bottom > current.bottom ? previous.bottom : current.bottom)}
});
return bounds;
};
VectorPaintEditorMorph.prototype.ok = function () {
var bounds,
myself = this,
img = new Image();
bounds = this.getBoundsVectorObjects(this.vectorObjects);
img.src = 'data:image/svg+xml, <svg xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMinYMin meet" viewBox="'
+ bounds.left + ' ' + bounds.top + ' ' + (bounds.right - bounds.left) + ' ' + (bounds.bottom - bounds.top)
+ '" width="' + (bounds.right - bounds.left) + '" height="' + (bounds.bottom - bounds.top) + '" > ' + this.getSVG() + '</svg>';
img.onload = function(){
myself.callback(
img,
myself.paper.rotationCenter.subtract(new Point(bounds.left, bounds.top)),
myself.vectorObjects
)};
this.destroy();
};
// VectorPaintCanvasMorph //////////////////////////
VectorPaintCanvasMorph.prototype = new PaintCanvasMorph();
VectorPaintCanvasMorph.prototype.constructor = VectorPaintCanvasMorph;
VectorPaintCanvasMorph.uber = PaintCanvasMorph.prototype;
function VectorPaintCanvasMorph(shift) {
this.init(shift);
}
VectorPaintCanvasMorph.prototype.init = function (shift) {
VectorPaintCanvasMorph.uber.init.call(this, shift);
this.vectorbrushBuffer = [];
this.currentTool = "selection";
this.settings = {
"primarycolor": new Color(0, 0, 0, 255), // stroke color
"secondarycolor": "transparent", // fill color
"linewidth": 3 // stroke width
};
this.polygonBuffer = [];
};
VectorPaintCanvasMorph.prototype.clearCanvas = function () {
var editor = this.parentThatIsA(VectorPaintEditorMorph);
editor.vectorObjects = [];
editor.vectorObjectsSelected = [];
this.mask.getContext("2d").clearRect(0, 0, this.bounds.width(), this.bounds.height());
this.drawNew(true);
this.changed();
};
VectorPaintCanvasMorph.prototype.drawNew = function(isPrintVectObject) {
var editor = this.parentThatIsA(VectorPaintEditorMorph),
myself = this,
can = newCanvas(this.extent());
if(typeof isPrintVectObject === 'undefined') var isPrintVectObject = true;
this.merge(this.background, can);
this.merge(this.paper, can);
if(isPrintVectObject) {
editor.vectorObjects.forEach(function(each) {
myself.merge(each.image, can)
});
}
this.merge(this.mask, can);
this.image = can;
this.drawFrame();
};
VectorPaintCanvasMorph.prototype.floodfill = function (aPoint) {
var shape,
editor = this.parentThatIsA(VectorPaintEditorMorph),
mctx = this.mask.getContext("2d"),
index = [-1, null], // index shape to floodfill
j; // iterator number
mctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()); // clear any temporary shape in mask
for (j = editor.vectorObjects.length-1; j >= 0; --j) {
shape = editor.vectorObjects[j];
if(typeof shape.fillColor !== 'undefined' && shape.containsPointEdge(aPoint)) {
index[0] = j;
index[1] = 'edge';
break;
}
if(shape.containsPoint(aPoint)) {
index[0] = j;
if(typeof shape.fillColor !== 'undefined') {
index[1] = 'inline';
}
else {
index[1] = 'edge';
}
break;
}
}
if(index[0] !== -1) {
for(j = 0; j < editor.vectorObjects.length; ++j) {
shape = editor.vectorObjects[j];
if(j === index[0]) {
if(shape.constructor.name == "VectorBrush" && this.isShiftPressed()) {
/* If shift is pressed and it's a brush, it will convert from brush to closedBrush,*/
editor.vectorObjects[j] = new VectorClosedBrushPath(shape.borderWidth, shape.borderColor, this.settings.secondarycolor, shape.origin, null);
shape = editor.vectorObjects[j];
}
else if(index[1] === 'edge') {
shape.borderColor = this.settings.primarycolor;
}
else {
shape.fillColor = this.settings.secondarycolor;
}
}
this.paintShape(shape, j);
}
this.drawNew(false);
this.changed();
mctx.restore();
}
};
VectorPaintCanvasMorph.prototype.duplicateShape = function (aPoint) {
var duplicatedShape,
bounds,
editor = this.parentThatIsA(VectorPaintEditorMorph),
movementX,
movementY,
moveBuffer = [],
tmp,
mctx = this.mask.getContext("2d"),
index = [];
mctx.clearRect(0, 0, this.bounds.width(), this.bounds.height());
if(!editor.vectorObjectsToDuplicate.length) {
for (j = editor.vectorObjects.length-1; j >= 0; --j) {
if(editor.vectorObjects[j].containsPoint(aPoint)) {
editor.vectorObjectsToDuplicate.push(editor.vectorObjects[j].copy());
break;
}
}
}
if(editor.vectorObjectsToDuplicate.length) {
bounds = editor.getBoundsVectorObjects(editor.vectorObjectsToDuplicate);
movementX = aPoint.x - (bounds.left+(bounds.right-bounds.left)/2);
movementY = aPoint.y - (bounds.top+(bounds.bottom-bounds.top)/2);
}
for (j = editor.vectorObjectsToDuplicate.length-1; j >= 0; --j) {
duplicatedShape = editor.vectorObjectsToDuplicate[j];
if(typeof duplicatedShape.destination !== 'undefined') {
duplicatedShape.origin = new Point (duplicatedShape.origin.x+movementX, duplicatedShape.origin.y+movementY);
duplicatedShape.destination = new Point (duplicatedShape.destination.x+movementX, duplicatedShape.destination.y+movementY);
}
else {
moveBuffer = [];
for(jj = 0; jj < duplicatedShape.origin.length; ++jj) {
tmp = new Point(duplicatedShape.origin[jj][0], duplicatedShape.origin[jj][1]);
moveBuffer.push([tmp.x+movementX, tmp.y+movementY]);
}
duplicatedShape.origin = moveBuffer.slice();
}
this.paintShape(duplicatedShape, null);
}
this.drawNew(true);
this.changed();
mctx.restore();
}
VectorPaintCanvasMorph.prototype.paintShape = function (shape, index) {
var p, q, w, h, hRadius, vRadius, pathCircle,
tmask = newCanvas(this.extent()),
mctx = this.mask.getContext("2d"),
tmctx = tmask.getContext("2d"),
tool = shape.constructor.name,
x = shape.origin.x,
y = shape.origin.y,
editor = this.parentThatIsA(VectorPaintEditorMorph),
width = this.paper.width;
if(typeof shape.destination !== 'undefined' && shape.destination !== null) {
p = shape.destination.x,
q = shape.destination.y,
w = (p - x) / 2,
h = (q - y) / 2;
}
if (editor.currentObject === null ||
typeof editor.currentObject === 'undefined') editor.currentObject = [];
tmctx.clearRect(0, 0, this.bounds.width(), this.bounds.height());
tmctx.save();
tmctx.lineWidth = shape.borderWidth;
if(typeof shape.fillColor !== 'undefined') tmctx.fillStyle = shape.fillColor.toString();
tmctx.strokeStyle = shape.borderColor.toString();
switch (tool) {
case "VectorRectangle":
if(shape.fillColor !== "transparent") tmctx.fillRect(x, y, w * 2, h * 2);
if(shape.borderColor !== "transparent") tmctx.strokeRect(x, y, w * 2, h * 2);
break;
case "VectorLine":
tmctx.beginPath();
tmctx.moveTo(x, y);
tmctx.lineTo(p, q);
tmctx.stroke();
break;
case "VectorEllipse":
tmctx.beginPath();
if(shape.vRadius === shape.hRadius) {
tmctx.arc(
x,
y,
new Point(x, y).distanceTo(new Point(p, q)),
0,
Math.PI * 2,
false
);
}
else {
vRadius = 0;
for (i = 0; i < width; ++i) {
pathCircle = 2 - Math.pow((i - x) / (2 * w),2);
tmctx.lineTo(
i,
(2 * h) * Math.sqrt(pathCircle) + y
);
if (i == x) {
vRadius = Math.abs((2 * h) * Math.sqrt(pathCircle));
}
if (Math.sqrt(pathCircle) > 0) {
hRadius = Math.abs(i-x);
}
}
for (i = width; i > 0; i -= 1) {
tmctx.lineTo(
i,
-1 * (2 * h) * Math.sqrt(2 - Math.pow(
(i - x) / (2 * w),
2
)) + y
);
}
}
tmctx.closePath();
tmctx.stroke();
tmctx.fill();
break;
case "VectorBrush": case "VectorClosedBrushPath": case "VectorPolygon":
tmctx.lineCap = "round";
tmctx.lineJoin = "round";
tmctx.beginPath();
tmctx.moveTo(shape.origin[0][0], shape.origin[0][1]); // first Point
for (i = 0; i < shape.origin.length; ++i) {
tmctx.lineTo(shape.origin[i][0], shape.origin[i][1]);
}
if(tool === 'VectorClosedBrushPath' || tool === 'VectorPolygon') {
tmctx.closePath();
if(shape.fillColor !== "transparent") tmctx.fill();
}
tmctx.stroke();
break;
default:
nop();
}
editor.currentObject.push([index, shape]);
/* Save only one image */
editor.currentObject[editor.currentObject.length-1][1].image.width = tmask.width;
editor.currentObject[editor.currentObject.length-1][1].image.height = tmask.height;
editor.currentObject[editor.currentObject.length-1][1].image.getContext('2d').drawImage(tmask, 0, 0);
mctx.drawImage(tmask, 0, 0);
}
VectorPaintCanvasMorph.prototype.mouseMove = function (pos) {
if (this.currentTool === "paintbucket" || this.currentTool === "duplicate") {
return;
}
var relpos = pos.subtract(this.bounds.origin), // relative position
mctx = this.mask.getContext("2d"), // current tool temporary context
tmask = newCanvas(this.extent()),
tmctx = tmask.getContext("2d"), // temporal draing context
pctx = this.paper.getContext("2d"), // drawing context
x = this.dragRect.origin.x, // original drag X
y = this.dragRect.origin.y, // original drag y
p = relpos.x, // current drag x
q = relpos.y, // current drag y
w = (p - x) / 2, // half the rect width
h = (q - y) / 2, // half the rect height
i, // iterator number
tool, // current tool
resizeRatioX, // escale ratio axis X
resizeRatioY, // escale ratio axis Y
axisX, axisY,
movementX,
movementY,
circleIsMoved = false,
tmp, // temporal variable
boundsVecSelected, // bounds of selection
shapeSelected, // current shape selected
action, // if there are vectorObjects selected then action have to do.
moveBuffer = [], // moveVector in polygon, brush and closedbrush
hRadius, vRadius, pathCircle, // horizontal and vertical circle
currentObjectIterator = -1,
width = this.paper.width,
editor = this.parentThatIsA(VectorPaintEditorMorph),
myself = this;
mctx.save();
function newW() {
return Math.max(Math.abs(w), Math.abs(w)) * (w / Math.abs(w));
}
function newH() {
return Math.max(Math.abs(w), Math.abs(h)) * (h / Math.abs(h));
}
this.brushBuffer.push([p, q]);
mctx.lineWidth = this.settings.linewidth;
mctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()); // mask, clear previous temporary drawing
this.dragRect.corner = relpos.subtract(this.dragRect.origin); // reset corner
mctx.fillStyle = this.settings.secondarycolor.toString();
mctx.strokeStyle = this.settings.primarycolor.toString();
action = false;
for(ii = 0; ii < editor.vectorObjectsSelected.length && action === false; ++ii) {
action = editor.vectorObjectsSelected[ii].isInBoundingBox(new Point(x,y));
}
if (action === false) editor.vectorObjectsSelected = [];
if(this.currentTool === 'selection' && editor.vectorObjectsSelected.length) {
if(action !== false) {
/* Resize functionality takes as reference boundaryBox */
boundsVecSelected = editor.getBoundsVectorObjects(editor.vectorObjectsSelected);
movementX = relpos.x-this.dragRect.origin.x; // distance moved
movementY = relpos.y-this.dragRect.origin.y; // distance moved
if(action === 'leftTop' || action === 'leftBottom') movementX *= -1;
if(action === 'leftTop' || action === 'rightTop') movementY *= -1;
resizeRatioX = (boundsVecSelected.right-boundsVecSelected.left+movementX)/((boundsVecSelected.right-boundsVecSelected.left) === 0 ? 1: boundsVecSelected.right-boundsVecSelected.left);
resizeRatioY = (boundsVecSelected.bottom-boundsVecSelected.top+movementY)/((boundsVecSelected.bottom-boundsVecSelected.top) === 0 ? 1: boundsVecSelected.bottom-boundsVecSelected.top);
axisX = (action === 'rightBottom' || action === 'rightTop')? boundsVecSelected.left: boundsVecSelected.right;
axisY = (action === 'rightBottom' || action === 'leftBottom')? boundsVecSelected.top: boundsVecSelected.bottom;
for(ii = 0; ii < editor.vectorObjects.length; ++ii) {
if(editor.vectorObjectsSelected.indexOf(editor.vectorObjects[ii]) === -1) {
mctx.drawImage(editor.vectorObjects[ii].image, 0, 0);
}
else {
++currentObjectIterator;
shapeSelected = editor.vectorObjects[ii];
tool = shapeSelected.constructor.name;
if(typeof shapeSelected.fillColor !== 'undefined') tmctx.fillStyle = shapeSelected.fillColor.toString();
tmctx.strokeStyle = shapeSelected.borderColor.toString();
tmctx.lineWidth = shapeSelected.borderWidth;
if (action === true){
/* Move figure */
if(tool === 'VectorBrush' || tool === 'VectorClosedBrushPath' || tool === 'VectorPolygon') {
moveBuffer = [], tmp;
/* Clone array */
for(z = 0; z < shapeSelected.origin.length; ++z) {
tmp = new Point(shapeSelected.origin[z][0], shapeSelected.origin[z][1]);
moveBuffer.push([tmp.x+movementX, tmp.y+movementY]);
}
}
else {
/* Line, Rectangle, Ellipse, */
if(tool === 'VectorEllipse' && shapeSelected.hRadius === shapeSelected.vRadius) circleIsMoved = true;
x = shapeSelected.origin.x + movementX;
y = shapeSelected.origin.y + movementY;
p = shapeSelected.destination.x + movementX;
q = shapeSelected.destination.y + movementY;
}
}
else {
if(tool === 'VectorEllipse' || tool === 'VectorRectangle' || tool === 'VectorLine') {
tmp = new Point(shapeSelected.origin.x-axisX, shapeSelected.origin.y-axisY);
x = (tmp.x*resizeRatioX)+axisX;
y = (tmp.y*resizeRatioY)+axisY;
tmp = new Point(shapeSelected.destination.x-axisX, shapeSelected.destination.y-axisY);
p = (tmp.x*resizeRatioX)+axisX;
q = (tmp.y*resizeRatioY)+axisY;
} else if(tool === 'VectorBrush' || tool === 'VectorClosedBrushPath' || tool === 'VectorPolygon') {
moveBuffer = [];
for(z = 0; z < shapeSelected.origin.length; ++z) {
tmp = new Point(shapeSelected.origin[z][0]-axisX, shapeSelected.origin[z][1]-axisY);
moveBuffer.push([(tmp.x*resizeRatioX)+axisX, (tmp.y*resizeRatioY)+axisY]);
}
}
}
w = (p - x) / 2, // recalculate half the rect width
h = (q - y) / 2; // recalculate half the rect height
/* drawing actioned */
if (editor.currentObject === null) editor.currentObject = [];
switch (tool) {
case "VectorRectangle":
if(shapeSelected.fillColor !== "transparent") tmctx.fillRect(x, y, w * 2, h * 2);
if(shapeSelected.borderColor !== "transparent") tmctx.strokeRect(x, y, w * 2, h * 2);
if (currentObjectIterator < editor.currentObject.length) {
editor.currentObject[currentObjectIterator][1].origin = new Point(x,y);
editor.currentObject[currentObjectIterator][1].destination = new Point(p,q);
} else {
editor.currentObject.push([ii, new VectorRectangle(shapeSelected.borderWidth, shapeSelected.borderColor, shapeSelected.fillColor, new Point(x,y), new Point(p,q))]);
}
break;
case "VectorLine":
tmctx.beginPath();
tmctx.moveTo(x, y);
tmctx.lineTo(p, q); // lineTo = create a line position
if (currentObjectIterator < editor.currentObject.length) {
editor.currentObject[currentObjectIterator][1].origin = new Point(x,y);
editor.currentObject[currentObjectIterator][1].destination = new Point(p, q);
} else {
/* borderWidth, borderColor, fillColor, origin, destination */
editor.currentObject.push([ii, new VectorLine(shapeSelected.borderWidth, shapeSelected.borderColor, shapeSelected.fillColor, new Point(x,y), new Point(p,q))]);
}
tmctx.stroke();
break;
case "VectorEllipse":
tmctx.beginPath();
if(circleIsMoved) {
tmctx.arc(
x,
y,
new Point(x, y).distanceTo(new Point(p, q)),
0,
Math.PI * 2,
false
);
hRadius = new Point(x, y).distanceTo(new Point(p, q));
vRadius = hRadius;
}
else {
vRadius = 0;
for (i = 0; i < width; ++i) {
pathCircle = 2 - Math.pow((i - x) / (2 * w),2);
tmctx.lineTo(
i,
(2 * h) * Math.sqrt(pathCircle) + y
);
if (i <= x) {
vRadius = Math.abs((2 * h) * Math.sqrt(pathCircle));
}
if (Math.sqrt(pathCircle) > 0) {
hRadius = Math.abs(i-x);
}
}
for (i = width; i > 0; i -= 1) {
tmctx.lineTo(
i,
-1 * (2 * h) * Math.sqrt(2 - Math.pow(
(i - x) / (2 * w),
2
)) + y
);
}
}
if (currentObjectIterator < editor.currentObject.length) {
editor.currentObject[currentObjectIterator][1].origin = new Point(x,y);
editor.currentObject[currentObjectIterator][1].destination = new Point(p,q);
editor.currentObject[currentObjectIterator][1].hRadius = hRadius;
editor.currentObject[currentObjectIterator][1].vRadius = vRadius;
}
else {
editor.currentObject.push([ii, new VectorEllipse(shapeSelected.borderWidth, shapeSelected.borderColor, shapeSelected.fillColor, new Point(x,y), new Point(p,q), hRadius , vRadius)]);
}
tmctx.closePath();
tmctx.stroke();
tmctx.fill();
break;
case "VectorBrush": case "VectorClosedBrushPath":
tmctx.lineCap = "round";
tmctx.lineJoin = "round";
tmctx.beginPath();
tmctx.moveTo(moveBuffer[0][0], moveBuffer[0][1]); // first Point
for (i = 0; i < moveBuffer.length; ++i) {
tmctx.lineTo(moveBuffer[i][0], moveBuffer[i][1]);
}
if(currentObjectIterator < editor.currentObject.length) {
editor.currentObject[currentObjectIterator][1].origin = moveBuffer;
} else {
if(tool === 'VectorBrush') editor.currentObject.push([ii, new VectorBrush(shapeSelected.borderWidth, shapeSelected.borderColor, shapeSelected.fillColor, moveBuffer, null)]);
else editor.currentObject.push([ii, new VectorClosedBrushPath(shapeSelected.borderWidth, shapeSelected.borderColor, shapeSelected.fillColor, moveBuffer, null)]);
}
if(tool === 'VectorClosedBrushPath') {
tmctx.closePath();
if(shapeSelected.fillColor !== "transparent") tmctx.fill();
}
tmctx.stroke();
break;
case "VectorPolygon":
tmctx.lineCap = "round"; // "A rounded end cap is added to each end of the line"
tmctx.lineJoin = "round";
tmctx.beginPath();
tmctx.moveTo(moveBuffer[0][0], moveBuffer[0][1]);
for (i = 0; i < moveBuffer.length; ++i) {
tmctx.lineTo(moveBuffer[i][0], moveBuffer[i][1]);
}
if(currentObjectIterator < editor.currentObject.length) {
editor.currentObject[currentObjectIterator][1].origin = moveBuffer;
} else {
editor.currentObject.push([ii, new VectorPolygon(shapeSelected.borderWidth, shapeSelected.borderColor, shapeSelected.fillColor, moveBuffer, null)]);
}
tmctx.closePath();
if(shapeSelected.fillColor !== "transparent") tmctx.fill();
tmctx.stroke();
break;
default:
nop();
}
/* Save only one image */
editor.currentObject[currentObjectIterator][1].image.width = tmask.width;
editor.currentObject[currentObjectIterator][1].image.height = tmask.height;
editor.currentObject[currentObjectIterator][1].image.getContext('2d').drawImage(tmask, 0, 0);
mctx.drawImage(tmask, 0, 0);
tmctx.clearRect(0, 0, this.bounds.width(), this.bounds.height());
}
}
}
this.drawNew(false);
this.changed();
mctx.restore();
} else {
// traditional
switch (this.currentTool) {
case "selection":
if (!editor.vectorObjectsSelected.length) {
tmp = mctx.strokeStyle; // auxColor
mctx.strokeStyle = "black";
mctx.lineWidth = 1;
mctx.setLineDash([6]);
mctx.strokeRect(x, y, w * 2, h * 2);
mctx.strokeStyle = tmp;
mctx.setLineDash([]);
}
break;
case "rectangle":
if (this.isShiftPressed()) {
if(this.settings.secondarycolor !== "transparent") mctx.fillRect(x, y, newW() * 2, newH() * 2);
if(this.settings.primarycolor !== "transparent") mctx.strokeRect(x, y, newW() * 2, newH() * 2);
if (editor.currentObject) {
editor.currentObject.origin = new Point(x,y);
editor.currentObject.destination = new Point(x + newW() * 2, y + newH() * 2);
} else {
editor.currentObject = new VectorRectangle(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, new Point(x,y), new Point(x + newW() * 2, y + newH() * 2));
}
} else {
if(this.settings.secondarycolor !== "transparent") mctx.fillRect(x, y, w * 2, h * 2);
if(this.settings.primarycolor !== "transparent") mctx.strokeRect(x, y, w * 2, h * 2);
if (editor.currentObject) {
editor.currentObject.origin = new Point(x,y);
editor.currentObject.destination = new Point(p,q);
} else {
editor.currentObject = new VectorRectangle(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, new Point(x,y), new Point(p,q));
}
}
break;
case "brush": case "closedBrushPath":
/* Save each point in a VectorBrusher */
this.brush = function(isClosed) {
mctx.lineWidth = this.settings.linewidth;
mctx.fillStyle = this.settings.secondarycolor.toString();
mctx.strokeStyle = this.settings.primarycolor.toString();
mctx.lineCap = "round"; // "A rounded end cap is added to each end of the line"
mctx.lineJoin = "round";
mctx.beginPath();
mctx.moveTo(this.brushBuffer[0][0], this.brushBuffer[0][1]); // first Point
for (i = 0; i < this.brushBuffer.length; ++i) {
mctx.lineTo(this.brushBuffer[i][0], this.brushBuffer[i][1]);
}
if (editor.currentObject) {
editor.currentObject.origin = this.brushBuffer.slice();
} else {
editor.currentObject = new VectorBrush(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, this.brushBuffer.slice(), null);
}
if(isClosed) {
editor.currentObject = new VectorClosedBrushPath(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, this.brushBuffer.slice(), null);
mctx.closePath();
mctx.fill();
}
mctx.stroke();
}
this.brush(false);
break;
case "line":
mctx.beginPath();
mctx.moveTo(x, y);
if (this.isShiftPressed()) {
if (Math.abs(h) > Math.abs(w)) {
mctx.lineTo(x, q);
if (editor.currentObject) {
editor.currentObject.origin = new Point(x,y);
editor.currentObject.destination = new Point(x, q);
} else {
/* borderWidth, borderColor, fillColor, origin, destination */
editor.currentObject = new VectorLine(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, new Point(x,y), new Point(x, q));
}
} else {
mctx.lineTo(p, y); // lineTo = create a line position
if (editor.currentObject) {
editor.currentObject.origin = new Point(x,y);
editor.currentObject.destination = new Point(p, y);
} else {
/* borderWidth, borderColor, fillColor, origin, destination */
editor.currentObject = new VectorLine(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, new Point(x,y), new Point(p, y));
}
}
} else {
mctx.lineTo(p, q);
if (editor.currentObject) {
editor.currentObject.origin = new Point(x,y);
editor.currentObject.destination = new Point(p,q); // p & q
} else {
editor.currentObject = new VectorLine(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, new Point(x,y), relpos);
}
}
mctx.stroke();
break;
case "circle":
mctx.beginPath();
if (this.isShiftPressed()) {
/* http://www.w3schools.com/tags/canvas_arc.asp */
mctx.arc(
x,
y,
new Point(x, y).distanceTo(new Point(p, q)),
0,
Math.PI * 2,
false
);
hRadius = new Point(x, y).distanceTo(new Point(p, q)),
vRadius = hRadius;
if (editor.currentObject) {
editor.currentObject.origin = new Point(x,y);
editor.currentObject.destination = new Point(p,q);
editor.currentObject.hRadius = hRadius;
editor.currentObject.vRadius = vRadius;
}
else {
editor.currentObject = new VectorEllipse(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, new Point(x,y), new Point(p,q), hRadius, vRadius);
}
} else {
vRadius = 0;
for (i = 0; i < width; ++i) {
pathCircle = 2 - Math.pow((i - x) / (2 * w),2);
mctx.lineTo(
i,
(2 * h) * Math.sqrt(pathCircle) + y
);
if (i == x) {
vRadius = Math.abs((2 * h) * Math.sqrt(pathCircle));
}
if (Math.sqrt(pathCircle) > 0) {
hRadius = Math.abs(i-x);
}
}
for (i = width; i > 0; i -= 1) {
mctx.lineTo(
i,
-1 * (2 * h) * Math.sqrt(2 - Math.pow(
(i - x) / (2 * w),
2
)) + y
);
}
if (editor.currentObject) {
editor.currentObject.origin = new Point(x,y);
editor.currentObject.destination = new Point(p,q);
editor.currentObject.hRadius = hRadius;
editor.currentObject.vRadius = vRadius;
}
else {
editor.currentObject = new VectorEllipse(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, new Point(x,y), new Point(p,q), hRadius , vRadius);
}
}
mctx.closePath();
mctx.stroke();
mctx.fill();
break;
case "polygon":
if(!this.polygonBuffer.length) this.polygonBuffer.push([x,y]);
this.polygon = function(isClosedYet) {
mctx.lineWidth = this.settings.linewidth;
mctx.fillStyle = this.settings.secondarycolor.toString();
mctx.strokeStyle = this.settings.primarycolor.toString();
mctx.lineCap = "round"; // "A rounded end cap is added to each end of the line"
mctx.lineJoin = "round";
mctx.beginPath();
mctx.moveTo(this.polygonBuffer[0][0], this.polygonBuffer[0][1]);
for (i = 1; i < this.polygonBuffer.length; ++i) {
mctx.lineTo(this.polygonBuffer[i][0], this.polygonBuffer[i][1]);
}
if (editor.currentObject) {
/* Is it necessary? */
editor.currentObject.origin = this.polygonBuffer.slice();
} else {
editor.currentObject = new VectorPolygon(this.settings.linewidth, this.settings.primarycolor, this.settings.secondarycolor, this.polygonBuffer.slice(), null);
}
mctx.lineTo(p, q);
if(isClosedYet) {
editor.currentObject.origin = this.polygonBuffer.slice();
mctx.closePath();
mctx.fill();
}
mctx.stroke();
}
this.polygon(false);
break;
case "crosshairs":
this.rotationCenter = relpos.copy();
this.drawcrosshair(mctx);
break;
default:
nop();
break;
}
this.drawNew(true);
this.changed();
mctx.restore();
}
this.previousDragPoint = new Point(p,q);
};
VectorPaintCanvasMorph.prototype.mouseClickLeft = function () {
var selectionBounds,
editor = this.parentThatIsA(VectorPaintEditorMorph),
mctx = this.mask.getContext("2d");
function deselect() {
/* erase selection*/
editor.vectorObjectsSelected = [];
}
if (this.currentTool === "selection" && editor.currentObject === null) {
deselect();
mctx.save();
mctx.clearRect(0, 0, editor.bounds.width(), editor.bounds.height()); // clear dashed rectangle
this.drawNew();
this.changed();
mctx.restore();
selectionBounds = new VectorRectangle(null, null, null, this.dragRect.origin, this.previousDragPoint);
for (j = editor.vectorObjects.length-1; j >= 0; --j) {
if(editor.vectorObjects[j].isFound(selectionBounds)) {
mctx.save();
editor.vectorObjects[j].drawBoundingBox(mctx);
this.drawNew();
this.changed();
mctx.restore();
editor.vectorObjectsSelected.push(editor.vectorObjects[j]);
if(selectionBounds.origin.x === selectionBounds.destination.x
&& selectionBounds.origin.y === selectionBounds.destination.y) {
break;
}
}
}
}
else if ((this.currentTool === "selection" || this.currentTool === "paintbucket") && editor.currentObject !== null) {
editor.vectorObjectsSelected = [];
for (ii = editor.currentObject.length-1; ii >= 0; --ii) {
editor.vectorObjects.splice(editor.currentObject[ii][0],1);
editor.vectorObjects.splice(editor.currentObject[ii][0], 0, editor.currentObject[ii][1]); // splice(position, numberOfItemsToRemove, item)
if(this.currentTool !== "paintbucket") {
editor.vectorObjectsSelected.push(editor.vectorObjects[editor.currentObject[ii][0]]);
mctx.save();
editor.vectorObjects[editor.currentObject[ii][0]].drawBoundingBox(mctx);
this.drawNew();
this.changed();
mctx.restore();
}
}
editor.currentObject = null;
}
else if (this.currentTool === "closedBrushPath") {
this.brush(true);
this.drawNew();
this.changed();
mctx.restore();
}
if(this.currentTool === "duplicate") {
if(editor.currentObject === null) {
editor.vectorObjectsToDuplicate = [];
this.duplicateShape(this.dragRect.origin);
}
if(editor.currentObject !== null) {
for (ii = 0; ii < editor.currentObject.length; ++ii) {
editor.vectorObjects.push(editor.currentObject[ii][1]);
}
}
editor.currentObject = null;
deselect();
}
else if(this.currentTool === "polygon") {
if(this.polygonBuffer[0][0] === this.previousDragPoint.x &&
this.polygonBuffer[0][0] === this.previousDragPoint.y ||
this.polygonBuffer[this.polygonBuffer.length-1][0] === this.previousDragPoint.x &&
this.polygonBuffer[this.polygonBuffer.length-1][1] === this.previousDragPoint.y) {
this.polygon(true);
this.polygonBuffer.length = 0;
this.drawNew();
this.changed();
mctx.restore();
editor.vectorObjects.push(editor.currentObject);
editor.currentObject.image.width = this.mask.width;
editor.currentObject.image.height = this.mask.height;
editor.currentObject.image.getContext('2d').drawImage(this.mask, 0, 0);
editor.currentObject = null;
}
else {
this.polygonBuffer.push([this.previousDragPoint.x, this.previousDragPoint.y]);
}
}
else if (editor.currentObject !== null && this.currentTool !== "crosshairs"
&& this.currentTool !== "selection"
&& this.currentTool !== "paintbucket") {
editor.vectorObjects.push(editor.currentObject);
editor.currentObject.image.width = this.mask.width;
editor.currentObject.image.height = this.mask.height;
editor.currentObject.image.getContext('2d').drawImage(this.mask, 0, 0);
editor.currentObject = null;
deselect();
}
this.brushBuffer.length = 0;
}
///////////////////////// VectorCostume //////////////////////////////////////
// SVG_Costume does not have an init function and thus needs default dummy values
VectorCostume.prototype = new SVG_Costume(new Image(), '', new Point(0,0));
VectorCostume.prototype.constructor = VectorCostume;
VectorCostume.uber = SVG_Costume.prototype;
// VectorCostume instance creation
function VectorCostume(image, name, rotationCenter, vectorObjects) {
if (image && name && rotationCenter) {
this.contents = image;
this.shrinkToFit(this.maxExtent());
this.name = name || null;
this.rotationCenter = rotationCenter;
}
this.vectorObjects = vectorObjects ? vectorObjects : [];
this.version = Date.now(); // for observer optimization
this.loaded = null; // for de-serialization only
}
// VectorCostume duplication
VectorCostume.prototype.copy = function () {
var img = new Image(),
cpy,
myself = this;
img.src = this.contents.src;
cpy = new VectorCostume(img, this.name ? copy(this.name) : null, this.rotationCenter.copy());
this.vectorObjects.forEach(function(each) {
cpy.vectorObjects.push(each.copy());
});
return cpy;
};
VectorCostume.prototype.edit = function (aWorld, anIDE, isnew, oncancel, onsubmit) {
var myself = this,
editor = new VectorPaintEditorMorph();
editor.oncancel = oncancel || nop;
editor.openIn(
aWorld,
isnew ?
newCanvas(StageMorph.prototype.dimensions) :
this.contents,
isnew ?
new Point(240, 180) :
myself.rotationCenter,
function (img, rc, vectorObjects) {
myself.contents = img;
myself.rotationCenter = rc;
myself.vectorObjects = vectorObjects;
myself.version = Date.now();
aWorld.changed();
if (anIDE) {
if (isnew) { anIDE.currentSprite.addCostume(myself) };
anIDE.currentSprite.wearCostume(myself);
anIDE.hasChangedMedia = true;
}
(onsubmit || nop)();
},
anIDE,
this.vectorObjects ? this.vectorObjects : []
);
};
///////////////////////// Costume //////////////////////////////////////
Costume.prototype.edit = function (aWorld, anIDE, isnew, oncancel, onsubmit) {
var myself = this,
editor = new PaintEditorMorph();
editor.oncancel = oncancel || nop;
editor.openIn(
aWorld,
isnew ?
newCanvas(StageMorph.prototype.dimensions) :
this.contents,
isnew ?
new Point(240, 180) :
this.rotationCenter,
function (img, rc) {
myself.contents = img;
myself.rotationCenter = rc;
if (anIDE.currentSprite instanceof SpriteMorph) {
// don't shrinkwrap stage costumes
myself.shrinkWrap();
}
myself.version = Date.now();
aWorld.changed();
if (anIDE) {
if (isnew) { anIDE.currentSprite.addCostume(myself) };
anIDE.currentSprite.wearCostume(myself);
anIDE.hasChangedMedia = true;
}
(onsubmit || nop)();
},
anIDE
);
};
///////////////////////// CostumeIconMorph //////////////////////////////////////
CostumeIconMorph.prototype.editCostume = function () {
if (this.object instanceof SVG_Costume && typeof this.object.vectorObjects === 'undefined') {
this.object.editRotationPointOnly(this.world());
} else {
this.object.edit(
this.world(),
this.parentThatIsA(IDE_Morph)
);
}
};